├── .gitignore
├── README.md
├── all.sh
├── assets
├── jellyfin.svg
├── jellytin-dark.svg
└── jellytin-light.svg
├── authentik
├── README.md
├── docker-compose.yml
└── template.env
├── authentik_ldap
├── .env
├── README.md
├── docker-compose.yml
└── template.env
├── docker_install.sh
├── docs
├── authentik_to_jellyfin.md
├── cloudflare_domain.md
├── jellyfin_client_whitelist.conf
├── jellyfin_client_whitelist.md
├── jellyfin_ldap.md
├── jellyfin_ldap_users.md
├── npm_to_authentik.md
├── tailscale_acl.jsonc
├── tailscale_configure.md
├── vps_harden.md
└── vps_routing.md
├── nginx_proxy_manager
├── README.md
├── docker-compose.yml
├── mounts
│ └── data
│ │ └── nginx
│ │ └── custom
│ │ ├── README.md
│ │ ├── http_top.conf
│ │ └── server_proxy.conf
└── template.env
├── npm_tunnel
├── README.md
├── docker-compose.yml
└── template.env
└── vps_tunnel
├── README.md
├── docker-compose.yml
├── mounts
└── data
│ └── nginx
│ └── custom
│ ├── README.md
│ └── server_proxy.conf
└── template.env
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode/*
2 | venv/*
3 | **/.DS_Store
4 |
5 | secret.env
6 | .env
7 | !authentik_ldap/.env
8 |
9 | */mounts
10 |
11 | !/nginx_proxy_manager/mounts
12 | /nginx_proxy_manager/mounts/*
13 | !/nginx_proxy_manager/mounts/data
14 | /nginx_proxy_manager/mounts/data/*
15 | !/nginx_proxy_manager/mounts/data/nginx
16 | /nginx_proxy_manager/mounts/data/nginx/*
17 | !/nginx_proxy_manager/mounts/data/nginx/custom
18 |
19 | !/vps_tunnel/mounts
20 | /vps_tunnel/mounts/*
21 | !/vps_tunnel/mounts/data
22 | /vps_tunnel/mounts/data/*
23 | !/vps_tunnel/mounts/data/nginx
24 | /vps_tunnel/mounts/data/nginx/*
25 | !/vps_tunnel/mounts/data/nginx/custom
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | # Jellytin
7 |
8 | Put your local Jellyfin server in a tin, and securely serve it up on the internet 🚀
9 |
10 |
11 | ### Intro
12 | This project is especially helpful if you:
13 | 1) Have a local Jellyfin server that you want to access over the internet
14 | 1) Do *not* currently have any infrastructure to expose services to the internet
15 | 1) Wish to hide + secure Jellyfin behind an identity provider
16 | 1) Wish to use Jellyfin [clients](https://jellyfin.org/clients/) (Android, etc.)
17 |
18 | If you're wondering, "why can't I just expose my Jellyfin server to the internet?"
19 | I recommend reading [Collection of potential security issues in Jellyfin](https://github.com/jellyfin/jellyfin/issues/5415)
20 |
21 | The final deployment looks like this:
22 |
23 | 👤 -> `VPS` -> `Nginx` -> `Tailscale` -> `Nginx` -> `Authentik` -> `Jellyfin`
24 |
25 |
26 | ### Dependencies
27 | * Virtual Private Server (VPS)
28 | * [BuyKVM](https://buyvm.net/kvm-dedicated-server-slices/) is $2/month,
29 | but any VPS with an IPv4 address will do
30 | * Additional layer of security, e.g. hide your personal IP
31 | * [Nginx Proxy Manager](https://nginxproxymanager.com/)
32 | * Management UI for Nginx
33 | * [Tailscale](https://tailscale.com/)
34 | * Tunnel from VPS to your local network
35 | * [Authentik](https://goauthentik.io/)
36 | * Provide authentication via LDAP, SSO, etc.
37 | * [Jellyfin](https://jellyfin.org/downloads/)
38 | * You should already have a Jellyfin server
39 |
40 |
41 | ### Future Improvements
42 |
43 | 1) Keep an eye on Jellyfin's [SSO plugin](https://github.com/9p4/jellyfin-plugin-sso)
44 | and incorporate it here, once it is stable and no longer "100% alpha software."
45 |
46 | 1) Await NPM's [Fail2Ban feature request](https://github.com/NginxProxyManager/nginx-proxy-manager/issues/39).
47 |
48 | 1) Await NPM's [CrowdSec feature request](https://github.com/NginxProxyManager/nginx-proxy-manager/issues/1131).
49 |
50 |
51 | ### Footnote
52 | If you're using a Raspberry Pi, then you will need the [64-bit OS](https://www.raspberrypi.com/news/raspberry-pi-os-64-bit/).
53 |
54 |
55 | # Instructions
56 |
57 |
58 | ### Install `docker` & `docker-compose`
59 | * Skip if already installed on your system
60 | * Otherwise, install via [./docker_install.sh](./docker_install.sh)
61 |
62 | ### Deploy Authentik
63 | * Deploy via [./authentik/](./authentik/)
64 | * Generalize yourself with [Outposts, Providers, & Applications](https://goauthentik.io/docs/terminology)
65 | * tl;dr to deploy an application in Authentik, you need an Outpost to service a Provider, which services an Application
66 |
67 | ### Deploy Nginx Proxy Manager
68 | * Deploy via [./nginx_proxy_manager/](./nginx_proxy_manager/)
69 |
70 | ### Purchase & Configure Cloudflare Domain
71 | * Configure via [./docs/cloudflare_domain.md](./docs/cloudflare_domain.md)
72 |
73 | ### Harden Your VPS
74 | * Harden via [./docs/vps_harden.md](./docs/vps_harden.md)
75 |
76 | ### Configure Tailscale
77 | * Configure via [./docs/tailscale_configure.md](./docs/tailscale_configure.md)
78 |
79 | ### Deploy VPS Tunnel
80 | * Deploy via [./vps_tunnel/](./vps_tunnel/)
81 |
82 | ### Deploy NPM Tunnel
83 | * Deploy via [./npm_tunnel/](./npm_tunnel/)
84 |
85 | ### Configure Tunnel Routing
86 | * Configure via [./docs/vps_routing.md](./docs/vps_routing.md)
87 |
88 | ### Configure Nginx -> Authentik
89 | * Configure via [./docs/npm_to_authentik.md](./docs/npm_to_authentik.md)
90 |
91 | ### Configure Authentik -> Jellyfin
92 | * Configure via [./docs/authentik_to_jellyfin.md](./docs/authentik_to_jellyfin.md)
93 |
94 | ### Deploy Authentik LDAP Service
95 | * Deploy via [./authentik_ldap/](./authentik_ldap/)
96 |
97 | ### Configure Jellyfin for LDAP Authentication
98 | * Configure via [./docs/jellyfin_ldap.md](./docs/jellyfin_ldap.md)
99 |
100 | ### Create Jellyfin Users via Authentik
101 | * Create via [./docs/jellyfin_ldap_users.md](./docs/jellyfin_ldap_users.md)
102 |
103 | ### Configure NPM to Enable Jellyfin Client Apps
104 | * Configure via [./docs/jellyfin_client_whitelist.md](./docs/jellyfin_client_whitelist.md)
105 |
106 | ---
107 |
108 | ### The End 🎉
109 |
110 | You can use the helper script at [`./all.sh`](./all.sh) to control this stack.
111 |
--------------------------------------------------------------------------------
/all.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 |
4 | ##
5 | # This is a helper script to run docker-compose
6 | # commands against all the stacks in this repo,
7 | # excluding ./vps_tunnel
8 | #
9 | # for example:
10 | # `./all.sh up -d`
11 | # `./all.sh ps`
12 | # `./all.sh down`
13 | ##
14 |
15 | dir_path() {
16 | # https://stackoverflow.com/a/43919044
17 | a="/$0"; a="${a%/*}"; a="${a:-.}"; a="${a##/}/"; BINDIR=$(cd "$a"; pwd)
18 | echo "$BINDIR"
19 | }
20 | DIR_PATH=`dir_path`
21 |
22 | for COMPOSE_DIR in 'authentik' 'authentik_ldap' 'nginx_proxy_manager' 'npm_tunnel'; do
23 | (cd "$DIR_PATH/$COMPOSE_DIR" && docker-compose "$@") || echo '^^ ERROR ^^'
24 | echo ''
25 | done
26 |
--------------------------------------------------------------------------------
/assets/jellyfin.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/assets/jellytin-dark.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/jellytin-light.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/authentik/README.md:
--------------------------------------------------------------------------------
1 | # Deploy Authentik
2 |
3 |
4 | The contents in this directory have been modified from the Authentik [docs](https://goauthentik.io/docs/installation/docker-compose/).
5 |
6 | * Copy the variables template via `cp ./template.env ./.env`
7 | * Set the `SECRET` values within `./.env`, as described by the above docs
8 | * Deploy via `docker-compose up -d`
9 |
10 | ---
11 |
12 | Login to the gui via below and set the admin password:
13 | > [`http://__HOST_IP__:9000/if/flow/initial-setup/`](http://__HOST_IP__:9000/if/flow/initial-setup/)
14 |
15 | ☝️ Very important -- don't skip that step ⚠️
16 |
17 | ---
18 |
19 | * Open the admin dashboard gui, and go to `Flows & Stages` > `Stages`.
20 | * Click the 📝 icon to update `default-authentication-identification`
21 | * Set `Password stage` = `default-authentication-password`
22 |
23 | ---
24 |
25 | After deploying, you can access the gui via [`http://__HOST_IP__:9000`](http://__HOST_IP__:9000)
26 |
--------------------------------------------------------------------------------
/authentik/docker-compose.yml:
--------------------------------------------------------------------------------
1 | ---
2 | version: '3.4'
3 |
4 | services:
5 | authentik-postgresql:
6 | image: postgres:12-alpine
7 | restart: unless-stopped
8 | healthcheck:
9 | test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
10 | start_period: 20s
11 | interval: 30s
12 | retries: 5
13 | timeout: 5s
14 | volumes:
15 | - authentik_database:/var/lib/postgresql/data
16 | environment:
17 | - POSTGRES_DB=$PG_DB
18 | - POSTGRES_PASSWORD=$PG_PASS
19 | - POSTGRES_USER=$PG_USER
20 | env_file:
21 | - .env
22 | authentik-redis:
23 | image: redis:alpine
24 | restart: unless-stopped
25 | healthcheck:
26 | test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
27 | start_period: 20s
28 | interval: 30s
29 | retries: 5
30 | timeout: 3s
31 | authentik-media-chown:
32 | image: privatebin/chown:$CHOWN_TAG
33 | read_only: true
34 | command: 1000:1000 /mnt
35 | volumes:
36 | - authentik_media:/mnt
37 | authentik-server:
38 | image: ghcr.io/goauthentik/server:$AUTHENTIK_TAG
39 | restart: unless-stopped
40 | depends_on:
41 | - authentik-media-chown
42 | command: server
43 | environment:
44 | AUTHENTIK_POSTGRESQL__HOST: authentik-postgresql
45 | AUTHENTIK_POSTGRESQL__NAME: $PG_DB
46 | AUTHENTIK_POSTGRESQL__PASSWORD: $PG_PASS
47 | AUTHENTIK_POSTGRESQL__USER: $PG_USER
48 | AUTHENTIK_REDIS__HOST: authentik-redis
49 | # AUTHENTIK_ERROR_REPORTING__ENABLED: "true"
50 | # WORKERS: 2
51 | env_file:
52 | - .env
53 | volumes:
54 | - ./mounts/custom_templates/:/templates
55 | - authentik_geoip:/geoip
56 | - authentik_media:/media
57 | ports:
58 | - "0.0.0.0:$AUTHENTIK_PORT_HTTP:9000"
59 | - "0.0.0.0:$AUTHENTIK_PORT_HTTPS:9443"
60 | networks:
61 | - authentik
62 | - default
63 | authentik-worker:
64 | image: ghcr.io/goauthentik/server:$AUTHENTIK_TAG
65 | restart: unless-stopped
66 | depends_on:
67 | - authentik-media-chown
68 | command: worker
69 | environment:
70 | AUTHENTIK_POSTGRESQL__HOST: authentik-postgresql
71 | AUTHENTIK_POSTGRESQL__NAME: $PG_DB
72 | AUTHENTIK_POSTGRESQL__PASSWORD: $PG_PASS
73 | AUTHENTIK_POSTGRESQL__USER: $PG_USER
74 | AUTHENTIK_REDIS__HOST: authentik-redis
75 | # AUTHENTIK_ERROR_REPORTING__ENABLED: "true"
76 | env_file:
77 | - .env
78 | # This is optional, and can be removed. If you remove this, the following will happen
79 | # - The permissions for the /media folders aren't fixed, so make sure they are 1000:1000
80 | # - The docker socket can't be accessed anymore
81 | #user: root
82 | volumes:
83 | - ./mounts/custom_templates/:/templates
84 | #- /var/run/docker.sock:/var/run/docker.sock
85 | - authentik_geoip:/geoip
86 | - authentik_media:/media
87 | authentik-geoipupdate:
88 | image: maxmindinc/geoipupdate:v4.9
89 | restart: unless-stopped
90 | volumes:
91 | - authentik_geoip:/usr/share/GeoIP
92 | environment:
93 | GEOIPUPDATE_EDITION_IDS: "GeoLite2-City"
94 | GEOIPUPDATE_FREQUENCY: "8"
95 | env_file:
96 | - .env
97 | volumes:
98 | authentik_database:
99 | driver: local
100 | authentik_geoip:
101 | driver: local
102 | authentik_media:
103 | driver: local
104 | networks:
105 | authentik:
106 | name: authentik
107 |
--------------------------------------------------------------------------------
/authentik/template.env:
--------------------------------------------------------------------------------
1 | AUTHENTIK_PORT_HTTP=9000
2 | AUTHENTIK_PORT_HTTPS=9443
3 | AUTHENTIK_SECRET_KEY=SECRET
4 | AUTHENTIK_TAG=latest # AUTHENTIK_TAG=2022.8.2 at time of writing
5 | CHOWN_TAG=latest # CHOWN_TAG=1.34.1-musl-1.2.2-r3 at time of writing
6 |
7 | PG_DB=authentik
8 | PG_PASS=SECRET
9 | PG_USER=authentik
10 |
11 | AUTHENTIK_AUTHENTIK__GEOIP=/geoip/GeoLite2-City.mmdb
12 | GEOIPUPDATE_ACCOUNT_ID=SECRET
13 | GEOIPUPDATE_LICENSE_KEY=SECRET
14 |
15 | AUTHENTIK_EMAIL__FROM=SECRET
16 | AUTHENTIK_EMAIL__HOST=smtp.gmail.com # if you want to use gmail -- https://bit.ly/2yyc9EE
17 | AUTHENTIK_EMAIL__PASSWORD=SECRET
18 | AUTHENTIK_EMAIL__PORT=587
19 | AUTHENTIK_EMAIL__TIMEOUT=10
20 | AUTHENTIK_EMAIL__USE_SSL=false
21 | AUTHENTIK_EMAIL__USE_TLS=true
22 | AUTHENTIK_EMAIL__USERNAME=SECRET
23 |
--------------------------------------------------------------------------------
/authentik_ldap/.env:
--------------------------------------------------------------------------------
1 | ../authentik/.env
--------------------------------------------------------------------------------
/authentik_ldap/README.md:
--------------------------------------------------------------------------------
1 | # Deploy Authentik LDAP Outpost
2 |
3 |
4 | * Open the Authentik Admin Dashboard
5 | * Go to `Directory` > `Users`
6 | * Select `Create Service Account`
7 | * Name the user `service-ldap-outpost`
8 | * Go to `Applications` > `Providers`
9 | * Click `Create`
10 | * Select `LDAP Provider`
11 | * On the 2nd tab:
12 | * `Name` = `ldap-provider`
13 | * `Search Group` = `service-ldap-outpost`
14 | * `Base DN` = `DC=ldap,DC=__MY_SITE__,DC=__COM__`
15 | * Use default values for all other fields
16 | * Go to `Applications` > `Applications`
17 | * Click `Create`
18 | * `Name` = `ldap-application`
19 | * `Provider` = `ldap-provider`
20 | * Go to `Applications` > `Outposts`
21 | * Click `Create`
22 | * `Name` = `ldap-outpost`
23 | * `Type` = `LDAP`
24 | * `Integration` = `---------`
25 | * (We'll integrate by deploying via docker-compose)
26 | * `Applications` = `ldap-application`
27 | * Click `View Deployment Info` on the outpost you just created
28 | * (note that you must access the webui via [https](https://__HOST_IP__:9443))
29 | * Copy the `AUTHENTIK_TOKEN`
30 | * Copy the variables template via `cp ./template.env ./secret.env`
31 | * Within `./secret.env`:
32 | * Update `AUTHENTIK_TOKEN` to the value you copied above
33 | * Update `AUTHENTIK_HOST_BROWSER` as necessary
34 | * Deploy via `docker-compose up -d`
35 | * Check that the deploy is working via `docker-compose logs`
36 | * You should see a message like `{"event":"Fetched outpost configuration", ...}`
37 |
--------------------------------------------------------------------------------
/authentik_ldap/docker-compose.yml:
--------------------------------------------------------------------------------
1 | ---
2 | version: '3.4'
3 |
4 | services:
5 | authentik-ldap-outpost:
6 | image: ghcr.io/goauthentik/ldap:$AUTHENTIK_TAG
7 | restart: unless-stopped
8 | env_file:
9 | - secret.env
10 | environment:
11 | AUTHENTIK_HOST: http://authentik-server:9000
12 | ports:
13 | - 389:3389
14 | - 636:6636
15 | networks:
16 | - authentik
17 | networks:
18 | authentik:
19 | name: authentik
20 |
--------------------------------------------------------------------------------
/authentik_ldap/template.env:
--------------------------------------------------------------------------------
1 | AUTHENTIK_HOST_BROWSER=https://auth.__MY_SITE__.__COM__
2 | AUTHENTIK_INSECURE=false
3 | AUTHENTIK_TOKEN=SECRET
4 |
--------------------------------------------------------------------------------
/docker_install.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 |
4 | # install docker
5 | curl -sSL https://get.docker.com | sh
6 | sudo usermod -aG docker "$USER" # assuming current $USER will run docker
7 | exec sudo su -l "$USER" # https://superuser.com/a/609141
8 | docker run hello-world # test the install
9 | sudo systemctl enable docker # start docker on boot
10 |
11 | # install docker-compose
12 | sudo apt-get install libffi-dev libssl-dev
13 | sudo apt install python3-dev
14 | sudo apt-get install -y python3 python3-pip
15 | sudo pip3 install docker-compose
16 |
--------------------------------------------------------------------------------
/docs/authentik_to_jellyfin.md:
--------------------------------------------------------------------------------
1 | # Configure Authentik -> Jellyfin
2 |
3 | * Open the Authentik Admin Dashboard
4 | * Go to `Directory` > `Groups`
5 | * `Create` two groups:
6 | 1) `jellyfin-users`
7 | 1) `jellyfin-admins`
8 | * Go to `Applications` > `Providers`
9 | * Click `Create`
10 | * Select `Proxy Provider`
11 | * On the 2nd tab:
12 | * `Name` = `jellyfin-provider`
13 | * `Authorization flow` = `Authorize Application (default-provider-authorization-implicit-consent)`
14 | * `External host` = `https://jf.__MY_SITE__.__COM__`
15 | * `Internal host` = `http://__JELLYFIN_IP__:__JELLYFIN_PORT__`
16 | * Go to `Applications` > `Applications`
17 | * Click `Create`
18 | * `Name` = `Jellyfin`
19 | * `Provider` = `jellyfin-provider`
20 | * Under `UI settings`
21 | * Upload an `Icon`, e.g. [this](../assets/jellyfin.svg)
22 | * Click `Jellyfin` (the app you just created) > `Policy / Group / User Bindings`
23 | * `Create Binding` for each `Group`:
24 | 1) `jellyfin-users`
25 | 1) `jellyfin-admins`
26 | 1) `authentik Admins`
27 | * Go to `Applications` > `Outposts`
28 | * Click the 📝 icon to update `authentik Embedded Outpost`
29 | * Add `Jellyfin` to the list of `Applications`
30 | * In `Configuration`, ensure that you have `authentik_host: https://auth.__MY_SITE__.__COM__`
31 | * You should now be able to access Jellyfin via [`https://jf.__MY_SITE__.__COM__`](https://jf.__MY_SITE__.__COM__)
32 |
--------------------------------------------------------------------------------
/docs/cloudflare_domain.md:
--------------------------------------------------------------------------------
1 | # Purchase & Configure Cloudflare Domain
2 |
3 | * Purchase your domain via [this](https://developers.cloudflare.com/registrar/get-started/register-domain)
4 | * We'll assume that you purchase a domain of `__MY_SITE__.__COM__`
5 | * Login to [cloudflare](https://cloudflare.com)
6 | * Add wildcard CNAME for subdomains
7 | * Go to `Websites` > `__MY_SITE__.__COM__` > `DNS` > `Add record`
8 | * `Type` = `CNAME`
9 | * `Name` = `*`
10 | * `Target` = `__MY_SITE__.__COM__`
11 | * `Proxy status` = Disabled
12 | * Configure ``
13 | * Sorry, this isn't fully documented...
14 | * ... feel free to open a PR & improve this =)
15 | * Cloudflare will recommend multiple security settings for you, so explore around the dashboard, for example:
16 | * Under DNS management, it should prompt you to set various `DMARC` configs to prevent email spoofing
17 | * There should be a `Review Settings` button on the `Website Overview` dashboard, which points to a link like [this](https://dash.cloudflare.com/__WEBSITE_ID__/__MY_SITE__.__COM__/recommendations)
18 |
--------------------------------------------------------------------------------
/docs/jellyfin_client_whitelist.conf:
--------------------------------------------------------------------------------
1 | access_by_lua_block {
2 | local cjson = require "cjson"
3 | local ip_cache_expiration_seconds = 86400
4 |
5 | function is_active_authentik_user_ip(client_ip)
6 | local ip_cache = ngx.shared.authentik_ip_cache
7 | local is_ip_cached, _flags = ip_cache:get(client_ip)
8 | if is_ip_cached then
9 | return true
10 | elseif is_active_authentik_session(client_ip) then
11 | ip_cache:flush_expired()
12 | success, err, _forcible = ip_cache:set(client_ip, true, ip_cache_expiration_seconds)
13 | if err then
14 | ngx.log(ngx.ERR, "is_active_authentik_user_ip error: ", err)
15 | error(err)
16 | end
17 | return true
18 | else
19 | return false
20 | end
21 | end
22 |
23 | function is_active_authentik_session(client_ip)
24 | ngx.req.read_body()
25 | local res = ngx.location.capture(
26 | "/authentik_sessions/",
27 | {
28 | method = ngx.HTTP_GET,
29 | args = {
30 | last_ip = client_ip,
31 | ordering = "expires desc",
32 | page_size = 1
33 | }
34 | }
35 | )
36 | if res.status ~= 200 then
37 | ngx.log(ngx.ERR, "is_active_authentik_session res.status: ", res.status)
38 | return false
39 | end
40 |
41 | local json_res = cjson.decode(res.body)
42 | if json_res.pagination.count == 0 then
43 | return false
44 | end
45 |
46 | local session = json_res.results[1]
47 | local expiration_time = parse_iso8601(session.expires)
48 | return os.time() < expiration_time
49 | end
50 |
51 | function parse_iso8601(stamp)
52 | local groups, err = ngx.re.match(
53 | stamp,
54 | [[^(?[0-9]{4})-(?[0-9]{1,2})-(?[0-9]{1,2})T(?[0-9]{1,2}):(?[0-9]{1,2}):(?[0-9]{1,2})(?:\.[0-9]+)?Z$]]
55 | )
56 | if err or (groups == nil) then
57 | ngx.log(ngx.ERR, "parse_iso8601 err: " .. err .. " groups: " .. groups)
58 | error(err)
59 | end
60 | return os.time(groups)
61 | end
62 |
63 | if not is_active_authentik_user_ip(ngx.var.remote_addr) then
64 | ngx.exit(ngx.HTTP_FORBIDDEN)
65 | end
66 | }
67 |
--------------------------------------------------------------------------------
/docs/jellyfin_client_whitelist.md:
--------------------------------------------------------------------------------
1 | # Jellyfin Dynamic IP Whitelist
2 |
3 |
4 | ### Background
5 |
6 | Currently, only web browsers are capable of accessing
7 | a Jellyfin server behind Authentik.
8 |
9 | See examples [here](https://github.com/jellyfin/jellyfin-android/issues/123),
10 | [here](https://features.jellyfin.org/posts/471/header-authentication),
11 | & [here](https://features.jellyfin.org/posts/1461/capability-to-specify-client-certificate-for-android-client).
12 |
13 | Following below is a work-around for the lack of authentication
14 | mechanisms supported by the various Jellyfin [clients](https://jellyfin.org/clients/)
15 | (Android, etc.).
16 |
17 | The end result is that the Jellyfin server will be accessible
18 | via any of the client apps, while still not exposed to the
19 | internet at large.
20 |
21 |
22 | You may recall that our current setup flows like this:
23 |
24 | 👤 -> `VPS` -> `Nginx` -> `Tailscale` -> `Nginx` -> `Authentik` -> `Jellyfin Login Screen`
25 |
26 | The work-around implemented here instead does this:
27 |
28 | 👤 -> `VPS` -> `Nginx` -> `Tailscale` -> `{Nginx IP Check}` -> `Jellyfin Login Screen`
29 |
30 |
31 | The logic of the `{Nginx IP Check}` works like this:
32 |
33 | 1) Nginx directly queries Authentik via API to
34 | check if the client IP address is associated
35 | with an unexpired Authentik session
36 | * (Authentik sessions expire after 2 weeks)
37 | 1) If the check passes, then Nginx proxies
38 | directly to Jellyfin, bypassing Authentik
39 | * (Nginx caches allowed IP addresses for 24 hours)
40 |
41 | ---
42 |
43 | ### Create an Authentik API Token
44 |
45 | * Open Authentik Admin web ui
46 | * Click `Directory` > `Tokens & App Passwords` > `Create`
47 | * `Identifier` = `npm-ip-whitelist`
48 | * `User` = `akadmin`
49 | * `Intent` = `API Token`
50 | * Disable `Expiration`
51 | * Click the 📋 button to copy the `npm-ip-whitelist` token
52 | * (We'll use this below)
53 |
54 |
55 | ### Configure Nginx Proxy Manager
56 |
57 | (Use Nginx Proxy Manager running on your local
58 | network, *not* on your VPS).
59 |
60 | * Open NPM and click `Add Proxy Host`
61 | * Click the `Details` tab
62 | * `Domain Names` = `jellyfin-whitelist.__MY_SITE__.__COM__`
63 | * `Scheme` = `http`
64 | * `Hostname` = `does-not-exist`
65 | * (we'll configure this elsewhere)
66 | * `Port` = `4321`
67 | * (we'll configure this elsewhere)
68 | * Enable `Block Common Exploits`
69 | * Enable `Websockets Support`
70 | * Click the `Custom locations` tab
71 | * Click `Add location`
72 | * `location` = `/`
73 | * `Scheme` = `http`
74 | * `Hostname` = `__JELLYFIN_IP__`
75 | * `Port` = `__JELLYFIN_PORT__`
76 | * Click the ⚙️ button to enter your custom Nginx configuration:
77 | * Paste the contents of [./jellyfin_client_whitelist.conf](./jellyfin_client_whitelist.conf)
78 | * Click `Add location`
79 | * `location` = `/authentik_sessions/`
80 | * `Scheme` = `http`
81 | * `Hostname` = `authentik-server/api/v3/core/authenticated_sessions/`
82 | * `Port` = `9000`
83 | * Click the ⚙️ button to enter your custom Nginx configuration:
84 | ```nginx
85 | internal;
86 | proxy_pass_request_headers off;
87 | proxy_pass_request_body off;
88 | proxy_set_header Accept "application/json";
89 | proxy_set_header Authorization "Bearer __Authentik_API_Token__";
90 | ```
91 | * (Replace `__Authentik_API_Token__` with the value from prior)
92 | * `Save` the proxy
93 |
94 |
95 | ### Test the Whitelist
96 |
97 | * Failure/Block Mode
98 | * Try to access `https://jellyfin-whitelist.__MY_SITE__.__COM__` from a new IP address
99 | * (Use your phone, vpn, etc.)
100 | * You should get a `403 Forbidden` error
101 | * Success/Allow Mode
102 | * Login to `https://auth.__MY_SITE__.__COM__`
103 | * Now visit `https://jellyfin-whitelist.__MY_SITE__.__COM__`
104 | * You should get the Jellyfin login page
105 |
106 | ---
107 |
108 | In order to use the Jellyfin clients:
109 |
110 | 1) Login to `https://auth.__MY_SITE__.__COM__` from a web browser
111 | 1) Use `https://jellyfin-whitelist.__MY_SITE__.__COM__:443` from your app
112 |
113 | ☝️ do (1) and (2) from the same IP address
114 |
--------------------------------------------------------------------------------
/docs/jellyfin_ldap.md:
--------------------------------------------------------------------------------
1 | # Configure Jellyfin for LDAP Authentication
2 |
3 | 1) Open the Authentik Admin Dashboard
4 | * Go to `Directory` > `Users`
5 | * Click `📁 Root`
6 | * Click `>` to expand user `service-ldap-outpost`
7 | * Click `Set Password`
8 | * You can use `openssl rand -base64 36` to generate a password
9 | 1) Install the [Jellyfin LDAP-Auth Plugin](https://github.com/jellyfin/jellyfin-plugin-ldapauth#installation)
10 | 1) Configure the plugin's settings via Jellyfin UI
11 | * `LDAP Server` = `__HOST_IP__`
12 | * `Secure LDAP` = false
13 | * `LDAP Base DN for searches` = `dc=ldap,dc=__MY_SITE__,dc=__COM__`
14 | * `LDAP Port` = `389`
15 | * `LDAP Attributes` = `cn`
16 | * `LDAP Name Attribute` = `cn`
17 | * `LDAP User Filter` = `(memberOf=cn=jellyfin-users,ou=groups,dc=ldap,dc=__MY_SITE__,dc=__COM__)`
18 | * `LDAP Admin Filter` = `(memberOf=cn=jellyfin-admins,ou=groups,dc=ldap,dc=__MY_SITE__,dc=__COM__)`
19 | * `LDAP Bind User` = `cn=service-ldap-outpost,ou=users,dc=ldap,dc=__MY_SITE__,dc=__COM__`
20 | * `LDAP Bind User Password` = `__PASSWORD_FROM_ABOVE__`
21 | * `Enable Case Insensitive Username` = true
22 |
--------------------------------------------------------------------------------
/docs/jellyfin_ldap_users.md:
--------------------------------------------------------------------------------
1 | # Create Jellyfin Users via Authentik
2 |
3 | 1) Open the Authentik Admin Dashboard
4 | * Go to `Directory` > `Users`
5 | * Click `Create`
6 | * `Username` = ``
7 | * `Groups` = `jellyfin-users`
8 | * Click `>` to expand user ``
9 | * Click `Set password`
10 | * Configure the user's password
11 | 1) You should now be able to login to Jellyfin with the above user via [`https://jf.__MY_SITE__.__COM__`](https://jf.__MY_SITE__.__COM__)
12 | * (note that with caching enabled on the LDAP Provider, it may take up to 5 minutes for the refresh to load a new user)
13 |
--------------------------------------------------------------------------------
/docs/npm_to_authentik.md:
--------------------------------------------------------------------------------
1 | # Configure Nginx -> Authentik
2 |
3 | (Use Nginx Proxy Manager running on your local
4 | network, *not* on your VPS).
5 |
6 | * Open NPM and click `Add Proxy Host`
7 | * Under the `Details` tab, set these values:
8 | * `Domain Names` = `*.__MY_SITE__.__COM__`
9 | * `Scheme` = `http`
10 | * `Forward Hostname` = `authentik-server`
11 | * `Forward Port` = `9000`
12 | * enable `Block Common Exploits`
13 | * enable `Websockets Support`
14 | * You should now be able to access Authentik via [`https://auth.__MY_SITE__.__COM__`](https://auth.__MY_SITE__.__COM__)
15 |
--------------------------------------------------------------------------------
/docs/tailscale_acl.jsonc:
--------------------------------------------------------------------------------
1 | {
2 | "groups": {
3 | "group:tailscale-admin": ["__YOUR_EMAIL__"],
4 | },
5 | "tagOwners": {
6 | "tag:vps-tunnel": ["group:tailscale-admin"],
7 | "tag:npm-tunnel": ["group:tailscale-admin"],
8 | },
9 | "acls": [
10 | {
11 | "action": "accept",
12 | "src": ["group:tailscale-admin"],
13 | "dst": ["*:*"],
14 | },
15 | {
16 | "action": "accept",
17 | "src": ["tag:vps-tunnel"],
18 | "dst": ["tag:npm-tunnel:80"],
19 | },
20 | {
21 | "action": "accept",
22 | "src": ["tag:npm-tunnel"],
23 | "dst": ["tag:vps-tunnel:80"],
24 | },
25 | ],
26 | "tests": [
27 | { // Accept admin
28 | "src": "group:tailscale-admin",
29 | "accept": ["tag:npm-tunnel:123"],
30 | },
31 | { // Accept admin
32 | "src": "group:tailscale-admin",
33 | "accept": ["tag:vps-tunnel:456"],
34 | },
35 | { // Accept http VPS Tunnel -> NPM Tunnel
36 | "src": "tag:vps-tunnel",
37 | "accept": ["tag:npm-tunnel:80"],
38 | },
39 | { // Accept http NPM Tunnel -> VPS Tunnel
40 | "src": "tag:npm-tunnel",
41 | "accept": ["tag:vps-tunnel:80"],
42 | },
43 | { // Deny non-http VPS Tunnel -> NPM Tunnel
44 | "src": "tag:vps-tunnel",
45 | "deny": ["tag:npm-tunnel:123"],
46 | },
47 | { // Deny non-http NPM Tunnel -> VPS Tunnel
48 | "src": "tag:npm-tunnel",
49 | "deny": ["tag:vps-tunnel:456"],
50 | },
51 | { // Deny VPS Tunnel -> Admin
52 | "src": "tag:vps-tunnel",
53 | "deny": ["group:tailscale-admin:80"],
54 | },
55 | { // Deny NPM Tunnel -> Admin
56 | "src": "tag:npm-tunnel",
57 | "deny": ["group:tailscale-admin:80"],
58 | },
59 | ],
60 | }
61 |
--------------------------------------------------------------------------------
/docs/tailscale_configure.md:
--------------------------------------------------------------------------------
1 | # Configure Tailscale
2 |
3 | * [Register](https://login.tailscale.com/start) for a Tailscale Account
4 | * `Enable MagicDNS` for Tailscale [here](https://login.tailscale.com/admin/dns)
5 | * Modify Tailscale ACL [here](https://login.tailscale.com/admin/acls)
6 | * Replace `__YOUR_EMAIL__` in [./tailscale_acl.jsonc](./tailscale_acl.jsonc)
7 |
--------------------------------------------------------------------------------
/docs/vps_harden.md:
--------------------------------------------------------------------------------
1 | # Harden Your VPS
2 |
3 | Unless otherwise noted, all commands are to be run on your VPS.
4 |
5 | Your VPS provider should have given you credentials to SSH to your VPS, for example:
6 |
7 | ```shell
8 | ssh "root@$VPS_IP"
9 | #
10 | ```
11 |
12 |
13 | ### Create Non-Root User
14 |
15 | ```shell
16 | YOUR_USER=''
17 | useradd -m -s /bin/bash "$YOUR_USER" # create user
18 | passwd "$YOUR_USER" # set password
19 | usermod -aG sudo "$YOUR_USER" # add to sudo
20 | id "$YOUR_USER" # should show sudo membership
21 | which sudo || apt install sudo # ensure sudo package install
22 | su "$YOUR_USER" # use this user going forward
23 | ```
24 |
25 |
26 | ### Configure User SSH Key
27 |
28 | Run these commands on your local machine
29 |
30 | ```shell
31 | KEY_NAME=''
32 | VPS_IP=''
33 |
34 | # create your public+private key pair
35 | ssh-keygen -t rsa -b 4096 -o -a 100 -f ~/.ssh/"$KEY_NAME"
36 |
37 | # ensure keys are only read-writable by $USER
38 | chmod 600 ~/.ssh/"$KEY_NAME" ~/.ssh/"$KEY_NAME".pub
39 |
40 | # install public key on VPS
41 | ssh-copy-id -i ~/.ssh/"$KEY_NAME".pub "$YOUR_USER@$VPS_IP"
42 |
43 | # test login with private key
44 | ssh -i ~/.ssh/"$KEY_NAME" "$YOUR_USER@$VPS_IP"
45 | ```
46 |
47 |
48 | ### Configure SSH Non-Standard Port
49 |
50 | ```shell
51 | # get a random port
52 | # https://unix.stackexchange.com/a/423052
53 | SSH_RAND_PORT=`comm -23 <(seq 49152 65535 | sort) <(ss -Htan | awk '{print $4}' | cut -d':' -f2 | sort -u) | shuf | head -n 1`
54 | echo "SSH_RAND_PORT = $SSH_RAND_PORT"
55 |
56 | # update /etc/ssh/sshd_config
57 | sudo sed -i.bak "s/^#Port 22$/Port $SSH_RAND_PORT/" /etc/ssh/sshd_config
58 |
59 | # review changes
60 | diff /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
61 |
62 | # reboot with changes
63 | sudo systemctl restart sshd
64 |
65 | # confirm reboot
66 | systemctl status sshd
67 |
68 | # from a shell on your local machine, confirm that you can connect via new port
69 | ssh -i ~/.ssh/"$KEY_NAME" -p "$SSH_RAND_PORT" "$YOUR_USER@$VPS_IP"
70 |
71 | # remove backup
72 | sudo rm /etc/ssh/sshd_config.bak
73 | ```
74 |
75 |
76 | ### Configure SSH Client
77 |
78 | Run these commands on your local machine to append to `~/.ssh/config`
79 |
80 | ```shell
81 | VPS_SSH_NAME='vps'
82 |
83 | cat << EOF >> ~/.ssh/config
84 | Host $VPS_SSH_NAME
85 | Hostname $VPS_IP
86 | Port $SSH_RAND_PORT
87 | User $YOUR_USER
88 | IdentityFile ~/.ssh/"$KEY_NAME"
89 | EOF
90 |
91 | # test that you can SSH via config
92 | ssh "$VPS_SSH_NAME"
93 | ```
94 |
95 |
96 | ### Limit SSH Login Methods
97 |
98 | We are going to config the following in `/etc/ssh/sshd_config`:
99 |
100 | ```
101 | ChallengeResponseAuthentication no
102 | PasswordAuthentication no
103 | UsePAM no
104 | PermitRootLogin no
105 | PermitRootLogin prohibit-password
106 | ```
107 |
108 | After this, you will only be able to SSH using (1) a key and (2) a non-root user.
109 |
110 | ```shell
111 | # update /etc/ssh/sshd_config
112 | new_line=$'\n'
113 | sudo sed -i.bak -r \
114 | -e 's/^#?ChallengeResponseAuthentication (yes|no)/ChallengeResponseAuthentication no/' \
115 | -e 's/^#?PasswordAuthentication (yes|no)/PasswordAuthentication no/' \
116 | -e 's/^#?UsePAM (yes|no)/UsePAM no/' \
117 | -e "s/^#?PermitRootLogin (yes|no)/PermitRootLogin no\\${new_line}PermitRootLogin prohibit-password/" \
118 | /etc/ssh/sshd_config
119 |
120 | # review changes
121 | diff /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
122 |
123 | # reboot with changes
124 | sudo systemctl restart sshd
125 |
126 | # confirm reboot
127 | systemctl status sshd
128 |
129 | # using your local machine, confirm that you can still connect
130 | ssh "$VPS_SSH_NAME"
131 |
132 | # remove backup
133 | sudo rm /etc/ssh/sshd_config.bak
134 | ```
135 |
136 | Run these commands on your local machine
137 |
138 | ```shell
139 | # check that root is blocked
140 | ssh -p "$SSH_RAND_PORT" "root@$VPS_IP"
141 |
142 | # check that password is blocked
143 | ssh -p "$SSH_RAND_PORT" "$YOUR_USER@$VPS_IP" -o 'PubkeyAuthentication=no'
144 | ```
145 |
146 |
147 | ### Disable SSH v1 Protocol
148 |
149 | ```shell
150 | # backup
151 | sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
152 |
153 | # append protocol config
154 | sudo tee -a /etc/ssh/sshd_config </dev/null
155 |
156 | # Only allow SSH2
157 | Protocol 2
158 | EOF
159 |
160 | # review changes
161 | diff /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
162 |
163 | # reboot with changes
164 | sudo systemctl restart sshd
165 |
166 | # confirm reboot
167 | systemctl status sshd
168 |
169 | # using your local machine, confirm that you can still connect
170 | ssh "$VPS_SSH_NAME"
171 |
172 | # remove backup
173 | sudo rm /etc/ssh/sshd_config.bak
174 | ```
175 |
--------------------------------------------------------------------------------
/docs/vps_routing.md:
--------------------------------------------------------------------------------
1 | # Route 👤 -> `VPS` -> `Nginx` -> `Tailscale` -> `Nginx`
2 |
3 |
4 | ### Configure `Nginx` -> `Tailscale` -> `Nginx`
5 |
6 | Reminder for how to access Nginx Proxy Manager on the VPS:
7 | * Copy the value of your `Tailnet name` from [here](https://login.tailscale.com/admin/dns)
8 | * The admin gui is available at [`http://vps-tunnel.__TAILNET_NAME__:81`](http://vps-tunnel.__TAILNET_NAME__:81)
9 |
10 | ---
11 |
12 | 1) Create Cloudflare API Token
13 | * Open the API Token dashboard [here](https://dash.cloudflare.com/profile/api-tokens)
14 | * Click `Create Token`
15 | * Click `Use template` for `Edit zone DNS`
16 | * Under `Zone Resources` select `__MY_SITE__.__COM__`
17 | * Under `Client IP Address Filtering` set `Is in` = `__VPS_IP__`
18 | * Click `Continue to Summary` > `Create Token`
19 | * Copy and record the token
20 | 1) Add SSL Certificate to Nginx Proxy Manager on the VPS
21 | * Open the `SSL Certificates` tab in NPM
22 | * Click `Add SSL Certificate`
23 | * `Domain Names` = `*.__MY_SITE__.__COM__` and `__MY_SITE__.__COM__`
24 | * Enable `Use a DNS Challenge`
25 | * `DNS Provider` = `Cloudflare`
26 | * Update `Credentials File Content` with your Cloudflare API token from above
27 | * Click `Save`
28 | 1) Define Reverse Proxy for Nginx Proxy Manager on the VPS
29 | * Open the `Hosts` > `Proxy Hosts` tab in NPM
30 | * Click `Add Proxy Host`
31 | * Under the `Details` tab
32 | * `Domain Names` = `*.__MY_SITE__.__COM__`
33 | * `Scheme` = `http`
34 | * `Forward Host` = `__NPM-TUNNEL_IP__`
35 | * (Acquire the above IP from [here](https://login.tailscale.com/admin/machines))
36 | * `Port` = `80`
37 | * Enable `Block Common Exploits`
38 | * Enable `Websockets Support`
39 | * Under the `SSL` tab
40 | * `SSL Certificate` = *the certificate you created above*
41 | * Enable `Force SSL`
42 | * Enable `HTTP/2 Support`
43 | * Click `Save`
44 | 1) Define Redirection Host for Nginx Proxy Manager on the VPS
45 | * Open the `Hosts` > `Redirection Hosts` tab in NPM
46 | * Click `Add Redirection Host`
47 | * Under the `Details` tab
48 | * `Domain Names` = `__MY_SITE__.__COM__`
49 | * `Scheme` = `https`
50 | * `Forward Domain` = `auth.__MY_SITE__.__COM__`
51 | * `HTTP Code` = `301`
52 | * Enable `Preserve Path`
53 | * Enable `Block Common Exploits`
54 | * Under the `SSL` tab
55 | * `SSL Certificate` = *the certificate you created above*
56 | * Enable `Force SSL`
57 | * Enable `HTTP/2 Support`
58 | * Click `Save`
59 |
60 |
61 | ### Configure Cloudflare DNS -> `VPS`
62 |
63 | * Login to the Cloudflare dashboard [here](https://dash.cloudflare.com)
64 | * Click through to `Websites` > `__MY_SITE__.__COM__` > `DNS`
65 | * Click `Edit` for `CNAME` of `__MY_SITE__.__COM__`
66 | * `Delete` the record
67 | * Click `Add Record`
68 | * `Type` = `A`
69 | * `Name` = `__MY_SITE__.__COM__`
70 | * `IPv4 address` = `__VPS_IP__`
71 | * `Proxy status` = Disabled
72 |
--------------------------------------------------------------------------------
/nginx_proxy_manager/README.md:
--------------------------------------------------------------------------------
1 | This was modified from the NPM [docs](https://nginxproxymanager.com/setup/#running-the-app)
2 |
3 | * Copy the variables template via `cp ./template.env ./.env`
4 | * Open `./.env` and set the SECRET values
5 | * Deploy via `docker-compose up -d`
6 |
7 | ---
8 |
9 | After deploying, you can access the admin gui via [`http://__HOST_IP__:81`](http://__HOST_IP__:81)
10 |
11 | ---
12 |
13 | Login to the gui with the below and reset your password:
14 | ```yml
15 | Email: admin@example.com
16 | Password: changeme
17 | ```
18 |
19 | ☝️ Very important -- don't skip that step ⚠️
20 |
--------------------------------------------------------------------------------
/nginx_proxy_manager/docker-compose.yml:
--------------------------------------------------------------------------------
1 | ---
2 | version: "3"
3 |
4 | services:
5 | nginx-proxy-manager:
6 | image: jc21/nginx-proxy-manager:$NPM_TAG
7 | restart: unless-stopped
8 | healthcheck:
9 | test: ["CMD", "/bin/check-health"]
10 | interval: 10s
11 | timeout: 3s
12 | depends_on:
13 | - npm-mysql
14 | environment:
15 | DB_MYSQL_HOST: npm-mysql
16 | DB_MYSQL_NAME: $NPM__MYSQL_DB
17 | DB_MYSQL_PASSWORD: $NPM__MYSQL_PASS
18 | DB_MYSQL_PORT: 3306
19 | DB_MYSQL_USER: $NPM__MYSQL_USER
20 | # Uncomment if IPv6 is not enabled on your host
21 | # DISABLE_IPV6: true
22 | volumes:
23 | - ./mounts/data:/data
24 | - npm_letsencrypt:/etc/letsencrypt
25 | ports:
26 | - 80:80
27 | - 443:443
28 | - 81:81
29 | networks:
30 | - authentik
31 | - default
32 | npm-mysql:
33 | image: yobasystems/alpine-mariadb:10.6
34 | restart: unless-stopped
35 | environment:
36 | MYSQL_DATABASE: $NPM__MYSQL_DB
37 | MYSQL_PASSWORD: $NPM__MYSQL_PASS
38 | MYSQL_ROOT_PASSWORD: $NPM__MYSQL_ROOT_PASS
39 | MYSQL_USER: $NPM__MYSQL_USER
40 | volumes:
41 | - npm_database:/var/lib/mysql
42 | volumes:
43 | npm_database:
44 | driver: local
45 | npm_letsencrypt:
46 | driver: local
47 | networks:
48 | authentik:
49 | name: authentik
50 |
--------------------------------------------------------------------------------
/nginx_proxy_manager/mounts/data/nginx/custom/README.md:
--------------------------------------------------------------------------------
1 | see the [docs](https://nginxproxymanager.com/advanced-config/#custom-nginx-configurations)
2 |
--------------------------------------------------------------------------------
/nginx_proxy_manager/mounts/data/nginx/custom/http_top.conf:
--------------------------------------------------------------------------------
1 | lua_shared_dict authentik_ip_cache 1m;
2 |
--------------------------------------------------------------------------------
/nginx_proxy_manager/mounts/data/nginx/custom/server_proxy.conf:
--------------------------------------------------------------------------------
1 | set_real_ip_from 100.64.0.0/10; # https://tailscale.com/kb/1015/100.x-addresses/
2 | real_ip_header X-Real-IP;
3 | real_ip_recursive on;
4 |
--------------------------------------------------------------------------------
/nginx_proxy_manager/template.env:
--------------------------------------------------------------------------------
1 | # replace SECRET with any random values
2 | # e.g. `openssl rand -base64 36`
3 |
4 | NPM__MYSQL_DB=npm_db
5 | NPM__MYSQL_PASS=SECRET
6 | NPM__MYSQL_ROOT_PASS=SECRET
7 | NPM__MYSQL_USER=npm_user
8 | NPM_TAG=latest # NPM_TAG=2.9.14 at time of writing
9 |
--------------------------------------------------------------------------------
/npm_tunnel/README.md:
--------------------------------------------------------------------------------
1 | # Deploy Tailscale
2 |
3 |
4 | 1) Config `./.env`
5 | * Copy the variables template via `cp ./template.env ./.env`
6 | 1) Authenticate Tailscale Container
7 | * Execute `docker-compose run --rm tailscale /bin/sh /tailscale/run.sh`
8 | * Copy the URL from the above command's output
9 | * Paste the URL into your web browser
10 | * Authorize the container
11 | * The container's output should update with an eventual `health("overall"): ok`
12 | * `Ctrl-C` to kill the container
13 | 1) Disable Key Expiry for `npm-tunnel` machine
14 | * Via the [docs](https://tailscale.com/kb/1028/key-expiry/#disabling-key-expiry)
15 | 1) Deploy the Stack
16 | * Deploy via `docker-compose up -d`
17 | * Check tailscale status:
18 | ```shell
19 | docker exec -it $(docker container ls | grep tailscale | awk '{print $1}') tailscale status
20 | ```
21 |
--------------------------------------------------------------------------------
/npm_tunnel/docker-compose.yml:
--------------------------------------------------------------------------------
1 | ---
2 | version: "3"
3 |
4 | services:
5 | tailscale:
6 | privileged: true
7 | network_mode: host
8 | cap_add:
9 | - net_admin
10 | - sys_module
11 | image: tailscale/tailscale:$TAILSCALE_TAG
12 | restart: unless-stopped
13 | hostname: npm-tunnel
14 | command: tailscaled --statedir=/var/lib/tailscale_state
15 | environment:
16 | TS_EXTRA_ARGS: --advertise-tags=tag:npm-tunnel
17 | TS_SOCKET: /var/run/tailscale/tailscaled.sock
18 | TS_STATE_DIR: /var/lib/tailscale_state
19 | volumes:
20 | - tailscale_state:/var/lib/tailscale_state
21 | - /dev/net/tun:/dev/net/tun
22 | volumes:
23 | tailscale_state:
24 | driver: local
25 |
--------------------------------------------------------------------------------
/npm_tunnel/template.env:
--------------------------------------------------------------------------------
1 | TAILSCALE_TAG=stable # TAILSCALE_TAG=v1.32.0 at time of writing
2 |
--------------------------------------------------------------------------------
/vps_tunnel/README.md:
--------------------------------------------------------------------------------
1 | # Deploy VPS Tunnel Stack
2 |
3 |
4 | ### Prerequisites
5 |
6 | * Install [docker-compose](../docker_install.sh) on your VPS
7 |
8 |
9 | ### Deploy Tunnel on VPS
10 |
11 | 1) Config `./.env` Secrets
12 | * Copy the variables template via `cp ./template.env ./.env`
13 | * Open `./.env` and set the SECRET values
14 | 1) Authenticate Tailscale Container
15 | * Execute `docker-compose run --rm tailscale /bin/sh /tailscale/run.sh`
16 | * Copy the URL from the above command's output
17 | * Paste the URL into your web browser
18 | * Authorize the container
19 | * The container's output should update with an eventual `health("overall"): ok`
20 | * `Ctrl-C` to kill the container
21 | 1) Disable Key Expiry for `vps-tunnel` machine
22 | * Via the [docs](https://tailscale.com/kb/1028/key-expiry/#disabling-key-expiry)
23 | 1) Deploy the Stack
24 | * Deploy via `docker-compose up -d`
25 | * Check the stack's health via `docker-compose ps`
26 | * All 3 containers should eventually have `State` = `Up`
27 | * Check tailscale status:
28 | ```shell
29 | docker exec -it $(docker container ls | grep tailscale | awk '{print $1}') tailscale status
30 | ```
31 |
32 |
33 | ### Configure NPM Admin
34 |
35 | * Ensure you have a [Tailscale client](https://tailscale.com/download) running on your local machine
36 | * Copy the value of your `Tailnet name` from [here](https://login.tailscale.com/admin/dns)
37 | * Open the Nginx Proxy Manager admin gui via [`http://vps-tunnel.__TAILNET_NAME__:81`](http://vps-tunnel.__TAILNET_NAME__:81)
38 | * Login to the gui with the below and reset your password:
39 | ```yml
40 | Email: admin@example.com
41 | Password: changeme
42 | ```
43 | ☝️ Very important -- don't skip that step ⚠️
44 |
--------------------------------------------------------------------------------
/vps_tunnel/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3.9"
2 | services:
3 | tailscale:
4 | privileged: true
5 | network_mode: host
6 | cap_add:
7 | - net_admin
8 | - sys_module
9 | image: tailscale/tailscale:$TAILSCALE_TAG
10 | restart: unless-stopped
11 | hostname: vps-tunnel
12 | command: tailscaled --statedir=/var/lib/tailscale_state
13 | environment:
14 | TS_EXTRA_ARGS: --advertise-tags=tag:vps-tunnel
15 | TS_SOCKET: /var/run/tailscale/tailscaled.sock
16 | TS_STATE_DIR: /var/lib/tailscale_state
17 | volumes:
18 | - tailscale_state:/var/lib/tailscale_state
19 | - /dev/net/tun:/dev/net/tun
20 | nginx-proxy-manager:
21 | image: jc21/nginx-proxy-manager:$NPM_TAG
22 | restart: unless-stopped
23 | depends_on:
24 | - npm-mysql
25 | - tailscale
26 | healthcheck:
27 | test: ["CMD", "/bin/check-health"]
28 | interval: 10s
29 | timeout: 3s
30 | environment:
31 | DB_MYSQL_HOST: npm-mysql
32 | DB_MYSQL_NAME: $NPM__MYSQL_DB
33 | DB_MYSQL_PASSWORD: $NPM__MYSQL_PASS
34 | DB_MYSQL_PORT: 3306
35 | DB_MYSQL_USER: $NPM__MYSQL_USER
36 | # Uncomment if IPv6 is not enabled on your host
37 | # DISABLE_IPV6: true
38 | volumes:
39 | - ./mounts/data:/data
40 | - npm_letsencrypt:/etc/letsencrypt
41 | ports:
42 | - 80:80
43 | - 443:443
44 | - 81:81
45 | npm-mysql:
46 | image: yobasystems/alpine-mariadb:10.6
47 | restart: unless-stopped
48 | environment:
49 | MYSQL_DATABASE: $NPM__MYSQL_DB
50 | MYSQL_PASSWORD: $NPM__MYSQL_PASS
51 | MYSQL_ROOT_PASSWORD: $NPM__MYSQL_ROOT_PASS
52 | MYSQL_USER: $NPM__MYSQL_USER
53 | volumes:
54 | - npm_database:/var/lib/mysql
55 | volumes:
56 | npm_database:
57 | driver: local
58 | npm_letsencrypt:
59 | driver: local
60 | tailscale_state:
61 | driver: local
62 |
--------------------------------------------------------------------------------
/vps_tunnel/mounts/data/nginx/custom/README.md:
--------------------------------------------------------------------------------
1 | see the [docs](https://nginxproxymanager.com/advanced-config/#custom-nginx-configurations)
2 |
--------------------------------------------------------------------------------
/vps_tunnel/mounts/data/nginx/custom/server_proxy.conf:
--------------------------------------------------------------------------------
1 | proxy_set_header X-Real-IP $remote_addr;
2 |
--------------------------------------------------------------------------------
/vps_tunnel/template.env:
--------------------------------------------------------------------------------
1 | # replace NPM__MYSQL_* SECRET with any random values
2 | # e.g. `openssl rand -base64 36`
3 |
4 | NPM__MYSQL_DB=npm_db
5 | NPM__MYSQL_PASS=SECRET
6 | NPM__MYSQL_ROOT_PASS=SECRET
7 | NPM__MYSQL_USER=npm_user
8 | NPM_TAG=latest # NPM_TAG=2.9.14 at time of writing
9 | TAILSCALE_TAG=stable # TAILSCALE_TAG=v1.32.0 at time of writing
10 |
--------------------------------------------------------------------------------