Skip to content

personal-site-2: vladmesh.dev: redirect old secretary project URL, 404 for unknown slugs, fix red CI (greenlet), retarget deploy to h01o - #34

Merged
vladmesh merged 1 commit into
mainfrom
pipeline/personal-site-2
Oct 2, 2026
Merged

vladmesh merged 1 commit into
mainfrom
pipeline/personal-site-2

Conversation

@vladmesh

@vladmesh vladmesh commented Oct 2, 2026

Copy link
Copy Markdown
Owner

Card: personal-site-2 — vladmesh.dev: redirect old secretary project URL, 404 for unknown slugs, fix red CI (greenlet), retarget deploy to h01o
Branch: pipeline/personal-site-2 → main

What the card asks for

Goal

Follow-up to personal-site-1 (report in its completion record; site access notes in knowledge projects/personal-site/access.md). Production vladmesh.dev runs on h01o (188.40.87.82, user vlad, rootless docker, ~/personal_site, compose project personal-site, ingress Cloudflare Tunnel personal-site-h01o). Three defects, one card:

  1. Old project URL is broken (live regression). After the slug change secretary -> ummanu, /en/projects/secretary/ and /ru/projects/secretary/ return 500: [slug].astro throws on an unknown slug. Add a permanent redirect secretary -> ummanu for both locales (a redirect map in middleware or astro.config redirects; keep it data-driven if the codebase allows). Make an unknown slug render 404, not 500.
  2. CI on main is red: ImportError: SQLAlchemy asyncio requires greenlet. poetry.lock is gitignored, so a fresh resolve drops greenlet. Pin sqlalchemy[asyncio] or greenlet explicitly in the backend dependencies, so CI is green from a fresh resolve.
  3. Deploy targets a dead host. .github/workflows/deploy.yml and the VPS_* secrets point at 185.81.166.84, which is gone. Retarget the deploy to the h01o layout so a merge to main deploys again:
    • use the user/path/compose project above;
    • rootless docker, so no sudo docker/sudo chown;
    • set the VPS_* secrets with gh secret set, using a deploy key that h01o's vlad accepts (~/.ssh/h01o_vlad or a dedicated key you add to vlad's authorized_keys, recorded in access.md).
    • The workflow must not clobber h01o's live .env. Today it rewrites .env from GitHub secrets: POSTGRES_PASSWORD must match the existing DB, and the admin hash must be the PBKDF2 one now live (personal-site-1 set the matching GitHub secrets, $$-escaped). Either verify every value matches before the first run, or change the workflow to leave an existing .env alone.

h01o is a friend's host. Its root docker daemon runs his production. Stay inside vlad's home and rootless daemon. Never prune, restart docker, reboot or touch host config. Log host changes in /home/vlad/HOST_CHANGES.md. Commits carry no AI co-author trailer. The owner authorised these site changes (2026-10-02).

Acceptance

  • CI green on main after merge, and the Deploy workflow run after the merge succeeds against h01o.
  • curl -sI https://vladmesh.dev/en/projects/secretary/ (and /ru/) is a 301/308 to /…/projects/ummanu/.
  • An unknown slug returns 404.
  • /en/projects/ummanu/ and /ru/projects/ummanu/ are still 200 with Ummanu, and admin login still works.
  • access.md is updated with the deploy path.

What the worker reports

personal-site-2: done (one commit on pipeline/personal-site-2, no AI trailer)

1. Old project URL (500 -> 301) and unknown slug (500 -> 404)

  • services/frontend/src/config/redirects.ts: a data map of renamed project slugs (secretary: 'ummanu').
  • services/frontend/src/middleware.ts: /{en,ru}/projects/<slug> with or without the trailing slash goes 301 to /{locale}/projects/<new>/ when the slug is in the map.
  • [slug].astro (en and ru): an unknown slug now returns new Response(null, {status: 404}) (the documented Astro pattern) instead of throwing.

2. CI red (greenlet)

  • services/backend/pyproject.toml: sqlalchemy = {extras = ["asyncio"], version = "^2.0.36"}. The installed SQLAlchemy 2.1.2 declares greenlet>=1; extra == "asyncio", so a fresh resolve without the extra has no greenlet.
  • The workspace venv reproduced the CI failure exactly.

3. Deploy retargeted to h01o

  • New infra/docker-compose.tunnel.yml and infra/configs/Caddyfile.tunnel mirror the live hand-made layout: project personal-site, tunnel, caddy on :8080, /cv bind mount, /api route. The only difference is that frontend comes from ghcr instead of personal-site-frontend:local.
  • I checked this on the host with docker compose config --hash '*', which is read-only. The hashes of postgres, caddy and cloudflared match the live ones, and backend's hash is the same too. A deploy recreates only frontend, plus backend when its image changed.
  • deploy.yml job deploy:
    • It runs in environment: production.
    • It uses ~/personal_site (repo var DEPLOY_DIR can override this) and DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock.
    • No sudo, no docker login (the ghcr packages are public, and a login would overwrite vlad's stored ghcr credential that codegen uses), no prune.
    • It runs docker compose pull backend frontend (only those two) and docker compose up -d, then waits for health.
    • It never writes .env, and it fails if .env is missing.
    • A runner-side smoke check follows: https://$SITE_DOMAIN/{en/,ru/,api/health} must be 200. All three return 200 today from the control host.
  • Remote scripts go through bash -s with quoted heredocs, with no nested quoting.
  • README deploy section and infra/.env.example updated. No owner or host names were added to the repo, to keep the repo depersonalized.

Secrets, key, host changes

  • Dedicated key ~/.ssh/personal_site_h01o_deploy (SHA256:), added to vlad's authorized_keys with restrict. I tested ssh and scp with it.
  • gh secret set --env production VPS_HOST=h01o.aipika.tech, VPS_USER=vlad, VPS_SSH_PORT=22, VPS_SSH_KEY. The existing empty production environment is now limited to deployments from main.
  • Why environment secrets and not the repo-level ones: the deploy.yml currently on main does sudo docker compose up in /opt. If repo-level VPS_* pointed at h01o, any run of the old workflow before this merge would start a stack on the friend's root daemon. So the repo-level VPS_* still point at the dead host. They can be deleted after the merge.
  • h01o changes:
    • The key line in authorized_keys.
    • Appended REGISTRY_OWNER=vladmesh to ~/personal_site/.env. Nothing else in it changed. Live config hashes were checked before and after and are unchanged, and no container was recreated.
    • Backups (0600) of authorized_keys, .env, docker-compose.yml and Caddyfile in ~/backups/personal-site/*-before-personal-site-2-*.
    • Everything is logged in /home/vlad/HOST_CHANGES.md.
  • access.md updated through ummanu knowledge write (instance commit eeaae9b63), with the deploy path, key, environment secrets, stale repo secrets, redirect map and the no-prune note.

Checks

  • Broad suite through the receipt wrapper.
    • The first run was red: ImportError ... requires greenlet at conftest. The workspace venv lacked greenlet, which is the defect this card fixes.
    • I installed sqlalchemy[asyncio]==2.1.2 into the workspace v

… (truncated; the full text is on the card)


Opened by the ummanu CI gate so that the pull_request workflow runs. Title and body are built from the card and the worker's done report.

…el host

- frontend: middleware 301s /{en,ru}/projects/<old>/ to the current slug
  from a data map (src/config/redirects.ts: secretary -> ummanu); an unknown
  slug now returns 404 instead of throwing a 500.
- backend: depend on sqlalchemy[asyncio]. SQLAlchemy 2.1 ships greenlet only
  in that extra, and poetry.lock is not committed, so a fresh resolve in CI
  failed with "SQLAlchemy asyncio requires greenlet".
- deploy: ship infra/docker-compose.tunnel.yml + Caddyfile.tunnel (Cloudflare
  Tunnel, no published ports) to ~/personal_site of an unprivileged user with
  rootless Docker. No sudo, no registry login (public ghcr images), no prune,
  pull only backend/frontend, and never write the host's .env (fail if it is
  missing). VPS_* secrets now come from the production environment
  (main only); add a smoke check through the public domain.
@vladmesh
vladmesh merged commit 3e09a71 into main Oct 2, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant