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.
3.5 KiB
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:
#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):
# 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):
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 = trueon the droplet. - Vultr:
vultr-clineedsVULTR_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 →
growpartmay need a rerun on firstssh.
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.