Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
52578737f0 | ||
|
|
412d7bc377 | ||
|
|
5012f61b00 | ||
|
|
5cc6258326 | ||
|
|
8064e5da8f | ||
|
|
63a255c5cb | ||
|
|
7ad1fbd5c6 | ||
|
|
67f5f4cffd | ||
|
|
8797335fbc | ||
|
|
53dc108603 | ||
|
|
ebf5ba2d4c | ||
|
|
bbac08a7d2 | ||
|
|
784a5a6514 | ||
|
|
0c31499fb0 | ||
|
|
f118344246 | ||
|
|
822df220ab | ||
|
|
9eb1265bc8 | ||
|
|
41cfc70b53 | ||
|
|
acedc5d9d9 | ||
|
|
521a8dd1e2 | ||
|
|
e145e2236c | ||
|
|
4d7e37e319 |
@@ -14,7 +14,7 @@
|
||||
},
|
||||
"route": {
|
||||
"label": "baserow",
|
||||
"port": 80
|
||||
"endpoint": "web"
|
||||
}
|
||||
},
|
||||
"binds": {
|
||||
@@ -30,6 +30,7 @@
|
||||
},
|
||||
"listens": [
|
||||
{
|
||||
"name": "web",
|
||||
"port": 80,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
},
|
||||
"listens": [
|
||||
{
|
||||
"name": "web",
|
||||
"port": 6767,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
@@ -93,7 +94,7 @@
|
||||
],
|
||||
"env": {
|
||||
"MESH_BROKER_FILE": "/run/secrets/broker",
|
||||
"MESH_BAZARR_URL": "http://127.0.0.1:6767",
|
||||
"MESH_BAZARR_URL": "http://127.0.0.1:${port:6767}",
|
||||
"MESH_BAZARR_API_KEY_FILE": "/run/secrets/api-key",
|
||||
"MESH_BAZARR_CONFIG_FILE": "/run/config/config.json",
|
||||
"MESH_BAZARR_CONFIG_DIR": "/var/lib/bazarr/config"
|
||||
@@ -110,7 +111,7 @@
|
||||
"contributes": {
|
||||
"route": {
|
||||
"label": "subs",
|
||||
"port": 6767
|
||||
"endpoint": "web"
|
||||
}
|
||||
},
|
||||
"binds": {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
},
|
||||
"listens": [
|
||||
{
|
||||
"name": "web",
|
||||
"port": 8787,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
@@ -75,7 +76,7 @@
|
||||
],
|
||||
"env": {
|
||||
"MESH_BROKER_FILE": "/run/secrets/broker",
|
||||
"MESH_BOOKSHELF_URL": "http://127.0.0.1:8787",
|
||||
"MESH_BOOKSHELF_URL": "http://127.0.0.1:${port:8787}",
|
||||
"MESH_BOOKSHELF_CONFIG_DIR": "/var/lib/bookshelf/config"
|
||||
},
|
||||
"artifact": "runtime"
|
||||
@@ -87,7 +88,7 @@
|
||||
"contributes": {
|
||||
"route": {
|
||||
"label": "books",
|
||||
"port": 8787
|
||||
"endpoint": "web"
|
||||
}
|
||||
},
|
||||
"binds": {
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"module": "ca-trust",
|
||||
"version": "1",
|
||||
"slug": "catrust",
|
||||
"capabilities": [
|
||||
"service-manager"
|
||||
],
|
||||
"requires": [
|
||||
"internal-acme-ca"
|
||||
],
|
||||
"seats": [
|
||||
{
|
||||
"name": "the-mesh-trust-anchor",
|
||||
"scope": "node"
|
||||
}
|
||||
],
|
||||
"claims": [
|
||||
{
|
||||
"name": "the-mesh-trust-anchor",
|
||||
"scope": "node"
|
||||
}
|
||||
],
|
||||
"resources": [
|
||||
{
|
||||
"id": "state",
|
||||
"type": "directory",
|
||||
"mode": "0700",
|
||||
"place": "."
|
||||
},
|
||||
{
|
||||
"id": "anchor",
|
||||
"type": "file",
|
||||
"path": "${dir:state}/anchor",
|
||||
"mode": "0755",
|
||||
"content": "#!/bin/sh\n# The mesh's internal certificate authority, trusted by this machine.\n#\n# Written by the mesh from the ca-trust module's manifest (novox/hq ADR 0147).\n# Editing it here lasts until the next apply.\n#\n# There is no prior trust to verify the fetch against \u2014 this is the thing that\n# establishes it \u2014 so it is made over the mesh's own private network, which is\n# what authenticates it (novox/hq ADR 0098, the same reasoning that lets the\n# route proxy fetch this root for itself). What comes back is checked here: a\n# body that is not a certificate is refused now, rather than believed and then\n# failed by whatever reads the trust store next.\nset -eu\n\nROOTS='https://${bound:internal-acme-ca:at}:${bound:internal-acme-ca:port}${bound:internal-acme-ca:roots}'\nANCHORS=/etc/ca-certificates/trust-source/anchors\nANCHOR=\"$ANCHORS/mesh-internal-ca.crt\"\n\n# Arch's layout, said out loud rather than assumed: a machine that keeps its\n# anchors elsewhere fails here, visibly, instead of writing a file nothing\n# reads. That failure is the signal that this belongs in the host, where one\n# operating system's difference lives (novox/hq ADR 0147, option 2).\n[ -d \"$ANCHORS\" ] || {\n\techo \"this machine keeps no trust anchors in $ANCHORS; ca-trust is written for that layout\" >&2\n\texit 1\n}\n\ncase \"${1:-}\" in\ninstall)\n\ttmp=$(mktemp)\n\ttrap 'rm -f \"$tmp\"' EXIT\n\t# The authority may still be starting, or this machine may have come up\n\t# before it: two minutes of asking, then an honest failure.\n\tn=0\n\twhile [ \"$n\" -lt 60 ]; do\n\t\tif curl --fail --silent --show-error --insecure --max-time 10 \\\n\t\t\t--output \"$tmp\" \"$ROOTS\" &&\n\t\t\tgrep -q 'BEGIN CERTIFICATE' \"$tmp\"; then\n\t\t\tinstall -m 0644 \"$tmp\" \"$ANCHOR\"\n\t\t\tupdate-ca-trust\n\t\t\texit 0\n\t\tfi\n\t\tn=$((n + 1))\n\t\tsleep 2\n\tdone\n\techo \"the authority at $ROOTS did not serve a certificate within two minutes\" >&2\n\texit 1\n\t;;\nremove)\n\t# What stopping the unit does, and therefore what being unassigned does.\n\trm -f \"$ANCHOR\"\n\tupdate-ca-trust\n\t;;\n*)\n\techo \"usage: $(basename \"$0\") install|remove\" >&2\n\texit 2\n\t;;\nesac\n"
|
||||
},
|
||||
{
|
||||
"id": "unit",
|
||||
"type": "file",
|
||||
"path": "/etc/systemd/system/mesh-ca-trust.service",
|
||||
"mode": "0644",
|
||||
"content": "[Unit]\nDescription=The mesh's internal certificate authority, trusted by this machine\n# novox/hq ADR 0147. Starting this unit places the mesh's root among this\n# machine's trust anchors; stopping it takes the root away again, which is what\n# the host does when the module is no longer assigned here.\nWants=network-online.target\nAfter=network-online.target\n\n[Service]\nType=oneshot\nRemainAfterExit=yes\nExecStart=${dir:state}/anchor install\nExecStop=${dir:state}/anchor remove\n\n[Install]\nWantedBy=multi-user.target\n"
|
||||
},
|
||||
{
|
||||
"id": "trust",
|
||||
"type": "service",
|
||||
"unit": "mesh-ca-trust.service",
|
||||
"state": "running",
|
||||
"boot": "enabled",
|
||||
"restart-on": [
|
||||
"anchor",
|
||||
"unit"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
"contributes": {
|
||||
"route": {
|
||||
"label": "de-spiegel",
|
||||
"port": 35621
|
||||
"endpoint": "web"
|
||||
}
|
||||
},
|
||||
"binds": {
|
||||
@@ -23,6 +23,7 @@
|
||||
},
|
||||
"listens": [
|
||||
{
|
||||
"name": "web",
|
||||
"port": 35621,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
},
|
||||
"listens": [
|
||||
{
|
||||
"name": "registry",
|
||||
"port": 5000,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
|
||||
@@ -22,11 +22,20 @@
|
||||
],
|
||||
"listens": [
|
||||
{
|
||||
"name": "dns-udp",
|
||||
"port": 53,
|
||||
"protocol": "udp",
|
||||
"from": "mesh",
|
||||
"why": "every name for this machine and what it runs \u2014 the mesh's own answered here, the rest forwarded",
|
||||
"fixed": true
|
||||
},
|
||||
{
|
||||
"name": "dns-tcp",
|
||||
"port": 53,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
"why": "the same names over tcp, which a resolver answers on as well and is asked for whenever an answer will not fit in a datagram. Declared because the daemon serves it: a declaration that covers one of the two protocols its own service listens on leaves the other closed while everything reports success",
|
||||
"fixed": true
|
||||
}
|
||||
],
|
||||
"resources": [
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"route": {
|
||||
"web": {
|
||||
"label": "git",
|
||||
"port": 3000
|
||||
"endpoint": "web"
|
||||
},
|
||||
"internal-api-refused": {
|
||||
"label": "git",
|
||||
@@ -44,12 +44,14 @@
|
||||
],
|
||||
"listens": [
|
||||
{
|
||||
"name": "web",
|
||||
"port": 3000,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
"why": "the forge, over http"
|
||||
},
|
||||
{
|
||||
"name": "ssh",
|
||||
"port": 22,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
],
|
||||
"listens": [
|
||||
{
|
||||
"name": "web",
|
||||
"port": 3000,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
@@ -96,7 +97,7 @@
|
||||
"contributes": {
|
||||
"route": {
|
||||
"label": "grafana",
|
||||
"port": 3000
|
||||
"endpoint": "web"
|
||||
}
|
||||
},
|
||||
"binds": {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"contributes": {
|
||||
"route": {
|
||||
"label": "hello",
|
||||
"port": 8080
|
||||
"endpoint": "web"
|
||||
}
|
||||
},
|
||||
"binds": {
|
||||
@@ -19,6 +19,7 @@
|
||||
},
|
||||
"listens": [
|
||||
{
|
||||
"name": "web",
|
||||
"port": 8080,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
},
|
||||
"listens": [
|
||||
{
|
||||
"name": "web",
|
||||
"port": 8123,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
@@ -85,7 +86,7 @@
|
||||
"contributes": {
|
||||
"route": {
|
||||
"label": "home-assistant",
|
||||
"port": 8123
|
||||
"endpoint": "web"
|
||||
}
|
||||
},
|
||||
"binds": {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
},
|
||||
"listens": [
|
||||
{
|
||||
"name": "stream",
|
||||
"port": 8000,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
},
|
||||
"listens": [
|
||||
{
|
||||
"name": "api",
|
||||
"port": 8086,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
"route": {
|
||||
"site": {
|
||||
"label": "invoicing",
|
||||
"port": 80
|
||||
"endpoint": "web"
|
||||
},
|
||||
"api": {
|
||||
"label": "invoicing-api",
|
||||
"port": 9000
|
||||
"endpoint": "api"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -36,12 +36,14 @@
|
||||
},
|
||||
"listens": [
|
||||
{
|
||||
"name": "web",
|
||||
"port": 80,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
"why": "the invoicing web frontend; a public name is a route grant later"
|
||||
},
|
||||
{
|
||||
"name": "api",
|
||||
"port": 9000,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
],
|
||||
"listens": [
|
||||
{
|
||||
"name": "web",
|
||||
"port": 9117,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
@@ -82,7 +83,7 @@
|
||||
"contributes": {
|
||||
"route": {
|
||||
"label": "indexers",
|
||||
"port": 9117
|
||||
"endpoint": "web"
|
||||
}
|
||||
},
|
||||
"binds": {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
},
|
||||
"route": {
|
||||
"label": "keycloak",
|
||||
"port": 8080
|
||||
"endpoint": "web"
|
||||
}
|
||||
},
|
||||
"binds": {
|
||||
@@ -34,6 +34,7 @@
|
||||
],
|
||||
"listens": [
|
||||
{
|
||||
"name": "web",
|
||||
"port": 8080,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
},
|
||||
"listens": [
|
||||
{
|
||||
"name": "web",
|
||||
"port": 8283,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
},
|
||||
"listens": [
|
||||
{
|
||||
"name": "web",
|
||||
"port": 8686,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
@@ -74,7 +75,7 @@
|
||||
],
|
||||
"env": {
|
||||
"MESH_BROKER_FILE": "/run/secrets/broker",
|
||||
"MESH_LIDARR_URL": "http://127.0.0.1:8686",
|
||||
"MESH_LIDARR_URL": "http://127.0.0.1:${port:8686}",
|
||||
"MESH_LIDARR_CONFIG_DIR": "/var/lib/lidarr/config"
|
||||
},
|
||||
"artifact": "runtime"
|
||||
@@ -86,7 +87,7 @@
|
||||
"contributes": {
|
||||
"route": {
|
||||
"label": "lidarr",
|
||||
"port": 8686
|
||||
"endpoint": "web"
|
||||
}
|
||||
},
|
||||
"binds": {
|
||||
|
||||
@@ -16,27 +16,27 @@
|
||||
"route": {
|
||||
"web": {
|
||||
"label": "mail",
|
||||
"port": 7443,
|
||||
"endpoint": "web-tls",
|
||||
"scheme": "https",
|
||||
"insecure": true
|
||||
},
|
||||
"acme": {
|
||||
"label": "mail",
|
||||
"path": "/.well-known/acme-challenge",
|
||||
"port": 7080,
|
||||
"endpoint": "web",
|
||||
"priority": 100
|
||||
},
|
||||
"autoconfig": {
|
||||
"label": "autoconfig",
|
||||
"port": 4243
|
||||
"endpoint": "autoconfig"
|
||||
},
|
||||
"autodiscover": {
|
||||
"label": "autodiscover",
|
||||
"port": 4243
|
||||
"endpoint": "autoconfig"
|
||||
},
|
||||
"automx": {
|
||||
"label": "automx",
|
||||
"port": 4243
|
||||
"endpoint": "autoconfig"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -60,6 +60,7 @@
|
||||
],
|
||||
"listens": [
|
||||
{
|
||||
"name": "smtp",
|
||||
"port": 25,
|
||||
"protocol": "tcp",
|
||||
"from": "anywhere",
|
||||
@@ -67,6 +68,7 @@
|
||||
"fixed": true
|
||||
},
|
||||
{
|
||||
"name": "pop3",
|
||||
"port": 110,
|
||||
"protocol": "tcp",
|
||||
"from": "anywhere",
|
||||
@@ -74,6 +76,7 @@
|
||||
"fixed": true
|
||||
},
|
||||
{
|
||||
"name": "imap",
|
||||
"port": 143,
|
||||
"protocol": "tcp",
|
||||
"from": "anywhere",
|
||||
@@ -81,6 +84,7 @@
|
||||
"fixed": true
|
||||
},
|
||||
{
|
||||
"name": "smtps",
|
||||
"port": 465,
|
||||
"protocol": "tcp",
|
||||
"from": "anywhere",
|
||||
@@ -88,6 +92,7 @@
|
||||
"fixed": true
|
||||
},
|
||||
{
|
||||
"name": "submission",
|
||||
"port": 587,
|
||||
"protocol": "tcp",
|
||||
"from": "anywhere",
|
||||
@@ -95,6 +100,7 @@
|
||||
"fixed": true
|
||||
},
|
||||
{
|
||||
"name": "imaps",
|
||||
"port": 993,
|
||||
"protocol": "tcp",
|
||||
"from": "anywhere",
|
||||
@@ -102,6 +108,7 @@
|
||||
"fixed": true
|
||||
},
|
||||
{
|
||||
"name": "pop3s",
|
||||
"port": 995,
|
||||
"protocol": "tcp",
|
||||
"from": "anywhere",
|
||||
@@ -109,18 +116,21 @@
|
||||
"fixed": true
|
||||
},
|
||||
{
|
||||
"name": "web",
|
||||
"port": 7080,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
"why": "the web front over http; only the ACME HTTP-01 passthrough is routed here \u2014 everything else 301s to https and would loop a proxy"
|
||||
},
|
||||
{
|
||||
"name": "web-tls",
|
||||
"port": 7443,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
"why": "the web front over its own TLS (admin, webmail, API); the public name mail.novox.be is a route grant reaching it here"
|
||||
},
|
||||
{
|
||||
"name": "autoconfig",
|
||||
"port": 4243,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
],
|
||||
"listens": [
|
||||
{
|
||||
"name": "api",
|
||||
"port": 59125,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
"route": {
|
||||
"api": {
|
||||
"label": "files-api",
|
||||
"port": 9000
|
||||
"endpoint": "s3"
|
||||
},
|
||||
"console": {
|
||||
"label": "files",
|
||||
"port": 9001
|
||||
"endpoint": "console"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -31,12 +31,14 @@
|
||||
],
|
||||
"listens": [
|
||||
{
|
||||
"name": "s3",
|
||||
"port": 9000,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
"why": "the S3 endpoint"
|
||||
},
|
||||
{
|
||||
"name": "console",
|
||||
"port": 9001,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
],
|
||||
"listens": [
|
||||
{
|
||||
"name": "database",
|
||||
"port": 27017,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
|
||||
@@ -34,12 +34,14 @@
|
||||
},
|
||||
"listens": [
|
||||
{
|
||||
"name": "mqtt",
|
||||
"port": 1883,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
"why": "modules on any machine that were granted a topic namespace"
|
||||
},
|
||||
{
|
||||
"name": "mqtt-websockets",
|
||||
"port": 8081,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
],
|
||||
"listens": [
|
||||
{
|
||||
"name": "database",
|
||||
"port": 4848,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
},
|
||||
"route": {
|
||||
"label": "n8n",
|
||||
"port": 5682
|
||||
"endpoint": "web"
|
||||
}
|
||||
},
|
||||
"binds": {
|
||||
@@ -29,6 +29,7 @@
|
||||
},
|
||||
"listens": [
|
||||
{
|
||||
"name": "web",
|
||||
"port": 5682,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"consumes": [],
|
||||
"listens": [
|
||||
{
|
||||
"name": "bus",
|
||||
"port": 4222,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
},
|
||||
"route": {
|
||||
"label": "drive",
|
||||
"port": 80
|
||||
"endpoint": "web"
|
||||
}
|
||||
},
|
||||
"binds": {
|
||||
@@ -38,6 +38,7 @@
|
||||
],
|
||||
"listens": [
|
||||
{
|
||||
"name": "web",
|
||||
"port": 80,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
],
|
||||
"listens": [
|
||||
{
|
||||
"name": "web",
|
||||
"port": 1880,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
@@ -81,7 +82,7 @@
|
||||
"contributes": {
|
||||
"route": {
|
||||
"label": "nodered",
|
||||
"port": 1880
|
||||
"endpoint": "web"
|
||||
}
|
||||
},
|
||||
"binds": {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"contributes": {
|
||||
"route": {
|
||||
"label": "@",
|
||||
"port": 4000
|
||||
"endpoint": "web"
|
||||
}
|
||||
},
|
||||
"binds": {
|
||||
@@ -18,6 +18,7 @@
|
||||
},
|
||||
"listens": [
|
||||
{
|
||||
"name": "web",
|
||||
"port": 4000,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
},
|
||||
"listens": [
|
||||
{
|
||||
"name": "web",
|
||||
"port": 6789,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
@@ -80,7 +81,7 @@
|
||||
],
|
||||
"env": {
|
||||
"MESH_BROKER_FILE": "/run/secrets/broker",
|
||||
"MESH_NZBGET_URL": "http://127.0.0.1:6789",
|
||||
"MESH_NZBGET_URL": "http://127.0.0.1:${port:6789}",
|
||||
"MESH_NZBGET_PASSWORD_FILE": "/run/secrets/password",
|
||||
"MESH_NZBGET_CONFIG_FILE": "/run/config/config.json",
|
||||
"MESH_NZBGET_CONFIG_DIR": "/var/lib/nzbget/config"
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
],
|
||||
"listens": [
|
||||
{
|
||||
"name": "api",
|
||||
"port": 11434,
|
||||
"protocol": "tcp",
|
||||
"from": "machine",
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
},
|
||||
"listens": [
|
||||
{
|
||||
"name": "web",
|
||||
"port": 3579,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
@@ -89,7 +90,7 @@
|
||||
"contributes": {
|
||||
"route": {
|
||||
"label": "ombi",
|
||||
"port": 3579
|
||||
"endpoint": "web"
|
||||
}
|
||||
},
|
||||
"binds": {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"contributes": {
|
||||
"route": {
|
||||
"label": "office",
|
||||
"port": 9070
|
||||
"endpoint": "web"
|
||||
}
|
||||
},
|
||||
"binds": {
|
||||
@@ -22,6 +22,7 @@
|
||||
},
|
||||
"listens": [
|
||||
{
|
||||
"name": "web",
|
||||
"port": 9070,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"contributes": {
|
||||
"route": {
|
||||
"label": "eef",
|
||||
"port": 4012
|
||||
"endpoint": "web"
|
||||
}
|
||||
},
|
||||
"binds": {
|
||||
@@ -19,6 +19,7 @@
|
||||
},
|
||||
"listens": [
|
||||
{
|
||||
"name": "web",
|
||||
"port": 4012,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"contributes": {
|
||||
"route": {
|
||||
"label": "filip",
|
||||
"port": 4013
|
||||
"endpoint": "web"
|
||||
}
|
||||
},
|
||||
"binds": {
|
||||
@@ -19,6 +19,7 @@
|
||||
},
|
||||
"listens": [
|
||||
{
|
||||
"name": "web",
|
||||
"port": 4013,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
},
|
||||
"route": {
|
||||
"label": "photos",
|
||||
"port": 4001
|
||||
"endpoint": "web"
|
||||
}
|
||||
},
|
||||
"binds": {
|
||||
@@ -32,12 +32,14 @@
|
||||
},
|
||||
"listens": [
|
||||
{
|
||||
"name": "api",
|
||||
"port": 9000,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
"why": "the photos backend API; the client sites on the module network call it"
|
||||
},
|
||||
{
|
||||
"name": "web",
|
||||
"port": 4001,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
},
|
||||
"listens": [
|
||||
{
|
||||
"name": "stream",
|
||||
"port": 32400,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
|
||||
@@ -7,12 +7,14 @@
|
||||
],
|
||||
"listens": [
|
||||
{
|
||||
"name": "web",
|
||||
"port": 9090,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
"why": "the dashboard over http; portainer.novox.be is a route grant and the proxy reaches it here \u2014 the machine side of 9090:9000, the predecessor's number"
|
||||
},
|
||||
{
|
||||
"name": "web-tls",
|
||||
"port": 9443,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
@@ -103,7 +105,7 @@
|
||||
"contributes": {
|
||||
"route": {
|
||||
"label": "portainer",
|
||||
"port": 9090
|
||||
"endpoint": "web"
|
||||
}
|
||||
},
|
||||
"binds": {
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
],
|
||||
"listens": [
|
||||
{
|
||||
"name": "database",
|
||||
"port": 5432,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
// qbittorrent. Both this module's tools and its events entrypoint import it, and nothing outside
|
||||
// qbittorrent does.
|
||||
//
|
||||
// The WebUI authenticates with a session cookie (SID) obtained by POSTing credentials, and guards
|
||||
// against CSRF by checking the Referer header. Node's fetch keeps no cookie jar, so the SID is
|
||||
// The WebUI authenticates with a session cookie obtained by POSTing credentials, and guards
|
||||
// against CSRF by checking the Referer header. The cookie was `SID` before qBittorrent 5.2 and is
|
||||
// `QBT_SID_<port>` since, and a successful login answers 200 "Ok." before and 204 with no body
|
||||
// since — both are accepted. Node's fetch keeps no cookie jar, so the cookie is
|
||||
// captured on login and carried by hand on every later call, with a single re-login on expiry.
|
||||
|
||||
import { readFileSync } from "node:fs";
|
||||
@@ -39,6 +41,21 @@ function meshConfig(file?: string): Record<string, string> {
|
||||
catch { return {}; }
|
||||
}
|
||||
|
||||
/** The WebUI username from qBittorrent's own qBittorrent.conf, in the config directory the mesh
|
||||
* mounts read-only (MESH_QBITTORRENT_CONFIG_DIR, which is the container's /config). The software's
|
||||
* file is the truth about who may log in, so the tools ask it rather than a setting that could
|
||||
* disagree. Absent, unreadable or unset yields undefined. */
|
||||
function confUsername(dir: string | undefined): string | undefined {
|
||||
if (!dir) return undefined;
|
||||
try {
|
||||
const line = readFileSync(`${dir.replace(/\/$/, "")}/qBittorrent/qBittorrent.conf`, "utf8")
|
||||
.split(/\r?\n/)
|
||||
.find((l) => l.startsWith("WebUI\\Username="));
|
||||
const value = line?.slice("WebUI\\Username=".length).trim();
|
||||
return value ? value : undefined;
|
||||
} catch { return undefined; }
|
||||
}
|
||||
|
||||
/** Read a secret the mesh mounted at a file path (an own-secret delivered by `secret accept`);
|
||||
* absent or unreadable yields undefined so callers fall back rather than crash. */
|
||||
function readSecret(file?: string): string | undefined {
|
||||
@@ -49,7 +66,8 @@ function readSecret(file?: string): string | undefined {
|
||||
|
||||
export class QbittorrentClient {
|
||||
readonly baseUrl: string;
|
||||
private sid: string | null = null;
|
||||
/** The session cookie as `name=value`, sent back exactly as it was set. */
|
||||
private session: string | null = null;
|
||||
|
||||
constructor(
|
||||
baseUrl: string,
|
||||
@@ -63,7 +81,9 @@ export class QbittorrentClient {
|
||||
* Build from the module's resolved environment. URL and password are read from
|
||||
* MESH_QBITTORRENT_URL and MESH_QBITTORRENT_PASSWORD; both must be present — an unconfigured
|
||||
* qBittorrent throws rather than pretend to be reachable, so the tools/events simply do not load
|
||||
* (the harness treats the throw as "exposes nothing"). The user defaults to "admin".
|
||||
* (the harness treats the throw as "exposes nothing"). The user is read from qBittorrent.conf,
|
||||
* falling back to "admin", the image's default. The password cannot be read there — qBittorrent
|
||||
* keeps only a PBKDF2 hash — so it is the own-secret the operator accepts.
|
||||
*/
|
||||
static fromEnv(env: NodeJS.ProcessEnv = process.env): QbittorrentClient {
|
||||
const cfg = meshConfig(env.MESH_QBITTORRENT_CONFIG_FILE);
|
||||
@@ -72,7 +92,9 @@ export class QbittorrentClient {
|
||||
if (!url || !password) {
|
||||
throw new Error("qBittorrent not configured — set MESH_QBITTORRENT_URL and MESH_QBITTORRENT_PASSWORD");
|
||||
}
|
||||
const user = cfg.user ?? env.MESH_QBITTORRENT_USER ?? "admin";
|
||||
// The WebUI username: a setting or the environment if one says so, else whatever
|
||||
// qBittorrent.conf holds (an adopted machine keeps its own), else the image's "admin".
|
||||
const user = cfg.user ?? env.MESH_QBITTORRENT_USER ?? confUsername(env.MESH_QBITTORRENT_CONFIG_DIR) ?? "admin";
|
||||
return new QbittorrentClient(url, user, password);
|
||||
}
|
||||
|
||||
@@ -82,19 +104,22 @@ export class QbittorrentClient {
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded", Referer: this.baseUrl },
|
||||
body: new URLSearchParams({ username: this.user, password: this.password }),
|
||||
});
|
||||
if (res.status === 401) throw new Error("qBittorrent login rejected — check credentials");
|
||||
if (!res.ok) throw new Error(`qBittorrent login: ${res.status} ${await res.text()}`);
|
||||
if ((await res.text()).trim() !== "Ok.") {
|
||||
// 4.x/5.0/5.1 answer 200 "Ok." or 200 "Fails."; 5.2 answers 204 with no body, or 401.
|
||||
const body = (await res.text()).trim();
|
||||
if (res.status !== 204 && body !== "Ok.") {
|
||||
throw new Error("qBittorrent login rejected — check credentials");
|
||||
}
|
||||
const match = res.headers.get("set-cookie")?.match(/SID=([^;]+)/);
|
||||
if (!match) throw new Error("qBittorrent login returned no SID cookie");
|
||||
this.sid = match[1];
|
||||
const match = res.headers.get("set-cookie")?.match(/((?:QBT_)?SID(?:_\d+)?)=([^;]+)/);
|
||||
if (!match) throw new Error("qBittorrent login returned no session cookie");
|
||||
this.session = `${match[1]}=${match[2]}`;
|
||||
}
|
||||
|
||||
private async call(method: "GET" | "POST", path: string, form?: Record<string, string>): Promise<Response> {
|
||||
if (!this.sid) await this.login();
|
||||
if (!this.session) await this.login();
|
||||
const doFetch = (): Promise<Response> => {
|
||||
const headers: Record<string, string> = { Referer: this.baseUrl, Cookie: `SID=${this.sid}` };
|
||||
const headers: Record<string, string> = { Referer: this.baseUrl, Cookie: this.session ?? "" };
|
||||
const init: RequestInit = { method, headers };
|
||||
if (form) {
|
||||
headers["Content-Type"] = "application/x-www-form-urlencoded";
|
||||
@@ -103,8 +128,8 @@ export class QbittorrentClient {
|
||||
return fetch(`${this.baseUrl}/api/v2/${path}`, init);
|
||||
};
|
||||
let res = await doFetch();
|
||||
if (res.status === 403) {
|
||||
// The SID expired — re-authenticate once and retry, rather than fail a routine call.
|
||||
if (res.status === 403 || res.status === 401) {
|
||||
// The session expired — re-authenticate once and retry, rather than fail a routine call.
|
||||
await this.login();
|
||||
res = await doFetch();
|
||||
}
|
||||
|
||||
@@ -16,10 +16,25 @@
|
||||
},
|
||||
"listens": [
|
||||
{
|
||||
"port": 8080,
|
||||
"name": "web",
|
||||
"port": 8112,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
"why": "the download client's pages"
|
||||
"why": "the download client's pages, and the WebUI API its consumers and its own tools call. qBittorrent refuses a request whose Host names a port other than the one it listens on, so it listens on the machine port itself (host network, WEBUI_PORT=${port:8112}) and the two cannot differ on any machine"
|
||||
},
|
||||
{
|
||||
"name": "peers",
|
||||
"port": 6881,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
"why": "incoming BitTorrent peer connections; announced to trackers and peers, so qBittorrent listens on the machine port itself (TORRENTING_PORT=${port:6881})"
|
||||
},
|
||||
{
|
||||
"name": "peers-udp",
|
||||
"port": 6881,
|
||||
"protocol": "udp",
|
||||
"from": "mesh",
|
||||
"why": "DHT and uTP on the same number as the peer port"
|
||||
}
|
||||
],
|
||||
"accesses": [
|
||||
@@ -35,10 +50,15 @@
|
||||
"path": "/var/lib/mesh/qbittorrent",
|
||||
"mode": "0700"
|
||||
},
|
||||
{
|
||||
"id": "state",
|
||||
"type": "directory",
|
||||
"mode": "0700",
|
||||
"place": "."
|
||||
},
|
||||
{
|
||||
"id": "config",
|
||||
"type": "directory",
|
||||
"path": "/services/qbittorrent/config",
|
||||
"mode": "0700",
|
||||
"owner": "1000:1000"
|
||||
},
|
||||
@@ -46,24 +66,24 @@
|
||||
"id": "server",
|
||||
"type": "container",
|
||||
"name": "qbittorrent",
|
||||
"image": "lscr.io/linuxserver/qbittorrent@sha256:a00b6a597a3832a1814cde0ef60abc55c94644f3f80902c3432f6af6de8d4a96",
|
||||
"image": "lscr.io/linuxserver/qbittorrent@sha256:457e4eec2ee3f5e4ef59f237ad51f6143deba9f7445ab48bb5204a98888ef9aa",
|
||||
"env": {
|
||||
"PUID": "1000",
|
||||
"PGID": "1000",
|
||||
"TZ": "Etc/UTC"
|
||||
"TZ": "Etc/UTC",
|
||||
"WEBUI_PORT": "${port:8112}",
|
||||
"TORRENTING_PORT": "${port:6881}"
|
||||
},
|
||||
"ports": [
|
||||
"8080"
|
||||
],
|
||||
"volumes": [
|
||||
"/services/qbittorrent/config:/config",
|
||||
"${dir:config}:/config",
|
||||
"/services/media/downloads:/downloads"
|
||||
]
|
||||
],
|
||||
"network": "host"
|
||||
},
|
||||
{
|
||||
"id": "runtime-config",
|
||||
"type": "file",
|
||||
"path": "/var/lib/mesh/qbittorrent/config.json",
|
||||
"path": "${dir:state}/config.json",
|
||||
"mode": "0600",
|
||||
"content": "{}\n",
|
||||
"merge": "json"
|
||||
@@ -76,22 +96,46 @@
|
||||
"volumes": [
|
||||
"/var/lib/mesh/qbittorrent/broker:/run/secrets/broker:ro",
|
||||
"/var/lib/mesh/qbittorrent/password:/run/secrets/password:ro",
|
||||
"/var/lib/mesh/qbittorrent/config.json:/run/config/config.json:ro",
|
||||
"/services/qbittorrent/config:/var/lib/qbittorrent/config:ro"
|
||||
"${dir:state}/config.json:/run/config/config.json:ro",
|
||||
"${dir:config}:/var/lib/qbittorrent/config:ro"
|
||||
],
|
||||
"env": {
|
||||
"MESH_BROKER_FILE": "/run/secrets/broker",
|
||||
"MESH_QBITTORRENT_URL": "http://127.0.0.1:8080",
|
||||
"MESH_QBITTORRENT_URL": "http://127.0.0.1:${port:8112}",
|
||||
"MESH_QBITTORRENT_PASSWORD_FILE": "/run/secrets/password",
|
||||
"MESH_QBITTORRENT_CONFIG_FILE": "/run/config/config.json",
|
||||
"MESH_QBITTORRENT_CONFIG_DIR": "/var/lib/qbittorrent/config"
|
||||
},
|
||||
"restart-on": [
|
||||
"runtime-config"
|
||||
"runtime-config",
|
||||
"needs-password"
|
||||
],
|
||||
"artifact": "runtime"
|
||||
}
|
||||
],
|
||||
"provides": [
|
||||
"qbittorrent-api"
|
||||
],
|
||||
"serves": {
|
||||
"qbittorrent-api": {
|
||||
"scheme": "http",
|
||||
"port": 8112,
|
||||
"url-base": "",
|
||||
"username": "admin"
|
||||
}
|
||||
},
|
||||
"requires": [
|
||||
"route"
|
||||
],
|
||||
"contributes": {
|
||||
"route": {
|
||||
"label": "qbittorrent",
|
||||
"endpoint": "web"
|
||||
}
|
||||
},
|
||||
"binds": {
|
||||
"route": "${dir:state}/route.json"
|
||||
},
|
||||
"build": {
|
||||
"on": [
|
||||
{
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
},
|
||||
"listens": [
|
||||
{
|
||||
"name": "web",
|
||||
"port": 7878,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
@@ -74,7 +75,7 @@
|
||||
],
|
||||
"env": {
|
||||
"MESH_BROKER_FILE": "/run/secrets/broker",
|
||||
"MESH_RADARR_URL": "http://127.0.0.1:7878",
|
||||
"MESH_RADARR_URL": "http://127.0.0.1:${port:7878}",
|
||||
"MESH_RADARR_CONFIG_DIR": "/var/lib/radarr/config"
|
||||
},
|
||||
"artifact": "runtime"
|
||||
@@ -86,7 +87,7 @@
|
||||
"contributes": {
|
||||
"route": {
|
||||
"label": "movies",
|
||||
"port": 7878
|
||||
"endpoint": "web"
|
||||
}
|
||||
},
|
||||
"binds": {
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
},
|
||||
"listens": [
|
||||
{
|
||||
"name": "cache",
|
||||
"port": 6379,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
|
||||
@@ -27,12 +27,14 @@
|
||||
},
|
||||
"listens": [
|
||||
{
|
||||
"name": "http",
|
||||
"port": 80,
|
||||
"protocol": "tcp",
|
||||
"from": "anywhere",
|
||||
"why": "public HTTP, and the ACME HTTP-01 challenge answered at the name being certified"
|
||||
},
|
||||
{
|
||||
"name": "https",
|
||||
"port": 443,
|
||||
"protocol": "tcp",
|
||||
"from": "anywhere",
|
||||
|
||||
+24
-22
@@ -5,11 +5,12 @@
|
||||
"container-runtime"
|
||||
],
|
||||
"own-secrets": {
|
||||
"secret": "/var/lib/searxng-module/secret.secret",
|
||||
"secret": "/var/lib/mesh/searxng/secret",
|
||||
"broker": "/var/lib/mesh/searxng/broker"
|
||||
},
|
||||
"listens": [
|
||||
{
|
||||
"name": "web",
|
||||
"port": 8080,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
@@ -26,22 +27,14 @@
|
||||
{
|
||||
"id": "state",
|
||||
"type": "directory",
|
||||
"path": "/var/lib/searxng-module",
|
||||
"mode": "0700"
|
||||
"mode": "0700",
|
||||
"place": "."
|
||||
},
|
||||
{
|
||||
"id": "valkey-data",
|
||||
"type": "directory",
|
||||
"path": "/var/lib/searxng-module/valkey-data",
|
||||
"mode": "0700"
|
||||
},
|
||||
{
|
||||
"id": "server-env",
|
||||
"type": "file",
|
||||
"path": "/var/lib/searxng-module/server.env",
|
||||
"mode": "0600",
|
||||
"content": "SEARXNG_SECRET=${secret:secret}\nSEARXNG_VALKEY_URL=valkey://valkey:6379/0\n"
|
||||
},
|
||||
{
|
||||
"id": "net",
|
||||
"type": "network",
|
||||
@@ -62,30 +55,39 @@
|
||||
"warning"
|
||||
],
|
||||
"volumes": [
|
||||
"/var/lib/searxng-module/valkey-data:/data"
|
||||
"${dir:valkey-data}:/data"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "settings",
|
||||
"type": "file",
|
||||
"path": "${dir:state}/settings.yml",
|
||||
"mode": "0600",
|
||||
"merge": "json",
|
||||
"content": "{\n \"use_default_settings\": true,\n \"server\": {\n \"secret_key\": \"${secret:secret}\",\n \"base_url\": false,\n \"limiter\": false,\n \"image_proxy\": false,\n \"public_instance\": false\n },\n \"search\": {\n \"formats\": [\"html\", \"json\"]\n },\n \"valkey\": {\n \"url\": \"valkey://valkey:6379/0\"\n }\n}\n"
|
||||
},
|
||||
{
|
||||
"id": "server",
|
||||
"type": "container",
|
||||
"name": "searxng",
|
||||
"image": "searxng/searxng@sha256:c7cc75852051bf6254afda6ed1b920dd1677d8efe4ab141bf558f02e582f4371",
|
||||
"image": "searxng/searxng@sha256:cd8812607ab73730a0b1a0dc4990223fe1b9e383f6f35947114d0bef7f8bb441",
|
||||
"network": "searxng",
|
||||
"env-file": [
|
||||
"/var/lib/searxng-module/server.env"
|
||||
],
|
||||
"ports": [
|
||||
"8080"
|
||||
],
|
||||
"secrets-in-environment": "SEARXNG_SECRET is env-only, but settings.yml carries server.secret_key; convertible by mounting a generated settings.yml, not yet done"
|
||||
"volumes": [
|
||||
"${dir:state}/settings.yml:/etc/searxng/settings.yml:ro"
|
||||
],
|
||||
"restart-on": [
|
||||
"settings"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "runtime-config",
|
||||
"type": "file",
|
||||
"path": "/var/lib/mesh/searxng/config.json",
|
||||
"mode": "0600",
|
||||
"content": "{}\n",
|
||||
"merge": "json"
|
||||
"content": "{}\n"
|
||||
},
|
||||
{
|
||||
"id": "runtime",
|
||||
@@ -98,7 +100,7 @@
|
||||
],
|
||||
"env": {
|
||||
"MESH_BROKER_FILE": "/run/secrets/broker",
|
||||
"MESH_SEARXNG_URL": "http://127.0.0.1:8080",
|
||||
"MESH_SEARXNG_URL": "http://127.0.0.1:${port:8080}",
|
||||
"MESH_SEARXNG_CONFIG_FILE": "/run/config/config.json"
|
||||
},
|
||||
"restart-on": [
|
||||
@@ -113,11 +115,11 @@
|
||||
"contributes": {
|
||||
"route": {
|
||||
"label": "searxng",
|
||||
"port": 8080
|
||||
"endpoint": "web"
|
||||
}
|
||||
},
|
||||
"binds": {
|
||||
"route": "/var/lib/searxng-module/route.json"
|
||||
"route": "${dir:state}/route.json"
|
||||
},
|
||||
"build": {
|
||||
"on": [
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
],
|
||||
"listens": [
|
||||
{
|
||||
"name": "web",
|
||||
"port": 8080,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
},
|
||||
"listens": [
|
||||
{
|
||||
"name": "web",
|
||||
"port": 8989,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
@@ -79,7 +80,7 @@
|
||||
],
|
||||
"env": {
|
||||
"MESH_BROKER_FILE": "/run/secrets/broker",
|
||||
"MESH_SONARR_URL": "http://127.0.0.1:8989",
|
||||
"MESH_SONARR_URL": "http://127.0.0.1:${port:8989}",
|
||||
"MESH_SONARR_CONFIG_DIR": "/var/lib/sonarr/config"
|
||||
},
|
||||
"artifact": "runtime"
|
||||
@@ -91,7 +92,7 @@
|
||||
"contributes": {
|
||||
"route": {
|
||||
"label": "series",
|
||||
"port": 8989
|
||||
"endpoint": "web"
|
||||
}
|
||||
},
|
||||
"binds": {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
],
|
||||
"listens": [
|
||||
{
|
||||
"name": "ssh",
|
||||
"port": 22,
|
||||
"protocol": "tcp",
|
||||
"from": "anywhere",
|
||||
@@ -31,6 +32,7 @@
|
||||
"type": "service",
|
||||
"unit": "sshd.service",
|
||||
"state": "running",
|
||||
"boot": "enabled",
|
||||
"restart-on": [
|
||||
"config"
|
||||
]
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
},
|
||||
"listens": [
|
||||
{
|
||||
"name": "acme",
|
||||
"port": 9000,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
],
|
||||
"listens": [
|
||||
{
|
||||
"name": "web",
|
||||
"port": 8181,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
@@ -85,7 +86,7 @@
|
||||
"contributes": {
|
||||
"route": {
|
||||
"label": "tautulli",
|
||||
"port": 8181
|
||||
"endpoint": "web"
|
||||
}
|
||||
},
|
||||
"binds": {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
},
|
||||
"route": {
|
||||
"label": "umami",
|
||||
"port": 3000
|
||||
"endpoint": "web"
|
||||
}
|
||||
},
|
||||
"binds": {
|
||||
@@ -49,10 +49,11 @@
|
||||
},
|
||||
"listens": [
|
||||
{
|
||||
"name": "web",
|
||||
"port": 3000,
|
||||
"protocol": "tcp",
|
||||
"from": "anywhere",
|
||||
"why": "one port serves two surfaces: the dashboard (the proxy gates it to the mesh) and the public collection endpoint that the browsers of every tracked site POST to \u2014 so the port itself must be reachable from anywhere"
|
||||
"from": "mesh",
|
||||
"why": "one port serves two surfaces \u2014 the dashboard and the collection endpoint that the browsers of every tracked site POST to. Both are reached through the proxy, by name, so the port is how the proxy reaches this module and nothing else (novox/hq ADR 0045). It said \"anywhere\" and gave the reason that the collection endpoint must be public, which is true of the name and not of the port: opened, the machine-side port served the dashboard over plain HTTP to the internet, bypassing every rule the proxy applies by path"
|
||||
}
|
||||
],
|
||||
"resources": [
|
||||
|
||||
@@ -6,54 +6,63 @@
|
||||
],
|
||||
"listens": [
|
||||
{
|
||||
"name": "web",
|
||||
"port": 8443,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
"why": "the controller web UI, over its own self-signed tls; reaching it from outside is a route grant later"
|
||||
},
|
||||
{
|
||||
"name": "inform",
|
||||
"port": 8080,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
"why": "device inform \u2014 how APs and switches check in and are adopted"
|
||||
},
|
||||
{
|
||||
"name": "stun",
|
||||
"port": 3478,
|
||||
"protocol": "udp",
|
||||
"from": "mesh",
|
||||
"why": "STUN, so managed devices can find the controller through NAT"
|
||||
},
|
||||
{
|
||||
"name": "discovery",
|
||||
"port": 10001,
|
||||
"protocol": "udp",
|
||||
"from": "mesh",
|
||||
"why": "device discovery \u2014 the controller finds unadopted devices on the network"
|
||||
},
|
||||
{
|
||||
"name": "discovery-l2",
|
||||
"port": 1902,
|
||||
"protocol": "udp",
|
||||
"from": "mesh",
|
||||
"why": "layer-2 (UBNT) discovery broadcasts; published on 1902, the container listens on 1900"
|
||||
},
|
||||
{
|
||||
"name": "portal-tls",
|
||||
"port": 8843,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
"why": "the guest captive portal over https"
|
||||
},
|
||||
{
|
||||
"name": "portal",
|
||||
"port": 8880,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
"why": "the guest captive portal over http"
|
||||
},
|
||||
{
|
||||
"name": "speedtest",
|
||||
"port": 6789,
|
||||
"protocol": "tcp",
|
||||
"from": "mesh",
|
||||
"why": "mobile-app speed-test throughput measurement"
|
||||
},
|
||||
{
|
||||
"name": "syslog",
|
||||
"port": 5514,
|
||||
"protocol": "udp",
|
||||
"from": "mesh",
|
||||
|
||||
Reference in New Issue
Block a user