diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..6867287 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,95 @@ +--- +name: CI + +'on': + push: {} + pull_request: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + ANSIBLE_FORCE_COLOR: '1' + ANSIBLE_CONFIG: ci/ansible.cfg + +jobs: + lint: + name: Lint code + runs-on: ubuntu-latest + steps: + - name: Check out sources + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: Install Python dependencies + run: python3 -m pip install -r requirements.txt + + - name: Install Ansible Galaxy dependencies + run: ansible-galaxy install -r requirements.yml + + - name: Run YAML linter + run: yamllint . + + - name: Run Ansible syntax check + run: ansible-playbook main.yml --syntax-check + + - name: Run Ansible linter + run: ansible-lint + + run-playbook: + name: Run playbook + runs-on: ubuntu-24.04 + steps: + - name: Check out sources + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + cache: pip + + - name: Install Python dependencies + run: python3 -m pip install -r requirements.txt + + - name: Install Ansible Galaxy dependencies + run: ansible-galaxy install -r requirements.yml + + - name: Remove encrypted secrets + run: grep -lZ -e '^\$ANSIBLE_VAULT' group_vars/*/*.yml | xargs -0 rm -f + + - name: Use sample config + run: cp -f config.yml.sample config.yml + + - name: Run playbook (first run) + run: ansible-playbook main.yml --extra-vars '@ci/config_overrides.yml' + + - name: Run playbook (second run) + run: ansible-playbook main.yml --extra-vars '@ci/config_overrides.yml' | tee /tmp/secondrun.log + + - name: Upload second run output as artifact + uses: actions/upload-artifact@v4 + with: + name: second-run-output + path: /tmp/secondrun.log + + idempotence: + name: Check idempotence + runs-on: ubuntu-latest + needs: run-playbook + steps: + - name: Download second run output artifact + uses: actions/download-artifact@v4 + with: + name: second-run-output + + - name: Check idempotence + run: >- + grep -A1 -e 'PLAY RECAP' secondrun.log | grep -q -e 'changed=0.*failed=0' + && (echo 'Idempotence test: pass' && exit 0) + || (echo 'Idempotence test: fail' && exit 1) diff --git a/.gitignore b/.gitignore index e92c364..d61edb2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ keys/* !keys/README.md -inventory +/inventory .facts_cache vault_password.txt venv/ diff --git a/.yamlignore b/.yamlignore index e945103..4337604 100644 --- a/.yamlignore +++ b/.yamlignore @@ -1 +1,3 @@ group_vars/homeserver/secrets.yml +group_vars/homeserver/prometheus_scrape_configs.yml +roles/monitoring/templates/prometheus.yml diff --git a/README.md b/README.md index 4294139..2b4f3e6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Home server playbook +[![CI](https://github.com/kdkasad/home-server/actions/workflows/ci.yml/badge.svg)](https://github.com/kdkasad/home-server/actions/workflows/ci.yml) + An Ansible playbook to manage my home server, a mini-PC running Debian. diff --git a/ansible.cfg b/ansible.cfg index d3539ab..4dee559 100644 --- a/ansible.cfg +++ b/ansible.cfg @@ -1,5 +1,8 @@ [defaults] +# Look for custom module(s) in library directory +library = ./library + # Set inventory file inventory = inventory @@ -16,3 +19,10 @@ vault_password_file = ./vault_password.txt # Print output/errors in human-readable format stdout_callback = ansible.posix.debug + +# Print timestamp for each task +;callbacks_enabled = ansible.posix.profile_tasks + +[callback_profile_tasks] +# Don't show summary of longest-running tasks +;task_output_limit = 0 diff --git a/ci/ansible.cfg b/ci/ansible.cfg new file mode 100644 index 0000000..0f64d19 --- /dev/null +++ b/ci/ansible.cfg @@ -0,0 +1,10 @@ +[defaults] +nocows = True +library = ../library +inventory = inventory +stdout_callback = ansible.posix.debug +strategy = free +callbacks_enabled = ansible.posix.profile_tasks + +[callback_profile_tasks] +task_output_limit = 0 diff --git a/ci/config_overrides.yml b/ci/config_overrides.yml new file mode 100644 index 0000000..50e0c79 --- /dev/null +++ b/ci/config_overrides.yml @@ -0,0 +1,41 @@ +--- +storage: + data_dir: /data + nas_dir: /nas + docker_dir: /var/lib/docker + + # Don't create LVM volumes since we don't control the disk setup + vg: pool + volumes: {} + +dnsmasq_port: 54 + +# Jellyfin takes too long to start on the GitHub Actions runner. +# TODO: Figure out why this is the case. +jellyfin_skip_setup: yes + + +########### +# SECRETS # +########### + +minio_root_user: "minio" +minio_root_password: "minio" + +secret_authentik_pg_pass: authentik +secret_authentik_bootstrap_email: authentik +secret_authentik_bootstrap_password: authentik + +secret_homarr_oidc_client_id: homarrclientid +secret_homarr_oidc_client_secret: homarrclientsecret + +secret_minio_openid_client_id: minioclientid +secret_minio_openid_client_secret: minioclientsecret + +secret_jellyfin_admin_username: jellyfin +secret_jellyfin_admin_password: jellyfin +secret_jellyfin_oidc_client_id: jellyfinclientid +secret_jellyfin_oidc_client_secret: jellyfinclientsecret + +secret_monitoring_grafana_client_id: grafanaclientid +secret_monitoring_grafana_client_secret: grafanaclientsecret diff --git a/ci/inventory b/ci/inventory new file mode 100644 index 0000000..0924397 --- /dev/null +++ b/ci/inventory @@ -0,0 +1,2 @@ +[homeserver] +localhost ansible_connection=local diff --git a/config.yml b/config.yml index cd7c814..bee6aea 100644 --- a/config.yml +++ b/config.yml @@ -13,9 +13,19 @@ general: # Set the timezone timezone: America/Los_Angeles - # Only automatically install security updates + # Configure which package updates are installed automatically + # If set to "security", only security updates will be installed + # If set to "all", all updates will be installed + # If set to "none", no updates will be installed auto_update_packages: security + # DNS servers to use for non-local lookups + upstream_dns_servers: + - '1.1.1.1' + - '1.0.0.1' + - '2606:4700:4700::1111' + - '2606:4700:4700::1001' + users: worker: worker nas: nas @@ -244,6 +254,7 @@ homarr_env: AUTH_OIDC_CLIENT_NAME: Authentik AUTH_OIDC_CLIENT_ID: "{{ secret_homarr_oidc_client_id }}" AUTH_OIDC_CLIENT_SECRET: "{{ secret_homarr_oidc_client_secret }}" + AUTH_OIDC_SCOPE_OVERWRITE: openid email profile groups AUTH_OIDC_ADMIN_GROUP: Administrators AUTH_OIDC_URI: >- https://{{ authentik_routing.subdomain | default('auth') }}.{{ general.domain }}/application/o/homarr diff --git a/config.yml.sample b/config.yml.sample index 88ef407..285be9a 100644 --- a/config.yml.sample +++ b/config.yml.sample @@ -14,11 +14,19 @@ general: # Set the timezone timezone: Etc/UTC - # Whether to auto-update packages. - # Can be "all" for all updates, "security" for security updates only, or - # "none" for no updates + # Configure which package updates are installed automatically + # If set to "security", only security updates will be installed + # If set to "all", all updates will be installed + # If set to "none", no updates will be installed auto_update_packages: security + # DNS servers to use for non-local lookups + upstream_dns_servers: + - '1.1.1.1' + - '1.0.0.1' + - '2606:4700:4700::1111' + - '2606:4700:4700::1001' + users: worker: worker nas: nas @@ -62,7 +70,7 @@ storage: # DDNS options # ################ -ddns_enabled: yes +ddns_enabled: no # Cloudflare zone to which the server's domain belongs ddns_cloudflare_zone: example.com @@ -76,12 +84,13 @@ ddns_cloudflare_api_token: # DNS server options # ###################### -dnsmasq_enabled: false +dnsmasq_enabled: yes dnsmasq_custom_hosts: - name: "{{ general.domain }}" ip: "{{ ansible_default_ipv4.address }}" - + - name: ".{{ general.domain }}" + ip: "{{ ansible_default_ipv4.address }}" ################# # Samba options # @@ -120,24 +129,50 @@ samba_shares: # Minecraft server options # ############################ -minecraft_server_enabled: no - +minecraft_server_enabled: yes +minecraft_server_env: + # Server properties + SERVER_PORT: "25565" + ENFORCE_SECURE_PROFILE: "false" + ONLINE_MODE: "true" + ENABLE_COMMAND_BLOCK: "true" + ENABLE_QUERY: "true" + + # Enable automatically pausing when nobody is online + EXEC_DIRECTLY: "false" + ENABLE_AUTOPAUSE: "true" + MAX_TICK_TIME: "-1" # See https://docker-minecraft-server.readthedocs.io/en/latest/misc/autopause-autostop/autopause/ + AUTOPAUSE_TIMEOUT_EST: "600" # Pause 10 minutes after last player disconnects + AUTOPAUSE_TIMEOUT_INIT: "60" # Pause 1 minute after startup if inactive + + # Server settings + TYPE: fabric + VERSION: "1.20.4" + INIT_MEMORY: "512M" + MAX_MEMORY: "1G" ###################### # Authentik settings # ###################### # After Authentik is enabled, go to https://:9443/if/flow/initial-setup/ -authentik_enabled: no +authentik_enabled: yes # Traefik routing options for Authentik authentik_routing: enabled: true subdomain: authentik -# See https://docs.goauthentik.io/docs/installation/configuration for documentation +# See https://docs.goauthentik.io/docs/installation/configuration for documentation. +# Use Ansible Vault to encrypt the secret_* variables. authentik_env: - PG_PASS: "{{ undef }}" # Use Ansible Vault to store an encrypted password here + # PostgreSQL root password + PG_PASS: "{{ secret_authentik_pg_pass }}" + + # Credentials for the Authentik "akadmin" user during initial setup. + # Use Ansible Vault to store an encrypted password here. + AUTHENTIK_BOOTSTRAP_EMAIL: "{{ secret_authentik_bootstrap_email }}" + AUTHENTIK_BOOTSTRAP_PASSWORD: "{{ secret_authentik_bootstrap_password }}" #################### @@ -154,7 +189,7 @@ traefik_ports: # See https://doc.traefik.io/traefik/https/acme/ for information on how to # configure the provider and environment variables. traefik_acme_dns: - enabled: yes + enabled: no provider: cloudflare # Email for Let's Encrypt, used to receive expiry notifications. @@ -181,7 +216,7 @@ tailscale_routes: - "10.0.0.0/24" # Use Ansible Vault to put your encrypted auth key here -tailscale_auth_key: "{{ undef }}" +tailscale_auth_key: "{{ undef() }}" ################### @@ -193,6 +228,19 @@ homarr_routing: enabled: true homepage: true +# OIDC settings for Authentik +homarr_env: + AUTH_PROVIDER: oidc + AUTH_OIDC_CLIENT_NAME: Authentik + AUTH_OIDC_CLIENT_ID: "{{ secret_homarr_oidc_client_id }}" + AUTH_OIDC_CLIENT_SECRET: "{{ secret_homarr_oidc_client_secret }}" + AUTH_OIDC_SCOPE_OVERWRITE: openid email profile groups + AUTH_OIDC_ADMIN_GROUP: Administrators + AUTH_OIDC_URI: >- + https://{{ authentik_routing.subdomain | default('auth') }}.{{ general.domain }}/application/o/homarr + AUTH_LOGOUT_REDIRECT_URL: >- + https://{{ authentik_routing.subdomain | default('auth') }}.{{ general.domain }}/application/o/homarr/end-session/ + ################## # Minio settings # @@ -206,15 +254,47 @@ minio_routing: ui_subdomain: minio # Use Ansible Vault to put your encrypted credentials here -minio_root_user: "{{ undef }}" -minio_root_password: "{{ undef }}" +minio_root_user: "{{ undef() }}" +minio_root_password: "{{ undef() }}" + +# Environment variables to configure integration with Authentik using OpenID Connect. +# Currently, this requires manual setup in the Authentik UI. +# Use Ansible Vault to put your encrypted client ID and secret below. +minio_env: + MINIO_IDENTITY_OPENID_CONFIG_URL: "https://{{ authentik_routing.subdomain | default('auth') }}.{{ general.domain }}\ + /application/o/minio/.well-known/openid-configuration" + MINIO_IDENTITY_OPENID_CLIENT_ID: "{{ secret_minio_openid_client_id }}" + MINIO_IDENTITY_OPENID_CLIENT_SECRET: "{{ secret_minio_openid_client_secret }}" + MINIO_IDENTITY_OPENID_CLAIM_NAME: policy + MINIO_IDENTITY_OPENID_DISPLAY_NAME: Log in with Authentik + MINIO_IDENTITY_OPENID_SCOPES: openid,profile,email,minio + MINIO_IDENTITY_OPENID_REDIRECT_URI: >- + {{ minio_routing.ui_subdomain | default('minio') }}.{{ general.domain }}/oauth_callback + MINIO_IDENTITY_OPENID_REDIRECT_URI_DYNAMIC: "on" + MINIO_IDENTITY_OPENID_CLAIM_USERINFO: "on" + MINIO_IDENTITY_OPENID_COMMENT: "Authentik" + + +###################### +# Bitwarden settings # +###################### + +bitwarden_enabled: true ##################### # Jellyfin settings # ##################### -jellyfin_enabled: false +jellyfin_enabled: yes + +jellyfin_enable_vaapi: false + +jellyfin_admin_username: "{{ secret_jellyfin_admin_username }}" +jellyfin_admin_password: "{{ secret_jellyfin_admin_password }}" + +jellyfin_oidc_client_id: "{{ secret_jellyfin_oidc_client_id }}" +jellyfin_oidc_client_secret: "{{ secret_jellyfin_oidc_client_secret }}" ####################### @@ -222,3 +302,14 @@ jellyfin_enabled: false ####################### monitoring_enabled: yes + +monitoring_prometheus_routing_private: yes +monitoring_grafana_routing_private: no + +# Grafana SSO settings +monitoring_grafana_sso: yes +monitoring_grafana_sso_only: yes +monitoring_grafana_client_id: "{{ secret_monitoring_grafana_client_id }}" +monitoring_grafana_client_secret: "{{ secret_monitoring_grafana_client_secret }}" + +# vim: ft=yaml diff --git a/main.yml b/main.yml index 61d641a..71a8e18 100644 --- a/main.yml +++ b/main.yml @@ -4,41 +4,55 @@ become: true roles: - - role: setup-general + - role: setup_general tags: - setup - - role: setup-disks + - role: setup_disks tags: - setup - nas - - role: setup-users + - role: setup_users tags: - setup - nas - - role: setup-ssh + - role: setup_dns tags: - setup + - dns - - role: setup-docker + - role: setup_ssh tags: - - docker + - setup - - role: ddns + - role: setup_docker tags: - - ddns + - docker - role: dnsmasq tags: - dnsmasq + - dns - role: vladgh.samba.server tags: - nas - - role: minecraft-server + # This must happen after Samba is installed, because wsdd2.service depends + # on smbd.service. + - role: setup_zeroconf + tags: + - setup + - dns + + - role: ddns + tags: + - ddns + - dns + + - role: minecraft_server tags: - minecraft diff --git a/roles/ddns/tasks/main.yml b/roles/ddns/tasks/main.yml index 279cc1a..98f1ca2 100644 --- a/roles/ddns/tasks/main.yml +++ b/roles/ddns/tasks/main.yml @@ -25,7 +25,11 @@ - name: Disable ddclient when: ddns_enabled is false block: + - name: Get service facts + ansible.builtin.service_facts: {} + - name: Stop & disable ddclient service + when: "'ddclient.service' in services" ansible.builtin.service: name: ddclient state: stopped diff --git a/roles/dnsmasq/defaults/main.yml b/roles/dnsmasq/defaults/main.yml index 297ab9d..9ef290d 100644 --- a/roles/dnsmasq/defaults/main.yml +++ b/roles/dnsmasq/defaults/main.yml @@ -5,12 +5,11 @@ dnsmasq_enabled: false # Set data directory dnsmasq_data_dir: "{{ storage.data_dir }}/dnsmasq" +# Set the port to listen on +dnsmasq_port: 53 + # Use Cloudflare's DNS servers by default -dnsmasq_upstream_dns_servers: - - '1.1.1.1' - - '1.0.0.1' - - '2606:4700:4700::1111' - - '2606:4700:4700::1001' +dnsmasq_upstream_dns_servers: "{{ general.upstream_dns_servers | mandatory }}" # Domains to treat as LAN-local dnsmasq_local_domain_suffix: local diff --git a/roles/dnsmasq/handlers/main.yml b/roles/dnsmasq/handlers/main.yml deleted file mode 100644 index 134f755..0000000 --- a/roles/dnsmasq/handlers/main.yml +++ /dev/null @@ -1,12 +0,0 @@ ---- -- name: Restart dnsmasq container - community.docker.docker_container: - name: dnsmasq - state: started - restart: true - -- name: Regenerate resolv.conf - ansible.builtin.command: - cmd: resolvconf -u - changed_when: true - become: true diff --git a/roles/dnsmasq/tasks/main.yml b/roles/dnsmasq/tasks/main.yml index 1cade7f..e8a17ea 100644 --- a/roles/dnsmasq/tasks/main.yml +++ b/roles/dnsmasq/tasks/main.yml @@ -17,7 +17,7 @@ owner: "{{ users.worker }}" group: "{{ users.worker }}" mode: '0640' - notify: Restart dnsmasq container + register: dnsmasq_config - name: Start dnsmasq community.docker.docker_container: @@ -26,10 +26,11 @@ pull: always image_name_mismatch: recreate state: started + restart: "{{ dnsmasq_config.changed }}" restart_policy: unless-stopped ports: - - "53:53/udp" - - "53:53/tcp" + - "{{ dnsmasq_port }}:{{ dnsmasq_port }}/udp" + - "{{ dnsmasq_port }}:{{ dnsmasq_port }}/tcp" volumes: - "{{ dnsmasq_data_dir }}/dnsmasq.conf:/etc/dnsmasq.conf:ro" capabilities: @@ -38,30 +39,6 @@ networks: - name: subway - - name: Use dnsmasq for our own DNS lookups - block: - - name: Ensure resolvconf is installed - ansible.builtin.package: - name: resolvconf - state: present - - - name: Set /etc/resolv.conf head contents - ansible.builtin.lineinfile: - path: /etc/resolvconf/resolv.conf.d/head - create: true - owner: root - group: root - mode: '0644' - line: nameserver 127.0.0.1 - regexp: "^nameserver\\s+(127\\.0\\.0\\.1|::1|localhost)" - insertafter: '^#' - notify: Regenerate resolv.conf - - - name: Ensure resolvconf is running - ansible.builtin.service: - name: resolvconf - state: started - - name: Disable dnsmasq when: dnsmasq_enabled is false block: diff --git a/roles/dnsmasq/templates/dnsmasq.conf b/roles/dnsmasq/templates/dnsmasq.conf index 96453fe..e83a341 100644 --- a/roles/dnsmasq/templates/dnsmasq.conf +++ b/roles/dnsmasq/templates/dnsmasq.conf @@ -1,7 +1,7 @@ # Listen on this specific port instead of the standard DNS port # (53). Setting this to zero completely disables DNS function, # leaving only DHCP and/or TFTP. -port=53 +port={{ dnsmasq_port }} # Don't provide DHCP services. Empty value means all interfaces. no-dhcp-interface= diff --git a/roles/jellyfin/defaults/main.yml b/roles/jellyfin/defaults/main.yml index 2308e9e..769b1ee 100644 --- a/roles/jellyfin/defaults/main.yml +++ b/roles/jellyfin/defaults/main.yml @@ -12,6 +12,10 @@ jellyfin_routing: enabled: true subdomain: jellyfin +# Whether to skip the initial Jellyfin setup. If set, you must manually set up +# Jellyfin using the web UI. +jellyfin_skip_setup: no + # Set the server name jellyfin_server_name: Jellyfin Media Server @@ -22,8 +26,8 @@ jellyfin_server_name: Jellyfin Media Server jellyfin_sso_only: true # OIDC client ID and secret for Jellyfin-Authentik integration. -jellyfin_oidc_client_id: "{{ undef }}" -jellyfin_oidc_client_secret: "{{ undef }}" +jellyfin_oidc_client_id: "{{ undef() }}" +jellyfin_oidc_client_secret: "{{ undef() }}" # Version/tag of the lscr.io/linuxserver/jellyfin image to use jellyfin_tag: "10.9.7" diff --git a/roles/jellyfin/tasks/main.yml b/roles/jellyfin/tasks/main.yml index 41cf3cb..4846230 100644 --- a/roles/jellyfin/tasks/main.yml +++ b/roles/jellyfin/tasks/main.yml @@ -94,16 +94,8 @@ networks: - name: subway - - name: Enable metrics for Jellyfin # noqa no-handler - ansible.builtin.lineinfile: - state: present - path: "{{ jellyfin_data_dir }}/config/system.xml" - line: " true" - regex: "^\\s*[^<]+\\s*$" - notify: Restart Jellyfin container - - name: Perform initial Jellyfin setup - when: not config_dir.stat.exists + when: (not config_dir.stat.exists) and (not jellyfin_skip_setup) jellyfin_setup: server_name: "{{ jellyfin_server_name }}" admin_username: "{{ jellyfin_admin_username }}" @@ -114,6 +106,18 @@ authentik_domain: "{{ authentik_routing.subdomain | default('authentik') }}.{{ general.domain }}" login_sso_only: "{{ jellyfin_sso_only }}" + - name: Enable metrics for Jellyfin + ansible.builtin.lineinfile: + state: present + path: "{{ jellyfin_data_dir }}/config/system.xml" + line: " true" + regex: "^\\s*[^<]+\\s*$" + notify: Restart Jellyfin container + register: enable_metrics + until: enable_metrics is success + retries: 24 + delay: 5 + - name: Disable Jellyfin when: jellyfin_enabled is false block: diff --git a/roles/minecraft-server/LICENSE b/roles/minecraft_server/LICENSE similarity index 94% rename from roles/minecraft-server/LICENSE rename to roles/minecraft_server/LICENSE index b89daa5..2f47cbe 100644 --- a/roles/minecraft-server/LICENSE +++ b/roles/minecraft_server/LICENSE @@ -1,4 +1,4 @@ -This subdirectory, roles/minecraft-server, is subject to the following license. +This subdirectory, roles/minecraft_server, is subject to the following license. MIT License diff --git a/roles/minecraft-server/defaults/main.yml b/roles/minecraft_server/defaults/main.yml similarity index 100% rename from roles/minecraft-server/defaults/main.yml rename to roles/minecraft_server/defaults/main.yml diff --git a/roles/minecraft-server/tasks/main.yml b/roles/minecraft_server/tasks/main.yml similarity index 100% rename from roles/minecraft-server/tasks/main.yml rename to roles/minecraft_server/tasks/main.yml diff --git a/roles/monitoring/defaults/main.yml b/roles/monitoring/defaults/main.yml index 49895dd..23018f6 100644 --- a/roles/monitoring/defaults/main.yml +++ b/roles/monitoring/defaults/main.yml @@ -12,7 +12,7 @@ monitoring_prometheus_tag: v2.53.1 monitoring_grafana_tag: "11.1.0" monitoring_grafana_image_renderer_tag: "3.11.1" monitoring_cadvisor_tag: v0.49.1 -monitoring_loki_tag: "3.1.0" # Be sure to also update the Loki driver in the setup-docker role +monitoring_loki_tag: "3.1.0" # Be sure to also update the Loki driver in the setup_docker role monitoring_promtail_tag: "{{ monitoring_loki_tag }}" monitoring_prometheus_routing_enabled: true diff --git a/roles/monitoring/templates/docker-compose.yml b/roles/monitoring/templates/docker-compose.yml index 37f4520..2b08b70 100644 --- a/roles/monitoring/templates/docker-compose.yml +++ b/roles/monitoring/templates/docker-compose.yml @@ -22,8 +22,10 @@ services: - "{{ monitoring_prometheus_data_dir }}/data:/prometheus" labels: traefik.enable: "{{ monitoring_prometheus_routing_enabled | ternary('true', 'false') }}" - traefik.http.routers.prometheus.rule: Host(`{{ monitoring_prometheus_routing_subdomain }}.{{ general.domain }}`) - traefik.http.routers.prometheus.middlewares: "{{ monitoring_prometheus_routing_private | ternary('privateip@file', '') }}" + traefik.http.routers.prometheus.rule: >- + Host(`{{ monitoring_prometheus_routing_subdomain }}.{{ general.domain }}`) + traefik.http.routers.prometheus.middlewares: >- + {{ monitoring_prometheus_routing_private | ternary('privateip@file', '') }} traefik.http.routers.prometheus.entrypoints: websecure traefik.http.services.prometheus.loadbalancer.server.port: "9090" networks: @@ -47,7 +49,8 @@ services: labels: traefik.enable: "{{ monitoring_grafana_routing_enabled | ternary('true', 'false')}}" traefik.http.routers.grafana.rule: Host(`{{ monitoring_grafana_routing_subdomain }}.{{ general.domain }}`) - traefik.http.routers.grafana.middlewares: "{{ monitoring_grafana_routing_private | ternary('privateip@file', '') }}" + traefik.http.routers.grafana.middlewares: >- + {{ monitoring_grafana_routing_private | ternary('privateip@file', '') }} traefik.http.routers.grafana.entrypoints: websecure traefik.http.services.grafana.loadbalancer.server.port: "3000" networks: @@ -126,7 +129,6 @@ services: - "/var/log:/var/log:ro,rslave" - "/run/log/journal:/run/log/journal:ro,rslave" # For systemd-journal scraper - "/etc/machine-id:/etc/machine-id:ro" # For systemd-journal scraper - # - "{{ storage.docker_dir | default('/var/lib/docker' ) }}/containers:/var/lib/docker/containers:ro" # For Docker log files - "/var/run/docker.sock:/var/run/docker.sock:ro" # For Docker container metadata # Don't include the subway network, because only Loki needs access to # this service diff --git a/roles/monitoring/templates/loki.yml b/roles/monitoring/templates/loki.yml index a1c9beb..50256f9 100644 --- a/roles/monitoring/templates/loki.yml +++ b/roles/monitoring/templates/loki.yml @@ -1,3 +1,4 @@ +--- auth_enabled: false server: @@ -55,5 +56,5 @@ limits_config: # Refer to the buildReport method to see what goes into a report. # # If you would like to disable reporting, uncomment the following lines: -#analytics: -# reporting_enabled: false +# analytics: +# reporting_enabled: false diff --git a/roles/monitoring/templates/promtail.yml b/roles/monitoring/templates/promtail.yml index b429cc7..f6619be 100644 --- a/roles/monitoring/templates/promtail.yml +++ b/roles/monitoring/templates/promtail.yml @@ -1,3 +1,4 @@ +--- server: http_listen_port: 80 grpc_listen_port: 0 diff --git a/roles/setup-disks/meta/main.yml b/roles/setup-disks/meta/main.yml deleted file mode 100644 index 23d65c7..0000000 --- a/roles/setup-disks/meta/main.yml +++ /dev/null @@ -1,2 +0,0 @@ ---- -dependencies: [] diff --git a/roles/setup-general/defaults/main.yml b/roles/setup-general/defaults/main.yml deleted file mode 100644 index fb5a8a4..0000000 --- a/roles/setup-general/defaults/main.yml +++ /dev/null @@ -1,9 +0,0 @@ ---- - -general: - - # Configure which package updates are installed automatically - # If set to "security", only security updates will be installed - # If set to "all", all updates will be installed - # If set to "none", no updates will be installed - auto_update_packages: security diff --git a/roles/setup-general/meta/main.yml b/roles/setup-general/meta/main.yml deleted file mode 100644 index e69de29..0000000 diff --git a/roles/setup-users/meta/main.yml b/roles/setup-users/meta/main.yml deleted file mode 100644 index 23d65c7..0000000 --- a/roles/setup-users/meta/main.yml +++ /dev/null @@ -1,2 +0,0 @@ ---- -dependencies: [] diff --git a/roles/setup-disks/tasks/main.yml b/roles/setup_disks/tasks/main.yml similarity index 100% rename from roles/setup-disks/tasks/main.yml rename to roles/setup_disks/tasks/main.yml diff --git a/roles/setup_dns/defaults/main.yml b/roles/setup_dns/defaults/main.yml new file mode 100644 index 0000000..12c1852 --- /dev/null +++ b/roles/setup_dns/defaults/main.yml @@ -0,0 +1,2 @@ +--- +setup_dns_upstream_dns_servers: "{{ general.upstream_dns_servers | mandatory }}" diff --git a/roles/setup_dns/tasks/main.yml b/roles/setup_dns/tasks/main.yml new file mode 100644 index 0000000..47286c1 --- /dev/null +++ b/roles/setup_dns/tasks/main.yml @@ -0,0 +1,35 @@ +--- +- name: Install systemd-resolved + ansible.builtin.package: + name: systemd-resolved + state: present + +- name: Ensure resolvconf is uninstalled + ansible.builtin.package: + name: resolvconf + state: absent + +- name: Template systemd-resolved configuration + ansible.builtin.template: + src: resolved.conf + dest: /etc/systemd/resolved.conf + owner: root + group: root + mode: '0644' + register: resolved_conf + +- name: Get service facts + ansible.builtin.service_facts: {} + +- name: Ensure resolvconf is not running + when: "'resolvconf.service' in ansible_facts['services']" + ansible.builtin.service: + name: resolvconf + state: stopped + enabled: no + +- name: Start & enable systemd-resolved + ansible.builtin.service: + name: systemd-resolved + state: "{{ resolved_conf.changed | ternary('restarted', 'started') }}" + enabled: yes diff --git a/roles/setup_dns/templates/resolved.conf b/roles/setup_dns/templates/resolved.conf new file mode 100644 index 0000000..a67c57f --- /dev/null +++ b/roles/setup_dns/templates/resolved.conf @@ -0,0 +1,13 @@ +# See resolved.conf(5) for documentation + +[Resolve] +DNS={{ dnsmasq_enabled | default(false) | ternary('[::1]:' ~ (dnsmasq_port | default(53)) ~ ' 127.0.0.1:' ~ (dnsmasq_port | default(53)), '') }} {{ setup_dns_upstream_dns_servers | join(' ') }} +FallbackDNS={{ setup_dns_upstream_dns_servers | join(' ') }} +Domains=~. +DNSStubListener=no +LLMNR=resolve +MulticastDNS=no # Avahi does this +DNSSEC=no +DNSOverTLS=no +Cache=yes +CacheFromLocalhost=no # dnsmasq will cache for us diff --git a/roles/setup-docker/defaults/main.yml b/roles/setup_docker/defaults/main.yml similarity index 100% rename from roles/setup-docker/defaults/main.yml rename to roles/setup_docker/defaults/main.yml diff --git a/roles/setup-docker/tasks/main.yml b/roles/setup_docker/tasks/main.yml similarity index 100% rename from roles/setup-docker/tasks/main.yml rename to roles/setup_docker/tasks/main.yml diff --git a/roles/setup-general/handlers/main.yml b/roles/setup_general/handlers/main.yml similarity index 55% rename from roles/setup-general/handlers/main.yml rename to roles/setup_general/handlers/main.yml index 528044c..b729f3b 100644 --- a/roles/setup-general/handlers/main.yml +++ b/roles/setup_general/handlers/main.yml @@ -1,9 +1,4 @@ --- -- name: Restart Avahi daemon - ansible.builtin.service: - name: avahi-daemon - state: restarted - - name: Restart unattended upgrades service ansible.builtin.service: name: unattended-upgrades diff --git a/roles/setup-general/tasks/main.yml b/roles/setup_general/tasks/main.yml similarity index 58% rename from roles/setup-general/tasks/main.yml rename to roles/setup_general/tasks/main.yml index 4e3ce83..a0af0cb 100644 --- a/roles/setup-general/tasks/main.yml +++ b/roles/setup_general/tasks/main.yml @@ -7,42 +7,14 @@ community.general.timezone: name: "{{ general.timezone }}" -# TODO: Extract Avahi setup to a separate setup-network role -- name: Install network discovery service daemons +- name: Install unattended upgrades package ansible.builtin.package: - name: - - avahi-daemon # for mDNS - - wsdd2 # for WSD and LLMNR + name: unattended-upgrades state: present -- name: Enable and start Avahi daemon - ansible.builtin.service: - name: avahi-daemon - enabled: yes - state: started - -- name: Enable and start WSDD - ansible.builtin.service: - name: wsdd2 - enabled: yes - state: started - -- name: Configure Avahi daemon - ansible.builtin.template: - src: templates/avahi-daemon.conf - dest: /etc/avahi/avahi-daemon.conf - owner: root - group: root - mode: '0644' - notify: Restart Avahi daemon - - name: Configure unattended upgrades when: ansible_facts['distribution'] == "Debian" and general.auto_update_packages != "none" block: - - name: Install unattended upgrades package - ansible.builtin.package: - name: unattended-upgrades - state: present - name: Copy unattended upgrades configuration file ansible.builtin.template: diff --git a/roles/setup-general/templates/unattended-upgrades.conf b/roles/setup_general/templates/unattended-upgrades.conf similarity index 100% rename from roles/setup-general/templates/unattended-upgrades.conf rename to roles/setup_general/templates/unattended-upgrades.conf diff --git a/roles/setup-ssh/files/sshd_config b/roles/setup_ssh/files/sshd_config similarity index 100% rename from roles/setup-ssh/files/sshd_config rename to roles/setup_ssh/files/sshd_config diff --git a/roles/setup-ssh/handlers/main.yml b/roles/setup_ssh/handlers/main.yml similarity index 100% rename from roles/setup-ssh/handlers/main.yml rename to roles/setup_ssh/handlers/main.yml diff --git a/roles/setup-ssh/tasks/main.yml b/roles/setup_ssh/tasks/main.yml similarity index 100% rename from roles/setup-ssh/tasks/main.yml rename to roles/setup_ssh/tasks/main.yml diff --git a/roles/setup-users/tasks/main.yml b/roles/setup_users/tasks/main.yml similarity index 100% rename from roles/setup-users/tasks/main.yml rename to roles/setup_users/tasks/main.yml diff --git a/roles/setup_zeroconf/tasks/main.yml b/roles/setup_zeroconf/tasks/main.yml new file mode 100644 index 0000000..8051d1c --- /dev/null +++ b/roles/setup_zeroconf/tasks/main.yml @@ -0,0 +1,28 @@ +--- +- name: Install network discovery service daemons + ansible.builtin.package: + name: + - avahi-daemon # for mDNS + - wsdd2 # for WSD and LLMNR + state: present + +- name: Configure Avahi daemon + ansible.builtin.template: + src: templates/avahi-daemon.conf + dest: /etc/avahi/avahi-daemon.conf + owner: root + group: root + mode: '0644' + register: avahi_conf + +- name: Enable and start Avahi daemon + ansible.builtin.service: + name: avahi-daemon + enabled: yes + state: "{{ avahi_conf.changed | ternary('restarted', 'started') }}" + +- name: Enable and start WSDD + ansible.builtin.service: + name: wsdd2 + enabled: yes + state: started diff --git a/roles/setup-general/templates/avahi-daemon.conf b/roles/setup_zeroconf/templates/avahi-daemon.conf similarity index 100% rename from roles/setup-general/templates/avahi-daemon.conf rename to roles/setup_zeroconf/templates/avahi-daemon.conf diff --git a/roles/traefik/templates/dynamic/privateip.yml b/roles/traefik/templates/dynamic/privateip.yml index 6d96643..aa84b85 100644 --- a/roles/traefik/templates/dynamic/privateip.yml +++ b/roles/traefik/templates/dynamic/privateip.yml @@ -8,4 +8,4 @@ http: - "fe80::/10" - "127.0.0.1/8" - "::1/128" - - "172.16.0.0/12" # Docker containers + - "172.16.0.0/12" # Docker containers