From 9a5f2ac6391415ec2e03edbbe13f1d883d78a408 Mon Sep 17 00:00:00 2001 From: Franzz Date: Mon, 14 Sep 2026 23:58:53 +0200 Subject: [PATCH] Swaping back to nodejs dependent checkout --- .gitea/workflows/deploy.yml | 88 +++++++++++++++++-------------------- README.md | 66 ++++++++++++++-------------- 2 files changed, 74 insertions(+), 80 deletions(-) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index a9a07f5..89c6649 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -3,17 +3,13 @@ # nobody offers sits in the queue rather than failing. # # The runner has to be in host mode on the machine that serves the panel: -# it updates DEPLOY_PATH and restarts the service, and it must run as the -# user that owns that directory. +# it writes into DEPLOY_PATH and restarts the service. It also needs node +# 20+ on its PATH, since actions/checkout is a JavaScript action and a +# host-mode runner has nothing else to run one with. # -# DEPLOY_PATH is a git checkout, cloned there ONCE by hand -- see -# "Deploying from Gitea" in the README. This workflow only fast-forwards -# it, so the remote and whatever credentials reach it are set up a single -# time and stay put. -# -# Every step is plain shell on purpose: actions/checkout is a JavaScript -# action, and a host-mode runner can only run those with node on its PATH, -# failing with "Cannot find: node in PATH" without one. +# DEPLOY_PATH does not have to be a git checkout -- an empty directory the +# runner can write to is enough. See "Deploying from Gitea" in the README +# for the service and the one sudoers line the restart needs. name: Deploy HEOS panel @@ -33,60 +29,56 @@ jobs: PANEL_URL: http://127.0.0.1:5005/ # WEB_PORT in config.py steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Check runner tools run: | - command -v git command -v python3 + command -v rsync command -v curl - - name: Check the deploy path is ready + - name: Check deploy path run: | - if [ ! -d "$DEPLOY_PATH" ] || [ ! -w "$DEPLOY_PATH" ]; then - echo "$DEPLOY_PATH is missing, or not writable by $(id -un)." - exit 1 - fi - if [ ! -d "$DEPLOY_PATH/.git" ]; then - echo "$DEPLOY_PATH is not a checkout yet. Once, on this machine," - echo "as $(id -un):" - echo - echo " git clone $DEPLOY_PATH" - echo " cd $DEPLOY_PATH" - echo " python3 -m venv .venv" - echo " .venv/bin/pip install -r requirements.txt" - echo " sudo cp deploy/heos-panel.service /etc/systemd/system/" - echo " sudo systemctl enable --now $SERVICE" - echo - echo "Then edit config.py for this house and push again." - exit 1 - fi + test -d "$DEPLOY_PATH" + test -w "$DEPLOY_PATH" - name: Check the restart is allowed without a password run: sudo -n systemctl is-active "$SERVICE" || true - - name: Fast-forward the checkout - run: | - cd "$DEPLOY_PATH" - git fetch --prune origin - # --ff-only on purpose: if someone has edited a tracked file on the - # box without committing it, this stops rather than throwing their - # change away. - git merge --ff-only "origin/${GITHUB_REF_NAME:-main}" - git --no-pager log -1 --oneline - + # A throwaway virtualenv in the workspace -- this is the npm ci of a + # Python project. The one under $DEPLOY_PATH/.venv is what the running + # panel imports from, and a test run has no business touching it. - name: Install dependencies run: | - cd "$DEPLOY_PATH" - test -d .venv || python3 -m venv .venv - .venv/bin/pip install --quiet -r requirements.txt + python3 -m venv .venv-ci + .venv-ci/bin/pip install --quiet --upgrade pip + .venv-ci/bin/pip install --quiet -r requirements.txt # Runs against the fake HEOS and AVR servers in tests/fakes.py, so it - # needs no speakers and touches nothing on the network. The new code - # is on disk by this point, but the running process is still the old - # one: a failure here stops the job before the restart below. + # needs no speakers and touches nothing on the network. Nothing has + # been deployed yet at this point, so a failure here leaves the server + # exactly as it was. - name: Run tests + run: .venv-ci/bin/python -m unittest discover -s tests -t . --verbose + + # .venv and members.json are excluded, so the runtime and the stereo + # pair's learned membership survive --delete untouched. + - name: Deploy to production run: | - cd "$DEPLOY_PATH" - .venv/bin/python -m unittest discover -s tests -t . --verbose + rsync -azc --no-times --delete \ + --exclude "/.git/" \ + --exclude "/.gitea/" \ + --exclude "/.venv/" \ + --exclude "/.venv-ci/" \ + --exclude "/members.json" \ + --exclude "__pycache__/" \ + ./ "$DEPLOY_PATH/" + + - name: Install runtime dependencies + run: | + test -d "$DEPLOY_PATH/.venv" || python3 -m venv "$DEPLOY_PATH/.venv" + "$DEPLOY_PATH/.venv/bin/pip" install --quiet -r "$DEPLOY_PATH/requirements.txt" - name: Restart run: sudo systemctl restart "$SERVICE" diff --git a/README.md b/README.md index 12c239d..01aa1e6 100644 --- a/README.md +++ b/README.md @@ -97,52 +97,54 @@ Edit `User=` and the paths in it if you keep the panel somewhere else. ## Deploying from Gitea -`.gitea/workflows/deploy.yml` fast-forwards the checkout on the server, -installs anything new from `requirements.txt`, runs the tests, restarts the -service and waits for the panel to answer again. +`.gitea/workflows/deploy.yml` checks out the push, runs the tests, rsyncs +the tree into place, installs anything new from `requirements.txt`, +restarts the service and waits for the panel to answer again. The tests run +before the rsync, so a failure leaves the server exactly as it was. -### Once, by hand on the server +### The runner -The workflow only ever *updates* a checkout. It never creates one, so the -remote and whatever credentials reach it are set up a single time and stay -put. As the user the runner runs as: +Host mode, on the machine that serves the panel, registered with the label +`heos` (`runs-on:` must match, or the job queues forever), running as a user +that can write to the deploy path. It also needs **node 20 or newer** on its +PATH: `actions/checkout` is a JavaScript action, and a host-mode runner has +nothing else to run one with — without it the job fails immediately with +`Cannot find: node in PATH`. + +### Once, on the server + +The deploy path does not need to be a checkout — an empty directory the +runner can write to is enough: ```bash -git clone /var/www/html/heos -cd /var/www/html/heos -python3 -m venv .venv -.venv/bin/pip install -r requirements.txt - -sudo cp deploy/heos-panel.service /etc/systemd/system/ -sudo systemctl daemon-reload -sudo systemctl enable --now heos-panel +sudo mkdir -p /var/www/html/heos +sudo chown "$(id -un)": /var/www/html/heos echo "$(id -un) ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart heos-panel" \ | sudo tee /etc/sudoers.d/heos-panel sudo chmod 440 /etc/sudoers.d/heos-panel ``` -Edit `User=` and the paths in the unit if the panel lives somewhere else or -runs as someone else. Until that clone exists the workflow stops on its -first real step and prints these commands back at you. +Then push. That first run deploys the files and builds the virtualenv, and +stops at the restart, because the service does not exist yet. Install it +from the copy it just put there: -### The runner +```bash +sudo cp /var/www/html/heos/deploy/heos-panel.service /etc/systemd/system/ +sudo systemctl daemon-reload +sudo systemctl enable heos-panel +``` -Host mode, on the machine that serves the panel, registered with the label -`heos` (`runs-on:` must match, or the job queues forever), running as the -user that owns the checkout — it writes there, and it fetches with that -checkout's own git credentials. +Edit `User=` and the paths in the unit first if the panel lives somewhere +else or runs as someone else. Re-run the workflow and it goes green. -Every step is plain shell. `actions/checkout` is a JavaScript action, and a -host-mode runner can only run those with `node` on its PATH; without one it -fails with `Cannot find: node in PATH`. +### What survives a deploy -Nothing untracked is disturbed, so `.venv` and `members.json` — the runtime -and the stereo pair's learned membership — survive a deploy untouched. The -fast-forward is `--ff-only`, so a tracked file edited on the box without -being committed stops the deploy rather than being silently overwritten. -`config.py` is tracked: your device names live in git, so change them there -and push, rather than editing the deployed copy. +The rsync excludes `.venv` and `members.json`, so the runtime and the stereo +pair's learned membership are left alone by `--delete`. Everything else in +the deploy path is made to match the repo, `config.py` included: your device +names live in git, so change them there and push rather than editing the +deployed copy. ## Behind a reverse proxy, at /heos