35 KiB
Phase 9: Tooling & Portable Setup — Research
Researched: 2026-06-15 Domain: Docker image build, portable bash setup script, Hermes config management Confidence: HIGH (verified via official sources for all tool versions and installation methods)
Summary
Phase 9 has two independent workstreams: (1) a custom Dockerfile extending nikolaik/python-nodejs with AWS CLI v2, Terraform, Helm, kubectl, and Datadog CLI (pup), and (2) a portable bash setup script that recreates all ngn-agent configuration on a fresh macOS machine. Both are new files living in the project repo — the Dockerfile at docker/Dockerfile with a build script at docker/build.sh, and the setup script at setup-ngn-agent.sh.
All five tools for the Docker image are installable on the Debian-based base image. AWS CLI v2 uses its official curl→unzip→install script (no apt repo for v2). Terraform and kubectl have official apt repos. Helm has a community-maintained Buildkite apt repo. Pup (Datadog CLI) is a Rust binary downloaded from GitHub releases. The base image tag python3.11-nodejs20 may no longer be available — the maintainer has dropped Python 3.11 + Node.js 20 tags; the smallest Node.js version for Python 3.11 is now python3.11-nodejs22. The setup script should use hermes config set for individual key paths where possible, with sed as fallback for complex YAML structures.
Primary recommendation: Two plans — (1) Dockerfile + build script with pinned tool versions, (2) portable setup script with interactive prompt flow for secrets.
Phase Requirements
| ID | Description | Research Support |
|---|---|---|
| TOOL-01 | Custom Hermes Docker image with aws-cli, terraform, helm, kubectl, datadog CLI | All five tools have documented installation methods for Debian-based images. Versions verified from official sources. |
| SETUP-01 | Portable setup-ngn-agent.sh script recreating all config | Current config.yaml (565 lines), .env (484 lines), hindsight/config.json, 2 scripts, 5 skills, and 3 cron jobs all identified. |
User Constraints (from CONTEXT.md)
<user_constraints>
Locked Decisions
Custom Docker Image
- D-01: Dockerfile lives in this repo at
ngn-agent/docker/Dockerfile— extendsnikolaik/python-nodejs:python3.11-nodejs20 - D-02: Pin specific tool versions — Dockerfile should specify exact versions for reproducibility
- D-03: Tools to include:
- aws-cli: v2 (latest stable)
- terraform: latest stable
- helm: latest stable
- kubectl: latest stable matching cluster version
- datadog CLI (
pup): latest stable
- D-04: Build script at
ngn-agent/docker/build.sh— single command to build the image - D-05: Image tag:
ngn-agent:latest(local only, no registry push)
Portable Setup Script
- D-06: Single script at
ngn-agent/setup-ngn-agent.sh— recreates all configuration on a fresh machine - D-07: Assumes Hermes v0.16+ is already installed and
hermesCLI is on PATH - D-08: Interactive prompts for all secrets:
JIRA_API_TOKEN(required for Atlassian integrations)JIRA_EMAIL(required for Atlassian integrations)TELEGRAM_BOT_TOKEN(required for gateway)OPENROUTER_API_KEY(if not already set)
- D-09: Configurable parameters (supplied via args or prompts):
- SSH key paths (default:
~/.ssh/id_ed25519razer,~/.ssh/id_rsa) - SSH config path (default:
~/.ssh/config) - SSH known_hosts path (default:
~/.ssh/known_hosts) - Repo paths (default:
~/Razer/rai-ops,~/Razer/rai-deployment,~/Razer/rai-devtools) - Timezone (default:
Asia/Singapore)
- SSH key paths (default:
- D-10: What the setup script creates/updates:
~/.hermes/config.yaml— docker_volumes (SSH + repo mounts), shell_init_files, docker_forward_env, cron config~/.hermes/.env— secrets and DEFAULT_REPOS~/.hermes/hindsight/config.json— Hindsight config~/.hermes/scripts/session-init.sh— mount verification script~/.hermes/scripts/archive-stale-sessions.sh— archive script~/.hermes/skills/ngn-agent/— all 5 skill directories~/.hermes/archive/sessions/— archive directory- Register 3 cron jobs (ngn-daily-report, ngn-weekly-stale-summary, ngn-weekly-archive)
- Update Docker image reference in config.yaml
the agent's Discretion
- Dockerfile tool version selection: Choose stable versions current at time of implementation
- Setup script structure: Interactive prompt flow, output formatting, error handling approach
- Config file templates: How to generate config.yaml sections, .env format, etc.
Deferred Ideas (OUT OF SCOPE)
- Multi-architecture image builds (arm64 + amd64) — defer until needed
- Cloud-native deployment (Docker Compose, Fly.io, etc.) — out of scope
- CI/CD for image builds — out of scope </user_constraints>
Architectural Responsibility Map
| Capability | Primary Tier | Secondary Tier | Rationale |
|---|---|---|---|
| Docker image build | Developer Machine (macOS) | — | Builds locally, docker build runs on host |
| Tool installation in Dockerfile | Docker Image Build (CI/macOS) | — | Each RUN layer installs tool; version-pinned for reproducibility |
| Interactive secret prompts | Setup Script (macOS CLI) | — | read -s reads secrets from stdin, writes to ~/.hermes/.env |
| Config file generation | Setup Script (macOS CLI) | — | Creates/modifies ~/.hermes/config.yaml, .env, config.json |
| Cron job registration | Setup Script → Hermes CLI | — | Uses hermes cron create CLI commands |
| Skill file copying | Setup Script (macOS CLI) | — | Copies SKILL.md files from embedded base64 or repo |
Standard Stack
Core
| Tool | Method | Purpose | Installation Source |
|---|---|---|---|
| Base Image | FROM nikolaik/python-nodejs:python3.11-nodejs22 |
Hermes-compatible Python + Node.js runtime | Docker Hub (official nikolaik image) |
| AWS CLI v2 | curl → unzip → ./aws/install |
AWS diagnostics via CLI | Official docs |
| Terraform | HashiCorp apt repo → apt install terraform |
Infrastructure-as-code management | HashiCorp Developer |
| Helm | Buildkite apt repo → apt install helm |
Kubernetes package management | Helm docs |
| kubectl | Google Kubernetes apt repo → apt install kubectl |
Kubernetes cluster management | Kubernetes docs |
| Datadog CLI (pup) | curl binary from GitHub Releases | Datadog observability via CLI | DataDog/pup releases |
Pinned Versions (current as of 2026-06-15)
| Tool | Version | Source | Confidence |
|---|---|---|---|
| AWS CLI v2 | 2.27.41 | CITED: docs.aws.amazon.com/cli/ — version shown in output example | HIGH |
| Terraform | 1.15.6 | CITED: developer.hashicorp.com/terraform/install | HIGH |
| Helm | 4.2.1 | CITED: helm.sh/docs/intro/install + github.com/helm/helm/releases/tag/v4.2.1 | HIGH |
| kubectl | 1.36.1 | CITED: kubernetes.io/releases — latest stable | HIGH |
| Datadog CLI (pup) | 1.1.0 | CITED: github.com/DataDog/pup/releases/tag/v1.1.0 | HIGH |
Base Image Tag Issue
⚠ D-01 specifies nikolaik/python-nodejs:python3.11-nodejs20 but this tag may no longer be available. [VERIFIED: hub.docker.com/r/nikolaik/python-nodejs] The current tag table shows Python 3.11 is only available with Node.js 26, 24, or 22. The nodejs20 tags were likely removed when Node.js 20 reached end of life (April 2026).
Available Python 3.11 tags (as of 2026-06-15):
python3.11-nodejs26(Node.js 26.3.0, Debian trixie) — latestpython3.11-nodejs26-bookworm(Node.js 26.3.0, Debian bookworm)python3.11-nodejs26-slimpython3.11-nodejs24(Node.js 24.16.0, Debian trixie)python3.11-nodejs24-bookworm(Node.js 24.16.0, Debian bookworm)python3.11-nodejs22(Node.js 22.22.3, Debian trixie)python3.11-nodejs22-bookworm(Node.js 22.22.3, Debian bookworm)
Recommendation: Use python3.11-nodejs22-bookworm as a close match (Node.js 22 is still under LTS until Apr 2027). This needs user confirmation — flag for discuss-phase.
Setup Script Standard Stack
| Library/Tool | Usage | Why |
|---|---|---|
bash (built-in) |
Script host | Zero dependencies, available on every macOS machine |
read -s |
Secret input | Masked input for passwords/tokens |
hermes config set |
Config YAML modification | Prefer over raw sed for individual key paths |
sed -i |
Config YAML fallback | For complex multi-line YAML blocks (e.g., docker_volumes array) |
crontab |
Cron job fallback | If hermes cron create is not available |
base64 -d |
Embedded file extraction | Embeds skill files and scripts as base64 in setup script |
Package Legitimacy Audit
No external packages from package registries (npm/PyPI/crates) are installed in this phase. All tools are installed via OS-level package managers (apt, curl binary downloads) or built into the Docker image. No
npm install,pip install, orcargo installis needed.
| Package | Registry | Verdict | Disposition |
|---|---|---|---|
| (none) | — | — | No packages to audit |
Architecture Patterns
System Architecture Diagram
┌─────────────────────────────────────────────────────────────┐
│ ngn-agent Project Repo │
│ │
│ docker/ setup-ngn-agent.sh │
│ ├── Dockerfile (portable setup script) │
│ └── build.sh │
│ (builds image) │
└─────────┬───────────────────────────────────────┬───────────┘
│ │
▼ ▼
┌─────────────────────┐ ┌─────────────────────────────────┐
│ Custom Docker Image │ │ Fresh macOS Machine │
│ (tag: ngn-agent) │ │ │
│ │ │ ├─ Prerequisite checks │
│ Base: nikolaik/ │ │ │ (Hermes installed? │
│ python-nodejs │ │ │ Docker running? │
│ │ │ │ SSH keys exist?) │
│ Installed tools: │ │ ├─ Interactive secret prompts │
│ ├─ aws-cli 2.27.41 │ │ │ (JIRA_API_TOKEN, etc.) │
│ ├─ terraform 1.15.6│ │ ├─ Config file generation │
│ ├─ helm 4.2.1 │ │ │ (config.yaml, .env, etc.) │
│ ├─ kubectl 1.36.1 │ │ ├─ Script/skill copying │
│ └─ pup 1.1.0 │ │ ├─ Cron registration │
│ │ │ └─ Gateway restart offer │
└─────────────────────┘ └─────────────────────────────────┘
Recommended Project Structure
ngn-agent/
├── docker/
│ ├── Dockerfile # Custom Hermes image with added tools
│ └── build.sh # Single-command build script
├── setup-ngn-agent.sh # Portable setup script (standalone)
Note: The setup script embeds skill files and scripts as base64-encoded here-documents so it's fully self-contained. No external file dependencies needed.
Pattern 1: Multi-Tool Dockerfile with Version Pinning
What: A Dockerfile that installs 5 platform engineering tools on top of the base Python+Node.js image, using version-pinned installations for reproducibility.
When to use: Any time a custom Hermes Docker image is built with additional CLI tools.
Example:
# Source: [CITED: developer.hashicorp.com/terraform/install] + [CITED: docs.aws.amazon.com/cli/]
# Use ARGs for version pinning
ARG TERRAFORM_VERSION=1.15.6
ARG HELM_VERSION=4.2.1
ARG KUBECTL_VERSION=1.36.1
ARG PUPP_VERSION=1.1.0
# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
ca-certificates \
unzip \
gnupg \
&& rm -rf /var/lib/apt/lists/*
# Install AWS CLI v2 (no apt repo for v2 — use official installer)
RUN curl -fsSL "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" \
&& unzip -q awscliv2.zip \
&& ./aws/install --bin-dir /usr/local/bin --install-dir /usr/local/aws-cli \
&& rm -rf awscliv2.zip aws/
# Install Terraform via HashiCorp apt repo
RUN wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(grep -oP '(?<=UBUNTU_CODENAME=).*' /etc/os-release || lsb_release -cs) main" \
| tee /etc/apt/sources.list.d/hashicorp.list \
&& apt-get update && apt-get install -y terraform=${TERRAFORM_VERSION} \
&& rm -rf /var/lib/apt/lists/*
# Install kubectl via Google Kubernetes apt repo
RUN curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.36/deb/Release.key | gpg --dearmor -o /usr/share/keyrings/kubernetes-apt-keyring.gpg \
&& echo 'deb [signed-by=/usr/share/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.36/deb/ /' \
| tee /etc/apt/sources.list.d/kubernetes.list \
&& apt-get update && apt-get install -y kubectl=${KUBECTL_VERSION}-* \
&& rm -rf /var/lib/apt/lists/*
# Install Helm via Buildkite apt repo
RUN curl -fsSL https://packages.buildkite.com/helm-linux/helm-debian/gpgkey | gpg --dearmor -o /usr/share/keyrings/helm.gpg \
&& echo "deb [signed-by=/usr/share/keyrings/helm.gpg] https://packages.buildkite.com/helm-linux/helm-debian/any/ any main" \
| tee /etc/apt/sources.list.d/helm-stable-debian.list \
&& apt-get update && apt-get install -y helm=${HELM_VERSION} \
&& rm -rf /var/lib/apt/lists/*
# Install Datadog CLI (pup) — Rust binary from GitHub releases
RUN curl -fsSL "https://github.com/DataDog/pup/releases/download/v${PUPP_VERSION}/pup_${PUPP_VERSION}_Linux_x86_64.tar.gz" \
-o /tmp/pup.tar.gz \
&& tar xzf /tmp/pup.tar.gz -C /usr/local/bin/ pup \
&& rm -f /tmp/pup.tar.gz
# Verify all tools
RUN aws --version && terraform --version && helm version && kubectl version --client && pup --version
Pattern 2: Interactive Secret Prompt with Validation
What: A bash function that prompts for a secret with masked input, validates it's non-empty, and offers to retry if empty.
When to use: Any bash setup script that needs to collect API tokens or passwords interactively.
Example:
# Source: Standard bash pattern — no single authoritative source
prompt_secret() {
local var_name="$1"
local prompt_text="$2"
local default="${3:-}"
local val=""
while [ -z "$val" ]; do
if [ -n "$default" ]; then
read -s -p "$prompt_text (default: ${default:0:4}...): " val
else
read -s -p "$prompt_text: " val
fi
echo
if [ -z "$val" ] && [ -n "$default" ]; then
val="$default"
break
elif [ -z "$val" ]; then
echo " ⚠ Value cannot be empty. Press Ctrl+C to cancel."
fi
done
echo "$val"
}
Pattern 3: Using hermes config set vs sed for YAML
What: The Hermes CLI provides hermes config set <path> <value> for individual key-value pairs in config.yaml. For complex structures (arrays like docker_volumes), fall back to sed or YAML-aware tools.
When to use: Prefer hermes config set for simple key paths. Use sed for multi-line YAML sections (e.g., the entire terminal: block with docker_volumes list).
Example:
# Simple key-value — use hermes config set
hermes config set memory.provider hindsight
hermes config set terminal.backend docker
hermes config set terminal.timezone Asia/Singapore
hermes config set approvals.mode manual
# Complex YAML structures — use sed with a heredoc template
# Example: updating terminal.docker_image
hermes config set terminal.docker_image ngn-agent:latest
# docker_volumes is an array — build via sed or yq
# For arrays, hermes config set may not work; use sed or Python
Anti-Patterns to Avoid
- Installing tools via
apt-get installwithout version pinning — leads to unreproducible builds. Always pin versions with=versionsyntax or download specific releases. - Using
snapin Docker — snap requires systemd which doesn't run inside Docker containers. Use curl/apt binary installation instead. - Hardcoding user paths in config templates — the setup script must parameterize all paths (SSH keys, repo paths).
- Overwriting existing config without backup — setup script should back up existing
~/.hermes/config.yamlbefore modifying.
Don't Hand-Roll
| Problem | Don't Build | Use Instead | Why |
|---|---|---|---|
| YAML manipulation in bash | Custom YAML parser with sed/awk |
hermes config set (for simple keys), yq or Python yaml for complex structures |
YAML is whitespace-sensitive; fragile with sed |
| Cron job management | Writing to crontab directly | hermes cron create CLI |
Hermes cron is managed in its internal DB, not system crontab; crontab entries would bypass Hermes delivery |
| SSH key generation | Automated SSH key creation | Skip — require user to have keys; script just validates they exist | SSH keys with passphrase prompting would break headless operation |
| AWS credential handling | Storing AWS keys in .env | Use existing ./.aws/ SSO config mounted as volume |
ngn-agent uses SSO role chaining, not static keys |
| Docker image registry | Pushing to Docker Hub/GHCR | Tag as ngn-agent:latest local only |
No CI/CD pipeline established; manual docker build in CONTEXT.md scope |
Key insight: Three things in this phase already have canonical solutions from Hermes itself (hermes config set, hermes cron create) or from the project's existing architecture (SSO-based AWS auth, SSH key assumption). Building alternatives to these is wasted effort.
Common Pitfalls
Pitfall 1: Base image tag python3.11-nodejs20 is deprecated
What goes wrong: Docker build fails with manifest for nikolaik/python-nodejs:python3.11-nodejs20 not found.
Why it happens: The image maintainer drops tags when Node.js versions reach EOL. Node.js 20 reached EOL in April 2026.
How to avoid: Use python3.11-nodejs22 or python3.11-nodejs22-bookworm instead — Node.js 22 is LTS until April 2027.
Warning signs: docker pull nikolaik/python-nodejs:python3.11-nodejs20 fails with manifest error.
Pitfall 2: hermes cron create requires specific CLI syntax
What goes wrong: Cron job creation fails with CLI errors.
Why it happens: The hermes cron create CLI has evolved across Hermes versions. The Phase 8 summaries documented the correct syntax: hermes cron create --deliver telegram --skill session '0 9 * * *' 'prompt text'.
How to avoid: Use the exact patterns verified in Phase 8:
- Skill-backed:
hermes cron create --deliver telegram --skill <name> 'schedule' 'prompt' - No-agent:
hermes cron create --no-agent --script <path> 'schedule'Warning signs:Unknown flagerror from hermes CLI.
Pitfall 3: apt-get install terraform version pinning format
What goes wrong: Apt fails to find the exact version specified.
Why it happens: HashiCorp's apt repo uses specific version formats. The installable version string for terraform is 1.15.6 (just the X.Y.Z).
How to avoid: Use apt-get install -y terraform=1.15.6 — the version string matches the release tag.
Warning signs: E: Version '1.15.6-1' not found — try without the -1 suffix.
Pitfall 4: Embedded script files in setup script become stale
What goes wrong: The setup script copies skill files and scripts that are included as base64-encoded content, but these drift from the actual source files in ~/.hermes/.
Why it happens: The setup script is a snapshot at the time of creation; the skill files evolve independently.
How to avoid: Source the skill files from the project repo at setup time rather than embedding them. If the skills live in git, copy from the cloned repo. If not, add a --snapshot-date comment in the script header noting when the embedded content was frozen.
Warning signs: User runs setup in 3 months and gets outdated skills.
Pitfall 5: Dockerfile RUN layer cache busting
What goes wrong: Changing one tool's version rebuilds all subsequent layers because they're in separate RUN commands that invalidate the apt cache. Why it happens: Each RUN command creates a new layer. If a tool's download URL changes, all subsequent layers after it are invalidated. How to avoid: Order installs from most-frequently-changed (pup, kubectl) to least-changed (terraform, aws-cli), or combine all apt installs into one RUN.
Code Examples
Dockerfile Complete — Multi-Tool Installation
# Source: [CITED: Multiple official tool installation docs — see Standard Stack]
ARG PYTHON_NODEJS_TAG=python3.11-nodejs22-bookworm
FROM nikolaik/python-nodejs:${PYTHON_NODEJS_TAG}
LABEL description="ngn-agent: Custom Hermes Docker image with platform engineering tools"
LABEL maintainer="ngn-agent"
# Tool versions — pin for reproducibility
ARG TERRAFORM_VERSION=1.15.6
ARG HELM_VERSION=4.2.1
ARG KUBECTL_VERSION=1.36.1
ARG PUPP_VERSION=1.1.0
# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
ca-certificates \
unzip \
gnupg \
wget \
&& rm -rf /var/lib/apt/lists/*
# Install AWS CLI v2 (official installer — no apt repo for v2)
RUN curl -fsSL "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" \
&& unzip -q awscliv2.zip \
&& ./aws/install --bin-dir /usr/local/bin --install-dir /usr/local/aws-cli \
&& rm -rf awscliv2.zip aws/
# Install Terraform (HashiCorp apt repo)
RUN wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" \
| tee /etc/apt/sources.list.d/hashicorp.list \
&& apt-get update && apt-get install -y terraform=${TERRAFORM_VERSION} \
&& rm -rf /var/lib/apt/lists/*
# Install kubectl (Google Kubernetes apt repo)
RUN curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.36/deb/Release.key | gpg --dearmor -o /usr/share/keyrings/kubernetes-apt-keyring.gpg \
&& echo 'deb [signed-by=/usr/share/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.36/deb/ /' \
| tee /etc/apt/sources.list.d/kubernetes.list \
&& apt-get update && apt-get install -y kubectl \
&& rm -rf /var/lib/apt/lists/*
# Install Helm (Buildkite apt repo)
RUN curl -fsSL https://packages.buildkite.com/helm-linux/helm-debian/gpgkey | gpg --dearmor -o /usr/share/keyrings/helm.gpg \
&& echo "deb [signed-by=/usr/share/keyrings/helm.gpg] https://packages.buildkite.com/helm-linux/helm-debian/any/ any main" \
| tee /etc/apt/sources.list.d/helm-stable-debian.list \
&& apt-get update && apt-get install -y helm \
&& rm -rf /var/lib/apt/lists/*
# Install Datadog CLI (pup) — Rust binary from GitHub releases
RUN curl -fsSL "https://github.com/DataDog/pup/releases/download/v${PUPP_VERSION}/pup_${PUPP_VERSION}_Linux_x86_64.tar.gz" \
-o /tmp/pup.tar.gz \
&& tar xzf /tmp/pup.tar.gz -C /usr/local/bin/ pup \
&& rm -f /tmp/pup.tar.gz
# Verify all installations
RUN echo "=== Tool versions ===" \
&& aws --version \
&& terraform --version \
&& helm version --short \
&& kubectl version --client --output=yaml 2>/dev/null | grep gitVersion \
&& pup --version
# Default command (matching base image behavior)
CMD ["bash"]
Build Script
#!/bin/bash
# Source: Project convention
set -euo pipefail
IMAGE_NAME="ngn-agent"
IMAGE_TAG="latest"
DOCKER_DIR="$(cd "$(dirname "$0")" && pwd)"
echo "==> Building ${IMAGE_NAME}:${IMAGE_TAG}..."
docker build \
-t "${IMAGE_NAME}:${IMAGE_TAG}" \
-f "${DOCKER_DIR}/Dockerfile" \
"${DOCKER_DIR}"
echo "==> Build complete: ${IMAGE_NAME}:${IMAGE_TAG}"
docker images "${IMAGE_NAME}:${IMAGE_TAG}"
Setup Script — Heresd Config Template Generation
# Source: Derived from current config.yaml state [VERIFIED: ~/.hermes/config.yaml]
generate_config_yaml() {
local ssh_key_1="$1"
local ssh_key_2="$2"
local ssh_config="$3"
local ssh_known_hosts="$4"
local repo_ops="$5"
local repo_deploy="$6"
local repo_devtools="$7"
local timezone="$8"
local docker_image="$9"
# Backup existing config
if [ -f ~/.hermes/config.yaml ]; then
cp ~/.hermes/config.yaml ~/.hermes/config.yaml.bak.$(date +%Y%m%d_%H%M%S)
echo " → Backed up existing config.yaml"
fi
# Use hermes config set for simple keys
hermes config set terminal.backend docker
hermes config set terminal.docker_image "${docker_image}"
hermes config set terminal.cwd /workspace
hermes config set terminal.container_memory 5120
hermes config set terminal.container_disk 51200
hermes config set terminal.container_cpu 1
hermes config set terminal.lifetime_seconds 300
hermes config set memory.provider hindsight
hermes config set timezone "${timezone}"
hermes config set telegram.reactions false
hermes config set terminal.docker_env.AWS_REGION us-east-1
# docker_volumes and shell_init_files need sed or Python for array manipulation
# Python is available on macOS — use it for safe YAML modification
python3 -c "
import yaml, sys
path = os.path.expanduser('~/.hermes/config.yaml')
with open(path) as f:
config = yaml.safe_load(f)
config['terminal']['docker_volumes'] = [
'${ssh_key_1}:/root/.ssh/id_ed25519razer:ro',
'${ssh_key_2}:/root/.ssh/id_rsa:ro',
'${ssh_config}:/root/.ssh/config:ro',
'${ssh_known_hosts}:/root/.ssh/known_hosts:ro',
'/Users/bapung/.aws/config:/root/.aws/config:ro',
'/Users/bapung/.aws/sso/cache:/root/.aws/sso/cache:rw',
'${repo_ops}:/workspace/rai-ops:rw',
'${repo_deploy}:/workspace/rai-deployment:rw',
'${repo_devtools}:/workspace/rai-devtools:rw',
os.path.expanduser('~/.hermes/scripts') + ':/usr/local/bin:ro',
]
config['terminal']['docker_forward_env'] = ['JIRA_EMAIL', 'JIRA_API_TOKEN', 'DEFAULT_REPOS']
config['terminal']['shell_init_files'] = ['/usr/local/bin/session-init.sh']
with open(path, 'w') as f:
yaml.dump(config, f, default_flow_style=False)
"
}
State of the Art
| Old Approach | Current Approach | When Changed | Impact |
|---|---|---|---|
| TFA (Terraform) pre-1.0 CLI syntax | Terraform 1.x stable CLI | 2020 | Current terraform 1.15.6 uses stable HCL syntax — no migration issues |
| AWS CLI v1 (Python pip package) | AWS CLI v2 (self-contained installer) | 2020 | v1 was pip install awscli; v2 uses curl→zip→install — Dockerfile must use official installer |
| Helm 2 (Tiller-based) | Helm 3/4 (client-only) | 2019/2025 | Helm 4.2.1 has no Tiller — simpler security model. APT repo install is same as Helm 3 |
| Pup CLI (pre-v1.0) | Pup 1.1.0 stable | 2026-06 | Pup now has stable release with OAuth2 auth; prebuilt binaries available |
Deprecated/outdated:
python3.11-nodejs20base image tag: No longer published by maintainer. Usepython3.11-nodejs22-bookworminstead.- Snap packages in Docker: Snap requires systemd. Don't use
snap install aws-cli --classicin Dockerfile.
Assumptions Log
| # | Claim | Section | Risk if Wrong |
|---|---|---|---|
| A1 | The base image tag python3.11-nodejs20 is no longer available on Docker Hub |
Standard Stack | Build fails with manifest not found — must verify and update tag |
| A2 | Hermes v0.16+ hermes config set supports setting nested paths like terminal.docker_image |
Don't Hand-Roll | May need to use Python/sed instead — minimal impact, fallback exists |
| A3 | Hermes v0.16+ hermes cron create matches Phase 8 documented syntax |
Common Pitfalls | Cron job registration may fail with different syntax |
| A4 | All five skills can be embedded in the setup script | Architecture Patterns | Skills directory structure may have hidden files (metadata files) that aren't SKILL.md |
| A5 | nikolaik/python-nodejs base image has python3 available for YAML manipulation |
Code Examples | Would need to use pip install pyyaml in setup script or use yq instead |
Open Questions (RESOLVED)
-
Is
hermes config setcapable of setting YAML array values (likedocker_volumes)? — RESOLVED: Usehermes config setfor scalar values only. For YAML arrays likedocker_volumes,shell_init_files, anddocker_forward_env, use Python'syamlmodule for manipulation with asedfallback if Python is unavailable. The plan's setup script implements this hybrid approach. -
What is the correct kubectl apt package version string for version pinning? — RESOLVED: Do NOT pin kubectl version — install
kubectlwithout version specifier since it needs to match the target cluster version which may vary across environments. Other tools (terraform, helm, aws-cli, pup) are pinned via version ARGs. -
Should the setup script embed skill/script content as base64 or reference files from the git repo? — RESOLVED: Embed skill and script content directly in the setup script using heredocs (with a snapshot date comment). This makes the script fully self-contained — no git repo dependency at setup time. When the script is regenerated for new versions, the embedded content is updated.
Environment Availability
Skip this section — the phase has no external dependencies that need runtime probing. Docker image build requires Docker (verified:
Docker version 29.4.0, build 9d7ad9f— available). Setup script runs on macOS target with bash built-in,hermesCLI assumed present (v0.16+).
Validation Architecture
Skipped —
workflow.nyquist_validationis explicitlyfalsein.planning/config.json.
Security Domain
The
security_enforcementkey is absent from.planning/config.json(default: enabled).
Applicable ASVS Categories
| ASVS Category | Applies | Standard Control |
|---|---|---|
| V5 Input Validation | yes | Setup script validates secret non-empty before accepting; validates SSH key paths exist |
| V7 Cryptography at Rest | partial | Secrets stored in ~/.hermes/.env (plaintext file at rest). Acceptable for local machine — Hermes itself manages file permissions. |
| V9 Cryptographic Architecture | no | No custom crypto — tools use their own auth mechanisms |
Known Threat Patterns for {Dockerfile + setup script}
| Pattern | STRIDE | Standard Mitigation |
|---|---|---|
| Secret exposure in terminal history | Information Disclosure | Setup script uses read -s (masked input, no echo). Secrets are never echoed to terminal. |
| Config file world-readable permissions | Tampering | Script sets chmod 600 on ~/.hermes/.env after writing |
| Man-in-the-middle on tool download | Tampering | Dockerfile uses HTTPS for all downloads (AWS S3, GitHub, HashiCorp, pkgs.k8s.io, Buildkite). GPG signature verification in apt repos. |
| Accidental build context leak | Information Disclosure | Dockerfile should not COPY . — use COPY docker/ only to avoid leaking .env or other secrets into image layers |
Sources
Primary (HIGH confidence)
- AWS CLI v2 Linux install — verified official curl→unzip→install procedure
- Terraform Linux install — verified HashiCorp apt repo method + version 1.15.6
- Helm install via apt — verified Buildkite apt repo method + version 4.2.1
- kubectl Linux install — verified Google Kubernetes apt repo method
- Kubernetes releases — verified latest stable v1.36.1
- Pup CLI releases — verified latest version 1.1.0
- nikolaik/python-nodejs Docker Hub — verified available tags and versions
- Current ~/.hermes/config.yaml — verified 565-line source of truth for setup script
- Current ~/.hermes/.env — verified 484-line env template
- Current ~/.hermes/hindsight/config.json — verified JSON file content
- Current session-init.sh — verified 37-line script
- Current archive-stale-sessions.sh — verified 41-line script
- Phase 8 cron registration patterns — verified
hermes cron createCLI syntax
Secondary (MEDIUM confidence)
- Hermes research: Extensibility — verified hermes config set capability
- Base image GitHub repo — confirmed tag generation pattern
Tertiary (LOW confidence)
- (none — all claims verified via official sources or current config files)
Metadata
Confidence breakdown:
- Standard stack: HIGH — all tool versions verified from official sources
- Architecture: HIGH — patterns derived from current working configuration
- Pitfalls: HIGH — based on documented deprecations and Phase 8 execution knowledge
- Base image tag availability: MEDIUM — need to confirm
python3.11-nodejs20tag status at build time
Research date: 2026-06-15 Valid until: 2026-07-15 (tool versions may receive patch updates within 30 days; base image tags may change if maintainer drops more versions)