# SaraSuite Installs, updates and removes Sara programs (SaraSortd, SaraSort, SaraFem, SaraTag and the SaraView desktop client). It's one AppImage file: download it, run it, pick a program. - **Install**: downloads the program's latest commit from its git repo, copies it into `~/.local/share/SaraSuite/Programs//` with its own Python venv, places its configs in `~/.config/SaraSuite//`, and writes its desktop entries, systemd user units, D-Bus services and so on. Without systemd (runit on Void, OpenRC, s6, dinit, ...), background services run the way they do on Windows: see the table below. - **Update**: shown when the repo has a newer commit. Program files and integration files are replaced; configs are *merged*: your edits always win, defaults you never touched follow the new version, new keys are added (see `ConfigMerge.py`). - **Remove**: takes out everything the install placed (it's all recorded in a receipt) plus everything the program made by itself while running. Configs are only removed when "Remove configs" is ticked. Your own files are never removed: folders that might hold them are only removed when empty. Files that existed before SaraSuite installed over them are put back. ## Sources Programs come straight from git over HTTPS, no `git` binary needed: the latest commit is read from `.git/info/refs` and the files from `/archive/.tar.gz`, which GitHub and Gitea both serve the same way. Repos must be public (no login support yet). The host is one setting in `~/.config/SaraSuite/SaraSuite/Config.toml`; manifests refer to it as `{GitHost}`. It defaults to the Gitea on SpineS: ```toml GitHost = "https://git.srhtnin.garden/sarah" ``` The only thing a program needs from the system is `python3` (for its venv). ## Updates - **Programs** update from their own repos (see Sources above). - **The program list** (`Programs/`: manifests, templates, icons) is fetched from this repo whenever it has a new commit, so adding a program or fixing a recipe only takes a push. It's cached in `~/.cache/SaraSuite/Catalog/`; the AppImage's bundled copy is the fallback. Running from source always uses the local `Programs/`. - **SaraSuite itself** checks this repo's latest Gitea release. When it's newer than its own `AppVersion`, an "Update SaraSuite" button downloads the new AppImage, swaps it in place (same path, so the app menu entry keeps working) and restarts. From a terminal: `SaraSuite-x86_64.AppImage self-update`. To publish a new SaraSuite: bump `AppVersion` in `Core.py`, commit and push, then run `Packaging/Release.py` (optionally `--notes "..."`). It builds the AppImage, creates the release `v` with the AppImage attached, and copies it to the Shared folder. The Gitea token comes from git's credential store, the same one `git push` uses. ## Shared settings Settings every SaraSuite program shares live in `~/.config/SaraSuite/Config.toml`: ```toml Theme = "CatppuccinMocha" # a file in ~/.config/SaraSuite/Themes/ FontFamily = "Fixedsys Excelsior" # the bundled font, or any installed font family HideWindowTitleBar = false ``` Each program's own `~/.config/SaraSuite//Config.toml` has the same three keys plus `UseSharedSettings` (default `true`). With it on, the program reads the shared file; with it off, it uses its own values instead. Both SaraSuite (the gear button, Shared and SaraSuite tabs) and SaraFem (its Settings, SaraSuite and General tabs) can edit them. Whichever program starts first creates the shared file from its own values. ## SaraSuite's own settings The gear button also holds the git host and, in one tab per program, what that program's install dialog starts with: which option boxes are ticked and the default answers (e.g. SaraSortd's root folder). They're stored in `~/.config/SaraSuite/SaraSuite/Config.toml` as `[OptionDefaults.]` and `[AnswerDefaults.]`, and override the manifest's defaults. ## Changing options after installing Installed programs with options (e.g. SaraFem's file picker) get an **Options** button. Turning an option off takes out what it placed and puts back any file it had replaced; turning it on places it. It reapplies the installed commit, so the program itself isn't updated. The **Update** button only shows up when there's an update (or a broken install to repair). ## Windows `SaraSuite.exe` is the Windows version. Each build only lists the programs for its own system (a manifest's `Platforms`), and does everything the Windows way: | Linux | Windows | | --- | --- | | `~/.config/SaraSuite/` (settings) | `%APPDATA%\SaraSuite\` | | `~/.local/share/SaraSuite/` (programs, receipts) | `%LOCALAPPDATA%\SaraSuite\` | | `~/.cache/...` | `%LOCALAPPDATA%\...` | | desktop entry | Start Menu shortcut (`[[Shortcuts]]`) | | systemd user service | `ServiceRun.py`, started at login by a shortcut in the Startup folder; its PID is in `%LOCALAPPDATA%\SaraSuite\Services\.pid` | Linux without systemd (no `/run/systemd/system`: runit on Void, OpenRC, s6, dinit, ...) runs services like Windows does (`Services.py`): `ServiceRun.py` runs them, started at login by an XDG autostart entry, `~/.config/autostart/.desktop`, which desktop sessions start whatever the init system is. It restarts a service 10 seconds after it fails, like the unit's `Restart=on-failure`, and keeps its output in `~/.local/share/SaraSuite/Services/.log` (its PID next to it, in `.pid`). Units aren't written there, and `RestartUnits` stops the process instead (D-Bus starts `xdg-desktop-portal` again when it's needed). A window manager that doesn't run XDG autostart entries needs to start them itself (e.g. `dex -a` in its startup config). | system `python3` | the `py` launcher / `python.exe`; if there's none, SaraSuite installs Python for the user with winget | | AppImage self-update (rename over it) | `.exe` self-update (the running one is renamed to `SaraSuite.old.exe`, deleted on the next start) | Programs run on Windows with `pythonw.exe -X utf8` (no console window; UTF-8 mode, since Python on Windows otherwise reads text files in the old ANSI code page). Nothing needs admin rights. Linux-only features (SaraFem's file picker portal and default-file-manager option) simply aren't offered there. ## Running and building - AppImage: `Packaging/BuildAppImage.sh` builds `dist/SaraSuite-x86_64.AppImage` (PyInstaller one-file build + `appimagetool`, like SaraFem's). When run as an AppImage, the header has an "Add to app menu" button. - Windows: `Packaging/BuildExe.sh` builds `dist/SaraSuite.exe` with the Windows Python inside a Wine prefix (`build/WinBuild/`, set up on its first run), since PyInstaller can't build for Windows from Linux directly. The .exe's header has an "Add to Start menu" button. - From source: `python3 -m venv .venv && .venv/bin/pip install -r requirements.txt`, then `Packaging/Launch.sh`. - Without a desktop (e.g. a server), the same file works from a terminal: ``` SaraSuite-x86_64.AppImage list SaraSuite-x86_64.AppImage install SaraSortd --set RootDir=~/Sorting --option Autostart=on SaraSuite-x86_64.AppImage update SaraSortd SaraSuite-x86_64.AppImage options SaraFem # list them SaraSuite-x86_64.AppImage options SaraFem --option FilePicker=off SaraSuite-x86_64.AppImage remove SaraSortd [--configs] [--keep-runtime] ``` ## Where things live On Linux (on Windows, the same layout under `%LOCALAPPDATA%` and `%APPDATA%`; see Windows above): | Path | What | | --- | --- | | `~/.local/share/SaraSuite/Programs//` | installed program (`App/`) and its `.venv/` | | `~/.local/share/SaraSuite/Receipts/.toml` | everything the install placed | | `~/.local/share/SaraSuite/Pristine//` | the default configs as last installed (the merge base) | | `~/.local/share/SaraSuite/Backups//` | files that were moved aside, and configs from before a merge | | `~/.cache/SaraSuite/Downloads/` | downloaded source, removed after installing | | `~/.config/SaraSuite//` | the program's configs | ## Adding a program Make `Programs//Manifest.toml`. Templates go in `Programs//Templates/`. Manifests are bundled into the AppImage, so a new or changed manifest means rebuilding it (installed programs then show an update, even without a new commit). Anything the manifest can't describe goes in an optional `Programs//Hooks.py`. Targets, templates and `Set` values can use `{Variables}`: `Name`, `GitHost`, `Home`, `ConfigHome`, `DataHome`, `CacheHome`, `ProgramDir`, `AppDir`, `Python` (the venv's python), `PythonW` (Windows: its console-less pythonw.exe), `ConfigDir`, `DesktopName` (e.g. `COSMIC`), `Desktop` (e.g. `cosmic`), plus every Question's `Id`. Linux only: `SystemdUserDir`, `ApplicationsDir`, `DBusServicesDir`. Windows only: `StartMenuDir`, `StartupDir`. Every list item (`[[Options]]`, `[[Templates]]`, `[[Services]]`, ...) can carry `Platform = "Linux"` or `"Windows"` to only apply on that system; items without one apply on both. In TOML templates, write paths as literal strings (`'{ConfigDir}'`): Windows paths are full of backslashes, which a `"..."` string would read as escapes. ```toml Name = "Example" Description = "Shown on the program's card." Platforms = ["Linux", "Windows"] # where it runs (default ["Linux"]) Parent = "SaraSortd" # optional: listed (indented) under that program RestartUnits = ["xdg-desktop-portal.service"] # try-restarted after install/update/remove [Version] # optional, shown on the card File = "Example.py" Pattern = 'Version = "([^"]+)"' [Source] Git = "{GitHost}/Example" Branch = "main" # optional, default: the repo's default branch Include = ["*.py", "assets/**"] # default: everything Exclude = ["Packaging"] # .git, .venv, build, ... are always excluded [Python] RequirementsFile = "requirements.txt" # or: Requirements = ["toml"] [[Questions]] # asked at install time, usable as {RootDir} Id = "RootDir" Prompt = "Folder to use" Default = "~/Somewhere" Kind = "Path" # expanded to an absolute path [[Options]] # checkboxes at install time Id = "Autostart" Label = "Start on login" Default = true [[Configs]] # placed once, merged on updates Source = "Conf.toml" # in the source checkout Target = "{ConfigDir}/Conf.toml" Set = { "Paths.RootDir" = "{RootDir}" } [[Templates]] # owned by SaraSuite, rewritten on every update Source = "Example.service" # in Templates/ Target = "{SystemdUserDir}/Example.service" Option = "Autostart" # optional: only while the option is ticked Root = false # true: outside $HOME, written through pkexec Executable = false [[Services]] # Linux: a systemd --user unit, enabled + started; without Platform = "Linux" # systemd, Name/Script/WorkingDir run by ServiceRun.py instead, Unit = "Example.service" # started at login by an autostart entry Name = "Example" Script = "Example.py" WorkingDir = "{ConfigDir}" Option = "Autostart" [[Services]] # Windows: run by ServiceRun.py from a Startup-folder shortcut Platform = "Windows" # (both: add ../../ServiceRun.py as a Template to {ProgramDir}) Name = "Example" Script = "Example.py" # relative to the app folder WorkingDir = "{ConfigDir}" Option = "Autostart" [[Shortcuts]] # Windows: a Start Menu shortcut (pythonw.exe -X utf8 Script) Platform = "Windows" Name = "Example" Script = "Main.py" # relative to the app folder, which is also its working folder Icon = "assets/Icon.png" # turned into an .ico [[IniEdits]] # one key in a file SaraSuite doesn't own; restored on removal File = "{ConfigHome}/mimeapps.list" Section = "Default Applications" Key = "inode/directory" Value = "Example.desktop" [[Leftovers]] # made by the program itself, only touched on removal Path = "{CacheHome}/Example" # globs allowed Kind = "Runtime" # or "Config" Mode = "Remove" # or "IfEmpty" Note = "thumbnail cache" ``` `Hooks.py` can define `Leftovers(Context: dict) -> list[Manifests.Leftover]` for leftovers that depend on the program's own config (see `Programs/SaraSortd/Hooks.py`).