refactor: move all service roles into a single alpina role

This commit is contained in:
2023-04-04 22:56:23 -07:00
parent ec335e5d3c
commit 145c5db29f
53 changed files with 109 additions and 100 deletions

View File

@@ -0,0 +1,12 @@
$ANSIBLE_VAULT;1.2;AES256;alpina
37653839366635373530306432303538626233356164633761316231623732316138643532383735
3132613432333636383363383162643434626638613234320a343337333435393461323735646338
34353764366561633738383933626261643734343266333364353162366161313738663064656530
6666313731343663650a343761646664356238373763383136366431383337313065613663303233
36613233653666306338373839623130323833393932386161353933613338613836326632653262
31646131646637646237373964376365336337386639396266393731623761393038396233663663
32393964313361326463356435343064643964343731386238643263653738356534383536353330
32376162376235663636626562646436613265656461656133643762396137313238383533653831
31396632656630626138326335363462383131343431336264656236346665366236353863326237
66653064653166373838653631653563303834303334633830383064323965393563663563636361
653139663339346331336435313263343936

View File

@@ -0,0 +1,26 @@
#!/usr/bin/env bash
set -x
local_gateway=$(ip route | grep default | awk '{print $3}')
# This used as the gateway address for NAT-PMP to work properly
wg_gateway="{{ wg_dns }}"
wg_peer_address=$(echo "{{ wg_peer_endpoint }}" | cut -d: -f1)
ip route add "$wg_peer_address" via "$local_gateway"
ip link add wg0 type wireguard
wg setconf wg0 /etc/wireguard/wg0.conf
ip address add dev wg0 "{{ wg_address }}"
ip link set wg0 up
ip route add "$wg_gateway" dev wg0
ip route del default
ip route add default via "$wg_gateway"
# Note that the DNS isn't changed, so there's actually a leak there
# That's on purpose, just in case I want to access local jackett from qbit
# Still need to figure out how to make this work with IPv6
# Prevent IPv6 leaks
# ip -6 route del default
# Finally, optionally allow access to the home network
# ip route add "\{\{ home_network }}" via "$local_gateway"

View File

@@ -0,0 +1,79 @@
{% import 'contrib/compose_helpers.j2' as helpers with context %}
{##}
version: "3.9"
networks:
{# {{ helpers.default_network(249) | indent(2) }}#}
# TODO: Figure out IPv6 leaks
ipv4_only:
traefik_traefik:
external: true
services:
qbittorrent:
image: linuxserver/qbittorrent:latest
container_name: qbittorrent
cap_add:
- NET_ADMIN
labels:
- {{ helpers.traefik_labels('qbit', port='8080', auth=true) | indent(6) }}
restart: unless-stopped
environment:
- DOCKER_MODS=linuxserver/mods:universal-package-install
- INSTALL_PACKAGES=wireguard-tools-wg
networks:
- ipv4_only
- traefik_traefik
volumes:
- ./wireguard:/etc/wireguard:ro
- ./custom-init:/custom-cont-init.d:ro
- {{ base_volume_path }}/arrstack/config/qbittorrent:/config
- {{ base_volume_path }}/arrstack/downloads:/downloads
- {{ media_volume_path }}/Plex:/media/Plex
- {{ media_volume_path }}/iso-img:/media/iso-img
prowlarr:
image: linuxserver/prowlarr:latest
container_name: prowlarr
labels:
- {{ helpers.traefik_labels('prowlarr', port='9696', auth=true) | indent(6) }}
restart: unless-stopped
depends_on:
- qbittorrent
networks:
- ipv4_only
- traefik_traefik
volumes:
- {{ base_volume_path }}/arrstack/config/prowlarr:/config
sonarr:
image: linuxserver/sonarr:latest
container_name: sonarr
labels:
- {{ helpers.traefik_labels('sonarr', port='8989', auth=true) | indent(6) }}
restart: unless-stopped
depends_on:
- qbittorrent
networks:
- ipv4_only
- traefik_traefik
volumes:
- {{ base_volume_path }}/arrstack/config/sonarr:/config
- {{ base_volume_path }}/arrstack/downloads:/downloads
- {{ media_volume_path }}/Plex:/media/Plex
radarr:
image: linuxserver/radarr:latest
container_name: radarr
labels:
- {{ helpers.traefik_labels('radarr', port='7878', auth=true) | indent(6) }}
restart: unless-stopped
depends_on:
- qbittorrent
networks:
- ipv4_only
- traefik_traefik
volumes:
- {{ base_volume_path }}/arrstack/config/radarr:/config
- {{ base_volume_path }}/arrstack/downloads:/downloads
- {{ media_volume_path }}/Plex:/media/Plex

View File

@@ -0,0 +1,11 @@
# Stripped version of the wg config
[Interface]
PrivateKey = {{ wg_privkey }}
# Address = {{ wg_address }}
# DNS = {{ wg_dns }} # This is also used as the gateway address for NAT-PMP to work properly
[Peer]
PublicKey = {{ wg_peer_pubkey }}
AllowedIPs = 0.0.0.0/0,::0/0
Endpoint = {{ wg_peer_endpoint }}

View File

@@ -0,0 +1,27 @@
$ANSIBLE_VAULT;1.2;AES256;alpina
35303032386566343430633238343936366234333434343763666231666232633539303232383534
3035346233346162373939333531613535353232626531640a646537616163353736653161326265
31336530316335623335353661373834613264326436303933326135396166346562343136353931
6439383039346465300a366266393130356630316630333336616565366562613038393239623738
65626664643630353236333932373337333363626337386163613464306638633964663264363964
30373661393531306662323134626664656233323762393037356434353066343830333033316365
65616636613437663737306263373066306361376630616331663031346434336663393862316464
62343339663461353934323063653566303932656264363562333136353665336263646230323832
35376666303531383961646234663230663634393135326664386665633538616233613866373965
64363361313232316336376631646662376565353536316438306361306261663532386564616566
61663534393035343233326562303863646165346538393761326335376165623964396130393831
64333665313461666335383134613831376138393061343238643661366439636534626265323865
35393035336632653038623438626366373733626331633866373935616531623664303063376562
31356332346164663364636235333461383437623161343338643839323765336237633266633864
64363234646533616439313638363865373364623637636537623666383664656630333533303233
64383734366666633832393230663739333435666138636462336332373061346239306136336263
39643666303863303035313738343664636536663939616335303834333834363739303938646665
66303637633239373461393434313036316563313132356432633337666537616363373830313034
61313538633663653230643262613333306361666131663036643162343966313365653566393235
36623832663034373734653664613038363137366437326565373761663963636336393536386435
30393831326134376639366661653439616138643438646363343632346131306532663439396534
32383661306539306635336262383563376561303862396532633362666266313562623336383235
36366565633734633639653239306331333237353233326563653930653739316230666362323931
39663931376562653530323434656436353166393836643238643632396430353034333034333665
62323338373839383132323537353431636537616366393965643463316164323034316536383961
6164333537633631646663333463306236613038326339643439

View File

@@ -0,0 +1,3 @@
POSTGRES_USER=gitea
POSTGRES_DB=gitea
POSTGRES_PASSWORD={{ db_password }}

View File

@@ -0,0 +1,29 @@
GITEA____APP_NAME=CazGitea
# Database
GITEA__database__DB_TYPE=postgres
GITEA__database__HOST=db:5432
GITEA__database__NAME={{ db_user }}
GITEA__database__USER={{ db_name }}
GITEA__database__PASSWD={{ db_password }}
# Server
GITEA__server__ROOT_URL=https://gitea.{{ domain }}/
GITEA__server__DISABLE_SSH=true
# Mail
GITEA__mailer__ENABLED=true
GITEA__mailer__SMTP_ADDR=smtp.sendgrid.net
GITEA__mailer__SMTP_PORT=587
GITEA__mailer__FROM=gitea@cazzzer.com
GITEA__mailer__USER=apikey
GITEA__mailer__PASSWD={{ sendgrid_api_key }}
# Security
GITEA__security__SECRET_KEY={{ secret_key }}
GITEA__security__INTERNAL_TOKEN={{ internal_token }}
GITEA__oauth2__JWT_SECRET={{ jwt_secret }}
# Indexer
GITEA__indexer__REPO_INDEXER_ENABLED=true

View File

@@ -0,0 +1,35 @@
{% import 'contrib/compose_helpers.j2' as helpers with context %}
{##}
version: "3.9"
networks:
{{ helpers.default_network(252) | indent(2) }}
traefik_traefik:
external: true
services:
server:
image: gitea/gitea:1.18
container_name: gitea_server
labels:
- {{ helpers.traefik_labels('gitea', port='3000') | indent(6) }}
restart: unless-stopped
env_file:
- .env.gitea
networks:
- default
- traefik_traefik
volumes:
- {{ base_volume_path }}/gitea/gitea:/data
depends_on:
- db
db:
image: postgres:14-alpine
container_name: gitea_db
restart: unless-stopped
env_file:
- .env.db
networks:
- default
volumes:
- {{ base_volume_path }}/gitea/postgres:/var/lib/postgresql/data

View File

@@ -0,0 +1,6 @@
$ANSIBLE_VAULT;1.2;AES256;alpina
61626665353536663033663661393434616339396434383530306265363837313839303939623465
3634333839333530383464613966326238363738663637360a343837623832343232316565346131
66663831356162653363383131396665326531363430656539333866313031306537343864343262
3730643765633232620a643734623336646565663266656262343162613239306166386665333139
6366

View File

@@ -0,0 +1 @@
JELLYFIN_PublishedServerUrl=https://jellyfin.{{ domain }}

View File

@@ -0,0 +1,30 @@
{% import 'contrib/compose_helpers.j2' as helpers with context %}
{##}
version: "3.9"
networks:
{{ helpers.default_network(250) | indent(2) }}
traefik_traefik:
external: true
services:
jellyfin:
image: jellyfin/jellyfin:10.8.6
container_name: jellyfin_jellyfin
labels:
- {{ helpers.traefik_labels('jellyfin', port='8096') | indent(6) }}
restart: unless-stopped
env_file:
- .env.jellyfin
networks:
- default
- traefik_traefik
volumes:
- {{ base_volume_path }}/jellyfin/config:/config
- {{ base_volume_path }}/jellyfin/cache:/cache
- {{ media_volume_path }}/Plex/media:/data/media:ro
- {{ media_volume_path }}/other_videos:/data/other_videos:ro
tmpfs:
- /tmp/transcodes
devices:
- /dev/dri:/dev/dri

View File

@@ -0,0 +1,14 @@
$ANSIBLE_VAULT;1.2;AES256;alpina
65313636646233613364363933616361346639653939346337303832646339316632383966666237
3766396134383434613534373937663162393134306536300a626139373732393037346630333838
63663439353238643532316231623866396434303034313130386635623363353263626362376334
3933346434633662320a386432373465646432343338666561366161646335636232353133393933
65313364666564353039626238383033343765323730316633356139326666623135326131353864
32386237643538636538356261393164633137636235346564393930346539623731386633336339
31303466653936343166366164383134306232613236663735623834393963306331376435616365
31313866383730393063353335626164303632636331303830636530656131636139376633623439
63663639323964623231343066373538633336353561646230363363643762393634643435306164
31366364326237636365336363343264343562353337303235633034383635373934376334353336
61373065386639643064303431623162373665363937353832313561386134613834613935653964
64656339316165313936333736643030356366663162316462636662326134396539356262666536
64336133393937396330353234316563356337623733326264363333373536633833

View File

@@ -0,0 +1,3 @@
POSTGRES_USER=nextcloud
POSTGRES_DB=nextcloud
POSTGRES_PASSWORD={{ db_password }}

View File

@@ -0,0 +1 @@
NEXTCLOUD_VERSION=25-fpm-alpine

View File

@@ -0,0 +1,23 @@
POSTGRES_DB=nextcloud
POSTGRES_USER=nextcloud
POSTGRES_PASSWORD={{ db_password }}
POSTGRES_HOST=db
NEXTCLOUD_TRUSTED_DOMAINS=nc.{{ domain }}
REDIS_HOST=redis
REDIS_HOST_PASSWORD={{ redis_password }}
SMTP_HOST=smtp.sendgrid.net
SMTP_SECURE=tls
SMTP_PORT=587
SMTP_AUTHTYPE=LOGIN
SMTP_NAME=apikey
SMTP_PASSWORD={{ sendgrid_api_key }}
MAIL_FROM_ADDRESS=nc
MAIL_DOMAIN=cazzzer.com
TRUSTED_PROXIES={{ traefik_ip }}
OVERWRITEHOST=nc.{{ domain }}
OVERWRITEPROTOCOL=https
OVERWRITECLIURL=https://nc.{{ domain }}

View File

@@ -0,0 +1 @@
REDIS_PASSWORD={{ redis_password }}

View File

@@ -0,0 +1,97 @@
{% import 'contrib/compose_helpers.j2' as helpers with context %}
{##}
version: "3.9"
networks:
{{ helpers.default_network(251) | indent(2) }}
traefik_traefik:
external: true
services:
app:
image: nextcloud:${NEXTCLOUD_VERSION}
container_name: nextcloud_app
restart: unless-stopped
depends_on:
- db
- redis
env_file:
- .env.nextcloud
networks:
- default
volumes:
- {{ base_volume_path }}/nextcloud/nextcloud:/var/www/html
- {{ base_volume_path }}/nextcloud/nextcloud_config:/var/www/html/config
- {{ base_volume_path }}/nextcloud/nextcloud_data:/var/www/html/data
cron:
image: nextcloud:${NEXTCLOUD_VERSION}
container_name: nextcloud_cron
restart: unless-stopped
depends_on:
- app
entrypoint: /cron.sh
networks:
- default
volumes:
- {{ base_volume_path }}/nextcloud/nextcloud:/var/www/html
- {{ base_volume_path }}/nextcloud/nextcloud_config:/var/www/html/config
- {{ base_volume_path }}/nextcloud/nextcloud_data:/var/www/html/data
notify_push:
image: nextcloud:${NEXTCLOUD_VERSION}
container_name: nextcloud_notify_push
restart: unless-stopped
depends_on:
- app
entrypoint:
- /var/www/html/custom_apps/notify_push/bin/x86_64/notify_push
- /var/www/html/config/config.php
networks:
- default
volumes:
- {{ base_volume_path }}/nextcloud/nextcloud:/var/www/html
- {{ base_volume_path }}/nextcloud/nextcloud_config:/var/www/html/config
- {{ base_volume_path }}/nextcloud/nextcloud_data:/var/www/html/data
db:
image: postgres:13-alpine
container_name: nextcloud_db
restart: unless-stopped
env_file:
- .env.db
networks:
- default
volumes:
- {{ base_volume_path }}/nextcloud/db:/var/lib/postgresql/data
redis:
image: redis:7-alpine
container_name: nextcloud_redis
restart: unless-stopped
env_file:
- .env.redis
networks:
- default
command:
- sh
- -c
- redis-server --requirepass $$REDIS_PASSWORD
web:
image: nginx:1.23-alpine
container_name: nextcloud_web
labels:
- {{ helpers.traefik_labels('nc') | indent(6) }}
restart: unless-stopped
links:
- app
networks:
- traefik_traefik
- default
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- {{ base_volume_path }}/nextcloud/nextcloud:/var/www/html
- {{ base_volume_path }}/nextcloud/nextcloud_config:/var/www/html/config
- {{ base_volume_path }}/nextcloud/nextcloud_data:/var/www/html/data

View File

@@ -0,0 +1,182 @@
# https://github.com/nextcloud/docker/blob/master/.examples/docker-compose/with-nginx-proxy/postgres/fpm/web/nginx.conf
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
# Prevent nginx HTTP Server Detection
server_tokens off;
keepalive_timeout 65;
upstream php-handler {
server app:9000;
}
server {
listen 80;
# HSTS settings
# WARNING: Only add the preload option once you read about
# the consequences in https://hstspreload.org/. This option
# will add the domain to a hardcoded list that is shipped
# in all major browsers and getting removed from this list
# could take several months.
#add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;" always;
# set max upload size
client_max_body_size 512M;
fastcgi_buffers 64 4K;
# Enable gzip but do not remove ETag headers
gzip on;
gzip_vary on;
gzip_comp_level 4;
gzip_min_length 256;
gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;
# Pagespeed is not supported by Nextcloud, so if your server is built
# with the `ngx_pagespeed` module, uncomment this line to disable it.
#pagespeed off;
# HTTP response headers borrowed from Nextcloud `.htaccess`
add_header Referrer-Policy "no-referrer" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Download-Options "noopen" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Permitted-Cross-Domain-Policies "none" always;
add_header X-Robots-Tag "none" always;
add_header X-XSS-Protection "1; mode=block" always;
# Remove X-Powered-By, which is an information leak
fastcgi_hide_header X-Powered-By;
# Path to the root of your installation
root /var/www/html;
# Specify how to handle directories -- specifying `/index.php$request_uri`
# here as the fallback means that Nginx always exhibits the desired behaviour
# when a client requests a path that corresponds to a directory that exists
# on the server. In particular, if that directory contains an index.php file,
# that file is correctly served; if it doesn't, then the request is passed to
# the front-end controller. This consistent behaviour means that we don't need
# to specify custom rules for certain paths (e.g. images and other assets,
# `/updater`, `/ocm-provider`, `/ocs-provider`), and thus
# `try_files $uri $uri/ /index.php$request_uri`
# always provides the desired behaviour.
index index.php index.html /index.php$request_uri;
# Rule borrowed from `.htaccess` to handle Microsoft DAV clients
location = / {
if ( $http_user_agent ~ ^DavClnt ) {
return 302 /remote.php/webdav/$is_args$args;
}
}
location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}
# Make a regex exception for `/.well-known` so that clients can still
# access it despite the existence of the regex rule
# `location ~ /(\.|autotest|...)` which would otherwise handle requests
# for `/.well-known`.
location ^~ /.well-known {
# The rules in this block are an adaptation of the rules
# in `.htaccess` that concern `/.well-known`.
location = /.well-known/carddav { return 301 /remote.php/dav/; }
location = /.well-known/caldav { return 301 /remote.php/dav/; }
location /.well-known/acme-challenge { try_files $uri $uri/ =404; }
location /.well-known/pki-validation { try_files $uri $uri/ =404; }
# Let Nextcloud's API for `/.well-known` URIs handle all other
# requests by passing them to the front-end controller.
return 301 /index.php$request_uri;
}
# Rules borrowed from `.htaccess` to hide certain paths from clients
location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/) { return 404; }
location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console) { return 404; }
# Ensure this block, which passes PHP files to the PHP process, is above the blocks
# which handle static assets (as seen below). If this block is not declared first,
# then Nginx will encounter an infinite rewriting loop when it prepends `/index.php`
# to the URI, resulting in a HTTP 500 error response.
location ~ \.php(?:$|/) {
# Required for legacy support
rewrite ^/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|oc[ms]-provider\/.+|.+\/richdocumentscode\/proxy) /index.php$request_uri;
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
set $path_info $fastcgi_path_info;
try_files $fastcgi_script_name =404;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $path_info;
#fastcgi_param HTTPS on;
fastcgi_param modHeadersAvailable true; # Avoid sending the security headers twice
fastcgi_param front_controller_active true; # Enable pretty urls
fastcgi_pass php-handler;
fastcgi_intercept_errors on;
fastcgi_request_buffering off;
}
location ~ \.(?:css|js|svg|gif)$ {
try_files $uri /index.php$request_uri;
expires 6M; # Cache-Control policy borrowed from `.htaccess`
access_log off; # Optional: Don't log access to assets
}
location ~ \.woff2?$ {
try_files $uri /index.php$request_uri;
expires 7d; # Cache-Control policy borrowed from `.htaccess`
access_log off; # Optional: Don't log access to assets
}
# Rule borrowed from `.htaccess`
location /remote {
return 301 /remote.php$request_uri;
}
location / {
try_files $uri $uri/ /index.php$request_uri;
}
location ^~ /push/ {
proxy_pass http://notify_push:7867/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}

View File

@@ -0,0 +1,19 @@
$ANSIBLE_VAULT;1.2;AES256;alpina
33343634343761393265346336326263346166326365656461353361373738343262613534363664
6238313838623436643730393465353361343730343938380a373235363835313561333934366361
61626630616365633134393337376464303537623839623639626561383036663432336537396338
6230366561373638350a636634613436346237373166626162656535306234346439666161633634
63633436376562373739396261313133383161353463393737346436623239346232393034363335
38363962306463386464633338363162623832363431373765656232343931376363653464313438
62316635623236633762353061326539343435393737333563313331393134643439393463623637
64633262656366333537663663346239653533353132343066383438333636396238393135623530
35323439666437313936343733376336383961653864396133373831316139353163613337306533
63366233333865653166336466343830336239346532373466376261333530666230633434393933
30383032613466393833353065653465633633663333663132636164303264316163343961653562
36356138343130316636333231613033646565353863323132643432656239636538366462353338
37353936326661303064313635633865663939316631623764393235383630353132343135616338
32623938616136326561323033336134636364623165646566646662353066623432363538386364
30663734366136313933666332323538346266306133383838323839363233653435643862316136
33353436313834356230313164623838363363336266323637353263353763326235636161383836
63323839363438333538326130653063313734303237623234376235396638343531623661626661
3364616438373366663837613933376361653664363532653833

View File

@@ -0,0 +1,21 @@
AUTHENTIK_ERROR_REPORTING__ENABLED=true
AUTHENTIK_REDIS__HOST=redis
AUTHENTIK_POSTGRESQL__HOST=postgres
AUTHENTIK_POSTGRESQL__USER=authentik
AUTHENTIK_POSTGRESQL__NAME=authentik
AUTHENTIK_POSTGRESQL__PASSWORD={{ db_password }}
AUTHENTIK_SECRET_KEY={{ authentik_secret_key }}
AUTHENTIK_EMAIL__HOST=smtp.sendgrid.net
AUTHENTIK_EMAIL__PORT=587
AUTHENTIK_EMAIL__USERNAME=apikey
AUTHENTIK_EMAIL__PASSWORD={{ sengrid_api_key }}
AUTHENTIK_EMAIL__USE_TLS=true
AUTHENTIK_EMAIL__TIMEOUT=10
AUTHENTIK_EMAIL__FROM=auth@cazzzer.com
AUTHENTIK_DEFAULT_USER_CHANGE_EMAIL=false
AUTHENTIK_DEFAULT_USER_CHANGE_USERNAME=false

View File

@@ -0,0 +1,3 @@
POSTGRES_USER=authentik
POSTGRES_DB=authentik
POSTGRES_PASSWORD={{ db_password }}

View File

@@ -0,0 +1 @@
AUTHENTIK_VERSION=2023.3

View File

@@ -0,0 +1,51 @@
version: 1
metadata:
labels:
blueprints.goauthentik.io/instantiate: "true"
name: Alpina - OAuth2 Apps
entries:
{% set apps = {
"Gitea": {
"redirect_uris": "https://gitea."~ domain ~"/user/oauth2/Authentik/callback",
"icon": "https://gitea."~ domain ~"/assets/img/logo.svg",
},
"Nextcloud": {
"redirect_uris": "https://nc."~ domain ~"/apps/sociallogin/custom_oidc/authentik",
"icon": "https://nc."~ domain ~"/apps/theming/favicon",
},
} -%}
{% for app in apps.keys() -%}
- identifiers:
name: {{ app }}
model: authentik_providers_oauth2.oauth2provider
id: {{ app | lower }}
attrs:
access_code_validity: minutes=1
access_token_validity: minutes=5
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
client_type: confidential
issuer_mode: per_provider
sub_mode: hashed_user_id
property_mappings:
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, openid]]
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, email]]
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, profile]]
redirect_uris: {{ apps[app]["redirect_uris"] }}
refresh_token_validity: days=30
signing_key: !Find [authentik_crypto.certificatekeypair, [name, "authentik Self-signed Certificate"]]
- identifiers:
slug: {{ app | lower }}
model: authentik_core.application
id: {{ app | lower }}
attrs:
name: {{ app }}
group: "Apps"
meta_description: "Hello, I'm {{ app }}!"
meta_publisher: Alpina
# This isn't supported yet, https://github.com/goauthentik/authentik/issues/3484
# meta_icon: "{{ apps[app]["icon"] }}"
open_in_new_tab: true
policy_engine_mode: any
provider: !KeyOf {{ app | lower }}
{% endfor %}

View File

@@ -0,0 +1,73 @@
version: 1
metadata:
labels:
blueprints.goauthentik.io/instantiate: "true"
name: Alpina - Arrstack Proxy
entries:
- identifiers:
name: arrstack
model: authentik_core.group
id: arrstack
attrs:
arrstack_username: "arr"
arrstack_password: "{{ arrstack_password }}"
{% for service in ["qBit", "Prowlarr", "Sonarr", "Radarr"] -%}
- identifiers:
name: {{ service }}
model: authentik_providers_proxy.proxyprovider
id: {{ service | lower }}
attrs:
access_token_validity: hours=24
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
certificate: !Find [authentik_crypto.certificatekeypair, [name, "authentik Self-signed Certificate"]]
{% if service != 'qBit' -%}
basic_auth_enabled: true
basic_auth_user_attribute: arrstack_username
basic_auth_password_attribute: arrstack_password
{% endif -%}
intercept_header_auth: true
external_host: https://{{ service | lower }}.{{ domain }}/
mode: forward_single
property_mappings:
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, openid]]
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, email]]
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, profile]]
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, ak_proxy]]
refresh_token_validity: days=30
skip_path_regex: {{ "/images/qbittorrent-tray.svg" if service == "qBit" else "/Content/Images/logo.svg" }}
- identifiers:
slug: {{ service | lower }}
model: authentik_core.application
id: {{ service | lower }}
attrs:
name: {{ service }}
group: "Arrstack"
meta_description: "Hello, I'm {{ service }}!"
meta_publisher: Alpina
# This isn't supported yet, https://github.com/goauthentik/authentik/issues/3484
# meta_icon: "https://{{ service }}.{{ domain }}/Content/Images/logo.svg"
open_in_new_tab: true
policy_engine_mode: any
provider: !KeyOf {{ service | lower }}
- identifiers:
group: !KeyOf arrstack
target: !Find [authentik_core.application, [slug, {{ service | lower }}]]
model: authentik_policies.policybinding
attrs:
enabled: true
order: 0
timeout: 30
{% endfor %}
- identifiers:
managed: goauthentik.io/outposts/embedded
name: authentik Embedded Outpost
model: authentik_outposts.outpost
attrs:
providers:
{% for service in ["qBit", "Prowlarr", "Sonarr", "Radarr"] -%}
- !KeyOf {{ service | lower }}
{% endfor %}

View File

@@ -0,0 +1,19 @@
version: 1
metadata:
labels:
blueprints.goauthentik.io/instantiate: "true"
name: Alpina - Default Identification Stage
entries:
- identifiers:
name: default-authentication-identification
model: authentik_stages_identification.identificationstage
attrs:
sources:
- !Find [authentik_core.source, [slug, authentik-built-in]]
- !Find [authentik_sources_oauth.oauthsource, [slug, github]]
- identifiers:
slug: default-authentication-flow
model: authentik_flows.flow
attrs:
compatibility_mode: true

View File

@@ -0,0 +1,25 @@
version: 1
metadata:
labels:
blueprints.goauthentik.io/instantiate: "true"
name: Alpina - GitHub OAuth
entries:
- identifiers:
slug: github
model: authentik_sources_oauth.oauthsource
attrs:
name: GitHub
slug: github
access_token_url: https://github.com/login/oauth/access_token
additional_scopes: openid read:org
authentication_flow: !Find [authentik_flows.flow, [slug, default-source-authentication]]
authorization_url: https://github.com/login/oauth/authorize
consumer_key: {{ github_consumer_key }}
consumer_secret: {{ github_consumer_secret }}
enabled: true
enrollment_flow: !Find [authentik_flows.flow, [slug, default-source-enrollment]]
policy_engine_mode: any
profile_url: https://api.github.com/user
provider_type: github
user_matching_mode: email_link
user_path_template: goauthentik.io/sources/%(slug)s

View File

@@ -0,0 +1,68 @@
{% import 'contrib/compose_helpers.j2' as helpers with context %}
{##}
version: "3.9"
networks:
{{ helpers.default_network(253) | indent(2) }}
traefik_traefik:
external: true
services:
server:
image: ghcr.io/goauthentik/server:${AUTHENTIK_VERSION}
container_name: authentik_server
labels:
- {{ helpers.traefik_labels('auth', port='9000') | indent(6) }}
- traefik.http.middlewares.authentik.forwardauth.address=http://localhost:9000/outpost.goauthentik.io/auth/traefik
- traefik.http.middlewares.authentik.forwardauth.trustForwardHeader=true
- traefik.http.middlewares.authentik.forwardauth.authResponseHeaders=Authorization,X-authentik-username,X-authentik-groups,X-authentik-email,X-authentik-name,X-authentik-uid,X-authentik-jwt,X-authentik-meta-jwks,X-authentik-meta-outpost,X-authentik-meta-provider,X-authentik-meta-app,X-authentik-meta-version
restart: unless-stopped
# Port forward is needed because traefik can't resolve the container name from the host network
ports:
- "9000:9000"
command: server
env_file:
- .env.authentik
networks:
- default
- traefik_traefik
worker:
image: ghcr.io/goauthentik/server:${AUTHENTIK_VERSION}
container_name: authentik_worker
restart: unless-stopped
command: worker
env_file:
- .env.authentik
volumes:
- ./blueprints:/blueprints/alpina
- {{ base_volume_path }}/authentik/certs:/certs
postgres:
image: postgres:12-alpine
container_name: authentik_postgres
restart: unless-stopped
env_file:
- .env.db
healthcheck:
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
start_period: 20s
interval: 30s
retries: 5
timeout: 5s
volumes:
- {{ base_volume_path }}/authentik/postgres:/var/lib/postgresql/data
redis:
image: redis:alpine
container_name: authentik_redis
restart: unless-stopped
command: --save 60 1 --loglevel warning
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
start_period: 20s
interval: 30s
retries: 5
timeout: 3s
volumes:
- {{ base_volume_path }}/authentik/redis:/data

View File

@@ -0,0 +1,10 @@
$ANSIBLE_VAULT;1.2;AES256;alpina
35326636356536326332383464373937366130613966663736396135306131353463353334336364
6438346563313332313835326634383063323739643165660a353431336266353239323863363637
34383565626131353530313531663034386530373133653463353063626466613436366235393638
6432363033343336620a336265666362663861393762316137356635363834326566623462373531
62313233306533336331383964346536303362383639633337386664646535313133633164316530
62623535633062656330363931353665396431376233613936626232313264376634646237303236
38396234323931613539393034396461383564363064356635343730633233366666313434646439
34633739333964383865396133313936363166643464613132633031663065623664616365656164
62643035623261623435336462643864396135323139336662363865306661356534

View File

@@ -0,0 +1 @@
CF_DNS_API_TOKEN={{ cloudflare_api_token }}

View File

@@ -0,0 +1,37 @@
{% import 'contrib/compose_helpers.j2' as helpers with context %}
{##}
version: "3.9"
networks:
traefik:
internal: true
enable_ipv6: true
ipam:
config:
- subnet: {{ traefik_ip }}/24
- subnet: {{ docker_ipv6_subnet | ansible.utils.ipsubnet(80, 255) }}
services:
traefik:
image: traefik:v2.9
container_name: traefik
restart: unless-stopped
env_file:
- .env.traefik
network_mode: host
volumes:
- ./traefik.yml:/etc/traefik/traefik.yml:ro
- ./rules:/rules:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- {{ base_volume_path }}/traefik/rules:/rules/extra:ro
- {{ base_volume_path }}/traefik/logs:/logs
- {{ base_volume_path }}/traefik/acme:/acme
# This is mostly just so that the traefik network gets created
whoami:
image: containous/whoami
container_name: whoami
labels:
- {{ helpers.traefik_labels('whoami', port=80) | indent(6) }}
networks:
- traefik

View File

@@ -0,0 +1,25 @@
http:
routers:
traefik-dash:
rule: "Host(`traefik.{{ domain }}`)"
entryPoints:
- web
service: traefik-dash
traefik-dash-tls:
rule: "Host(`traefik.{{ domain }}`)"
entryPoints:
- websecure
service: traefik-dash
tls:
certResolver: letsencrypt
domains:
- main: "{{ domain }}"
sans:
- "*.{{ domain }}"
services:
traefik-dash:
loadBalancer:
servers:
- url: "http://localhost:8080"

View File

@@ -0,0 +1,42 @@
api:
insecure: true
log:
filePath: /logs/traefik.log
level: INFO
accessLog:
filePath: /logs/access.log
bufferingSize: 100
experimental:
http3: true
entryPoints:
web:
address: ":80"
websecure:
address: ":443"
http3:
advertisedPort: 443
certificatesResolvers:
letsencrypt:
acme:
email: {{ acme_email }}
storage: "/acme/acme.json"
keyType: "EC384"
dnsChallenge:
provider: "cloudflare"
delayBeforeCheck: 10
resolvers:
- 1.1.1.1
- 8.8.8.8
- 9.9.9.9
providers:
docker:
exposedByDefault: false
network: traefik_traefik
file:
directory: /rules
watch: true

View File

@@ -0,0 +1,18 @@
- name: Ensure {{ collection }} collection directory exists
file:
path: "{{ my_svc_path }}/{{ collection }}"
state: directory
mode: "700"
- name: Deploy docker compose stacks for {{ collection }}
vars:
current_stack_name: "{{ stack }}"
current_stack_dest: "{{ my_svc_path }}/{{ collection }}/{{ stack }}"
current_stack_source: "{{ role_path }}/collections/{{ collection }}/{{ stack }}"
include_tasks: deploy_compose_stack.yml
loop: "{{ stacks }}"
loop_control:
loop_var: stack
- debug:
var: acme_email

View File

@@ -0,0 +1,40 @@
# https://stackoverflow.com/questions/41667864/can-the-templates-module-handle-multiple-templates-directories
- name: Ensure stack directory exists
file:
path: "{{ current_stack_dest }}"
state: directory
mode: "700"
- name: Ensure directory structure exists
file:
path: "{{ current_stack_dest }}/{{ item.path }}"
state: directory
mode: "700"
with_community.general.filetree: "{{ current_stack_source }}/templates"
when: item.state == "directory"
# TODO: This is not ideal as it leaks the variables between stacks
# But that's also not really a problem, as they won't conflict if everything is done right
- name: Include variables for stack {{ stack }}
include_vars:
file: "{{ current_stack_source }}/app_config.yml"
- name: Generate {{ current_stack_name }} deployment from templates
template:
src: "{{ item.src }}"
dest: "{{ current_stack_dest }}/{{ item.path | regex_replace('\\.j2$', '') }}"
mode: "600"
with_community.general.filetree: "{{ current_stack_source }}/templates"
when: item.state == "file"
- name: Deploy docker-compose for {{ current_stack_name }}
command: docker compose -f "{{ current_stack_dest }}/docker-compose.yml" up -d --pull --remove-orphans
register: docker_compose_output
# Not perfect idempotency, but the built-in docker_compose module doesn't support docker-compose v2
# And of course there's an IPv6 bug in docker-compose v1, smh
# https://github.com/docker/compose/issues/7670
changed_when: "'created' in docker_compose_output.stderr.lower()"
- debug:
var: docker_compose_output

View File

@@ -0,0 +1,23 @@
- name: Ensure alpina directory exists
file:
state: directory
path: "{{ my_svc_path }}"
mode: "700"
- name: Deploy collection services
vars:
collection: services
stacks:
- traefik
- authentik
import_tasks: deploy_collection.yml
- name: Deploy collection apps
vars:
collection: apps
stacks:
- gitea
- nextcloud
- jellyfin
- arrstack
import_tasks: deploy_collection.yml