From 15335a9f4cb850925884ac8735cfb2f4e0a3b6a6 Mon Sep 17 00:00:00 2001 From: Jean-Michel Tremblay Date: Fri, 19 Jun 2026 14:31:20 -0400 Subject: [PATCH 1/6] Add Forgejo Actions workflow to build and test on push Co-Authored-By: Claude Opus 4.8 --- .forgejo/workflows/build.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .forgejo/workflows/build.yml diff --git a/.forgejo/workflows/build.yml b/.forgejo/workflows/build.yml new file mode 100644 index 0000000..9f548d7 --- /dev/null +++ b/.forgejo/workflows/build.yml @@ -0,0 +1,18 @@ +name: Build and Test +on: [push] +jobs: + build-and-test: + runs-on: docker + steps: + - name: Checkout + run: | + git clone --branch ${{ github.ref_name }} ${{ github.server_url }}/${{ github.repository }}.git . + - name: Test + run: docker run --rm -w /src -v $PWD:/src golang:1.26 go test ./... + - name: Build + run: docker run --rm -w /src -v $PWD:/src -e CGO_ENABLED=0 golang:1.26 go build -ldflags "-s -w" -o hsa ./cmd/hsa + - name: Upload binary + uses: actions/upload-artifact@v3 + with: + name: hsa + path: hsa From 5bf03feb946002d660dfda3f91eb41e0cccb0160 Mon Sep 17 00:00:00 2001 From: Jean-Michel Tremblay Date: Fri, 19 Jun 2026 14:36:25 -0400 Subject: [PATCH 2/6] CI: target the shell runner label instead of docker The only registered act_runner advertises the `shell` label, not `docker`, so `runs-on: docker` left the job queued forever. The host has the docker CLI (the resume repo builds via `docker run` on this same runner), so keep running the build/test inside a golang container. Co-Authored-By: Claude Opus 4.8 --- .forgejo/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forgejo/workflows/build.yml b/.forgejo/workflows/build.yml index 9f548d7..4088fd8 100644 --- a/.forgejo/workflows/build.yml +++ b/.forgejo/workflows/build.yml @@ -2,7 +2,7 @@ name: Build and Test on: [push] jobs: build-and-test: - runs-on: docker + runs-on: shell steps: - name: Checkout run: | From cddc42b32d5d50ccbbc5d240d50e8a033bdaf7f3 Mon Sep 17 00:00:00 2001 From: Jean-Michel Tremblay Date: Fri, 19 Jun 2026 14:39:14 -0400 Subject: [PATCH 3/6] CI: disable VCS stamping in containerized build git inside the golang container flags the mounted repo as dubious ownership (different UID), so `go build` fails obtaining VCS status (exit 128). The binary doesn't need git version stamping here. Co-Authored-By: Claude Opus 4.8 --- .forgejo/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forgejo/workflows/build.yml b/.forgejo/workflows/build.yml index 4088fd8..cee217c 100644 --- a/.forgejo/workflows/build.yml +++ b/.forgejo/workflows/build.yml @@ -10,7 +10,7 @@ jobs: - name: Test run: docker run --rm -w /src -v $PWD:/src golang:1.26 go test ./... - name: Build - run: docker run --rm -w /src -v $PWD:/src -e CGO_ENABLED=0 golang:1.26 go build -ldflags "-s -w" -o hsa ./cmd/hsa + run: docker run --rm -w /src -v $PWD:/src -e CGO_ENABLED=0 golang:1.26 go build -buildvcs=false -ldflags "-s -w" -o hsa ./cmd/hsa - name: Upload binary uses: actions/upload-artifact@v3 with: From 2f817d16d0b3837a2ca89d62ceffa763f4d1383e Mon Sep 17 00:00:00 2001 From: Jean-Michel Tremblay Date: Fri, 19 Jun 2026 14:44:16 -0400 Subject: [PATCH 4/6] CI: deploy binary to versioned dir on tag push On a tag push, scp the built hsa binary to ~/hsa-app-V on the target host (created if missing). Reuses the FORGEJO_SSH key; host and user come from the HSA_APP_HOST / HSA_APP_USER repo vars. Branch pushes still only build and test. Co-Authored-By: Claude Opus 4.8 --- .forgejo/workflows/build.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.forgejo/workflows/build.yml b/.forgejo/workflows/build.yml index cee217c..92b5ba9 100644 --- a/.forgejo/workflows/build.yml +++ b/.forgejo/workflows/build.yml @@ -16,3 +16,12 @@ jobs: with: name: hsa path: hsa + - name: Deploy + if: startsWith(github.ref, 'refs/tags/') + run: | + echo "${{ secrets.FORGEJO_SSH }}" > /tmp/deploy_key + chmod 600 /tmp/deploy_key + DEST=hsa-app-V${{ github.ref_name }} + ssh -i /tmp/deploy_key -o StrictHostKeyChecking=no ${{ vars.HSA_APP_USER }}@${{ vars.HSA_APP_HOST }} "mkdir -p $DEST" + scp -i /tmp/deploy_key -o StrictHostKeyChecking=no hsa ${{ vars.HSA_APP_USER }}@${{ vars.HSA_APP_HOST }}:$DEST/hsa + rm /tmp/deploy_key From f47e611e2c7af2789eaf7b51dd166813b6230572 Mon Sep 17 00:00:00 2001 From: Jean-Michel Tremblay Date: Fri, 19 Jun 2026 15:08:20 -0400 Subject: [PATCH 5/6] Deploy tagged releases via versioned dir + symlink swap On tag push, scp the binary to ~/hsa-app/releases/hsa-app-V/hsa, repoint the ~/hsa-app/hsa symlink, and restart the user systemd service. Add deploy/hsa_app.service unit and deploy/INSTALL.md with host layout, one-time install, deploy, and rollback steps. Co-Authored-By: Claude Opus 4.8 --- .forgejo/workflows/build.yml | 10 +++-- deploy/INSTALL.md | 86 ++++++++++++++++++++++++++++++++++++ deploy/hsa_app.service | 14 ++++++ 3 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 deploy/INSTALL.md create mode 100644 deploy/hsa_app.service diff --git a/.forgejo/workflows/build.yml b/.forgejo/workflows/build.yml index 92b5ba9..61db21e 100644 --- a/.forgejo/workflows/build.yml +++ b/.forgejo/workflows/build.yml @@ -21,7 +21,11 @@ jobs: run: | echo "${{ secrets.FORGEJO_SSH }}" > /tmp/deploy_key chmod 600 /tmp/deploy_key - DEST=hsa-app-V${{ github.ref_name }} - ssh -i /tmp/deploy_key -o StrictHostKeyChecking=no ${{ vars.HSA_APP_USER }}@${{ vars.HSA_APP_HOST }} "mkdir -p $DEST" - scp -i /tmp/deploy_key -o StrictHostKeyChecking=no hsa ${{ vars.HSA_APP_USER }}@${{ vars.HSA_APP_HOST }}:$DEST/hsa + TAG=${{ github.ref_name }} + REL=hsa-app/releases/hsa-app-V$TAG + TARGET=${{ vars.HSA_APP_USER }}@${{ vars.HSA_APP_HOST }} + ssh -i /tmp/deploy_key -o StrictHostKeyChecking=no $TARGET "mkdir -p $REL" + scp -i /tmp/deploy_key -o StrictHostKeyChecking=no hsa $TARGET:$REL/hsa + ssh -i /tmp/deploy_key -o StrictHostKeyChecking=no $TARGET \ + "cd hsa-app && chmod +x releases/hsa-app-V$TAG/hsa && ln -sfn releases/hsa-app-V$TAG/hsa hsa && systemctl --user restart hsa_app" rm /tmp/deploy_key diff --git a/deploy/INSTALL.md b/deploy/INSTALL.md new file mode 100644 index 0000000..ff8cd45 --- /dev/null +++ b/deploy/INSTALL.md @@ -0,0 +1,86 @@ +# Deploying hsa-app + +The app runs as a **user systemd service** (no sudo). Tagged releases are +pushed by CI into `~/hsa-app/releases/hsa-app-V/hsa`, and a `current` +symlink (`~/hsa-app/hsa`) points at the live release. Restarting the service +picks up whatever the symlink resolves to. + +## Layout on the host + +Mutable state (DB, env, config) lives at the top of `~/hsa-app/` and survives +every deploy. Only the binaries live under `releases/`. + +``` +~/hsa-app/ + hsa_app.sh # launcher: sources env file, exec's the binary + hsa_app.env # environment (KEY=VALUE) + config.json + hsa.db, hsa.db-shm, hsa.db-wal # SQLite state + releases/ + hsa-app-V0.0.0a0/hsa + hsa-app-V0.0.1/hsa + hsa -> releases/hsa-app-V0.0.1/hsa # current symlink, swapped on deploy +``` + +## The current symlink + +Create/repoint it **from inside `~/hsa-app`** so the relative target resolves +against the link's own directory (running `ln -s` from `~` produces a broken +link that points at `~/hsa-app/hsa-app/...`): + +```bash +cd ~/hsa-app +chmod +x releases/hsa-app-V/hsa +ln -sfn releases/hsa-app-V/hsa hsa # -f replace, -n don't follow existing link +ls -l hsa # target must resolve (not broken) +``` + +This same `ln -sfn` + `chmod +x` is what the CI deploy step runs on each tag. + +## Install the service (once) + +Save [hsa_app.service](hsa_app.service) to `~/.config/systemd/user/hsa_app.service`, +then: + +```bash +loginctl enable-linger "$USER" # run the service without an active login session +systemctl --user daemon-reload +systemctl --user enable --now hsa_app +systemctl --user status hsa_app +journalctl --user -u hsa_app -f # follow logs +``` + +### Why these choices + +- **User service** (`systemctl --user`) — no sudo, matches the home-dir deploy. + `enable-linger` lets it start at boot / stay up without an interactive login. +- **`Type=exec`** — `hsa_app.sh` ends in `exec "$BIN"`, so the binary becomes the + unit's main process; signals and exit codes propagate correctly. +- **`WorkingDirectory=%h/hsa-app`** — relative paths in the env file resolve here. + The launcher's comment recommends absolute paths for `DB_PATH` / `STORAGE_DIR` / + `BACKUP_DIR` / `CONFIG_PATH`; either works. +- **ExecStart passes the env file as `$1`** — matches `hsa_app.sh`'s first-arg + precedence, so no `/etc/hsa-app/...` file is needed. `%h` expands to the home dir. +- The launcher defaults its binary to `./hsa` next to itself (`~/hsa-app/hsa`, the + symlink), so a restart after a symlink swap runs the new release automatically. + +## Deploy a tagged release + +CI pushes the binary on tag and runs the swap + restart. To do it manually: + +```bash +cd ~/hsa-app +chmod +x releases/hsa-app-V/hsa +ln -sfn releases/hsa-app-V/hsa hsa +systemctl --user restart hsa_app +``` + +## Roll back + +Every release stays under `releases/`, so rollback is a symlink repoint: + +```bash +cd ~/hsa-app +ln -sfn releases/hsa-app-V/hsa hsa +systemctl --user restart hsa_app +``` diff --git a/deploy/hsa_app.service b/deploy/hsa_app.service new file mode 100644 index 0000000..140d882 --- /dev/null +++ b/deploy/hsa_app.service @@ -0,0 +1,14 @@ +[Unit] +Description=HSA app +After=network-online.target +Wants=network-online.target + +[Service] +Type=exec +WorkingDirectory=%h/hsa-app +ExecStart=%h/hsa-app/hsa_app.sh %h/hsa-app/hsa_app.env +Restart=on-failure +RestartSec=2 + +[Install] +WantedBy=default.target From fafd763d990b0d7905dc408afb5bd426b9d3efc8 Mon Sep 17 00:00:00 2001 From: Jean-Michel Tremblay Date: Fri, 19 Jun 2026 15:12:22 -0400 Subject: [PATCH 6/6] Split deploy: auto-stage on tag, manual activate via button build.yml now only stages the binary into ~/hsa-app/releases on tag (no symlink swap or restart). Activation is a separate workflow_dispatch workflow (deploy.yml) the user triggers from the Forgejo Actions tab, passing the tag to point the symlink at and restart. Update INSTALL.md to describe the stage/activate split. Co-Authored-By: Claude Opus 4.8 --- .forgejo/workflows/build.yml | 5 ++--- .forgejo/workflows/deploy.yml | 24 ++++++++++++++++++++++++ deploy/INSTALL.md | 19 ++++++++++++++----- 3 files changed, 40 insertions(+), 8 deletions(-) create mode 100644 .forgejo/workflows/deploy.yml diff --git a/.forgejo/workflows/build.yml b/.forgejo/workflows/build.yml index 61db21e..0265948 100644 --- a/.forgejo/workflows/build.yml +++ b/.forgejo/workflows/build.yml @@ -16,7 +16,7 @@ jobs: with: name: hsa path: hsa - - name: Deploy + - name: Stage release if: startsWith(github.ref, 'refs/tags/') run: | echo "${{ secrets.FORGEJO_SSH }}" > /tmp/deploy_key @@ -26,6 +26,5 @@ jobs: TARGET=${{ vars.HSA_APP_USER }}@${{ vars.HSA_APP_HOST }} ssh -i /tmp/deploy_key -o StrictHostKeyChecking=no $TARGET "mkdir -p $REL" scp -i /tmp/deploy_key -o StrictHostKeyChecking=no hsa $TARGET:$REL/hsa - ssh -i /tmp/deploy_key -o StrictHostKeyChecking=no $TARGET \ - "cd hsa-app && chmod +x releases/hsa-app-V$TAG/hsa && ln -sfn releases/hsa-app-V$TAG/hsa hsa && systemctl --user restart hsa_app" + ssh -i /tmp/deploy_key -o StrictHostKeyChecking=no $TARGET "chmod +x $REL/hsa" rm /tmp/deploy_key diff --git a/.forgejo/workflows/deploy.yml b/.forgejo/workflows/deploy.yml new file mode 100644 index 0000000..3921c4d --- /dev/null +++ b/.forgejo/workflows/deploy.yml @@ -0,0 +1,24 @@ +name: Deploy +on: + workflow_dispatch: + inputs: + tag: + description: 'Release version to activate, e.g. 0.0.0a1 (must already be staged in ~/hsa-app/releases/hsa-app-V/hsa)' + required: true +jobs: + deploy: + runs-on: shell + steps: + - name: Activate release + run: | + echo "${{ secrets.FORGEJO_SSH }}" > /tmp/deploy_key + chmod 600 /tmp/deploy_key + TAG=${{ github.event.inputs.tag }} + TARGET=${{ vars.HSA_APP_USER }}@${{ vars.HSA_APP_HOST }} + ssh -i /tmp/deploy_key -o StrictHostKeyChecking=no $TARGET \ + "cd hsa-app \ + && test -f releases/hsa-app-V$TAG/hsa \ + && chmod +x releases/hsa-app-V$TAG/hsa \ + && ln -sfn releases/hsa-app-V$TAG/hsa hsa \ + && systemctl --user restart hsa_app" + rm /tmp/deploy_key diff --git a/deploy/INSTALL.md b/deploy/INSTALL.md index ff8cd45..3478740 100644 --- a/deploy/INSTALL.md +++ b/deploy/INSTALL.md @@ -1,9 +1,16 @@ # Deploying hsa-app -The app runs as a **user systemd service** (no sudo). Tagged releases are -pushed by CI into `~/hsa-app/releases/hsa-app-V/hsa`, and a `current` -symlink (`~/hsa-app/hsa`) points at the live release. Restarting the service -picks up whatever the symlink resolves to. +The app runs as a **user systemd service** (no sudo). Deployment is two steps: + +1. **Stage (automatic on tag)** — pushing a git tag builds + tests, then CI + copies the binary into `~/hsa-app/releases/hsa-app-V/hsa`. Nothing goes + live yet. +2. **Activate (manual button)** — run the **Deploy** workflow from the Forgejo + Actions tab ("Run workflow"), entering the tag to activate. It points the + `~/hsa-app/hsa` symlink at that release and restarts the service. + +The symlink decouples "what's on disk" from "what's running," so activation and +rollback are just a symlink repoint + restart. ## Layout on the host @@ -66,7 +73,9 @@ journalctl --user -u hsa_app -f # follow logs ## Deploy a tagged release -CI pushes the binary on tag and runs the swap + restart. To do it manually: +Pushing a tag stages the binary automatically. To activate it, open the repo's +**Actions → Deploy** workflow, click **Run workflow**, and enter the tag (e.g. +`0.0.0a1`). To activate from the host instead: ```bash cd ~/hsa-app