name: deploy on: # Auto-deploy on every push to trunk, but only after CI succeeds — # workflow_run waits for the named workflow to complete. workflow_run: workflows: [ci] branches: [trunk] types: [completed] # Escape hatch: redeploy current trunk without pushing. workflow_dispatch: permissions: contents: read # GH Actions is migrating its built-in Node from 20 to 24 by 2026-09-16. # Force our pinned action versions onto Node 24 now so the deprecation # warning clears and the eventual default flip is a no-op for us. Drop # this once every action below is bumped to a Node-24-native release. env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" concurrency: # Serialize deploys so two pushes in flight don't race on the # remote git reset / binary swap. group: deploy-prod cancel-in-progress: false jobs: deploy: # Skip when the upstream CI run failed; workflow_dispatch always runs. if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest environment: production steps: - uses: actions/checkout@v4 with: # Full history so the Mirror-to-shithub.sh step can prove # ancestry against whatever the mirror's tip is. Default # depth=1 makes any push that isn't the very first commit # fail "non-fast-forward" because the runner can't see # the parent chain. fetch-depth: 0 - uses: actions/setup-go@v5 with: go-version-file: go.mod cache: true # Delegate to `make build` so the version-injection ldflags # (Version/Commit/BuiltAt → internal/version) are the same set # the local Makefile uses. The previous inline `go build` here # passed only `-s -w`, which left the homepage stamps showing # "dev (unknown, built unknown)" on every deployed binary. # The runner is linux/amd64 so no cross-compile is needed. # Output goes to ./shithubd (overriding BIN) so the next step's # `< shithubd` redirect picks it up. - name: Build shithubd env: CGO_ENABLED: "0" run: make build BIN=shithubd - name: Configure SSH env: DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }} DEPLOY_KNOWN_HOSTS: ${{ secrets.DEPLOY_KNOWN_HOSTS }} run: | mkdir -p ~/.ssh chmod 700 ~/.ssh printf '%s\n' "$DEPLOY_SSH_KEY" > ~/.ssh/id_ed25519 printf '%s\n' "$DEPLOY_KNOWN_HOSTS" > ~/.ssh/known_hosts chmod 600 ~/.ssh/id_ed25519 ~/.ssh/known_hosts - name: Ship binary env: DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }} DEPLOY_USER: ${{ secrets.DEPLOY_USER }} # Stream over an exec channel rather than scp/sftp — the # hardened sshd on the droplet has the sftp-server subsystem # disabled, and modern scp insists on sftp. run: | ssh -o BatchMode=yes "${DEPLOY_USER}@${DEPLOY_HOST}" \ 'cat > /tmp/shithubd-new && chmod 0755 /tmp/shithubd-new' < shithubd - name: Redeploy env: DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }} DEPLOY_USER: ${{ secrets.DEPLOY_USER }} run: | ssh -o BatchMode=yes "${DEPLOY_USER}@${DEPLOY_HOST}" \ 'cd /root/src/shithub && git fetch --quiet origin trunk && git reset --hard origin/trunk && bash deploy/redeploy.sh' # Mirror the just-deployed SHA to the self-hosted shithub.sh # instance so anyone reading the source on shithub.sh sees the # same tip as github. Runs only after the redeploy succeeds, so # a broken commit never appears canonical on the dogfood mirror. # The credential helper feeds the PAT via stdin to avoid leaking # it via the process listing or git's url-with-credentials log. # # Plain `push` (no --force-with-lease): if shithub.sh ever # diverges from origin/trunk, the push fails non-fast-forward # and a human reconciles. We never want a runner to silently # overwrite a human edit on the mirror. # # The app may still be coming back through Caddy immediately # after the systemd restart. Wait for the public HTTP surface # and retry the mirror push so a transient 502 does not mark a # successful deploy as failed. Persistent auth, divergence, or # server errors still fail the job. - name: Mirror to shithub.sh if: success() env: SHITHUB_PUSH_USER: ${{ secrets.SHITHUB_PUSH_USER }} SHITHUB_PUSH_PAT: ${{ secrets.SHITHUB_PUSH_PAT }} run: | curl --fail --silent --show-error \ --retry 6 --retry-delay 5 --retry-all-errors \ https://shithub.sh/ \ --output /dev/null mirror_push() { git -c "credential.helper=!f() { echo username=$SHITHUB_PUSH_USER; echo password=$SHITHUB_PUSH_PAT; }; f" \ push \ https://shithub.sh/tenseleyflow/shithub.git \ HEAD:trunk } status=0 for attempt in 1 2 3 4 5; do if mirror_push; then exit 0 fi status=$? if [ "$attempt" -eq 5 ]; then break fi sleep "$((attempt * 10))" done exit "$status"