# Milestone 10: Device-Side App Management **Status**: Decided **Goal**: Install, update, and manage apps on Mosis devices. ## Decision **C++ AppManager + Lua bindings** running on MosisService: ``` AppManager: C++ class managing installation/updates Storage: Local device storage (/data/mosis/apps/) Updates: Background service checking Portal API UI: App Store system app (RML/Lua) API: Connects to Portal at portal.mosis.dev (or self-hosted) ``` ### Rationale 1. **Native C++** - AppManager runs in MosisService process for performance 2. **Background updates** - UpdateService thread checks Portal API periodically 3. **System app** - App Store is a privileged RML/Lua app with special permissions 4. **Ed25519 verification** - All packages verified before installation ### API Integration ``` Device Portal (Synology NAS) ┌──────────────┐ ┌──────────────────────┐ │ MosisService │ │ mosis-portal │ │ │ │ │ │ UpdateService├──────GET /store/apps────►│ Chi API Router │ │ │ /updates?pkgs=... │ │ │ │◄─────{updates: [...]}───┤ SQLite portal.db │ │ │ │ │ │ AppManager ├──────GET /packages/...──►│ /volume1/mosis/ │ │ │◄─────[package.mosis]────┤ packages/{dev}/... │ └──────────────┘ └──────────────────────┘ ``` --- ## Overview Device-side app management handles the full lifecycle of apps on user devices: discovery, installation, updates, and removal. --- ## Components ``` ┌─────────────────────────────────────────────────────────────┐ │ MosisService │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ │ │ AppManager │ │ UpdateService │ │ AppStore UI │ │ │ │ (C++ class) │ │ (Background) │ │ (System App) │ │ │ └───────────────┘ └───────────────┘ └───────────────┘ │ │ │ │ │ │ │ └───────────────────┼───────────────────┘ │ │ │ │ │ ┌────────┴────────┐ │ │ │ LuaSandboxManager│ │ │ └─────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ ``` --- ## Storage Layout ``` /data/mosis/ ├── config/ │ ├── device.json # Device ID, settings │ └── apps.json # Installed apps registry ├── apps/ │ └── {package_id}/ │ ├── package/ # Extracted app files │ │ ├── manifest.json │ │ └── assets/ │ ├── data/ # App data (VirtualFS) │ ├── cache/ # App cache │ └── db/ # SQLite databases ├── downloads/ # Temp download location └── backups/ # App data backups (before update) ``` --- ## AppManager Class ### Interface ```cpp namespace mosis { struct InstalledApp { std::string package_id; std::string name; std::string version_name; int version_code; std::string install_path; std::vector permissions; std::chrono::system_clock::time_point installed_at; std::chrono::system_clock::time_point updated_at; int64_t package_size; int64_t data_size; }; struct InstallProgress { enum class Stage { Downloading, Verifying, Extracting, Registering, Complete, Failed }; Stage stage; float progress; // 0.0 - 1.0 std::string error; }; using ProgressCallback = std::function; class AppManager { public: explicit AppManager(const std::string& data_root); ~AppManager(); // Installation bool Install(const std::string& package_url, const std::string& signature, ProgressCallback callback); bool InstallFromFile(const std::string& package_path, ProgressCallback callback); // Uninstallation bool Uninstall(const std::string& package_id, bool keep_data = false); // Updates bool Update(const std::string& package_id, const std::string& package_url, const std::string& signature, ProgressCallback callback); // Queries std::vector GetInstalledApps() const; std::optional GetApp(const std::string& package_id) const; bool IsInstalled(const std::string& package_id) const; // Data management int64_t GetAppDataSize(const std::string& package_id) const; bool ClearAppData(const std::string& package_id); bool ClearAppCache(const std::string& package_id); bool BackupAppData(const std::string& package_id); bool RestoreAppData(const std::string& package_id); // Integration with sandbox void SetSandboxManager(LuaSandboxManager* manager); private: std::string m_data_root; LuaSandboxManager* m_sandbox_manager = nullptr; mutable std::mutex m_mutex; std::map m_installed_apps; bool VerifyPackage(const std::string& path, const std::string& signature); bool ExtractPackage(const std::string& path, const std::string& dest); void LoadInstalledApps(); void SaveInstalledApps(); }; } // namespace mosis ``` ### Implementation ```cpp bool AppManager::Install(const std::string& package_url, const std::string& signature, ProgressCallback callback) { callback({InstallProgress::Stage::Downloading, 0.0f, ""}); // Download package std::string download_path = m_data_root + "/downloads/" + GenerateUUID(); if (!DownloadFile(package_url, download_path, [&](float p) { callback({InstallProgress::Stage::Downloading, p, ""}); })) { callback({InstallProgress::Stage::Failed, 0.0f, "Download failed"}); return false; } callback({InstallProgress::Stage::Verifying, 0.0f, ""}); // Verify signature if (!VerifyPackage(download_path, signature)) { std::filesystem::remove(download_path); callback({InstallProgress::Stage::Failed, 0.0f, "Signature verification failed"}); return false; } // Extract manifest to get package_id auto manifest = ExtractManifest(download_path); if (!manifest) { std::filesystem::remove(download_path); callback({InstallProgress::Stage::Failed, 0.0f, "Invalid manifest"}); return false; } callback({InstallProgress::Stage::Extracting, 0.0f, ""}); // Check if already installed (update path) std::string install_path = m_data_root + "/apps/" + manifest->package_id; if (std::filesystem::exists(install_path + "/package")) { // Backup existing data BackupAppData(manifest->package_id); // Remove old package std::filesystem::remove_all(install_path + "/package"); } // Extract package std::filesystem::create_directories(install_path + "/package"); if (!ExtractPackage(download_path, install_path + "/package")) { callback({InstallProgress::Stage::Failed, 0.0f, "Extraction failed"}); return false; } // Clean up download std::filesystem::remove(download_path); callback({InstallProgress::Stage::Registering, 0.0f, ""}); // Create data directories std::filesystem::create_directories(install_path + "/data"); std::filesystem::create_directories(install_path + "/cache"); std::filesystem::create_directories(install_path + "/db"); // Register app InstalledApp app{ .package_id = manifest->package_id, .name = manifest->name, .version_name = manifest->version, .version_code = manifest->version_code, .install_path = install_path, .permissions = manifest->permissions, .installed_at = std::chrono::system_clock::now(), .updated_at = std::chrono::system_clock::now(), .package_size = std::filesystem::file_size(download_path) }; { std::lock_guard lock(m_mutex); m_installed_apps[manifest->package_id] = app; SaveInstalledApps(); } callback({InstallProgress::Stage::Complete, 1.0f, ""}); return true; } bool AppManager::Uninstall(const std::string& package_id, bool keep_data) { std::lock_guard lock(m_mutex); auto it = m_installed_apps.find(package_id); if (it == m_installed_apps.end()) { return false; } // Stop app if running if (m_sandbox_manager && m_sandbox_manager->IsAppRunning(package_id)) { m_sandbox_manager->StopApp(package_id); } // Remove files std::string install_path = it->second.install_path; std::filesystem::remove_all(install_path + "/package"); std::filesystem::remove_all(install_path + "/cache"); if (!keep_data) { std::filesystem::remove_all(install_path + "/data"); std::filesystem::remove_all(install_path + "/db"); std::filesystem::remove_all(install_path); } // Unregister m_installed_apps.erase(it); SaveInstalledApps(); return true; } ``` --- ## Update Service ### Background Update Checker ```cpp class UpdateService { public: UpdateService(AppManager* app_manager, const std::string& api_base); // Start background checking void Start(std::chrono::hours interval = std::chrono::hours(24)); void Stop(); // Manual check std::vector CheckForUpdates(); // Download and install update bool ApplyUpdate(const std::string& package_id, ProgressCallback callback); // Settings void SetAutoUpdate(bool enabled); void SetWifiOnly(bool wifi_only); private: void CheckLoop(); AppManager* m_app_manager; std::string m_api_base; std::thread m_check_thread; std::atomic m_running{false}; bool m_auto_update = false; bool m_wifi_only = true; }; ``` ### Update Check Flow ``` 1. Get list of installed apps 2. Call API: GET /store/apps/updates?packages=com.a,com.b,com.c 3. API returns list of available updates 4. If auto-update enabled and on WiFi: - Download and install in background - Notify user of completed updates 5. If manual: - Show notification with update count - User opens App Store to review ``` --- ## App Store System App ### UI Screens ``` Home ├── Featured apps ├── Categories ├── Search └── My Apps ├── Installed ├── Updates available └── Previously installed App Detail ├── Icon, name, developer ├── Screenshots ├── Description ├── Permissions ├── Reviews (future) └── [Install] / [Update] / [Open] Settings ├── Auto-update (WiFi only) ├── Storage usage └── Clear all caches ``` ### RML Structure ```xml