KeiSeiKit-1.0/_blocks/deploy-vps-generic.md
Parfii-bot 0be354a920 KeiSeiKit-public — clean state
Single-commit clean baseline after security scrub of niche-tells,
project codenames, internal jargon, and contributor-email leaks.

Contents:
- 100 Rust crates (_primitives/_rust/)
- 37 agent manifests (_manifests/) + generated specs (_generated/)
- 67 user-invocable skills (skills/)
- 33 hooks (hooks/)
- Composition blocks (_blocks/)
- Documentation (docs/, README.md)
- TS adapter packages (_ts_packages/)
- Assembler (_assembler/)
- Roles (_roles/)
- Templates (_templates/)
- Forgejo CI (.forgejo/)

Author: Denis Parfionovich <info@greendragon.info>

License: see LICENSE.
2026-05-01 12:09:03 +08:00

79 lines
3.5 KiB
Markdown

# DEPLOY — Generic VPS (provider-agnostic cloud-init + ssh-first-contact)
**Target providers:** DigitalOcean Droplets, Vultr, UpCloud, Linode/Akamai. Each has slightly different Terraform providers + CLIs, but the Day-0 contract is identical: **boot a Debian/Ubuntu image with a cloud-init user-data blob; add one admin SSH key; nothing else.**
**Day-0 cloud-init blob (`cloud-init.yaml`) — universal:**
```yaml
#cloud-config
hostname: kei-${env}-${role}
timezone: UTC
package_update: true
package_upgrade: true
packages:
- ufw
- fail2ban
- unattended-upgrades
- auditd
- needrestart
- curl
- jq
users:
- name: keiadmin
groups: sudo
shell: /bin/bash
sudo: ALL=(ALL) NOPASSWD:ALL
ssh_authorized_keys:
- ${ADMIN_PUBKEY}
ssh_pwauth: false
disable_root: true
write_files:
- path: /etc/ssh/sshd_config.d/99-kei.conf
permissions: '0644'
content: |
PasswordAuthentication no
PermitRootLogin no
MaxAuthTries 3
AllowUsers keiadmin
ClientAliveInterval 120
ClientAliveCountMax 2
runcmd:
- [ systemctl, restart, ssh ]
- [ ufw, default, deny, incoming ]
- [ ufw, default, allow, outgoing ]
- [ ufw, allow, 22/tcp ]
- [ ufw, --force, enable ]
```
The blob is intentionally provider-neutral. Provider-specific bits (private-network bring-up, metadata service quirks) go in a short appendix the provisioner appends. See `_primitives/harden-base.sh` for post-boot hardening re-runs.
**SSH-first-contact (`ssh-first-contact.sh` pattern):**
```bash
# Wait for cloud-init to finish AND sshd to be ready on the new IP.
for i in $(seq 1 60); do
ssh -o ConnectTimeout=3 -o StrictHostKeyChecking=accept-new \
"keiadmin@$IP" "cloud-init status --wait" && break
sleep 5
done
ssh "keiadmin@$IP" "sudo test -f /var/lib/cloud/instance/boot-finished"
```
`StrictHostKeyChecking=accept-new` is OK only for the FIRST contact (TOFU). Store the fingerprint to `~/.ssh/known_hosts`; subsequent connects use default strict mode. Never use `StrictHostKeyChecking=no` — accepts MitM silently.
**Terraform skeleton (provider-agnostic via vars):**
```hcl
variable "provider_kind" {} # "digitalocean" | "vultr" | "upcloud" | "linode"
variable "region" {}
variable "size_slug" {} # provider-specific size id
variable "admin_pubkey" {} # raw ssh-ed25519 …
locals {
user_data = templatefile("${path.module}/cloud-init.yaml", { ADMIN_PUBKEY = var.admin_pubkey })
}
# ... then a module-per-provider resource that all read `local.user_data`
```
Keep TF state **local per-env-per-dev by default**; upgrade to remote backend (R2, S3, Terraform Cloud) only when ≥ 2 humans share state.
**Per-provider gotchas (verified 2026-04-21):**
- **DigitalOcean:** Marketplace "Docker" images skip unattended-upgrades — start from plain Debian 12 instead. IPv6 requires `ipv6 = true` on the droplet.
- **Vultr:** `vultr-cli` needs `VULTR_API_KEY`; default firewall is OPEN — attach a firewall group or rely solely on ufw.
- **UpCloud:** IPs rotate on full stop+start unless you request `floating_ip`. Consider Finnish ASN if Hetzner is blocked or rate-limited for your geo.
- **Linode:** cloud-init runs before disk resize on some plans → `growpart` may need a rerun on first `ssh`.
**Forbidden:** baking the admin private key into an AMI/snapshot; reusing one SSH keypair across envs; letting cloud-init pull scripts from a mutable URL (`curl … | bash` in `runcmd:` — pin to a hash); running `apt-get dist-upgrade -y` in `runcmd` without `needrestart` to surface pending reboots.