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,46 @@
---
# LDAPS certificate for the DC, auto-renewed from OpenBao's internal ACME.
#
# WHY: the first bao-issued LDAPS cert (2026-07-25) was placed by hand and expires
# 2027-07-25 with nothing to renew it. If it lapses, Authelia loses its LDAPS
# backend and every SSO consumer (Gitea, Grafana, OpenBao's own OIDC login) fails
# at once — a year later, with no memory of how it got there.
# --- lego (ACME client). Same pinned release as openbao_acme, deliberately. ---
samba_ad_acme_version: "5.3.1"
samba_ad_acme_checksum: "sha256:b3c71b122ee1947eacfe0b809b955647f6377239fe4bfc49f73b1a091ae1252a"
samba_ad_acme_url: "https://github.com/go-acme/lego/releases/download/v{{ samba_ad_acme_version }}/lego_v{{ samba_ad_acme_version }}_linux_amd64.tar.gz"
samba_ad_acme_bin: "/usr/local/bin/lego"
# --- Paths ---
samba_ad_acme_dir: "/etc/samba/acme" # lego state (account + certs)
samba_ad_acme_tls_dir: "/var/lib/samba/private/tls" # where Samba reads cert/key/ca
# --- Identity ---
samba_ad_acme_domain: "dc1.ad.ddupan.top"
samba_ad_acme_email: "[email protected]"
# OpenBao's ACME directory, pinned to the ROLE-scoped path so issuance is capped by
# the bao-server role (allowed_domains=ad.ddupan.top) rather than sign-verbatim.
samba_ad_acme_server: "https://bao.ad.ddupan.top:8200/v1/pki/roles/bao-server/acme/directory"
# http-01: lego binds this address only while validating, then releases it. Verified nothing
# else listens on :80 on the DC, and bao (192.168.10.8) can reach it.
# NOTE: this yields a cert with a DNS SAN ONLY — no IP SAN, unlike the hand-issued
# one it replaces. Clients MUST connect as dc1.ad.ddupan.top, not 192.168.10.5.
# NOTE: lego v5 calls this --http.address (NOT --http.port, which is a v4-ism
# and fails with "flag provided but not defined"). Mirrors --tls.address.
samba_ad_acme_http_address: ":80"
# OpenBao's ACME caps certificate lifetime (issued cert is ~32 days, NOT the
# role's 1y max_ttl — ACME deliberately issues short-lived certs). A 30-day
# threshold against a 32-day cert would try to renew on almost every run, so keep
# the window well inside the lifetime: renew with ~10 days of headroom.
samba_ad_acme_renew_days: 10
samba_ad_acme_renew_oncalendar: "*-*-* 03:42:00"
# lego defaults to an EC (P-256) key, but the bao-server PKI role pins
# key_type=rsa / key_bits=2048, so an EC CSR is rejected at finalize with
# "badCSR :: refusing to sign CSR: role requires keys of type rsa".
# Match the role rather than loosening it — the role is what caps ACME issuance.
samba_ad_acme_key_type: "rsa2048"
@@ -0,0 +1,2 @@
---
dependencies: []
@@ -0,0 +1,128 @@
---
# Install lego, obtain the DC's LDAPS cert from OpenBao's ACME, deploy it into
# Samba's TLS dir, and enable a renewal timer. Idempotent.
- name: Check installed lego version
ansible.builtin.command: "{{ samba_ad_acme_bin }} --version"
register: lego_installed
changed_when: false
failed_when: false
- name: Install lego when missing or version mismatch
when: samba_ad_acme_version not in (lego_installed.stdout | default(''))
block:
- name: Download lego release tarball (checksum-verified)
ansible.builtin.get_url:
url: "{{ samba_ad_acme_url }}"
dest: "/tmp/lego_{{ samba_ad_acme_version }}.tar.gz"
checksum: "{{ samba_ad_acme_checksum }}"
mode: "0644"
retries: 3
delay: 10
- name: Create lego staging dir
ansible.builtin.file:
path: "/tmp/lego_{{ samba_ad_acme_version }}"
state: directory
mode: "0755"
- name: Extract lego
ansible.builtin.unarchive:
src: "/tmp/lego_{{ samba_ad_acme_version }}.tar.gz"
dest: "/tmp/lego_{{ samba_ad_acme_version }}"
remote_src: true
- name: Install lego binary
ansible.builtin.copy:
src: "/tmp/lego_{{ samba_ad_acme_version }}/lego"
dest: "{{ samba_ad_acme_bin }}"
remote_src: true
owner: root
group: root
mode: "0755"
- name: Create ACME state directory
ansible.builtin.file:
path: "{{ samba_ad_acme_dir }}"
state: directory
owner: root
group: root
mode: "0700"
- name: Install the obtain/renew wrapper and deploy hook
ansible.builtin.template:
src: "{{ item.src }}"
dest: "{{ item.dest }}"
owner: root
group: root
mode: "0755"
loop:
- { src: samba-acme.sh.j2, dest: /usr/local/bin/samba-acme.sh }
- { src: samba-acme-deploy.sh.j2, dest: /usr/local/bin/samba-acme-deploy.sh }
- name: Install the renewal systemd service + timer
ansible.builtin.template:
src: "{{ item.src }}"
dest: "{{ item.dest }}"
owner: root
group: root
mode: "0644"
loop:
- { src: samba-acme.service.j2, dest: /etc/systemd/system/samba-acme.service }
- { src: samba-acme.timer.j2, dest: /etc/systemd/system/samba-acme.timer }
register: acme_units
- name: Reload systemd
ansible.builtin.systemd_service:
daemon_reload: true
when: acme_units is changed
# --- Preflight: the two things that actually make http-01 fail here ------------
- name: Confirm nothing else is bound to port 80
# lego binds :80 for the duration of validation. Anything already holding it
# makes issuance fail with a bind error rather than anything ACME-shaped.
ansible.builtin.shell:
cmd: "ss -ltn '( sport = :80 )' | tail -n +2 | wc -l"
register: port80
changed_when: false
- name: Fail if port 80 is occupied
ansible.builtin.fail:
msg: "Port 80 is in use on {{ inventory_hostname }}; lego's http-01 cannot bind it."
when: port80.stdout | trim | int > 0
- name: Confirm the OpenBao ACME directory is reachable
ansible.builtin.uri:
url: "{{ samba_ad_acme_server }}"
return_content: false
validate_certs: true
register: acme_dir
retries: 3
delay: 10
until: acme_dir is succeeded
changed_when: false
- name: Obtain/renew the certificate now
# Safe to run every time: lego only acts when the cert is missing or within
# --renew-days of expiry, and only then fires the deploy hook.
ansible.builtin.command: /usr/local/bin/samba-acme.sh
register: lego_run
changed_when: "'Server responded with a certificate' in (lego_run.stdout | default('') + lego_run.stderr | default(''))"
- name: Enable and start the renewal timer
ansible.builtin.systemd_service:
name: samba-acme.timer
enabled: true
state: started
- name: Report the live LDAPS certificate
ansible.builtin.shell:
cmd: >-
echo | timeout 10 openssl s_client -connect 127.0.0.1:636 2>/dev/null
| openssl x509 -noout -subject -issuer -dates
register: live_cert
changed_when: false
- name: Show it
ansible.builtin.debug:
msg: "{{ live_cert.stdout_lines }}"
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
# {{ ansible_managed }}
# Install a freshly issued/renewed cert into Samba's TLS dir.
# lego passes the paths in LEGO_CERT_PATH / LEGO_CERT_KEY_PATH.
set -euo pipefail
TLS="{{ samba_ad_acme_tls_dir }}"
CRT="${LEGO_CERT_PATH:-{{ samba_ad_acme_dir }}/certificates/{{ samba_ad_acme_domain }}.crt}"
KEY="${LEGO_CERT_KEY_PATH:-{{ samba_ad_acme_dir }}/certificates/{{ samba_ad_acme_domain }}.key}"
ISS="${CRT%.crt}.issuer.crt"
install -o root -g root -m 0644 "${CRT}" "${TLS}/cert.pem"
install -o root -g root -m 0600 "${KEY}" "${TLS}/key.pem"
[ -s "${ISS}" ] && install -o root -g root -m 0644 "${ISS}" "${TLS}/ca.pem"
# Samba re-reads its TLS credentials PER CONNECTION, so a renewal normally goes
# live with no restart and no LDAPS downtime (observed 2026-07-25). Do not assume
# it though: if the served cert does not match what we just installed, the old one
# is still being handed out and would eventually expire in place. Verify, and only
# restart if we must — that keeps the common path at zero downtime while making
# the failure mode loud instead of silent.
new="$(openssl x509 -noout -fingerprint -sha256 -in "${TLS}/cert.pem" | cut -d= -f2)"
served="$(echo | timeout 10 openssl s_client -connect 127.0.0.1:636 2>/dev/null \
| openssl x509 -noout -fingerprint -sha256 2>/dev/null | cut -d= -f2 || true)"
if [ "${new}" != "${served}" ]; then
echo "served cert != installed cert; restarting samba-ad-dc to load it"
systemctl restart samba-ad-dc
else
echo "samba already serving the new cert; no restart needed"
fi
@@ -0,0 +1,9 @@
# {{ ansible_managed }}
[Unit]
Description=Samba AD DC LDAPS certificate (lego, OpenBao ACME http-01)
After=network-online.target samba-ad-dc.service
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/samba-acme.sh
@@ -0,0 +1,19 @@
#!/usr/bin/env bash
# {{ ansible_managed }}
# Obtain or renew the DC's LDAPS cert from OpenBao's internal ACME (http-01).
# lego's `run` does BOTH: it renews only when due (--renew-days) and fires
# --deploy-hook on any actual create/renew. There is no separate `renew` command
# in lego v5, and every flag must come AFTER `run`.
set -euo pipefail
exec {{ samba_ad_acme_bin }} run \
--accept-tos \
--email "{{ samba_ad_acme_email }}" \
--server "{{ samba_ad_acme_server }}" \
--http \
--http.address "{{ samba_ad_acme_http_address }}" \
--domains "{{ samba_ad_acme_domain }}" \
--key-type "{{ samba_ad_acme_key_type }}" \
--path "{{ samba_ad_acme_dir }}" \
--renew-days {{ samba_ad_acme_renew_days }} \
--deploy-hook /usr/local/bin/samba-acme-deploy.sh
@@ -0,0 +1,11 @@
# {{ ansible_managed }}
[Unit]
Description=Samba AD DC LDAPS certificate renewal timer
[Timer]
OnCalendar={{ samba_ad_acme_renew_oncalendar }}
RandomizedDelaySec=3600
Persistent=true
[Install]
WantedBy=timers.target