From 3959ff2b3a0ab02aabf4777bbd7a044d08df6b73 Mon Sep 17 00:00:00 2001 From: Jesse_Chen Date: Mon, 20 Jul 2026 15:31:36 +0800 Subject: [PATCH] docs: use ubuntu for staging bootstrap --- ...-07-20-staging-infrastructure-bootstrap.md | 144 ++++++++++-------- .../specs/2026-07-20-staging-server-design.md | 5 +- 2 files changed, 84 insertions(+), 65 deletions(-) diff --git a/docs/superpowers/plans/2026-07-20-staging-infrastructure-bootstrap.md b/docs/superpowers/plans/2026-07-20-staging-infrastructure-bootstrap.md index 74b794df..a5022ed9 100644 --- a/docs/superpowers/plans/2026-07-20-staging-infrastructure-bootstrap.md +++ b/docs/superpowers/plans/2026-07-20-staging-infrastructure-bootstrap.md @@ -17,7 +17,7 @@ - Runtime environment file is exactly `/opt/jyotisha-staging/.env.staging` with mode `0600`. - SSH, Supabase, database, and model-provider credentials must be staging-specific. - Do not expose container ports `3000` or `5200` on the host. -- Do not disable root/password SSH until deploy-key login succeeds in a second terminal. +- Do not disable password SSH until both `ubuntu` admin-key login and `deploy` deploy-key login succeed in separate terminals. - Do not execute a database reset against any linked remote project. --- @@ -25,7 +25,7 @@ ## File and Control-Plane Map - Cloud provider console: OS image, rescue console, security group. -- Local Mac: `~/.ssh/jyotisha-staging` and `~/.ssh/jyotisha-staging.pub`. +- Local Mac: deploy pair `~/.ssh/jyotisha-staging*` and admin pair `~/.ssh/jyotisha-staging-admin*`. - VPS: `/home/deploy/.ssh/authorized_keys`, `/etc/ssh/sshd_config.d/60-jyotisha-staging.conf`, `/etc/docker/daemon.json`, `/opt/jyotisha-staging/.env.staging`. - DNS provider: `A staging.jyotisha.chat -> 118.26.111.127`. - Supabase Dashboard: a new staging project, Auth URL configuration, staging credentials. @@ -75,10 +75,10 @@ Expected: a provider-console screenshot or rule list contains only the four inte From the local Mac: ```bash -ssh root@118.26.111.127 +ssh ubuntu@118.26.111.127 ``` -Expected: a first-use host-key prompt followed by the provider's root-password prompt, then a root shell. Do not send the root password in chat, GitHub, or shell history. +Expected: a first-use host-key prompt followed by the provider's `ubuntu` password prompt, then an Ubuntu shell. Do not send the password in chat, GitHub, or shell history. - [ ] **Step 4: Confirm machine identity from the server** @@ -106,68 +106,86 @@ at least 40 GB root disk ### Task 2: Create and Verify the Dedicated Deploy Identity **Interfaces:** -- Consumes: Initial root access from Task 1. -- Produces: `deploy@118.26.111.127` authenticated by `~/.ssh/jyotisha-staging`. +- Consumes: Initial `ubuntu` access from Task 1. +- Produces: `ubuntu@118.26.111.127` authenticated by the admin key and `deploy@118.26.111.127` authenticated by the deploy key. -- [ ] **Step 1: Generate a dedicated key on the local Mac** +- [ ] **Step 1: Generate separate admin and deploy keys on the local Mac** Run locally, not on the VPS: ```bash +test ! -e "$HOME/.ssh/jyotisha-staging-admin" test ! -e "$HOME/.ssh/jyotisha-staging" +ssh-keygen -t ed25519 -a 64 -N '' -f "$HOME/.ssh/jyotisha-staging-admin" -C "jyotisha-staging-admin" ssh-keygen -t ed25519 -a 64 -N '' -f "$HOME/.ssh/jyotisha-staging" -C "github-actions-jyotisha-staging" +chmod 600 "$HOME/.ssh/jyotisha-staging-admin" chmod 600 "$HOME/.ssh/jyotisha-staging" +chmod 644 "$HOME/.ssh/jyotisha-staging-admin.pub" chmod 644 "$HOME/.ssh/jyotisha-staging.pub" +ssh-keygen -lf "$HOME/.ssh/jyotisha-staging-admin.pub" ssh-keygen -lf "$HOME/.ssh/jyotisha-staging.pub" ``` -Expected: the first command exits successfully, two key files are created, and the fingerprint uses `ED25519`. This no-passphrase key is dedicated to the staging deploy user and GitHub Environment; it must never be used for production or copied to another host. +Expected: four key files are created and both fingerprints use `ED25519`. The admin private key remains only on the Mac. The deploy private key is later stored only in the GitHub `staging` Environment and must never be used for production. -- [ ] **Step 2: Create the deploy user on the VPS** +- [ ] **Step 2: Install only the admin public key on the ubuntu account** -Run in the root SSH session: +Run locally and type the server password only at the terminal prompt: ```bash -adduser --disabled-password --gecos "" deploy -install -d -m 700 -o deploy -g deploy /home/deploy/.ssh -install -d -m 755 -o deploy -g deploy /opt/jyotisha-staging +ssh-copy-id -i "$HOME/.ssh/jyotisha-staging-admin.pub" ubuntu@118.26.111.127 +ssh -i "$HOME/.ssh/jyotisha-staging-admin" -o IdentitiesOnly=yes ubuntu@118.26.111.127 'id && sudo -n true' +``` + +Expected: the first command installs the public key; the second logs in as `ubuntu`. `sudo -n true` must exit `0`; if the provider requires a sudo password, keep the interactive admin session open and use `sudo` with the password typed directly at its prompt. + +- [ ] **Step 3: Create the deploy user on the VPS** + +Log in with the admin key and run: + +```bash +ssh -i "$HOME/.ssh/jyotisha-staging-admin" -o IdentitiesOnly=yes ubuntu@118.26.111.127 +sudo adduser --disabled-password --gecos "" deploy +sudo install -d -m 700 -o deploy -g deploy /home/deploy/.ssh +sudo install -d -m 755 -o deploy -g deploy /opt/jyotisha-staging ``` Expected: ```bash id deploy -stat -c '%U %G %a %n' /home/deploy/.ssh /opt/jyotisha-staging +sudo stat -c '%U %G %a %n' /home/deploy/.ssh /opt/jyotisha-staging ``` The output shows user `deploy`, `.ssh` mode `700`, and `/opt/jyotisha-staging` owned by `deploy`. -- [ ] **Step 3: Copy only the public key to the VPS** +- [ ] **Step 4: Copy only the deploy public key to the VPS** From a second local terminal: ```bash -scp "$HOME/.ssh/jyotisha-staging.pub" root@118.26.111.127:/tmp/jyotisha-staging.pub +scp -i "$HOME/.ssh/jyotisha-staging-admin" -o IdentitiesOnly=yes \ + "$HOME/.ssh/jyotisha-staging.pub" ubuntu@118.26.111.127:/tmp/jyotisha-staging.pub ``` -Then in the root VPS session: +Then in the authenticated `ubuntu` session: ```bash -install -m 600 -o deploy -g deploy /tmp/jyotisha-staging.pub /home/deploy/.ssh/authorized_keys -shred -u /tmp/jyotisha-staging.pub +sudo install -m 600 -o deploy -g deploy /tmp/jyotisha-staging.pub /home/deploy/.ssh/authorized_keys +sudo shred -u /tmp/jyotisha-staging.pub ``` Expected: ```bash -stat -c '%U %G %a %n' /home/deploy/.ssh/authorized_keys +sudo stat -c '%U %G %a %n' /home/deploy/.ssh/authorized_keys ``` Output: `deploy deploy 600 /home/deploy/.ssh/authorized_keys`. -- [ ] **Step 4: Verify deploy-key login in a new terminal** +- [ ] **Step 5: Verify deploy-key login in a new terminal** -Keep the root session open. From the local Mac: +Keep the `ubuntu` admin session open. From the local Mac: ```bash ssh -i "$HOME/.ssh/jyotisha-staging" -o IdentitiesOnly=yes deploy@118.26.111.127 'id && hostname' @@ -175,12 +193,12 @@ ssh -i "$HOME/.ssh/jyotisha-staging" -o IdentitiesOnly=yes deploy@118.26.111.127 Expected: exit code `0`; output contains `uid=` for `deploy`. Do not continue if this fails. -- [ ] **Step 5: Pin and compare the server host key** +- [ ] **Step 6: Pin and compare the server host key** -On the VPS root session: +On the VPS `ubuntu` session: ```bash -ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub +sudo ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub ``` On the local Mac: @@ -200,34 +218,34 @@ Expected: both fingerprints are identical. Preserve the exact line in `/tmp/jyot - [ ] **Step 1: Install base administration packages** -Run as root on the VPS: +Run from the authenticated `ubuntu` session with `sudo`: ```bash export DEBIAN_FRONTEND=noninteractive -apt-get update -apt-get dist-upgrade -y -apt-get install -y ca-certificates curl git rsync ufw unattended-upgrades -hostnamectl set-hostname jyotisha-staging -timedatectl set-timezone UTC -systemctl enable --now unattended-upgrades +sudo apt-get update +sudo apt-get dist-upgrade -y +sudo apt-get install -y ca-certificates curl git rsync ufw unattended-upgrades +sudo hostnamectl set-hostname jyotisha-staging +sudo timedatectl set-timezone UTC +sudo systemctl enable --now unattended-upgrades ``` Expected: all commands exit `0` and `hostnamectl --static` prints `jyotisha-staging`. - [ ] **Step 2: Create swap only if the VPS has none** -Run as root: +Run with `sudo`: ```bash if [ "$(swapon --noheadings | wc -l)" -eq 0 ]; then - fallocate -l 4G /swapfile - chmod 600 /swapfile - mkswap /swapfile - swapon /swapfile - printf '%s\n' '/swapfile none swap sw 0 0' >> /etc/fstab + sudo fallocate -l 4G /swapfile + sudo chmod 600 /swapfile + sudo mkswap /swapfile + sudo swapon /swapfile + printf '%s\n' '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab >/dev/null fi -sysctl vm.swappiness=10 -printf '%s\n' 'vm.swappiness=10' > /etc/sysctl.d/60-jyotisha-staging.conf +sudo sysctl vm.swappiness=10 +printf '%s\n' 'vm.swappiness=10' | sudo tee /etc/sysctl.d/60-jyotisha-staging.conf >/dev/null ``` Expected: @@ -242,27 +260,27 @@ Output shows one 4 GB swap file and one matching `fstab` entry. - [ ] **Step 3: Configure UFW before enabling it** -Run as root: +Run with `sudo`: ```bash -ufw default deny incoming -ufw default allow outgoing -ufw allow 22/tcp comment 'SSH' -ufw allow 80/tcp comment 'HTTP' -ufw allow 443/tcp comment 'HTTPS' -ufw allow 443/udp comment 'HTTP3' -ufw --force enable -ufw status verbose +sudo ufw default deny incoming +sudo ufw default allow outgoing +sudo ufw allow 22/tcp comment 'SSH' +sudo ufw allow 80/tcp comment 'HTTP' +sudo ufw allow 443/tcp comment 'HTTPS' +sudo ufw allow 443/udp comment 'HTTP3' +sudo ufw --force enable +sudo ufw status verbose ``` Expected: UFW is active; only `22/tcp`, `80/tcp`, `443/tcp`, and `443/udp` are allowed. Docker-published ports must still be reviewed separately because Docker can bypass UFW; the application Compose file may publish only 80/443. - [ ] **Step 4: Harden SSH with a configuration snippet** -Run as root: +Run with `sudo`: ```bash -install -m 600 /dev/null /etc/ssh/sshd_config.d/60-jyotisha-staging.conf +sudo install -m 600 /dev/null /etc/ssh/sshd_config.d/60-jyotisha-staging.conf printf '%s\n' \ 'PubkeyAuthentication yes' \ 'PasswordAuthentication no' \ @@ -270,23 +288,24 @@ printf '%s\n' \ 'PermitRootLogin no' \ 'X11Forwarding no' \ 'MaxAuthTries 3' \ - > /etc/ssh/sshd_config.d/60-jyotisha-staging.conf -sshd -t -systemctl reload ssh + | sudo tee /etc/ssh/sshd_config.d/60-jyotisha-staging.conf >/dev/null +sudo sshd -t +sudo systemctl reload ssh ``` Expected: `sshd -t` emits nothing and exits `0`. -- [ ] **Step 5: Re-test access before closing the root session** +- [ ] **Step 5: Re-test access before closing the original password session** From the local Mac: ```bash +ssh -i "$HOME/.ssh/jyotisha-staging-admin" -o IdentitiesOnly=yes ubuntu@118.26.111.127 'printf "admin-key-ok\n"' ssh -i "$HOME/.ssh/jyotisha-staging" -o IdentitiesOnly=yes deploy@118.26.111.127 'printf "deploy-key-ok\n"' -ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no -o NumberOfPasswordPrompts=0 root@118.26.111.127 true +ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no -o NumberOfPasswordPrompts=0 ubuntu@118.26.111.127 true ``` -Expected: the first command prints `deploy-key-ok`. The second command is rejected. Only now close the original root session. +Expected: the first two commands print `admin-key-ok` and `deploy-key-ok`. The password-only command is rejected. Only now close the original password-authenticated session. ### Task 4: Install Docker and Bound Its Disk Usage @@ -296,7 +315,7 @@ Expected: the first command prints `deploy-key-ok`. The second command is reject - [ ] **Step 1: Install Docker from Docker's official apt repository** -Use the provider's authenticated rescue/console root session. The `deploy` user intentionally has no general sudo access. Run: +Use the authenticated `ubuntu` admin-key session and enter `sudo -i`. The `deploy` user intentionally has no general sudo access. Run: ```bash apt-get update @@ -321,7 +340,7 @@ Expected: packages come from `download.docker.com`; no convenience `curl | sh` i - [ ] **Step 2: Configure bounded Docker logs** -Run as root on the fresh server: +Run in the `sudo -i` admin shell on the fresh server: ```bash printf '%s\n' \ @@ -340,7 +359,7 @@ Expected: `docker info --format '{{.LoggingDriver}}'` prints `local`. - [ ] **Step 3: Allow the deploy user to run Docker** -Run as root: +Run in the `sudo -i` admin shell: ```bash usermod -aG docker deploy @@ -593,10 +612,10 @@ Expected: 22 is reachable. Before application deployment, 80/443 may refuse beca - [ ] **Step 3: Reboot once and verify the bootstrap survives** -From the provider's authenticated console root session: +From the authenticated `ubuntu` admin-key session: ```bash -systemctl reboot +sudo systemctl reboot ``` Wait for the provider console to report the VPS online, then run locally: @@ -613,6 +632,7 @@ Expected: deploy-key login works after reboot, swap is present, Docker is `activ Go only if all are true: ```text +ubuntu admin key works deploy key works root/password SSH is disabled host-key fingerprints match diff --git a/docs/superpowers/specs/2026-07-20-staging-server-design.md b/docs/superpowers/specs/2026-07-20-staging-server-design.md index 5aca0d04..bfb65ae1 100644 --- a/docs/superpowers/specs/2026-07-20-staging-server-design.md +++ b/docs/superpowers/specs/2026-07-20-staging-server-design.md @@ -25,7 +25,7 @@ staging 不得写入生产数据库,不得复用 service-role key、数据库 ## 服务器基础设计 - 操作系统:Ubuntu 24.04 LTS x86_64。 -- 访问:首次通过供应商 root 凭据进入;建立 `deploy` 用户和独立 Ed25519 key;验证 key 登录后再关闭 SSH 密码登录和直接 root 登录。 +- 访问:供应商默认 SSH 用户为 `ubuntu`;本机管理密钥只授权给 `ubuntu`,GitHub deploy 密钥只授权给 `deploy`。两个 key 登录都验证成功后再关闭 SSH 密码登录和直接 root 登录。 - 内存:2 vCPU / 4 GB RAM,增加 4 GB swap;staging 部署串行执行,避免构建峰值并发。 - 防火墙:只开放 SSH、80、443;Python API 5200 和 Next.js 3000 只在 Docker 网络暴露。 - 运行时:Docker Engine、Buildx 和 Compose plugin,从 Docker 官方 apt repository 安装。 @@ -89,7 +89,7 @@ push staging 服务器基础验收: -- deploy key 登录成功,密码/root 登录按设计受限; +- ubuntu admin key 与 deploy key 分别登录成功,密码/root 登录按设计受限; - UFW 与云防火墙只开放预期端口; - Docker/Compose 正常;swap 生效;重启后容器能恢复。 @@ -128,4 +128,3 @@ push staging - 不购买或部署国内后端服务器。 - 不在本机运行大模型。 - 不在 staging 和 production 之间做应用双写。 -