📖 Dependency Management: This project uses a dual workflow for dependency management. See DEPENDENCIES.md for details on installing external roles and collections for Molecule vs. standalone playbook execution.
| Role | Purpose | Used By |
|---|---|---|
bootstrap |
Minimal server setup: user + Docker + SSH keys | Servers (via Tailscale SSH) |
common_cli |
CLI tools, Tailscale, dotfiles, dev environment | Workstations |
common_gui |
GUI apps, fonts, desktop settings | GUI machines |
| Role | Purpose | Used By |
|---|---|---|
dev |
Dev tools: SDKs, IDEs, languages | dev group |
dev_gui |
GUI dev tools: VS Code, Android Studio | dev_gui group |
academic_gui |
Academic tools: LaTeX, Zotero | academic_gui group |
| Role | Purpose | Used By |
|---|---|---|
jasonernst_com |
Personal website (goblog) | www.jasonernst.com |
stalwart |
Stalwart mail server | mail.jasonernst.com |
media_server |
Plex, Sonarr, Radarr, etc. | NAS |
home_assistant |
Home automation | NAS |
dyndns |
Dynamic DNS updater | NAS, workstations |
| Role | Purpose | Used By |
|---|---|---|
rust_game |
Rust game server | NAS |
cs2_game |
Counter-Strike 2 server | NAS |
1. Terraform creates droplet with cloud-init (installs Tailscale)
2. Droplet joins your Tailscale network
3. Run bootstrap via Tailscale SSH:
ansible-playbook -i inventory.yml bootstrap.yml --limit <hostname> -u root
4. Deploy your application
ansible-playbook -i inventory.yml common.yml --limit <hostname> --ask-become-passThe machine running the ansible plays requires ansible >= 3.2.
Note, this doesn't have to be the target machine where you are deploying things to.
If you want to add it to ubuntu, for example, do the following (the ansible included in ubuntu is very old)
sudo add-apt-repository ppa:ansible/ansible && sudo apt update && sudo apt install ansible
The only real requirement on the target machines, is that they have SSH, are reachable and have the authorized the key from the deploying machine.
- Install ssh and import ssh authorized key:
sudo apt install ssh
ssh-import-id gh:compscidr
- Ensure command lines tools are installed:
xcode-select --install - Import ssh authorized key:
pip3 install ssh-import-id - Add the python bin directory to your path, for example:
export PATH="$HOME/Library/Python/3.9/bin:$PATH"- Import the key:
ssh-import-id gh:compscidr - Disable SSH password login (edit /etc/ssh/sshd_config and set
PasswordAuthentication no, setKbdInteractiveAuthentication no) - Turn on SSH access (System Preferences -> Sharing -> Remote Login)
For any plays which deploy secrets / credentials, all of these are managed by 1password. The plays are setup to lookup the secrets using 1password cli (op). In order for this to work, you must login to onepassword in the terminal you are doing the deploying from.
On your first run, before the dotfiles are deployed, sign in manually:
eval $(op signin --account CZG3A4373RA2FC5W5JKFUMYILI) # Personal accountOnce the common.yml playbook has run, you'll have convenient aliases:
op-personal # Sign into personal account ([email protected])
op-work # Sign into work account ([email protected])The session lasts 30 minutes, so you can run multiple playbooks without re-authenticating.
After signing in, you can run any of the example commands below. You'll still need to use
--ask-become-pass to provide your sudo password (typing it once per playbook run is simpler
than dealing with 1Password desktop app prompts for every secret lookup).
Important: Always sign in to 1Password first:
op-personal # Use this for personal infrastructureRun all the roles in the common playbook:
ansible-playbook -i inventory.yml common.yml --ask-become-passRun specific roles by tag in the common playbook:
ansible-playbook -i inventory.yml common.yml --tags sometag --ask-become-passRun all roles in the common playbook on a specific machine:
ansible-playbook -i inventory.yml common.yml --limit ubuntu-beast --ask-become-passRun all roles in the common playbook on a specific machine that requires an ssh password:
ansible-playbook -i inventory.yml common.yml --ask-pass --ask-become-pass --limit nas.localFor Ubuntu 24.04+ headless servers, the playbook automatically:
- Configures systemd-networkd with wildcard interface matching (works with any interface names)
- Disables obsolete isc-dhcp-client (Ubuntu 24.04+ uses systemd-networkd's built-in DHCP)
- Disables systemd-networkd-wait-online to prevent boot delays
- Removes netplan and cloud-init network configurations to prevent conflicts
- Masks [email protected] to avoid conflicts with systemd-networkd-managed WiFi. On systems that use a temporary wlan0 name during boot, this prevents the transient wpa_supplicant unit from interfering after the interface is renamed; on systems where wlan0 is permanent, this simply disables that legacy wpa_supplicant instance.
- Configures WiFi with wpa_supplicant using credentials from 1Password
GUI systems with NetworkManager are automatically detected and skipped.
Molecule tests use OP_SERVICE_ACCOUNT_TOKEN for non-interactive authentication, while normal playbook runs use interactive op signin to avoid desktop app prompts. This allows automated testing while keeping interactive workflows smooth for manual use.
Set your personal 1Password account service account token:
export OP_SERVICE_ACCOUNT_TOKEN="your-personal-account-token"
cd ansible
python -m venv venv
. venv/bin/activate
pip install molecule molecule-docker passlib
molecule test