From 4c337f6d08850d4fbeb8420e36852b6d24eec6a3 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 26 Aug 2026 08:55:32 -0400 Subject: [PATCH] Scaffold Ansible deployment structure + Homarr playbook --- .gitignore | 4 ++ README.md | 35 ++++++++++++++++- ansible.cfg | 10 +++++ group_vars/all.yml | 2 + inventory/hosts.yml | 18 +++++++++ playbooks/deploy-homarr.yml | 76 +++++++++++++++++++++++++++++++++++++ playbooks/ping.yml | 11 ++++++ requirements.yml | 2 + 8 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 ansible.cfg create mode 100644 group_vars/all.yml create mode 100644 inventory/hosts.yml create mode 100644 playbooks/deploy-homarr.yml create mode 100644 playbooks/ping.yml create mode 100644 requirements.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..015395b --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.retry +*.log +.vault_pass +secrets.yml diff --git a/README.md b/README.md index 8c393cf..7fffc0d 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/ansible.cfg b/ansible.cfg new file mode 100644 index 0000000..d0888fc --- /dev/null +++ b/ansible.cfg @@ -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 diff --git a/group_vars/all.yml b/group_vars/all.yml new file mode 100644 index 0000000..f887dcf --- /dev/null +++ b/group_vars/all.yml @@ -0,0 +1,2 @@ +# Shared vars. Secrets should come from Semaphore's variable/secret store, NOT committed here. +ansible_python_interpreter: /usr/bin/python3 diff --git a/inventory/hosts.yml b/inventory/hosts.yml new file mode 100644 index 0000000..9664a2a --- /dev/null +++ b/inventory/hosts.yml @@ -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: diff --git a/playbooks/deploy-homarr.yml b/playbooks/deploy-homarr.yml new file mode 100644 index 0000000..d1e2738 --- /dev/null +++ b/playbooks/deploy-homarr.yml @@ -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: "" + 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') }})" diff --git a/playbooks/ping.yml b/playbooks/ping.yml new file mode 100644 index 0000000..81fb13c --- /dev/null +++ b/playbooks/ping.yml @@ -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 diff --git a/requirements.yml b/requirements.yml new file mode 100644 index 0000000..bfa7410 --- /dev/null +++ b/requirements.yml @@ -0,0 +1,2 @@ +collections: + - name: community.docker