Skip to content

use Miso's foxfire social card as the OG image #38

use Miso's foxfire social card as the OG image

use Miso's foxfire social card as the OG image #38

Workflow file for this run

# 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"