Skip to content

fix(tailscale): official doc validation — schema, flags, key-expiry - #826

Merged
POWERFULMOVES merged 16 commits into
mainfrom
fix/tailscale-hostinger-doc-validation
Mar 8, 2026
Merged

POWERFULMOVES merged 16 commits into
mainfrom
fix/tailscale-hostinger-doc-validation

Conversation

@POWERFULMOVES

@POWERFULMOVES POWERFULMOVES commented Mar 8, 2026

Copy link
Copy Markdown
Owner

Summary

  • Terraform: Align Hostinger provider schema to v0.1.22 — fix provider version, ssh_key schema, post_install_script, ip_address→ipv4_address, template_id refs
  • Tailscale: Standardize --auth-key flag (replace deprecated --authkey), add --key-expiry=off for persistent node auth across all provisioning scripts
  • Docs: Replace WireGuard references with Tailscale in VPS README
  • Shell: Fix local keyword used outside function scope in deploy-vps.sh

Dependency

Branches from audit/production-sweep-2026-03-08 (PR #825). Merge PR #825 first, then this PR applies cleanly.

Test plan

  • bash -n syntax check passes on all 4 shell scripts
  • No --authkey (deprecated) in deploy scripts
  • No wireguard or 10.0.0. references in README
  • No .ip_address (should be .ipv4_address) in Terraform
  • terraform validate on mcp-integration.tf (requires provider init)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Multi-node VPS fleet support (kvm4-1, kvm4-2, kvm2): end-to-end provisioning, deploy, status, and UI entries; Tailscale mesh with exit-node support.
  • Infrastructure & CI

    • CI runners migrated to ubuntu-latest; added concurrency controls and parallelism caps; multi-arch build setup (QEMU/Buildx).
  • Configuration

    • Terraform, env, and secrets updates for multi-node fleet and Hostinger variables; new Make targets for Supabase collation.
  • Documentation

    • Updated docs, dashboards, and next-steps to reflect audit and CI migration.

hunnibear and others added 10 commits March 8, 2026 08:13
Static gates 6/7 PASS, runtime smoke/model-readiness/monitoring/GPU all PASS,
release gates RG-1/2/4/5 PASS (RG-3 collation mismatch pre-existing).
Dashboard and NEXT_STEPS updated with Mar 8 snapshot.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move sql-policy-lint, python-tests, webhook-smoke, yt-dlp-bump, and
deploy-gateway-agent (validate job) off self-hosted runners to
ubuntu-latest. Adds concurrency block to webhook-smoke and removes
QEMU/Buildx steps from yt-dlp-bump (not needed for bump-only job).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move 4 jobs (lint-dockerfiles, check-compose-hardening, audit-env-files,
validate-security-patterns) to ubuntu-latest. docker-bench-security
retains self-hosted with vps label since it needs the Docker daemon.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
build-images: setup-matrix job → ubuntu-latest, max-parallel: 4.
codex-parity-advisory: add concurrency block to prevent queue pile-up.
self-hosted-builds-hardened: max-parallel: 3 to limit runner contention.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add supa-collation-refresh and supa-collation-check Make targets to
automate the C.UTF-8 collation fix for Supabase Postgres. Integrate
check into supa-start. Update PRODUCTION_AUDIT_DASHBOARD and
NEXT_STEPS with RG-3 resolution status and CI runner migration notes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Addresses CodeRabbit nitpick on PR #825 lines 189-190.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update docker-compose VPS override, KVM4 environment files, runner
lane hosts/phase policy, and hardened runner install script.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update CHIT secrets manifest schemas and agent registry configuration
with new service entries and crush configurator improvements.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update sync-secrets-local and deploy-gateway-agent workflows with
CI hardening improvements. Add new gitignore patterns.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add KVM2, KVM4-1, KVM4-2 deploy scripts and VPS status panel to
Pinokio launcher. Update pinokio.js with new menu entries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@github-actions

github-actions Bot commented Mar 8, 2026

Copy link
Copy Markdown
Contributor

Docker Hardening Validation

Hardening Validation Report

Validated: Sun Mar 8 16:52:17 UTC 2026

Services Checked

PMOVES.AI Docker Hardening Validation

[INFO] Checking: pmoves/docker-compose.hardened.yml

[INFO] Validating: hi-rag-gateway-v2
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: extract-worker
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: langextract
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: presign
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: render-webhook
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: retrieval-eval
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: pdf-ingest
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: jellyfin-bridge
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: invidious-companion-proxy
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: ffmpeg-whisper
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: media-video
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: media-audio
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: hi-rag-gateway-v2-gpu
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: hi-rag-gateway-gpu
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: deepresearch
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: supaserch
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: publisher-discord
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: mesh-agent
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: nats-echo-req
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: nats-echo-res
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: publisher
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: analysis-echo
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: graph-linker
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: comfy-watcher
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: grayjay-plugin-host
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: agent-zero
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: archon
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: channel-monitor
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: pmoves-yt
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: notebook-sync
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: supabase_service_role_key
[WARN] No user directive
[WARN] No read_only directive
[WARN] No cap_drop: ["ALL"]
[WARN] No no-new-privileges
[WARN] No resource limits

[INFO] Validating: supabase_jwt_secret
[WARN] No user directive
[WARN] No read_only directive
[WARN] No cap_drop: ["ALL"]
[WARN] No no-new-privileges
[WARN] No resource limits

======================================
Summary: 120 passed, 40 warnings, 0 errors

@coderabbitai

coderabbitai Bot commented Mar 8, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9a2d87ca-f052-4384-9c75-3c74ead2be5e

📥 Commits

Reviewing files that changed from the base of the PR and between c671f47 and c7830d7.

📒 Files selected for processing (6)
  • .gitignore
  • deploy/provision/hostinger-kvm-setup.sh
  • deploy/provision/kvm2-exit-node.sh
  • deploy/scripts/deploy-vps.sh
  • pmoves/examples/distributed/vps/README.md
  • pmoves/scripts/tailscale_setup.sh

📝 Walkthrough

Walkthrough

Adds multi-node Hostinger VPS fleet support (kvm4-1, kvm4-2, kvm2): provisioning, Tailscale mesh and exit-node handling, hardened GitHub Actions runner installation, deploy orchestration (SSH over Tailscale), Terraform multi-node resources/variables, CI runner/workflow adjustments, secrets and Pinokio UI/status integrations.

Changes

Cohort / File(s) Summary
CI workflows & runner selection
.github/workflows/build-images.yml, .github/workflows/hardening-validation.yml, .github/workflows/python-tests.yml, .github/workflows/sql-policy-lint.yml, .github/workflows/webhook-smoke.yml, .github/workflows/yt-dlp-bump.yml, .github/workflows/codex-parity-advisory.yml, .github/workflows/deploy-gateway-agent.yml, .github/workflows/self-hosted-builds-hardened.yml, .github/workflows/sync-secrets-local.yml
Many jobs moved from self-hosted labels to ubuntu-latest; added concurrency controls and max-parallel caps; added QEMU/Buildx setup in yt-dlp flow; improved secrets verification and exposed Hostinger secrets in sync workflow.
Provisioning & bootstrap scripts
deploy/provision/hostinger-kvm-setup.sh, deploy/provision/kvm2-exit-node.sh, pmoves/terraform/bootstrap-script.sh
New end-to-end provisioning and exit-node scripts plus Terraform-templated bootstrap: system hardening, Docker/Compose, Tailscale (kvm2 exit-node), runner install (hardened or fallback), and repo/workdir setup.
VPS deployment orchestration & Pinokio runbooks
deploy/scripts/deploy-vps.sh, pbnj/pinokio/api/pmoves-pbnj/kvm4-1-deploy.json, pbnj/pinokio/api/pmoves-pbnj/kvm4-2-deploy.json, pbnj/pinokio/api/pmoves-pbnj/kvm2-deploy.json, pbnj/pinokio/api/pmoves-pbnj/vps-status.json
Added fleet deploy script and Pinokio pages: tailscale SSH deploys, per-node health checks, connectivity checks, aggregated fleet/status reporting and UI entries.
Runner install & Tailscale tooling
.claude/scripts/setup-runner.sh, deploy/runners/vps/install-hardened.sh, pmoves/scripts/tailscale_setup.sh
Extended supported host types/labels (kvm4-1, kvm4-2, kvm2), bumped runner defaults, added node detection, dynamic tailscale tag generation, exit-node advertise logic and connection readiness handling.
Terraform multi-node & variables
pmoves/terraform/mcp-integration.tf, pmoves/terraform/variables.auto.tfvars.example
Added kvm_nodes map and hostinger_vps.fleet for multi-instance provisioning, sensitive vars (tailscale_authkey, github_pat, os_template_id), fleet outputs, and switched references to ipv4_address; adjusted resource signatures.
Secrets & manifests
pmoves/chit/secrets_manifest.yaml, pmoves/chit/secrets_manifest_v2.yaml, .github/workflows/sync-secrets-local.yml
Added Hostinger node secrets (IP and user entries for kvm4-1/kvm4-2/kvm2) and alias for HOSTINGER_API_TOKEN; surfaced new secrets into sync workflow environment.
Runner inventory & policies
pmoves/integrations/github-runners/compose/lane_hosts.json, pmoves/integrations/github-runners/compose/runner_phase_policy.json, pmoves/config/agent_registry.yaml
Registered kvm4-1/kvm4-2/kvm2 hosts, updated provisioning targets to hardened installer, added vps-deployment phase and optional_online for production, and added vps_fleet_manager agent entry.
Compose, envs & examples
pmoves/docker-compose.vps.override.yml, pmoves/examples/distributed/vps/README.md, pmoves/examples/distributed/vps/kvm4-1.env, pmoves/examples/distributed/vps/kvm4-2.env
Removed WireGuard service/network; migrated networking and hostnames to Tailscale mesh; updated service URLs, NATS auth/config, and provider/model envs.
Pinokio UI/menu
pbnj/pinokio/api/pmoves-pbnj/pinokio.js
Added "VPS Fleet" menu entries linking new deploy and status JSON pages.
Makefile & tooling
pmoves/Makefile, .gitignore, pmoves/tools/crush_configurator.py
Added Supabase collation Make targets; added Terraform-specific ignores to .gitignore; added Hostinger MCP spec entry to crush configurator.
Documentation & evidence
pmoves/docs/*, many pmoves/docs/evidence/submodule_layer/*
Updated production audit / next-steps docs (Mar 8 snapshot), refreshed timestamps and some submodule commit pointers, and expanded tooling-audit overlap candidates.

Sequence Diagram(s)

sequenceDiagram
    participant Operator as Operator
    participant Terraform as Terraform
    participant Host as Hostinger KVM
    participant Bootstrap as Bootstrap Script
    participant Tailscale as Tailscale Mesh
    participant Docker as Docker Services
    participant GHA as GitHub Actions Runner
    participant Pinokio as Pinokio Orchestrator

    Operator->>Terraform: apply kvm_nodes (kvm4-1,kvm4-2,kvm2)
    Terraform->>Host: provision VPS instance(s) + inject bootstrap
    Host->>Bootstrap: run bootstrap-script (hardening, docker, tailscale)
    Bootstrap->>Tailscale: tailscale up (authkey, tags, exit-node for kvm2)
    Tailscale->>Host: assign hostname & routes
    Host->>Docker: pull/start services (docker compose)
    Host->>GHA: install/register self-hosted runner (labels)
    Operator->>Pinokio: trigger deploy/status via Tailscale SSH
    Pinokio->>Host: ssh -> git pull, docker compose pull/up, health checks
    Host->>Pinokio: report health checks
    Pinokio->>Operator: aggregate fleet status
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

codex

Suggested reviewers

  • hunnibear

Poem

🐰
I nudge the tails that weave the mesh,
I hop through scripts and make them fresh.
KVMs awake, the services hum,
Terraform gardens — deployments come.
Hooray! The fleet is snug and plush.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: fixing Tailscale documentation validation, schema issues, flags, and key-expiry handling across Terraform and shell scripts.
Description check ✅ Passed The description is mostly complete with a clear summary of changes, test plan status, and documentation of dependencies. However, it lacks explicit testing command output and the CHIT Contract Check checkbox remains unchecked as required by the template.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/tailscale-hostinger-doc-validation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

- HOSTINGER_API_KEY → HOSTINGER_API_TOKEN (canonical per secrets_manifest_v2)
- tensorzero → tensorzero-gateway in kvm4-1-deploy.json (match compose service name)
- Remove kvm4 label from kvm4-2 runner (prevent cross-routing)
- RG-3 manual checklist → make supa-collation-check (automated target)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
pmoves/chit/secrets_manifest_v2.yaml (1)

722-776: ⚠️ Potential issue | 🟠 Major

YAML syntax error: duplicate key: entries in surreal_ targets.*

Multiple entries have duplicate key: lines within the same target block, which is invalid YAML and will cause parsing errors or unexpected behavior:

  • Lines 728-729: surreal_address
  • Lines 739-740: surreal_database
  • Lines 750-751: surreal_namespace
  • Lines 761-762: surreal_port
  • Lines 772-773: surreal_url
🐛 Fix duplicate key entries
 - id: surreal_address
   source:
     type: cgp
     label: SURREAL_ADDRESS
   targets:
   - file: .env.generated
     key: SURREAL_ADDRESS
-    key: SURREAL_ADDRESS
   - github_secret: SURREAL_ADDRESS

Apply similar fix to surreal_database, surreal_namespace, surreal_port, and surreal_url entries.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pmoves/chit/secrets_manifest_v2.yaml` around lines 722 - 776, The target
blocks for id values surreal_address, surreal_database, surreal_namespace,
surreal_port, and surreal_url contain duplicate "key:" entries per file target
which breaks YAML; for each target under the file: .env.generated in the entries
(look for id: surreal_address, id: surreal_database, id: surreal_namespace, id:
surreal_port, id: surreal_url) remove the duplicate "key:" line so each file
target has a single "key: <ENV_NAME>" entry; keep the other targets
(github_secret and docker_secret) unchanged and verify indentation/spacing
remains valid YAML.
pmoves/terraform/mcp-integration.tf (1)

268-275: ⚠️ Potential issue | 🟡 Minor

timestamp() in tags will cause perpetual Terraform plan drift.

Using timestamp() in common_tags.CreatedAt means every terraform plan will show changes, even when no actual infrastructure changes are needed. This defeats idempotency.

♻️ Consider removing or using a different approach
   common_tags = {
     Project     = var.project_name
     Environment = local.environment
     ManagedBy   = "Terraform"
     Repository  = "PMOVES.AI"
-    CreatedAt   = timestamp()
   }

If you need creation timestamp, consider using lifecycle { ignore_changes = [tags["CreatedAt"]] } on resources, or set it only during initial creation via a null_resource trigger.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pmoves/terraform/mcp-integration.tf` around lines 268 - 275, The use of
timestamp() in common_tags.CreatedAt causes perpetual plan drift; remove
timestamp() from common_tags and instead either (A) stop setting CreatedAt in
the shared common_tags map and set a CreatedAt tag only once during resource
creation (for example via a null_resource/one-time data source), or (B) keep
resource-level tags but add lifecycle { ignore_changes = [tags["CreatedAt"]] }
on resources that must keep a mutable CreatedAt tag; update references to
common_tags and the resources using it (common_tags, CreatedAt, timestamp())
accordingly so Terraform plans are idempotent.
pmoves/examples/distributed/vps/kvm4-2.env (1)

49-52: ⚠️ Potential issue | 🟡 Minor

Line 52 should use a placeholder value consistent with lines 43-44.

The ${NEO4J_PASSWORD} variable reference requires the variable to already be defined elsewhere. For an example file, this is unclear to operators. Change line 52 to:

NEO4J_PASSWORD=your-strong-password-here

This matches the placeholder pattern used for SUPABASE_JWT_SECRET and POSTGRES_PASSWORD above, making it clear that users must replace it with their actual password before deployment.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pmoves/examples/distributed/vps/kvm4-2.env` around lines 49 - 52, Replace the
indirect variable reference on the NEO4J_PASSWORD line with an explicit
placeholder consistent with the other example values: change the value for
NEO4J_PASSWORD (found alongside NEO4J_URI and NEO4J_USER) from ${NEO4J_PASSWORD}
to a clear placeholder such as your-strong-password-here so operators know to
supply a real password in the example env file.
🧹 Nitpick comments (11)
pmoves/docs/PRODUCTION_AUDIT_DASHBOARD.md (2)

28-28: Clarify PASS criteria for partial test results.

The smoke test is marked as "PASS" but only 10/12 tests passed (83%). While the note explains that Meilisearch and Neo4j failures are "pre-existing, not running locally," marking this as an unconditional "PASS" may be misleading in a production audit context.

Consider one of these alternatives:

  • smoke: PASS (with known exceptions)
  • smoke: 10/12 PASS
  • smoke: CONDITIONAL PASS (10/12 OK; 2 services not running locally)

This makes the partial nature of the pass immediately visible without requiring readers to parse the parenthetical note.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pmoves/docs/PRODUCTION_AUDIT_DASHBOARD.md` at line 28, The current line
marking the smoke test as "`smoke`: PASS (10/12 OK; Meilisearch + Neo4j WARN —
pre-existing, not running locally)" is misleading because it hides the partial
nature of the pass; update the string to make the partial result explicit (for
example change to "`smoke`: CONDITIONAL PASS (10/12 OK; 2 services not running
locally)" or "`smoke`: 10/12 PASS" or "`smoke`: PASS (with known exceptions)`")
by editing that specific markdown line so readers immediately see the 10/12
result and the known exceptions.

570-570: Improve changelog entry readability.

The Mar 8 changelog entry is accurate but extremely dense, making it difficult to quickly scan for specific information. Consider using bullet points or breaking it into multiple lines.

♻️ Suggested refactor for readability
-| 2026-03-08 | **Post-merge audit sweep:** PRs `#823/`#824 merged. Static gates 6/7 PASS (secrets-audit timeout). Runtime: smoke PASS (10/12), model-readiness 17/17 PASS, monitoring-smoke PASS, auth-alignment 0 errors, GPU smoke PASS. Release gates: RG-1/2/4/5 PASS, RG-3 KNOWN (collation). CI: all 4 runners offline, 4 queued runs (cancel candidates). Live metrics: 0 PRs, 0 CodeQL, 1 Dependabot (medium). |
+| 2026-03-08 | **Post-merge audit sweep:** PRs `#823/`#824 merged. <br>• Static gates: 6/7 PASS (secrets-audit timeout)<br>• Runtime: smoke 10/12, model-readiness 17/17, monitoring-smoke PASS, auth-alignment 0 errors, GPU smoke PASS<br>• Release gates: RG-1/2/4/5 PASS, RG-3 KNOWN<br>• CI: 4 runners offline, 4 queued (cancel candidates)<br>• Metrics: 0 PRs, 0 CodeQL, 1 Dependabot (medium) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pmoves/docs/PRODUCTION_AUDIT_DASHBOARD.md` at line 570, The single-line
changelog entry for "2026-03-08" is too dense; please split it into a short
header plus bulletized items for readability: keep the date and title
("2026-03-08 | Post-merge audit sweep"), then add bullets for PRs merged (PRs
`#823/`#824), static gates (6/7 PASS with secrets-audit timeout), runtime checks
(smoke 10/12, model-readiness 17/17, monitoring-smoke PASS, auth-alignment 0
errors, GPU smoke PASS), release gates (RG-1/2/4/5 PASS, RG-3 KNOWN), CI status
(runners offline, queued runs), and live metrics (PRs/CodeQL/Dependabot counts);
update the markdown line containing the entry so each item is on its own bullet
or subline to improve scannability.
deploy/runners/vps/install-hardened.sh (1)

26-26: Unused variable SCRIPT_DIR.

Static analysis flags SCRIPT_DIR as unused. This is a minor issue — the variable is commonly defined for future use or sourcing relative scripts. Consider removing if not needed, or suppressing with a comment.

Optional: Remove unused variable
-SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

Or suppress the warning:

-SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"  # shellcheck disable=SC2034
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@deploy/runners/vps/install-hardened.sh` at line 26, The SCRIPT_DIR variable
is defined but never used; either remove the assignment to SCRIPT_DIR to
eliminate the unused-variable warning, or explicitly suppress the linter by
adding a suppression comment for that symbol (for example a ShellCheck disable
for SC2034) or otherwise reference SCRIPT_DIR in a follow-up use; locate the
assignment to SCRIPT_DIR and apply one of these fixes to clear the
static-analysis warning.
deploy/provision/hostinger-kvm-setup.sh (3)

165-166: Piping curl output directly to shell has security implications.

While common practice, downloading and executing scripts in one step (curl | sh) prevents inspection before execution and is vulnerable to partial download attacks. The official Docker and Tailscale scripts are generally trusted, but consider documenting this trade-off or providing an alternative for security-conscious deployments.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@deploy/provision/hostinger-kvm-setup.sh` around lines 165 - 166, The script
currently runs the Docker install with a direct pipe ("curl -fsSL
https://get.docker.com | sh"); replace this with a safer two-step approach:
download the installer to a temporary file, allow inspection (or verify
checksum/signature if available), then execute it explicitly (e.g., run the
downloaded file with sh) and remove the temp file afterward; update the line
containing "curl -fsSL https://get.docker.com | sh" to implement this
download-then-execute pattern and add a short comment noting the security
trade-off for future readers.

173-178: Docker Compose version is pinned; consider documenting update strategy.

Version v2.27.0 is hardcoded. This is good for reproducibility but may become outdated. Consider adding a comment about how/when to update this version, or fetching the latest stable release dynamically.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@deploy/provision/hostinger-kvm-setup.sh` around lines 173 - 178, The script
pins Docker Compose via the COMPOSE_VERSION variable (COMPOSE_VERSION="v2.27.0")
which can become stale; change this by either (A) making COMPOSE_VERSION
configurable via an environment variable with a clear comment describing the
update cadence (e.g., COMPOSE_VERSION="${COMPOSE_VERSION:-v2.27.0}" plus a brief
note on when to bump it) or (B) implement a small dynamic lookup before the curl
(e.g., query the GitHub releases API for the latest stable compose tag) and
assign that to COMPOSE_VERSION; ensure the rest of the block (the curl target
"https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-$(uname
-s)-$(uname -m)" and the destination
/usr/local/lib/docker/cli-plugins/docker-compose) uses the chosen
COMPOSE_VERSION and add a one-line comment explaining the chosen update
strategy.

146-151: SSH hardening sed commands assume default configuration.

The sed commands assume commented-out defaults (e.g., #PermitRootLogin yes). If sshd_config has already been modified or uses different formatting, these commands may silently fail to apply the intended changes.

♻️ Consider using more robust pattern matching
-    sed -i 's/#PermitRootLogin yes/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config
-    sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
-    sed -i 's/#MaxAuthTries 6/MaxAuthTries 3/' /etc/ssh/sshd_config
+    # Handle both commented and uncommented forms
+    sed -i -E 's/^#?\s*PermitRootLogin\s+.*/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config
+    sed -i -E 's/^#?\s*PasswordAuthentication\s+.*/PasswordAuthentication no/' /etc/ssh/sshd_config
+    sed -i -E 's/^#?\s*MaxAuthTries\s+.*/MaxAuthTries 3/' /etc/ssh/sshd_config
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@deploy/provision/hostinger-kvm-setup.sh` around lines 146 - 151, The SSH
hardening sed commands assume commented default lines and can fail silently;
update the "Hardening SSH" block to robustly replace any existing
PermitRootLogin, PasswordAuthentication, and MaxAuthTries entries (matching
commented or uncommented and any spacing) using regex-style matching (e.g.,
match ^\s*#?\s*PermitRootLogin\s+.*) to replace with the desired values, and if
a pattern is not found append the setting to /etc/ssh/sshd_config; ensure you
still restart sshd (systemctl restart sshd) after making the replacements.
pmoves/terraform/variables.auto.tfvars.example (2)

1-38: Consider adding tailscale_authkey and github_pat placeholders.

The mcp-integration.tf defines tailscale_authkey and github_pat as sensitive variables, but they're not included in this example file. Adding commented placeholders would help users discover these options.

♻️ Suggested additions
 # Monitoring
 monitoring_retention_days = 30
+
+# Optional: Tailscale mesh network
+# tailscale_authkey = "tskey-auth-xxx"
+
+# Optional: GitHub PAT for self-hosted runner registration
+# github_pat = "ghp_xxx"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pmoves/terraform/variables.auto.tfvars.example` around lines 1 - 38, Add
commented placeholder entries for the sensitive variables referenced in
mcp-integration.tf by including tailscale_authkey and github_pat in the
variables.auto.tfvars.example; add them as commented lines (e.g., #
tailscale_authkey = "tskey_..." and # github_pat = "ghp_...") with a note that
they are sensitive and should be filled in the local variables.auto.tfvars (not
committed), and include brief example formats for both so users can discover and
supply those values.

18-20: Example password uses predictable patterns.

While this is an example file, the password YourStr0ngP@ssword! uses common substitution patterns (o→0, a→@) that are easily guessable. Consider using a more random-looking example or adding a comment encouraging use of a password generator.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pmoves/terraform/variables.auto.tfvars.example` around lines 18 - 20, The
example uses an easily guessable pattern for the root_password variable; update
the root_password value to a non-patterned placeholder (e.g., a random-looking
string or simply "" or "<GENERATE_SECURE_PASSWORD>") and add a one-line comment
next to root_password recommending use of a password manager or generator; leave
ssh_public_key as-is but consider adding a brief comment that an SSH public key
should be provided instead of a password for better security.
pbnj/pinokio/api/pmoves-pbnj/kvm2-deploy.json (1)

9-9: Consider adding explicit error messaging for SSH failures.

The current flow relies on implicit && failure propagation. For operational visibility, consider adding an explicit error message if SSH connection or deployment fails.

💡 Optional: Add error trap
-          "ssh root@pmoves-kvm2 'cd /opt/pmoves && git pull --ff-only origin main && cd pmoves && docker compose -f docker-compose.yml -f docker-compose.vps.override.yml pull nginx && docker compose -f docker-compose.yml -f docker-compose.vps.override.yml up -d nginx'",
+          "ssh root@pmoves-kvm2 'cd /opt/pmoves && git pull --ff-only origin main && cd pmoves && docker compose -f docker-compose.yml -f docker-compose.vps.override.yml pull nginx && docker compose -f docker-compose.yml -f docker-compose.vps.override.yml up -d nginx' || echo 'ERROR: KVM2 deployment failed'",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pbnj/pinokio/api/pmoves-pbnj/kvm2-deploy.json` at line 9, The SSH deployment
command string ("ssh root@pmoves-kvm2 'cd /opt/pmoves && git pull --ff-only
origin main && cd pmoves && docker compose -f docker-compose.yml -f
docker-compose.vps.override.yml pull nginx && docker compose -f
docker-compose.yml -f docker-compose.vps.override.yml up -d nginx'") relies on
implicit && propagation; modify this entry to add an explicit failure handler so
any SSH or remote command failure emits a clear error and exits non‑zero (for
example, append an explicit || { echo "SSH deploy to pmoves-kvm2 failed:
<contextual message>" >&2; exit 1; } or ensure the remote side uses set -e and
echoes a descriptive error on failure), ensuring the process logs the failure
message when the ssh invocation or remote steps fail.
pbnj/pinokio/api/pmoves-pbnj/pinokio.js (1)

17-23: Consider using info.exists() for conditional menu items.

The VPS Fleet menu items are statically defined. Per coding guidelines, info.exists(relative_path) can conditionally show menu items based on file presence, and info.running(relative_path) can reflect script state. This could prevent errors if a deployment JSON is missing or show active deployment status.

Additionally, line 19 uses an empty href: "" for the section divider. Verify this doesn't cause navigation issues when clicked.

As per coding guidelines: "In pinokio.js, use info.exists(relative_path) to check whether a relative path exists and determine which menu items to return dynamically."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pbnj/pinokio/api/pmoves-pbnj/pinokio.js` around lines 17 - 23, Replace the
static VPS Fleet entries with conditional menu-item generation using
info.exists("kvm4-1-deploy.json"), info.exists("kvm4-2-deploy.json"),
info.exists("kvm2-deploy.json"), and info.exists("vps-status.json") so each
Deploy/KVM/VPS item is only added if its JSON exists; use
info.running("kvm4-1-deploy.json") etc. to mark active/running state where
appropriate; and remove or replace the divider item that currently has href: ""
(the "─── VPS Fleet ───" entry) with a non-navigable label (e.g., no href or
null) to avoid clickable navigation. Ensure changes are applied in the menu
construction code that returns the array of items in pinokio.js so menu
generation becomes dynamic and safe.
pbnj/pinokio/api/pmoves-pbnj/kvm4-2-deploy.json (1)

9-13: Consider adding health checks for other critical services.

Health verification covers Prometheus and Qdrant but skips other services being deployed (neo4j, meilisearch, nats, supabase-db/rest). For a data services node, verifying database readiness could catch deployment issues earlier.

Additionally, the SSH commands will continue executing even if earlier commands fail. Consider adding set -e at the start or using && chaining within the SSH session to fail fast on errors.

Example: Add health checks for other services
          "ssh root@pmoves-kvm4-2 'curl -sf http://localhost:6333/healthz > /dev/null && echo \" Qdrant: OK\" || echo \" Qdrant: FAIL\"'",
+         "ssh root@pmoves-kvm4-2 'curl -sf http://localhost:7474 > /dev/null && echo \" Neo4j: OK\" || echo \" Neo4j: FAIL\"'",
+         "ssh root@pmoves-kvm4-2 'curl -sf http://localhost:7700/health > /dev/null && echo \" Meilisearch: OK\" || echo \" Meilisearch: FAIL\"'",
          "echo 'KVM4-2 deployment complete.'"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pbnj/pinokio/api/pmoves-pbnj/kvm4-2-deploy.json` around lines 9 - 13, Update
the deployment command sequence that targets "ssh root@pmoves-kvm4-2" to include
health checks for the other deployed services (neo4j, meilisearch, nats,
supabase-db and supabase-rest) similar to the existing Prometheus and Qdrant
checks, and make the remote SSH command fail-fast by adding "set -e" at the
start of the remote shell or ensuring each critical step inside the quoted SSH
session is joined with && so an earlier failure stops subsequent commands;
reference the existing SSH command strings (the lines containing "git pull
--ff-only origin main ... docker compose ... pull ... up -d" and the
health-check lines with curl) when adding the new checks and fail-fast behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.gitignore:
- Around line 160-164: The .gitignore currently covers "*.tfvars" but misses
JSON-format tfvars (e.g., terraform.tfvars.json), so add patterns for JSON
tfvars to prevent committing Terraform credentials; update the .gitignore
entries alongside the existing "*.tfvars" and "*.tfstate" patterns to include
"*.tfvars.json" and "terraform.tfvars.json" (and optionally "*.tfvars.*json" if
you prefer glob coverage) so files produced for automated Terraform runs are
ignored.

In `@deploy/provision/hostinger-kvm-setup.sh`:
- Around line 226-229: Remove the non-functional "tailscale set
--key-expiry-disabled" invocation and its suppressed stderr; instead, delete
that command and replace its behavior with a clear log_warn (using the existing
log_warn symbol) that instructs the operator to disable key expiry via the
Tailscale Admin Console or the Tailscale API (include a short comment
referencing the API endpoint /device keys and keyExpiryDisabled: true for
implementers who may later automate it). Ensure there is no silent failure
redirection (2>/dev/null) and that the message accurately reflects that the
change must be performed manually or via the API.

In `@deploy/scripts/deploy-vps.sh`:
- Around line 100-101: The kvm2 branch currently only prints the nginx container
status but never sets health_fail; update the kvm2 block (the ssh call that runs
"docker compose ... ps nginx --format '{{.Status}}'") to evaluate the returned
status string and set health_fail=1 when the container is not running (e.g., not
containing "Up" or when the command returns empty/exit code indicates missing
container). Ensure you capture the ssh output into a variable, check it for a
healthy state, and assign health_fail accordingly so failed/exited/missing nginx
containers fail the deployment.
- Around line 35-40: check_node and fleet_status still use hard-coded tailscale
ping "pmoves-${node}" which ignores the NODE_SSH overrides; update both
functions to resolve the actual SSH target from the NODE_SSH mapping (the same
helper used where NODE_SSH["..."] is defined) and use that resolved host for
reachability checks instead of "pmoves-${node}"; specifically, replace calls to
tailscale ping "pmoves-${node}" in check_node and fleet_status with a lookup
that extracts the hostname/IP portion from NODE_SSH["${node}"] (preserving user@
if needed) and ping that value so HOSTINGER_*_IP overrides are honored.
- Around line 82-85: The remote compose command run in the "log_info \"$node:
Starting services: $services\"; ssh \"$ssh_target\" \"cd ${WORK_DIR} &&
${COMPOSE_CMD} pull ${services} 2>/dev/null && ${COMPOSE_CMD} up -d
${services}\"" line does not set the failure flag if the SSH/compose call fails;
change it so the SSH command's exit status is checked and on non‑zero set
failed=1 (and log the failure). For example, run the SSH command in a way that
captures its exit code (e.g., run the SSH command and then "|| failed=1" or test
"$?" immediately after), and add a log_error invocation referencing the node and
services when setting failed to help trace which rollout failed. Ensure you
update the same block that currently uses COMPOSE_CMD/pull/up so the script
records failed=1 on any remote error.

In `@pmoves/config/agent_registry.yaml`:
- Around line 687-709: Add operator docs for the new vps_fleet_manager: in
.claude/context/nats-subjects.md create entries for mesh.vps.deploy.v1,
mesh.vps.status.v1, and mesh.vps.command.v1 following the existing pattern
(direction, short purpose, payload schema reference) and mirror
wording/formatting from other subjects; in .claude/context/services-catalog.md
add a vps_fleet_manager service block that includes name, purpose ("Hostinger
KVM fleet orchestration — deploy, status, restart via MCP tools"), port
(null/placeholder), NATS topics (the three subjects),
layers/evolution_stage/resilience summary, and dependencies modeled after the
BoTZ MCP Gateway entry so operators have port, purpose, NATS topics, and
dependency guidance.

In `@pmoves/docs/AGENTS/TOOLING_SCRIPT_AUDIT.md`:
- Around line 117-126: Update the tooling audit so vendored virtualenv paths are
excluded from overlap evidence: filter out any table rows or generated entries
whose file path contains "/.venv/" (e.g., the rows showing
PMOVES-Archon/python/.venv/..., PMOVES-Pipecat/.venv/...,
PMOVES-Open-Notebook/.venv/...), remove those entries from the overlap table in
pmoves/docs/AGENTS/TOOLING_SCRIPT_AUDIT.md and recompute the headline counts so
they reflect only repo-owned tooling (ensure the filter is applied where the
overlap list is generated or rendered, so future runs automatically omit .venv
paths).

In `@pmoves/docs/PRODUCTION_AUDIT_DASHBOARD.md`:
- Around line 14-46: The Mar 8 audit note omitted PR `#826` infrastructure
changes: add a short subsection under "Latest Changes (Mar 8, 2026)" that
documents PR `#826` and states the Tailscale flag migration from '--authkey' →
'--auth-key', the addition of 'tailscale set --key-expiry=off' for persistent
node auth, the Hostinger provider schema bump to v0.1.22, and the validation
outcomes (e.g., static gate status and runtime check results specific to these
changes and RG-3 collation confirmation); reference PR `#826`, the exact flags
'--auth-key' and '--key-expiry=off', and Hostinger v0.1.22 so reviewers can
locate related diffs and verify RG-3/colation hygiene.

In `@pmoves/examples/distributed/vps/README.md`:
- Around line 88-95: The generic Tailscale join step hard-codes --hostname
pmoves-kvm4-1 which will register multiple nodes with the same name; change the
generic command (the tailscale up invocation) to parameterize the hostname
(e.g., use a placeholder like <HOSTNAME> or an environment variable) so each
node runs tailscale up --hostname <unique-name> --accept-routes --accept-dns,
and keep the separate KVM2-specific block for the exit-node tailscale up step;
update any repeated occurrences (including the block around lines 101-113) to
use the same placeholder pattern.
- Around line 97-99: Remove the invalid CLI instruction "tailscale set
--key-expiry-disabled" from the README and replace it with one of the supported
methods to disable 180-day key expiry: instruct users to disable expiry via the
Tailscale admin console (Machines page), or via the Tailscale API by POSTing to
/api/v2/device/{deviceID}/key with {"keyExpiryDisabled": true}, or note that
auth keys with tags disable expiry by default for tagged devices; update the
README text where the offending command string appears to reference one of these
correct approaches.

In `@pmoves/Makefile`:
- Around line 469-473: The standalone target supa-collation-refresh currently
swallows failures by appending "|| true" to each psql ALTER DATABASE call inside
the loop (for dbname in postgres _supabase; do ... docker exec ... psql ... -c
"ALTER DATABASE $$dbname REFRESH COLLATION VERSION;" 2>&1 || true; done). Remove
the "|| true" so that a failing docker/psql command returns a non-zero exit and
causes the make target to fail (or alternatively test the command exit and
explicitly exit with non-zero), ensuring supa-collation-refresh surfaces real
failures to operators.
- Around line 479-480: The Makefile target that runs the psql command (the line
starting with docker exec -i "$$db" psql -U postgres -tAc "SELECT datname || ':
' || COALESCE(datcollversion, 'NULL') ...") only prints datcollversion and does
not detect drift; update that target to either (A) perform a comparison against
an expected/stored collation version and exit non‑zero on mismatch (e.g., load
the expected value from a file or variable and run a SQL comparison that fails
when datcollversion != expected), or (B) rename the target and its help text to
clearly indicate it is an informational dump rather than a check; modify the
command invoked (or add a follow-up check) so that mismatches cause make to fail
when option A is chosen.

In `@pmoves/scripts/tailscale_setup.sh`:
- Around line 173-179: Remove the unsupported "tailscale set
--key-expiry-disabled" post-join call and any conditional branches that attempt
to toggle key expiry; instead rely on existing tagging behavior for
non-workstation servers (keep the ts_args+=(--advertise-tags="$tags") logic when
node_type != "workstation") and do not attempt to disable key expiry for
workstation nodes (node_type == "workstation") via CLI; update the script
(around the ts_args and node_type handling) to remove the invalid command and
add a brief comment that key expiry for untagged/workstation devices must be
handled via the Admin Console or Tailscale API if persistent keys are required.

In `@pmoves/terraform/bootstrap-script.sh`:
- Around line 81-101: The compose launch never loads the generated .env.vps, so
services fall back to defaults; update the docker compose invocation in the
start block (the docker compose -f docker-compose.yml -f
docker-compose.vps.override.yml ... command that uses docker_compose_profile) to
include --env-file .env.vps, or alternatively add env_file: - .env.vps to the
impacted service definitions in docker-compose.yml /
docker-compose.vps.override.yml so the generated .env.vps is actually applied at
runtime.

In `@pmoves/terraform/mcp-integration.tf`:
- Around line 6-11: The terraform required_providers entry for the hostinger
provider uses the loose 0.x constraint "~> 0.1", which can pull in unintended
behavioral changes; update the hostinger version specification in the terraform
{ required_providers { hostinger = { ... } } } block to either pin to a specific
tested release (for example set version to "= 0.1.22") or replace the constraint
with a documented, intentional range (and add a comment explaining acceptance of
all 0.1.x), so the code explicitly controls which 0.1.x provider you will
accept.

---

Outside diff comments:
In `@pmoves/chit/secrets_manifest_v2.yaml`:
- Around line 722-776: The target blocks for id values surreal_address,
surreal_database, surreal_namespace, surreal_port, and surreal_url contain
duplicate "key:" entries per file target which breaks YAML; for each target
under the file: .env.generated in the entries (look for id: surreal_address, id:
surreal_database, id: surreal_namespace, id: surreal_port, id: surreal_url)
remove the duplicate "key:" line so each file target has a single "key:
<ENV_NAME>" entry; keep the other targets (github_secret and docker_secret)
unchanged and verify indentation/spacing remains valid YAML.

In `@pmoves/examples/distributed/vps/kvm4-2.env`:
- Around line 49-52: Replace the indirect variable reference on the
NEO4J_PASSWORD line with an explicit placeholder consistent with the other
example values: change the value for NEO4J_PASSWORD (found alongside NEO4J_URI
and NEO4J_USER) from ${NEO4J_PASSWORD} to a clear placeholder such as
your-strong-password-here so operators know to supply a real password in the
example env file.

In `@pmoves/terraform/mcp-integration.tf`:
- Around line 268-275: The use of timestamp() in common_tags.CreatedAt causes
perpetual plan drift; remove timestamp() from common_tags and instead either (A)
stop setting CreatedAt in the shared common_tags map and set a CreatedAt tag
only once during resource creation (for example via a null_resource/one-time
data source), or (B) keep resource-level tags but add lifecycle { ignore_changes
= [tags["CreatedAt"]] } on resources that must keep a mutable CreatedAt tag;
update references to common_tags and the resources using it (common_tags,
CreatedAt, timestamp()) accordingly so Terraform plans are idempotent.

---

Nitpick comments:
In `@deploy/provision/hostinger-kvm-setup.sh`:
- Around line 165-166: The script currently runs the Docker install with a
direct pipe ("curl -fsSL https://get.docker.com | sh"); replace this with a
safer two-step approach: download the installer to a temporary file, allow
inspection (or verify checksum/signature if available), then execute it
explicitly (e.g., run the downloaded file with sh) and remove the temp file
afterward; update the line containing "curl -fsSL https://get.docker.com | sh"
to implement this download-then-execute pattern and add a short comment noting
the security trade-off for future readers.
- Around line 173-178: The script pins Docker Compose via the COMPOSE_VERSION
variable (COMPOSE_VERSION="v2.27.0") which can become stale; change this by
either (A) making COMPOSE_VERSION configurable via an environment variable with
a clear comment describing the update cadence (e.g.,
COMPOSE_VERSION="${COMPOSE_VERSION:-v2.27.0}" plus a brief note on when to bump
it) or (B) implement a small dynamic lookup before the curl (e.g., query the
GitHub releases API for the latest stable compose tag) and assign that to
COMPOSE_VERSION; ensure the rest of the block (the curl target
"https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-$(uname
-s)-$(uname -m)" and the destination
/usr/local/lib/docker/cli-plugins/docker-compose) uses the chosen
COMPOSE_VERSION and add a one-line comment explaining the chosen update
strategy.
- Around line 146-151: The SSH hardening sed commands assume commented default
lines and can fail silently; update the "Hardening SSH" block to robustly
replace any existing PermitRootLogin, PasswordAuthentication, and MaxAuthTries
entries (matching commented or uncommented and any spacing) using regex-style
matching (e.g., match ^\s*#?\s*PermitRootLogin\s+.*) to replace with the desired
values, and if a pattern is not found append the setting to
/etc/ssh/sshd_config; ensure you still restart sshd (systemctl restart sshd)
after making the replacements.

In `@deploy/runners/vps/install-hardened.sh`:
- Line 26: The SCRIPT_DIR variable is defined but never used; either remove the
assignment to SCRIPT_DIR to eliminate the unused-variable warning, or explicitly
suppress the linter by adding a suppression comment for that symbol (for example
a ShellCheck disable for SC2034) or otherwise reference SCRIPT_DIR in a
follow-up use; locate the assignment to SCRIPT_DIR and apply one of these fixes
to clear the static-analysis warning.

In `@pbnj/pinokio/api/pmoves-pbnj/kvm2-deploy.json`:
- Line 9: The SSH deployment command string ("ssh root@pmoves-kvm2 'cd
/opt/pmoves && git pull --ff-only origin main && cd pmoves && docker compose -f
docker-compose.yml -f docker-compose.vps.override.yml pull nginx && docker
compose -f docker-compose.yml -f docker-compose.vps.override.yml up -d nginx'")
relies on implicit && propagation; modify this entry to add an explicit failure
handler so any SSH or remote command failure emits a clear error and exits
non‑zero (for example, append an explicit || { echo "SSH deploy to pmoves-kvm2
failed: <contextual message>" >&2; exit 1; } or ensure the remote side uses set
-e and echoes a descriptive error on failure), ensuring the process logs the
failure message when the ssh invocation or remote steps fail.

In `@pbnj/pinokio/api/pmoves-pbnj/kvm4-2-deploy.json`:
- Around line 9-13: Update the deployment command sequence that targets "ssh
root@pmoves-kvm4-2" to include health checks for the other deployed services
(neo4j, meilisearch, nats, supabase-db and supabase-rest) similar to the
existing Prometheus and Qdrant checks, and make the remote SSH command fail-fast
by adding "set -e" at the start of the remote shell or ensuring each critical
step inside the quoted SSH session is joined with && so an earlier failure stops
subsequent commands; reference the existing SSH command strings (the lines
containing "git pull --ff-only origin main ... docker compose ... pull ... up
-d" and the health-check lines with curl) when adding the new checks and
fail-fast behavior.

In `@pbnj/pinokio/api/pmoves-pbnj/pinokio.js`:
- Around line 17-23: Replace the static VPS Fleet entries with conditional
menu-item generation using info.exists("kvm4-1-deploy.json"),
info.exists("kvm4-2-deploy.json"), info.exists("kvm2-deploy.json"), and
info.exists("vps-status.json") so each Deploy/KVM/VPS item is only added if its
JSON exists; use info.running("kvm4-1-deploy.json") etc. to mark active/running
state where appropriate; and remove or replace the divider item that currently
has href: "" (the "─── VPS Fleet ───" entry) with a non-navigable label (e.g.,
no href or null) to avoid clickable navigation. Ensure changes are applied in
the menu construction code that returns the array of items in pinokio.js so menu
generation becomes dynamic and safe.

In `@pmoves/docs/PRODUCTION_AUDIT_DASHBOARD.md`:
- Line 28: The current line marking the smoke test as "`smoke`: PASS (10/12 OK;
Meilisearch + Neo4j WARN — pre-existing, not running locally)" is misleading
because it hides the partial nature of the pass; update the string to make the
partial result explicit (for example change to "`smoke`: CONDITIONAL PASS (10/12
OK; 2 services not running locally)" or "`smoke`: 10/12 PASS" or "`smoke`: PASS
(with known exceptions)`") by editing that specific markdown line so readers
immediately see the 10/12 result and the known exceptions.
- Line 570: The single-line changelog entry for "2026-03-08" is too dense;
please split it into a short header plus bulletized items for readability: keep
the date and title ("2026-03-08 | Post-merge audit sweep"), then add bullets for
PRs merged (PRs `#823/`#824), static gates (6/7 PASS with secrets-audit timeout),
runtime checks (smoke 10/12, model-readiness 17/17, monitoring-smoke PASS,
auth-alignment 0 errors, GPU smoke PASS), release gates (RG-1/2/4/5 PASS, RG-3
KNOWN), CI status (runners offline, queued runs), and live metrics
(PRs/CodeQL/Dependabot counts); update the markdown line containing the entry so
each item is on its own bullet or subline to improve scannability.

In `@pmoves/terraform/variables.auto.tfvars.example`:
- Around line 1-38: Add commented placeholder entries for the sensitive
variables referenced in mcp-integration.tf by including tailscale_authkey and
github_pat in the variables.auto.tfvars.example; add them as commented lines
(e.g., # tailscale_authkey = "tskey_..." and # github_pat = "ghp_...") with a
note that they are sensitive and should be filled in the local
variables.auto.tfvars (not committed), and include brief example formats for
both so users can discover and supply those values.
- Around line 18-20: The example uses an easily guessable pattern for the
root_password variable; update the root_password value to a non-patterned
placeholder (e.g., a random-looking string or simply "" or
"<GENERATE_SECURE_PASSWORD>") and add a one-line comment next to root_password
recommending use of a password manager or generator; leave ssh_public_key as-is
but consider adding a brief comment that an SSH public key should be provided
instead of a password for better security.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8494716d-b287-4936-977b-29fbb3a7ccac

📥 Commits

Reviewing files that changed from the base of the PR and between ba3c7f8 and e06bdb4.

📒 Files selected for processing (120)
  • .claude/scripts/setup-runner.sh
  • .github/workflows/build-images.yml
  • .github/workflows/codex-parity-advisory.yml
  • .github/workflows/deploy-gateway-agent.yml
  • .github/workflows/hardening-validation.yml
  • .github/workflows/python-tests.yml
  • .github/workflows/self-hosted-builds-hardened.yml
  • .github/workflows/sql-policy-lint.yml
  • .github/workflows/sync-secrets-local.yml
  • .github/workflows/webhook-smoke.yml
  • .github/workflows/yt-dlp-bump.yml
  • .gitignore
  • deploy/provision/hostinger-kvm-setup.sh
  • deploy/provision/kvm2-exit-node.sh
  • deploy/runners/vps/install-hardened.sh
  • deploy/scripts/deploy-vps.sh
  • pbnj/pinokio/api/pmoves-pbnj/kvm2-deploy.json
  • pbnj/pinokio/api/pmoves-pbnj/kvm4-1-deploy.json
  • pbnj/pinokio/api/pmoves-pbnj/kvm4-2-deploy.json
  • pbnj/pinokio/api/pmoves-pbnj/pinokio.js
  • pbnj/pinokio/api/pmoves-pbnj/vps-status.json
  • pmoves/Makefile
  • pmoves/chit/secrets_manifest.yaml
  • pmoves/chit/secrets_manifest_v2.yaml
  • pmoves/config/agent_registry.yaml
  • pmoves/docker-compose.vps.override.yml
  • pmoves/docs/AGENTS/TOOLING_SCRIPT_AUDIT.md
  • pmoves/docs/NEXT_STEPS.md
  • pmoves/docs/PRODUCTION_AUDIT_DASHBOARD.md
  • pmoves/docs/SUBMODULE_DOCS_DOSSIER.md
  • pmoves/docs/evidence/submodule_layer/PMOVES-A2UI.json
  • pmoves/docs/evidence/submodule_layer/PMOVES-A2UI.md
  • pmoves/docs/evidence/submodule_layer/PMOVES-Agent-Zero.json
  • pmoves/docs/evidence/submodule_layer/PMOVES-Agent-Zero.md
  • pmoves/docs/evidence/submodule_layer/PMOVES-AgentGym.json
  • pmoves/docs/evidence/submodule_layer/PMOVES-AgentGym.md
  • pmoves/docs/evidence/submodule_layer/PMOVES-Archon.json
  • pmoves/docs/evidence/submodule_layer/PMOVES-Archon.md
  • pmoves/docs/evidence/submodule_layer/PMOVES-BoTZ.json
  • pmoves/docs/evidence/submodule_layer/PMOVES-BoTZ.md
  • pmoves/docs/evidence/submodule_layer/PMOVES-BotZ-gateway.json
  • pmoves/docs/evidence/submodule_layer/PMOVES-BotZ-gateway.md
  • pmoves/docs/evidence/submodule_layer/PMOVES-Creator.json
  • pmoves/docs/evidence/submodule_layer/PMOVES-Creator.md
  • pmoves/docs/evidence/submodule_layer/PMOVES-Danger-infra.json
  • pmoves/docs/evidence/submodule_layer/PMOVES-Danger-infra.md
  • pmoves/docs/evidence/submodule_layer/PMOVES-Deep-Serch.json
  • pmoves/docs/evidence/submodule_layer/PMOVES-Deep-Serch.md
  • pmoves/docs/evidence/submodule_layer/PMOVES-DoX.json
  • pmoves/docs/evidence/submodule_layer/PMOVES-DoX.md
  • pmoves/docs/evidence/submodule_layer/PMOVES-E2B-Danger-Room-Desktop.json
  • pmoves/docs/evidence/submodule_layer/PMOVES-E2B-Danger-Room-Desktop.md
  • pmoves/docs/evidence/submodule_layer/PMOVES-E2B-Danger-Room.json
  • pmoves/docs/evidence/submodule_layer/PMOVES-E2B-Danger-Room.md
  • pmoves/docs/evidence/submodule_layer/PMOVES-E2b-Spells.json
  • pmoves/docs/evidence/submodule_layer/PMOVES-E2b-Spells.md
  • pmoves/docs/evidence/submodule_layer/PMOVES-Headscale.json
  • pmoves/docs/evidence/submodule_layer/PMOVES-Headscale.md
  • pmoves/docs/evidence/submodule_layer/PMOVES-HiRAG.json
  • pmoves/docs/evidence/submodule_layer/PMOVES-HiRAG.md
  • pmoves/docs/evidence/submodule_layer/PMOVES-Jellyfin.json
  • pmoves/docs/evidence/submodule_layer/PMOVES-Jellyfin.md
  • pmoves/docs/evidence/submodule_layer/PMOVES-MAI-UI.json
  • pmoves/docs/evidence/submodule_layer/PMOVES-MAI-UI.md
  • pmoves/docs/evidence/submodule_layer/PMOVES-Open-Notebook.json
  • pmoves/docs/evidence/submodule_layer/PMOVES-Open-Notebook.md
  • pmoves/docs/evidence/submodule_layer/PMOVES-Pinokio-Ultimate-TTS-Studio.json
  • pmoves/docs/evidence/submodule_layer/PMOVES-Pinokio-Ultimate-TTS-Studio.md
  • pmoves/docs/evidence/submodule_layer/PMOVES-Pipecat.json
  • pmoves/docs/evidence/submodule_layer/PMOVES-Pipecat.md
  • pmoves/docs/evidence/submodule_layer/PMOVES-Remote-View.json
  • pmoves/docs/evidence/submodule_layer/PMOVES-Remote-View.md
  • pmoves/docs/evidence/submodule_layer/PMOVES-Tailscale.json
  • pmoves/docs/evidence/submodule_layer/PMOVES-Tailscale.md
  • pmoves/docs/evidence/submodule_layer/PMOVES-ToKenism-Multi.json
  • pmoves/docs/evidence/submodule_layer/PMOVES-ToKenism-Multi.md
  • pmoves/docs/evidence/submodule_layer/PMOVES-Ultimate-TTS-Studio.json
  • pmoves/docs/evidence/submodule_layer/PMOVES-Ultimate-TTS-Studio.md
  • pmoves/docs/evidence/submodule_layer/PMOVES-Wealth.json
  • pmoves/docs/evidence/submodule_layer/PMOVES-Wealth.md
  • pmoves/docs/evidence/submodule_layer/PMOVES-crush.json
  • pmoves/docs/evidence/submodule_layer/PMOVES-crush.md
  • pmoves/docs/evidence/submodule_layer/PMOVES-llama-throughput-lab.json
  • pmoves/docs/evidence/submodule_layer/PMOVES-llama-throughput-lab.md
  • pmoves/docs/evidence/submodule_layer/PMOVES-n8n.json
  • pmoves/docs/evidence/submodule_layer/PMOVES-n8n.md
  • pmoves/docs/evidence/submodule_layer/PMOVES-supabase.json
  • pmoves/docs/evidence/submodule_layer/PMOVES-supabase.md
  • pmoves/docs/evidence/submodule_layer/PMOVES-surf.json
  • pmoves/docs/evidence/submodule_layer/PMOVES-surf.md
  • pmoves/docs/evidence/submodule_layer/PMOVES-tensorzero.json
  • pmoves/docs/evidence/submodule_layer/PMOVES-tensorzero.md
  • pmoves/docs/evidence/submodule_layer/PMOVES-transcribe-and-fetch.json
  • pmoves/docs/evidence/submodule_layer/PMOVES-transcribe-and-fetch.md
  • pmoves/docs/evidence/submodule_layer/PMOVES.YT.json
  • pmoves/docs/evidence/submodule_layer/PMOVES.YT.md
  • pmoves/docs/evidence/submodule_layer/Pmoves-AgentGym-RL.json
  • pmoves/docs/evidence/submodule_layer/Pmoves-AgentGym-RL.md
  • pmoves/docs/evidence/submodule_layer/Pmoves-Health-wger.json
  • pmoves/docs/evidence/submodule_layer/Pmoves-Health-wger.md
  • pmoves/docs/evidence/submodule_layer/Pmoves-Jellyfin-AI-Media-Stack.json
  • pmoves/docs/evidence/submodule_layer/Pmoves-Jellyfin-AI-Media-Stack.md
  • pmoves/docs/evidence/submodule_layer/Pmoves-cipher.json
  • pmoves/docs/evidence/submodule_layer/Pmoves-cipher.md
  • pmoves/docs/evidence/submodule_layer/Pmoves-hyperdimensions.json
  • pmoves/docs/evidence/submodule_layer/Pmoves-hyperdimensions.md
  • pmoves/docs/evidence/submodule_layer/pmoves-e2b-mcp-server.json
  • pmoves/docs/evidence/submodule_layer/pmoves-e2b-mcp-server.md
  • pmoves/docs/evidence/submodule_layer/pmoves__integrations__archon.json
  • pmoves/docs/evidence/submodule_layer/pmoves__integrations__archon.md
  • pmoves/examples/distributed/vps/README.md
  • pmoves/examples/distributed/vps/kvm4-1.env
  • pmoves/examples/distributed/vps/kvm4-2.env
  • pmoves/integrations/github-runners/compose/lane_hosts.json
  • pmoves/integrations/github-runners/compose/runner_phase_policy.json
  • pmoves/scripts/tailscale_setup.sh
  • pmoves/terraform/bootstrap-script.sh
  • pmoves/terraform/mcp-integration.tf
  • pmoves/terraform/variables.auto.tfvars.example
  • pmoves/tools/crush_configurator.py

Comment thread .gitignore
Comment thread deploy/provision/hostinger-kvm-setup.sh Outdated
Comment on lines +226 to +229
# Disable key expiry for persistent VPS node (default 180-day timeout)
if ! tailscale set --key-expiry-disabled 2>/dev/null; then
log_warn "Could not disable key expiry — approve manually in admin console"
fi

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

tailscale set --key-expiry-disabled flag syntax

💡 Result:

--key-expiry-disabled is not a valid tailscale set flag. Tailscale key expiry (node key expiry) is managed in the Admin Console (Machines page) or via the Tailscale API (“Update device key”). [1]

To disable a device’s key expiry via API, POST to the device key endpoint with keyExpiryDisabled: true: [2]

curl -u "$TS_API_KEY:" \
  -H 'Content-Type: application/json' \
  -X POST "https://api.tailscale.com/api/v2/device/$DEVICE_ID/key" \
  -d '{"keyExpiryDisabled":true}'

(Use the device’s nodeId/device ID from the Machines page.) [1]

Sources: [1] https://tailscale.com/docs/features/access-control/auth-keys [2] https://github.com/tailscale/tailscale-client-go-v2/blob/v2.8.0/devices.go?plain=1


Remove non-functional tailscale set --key-expiry-disabled call — this flag does not exist.

The --key-expiry-disabled flag is not a valid argument for the tailscale set command. Tailscale key expiry can only be disabled via the Admin Console (Machines page) or the Tailscale API (POST to the device key endpoint with keyExpiryDisabled: true). The current code silently fails due to error suppression (2>/dev/null), making the fallback message misleading. Either implement the API approach or remove this command and rely on manual configuration in the Admin Console.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@deploy/provision/hostinger-kvm-setup.sh` around lines 226 - 229, Remove the
non-functional "tailscale set --key-expiry-disabled" invocation and its
suppressed stderr; instead, delete that command and replace its behavior with a
clear log_warn (using the existing log_warn symbol) that instructs the operator
to disable key expiry via the Tailscale Admin Console or the Tailscale API
(include a short comment referencing the API endpoint /device keys and
keyExpiryDisabled: true for implementers who may later automate it). Ensure
there is no silent failure redirection (2>/dev/null) and that the message
accurately reflects that the change must be performed manually or via the API.

Comment on lines +35 to +40
# Node SSH targets — env-var fallback allows both direct IP (from CHIT secrets)
# and Tailscale hostname discovery
declare -A NODE_SSH
NODE_SSH["kvm4-1"]="${HOSTINGER_KVM4_1_USER:-root}@${HOSTINGER_KVM4_1_IP:-pmoves-kvm4-1}"
NODE_SSH["kvm4-2"]="${HOSTINGER_KVM4_2_USER:-root}@${HOSTINGER_KVM4_2_IP:-pmoves-kvm4-2}"
NODE_SSH["kvm2"]="${HOSTINGER_KVM2_USER:-root}@${HOSTINGER_KVM2_IP:-pmoves-kvm2}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

HOSTINGER_*_IP overrides are not honored by the reachability checks.

NODE_SSH allows direct-IP overrides, but both check_node and fleet_status still hard-code tailscale ping "pmoves-${node}". If a node is configured via HOSTINGER_*_IP, it can be marked offline and skipped even though the SSH target is valid.

Suggested direction
+probe_node() {
+    local node="$1"
+    local ssh_target="${NODE_SSH[$node]}"
+    ssh -o BatchMode=yes -o ConnectTimeout=3 "$ssh_target" true &>/dev/null
+}
+
 check_node() {
     local node="$1"
-    local ssh_target="${NODE_SSH[$node]}"
-
-    if tailscale ping --timeout 3s "pmoves-${node}" &>/dev/null; then
+    if probe_node "$node"; then
         log_info "$node: Tailscale reachable"
         return 0

Apply the same helper in fleet_status.

Also applies to: 52-56, 122-126

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@deploy/scripts/deploy-vps.sh` around lines 35 - 40, check_node and
fleet_status still use hard-coded tailscale ping "pmoves-${node}" which ignores
the NODE_SSH overrides; update both functions to resolve the actual SSH target
from the NODE_SSH mapping (the same helper used where NODE_SSH["..."] is
defined) and use that resolved host for reachability checks instead of
"pmoves-${node}"; specifically, replace calls to tailscale ping "pmoves-${node}"
in check_node and fleet_status with a lookup that extracts the hostname/IP
portion from NODE_SSH["${node}"] (preserving user@ if needed) and ping that
value so HOSTINGER_*_IP overrides are honored.

Comment thread deploy/scripts/deploy-vps.sh
Comment thread deploy/scripts/deploy-vps.sh Outdated
Comment thread pmoves/Makefile
Comment on lines +469 to +473
for dbname in postgres _supabase; do \
echo " -> ALTER DATABASE $$dbname REFRESH COLLATION VERSION"; \
docker exec -i "$$db" psql -U postgres -d "$$dbname" \
-c "ALTER DATABASE $$dbname REFRESH COLLATION VERSION;" 2>&1 || true; \
done

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Don't swallow ALTER DATABASE failures in the standalone refresh target.

make supa-collation-refresh currently reports success even when every psql call fails, because each ALTER DATABASE is wrapped in || true. Since Line 316 already makes the post-start call best-effort, the standalone remediation target should surface failures so operators can tell whether RG-3 drift was actually cleared.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pmoves/Makefile` around lines 469 - 473, The standalone target
supa-collation-refresh currently swallows failures by appending "|| true" to
each psql ALTER DATABASE call inside the loop (for dbname in postgres _supabase;
do ... docker exec ... psql ... -c "ALTER DATABASE $$dbname REFRESH COLLATION
VERSION;" 2>&1 || true; done). Remove the "|| true" so that a failing
docker/psql command returns a non-zero exit and causes the make target to fail
(or alternatively test the command exit and explicitly exit with non-zero),
ensuring supa-collation-refresh surfaces real failures to operators.

Comment thread pmoves/Makefile
Comment on lines +479 to +480
docker exec -i "$$db" psql -U postgres -tAc \
"SELECT datname || ': ' || COALESCE(datcollversion, 'NULL') FROM pg_database WHERE datname NOT IN ('template0','template1') ORDER BY datname;"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

This target reports versions but never checks for a mismatch.

The query only dumps datcollversion, so a stale database and a healthy one are indistinguishable unless the operator already knows the expected value. Either compare stored vs current collation versions and fail on drift, or rename this target to make it clear it's an info dump rather than a check.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pmoves/Makefile` around lines 479 - 480, The Makefile target that runs the
psql command (the line starting with docker exec -i "$$db" psql -U postgres -tAc
"SELECT datname || ': ' || COALESCE(datcollversion, 'NULL') ...") only prints
datcollversion and does not detect drift; update that target to either (A)
perform a comparison against an expected/stored collation version and exit
non‑zero on mismatch (e.g., load the expected value from a file or variable and
run a SQL comparison that fails when datcollversion != expected), or (B) rename
the target and its help text to clearly indicate it is an informational dump
rather than a check; modify the command invoked (or add a follow-up check) so
that mismatches cause make to fail when option A is chosen.

Comment thread pmoves/scripts/tailscale_setup.sh
Comment on lines +81 to +101
cat > .env.vps <<'ENVEOF'
NATS_URL=${nats_url}
SUPABASE_URL=${supabase_url}
SUPABASE_SERVICE_KEY=${supabase_service_key}
OPEN_NOTEBOOK_API_URL=${open_notebook_api_url}
OPEN_NOTEBOOK_API_TOKEN=${open_notebook_api_token}
HIRAG_URL=${hirag_url}
COMFYUI_WEBHOOK_URL=${comfyui_webhook_url}
YOUTUBE_API_KEY=${youtube_api_key}
DOMAIN_NAME=${domain_name}
ENVEOF

chmod 600 .env.vps

# ============================================================================
# 7. Start Services
# ============================================================================
%{ if docker_compose_profile == "full" ~}
docker compose -f docker-compose.yml -f docker-compose.vps.override.yml up -d
%{ else ~}
docker compose -f docker-compose.yml -f docker-compose.vps.override.yml --profile ${docker_compose_profile} up -d

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "--- references to .env.vps / env-file wiring ---"
rg -n '\.env\.vps|--env-file|env_file:' pmoves -g 'docker-compose*.yml' -g 'docker-compose*.yaml' -g '*.sh' || true
echo "--- compose files referencing injected vars ---"
rg -n 'NATS_URL|SUPABASE_URL|OPEN_NOTEBOOK_API_URL|HIRAG_URL|COMFYUI_WEBHOOK_URL|YOUTUBE_API_KEY|DOMAIN_NAME' pmoves -g 'docker-compose*.yml' -g 'docker-compose*.yaml' || true

Repository: POWERFULMOVES/PMOVES.AI

Length of output: 9232


🏁 Script executed:

cat pmoves/docker-compose.vps.override.yml

Repository: POWERFULMOVES/PMOVES.AI

Length of output: 5412


🏁 Script executed:

head -100 pmoves/docker-compose.yml | tail -50

Repository: POWERFULMOVES/PMOVES.AI

Length of output: 841


🏁 Script executed:

rg -A 2 'env_file:' pmoves/docker-compose.vps.override.yml

Repository: POWERFULMOVES/PMOVES.AI

Length of output: 49


🏁 Script executed:

rg 'env_file:' pmoves -g '*.yml' -A 3 | grep -B 3 '.env.vps' || echo "No .env.vps found in env_file directives"

Repository: POWERFULMOVES/PMOVES.AI

Length of output: 107


🏁 Script executed:

git ls-files pmoves | grep -E '\.env|docker-compose' | head -30

Repository: POWERFULMOVES/PMOVES.AI

Length of output: 1161


🏁 Script executed:

# Check the specific docker-compose.yml env_file declarations for services that use the injected vars
rg -B 5 'NATS_URL=\$\{NATS_URL' pmoves/docker-compose.yml | head -40

Repository: POWERFULMOVES/PMOVES.AI

Length of output: 2243


Pass --env-file .env.vps to docker compose or add .env.vps to service env_file: directives.

This script provisions values into .env.vps, then starts Compose without --env-file .env.vps. Docker Compose does not load .env.vps by default unless explicitly passed via --env-file or listed in service env_file: directives. As a result, services will use their fallback defaults (e.g., ${SUPABASE_URL:-http://supabase-rest:3010} resolves to the internal container IP) instead of the externally configured VPS URLs written to .env.vps.

Fix by either:

  • Adding --env-file .env.vps to the docker compose command
  • Adding env_file: [... .env.vps] to affected services in the compose files
🧰 Tools
🪛 Shellcheck (0.11.0)

[warning] 98-98: This is interpreted as a command name ending with '{'. Double check syntax.

(SC2288)


[warning] 98-98: This { is literal. Check expression (missing ;/\n?) or quote it.

(SC1083)


[warning] 98-98: This } is literal. Check expression (missing ;/\n?) or quote it.

(SC1083)


[warning] 100-100: This is interpreted as a command name ending with '{'. Double check syntax.

(SC2288)


[warning] 100-100: This { is literal. Check expression (missing ;/\n?) or quote it.

(SC1083)


[warning] 100-100: This } is literal. Check expression (missing ;/\n?) or quote it.

(SC1083)


[warning] 101-101: docker_compose_profile is referenced but not assigned.

(SC2154)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pmoves/terraform/bootstrap-script.sh` around lines 81 - 101, The compose
launch never loads the generated .env.vps, so services fall back to defaults;
update the docker compose invocation in the start block (the docker compose -f
docker-compose.yml -f docker-compose.vps.override.yml ... command that uses
docker_compose_profile) to include --env-file .env.vps, or alternatively add
env_file: - .env.vps to the impacted service definitions in docker-compose.yml /
docker-compose.vps.override.yml so the generated .env.vps is actually applied at
runtime.

Comment on lines 6 to +11
terraform {
required_version = ">= 1.0"
required_version = ">= 1.3.0"
required_providers {
hostinger = {
source = "hostinger/hostinger"
version = "~> 0.1.3"
version = "~> 0.1"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

hostinger terraform provider 0.1.22 schema

💡 Result:

To get the Terraform provider schema (machine-readable) for hostinger/hostinger v0.1.22, install that exact version and run Terraform’s built-in schema dump:

# 1) Configure provider version 0.1.22
cat > main.tf <<'EOF'
terraform {
  required_providers {
    hostinger = {
      source  = "hostinger/hostinger"
      version = "0.1.22"
    }
  }
}
provider "hostinger" {
  api_token = var.hostinger_api_token
}
EOF

# 2) Install provider
terraform init

# 3) Dump provider/resource/data source schemas as JSON
terraform providers schema -json > hostinger-0.1.22.schema.json

terraform providers schema -json is the official way to print detailed provider/resource/data-source schemas in JSON (with provider_schemas, resource_schemas, data_source_schemas, etc.). [1] The Hostinger repo README shows using source = "hostinger/hostinger" with version = "0.1.22". [2]

What you should expect to see in that schema (v0.1.22):

  • Provider config: api_token (also supported via HOSTINGER_API_TOKEN). [2]
  • Resources: hostinger_vps, hostinger_vps_post_install_script, hostinger_vps_ssh_key, and hostinger_dns_record. [3]
  • Data sources: hostinger_vps_templates, hostinger_vps_data_centers, hostinger_vps_plans. [3]

Resource/data-source argument details are documented in the provider’s generated docs for v0.1.22 (e.g., hostinger_vps args like plan, data_center_id, template_id, optional password, hostname, etc.). [4]

Sources: [1] [2] [3] [4]


🌐 Web query:

Hostinger Terraform provider GitHub changelog 0.1.x releases

💡 Result:

Hostinger Terraform provider (hostinger/terraform-provider-hostinger) has the following v0.1.x GitHub release/tag notes (oldest → newest):

  • v0.1.3 (Apr 11, 2025): chore: update goreleaser config [1]
  • v0.1.4 (Apr 23, 2025): feat(dns): add hostinger_dns_record resource (feat: LangExtract core + Hi‑RAG v2 hybrid, data IO, Supabase full, realtime UI, avatars #3) [1]
  • v0.1.5 (Apr 28, 2025): chore: test golangci-lint workflow with formatting fixes (Feat/langextract supabase realtime #7) [1]
  • v0.1.6 (May 12, 2025): Improve Domain DNS Management [1]
  • v0.1.7 (Jul 7, 2025): fix(dns): properly delete DNS records [1]
  • v0.1.18 (Jul 7, 2025): fix(provider): update version number [1]
  • v0.1.19 (Jan 5, 2026): fix: use new VPS creation API endpoint [1]
  • v0.1.20 (Jan 19, 2026): chore: Bump version [1]
  • v0.1.21 (Jan 27, 2026): “Add VPS Import Support” (adds VPS import + related client/docs updates) [1]
  • v0.1.22 (Jan 27, 2026): “DNS record fixes and improvements” (fix inconsistent apply result, prevent deleting unrelated records, TXT case-sensitivity, error handling/validation, refactors, retry API updates) [1][2]

Sources: [1] tags list/release notes, [2] releases page.


🌐 Web query:

hostinger/hostinger terraform provider v0.1 breaking changes releases

💡 Result:

Hostinger Terraform provider (hostinger/hostinger) v0.1.x — breaking changes?

I couldn’t find any v0.1.x release notes that explicitly declare “BREAKING CHANGES” (the GitHub release entries are written as fixes/features without a breaking-change callout). (github.com)

That said, because this is a 0.x provider series, you should treat any v0.1.x upgrade as potentially breaking and always pin versions + read release notes before upgrading. (developer.hashicorp.com)

v0.1.x releases that are most likely to cause upgrade surprises (behavior changes)

  • v0.1.19 (Jan 5, 2026): “use new VPS creation API endpoint” (API-level changes can affect provisioning behavior). (github.com)
  • v0.1.22 (Jan 27, 2026): DNS record update behavior fixes (e.g., fixes for inconsistent apply results and deleting unrelated DNS records) may change how existing DNS resources converge. (github.com)
  • v0.1.4 (Apr 23, 2025): adds hostinger_dns_record resource (not breaking by itself, but expands surface area and may affect DNS workflows). (github.com)
  • v0.1.21 (Jan 27, 2026): adds VPS import support (generally non-breaking, but can change recommended workflows). (github.com)

Citations:


🌐 Web query:

hostinger terraform provider resource hostinger_vps attributes documentation

💡 Result:

hostinger_vps resource docs (Terraform Provider: Hostinger) list these exported attributes: id, ipv4_address, ipv6_address, status, vps_id (same as id, kept for compatibility). [1]

For context, the same documentation page lists these arguments: required plan, data_center_id, template_id; optional/sensitive password; optional hostname, payment_method_id, post_install_script_id, ssh_key_ids. [1]

Provider setup + a hostinger_vps example are also shown in Hostinger’s “Getting started” guide. [2]

Sources: [1] [2]


🌐 Web query:

hostinger terraform provider hostinger_dns_record key content ipv4_address

💡 Result:

In the Hostinger Terraform provider, hostinger_dns_record does not use key or content—it uses value for the record’s data (the “content” of the DNS record). The arguments shown in the provider docs are zone, name, type, value, and ttl. [1]

ipv4_address is an output attribute of hostinger_vps (the VPS public IPv4), and you typically feed it into the DNS record’s value when creating an A record. [2]

Example (A record pointing to a Hostinger VPS):

resource "hostinger_dns_record" "api" {
  zone  = "example.com"
  name  = "api"
  type  = "A"
  value = hostinger_vps.web.ipv4_address
  ttl   = 14400
}

key belongs to hostinger_vps_ssh_key (SSH public key material). [3]
content belongs to hostinger_vps_post_install_script (script body). [4]


Reconsider version constraint for 0.x provider series.

The version constraint ~> 0.1 accepts any 0.1.x version, including v0.1.19 and v0.1.22 which contain behavioral changes: v0.1.19 introduced a new VPS creation API endpoint, and v0.1.22 modified DNS record update behavior. The attributes you reference (key, content, ipv4_address, template_id) exist and have stable schemas across versions, but the loose constraint may pull in unintended behavioral changes. For a 0.x provider, either pin to a specific tested version (e.g., = 0.1.22) or document that accepting all 0.1.x versions is intentional.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pmoves/terraform/mcp-integration.tf` around lines 6 - 11, The terraform
required_providers entry for the hostinger provider uses the loose 0.x
constraint "~> 0.1", which can pull in unintended behavioral changes; update the
hostinger version specification in the terraform { required_providers {
hostinger = { ... } } } block to either pin to a specific tested release (for
example set version to "= 0.1.22") or replace the constraint with a documented,
intentional range (and add a comment explaining acceptance of all 0.1.x), so the
code explicitly controls which 0.1.x provider you will accept.

hunnibear and others added 4 commits March 8, 2026 13:57
Fix provider version constraint, required_version, ssh_key schema,
post_install_script schema, ip_address→ipv4_address attribute, and
template_id references per official Hostinger Terraform docs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace deprecated --authkey with --auth-key across all provisioning
scripts. Add tailscale set --key-expiry=off for persistent node auth.
Add error logging and SSH hardening comments.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update VPS distributed deployment docs to reflect migration from
WireGuard to Tailscale mesh networking.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix bash syntax error where 'local' keyword was used outside of
a function, which is invalid in POSIX sh and strict bash modes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@POWERFULMOVES
POWERFULMOVES force-pushed the fix/tailscale-hostinger-doc-validation branch from e06bdb4 to c671f47 Compare March 8, 2026 18:57
@github-actions

github-actions Bot commented Mar 8, 2026

Copy link
Copy Markdown
Contributor

Docker Hardening Validation

Hardening Validation Report

Validated: Sun Mar 8 18:57:58 UTC 2026

Services Checked

PMOVES.AI Docker Hardening Validation

[INFO] Checking: pmoves/docker-compose.hardened.yml

[INFO] Validating: hi-rag-gateway-v2
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: extract-worker
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: langextract
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: presign
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: render-webhook
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: retrieval-eval
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: pdf-ingest
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: jellyfin-bridge
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: invidious-companion-proxy
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: ffmpeg-whisper
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: media-video
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: media-audio
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: hi-rag-gateway-v2-gpu
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: hi-rag-gateway-gpu
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: deepresearch
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: supaserch
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: publisher-discord
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: mesh-agent
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: nats-echo-req
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: nats-echo-res
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: publisher
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: analysis-echo
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: graph-linker
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: comfy-watcher
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: grayjay-plugin-host
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: agent-zero
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: archon
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: channel-monitor
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: pmoves-yt
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: notebook-sync
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: supabase_service_role_key
[WARN] No user directive
[WARN] No read_only directive
[WARN] No cap_drop: ["ALL"]
[WARN] No no-new-privileges
[WARN] No resource limits

[INFO] Validating: supabase_jwt_secret
[WARN] No user directive
[WARN] No read_only directive
[WARN] No cap_drop: ["ALL"]
[WARN] No no-new-privileges
[WARN] No resource limits

======================================
Summary: 120 passed, 40 warnings, 0 errors

- Remove invalid `tailscale set --key-expiry-disabled` (not a real flag);
  tagged auth keys auto-disable expiry, document API fallback
- Add *.tfvars.json to .gitignore (prevent credential leaks)
- Parameterize hostname in VPS README (was hardcoded to kvm4-1)
- Record failed compose rollout in deploy-vps.sh (was silently ignored)
- Fail deployment on kvm2 nginx health check failure (was print-only)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@POWERFULMOVES

Copy link
Copy Markdown
Owner Author

CodeRabbit Review Response (commit c7830d7)

Fixed

  • tailscale set --key-expiry-disabled (Critical x2) — Removed non-existent flag from all 4 scripts + README. Tagged auth keys auto-disable expiry; documented API fallback for untagged keys
  • *.tfvars.json gitignore (Major) — Added to prevent JSON-backed tfvars credential leaks
  • README hostname parameterization (Major) — Changed from hardcoded pmoves-kvm4-1 to <pmoves-kvm4-1|pmoves-kvm4-2>, separated KVM2 block
  • deploy-vps.sh rollout failure (Major) — compose pull/up failures now set failed=1
  • kvm2 health check (Major) — nginx status now evaluated and fails deployment if not running
  • HOSTINGER_*_IP reachability (Major) — Acknowledged; SSH-based probe is a valid improvement, will consider in follow-up

Acknowledged — Out of Scope

  • Makefile collation targets (Minor x2) — Valid improvements for error handling, will address in follow-up
  • agent_registry.yaml NATS subjects (Minor) — Forward declarations for VPS Fleet Manager
  • TOOLING_SCRIPT_AUDIT.md vendored paths (Minor) — Cosmetic, evidence generated by audit tooling
  • PRODUCTION_AUDIT_DASHBOARD.md (Minor) — Will add Tailscale/Hostinger workstream section in follow-up
  • tailscale_setup.sh (Major) — Addressed (key-expiry flag removed)
  • bootstrap-script.sh (Major) — Will review env file wiring in follow-up
  • mcp-integration.tf (Minor) — Terraform provider schema alignment noted

@github-actions

github-actions Bot commented Mar 8, 2026

Copy link
Copy Markdown
Contributor

Docker Hardening Validation

Hardening Validation Report

Validated: Sun Mar 8 19:02:01 UTC 2026

Services Checked

PMOVES.AI Docker Hardening Validation

[INFO] Checking: pmoves/docker-compose.hardened.yml

[INFO] Validating: hi-rag-gateway-v2
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: extract-worker
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: langextract
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: presign
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: render-webhook
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: retrieval-eval
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: pdf-ingest
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: jellyfin-bridge
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: invidious-companion-proxy
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: ffmpeg-whisper
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: media-video
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: media-audio
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: hi-rag-gateway-v2-gpu
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: hi-rag-gateway-gpu
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: deepresearch
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: supaserch
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: publisher-discord
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: mesh-agent
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: nats-echo-req
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: nats-echo-res
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: publisher
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: analysis-echo
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: graph-linker
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: comfy-watcher
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: grayjay-plugin-host
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: agent-zero
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: archon
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: channel-monitor
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: pmoves-yt
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: notebook-sync
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: supabase_service_role_key
[WARN] No user directive
[WARN] No read_only directive
[WARN] No cap_drop: ["ALL"]
[WARN] No no-new-privileges
[WARN] No resource limits

[INFO] Validating: supabase_jwt_secret
[WARN] No user directive
[WARN] No read_only directive
[WARN] No cap_drop: ["ALL"]
[WARN] No no-new-privileges
[WARN] No resource limits

======================================
Summary: 120 passed, 40 warnings, 0 errors

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (7)
pmoves/docs/PRODUCTION_AUDIT_DASHBOARD.md (1)

14-46: ⚠️ Potential issue | 🟠 Major

Missing PR #826 Tailscale/Hostinger infrastructure documentation (duplicate concern).

The Mar 8 audit section documents PRs #823 and #824 but omits PR #826, which contains production-critical infrastructure changes that should be reflected in this production audit dashboard:

  • Tailscale: Migration from deprecated --authkey to --auth-key flag across provisioning scripts
  • Tailscale: Addition of tailscale set --key-expiry=off for persistent node authentication
  • Hostinger: Provider schema alignment to v0.1.22 (ssh_key, post_install_script, ip_address → ipv4_address corrections)
  • Terraform: Validation status for these schema changes

As per coding guidelines, the dashboard should "ensure key-expiry and auth-key changes are reflected, and confirm RG/RG-3 collation hygiene around Hostinger/Tailscale workstreams."

📋 Suggested addition

Consider adding after line 44:

 - Live metrics: Open PRs `0`, Dependabot `1` (medium), Code Scanning `0`
+- **Infrastructure changes (PR `#826` — pending merge):**
+  - Tailscale: Migrated from deprecated `--authkey` to `--auth-key` flag in all VPS provisioning scripts
+  - Tailscale: Added `tailscale set --key-expiry=off` for persistent node authentication (prevents auth expiry)
+  - Hostinger: Provider schema aligned to v0.1.22 — corrected `ssh_key`, `post_install_script`, `ip_address` → `ipv4_address` attribute mapping
+  - Terraform: `required_version`, schema validation pending (requires provider init)
+  - Shell: Fixed bash syntax (removed top-level `local` keyword in deploy-vps.sh)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pmoves/docs/PRODUCTION_AUDIT_DASHBOARD.md` around lines 14 - 46, Add a bullet
for PR `#826` under the "Latest Changes (Mar 8, 2026)" section describing the
Tailscale and Hostinger infra changes and validation status: include that PR
`#826` migrated Tailscale from --authkey to --auth-key and added `tailscale set
--key-expiry=off` for persistent auth, note Hostinger provider schema alignment
to v0.1.22 (ssh_key, post_install_script, ip_address → ipv4_address), and add
Terraform validation status and a short confirmation that RG/RG-3 collation
hygiene and key-expiry/auth-key changes were verified; place this immediately
after the existing PR `#824` bullets so the audit lists `#823`, `#824`, and `#826`
together.
deploy/scripts/deploy-vps.sh (3)

100-102: ⚠️ Potential issue | 🟠 Major

kvm2 health check prints status but never fails the deployment.

The kvm2 branch only prints nginx container status without checking it. A stopped/exited nginx container won't set health_fail.

Proposed fix
         kvm2)
-            ssh "$ssh_target" "docker compose -f docker-compose.yml -f docker-compose.vps.override.yml ps nginx --format '{{.Status}}'"
+            local nginx_status
+            nginx_status=$(ssh "$ssh_target" "docker compose -f docker-compose.yml -f docker-compose.vps.override.yml ps nginx --format '{{.Status}}'" 2>/dev/null || echo "")
+            if [[ "$nginx_status" != *"Up"* ]]; then
+                echo "Nginx: FAIL ($nginx_status)"
+                health_fail=1
+            else
+                echo "Nginx: OK"
+            fi
             ;;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@deploy/scripts/deploy-vps.sh` around lines 100 - 102, The kvm2 branch
currently only prints the nginx container status via ssh without updating the
health_fail flag; capture the output of the ssh "docker compose ... ps nginx
--format '{{.Status}}'" command into a variable (e.g., status_out) inside the
kvm2 case, inspect that value for a healthy indicator (e.g., contains "Up" or
"running") and if it does not, set health_fail=1 and emit a helpful error/log
message; update the kvm2 case in deploy-vps.sh to perform this check rather than
only printing the status.

82-85: ⚠️ Potential issue | 🟠 Major

Remote compose failure is not recorded.

If docker compose pull ... && up -d fails, failed is never set to 1. The deployment can fail silently.

Proposed fix
     # Build and start services
     log_info "$node: Starting services: $services"
-    ssh "$ssh_target" "cd ${WORK_DIR} && ${COMPOSE_CMD} pull ${services} 2>/dev/null && ${COMPOSE_CMD} up -d ${services}"
+    if ! ssh "$ssh_target" "cd ${WORK_DIR} && ${COMPOSE_CMD} pull ${services} 2>/dev/null && ${COMPOSE_CMD} up -d ${services}"; then
+        log_error "$node: compose pull/up failed"
+        failed=1
+    fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@deploy/scripts/deploy-vps.sh` around lines 82 - 85, The ssh command that runs
"${COMPOSE_CMD} pull ... && ${COMPOSE_CMD} up -d ${services}" can fail but the
script never sets failed=1; update the remote-deploy step that calls ssh (using
variables ssh_target, WORK_DIR, COMPOSE_CMD, services, node) to capture the ssh
exit status and on non-zero set failed=1 and log an error via log_error (or
log_info if that's the logger used) including the node and exit code; e.g. run
the ssh call, check its return ($?), and if non-zero call failed=1 and emit a
clear log message mentioning node and services.

52-62: ⚠️ Potential issue | 🟠 Major

check_node uses Tailscale ping but ignores HOSTINGER_*_IP overrides.

When HOSTINGER_KVM4_1_IP is set to a direct IP, check_node still runs tailscale ping "pmoves-${node}", which may fail even though the SSH target is valid. This can incorrectly mark nodes as offline.

Suggested direction
+# Probe node reachability via SSH (honors IP overrides)
+probe_node() {
+    local node="$1"
+    local ssh_target="${NODE_SSH[$node]}"
+    ssh -o BatchMode=yes -o ConnectTimeout=5 "$ssh_target" true &>/dev/null
+}
+
 check_node() {
     local node="$1"
-    if tailscale ping --timeout 3s "pmoves-${node}" &>/dev/null; then
+    if probe_node "$node"; then
         log_info "$node: Tailscale reachable"

Apply similar fix to fleet_status (line 126).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@deploy/scripts/deploy-vps.sh` around lines 52 - 62, The check_node function
currently always uses tailscale ping against "pmoves-${node}" and ignores any
HOSTINGER_*_IP overrides; change check_node to prefer a direct-IP override when
present (e.g., detect HOSTINGER_${node}_IP or use the existing NODE_SSH[$node]
ssh_target) and only use tailscale ping if no direct IP/ssh_target is available,
logging and returning based on that check; apply the same logic to fleet_status
so it also respects HOSTINGER_*_IP overrides instead of always pinging the
tailscale name.
pmoves/examples/distributed/vps/README.md (1)

88-99: ⚠️ Potential issue | 🟠 Major

Hard-coded hostname and invalid CLI command persist.

Two issues flagged in previous reviews remain:

  1. Line 95: The instruction says "On each node" but hard-codes --hostname pmoves-kvm4-1. Following this verbatim will register multiple nodes with the same hostname.

  2. Lines 97-99: tailscale set --key-expiry-disabled is not a valid Tailscale CLI command. Key expiry is managed via admin console or API.

Proposed fix
-On each node (KVM4-1, KVM4-2, KVM2):
+On KVM4-1 and KVM4-2 (replace <HOSTNAME> with actual node name):

 ```bash
 # Install Tailscale
 curl -fsSL https://tailscale.com/install.sh | sh

 # Join mesh with auth key (generate at https://login.tailscale.com/admin/settings/keys)
-tailscale up --auth-key tskey-xxx --hostname pmoves-kvm4-1 --accept-routes --accept-dns
+tailscale up --auth-key tskey-xxx --hostname <HOSTNAME> --accept-routes --accept-dns

-# Disable 180-day key expiry for persistent VPS nodes
-tailscale set --key-expiry-disabled
+# Key expiry: For tagged devices (recommended), expiry is disabled by default.
+# For untagged devices, disable via admin console: https://login.tailscale.com/admin/machines
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @pmoves/examples/distributed/vps/README.md around lines 88 - 99, The README
contains a hard-coded hostname and an invalid Tailscale CLI command; update the
tailscale up invocation to require a per-node placeholder (replace the
hard-coded --hostname pmoves-kvm4-1 with --hostname or instruct users
to set a unique hostname per node) and remove the invalid command tailscale set
--key-expiry-disabled, replacing it with guidance that key expiry is managed
from the admin console or API (point users to the admin machines/settings pages)
and note that tagged devices have expiry disabled by default.


</details>

</blockquote></details>
<details>
<summary>pmoves/terraform/bootstrap-script.sh (1)</summary><blockquote>

`98-102`: _⚠️ Potential issue_ | _🟠 Major_

**`.env.vps` is written but never loaded by Docker Compose.**

The script writes configuration to `.env.vps` (lines 81-91) but the `docker compose up` command doesn't include `--env-file .env.vps`. Services will use their fallback defaults (e.g., internal container URLs) instead of the externally configured VPS URLs.

<details>
<summary>Proposed fix</summary>

```diff
 %{ if docker_compose_profile == "full" ~}
-docker compose -f docker-compose.yml -f docker-compose.vps.override.yml up -d
+docker compose -f docker-compose.yml -f docker-compose.vps.override.yml --env-file .env.vps up -d
 %{ else ~}
-docker compose -f docker-compose.yml -f docker-compose.vps.override.yml --profile ${docker_compose_profile} up -d
+docker compose -f docker-compose.yml -f docker-compose.vps.override.yml --env-file .env.vps --profile ${docker_compose_profile} up -d
 %{ endif ~}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pmoves/terraform/bootstrap-script.sh` around lines 98 - 102, The docker
compose invocation ignores the written .env.vps file so services never get the
VPS env values; update both branches of the docker compose command (the lines
invoking "docker compose -f docker-compose.yml -f
docker-compose.vps.override.yml up -d" and the alternate with "--profile
${docker_compose_profile} up -d") to pass --env-file .env.vps so Docker Compose
loads the .env.vps variables when bringing up the services.
pmoves/scripts/tailscale_setup.sh (1)

190-193: ⚠️ Potential issue | 🟠 Major

The tailscale set --key-expiry-disabled command is invalid and will fail.

This flag does not exist in the Tailscale CLI. Key expiry can only be disabled via:

  • The Tailscale admin console (Machines page)
  • The Tailscale API: POST /api/v2/device/{deviceID}/key with {"keyExpiryDisabled": true}
  • Using auth keys with tags (tagged devices have key expiry disabled by default)

Since the script already applies tags for non-workstation nodes (line 174), those nodes will have key expiry disabled automatically. For workstations, document that manual admin console action is required.

Proposed fix
-    # Disable key expiry for persistent nodes (default 180-day timeout)
-    if ! tailscale set --key-expiry-disabled 2>/dev/null; then
-        log_warning "Could not disable key expiry — approve manually in admin console"
-    fi
+    # Key expiry is disabled automatically for tagged nodes.
+    # For workstations (untagged), disable manually via admin console:
+    # https://login.tailscale.com/admin/machines
+    if [[ "$node_type" == "workstation" ]]; then
+        log_warning "Workstation node: disable 180-day key expiry manually in admin console"
+    fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pmoves/scripts/tailscale_setup.sh` around lines 190 - 193, Remove the invalid
"tailscale set --key-expiry-disabled" invocation and update the fallback
message: reference the tagging logic that runs earlier for non-workstation nodes
(the block that applies Tailscale tags) and state that tagged devices already
have key expiry disabled, and for workstations instruct admins to disable key
expiry via the Tailscale admin console (or the API POST
/api/v2/device/{deviceID}/key with {"keyExpiryDisabled": true}). Replace the
current log_warning call so it clearly says manual action is required for
workstations and mentions the API option; keep the existing log_warning symbol
and the surrounding conditional behavior.
🧹 Nitpick comments (3)
deploy/provision/kvm2-exit-node.sh (1)

31-42: Potential duplication with hostinger-kvm-setup.sh.

This script writes IP forwarding config to /etc/sysctl.d/99-tailscale-exit-node.conf, while deploy/provision/hostinger-kvm-setup.sh (context snippet, lines 212-222) writes the same settings to /etc/sysctl.d/99-tailscale.conf for kvm2 nodes.

If both scripts run on the same node, you'll have duplicate sysctl entries. Consider:

  • Consolidating to a single config filename
  • Having one script source/call the other
  • Documenting that only one should be run
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@deploy/provision/kvm2-exit-node.sh` around lines 31 - 42, The scripts
deploy/provision/kvm2-exit-node.sh and deploy/provision/hostinger-kvm-setup.sh
both deploy identical sysctl entries under different filenames
(99-tailscale-exit-node.conf vs 99-tailscale.conf), causing duplicate sysctl
entries if both run; consolidate by choosing a single canonical config filename
(e.g., 99-tailscale.conf) or make kvm2-exit-node.sh source/call
hostinger-kvm-setup.sh (or vice‑versa) so only one writer manages the sysctl
block, and update the sysctl write in kvm2-exit-node.sh (the cat >>
/etc/sysctl.d/... block and related sysctl --system call) to use that canonical
name or delegation approach and adjust any documentation/comments to state which
script is authoritative.
deploy/provision/hostinger-kvm-setup.sh (2)

148-151: SSH hardening sed commands assume default config format.

These sed substitutions rely on the settings being commented with specific patterns (e.g., #PermitRootLogin yes). On systems where the config has been modified, the patterns may not match and hardening silently won't apply.

Consider using a more robust approach like grep -q to check current state first, or use sshd -T to verify final configuration:

# Verify after sed
sshd -T 2>/dev/null | grep -E '^(permitrootlogin|passwordauthentication|maxauthtries)'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@deploy/provision/hostinger-kvm-setup.sh` around lines 148 - 151, The sed
substitutions that modify /etc/ssh/sshd_config (the three sed commands) assume
exact commented patterns and may silently fail; update the script to be robust
by first checking current settings (e.g., use grep -q or parse sshd -T output)
and then apply idempotent edits that match both commented and uncommented forms
(or replace/ensure keys with a single canonical line), and finally run sshd -T
to verify the effective values for permitrootlogin, passwordauthentication and
maxauthtries before calling systemctl restart sshd to avoid restarting with an
unchanged or invalid config.

269-273: Label inconsistency with hardened runner script for kvm4-2.

The fallback labels for kvm4-2 include kvm4, but install-hardened.sh (line 57) does not include kvm4 for kvm4-2:

Path kvm4-2 labels
Hardened script self-hosted,vps,linux,x64,kvm4-2,production
Fallback here self-hosted,vps,kvm4,kvm4-2,production,Linux,X64

If workflows target the kvm4 label expecting to hit kvm4-2, behavior will differ based on which install path was used.

🔧 Proposed fix to align with hardened script
         case "$NODE_TYPE" in
             kvm4-1) labels="self-hosted,vps,kvm4,kvm4-1,production,Linux,X64" ;;
-            kvm4-2) labels="self-hosted,vps,kvm4,kvm4-2,production,Linux,X64" ;;
+            kvm4-2) labels="self-hosted,vps,kvm4-2,production,Linux,X64" ;;
             kvm2)   labels="self-hosted,vps,kvm2,backup,Linux,X64" ;;
         esac
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@deploy/provision/hostinger-kvm-setup.sh` around lines 269 - 273, The fallback
labels for the NODE_TYPE case "kvm4-2" are inconsistent with the hardened
runner; update the kvm4-2 branch in the case block that sets the labels variable
so it matches the hardened script by removing the generic kvm4 label and using
lowercase platform tags, e.g. set labels to
"self-hosted,vps,linux,x64,kvm4-2,production" for the kvm4-2 case so both
install paths yield identical labels.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@deploy/provision/kvm2-exit-node.sh`:
- Around line 58-61: The tailscale CLI call using "tailscale set
--key-expiry-disabled" is invalid; update the provisioning so key expiry is
handled correctly: either add appropriate advertise tags to the "tailscale up"
invocation (e.g., include "--advertise-tags=tag:pmoves,tag:exit-node" alongside
the existing "--auth-key") so the device gets persistent key behavior, or remove
the invalid "tailscale set --key-expiry-disabled" block and add a clear note in
the script (or surrounding docs) that an admin must disable key expiry in the
Tailscale admin console; adjust the log function "log_warn" message accordingly
to reflect the chosen approach.

In `@deploy/runners/vps/install-hardened.sh`:
- Around line 55-62: The kvm4-2 case in the shell case statement omits the
shared kvm4 label causing workflows targeting "kvm4" to miss that runner; update
the "*kvm4-2*)" branch so it echoes the same base_labels plus
"kvm4,kvm4-2,production" (matching the "*kvm4-1*)" pattern) — specifically
modify the line that currently echoes "${base_labels},kvm4-2,production" to
include "kvm4" as "${base_labels},kvm4,kvm4-2,production" so the runner matches
runs-on: [self-hosted, kvm4, production].

In `@pmoves/terraform/variables.auto.tfvars.example`:
- Around line 1-38: The example vars file is missing the required variable
kvm_nodes (declared without default in mcp-integration.tf), which breaks
terraform validate; add an example kvm_nodes entry (e.g., a numeric or list
value matching the variable type used in mcp-integration.tf) to
variables.auto.tfvars.example so users have a valid default to copy, and also
add optional placeholder entries for tailscale_authkey and github_pat (marked as
sensitive in docs) for discoverability; ensure the variable names match exactly:
kvm_nodes, tailscale_authkey, github_pat.

---

Duplicate comments:
In `@deploy/scripts/deploy-vps.sh`:
- Around line 100-102: The kvm2 branch currently only prints the nginx container
status via ssh without updating the health_fail flag; capture the output of the
ssh "docker compose ... ps nginx --format '{{.Status}}'" command into a variable
(e.g., status_out) inside the kvm2 case, inspect that value for a healthy
indicator (e.g., contains "Up" or "running") and if it does not, set
health_fail=1 and emit a helpful error/log message; update the kvm2 case in
deploy-vps.sh to perform this check rather than only printing the status.
- Around line 82-85: The ssh command that runs "${COMPOSE_CMD} pull ... &&
${COMPOSE_CMD} up -d ${services}" can fail but the script never sets failed=1;
update the remote-deploy step that calls ssh (using variables ssh_target,
WORK_DIR, COMPOSE_CMD, services, node) to capture the ssh exit status and on
non-zero set failed=1 and log an error via log_error (or log_info if that's the
logger used) including the node and exit code; e.g. run the ssh call, check its
return ($?), and if non-zero call failed=1 and emit a clear log message
mentioning node and services.
- Around line 52-62: The check_node function currently always uses tailscale
ping against "pmoves-${node}" and ignores any HOSTINGER_*_IP overrides; change
check_node to prefer a direct-IP override when present (e.g., detect
HOSTINGER_${node}_IP or use the existing NODE_SSH[$node] ssh_target) and only
use tailscale ping if no direct IP/ssh_target is available, logging and
returning based on that check; apply the same logic to fleet_status so it also
respects HOSTINGER_*_IP overrides instead of always pinging the tailscale name.

In `@pmoves/docs/PRODUCTION_AUDIT_DASHBOARD.md`:
- Around line 14-46: Add a bullet for PR `#826` under the "Latest Changes (Mar 8,
2026)" section describing the Tailscale and Hostinger infra changes and
validation status: include that PR `#826` migrated Tailscale from --authkey to
--auth-key and added `tailscale set --key-expiry=off` for persistent auth, note
Hostinger provider schema alignment to v0.1.22 (ssh_key, post_install_script,
ip_address → ipv4_address), and add Terraform validation status and a short
confirmation that RG/RG-3 collation hygiene and key-expiry/auth-key changes were
verified; place this immediately after the existing PR `#824` bullets so the audit
lists `#823`, `#824`, and `#826` together.

In `@pmoves/examples/distributed/vps/README.md`:
- Around line 88-99: The README contains a hard-coded hostname and an invalid
Tailscale CLI command; update the tailscale up invocation to require a per-node
placeholder (replace the hard-coded --hostname pmoves-kvm4-1 with --hostname
<HOSTNAME> or instruct users to set a unique hostname per node) and remove the
invalid command tailscale set --key-expiry-disabled, replacing it with guidance
that key expiry is managed from the admin console or API (point users to the
admin machines/settings pages) and note that tagged devices have expiry disabled
by default.

In `@pmoves/scripts/tailscale_setup.sh`:
- Around line 190-193: Remove the invalid "tailscale set --key-expiry-disabled"
invocation and update the fallback message: reference the tagging logic that
runs earlier for non-workstation nodes (the block that applies Tailscale tags)
and state that tagged devices already have key expiry disabled, and for
workstations instruct admins to disable key expiry via the Tailscale admin
console (or the API POST /api/v2/device/{deviceID}/key with
{"keyExpiryDisabled": true}). Replace the current log_warning call so it clearly
says manual action is required for workstations and mentions the API option;
keep the existing log_warning symbol and the surrounding conditional behavior.

In `@pmoves/terraform/bootstrap-script.sh`:
- Around line 98-102: The docker compose invocation ignores the written .env.vps
file so services never get the VPS env values; update both branches of the
docker compose command (the lines invoking "docker compose -f docker-compose.yml
-f docker-compose.vps.override.yml up -d" and the alternate with "--profile
${docker_compose_profile} up -d") to pass --env-file .env.vps so Docker Compose
loads the .env.vps variables when bringing up the services.

---

Nitpick comments:
In `@deploy/provision/hostinger-kvm-setup.sh`:
- Around line 148-151: The sed substitutions that modify /etc/ssh/sshd_config
(the three sed commands) assume exact commented patterns and may silently fail;
update the script to be robust by first checking current settings (e.g., use
grep -q or parse sshd -T output) and then apply idempotent edits that match both
commented and uncommented forms (or replace/ensure keys with a single canonical
line), and finally run sshd -T to verify the effective values for
permitrootlogin, passwordauthentication and maxauthtries before calling
systemctl restart sshd to avoid restarting with an unchanged or invalid config.
- Around line 269-273: The fallback labels for the NODE_TYPE case "kvm4-2" are
inconsistent with the hardened runner; update the kvm4-2 branch in the case
block that sets the labels variable so it matches the hardened script by
removing the generic kvm4 label and using lowercase platform tags, e.g. set
labels to "self-hosted,vps,linux,x64,kvm4-2,production" for the kvm4-2 case so
both install paths yield identical labels.

In `@deploy/provision/kvm2-exit-node.sh`:
- Around line 31-42: The scripts deploy/provision/kvm2-exit-node.sh and
deploy/provision/hostinger-kvm-setup.sh both deploy identical sysctl entries
under different filenames (99-tailscale-exit-node.conf vs 99-tailscale.conf),
causing duplicate sysctl entries if both run; consolidate by choosing a single
canonical config filename (e.g., 99-tailscale.conf) or make kvm2-exit-node.sh
source/call hostinger-kvm-setup.sh (or vice‑versa) so only one writer manages
the sysctl block, and update the sysctl write in kvm2-exit-node.sh (the cat >>
/etc/sysctl.d/... block and related sysctl --system call) to use that canonical
name or delegation approach and adjust any documentation/comments to state which
script is authoritative.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2a9964fc-0e28-4b8b-b58c-edefbb76792d

📥 Commits

Reviewing files that changed from the base of the PR and between e06bdb4 and c671f47.

📒 Files selected for processing (13)
  • .github/workflows/sync-secrets-local.yml
  • deploy/provision/hostinger-kvm-setup.sh
  • deploy/provision/kvm2-exit-node.sh
  • deploy/runners/vps/install-hardened.sh
  • deploy/scripts/deploy-vps.sh
  • pbnj/pinokio/api/pmoves-pbnj/kvm4-1-deploy.json
  • pmoves/docs/PRODUCTION_AUDIT_DASHBOARD.md
  • pmoves/examples/distributed/vps/README.md
  • pmoves/scripts/tailscale_setup.sh
  • pmoves/terraform/bootstrap-script.sh
  • pmoves/terraform/mcp-integration.tf
  • pmoves/terraform/variables.auto.tfvars.example
  • pmoves/tools/crush_configurator.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • pbnj/pinokio/api/pmoves-pbnj/kvm4-1-deploy.json
  • pmoves/tools/crush_configurator.py
  • .github/workflows/sync-secrets-local.yml

Comment thread deploy/provision/kvm2-exit-node.sh Outdated
Comment on lines +55 to 62
*kvm4-1*)
echo "${base_labels},kvm4,kvm4-1,production"
;;
*kvm4-2*)
echo "${base_labels},kvm4-2,production"
;;
*kvm4*)
echo "${base_labels},kvm4,production"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Inconsistent labeling: kvm4-2 lacks kvm4 label, breaking workflow targeting.

The kvm4-1 case (line 56) correctly includes kvm4,kvm4-1,production, but kvm4-2 (line 59) only emits kvm4-2,production without the kvm4 label.

The workflow at .github/workflows/self-hosted-builds-hardened.yml:382 uses runs-on: [self-hosted, kvm4, production], which will never match kvm4-2 runners.

Proposed fix
         *kvm4-2*)
-            echo "${base_labels},kvm4-2,production"
+            echo "${base_labels},kvm4,kvm4-2,production"
             ;;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
*kvm4-1*)
echo "${base_labels},kvm4,kvm4-1,production"
;;
*kvm4-2*)
echo "${base_labels},kvm4-2,production"
;;
*kvm4*)
echo "${base_labels},kvm4,production"
*kvm4-1*)
echo "${base_labels},kvm4,kvm4-1,production"
;;
*kvm4-2*)
echo "${base_labels},kvm4,kvm4-2,production"
;;
*kvm4*)
echo "${base_labels},kvm4,production"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@deploy/runners/vps/install-hardened.sh` around lines 55 - 62, The kvm4-2 case
in the shell case statement omits the shared kvm4 label causing workflows
targeting "kvm4" to miss that runner; update the "*kvm4-2*)" branch so it echoes
the same base_labels plus "kvm4,kvm4-2,production" (matching the "*kvm4-1*)"
pattern) — specifically modify the line that currently echoes
"${base_labels},kvm4-2,production" to include "kvm4" as
"${base_labels},kvm4,kvm4-2,production" so the runner matches runs-on:
[self-hosted, kvm4, production].

Comment on lines +1 to +38
# PMOVES.AI Terraform Variables — Example Configuration
# Copy to variables.auto.tfvars and fill in real values.
# NEVER commit the actual .tfvars file (it's in .gitignore).

# Hostinger API (get from hPanel → API)
hostinger_api_token = "YOUR_HOSTINGER_API_TOKEN"

# Project settings
project_name = "pmoves-ai"
data_center_id = 13 # US

# Node plans (see Hostinger VPS plans)
vps_plan = "hostingercom-vps-kvm4-usd-4m"

# OS template (verify via Hostinger API — 1 = Ubuntu 22.04 placeholder)
os_template_id = 1

# SSH access
root_password = "YourStr0ngP@ssword!"
ssh_public_key = "ssh-ed25519 AAAA..."

# Domain
domain_name = "pmoves.ai"

# Git
git_repo_url = "https://github.com/POWERFULMOVES/PMOVES.AI.git"
git_branch = "main"
submodule_init = true

# Docker Compose profile
docker_compose_profile = "full"
enable_gpu = false

# NATS (with authentication)
nats_url = "nats://nats:pmoves@nats:4222"

# Monitoring
monitoring_retention_days = 30

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing required variable kvm_nodes will cause terraform validate to fail.

The kvm_nodes variable is declared in pmoves/terraform/mcp-integration.tf (lines 50-78) without a default value, making it required. Users who copy this example file will encounter:

Error: No value for required variable
Proposed fix: add kvm_nodes example
 # Monitoring
 monitoring_retention_days = 30
+
+# Multi-node fleet configuration (required)
+kvm_nodes = {
+  "kvm4-1" = {
+    plan     = "hostingercom-vps-kvm4-usd-4m"
+    role     = "api-gateway"
+    hostname = "pmoves-kvm4-1"
+    profile  = "agents"
+  }
+  "kvm4-2" = {
+    plan     = "hostingercom-vps-kvm4-usd-4m"
+    role     = "data-services"
+    hostname = "pmoves-kvm4-2"
+    profile  = "knowledge"
+  }
+  "kvm2" = {
+    plan     = "hostingercom-vps-kvm2-usd-4m"
+    role     = "exit-node"
+    hostname = "pmoves-kvm2"
+    profile  = "monitoring"
+  }
+}

Also consider adding placeholders for tailscale_authkey and github_pat (sensitive, but useful for discoverability).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pmoves/terraform/variables.auto.tfvars.example` around lines 1 - 38, The
example vars file is missing the required variable kvm_nodes (declared without
default in mcp-integration.tf), which breaks terraform validate; add an example
kvm_nodes entry (e.g., a numeric or list value matching the variable type used
in mcp-integration.tf) to variables.auto.tfvars.example so users have a valid
default to copy, and also add optional placeholder entries for tailscale_authkey
and github_pat (marked as sensitive in docs) for discoverability; ensure the
variable names match exactly: kvm_nodes, tailscale_authkey, github_pat.

@POWERFULMOVES
POWERFULMOVES merged commit a109938 into main Mar 8, 2026
19 of 20 checks passed
POWERFULMOVES pushed a commit that referenced this pull request Mar 8, 2026
- Makefile: supa-collation-refresh fails loudly on ALTER DATABASE errors
- Makefile: supa-collation-check detects NULL/mismatched collation versions
- deploy-vps: check_node() uses SSH probe, honors HOSTINGER_*_IP overrides
- terraform: pin Hostinger provider to 0.1.22
- terraform/deploy: wire .env.vps into compose via --env-file
- docs: filter 12 .venv false positives from tooling audit overlap table
- docs: add VPS fleet workstream to production audit dashboard

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
POWERFULMOVES added a commit that referenced this pull request Mar 8, 2026
…5-826

fix(review): address deferred CodeRabbit items from #825/#826
@POWERFULMOVES
POWERFULMOVES deleted the fix/tailscale-hostinger-doc-validation branch March 8, 2026 23:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants