docs: use ubuntu for staging bootstrap
This commit is contained in:
@@ -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 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 之间做应用双写。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user