# runs-on must match the label the runner was registered with -- `heos` # here, the way Livetrail uses `livetrail`. A job asking for a label # 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 writes into DEPLOY_PATH and restarts the service. See # deploy/heos-panel.service for the unit and the one sudoers line the # restart needs. # # Every step here is plain shell, on purpose. actions/checkout is a # JavaScript action, and a host-mode runner can only run those if node is # on its PATH -- swapping the clone below back to `uses: actions/checkout` # brings back "Cannot find: node in PATH" on a runner without it. # # DEPLOY_PATH is also where you edit: an rsync --delete lands on top of # whatever is sitting there uncommitted, so commit before you push. name: Deploy HEOS panel on: push: branches: - main workflow_dispatch: jobs: deploy: runs-on: heos env: DEPLOY_PATH: /var/www/html/heos SERVICE: heos-panel PANEL_URL: http://127.0.0.1:5005/ # WEB_PORT in config.py SRC: src # the clone, inside the workspace steps: - name: Check runner tools run: | command -v git command -v python3 command -v rsync command -v curl - name: Check deploy path run: | 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 # A shallow clone of the pushed branch, taking the remote from the # 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: | rm -rf "$SRC" git init --quiet "$SRC" git -C "$SRC" remote add origin "$(git -C "$DEPLOY_PATH" remote get-url origin)" git -C "$SRC" fetch --quiet --depth 1 origin "${GITHUB_REF_NAME:-main}" git -C "$SRC" checkout --quiet FETCH_HEAD git -C "$SRC" --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 working-directory: src run: | 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. - 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: | rsync -azc --no-times --delete \ --exclude "/.git/" \ --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 run: sudo systemctl restart "$SERVICE" - name: Wait for the panel to answer run: | # The home page renders from config alone, so this proves the app # came back up without waiting on the speakers to reply. for attempt in $(seq 1 20); do if curl -fsS -o /dev/null "$PANEL_URL"; then echo "panel is up after ${attempt}s" exit 0 fi sleep 1 done echo "panel did not come back -- last of its log:" sudo systemctl status "$SERVICE" --no-pager --lines 30 || true exit 1