+44
-54
@@ -3,17 +3,17 @@
|
|||||||
# 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 writes into DEPLOY_PATH and restarts the service. See
|
# it updates DEPLOY_PATH and restarts the service, and it must run as the
|
||||||
# deploy/heos-panel.service for the unit and the one sudoers line the
|
# user that owns that directory.
|
||||||
# restart needs.
|
|
||||||
#
|
#
|
||||||
# Every step here is plain shell, on purpose. actions/checkout is a
|
# DEPLOY_PATH is a git checkout, cloned there ONCE by hand -- see
|
||||||
# JavaScript action, and a host-mode runner can only run those if node is
|
# "Deploying from Gitea" in the README. This workflow only fast-forwards
|
||||||
# on its PATH -- swapping the clone below back to `uses: actions/checkout`
|
# it, so the remote and whatever credentials reach it are set up a single
|
||||||
# brings back "Cannot find: node in PATH" on a runner without it.
|
# time and stay put.
|
||||||
#
|
#
|
||||||
# DEPLOY_PATH is also where you edit: an rsync --delete lands on top of
|
# Every step is plain shell on purpose: actions/checkout is a JavaScript
|
||||||
# whatever is sitting there uncommitted, so commit before you push.
|
# 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
|
||||||
|
|
||||||
@@ -31,72 +31,62 @@ jobs:
|
|||||||
DEPLOY_PATH: /var/www/html/heos
|
DEPLOY_PATH: /var/www/html/heos
|
||||||
SERVICE: heos-panel
|
SERVICE: heos-panel
|
||||||
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
|
||||||
SRC: src # the clone, inside the workspace
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check runner tools
|
- name: Check runner tools
|
||||||
run: |
|
run: |
|
||||||
command -v git
|
command -v git
|
||||||
command -v python3
|
command -v python3
|
||||||
command -v rsync
|
|
||||||
command -v curl
|
command -v curl
|
||||||
|
|
||||||
- name: Check deploy path
|
- name: Check the deploy path is ready
|
||||||
run: |
|
run: |
|
||||||
test -d "$DEPLOY_PATH"
|
if [ ! -d "$DEPLOY_PATH" ] || [ ! -w "$DEPLOY_PATH" ]; then
|
||||||
test -w "$DEPLOY_PATH"
|
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 <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
|
||||||
|
|
||||||
# A shallow clone of the pushed branch, taking the remote from the
|
- name: Fast-forward the checkout
|
||||||
# live checkout so there is no URL or token written down here. It
|
|
||||||
# works because the runner runs as the user that owns that checkout,
|
|
||||||
# which is the same reason it can write to DEPLOY_PATH at all.
|
|
||||||
- name: Checkout
|
|
||||||
run: |
|
run: |
|
||||||
rm -rf "$SRC"
|
cd "$DEPLOY_PATH"
|
||||||
git init --quiet "$SRC"
|
git fetch --prune origin
|
||||||
git -C "$SRC" remote add origin "$(git -C "$DEPLOY_PATH" remote get-url origin)"
|
# --ff-only on purpose: if someone has edited a tracked file on the
|
||||||
git -C "$SRC" fetch --quiet --depth 1 origin "${GITHUB_REF_NAME:-main}"
|
# box without committing it, this stops rather than throwing their
|
||||||
git -C "$SRC" checkout --quiet FETCH_HEAD
|
# change away.
|
||||||
git -C "$SRC" --no-pager log -1 --oneline
|
git merge --ff-only "origin/${GITHUB_REF_NAME:-main}"
|
||||||
|
git --no-pager log -1 --oneline
|
||||||
|
|
||||||
# A throwaway virtualenv in the workspace: 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
|
- name: Install dependencies
|
||||||
working-directory: src
|
|
||||||
run: |
|
run: |
|
||||||
python3 -m venv .venv-ci
|
cd "$DEPLOY_PATH"
|
||||||
.venv-ci/bin/pip install --quiet --upgrade pip
|
test -d .venv || python3 -m venv .venv
|
||||||
.venv-ci/bin/pip install --quiet -r requirements.txt
|
.venv/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.
|
# 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.
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
working-directory: src
|
|
||||||
run: .venv-ci/bin/python -m unittest discover -s tests -t . --verbose
|
|
||||||
|
|
||||||
- name: Deploy to production
|
|
||||||
working-directory: src
|
|
||||||
run: |
|
run: |
|
||||||
rsync -azc --no-times --delete \
|
cd "$DEPLOY_PATH"
|
||||||
--exclude "/.git/" \
|
.venv/bin/python -m unittest discover -s tests -t . --verbose
|
||||||
--exclude "/.gitea/" \
|
|
||||||
--exclude "/.venv/" \
|
|
||||||
--exclude "/.venv-ci/" \
|
|
||||||
--exclude "/members.json" \
|
|
||||||
--exclude "__pycache__/" \
|
|
||||||
./ "$DEPLOY_PATH/"
|
|
||||||
|
|
||||||
# members.json is the stereo pair's learned membership and .venv is
|
|
||||||
# the runtime -- both are excluded above, so --delete leaves them be.
|
|
||||||
|
|
||||||
- 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,34 +97,52 @@ Edit `User=` and the paths in it if you keep the panel somewhere else.
|
|||||||
|
|
||||||
## Deploying from Gitea
|
## Deploying from Gitea
|
||||||
|
|
||||||
`.gitea/workflows/deploy.yml` runs the tests on every push to `main`, then
|
`.gitea/workflows/deploy.yml` fast-forwards the checkout on the server,
|
||||||
rsyncs the tree into place, installs anything new from `requirements.txt`,
|
installs anything new from `requirements.txt`, runs the tests, restarts the
|
||||||
restarts the service and waits for the panel to answer again.
|
service and waits for the panel to answer again.
|
||||||
|
|
||||||
It needs a runner **in host mode on the machine that serves the panel**,
|
### Once, by hand on the server
|
||||||
registered with the label `heos` (`runs-on:` must match, or the job queues
|
|
||||||
forever), running as the user that owns the directory.
|
|
||||||
|
|
||||||
Every step is plain shell. `actions/checkout` is a JavaScript action, and a
|
The workflow only ever *updates* a checkout. It never creates one, so the
|
||||||
host-mode runner can only run those with `node` on its PATH — swapping the
|
remote and whatever credentials reach it are set up a single time and stay
|
||||||
clone back for it brings back `Cannot find: node in PATH`. The clone takes
|
put. As the user the runner runs as:
|
||||||
its URL from the live checkout's own remote, so no URL or token is written
|
|
||||||
down here.
|
|
||||||
|
|
||||||
Restarting needs one sudoers line:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
echo 'franzz ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart heos-panel' \
|
git clone <this repo> /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
|
||||||
|
|
||||||
|
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
|
||||||
```
|
```
|
||||||
|
|
||||||
The rsync excludes `.venv` and `members.json`, so the runtime and the
|
Edit `User=` and the paths in the unit if the panel lives somewhere else or
|
||||||
learned stereo-pair membership survive a deploy. It does *not* exclude
|
runs as someone else. Until that clone exists the workflow stops on its
|
||||||
`config.py`: your device names live in git, so commit changes to them
|
first real step and prints these commands back at you.
|
||||||
rather than editing the deployed copy. And since the deploy path is also
|
|
||||||
where you edit, `--delete` lands on top of anything uncommitted sitting
|
### The runner
|
||||||
there.
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
## Behind a reverse proxy, at /heos
|
## Behind a reverse proxy, at /heos
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user