docs: use ubuntu for staging bootstrap

This commit is contained in:
Jesse_Chen
2026-07-20 15:31:36 +08:00
parent 7a4c278886
commit 3959ff2b3a
2 changed files with 84 additions and 65 deletions
@@ -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
@@ -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 swapstaging 部署串行执行,避免构建峰值并发。
- 防火墙:只开放 SSH、80、443Python 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 之间做应用双写。