chore: Refactor swarm setup
This commit is contained in:
parent
95ac0318ed
commit
2758ecf5d3
1
ansible/inventory/full/group_vars/all/.gitignore
vendored
Normal file
1
ansible/inventory/full/group_vars/all/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
secrets.yml
|
@ -27,7 +27,7 @@ app_domain_name: stingray.mnke.org
|
||||
# So I'm ending up back where I started :)
|
||||
nfs_export_path: /mnt/emc14t9/managed/stingray
|
||||
nfs_server: truenas.local
|
||||
nfs_mount_path: /mnt/stingray/
|
||||
nfs_mount_path: /mnt/stingray
|
||||
|
||||
portainer_app_name: portainer
|
||||
portainer_admin_password: "{{ secrets.portainer_admin_password }}"
|
||||
@ -36,8 +36,6 @@ portainer_agent_secret: "{{ secrets.portainer_agent_secret }}"
|
||||
cf_dns_api_token: "{{ secrets.cf_dns_api_token }}"
|
||||
cf_email: tonydu121@hotmail.com
|
||||
|
||||
traefik_listen_port: 80
|
||||
traefik_secure_listen_port: 443
|
||||
traefik_admin_port: 8080
|
||||
traefik_admin_user: admin
|
||||
traefik_admin_password: "{{ secrets.traefik_admin_password }}"
|
||||
|
@ -4,35 +4,10 @@
|
||||
mount:
|
||||
src: "{{nfs_server}}:{{nfs_export_path}}"
|
||||
path: "{{nfs_mount_path}}"
|
||||
opts: rw,sync,hard,intr
|
||||
opts: rw,_netdev,hard,intr,nolock
|
||||
state: mounted
|
||||
fstype: nfs
|
||||
|
||||
- name: Verify stacks directory exists (on first swarm node)
|
||||
when: inventory_hostname == groups['swarm_managers'][0]
|
||||
file:
|
||||
path: "/home/{{ansible_user}}/stacks/swarm-bootstrap"
|
||||
state: directory
|
||||
|
||||
- name: Verify bootstrap volume path (on first swarm node)
|
||||
become: true
|
||||
when: inventory_hostname == groups['swarm_managers'][0]
|
||||
file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
loop:
|
||||
- "{{nfs_mount_path}}/swarm-bootstrap/traefik/letsencrypt"
|
||||
- "{{nfs_mount_path}}/swarm-bootstrap/traefik/secrets"
|
||||
- "{{nfs_mount_path}}/swarm-bootstrap/portainer"
|
||||
- "{{nfs_mount_path}}/swarm-bootstrap/gitea"
|
||||
|
||||
- name: Create CF secret
|
||||
become: true
|
||||
copy:
|
||||
content: "{{ cf_dns_api_token }}"
|
||||
dest: "{{nfs_mount_path}}/swarm-bootstrap/traefik/secrets/cf-dns-api-token.secret"
|
||||
mode: '0740'
|
||||
|
||||
- name: Set DNS servers
|
||||
become: true
|
||||
tags: [set_dns_servers]
|
||||
@ -50,46 +25,7 @@
|
||||
name: systemd-resolved
|
||||
state: restarted
|
||||
when: dns_servers_configuration.changed
|
||||
retries: 2
|
||||
|
||||
- name: Generate Traefik admin password hash
|
||||
when: inventory_hostname == groups['swarm_managers'][0]
|
||||
shell: echo $(htpasswd -nb {{traefik_admin_user}} {{traefik_admin_password}}) | sed -e s/\\$/\\$\\$/g
|
||||
register: traefikpassword
|
||||
changed_when: false
|
||||
|
||||
- name: Generate Portainer admin password hash
|
||||
when: inventory_hostname == groups['swarm_managers'][0]
|
||||
shell: echo $(htpasswd -nBb admin {{portainer_admin_password}}) | cut -d ":" -f 2 | sed -e s/\\$/\\$\\$/g
|
||||
register: portainerpassword
|
||||
changed_when: false
|
||||
|
||||
- name: Create git user
|
||||
become: true
|
||||
user:
|
||||
name: git
|
||||
create_home: true
|
||||
register: git_user
|
||||
|
||||
- set_fact:
|
||||
portainer_htpasswd: "{{portainerpassword.stdout}}"
|
||||
traefik_htpasswd: "{{traefikpassword.stdout}}"
|
||||
git_user_id: "{{git_user.uid}}"
|
||||
git_group_id: "{{git_user.group}}"
|
||||
when: inventory_hostname == groups['swarm_managers'][0]
|
||||
|
||||
- name: Create docker-compose stack file (on first swarm node)
|
||||
when: inventory_hostname == groups['swarm_managers'][0]
|
||||
template:
|
||||
src: docker-stack.yml.j2
|
||||
dest: /home/{{ansible_user}}/stacks/swarm-bootstrap/docker-stack.yml
|
||||
mode: 0755
|
||||
|
||||
- name: Deploy stack from a compose file (on first swarm node)
|
||||
when: inventory_hostname == groups['swarm_managers'][0]
|
||||
become: true
|
||||
docker_stack:
|
||||
state: present
|
||||
name: swarm-bootstrap
|
||||
detach: false
|
||||
compose:
|
||||
- /home/{{ansible_user}}/stacks/swarm-bootstrap/docker-stack.yml
|
||||
- import_tasks: traefik.yml
|
||||
- import_tasks: portainer.yml
|
||||
|
44
ansible/roles/swarm-bootstrap/tasks/portainer.yml
Normal file
44
ansible/roles/swarm-bootstrap/tasks/portainer.yml
Normal file
@ -0,0 +1,44 @@
|
||||
- name: Verify stacks directory exists
|
||||
when: inventory_hostname == groups['swarm_managers'][0]
|
||||
file:
|
||||
path: "/home/{{ansible_user}}/stacks/portainer"
|
||||
state: directory
|
||||
mode: 0755
|
||||
|
||||
- name: Verify volume paths
|
||||
become: true
|
||||
when: inventory_hostname == groups['swarm_managers'][0]
|
||||
file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
mode: 0755
|
||||
loop:
|
||||
- "{{nfs_mount_path}}/portainer"
|
||||
- "{{nfs_mount_path}}/portainer/data"
|
||||
|
||||
- name: Generate Portainer admin password hash
|
||||
when: inventory_hostname == groups['swarm_managers'][0]
|
||||
shell: echo $(htpasswd -nBb admin {{portainer_admin_password}}) | cut -d ":" -f 2 | sed -e s/\\$/\\$\\$/g
|
||||
register: portainerpassword
|
||||
changed_when: false
|
||||
|
||||
- set_fact:
|
||||
portainer_htpasswd: "{{portainerpassword.stdout}}"
|
||||
when: inventory_hostname == groups['swarm_managers'][0]
|
||||
|
||||
- name: Create Portainer docker-compose stack file
|
||||
when: inventory_hostname == groups['swarm_managers'][0]
|
||||
template:
|
||||
src: portainer/docker-stack.yml.j2
|
||||
dest: /home/{{ansible_user}}/stacks/portainer/docker-stack.yml
|
||||
mode: 0755
|
||||
|
||||
- name: Deploy Portainer stack from a compose file
|
||||
when: inventory_hostname == groups['swarm_managers'][0]
|
||||
become: true
|
||||
docker_stack:
|
||||
state: present
|
||||
name: portainer
|
||||
detach: false
|
||||
compose:
|
||||
- /home/{{ansible_user}}/stacks/portainer/docker-stack.yml
|
64
ansible/roles/swarm-bootstrap/tasks/traefik.yml
Normal file
64
ansible/roles/swarm-bootstrap/tasks/traefik.yml
Normal file
@ -0,0 +1,64 @@
|
||||
---
|
||||
- name: Verify stacks directory exists
|
||||
when: inventory_hostname == groups['swarm_managers'][0]
|
||||
file:
|
||||
path: "/home/{{ansible_user}}/stacks/traefik"
|
||||
state: directory
|
||||
mode: 0755
|
||||
|
||||
- name: Verify volume paths
|
||||
become: true
|
||||
when: inventory_hostname == groups['swarm_managers'][0]
|
||||
file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
mode: 0755
|
||||
loop:
|
||||
- "{{nfs_mount_path}}/traefik/letsencrypt"
|
||||
- "{{nfs_mount_path}}/traefik/secrets"
|
||||
- "{{nfs_mount_path}}/traefik/config"
|
||||
- "{{nfs_mount_path}}/traefik/config/dynamic"
|
||||
|
||||
- name: Create CF secret
|
||||
become: true
|
||||
when: inventory_hostname == groups['swarm_managers'][0]
|
||||
copy:
|
||||
content: "{{ cf_dns_api_token }}"
|
||||
dest: "{{nfs_mount_path}}/traefik/secrets/cf-dns-api-token.secret"
|
||||
mode: '0740'
|
||||
|
||||
- name: Generate Traefik admin password hash
|
||||
when: inventory_hostname == groups['swarm_managers'][0]
|
||||
shell: echo $(htpasswd -nb {{traefik_admin_user}} {{traefik_admin_password}}) | sed -e s/\\$/\\$\\$/g
|
||||
register: traefikpassword
|
||||
changed_when: false
|
||||
|
||||
- set_fact:
|
||||
traefik_htpasswd: "{{traefikpassword.stdout}}"
|
||||
when: inventory_hostname == groups['swarm_managers'][0]
|
||||
|
||||
- name: Create Traefik docker-compose stack file
|
||||
when: inventory_hostname == groups['swarm_managers'][0]
|
||||
template:
|
||||
src: traefik/docker-stack.yml.j2
|
||||
dest: /home/{{ansible_user}}/stacks/traefik/docker-stack.yml
|
||||
mode: 0755
|
||||
|
||||
- name: Template Traefik configuration
|
||||
when: inventory_hostname == groups['swarm_managers'][0]
|
||||
become: true
|
||||
template:
|
||||
src: traefik/config/traefik.yml.j2
|
||||
dest: "{{nfs_mount_path}}/traefik/config/traefik.yml"
|
||||
mode: 0755
|
||||
|
||||
- name: Deploy Traefik stack from a compose file
|
||||
when: inventory_hostname == groups['swarm_managers'][0]
|
||||
become: true
|
||||
docker_stack:
|
||||
state: present
|
||||
name: traefik
|
||||
detach: false
|
||||
compose:
|
||||
- /home/{{ansible_user}}/stacks/traefik/docker-stack.yml
|
||||
|
@ -1,183 +0,0 @@
|
||||
networks:
|
||||
gitea:
|
||||
driver: overlay
|
||||
attachable: true
|
||||
name: gitea
|
||||
traefik:
|
||||
driver: overlay
|
||||
attachable: true
|
||||
name: traefik
|
||||
portainer:
|
||||
driver: overlay
|
||||
attachable: true
|
||||
name: portainer
|
||||
|
||||
volumes:
|
||||
gitea:
|
||||
driver: local
|
||||
driver_opts:
|
||||
o: bind
|
||||
type: none
|
||||
device: {{nfs_mount_path}}/swarm-bootstrap/gitea
|
||||
name: gitea
|
||||
portainer_data:
|
||||
driver: local
|
||||
driver_opts:
|
||||
o: bind
|
||||
type: none
|
||||
device: {{nfs_mount_path}}/swarm-bootstrap/portainer
|
||||
name: portainer_data
|
||||
traefik:
|
||||
driver: local
|
||||
driver_opts:
|
||||
o: bind
|
||||
type: none
|
||||
device: {{nfs_mount_path}}/swarm-bootstrap/traefik
|
||||
name: traefik
|
||||
|
||||
secrets:
|
||||
cf_dns_api_token:
|
||||
file: "{{nfs_mount_path}}/swarm-bootstrap/traefik/secrets/cf-dns-api-token.secret"
|
||||
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:v3.3
|
||||
dns:
|
||||
- 1.1.1.1
|
||||
command:
|
||||
- "--log.level=DEBUG"
|
||||
- "--api.dashboard=true"
|
||||
# Allow invalid TLS certs internally
|
||||
- "--api.insecure=true"
|
||||
# Swarm settings
|
||||
- "--providers.swarm=true"
|
||||
- "--providers.swarm.exposedByDefault=false"
|
||||
- "--providers.swarm.endpoint=unix:///var/run/docker.sock"
|
||||
# HTTP
|
||||
- "--entrypoints.web.address=:{{traefik_listen_port}}"
|
||||
# Redirect to HTTPS
|
||||
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
|
||||
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
|
||||
- "--entrypoints.websecure.address=:{{traefik_secure_listen_port}}"
|
||||
# TLS
|
||||
- "--entrypoints.websecure.http.tls.domains[0].main=stingray.mnke.org"
|
||||
- "--entrypoints.websecure.http.tls.domains[0].sans=*.stingray.mnke.org"
|
||||
- "--certificatesresolvers.letsencrypt.acme.dnschallenge=true"
|
||||
- "--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare"
|
||||
- "--certificatesresolvers.letsencrypt.acme.caserver={{'https://acme-v02.api.letsencrypt.org/directory' if traefik_tls_mode == 'production' else 'https://acme-staging-v02.api.letsencrypt.org/directory'}}"
|
||||
- "--certificatesresolvers.letsencrypt.acme.email={{cf_email}}"
|
||||
- "--certificatesresolvers.letsencrypt.acme.storage=/data/letsencrypt/acme.json"
|
||||
ports:
|
||||
- "{{traefik_listen_port}}:{{traefik_listen_port}}"
|
||||
- "{{traefik_secure_listen_port}}:{{traefik_secure_listen_port}}"
|
||||
- "{{traefik_admin_port}}:8080"
|
||||
secrets:
|
||||
- "cf_dns_api_token"
|
||||
environment:
|
||||
- "CLOUDFLARE_EMAIL={{cf_email}}"
|
||||
- "CF_DNS_API_TOKEN_FILE=/run/secrets/cf_dns_api_token"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- traefik:/data
|
||||
networks:
|
||||
- traefik
|
||||
deploy:
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.api.rule=Host(`traefik.{{app_domain_name}}`)"
|
||||
- "traefik.http.routers.api.entrypoints=websecure"
|
||||
- "traefik.http.routers.api.service=api@internal"
|
||||
- "traefik.http.routers.api.middlewares=auth"
|
||||
- "traefik.http.routers.api.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.middlewares.auth.basicauth.users={{traefik_htpasswd}}"
|
||||
# Dummy service for Swarm port detection. The port can be any valid integer value.
|
||||
- "traefik.http.services.dummy-svc.loadbalancer.server.port=9999"
|
||||
mode: global
|
||||
placement:
|
||||
constraints: [node.role == manager]
|
||||
|
||||
whoami:
|
||||
image: "traefik/whoami"
|
||||
networks:
|
||||
- traefik
|
||||
deploy:
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.whoami.rule=Host(`whoami.stingray.mnke.org`)"
|
||||
- "traefik.http.routers.whoami.entrypoints=websecure"
|
||||
- "traefik.http.routers.whoami.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.whoami.loadbalancer.server.port=80"
|
||||
- "traefik.swarm.network=traefik"
|
||||
|
||||
agent:
|
||||
image: portainer/agent:latest
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /var/lib/docker/volumes:/var/lib/docker/volumes
|
||||
networks:
|
||||
- portainer
|
||||
environment:
|
||||
AGENT_SECRET: {{portainer_agent_secret}}
|
||||
deploy:
|
||||
mode: global
|
||||
placement:
|
||||
constraints: [node.platform.os == linux]
|
||||
|
||||
portainer:
|
||||
image: portainer/portainer:latest
|
||||
command: "-H tcp://tasks.agent:9001 --tlsskipverify --bind :9000 --tunnel-port 8000 --admin-password {{portainer_htpasswd}}"
|
||||
ports:
|
||||
- "9000:9000"
|
||||
- "8000:8000"
|
||||
volumes:
|
||||
- portainer_data:/data
|
||||
networks:
|
||||
- portainer
|
||||
- traefik
|
||||
environment:
|
||||
# TODO: Load this in a secret
|
||||
AGENT_SECRET: {{portainer_agent_secret}}
|
||||
deploy:
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.portainer.rule=Host(`portainer.{{app_domain_name}}`)"
|
||||
- "traefik.http.routers.portainer.entrypoints=websecure"
|
||||
- "traefik.http.routers.portainer.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.portainer.loadbalancer.server.port=9000"
|
||||
- "traefik.swarm.network=traefik"
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints: [node.role == manager]
|
||||
|
||||
gitea:
|
||||
image: docker.io/gitea/gitea:1.23.1
|
||||
environment:
|
||||
- USER_UID={{git_user_id}}
|
||||
- USER_GID={{git_group_id}}
|
||||
- USER=git
|
||||
- GITEA_APP_NAME=mnke
|
||||
- GITEA__server__DOMAIN={{gitea_primary_domain_name}}
|
||||
- GITEA__server__ROOT_URL=https://{{gitea_primary_domain_name}}
|
||||
networks:
|
||||
- gitea
|
||||
- traefik
|
||||
volumes:
|
||||
- gitea:/data
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- "222:22"
|
||||
deploy:
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.gitea.rule=Host(`git.{{app_domain_name}}`) || Host(`{{gitea_primary_domain_name}}`)"
|
||||
- "traefik.http.routers.gitea.entrypoints=websecure"
|
||||
- "traefik.http.routers.gitea.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.gitea.loadbalancer.server.port=3000"
|
||||
- "traefik.swarm.network=traefik"
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints: [node.role == manager]
|
@ -0,0 +1,54 @@
|
||||
networks:
|
||||
portainer:
|
||||
traefik:
|
||||
external: true
|
||||
|
||||
volumes:
|
||||
portainer_data:
|
||||
driver: local
|
||||
driver_opts:
|
||||
o: bind
|
||||
type: none
|
||||
device: {{nfs_mount_path}}/portainer/data
|
||||
name: portainer_data
|
||||
|
||||
services:
|
||||
agent:
|
||||
image: portainer/agent:latest
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /var/lib/docker/volumes:/var/lib/docker/volumes
|
||||
networks:
|
||||
- portainer
|
||||
environment:
|
||||
AGENT_SECRET: {{portainer_agent_secret}}
|
||||
deploy:
|
||||
mode: global
|
||||
placement:
|
||||
constraints: [node.platform.os == linux]
|
||||
|
||||
portainer:
|
||||
image: portainer/portainer:latest
|
||||
command: "-H tcp://tasks.agent:9001 --tlsskipverify --bind :9000 --tunnel-port 8000 --admin-password {{portainer_htpasswd}}"
|
||||
ports:
|
||||
- "9000:9000"
|
||||
- "8000:8000"
|
||||
volumes:
|
||||
- portainer_data:/data
|
||||
networks:
|
||||
- portainer
|
||||
- traefik
|
||||
environment:
|
||||
AGENT_SECRET: {{portainer_agent_secret}}
|
||||
deploy:
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.portainer.rule=Host(`portainer.{{app_domain_name}}`)"
|
||||
- "traefik.http.routers.portainer.entrypoints=websecure"
|
||||
- "traefik.http.routers.portainer.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.portainer.loadbalancer.server.port=9000"
|
||||
- "traefik.swarm.network=traefik"
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints: [node.role == manager]
|
@ -0,0 +1,31 @@
|
||||
api:
|
||||
dashboard: true
|
||||
insecure: true
|
||||
|
||||
providers:
|
||||
swarm:
|
||||
exposedByDefault: false
|
||||
endpoint: unix:///var/run/docker.sock
|
||||
|
||||
entrypoints:
|
||||
web:
|
||||
address: :80
|
||||
http:
|
||||
redirections:
|
||||
entrypoint:
|
||||
to: websecure
|
||||
scheme: https
|
||||
websecure:
|
||||
address: :443
|
||||
|
||||
certificatesResolvers:
|
||||
letsencrypt:
|
||||
acme:
|
||||
email: {{ cf_email }}
|
||||
storage: /data/letsencrypt/acme.json
|
||||
caServer: {{ 'https://acme-v02.api.letsencrypt.org/directory' if traefik_tls_mode == 'production' else 'https://acme-staging-v02.api.letsencrypt.org/directory' }}
|
||||
dnsChallenge:
|
||||
provider: cloudflare
|
||||
resolvers:
|
||||
- "1.1.1.1:53"
|
||||
- "1.0.0.1:53"
|
@ -0,0 +1,72 @@
|
||||
networks:
|
||||
traefik:
|
||||
driver: overlay
|
||||
attachable: true
|
||||
name: traefik
|
||||
|
||||
volumes:
|
||||
traefik:
|
||||
driver: local
|
||||
driver_opts:
|
||||
o: bind
|
||||
type: none
|
||||
device: {{nfs_mount_path}}/traefik
|
||||
name: traefik
|
||||
|
||||
secrets:
|
||||
cf_dns_api_token:
|
||||
file: "{{nfs_mount_path}}/traefik/secrets/cf-dns-api-token.secret"
|
||||
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:v3.3
|
||||
command:
|
||||
- "--log.level=DEBUG"
|
||||
- "--configFile=/data/config/traefik.yml"
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "{{traefik_admin_port}}:8080"
|
||||
secrets:
|
||||
- "cf_dns_api_token"
|
||||
environment:
|
||||
- "CLOUDFLARE_EMAIL={{cf_email}}"
|
||||
- "CF_DNS_API_TOKEN_FILE=/run/secrets/cf_dns_api_token"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- traefik:/data
|
||||
networks:
|
||||
- traefik
|
||||
deploy:
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.api.rule=Host(`traefik.{{app_domain_name}}`)"
|
||||
- "traefik.http.routers.api.entrypoints=websecure"
|
||||
- "traefik.http.routers.api.service=api@internal"
|
||||
- "traefik.http.routers.api.middlewares=auth"
|
||||
- "traefik.http.routers.api.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.api.tls.domains[0].main=mnke.org"
|
||||
- "traefik.http.routers.api.tls.domains[0].sans=*.mnke.org"
|
||||
- "traefik.http.routers.api.tls.domains[1].main=stingray.mnke.org"
|
||||
- "traefik.http.routers.api.tls.domains[1].sans=*.stingray.mnke.org"
|
||||
- "traefik.http.middlewares.auth.basicauth.users={{traefik_htpasswd}}"
|
||||
# Dummy service for Swarm port detection. The port can be any valid integer value.
|
||||
- "traefik.http.services.dummy-svc.loadbalancer.server.port=9999"
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints: [node.role == manager]
|
||||
|
||||
whoami:
|
||||
image: "traefik/whoami"
|
||||
networks:
|
||||
- traefik
|
||||
deploy:
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.whoami.rule=Host(`whoami.stingray.mnke.org`)"
|
||||
- "traefik.http.routers.whoami.entrypoints=websecure"
|
||||
- "traefik.http.routers.whoami.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.whoami.loadbalancer.server.port=80"
|
||||
- "traefik.swarm.network=traefik"
|
||||
|
@ -1,6 +1,47 @@
|
||||
---
|
||||
- name: Set up Docker Swarm
|
||||
hosts: swarm
|
||||
tags: [swarm-init]
|
||||
roles:
|
||||
- docker-swarm
|
||||
- swarm-bootstrap
|
||||
|
||||
- name: Mount extra shares
|
||||
tags: [extra-shares]
|
||||
hosts: swarm
|
||||
vars:
|
||||
shares:
|
||||
# I can't seem to use a domain name, so unfortunately, we have to hard-
|
||||
# code the IP address
|
||||
- src: //10.0.0.160:/stingray
|
||||
path: /mnt/stingray-cifs
|
||||
opts: rw,_netdev,hard,intr,nobrl,username=stingray,password={{ secrets.stingray_password }}
|
||||
fstype: cifs
|
||||
state: unmounted
|
||||
- src: //10.0.0.160:/lfs-media
|
||||
path: /mnt/media-cifs
|
||||
opts: rw,_netdev,hard,intr,nobrl,username=mediadm,password={{ secrets.mediadm_password }}
|
||||
fstype: cifs
|
||||
state: unmounted
|
||||
- src: truenas.local:/mnt/emc14t9/lfs/media
|
||||
path: /mnt/media
|
||||
opts: rw,_netdev,hard,intr,nolock
|
||||
fstype: nfs
|
||||
state: mounted
|
||||
tasks:
|
||||
- name: Ensure mount directory exists
|
||||
become: true
|
||||
file:
|
||||
path: "{{ item.path }}"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
loop: "{{ shares }}"
|
||||
- name: Mount media share
|
||||
become: true
|
||||
mount:
|
||||
src: "{{ item.src }}"
|
||||
path: "{{ item.path }}"
|
||||
opts: "{{ item.opts }}"
|
||||
state: "{{ item.state | default('mounted') }}"
|
||||
fstype: "{{ item.fstype }}"
|
||||
loop: "{{ shares }}"
|
||||
|
52
docker/stacks/gitea/docker-stack.yml
Normal file
52
docker/stacks/gitea/docker-stack.yml
Normal file
@ -0,0 +1,52 @@
|
||||
version: '3.8'
|
||||
|
||||
networks:
|
||||
gitea:
|
||||
driver: overlay
|
||||
attachable: true
|
||||
name: gitea
|
||||
traefik:
|
||||
external: true
|
||||
|
||||
volumes:
|
||||
gitea:
|
||||
driver: local
|
||||
driver_opts:
|
||||
o: bind
|
||||
type: none
|
||||
device: /mnt/stingray/gitea
|
||||
name: gitea
|
||||
|
||||
services:
|
||||
gitea:
|
||||
image: docker.io/gitea/gitea:1.23.1
|
||||
environment:
|
||||
- USER_UID=1002
|
||||
- USER_GID=1002
|
||||
- USER=git
|
||||
- GITEA_APP_NAME=mnke
|
||||
- GITEA__server__DOMAIN=git.mnke.org
|
||||
- GITEA__server__ROOT_URL=https://git.mnke.org
|
||||
networks:
|
||||
- gitea
|
||||
- traefik
|
||||
volumes:
|
||||
- gitea:/data
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- "222:22"
|
||||
deploy:
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.gitea.rule=Host(`git.mnke.org`) || Host(`git.stingray.mnke.org`)"
|
||||
- "traefik.http.routers.gitea.entrypoints=websecure"
|
||||
- "traefik.http.routers.gitea.tls=true"
|
||||
- "traefik.http.services.gitea.loadbalancer.server.port=3000"
|
||||
- "traefik.swarm.network=traefik"
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints: [node.role == manager]
|
||||
|
243
docker/stacks/media/docker-stack.yml
Normal file
243
docker/stacks/media/docker-stack.yml
Normal file
@ -0,0 +1,243 @@
|
||||
---
|
||||
version: "3.8"
|
||||
|
||||
networks:
|
||||
traefik:
|
||||
external: true
|
||||
media:
|
||||
|
||||
volumes:
|
||||
transmission_config:
|
||||
driver: local
|
||||
driver_opts:
|
||||
o: bind
|
||||
type: none
|
||||
device: ${TRANSMISSION_CONFIG_DIRECTORY:-/mnt/stingray/media/transmission-openvpn/config}
|
||||
# Argh, mounting the configuration on the NFS drive is not a good idea!
|
||||
# The sqlite databases in these volumes can have pretty high random RW, so
|
||||
# we should keep these local. But fuck, managing them on a non-network drive
|
||||
# on Swarm is such a pain...
|
||||
# Maybe we can get these to connect to a different DB?
|
||||
jellyseerr_config:
|
||||
driver: local
|
||||
driver_opts:
|
||||
o: bind
|
||||
type: none
|
||||
device: ${JELLYSEERR_CONFIG_DIRECTORY:-/mnt/stingray/media/jellyseerr/config}
|
||||
sonarr_config:
|
||||
driver: local
|
||||
driver_opts:
|
||||
o: bind
|
||||
type: none
|
||||
device: ${SONARR_CONFIG_DIRECTORY:-/mnt/stingray/media/sonarr/config}
|
||||
radarr_config:
|
||||
driver: local
|
||||
driver_opts:
|
||||
o: bind
|
||||
type: none
|
||||
device: ${RADARR_CONFIG_DIRECTORY:-/mnt/stingray/media/radarr/config}
|
||||
prowlarr_config:
|
||||
driver: local
|
||||
driver_opts:
|
||||
o: bind
|
||||
type: none
|
||||
device: ${PROWLARR_CONFIG_DIRECTORY:-/mnt/stingray/media/prowlarr/config}
|
||||
|
||||
services:
|
||||
transmission-openvpn:
|
||||
image: haugene/transmission-openvpn
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
networks:
|
||||
- traefik
|
||||
- media
|
||||
volumes:
|
||||
- transmission_config:/config
|
||||
- ${MEDIA_DIRECTORY:-/mnt/media}/torrents:${MEDIA_DIRECTORY:-/mnt/media}/torrents
|
||||
environment:
|
||||
- PUID=${PUID:-8796}
|
||||
- PGID=${PGID:-3005}
|
||||
- OPENVPN_PROVIDER=${OPENVPN_PROVIDER:-SURFSHARK}
|
||||
# - OPENVPN_CONFIG=us-sea.prod.surfshark.com_udp
|
||||
# The startup script fails when cloning and checking out the configuration
|
||||
# repo for some reason. Passing a URL to skip that seems to work though.
|
||||
- OPENVPN_CONFIG_URL=${OPENVPN_CONFIG_URL:-https://raw.githubusercontent.com/haugene/vpn-configs-contrib/refs/heads/main/openvpn/surfshark/us-lax.prod.surfshark.com_udp.ovpn}
|
||||
- OPENVPN_USERNAME=${OPENVPN_USERNAME}
|
||||
- OPENVPN_PASSWORD=${OPENVPN_PASSWORD}
|
||||
# If OpenVPN stops, container will stop and then restart option
|
||||
# below will bring it back to life.
|
||||
# Surfshark seems to switch IPs frequently (as of 9/8/2023) so
|
||||
# this is helpful.
|
||||
- OPENVPN_OPTS=--inactive 3600 --ping 10 --ping-exit 60
|
||||
- TRANSMISSION_DOWNLOAD_DIR=${MEDIA_DIRECTORY:-/mnt/media}/torrents/completed
|
||||
- TRANSMISSION_INCOMPLETE_DIR=${MEDIA_DIRECTORY:-/mnt/media}/torrents/incomplete
|
||||
- TRANSMISSION_WEB_UI=flood-for-transmission
|
||||
- TZ=America/Vancouver
|
||||
- LOCAL_NETWORK=10.0.0.0/16
|
||||
deploy:
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.transmission_openvpn.rule=Host(`${TRANSMISSION_HOST:-tovpn.stingray.mnke.org}`)"
|
||||
- "traefik.http.routers.transmission_openvpn.entrypoints=websecure"
|
||||
- "traefik.http.routers.transmission_openvpn.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.transmission_openvpn.loadbalancer.server.port=9091"
|
||||
- "traefik.swarm.network=traefik"
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints: [node.role != manager]
|
||||
resources:
|
||||
limits:
|
||||
cpus: '0.50'
|
||||
memory: 512M
|
||||
reservations:
|
||||
cpus: '0.25'
|
||||
memory: 64M
|
||||
|
||||
prowlarr:
|
||||
image: lscr.io/linuxserver/prowlarr
|
||||
environment:
|
||||
- PUID=${PUID:-8796}
|
||||
- PGID=${PGID:-3005}
|
||||
- TZ=America/Vancouver
|
||||
networks:
|
||||
- media
|
||||
- traefik
|
||||
volumes:
|
||||
- prowlarr_config:/config
|
||||
deploy:
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.prowlarr.rule=Host(`${PROWLARR_HOST:-prowlarr.stingray.mnke.org}`)"
|
||||
- "traefik.http.routers.prowlarr.entrypoints=websecure"
|
||||
- "traefik.http.routers.prowlarr.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.prowlarr.loadbalancer.server.port=9696"
|
||||
- "traefik.swarm.network=traefik"
|
||||
placement:
|
||||
constraints: [node.role != manager]
|
||||
resources:
|
||||
limits:
|
||||
cpus: '0.5'
|
||||
memory: 256M
|
||||
reservations:
|
||||
cpus: '0.1'
|
||||
memory: 64M
|
||||
|
||||
radarr:
|
||||
image: lscr.io/linuxserver/radarr
|
||||
environment:
|
||||
- PUID=${PUID:-8796}
|
||||
- PGID=${PGID:-3005}
|
||||
- TZ=America/Vancouver
|
||||
networks:
|
||||
- media
|
||||
- traefik
|
||||
volumes:
|
||||
- radarr_config:/config
|
||||
- ${MEDIA_DIRECTORY:-/mnt/media}:${MEDIA_DIRECTORY:-/mnt/media}
|
||||
deploy:
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.radarr.rule=Host(`${RADARR_HOST:-radarr.stingray.mnke.org}`)"
|
||||
- "traefik.http.routers.radarr.entrypoints=websecure"
|
||||
- "traefik.http.routers.radarr.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.radarr.loadbalancer.server.port=7878"
|
||||
- "traefik.swarm.network=traefik"
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints: [node.role != manager]
|
||||
resources:
|
||||
limits:
|
||||
cpus: '0.5'
|
||||
memory: 256M
|
||||
reservations:
|
||||
cpus: '0.1'
|
||||
memory: 64M
|
||||
|
||||
sonarr:
|
||||
image: lscr.io/linuxserver/sonarr
|
||||
environment:
|
||||
- PUID=${PUID:-8796}
|
||||
- PGID=${PGID:-3005}
|
||||
- TZ=America/Vancouver
|
||||
networks:
|
||||
- media
|
||||
- traefik
|
||||
volumes:
|
||||
- sonarr_config:/config
|
||||
- ${MEDIA_DIRECTORY:-/mnt/media}:${MEDIA_DIRECTORY:-/mnt/media}
|
||||
deploy:
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.sonarr.rule=Host(`${SONARR_HOST:-sonarr.stingray.mnke.org}`)"
|
||||
- "traefik.http.routers.sonarr.entrypoints=websecure"
|
||||
- "traefik.http.routers.sonarr.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.sonarr.loadbalancer.server.port=8989"
|
||||
- "traefik.swarm.network=traefik"
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints: [node.role != manager]
|
||||
resources:
|
||||
limits:
|
||||
cpus: '0.5'
|
||||
memory: 256M
|
||||
reservations:
|
||||
cpus: '0.1'
|
||||
memory: 64M
|
||||
|
||||
flaresolverr:
|
||||
image: ghcr.io/flaresolverr/flaresolverr
|
||||
environment:
|
||||
- LOG_LEVEL=${FLARESOLVERR_LOG_LEVEL:-info}
|
||||
- LOG_HTML=${FLARESOLVERR_LOG_HTML:-false}
|
||||
- CAPTCHA_SOLVER=${FLARESOLVERR_CAPTCHA_SOLVER:-none}
|
||||
- TZ=America/Vancouver
|
||||
networks:
|
||||
- media
|
||||
# ports:
|
||||
# - "${PORT:-8191}:8191"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints: [node.role != manager]
|
||||
resources:
|
||||
limits:
|
||||
cpus: '0.25'
|
||||
memory: 256M
|
||||
reservations:
|
||||
cpus: '0.1'
|
||||
memory: 64M
|
||||
|
||||
jellyseerr:
|
||||
image: fallenbagel/jellyseerr
|
||||
environment:
|
||||
- LOG_LEVEL=debug
|
||||
- TZ=America/Vancouver
|
||||
networks:
|
||||
- media
|
||||
- traefik
|
||||
volumes:
|
||||
- jellyseerr_config:/app/config
|
||||
deploy:
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.jellyseerr.rule=Host(`${JELLYSEERR_HOST:-seerr.stingray.mnke.org}`)"
|
||||
- "traefik.http.routers.jellyseerr.entrypoints=websecure"
|
||||
- "traefik.http.routers.jellyseerr.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.jellyseerr.loadbalancer.server.port=5055"
|
||||
- "traefik.swarm.network=traefik"
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints: [node.role != manager]
|
||||
resources:
|
||||
limits:
|
||||
cpus: '0.5'
|
||||
memory: 512M
|
||||
reservations:
|
||||
cpus: '0.1'
|
||||
memory: 64M
|
||||
|
Loading…
x
Reference in New Issue
Block a user