4.9 KiB
Ansible control project
This directory contains the reproducible control project for ghostnet-ki.
The read-only playbooks/preflight.yml verifies the documented host identity
and hardware. The first writing milestone, playbooks/host.yml, applies a
small shared host baseline through the dedicated deployment account.
Control-node setup
Use Python 3.12 through 3.14 and create a virtual environment at the repository root:
python3 -m venv .venv
.venv/bin/python -m pip install --upgrade pip
.venv/bin/python -m pip install -r ansible/requirements-control.txt
Load the private key for the read-only ghost-agent account into an SSH agent.
Do not store private keys or passphrases in this repository:
ssh-add /path/to/ghost-agent-private-key
The workstation must already trust the verified SSH host key for 10.0.0.90.
An unexpected host-key change must be checked through a trusted console before
continuing.
Read-only preflight
Run the commands from the ansible/ directory:
cd ansible
../.venv/bin/ansible-inventory --graph
../.venv/bin/ansible-playbook playbooks/preflight.yml --syntax-check
../.venv/bin/ansible-playbook playbooks/preflight.yml
The final recap must report changed=0 and failed=0. The playbook verifies:
- hostname
ghostnet-ki; - Debian major version 13;
- default IPv4 address
10.0.0.90; - remote Python availability; and
- two AMD Navi 21 PCI devices with ID
1002:73bf.
The inventory deliberately contains no private-key path. Each operator supplies
their authorized key through ssh-agent or a local Ansible override.
One-time deployment-account bootstrap
Status 2026-09-19: The initial bootstrap has been applied to ghostnet-ki, and
the separate verification playbook confirmed SSH access and non-interactive
effective UID 0 with changed=0 and failed=0. Run the bootstrap again only
for an intentional repair, rotation or idempotence check.
The bootstrap creates the dedicated ansible-deploy account. Its password is
locked, its committed public key is restricted to the LAN and WireGuard source
networks, and /etc/sudoers.d/90-ansible-deploy is validated with visudo
before installation. The account receives NOPASSWD root escalation because
the planned host, package, systemd and container management cannot be expressed
as a small stable command allowlist.
The matching private key stays outside this repository. On the workstation on which the initial key was created, it is stored as:
~/.ssh/id_ed25519_ghostnet_ai_ansible_deploy
The automation key has no interactive passphrase so a future runner can use it
non-interactively. It is protected by filesystem mode 0600, SSH source-network
restrictions and the dedicated account. Compromise of this private key would
nevertheless grant root-equivalent deployment access; rotate it when moving the
deployment identity to the runner or after any suspected disclosure.
Load the existing personal administrator key into an SSH agent, then run the
bootstrap interactively from the ansible/ directory. Ansible will ask for the
personal administrator's existing sudo password:
ssh-add /path/to/personal-ghost-private-key
../.venv/bin/ansible-playbook \
--inventory inventories/bootstrap/hosts.yml \
playbooks/bootstrap-deploy-account.yml \
--ask-become-pass
Do not send the password through chat, store it in Git, or place it in a shell history entry. The bootstrap is idempotent; a second run should report no changes.
After a successful bootstrap, verify the new account independently:
../.venv/bin/ansible-playbook \
playbooks/verify-deploy-access.yml \
--extra-vars ansible_user=ansible-deploy \
--private-key ~/.ssh/id_ed25519_ghostnet_ai_ansible_deploy
The verification must report changed=0 and failed=0. It confirms both the
unprivileged SSH identity and non-interactive escalation to effective UID 0.
Shared host baseline
The host baseline is common to Production and Testing. It verifies the target identity and at least 100 GiB free on the root filesystem, then manages these root-owned directory roots:
/etc/ghostnet-ai/{production,testing}
/var/lib/ghostnet-ai/{production,testing,shared}
It does not install software or start services. Use the dedicated host inventory and supply the private deployment key outside the repository:
../.venv/bin/ansible-playbook \
--inventory inventories/host/hosts.yml \
playbooks/host.yml \
--syntax-check
../.venv/bin/ansible-playbook \
--inventory inventories/host/hosts.yml \
playbooks/host.yml \
--check --diff \
--private-key ~/.ssh/id_ed25519_ghostnet_ai_ansible_deploy
../.venv/bin/ansible-playbook \
--inventory inventories/host/hosts.yml \
playbooks/host.yml \
--private-key ~/.ssh/id_ed25519_ghostnet_ai_ansible_deploy
Run the final command a second time. The second recap must report changed=0
and failed=0; otherwise the role has not demonstrated convergence.