From 7844cc44168f457c95c896b501624872aea501f1 Mon Sep 17 00:00:00 2001 From: hiperman Date: Fri, 30 Jan 2026 15:07:31 -0500 Subject: [PATCH] initial commit --- .gitmodules | 3 + README.md | 222 ++++++++ galaxy.yml | 22 + roles/audiobookshelf/README.md | 34 ++ roles/audiobookshelf/defaults/main.yaml | 14 + roles/audiobookshelf/meta/main.yaml | 16 + roles/audiobookshelf/templates/compose.yml.j2 | 14 + roles/calibre-web/README.md | 36 ++ roles/calibre-web/defaults/main.yml | 22 + roles/calibre-web/meta/main.yml | 15 + roles/calibre-web/templates/compose.yml.j2 | 17 + roles/docker_compose_app | 1 + roles/gitea/.gitignore | 3 + roles/gitea/LICENSE | 18 + roles/gitea/README.md | 512 ++++++++++++++++++ roles/gitea/defaults/main.yaml | 253 +++++++++ roles/gitea/handlers/main.yaml | 6 + roles/gitea/meta/.galaxy_install_info | 2 + roles/gitea/meta/main.yaml | 40 ++ roles/gitea/tasks/generate-config.yaml | 24 + roles/gitea/tasks/main.yaml | 17 + roles/gitea/tasks/manage_users.yaml | 58 ++ roles/gitea/tasks/setup-fail2ban.yaml | 39 ++ roles/gitea/templates/actions/config.yaml.j2 | 111 ++++ roles/gitea/templates/compose.yml.j2 | 42 ++ roles/gitea/templates/env.j2 | 429 +++++++++++++++ roles/gitea/templates/fail2ban/filter.conf.j2 | 4 + roles/gitea/templates/fail2ban/jail.conf.j2 | 11 + roles/glance/README.md | 44 ++ roles/glance/defaults/main.yml | 16 + roles/glance/meta/main.yml | 17 + roles/glance/templates/compose.yml.j2 | 10 + roles/glance/templates/glance.yml.j2 | 105 ++++ roles/grafana/README.md | 25 + roles/grafana/defaults/main.yml | 8 + roles/grafana/meta/main.yml | 14 + roles/grafana/templates/compose.yml.j2 | 10 + roles/immich/README.md | 37 ++ roles/immich/defaults/main.yml | 18 + roles/immich/meta/main.yml | 20 + roles/immich/templates/.env.j2 | 22 + roles/immich/templates/compose.yml.j2 | 66 +++ roles/influxdb/README.md | 30 + roles/influxdb/defaults/main.yml | 15 + roles/influxdb/meta/main.yml | 16 + roles/influxdb/templates/compose.yml.j2 | 12 + roles/jellyfin/README.md | 50 ++ roles/jellyfin/defaults/main.yaml | 22 + roles/jellyfin/meta/main.yaml | 16 + roles/jellyfin/templates/compose.yml.j2 | 33 ++ roles/linkwarden/README.md | 35 ++ roles/linkwarden/defaults/main.yml | 20 + roles/linkwarden/meta/main.yml | 22 + roles/linkwarden/templates/.env.j2 | 473 ++++++++++++++++ roles/linkwarden/templates/compose.yml.j2 | 27 + roles/mealie/README.md | 27 + roles/mealie/defaults/main.yml | 21 + roles/mealie/meta/main.yml | 14 + roles/mealie/templates/compose.yml.j2 | 25 + roles/navidrome/README.md | 34 ++ roles/navidrome/defaults/main.yaml | 21 + roles/navidrome/meta/main.yaml | 14 + roles/navidrome/templates/compose.yml.j2 | 22 + roles/open-webui/README.md | 29 + roles/open-webui/defaults/main.yml | 18 + roles/open-webui/meta/main.yml | 14 + roles/open-webui/templates/compose.yml.j2 | 18 + roles/paperless-ngx/README.md | 40 ++ roles/paperless-ngx/defaults/main.yml | 23 + roles/paperless-ngx/meta/main.yml | 23 + roles/paperless-ngx/templates/compose.yml.j2 | 50 ++ roles/redlib/README.md | 23 + roles/redlib/defaults/main.yml | 13 + roles/redlib/meta/main.yml | 11 + roles/redlib/templates/compose.yml.j2 | 30 + roles/speedtest-tracker/README.md | 39 ++ roles/speedtest-tracker/defaults/main.yml | 23 + roles/speedtest-tracker/meta/main.yml | 14 + .../templates/compose.yml.j2 | 26 + roles/uptime-kuma/README.md | 25 + roles/uptime-kuma/defaults/main.yml | 13 + roles/uptime-kuma/meta/main.yml | 14 + roles/uptime-kuma/templates/compose.yml.j2 | 10 + 83 files changed, 3802 insertions(+) create mode 100644 .gitmodules create mode 100644 README.md create mode 100644 galaxy.yml create mode 100644 roles/audiobookshelf/README.md create mode 100644 roles/audiobookshelf/defaults/main.yaml create mode 100644 roles/audiobookshelf/meta/main.yaml create mode 100644 roles/audiobookshelf/templates/compose.yml.j2 create mode 100644 roles/calibre-web/README.md create mode 100644 roles/calibre-web/defaults/main.yml create mode 100644 roles/calibre-web/meta/main.yml create mode 100644 roles/calibre-web/templates/compose.yml.j2 create mode 160000 roles/docker_compose_app create mode 100644 roles/gitea/.gitignore create mode 100644 roles/gitea/LICENSE create mode 100644 roles/gitea/README.md create mode 100644 roles/gitea/defaults/main.yaml create mode 100644 roles/gitea/handlers/main.yaml create mode 100644 roles/gitea/meta/.galaxy_install_info create mode 100644 roles/gitea/meta/main.yaml create mode 100644 roles/gitea/tasks/generate-config.yaml create mode 100644 roles/gitea/tasks/main.yaml create mode 100644 roles/gitea/tasks/manage_users.yaml create mode 100644 roles/gitea/tasks/setup-fail2ban.yaml create mode 100644 roles/gitea/templates/actions/config.yaml.j2 create mode 100644 roles/gitea/templates/compose.yml.j2 create mode 100644 roles/gitea/templates/env.j2 create mode 100644 roles/gitea/templates/fail2ban/filter.conf.j2 create mode 100644 roles/gitea/templates/fail2ban/jail.conf.j2 create mode 100644 roles/glance/README.md create mode 100644 roles/glance/defaults/main.yml create mode 100644 roles/glance/meta/main.yml create mode 100644 roles/glance/templates/compose.yml.j2 create mode 100644 roles/glance/templates/glance.yml.j2 create mode 100644 roles/grafana/README.md create mode 100644 roles/grafana/defaults/main.yml create mode 100644 roles/grafana/meta/main.yml create mode 100644 roles/grafana/templates/compose.yml.j2 create mode 100644 roles/immich/README.md create mode 100644 roles/immich/defaults/main.yml create mode 100644 roles/immich/meta/main.yml create mode 100644 roles/immich/templates/.env.j2 create mode 100644 roles/immich/templates/compose.yml.j2 create mode 100644 roles/influxdb/README.md create mode 100644 roles/influxdb/defaults/main.yml create mode 100644 roles/influxdb/meta/main.yml create mode 100644 roles/influxdb/templates/compose.yml.j2 create mode 100644 roles/jellyfin/README.md create mode 100644 roles/jellyfin/defaults/main.yaml create mode 100644 roles/jellyfin/meta/main.yaml create mode 100644 roles/jellyfin/templates/compose.yml.j2 create mode 100644 roles/linkwarden/README.md create mode 100644 roles/linkwarden/defaults/main.yml create mode 100644 roles/linkwarden/meta/main.yml create mode 100644 roles/linkwarden/templates/.env.j2 create mode 100644 roles/linkwarden/templates/compose.yml.j2 create mode 100644 roles/mealie/README.md create mode 100644 roles/mealie/defaults/main.yml create mode 100644 roles/mealie/meta/main.yml create mode 100644 roles/mealie/templates/compose.yml.j2 create mode 100644 roles/navidrome/README.md create mode 100644 roles/navidrome/defaults/main.yaml create mode 100644 roles/navidrome/meta/main.yaml create mode 100644 roles/navidrome/templates/compose.yml.j2 create mode 100644 roles/open-webui/README.md create mode 100644 roles/open-webui/defaults/main.yml create mode 100644 roles/open-webui/meta/main.yml create mode 100644 roles/open-webui/templates/compose.yml.j2 create mode 100644 roles/paperless-ngx/README.md create mode 100644 roles/paperless-ngx/defaults/main.yml create mode 100644 roles/paperless-ngx/meta/main.yml create mode 100644 roles/paperless-ngx/templates/compose.yml.j2 create mode 100644 roles/redlib/README.md create mode 100644 roles/redlib/defaults/main.yml create mode 100644 roles/redlib/meta/main.yml create mode 100644 roles/redlib/templates/compose.yml.j2 create mode 100644 roles/speedtest-tracker/README.md create mode 100644 roles/speedtest-tracker/defaults/main.yml create mode 100644 roles/speedtest-tracker/meta/main.yml create mode 100644 roles/speedtest-tracker/templates/compose.yml.j2 create mode 100644 roles/uptime-kuma/README.md create mode 100644 roles/uptime-kuma/defaults/main.yml create mode 100644 roles/uptime-kuma/meta/main.yml create mode 100644 roles/uptime-kuma/templates/compose.yml.j2 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..d277178 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "roles/docker_compose_app"] + path = roles/docker_compose_app + url = ssh://git@ssh.git.jaroszew.ski:2222/ansible/ansible-role-docker-compose-app.git diff --git a/README.md b/README.md new file mode 100644 index 0000000..1ae15b4 --- /dev/null +++ b/README.md @@ -0,0 +1,222 @@ +# Ansible Collection - patrickj.docker_apps + +A comprehensive Ansible collection for deploying and managing Docker Compose applications with enterprise-grade features including backup/restore, health checks, and automated updates. + +## Overview + +A curated collection of Docker Compose roles for self-hosted applications. Each role leverages the `docker_compose_app` base to provide standardized deployment, configuration management, backup procedures, and lifecycle operations across diverse containerized services. + +Consult the [role development documentation](https://git.jaroszew.ski/ansible/ansible-role-docker-compose-app/src/branch/main/README.md) for guidance on creating custom roles, variable conventions, and integration patterns. + +## Features + +- **🏗️ Template-based deployment** with Jinja2 templating +- **🔄 Backup & restore system** (store backups on the Ansible controller + remote host) +- **🏥 Health checks** and deployment verification +- **🏷️ Tag-based execution** for selective operations +- **📦 Multiple deployment modes** (template, file, or inline content) +- **🔧 Flexible directory management** +- **⚙️ Configurable retention policies** +- **🔄 Container update automation** + +## Installation + +### From Git Repository + +```bash +ansible-galaxy collection install https://git.jaroszew.ski/ansible/ansible-collection-docker-apps.git +``` + +### From Local Development + +```bash +# From the collection directory +ansible-galaxy collection install . + +# Or specify path +ansible-galaxy collection install /path/to/patrickj/docker_apps +``` + +## Quick Start + +### 1. Basic Deployment + +Create a playbook using the collection roles: + +```yaml +--- +- name: Deploy media server applications + hosts: localhost + become: yes + vars: + host_root_path: /opt/docker + app_timezone: "America/New_York" + roles: + - patrickj.docker_apps.open-webui + - patrickj.docker_apps.audiobookshelf + - patrickj.docker_apps.paperless-ngx +``` + +### 2. Run the Deployment + +```bash +# Full deployment +ansible-playbook deploy.yml + +# Setup only (directories, templates - no containers) +ansible-playbook --tags setup deploy.yml + +# Deploy specific apps +ansible-playbook deploy.yml --limit jellyfin +``` + +## Available Roles + +### Media Applications +- **`jellyfin`** - Media server for movies, TV shows, music +- **`audiobookshelf`** - Audiobook and podcast server +- **`navidrome`** - Music streaming server + +### Utilities & Monitoring +- **`uptime-kuma`** - Uptime monitoring +- **`speedtest-tracker`** - Internet speed monitoring +- **`glance`** - Dashboard +- **`grafana`** - Data visualization +- **`influxdb`** - Time series database + +### Productivity +- **`paperless-ngx`** - Document management +- **`mealie`** - Recipe manager +- **`linkwarden`** - Bookmark manager +- **`immich`** - Photo management +- **`gitea`** - Git repository server + +### Web Services +- **`open-webui`** - Web interface for LLMs +- **`redlib`** - Alternative Reddit frontend +- **`calibre-web`** - eBook server + +I'll be adding more roles as I incorporate them into my homelab. + +## Usage Examples + +### Tag-Based Operations + +```bash +# Setup applications (do not start containers) +ansible-playbook --tags setup playbook.yml + +# Full deployment (setup + start containers) +ansible-playbook --tags deploy playbook.yml + +# Backup applications +ansible-playbook --tags backup playbook.yml + +# Restore from backup then deploy +ansible-playbook --tags restore,deploy playbook.yml + +# Update container images +ansible-playbook --tags update playbook.yml + +# Backup and update +ansible-playbook --tags backup,update playbook.yml + +# Health check only +ansible-playbook --tags healthcheck playbook.yml +``` + +## Configuration + +### Global Variables + +Set these variables in your playbook or inventory: + +```yaml +# Required +host_root_path: /opt/docker # Base directory for all apps + +# Optional +app_timezone: "UTC" # Container timezone +app_restart_policy: unless-stopped # Docker restart policy +backup_path: /opt/backups # Backup storage location +``` + +### Per-Application Variables + +Each application role accepts specific configuration. See individual role documentation in `roles//README.md`. + +Common patterns: +```yaml +app_name: custom-instance-name # Override default name +{app}_http_port: 8080 # Custom port +{app}_config_path: /custom/path # Override paths +``` + +## Backup & Restore + +### Automated Backups + +```bash +# Create backups for all deployed applications +ansible-playbook --tags backup site.yml + +# Schedule daily backups (cron) +0 2 * * * ansible-playbook --tags backup /path/to/playbook.yml +``` + +### Restore Operations + +```bash +# Restore latest backup +ansible-playbook --tags restore playbook.yml + +# Full restore workflow (setup + restore + deploy) +ansible-playbook --tags setup,restore,deploy playbook.yml + +# Restore specific archive +ansible-playbook --tags restore playbook.yml \ + -e app_restore_archive=/path/to/backup.tar.gz +``` + +## Requirements + +- **Ansible**: >= 2.14 +- **Docker**: >= 20.10 +- **Docker Compose**: >= 2.0 +- **Python**: >= 3.8 + +## Dependencies + +The collection automatically handles dependencies: + +```yaml +collections: + - community.docker # For Docker Compose operations +``` + +## Development + +### Adding New Applications + +1. Create role directory: `roles/new-app/` +2. Follow the template structure from existing roles +3. Configure meta dependency on `docker_compose_app` +4. Add to documentation + +### Testing + +```bash +# Test role syntax +ansible-playbook --syntax-check playbook.yml + +# Test specific role +ansible-playbook --tags setup --check playbook.yml +``` + +## License + +MIT + +## Author + +Patrick Jaroszewski diff --git a/galaxy.yml b/galaxy.yml new file mode 100644 index 0000000..6d27340 --- /dev/null +++ b/galaxy.yml @@ -0,0 +1,22 @@ +--- +namespace: patrickj +name: docker_apps +version: 1.0.0 +readme: README.md +authors: + - Patrick Jaroszewski +description: Ansible collection for deploying Docker Compose applications +license: + - MIT +tags: + - docker + - compose + - containers + - selfhosted + - homelab +dependencies: + "community.docker": ">=1.10.0" +repository: https://git.jaroszew.ski/ansible/ansible-collection-docker-apps +documentation: https://git.jaroszew.ski/ansible/ansible-collection-docker-apps/README.md +homepage: https://git.jaroszew.ski/ansible/ansible-collection-docker-apps +issues: https://git.jaroszew.ski/ansible/ansible-collection-docker-apps/issues \ No newline at end of file diff --git a/roles/audiobookshelf/README.md b/roles/audiobookshelf/README.md new file mode 100644 index 0000000..3703b0f --- /dev/null +++ b/roles/audiobookshelf/README.md @@ -0,0 +1,34 @@ +# Audiobookshelf + +Deploy Audiobookshelf audiobook and podcast server using Docker Compose. + +## Description + +Audiobookshelf is a self-hosted audiobook and podcast server with mobile apps, chapter support, and progress tracking. + +## Variables + +### Required + +| Variable | Description | +|----------|-------------| +| `audiobookshelf_libraries` | List of media library mappings with `host_path` and `container_path` | + +### Optional + +| Variable | Default | Description | +|----------|---------|-------------| +| `audiobookshelf_http_port` | `13378` | Web interface port | +| `audiobookshelf_config_path` | `{{ app_dir }}/config` | Configuration storage | +| `audiobookshelf_metadata_path` | `{{ app_dir }}/metadata` | Metadata cache storage | +| `audiobookshelf_container_version` | `latest` | Audiobookshelf Docker image tag | + +## Example + +```yaml +- role: patrickj.docker_apps.audiobookshelf + vars: + audiobookshelf_libraries: + - host_path: /storage/audiobooks + container_path: /audiobooks +``` \ No newline at end of file diff --git a/roles/audiobookshelf/defaults/main.yaml b/roles/audiobookshelf/defaults/main.yaml new file mode 100644 index 0000000..306a991 --- /dev/null +++ b/roles/audiobookshelf/defaults/main.yaml @@ -0,0 +1,14 @@ +--- +app_role_name: audiobookshelf +app_name: audiobookshelf + +# Container configuration +audiobookshelf_container_name: "{{ app_name | default('audiobookshelf') }}" +audiobookshelf_container_version: latest +audiobookshelf_restart_policy: "{{ app_restart_policy }}" +audiobookshelf_http_port: 13378 + +# Volume paths +audiobookshelf_config_path: "{{ app_dir }}/config" +audiobookshelf_metadata_path: "{{ app_dir }}/metadata" + diff --git a/roles/audiobookshelf/meta/main.yaml b/roles/audiobookshelf/meta/main.yaml new file mode 100644 index 0000000..bb85a77 --- /dev/null +++ b/roles/audiobookshelf/meta/main.yaml @@ -0,0 +1,16 @@ +--- +galaxy_info: + author: Patrick Jaroszewski + description: Deploy Audiobookshelf with Docker Compose + license: MIT + +dependencies: + - role: patrickj.docker_apps.docker_compose_app + vars: + app_role_name: audiobookshelf + app_subdirectories: + - "{{ audiobookshelf_config_path }}" + - "{{ audiobookshelf_metadata_path }}" + app_backup_subdirectories: + - "{{ audiobookshelf_config_path }}" + - "{{ audiobookshelf_metadata_path }}" diff --git a/roles/audiobookshelf/templates/compose.yml.j2 b/roles/audiobookshelf/templates/compose.yml.j2 new file mode 100644 index 0000000..be8e5b7 --- /dev/null +++ b/roles/audiobookshelf/templates/compose.yml.j2 @@ -0,0 +1,14 @@ +--- +services: + audiobookshelf: + image: "ghcr.io/advplyr/audiobookshelf:{{ audiobookshelf_container_version }}" + container_name: "{{ audiobookshelf_container_name }}" + restart: "{{ audiobookshelf_restart_policy }}" + ports: + - "{{ audiobookshelf_http_port }}:80" + volumes: + - "{{ audiobookshelf_config_path }}:/config" + - "{{ audiobookshelf_metadata_path }}:/metadata" +{% for item in audiobookshelf_libraries %} + - "{{ item.host_path }}:{{ item.container_path }}" +{% endfor %} \ No newline at end of file diff --git a/roles/calibre-web/README.md b/roles/calibre-web/README.md new file mode 100644 index 0000000..1adadfe --- /dev/null +++ b/roles/calibre-web/README.md @@ -0,0 +1,36 @@ +# Calibre-Web + +Deploy Calibre-Web ebook server using Docker Compose. + +## Description + +Calibre-Web is a web app providing a clean interface for browsing, reading and downloading eBooks from a Calibre database. + +## Variables + +### Required + +| Variable | Description | +|----------|-------------| +| `calibre_web_library_path` | Path to existing Calibre library directory | + +### Optional + +| Variable | Default | Description | +|----------|---------|-------------| +| `calibre_web_http_port` | `8083` | Web interface port | +| `calibre_web_config_path` | `{{ app_dir }}/config` | Configuration storage | +| `calibre_web_container_version` | `latest` | Calibre-Web Docker image tag | +| `calibre_web_uid` | `{{ app_uid }}` | User ID for file permissions | +| `calibre_web_gid` | `{{ app_gid }}` | Group ID for file permissions | +| `calibre_web_timezone` | `{{ app_timezone }}` | Container timezone | +| `calibre_web_docker_mods` | `linuxserver/mods:universal-calibre` | Adds ebook conversion capability (x86-64 only) | +| `calibre_web_oauth_relax` | `1` | Allows Google OAuth to work. Set to `0` to disable | + +## Example + +```yaml +- role: patrickj.docker_apps.calibre-web + vars: + calibre_web_library_path: /storage/ebooks/calibre-library +``` diff --git a/roles/calibre-web/defaults/main.yml b/roles/calibre-web/defaults/main.yml new file mode 100644 index 0000000..b27e517 --- /dev/null +++ b/roles/calibre-web/defaults/main.yml @@ -0,0 +1,22 @@ +--- +app_name: calibre-web + +# Container configuration +calibre_web_container_name: "{{ app_name | default('calibre-web') }}" +calibre_web_container_version: latest +calibre_web_restart_policy: "{{ app_restart_policy }}" +calibre_web_uid: "{{ app_uid }}" +calibre_web_gid: "{{ app_gid }}" + +# Network configuration +calibre_web_http_port: 8083 + +# Volume paths +calibre_web_config_path: "{{ app_dir }}/config" +calibre_web_library_path: "{{ app_dir }}/library" + +# App-specific configuration +calibre_web_timezone: "{{ app_timezone | default('Etc/UTC') }}" +calibre_web_docker_mods: "linuxserver/mods:universal-calibre" +calibre_web_oauth_relax: "1" + diff --git a/roles/calibre-web/meta/main.yml b/roles/calibre-web/meta/main.yml new file mode 100644 index 0000000..de352c4 --- /dev/null +++ b/roles/calibre-web/meta/main.yml @@ -0,0 +1,15 @@ +--- +galaxy_info: + author: Patrick Jaroszewski + description: Deploy Calibre-Web with Docker Compose + license: MIT + +dependencies: + - role: patrickj.docker_apps.docker_compose_app + vars: + app_role_name: calibre-web + app_subdirectories: + - "{{ calibre_web_config_path }}" + - "{{ calibre_web_books_path }}" + app_backup_subdirectories: + - "{{ calibre_web_config_path }}" \ No newline at end of file diff --git a/roles/calibre-web/templates/compose.yml.j2 b/roles/calibre-web/templates/compose.yml.j2 new file mode 100644 index 0000000..df1c260 --- /dev/null +++ b/roles/calibre-web/templates/compose.yml.j2 @@ -0,0 +1,17 @@ +--- +services: + calibre-web: + image: "lscr.io/linuxserver/calibre-web:{{ calibre_web_container_version }}" + container_name: "{{ calibre_web_container_name }}" + restart: "{{ calibre_web_restart_policy }}" + ports: + - "{{ calibre_web_http_port }}:8083" + environment: + - PUID={{ calibre_web_uid }} + - PGID={{ calibre_web_gid }} + - TZ={{ calibre_web_timezone }} + - DOCKER_MODS={{ calibre_web_docker_mods }} + - OAUTHLIB_RELAX_TOKEN_SCOPE={{ calibre_web_oauth_relax }} + volumes: + - "{{ calibre_web_config_path }}:/config" + - "{{ calibre_web_library_path }}:/books" \ No newline at end of file diff --git a/roles/docker_compose_app b/roles/docker_compose_app new file mode 160000 index 0000000..4211fad --- /dev/null +++ b/roles/docker_compose_app @@ -0,0 +1 @@ +Subproject commit 4211fad10cc0c169317836b3db99274546aa7cd6 diff --git a/roles/gitea/.gitignore b/roles/gitea/.gitignore new file mode 100644 index 0000000..5c199eb --- /dev/null +++ b/roles/gitea/.gitignore @@ -0,0 +1,3 @@ +# ---> Ansible +*.retry + diff --git a/roles/gitea/LICENSE b/roles/gitea/LICENSE new file mode 100644 index 0000000..7677f57 --- /dev/null +++ b/roles/gitea/LICENSE @@ -0,0 +1,18 @@ +MIT License + +Copyright (c) 2025 ansible + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/roles/gitea/README.md b/roles/gitea/README.md new file mode 100644 index 0000000..e3a010b --- /dev/null +++ b/roles/gitea/README.md @@ -0,0 +1,512 @@ +# Ansible Role: gitea + +This Ansible role automates the deployment of Gitea - a self-hosted Git service, using Docker Compose. It provides a comprehensive setup with support for runners, fail2ban integration, and extensive configuration options. This role uses the centralized `docker_compose_app` role for standardized deployment, backup/restore, and health checks. + +## Features +- **Standardized deployment** - Uses `docker_compose_app` for consistent deployment patterns +- **Docker-based deployment** - Runs Gitea in a rootless container for enhanced security +- **Complete security configuration** - Password policies, 2FA, fail2ban integration, etc. +- **Enterprise backup/restore** - Dual storage system with configurable retention policies +- **Health monitoring** - Automated health checks and deployment verification +- **Gitea Actions support** - Supports creating multiple runners running in rootless containers using DinD (Docker-in-Docker) +- **User management** - Automated user creation and removal +- **Extensible** - Extra config variables for any Gitea setting not explicitly defined + + +## Requirements + +- Docker and Docker Compose installed (handled by `geerlingguy.docker` dependency) +- jmespath Python library (install with: `pip install jmespath`) + +## Sample Usage in a Playbook +The following playbook was tested on the latest Debian 12, it should work on Ubuntu as well. + +```yaml +# Installs Docker and Compose if not available +- name: Install Gitea using Docker Compose + hosts: git.example.com + become: yes + roles: + - role: gitea + vars: + # The default vars assume you are running a reverse proxy that handles HTTPS + app_name: gitea-prod + gitea_fqdn: 'git.example.com' + gitea_root_url: 'https://git.example.com' + gitea_protocol: http + gitea_http_listen: '0.0.0.0' +``` + +## Backup and Restore + +This role uses the standardized backup/restore system from `docker_compose_app`, providing dual storage (controller + remote host) with configurable retention policies. + +### Creating a Backup + +```yaml +- name: Create Gitea backup + hosts: git.example.com + become: yes + roles: + - role: gitea + vars: + app_name: gitea-prod + app_backup: true + app_backup_retention_days_controller: 90 + app_backup_retention_days_remote: 7 +``` + +### Restoring from Backup + +Restore from latest backup: +```yaml +- name: Restore Gitea from latest backup + hosts: git.example.com + become: yes + roles: + - role: gitea + vars: + app_name: gitea-prod + app_restore: true + app_restore_source: controller # or 'remote' +``` + +Restore from specific archive: +```yaml +- name: Restore Gitea from specific backup + hosts: git.example.com + become: yes + roles: + - role: gitea + vars: + app_name: gitea-prod + app_restore: true + app_restore_source: controller + app_restore_archive: /path/to/gitea-20240315-120000.tar.gz +``` + +> [!WARNING] +> Restore operations will **completely replace** all existing Gitea data. + +# Variables + +## Standardized Variables + +This role uses the standardized `docker_compose_app` pattern. Core variables include: + +| Variable Name | Default Value | Description | +|---------------|---------------|-------------| +| `app_name` | Required | Unique application instance name | +| `app_dir` | `{{ host_root_path }}/{{ app_name }}` | Application directory | +| `app_uid` | `{{ ansible_facts.user_uid }}` | File ownership UID | +| `app_gid` | `{{ ansible_facts.user_gid }}` | File ownership GID | +| `app_permission_mode` | `"0640"` | File permission mode | +| `app_restart_policy` | `"unless-stopped"` | Docker restart policy | +| `app_subdirectories` | `['data', 'config', 'log']` | Directories created in app_dir | + +## Container Configuration + +| Variable Name | Default Value | Description | +|---------------|---------------|-------------| +| `gitea_container_name` | `{{ app_name \| default('gitea') }}` | Docker container name for the Gitea service | +| `gitea_container_version` | `latest-rootless` | Docker image tag for the Gitea container (e.g., `latest-rootless`, `1.21.5-rootless`) | +| `gitea_restart_policy` | `{{ app_restart_policy }}` | Container restart policy (unless-stopped, always, on-failure, no) | +| `gitea_http_port` | `3000` | Host port that maps to Gitea's web interface | +| `gitea_ssh_port` | `2222` | Host port that maps to Gitea's SSH server | +| `gitea_data_path` | `{{ app_dir }}/data` | Full path to Gitea data directory on the host | +| `gitea_config_path` | `{{ app_dir }}/config` | Full path to Gitea configuration directory on the host | +| `gitea_log_path` | `{{ app_dir }}/log` | Full path to Gitea log directory on the host | + +## Initial User Setup + +| Variable Name | Option | Description | +|---------------|---------------|-------------| +| `gitea_users` | `[]` | List of user objects to create or remove in Gitea | +| | `username` | Login username for the Gitea user | +| | `email` | Email address for the Gitea user | +| | `password` | Password for the user (should be encrypted with Ansible Vault) | +| | `must_change_password` | If `true`, user must change password on first login | +| | `admin` | If `true`, user is created with administrator privileges | +| | `state` | Set to `present` to create user or `absent` to delete user | + +## Backup and Restore + +Backup and restore functionality is provided by the `docker_compose_app` role. Use the standard backup/restore variables: + +| Variable Name | Default Value | Description | +|---------------|---------------|-------------| +| `app_backup` | `false` | Enable backup functionality | +| `app_backup_subdirectories` | `['data', 'config']` | Directories to backup (relative to app_dir) | +| `app_restore` | `false` | Enable restore functionality | +| `app_restore_source` | `controller` | Restore source (`controller` or `remote`) | +| `app_restore_archive` | - | Specific archive file to restore (overrides latest) | +| `app_backup_retention_days_controller` | `90` | Controller backup retention in days | +| `app_backup_retention_days_remote` | `7` | Remote backup retention in days | + +## Runners Configuration + +| Variable Name | Default Value | Description | +|---------------|---------------|-------------| +| `gitea_runner_global_registration_token` | `""` | Global registration token that applies to all runners (can be found in Gitea admin settings) | +| `gitea_runner_image` | `gitea/act_runner` | Docker image for Gitea Actions runners | +| `gitea_runner_container_version` | `latest-dind-rootless` | Docker image tag for runner containers | +| `gitea_runner_restart_policy` | `{{ gitea_restart_policy }}` | Container restart policy for runners | +| `gitea_runners` | `{}` | Dictionary of Gitea Actions runner configurations | + +### Runner Configuration Options + +Each runner in the `gitea_runners` dictionary supports these options: + +| Option | Description | +|--------|-------------| +| `name` | Unique name identifier for the runner | +| `data_mount` | Path relative to app_dir for persistent runner data storage | +| `config_file_mount` | Path relative to app_dir for runner configuration files | +| `cache_enabled` | If `true`, enables action cache functionality (for `actions/cache` support) | +| `cache_port` | Port number on the host for accessing the runner's cache service (must be unique per runner) | +| `registration_token` | Runner-specific registration token (optional if `gitea_runner_global_registration_token` is set) | + +## Fail2Ban Security + +| Variable Name | Default Value | Description | +|---------------|---------------|-------------| +| `gitea_fail2ban_enabled` | `false` | Enable fail2ban protection to automatically ban IPs after failed login attempts | +| `gitea_fail2ban_jail_maxretry` | `10` | Number of failed login attempts before an IP is banned | +| `gitea_fail2ban_jail_findtime` | `3600` | Time window in seconds during which failed attempts are counted | +| `gitea_fail2ban_jail_bantime` | `900` | Duration in seconds that an IP remains banned | +| `gitea_fail2ban_jail_action` | `iptables-allports[chain="FORWARD"]` | Fail2ban action/rule to execute when banning an IP | + +## Overall Configuration + +| Variable Name | Default Value | Description | +|---------------|---------------|-------------| +| `gitea_app_name` | `Gitea` | Application name displayed throughout the Gitea interface | +| `gitea_user` | `gitea` | System username that runs the Gitea process inside the container | +| `gitea_run_mode` | `prod` | Application run mode: `prod` (production), `dev` (development), or `test` | +| `gitea_fqdn` | `localhost` | Fully qualified domain name for accessing the Gitea instance | + +## Server Configuration + +| Variable Name | Default Value | Description | +|---------------|---------------|-------------| +| `gitea_protocol` | `http` | Protocol for the root URL: `http` or `https` | +| `gitea_http_domain` | `{{ gitea_fqdn }}` | Domain name used in HTTP headers and clone URLs | +| `gitea_root_url` | `{{ gitea_protocol }}://{{ gitea_fqdn }}` | Full public URL where Gitea is accessible | +| `gitea_http_listen` | `127.0.0.1` | IP address the HTTP server binds to; use `0.0.0.0` to allow external access | +| `gitea_internal_http_port` | `3000` | Port number Gitea listens on inside the container | +| `gitea_internal_ssh_port` | `2222` | Port number SSH server listens on inside the container | +| `gitea_ssh_listen` | `0.0.0.0` | IP address the SSH server binds to | +| `gitea_ssh_domain` | `{{ gitea_http_domain }}` | Domain name shown in SSH clone URLs | +| `gitea_start_ssh` | `true` | Enable or disable the built-in SSH server | +| `gitea_landing_page` | `home` | Default page for non-authenticated users: `home`, `explore`, `organizations`, or `login` | +| `gitea_server_extra_config` | `{}` | Dictionary of additional server configuration options not covered above | + +## Security Configuration + +| Variable Name | Default Value | Description | +|---------------|---------------|-------------| +| `gitea_secret_key` | | Secret key used for encrypting cookies and tokens (generate with `gitea generate secret SECRET_KEY`) | +| `gitea_internal_token` | | Token used for internal API authentication (generate with `gitea generate secret INTERNAL_TOKEN`) | +| `gitea_install_lock` | `false` | If `true`, prevents the installation wizard from running | +| `gitea_disable_git_hooks` | `true` | If `true`, disables custom Git hooks for security | +| `gitea_disable_webhooks` | `false` | If `true`, disables all webhook functionality | +| `gitea_reverse_proxy_limit` | `1` | Number of reverse proxy hops to trust for obtaining the real client IP | +| `gitea_reverse_proxy_trusted_proxies` | `127.0.0.0/8,::1/128` | Comma-separated list of trusted proxy IP addresses or CIDR ranges | +| `gitea_password_complexity` | `off` | Password requirements: `off`, or comma-separated list of `lower`, `upper`, `digit`, `spec` | +| `gitea_password_min_length` | `8` | Minimum number of characters required for passwords | +| `gitea_password_check_pwn` | `false` | If `true`, checks passwords against the HaveIBeenPwned breach database | +| `gitea_2fa` | | Two-factor authentication setting; set to `enforced` to require 2FA for all users | +| `gitea_login_remember_days` | `31` | Number of days to keep users logged in when they select "Remember Me" | +| `gitea_cookie_remember_name` | `gitea_incredible` | Name of the cookie used for "Remember Me" functionality | +| `gitea_security_extra_config` | `{}` | Dictionary of additional security configuration options | + +## Service Configuration + +| Variable Name | Default Value | Description | +|---------------|---------------|-------------| +| `gitea_disable_registration` | `false` | If `true`, disables new user registration entirely | +| `gitea_register_email_confirm` | `false` | If `true`, requires email verification to complete registration | +| `gitea_register_manual_confirm` | `false` | If `true`, requires admin approval for new registrations | +| `gitea_require_signin_view` | `false` | If `true`, requires users to sign in to view any content | +| `gitea_enable_notify_mail` | `false` | If `true`, enables email notifications for events | +| `gitea_enable_captcha` | `false` | If `true`, shows CAPTCHA on registration form | +| `gitea_captcha_type` | `image` | Type of CAPTCHA to use: `image`, `recaptcha`, `hcaptcha`, or `mcaptcha` | +| `gitea_show_registration_button` | `true` | If `true`, displays registration button on login page | +| `gitea_default_keep_email_private` | `false` | If `true`, new users have email privacy enabled by default | +| `gitea_default_allow_create_organization` | `true` | If `true`, new users can create organizations | +| `gitea_default_user_is_restricted` | `false` | If `true`, new users are created with restricted permissions | +| `gitea_default_user_visibility` | `public` | Default visibility for new user profiles: `public`, `limited`, or `private` | +| `gitea_default_org_visibility` | `public` | Default visibility for new organizations: `public`, `limited`, or `private` | +| `gitea_default_org_member_visible` | `false` | If `true`, organization members are visible by default | +| `gitea_allow_only_internal_registration` | `false` | If `true`, only allows registration via internal authentication | +| `gitea_allow_only_external_registration` | `false` | If `true`, only allows registration via external authentication sources | +| `gitea_email_domain_allowlist` | | Comma-separated list of allowed email domains for registration | +| `gitea_email_domain_blocklist` | | Comma-separated list of blocked email domains for registration | +| `gitea_no_reply_address` | | No-reply email address used for system-generated emails | +| `gitea_enable_user_heatmap` | `true` | If `true`, displays user activity heatmap on profiles | +| `gitea_enable_timetracking` | `true` | If `true`, enables time tracking features for issues | +| `gitea_auto_watch_new_repos` | `true` | If `true`, users automatically watch repositories they create | +| `gitea_auto_watch_on_changes` | `false` | If `true`, users automatically watch repositories they contribute to | +| `gitea_show_milestones_dashboard_page` | `true` | If `true`, shows milestones on the dashboard page | +| `gitea_service_extra_config` | `{}` | Dictionary of additional service configuration options | + +## Repository Configuration + +| Variable Name | Default Value | Description | +|---------------|---------------|-------------| +| `gitea_repo_force_private` | `false` | If `true`, forces all new repositories to be private | +| `gitea_repo_default_private` | `last` | Default visibility for new repositories: `public`, `private`, or `last` (uses last selection) | +| `gitea_repo_default_push_create_private` | `true` | If `true`, repositories created via push are private by default | +| `gitea_repo_preferred_licenses` | `Apache License 2.0,MIT License` | Comma-separated list of license names to show at the top when creating repositories | +| `gitea_repo_disable_http_git` | `false` | If `true`, disables HTTP(S) Git operations (clone, push, pull) | +| `gitea_repo_disable_migrations` | `false` | If `true`, disables repository migration feature | +| `gitea_repo_disable_stars` | `false` | If `true`, disables the ability to star repositories | +| `gitea_repo_default_branch` | `main` | Default branch name for new repositories | +| `gitea_repository_extra_config` | `{}` | Dictionary of additional repository configuration options | + +## CORS Configuration + +| Variable Name | Default Value | Description | +|---------------|---------------|-------------| +| `gitea_cors_enabled` | `false` | Enable Cross-Origin Resource Sharing (CORS) headers | +| `gitea_cors_allowed_domains` | `*` | Domains allowed to make cross-origin requests; `*` allows all | +| `gitea_cors_allowed_methods` | `GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS` | HTTP methods allowed for cross-origin requests | +| `gitea_cors_max_age` | `10m` | Duration to cache preflight CORS request results | +| `gitea_cors_allowed_credentials` | `false` | If `true`, allows credentials (cookies, auth headers) in cross-origin requests | +| `gitea_cors_headers` | `Content-Type,User-Agent` | Custom headers allowed in cross-origin requests | +| `gitea_cors_x_frame_options` | `SAMEORIGIN` | X-Frame-Options header value: `SAMEORIGIN`, `DENY`, or `ALLOW-FROM uri` | + +## UI Configuration + +| Variable Name | Default Value | Description | +|---------------|---------------|-------------| +| `gitea_ui_default_theme` | `gitea-auto` | Default theme for the UI; `gitea-auto` switches based on system preference | +| `gitea_ui_themes` | | Comma-separated list of themes to make available; if empty, all themes are available | +| `gitea_ui_show_user_email` | `true` | If `true`, displays user email addresses in the UI (respects privacy settings) | +| `gitea_ui_show_full_name` | `false` | If `true`, displays full names instead of usernames where applicable | +| `gitea_ui_extra_config` | `{}` | Dictionary of additional UI configuration options | + +## UI Meta Configuration + +| Variable Name | Default Value | Description | +|---------------|---------------|-------------| +| `gitea_ui_meta_author` | `Gitea - Git with a cup of tea` | Content for the HTML meta author tag | +| `gitea_ui_meta_description` | `Gitea (Git with a cup of tea) is a painless self-hosted Git service written in Go` | Content for the HTML meta description tag (used by search engines) | +| `gitea_ui_meta_keywords` | `go,git,self-hosted,gitea` | Comma-separated keywords for the HTML meta keywords tag | + +## Indexer Configuration + +| Variable Name | Default Value | Description | +|---------------|---------------|-------------| +| `gitea_issue_indexer_type` | `bleve` | Issue search indexer engine: `bleve` (built-in), `db` (database), `elasticsearch`, or `meilisearch` | +| `gitea_issue_indexer_connection_string` | | Connection string for external indexer (required for `elasticsearch` or `meilisearch`) | +| `gitea_issue_indexer_name` | `gitea_issues` | Index name used in external indexer services | +| `gitea_issue_indexer_path` | `indexers/issues.bleve` | File path for bleve index storage (relative to Gitea data directory) | +| `gitea_repo_indexer_enabled` | `false` | Enable code search functionality (WARNING: requires significant disk space, ~6x repository size) | +| `gitea_repo_indexer_repo_types` | `sources,forks,mirrors,templates` | Types of repositories to include in code search index | +| `gitea_repo_indexer_type` | `bleve` | Code search indexer engine: `bleve` (built-in) or `elasticsearch` | +| `gitea_repo_indexer_path` | `indexers/repos.bleve` | File path for bleve code index storage (relative to Gitea data directory) | +| `gitea_repo_indexer_connection_string` | | Connection string for elasticsearch code indexer | +| `gitea_repo_indexer_name` | `gitea_codes` | Index name for code search in elasticsearch | +| `gitea_repo_indexer_include` | | Glob patterns for files to include in code search (e.g., `**.txt,**.md`); empty includes all | +| `gitea_repo_indexer_exclude` | | Glob patterns for files to exclude from code search (takes precedence over includes) | +| `gitea_repo_indexer_exclude_vendored` | `true` | If `true`, excludes vendored/third-party code from indexing | +| `gitea_repo_indexer_max_file_size` | `1048576` | Maximum file size in bytes to index (default 1 MiB); larger files are skipped | +| `gitea_indexer_startup_timeout` | `30s` | Maximum time to wait for indexer initialization; `-1` disables timeout | +| `gitea_indexer_extra_config` | `{}` | Dictionary of additional indexer configuration options | + +## Packages Configuration + +| Variable Name | Default Value | Description | +|---------------|---------------|-------------| +| `gitea_packages_enabled` | `true` | Enable package registry functionality (for npm, Maven, Docker, etc.) | +| `gitea_packages_extra_config` | `{}` | Dictionary of additional package registry configuration options | + +## Actions Configuration + +| Variable Name | Default Value | Description | +|---------------|---------------|-------------| +| `gitea_actions_enabled` | `false` | Enable Gitea Actions (CI/CD pipeline functionality) | +| `gitea_actions_default_actions_url` | `github` | Default URL for Actions marketplace; `github`, `self`, or custom URL | +| `gitea_actions_extra_config` | `{}` | Dictionary of additional Actions configuration options | + +## Logging Configuration + +| Variable Name | Default Value | Description | +|---------------|---------------|-------------| +| `gitea_log_root_path` | | Root directory for log files; required if `gitea_log_mode` is `file` or for fail2ban integration | +| `gitea_log_mode` | `console` | Log output destination: `console`, `file`, `conn` (network), or `smtp` (email) | +| `gitea_log_level` | `Info` | Logging verbosity level: `Trace`, `Debug`, `Info`, `Warn`, `Error`, or `Critical` | +| `gitea_enable_ssh_log` | `false` | If `true`, enables detailed logging for SSH operations | +| `gitea_log_extra_config` | `{}` | Dictionary of additional logging configuration options | + +## Mailer Configuration + +| Variable Name | Default Value | Description | +|---------------|---------------|-------------| +| `gitea_mailer_enabled` | `false` | Enable email functionality for notifications and registration | +| `gitea_mailer_protocol` | | Mail protocol: `smtp`, `smtps`, `smtp+starttls`, `smtp+unix`, `sendmail`, or `dummy` (logs only) | +| `gitea_mailer_smtp_addr` | | Hostname or IP address of the SMTP server | +| `gitea_mailer_smtp_port` | | Port number for the SMTP server (typically 25, 465, or 587) | +| `gitea_mailer_user` | | Username for SMTP authentication | +| `gitea_mailer_password` | | Password for SMTP authentication (should be encrypted with Ansible Vault) | +| `gitea_mailer_from` | `noreply@{{ gitea_http_domain }}` | Email address used as the sender for all outgoing emails | +| `gitea_mailer_subject_prefix` | | Text prepended to all email subject lines | +| `gitea_mailer_send_as_plain_text` | `false` | If `true`, sends emails as plain text instead of HTML | +| `gitea_mailer_extra_config` | `{}` | Dictionary of additional mailer configuration options | + +## Mirror Configuration + +| Variable Name | Default Value | Description | +|---------------|---------------|-------------| +| `gitea_mirror_enabled` | `true` | Enable repository mirroring functionality | +| `gitea_mirror_disable_new_pull` | `false` | If `true`, prevents creation of new pull mirrors (mirroring from external repos) | +| `gitea_mirror_disable_new_push` | `false` | If `true`, prevents creation of new push mirrors (mirroring to external repos) | +| `gitea_mirror_default_interval` | `8h` | Default synchronization interval for new mirrors | +| `gitea_mirror_min_interval` | `10m` | Minimum allowed interval between mirror synchronizations | +| `gitea_mirror_extra_config` | `{}` | Dictionary of additional mirror configuration options | + +## Metrics Configuration + +| Variable Name | Default Value | Description | +|---------------|---------------|-------------| +| `gitea_metrics_enabled` | `false` | Enable Prometheus metrics endpoint at `/metrics` | +| `gitea_metrics_token` | | Authentication token required to access the metrics endpoint | +| `gitea_metrics_extra_config` | `{}` | Dictionary of additional metrics configuration options | + +## API Configuration + +| Variable Name | Default Value | Description | +|---------------|---------------|-------------| +| `gitea_api_enable_swagger` | `true` | Enable Swagger UI for API documentation at `/api/swagger` | +| `gitea_api_max_response_items` | `50` | Maximum number of items returned in a single API response page | +| `gitea_api_default_paging_num` | `30` | Default number of items per page for API responses | +| `gitea_api_extra_config` | `{}` | Dictionary of additional API configuration options | + +## OAuth2 Configuration + +| Variable Name | Default Value | Description | +|---------------|---------------|-------------| +| `gitea_oauth2_enabled` | `true` | Enable OAuth2 provider functionality (allows other apps to use Gitea for authentication) | +| `gitea_oauth2_access_token_expiration_time` | `3600` | OAuth2 access token lifetime in seconds (default 1 hour) | +| `gitea_oauth2_refresh_token_expiration_time` | `730` | OAuth2 refresh token lifetime in hours (default ~30 days) | +| `gitea_oauth2_jwt_signing_algorithm` | `RS256` | Algorithm used to sign JWT tokens: `RS256`, `RS384`, `RS512`, `ES256`, `ES384`, or `ES512` | +| `gitea_oauth2_jwt_secret` | | Secret key for signing JWT tokens (auto-generated if not provided) | +| `gitea_oauth2_extra_config` | `{}` | Dictionary of additional OAuth2 configuration options | + +## Other Configuration + +| Variable Name | Default Value | Description | +|---------------|---------------|-------------| +| `gitea_show_footer_version` | `true` | If `true`, displays Gitea version number in the page footer | +| `gitea_show_footer_template_load_time` | `true` | If `true`, displays page generation time in the footer | +| `gitea_enable_sitemap` | `true` | If `true`, generates XML sitemap at `/sitemap.xml` for search engines | +| `gitea_enable_feed` | `true` | If `true`, enables RSS and Atom feeds for repositories and users | +| `gitea_other_extra_config` | `{}` | Dictionary of additional miscellaneous configuration options | + +## Additional Configuration + +> [!NOTE] +> Not all variables that Gitea supports have been implemented yet. Read the following secion to add additional configuration options. + +| Variable Name | Default Value | Description | +|---------------|---------------|-------------| +| `gitea_extra_config` | `{}` | Dictionary of any additional environment variables to pass to Gitea; consult the [Gitea configuration cheat sheet](https://docs.gitea.com/administration/config-cheat-sheet) for available options | + +### Example: Using `gitea_extra_config` + +```yaml +gitea_extra_config: + GITEA__webhook__ALLOWED_HOST_LIST: "external" + GITEA__webhook__SKIP_TLS_VERIFY: "false" + GITEA__cron__ENABLED: "true" +``` + +## Understanding Extra Configuration Variables + +Many configuration sections include an `*_extra_config` variable (e.g., `gitea_server_extra_config`, `gitea_security_extra_config`, etc.). These variables allow you to add additional Gitea configuration options that aren't explicitly defined as separate variables in this role. + +### How Extra Config Variables Work + +Each `*_extra_config` variable is a dictionary that gets merged into the corresponding section of the Gitea configuration. The role converts these dictionaries into the appropriate environment variable format that Gitea expects. + +### Naming Convention + +Gitea uses environment variables with a specific naming pattern: +``` +GITEA__
__ +``` + +When you use an `*_extra_config` variable, the role automatically handles the section name for you. You only need to provide the key-value pairs. + +### Available Extra Config Variables + +| Variable | Maps to Gitea Section | +|----------|----------------------| +| `gitea_server_extra_config` | `[server]` | +| `gitea_security_extra_config` | `[security]` | +| `gitea_service_extra_config` | `[service]` | +| `gitea_repository_extra_config` | `[repository]` | +| `gitea_ui_extra_config` | `[ui]` | +| `gitea_indexer_extra_config` | `[indexer]` | +| `gitea_packages_extra_config` | `[packages]` | +| `gitea_actions_extra_config` | `[actions]` | +| `gitea_log_extra_config` | `[log]` | +| `gitea_mailer_extra_config` | `[mailer]` | +| `gitea_mirror_extra_config` | `[mirror]` | +| `gitea_metrics_extra_config` | `[metrics]` | +| `gitea_api_extra_config` | `[api]` | +| `gitea_oauth2_extra_config` | `[oauth2]` | +| `gitea_other_extra_config` | `[other]` | + +### Examples + +#### Adding Server Configuration + +```yaml +gitea_server_extra_config: + OFFLINE_MODE: true + DISABLE_ROUTER_LOG: false + ENABLE_GZIP: true +``` + +This translates to these environment variables: +``` +GITEA__server__OFFLINE_MODE=true +GITEA__server__DISABLE_ROUTER_LOG=false +GITEA__server__ENABLE_GZIP=true +``` + +#### Using gitea_extra_config for Any Section + +If you need to configure a section that doesn't have a dedicated `*_extra_config` variable, or want to set options across multiple sections, use `gitea_extra_config`: + +```yaml +gitea_extra_config: + # Webhook configuration + GITEA__webhook__ALLOWED_HOST_LIST: "external" + GITEA__webhook__SKIP_TLS_VERIFY: "false" + + # Cron configuration + GITEA__cron__ENABLED: "true" + GITEA__cron_DOT_update_checker__ENABLED: "false" + + # Git configuration + GITEA__git__DISABLE_DIFF_HIGHLIGHT: "false" + GITEA__git__timeout__DEFAULT: "360" +``` + +### Finding Available Options + +For a complete list of available configuration options, refer to the official [Gitea Configuration Cheat Sheet](https://docs.gitea.com/administration/config-cheat-sheet). + +### Important Notes + +1. **Values**: All values in `*_extra_config` dictionaries should be strings, numbers, or booleans. + +2. **Priority**: Variables defined in `*_extra_config` can override the explicitly defined role variables if they conflict. + +3. **Validation**: The role does not validate extra configuration options. Ensure you're using valid Gitea configuration keys by consulting the official documentation. + +# License +MIT License diff --git a/roles/gitea/defaults/main.yaml b/roles/gitea/defaults/main.yaml new file mode 100644 index 0000000..28682c6 --- /dev/null +++ b/roles/gitea/defaults/main.yaml @@ -0,0 +1,253 @@ +--- +app_name: gitea + +# User configuration. Note that if using rootless, the uid and gid must be 1000 +gitea_uid: 1000 +gitea_gid: 1000 + +# Container configuration +gitea_container_name: "{{ app_name | default('gitea') }}" +gitea_container_version: latest-rootless +gitea_restart_policy: "{{ app_restart_policy }}" + +# Network configuration +gitea_http_port: 3000 +gitea_ssh_port: 2222 + +# Volume paths +gitea_data_path: "{{ app_dir }}/data" +gitea_config_path: "{{ app_dir }}/config" +gitea_log_path: "{{ app_dir }}/log" +gitea_users: [] +# gitea_users: +# - username: someuser + # email: user@example.com + # password: somepass # Should be a vault secret + # admin: true + # must_change_password: true + # state: present # `absent` if you want to delete this user + +gitea_runners: [] + # - name: + # data_mount: ./runners/main_runner + # registration_token: + # cache_enabled: true + # cache_port: # Use a unique port + +gitea_runner_global_registration_token: "" +gitea_runner_image: gitea/act_runner +gitea_runner_container_version: latest-dind-rootless +gitea_runner_restart_policy: "{{ gitea_restart_policy }}" + +# Fail2Ban vars +gitea_fail2ban_enabled: false +gitea_fail2ban_jail_maxretry: 10 +gitea_fail2ban_jail_findtime: 3600 +gitea_fail2ban_jail_bantime: 900 +gitea_fail2ban_jail_action: 'iptables-allports[chain="FORWARD"]' + +# App-specific configuration + +# Overall (DEFAULT) +# -> https://docs.gitea.io/en-us/config-cheat-sheet/#overall-default +gitea_app_name: "Gitea" +gitea_user: "gitea" +gitea_run_mode: "prod" +gitea_fqdn: "localhost" + +# Repository Configuration +# -> +gitea_repo_force_private: false +gitea_repo_default_private: 'last' +gitea_repo_default_push_create_private: true +gitea_repo_preferred_licenses: 'Apache License 2.0,MIT License' +gitea_repo_disable_http_git: false +gitea_repo_disable_migrations: false +gitea_repo_disable_stars: false +gitea_repo_default_branch: 'main' +gitea_repository_extra_config: {} + + +# CORS Configuration +# -> +gitea_cors_enabled: false +gitea_cors_allowed_domains: '*' +gitea_cors_allowed_methods: 'GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS' +gitea_cors_max_age: '10m' +gitea_cors_allowed_credentials: false +gitea_cors_headers: 'Content-Type,User-Agent' +gitea_cors_x_frame_options: 'SAMEORIGIN' + +# UI Configuration +# -> +gitea_ui_default_theme: "gitea-auto" +gitea_ui_themes: "" +gitea_ui_show_user_email: true +gitea_ui_show_full_name: false +gitea_ui_extra_config: {} + +# UI Meta Configuration +# -> +gitea_ui_meta_author: 'Gitea - Git with a cup of tea' +gitea_ui_meta_description: 'Gitea (Git with a cup of tea) is a painless self-hosted Git service written in Go' +gitea_ui_meta_keywords: 'go,git,self-hosted,gitea' + +# Server (server) +# -> https://docs.gitea.io/en-us/config-cheat-sheet/#server-server +gitea_protocol: "http" +gitea_http_domain: "{{ gitea_fqdn }}" +gitea_root_url: "{{ gitea_protocol }}://{{ gitea_fqdn }}" +gitea_http_listen: "127.0.0.1" +gitea_internal_http_port: 3000 +gitea_internal_ssh_port: 2222 +gitea_ssh_listen: "0.0.0.0" +gitea_start_ssh: true +gitea_ssh_domain: "{{ gitea_http_domain }}" +gitea_landing_page: 'home' +gitea_server_extra_config: {} + +# Security (security) +# -> https://docs.gitea.io/en-us/config-cheat-sheet/#security-security +gitea_secret_key: "" +gitea_internal_token: "" +gitea_install_lock: false +gitea_disable_git_hooks: true +gitea_disable_webhooks: false +gitea_reverse_proxy_limit: 1 +gitea_reverse_proxy_trusted_proxies: "127.0.0.0/8,::1/128" +gitea_password_complexity: "off" +gitea_password_min_length: 8 +gitea_password_check_pwn: false +gitea_2fa: "" +gitea_login_remember_days: 31 +gitea_cookie_remember_name: "gitea_incredible" +gitea_security_extra_config: {} + +# Service (service) +# -> https://docs.gitea.io/en-us/config-cheat-sheet/#service-service +gitea_disable_registration: false +gitea_register_email_confirm: false +gitea_register_manual_confirm: false +gitea_require_signin_view: false +gitea_enable_notify_mail: false +gitea_enable_captcha: false +gitea_require_captcha_for_login: false +gitea_captcha_type: image +gitea_show_registration_button: true +gitea_default_keep_email_private: false +gitea_default_allow_create_organization: true +gitea_default_user_is_restricted: false +gitea_default_user_visibility: public +gitea_default_org_visibility: public +gitea_default_org_member_visible: false +gitea_allow_only_internal_registration: false +gitea_allow_only_external_registration: false +gitea_email_domain_allowlist: "" +gitea_email_domain_blocklist: "" +gitea_no_reply_address: "" +gitea_enable_user_heatmap: true +gitea_enable_timetracking: true +gitea_auto_watch_new_repos: true +gitea_auto_watch_on_changes: false +gitea_show_milestones_dashboard_page: true +gitea_service_extra_config: {} + +# Indexer (indexer) +# -> https://docs.gitea.com/administration/config-cheat-sheet#indexer-indexer +gitea_issue_indexer_type: 'bleve' +gitea_issue_indexer_connection_string: '' +gitea_issue_indexer_name: 'gitea_issues' +gitea_issue_indexer_path: 'indexers/issues.bleve' +gitea_repo_indexer_enabled: false +gitea_repo_indexer_repo_types: 'sources,forks,mirrors,templates' +gitea_repo_indexer_type: 'bleve' +gitea_repo_indexer_path: 'indexers/repos.bleve' +gitea_repo_indexer_connection_string: '' +gitea_repo_indexer_name: 'gitea_codes' +gitea_repo_indexer_include: '' +gitea_repo_indexer_exclude: '' +gitea_repo_indexer_exclude_vendored: true +gitea_repo_indexer_max_file_size: 1048576 +gitea_indexer_startup_timeout: '30s' +gitea_indexer_extra_config: {} + + + +# Packages (packages) +# -> https://docs.gitea.io/en-us/config-cheat-sheet/#packages-packages +gitea_packages_enabled: true +gitea_packages_extra_config: {} + +# Actions (actions) +# -> https://docs.gitea.io/en-us/config-cheat-sheet/#actions-actions +gitea_actions_enabled: false +gitea_actions_default_actions_url: github +gitea_actions_extra_config: {} + +# Log (log) +# -> https://docs.gitea.com/next/administration/config-cheat-sheet#log-log +gitea_log_root_path: "" +gitea_log_mode: console +gitea_log_level: Info +gitea_enable_ssh_log: false +gitea_log_extra_config: {} + +# Mailer (mailer) +# -> https://docs.gitea.io/en-us/config-cheat-sheet/#mailer-mailer +gitea_mailer_enabled: false +gitea_mailer_protocol: "" # smtp, smtps, smtp+starttls, smtp+unix, sendmail, dummy +gitea_mailer_smtp_addr: "" +gitea_mailer_smtp_port: "" +gitea_mailer_user: "" +gitea_mailer_password: "" +gitea_mailer_from: "noreply@{{ gitea_http_domain }}" +gitea_mailer_subject_prefix: "" +gitea_mailer_send_as_plain_text: false +gitea_mailer_extra_config: {} + +# Mirror (mirror) +# -> https://docs.gitea.io/en-us/config-cheat-sheet/#mirror-mirror +gitea_mirror_enabled: true +gitea_mirror_disable_new_pull: false +gitea_mirror_disable_new_push: false +gitea_mirror_default_interval: 8h +gitea_mirror_min_interval: 10m +gitea_mirror_extra_config: {} + +# Other (other) +# -> https://docs.gitea.io/en-us/config-cheat-sheet/#other-other +gitea_show_footer_version: true +gitea_show_footer_template_load_time: true +gitea_enable_sitemap: true +gitea_enable_feed: true +gitea_other_extra_config: {} + +# Metrics (metrics) +# -> https://docs.gitea.io/en-us/config-cheat-sheet/#metrics-metrics +gitea_metrics_enabled: false +gitea_metrics_token: "" +gitea_metrics_extra_config: {} + +# API (api) +# -> https://docs.gitea.io/en-us/config-cheat-sheet/#api-api +gitea_api_enable_swagger: true +gitea_api_max_response_items: 50 +gitea_api_default_paging_num: 30 +gitea_api_extra_config: {} + +# OAuth2 (oauth2) +# -> https://docs.gitea.io/en-us/config-cheat-sheet/#oauth2-oauth2 +gitea_oauth2_enabled: true +gitea_oauth2_access_token_expiration_time: 3600 +gitea_oauth2_refresh_token_expiration_time: 730 +gitea_oauth2_jwt_signing_algorithm: RS256 +gitea_oauth2_jwt_secret: "" +gitea_oauth2_extra_config: {} + +# A dictionary of additional environment variables +# Read the cheat sheet before adding configurations https://docs.gitea.com/administration/config-cheat-sheet +gitea_extra_config: {} + # ENV_VAR_KEY: value + + + diff --git a/roles/gitea/handlers/main.yaml b/roles/gitea/handlers/main.yaml new file mode 100644 index 0000000..fc28864 --- /dev/null +++ b/roles/gitea/handlers/main.yaml @@ -0,0 +1,6 @@ +--- +- name: "Restart fail2ban" + become: true + ansible.builtin.service: + name: fail2ban + state: restarted diff --git a/roles/gitea/meta/.galaxy_install_info b/roles/gitea/meta/.galaxy_install_info new file mode 100644 index 0000000..99d68ea --- /dev/null +++ b/roles/gitea/meta/.galaxy_install_info @@ -0,0 +1,2 @@ +install_date: Tue Dec 2 19:10:42 2025 +version: '' diff --git a/roles/gitea/meta/main.yaml b/roles/gitea/meta/main.yaml new file mode 100644 index 0000000..c7a2e3f --- /dev/null +++ b/roles/gitea/meta/main.yaml @@ -0,0 +1,40 @@ +--- +galaxy_info: + app_role_name: gitea + author: Patrick Jaroszewski + description: Ansible role to configure and deploy Gitea in Docker, with optional support for runners. + license: MIT License + min_ansible_version: 2.11 + platforms: + - name: Debian + versions: all + - name: Ubuntu + version: all + galaxy_tags: + - gitea + - act_runner + - git + - gitserver + - selfhosted + +dependencies: + - role: patrickj.docker_apps.docker_compose_app + vars: + app_role_name: gitea + app_compose_validate: false + app_compose_start: false + app_uid: "{{ gitea_uid }}" + app_gid: "{{ gitea_gid }}" + app_extra_templates: + - src: env.j2 + dest: "{{ app_dir }}/.env" + # Directory structure + app_subdirectories: + - "{{ gitea_data_path }}" + - "{{ gitea_config_path }}" + - "{{ gitea_log_path }}" + + # Backup configuration + app_backup_subdirectories: + - "{{ gitea_data_path }}" + - "{{ gitea_config_path }}" diff --git a/roles/gitea/tasks/generate-config.yaml b/roles/gitea/tasks/generate-config.yaml new file mode 100644 index 0000000..873e1d2 --- /dev/null +++ b/roles/gitea/tasks/generate-config.yaml @@ -0,0 +1,24 @@ +--- + +- name: Get host IP + ansible.builtin.set_fact: + gitea_host_ip: "{{ ansible_default_ipv4.address }}" + +- name: Create data directories for runners + ansible.builtin.file: + path: "{{ app_dir }}/{{ item.data_mount }}" + state: directory + mode: "{{ app_permission_mode }}" + owner: "{{ app_uid }}" + group: "{{ app_gid }}" + loop: "{{ gitea_runners }}" + +- name: Create configuration files for each runner + ansible.builtin.template: + src: actions/config.yaml.j2 + dest: "{{ app_dir }}/{{ item.config_file_mount }}" + mode: "{{ app_permission_mode }}" + owner: "{{ app_uid }}" + group: "{{ app_gid }}" + loop: "{{ gitea_runners }}" + diff --git a/roles/gitea/tasks/main.yaml b/roles/gitea/tasks/main.yaml new file mode 100644 index 0000000..94aa6a1 --- /dev/null +++ b/roles/gitea/tasks/main.yaml @@ -0,0 +1,17 @@ +--- +- name: Deploy optional fail2ban rules + ansible.builtin.include_tasks: setup-fail2ban.yaml + when: gitea_fail2ban_enabled | bool + +- name: Generate Gitea configuration files + ansible.builtin.include_tasks: generate-config.yaml + +- name: Create or remove Gitea users + ansible.builtin.include_tasks: manage_users.yaml + when: gitea_users | length > 0 + +- name: Start Docker Compose stack after configuration + ansible.builtin.include_tasks: "{{ app_roles_path }}/docker_compose_app/tasks/manage_compose.yaml" + vars: + app_compose_start: true + diff --git a/roles/gitea/tasks/manage_users.yaml b/roles/gitea/tasks/manage_users.yaml new file mode 100644 index 0000000..60ea8be --- /dev/null +++ b/roles/gitea/tasks/manage_users.yaml @@ -0,0 +1,58 @@ +--- +- name: Get list of users + community.docker.docker_container_exec: + container: "{{ gitea_container_name }}" + command: /bin/bash -c "gitea admin user list" + register: user_list + failed_when: false + changed_when: false + when: gitea_users | length > 0 + +- name: Extract existing usernames + ansible.builtin.set_fact: + gitea_existing_users: "{{ user_list.stdout_lines[1:] | map('regex_replace', '^\\d+\\s+(\\S+)\\s+.*$', '\\1') | list | default([]) }}" + when: + - gitea_users | length > 0 + - user_list.stdout_lines | default([]) | length > 1 + +- name: Create Gitea users + community.docker.docker_container_exec: + container: "{{ gitea_container_name }}" + command: > + /bin/bash -c "gitea admin user create + --username {{ user.username }} + --email {{ user.email }} + --password {{ user.password }} + --must-change-password={{ user.must_change_password | default(false) }} + --admin={{ user.admin | default(false) }}" + register: _gitea_user_result + failed_when: + - '"successfully created" not in _gitea_user_result.stdout' + changed_when: + - '"successfully created!" in _gitea_user_result.stdout' + when: + - user.username not in gitea_existing_users | default([]) and user.state | default('present') == 'present' + loop: "{{ gitea_users }}" + loop_control: + label: "user={{ user.username }}" + loop_var: user + # no_log: true # Avoid logging passwords + + +- name: Remove gitea users + community.docker.docker_container_exec: + container: "{{ gitea_container_name }}" + command: > + /bin/bash -c "gitea admin user delete + --username {{ user.username }} + --email {{ user.email }}" + register: _gitea_user_del_result + failed_when: + - '"error" in _gitea_user_del_result.stdout' + changed_when: "user.username in gitea_existing_users" + when: "user.username in gitea_existing_users | default([]) and user.state | default('present') == 'absent'" + loop: "{{ gitea_users }}" + loop_control: + label: "user={{ user.username }}" + loop_var: user + \ No newline at end of file diff --git a/roles/gitea/tasks/setup-fail2ban.yaml b/roles/gitea/tasks/setup-fail2ban.yaml new file mode 100644 index 0000000..4b3efa9 --- /dev/null +++ b/roles/gitea/tasks/setup-fail2ban.yaml @@ -0,0 +1,39 @@ +--- + +- name: Gather installed packages for checks later on + ansible.builtin.package_facts: + manager: "auto" + +- name: Warn if fail2ban is not installed + ansible.builtin.fail: + msg: "the package fail2ban is not installed. no fail2ban filters deployed." + when: "'fail2ban' not in ansible_facts.packages" + failed_when: false + +- name: Install fail2ban filter + become: true + ansible.builtin.template: + src: fail2ban/filter.conf.j2 + dest: /etc/fail2ban/filter.d/gitea.local + owner: root + group: root + mode: "0444" + notify: "Restart fail2ban" + when: "'fail2ban' in ansible_facts.packages" + +- name: Install fail2ban jail for logins over HTTP(S) + become: true + vars: + gitea_fail2ban_filter: gitea + gitea_fail2ban_port: "http,https,{{ gitea_ssh_port }}" + gitea_fail2ban_jail_name: gitea-docker + ansible.builtin.template: + src: fail2ban/jail.conf.j2 + dest: /etc/fail2ban/jail.d/gitea.local + owner: root + group: root + mode: "0444" + notify: "Restart fail2ban" + when: "'fail2ban' in ansible_facts.packages" + + diff --git a/roles/gitea/templates/actions/config.yaml.j2 b/roles/gitea/templates/actions/config.yaml.j2 new file mode 100644 index 0000000..3487aac --- /dev/null +++ b/roles/gitea/templates/actions/config.yaml.j2 @@ -0,0 +1,111 @@ +# Example configuration file, it's safe to copy this as the default config file without any modification. +# https://gitea.com/gitea/act_runner/src/branch/main/internal/pkg/config/config.example.yaml + +# You don't have to copy this file to your instance, +# just run `./act_runner generate-config > config.yaml` to generate a config file. + +log: + # The level of logging, can be trace, debug, info, warn, error, fatal + level: info + +runner: + # Where to store the registration result. + file: .runner + # Execute how many tasks concurrently at the same time. + capacity: 1 + # Extra environment variables to run jobs. + envs: + # A_TEST_ENV_NAME_1: a_test_env_value_1 + # A_TEST_ENV_NAME_2: a_test_env_value_2 + # Extra environment variables to run jobs from a file. + # It will be ignored if it's empty or the file doesn't exist. + env_file: .env + # The timeout for a job to be finished. + # Please note that the Gitea instance also has a timeout (3h by default) for the job. + # So the job could be stopped by the Gitea instance if its timeout is shorter than this. + timeout: 3h + # The timeout for the runner to wait for running jobs to finish when shutting down. + # Any running jobs that haven't finished after this timeout will be cancelled. + shutdown_timeout: 0s + # Whether skip verifying the TLS certificate of the Gitea instance. + insecure: false + # The timeout for fetching the job from the Gitea instance. + fetch_timeout: 5s + # The interval for fetching the job from the Gitea instance. + fetch_interval: 2s + # The github_mirror of a runner is used to specify the mirror address of the github that pulls the action repository. + # It works when something like `uses: actions/checkout@v4` is used and DEFAULT_ACTIONS_URL is set to github, + # and github_mirror is not empty. In this case, + # it replaces https://github.com with the value here, which is useful for some special network environments. + github_mirror: '' + # The labels of a runner are used to determine which jobs the runner can run, and how to run them. + # Like: "macos-arm64:host" or "ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest" + # Find more images provided by Gitea at https://gitea.com/gitea/runner-images . + # If it's empty when registering, it will ask for inputting labels. + # If it's empty when execute `daemon`, will use labels in `.runner` file. + labels: + - "ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest" + - "ubuntu-24.04:docker://docker.gitea.com/runner-images:ubuntu-24.04" + - "ubuntu-22.04:docker://docker.gitea.com/runner-images:ubuntu-22.04" + +cache: + # Enable cache server to use actions/cache. + enabled: {{ item.cache_enabled | bool | lower }} + # The directory to store the cache data. + # If it's empty, the cache data will be stored in $HOME/.cache/actcache. + dir: "" + # The host of the cache server. + # It's not for the address to listen, but the address to connect from job containers. + # So 0.0.0.0 is a bad choice, leave it empty to detect automatically. + host: {{ gitea_host_ip }} + # The port of the cache server. + # 0 means to use a random available port. + port: {{ item.cache_port }} + # The external cache server URL. Valid only when enable is true. + # If it's specified, act_runner will use this URL as the ACTIONS_CACHE_URL rather than start a server by itself. + # The URL should generally end with "/". + external_server: "" + +container: + # Specifies the network to which the container will connect. + # Could be host, bridge or the name of a custom network. + # If it's empty, act_runner will create a network automatically. + network: "" + # Whether to use privileged mode or not when launching task containers (privileged mode is required for Docker-in-Docker). + privileged: false + # Any other options to be used when the container is started (e.g., --add-host=my.gitea.url:host-gateway). + options: + # The parent directory of a job's working directory. + # NOTE: There is no need to add the first '/' of the path as act_runner will add it automatically. + # If the path starts with '/', the '/' will be trimmed. + # For example, if the parent directory is /path/to/my/dir, workdir_parent should be path/to/my/dir + # If it's empty, /workspace will be used. + workdir_parent: + # Volumes (including bind mounts) can be mounted to containers. Glob syntax is supported, see https://github.com/gobwas/glob + # You can specify multiple volumes. If the sequence is empty, no volumes can be mounted. + # For example, if you only allow containers to mount the `data` volume and all the json files in `/src`, you should change the config to: + # valid_volumes: + # - data + # - /src/*.json + # If you want to allow any volume, please use the following configuration: + # valid_volumes: + # - '**' + valid_volumes: [] + # Overrides the docker client host with the specified one. + # If it's empty, act_runner will find an available docker host automatically. + # If it's "-", act_runner will find an available docker host automatically, but the docker host won't be mounted to the job containers and service containers. + # If it's not empty or "-", the specified docker host will be used. An error will be returned if it doesn't work. + docker_host: "" + # Pull docker image(s) even if already present + force_pull: true + # Rebuild docker image(s) even if already present + force_rebuild: false + # Always require a reachable docker daemon, even if not required by act_runner + require_docker: false + # Timeout to wait for the docker daemon to be reachable, if docker is required by require_docker or act_runner + docker_timeout: 0s + +host: + # The parent directory of a job's working directory. + # If it's empty, $HOME/.cache/act/ will be used. + workdir_parent: diff --git a/roles/gitea/templates/compose.yml.j2 b/roles/gitea/templates/compose.yml.j2 new file mode 100644 index 0000000..16d3a93 --- /dev/null +++ b/roles/gitea/templates/compose.yml.j2 @@ -0,0 +1,42 @@ +--- +services: + gitea: + image: docker.gitea.com/gitea:{{ gitea_container_version }} + container_name: "{{ gitea_container_name }}" + restart: "{{ gitea_restart_policy }}" + environment: + - USER_UID={{ app_uid }} + - USER_GID={{ app_gid }} + env_file: + - .env + volumes: + - "{{ gitea_data_path }}:/var/lib/gitea" + - "{{ gitea_config_path }}:/etc/gitea" + - "{{ gitea_log_path }}:/var/log/gitea" + - "/etc/timezone:/etc/timezone:ro" + - "/etc/localtime:/etc/localtime:ro" + ports: + - "127.0.0.1:{{ gitea_http_port }}:3000" + - "0.0.0.0:{{ gitea_ssh_port }}:2222" +{% for runner in gitea_runners %} + {{ runner.name }}: + image: "{{ gitea_runner_image }}:{{ gitea_runner_container_version }}" + container_name: "{{ app_name }}-{{ runner.name }}" + restart: "{{ gitea_runner_restart_policy }}" + privileged: true + depends_on: + - "{{ gitea_container_name }}" + volumes: + - {{ runner.config_file_mount }}:/config.yaml + - {{ runner.data_mount }}:/data +{% if runner.cache_enabled %} + ports: + - 127.0.0.1:{{ runner.cache_port }}:{{ runner.cache_port }} +{% endif %} + environment: + CONFIG_FILE: /config.yaml + GITEA_RUNNER_NAME: {{ runner.name }} + GITEA_INSTANCE_URL: {{ gitea_root_url }} + DOCKER_HOST: "unix:///var/run/user/{{ app_uid }}/docker.sock" + GITEA_RUNNER_REGISTRATION_TOKEN: {{ gitea_runner_global_registration_token | default(runner.registration_token) | default('') }} +{% endfor %} \ No newline at end of file diff --git a/roles/gitea/templates/env.j2 b/roles/gitea/templates/env.j2 new file mode 100644 index 0000000..e05a427 --- /dev/null +++ b/roles/gitea/templates/env.j2 @@ -0,0 +1,429 @@ +# ============================================ +# Default Configuration +# ============================================ +GITEA__DEFAULT__APP_NAME={{ gitea_app_name }} +GITEA__DEFAULT__RUN_MODE={{ gitea_run_mode }} +GITEA__DEFAULT__RUN_USER={{ gitea_user }} + +# ============================================ +# Server Configuration +# ============================================ +GITEA__server__PROTOCOL={{ gitea_protocol }} +GITEA__server__DOMAIN={{ gitea_http_domain }} +GITEA__server__ROOT_URL={{ gitea_root_url }} +GITEA__server__HTTP_ADDR={{ gitea_http_listen }} +GITEA__server__HTTP_PORT={{ gitea_internal_http_port }} +GITEA__server__SSH_PORT={{ gitea_internal_ssh_port }} +GITEA__server__SSH_LISTEN_HOST={{ gitea_ssh_listen }} +GITEA__server__START_SSH_SERVER={{ gitea_start_ssh | bool | lower }} +GITEA__server__SSH_DOMAIN={{ gitea_ssh_domain }} +GITEA__server__LANDING_PAGE={{ gitea_landing_page }} + +{% if gitea_server_extra_config %} +# Additional Server Configuration +{% for item in gitea_server_extra_config | dict2items %} +GITEA__server__{{ item.key }}={{ item.value }} +{% endfor %} +{% endif %} + +# ============================================ +# Service Configuration +# ============================================ +GITEA__service__DISABLE_REGISTRATION={{ gitea_disable_registration | bool | lower }} +GITEA__service__REGISTER_EMAIL_CONFIRM={{ gitea_register_email_confirm | bool | lower }} +GITEA__service__REGISTER_MANUAL_CONFIRM={{ gitea_register_manual_confirm | bool | lower }} +GITEA__service__REQUIRE_SIGNIN_VIEW={{ gitea_require_signin_view | bool | lower }} +GITEA__service__ENABLE_NOTIFY_MAIL={{ gitea_enable_notify_mail | bool | lower }} +GITEA__service__ENABLE_CAPTCHA={{ gitea_enable_captcha | bool | lower }} +{% if gitea_enable_captcha %} +GITEA__service__REQUIRE_CAPTCHA_FOR_LOGIN={{ gitea_require_captcha_for_login }} +GITEA__service__CAPTCHA_TYPE={{ gitea_captcha_type }} +{% endif %} +GITEA__service__SHOW_REGISTRATION_BUTTON={{ gitea_show_registration_button | bool | lower }} +GITEA__service__DEFAULT_KEEP_EMAIL_PRIVATE={{ gitea_default_keep_email_private | bool | lower }} +GITEA__service__DEFAULT_ALLOW_CREATE_ORGANIZATION={{ gitea_default_allow_create_organization | bool | lower }} +GITEA__service__DEFAULT_USER_IS_RESTRICTED={{ gitea_default_user_is_restricted | bool | lower }} +GITEA__service__DEFAULT_USER_VISIBILITY={{ gitea_default_user_visibility }} +GITEA__service__DEFAULT_ORG_VISIBILITY={{ gitea_default_org_visibility }} +GITEA__service__DEFAULT_ORG_MEMBER_VISIBLE={{ gitea_default_org_member_visible | bool | lower }} +GITEA__service__ALLOW_ONLY_INTERNAL_REGISTRATION={{ gitea_allow_only_internal_registration | bool | lower }} +GITEA__service__ALLOW_ONLY_EXTERNAL_REGISTRATION={{ gitea_allow_only_external_registration | bool | lower }} +{% if gitea_email_domain_allowlist %} +GITEA__service__EMAIL_DOMAIN_ALLOWLIST={{ gitea_email_domain_allowlist }} +{% endif %} +{% if gitea_email_domain_blocklist %} +GITEA__service__EMAIL_DOMAIN_BLOCKLIST={{ gitea_email_domain_blocklist }} +{% endif %} +{% if gitea_no_reply_address %} +GITEA__service__NO_REPLY_ADDRESS={{ gitea_no_reply_address }} +{% endif %} +GITEA__service__ENABLE_USER_HEATMAP={{ gitea_enable_user_heatmap | bool | lower }} +GITEA__service__ENABLE_TIMETRACKING={{ gitea_enable_timetracking | bool | lower }} +GITEA__service__AUTO_WATCH_NEW_REPOS={{ gitea_auto_watch_new_repos | bool | lower }} +GITEA__service__AUTO_WATCH_ON_CHANGES={{ gitea_auto_watch_on_changes | bool | lower }} +GITEA__service__SHOW_MILESTONES_DASHBOARD_PAGE={{ gitea_show_milestones_dashboard_page | bool | lower }} + +{% if gitea_service_extra_config %} +# Additional Service Configuration +{% for item in gitea_service_extra_config | dict2items %} +{% if item.value is boolean %} +GITEA__service__{{ item.key }}={{ item.value | bool | lower }} +{% else %} +GITEA__service__{{ item.key }}={{ item.value }} +{% endif %} +{% endfor %} +{% endif %} + + +# ============================================ +# Security Configuration +# ============================================ +{% if gitea_secret_key %} +GITEA__security__SECRET_KEY={{ gitea_secret_key }} +{% endif %} +{% if gitea_internal_token %} +GITEA__security__INTERNAL_TOKEN={{ gitea_internal_token }} +{% endif %} +GITEA__security__INSTALL_LOCK={{ gitea_install_lock | bool | lower }} +GITEA__security__DISABLE_GIT_HOOKS={{ gitea_disable_git_hooks | bool | lower }} +GITEA__security__DISABLE_WEBHOOKS={{ gitea_disable_webhooks | bool | lower }} +GITEA__security__PASSWORD_COMPLEXITY={{ gitea_password_complexity }} +GITEA__security__PASSWORD_MIN_LENGTH={{ gitea_password_min_length }} +GITEA__security__PASSWORD_CHECK_PWN={{ gitea_password_check_pwn | bool | lower }} +{% if gitea_2fa %} +GITEA__security__TWO_FACTOR_AUTH={{ gitea_2fa }} +{% endif %} +GITEA__security__LOGIN_REMEMBER_DAYS={{ gitea_login_remember_days }} +GITEA__security__COOKIE_REMEMBER_NAME={{ gitea_cookie_remember_name }} + +{% if gitea_security_extra_config %} +# Additional Security Configuration +{% for item in gitea_security_extra_config | dict2items %} +GITEA__security__{{ item.key }}={{ item.value if item.value is not boolean else (item.value | bool | lower) }} +{% endfor %} +{% endif %} + + +# ============================================ +# Repository Configuration +# ============================================ +GITEA__repository__FORCE_PRIVATE={{ gitea_repo_force_private }} +GITEA__repository__DEFAULT_PRIVATE={{ gitea_repo_default_private }} +GITEA__repository__DEFAULT_PUSH_CREATE_PRIVATE={{ gitea_repo_default_push_create_private }} +GITEA__repository__PREFERRED_LICENSES={{ gitea_repo_preferred_licenses }} +GITEA__repository__DISABLE_HTTP_GIT={{ gitea_repo_disable_http_git }} +GITEA__repository__DEFAULT_BRANCH={{ gitea_repo_default_branch }} +GITEA__repository__DISABLE_STARS={{ gitea_repo_disable_stars }} +GITEA__repository__DISABLE_MIGRATIONS={{ gitea_repo_disable_migrations }} + +{% if gitea_repository_extra_config %} +# Additional Repository Configuration +{% for item in gitea_repository_extra_config | dict2items %} +{% if item.value is boolean %} +GITEA__repository__{{ item.key }}={{ item.value | bool | lower }} +{% else %} +GITEA__repository__{{ item.key }}={{ item.value }} +{% endif %} +{% endfor %} +{% endif %} + +# ============================================ +# CORS Configuration +# ============================================ +GITEA__cors__ENABLED={{ gitea_cors_enabled }} +GITEA__cors__ALLOW_DOMAIN={{ gitea_cors_allowed_domains }} +GITEA__cors__METHODS={{ gitea_cors_allowed_methods }} +GITEA__cors__MAX_AGE={{ gitea_cors_max_age }} +GITEA__cors__ALLOW_CREDENTIALS={{ gitea_cors_allowed_credentials | bool | lower }} +GITEA__cors__HEADERS={{ gitea_cors_headers }} +GITEA__cors__X_FRAME_OPTIONS={{ gitea_cors_x_frame_options }} + +# ============================================ +# UI Configuration +# ============================================ +GITEA__ui__DEFAULT_THEME={{ gitea_ui_default_theme }} +GITEA__ui__THEMES={{ gitea_ui_themes }} +GITEA__ui__SHOW_USER_EMAIL={{ gitea_ui_show_user_email }} +GITEA__ui__DEFAULT_SHOW_FULL_NAME={{ gitea_ui_show_full_name }} + +{% if gitea_ui_extra_config %} +# Additional UI Configuration +{% for item in gitea_ui_extra_config | dict2items %} +{% if item.value is boolean %} +GITEA__ui__{{ item.key }}={{ item.value | bool | lower }} +{% else %} +GITEA__ui__{{ item.key }}={{ item.value }} +{% endif %} +{% endfor %} +{% endif %} + +# ============================================ +# UI.meta Configuration +# ============================================ +GITEA__ui.meta__AUTHOR={{ gitea_ui_meta_author }} +GITEA__ui.meta__DESCRIPTION={{ gitea_ui_meta_description }} +GITEA__ui.meta__KEYWORDS={{ gitea_ui_meta_keywords }} + + +# ============================================ +# Indexer Configuration +# ============================================ +# Issue Indexer +GITEA__indexer__ISSUE_INDEXER_TYPE={{ gitea_issue_indexer_type }} +{% if gitea_issue_indexer_type in ['elasticsearch', 'meilisearch'] and gitea_issue_indexer_connection_string %} +GITEA__indexer__ISSUE_INDEXER_CONN_STR={{ gitea_issue_indexer_connection_string }} +{% endif %} +{% if gitea_issue_indexer_type in ['elasticsearch', 'meilisearch'] and gitea_issue_indexer_name %} +GITEA__indexer__ISSUE_INDEXER_NAME={{ gitea_issue_indexer_name }} +{% endif %} +{% if gitea_issue_indexer_type == 'bleve' and gitea_issue_indexer_path %} +GITEA__indexer__ISSUE_INDEXER_PATH={{ gitea_issue_indexer_path }} +{% endif %} + +# Repository Indexer +GITEA__indexer__REPO_INDEXER_ENABLED={{ gitea_repo_indexer_enabled | bool | lower }} +{% if gitea_repo_indexer_enabled %} +{% if gitea_repo_indexer_repo_types %} +GITEA__indexer__REPO_INDEXER_REPO_TYPES={{ gitea_repo_indexer_repo_types }} +{% endif %} +GITEA__indexer__REPO_INDEXER_TYPE={{ gitea_repo_indexer_type }} +{% if gitea_repo_indexer_type == 'bleve' and gitea_repo_indexer_path %} +GITEA__indexer__REPO_INDEXER_PATH={{ gitea_repo_indexer_path }} +{% endif %} +{% if gitea_repo_indexer_type == 'elasticsearch' and gitea_repo_indexer_connection_string %} +GITEA__indexer__REPO_INDEXER_CONN_STR={{ gitea_repo_indexer_connection_string }} +{% endif %} +{% if gitea_repo_indexer_type == 'elasticsearch' and gitea_repo_indexer_name %} +GITEA__indexer__REPO_INDEXER_NAME={{ gitea_repo_indexer_name }} +{% endif %} +{% if gitea_repo_indexer_include %} +GITEA__indexer__REPO_INDEXER_INCLUDE={{ gitea_repo_indexer_include }} +{% endif %} +{% if gitea_repo_indexer_exclude %} +GITEA__indexer__REPO_INDEXER_EXCLUDE={{ gitea_repo_indexer_exclude }} +{% endif %} +GITEA__indexer__REPO_INDEXER_EXCLUDE_VENDORED={{ gitea_repo_indexer_exclude_vendored | bool | lower }} +GITEA__indexer__MAX_FILE_SIZE={{ gitea_repo_indexer_max_file_size }} +{% endif %} + +{% if gitea_indexer_startup_timeout %} +GITEA__indexer__STARTUP_TIMEOUT={{ gitea_indexer_startup_timeout }} +{% endif %} + +{% if gitea_indexer_extra_config %} +# Additional Indexer Configuration +{% for item in gitea_indexer_extra_config | dict2items %} +{% if item.value is boolean %} +GITEA__indexer__{{ item.key }}={{ item.value | bool | lower }} +{% else %} +GITEA__indexer__{{ item.key }}={{ item.value }} +{% endif %} +{% endfor %} +{% endif %} + + +# ============================================ +# Packages Configuration +# ============================================ +GITEA__packages__ENABLED={{ gitea_packages_enabled | bool | lower }} + +{% if gitea_packages_extra_config %} +# Additional Packages Configuration +{% for item in gitea_packages_extra_config | dict2items %} +GITEA__packages__{{ item.key }}={{ item.value if item.value is not boolean else (item.value | bool | lower) }} +{% endfor %} +{% endif %} + +# ============================================ +# Actions Configuration +# ============================================ +GITEA__actions__ENABLED={{ gitea_actions_enabled | bool | lower }} +{% if gitea_actions_enabled %} +GITEA__actions__DEFAULT_ACTIONS_URL={{ gitea_actions_default_actions_url }} +{% endif %} + +{% if gitea_runner_global_registration_token %} +GITEA_RUNNER_REGISTRATION_TOKEN={{ gitea_runner_global_registration_token }} +{% endif %} + +{% if gitea_actions_extra_config %} +# Additional Actions Configuration +{% for item in gitea_actions_extra_config | dict2items %} +GITEA__actions__{{ item.key }}={{ item.value if item.value is not boolean else (item.value | bool | lower) }} +{% endfor %} +{% endif %} + +# ============================================ +# Log Configuration +# ============================================ +{% if gitea_log_root_path %} +GITEA__log__ROOT_PATH={{ gitea_log_root_path }} +{% endif %} +GITEA__log__MODE={{ gitea_log_mode }} +GITEA__log__LEVEL={{ gitea_log_level }} + +{% if gitea_log_extra_config %} +# Additional Log Configuration +{% for item in gitea_log_extra_config | dict2items %} +{% if item.value is boolean %} +GITEA__log__{{ item.key }}={{ item.value | bool | lower }} +{% else %} +GITEA__log__{{ item.key }}={{ item.value }} +{% endif %} +{% endfor %} +{% endif %} + +# ============================================ +# Mailer Configuration +# ============================================ +GITEA__mailer__ENABLED={{ gitea_mailer_enabled | bool | lower }} +{% if gitea_mailer_enabled %} +{% if gitea_mailer_protocol %} +GITEA__mailer__PROTOCOL={{ gitea_mailer_protocol }} +{% endif %} +{% if gitea_mailer_smtp_addr %} +GITEA__mailer__SMTP_ADDR={{ gitea_mailer_smtp_addr }} +{% endif %} +{% if gitea_mailer_smtp_port %} +GITEA__mailer__SMTP_PORT={{ gitea_mailer_smtp_port }} +{% endif %} +{% if gitea_mailer_user %} +GITEA__mailer__USER={{ gitea_mailer_user }} +{% endif %} +{% if gitea_mailer_password %} +GITEA__mailer__PASSWD={{ gitea_mailer_password }} +{% endif %} +{% if gitea_mailer_from %} +GITEA__mailer__FROM={{ gitea_mailer_from }} +{% endif %} +{% if gitea_mailer_subject_prefix %} +GITEA__mailer__SUBJECT_PREFIX={{ gitea_mailer_subject_prefix }} +{% endif %} +GITEA__mailer__SEND_AS_PLAIN_TEXT={{ gitea_mailer_send_as_plain_text | bool | lower }} +{% endif %} + +{% if gitea_mailer_extra_config %} +# Additional Mailer Configuration +{% for item in gitea_mailer_extra_config | dict2items %} +{% if item.value is boolean %} +GITEA__mailer__{{ item.key }}={{ item.value | bool | lower }} +{% else %} +GITEA__mailer__{{ item.key }}={{ item.value }} +{% endif %} +{% endfor %} +{% endif %} + + +# ============================================ +# Mirror Configuration +# ============================================ +GITEA__mirror__ENABLED={{ gitea_mirror_enabled | bool | lower }} +GITEA__mirror__DISABLE_NEW_PULL={{ gitea_mirror_disable_new_pull | bool | lower }} +GITEA__mirror__DISABLE_NEW_PUSH={{ gitea_mirror_disable_new_push | bool | lower }} +GITEA__mirror__DEFAULT_INTERVAL={{ gitea_mirror_default_interval }} +GITEA__mirror__MIN_INTERVAL={{ gitea_mirror_min_interval }} + +{% if gitea_mirror_extra_config %} +# Additional Mirror Configuration +{% for item in gitea_mirror_extra_config | dict2items %} +{% if item.value is boolean %} +GITEA__mirror__{{ item.key }}={{ item.value | bool | lower }} +{% else %} +GITEA__mirror__{{ item.key }}={{ item.value }} +{% endif %} +{% endfor %} +{% endif %} + +# ============================================ +# Metrics Configuration +# ============================================ +GITEA__metrics__ENABLED={{ gitea_metrics_enabled | bool | lower }} +{% if gitea_metrics_enabled and gitea_metrics_token %} +GITEA__metrics__TOKEN={{ gitea_metrics_token }} +{% endif %} + +{% if gitea_metrics_extra_config %} +# Additional Metrics Configuration +{% for item in gitea_metrics_extra_config | dict2items %} +{% if item.value is boolean %} +GITEA__metrics__{{ item.key }}={{ item.value | bool | lower }} +{% else %} +GITEA__metrics__{{ item.key }}={{ item.value }} +{% endif %} +{% endfor %} +{% endif %} + +# ============================================ +# API Configuration +# ============================================ +GITEA__api__ENABLE_SWAGGER={{ gitea_api_enable_swagger | bool | lower }} +GITEA__api__MAX_RESPONSE_ITEMS={{ gitea_api_max_response_items }} +GITEA__api__DEFAULT_PAGING_NUM={{ gitea_api_default_paging_num }} + +{% if gitea_api_extra_config %} +# Additional API Configuration +{% for item in gitea_api_extra_config | dict2items %} +{% if item.value is boolean %} +GITEA__api__{{ item.key }}={{ item.value | bool | lower }} +{% else %} +GITEA__api__{{ item.key }}={{ item.value }} +{% endif %} +{% endfor %} +{% endif %} + +# ============================================ +# OAuth2 Configuration +# ============================================ +GITEA__oauth2__ENABLED={{ gitea_oauth2_enabled | bool | lower }} +{% if gitea_oauth2_enabled %} +GITEA__oauth2__ACCESS_TOKEN_EXPIRATION_TIME={{ gitea_oauth2_access_token_expiration_time }} +GITEA__oauth2__REFRESH_TOKEN_EXPIRATION_TIME={{ gitea_oauth2_refresh_token_expiration_time }} +GITEA__oauth2__JWT_SIGNING_ALGORITHM={{ gitea_oauth2_jwt_signing_algorithm }} +{% if gitea_oauth2_jwt_secret %} +GITEA__oauth2__JWT_SECRET={{ gitea_oauth2_jwt_secret }} +{% endif %} +{% endif %} + +{% if gitea_oauth2_extra_config %} +# Additional OAuth2 Configuration +{% for item in gitea_oauth2_extra_config | dict2items %} +{% if item.value is boolean %} +GITEA__oauth2__{{ item.key }}={{ item.value | bool | lower }} +{% else %} +GITEA__oauth2__{{ item.key }}={{ item.value }} +{% endif %} +{% endfor %} +{% endif %} + +# ============================================ +# Other Configuration +# ============================================ +GITEA__other__SHOW_FOOTER_VERSION={{ gitea_show_footer_version | bool | lower }} +GITEA__other__SHOW_FOOTER_TEMPLATE_LOAD_TIME={{ gitea_show_footer_template_load_time | bool | lower }} +GITEA__other__ENABLE_SITEMAP={{ gitea_enable_sitemap | bool | lower }} +GITEA__other__ENABLE_FEED={{ gitea_enable_feed | bool | lower }} + +{% if gitea_other_extra_config %} +# Additional Other Configuration +{% for item in gitea_other_extra_config | dict2items %} +{% if item.value is boolean %} +GITEA__other__{{ item.key }}={{ item.value | bool | lower }} +{% else %} +GITEA__other__{{ item.key }}={{ item.value }} +{% endif %} +{% endfor %} +{% endif %} + + +# ============================================ +# Additonal environment variables +# ============================================ + +{% for item in gitea_extra_config | dict2items %} +{% if item.value is boolean %} +{{ item.key }}={{ item.value | bool | lower }} +{% else %} +{{ item.key }}={{ item.value }} +{% endif %} +{% endfor %} + diff --git a/roles/gitea/templates/fail2ban/filter.conf.j2 b/roles/gitea/templates/fail2ban/filter.conf.j2 new file mode 100644 index 0000000..05c897b --- /dev/null +++ b/roles/gitea/templates/fail2ban/filter.conf.j2 @@ -0,0 +1,4 @@ +# gitea.local +[Definition] +failregex = .*(Failed authentication attempt|invalid credentials|Attempted access of unknown user).* from +ignoreregex = \ No newline at end of file diff --git a/roles/gitea/templates/fail2ban/jail.conf.j2 b/roles/gitea/templates/fail2ban/jail.conf.j2 new file mode 100644 index 0000000..3a3fb3b --- /dev/null +++ b/roles/gitea/templates/fail2ban/jail.conf.j2 @@ -0,0 +1,11 @@ +{# https://docs.gitea.com/administration/fail2ban-setup #} + +[{{ gitea_fail2ban_jail_name }}] +enabled = true +filter = {{ gitea_fail2ban_filter }} +port = {{ gitea_fail2ban_port }} +logpath = {{ gitea_mounted_log_path }} +maxretry = {{ gitea_fail2ban_jail_maxretry }} +findtime = {{ gitea_fail2ban_jail_findtime }} +bantime = {{ gitea_fail2ban_jail_bantime }} +action = {{ gitea_fail2ban_jail_action }} \ No newline at end of file diff --git a/roles/glance/README.md b/roles/glance/README.md new file mode 100644 index 0000000..4a0d10a --- /dev/null +++ b/roles/glance/README.md @@ -0,0 +1,44 @@ +# Glance + +Deploy Glance dashboard using Docker Compose. + +## Description + +Glance is a self-hosted dashboard that displays information from various sources in a simple, configurable interface. + +## Variables + +### Optional + +| Variable | Default | Description | +|----------|---------|-------------| +| `glance_config_template` | `{{ app_templates_path }}/glance.yml.j2` | Template file for generating glance.yml config | +| `glance_config_path` | `{{ app_dir }}/config` | Configuration storage | +| `glance_http_port` | `8080` | Web interface port | +| `glance_container_version` | `latest` | Glance Docker image tag | +| `glance_restart_policy` | `{{ app_restart_policy }}` | Container restart policy | + + +## Example + +```yaml +- role: patrickj.docker_apps.glance + vars: + glance_http_port: 3000 + glance_config_template: "/path/to/your/custom/glance.yml.j2" +``` + + +## Configuration + +The role includes a default configuration template with example widgets (RSS feeds, weather, calendar, markets, etc.). To customize your dashboard: + +1. **Option 1**: Create your own template file and set `glance_config_template` to point to it +2. **Option 2**: Manually edit `{app_dir}/glance.yml` after deployment + +The default template includes widgets for: +- Calendar and weather +- RSS feeds (selfhosted, tech blogs) +- Twitch channels and YouTube videos +- Reddit subreddits and Hacker News +- Stock market data and GitHub releases diff --git a/roles/glance/defaults/main.yml b/roles/glance/defaults/main.yml new file mode 100644 index 0000000..981c88c --- /dev/null +++ b/roles/glance/defaults/main.yml @@ -0,0 +1,16 @@ +--- +app_name: glance + +# Container configuration +glance_container_name: "{{ app_name | default('glance') }}" +glance_container_version: latest +glance_restart_policy: "{{ app_restart_policy }}" + +# Network configuration +glance_http_port: 8080 + +# Volume paths +glance_config_path: "{{ app_dir }}/config" + +# App-specific configuration +glance_config_template: "{{ app_templates_path }}/glance.yml.j2" \ No newline at end of file diff --git a/roles/glance/meta/main.yml b/roles/glance/meta/main.yml new file mode 100644 index 0000000..e45071c --- /dev/null +++ b/roles/glance/meta/main.yml @@ -0,0 +1,17 @@ +--- +galaxy_info: + author: Patrick Jaroszewski + description: Deploy Glance with Docker Compose + license: MIT + +dependencies: + - role: patrickj.docker_apps.docker_compose_app + vars: + app_role_name: glance + app_subdirectories: + - "{{ glance_config_path }}" + app_backup_subdirectories: + - "{{ glance_config_path }}" + app_extra_templates: + - src: "{{ glance_config_template }}" + dest: "{{ glance_config_path }}/glance.yml" diff --git a/roles/glance/templates/compose.yml.j2 b/roles/glance/templates/compose.yml.j2 new file mode 100644 index 0000000..cd832e7 --- /dev/null +++ b/roles/glance/templates/compose.yml.j2 @@ -0,0 +1,10 @@ +--- +services: + glance: + container_name: {{ glance_container_name }} + image: glanceapp/glance:{{ glance_container_version }} + restart: {{ glance_restart_policy }} + volumes: + - {{ glance_config_path }}:/app/config + ports: + - "{{ glance_http_port }}:8080" \ No newline at end of file diff --git a/roles/glance/templates/glance.yml.j2 b/roles/glance/templates/glance.yml.j2 new file mode 100644 index 0000000..7666f5a --- /dev/null +++ b/roles/glance/templates/glance.yml.j2 @@ -0,0 +1,105 @@ +pages: + - name: Home + # Optionally, if you only have a single page you can hide the desktop navigation for a cleaner look + # hide-desktop-navigation: true + columns: + - size: small + widgets: + - type: calendar + first-day-of-week: monday + + - type: rss + limit: 10 + collapse-after: 3 + cache: 12h + feeds: + - url: https://selfh.st/rss/ + title: selfh.st + limit: 4 + - url: https://ciechanow.ski/atom.xml + - url: https://www.joshwcomeau.com/rss.xml + title: Josh Comeau + - url: https://samwho.dev/rss.xml + - url: https://ishadeed.com/feed.xml + title: Ahmad Shadeed + + - type: twitch-channels + channels: + - theprimeagen + - j_blow + - giantwaffle + - cohhcarnage + - christitustech + - EJ_SA + + - size: full + widgets: + - type: group + widgets: + - type: hacker-news + - type: lobsters + + - type: videos + channels: + - UCXuqSBlHAE6Xw-yeJA0Tunw # Linus Tech Tips + - UCR-DXc1voovS8nhAvccRZhg # Jeff Geerling + - UCsBjURrPoezykLs9EqgamOA # Fireship + - UCBJycsmduvYEL83R_U4JriQ # Marques Brownlee + - UCHnyfMqiRRG1u-2MsSQLbXA # Veritasium + + - type: group + widgets: + - type: reddit + subreddit: technology + show-thumbnails: true + - type: reddit + subreddit: selfhosted + show-thumbnails: true + + - size: small + widgets: + - type: weather + location: London, United Kingdom + units: metric # alternatively "imperial" + hour-format: 12h # alternatively "24h" + # Optionally hide the location from being displayed in the widget + # hide-location: true + + - type: markets + markets: + - symbol: SPY + name: S&P 500 + - symbol: BTC-USD + name: Bitcoin + - symbol: NVDA + name: NVIDIA + - symbol: AAPL + name: Apple + - symbol: MSFT + name: Microsoft + + - type: releases + cache: 1d + # Without authentication the Github API allows for up to 60 requests per hour. You can create a + # read-only token from your Github account settings and use it here to increase the limit. + # token: ... + repositories: + - glanceapp/glance + - go-gitea/gitea + - immich-app/immich + - syncthing/syncthing + + # Add more pages here: + # - name: Your page name + # columns: + # - size: small + # widgets: + # # Add widgets here + + # - size: full + # widgets: + # # Add widgets here + + # - size: small + # widgets: + # # Add widgets here \ No newline at end of file diff --git a/roles/grafana/README.md b/roles/grafana/README.md new file mode 100644 index 0000000..201e932 --- /dev/null +++ b/roles/grafana/README.md @@ -0,0 +1,25 @@ +# Grafana + +Deploy Grafana data visualization platform using Docker Compose. + +## Description + +Grafana is an analytics and monitoring platform with beautiful dashboards and alerting capabilities. + +## Variables + +### Optional + +| Variable | Default | Description | +|----------|---------|-------------| +| `grafana_http_port` | `3000` | Web interface port | +| `grafana_data_path` | `{{ app_dir }}/data` | Data storage | +| `grafana_container_version` | `latest` | Grafana Docker image tag | + +## Example + +```yaml +- role: patrickj.docker_apps.grafana + vars: + grafana_http_port: 3001 +``` diff --git a/roles/grafana/defaults/main.yml b/roles/grafana/defaults/main.yml new file mode 100644 index 0000000..40a8c7b --- /dev/null +++ b/roles/grafana/defaults/main.yml @@ -0,0 +1,8 @@ +--- +app_name: grafana + +grafana_container_name: "{{ app_name | default('grafana') }}" +grafana_container_version: latest +grafana_restart_policy: "{{ app_restart_policy }}" +grafana_http_port: 3000 +grafana_data_path: "{{ app_dir }}/data" diff --git a/roles/grafana/meta/main.yml b/roles/grafana/meta/main.yml new file mode 100644 index 0000000..dd2870c --- /dev/null +++ b/roles/grafana/meta/main.yml @@ -0,0 +1,14 @@ +--- +galaxy_info: + author: Patrick Jaroszewski + description: Deploy grafana with Docker Compose + license: MIT + +dependencies: + - role: patrickj.docker_apps.docker_compose_app + vars: + app_role_name: grafana + app_subdirectories: + - "{{ grafana_data_path }}" + app_backup_subdirectories: + - "{{ grafana_data_path }}" diff --git a/roles/grafana/templates/compose.yml.j2 b/roles/grafana/templates/compose.yml.j2 new file mode 100644 index 0000000..49b734f --- /dev/null +++ b/roles/grafana/templates/compose.yml.j2 @@ -0,0 +1,10 @@ +--- +services: + grafana: + image: "grafana/grafana-oss:{{ grafana_container_version }}" + container_name: "{{ grafana_container_name }}" + restart: "{{ grafana_restart_policy }}" + ports: + - "{{ grafana_http_port }}:3000" + volumes: + - "{{ grafana_data_path }}:/var/lib/grafana" \ No newline at end of file diff --git a/roles/immich/README.md b/roles/immich/README.md new file mode 100644 index 0000000..c155c12 --- /dev/null +++ b/roles/immich/README.md @@ -0,0 +1,37 @@ +# Immich + +Deploy Immich photo management platform using Docker Compose. + +## Description + +Immich is a self-hosted photo and video backup solution with mobile apps, AI features, and automatic organization. Includes web server, machine learning, PostgreSQL database, and Redis. + +## Variables + +### Required (Set in Vault) + +| Variable | Description | +|----------|-------------| +| `immich_db_password` | PostgreSQL database password (use only A-Za-z0-9 characters) | + +### Optional + +| Variable | Default | Description | +|----------|---------|-------------| +| `immich_http_port` | `2283` | Web interface port | +| `immich_library_path` | `{{ app_dir }}/library` | Photo and video upload storage location | +| `immich_pgdata_path` | `{{ app_dir }}/pgdata` | PostgreSQL database storage location | +| `immich_container_version` | `v2` | Immich version tag (can pin to specific version like "v2.1.0") | +| `immich_container_name` | `{{ app_name }}` | Container name | +| `immich_restart_policy` | `{{ app_restart_policy }}` | Container restart policy | +| `immich_timezone` | `{{ app_timezone }}` | Container timezone | + +## Example + +```yaml +- role: patrickj.docker_apps.immich + vars: + immich_db_password: "{{ vault_immich_db_password }}" + immich_library_path: /storage/photos + immich_container_version: "v2.1.0" +``` \ No newline at end of file diff --git a/roles/immich/defaults/main.yml b/roles/immich/defaults/main.yml new file mode 100644 index 0000000..fd989c7 --- /dev/null +++ b/roles/immich/defaults/main.yml @@ -0,0 +1,18 @@ +--- +app_name: immich + +# Container configuration +immich_container_name: "{{ app_name | default('immich') }}" +immich_container_version: v2 +immich_restart_policy: "{{ app_restart_policy | default('unless-stopped') }}" + +# Network configuration +immich_http_port: 2283 + +# Volume paths +immich_library_path: "{{ app_dir }}/library" +immich_pgdata_path: "{{ app_dir }}/pgdata" + +# App-specific configuration +immich_db_password: postgres +immich_timezone: "{{ app_timezone }}" diff --git a/roles/immich/meta/main.yml b/roles/immich/meta/main.yml new file mode 100644 index 0000000..6334f19 --- /dev/null +++ b/roles/immich/meta/main.yml @@ -0,0 +1,20 @@ +--- +galaxy_info: + author: Patrick Jaroszewski + description:: Deploy Immich with Docker Compose + license: MIT + +dependencies: + - role: patrickj.docker_apps.docker_compose_app + vars: + app_role_name: immich + app_subdirectories: + - "{{ immich_library_path }}" + - "{{ immich_pgdata_path }}" + app_backup_subdirectories: + - "{{ immich_library_path }}" + - "{{ immich_pgdata_path }}" + app_extra_templates: + - src: "{{ app_templates_path }}/.env.j2" + dest: "{{ app_dir }}/.env" + app_compose_validate: false diff --git a/roles/immich/templates/.env.j2 b/roles/immich/templates/.env.j2 new file mode 100644 index 0000000..c82d61d --- /dev/null +++ b/roles/immich/templates/.env.j2 @@ -0,0 +1,22 @@ +# You can find documentation for all the supported env variables at https://docs.immich.app/install/environment-variables + +# The location where your uploaded files are stored +UPLOAD_LOCATION={{ immich_library_path }} + +# The location where your database files are stored. Network shares are not supported for the database +DB_DATA_LOCATION={{ immich_pgdata_path }} + +# To set a timezone, uncomment the next line and change Etc/UTC to a TZ identifier from this list: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List +TZ={{ immich_timezone }} + +# The Immich version to use. You can pin this to a specific version like "v2.1.0" +IMMICH_VERSION={{ immich_container_version }} + +# Connection secret for postgres. You should change it to a random password +# Please use only the characters `A-Za-z0-9`, without special characters or spaces +DB_PASSWORD={{ immich_db_password }} + +# The values below this line do not need to be changed +################################################################################### +DB_USERNAME=postgres +DB_DATABASE_NAME=immich diff --git a/roles/immich/templates/compose.yml.j2 b/roles/immich/templates/compose.yml.j2 new file mode 100644 index 0000000..9206661 --- /dev/null +++ b/roles/immich/templates/compose.yml.j2 @@ -0,0 +1,66 @@ + +name: immich + +services: + immich-server: + container_name: "{{ immich_container_name }}" + image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release} + # extends: + # file: hwaccel.transcoding.yml + # service: cpu # set to one of [nvenc, quicksync, rkmpp, vaapi, vaapi-wsl] for accelerated transcoding + volumes: + # Do not edit the next line. If you want to change the media storage location on your system, edit the value of UPLOAD_LOCATION in the .env file + - ${UPLOAD_LOCATION}:/data + - /etc/localtime:/etc/localtime:ro + env_file: + - .env + ports: + - "{{ immich_http_port }}:2283" + depends_on: + - redis + - database + restart: {{ immich_restart_policy }} + healthcheck: + disable: false + + immich-machine-learning: + container_name: immich_machine_learning + # For hardware acceleration, add one of -[armnn, cuda, rocm, openvino, rknn] to the image tag. + # Example tag: ${IMMICH_VERSION:-release}-cuda + image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release} + # extends: # uncomment this section for hardware acceleration - see https://docs.immich.app/features/ml-hardware-acceleration + # file: hwaccel.ml.yml + # service: cpu # set to one of [armnn, cuda, rocm, openvino, openvino-wsl, rknn] for accelerated inference - use the `-wsl` version for WSL2 where applicable + volumes: + - model-cache:/cache + env_file: + - .env + restart: {{ immich_restart_policy }} + healthcheck: + disable: false + + redis: + container_name: immich_redis + image: docker.io/valkey/valkey:9@sha256:fb8d272e529ea567b9bf1302245796f21a2672b8368ca3fcb938ac334e613c8f + healthcheck: + test: redis-cli ping || exit 1 + restart: {{ immich_restart_policy }} + + database: + container_name: immich_postgres + image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:bcf63357191b76a916ae5eb93464d65c07511da41e3bf7a8416db519b40b1c23 + environment: + POSTGRES_PASSWORD: ${DB_PASSWORD} + POSTGRES_USER: ${DB_USERNAME} + POSTGRES_DB: ${DB_DATABASE_NAME} + POSTGRES_INITDB_ARGS: '--data-checksums' + # Uncomment the DB_STORAGE_TYPE: 'HDD' var if your database isn't stored on SSDs + # DB_STORAGE_TYPE: 'HDD' + volumes: + # Do not edit the next line. If you want to change the database storage location on your system, edit the value of DB_DATA_LOCATION in the .env file + - ${DB_DATA_LOCATION}:/var/lib/postgresql/data + shm_size: 128mb + restart: {{ immich_restart_policy }} + +volumes: + model-cache: diff --git a/roles/influxdb/README.md b/roles/influxdb/README.md new file mode 100644 index 0000000..b4df851 --- /dev/null +++ b/roles/influxdb/README.md @@ -0,0 +1,30 @@ +# InfluxDB + +Deploy InfluxDB time series database using Docker Compose. + +## Description + +InfluxDB is a high-performance time series database designed for handling large amounts of timestamped data. This deploys InfluxDB v2 with web UI and API. + +## Variables + +### Optional + +| Variable | Default | Description | +|----------|---------|-------------| +| `influxdb_http_port` | `8086` | HTTP API and web UI port | +| `influxdb_udp_port` | `8089` | UDP port for data ingestion | +| `influxdb_data_path` | `{{ app_dir }}/data` | Database storage location | +| `influxdb_config_path` | `{{ app_dir }}/config` | Configuration files location | +| `influxdb_container_version` | `latest` | InfluxDB Docker image tag | +| `influxdb_container_name` | `{{ app_name }}` | Container name | +| `influxdb_restart_policy` | `{{ app_restart_policy }}` | Container restart policy | + +## Example + +```yaml +- role: patrickj.docker_apps.influxdb + vars: + influxdb_http_port: 8087 + influxdb_container_version: "2.7" +``` diff --git a/roles/influxdb/defaults/main.yml b/roles/influxdb/defaults/main.yml new file mode 100644 index 0000000..f6e875d --- /dev/null +++ b/roles/influxdb/defaults/main.yml @@ -0,0 +1,15 @@ +--- +app_name: influxdb + +# Container configuration +influxdb_container_name: "{{ app_name | default('influxdb') }}" +influxdb_container_version: latest +influxdb_restart_policy: "{{ app_restart_policy }}" + +# Network configuration +influxdb_http_port: 8086 +influxdb_udp_port: 8089 + +# Volume paths +influxdb_data_path: "{{ app_dir }}/data" +influxdb_config_path: "{{ app_dir }}/config" diff --git a/roles/influxdb/meta/main.yml b/roles/influxdb/meta/main.yml new file mode 100644 index 0000000..d042de3 --- /dev/null +++ b/roles/influxdb/meta/main.yml @@ -0,0 +1,16 @@ +--- +galaxy_info: + author: + description: Deploy influxdb with Docker Compose + license: MIT + +dependencies: + - role: patrickj.docker_apps.docker_compose_app + vars: + app_role_name: influxdb + app_subdirectories: + - "{{ influxdb_data_path }}" + - "{{ influxdb_config_path }}" + app_backup_subdirectories: + - "{{ influxdb_data_path }}" + - "{{ influxdb_config_path }}" diff --git a/roles/influxdb/templates/compose.yml.j2 b/roles/influxdb/templates/compose.yml.j2 new file mode 100644 index 0000000..af6f383 --- /dev/null +++ b/roles/influxdb/templates/compose.yml.j2 @@ -0,0 +1,12 @@ +--- +services: + influxdb: + image: "influxdb:{{ influxdb_container_version }}" + container_name: "{{ influxdb_container_name }}" + restart: "{{ influxdb_restart_policy }}" + ports: + - "{{ influxdb_http_port }}:8086" + - "{{ influxdb_udp_port }}:8089/udp" + volumes: + - "{{ influxdb_data_path }}:/var/lib/influxdb2" + - "{{ influxdb_config_path }}:/etc/influxdb2" \ No newline at end of file diff --git a/roles/jellyfin/README.md b/roles/jellyfin/README.md new file mode 100644 index 0000000..83310af --- /dev/null +++ b/roles/jellyfin/README.md @@ -0,0 +1,50 @@ +# Jellyfin + +Deploy Jellyfin media server using Docker Compose. + +## Description + +Jellyfin is a free and open-source media server for organizing, managing, and streaming media files with optional GPU hardware acceleration. + +## Variables + +### Optional + +| Variable | Default | Description | +|----------|---------|-------------| +| `jellyfin_http_port` | `8096` | Web interface port | +| `jellyfin_discovery_port` | `7359` | Auto-discovery port | +| `jellyfin_config_path` | `{{ app_dir }}/config` | Configuration storage | +| `jellyfin_cache_path` | `{{ app_dir }}/cache` | Cache storage | +| `jellyfin_container_version` | `latest` | Jellyfin Docker image tag | +| `jellyfin_container_name` | `{{ app_name }}` | Container name | +| `jellyfin_restart_policy` | `{{ app_restart_policy }}` | Container restart policy | +| `jellyfin_uid` | `{{ app_uid }}` | User ID for file permissions | +| `jellyfin_gid` | `{{ app_gid }}` | Group ID for file permissions | +| `jellyfin_gpu_passthrough` | `false` | Enable NVIDIA GPU support | +| `jellyfin_server_url` | See defaults | Custom domain for server | +| `jellyfin_libraries` | `[]` | List of media library mappings with `host_path` and `container_path` | + +#### Example Media Libraries Configuration + +```yaml +jellyfin_libraries: + - host_path: "/storage/movies" + container_path: /movies + - host_path: "/storage/tv_shows" + container_path: /tv_shows + - host_path: "/storage/anime" + container_path: /anime +``` + +## Example + +```yaml +- role: patrickj.docker_apps.jellyfin + vars: + shared_media_path: /storage/media + jellyfin_gpu_passthrough: true + jellyfin_libraries: + - host_path: /storage/media/movies + container_path: /movies +``` diff --git a/roles/jellyfin/defaults/main.yaml b/roles/jellyfin/defaults/main.yaml new file mode 100644 index 0000000..7e6f07e --- /dev/null +++ b/roles/jellyfin/defaults/main.yaml @@ -0,0 +1,22 @@ +--- +app_name: jellyfin + +# Container configuration +jellyfin_container_name: "{{ app_name | default('jellyfin') }}" +jellyfin_container_version: latest +jellyfin_restart_policy: "{{ app_restart_policy }}" +jellyfin_uid: "{{ app_uid }}" +jellyfin_gid: "{{ app_gid }}" + +# Network configuration +jellyfin_http_port: 8096 +jellyfin_discovery_port: 7359 + +# Volume paths +jellyfin_config_path: "{{ app_dir }}/config" +jellyfin_cache_path: "{{ app_dir }}/cache" + +# App-specific configuration +jellyfin_gpu_passthrough: false +jellyfin_libraries: [] + diff --git a/roles/jellyfin/meta/main.yaml b/roles/jellyfin/meta/main.yaml new file mode 100644 index 0000000..d63e348 --- /dev/null +++ b/roles/jellyfin/meta/main.yaml @@ -0,0 +1,16 @@ +--- +galaxy_info: + author: Patrick Jaroszewski + description: Deploy Jellyfin with Docker Compose + license: MIT + +dependencies: + - role: patrickj.docker_apps.docker_compose_app + vars: + app_role_name: jellyfin + app_subdirectories: + - "{{ jellyfin_config_path }}" + - "{{ jellyfin_cache_path }}" + app_backup_subdirectories: + - "{{ jellyfin_config_path }}" + - "{{ jellyfin_cache_path }}" diff --git a/roles/jellyfin/templates/compose.yml.j2 b/roles/jellyfin/templates/compose.yml.j2 new file mode 100644 index 0000000..bb1a610 --- /dev/null +++ b/roles/jellyfin/templates/compose.yml.j2 @@ -0,0 +1,33 @@ +--- +services: + jellyfin: + image: "jellyfin/jellyfin:{{ jellyfin_container_version }}" + container_name: "{{ jellyfin_container_name }}" + user: {{ jellyfin_uid }}:{{ jellyfin_gid }} + restart: {{ jellyfin_restart_policy }} + ports: + - {{ jellyfin_http_port }}:8096/tcp + - {{ jellyfin_discovery_port }}:7359/udp + volumes: + - "{{ jellyfin_config_path }}:/config" + - "{{ jellyfin_cache_path }}:/cache" +{% for item in jellyfin_libraries %} + - "{{ item.host_path }}:{{ item.container_path }}:ro" +{% endfor %} + +{% if jellyfin_server_url %} + # Optional - alternative address used for autodiscovery + environment: + - JELLYFIN_PublishedServerUrl={{ jellyfin_server_url }} +{% endif %} + +{% if jellyfin_gpu_passthrough %} + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: 1 + capabilities: [gpu] +{% endif %} + diff --git a/roles/linkwarden/README.md b/roles/linkwarden/README.md new file mode 100644 index 0000000..d4ef3ca --- /dev/null +++ b/roles/linkwarden/README.md @@ -0,0 +1,35 @@ +# Linkwarden + +Deploy Linkwarden bookmark manager using Docker Compose. + +## Description + +Linkwarden is a self-hosted bookmark manager with tagging, collections, and full-text search capabilities. + +## Variables + +### Required (Set in Vault) + +| Variable | Description | +|----------|-------------| +| `linkwarden_nextauth_secret` | NextAuth authentication secret | +| `linkwarden_postgres_password` | PostgreSQL database password | +| `linkwarden_meili_master_key` | Meilisearch master key for search functionality | + +### Optional + +| Variable | Default | Description | +|----------|---------|-------------| +| `linkwarden_http_port` | `3000` | Web interface port | +| `linkwarden_data_path` | `{{ app_dir }}/data` | Application data storage path | +| `linkwarden_pgdata_path` | `{{ app_dir }}/pgdata` | PostgreSQL data storage path | +| `linkwarden_meili_data_path` | `{{ app_dir }}/meili_data` | Meilisearch data storage path | +| `linkwarden_container_version` | `latest` | Linkwarden Docker image tag | + +## Example + +```yaml +- role: patrickj.docker_apps.linkwarden + vars: + linkwarden_http_port: 3001 +``` diff --git a/roles/linkwarden/defaults/main.yml b/roles/linkwarden/defaults/main.yml new file mode 100644 index 0000000..c64ad86 --- /dev/null +++ b/roles/linkwarden/defaults/main.yml @@ -0,0 +1,20 @@ +--- +app_name: linkwarden + +# Container configuration +linkwarden_container_name: "{{ app_name | default('linkwarden') }}" +linkwarden_container_version: latest +linkwarden_restart_policy: "{{ app_restart_policy }}" + +# Network configuration +linkwarden_http_port: 3000 + +# Volume paths +linkwarden_data_path: "{{ app_dir }}/data" +linkwarden_pgdata_path: "{{ app_dir }}/pgdata" +linkwarden_meili_data_path: "{{ app_dir }}/meili_data" + +# App-specific configuration +linkwarden_nextauth_secret: change-me! +linkwarden_postgres_password: change-me! +linkwarden_meili_master_key: change-me! diff --git a/roles/linkwarden/meta/main.yml b/roles/linkwarden/meta/main.yml new file mode 100644 index 0000000..0b5964f --- /dev/null +++ b/roles/linkwarden/meta/main.yml @@ -0,0 +1,22 @@ +--- +galaxy_info: + author: Patrick Jaroszewski + description: Deploy Linkwarden with Docker Compose + license: MIT + +dependencies: + - role: patrickj.docker_apps.docker_compose_app + vars: + app_role_name: linkwarden + app_subdirectories: + - "{{ linkwarden_data_path }}" + - "{{ linkwarden_pgdata_path }}" + - "{{ linkwarden_meili_data_path }}" + app_backup_subdirectories: + - "{{ linkwarden_data_path }}" + - "{{ linkwarden_pgdata_path }}" + - "{{ linkwarden_meili_data_path }}" + app_extra_templates: + - src: "{{ app_templates_path }}/.env.j2" + dest: "{{ app_dir }}/.env" + app_compose_validate: false # Issues validating when needing a .env diff --git a/roles/linkwarden/templates/.env.j2 b/roles/linkwarden/templates/.env.j2 new file mode 100644 index 0000000..2681db2 --- /dev/null +++ b/roles/linkwarden/templates/.env.j2 @@ -0,0 +1,473 @@ +NEXTAUTH_URL=http://localhost:3000/api/v1/auth +NEXTAUTH_SECRET={{ linkwarden_nextauth_secret }} + +# Manual installation database settings +# Example: DATABASE_URL=postgresql://user:password@localhost:5432/linkwarden +DATABASE_URL= + +# Docker installation database settings +POSTGRES_PASSWORD={{ linkwarden_postgres_password }} + +# Additional Optional Settings +PAGINATION_TAKE_COUNT= +STORAGE_FOLDER= +AUTOSCROLL_TIMEOUT= +NEXT_PUBLIC_DISABLE_REGISTRATION= +NEXT_PUBLIC_CREDENTIALS_ENABLED= +DISABLE_NEW_SSO_USERS= +MAX_LINKS_PER_USER= +ARCHIVE_TAKE_COUNT= +BROWSER_TIMEOUT= +IGNORE_UNAUTHORIZED_CA= +IGNORE_HTTPS_ERRORS= +IGNORE_URL_SIZE_LIMIT= +NEXT_PUBLIC_DEMO= +NEXT_PUBLIC_DEMO_USERNAME= +NEXT_PUBLIC_DEMO_PASSWORD= +NEXT_PUBLIC_ADMIN= +NEXT_PUBLIC_MAX_FILE_BUFFER= +PDF_MAX_BUFFER= +SCREENSHOT_MAX_BUFFER= +READABILITY_MAX_BUFFER= +PREVIEW_MAX_BUFFER= +MONOLITH_MAX_BUFFER= +MONOLITH_CUSTOM_OPTIONS= +IMPORT_LIMIT= +PLAYWRIGHT_LAUNCH_OPTIONS_EXECUTABLE_PATH= +PLAYWRIGHT_WS_URL= +MAX_WORKERS= +DISABLE_PRESERVATION= +NEXT_PUBLIC_RSS_POLLING_INTERVAL_MINUTES= +RSS_SUBSCRIPTION_LIMIT_PER_USER= +TEXT_CONTENT_LIMIT= +SEARCH_FILTER_LIMIT= +INDEX_TAKE_COUNT= +MEILI_TIMEOUT= + +# AI Settings +NEXT_PUBLIC_OLLAMA_ENDPOINT_URL= +OLLAMA_MODEL= + +# https://ai-sdk.dev/providers/openai-compatible-providers +OPENAI_API_KEY= +OPENAI_MODEL= +# Optional: Set a custom OpenAI base URL and name (for third-party providers) +CUSTOM_OPENAI_BASE_URL= +CUSTOM_OPENAI_NAME= + +# https://sdk.vercel.ai/providers/ai-sdk-providers/azure +AZURE_API_KEY= +AZURE_RESOURCE_NAME= +AZURE_MODEL= + +# https://sdk.vercel.ai/providers/ai-sdk-providers/anthropic +ANTHROPIC_API_KEY= +ANTHROPIC_MODEL= + +# https://github.com/OpenRouterTeam/ai-sdk-provider +OPENROUTER_API_KEY= +OPENROUTER_MODEL= + +# https://ai-sdk.dev/providers/ai-sdk-providers/perplexity +PERPLEXITY_API_KEY= +PERPLEXITY_MODEL= + +# MeiliSearch Settings +MEILI_HOST= +MEILI_MASTER_KEY={{ linkwarden_meili_master_key }} + +# AWS S3 Settings +SPACES_KEY= +SPACES_SECRET= +SPACES_ENDPOINT= +SPACES_BUCKET_NAME= +SPACES_REGION= +SPACES_FORCE_PATH_STYLE= + +# SMTP Settings +NEXT_PUBLIC_EMAIL_PROVIDER= +EMAIL_FROM= +EMAIL_SERVER= +BASE_URL= + +# Proxy settings +PROXY= +PROXY_USERNAME= +PROXY_PASSWORD= +PROXY_BYPASS= + +# PDF archive settings +PDF_MARGIN_TOP= +PDF_MARGIN_BOTTOM= + +################# +# SSO Providers # +################# + +# 42 School +NEXT_PUBLIC_FORTYTWO_ENABLED= +FORTYTWO_CUSTOM_NAME= +FORTYTWO_CLIENT_ID= +FORTYTWO_CLIENT_SECRET= + +# Apple +NEXT_PUBLIC_APPLE_ENABLED= +APPLE_CUSTOM_NAME= +APPLE_ID= +APPLE_SECRET= + +# Atlassian +NEXT_PUBLIC_ATLASSIAN_ENABLED= +ATLASSIAN_CUSTOM_NAME= +ATLASSIAN_CLIENT_ID= +ATLASSIAN_CLIENT_SECRET= +ATLASSIAN_SCOPE= + +# Auth0 +NEXT_PUBLIC_AUTH0_ENABLED= +AUTH0_CUSTOM_NAME= +AUTH0_ISSUER= +AUTH0_CLIENT_SECRET= +AUTH0_CLIENT_ID= + +# Authelia +NEXT_PUBLIC_AUTHELIA_ENABLED= +AUTHELIA_CLIENT_ID= +AUTHELIA_CLIENT_SECRET= +AUTHELIA_WELLKNOWN_URL= + +# Authentik +NEXT_PUBLIC_AUTHENTIK_ENABLED= +AUTHENTIK_CUSTOM_NAME= +AUTHENTIK_ISSUER= +AUTHENTIK_CLIENT_ID= +AUTHENTIK_CLIENT_SECRET= + +# Azure AD B2C +NEXT_PUBLIC_AZURE_AD_B2C_ENABLED= +AZURE_AD_B2C_TENANT_NAME= +AZURE_AD_B2C_CLIENT_ID= +AZURE_AD_B2C_CLIENT_SECRET= +AZURE_AD_B2C_PRIMARY_USER_FLOW= + +# Azure AD +NEXT_PUBLIC_AZURE_AD_ENABLED= +AZURE_AD_CLIENT_ID= +AZURE_AD_CLIENT_SECRET= +AZURE_AD_TENANT_ID= + +# Battle.net +NEXT_PUBLIC_BATTLENET_ENABLED= +BATTLENET_CUSTOM_NAME= +BATTLENET_CLIENT_ID= +BATTLENET_CLIENT_SECRET= +BATTLENET_ISSUER= + +# Box +NEXT_PUBLIC_BOX_ENABLED= +BOX_CUSTOM_NAME= +BOX_CLIENT_ID= +BOX_CLIENT_SECRET= + +# Bungie +NEXT_PUBLIC_BUNGIE_ENABLED= +BUNGIE_CUSTOM_NAME= +BUNGIE_CLIENT_ID= +BUNGIE_CLIENT_SECRET= +BUNGIE_API_KEY= + +# Cognito +NEXT_PUBLIC_COGNITO_ENABLED= +COGNITO_CUSTOM_NAME= +COGNITO_CLIENT_ID= +COGNITO_CLIENT_SECRET= +COGNITO_ISSUER= + +# Coinbase +NEXT_PUBLIC_COINBASE_ENABLED= +COINBASE_CUSTOM_NAME= +COINBASE_CLIENT_ID= +COINBASE_CLIENT_SECRET= + +# Discord +NEXT_PUBLIC_DISCORD_ENABLED= +DISCORD_CUSTOM_NAME= +DISCORD_CLIENT_ID= +DISCORD_CLIENT_SECRET= + +# Dropbox +NEXT_PUBLIC_DROPBOX_ENABLED= +DROPBOX_CUSTOM_NAME= +DROPBOX_CLIENT_ID= +DROPBOX_CLIENT_SECRET= + +# DuendeIndentityServer6 +NEXT_PUBLIC_DUENDE_IDS6_ENABLED= +DUENDE_IDS6_CUSTOM_NAME= +DUENDE_IDS6_CLIENT_ID= +DUENDE_IDS6_CLIENT_SECRET= +DUENDE_IDS6_ISSUER= + +# EVE Online +NEXT_PUBLIC_EVEONLINE_ENABLED= +EVEONLINE_CUSTOM_NAME= +EVEONLINE_CLIENT_ID= +EVEONLINE_CLIENT_SECRET= + +# Facebook +NEXT_PUBLIC_FACEBOOK_ENABLED= +FACEBOOK_CUSTOM_NAME= +FACEBOOK_CLIENT_ID= +FACEBOOK_CLIENT_SECRET= + +# FACEIT +NEXT_PUBLIC_FACEIT_ENABLED= +FACEIT_CUSTOM_NAME= +FACEIT_CLIENT_ID= +FACEIT_CLIENT_SECRET= + +# Foursquare +NEXT_PUBLIC_FOURSQUARE_ENABLED= +FOURSQUARE_CUSTOM_NAME= +FOURSQUARE_CLIENT_ID= +FOURSQUARE_CLIENT_SECRET= +FOURSQUARE_APIVERSION= + +# Freshbooks +NEXT_PUBLIC_FRESHBOOKS_ENABLED= +FRESHBOOKS_CUSTOM_NAME= +FRESHBOOKS_CLIENT_ID= +FRESHBOOKS_CLIENT_SECRET= + +# FusionAuth +NEXT_PUBLIC_FUSIONAUTH_ENABLED= +FUSIONAUTH_CUSTOM_NAME= +FUSIONAUTH_CLIENT_ID= +FUSIONAUTH_CLIENT_SECRET= +FUSIONAUTH_ISSUER= +FUSIONAUTH_TENANT_ID= + +# GitHub +NEXT_PUBLIC_GITHUB_ENABLED= +GITHUB_CUSTOM_NAME= +GITHUB_ID= +GITHUB_SECRET= + +# GitLab +NEXT_PUBLIC_GITLAB_ENABLED= +GITLAB_CUSTOM_NAME= +GITLAB_CLIENT_ID= +GITLAB_CLIENT_SECRET= +GITLAB_AUTH_URL= + +# Google +NEXT_PUBLIC_GOOGLE_ENABLED= +GOOGLE_CUSTOM_NAME= +GOOGLE_CLIENT_ID= +GOOGLE_CLIENT_SECRET= + +# HubSpot +NEXT_PUBLIC_HUBSPOT_ENABLED= +HUBSPOT_CUSTOM_NAME= +HUBSPOT_CLIENT_ID= +HUBSPOT_CLIENT_SECRET= + +# IdentityServer4 +NEXT_PUBLIC_IDS4_ENABLED= +IDS4_CUSTOM_NAME= +IDS4_CLIENT_ID= +IDS4_CLIENT_SECRET= +IDS4_ISSUER= + +# Kakao +NEXT_PUBLIC_KAKAO_ENABLED= +KAKAO_CUSTOM_NAME= +KAKAO_CLIENT_ID= +KAKAO_CLIENT_SECRET= + +# Keycloak +NEXT_PUBLIC_KEYCLOAK_ENABLED= +KEYCLOAK_CUSTOM_NAME= +KEYCLOAK_ISSUER= +KEYCLOAK_CLIENT_ID= +KEYCLOAK_CLIENT_SECRET= + +# LINE +NEXT_PUBLIC_LINE_ENABLED= +LINE_CUSTOM_NAME= +LINE_CLIENT_ID= +LINE_CLIENT_SECRET= + +# LinkedIn +NEXT_PUBLIC_LINKEDIN_ENABLED= +LINKEDIN_CUSTOM_NAME= +LINKEDIN_CLIENT_ID= +LINKEDIN_CLIENT_SECRET= + +# Mailchimp +NEXT_PUBLIC_MAILCHIMP_ENABLED= +MAILCHIMP_CUSTOM_NAME= +MAILCHIMP_CLIENT_ID= +MAILCHIMP_CLIENT_SECRET= + +# Mail.ru +NEXT_PUBLIC_MAILRU_ENABLED= +MAILRU_CUSTOM_NAME= +MAILRU_CLIENT_ID= +MAILRU_CLIENT_SECRET= + +# Naver +NEXT_PUBLIC_NAVER_ENABLED= +NAVER_CUSTOM_NAME= +NAVER_CLIENT_ID= +NAVER_CLIENT_SECRET= + +# Netlify +NEXT_PUBLIC_NETLIFY_ENABLED= +NETLIFY_CUSTOM_NAME= +NETLIFY_CLIENT_ID= +NETLIFY_CLIENT_SECRET= + +# Okta +NEXT_PUBLIC_OKTA_ENABLED= +OKTA_CUSTOM_NAME= +OKTA_CLIENT_ID= +OKTA_CLIENT_SECRET= +OKTA_ISSUER= + +# OneLogin +NEXT_PUBLIC_ONELOGIN_ENABLED= +ONELOGIN_CUSTOM_NAME= +ONELOGIN_CLIENT_ID= +ONELOGIN_CLIENT_SECRET= +ONELOGIN_ISSUER= + +# Osso +NEXT_PUBLIC_OSSO_ENABLED= +OSSO_CUSTOM_NAME= +OSSO_CLIENT_ID= +OSSO_CLIENT_SECRET= +OSSO_ISSUER= + +# osu! +NEXT_PUBLIC_OSU_ENABLED= +OSU_CUSTOM_NAME= +OSU_CLIENT_ID= +OSU_CLIENT_SECRET= + +# Patreon +NEXT_PUBLIC_PATREON_ENABLED= +PATREON_CUSTOM_NAME= +PATREON_CLIENT_ID= +PATREON_CLIENT_SECRET= + +# Pinterest +NEXT_PUBLIC_PINTEREST_ENABLED= +PINTEREST_CUSTOM_NAME= +PINTEREST_CLIENT_ID= +PINTEREST_CLIENT_SECRET= + +# Pipedrive +NEXT_PUBLIC_PIPEDRIVE_ENABLED= +PIPEDRIVE_CUSTOM_NAME= +PIPEDRIVE_CLIENT_ID= +PIPEDRIVE_CLIENT_SECRET= + +# Reddit +NEXT_PUBLIC_REDDIT_ENABLED= +REDDIT_CUSTOM_NAME= +REDDIT_CLIENT_ID= +REDDIT_CLIENT_SECRET= + +# Salesforce +NEXT_PUBLIC_SALESFORCE_ENABLED= +SALESFORCE_CUSTOM_NAME= +SALESFORCE_CLIENT_ID= +SALESFORCE_CLIENT_SECRET= + +# Slack +NEXT_PUBLIC_SLACK_ENABLED= +SLACK_CUSTOM_NAME= +SLACK_CLIENT_ID= +SLACK_CLIENT_SECRET= + +# Spotify +NEXT_PUBLIC_SPOTIFY_ENABLED= +SPOTIFY_CUSTOM_NAME= +SPOTIFY_CLIENT_ID= +SPOTIFY_CLIENT_SECRET= + +# Strava +NEXT_PUBLIC_STRAVA_ENABLED= +STRAVA_CUSTOM_NAME= +STRAVA_CLIENT_ID= +STRAVA_CLIENT_SECRET= + +# Synology +NEXT_PUBLIC_SYNOLOGY_ENABLED= +SYNOLOGY_CUSTOM_NAME= +SYNOLOGY_CLIENT_ID= +SYNOLOGY_CLIENT_SECRET= +SYNOLOGY_WELLKNOWN_URL= + +# Todoist +NEXT_PUBLIC_TODOIST_ENABLED= +TODOIST_CUSTOM_NAME= +TODOIST_CLIENT_ID= +TODOIST_CLIENT_SECRET= + +# Twitch +NEXT_PUBLIC_TWITCH_ENABLED= +TWITCH_CUSTOM_NAME= +TWITCH_CLIENT_ID= +TWITCH_CLIENT_SECRET= + +# United Effects +NEXT_PUBLIC_UNITED_EFFECTS_ENABLED= +UNITED_EFFECTS_CUSTOM_NAME= +UNITED_EFFECTS_CLIENT_ID= +UNITED_EFFECTS_CLIENT_SECRET= +UNITED_EFFECTS_ISSUER= + +# VK +NEXT_PUBLIC_VK_ENABLED= +VK_CUSTOM_NAME= +VK_CLIENT_ID= +VK_CLIENT_SECRET= + +# Wikimedia +NEXT_PUBLIC_WIKIMEDIA_ENABLED= +WIKIMEDIA_CUSTOM_NAME= +WIKIMEDIA_CLIENT_ID= +WIKIMEDIA_CLIENT_SECRET= + +# Wordpress.com +NEXT_PUBLIC_WORDPRESS_ENABLED= +WORDPRESS_CUSTOM_NAME= +WORDPRESS_CLIENT_ID= +WORDPRESS_CLIENT_SECRET= + +# Yandex +NEXT_PUBLIC_YANDEX_ENABLED= +YANDEX_CUSTOM_NAME= +YANDEX_CLIENT_ID= +YANDEX_CLIENT_SECRET= + +# Zitadel +NEXT_PUBLIC_ZITADEL_ENABLED= +ZITADEL_CUSTOM_NAME= +ZITADEL_CLIENT_ID= +ZITADEL_CLIENT_SECRET= +ZITADEL_ISSUER= + +# Zoho +NEXT_PUBLIC_ZOHO_ENABLED= +ZOHO_CUSTOM_NAME= +ZOHO_CLIENT_ID= +ZOHO_CLIENT_SECRET= + +# Zoom +NEXT_PUBLIC_ZOOM_ENABLED= +ZOOM_CUSTOM_NAME= +ZOOM_CLIENT_ID= +ZOOM_CLIENT_SECRET= \ No newline at end of file diff --git a/roles/linkwarden/templates/compose.yml.j2 b/roles/linkwarden/templates/compose.yml.j2 new file mode 100644 index 0000000..13935ec --- /dev/null +++ b/roles/linkwarden/templates/compose.yml.j2 @@ -0,0 +1,27 @@ +services: + postgres: + image: postgres:16-alpine + env_file: .env + restart: {{ linkwarden_restart_policy }} + volumes: + - {{ linkwarden_pgdata_path }}:/var/lib/postgresql/data + linkwarden: + env_file: .env + environment: + - DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/postgres + restart: {{ linkwarden_restart_policy }} + image: ghcr.io/linkwarden/linkwarden:{{ linkwarden_container_version }} + ports: + - {{ linkwarden_http_port }}:3000 + volumes: + - {{ linkwarden_data_path }}:/data/data + depends_on: + - postgres + - meilisearch + meilisearch: + image: getmeili/meilisearch:v1.12.8 + restart: {{ linkwarden_restart_policy }} + env_file: + - .env + volumes: + - {{ linkwarden_meili_data_path }}:/meili_data \ No newline at end of file diff --git a/roles/mealie/README.md b/roles/mealie/README.md new file mode 100644 index 0000000..35981ba --- /dev/null +++ b/roles/mealie/README.md @@ -0,0 +1,27 @@ +# Mealie + +Deploy Mealie recipe manager using Docker Compose. + +## Description + +Mealie is a self-hosted recipe manager with meal planning, shopping lists, and nutrition tracking. + +## Variables + +### Optional + +| Variable | Default | Description | +|----------|---------|-------------| +| `mealie_http_port` | `9925` | Web interface port | +| `mealie_base_url` | `http://localhost:8080` | Base URL for application links and redirects | +| `mealie_data_path` | `{{ app_dir }}/data` | Application data storage path | +| `mealie_container_version` | `latest` | Mealie Docker image tag | +| `mealie_extra_env_vars` | `{}` | Additional environment variables as key-value pairs | + +## Example + +```yaml +- role: patrickj.docker_apps.mealie + vars: + mealie_base_url: "https://recipes.example.com" +``` diff --git a/roles/mealie/defaults/main.yml b/roles/mealie/defaults/main.yml new file mode 100644 index 0000000..4f2e468 --- /dev/null +++ b/roles/mealie/defaults/main.yml @@ -0,0 +1,21 @@ +--- +app_name: mealie + +# Container configuration +mealie_container_name: "{{ app_name | default('mealie') }}" +mealie_container_version: latest +mealie_restart_policy: "{{ app_restart_policy }}" +mealie_user: "{{ app_uid }}" +mealie_group: "{{ app_gid }}" + +# Network configuration +mealie_http_port: 9925 + +# Volume paths +mealie_data_path: "{{ app_dir }}/data" + +# App-specific configuration +mealie_timezone: "{{ app_timezone_cc }}" +mealie_base_url: http://localhost:8080 +mealie_extra_env_vars: {} + # ALLOW_SIGNUP: "false" diff --git a/roles/mealie/meta/main.yml b/roles/mealie/meta/main.yml new file mode 100644 index 0000000..0998e2c --- /dev/null +++ b/roles/mealie/meta/main.yml @@ -0,0 +1,14 @@ +--- +galaxy_info: + author: Patrick Jaroszewski + description: Deploy Mealie with Docker Compose + license: MIT + +dependencies: + - role: patrickj.docker_apps.docker_compose_app + vars: + app_role_name: mealie + app_subdirectories: + - "{{ mealie_data_path }}" + app_backup_subdirectories: + - "{{ mealie_data_path }}" diff --git a/roles/mealie/templates/compose.yml.j2 b/roles/mealie/templates/compose.yml.j2 new file mode 100644 index 0000000..0081539 --- /dev/null +++ b/roles/mealie/templates/compose.yml.j2 @@ -0,0 +1,25 @@ +--- +services: + mealie: + image: ghcr.io/mealie-recipes/mealie:{{ mealie_container_version }} + container_name: {{ mealie_container_name }} + restart: {{ mealie_restart_policy }} + ports: + - "{{ mealie_http_port }}:9000" + + deploy: + resources: + limits: + memory: 1000M + + volumes: + - "{{ mealie_data_path }}:/app/data/" + environment: + # Set Backend ENV Variables Here + PUID: {{ mealie_user }} + PGID: {{ mealie_group }} + TZ: {{ mealie_timezone }} + BASE_URL: {{ mealie_base_url }} +{% for item in mealie_extra_env_vars %} + {{ item.key }}: {{ item.value }} +{% endfor %} diff --git a/roles/navidrome/README.md b/roles/navidrome/README.md new file mode 100644 index 0000000..5332f92 --- /dev/null +++ b/roles/navidrome/README.md @@ -0,0 +1,34 @@ +# Navidrome + +Deploy Navidrome music streaming server using Docker Compose. + +## Description + +Navidrome is a modern music server compatible with Subsonic/Airsonic clients, with web interface and mobile app support. + +## Variables + +### Required +| Variable | Default | Description | +|----------|---------|-------------| +| `navidrome_libraries` | `[]` | List of music library mappings with `host_path` and `container_path` | + +### Optional + +| Variable | Default | Description | +|----------|---------|-------------| +| `navidrome_http_port` | `4533` | Web interface port | +| `navidrome_data_path` | `{{ app_dir }}/data` | Database and application data storage | +| `navidrome_base_url` | `""` | Base URL if accessing through reverse proxy | +| `navidrome_container_version` | `latest` | Navidrome Docker image tag | + + +## Example + +```yaml +- role: patrickj.docker_apps.navidrome + vars: + navidrome_libraries: + - host_path: /storage/music + container_path: /music +``` diff --git a/roles/navidrome/defaults/main.yaml b/roles/navidrome/defaults/main.yaml new file mode 100644 index 0000000..554ddd1 --- /dev/null +++ b/roles/navidrome/defaults/main.yaml @@ -0,0 +1,21 @@ +--- +app_name: navidrome + +# Container configuration +navidrome_container_name: "{{ app_name | default('navidrome') }}" +navidrome_container_version: latest +navidrome_restart_policy: "{{ app_restart_policy }}" +navidrome_uid: "{{ app_uid }}" +navidrome_gid: "{{ app_gid }}" + +# Network configuration +navidrome_http_port: 4533 + +# Volume paths +navidrome_data_path: "{{ app_dir }}/data" + +# App-specific configuration +navidrome_base_url: "" +navidrome_libraries: [] + # - host_path: /path/to/music + # container_path: /music diff --git a/roles/navidrome/meta/main.yaml b/roles/navidrome/meta/main.yaml new file mode 100644 index 0000000..738a4aa --- /dev/null +++ b/roles/navidrome/meta/main.yaml @@ -0,0 +1,14 @@ +--- +galaxy_info: + author: Patrick Jaroszewski + description: Deploy Navidrome with Docker Compose + license: MIT + +dependencies: + - role: patrickj.docker_apps.docker_compose_app + vars: + app_role_name: navidrome + app_subdirectories: + - "{{ navidrome_data_path }}" + app_backup_subdirectories: + - "{{ navidrome_data_path }}" diff --git a/roles/navidrome/templates/compose.yml.j2 b/roles/navidrome/templates/compose.yml.j2 new file mode 100644 index 0000000..7b484b4 --- /dev/null +++ b/roles/navidrome/templates/compose.yml.j2 @@ -0,0 +1,22 @@ +--- +services: + navidrome: + image: deluan/navidrome:{{ navidrome_container_version }} + container_name: "{{ navidrome_container_name }}" + user: {{ navidrome_uid }}:{{ navidrome_gid }} # should be owner of volumes + restart: "{{ navidrome_restart_policy }}" + ports: + - "{{ navidrome_http_port }}:4533" + environment: + # Optional: put your config options customization here. Examples: + ND_SCANSCHEDULE: 1h + ND_LOGLEVEL: info + ND_SESSIONTIMEOUT: 24h +{% if navidrome_base_url is defined %} + ND_BASEURL: "{{ navidrome_base_url }}" +{% endif %} + volumes: + - "{{ navidrome_data_path }}:/data" +{% for item in navidrome_libraries %} + - "{{ item.host_path }}:{{ item.container_path }}:ro" +{% endfor %} diff --git a/roles/open-webui/README.md b/roles/open-webui/README.md new file mode 100644 index 0000000..48dbad8 --- /dev/null +++ b/roles/open-webui/README.md @@ -0,0 +1,29 @@ +# Open WebUI + +Deploy Open WebUI for LLM interactions using Docker Compose. + +## Description + +Open WebUI is a user-friendly web interface for Large Language Models, compatible with OpenAI API. + +## Variables + +### Optional + +| Variable | Default | Description | +|----------|---------|-------------| +| `open_webui_http_port` | `5000` | Web interface port | +| `open_webui_data_path` | `{{ app_dir }}/data` | Application data storage | +| `open_webui_container_version` | `main` | Open WebUI Docker image tag | +| `open_webui_env_vars` | See defaults | Dictionary of environment variables | + +## Example + +```yaml +- role: patrickj.docker_apps.open-webui + vars: + open_webui_http_port: 8080 + open_webui_env_vars: + OLLAMA_BASE_URL: http://localhost:11434 + BYPASS_MODEL_ACCESS_CONTROL: "true" +``` diff --git a/roles/open-webui/defaults/main.yml b/roles/open-webui/defaults/main.yml new file mode 100644 index 0000000..36f1c7a --- /dev/null +++ b/roles/open-webui/defaults/main.yml @@ -0,0 +1,18 @@ +--- +app_name: open-webui + +# Container configuration +open_webui_container_name: "{{ app_name | default('open-webui') }}" +open_webui_container_version: main +open_webui_restart_policy: "{{ app_restart_policy }}" + +# Network configuration +open_webui_http_port: 5000 + +# Volume paths +open_webui_data_path: "{{ app_dir }}/data" + +# App-specific configuration +open_webui_env_vars: + OLLAMA_BASE_URL: http://localhost:11434 + BYPASS_MODEL_ACCESS_CONTROL: "true" diff --git a/roles/open-webui/meta/main.yml b/roles/open-webui/meta/main.yml new file mode 100644 index 0000000..8f7e305 --- /dev/null +++ b/roles/open-webui/meta/main.yml @@ -0,0 +1,14 @@ +--- +galaxy_info: + author: Patrick Jaroszewski + description: Deploy Open-WebUI with Docker Compose + license: MIT + +dependencies: + - role: patrickj.docker_apps.docker_compose_app + vars: + app_role_name: open-webui + app_subdirectories: + - "{{ open_webui_data_path }}" + app_backup_subdirectories: + - "{{ open_webui_data_path }}" diff --git a/roles/open-webui/templates/compose.yml.j2 b/roles/open-webui/templates/compose.yml.j2 new file mode 100644 index 0000000..dd4eaee --- /dev/null +++ b/roles/open-webui/templates/compose.yml.j2 @@ -0,0 +1,18 @@ +--- + +services: + openWebUI: + image: ghcr.io/open-webui/open-webui:{{ open_webui_container_version }} + container_name: {{ open_webui_container_name }} + restart: {{ open_webui_restart_policy }} + ports: + - "{{ open_webui_http_port }}:8080" + volumes: + - "{{ open_webui_data_path }}:/app/backend/data" + +{% if open_webui_env_vars | length > 0 %} + environment: +{% for item in open_webui_env_vars | dict2items %} + - {{ item.key }}={{ item.value }} +{% endfor %} +{% endif %} \ No newline at end of file diff --git a/roles/paperless-ngx/README.md b/roles/paperless-ngx/README.md new file mode 100644 index 0000000..f828c44 --- /dev/null +++ b/roles/paperless-ngx/README.md @@ -0,0 +1,40 @@ +# Paperless-ngx + +Deploy Paperless-ngx document management system using Docker Compose. + +## Description + +Paperless-ngx is a document management system that transforms your physical documents into a searchable online archive. + +## Variables + +## Required + +| Variable | Default | Description | +|----------|---------|-------------| +| `paperless_db_password` | `paperless` | PostgreSQL password | +| `paperless_consume_path` | `{{ app_dir }}/consume` | Document intake directory | + +### Optional + +| Variable | Default | Description | +|----------|---------|-------------| +| `paperless_http_port` | `8000` | Web interface port | +| `paperless_data_path` | `{{ app_dir }}/data` | Application data storage | +| `paperless_media_path` | `{{ app_dir }}/media` | Processed document storage | +| `paperless_export_path` | `{{ app_dir }}/export` | Document export directory | +| `paperless_pgdata_path` | `{{ app_dir }}/pgdata` | PostgreSQL data storage | +| `paperless_redisdata_path` | `{{ app_dir }}/redisdata` | Redis data storage | +| `paperless_container_version` | `latest` | Paperless-ngx Docker image tag | +| `paperless_db_name` | `paperless` | PostgreSQL database name | +| `paperless_db_user` | `paperless` | PostgreSQL username | + +## Example + +```yaml +- role: patrickj.docker_apps.paperless-ngx + vars: + paperless_http_port: 8080 + paperless_db_password: "{{ vault_paperless_db_password }}" + paperless_consume_path: /path/to/consume +``` \ No newline at end of file diff --git a/roles/paperless-ngx/defaults/main.yml b/roles/paperless-ngx/defaults/main.yml new file mode 100644 index 0000000..b9631dc --- /dev/null +++ b/roles/paperless-ngx/defaults/main.yml @@ -0,0 +1,23 @@ +--- +app_name: paperless-ngx + +# Container configuration +paperless_container_name: "{{ app_name | default('paperless-ngx') }}" +paperless_container_version: latest +paperless_restart_policy: "{{ app_restart_policy }}" + +# Network configuration +paperless_http_port: 8000 + +# Volume paths +paperless_data_path: "{{ app_dir }}/data" +paperless_media_path: "{{ app_dir }}/media" +paperless_export_path: "{{ app_dir }}/export" +paperless_consume_path: "{{ app_dir }}/consume" +paperless_pgdata_path: "{{ app_dir }}/pgdata" +paperless_redisdata_path: "{{ app_dir }}/redisdata" + +# App-specific configuration +paperless_db_name: paperless +paperless_db_user: paperless +paperless_db_password: paperless diff --git a/roles/paperless-ngx/meta/main.yml b/roles/paperless-ngx/meta/main.yml new file mode 100644 index 0000000..02c5ccb --- /dev/null +++ b/roles/paperless-ngx/meta/main.yml @@ -0,0 +1,23 @@ +--- +galaxy_info: + author: Patrick Jaroszewski + description: Deploy Mealie with Docker Compose + license: MIT + +dependencies: + - role: patrickj.docker_apps.docker_compose_app + vars: + app_role_name: paperless-ngx + app_subdirectories: + - "{{ paperless_data_path }}" + - "{{ paperless_media_path }}" + - "{{ paperless_export_path}}" + - "{{ paperless_consume_path}}" + - "{{ paperless_pgdata_path}}" + - "{{ paperless_redisdata_path}}" + app_backup_subdirectories: + - "{{ paperless_data_path }}" + - "{{ paperless_media_path }}" + - "{{ paperless_export_path}}" + - "{{ paperless_pgdata_path}}" + - "{{ paperless_redisdata_path}}" diff --git a/roles/paperless-ngx/templates/compose.yml.j2 b/roles/paperless-ngx/templates/compose.yml.j2 new file mode 100644 index 0000000..80434be --- /dev/null +++ b/roles/paperless-ngx/templates/compose.yml.j2 @@ -0,0 +1,50 @@ +--- +services: + broker: + image: docker.io/library/redis:8 + restart: {{ paperless_restart_policy }} + volumes: + - "{{ paperless_redisdata_path }}:/data" + db: + image: docker.io/library/postgres:18 + restart: {{ paperless_restart_policy }} + volumes: + - "{{ paperless_pgdata_path }}:/var/lib/postgresql" + environment: + POSTGRES_DB: paperless + POSTGRES_USER: paperless + POSTGRES_PASSWORD: paperless + webserver: + image: ghcr.io/paperless-ngx/paperless-ngx:{{ paperless_container_version }} + restart: {{ paperless_restart_policy }} + depends_on: + - db + - broker + - gotenberg + - tika + ports: + - "{{ paperless_http_port }}:8000" + volumes: + - "{{ paperless_data_path }}:/usr/src/paperless/data" + - "{{ paperless_media_path }}:/usr/src/paperless/media" + - "{{ paperless_export_path }}:/usr/src/paperless/export" + - "{{ paperless_consume_path }}:/usr/src/paperless/consume" + environment: + COMPOSE_PROJECT_NAME: {{ app_name }} + PAPERLESS_REDIS: redis://broker:6379 + PAPERLESS_DBHOST: db + PAPERLESS_TIKA_ENABLED: 1 + PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000 + PAPERLESS_TIKA_ENDPOINT: http://tika:9998 + gotenberg: + image: docker.io/gotenberg/gotenberg:8.25 + restart: {{ paperless_restart_policy }} + # The gotenberg chromium route is used to convert .eml files. We do not + # want to allow external content like tracking pixels or even javascript. + command: + - "gotenberg" + - "--chromium-disable-javascript=true" + - "--chromium-allow-list=file:///tmp/.*" + tika: + image: docker.io/apache/tika:latest + restart: {{ paperless_restart_policy }} diff --git a/roles/redlib/README.md b/roles/redlib/README.md new file mode 100644 index 0000000..cd0f845 --- /dev/null +++ b/roles/redlib/README.md @@ -0,0 +1,23 @@ +# Redlib + +Deploy Redlib privacy-focused Reddit frontend using Docker Compose. + +## Description + +Redlib is an alternative private front-end to Reddit, providing a clean interface without tracking. + +## Variables + +### Optional + +| Variable | Default | Description | +|----------|---------|-------------| +| `redlib_http_port` | `8080` | Web interface port | +| `redlib_container_version` | `latest` | Redlib Docker image tag | +| `redlib_extra_env_vars` | `{}` | Dictionary of environment variables | + +## Example + +```yaml +- role: patrickj.docker_apps.redlib +``` diff --git a/roles/redlib/defaults/main.yml b/roles/redlib/defaults/main.yml new file mode 100644 index 0000000..d645eb1 --- /dev/null +++ b/roles/redlib/defaults/main.yml @@ -0,0 +1,13 @@ +--- +app_name: redlib + +# Container configuration +redlib_container_name: "{{ app_name | default('redlib') }}" +redlib_container_version: latest +redlib_restart_policy: "{{ app_restart_policy }}" + +# Network configuration +redlib_http_port: 8080 + +# App-specific configuration +redlib_extra_env_vars: {} diff --git a/roles/redlib/meta/main.yml b/roles/redlib/meta/main.yml new file mode 100644 index 0000000..895f26c --- /dev/null +++ b/roles/redlib/meta/main.yml @@ -0,0 +1,11 @@ +--- +galaxy_info: + author: Patrick Jaroszewski + description: Deploy redlib with Docker Compose + license: MIT + +dependencies: + - role: patrickj.docker_apps.docker_compose_app + vars: + app_role_name: redlib + app_health_check: false # Issues with redlib healthcheck diff --git a/roles/redlib/templates/compose.yml.j2 b/roles/redlib/templates/compose.yml.j2 new file mode 100644 index 0000000..2e7ccea --- /dev/null +++ b/roles/redlib/templates/compose.yml.j2 @@ -0,0 +1,30 @@ +--- +services: + redlib: + image: quay.io/redlib/redlib:{{ redlib_container_version }} + restart: {{ redlib_restart_policy }} + container_name: {{ redlib_container_name }} + ports: + - "{{ redlib_http_port }}:8080" + user: nobody + read_only: true + security_opt: + - no-new-privileges:true + # - seccomp=seccomp-redlib.json + cap_drop: + - ALL + networks: + - redlib + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "--tries=1", "http://localhost:8080/settings"] + interval: 5m + timeout: 3s +{% if redlib_extra_env_vars %} + environment: +{% for item in redlib_extra_env_vars | dict2items %} + - {{ item.key }}="{{ item.value }}" +{% endfor %} +{% endif %} + +networks: + redlib: \ No newline at end of file diff --git a/roles/speedtest-tracker/README.md b/roles/speedtest-tracker/README.md new file mode 100644 index 0000000..e6cfe8a --- /dev/null +++ b/roles/speedtest-tracker/README.md @@ -0,0 +1,39 @@ +# Speedtest Tracker + +Deploy Speedtest Tracker internet monitoring tool using Docker Compose. + +## Description + +Speedtest Tracker runs automatic internet speed tests and tracks results over time with beautiful charts. + +## Variables + +### Required +| Variable | Description | +|----------|-------------| +| `speedtest_tracker_app_key` | Application key | + +### Optional + +| Variable | Default | Description | +|----------|---------|-------------| +| `speedtest_tracker_http_port` | `8080` | HTTP web interface port | +| `speedtest_tracker_https_port` | `8443` | HTTPS web interface port | +| `speedtest_tracker_http` | `true` | Enable HTTP port mapping | +| `speedtest_tracker_https` | `false` | Enable HTTPS port mapping | +| `speedtest_tracker_config_path` | `{{ app_dir }}/config` | Configuration and database storage | +| `speedtest_tracker_container_version` | `latest` | Speedtest Tracker Docker image tag | +| `speedtest_tracker_user` | `{{ app_uid }}` | User ID for file permissions | +| `speedtest_tracker_group` | `{{ app_gid }}` | Group ID for file permissions | +| `speedtest_tracker_timezone` | `{{ app_timezone_cc }}` | Display timezone | +| `speedtest_tracker_extra_env_vars` | See defaults | Dictionary of additional environment variables | + + +## Example + +```yaml +- role: patrickj.docker_apps.speedtest-tracker + vars: + speedtest_tracker_extra_env_vars: + SPEEDTEST_SCHEDULE: "0 * * * *" # Run every hour +``` \ No newline at end of file diff --git a/roles/speedtest-tracker/defaults/main.yml b/roles/speedtest-tracker/defaults/main.yml new file mode 100644 index 0000000..b5f2e2b --- /dev/null +++ b/roles/speedtest-tracker/defaults/main.yml @@ -0,0 +1,23 @@ +--- +app_name: speedtest-tracker + +# Container configuration +speedtest_tracker_container_name: "{{ app_name | default('speedtest-tracker') }}" +speedtest_tracker_container_version: latest +speedtest_tracker_restart_policy: "{{ app_restart_policy }}" +speedtest_tracker_user: "{{ app_uid }}" +speedtest_tracker_group: "{{ app_gid }}" + +# Network configuration +speedtest_tracker_http_port: 8080 +speedtest_tracker_https_port: 8443 +speedtest_tracker_http: true +speedtest_tracker_https: false + +# Volume paths +speedtest_tracker_config_path: "{{ app_dir }}/config" + +# App-specific configuration +speedtest_tracker_timezone: "{{ app_timezone_cc }}" +speedtest_tracker_extra_env_vars: + SPEEDTEST_SCHEDULE: "0 * * * *" diff --git a/roles/speedtest-tracker/meta/main.yml b/roles/speedtest-tracker/meta/main.yml new file mode 100644 index 0000000..ed42593 --- /dev/null +++ b/roles/speedtest-tracker/meta/main.yml @@ -0,0 +1,14 @@ +--- +galaxy_info: + author: Patrick Jaroszewski + description: Deploy Speedtest Tracker with Docker Compose + license: MIT + +dependencies: + - role: patrickj.docker_apps.docker_compose_app + vars: + app_role_name: speedtest-tracker + app_subdirectories: + - "{{ speedtest_tracker_config_path }}" + app_backup_subdirectories: + - "{{ speedtest_tracker_config_path }}" diff --git a/roles/speedtest-tracker/templates/compose.yml.j2 b/roles/speedtest-tracker/templates/compose.yml.j2 new file mode 100644 index 0000000..f2113ef --- /dev/null +++ b/roles/speedtest-tracker/templates/compose.yml.j2 @@ -0,0 +1,26 @@ +--- +services: + speedtest-tracker: + image: lscr.io/linuxserver/speedtest-tracker:{{ speedtest_tracker_container_version }} + restart: {{ speedtest_tracker_restart_policy }} + container_name: {{ speedtest_tracker_container_name }} + ports: +{% if speedtest_tracker_http %} + - {{ speedtest_tracker_http_port }}:80 +{% endif %} +{% if speedtest_tracker_https %} + - {{ speedtest_tracker_https_port }}:443 +{% endif %} + environment: + - PUID={{ speedtest_tracker_user }} + - PGID={{ speedtest_tracker_group }} + - APP_KEY={{ speedtest_tracker_app_key }} + - DISPLAY_TIMEZONE={{ speedtest_tracker_timezone }} +{% for item in speedtest_tracker_extra_env_vars | dict2items %} + - {{ item.key }}="{{ item.value }}" +{% endfor %} + volumes: + - {{ speedtest_tracker_config_path }}:/config +{% if speedtest_tracker_ssl_path is defined %} + - {{ speedtest_tracker_ssl_path }}:/config/keys +{% endif %} \ No newline at end of file diff --git a/roles/uptime-kuma/README.md b/roles/uptime-kuma/README.md new file mode 100644 index 0000000..f9c7f59 --- /dev/null +++ b/roles/uptime-kuma/README.md @@ -0,0 +1,25 @@ +# Uptime Kuma + +Deploy Uptime Kuma monitoring tool using Docker Compose. + +## Description + +Uptime Kuma is a fancy self-hosted monitoring tool with notifications and status pages. + +## Variables + +### Optional + +| Variable | Default | Description | +|----------|---------|-------------| +| `uptime_kuma_http_port` | `3001` | Web interface port | +| `uptime_kuma_data_path` | `{{ app_dir }}/data` | Application data storage | +| `uptime_kuma_container_version` | `2` | Uptime Kuma Docker image tag | + +## Example + +```yaml +- role: patrickj.docker_apps.uptime-kuma + vars: + uptime_kuma_http_port: 3002 +``` diff --git a/roles/uptime-kuma/defaults/main.yml b/roles/uptime-kuma/defaults/main.yml new file mode 100644 index 0000000..2313dce --- /dev/null +++ b/roles/uptime-kuma/defaults/main.yml @@ -0,0 +1,13 @@ +--- +app_name: uptime-kuma + +# Container configuration +uptime_kuma_container_name: "{{ app_name | default('uptime-kuma') }}" +uptime_kuma_container_version: 2 +uptime_kuma_restart_policy: "{{ app_restart_policy }}" + +# Network configuration +uptime_kuma_http_port: 3001 + +# Volume paths +uptime_kuma_data_path: "{{ app_dir }}/data" diff --git a/roles/uptime-kuma/meta/main.yml b/roles/uptime-kuma/meta/main.yml new file mode 100644 index 0000000..075fb8a --- /dev/null +++ b/roles/uptime-kuma/meta/main.yml @@ -0,0 +1,14 @@ +--- +galaxy_info: + author: Patrick Jaroszewski + description: Deploy Uptime Kuma with Docker Compose + license: MIT + +dependencies: + - role: patrickj.docker_apps.docker_compose_app + vars: + app_role_name: uptime-kuma + app_subdirectories: + - "{{ uptime_kuma_data_path }}" + app_backup_subdirectories: + - "{{ uptime_kuma_data_path }}" diff --git a/roles/uptime-kuma/templates/compose.yml.j2 b/roles/uptime-kuma/templates/compose.yml.j2 new file mode 100644 index 0000000..4b331ed --- /dev/null +++ b/roles/uptime-kuma/templates/compose.yml.j2 @@ -0,0 +1,10 @@ +--- +services: + uptime-kuma: + image: louislam/uptime-kuma:{{ uptime_kuma_container_version }} + container_name: "{{ uptime_kuma_container_name }}" + restart: "{{ uptime_kuma_restart_policy }}" + volumes: + - "{{ uptime_kuma_data_path }}:/app/data" + ports: + - "{{ uptime_kuma_http_port }}:3001" \ No newline at end of file