Release both builds; Windows docs

This commit is contained in:
sarah committed 2026-10-09 14:33:35 +02:00
1 parent 6fe282f686
commit 9f99adc519
3 files changed
+76 -21

No files matched your search

+1 -1
View File
@@ -23,7 +23,7 @@ import tomlkit
AppName = "SaraSuite"
# Compared with the latest Gitea release to offer self-updates (SelfUpdate.py).
# Bump it, then run Packaging/Release.py, to publish a new SaraSuite.
AppVersion = "1.1.1"
AppVersion = "1.2.0"
IsFrozen = bool(getattr(sys, "frozen", False)) # running from the AppImage / .exe
IsWindows = sys.platform == "win32"
Platform = "Windows" if IsWindows else "Linux" # matches manifests' Platforms / Platform
+25 -16
View File
@@ -1,7 +1,9 @@
#!/usr/bin/env python3
"""Publish a SaraSuite release: build the AppImage, create the Gitea release
v<AppVersion> (from Core.py) with it attached, and copy it to the Shared
folder. Running SaraSuites then offer to update themselves (SelfUpdate.py).
"""Publish a SaraSuite release: build the AppImage (Linux) and SaraSuite.exe
(Windows, built under Wine by BuildExe.sh), create the Gitea release
v<AppVersion> (from Core.py) with both attached, and copy them to the Shared
folder. Running SaraSuites then offer to update themselves (SelfUpdate.py),
each to its own platform's file.
Packaging/Release.py # after bumping AppVersion in Core.py
Packaging/Release.py --notes "What changed"
@@ -25,7 +27,11 @@ from pathlib import Path
from urllib.parse import urlsplit
RepoDir = Path(__file__).resolve().parent.parent
AppImage = RepoDir / "dist" / "SaraSuite-x86_64.AppImage"
# Each running SaraSuite downloads the one for its own platform (SelfUpdate.AssetName).
Builds = [
("Packaging/BuildAppImage.sh", RepoDir / "dist" / "SaraSuite-x86_64.AppImage"),
("Packaging/BuildExe.sh", RepoDir / "dist" / "SaraSuite.exe"),
]
SharedDir = Path("/mnt/files/Shared/SaraSuite")
@@ -93,26 +99,29 @@ def Main() -> None:
Range = f"{Previous[0]}..HEAD" if Previous else "HEAD"
Notes = Git("log", "--format=- %s", Range) or "- First release with self-updates."
print(f"== Building SaraSuite {Version}")
subprocess.run(["Packaging/BuildAppImage.sh"], cwd=RepoDir, check=True,
env={**os.environ, "PATH": f"{Path.home()}/.local/bin:{os.environ['PATH']}"})
for Script, Output in Builds:
print(f"== Building {Output.name} {Version}")
subprocess.run([Script], cwd=RepoDir, check=True,
env={**os.environ, "PATH": f"{Path.home()}/.local/bin:{os.environ['PATH']}"})
print(f"== Creating release {Tag}")
Created = Api("POST", f"{ApiBase}/releases", TokenValue, json.dumps({
"tag_name": Tag, "target_commitish": Head, "name": f"SaraSuite {Version}", "body": Notes,
}).encode())
print(f"== Uploading {AppImage.name} ({AppImage.stat().st_size // 1_000_000} MB)")
Boundary = uuid.uuid4().hex
Body = (
f"--{Boundary}\r\nContent-Disposition: form-data; name=\"attachment\"; filename=\"{AppImage.name}\"\r\n"
f"Content-Type: application/octet-stream\r\n\r\n"
).encode() + AppImage.read_bytes() + f"\r\n--{Boundary}--\r\n".encode()
Api("POST", f"{ApiBase}/releases/{Created['id']}/assets?name={AppImage.name}", TokenValue, Body,
f"multipart/form-data; boundary={Boundary}")
for _Script, Output in Builds:
print(f"== Uploading {Output.name} ({Output.stat().st_size // 1_000_000} MB)")
Boundary = uuid.uuid4().hex
Body = (
f"--{Boundary}\r\nContent-Disposition: form-data; name=\"attachment\"; filename=\"{Output.name}\"\r\n"
f"Content-Type: application/octet-stream\r\n\r\n"
).encode() + Output.read_bytes() + f"\r\n--{Boundary}--\r\n".encode()
Api("POST", f"{ApiBase}/releases/{Created['id']}/assets?name={Output.name}", TokenValue, Body,
f"multipart/form-data; boundary={Boundary}")
if SharedDir.is_dir():
shutil.copy2(AppImage, SharedDir / AppImage.name)
for _Script, Output in Builds:
shutil.copy2(Output, SharedDir / Output.name)
print(f"== Copied to {SharedDir}")
Git("fetch", "--quiet", "--tags", "origin") # the new tag, for the next release's notes
print(f"== Released {Tag}: {Origin.scheme}://{Origin.netloc}/{Owner}/{Repo}/releases/tag/{Tag}")
+50 -4
View File
@@ -78,11 +78,34 @@ an option off takes out what it placed and puts back any file it had replaced; t
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\<Name>.pid` |
| 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:
@@ -98,6 +121,8 @@ places it. It reapplies the installed commit, so the program itself isn't update
## Where things live
On Linux (on Windows, the same layout under `%LOCALAPPDATA%` and `%APPDATA%`; see Windows above):
| Path | What |
| --- | --- |
| `~/.local/share/SaraSuite/Programs/<Name>/` | installed program (`App/`) and its `.venv/` |
@@ -115,13 +140,20 @@ Manifests are bundled into the AppImage, so a new or changed manifest means rebu
Anything the manifest can't describe goes in an optional `Programs/<Name>/Hooks.py`.
Targets, templates and `Set` values can use `{Variables}`: `Name`, `GitHost`, `Home`, `ConfigHome`,
`DataHome`, `CacheHome`, `SystemdUserDir`, `ApplicationsDir`, `DBusServicesDir`, `ProgramDir`,
`AppDir`, `Python` (the venv's python), `ConfigDir`, `DesktopName` (e.g. `COSMIC`),
`Desktop` (e.g. `cosmic`), plus every Question's `Id`.
`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
@@ -161,10 +193,24 @@ Option = "Autostart" # optional: only while the option is ticked
Root = false # true: outside $HOME, written through pkexec
Executable = false
[[Services]] # systemd --user units, enabled + started
[[Services]] # Linux: systemd --user units, enabled + started
Platform = "Linux"
Unit = "Example.service"
Option = "Autostart"
[[Services]] # Windows: run by ServiceRun.py from a Startup-folder shortcut
Platform = "Windows" # (also 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"