feat(share): Tailscale serve stack to share internal services with external tailnet users
Adds stacks/share/ — a dedicated stack of userspace Tailscale 'serve' nodes that expose individual internal services to external tailnet users (a friend on his own tailnet) over each node's 100.x identity. No public exposure, no LAN access, immune to the friend's home-subnet addressing. First node ts-roms serves RomM at roms-share.<tailnet>.ts.net -> romm:8080. One reusable tag:share auth key + one ACL rule cover every node; adding a service is a serve-<svc>.json + a copied service block. roms stack reverted to a pointer comment.
This commit is contained in:
@@ -15,6 +15,10 @@
|
||||
# The labdata/romm/* targets are auto-created by Docker as root on first start.
|
||||
#
|
||||
# Only romm joins `edge` (Caddy proxies romm:8080); romm-db stays on `roms` only.
|
||||
#
|
||||
# To share RomM with an EXTERNAL tailnet user (a friend on his own tailnet), see
|
||||
# the dedicated `share` stack (stacks/share/), which runs a Tailscale `serve`
|
||||
# node joined to this `roms` network.
|
||||
|
||||
services:
|
||||
romm:
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
# share stack — Tailscale `serve` nodes that expose individual INTERNAL services
|
||||
# to EXTERNAL tailnet users (a friend on his own tailnet) over each node's 100.x
|
||||
# CGNAT identity. No public exposure, no LAN access, and immune to the friend's
|
||||
# home-subnet addressing (a node IP can never collide with his 192.168.1.0/24).
|
||||
#
|
||||
# Each service to share = one userspace Tailscale container that `tailscale serve`s
|
||||
# straight to that service's backend, so the container joins that service's PRIVATE
|
||||
# network (roms, foundry, media, ...) — NOT edge/Caddy.
|
||||
#
|
||||
# Shared, set-once-for-all plumbing:
|
||||
# - Auth: ONE reusable, NON-ephemeral, TAGGED (tag:share) auth key in stack.env
|
||||
# authenticates EVERY node here. Tagged nodes never expire.
|
||||
# - ACL : ONE rule { src: [friend@…], dst: ["tag:share:443"] } governs them all.
|
||||
# - Admin (one-time): enable MagicDNS + HTTPS certificates; add
|
||||
# "tagOwners": { "tag:share": ["autogroup:admin"] }.
|
||||
#
|
||||
# To share a NEW service:
|
||||
# 1. add a `serve-<svc>.json` (Proxy -> http://<container>:<port>)
|
||||
# 2. add a `ts-<svc>` service block below (copy ts-roms), join the svc network
|
||||
# 3. add that network under `networks:` as external
|
||||
# 4. push, then admin console -> Machines -> <svc>-share -> Share -> friend email
|
||||
|
||||
services:
|
||||
ts-roms:
|
||||
image: tailscale/tailscale:latest
|
||||
container_name: ts-roms
|
||||
hostname: roms-share
|
||||
restart: unless-stopped
|
||||
networks: [roms]
|
||||
env_file:
|
||||
- stack.env
|
||||
environment:
|
||||
- TS_HOSTNAME=roms-share
|
||||
- TS_STATE_DIR=/var/lib/tailscale
|
||||
- TS_USERSPACE=true
|
||||
- TS_SERVE_CONFIG=/config/serve.json
|
||||
- TS_EXTRA_ARGS=--advertise-tags=tag:share
|
||||
volumes:
|
||||
- /config/share/ts-roms:/var/lib/tailscale
|
||||
- ./serve-roms.json:/config/serve.json:ro
|
||||
|
||||
networks:
|
||||
roms:
|
||||
name: roms
|
||||
external: true
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"TCP": {
|
||||
"443": {
|
||||
"HTTPS": true
|
||||
}
|
||||
},
|
||||
"Web": {
|
||||
"${TS_CERT_DOMAIN}:443": {
|
||||
"Handlers": {
|
||||
"/": {
|
||||
"Proxy": "http://romm:8080"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
# share stack secrets — Tailscale `serve` nodes.
|
||||
# Read via env_file (no ${VAR} interpolation), so this git stack needs no
|
||||
# Portainer UI env vars. See memory portainer-env-interpolation.
|
||||
TZ=America/Chicago
|
||||
|
||||
# ── Tailscale auth (shared by EVERY ts-* node in this stack) ──────────────────
|
||||
# ONE reusable, NON-ephemeral, TAGGED auth key from:
|
||||
# https://login.tailscale.com/admin/settings/keys ("Generate auth key")
|
||||
# - Reusable: ON - Ephemeral: OFF - Tags: tag:share
|
||||
# Prereq: add "tagOwners": { "tag:share": ["autogroup:admin"] } to your ACLs
|
||||
# first, or key generation rejects the tag. Tagged nodes never expire, so once
|
||||
# the nodes have joined this key is no longer needed (state lives in the volumes).
|
||||
TS_AUTHKEY=tskey-auth-kGQ7kgFizL11CNTRL-HWpbE5TudYTaV2ss4LDmYT7sZ8EddXVH
|
||||
Reference in New Issue
Block a user