diff --git a/.forgejo/workflows/ansible-ci.yml b/.forgejo/workflows/ansible-ci.yml
new file mode 100644
index 0000000..af2bd69
--- /dev/null
+++ b/.forgejo/workflows/ansible-ci.yml
@@ -0,0 +1,38 @@
+---
+on:
+ - push
+
+jobs:
+ ansible-docsmith:
+ runs-on: node-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Install ansible-docsmith
+ run: |
+ apt-get update
+ apt-get install -y python3-pip
+ pip install ansible-docsmith --break-system-packages
+
+ - name: Run ansible-docsmith
+ run: |
+ for role_dir in $(ls --color=none roles); do
+ ansible-docsmith generate "roles/${role_dir}"
+ done
+
+ - name: Verify changes
+ run: |
+ set -e
+ git diff > diff.txt
+ CHANGES=$(wc -l diff.txt | awk '{ print $1 }')
+ echo "Number of changes: ${CHANGES}"
+ if [ ${CHANGES} -gt 0 ] ; then
+ echo ""
+ cat diff.txt
+ echo ""
+ echo "Fix with the following command:"
+ echo ""
+ echo "run ansible-docsmith generate ${role_dir}"
+ exit 1
+ fi
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f646311
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+venv
+TODO.txt
diff --git a/README.md b/README.md
index 450baf0..ca9e30a 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,40 @@
-# ansible
+# jriou.general
-My Ansible collection
\ No newline at end of file
+My Ansible collection.
+
+## Releases
+
+See [Releases](https://git.riou.xyz/jriou/ansible/releases) for the available
+versions.
+
+## Installation
+
+File `requirements.yml`:
+
+```yaml
+collections:
+ - name: https://git.riou.xyz/jriou/ansible.git
+ type: git
+ version: 1.0.0
+```
+
+Install with ansible-galaxy:
+
+```
+ansible-galaxy collection install -r requirements.yml
+```
+
+## Roles
+
+See the [roles](roles) directory for the complete list of roles and their
+documentation.
+
+## Usage
+
+```yaml
+- hosts: all
+ roles:
+ - jriou.general.role_name
+```
+
+Replace `role_name` with one of the available roles.
diff --git a/galaxy.yml b/galaxy.yml
new file mode 100644
index 0000000..497d0fe
--- /dev/null
+++ b/galaxy.yml
@@ -0,0 +1,29 @@
+---
+namespace: jriou
+name: general
+version: 1.0.0
+readme: README.md
+authors:
+ - "Julien Riou (https://git.riou.xyz/jriou)"
+description: "My Ansible Collection"
+license_file: LICENSE
+tags:
+ - infrastructure
+ - certbot
+ - coller
+ - firefly
+ - forgejo
+ - forgejo_runners
+ - galene
+ - golang
+ - navidrome
+repository: "https://git.riou.xyz/jriou/ansible"
+issues: "https://git.riou.xyz/jriou/ansible/issues"
+build_ignore:
+ - .github
+ - .gitignore
+ - changelogs
+ - roles/*/molecule
+ - '*.tar.gz'
+ - test-requirements.txt
+ - test-requirements.yml
diff --git a/roles/certbot/README.md b/roles/certbot/README.md
new file mode 100644
index 0000000..6533f36
--- /dev/null
+++ b/roles/certbot/README.md
@@ -0,0 +1,77 @@
+# Ansible Role Certbot
+
+## Table of content
+
+
+* [Role variables](#variables)
+ * [`certbot_email`](#variable-certbot_email)
+ * [`certbot_domain`](#variable-certbot_domain)
+ * [`certbot_molecule`](#variable-certbot_molecule)
+
+
+
+
+## Role variables
+
+The following variables can be configured for this role:
+
+| Variable | Type | Required | Default | Description (abstract) |
+|----------|------|----------|---------|------------------------|
+| `certbot_email` | `str` | Yes | N/A | E-mail to register the certificate. |
+| `certbot_domain` | `str` | Yes | N/A | Domain name to register the certificate. |
+| `certbot_molecule` | `bool` | No | `false` | Run the role with Ansible Molecule.
Disable cert generation in the CI. |
+
+### `certbot_email`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+E-mail to register the certificate.
+
+- **Type**: `str`
+- **Required**: Yes
+
+
+
+### `certbot_domain`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Domain name to register the certificate.
+
+- **Type**: `str`
+- **Required**: Yes
+
+
+
+### `certbot_molecule`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Run the role with Ansible Molecule.
+
+Disable cert generation in the CI.
+
+- **Type**: `bool`
+- **Required**: No
+- **Default**: `false`
+
+
+
+
+
+
+## Usage
+
+Playbook example:
+
+```yaml
+- hosts: all
+ roles:
+ - jriou.general.certbot
+```
+
+Then run the playbook:
+
+```
+ansible-playbook play.yml
+```
diff --git a/roles/certbot/defaults/main.yml b/roles/certbot/defaults/main.yml
new file mode 100644
index 0000000..e795850
--- /dev/null
+++ b/roles/certbot/defaults/main.yml
@@ -0,0 +1,10 @@
+---
+
+# Run the role with Ansible Molecule.
+#
+# Disable cert generation in the CI.
+#
+# - Type: bool
+# - Required: No
+# - Default: false
+certbot_molecule: false
diff --git a/roles/certbot/meta/argument_specs.yml b/roles/certbot/meta/argument_specs.yml
new file mode 100644
index 0000000..17df03e
--- /dev/null
+++ b/roles/certbot/meta/argument_specs.yml
@@ -0,0 +1,25 @@
+---
+argument_specs:
+ main:
+ short_description: Install and configure a certbot
+ description:
+ - Install and configure a [certbot](https://certbot.eff.org/).
+ author:
+ - jriou
+ options:
+ certbot_email:
+ description:
+ - E-mail to register the certificate.
+ required: true
+
+ certbot_domain:
+ description:
+ - Domain name to register the certificate.
+ required: true
+
+ certbot_molecule:
+ description:
+ - Run the role with Ansible Molecule.
+ - Disable cert generation in the CI.
+ type: bool
+ default: false
diff --git a/roles/certbot/molecule/default/converge.yml b/roles/certbot/molecule/default/converge.yml
new file mode 100644
index 0000000..8f529c8
--- /dev/null
+++ b/roles/certbot/molecule/default/converge.yml
@@ -0,0 +1,9 @@
+---
+- name: Converge
+ hosts: molecule
+ roles:
+ - certbot
+ vars:
+ certbot_domain: test.org
+ certbot_email: test@test.org
+ certbot_molecule: true
diff --git a/roles/certbot/molecule/default/create.yml b/roles/certbot/molecule/default/create.yml
new file mode 100644
index 0000000..6049019
--- /dev/null
+++ b/roles/certbot/molecule/default/create.yml
@@ -0,0 +1,18 @@
+---
+- name: Create containers
+ hosts: localhost
+ gather_facts: false
+ tasks:
+ - name: Create containers
+ containers.podman.podman_container:
+ hostname: "{{ item }}"
+ name: "{{ item }}"
+ image: "{{ hostvars[item]['container_image'] }}"
+ state: started
+ loop: "{{ groups['molecule'] }}"
+
+ - name: Wait for containers to be ready
+ ansible.builtin.wait_for_connection:
+ timeout: 300
+ delegate_to: "{{ item }}"
+ loop: "{{ groups['molecule'] }}"
diff --git a/roles/certbot/molecule/default/destroy.yml b/roles/certbot/molecule/default/destroy.yml
new file mode 100644
index 0000000..b0596be
--- /dev/null
+++ b/roles/certbot/molecule/default/destroy.yml
@@ -0,0 +1,11 @@
+---
+- name: Destroy container instances
+ hosts: localhost
+ gather_facts: false
+ tasks:
+ - name: Remove containers
+ containers.podman.podman_container:
+ name: "{{ item }}"
+ state: absent
+ loop: "{{ groups['molecule'] }}"
+ failed_when: false
diff --git a/roles/certbot/molecule/default/inventory/hosts.yml b/roles/certbot/molecule/default/inventory/hosts.yml
new file mode 100644
index 0000000..f107edb
--- /dev/null
+++ b/roles/certbot/molecule/default/inventory/hosts.yml
@@ -0,0 +1,12 @@
+---
+molecule:
+ hosts:
+ debian11:
+ ansible_connection: containers.podman.podman
+ container_image: docker.io/geerlingguy/docker-debian11-ansible:latest
+ debian12:
+ ansible_connection: containers.podman.podman
+ container_image: docker.io/geerlingguy/docker-debian12-ansible:latest
+ debian13:
+ ansible_connection: containers.podman.podman
+ container_image: docker.io/geerlingguy/docker-debian13-ansible:latest
diff --git a/roles/certbot/molecule/default/molecule.yml b/roles/certbot/molecule/default/molecule.yml
new file mode 100644
index 0000000..b4d7fb1
--- /dev/null
+++ b/roles/certbot/molecule/default/molecule.yml
@@ -0,0 +1,24 @@
+---
+ansible:
+ executor:
+ args:
+ ansible_playbook:
+ - --inventory=inventory/
+ env:
+ ANSIBLE_ROLES_PATH: ../../../../roles
+ playbooks:
+ create: create.yml
+ destroy: destroy.yml
+ converge: converge.yml
+
+dependency:
+ name: galaxy
+ options:
+ requirements-file: ${MOLECULE_SCENARIO_DIRECTORY}/requirements.yml
+
+scenario:
+ test_sequence:
+ - create
+ - converge
+ - idempotence
+ - destroy
diff --git a/roles/certbot/molecule/default/requirements.yml b/roles/certbot/molecule/default/requirements.yml
new file mode 100644
index 0000000..25a97a4
--- /dev/null
+++ b/roles/certbot/molecule/default/requirements.yml
@@ -0,0 +1,3 @@
+---
+collections:
+ - name: containers.podman
diff --git a/roles/certbot/tasks/main.yml b/roles/certbot/tasks/main.yml
new file mode 100644
index 0000000..bfea560
--- /dev/null
+++ b/roles/certbot/tasks/main.yml
@@ -0,0 +1,13 @@
+---
+- name: Install packages
+ ansible.builtin.apt:
+ name: certbot
+ update_cache: true
+
+- name: Request certificate
+ ansible.builtin.command:
+ cmd: >-
+ certbot certonly --standalone -n --agree-tos
+ --email {{ certbot_email }} -d {{ certbot_domain }}
+ creates: /etc/letsencrypt/live/{{ certbot_domain }}/fullchain.pem
+ when: not certbot_molecule
diff --git a/roles/coller/README.md b/roles/coller/README.md
new file mode 100644
index 0000000..a825dcf
--- /dev/null
+++ b/roles/coller/README.md
@@ -0,0 +1,159 @@
+# Ansible Role Coller
+
+Ansible role to manage a [coller](https://git.riou.xyz/jriou/coller) instance.
+
+## Configuration
+
+See [Variable
+precedence](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_variables.html#ansible-variable-precedence)
+to find where you should put your own variables.
+
+Then define at least `coller_db_password` with a strong and secure password,
+encrypted using
+[ansible-vault](https://docs.ansible.com/ansible/latest/cli/ansible-vault.html).
+
+## Table of content
+
+
+* [Role variables](#variables)
+ * [`coller_version`](#variable-coller_version)
+ * [`coller_config_dir`](#variable-coller_config_dir)
+ * [`coller_port`](#variable-coller_port)
+ * [`coller_manage_iptables`](#variable-coller_manage_iptables)
+ * [`coller_allowed_sources`](#variable-coller_allowed_sources)
+ * [`coller_db_name`](#variable-coller_db_name)
+ * [`coller_db_user`](#variable-coller_db_user)
+ * [`coller_db_password`](#variable-coller_db_password)
+
+
+
+
+## Role variables
+
+The following variables can be configured for this role:
+
+| Variable | Type | Required | Default | Description (abstract) |
+|----------|------|----------|---------|------------------------|
+| `coller_version` | `str` | No | `"1.3.1"` | Version of the binary. |
+| `coller_config_dir` | `path` | No | `"/etc/coller"` | Directory of the configuration files. |
+| `coller_port` | `int` | No | `8080` | Port to listen. |
+| `coller_manage_iptables` | `bool` | No | `false` | Create iptables rule to allow the service. |
+| `coller_allowed_sources` | `list` | No | N/A | List of allowed networks to allow.
Enabled when `coller_manage_iptables` is enabled. |
+| `coller_db_name` | `str` | No | `"coller"` | Name of the database to connect. |
+| `coller_db_user` | `str` | No | `"coller"` | User to connect to the database. |
+| `coller_db_password` | `str` | Yes | N/A | Password to connect to the database. |
+
+### `coller_version`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Version of the binary.
+
+- **Type**: `str`
+- **Required**: No
+- **Default**: `"1.3.1"`
+
+
+
+### `coller_config_dir`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Directory of the configuration files.
+
+- **Type**: `path`
+- **Required**: No
+- **Default**: `"/etc/coller"`
+
+
+
+### `coller_port`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Port to listen.
+
+- **Type**: `int`
+- **Required**: No
+- **Default**: `8080`
+
+
+
+### `coller_manage_iptables`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Create iptables rule to allow the service.
+
+- **Type**: `bool`
+- **Required**: No
+- **Default**: `false`
+
+
+
+### `coller_allowed_sources`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+List of allowed networks to allow.
+
+Enabled when `coller_manage_iptables` is enabled.
+
+- **Type**: `list`
+- **Required**: No
+
+
+
+### `coller_db_name`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Name of the database to connect.
+
+- **Type**: `str`
+- **Required**: No
+- **Default**: `"coller"`
+
+
+
+### `coller_db_user`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+User to connect to the database.
+
+- **Type**: `str`
+- **Required**: No
+- **Default**: `"coller"`
+
+
+
+### `coller_db_password`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Password to connect to the database.
+
+- **Type**: `str`
+- **Required**: Yes
+
+
+
+
+
+
+## Usage
+
+Playbook example:
+
+```yaml
+- hosts: all
+ roles:
+ - jriou.general.coller
+```
+
+Then run the playbook:
+
+```
+ansible-playbook play.yml
+```
diff --git a/roles/coller/defaults/main.yml b/roles/coller/defaults/main.yml
new file mode 100644
index 0000000..638ab7c
--- /dev/null
+++ b/roles/coller/defaults/main.yml
@@ -0,0 +1,51 @@
+---
+
+# Version of the binary.
+#
+# - Type: str
+# - Required: No
+# - Default: 1.3.1
+coller_version: 1.3.1
+
+# Directory of the configuration files.
+#
+# - Type: path
+# - Required: No
+# - Default: /etc/coller
+coller_config_dir: /etc/coller
+
+# Port to listen.
+#
+# - Type: int
+# - Required: No
+# - Default: 8080
+coller_port: 8080
+
+# Create iptables rule to allow the service.
+#
+# - Type: bool
+# - Required: No
+# - Default: false
+coller_manage_iptables: false
+
+# List of allowed networks to allow.
+#
+# Enabled when `coller_manage_iptables` is enabled.
+#
+# - Type: list
+# - Required: No
+coller_allowed_sources: []
+
+# Name of the database to connect.
+#
+# - Type: str
+# - Required: No
+# - Default: coller
+coller_db_name: coller
+
+# User to connect to the database.
+#
+# - Type: str
+# - Required: No
+# - Default: coller
+coller_db_user: coller
diff --git a/roles/coller/handlers/main.yml b/roles/coller/handlers/main.yml
new file mode 100644
index 0000000..9640331
--- /dev/null
+++ b/roles/coller/handlers/main.yml
@@ -0,0 +1,4 @@
+---
+- name: Save iptables
+ ansible.builtin.shell:
+ cmd: netfilter-persistent save
diff --git a/roles/coller/meta/argument_specs.yml b/roles/coller/meta/argument_specs.yml
new file mode 100644
index 0000000..a4b4744
--- /dev/null
+++ b/roles/coller/meta/argument_specs.yml
@@ -0,0 +1,52 @@
+---
+argument_specs:
+ main:
+ short_description: Install and configure a coller instance
+ description:
+ - Install and configure a [coller](https://git.riou.xyz/jriou/coller) instance.
+ author:
+ - jriou
+ options:
+ coller_version:
+ description:
+ - Version of the binary.
+ default: "1.3.1"
+
+ coller_config_dir:
+ description:
+ - Directory of the configuration files.
+ type: path
+ default: /etc/coller
+
+ coller_port:
+ description:
+ - Port to listen.
+ type: int
+ default: 8080
+
+ coller_manage_iptables:
+ description:
+ - Create iptables rule to allow the service.
+ type: bool
+ default: false
+
+ coller_allowed_sources:
+ description:
+ - List of allowed networks to allow.
+ - Enabled when `coller_manage_iptables` is enabled.
+ type: list
+
+ coller_db_name:
+ description:
+ - Name of the database to connect.
+ default: coller
+
+ coller_db_user:
+ description:
+ - User to connect to the database.
+ default: coller
+
+ coller_db_password:
+ description:
+ - Password to connect to the database.
+ required: true
diff --git a/roles/coller/meta/main.yml b/roles/coller/meta/main.yml
new file mode 100644
index 0000000..3f5647c
--- /dev/null
+++ b/roles/coller/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+- role: geerlingguy.docker
diff --git a/roles/coller/tasks/main.yml b/roles/coller/tasks/main.yml
new file mode 100644
index 0000000..736494c
--- /dev/null
+++ b/roles/coller/tasks/main.yml
@@ -0,0 +1,55 @@
+---
+- name: Check variables
+ ansible.builtin.assert:
+ that:
+ - coller_db_password is defined
+
+- name: Download source code
+ ansible.builtin.git:
+ repo: https://git.riou.xyz/jriou/coller.git
+ dest: /opt/coller
+ version: "{{ coller_version }}"
+
+- name: Create directories
+ ansible.builtin.file:
+ path: "{{ item }}"
+ state: directory
+ owner: root
+ group: root
+ mode: "0755"
+ loop:
+ - "{{ coller_config_dir }}"
+
+- name: Create docker-compose files
+ ansible.builtin.template:
+ src: "{{ item.src }}.j2"
+ dest: "{{ coller_config_dir }}/{{ item.src }}"
+ owner: root
+ group: root
+ mode: "{{ item.mode }}"
+ loop:
+ - src: docker-compose.yml
+ mode: "0644"
+ - src: db.env
+ mode: "0600"
+
+- name: Create configuration file
+ ansible.builtin.copy:
+ content:
+ database_type: postgres
+ database_dsn: "host=db dbname={{ coller_db_name }} user={{ coller_db_user }} password={{ coller_db_password }}"
+ dest: "{{ coller_config_dir }}/collerd.json"
+ owner: root
+ group: root
+ mode: "0640"
+ no_log: true
+
+- name: Start service
+ community.docker.docker_compose_v2:
+ project_src: "{{ coller_config_dir }}"
+ files:
+ - docker-compose.yml
+
+- name: Manage iptables
+ when: coller_manage_iptables is truthy
+ ansible.builtin.include_tasks: manage-iptables.yml
diff --git a/roles/coller/tasks/manage-iptables.yml b/roles/coller/tasks/manage-iptables.yml
new file mode 100644
index 0000000..456b0f3
--- /dev/null
+++ b/roles/coller/tasks/manage-iptables.yml
@@ -0,0 +1,16 @@
+---
+- name: Install packages
+ ansible.builtin.package:
+ name: netfilter-persistent
+
+- name: Allow with iptables
+ ansible.builtin.iptables:
+ chain: INPUT
+ protocol: tcp
+ source: "{{ item }}"
+ destination_ports:
+ - "{{ coller_port }}"
+ jump: ACCEPT
+ comment: coller
+ loop: "{{ coller_allowed_sources }}"
+ notify: Save iptables
diff --git a/roles/coller/templates/db.env.j2 b/roles/coller/templates/db.env.j2
new file mode 100644
index 0000000..511cba4
--- /dev/null
+++ b/roles/coller/templates/db.env.j2
@@ -0,0 +1,6 @@
+{{ ansible_managed | comment }}
+POSTGRES_USER={{ coller_db_user }}
+POSTGRES_PASSWORD={{ coller_db_password }}
+POSTGRES_DB={{ coller_db_name }}
+POSTGRES_INITDB_ARGS="--data-checksums"
+POSTGRES_HOST_AUTH_METHOD=scram-sha-256
diff --git a/roles/coller/templates/docker-compose.yml.j2 b/roles/coller/templates/docker-compose.yml.j2
new file mode 100644
index 0000000..1d46c35
--- /dev/null
+++ b/roles/coller/templates/docker-compose.yml.j2
@@ -0,0 +1,32 @@
+---
+{{ ansible_managed | comment }}
+services:
+ server:
+ image: coller:{{ coller_version }}
+ build: /opt/coller
+ container_name: collerd
+ restart: always
+ networks:
+ - coller
+ ports:
+ - "{{ coller_port }}:8080"
+ volumes:
+ - "{{ coller_config_dir }}/collerd.json:/etc/collerd.json:ro"
+ command: collerd -config /etc/collerd.json
+
+ db:
+ image: postgres:17
+ hostname: db
+ container_name: collerd_db
+ restart: always
+ env_file: {{ coller_config_dir }}/db.env
+ networks:
+ - coller
+ volumes:
+ - coller:/var/lib/postgresql/data
+
+networks:
+ coller:
+
+volumes:
+ coller:
diff --git a/roles/firefly/README.md b/roles/firefly/README.md
new file mode 100644
index 0000000..56c9205
--- /dev/null
+++ b/roles/firefly/README.md
@@ -0,0 +1,232 @@
+# Ansible Role Firefly
+
+Ansible role to manage a [Firefly III](https://firefly-iii.org/) instance.
+
+## Table of content
+
+
+* [Role variables](#variables)
+ * [`firefly_version`](#variable-firefly_version)
+ * [`firefly_port`](#variable-firefly_port)
+ * [`firefly_static_cron_token`](#variable-firefly_static_cron_token)
+ * [`firefly_home`](#variable-firefly_home)
+ * [`firefly_site_owner`](#variable-firefly_site_owner)
+ * [`firefly_app_key`](#variable-firefly_app_key)
+ * [`firefly_language`](#variable-firefly_language)
+ * [`firefly_tz`](#variable-firefly_tz)
+ * [`firefly_db_database`](#variable-firefly_db_database)
+ * [`firefly_db_username`](#variable-firefly_db_username)
+ * [`firefly_db_password`](#variable-firefly_db_password)
+ * [`firefly_manage_iptables`](#variable-firefly_manage_iptables)
+ * [`firefly_allowed_sources`](#variable-firefly_allowed_sources)
+
+
+
+## Role variables
+
+The following variables can be configured for this role:
+
+| Variable | Type | Required | Default | Description (abstract) |
+|----------|------|----------|---------|------------------------|
+| `firefly_version` | `str` | No | `"latest"` | Version of the docker image. |
+| `firefly_port` | `int` | No | `8080` | |
+| `firefly_static_cron_token` | `str` | Yes | N/A | Token used by the cron job (sensitive). |
+| `firefly_home` | `path` | No | `"/var/lib/firefly"` | Directory where to store data files. |
+| `firefly_site_owner` | `str` | No | `"root@localhost"` | E-mail address of the site owner. |
+| `firefly_app_key` | `str` | Yes | N/A | Application key (sensitive). |
+| `firefly_language` | `str` | No | `"en_US"` | Language of the web interface. |
+| `firefly_tz` | `str` | No | `"Etc/UTC"` | Time zone of the web interface. |
+| `firefly_db_database` | `str` | No | `"firefly"` | Name of the database. |
+| `firefly_db_username` | `str` | No | `"firefly"` | Name of the user to connect to the database. |
+| `firefly_db_password` | `str` | Yes | N/A | Password to connect to the database (sensitive). |
+| `firefly_manage_iptables` | `bool` | No | `false` | Configure iptables rules. |
+| `firefly_allowed_sources` | `list` | No | N/A | List of IP ranges to allow when `firefly_manage_iptables` is enabled. |
+
+### `firefly_version`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Version of the docker image.
+
+- **Type**: `str`
+- **Required**: No
+- **Default**: `"latest"`
+
+
+
+### `firefly_port`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+
+
+- **Type**: `int`
+- **Required**: No
+- **Default**: `8080`
+
+
+
+### `firefly_static_cron_token`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Token used by the cron job (sensitive).
+
+- **Type**: `str`
+- **Required**: Yes
+
+
+
+### `firefly_home`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Directory where to store data files.
+
+- **Type**: `path`
+- **Required**: No
+- **Default**: `"/var/lib/firefly"`
+
+
+
+### `firefly_site_owner`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+E-mail address of the site owner.
+
+- **Type**: `str`
+- **Required**: No
+- **Default**: `"root@localhost"`
+
+
+
+### `firefly_app_key`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Application key (sensitive).
+
+- **Type**: `str`
+- **Required**: Yes
+
+
+
+### `firefly_language`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Language of the web interface.
+
+- **Type**: `str`
+- **Required**: No
+- **Default**: `"en_US"`
+
+
+
+### `firefly_tz`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Time zone of the web interface.
+
+- **Type**: `str`
+- **Required**: No
+- **Default**: `"Etc/UTC"`
+
+
+
+### `firefly_db_database`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Name of the database.
+
+- **Type**: `str`
+- **Required**: No
+- **Default**: `"firefly"`
+
+
+
+### `firefly_db_username`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Name of the user to connect to the database.
+
+- **Type**: `str`
+- **Required**: No
+- **Default**: `"firefly"`
+
+
+
+### `firefly_db_password`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Password to connect to the database (sensitive).
+
+- **Type**: `str`
+- **Required**: Yes
+
+
+
+### `firefly_manage_iptables`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Configure iptables rules.
+
+- **Type**: `bool`
+- **Required**: No
+- **Default**: `false`
+
+
+
+### `firefly_allowed_sources`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+List of IP ranges to allow when `firefly_manage_iptables` is enabled.
+
+- **Type**: `list`
+- **Required**: No
+
+
+
+
+
+
+## Configuration
+
+See [Variable
+precedence](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_variables.html#ansible-variable-precedence)
+to find where you should put your own variables.
+
+Then define at least `firefly_static_cron_token`, `firefly_db_password` and
+`firefly_app_key` variables with a strong and secure password, encrypted using
+[ansible-vault](https://docs.ansible.com/ansible/latest/cli/ansible-vault.html).
+
+See list of [default variables](defaults/main.yml).
+
+
+## Usage
+
+Playbook example:
+
+```yaml
+- hosts: all
+ roles:
+ - jriou.general.firefly
+```
+
+Then run the playbook:
+
+```
+ansible-playbook play.yml
+```
+
+## Donate
+
+As we all love FOSS projects, you should consider [sponsoring and/or
+contribute](https://github.com/firefly-iii/firefly-iii).
diff --git a/roles/firefly/defaults/main.yml b/roles/firefly/defaults/main.yml
new file mode 100644
index 0000000..96a2f4e
--- /dev/null
+++ b/roles/firefly/defaults/main.yml
@@ -0,0 +1,66 @@
+---
+firefly_port: 8080
+
+# Version of the docker image.
+#
+# - Type: str
+# - Required: No
+# - Default: latest
+firefly_version: latest
+
+# Directory where to store data files.
+#
+# - Type: path
+# - Required: No
+# - Default: /var/lib/firefly
+firefly_home: /var/lib/firefly
+
+# E-mail address of the site owner.
+#
+# - Type: str
+# - Required: No
+# - Default: root@localhost
+firefly_site_owner: root@localhost
+
+
+# Language of the web interface.
+#
+# - Type: str
+# - Required: No
+# - Default: en_US
+firefly_language: en_US
+
+# Time zone of the web interface.
+#
+# - Type: str
+# - Required: No
+# - Default: Etc/UTC
+firefly_tz: Etc/UTC
+
+# Name of the database.
+#
+# - Type: str
+# - Required: No
+# - Default: firefly
+firefly_db_database: firefly
+
+# Name of the user to connect to the database.
+#
+# - Type: str
+# - Required: No
+# - Default: firefly
+firefly_db_username: firefly
+
+
+# Configure iptables rules.
+#
+# - Type: bool
+# - Required: No
+# - Default: false
+firefly_manage_iptables: false
+
+# List of IP ranges to allow when `firefly_manage_iptables` is enabled.
+#
+# - Type: list
+# - Required: No
+firefly_allowed_sources: []
diff --git a/roles/firefly/handlers/main.yml b/roles/firefly/handlers/main.yml
new file mode 100644
index 0000000..9640331
--- /dev/null
+++ b/roles/firefly/handlers/main.yml
@@ -0,0 +1,4 @@
+---
+- name: Save iptables
+ ansible.builtin.shell:
+ cmd: netfilter-persistent save
diff --git a/roles/firefly/meta/argument_specs.yml b/roles/firefly/meta/argument_specs.yml
new file mode 100644
index 0000000..c87f062
--- /dev/null
+++ b/roles/firefly/meta/argument_specs.yml
@@ -0,0 +1,76 @@
+---
+argument_specs:
+ main:
+ short_description: Install and configure Firefly III
+ description:
+ - Install and configure [Firefly III](https://www.firefly-iii.org/).
+ author:
+ - jriou
+ options:
+ firefly_version:
+ description:
+ - Version of the docker image.
+ default: latest
+
+ firefly_port:
+ descritpion:
+ - Port to listen.
+ type: int
+ default: 8080
+
+ firefly_static_cron_token:
+ description:
+ - Token used by the cron job (sensitive).
+ required: true
+
+ firefly_home:
+ description:
+ - Directory where to store data files.
+ type: path
+ default: /var/lib/firefly
+
+ firefly_site_owner:
+ description:
+ - E-mail address of the site owner.
+ default: root@localhost
+
+ firefly_app_key:
+ description:
+ - Application key (sensitive).
+ required: true
+
+ firefly_language:
+ description:
+ - Language of the web interface.
+ default: en_US
+
+ firefly_tz:
+ description:
+ - Time zone of the web interface.
+ default: Etc/UTC
+
+ firefly_db_database:
+ description:
+ - Name of the database.
+ default: firefly
+
+ firefly_db_username:
+ description:
+ - Name of the user to connect to the database.
+ default: firefly
+
+ firefly_db_password:
+ description:
+ - Password to connect to the database (sensitive).
+ required: true
+
+ firefly_manage_iptables:
+ description:
+ - Configure iptables rules.
+ type: bool
+ default: false
+
+ firefly_allowed_sources:
+ description:
+ - List of IP ranges to allow when `firefly_manage_iptables` is enabled.
+ type: list
diff --git a/roles/firefly/meta/main.yml b/roles/firefly/meta/main.yml
new file mode 100644
index 0000000..3f5647c
--- /dev/null
+++ b/roles/firefly/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+- role: geerlingguy.docker
diff --git a/roles/firefly/tasks/main.yml b/roles/firefly/tasks/main.yml
new file mode 100644
index 0000000..8d44a22
--- /dev/null
+++ b/roles/firefly/tasks/main.yml
@@ -0,0 +1,47 @@
+---
+- name: Check requirements
+ ansible.builtin.assert:
+ that:
+ - firefly_static_cron_token is defined
+ - firefly_db_password is defined
+ - firefly_app_key is defined
+
+- name: Install dependencies
+ ansible.builtin.apt:
+ name:
+ - python3-docker
+ - python3-compose
+
+- name: Create directories
+ ansible.builtin.file:
+ path: /etc/firefly
+ state: directory
+
+- name: Create configuration files
+ ansible.builtin.template:
+ src: "{{ item }}.j2"
+ dest: "/etc/firefly/{{ item }}"
+ mode: "0600"
+ loop:
+ - docker-compose.yml
+ - db.env
+ - app.env
+
+- name: Start service
+ community.docker.docker_compose_v2:
+ project_src: /etc/firefly
+ files:
+ - docker-compose.yml
+
+- name: Allow with iptables
+ ansible.builtin.iptables:
+ chain: INPUT
+ protocol: tcp
+ source: "{{ item }}"
+ destination_ports:
+ - "{{ firefly_port }}"
+ jump: ACCEPT
+ comment: firefly
+ loop: "{{ firefly_allowed_sources }}"
+ notify: Save iptables
+ when: firefly_manage_iptables is truthy
diff --git a/roles/firefly/templates/app.env.j2 b/roles/firefly/templates/app.env.j2
new file mode 100644
index 0000000..89aa8aa
--- /dev/null
+++ b/roles/firefly/templates/app.env.j2
@@ -0,0 +1,132 @@
+APP_ENV=local
+APP_DEBUG=false
+
+SITE_OWNER={{ firefly_site_owner }}
+
+APP_KEY={{ firefly_app_key }}
+
+DEFAULT_LANGUAGE={{ firefly_language }}
+DEFAULT_LOCALE=equal
+
+TZ={{ firefly_tz }}
+
+TRUSTED_PROXIES=*
+
+LOG_CHANNEL=stack
+
+APP_LOG_LEVEL=notice
+
+AUDIT_LOG_LEVEL=emergency
+AUDIT_LOG_CHANNEL=
+PAPERTRAIL_HOST=
+PAPERTRAIL_PORT=
+
+# Database credentials. Make sure the database exists. I recommend a dedicated user for Firefly III
+# For other database types, please see the FAQ: https://docs.firefly-iii.org/firefly-iii/faq/self-hosted/#i-want-to-use-sqlite
+# If you use Docker or similar, you can set these variables from a file by appending them with _FILE
+# Use "pgsql" for PostgreSQL
+# Use "mysql" for MySQL and MariaDB.
+# Use "sqlite" for SQLite.
+DB_CONNECTION=pgsql
+DB_HOST=db
+DB_PORT=5432
+DB_DATABASE={{ firefly_db_database }}
+DB_USERNAME={{ firefly_db_username }}
+DB_PASSWORD={{ firefly_db_password }}
+DB_SOCKET=
+
+PGSQL_SSL_MODE=prefer
+PGSQL_SCHEMA=public
+
+CACHE_DRIVER=file
+SESSION_DRIVER=file
+
+REDIS_SCHEME=tcp
+REDIS_PATH=
+REDIS_HOST=redis
+REDIS_PORT=6379
+REDIS_USERNAME=firefly
+REDIS_PASSWORD=
+REDIS_DB="0"
+REDIS_CACHE_DB="1"
+
+COOKIE_PATH="/"
+COOKIE_DOMAIN=
+COOKIE_SECURE=false
+COOKIE_SAMESITE=lax
+
+MAIL_MAILER=log
+MAIL_HOST=null
+MAIL_PORT=2525
+MAIL_FROM=changeme@example.com
+MAIL_USERNAME=null
+MAIL_PASSWORD=null
+MAIL_ENCRYPTION=null
+MAIL_SENDMAIL_COMMAND=
+MAILGUN_DOMAIN=
+MAILGUN_SECRET=
+MAILGUN_ENDPOINT=api.mailgun.net
+MANDRILL_SECRET=
+SPARKPOST_SECRET=
+SEND_ERROR_MESSAGE=true
+SEND_REPORT_JOURNALS=true
+
+ENABLE_EXTERNAL_MAP=false
+ENABLE_EXTERNAL_RATES=false
+MAP_DEFAULT_LAT=51.983333
+MAP_DEFAULT_LONG=5.916667
+MAP_DEFAULT_ZOOM=6
+
+VALID_URL_PROTOCOLS=
+
+AUTHENTICATION_GUARD=web
+AUTHENTICATION_GUARD_HEADER=REMOTE_USER
+AUTHENTICATION_GUARD_EMAIL=
+
+PASSPORT_PRIVATE_KEY=
+PASSPORT_PUBLIC_KEY=
+
+CUSTOM_LOGOUT_URL=
+
+DISABLE_FRAME_HEADER=false
+DISABLE_CSP_HEADER=false
+TRACKER_SITE_ID=
+TRACKER_URL=
+
+ALLOW_WEBHOOKS=false
+
+STATIC_CRON_TOKEN={{ firefly_static_cron_token }}
+
+DKR_BUILD_LOCALE=false
+DKR_CHECK_SQLITE=true
+DKR_RUN_MIGRATION=true
+DKR_RUN_UPGRADE=true
+DKR_RUN_VERIFY=true
+DKR_RUN_REPORT=true
+DKR_RUN_PASSPORT_INSTALL=true
+
+APP_NAME=FireflyIII
+BROADCAST_DRIVER=log
+QUEUE_DRIVER=sync
+CACHE_PREFIX=firefly
+PUSHER_KEY=
+IPINFO_TOKEN=
+PUSHER_SECRET=
+PUSHER_ID=
+DEMO_USERNAME=
+DEMO_PASSWORD=
+FIREFLY_III_LAYOUT=v1
+
+#
+# If you have trouble configuring your Firefly III installation, DON'T BOTHER setting this variable.
+# It won't work. It doesn't do ANYTHING. Don't believe the lies you read online. I'm not joking.
+# This configuration value WILL NOT HELP.
+#
+# Notable exception to this rule is Synology, which, according to some users, will use APP_URL to rewrite stuff.
+#
+# This variable is ONLY used in some of the emails Firefly III sends around. Nowhere else.
+# So when configuring anything WEB related this variable doesn't do anything. Nothing
+#
+# If you're stuck I understand you get desperate but look SOMEWHERE ELSE.
+#
+APP_URL=http://localhost
diff --git a/roles/firefly/templates/db.env.j2 b/roles/firefly/templates/db.env.j2
new file mode 100644
index 0000000..6805c19
--- /dev/null
+++ b/roles/firefly/templates/db.env.j2
@@ -0,0 +1,5 @@
+POSTGRES_USER={{ firefly_db_username }}
+POSTGRES_PASSWORD={{ firefly_db_password }}
+POSTGRES_DB={{ firefly_db_database }}
+POSTGRES_INITDB_ARGS="--data-checksums"
+POSTGRES_HOST_AUTH_METHOD=scram-sha-256
diff --git a/roles/firefly/templates/docker-compose.yml.j2 b/roles/firefly/templates/docker-compose.yml.j2
new file mode 100644
index 0000000..81ae8f0
--- /dev/null
+++ b/roles/firefly/templates/docker-compose.yml.j2
@@ -0,0 +1,40 @@
+---
+{{ ansible_managed | comment }}
+services:
+ app:
+ image: fireflyiii/core:{{ firefly_version }}
+ hostname: app
+ container_name: firefly_iii_core
+ restart: always
+ volumes:
+ - {{ firefly_home }}/app/upload:/var/www/html/storage/upload
+ env_file: /etc/firefly/app.env
+ networks:
+ - firefly_iii
+ ports:
+ - {{ firefly_port }}:8080
+ depends_on:
+ - db
+ db:
+ image: postgres:17
+ hostname: db
+ container_name: firefly_iii_db
+ restart: always
+ env_file: /etc/firefly/db.env
+ networks:
+ - firefly_iii
+ volumes:
+ - {{ firefly_home }}/db/data:/var/lib/postgresql/data
+ - {{ firefly_home }}/db/backup:/var/lib/postgresql/backup
+
+ cron:
+ image: alpine
+ restart: always
+ container_name: firefly_iii_cron
+ command: sh -c "echo \"0 3 * * * wget -qO- http://app:8080/api/v1/cron/{{ firefly_static_cron_token }}\" | crontab - && crond -f -L /dev/stdout"
+ networks:
+ - firefly_iii
+
+networks:
+ firefly_iii:
+ driver: bridge
diff --git a/roles/forgejo/README.md b/roles/forgejo/README.md
new file mode 100644
index 0000000..b7bbed9
--- /dev/null
+++ b/roles/forgejo/README.md
@@ -0,0 +1,239 @@
+# Ansible Role Forgejo
+
+Ansible role to manage a [Forgejo](https://forgejo.org/) instance.
+
+## Configuration
+
+See [Variable
+precedence](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_variables.html#ansible-variable-precedence)
+to find where you should put your own variables.
+
+Then define at least `forgejo_db_password` with a strong and secure password,
+encrypted using
+[ansible-vault](https://docs.ansible.com/ansible/latest/cli/ansible-vault.html).
+
+
+## Table of Content
+
+
+* [Role variables](#variables)
+ * [`forgejo_version`](#variable-forgejo_version)
+ * [`forgejo_user`](#variable-forgejo_user)
+ * [`forgejo_home_dir`](#variable-forgejo_home_dir)
+ * [`forgejo_config_dir`](#variable-forgejo_config_dir)
+ * [`forgejo_web_port`](#variable-forgejo_web_port)
+ * [`forgejo_ssh_port`](#variable-forgejo_ssh_port)
+ * [`forgejo_db_username`](#variable-forgejo_db_username)
+ * [`forgejo_db_password`](#variable-forgejo_db_password)
+ * [`forgejo_db_database`](#variable-forgejo_db_database)
+ * [`forgejo_mailer`](#variable-forgejo_mailer)
+ * [`forgejo_service`](#variable-forgejo_service)
+ * [`forgejo_manage_iptables`](#variable-forgejo_manage_iptables)
+ * [`forgejo_allowed_sources`](#variable-forgejo_allowed_sources)
+
+
+
+## Role variables
+
+The following variables can be configured for this role:
+
+| Variable | Type | Required | Default | Description (abstract) |
+|----------|------|----------|---------|------------------------|
+| `forgejo_version` | `int` | No | `14` | Version of the Forgejo binaries |
+| `forgejo_user` | `path` | No | `"forgejo"` | Operating system user to run the server |
+| `forgejo_home_dir` | `path` | No | `"/var/lib/forgejo"` | Path to the home directory |
+| `forgejo_config_dir` | `path` | No | `"/etc/forgejo"` | Path to the configuration directory |
+| `forgejo_web_port` | `int` | No | `3000` | Port to listen for the web UI |
+| `forgejo_ssh_port` | `int` | No | `222` | Port to listen for SSH |
+| `forgejo_db_username` | `str` | No | `"forgejo"` | Name of the user in the database |
+| `forgejo_db_password` | `str` | Yes | N/A | Password of the user in the database |
+| `forgejo_db_database` | `str` | No | `"forgejo"` | Name of the database |
+| `forgejo_mailer` | `dict` | No | N/A | Configure the mailer to send e-mail notifications
Define a `enabled` key with a boolean to enable the mailer
Define a `from` key with the source e-mail address
See [Email setup](https://forgejo.org/docs/latest/admin/setup/email/) |
+| `forgejo_service` | `dict` | No | N/A | Configure service settings
See [Service](https://forgejo.org/docs/latest/admin/config-cheat-sheet/#service-service) |
+| `forgejo_manage_iptables` | `bool` | No | `false` | Configure iptables rules |
+| `forgejo_allowed_sources` | `list` | No | N/A | List of IP ranges to allow when `forgejo_manage_iptables` is enabled |
+
+### `forgejo_version`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Version of the Forgejo binaries
+
+- **Type**: `int`
+- **Required**: No
+- **Default**: `14`
+
+
+
+### `forgejo_user`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Operating system user to run the server
+
+- **Type**: `path`
+- **Required**: No
+- **Default**: `"forgejo"`
+
+
+
+### `forgejo_home_dir`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Path to the home directory
+
+- **Type**: `path`
+- **Required**: No
+- **Default**: `"/var/lib/forgejo"`
+
+
+
+### `forgejo_config_dir`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Path to the configuration directory
+
+- **Type**: `path`
+- **Required**: No
+- **Default**: `"/etc/forgejo"`
+
+
+
+### `forgejo_web_port`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Port to listen for the web UI
+
+- **Type**: `int`
+- **Required**: No
+- **Default**: `3000`
+
+
+
+### `forgejo_ssh_port`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Port to listen for SSH
+
+- **Type**: `int`
+- **Required**: No
+- **Default**: `222`
+
+
+
+### `forgejo_db_username`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Name of the user in the database
+
+- **Type**: `str`
+- **Required**: No
+- **Default**: `"forgejo"`
+
+
+
+### `forgejo_db_password`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Password of the user in the database
+
+- **Type**: `str`
+- **Required**: Yes
+
+
+
+### `forgejo_db_database`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Name of the database
+
+- **Type**: `str`
+- **Required**: No
+- **Default**: `"forgejo"`
+
+
+
+### `forgejo_mailer`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Configure the mailer to send e-mail notifications
+
+Define a `enabled` key with a boolean to enable the mailer
+
+Define a `from` key with the source e-mail address
+
+See [Email setup](https://forgejo.org/docs/latest/admin/setup/email/)
+
+- **Type**: `dict`
+- **Required**: No
+
+
+
+### `forgejo_service`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Configure service settings
+
+See [Service](https://forgejo.org/docs/latest/admin/config-cheat-sheet/#service-service)
+
+- **Type**: `dict`
+- **Required**: No
+
+
+
+### `forgejo_manage_iptables`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Configure iptables rules
+
+- **Type**: `bool`
+- **Required**: No
+- **Default**: `false`
+
+
+
+### `forgejo_allowed_sources`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+List of IP ranges to allow when `forgejo_manage_iptables` is enabled
+
+- **Type**: `list`
+- **Required**: No
+
+
+
+
+
+
+## Usage
+
+Playbook example:
+
+```yaml
+- hosts: all
+ roles:
+ - jriou.general.forgejo
+```
+
+Then run the playbook:
+
+```
+ansible-playbook play.yml
+```
+
+## Donate
+
+As we all love FOSS projects, you should consider [donating to
+Codeberg](https://donate.codeberg.org/), the non-profit organization behind
+Forgejo.
diff --git a/roles/forgejo/defaults/main.yml b/roles/forgejo/defaults/main.yml
new file mode 100644
index 0000000..e494b70
--- /dev/null
+++ b/roles/forgejo/defaults/main.yml
@@ -0,0 +1,91 @@
+---
+
+# Version of the Forgejo binaries
+#
+# - Type: int
+# - Required: No
+# - Default: 14
+forgejo_version: 14
+
+
+# Operating system user to run the server
+#
+# - Type: path
+# - Required: No
+# - Default: forgejo
+forgejo_user: forgejo
+
+# Path to the home directory
+#
+# - Type: path
+# - Required: No
+# - Default: /var/lib/forgejo
+forgejo_home_dir: /var/lib/forgejo
+
+# Path to the configuration directory
+#
+# - Type: path
+# - Required: No
+# - Default: /etc/forgejo
+forgejo_config_dir: /etc/forgejo
+
+# Port to listen for the web UI
+#
+# - Type: int
+# - Required: No
+# - Default: 3000
+forgejo_web_port: 3000
+
+# Port to listen for SSH
+#
+# - Type: int
+# - Required: No
+# - Default: 222
+forgejo_ssh_port: 222
+
+# Name of the user in the database
+#
+# - Type: str
+# - Required: No
+# - Default: forgejo
+forgejo_db_username: forgejo
+
+# Name of the database
+#
+# - Type: str
+# - Required: No
+# - Default: forgejo
+forgejo_db_database: forgejo
+
+# Configure the mailer to send e-mail notifications
+#
+# Define a `enabled` key with a boolean to enable the mailer
+#
+# Define a `from` key with the source e-mail address
+#
+# See
+#
+# - Type: dict
+# - Required: No
+forgejo_mailer: {}
+
+# Configure service settings
+#
+# See
+#
+# - Type: dict
+# - Required: No
+forgejo_service: {}
+
+# Configure iptables rules
+#
+# - Type: bool
+# - Required: No
+# - Default: false
+forgejo_manage_iptables: false
+
+# List of IP ranges to allow when `forgejo_manage_iptables` is enabled
+#
+# - Type: list
+# - Required: No
+forgejo_allowed_sources: []
diff --git a/roles/forgejo/handlers/main.yml b/roles/forgejo/handlers/main.yml
new file mode 100644
index 0000000..9640331
--- /dev/null
+++ b/roles/forgejo/handlers/main.yml
@@ -0,0 +1,4 @@
+---
+- name: Save iptables
+ ansible.builtin.shell:
+ cmd: netfilter-persistent save
diff --git a/roles/forgejo/meta/argument_specs.yml b/roles/forgejo/meta/argument_specs.yml
new file mode 100644
index 0000000..f153919
--- /dev/null
+++ b/roles/forgejo/meta/argument_specs.yml
@@ -0,0 +1,84 @@
+---
+argument_specs:
+ main:
+ short_description: Install and configure a Forgejo instance
+ description:
+ - Install and configure a [Forgejo](https://forgejo.org/) instance.
+ author:
+ - jriou
+ options:
+ forgejo_version:
+ description:
+ - Version of the Forgejo binaries
+ type: int
+ default: 14
+
+ forgejo_user:
+ description:
+ - Operating system user to run the server
+ type: path
+ default: forgejo
+
+ forgejo_home_dir:
+ description:
+ - Path to the home directory
+ type: path
+ default: /var/lib/forgejo
+
+ forgejo_config_dir:
+ description:
+ - Path to the configuration directory
+ type: path
+ default: /etc/forgejo
+
+ forgejo_web_port:
+ description:
+ - Port to listen for the web UI
+ type: int
+ default: 3000
+
+ forgejo_ssh_port:
+ description:
+ - Port to listen for SSH
+ type: int
+ default: 222
+
+ forgejo_db_username:
+ description:
+ - Name of the user in the database
+ default: forgejo
+
+ forgejo_db_password:
+ description:
+ - Password of the user in the database
+ required: true
+
+ forgejo_db_database:
+ description:
+ - Name of the database
+ default: forgejo
+
+ forgejo_mailer:
+ description:
+ - Configure the mailer to send e-mail notifications
+ - Define a `enabled` key with a boolean to enable the mailer
+ - Define a `from` key with the source e-mail address
+ - See [Email setup](https://forgejo.org/docs/latest/admin/setup/email/)
+ type: dict
+
+ forgejo_service:
+ description:
+ - Configure service settings
+ - See [Service](https://forgejo.org/docs/latest/admin/config-cheat-sheet/#service-service)
+ type: dict
+
+ forgejo_manage_iptables:
+ description:
+ - Configure iptables rules
+ type: bool
+ default: false
+
+ forgejo_allowed_sources:
+ description:
+ - List of IP ranges to allow when `forgejo_manage_iptables` is enabled
+ type: list
diff --git a/roles/forgejo/meta/main.yml b/roles/forgejo/meta/main.yml
new file mode 100644
index 0000000..3f5647c
--- /dev/null
+++ b/roles/forgejo/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+- role: geerlingguy.docker
diff --git a/roles/forgejo/tasks/main.yml b/roles/forgejo/tasks/main.yml
new file mode 100644
index 0000000..0a90aba
--- /dev/null
+++ b/roles/forgejo/tasks/main.yml
@@ -0,0 +1,72 @@
+---
+- name: Check required variables
+ ansible.builtin.assert:
+ that:
+ - forgejo_db_password is defined
+
+- name: Create user
+ ansible.builtin.user:
+ name: "{{ forgejo_user }}"
+ system: true
+ password: '!'
+ home: "{{ forgejo_home_dir }}"
+ create_home: false
+
+- name: Read attributes
+ ansible.builtin.getent:
+ database: passwd
+ key: "{{ forgejo_user }}"
+
+- name: Create directories
+ ansible.builtin.file:
+ state: directory
+ path: "{{ item }}"
+ owner: "{{ forgejo_user }}"
+ group: "{{ forgejo_user }}"
+ mode: "0755"
+ loop: &forgejo_directories
+ - "{{ forgejo_config_dir }}"
+ - "{{ forgejo_home_dir }}"
+ - "{{ forgejo_home_dir }}/server"
+ - "{{ forgejo_home_dir }}/db"
+
+- name: Ensure permissions on the directories
+ ansible.builtin.command:
+ cmd: "chown -v -R {{ forgejo_user }}:{{ forgejo_user }} {{ item }}"
+ loop: *forgejo_directories
+ register: forgejo_chown
+ changed_when: forgejo_chown.stdout_lines | regex_search('^changed ownership of') != None
+
+- name: Create docker-compose configuration
+ ansible.builtin.template:
+ src: "{{ item.name }}.j2"
+ dest: "{{ forgejo_config_dir }}/{{ item.name }}"
+ owner: "{{ forgejo_user }}"
+ group: "{{ forgejo_user }}"
+ mode: "{{ item.mode }}"
+ loop:
+ - name: docker-compose.yml
+ mode: "0644"
+ - name: server.env
+ mode: "0600"
+ - name: db.env
+ mode: "0600"
+
+- name: Start service
+ community.docker.docker_compose_v2:
+ project_src: "{{ forgejo_config_dir }}"
+ files:
+ - docker-compose.yml
+
+- name: Allow with iptables
+ ansible.builtin.iptables:
+ chain: INPUT
+ protocol: tcp
+ source: "{{ item }}"
+ destination_ports:
+ - "{{ forgejo_web_port }}"
+ - "{{ forgejo_ssh_port }}"
+ jump: ACCEPT
+ comment: forgejo
+ loop: "{{ forgejo_allowed_sources }}"
+ when: forgejo_manage_iptables is truthy
diff --git a/roles/forgejo/templates/db.env.j2 b/roles/forgejo/templates/db.env.j2
new file mode 100644
index 0000000..ba5ecbd
--- /dev/null
+++ b/roles/forgejo/templates/db.env.j2
@@ -0,0 +1,6 @@
+{{ ansible_managed | comment }}
+POSTGRES_USER="{{ forgejo_db_username }}"
+POSTGRES_PASSWORD="{{ forgejo_db_password }}"
+POSTGRES_DB="{{ forgejo_db_database }}"
+POSTGRES_INITDB_ARGS="--data-checksums"
+POSTGRES_HOST_AUTH_METHOD=scram-sha-256
diff --git a/roles/forgejo/templates/docker-compose.yml.j2 b/roles/forgejo/templates/docker-compose.yml.j2
new file mode 100644
index 0000000..d54e1d7
--- /dev/null
+++ b/roles/forgejo/templates/docker-compose.yml.j2
@@ -0,0 +1,35 @@
+---
+{{ ansible_managed | comment }}
+services:
+ server:
+ image: codeberg.org/forgejo/forgejo:{{ forgejo_version }}
+ container_name: forgejo-server
+ env_file: {{ forgejo_config_dir }}/server.env
+ restart: always
+ networks:
+ - forgejo
+ volumes:
+ - "{{ forgejo_home_dir }}/server:/data"
+ - /etc/timezone:/etc/timezone:ro
+ - /etc/localtime:/etc/localtime:ro
+ ports:
+ - "{{ forgejo_web_port }}:3000"
+ - "{{ forgejo_ssh_port }}:22"
+ depends_on:
+ - db
+
+ db:
+ image: postgres:17
+ hostname: db
+ container_name: forgejo-db
+ restart: always
+ env_file: {{ forgejo_config_dir }}/db.env
+ user: "{{ ansible_facts.getent_passwd.forgejo[1] }}:{{ ansible_facts.getent_passwd.forgejo[2] }}"
+ networks:
+ - forgejo
+ volumes:
+ - "{{ forgejo_home_dir }}/db:/var/lib/postgresql/data"
+
+networks:
+ forgejo:
+ external: false
diff --git a/roles/forgejo/templates/server.env.j2 b/roles/forgejo/templates/server.env.j2
new file mode 100644
index 0000000..0bf2d4c
--- /dev/null
+++ b/roles/forgejo/templates/server.env.j2
@@ -0,0 +1,19 @@
+{{ ansible_managed | comment }}
+USER_UID={{ ansible_facts.getent_passwd.forgejo[1] }}
+USER_GID={{ ansible_facts.getent_passwd.forgejo[2] }}
+FORGEJO__server__SSH_PORT={{ forgejo_ssh_port }}
+FORGEJO__database__DB_TYPE=postgres
+FORGEJO__database__HOST=db:5432
+FORGEJO__database__NAME="{{ forgejo_db_database }}"
+FORGEJO__database__USER="{{ forgejo_db_username }}"
+FORGEJO__database__PASSWD="{{ forgejo_db_password }}"
+{% if forgejo_mailer %}
+{% for k, v in forgejo_mailer.items() %}
+FORGEJO__mailer__{{ k | upper }}="{{ v }}"
+{% endfor %}
+{% endif %}
+{% if forgejo_service %}
+{% for k, v in forgejo_service.items() %}
+FORGEJO__service__{{ k | upper }}="{{ v }}"
+{% endfor %}
+{% endif %}
diff --git a/roles/forgejo_runners/README.md b/roles/forgejo_runners/README.md
new file mode 100644
index 0000000..33f9166
--- /dev/null
+++ b/roles/forgejo_runners/README.md
@@ -0,0 +1,146 @@
+# Ansible Role Forgejo Runners
+
+Ansible role to manage [Forgejo](https://forgejo.org/) runners.
+
+## Configuration
+
+See [Variable
+precedence](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_variables.html#ansible-variable-precedence)
+to find where you should put your own variables.
+
+Then define at least `forgejo_db_password` with a strong and secure password,
+encrypted using
+[ansible-vault](https://docs.ansible.com/ansible/latest/cli/ansible-vault.html).
+
+
+## Table of Content
+
+
+* [Role variables](#variables)
+ * [`forgejo_runners_version`](#variable-forgejo_runners_version)
+ * [`forgejo_runners_user`](#variable-forgejo_runners_user)
+ * [`forgejo_runners_home_dir`](#variable-forgejo_runners_home_dir)
+ * [`forgejo_runners_config_dir`](#variable-forgejo_runners_config_dir)
+ * [`forgejo_runners_instance`](#variable-forgejo_runners_instance)
+ * [`forgejo_runners_settings`](#variable-forgejo_runners_settings)
+
+
+
+## Role variables
+
+The following variables can be configured for this role:
+
+| Variable | Type | Required | Default | Description (abstract) |
+|----------|------|----------|---------|------------------------|
+| `forgejo_runners_version` | `str` | No | `"9.1.1"` | Version of the runners |
+| `forgejo_runners_user` | `str` | No | `"forgejo"` | Operating system user to run the runners |
+| `forgejo_runners_home_dir` | `str` | No | `"/var/lib/forgejo"` | Home directory of the operating system user |
+| `forgejo_runners_config_dir` | `path` | No | `"/etc/forgejo-runners"` | Path to the configuration directory of the runners |
+| `forgejo_runners_instance` | `str` | No | N/A | URL of the Forgejo instance to register the runners |
+| `forgejo_runners_settings` | `dict` | No | N/A | Dict of runners to configure
The key is the name of the repository on the instance
The value is a dict with a `token` key and optionally a dict of `labels` |
+
+### `forgejo_runners_version`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Version of the runners
+
+- **Type**: `str`
+- **Required**: No
+- **Default**: `"9.1.1"`
+
+
+
+### `forgejo_runners_user`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Operating system user to run the runners
+
+- **Type**: `str`
+- **Required**: No
+- **Default**: `"forgejo"`
+
+
+
+### `forgejo_runners_home_dir`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Home directory of the operating system user
+
+- **Type**: `str`
+- **Required**: No
+- **Default**: `"/var/lib/forgejo"`
+
+
+
+### `forgejo_runners_config_dir`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Path to the configuration directory of the runners
+
+- **Type**: `path`
+- **Required**: No
+- **Default**: `"/etc/forgejo-runners"`
+
+
+
+### `forgejo_runners_instance`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+URL of the Forgejo instance to register the runners
+
+- **Type**: `str`
+- **Required**: No
+
+
+
+### `forgejo_runners_settings`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Dict of runners to configure
+
+The key is the name of the repository on the instance
+
+The value is a dict with a `token` key and optionally a dict of `labels`
+
+- **Type**: `dict`
+- **Required**: No
+
+
+
+
+
+
+## Usage
+
+Playbook example:
+
+```yaml
+- hosts: all
+ roles:
+ - jriou.general.forgejo_runners
+ vars:
+ forgejo_runners_instance: https://codeberg.org # FIXME
+ forgejo_runners_settings:
+ my_runner:
+ token: **redacted**
+ labels:
+ node-latest: docker://data.forgejo.org/oci/node:latest
+```
+
+Then run the playbook:
+
+```
+ansible-playbook play.yml
+```
+
+## Donate
+
+As we all love FOSS projects, you should consider [donating to
+Codeberg](https://donate.codeberg.org/), the non-profit organization behind
+Forgejo.
diff --git a/roles/forgejo_runners/defaults/main.yml b/roles/forgejo_runners/defaults/main.yml
new file mode 100644
index 0000000..391f088
--- /dev/null
+++ b/roles/forgejo_runners/defaults/main.yml
@@ -0,0 +1,39 @@
+---
+
+# Version of the runners
+#
+# - Type: str
+# - Required: No
+# - Default: 9.1.1
+forgejo_runners_version: 9.1.1
+
+# Path to the configuration directory of the runners
+#
+# - Type: path
+# - Required: No
+# - Default: /etc/forgejo-runners
+forgejo_runners_config_dir: /etc/forgejo-runners
+
+# Dict of runners to configure
+#
+# The key is the name of the repository on the instance
+#
+# The value is a dict with a `token` key and optionally a dict of `labels`
+#
+# - Type: dict
+# - Required: No
+forgejo_runners_settings: {}
+
+# Operating system user to run the runners
+#
+# - Type: str
+# - Required: No
+# - Default: forgejo
+forgejo_runners_user: forgejo
+
+# Home directory of the operating system user
+#
+# - Type: str
+# - Required: No
+# - Default: /var/lib/forgejo
+forgejo_runners_home_dir: /var/lib/forgejo
diff --git a/roles/forgejo_runners/handlers/main.yml b/roles/forgejo_runners/handlers/main.yml
new file mode 100644
index 0000000..e27abb5
--- /dev/null
+++ b/roles/forgejo_runners/handlers/main.yml
@@ -0,0 +1,13 @@
+---
+- name: Start runners
+ community.docker.docker_compose_v2:
+ project_src: "{{ forgejo_runners_config_dir }}"
+ files:
+ - docker-compose.yml
+
+- name: Restart runners
+ community.docker.docker_compose_v2:
+ project_src: "{{ forgejo_runners_config_dir }}"
+ files:
+ - docker-compose.yml
+ state: restarted
diff --git a/roles/forgejo_runners/meta/argument_specs.yml b/roles/forgejo_runners/meta/argument_specs.yml
new file mode 100644
index 0000000..39f9e0a
--- /dev/null
+++ b/roles/forgejo_runners/meta/argument_specs.yml
@@ -0,0 +1,40 @@
+---
+argument_specs:
+ main:
+ short_description: Install and configure Forgejo runners
+ description:
+ - Install and configure [Forgejo](https://forgejo.org/) runners.
+ author:
+ - jriou
+ options:
+ forgejo_runners_version:
+ description:
+ - Version of the runners
+ default: 9.1.1
+
+ forgejo_runners_user:
+ description:
+ - Operating system user to run the runners
+ default: forgejo
+
+ forgejo_runners_home_dir:
+ description:
+ - Home directory of the operating system user
+ default: /var/lib/forgejo
+
+ forgejo_runners_config_dir:
+ description:
+ - Path to the configuration directory of the runners
+ type: path
+ default: /etc/forgejo-runners
+
+ forgejo_runners_instance:
+ description:
+ - URL of the Forgejo instance to register the runners
+
+ forgejo_runners_settings:
+ description:
+ - Dict of runners to configure
+ - The key is the name of the repository on the instance
+ - The value is a dict with a `token` key and optionally a dict of `labels`
+ type: dict
diff --git a/roles/forgejo_runners/meta/main.yml b/roles/forgejo_runners/meta/main.yml
new file mode 100644
index 0000000..3f5647c
--- /dev/null
+++ b/roles/forgejo_runners/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+- role: geerlingguy.docker
diff --git a/roles/forgejo_runners/tasks/main.yml b/roles/forgejo_runners/tasks/main.yml
new file mode 100644
index 0000000..490d84b
--- /dev/null
+++ b/roles/forgejo_runners/tasks/main.yml
@@ -0,0 +1,33 @@
+---
+- name: Create user
+ ansible.builtin.user:
+ name: "{{ forgejo_runners_user }}"
+ system: true
+ password: '!'
+ home: "{{ forgejo_runners_home_dir }}"
+ create_home: false
+
+- name: Read attributes
+ ansible.builtin.getent:
+ database: passwd
+ key: "{{ forgejo_runners_user }}"
+
+- name: Register runners
+ ansible.builtin.include_tasks: register-runner.yml
+ loop: "{{ forgejo_runners_settings | dict2items }}"
+ loop_control:
+ label: "{{ item.key }}"
+
+- name: Create runners configuration
+ ansible.builtin.template:
+ src: docker-compose.yml.j2
+ dest: "{{ forgejo_runners_config_dir }}/docker-compose.yml"
+ owner: "{{ forgejo_runners_user }}"
+ group: "{{ forgejo_runners_user }}"
+ mode: "0644"
+
+- name: Start runners service
+ community.docker.docker_compose_v2:
+ project_src: "{{ forgejo_runners_config_dir }}"
+ files:
+ - docker-compose.yml
diff --git a/roles/forgejo_runners/tasks/register-runner.yml b/roles/forgejo_runners/tasks/register-runner.yml
new file mode 100644
index 0000000..ed5b84f
--- /dev/null
+++ b/roles/forgejo_runners/tasks/register-runner.yml
@@ -0,0 +1,39 @@
+---
+- name: Check variables
+ ansible.builtin.assert:
+ that:
+ - forgejo_runners_instance is defined
+ - forgejo_runners_version is defined
+ - forgejo_runners_config_dir is defined
+ - "'key' in item"
+ - "'value' in item"
+
+- name: Create runner subdirectory
+ ansible.builtin.file:
+ path: "{{ forgejo_runners_config_dir }}/{{ item.key }}"
+ state: directory
+ mode: "0755"
+ owner: "{{ forgejo_runners_user }}"
+ group: "{{ forgejo_runners_user }}"
+
+- name: Register runner
+ ansible.builtin.command:
+ cmd: >-
+ docker run
+ -v /var/run/docker.sock:/var/run/docker.sock
+ -v {{ forgejo_runners_config_dir }}/{{ item.key }}:/data
+ --rm
+ --user {{ ansible_facts.getent_passwd[forgejo_runners_user][1] }}:{{ ansible_facts.getent_passwd[forgejo_runners_user][2] }}
+ code.forgejo.org/forgejo/runner:{{ forgejo_runners_version }}
+ forgejo-runner register --no-interactive
+ --token {{ item.value.token }}
+ --name {{ item.key }}
+ --instance {{ forgejo_runners_instance }}
+ creates: "{{ forgejo_runners_config_dir }}/{{ item.key }}/.runner"
+ notify: Start runners
+
+- name: Create runner configuration
+ ansible.builtin.template:
+ src: config.yml.j2
+ dest: "{{ forgejo_runners_config_dir }}/{{ item.key }}/config.yml"
+ notify: Restart runners
diff --git a/roles/forgejo_runners/templates/config.yml.j2 b/roles/forgejo_runners/templates/config.yml.j2
new file mode 100644
index 0000000..39eb084
--- /dev/null
+++ b/roles/forgejo_runners/templates/config.yml.j2
@@ -0,0 +1,44 @@
+{{ ansible_managed | comment }}
+log:
+ level: info
+ job_level: info
+
+runner:
+ file: .runner
+ capacity: 1
+ timeout: 3h
+ shutdown_timeout: 3h
+ insecure: false
+ fetch_timeout: 5s
+ fetch_interval: 2s
+ report_interval: 1s
+{% if item.value.labels | default({}) %}
+ labels:
+{% for label_name, label_value in item.value.labels.items() %}
+ - "{{ label_name }}:{{ label_value }}"
+{% endfor %}
+{% endif %}
+
+cache:
+ enabled: true
+ port: 0
+ dir: ""
+ external_server: ""
+ secret: ""
+ host: ""
+ proxy_port: 0
+ actions_cache_url_override: ""
+
+container:
+ network: ""
+ enable_ipv6: false
+ privileged: false
+ options:
+ workdir_parent:
+ valid_volumes: []
+ docker_host: "-"
+ force_pull: false
+ force_rebuild: false
+
+host:
+ workdir_parent:
diff --git a/roles/forgejo_runners/templates/docker-compose.yml.j2 b/roles/forgejo_runners/templates/docker-compose.yml.j2
new file mode 100644
index 0000000..3bf2eb6
--- /dev/null
+++ b/roles/forgejo_runners/templates/docker-compose.yml.j2
@@ -0,0 +1,27 @@
+---
+{{ ansible_managed | comment }}
+services:
+ docker:
+ image: docker:dind
+ privileged: true
+ restart: always
+ volumes:
+ - certs:/certs
+
+{% for runner in forgejo_runners_settings %}
+ runner-{{ runner }}:
+ image: code.forgejo.org/forgejo/runner:{{ forgejo_runners_version }}
+ user: {{ ansible_facts.getent_passwd[forgejo_runners_user][1] }}:{{ ansible_facts.getent_passwd[forgejo_runners_user][2] }}
+ environment:
+ DOCKER_HOST: tcp://docker:2376
+ DOCKER_TLS_VERIFY: 1
+ DOCKER_CERT_PATH: /certs/client
+ restart: always
+ volumes:
+ - {{ forgejo_runners_config_dir }}/{{ runner }}:/data
+ - certs:/certs
+ command: 'forgejo-runner --config config.yml daemon'
+{% endfor %}
+
+volumes:
+ certs:
diff --git a/roles/galene/README.md b/roles/galene/README.md
new file mode 100644
index 0000000..16b7104
--- /dev/null
+++ b/roles/galene/README.md
@@ -0,0 +1,229 @@
+# Ansible Role Galene
+
+Install and configure [Galene](https://galene.org) videoconference server.
+
+## Table of content
+
+
+* [Role variables](#variables)
+ * [`galene_version`](#variable-galene_version)
+ * [`galene_http_port`](#variable-galene_http_port)
+ * [`galene_turn`](#variable-galene_turn)
+ * [`galene_user`](#variable-galene_user)
+ * [`galene_group`](#variable-galene_group)
+ * [`galene_base_directory`](#variable-galene_base_directory)
+ * [`galene_data_directory`](#variable-galene_data_directory)
+ * [`galene_groups_directory`](#variable-galene_groups_directory)
+ * [`galene_recording_directory`](#variable-galene_recording_directory)
+ * [`galene_static_directory`](#variable-galene_static_directory)
+ * [`galene_domain`](#variable-galene_domain)
+ * [`galene_config`](#variable-galene_config)
+ * [`galene_groups`](#variable-galene_groups)
+
+
+
+## Role variables
+
+The following variables can be configured for this role:
+
+| Variable | Type | Required | Default | Description (abstract) |
+|----------|------|----------|---------|------------------------|
+| `galene_version` | `str` | No | `"galene-1.0"` | Reference (branch or tag) of the Galene git repository. |
+| `galene_http_port` | `int` | No | `443` | Port to listen. |
+| `galene_turn` | `str` | No | `":1194"` | TURN address. |
+| `galene_user` | `str` | No | `"galene"` | Operating system user to run the service. |
+| `galene_group` | `str` | No | `"galene"` | Operating system group to run the service. |
+| `galene_base_directory` | `path` | No | `"/var/lib/galene"` | Path to the base directory. |
+| `galene_data_directory` | `path` | No | `"{{ galene_base_directory }}/data"` | Path to the data directory. |
+| `galene_groups_directory` | `path` | No | `"{{ galene_base_directory }}/groups"` | Path to the groups directory. |
+| `galene_recording_directory` | `path` | No | `"{{ galene_base_directory }}/recordings"` | Path to the recordings directory. |
+| `galene_static_directory` | `path` | No | `"{{ galene_base_directory }}/static"` | Path to the static directory. |
+| `galene_domain` | `str` | No | N/A | Domain name.
Used to generate TLS certificates. |
+| `galene_config` | `dict` | No | N/A | Custom settings.
Key is the name of the setting.
Value is the value of the setting.
See [The global configuration file](https://galene.org/galene.html#the-global-configuration-file). |
+| `galene_groups` | `dict` | No | N/A | Dict of groups.
Key is the group name.
Value is the group definition.
See [Group definitions](https://galene.org/galene.html#group-definitions). |
+
+### `galene_version`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Reference (branch or tag) of the Galene git repository.
+
+- **Type**: `str`
+- **Required**: No
+- **Default**: `"galene-1.0"`
+
+
+
+### `galene_http_port`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Port to listen.
+
+- **Type**: `int`
+- **Required**: No
+- **Default**: `443`
+
+
+
+### `galene_turn`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+TURN address.
+
+- **Type**: `str`
+- **Required**: No
+- **Default**: `":1194"`
+
+
+
+### `galene_user`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Operating system user to run the service.
+
+- **Type**: `str`
+- **Required**: No
+- **Default**: `"galene"`
+
+
+
+### `galene_group`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Operating system group to run the service.
+
+- **Type**: `str`
+- **Required**: No
+- **Default**: `"galene"`
+
+
+
+### `galene_base_directory`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Path to the base directory.
+
+- **Type**: `path`
+- **Required**: No
+- **Default**: `"/var/lib/galene"`
+
+
+
+### `galene_data_directory`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Path to the data directory.
+
+- **Type**: `path`
+- **Required**: No
+- **Default**: `"{{ galene_base_directory }}/data"`
+
+
+
+### `galene_groups_directory`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Path to the groups directory.
+
+- **Type**: `path`
+- **Required**: No
+- **Default**: `"{{ galene_base_directory }}/groups"`
+
+
+
+### `galene_recording_directory`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Path to the recordings directory.
+
+- **Type**: `path`
+- **Required**: No
+- **Default**: `"{{ galene_base_directory }}/recordings"`
+
+
+
+### `galene_static_directory`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Path to the static directory.
+
+- **Type**: `path`
+- **Required**: No
+- **Default**: `"{{ galene_base_directory }}/static"`
+
+
+
+### `galene_domain`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Domain name.
+
+Used to generate TLS certificates.
+
+- **Type**: `str`
+- **Required**: No
+
+
+
+### `galene_config`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Custom settings.
+
+Key is the name of the setting.
+
+Value is the value of the setting.
+
+See [The global configuration file](https://galene.org/galene.html#the-global-configuration-file).
+
+- **Type**: `dict`
+- **Required**: No
+
+
+
+### `galene_groups`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Dict of groups.
+
+Key is the group name.
+
+Value is the group definition.
+
+See [Group definitions](https://galene.org/galene.html#group-definitions).
+
+- **Type**: `dict`
+- **Required**: No
+
+
+
+
+
+
+## Usage
+
+Playbook example:
+
+```yaml
+- hosts: all
+ roles:
+ - jriou.general.galene
+```
+
+Then run the playbook:
+
+```
+ansible-playbook play.yml
+```
diff --git a/roles/galene/defaults/main.yml b/roles/galene/defaults/main.yml
new file mode 100644
index 0000000..7019a4a
--- /dev/null
+++ b/roles/galene/defaults/main.yml
@@ -0,0 +1,95 @@
+---
+
+# Reference (branch or tag) of the Galene git repository.
+#
+# - Type: str
+# - Required: No
+# - Default: galene-1.0
+galene_version: galene-1.0
+
+# Port to listen.
+#
+# - Type: int
+# - Required: No
+# - Default: 443
+galene_http_port: 443
+
+# TURN address.
+#
+# - Type: str
+# - Required: No
+# - Default: :1194
+galene_turn: ":1194"
+
+# Operating system user to run the service.
+#
+# - Type: str
+# - Required: No
+# - Default: galene
+galene_user: galene
+
+# Operating system group to run the service.
+#
+# - Type: str
+# - Required: No
+# - Default: galene
+galene_group: galene
+
+# Path to the base directory.
+#
+# - Type: path
+# - Required: No
+# - Default: /var/lib/galene
+galene_base_directory: /var/lib/galene
+
+# Path to the data directory.
+#
+# - Type: path
+# - Required: No
+# - Default: {{ galene_base_directory }}/data
+galene_data_directory: "{{ galene_base_directory }}/data"
+
+# Path to the groups directory.
+#
+# - Type: path
+# - Required: No
+# - Default: {{ galene_base_directory }}/groups
+galene_groups_directory: "{{ galene_base_directory }}/groups"
+
+# Path to the recordings directory.
+#
+# - Type: path
+# - Required: No
+# - Default: {{ galene_base_directory }}/recordings
+galene_recording_directory: "{{ galene_base_directory }}/recordings"
+
+# Path to the static directory.
+#
+# - Type: path
+# - Required: No
+# - Default: {{ galene_base_directory }}/static
+galene_static_directory: "{{ galene_base_directory }}/static"
+
+# Custom settings.
+#
+# Key is the name of the setting.
+#
+# Value is the value of the setting.
+#
+# See .
+#
+# - Type: dict
+# - Required: No
+galene_config: {}
+
+# Dict of groups.
+#
+# Key is the group name.
+#
+# Value is the group definition.
+#
+# See .
+#
+# - Type: dict
+# - Required: No
+galene_groups: {}
diff --git a/roles/galene/handlers/main.yml b/roles/galene/handlers/main.yml
new file mode 100644
index 0000000..24198bf
--- /dev/null
+++ b/roles/galene/handlers/main.yml
@@ -0,0 +1,9 @@
+---
+- name: Reload systemd
+ ansible.builtin.systemd_service:
+ daemon_reload: true
+
+- name: Restart galene
+ ansible.builtin.service:
+ name: galene
+ state: restarted
diff --git a/roles/galene/meta/argument_specs.yml b/roles/galene/meta/argument_specs.yml
new file mode 100644
index 0000000..aa3448c
--- /dev/null
+++ b/roles/galene/meta/argument_specs.yml
@@ -0,0 +1,85 @@
+---
+argument_specs:
+ main:
+ short_description: Install and configure Galene videoconference server
+ description:
+ - Install and configure [Galene](https://galene.org/) videoconference server
+ author:
+ - jriou
+ options:
+ galene_version:
+ description:
+ - Reference (branch or tag) of the Galene git repository.
+ default: galene-1.0
+
+ galene_http_port:
+ description:
+ - Port to listen.
+ type: int
+ default: 443
+
+ galene_turn:
+ description:
+ - TURN address.
+ default: ":1194"
+
+ galene_user:
+ description:
+ - Operating system user to run the service.
+ default: galene
+
+ galene_group:
+ description:
+ - Operating system group to run the service.
+ default: galene
+
+ galene_base_directory:
+ description:
+ - Path to the base directory.
+ type: path
+ default: /var/lib/galene
+
+ galene_data_directory:
+ description:
+ - Path to the data directory.
+ type: path
+ default: "{{ galene_base_directory }}/data"
+
+ galene_groups_directory:
+ description:
+ - Path to the groups directory.
+ type: path
+ default: "{{ galene_base_directory }}/groups"
+
+ galene_recording_directory:
+ description:
+ - Path to the recordings directory.
+ type: path
+ default: "{{ galene_base_directory }}/recordings"
+
+ galene_static_directory:
+ description:
+ - Path to the static directory.
+ type: path
+ default: "{{ galene_base_directory }}/static"
+
+ galene_domain:
+ description:
+ - Domain name.
+ - Used to generate TLS certificates.
+
+ galene_config:
+ description:
+ - Custom settings.
+ - Key is the name of the setting.
+ - Value is the value of the setting.
+ - See [The global configuration file](https://galene.org/galene.html#the-global-configuration-file).
+ type: dict
+
+ galene_groups:
+ description:
+ - Dict of groups.
+ - Key is the group name.
+ - Value is the group definition.
+ - See [Group definitions](https://galene.org/galene.html#group-definitions).
+ type: dict
diff --git a/roles/galene/meta/main.yml b/roles/galene/meta/main.yml
new file mode 100644
index 0000000..affff51
--- /dev/null
+++ b/roles/galene/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - role: golang
diff --git a/roles/galene/molecule/default/converge.yml b/roles/galene/molecule/default/converge.yml
new file mode 100644
index 0000000..bcc2b3b
--- /dev/null
+++ b/roles/galene/molecule/default/converge.yml
@@ -0,0 +1,5 @@
+---
+- name: Converge
+ hosts: molecule
+ roles:
+ - galene
diff --git a/roles/galene/molecule/default/create.yml b/roles/galene/molecule/default/create.yml
new file mode 100644
index 0000000..6049019
--- /dev/null
+++ b/roles/galene/molecule/default/create.yml
@@ -0,0 +1,18 @@
+---
+- name: Create containers
+ hosts: localhost
+ gather_facts: false
+ tasks:
+ - name: Create containers
+ containers.podman.podman_container:
+ hostname: "{{ item }}"
+ name: "{{ item }}"
+ image: "{{ hostvars[item]['container_image'] }}"
+ state: started
+ loop: "{{ groups['molecule'] }}"
+
+ - name: Wait for containers to be ready
+ ansible.builtin.wait_for_connection:
+ timeout: 300
+ delegate_to: "{{ item }}"
+ loop: "{{ groups['molecule'] }}"
diff --git a/roles/galene/molecule/default/destroy.yml b/roles/galene/molecule/default/destroy.yml
new file mode 100644
index 0000000..b0596be
--- /dev/null
+++ b/roles/galene/molecule/default/destroy.yml
@@ -0,0 +1,11 @@
+---
+- name: Destroy container instances
+ hosts: localhost
+ gather_facts: false
+ tasks:
+ - name: Remove containers
+ containers.podman.podman_container:
+ name: "{{ item }}"
+ state: absent
+ loop: "{{ groups['molecule'] }}"
+ failed_when: false
diff --git a/roles/galene/molecule/default/inventory/hosts.yml b/roles/galene/molecule/default/inventory/hosts.yml
new file mode 100644
index 0000000..f107edb
--- /dev/null
+++ b/roles/galene/molecule/default/inventory/hosts.yml
@@ -0,0 +1,12 @@
+---
+molecule:
+ hosts:
+ debian11:
+ ansible_connection: containers.podman.podman
+ container_image: docker.io/geerlingguy/docker-debian11-ansible:latest
+ debian12:
+ ansible_connection: containers.podman.podman
+ container_image: docker.io/geerlingguy/docker-debian12-ansible:latest
+ debian13:
+ ansible_connection: containers.podman.podman
+ container_image: docker.io/geerlingguy/docker-debian13-ansible:latest
diff --git a/roles/galene/molecule/default/molecule.yml b/roles/galene/molecule/default/molecule.yml
new file mode 100644
index 0000000..af82e39
--- /dev/null
+++ b/roles/galene/molecule/default/molecule.yml
@@ -0,0 +1,26 @@
+---
+ansible:
+ executor:
+ args:
+ ansible_playbook:
+ - --inventory=inventory/
+ env:
+ ANSIBLE_ROLES_PATH: ../../../../roles
+ playbooks:
+ create: create.yml
+ converge: converge.yml
+ verify: verify.yml
+ destroy: destroy.yml
+
+dependency:
+ name: galaxy
+ options:
+ requirements-file: ${MOLECULE_SCENARIO_DIRECTORY}/requirements.yml
+
+scenario:
+ test_sequence:
+ - create
+ - converge
+ - idempotence
+ - verify
+ - destroy
diff --git a/roles/galene/molecule/default/requirements.yml b/roles/galene/molecule/default/requirements.yml
new file mode 100644
index 0000000..25a97a4
--- /dev/null
+++ b/roles/galene/molecule/default/requirements.yml
@@ -0,0 +1,3 @@
+---
+collections:
+ - name: containers.podman
diff --git a/roles/galene/molecule/default/verify.yml b/roles/galene/molecule/default/verify.yml
new file mode 100644
index 0000000..fc4053c
--- /dev/null
+++ b/roles/galene/molecule/default/verify.yml
@@ -0,0 +1,8 @@
+---
+- name: Verify
+ hosts: molecule
+ tasks:
+ - name: Check service
+ ansible.builtin.uri:
+ url: https://localhost:443
+ validate_certs: false
diff --git a/roles/galene/tasks/main.yml b/roles/galene/tasks/main.yml
new file mode 100644
index 0000000..d619a99
--- /dev/null
+++ b/roles/galene/tasks/main.yml
@@ -0,0 +1,117 @@
+---
+# TODO: install in block
+- name: Install requirements
+ ansible.builtin.apt:
+ name: git
+ update_cache: true
+
+- name: Clone source code
+ ansible.builtin.git:
+ repo: https://github.com/jech/galene
+ dest: /opt/galene
+ version: "{{ galene_version }}"
+
+- name: Compile
+ ansible.builtin.command:
+ chdir: /opt/galene
+ cmd: go build -ldflags='-s -w'
+ creates: /opt/galene/galene
+ environment:
+ CGO_ENABLED: "0"
+ PATH: /usr/local/go/bin
+
+- name: Install
+ ansible.builtin.copy:
+ remote_src: true
+ src: /opt/galene/galene
+ dest: /usr/local/bin/galene
+ owner: root
+ group: root
+ mode: "0755"
+# TODO End of install in block
+
+- name: Create user
+ ansible.builtin.user:
+ name: "{{ galene_user }}"
+ system: true
+ password: '!'
+ home: "{{ galene_base_directory }}"
+ create_home: false
+
+- name: Create directories
+ ansible.builtin.file:
+ path: "{{ item }}"
+ state: directory
+ owner: "{{ galene_user }}"
+ group: "{{ galene_group }}"
+ mode: "0755"
+ loop:
+ - "{{ galene_base_directory }}"
+ - "{{ galene_data_directory }}"
+ - "{{ galene_groups_directory }}"
+ - "{{ galene_recording_directory }}"
+ - "{{ galene_static_directory }}"
+
+- name: Copy static directory
+ ansible.builtin.copy:
+ src: /opt/galene/static/
+ dest: "{{ galene_static_directory }}/"
+ remote_src: true
+ mode: "0755"
+ owner: "{{ galene_user }}"
+ group: "{{ galene_group }}"
+ when: galene_static_directory != "/opt/galene/static"
+
+- name: Configure groups
+ ansible.builtin.copy:
+ content: "{{ item.value | to_json }}"
+ dest: "{{ galene_groups_directory }}/{{ item.key }}.json"
+ owner: "{{ galene_user }}"
+ group: "{{ galene_group }}"
+ mode: "0600"
+ loop: "{{ galene_groups | dict2items }}"
+ loop_control:
+ label: "{{ item.key }}"
+ notify: Restart galene
+
+- name: Create global configuration
+ ansible.builtin.copy:
+ content: "{{ galene_config | to_json }}"
+ dest: "{{ galene_data_directory }}/config.json"
+ owner: "{{ galene_user }}"
+ group: "{{ galene_group }}"
+ mode: "0600"
+ notify: Restart galene
+
+- name: Configure TLS certificates
+ when: galene_domain is defined
+ ansible.builtin.copy:
+ remote_src: true
+ src: "{{ item.src }}"
+ dest: "{{ item.dest }}"
+ owner: "{{ galene_user }}"
+ group: "{{ galene_group }}"
+ loop:
+ - src: "/etc/letsencrypt/live/{{ galene_domain }}/fullchain.pem"
+ dest: "{{ galene_data_directory }}/cert.pem"
+ mode: "0644"
+ - src: "/etc/letsencrypt/live/{{ galene_domain }}/privkey.pem"
+ dest: "{{ galene_data_directory }}/key.pem"
+ mode: "0600"
+
+- name: Create service
+ ansible.builtin.template:
+ src: galene.service.j2
+ dest: /etc/systemd/system/galene.service
+ mode: "0644"
+ owner: root
+ group: root
+ notify:
+ - Reload systemd
+ - Restart galene
+
+- name: Start service
+ ansible.builtin.service:
+ name: galene
+ state: started
+ enabled: true
diff --git a/roles/galene/templates/galene.service.j2 b/roles/galene/templates/galene.service.j2
new file mode 100644
index 0000000..5a90399
--- /dev/null
+++ b/roles/galene/templates/galene.service.j2
@@ -0,0 +1,19 @@
+{{ ansible_managed | comment }}
+
+[Unit]
+Description=Galene
+After=network.target
+
+[Service]
+Type=simple
+WorkingDirectory={{ galene_base_directory }}
+User={{ galene_user }}
+Group={{ galene_group }}
+{% if galene_http_port < 1024 %}
+AmbientCapabilities=CAP_NET_BIND_SERVICE
+{% endif %}
+ExecStart=/usr/local/bin/galene -http :{{ galene_http_port }} -data {{ galene_data_directory }} -groups {{ galene_groups_directory }} -recordings {{ galene_recording_directory }} -static {{ galene_static_directory }} -turn "{{ galene_turn }}"
+LimitNOFILE=65536
+
+[Install]
+WantedBy=multi-user.target
diff --git a/roles/golang/README.md b/roles/golang/README.md
new file mode 100644
index 0000000..30e5eb5
--- /dev/null
+++ b/roles/golang/README.md
@@ -0,0 +1,50 @@
+# Ansible Role Go
+
+Install [Go](https://go.dev/).
+
+## Table of content
+
+
+* [Role variables](#variables)
+ * [`golang_version`](#variable-golang_version)
+
+
+
+## Role variables
+
+The following variables can be configured for this role:
+
+| Variable | Type | Required | Default | Description (abstract) |
+|----------|------|----------|---------|------------------------|
+| `golang_version` | `str` | No | `"1.25.4"` | Version to install. |
+
+### `golang_version`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Version to install.
+
+- **Type**: `str`
+- **Required**: No
+- **Default**: `"1.25.4"`
+
+
+
+
+
+
+## Usage
+
+Playbook example:
+
+```yaml
+- hosts: all
+ roles:
+ - jriou.general.golang
+```
+
+Then run the playbook:
+
+```
+ansible-playbook play.yml
+```
diff --git a/roles/golang/defaults/main.yml b/roles/golang/defaults/main.yml
new file mode 100644
index 0000000..b127ea8
--- /dev/null
+++ b/roles/golang/defaults/main.yml
@@ -0,0 +1,8 @@
+---
+
+# Version to install.
+#
+# - Type: str
+# - Required: No
+# - Default: 1.25.4
+golang_version: 1.25.4
diff --git a/roles/golang/meta/argument_specs.yml b/roles/golang/meta/argument_specs.yml
new file mode 100644
index 0000000..67efd49
--- /dev/null
+++ b/roles/golang/meta/argument_specs.yml
@@ -0,0 +1,13 @@
+---
+argument_specs:
+ main:
+ short_description: Install Go
+ description:
+ - Install [Go](https://go.dev/).
+ author:
+ - jriou
+ options:
+ golang_version:
+ description:
+ - Version to install.
+ default: 1.25.4
diff --git a/roles/golang/molecule/default/converge.yml b/roles/golang/molecule/default/converge.yml
new file mode 100644
index 0000000..aec4e7b
--- /dev/null
+++ b/roles/golang/molecule/default/converge.yml
@@ -0,0 +1,7 @@
+---
+- name: Converge
+ hosts: molecule
+ roles:
+ - golang
+ vars:
+ golang_version: 1.25.4
diff --git a/roles/golang/molecule/default/create.yml b/roles/golang/molecule/default/create.yml
new file mode 100644
index 0000000..6049019
--- /dev/null
+++ b/roles/golang/molecule/default/create.yml
@@ -0,0 +1,18 @@
+---
+- name: Create containers
+ hosts: localhost
+ gather_facts: false
+ tasks:
+ - name: Create containers
+ containers.podman.podman_container:
+ hostname: "{{ item }}"
+ name: "{{ item }}"
+ image: "{{ hostvars[item]['container_image'] }}"
+ state: started
+ loop: "{{ groups['molecule'] }}"
+
+ - name: Wait for containers to be ready
+ ansible.builtin.wait_for_connection:
+ timeout: 300
+ delegate_to: "{{ item }}"
+ loop: "{{ groups['molecule'] }}"
diff --git a/roles/golang/molecule/default/destroy.yml b/roles/golang/molecule/default/destroy.yml
new file mode 100644
index 0000000..b0596be
--- /dev/null
+++ b/roles/golang/molecule/default/destroy.yml
@@ -0,0 +1,11 @@
+---
+- name: Destroy container instances
+ hosts: localhost
+ gather_facts: false
+ tasks:
+ - name: Remove containers
+ containers.podman.podman_container:
+ name: "{{ item }}"
+ state: absent
+ loop: "{{ groups['molecule'] }}"
+ failed_when: false
diff --git a/roles/golang/molecule/default/inventory/hosts.yml b/roles/golang/molecule/default/inventory/hosts.yml
new file mode 100644
index 0000000..f107edb
--- /dev/null
+++ b/roles/golang/molecule/default/inventory/hosts.yml
@@ -0,0 +1,12 @@
+---
+molecule:
+ hosts:
+ debian11:
+ ansible_connection: containers.podman.podman
+ container_image: docker.io/geerlingguy/docker-debian11-ansible:latest
+ debian12:
+ ansible_connection: containers.podman.podman
+ container_image: docker.io/geerlingguy/docker-debian12-ansible:latest
+ debian13:
+ ansible_connection: containers.podman.podman
+ container_image: docker.io/geerlingguy/docker-debian13-ansible:latest
diff --git a/roles/golang/molecule/default/molecule.yml b/roles/golang/molecule/default/molecule.yml
new file mode 100644
index 0000000..af82e39
--- /dev/null
+++ b/roles/golang/molecule/default/molecule.yml
@@ -0,0 +1,26 @@
+---
+ansible:
+ executor:
+ args:
+ ansible_playbook:
+ - --inventory=inventory/
+ env:
+ ANSIBLE_ROLES_PATH: ../../../../roles
+ playbooks:
+ create: create.yml
+ converge: converge.yml
+ verify: verify.yml
+ destroy: destroy.yml
+
+dependency:
+ name: galaxy
+ options:
+ requirements-file: ${MOLECULE_SCENARIO_DIRECTORY}/requirements.yml
+
+scenario:
+ test_sequence:
+ - create
+ - converge
+ - idempotence
+ - verify
+ - destroy
diff --git a/roles/golang/molecule/default/requirements.yml b/roles/golang/molecule/default/requirements.yml
new file mode 100644
index 0000000..25a97a4
--- /dev/null
+++ b/roles/golang/molecule/default/requirements.yml
@@ -0,0 +1,3 @@
+---
+collections:
+ - name: containers.podman
diff --git a/roles/golang/molecule/default/verify.yml b/roles/golang/molecule/default/verify.yml
new file mode 100644
index 0000000..03642db
--- /dev/null
+++ b/roles/golang/molecule/default/verify.yml
@@ -0,0 +1,15 @@
+---
+- name: Verify
+ hosts: molecule
+ vars:
+ golang_version: 1.25.4
+ tasks:
+ - name: Get version
+ ansible.builtin.command:
+ cmd: /usr/local/go/bin/go version
+ register: golang_version_cmd
+
+ - name: Compare versions
+ ansible.builtin.assert:
+ that:
+ - golang_version_cmd.stdout | regex_search('^go version go' + golang_version + ' linux/amd64') != ""
diff --git a/roles/golang/tasks/main.yml b/roles/golang/tasks/main.yml
new file mode 100644
index 0000000..6af3007
--- /dev/null
+++ b/roles/golang/tasks/main.yml
@@ -0,0 +1,7 @@
+---
+- name: Install
+ ansible.builtin.unarchive:
+ src: "https://go.dev/dl/go{{ golang_version }}.linux-amd64.tar.gz"
+ dest: /usr/local
+ remote_src: true
+ creates: /usr/local/go
diff --git a/roles/navidrome/.gitignore b/roles/navidrome/.gitignore
new file mode 100644
index 0000000..5ceb386
--- /dev/null
+++ b/roles/navidrome/.gitignore
@@ -0,0 +1 @@
+venv
diff --git a/roles/navidrome/README.md b/roles/navidrome/README.md
new file mode 100644
index 0000000..24afac6
--- /dev/null
+++ b/roles/navidrome/README.md
@@ -0,0 +1,221 @@
+# Ansible Role Navidrome
+
+Ansible role to manage a [Navidrome](https://github.com/navidrome/navidrome) instance.
+
+## Table of Content
+
+
+* [Role variables](#variables)
+ * [`navidrome_version`](#variable-navidrome_version)
+ * [`navidrome_arch`](#variable-navidrome_arch)
+ * [`navidrome_user`](#variable-navidrome_user)
+ * [`navidrome_group`](#variable-navidrome_group)
+ * [`navidrome_music_folder`](#variable-navidrome_music_folder)
+ * [`navidrome_data_folder`](#variable-navidrome_data_folder)
+ * [`navidrome_cache_folder`](#variable-navidrome_cache_folder)
+ * [`navidrome_manage_iptables`](#variable-navidrome_manage_iptables)
+ * [`navidrome_allowed_sources`](#variable-navidrome_allowed_sources)
+ * [`navidrome_address`](#variable-navidrome_address)
+ * [`navidrome_port`](#variable-navidrome_port)
+ * [`navidrome_enable_insights_collector`](#variable-navidrome_enable_insights_collector)
+ * [`navidrome_base_url`](#variable-navidrome_base_url)
+
+
+
+## Role variables
+
+The following variables can be configured for this role:
+
+| Variable | Type | Required | Default | Description (abstract) |
+|----------|------|----------|---------|------------------------|
+| `navidrome_version` | `str` | No | `"0.54.5"` | Version of the debian package. |
+| `navidrome_arch` | `str` | No | `"amd64"` | Architecture of the debian package. |
+| `navidrome_user` | `str` | No | `"navidrome"` | Operating system user to run the service. |
+| `navidrome_group` | `str` | No | `"navidrome"` | Operating system group to run the service. |
+| `navidrome_music_folder` | `path` | No | `"/opt/navidrome/music"` | Path to the music folder. |
+| `navidrome_data_folder` | `path` | No | `"/var/lib/navidrome"` | Path to the data directory. |
+| `navidrome_cache_folder` | `path` | No | `"{{ navidrome_data_folder }}/cache"` | Path to the cache folder. |
+| `navidrome_manage_iptables` | `bool` | No | `false` | Configure iptables rules |
+| `navidrome_allowed_sources` | `list` | No | N/A | List of IP ranges to allow when `navidrome_manage_iptables` is enabled |
+| `navidrome_address` | `str` | No | `"localhost"` | Address to listen. |
+| `navidrome_port` | `int` | No | `4533` | Port to listen |
+| `navidrome_enable_insights_collector` | `bool` | No | `false` | Enable the insights collector. |
+| `navidrome_base_url` | `str` | No | N/A | Base URL of the web interface. |
+
+### `navidrome_version`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Version of the debian package.
+
+- **Type**: `str`
+- **Required**: No
+- **Default**: `"0.54.5"`
+
+
+
+### `navidrome_arch`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Architecture of the debian package.
+
+- **Type**: `str`
+- **Required**: No
+- **Default**: `"amd64"`
+
+
+
+### `navidrome_user`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Operating system user to run the service.
+
+- **Type**: `str`
+- **Required**: No
+- **Default**: `"navidrome"`
+
+
+
+### `navidrome_group`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Operating system group to run the service.
+
+- **Type**: `str`
+- **Required**: No
+- **Default**: `"navidrome"`
+
+
+
+### `navidrome_music_folder`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Path to the music folder.
+
+- **Type**: `path`
+- **Required**: No
+- **Default**: `"/opt/navidrome/music"`
+
+
+
+### `navidrome_data_folder`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Path to the data directory.
+
+- **Type**: `path`
+- **Required**: No
+- **Default**: `"/var/lib/navidrome"`
+
+
+
+### `navidrome_cache_folder`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Path to the cache folder.
+
+- **Type**: `path`
+- **Required**: No
+- **Default**: `"{{ navidrome_data_folder }}/cache"`
+
+
+
+### `navidrome_manage_iptables`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Configure iptables rules
+
+- **Type**: `bool`
+- **Required**: No
+- **Default**: `false`
+
+
+
+### `navidrome_allowed_sources`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+List of IP ranges to allow when `navidrome_manage_iptables` is enabled
+
+- **Type**: `list`
+- **Required**: No
+
+
+
+### `navidrome_address`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Address to listen.
+
+- **Type**: `str`
+- **Required**: No
+- **Default**: `"localhost"`
+
+
+
+### `navidrome_port`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Port to listen
+
+- **Type**: `int`
+- **Required**: No
+- **Default**: `4533`
+
+
+
+### `navidrome_enable_insights_collector`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Enable the insights collector.
+
+- **Type**: `bool`
+- **Required**: No
+- **Default**: `false`
+
+
+
+### `navidrome_base_url`
+
+[*⇑ Back to ToC ⇑*](#toc)
+
+Base URL of the web interface.
+
+- **Type**: `str`
+- **Required**: No
+
+
+
+
+
+
+## Usage
+
+Playbook example:
+
+```yaml
+- hosts: all
+ roles:
+ - jriou.general.navidrome
+```
+
+Then run the playbook:
+
+```
+ansible-playbook play.yml
+```
+
+## Donate
+
+As we all love FOSS, you should consider sponsoring the
+[Navidrome](https://github.com/navidrome/navidrome) project.
diff --git a/roles/navidrome/defaults/main.yml b/roles/navidrome/defaults/main.yml
new file mode 100644
index 0000000..a30acb5
--- /dev/null
+++ b/roles/navidrome/defaults/main.yml
@@ -0,0 +1,84 @@
+---
+
+# Version of the debian package.
+#
+# - Type: str
+# - Required: No
+# - Default: 0.54.5
+navidrome_version: 0.54.5
+
+# Architecture of the debian package.
+#
+# - Type: str
+# - Required: No
+# - Default: amd64
+navidrome_arch: amd64
+
+# Operating system user to run the service.
+#
+# - Type: str
+# - Required: No
+# - Default: navidrome
+navidrome_user: navidrome
+
+# Operating system group to run the service.
+#
+# - Type: str
+# - Required: No
+# - Default: navidrome
+navidrome_group: navidrome
+
+# Path to the music folder.
+#
+# - Type: path
+# - Required: No
+# - Default: /opt/navidrome/music
+navidrome_music_folder: /opt/navidrome/music
+
+# Path to the data directory.
+#
+# - Type: path
+# - Required: No
+# - Default: /var/lib/navidrome
+navidrome_data_folder: /var/lib/navidrome
+
+# Path to the cache folder.
+#
+# - Type: path
+# - Required: No
+# - Default: {{ navidrome_data_folder }}/cache
+navidrome_cache_folder: "{{ navidrome_data_folder }}/cache"
+
+# Configure iptables rules
+#
+# - Type: bool
+# - Required: No
+# - Default: false
+navidrome_manage_iptables: false
+
+# List of IP ranges to allow when `navidrome_manage_iptables` is enabled
+#
+# - Type: list
+# - Required: No
+navidrome_allowed_sources: []
+
+# Address to listen.
+#
+# - Type: str
+# - Required: No
+# - Default: localhost
+navidrome_address: localhost
+
+# Port to listen
+#
+# - Type: int
+# - Required: No
+# - Default: 4533
+navidrome_port: 4533
+
+# Enable the insights collector.
+#
+# - Type: bool
+# - Required: No
+# - Default: false
+navidrome_enable_insights_collector: false
diff --git a/roles/navidrome/handlers/main.yml b/roles/navidrome/handlers/main.yml
new file mode 100644
index 0000000..152799e
--- /dev/null
+++ b/roles/navidrome/handlers/main.yml
@@ -0,0 +1,14 @@
+---
+- name: Reload systemd
+ ansible.builtin.systemd_service:
+ daemon_reload: true
+
+- name: Restart navidrome
+ ansible.builtin.service:
+ name: navidrome
+ state: restarted
+
+- name: Save iptables
+ ansible.builtin.command:
+ cmd: netfilter-persistent save
+ changed_when: true
diff --git a/roles/navidrome/meta/argument_specs.yml b/roles/navidrome/meta/argument_specs.yml
new file mode 100644
index 0000000..9c1146e
--- /dev/null
+++ b/roles/navidrome/meta/argument_specs.yml
@@ -0,0 +1,78 @@
+---
+argument_specs:
+ main:
+ short_description: Install Navidrome.
+ description:
+ - Install [Navidrome](https://github.com/navidrome/navidrome).
+ author:
+ - jriou
+ options:
+ navidrome_version:
+ description:
+ - Version of the debian package.
+ default: 0.54.5
+
+ navidrome_arch:
+ description:
+ - Architecture of the debian package.
+ default: amd64
+
+ navidrome_user:
+ description:
+ - Operating system user to run the service.
+ default: navidrome
+
+ navidrome_group:
+ description:
+ - Operating system group to run the service.
+ default: navidrome
+
+ navidrome_music_folder:
+ description:
+ - Path to the music folder.
+ type: path
+ default: /opt/navidrome/music
+
+ navidrome_data_folder:
+ description:
+ - Path to the data directory.
+ type: path
+ default: /var/lib/navidrome
+
+ navidrome_cache_folder:
+ description:
+ - Path to the cache folder.
+ type: path
+ default: "{{ navidrome_data_folder }}/cache"
+
+ navidrome_manage_iptables:
+ description:
+ - Configure iptables rules
+ type: bool
+ default: false
+
+ navidrome_allowed_sources:
+ description:
+ - List of IP ranges to allow when `navidrome_manage_iptables` is enabled
+ type: list
+
+ navidrome_address:
+ description:
+ - Address to listen.
+ default: localhost
+
+ navidrome_port:
+ description:
+ - Port to listen
+ type: int
+ default: 4533
+
+ navidrome_enable_insights_collector:
+ description:
+ - Enable the insights collector.
+ type: bool
+ default: false
+
+ navidrome_base_url:
+ description:
+ - Base URL of the web interface.
diff --git a/roles/navidrome/tasks/main.yml b/roles/navidrome/tasks/main.yml
new file mode 100644
index 0000000..d575f0e
--- /dev/null
+++ b/roles/navidrome/tasks/main.yml
@@ -0,0 +1,62 @@
+---
+- name: Install package
+ ansible.builtin.apt:
+ deb: "https://github.com/navidrome/navidrome/releases/download/v{{ navidrome_version }}/navidrome_{{ navidrome_version }}_linux_{{ navidrome_arch }}.deb"
+
+- name: Create directories
+ ansible.builtin.file:
+ state: directory
+ path: "{{ item }}"
+ owner: "{{ navidrome_user }}"
+ group: "{{ navidrome_group }}"
+ mode: "0755"
+ loop:
+ - "{{ navidrome_music_folder }}"
+ - "{{ navidrome_data_folder }}"
+ - "{{ navidrome_cache_folder }}"
+
+- name: Create configuration file
+ ansible.builtin.template:
+ src: navidrome.toml.j2
+ dest: /etc/navidrome/navidrome.toml
+ owner: "{{ navidrome_user }}"
+ group: "{{ navidrome_group }}"
+ mode: "0644"
+ notify:
+ - Restart navidrome
+
+- name: Create systemd override directory
+ ansible.builtin.file:
+ state: directory
+ path: /etc/systemd/system/navidrome.service.d
+ owner: root
+ group: root
+ mode: "0755"
+
+- name: Add systemd override
+ ansible.builtin.template:
+ src: override.conf.j2
+ dest: /etc/systemd/system/navidrome.service.d/override.conf
+ owner: root
+ group: root
+ mode: "0755"
+ notify:
+ - Reload systemd
+ - Restart navidrome
+
+- name: Start service
+ ansible.builtin.service:
+ name: navidrome
+ state: started
+
+- name: Manage iptables
+ ansible.builtin.iptables:
+ chain: INPUT
+ protocol: tcp
+ source: "{{ item }}"
+ destination_port: "{{ navidrome_port }}"
+ jump: ACCEPT
+ comment: navidrome
+ loop: "{{ navidrome_allowed_sources }}"
+ notify: Save iptables
+ when: navidrome_manage_iptables
diff --git a/roles/navidrome/templates/navidrome.toml.j2 b/roles/navidrome/templates/navidrome.toml.j2
new file mode 100644
index 0000000..2b6492b
--- /dev/null
+++ b/roles/navidrome/templates/navidrome.toml.j2
@@ -0,0 +1,10 @@
+{{ ansible_managed | comment }}
+MusicFolder = "{{ navidrome_music_folder }}"
+DataFolder = "{{ navidrome_data_folder }}"
+CacheFolder = "{{ navidrome_cache_folder }}"
+Address = "{{ navidrome_address }}"
+Port = "{{ navidrome_port }}"
+EnableInsightsCollector = "{{ navidrome_enable_insights_collector }}"
+{% if navidrome_base_url is defined %}
+BaseUrl = "{{ navidrome_base_url }}"
+{% endif %}
diff --git a/roles/navidrome/templates/override.conf.j2 b/roles/navidrome/templates/override.conf.j2
new file mode 100644
index 0000000..371ef28
--- /dev/null
+++ b/roles/navidrome/templates/override.conf.j2
@@ -0,0 +1,5 @@
+{{ ansible_managed | comment }}
+[Service]
+User={{ navidrome_user }}
+WorkingDirectory={{ navidrome_data_folder }}
+ReadWritePaths={{ navidrome_data_folder }}
diff --git a/test-requirements.txt b/test-requirements.txt
new file mode 100644
index 0000000..4c8ddfb
--- /dev/null
+++ b/test-requirements.txt
@@ -0,0 +1,2 @@
+ansible-core
+molecule
diff --git a/test-requirements.yml b/test-requirements.yml
new file mode 100644
index 0000000..25a97a4
--- /dev/null
+++ b/test-requirements.yml
@@ -0,0 +1,3 @@
+---
+collections:
+ - name: containers.podman