feat(apps): WireGuard, Tailscale, Cloudflare Tunnel en Proton Mail Bridge
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 3m18s
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 3m18s
De catalogus had niets om een machine elders bij je eigen netwerk te laten komen, en niets om Proton Mail bruikbaar te maken voor apps die geen Proton spreken. - wireguard: client (eigen wg0.conf in wg_confs) of server, met NET_ADMIN, SYS_MODULE, de kernelmodules van de host en src_valid_mark. - tailscale: network_mode host zodat de tunnel voor de hele machine geldt, met bewaarde staat en optionele subnetroutes. - cloudflared: publiceert zelf geen poorten en werkt zichzelf niet bij. - protonmail-bridge: SMTP op 1026 zodat hij naast Mailpit past; het sjabloon legt de eenmalige aanmelding via de CLI uit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H9Vcwu7owJZ3nj5dCf4VFT
This commit is contained in:
parent
5068997599
commit
ec49337167
11 changed files with 520 additions and 3 deletions
22
CHANGELOG.md
22
CHANGELOG.md
|
|
@ -1,4 +1,24 @@
|
||||||
# v0.8.30-beta — De updater neemt genoegen met het adres uit je adresbalk
|
# v0.8.30-beta — Verbinden met een netwerk, en de updater neemt genoegen met het adres uit je adresbalk
|
||||||
|
|
||||||
|
Vier nieuwe apps voor iets wat de catalogus nog niet had: een machine ergens
|
||||||
|
anders bij je eigen netwerk laten komen, en Proton Mail bruikbaar maken voor
|
||||||
|
apps die geen Proton spreken.
|
||||||
|
|
||||||
|
- **WireGuard.** Verbind deze machine met een ander netwerk door je eigen
|
||||||
|
`wg0.conf` in de map `wg_confs` te zetten, of laat hem zelf een server met
|
||||||
|
clientprofielen aanmaken. Krijgt de rechten die een tunnel nodig heeft:
|
||||||
|
`NET_ADMIN`, de kernelmodules van de host en `src_valid_mark`.
|
||||||
|
- **Tailscale.** Zet de machine in je eigen tailnet, met `network_mode: host`
|
||||||
|
zodat de tunnel voor de hele machine geldt. Optioneel als subnetrouter, zodat
|
||||||
|
je bij je hele thuisnetwerk kunt.
|
||||||
|
- **Cloudflare Tunnel.** Een app van buitenaf bereikbaar zonder een poort open
|
||||||
|
te zetten; publiceert zelf niets, want dat is het hele punt.
|
||||||
|
- **Proton Mail Bridge.** Vertaalt je Proton-account naar gewone IMAP en SMTP.
|
||||||
|
Staat op poort 1026 in plaats van 1025, zodat hij naast Mailpit past. Het
|
||||||
|
sjabloon vertelt hoe je de eenmalige aanmelding doet — die kan niet via een
|
||||||
|
formulier, want Proton vraagt om een tweede factor.
|
||||||
|
|
||||||
|
En de updater is niet meer kieskeurig over het adres dat je invult.
|
||||||
|
|
||||||
Wie in Forgejo naar zijn releases keek en dat adres in de instellingen plakte,
|
Wie in Forgejo naar zijn releases keek en dat adres in de instellingen plakte,
|
||||||
kreeg een webpagina in plaats van JSON — en als enige melding
|
kreeg een webpagina in plaats van JSON — en als enige melding
|
||||||
|
|
|
||||||
12
apps/cloudflared/files/compose.yaml
Normal file
12
apps/cloudflared/files/compose.yaml
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# De tunnel belt zelf naar buiten naar Cloudflare, dus er hoeft geen poort open
|
||||||
|
# in je router en je publieke IP blijft onzichtbaar. Wat er door de tunnel
|
||||||
|
# bereikbaar is, bepaal je in het Cloudflare-dashboard bij de tunnel zelf.
|
||||||
|
services:
|
||||||
|
<< service_name >>:
|
||||||
|
image: cloudflare/cloudflared:latest
|
||||||
|
container_name: << service_name >>
|
||||||
|
command: tunnel --no-autoupdate run --token << tunnel_token >>
|
||||||
|
environment:
|
||||||
|
- TZ=<< timezone >>
|
||||||
|
- TUNNEL_METRICS=0.0.0.0:2000
|
||||||
|
restart: unless-stopped
|
||||||
31
apps/cloudflared/template.json
Normal file
31
apps/cloudflared/template.json
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
"kind": "compose",
|
||||||
|
"metadata": {
|
||||||
|
"name": "Cloudflare Tunnel",
|
||||||
|
"description": "Maak een app van buitenaf bereikbaar zonder een poort open te zetten. De tunnel belt zelf naar Cloudflare; je publieke IP blijft onzichtbaar en er komt niets ongevraagd binnen.",
|
||||||
|
"tags": ["tunnel", "proxy", "op afstand", "netwerk", "cloudflare"],
|
||||||
|
"icon": {"provider": "selfhst", "id": "cloudflare"},
|
||||||
|
"version": {"name": "latest"},
|
||||||
|
"categories": ["netwerk", "beveiliging"],
|
||||||
|
"architectures": ["amd64", "arm64"]
|
||||||
|
},
|
||||||
|
"variables": [
|
||||||
|
{
|
||||||
|
"title": "Algemeen",
|
||||||
|
"items": [
|
||||||
|
{"name": "service_name", "type": "str", "title": "Servicenaam",
|
||||||
|
"default": "cloudflared", "required": true},
|
||||||
|
{"name": "timezone", "type": "str", "title": "Tijdzone",
|
||||||
|
"default": "Europe/Amsterdam", "required": true}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Toegang",
|
||||||
|
"items": [
|
||||||
|
{"name": "tunnel_token", "type": "str", "title": "Tunneltoken",
|
||||||
|
"default": "", "required": true, "secret": true, "eigen_invoer": true,
|
||||||
|
"help": "Maak in het Zero Trust-dashboard van Cloudflare een tunnel aan; bij 'Docker' staat het token in het voorbeeldcommando. Koppel daar ook welke app op welk adres uitkomt."}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
24
apps/protonmail-bridge/files/compose.yaml
Normal file
24
apps/protonmail-bridge/files/compose.yaml
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
# De bridge vertaalt je Proton Mail-account naar gewone IMAP en SMTP, zodat
|
||||||
|
# apps die geen Proton spreken er toch bij kunnen.
|
||||||
|
#
|
||||||
|
# Eenmalig aanmelden gebeurt in de container zelf; dat kan niet via een
|
||||||
|
# formulier omdat Proton om een tweede factor kan vragen:
|
||||||
|
#
|
||||||
|
# docker exec -it << service_name >> /protonmail/bridge --cli
|
||||||
|
# login — je Proton-account, inclusief tweede factor
|
||||||
|
# info — toont de gebruikersnaam en het wachtwoord voor je mailclient
|
||||||
|
#
|
||||||
|
# Let op: het wachtwoord dat 'info' toont is een bridge-wachtwoord, niet je
|
||||||
|
# Proton-wachtwoord. Dat vul je in bij de app die mail moet versturen.
|
||||||
|
services:
|
||||||
|
<< service_name >>:
|
||||||
|
image: shenxn/protonmail-bridge:latest
|
||||||
|
container_name: << service_name >>
|
||||||
|
environment:
|
||||||
|
- TZ=<< timezone >>
|
||||||
|
volumes:
|
||||||
|
- << appdata_dir >>/<< service_name >>:/root
|
||||||
|
ports:
|
||||||
|
- "<< port_imap >>:143"
|
||||||
|
- "<< port_smtp >>:25"
|
||||||
|
restart: unless-stopped
|
||||||
75
apps/protonmail-bridge/template.json
Normal file
75
apps/protonmail-bridge/template.json
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
{
|
||||||
|
"kind": "compose",
|
||||||
|
"metadata": {
|
||||||
|
"name": "Proton Mail Bridge",
|
||||||
|
"description": "Vertaalt je Proton Mail-account naar gewone IMAP en SMTP, zodat apps die geen Proton spreken toch mail kunnen lezen en versturen. Denk aan meldingen uit Uptime Kuma, Vaultwarden of Paperless.",
|
||||||
|
"tags": [
|
||||||
|
"mail",
|
||||||
|
"smtp",
|
||||||
|
"imap",
|
||||||
|
"proton",
|
||||||
|
"meldingen"
|
||||||
|
],
|
||||||
|
"icon": {
|
||||||
|
"provider": "selfhst",
|
||||||
|
"id": "proton-mail"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"name": "latest"
|
||||||
|
},
|
||||||
|
"categories": [
|
||||||
|
"communicatie",
|
||||||
|
"netwerk"
|
||||||
|
],
|
||||||
|
"architectures": [
|
||||||
|
"amd64",
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"architecture_note": "Aanmelden gebeurt eenmalig in de container zelf, omdat Proton om een tweede factor kan vragen: docker exec -it protonmail-bridge /protonmail/bridge --cli, dan 'login' en daarna 'info' voor de gegevens voor je mailclient."
|
||||||
|
},
|
||||||
|
"variables": [
|
||||||
|
{
|
||||||
|
"title": "Algemeen",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"name": "service_name",
|
||||||
|
"type": "str",
|
||||||
|
"title": "Servicenaam",
|
||||||
|
"default": "protonmail-bridge",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "port_smtp",
|
||||||
|
"type": "int",
|
||||||
|
"title": "SMTP-poort",
|
||||||
|
"default": 1026,
|
||||||
|
"required": true,
|
||||||
|
"description": "Hierop laat je andere apps hun mail aanbieden. Standaard 1026 en niet 1025, omdat Mailpit die al gebruikt en je ze prima naast elkaar kunt draaien."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "port_imap",
|
||||||
|
"type": "int",
|
||||||
|
"title": "IMAP-poort",
|
||||||
|
"default": 1143,
|
||||||
|
"required": true,
|
||||||
|
"description": "Alleen nodig als een app je mail moet kunnen lézen."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "appdata_dir",
|
||||||
|
"type": "str",
|
||||||
|
"title": "Appdata-map",
|
||||||
|
"default": "/opt/serverup/appdata",
|
||||||
|
"required": true,
|
||||||
|
"description": "Hier blijft de aanmelding staan, zodat je na een herstart niet opnieuw hoeft in te loggen."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "timezone",
|
||||||
|
"type": "str",
|
||||||
|
"title": "Tijdzone",
|
||||||
|
"default": "Europe/Amsterdam",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
23
apps/tailscale/files/compose.yaml
Normal file
23
apps/tailscale/files/compose.yaml
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Tailscale bouwt zijn eigen netwerk over bestaande verbindingen heen: geen
|
||||||
|
# poort open in je router, geen vaste adressen. Deze container brengt de hele
|
||||||
|
# host in dat netwerk als je hem als subnetrouter laat draaien.
|
||||||
|
services:
|
||||||
|
<< service_name >>:
|
||||||
|
image: tailscale/tailscale:latest
|
||||||
|
container_name: << service_name >>
|
||||||
|
hostname: << machine_naam >>
|
||||||
|
environment:
|
||||||
|
- TS_AUTHKEY=<< auth_key >>
|
||||||
|
- TS_STATE_DIR=/var/lib/tailscale
|
||||||
|
- TS_EXTRA_ARGS=<< extra_args >>
|
||||||
|
- TS_ROUTES=<< routes >>
|
||||||
|
- TS_ACCEPT_DNS=<< accept_dns >>
|
||||||
|
- TS_USERSPACE=false
|
||||||
|
volumes:
|
||||||
|
- << appdata_dir >>/<< service_name >>:/var/lib/tailscale
|
||||||
|
- /dev/net/tun:/dev/net/tun
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
- NET_RAW
|
||||||
|
network_mode: host
|
||||||
|
restart: unless-stopped
|
||||||
50
apps/tailscale/template.json
Normal file
50
apps/tailscale/template.json
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
{
|
||||||
|
"kind": "compose",
|
||||||
|
"metadata": {
|
||||||
|
"name": "Tailscale",
|
||||||
|
"description": "Zet deze machine in je eigen Tailscale-netwerk, zodat je er vanaf elke locatie bij kunt zonder poorten open te zetten. Als subnetrouter maakt hij ook de rest van je thuisnetwerk bereikbaar.",
|
||||||
|
"tags": ["vpn", "wireguard", "op afstand", "netwerk", "mesh"],
|
||||||
|
"icon": {"provider": "selfhst", "id": "tailscale"},
|
||||||
|
"version": {"name": "latest"},
|
||||||
|
"categories": ["netwerk", "beveiliging"],
|
||||||
|
"architectures": ["amd64", "arm64"],
|
||||||
|
"architecture_note": "Draait met network_mode: host, zodat de tunnel voor de hele machine geldt en niet alleen voor deze container."
|
||||||
|
},
|
||||||
|
"variables": [
|
||||||
|
{
|
||||||
|
"title": "Algemeen",
|
||||||
|
"items": [
|
||||||
|
{"name": "service_name", "type": "str", "title": "Servicenaam",
|
||||||
|
"default": "tailscale", "required": true},
|
||||||
|
{"name": "machine_naam", "type": "str", "title": "Naam in je netwerk",
|
||||||
|
"default": "server-up", "required": true,
|
||||||
|
"description": "Onder deze naam verschijnt de machine in je tailnet."},
|
||||||
|
{"name": "appdata_dir", "type": "str", "title": "Appdata-map",
|
||||||
|
"default": "/opt/serverup/appdata", "required": true,
|
||||||
|
"description": "Hier blijft de aanmelding staan, zodat je na een herstart niet opnieuw hoeft te koppelen."}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Toegang",
|
||||||
|
"items": [
|
||||||
|
{"name": "auth_key", "type": "str", "title": "Auth key",
|
||||||
|
"default": "", "required": true, "secret": true, "eigen_invoer": true,
|
||||||
|
"help": "Maak er een aan in de Tailscale-console onder Settings → Keys. Een reusable key is handig als je meerdere machines aansluit."}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Netwerk",
|
||||||
|
"items": [
|
||||||
|
{"name": "routes", "type": "str", "title": "Netwerken achter deze machine",
|
||||||
|
"default": "", "required": false,
|
||||||
|
"description": "Bijvoorbeeld 10.0.20.0/24 om je hele thuisnetwerk bereikbaar te maken. Keur de route daarna goed in de Tailscale-console. Leeg = alleen deze machine."},
|
||||||
|
{"name": "accept_dns", "type": "str", "title": "DNS van Tailscale gebruiken",
|
||||||
|
"default": "false", "required": true, "advanced": true,
|
||||||
|
"description": "'true' laat deze machine de DNS-instellingen uit je tailnet volgen — handig voor split-DNS naar je eigen domein, maar het overschrijft de resolver van de host."},
|
||||||
|
{"name": "extra_args", "type": "str", "title": "Extra opties",
|
||||||
|
"default": "--accept-routes", "required": false, "advanced": true,
|
||||||
|
"description": "Wordt meegegeven aan het commando 'tailscale up'."}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
29
apps/wireguard/files/compose.yaml
Normal file
29
apps/wireguard/files/compose.yaml
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Twee rollen in één image. Zet je eigen `wg0.conf` in de map wg_confs, dan
|
||||||
|
# verbindt deze container met dát netwerk (client). Laat je die map leeg en vul
|
||||||
|
# je de servervelden in, dan maakt hij zelf een server met clientprofielen aan.
|
||||||
|
services:
|
||||||
|
<< service_name >>:
|
||||||
|
image: lscr.io/linuxserver/wireguard:latest
|
||||||
|
container_name: << service_name >>
|
||||||
|
environment:
|
||||||
|
- PUID=<< puid >>
|
||||||
|
- PGID=<< pgid >>
|
||||||
|
- TZ=<< timezone >>
|
||||||
|
# Alleen voor de serverrol; bij een eigen wg0.conf doen ze niets.
|
||||||
|
- SERVERURL=<< server_url >>
|
||||||
|
- SERVERPORT=<< port >>
|
||||||
|
- PEERS=<< peers >>
|
||||||
|
- PEERDNS=<< peer_dns >>
|
||||||
|
- INTERNAL_SUBNET=<< internal_subnet >>
|
||||||
|
- ALLOWEDIPS=<< allowed_ips >>
|
||||||
|
volumes:
|
||||||
|
- << appdata_dir >>/<< service_name >>:/config
|
||||||
|
- /lib/modules:/lib/modules:ro
|
||||||
|
ports:
|
||||||
|
- "<< port >>:51820/udp"
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
- SYS_MODULE
|
||||||
|
sysctls:
|
||||||
|
- net.ipv4.conf.all.src_valid_mark=1
|
||||||
|
restart: unless-stopped
|
||||||
138
apps/wireguard/template.json
Normal file
138
apps/wireguard/template.json
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
{
|
||||||
|
"kind": "compose",
|
||||||
|
"metadata": {
|
||||||
|
"name": "WireGuard",
|
||||||
|
"description": "Verbind deze machine met een ander netwerk via WireGuard, of draai er zelf een VPN-server mee. Zet je eigen wg0.conf in de configuratiemap voor de clientrol; vul de servervelden in om profielen te laten aanmaken.",
|
||||||
|
"tags": [
|
||||||
|
"vpn",
|
||||||
|
"wireguard",
|
||||||
|
"tunnel",
|
||||||
|
"netwerk",
|
||||||
|
"op afstand"
|
||||||
|
],
|
||||||
|
"icon": {
|
||||||
|
"provider": "selfhst",
|
||||||
|
"id": "wireguard"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"name": "latest"
|
||||||
|
},
|
||||||
|
"categories": [
|
||||||
|
"netwerk",
|
||||||
|
"beveiliging"
|
||||||
|
],
|
||||||
|
"architectures": [
|
||||||
|
"amd64",
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"architecture_note": "De kernelmodule komt van de host. Op een Raspberry Pi met een recente 64-bits kernel zit WireGuard er standaard in; op oudere kernels moet je hem zelf installeren."
|
||||||
|
},
|
||||||
|
"variables": [
|
||||||
|
{
|
||||||
|
"title": "Algemeen",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"name": "service_name",
|
||||||
|
"type": "str",
|
||||||
|
"title": "Servicenaam",
|
||||||
|
"default": "wireguard",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "port",
|
||||||
|
"type": "int",
|
||||||
|
"title": "WireGuard-poort (UDP)",
|
||||||
|
"default": 51820,
|
||||||
|
"required": true,
|
||||||
|
"description": "Alleen nodig als deze machine de server is. Als client verbind je naar buiten en hoeft er niets open."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "appdata_dir",
|
||||||
|
"type": "str",
|
||||||
|
"title": "Appdata-map",
|
||||||
|
"default": "/opt/serverup/appdata",
|
||||||
|
"required": true,
|
||||||
|
"description": "Hierin komt de map wg_confs; zet daar je eigen wg0.conf neer om met een bestaand netwerk te verbinden."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "timezone",
|
||||||
|
"type": "str",
|
||||||
|
"title": "Tijdzone",
|
||||||
|
"default": "Europe/Amsterdam",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Gebruiker",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"name": "puid",
|
||||||
|
"type": "int",
|
||||||
|
"title": "PUID",
|
||||||
|
"default": 1000,
|
||||||
|
"required": true,
|
||||||
|
"advanced": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pgid",
|
||||||
|
"type": "int",
|
||||||
|
"title": "PGID",
|
||||||
|
"default": 1000,
|
||||||
|
"required": true,
|
||||||
|
"advanced": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Als server",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"name": "server_url",
|
||||||
|
"type": "str",
|
||||||
|
"title": "Publiek adres",
|
||||||
|
"default": "auto",
|
||||||
|
"required": true,
|
||||||
|
"description": "Het adres waarop clients deze server bereiken. 'auto' laat de container zijn eigen publieke IP opzoeken.",
|
||||||
|
"advanced": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "peers",
|
||||||
|
"type": "str",
|
||||||
|
"title": "Clientprofielen",
|
||||||
|
"default": "1",
|
||||||
|
"required": true,
|
||||||
|
"description": "Een aantal (bijvoorbeeld 3) of namen gescheiden door komma’s (laptop,telefoon). De profielen en QR-codes komen in de appdata-map.",
|
||||||
|
"advanced": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "peer_dns",
|
||||||
|
"type": "str",
|
||||||
|
"title": "DNS voor clients",
|
||||||
|
"default": "auto",
|
||||||
|
"required": true,
|
||||||
|
"description": "Wijs dit naar je eigen DNS-server als je thuisnamen wilt kunnen opzoeken door de tunnel.",
|
||||||
|
"advanced": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "internal_subnet",
|
||||||
|
"type": "str",
|
||||||
|
"title": "Subnet van de tunnel",
|
||||||
|
"default": "10.13.13.0",
|
||||||
|
"required": true,
|
||||||
|
"description": "Mag niet botsen met een netwerk dat je al gebruikt.",
|
||||||
|
"advanced": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "allowed_ips",
|
||||||
|
"type": "str",
|
||||||
|
"title": "Netwerken door de tunnel",
|
||||||
|
"default": "0.0.0.0/0",
|
||||||
|
"required": true,
|
||||||
|
"description": "Wat clients via deze server bereiken. Alleen je thuisnetwerk? Zet er dan bijvoorbeeld 10.0.20.0/24 neer in plaats van al het verkeer.",
|
||||||
|
"advanced": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -157,8 +157,8 @@ GEDEELDE_POORTEN = {
|
||||||
80: {"nginx-proxy-manager", "traefik", "caddy", "zoraxy", "pangolin"}, # reverse proxies
|
80: {"nginx-proxy-manager", "traefik", "caddy", "zoraxy", "pangolin"}, # reverse proxies
|
||||||
443: {"nginx-proxy-manager", "traefik", "caddy", "zoraxy", "pangolin"},
|
443: {"nginx-proxy-manager", "traefik", "caddy", "zoraxy", "pangolin"},
|
||||||
53: {"adguard-home", "pihole"}, # dns-blokkers
|
53: {"adguard-home", "pihole"}, # dns-blokkers
|
||||||
# Twee manieren om een WireGuard-tunnel te draaien; je gebruikt er één.
|
# Drie manieren om een WireGuard-eindpunt te draaien; je gebruikt er één.
|
||||||
51820: {"pangolin", "wg-easy"},
|
51820: {"pangolin", "wg-easy", "wireguard"},
|
||||||
# Hieronder: botsingen die er al waren vóór deze testsuite. De
|
# Hieronder: botsingen die er al waren vóór deze testsuite. De
|
||||||
# vrije-poortsuggestie bij installeren vangt ze op, dus ze zijn hinderlijk
|
# vrije-poortsuggestie bij installeren vangt ze op, dus ze zijn hinderlijk
|
||||||
# maar niet kapot. Nieuwe apps horen hier niet bij te komen.
|
# maar niet kapot. Nieuwe apps horen hier niet bij te komen.
|
||||||
|
|
|
||||||
115
tests/test_verbindingsapps.py
Normal file
115
tests/test_verbindingsapps.py
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
"""De apps waarmee je een machine met een ander netwerk verbindt.
|
||||||
|
|
||||||
|
Deze vier hebben iets gemeen dat je in een formulier niet ziet: ze werken alleen
|
||||||
|
met rechten die een gewone container niet heeft (NET_ADMIN, /dev/net/tun), of ze
|
||||||
|
vragen een eenmalige handeling in de terminal. Zonder dat staat er een app die
|
||||||
|
start en niets doet.
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
WORTEL = Path(__file__).resolve().parent.parent
|
||||||
|
sys.path.insert(0, str(WORTEL / "server-up"))
|
||||||
|
|
||||||
|
from core import boilerplates as bp
|
||||||
|
|
||||||
|
VERBINDINGSAPPS = ["cloudflared", "protonmail-bridge", "tailscale", "wireguard"]
|
||||||
|
|
||||||
|
|
||||||
|
def _tpl(app: str) -> dict:
|
||||||
|
return json.loads((WORTEL / "apps" / app / "template.json").read_text("utf-8"))
|
||||||
|
|
||||||
|
|
||||||
|
def _render(app: str) -> dict:
|
||||||
|
pad = WORTEL / "apps" / app
|
||||||
|
velden = bp.fields(pad)
|
||||||
|
waarden = {f["name"]: f["default"] for f in velden if f.get("default") is not None}
|
||||||
|
for f in velden:
|
||||||
|
if f.get("required") and not waarden.get(f["name"]):
|
||||||
|
waarden[f["name"]] = "testwaarde12345678901234567890ab"
|
||||||
|
with tempfile.TemporaryDirectory() as t:
|
||||||
|
bp.render_to_dir(pad, Path(t), waarden)
|
||||||
|
return yaml.safe_load((Path(t) / "docker-compose.yml").read_text())
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("app", VERBINDINGSAPPS)
|
||||||
|
def test_staan_in_de_catalogus_en_draaien_op_een_pi(app):
|
||||||
|
meta = _tpl(app)["metadata"]
|
||||||
|
assert {"amd64", "arm64"} <= set(meta["architectures"]), \
|
||||||
|
"juist op een Pi wil je een tunnel draaien"
|
||||||
|
assert "netwerk" in meta["categories"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_wireguard_krijgt_de_rechten_die_een_tunnel_nodig_heeft():
|
||||||
|
"""Zonder NET_ADMIN en de kernelmodules start wg-quick niet, en zonder
|
||||||
|
src_valid_mark valt het verkeer stil zodra alles door de tunnel gaat."""
|
||||||
|
svc = _render("wireguard")["services"]["wireguard"]
|
||||||
|
assert "NET_ADMIN" in svc["cap_add"]
|
||||||
|
assert "SYS_MODULE" in svc["cap_add"]
|
||||||
|
assert "/lib/modules:/lib/modules:ro" in svc["volumes"]
|
||||||
|
assert "net.ipv4.conf.all.src_valid_mark=1" in svc["sysctls"]
|
||||||
|
assert any(str(p).endswith("51820/udp") for p in svc["ports"])
|
||||||
|
|
||||||
|
|
||||||
|
def test_wireguard_legt_uit_waar_je_eigen_configuratie_heen_moet():
|
||||||
|
"""De clientrol is de reden dat deze app er is; die werkt alleen als je
|
||||||
|
weet dat je wg0.conf in wg_confs hoort."""
|
||||||
|
bron = (WORTEL / "apps" / "wireguard" / "files" / "compose.yaml").read_text("utf-8")
|
||||||
|
assert "wg_confs" in bron
|
||||||
|
velden = {f["name"]: f for f in bp.fields(WORTEL / "apps" / "wireguard")}
|
||||||
|
assert "wg_confs" in velden["appdata_dir"]["description"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_tailscale_draait_op_de_host_en_onthoudt_zijn_aanmelding():
|
||||||
|
"""In een eigen netwerknamespace bereikt de tunnel alleen die container;
|
||||||
|
en zonder bewaarde staat moet je na elke herstart opnieuw koppelen."""
|
||||||
|
svc = _render("tailscale")["services"]["tailscale"]
|
||||||
|
assert svc["network_mode"] == "host"
|
||||||
|
assert "NET_ADMIN" in svc["cap_add"]
|
||||||
|
assert any("/var/lib/tailscale" in v for v in svc["volumes"])
|
||||||
|
assert "/dev/net/tun:/dev/net/tun" in svc["volumes"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_tailscale_vraagt_om_een_auth_key_als_geheim():
|
||||||
|
velden = {f["name"]: f for f in bp.fields(WORTEL / "apps" / "tailscale")}
|
||||||
|
assert velden["auth_key"]["secret"] is True
|
||||||
|
assert velden["auth_key"]["required"] is True
|
||||||
|
# De routes zijn optioneel: de meeste mensen willen alleen deze machine.
|
||||||
|
assert velden["routes"]["required"] is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_cloudflared_zet_geen_poort_open():
|
||||||
|
"""Dat is het hele punt van een tunnel; publiceerde hij poorten, dan kon je
|
||||||
|
net zo goed een reverse proxy nemen."""
|
||||||
|
svc = _render("cloudflared")["services"]["cloudflared"]
|
||||||
|
assert "ports" not in svc
|
||||||
|
assert "--no-autoupdate" in svc["command"], \
|
||||||
|
"een container die zichzelf bijwerkt, wijkt af van wat je uitrolde"
|
||||||
|
velden = {f["name"]: f for f in bp.fields(WORTEL / "apps" / "cloudflared")}
|
||||||
|
assert velden["tunnel_token"]["secret"] is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_protonmail_bridge_botst_niet_met_mailpit():
|
||||||
|
"""Allebei praten ze SMTP en je kunt ze prima naast elkaar draaien: de een
|
||||||
|
vangt testmail op, de ander verstuurt echte mail."""
|
||||||
|
velden = {f["name"]: f for f in bp.fields(WORTEL / "apps" / "protonmail-bridge")}
|
||||||
|
mailpit = {f["name"]: f for f in bp.fields(WORTEL / "apps" / "mailpit")}
|
||||||
|
smtp_poorten = {f["default"] for f in mailpit.values()
|
||||||
|
if f.get("type") == "int"}
|
||||||
|
assert velden["port_smtp"]["default"] not in smtp_poorten
|
||||||
|
|
||||||
|
|
||||||
|
def test_protonmail_bridge_vertelt_over_de_eenmalige_aanmelding():
|
||||||
|
"""Die kan niet via een formulier: Proton vraagt om een tweede factor. Staat
|
||||||
|
het er niet bij, dan draait de bridge zonder account en snapt niemand
|
||||||
|
waarom er geen mail doorkomt."""
|
||||||
|
meta = _tpl("protonmail-bridge")["metadata"]
|
||||||
|
bron = (WORTEL / "apps" / "protonmail-bridge" / "files" / "compose.yaml").read_text("utf-8")
|
||||||
|
for tekst in (meta["architecture_note"], bron):
|
||||||
|
assert "--cli" in tekst
|
||||||
|
assert "docker exec" in meta["architecture_note"]
|
||||||
Loading…
Reference in a new issue