Skip to content

Commit 4fcfc50

Browse files
committed
fixed urls for mediaservers and added dockhand
1 parent 8ca0b1c commit 4fcfc50

File tree

6 files changed

+491
-2
lines changed

6 files changed

+491
-2
lines changed

.github/workflows/tailscale.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Sync Tailscale ACLs
2+
3+
on:
4+
push:
5+
branches: ["main"]
6+
pull_request:
7+
branches: ["main"]
8+
9+
jobs:
10+
acls:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- uses: actions/checkout@v4
15+
16+
- name: Validate HuJSON
17+
run: python3 tailscale/validate-acl.py tailscale/acl.hujson
18+
19+
- name: Deploy ACL
20+
if: github.event_name == 'push'
21+
id: deploy-acl
22+
uses: tailscale/gitops-acl-action@v1
23+
with:
24+
api-key: ${{ secrets.TS_API_KEY }}
25+
tailnet: ${{ secrets.TS_TAILNET }}
26+
action: apply
27+
policy-file: tailscale/acl.hujson
28+
29+
- name: Test ACL
30+
if: github.event_name == 'pull_request'
31+
id: test-acl
32+
uses: tailscale/gitops-acl-action@v1
33+
with:
34+
api-key: ${{ secrets.TS_API_KEY }}
35+
tailnet: ${{ secrets.TS_TAILNET }}
36+
action: test
37+
policy-file: tailscale/acl.hujson

services/c137/01-infra/compose.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,16 @@ services:
3131
- --certificatesresolvers.cloudflare.acme.storage=/letsencrypt/acme.json
3232
- --serversTransport.insecureSkipVerify=true
3333
restart: unless-stopped
34+
dockhand:
35+
image: fnsys/dockhand:latest
36+
container_name: dockhand
37+
labels:
38+
- traefik.enable=true
39+
- "traefik.http.routers.dockhand.rule=Host(`dockhand.{{ m_wd_domain_me }}`)"
40+
volumes:
41+
- /var/run/docker.sock:/var/run/docker.sock
42+
- "{{ appdata_path }}/apps/dockhand:/app/data"
43+
restart: unless-stopped
3444
# copyparty:
3545
# image: copyparty/ac:latest
3646
# container_name: copyparty

services/c137/02-mediaservers/compose.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ services:
6666
- "/mnt/rust/media/music/library:/music:ro"
6767
labels:
6868
- traefik.enable=true
69-
- "traefik.http.routers.navidrome.rule=Host(`navidrome.m.wd.ktz.me`)"
69+
- "traefik.http.routers.navidrome.rule=Host(`navidrome.{{ m_wd_domain_me }}`)"
7070
- traefik.http.services.navidrome.loadbalancer.server.port=4533
7171
environment:
7272
- ND_MUSICFOLDER=/music
@@ -79,7 +79,7 @@ services:
7979
container_name: cwa
8080
labels:
8181
- traefik.enable=true
82-
- "traefik.http.routers.cwa.rule=Host(`cwa.m.wd.ktz.me`)"
82+
- "traefik.http.routers.cwa.rule=Host(`cwa.{{ m_wd_domain_me }}`)"
8383
- traefik.http.routers.cwa.entrypoints=websecure
8484
- traefik.http.routers.cwa.tls=true
8585
- traefik.http.services.cwa.loadbalancer.server.port=8083

tailscale/acl.hujson

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
{
2+
// Tailscale ACLs (GitOps-managed).
3+
"randomizeClientPort": true, // for OPNsense
4+
5+
"tagOwners": {
6+
// Site tags
7+
"tag:norfolk": [],
8+
9+
// Role/service tags
10+
"tag:server": [],
11+
"tag:ssh": [],
12+
"tag:idp": [],
13+
"tag:connector-uk": [],
14+
"tag:connector-github": [],
15+
"tag:container": [],
16+
17+
// K8s tags (owned by the operator tag)
18+
"tag:k8s-operator": ["tag:k8s-operator"],
19+
"tag:k8s": ["tag:k8s-operator"],
20+
"tag:k8s-m720q": ["tag:k8s-operator"],
21+
"tag:k8s-funnel": ["tag:k8s-operator"]
22+
},
23+
24+
"groups": {
25+
// User-based grouping for client devices (exit-node access).
26+
"group:alex": ["alexktz@gmail.com"]
27+
},
28+
29+
"hosts": {
30+
// Godmode workstations (explicit device ownership, no tags).
31+
"baldrick": "100.67.48.126",
32+
"magrathea": "100.95.89.118",
33+
"alexs-mac-studio": "100.92.189.64",
34+
"alexs-macbook-pro14": "100.82.236.9",
35+
"mooncake": "100.80.82.66"
36+
},
37+
38+
"ssh": [
39+
{
40+
"action": "accept",
41+
// Keep this list in sync with hosts above.
42+
"src": [
43+
"baldrick",
44+
"magrathea",
45+
"alexs-mac-studio",
46+
"alexs-macbook-pro14",
47+
"mooncake"
48+
],
49+
"dst": ["tag:ssh", "autogroup:self"],
50+
"users": ["root", "autogroup:nonroot"]
51+
}
52+
],
53+
54+
"nodeAttrs": [
55+
{
56+
"target": ["tag:k8s-funnel"],
57+
"attr": ["funnel"]
58+
},
59+
{
60+
"target": ["tag:idp"],
61+
"attr": ["funnel"]
62+
},
63+
{
64+
"target": ["autogroup:member"],
65+
"attr": ["funnel"]
66+
},
67+
{
68+
"target": ["autogroup:member"],
69+
"attr": ["drive:share", "drive:access"]
70+
},
71+
{
72+
"target": ["*"],
73+
"app": {
74+
"tailscale.com/app-connectors": [
75+
{
76+
"name": "bbc",
77+
"connectors": ["tag:connector-uk"],
78+
"domains": ["*.bbc.com", "*.bbc.co.uk", "*.bbci.co.uk"]
79+
},
80+
{
81+
"name": "github",
82+
"connectors": ["tag:connector-github"],
83+
"presetAppID": "github"
84+
}
85+
]
86+
}
87+
}
88+
],
89+
90+
"grants": [
91+
{
92+
// Godmode workstations
93+
"src": [
94+
"baldrick",
95+
"magrathea",
96+
"alexs-mac-studio",
97+
"alexs-macbook-pro14",
98+
"mooncake"
99+
],
100+
"dst": ["*"],
101+
"ip": ["*"]
102+
},
103+
{
104+
// Servers can access each other
105+
"src": ["tag:server"],
106+
"dst": ["tag:server"],
107+
"ip": ["*"]
108+
},
109+
{
110+
// Allow your user-owned devices to use exit nodes.
111+
"src": ["group:alex"],
112+
"dst": ["autogroup:internet"],
113+
"ip": ["*"]
114+
},
115+
{
116+
// RDU subnets
117+
"src": [
118+
"baldrick",
119+
"magrathea",
120+
"alexs-mac-studio",
121+
"alexs-macbook-pro14",
122+
"mooncake"
123+
],
124+
"dst": ["10.42.0.0/21"],
125+
"ip": ["*"]
126+
},
127+
{
128+
// NR subnets
129+
"src": [
130+
"baldrick",
131+
"magrathea",
132+
"alexs-mac-studio",
133+
"alexs-macbook-pro14",
134+
"mooncake"
135+
],
136+
"dst": ["192.168.16.0/24"],
137+
"ip": ["*"]
138+
},
139+
{
140+
// Default DNS for tailnet nodes
141+
"src": ["*"],
142+
"dst": ["10.42.0.253"],
143+
"ip": ["udp:53"]
144+
},
145+
{
146+
"src": ["autogroup:shared"],
147+
"dst": ["*"],
148+
"ip": ["*"]
149+
},
150+
{
151+
// K8s API server access
152+
"src": ["*"],
153+
"dst": ["tag:k8s-operator"],
154+
"ip": ["tcp:443"]
155+
},
156+
{
157+
"src": ["autogroup:member"],
158+
"dst": ["tag:k8s-operator"],
159+
"app": {
160+
"tailscale.com/cap/kubernetes": [
161+
{
162+
"impersonate": {
163+
"groups": ["system:masters"]
164+
}
165+
}
166+
]
167+
}
168+
},
169+
{
170+
"src": ["*"],
171+
"dst": ["tag:idp"],
172+
"ip": ["*"]
173+
},
174+
{
175+
"src": ["*"],
176+
"dst": ["tag:idp"],
177+
"app": {
178+
"tailscale.com/cap/tsidp": [
179+
{
180+
"allow_admin_ui": true,
181+
"allow_dcr": true,
182+
"includeInUserInfo": true,
183+
"users": ["*"],
184+
"resources": ["*"]
185+
}
186+
]
187+
}
188+
}
189+
],
190+
191+
"autoApprovers": {
192+
"routes": {
193+
"0.0.0.0/0": ["tag:connector-uk"],
194+
"::/0": ["tag:connector-uk"]
195+
},
196+
"services": {
197+
"tag:k8s": ["tag:k8s"],
198+
"tag:k8s-operator": ["tag:k8s-operator"]
199+
}
200+
}
201+
}

tailscale/cleanup-tags.sh

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
#!/usr/bin/env bash
2+
set -uo pipefail
3+
4+
TAILSCALE_API_KEY="${TAILSCALE_API_KEY:-}"
5+
TAILNET="${TAILNET:-}"
6+
DRY_RUN="${DRY_RUN:-1}"
7+
8+
if [[ -z "$TAILSCALE_API_KEY" ]]; then
9+
echo "TAILSCALE_API_KEY is required." >&2
10+
exit 1
11+
fi
12+
13+
if [[ -z "$TAILNET" ]]; then
14+
if command -v tailscale >/dev/null 2>&1; then
15+
TAILNET="$(tailscale status --json | jq -r '.CurrentTailnet.MagicDNSSuffix // empty')"
16+
fi
17+
fi
18+
19+
if [[ -z "$TAILNET" ]]; then
20+
echo "TAILNET is required (example: ktz.ts.net)." >&2
21+
exit 1
22+
fi
23+
24+
REMOVE_TAGS=(
25+
"tag:rdu"
26+
"tag:rdu-px"
27+
"tag:rdu-sr"
28+
"tag:norfolk-sr"
29+
"tag:ca-ont"
30+
"tag:cloud"
31+
"tag:zfs-replication"
32+
"tag:family-share"
33+
"tag:exitnode"
34+
"tag:use-exitnode"
35+
)
36+
37+
remove_json="$(printf '%s\n' "${REMOVE_TAGS[@]}" | jq -R . | jq -s .)"
38+
39+
devices_json="$(curl -fsS -u "${TAILSCALE_API_KEY}:" \
40+
"https://api.tailscale.com/api/v2/tailnet/${TAILNET}/devices")"
41+
42+
updates="$(
43+
echo "$devices_json" | jq -r --argjson remove "$remove_json" '
44+
.devices[]
45+
| {id, name, tags: (.tags // [])}
46+
| . as $d
47+
| ($d.tags - $remove) as $newtags
48+
| select(($d.tags|length) != ($newtags|length))
49+
| {id: $d.id, name: $d.name, tags: $newtags}
50+
| @base64
51+
'
52+
)"
53+
54+
if [[ -z "$updates" ]]; then
55+
echo "No devices found with removable tags."
56+
exit 0
57+
fi
58+
59+
echo "Tailnet: $TAILNET"
60+
echo "Remove tags: ${REMOVE_TAGS[*]}"
61+
echo ""
62+
63+
while read -r row; do
64+
obj="$(echo "$row" | base64 --decode)"
65+
id="$(echo "$obj" | jq -r '.id')"
66+
name="$(echo "$obj" | jq -r '.name')"
67+
tags="$(echo "$obj" | jq -c '.tags')"
68+
69+
if [[ "$DRY_RUN" != "0" ]]; then
70+
echo "DRY RUN: $name ($id) -> $tags"
71+
continue
72+
fi
73+
74+
resp_file="$(mktemp)"
75+
http_code="$(
76+
curl -sS -u "${TAILSCALE_API_KEY}:" \
77+
-H "Content-Type: application/json" \
78+
-d "{\"tags\":$tags}" \
79+
-o "$resp_file" \
80+
-w "%{http_code}" \
81+
"https://api.tailscale.com/api/v2/device/${id}/tags" || true
82+
)"
83+
84+
if [[ "$http_code" == "200" || "$http_code" == "204" ]]; then
85+
echo "Updated: $name ($id)"
86+
else
87+
echo "ERROR: $name ($id) -> HTTP $http_code" >&2
88+
if [[ -s "$resp_file" ]]; then
89+
echo "Response: $(cat "$resp_file")" >&2
90+
fi
91+
fi
92+
93+
rm -f "$resp_file"
94+
done <<< "$updates"

0 commit comments

Comments
 (0)