├── .github ├── FUNDING.yml ├── ansible │ └── playbook-ci.yml └── workflows │ ├── release.yml │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── galaxy.yml ├── meta └── runtime.yml └── roles ├── adguardhome ├── README.md ├── defaults │ └── main.yml ├── files │ └── adguardhome.conf ├── tasks │ ├── adguardhome.yml │ ├── main.yml │ └── resolved.yml └── vars │ └── main.yml ├── authelia ├── README.md ├── defaults │ └── main.yml ├── tasks │ └── main.yml ├── templates │ ├── configuration.yml.j2 │ └── users_database.yml.j2 └── vars │ └── main.yml ├── changedetection ├── README.md ├── defaults │ └── main.yml ├── tasks │ ├── changedetection.yml │ ├── main.yml │ └── selenium.yml └── vars │ └── main.yml ├── cloudflare ├── README.md ├── defaults │ └── main.yml ├── tasks │ ├── cloudflared-ssh.yml │ ├── cloudflared.yml │ └── main.yml └── vars │ └── main.yml ├── docker ├── README.md ├── defaults │ └── main.yml ├── tasks │ ├── autobackup.yml │ ├── autoprune.yml │ ├── main.yml │ └── unmanage-network.yml ├── templates │ ├── docker-backup.sh.j2 │ ├── docker-networkd.j2 │ ├── docker-prune.sh.j2 │ └── docker-restore.sh.j2 └── vars │ └── Ubuntu.yml ├── duplicati ├── README.md ├── defaults │ └── main.yml ├── tasks │ └── main.yml └── vars │ └── main.yml ├── filebrowser ├── README.md ├── defaults │ └── main.yml ├── tasks │ └── main.yml └── vars │ └── main.yml ├── forgejo ├── README.md ├── defaults │ └── main.yml ├── tasks │ └── main.yml └── vars │ └── main.yml ├── glances ├── README.md ├── defaults │ └── main.yml ├── files │ └── notify.py ├── tasks │ └── main.yml ├── templates │ └── glances.conf.j2 └── vars │ └── main.yml ├── homarr ├── README.md ├── defaults │ └── main.yml └── tasks │ └── main.yml ├── homebox ├── README.md ├── defaults │ └── main.yml ├── tasks │ ├── homebox.yml │ └── main.yml └── vars │ └── main.yml ├── nginx ├── README.md ├── defaults │ └── main.yml ├── tasks │ └── main.yml └── vars │ └── main.yml ├── olivetin ├── README.md ├── defaults │ └── main.yml ├── handlers │ └── main.yml ├── tasks │ └── main.yml ├── templates │ └── config.yaml.j2 └── vars │ └── main.yml ├── paperless_ai ├── README.md ├── defaults │ └── main.yml ├── tasks │ └── main.yml └── vars │ └── main.yml ├── paperlessngx ├── README.md ├── defaults │ └── main.yml ├── tasks │ └── main.yml └── vars │ └── main.yml ├── postiz ├── README.md ├── defaults │ └── main.yml ├── tasks │ └── main.yml └── vars │ └── main.yml ├── rss ├── README.md ├── defaults │ └── main.yml ├── tasks │ ├── main.yml │ ├── miniflux.yml │ └── rssbridge.yml ├── templates │ └── rssbridge-whitelist.txt.j2 └── vars │ └── main.yml ├── vaultwarden ├── README.md ├── defaults │ └── main.yml ├── tasks │ └── main.yml └── vars │ └── main.yml ├── wallos ├── README.md ├── defaults │ └── main.yml ├── tasks │ ├── main.yml │ └── wallos.yml └── vars │ └── main.yml ├── watchtower ├── README.md ├── defaults │ └── main.yml ├── tasks │ ├── main.yml │ └── watchtower.yml └── vars │ └── main.yml └── wgeasy ├── README.md ├── defaults └── main.yml ├── tasks └── main.yml └── vars └── main.yml /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | buy_me_a_coffee: artyorsh 2 | -------------------------------------------------------------------------------- /.github/ansible/playbook-ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "GitHub CI Playbook" 3 | hosts: "localhost" 4 | become: true 5 | 6 | vars: 7 | forgejo_docker_settings: 8 | # FIXME: this is needed to fix '/var/lib/gitea/git': Permission denied errors. 9 | puid: "{{ puid }}" 10 | pgid: "{{ pgid }}" 11 | 12 | postiz_docker_settings: 13 | # TODO: Make sure the database is accessible at `postiz-db:5432` 14 | network: "postiz" 15 | 16 | wgeasy_docker_settings: 17 | # TODO: wg-easy requires net.ipv4.conf.all.src_valid_mark=1 sysctl which is not allowed on host network. 18 | network: "wgeasy" 19 | 20 | pre_tasks: 21 | - include_role: 22 | name: "artyorsh.selfhosted.docker" 23 | 24 | roles: 25 | - { role: "artyorsh.selfhosted.adguardhome", tags: "adguardhome" } 26 | - { role: "artyorsh.selfhosted.authelia", tags: "authelia" } 27 | - { role: "artyorsh.selfhosted.changedetection", tags: "changedetection" } 28 | - { role: "artyorsh.selfhosted.duplicati", tags: "duplicati" } 29 | - { role: "artyorsh.selfhosted.filebrowser", tags: "filebrowser" } 30 | - { role: "artyorsh.selfhosted.forgejo", tags: "forgejo" } 31 | - { role: "artyorsh.selfhosted.glances", tags: "glances" } 32 | - { role: "artyorsh.selfhosted.homarr", tags: "homarr" } 33 | - { role: "artyorsh.selfhosted.homebox", tags: "homebox" } 34 | - { role: "artyorsh.selfhosted.nginx", tags: "nginx" } 35 | - { role: "artyorsh.selfhosted.olivetin", tags: "olivetin" } 36 | - { role: "artyorsh.selfhosted.paperless_ai", tags: "paperless_ai" } 37 | - { role: "artyorsh.selfhosted.paperlessngx", tags: "paperlessngx" } 38 | - { role: "artyorsh.selfhosted.postiz", tags: "postiz" } 39 | - { role: "artyorsh.selfhosted.rss", tags: "rss" } 40 | - { role: "artyorsh.selfhosted.vaultwarden", tags: "vaultwarden" } 41 | - { role: "artyorsh.selfhosted.wallos", tags: "wallos" } 42 | - { role: "artyorsh.selfhosted.watchtower", tags: "watchtower" } 43 | - { role: "artyorsh.selfhosted.wgeasy", tags: "wgeasy" } 44 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Release" 3 | 4 | on: 5 | push: 6 | branches: 7 | - "master" 8 | 9 | permissions: 10 | contents: "write" 11 | 12 | jobs: 13 | check_version_update: 14 | runs-on: "ubuntu-latest" 15 | 16 | outputs: 17 | version: ${{ steps.galaxy_yml_version.outputs.value }} 18 | is_updated: ${{ steps.compare_versions.outputs.value }} 19 | 20 | steps: 21 | - uses: "actions/checkout@v4" 22 | 23 | - name: "Install PyYAML" 24 | run: pip install pyyaml 25 | 26 | - name: "Read version from galaxy.yml" 27 | id: galaxy_yml_version 28 | run: | 29 | import yaml 30 | import os 31 | with open('galaxy.yml', 'r') as file: 32 | data = yaml.safe_load(file) 33 | with open(os.environ['GITHUB_OUTPUT'], 'a') as output_file: 34 | output_file.write(f"value={data['version']}\n") 35 | shell: "python" 36 | 37 | - name: "Compare with latest git tag" 38 | id: compare_versions 39 | run: | 40 | latest_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo 'v0.0.0') 41 | 42 | if [[ "${{ steps.galaxy_yml_version.outputs.value }}" != "$latest_tag" ]]; then 43 | echo "value=true" >> $GITHUB_OUTPUT 44 | else 45 | echo "value=false" >> $GITHUB_OUTPUT 46 | fi 47 | 48 | publish: 49 | runs-on: "ubuntu-latest" 50 | needs: check_version_update 51 | if: needs.check_version_update.outputs.is_updated == 'true' 52 | 53 | steps: 54 | - uses: "actions/checkout@v4" 55 | 56 | - name: "Build the Collection" 57 | run: ansible-galaxy collection build 58 | 59 | - name: "Publish to Galaxy" 60 | run: >- 61 | ansible-galaxy collection publish 62 | --api-key ${{ secrets.GALAXY_API_KEY }} 63 | ./artyorsh-selfhosted-${{ needs.check_version_update.outputs.version }}.tar.gz 64 | 65 | - name: "Publish a new tag" 66 | run: | 67 | git config --global user.name "github-actions[bot]" 68 | git config --global user.email "github-actions[bot]@users.noreply.github.com" 69 | git tag ${{ needs.check_version_update.outputs.version }} 70 | git push origin ${{ needs.check_version_update.outputs.version }} 71 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Test" 3 | 4 | on: 5 | pull_request: 6 | branches: 7 | - "master" 8 | paths-ignore: 9 | - "**.md" 10 | 11 | push: 12 | branches: 13 | - "master" 14 | paths-ignore: 15 | - "**.md" 16 | 17 | schedule: 18 | - cron: "0 7 * * 0" 19 | 20 | jobs: 21 | test: 22 | runs-on: "ubuntu-22.04" 23 | 24 | strategy: 25 | matrix: 26 | role: 27 | - { container_name: "adguardhome", tags: "adguardhome" } 28 | - { container_name: "authelia", tags: "authelia" } 29 | - { container_name: "changedetection", tags: "changedetection" } 30 | - { container_name: "duplicati", tags: "duplicati" } 31 | - { container_name: "filebrowser", tags: "filebrowser" } 32 | - { container_name: "glances", tags: "glances" } 33 | - { container_name: "forgejo", tags: "forgejo" } 34 | - { container_name: "homebox", tags: "homebox" } 35 | - { container_name: "miniflux", tags: "rss" } 36 | - { container_name: "rssbridge", tags: "rss" } 37 | - { container_name: "nginx-proxy-manager", tags: "nginx" } 38 | - { container_name: "olivetin", tags: "olivetin" } 39 | - { container_name: "paperless-ai", tags: "paperless_ai" } 40 | - { container_name: "paperlessngx", tags: "paperlessngx" } 41 | - { container_name: "postiz", tags: "postiz" } 42 | - { container_name: "vaultwarden", tags: "vaultwarden" } 43 | - { container_name: "wallos", tags: "wallos" } 44 | - { container_name: "watchtower", tags: "watchtower" } 45 | - { container_name: "wg-easy", tags: "wgeasy" } 46 | 47 | steps: 48 | - uses: "actions/checkout@v4" 49 | 50 | - name: "Print Ansible environment" 51 | run: "ansible --version" 52 | 53 | - name: "Install dependencies" 54 | run: | 55 | # fix ModuleNotFoundError: No module named 'requests' 56 | /opt/pipx/venvs/ansible-core/bin/python -m pip install requests 57 | 58 | ansible-galaxy install geerlingguy.docker 59 | ansible-galaxy collection install community.docker 60 | ansible-galaxy collection install community.general 61 | ansible-galaxy collection install . -f 62 | 63 | - name: "Run a role" 64 | run: | 65 | ansible-playbook .github/ansible/playbook-ci.yml \ 66 | --tags ${{ matrix.role.tags }} \ 67 | -i localhost \ 68 | -e "ansible_user=${USER}" \ 69 | -e "puid=$(id -u)" \ 70 | -e "pgid=$(id -g)" \ 71 | -v 72 | 73 | - name: "Wait for containers to start" 74 | run: "sleep 15" 75 | 76 | - name: "Print container logs" 77 | run: | 78 | docker ps -a 79 | docker logs ${{ matrix.role.container_name }} 80 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .ansible 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Artur Yersh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ansible Collection - Selfhosted 2 | 3 | [![Ansible Galaxy](https://img.shields.io/badge/collection-artyorsh.selfhosted-blue)](https://galaxy.ansible.com/artyorsh/selfhosted) 4 | [![Test](https://github.com/artyorsh/ansible-collection-selfhosted/actions/workflows/test.yml/badge.svg?event=push)](https://github.com/artyorsh/ansible-collection-selfhosted/actions/workflows/test.yml) 5 | 6 | An opinionated collection of selfhosted apps, managed with Ansible. 7 | 8 | ## Roles 9 | 10 | - [adguardhome](./roles/adguardhome/README.md) 11 | - [authelia](./roles/authelia/README.md) 12 | - [changedetection](./roles/changedetection/README.md) 13 | - [cloudflare](./roles/cloudflare/README.md) 14 | - [docker](./roles/docker/README.md) 15 | - [duplicati](./roles/duplicati/README.md) 16 | - [filebrowser](./roles/filebrowser/README.md) 17 | - [forgejo](./roles/forgejo/README.md) 18 | - [glances](./roles/glances/README.md) 19 | - [homebox](./roles/homebox/README.md) 20 | - [homarr](./roles/homarr/README.md) 21 | - [nginx](./roles/nginx/README.md) 22 | - [olivetin](./roles/olivetin/README.md) 23 | - [paperless_ai](./roles/paperless_ai/README.md) 24 | - [paperlessngx](./roles/paperlessngx/README.md) 25 | - [postiz](./roles/postiz/README.md) 26 | - [rss](./roles/rss/README.md) 27 | - [vaultwarden](./roles/vaultwarden/README.md) 28 | - [wallos](./roles/wallos/README.md) 29 | - [watchtower](./roles/watchtower/README.md) 30 | - [wg-easy](./roles/wgeasy/README.md) 31 | 32 | ## Installation 33 | 34 | ``` 35 | ansible-galaxy collection install artyorsh.selfhosted 36 | ``` 37 | 38 | ## Example Playbook 39 | 40 | See more examples in roles' README files. 41 | 42 | ```yaml 43 | - hosts: localhost 44 | 45 | roles: 46 | - artyorsh.selfhosted.docker 47 | - artyorsh.selfhosted.duplicati 48 | - artyorsh.selfhosted.watchtower 49 | ``` 50 | 51 | ## More collections 52 | 53 | - [artyorsh.smarthome](https://github.com/artyorsh/ansible-collection-smarthome) 54 | 55 | ## License 56 | 57 | MIT -------------------------------------------------------------------------------- /galaxy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | namespace: artyorsh 3 | 4 | name: selfhosted 5 | 6 | version: 1.0.2 7 | 8 | readme: README.md 9 | 10 | authors: 11 | - artyorsh 12 | 13 | description: An opinionated collection of selfhosted apps, managed with Ansible. 14 | 15 | license: 16 | - MIT 17 | 18 | tags: 19 | - selfhosted 20 | - docker 21 | - adguardhome 22 | - authelia 23 | - changedetection 24 | - cloudflare 25 | - duplicati 26 | - filebrowser 27 | - forgejo 28 | - glances 29 | - homebox 30 | - nginx 31 | - olivetin 32 | - paperless_ai 33 | - paperlessngx 34 | - rss 35 | - vaultwarden 36 | - wallos 37 | - watchtower 38 | - wgeasy 39 | 40 | dependencies: 41 | "community.docker": ">=3.0.0" 42 | 43 | repository: https://github.com/artyorsh/ansible-collection-selfhosted 44 | documentation: https://github.com/artyorsh/ansible-collection-selfhosted 45 | homepage: https://artyorsh.me 46 | issues: https://github.com/artyorsh/ansible-collection-selfhosted/issues 47 | 48 | build_ignore: [] 49 | -------------------------------------------------------------------------------- /meta/runtime.yml: -------------------------------------------------------------------------------- 1 | --- 2 | requires_ansible: ">=2.17.3" 3 | -------------------------------------------------------------------------------- /roles/adguardhome/README.md: -------------------------------------------------------------------------------- 1 | # selfhosted.adguardhome 2 | 3 | Installs [AdGuardHome](https://github.com/AdguardTeam/AdGuardHome) - a network-wide ads & trackers blocking DNS server. 4 | 5 | > [!NOTE] 6 | > This role is not actively maintained as it is currently not in use (since November 2024). 7 | > Bugs may be present due to incompatibility with the latest versions of AdGuardHome image. 8 | 9 | > [!WARNING] 10 | > This role modifies `/etc/resolv.conf` file to bind AdGuardHome on port 53. 11 | > See [Note on resolved daemon and adguardhome_dns_port](#note-on-resolved-daemon-and-adguardhome_dns_port). 12 | 13 | ## Role Variables 14 | 15 | - `adguardhome_version` 16 | - Default: `latest` 17 | - Description: The version of AdGuardHome to install. See [tags](https://hub.docker.com/r/adguard/adguardhome/tags). 18 | - Type: str 19 | - Required: no 20 | - `adguardhome_dns_port` 21 | - Default: `53` 22 | - Description: The port on which AdGuardHome will listen for DNS requests. See [docs](https://github.com/AdguardTeam/AdGuardHome/wiki/Docker#create-and-run-the-container). See [Note on resolved daemon and adguardhome_dns_port](#note-on-resolved-daemon-and-adguardhome_dns_port). 23 | - Type: int 24 | - Required: no 25 | - `adguardhome_webui_port` 26 | - Default: `3000` 27 | - Description: The port on which AdGuardHome WebUI will be accessible. 28 | - Type: int 29 | - Required: no 30 | - `adguardhome_install_dir` 31 | - Default: `/opt/docker/adguardhome` 32 | - Description: The directory where AdGuardHome will be installed. 33 | - Type: str 34 | - Required: no 35 | - `adguardhome_env` 36 | - Default: See [adguardhome_env_default](./vars/main.yml) 37 | - Description: Docker container environment variables. 38 | - Type: dict 39 | - Required: no 40 | - `adguardhome_docker_settings` 41 | - Default: See [adguardhome_docker_settings_default](./vars/main.yml) 42 | - Description: Docker container settings. 43 | - Type: dict 44 | - Required: no 45 | 46 | ## Dependencies 47 | 48 | - [community.docker](https://docs.ansible.com/ansible/latest/collections/community/docker/index.html) 49 | 50 | ## Example Playbook 51 | 52 | ```yaml 53 | - hosts: localhost 54 | 55 | roles: 56 | - artyorsh.selfhosted.adguardhome 57 | ``` 58 | 59 | ## Note on resolved daemon and adguardhome_dns_port 60 | 61 | If the system is runing resolved daemon, docker will fail to bind on port 53. For this reason a new resolv.conf file is created. See [docs](https://github.com/AdguardTeam/AdGuardHome/wiki/Docker#resolved) and [the task](./tasks/resolved.yml). -------------------------------------------------------------------------------- /roles/adguardhome/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://hub.docker.com/r/adguard/adguardhome/tags 3 | adguardhome_version: "latest" 4 | adguardhome_install_dir: "/opt/docker/adguardhome" 5 | 6 | # https://github.com/AdguardTeam/AdGuardHome/wiki/Docker#create-and-run-the-container 7 | adguardhome_dns_port: 53 8 | # a permanent webui port is set during onboarding 9 | adguardhome_webui_port: 3000 10 | 11 | adguardhome_work_dir: "{{ adguardhome_install_dir }}/work" 12 | adguardhome_config_dir: "{{ adguardhome_install_dir }}/conf" 13 | 14 | adguardhome_env: {} 15 | 16 | adguardhome_docker_settings: {} 17 | -------------------------------------------------------------------------------- /roles/adguardhome/files/adguardhome.conf: -------------------------------------------------------------------------------- 1 | [Resolve] 2 | DNS=127.0.0.1 3 | DNSStubListener=no -------------------------------------------------------------------------------- /roles/adguardhome/tasks/adguardhome.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Prepare task variables" 3 | ansible.builtin.set_fact: 4 | adguardhome_service_id: "adguardhome" 5 | adguardhome_docker_settings: "{{ adguardhome_docker_settings_default | combine(adguardhome_docker_settings) }}" 6 | 7 | - name: "Make sure config directories exist" 8 | ansible.builtin.file: 9 | path: "{{ item }}" 10 | owner: "{{ ansible_user }}" 11 | group: "{{ ansible_user }}" 12 | state: "directory" 13 | mode: "0700" 14 | loop: 15 | - "{{ adguardhome_work_dir }}" 16 | - "{{ adguardhome_config_dir }}" 17 | 18 | - name: "Make sure {{ adguardhome_docker_settings.network }} docker network exists" 19 | when: adguardhome_docker_settings.network != "host" 20 | community.general.docker_network: 21 | name: "{{ adguardhome_docker_settings.network }}" 22 | state: "present" 23 | 24 | - name: "Run adguardhome container" 25 | community.general.docker_container: 26 | name: "{{ adguardhome_service_id }}" 27 | image: "adguard/adguardhome:{{ adguardhome_version }}" 28 | networks: 29 | - name: "{{ adguardhome_docker_settings.network }}" 30 | state: "started" 31 | env: "{{ adguardhome_env_default | combine(adguardhome_env) }}" 32 | ports: 33 | - "{{ adguardhome_dns_port }}:53/tcp" 34 | - "{{ adguardhome_dns_port }}:53/udp" 35 | - "{{ adguardhome_webui_port }}:3000/tcp" 36 | volumes: 37 | - "{{ adguardhome_work_dir }}:/opt/adguardhome/work" 38 | - "{{ adguardhome_config_dir }}:/opt/adguardhome/conf" 39 | restart_policy: "unless-stopped" 40 | -------------------------------------------------------------------------------- /roles/adguardhome/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Install and configure AdGuardHome" 3 | ansible.builtin.include_tasks: "adguardhome.yml" 4 | 5 | - name: "Configure resolved to use local DNS" 6 | ansible.builtin.include_tasks: "resolved.yml" -------------------------------------------------------------------------------- /roles/adguardhome/tasks/resolved.yml: -------------------------------------------------------------------------------- 1 | # https://github.com/AdguardTeam/AdGuardHome/wiki/Docker#resolved 2 | --- 3 | - name: "Prepare task variables" 4 | ansible.builtin.set_fact: 5 | adguardhome_resolved_dir: "/etc/systemd/resolved.conf.d" 6 | adguardhome_resolv_path: "/etc/resolv.conf" 7 | 8 | - name: "Make sure resolved.conf.d/adguardhome.conf exists" 9 | block: 10 | - ansible.builtin.file: 11 | path: "{{ adguardhome_resolved_dir }}" 12 | state: "directory" 13 | owner: "root" 14 | group: "root" 15 | mode: "0644" 16 | 17 | - ansible.builtin.copy: 18 | src: "files/adguardhome.conf" 19 | dest: "{{ adguardhome_resolved_dir }}/adguardhome.conf" 20 | owner: "root" 21 | group: "root" 22 | mode: "0644" 23 | 24 | - name: "Activate a new resolv.conf" 25 | block: 26 | - name: "Backup existing resolv.conf" 27 | ansible.builtin.command: "mv {{ adguardhome_resolv_path }} {{ adguardhome_resolv_path }}.backup" 28 | args: 29 | creates: "{{ adguardhome_resolv_path }}.backup" 30 | removes: "{{ adguardhome_resolv_path }}" 31 | 32 | - name: "Symlink original /run/systemd/resolve/resolv.conf" 33 | ansible.builtin.command: "ln -sf /run/systemd/resolve/resolv.conf {{ adguardhome_resolv_path }}" 34 | 35 | - name: "Disable DNSStubListener (/etc/systemd/resolved.conf)" 36 | ansible.builtin.lineinfile: 37 | dest: "/etc/systemd/resolved.conf" 38 | regexp: "{{ item.regexp }}" 39 | line: "{{ item.line }}" 40 | state: "present" 41 | loop: 42 | - regexp: "#?DNS=" 43 | line: "DNS=127.0.0.1" 44 | - regexp: "#?DNSStubListener=" 45 | line: "DNSStubListener=no" 46 | 47 | - name: "Reload resolved service" 48 | ansible.builtin.systemd: 49 | name: "systemd-resolved" 50 | state: "restarted" 51 | -------------------------------------------------------------------------------- /roles/adguardhome/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | adguardhome_docker_settings_default: 3 | network: "host" 4 | puid: "1000" 5 | pgid: "1000" 6 | tz: "Etc/UTC" 7 | 8 | adguardhome_env_default: 9 | TZ: "{{ adguardhome_docker_settings.tz }}" 10 | PUID: "{{ adguardhome_docker_settings.puid }}" 11 | PGID: "{{ adguardhome_docker_settings.pgid }}" 12 | -------------------------------------------------------------------------------- /roles/authelia/README.md: -------------------------------------------------------------------------------- 1 | # selfhosted.authelia 2 | 3 | Installs [Authelia](https://www.authelia.com) service - an open-source authentication and authorization server. 4 | 5 | > [!NOTE] 6 | > This role is not actively maintained as it is currently not in use (since November 2024). 7 | > Bugs may be present due to incompatibility with the latest versions of Authelia image. 8 | 9 | ## Role Variables 10 | 11 | - `authelia_version` 12 | - Default: `latest` 13 | - Description: The version of Authelia to install. See [tags](https://hub.docker.com/r/authelia/authelia/tags). 14 | - Type: str 15 | - Required: no 16 | - `authelia_port` 17 | - Default: `9091` 18 | - Description: The port on which Authelia will be accessible. 19 | - Type: int 20 | - Required: no 21 | - `authelia_install_dir` 22 | - Default: `/opt/docker/authelia` 23 | - Description: The directory where Authelia will be installed. 24 | - Type: str 25 | - Required: no 26 | - `authelia_env` 27 | - Default: See [authelia_env_default](./vars/main.yml) 28 | - Description: Docker container environment variables. See [docs](https://www.authelia.com/configuration/methods/environment/#environment-variables). 29 | - Type: dict 30 | - Required: no 31 | - `authelia_config` 32 | - Default: See [authelia_default_config](./vars/main.yml) 33 | - Description: Authelia configuration. See [docs](https://www.authelia.com/integration/prologue/get-started/#configuration). 34 | - Type: dict 35 | - Required: no 36 | - `authelia_users` 37 | - Default: See [authelia_users](./defaults/main.yml) 38 | - Description: Authelia users create for the [file authentication backend](https://www.authelia.com/configuration/first-factor/introduction/). 39 | - Type: dict 40 | - Required: no 41 | - `authelia_docker_settings` 42 | - Default: See [authelia_docker_settings_default](./vars/main.yml) 43 | - Description: Docker container settings. 44 | - Type: dict 45 | - Required: no 46 | 47 | ## Dependencies 48 | 49 | - [community.docker](https://docs.ansible.com/ansible/latest/collections/community/docker/index.html) 50 | 51 | ## Example Playbook 52 | 53 | ```yaml 54 | - hosts: localhost 55 | 56 | vars: 57 | domain: "example.com" 58 | authelia_users: 59 | users: 60 | admin: 61 | displayname: "Admin" 62 | email: "admin@{{ domain }}" 63 | # supersecret, salt=supersecret 64 | # https://argon2.online 65 | password: "$argon2id$v=19$m=1024,t=1,p=8$c3VwZXJzZWNyZXQ$3DK6bsCIIa3MWorb4zatsQ" 66 | groups: ["admins"] 67 | 68 | authelia_config: 69 | access_control: 70 | default_policy: "one_factor" 71 | rules: 72 | - { domain: "auth.{{ domain }}", policy: "bypass" } 73 | 74 | identity_validation: 75 | reset_password: 76 | # https://www.authelia.com/configuration/identity-validation/reset-password/#jwt_secret 77 | jwt_secret: "supersecret" 78 | 79 | totp: 80 | issuer: "{{ domain }}" 81 | 82 | session: 83 | # https://www.authelia.com/configuration/session/introduction/#secret 84 | secret: "supersecret" 85 | cookies: 86 | - { domain: "{{ domain }}", authelia_url: "https://auth.{{ domain }}" } 87 | 88 | storage: 89 | # https://www.authelia.com/configuration/storage/introduction/#encryption_key 90 | encryption_key: "supersecret" 91 | 92 | authentication_backend: 93 | password_reset: 94 | disable: true 95 | 96 | roles: 97 | - artyorsh.selfhosted.authelia 98 | ``` -------------------------------------------------------------------------------- /roles/authelia/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://hub.docker.com/r/authelia/authelia/tags 3 | authelia_version: "latest" 4 | authelia_port: 9091 5 | authelia_install_dir: "/opt/docker/authelia" 6 | 7 | # https://www.authelia.com/configuration/methods/environment/#environment-variables 8 | authelia_env: {} 9 | authelia_docker_settings: {} 10 | 11 | # https://www.authelia.com/integration/prologue/get-started/#configuration 12 | authelia_config: {} 13 | authelia_users: 14 | users: 15 | admin: 16 | displayname: "Admin" 17 | email: "admin@example.com" 18 | # "changeme" encrypted with https://argon2.online 19 | # the following settings should be in sync with authentication_backend.file.password (see vars/main.yml) 20 | # iterations=1, key_length=32, salt_length=16, memory=1024, parallelism=8 21 | password: "$argon2id$v=19$m=1024,t=1,p=8$RjhzeXBseWdGcDZlVGpoRA$DjQLS0n+X5mt9WgKUdSeM/aHIa4BNJg6tooiyeKZy7w" 22 | groups: ["admins"] 23 | -------------------------------------------------------------------------------- /roles/authelia/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Prepare task variables" 3 | ansible.builtin.set_fact: 4 | authelia_service_id: "authelia" 5 | authelia_docker_settings: "{{ authelia_docker_settings_default | combine(authelia_docker_settings) }}" 6 | 7 | - name: "Make sure {{ authelia_docker_settings.network }} docker network exists" 8 | when: authelia_docker_settings.network != "host" 9 | community.general.docker_network: 10 | name: "{{ authelia_docker_settings.network }}" 11 | state: "present" 12 | 13 | - name: "Make sure config directories exist" 14 | ansible.builtin.file: 15 | path: "{{ item }}" 16 | owner: "{{ ansible_user }}" 17 | group: "{{ ansible_user }}" 18 | state: "directory" 19 | mode: "0744" 20 | loop: 21 | - "{{ authelia_install_dir }}" 22 | 23 | - name: "Template configuration.yml" 24 | ansible.builtin.template: 25 | src: "configuration.yml.j2" 26 | dest: "{{ authelia_install_dir }}/configuration.yml" 27 | owner: "{{ ansible_user }}" 28 | group: "{{ ansible_user }}" 29 | mode: "0740" 30 | 31 | - name: "Template users_database.yml" 32 | ansible.builtin.template: 33 | src: "users_database.yml.j2" 34 | dest: "{{ authelia_install_dir }}/users_database.yml" 35 | owner: "{{ ansible_user }}" 36 | group: "{{ ansible_user }}" 37 | mode: "0740" 38 | 39 | - name: "Run authelia container" 40 | community.general.docker_container: 41 | name: "{{ authelia_service_id }}" 42 | image: "authelia/authelia:{{ authelia_version }}" 43 | networks: 44 | - name: "{{ authelia_docker_settings.network }}" 45 | state: "started" 46 | env: "{{ authelia_env_default | combine(authelia_env) }}" 47 | ports: 48 | - "{{ authelia_port }}:9091" 49 | volumes: 50 | - "{{ authelia_install_dir }}:/config" 51 | restart_policy: "unless-stopped" 52 | -------------------------------------------------------------------------------- /roles/authelia/templates/configuration.yml.j2: -------------------------------------------------------------------------------- 1 | {{ authelia_default_config | combine(authelia_config, recursive=true) }} -------------------------------------------------------------------------------- /roles/authelia/templates/users_database.yml.j2: -------------------------------------------------------------------------------- 1 | {{ authelia_users }} -------------------------------------------------------------------------------- /roles/authelia/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | authelia_docker_settings_default: 3 | network: "host" 4 | puid: "1000" 5 | pgid: "1000" 6 | tz: "Etc/UTC" 7 | 8 | authelia_env_default: 9 | TZ: "{{ authelia_docker_settings.tz }}" 10 | PUID: "{{ authelia_docker_settings.puid }}" 11 | PGID: "{{ authelia_docker_settings.pgid }}" 12 | 13 | # https://github.com/authelia/authelia/blob/master/config.template.yml 14 | authelia_default_config: 15 | # https://www.authelia.com/configuration/identity-validation/reset-password 16 | identity_validation: 17 | reset_password: 18 | jwt_secret: "changeme" 19 | 20 | # https://www.authelia.com/configuration/miscellaneous/server/ 21 | server: 22 | address: "tcp://:9091" 23 | 24 | # https://www.authelia.com/configuration/first-factor/introduction/ 25 | authentication_backend: 26 | refresh_interval: "5m" 27 | file: 28 | path: "/config/users_database.yml" 29 | password: 30 | algorithm: "argon2" 31 | argon2: 32 | variant: "argon2id" 33 | iterations: 1 34 | key_length: 32 35 | salt_length: 16 36 | memory: 1024 37 | parallelism: 8 38 | 39 | # https://www.authelia.com/configuration/session/introduction/ 40 | session: 41 | secret: "changeme" 42 | cookies: 43 | - domain: "example.com" 44 | authelia_url: "https://auth.example.com" 45 | 46 | # https://www.authelia.com/configuration/security/regulation/ 47 | regulation: 48 | max_retries: 3 49 | ban_time: "12h" 50 | 51 | # https://www.authelia.com/configuration/storage/introduction/ 52 | storage: 53 | local: 54 | path: "/config/db.sqlite3" 55 | encryption_key: "changemechangemechangeme" 56 | 57 | # https://www.authelia.com/configuration/notifications/introduction/ 58 | notifier: 59 | filesystem: 60 | filename: "/config/notification.txt" 61 | 62 | # https://www.authelia.com/configuration/security/access-control/ 63 | access_control: 64 | default_policy: "one_factor" 65 | -------------------------------------------------------------------------------- /roles/changedetection/README.md: -------------------------------------------------------------------------------- 1 | # selfhosted.changedetection 2 | 3 | Installs the [ChangeDetection](https://changedetection.io/) and [Selenium](https://hub.docker.com/r/seleniarm/standalone-chromium) containers - a page change monitoring tools with alerts. 4 | 5 | > [!NOTE] 6 | > This role is not actively maintained as it is currently not in use (since June 2024). 7 | > Bugs may be present due to incompatibility with the latest versions of ChangeDetection/Selenium images. 8 | 9 | ## Role Variables 10 | 11 | - `changedetection_version` 12 | - Default: `latest` 13 | - Description: The version of ChangeDetection to install. See [tags](https://hub.docker.com/r/dgtlmoon/changedetection.io/tags). 14 | - Type: str 15 | - Required: no 16 | - `changedetection_webui_port` 17 | - Default: `5000` 18 | - Description: The port on which ChangeDetection will be accessible. 19 | - Type: int 20 | - Required: no 21 | - `changedetection_install_dir` 22 | - Default: `/opt/docker/changedetection` 23 | - Description: The directory where ChangeDetection will be installed. 24 | - Type: str 25 | - Required: no 26 | - `changedetection_env` 27 | - Default: See [changedetection_env_default](./vars/main.yml) 28 | - Description: Docker container environment variables. See [docs](https://changedetection.io/docs/configuration/environment-variables). 29 | - Type: dict 30 | - Required: no 31 | - `changedetection_selenium_version` 32 | - Default: `latest` 33 | - Description: The version of Selenium to install. See [tags](https://hub.docker.com/r/seleniarm/standalone-chromium/tags). 34 | - Type: str 35 | - Required: no 36 | - `changedetection_selenium_port` 37 | - Default: `5001` 38 | - Description: The port on which Selenium will be accessible. 39 | - Type: int 40 | - Required: no 41 | - `changedetection_selenium_env` 42 | - Default: See [changedetection_selenium_env_default](./vars/main.yml) 43 | - Description: Docker container environment variables for Selenium. See [docs](https://github.com/dgtlmoon/changedetection.io/wiki/Fetching-pages-with-WebDriver). 44 | - Type: dict 45 | - Required: no 46 | - `changedetection_docker_settings` 47 | - Default: See [changedetection_docker_settings_default](./vars/main.yml) 48 | - Description: Docker container settings. This is applied to both the ChangeDetection and Selenium containers. 49 | - Type: dict 50 | - Required: no 51 | 52 | ## Dependencies 53 | 54 | - [community.docker](https://docs.ansible.com/ansible/latest/collections/community/docker/index.html) 55 | 56 | ## Example Playbook 57 | 58 | ```yaml 59 | - hosts: localhost 60 | 61 | roles: 62 | - artyorsh.selfhosted.changedetection 63 | ``` -------------------------------------------------------------------------------- /roles/changedetection/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://hub.docker.com/r/dgtlmoon/changedetection.io/tags 3 | changedetection_version: "latest" 4 | changedetection_install_dir: "/opt/docker/changedetection" 5 | changedetection_webui_port: 5000 6 | # https://github.com/dgtlmoon/changedetection.io/blob/master/docker-compose.yml 7 | changedetection_env: {} 8 | 9 | # https://hub.docker.com/r/seleniarm/standalone-chromium/tags 10 | # changedetection_selenium_version: "4.0.0-20211213" 11 | changedetection_selenium_version: "latest" 12 | changedetection_selenium_port: 5001 13 | # # https://github.com/dgtlmoon/changedetection.io/wiki/Fetching-pages-with-WebDriver 14 | changedetection_selenium_env: {} 15 | 16 | changedetection_docker_settings: {} 17 | -------------------------------------------------------------------------------- /roles/changedetection/tasks/changedetection.yml: -------------------------------------------------------------------------------- 1 | # https://hay-kot.github.io/changedetection/quick-start/#env-variables-configuration 2 | --- 3 | 4 | - name: "Make sure config directories exist" 5 | ansible.builtin.file: 6 | path: "{{ item }}" 7 | owner: "{{ ansible_user }}" 8 | group: "{{ ansible_user }}" 9 | state: "directory" 10 | mode: "0750" 11 | loop: 12 | - "{{ changedetection_install_dir }}/datastore" 13 | 14 | - name: "Run changedetection container" 15 | community.general.docker_container: 16 | name: "{{ changedetection_service_id }}" 17 | image: "dgtlmoon/changedetection.io:{{ changedetection_version }}" 18 | networks: 19 | - name: "{{ changedetection_docker_settings.network }}" 20 | state: "started" 21 | env: "{{ changedetection_env_default | combine(changedetection_env) }}" 22 | ports: 23 | - "{{ changedetection_webui_port }}:5000" 24 | volumes: 25 | - "{{ changedetection_install_dir }}/datastore:/datastore" 26 | restart_policy: "unless-stopped" 27 | -------------------------------------------------------------------------------- /roles/changedetection/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Prepare task variables" 3 | ansible.builtin.set_fact: 4 | changedetection_service_id: "changedetection" 5 | changedetection_docker_settings: "{{ changedetection_docker_settings_default | combine(changedetection_docker_settings) }}" 6 | 7 | - name: "Make sure {{ changedetection_docker_settings.network }} docker network exists" 8 | when: changedetection_docker_settings.network != "host" 9 | community.general.docker_network: 10 | name: "{{ changedetection_docker_settings.network }}" 11 | state: "present" 12 | 13 | - name: "Install and configure Selenium" 14 | ansible.builtin.include_tasks: "selenium.yml" 15 | 16 | - name: "Install and configure changedetection.io" 17 | ansible.builtin.include_tasks: "changedetection.yml" 18 | -------------------------------------------------------------------------------- /roles/changedetection/tasks/selenium.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Run selenium container" 3 | community.general.docker_container: 4 | name: "{{ changedetection_service_id }}-selenium" 5 | image: "seleniarm/standalone-chromium:{{ changedetection_selenium_version }}" 6 | networks: 7 | - name: "{{ changedetection_docker_settings.network }}" 8 | shm_size: "2g" 9 | state: "started" 10 | env: "{{ changedetection_selenium_env_default | combine(changedetection_selenium_env) }}" 11 | ports: 12 | - "{{ changedetection_selenium_port }}:4444" 13 | volumes: 14 | - "{{ changedetection_install_dir }}/datastore:/datastore" 15 | restart_policy: "unless-stopped" 16 | -------------------------------------------------------------------------------- /roles/changedetection/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | changedetection_docker_settings_default: 3 | network: "host" 4 | puid: "1000" 5 | pgid: "1000" 6 | tz: "Etc/UTC" 7 | 8 | changedetection_env_default: 9 | TZ: "{{ changedetection_docker_settings.tz }}" 10 | PUID: "{{ changedetection_docker_settings.puid }}" 11 | PGID: "{{ changedetection_docker_settings.pgid }}" 12 | WEBDRIVER_URL: "http://selenium:4444/wd/hub" 13 | 14 | changedetection_selenium_env_default: 15 | TZ: "{{ changedetection_docker_settings.tz }}" 16 | PUID: "{{ changedetection_docker_settings.puid }}" 17 | PGID: "{{ changedetection_docker_settings.pgid }}" 18 | FETCH_WORKERS: "2" 19 | -------------------------------------------------------------------------------- /roles/cloudflare/README.md: -------------------------------------------------------------------------------- 1 | # selfhosted.cloudflare 2 | 3 | Installs [cloudflared](https://hub.docker.com/r/cloudflare/cloudflared) - an easy way to make apps accessible remotely without a publicly routable IP address. 4 | 5 | > [!NOTE] 6 | > This role is not actively maintained since I moved to [nginx-proxy-manager](../nginx/README.md) and [wg-easy](../wgeasy/README.md). 7 | > Bugs may be present due to incompatibility with the latest versions of cloudflared image. 8 | 9 | ## Requirements 10 | 11 | - [Cloudflare Tunnel Token](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/get-started/create-remote-tunnel) 12 | 13 | ## Role Variables 14 | 15 | - `cloudflare_tunnel_version` 16 | - Default: `latest` 17 | - Description: The version of Cloudflare Tunnel to install. See [tags](https://hub.docker.com/r/cloudflare/cloudflared/tags). 18 | - Type: str 19 | - Required: no 20 | - `cloudflare_tunnel_uuid_or_name` 21 | - Default: `""` 22 | - Description: The UUID or name of the tunnel to use. See [docs](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/get-started/tunnel-useful-terms/#tunnel). 23 | - Type: str 24 | - Required: yes 25 | - `cloudflare_tunnel_networks` 26 | - Default: `[]` 27 | - Description: A list of docker networks cloudflare tunnel is connected to. If an application is running on a specific bridge network, it won't be accessible to the tunnel unless it's added to the list. 28 | - Type: list 29 | - Required: no 30 | - `cloudflare_tunnel_ssh_enabled` 31 | - Default: `true` 32 | - Description: Whether to enable SSH on the tunnel. Copies a public key (`{{ cloudflare_certificate_public_key }}`) in `/etc/ssh/ca.pub` and makes it trusted in `/etc/ssh/sshd_config`. See [docs](https://developers.cloudflare.com/cloudflare-one/identity/users/short-lived-certificates#3-generate-a-short-lived-certificate-public-key) 33 | - Type: bool 34 | - Required: no 35 | - `cloudflare_env` 36 | - Default: See [cloudflare_env_default](./vars/main.yml) 37 | - Description: Cloudflared environment variables. Must contain `TUNNEL_TOKEN` variable. 38 | - Type: dict 39 | - Required: yes 40 | 41 | ## Dependencies 42 | 43 | - [community.docker](https://docs.ansible.com/ansible/latest/collections/community/docker/index.html) 44 | 45 | ## Example Playbook 46 | 47 | ```yaml 48 | - hosts: localhost 49 | 50 | vars: 51 | cloudflare_tunnel_uuid_or_name: "my-tunnel-uuid" 52 | cloudflare_env: 53 | TUNNEL_TOKEN: "..." 54 | 55 | roles: 56 | - artyorsh.selfhosted.cloudflare 57 | ``` -------------------------------------------------------------------------------- /roles/cloudflare/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://hub.docker.com/r/cloudflare/cloudflared/tags 3 | cloudflare_tunnel_version: "latest" 4 | 5 | # https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/get-started/tunnel-useful-terms/#tunnel 6 | cloudflare_tunnel_uuid_or_name: "" 7 | cloudflare_tunnel_networks: [] 8 | cloudflare_tunnel_ssh_enabled: true 9 | cloudflare_certificate_public_key: "" 10 | 11 | cloudflare_env: {} 12 | -------------------------------------------------------------------------------- /roles/cloudflare/tasks/cloudflared-ssh.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://developers.cloudflare.com/cloudflare-one/identity/users/short-lived-certificates#3-generate-a-short-lived-certificate-public-key 3 | # https://developers.cloudflare.com/cloudflare-one/identity/users/short-lived-certificates#7-connect-as-a-user" 4 | 5 | - name: "Prepare task variables" 6 | ansible.builtin.set_fact: 7 | cf_certificate_public_key_file: "/etc/ssh/ca.pub" 8 | 9 | - name: "Copy short-lived certificate public key" 10 | ansible.builtin.lineinfile: 11 | dest: "{{ cf_certificate_public_key_file }}" 12 | line: "{{ cloudflare_certificate_public_key }}" 13 | state: "present" 14 | create: true 15 | mode: "0644" 16 | 17 | - name: "(SSH) allow public key authentication" 18 | block: 19 | - name: "(SSH) allow PubkeyAuthentication" 20 | ansible.builtin.lineinfile: 21 | dest: "/etc/ssh/sshd_config" 22 | regexp: "#?PubkeyAuthentication" 23 | line: "PubkeyAuthentication yes" 24 | state: "present" 25 | 26 | - name: "(SSH) trust Cloudflare CA key" 27 | ansible.builtin.lineinfile: 28 | path: "/etc/ssh/sshd_config" 29 | insertafter: "#?PubkeyAuthentication" 30 | line: "TrustedUserCAKeys {{ cf_certificate_public_key_file }}" 31 | 32 | - name: "Restart ssh service" 33 | ansible.builtin.service: 34 | name: "ssh" 35 | state: "restarted" 36 | -------------------------------------------------------------------------------- /roles/cloudflare/tasks/cloudflared.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Prepare task variables" 3 | ansible.builtin.set_fact: 4 | cloudflare_service_id: "cloudflare" 5 | cloudflare_root_dir: "/opt/docker/cloudflare" 6 | 7 | - name: "Prepare task variables" 8 | ansible.builtin.set_fact: 9 | tunnel_networks: "{{ tunnel_networks | default([]) + [{'name': item}] }}" 10 | with_items: "{{ cloudflare_tunnel_networks }}" 11 | 12 | - name: "Run cloudflared container" 13 | community.general.docker_container: 14 | name: "{{ cloudflare_service_id }}" 15 | image: "cloudflare/cloudflared:{{ cloudflare_tunnel_version }}" 16 | networks: "{{ tunnel_networks | default([]) | list }}" 17 | state: "started" 18 | env: "{{ cloudflare_env_default | combine(cloudflare_env) }}" 19 | command: "tunnel --no-autoupdate run {{ cloudflare_tunnel_uuid_or_name }}" 20 | restart_policy: "unless-stopped" 21 | -------------------------------------------------------------------------------- /roles/cloudflare/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Install cloudflared" 3 | ansible.builtin.include_tasks: "cloudflared.yml" 4 | 5 | - name: "Allow SSH via cloudflare tunnel" 6 | when: cloudflare_tunnel_ssh_enabled 7 | ansible.builtin.include_tasks: "cloudflared-ssh.yml" 8 | -------------------------------------------------------------------------------- /roles/cloudflare/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | cloudflare_env_default: 3 | TUNNEL_TOKEN: "" 4 | -------------------------------------------------------------------------------- /roles/docker/README.md: -------------------------------------------------------------------------------- 1 | # selfhosted.docker 2 | 3 | Installs Docker and optionally configures automatic backups and system pruning. 4 | 5 | ## Role Variables 6 | 7 | More variables are inherited from [geerlingguy.docker](https://github.com/geerlingguy/ansible-role-docker) role. 8 | 9 | - `docker_dir` 10 | - Default: `/opt/docker` 11 | - Description: The directory where Docker apps will be installed. 12 | - Type: str 13 | - Required: no 14 | - `docker_autobackup_enabled` 15 | - Default: `false` 16 | - Description: When set to `true`, the [docker-backup.sh](./templates/docker-backup.sh.j2) script will be created and added to the root cron. 17 | Additionaly, the [docker-restore.sh](./templates/docker-restore.sh.j2) script will be created. See [Automatic Backups](#automatic-backups). 18 | - Type: bool 19 | - Required: no 20 | - `docker_autobackup_dest_dir` 21 | - Default: `{{ docker_dir }}/.backups` 22 | - Description: The directory where the backups will be stored. 23 | - Type: str 24 | - Required: no 25 | - `docker_autobackup_containers` 26 | - Default: `[]` 27 | - Description: A list of containers to backup. Each container should have a `name` and a list of `volumes`. 28 | - Type: list 29 | - Required: no 30 | - `docker_autobackup_schedule` 31 | - Default: See [docker_autobackup_schedule](./defaults/main.yml) 32 | - Description: The schedule for the backup cron (in Ansible cron format). 33 | - Type: dict 34 | - Required: no 35 | - `docker_autoprune_enabled` 36 | - Default: `false` 37 | - Description: When set to `true`, the [docker-prune.sh](./templates/docker-prune.sh.j2) script will be added to the root cron to remove unused Docker resources. 38 | See [docs](https://docs.docker.com/reference/cli/docker/system/prune) for more information. 39 | See [Automatic Pruning](#automatic-pruning). 40 | - Type: bool 41 | - Required: no 42 | - `docker_autoprune_schedule` 43 | - Default: See [docker_autoprune_schedule](./defaults/main.yml) 44 | - Description: The schedule for the prune cron (in Ansible cron format). 45 | - Type: dict 46 | - Required: no 47 | - `docker_autoprune_until_hours` 48 | - Default: `720` 49 | - Description: only remove containers, images, and networks created before given number of hours. 50 | - Type: int 51 | - Required: no 52 | - `docker_unmanaged_networks` 53 | - Default: `docker0` and `br-*` networks. See [docker_unmanaged_networks](./defaults/main.yml) 54 | - Description: Bridge networks [may lose IPv4 address](https://serverfault.com/a/1093504) and therefore apps deployed on them may be unable to communicate with the rest of the internet. This variable controls the list of networks to unmanage with `/etc/systemd/network`. 55 | - Type: list 56 | - Required: no 57 | 58 | ## Dependencies 59 | 60 | - [geerlingguy.docker](https://github.com/geerlingguy/ansible-role-docker) 61 | 62 | ## Example Playbook 63 | 64 | ```yaml 65 | - hosts: localhost 66 | 67 | roles: 68 | - artyorsh.selfhosted.docker 69 | ``` 70 | 71 | ## Automatic Backups 72 | 73 | The configuration will backup all volumes of all containers listed in `docker_autobackup_containers` to the directory specified in `docker_autobackup_dest_dir`. 74 | 75 | ```yaml 76 | - hosts: localhost 77 | 78 | vars: 79 | docker_autobackup_enabled: true 80 | docker_autobackup_dest_dir: "/opt/docker/backups/docker-volumes" 81 | docker_autobackup_schedule: 82 | weekday: "*" 83 | hour: 1 84 | minute: 30 85 | 86 | docker_autobackup_containers: 87 | - { name: "forgejo", volumes: ["/etc/gitea", "/var/lib/gitea"] } 88 | - { name: "paperlessngx", volumes: ["/usr/src/paperless/data", "/usr/src/paperless/media"] } 89 | - { name: "vaultwarden", volumes: ["/data"] } 90 | 91 | roles: 92 | - artyorsh.selfhosted.docker 93 | ``` 94 | 95 | ## Automatic Pruning 96 | 97 | The configuration will remove unused Docker resources (containers, images, and networks) created before the number of hours specified in `docker_autoprune_until_hours`. 98 | 99 | ```yaml 100 | - hosts: localhost 101 | 102 | vars: 103 | docker_autoprune_enabled: true 104 | docker_autoprune_schedule: 105 | weekday: "*" 106 | hour: 1 107 | minute: 45 108 | docker_autoprune_until_hours: 72 109 | 110 | roles: 111 | - artyorsh.selfhosted.docker 112 | ``` 113 | 114 | -------------------------------------------------------------------------------- /roles/docker/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | docker_dir: "/opt/docker" 3 | 4 | docker_autobackup_enabled: false 5 | docker_autobackup_dest_dir: "{{ docker_dir }}/.backups" 6 | docker_autobackup_containers: [] 7 | # - { name: "homarr", volumes: ["/data"] } 8 | 9 | docker_autobackup_schedule: # 0AM every Monday 10 | weekday: 1 11 | hour: 0 12 | 13 | docker_autoprune_enabled: false 14 | docker_autoprune_until_hours: 720 15 | docker_autoprune_schedule: # 0AM every Monday 16 | weekday: 1 17 | hour: 0 18 | 19 | docker_unmanaged_networks: 20 | - docker_networkd_interface: "dockerdefault" 21 | docker_networkd_match: "docker0" 22 | - docker_networkd_interface: "dockerbridge" 23 | docker_networkd_match: "br-*" 24 | -------------------------------------------------------------------------------- /roles/docker/tasks/autobackup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Prepare task variables" 3 | ansible.builtin.set_fact: 4 | docker_backup_script: "/etc/docker-backup.sh" 5 | 6 | - name: "Template backup script" 7 | ansible.builtin.template: 8 | src: "docker-backup.sh.j2" 9 | dest: "{{ docker_backup_script }}" 10 | owner: "{{ ansible_user }}" 11 | group: "{{ ansible_user }}" 12 | mode: "0740" 13 | 14 | - name: "Template restore script" 15 | ansible.builtin.template: 16 | src: "docker-restore.sh.j2" 17 | dest: "/etc/docker-restore.sh" 18 | owner: "{{ ansible_user }}" 19 | group: "{{ ansible_user }}" 20 | mode: "0740" 21 | 22 | - name: "Create a backup cron" 23 | ansible.builtin.cron: 24 | name: "Backup docker containers" 25 | user: "{{ ansible_user }}" 26 | job: "{{ docker_backup_script }}" 27 | month: "{{ docker_autobackup_schedule.month | default('*') }}" 28 | weekday: "{{ docker_autobackup_schedule.weekday | default('0') }}" 29 | hour: "{{ docker_autobackup_schedule.hour | default('3') }}" 30 | minute: "{{ docker_autobackup_schedule.minute | default('0') }}" 31 | -------------------------------------------------------------------------------- /roles/docker/tasks/autoprune.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Prepare task variables" 3 | ansible.builtin.set_fact: 4 | docker_prune_script: "/etc/docker-prune.sh" 5 | 6 | - name: "Template backup script" 7 | ansible.builtin.template: 8 | src: "docker-prune.sh.j2" 9 | dest: "{{ docker_prune_script }}" 10 | owner: "{{ ansible_user }}" 11 | group: "{{ ansible_user }}" 12 | mode: "0740" 13 | 14 | - name: "Create a prune cron" 15 | ansible.builtin.cron: 16 | name: "Prune docker system" 17 | user: "{{ ansible_user }}" 18 | job: "{{ docker_prune_script }}" 19 | month: "{{ docker_autoprune_schedule.month | default('*') }}" 20 | weekday: "{{ docker_autoprune_schedule.weekday | default('0') }}" 21 | hour: "{{ docker_autoprune_schedule.hour | default('3') }}" 22 | minute: "{{ docker_autoprune_schedule.minute | default('0') }}" 23 | -------------------------------------------------------------------------------- /roles/docker/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://github.com/geerlingguy/ansible-role-docker/issues/434#issuecomment-1815767476 3 | 4 | - name: "Run geerlingguy.docker role" 5 | ansible.builtin.include_role: 6 | name: "geerlingguy.docker" 7 | 8 | - name: "Make docker networks unmanaged" 9 | ansible.builtin.include_tasks: "unmanage-network.yml" 10 | vars: 11 | docker_networkd_interface: "{{ item.docker_networkd_interface }}" 12 | docker_networkd_match: "{{ item.docker_networkd_match }}" 13 | loop: "{{ docker_unmanaged_networks }}" 14 | 15 | - name: "Enable volume auto-backup" 16 | when: docker_autobackup_enabled 17 | ansible.builtin.include_tasks: "autobackup.yml" 18 | 19 | - name: "Enable auto-prune" 20 | when: docker_autoprune_enabled 21 | ansible.builtin.include_tasks: "autoprune.yml" 22 | -------------------------------------------------------------------------------- /roles/docker/tasks/unmanage-network.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Template dockerdefault.network" 3 | ansible.builtin.template: 4 | src: "docker-networkd.j2" 5 | dest: "/etc/systemd/network/{{ docker_networkd_interface }}.network" 6 | owner: "root" 7 | group: "root" 8 | mode: "0644" 9 | -------------------------------------------------------------------------------- /roles/docker/templates/docker-backup.sh.j2: -------------------------------------------------------------------------------- 1 | {% for container in docker_autobackup_containers %} 2 | 3 | # {{ container.name }} 4 | {% for volume in container.volumes %} 5 | docker run --rm --volumes-from {{ container.name }} -v {{ docker_autobackup_dest_dir }}/{{ container.name }}:/backup ubuntu tar cvfz /backup/{{ volume | replace('/', '-') }}.tar.gz {{ volume }} 6 | {% endfor %} 7 | {% endfor %} 8 | -------------------------------------------------------------------------------- /roles/docker/templates/docker-networkd.j2: -------------------------------------------------------------------------------- 1 | # Ensure that the '{{ docker_networkd_interface }}' interface is unmanaged 2 | 3 | [Match] 4 | Name={{ docker_networkd_match }} 5 | 6 | [Link] 7 | Unmanaged=yes -------------------------------------------------------------------------------- /roles/docker/templates/docker-prune.sh.j2: -------------------------------------------------------------------------------- 1 | docker system prune -af --filter "until={{ docker_autoprune_until_hours }}h" 2 | docker image prune -af --filter "until={{ docker_autoprune_until_hours }}h" 3 | -------------------------------------------------------------------------------- /roles/docker/templates/docker-restore.sh.j2: -------------------------------------------------------------------------------- 1 | {% for container in docker_autobackup_containers %} 2 | 3 | # {{ container.name }} 4 | {% for volume in container.volumes %} 5 | docker run --rm --volumes-from {{ container.name }} -v {{ docker_autobackup_dest_dir }}/{{ container.name }}:/backup ubuntu sh -c "cd {{ volume }} && tar xvf /backup/{{ volume | replace('/', '-') }}.tar.gz --strip 1" 6 | {% endfor %} 7 | {% endfor %} 8 | -------------------------------------------------------------------------------- /roles/docker/vars/Ubuntu.yml: -------------------------------------------------------------------------------- 1 | --- 2 | docker_deps: 3 | - "apt-transport-https" 4 | - "ca-certificates" 5 | - "gnupg2" 6 | - "curl" 7 | - "software-properties-common" 8 | - "python3-pip" 9 | - "virtualenv" 10 | - "python3-setuptools" 11 | - "docker-compose" 12 | 13 | docker_packages: 14 | - "docker-ce" 15 | - "docker-compose" 16 | - "cgroupfs-mount" 17 | -------------------------------------------------------------------------------- /roles/duplicati/README.md: -------------------------------------------------------------------------------- 1 | # selfhosted.duplicati 2 | 3 | Installs [Duplicati](https://duplicati.com) - a central backup management & monitoring system. 4 | This role installs the [linuxserver/duplicati](https://docs.linuxserver.io/images/docker-duplicati) image. 5 | 6 | ## Role Variables 7 | 8 | - `duplicati_version` 9 | - Default: `latest` 10 | - Description: The version of Duplicati to install. See [tags](https://hub.docker.com/r/linuxserver/duplicati/tags). 11 | - Type: str 12 | - Required: no 13 | - `duplicati_port` 14 | - Default: `8200` 15 | - Description: The port on which Duplicati will be accessible. 16 | - Type: int 17 | - Required: no 18 | - `duplicati_install_dir` 19 | - Default: `/opt/docker/duplicati` 20 | - Description: The directory where Duplicati will be installed. 21 | - Type: str 22 | - Required: no 23 | - `duplicati_source_dir` 24 | - Default: `{{ duplicati_install_dir }}/source` 25 | - Description: Path to source for files to backup. 26 | - Type: str 27 | - Required: no 28 | - `duplicati_backups_dir` 29 | - Default: `{{ duplicati_install_dir }}/backups` 30 | - Description: Path to store local backups. 31 | - `duplicati_env` 32 | - Default: See [duplicati_env_default](./vars/main.yml) 33 | - Description: Docker container environment variables. See [docs](https://docs.linuxserver.io/images/docker-duplicati/#environment-variables-e). 34 | - Type: dict 35 | - Required: no 36 | - `duplicati_docker_settings` 37 | - Default: See [duplicati_docker_settings_default](./vars/main.yml) 38 | - Description: Docker container settings. 39 | - Type: dict 40 | - Required: no 41 | 42 | ## Dependencies 43 | 44 | - [community.docker](https://docs.ansible.com/ansible/latest/collections/community/docker/index.html) 45 | 46 | ## Example Playbook 47 | 48 | ```yaml 49 | - hosts: localhost 50 | 51 | vars: 52 | duplicati_source_dir: "/path/to/source/files" 53 | duplicati_backups_dir: "/path/to/backups" 54 | duplicati_env: 55 | # https://docs.duplicati.com/detailed-descriptions/using-duplicati-from-docker#managing-secrets-in-docker 56 | SETTINGS_ENCRYPTION_KEY: "supersecret" 57 | 58 | roles: 59 | - artyorsh.selfhosted.duplicati 60 | ``` -------------------------------------------------------------------------------- /roles/duplicati/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://hub.docker.com/r/linuxserver/duplicati/tags 3 | duplicati_version: "latest" 4 | duplicati_port: 8200 5 | duplicati_install_dir: "/opt/docker/duplicati" 6 | 7 | duplicati_source_dir: "{{ duplicati_install_dir }}/source" 8 | duplicati_backups_dir: "{{ duplicati_install_dir }}/backups" 9 | 10 | # https://docs.linuxserver.io/images/docker-duplicati/#parameters 11 | duplicati_env: {} 12 | 13 | duplicati_docker_settings: {} 14 | -------------------------------------------------------------------------------- /roles/duplicati/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://containrrr.dev/duplicati/usage-overview/ 3 | # https://containrrr.dev/duplicati/arguments/ 4 | 5 | - name: "Prepare task variables" 6 | ansible.builtin.set_fact: 7 | duplicati_service_id: "duplicati" 8 | duplicati_docker_settings: "{{ duplicati_docker_settings_default | combine(duplicati_docker_settings) }}" 9 | 10 | - name: "Make sure {{ duplicati_docker_settings.network }} docker network exists" 11 | when: duplicati_docker_settings.network != "host" 12 | community.general.docker_network: 13 | name: "{{ duplicati_docker_settings.network }}" 14 | state: "present" 15 | 16 | - name: "Make sure config directories exist" 17 | ansible.builtin.file: 18 | path: "{{ item }}" 19 | owner: "{{ ansible_user }}" 20 | group: "{{ ansible_user }}" 21 | state: "directory" 22 | mode: "0774" 23 | loop: 24 | - "{{ duplicati_source_dir }}" 25 | - "{{ duplicati_backups_dir }}" 26 | 27 | - name: "Run duplicati container" 28 | community.general.docker_container: 29 | name: "{{ duplicati_service_id }}" 30 | image: "lscr.io/linuxserver/duplicati:{{ duplicati_version }}" 31 | networks: 32 | - name: "{{ duplicati_docker_settings.network }}" 33 | state: "started" 34 | env: "{{ duplicati_env_default | combine(duplicati_env) }}" 35 | ports: 36 | - "{{ duplicati_port }}:8200" 37 | volumes: 38 | - "{{ duplicati_install_dir }}:/config" 39 | - "{{ duplicati_source_dir }}:/source" 40 | - "{{ duplicati_backups_dir }}:/backups" 41 | restart_policy: "unless-stopped" 42 | -------------------------------------------------------------------------------- /roles/duplicati/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | duplicati_docker_settings_default: 3 | network: "host" 4 | puid: "1000" 5 | pgid: "1000" 6 | tz: "Etc/UTC" 7 | 8 | duplicati_env_default: 9 | TZ: "{{ duplicati_docker_settings.tz }}" 10 | PUID: "{{ duplicati_docker_settings.puid }}" 11 | PGID: "{{ duplicati_docker_settings.pgid }}" 12 | -------------------------------------------------------------------------------- /roles/filebrowser/README.md: -------------------------------------------------------------------------------- 1 | # selfhosted.filebrowser 2 | 3 | Installs [Filebrowser](https://github.com/filebrowser/filebrowser) - a web-based file browser. 4 | 5 | > [!NOTE] 6 | > This role is not actively maintained as it is currently not in use (since June 2024). 7 | > Bugs may be present due to incompatibility with the latest versions of Filebrowser image. 8 | 9 | ## Role Variables 10 | 11 | - `filebrowser_version` 12 | - Default: `latest` 13 | - Description: The version of Filebrowser to install. See [tags](https://hub.docker.com/r/filebrowser/filebrowser/tags). 14 | - Type: str 15 | - Required: no 16 | - `filebrowser_port` 17 | - Default: `8081` 18 | - Description: The port on which Filebrowser will be accessible. 19 | - Type: int 20 | - Required: no 21 | - `filebrowser_install_dir` 22 | - Default: `/opt/docker/filebrowser` 23 | - Description: The directory where Filebrowser will be installed. 24 | - Type: str 25 | - Required: no 26 | - `filebrowser_env` 27 | - Default: See [filebrowser_env_default](./vars/main.yml) 28 | - Description: Docker container environment variables. See [docs](https://filebrowser.org/configuration). 29 | - Type: dict 30 | - Required: no 31 | - `filebrowser_docker_settings` 32 | - Default: See [filebrowser_docker_settings_default](./vars/main.yml) 33 | - Description: Docker container settings. 34 | - Type: dict 35 | - Required: no 36 | 37 | ## Dependencies 38 | 39 | - [community.docker](https://docs.ansible.com/ansible/latest/collections/community/docker/index.html) 40 | 41 | ## Example Playbook 42 | 43 | ```yaml 44 | - hosts: localhost 45 | 46 | roles: 47 | - artyorsh.selfhosted.filebrowser 48 | ``` -------------------------------------------------------------------------------- /roles/filebrowser/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://hub.docker.com/r/filebrowser/filebrowser/tags 3 | filebrowser_version: "latest" 4 | filebrowser_port: 8081 5 | filebrowser_install_dir: "/opt/docker/filebrowser" 6 | 7 | filebrowser_storage_root_dir: "/opt" 8 | 9 | # https://filebrowser.org/configuration 10 | filebrowser_env: {} 11 | 12 | filebrowser_docker_settings: {} 13 | -------------------------------------------------------------------------------- /roles/filebrowser/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://docs.linuxserver.io/images/docker-filebrowser/#parameters 3 | 4 | - name: "Prepare task variables" 5 | ansible.builtin.set_fact: 6 | filebrowser_service_id: "filebrowser" 7 | filebrowser_docker_settings: "{{ filebrowser_docker_settings_default | combine(filebrowser_docker_settings) }}" 8 | 9 | - name: "Make sure config directory exists" 10 | ansible.builtin.file: 11 | path: "{{ filebrowser_install_dir }}" 12 | owner: "{{ ansible_user }}" 13 | group: "{{ ansible_user }}" 14 | state: "directory" 15 | mode: "0774" 16 | 17 | - name: "Make sure {{ filebrowser_docker_settings.network }} docker network exists" 18 | when: filebrowser_docker_settings.network != "host" 19 | community.general.docker_network: 20 | name: "{{ filebrowser_docker_settings.network }}" 21 | state: "present" 22 | 23 | - name: "Create database" 24 | ansible.builtin.file: 25 | path: "{{ filebrowser_install_dir }}/filebrowser.db" 26 | owner: "{{ ansible_user }}" 27 | group: "{{ ansible_user }}" 28 | state: "touch" 29 | mode: "0774" 30 | 31 | - name: "Run filebrowser container" 32 | community.general.docker_container: 33 | name: "{{ filebrowser_service_id }}" 34 | image: "filebrowser/filebrowser:{{ filebrowser_version }}" 35 | networks: 36 | - name: "{{ filebrowser_docker_settings.network }}" 37 | state: "started" 38 | env: "{{ filebrowser_env_default | combine(filebrowser_env) }}" 39 | ports: 40 | - "{{ filebrowser_port }}:80" 41 | volumes: 42 | - "{{ filebrowser_install_dir }}/filebrowser.db:/database.db" 43 | - "{{ filebrowser_storage_root_dir }}:/srv" 44 | restart_policy: "unless-stopped" 45 | -------------------------------------------------------------------------------- /roles/filebrowser/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | filebrowser_docker_settings_default: 3 | network: "host" 4 | puid: "1000" 5 | pgid: "1000" 6 | tz: "Etc/UTC" 7 | 8 | filebrowser_env_default: 9 | TZ: "{{ filebrowser_docker_settings.tz }}" 10 | PUID: "{{ filebrowser_docker_settings.puid }}" 11 | PGID: "{{ filebrowser_docker_settings.pgid }}" 12 | -------------------------------------------------------------------------------- /roles/forgejo/README.md: -------------------------------------------------------------------------------- 1 | # selfhosted.forgejo 2 | 3 | Installs [Forgejo](https://forgejo.org/) - a lightweight git service. 4 | Also installs a MySQL database that is [required](https://forgejo.org/docs/latest/admin/database-preparation/#mysqlmariadb) by Forgejo to work. 5 | 6 | ## Role Variables 7 | 8 | - `forgejo_version` 9 | - Default: `9-rootless` 10 | - Description: The version of Forgejo to install. See [tags](https://codeberg.org/forgejo/forgejo/packages). 11 | - Type: str 12 | - Required: no 13 | - `forgejo_webui_port` 14 | - Default: `3000` 15 | - Description: The port on which Forgejo's web UI will be accessible. 16 | - Type: int 17 | - Required: no 18 | - `forgejo_ssh_port` 19 | - Default: `2222` 20 | - Description: The port on which Forgejo's SSH service will be accessible. 21 | - Type: int 22 | - Required: no 23 | - `forgejo_install_dir` 24 | - Default: `/opt/docker/forgejo` 25 | - Description: The directory where Forgejo will be installed. 26 | - Type: str 27 | - Required: no 28 | - `forgejo_env` 29 | - Default: See [forgejo_env_default](./vars/main.yml) 30 | - Description: Docker container environment variables. See [docs](https://forgejo.org/docs/latest/admin/config-cheat-sheet/#default-configuration-non-appini-configuration) 31 | - Type: dict 32 | - Required: no 33 | - `forgejo_docker_settings` 34 | - Default: See [forgejo_docker_settings_default](./vars/main.yml) 35 | - Description: Docker container settings. 36 | - Type: dict 37 | - Required: no 38 | - `forgejo_repositories_dir` 39 | - Default: `{{ forgejo_install_dir }}/repositories` 40 | - Description: The directory where Forgejo will store the repositories. 41 | - Type: str 42 | - Required: no 43 | - `forgejo_db_mysql_version` 44 | - Default: `8` 45 | - Description: The version of MySQL to use for the database. 46 | - Type: int 47 | - Required: no 48 | - `forgejo_db_env` 49 | - Default: See [forgejo_db_env_default](./vars/main.yml) 50 | - Description: Docker container environment variables for the database. 51 | - Type: dict 52 | - Required: no 53 | 54 | ## Dependencies 55 | 56 | - [community.docker](https://docs.ansible.com/ansible/latest/collections/community/docker/index.html) 57 | 58 | ## Example Playbook 59 | 60 | ```yaml 61 | - hosts: localhost 62 | 63 | vars: 64 | forgejo_repositories_dir: "/path/to/repositories" 65 | 66 | roles: 67 | - artyorsh.selfhosted.forgejo 68 | ``` 69 | 70 | ## Disable features 71 | 72 | ```yaml 73 | - hosts: localhost 74 | 75 | vars: 76 | forgejo_env: 77 | FORGEJO__actions__ENABLED: "false" 78 | FORGEJO__attachment__ENABLED: "false" 79 | FORGEJO__picture__DISABLE_GRAVATAR: "true" 80 | FORGEJO__repository__DISABLED_REPO_UNITS: "repo.actions" 81 | FORGEJO__repository__DISABLE_STARS: "true" 82 | FORGEJO__repository.upload__ENABLED: "false" 83 | FORGEJO__security__DISABLE_WEBHOOKS: "true" 84 | FORGEJO__service__DISABLE_REGISTRATION: "true" 85 | FORGEJO__service__ENABLE_BASIC_AUTHENTICATION: "true" 86 | FORGEJO__service.explore__DISABLE_USERS_PAGE: "true" 87 | FORGEJO__service.explore__REQUIRE_SIGNIN_VIEW: "true" 88 | FORGEJO__ui.notification__EVENT_SOURCE_UPDATE_TIME: "-1" 89 | 90 | roles: 91 | - artyorsh.selfhosted.forgejo 92 | ``` -------------------------------------------------------------------------------- /roles/forgejo/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://codeberg.org/forgejo/-/packages/container/forgejo 3 | forgejo_version: "9-rootless" 4 | forgejo_db_mysql_version: "8" 5 | forgejo_webui_port: 3000 6 | forgejo_ssh_port: 2222 7 | forgejo_install_dir: "/opt/docker/forgejo" 8 | 9 | # https://forgejo.org/docs/latest/admin/installation-docker/#hosting-repository-data-on-remote-storage-systems 10 | forgejo_repositories_dir: "{{ forgejo_install_dir }}/repositories" 11 | forgejo_timezone_file: "/etc/timezone" 12 | forgejo_localtime_bin: "/etc/localtime" 13 | 14 | # https://forgejo.org/docs/latest/admin/config-cheat-sheet/#default-configuration-non-appini-configuration 15 | forgejo_env: {} 16 | forgejo_db_env: {} 17 | 18 | forgejo_docker_settings: {} 19 | -------------------------------------------------------------------------------- /roles/forgejo/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Combine docker_settings with default docker settings" 3 | ansible.builtin.set_fact: 4 | forgejo_service_id: "forgejo" 5 | forgejo_docker_settings: "{{ forgejo_docker_settings_default | combine(forgejo_docker_settings) }}" 6 | 7 | - name: "Prepare task variables" 8 | ansible.builtin.set_fact: 9 | forgejo_service_id: "forgejo" 10 | forgejo_env: "{{ forgejo_env_default | combine(forgejo_env) }}" 11 | forgejo_db_env: "{{ forgejo_db_env_default | combine(forgejo_db_env) }}" 12 | 13 | - name: "Make sure {{ forgejo_docker_settings.network }} docker network exists" 14 | when: forgejo_docker_settings.network != "host" 15 | community.general.docker_network: 16 | name: "{{ forgejo_docker_settings.network }}" 17 | state: "present" 18 | 19 | - name: "Make sure config directories exist" 20 | ansible.builtin.file: 21 | path: "{{ item }}" 22 | owner: "{{ ansible_user }}" 23 | group: "{{ ansible_user }}" 24 | state: "directory" 25 | mode: "0774" 26 | loop: 27 | - "{{ forgejo_repositories_dir }}/data" 28 | - "{{ forgejo_install_dir }}/conf" 29 | - "{{ forgejo_install_dir }}/mysql" 30 | 31 | - name: "Run forgejo container" 32 | community.general.docker_container: 33 | name: "{{ forgejo_service_id }}" 34 | image: "codeberg.org/forgejo/forgejo:{{ forgejo_version }}" 35 | networks: 36 | - name: "{{ forgejo_docker_settings.network }}" 37 | state: "started" 38 | env: "{{ forgejo_env }}" 39 | user: "{{ forgejo_docker_settings.puid }}:{{ forgejo_docker_settings.pgid }}" 40 | ports: 41 | - "{{ forgejo_webui_port }}:3000" 42 | - "{{ forgejo_ssh_port }}:2222" 43 | volumes: 44 | - "{{ forgejo_repositories_dir }}:/var/lib/gitea" 45 | - "{{ forgejo_install_dir }}/conf:/etc/gitea" 46 | - "{{ forgejo_timezone_file }}:/etc/timezone:ro" 47 | - "{{ forgejo_localtime_bin }}:/etc/localtime:ro" 48 | restart_policy: "always" 49 | 50 | - name: "Run db container" 51 | community.general.docker_container: 52 | name: "{{ forgejo_service_id }}-db" 53 | image: "mysql:{{ forgejo_db_mysql_version }}" 54 | networks: 55 | - name: "{{ forgejo_docker_settings.network }}" 56 | state: "started" 57 | env: "{{ forgejo_db_env }}" 58 | volumes: 59 | - "{{ forgejo_install_dir }}/mysql:/var/lib/mysql" 60 | restart_policy: "always" -------------------------------------------------------------------------------- /roles/forgejo/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | forgejo_docker_settings_default: 3 | network: "host" 4 | puid: "1000" 5 | pgid: "1000" 6 | tz: "Etc/UTC" 7 | 8 | forgejo_env_default: 9 | TZ: "{{ forgejo_docker_settings.tz }}" 10 | PUID: "{{ forgejo_docker_settings.puid }}" 11 | PGID: "{{ forgejo_docker_settings.pgid }}" 12 | FORGEJO__database__DB_TYPE: "mysql" 13 | FORGEJO__database__HOST: "forgejo-db" 14 | FORGEJO__database__NAME: "forgejo" 15 | FORGEJO__database__USER: "forgejo" 16 | FORGEJO__database__PASSWD: "forgejo" 17 | FORGEJO__cron.update_checker__ENABLED: "false" 18 | 19 | forgejo_db_env_default: 20 | TZ: "{{ forgejo_docker_settings.tz }}" 21 | PUID: "{{ forgejo_docker_settings.puid }}" 22 | PGID: "{{ forgejo_docker_settings.pgid }}" 23 | MYSQL_ROOT_PASSWORD: "forgejo" 24 | MYSQL_USER: "forgejo" 25 | MYSQL_PASSWORD: "forgejo" 26 | MYSQL_DATABASE: "forgejo" 27 | -------------------------------------------------------------------------------- /roles/glances/README.md: -------------------------------------------------------------------------------- 1 | # selfhosted.glances 2 | 3 | Installs [Glances](https://nicolargo.github.io/glances/) - a cross-platform system monitoring tool. 4 | 5 | ## Role Variables 6 | 7 | - `glances_version` 8 | - Default: `latest-full` 9 | - Description: The version of Glances to install. See [tags](https://hub.docker.com/r/nicolargo/glances/tags). 10 | - Type: str 11 | - Required: no 12 | - `glances_webui_port` 13 | - Default: `61208` 14 | - Description: The port on which Glances' web interface will be accessible. 15 | - Type: int 16 | - Required: no 17 | - `glances_server_port` 18 | - Default: `61209` 19 | - Description: The port on which Glances' server is listening. 20 | - Type: int 21 | - Required: no 22 | - `glances_install_dir` 23 | - Default: `/opt/docker/glances` 24 | - Description: The directory where Glances will be installed. 25 | - Type: str 26 | - Required: no 27 | - `glances_password` 28 | - Default: `changeme` 29 | - Description: The password for the Glances web interface. 30 | - Type: str 31 | - Required: no 32 | - `glances_fs_volumes` 33 | - Default: `[]` 34 | - Description: A list of directories to mount on the Glances container. Will be mounted in "ro" mode. 35 | - Type: list 36 | - Required: no 37 | - `glances_docker_settings` 38 | - Default: See [glances_docker_settings_default](./vars/main.yml) 39 | - Description: Docker container settings. 40 | 41 | ## Dependencies 42 | 43 | - [community.docker](https://docs.ansible.com/ansible/latest/collections/community/docker/index.html) 44 | 45 | ## Example Playbook 46 | 47 | ```yaml 48 | - hosts: localhost 49 | 50 | roles: 51 | - artyorsh.selfhosted.glances 52 | ``` -------------------------------------------------------------------------------- /roles/glances/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://hub.docker.com/r/nicolargo/glances/tags 3 | glances_version: "latest-full" 4 | glances_webui_port: "61208" 5 | glances_server_port: "61209" 6 | glances_install_dir: "/opt/docker/glances" 7 | glances_env: {} 8 | 9 | glances_password: "changeme" 10 | 11 | # A path to a custom notifier shell script to allow delivering messages to a communication app of choice. 12 | # The script is executed with 2 arguments: a message and a status. E.g: 13 | # echo "Experiencing extreme load" "danger" 14 | glances_notifier: "" 15 | 16 | # Will include the paths to container mounts in "ro" mode 17 | glances_fs_volumes: [] 18 | # - "/mnt/data" 19 | 20 | glances_docker_settings: {} 21 | -------------------------------------------------------------------------------- /roles/glances/files/notify.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import subprocess 3 | 4 | def main(): 5 | message = sys.argv[1] 6 | status = sys.argv[2] 7 | 8 | subprocess.run(['/etc/notify.sh', message, status]) 9 | 10 | main() -------------------------------------------------------------------------------- /roles/glances/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Prepare task variables" 3 | ansible.builtin.set_fact: 4 | glances_service_id: "glances" 5 | glances_docker_settings: "{{ glances_docker_settings_default | combine(glances_docker_settings) }}" 6 | glances_docker_volumes: 7 | - "{{ glances_install_dir }}/glances.conf:/etc/glances/glances.conf" 8 | - "{{ glances_install_dir }}/notify.py:/etc/notify.py" 9 | - "/var/run/docker.sock:/var/run/docker.sock:ro" 10 | glances_fs_docker_volumes: "{{ glances_fs_volumes | map('regex_replace', '^(.*)$', '\\1:\\1:ro') | list }}" 11 | 12 | - name: "Append notification script to volumes" 13 | when: glances_notifier | length > 0 14 | ansible.builtin.set_fact: 15 | glances_extra_volumes: 16 | - "{{ glances_notifier }}:/etc/notify.sh:ro" 17 | 18 | - name: "Make sure config directory exists" 19 | ansible.builtin.file: 20 | path: "{{ glances_install_dir }}" 21 | owner: "{{ ansible_user }}" 22 | group: "{{ ansible_user }}" 23 | state: "directory" 24 | mode: "0774" 25 | 26 | - name: "Make sure {{ glances_docker_settings.network }} docker network exists" 27 | when: glances_docker_settings.network != "host" 28 | community.general.docker_network: 29 | name: "{{ glances_docker_settings.network }}" 30 | state: "present" 31 | 32 | - name: "Template the config file" 33 | ansible.builtin.template: 34 | src: "glances.conf.j2" 35 | dest: "{{ glances_install_dir }}/glances.conf" 36 | owner: "{{ ansible_user }}" 37 | group: "{{ ansible_user }}" 38 | mode: "0750" 39 | 40 | - name: "Template notifications file" 41 | ansible.builtin.copy: 42 | src: "files/notify.py" 43 | dest: "{{ glances_install_dir }}/notify.py" 44 | owner: "{{ ansible_user }}" 45 | group: "{{ ansible_user }}" 46 | mode: "0740" 47 | 48 | - name: "Run glances container" 49 | community.general.docker_container: 50 | name: "{{ glances_service_id }}" 51 | image: "nicolargo/glances:{{ glances_version }}" 52 | networks: 53 | - name: "{{ glances_docker_settings.network }}" 54 | state: "started" 55 | env: "{{ glances_env_default | combine(glances_env) }}" 56 | ports: 57 | - "{{ glances_webui_port }}:61208" 58 | - "{{ glances_server_port }}:61209" 59 | volumes: "{{ glances_docker_volumes + glances_fs_docker_volumes + (glances_extra_volumes | default([])) }}" 60 | restart_policy: "always" 61 | -------------------------------------------------------------------------------- /roles/glances/templates/glances.conf.j2: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # Globals Glances parameters 3 | # https://raw.githubusercontent.com/nicolargo/glances/master/conf/glances.conf 4 | ############################################################################## 5 | 6 | [global] 7 | # Stats refresh rate (default is a minimum of 2 seconds) 8 | # Can be overwrite by the -t option 9 | # It is also possible to overwrite it in each plugin sections 10 | refresh=2 11 | # Does Glances should check if a newer version is available on PyPI ? 12 | check_update=true 13 | # History size (maximum number of values) 14 | # Default is 1200 values (~1h with the default refresh rate) 15 | history_size=1200 16 | # Set the way Glances should display the date (default is %Y-%m-%d %H:%M:%S %Z) 17 | #strftime_format="%Y-%m-%d %H:%M:%S %Z" 18 | 19 | ############################################################################## 20 | # User interface 21 | ############################################################################## 22 | 23 | [outputs] 24 | # Theme name for the Curses interface: black or white 25 | curse_theme=black 26 | # Limit the number of processes to display in the WebUI 27 | max_processes_display=30 28 | 29 | ############################################################################## 30 | # plugins 31 | ############################################################################## 32 | 33 | [quicklook] 34 | # Set to true to disable a plugin 35 | # Note: you can also disable it from the command line (see --disable-plugin ) 36 | disable=False 37 | # Graphical percentage char used in the terminal user interface (default is |) 38 | percentage_char=| 39 | # Define CPU, MEM and SWAP thresholds in % 40 | cpu_careful=50 41 | cpu_warning=70 42 | cpu_critical=90 43 | mem_careful=50 44 | mem_warning=70 45 | mem_critical=90 46 | swap_careful=50 47 | swap_warning=70 48 | swap_critical=90 49 | 50 | [system] 51 | # This plugin display the first line in the Glances UI with: 52 | # Hostname / Operating system name / Architecture information 53 | # Set to true to disable a plugin 54 | disable=False 55 | # Default refresh rate is 60 seconds 56 | #refresh=60 57 | 58 | [cpu] 59 | disable=False 60 | # See https://scoutapm.com/blog/slow_server_flow_chart 61 | # 62 | # I/O wait percentage should be lower than 1/# (# = Logical CPU cores) 63 | # Leave commented to just use the default config: 64 | # Careful=1/#*100-20% / Warning=1/#*100-10% / Critical=1/#*100 65 | #iowait_careful=30 66 | #iowait_warning=40 67 | #iowait_critical=50 68 | # 69 | # Total % is 100 - idle 70 | total_careful=65 71 | total_warning=75 72 | total_critical=85 73 | total_log=True 74 | # 75 | # Default values if not defined: 50/70/90 (except for iowait) 76 | user_careful=50 77 | user_warning=70 78 | user_critical=90 79 | user_log=False 80 | # 81 | system_careful=50 82 | system_warning=70 83 | system_critical=90 84 | system_log=False 85 | # 86 | steal_careful=50 87 | steal_warning=70 88 | steal_critical=90 89 | #steal_log=True 90 | # 91 | # Context switch limit (core / second) 92 | # Leave commented to just use the default config (critical is 50000*# (Logical CPU cores) 93 | #ctx_switches_careful=10000 94 | #ctx_switches_warning=12000 95 | #ctx_switches_critical=14000 96 | 97 | [percpu] 98 | disable=False 99 | # Define CPU thresholds in % 100 | # Default values if not defined: 50/70/90 101 | user_careful=50 102 | user_warning=70 103 | user_critical=90 104 | iowait_careful=50 105 | iowait_warning=70 106 | iowait_critical=90 107 | system_careful=50 108 | system_warning=70 109 | system_critical=90 110 | 111 | [gpu] 112 | disable=False 113 | # Default processor values if not defined: 50/70/90 114 | proc_careful=50 115 | proc_warning=70 116 | proc_critical=90 117 | # Default memory values if not defined: 50/70/90 118 | mem_careful=50 119 | mem_warning=70 120 | mem_critical=90 121 | 122 | [mem] 123 | disable=False 124 | # Define RAM thresholds in % 125 | # Default values if not defined: 50/70/90 126 | careful=50 127 | warning=70 128 | critical=90 129 | 130 | [memswap] 131 | disable=False 132 | # Define SWAP thresholds in % 133 | # Default values if not defined: 50/70/90 134 | careful=50 135 | warning=70 136 | critical=90 137 | 138 | [load] 139 | disable=False 140 | # Define LOAD thresholds 141 | # Value * number of cores 142 | # Default values if not defined: 0.7/1.0/5.0 per number of cores 143 | # Source: http://blog.scoutapp.com/articles/2009/07/31/understanding-load-averages 144 | # http://www.linuxjournal.com/article/9001 145 | careful=0.7 146 | warning=1.0 147 | critical=5.0 148 | critical_action=python /etc/notify.py "Experiencing extreme load" "danger" 149 | #log=False 150 | 151 | [network] 152 | disable=False 153 | # Default bitrate thresholds in % of the network interface speed 154 | # Default values if not defined: 70/80/90 155 | rx_careful=70 156 | rx_warning=80 157 | rx_critical=90 158 | tx_careful=70 159 | tx_warning=80 160 | tx_critical=90 161 | # Define the list of hidden network interfaces (comma-separated regexp) 162 | hide=docker.*,lo,veth.*,br-.* 163 | # Define the list of wireless network interfaces to be show (comma-separated) 164 | #show=docker.* 165 | # WLAN 0 alias 166 | #wlan0_alias=Wireless 167 | # It is possible to overwrite the bitrate thresholds per interface 168 | # WLAN 0 Default limits (in bits per second aka bps) for interface bitrate 169 | #wlan0_rx_careful=4000000 170 | #wlan0_rx_warning=5000000 171 | #wlan0_rx_critical=6000000 172 | #wlan0_rx_log=True 173 | #wlan0_tx_careful=700000 174 | #wlan0_tx_warning=900000 175 | #wlan0_tx_critical=1000000 176 | #wlan0_tx_log=True 177 | 178 | [ip] 179 | disable=False 180 | public_refresh_interval=300 181 | public_ip_disabled=False 182 | # Configuration for the Censys online service 183 | # Need to create an aacount: https://censys.io/login 184 | censys_url=https://search.censys.io/api 185 | # Get your own credential here: https://search.censys.io/account/api 186 | # Enter your credential and uncomment the following lines 187 | #censys_username= 188 | #censys_password= 189 | # List of fields to be displayed in user interface (comma separated) 190 | censys_fields=location:continent,location:country,autonomous_system:name 191 | 192 | [connections] 193 | # Display additional information about TCP connections 194 | # This plugin is disabled by default 195 | disable=False 196 | # nf_conntrack thresholds in % 197 | nf_conntrack_percent_careful=70 198 | nf_conntrack_percent_warning=80 199 | nf_conntrack_percent_critical=90 200 | 201 | [wifi] 202 | disable=True 203 | # Define the list of hidden wireless network interfaces (comma-separated regexp) 204 | hide=lo,docker.* 205 | # Define the list of wireless network interfaces to be show (comma-separated) 206 | #show=docker.* 207 | # Define SIGNAL thresholds in db (lower is better...) 208 | # Based on: http://serverfault.com/questions/501025/industry-standard-for-minimum-wifi-signal-strength 209 | careful=-65 210 | warning=-75 211 | critical=-85 212 | 213 | [diskio] 214 | disable=False 215 | # Define the list of hidden disks (comma-separated regexp) 216 | #hide=sda2,sda5,loop.* 217 | hide=loop.*,/dev/loop.* 218 | # Define the list of disks to be show (comma-separated) 219 | #show=sda.* 220 | # Alias for sda1 221 | #sda1_alias=InternalDisk 222 | 223 | [fs] 224 | disable=False 225 | # Define the list of file system to hide (comma-separated regexp) 226 | hide=/snap.*,/etc/resolv.*,/etc/hostname.*,/etc/glances.*,/etc/notify.* 227 | # Define the list of file system to show (comma-separated regexp) 228 | #show=/,/srv 229 | # Define filesystem space thresholds in % 230 | # Default values if not defined: 50/70/90 231 | # It is also possible to define per mount point value 232 | # Example: /_careful=40 233 | careful=50 234 | warning=70 235 | critical=90 236 | critical_action=python /etc/notify.py "{{ '{{ mnt_point }}' }}: Disk space is used by 90%" "danger" 237 | # Allow additional file system types (comma-separated FS type) 238 | allow=cifs 239 | 240 | [irq] 241 | # Documentation: https://glances.readthedocs.io/en/latest/aoa/irq.html 242 | # This plugin is disabled by default 243 | disable=True 244 | 245 | [folders] 246 | # Documentation: https://glances.readthedocs.io/en/latest/aoa/folders.html 247 | disable=False 248 | # Define a folder list to monitor 249 | # The list is composed of items (list_#nb <= 10) 250 | # An item is defined by: 251 | # * path: absolute path 252 | # * careful: optional careful threshold (in MB) 253 | # * warning: optional warning threshold (in MB) 254 | # * critical: optional critical threshold (in MB) 255 | # * refresh: interval in second between two refreshes 256 | #folder_1_path=/tmp 257 | #folder_1_careful=2500 258 | #folder_1_warning=3000 259 | #folder_1_critical=3500 260 | #folder_1_refresh=60 261 | #folder_2_path=/home/nicolargo/Videos 262 | #folder_2_warning=17000 263 | #folder_2_critical=20000 264 | #folder_3_path=/nonexisting 265 | #folder_4_path=/root 266 | 267 | [cloud] 268 | # Documentation: https://glances.readthedocs.io/en/latest/aoa/cloud.html 269 | # This plugin is disabled by default 270 | disable=True 271 | 272 | [raid] 273 | # Documentation: https://glances.readthedocs.io/en/latest/aoa/raid.html 274 | # This plugin is disabled by default 275 | disable=True 276 | 277 | [smart] 278 | # Documentation: https://glances.readthedocs.io/en/latest/aoa/smart.html 279 | # This plugin is disabled by default 280 | disable=True 281 | 282 | [hddtemp] 283 | disable=False 284 | # Define hddtemp server IP and port (default is 127.0.0.1 and 7634 (TCP)) 285 | host=127.0.0.1 286 | port=7634 287 | 288 | [sensors] 289 | # Documentation: https://glances.readthedocs.io/en/latest/aoa/sensors.html 290 | disable=False 291 | # By default refresh every refresh time * 2 292 | #refresh=6 293 | # Hide some sensors 294 | #hide=ambient 295 | # Sensors core thresholds (in Celsius...) 296 | # Default values are grabbed from the system 297 | #temperature_core_careful=60 298 | #temperature_core_warning=70 299 | #temperature_core_critical=80 300 | # Temperatures threshold in °C for hddtemp 301 | # Default values if not defined: 45/52/60 302 | temperature_hdd_careful=45 303 | temperature_hdd_warning=52 304 | temperature_hdd_critical=60 305 | # Battery threshold in % 306 | battery_careful=80 307 | battery_warning=90 308 | battery_critical=95 309 | # Sensors alias 310 | #temp1_alias=Motherboard 0 311 | #temp2_alias=Motherboard 1 312 | #core 0_temperature_core_alias=CPU Core 0 temp 313 | #core 0_fans_speed_alias=CPU Core 0 fan 314 | #or 315 | #core 0_alias=CPU Core 0 316 | #core 1_alias=CPU Core 1 317 | 318 | [processcount] 319 | disable=False 320 | # If you want to change the refresh rate of the processing list, please uncomment: 321 | #refresh=10 322 | 323 | [processlist] 324 | disable=False 325 | # Sort key: if not defined, the sort is automatically done by Glances (recommended) 326 | # Should be one of the following: 327 | # cpu_percent, memory_percent, io_counters, name, cpu_times, username 328 | #sort_key=memory_percent 329 | # Define CPU/MEM (per process) thresholds in % 330 | # Default values if not defined: 50/70/90 331 | cpu_careful=50 332 | cpu_warning=70 333 | cpu_critical=90 334 | mem_careful=50 335 | mem_warning=70 336 | mem_critical=90 337 | # 338 | # Nice priorities range from -20 to 19. 339 | # Configure nice levels using a comma separated list. 340 | # 341 | # Nice: Example 1, non-zero is warning (default behavior) 342 | nice_warning=-20,-19,-18,-17,-16,-15,-14,-13,-12,-11,-10,-9,-8,-7,-6,-5,-4,-3,-2,-1,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19 343 | # 344 | # Nice: Example 2, low priority processes escalate from careful to critical 345 | #nice_careful=1,2,3,4,5,6,7,8,9 346 | #nice_warning=10,11,12,13,14 347 | #nice_critical=15,16,17,18,19 348 | 349 | [ports] 350 | disable=True 351 | # Interval in second between two scans 352 | # Ports scanner plugin configuration 353 | refresh=30 354 | # Set the default timeout (in second) for a scan (can be overwritten in the scan list) 355 | timeout=3 356 | # If port_default_gateway is True, add the default gateway on top of the scan list 357 | port_default_gateway=True 358 | # 359 | # Define the scan list (1 < x < 255) 360 | # port_x_host (name or IP) is mandatory 361 | # port_x_port (TCP port number) is optional (if not set, use ICMP) 362 | # port_x_description is optional (if not set, define to host:port) 363 | # port_x_timeout is optional and overwrite the default timeout value 364 | # port_x_rtt_warning is optional and defines the warning threshold in ms 365 | # 366 | #port_1_host=192.168.0.1 367 | #port_1_port=80 368 | #port_1_description=Home Box 369 | #port_1_timeout=1 370 | #port_2_host=www.free.fr 371 | #port_2_description=My ISP 372 | #port_3_host=www.google.com 373 | #port_3_description=Internet ICMP 374 | #port_3_rtt_warning=1000 375 | #port_4_description=Internet Web 376 | #port_4_host=www.google.com 377 | #port_4_port=80 378 | #port_4_rtt_warning=1000 379 | # 380 | # Define Web (URL) monitoring list (1 < x < 255) 381 | # web_x_url is the URL to monitor (example: http://my.site.com/folder) 382 | # web_x_description is optional (if not set, define to URL) 383 | # web_x_timeout is optional and overwrite the default timeout value 384 | # web_x_rtt_warning is optional and defines the warning respond time in ms (approximately) 385 | # 386 | #web_1_url=https://blog.nicolargo.com 387 | #web_1_description=My Blog 388 | #web_1_rtt_warning=3000 389 | #web_2_url=https://github.com 390 | #web_3_url=http://www.google.fr 391 | #web_3_description=Google Fr 392 | #web_4_url=https://blog.nicolargo.com/nonexist 393 | #web_4_description=Intranet 394 | 395 | [containers] 396 | disable=False 397 | # Only show specific containers (comma separated list of container name or regular expression) 398 | # Comment this line to display all containers (default configuration) 399 | #show=telegraf 400 | # Hide some containers (comma separated list of container name or regular expression) 401 | # Comment this line to display all containers (default configuration) 402 | #hide=telegraf 403 | # Define the maximum docker size name (default is 20 chars) 404 | max_name_size=20 405 | cpu_careful=50 406 | # Thresholds for CPU and MEM (in %) 407 | cpu_warning=70 408 | cpu_critical=90 409 | mem_careful=20 410 | mem_warning=50 411 | mem_critical=70 412 | # 413 | # Per container thresholds 414 | #containername_cpu_careful=10 415 | #containername_cpu_warning=20 416 | #containername_cpu_critical=30 417 | # 418 | # By default, Glances only display running containers 419 | # Set the following key to True to display all containers 420 | all=True 421 | # Define Podman sock 422 | #podman_sock=unix:///run/user/1000/podman/podman.sock 423 | 424 | [amps] 425 | # AMPs configuration are defined in the bottom of this file 426 | disable=False 427 | 428 | ############################################################################## 429 | # Client/server 430 | ############################################################################## 431 | 432 | [serverlist] 433 | # Define the static servers list 434 | #server_1_name=localhost 435 | #server_1_alias=My local PC 436 | #server_1_port=61209 437 | #server_2_name=localhost 438 | #server_2_port=61235 439 | #server_3_name=192.168.0.17 440 | #server_3_alias=Another PC on my network 441 | #server_3_port=61209 442 | #server_4_name=pasbon 443 | #server_4_port=61237 444 | 445 | [passwords] 446 | # Define the passwords list related to the [serverlist] section 447 | # Syntax: host=password 448 | # Where: host is the hostname 449 | # password is the clear password 450 | # Additionally (and optionally) a default password could be defined 451 | #localhost=abc 452 | default={{ glances_password }} 453 | # 454 | # Define the path of the local '.pwd' file (default is system one) 455 | #local_password_path=~/.config/glances 456 | 457 | ############################################################################## 458 | # Exports 459 | ############################################################################## 460 | 461 | [graph] 462 | # Configuration for the --export graph option 463 | # Set the path where the graph (.svg files) will be created 464 | # Can be overwrite by the --graph-path command line option 465 | path=/tmp 466 | # It is possible to generate the graphs automatically by setting the 467 | # generate_every to a non zero value corresponding to the seconds between 468 | # two generation. Set it to 0 to disable graph auto generation. 469 | generate_every=0 470 | # See following configuration keys definitions in the Pygal lib documentation 471 | # http://pygal.org/en/stable/documentation/index.html 472 | width=800 473 | height=600 474 | style=DarkStyle 475 | 476 | [influxdb] 477 | # !!! 478 | # Will be DEPRECATED in future release. 479 | # Please have a look on the new influxdb2 export module (compatible with InfluxDB 1.8.x and 2.x) 480 | # !!! 481 | # Configuration for the --export influxdb option 482 | # https://influxdb.com/ 483 | host=localhost 484 | port=8086 485 | protocol=http 486 | user=root 487 | password=root 488 | db=glances 489 | # Prefix will be added for all measurement name 490 | # Ex: prefix=foo 491 | # => foo.cpu 492 | # => foo.mem 493 | # You can also use dynamic values 494 | #prefix=foo 495 | # Following tags will be added for all measurements 496 | # You can also use dynamic values. 497 | # Note: hostname is always added as a tag 498 | #tags=foo:bar,spam:eggs,domain:`domainname` 499 | 500 | [influxdb2] 501 | # Configuration for the --export influxdb2 option 502 | # https://influxdb.com/ 503 | host=localhost 504 | port=8086 505 | protocol=http 506 | org=nicolargo 507 | bucket=glances 508 | token=EjFUTWe8U-MIseEAkaVIgVnej_TrnbdvEcRkaB1imstW7gapSqy6_6-8XD-yd51V0zUUpDy-kAdVD1purDLuxA== 509 | # Set the interval between two exports (in seconds) 510 | # If the interval is set to 0, the Glances refresh time is used (default behavor) 511 | #interval=0 512 | # Prefix will be added for all measurement name 513 | # Ex: prefix=foo 514 | # => foo.cpu 515 | # => foo.mem 516 | # You can also use dynamic values 517 | #prefix=foo 518 | # Following tags will be added for all measurements 519 | # You can also use dynamic values. 520 | # Note: hostname is always added as a tag 521 | #tags=foo:bar,spam:eggs,domain:`domainname` 522 | 523 | [cassandra] 524 | # Configuration for the --export cassandra option 525 | # Also works for the ScyllaDB 526 | # https://influxdb.com/ or http://www.scylladb.com/ 527 | host=localhost 528 | port=9042 529 | protocol_version=3 530 | keyspace=glances 531 | replication_factor=2 532 | # If not define, table name is set to host key 533 | table=localhost 534 | # If not define, username and password will not be used 535 | #username=cassandra 536 | #password=password 537 | 538 | [opentsdb] 539 | # Configuration for the --export opentsdb option 540 | # http://opentsdb.net/ 541 | host=localhost 542 | port=4242 543 | #prefix=glances 544 | #tags=foo:bar,spam:eggs 545 | 546 | [statsd] 547 | # Configuration for the --export statsd option 548 | # https://github.com/etsy/statsd 549 | host=localhost 550 | port=8125 551 | #prefix=glances 552 | 553 | [elasticsearch] 554 | # Configuration for the --export elasticsearch option 555 | # Data are available via the ES RESTful API. ex: URL//cpu 556 | # https://www.elastic.co 557 | scheme=http 558 | host=localhost 559 | port=9200 560 | index=glances 561 | 562 | [riemann] 563 | # Configuration for the --export riemann option 564 | # http://riemann.io 565 | host=localhost 566 | port=5555 567 | 568 | [rabbitmq] 569 | # Configuration for the --export rabbitmq option 570 | host=localhost 571 | port=5672 572 | user=guest 573 | password=guest 574 | queue=glances_queue 575 | #protocol=amqps 576 | 577 | [mqtt] 578 | # Configuration for the --export mqtt option 579 | host=localhost 580 | port=8883 581 | tls=false 582 | user=guest 583 | password=guest 584 | topic=glances 585 | topic_structure=per-metric 586 | 587 | [couchdb] 588 | # Configuration for the --export couchdb option 589 | # https://www.couchdb.org 590 | host=localhost 591 | port=5984 592 | db=glances 593 | # user and password are optional (comment if not configured on the server side) 594 | # If they are used, then the https protocol will be used 595 | #user=root 596 | #password=root 597 | 598 | [mongodb] 599 | # Configuration for the --export mongodb option 600 | # https://www.mongodb.com 601 | host=localhost 602 | port=27017 603 | db=glances 604 | user=root 605 | password=example 606 | 607 | [kafka] 608 | # Configuration for the --export kafka option 609 | # http://kafka.apache.org/ 610 | host=localhost 611 | port=9092 612 | topic=glances 613 | #compression=gzip 614 | # Tags will be added for all events 615 | #tags=foo:bar,spam:eggs 616 | # You can also use dynamic values 617 | #tags=hostname:`hostname -f` 618 | 619 | [zeromq] 620 | # Configuration for the --export zeromq option 621 | # http://www.zeromq.org 622 | # Use * to bind on all interfaces 623 | host=* 624 | port=5678 625 | # Glances envelopes the stats in a publish message with two frames: 626 | # - First frame containing the following prefix (STRING) 627 | # - Second frame with the Glances plugin name (STRING) 628 | # - Third frame with the Glances plugin stats (JSON) 629 | prefix=G 630 | 631 | [prometheus] 632 | # Configuration for the --export prometheus option 633 | # https://prometheus.io 634 | # Create a Prometheus exporter listening on localhost:9091 (default configuration) 635 | # Metric are exporter using the following name: 636 | # __{labelkey:labelvalue} 637 | # Note: You should add this exporter to your Prometheus server configuration: 638 | # scrape_configs: 639 | # - job_name: 'glances_exporter' 640 | # scrape_interval: 5s 641 | # static_configs: 642 | # - targets: ['localhost:9091'] 643 | # 644 | # Labels will be added for all measurements (default is src:glances) 645 | # labels=foo:bar,spam:eggs 646 | # You can also use dynamic values 647 | # labels=system:`uname -s` 648 | # 649 | host=localhost 650 | port=9091 651 | #prefix=glances 652 | labels=src:glances 653 | 654 | [restful] 655 | # Configuration for the --export restful option 656 | # Example, export to http://localhost:6789/ 657 | host=localhost 658 | port=6789 659 | protocol=http 660 | path=/ 661 | 662 | [graphite] 663 | # Configuration for the --export graphite option 664 | # https://graphiteapp.org/ 665 | host=localhost 666 | port=2003 667 | # Prefix will be added for all measurement name 668 | prefix=glances 669 | # System name added between the prefix and the stats 670 | # By default, system_name = FQDN 671 | #system_name=mycomputer 672 | 673 | ############################################################################## 674 | # AMPS 675 | # * enable: Enable (true) or disable (false) the AMP 676 | # * regex: Regular expression to filter the process(es) 677 | # * refresh: The AMP is executed every refresh seconds 678 | # * one_line: (optional) Force (if true) the AMP to be displayed in one line 679 | # * command: (optional) command to execute when the process is detected (thk to the regex) 680 | # * countmin: (optional) minimal number of processes 681 | # A warning will be displayed if number of process < count 682 | # * countmax: (optional) maximum number of processes 683 | # A warning will be displayed if number of process > count 684 | # * : Others variables can be defined and used in the AMP script 685 | ############################################################################## 686 | 687 | [amp_dropbox] 688 | # Use the default AMP (no dedicated AMP Python script) 689 | # Check if the Dropbox daemon is running 690 | # Every 3 seconds, display the 'dropbox status' command line 691 | enable=false 692 | regex=.*dropbox.* 693 | refresh=3 694 | one_line=false 695 | command=dropbox status 696 | countmin=1 697 | 698 | [amp_python] 699 | # Use the default AMP (no dedicated AMP Python script) 700 | # Monitor all the Python scripts 701 | # Alert if more than 20 Python scripts are running 702 | enable=false 703 | regex=.*python.* 704 | refresh=3 705 | countmax=20 706 | 707 | [amp_conntrack] 708 | # Use comma separated for multiple commands (no space around the comma) 709 | # If the regex key is not defined, the AMP will be executed every refresh second 710 | # and the process count will not be displayed (countmin and countmax will be ignore) 711 | enable=false 712 | refresh=30 713 | one_line=false 714 | command=sysctl net.netfilter.nf_conntrack_count;sysctl net.netfilter.nf_conntrack_max 715 | 716 | [amp_nginx] 717 | # Use the NGinx AMP 718 | # Nginx status page should be enable (https://easyengine.io/tutorials/nginx/status-page/) 719 | enable=false 720 | regex=\/usr\/sbin\/nginx 721 | refresh=60 722 | one_line=false 723 | status_url=http://localhost/nginx_status 724 | 725 | [amp_systemd] 726 | # Use the Systemd AMP 727 | enable=false 728 | regex=\/lib\/systemd\/systemd 729 | refresh=30 730 | one_line=true 731 | systemctl_cmd=/bin/systemctl --plain 732 | 733 | [amp_systemv] 734 | # Use the Systemv AMP 735 | enable=false 736 | regex=\/sbin\/init 737 | refresh=30 738 | one_line=true 739 | service_cmd=/usr/bin/service --status-all -------------------------------------------------------------------------------- /roles/glances/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | glances_docker_settings_default: 3 | network: "host" 4 | puid: "1000" 5 | pgid: "1000" 6 | tz: "Etc/UTC" 7 | 8 | glances_env_default: 9 | TZ: "{{ glances_docker_settings.tz }}" 10 | PUID: "{{ glances_docker_settings.puid }}" 11 | PGID: "{{ glances_docker_settings.pgid }}" 12 | GLANCES_OPT: "-w" 13 | -------------------------------------------------------------------------------- /roles/homarr/README.md: -------------------------------------------------------------------------------- 1 | # selfhosted.homarr 2 | 3 | Installs [Homarr](https://homarr.dev) - A simple, yet powerful dashboard for your server. 4 | 5 | > [!NOTE] 6 | > This role is not actively maintained as it is not in use since I moved to a much more simplified approach - bookmarks. 7 | > Bugs may be present due to incompatibility with the latest versions of Homarr image. 8 | 9 | ## Role Variables 10 | 11 | - `homarr_version` 12 | - Default: `latest` 13 | - Description: The version of Homarr to install. See [tags](https://hub.docker.com/r/jesec/homarr/tags). 14 | - Type: str 15 | - Required: no 16 | - `homarr_port` 17 | - Default: `80` 18 | - Description: The port on which Homarr will be accessible. 19 | - Type: int 20 | - Required: no 21 | - `homarr_install_dir` 22 | - Default: `/opt/docker/homarr` 23 | - Description: The directory where Homarr will be installed. 24 | - Type: str 25 | - Required: no 26 | - `homarr_docker_settings` 27 | - Default: See [homarr_docker_settings_default](./vars/main.yml) 28 | - Description: Docker container settings. 29 | - Type: dict 30 | - Required: no 31 | 32 | ## Dependencies 33 | 34 | - [community.docker](https://docs.ansible.com/ansible/latest/collections/community/docker/index.html) 35 | 36 | ## Example Playbook 37 | 38 | ```yaml 39 | - hosts: localhost 40 | 41 | roles: 42 | - artyorsh.selfhosted.homarr 43 | ``` -------------------------------------------------------------------------------- /roles/homarr/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://github.com/ajnart/homarr/pkgs/container/homarr 3 | homarr_version: "latest" 4 | homarr_port: 80 5 | homarr_install_dir: "/opt/docker/homarr" 6 | 7 | homarr_docker_settings: 8 | network: "host" 9 | puid: "1000" 10 | pgid: "1000" 11 | tz: "Etc/UTC" -------------------------------------------------------------------------------- /roles/homarr/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://github.com/linuxserver/docker-homarr#parameters 3 | 4 | - name: "Prepare task variables" 5 | ansible.builtin.set_fact: 6 | homarr_service_id: "homarr" 7 | 8 | - name: "Make sure config directories exist" 9 | ansible.builtin.file: 10 | path: "{{ homarr_install_dir }}/{{ item }}" 11 | state: "directory" 12 | owner: "{{ ansible_user }}" 13 | group: "{{ ansible_user }}" 14 | mode: "0774" 15 | with_items: 16 | - "configs" 17 | - "icons" 18 | - "data" 19 | 20 | - name: "Make sure {{ homarr_docker_settings.network }} docker network exists" 21 | when: homarr_docker_settings.network != "host" 22 | community.general.docker_network: 23 | name: "{{ homarr_docker_settings.network }}" 24 | state: "present" 25 | 26 | - name: "Run homarr container" 27 | community.general.docker_container: 28 | name: "{{ homarr_service_id }}" 29 | image: "ghcr.io/ajnart/homarr:{{ homarr_version }}" 30 | networks: 31 | - name: "{{ homarr_docker_settings.network }}" 32 | state: "started" 33 | env: 34 | TZ: "{{ homarr_docker_settings.tz }}" 35 | PUID: "{{ homarr_docker_settings.puid }}" 36 | PGID: "{{ homarr_docker_settings.pgid }}" 37 | DISABLE_ANALYTICS: "true" 38 | ports: 39 | - "{{ homarr_port }}:7575" 40 | volumes: 41 | - "{{ homarr_install_dir }}/configs:/app/data/configs" 42 | - "{{ homarr_install_dir }}/icons:/app/public/icons" 43 | - "{{ homarr_install_dir }}/data:/data" 44 | restart_policy: "unless-stopped" 45 | -------------------------------------------------------------------------------- /roles/homebox/README.md: -------------------------------------------------------------------------------- 1 | # selfhosted.homebox 2 | 3 | Installs [Homebox](https://homebox.software/en/) - a simple home inventory management software. 4 | 5 | ## Role Variables 6 | 7 | - `homebox_version` 8 | - Default: `latest` 9 | - Description: The version of Homebox to install. See [tags](https://github.com/sysadminsmedia/homebox/pkgs/container/homebox). 10 | - Type: str 11 | - Required: no 12 | - `homebox_webui_port` 13 | - Default: `7745` 14 | - Description: The port on which Homebox's web interface will be accessible. 15 | - Type: int 16 | - Required: no 17 | - `homebox_install_dir` 18 | - Default: `/opt/docker/homebox` 19 | - Description: The directory where Homebox will be installed. 20 | - Type: str 21 | - Required: no 22 | - `homebox_env` 23 | - Default: See [homebox_env_default](./vars/main.yml) 24 | - Description: Docker container environment variables. See [docs](https://homebox.software/en/configure-homebox.html#env-variables-configuration). 25 | - Type: dict 26 | - Required: no 27 | - `homebox_docker_settings` 28 | - Default: See [homebox_docker_settings_default](./vars/main.yml) 29 | - Description: Docker container settings. 30 | - Type: dict 31 | - Required: no 32 | 33 | ## Dependencies 34 | 35 | - [community.docker](https://docs.ansible.com/ansible/latest/collections/community/docker/index.html) 36 | 37 | ## Example Playbook 38 | 39 | ```yaml 40 | - hosts: localhost 41 | 42 | roles: 43 | - artyorsh.selfhosted.homebox 44 | ``` -------------------------------------------------------------------------------- /roles/homebox/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://github.com/sysadminsmedia/homebox/pkgs/container/homebox 3 | homebox_version: "latest" 4 | homebox_install_dir: "/opt/docker/homebox" 5 | 6 | homebox_webui_port: 7745 7 | # https://homebox.software/en/configure-homebox.html#env-variables-configuration 8 | homebox_env: {} 9 | 10 | homebox_docker_settings: {} 11 | -------------------------------------------------------------------------------- /roles/homebox/tasks/homebox.yml: -------------------------------------------------------------------------------- 1 | # https://hay-kot.github.io/homebox/quick-start/#env-variables-configuration 2 | --- 3 | 4 | - name: "Prepare task variables" 5 | ansible.builtin.set_fact: 6 | homebox_service_id: "homebox" 7 | homebox_docker_settings: "{{ homebox_docker_settings_default | combine(homebox_docker_settings) }}" 8 | 9 | - name: "Make sure {{ homebox_docker_settings.network }} docker network exists" 10 | when: homebox_docker_settings.network != "host" 11 | community.general.docker_network: 12 | name: "{{ homebox_docker_settings.network }}" 13 | state: "present" 14 | 15 | - name: "Run homebox container" 16 | community.general.docker_container: 17 | name: "{{ homebox_service_id }}" 18 | image: "ghcr.io/sysadminsmedia/homebox:{{ homebox_version }}" 19 | networks: 20 | - name: "{{ homebox_docker_settings.network }}" 21 | state: "started" 22 | env: "{{ homebox_env_default | combine(homebox_env) }}" 23 | ports: 24 | - "{{ homebox_webui_port }}:7745" 25 | volumes: 26 | - "{{ homebox_install_dir }}:/data" 27 | restart_policy: "unless-stopped" 28 | -------------------------------------------------------------------------------- /roles/homebox/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Install and configure Homebox" 3 | ansible.builtin.include_tasks: "homebox.yml" -------------------------------------------------------------------------------- /roles/homebox/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | homebox_docker_settings_default: 3 | network: "host" 4 | puid: "1000" 5 | pgid: "1000" 6 | tz: "Etc/UTC" 7 | 8 | homebox_env_default: 9 | TZ: "{{ homebox_docker_settings.tz }}" 10 | PUID: "{{ homebox_docker_settings.puid }}" 11 | PGID: "{{ homebox_docker_settings.pgid }}" 12 | HBOX_OPTIONS_ALLOW_REGISTRATION: "true" 13 | HBOX_WEB_MAX_UPLOAD_SIZE: "50" 14 | -------------------------------------------------------------------------------- /roles/nginx/README.md: -------------------------------------------------------------------------------- 1 | # selfhosted.nginx 2 | 3 | Installs [nginx-proxy-manager](https://github.com/NginxProxyManager/nginx-proxy-manager) - a Docker container for managing Nginx proxy hosts with a simple, powerful interface. 4 | 5 | > [!NOTE] 6 | > The role is tested to run on a bridge network. 7 | > Running on host network (which is the default) may cause issues with binding. 8 | 9 | ## Role Variables 10 | 11 | - `nginx_version` 12 | - Default: `latest` 13 | - Description: The version of nginx-proxy-manager to install. See [tags](https://hub.docker.com/r/jc21/nginx-proxy-manager/tags). 14 | - Type: str 15 | - Required: no 16 | - `nginx_port_http` 17 | - Default: `80` 18 | - Description: The port on which nginx-proxy-manager will be accessible. 19 | - Type: int 20 | - Required: no 21 | - `nginx_port_https` 22 | - Default: `443` 23 | - Description: The port on which nginx-proxy-manager will be accessible. 24 | - Type: int 25 | - Required: no 26 | - `nginx_port_admin` 27 | - Default: `81` 28 | - Description: The port on which nginx-proxy-manager's admin interface will be accessible. 29 | - Type: int 30 | - Required: no 31 | - `nginx_install_dir` 32 | - Default: `/opt/docker/nginx` 33 | - Description: The directory where nginx-proxy-manager will be installed. 34 | - Type: str 35 | - Required: no 36 | - `nginx_env` 37 | - Default: See [nginx_env_default](./vars/main.yml) 38 | - Description: Docker container environment variables. See [docs](https://nginxproxymanager.com/advanced-config/#advanced-configuration). 39 | - Type: dict 40 | - Required: no 41 | - `nginx_docker_settings` 42 | - Default: See [nginx_docker_settings_default](./vars/main.yml) 43 | - Description: Docker container settings. 44 | - Type: dict 45 | - Required: no 46 | 47 | ## Dependencies 48 | 49 | - [community.docker](https://docs.ansible.com/ansible/latest/collections/community/docker/index.html) 50 | 51 | ## Example Playbook 52 | 53 | ```yaml 54 | - hosts: localhost 55 | 56 | vars: 57 | nginx_docker_settings: 58 | network: "my-docker-network" 59 | puid: "1000" # id -u 60 | pgid: "1000" # id -g 61 | 62 | roles: 63 | - artyorsh.selfhosted.nginx 64 | ``` 65 | 66 | ## Disable IPv6 67 | 68 | ```yaml 69 | - hosts: localhost 70 | 71 | vars: 72 | nginx_docker_settings: 73 | network: "my-docker-network" 74 | puid: "1000" # id -u 75 | pgid: "1000" # id -g 76 | nginx_env: 77 | disable_ipv6: "true" 78 | 79 | roles: 80 | - artyorsh.selfhosted.nginx 81 | ``` -------------------------------------------------------------------------------- /roles/nginx/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://hub.docker.com/r/jc21/nginx-proxy-manager/tags 3 | nginx_version: "latest" 4 | nginx_port_http: 80 5 | nginx_port_https: 443 6 | nginx_port_admin: 81 7 | nginx_install_dir: "/opt/docker/nginx" 8 | 9 | # https://nginxproxymanager.com/advanced-config/#advanced-configuration 10 | nginx_env: {} 11 | nginx_docker_settings: {} 12 | -------------------------------------------------------------------------------- /roles/nginx/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Prepare task variables" 3 | ansible.builtin.set_fact: 4 | nginx_service_id: "nginx-proxy-manager" 5 | nginx_docker_settings: "{{ nginx_docker_settings_default | combine(nginx_docker_settings) }}" 6 | 7 | - name: "Make sure config directory exists" 8 | ansible.builtin.file: 9 | path: "{{ nginx_install_dir }}" 10 | owner: "{{ ansible_user }}" 11 | group: "{{ ansible_user }}" 12 | state: "directory" 13 | mode: "0774" 14 | loop: 15 | - "data" 16 | - "letsencrypt" 17 | 18 | - name: "Make sure {{ nginx_docker_settings.network }} docker network exists" 19 | when: nginx_docker_settings.network != "host" 20 | community.general.docker_network: 21 | name: "{{ nginx_docker_settings.network }}" 22 | state: "present" 23 | 24 | - name: "Run nginx-proxy-manager container" 25 | community.general.docker_container: 26 | name: "{{ nginx_service_id }}" 27 | image: "jc21/nginx-proxy-manager:{{ nginx_version }}" 28 | networks: 29 | - name: "{{ nginx_docker_settings.network }}" 30 | state: "started" 31 | env: "{{ nginx_env_default | combine(nginx_env) }}" 32 | ports: 33 | - "{{ nginx_port_http }}:80" 34 | - "{{ nginx_port_https }}:443" 35 | - "{{ nginx_port_admin }}:81" 36 | volumes: 37 | - "{{ nginx_install_dir }}/data:/data" 38 | - "{{ nginx_install_dir }}/letsencrypt:/etc/letsencrypt" 39 | restart_policy: "unless-stopped" 40 | -------------------------------------------------------------------------------- /roles/nginx/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | nginx_docker_settings_default: 3 | network: "host" 4 | puid: "1000" 5 | pgid: "1000" 6 | tz: "Etc/UTC" 7 | 8 | nginx_env_default: 9 | TZ: "{{ nginx_docker_settings.tz }}" 10 | PUID: "{{ nginx_docker_settings.puid }}" 11 | PGID: "{{ nginx_docker_settings.pgid }}" 12 | -------------------------------------------------------------------------------- /roles/olivetin/README.md: -------------------------------------------------------------------------------- 1 | # selfhosted.olivetin 2 | 3 | Installs [Olivetin](https://www.olivetin.app) - a simple access to predefined shell commands from a web interface. 4 | 5 | ## Role Variables 6 | 7 | - `olivetin_version` 8 | - Default: `latest` 9 | - Description: The version of Olivetin to install. See [tags](https://hub.docker.com/r/jamesread/olivetin/tags). 10 | - Type: str 11 | - Required: no 12 | - `olivetin_port` 13 | - Default: `1337` 14 | - Description: The port on which Olivetin will be accessible. 15 | - Type: int 16 | - Required: no 17 | - `olivetin_install_dir` 18 | - Default: `/opt/docker/olivetin` 19 | - Description: The directory where Olivetin will be installed. 20 | - Type: str 21 | - Required: no 22 | - `olivetin_env` 23 | - Default: See [olivetin_env_default](./vars/main.yml) 24 | - Description: Docker container environment variables. 25 | - Type: dict 26 | - Required: no 27 | - `olivetin_config` 28 | - Default: See [olivetin_config_default](./vars/main.yml) 29 | - Description: The contents of `config.yaml`. See [docs](https://raw.githubusercontent.com/OliveTin/OliveTin/main/config.yaml). 30 | - Type: dict 31 | - Required: no 32 | - `olivetin_docker_settings` 33 | - Default: See [olivetin_docker_settings_default](./vars/main.yml) 34 | - Description: Docker container settings. 35 | - Type: dict 36 | - Required: no 37 | 38 | ## Dependencies 39 | 40 | - [community.docker](https://docs.ansible.com/ansible/latest/collections/community/docker/index.html) 41 | 42 | ## Example Playbook 43 | 44 | ```yaml 45 | - hosts: localhost 46 | 47 | roles: 48 | - artyorsh.selfhosted.olivetin 49 | ``` -------------------------------------------------------------------------------- /roles/olivetin/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://hub.docker.com/r/jamesread/olivetin/tags 3 | olivetin_version: "latest" 4 | olivetin_port: 1337 5 | olivetin_install_dir: "/opt/docker/olivetin" 6 | 7 | olivetin_dockersock_path: "/var/run/docker.sock" 8 | 9 | olivetin_env: {} 10 | 11 | # https://raw.githubusercontent.com/OliveTin/OliveTin/main/config.yaml 12 | olivetin_config: {} 13 | 14 | olivetin_docker_settings: {} 15 | -------------------------------------------------------------------------------- /roles/olivetin/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Restart olivetin container" 3 | community.general.docker_container: 4 | name: "{{ olivetin_service_id }}" 5 | state: "started" 6 | restart: true 7 | -------------------------------------------------------------------------------- /roles/olivetin/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Prepare task variables" 3 | ansible.builtin.set_fact: 4 | olivetin_service_id: "olivetin" 5 | olivetin_docker_settings: "{{ olivetin_docker_settings_default | combine(olivetin_docker_settings) }}" 6 | 7 | - name: "Make sure {{ olivetin_docker_settings.network }} docker network exists" 8 | when: olivetin_docker_settings.network != "host" 9 | community.general.docker_network: 10 | name: "{{ olivetin_docker_settings.network }}" 11 | state: "present" 12 | 13 | - name: "Make sure config directories exist" 14 | ansible.builtin.file: 15 | path: "{{ item }}" 16 | owner: "{{ ansible_user }}" 17 | group: "{{ ansible_user }}" 18 | state: "directory" 19 | mode: "0774" 20 | loop: 21 | - "{{ olivetin_install_dir }}" 22 | 23 | - name: "Template configuration.yml" 24 | ansible.builtin.template: 25 | src: "config.yaml.j2" 26 | dest: "{{ olivetin_install_dir }}/config.yaml" 27 | owner: "{{ ansible_user }}" 28 | group: "{{ ansible_user }}" 29 | mode: "0740" 30 | notify: 31 | - "Restart olivetin container" 32 | 33 | - name: "Run olivetin container" 34 | community.general.docker_container: 35 | name: "{{ olivetin_service_id }}" 36 | image: "jamesread/olivetin:{{ olivetin_version }}" 37 | networks: 38 | - name: "{{ olivetin_docker_settings.network }}" 39 | state: "started" 40 | env: "{{ olivetin_env_default | combine(olivetin_env) }}" 41 | ports: 42 | - "{{ olivetin_port }}:1337" 43 | # Privileged mode, "root" user and a docker.sock mount are required to control other docker processes with OliveTin 44 | # https://docs.olivetin.app/action-container-control.html#_setup_if_running_inside_a_container 45 | privileged: true 46 | user: "root" 47 | volumes: 48 | - "{{ olivetin_dockersock_path }}:/var/run/docker.sock" 49 | - "{{ olivetin_install_dir }}:/config:ro" 50 | restart_policy: "unless-stopped" 51 | -------------------------------------------------------------------------------- /roles/olivetin/templates/config.yaml.j2: -------------------------------------------------------------------------------- 1 | {{ olivetin_default_config | combine(olivetin_config, recursive=true) }} -------------------------------------------------------------------------------- /roles/olivetin/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | olivetin_docker_settings_default: 3 | network: "host" 4 | puid: "1000" 5 | pgid: "1000" 6 | tz: "Etc/UTC" 7 | 8 | olivetin_env_default: 9 | TZ: "{{ olivetin_docker_settings.tz }}" 10 | # https://docs.olivetin.app/no-puid-pgid.html#no-puid-pgid 11 | # PUID: "{{ olivetin_docker_settings.puid }}" 12 | # PGID: "{{ olivetin_docker_settings.pgid }}" 13 | 14 | olivetin_default_config: {} 15 | -------------------------------------------------------------------------------- /roles/paperless_ai/README.md: -------------------------------------------------------------------------------- 1 | # selfhosted.paperless_ai 2 | 3 | Installs [paperless-ai](https://github.com/clusterzx/paperless-ai) - an automated document analyzer for Paperless-ngx using OpenAI API, Ollama and all OpenAI API compatible LLMs. 4 | 5 | ## Requirements 6 | 7 | - [Paperless-ngx](../paperlessngx/README.md) 8 | - [OpenAI API key](https://platform.openai.com/settings/organization/api-keys) in case not running local LLMs. 9 | 10 | ## Role Variables 11 | 12 | - `paperless_ai_version` 13 | - Default: `latest` 14 | - Description: The version of paperless-ai to install. See [docker tags](https://hub.docker.com/r/clusterzx/paperless-ai/tags). 15 | - Type: str 16 | - Required: no 17 | - `paperless_ai_port` 18 | - Default: `3000` 19 | - Description: The port on which paperless-ai will be accessible. 20 | - Type: int 21 | - Required: no 22 | - `paperless_ai_docker_settings` 23 | - Default: See [paperless_ai_docker_settings_default](./vars/main.yml) 24 | - Description: Docker container settings. 25 | - Type: dict 26 | - Required: no 27 | - `paperless_ai_install_dir` 28 | - Default: `/opt/docker/paperless-ai` 29 | - Description: The directory where paperless-ai will be installed. 30 | - Type: str 31 | - Required: no 32 | - `paperless_ai_env` 33 | - Default: See [paperless_ai_env_default](./vars/main.yml) 34 | - Description: Docker container environment variables. See [.env.example](https://github.com/clusterzx/paperless-ai/blob/main/.env.example). 35 | - Type: dict 36 | - Required: no 37 | 38 | ## Dependencies 39 | 40 | - [community.docker](https://docs.ansible.com/ansible/latest/collections/community/docker/index.html) 41 | 42 | ## Example Playbook 43 | 44 | ```yaml 45 | - hosts: localhost 46 | 47 | roles: 48 | - artyorsh.selfhosted.paperless_ai 49 | ``` 50 | 51 | ### Install with paperlessngx 52 | 53 | Paperless-AI requires API access to Paperless-NGX. After the installation, visit the {{ paperlessngx_url}}/admin/authtoken/tokenproxy to create one. 54 | 55 | ```yaml 56 | - hosts: localhost 57 | 58 | roles: 59 | - artyorsh.selfhosted.paperlessngx 60 | - artyorsh.selfhosted.paperless_ai 61 | ``` 62 | -------------------------------------------------------------------------------- /roles/paperless_ai/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://hub.docker.com/r/clusterzx/paperless-ai/tags 3 | paperless_ai_version: "latest" 4 | 5 | paperless_ai_port: 3000 6 | paperless_ai_install_dir: "/opt/docker/paperless-ai" 7 | 8 | # https://github.com/clusterzx/paperless-ai/blob/main/.env.example 9 | paperless_ai_env: {} 10 | 11 | paperless_ai_docker_settings: {} -------------------------------------------------------------------------------- /roles/paperless_ai/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Prepare task variables" 3 | ansible.builtin.set_fact: 4 | paperless_ai_service_id: "paperless-ai" 5 | paperless_ai_docker_settings: "{{ paperless_ai_docker_settings_default | combine(paperless_ai_docker_settings) }}" 6 | 7 | - name: "Make sure config directories exist" 8 | ansible.builtin.file: 9 | path: "{{ item }}" 10 | owner: "{{ ansible_user }}" 11 | group: "{{ ansible_user }}" 12 | state: "directory" 13 | mode: "0774" 14 | loop: 15 | - "{{ paperless_ai_install_dir }}/data" 16 | 17 | - name: "Ensure {{ paperless_ai_docker_settings.network }} network exists" 18 | when: paperless_ai_docker_settings.network != "host" 19 | community.general.docker_network: 20 | name: "{{ paperless_ai_docker_settings.network }}" 21 | state: "present" 22 | 23 | - name: "Run app container" 24 | community.general.docker_container: 25 | name: "{{ paperless_ai_service_id }}" 26 | image: "clusterzx/paperless-ai:{{ paperless_ai_version }}" 27 | networks: 28 | - name: "{{ paperless_ai_docker_settings.network }}" 29 | state: "started" 30 | env: "{{ paperless_ai_env_default | combine(paperless_ai_env) }}" 31 | ports: 32 | - "{{ paperless_ai_port }}:3000" 33 | volumes: 34 | - "{{ paperless_ai_install_dir }}/data:/app/data" 35 | restart_policy: "unless-stopped" 36 | -------------------------------------------------------------------------------- /roles/paperless_ai/vars/main.yml: -------------------------------------------------------------------------------- 1 | paperless_ai_docker_settings_default: 2 | network: "host" 3 | puid: "1000" 4 | pgid: "1000" 5 | tz: "Etc/UTC" 6 | 7 | paperless_ai_env_default: 8 | TZ: "{{ paperless_ai_docker_settings.tz }}" 9 | PUID: "{{ paperless_ai_docker_settings.puid }}" 10 | PGID: "{{ paperless_ai_docker_settings.pgid }}" 11 | 12 | PAPERLESS_API_URL: "http://paperlessngx:8000/api" -------------------------------------------------------------------------------- /roles/paperlessngx/README.md: -------------------------------------------------------------------------------- 1 | # selfhosted.paperlessngx 2 | 3 | Installs [paperless-ngx](https://docs.paperless-ngx.com) - a community-supported open-source document management system that transforms your physical documents into a searchable online archive. 4 | 5 | > [!NOTE] 6 | > The role is tested to run on a bridge network. 7 | > Running on host network (which is the default) may cause issues with Redis. 8 | 9 | ## Role Variables 10 | 11 | - `paperlessngx_version` 12 | - Default: `latest` 13 | - Description: The version of paperless-ngx to install. See [docker tags](https://hub.docker.com/r/paperlessngx/paperless-ngx/tags). 14 | - Type: str 15 | - Required: no 16 | - `paperlessngx_redis_version` 17 | - Default: `7.2.4-alpine` 18 | - Description: The version of redis to install. Redis is required for processing scheduled tasks. See [docs](https://docs.paperless-ngx.com/configuration/#redis-broker). 19 | - Type: str 20 | - Required: no 21 | - `paperlessngx_port` 22 | - Default: `9898` 23 | - Description: The port on which paperless-ngx will be accessible. 24 | - Type: int 25 | - Required: no 26 | - `paperlessngx_docker_settings` 27 | - Default: See [paperlessngx_docker_settings_default](./vars/main.yml) 28 | - Description: Docker container settings. 29 | - Type: dict 30 | - Required: no 31 | - `paperlessngx_install_dir` 32 | - Default: `/opt/docker/paperlessngx` 33 | - Description: The directory where paperless-ngx will be installed. 34 | - Type: str 35 | - Required: no 36 | - `paperlessngx_consume_dir` 37 | - Default: `{{ paperlessngx_install_dir }}/consume` 38 | - Description: The directory where paperless-ngx will consume documents. See [docs](https://docs.paperless-ngx.com/usage/#the-consumption-directory). 39 | - Type: str 40 | - Required: no 41 | - `paperlessngx_media_dir` 42 | - Default: `{{ paperlessngx_install_dir }}/media` 43 | - Description: The directory where paperless-ngx will store media files. 44 | - Type: str 45 | - Required: no 46 | - `paperlessngx_env` 47 | - Default: See [paperlessngx_env_default](./vars/main.yml) 48 | - Description: Docker container environment variables. See [docs](https://docs.paperless-ngx.com/configuration/). 49 | - Type: dict 50 | - Required: no 51 | 52 | ## Dependencies 53 | 54 | - [community.docker](https://docs.ansible.com/ansible/latest/collections/community/docker/index.html) 55 | 56 | ## Example Playbook 57 | 58 | ```yaml 59 | - hosts: localhost 60 | 61 | vars: 62 | paperlessngx_consume_dir: "/path/to/documents/inbox" 63 | paperlessngx_media_dir: "/path/to/documents" 64 | paperlessngx_docker_settings: 65 | network: "my-bridge-network" 66 | 67 | roles: 68 | - artyorsh.selfhosted.paperlessngx 69 | ``` -------------------------------------------------------------------------------- /roles/paperlessngx/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://hub.docker.com/r/paperlessngx/paperless-ngx/tags 3 | paperlessngx_version: "latest" 4 | 5 | paperlessngx_port: 9898 6 | paperlessngx_install_dir: "/opt/docker/paperlessngx" 7 | paperlessngx_redis_version: "7.2.4-alpine" 8 | 9 | # https://docs.paperless-ngx.com/usage/#the-consumption-directory 10 | paperlessngx_consume_dir: "{{ paperlessngx_install_dir }}/consume" 11 | paperlessngx_media_dir: "{{ paperlessngx_install_dir }}/media" 12 | paperlessngx_export_dir: "{{ paperlessngx_install_dir }}/export" 13 | 14 | # https://docs.paperless-ngx.com/configuration/ 15 | paperlessngx_env: {} 16 | 17 | paperlessngx_docker_settings: {} -------------------------------------------------------------------------------- /roles/paperlessngx/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Prepare task variables" 3 | ansible.builtin.set_fact: 4 | paperlessngx_service_id: "paperlessngx" 5 | paperlessngx_docker_settings: "{{ paperlessngx_docker_settings_default | combine(paperlessngx_docker_settings) }}" 6 | 7 | - name: "Make sure config directories exist" 8 | ansible.builtin.file: 9 | path: "{{ item }}" 10 | owner: "{{ ansible_user }}" 11 | group: "{{ ansible_user }}" 12 | state: "directory" 13 | mode: "0774" 14 | loop: 15 | - "{{ paperlessngx_install_dir }}" 16 | - "{{ paperlessngx_consume_dir }}" 17 | - "{{ paperlessngx_media_dir }}" 18 | - "{{ paperlessngx_export_dir }}" 19 | 20 | - name: "Ensure {{ paperlessngx_docker_settings.network }} network exists" 21 | when: paperlessngx_docker_settings.network != "host" 22 | community.general.docker_network: 23 | name: "{{ paperlessngx_docker_settings.network }}" 24 | state: "present" 25 | 26 | - name: "Run redis container" 27 | community.general.docker_container: 28 | name: "{{ paperlessngx_service_id }}-redis" 29 | image: "redis:{{ paperlessngx_redis_version }}" 30 | networks: 31 | - name: "{{ paperlessngx_docker_settings.network }}" 32 | restart_policy: "always" 33 | 34 | - name: "Run app container" 35 | community.general.docker_container: 36 | name: "{{ paperlessngx_service_id }}" 37 | image: "ghcr.io/paperless-ngx/paperless-ngx:{{ paperlessngx_version }}" 38 | networks: 39 | - name: "{{ paperlessngx_docker_settings.network }}" 40 | state: "started" 41 | env: "{{ paperlessngx_env_default | combine(paperlessngx_env) }}" 42 | ports: 43 | - "{{ paperlessngx_port }}:8000" 44 | volumes: 45 | - "{{ paperlessngx_install_dir }}/data:/usr/src/paperless/data" 46 | - "{{ paperlessngx_media_dir }}:/usr/src/paperless/media" 47 | - "{{ paperlessngx_export_dir }}:/usr/src/paperless/export" 48 | - "{{ paperlessngx_consume_dir }}:/usr/src/paperless/consume" 49 | restart_policy: "unless-stopped" 50 | -------------------------------------------------------------------------------- /roles/paperlessngx/vars/main.yml: -------------------------------------------------------------------------------- 1 | paperlessngx_docker_settings_default: 2 | network: "host" 3 | puid: "1000" 4 | pgid: "1000" 5 | tz: "Etc/UTC" 6 | 7 | paperlessngx_env_default: 8 | TZ: "{{ paperlessngx_docker_settings.tz }}" 9 | PUID: "{{ paperlessngx_docker_settings.puid }}" 10 | PGID: "{{ paperlessngx_docker_settings.pgid }}" 11 | 12 | USERMAP_UID: "{{ paperlessngx_docker_settings.puid }}" 13 | USERMAP_GID: "{{ paperlessngx_docker_settings.pgid }}" 14 | PAPERLESS_REDIS: "redis://{{ paperlessngx_service_id }}-redis:6379" 15 | PAPERLESS_TIME_ZONE: "{{ paperlessngx_docker_settings.tz }}" 16 | PAPERLESS_OCR_LANGUAGE: "eng" 17 | # https://github.com/paperless-ngx/paperless-ngx/discussions/4047#discussioncomment-7019544 18 | PAPERLESS_OCR_USER_ARGS: '{"invalidate_digital_signatures": true}' 19 | # https://docs.paperless-ngx.com/configuration/#PAPERLESS_CONSUMER_RECURSIVE 20 | PAPERLESS_CONSUMER_RECURSIVE: "true" 21 | -------------------------------------------------------------------------------- /roles/postiz/README.md: -------------------------------------------------------------------------------- 1 | # selfhosted.postiz 2 | 3 | Installs [postiz](https://postiz.com) - an ultimate social media scheduling tool. 4 | Also installs a PostgreSQL database that is [required](https://docs.postiz.com/installation/docker-compose#docker-compose) by postiz to work. 5 | 6 | ## Role Variables 7 | 8 | - `postiz_version` 9 | - Default: `latest` 10 | - Description: The version of postiz to install. See [tags](https://github.com/gitroomhq/postiz-app/pkgs/container/postiz-app). 11 | - Type: str 12 | - Required: no 13 | - `postiz_webui_port` 14 | - Default: `5000` 15 | - Description: The port on which postiz's web UI will be accessible. 16 | - Type: int 17 | - Required: no 18 | - `postiz_install_dir` 19 | - Default: `/opt/docker/postiz` 20 | - Description: The directory where postiz will be installed. 21 | - Type: str 22 | - Required: no 23 | - `postiz_uploads_dir` 24 | - Default: `{{ postiz_uploads_dir }}/uploads` 25 | - Description: The directory where postiz will store uploads. 26 | - Type: str 27 | - Required: no 28 | - `postiz_env` 29 | - Default: See [postiz_env_default](./vars/main.yml) 30 | - Description: Docker container environment variables. See [docs](https://docs.postiz.com/installation/docker-compose#docker-compose) 31 | - Type: dict 32 | - Required: no 33 | - `postiz_db_env` 34 | - Default: See [postiz_db_env_default](./vars/main.yml) 35 | - Description: Postgres environment variables. See [docs](https://docs.postiz.com/installation/docker-compose#docker-compose) 36 | - Type: dict 37 | - Required: no 38 | - `postiz_docker_settings` 39 | - Default: See [postiz_docker_settings_default](./vars/main.yml) 40 | - Description: Docker container settings. 41 | - Type: dict 42 | - Required: no 43 | - `postiz_db_postgres_version` 44 | - Default: `17-alpine` 45 | - Description: The version of PostgreSQL to use for the database. 46 | - Type: str 47 | - Required: no 48 | - `postiz_redis_version` 49 | - Default: `7.2` 50 | - Description: The version of Redis to use. 51 | - Type: str 52 | - Required: no 53 | 54 | ## Dependencies 55 | 56 | - [community.docker](https://docs.ansible.com/ansible/latest/collections/community/docker/index.html) 57 | 58 | ## Example Playbook 59 | 60 | ```yaml 61 | - hosts: localhost 62 | 63 | vars: 64 | postiz_env: 65 | NOT_SECURED: "true" 66 | JWT_SECRET: "supersecret" 67 | MAIN_URL: "https://postiz.example.com" 68 | FRONTEND_URL: "https://postiz.example.com" 69 | NEXT_PUBLIC_BACKEND_URL: "https://postiz.example.com/api" 70 | EMAIL_PROVIDER: "nodemailer" 71 | EMAIL_FROM_NAME: "Postiz Notifications" 72 | EMAIL_FROM_ADDRESS: "postiz@example.com" 73 | EMAIL_HOST: "smtp.gmail.com" 74 | EMAIL_PORT: "465" 75 | EMAIL_SECURE: "true" 76 | EMAIL_USER: "postiz@gmail.com" 77 | EMAIL_PASS: "" # https://support.google.com/mail/answer/185833?hl=en 78 | OPENAI_API_KEY: "" # https://platform.openai.com/api-keys 79 | INSTAGRAM_APP_ID: "" # https://github.com/gitroomhq/postiz-docs/blob/main/pages/providers/instagram.mdx 80 | INSTAGRAM_APP_SECRET: "" 81 | 82 | roles: 83 | - artyorsh.selfhosted.postiz 84 | ``` -------------------------------------------------------------------------------- /roles/postiz/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://github.com/gitroomhq/postiz-app/pkgs/container/postiz-app 3 | postiz_version: "latest" 4 | postiz_db_postgres_version: "17-alpine" 5 | postiz_redis_version: "7.2" 6 | 7 | postiz_webui_port: 5000 8 | postiz_install_dir: "/opt/docker/postiz" 9 | 10 | postiz_uploads_dir: "{{ postiz_install_dir }}/uploads" 11 | 12 | # https://docs.postiz.com/installation/docker-compose#docker-compose 13 | postiz_env: {} 14 | postiz_db_env: {} 15 | 16 | postiz_docker_settings: {} 17 | -------------------------------------------------------------------------------- /roles/postiz/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Combine docker_settings with default docker settings" 3 | ansible.builtin.set_fact: 4 | postiz_service_id: "postiz" 5 | postiz_docker_settings: "{{ postiz_docker_settings_default | combine(postiz_docker_settings) }}" 6 | 7 | - name: "Prepare task variables" 8 | ansible.builtin.set_fact: 9 | postiz_service_id: "postiz" 10 | postiz_env: "{{ postiz_env_default | combine(postiz_env) }}" 11 | postiz_db_env: "{{ postiz_db_env_default | combine(postiz_db_env) }}" 12 | 13 | - name: "Make sure {{ postiz_docker_settings.network }} docker network exists" 14 | when: postiz_docker_settings.network != "host" 15 | community.general.docker_network: 16 | name: "{{ postiz_docker_settings.network }}" 17 | state: "present" 18 | 19 | - name: "Make sure config directories exist" 20 | ansible.builtin.file: 21 | path: "{{ item }}" 22 | owner: "{{ ansible_user }}" 23 | group: "{{ ansible_user }}" 24 | state: "directory" 25 | mode: "0774" 26 | loop: 27 | - "{{ postiz_uploads_dir }}" 28 | - "{{ postiz_install_dir }}/db" 29 | - "{{ postiz_install_dir }}/redis" 30 | 31 | - name: "Run redis container" 32 | community.general.docker_container: 33 | name: "{{ postiz_service_id }}-redis" 34 | image: "redis:{{ postiz_redis_version }}" 35 | networks: 36 | - name: "{{ postiz_docker_settings.network }}" 37 | state: "started" 38 | # healthcheck: 39 | # test: "redis-cli ping" 40 | # interval: 10s 41 | # timeout: 3s 42 | # retries: 3 43 | volumes: 44 | - "{{ postiz_install_dir }}/redis:/data" 45 | restart_policy: "always" 46 | 47 | - name: "Run db container" 48 | community.general.docker_container: 49 | name: "{{ postiz_service_id }}-db" 50 | image: "postgres:{{ postiz_db_postgres_version }}" 51 | networks: 52 | - name: "{{ postiz_docker_settings.network }}" 53 | state: "started" 54 | healthcheck: 55 | test: ["CMD", "pg_isready", "-U", "postiz"] 56 | interval: "10s" 57 | start_period: "30s" 58 | env: "{{ postiz_db_env }}" 59 | volumes: 60 | - "{{ postiz_install_dir }}/db:/var/lib/postgresql/data" 61 | restart_policy: "always" 62 | 63 | - name: "Run postiz container" 64 | community.general.docker_container: 65 | name: "{{ postiz_service_id }}" 66 | image: "ghcr.io/gitroomhq/postiz-app:{{ postiz_version }}" 67 | networks: 68 | - name: "{{ postiz_docker_settings.network }}" 69 | state: "started" 70 | env: "{{ postiz_env }}" 71 | ports: 72 | - "{{ postiz_webui_port }}:5000" 73 | volumes: 74 | - "{{ postiz_install_dir }}:/config/" 75 | - "{{ postiz_uploads_dir }}:/uploads/" 76 | restart_policy: "always" -------------------------------------------------------------------------------- /roles/postiz/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | postiz_docker_settings_default: 3 | network: "host" 4 | puid: "1000" 5 | pgid: "1000" 6 | tz: "Etc/UTC" 7 | 8 | postiz_env_default: 9 | TZ: "{{ postiz_docker_settings.tz }}" 10 | PUID: "{{ postiz_docker_settings.puid }}" 11 | PGID: "{{ postiz_docker_settings.pgid }}" 12 | JWT_SECRET: "supersecret" 13 | DATABASE_URL: "postgres://postiz:postiz@postiz-db/postiz?sslmode=disable" 14 | REDIS_URL: "redis://postiz-redis:6379" 15 | BACKEND_INTERNAL_URL: "http://localhost:3000" 16 | IS_GENERAL: "true" 17 | STORAGE_PROVIDER: "local" 18 | UPLOAD_DIRECTORY: "/uploads" 19 | NEXT_PUBLIC_UPLOAD_DIRECTORY: "/uploads" 20 | 21 | postiz_db_env_default: 22 | TZ: "{{ postiz_docker_settings.tz }}" 23 | PUID: "{{ postiz_docker_settings.puid }}" 24 | PGID: "{{ postiz_docker_settings.pgid }}" 25 | POSTGRES_USER: "postiz" 26 | POSTGRES_PASSWORD: "postiz" 27 | POSTGRES_DB: "postiz" 28 | -------------------------------------------------------------------------------- /roles/rss/README.md: -------------------------------------------------------------------------------- 1 | # selfhosted.rss 2 | 3 | Installs [miniflux](https://miniflux.app) - a minimalist and opinionated feed reader 4 | and [rss-bridge](https://rss-bridge.org) - a PHP project capable of generating RSS and Atom feeds for websites that don't have one. 5 | 6 | Also installs a PostgreSQL database that is [required](https://miniflux.app/docs/database.html) by Miniflux to work. 7 | 8 | ## Role Variables 9 | 10 | - `rss_miniflux_version` 11 | - Default: `latest` 12 | - Description: The version of Miniflux to install. See [docker tags](https://hub.docker.com/r/linuxserver/miniflux/tags). 13 | - Type: str 14 | - Required: no 15 | - `rss_miniflux_webui_port` 16 | - Default: `8082` 17 | - Description: The port on which Miniflux will be accessible. 18 | - Type: int 19 | - Required: no 20 | - `rss_miniflux_install_dir` 21 | - Default: `/opt/docker/miniflux` 22 | - Description: The directory where Miniflux will be installed. 23 | - Type: str 24 | - Required: no 25 | - `rss_miniflux_env` 26 | - Default: See [rss_miniflux_env_default](./vars/main.yml) 27 | - Description: Docker container environment variables for Miniflux. See [docs](https://miniflux.app/docs/configuration.html#options). 28 | - Type: dict 29 | - Required: no 30 | - `rss_miniflux_postgres_version` 31 | - Default: `15` 32 | - Description: The version of PostgreSQL to use with Miniflux. See [docker tags](https://hub.docker.com/_/postgres/tags). 33 | - Type: str 34 | - Required: no 35 | - `rss_miniflux_postgres_env` 36 | - Default: See [rss_miniflux_postgres_env_default](./vars/main.yml) 37 | - Description: Docker container environment variables for PostgreSQL. 38 | - Type: dict 39 | - Required: no 40 | - `rss_rssbridge_version` 41 | - Default: `latest` 42 | - Description: The version of RSS Bridge to install. See [docker tags](https://hub.docker.com/r/rssbridge/rss-bridge/tags). 43 | - Type: str 44 | - Required: no 45 | - `rss_rssbridge_webui_port` 46 | - Default: `8083` 47 | - Description: The port on which RSS Bridge will be accessible. 48 | - Type: int 49 | - Required: no 50 | - `rss_rssbridge_install_dir` 51 | - Default: `/opt/docker/rssbridge` 52 | - Description: The directory where RSS Bridge will be installed. 53 | - Type: str 54 | - Required: no 55 | - `rss_rssbridge_env` 56 | - Default: See [rss_rssbridge_env_default](./vars/main.yml) 57 | - Description: Docker container environment variables for RSS Bridge. 58 | - Type: dict 59 | - Required: no 60 | - `rss_rssbridge_bridges` 61 | - Default: `[]` 62 | - Description: List of bridges to be used by RSS Bridge. See [docs](https://github.com/RSS-Bridge/rss-bridge/tree/master/bridges). 63 | - Type: list 64 | - Required: no 65 | 66 | ## Dependencies 67 | 68 | - [community.docker](https://docs.ansible.com/ansible/latest/collections/community/docker/index.html) 69 | 70 | ## Example Playbook 71 | 72 | ```yaml 73 | - hosts: localhost 74 | 75 | roles: 76 | - artyorsh.selfhosted.rss 77 | ``` 78 | 79 | ## Polling frequency, bridges 80 | 81 | ```yaml 82 | - hosts: localhost 83 | 84 | vars: 85 | rss_miniflux_env: 86 | POLLING_FREQUENCY: "720" 87 | FORCE_REFRESH_INTERVAL: "1" 88 | rss_rssbridge_bridges: 89 | - Soundcloud 90 | - Reddit 91 | 92 | roles: 93 | - artyorsh.selfhosted.rss -------------------------------------------------------------------------------- /roles/rss/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://hub.docker.com/r/linuxserver/miniflux/tags 3 | rss_miniflux_version: "latest" 4 | rss_miniflux_webui_port: 8082 5 | rss_miniflux_install_dir: "/opt/docker/miniflux" 6 | # https://miniflux.app/docs/configuration.html#options 7 | rss_miniflux_env: {} 8 | 9 | # https://hub.docker.com/_/postgres/tags 10 | rss_miniflux_postgres_version: 15 11 | rss_miniflux_postgres_env: {} 12 | 13 | # https://hub.docker.com/r/rssbridge/rss-bridge/tags 14 | rss_rssbridge_version: "latest" 15 | rss_rssbridge_webui_port: 8083 16 | rss_rssbridge_install_dir: "/opt/docker/rssbridge" 17 | rss_rssbridge_env: {} 18 | rss_rssbridge_bridges: [] 19 | # - Soundcloud 20 | 21 | rss_docker_settings: {} 22 | -------------------------------------------------------------------------------- /roles/rss/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Combine docker_settings with default docker_settings_default" 3 | ansible.builtin.set_fact: 4 | rss_docker_settings: "{{ rss_docker_settings_default | combine(rss_docker_settings) }}" 5 | 6 | - name: "Make sure {{ rss_docker_settings.network }} docker network exists" 7 | when: rss_docker_settings.network != "host" 8 | community.general.docker_network: 9 | name: "{{ rss_docker_settings.network }}" 10 | state: "present" 11 | driver_options: 12 | # Limits docker interface MTU to fit Wireguard MTU https://stackoverflow.com/a/75868444 13 | # 14 | # TODO: Find a way to set this with _docker_settings variables, for all roles in the collection. 15 | # For bridge networks, /etc/docker/daemon.json is not an option: https://mlohr.com/docker-mtu 16 | com.docker.network.driver.mtu: 1420 17 | 18 | - name: "Install RSSBridge" 19 | ansible.builtin.include_tasks: "rssbridge.yml" 20 | 21 | - name: "Install Miniflux" 22 | ansible.builtin.include_tasks: "miniflux.yml" 23 | -------------------------------------------------------------------------------- /roles/rss/tasks/miniflux.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://miniflux.app/docs/docker.html#docker-compose 3 | 4 | - name: "Prepare task variables" 5 | ansible.builtin.set_fact: 6 | rss_miniflux_service_id: "miniflux" 7 | 8 | - name: "Make sure config directories exist" 9 | ansible.builtin.file: 10 | path: "{{ rss_miniflux_install_dir }}/db" 11 | owner: "{{ ansible_user }}" 12 | group: "{{ ansible_user }}" 13 | state: "directory" 14 | mode: "0774" 15 | 16 | - name: "Run app container" 17 | community.general.docker_container: 18 | name: "{{ rss_miniflux_service_id }}" 19 | image: "miniflux/miniflux:{{ rss_miniflux_version }}" 20 | networks: 21 | - name: "{{ rss_docker_settings.network }}" 22 | state: "started" 23 | env: "{{ rss_miniflux_env_default | combine(rss_miniflux_env) }}" 24 | ports: 25 | - "{{ rss_miniflux_webui_port }}:8080" 26 | restart_policy: "unless-stopped" 27 | 28 | - name: "Run db container" 29 | community.general.docker_container: 30 | name: "{{ rss_miniflux_service_id }}-db" 31 | image: "postgres:{{ rss_miniflux_postgres_version }}" 32 | networks: 33 | - name: "{{ rss_docker_settings.network }}" 34 | state: "started" 35 | env: "{{ rss_miniflux_postgres_env_default | combine(rss_miniflux_postgres_env) }}" 36 | volumes: 37 | - "{{ rss_miniflux_install_dir }}/db:/var/lib/postgresql/data" 38 | healthcheck: 39 | test: ["CMD", "pg_isready", "-U", "miniflux"] 40 | interval: "10s" 41 | start_period: "30s" 42 | restart_policy: "unless-stopped" 43 | -------------------------------------------------------------------------------- /roles/rss/tasks/rssbridge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Prepare task variables" 3 | ansible.builtin.set_fact: 4 | rssbridge_service_id: "rssbridge" 5 | 6 | - name: "Make sure config directories exist" 7 | ansible.builtin.file: 8 | path: "{{ rss_rssbridge_install_dir }}" 9 | owner: "{{ ansible_user }}" 10 | group: "{{ ansible_user }}" 11 | state: "directory" 12 | mode: "0774" 13 | 14 | - name: "Template bridge whitelist" 15 | ansible.builtin.template: 16 | src: "rssbridge-whitelist.txt.j2" 17 | dest: "{{ rss_rssbridge_install_dir }}/whitelist.txt" 18 | owner: "{{ ansible_user }}" 19 | group: "{{ ansible_user }}" 20 | mode: "0664" 21 | 22 | - name: "Run RSSBridge container" 23 | community.general.docker_container: 24 | name: "{{ rssbridge_service_id }}" 25 | image: "rssbridge/rss-bridge:{{ rss_rssbridge_version }}" 26 | networks: 27 | - name: "{{ rss_docker_settings.network }}" 28 | state: "started" 29 | env: "{{ rss_rssbridge_env_default | combine(rss_rssbridge_env) }}" 30 | ports: 31 | - "{{ rss_rssbridge_webui_port }}:80" 32 | volumes: 33 | - "{{ rss_rssbridge_install_dir }}:/config" 34 | restart_policy: "unless-stopped" 35 | -------------------------------------------------------------------------------- /roles/rss/templates/rssbridge-whitelist.txt.j2: -------------------------------------------------------------------------------- 1 | {% for bridge in rss_rssbridge_bridges %} 2 | 3 | {{ bridge }} 4 | 5 | {% endfor %} -------------------------------------------------------------------------------- /roles/rss/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | rss_docker_settings_default: 3 | network: "host" 4 | puid: "1000" 5 | pgid: "1000" 6 | tz: "Etc/UTC" 7 | 8 | rss_miniflux_db_name: "miniflux" 9 | rss_miniflux_db_user: 10 | name: "miniflux" 11 | password: "miniflux" 12 | 13 | rss_miniflux_env_default: 14 | TZ: "{{ rss_docker_settings.tz }}" 15 | PUID: "{{ rss_docker_settings.puid }}" 16 | PGID: "{{ rss_docker_settings.pgid }}" 17 | DATABASE_URL: "postgres://{{ rss_miniflux_db_user.name }}:{{ rss_miniflux_db_user.password }}@{{ rss_miniflux_service_id }}-db/{{ rss_miniflux_db_name }}?sslmode=disable" 18 | RUN_MIGRATIONS: "1" 19 | CREATE_ADMIN: "1" 20 | ADMIN_USERNAME: "{{ rss_miniflux_db_user.name }}" 21 | ADMIN_PASSWORD: "{{ rss_miniflux_db_user.password }}" 22 | 23 | rss_miniflux_postgres_env_default: 24 | TZ: "{{ rss_docker_settings.tz }}" 25 | PUID: "{{ rss_docker_settings.puid }}" 26 | PGID: "{{ rss_docker_settings.pgid }}" 27 | POSTGRES_USER: "{{ rss_miniflux_db_user.name }}" 28 | POSTGRES_PASSWORD: "{{ rss_miniflux_db_user.password }}" 29 | POSTGRES_DB: "{{ rss_miniflux_db_name }}" 30 | 31 | rss_rssbridge_env_default: 32 | TZ: "{{ rss_docker_settings.tz }}" 33 | PUID: "{{ rss_docker_settings.puid }}" 34 | PGID: "{{ rss_docker_settings.pgid }}" 35 | -------------------------------------------------------------------------------- /roles/vaultwarden/README.md: -------------------------------------------------------------------------------- 1 | # selfhosted.vaultwarden 2 | 3 | Installs [vaultwarden](https://github.com/dani-garcia/vaultwarden) - an unofficial Bitwarden compatible server written in Rust. 4 | 5 | ## Role Variables 6 | 7 | - `vaultwarden_version` 8 | - Default: `latest-alpine` 9 | - Description: The version of vaultwarden to install. See [docker tags](https://hub.docker.com/r/vaultwarden/server/tags). 10 | - Type: str 11 | - Required: no 12 | - `vaultwarden_port` 13 | - Default: `4430` 14 | - Description: The port on which vaultwarden will be accessible. 15 | - Type: int 16 | - Required: no 17 | - `vaultwarden_install_dir` 18 | - Default: `/opt/docker/vaultwarden` 19 | - Description: The directory where vaultwarden will be installed. 20 | - Type: str 21 | - Required: no 22 | - `vaultwarden_env` 23 | - Default: See [vaultwarden_env_default](./vars/main.yml) 24 | - Description: Docker container environment variables. See [docs](https://github.com/dani-garcia/vaultwarden/wiki/Configuration-overview). 25 | - Type: dict 26 | - Required: no 27 | - `vaultwarden_docker_settings` 28 | - Default: See [vaultwarden_docker_settings_default](./vars/main.yml) 29 | - Description: Docker container settings. 30 | - Type: dict 31 | - Required: no 32 | 33 | ## Dependencies 34 | 35 | - [community.docker](https://docs.ansible.com/ansible/latest/collections/community/docker/index.html) 36 | 37 | ## Example Playbook 38 | 39 | ```yaml 40 | - hosts: localhost 41 | 42 | roles: 43 | - artyorsh.selfhosted.vaultwarden 44 | ``` -------------------------------------------------------------------------------- /roles/vaultwarden/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://hub.docker.com/r/vaultwarden/server/tags 3 | vaultwarden_version: "latest-alpine" 4 | vaultwarden_port: 4430 5 | vaultwarden_install_dir: "/opt/docker/vaultwarden" 6 | 7 | # https://github.com/dani-garcia/vaultwarden/wiki/Configuration-overview 8 | vaultwarden_env: {} 9 | vaultwarden_docker_settings: {} 10 | -------------------------------------------------------------------------------- /roles/vaultwarden/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Prepare task variables" 3 | ansible.builtin.set_fact: 4 | vaultwarden_service_id: "vaultwarden" 5 | vaultwarden_docker_settings: "{{ vaultwarden_docker_settings_default | combine(vaultwarden_docker_settings) }}" 6 | 7 | - name: "Make sure config directory exists" 8 | ansible.builtin.file: 9 | path: "{{ vaultwarden_install_dir }}" 10 | owner: "{{ ansible_user }}" 11 | group: "{{ ansible_user }}" 12 | state: "directory" 13 | mode: "0774" 14 | 15 | - name: "Make sure {{ vaultwarden_docker_settings.network }} docker network exists" 16 | when: vaultwarden_docker_settings.network != "host" 17 | community.general.docker_network: 18 | name: "{{ vaultwarden_docker_settings.network }}" 19 | state: "present" 20 | 21 | - name: "Run vaultwarden container" 22 | community.general.docker_container: 23 | name: "{{ vaultwarden_service_id }}" 24 | image: "vaultwarden/server:{{ vaultwarden_version }}" 25 | networks: 26 | - name: "{{ vaultwarden_docker_settings.network }}" 27 | state: "started" 28 | env: "{{ vaultwarden_env_default | combine(vaultwarden_env) }}" 29 | ports: 30 | - "{{ vaultwarden_port }}:80" 31 | volumes: 32 | - "{{ vaultwarden_install_dir }}:/data" 33 | restart_policy: "unless-stopped" 34 | -------------------------------------------------------------------------------- /roles/vaultwarden/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | vaultwarden_docker_settings_default: 3 | network: "host" 4 | puid: "1000" 5 | pgid: "1000" 6 | tz: "Etc/UTC" 7 | 8 | vaultwarden_env_default: 9 | TZ: "{{ vaultwarden_docker_settings.tz }}" 10 | PUID: "{{ vaultwarden_docker_settings.puid }}" 11 | PGID: "{{ vaultwarden_docker_settings.pgid }}" 12 | WEBSOCKET_ENABLED: "true" 13 | SIGNUPS_ALLOWED: "false" 14 | -------------------------------------------------------------------------------- /roles/wallos/README.md: -------------------------------------------------------------------------------- 1 | # selfhosted.wallos 2 | 3 | Installs [wallos](https://github.com/ellite/Wallos/pkgs/container/wallos) - an Open-Source Personal Subscription Tracker. 4 | 5 | ## Role Variables 6 | 7 | - `wallos_version` 8 | - Default: `latest` 9 | - Description: The version of wallos to install. See [tags](https://github.com/ellite/Wallos/pkgs/container/wallos) 10 | - Type: str 11 | - Required: no 12 | - `wallos_install_dir` 13 | - Default: `/opt/docker/wallos` 14 | - Description: The directory where wallos will be installed. 15 | - Type: str 16 | - Required: no 17 | - `wallos_webui_port` 18 | - Default: `8282` 19 | - Description: The port on which the wallos web UI will be accessible. 20 | - Type: int 21 | - Required: no 22 | - `wallos_env` 23 | - Default: See [wallos_env_default](./vars/main.yml) 24 | - Description: Docker container environment variables. 25 | - Type: dict 26 | - Required: no 27 | - `wallos_docker_settings` 28 | - Default: See [wallos_docker_settings_default](./vars/main.yml) 29 | - Description: Docker container settings. 30 | - Type: dict 31 | - Required: no 32 | 33 | ## Dependencies 34 | 35 | - [community.docker](https://docs.ansible.com/ansible/latest/collections/community/docker/index.html) 36 | 37 | ## Example Playbook 38 | 39 | ```yaml 40 | - hosts: localhost 41 | 42 | roles: 43 | - artyorsh.selfhosted.wallos 44 | ``` -------------------------------------------------------------------------------- /roles/wallos/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://github.com/ellite/Wallos/pkgs/container/wallos 3 | wallos_version: "latest" 4 | wallos_install_dir: "/opt/docker/wallos" 5 | 6 | wallos_webui_port: 8282 7 | 8 | wallos_env: {} 9 | 10 | wallos_docker_settings: {} 11 | -------------------------------------------------------------------------------- /roles/wallos/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Install and configure Wallos" 3 | ansible.builtin.include_tasks: "wallos.yml" -------------------------------------------------------------------------------- /roles/wallos/tasks/wallos.yml: -------------------------------------------------------------------------------- 1 | # https://github.com/ellite/Wallos?tab=readme-ov-file#docker-1 2 | --- 3 | 4 | - name: "Prepare task variables" 5 | ansible.builtin.set_fact: 6 | wallos_service_id: "wallos" 7 | wallos_docker_settings: "{{ wallos_docker_settings_default | combine(wallos_docker_settings) }}" 8 | 9 | - name: "Make sure config directories exist" 10 | ansible.builtin.file: 11 | path: "{{ item }}" 12 | owner: "{{ ansible_user }}" 13 | group: "{{ ansible_user }}" 14 | state: "directory" 15 | mode: "0750" 16 | loop: 17 | - "{{ wallos_install_dir }}/db" 18 | - "{{ wallos_install_dir }}/logos" 19 | 20 | - name: "Make sure {{ wallos_docker_settings.network }} docker network exists" 21 | when: wallos_docker_settings.network != "host" 22 | community.general.docker_network: 23 | name: "{{ wallos_docker_settings.network }}" 24 | state: "present" 25 | 26 | - name: "Run wallos container" 27 | community.general.docker_container: 28 | name: "{{ wallos_service_id }}" 29 | image: "bellamy/wallos:{{ wallos_version }}" 30 | networks: 31 | - name: "{{ wallos_docker_settings.network }}" 32 | state: "started" 33 | env: "{{ wallos_env_default | combine(wallos_env) }}" 34 | ports: 35 | - "{{ wallos_webui_port }}:80" 36 | volumes: 37 | - "{{ wallos_install_dir }}/db:/var/www/html/db" 38 | - "{{ wallos_install_dir }}/logos:/var/www/html/images/uploads/logos" 39 | restart_policy: "unless-stopped" 40 | -------------------------------------------------------------------------------- /roles/wallos/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | wallos_docker_settings_default: 3 | network: "host" 4 | puid: "1000" 5 | pgid: "1000" 6 | tz: "Etc/UTC" 7 | 8 | wallos_env_default: 9 | TZ: "{{ wallos_docker_settings.tz }}" 10 | PUID: "{{ wallos_docker_settings.puid }}" 11 | PGID: "{{ wallos_docker_settings.pgid }}" 12 | -------------------------------------------------------------------------------- /roles/watchtower/README.md: -------------------------------------------------------------------------------- 1 | # selfhosted.watchtower 2 | 3 | Installs [Watchtower](https://containrrr.dev/watchtower/) - a container-based solution for automating Docker container base image updates. 4 | 5 | ## Role Variables 6 | 7 | - `watchtower_version` 8 | - Default: `latest` 9 | - Description: The version of Watchtower to install. See [tags](https://hub.docker.com/r/containrrr/watchtower/tags). 10 | - Type: str 11 | - Required: no 12 | - `watchtower_install_dir` 13 | - Default: `/opt/docker/watchtower` 14 | - Description: The directory where Watchtower will be installed. 15 | - Type: str 16 | - Required: no 17 | - `watchtower_env` 18 | - Default: See [watchtower_env_default](./vars/main.yml) 19 | - Description: Docker container environment variables. See [docs](https://containrrr.dev/watchtower/arguments/). 20 | - Type: dict 21 | - Required: no 22 | - `watchtower_docker_settings` 23 | - Default: See [watchtower_docker_settings_default](./vars/main.yml) 24 | - Description: Docker container settings. 25 | - Type: dict 26 | - Required: no 27 | - `watchtower_config_json_as_yaml` 28 | - Default: `{}` 29 | - Description: The contents of `config.json` in yaml format. See [docs](https://containrrr.dev/watchtower/private-registries/#create_the_configuration_file_manually). 30 | - Type: dict 31 | - Required: no 32 | 33 | ## Dependencies 34 | 35 | - [community.docker](https://docs.ansible.com/ansible/latest/collections/community/docker/index.html) 36 | 37 | ## Example Playbook 38 | 39 | ```yaml 40 | - hosts: localhost 41 | 42 | roles: 43 | - artyorsh.selfhosted.watchtower 44 | ``` 45 | 46 | ## Custom schedule, disable containers, notifications 47 | 48 | ```yaml 49 | - hosts: localhost 50 | 51 | vars: 52 | watchtower_env: 53 | WATCHTOWER_SCHEDULE: "0 0 6 * * *" 54 | WATCHTOWER_DISABLE_CONTAINERS: "forgejo-db" 55 | # https://containrrr.dev/watchtower/notifications/#shoutrrr_notifications 56 | WATCHTOWER_NOTIFICATION_URL: "slack://token-a/token-b/token-c" 57 | 58 | roles: 59 | - artyorsh.selfhosted.watchtower 60 | ``` 61 | 62 | ## Private registries 63 | 64 | ```yaml 65 | - hosts: localhost 66 | 67 | vars: 68 | watchtower_config_json_as_yaml: 69 | auths: 70 | ghcr.io: 71 | auth: "supersecret" 72 | 73 | roles: 74 | - artyorsh.selfhosted.watchtower 75 | ``` 76 | -------------------------------------------------------------------------------- /roles/watchtower/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://hub.docker.com/r/containrrr/watchtower/tags 3 | watchtower_version: "latest" 4 | watchtower_install_dir: "/opt/docker/watchtower" 5 | 6 | # https://containrrr.dev/watchtower/arguments/ 7 | watchtover_env: {} 8 | 9 | watchtower_docker_settings: {} 10 | 11 | # contents of config.json (see https://containrrr.dev/watchtower/private-registries/#create_the_configuration_file_manually), 12 | # in yaml format 13 | # example: 14 | # watchtower_config_json_as_yaml: 15 | # auths: { ghcr.io: auth: "base64" } 16 | watchtower_config_json_as_yaml: {} 17 | -------------------------------------------------------------------------------- /roles/watchtower/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Install and configure Watchtower" 3 | ansible.builtin.include_tasks: "watchtower.yml" 4 | -------------------------------------------------------------------------------- /roles/watchtower/tasks/watchtower.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Prepare task variables" 3 | ansible.builtin.set_fact: 4 | watchtower_service_id: "watchtower" 5 | watchtower_http_port: 80 6 | watchtower_https_port: 443 7 | watchtower_docker_settings: "{{ watchtower_docker_settings_default | combine(watchtower_docker_settings) }}" 8 | 9 | - name: "Make sure {{ watchtower_docker_settings.network }} docker network exists" 10 | when: watchtower_docker_settings.network != "host" 11 | community.general.docker_network: 12 | name: "{{ watchtower_docker_settings.network }}" 13 | state: "present" 14 | 15 | - name: "Make sure config directory exists" 16 | ansible.builtin.file: 17 | path: "{{ watchtower_install_dir }}" 18 | owner: "{{ ansible_user }}" 19 | group: "{{ ansible_user }}" 20 | state: "directory" 21 | mode: "0774" 22 | 23 | - name: "Template config.json" 24 | ansible.builtin.copy: 25 | content: "{{ watchtower_config_json_as_yaml | to_nice_json }}" 26 | dest: "{{ watchtower_install_dir }}/config.json" 27 | owner: "{{ ansible_user }}" 28 | group: "{{ ansible_user }}" 29 | mode: "0644" 30 | 31 | - name: "Run watchtower container" 32 | community.general.docker_container: 33 | name: "{{ watchtower_service_id }}" 34 | image: "containrrr/watchtower:{{ watchtower_version }}" 35 | networks: 36 | - name: "{{ watchtower_docker_settings.network }}" 37 | state: "started" 38 | env: "{{ watchtover_env_default | combine(watchtover_env) }}" 39 | ports: 40 | - "{{ watchtower_https_port }}:3443" 41 | - "{{ watchtower_http_port }}:3080" 42 | volumes: 43 | - "/var/run/docker.sock:/var/run/docker.sock" 44 | - "{{ watchtower_install_dir }}/config.json:/config.json" 45 | restart_policy: "unless-stopped" 46 | -------------------------------------------------------------------------------- /roles/watchtower/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | watchtower_docker_settings_default: 3 | network: "host" 4 | puid: "1000" 5 | pgid: "1000" 6 | tz: "Etc/UTC" 7 | 8 | watchtover_env_default: 9 | TZ: "{{ watchtower_docker_settings.tz }}" 10 | WATCHTOWER_SCHEDULE: "0 0 2 * * *" # 2AM every day 11 | WATCHTOWER_REMOVE_VOLUMES: "true" 12 | WATCHTOWER_NO_STARTUP_MESSAGE: "true" 13 | -------------------------------------------------------------------------------- /roles/wgeasy/README.md: -------------------------------------------------------------------------------- 1 | # selfhosted.wgeasy 2 | 3 | Installs [wg-easy](https://github.com/wg-easy/wg-easy/pkgs/container/wg-easy) - the easiest way to run WireGuard VPN + Web-based Admin UI. 4 | 5 | > [!NOTE] 6 | > The role is tested to run on a bridge network. 7 | > Running on host network (which is the default) may cause issues. 8 | 9 | ## Role Variables 10 | 11 | - `wgeasy_version` 12 | - Default: `latest` 13 | - Description: The version of wg-easy to install. See [tags](https://github.com/wg-easy/wg-easy/pkgs/container/wg-easy). 14 | - Type: str 15 | - Required: no 16 | - `wgeasy_webui_port` 17 | - Default: `51821` 18 | - Description: The port on which wg-easy's web UI will be accessible. 19 | - Type: int 20 | - Required: no 21 | - `wgeasy_install_dir` 22 | - Default: `/opt/docker/wgeasy` 23 | - Description: The directory where wg-easy will be installed. 24 | - Type: str 25 | - Required: no 26 | - `wgeasy_env` 27 | - Default: See [wgeasy_env_default](./vars/main.yml) 28 | - Description: Docker container environment variables. See [docs](https://github.com/wg-easy/wg-easy?tab=readme-ov-file#options). 29 | - Type: dict 30 | - Required: no 31 | - `wgeasy_docker_settings` 32 | - Default: See [wgeasy_docker_settings_default](./vars/main.yml) 33 | - Description: Docker container settings. 34 | - Type: dict 35 | - Required: no 36 | 37 | ## Dependencies 38 | 39 | - [community.docker](https://docs.ansible.com/ansible/latest/collections/community/docker/index.html) 40 | 41 | ## Example Playbook 42 | 43 | ```yaml 44 | - hosts: localhost 45 | 46 | roles: 47 | - artyorsh.selfhosted.wgeasy 48 | ``` 49 | 50 | ## Ping peers from the host 51 | 52 | ```yaml 53 | - hosts: localhost 54 | 55 | vars: 56 | wgeasy_docker_settings: 57 | network: "my-bridge-network" 58 | wgeasy_env: 59 | # https://github.com/wg-easy/wg-easy/issues/427#issuecomment-1644527712 60 | WG_POST_UP: "iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE; iptables -t nat -A POSTROUTING -o wg+ -j MASQUERADE" 61 | 62 | roles: 63 | - artyorsh.selfhosted.wgeasy 64 | ``` -------------------------------------------------------------------------------- /roles/wgeasy/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://github.com/wg-easy/wg-easy/pkgs/container/wg-easy 3 | wgeasy_version: "latest" 4 | wgeasy_install_dir: "/opt/docker/wg-easy" 5 | 6 | wgeasy_webui_port: 51821 7 | 8 | # https://github.com/wg-easy/wg-easy?tab=readme-ov-file#options 9 | wgeasy_env: {} 10 | 11 | wgeasy_docker_settings: {} 12 | -------------------------------------------------------------------------------- /roles/wgeasy/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Combine docker_settings with docker_settings_default" 3 | ansible.builtin.set_fact: 4 | wgeasy_docker_settings: "{{ wgeasy_docker_settings_default | combine(wgeasy_docker_settings) }}" 5 | 6 | - name: "Prepare task variables" 7 | ansible.builtin.set_fact: 8 | wgeasy_service_id: "wg-easy" 9 | wgeasy_env: "{{ wgeasy_env_default | combine(wgeasy_env) }}" 10 | 11 | - name: "Create docker directories" 12 | ansible.builtin.file: 13 | path: "{{ wgeasy_install_dir }}" 14 | state: "directory" 15 | owner: "{{ ansible_user }}" 16 | group: "{{ ansible_user }}" 17 | mode: "0644" 18 | 19 | - name: "Make sure {{ wgeasy_docker_settings.network }} docker network exists" 20 | when: wgeasy_docker_settings.network != "host" 21 | community.general.docker_network: 22 | name: "{{ wgeasy_docker_settings.network }}" 23 | state: "present" 24 | 25 | - name: "Make sure the Wireguard container is created and running" 26 | community.general.docker_container: 27 | name: "{{ wgeasy_service_id }}" 28 | image: "ghcr.io/wg-easy/wg-easy:{{ wgeasy_version }}" 29 | networks: 30 | - name: "{{ wgeasy_docker_settings.network }}" 31 | sysctls: 32 | "net.ipv4.conf.all.src_valid_mark": "1" 33 | "net.ipv4.ip_forward": "1" 34 | capabilities: 35 | - "net_admin" 36 | - "sys_module" 37 | state: "started" 38 | env: "{{ wgeasy_env_default | combine(wgeasy_env) }}" 39 | ports: 40 | - "{{ wgeasy_env.WG_PORT }}:{{ wgeasy_env.WG_PORT }}/udp" 41 | - "{{ wgeasy_webui_port }}:51821/tcp" 42 | volumes: 43 | - "{{ wgeasy_install_dir }}:/etc/wireguard" 44 | restart_policy: "unless-stopped" 45 | -------------------------------------------------------------------------------- /roles/wgeasy/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | wgeasy_docker_settings_default: 3 | network: "host" 4 | puid: "1000" 5 | pgid: "1000" 6 | tz: "Etc/UTC" 7 | 8 | wgeasy_env_default: 9 | TZ: "{{ wgeasy_docker_settings.tz }}" 10 | PUID: "{{ wgeasy_docker_settings.puid }}" 11 | PGID: "{{ wgeasy_docker_settings.pgid }}" 12 | WG_HOST: "{{ ansible_host }}" 13 | WG_DEFAULT_ADDRESS: "10.8.0.x" 14 | WG_PORT: "51820" 15 | WG_PERSISTENT_KEEPALIVE: "25" 16 | --------------------------------------------------------------------------------