commit 53695f4fff87c968f1ecc012b2fe52a170f3f309 Author: boris Date: Wed Jul 8 10:29:51 2026 +0100 Initialise repository structure and placeholder files. Generic functionality works i guess, need to come up with a better solution for compose templates diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9c7fe48 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.retry +.ansible/ +.cache/ +.vault-pass +*.log +*.pem +*.key +*.crt +*.csr +*.p12 +*.env +secrets.yml +vault.yml + +TODO.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..a846eed --- /dev/null +++ b/README.md @@ -0,0 +1,278 @@ +# nbops-ansible + +## nbops +#### Infrastructure as Code for notBoris' Homelab + +nbops is the central operations repository for my hosted infrastructure, containing everything required to provision, configure, and maintain my virtual machines and services. The goal is to treat my infrastructure as code for reproducibility and automation from source control, while having minimal manual intervention. + +## About + +nbops-ansible contains the configuration management layer of the nbops platform. + +Every virtual machine begins life as a standardised Debian 13 GenericCloud template, provisioned by Proxmox and bootstrapped with Cloud-Init. Once the operating system has completed its initial configuration, the host runs ansible-pull against this repository to configure itself according to its assigned role. + +A freshly created virtual machine should require little more than a hostname, network configuration and SSH key before automatically configuring itself into a production-ready host. + + +# Design Philosophy + +This repository follows a layered approach where each component has a clearly defined responsibility. + +```mermaid +flowchart TD + A[GitHub: nbops-ansible] --> B[Proxmox VE] + B --> C[Debian 13 GenericCloud Template] + C --> D[Clone VM] + D --> E[Cloud-Init Bootstrap] + E --> F[ansible-pull] + F --> G[Apply Base Roles] + G --> H[Apply Service Roles] + H --> I[Docker Compose] + I --> J[Running Services] +``` + +## Layer Responsibilities + +```mermaid +flowchart LR + P[Proxmox VE
VM lifecycle
storage
networking] + D[Debian 13
GenericCloud
minimal OS] + C[Cloud-Init
identity
network
bootstrap] + A[Ansible
desired state
roles
configuration] + DC[Docker Compose
service runtime] + S[Applications
actual workloads] + + P --> D --> C --> A --> DC --> S +``` + +# Repository Structure + +. +├── inventories/ +│ ├── production/ +│ └── development/ +│ +├── playbooks/ +│ +├── roles/ +│ ├── base/ +│ ├── docker/ +│ ├── ssh/ +│ ├── ufw/ +│ ├── mtls/ +│ ├── monitoring/ +│ ├── traefik/ +│ ├── authentik/ +│ ├── immich/ +│ ├── gitea/ +│ ├── arr/ +│ ├── ocis/ +│ └── pterodactyl/ +│ +├── group_vars/ +├── host_vars/ +├── templates/ +├── files/ +├── scripts/ +└── docs/ + +As the infrastructure grows, additional functionality should be implemented as new roles rather than modifying existing ones wherever practical. + + +# Configuration Workflow + +Cloud-Init is intentionally lightweight. + +Its responsibility is limited to preparing the operating system by configuring: + +* Hostname +* Networking +* DNS +* Users +* SSH +* Docker +* Firewall +* Internal CA trust +* Base packages + +Once complete, Cloud-Init hands over to Ansible. + +Ansible is responsible for configuring and maintaining the desired state of every machine for the remainder of its lifecycle. + +# Roles + +The repository is built around reusable Ansible roles; every server receives a common baseline before additional service-specific roles are applied. + +## Core Roles + +* base +* docker +* ssh +* ufw +* mtls +* monitoring +* updates + +## Service Roles + +* traefik +* authentik +* gitea +* Pelican +* etc.. + +This approach allows new services to inherit the same security, logging and operating system configuration while remaining independent from one another. + +# Operating System Standard + +Unless otherwise required, every virtual machine deployed by nbops follows the same baseline. + +* Debian 13 (Trixie) +* GenericCloud image +* UEFI (OVMF) +* Q35 machine type +* VirtIO networking +* VirtIO SCSI storage +* QEMU Guest Agent +* Docker Engine +* Docker Compose Plugin +* Chrony +* UFW +* SSH key authentication only + +Standardising the operating system significantly reduces maintenance overhead and configuration drift across the environment. + +# Infrastructure Principles + +The following principles guide every decision made within this repository. + +* Everything should be reproducible. +* Everything should be version controlled. +* Manual configuration should be avoided. +* Infrastructure should be disposable rather than unique. +* Security should be built into the baseline. +* Applications should remain isolated from the operating system wherever possible. +* Configuration should be modular and reusable. + +# Future Goals + +Planned work includes: + +* Automated Debian template creation +* Cloud-Init template generation +* Automated certificate management +* Secret management +* Monitoring and alerting +* Backup automation +* Configuration validation +* Disaster recovery testing +* Multi-node infrastructure support + +# Contributing + +This repository is primarily maintained for my own homelab, but the techniques and playbooks may be useful to others building similar self-hosted environments. + +Suggestions, improvements and ideas are always welcome. + +# License + +Unless otherwise stated, all code within this repository is released under the MIT License. +# nbops-ansible + +Ansible repository for a reproducible Proxmox-based Infrastructure as Code +platform using Debian 13 GenericCloud, Cloud-Init, Docker Engine, Docker Compose, +and `ansible-pull`. + +## Model + +```text +Proxmox + -> Debian 13 GenericCloud template + -> clone + -> Cloud-Init + -> ansible-pull + -> base roles + -> service roles + -> Docker Compose +``` + +Cloud-Init handles Day 0 bootstrap only. Ansible owns Day 1 onward state. + +## Layout + +- `inventories/`: production and development inventories +- `playbooks/`: site and pull entrypoints +- `roles/`: reusable core and application roles +- `group_vars/`: shared defaults +- `host_vars/`: per-VM overrides +- `templates/`: Cloud-Init and shared templates +- `scripts/`: local helper scripts +- `docs/`: operational notes + +## Roles + +Core roles: + +- `base` +- `ssh` +- `ufw` +- `updates` +- `docker` +- `mtls` +- `monitoring` + +Application roles: + +- `traefik` +- `authentik` +- `immich` +- `gitea` +- `ocis` +- `pterodactyl` +- `arr` + +Enable roles per host with `nbops_roles`. + +```yaml +nbops_roles: + - base + - ssh + - ufw + - updates + - docker + - mtls + - monitoring + - traefik +``` + +## VM Topology + +Every VM is expected to run Docker. Put `docker` in `nbops_roles` for each VM. + +The edge VM normally runs `traefik` and `authentik`, with Traefik publishing +ports `80` and `443`. Downstream VMs can run their own internal Traefik and set +`nbops_service_routing_mode: traefik` so applications register with Traefik +instead of exposing individual host ports. + +See `host_vars/edge.yml.example`, `host_vars/downstream.yml.example`, and +`docs/topology.md`. The full Docker service list is in `docs/service-catalog.md`. + +## Bootstrap + +Render `templates/cloud-init-user-data.yml.j2` with host variables and pass it to +Proxmox as user-data. The template installs baseline packages, Docker from +Docker's official Debian repository, configures UFW and SSH, creates `/srv` and +PKI directories, and executes `ansible-pull`. + +## Secrets + +Secrets are intentionally excluded. Do not commit passwords, API keys, +certificates, private keys, or generated `.env` files. Use Ansible Vault, SOPS, +Vault, or Smallstep-backed issuance when introducing real secret material. + +## Validation + +```sh +ansible-galaxy collection install -r requirements.yml +scripts/check.sh +``` diff --git a/ansible.cfg b/ansible.cfg new file mode 100644 index 0000000..62d2ad5 --- /dev/null +++ b/ansible.cfg @@ -0,0 +1,22 @@ +[defaults] +inventory = inventories/production/hosts.yml +roles_path = roles +collections_path = .ansible/collections:~/.ansible/collections:/usr/share/ansible/collections +stdout_callback = yaml +bin_ansible_callbacks = True +host_key_checking = True +retry_files_enabled = False +interpreter_python = auto_silent +fact_caching = jsonfile +fact_caching_connection = .ansible/facts +fact_caching_timeout = 86400 + +[privilege_escalation] +become = True +become_method = sudo +become_ask_pass = False + +[ssh_connection] +pipelining = True +ssh_args = -o ControlMaster=auto -o ControlPersist=60s + diff --git a/docs/operations.md b/docs/operations.md new file mode 100644 index 0000000..9d4023b --- /dev/null +++ b/docs/operations.md @@ -0,0 +1,32 @@ +# Operations + +Install collections: + +```sh +ansible-galaxy collection install -r requirements.yml +``` + +Run a syntax check: + +```sh +ansible-playbook --syntax-check playbooks/site.yml +``` + +Run locally: + +```sh +ansible-playbook -i inventories/development/hosts.yml playbooks/site.yml +``` + +Run selected tags: + +```sh +ansible-playbook -i inventories/production/hosts.yml playbooks/site.yml --tags base,docker,traefik +``` + +Run with ansible-pull: + +```sh +REPO_URL=git@github.com:example/nbops-ansible.git scripts/ansible-pull.sh +``` + diff --git a/docs/proxmox-template.md b/docs/proxmox-template.md new file mode 100644 index 0000000..f705c9b --- /dev/null +++ b/docs/proxmox-template.md @@ -0,0 +1,20 @@ +# Proxmox Debian 13 GenericCloud Template + +Expected VM defaults: + +- Debian 13 Trixie GenericCloud image +- Q35 machine type +- UEFI / OVMF +- VirtIO SCSI controller +- VirtIO network device +- CPU type `host` +- QEMU Guest Agent enabled +- Cloud-Init drive attached +- 2 vCPU, 2 GB RAM, 32 GB disk unless a service needs more + +Cloud-Init is only responsible for Day 0 bootstrap: user creation, SSH +hardening, Docker installation, baseline packages, firewall defaults, directory +layout, PKI directory preparation, and `ansible-pull`. + +Applications and durable service configuration belong in Ansible roles. + diff --git a/docs/secrets.md b/docs/secrets.md new file mode 100644 index 0000000..2d74c86 --- /dev/null +++ b/docs/secrets.md @@ -0,0 +1,16 @@ +# Secrets + +Do not commit passwords, API keys, issued certificates, private keys, or `.env` +files containing secrets. + +This repository is ready for these future integrations: + +- Ansible Vault +- SOPS +- HashiCorp Vault +- Smallstep certificates + +Role defaults deliberately use `CHANGE_ME_IN_VAULT` placeholders for required +application secrets. Override those values in encrypted inventory data before +deploying real services. + diff --git a/docs/service-catalog.md b/docs/service-catalog.md new file mode 100644 index 0000000..fb583c0 --- /dev/null +++ b/docs/service-catalog.md @@ -0,0 +1,63 @@ +# Service Catalog + +These Docker service roles are available through `nbops_roles`. + +Media and downloads: + +- `jellyfin` +- `jellyseerr` +- `qbittorrent` +- `transmission` +- `wizarr` +- `flaresolverr` +- `cleanuparr` +- `pinchflat` +- `kavita` + +Identity and access: + +- `authentik` +- `authentik_ldap` +- `authentik_proxy` +- `authentik_rac` +- `vaultwarden` + +Dashboards and file tools: + +- `homarr` +- `nginx_content` +- `code_server` +- `filebrowser_quantum` +- `kiwix_serve` +- `requarks` +- `fiveetools` +- `ntfy` + +Infrastructure: + +- `pelican_panel` +- `pelican_server` +- `gitea` +- `gitea_act_runner` +- `postgres` +- `php_fpm` +- `valkey` +- `syncthing` +- `ipv6nat` + +Monitoring: + +- `grafana` +- `loki` +- `alloy` +- `grafana_renderer` +- `prometheus` +- `uptime_kuma` + +Most roles use the shared `docker_app` implementation and support: + +- `host` routing mode: publish ports directly and open UFW rules. +- `traefik` routing mode: attach to the proxy network and render Traefik labels. + +Override role defaults in `host_vars` or `group_vars`; do not edit generated +Compose files on the VM. diff --git a/docs/topology.md b/docs/topology.md new file mode 100644 index 0000000..b995442 --- /dev/null +++ b/docs/topology.md @@ -0,0 +1,59 @@ +# Topology + +Every VM is a Docker host. Proxmox and Cloud-Init only create the machine and +bootstrap Ansible; service placement is controlled by inventory. + +## Edge VM + +The edge VM runs: + +- Docker +- Traefik +- authentik IAM + +It publishes Traefik entrypoints on host ports `80` and `443`. authentik can run +behind the same local Traefik instance by setting: + +```yaml +nbops_service_routing_mode: traefik +authentik_domain: auth.example.invalid +``` + +## Downstream VMs + +Downstream VMs also run Docker. Instead of publishing each application port on +the host, run Traefik on the VM and attach application containers to the shared +Docker proxy network. + +```yaml +nbops_roles: + - base + - ssh + - ufw + - updates + - docker + - traefik + - gitea + - immich + +nbops_service_routing_mode: traefik +nbops_traefik_entrypoint: web +nbops_traefik_tls: false +``` + +In this mode application roles do not publish their HTTP ports. They render +Traefik labels and join `nbops_proxy_network`, which defaults to `proxy`. + +Only Traefik entrypoints should be exposed on the downstream host firewall. Use +`traefik_bind_address` to bind those entrypoints to the VM's internal address. + +## Routing Modes + +Application roles support: + +- `host`: publish application ports directly and open those ports in UFW. +- `traefik`: do not publish HTTP ports; attach to Traefik and render labels. + +Gitea is special: HTTP can be routed through Traefik while SSH remains published +with `gitea_publish_ssh: true`. + diff --git a/files/.gitkeep b/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/group_vars/README.md b/group_vars/README.md new file mode 100644 index 0000000..5ade954 --- /dev/null +++ b/group_vars/README.md @@ -0,0 +1,9 @@ +# Group Variables + +Group variables define shared defaults for every VM. Override them in environment +or host-specific files rather than editing role tasks. + +Secrets, private keys, API tokens, and issued certificates must not be committed. +Use Ansible Vault, SOPS, Vault, or Smallstep-backed workflows when those values +are introduced. + diff --git a/group_vars/all.yml b/group_vars/all.yml new file mode 100644 index 0000000..42a4f21 --- /dev/null +++ b/group_vars/all.yml @@ -0,0 +1,71 @@ +--- +nbops_environment: production +nbops_admin_user: admin +nbops_admin_groups: + - sudo +nbops_hostname: "{{ inventory_hostname }}" +nbops_fqdn: "{{ inventory_hostname }}" +nbops_timezone: Etc/UTC +nbops_locale: en_GB.UTF-8 +nbops_ssh_port: 22 +nbops_dns_servers: + - 1.1.1.1 + - 9.9.9.9 + +nbops_standard_directories: + - /srv/docker + - /srv/config + - /srv/backups + - /srv/data + - /srv/media + - /etc/pki/internal + - /etc/pki/internal/private + - /etc/pki/internal/certs + - /usr/local/share/ca-certificates/internal + +nbops_base_packages: + - acl + - apt-transport-https + - bash-completion + - ca-certificates + - chrony + - curl + - git + - gnupg + - htop + - jq + - locales + - lsb-release + - needrestart + - qemu-guest-agent + - rsync + - sudo + - ufw + - unattended-upgrades + - vim + +nbops_sysctl: + net.ipv4.ip_forward: "1" + net.ipv6.conf.all.forwarding: "1" + fs.inotify.max_user_watches: "1048576" + fs.inotify.max_user_instances: "1024" + +docker_users: + - "{{ nbops_admin_user }}" + +ufw_allowed_tcp_ports: + - "{{ nbops_ssh_port }}" +ufw_allowed_udp_ports: [] + +monitoring_agent: none +monitoring_packages: [] + +mtls_ca_certificates: [] +mtls_host_certificates: [] +mtls_private_keys: [] + +nbops_proxy_network: proxy +nbops_service_routing_mode: host +nbops_service_domain_suffix: example.invalid +nbops_traefik_entrypoint: websecure +nbops_traefik_tls: true diff --git a/host_vars/README.md b/host_vars/README.md new file mode 100644 index 0000000..705b37e --- /dev/null +++ b/host_vars/README.md @@ -0,0 +1,7 @@ +# Host Variables + +Create one file per VM, for example `host_vars/media-01.yml`. + +Use host variables for hostnames, service enablement, storage paths, and port +assignments. Keep credentials and TLS private material outside Git. + diff --git a/host_vars/cloud.internal.yml.example b/host_vars/cloud.internal.yml.example new file mode 100644 index 0000000..21a33a8 --- /dev/null +++ b/host_vars/cloud.internal.yml.example @@ -0,0 +1,29 @@ +--- +nbops_hostname: cloud +nbops_fqdn: cloud.example.internal +nbops_roles: + - base + - ssh + - ufw + - updates + - docker + - mtls + - monitoring + - traefik + - ocis + +nbops_service_routing_mode: traefik +nbops_traefik_entrypoint: web +nbops_traefik_tls: false + +# Bind this to the VM's internal/LAN address in production. +traefik_bind_address: "0.0.0.0" +traefik_publish_entrypoints: true +traefik_domain: apps-01.example.internal +traefik_forwarded_headers_trusted_ips: + - 10.0.0.0/8 + - 172.16.0.0/12 + - 192.168.0.0/16 + +ocis_url: https://files.example.internal + diff --git a/host_vars/edge.yml.example b/host_vars/edge.yml.example new file mode 100644 index 0000000..49e819c --- /dev/null +++ b/host_vars/edge.yml.example @@ -0,0 +1,31 @@ +--- +nbops_hostname: edge +nbops_fqdn: edge.example.invalid +nbops_roles: + - base + - ssh + - ufw + - updates + - docker + - mtls + - monitoring + - traefik + - authentik + - authentik_rac + - authentik_ldap + - authentik_worker + +nbops_service_routing_mode: traefik +nbops_service_domain_suffix: example.invalid +nbops_traefik_entrypoint: websecure +nbops_traefik_tls: true + +traefik_bind_address: "0.0.0.0" +traefik_publish_entrypoints: true +traefik_domain: example.invalid +traefik_acme_email: admin@example.invalid + +authentik_domain: auth.example.invalid +authentik_http_port: 9000 +authentik_https_port: 9443 + diff --git a/host_vars/generic-downstream.yml.example b/host_vars/generic-downstream.yml.example new file mode 100644 index 0000000..1126d66 --- /dev/null +++ b/host_vars/generic-downstream.yml.example @@ -0,0 +1,57 @@ +--- +nbops_hostname: apps-01 +nbops_fqdn: apps-01.example.internal +nbops_roles: + - base + - ssh + - ufw + - updates + - docker + - mtls + - monitoring + - traefik + - gitea + - immich + - ocis + - arr + +nbops_service_routing_mode: traefik +nbops_traefik_entrypoint: web +nbops_traefik_tls: false + +# Bind this to the VM's internal/LAN address in production. +traefik_bind_address: "0.0.0.0" +traefik_publish_entrypoints: true +traefik_domain: apps-01.example.internal +traefik_forwarded_headers_trusted_ips: + - 10.0.0.0/8 + - 172.16.0.0/12 + - 192.168.0.0/16 + +gitea_domain: git.example.internal +gitea_publish_ssh: true +immich_domain: photos.example.internal +ocis_url: https://files.example.internal + +arr_services: + prowlarr: + image: lscr.io/linuxserver/prowlarr:latest + port: 9696 + domain: prowlarr.example.internal + sonarr: + image: lscr.io/linuxserver/sonarr:latest + port: 8989 + domain: sonarr.example.internal + radarr: + image: lscr.io/linuxserver/radarr:latest + port: 7878 + domain: radarr.example.internal + lidarr: + image: lscr.io/linuxserver/lidarr:latest + port: 8686 + domain: lidarr.example.internal + bazarr: + image: lscr.io/linuxserver/bazarr:latest + port: 6767 + domain: bazarr.example.internal + diff --git a/host_vars/pelican-server.yml.example b/host_vars/pelican-server.yml.example new file mode 100644 index 0000000..e9bed80 --- /dev/null +++ b/host_vars/pelican-server.yml.example @@ -0,0 +1,15 @@ +--- +nbops_hostname: pelican-node-01 +nbops_fqdn: pelican-node-01.example.internal +nbops_roles: + - base + - ssh + - ufw + - updates + - docker + - pelican_server + +pelican_server_daemon_port: 8080 +pelican_server_sftp_port: 2022 +pelican_server_allocation_ports: + - { port: "25565:25575", proto: tcp } diff --git a/host_vars/wings.internal.example b/host_vars/wings.internal.example new file mode 100644 index 0000000..21a33a8 --- /dev/null +++ b/host_vars/wings.internal.example @@ -0,0 +1,29 @@ +--- +nbops_hostname: cloud +nbops_fqdn: cloud.example.internal +nbops_roles: + - base + - ssh + - ufw + - updates + - docker + - mtls + - monitoring + - traefik + - ocis + +nbops_service_routing_mode: traefik +nbops_traefik_entrypoint: web +nbops_traefik_tls: false + +# Bind this to the VM's internal/LAN address in production. +traefik_bind_address: "0.0.0.0" +traefik_publish_entrypoints: true +traefik_domain: apps-01.example.internal +traefik_forwarded_headers_trusted_ips: + - 10.0.0.0/8 + - 172.16.0.0/12 + - 192.168.0.0/16 + +ocis_url: https://files.example.internal + diff --git a/inventories/development/hosts.yml b/inventories/development/hosts.yml new file mode 100644 index 0000000..7e18267 --- /dev/null +++ b/inventories/development/hosts.yml @@ -0,0 +1,16 @@ +--- +all: + children: + nbops: + hosts: + localhost: + ansible_connection: local + ansible_python_interpreter: /usr/bin/python3 + nbops_environment: development + nbops_roles: + - base + - ssh + - ufw + - updates + - docker + diff --git a/inventories/production/hosts.yml b/inventories/production/hosts.yml new file mode 100644 index 0000000..6ff3856 --- /dev/null +++ b/inventories/production/hosts.yml @@ -0,0 +1,17 @@ +--- +all: + children: + nbops: + hosts: + localhost: + ansible_connection: local + ansible_python_interpreter: /usr/bin/python3 + nbops_roles: + - base + - ssh + - ufw + - updates + - docker + - mtls + - monitoring + diff --git a/playbooks/pull.yml b/playbooks/pull.yml new file mode 100644 index 0000000..acdff45 --- /dev/null +++ b/playbooks/pull.yml @@ -0,0 +1,4 @@ +--- +- name: Local ansible-pull entrypoint + ansible.builtin.import_playbook: site.yml + diff --git a/playbooks/site.yml b/playbooks/site.yml new file mode 100644 index 0000000..d352c52 --- /dev/null +++ b/playbooks/site.yml @@ -0,0 +1,167 @@ +--- +- name: Converge nbops host + hosts: all + become: true + gather_facts: true + + pre_tasks: + - name: Validate supported operating system + ansible.builtin.assert: + that: + - ansible_distribution == "Debian" + - ansible_distribution_major_version | int >= 13 + fail_msg: "nbops targets Debian 13 Trixie or newer." + tags: + - always + + roles: + - role: base + tags: [base] + when: "'base' in nbops_roles | default([])" + - role: ssh + tags: [ssh] + when: "'ssh' in nbops_roles | default([])" + - role: ufw + tags: [ufw] + when: "'ufw' in nbops_roles | default([])" + - role: updates + tags: [updates] + when: "'updates' in nbops_roles | default([])" + - role: docker + tags: [docker] + when: "'docker' in nbops_roles | default([])" + - role: mtls + tags: [mtls] + when: "'mtls' in nbops_roles | default([])" + - role: monitoring + tags: [monitoring] + when: "'monitoring' in nbops_roles | default([])" + - role: traefik + tags: [traefik] + when: "'traefik' in nbops_roles | default([])" + - role: authentik + tags: [authentik] + when: "'authentik' in nbops_roles | default([])" + - role: immich + tags: [immich] + when: "'immich' in nbops_roles | default([])" + - role: gitea + tags: [gitea] + when: "'gitea' in nbops_roles | default([])" + - role: ocis + tags: [ocis] + when: "'ocis' in nbops_roles | default([])" + - role: pterodactyl + tags: [pterodactyl] + when: "'pterodactyl' in nbops_roles | default([])" + - role: arr + tags: [arr] + when: "'arr' in nbops_roles | default([])" + - role: jellyfin + tags: [jellyfin] + when: "'jellyfin' in nbops_roles | default([])" + - role: pelican_panel + tags: [pelican_panel] + when: "'pelican_panel' in nbops_roles | default([])" + - role: pelican_server + tags: [pelican_server] + when: "'pelican_server' in nbops_roles | default([])" + - role: authentik_ldap + tags: [authentik_ldap] + when: "'authentik_ldap' in nbops_roles | default([])" + - role: authentik_proxy + tags: [authentik_proxy] + when: "'authentik_proxy' in nbops_roles | default([])" + - role: authentik_rac + tags: [authentik_rac] + when: "'authentik_rac' in nbops_roles | default([])" + - role: jellyseerr + tags: [jellyseerr] + when: "'jellyseerr' in nbops_roles | default([])" + - role: qbittorrent + tags: [qbittorrent] + when: "'qbittorrent' in nbops_roles | default([])" + - role: transmission + tags: [transmission] + when: "'transmission' in nbops_roles | default([])" + - role: wizarr + tags: [wizarr] + when: "'wizarr' in nbops_roles | default([])" + - role: homarr + tags: [homarr] + when: "'homarr' in nbops_roles | default([])" + - role: flaresolverr + tags: [flaresolverr] + when: "'flaresolverr' in nbops_roles | default([])" + - role: cleanuparr + tags: [cleanuparr] + when: "'cleanuparr' in nbops_roles | default([])" + - role: pinchflat + tags: [pinchflat] + when: "'pinchflat' in nbops_roles | default([])" + - role: nginx_content + tags: [nginx_content] + when: "'nginx_content' in nbops_roles | default([])" + - role: code_server + tags: [code_server] + when: "'code_server' in nbops_roles | default([])" + - role: filebrowser_quantum + tags: [filebrowser_quantum] + when: "'filebrowser_quantum' in nbops_roles | default([])" + - role: kiwix_serve + tags: [kiwix_serve] + when: "'kiwix_serve' in nbops_roles | default([])" + - role: ipv6nat + tags: [ipv6nat] + when: "'ipv6nat' in nbops_roles | default([])" + - role: php_fpm + tags: [php_fpm] + when: "'php_fpm' in nbops_roles | default([])" + - role: valkey + tags: [valkey] + when: "'valkey' in nbops_roles | default([])" + - role: syncthing + tags: [syncthing] + when: "'syncthing' in nbops_roles | default([])" + - role: paperless_ngx + tags: [paperless_ngx] + when: "'paperless_ngx' in nbops_roles | default([])" + - role: kavita + tags: [kavita] + when: "'kavita' in nbops_roles | default([])" + - role: gitea_act_runner + tags: [gitea_act_runner] + when: "'gitea_act_runner' in nbops_roles | default([])" + - role: vaultwarden + tags: [vaultwarden] + when: "'vaultwarden' in nbops_roles | default([])" + - role: grafana + tags: [grafana] + when: "'grafana' in nbops_roles | default([])" + - role: loki + tags: [loki] + when: "'loki' in nbops_roles | default([])" + - role: alloy + tags: [alloy] + when: "'alloy' in nbops_roles | default([])" + - role: grafana_renderer + tags: [grafana_renderer] + when: "'grafana_renderer' in nbops_roles | default([])" + - role: prometheus + tags: [prometheus] + when: "'prometheus' in nbops_roles | default([])" + - role: uptime_kuma + tags: [uptime_kuma] + when: "'uptime_kuma' in nbops_roles | default([])" + - role: requarks + tags: [requarks] + when: "'requarks' in nbops_roles | default([])" + - role: postgres + tags: [postgres] + when: "'postgres' in nbops_roles | default([])" + - role: fiveetools + tags: [fiveetools] + when: "'fiveetools' in nbops_roles | default([])" + - role: ntfy + tags: [ntfy] + when: "'ntfy' in nbops_roles | default([])" diff --git a/requirements.yml b/requirements.yml new file mode 100644 index 0000000..203d13d --- /dev/null +++ b/requirements.yml @@ -0,0 +1,6 @@ +--- +collections: + - name: ansible.posix + - name: community.docker + - name: community.general + diff --git a/roles/alloy/README.md b/roles/alloy/README.md new file mode 100644 index 0000000..ea34145 --- /dev/null +++ b/roles/alloy/README.md @@ -0,0 +1,4 @@ +# alloy + +Deploys Grafana Alloy with Docker Compose. + diff --git a/roles/alloy/defaults/main.yml b/roles/alloy/defaults/main.yml new file mode 100644 index 0000000..c138547 --- /dev/null +++ b/roles/alloy/defaults/main.yml @@ -0,0 +1,17 @@ +--- +alloy_name: alloy +alloy_dir: /srv/docker/alloy +alloy_image: grafana/alloy:latest +alloy_routing_mode: host +alloy_command: "run --server.http.listen-addr=0.0.0.0:12345 /etc/alloy/config.alloy" +alloy_env: {} +alloy_directories: + - "{{ alloy_dir }}" + - /srv/config/alloy +alloy_volumes: + - /srv/config/alloy:/etc/alloy + - /var/log:/var/log:ro + - /var/run/docker.sock:/var/run/docker.sock:ro +alloy_ports: + - { host: 12345, container: 12345, protocol: tcp } + diff --git a/roles/alloy/files/.gitkeep b/roles/alloy/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/alloy/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/alloy/handlers/.gitkeep b/roles/alloy/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/alloy/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/alloy/tasks/main.yml b/roles/alloy/tasks/main.yml new file mode 100644 index 0000000..f82548e --- /dev/null +++ b/roles/alloy/tasks/main.yml @@ -0,0 +1,17 @@ +--- +- name: Deploy Grafana Alloy + ansible.builtin.include_role: + name: docker_app + vars: + docker_app_env_template_src: "{{ playbook_dir }}/../roles/alloy/templates/env.j2" + docker_app_compose_template_src: "{{ playbook_dir }}/../roles/alloy/templates/compose.yaml.j2" + docker_app_name: "{{ alloy_name }}" + docker_app_dir: "{{ alloy_dir }}" + docker_app_image: "{{ alloy_image }}" + docker_app_command: "{{ alloy_command }}" + docker_app_env: "{{ alloy_env }}" + docker_app_directories: "{{ alloy_directories }}" + docker_app_volumes: "{{ alloy_volumes }}" + docker_app_ports: "{{ alloy_ports }}" + docker_app_routing_mode: "{{ alloy_routing_mode }}" + diff --git a/roles/alloy/templates/.gitkeep b/roles/alloy/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/alloy/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/alloy/templates/compose.yaml.j2 b/roles/alloy/templates/compose.yaml.j2 new file mode 100644 index 0000000..6de1603 --- /dev/null +++ b/roles/alloy/templates/compose.yaml.j2 @@ -0,0 +1,90 @@ +services: + {{ docker_app_name }}: + image: {{ docker_app_image }} + restart: {{ docker_app_restart }} +{% if docker_app_command | length > 0 %} + command: {{ docker_app_command | to_json }} +{% endif %} +{% if docker_app_user | length > 0 %} + user: "{{ docker_app_user }}" +{% endif %} +{% if docker_app_network_mode | length > 0 %} + network_mode: {{ docker_app_network_mode }} +{% endif %} +{% if docker_app_privileged | bool %} + privileged: true +{% endif %} +{% if docker_app_env | length > 0 %} + env_file: .env +{% endif %} +{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} + ports: +{% for port in docker_app_ports %} + - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_service_port %} + # internal service port: {{ docker_app_service_port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }} + - traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }} + - traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }} +{% if docker_app_traefik_middlewares | length > 0 %} + - traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }} +{% endif %} + - traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }} +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% elif docker_app_labels | length > 0 %} + labels: +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% endif %} +{% if docker_app_volumes | length > 0 %} + volumes: +{% for volume in docker_app_volumes %} + - {{ volume }} +{% endfor %} +{% endif %} +{% if docker_app_devices | length > 0 %} + devices: +{% for device in docker_app_devices %} + - {{ device }} +{% endfor %} +{% endif %} +{% if docker_app_cap_add | length > 0 %} + cap_add: +{% for capability in docker_app_cap_add %} + - {{ capability }} +{% endfor %} +{% endif %} +{% if docker_app_group_add | length > 0 %} + group_add: +{% for group in docker_app_group_add %} + - "{{ group }}" +{% endfor %} +{% endif %} +{% if docker_app_extra_hosts | length > 0 %} + extra_hosts: +{% for host in docker_app_extra_hosts %} + - {{ host }} +{% endfor %} +{% endif %} +{% if docker_app_depends_on | length > 0 %} + depends_on: +{% for dependency in docker_app_depends_on %} + - {{ dependency }} +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} + networks: + - {{ docker_app_traefik_network }} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} +networks: + {{ docker_app_traefik_network }}: + external: true +{% endif %} diff --git a/roles/alloy/templates/env.j2 b/roles/alloy/templates/env.j2 new file mode 100644 index 0000000..c4f1a9f --- /dev/null +++ b/roles/alloy/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in docker_app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/arr/README.md b/roles/arr/README.md new file mode 100644 index 0000000..9814567 --- /dev/null +++ b/roles/arr/README.md @@ -0,0 +1,7 @@ +# arr + +Deploys a LinuxServer.io Arr stack with Prowlarr, Sonarr, Radarr, Lidarr, and +Bazarr. Override `arr_services` to add or remove services. + +Tags: `arr`, `config`, `compose`, `firewall`. + diff --git a/roles/arr/defaults/main.yml b/roles/arr/defaults/main.yml new file mode 100644 index 0000000..b59a0f6 --- /dev/null +++ b/roles/arr/defaults/main.yml @@ -0,0 +1,34 @@ +--- +arr_dir: /srv/docker/arr +arr_puid: "1000" +arr_pgid: "1000" +arr_timezone: "{{ nbops_timezone }}" +arr_config_root: /srv/config/arr +arr_media_root: /srv/media +arr_downloads_root: /srv/data/downloads +arr_routing_mode: "{{ nbops_service_routing_mode }}" +arr_traefik_network: "{{ nbops_proxy_network }}" +arr_traefik_entrypoint: "{{ nbops_traefik_entrypoint }}" +arr_traefik_tls: "{{ nbops_traefik_tls }}" +arr_services: + prowlarr: + image: lscr.io/linuxserver/prowlarr:latest + port: 9696 + domain: prowlarr.example.invalid + sonarr: + image: lscr.io/linuxserver/sonarr:latest + port: 8989 + domain: sonarr.example.invalid + radarr: + image: lscr.io/linuxserver/radarr:latest + port: 7878 + domain: radarr.example.invalid + lidarr: + image: lscr.io/linuxserver/lidarr:latest + port: 8686 + domain: lidarr.example.invalid + bazarr: + image: lscr.io/linuxserver/bazarr:latest + port: 6767 + domain: bazarr.example.invalid +arr_tcp_ports: "{{ arr_services | dict2items | map(attribute='value.port') | list }}" diff --git a/roles/arr/files/.gitkeep b/roles/arr/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/arr/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/arr/handlers/main.yml b/roles/arr/handlers/main.yml new file mode 100644 index 0000000..504ee8d --- /dev/null +++ b/roles/arr/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: restart arr + community.docker.docker_compose_v2: + project_src: "{{ arr_dir }}" + state: restarted + diff --git a/roles/arr/tasks/main.yml b/roles/arr/tasks/main.yml new file mode 100644 index 0000000..7b39f0b --- /dev/null +++ b/roles/arr/tasks/main.yml @@ -0,0 +1,65 @@ +--- +- name: Create Arr stack directories + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: root + group: root + mode: "0755" + loop: + - "{{ arr_dir }}" + - "{{ arr_config_root }}" + - "{{ arr_media_root }}" + - "{{ arr_downloads_root }}" + tags: [arr] + +- name: Create Arr application config directories + ansible.builtin.file: + path: "{{ arr_config_root }}/{{ item.key }}" + state: directory + owner: root + group: root + mode: "0755" + loop: "{{ arr_services | dict2items }}" + tags: [arr, config] + +- name: Render Arr environment + ansible.builtin.template: + src: env.j2 + dest: "{{ arr_dir }}/.env" + owner: root + group: root + mode: "0644" + tags: [arr, config] + +- name: Ensure Arr proxy network exists + community.docker.docker_network: + name: "{{ arr_traefik_network }}" + state: present + when: arr_routing_mode == "traefik" + tags: [arr, docker] + +- name: Render Arr Compose file + ansible.builtin.template: + src: compose.yaml.j2 + dest: "{{ arr_dir }}/compose.yaml" + owner: root + group: root + mode: "0644" + notify: restart arr + tags: [arr, compose] + +- name: Allow Arr ports + community.general.ufw: + rule: allow + port: "{{ item }}" + proto: tcp + loop: "{{ arr_tcp_ports }}" + when: arr_routing_mode == "host" + tags: [arr, firewall] + +- name: Start Arr stack + community.docker.docker_compose_v2: + project_src: "{{ arr_dir }}" + state: present + tags: [arr, compose] diff --git a/roles/arr/templates/compose.yaml.j2 b/roles/arr/templates/compose.yaml.j2 new file mode 100644 index 0000000..bc2891b --- /dev/null +++ b/roles/arr/templates/compose.yaml.j2 @@ -0,0 +1,31 @@ +services: +{% for name, service in arr_services.items() %} + {{ name }}: + image: {{ service.image }} + restart: unless-stopped + env_file: .env +{% if arr_routing_mode == "host" %} + ports: + - "{{ service.port }}:{{ service.port }}" +{% endif %} +{% if arr_routing_mode == "traefik" %} + # internal service port: {{ service.port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ name }}.rule=Host(`{{ service.domain }}`) + - traefik.http.routers.{{ name }}.entrypoints={{ arr_traefik_entrypoint }} + - traefik.http.routers.{{ name }}.tls={{ arr_traefik_tls | bool | ternary('true', 'false') }} + - traefik.http.services.{{ name }}.loadbalancer.server.port={{ service.port }} + networks: + - {{ arr_traefik_network }} +{% endif %} + volumes: + - {{ arr_config_root }}/{{ name }}:/config + - {{ arr_media_root }}:/media + - {{ arr_downloads_root }}:/downloads +{% endfor %} +{% if arr_routing_mode == "traefik" %} +networks: + {{ arr_traefik_network }}: + external: true +{% endif %} diff --git a/roles/arr/templates/env.j2 b/roles/arr/templates/env.j2 new file mode 100644 index 0000000..8bf8dcc --- /dev/null +++ b/roles/arr/templates/env.j2 @@ -0,0 +1,4 @@ +PUID={{ arr_puid }} +PGID={{ arr_pgid }} +TZ={{ arr_timezone }} + diff --git a/roles/authentik/README.md b/roles/authentik/README.md new file mode 100644 index 0000000..3741ad6 --- /dev/null +++ b/roles/authentik/README.md @@ -0,0 +1,7 @@ +# authentik + +Deploys authentik with PostgreSQL and Redis. Override secret values from Vault, +SOPS, or Ansible Vault before production use. + +Tags: `authentik`, `config`, `compose`, `firewall`. + diff --git a/roles/authentik/defaults/main.yml b/roles/authentik/defaults/main.yml new file mode 100644 index 0000000..99b68a7 --- /dev/null +++ b/roles/authentik/defaults/main.yml @@ -0,0 +1,24 @@ +--- +authentik_dir: /srv/docker/authentik +authentik_image: ghcr.io/goauthentik/server:2025.6 +authentik_postgres_image: docker.io/library/postgres:16-alpine +authentik_redis_image: docker.io/library/redis:7-alpine +authentik_http_port: 9000 +authentik_https_port: 9443 +authentik_domain: auth.example.invalid +authentik_routing_mode: "{{ nbops_service_routing_mode }}" +authentik_traefik_network: "{{ nbops_proxy_network }}" +authentik_traefik_entrypoint: "{{ nbops_traefik_entrypoint }}" +authentik_traefik_tls: "{{ nbops_traefik_tls }}" +authentik_secret_key: "{{ vault_authentik_secret_key | default('CHANGE_ME_IN_VAULT') }}" +authentik_postgres_password: "{{ vault_authentik_postgres_password | default('CHANGE_ME_IN_VAULT') }}" +authentik_env: + AUTHENTIK_SECRET_KEY: "{{ authentik_secret_key }}" + AUTHENTIK_POSTGRESQL__HOST: postgresql + AUTHENTIK_POSTGRESQL__USER: authentik + AUTHENTIK_POSTGRESQL__NAME: authentik + AUTHENTIK_POSTGRESQL__PASSWORD: "{{ authentik_postgres_password }}" + AUTHENTIK_REDIS__HOST: redis +authentik_tcp_ports: + - "{{ authentik_http_port }}" + - "{{ authentik_https_port }}" diff --git a/roles/authentik/files/.gitkeep b/roles/authentik/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/authentik/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/authentik/handlers/main.yml b/roles/authentik/handlers/main.yml new file mode 100644 index 0000000..bf098a9 --- /dev/null +++ b/roles/authentik/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: restart authentik + community.docker.docker_compose_v2: + project_src: "{{ authentik_dir }}" + state: restarted + diff --git a/roles/authentik/tasks/main.yml b/roles/authentik/tasks/main.yml new file mode 100644 index 0000000..1c6be2b --- /dev/null +++ b/roles/authentik/tasks/main.yml @@ -0,0 +1,51 @@ +--- +- name: Create authentik directory + ansible.builtin.file: + path: "{{ authentik_dir }}" + state: directory + owner: root + group: root + mode: "0755" + tags: [authentik] + +- name: Render authentik environment + ansible.builtin.template: + src: env.j2 + dest: "{{ authentik_dir }}/.env" + owner: root + group: root + mode: "0600" + no_log: true + tags: [authentik, config] + +- name: Ensure authentik proxy network exists + community.docker.docker_network: + name: "{{ authentik_traefik_network }}" + state: present + when: authentik_routing_mode == "traefik" + tags: [authentik, docker] + +- name: Render authentik Compose file + ansible.builtin.template: + src: compose.yaml.j2 + dest: "{{ authentik_dir }}/compose.yaml" + owner: root + group: root + mode: "0644" + notify: restart authentik + tags: [authentik, compose] + +- name: Allow authentik ports + community.general.ufw: + rule: allow + port: "{{ item }}" + proto: tcp + loop: "{{ authentik_tcp_ports }}" + when: authentik_routing_mode == "host" + tags: [authentik, firewall] + +- name: Start authentik + community.docker.docker_compose_v2: + project_src: "{{ authentik_dir }}" + state: present + tags: [authentik, compose] diff --git a/roles/authentik/templates/compose.yaml.j2 b/roles/authentik/templates/compose.yaml.j2 new file mode 100644 index 0000000..82efd02 --- /dev/null +++ b/roles/authentik/templates/compose.yaml.j2 @@ -0,0 +1,51 @@ +services: + postgresql: + image: {{ authentik_postgres_image }} + restart: unless-stopped + environment: + POSTGRES_DB: authentik + POSTGRES_USER: authentik + POSTGRES_PASSWORD: ${PG_PASS} + volumes: + - ./postgresql:/var/lib/postgresql/data + redis: + image: {{ authentik_redis_image }} + restart: unless-stopped + server: + image: {{ authentik_image }} + restart: unless-stopped + command: server + env_file: .env +{% if authentik_routing_mode == "host" %} + ports: + - "{{ authentik_http_port }}:9000" + - "{{ authentik_https_port }}:9443" +{% endif %} +{% if authentik_routing_mode == "traefik" %} + # internal HTTP service port: 9000; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.authentik.rule=Host(`{{ authentik_domain }}`) + - traefik.http.routers.authentik.entrypoints={{ authentik_traefik_entrypoint }} + - traefik.http.routers.authentik.tls={{ authentik_traefik_tls | bool | ternary('true', 'false') }} + - traefik.http.services.authentik.loadbalancer.server.port=9000 + networks: + - default + - {{ authentik_traefik_network }} +{% endif %} + depends_on: + - postgresql + - redis + worker: + image: {{ authentik_image }} + restart: unless-stopped + command: worker + env_file: .env + depends_on: + - postgresql + - redis +{% if authentik_routing_mode == "traefik" %} +networks: + {{ authentik_traefik_network }}: + external: true +{% endif %} diff --git a/roles/authentik/templates/env.j2 b/roles/authentik/templates/env.j2 new file mode 100644 index 0000000..3a8c45e --- /dev/null +++ b/roles/authentik/templates/env.j2 @@ -0,0 +1,5 @@ +PG_PASS={{ authentik_postgres_password }} +{% for key, value in authentik_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/authentik_ldap/README.md b/roles/authentik_ldap/README.md new file mode 100644 index 0000000..7d27bcb --- /dev/null +++ b/roles/authentik_ldap/README.md @@ -0,0 +1,4 @@ +# authentik_ldap + +Deploys an authentik LDAP outpost. + diff --git a/roles/authentik_ldap/defaults/main.yml b/roles/authentik_ldap/defaults/main.yml new file mode 100644 index 0000000..2b53715 --- /dev/null +++ b/roles/authentik_ldap/defaults/main.yml @@ -0,0 +1,15 @@ +--- +authentik_ldap_name: authentik-ldap +authentik_ldap_dir: /srv/docker/authentik-ldap +authentik_ldap_image: ghcr.io/goauthentik/ldap:2025.6 +authentik_ldap_routing_mode: host +authentik_ldap_env: + AUTHENTIK_HOST: https://auth.example.invalid + AUTHENTIK_TOKEN: "{{ vault_authentik_ldap_token | default('CHANGE_ME_IN_VAULT') }}" +authentik_ldap_directories: + - "{{ authentik_ldap_dir }}" +authentik_ldap_volumes: [] +authentik_ldap_ports: + - { host: 3389, container: 3389, protocol: tcp } + - { host: 6636, container: 6636, protocol: tcp } + diff --git a/roles/authentik_ldap/files/.gitkeep b/roles/authentik_ldap/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/authentik_ldap/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/authentik_ldap/handlers/.gitkeep b/roles/authentik_ldap/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/authentik_ldap/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/authentik_ldap/tasks/main.yml b/roles/authentik_ldap/tasks/main.yml new file mode 100644 index 0000000..826c2f6 --- /dev/null +++ b/roles/authentik_ldap/tasks/main.yml @@ -0,0 +1,16 @@ +--- +- name: Deploy authentik LDAP outpost + ansible.builtin.include_role: + name: docker_app + vars: + docker_app_env_template_src: "{{ playbook_dir }}/../roles/authentik_ldap/templates/env.j2" + docker_app_compose_template_src: "{{ playbook_dir }}/../roles/authentik_ldap/templates/compose.yaml.j2" + docker_app_name: "{{ authentik_ldap_name }}" + docker_app_dir: "{{ authentik_ldap_dir }}" + docker_app_image: "{{ authentik_ldap_image }}" + docker_app_env: "{{ authentik_ldap_env }}" + docker_app_directories: "{{ authentik_ldap_directories }}" + docker_app_volumes: "{{ authentik_ldap_volumes }}" + docker_app_ports: "{{ authentik_ldap_ports }}" + docker_app_routing_mode: "{{ authentik_ldap_routing_mode }}" + diff --git a/roles/authentik_ldap/templates/.gitkeep b/roles/authentik_ldap/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/authentik_ldap/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/authentik_ldap/templates/compose.yaml.j2 b/roles/authentik_ldap/templates/compose.yaml.j2 new file mode 100644 index 0000000..6de1603 --- /dev/null +++ b/roles/authentik_ldap/templates/compose.yaml.j2 @@ -0,0 +1,90 @@ +services: + {{ docker_app_name }}: + image: {{ docker_app_image }} + restart: {{ docker_app_restart }} +{% if docker_app_command | length > 0 %} + command: {{ docker_app_command | to_json }} +{% endif %} +{% if docker_app_user | length > 0 %} + user: "{{ docker_app_user }}" +{% endif %} +{% if docker_app_network_mode | length > 0 %} + network_mode: {{ docker_app_network_mode }} +{% endif %} +{% if docker_app_privileged | bool %} + privileged: true +{% endif %} +{% if docker_app_env | length > 0 %} + env_file: .env +{% endif %} +{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} + ports: +{% for port in docker_app_ports %} + - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_service_port %} + # internal service port: {{ docker_app_service_port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }} + - traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }} + - traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }} +{% if docker_app_traefik_middlewares | length > 0 %} + - traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }} +{% endif %} + - traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }} +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% elif docker_app_labels | length > 0 %} + labels: +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% endif %} +{% if docker_app_volumes | length > 0 %} + volumes: +{% for volume in docker_app_volumes %} + - {{ volume }} +{% endfor %} +{% endif %} +{% if docker_app_devices | length > 0 %} + devices: +{% for device in docker_app_devices %} + - {{ device }} +{% endfor %} +{% endif %} +{% if docker_app_cap_add | length > 0 %} + cap_add: +{% for capability in docker_app_cap_add %} + - {{ capability }} +{% endfor %} +{% endif %} +{% if docker_app_group_add | length > 0 %} + group_add: +{% for group in docker_app_group_add %} + - "{{ group }}" +{% endfor %} +{% endif %} +{% if docker_app_extra_hosts | length > 0 %} + extra_hosts: +{% for host in docker_app_extra_hosts %} + - {{ host }} +{% endfor %} +{% endif %} +{% if docker_app_depends_on | length > 0 %} + depends_on: +{% for dependency in docker_app_depends_on %} + - {{ dependency }} +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} + networks: + - {{ docker_app_traefik_network }} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} +networks: + {{ docker_app_traefik_network }}: + external: true +{% endif %} diff --git a/roles/authentik_ldap/templates/env.j2 b/roles/authentik_ldap/templates/env.j2 new file mode 100644 index 0000000..c4f1a9f --- /dev/null +++ b/roles/authentik_ldap/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in docker_app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/authentik_proxy/README.md b/roles/authentik_proxy/README.md new file mode 100644 index 0000000..2fb9891 --- /dev/null +++ b/roles/authentik_proxy/README.md @@ -0,0 +1,4 @@ +# authentik_proxy + +Deploys an authentik proxy outpost. + diff --git a/roles/authentik_proxy/defaults/main.yml b/roles/authentik_proxy/defaults/main.yml new file mode 100644 index 0000000..6ca45a9 --- /dev/null +++ b/roles/authentik_proxy/defaults/main.yml @@ -0,0 +1,17 @@ +--- +authentik_proxy_name: authentik-proxy +authentik_proxy_dir: /srv/docker/authentik-proxy +authentik_proxy_image: ghcr.io/goauthentik/proxy:2025.6 +authentik_proxy_domain: proxy.example.invalid +authentik_proxy_service_port: 9000 +authentik_proxy_routing_mode: "{{ nbops_service_routing_mode }}" +authentik_proxy_env: + AUTHENTIK_HOST: https://auth.example.invalid + AUTHENTIK_TOKEN: "{{ vault_authentik_proxy_token | default('CHANGE_ME_IN_VAULT') }}" +authentik_proxy_directories: + - "{{ authentik_proxy_dir }}" +authentik_proxy_volumes: [] +authentik_proxy_ports: + - { host: 9001, container: 9000, protocol: tcp } + - { host: 9444, container: 9443, protocol: tcp } + diff --git a/roles/authentik_proxy/files/.gitkeep b/roles/authentik_proxy/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/authentik_proxy/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/authentik_proxy/handlers/.gitkeep b/roles/authentik_proxy/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/authentik_proxy/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/authentik_proxy/tasks/main.yml b/roles/authentik_proxy/tasks/main.yml new file mode 100644 index 0000000..12e5b35 --- /dev/null +++ b/roles/authentik_proxy/tasks/main.yml @@ -0,0 +1,18 @@ +--- +- name: Deploy authentik proxy outpost + ansible.builtin.include_role: + name: docker_app + vars: + docker_app_env_template_src: "{{ playbook_dir }}/../roles/authentik_proxy/templates/env.j2" + docker_app_compose_template_src: "{{ playbook_dir }}/../roles/authentik_proxy/templates/compose.yaml.j2" + docker_app_name: "{{ authentik_proxy_name }}" + docker_app_dir: "{{ authentik_proxy_dir }}" + docker_app_image: "{{ authentik_proxy_image }}" + docker_app_env: "{{ authentik_proxy_env }}" + docker_app_directories: "{{ authentik_proxy_directories }}" + docker_app_volumes: "{{ authentik_proxy_volumes }}" + docker_app_ports: "{{ authentik_proxy_ports }}" + docker_app_routing_mode: "{{ authentik_proxy_routing_mode }}" + docker_app_domain: "{{ authentik_proxy_domain }}" + docker_app_service_port: "{{ authentik_proxy_service_port }}" + diff --git a/roles/authentik_proxy/templates/.gitkeep b/roles/authentik_proxy/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/authentik_proxy/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/authentik_proxy/templates/compose.yaml.j2 b/roles/authentik_proxy/templates/compose.yaml.j2 new file mode 100644 index 0000000..6de1603 --- /dev/null +++ b/roles/authentik_proxy/templates/compose.yaml.j2 @@ -0,0 +1,90 @@ +services: + {{ docker_app_name }}: + image: {{ docker_app_image }} + restart: {{ docker_app_restart }} +{% if docker_app_command | length > 0 %} + command: {{ docker_app_command | to_json }} +{% endif %} +{% if docker_app_user | length > 0 %} + user: "{{ docker_app_user }}" +{% endif %} +{% if docker_app_network_mode | length > 0 %} + network_mode: {{ docker_app_network_mode }} +{% endif %} +{% if docker_app_privileged | bool %} + privileged: true +{% endif %} +{% if docker_app_env | length > 0 %} + env_file: .env +{% endif %} +{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} + ports: +{% for port in docker_app_ports %} + - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_service_port %} + # internal service port: {{ docker_app_service_port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }} + - traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }} + - traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }} +{% if docker_app_traefik_middlewares | length > 0 %} + - traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }} +{% endif %} + - traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }} +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% elif docker_app_labels | length > 0 %} + labels: +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% endif %} +{% if docker_app_volumes | length > 0 %} + volumes: +{% for volume in docker_app_volumes %} + - {{ volume }} +{% endfor %} +{% endif %} +{% if docker_app_devices | length > 0 %} + devices: +{% for device in docker_app_devices %} + - {{ device }} +{% endfor %} +{% endif %} +{% if docker_app_cap_add | length > 0 %} + cap_add: +{% for capability in docker_app_cap_add %} + - {{ capability }} +{% endfor %} +{% endif %} +{% if docker_app_group_add | length > 0 %} + group_add: +{% for group in docker_app_group_add %} + - "{{ group }}" +{% endfor %} +{% endif %} +{% if docker_app_extra_hosts | length > 0 %} + extra_hosts: +{% for host in docker_app_extra_hosts %} + - {{ host }} +{% endfor %} +{% endif %} +{% if docker_app_depends_on | length > 0 %} + depends_on: +{% for dependency in docker_app_depends_on %} + - {{ dependency }} +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} + networks: + - {{ docker_app_traefik_network }} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} +networks: + {{ docker_app_traefik_network }}: + external: true +{% endif %} diff --git a/roles/authentik_proxy/templates/env.j2 b/roles/authentik_proxy/templates/env.j2 new file mode 100644 index 0000000..c4f1a9f --- /dev/null +++ b/roles/authentik_proxy/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in docker_app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/authentik_rac/README.md b/roles/authentik_rac/README.md new file mode 100644 index 0000000..43e3448 --- /dev/null +++ b/roles/authentik_rac/README.md @@ -0,0 +1,4 @@ +# authentik_rac + +Deploys an authentik RAC outpost. + diff --git a/roles/authentik_rac/defaults/main.yml b/roles/authentik_rac/defaults/main.yml new file mode 100644 index 0000000..a114ef7 --- /dev/null +++ b/roles/authentik_rac/defaults/main.yml @@ -0,0 +1,16 @@ +--- +authentik_rac_name: authentik-rac +authentik_rac_dir: /srv/docker/authentik-rac +authentik_rac_image: ghcr.io/goauthentik/rac:2025.6 +authentik_rac_domain: rac.example.invalid +authentik_rac_service_port: 3389 +authentik_rac_routing_mode: host +authentik_rac_env: + AUTHENTIK_HOST: https://auth.example.invalid + AUTHENTIK_TOKEN: "{{ vault_authentik_rac_token | default('CHANGE_ME_IN_VAULT') }}" +authentik_rac_directories: + - "{{ authentik_rac_dir }}" +authentik_rac_volumes: [] +authentik_rac_ports: + - { host: 3390, container: 3389, protocol: tcp } + diff --git a/roles/authentik_rac/files/.gitkeep b/roles/authentik_rac/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/authentik_rac/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/authentik_rac/handlers/.gitkeep b/roles/authentik_rac/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/authentik_rac/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/authentik_rac/tasks/main.yml b/roles/authentik_rac/tasks/main.yml new file mode 100644 index 0000000..232965e --- /dev/null +++ b/roles/authentik_rac/tasks/main.yml @@ -0,0 +1,18 @@ +--- +- name: Deploy authentik RAC outpost + ansible.builtin.include_role: + name: docker_app + vars: + docker_app_env_template_src: "{{ playbook_dir }}/../roles/authentik_rac/templates/env.j2" + docker_app_compose_template_src: "{{ playbook_dir }}/../roles/authentik_rac/templates/compose.yaml.j2" + docker_app_name: "{{ authentik_rac_name }}" + docker_app_dir: "{{ authentik_rac_dir }}" + docker_app_image: "{{ authentik_rac_image }}" + docker_app_env: "{{ authentik_rac_env }}" + docker_app_directories: "{{ authentik_rac_directories }}" + docker_app_volumes: "{{ authentik_rac_volumes }}" + docker_app_ports: "{{ authentik_rac_ports }}" + docker_app_routing_mode: "{{ authentik_rac_routing_mode }}" + docker_app_domain: "{{ authentik_rac_domain }}" + docker_app_service_port: "{{ authentik_rac_service_port }}" + diff --git a/roles/authentik_rac/templates/.gitkeep b/roles/authentik_rac/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/authentik_rac/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/authentik_rac/templates/compose.yaml.j2 b/roles/authentik_rac/templates/compose.yaml.j2 new file mode 100644 index 0000000..6de1603 --- /dev/null +++ b/roles/authentik_rac/templates/compose.yaml.j2 @@ -0,0 +1,90 @@ +services: + {{ docker_app_name }}: + image: {{ docker_app_image }} + restart: {{ docker_app_restart }} +{% if docker_app_command | length > 0 %} + command: {{ docker_app_command | to_json }} +{% endif %} +{% if docker_app_user | length > 0 %} + user: "{{ docker_app_user }}" +{% endif %} +{% if docker_app_network_mode | length > 0 %} + network_mode: {{ docker_app_network_mode }} +{% endif %} +{% if docker_app_privileged | bool %} + privileged: true +{% endif %} +{% if docker_app_env | length > 0 %} + env_file: .env +{% endif %} +{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} + ports: +{% for port in docker_app_ports %} + - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_service_port %} + # internal service port: {{ docker_app_service_port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }} + - traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }} + - traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }} +{% if docker_app_traefik_middlewares | length > 0 %} + - traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }} +{% endif %} + - traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }} +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% elif docker_app_labels | length > 0 %} + labels: +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% endif %} +{% if docker_app_volumes | length > 0 %} + volumes: +{% for volume in docker_app_volumes %} + - {{ volume }} +{% endfor %} +{% endif %} +{% if docker_app_devices | length > 0 %} + devices: +{% for device in docker_app_devices %} + - {{ device }} +{% endfor %} +{% endif %} +{% if docker_app_cap_add | length > 0 %} + cap_add: +{% for capability in docker_app_cap_add %} + - {{ capability }} +{% endfor %} +{% endif %} +{% if docker_app_group_add | length > 0 %} + group_add: +{% for group in docker_app_group_add %} + - "{{ group }}" +{% endfor %} +{% endif %} +{% if docker_app_extra_hosts | length > 0 %} + extra_hosts: +{% for host in docker_app_extra_hosts %} + - {{ host }} +{% endfor %} +{% endif %} +{% if docker_app_depends_on | length > 0 %} + depends_on: +{% for dependency in docker_app_depends_on %} + - {{ dependency }} +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} + networks: + - {{ docker_app_traefik_network }} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} +networks: + {{ docker_app_traefik_network }}: + external: true +{% endif %} diff --git a/roles/authentik_rac/templates/env.j2 b/roles/authentik_rac/templates/env.j2 new file mode 100644 index 0000000..c4f1a9f --- /dev/null +++ b/roles/authentik_rac/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in docker_app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/base/README.md b/roles/base/README.md new file mode 100644 index 0000000..b8b89d6 --- /dev/null +++ b/roles/base/README.md @@ -0,0 +1,9 @@ +# base + +Configures the operating system baseline: hostname, locale, timezone, packages, +Chrony, QEMU guest agent, persistent journald, sysctl, aliases, PKI directories, +and `/srv` layout. + +Tags: `base`, `hostname`, `packages`, `locale`, `timezone`, `directories`, +`journald`, `sysctl`, `shell`, `services`, `pki`. + diff --git a/roles/base/defaults/main.yml b/roles/base/defaults/main.yml new file mode 100644 index 0000000..94d1d10 --- /dev/null +++ b/roles/base/defaults/main.yml @@ -0,0 +1,13 @@ +--- +base_hostname: "{{ nbops_hostname }}" +base_fqdn: "{{ nbops_fqdn }}" +base_timezone: "{{ nbops_timezone }}" +base_locale: "{{ nbops_locale }}" +base_packages: "{{ nbops_base_packages }}" +base_standard_directories: "{{ nbops_standard_directories }}" +base_sysctl: "{{ nbops_sysctl }}" +base_aliases: + ll: "ls -alF" + la: "ls -A" + l: "ls -CF" + diff --git a/roles/base/files/.gitkeep b/roles/base/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/base/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/base/handlers/main.yml b/roles/base/handlers/main.yml new file mode 100644 index 0000000..6596d8b --- /dev/null +++ b/roles/base/handlers/main.yml @@ -0,0 +1,16 @@ +--- +- name: restart chrony + ansible.builtin.service: + name: chrony + state: restarted + +- name: restart qemu guest agent + ansible.builtin.service: + name: qemu-guest-agent + state: restarted + +- name: restart systemd-journald + ansible.builtin.service: + name: systemd-journald + state: restarted + diff --git a/roles/base/tasks/main.yml b/roles/base/tasks/main.yml new file mode 100644 index 0000000..ee632ca --- /dev/null +++ b/roles/base/tasks/main.yml @@ -0,0 +1,104 @@ +--- +- name: Set hostname + ansible.builtin.hostname: + name: "{{ base_hostname }}" + tags: [base, hostname] + +- name: Configure hosts file + ansible.builtin.template: + src: hosts.j2 + dest: /etc/hosts + owner: root + group: root + mode: "0644" + tags: [base, hostname] + +- name: Install base packages + ansible.builtin.apt: + name: "{{ base_packages }}" + state: present + update_cache: true + cache_valid_time: 3600 + tags: [base, packages] + +- name: Configure locale + community.general.locale_gen: + name: "{{ base_locale }}" + state: present + tags: [base, locale] + +- name: Set system locale + ansible.builtin.copy: + dest: /etc/default/locale + owner: root + group: root + mode: "0644" + content: | + LANG={{ base_locale }} + LC_ALL={{ base_locale }} + tags: [base, locale] + +- name: Configure timezone + community.general.timezone: + name: "{{ base_timezone }}" + notify: restart chrony + tags: [base, timezone] + +- name: Ensure standard directories exist + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: root + group: root + mode: "{{ '0700' if item.endswith('/private') else '0755' }}" + loop: "{{ base_standard_directories }}" + tags: [base, directories] + +- name: Configure persistent journald storage + ansible.builtin.copy: + dest: /etc/systemd/journald.conf.d/10-nbops.conf + owner: root + group: root + mode: "0644" + content: | + [Journal] + Storage=persistent + Compress=yes + SystemMaxUse=1G + notify: restart systemd-journald + tags: [base, journald] + +- name: Configure sysctl + ansible.posix.sysctl: + name: "{{ item.key }}" + value: "{{ item.value }}" + state: present + reload: true + loop: "{{ base_sysctl | dict2items }}" + tags: [base, sysctl] + +- name: Configure shell aliases + ansible.builtin.template: + src: nbops-aliases.sh.j2 + dest: /etc/profile.d/nbops-aliases.sh + owner: root + group: root + mode: "0644" + tags: [base, shell] + +- name: Enable base services + ansible.builtin.service: + name: "{{ item }}" + enabled: true + state: started + loop: + - chrony + - qemu-guest-agent + tags: [base, services] + +- name: Refresh CA certificate trust + ansible.builtin.command: + cmd: update-ca-certificates + changed_when: false + tags: [base, pki] + diff --git a/roles/base/templates/hosts.j2 b/roles/base/templates/hosts.j2 new file mode 100644 index 0000000..3629de8 --- /dev/null +++ b/roles/base/templates/hosts.j2 @@ -0,0 +1,7 @@ +127.0.0.1 localhost +127.0.1.1 {{ base_fqdn }} {{ base_hostname }} + +::1 localhost ip6-localhost ip6-loopback +ff02::1 ip6-allnodes +ff02::2 ip6-allrouters + diff --git a/roles/base/templates/nbops-aliases.sh.j2 b/roles/base/templates/nbops-aliases.sh.j2 new file mode 100644 index 0000000..f4507ff --- /dev/null +++ b/roles/base/templates/nbops-aliases.sh.j2 @@ -0,0 +1,4 @@ +{% for name, command in base_aliases.items() %} +alias {{ name }}='{{ command }}' +{% endfor %} + diff --git a/roles/cleanuparr/README.md b/roles/cleanuparr/README.md new file mode 100644 index 0000000..a76c405 --- /dev/null +++ b/roles/cleanuparr/README.md @@ -0,0 +1,4 @@ +# cleanuparr + +Deploys Cleanuparr with Docker Compose. + diff --git a/roles/cleanuparr/defaults/main.yml b/roles/cleanuparr/defaults/main.yml new file mode 100644 index 0000000..da16058 --- /dev/null +++ b/roles/cleanuparr/defaults/main.yml @@ -0,0 +1,18 @@ +--- +cleanuparr_name: cleanuparr +cleanuparr_dir: /srv/docker/cleanuparr +cleanuparr_image: ghcr.io/cleanuparr/cleanuparr:latest +cleanuparr_domain: cleanuparr.example.invalid +cleanuparr_service_port: 11011 +cleanuparr_routing_mode: "{{ nbops_service_routing_mode }}" +cleanuparr_env: + TZ: "{{ nbops_timezone }}" +cleanuparr_directories: + - "{{ cleanuparr_dir }}" + - /srv/config/cleanuparr +cleanuparr_volumes: + - /srv/config/cleanuparr:/config + - /srv/data/downloads:/downloads +cleanuparr_ports: + - { host: 11011, container: 11011, protocol: tcp } + diff --git a/roles/cleanuparr/files/.gitkeep b/roles/cleanuparr/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/cleanuparr/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/cleanuparr/handlers/.gitkeep b/roles/cleanuparr/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/cleanuparr/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/cleanuparr/tasks/main.yml b/roles/cleanuparr/tasks/main.yml new file mode 100644 index 0000000..01fd409 --- /dev/null +++ b/roles/cleanuparr/tasks/main.yml @@ -0,0 +1,18 @@ +--- +- name: Deploy Cleanuparr + ansible.builtin.include_role: + name: docker_app + vars: + docker_app_env_template_src: "{{ playbook_dir }}/../roles/cleanuparr/templates/env.j2" + docker_app_compose_template_src: "{{ playbook_dir }}/../roles/cleanuparr/templates/compose.yaml.j2" + docker_app_name: "{{ cleanuparr_name }}" + docker_app_dir: "{{ cleanuparr_dir }}" + docker_app_image: "{{ cleanuparr_image }}" + docker_app_env: "{{ cleanuparr_env }}" + docker_app_directories: "{{ cleanuparr_directories }}" + docker_app_volumes: "{{ cleanuparr_volumes }}" + docker_app_ports: "{{ cleanuparr_ports }}" + docker_app_routing_mode: "{{ cleanuparr_routing_mode }}" + docker_app_domain: "{{ cleanuparr_domain }}" + docker_app_service_port: "{{ cleanuparr_service_port }}" + diff --git a/roles/cleanuparr/templates/.gitkeep b/roles/cleanuparr/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/cleanuparr/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/cleanuparr/templates/compose.yaml.j2 b/roles/cleanuparr/templates/compose.yaml.j2 new file mode 100644 index 0000000..6de1603 --- /dev/null +++ b/roles/cleanuparr/templates/compose.yaml.j2 @@ -0,0 +1,90 @@ +services: + {{ docker_app_name }}: + image: {{ docker_app_image }} + restart: {{ docker_app_restart }} +{% if docker_app_command | length > 0 %} + command: {{ docker_app_command | to_json }} +{% endif %} +{% if docker_app_user | length > 0 %} + user: "{{ docker_app_user }}" +{% endif %} +{% if docker_app_network_mode | length > 0 %} + network_mode: {{ docker_app_network_mode }} +{% endif %} +{% if docker_app_privileged | bool %} + privileged: true +{% endif %} +{% if docker_app_env | length > 0 %} + env_file: .env +{% endif %} +{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} + ports: +{% for port in docker_app_ports %} + - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_service_port %} + # internal service port: {{ docker_app_service_port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }} + - traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }} + - traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }} +{% if docker_app_traefik_middlewares | length > 0 %} + - traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }} +{% endif %} + - traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }} +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% elif docker_app_labels | length > 0 %} + labels: +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% endif %} +{% if docker_app_volumes | length > 0 %} + volumes: +{% for volume in docker_app_volumes %} + - {{ volume }} +{% endfor %} +{% endif %} +{% if docker_app_devices | length > 0 %} + devices: +{% for device in docker_app_devices %} + - {{ device }} +{% endfor %} +{% endif %} +{% if docker_app_cap_add | length > 0 %} + cap_add: +{% for capability in docker_app_cap_add %} + - {{ capability }} +{% endfor %} +{% endif %} +{% if docker_app_group_add | length > 0 %} + group_add: +{% for group in docker_app_group_add %} + - "{{ group }}" +{% endfor %} +{% endif %} +{% if docker_app_extra_hosts | length > 0 %} + extra_hosts: +{% for host in docker_app_extra_hosts %} + - {{ host }} +{% endfor %} +{% endif %} +{% if docker_app_depends_on | length > 0 %} + depends_on: +{% for dependency in docker_app_depends_on %} + - {{ dependency }} +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} + networks: + - {{ docker_app_traefik_network }} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} +networks: + {{ docker_app_traefik_network }}: + external: true +{% endif %} diff --git a/roles/cleanuparr/templates/env.j2 b/roles/cleanuparr/templates/env.j2 new file mode 100644 index 0000000..c4f1a9f --- /dev/null +++ b/roles/cleanuparr/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in docker_app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/code_server/README.md b/roles/code_server/README.md new file mode 100644 index 0000000..07f1d25 --- /dev/null +++ b/roles/code_server/README.md @@ -0,0 +1,4 @@ +# code_server + +Deploys code-server with Docker Compose. + diff --git a/roles/code_server/defaults/main.yml b/roles/code_server/defaults/main.yml new file mode 100644 index 0000000..c207e06 --- /dev/null +++ b/roles/code_server/defaults/main.yml @@ -0,0 +1,22 @@ +--- +code_server_name: code-server +code_server_dir: /srv/docker/code-server +code_server_image: lscr.io/linuxserver/code-server:latest +code_server_domain: code.example.invalid +code_server_service_port: 8443 +code_server_routing_mode: "{{ nbops_service_routing_mode }}" +code_server_env: + PUID: "1000" + PGID: "1000" + TZ: "{{ nbops_timezone }}" + PASSWORD: "{{ vault_code_server_password | default('CHANGE_ME_IN_VAULT') }}" +code_server_directories: + - "{{ code_server_dir }}" + - /srv/config/code-server + - /srv/data/workspace +code_server_volumes: + - /srv/config/code-server:/config + - /srv/data/workspace:/workspace +code_server_ports: + - { host: 8443, container: 8443, protocol: tcp } + diff --git a/roles/code_server/files/.gitkeep b/roles/code_server/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/code_server/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/code_server/handlers/.gitkeep b/roles/code_server/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/code_server/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/code_server/tasks/main.yml b/roles/code_server/tasks/main.yml new file mode 100644 index 0000000..ae7a8a3 --- /dev/null +++ b/roles/code_server/tasks/main.yml @@ -0,0 +1,18 @@ +--- +- name: Deploy code-server + ansible.builtin.include_role: + name: docker_app + vars: + docker_app_env_template_src: "{{ playbook_dir }}/../roles/code_server/templates/env.j2" + docker_app_compose_template_src: "{{ playbook_dir }}/../roles/code_server/templates/compose.yaml.j2" + docker_app_name: "{{ code_server_name }}" + docker_app_dir: "{{ code_server_dir }}" + docker_app_image: "{{ code_server_image }}" + docker_app_env: "{{ code_server_env }}" + docker_app_directories: "{{ code_server_directories }}" + docker_app_volumes: "{{ code_server_volumes }}" + docker_app_ports: "{{ code_server_ports }}" + docker_app_routing_mode: "{{ code_server_routing_mode }}" + docker_app_domain: "{{ code_server_domain }}" + docker_app_service_port: "{{ code_server_service_port }}" + diff --git a/roles/code_server/templates/.gitkeep b/roles/code_server/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/code_server/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/code_server/templates/compose.yaml.j2 b/roles/code_server/templates/compose.yaml.j2 new file mode 100644 index 0000000..6de1603 --- /dev/null +++ b/roles/code_server/templates/compose.yaml.j2 @@ -0,0 +1,90 @@ +services: + {{ docker_app_name }}: + image: {{ docker_app_image }} + restart: {{ docker_app_restart }} +{% if docker_app_command | length > 0 %} + command: {{ docker_app_command | to_json }} +{% endif %} +{% if docker_app_user | length > 0 %} + user: "{{ docker_app_user }}" +{% endif %} +{% if docker_app_network_mode | length > 0 %} + network_mode: {{ docker_app_network_mode }} +{% endif %} +{% if docker_app_privileged | bool %} + privileged: true +{% endif %} +{% if docker_app_env | length > 0 %} + env_file: .env +{% endif %} +{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} + ports: +{% for port in docker_app_ports %} + - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_service_port %} + # internal service port: {{ docker_app_service_port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }} + - traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }} + - traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }} +{% if docker_app_traefik_middlewares | length > 0 %} + - traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }} +{% endif %} + - traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }} +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% elif docker_app_labels | length > 0 %} + labels: +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% endif %} +{% if docker_app_volumes | length > 0 %} + volumes: +{% for volume in docker_app_volumes %} + - {{ volume }} +{% endfor %} +{% endif %} +{% if docker_app_devices | length > 0 %} + devices: +{% for device in docker_app_devices %} + - {{ device }} +{% endfor %} +{% endif %} +{% if docker_app_cap_add | length > 0 %} + cap_add: +{% for capability in docker_app_cap_add %} + - {{ capability }} +{% endfor %} +{% endif %} +{% if docker_app_group_add | length > 0 %} + group_add: +{% for group in docker_app_group_add %} + - "{{ group }}" +{% endfor %} +{% endif %} +{% if docker_app_extra_hosts | length > 0 %} + extra_hosts: +{% for host in docker_app_extra_hosts %} + - {{ host }} +{% endfor %} +{% endif %} +{% if docker_app_depends_on | length > 0 %} + depends_on: +{% for dependency in docker_app_depends_on %} + - {{ dependency }} +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} + networks: + - {{ docker_app_traefik_network }} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} +networks: + {{ docker_app_traefik_network }}: + external: true +{% endif %} diff --git a/roles/code_server/templates/env.j2 b/roles/code_server/templates/env.j2 new file mode 100644 index 0000000..c4f1a9f --- /dev/null +++ b/roles/code_server/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in docker_app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/docker/README.md b/roles/docker/README.md new file mode 100644 index 0000000..c9c6ac6 --- /dev/null +++ b/roles/docker/README.md @@ -0,0 +1,8 @@ +# docker + +Installs Docker Engine from Docker's official Debian repository, configures +`/etc/docker/daemon.json`, enables the service, and adds configured users to the +`docker` group. + +Tags: `docker`, `packages`, `config`, `users`, `services`. + diff --git a/roles/docker/defaults/main.yml b/roles/docker/defaults/main.yml new file mode 100644 index 0000000..947f3b7 --- /dev/null +++ b/roles/docker/defaults/main.yml @@ -0,0 +1,18 @@ +--- +docker_apt_key_url: https://download.docker.com/linux/debian/gpg +docker_apt_key_path: /etc/apt/keyrings/docker.asc +docker_apt_architecture: "{{ ansible_architecture | regex_replace('x86_64', 'amd64') | regex_replace('aarch64', 'arm64') }}" +docker_apt_uri: https://download.docker.com/linux/debian +docker_packages: + - docker-ce + - docker-ce-cli + - containerd.io + - docker-buildx-plugin + - docker-compose-plugin +docker_daemon_config: + log-driver: local + live-restore: true + storage-driver: overlay2 + iptables: true +docker_service_state: started +docker_service_enabled: true diff --git a/roles/docker/files/.gitkeep b/roles/docker/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/docker/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/docker/handlers/main.yml b/roles/docker/handlers/main.yml new file mode 100644 index 0000000..cf84b5f --- /dev/null +++ b/roles/docker/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: restart docker + ansible.builtin.service: + name: docker + state: restarted + diff --git a/roles/docker/tasks/main.yml b/roles/docker/tasks/main.yml new file mode 100644 index 0000000..a908b8e --- /dev/null +++ b/roles/docker/tasks/main.yml @@ -0,0 +1,69 @@ +--- +- name: Ensure apt keyring directory exists + ansible.builtin.file: + path: /etc/apt/keyrings + state: directory + owner: root + group: root + mode: "0755" + tags: [docker, packages] + +- name: Install Docker repository signing key + ansible.builtin.get_url: + url: "{{ docker_apt_key_url }}" + dest: "{{ docker_apt_key_path }}" + owner: root + group: root + mode: "0644" + tags: [docker, packages] + +- name: Configure Docker apt repository + ansible.builtin.deb822_repository: + name: docker + types: + - deb + uris: + - "{{ docker_apt_uri }}" + suites: + - "{{ ansible_distribution_release }}" + components: + - stable + architectures: + - "{{ docker_apt_architecture }}" + signed_by: "{{ docker_apt_key_path }}" + state: present + enabled: true + install_python_debian: true + tags: [docker, packages] + +- name: Install Docker Engine and plugins + ansible.builtin.apt: + name: "{{ docker_packages }}" + state: present + update_cache: true + tags: [docker, packages] + +- name: Configure Docker daemon + ansible.builtin.copy: + dest: /etc/docker/daemon.json + owner: root + group: root + mode: "0644" + content: "{{ docker_daemon_config | to_nice_json }}\n" + notify: restart docker + tags: [docker, config] + +- name: Add users to docker group + ansible.builtin.user: + name: "{{ item }}" + groups: docker + append: true + loop: "{{ docker_users }}" + tags: [docker, users] + +- name: Enable Docker service + ansible.builtin.service: + name: docker + enabled: "{{ docker_service_enabled }}" + state: "{{ docker_service_state }}" + tags: [docker, services] diff --git a/roles/docker/templates/.gitkeep b/roles/docker/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/docker/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/docker_app/README.md b/roles/docker_app/README.md new file mode 100644 index 0000000..9150e3d --- /dev/null +++ b/roles/docker_app/README.md @@ -0,0 +1,10 @@ +# docker_app + +Shared implementation role for single-container Docker Compose applications. +Application roles set `docker_app_*` variables and include this role. + +It supports direct host-port publishing with UFW rules or Traefik routing via +labels and a shared Docker network. + +Tags: `docker_app`, `docker`, `config`, `compose`, `firewall`. + diff --git a/roles/docker_app/defaults/main.yml b/roles/docker_app/defaults/main.yml new file mode 100644 index 0000000..c31cfdd --- /dev/null +++ b/roles/docker_app/defaults/main.yml @@ -0,0 +1,32 @@ +--- +docker_app_name: app +docker_app_dir: "/srv/docker/{{ docker_app_name }}" +docker_app_image: "" +docker_app_restart: unless-stopped +docker_app_command: "" +docker_app_user: "" +docker_app_network_mode: "" +docker_app_privileged: false +docker_app_pull: missing +docker_app_env: {} +docker_app_volumes: [] +docker_app_ports: [] +docker_app_devices: [] +docker_app_cap_add: [] +docker_app_group_add: [] +docker_app_extra_hosts: [] +docker_app_depends_on: [] +docker_app_directories: + - "{{ docker_app_dir }}" +docker_app_routing_mode: "{{ nbops_service_routing_mode }}" +docker_app_domain: "{{ docker_app_name }}.{{ nbops_service_domain_suffix }}" +docker_app_service_port: null +docker_app_traefik_network: "{{ nbops_proxy_network }}" +docker_app_traefik_entrypoint: "{{ nbops_traefik_entrypoint }}" +docker_app_traefik_tls: "{{ nbops_traefik_tls }}" +docker_app_traefik_middlewares: [] +docker_app_traefik_rule: "Host(`{{ docker_app_domain }}`)" +docker_app_labels: {} +docker_app_ufw_ports: "{{ docker_app_ports }}" +docker_app_env_template_src: env.j2 +docker_app_compose_template_src: compose.yaml.j2 diff --git a/roles/docker_app/files/.gitkeep b/roles/docker_app/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/docker_app/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/docker_app/handlers/main.yml b/roles/docker_app/handlers/main.yml new file mode 100644 index 0000000..53b70f1 --- /dev/null +++ b/roles/docker_app/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: restart docker app + community.docker.docker_compose_v2: + project_src: "{{ docker_app_dir }}" + state: restarted + diff --git a/roles/docker_app/tasks/main.yml b/roles/docker_app/tasks/main.yml new file mode 100644 index 0000000..b62d689 --- /dev/null +++ b/roles/docker_app/tasks/main.yml @@ -0,0 +1,62 @@ +--- +- name: Validate Docker app image is configured + ansible.builtin.assert: + that: + - docker_app_image | length > 0 + fail_msg: "docker_app_image must be configured for {{ docker_app_name }}." + tags: [docker_app] + +- name: Create Docker app directories + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: root + group: root + mode: "0755" + loop: "{{ docker_app_directories }}" + tags: [docker_app] + +- name: Ensure proxy network exists + community.docker.docker_network: + name: "{{ docker_app_traefik_network }}" + state: present + when: + - docker_app_routing_mode == "traefik" + - docker_app_network_mode | length == 0 + tags: [docker_app, docker] + +- name: Render Docker app environment + ansible.builtin.template: + src: "{{ docker_app_env_template_src }}" + dest: "{{ docker_app_dir }}/.env" + owner: root + group: root + mode: "0600" + no_log: true + tags: [docker_app, config] + +- name: Render Docker app Compose file + ansible.builtin.template: + src: "{{ docker_app_compose_template_src }}" + dest: "{{ docker_app_dir }}/compose.yaml" + owner: root + group: root + mode: "0644" + notify: restart docker app + tags: [docker_app, compose] + +- name: Allow Docker app host ports + community.general.ufw: + rule: allow + port: "{{ item.host | default(item.container) }}" + proto: "{{ item.protocol | default('tcp') }}" + loop: "{{ docker_app_ufw_ports }}" + when: docker_app_routing_mode == "host" + tags: [docker_app, firewall] + +- name: Start Docker app + community.docker.docker_compose_v2: + project_src: "{{ docker_app_dir }}" + state: present + pull: "{{ docker_app_pull }}" + tags: [docker_app, compose] diff --git a/roles/docker_app/templates/compose.yaml.j2 b/roles/docker_app/templates/compose.yaml.j2 new file mode 100644 index 0000000..6de1603 --- /dev/null +++ b/roles/docker_app/templates/compose.yaml.j2 @@ -0,0 +1,90 @@ +services: + {{ docker_app_name }}: + image: {{ docker_app_image }} + restart: {{ docker_app_restart }} +{% if docker_app_command | length > 0 %} + command: {{ docker_app_command | to_json }} +{% endif %} +{% if docker_app_user | length > 0 %} + user: "{{ docker_app_user }}" +{% endif %} +{% if docker_app_network_mode | length > 0 %} + network_mode: {{ docker_app_network_mode }} +{% endif %} +{% if docker_app_privileged | bool %} + privileged: true +{% endif %} +{% if docker_app_env | length > 0 %} + env_file: .env +{% endif %} +{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} + ports: +{% for port in docker_app_ports %} + - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_service_port %} + # internal service port: {{ docker_app_service_port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }} + - traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }} + - traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }} +{% if docker_app_traefik_middlewares | length > 0 %} + - traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }} +{% endif %} + - traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }} +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% elif docker_app_labels | length > 0 %} + labels: +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% endif %} +{% if docker_app_volumes | length > 0 %} + volumes: +{% for volume in docker_app_volumes %} + - {{ volume }} +{% endfor %} +{% endif %} +{% if docker_app_devices | length > 0 %} + devices: +{% for device in docker_app_devices %} + - {{ device }} +{% endfor %} +{% endif %} +{% if docker_app_cap_add | length > 0 %} + cap_add: +{% for capability in docker_app_cap_add %} + - {{ capability }} +{% endfor %} +{% endif %} +{% if docker_app_group_add | length > 0 %} + group_add: +{% for group in docker_app_group_add %} + - "{{ group }}" +{% endfor %} +{% endif %} +{% if docker_app_extra_hosts | length > 0 %} + extra_hosts: +{% for host in docker_app_extra_hosts %} + - {{ host }} +{% endfor %} +{% endif %} +{% if docker_app_depends_on | length > 0 %} + depends_on: +{% for dependency in docker_app_depends_on %} + - {{ dependency }} +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} + networks: + - {{ docker_app_traefik_network }} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} +networks: + {{ docker_app_traefik_network }}: + external: true +{% endif %} diff --git a/roles/docker_app/templates/env.j2 b/roles/docker_app/templates/env.j2 new file mode 100644 index 0000000..c4f1a9f --- /dev/null +++ b/roles/docker_app/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in docker_app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/filebrowser_quantum/README.md b/roles/filebrowser_quantum/README.md new file mode 100644 index 0000000..7f7a785 --- /dev/null +++ b/roles/filebrowser_quantum/README.md @@ -0,0 +1,4 @@ +# filebrowser_quantum + +Deploys Filebrowser Quantum with Docker Compose. + diff --git a/roles/filebrowser_quantum/defaults/main.yml b/roles/filebrowser_quantum/defaults/main.yml new file mode 100644 index 0000000..0430821 --- /dev/null +++ b/roles/filebrowser_quantum/defaults/main.yml @@ -0,0 +1,19 @@ +--- +filebrowser_quantum_name: filebrowser-quantum +filebrowser_quantum_dir: /srv/docker/filebrowser-quantum +filebrowser_quantum_image: gtstef/filebrowser:latest +filebrowser_quantum_domain: files.example.invalid +filebrowser_quantum_service_port: 80 +filebrowser_quantum_routing_mode: "{{ nbops_service_routing_mode }}" +filebrowser_quantum_env: + TZ: "{{ nbops_timezone }}" +filebrowser_quantum_directories: + - "{{ filebrowser_quantum_dir }}" + - /srv/config/filebrowser-quantum + - /srv/data/files +filebrowser_quantum_volumes: + - /srv/config/filebrowser-quantum:/config + - /srv/data/files:/srv +filebrowser_quantum_ports: + - { host: 8089, container: 80, protocol: tcp } + diff --git a/roles/filebrowser_quantum/files/.gitkeep b/roles/filebrowser_quantum/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/filebrowser_quantum/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/filebrowser_quantum/handlers/.gitkeep b/roles/filebrowser_quantum/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/filebrowser_quantum/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/filebrowser_quantum/tasks/main.yml b/roles/filebrowser_quantum/tasks/main.yml new file mode 100644 index 0000000..ac1eade --- /dev/null +++ b/roles/filebrowser_quantum/tasks/main.yml @@ -0,0 +1,18 @@ +--- +- name: Deploy Filebrowser Quantum + ansible.builtin.include_role: + name: docker_app + vars: + docker_app_env_template_src: "{{ playbook_dir }}/../roles/filebrowser_quantum/templates/env.j2" + docker_app_compose_template_src: "{{ playbook_dir }}/../roles/filebrowser_quantum/templates/compose.yaml.j2" + docker_app_name: "{{ filebrowser_quantum_name }}" + docker_app_dir: "{{ filebrowser_quantum_dir }}" + docker_app_image: "{{ filebrowser_quantum_image }}" + docker_app_env: "{{ filebrowser_quantum_env }}" + docker_app_directories: "{{ filebrowser_quantum_directories }}" + docker_app_volumes: "{{ filebrowser_quantum_volumes }}" + docker_app_ports: "{{ filebrowser_quantum_ports }}" + docker_app_routing_mode: "{{ filebrowser_quantum_routing_mode }}" + docker_app_domain: "{{ filebrowser_quantum_domain }}" + docker_app_service_port: "{{ filebrowser_quantum_service_port }}" + diff --git a/roles/filebrowser_quantum/templates/.gitkeep b/roles/filebrowser_quantum/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/filebrowser_quantum/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/filebrowser_quantum/templates/compose.yaml.j2 b/roles/filebrowser_quantum/templates/compose.yaml.j2 new file mode 100644 index 0000000..6de1603 --- /dev/null +++ b/roles/filebrowser_quantum/templates/compose.yaml.j2 @@ -0,0 +1,90 @@ +services: + {{ docker_app_name }}: + image: {{ docker_app_image }} + restart: {{ docker_app_restart }} +{% if docker_app_command | length > 0 %} + command: {{ docker_app_command | to_json }} +{% endif %} +{% if docker_app_user | length > 0 %} + user: "{{ docker_app_user }}" +{% endif %} +{% if docker_app_network_mode | length > 0 %} + network_mode: {{ docker_app_network_mode }} +{% endif %} +{% if docker_app_privileged | bool %} + privileged: true +{% endif %} +{% if docker_app_env | length > 0 %} + env_file: .env +{% endif %} +{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} + ports: +{% for port in docker_app_ports %} + - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_service_port %} + # internal service port: {{ docker_app_service_port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }} + - traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }} + - traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }} +{% if docker_app_traefik_middlewares | length > 0 %} + - traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }} +{% endif %} + - traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }} +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% elif docker_app_labels | length > 0 %} + labels: +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% endif %} +{% if docker_app_volumes | length > 0 %} + volumes: +{% for volume in docker_app_volumes %} + - {{ volume }} +{% endfor %} +{% endif %} +{% if docker_app_devices | length > 0 %} + devices: +{% for device in docker_app_devices %} + - {{ device }} +{% endfor %} +{% endif %} +{% if docker_app_cap_add | length > 0 %} + cap_add: +{% for capability in docker_app_cap_add %} + - {{ capability }} +{% endfor %} +{% endif %} +{% if docker_app_group_add | length > 0 %} + group_add: +{% for group in docker_app_group_add %} + - "{{ group }}" +{% endfor %} +{% endif %} +{% if docker_app_extra_hosts | length > 0 %} + extra_hosts: +{% for host in docker_app_extra_hosts %} + - {{ host }} +{% endfor %} +{% endif %} +{% if docker_app_depends_on | length > 0 %} + depends_on: +{% for dependency in docker_app_depends_on %} + - {{ dependency }} +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} + networks: + - {{ docker_app_traefik_network }} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} +networks: + {{ docker_app_traefik_network }}: + external: true +{% endif %} diff --git a/roles/filebrowser_quantum/templates/env.j2 b/roles/filebrowser_quantum/templates/env.j2 new file mode 100644 index 0000000..c4f1a9f --- /dev/null +++ b/roles/filebrowser_quantum/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in docker_app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/fiveetools/README.md b/roles/fiveetools/README.md new file mode 100644 index 0000000..bc0204a --- /dev/null +++ b/roles/fiveetools/README.md @@ -0,0 +1,4 @@ +# fiveetools + +Deploys 5etools with Docker Compose. + diff --git a/roles/fiveetools/defaults/main.yml b/roles/fiveetools/defaults/main.yml new file mode 100644 index 0000000..bb378ed --- /dev/null +++ b/roles/fiveetools/defaults/main.yml @@ -0,0 +1,14 @@ +--- +fiveetools_name: fiveetools +fiveetools_dir: /srv/docker/fiveetools +fiveetools_image: jafner/5etools:latest +fiveetools_domain: 5etools.example.invalid +fiveetools_service_port: 80 +fiveetools_routing_mode: "{{ nbops_service_routing_mode }}" +fiveetools_env: {} +fiveetools_directories: + - "{{ fiveetools_dir }}" +fiveetools_volumes: [] +fiveetools_ports: + - { host: 8087, container: 80, protocol: tcp } + diff --git a/roles/fiveetools/files/.gitkeep b/roles/fiveetools/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/fiveetools/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/fiveetools/handlers/.gitkeep b/roles/fiveetools/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/fiveetools/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/fiveetools/tasks/main.yml b/roles/fiveetools/tasks/main.yml new file mode 100644 index 0000000..f0f6ba7 --- /dev/null +++ b/roles/fiveetools/tasks/main.yml @@ -0,0 +1,18 @@ +--- +- name: Deploy 5etools + ansible.builtin.include_role: + name: docker_app + vars: + docker_app_env_template_src: "{{ playbook_dir }}/../roles/fiveetools/templates/env.j2" + docker_app_compose_template_src: "{{ playbook_dir }}/../roles/fiveetools/templates/compose.yaml.j2" + docker_app_name: "{{ fiveetools_name }}" + docker_app_dir: "{{ fiveetools_dir }}" + docker_app_image: "{{ fiveetools_image }}" + docker_app_env: "{{ fiveetools_env }}" + docker_app_directories: "{{ fiveetools_directories }}" + docker_app_volumes: "{{ fiveetools_volumes }}" + docker_app_ports: "{{ fiveetools_ports }}" + docker_app_routing_mode: "{{ fiveetools_routing_mode }}" + docker_app_domain: "{{ fiveetools_domain }}" + docker_app_service_port: "{{ fiveetools_service_port }}" + diff --git a/roles/fiveetools/templates/.gitkeep b/roles/fiveetools/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/fiveetools/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/fiveetools/templates/compose.yaml.j2 b/roles/fiveetools/templates/compose.yaml.j2 new file mode 100644 index 0000000..6de1603 --- /dev/null +++ b/roles/fiveetools/templates/compose.yaml.j2 @@ -0,0 +1,90 @@ +services: + {{ docker_app_name }}: + image: {{ docker_app_image }} + restart: {{ docker_app_restart }} +{% if docker_app_command | length > 0 %} + command: {{ docker_app_command | to_json }} +{% endif %} +{% if docker_app_user | length > 0 %} + user: "{{ docker_app_user }}" +{% endif %} +{% if docker_app_network_mode | length > 0 %} + network_mode: {{ docker_app_network_mode }} +{% endif %} +{% if docker_app_privileged | bool %} + privileged: true +{% endif %} +{% if docker_app_env | length > 0 %} + env_file: .env +{% endif %} +{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} + ports: +{% for port in docker_app_ports %} + - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_service_port %} + # internal service port: {{ docker_app_service_port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }} + - traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }} + - traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }} +{% if docker_app_traefik_middlewares | length > 0 %} + - traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }} +{% endif %} + - traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }} +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% elif docker_app_labels | length > 0 %} + labels: +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% endif %} +{% if docker_app_volumes | length > 0 %} + volumes: +{% for volume in docker_app_volumes %} + - {{ volume }} +{% endfor %} +{% endif %} +{% if docker_app_devices | length > 0 %} + devices: +{% for device in docker_app_devices %} + - {{ device }} +{% endfor %} +{% endif %} +{% if docker_app_cap_add | length > 0 %} + cap_add: +{% for capability in docker_app_cap_add %} + - {{ capability }} +{% endfor %} +{% endif %} +{% if docker_app_group_add | length > 0 %} + group_add: +{% for group in docker_app_group_add %} + - "{{ group }}" +{% endfor %} +{% endif %} +{% if docker_app_extra_hosts | length > 0 %} + extra_hosts: +{% for host in docker_app_extra_hosts %} + - {{ host }} +{% endfor %} +{% endif %} +{% if docker_app_depends_on | length > 0 %} + depends_on: +{% for dependency in docker_app_depends_on %} + - {{ dependency }} +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} + networks: + - {{ docker_app_traefik_network }} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} +networks: + {{ docker_app_traefik_network }}: + external: true +{% endif %} diff --git a/roles/fiveetools/templates/env.j2 b/roles/fiveetools/templates/env.j2 new file mode 100644 index 0000000..c4f1a9f --- /dev/null +++ b/roles/fiveetools/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in docker_app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/flaresolverr/README.md b/roles/flaresolverr/README.md new file mode 100644 index 0000000..e837d8d --- /dev/null +++ b/roles/flaresolverr/README.md @@ -0,0 +1,4 @@ +# flaresolverr + +Deploys FlareSolverr with Docker Compose. + diff --git a/roles/flaresolverr/defaults/main.yml b/roles/flaresolverr/defaults/main.yml new file mode 100644 index 0000000..fba38a6 --- /dev/null +++ b/roles/flaresolverr/defaults/main.yml @@ -0,0 +1,16 @@ +--- +flaresolverr_name: flaresolverr +flaresolverr_dir: /srv/docker/flaresolverr +flaresolverr_image: ghcr.io/flaresolverr/flaresolverr:latest +flaresolverr_domain: flaresolverr.example.invalid +flaresolverr_service_port: 8191 +flaresolverr_routing_mode: "{{ nbops_service_routing_mode }}" +flaresolverr_env: + LOG_LEVEL: info + TZ: "{{ nbops_timezone }}" +flaresolverr_directories: + - "{{ flaresolverr_dir }}" +flaresolverr_volumes: [] +flaresolverr_ports: + - { host: 8191, container: 8191, protocol: tcp } + diff --git a/roles/flaresolverr/files/.gitkeep b/roles/flaresolverr/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/flaresolverr/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/flaresolverr/handlers/.gitkeep b/roles/flaresolverr/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/flaresolverr/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/flaresolverr/tasks/main.yml b/roles/flaresolverr/tasks/main.yml new file mode 100644 index 0000000..aaef353 --- /dev/null +++ b/roles/flaresolverr/tasks/main.yml @@ -0,0 +1,18 @@ +--- +- name: Deploy FlareSolverr + ansible.builtin.include_role: + name: docker_app + vars: + docker_app_env_template_src: "{{ playbook_dir }}/../roles/flaresolverr/templates/env.j2" + docker_app_compose_template_src: "{{ playbook_dir }}/../roles/flaresolverr/templates/compose.yaml.j2" + docker_app_name: "{{ flaresolverr_name }}" + docker_app_dir: "{{ flaresolverr_dir }}" + docker_app_image: "{{ flaresolverr_image }}" + docker_app_env: "{{ flaresolverr_env }}" + docker_app_directories: "{{ flaresolverr_directories }}" + docker_app_volumes: "{{ flaresolverr_volumes }}" + docker_app_ports: "{{ flaresolverr_ports }}" + docker_app_routing_mode: "{{ flaresolverr_routing_mode }}" + docker_app_domain: "{{ flaresolverr_domain }}" + docker_app_service_port: "{{ flaresolverr_service_port }}" + diff --git a/roles/flaresolverr/templates/.gitkeep b/roles/flaresolverr/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/flaresolverr/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/flaresolverr/templates/compose.yaml.j2 b/roles/flaresolverr/templates/compose.yaml.j2 new file mode 100644 index 0000000..6de1603 --- /dev/null +++ b/roles/flaresolverr/templates/compose.yaml.j2 @@ -0,0 +1,90 @@ +services: + {{ docker_app_name }}: + image: {{ docker_app_image }} + restart: {{ docker_app_restart }} +{% if docker_app_command | length > 0 %} + command: {{ docker_app_command | to_json }} +{% endif %} +{% if docker_app_user | length > 0 %} + user: "{{ docker_app_user }}" +{% endif %} +{% if docker_app_network_mode | length > 0 %} + network_mode: {{ docker_app_network_mode }} +{% endif %} +{% if docker_app_privileged | bool %} + privileged: true +{% endif %} +{% if docker_app_env | length > 0 %} + env_file: .env +{% endif %} +{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} + ports: +{% for port in docker_app_ports %} + - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_service_port %} + # internal service port: {{ docker_app_service_port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }} + - traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }} + - traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }} +{% if docker_app_traefik_middlewares | length > 0 %} + - traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }} +{% endif %} + - traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }} +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% elif docker_app_labels | length > 0 %} + labels: +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% endif %} +{% if docker_app_volumes | length > 0 %} + volumes: +{% for volume in docker_app_volumes %} + - {{ volume }} +{% endfor %} +{% endif %} +{% if docker_app_devices | length > 0 %} + devices: +{% for device in docker_app_devices %} + - {{ device }} +{% endfor %} +{% endif %} +{% if docker_app_cap_add | length > 0 %} + cap_add: +{% for capability in docker_app_cap_add %} + - {{ capability }} +{% endfor %} +{% endif %} +{% if docker_app_group_add | length > 0 %} + group_add: +{% for group in docker_app_group_add %} + - "{{ group }}" +{% endfor %} +{% endif %} +{% if docker_app_extra_hosts | length > 0 %} + extra_hosts: +{% for host in docker_app_extra_hosts %} + - {{ host }} +{% endfor %} +{% endif %} +{% if docker_app_depends_on | length > 0 %} + depends_on: +{% for dependency in docker_app_depends_on %} + - {{ dependency }} +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} + networks: + - {{ docker_app_traefik_network }} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} +networks: + {{ docker_app_traefik_network }}: + external: true +{% endif %} diff --git a/roles/flaresolverr/templates/env.j2 b/roles/flaresolverr/templates/env.j2 new file mode 100644 index 0000000..c4f1a9f --- /dev/null +++ b/roles/flaresolverr/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in docker_app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/gitea/README.md b/roles/gitea/README.md new file mode 100644 index 0000000..e9154d1 --- /dev/null +++ b/roles/gitea/README.md @@ -0,0 +1,6 @@ +# gitea + +Deploys Gitea with Docker Compose and host-configurable HTTP/SSH ports. + +Tags: `gitea`, `config`, `compose`, `firewall`. + diff --git a/roles/gitea/defaults/main.yml b/roles/gitea/defaults/main.yml new file mode 100644 index 0000000..93a7b6d --- /dev/null +++ b/roles/gitea/defaults/main.yml @@ -0,0 +1,19 @@ +--- +gitea_dir: /srv/docker/gitea +gitea_image: gitea/gitea:1.23 +gitea_http_port: 3000 +gitea_ssh_port: 2222 +gitea_domain: git.example.invalid +gitea_routing_mode: "{{ nbops_service_routing_mode }}" +gitea_traefik_network: "{{ nbops_proxy_network }}" +gitea_traefik_entrypoint: "{{ nbops_traefik_entrypoint }}" +gitea_traefik_tls: "{{ nbops_traefik_tls }}" +gitea_publish_ssh: true +gitea_env: + USER_UID: "1000" + USER_GID: "1000" + GITEA__server__ROOT_URL: "https://{{ gitea_domain }}/" + GITEA__server__SSH_PORT: "{{ gitea_ssh_port }}" +gitea_tcp_ports: + - "{{ gitea_http_port }}" + - "{{ gitea_ssh_port }}" diff --git a/roles/gitea/files/.gitkeep b/roles/gitea/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/gitea/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/gitea/handlers/main.yml b/roles/gitea/handlers/main.yml new file mode 100644 index 0000000..225b084 --- /dev/null +++ b/roles/gitea/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: restart gitea + community.docker.docker_compose_v2: + project_src: "{{ gitea_dir }}" + state: restarted + diff --git a/roles/gitea/tasks/main.yml b/roles/gitea/tasks/main.yml new file mode 100644 index 0000000..4fee573 --- /dev/null +++ b/roles/gitea/tasks/main.yml @@ -0,0 +1,61 @@ +--- +- name: Create Gitea directory + ansible.builtin.file: + path: "{{ gitea_dir }}" + state: directory + owner: root + group: root + mode: "0755" + tags: [gitea] + +- name: Render Gitea environment + ansible.builtin.template: + src: env.j2 + dest: "{{ gitea_dir }}/.env" + owner: root + group: root + mode: "0600" + no_log: true + tags: [gitea, config] + +- name: Ensure Gitea proxy network exists + community.docker.docker_network: + name: "{{ gitea_traefik_network }}" + state: present + when: gitea_routing_mode == "traefik" + tags: [gitea, docker] + +- name: Render Gitea Compose file + ansible.builtin.template: + src: compose.yaml.j2 + dest: "{{ gitea_dir }}/compose.yaml" + owner: root + group: root + mode: "0644" + notify: restart gitea + tags: [gitea, compose] + +- name: Allow Gitea ports + community.general.ufw: + rule: allow + port: "{{ item }}" + proto: tcp + loop: "{{ gitea_tcp_ports }}" + when: gitea_routing_mode == "host" + tags: [gitea, firewall] + +- name: Allow Gitea SSH port when published behind Traefik + community.general.ufw: + rule: allow + port: "{{ gitea_ssh_port }}" + proto: tcp + when: + - gitea_routing_mode == "traefik" + - gitea_publish_ssh | bool + tags: [gitea, firewall] + +- name: Start Gitea + community.docker.docker_compose_v2: + project_src: "{{ gitea_dir }}" + state: present + tags: [gitea, compose] diff --git a/roles/gitea/templates/compose.yaml.j2 b/roles/gitea/templates/compose.yaml.j2 new file mode 100644 index 0000000..19d53c9 --- /dev/null +++ b/roles/gitea/templates/compose.yaml.j2 @@ -0,0 +1,33 @@ +services: + gitea: + image: {{ gitea_image }} + restart: unless-stopped + env_file: .env +{% if gitea_routing_mode == "host" or gitea_publish_ssh | bool %} + ports: +{% if gitea_routing_mode == "host" %} + - "{{ gitea_http_port }}:3000" +{% endif %} +{% if gitea_publish_ssh | bool %} + - "{{ gitea_ssh_port }}:22" +{% endif %} +{% endif %} +{% if gitea_routing_mode == "traefik" %} + # internal HTTP service port: 3000; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.gitea.rule=Host(`{{ gitea_domain }}`) + - traefik.http.routers.gitea.entrypoints={{ gitea_traefik_entrypoint }} + - traefik.http.routers.gitea.tls={{ gitea_traefik_tls | bool | ternary('true', 'false') }} + - traefik.http.services.gitea.loadbalancer.server.port=3000 + networks: + - default + - {{ gitea_traefik_network }} +{% endif %} + volumes: + - ./data:/data +{% if gitea_routing_mode == "traefik" %} +networks: + {{ gitea_traefik_network }}: + external: true +{% endif %} diff --git a/roles/gitea/templates/env.j2 b/roles/gitea/templates/env.j2 new file mode 100644 index 0000000..c4a7665 --- /dev/null +++ b/roles/gitea/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in gitea_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/gitea_act_runner/README.md b/roles/gitea_act_runner/README.md new file mode 100644 index 0000000..7018edc --- /dev/null +++ b/roles/gitea_act_runner/README.md @@ -0,0 +1,5 @@ +# gitea_act_runner + +Deploys a Gitea act runner. Store the registration token in encrypted +inventory. + diff --git a/roles/gitea_act_runner/defaults/main.yml b/roles/gitea_act_runner/defaults/main.yml new file mode 100644 index 0000000..29bcfb1 --- /dev/null +++ b/roles/gitea_act_runner/defaults/main.yml @@ -0,0 +1,17 @@ +--- +gitea_act_runner_name: gitea-act-runner +gitea_act_runner_dir: /srv/docker/gitea-act-runner +gitea_act_runner_image: gitea/act_runner:latest +gitea_act_runner_routing_mode: host +gitea_act_runner_env: + GITEA_INSTANCE_URL: https://git.example.invalid + GITEA_RUNNER_REGISTRATION_TOKEN: "{{ vault_gitea_runner_registration_token | default('CHANGE_ME_IN_VAULT') }}" + GITEA_RUNNER_NAME: "{{ inventory_hostname }}-runner" +gitea_act_runner_directories: + - "{{ gitea_act_runner_dir }}" + - /srv/data/gitea-act-runner +gitea_act_runner_volumes: + - /srv/data/gitea-act-runner:/data + - /var/run/docker.sock:/var/run/docker.sock +gitea_act_runner_ports: [] + diff --git a/roles/gitea_act_runner/files/.gitkeep b/roles/gitea_act_runner/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/gitea_act_runner/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/gitea_act_runner/handlers/.gitkeep b/roles/gitea_act_runner/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/gitea_act_runner/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/gitea_act_runner/tasks/main.yml b/roles/gitea_act_runner/tasks/main.yml new file mode 100644 index 0000000..7f1f98e --- /dev/null +++ b/roles/gitea_act_runner/tasks/main.yml @@ -0,0 +1,16 @@ +--- +- name: Deploy Gitea act runner + ansible.builtin.include_role: + name: docker_app + vars: + docker_app_env_template_src: "{{ playbook_dir }}/../roles/gitea_act_runner/templates/env.j2" + docker_app_compose_template_src: "{{ playbook_dir }}/../roles/gitea_act_runner/templates/compose.yaml.j2" + docker_app_name: "{{ gitea_act_runner_name }}" + docker_app_dir: "{{ gitea_act_runner_dir }}" + docker_app_image: "{{ gitea_act_runner_image }}" + docker_app_env: "{{ gitea_act_runner_env }}" + docker_app_directories: "{{ gitea_act_runner_directories }}" + docker_app_volumes: "{{ gitea_act_runner_volumes }}" + docker_app_ports: "{{ gitea_act_runner_ports }}" + docker_app_routing_mode: "{{ gitea_act_runner_routing_mode }}" + diff --git a/roles/gitea_act_runner/templates/.gitkeep b/roles/gitea_act_runner/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/gitea_act_runner/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/gitea_act_runner/templates/compose.yaml.j2 b/roles/gitea_act_runner/templates/compose.yaml.j2 new file mode 100644 index 0000000..6de1603 --- /dev/null +++ b/roles/gitea_act_runner/templates/compose.yaml.j2 @@ -0,0 +1,90 @@ +services: + {{ docker_app_name }}: + image: {{ docker_app_image }} + restart: {{ docker_app_restart }} +{% if docker_app_command | length > 0 %} + command: {{ docker_app_command | to_json }} +{% endif %} +{% if docker_app_user | length > 0 %} + user: "{{ docker_app_user }}" +{% endif %} +{% if docker_app_network_mode | length > 0 %} + network_mode: {{ docker_app_network_mode }} +{% endif %} +{% if docker_app_privileged | bool %} + privileged: true +{% endif %} +{% if docker_app_env | length > 0 %} + env_file: .env +{% endif %} +{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} + ports: +{% for port in docker_app_ports %} + - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_service_port %} + # internal service port: {{ docker_app_service_port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }} + - traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }} + - traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }} +{% if docker_app_traefik_middlewares | length > 0 %} + - traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }} +{% endif %} + - traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }} +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% elif docker_app_labels | length > 0 %} + labels: +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% endif %} +{% if docker_app_volumes | length > 0 %} + volumes: +{% for volume in docker_app_volumes %} + - {{ volume }} +{% endfor %} +{% endif %} +{% if docker_app_devices | length > 0 %} + devices: +{% for device in docker_app_devices %} + - {{ device }} +{% endfor %} +{% endif %} +{% if docker_app_cap_add | length > 0 %} + cap_add: +{% for capability in docker_app_cap_add %} + - {{ capability }} +{% endfor %} +{% endif %} +{% if docker_app_group_add | length > 0 %} + group_add: +{% for group in docker_app_group_add %} + - "{{ group }}" +{% endfor %} +{% endif %} +{% if docker_app_extra_hosts | length > 0 %} + extra_hosts: +{% for host in docker_app_extra_hosts %} + - {{ host }} +{% endfor %} +{% endif %} +{% if docker_app_depends_on | length > 0 %} + depends_on: +{% for dependency in docker_app_depends_on %} + - {{ dependency }} +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} + networks: + - {{ docker_app_traefik_network }} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} +networks: + {{ docker_app_traefik_network }}: + external: true +{% endif %} diff --git a/roles/gitea_act_runner/templates/env.j2 b/roles/gitea_act_runner/templates/env.j2 new file mode 100644 index 0000000..c4f1a9f --- /dev/null +++ b/roles/gitea_act_runner/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in docker_app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/grafana/README.md b/roles/grafana/README.md new file mode 100644 index 0000000..bd53c59 --- /dev/null +++ b/roles/grafana/README.md @@ -0,0 +1,4 @@ +# grafana + +Deploys Grafana with Docker Compose. + diff --git a/roles/grafana/defaults/main.yml b/roles/grafana/defaults/main.yml new file mode 100644 index 0000000..10dcfd5 --- /dev/null +++ b/roles/grafana/defaults/main.yml @@ -0,0 +1,17 @@ +--- +grafana_name: grafana +grafana_dir: /srv/docker/grafana +grafana_image: grafana/grafana-oss:latest +grafana_domain: grafana.example.invalid +grafana_service_port: 3000 +grafana_routing_mode: "{{ nbops_service_routing_mode }}" +grafana_env: + GF_SECURITY_ADMIN_PASSWORD: "{{ vault_grafana_admin_password | default('CHANGE_ME_IN_VAULT') }}" +grafana_directories: + - "{{ grafana_dir }}" + - /srv/data/grafana +grafana_volumes: + - /srv/data/grafana:/var/lib/grafana +grafana_ports: + - { host: 3000, container: 3000, protocol: tcp } + diff --git a/roles/grafana/files/.gitkeep b/roles/grafana/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/grafana/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/grafana/handlers/.gitkeep b/roles/grafana/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/grafana/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/grafana/tasks/main.yml b/roles/grafana/tasks/main.yml new file mode 100644 index 0000000..a17dca4 --- /dev/null +++ b/roles/grafana/tasks/main.yml @@ -0,0 +1,18 @@ +--- +- name: Deploy Grafana + ansible.builtin.include_role: + name: docker_app + vars: + docker_app_env_template_src: "{{ playbook_dir }}/../roles/grafana/templates/env.j2" + docker_app_compose_template_src: "{{ playbook_dir }}/../roles/grafana/templates/compose.yaml.j2" + docker_app_name: "{{ grafana_name }}" + docker_app_dir: "{{ grafana_dir }}" + docker_app_image: "{{ grafana_image }}" + docker_app_env: "{{ grafana_env }}" + docker_app_directories: "{{ grafana_directories }}" + docker_app_volumes: "{{ grafana_volumes }}" + docker_app_ports: "{{ grafana_ports }}" + docker_app_routing_mode: "{{ grafana_routing_mode }}" + docker_app_domain: "{{ grafana_domain }}" + docker_app_service_port: "{{ grafana_service_port }}" + diff --git a/roles/grafana/templates/.gitkeep b/roles/grafana/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/grafana/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/grafana/templates/compose.yaml.j2 b/roles/grafana/templates/compose.yaml.j2 new file mode 100644 index 0000000..6de1603 --- /dev/null +++ b/roles/grafana/templates/compose.yaml.j2 @@ -0,0 +1,90 @@ +services: + {{ docker_app_name }}: + image: {{ docker_app_image }} + restart: {{ docker_app_restart }} +{% if docker_app_command | length > 0 %} + command: {{ docker_app_command | to_json }} +{% endif %} +{% if docker_app_user | length > 0 %} + user: "{{ docker_app_user }}" +{% endif %} +{% if docker_app_network_mode | length > 0 %} + network_mode: {{ docker_app_network_mode }} +{% endif %} +{% if docker_app_privileged | bool %} + privileged: true +{% endif %} +{% if docker_app_env | length > 0 %} + env_file: .env +{% endif %} +{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} + ports: +{% for port in docker_app_ports %} + - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_service_port %} + # internal service port: {{ docker_app_service_port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }} + - traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }} + - traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }} +{% if docker_app_traefik_middlewares | length > 0 %} + - traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }} +{% endif %} + - traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }} +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% elif docker_app_labels | length > 0 %} + labels: +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% endif %} +{% if docker_app_volumes | length > 0 %} + volumes: +{% for volume in docker_app_volumes %} + - {{ volume }} +{% endfor %} +{% endif %} +{% if docker_app_devices | length > 0 %} + devices: +{% for device in docker_app_devices %} + - {{ device }} +{% endfor %} +{% endif %} +{% if docker_app_cap_add | length > 0 %} + cap_add: +{% for capability in docker_app_cap_add %} + - {{ capability }} +{% endfor %} +{% endif %} +{% if docker_app_group_add | length > 0 %} + group_add: +{% for group in docker_app_group_add %} + - "{{ group }}" +{% endfor %} +{% endif %} +{% if docker_app_extra_hosts | length > 0 %} + extra_hosts: +{% for host in docker_app_extra_hosts %} + - {{ host }} +{% endfor %} +{% endif %} +{% if docker_app_depends_on | length > 0 %} + depends_on: +{% for dependency in docker_app_depends_on %} + - {{ dependency }} +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} + networks: + - {{ docker_app_traefik_network }} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} +networks: + {{ docker_app_traefik_network }}: + external: true +{% endif %} diff --git a/roles/grafana/templates/env.j2 b/roles/grafana/templates/env.j2 new file mode 100644 index 0000000..c4f1a9f --- /dev/null +++ b/roles/grafana/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in docker_app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/grafana_renderer/README.md b/roles/grafana_renderer/README.md new file mode 100644 index 0000000..fb6c674 --- /dev/null +++ b/roles/grafana_renderer/README.md @@ -0,0 +1,4 @@ +# grafana_renderer + +Deploys Grafana Image Renderer with Docker Compose. + diff --git a/roles/grafana_renderer/defaults/main.yml b/roles/grafana_renderer/defaults/main.yml new file mode 100644 index 0000000..fafec88 --- /dev/null +++ b/roles/grafana_renderer/defaults/main.yml @@ -0,0 +1,13 @@ +--- +grafana_renderer_name: grafana-renderer +grafana_renderer_dir: /srv/docker/grafana-renderer +grafana_renderer_image: grafana/grafana-image-renderer:latest +grafana_renderer_routing_mode: host +grafana_renderer_env: + ENABLE_METRICS: "true" +grafana_renderer_directories: + - "{{ grafana_renderer_dir }}" +grafana_renderer_volumes: [] +grafana_renderer_ports: + - { host: 8081, container: 8081, protocol: tcp } + diff --git a/roles/grafana_renderer/files/.gitkeep b/roles/grafana_renderer/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/grafana_renderer/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/grafana_renderer/handlers/.gitkeep b/roles/grafana_renderer/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/grafana_renderer/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/grafana_renderer/tasks/main.yml b/roles/grafana_renderer/tasks/main.yml new file mode 100644 index 0000000..c252757 --- /dev/null +++ b/roles/grafana_renderer/tasks/main.yml @@ -0,0 +1,16 @@ +--- +- name: Deploy Grafana renderer + ansible.builtin.include_role: + name: docker_app + vars: + docker_app_env_template_src: "{{ playbook_dir }}/../roles/grafana_renderer/templates/env.j2" + docker_app_compose_template_src: "{{ playbook_dir }}/../roles/grafana_renderer/templates/compose.yaml.j2" + docker_app_name: "{{ grafana_renderer_name }}" + docker_app_dir: "{{ grafana_renderer_dir }}" + docker_app_image: "{{ grafana_renderer_image }}" + docker_app_env: "{{ grafana_renderer_env }}" + docker_app_directories: "{{ grafana_renderer_directories }}" + docker_app_volumes: "{{ grafana_renderer_volumes }}" + docker_app_ports: "{{ grafana_renderer_ports }}" + docker_app_routing_mode: "{{ grafana_renderer_routing_mode }}" + diff --git a/roles/grafana_renderer/templates/.gitkeep b/roles/grafana_renderer/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/grafana_renderer/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/grafana_renderer/templates/compose.yaml.j2 b/roles/grafana_renderer/templates/compose.yaml.j2 new file mode 100644 index 0000000..6de1603 --- /dev/null +++ b/roles/grafana_renderer/templates/compose.yaml.j2 @@ -0,0 +1,90 @@ +services: + {{ docker_app_name }}: + image: {{ docker_app_image }} + restart: {{ docker_app_restart }} +{% if docker_app_command | length > 0 %} + command: {{ docker_app_command | to_json }} +{% endif %} +{% if docker_app_user | length > 0 %} + user: "{{ docker_app_user }}" +{% endif %} +{% if docker_app_network_mode | length > 0 %} + network_mode: {{ docker_app_network_mode }} +{% endif %} +{% if docker_app_privileged | bool %} + privileged: true +{% endif %} +{% if docker_app_env | length > 0 %} + env_file: .env +{% endif %} +{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} + ports: +{% for port in docker_app_ports %} + - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_service_port %} + # internal service port: {{ docker_app_service_port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }} + - traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }} + - traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }} +{% if docker_app_traefik_middlewares | length > 0 %} + - traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }} +{% endif %} + - traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }} +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% elif docker_app_labels | length > 0 %} + labels: +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% endif %} +{% if docker_app_volumes | length > 0 %} + volumes: +{% for volume in docker_app_volumes %} + - {{ volume }} +{% endfor %} +{% endif %} +{% if docker_app_devices | length > 0 %} + devices: +{% for device in docker_app_devices %} + - {{ device }} +{% endfor %} +{% endif %} +{% if docker_app_cap_add | length > 0 %} + cap_add: +{% for capability in docker_app_cap_add %} + - {{ capability }} +{% endfor %} +{% endif %} +{% if docker_app_group_add | length > 0 %} + group_add: +{% for group in docker_app_group_add %} + - "{{ group }}" +{% endfor %} +{% endif %} +{% if docker_app_extra_hosts | length > 0 %} + extra_hosts: +{% for host in docker_app_extra_hosts %} + - {{ host }} +{% endfor %} +{% endif %} +{% if docker_app_depends_on | length > 0 %} + depends_on: +{% for dependency in docker_app_depends_on %} + - {{ dependency }} +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} + networks: + - {{ docker_app_traefik_network }} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} +networks: + {{ docker_app_traefik_network }}: + external: true +{% endif %} diff --git a/roles/grafana_renderer/templates/env.j2 b/roles/grafana_renderer/templates/env.j2 new file mode 100644 index 0000000..c4f1a9f --- /dev/null +++ b/roles/grafana_renderer/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in docker_app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/homarr/README.md b/roles/homarr/README.md new file mode 100644 index 0000000..4030fe4 --- /dev/null +++ b/roles/homarr/README.md @@ -0,0 +1,4 @@ +# homarr + +Deploys Homarr with Docker Compose. + diff --git a/roles/homarr/defaults/main.yml b/roles/homarr/defaults/main.yml new file mode 100644 index 0000000..511f763 --- /dev/null +++ b/roles/homarr/defaults/main.yml @@ -0,0 +1,18 @@ +--- +homarr_name: homarr +homarr_dir: /srv/docker/homarr +homarr_image: ghcr.io/homarr-labs/homarr:latest +homarr_domain: homarr.example.invalid +homarr_service_port: 7575 +homarr_routing_mode: "{{ nbops_service_routing_mode }}" +homarr_env: + TZ: "{{ nbops_timezone }}" + SECRET_ENCRYPTION_KEY: "{{ vault_homarr_secret_encryption_key | default('CHANGE_ME_IN_VAULT') }}" +homarr_directories: + - "{{ homarr_dir }}" + - /srv/config/homarr +homarr_volumes: + - /srv/config/homarr:/appdata +homarr_ports: + - { host: 7575, container: 7575, protocol: tcp } + diff --git a/roles/homarr/files/.gitkeep b/roles/homarr/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/homarr/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/homarr/handlers/.gitkeep b/roles/homarr/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/homarr/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/homarr/tasks/main.yml b/roles/homarr/tasks/main.yml new file mode 100644 index 0000000..af287cd --- /dev/null +++ b/roles/homarr/tasks/main.yml @@ -0,0 +1,18 @@ +--- +- name: Deploy Homarr + ansible.builtin.include_role: + name: docker_app + vars: + docker_app_env_template_src: "{{ playbook_dir }}/../roles/homarr/templates/env.j2" + docker_app_compose_template_src: "{{ playbook_dir }}/../roles/homarr/templates/compose.yaml.j2" + docker_app_name: "{{ homarr_name }}" + docker_app_dir: "{{ homarr_dir }}" + docker_app_image: "{{ homarr_image }}" + docker_app_env: "{{ homarr_env }}" + docker_app_directories: "{{ homarr_directories }}" + docker_app_volumes: "{{ homarr_volumes }}" + docker_app_ports: "{{ homarr_ports }}" + docker_app_routing_mode: "{{ homarr_routing_mode }}" + docker_app_domain: "{{ homarr_domain }}" + docker_app_service_port: "{{ homarr_service_port }}" + diff --git a/roles/homarr/templates/.gitkeep b/roles/homarr/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/homarr/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/homarr/templates/compose.yaml.j2 b/roles/homarr/templates/compose.yaml.j2 new file mode 100644 index 0000000..6de1603 --- /dev/null +++ b/roles/homarr/templates/compose.yaml.j2 @@ -0,0 +1,90 @@ +services: + {{ docker_app_name }}: + image: {{ docker_app_image }} + restart: {{ docker_app_restart }} +{% if docker_app_command | length > 0 %} + command: {{ docker_app_command | to_json }} +{% endif %} +{% if docker_app_user | length > 0 %} + user: "{{ docker_app_user }}" +{% endif %} +{% if docker_app_network_mode | length > 0 %} + network_mode: {{ docker_app_network_mode }} +{% endif %} +{% if docker_app_privileged | bool %} + privileged: true +{% endif %} +{% if docker_app_env | length > 0 %} + env_file: .env +{% endif %} +{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} + ports: +{% for port in docker_app_ports %} + - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_service_port %} + # internal service port: {{ docker_app_service_port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }} + - traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }} + - traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }} +{% if docker_app_traefik_middlewares | length > 0 %} + - traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }} +{% endif %} + - traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }} +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% elif docker_app_labels | length > 0 %} + labels: +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% endif %} +{% if docker_app_volumes | length > 0 %} + volumes: +{% for volume in docker_app_volumes %} + - {{ volume }} +{% endfor %} +{% endif %} +{% if docker_app_devices | length > 0 %} + devices: +{% for device in docker_app_devices %} + - {{ device }} +{% endfor %} +{% endif %} +{% if docker_app_cap_add | length > 0 %} + cap_add: +{% for capability in docker_app_cap_add %} + - {{ capability }} +{% endfor %} +{% endif %} +{% if docker_app_group_add | length > 0 %} + group_add: +{% for group in docker_app_group_add %} + - "{{ group }}" +{% endfor %} +{% endif %} +{% if docker_app_extra_hosts | length > 0 %} + extra_hosts: +{% for host in docker_app_extra_hosts %} + - {{ host }} +{% endfor %} +{% endif %} +{% if docker_app_depends_on | length > 0 %} + depends_on: +{% for dependency in docker_app_depends_on %} + - {{ dependency }} +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} + networks: + - {{ docker_app_traefik_network }} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} +networks: + {{ docker_app_traefik_network }}: + external: true +{% endif %} diff --git a/roles/homarr/templates/env.j2 b/roles/homarr/templates/env.j2 new file mode 100644 index 0000000..c4f1a9f --- /dev/null +++ b/roles/homarr/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in docker_app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/immich/README.md b/roles/immich/README.md new file mode 100644 index 0000000..bd35cdd --- /dev/null +++ b/roles/immich/README.md @@ -0,0 +1,6 @@ +# immich + +Deploys Immich with server, machine learning, Redis, and PostgreSQL services. + +Tags: `immich`, `config`, `compose`, `firewall`. + diff --git a/roles/immich/defaults/main.yml b/roles/immich/defaults/main.yml new file mode 100644 index 0000000..feeeaf9 --- /dev/null +++ b/roles/immich/defaults/main.yml @@ -0,0 +1,23 @@ +--- +immich_dir: /srv/docker/immich +immich_server_image: ghcr.io/immich-app/immich-server:release +immich_ml_image: ghcr.io/immich-app/immich-machine-learning:release +immich_postgres_image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0 +immich_redis_image: docker.io/library/redis:7-alpine +immich_http_port: 2283 +immich_domain: immich.example.invalid +immich_routing_mode: "{{ nbops_service_routing_mode }}" +immich_traefik_network: "{{ nbops_proxy_network }}" +immich_traefik_entrypoint: "{{ nbops_traefik_entrypoint }}" +immich_traefik_tls: "{{ nbops_traefik_tls }}" +immich_upload_location: /srv/media/immich +immich_database_password: "{{ vault_immich_database_password | default('CHANGE_ME_IN_VAULT') }}" +immich_env: + DB_HOSTNAME: database + DB_USERNAME: postgres + DB_DATABASE_NAME: immich + DB_PASSWORD: "{{ immich_database_password }}" + REDIS_HOSTNAME: redis + UPLOAD_LOCATION: "{{ immich_upload_location }}" +immich_tcp_ports: + - "{{ immich_http_port }}" diff --git a/roles/immich/files/.gitkeep b/roles/immich/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/immich/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/immich/handlers/main.yml b/roles/immich/handlers/main.yml new file mode 100644 index 0000000..8eb6639 --- /dev/null +++ b/roles/immich/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: restart immich + community.docker.docker_compose_v2: + project_src: "{{ immich_dir }}" + state: restarted + diff --git a/roles/immich/tasks/main.yml b/roles/immich/tasks/main.yml new file mode 100644 index 0000000..31cf165 --- /dev/null +++ b/roles/immich/tasks/main.yml @@ -0,0 +1,54 @@ +--- +- name: Create Immich directories + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: root + group: root + mode: "0755" + loop: + - "{{ immich_dir }}" + - "{{ immich_upload_location }}" + tags: [immich] + +- name: Render Immich environment + ansible.builtin.template: + src: env.j2 + dest: "{{ immich_dir }}/.env" + owner: root + group: root + mode: "0600" + no_log: true + tags: [immich, config] + +- name: Ensure Immich proxy network exists + community.docker.docker_network: + name: "{{ immich_traefik_network }}" + state: present + when: immich_routing_mode == "traefik" + tags: [immich, docker] + +- name: Render Immich Compose file + ansible.builtin.template: + src: compose.yaml.j2 + dest: "{{ immich_dir }}/compose.yaml" + owner: root + group: root + mode: "0644" + notify: restart immich + tags: [immich, compose] + +- name: Allow Immich ports + community.general.ufw: + rule: allow + port: "{{ item }}" + proto: tcp + loop: "{{ immich_tcp_ports }}" + when: immich_routing_mode == "host" + tags: [immich, firewall] + +- name: Start Immich + community.docker.docker_compose_v2: + project_src: "{{ immich_dir }}" + state: present + tags: [immich, compose] diff --git a/roles/immich/templates/compose.yaml.j2 b/roles/immich/templates/compose.yaml.j2 new file mode 100644 index 0000000..98d6313 --- /dev/null +++ b/roles/immich/templates/compose.yaml.j2 @@ -0,0 +1,48 @@ +services: + immich-server: + image: {{ immich_server_image }} + restart: unless-stopped + env_file: .env +{% if immich_routing_mode == "host" %} + ports: + - "{{ immich_http_port }}:2283" +{% endif %} +{% if immich_routing_mode == "traefik" %} + # internal HTTP service port: 2283; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.immich.rule=Host(`{{ immich_domain }}`) + - traefik.http.routers.immich.entrypoints={{ immich_traefik_entrypoint }} + - traefik.http.routers.immich.tls={{ immich_traefik_tls | bool | ternary('true', 'false') }} + - traefik.http.services.immich.loadbalancer.server.port=2283 + networks: + - default + - {{ immich_traefik_network }} +{% endif %} + volumes: + - {{ immich_upload_location }}:/usr/src/app/upload + depends_on: + - redis + - database + immich-machine-learning: + image: {{ immich_ml_image }} + restart: unless-stopped + volumes: + - ./model-cache:/cache + redis: + image: {{ immich_redis_image }} + restart: unless-stopped + database: + image: {{ immich_postgres_image }} + restart: unless-stopped + environment: + POSTGRES_PASSWORD: ${DB_PASSWORD} + POSTGRES_USER: ${DB_USERNAME} + POSTGRES_DB: ${DB_DATABASE_NAME} + volumes: + - ./postgres:/var/lib/postgresql/data +{% if immich_routing_mode == "traefik" %} +networks: + {{ immich_traefik_network }}: + external: true +{% endif %} diff --git a/roles/immich/templates/env.j2 b/roles/immich/templates/env.j2 new file mode 100644 index 0000000..0929647 --- /dev/null +++ b/roles/immich/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in immich_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/ipv6nat/README.md b/roles/ipv6nat/README.md new file mode 100644 index 0000000..f6b8082 --- /dev/null +++ b/roles/ipv6nat/README.md @@ -0,0 +1,4 @@ +# ipv6nat + +Deploys ipv6nat with host networking and privileged access. + diff --git a/roles/ipv6nat/defaults/main.yml b/roles/ipv6nat/defaults/main.yml new file mode 100644 index 0000000..9217c00 --- /dev/null +++ b/roles/ipv6nat/defaults/main.yml @@ -0,0 +1,15 @@ +--- +ipv6nat_name: ipv6nat +ipv6nat_dir: /srv/docker/ipv6nat +ipv6nat_image: robbertkl/ipv6nat:latest +ipv6nat_routing_mode: host +ipv6nat_network_mode: host +ipv6nat_privileged: true +ipv6nat_env: {} +ipv6nat_directories: + - "{{ ipv6nat_dir }}" +ipv6nat_volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - /lib/modules:/lib/modules:ro +ipv6nat_ports: [] + diff --git a/roles/ipv6nat/files/.gitkeep b/roles/ipv6nat/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/ipv6nat/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/ipv6nat/handlers/.gitkeep b/roles/ipv6nat/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/ipv6nat/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/ipv6nat/tasks/main.yml b/roles/ipv6nat/tasks/main.yml new file mode 100644 index 0000000..9683b09 --- /dev/null +++ b/roles/ipv6nat/tasks/main.yml @@ -0,0 +1,18 @@ +--- +- name: Deploy ipv6nat + ansible.builtin.include_role: + name: docker_app + vars: + docker_app_env_template_src: "{{ playbook_dir }}/../roles/ipv6nat/templates/env.j2" + docker_app_compose_template_src: "{{ playbook_dir }}/../roles/ipv6nat/templates/compose.yaml.j2" + docker_app_name: "{{ ipv6nat_name }}" + docker_app_dir: "{{ ipv6nat_dir }}" + docker_app_image: "{{ ipv6nat_image }}" + docker_app_env: "{{ ipv6nat_env }}" + docker_app_directories: "{{ ipv6nat_directories }}" + docker_app_volumes: "{{ ipv6nat_volumes }}" + docker_app_ports: "{{ ipv6nat_ports }}" + docker_app_routing_mode: "{{ ipv6nat_routing_mode }}" + docker_app_network_mode: "{{ ipv6nat_network_mode }}" + docker_app_privileged: "{{ ipv6nat_privileged }}" + diff --git a/roles/ipv6nat/templates/.gitkeep b/roles/ipv6nat/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/ipv6nat/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/ipv6nat/templates/compose.yaml.j2 b/roles/ipv6nat/templates/compose.yaml.j2 new file mode 100644 index 0000000..6de1603 --- /dev/null +++ b/roles/ipv6nat/templates/compose.yaml.j2 @@ -0,0 +1,90 @@ +services: + {{ docker_app_name }}: + image: {{ docker_app_image }} + restart: {{ docker_app_restart }} +{% if docker_app_command | length > 0 %} + command: {{ docker_app_command | to_json }} +{% endif %} +{% if docker_app_user | length > 0 %} + user: "{{ docker_app_user }}" +{% endif %} +{% if docker_app_network_mode | length > 0 %} + network_mode: {{ docker_app_network_mode }} +{% endif %} +{% if docker_app_privileged | bool %} + privileged: true +{% endif %} +{% if docker_app_env | length > 0 %} + env_file: .env +{% endif %} +{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} + ports: +{% for port in docker_app_ports %} + - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_service_port %} + # internal service port: {{ docker_app_service_port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }} + - traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }} + - traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }} +{% if docker_app_traefik_middlewares | length > 0 %} + - traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }} +{% endif %} + - traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }} +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% elif docker_app_labels | length > 0 %} + labels: +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% endif %} +{% if docker_app_volumes | length > 0 %} + volumes: +{% for volume in docker_app_volumes %} + - {{ volume }} +{% endfor %} +{% endif %} +{% if docker_app_devices | length > 0 %} + devices: +{% for device in docker_app_devices %} + - {{ device }} +{% endfor %} +{% endif %} +{% if docker_app_cap_add | length > 0 %} + cap_add: +{% for capability in docker_app_cap_add %} + - {{ capability }} +{% endfor %} +{% endif %} +{% if docker_app_group_add | length > 0 %} + group_add: +{% for group in docker_app_group_add %} + - "{{ group }}" +{% endfor %} +{% endif %} +{% if docker_app_extra_hosts | length > 0 %} + extra_hosts: +{% for host in docker_app_extra_hosts %} + - {{ host }} +{% endfor %} +{% endif %} +{% if docker_app_depends_on | length > 0 %} + depends_on: +{% for dependency in docker_app_depends_on %} + - {{ dependency }} +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} + networks: + - {{ docker_app_traefik_network }} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} +networks: + {{ docker_app_traefik_network }}: + external: true +{% endif %} diff --git a/roles/ipv6nat/templates/env.j2 b/roles/ipv6nat/templates/env.j2 new file mode 100644 index 0000000..c4f1a9f --- /dev/null +++ b/roles/ipv6nat/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in docker_app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/jellyfin/README.md b/roles/jellyfin/README.md new file mode 100644 index 0000000..996453d --- /dev/null +++ b/roles/jellyfin/README.md @@ -0,0 +1,6 @@ +# jellyfin + +Deploys Jellyfin with Docker Compose. + +Tags: `jellyfin`. + diff --git a/roles/jellyfin/defaults/main.yml b/roles/jellyfin/defaults/main.yml new file mode 100644 index 0000000..dbc7233 --- /dev/null +++ b/roles/jellyfin/defaults/main.yml @@ -0,0 +1,21 @@ +--- +jellyfin_name: jellyfin +jellyfin_dir: /srv/docker/jellyfin +jellyfin_image: lscr.io/linuxserver/jellyfin:latest +jellyfin_domain: jellyfin.example.invalid +jellyfin_service_port: 8096 +jellyfin_routing_mode: "{{ nbops_service_routing_mode }}" +jellyfin_env: + PUID: "1000" + PGID: "1000" + TZ: "{{ nbops_timezone }}" +jellyfin_directories: + - "{{ jellyfin_dir }}" + - /srv/config/jellyfin + - /srv/media +jellyfin_volumes: + - /srv/config/jellyfin:/config + - /srv/media:/media +jellyfin_ports: + - { host: 8096, container: 8096, protocol: tcp } + diff --git a/roles/jellyfin/files/.gitkeep b/roles/jellyfin/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/jellyfin/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/jellyfin/handlers/.gitkeep b/roles/jellyfin/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/jellyfin/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/jellyfin/tasks/main.yml b/roles/jellyfin/tasks/main.yml new file mode 100644 index 0000000..2f042e8 --- /dev/null +++ b/roles/jellyfin/tasks/main.yml @@ -0,0 +1,18 @@ +--- +- name: Deploy Jellyfin + ansible.builtin.include_role: + name: docker_app + vars: + docker_app_env_template_src: "{{ playbook_dir }}/../roles/jellyfin/templates/env.j2" + docker_app_compose_template_src: "{{ playbook_dir }}/../roles/jellyfin/templates/compose.yaml.j2" + docker_app_name: "{{ jellyfin_name }}" + docker_app_dir: "{{ jellyfin_dir }}" + docker_app_image: "{{ jellyfin_image }}" + docker_app_env: "{{ jellyfin_env }}" + docker_app_directories: "{{ jellyfin_directories }}" + docker_app_volumes: "{{ jellyfin_volumes }}" + docker_app_ports: "{{ jellyfin_ports }}" + docker_app_routing_mode: "{{ jellyfin_routing_mode }}" + docker_app_domain: "{{ jellyfin_domain }}" + docker_app_service_port: "{{ jellyfin_service_port }}" + diff --git a/roles/jellyfin/templates/.gitkeep b/roles/jellyfin/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/jellyfin/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/jellyfin/templates/compose.yaml.j2 b/roles/jellyfin/templates/compose.yaml.j2 new file mode 100644 index 0000000..6de1603 --- /dev/null +++ b/roles/jellyfin/templates/compose.yaml.j2 @@ -0,0 +1,90 @@ +services: + {{ docker_app_name }}: + image: {{ docker_app_image }} + restart: {{ docker_app_restart }} +{% if docker_app_command | length > 0 %} + command: {{ docker_app_command | to_json }} +{% endif %} +{% if docker_app_user | length > 0 %} + user: "{{ docker_app_user }}" +{% endif %} +{% if docker_app_network_mode | length > 0 %} + network_mode: {{ docker_app_network_mode }} +{% endif %} +{% if docker_app_privileged | bool %} + privileged: true +{% endif %} +{% if docker_app_env | length > 0 %} + env_file: .env +{% endif %} +{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} + ports: +{% for port in docker_app_ports %} + - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_service_port %} + # internal service port: {{ docker_app_service_port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }} + - traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }} + - traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }} +{% if docker_app_traefik_middlewares | length > 0 %} + - traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }} +{% endif %} + - traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }} +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% elif docker_app_labels | length > 0 %} + labels: +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% endif %} +{% if docker_app_volumes | length > 0 %} + volumes: +{% for volume in docker_app_volumes %} + - {{ volume }} +{% endfor %} +{% endif %} +{% if docker_app_devices | length > 0 %} + devices: +{% for device in docker_app_devices %} + - {{ device }} +{% endfor %} +{% endif %} +{% if docker_app_cap_add | length > 0 %} + cap_add: +{% for capability in docker_app_cap_add %} + - {{ capability }} +{% endfor %} +{% endif %} +{% if docker_app_group_add | length > 0 %} + group_add: +{% for group in docker_app_group_add %} + - "{{ group }}" +{% endfor %} +{% endif %} +{% if docker_app_extra_hosts | length > 0 %} + extra_hosts: +{% for host in docker_app_extra_hosts %} + - {{ host }} +{% endfor %} +{% endif %} +{% if docker_app_depends_on | length > 0 %} + depends_on: +{% for dependency in docker_app_depends_on %} + - {{ dependency }} +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} + networks: + - {{ docker_app_traefik_network }} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} +networks: + {{ docker_app_traefik_network }}: + external: true +{% endif %} diff --git a/roles/jellyfin/templates/env.j2 b/roles/jellyfin/templates/env.j2 new file mode 100644 index 0000000..c4f1a9f --- /dev/null +++ b/roles/jellyfin/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in docker_app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/jellyseerr/README.md b/roles/jellyseerr/README.md new file mode 100644 index 0000000..b0cc2f6 --- /dev/null +++ b/roles/jellyseerr/README.md @@ -0,0 +1,6 @@ +# jellyseerr + +Deploys Jellyseerr with Docker Compose. + +Tags: `jellyseerr`. + diff --git a/roles/jellyseerr/defaults/main.yml b/roles/jellyseerr/defaults/main.yml new file mode 100644 index 0000000..eb2f327 --- /dev/null +++ b/roles/jellyseerr/defaults/main.yml @@ -0,0 +1,18 @@ +--- +jellyseerr_name: jellyseerr +jellyseerr_dir: /srv/docker/jellyseerr +jellyseerr_image: fallenbagel/jellyseerr:latest +jellyseerr_domain: jellyseerr.example.invalid +jellyseerr_service_port: 5055 +jellyseerr_routing_mode: "{{ nbops_service_routing_mode }}" +jellyseerr_env: + LOG_LEVEL: info + TZ: "{{ nbops_timezone }}" +jellyseerr_directories: + - "{{ jellyseerr_dir }}" + - /srv/config/jellyseerr +jellyseerr_volumes: + - /srv/config/jellyseerr:/app/config +jellyseerr_ports: + - { host: 5055, container: 5055, protocol: tcp } + diff --git a/roles/jellyseerr/files/.gitkeep b/roles/jellyseerr/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/jellyseerr/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/jellyseerr/handlers/.gitkeep b/roles/jellyseerr/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/jellyseerr/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/jellyseerr/tasks/main.yml b/roles/jellyseerr/tasks/main.yml new file mode 100644 index 0000000..eed656b --- /dev/null +++ b/roles/jellyseerr/tasks/main.yml @@ -0,0 +1,18 @@ +--- +- name: Deploy Jellyseerr + ansible.builtin.include_role: + name: docker_app + vars: + docker_app_env_template_src: "{{ playbook_dir }}/../roles/jellyseerr/templates/env.j2" + docker_app_compose_template_src: "{{ playbook_dir }}/../roles/jellyseerr/templates/compose.yaml.j2" + docker_app_name: "{{ jellyseerr_name }}" + docker_app_dir: "{{ jellyseerr_dir }}" + docker_app_image: "{{ jellyseerr_image }}" + docker_app_env: "{{ jellyseerr_env }}" + docker_app_directories: "{{ jellyseerr_directories }}" + docker_app_volumes: "{{ jellyseerr_volumes }}" + docker_app_ports: "{{ jellyseerr_ports }}" + docker_app_routing_mode: "{{ jellyseerr_routing_mode }}" + docker_app_domain: "{{ jellyseerr_domain }}" + docker_app_service_port: "{{ jellyseerr_service_port }}" + diff --git a/roles/jellyseerr/templates/.gitkeep b/roles/jellyseerr/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/jellyseerr/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/jellyseerr/templates/compose.yaml.j2 b/roles/jellyseerr/templates/compose.yaml.j2 new file mode 100644 index 0000000..6de1603 --- /dev/null +++ b/roles/jellyseerr/templates/compose.yaml.j2 @@ -0,0 +1,90 @@ +services: + {{ docker_app_name }}: + image: {{ docker_app_image }} + restart: {{ docker_app_restart }} +{% if docker_app_command | length > 0 %} + command: {{ docker_app_command | to_json }} +{% endif %} +{% if docker_app_user | length > 0 %} + user: "{{ docker_app_user }}" +{% endif %} +{% if docker_app_network_mode | length > 0 %} + network_mode: {{ docker_app_network_mode }} +{% endif %} +{% if docker_app_privileged | bool %} + privileged: true +{% endif %} +{% if docker_app_env | length > 0 %} + env_file: .env +{% endif %} +{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} + ports: +{% for port in docker_app_ports %} + - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_service_port %} + # internal service port: {{ docker_app_service_port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }} + - traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }} + - traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }} +{% if docker_app_traefik_middlewares | length > 0 %} + - traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }} +{% endif %} + - traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }} +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% elif docker_app_labels | length > 0 %} + labels: +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% endif %} +{% if docker_app_volumes | length > 0 %} + volumes: +{% for volume in docker_app_volumes %} + - {{ volume }} +{% endfor %} +{% endif %} +{% if docker_app_devices | length > 0 %} + devices: +{% for device in docker_app_devices %} + - {{ device }} +{% endfor %} +{% endif %} +{% if docker_app_cap_add | length > 0 %} + cap_add: +{% for capability in docker_app_cap_add %} + - {{ capability }} +{% endfor %} +{% endif %} +{% if docker_app_group_add | length > 0 %} + group_add: +{% for group in docker_app_group_add %} + - "{{ group }}" +{% endfor %} +{% endif %} +{% if docker_app_extra_hosts | length > 0 %} + extra_hosts: +{% for host in docker_app_extra_hosts %} + - {{ host }} +{% endfor %} +{% endif %} +{% if docker_app_depends_on | length > 0 %} + depends_on: +{% for dependency in docker_app_depends_on %} + - {{ dependency }} +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} + networks: + - {{ docker_app_traefik_network }} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} +networks: + {{ docker_app_traefik_network }}: + external: true +{% endif %} diff --git a/roles/jellyseerr/templates/env.j2 b/roles/jellyseerr/templates/env.j2 new file mode 100644 index 0000000..c4f1a9f --- /dev/null +++ b/roles/jellyseerr/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in docker_app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/kavita/README.md b/roles/kavita/README.md new file mode 100644 index 0000000..d6c5edc --- /dev/null +++ b/roles/kavita/README.md @@ -0,0 +1,4 @@ +# kavita + +Deploys Kavita with Docker Compose. + diff --git a/roles/kavita/defaults/main.yml b/roles/kavita/defaults/main.yml new file mode 100644 index 0000000..ba40af0 --- /dev/null +++ b/roles/kavita/defaults/main.yml @@ -0,0 +1,21 @@ +--- +kavita_name: kavita +kavita_dir: /srv/docker/kavita +kavita_image: lscr.io/linuxserver/kavita:latest +kavita_domain: kavita.example.invalid +kavita_service_port: 5000 +kavita_routing_mode: "{{ nbops_service_routing_mode }}" +kavita_env: + PUID: "1000" + PGID: "1000" + TZ: "{{ nbops_timezone }}" +kavita_directories: + - "{{ kavita_dir }}" + - /srv/config/kavita + - /srv/media/books +kavita_volumes: + - /srv/config/kavita:/config + - /srv/media/books:/books +kavita_ports: + - { host: 5000, container: 5000, protocol: tcp } + diff --git a/roles/kavita/files/.gitkeep b/roles/kavita/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/kavita/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/kavita/handlers/.gitkeep b/roles/kavita/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/kavita/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/kavita/tasks/main.yml b/roles/kavita/tasks/main.yml new file mode 100644 index 0000000..c6ef68a --- /dev/null +++ b/roles/kavita/tasks/main.yml @@ -0,0 +1,18 @@ +--- +- name: Deploy Kavita + ansible.builtin.include_role: + name: docker_app + vars: + docker_app_env_template_src: "{{ playbook_dir }}/../roles/kavita/templates/env.j2" + docker_app_compose_template_src: "{{ playbook_dir }}/../roles/kavita/templates/compose.yaml.j2" + docker_app_name: "{{ kavita_name }}" + docker_app_dir: "{{ kavita_dir }}" + docker_app_image: "{{ kavita_image }}" + docker_app_env: "{{ kavita_env }}" + docker_app_directories: "{{ kavita_directories }}" + docker_app_volumes: "{{ kavita_volumes }}" + docker_app_ports: "{{ kavita_ports }}" + docker_app_routing_mode: "{{ kavita_routing_mode }}" + docker_app_domain: "{{ kavita_domain }}" + docker_app_service_port: "{{ kavita_service_port }}" + diff --git a/roles/kavita/templates/.gitkeep b/roles/kavita/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/kavita/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/kavita/templates/compose.yaml.j2 b/roles/kavita/templates/compose.yaml.j2 new file mode 100644 index 0000000..6de1603 --- /dev/null +++ b/roles/kavita/templates/compose.yaml.j2 @@ -0,0 +1,90 @@ +services: + {{ docker_app_name }}: + image: {{ docker_app_image }} + restart: {{ docker_app_restart }} +{% if docker_app_command | length > 0 %} + command: {{ docker_app_command | to_json }} +{% endif %} +{% if docker_app_user | length > 0 %} + user: "{{ docker_app_user }}" +{% endif %} +{% if docker_app_network_mode | length > 0 %} + network_mode: {{ docker_app_network_mode }} +{% endif %} +{% if docker_app_privileged | bool %} + privileged: true +{% endif %} +{% if docker_app_env | length > 0 %} + env_file: .env +{% endif %} +{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} + ports: +{% for port in docker_app_ports %} + - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_service_port %} + # internal service port: {{ docker_app_service_port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }} + - traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }} + - traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }} +{% if docker_app_traefik_middlewares | length > 0 %} + - traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }} +{% endif %} + - traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }} +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% elif docker_app_labels | length > 0 %} + labels: +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% endif %} +{% if docker_app_volumes | length > 0 %} + volumes: +{% for volume in docker_app_volumes %} + - {{ volume }} +{% endfor %} +{% endif %} +{% if docker_app_devices | length > 0 %} + devices: +{% for device in docker_app_devices %} + - {{ device }} +{% endfor %} +{% endif %} +{% if docker_app_cap_add | length > 0 %} + cap_add: +{% for capability in docker_app_cap_add %} + - {{ capability }} +{% endfor %} +{% endif %} +{% if docker_app_group_add | length > 0 %} + group_add: +{% for group in docker_app_group_add %} + - "{{ group }}" +{% endfor %} +{% endif %} +{% if docker_app_extra_hosts | length > 0 %} + extra_hosts: +{% for host in docker_app_extra_hosts %} + - {{ host }} +{% endfor %} +{% endif %} +{% if docker_app_depends_on | length > 0 %} + depends_on: +{% for dependency in docker_app_depends_on %} + - {{ dependency }} +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} + networks: + - {{ docker_app_traefik_network }} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} +networks: + {{ docker_app_traefik_network }}: + external: true +{% endif %} diff --git a/roles/kavita/templates/env.j2 b/roles/kavita/templates/env.j2 new file mode 100644 index 0000000..c4f1a9f --- /dev/null +++ b/roles/kavita/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in docker_app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/kiwix_serve/README.md b/roles/kiwix_serve/README.md new file mode 100644 index 0000000..01383d5 --- /dev/null +++ b/roles/kiwix_serve/README.md @@ -0,0 +1,4 @@ +# kiwix_serve + +Deploys kiwix-serve with Docker Compose. + diff --git a/roles/kiwix_serve/defaults/main.yml b/roles/kiwix_serve/defaults/main.yml new file mode 100644 index 0000000..71fdab2 --- /dev/null +++ b/roles/kiwix_serve/defaults/main.yml @@ -0,0 +1,17 @@ +--- +kiwix_serve_name: kiwix-serve +kiwix_serve_dir: /srv/docker/kiwix-serve +kiwix_serve_image: ghcr.io/kiwix/kiwix-serve:latest +kiwix_serve_domain: kiwix.example.invalid +kiwix_serve_service_port: 8080 +kiwix_serve_routing_mode: "{{ nbops_service_routing_mode }}" +kiwix_serve_command: "*.zim" +kiwix_serve_env: {} +kiwix_serve_directories: + - "{{ kiwix_serve_dir }}" + - /srv/media/zim +kiwix_serve_volumes: + - /srv/media/zim:/data:ro +kiwix_serve_ports: + - { host: 8084, container: 8080, protocol: tcp } + diff --git a/roles/kiwix_serve/files/.gitkeep b/roles/kiwix_serve/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/kiwix_serve/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/kiwix_serve/handlers/.gitkeep b/roles/kiwix_serve/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/kiwix_serve/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/kiwix_serve/tasks/main.yml b/roles/kiwix_serve/tasks/main.yml new file mode 100644 index 0000000..4bbde1a --- /dev/null +++ b/roles/kiwix_serve/tasks/main.yml @@ -0,0 +1,19 @@ +--- +- name: Deploy kiwix-serve + ansible.builtin.include_role: + name: docker_app + vars: + docker_app_env_template_src: "{{ playbook_dir }}/../roles/kiwix_serve/templates/env.j2" + docker_app_compose_template_src: "{{ playbook_dir }}/../roles/kiwix_serve/templates/compose.yaml.j2" + docker_app_name: "{{ kiwix_serve_name }}" + docker_app_dir: "{{ kiwix_serve_dir }}" + docker_app_image: "{{ kiwix_serve_image }}" + docker_app_command: "{{ kiwix_serve_command }}" + docker_app_env: "{{ kiwix_serve_env }}" + docker_app_directories: "{{ kiwix_serve_directories }}" + docker_app_volumes: "{{ kiwix_serve_volumes }}" + docker_app_ports: "{{ kiwix_serve_ports }}" + docker_app_routing_mode: "{{ kiwix_serve_routing_mode }}" + docker_app_domain: "{{ kiwix_serve_domain }}" + docker_app_service_port: "{{ kiwix_serve_service_port }}" + diff --git a/roles/kiwix_serve/templates/.gitkeep b/roles/kiwix_serve/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/kiwix_serve/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/kiwix_serve/templates/compose.yaml.j2 b/roles/kiwix_serve/templates/compose.yaml.j2 new file mode 100644 index 0000000..6de1603 --- /dev/null +++ b/roles/kiwix_serve/templates/compose.yaml.j2 @@ -0,0 +1,90 @@ +services: + {{ docker_app_name }}: + image: {{ docker_app_image }} + restart: {{ docker_app_restart }} +{% if docker_app_command | length > 0 %} + command: {{ docker_app_command | to_json }} +{% endif %} +{% if docker_app_user | length > 0 %} + user: "{{ docker_app_user }}" +{% endif %} +{% if docker_app_network_mode | length > 0 %} + network_mode: {{ docker_app_network_mode }} +{% endif %} +{% if docker_app_privileged | bool %} + privileged: true +{% endif %} +{% if docker_app_env | length > 0 %} + env_file: .env +{% endif %} +{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} + ports: +{% for port in docker_app_ports %} + - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_service_port %} + # internal service port: {{ docker_app_service_port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }} + - traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }} + - traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }} +{% if docker_app_traefik_middlewares | length > 0 %} + - traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }} +{% endif %} + - traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }} +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% elif docker_app_labels | length > 0 %} + labels: +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% endif %} +{% if docker_app_volumes | length > 0 %} + volumes: +{% for volume in docker_app_volumes %} + - {{ volume }} +{% endfor %} +{% endif %} +{% if docker_app_devices | length > 0 %} + devices: +{% for device in docker_app_devices %} + - {{ device }} +{% endfor %} +{% endif %} +{% if docker_app_cap_add | length > 0 %} + cap_add: +{% for capability in docker_app_cap_add %} + - {{ capability }} +{% endfor %} +{% endif %} +{% if docker_app_group_add | length > 0 %} + group_add: +{% for group in docker_app_group_add %} + - "{{ group }}" +{% endfor %} +{% endif %} +{% if docker_app_extra_hosts | length > 0 %} + extra_hosts: +{% for host in docker_app_extra_hosts %} + - {{ host }} +{% endfor %} +{% endif %} +{% if docker_app_depends_on | length > 0 %} + depends_on: +{% for dependency in docker_app_depends_on %} + - {{ dependency }} +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} + networks: + - {{ docker_app_traefik_network }} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} +networks: + {{ docker_app_traefik_network }}: + external: true +{% endif %} diff --git a/roles/kiwix_serve/templates/env.j2 b/roles/kiwix_serve/templates/env.j2 new file mode 100644 index 0000000..c4f1a9f --- /dev/null +++ b/roles/kiwix_serve/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in docker_app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/loki/README.md b/roles/loki/README.md new file mode 100644 index 0000000..e2dcc4b --- /dev/null +++ b/roles/loki/README.md @@ -0,0 +1,4 @@ +# loki + +Deploys Grafana Loki with Docker Compose. + diff --git a/roles/loki/defaults/main.yml b/roles/loki/defaults/main.yml new file mode 100644 index 0000000..906010a --- /dev/null +++ b/roles/loki/defaults/main.yml @@ -0,0 +1,17 @@ +--- +loki_name: loki +loki_dir: /srv/docker/loki +loki_image: grafana/loki:latest +loki_domain: loki.example.invalid +loki_service_port: 3100 +loki_routing_mode: host +loki_command: "-config.file=/etc/loki/local-config.yaml" +loki_env: {} +loki_directories: + - "{{ loki_dir }}" + - /srv/data/loki +loki_volumes: + - /srv/data/loki:/loki +loki_ports: + - { host: 3100, container: 3100, protocol: tcp } + diff --git a/roles/loki/files/.gitkeep b/roles/loki/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/loki/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/loki/handlers/.gitkeep b/roles/loki/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/loki/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/loki/tasks/main.yml b/roles/loki/tasks/main.yml new file mode 100644 index 0000000..cae73f7 --- /dev/null +++ b/roles/loki/tasks/main.yml @@ -0,0 +1,19 @@ +--- +- name: Deploy Grafana Loki + ansible.builtin.include_role: + name: docker_app + vars: + docker_app_env_template_src: "{{ playbook_dir }}/../roles/loki/templates/env.j2" + docker_app_compose_template_src: "{{ playbook_dir }}/../roles/loki/templates/compose.yaml.j2" + docker_app_name: "{{ loki_name }}" + docker_app_dir: "{{ loki_dir }}" + docker_app_image: "{{ loki_image }}" + docker_app_command: "{{ loki_command }}" + docker_app_env: "{{ loki_env }}" + docker_app_directories: "{{ loki_directories }}" + docker_app_volumes: "{{ loki_volumes }}" + docker_app_ports: "{{ loki_ports }}" + docker_app_routing_mode: "{{ loki_routing_mode }}" + docker_app_domain: "{{ loki_domain }}" + docker_app_service_port: "{{ loki_service_port }}" + diff --git a/roles/loki/templates/.gitkeep b/roles/loki/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/loki/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/loki/templates/compose.yaml.j2 b/roles/loki/templates/compose.yaml.j2 new file mode 100644 index 0000000..6de1603 --- /dev/null +++ b/roles/loki/templates/compose.yaml.j2 @@ -0,0 +1,90 @@ +services: + {{ docker_app_name }}: + image: {{ docker_app_image }} + restart: {{ docker_app_restart }} +{% if docker_app_command | length > 0 %} + command: {{ docker_app_command | to_json }} +{% endif %} +{% if docker_app_user | length > 0 %} + user: "{{ docker_app_user }}" +{% endif %} +{% if docker_app_network_mode | length > 0 %} + network_mode: {{ docker_app_network_mode }} +{% endif %} +{% if docker_app_privileged | bool %} + privileged: true +{% endif %} +{% if docker_app_env | length > 0 %} + env_file: .env +{% endif %} +{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} + ports: +{% for port in docker_app_ports %} + - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_service_port %} + # internal service port: {{ docker_app_service_port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }} + - traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }} + - traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }} +{% if docker_app_traefik_middlewares | length > 0 %} + - traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }} +{% endif %} + - traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }} +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% elif docker_app_labels | length > 0 %} + labels: +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% endif %} +{% if docker_app_volumes | length > 0 %} + volumes: +{% for volume in docker_app_volumes %} + - {{ volume }} +{% endfor %} +{% endif %} +{% if docker_app_devices | length > 0 %} + devices: +{% for device in docker_app_devices %} + - {{ device }} +{% endfor %} +{% endif %} +{% if docker_app_cap_add | length > 0 %} + cap_add: +{% for capability in docker_app_cap_add %} + - {{ capability }} +{% endfor %} +{% endif %} +{% if docker_app_group_add | length > 0 %} + group_add: +{% for group in docker_app_group_add %} + - "{{ group }}" +{% endfor %} +{% endif %} +{% if docker_app_extra_hosts | length > 0 %} + extra_hosts: +{% for host in docker_app_extra_hosts %} + - {{ host }} +{% endfor %} +{% endif %} +{% if docker_app_depends_on | length > 0 %} + depends_on: +{% for dependency in docker_app_depends_on %} + - {{ dependency }} +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} + networks: + - {{ docker_app_traefik_network }} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} +networks: + {{ docker_app_traefik_network }}: + external: true +{% endif %} diff --git a/roles/loki/templates/env.j2 b/roles/loki/templates/env.j2 new file mode 100644 index 0000000..c4f1a9f --- /dev/null +++ b/roles/loki/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in docker_app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/monitoring/README.md b/roles/monitoring/README.md new file mode 100644 index 0000000..5a5da30 --- /dev/null +++ b/roles/monitoring/README.md @@ -0,0 +1,8 @@ +# monitoring + +Abstract monitoring hook for installing a replaceable agent. Set +`monitoring_agent`, `monitoring_packages`, `monitoring_services`, and optional +template mappings in inventory. + +Tags: `monitoring`, `config`, `services`. + diff --git a/roles/monitoring/defaults/main.yml b/roles/monitoring/defaults/main.yml new file mode 100644 index 0000000..4bd9a10 --- /dev/null +++ b/roles/monitoring/defaults/main.yml @@ -0,0 +1,6 @@ +--- +monitoring_agent: none +monitoring_packages: [] +monitoring_services: [] +monitoring_config_files: [] + diff --git a/roles/monitoring/files/.gitkeep b/roles/monitoring/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/monitoring/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/monitoring/handlers/main.yml b/roles/monitoring/handlers/main.yml new file mode 100644 index 0000000..78ef5da --- /dev/null +++ b/roles/monitoring/handlers/main.yml @@ -0,0 +1,7 @@ +--- +- name: restart monitoring services + ansible.builtin.service: + name: "{{ item }}" + state: restarted + loop: "{{ monitoring_services }}" + diff --git a/roles/monitoring/tasks/main.yml b/roles/monitoring/tasks/main.yml new file mode 100644 index 0000000..20663a1 --- /dev/null +++ b/roles/monitoring/tasks/main.yml @@ -0,0 +1,30 @@ +--- +- name: Install monitoring packages + ansible.builtin.apt: + name: "{{ monitoring_packages }}" + state: present + update_cache: true + when: monitoring_agent != "none" + tags: [monitoring] + +- name: Deploy monitoring configuration files + ansible.builtin.template: + src: "{{ item.template }}" + dest: "{{ item.dest }}" + owner: "{{ item.owner | default('root') }}" + group: "{{ item.group | default('root') }}" + mode: "{{ item.mode | default('0644') }}" + loop: "{{ monitoring_config_files }}" + when: monitoring_agent != "none" + notify: restart monitoring services + tags: [monitoring, config] + +- name: Enable monitoring services + ansible.builtin.service: + name: "{{ item }}" + enabled: true + state: started + loop: "{{ monitoring_services }}" + when: monitoring_agent != "none" + tags: [monitoring, services] + diff --git a/roles/monitoring/templates/.gitkeep b/roles/monitoring/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/monitoring/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/mtls/README.md b/roles/mtls/README.md new file mode 100644 index 0000000..0b93cc9 --- /dev/null +++ b/roles/mtls/README.md @@ -0,0 +1,7 @@ +# mtls + +Prepares trust directories and deploys externally issued CA certificates, host +certificates, and private keys. This role does not generate certificates. + +Tags: `mtls`, `pki`. + diff --git a/roles/mtls/defaults/main.yml b/roles/mtls/defaults/main.yml new file mode 100644 index 0000000..a4e9763 --- /dev/null +++ b/roles/mtls/defaults/main.yml @@ -0,0 +1,8 @@ +--- +mtls_ca_certificates: [] +mtls_host_certificates: [] +mtls_private_keys: [] +mtls_ca_directory: /usr/local/share/ca-certificates/internal +mtls_cert_directory: /etc/pki/internal/certs +mtls_private_directory: /etc/pki/internal/private + diff --git a/roles/mtls/files/.gitkeep b/roles/mtls/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/mtls/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/mtls/handlers/main.yml b/roles/mtls/handlers/main.yml new file mode 100644 index 0000000..895edbc --- /dev/null +++ b/roles/mtls/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: update ca certificates + ansible.builtin.command: + cmd: update-ca-certificates + changed_when: true + diff --git a/roles/mtls/tasks/main.yml b/roles/mtls/tasks/main.yml new file mode 100644 index 0000000..d21d533 --- /dev/null +++ b/roles/mtls/tasks/main.yml @@ -0,0 +1,48 @@ +--- +- name: Ensure mTLS directories exist + ansible.builtin.file: + path: "{{ item.path }}" + state: directory + owner: root + group: root + mode: "{{ item.mode }}" + loop: + - { path: "{{ mtls_ca_directory }}", mode: "0755" } + - { path: "{{ mtls_cert_directory }}", mode: "0755" } + - { path: "{{ mtls_private_directory }}", mode: "0700" } + tags: [mtls, pki] + +- name: Deploy trusted CA certificates + ansible.builtin.copy: + src: "{{ item.src }}" + dest: "{{ mtls_ca_directory }}/{{ item.name }}" + owner: root + group: root + mode: "0644" + loop: "{{ mtls_ca_certificates }}" + notify: update ca certificates + no_log: "{{ item.no_log | default(false) }}" + tags: [mtls, pki] + +- name: Deploy host certificates + ansible.builtin.copy: + src: "{{ item.src }}" + dest: "{{ mtls_cert_directory }}/{{ item.name }}" + owner: root + group: root + mode: "0644" + loop: "{{ mtls_host_certificates }}" + no_log: "{{ item.no_log | default(false) }}" + tags: [mtls, pki] + +- name: Deploy host private keys + ansible.builtin.copy: + src: "{{ item.src }}" + dest: "{{ mtls_private_directory }}/{{ item.name }}" + owner: root + group: root + mode: "0600" + loop: "{{ mtls_private_keys }}" + no_log: true + tags: [mtls, pki] + diff --git a/roles/mtls/templates/.gitkeep b/roles/mtls/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/mtls/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/nginx_content/README.md b/roles/nginx_content/README.md new file mode 100644 index 0000000..43e6d34 --- /dev/null +++ b/roles/nginx_content/README.md @@ -0,0 +1,4 @@ +# nginx_content + +Deploys Nginx for static content delivery and file serving. + diff --git a/roles/nginx_content/defaults/main.yml b/roles/nginx_content/defaults/main.yml new file mode 100644 index 0000000..e73675d --- /dev/null +++ b/roles/nginx_content/defaults/main.yml @@ -0,0 +1,18 @@ +--- +nginx_content_name: nginx-content +nginx_content_dir: /srv/docker/nginx-content +nginx_content_image: nginx:stable-alpine +nginx_content_domain: content.example.invalid +nginx_content_service_port: 80 +nginx_content_routing_mode: "{{ nbops_service_routing_mode }}" +nginx_content_env: {} +nginx_content_directories: + - "{{ nginx_content_dir }}" + - /srv/media/content + - /srv/config/nginx-content +nginx_content_volumes: + - /srv/media/content:/usr/share/nginx/html:ro + - /srv/config/nginx-content:/etc/nginx/conf.d:ro +nginx_content_ports: + - { host: 8088, container: 80, protocol: tcp } + diff --git a/roles/nginx_content/files/.gitkeep b/roles/nginx_content/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/nginx_content/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/nginx_content/handlers/.gitkeep b/roles/nginx_content/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/nginx_content/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/nginx_content/tasks/main.yml b/roles/nginx_content/tasks/main.yml new file mode 100644 index 0000000..a349137 --- /dev/null +++ b/roles/nginx_content/tasks/main.yml @@ -0,0 +1,18 @@ +--- +- name: Deploy Nginx content server + ansible.builtin.include_role: + name: docker_app + vars: + docker_app_env_template_src: "{{ playbook_dir }}/../roles/nginx_content/templates/env.j2" + docker_app_compose_template_src: "{{ playbook_dir }}/../roles/nginx_content/templates/compose.yaml.j2" + docker_app_name: "{{ nginx_content_name }}" + docker_app_dir: "{{ nginx_content_dir }}" + docker_app_image: "{{ nginx_content_image }}" + docker_app_env: "{{ nginx_content_env }}" + docker_app_directories: "{{ nginx_content_directories }}" + docker_app_volumes: "{{ nginx_content_volumes }}" + docker_app_ports: "{{ nginx_content_ports }}" + docker_app_routing_mode: "{{ nginx_content_routing_mode }}" + docker_app_domain: "{{ nginx_content_domain }}" + docker_app_service_port: "{{ nginx_content_service_port }}" + diff --git a/roles/nginx_content/templates/.gitkeep b/roles/nginx_content/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/nginx_content/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/nginx_content/templates/compose.yaml.j2 b/roles/nginx_content/templates/compose.yaml.j2 new file mode 100644 index 0000000..6de1603 --- /dev/null +++ b/roles/nginx_content/templates/compose.yaml.j2 @@ -0,0 +1,90 @@ +services: + {{ docker_app_name }}: + image: {{ docker_app_image }} + restart: {{ docker_app_restart }} +{% if docker_app_command | length > 0 %} + command: {{ docker_app_command | to_json }} +{% endif %} +{% if docker_app_user | length > 0 %} + user: "{{ docker_app_user }}" +{% endif %} +{% if docker_app_network_mode | length > 0 %} + network_mode: {{ docker_app_network_mode }} +{% endif %} +{% if docker_app_privileged | bool %} + privileged: true +{% endif %} +{% if docker_app_env | length > 0 %} + env_file: .env +{% endif %} +{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} + ports: +{% for port in docker_app_ports %} + - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_service_port %} + # internal service port: {{ docker_app_service_port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }} + - traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }} + - traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }} +{% if docker_app_traefik_middlewares | length > 0 %} + - traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }} +{% endif %} + - traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }} +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% elif docker_app_labels | length > 0 %} + labels: +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% endif %} +{% if docker_app_volumes | length > 0 %} + volumes: +{% for volume in docker_app_volumes %} + - {{ volume }} +{% endfor %} +{% endif %} +{% if docker_app_devices | length > 0 %} + devices: +{% for device in docker_app_devices %} + - {{ device }} +{% endfor %} +{% endif %} +{% if docker_app_cap_add | length > 0 %} + cap_add: +{% for capability in docker_app_cap_add %} + - {{ capability }} +{% endfor %} +{% endif %} +{% if docker_app_group_add | length > 0 %} + group_add: +{% for group in docker_app_group_add %} + - "{{ group }}" +{% endfor %} +{% endif %} +{% if docker_app_extra_hosts | length > 0 %} + extra_hosts: +{% for host in docker_app_extra_hosts %} + - {{ host }} +{% endfor %} +{% endif %} +{% if docker_app_depends_on | length > 0 %} + depends_on: +{% for dependency in docker_app_depends_on %} + - {{ dependency }} +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} + networks: + - {{ docker_app_traefik_network }} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} +networks: + {{ docker_app_traefik_network }}: + external: true +{% endif %} diff --git a/roles/nginx_content/templates/env.j2 b/roles/nginx_content/templates/env.j2 new file mode 100644 index 0000000..c4f1a9f --- /dev/null +++ b/roles/nginx_content/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in docker_app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/ntfy/README.md b/roles/ntfy/README.md new file mode 100644 index 0000000..2d28d92 --- /dev/null +++ b/roles/ntfy/README.md @@ -0,0 +1,4 @@ +# ntfy + +Deploys ntfy with Docker Compose. + diff --git a/roles/ntfy/defaults/main.yml b/roles/ntfy/defaults/main.yml new file mode 100644 index 0000000..e0073de --- /dev/null +++ b/roles/ntfy/defaults/main.yml @@ -0,0 +1,20 @@ +--- +ntfy_name: ntfy +ntfy_dir: /srv/docker/ntfy +ntfy_image: binwiederhier/ntfy:latest +ntfy_domain: ntfy.example.invalid +ntfy_service_port: 80 +ntfy_routing_mode: "{{ nbops_service_routing_mode }}" +ntfy_command: serve +ntfy_env: + TZ: "{{ nbops_timezone }}" +ntfy_directories: + - "{{ ntfy_dir }}" + - /srv/cache/ntfy + - /srv/config/ntfy +ntfy_volumes: + - /srv/cache/ntfy:/var/cache/ntfy + - /srv/config/ntfy:/etc/ntfy +ntfy_ports: + - { host: 8085, container: 80, protocol: tcp } + diff --git a/roles/ntfy/files/.gitkeep b/roles/ntfy/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/ntfy/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/ntfy/handlers/.gitkeep b/roles/ntfy/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/ntfy/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/ntfy/tasks/main.yml b/roles/ntfy/tasks/main.yml new file mode 100644 index 0000000..3e6016a --- /dev/null +++ b/roles/ntfy/tasks/main.yml @@ -0,0 +1,19 @@ +--- +- name: Deploy ntfy + ansible.builtin.include_role: + name: docker_app + vars: + docker_app_env_template_src: "{{ playbook_dir }}/../roles/ntfy/templates/env.j2" + docker_app_compose_template_src: "{{ playbook_dir }}/../roles/ntfy/templates/compose.yaml.j2" + docker_app_name: "{{ ntfy_name }}" + docker_app_dir: "{{ ntfy_dir }}" + docker_app_image: "{{ ntfy_image }}" + docker_app_command: "{{ ntfy_command }}" + docker_app_env: "{{ ntfy_env }}" + docker_app_directories: "{{ ntfy_directories }}" + docker_app_volumes: "{{ ntfy_volumes }}" + docker_app_ports: "{{ ntfy_ports }}" + docker_app_routing_mode: "{{ ntfy_routing_mode }}" + docker_app_domain: "{{ ntfy_domain }}" + docker_app_service_port: "{{ ntfy_service_port }}" + diff --git a/roles/ntfy/templates/.gitkeep b/roles/ntfy/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/ntfy/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/ntfy/templates/compose.yaml.j2 b/roles/ntfy/templates/compose.yaml.j2 new file mode 100644 index 0000000..6de1603 --- /dev/null +++ b/roles/ntfy/templates/compose.yaml.j2 @@ -0,0 +1,90 @@ +services: + {{ docker_app_name }}: + image: {{ docker_app_image }} + restart: {{ docker_app_restart }} +{% if docker_app_command | length > 0 %} + command: {{ docker_app_command | to_json }} +{% endif %} +{% if docker_app_user | length > 0 %} + user: "{{ docker_app_user }}" +{% endif %} +{% if docker_app_network_mode | length > 0 %} + network_mode: {{ docker_app_network_mode }} +{% endif %} +{% if docker_app_privileged | bool %} + privileged: true +{% endif %} +{% if docker_app_env | length > 0 %} + env_file: .env +{% endif %} +{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} + ports: +{% for port in docker_app_ports %} + - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_service_port %} + # internal service port: {{ docker_app_service_port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }} + - traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }} + - traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }} +{% if docker_app_traefik_middlewares | length > 0 %} + - traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }} +{% endif %} + - traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }} +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% elif docker_app_labels | length > 0 %} + labels: +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% endif %} +{% if docker_app_volumes | length > 0 %} + volumes: +{% for volume in docker_app_volumes %} + - {{ volume }} +{% endfor %} +{% endif %} +{% if docker_app_devices | length > 0 %} + devices: +{% for device in docker_app_devices %} + - {{ device }} +{% endfor %} +{% endif %} +{% if docker_app_cap_add | length > 0 %} + cap_add: +{% for capability in docker_app_cap_add %} + - {{ capability }} +{% endfor %} +{% endif %} +{% if docker_app_group_add | length > 0 %} + group_add: +{% for group in docker_app_group_add %} + - "{{ group }}" +{% endfor %} +{% endif %} +{% if docker_app_extra_hosts | length > 0 %} + extra_hosts: +{% for host in docker_app_extra_hosts %} + - {{ host }} +{% endfor %} +{% endif %} +{% if docker_app_depends_on | length > 0 %} + depends_on: +{% for dependency in docker_app_depends_on %} + - {{ dependency }} +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} + networks: + - {{ docker_app_traefik_network }} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} +networks: + {{ docker_app_traefik_network }}: + external: true +{% endif %} diff --git a/roles/ntfy/templates/env.j2 b/roles/ntfy/templates/env.j2 new file mode 100644 index 0000000..c4f1a9f --- /dev/null +++ b/roles/ntfy/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in docker_app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/ocis/README.md b/roles/ocis/README.md new file mode 100644 index 0000000..5bdacaa --- /dev/null +++ b/roles/ocis/README.md @@ -0,0 +1,7 @@ +# ocis + +Deploys ownCloud Infinite Scale using Docker Compose with externalized config +and data directories. + +Tags: `ocis`, `config`, `compose`, `firewall`. + diff --git a/roles/ocis/defaults/main.yml b/roles/ocis/defaults/main.yml new file mode 100644 index 0000000..907b831 --- /dev/null +++ b/roles/ocis/defaults/main.yml @@ -0,0 +1,20 @@ +--- +ocis_dir: /srv/docker/ocis +ocis_image: owncloud/ocis:latest +ocis_http_port: 9200 +ocis_url: https://ocis.example.invalid +ocis_domain: "{{ ocis_url | regex_replace('^https?://', '') | regex_replace('/.*$', '') }}" +ocis_routing_mode: "{{ nbops_service_routing_mode }}" +ocis_traefik_network: "{{ nbops_proxy_network }}" +ocis_traefik_entrypoint: "{{ nbops_traefik_entrypoint }}" +ocis_traefik_tls: "{{ nbops_traefik_tls }}" +ocis_config_dir: /srv/config/ocis +ocis_data_dir: /srv/data/ocis +ocis_insecure: "false" +ocis_admin_password: "{{ vault_ocis_admin_password | default('CHANGE_ME_IN_VAULT') }}" +ocis_env: + OCIS_URL: "{{ ocis_url }}" + OCIS_INSECURE: "{{ ocis_insecure }}" + IDM_ADMIN_PASSWORD: "{{ ocis_admin_password }}" +ocis_tcp_ports: + - "{{ ocis_http_port }}" diff --git a/roles/ocis/files/.gitkeep b/roles/ocis/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/ocis/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/ocis/handlers/main.yml b/roles/ocis/handlers/main.yml new file mode 100644 index 0000000..ebff1a9 --- /dev/null +++ b/roles/ocis/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: restart ocis + community.docker.docker_compose_v2: + project_src: "{{ ocis_dir }}" + state: restarted + diff --git a/roles/ocis/tasks/main.yml b/roles/ocis/tasks/main.yml new file mode 100644 index 0000000..d767049 --- /dev/null +++ b/roles/ocis/tasks/main.yml @@ -0,0 +1,55 @@ +--- +- name: Create oCIS directories + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: root + group: root + mode: "0755" + loop: + - "{{ ocis_dir }}" + - "{{ ocis_config_dir }}" + - "{{ ocis_data_dir }}" + tags: [ocis] + +- name: Render oCIS environment + ansible.builtin.template: + src: env.j2 + dest: "{{ ocis_dir }}/.env" + owner: root + group: root + mode: "0600" + no_log: true + tags: [ocis, config] + +- name: Ensure oCIS proxy network exists + community.docker.docker_network: + name: "{{ ocis_traefik_network }}" + state: present + when: ocis_routing_mode == "traefik" + tags: [ocis, docker] + +- name: Render oCIS Compose file + ansible.builtin.template: + src: compose.yaml.j2 + dest: "{{ ocis_dir }}/compose.yaml" + owner: root + group: root + mode: "0644" + notify: restart ocis + tags: [ocis, compose] + +- name: Allow oCIS ports + community.general.ufw: + rule: allow + port: "{{ item }}" + proto: tcp + loop: "{{ ocis_tcp_ports }}" + when: ocis_routing_mode == "host" + tags: [ocis, firewall] + +- name: Start oCIS + community.docker.docker_compose_v2: + project_src: "{{ ocis_dir }}" + state: present + tags: [ocis, compose] diff --git a/roles/ocis/templates/compose.yaml.j2 b/roles/ocis/templates/compose.yaml.j2 new file mode 100644 index 0000000..4cb13ef --- /dev/null +++ b/roles/ocis/templates/compose.yaml.j2 @@ -0,0 +1,29 @@ +services: + ocis: + image: {{ ocis_image }} + restart: unless-stopped + command: server + env_file: .env +{% if ocis_routing_mode == "host" %} + ports: + - "{{ ocis_http_port }}:9200" +{% endif %} +{% if ocis_routing_mode == "traefik" %} + # internal HTTP service port: 9200; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.ocis.rule=Host(`{{ ocis_domain }}`) + - traefik.http.routers.ocis.entrypoints={{ ocis_traefik_entrypoint }} + - traefik.http.routers.ocis.tls={{ ocis_traefik_tls | bool | ternary('true', 'false') }} + - traefik.http.services.ocis.loadbalancer.server.port=9200 + networks: + - {{ ocis_traefik_network }} +{% endif %} + volumes: + - {{ ocis_config_dir }}:/etc/ocis + - {{ ocis_data_dir }}:/var/lib/ocis +{% if ocis_routing_mode == "traefik" %} +networks: + {{ ocis_traefik_network }}: + external: true +{% endif %} diff --git a/roles/ocis/templates/env.j2 b/roles/ocis/templates/env.j2 new file mode 100644 index 0000000..916d8ab --- /dev/null +++ b/roles/ocis/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in ocis_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/paperless_ngx/README.md b/roles/paperless_ngx/README.md new file mode 100644 index 0000000..400bb61 --- /dev/null +++ b/roles/paperless_ngx/README.md @@ -0,0 +1,4 @@ +# paperless_ngx + +Deploys paperless-ngx with Docker Compose. + diff --git a/roles/paperless_ngx/defaults/main.yml b/roles/paperless_ngx/defaults/main.yml new file mode 100644 index 0000000..6a68921 --- /dev/null +++ b/roles/paperless_ngx/defaults/main.yml @@ -0,0 +1,24 @@ +--- +paperless_ngx_name: paperless-ngx +paperless_ngx_dir: /srv/docker/paperless-ngx +paperless_ngx_image: ghcr.io/paperless-ngx/paperless-ngx:latest +paperless_ngx_domain: paperless.example.invalid +paperless_ngx_service_port: 8000 +paperless_ngx_routing_mode: "{{ nbops_service_routing_mode }}" +paperless_ngx_env: + PAPERLESS_REDIS: redis://valkey:6379 + PAPERLESS_TIME_ZONE: "{{ nbops_timezone }}" + PAPERLESS_SECRET_KEY: "{{ vault_paperless_secret_key | default('CHANGE_ME_IN_VAULT') }}" +paperless_ngx_directories: + - "{{ paperless_ngx_dir }}" + - /srv/config/paperless-ngx + - /srv/data/paperless-ngx + - /srv/media/paperless-ngx +paperless_ngx_volumes: + - /srv/data/paperless-ngx:/usr/src/paperless/data + - /srv/media/paperless-ngx:/usr/src/paperless/media + - /srv/config/paperless-ngx/export:/usr/src/paperless/export + - /srv/config/paperless-ngx/consume:/usr/src/paperless/consume +paperless_ngx_ports: + - { host: 8000, container: 8000, protocol: tcp } + diff --git a/roles/paperless_ngx/files/.gitkeep b/roles/paperless_ngx/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/paperless_ngx/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/paperless_ngx/handlers/.gitkeep b/roles/paperless_ngx/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/paperless_ngx/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/paperless_ngx/tasks/main.yml b/roles/paperless_ngx/tasks/main.yml new file mode 100644 index 0000000..f168d2f --- /dev/null +++ b/roles/paperless_ngx/tasks/main.yml @@ -0,0 +1,18 @@ +--- +- name: Deploy paperless-ngx + ansible.builtin.include_role: + name: docker_app + vars: + docker_app_env_template_src: "{{ playbook_dir }}/../roles/paperless_ngx/templates/env.j2" + docker_app_compose_template_src: "{{ playbook_dir }}/../roles/paperless_ngx/templates/compose.yaml.j2" + docker_app_name: "{{ paperless_ngx_name }}" + docker_app_dir: "{{ paperless_ngx_dir }}" + docker_app_image: "{{ paperless_ngx_image }}" + docker_app_env: "{{ paperless_ngx_env }}" + docker_app_directories: "{{ paperless_ngx_directories }}" + docker_app_volumes: "{{ paperless_ngx_volumes }}" + docker_app_ports: "{{ paperless_ngx_ports }}" + docker_app_routing_mode: "{{ paperless_ngx_routing_mode }}" + docker_app_domain: "{{ paperless_ngx_domain }}" + docker_app_service_port: "{{ paperless_ngx_service_port }}" + diff --git a/roles/paperless_ngx/templates/.gitkeep b/roles/paperless_ngx/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/paperless_ngx/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/paperless_ngx/templates/compose.yaml.j2 b/roles/paperless_ngx/templates/compose.yaml.j2 new file mode 100644 index 0000000..6de1603 --- /dev/null +++ b/roles/paperless_ngx/templates/compose.yaml.j2 @@ -0,0 +1,90 @@ +services: + {{ docker_app_name }}: + image: {{ docker_app_image }} + restart: {{ docker_app_restart }} +{% if docker_app_command | length > 0 %} + command: {{ docker_app_command | to_json }} +{% endif %} +{% if docker_app_user | length > 0 %} + user: "{{ docker_app_user }}" +{% endif %} +{% if docker_app_network_mode | length > 0 %} + network_mode: {{ docker_app_network_mode }} +{% endif %} +{% if docker_app_privileged | bool %} + privileged: true +{% endif %} +{% if docker_app_env | length > 0 %} + env_file: .env +{% endif %} +{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} + ports: +{% for port in docker_app_ports %} + - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_service_port %} + # internal service port: {{ docker_app_service_port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }} + - traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }} + - traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }} +{% if docker_app_traefik_middlewares | length > 0 %} + - traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }} +{% endif %} + - traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }} +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% elif docker_app_labels | length > 0 %} + labels: +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% endif %} +{% if docker_app_volumes | length > 0 %} + volumes: +{% for volume in docker_app_volumes %} + - {{ volume }} +{% endfor %} +{% endif %} +{% if docker_app_devices | length > 0 %} + devices: +{% for device in docker_app_devices %} + - {{ device }} +{% endfor %} +{% endif %} +{% if docker_app_cap_add | length > 0 %} + cap_add: +{% for capability in docker_app_cap_add %} + - {{ capability }} +{% endfor %} +{% endif %} +{% if docker_app_group_add | length > 0 %} + group_add: +{% for group in docker_app_group_add %} + - "{{ group }}" +{% endfor %} +{% endif %} +{% if docker_app_extra_hosts | length > 0 %} + extra_hosts: +{% for host in docker_app_extra_hosts %} + - {{ host }} +{% endfor %} +{% endif %} +{% if docker_app_depends_on | length > 0 %} + depends_on: +{% for dependency in docker_app_depends_on %} + - {{ dependency }} +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} + networks: + - {{ docker_app_traefik_network }} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} +networks: + {{ docker_app_traefik_network }}: + external: true +{% endif %} diff --git a/roles/paperless_ngx/templates/env.j2 b/roles/paperless_ngx/templates/env.j2 new file mode 100644 index 0000000..c4f1a9f --- /dev/null +++ b/roles/paperless_ngx/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in docker_app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/pelican_panel/README.md b/roles/pelican_panel/README.md new file mode 100644 index 0000000..f094f7d --- /dev/null +++ b/roles/pelican_panel/README.md @@ -0,0 +1,4 @@ +# pelican_panel + +Deploys Pelican Panel with Docker Compose. + diff --git a/roles/pelican_panel/defaults/main.yml b/roles/pelican_panel/defaults/main.yml new file mode 100644 index 0000000..7a05444 --- /dev/null +++ b/roles/pelican_panel/defaults/main.yml @@ -0,0 +1,23 @@ +--- +pelican_panel_name: pelican-panel +pelican_panel_dir: /srv/docker/pelican-panel +pelican_panel_image: ghcr.io/pelican-dev/panel:latest +pelican_panel_domain: panel.example.invalid +pelican_panel_service_port: 80 +pelican_panel_routing_mode: "{{ nbops_service_routing_mode }}" +pelican_panel_env: + APP_URL: "https://{{ pelican_panel_domain }}" + APP_KEY: "{{ vault_pelican_panel_app_key | default('CHANGE_ME_IN_VAULT') }}" + DB_CONNECTION: pgsql + DB_HOST: postgres + DB_DATABASE: pelican + DB_USERNAME: pelican + DB_PASSWORD: "{{ vault_pelican_panel_db_password | default('CHANGE_ME_IN_VAULT') }}" +pelican_panel_directories: + - "{{ pelican_panel_dir }}" + - /srv/data/pelican-panel +pelican_panel_volumes: + - /srv/data/pelican-panel:/var/www/html/storage +pelican_panel_ports: + - { host: 8082, container: 80, protocol: tcp } + diff --git a/roles/pelican_panel/files/.gitkeep b/roles/pelican_panel/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/pelican_panel/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/pelican_panel/handlers/.gitkeep b/roles/pelican_panel/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/pelican_panel/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/pelican_panel/tasks/main.yml b/roles/pelican_panel/tasks/main.yml new file mode 100644 index 0000000..5301679 --- /dev/null +++ b/roles/pelican_panel/tasks/main.yml @@ -0,0 +1,18 @@ +--- +- name: Deploy Pelican Panel + ansible.builtin.include_role: + name: docker_app + vars: + docker_app_env_template_src: "{{ playbook_dir }}/../roles/pelican_panel/templates/env.j2" + docker_app_compose_template_src: "{{ playbook_dir }}/../roles/pelican_panel/templates/compose.yaml.j2" + docker_app_name: "{{ pelican_panel_name }}" + docker_app_dir: "{{ pelican_panel_dir }}" + docker_app_image: "{{ pelican_panel_image }}" + docker_app_env: "{{ pelican_panel_env }}" + docker_app_directories: "{{ pelican_panel_directories }}" + docker_app_volumes: "{{ pelican_panel_volumes }}" + docker_app_ports: "{{ pelican_panel_ports }}" + docker_app_routing_mode: "{{ pelican_panel_routing_mode }}" + docker_app_domain: "{{ pelican_panel_domain }}" + docker_app_service_port: "{{ pelican_panel_service_port }}" + diff --git a/roles/pelican_panel/templates/.gitkeep b/roles/pelican_panel/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/pelican_panel/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/pelican_panel/templates/compose.yaml.j2 b/roles/pelican_panel/templates/compose.yaml.j2 new file mode 100644 index 0000000..6de1603 --- /dev/null +++ b/roles/pelican_panel/templates/compose.yaml.j2 @@ -0,0 +1,90 @@ +services: + {{ docker_app_name }}: + image: {{ docker_app_image }} + restart: {{ docker_app_restart }} +{% if docker_app_command | length > 0 %} + command: {{ docker_app_command | to_json }} +{% endif %} +{% if docker_app_user | length > 0 %} + user: "{{ docker_app_user }}" +{% endif %} +{% if docker_app_network_mode | length > 0 %} + network_mode: {{ docker_app_network_mode }} +{% endif %} +{% if docker_app_privileged | bool %} + privileged: true +{% endif %} +{% if docker_app_env | length > 0 %} + env_file: .env +{% endif %} +{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} + ports: +{% for port in docker_app_ports %} + - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_service_port %} + # internal service port: {{ docker_app_service_port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }} + - traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }} + - traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }} +{% if docker_app_traefik_middlewares | length > 0 %} + - traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }} +{% endif %} + - traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }} +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% elif docker_app_labels | length > 0 %} + labels: +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% endif %} +{% if docker_app_volumes | length > 0 %} + volumes: +{% for volume in docker_app_volumes %} + - {{ volume }} +{% endfor %} +{% endif %} +{% if docker_app_devices | length > 0 %} + devices: +{% for device in docker_app_devices %} + - {{ device }} +{% endfor %} +{% endif %} +{% if docker_app_cap_add | length > 0 %} + cap_add: +{% for capability in docker_app_cap_add %} + - {{ capability }} +{% endfor %} +{% endif %} +{% if docker_app_group_add | length > 0 %} + group_add: +{% for group in docker_app_group_add %} + - "{{ group }}" +{% endfor %} +{% endif %} +{% if docker_app_extra_hosts | length > 0 %} + extra_hosts: +{% for host in docker_app_extra_hosts %} + - {{ host }} +{% endfor %} +{% endif %} +{% if docker_app_depends_on | length > 0 %} + depends_on: +{% for dependency in docker_app_depends_on %} + - {{ dependency }} +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} + networks: + - {{ docker_app_traefik_network }} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} +networks: + {{ docker_app_traefik_network }}: + external: true +{% endif %} diff --git a/roles/pelican_panel/templates/env.j2 b/roles/pelican_panel/templates/env.j2 new file mode 100644 index 0000000..c4f1a9f --- /dev/null +++ b/roles/pelican_panel/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in docker_app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/pelican_server/README.md b/roles/pelican_server/README.md new file mode 100644 index 0000000..f1c83d1 --- /dev/null +++ b/roles/pelican_server/README.md @@ -0,0 +1,22 @@ +# pelican_server + +Deploys a Pelican server/node daemon directly on a VM with systemd. + +This is intentionally separate from `pelican_panel`: the panel is the web +control plane, while this role installs the Wings daemon on a game node VM and +opens SFTP plus game allocation firewall ports. Docker is still required because the daemon uses Docker to run game server containers. + +Default ports: + +- Daemon API: `8080/tcp` +- SFTP: `2022/tcp` +- Game allocations: `25565:25575/tcp` + +By default the role downloads the latest binary from GitHub using the host +architecture. Override `pelican_server_binary_url` to pin a release. + +The role does not create the panel-provided `config.yml`; put it at +`pelican_server_config_file`. Set `pelican_server_require_config: false` only +when staging the node before the panel config exists. + +Tags: `pelican_server`. diff --git a/roles/pelican_server/defaults/main.yml b/roles/pelican_server/defaults/main.yml new file mode 100644 index 0000000..116518c --- /dev/null +++ b/roles/pelican_server/defaults/main.yml @@ -0,0 +1,29 @@ +--- +pelican_server_architecture_map: + x86_64: amd64 + amd64: amd64 + aarch64: arm64 + arm64: arm64 +pelican_server_architecture: "{{ pelican_server_architecture_map.get(ansible_architecture, 'arm64') }}" +pelican_server_binary_url: "https://github.com/pelican-dev/wings/releases/latest/download/wings_linux_{{ pelican_server_architecture }}" +pelican_server_binary_path: /usr/local/bin/wings +pelican_server_binary_mode: "0755" +pelican_server_config_file: /etc/pelican/config.yml +pelican_server_require_config: true + +pelican_server_config_dir: /etc/pelican +pelican_server_runtime_dir: /var/run/wings + +pelican_server_daemon_port: 8080 +pelican_server_sftp_port: 2022 +pelican_server_allocation_ports: + - { port: "25565:25575", proto: tcp } + +pelican_server_firewall_ports: + - { port: "{{ pelican_server_daemon_port }}", proto: tcp } + - { port: "{{ pelican_server_sftp_port }}", proto: tcp } + +pelican_server_service_name: wings +pelican_server_service_enabled: true +pelican_server_service_state: started +pelican_server_extra_args: [] diff --git a/roles/pelican_server/files/.gitkeep b/roles/pelican_server/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/pelican_server/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/pelican_server/handlers/main.yml b/roles/pelican_server/handlers/main.yml new file mode 100644 index 0000000..13e62a1 --- /dev/null +++ b/roles/pelican_server/handlers/main.yml @@ -0,0 +1,10 @@ +--- +- name: reload systemd + ansible.builtin.systemd: + daemon_reload: true + +- name: restart pelican server + ansible.builtin.service: + name: "{{ pelican_server_service_name }}" + state: restarted + diff --git a/roles/pelican_server/tasks/main.yml b/roles/pelican_server/tasks/main.yml new file mode 100644 index 0000000..95199b2 --- /dev/null +++ b/roles/pelican_server/tasks/main.yml @@ -0,0 +1,100 @@ +--- +- name: Install Pelican server daemon packages + ansible.builtin.apt: + name: + - ca-certificates + - curl + - tar + state: present + update_cache: true + tags: [pelican_server, packages] + +- name: Create Pelican server directories + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: root + group: root + mode: "0755" + loop: + - "{{ pelican_server_config_dir }}" + - "{{ pelican_server_runtime_dir }}" + tags: [pelican_server, directories] + +- name: Install Pelican server daemon binary + ansible.builtin.get_url: + url: "{{ pelican_server_binary_url }}" + dest: "{{ pelican_server_binary_path }}" + owner: root + group: root + mode: "{{ pelican_server_binary_mode }}" + notify: restart pelican server + tags: [pelican_server, install] + +- name: Check Pelican server daemon binary + ansible.builtin.stat: + path: "{{ pelican_server_binary_path }}" + register: pelican_server_binary + tags: [pelican_server, install] + +- name: Require Pelican server daemon binary + ansible.builtin.assert: + that: + - pelican_server_binary.stat.exists + - pelican_server_binary.stat.executable + fail_msg: >- + Pelican server daemon binary was not found at {{ pelican_server_binary_path }}. + Set pelican_server_binary_url or install the daemon binary before enabling this role. + tags: [pelican_server, install] + +- name: Check Pelican server config file + ansible.builtin.stat: + path: "{{ pelican_server_config_file }}" + register: pelican_server_config + when: pelican_server_require_config | bool + tags: [pelican_server, config] + +- name: Require Pelican server config file + ansible.builtin.assert: + that: + - pelican_server_config.stat.exists + fail_msg: >- + Pelican server config was not found at {{ pelican_server_config_file }}. + Create it from the Pelican panel node configuration before starting the daemon. + when: pelican_server_require_config | bool + tags: [pelican_server, config] + +- name: Install Pelican server systemd unit + ansible.builtin.template: + src: wings.service.j2 + dest: "/etc/systemd/system/{{ pelican_server_service_name }}.service" + owner: root + group: root + mode: "0644" + notify: + - reload systemd + - restart pelican server + tags: [pelican_server, systemd] + +- name: Open Pelican server daemon and SFTP ports + community.general.ufw: + rule: allow + port: "{{ item.port }}" + proto: "{{ item.proto | default('tcp') }}" + loop: "{{ pelican_server_firewall_ports }}" + tags: [pelican_server, firewall] + +- name: Open Pelican server allocation ports + community.general.ufw: + rule: allow + port: "{{ item.port }}" + proto: "{{ item.proto | default('tcp') }}" + loop: "{{ pelican_server_allocation_ports }}" + tags: [pelican_server, firewall] + +- name: Enable Pelican server service + ansible.builtin.service: + name: "{{ pelican_server_service_name }}" + enabled: "{{ pelican_server_service_enabled }}" + state: "{{ pelican_server_service_state }}" + tags: [pelican_server, services] diff --git a/roles/pelican_server/templates/wings.service.j2 b/roles/pelican_server/templates/wings.service.j2 new file mode 100644 index 0000000..cb8536c --- /dev/null +++ b/roles/pelican_server/templates/wings.service.j2 @@ -0,0 +1,20 @@ +[Unit] +Description=Wings Daemon +After=docker.service +Requires=docker.service +PartOf=docker.service + +[Service] +User=root +WorkingDirectory={{ pelican_server_config_dir }} +LimitNOFILE=4096 +RuntimeDirectory=wings +PIDFile={{ pelican_server_runtime_dir }}/daemon.pid +ExecStart={{ pelican_server_binary_path }}{% for arg in pelican_server_extra_args %} {{ arg }}{% endfor %} +Restart=on-failure +StartLimitInterval=180 +StartLimitBurst=30 +RestartSec=5s + +[Install] +WantedBy=multi-user.target diff --git a/roles/php_fpm/README.md b/roles/php_fpm/README.md new file mode 100644 index 0000000..0612e84 --- /dev/null +++ b/roles/php_fpm/README.md @@ -0,0 +1,4 @@ +# php_fpm + +Deploys PHP-FPM with Docker Compose. + diff --git a/roles/php_fpm/defaults/main.yml b/roles/php_fpm/defaults/main.yml new file mode 100644 index 0000000..bc54ccf --- /dev/null +++ b/roles/php_fpm/defaults/main.yml @@ -0,0 +1,14 @@ +--- +php_fpm_name: php-fpm +php_fpm_dir: /srv/docker/php-fpm +php_fpm_image: php:8.4-fpm-alpine +php_fpm_routing_mode: host +php_fpm_env: {} +php_fpm_directories: + - "{{ php_fpm_dir }}" + - /srv/data/php +php_fpm_volumes: + - /srv/data/php:/var/www/html +php_fpm_ports: + - { host: 9000, container: 9000, protocol: tcp } + diff --git a/roles/php_fpm/files/.gitkeep b/roles/php_fpm/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/php_fpm/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/php_fpm/handlers/.gitkeep b/roles/php_fpm/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/php_fpm/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/php_fpm/tasks/main.yml b/roles/php_fpm/tasks/main.yml new file mode 100644 index 0000000..1903b60 --- /dev/null +++ b/roles/php_fpm/tasks/main.yml @@ -0,0 +1,16 @@ +--- +- name: Deploy php-fpm + ansible.builtin.include_role: + name: docker_app + vars: + docker_app_env_template_src: "{{ playbook_dir }}/../roles/php_fpm/templates/env.j2" + docker_app_compose_template_src: "{{ playbook_dir }}/../roles/php_fpm/templates/compose.yaml.j2" + docker_app_name: "{{ php_fpm_name }}" + docker_app_dir: "{{ php_fpm_dir }}" + docker_app_image: "{{ php_fpm_image }}" + docker_app_env: "{{ php_fpm_env }}" + docker_app_directories: "{{ php_fpm_directories }}" + docker_app_volumes: "{{ php_fpm_volumes }}" + docker_app_ports: "{{ php_fpm_ports }}" + docker_app_routing_mode: "{{ php_fpm_routing_mode }}" + diff --git a/roles/php_fpm/templates/.gitkeep b/roles/php_fpm/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/php_fpm/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/php_fpm/templates/compose.yaml.j2 b/roles/php_fpm/templates/compose.yaml.j2 new file mode 100644 index 0000000..6de1603 --- /dev/null +++ b/roles/php_fpm/templates/compose.yaml.j2 @@ -0,0 +1,90 @@ +services: + {{ docker_app_name }}: + image: {{ docker_app_image }} + restart: {{ docker_app_restart }} +{% if docker_app_command | length > 0 %} + command: {{ docker_app_command | to_json }} +{% endif %} +{% if docker_app_user | length > 0 %} + user: "{{ docker_app_user }}" +{% endif %} +{% if docker_app_network_mode | length > 0 %} + network_mode: {{ docker_app_network_mode }} +{% endif %} +{% if docker_app_privileged | bool %} + privileged: true +{% endif %} +{% if docker_app_env | length > 0 %} + env_file: .env +{% endif %} +{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} + ports: +{% for port in docker_app_ports %} + - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_service_port %} + # internal service port: {{ docker_app_service_port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }} + - traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }} + - traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }} +{% if docker_app_traefik_middlewares | length > 0 %} + - traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }} +{% endif %} + - traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }} +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% elif docker_app_labels | length > 0 %} + labels: +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% endif %} +{% if docker_app_volumes | length > 0 %} + volumes: +{% for volume in docker_app_volumes %} + - {{ volume }} +{% endfor %} +{% endif %} +{% if docker_app_devices | length > 0 %} + devices: +{% for device in docker_app_devices %} + - {{ device }} +{% endfor %} +{% endif %} +{% if docker_app_cap_add | length > 0 %} + cap_add: +{% for capability in docker_app_cap_add %} + - {{ capability }} +{% endfor %} +{% endif %} +{% if docker_app_group_add | length > 0 %} + group_add: +{% for group in docker_app_group_add %} + - "{{ group }}" +{% endfor %} +{% endif %} +{% if docker_app_extra_hosts | length > 0 %} + extra_hosts: +{% for host in docker_app_extra_hosts %} + - {{ host }} +{% endfor %} +{% endif %} +{% if docker_app_depends_on | length > 0 %} + depends_on: +{% for dependency in docker_app_depends_on %} + - {{ dependency }} +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} + networks: + - {{ docker_app_traefik_network }} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} +networks: + {{ docker_app_traefik_network }}: + external: true +{% endif %} diff --git a/roles/php_fpm/templates/env.j2 b/roles/php_fpm/templates/env.j2 new file mode 100644 index 0000000..c4f1a9f --- /dev/null +++ b/roles/php_fpm/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in docker_app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/pinchflat/README.md b/roles/pinchflat/README.md new file mode 100644 index 0000000..12ea074 --- /dev/null +++ b/roles/pinchflat/README.md @@ -0,0 +1,4 @@ +# pinchflat + +Deploys Pinchflat with Docker Compose. + diff --git a/roles/pinchflat/defaults/main.yml b/roles/pinchflat/defaults/main.yml new file mode 100644 index 0000000..e0bf4cf --- /dev/null +++ b/roles/pinchflat/defaults/main.yml @@ -0,0 +1,19 @@ +--- +pinchflat_name: pinchflat +pinchflat_dir: /srv/docker/pinchflat +pinchflat_image: ghcr.io/kieraneglin/pinchflat:latest +pinchflat_domain: pinchflat.example.invalid +pinchflat_service_port: 8945 +pinchflat_routing_mode: "{{ nbops_service_routing_mode }}" +pinchflat_env: + TZ: "{{ nbops_timezone }}" +pinchflat_directories: + - "{{ pinchflat_dir }}" + - /srv/config/pinchflat + - /srv/media/pinchflat +pinchflat_volumes: + - /srv/config/pinchflat:/config + - /srv/media/pinchflat:/downloads +pinchflat_ports: + - { host: 8945, container: 8945, protocol: tcp } + diff --git a/roles/pinchflat/files/.gitkeep b/roles/pinchflat/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/pinchflat/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/pinchflat/handlers/.gitkeep b/roles/pinchflat/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/pinchflat/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/pinchflat/tasks/main.yml b/roles/pinchflat/tasks/main.yml new file mode 100644 index 0000000..c199d67 --- /dev/null +++ b/roles/pinchflat/tasks/main.yml @@ -0,0 +1,18 @@ +--- +- name: Deploy Pinchflat + ansible.builtin.include_role: + name: docker_app + vars: + docker_app_env_template_src: "{{ playbook_dir }}/../roles/pinchflat/templates/env.j2" + docker_app_compose_template_src: "{{ playbook_dir }}/../roles/pinchflat/templates/compose.yaml.j2" + docker_app_name: "{{ pinchflat_name }}" + docker_app_dir: "{{ pinchflat_dir }}" + docker_app_image: "{{ pinchflat_image }}" + docker_app_env: "{{ pinchflat_env }}" + docker_app_directories: "{{ pinchflat_directories }}" + docker_app_volumes: "{{ pinchflat_volumes }}" + docker_app_ports: "{{ pinchflat_ports }}" + docker_app_routing_mode: "{{ pinchflat_routing_mode }}" + docker_app_domain: "{{ pinchflat_domain }}" + docker_app_service_port: "{{ pinchflat_service_port }}" + diff --git a/roles/pinchflat/templates/.gitkeep b/roles/pinchflat/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/pinchflat/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/pinchflat/templates/compose.yaml.j2 b/roles/pinchflat/templates/compose.yaml.j2 new file mode 100644 index 0000000..6de1603 --- /dev/null +++ b/roles/pinchflat/templates/compose.yaml.j2 @@ -0,0 +1,90 @@ +services: + {{ docker_app_name }}: + image: {{ docker_app_image }} + restart: {{ docker_app_restart }} +{% if docker_app_command | length > 0 %} + command: {{ docker_app_command | to_json }} +{% endif %} +{% if docker_app_user | length > 0 %} + user: "{{ docker_app_user }}" +{% endif %} +{% if docker_app_network_mode | length > 0 %} + network_mode: {{ docker_app_network_mode }} +{% endif %} +{% if docker_app_privileged | bool %} + privileged: true +{% endif %} +{% if docker_app_env | length > 0 %} + env_file: .env +{% endif %} +{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} + ports: +{% for port in docker_app_ports %} + - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_service_port %} + # internal service port: {{ docker_app_service_port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }} + - traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }} + - traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }} +{% if docker_app_traefik_middlewares | length > 0 %} + - traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }} +{% endif %} + - traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }} +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% elif docker_app_labels | length > 0 %} + labels: +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% endif %} +{% if docker_app_volumes | length > 0 %} + volumes: +{% for volume in docker_app_volumes %} + - {{ volume }} +{% endfor %} +{% endif %} +{% if docker_app_devices | length > 0 %} + devices: +{% for device in docker_app_devices %} + - {{ device }} +{% endfor %} +{% endif %} +{% if docker_app_cap_add | length > 0 %} + cap_add: +{% for capability in docker_app_cap_add %} + - {{ capability }} +{% endfor %} +{% endif %} +{% if docker_app_group_add | length > 0 %} + group_add: +{% for group in docker_app_group_add %} + - "{{ group }}" +{% endfor %} +{% endif %} +{% if docker_app_extra_hosts | length > 0 %} + extra_hosts: +{% for host in docker_app_extra_hosts %} + - {{ host }} +{% endfor %} +{% endif %} +{% if docker_app_depends_on | length > 0 %} + depends_on: +{% for dependency in docker_app_depends_on %} + - {{ dependency }} +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} + networks: + - {{ docker_app_traefik_network }} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} +networks: + {{ docker_app_traefik_network }}: + external: true +{% endif %} diff --git a/roles/pinchflat/templates/env.j2 b/roles/pinchflat/templates/env.j2 new file mode 100644 index 0000000..c4f1a9f --- /dev/null +++ b/roles/pinchflat/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in docker_app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/postgres/README.md b/roles/postgres/README.md new file mode 100644 index 0000000..86d2e06 --- /dev/null +++ b/roles/postgres/README.md @@ -0,0 +1,4 @@ +# postgres + +Deploys PostgreSQL with Docker Compose. + diff --git a/roles/postgres/defaults/main.yml b/roles/postgres/defaults/main.yml new file mode 100644 index 0000000..e9ce12a --- /dev/null +++ b/roles/postgres/defaults/main.yml @@ -0,0 +1,17 @@ +--- +postgres_name: postgres +postgres_dir: /srv/docker/postgres +postgres_image: postgres:17-alpine +postgres_routing_mode: host +postgres_env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: "{{ vault_postgres_password | default('CHANGE_ME_IN_VAULT') }}" + POSTGRES_DB: postgres +postgres_directories: + - "{{ postgres_dir }}" + - /srv/data/postgres +postgres_volumes: + - /srv/data/postgres:/var/lib/postgresql/data +postgres_ports: + - { host: 5432, container: 5432, protocol: tcp } + diff --git a/roles/postgres/files/.gitkeep b/roles/postgres/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/postgres/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/postgres/handlers/.gitkeep b/roles/postgres/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/postgres/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/postgres/tasks/main.yml b/roles/postgres/tasks/main.yml new file mode 100644 index 0000000..542421f --- /dev/null +++ b/roles/postgres/tasks/main.yml @@ -0,0 +1,16 @@ +--- +- name: Deploy PostgreSQL + ansible.builtin.include_role: + name: docker_app + vars: + docker_app_env_template_src: "{{ playbook_dir }}/../roles/postgres/templates/env.j2" + docker_app_compose_template_src: "{{ playbook_dir }}/../roles/postgres/templates/compose.yaml.j2" + docker_app_name: "{{ postgres_name }}" + docker_app_dir: "{{ postgres_dir }}" + docker_app_image: "{{ postgres_image }}" + docker_app_env: "{{ postgres_env }}" + docker_app_directories: "{{ postgres_directories }}" + docker_app_volumes: "{{ postgres_volumes }}" + docker_app_ports: "{{ postgres_ports }}" + docker_app_routing_mode: "{{ postgres_routing_mode }}" + diff --git a/roles/postgres/templates/.gitkeep b/roles/postgres/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/postgres/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/postgres/templates/compose.yaml.j2 b/roles/postgres/templates/compose.yaml.j2 new file mode 100644 index 0000000..6de1603 --- /dev/null +++ b/roles/postgres/templates/compose.yaml.j2 @@ -0,0 +1,90 @@ +services: + {{ docker_app_name }}: + image: {{ docker_app_image }} + restart: {{ docker_app_restart }} +{% if docker_app_command | length > 0 %} + command: {{ docker_app_command | to_json }} +{% endif %} +{% if docker_app_user | length > 0 %} + user: "{{ docker_app_user }}" +{% endif %} +{% if docker_app_network_mode | length > 0 %} + network_mode: {{ docker_app_network_mode }} +{% endif %} +{% if docker_app_privileged | bool %} + privileged: true +{% endif %} +{% if docker_app_env | length > 0 %} + env_file: .env +{% endif %} +{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} + ports: +{% for port in docker_app_ports %} + - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_service_port %} + # internal service port: {{ docker_app_service_port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }} + - traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }} + - traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }} +{% if docker_app_traefik_middlewares | length > 0 %} + - traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }} +{% endif %} + - traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }} +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% elif docker_app_labels | length > 0 %} + labels: +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% endif %} +{% if docker_app_volumes | length > 0 %} + volumes: +{% for volume in docker_app_volumes %} + - {{ volume }} +{% endfor %} +{% endif %} +{% if docker_app_devices | length > 0 %} + devices: +{% for device in docker_app_devices %} + - {{ device }} +{% endfor %} +{% endif %} +{% if docker_app_cap_add | length > 0 %} + cap_add: +{% for capability in docker_app_cap_add %} + - {{ capability }} +{% endfor %} +{% endif %} +{% if docker_app_group_add | length > 0 %} + group_add: +{% for group in docker_app_group_add %} + - "{{ group }}" +{% endfor %} +{% endif %} +{% if docker_app_extra_hosts | length > 0 %} + extra_hosts: +{% for host in docker_app_extra_hosts %} + - {{ host }} +{% endfor %} +{% endif %} +{% if docker_app_depends_on | length > 0 %} + depends_on: +{% for dependency in docker_app_depends_on %} + - {{ dependency }} +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} + networks: + - {{ docker_app_traefik_network }} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} +networks: + {{ docker_app_traefik_network }}: + external: true +{% endif %} diff --git a/roles/postgres/templates/env.j2 b/roles/postgres/templates/env.j2 new file mode 100644 index 0000000..c4f1a9f --- /dev/null +++ b/roles/postgres/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in docker_app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/prometheus/README.md b/roles/prometheus/README.md new file mode 100644 index 0000000..94fd13e --- /dev/null +++ b/roles/prometheus/README.md @@ -0,0 +1,5 @@ +# prometheus + +Deploys Prometheus with Docker Compose. This can be used directly or composed +into the broader monitoring role. + diff --git a/roles/prometheus/defaults/main.yml b/roles/prometheus/defaults/main.yml new file mode 100644 index 0000000..65c7f1a --- /dev/null +++ b/roles/prometheus/defaults/main.yml @@ -0,0 +1,19 @@ +--- +prometheus_name: prometheus +prometheus_dir: /srv/docker/prometheus +prometheus_image: prom/prometheus:latest +prometheus_domain: prometheus.example.invalid +prometheus_service_port: 9090 +prometheus_routing_mode: host +prometheus_command: "--config.file=/etc/prometheus/prometheus.yml --storage.tsdb.path=/prometheus" +prometheus_env: {} +prometheus_directories: + - "{{ prometheus_dir }}" + - /srv/config/prometheus + - /srv/data/prometheus +prometheus_volumes: + - /srv/config/prometheus:/etc/prometheus + - /srv/data/prometheus:/prometheus +prometheus_ports: + - { host: 9090, container: 9090, protocol: tcp } + diff --git a/roles/prometheus/files/.gitkeep b/roles/prometheus/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/prometheus/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/prometheus/handlers/.gitkeep b/roles/prometheus/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/prometheus/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/prometheus/tasks/main.yml b/roles/prometheus/tasks/main.yml new file mode 100644 index 0000000..58bbbab --- /dev/null +++ b/roles/prometheus/tasks/main.yml @@ -0,0 +1,19 @@ +--- +- name: Deploy Prometheus + ansible.builtin.include_role: + name: docker_app + vars: + docker_app_env_template_src: "{{ playbook_dir }}/../roles/prometheus/templates/env.j2" + docker_app_compose_template_src: "{{ playbook_dir }}/../roles/prometheus/templates/compose.yaml.j2" + docker_app_name: "{{ prometheus_name }}" + docker_app_dir: "{{ prometheus_dir }}" + docker_app_image: "{{ prometheus_image }}" + docker_app_command: "{{ prometheus_command }}" + docker_app_env: "{{ prometheus_env }}" + docker_app_directories: "{{ prometheus_directories }}" + docker_app_volumes: "{{ prometheus_volumes }}" + docker_app_ports: "{{ prometheus_ports }}" + docker_app_routing_mode: "{{ prometheus_routing_mode }}" + docker_app_domain: "{{ prometheus_domain }}" + docker_app_service_port: "{{ prometheus_service_port }}" + diff --git a/roles/prometheus/templates/.gitkeep b/roles/prometheus/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/prometheus/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/prometheus/templates/compose.yaml.j2 b/roles/prometheus/templates/compose.yaml.j2 new file mode 100644 index 0000000..6de1603 --- /dev/null +++ b/roles/prometheus/templates/compose.yaml.j2 @@ -0,0 +1,90 @@ +services: + {{ docker_app_name }}: + image: {{ docker_app_image }} + restart: {{ docker_app_restart }} +{% if docker_app_command | length > 0 %} + command: {{ docker_app_command | to_json }} +{% endif %} +{% if docker_app_user | length > 0 %} + user: "{{ docker_app_user }}" +{% endif %} +{% if docker_app_network_mode | length > 0 %} + network_mode: {{ docker_app_network_mode }} +{% endif %} +{% if docker_app_privileged | bool %} + privileged: true +{% endif %} +{% if docker_app_env | length > 0 %} + env_file: .env +{% endif %} +{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} + ports: +{% for port in docker_app_ports %} + - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_service_port %} + # internal service port: {{ docker_app_service_port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }} + - traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }} + - traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }} +{% if docker_app_traefik_middlewares | length > 0 %} + - traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }} +{% endif %} + - traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }} +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% elif docker_app_labels | length > 0 %} + labels: +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% endif %} +{% if docker_app_volumes | length > 0 %} + volumes: +{% for volume in docker_app_volumes %} + - {{ volume }} +{% endfor %} +{% endif %} +{% if docker_app_devices | length > 0 %} + devices: +{% for device in docker_app_devices %} + - {{ device }} +{% endfor %} +{% endif %} +{% if docker_app_cap_add | length > 0 %} + cap_add: +{% for capability in docker_app_cap_add %} + - {{ capability }} +{% endfor %} +{% endif %} +{% if docker_app_group_add | length > 0 %} + group_add: +{% for group in docker_app_group_add %} + - "{{ group }}" +{% endfor %} +{% endif %} +{% if docker_app_extra_hosts | length > 0 %} + extra_hosts: +{% for host in docker_app_extra_hosts %} + - {{ host }} +{% endfor %} +{% endif %} +{% if docker_app_depends_on | length > 0 %} + depends_on: +{% for dependency in docker_app_depends_on %} + - {{ dependency }} +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} + networks: + - {{ docker_app_traefik_network }} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} +networks: + {{ docker_app_traefik_network }}: + external: true +{% endif %} diff --git a/roles/prometheus/templates/env.j2 b/roles/prometheus/templates/env.j2 new file mode 100644 index 0000000..c4f1a9f --- /dev/null +++ b/roles/prometheus/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in docker_app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/pterodactyl/README.md b/roles/pterodactyl/README.md new file mode 100644 index 0000000..f1ee83c --- /dev/null +++ b/roles/pterodactyl/README.md @@ -0,0 +1,7 @@ +# pterodactyl + +Deploys the Pterodactyl panel with MariaDB and Redis. Wings/node deployment can +be added as a separate role when game nodes are introduced. + +Tags: `pterodactyl`, `config`, `compose`, `firewall`. + diff --git a/roles/pterodactyl/defaults/main.yml b/roles/pterodactyl/defaults/main.yml new file mode 100644 index 0000000..6e7e38b --- /dev/null +++ b/roles/pterodactyl/defaults/main.yml @@ -0,0 +1,23 @@ +--- +pterodactyl_dir: /srv/docker/pterodactyl +pterodactyl_panel_image: ghcr.io/pterodactyl/panel:latest +pterodactyl_db_image: mariadb:10.11 +pterodactyl_cache_image: redis:7-alpine +pterodactyl_http_port: 8081 +pterodactyl_domain: panel.example.invalid +pterodactyl_routing_mode: "{{ nbops_service_routing_mode }}" +pterodactyl_traefik_network: "{{ nbops_proxy_network }}" +pterodactyl_traefik_entrypoint: "{{ nbops_traefik_entrypoint }}" +pterodactyl_traefik_tls: "{{ nbops_traefik_tls }}" +pterodactyl_app_key: "{{ vault_pterodactyl_app_key | default('CHANGE_ME_IN_VAULT') }}" +pterodactyl_db_password: "{{ vault_pterodactyl_db_password | default('CHANGE_ME_IN_VAULT') }}" +pterodactyl_env: + APP_URL: "https://{{ pterodactyl_domain }}" + APP_KEY: "{{ pterodactyl_app_key }}" + DB_HOST: database + DB_DATABASE: panel + DB_USERNAME: pterodactyl + DB_PASSWORD: "{{ pterodactyl_db_password }}" + REDIS_HOST: cache +pterodactyl_tcp_ports: + - "{{ pterodactyl_http_port }}" diff --git a/roles/pterodactyl/files/.gitkeep b/roles/pterodactyl/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/pterodactyl/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/pterodactyl/handlers/main.yml b/roles/pterodactyl/handlers/main.yml new file mode 100644 index 0000000..4f50c8a --- /dev/null +++ b/roles/pterodactyl/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: restart pterodactyl + community.docker.docker_compose_v2: + project_src: "{{ pterodactyl_dir }}" + state: restarted + diff --git a/roles/pterodactyl/tasks/main.yml b/roles/pterodactyl/tasks/main.yml new file mode 100644 index 0000000..6992c9b --- /dev/null +++ b/roles/pterodactyl/tasks/main.yml @@ -0,0 +1,51 @@ +--- +- name: Create Pterodactyl directory + ansible.builtin.file: + path: "{{ pterodactyl_dir }}" + state: directory + owner: root + group: root + mode: "0755" + tags: [pterodactyl] + +- name: Render Pterodactyl environment + ansible.builtin.template: + src: env.j2 + dest: "{{ pterodactyl_dir }}/.env" + owner: root + group: root + mode: "0600" + no_log: true + tags: [pterodactyl, config] + +- name: Ensure Pterodactyl proxy network exists + community.docker.docker_network: + name: "{{ pterodactyl_traefik_network }}" + state: present + when: pterodactyl_routing_mode == "traefik" + tags: [pterodactyl, docker] + +- name: Render Pterodactyl Compose file + ansible.builtin.template: + src: compose.yaml.j2 + dest: "{{ pterodactyl_dir }}/compose.yaml" + owner: root + group: root + mode: "0644" + notify: restart pterodactyl + tags: [pterodactyl, compose] + +- name: Allow Pterodactyl ports + community.general.ufw: + rule: allow + port: "{{ item }}" + proto: tcp + loop: "{{ pterodactyl_tcp_ports }}" + when: pterodactyl_routing_mode == "host" + tags: [pterodactyl, firewall] + +- name: Start Pterodactyl + community.docker.docker_compose_v2: + project_src: "{{ pterodactyl_dir }}" + state: present + tags: [pterodactyl, compose] diff --git a/roles/pterodactyl/templates/compose.yaml.j2 b/roles/pterodactyl/templates/compose.yaml.j2 new file mode 100644 index 0000000..986a754 --- /dev/null +++ b/roles/pterodactyl/templates/compose.yaml.j2 @@ -0,0 +1,47 @@ +services: + panel: + image: {{ pterodactyl_panel_image }} + restart: unless-stopped + env_file: .env +{% if pterodactyl_routing_mode == "host" %} + ports: + - "{{ pterodactyl_http_port }}:80" +{% endif %} +{% if pterodactyl_routing_mode == "traefik" %} + # internal HTTP service port: 80; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.pterodactyl.rule=Host(`{{ pterodactyl_domain }}`) + - traefik.http.routers.pterodactyl.entrypoints={{ pterodactyl_traefik_entrypoint }} + - traefik.http.routers.pterodactyl.tls={{ pterodactyl_traefik_tls | bool | ternary('true', 'false') }} + - traefik.http.services.pterodactyl.loadbalancer.server.port=80 + networks: + - default + - {{ pterodactyl_traefik_network }} +{% endif %} + volumes: + - ./var:/app/var + - ./nginx:/etc/nginx/http.d + - ./certs:/etc/letsencrypt + - ./logs:/app/storage/logs + depends_on: + - database + - cache + database: + image: {{ pterodactyl_db_image }} + restart: unless-stopped + environment: + MYSQL_DATABASE: panel + MYSQL_USER: pterodactyl + MYSQL_PASSWORD: ${MYSQL_PASSWORD} + MYSQL_ROOT_PASSWORD: ${MYSQL_PASSWORD} + volumes: + - ./database:/var/lib/mysql + cache: + image: {{ pterodactyl_cache_image }} + restart: unless-stopped +{% if pterodactyl_routing_mode == "traefik" %} +networks: + {{ pterodactyl_traefik_network }}: + external: true +{% endif %} diff --git a/roles/pterodactyl/templates/env.j2 b/roles/pterodactyl/templates/env.j2 new file mode 100644 index 0000000..3b436e8 --- /dev/null +++ b/roles/pterodactyl/templates/env.j2 @@ -0,0 +1,5 @@ +MYSQL_PASSWORD={{ pterodactyl_db_password }} +{% for key, value in pterodactyl_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/qbittorrent/README.md b/roles/qbittorrent/README.md new file mode 100644 index 0000000..20392e5 --- /dev/null +++ b/roles/qbittorrent/README.md @@ -0,0 +1,6 @@ +# qbittorrent + +Deploys qBittorrent with Docker Compose. + +Tags: `qbittorrent`. + diff --git a/roles/qbittorrent/defaults/main.yml b/roles/qbittorrent/defaults/main.yml new file mode 100644 index 0000000..c29970d --- /dev/null +++ b/roles/qbittorrent/defaults/main.yml @@ -0,0 +1,24 @@ +--- +qbittorrent_name: qbittorrent +qbittorrent_dir: /srv/docker/qbittorrent +qbittorrent_image: lscr.io/linuxserver/qbittorrent:latest +qbittorrent_domain: qbittorrent.example.invalid +qbittorrent_service_port: 8080 +qbittorrent_routing_mode: "{{ nbops_service_routing_mode }}" +qbittorrent_env: + PUID: "1000" + PGID: "1000" + TZ: "{{ nbops_timezone }}" + WEBUI_PORT: "8080" +qbittorrent_directories: + - "{{ qbittorrent_dir }}" + - /srv/config/qbittorrent + - /srv/data/downloads +qbittorrent_volumes: + - /srv/config/qbittorrent:/config + - /srv/data/downloads:/downloads +qbittorrent_ports: + - { host: 8080, container: 8080, protocol: tcp } + - { host: 6881, container: 6881, protocol: tcp } + - { host: 6881, container: 6881, protocol: udp } + diff --git a/roles/qbittorrent/files/.gitkeep b/roles/qbittorrent/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/qbittorrent/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/qbittorrent/handlers/.gitkeep b/roles/qbittorrent/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/qbittorrent/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/qbittorrent/tasks/main.yml b/roles/qbittorrent/tasks/main.yml new file mode 100644 index 0000000..fcddc72 --- /dev/null +++ b/roles/qbittorrent/tasks/main.yml @@ -0,0 +1,18 @@ +--- +- name: Deploy qBittorrent + ansible.builtin.include_role: + name: docker_app + vars: + docker_app_env_template_src: "{{ playbook_dir }}/../roles/qbittorrent/templates/env.j2" + docker_app_compose_template_src: "{{ playbook_dir }}/../roles/qbittorrent/templates/compose.yaml.j2" + docker_app_name: "{{ qbittorrent_name }}" + docker_app_dir: "{{ qbittorrent_dir }}" + docker_app_image: "{{ qbittorrent_image }}" + docker_app_env: "{{ qbittorrent_env }}" + docker_app_directories: "{{ qbittorrent_directories }}" + docker_app_volumes: "{{ qbittorrent_volumes }}" + docker_app_ports: "{{ qbittorrent_ports }}" + docker_app_routing_mode: "{{ qbittorrent_routing_mode }}" + docker_app_domain: "{{ qbittorrent_domain }}" + docker_app_service_port: "{{ qbittorrent_service_port }}" + diff --git a/roles/qbittorrent/templates/.gitkeep b/roles/qbittorrent/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/qbittorrent/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/qbittorrent/templates/compose.yaml.j2 b/roles/qbittorrent/templates/compose.yaml.j2 new file mode 100644 index 0000000..6de1603 --- /dev/null +++ b/roles/qbittorrent/templates/compose.yaml.j2 @@ -0,0 +1,90 @@ +services: + {{ docker_app_name }}: + image: {{ docker_app_image }} + restart: {{ docker_app_restart }} +{% if docker_app_command | length > 0 %} + command: {{ docker_app_command | to_json }} +{% endif %} +{% if docker_app_user | length > 0 %} + user: "{{ docker_app_user }}" +{% endif %} +{% if docker_app_network_mode | length > 0 %} + network_mode: {{ docker_app_network_mode }} +{% endif %} +{% if docker_app_privileged | bool %} + privileged: true +{% endif %} +{% if docker_app_env | length > 0 %} + env_file: .env +{% endif %} +{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} + ports: +{% for port in docker_app_ports %} + - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_service_port %} + # internal service port: {{ docker_app_service_port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }} + - traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }} + - traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }} +{% if docker_app_traefik_middlewares | length > 0 %} + - traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }} +{% endif %} + - traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }} +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% elif docker_app_labels | length > 0 %} + labels: +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% endif %} +{% if docker_app_volumes | length > 0 %} + volumes: +{% for volume in docker_app_volumes %} + - {{ volume }} +{% endfor %} +{% endif %} +{% if docker_app_devices | length > 0 %} + devices: +{% for device in docker_app_devices %} + - {{ device }} +{% endfor %} +{% endif %} +{% if docker_app_cap_add | length > 0 %} + cap_add: +{% for capability in docker_app_cap_add %} + - {{ capability }} +{% endfor %} +{% endif %} +{% if docker_app_group_add | length > 0 %} + group_add: +{% for group in docker_app_group_add %} + - "{{ group }}" +{% endfor %} +{% endif %} +{% if docker_app_extra_hosts | length > 0 %} + extra_hosts: +{% for host in docker_app_extra_hosts %} + - {{ host }} +{% endfor %} +{% endif %} +{% if docker_app_depends_on | length > 0 %} + depends_on: +{% for dependency in docker_app_depends_on %} + - {{ dependency }} +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} + networks: + - {{ docker_app_traefik_network }} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} +networks: + {{ docker_app_traefik_network }}: + external: true +{% endif %} diff --git a/roles/qbittorrent/templates/env.j2 b/roles/qbittorrent/templates/env.j2 new file mode 100644 index 0000000..c4f1a9f --- /dev/null +++ b/roles/qbittorrent/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in docker_app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/requarks/README.md b/roles/requarks/README.md new file mode 100644 index 0000000..5cac0ca --- /dev/null +++ b/roles/requarks/README.md @@ -0,0 +1,4 @@ +# requarks + +Deploys Requarks Wiki.js with Docker Compose. + diff --git a/roles/requarks/defaults/main.yml b/roles/requarks/defaults/main.yml new file mode 100644 index 0000000..834ca43 --- /dev/null +++ b/roles/requarks/defaults/main.yml @@ -0,0 +1,20 @@ +--- +requarks_name: requarks +requarks_dir: /srv/docker/requarks +requarks_image: ghcr.io/requarks/wiki:2 +requarks_domain: wiki.example.invalid +requarks_service_port: 3000 +requarks_routing_mode: "{{ nbops_service_routing_mode }}" +requarks_env: + DB_TYPE: postgres + DB_HOST: postgres + DB_PORT: "5432" + DB_USER: wiki + DB_PASS: "{{ vault_requarks_db_password | default('CHANGE_ME_IN_VAULT') }}" + DB_NAME: wiki +requarks_directories: + - "{{ requarks_dir }}" +requarks_volumes: [] +requarks_ports: + - { host: 3001, container: 3000, protocol: tcp } + diff --git a/roles/requarks/files/.gitkeep b/roles/requarks/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/requarks/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/requarks/handlers/.gitkeep b/roles/requarks/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/requarks/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/requarks/tasks/main.yml b/roles/requarks/tasks/main.yml new file mode 100644 index 0000000..baedde3 --- /dev/null +++ b/roles/requarks/tasks/main.yml @@ -0,0 +1,18 @@ +--- +- name: Deploy Requarks Wiki.js + ansible.builtin.include_role: + name: docker_app + vars: + docker_app_env_template_src: "{{ playbook_dir }}/../roles/requarks/templates/env.j2" + docker_app_compose_template_src: "{{ playbook_dir }}/../roles/requarks/templates/compose.yaml.j2" + docker_app_name: "{{ requarks_name }}" + docker_app_dir: "{{ requarks_dir }}" + docker_app_image: "{{ requarks_image }}" + docker_app_env: "{{ requarks_env }}" + docker_app_directories: "{{ requarks_directories }}" + docker_app_volumes: "{{ requarks_volumes }}" + docker_app_ports: "{{ requarks_ports }}" + docker_app_routing_mode: "{{ requarks_routing_mode }}" + docker_app_domain: "{{ requarks_domain }}" + docker_app_service_port: "{{ requarks_service_port }}" + diff --git a/roles/requarks/templates/.gitkeep b/roles/requarks/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/requarks/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/requarks/templates/compose.yaml.j2 b/roles/requarks/templates/compose.yaml.j2 new file mode 100644 index 0000000..6de1603 --- /dev/null +++ b/roles/requarks/templates/compose.yaml.j2 @@ -0,0 +1,90 @@ +services: + {{ docker_app_name }}: + image: {{ docker_app_image }} + restart: {{ docker_app_restart }} +{% if docker_app_command | length > 0 %} + command: {{ docker_app_command | to_json }} +{% endif %} +{% if docker_app_user | length > 0 %} + user: "{{ docker_app_user }}" +{% endif %} +{% if docker_app_network_mode | length > 0 %} + network_mode: {{ docker_app_network_mode }} +{% endif %} +{% if docker_app_privileged | bool %} + privileged: true +{% endif %} +{% if docker_app_env | length > 0 %} + env_file: .env +{% endif %} +{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} + ports: +{% for port in docker_app_ports %} + - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_service_port %} + # internal service port: {{ docker_app_service_port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }} + - traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }} + - traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }} +{% if docker_app_traefik_middlewares | length > 0 %} + - traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }} +{% endif %} + - traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }} +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% elif docker_app_labels | length > 0 %} + labels: +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% endif %} +{% if docker_app_volumes | length > 0 %} + volumes: +{% for volume in docker_app_volumes %} + - {{ volume }} +{% endfor %} +{% endif %} +{% if docker_app_devices | length > 0 %} + devices: +{% for device in docker_app_devices %} + - {{ device }} +{% endfor %} +{% endif %} +{% if docker_app_cap_add | length > 0 %} + cap_add: +{% for capability in docker_app_cap_add %} + - {{ capability }} +{% endfor %} +{% endif %} +{% if docker_app_group_add | length > 0 %} + group_add: +{% for group in docker_app_group_add %} + - "{{ group }}" +{% endfor %} +{% endif %} +{% if docker_app_extra_hosts | length > 0 %} + extra_hosts: +{% for host in docker_app_extra_hosts %} + - {{ host }} +{% endfor %} +{% endif %} +{% if docker_app_depends_on | length > 0 %} + depends_on: +{% for dependency in docker_app_depends_on %} + - {{ dependency }} +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} + networks: + - {{ docker_app_traefik_network }} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} +networks: + {{ docker_app_traefik_network }}: + external: true +{% endif %} diff --git a/roles/requarks/templates/env.j2 b/roles/requarks/templates/env.j2 new file mode 100644 index 0000000..c4f1a9f --- /dev/null +++ b/roles/requarks/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in docker_app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/ssh/README.md b/roles/ssh/README.md new file mode 100644 index 0000000..721ba87 --- /dev/null +++ b/roles/ssh/README.md @@ -0,0 +1,6 @@ +# ssh + +Hardens OpenSSH for key-only administration and disables root/password login. + +Tags: `ssh`, `hardening`. + diff --git a/roles/ssh/defaults/main.yml b/roles/ssh/defaults/main.yml new file mode 100644 index 0000000..c959d4f --- /dev/null +++ b/roles/ssh/defaults/main.yml @@ -0,0 +1,9 @@ +--- +ssh_port: "{{ nbops_ssh_port }}" +ssh_permit_root_login: "no" +ssh_password_authentication: "no" +ssh_kbd_interactive_authentication: "no" +ssh_pubkey_authentication: "yes" +ssh_use_dns: "no" +ssh_x11_forwarding: "no" + diff --git a/roles/ssh/files/.gitkeep b/roles/ssh/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/ssh/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/ssh/handlers/main.yml b/roles/ssh/handlers/main.yml new file mode 100644 index 0000000..cba893b --- /dev/null +++ b/roles/ssh/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: restart ssh + ansible.builtin.service: + name: ssh + state: restarted + diff --git a/roles/ssh/tasks/main.yml b/roles/ssh/tasks/main.yml new file mode 100644 index 0000000..0c3d368 --- /dev/null +++ b/roles/ssh/tasks/main.yml @@ -0,0 +1,25 @@ +--- +- name: Install OpenSSH server + ansible.builtin.apt: + name: openssh-server + state: present + tags: [ssh] + +- name: Configure sshd hardening + ansible.builtin.template: + src: nbops-sshd.conf.j2 + dest: /etc/ssh/sshd_config.d/10-nbops.conf + owner: root + group: root + mode: "0644" + validate: /usr/sbin/sshd -t -f %s + notify: restart ssh + tags: [ssh, hardening] + +- name: Enable SSH + ansible.builtin.service: + name: ssh + enabled: true + state: started + tags: [ssh] + diff --git a/roles/ssh/templates/nbops-sshd.conf.j2 b/roles/ssh/templates/nbops-sshd.conf.j2 new file mode 100644 index 0000000..f18dc0b --- /dev/null +++ b/roles/ssh/templates/nbops-sshd.conf.j2 @@ -0,0 +1,8 @@ +Port {{ ssh_port }} +PermitRootLogin {{ ssh_permit_root_login }} +PasswordAuthentication {{ ssh_password_authentication }} +KbdInteractiveAuthentication {{ ssh_kbd_interactive_authentication }} +PubkeyAuthentication {{ ssh_pubkey_authentication }} +UseDNS {{ ssh_use_dns }} +X11Forwarding {{ ssh_x11_forwarding }} + diff --git a/roles/syncthing/README.md b/roles/syncthing/README.md new file mode 100644 index 0000000..17c1737 --- /dev/null +++ b/roles/syncthing/README.md @@ -0,0 +1,4 @@ +# syncthing + +Deploys Syncthing with Docker Compose. + diff --git a/roles/syncthing/defaults/main.yml b/roles/syncthing/defaults/main.yml new file mode 100644 index 0000000..b567f5e --- /dev/null +++ b/roles/syncthing/defaults/main.yml @@ -0,0 +1,24 @@ +--- +syncthing_name: syncthing +syncthing_dir: /srv/docker/syncthing +syncthing_image: lscr.io/linuxserver/syncthing:latest +syncthing_domain: syncthing.example.invalid +syncthing_service_port: 8384 +syncthing_routing_mode: "{{ nbops_service_routing_mode }}" +syncthing_env: + PUID: "1000" + PGID: "1000" + TZ: "{{ nbops_timezone }}" +syncthing_directories: + - "{{ syncthing_dir }}" + - /srv/config/syncthing + - /srv/data/syncthing +syncthing_volumes: + - /srv/config/syncthing:/config + - /srv/data/syncthing:/data +syncthing_ports: + - { host: 8384, container: 8384, protocol: tcp } + - { host: 22000, container: 22000, protocol: tcp } + - { host: 22000, container: 22000, protocol: udp } + - { host: 21027, container: 21027, protocol: udp } + diff --git a/roles/syncthing/files/.gitkeep b/roles/syncthing/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/syncthing/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/syncthing/handlers/.gitkeep b/roles/syncthing/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/syncthing/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/syncthing/tasks/main.yml b/roles/syncthing/tasks/main.yml new file mode 100644 index 0000000..3921a86 --- /dev/null +++ b/roles/syncthing/tasks/main.yml @@ -0,0 +1,18 @@ +--- +- name: Deploy Syncthing + ansible.builtin.include_role: + name: docker_app + vars: + docker_app_env_template_src: "{{ playbook_dir }}/../roles/syncthing/templates/env.j2" + docker_app_compose_template_src: "{{ playbook_dir }}/../roles/syncthing/templates/compose.yaml.j2" + docker_app_name: "{{ syncthing_name }}" + docker_app_dir: "{{ syncthing_dir }}" + docker_app_image: "{{ syncthing_image }}" + docker_app_env: "{{ syncthing_env }}" + docker_app_directories: "{{ syncthing_directories }}" + docker_app_volumes: "{{ syncthing_volumes }}" + docker_app_ports: "{{ syncthing_ports }}" + docker_app_routing_mode: "{{ syncthing_routing_mode }}" + docker_app_domain: "{{ syncthing_domain }}" + docker_app_service_port: "{{ syncthing_service_port }}" + diff --git a/roles/syncthing/templates/.gitkeep b/roles/syncthing/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/syncthing/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/syncthing/templates/compose.yaml.j2 b/roles/syncthing/templates/compose.yaml.j2 new file mode 100644 index 0000000..6de1603 --- /dev/null +++ b/roles/syncthing/templates/compose.yaml.j2 @@ -0,0 +1,90 @@ +services: + {{ docker_app_name }}: + image: {{ docker_app_image }} + restart: {{ docker_app_restart }} +{% if docker_app_command | length > 0 %} + command: {{ docker_app_command | to_json }} +{% endif %} +{% if docker_app_user | length > 0 %} + user: "{{ docker_app_user }}" +{% endif %} +{% if docker_app_network_mode | length > 0 %} + network_mode: {{ docker_app_network_mode }} +{% endif %} +{% if docker_app_privileged | bool %} + privileged: true +{% endif %} +{% if docker_app_env | length > 0 %} + env_file: .env +{% endif %} +{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} + ports: +{% for port in docker_app_ports %} + - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_service_port %} + # internal service port: {{ docker_app_service_port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }} + - traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }} + - traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }} +{% if docker_app_traefik_middlewares | length > 0 %} + - traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }} +{% endif %} + - traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }} +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% elif docker_app_labels | length > 0 %} + labels: +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% endif %} +{% if docker_app_volumes | length > 0 %} + volumes: +{% for volume in docker_app_volumes %} + - {{ volume }} +{% endfor %} +{% endif %} +{% if docker_app_devices | length > 0 %} + devices: +{% for device in docker_app_devices %} + - {{ device }} +{% endfor %} +{% endif %} +{% if docker_app_cap_add | length > 0 %} + cap_add: +{% for capability in docker_app_cap_add %} + - {{ capability }} +{% endfor %} +{% endif %} +{% if docker_app_group_add | length > 0 %} + group_add: +{% for group in docker_app_group_add %} + - "{{ group }}" +{% endfor %} +{% endif %} +{% if docker_app_extra_hosts | length > 0 %} + extra_hosts: +{% for host in docker_app_extra_hosts %} + - {{ host }} +{% endfor %} +{% endif %} +{% if docker_app_depends_on | length > 0 %} + depends_on: +{% for dependency in docker_app_depends_on %} + - {{ dependency }} +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} + networks: + - {{ docker_app_traefik_network }} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} +networks: + {{ docker_app_traefik_network }}: + external: true +{% endif %} diff --git a/roles/syncthing/templates/env.j2 b/roles/syncthing/templates/env.j2 new file mode 100644 index 0000000..c4f1a9f --- /dev/null +++ b/roles/syncthing/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in docker_app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/traefik/README.md b/roles/traefik/README.md new file mode 100644 index 0000000..7f99ee1 --- /dev/null +++ b/roles/traefik/README.md @@ -0,0 +1,7 @@ +# traefik + +Deploys Traefik with Docker Compose, a shared proxy network, static config, and +UFW rules for HTTP and HTTPS. + +Tags: `traefik`, `docker`, `config`, `compose`, `firewall`. + diff --git a/roles/traefik/defaults/main.yml b/roles/traefik/defaults/main.yml new file mode 100644 index 0000000..cd9d78e --- /dev/null +++ b/roles/traefik/defaults/main.yml @@ -0,0 +1,17 @@ +--- +traefik_dir: /srv/docker/traefik +traefik_image: traefik:v3.4 +traefik_network: "{{ nbops_proxy_network }}" +traefik_bind_address: "0.0.0.0" +traefik_http_port: 80 +traefik_https_port: 443 +traefik_dashboard_enabled: false +traefik_dashboard_port: 8080 +traefik_acme_email: admin@example.invalid +traefik_domain: example.invalid +traefik_env: {} +traefik_publish_entrypoints: true +traefik_forwarded_headers_trusted_ips: [] +traefik_tcp_ports: + - "{{ traefik_http_port }}" + - "{{ traefik_https_port }}" diff --git a/roles/traefik/files/.gitkeep b/roles/traefik/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/traefik/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/traefik/handlers/main.yml b/roles/traefik/handlers/main.yml new file mode 100644 index 0000000..f4fe437 --- /dev/null +++ b/roles/traefik/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: restart traefik + community.docker.docker_compose_v2: + project_src: "{{ traefik_dir }}" + state: restarted + diff --git a/roles/traefik/tasks/main.yml b/roles/traefik/tasks/main.yml new file mode 100644 index 0000000..4396b2a --- /dev/null +++ b/roles/traefik/tasks/main.yml @@ -0,0 +1,63 @@ +--- +- name: Create Traefik directories + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: root + group: root + mode: "0755" + loop: + - "{{ traefik_dir }}" + - "{{ traefik_dir }}/data" + tags: [traefik] + +- name: Create proxy network + community.docker.docker_network: + name: "{{ traefik_network }}" + state: present + tags: [traefik, docker] + +- name: Render Traefik environment + ansible.builtin.template: + src: env.j2 + dest: "{{ traefik_dir }}/.env" + owner: root + group: root + mode: "0600" + no_log: true + tags: [traefik, config] + +- name: Render Traefik static config + ansible.builtin.template: + src: traefik.yml.j2 + dest: "{{ traefik_dir }}/traefik.yml" + owner: root + group: root + mode: "0644" + notify: restart traefik + tags: [traefik, config] + +- name: Render Traefik Compose file + ansible.builtin.template: + src: compose.yaml.j2 + dest: "{{ traefik_dir }}/compose.yaml" + owner: root + group: root + mode: "0644" + notify: restart traefik + tags: [traefik, compose] + +- name: Allow Traefik TCP ports + community.general.ufw: + rule: allow + port: "{{ item }}" + proto: tcp + loop: "{{ traefik_tcp_ports }}" + when: traefik_publish_entrypoints | bool + tags: [traefik, firewall] + +- name: Start Traefik + community.docker.docker_compose_v2: + project_src: "{{ traefik_dir }}" + state: present + tags: [traefik, compose] diff --git a/roles/traefik/templates/compose.yaml.j2 b/roles/traefik/templates/compose.yaml.j2 new file mode 100644 index 0000000..fe156e0 --- /dev/null +++ b/roles/traefik/templates/compose.yaml.j2 @@ -0,0 +1,22 @@ +services: + traefik: + image: {{ traefik_image }} + restart: unless-stopped + env_file: .env +{% if traefik_publish_entrypoints %} + ports: + - "{{ traefik_bind_address }}:{{ traefik_http_port }}:{{ traefik_http_port }}" + - "{{ traefik_bind_address }}:{{ traefik_https_port }}:{{ traefik_https_port }}" +{% if traefik_dashboard_enabled %} + - "{{ traefik_bind_address }}:{{ traefik_dashboard_port }}:{{ traefik_dashboard_port }}" +{% endif %} +{% endif %} + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - ./traefik.yml:/etc/traefik/traefik.yml:ro + - ./data:/data + networks: + - {{ traefik_network }} +networks: + {{ traefik_network }}: + external: true diff --git a/roles/traefik/templates/env.j2 b/roles/traefik/templates/env.j2 new file mode 100644 index 0000000..0417db4 --- /dev/null +++ b/roles/traefik/templates/env.j2 @@ -0,0 +1,6 @@ +TRAEFIK_DOMAIN={{ traefik_domain }} +TRAEFIK_ACME_EMAIL={{ traefik_acme_email }} +{% for key, value in traefik_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/traefik/templates/traefik.yml.j2 b/roles/traefik/templates/traefik.yml.j2 new file mode 100644 index 0000000..0e0a549 --- /dev/null +++ b/roles/traefik/templates/traefik.yml.j2 @@ -0,0 +1,31 @@ +api: + dashboard: {{ traefik_dashboard_enabled | bool | ternary('true', 'false') }} +entryPoints: + web: + address: ":{{ traefik_http_port }}" +{% if traefik_forwarded_headers_trusted_ips | length > 0 %} + forwardedHeaders: + trustedIPs: +{% for cidr in traefik_forwarded_headers_trusted_ips %} + - "{{ cidr }}" +{% endfor %} +{% endif %} + websecure: + address: ":{{ traefik_https_port }}" +{% if traefik_forwarded_headers_trusted_ips | length > 0 %} + forwardedHeaders: + trustedIPs: +{% for cidr in traefik_forwarded_headers_trusted_ips %} + - "{{ cidr }}" +{% endfor %} +{% endif %} +providers: + docker: + exposedByDefault: false +certificatesResolvers: + letsencrypt: + acme: + email: "{{ traefik_acme_email }}" + storage: /data/acme.json + httpChallenge: + entryPoint: web diff --git a/roles/transmission/README.md b/roles/transmission/README.md new file mode 100644 index 0000000..b2380c7 --- /dev/null +++ b/roles/transmission/README.md @@ -0,0 +1,6 @@ +# transmission + +Deploys Transmission with Docker Compose. + +Tags: `transmission`. + diff --git a/roles/transmission/defaults/main.yml b/roles/transmission/defaults/main.yml new file mode 100644 index 0000000..cb74b58 --- /dev/null +++ b/roles/transmission/defaults/main.yml @@ -0,0 +1,23 @@ +--- +transmission_name: transmission +transmission_dir: /srv/docker/transmission +transmission_image: lscr.io/linuxserver/transmission:latest +transmission_domain: transmission.example.invalid +transmission_service_port: 9091 +transmission_routing_mode: "{{ nbops_service_routing_mode }}" +transmission_env: + PUID: "1000" + PGID: "1000" + TZ: "{{ nbops_timezone }}" +transmission_directories: + - "{{ transmission_dir }}" + - /srv/config/transmission + - /srv/data/downloads +transmission_volumes: + - /srv/config/transmission:/config + - /srv/data/downloads:/downloads +transmission_ports: + - { host: 9091, container: 9091, protocol: tcp } + - { host: 51413, container: 51413, protocol: tcp } + - { host: 51413, container: 51413, protocol: udp } + diff --git a/roles/transmission/files/.gitkeep b/roles/transmission/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/transmission/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/transmission/handlers/.gitkeep b/roles/transmission/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/transmission/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/transmission/tasks/main.yml b/roles/transmission/tasks/main.yml new file mode 100644 index 0000000..dc9a72a --- /dev/null +++ b/roles/transmission/tasks/main.yml @@ -0,0 +1,18 @@ +--- +- name: Deploy Transmission + ansible.builtin.include_role: + name: docker_app + vars: + docker_app_env_template_src: "{{ playbook_dir }}/../roles/transmission/templates/env.j2" + docker_app_compose_template_src: "{{ playbook_dir }}/../roles/transmission/templates/compose.yaml.j2" + docker_app_name: "{{ transmission_name }}" + docker_app_dir: "{{ transmission_dir }}" + docker_app_image: "{{ transmission_image }}" + docker_app_env: "{{ transmission_env }}" + docker_app_directories: "{{ transmission_directories }}" + docker_app_volumes: "{{ transmission_volumes }}" + docker_app_ports: "{{ transmission_ports }}" + docker_app_routing_mode: "{{ transmission_routing_mode }}" + docker_app_domain: "{{ transmission_domain }}" + docker_app_service_port: "{{ transmission_service_port }}" + diff --git a/roles/transmission/templates/.gitkeep b/roles/transmission/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/transmission/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/transmission/templates/compose.yaml.j2 b/roles/transmission/templates/compose.yaml.j2 new file mode 100644 index 0000000..6de1603 --- /dev/null +++ b/roles/transmission/templates/compose.yaml.j2 @@ -0,0 +1,90 @@ +services: + {{ docker_app_name }}: + image: {{ docker_app_image }} + restart: {{ docker_app_restart }} +{% if docker_app_command | length > 0 %} + command: {{ docker_app_command | to_json }} +{% endif %} +{% if docker_app_user | length > 0 %} + user: "{{ docker_app_user }}" +{% endif %} +{% if docker_app_network_mode | length > 0 %} + network_mode: {{ docker_app_network_mode }} +{% endif %} +{% if docker_app_privileged | bool %} + privileged: true +{% endif %} +{% if docker_app_env | length > 0 %} + env_file: .env +{% endif %} +{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} + ports: +{% for port in docker_app_ports %} + - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_service_port %} + # internal service port: {{ docker_app_service_port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }} + - traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }} + - traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }} +{% if docker_app_traefik_middlewares | length > 0 %} + - traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }} +{% endif %} + - traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }} +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% elif docker_app_labels | length > 0 %} + labels: +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% endif %} +{% if docker_app_volumes | length > 0 %} + volumes: +{% for volume in docker_app_volumes %} + - {{ volume }} +{% endfor %} +{% endif %} +{% if docker_app_devices | length > 0 %} + devices: +{% for device in docker_app_devices %} + - {{ device }} +{% endfor %} +{% endif %} +{% if docker_app_cap_add | length > 0 %} + cap_add: +{% for capability in docker_app_cap_add %} + - {{ capability }} +{% endfor %} +{% endif %} +{% if docker_app_group_add | length > 0 %} + group_add: +{% for group in docker_app_group_add %} + - "{{ group }}" +{% endfor %} +{% endif %} +{% if docker_app_extra_hosts | length > 0 %} + extra_hosts: +{% for host in docker_app_extra_hosts %} + - {{ host }} +{% endfor %} +{% endif %} +{% if docker_app_depends_on | length > 0 %} + depends_on: +{% for dependency in docker_app_depends_on %} + - {{ dependency }} +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} + networks: + - {{ docker_app_traefik_network }} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} +networks: + {{ docker_app_traefik_network }}: + external: true +{% endif %} diff --git a/roles/transmission/templates/env.j2 b/roles/transmission/templates/env.j2 new file mode 100644 index 0000000..c4f1a9f --- /dev/null +++ b/roles/transmission/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in docker_app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/ufw/README.md b/roles/ufw/README.md new file mode 100644 index 0000000..15b197f --- /dev/null +++ b/roles/ufw/README.md @@ -0,0 +1,7 @@ +# ufw + +Applies the default firewall policy: deny incoming, allow outgoing, allow SSH, +and open any service ports declared by host or application variables. + +Tags: `ufw`, `firewall`. + diff --git a/roles/ufw/defaults/main.yml b/roles/ufw/defaults/main.yml new file mode 100644 index 0000000..67444c0 --- /dev/null +++ b/roles/ufw/defaults/main.yml @@ -0,0 +1,6 @@ +--- +ufw_default_incoming_policy: deny +ufw_default_outgoing_policy: allow +ufw_tcp_ports: "{{ ufw_allowed_tcp_ports }}" +ufw_udp_ports: "{{ ufw_allowed_udp_ports }}" + diff --git a/roles/ufw/files/.gitkeep b/roles/ufw/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/ufw/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/ufw/handlers/main.yml b/roles/ufw/handlers/main.yml new file mode 100644 index 0000000..b289c4a --- /dev/null +++ b/roles/ufw/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: reload ufw + community.general.ufw: + state: reloaded + diff --git a/roles/ufw/tasks/main.yml b/roles/ufw/tasks/main.yml new file mode 100644 index 0000000..3f2947c --- /dev/null +++ b/roles/ufw/tasks/main.yml @@ -0,0 +1,40 @@ +--- +- name: Install UFW + ansible.builtin.apt: + name: ufw + state: present + tags: [ufw] + +- name: Set incoming policy + community.general.ufw: + direction: incoming + policy: "{{ ufw_default_incoming_policy }}" + tags: [ufw, firewall] + +- name: Set outgoing policy + community.general.ufw: + direction: outgoing + policy: "{{ ufw_default_outgoing_policy }}" + tags: [ufw, firewall] + +- name: Allow TCP ports + community.general.ufw: + rule: allow + port: "{{ item }}" + proto: tcp + loop: "{{ ufw_tcp_ports }}" + tags: [ufw, firewall] + +- name: Allow UDP ports + community.general.ufw: + rule: allow + port: "{{ item }}" + proto: udp + loop: "{{ ufw_udp_ports }}" + tags: [ufw, firewall] + +- name: Enable UFW + community.general.ufw: + state: enabled + tags: [ufw, firewall] + diff --git a/roles/ufw/templates/.gitkeep b/roles/ufw/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/ufw/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/updates/README.md b/roles/updates/README.md new file mode 100644 index 0000000..2e37049 --- /dev/null +++ b/roles/updates/README.md @@ -0,0 +1,6 @@ +# updates + +Installs and configures unattended-upgrades and needrestart. + +Tags: `updates`. + diff --git a/roles/updates/defaults/main.yml b/roles/updates/defaults/main.yml new file mode 100644 index 0000000..9115cf5 --- /dev/null +++ b/roles/updates/defaults/main.yml @@ -0,0 +1,6 @@ +--- +updates_unattended_origins: + - origin=Debian,codename=${distro_codename},label=Debian + - origin=Debian,codename=${distro_codename}-security,label=Debian-Security +updates_reboot: "false" + diff --git a/roles/updates/files/.gitkeep b/roles/updates/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/updates/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/updates/handlers/main.yml b/roles/updates/handlers/main.yml new file mode 100644 index 0000000..d2b2ff9 --- /dev/null +++ b/roles/updates/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: restart unattended-upgrades + ansible.builtin.service: + name: unattended-upgrades + state: restarted + diff --git a/roles/updates/tasks/main.yml b/roles/updates/tasks/main.yml new file mode 100644 index 0000000..b1044bd --- /dev/null +++ b/roles/updates/tasks/main.yml @@ -0,0 +1,39 @@ +--- +- name: Install update automation packages + ansible.builtin.apt: + name: + - unattended-upgrades + - needrestart + state: present + tags: [updates] + +- name: Configure unattended upgrades + ansible.builtin.template: + src: 50unattended-upgrades.j2 + dest: /etc/apt/apt.conf.d/50unattended-upgrades + owner: root + group: root + mode: "0644" + notify: restart unattended-upgrades + tags: [updates] + +- name: Enable periodic apt updates + ansible.builtin.copy: + dest: /etc/apt/apt.conf.d/20auto-upgrades + owner: root + group: root + mode: "0644" + content: | + APT::Periodic::Update-Package-Lists "1"; + APT::Periodic::Unattended-Upgrade "1"; + APT::Periodic::AutocleanInterval "7"; + notify: restart unattended-upgrades + tags: [updates] + +- name: Enable unattended-upgrades service + ansible.builtin.service: + name: unattended-upgrades + enabled: true + state: started + tags: [updates] + diff --git a/roles/updates/templates/50unattended-upgrades.j2 b/roles/updates/templates/50unattended-upgrades.j2 new file mode 100644 index 0000000..a903d4f --- /dev/null +++ b/roles/updates/templates/50unattended-upgrades.j2 @@ -0,0 +1,7 @@ +Unattended-Upgrade::Origins-Pattern { +{% for origin in updates_unattended_origins %} + "{{ origin }}"; +{% endfor %} +}; +Unattended-Upgrade::Automatic-Reboot "{{ updates_reboot }}"; + diff --git a/roles/uptime_kuma/README.md b/roles/uptime_kuma/README.md new file mode 100644 index 0000000..5f0b26b --- /dev/null +++ b/roles/uptime_kuma/README.md @@ -0,0 +1,4 @@ +# uptime_kuma + +Deploys Uptime Kuma with Docker Compose. + diff --git a/roles/uptime_kuma/defaults/main.yml b/roles/uptime_kuma/defaults/main.yml new file mode 100644 index 0000000..a7ec33f --- /dev/null +++ b/roles/uptime_kuma/defaults/main.yml @@ -0,0 +1,17 @@ +--- +uptime_kuma_name: uptime-kuma +uptime_kuma_dir: /srv/docker/uptime-kuma +uptime_kuma_image: louislam/uptime-kuma:latest +uptime_kuma_domain: status.example.invalid +uptime_kuma_service_port: 3001 +uptime_kuma_routing_mode: "{{ nbops_service_routing_mode }}" +uptime_kuma_env: + TZ: "{{ nbops_timezone }}" +uptime_kuma_directories: + - "{{ uptime_kuma_dir }}" + - /srv/data/uptime-kuma +uptime_kuma_volumes: + - /srv/data/uptime-kuma:/app/data +uptime_kuma_ports: + - { host: 3002, container: 3001, protocol: tcp } + diff --git a/roles/uptime_kuma/files/.gitkeep b/roles/uptime_kuma/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/uptime_kuma/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/uptime_kuma/handlers/.gitkeep b/roles/uptime_kuma/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/uptime_kuma/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/uptime_kuma/tasks/main.yml b/roles/uptime_kuma/tasks/main.yml new file mode 100644 index 0000000..671785d --- /dev/null +++ b/roles/uptime_kuma/tasks/main.yml @@ -0,0 +1,18 @@ +--- +- name: Deploy Uptime Kuma + ansible.builtin.include_role: + name: docker_app + vars: + docker_app_env_template_src: "{{ playbook_dir }}/../roles/uptime_kuma/templates/env.j2" + docker_app_compose_template_src: "{{ playbook_dir }}/../roles/uptime_kuma/templates/compose.yaml.j2" + docker_app_name: "{{ uptime_kuma_name }}" + docker_app_dir: "{{ uptime_kuma_dir }}" + docker_app_image: "{{ uptime_kuma_image }}" + docker_app_env: "{{ uptime_kuma_env }}" + docker_app_directories: "{{ uptime_kuma_directories }}" + docker_app_volumes: "{{ uptime_kuma_volumes }}" + docker_app_ports: "{{ uptime_kuma_ports }}" + docker_app_routing_mode: "{{ uptime_kuma_routing_mode }}" + docker_app_domain: "{{ uptime_kuma_domain }}" + docker_app_service_port: "{{ uptime_kuma_service_port }}" + diff --git a/roles/uptime_kuma/templates/.gitkeep b/roles/uptime_kuma/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/uptime_kuma/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/uptime_kuma/templates/compose.yaml.j2 b/roles/uptime_kuma/templates/compose.yaml.j2 new file mode 100644 index 0000000..6de1603 --- /dev/null +++ b/roles/uptime_kuma/templates/compose.yaml.j2 @@ -0,0 +1,90 @@ +services: + {{ docker_app_name }}: + image: {{ docker_app_image }} + restart: {{ docker_app_restart }} +{% if docker_app_command | length > 0 %} + command: {{ docker_app_command | to_json }} +{% endif %} +{% if docker_app_user | length > 0 %} + user: "{{ docker_app_user }}" +{% endif %} +{% if docker_app_network_mode | length > 0 %} + network_mode: {{ docker_app_network_mode }} +{% endif %} +{% if docker_app_privileged | bool %} + privileged: true +{% endif %} +{% if docker_app_env | length > 0 %} + env_file: .env +{% endif %} +{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} + ports: +{% for port in docker_app_ports %} + - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_service_port %} + # internal service port: {{ docker_app_service_port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }} + - traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }} + - traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }} +{% if docker_app_traefik_middlewares | length > 0 %} + - traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }} +{% endif %} + - traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }} +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% elif docker_app_labels | length > 0 %} + labels: +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% endif %} +{% if docker_app_volumes | length > 0 %} + volumes: +{% for volume in docker_app_volumes %} + - {{ volume }} +{% endfor %} +{% endif %} +{% if docker_app_devices | length > 0 %} + devices: +{% for device in docker_app_devices %} + - {{ device }} +{% endfor %} +{% endif %} +{% if docker_app_cap_add | length > 0 %} + cap_add: +{% for capability in docker_app_cap_add %} + - {{ capability }} +{% endfor %} +{% endif %} +{% if docker_app_group_add | length > 0 %} + group_add: +{% for group in docker_app_group_add %} + - "{{ group }}" +{% endfor %} +{% endif %} +{% if docker_app_extra_hosts | length > 0 %} + extra_hosts: +{% for host in docker_app_extra_hosts %} + - {{ host }} +{% endfor %} +{% endif %} +{% if docker_app_depends_on | length > 0 %} + depends_on: +{% for dependency in docker_app_depends_on %} + - {{ dependency }} +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} + networks: + - {{ docker_app_traefik_network }} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} +networks: + {{ docker_app_traefik_network }}: + external: true +{% endif %} diff --git a/roles/uptime_kuma/templates/env.j2 b/roles/uptime_kuma/templates/env.j2 new file mode 100644 index 0000000..c4f1a9f --- /dev/null +++ b/roles/uptime_kuma/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in docker_app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/valkey/README.md b/roles/valkey/README.md new file mode 100644 index 0000000..5e4f5ce --- /dev/null +++ b/roles/valkey/README.md @@ -0,0 +1,4 @@ +# valkey + +Deploys Valkey with Docker Compose. + diff --git a/roles/valkey/defaults/main.yml b/roles/valkey/defaults/main.yml new file mode 100644 index 0000000..97c0320 --- /dev/null +++ b/roles/valkey/defaults/main.yml @@ -0,0 +1,14 @@ +--- +valkey_name: valkey +valkey_dir: /srv/docker/valkey +valkey_image: valkey/valkey:8-alpine +valkey_routing_mode: host +valkey_env: {} +valkey_directories: + - "{{ valkey_dir }}" + - /srv/data/valkey +valkey_volumes: + - /srv/data/valkey:/data +valkey_ports: + - { host: 6379, container: 6379, protocol: tcp } + diff --git a/roles/valkey/files/.gitkeep b/roles/valkey/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/valkey/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/valkey/handlers/.gitkeep b/roles/valkey/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/valkey/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/valkey/tasks/main.yml b/roles/valkey/tasks/main.yml new file mode 100644 index 0000000..8256543 --- /dev/null +++ b/roles/valkey/tasks/main.yml @@ -0,0 +1,16 @@ +--- +- name: Deploy Valkey + ansible.builtin.include_role: + name: docker_app + vars: + docker_app_env_template_src: "{{ playbook_dir }}/../roles/valkey/templates/env.j2" + docker_app_compose_template_src: "{{ playbook_dir }}/../roles/valkey/templates/compose.yaml.j2" + docker_app_name: "{{ valkey_name }}" + docker_app_dir: "{{ valkey_dir }}" + docker_app_image: "{{ valkey_image }}" + docker_app_env: "{{ valkey_env }}" + docker_app_directories: "{{ valkey_directories }}" + docker_app_volumes: "{{ valkey_volumes }}" + docker_app_ports: "{{ valkey_ports }}" + docker_app_routing_mode: "{{ valkey_routing_mode }}" + diff --git a/roles/valkey/templates/.gitkeep b/roles/valkey/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/valkey/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/valkey/templates/compose.yaml.j2 b/roles/valkey/templates/compose.yaml.j2 new file mode 100644 index 0000000..6de1603 --- /dev/null +++ b/roles/valkey/templates/compose.yaml.j2 @@ -0,0 +1,90 @@ +services: + {{ docker_app_name }}: + image: {{ docker_app_image }} + restart: {{ docker_app_restart }} +{% if docker_app_command | length > 0 %} + command: {{ docker_app_command | to_json }} +{% endif %} +{% if docker_app_user | length > 0 %} + user: "{{ docker_app_user }}" +{% endif %} +{% if docker_app_network_mode | length > 0 %} + network_mode: {{ docker_app_network_mode }} +{% endif %} +{% if docker_app_privileged | bool %} + privileged: true +{% endif %} +{% if docker_app_env | length > 0 %} + env_file: .env +{% endif %} +{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} + ports: +{% for port in docker_app_ports %} + - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_service_port %} + # internal service port: {{ docker_app_service_port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }} + - traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }} + - traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }} +{% if docker_app_traefik_middlewares | length > 0 %} + - traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }} +{% endif %} + - traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }} +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% elif docker_app_labels | length > 0 %} + labels: +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% endif %} +{% if docker_app_volumes | length > 0 %} + volumes: +{% for volume in docker_app_volumes %} + - {{ volume }} +{% endfor %} +{% endif %} +{% if docker_app_devices | length > 0 %} + devices: +{% for device in docker_app_devices %} + - {{ device }} +{% endfor %} +{% endif %} +{% if docker_app_cap_add | length > 0 %} + cap_add: +{% for capability in docker_app_cap_add %} + - {{ capability }} +{% endfor %} +{% endif %} +{% if docker_app_group_add | length > 0 %} + group_add: +{% for group in docker_app_group_add %} + - "{{ group }}" +{% endfor %} +{% endif %} +{% if docker_app_extra_hosts | length > 0 %} + extra_hosts: +{% for host in docker_app_extra_hosts %} + - {{ host }} +{% endfor %} +{% endif %} +{% if docker_app_depends_on | length > 0 %} + depends_on: +{% for dependency in docker_app_depends_on %} + - {{ dependency }} +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} + networks: + - {{ docker_app_traefik_network }} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} +networks: + {{ docker_app_traefik_network }}: + external: true +{% endif %} diff --git a/roles/valkey/templates/env.j2 b/roles/valkey/templates/env.j2 new file mode 100644 index 0000000..c4f1a9f --- /dev/null +++ b/roles/valkey/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in docker_app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/vaultwarden/README.md b/roles/vaultwarden/README.md new file mode 100644 index 0000000..7cdf1da --- /dev/null +++ b/roles/vaultwarden/README.md @@ -0,0 +1,4 @@ +# vaultwarden + +Deploys Vaultwarden with Docker Compose. + diff --git a/roles/vaultwarden/defaults/main.yml b/roles/vaultwarden/defaults/main.yml new file mode 100644 index 0000000..1d5be13 --- /dev/null +++ b/roles/vaultwarden/defaults/main.yml @@ -0,0 +1,19 @@ +--- +vaultwarden_name: vaultwarden +vaultwarden_dir: /srv/docker/vaultwarden +vaultwarden_image: vaultwarden/server:latest +vaultwarden_domain: vaultwarden.example.invalid +vaultwarden_service_port: 80 +vaultwarden_routing_mode: "{{ nbops_service_routing_mode }}" +vaultwarden_env: + DOMAIN: "https://{{ vaultwarden_domain }}" + SIGNUPS_ALLOWED: "false" + ADMIN_TOKEN: "{{ vault_vaultwarden_admin_token | default('CHANGE_ME_IN_VAULT') }}" +vaultwarden_directories: + - "{{ vaultwarden_dir }}" + - /srv/data/vaultwarden +vaultwarden_volumes: + - /srv/data/vaultwarden:/data +vaultwarden_ports: + - { host: 8086, container: 80, protocol: tcp } + diff --git a/roles/vaultwarden/files/.gitkeep b/roles/vaultwarden/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/vaultwarden/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/vaultwarden/handlers/.gitkeep b/roles/vaultwarden/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/vaultwarden/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/vaultwarden/tasks/main.yml b/roles/vaultwarden/tasks/main.yml new file mode 100644 index 0000000..d23e258 --- /dev/null +++ b/roles/vaultwarden/tasks/main.yml @@ -0,0 +1,18 @@ +--- +- name: Deploy Vaultwarden + ansible.builtin.include_role: + name: docker_app + vars: + docker_app_env_template_src: "{{ playbook_dir }}/../roles/vaultwarden/templates/env.j2" + docker_app_compose_template_src: "{{ playbook_dir }}/../roles/vaultwarden/templates/compose.yaml.j2" + docker_app_name: "{{ vaultwarden_name }}" + docker_app_dir: "{{ vaultwarden_dir }}" + docker_app_image: "{{ vaultwarden_image }}" + docker_app_env: "{{ vaultwarden_env }}" + docker_app_directories: "{{ vaultwarden_directories }}" + docker_app_volumes: "{{ vaultwarden_volumes }}" + docker_app_ports: "{{ vaultwarden_ports }}" + docker_app_routing_mode: "{{ vaultwarden_routing_mode }}" + docker_app_domain: "{{ vaultwarden_domain }}" + docker_app_service_port: "{{ vaultwarden_service_port }}" + diff --git a/roles/vaultwarden/templates/.gitkeep b/roles/vaultwarden/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/vaultwarden/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/vaultwarden/templates/compose.yaml.j2 b/roles/vaultwarden/templates/compose.yaml.j2 new file mode 100644 index 0000000..6de1603 --- /dev/null +++ b/roles/vaultwarden/templates/compose.yaml.j2 @@ -0,0 +1,90 @@ +services: + {{ docker_app_name }}: + image: {{ docker_app_image }} + restart: {{ docker_app_restart }} +{% if docker_app_command | length > 0 %} + command: {{ docker_app_command | to_json }} +{% endif %} +{% if docker_app_user | length > 0 %} + user: "{{ docker_app_user }}" +{% endif %} +{% if docker_app_network_mode | length > 0 %} + network_mode: {{ docker_app_network_mode }} +{% endif %} +{% if docker_app_privileged | bool %} + privileged: true +{% endif %} +{% if docker_app_env | length > 0 %} + env_file: .env +{% endif %} +{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} + ports: +{% for port in docker_app_ports %} + - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_service_port %} + # internal service port: {{ docker_app_service_port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }} + - traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }} + - traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }} +{% if docker_app_traefik_middlewares | length > 0 %} + - traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }} +{% endif %} + - traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }} +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% elif docker_app_labels | length > 0 %} + labels: +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% endif %} +{% if docker_app_volumes | length > 0 %} + volumes: +{% for volume in docker_app_volumes %} + - {{ volume }} +{% endfor %} +{% endif %} +{% if docker_app_devices | length > 0 %} + devices: +{% for device in docker_app_devices %} + - {{ device }} +{% endfor %} +{% endif %} +{% if docker_app_cap_add | length > 0 %} + cap_add: +{% for capability in docker_app_cap_add %} + - {{ capability }} +{% endfor %} +{% endif %} +{% if docker_app_group_add | length > 0 %} + group_add: +{% for group in docker_app_group_add %} + - "{{ group }}" +{% endfor %} +{% endif %} +{% if docker_app_extra_hosts | length > 0 %} + extra_hosts: +{% for host in docker_app_extra_hosts %} + - {{ host }} +{% endfor %} +{% endif %} +{% if docker_app_depends_on | length > 0 %} + depends_on: +{% for dependency in docker_app_depends_on %} + - {{ dependency }} +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} + networks: + - {{ docker_app_traefik_network }} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} +networks: + {{ docker_app_traefik_network }}: + external: true +{% endif %} diff --git a/roles/vaultwarden/templates/env.j2 b/roles/vaultwarden/templates/env.j2 new file mode 100644 index 0000000..c4f1a9f --- /dev/null +++ b/roles/vaultwarden/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in docker_app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/roles/wizarr/README.md b/roles/wizarr/README.md new file mode 100644 index 0000000..e548008 --- /dev/null +++ b/roles/wizarr/README.md @@ -0,0 +1,4 @@ +# wizarr + +Deploys Wizarr with Docker Compose. + diff --git a/roles/wizarr/defaults/main.yml b/roles/wizarr/defaults/main.yml new file mode 100644 index 0000000..f654a47 --- /dev/null +++ b/roles/wizarr/defaults/main.yml @@ -0,0 +1,17 @@ +--- +wizarr_name: wizarr +wizarr_dir: /srv/docker/wizarr +wizarr_image: ghcr.io/wizarrrr/wizarr:latest +wizarr_domain: wizarr.example.invalid +wizarr_service_port: 5690 +wizarr_routing_mode: "{{ nbops_service_routing_mode }}" +wizarr_env: + TZ: "{{ nbops_timezone }}" +wizarr_directories: + - "{{ wizarr_dir }}" + - /srv/config/wizarr +wizarr_volumes: + - /srv/config/wizarr:/data/database +wizarr_ports: + - { host: 5690, container: 5690, protocol: tcp } + diff --git a/roles/wizarr/files/.gitkeep b/roles/wizarr/files/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/wizarr/files/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/wizarr/handlers/.gitkeep b/roles/wizarr/handlers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/wizarr/handlers/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/wizarr/tasks/main.yml b/roles/wizarr/tasks/main.yml new file mode 100644 index 0000000..63f9cc2 --- /dev/null +++ b/roles/wizarr/tasks/main.yml @@ -0,0 +1,18 @@ +--- +- name: Deploy Wizarr + ansible.builtin.include_role: + name: docker_app + vars: + docker_app_env_template_src: "{{ playbook_dir }}/../roles/wizarr/templates/env.j2" + docker_app_compose_template_src: "{{ playbook_dir }}/../roles/wizarr/templates/compose.yaml.j2" + docker_app_name: "{{ wizarr_name }}" + docker_app_dir: "{{ wizarr_dir }}" + docker_app_image: "{{ wizarr_image }}" + docker_app_env: "{{ wizarr_env }}" + docker_app_directories: "{{ wizarr_directories }}" + docker_app_volumes: "{{ wizarr_volumes }}" + docker_app_ports: "{{ wizarr_ports }}" + docker_app_routing_mode: "{{ wizarr_routing_mode }}" + docker_app_domain: "{{ wizarr_domain }}" + docker_app_service_port: "{{ wizarr_service_port }}" + diff --git a/roles/wizarr/templates/.gitkeep b/roles/wizarr/templates/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/roles/wizarr/templates/.gitkeep @@ -0,0 +1 @@ + diff --git a/roles/wizarr/templates/compose.yaml.j2 b/roles/wizarr/templates/compose.yaml.j2 new file mode 100644 index 0000000..6de1603 --- /dev/null +++ b/roles/wizarr/templates/compose.yaml.j2 @@ -0,0 +1,90 @@ +services: + {{ docker_app_name }}: + image: {{ docker_app_image }} + restart: {{ docker_app_restart }} +{% if docker_app_command | length > 0 %} + command: {{ docker_app_command | to_json }} +{% endif %} +{% if docker_app_user | length > 0 %} + user: "{{ docker_app_user }}" +{% endif %} +{% if docker_app_network_mode | length > 0 %} + network_mode: {{ docker_app_network_mode }} +{% endif %} +{% if docker_app_privileged | bool %} + privileged: true +{% endif %} +{% if docker_app_env | length > 0 %} + env_file: .env +{% endif %} +{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %} + ports: +{% for port in docker_app_ports %} + - "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}" +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_service_port %} + # internal service port: {{ docker_app_service_port }}; not published on the Docker host + labels: + - traefik.enable=true + - traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }} + - traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }} + - traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }} +{% if docker_app_traefik_middlewares | length > 0 %} + - traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }} +{% endif %} + - traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }} +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% elif docker_app_labels | length > 0 %} + labels: +{% for key, value in docker_app_labels.items() %} + - {{ key }}={{ value }} +{% endfor %} +{% endif %} +{% if docker_app_volumes | length > 0 %} + volumes: +{% for volume in docker_app_volumes %} + - {{ volume }} +{% endfor %} +{% endif %} +{% if docker_app_devices | length > 0 %} + devices: +{% for device in docker_app_devices %} + - {{ device }} +{% endfor %} +{% endif %} +{% if docker_app_cap_add | length > 0 %} + cap_add: +{% for capability in docker_app_cap_add %} + - {{ capability }} +{% endfor %} +{% endif %} +{% if docker_app_group_add | length > 0 %} + group_add: +{% for group in docker_app_group_add %} + - "{{ group }}" +{% endfor %} +{% endif %} +{% if docker_app_extra_hosts | length > 0 %} + extra_hosts: +{% for host in docker_app_extra_hosts %} + - {{ host }} +{% endfor %} +{% endif %} +{% if docker_app_depends_on | length > 0 %} + depends_on: +{% for dependency in docker_app_depends_on %} + - {{ dependency }} +{% endfor %} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} + networks: + - {{ docker_app_traefik_network }} +{% endif %} +{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %} +networks: + {{ docker_app_traefik_network }}: + external: true +{% endif %} diff --git a/roles/wizarr/templates/env.j2 b/roles/wizarr/templates/env.j2 new file mode 100644 index 0000000..c4f1a9f --- /dev/null +++ b/roles/wizarr/templates/env.j2 @@ -0,0 +1,4 @@ +{% for key, value in docker_app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + diff --git a/scripts/ansible-pull.sh b/scripts/ansible-pull.sh new file mode 100755 index 0000000..048ed24 --- /dev/null +++ b/scripts/ansible-pull.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env sh +set -eu + +REPO_URL="${REPO_URL:?Set REPO_URL to the nbops-ansible Git URL}" +PLAYBOOK="${PLAYBOOK:-playbooks/pull.yml}" +BRANCH="${BRANCH:-main}" + +ansible-pull \ + -U "$REPO_URL" \ + -C "$BRANCH" \ + -i localhost, \ + -c local \ + "$PLAYBOOK" \ + --accept-host-key + diff --git a/scripts/check.sh b/scripts/check.sh new file mode 100755 index 0000000..bc6f342 --- /dev/null +++ b/scripts/check.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env sh +set -eu + +ansible-playbook --syntax-check playbooks/site.yml + +if command -v ansible-lint >/dev/null 2>&1; then + ansible-lint +fi + diff --git a/templates/cloud-init-user-data.yml.j2 b/templates/cloud-init-user-data.yml.j2 new file mode 100644 index 0000000..618e897 --- /dev/null +++ b/templates/cloud-init-user-data.yml.j2 @@ -0,0 +1,95 @@ +#cloud-config +hostname: {{ cloud_init_hostname | default(nbops_hostname) }} +fqdn: {{ cloud_init_fqdn | default(nbops_fqdn) }} +manage_etc_hosts: true +timezone: {{ cloud_init_timezone | default(nbops_timezone) }} +locale: {{ cloud_init_locale | default(nbops_locale) }} + +users: + - default + - name: {{ cloud_init_admin_user | default(nbops_admin_user) }} + groups: {{ cloud_init_admin_groups | default(nbops_admin_groups) | join(',') }} + shell: /bin/bash + sudo: ALL=(ALL) NOPASSWD:ALL + lock_passwd: true + ssh_authorized_keys: +{% for key in cloud_init_ssh_authorized_keys | default([]) %} + - {{ key }} +{% endfor %} + +disable_root: true +ssh_pwauth: false + +package_update: true +package_upgrade: true +packages: + - ansible + - apt-transport-https + - bash-completion + - ca-certificates + - chrony + - curl + - git + - gnupg + - jq + - locales + - needrestart + - python3 + - python3-apt + - qemu-guest-agent + - rsync + - sudo + - ufw + - unattended-upgrades + - vim + +write_files: + - path: /etc/ssh/sshd_config.d/10-nbops-cloud-init.conf + owner: root:root + permissions: "0644" + content: | + PermitRootLogin no + PasswordAuthentication no + KbdInteractiveAuthentication no + PubkeyAuthentication yes + UseDNS no + X11Forwarding no + - path: /etc/docker/daemon.json + owner: root:root + permissions: "0644" + content: | + { + "log-driver": "local", + "live-restore": true, + "storage-driver": "overlay2", + "iptables": true + } + - path: /etc/sysctl.d/99-nbops.conf + owner: root:root + permissions: "0644" + content: | + net.ipv4.ip_forward = 1 + net.ipv6.conf.all.forwarding = 1 + fs.inotify.max_user_watches = 1048576 + fs.inotify.max_user_instances = 1024 + +runcmd: + - install -m 0755 -d /etc/apt/keyrings + - curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc + - chmod a+r /etc/apt/keyrings/docker.asc + - sh -c 'echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian $(. /etc/os-release && echo $VERSION_CODENAME) stable" > /etc/apt/sources.list.d/docker.list' + - apt-get update + - apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + - mkdir -p /srv/docker /srv/config /srv/backups /srv/data /srv/media /etc/pki/internal/private /etc/pki/internal/certs /usr/local/share/ca-certificates/internal + - chmod 0700 /etc/pki/internal/private + - ufw default deny incoming + - ufw default allow outgoing + - ufw allow {{ cloud_init_ssh_port | default(nbops_ssh_port) }}/tcp + - ufw --force enable + - systemctl enable --now chrony qemu-guest-agent docker unattended-upgrades + - sysctl --system + - ansible-galaxy collection install -r {{ cloud_init_repo_path | default('/opt/nbops-ansible') }}/requirements.yml || true + - ansible-pull -U {{ cloud_init_repo_url }} -i localhost, -c local {{ cloud_init_playbook | default('playbooks/pull.yml') }} --accept-host-key + +final_message: "nbops cloud-init bootstrap complete after $UPTIME seconds" +