Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .github/copilot/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ This project provisions Freifunk Südholstein gateways and related services. The

- README.md: deployment, usage, and vault guidance
- setup.yml: main playbook with gateway, service, and monitoring roles
- hosts.yml: inventory for gateways, services, and monitoring hosts
- hosts.yml: inventory for gateways, split service groups, and monitoring hosts
- group_vars/versions.yml: shared software versions
- group_vars/secrets.yml: shared encrypted vars
- host_vars/: host-specific secret and config values
Expand All @@ -26,15 +26,18 @@ This project provisions Freifunk Südholstein gateways and related services. The
## Host groups

- gateways: complete production router deployment
- services: lightweight service-only hosts
- services_map: map service host roles
- services_web: web service host roles
- monitoring: Prometheus/Grafana stack

## Typical workflow

```bash
ansible-playbook --vault-id=fastd_key@prompt setup.yml
ansible-playbook --vault-id=fastd_key@prompt setup.yml --limit gateways
ansible-playbook --vault-id=fastd_key@prompt setup.yml --limit services
ansible-playbook --vault-id=fastd_key@prompt setup.yml --limit "services_map:services_web"
ansible-playbook --vault-id=fastd_key@prompt setup.yml --limit services_map
ansible-playbook --vault-id=fastd_key@prompt setup.yml --limit services_web
ansible-playbook --vault-id=fastd_key@prompt setup.yml --check
ansible-lint
```
Expand Down
23 changes: 19 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Ansible
[![Ansible Lint](https://github.com/ffsh/ansible/actions/workflows/ansible-lint.yml/badge.svg?branch=main)](https://github.com/ffsh/ansible/actions/workflows/ansible-lint.yml)

This repository holds the Ansible playbook to deploy a ffsh gateway in the standard configuration and to provision batman-only service hosts.
This repository holds the Ansible playbook to deploy a ffsh gateway in the standard configuration and to provision dedicated service hosts.
It is based on https://docs.freifunk-suedholstein.de/de/1.5/gateway.html but changes were made where it was plausible.

## requirements
Expand Down Expand Up @@ -48,7 +48,10 @@ That way Ansible will be able to automatically detect the right ssh key and conn
## hosts
If you want to deploy to a new gateway you need to add it to the `hosts.yml` file in the repo, just compare it to brunsbach and add your own details.

If you want to add a batman-only service host, add it to the `services` group in `hosts.yml`. These hosts only receive the `batman` and `batctl` roles.
If you want to add a service host, place it in the dedicated service group in `hosts.yml`:

- `services_map` for the map stack (`batman`, `batctl`, `grafana_public`, `meshviewer`)
- `services_web` for the web stack (`batman`, `batctl`, `website`, `firmware_release`, `firmware_selector`)

The standalone DNS host belongs to the `dns` group. The DNS play only installs
node_exporter and bind_exporter; it does not manage the existing BIND service,
Expand Down Expand Up @@ -129,10 +132,22 @@ Run playbook on all gateways listed in `hosts`:
ansible-playbook --vault-id=fastd_key@prompt setup.yml --limit gateways
```

Run the batman-only service hosts (web-server map-server):
Run both service hosts:

```bash
ansible-playbook --vault-id=fastd_key@prompt setup.yml --limit "services_map:services_web"
```

Run only the map service host:

```bash
ansible-playbook --vault-id=fastd_key@prompt setup.yml --limit services_map
```

Run only the web service host:

```bash
ansible-playbook --vault-id=fastd_key@prompt setup.yml --limit services
ansible-playbook --vault-id=fastd_key@prompt setup.yml --limit services_web
```

Run the monitoring host:
Expand Down
File renamed without changes.
7 changes: 6 additions & 1 deletion hosts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,16 @@ gateways:
vars:
ansible_user: root

services:
services_map:
hosts:
map-ffsh:
ansible_ssh_host: 116.203.148.80
blackbox_http_url: https://map.freifunk-suedholstein.de/
vars:
ansible_user: root

services_web:
hosts:
web-ffsh:
ansible_ssh_host: 116.203.27.68
blackbox_http_url: https://freifunk-suedholstein.de/
Expand Down
4 changes: 0 additions & 4 deletions roles/firmware_release/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
- name: Skip firmware release helper on non-target hosts
ansible.builtin.meta: end_role
when: inventory_hostname != firmware_release_target_host

- name: Install firmware release helper dependencies
ansible.builtin.apt:
name:
Expand Down
4 changes: 0 additions & 4 deletions roles/firmware_selector/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
- name: Skip firmware selector on non-target hosts
ansible.builtin.meta: end_role
when: inventory_hostname != firmware_selector_target_host

- name: Install firmware selector dependency
ansible.builtin.apt:
name: git
Expand Down
14 changes: 14 additions & 0 deletions roles/grafana_public/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
grafana_public_apt_key_url: https://apt.grafana.com/gpg.key
grafana_public_apt_keyring: /etc/apt/keyrings/grafana.gpg
grafana_public_apt_repository: "deb [signed-by={{ grafana_public_apt_keyring }}] https://apt.grafana.com stable main"

grafana_public_domain: grafana.freifunk-suedholstein.de
grafana_public_root_url: "https://{{ grafana_public_domain }}/"
grafana_public_http_addr: 127.0.0.1
grafana_public_http_port: 3000
grafana_public_cert_file: "/etc/letsencrypt/live/{{ grafana_public_domain }}/fullchain.pem"
grafana_public_cert_key: "/etc/letsencrypt/live/{{ grafana_public_domain }}/privkey.pem"

grafana_public_nginx_sites_available_dir: /etc/nginx/sites-available
grafana_public_nginx_sites_enabled_dir: /etc/nginx/sites-enabled
10 changes: 10 additions & 0 deletions roles/grafana_public/handlers/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
- name: Restart Grafana
ansible.builtin.systemd:
name: grafana-server
state: restarted

- name: Reload nginx
ansible.builtin.systemd:
name: nginx
state: reloaded
81 changes: 81 additions & 0 deletions roles/grafana_public/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
- name: Install prerequisites for Grafana repository
ansible.builtin.apt:
name: gnupg
state: present

- name: Create APT keyrings directory
ansible.builtin.file:
path: /etc/apt/keyrings
state: directory
owner: root
group: root
mode: "0755"

- name: Download Grafana repository signing key
ansible.builtin.get_url:
url: "{{ grafana_public_apt_key_url }}"
dest: /tmp/grafana-public.gpg.key
mode: "0644"

- name: Convert Grafana repository signing key
ansible.builtin.command:
cmd: "gpg --dearmor --output {{ grafana_public_apt_keyring }} /tmp/grafana-public.gpg.key"
creates: "{{ grafana_public_apt_keyring }}"

- name: Configure Grafana APT repository
ansible.builtin.apt_repository:
repo: "{{ grafana_public_apt_repository }}"
filename: grafana-public
state: present
update_cache: true

- name: Install Grafana
ansible.builtin.apt:
name: grafana
state: present

- name: Ensure Grafana service is enabled and running
ansible.builtin.systemd:
name: grafana-server
enabled: true
state: started

- name: Configure Grafana public instance
ansible.builtin.template:
src: grafana.ini.j2
dest: /etc/grafana/grafana.ini
owner: root
group: grafana
mode: "0640"
notify: Restart Grafana

- name: Ensure nginx vhost directory exists
ansible.builtin.file:
path: "{{ grafana_public_nginx_sites_available_dir }}"
state: directory
owner: root
group: root
mode: "0755"

- name: Configure Grafana nginx vhost
ansible.builtin.template:
src: grafana.conf.j2
dest: "{{ grafana_public_nginx_sites_available_dir }}/{{ grafana_public_domain | replace('.', '-') }}.conf"
owner: root
group: root
mode: "0644"
notify:
- Reload nginx

- name: Enable Grafana nginx vhost
ansible.builtin.file:
src: "{{ grafana_public_nginx_sites_available_dir }}/{{ grafana_public_domain | replace('.', '-') }}.conf"
dest: "{{ grafana_public_nginx_sites_enabled_dir }}/{{ grafana_public_domain | replace('.', '-') }}.conf"
state: link
force: true

- name: Validate nginx configuration
ansible.builtin.command: nginx -t
changed_when: false
notify:
- Reload nginx
34 changes: 34 additions & 0 deletions roles/grafana_public/templates/grafana.conf.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
server {
listen 80;
listen [::]:80;

server_name {{ grafana_public_domain }};

return 301 https://$host$request_uri;
}

server {
listen 443 ssl http2;
listen [::]:443 ssl http2;

server_name {{ grafana_public_domain }};

ssl_certificate {{ grafana_public_cert_file }};
ssl_certificate_key {{ grafana_public_cert_key }};
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;

add_header Strict-Transport-Security "max-age=31536000" always;
add_header Content-Security-Policy "frame-ancestors https://freifunk-suedholstein.de https://*.freifunk-suedholstein.de" always;

location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
22 changes: 22 additions & 0 deletions roles/grafana_public/templates/grafana.ini.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
##################### Grafana Configuration #####################
[paths]
[server]
http_addr = {{ grafana_public_http_addr }}
http_port = {{ grafana_public_http_port }}
domain = {{ grafana_public_domain }}
root_url = {{ grafana_public_root_url }}

[database]
;type = sqlite3
;path = grafana.db

[security]
allow_embedding = true

[users]
allow_sign_up = false

[auth.anonymous]
enabled = true
org_name = Freifunk Südholstein
org_role = Viewer
1 change: 1 addition & 0 deletions roles/meshviewer/defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ meshviewer_site_name: Freifunk Südholstein
meshviewer_max_age: 21
meshviewer_node_zoom: 19

# Grafana is proxied through the meshviewer nginx config to prevent cors issues
meshviewer_grafana:
url: /_grafana
orgId: 1
Expand Down
5 changes: 0 additions & 5 deletions roles/meshviewer/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
---
- name: Skip meshviewer on non-target hosts
ansible.builtin.meta: end_role
when: inventory_hostname != meshviewer_target_host

- name: Install meshviewer dependencies
ansible.builtin.apt:
name:
Expand Down
64 changes: 61 additions & 3 deletions setup.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,67 @@
tags:
- always

- name: Services Deployment
hosts: services
- name: Services Deployment (map)
hosts: services_map
serial: "{{ deploy_serial | default(1) }}"
vars_files:
- group_vars/versions.yml
vars:
users:
- username: root
pre_tasks:
- name: Load shared deployment secrets
ansible.builtin.include_vars:
file: group_vars/secrets.yml
name: vault_secrets
tags:
- always

- name: Abort if target OS is not Ubuntu 24.04
ansible.builtin.assert:
that:
- ansible_facts["distribution"] == "Ubuntu"
- ansible_facts["distribution_version"] == "24.04"
fail_msg: >-
Unsupported target OS: {{ ansible_facts["distribution"] }} {{
ansible_facts["distribution_version"] }}. This playbook supports
Ubuntu 24.04 only.
success_msg: "Target OS check passed: Ubuntu 24.04"
tags:
- always

- name: Update apt cache
ansible.builtin.apt:
update_cache: true
roles:
- {role: batman, tags: "batman-adv"}
- {role: batctl, tags: "batctl"}
- {role: grafana_public, tags: "grafana-public"}
- {role: meshviewer, tags: "meshviewer"}

post_tasks:
- name: Check if reboot is required
ansible.builtin.stat:
path: /var/run/reboot-required
register: system_reboot_required
changed_when: false
tags:
- always

- name: Reboot if required
ansible.builtin.reboot:
msg: Reboot triggered by Ansible because /var/run/reboot-required exists.
reboot_timeout: 900
connect_timeout: 10
post_reboot_delay: 15
when:
- system_reboot_required.stat.exists
- not ansible_check_mode
tags:
- always

- name: Services Deployment (web)
hosts: services_web
serial: "{{ deploy_serial | default(1) }}"
vars_files:
- group_vars/versions.yml
Expand Down Expand Up @@ -104,7 +163,6 @@
- {role: firmware_release, tags: "firmware-release"}
- {role: firmware_selector, tags: "firmware-selector"}
- {role: website, tags: "website"}
- {role: meshviewer, tags: "meshviewer"}

post_tasks:
- name: Check if reboot is required
Expand Down
Loading