Scaffold Ansible deployment structure + Homarr playbook

This commit is contained in:
Samuel James 2026-08-26 08:55:32 -04:00
parent 3ac4720dd1
commit 4c337f6d08
8 changed files with 157 additions and 1 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
*.retry
*.log
.vault_pass
secrets.yml

View File

@ -1,2 +1,35 @@
# semaphore
# semaphore — SnS deployment playbooks
Ansible playbooks run by [Semaphore](http://192.168.122.99:3000) for deploying
services across SnS hosts. Source of truth is this Gitea repo (`sam/semaphore`);
Semaphore pulls from here.
## Layout
```
ansible.cfg # ansible defaults (inventory path, ssh opts)
inventory/hosts.yml # target hosts (racknerd1/2/3), mesh IPs
group_vars/all.yml # shared non-secret vars
playbooks/
ping.yml # connectivity check (run this first)
deploy-homarr.yml # Homarr dashboard on racknerd1
requirements.yml # ansible collections (community.docker)
```
## Semaphore setup (one-time)
1. **Key Store**: add the SSH private key Semaphore uses to reach target hosts.
Its public half must be in each target's `~/.ssh/authorized_keys`.
2. **Repository**: this repo — `git@gitea.snsnetlabs.com:sam/semaphore.git`
(SSH deploy key) or HTTPS + Gitea token.
3. **Inventory**: point at `inventory/hosts.yml`.
4. **Environment/Variable Group**: set `homarr_secret_encryption_key`
= `openssl rand -hex 32` (secret; do NOT commit).
5. **Task Templates**:
- `ping` -> playbook `playbooks/ping.yml`
- `homarr` -> playbook `playbooks/deploy-homarr.yml`, limit `racknerd1`
## Run order
Run `ping` first to confirm Semaphore can reach the target, then `homarr`.
## Notes
- Targets are reached over the NetBird mesh (Semaphore LXC is on the mesh).
- Secrets live in Semaphore's variable store, never in this repo.

10
ansible.cfg Normal file
View File

@ -0,0 +1,10 @@
[defaults]
inventory = inventory/hosts.yml
host_key_checking = False
retry_files_enabled = False
stdout_callback = yaml
interpreter_python = auto_silent
[ssh_connection]
pipelining = True
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o ServerAliveInterval=15

2
group_vars/all.yml Normal file
View File

@ -0,0 +1,2 @@
# Shared vars. Secrets should come from Semaphore's variable/secret store, NOT committed here.
ansible_python_interpreter: /usr/bin/python3

18
inventory/hosts.yml Normal file
View File

@ -0,0 +1,18 @@
# Semaphore deployment inventory
# Hosts reached over the NetBird mesh (Semaphore LXC .99 is on the mesh).
all:
children:
vps:
hosts:
racknerd1:
ansible_host: 100.96.198.46 # NetBird mesh IP (public: 172.245.72.108)
ansible_user: root
racknerd2:
ansible_host: 100.96.217.250
ansible_user: root
racknerd3:
ansible_host: 67.215.255.203
ansible_user: root
dashboards:
hosts:
racknerd1:

View File

@ -0,0 +1,76 @@
---
# Deploy Homarr dashboard via Docker Compose.
# Usage (Semaphore Task Template): playbook = playbooks/deploy-homarr.yml, inventory = inventory/hosts.yml, limit = racknerd1
#
# REQUIRED extra var (set in Semaphore -> Variable Groups / Survey, NOT committed):
# homarr_secret_encryption_key : 64-char hex (openssl rand -hex 32). Homarr v1 refuses to start without it.
- name: Deploy Homarr
hosts: racknerd1
become: true
vars:
homarr_dir: /opt/homarr
homarr_port: 7575
homarr_image: ghcr.io/homarr-labs/homarr:latest
# homarr_secret_encryption_key: "<injected by Semaphore>"
tasks:
- name: Ensure Docker is present
ansible.builtin.command: docker version
register: docker_check
changed_when: false
failed_when: docker_check.rc != 0
- name: Fail early if encryption key not provided
ansible.builtin.assert:
that:
- homarr_secret_encryption_key is defined
- homarr_secret_encryption_key | length == 64
fail_msg: >-
homarr_secret_encryption_key must be a 64-char hex string.
Generate with `openssl rand -hex 32` and set it as a Semaphore variable.
- name: Create Homarr directories
ansible.builtin.file:
path: "{{ item }}"
state: directory
mode: "0755"
loop:
- "{{ homarr_dir }}"
- "{{ homarr_dir }}/appdata"
- name: Write docker-compose.yml
ansible.builtin.copy:
dest: "{{ homarr_dir }}/docker-compose.yml"
mode: "0644"
content: |
services:
homarr:
container_name: homarr
image: {{ homarr_image }}
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- {{ homarr_dir }}/appdata:/appdata
environment:
- SECRET_ENCRYPTION_KEY={{ homarr_secret_encryption_key }}
ports:
- "{{ homarr_port }}:7575"
- name: Pull and start Homarr
community.docker.docker_compose_v2:
project_src: "{{ homarr_dir }}"
pull: always
state: present
register: compose_result
- name: Wait for Homarr to answer
ansible.builtin.uri:
url: "http://127.0.0.1:{{ homarr_port }}/"
status_code: [200, 302, 307]
register: health
retries: 12
delay: 5
until: health.status is defined
- name: Report
ansible.builtin.debug:
msg: "Homarr is up on {{ inventory_hostname }}:{{ homarr_port }} (status {{ health.status | default('n/a') }})"

11
playbooks/ping.yml Normal file
View File

@ -0,0 +1,11 @@
---
# Quick connectivity + facts check for all/limited hosts.
- name: Connectivity check
hosts: all
gather_facts: false
tasks:
- name: Ping over SSH
ansible.builtin.ping:
- name: Show basics
ansible.builtin.raw: "echo $(hostname) reachable, docker=$(command -v docker >/dev/null && echo yes || echo no)"
changed_when: false

2
requirements.yml Normal file
View File

@ -0,0 +1,2 @@
collections:
- name: community.docker