Skip to content

Latest commit

 

History

History

deploy

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 

README.md

CI push-to-deploy template (Gitea Actions)

This document is LLM-generated.

Optional tag-triggered deployment: pushing a prod-* tag makes CI build a runtime-only tarball (main.py, piclock/, requirements files) and pipe it over SSH to the Pi, where a locked-down account applies it. Nothing here runs as-is — deploy.yml.example is a template, and the host side needs a one-time bootstrap.

Security model: CI holds the key for piclock-deploy, an unprivileged account whose key is pinned to a single forced command, piclock-receive. That script validates the request, stages the tarball, and sudos exactly one root command, piclock-apply, which syncs /opt/piclock, refreshes the clocker venv, and try-restarts the service. It never touches units, accounts, sudoers, or these scripts — so a leaked deploy key can only ship code that runs as the unprivileged clocker, never escalate to root. Runtime copying and dependency installation run as clocker, including when invoked by the root helper. Staging is readable by that account; receive/apply locks serialize overlapping deployments. The deploy home is root-owned so the account cannot replace its .ssh directory. The account's authorized_keys is also root-owned (so a hostile payload can't rewrite its own forced-command restriction — see bootstrap step 4), and CI pins the Pi's host key (so the tarball and trigger can't be intercepted by whatever answers to the Pi's name).

One-time host bootstrap (on the Pi)

  1. Run sudo scripts/install.sh first — the service, the clocker account, and /opt/piclock must already exist.

  2. Create the deploy account (a real shell is required for the forced command):

    sudo useradd --system -m -d /var/lib/piclock-deploy -s /bin/sh piclock-deploy
    sudo chown root:root /var/lib/piclock-deploy
    sudo chmod 755 /var/lib/piclock-deploy
    sudo install -d -o piclock-deploy -g piclock-deploy -m 0755 /var/lib/piclock-deploy/staging
    sudo touch /var/lib/piclock-deploy/receive.lock
    sudo chown piclock-deploy:piclock-deploy /var/lib/piclock-deploy/receive.lock
  3. Install both scripts root-owned:

    sudo install -o root -g root -m 0755 deploy/piclock-receive deploy/piclock-apply /usr/local/sbin/
  4. Generate a keypair (ssh-keygen -t ed25519 -f piclock-deploy-key -N '') and pin the public key in /var/lib/piclock-deploy/.ssh/authorized_keys, with this one line as its contents:

    command="/usr/local/sbin/piclock-receive",restrict ssh-ed25519 AAAA... piclock-ci
    

    Make the directory and file root-owned and read-only to the account (sshd's StrictModes accepts root ownership):

    sudo install -d -o root -g root -m 0755 /var/lib/piclock-deploy/.ssh
    sudo install -o root -g root -m 0644 authorized_keys /var/lib/piclock-deploy/.ssh/

    Root ownership is deliberate, not just tidiness: piclock-receive extracts a CI-supplied tarball as piclock-deploy, so the account itself must not be able to edit its own key restrictions — otherwise a malicious payload that escaped the staging directory could rewrite authorized_keys and remove the forced-command pinning.

  5. Allow the one privileged step, in /etc/sudoers.d/piclock-deploy (mode 0440):

    piclock-deploy ALL=(root) NOPASSWD: /usr/local/sbin/piclock-apply
    

Activate in Gitea

  1. Copy deploy.yml.example to .gitea/workflows/deploy.yml and replace <pi-host> with your Pi's hostname or IP.
  2. Add two repository secrets:
    • PICLOCK_DEPLOY_KEY — the private key from step 4 above.
    • PICLOCK_HOST_KEY — the Pi's public host key, so CI can verify it is talking to the real Pi. Capture it once with ssh-keyscan -t ed25519 <pi-host> (run from a machine that you trust is really reaching the Pi; use the same hostname/IP as in the workflow) and paste the output line in verbatim. If you ever reimage the Pi, re-run the scan and update the secret.
  3. Deploy by tagging: git tag prod-2026-01-01.1 && git push origin prod-2026-01-01.1.

Note the workflow deploys only the runtime bundle — it will not create accounts, change systemd units, or apply new feature flags. For those, run scripts/install.sh on the Pi as before.

Updating existing bootstrap scripts

These scripts are host configuration and are not included in runtime deploys. When upgrading them, apply the ownership, staging-directory and lock-file setup in step 2, then reinstall both scripts as in step 3. The staging tree must be readable by clocker; the receiver normalizes payload permissions. HomeKit requirements are refreshed when the installer-managed HomeKit drop-in exists.

Deployments update the runtime in place and have no automatic rollback. A failed dependency install can leave a partially updated bundle. Custom HomeKit enablement outside the installer-managed homekit.conf needs its own dependency refresh.