diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5a209f2..0e73024 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -111,6 +111,17 @@ jobs: path: release-assets merge-multiple: true + # The installers carry no application: each downloads the newest release + # when the user runs it. The macOS one is zipped because a release asset + # loses its executable bit, which a .command needs to be double-clicked. + - name: Collect installers + run: | + cp installers/maturita-installer-linux.sh release-assets/ + cp installers/maturita-installer-windows.cmd release-assets/ + chmod +x installers/maturita-installer-macos.command + (cd installers && zip -q ../release-assets/maturita-installer-macos.zip \ + maturita-installer-macos.command) + - name: List assets run: find release-assets -type f -print @@ -121,6 +132,9 @@ jobs: release-assets/maturita-linux-*.AppImage release-assets/maturita-windows-x64.zip release-assets/maturita-macos-*.zip + release-assets/maturita-installer-macos.zip + release-assets/maturita-installer-windows.cmd + release-assets/maturita-installer-linux.sh generate_release_notes: true fail_on_unmatched_files: true env: diff --git a/.gitignore b/.gitignore index 7e776a5..dfe1514 100644 --- a/.gitignore +++ b/.gitignore @@ -63,6 +63,9 @@ assets/icons/ # Kernel Module Compile Results *.mod* *.cmd +# The rule above targets kernel build artifacts such as .foo.o.cmd; the +# Windows installer is a source file that has to be tracked. +!installers/*.cmd .tmp_versions/ modules.order Module.symvers diff --git a/README.md b/README.md index 3ebafd2..351fd3e 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,24 @@ are required: Progress and settings are saved next to the AppImage / `.app` / unzipped folder in a `progress/` directory. +#### Installers + +Each release also ships a small installer that contains no application of its +own: it downloads the newest build when you run it, so the file never goes out +of date. Once installed, the app keeps itself up to date on its own — it checks +shortly after startup and offers the update in a banner, and there is a manual +check under **Settings → Updates**. + +| Platform | Asset | How to run | +| -------- | ----- | ---------- | +| macOS (Apple Silicon) | `maturita-installer-macos.zip` | Unzip, then double-click the `.command` file | +| Windows (x64) | `maturita-installer-windows.cmd` | Double-click the file | +| Linux (x86_64) | `maturita-installer-linux.sh` | `sh maturita-installer-linux.sh` | + +The builds are not code-signed, so the first launch needs one extra step: +on macOS right-click the installer and pick **Open**, and on Windows choose +**More info → Run anyway** if SmartScreen appears. + To cut a release yourself (needs a GitHub remote with Actions enabled): ```bash @@ -600,6 +618,24 @@ při pushi tagu `v*`. Kompilátor ani vývojové balíčky GTK nejsou potřeba: Postup a nastavení se ukládají vedle AppImage / `.app` / rozbalené složky do adresáře `progress/`. +#### Instalátory + +Ke každému releasu vychází i malý instalátor, který samotnou aplikaci +neobsahuje: nejnovější build si stáhne až při spuštění, takže soubor nikdy +nezestárne. Nainstalovaná aplikace se pak udržuje aktuální sama — chvíli po +startu zkontroluje nové verze a nabídne je v pruhu nahoře, ruční kontrola je +v **Nastavení → Aktualizace**. + +| Platforma | Soubor | Spuštění | +| --------- | ------ | -------- | +| macOS (Apple Silicon) | `maturita-installer-macos.zip` | Rozbalit a dvojklik na soubor `.command` | +| Windows (x64) | `maturita-installer-windows.cmd` | Dvojklik na soubor | +| Linux (x86_64) | `maturita-installer-linux.sh` | `sh maturita-installer-linux.sh` | + +Buildy nejsou podepsané, takže první spuštění chce jeden krok navíc: na macOS +klikněte na instalátor pravým tlačítkem a zvolte **Otevřít**, na Windows při +hlášce SmartScreenu **Další informace → Přesto spustit**. + Vytvoření releasu (GitHub remote s Actions): ```bash diff --git a/installers/maturita-installer-linux.sh b/installers/maturita-installer-linux.sh new file mode 100755 index 0000000..a72c6e3 --- /dev/null +++ b/installers/maturita-installer-linux.sh @@ -0,0 +1,79 @@ +#!/bin/sh +# maturita.c installer for Linux. +# +# Carries no application of its own: it downloads whatever the newest release +# is at the moment you run it, so this file never goes stale. GitHub resolves +# the /releases/latest/download/ path to the current tag on every request. +# +# Run it with: sh maturita-installer-linux.sh +# The app updates itself from then on. + +set -eu + +REPO="pepaskopec-blip/maturita.c" +ASSET="maturita-linux-x86_64.AppImage" +URL="https://github.com/$REPO/releases/latest/download/$ASSET" + +# ~/Applications is where desktop environments look for AppImages, and the app +# keeps its progress/ directory next to its own file. +DEST_DIR="$HOME/Applications" +DEST="$DEST_DIR/maturita.AppImage" +DESKTOP_DIR="$HOME/.local/share/applications" +ICON_DIR="$HOME/.local/share/icons/hicolor/512x512/apps" + +say() { printf '%s\n' "$*"; } +die() { printf '\nError: %s\n' "$*" >&2; exit 1; } + +say "Installing maturita.C" +say "---------------------" + +arch=$(uname -m) +if [ "$arch" != "x86_64" ]; then + die "only x86_64 builds are published, but this machine reports '$arch'. +Build from source instead: https://github.com/$REPO" +fi + +command -v curl >/dev/null 2>&1 || die "curl was not found. Install it first." + +mkdir -p "$DEST_DIR" + +tmp=$(mktemp -d) +trap 'rm -rf "$tmp"' EXIT INT TERM + +say "Downloading the latest release..." +curl -fL --progress-bar -o "$tmp/$ASSET" "$URL" || + die "the download failed. Check your internet connection." + +say "Installing to $DEST" +chmod +x "$tmp/$ASSET" +mv -f "$tmp/$ASSET" "$DEST" || die "could not write to $DEST_DIR." + +# A menu entry needs the icon as a separate file, which the AppImage can +# unpack without FUSE. Losing the icon is not worth failing the install over. +if (cd "$tmp" && "$DEST" --appimage-extract \ + 'usr/share/icons/hicolor/512x512/apps/maturita.png' >/dev/null 2>&1); then + icon="$tmp/squashfs-root/usr/share/icons/hicolor/512x512/apps/maturita.png" + if [ -f "$icon" ]; then + mkdir -p "$ICON_DIR" + cp -f "$icon" "$ICON_DIR/maturita.png" + fi +fi + +mkdir -p "$DESKTOP_DIR" +cat > "$DESKTOP_DIR/maturita.desktop" </dev/null 2>&1 && + update-desktop-database "$DESKTOP_DIR" >/dev/null 2>&1 || true + +say "" +say "Done. Starting the app..." +"$DEST" >/dev/null 2>&1 & diff --git a/installers/maturita-installer-macos.command b/installers/maturita-installer-macos.command new file mode 100755 index 0000000..b5f79d1 --- /dev/null +++ b/installers/maturita-installer-macos.command @@ -0,0 +1,74 @@ +#!/bin/sh +# maturita.c installer for macOS. +# +# Carries no application of its own: it downloads whatever the newest release +# is at the moment you run it, so this file never goes stale. GitHub resolves +# the /releases/latest/download/ path to the current tag on every request. +# +# Double-click the file to run it. The app updates itself from then on. + +set -eu + +REPO="pepaskopec-blip/maturita.c" +ASSET="maturita-macos-arm64.zip" +URL="https://github.com/$REPO/releases/latest/download/$ASSET" + +say() { printf '%s\n' "$*"; } +die() { printf '\nError: %s\n' "$*" >&2; printf 'Press Return to close. '; read -r _; exit 1; } + +say "Installing maturita.C" +say "---------------------" + +# Only Apple Silicon builds are published; Rosetta cannot run an arm64 app. +arch=$(uname -m) +if [ "$arch" != "arm64" ]; then + die "this build is for Apple Silicon Macs, but this one reports '$arch'. +Build from source instead: https://github.com/$REPO" +fi + +command -v curl >/dev/null 2>&1 || die "curl was not found." + +# Prefer the shared folder, fall back to the user's own when it is locked down. +if [ -w /Applications ]; then + dest_dir="/Applications" +else + dest_dir="$HOME/Applications" + mkdir -p "$dest_dir" +fi + +tmp=$(mktemp -d) +trap 'rm -rf "$tmp"' EXIT INT TERM + +say "Downloading the latest release..." +curl -fL --progress-bar -o "$tmp/$ASSET" "$URL" || + die "the download failed. Check your internet connection." + +say "Unpacking..." +mkdir -p "$tmp/new" +# ditto keeps bundle symlinks and permissions intact, unlike plain unzip. +ditto -x -k "$tmp/$ASSET" "$tmp/new" || die "the archive could not be unpacked." + +app=$(find "$tmp/new" -maxdepth 1 -name '*.app' -print -quit) +[ -n "$app" ] || die "the archive did not contain an application." +name=$(basename "$app") + +say "Installing to $dest_dir/$name" +rm -rf "$dest_dir/$name.old" +if [ -e "$dest_dir/$name" ]; then + mv "$dest_dir/$name" "$dest_dir/$name.old" +fi +if mv "$app" "$dest_dir/$name"; then + rm -rf "$dest_dir/$name.old" +else + # Put the previous copy back rather than leaving nothing installed. + [ -e "$dest_dir/$name.old" ] && mv "$dest_dir/$name.old" "$dest_dir/$name" + die "could not write to $dest_dir." +fi + +# The download carries a quarantine flag, and the app is not signed, so +# without this macOS refuses to open it. +xattr -cr "$dest_dir/$name" 2>/dev/null || true + +say "" +say "Done. Starting the app..." +open "$dest_dir/$name" diff --git a/installers/maturita-installer-windows.cmd b/installers/maturita-installer-windows.cmd new file mode 100644 index 0000000..661e286 --- /dev/null +++ b/installers/maturita-installer-windows.cmd @@ -0,0 +1,80 @@ +@echo off +rem maturita.c installer for Windows. +rem +rem Carries no application of its own: it downloads whatever the newest release +rem is at the moment you run it, so this file never goes stale. GitHub resolves +rem the /releases/latest/download/ path to the current tag on every request. +rem +rem Double-click the file to run it. The app updates itself from then on. + +setlocal +set "REPO=pepaskopec-blip/maturita.c" +set "ASSET=maturita-windows-x64.zip" +set "URL=https://github.com/%REPO%/releases/latest/download/%ASSET%" +set "DEST=%LOCALAPPDATA%\Programs\Maturita" + +echo Installing maturita.C +echo --------------------- + +rem curl and tar both ship with Windows 10 1803 and later. +where curl >nul 2>&1 || goto :no_tools +where tar >nul 2>&1 || goto :no_tools + +set "TMP_DIR=%TEMP%\maturita-installer" +rmdir /s /q "%TMP_DIR%" 2>nul +mkdir "%TMP_DIR%" || goto :fail_tmp + +echo Downloading the latest release... +curl -fL --progress-bar -o "%TMP_DIR%\%ASSET%" "%URL%" || goto :fail_download + +echo Installing to %DEST% +if not exist "%DEST%" mkdir "%DEST%" || goto :fail_dest + +rem Extracted over any existing install rather than replacing the folder, so +rem the progress files kept inside it survive an upgrade. +tar -xf "%TMP_DIR%\%ASSET%" -C "%DEST%" || goto :fail_extract + +rem Start menu entry, so the app can be found without digging for the folder. +set "LNK=%APPDATA%\Microsoft\Windows\Start Menu\Programs\maturita.C.lnk" +powershell -NoProfile -ExecutionPolicy Bypass -Command ^ + "$s=(New-Object -ComObject WScript.Shell).CreateShortcut('%LNK%');" ^ + "$s.TargetPath='%DEST%\maturita.exe';$s.WorkingDirectory='%DEST%';$s.Save()" >nul 2>&1 + +rmdir /s /q "%TMP_DIR%" 2>nul + +echo. +echo Done. Starting the app... +start "" "%DEST%\maturita.exe" +exit /b 0 + +:no_tools +echo. +echo Error: this installer needs curl and tar, which come with Windows 10 +echo version 1803 and later. Please update Windows, or download the zip +echo manually from https://github.com/%REPO%/releases/latest +pause +exit /b 1 + +:fail_tmp +echo. +echo Error: could not create a temporary folder in %TEMP%. +pause +exit /b 1 + +:fail_download +echo. +echo Error: the download failed. Check your internet connection. +pause +exit /b 1 + +:fail_dest +echo. +echo Error: could not create %DEST%. +pause +exit /b 1 + +:fail_extract +echo. +echo Error: the archive could not be unpacked. +pause +exit /b 1