use Miso's foxfire social card as the OG image #38
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Auto-deploy kitsunebi to the DreamHost VPS on every push to main. | |
| # | |
| # Path-filtered: pure card-content commits don't trigger a deploy. That's | |
| # important because the running app pushes its own commits to GitHub via the | |
| # git-sync layer (every API mutation → debounced 5s commit → push). If those | |
| # triggered a redeploy, every drag-drop would feedback-loop into a build. | |
| # | |
| # The build runs on the GH runner — Astro's Node-adapter standalone bundles | |
| # every dependency, so we only rsync `dist/` and `ecosystem.config.cjs` | |
| # over to the VPS. Cards and attachments stay on the VPS where the API | |
| # writes them; we don't overwrite live state. | |
| # | |
| # Required repo secret: | |
| # DH_SSH_KEY — private key for humanpatternlab@vps32678.dreamhostps.com | |
| # (matches the key on disk: ~/.ssh/hpl_notebook_deploy) | |
| name: Deploy to DreamHost | |
| on: | |
| push: | |
| branches: [main] | |
| paths: | |
| # Only deploy when code or config changes. Card content lives in cards/ | |
| # and gets synced via the running app's git-sync layer, not via this | |
| # workflow — see the rationale at the top of this file. | |
| - 'src/**' | |
| - 'package.json' | |
| - 'package-lock.json' | |
| - 'astro.config.mjs' | |
| - 'ecosystem.config.cjs' | |
| - 'tools/deploy.sh' | |
| - '.github/workflows/deploy.yml' | |
| # Manual button in the Actions tab, useful for forcing a redeploy after | |
| # tweaking VPS-side config or after a Cloudflare DNS swap. | |
| workflow_dispatch: | |
| # A push that lands while a previous deploy is still running cancels the | |
| # older one — the freshest commit is what should be live. | |
| concurrency: | |
| group: deploy-prod | |
| cancel-in-progress: true | |
| jobs: | |
| deploy: | |
| runs-on: ubuntu-latest | |
| env: | |
| DH_HOST: vps32678.dreamhostps.com | |
| DH_USER: humanpatternlab | |
| DH_PATH: /home/humanpatternlab/kitsunebi.kitsuneden.net | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| - name: Install deps | |
| run: npm ci | |
| - name: Build (Astro standalone) | |
| run: npm run build | |
| - name: Configure SSH | |
| run: | | |
| mkdir -p ~/.ssh | |
| chmod 700 ~/.ssh | |
| # Pull the host key fresh so a known-hosts mismatch fails loudly | |
| # rather than silently downgrading to "yes accept anything". | |
| ssh-keyscan -H "$DH_HOST" >> ~/.ssh/known_hosts | |
| chmod 644 ~/.ssh/known_hosts | |
| # The secret is the raw private key, written 600 like ssh wants. | |
| echo "${{ secrets.DH_SSH_KEY }}" > ~/.ssh/deploy_key | |
| chmod 600 ~/.ssh/deploy_key | |
| - name: Pull latest cards into VPS working tree | |
| # The running app's git-sync layer commits + pushes API mutations | |
| # to origin, but never pulls. So a card created from a developer's | |
| # local machine (or any commit landing on main outside the API | |
| # path) never reaches the VPS disk ~ and any attempt to mutate it | |
| # via the API 404s with "No card with id ..." even though the UI | |
| # renders it (the UI shows cards bundled into dist/ at build time, | |
| # which is in sync with main). | |
| # | |
| # Catch the VPS up before reloading. --autostash protects any | |
| # uncommitted change the debounced commit hasn't gotten to yet | |
| # (extremely unlikely in the deploy window, but cheap to be | |
| # defensive about). | |
| run: | | |
| ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=yes \ | |
| "$DH_USER@$DH_HOST" \ | |
| "cd $DH_PATH && git fetch origin main && git pull --rebase --autostash origin main" | |
| - name: Rsync dist/ + ecosystem cjs to VPS | |
| run: | | |
| # Use --delete on dist/ so stale chunks get cleaned up between deploys. | |
| # Don't touch cards/ or public/attachments/ — those are live state. | |
| rsync -avz --delete \ | |
| -e "ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=yes" \ | |
| dist/ "$DH_USER@$DH_HOST:$DH_PATH/dist/" | |
| rsync -avz \ | |
| -e "ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=yes" \ | |
| ecosystem.config.cjs \ | |
| "$DH_USER@$DH_HOST:$DH_PATH/" | |
| - name: Write GitHub OAuth + session secrets file | |
| # Regenerated on every deploy from GH repo secrets. Avoids needing | |
| # humanpatternlab SSH access on Ada's local machine for routine | |
| # secrets management. Missing GH secret = empty value in env file | |
| # = sign-in 500s with a clear error in the server log; the | |
| # marketing landing and read-only board still serve fine. | |
| env: | |
| GH_OAUTH_CLIENT_ID: ${{ secrets.KITSUNEBI_GITHUB_CLIENT_ID }} | |
| GH_OAUTH_CLIENT_SECRET: ${{ secrets.KITSUNEBI_GITHUB_CLIENT_SECRET }} | |
| SESSION_SECRET: ${{ secrets.KITSUNEBI_SESSION_SECRET }} | |
| GH_ALLOWLIST: ${{ secrets.KITSUNEBI_GITHUB_ALLOWLIST }} | |
| run: | | |
| { | |
| echo "KITSUNEBI_GITHUB_CLIENT_ID=$GH_OAUTH_CLIENT_ID" | |
| echo "KITSUNEBI_GITHUB_CLIENT_SECRET=$GH_OAUTH_CLIENT_SECRET" | |
| echo "KITSUNEBI_SESSION_SECRET=$SESSION_SECRET" | |
| echo "KITSUNEBI_GITHUB_ALLOWLIST=$GH_ALLOWLIST" | |
| } > /tmp/kitsunebi-oauth.env | |
| scp -i ~/.ssh/deploy_key -o StrictHostKeyChecking=yes \ | |
| /tmp/kitsunebi-oauth.env \ | |
| "$DH_USER@$DH_HOST:~/.kitsunebi-github-oauth.env" | |
| ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=yes \ | |
| "$DH_USER@$DH_HOST" \ | |
| "chmod 600 ~/.kitsunebi-github-oauth.env" | |
| rm /tmp/kitsunebi-oauth.env | |
| - name: Reload PM2 | |
| run: | | |
| # Reload from the config file path (not by process name), so PM2 | |
| # actually re-evaluates ecosystem.config.cjs ~ which reads the | |
| # secrets from sibling files (~/.kitsunebi-agent-tokens and | |
| # ~/.kitsunebi-github-oauth.env). With --update-env, the running | |
| # processes pick up the freshly-evaluated env. | |
| # | |
| # `pm2 reload <name> --update-env` looked right but is a footgun: | |
| # it only updates env from the CURRENT shell, never re-reads the | |
| # config, so file-backed secrets sit ignored. (Spent an hour on | |
| # this; the deploy looked successful, the site kept 500-ing.) | |
| ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=yes \ | |
| "$DH_USER@$DH_HOST" \ | |
| "cd $DH_PATH && \ | |
| ~/.nvm/versions/node/v20.19.6/bin/pm2 startOrReload ecosystem.config.cjs --update-env" | |
| - name: Verify | |
| run: | | |
| # PM2's status report is enough — the app responding to HTTP would | |
| # require either CF tunnel access from the runner, or hitting the | |
| # VPS's bound port (which is localhost-only). PM2 "online" status is | |
| # what we can cheaply confirm. | |
| ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=yes \ | |
| "$DH_USER@$DH_HOST" \ | |
| "~/.nvm/versions/node/v20.19.6/bin/pm2 list | grep -E 'kitsunebi|cf-tunnel' || true" |