This commit is contained in:
+40
-48
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user