--- id: RAIL-HO-WP-0001 type: workplan title: "Secure Single-Server Bootstrap at HostEurope" domain: railiance repo: railiance-infra status: completed owner: railiance topic_slug: railiance repo_goal_id: 9e835b82-acca-493a-943f-2553ffe0bf54 state_hub_workstream_id: "bf40b47e-be5b-4930-a7d2-362e76b943bb" created: "2026-03-08" updated: "2026-03-09" completed: "2026-03-09" handoff_note: > T01 and T02 (inventory entry and SSH tunnel setup) are prerequisites to run T03 onwards from the HostEurope server itself. The ansible work previously done in railiance-bootstrap (harden.yml, bootstrap.yml) is superseded by the roles/base and roles/sops_agent structure in this repo. The HostEurope server IP is 92.205.62.239 (hosts.ini is gitignored — recreate from inventory/servers.yaml when working on the host). --- # Secure Single-Server Bootstrap at HostEurope ## Goal Provision and converge the HostEurope server so that it becomes a secure, hardened node ready to join the ThreePhoenix Kubernetes cluster. This workplan covers everything from establishing SSH access and tunnel connectivity through a fully converged, verified server — all secrets managed via SOPS/age, all services secured from the start. Scope is deliberately narrow: one server, secure from day one. Automated provisioning of additional server resources and the full three-node setup are deferred. ## Boundary conditions - Ubuntu 24.04 LTS at HostEurope (92.205.62.239), manually provisioned - All remote access key-based only (no password auth) - Firewall active before k3s is installed - Secrets managed via SOPS/age — nothing committed in plaintext - This workplan is **run from the HostEurope server** via SSH, not from WSL ## Remote execution setup This repo is intended to run from the HostEurope server itself. To retain State Hub connectivity during Claude sessions on that server, set up an SSH reverse tunnel from your local machine before connecting: ```bash # On your local machine — forward local state-hub to the remote ssh -R 8000:127.0.0.1:8000 @92.205.62.239 ``` Then Claude on the remote host can reach `http://127.0.0.1:8000` as normal. See also: the tunnel setup note in `CLAUDE.md`. --- ## Tasks ### T01 — Add HostEurope host to inventory ```task id: T01 status: done completed: "2026-03-08" priority: high state_hub_task_id: "9b2222a3-0f9f-4543-9321-e4cd5f87a457" ``` Add the HostEurope host to `inventory/servers.yaml` under a `hosteurope` group. Create `ansible/hosts.ini` from the inventory (gitignored — recreate on each new working machine): ```bash python3 ansible/inventory_from_yaml.py > ansible/hosts.ini # or add manually: [hosteurope]\n92.205.62.239 ansible_user=... ``` Verify connectivity: ```bash ansible -i ansible/hosts.ini hosteurope -m ping ``` **Done when:** ping succeeds from a control node with network access to the host. --- ### T02 — Set up SSH tunnel for State Hub access ```task id: T02 status: done completed: "2026-03-09" priority: high state_hub_task_id: "e4dda416-19bc-4672-b9ba-8ddb1b9e9659" ``` On your local machine, establish a reverse tunnel before SSH-ing to the HostEurope server so the State Hub MCP server is reachable from Claude sessions on that host: ```bash make tunnel # reads host from inventory/servers.yaml ``` Or manually: ```bash ssh -R 8000:127.0.0.1:8000 tegwick@92.205.62.239 ``` Verify the tunnel is working from the remote: ```bash curl http://127.0.0.1:8000/state/health ``` **Done when:** `make tunnel` target implemented; procedure documented. --- ### T03 — Extend base role with security hardening ```task id: T03 status: done completed: "2026-03-08" priority: high state_hub_task_id: "6eda6875-1301-4794-a07e-3e13ff1d92bf" ``` Extend `ansible/roles/base/tasks/main.yml` to cover security hardening that must run before any service installation: - Disable root SSH login (`PermitRootLogin no`) - Disable password authentication (`PasswordAuthentication no`) - Enable and configure UFW: deny all inbound, allow SSH (22), k3s API (6443), Flannel VXLAN (8472/UDP) - Install and enable `fail2ban` with SSH jail - Set `HISTCONTROL=ignorespace` in `/etc/profile.d/` Verify with: ```bash ansible-playbook -i ansible/hosts.ini -l hosteurope ansible/playbooks/bootstrap.yaml --check ``` **Done when:** dry-run produces no errors and hardening tasks are visible in the play recap. --- ### T04 — Run bootstrap on the HostEurope host ```task id: T04 status: done completed: "2026-03-08" priority: high state_hub_task_id: "77921431-3a45-45b2-a0b0-cf0c43262205" ``` Execute the full bootstrap playbook from the HostEurope server (tunnel must be active per T02): ```bash ansible-playbook -i ansible/hosts.ini -l hosteurope ansible/playbooks/bootstrap.yaml ``` **Done when:** - Playbook completes with no failed tasks - UFW is active and SSH still works after the run - SOPS agent role applied successfully --- ### T05 — Smoke test and record ```task id: T05 status: done completed: "2026-03-08" priority: medium state_hub_task_id: "c573c200-bf22-49d1-86f9-dca1fc71743c" ``` Verify the converged state: ```bash # Confirm UFW active ansible -i ansible/hosts.ini hosteurope -m shell -a "ufw status" # Confirm fail2ban running ansible -i ansible/hosts.ini hosteurope -m shell -a "systemctl is-active fail2ban" # Confirm SSH hardening applied ansible -i ansible/hosts.ini hosteurope -m shell -a "sshd -T | grep -E 'permitrootlogin|passwordauthentication'" ``` Add `docs/hosteurope-bootstrap.md` recording: - Server specs (vCPU, RAM, disk) - Public IP (no credentials) - Date bootstrapped - Roles applied Log completion to the State Hub: ``` add_progress_event(summary="HostEurope server bootstrapped and hardened", event_type="milestone", workstream_id="bf40b47e-be5b-4930-a7d2-362e76b943bb") ``` **Done when:** all checks pass and the progress event is logged. --- ## References - Repo goal: `9e835b82-acca-493a-943f-2553ffe0bf54` - Domain goal: `6f96c712-60e6-4ea9-ab06-168878eafbce` (Three-Phoenix Secure Kubernetes Infrastructure) - Previous ansible work: `railiance-bootstrap/ansible/` (harden.yml, bootstrap.yml — superseded by this repo's role structure)