From 0ba0d06d429986e8877990ea52bd3e0395d6c651 Mon Sep 17 00:00:00 2001 From: kipavy Date: Tue, 22 Sep 2026 16:11:16 +0000 Subject: [PATCH] feat(infra): give new hosts a route out, and let the rehearsal read the key from a file This VCN has an internet gateway and no NAT, so a host created without a public address cannot reach GHCR, R2 or apt. assign_public_ip defaults to true and stays per host. rehearse.yml can also take voltius_age_key_file, a path on the controller, so a run does not need a terminal. Under /dev/shm the key never reaches a disk on either machine. Co-Authored-By: Claude Opus 5 --- ansible/rehearse.yml | 22 +++++++++++++++++++++- infra/oci/hosts.tf | 5 ++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/ansible/rehearse.yml b/ansible/rehearse.yml index 53bd08b..10d23d3 100644 --- a/ansible/rehearse.yml +++ b/ansible/rehearse.yml @@ -23,8 +23,10 @@ - name: voltius_age_key prompt: | Paste the age private key (AGE-SECRET-KEY-…), then Enter. + Leave empty if you passed voltius_age_key_file instead. It is written to RAM on the target, used once, and deleted private: true + unsafe: true vars: voltius_rehearsal_volume: voltius-rehearsal-data voltius_rehearsal_container: voltius-rehearsal @@ -44,13 +46,31 @@ that: voltius_rehearse_guard.stdout == "" fail_msg: "{{ inventory_hostname }} has a voltius-server container — rehearse on a throwaway host" + # A non-interactive run passes voltius_age_key_file, a path on the + # controller — ideally under /dev/shm, so the key never reaches a disk. + - name: Read the key from the controller + ansible.builtin.slurp: + src: "{{ voltius_age_key_file }}" + register: voltius_age_key_slurped + delegate_to: localhost + when: voltius_age_key_file | default("") != "" + no_log: true + - name: Put the key in RAM only ansible.builtin.copy: - content: "{{ voltius_age_key }}\n" + content: >- + {{ (voltius_age_key_slurped.content | b64decode) if voltius_age_key_file | default("") != "" + else voltius_age_key ~ "\n" }} dest: /dev/shm/voltius-age.key mode: "0600" no_log: true + - name: The key must not be empty + ansible.builtin.stat: + path: /dev/shm/voltius-age.key + register: voltius_age_key_stat + failed_when: voltius_age_key_stat.stat.size < 60 + - name: Unpack the secrets bundle ansible.builtin.shell: cmd: >- diff --git a/infra/oci/hosts.tf b/infra/oci/hosts.tf index 1cdf56f..fdf6b9f 100644 --- a/infra/oci/hosts.tf +++ b/infra/oci/hosts.tf @@ -12,6 +12,9 @@ variable "hosts" { memory_in_gbs = number boot_volume_size_in_gbs = optional(number, 200) availability_domain = optional(string) + # There is no NAT gateway in this VCN, so a host without one has no route + # out and cannot reach GHCR, R2 or apt. + assign_public_ip = optional(bool, true) })) default = {} description = "Additional hosts to create, keyed by the name used in the Ansible inventory." @@ -59,7 +62,7 @@ resource "oci_core_instance" "host" { create_vnic_details { subnet_id = oci_core_subnet.main.id - assign_public_ip = false + assign_public_ip = each.value.assign_public_ip display_name = each.key }