Establish clean homelab infrastructure baseline
lint / yaml (push) Has been cancelled
lint / ansible (push) Has been cancelled
lint / terraform (push) Has been cancelled

Reorganize the brownfield repository, remove retired and generated artifacts, harden ignore rules, and record the GitOps/IaC redesign.
This commit is contained in:
2026-09-09 16:47:20 +00:00
commit 88a02ababa
418 changed files with 50579 additions and 0 deletions
@@ -0,0 +1,41 @@
---
# openbao_acme role defaults. Cloudflare token comes from group_vars/all/vault.yml.
# --- lego (ACME client) release ---
openbao_acme_version: "5.3.1"
openbao_acme_checksum: "sha256:b3c71b122ee1947eacfe0b809b955647f6377239fe4bfc49f73b1a091ae1252a"
openbao_acme_url: "https://github.com/go-acme/lego/releases/download/v{{ openbao_acme_version }}/lego_v{{ openbao_acme_version }}_linux_amd64.tar.gz"
openbao_acme_bin: "/usr/local/bin/lego"
# --- Paths / identity ---
openbao_acme_dir: "/etc/openbao/acme" # lego state (account, certs, cloudflare.env)
openbao_acme_tls_dir: "/etc/openbao/tls" # where bao's listener reads cert.pem/key.pem
openbao_user: "openbao"
openbao_group: "openbao"
# --- Certificate ---
openbao_acme_domain: "{{ openbao_fqdn }}" # bao.ad.ddupan.top (from group_vars/all)
openbao_acme_email: "[email protected]" # ACME account / expiry-notice email
# Cloudflare API token with Zone:DNS:Edit on ddupan.top. Set vault_openbao_cf_dns_token
# in group_vars/all/vault.yml (you can copy the value from the cloudflared tunnel's
# terraform.tfvars, which is scoped the same).
openbao_acme_cf_token: "{{ vault_openbao_cf_dns_token | default('') }}"
# Empty = Let's Encrypt production. To dry-run without burning rate limits, set:
# https://acme-staging-v02.api.letsencrypt.org/directory
openbao_acme_server: ""
# Resolvers for lego's zone/apex detection. Must give the PUBLIC view: both the DC AND
# the LAN gateway forward ad.ddupan.top to the DC (split-horizon) → they'd resolve the
# zone to the non-existent CF zone "ad.ddupan.top". Only real public resolvers see that
# ad.ddupan.top isn't delegated and return the ddupan.top apex. List several so a flaky
# WAN query to one falls through to another.
openbao_acme_dns_resolvers: "1.1.1.1:53,1.0.0.1:53,8.8.8.8:53,9.9.9.9:53"
openbao_acme_dns_timeout: 30 # per-query DNS timeout (s); default 10 is tight over a flaky WAN
# Skip the 2-min propagation polling (many WAN DNS queries); just wait, then ask LE to
# validate (LE queries public DNS itself, independent of this host's WAN).
openbao_acme_propagation_wait: "120s"
# Renewal timer (lego only renews within --days of expiry).
openbao_acme_renew_oncalendar: "*-*-* 03:17:00"
@@ -0,0 +1,11 @@
---
galaxy_info:
role_name: openbao_acme
description: Publicly-trusted Let's Encrypt cert for bao's listener via lego + Cloudflare DNS-01, with SIGHUP reload and auto-renewal.
min_ansible_version: "2.15"
platforms:
- name: Debian
versions: [bookworm]
- name: Ubuntu
versions: [jammy, noble]
dependencies: []
@@ -0,0 +1,105 @@
---
# Install lego, obtain the initial cert (DNS-01 via Cloudflare), deploy it to bao's
# listener + reload, and enable a renewal timer. Idempotent.
- name: Assert a Cloudflare DNS token is available
ansible.builtin.assert:
that:
- openbao_acme_cf_token | length > 0
fail_msg: >-
Set vault_openbao_cf_dns_token in group_vars/all/vault.yml — a Cloudflare API
token with Zone:DNS:Edit on ddupan.top.
# --- Install lego (checksum-verified) -----------------------------------------
- name: Check installed lego version
ansible.builtin.command: "{{ openbao_acme_bin }} --version"
register: lego_installed
changed_when: false
failed_when: false
- name: Install lego when missing or version mismatch
when: openbao_acme_version not in (lego_installed.stdout | default(''))
block:
- name: Download lego release tarball (checksum-verified)
ansible.builtin.get_url:
url: "{{ openbao_acme_url }}"
dest: "/tmp/lego_{{ openbao_acme_version }}.tar.gz"
checksum: "{{ openbao_acme_checksum }}"
mode: "0644"
- name: Create lego staging dir
ansible.builtin.file:
path: "/tmp/lego_{{ openbao_acme_version }}"
state: directory
mode: "0755"
- name: Extract lego
ansible.builtin.unarchive:
src: "/tmp/lego_{{ openbao_acme_version }}.tar.gz"
dest: "/tmp/lego_{{ openbao_acme_version }}"
remote_src: true
- name: Install lego binary
ansible.builtin.copy:
src: "/tmp/lego_{{ openbao_acme_version }}/lego"
dest: "{{ openbao_acme_bin }}"
remote_src: true
owner: root
group: root
mode: "0755"
# --- State dir + credentials + scripts ----------------------------------------
- name: Create ACME state directory
ansible.builtin.file:
path: "{{ openbao_acme_dir }}"
state: directory
owner: root
group: root
mode: "0700"
- name: Write Cloudflare credentials env file
ansible.builtin.copy:
content: "CLOUDFLARE_DNS_API_TOKEN={{ openbao_acme_cf_token }}\n"
dest: "{{ openbao_acme_dir }}/cloudflare.env"
owner: root
group: root
mode: "0600"
no_log: true
- name: Install the obtain/renew wrapper and deploy hook
ansible.builtin.template:
src: "{{ item }}.j2"
dest: "/usr/local/bin/{{ item }}"
owner: root
group: root
mode: "0755"
loop:
- openbao-acme.sh
- openbao-acme-deploy.sh
- name: Install the ACME systemd service + timer
ansible.builtin.template:
src: "{{ item }}.j2"
dest: "/etc/systemd/system/{{ item }}"
owner: root
group: root
mode: "0644"
loop:
- openbao-acme.service
- openbao-acme.timer
# --- Obtain the first cert (deploys to bao + reloads via SIGHUP) ---------------
- name: Obtain the initial certificate and deploy it
ansible.builtin.command: /usr/local/bin/openbao-acme.sh
environment:
CLOUDFLARE_DNS_API_TOKEN: "{{ openbao_acme_cf_token }}"
args:
creates: "{{ openbao_acme_dir }}/certificates/{{ openbao_acme_domain }}.crt"
no_log: true
- name: Enable and start the renewal timer
ansible.builtin.systemd:
name: openbao-acme.timer
state: started
enabled: true
daemon_reload: true
@@ -0,0 +1,14 @@
#!/usr/bin/env bash
# {{ ansible_managed }}
# Install the freshly issued/renewed cert into bao's listener and reload (SIGHUP —
# no restart, no reseal). Invoked by lego's --deploy-hook on create/renew; lego passes
# the cert paths in LEGO_CERT_PATH / LEGO_CERT_KEY_PATH.
set -euo pipefail
CRT="${LEGO_CERT_PATH:-{{ openbao_acme_dir }}/certificates/{{ openbao_acme_domain }}.crt}"
KEY="${LEGO_CERT_KEY_PATH:-{{ openbao_acme_dir }}/certificates/{{ openbao_acme_domain }}.key}"
install -o {{ openbao_user }} -g {{ openbao_group }} -m 0644 "${CRT}" "{{ openbao_acme_tls_dir }}/cert.pem"
install -o {{ openbao_user }} -g {{ openbao_group }} -m 0640 "${KEY}" "{{ openbao_acme_tls_dir }}/key.pem"
systemctl reload openbao
@@ -0,0 +1,10 @@
# {{ ansible_managed }}
[Unit]
Description=OpenBao ACME certificate (lego, Cloudflare DNS-01)
After=network-online.target openbao.service
Wants=network-online.target
[Service]
Type=oneshot
EnvironmentFile={{ openbao_acme_dir }}/cloudflare.env
ExecStart=/usr/local/bin/openbao-acme.sh
@@ -0,0 +1,20 @@
#!/usr/bin/env bash
# {{ ansible_managed }}
# Obtain or renew bao's Let's Encrypt cert via Cloudflare DNS-01. lego's `run` does both:
# it renews only when due (ARI + --renew-days) and fires --deploy-hook on any actual
# create/renew to install the cert and reload bao. CLOUDFLARE_DNS_API_TOKEN comes from
# the environment (systemd EnvironmentFile, or Ansible on the first run).
set -euo pipefail
exec {{ openbao_acme_bin }} run \
--accept-tos \
--email "{{ openbao_acme_email }}" \
--dns cloudflare \
--dns.resolvers "{{ openbao_acme_dns_resolvers }}" \
--dns.timeout {{ openbao_acme_dns_timeout }} \
--dns.propagation.wait "{{ openbao_acme_propagation_wait }}" \
--domains "{{ openbao_acme_domain }}" \
--path "{{ openbao_acme_dir }}" \
--renew-days 30 \
--deploy-hook /usr/local/bin/openbao-acme-deploy.sh{% if openbao_acme_server %} \
--server "{{ openbao_acme_server }}"{% endif %}
@@ -0,0 +1,11 @@
# {{ ansible_managed }}
[Unit]
Description=OpenBao ACME renewal timer
[Timer]
OnCalendar={{ openbao_acme_renew_oncalendar }}
RandomizedDelaySec=3600
Persistent=true
[Install]
WantedBy=timers.target