├── inventory ├── ansible.cfg ├── .yamllint ├── tasks ├── watchtower.yml ├── guacamole.yml ├── vaultwarden.yml ├── dashdot.yml ├── n8n.yml ├── requestrr.yml ├── uptime_kuma.yml ├── homarr.yml ├── heimdall.yml ├── prowlarr.yml ├── jellyseerr.yml ├── add_ownership.yml ├── radarr.yml ├── sonarr.yml ├── portainer.yml ├── nextcloud.yml ├── unmanic.yml ├── duplicati.yml ├── jellyfin.yml ├── syncthing.yml ├── code_server.yml ├── wireguard.yml ├── filebrowser.yml ├── qbittorrent.yml ├── docker.yml ├── traefik.yml ├── monitoring.yml ├── essential.yml └── authelia.yml ├── LICENSE.md ├── .gitignore ├── group_vars └── all │ └── vars.yml ├── .terraform.lock.hcl ├── main.yml ├── bootstrap.py ├── CONTRIBUTING.md ├── main.tf ├── README.md └── CODE_OF_CONDUCT.md /inventory: -------------------------------------------------------------------------------- 1 | [homeserver] 2 | 3 | 4 | [homeserver:vars] 5 | ansible_user = 6 | ansible_ssh_private_key_file = -------------------------------------------------------------------------------- /ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | nocows = 1 3 | inventory = inventory 4 | host_key_checking = False 5 | forks = 32 6 | timeout = 60 7 | 8 | [ssh_connection] 9 | pipelining = True -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | extends: default 3 | 4 | rules: 5 | line-length: 6 | max: 150 7 | allow-non-breakable-words: true 8 | allow-non-breakable-inline-mappings: true 9 | level: warning 10 | -------------------------------------------------------------------------------- /tasks/watchtower.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create watchtower container 3 | community.docker.docker_container: 4 | name: watchtower 5 | image: containrrr/watchtower 6 | pull: true 7 | state: started 8 | restart_policy: unless-stopped 9 | volumes: 10 | - "/var/run/docker.sock:/var/run/docker.sock" 11 | env: 12 | WATCHTOWER_CLEANUP: "true" 13 | networks: 14 | - name: homelab 15 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Private ssh keys 2 | *.pem 3 | linode_ansible 4 | linode_ansible.pub 5 | .vscode 6 | 7 | # Local .terraform directories 8 | **/.terraform/* 9 | 10 | # .tfstate files 11 | *.tfstate 12 | *.tfstate.* 13 | 14 | # Crash log files 15 | crash.log 16 | crash.*.log 17 | 18 | # Exclude all .tfvars files, which are likely to contain sensitive data, such as 19 | # password, private keys, and other secrets. These should not be part of version 20 | # control as they are data points which are potentially sensitive and subject 21 | # to change depending on the environment. 22 | *.tfvars 23 | *.tfvars.json 24 | 25 | # Ignore override files as they are usually used to override resources locally and so 26 | # are not checked in 27 | override.tf 28 | override.tf.json 29 | *_override.tf 30 | *_override.tf.json 31 | 32 | # Include override files you do wish to add to version control using negated pattern 33 | # !example_override.tf 34 | 35 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 36 | # example: *tfplan* 37 | 38 | # Ignore CLI configuration files 39 | .terraformrc 40 | terraform.rc -------------------------------------------------------------------------------- /tasks/guacamole.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create guacamole container 3 | community.docker.docker_container: 4 | name: guacamole 5 | image: maxwaldorf/guacamole 6 | pull: true 7 | state: started 8 | restart_policy: unless-stopped 9 | volumes: 10 | - "{{ docker_dir }}/guacamole/config:/config" 11 | networks: 12 | - name: homelab 13 | labels: 14 | traefik.enable: "true" 15 | traefik.http.routers.guac.entrypoints: "http" 16 | traefik.http.routers.guac.rule: "Host(`guac.{{ domain }}`)" 17 | traefik.http.middlewares.guac-https-redirect.redirectscheme.scheme: "https" 18 | traefik.http.routers.guac.middlewares: "guac-https-redirect" 19 | traefik.http.routers.guac-secure.entrypoints: "https" 20 | traefik.http.routers.guac-secure.rule: "Host(`guac.{{ domain }}`)" 21 | traefik.http.routers.guac-secure.tls: "true" 22 | traefik.http.routers.guac-secure.service: "guac" 23 | traefik.http.routers.guac-secure.middlewares: "authelia@docker" 24 | traefik.http.services.guac.loadbalancer.server.port: "8080" 25 | traefik.docker.network: "homelab" 26 | -------------------------------------------------------------------------------- /tasks/vaultwarden.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create vaultwarden container 3 | community.docker.docker_container: 4 | name: vaultwarden 5 | image: vaultwarden/server:latest 6 | pull: true 7 | state: started 8 | restart_policy: unless-stopped 9 | volumes: 10 | - "{{ docker_dir }}/vaultwarden/data:/data" 11 | networks: 12 | - name: homelab 13 | labels: 14 | traefik.enable: "true" 15 | traefik.http.routers.vault.entrypoints: "http" 16 | traefik.http.routers.vault.rule: "Host(`vault.{{ domain }}`)" 17 | traefik.http.middlewares.vault-https-redirect.redirectscheme.scheme: "https" 18 | traefik.http.routers.vault.middlewares: "vault-https-redirect" 19 | traefik.http.routers.vault-secure.entrypoints: "https" 20 | traefik.http.routers.vault-secure.rule: "Host(`vault.{{ domain }}`)" 21 | traefik.http.routers.vault-secure.tls: "true" 22 | traefik.http.routers.vault-secure.service: "vault" 23 | traefik.http.routers.vault-secure.middlewares: "authelia@docker" 24 | traefik.http.services.vault.loadbalancer.server.port: "80" 25 | traefik.docker.network: "homelab" 26 | -------------------------------------------------------------------------------- /tasks/dashdot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create dashdot container 3 | community.docker.docker_container: 4 | name: dashdot 5 | image: mauricenino/dashdot:latest 6 | restart_policy: unless-stopped 7 | state: started 8 | pull: true 9 | privileged: true 10 | networks: 11 | - name: homelab 12 | volumes: 13 | - "/:/mnt/host:ro" 14 | env: 15 | DASHDOT_ACCEPT_OOKLA_EULA: "true" 16 | labels: 17 | traefik.enable: "true" 18 | traefik.http.routers.dash.entrypoints: "http" 19 | traefik.http.routers.dash.rule: "Host(`dash.{{ domain }}`)" 20 | traefik.http.middlewares.dash-https-redirect.redirectscheme.scheme: "https" 21 | traefik.http.routers.dash.middlewares: "dash-https-redirect" 22 | traefik.http.routers.dash-secure.entrypoints: "https" 23 | traefik.http.routers.dash-secure.rule: "Host(`dash.{{ domain }}`)" 24 | traefik.http.routers.dash-secure.tls: "true" 25 | traefik.http.routers.dash-secure.service: "dash" 26 | traefik.http.routers.dash-secure.middlewares: "authelia@docker" 27 | traefik.http.services.dash.loadbalancer.server.port: "3001" 28 | traefik.docker.network: "homelab" 29 | -------------------------------------------------------------------------------- /tasks/n8n.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create n8n container 3 | community.docker.docker_container: 4 | name: n8n 5 | image: docker.n8n.io/n8nio/n8n 6 | state: started 7 | restart_policy: unless-stopped 8 | pull: true 9 | volumes: 10 | - "{{ docker_dir }}/n8n:/home/node/.n8n" 11 | networks: 12 | - name: homelab 13 | env: 14 | GENERIC_TIMEZONE: "{{ timezone }}" 15 | TZ: "{{ timezone }}" 16 | labels: 17 | traefik.enable: "true" 18 | traefik.http.routers.n8n.entrypoints: "http" 19 | traefik.http.routers.n8n.rule: "Host(`n8n.{{ domain }}`)" 20 | traefik.http.middlewares.n8n-https-redirect.redirectscheme.scheme: "https" 21 | traefik.http.routers.n8n.middlewares: "n8n-https-redirect" 22 | traefik.http.routers.n8n-secure.entrypoints: "https" 23 | traefik.http.routers.n8n-secure.rule: "Host(`n8n.{{ domain }}`)" 24 | traefik.http.routers.n8n-secure.tls: "true" 25 | traefik.http.routers.n8n-secure.service: "n8n" 26 | traefik.http.routers.n8n-secure.middlewares: "authelia@docker" 27 | traefik.http.services.n8n.loadbalancer.server.port: "5678" 28 | traefik.docker.network: "homelab" 29 | -------------------------------------------------------------------------------- /tasks/requestrr.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create requestrr container 3 | community.docker.docker_container: 4 | name: requestrr 5 | image: darkalfx/requestrr 6 | pull: true 7 | state: started 8 | restart_policy: unless-stopped 9 | networks: 10 | - name: homelab 11 | volumes: 12 | - "{{ docker_dir }}/requestrr:/root/config" 13 | labels: 14 | traefik.enable: "true" 15 | traefik.http.routers.requestrr.entrypoints: "http" 16 | traefik.http.routers.requestrr.rule: "Host(`requestrr.{{ domain }}`)" 17 | traefik.http.middlewares.requestrr-https-redirect.redirectscheme.scheme: "https" 18 | traefik.http.routers.requestrr.middlewares: "requestrr-https-redirect" 19 | traefik.http.routers.requestrr-secure.entrypoints: "https" 20 | traefik.http.routers.requestrr-secure.rule: "Host(`requestrr.{{ domain }}`)" 21 | traefik.http.routers.requestrr-secure.tls: "true" 22 | traefik.http.routers.requestrr-secure.service: "requestrr" 23 | traefik.http.routers.requestrr-secure.middlewares: "authelia@docker" 24 | traefik.http.services.requestrr.loadbalancer.server.port: "4545" 25 | traefik.docker.network: "homelab" 26 | -------------------------------------------------------------------------------- /tasks/uptime_kuma.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create uptime kuma container 3 | community.docker.docker_container: 4 | name: uptime_kuma 5 | image: louislam/uptime-kuma:1 6 | pull: true 7 | state: started 8 | restart_policy: unless-stopped 9 | volumes: 10 | - "{{ docker_dir }}/uptime_kuma/data:/app/data" 11 | security_opts: 12 | - no-new-privileges:true 13 | networks: 14 | - name: homelab 15 | labels: 16 | traefik.enable: "true" 17 | traefik.http.routers.uptime.entrypoints: "http" 18 | traefik.http.routers.uptime.rule: "Host(`uptime.{{ domain }}`)" 19 | traefik.http.middlewares.uptime-https-redirect.redirectscheme.scheme: "https" 20 | traefik.http.routers.uptime.middlewares: "uptime-https-redirect" 21 | traefik.http.routers.uptime-secure.entrypoints: "https" 22 | traefik.http.routers.uptime-secure.rule: "Host(`uptime.{{ domain }}`)" 23 | traefik.http.routers.uptime-secure.tls: "true" 24 | traefik.http.routers.uptime-secure.service: "uptime" 25 | traefik.http.routers.uptime-secure.middlewares: "authelia@docker" 26 | traefik.http.services.uptime.loadbalancer.server.port: "3001" 27 | traefik.docker.network: "homelab" 28 | -------------------------------------------------------------------------------- /tasks/homarr.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create homarr container 3 | community.docker.docker_container: 4 | name: homarr 5 | image: ghcr.io/ajnart/homarr:latest 6 | state: started 7 | restart_policy: unless-stopped 8 | pull: true 9 | networks: 10 | - name: homelab 11 | volumes: 12 | - "{{ docker_dir }}/homarr/configs:/app/data/configs" 13 | - "{{ docker_dir }}/homarr/icons:/app/public/icons" 14 | - "/var/run/docker.sock:/var/run/docker.sock:ro" 15 | labels: 16 | traefik.enable: "true" 17 | traefik.http.routers.homarr.entrypoints: "http" 18 | traefik.http.routers.homarr.rule: "Host(`homarr.{{ domain }}`)" 19 | traefik.http.middlewares.homarr-https-redirect.redirectscheme.scheme: "https" 20 | traefik.http.routers.homarr.middlewares: "homarr-https-redirect" 21 | traefik.http.routers.homarr-secure.entrypoints: "https" 22 | traefik.http.routers.homarr-secure.rule: "Host(`homarr.{{ domain }}`)" 23 | traefik.http.routers.homarr-secure.tls: "true" 24 | traefik.http.routers.homarr-secure.service: "homarr" 25 | traefik.http.routers.homarr-secure.middlewares: "authelia@docker" 26 | traefik.http.services.homarr.loadbalancer.server.port: "7575" 27 | traefik.docker.network: "homelab" 28 | -------------------------------------------------------------------------------- /tasks/heimdall.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create heimdall container 3 | community.docker.docker_container: 4 | name: heimdall 5 | image: lscr.io/linuxserver/heimdall 6 | pull: true 7 | state: started 8 | restart_policy: unless-stopped 9 | env: 10 | PUID: "{{ puid }}" 11 | PGID: "{{ pgid }}" 12 | TZ: "{{ timezone }}" 13 | volumes: 14 | - "{{ docker_dir }}/heimdall/config:/config" 15 | networks: 16 | - name: homelab 17 | labels: 18 | traefik.enable: "true" 19 | traefik.http.routers.heimdall.entrypoints: "http" 20 | traefik.http.routers.heimdall.rule: "Host(`heimdall.{{ domain }}`)" 21 | traefik.http.middlewares.heimdall-https-redirect.redirectscheme.scheme: "https" 22 | traefik.http.routers.heimdall.middlewares: "heimdall-https-redirect" 23 | traefik.http.routers.heimdall-secure.entrypoints: "https" 24 | traefik.http.routers.heimdall-secure.rule: "Host(`heimdall.{{ domain }}`)" 25 | traefik.http.routers.heimdall-secure.tls: "true" 26 | traefik.http.routers.heimdall-secure.service: "heimdall" 27 | traefik.http.routers.heimdall-secure.middlewares: "authelia@docker" 28 | traefik.http.services.heimdall.loadbalancer.server.port: "80" 29 | traefik.docker.network: "homelab" 30 | -------------------------------------------------------------------------------- /tasks/prowlarr.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create prowlarr container 3 | community.docker.docker_container: 4 | name: prowlarr 5 | image: lscr.io/linuxserver/prowlarr 6 | pull: true 7 | state: started 8 | restart_policy: unless-stopped 9 | env: 10 | PUID: "{{ puid }}" 11 | PGID: "{{ pgid }}" 12 | TZ: "{{ timezone }}" 13 | volumes: 14 | - "{{ docker_dir }}/prowlarr/config:/config" 15 | networks: 16 | - name: homelab 17 | labels: 18 | traefik.enable: "true" 19 | traefik.http.routers.prowlarr.entrypoints: "http" 20 | traefik.http.routers.prowlarr.rule: "Host(`prowlarr.{{ domain }}`)" 21 | traefik.http.middlewares.prowlarr-https-redirect.redirectscheme.scheme: "https" 22 | traefik.http.routers.prowlarr.middlewares: "prowlarr-https-redirect" 23 | traefik.http.routers.prowlarr-secure.entrypoints: "https" 24 | traefik.http.routers.prowlarr-secure.rule: "Host(`prowlarr.{{ domain }}`)" 25 | traefik.http.routers.prowlarr-secure.tls: "true" 26 | traefik.http.routers.prowlarr-secure.service: "prowlarr" 27 | traefik.http.routers.prowlarr-secure.middlewares: "authelia@docker" 28 | traefik.http.services.prowlarr.loadbalancer.server.port: "9696" 29 | traefik.docker.network: "homelab" 30 | -------------------------------------------------------------------------------- /tasks/jellyseerr.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create jellyseerr container 3 | community.docker.docker_container: 4 | name: jellyseerr 5 | image: fallenbagel/jellyseerr:latest 6 | pull: true 7 | state: started 8 | restart_policy: unless-stopped 9 | volumes: 10 | - "{{ docker_dir }}/jellyseerr/config:/app/config" 11 | networks: 12 | - name: homelab 13 | env: 14 | LOG_LEVEL: debug 15 | TZ: "{{ timezone }}" 16 | labels: 17 | traefik.enable: "true" 18 | traefik.http.routers.jellyseerr.entrypoints: "http" 19 | traefik.http.routers.jellyseerr.rule: "Host(`jellyseerr.{{ domain }}`)" 20 | traefik.http.middlewares.jellyseerr-https-redirect.redirectscheme.scheme: "https" 21 | traefik.http.routers.jellyseerr.middlewares: "jellyseerr-https-redirect" 22 | traefik.http.routers.jellyseerr-secure.entrypoints: "https" 23 | traefik.http.routers.jellyseerr-secure.rule: "Host(`jellyseerr.{{ domain }}`)" 24 | traefik.http.routers.jellyseerr-secure.tls: "true" 25 | traefik.http.routers.jellyseerr-secure.service: "jellyseerr" 26 | traefik.http.routers.jellyseerr-secure.middlewares: "authelia@docker" 27 | traefik.http.services.jellyseerr.loadbalancer.server.port: "5055" 28 | traefik.docker.network: home"lab 29 | -------------------------------------------------------------------------------- /group_vars/all/vars.yml: -------------------------------------------------------------------------------- 1 | --- 2 | username: "" 3 | 4 | docker_dir: /home/{{ username }}/docker_apps 5 | data_dir: /home/{{ username }}/data 6 | 7 | puid: "" 8 | 9 | pgid: "" 10 | 11 | ip_address: "" 12 | 13 | timezone: "" 14 | 15 | domain: "" 16 | 17 | cloudflare_email: "" 18 | 19 | cloudflare_api_key: "" 20 | 21 | traefik_basic_auth_hash: "" 22 | 23 | jwt_secret: "" 24 | 25 | authelia_sqlite_encryption_key: "" 26 | 27 | google_mail: "" 28 | 29 | google_insecure_app_pass: "" 30 | 31 | authelia_admin_mail: "" 32 | 33 | authelia_admin_argon2id_pass: "" 34 | 35 | wg_password: "" 36 | 37 | codeserver_password: "" 38 | 39 | packages: 40 | - unzip 41 | - wget 42 | - curl 43 | - git 44 | - python3 45 | - python3-pip 46 | 47 | pip_packages: 48 | - ansible 49 | - github3.py 50 | - docker 51 | 52 | docker_dependencies: 53 | - ca-certificates 54 | - gnupg 55 | - curl 56 | 57 | docker_packages: 58 | - docker-ce 59 | - docker-ce-cli 60 | - containerd.io 61 | - docker-buildx-plugin 62 | - docker-compose-plugin 63 | -------------------------------------------------------------------------------- /tasks/add_ownership.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Add required permissions to data and docker_apps directories 3 | ansible.builtin.file: 4 | path: "{{ item }}" 5 | recurse: true 6 | state: directory 7 | owner: "{{ username }}" 8 | group: "{{ username }}" 9 | mode: "0755" 10 | loop: 11 | - "{{ data_dir }}" 12 | - "{{ docker_dir }}/authelia" 13 | - "{{ docker_dir }}/code_server" 14 | - "{{ docker_dir }}/dashdot" 15 | - "{{ docker_dir }}/duplicati" 16 | - "{{ docker_dir }}/filebrowser" 17 | - "{{ docker_dir }}/guacamole" 18 | - "{{ docker_dir }}/heimdall" 19 | - "{{ docker_dir }}/homarr" 20 | - "{{ docker_dir }}/jellyfin" 21 | - "{{ docker_dir }}/jellyseerr" 22 | - "{{ docker_dir }}/monitoring" 23 | - "{{ docker_dir }}/n8n" 24 | - "{{ docker_dir }}/nextcloud" 25 | - "{{ docker_dir }}/portainer" 26 | - "{{ docker_dir }}/prowlarr" 27 | - "{{ docker_dir }}/qbittorrent" 28 | - "{{ docker_dir }}/radarr" 29 | - "{{ docker_dir }}/requestrr" 30 | - "{{ docker_dir }}/sonarr" 31 | - "{{ docker_dir }}/syncthing" 32 | - "{{ docker_dir }}/traefik" 33 | - "{{ docker_dir }}/unmanic" 34 | - "{{ docker_dir }}/uptime_kuma" 35 | - "{{ docker_dir }}/vaultwarden" 36 | - "{{ docker_dir }}/watchtower" 37 | - "{{ docker_dir }}/wireguard" 38 | -------------------------------------------------------------------------------- /tasks/radarr.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create radarr container 3 | community.docker.docker_container: 4 | name: radarr 5 | image: lscr.io/linuxserver/radarr 6 | pull: true 7 | state: started 8 | restart_policy: unless-stopped 9 | env: 10 | PUID: "{{ puid }}" 11 | PGID: "{{ pgid }}" 12 | TZ: "{{ timezone }}" 13 | DOCKER_MODS: ghcr.io/gilbn/theme.park:radarr 14 | volumes: 15 | - "{{ docker_dir }}/radarr/config:/config" 16 | - "{{ data_dir }}:/data" 17 | networks: 18 | - name: homelab 19 | labels: 20 | traefik.enable: "true" 21 | traefik.http.routers.radarr.entrypoints: "http" 22 | traefik.http.routers.radarr.rule: "Host(`radarr.{{ domain }}`)" 23 | traefik.http.middlewares.radarr-https-redirect.redirectscheme.scheme: "https" 24 | traefik.http.routers.radarr.middlewares: "radarr-https-redirect" 25 | traefik.http.routers.radarr-secure.entrypoints: "https" 26 | traefik.http.routers.radarr-secure.rule: "Host(`radarr.{{ domain }}`)" 27 | traefik.http.routers.radarr-secure.tls: "true" 28 | traefik.http.routers.radarr-secure.service: "radarr" 29 | traefik.http.routers.radarr-secure.middlewares: "authelia@docker" 30 | traefik.http.services.radarr.loadbalancer.server.port: "7878" 31 | traefik.docker.network: "homelab" 32 | -------------------------------------------------------------------------------- /tasks/sonarr.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create sonarr container 3 | community.docker.docker_container: 4 | name: sonarr 5 | image: lscr.io/linuxserver/sonarr 6 | pull: true 7 | state: started 8 | restart_policy: unless-stopped 9 | env: 10 | PUID: "{{ puid }}" 11 | PGID: "{{ pgid }}" 12 | TZ: "{{ timezone }}" 13 | DOCKER_MODS: ghcr.io/gilbn/theme.park:sonarr 14 | volumes: 15 | - "{{ docker_dir }}/sonarr/config:/config" 16 | - "{{ data_dir }}:/data" 17 | networks: 18 | - name: homelab 19 | labels: 20 | traefik.enable: "true" 21 | traefik.http.routers.sonarr.entrypoints: "http" 22 | traefik.http.routers.sonarr.rule: "Host(`sonarr.{{ domain }}`)" 23 | traefik.http.middlewares.sonarr-https-redirect.redirectscheme.scheme: "https" 24 | traefik.http.routers.sonarr.middlewares: "sonarr-https-redirect" 25 | traefik.http.routers.sonarr-secure.entrypoints: "https" 26 | traefik.http.routers.sonarr-secure.rule: "Host(`sonarr.{{ domain }}`)" 27 | traefik.http.routers.sonarr-secure.tls: "true" 28 | traefik.http.routers.sonarr-secure.service: "sonarr" 29 | traefik.http.routers.sonarr-secure.middlewares: "authelia@docker" 30 | traefik.http.services.sonarr.loadbalancer.server.port: "8989" 31 | traefik.docker.network: "homelab" 32 | -------------------------------------------------------------------------------- /tasks/portainer.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create portainer container 3 | community.docker.docker_container: 4 | name: portainer 5 | image: portainer/portainer-ce 6 | pull: true 7 | state: started 8 | restart_policy: unless-stopped 9 | volumes: 10 | - /etc/localtime:/etc/localtime:ro 11 | - /var/run/docker.sock:/var/run/docker.sock:ro 12 | - "{{ docker_dir }}/portainer/data:/data" 13 | security_opts: 14 | - no-new-privileges:true 15 | networks: 16 | - name: homelab 17 | labels: 18 | traefik.enable: "true" 19 | traefik.http.routers.portainer.entrypoints: "http" 20 | traefik.http.routers.portainer.rule: "Host(`portainer.{{ domain }}`)" 21 | traefik.http.middlewares.portainer-https-redirect.redirectscheme.scheme: "https" 22 | traefik.http.routers.portainer.middlewares: "portainer-https-redirect" 23 | traefik.http.routers.portainer-secure.entrypoints: "https" 24 | traefik.http.routers.portainer-secure.rule: "Host(`portainer.{{ domain }}`)" 25 | traefik.http.routers.portainer-secure.tls: "true" 26 | traefik.http.routers.portainer-secure.service: "portainer" 27 | traefik.http.routers.portainer-secure.middlewares: "authelia@docker" 28 | traefik.http.services.portainer.loadbalancer.server.port: "9000" 29 | traefik.docker.network: "homelab" 30 | -------------------------------------------------------------------------------- /tasks/nextcloud.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create nextcloud container 3 | community.docker.docker_container: 4 | name: nextcloud 5 | image: lscr.io/linuxserver/nextcloud:latest 6 | restart_policy: unless-stopped 7 | state: started 8 | networks: 9 | - name: homelab 10 | pull: true 11 | env: 12 | PUID: "{{ puid }}" 13 | PGID: "{{ pgid }}" 14 | TZ: "{{ timezone }}" 15 | volumes: 16 | - "{{ docker_dir }}/nextcloud/config:/config" 17 | - "{{ docker_dir }}/nextcloud/data:/data" 18 | labels: 19 | traefik.enable: "true" 20 | traefik.http.routers.nextcloud.entrypoints: "http" 21 | traefik.http.routers.nextcloud.rule: "Host(`nextcloud.{{ domain }}`)" 22 | traefik.http.middlewares.nextcloud-https-redirect.redirectscheme.scheme: "https" 23 | traefik.http.routers.nextcloud.middlewares: "nextcloud-https-redirect" 24 | traefik.http.routers.nextcloud-secure.entrypoints: "https" 25 | traefik.http.routers.nextcloud-secure.rule: "Host(`nextcloud.{{ domain }}`)" 26 | traefik.http.routers.nextcloud-secure.tls: "true" 27 | traefik.http.routers.nextcloud-secure.service: "nextcloud" 28 | traefik.http.routers.nextcloud-secure.middlewares: "authelia@docker" 29 | traefik.http.services.nextcloud.loadbalancer.server.port: "80" 30 | traefik.docker.network: "homelab" 31 | -------------------------------------------------------------------------------- /tasks/unmanic.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create unmanic container 3 | community.docker.docker_container: 4 | name: unmanic 5 | image: josh5/unmanic:latest 6 | pull: true 7 | state: started 8 | restart_policy: unless-stopped 9 | env: 10 | PUID: "{{ puid }}" 11 | PGID: "{{ pgid }}" 12 | volumes: 13 | - "{{ docker_dir }}/unmanic/config:/config" 14 | - "{{ data_dir }}/media/movies:/library/movies" 15 | - "{{ data_dir }}/media/tv:/library/tv" 16 | - /tmp:/tmp/unmanic 17 | networks: 18 | - name: homelab 19 | labels: 20 | traefik.enable: "true" 21 | traefik.http.routers.unmanic.entrypoints: "http" 22 | traefik.http.routers.unmanic.rule: "Host(`unmanic.{{ domain }}`)" 23 | traefik.http.middlewares.unmanic-https-redirect.redirectscheme.scheme: "https" 24 | traefik.http.routers.unmanic.middlewares: "unmanic-https-redirect" 25 | traefik.http.routers.unmanic-secure.entrypoints: "https" 26 | traefik.http.routers.unmanic-secure.rule: "Host(`unmanic.{{ domain }}`)" 27 | traefik.http.routers.unmanic-secure.tls: "true" 28 | traefik.http.routers.unmanic-secure.service: "unmanic" 29 | traefik.http.routers.unmanic-secure.middlewares: "authelia@docker" 30 | traefik.http.services.unmanic.loadbalancer.server.port: "8888" 31 | traefik.docker.network: "homelab" 32 | -------------------------------------------------------------------------------- /tasks/duplicati.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create duplicati container 3 | community.docker.docker_container: 4 | name: duplicati 5 | image: lscr.io/linuxserver/duplicati:latest 6 | pull: true 7 | state: started 8 | restart_policy: unless-stopped 9 | volumes: 10 | - "{{ docker_dir }}/duplicati/config:/config" 11 | - "{{ docker_dir }}/duplicati/backups:/backups" 12 | - "/:/source" 13 | networks: 14 | - name: homelab 15 | env: 16 | PUID: "0" 17 | PGID: "0" 18 | TZ: "{{ timezone }}" 19 | labels: 20 | traefik.enable: "true" 21 | traefik.http.routers.duplicati.entrypoints: "http" 22 | traefik.http.routers.duplicati.rule: "Host(`duplicati.{{ domain }}`)" 23 | traefik.http.middlewares.duplicati-https-redirect.redirectscheme.scheme: "https" 24 | traefik.http.routers.duplicati.middlewares: "duplicati-https-redirect" 25 | traefik.http.routers.duplicati-secure.entrypoints: "https" 26 | traefik.http.routers.duplicati-secure.rule: "Host(`duplicati.{{ domain }}`)" 27 | traefik.http.routers.duplicati-secure.tls: "true" 28 | traefik.http.routers.duplicati-secure.service: "duplicati" 29 | traefik.http.routers.duplicati-secure.middlewares: "authelia@docker" 30 | traefik.http.services.duplicati.loadbalancer.server.port: "8200" 31 | traefik.docker.network: "homelab" 32 | -------------------------------------------------------------------------------- /tasks/jellyfin.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create jellyfin container 3 | community.docker.docker_container: 4 | name: jellyfin 5 | image: lscr.io/linuxserver/jellyfin 6 | pull: true 7 | state: started 8 | restart_policy: unless-stopped 9 | env: 10 | PUID: "{{ puid }}" 11 | PGID: "{{ pgid }}" 12 | TZ: "{{ timezone }}" 13 | JELLYFIN_PublishedServerUrl: "{{ ip_address }}" 14 | volumes: 15 | - "{{ docker_dir }}/jellyfin/config:/config" 16 | - "{{ data_dir }}/media:/data/media" 17 | networks: 18 | - name: homelab 19 | labels: 20 | traefik.enable: "true" 21 | traefik.http.routers.jellyfin.entrypoints: "http" 22 | traefik.http.routers.jellyfin.rule: "Host(`jellyfin.{{ domain }}`)" 23 | traefik.http.middlewares.jellyfin-https-redirect.redirectscheme.scheme: "https" 24 | traefik.http.routers.jellyfin.middlewares: "jellyfin-https-redirect" 25 | traefik.http.routers.jellyfin-secure.entrypoints: "https" 26 | traefik.http.routers.jellyfin-secure.rule: "Host(`jellyfin.{{ domain }}`)" 27 | traefik.http.routers.jellyfin-secure.tls: "true" 28 | traefik.http.routers.jellyfin-secure.service: "jellyfin" 29 | traefik.http.routers.jellyfin-secure.middlewares: "authelia@docker" 30 | traefik.http.services.jellyfin.loadbalancer.server.port: "8096" 31 | traefik.docker.network: "homelab" 32 | -------------------------------------------------------------------------------- /tasks/syncthing.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create syncthing container 3 | community.docker.docker_container: 4 | name: syncthing 5 | image: lscr.io/linuxserver/syncthing:latest 6 | pull: true 7 | state: started 8 | restart_policy: unless-stopped 9 | ports: 10 | - "22000:22000" 11 | - "22000:22000/udp" 12 | - "21027:21027/udp" 13 | volumes: 14 | - "{{ docker_dir }}/syncthing/config:/config" 15 | - "{{ docker_dir }}/syncthing/:/data1" 16 | networks: 17 | - name: homelab 18 | env: 19 | PUID: "{{ puid }}" 20 | PGID: "{{ pgid }}" 21 | TZ: "{{ timezone }}" 22 | labels: 23 | traefik.enable: "true" 24 | traefik.http.routers.sync.entrypoints: "http" 25 | traefik.http.routers.sync.rule: "Host(`sync.{{ domain }}`)" 26 | traefik.http.middlewares.sync-https-redirect.redirectscheme.scheme: "https" 27 | traefik.http.routers.sync.middlewares: "sync-https-redirect" 28 | traefik.http.routers.sync-secure.entrypoints: "https" 29 | traefik.http.routers.sync-secure.rule: "Host(`sync.{{ domain }}`)" 30 | traefik.http.routers.sync-secure.tls: "true" 31 | traefik.http.routers.sync-secure.service: "sync" 32 | traefik.http.routers.sync-secure.middlewares: "authelia@docker" 33 | traefik.http.services.sync.loadbalancer.server.port: "8384" 34 | traefik.docker.network: "homelab" 35 | -------------------------------------------------------------------------------- /tasks/code_server.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create codeserver container 3 | community.docker.docker_container: 4 | name: codeserver 5 | image: lscr.io/linuxserver/code-server:latest 6 | pull: true 7 | state: started 8 | restart_policy: unless-stopped 9 | volumes: 10 | - "/home/{{ username }}/:/home/{{ username }}" 11 | - "{{ docker_dir }}/code_server/config:/config" 12 | networks: 13 | - name: homelab 14 | env: 15 | PUID: "{{ puid }}" 16 | PGID: "{{ pgid }}" 17 | PASSWORD: "{{ codeserver_password }}" 18 | SUDO_PASSWORD: "{{ codeserver_password }}" 19 | TZ: "{{ timezone }}" 20 | labels: 21 | traefik.enable: "true" 22 | traefik.http.routers.code.entrypoints: "http" 23 | traefik.http.routers.code.rule: "Host(`code.{{ domain }}`)" 24 | traefik.http.middlewares.code-https-redirect.redirectscheme.scheme: "https" 25 | traefik.http.routers.code.middlewares: "code-https-redirect" 26 | traefik.http.routers.code-secure.entrypoints: "https" 27 | traefik.http.routers.code-secure.rule: "Host(`code.{{ domain }}`)" 28 | traefik.http.routers.code-secure.tls: "true" 29 | traefik.http.routers.code-secure.service: "code" 30 | traefik.http.routers.code-secure.middlewares: "authelia@docker" 31 | traefik.http.services.code.loadbalancer.server.port: "8443" 32 | traefik.docker.network: "homelab" 33 | -------------------------------------------------------------------------------- /tasks/wireguard.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create wireguard container 3 | community.docker.docker_container: 4 | name: wireguard 5 | image: weejewel/wg-easy 6 | pull: true 7 | sysctls: 8 | "net.ipv4.ip_forward": "1" 9 | "net.ipv4.conf.all.src_valid_mark": "1" 10 | capabilities: 11 | - NET_ADMIN 12 | - SYS_MODULE 13 | state: started 14 | env: 15 | "PASSWORD": "{{ wg_password }}" 16 | "WG_HOST": "{{ ip_address }}" 17 | ports: 18 | - "51820:51820/udp" 19 | volumes: 20 | - "{{ docker_dir }}/wireguard/config:/etc/wireguard" 21 | restart_policy: unless-stopped 22 | networks: 23 | - name: homelab 24 | labels: 25 | traefik.enable: "true" 26 | traefik.http.routers.wg.entrypoints: "http" 27 | traefik.http.routers.wg.rule: "Host(`wg.{{ domain }}`)" 28 | traefik.http.middlewares.wg-https-redirect.redirectscheme.scheme: "https" 29 | traefik.http.routers.wg.middlewares: "wg-https-redirect" 30 | traefik.http.routers.wg-secure.entrypoints: "https" 31 | traefik.http.routers.wg-secure.rule: "Host(`wg.{{ domain }}`)" 32 | traefik.http.routers.wg-secure.tls: "true" 33 | traefik.http.routers.wg-secure.service: "wg" 34 | traefik.http.routers.wg-secure.middlewares: "authelia@docker" 35 | traefik.http.services.wg.loadbalancer.server.port: "51821" 36 | traefik.docker.network: "homelab" 37 | -------------------------------------------------------------------------------- /.terraform.lock.hcl: -------------------------------------------------------------------------------- 1 | # This file is maintained automatically by "terraform init". 2 | # Manual edits may be lost in future updates. 3 | 4 | provider "registry.terraform.io/hashicorp/aws" { 5 | version = "4.59.0" 6 | hashes = [ 7 | "h1:fuIdjl9f2JEH0TLoq5kc9NIPbJAAV7YBbZ8fvNp5XSg=", 8 | "zh:0341a460210463a0bebd5c12ce13dc49bd8cae2399b215418c5efa607fed84e4", 9 | "zh:0544e9bbdd31d3551e7273bed7326d26a28653fd9c26b5cd06ac8ed76f188798", 10 | "zh:3d13acd0363f0a48d2725cae9d224481df38dddb90ef4a66eb82303f0aa45a99", 11 | "zh:416f5b92d41dce1d7ee1a1acb06ba8b0f10679eecee2fcc134853adbb09d9757", 12 | "zh:80c9c3b901151cd697caa58bfa196816d4622e4ce11aa789e36efc460695313b", 13 | "zh:8fc3659ebdae1ac9de899f57e5a3a50274a2e96c46aa2cf74be51ffdac56300a", 14 | "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", 15 | "zh:a235b44ad074446a6138b3fb454dd0d234aacf7a1efea89d1eafac7284689d19", 16 | "zh:a36a7f1cd7f9f6c45127d916a65b5441cc0430393535a5a3de4b646405c50c41", 17 | "zh:c161c38727902271efa19020b95b69ebe0282989d575f31dff603a1d551bafd2", 18 | "zh:d1562223347c49cbe3ff6e7295e25816a35dfef862d28cd8a7870e7be6ec8093", 19 | "zh:e7a1d08bfe91d3789755ee587fc816907c3bea203342c717144c7459111ce20c", 20 | "zh:e89d5a668c391669ed323d493c5ea131fe8833d562a6fe31f525bdcbe959056e", 21 | "zh:f268ccd3e1a32ba7fd59bbf0c8d85611201c0c87462a2a5cddd02babde7b5fe8", 22 | "zh:fe8c2eae8c367d2cb7cade250a8d5f6c411ac4a8214c46df0a1fd90d9eaf7152", 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /tasks/filebrowser.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create filebrowser.db file 3 | ansible.builtin.file: 4 | path: "{{ docker_dir }}/filebrowser/filebrowser.db" 5 | state: touch 6 | owner: "{{ username }}" 7 | group: "{{ username }}" 8 | mode: "0755" 9 | access_time: preserve 10 | modification_time: preserve 11 | 12 | - name: Create file browser container 13 | community.docker.docker_container: 14 | name: filebrowser 15 | image: filebrowser/filebrowser 16 | env: 17 | PUID: "{{ puid }}" 18 | PGID: "{{ pgid }}" 19 | pull: true 20 | state: started 21 | networks: 22 | - name: homelab 23 | volumes: 24 | - "/home/{{ username }}/:/srv" 25 | - "{{ docker_dir }}/filebrowser/filebrowser.db:/database.db" 26 | restart_policy: unless-stopped 27 | security_opts: 28 | - no-new-privileges:true 29 | labels: 30 | traefik.enable: "true" 31 | traefik.http.routers.files.entrypoints: "http" 32 | traefik.http.routers.files.rule: "Host(`files.{{ domain }}`)" 33 | traefik.http.middlewares.files-https-redirect.redirectscheme.scheme: "https" 34 | traefik.http.routers.files.middlewares: "files-https-redirect" 35 | traefik.http.routers.files-secure.entrypoints: "https" 36 | traefik.http.routers.files-secure.rule: "Host(`files.{{ domain }}`)" 37 | traefik.http.routers.files-secure.tls: "true" 38 | traefik.http.routers.files-secure.service: "files" 39 | traefik.http.routers.files-secure.middlewares: "authelia@docker" 40 | traefik.http.services.files.loadbalancer.server.port: "80" 41 | traefik.docker.network: "homelab" 42 | -------------------------------------------------------------------------------- /main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Setup the homeserver 3 | hosts: all 4 | become: true 5 | 6 | tasks: 7 | - name: Update server and install essential packages 8 | ansible.builtin.include_tasks: tasks/essential.yml 9 | 10 | - name: Install docker 11 | ansible.builtin.include_tasks: tasks/docker.yml 12 | 13 | - name: Deploy docker apps 14 | ansible.builtin.include_tasks: "tasks/{{ item }}.yml" 15 | loop: 16 | - authelia 17 | - traefik 18 | - code_server 19 | - dashdot 20 | - duplicati 21 | - filebrowser 22 | - guacamole 23 | - heimdall 24 | - homarr 25 | - jellyfin 26 | - jellyseerr 27 | - monitoring 28 | - n8n 29 | - nextcloud 30 | - portainer 31 | - prowlarr 32 | - qbittorrent 33 | - radarr 34 | - requestrr 35 | - sonarr 36 | - syncthing 37 | - unmanic 38 | - uptime_kuma 39 | - vaultwarden 40 | - watchtower 41 | - wireguard 42 | 43 | - name: Modify ownership of data and docker_apps directories 44 | ansible.builtin.include_tasks: tasks/add_ownership.yml 45 | 46 | - name: Reboot the server 47 | ansible.builtin.reboot: 48 | msg: "Rebooting server to finish setup" 49 | 50 | - name: Check if traefik is working properly with authelia middleware 51 | ansible.builtin.uri: 52 | url: "https://wg.{{ domain }}" 53 | ignore_errors: true 54 | register: traefik_authelia_check 55 | 56 | - name: Wait to ensure authelia is up and running 57 | ansible.builtin.wait_for: 58 | timeout: 10 59 | when: traefik_authelia_check.status != 200 60 | 61 | - name: Restart traefik container to ensure it can find authelia middleware 62 | community.docker.docker_container: 63 | name: traefik 64 | state: started 65 | restart: true 66 | when: traefik_authelia_check.status != 200 67 | -------------------------------------------------------------------------------- /tasks/qbittorrent.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Get latest release of vuetorrent 3 | community.general.github_release: 4 | user: "WDaan" 5 | repo: "VueTorrent" 6 | action: latest_release 7 | register: latest_release 8 | 9 | - name: Unzip vuetorrent 10 | ansible.builtin.unarchive: 11 | src: "https://github.com/WDaan/VueTorrent/releases/download/{{ latest_release.tag }}/vuetorrent.zip" 12 | dest: "{{ docker_dir }}/qbittorrent" 13 | creates: "{{ docker_dir }}/qbittorrent/vuetorrent" 14 | remote_src: true 15 | owner: "{{ username }}" 16 | group: "{{ username }}" 17 | mode: "0755" 18 | 19 | - name: Create qbittorrent container 20 | community.docker.docker_container: 21 | name: qbittorrent 22 | image: linuxserver/qbittorrent 23 | restart_policy: unless-stopped 24 | state: started 25 | networks: 26 | - name: homelab 27 | ports: 28 | - "6881:6881" 29 | - "6881:6881/udp" 30 | volumes: 31 | - "{{ docker_dir }}/qbittorrent/config:/config" 32 | - "{{ data_dir }}/torrents:/data/torrents" 33 | - "{{ docker_dir }}/qbittorrent/vuetorrent:/vuetorrent" 34 | env: 35 | PUID: "{{ puid }}" 36 | PGID: "{{ pgid }}" 37 | TZ: "{{ timezone }}" 38 | labels: 39 | traefik.enable: "true" 40 | traefik.http.routers.qbit.entrypoints: "http" 41 | traefik.http.routers.qbit.rule: "Host(`qbit.{{ domain }}`)" 42 | traefik.http.middlewares.qbit-https-redirect.redirectscheme.scheme: "https" 43 | traefik.http.routers.qbit.middlewares: "qbit-https-redirect" 44 | traefik.http.routers.qbit-secure.entrypoints: "https" 45 | traefik.http.routers.qbit-secure.rule: "Host(`qbit.{{ domain }}`)" 46 | traefik.http.routers.qbit-secure.tls: "true" 47 | traefik.http.routers.qbit-secure.service: "qbit" 48 | traefik.http.routers.qbit-secure.middlewares: "authelia@docker" 49 | traefik.http.services.qbit.loadbalancer.server.port: "8080" 50 | traefik.docker.network: "homelab" 51 | -------------------------------------------------------------------------------- /tasks/docker.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Set amd64 architecture 3 | ansible.builtin.set_fact: 4 | docker_arch: amd64 5 | when: ansible_architecture == "x86_64" 6 | 7 | - name: Set arm64 architecture 8 | ansible.builtin.set_fact: 9 | docker_arch: arm64 10 | when: ansible_architecture == "aarch64" 11 | 12 | - name: Set RedHat distribution 13 | ansible.builtin.set_fact: 14 | rh_distribution: "{{ 'rhel' if ansible_distribution == 'RedHat' else ansible_distribution | lower }}" 15 | 16 | - name: Install Docker dependencies 17 | ansible.builtin.package: 18 | name: "{{ docker_dependencies }}" 19 | state: present 20 | 21 | - name: Add Docker GPG key for apt 22 | ansible.builtin.apt_key: 23 | url: https://download.docker.com/linux/{{ ansible_distribution | lower }}/gpg 24 | state: present 25 | when: ansible_distribution in ["Ubuntu", "Debian"] 26 | 27 | - name: Add Docker repository for apt 28 | ansible.builtin.apt_repository: 29 | repo: deb [arch={{ docker_arch }}] https://download.docker.com/linux/{{ ansible_distribution | lower }} {{ ansible_distribution_release }} stable 30 | state: present 31 | when: ansible_distribution in ["Ubuntu", "Debian"] 32 | 33 | - name: Add signing key for yum 34 | ansible.builtin.rpm_key: 35 | key: "https://download.docker.com/linux/{{ rh_distribution }}/gpg" 36 | state: present 37 | when: ansible_distribution in ["Fedora", "CentOS", "RedHat"] 38 | 39 | - name: Add repository into repo.d list for yum 40 | ansible.builtin.yum_repository: 41 | name: docker 42 | description: docker repository 43 | baseurl: "https://download.docker.com/linux/{{ rh_distribution }}/$releasever/$basearch/stable" 44 | enabled: true 45 | gpgcheck: true 46 | gpgkey: "https://download.docker.com/linux/{{ rh_distribution }}/gpg" 47 | when: ansible_distribution in ["Fedora", "CentOS", "RedHat"] 48 | 49 | - name: Install docker 50 | ansible.builtin.package: 51 | name: "{{ docker_packages }}" 52 | state: present 53 | 54 | - name: Ensure Docker group exists 55 | ansible.builtin.group: 56 | name: docker 57 | state: present 58 | 59 | - name: Add user to Docker group 60 | ansible.builtin.user: 61 | name: "{{ username }}" 62 | groups: docker 63 | append: true 64 | 65 | - name: Enable Docker service 66 | ansible.builtin.service: 67 | name: docker 68 | enabled: true 69 | state: started 70 | 71 | - name: Create custom docker network 72 | community.docker.docker_network: 73 | name: homelab 74 | state: present 75 | driver: bridge 76 | -------------------------------------------------------------------------------- /tasks/traefik.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create traefik data directory 3 | ansible.builtin.file: 4 | path: "{{ docker_dir }}/traefik/data" 5 | state: directory 6 | mode: '0755' 7 | access_time: preserve 8 | modification_time: preserve 9 | 10 | - name: Create acme.json file 11 | ansible.builtin.file: 12 | path: "{{ docker_dir }}/traefik/data/acme.json" 13 | state: touch 14 | mode: '0600' 15 | access_time: preserve 16 | modification_time: preserve 17 | 18 | - name: Create traefik.yml config file 19 | ansible.builtin.copy: 20 | content: | 21 | api: 22 | debug: true 23 | dashboard: true 24 | entryPoints: 25 | http: 26 | address: ":80" 27 | http: 28 | redirections: 29 | entryPoint: 30 | to: "https" 31 | scheme: "https" 32 | https: 33 | address: ":443" 34 | serversTransport: 35 | insecureSkipVerify: true 36 | providers: 37 | docker: 38 | endpoint: "unix:///var/run/docker.sock" 39 | exposedByDefault: false 40 | certificatesResolvers: 41 | cloudflare: 42 | acme: 43 | email: "{{ cloudflare_email }}" 44 | storage: "acme.json" 45 | dnsChallenge: 46 | provider: "cloudflare" 47 | resolvers: 48 | - "1.1.1.1:53" 49 | - "1.0.0.1:53" 50 | dest: "{{ docker_dir }}/traefik/data/traefik.yml" 51 | mode: '0644' 52 | owner: "{{ username }}" 53 | group: "{{ username }}" 54 | 55 | - name: Deploy traefik with docker container 56 | community.docker.docker_container: 57 | name: traefik 58 | image: traefik:latest 59 | restart_policy: unless-stopped 60 | state: started 61 | security_opts: 62 | - no-new-privileges:true 63 | networks: 64 | - name: homelab 65 | ports: 66 | - "80:80" 67 | - "443:443" 68 | volumes: 69 | - /var/run/docker.sock:/var/run/docker.sock:ro 70 | - "{{ docker_dir }}/traefik/data/acme.json:/acme.json" 71 | - "{{ docker_dir }}/traefik/data/traefik.yml:/traefik.yml:ro" 72 | env: 73 | CF_API_EMAIL: "{{ cloudflare_email }}" 74 | CF_API_KEY: "{{ cloudflare_api_key }}" 75 | labels: 76 | traefik.enable: "true" 77 | traefik.http.routers.traefik.entrypoints: "http" 78 | traefik.http.routers.traefik.rule: "Host(`traefik.{{ domain }}`)" 79 | traefik.http.middlewares.traefik-auth.basicauth.users: "{{ traefik_basic_auth_hash }}" 80 | traefik.http.middlewares.traefik-https-redirect.redirectscheme.scheme: "https" 81 | traefik.http.middlewares.sslheader.headers.customrequestheaders.X-Forwarded-Proto: "https" 82 | traefik.http.routers.traefik.middlewares: "traefik-https-redirect" 83 | traefik.http.routers.traefik-secure.entrypoints: "https" 84 | traefik.http.routers.traefik-secure.rule: "Host(`traefik.{{ domain }}`)" 85 | traefik.http.routers.traefik-secure.middlewares: "traefik-auth" 86 | traefik.http.routers.traefik-secure.tls: "true" 87 | traefik.http.routers.traefik-secure.tls.certresolver: "cloudflare" 88 | traefik.http.routers.traefik-secure.tls.domains[0].main: "{{ domain }}" 89 | traefik.http.routers.traefik-secure.tls.domains[0].sans: "*.{{ domain }}" 90 | traefik.http.routers.traefik-secure.service: "api@internal" 91 | -------------------------------------------------------------------------------- /bootstrap.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import subprocess 5 | 6 | # Clone the repository 7 | subprocess.run( 8 | ['git', 'clone', 'https://github.com/rishavnandi/ansible_homelab.git']) 9 | os.chdir('ansible_homelab') 10 | 11 | # Read user input 12 | username = input("Enter username: ") 13 | puid = input("Enter puid of the user: ") 14 | pgid = input("Enter pgid of the user: ") 15 | timezone = input("Enter timezone: ") 16 | wg_password = input("Enter password for wireguard: ") 17 | codeserver_password = input("Enter password for codeserver: ") 18 | server_ip = input("Enter server IP address: ") 19 | use_password = input("Are you using a password instead of SSH keys? [y/n]: ") 20 | domain_name = input("Enter the domain name: ") 21 | cloudflare_email = input("Enter the Cloudflare email for traefik: ") 22 | cloudflare_api_key = input("Enter the Cloudflare API key for traefik: ") 23 | traefik_user_hash = input("Enter traefik dashboard user hash: ") 24 | jwt_secret = input("Enter jwt secret for authelia: ") 25 | encryption_key = input("Enter encryption key for authelia sqlite database: ") 26 | gmail_address = input("Enter gmail address for authelia smtp: ") 27 | gmail_password = input("Enter gmail insecure app password for authelia smtp: ") 28 | admin_email = input("Enter email for authelia admin: ") 29 | admin_password = input("Enter argon2id password hash for authelia admin: ") 30 | 31 | # Replace values in vars.yml file 32 | with open('group_vars/all/vars.yml', 'r') as f: 33 | content = f.read() 34 | content = content.replace('', username) 35 | content = content.replace('', timezone) 36 | content = content.replace('', wg_password) 37 | content = content.replace('', codeserver_password) 38 | content = content.replace('', server_ip) 39 | content = content.replace('', puid) 40 | content = content.replace('', pgid) 41 | content = content.replace('', domain_name) 42 | content = content.replace('', cloudflare_email) 43 | content = content.replace('', cloudflare_api_key) 44 | content = content.replace('', traefik_user_hash) 45 | content = content.replace('', jwt_secret) 46 | content = content.replace('', encryption_key) 47 | content = content.replace('', gmail_address) 48 | content = content.replace('', gmail_password) 49 | content = content.replace('', admin_email) 50 | content = content.replace('', admin_password) 51 | with open('group_vars/all/vars.yml', 'w') as f: 52 | f.write(content) 53 | 54 | # Replace values in inventory file 55 | with open('inventory', 'r') as f: 56 | content = f.read() 57 | content = content.replace('', server_ip) 58 | content = content.replace('', username) 59 | if use_password == 'y': 60 | ssh_password = input("Enter SSH password: ") 61 | content = content.replace( 62 | 'ansible_ssh_private_key_file = ', '') 63 | content += f'ansible_ssh_pass = {ssh_password}\n' 64 | else: 65 | private_key_path = input("Enter path to private key: ") 66 | content = content.replace('', private_key_path) 67 | with open('inventory', 'w') as f: 68 | f.write(content) 69 | 70 | # Run the playbook 71 | subprocess.run(['ansible-playbook', 'main.yml']) 72 | -------------------------------------------------------------------------------- /tasks/monitoring.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create prometheus directory 3 | ansible.builtin.file: 4 | path: "/etc/prometheus/" 5 | state: directory 6 | owner: "{{ username }}" 7 | group: "{{ username }}" 8 | mode: "0755" 9 | 10 | - name: Create prometheus.yml file 11 | ansible.builtin.copy: 12 | content: | 13 | global: 14 | scrape_interval: 15s 15 | scrape_configs: 16 | - job_name: 'prometheus' 17 | scrape_interval: 5s 18 | static_configs: 19 | - targets: ['localhost:9090'] 20 | - job_name: 'node_exporter' 21 | static_configs: 22 | - targets: ['node_exporter:9100'] 23 | - job_name: 'cadvisor' 24 | static_configs: 25 | - targets: ['cadvisor:8080'] 26 | dest: /etc/prometheus/prometheus.yml 27 | owner: "{{ username }}" 28 | group: "{{ username }}" 29 | mode: "0755" 30 | 31 | - name: Create volumes for monitoring containers 32 | community.docker.docker_volume: 33 | name: "{{ item }}" 34 | state: present 35 | driver: local 36 | loop: 37 | - prometheus-data 38 | - grafana-data 39 | 40 | - name: Create prometheus container 41 | community.docker.docker_container: 42 | name: prometheus 43 | image: prom/prometheus:latest 44 | pull: true 45 | state: started 46 | volumes: 47 | - /etc/prometheus/:/etc/prometheus/ 48 | - prometheus-data:/prometheus 49 | restart_policy: unless-stopped 50 | command: "--config.file=/etc/prometheus/prometheus.yml" 51 | networks: 52 | - name: homelab 53 | 54 | - name: Create node exporter container 55 | community.docker.docker_container: 56 | name: node_exporter 57 | image: quay.io/prometheus/node-exporter:latest 58 | pull: true 59 | state: started 60 | pid_mode: host 61 | restart_policy: unless-stopped 62 | command: "--path.rootfs=/host" 63 | volumes: 64 | - /:/host:ro,rslave 65 | networks: 66 | - name: homelab 67 | 68 | - name: Get cadvisor latest release 69 | community.general.github_release: 70 | user: google 71 | repo: cadvisor 72 | action: latest_release 73 | register: cadvisor_latest_release 74 | 75 | - name: Create cadvisor container 76 | community.docker.docker_container: 77 | name: cadvisor 78 | image: gcr.io/cadvisor/cadvisor:{{ cadvisor_latest_release.tag }} 79 | pull: true 80 | state: started 81 | restart_policy: unless-stopped 82 | privileged: true 83 | volumes: 84 | - /:/rootfs:ro 85 | - /var/run:/var/run:ro 86 | - /sys:/sys:ro 87 | - /var/lib/docker/:/var/lib/docker:ro 88 | - /dev/disk/:/dev/disk:ro 89 | devices: 90 | - /dev/kmsg 91 | networks: 92 | - name: homelab 93 | 94 | - name: Create grafana container 95 | community.docker.docker_container: 96 | name: grafana 97 | image: grafana/grafana-oss:latest 98 | pull: true 99 | state: started 100 | restart_policy: unless-stopped 101 | volumes: 102 | - grafana-data:/var/lib/grafana 103 | networks: 104 | - name: homelab 105 | labels: 106 | traefik.enable: "true" 107 | traefik.http.routers.grafana.entrypoints: "http" 108 | traefik.http.routers.grafana.rule: "Host(`grafana.{{ domain }}`)" 109 | traefik.http.middlewares.grafana-https-redirect.redirectscheme.scheme: "https" 110 | traefik.http.routers.grafana.middlewares: "grafana-https-redirect" 111 | traefik.http.routers.grafana-secure.entrypoints: "https" 112 | traefik.http.routers.grafana-secure.rule: "Host(`grafana.{{ domain }}`)" 113 | traefik.http.routers.grafana-secure.tls: "true" 114 | traefik.http.routers.grafana-secure.service: "grafana" 115 | traefik.http.routers.grafana-secure.middlewares: "authelia@docker" 116 | traefik.http.services.grafana.loadbalancer.server.port: "3000" 117 | traefik.docker.network: "homelab" 118 | -------------------------------------------------------------------------------- /tasks/essential.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Update apt packages 3 | ansible.builtin.apt: 4 | update_cache: true 5 | cache_valid_time: 3600 6 | upgrade: full 7 | when: ansible_distribution in ["Ubuntu", "Debian"] 8 | 9 | - name: Update dnf packages 10 | ansible.builtin.dnf: 11 | name: "*" 12 | state: latest 13 | update_only: true 14 | when: ansible_distribution in ["Fedora", "CentOS", "RedHat"] 15 | 16 | - name: Install dnf-utils if on Fedora 17 | ansible.builtin.package: 18 | name: dnf-utils 19 | state: present 20 | when: ansible_distribution in ["Fedora", "CentOS", "RedHat"] 21 | 22 | - name: Check if a reboot is required on Fedora 23 | ansible.builtin.command: needs-restarting -r 24 | register: reboot_required 25 | ignore_errors: true 26 | changed_when: reboot_required.rc != 0 27 | when: ansible_distribution in ["Fedora", "CentOS", "RedHat"] 28 | 29 | - name: Reboot if required on Fedora 30 | ansible.builtin.reboot: 31 | msg: Rebooting due to a kernel update 32 | when: ansible_distribution in ["Fedora", "CentOS", "RedHat "] and reboot_required.rc == 1 33 | 34 | - name: Check if reboot required on Ubuntu 35 | ansible.builtin.stat: 36 | path: /var/run/reboot-required 37 | register: reboot_required_file 38 | when: ansible_distribution in ["Ubuntu", "Debian"] 39 | 40 | - name: Reboot if required on Ubuntu 41 | ansible.builtin.reboot: 42 | msg: Rebooting due to a kernel update 43 | when: (ansible_distribution in ["Ubuntu", "Debian"]) and reboot_required_file.stat.exists 44 | 45 | - name: Install packages 46 | ansible.builtin.package: 47 | name: "{{ packages }}" 48 | state: present 49 | 50 | - name: Install pip packages 51 | ansible.builtin.pip: 52 | name: "{{ pip_packages }}" 53 | state: present 54 | break_system_packages: true 55 | 56 | - name: Create Docker directory 57 | ansible.builtin.file: 58 | path: "{{ docker_dir }}" 59 | state: directory 60 | owner: "{{ username }}" 61 | group: "{{ username }}" 62 | mode: "0755" 63 | 64 | - name: Create data directory 65 | ansible.builtin.file: 66 | path: "{{ item }}" 67 | state: directory 68 | owner: "{{ username }}" 69 | group: "{{ username }}" 70 | mode: "0755" 71 | loop: 72 | - "{{ data_dir }}" 73 | - "{{ data_dir }}/torrents" 74 | - "{{ data_dir }}/media" 75 | - "{{ data_dir }}/media/movies" 76 | - "{{ data_dir }}/media/tv" 77 | - "{{ data_dir }}/torrents/movies" 78 | - "{{ data_dir }}/torrents/tv" 79 | 80 | - name: Create app directories 81 | ansible.builtin.file: 82 | path: "{{ item }}" 83 | state: directory 84 | owner: "{{ username }}" 85 | group: "{{ username }}" 86 | mode: "0755" 87 | loop: 88 | - "{{ docker_dir }}/authelia" 89 | - "{{ docker_dir }}/code_server" 90 | - "{{ docker_dir }}/dashdot" 91 | - "{{ docker_dir }}/duplicati" 92 | - "{{ docker_dir }}/filebrowser" 93 | - "{{ docker_dir }}/guacamole" 94 | - "{{ docker_dir }}/heimdall" 95 | - "{{ docker_dir }}/homarr" 96 | - "{{ docker_dir }}/jellyfin" 97 | - "{{ docker_dir }}/jellyseerr" 98 | - "{{ docker_dir }}/monitoring" 99 | - "{{ docker_dir }}/n8n" 100 | - "{{ docker_dir }}/nextcloud" 101 | - "{{ docker_dir }}/portainer" 102 | - "{{ docker_dir }}/prowlarr" 103 | - "{{ docker_dir }}/qbittorrent" 104 | - "{{ docker_dir }}/radarr" 105 | - "{{ docker_dir }}/requestrr" 106 | - "{{ docker_dir }}/sonarr" 107 | - "{{ docker_dir }}/syncthing" 108 | - "{{ docker_dir }}/traefik" 109 | - "{{ docker_dir }}/unmanic" 110 | - "{{ docker_dir }}/uptime_kuma" 111 | - "{{ docker_dir }}/vaultwarden" 112 | - "{{ docker_dir }}/watchtower" 113 | - "{{ docker_dir }}/wireguard" 114 | 115 | - name: Suppress login messages 116 | ansible.builtin.file: 117 | name: /home/{{ username }}/.hushlogin 118 | mode: "0644" 119 | state: touch 120 | owner: "{{ username }}" 121 | group: "{{ username }}" 122 | modification_time: preserve 123 | access_time: preserve 124 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributions 2 | 3 | ## General Contribution Guidelines 4 | 5 | A big welcome and thank you for considering contributing to this open source project! It’s people like you that make it a reality for users in our community. 6 | 7 | Reading and following these guidelines will help us make the contribution process easy and effective for everyone involved. It also communicates that you agree to respect the time of the developers managing and developing this open source project. In return, we will reciprocate that respect by addressing your issue, assessing changes, and helping you finalize your pull requests. 8 | 9 | ## Quicklinks 10 | 11 | - [Contributions](#contributions) 12 | - [General Contribution Guidelines](#general-contribution-guidelines) 13 | - [Quicklinks](#quicklinks) 14 | - [Getting Started](#getting-started) 15 | - [Code of Conduct](#code-of-conduct) 16 | - [Issues](#issues) 17 | - [Pull Requests](#pull-requests) 18 | - [Getting Help](#getting-help) 19 | 20 | ## Getting Started 21 | 22 | ## Code of Conduct 23 | 24 | We take our open source community seriously and hold ourselves and other contributors to high standards of communication. By participating and contributing to this project, you agree to uphold our [Code of Conduct](/CODE_OF_CONDUCT.md). 25 | 26 |
27 | 28 | Contributions are made to this repo via Issues and Pull Requests (PRs). A few general guidelines that cover both: 29 | 30 | - Search for existing Issues and PRs before creating your own. 31 | - We work hard to makes sure issues are handled in a timely manner but, depending on the impact, it could take a while to investigate the root cause. A friendly ping in the comment thread to the submitter or a contributor can help draw attention if your issue is blocking. 32 | - If you've never contributed before, check out [this awesome blog](https://auth0.com/blog/a-first-timers-guide-to-an-open-source-project/) for resources and tips on how to get started. 33 | 34 | ### Issues 35 | 36 | Issues should be used to report problems with the project, request a new feature, or to discuss potential changes before a PR is created. When you create a new Issue, a template will be loaded that will guide you through collecting and providing the information we need to investigate. 37 | 38 | If you find an Issue that addresses the problem you're having, please add your own reproduction information to the existing issue rather than creating a new one. Adding a [reaction](https://github.blog/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) can also help be indicating to our maintainers that a particular problem is affecting more than just the reporter. 39 | 40 | ### Pull Requests 41 | 42 | PRs to our libraries are always welcome and can be a quick way to get your fix or improvement slated for the next release. In general, PRs should: 43 | 44 | - Only fix/add the functionality in question **OR** address wide-spread whitespace/style issues, not both. 45 | - Add unit or integration tests for fixed or changed functionality (if a test suite already exists). 46 | - Address a single concern in the least number of changed lines as possible. 47 | - Include documentation in the repo. 48 | - Be accompanied by a complete Pull Request template (loaded automatically when a PR is created). 49 | 50 | For changes that address core functionality or would require breaking changes (e.g. a major release), it's best to open an Issue to discuss your proposal first. This is not required but can save time creating and reviewing changes. 51 | 52 | In general, we follow the ["fork-and-pull" Git workflow](https://github.com/susam/gitpr) 53 | 54 | 1. Fork the repository to your own Github account 55 | 2. Clone the project to your machine 56 | 3. Create a branch locally with a succinct but descriptive name 57 | 4. Commit changes to the branch 58 | 5. Following any formatting and testing guidelines specific to this repo 59 | 6. Push changes to your fork 60 | 7. Open a PR in our repository and follow the PR template so that we can efficiently review the changes. 61 | 62 | ## Getting Help 63 | 64 | You can always reach out to the community or one of the maintainers for help. Feel free to triage issues. Every bit of information helps ! -------------------------------------------------------------------------------- /main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "us-east-1" 3 | } 4 | 5 | resource "aws_vpc" "tf_vpc" { 6 | cidr_block = "10.0.0.0/16" 7 | 8 | tags = { 9 | Name = "tf_vpc" 10 | } 11 | } 12 | 13 | resource "aws_subnet" "tf_public_subnet" { 14 | vpc_id = aws_vpc.tf_vpc.id 15 | cidr_block = "10.0.1.0/24" 16 | availability_zone = "us-east-1a" 17 | map_public_ip_on_launch = true 18 | 19 | tags = { 20 | Name = "tf_public_subnet" 21 | } 22 | } 23 | 24 | resource "aws_subnet" "tf_private_subnet" { 25 | vpc_id = aws_vpc.tf_vpc.id 26 | cidr_block = "10.0.2.0/24" 27 | availability_zone = "us-east-1a" 28 | 29 | tags = { 30 | Name = "tf_private_subnet" 31 | } 32 | 33 | } 34 | 35 | resource "aws_internet_gateway" "tf_igw" { 36 | vpc_id = aws_vpc.tf_vpc.id 37 | 38 | tags = { 39 | Name = "tf_igw" 40 | } 41 | } 42 | 43 | resource "aws_route_table" "tf_rt" { 44 | vpc_id = aws_vpc.tf_vpc.id 45 | 46 | route { 47 | cidr_block = "0.0.0.0/0" 48 | gateway_id = aws_internet_gateway.tf_igw.id 49 | } 50 | 51 | tags = { 52 | Name = "tf_rt" 53 | } 54 | } 55 | 56 | resource "aws_route_table_association" "tf_rta" { 57 | subnet_id = aws_subnet.tf_public_subnet.id 58 | route_table_id = aws_route_table.tf_rt.id 59 | } 60 | 61 | resource "aws_security_group" "tf_sg" { 62 | name = "tf_sg" 63 | vpc_id = aws_vpc.tf_vpc.id 64 | 65 | ingress { 66 | from_port = 22 67 | to_port = 22 68 | protocol = "tcp" 69 | cidr_blocks = ["0.0.0.0/0"] 70 | } 71 | 72 | ingress { 73 | from_port = 8200 74 | to_port = 8200 75 | protocol = "tcp" 76 | cidr_blocks = ["0.0.0.0/0"] 77 | } 78 | 79 | ingress { 80 | from_port = 8081 81 | to_port = 8081 82 | protocol = "tcp" 83 | cidr_blocks = ["0.0.0.0/0"] 84 | } 85 | 86 | ingress { 87 | from_port = 9000 88 | to_port = 9000 89 | protocol = "tcp" 90 | cidr_blocks = ["0.0.0.0/0"] 91 | } 92 | 93 | ingress { 94 | from_port = 5000 95 | to_port = 5000 96 | protocol = "tcp" 97 | cidr_blocks = ["0.0.0.0/0"] 98 | } 99 | 100 | ingress { 101 | from_port = 80 102 | to_port = 80 103 | protocol = "tcp" 104 | cidr_blocks = ["0.0.0.0/0"] 105 | } 106 | 107 | ingress { 108 | from_port = 443 109 | to_port = 443 110 | protocol = "tcp" 111 | cidr_blocks = ["0.0.0.0/0"] 112 | } 113 | 114 | ingress { 115 | from_port = 81 116 | to_port = 81 117 | protocol = "tcp" 118 | cidr_blocks = ["0.0.0.0/0"] 119 | } 120 | 121 | ingress { 122 | from_port = 9443 123 | to_port = 9443 124 | protocol = "tcp" 125 | cidr_blocks = ["0.0.0.0/0"] 126 | } 127 | 128 | ingress { 129 | from_port = 6881 130 | to_port = 6881 131 | protocol = "tcp" 132 | cidr_blocks = ["0.0.0.0/0"] 133 | } 134 | 135 | ingress { 136 | from_port = 6881 137 | to_port = 6881 138 | protocol = "udp" 139 | cidr_blocks = ["0.0.0.0/0"] 140 | } 141 | 142 | ingress { 143 | from_port = 8080 144 | to_port = 8080 145 | protocol = "tcp" 146 | cidr_blocks = ["0.0.0.0/0"] 147 | } 148 | 149 | ingress { 150 | from_port = 21027 151 | to_port = 21027 152 | protocol = "tcp" 153 | cidr_blocks = ["0.0.0.0/0"] 154 | } 155 | 156 | ingress { 157 | from_port = 22000 158 | to_port = 22000 159 | protocol = "tcp" 160 | cidr_blocks = ["0.0.0.0/0"] 161 | } 162 | 163 | ingress { 164 | from_port = 22000 165 | to_port = 22000 166 | protocol = "udp" 167 | cidr_blocks = ["0.0.0.0/0"] 168 | } 169 | 170 | ingress { 171 | from_port = 51820 172 | to_port = 51820 173 | protocol = "udp" 174 | cidr_blocks = ["0.0.0.0/0"] 175 | } 176 | 177 | ingress { 178 | from_port = 51821 179 | to_port = 51821 180 | protocol = "tcp" 181 | cidr_blocks = ["0.0.0.0/0"] 182 | } 183 | 184 | egress { 185 | from_port = 0 186 | to_port = 0 187 | protocol = "-1" 188 | cidr_blocks = ["0.0.0.0/0"] 189 | } 190 | 191 | tags = { 192 | Name = "tf_sg" 193 | } 194 | } 195 | 196 | data "aws_ami" "tf_ami" { 197 | most_recent = true 198 | owners = ["amazon"] 199 | 200 | filter { 201 | name = "name" 202 | values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"] 203 | } 204 | 205 | filter { 206 | name = "virtualization-type" 207 | values = ["hvm"] 208 | } 209 | 210 | filter { 211 | name = "architecture" 212 | values = ["x86_64"] 213 | } 214 | } 215 | 216 | resource "aws_instance" "tf_instance" { 217 | ami = data.aws_ami.tf_ami.id 218 | instance_type = "t2.micro" 219 | key_name = "ansible_user" 220 | subnet_id = aws_subnet.tf_public_subnet.id 221 | vpc_security_group_ids = [aws_security_group.tf_sg.id] 222 | 223 | tags = { 224 | Name = "tf_instance" 225 | } 226 | } 227 | 228 | output "aws_instance_public_ip" { 229 | value = aws_instance.tf_instance.public_ip 230 | } 231 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ansible homelab 2 | 3 | Ansible playbooks to quickly setup a homelab. These playbooks are designed to be run on a fresh install of Ubuntu/Debian or RedHat based distros (Fedora, CentOS). 4 | The playbook will update the system, install Docker, and then deploy the Docker containers. 5 | 6 | ## Bootstrap script 7 | 8 | I have included a bootstrap script that clones the repo, asks the user for the username and IP address of the server, and then runs the playbook. You can run the script like this: 9 | 10 | ```bash 11 | wget https://raw.githubusercontent.com/rishavnandi/ansible_homelab/master/bootstrap.py && python3 bootstrap.py 12 | ``` 13 | 14 | ## Using Traefik as a reverse proxy 15 | 16 | I have finally switched from using nginx proxy manager to using Traefik as a reverse proxy. I have included a playbook to install Traefik and configure it to work with the apps. 17 | 18 | For traefik to pull certificates I use cloudflare, so you will need to create a cloudflare account and add your domain to it. You will also need to create an API token in cloudflare and add it to the `group_vars/vars.yml` file. 19 | 20 | For the traefik user basic auth, I use a password hash, you can generate a password hash using the `htpasswd` command, for example: 21 | 22 | ```bash 23 | echo $(htpasswd -nB ) | sed -e s/\\$/\\$\\$/g 24 | ``` 25 | 26 | It will prompt you for a password, enter the password and it will output the password hash, copy the output and remove any double `$` characters as they are only needed when using it directly in a docker compose file. Then add the password hash to the `group_vars/vars.yml` file. 27 | 28 | ## Using Authelia as a second factor authentication 29 | 30 | I have also added support for Authelia, which is a second factor authentication service. There are many variables that need to be set for Authelia to work, since I also use the google smtp server to send password reset emails. 31 | 32 | - jwt_secret: Needs to be a random string upto 64 characters I believe 33 | - sqlite_encryption_key: Needs to be a random string atleast 20 characters 34 | - google_mail_id: Of the account which will be used to send the password reset emails 35 | - google_insecure_app_password: You will need to generate an insecure app password for authelia to use, check more details in [Authelia docs](https://www.authelia.com/configuration/notifications/smtp/) 36 | - authelia_admin_mail: email of the authelia admin user 37 | - authelia_admin_argon2id: argon2id hash of the password for admin user, see how to generate one in the [Authelia docs](https://www.authelia.com/reference/guides/passwords/) 38 | 39 | ## Blog post 40 | 41 | I have written a blog post about this repo, you can find it here: [https://www.rishavnandi.com/posts/Ansible_homelab](https://www.rishavnandi.com/posts/Ansible_homelab) 42 | 43 | ## Usage 44 | 45 | - Clone the repo to your local machine 46 | 47 | ```bash 48 | git clone https://github.com/rishavnandi/ansible_homelab.git 49 | ``` 50 | 51 | - Update the inventory file with the IP address of your server and the user you want to use to connect to the server and add the path to your ssh key, incase you are not using ssh keys (you should always use ssh keys for security) then you can replace the `ansible_ssh_private_key_file` with `ansible_ssh_pass` and add the password for the user. 52 | 53 | - Also update the `group_vars/vars.yml` file with the correct variables for your setup, for the pgid and puid, you can find the correct values by running the `id` command on your server and using the values for the `uid` and `gid` fields. 54 | 55 | - Run the playbook 56 | 57 | ```bash 58 | ansible-playbook main.yml 59 | ``` 60 | 61 | You'll notice that for most apps the ports are not exposed, as I prefer exposing only the neccessary ports and for the rest I add them to a custom Docker network and then use nginx proxy manager to access the apps, a benefit of putting all the containers on a custom Docker network is that you can reference them in nginx proxy manager using their container name instead of the IP address, which makes it easier to manage. 62 | 63 | ## Removing unwanted apps 64 | 65 | If you don't want to run some of the apps, you can easily remove them from the `main.yml` file since all the containers are stored as tasks in the tasks folder and are included in the `main.yml` file. 66 | 67 | ## Info about the apps 68 | 69 | If you want to learn more about any of the apps, you can check out the [awesome selfhosted repo](https://github.com/awesome-selfhosted/awesome-selfhosted). 70 | 71 | ## Included Terraform script 72 | 73 | I have included a Terraform script that I use to quickly spin up an AWS instance to run the playbook on. You can use this script to spin up an instance, or you can use it as a reference to create your own Terraform script. 74 | You can find more info about using Terraform with AWS here: [https://learn.hashicorp.com/tutorials/terraform/aws-build](https://learn.hashicorp.com/tutorials/terraform/aws-build) 75 | 76 | ## Goals 77 | 78 | - [x] Add support for Ubuntu 22.04 79 | - [x] Add support for Debian 11 80 | - [x] Add support for RedHat based distros (Fedora, CentOS) 81 | - [x] Find a permanent fix for the Docker install issue on Ubuntu 22.04 82 | 83 | ## Credits 84 | 85 | - [Jeff Geerling](https://www.jeffgeerling.com/) for all the awesome Ansible content 86 | - [linuxserver.io](https://linuxserver.io/) for the Docker containers 87 | - [Ansible docs](https://docs.ansible.com/ansible/latest/) for the Ansible documentation 88 | - [Wolfgang's infra repo](https://github.com/notthebee/infra) for the Docker install fix for Ubuntu 22.04 89 | 90 | ## Star History 91 | 92 | [![Star History Chart](https://api.star-history.com/svg?repos=rishavnandi/ansible_homelab&type=Date)](https://star-history.com/#rishavnandi/ansible_homelab&Date) 93 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | mail@rishavnandi.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | . 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | . Translations are available at 128 | . -------------------------------------------------------------------------------- /tasks/authelia.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create authelia config directory 3 | ansible.builtin.file: 4 | path: "{{ docker_dir }}/authelia/config" 5 | state: directory 6 | mode: "0755" 7 | access_time: preserve 8 | modification_time: preserve 9 | 10 | - name: Create authelia config file 11 | ansible.builtin.copy: 12 | content: | 13 | --- 14 | server: 15 | host: 0.0.0.0 16 | port: 9091 17 | log: 18 | level: debug 19 | theme: dark 20 | # This secret can also be set using the env variables AUTHELIA_JWT_SECRET_FILE 21 | jwt_secret: "{{ jwt_secret }}" 22 | default_redirection_url: "https://auth.{{ domain }}" 23 | totp: 24 | issuer: authelia.com 25 | 26 | authentication_backend: 27 | file: 28 | path: /config/users_database.yml 29 | password: 30 | algorithm: argon2id 31 | iterations: 1 32 | salt_length: 16 33 | parallelism: 8 34 | memory: 64 35 | 36 | access_control: 37 | default_policy: deny 38 | rules: 39 | # Rules applied to everyone 40 | - domain: "traefik.{{ domain }}" 41 | policy: bypass 42 | - domain: "code.{{ domain }}" 43 | policy: two_factor 44 | - domain: "dash.{{ domain }}" 45 | policy: bypass 46 | - domain: "duplicati.{{ domain }}" 47 | policy: two_factor 48 | - domain: "files.{{ domain }}" 49 | policy: two_factor 50 | - domain: "guac.{{ domain }}" 51 | policy: two_factor 52 | - domain: "heimdall.{{ domain }}" 53 | policy: two_factor 54 | - domain: "homarr.{{ domain }}" 55 | policy: two_factor 56 | - domain: "jellyfin.{{ domain }}" 57 | policy: two_factor 58 | - domain: "jellyseerr.{{ domain }}" 59 | policy: two_factor 60 | - domain: "grafana.{{ domain }}" 61 | policy: two_factor 62 | - domain: "n8n.{{ domain }}" 63 | policy: two_factor 64 | - domain: "nextcloud.{{ domain }}" 65 | policy: bypass 66 | - domain: "portainer.{{ domain }}" 67 | policy: two_factor 68 | - domain: "prowlarr.{{ domain }}" 69 | policy: two_factor 70 | - domain: "qbit.{{ domain }}" 71 | policy: two_factor 72 | - domain: "radarr.{{ domain }}" 73 | policy: two_factor 74 | - domain: "requestrr.{{ domain }}" 75 | policy: two_factor 76 | - domain: "sonarr.{{ domain }}" 77 | policy: two_factor 78 | - domain: "sync.{{ domain }}" 79 | policy: two_factor 80 | - domain: "unmanic.{{ domain }}" 81 | policy: two_factor 82 | - domain: "uptime.{{ domain }}" 83 | policy: two_factor 84 | - domain: "vault.{{ domain }}" 85 | policy: bypass 86 | - domain: "wg.{{ domain }}" 87 | policy: two_factor 88 | 89 | session: 90 | name: authelia_session 91 | # This secret can also be set using the env variables AUTHELIA_SESSION_SECRET_FILE 92 | secret: unsecure_session_secret 93 | expiration: 3600 # 1 hour 94 | inactivity: 300 # 5 minutes 95 | domain: "{{ domain }}" # Should match whatever your root protected domain is 96 | 97 | regulation: 98 | max_retries: 3 99 | find_time: 120 100 | ban_time: 300 101 | 102 | storage: 103 | encryption_key: "{{ authelia_sqlite_encryption_key }}" 104 | local: 105 | path: /config/db.sqlite3 106 | 107 | notifier: 108 | smtp: 109 | username: "{{ google_mail }}" 110 | # Password can also be set using a secret: https://www.authelia.com/configuration/methods/secrets/ 111 | password: "{{ google_insecure_app_pass }}" 112 | sender: "authelia@{{ domain }}" 113 | host: smtp.gmail.com 114 | port: 587 115 | ... 116 | dest: "{{ docker_dir }}/authelia/config/configuration.yml" 117 | mode: "0644" 118 | owner: "{{ username }}" 119 | group: "{{ username }}" 120 | changed_when: false 121 | 122 | - name: Create authelia users database file 123 | ansible.builtin.copy: 124 | content: | 125 | --- 126 | users: 127 | admin: 128 | displayname: admin 129 | email: "{{ authelia_admin_mail }}" 130 | password: "{{ authelia_admin_argon2id_pass }}" 131 | groups: 132 | - admins 133 | - dev 134 | ... 135 | dest: "{{ docker_dir }}/authelia/config/users_database.yml" 136 | mode: "0644" 137 | owner: "{{ username }}" 138 | group: "{{ username }}" 139 | changed_when: false 140 | 141 | - name: Create authelia container 142 | community.docker.docker_container: 143 | name: authelia 144 | image: authelia/authelia:latest 145 | state: started 146 | restart_policy: unless-stopped 147 | volumes: 148 | - "{{ docker_dir }}/authelia/config:/config" 149 | networks: 150 | - name: homelab 151 | labels: 152 | traefik.enable: "true" 153 | traefik.http.routers.authelia.rule: "Host(`auth.{{ domain }}`)" 154 | traefik.http.routers.authelia.entrypoints: "https" 155 | traefik.http.routers.authelia.tls: "true" 156 | traefik.http.middlewares.authelia.forwardauth.address: "http://authelia:9091/api/verify?rd=https://auth.{{ domain }}" 157 | traefik.http.middlewares.authelia.forwardauth.trustForwardHeader: "true" 158 | traefik.http.middlewares.authelia.forwardauth.authResponseHeaders: "Remote-User, Remote-Groups, Remote-Name, Remote-Email" 159 | exposed_ports: 160 | - "9091" 161 | env: 162 | TZ: "{{ timezone }}" 163 | --------------------------------------------------------------------------------