Swaping back to nodejs dependent checkout
Deploy HEOS panel / deploy (push) Failing after 36s

This commit is contained in:
2026-09-14 23:58:53 +02:00
parent a7a8ab6f4e
commit 9a5f2ac639
2 changed files with 74 additions and 80 deletions
+40 -48
View File
@@ -3,17 +3,13 @@
# nobody offers sits in the queue rather than failing. # nobody offers sits in the queue rather than failing.
# #
# The runner has to be in host mode on the machine that serves the panel: # 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 # it writes into DEPLOY_PATH and restarts the service. It also needs node
# user that owns that directory. # 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 # DEPLOY_PATH does not have to be a git checkout -- an empty directory the
# "Deploying from Gitea" in the README. This workflow only fast-forwards # runner can write to is enough. See "Deploying from Gitea" in the README
# it, so the remote and whatever credentials reach it are set up a single # for the service and the one sudoers line the restart needs.
# 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.
name: Deploy HEOS panel name: Deploy HEOS panel
@@ -33,60 +29,56 @@ jobs:
PANEL_URL: http://127.0.0.1:5005/ # WEB_PORT in config.py PANEL_URL: http://127.0.0.1:5005/ # WEB_PORT in config.py
steps: steps:
- name: Checkout
uses: actions/checkout@v4
- name: Check runner tools - name: Check runner tools
run: | run: |
command -v git
command -v python3 command -v python3
command -v rsync
command -v curl command -v curl
- name: Check the deploy path is ready - name: Check deploy path
run: | run: |
if [ ! -d "$DEPLOY_PATH" ] || [ ! -w "$DEPLOY_PATH" ]; then test -d "$DEPLOY_PATH"
echo "$DEPLOY_PATH is missing, or not writable by $(id -un)." test -w "$DEPLOY_PATH"
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 <this repo> $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
- name: Check the restart is allowed without a password - name: Check the restart is allowed without a password
run: sudo -n systemctl is-active "$SERVICE" || true run: sudo -n systemctl is-active "$SERVICE" || true
- name: Fast-forward the checkout # A throwaway virtualenv in the workspace -- this is the npm ci of a
run: | # Python project. The one under $DEPLOY_PATH/.venv is what the running
cd "$DEPLOY_PATH" # panel imports from, and a test run has no business touching it.
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
- name: Install dependencies - name: Install dependencies
run: | run: |
cd "$DEPLOY_PATH" python3 -m venv .venv-ci
test -d .venv || python3 -m venv .venv .venv-ci/bin/pip install --quiet --upgrade pip
.venv/bin/pip install --quiet -r requirements.txt .venv-ci/bin/pip install --quiet -r requirements.txt
# Runs against the fake HEOS and AVR servers in tests/fakes.py, so it # 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 # needs no speakers and touches nothing on the network. Nothing has
# is on disk by this point, but the running process is still the old # been deployed yet at this point, so a failure here leaves the server
# one: a failure here stops the job before the restart below. # exactly as it was.
- name: Run tests - 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: | run: |
cd "$DEPLOY_PATH" rsync -azc --no-times --delete \
.venv/bin/python -m unittest discover -s tests -t . --verbose --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 - name: Restart
run: sudo systemctl restart "$SERVICE" run: sudo systemctl restart "$SERVICE"
+34 -32
View File
@@ -97,52 +97,54 @@ Edit `User=` and the paths in it if you keep the panel somewhere else.
## Deploying from Gitea ## Deploying from Gitea
`.gitea/workflows/deploy.yml` fast-forwards the checkout on the server, `.gitea/workflows/deploy.yml` checks out the push, runs the tests, rsyncs
installs anything new from `requirements.txt`, runs the tests, restarts the the tree into place, installs anything new from `requirements.txt`,
service and waits for the panel to answer again. 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 Host mode, on the machine that serves the panel, registered with the label
remote and whatever credentials reach it are set up a single time and stay `heos` (`runs-on:` must match, or the job queues forever), running as a user
put. As the user the runner runs as: 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 ```bash
git clone <this repo> /var/www/html/heos sudo mkdir -p /var/www/html/heos
cd /var/www/html/heos sudo chown "$(id -un)": /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
echo "$(id -un) ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart heos-panel" \ echo "$(id -un) ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart heos-panel" \
| sudo tee /etc/sudoers.d/heos-panel | sudo tee /etc/sudoers.d/heos-panel
sudo chmod 440 /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 Then push. That first run deploys the files and builds the virtualenv, and
runs as someone else. Until that clone exists the workflow stops on its stops at the restart, because the service does not exist yet. Install it
first real step and prints these commands back at you. 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 Edit `User=` and the paths in the unit first if the panel lives somewhere
`heos` (`runs-on:` must match, or the job queues forever), running as the else or runs as someone else. Re-run the workflow and it goes green.
user that owns the checkout — it writes there, and it fetches with that
checkout's own git credentials.
Every step is plain shell. `actions/checkout` is a JavaScript action, and a ### What survives a deploy
host-mode runner can only run those with `node` on its PATH; without one it
fails with `Cannot find: node in PATH`.
Nothing untracked is disturbed, so `.venv` and `members.json` the runtime The rsync excludes `.venv` and `members.json`, so the runtime and the stereo
and the stereo pair's learned membership — survive a deploy untouched. The pair's learned membership are left alone by `--delete`. Everything else in
fast-forward is `--ff-only`, so a tracked file edited on the box without the deploy path is made to match the repo, `config.py` included: your device
being committed stops the deploy rather than being silently overwritten. names live in git, so change them there and push rather than editing the
`config.py` is tracked: your device names live in git, so change them there deployed copy.
and push, rather than editing the deployed copy.
## Behind a reverse proxy, at /heos ## Behind a reverse proxy, at /heos