vibed-infra 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -19,6 +19,8 @@ git add dist && git commit && git push
19
19
 
20
20
  ## Operator flow (VPS)
21
21
 
22
+ Non-root agent prep (SSH, Docker group, uid 1000 data dirs): [`skills/agent-vps-prep/SKILL.md`](skills/agent-vps-prep/SKILL.md).
23
+
22
24
  ```bash
23
25
  # 1) DNS — paste dist/DNS-SKILL.md into AU browser agent
24
26
  # 2) App roles
@@ -62,7 +64,7 @@ See [`skills/infra-update-agent/SKILL.md`](skills/infra-update-agent/SKILL.md).
62
64
  | `templates/update-agent/` | Queue agent + webhook |
63
65
  | `templates/persist-logs/` | Shipper install |
64
66
  | `lib/persistlog/` | Python append / seal / replay |
65
- | `skills/` | system-gateway, infra-update-agent, infra-cicd, infra-packager, persist-logs, dns-configure |
67
+ | `skills/` | system-gateway, agent-vps-prep, infra-update-agent, infra-cicd, infra-packager, persist-logs, dns-configure |
66
68
 
67
69
  ## Environment
68
70
 
@@ -220,7 +220,16 @@ def dump_yaml(data: Any, indent: int = 0) -> str:
220
220
  else:
221
221
  lines.append(f"{sp}{pref}{k}: {v}")
222
222
  else:
223
- lines.append(f"{sp}- {item}")
223
+ s = str(item)
224
+ if isinstance(item, bool):
225
+ s = "true" if item else "false"
226
+ elif item is None:
227
+ s = ""
228
+ elif any(c in s for c in ":{}[]#&*!|>'\"%@`") or s == "" or s.lower() in (
229
+ "null", "true", "false", "yes", "no", "on", "off",
230
+ ):
231
+ s = json.dumps(s)
232
+ lines.append(f"{sp}- {s}")
224
233
  return "\n".join(lines) + ("\n" if lines else "")
225
234
  return f"{sp}{data}\n"
226
235
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibed-infra",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "description": "Product-agnostic VPS packager: wget install, Docker Compose, TLS/nginx, auto-update",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -0,0 +1,119 @@
1
+ ---
2
+ name: agent-vps-prep
3
+ description: >-
4
+ Prepare a fresh VPS for vibed-infra installs run by an automation agent
5
+ (cloud_agent): SSH key, sudoers, Docker group, data-dir ownership, firewall.
6
+ ---
7
+
8
+ # Prepare a VPS for vibed-infra (agent operator)
9
+
10
+ ## Who this is for
11
+
12
+ Non-root user (e.g. `cloud_agent`) that will run `wget …/dist/install-*.sh | bash`,
13
+ `setup-tls.sh` (docker certbot), and `start-*.sh` without interactive sudo.
14
+
15
+ ## 1. Create the operator user
16
+
17
+ (as root)
18
+
19
+ ```bash
20
+ adduser --disabled-password --gecos "" cloud_agent
21
+ mkdir -p /home/cloud_agent/.ssh
22
+ chmod 700 /home/cloud_agent/.ssh
23
+ # paste agent public key:
24
+ echo 'ssh-ed25519 AAAA… cloud_agent' >> /home/cloud_agent/.ssh/authorized_keys
25
+ chmod 600 /home/cloud_agent/.ssh/authorized_keys
26
+ chown -R cloud_agent:cloud_agent /home/cloud_agent/.ssh
27
+ ```
28
+
29
+ ## 2. SSH from the agent machine
30
+
31
+ Generate a key if needed, then connect with `StrictHostKeyChecking=accept-new`:
32
+
33
+ ```bash
34
+ ssh-keygen -t ed25519 -f ~/.ssh/cloud_agent_vps -N ""
35
+ ssh -o StrictHostKeyChecking=accept-new -i ~/.ssh/cloud_agent_vps cloud_agent@VPS_IP
36
+ ```
37
+
38
+ ## 3. Install Docker + add user to docker group
39
+
40
+ ```bash
41
+ # as root — use distro docs or https://get.docker.com
42
+ usermod -aG docker cloud_agent
43
+ # cloud_agent must re-login for group to apply
44
+ ```
45
+
46
+ Verify as `cloud_agent`: `docker ps` (no sudo).
47
+
48
+ ## 4. Home ownership
49
+
50
+ If home was created oddly (root-owned `$HOME`):
51
+
52
+ ```bash
53
+ sudo chown -R cloud_agent:cloud_agent /home/cloud_agent
54
+ ```
55
+
56
+ vibed-infra defaults: `~/services/gateway`, `~/services/vibed-infra`, product dirs under `~/services/<app>/`.
57
+
58
+ ## 5. Container data directory ownership (uid 1000)
59
+
60
+ Product images often run as `node` (uid/gid **1000**). Bind-mounted `./data` and `./logs` created by `cloud_agent` (e.g. uid 1001) cause `SQLITE_CANTOPEN` / `EACCES`.
61
+
62
+ **One-shot after first install (or before first start), as root/sudo:**
63
+
64
+ ```bash
65
+ # Trustless Commerce / typical vibed apps — adjust paths to your INSTALL_DIRs
66
+ sudo chown -R 1000:1000 \
67
+ /home/cloud_agent/services/*/api/data \
68
+ /home/cloud_agent/services/*/api/persist-logs \
69
+ /home/cloud_agent/services/*/nodes/logs
70
+ ```
71
+
72
+ Or per product:
73
+
74
+ ```bash
75
+ sudo chown -R 1000:1000 ~/services/tctest/api/data ~/services/tctest/api/persist-logs
76
+ sudo chown -R 1000:1000 ~/services/tcmain/api/data ~/services/tcmain/api/persist-logs
77
+ sudo chown -R 1000:1000 ~/services/tctest/nodes/logs ~/services/tcmain/nodes/logs
78
+ ```
79
+
80
+ Optional: allow passwordless chown for the agent (tight sudoers):
81
+
82
+ ```bash
83
+ # /etc/sudoers.d/cloud_agent-chown
84
+ cloud_agent ALL=(root) NOPASSWD: /usr/bin/chown -R 1000\:1000 /home/cloud_agent/services/*
85
+ ```
86
+
87
+ Prefer the explicit one-shot; sudoers is optional.
88
+
89
+ Note: start scripts may try `sudo chown` or a docker alpine chown — host sudo still helps for empty dirs created as the operator user.
90
+
91
+ ## 6. Ports + DNS
92
+
93
+ - Free **80/443** for the host gateway (only vps-gateway binds them)
94
+ - Point DNS A records at `gateway.publicIp` before `setup-tls.sh` (Let’s Encrypt)
95
+
96
+ ## 7. TLS (no host certbot required)
97
+
98
+ With Docker, `setup-tls.sh` uses `certbot/certbot` and writes PEMs under `~/services/gateway/certs/`. No sudo needed for LE when docker works. See [system-gateway](../system-gateway/SKILL.md).
99
+
100
+ ## 8. First product install
101
+
102
+ ```bash
103
+ export INSTALL_DIR=$HOME/services/myapp/api
104
+ mkdir -p "$INSTALL_DIR"
105
+ wget -qO- https://raw.githubusercontent.com/ORG/REPO/main/deploy/.../dist/install-api.sh | bash
106
+ # edit .env, then ./start-api.sh
107
+ # repeat ui, nodes, gateway
108
+ ```
109
+
110
+ **Important:** `export INSTALL_DIR=...` before the pipe — `INSTALL_DIR=... wget | bash` does **not** pass the var into bash.
111
+
112
+ ## 9. Checklist
113
+
114
+ - [ ] `ssh cloud_agent@VPS` works with key only
115
+ - [ ] `docker ps` works without sudo
116
+ - [ ] `$HOME` owned by `cloud_agent`
117
+ - [ ] uid 1000 chown on data/logs after first mkdir/install
118
+ - [ ] DNS → VPS IP
119
+ - [ ] install-gateway + setup-tls + start-gateway
@@ -7,6 +7,8 @@ description: >-
7
7
 
8
8
  # System-wide host gateway (multi-app)
9
9
 
10
+ Fresh VPS as non-root agent: see [agent-vps-prep](../agent-vps-prep/SKILL.md).
11
+
10
12
  ## Model
11
13
 
12
14
  ```
@@ -55,7 +57,7 @@ cd ~/services/gateway
55
57
  ./setup-tls.sh --force # re-issue after host or IP change
56
58
  ```
57
59
 
58
- - **Production:** `setup-tls.sh` issues Let’s Encrypt via docker `certbot/certbot` by default (config under `$GATEWAY_HOME/letsencrypt`, PEMs copied to `$GATEWAY_HOME/certs/`). No sudo required when Docker is available. Host certbot + sudo still preferred when present (same `--config-dir` under gateway home). Webroot if gateway is up, else standalone. On failure, fix DNS then re-run `setup-tls.sh`.
60
+ - **Production:** `setup-tls.sh` issues Let’s Encrypt via docker `certbot/certbot` by default (config under `$GATEWAY_HOME/letsencrypt`, PEMs installed into `$GATEWAY_HOME/certs/` — direct `cp` when readable, else alpine container copy+chown for root-owned `live/`). No sudo required when Docker is available. Host certbot + sudo still preferred when present (same `--config-dir` under gateway home). Webroot if gateway is up, else standalone. On failure, fix DNS then re-run `setup-tls.sh`.
59
61
  - **Lab/CI:** `TLS_MODE=lab` → multi-SAN self-signed under `./certs/`.
60
62
 
61
63
  ## Webhook path (immediate docker pull)
@@ -64,6 +66,7 @@ Host `00-default.conf` (HTTP) and each app `sites.conf` (HTTPS) proxy `/_vibed/h
64
66
 
65
67
  ## Pitfalls
66
68
 
69
+ - Operator user / Docker / uid 1000 data dirs: [agent-vps-prep](../agent-vps-prep/SKILL.md).
67
70
  - Do not run a second standalone nginx on 80/443.
68
71
  - Container names in `sites.conf` must match running API/UI names on `vps-edge`.
69
72
  - After cert renewal or `setup-tls.sh`, reload happens automatically when the gateway container is already running.
@@ -254,13 +254,33 @@ EOF
254
254
  return 0
255
255
  fi
256
256
 
257
- local live="${LE_HOME}/live/${primary}"
258
- if [[ ! -f "${live}/fullchain.pem" || ! -f "${live}/privkey.pem" ]]; then
259
- echo "certbot succeeded but ${live} PEMs missing" >&2
260
- return 1
261
- fi
262
- cp -L "${live}/fullchain.pem" "${CERT_DIR}/fullchain.pem"
263
- cp -L "${live}/privkey.pem" "${CERT_DIR}/privkey.pem"
257
+ # Copy PEMs into CERT_DIR without sudo. Docker certbot leaves root-owned live/
258
+ # (mode 700); host path may be readable after sudo chown above.
259
+ install_pems_to_certs() {
260
+ local primary="$1"
261
+ local live="${LE_HOME}/live/${primary}"
262
+ mkdir -p "$CERT_DIR"
263
+ if [[ -r "${live}/fullchain.pem" && -r "${live}/privkey.pem" ]]; then
264
+ cp -L "${live}/fullchain.pem" "${CERT_DIR}/fullchain.pem"
265
+ cp -L "${live}/privkey.pem" "${CERT_DIR}/privkey.pem"
266
+ elif command -v docker >/dev/null 2>&1; then
267
+ # certbot container wrote root-owned live/; copy+chown via alpine
268
+ docker run --rm \
269
+ -v "${LE_HOME}:/etc/letsencrypt:ro" \
270
+ -v "${CERT_DIR}:/out" \
271
+ "${CERTBOT_HELPER_IMAGE:-alpine:3.20}" \
272
+ sh -c "cp -L /etc/letsencrypt/live/${primary}/fullchain.pem /out/fullchain.pem && cp -L /etc/letsencrypt/live/${primary}/privkey.pem /out/privkey.pem && chown $(id -u):$(id -g) /out/fullchain.pem /out/privkey.pem && chmod 644 /out/fullchain.pem && chmod 600 /out/privkey.pem"
273
+ else
274
+ echo "Cannot read ${live} PEMs (root-owned?) and docker unavailable to copy them" >&2
275
+ return 1
276
+ fi
277
+ if [[ ! -f "${CERT_DIR}/fullchain.pem" || ! -f "${CERT_DIR}/privkey.pem" ]]; then
278
+ echo "Failed to install PEMs into ${CERT_DIR}" >&2
279
+ return 1
280
+ fi
281
+ }
282
+
283
+ install_pems_to_certs "$primary"
264
284
  chmod 644 "${CERT_DIR}/fullchain.pem"
265
285
  chmod 600 "${CERT_DIR}/privkey.pem"
266
286
  patch_env_key TLS_FULLCHAIN "${CERT_DIR}/fullchain.pem"