Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ ScaleTail provides ready-to-run [Docker Compose](https://docs.docker.com/compose
| 🧩 **Pi-hole** | A network-level ad blocker that acts as a DNS sinkhole. | [Details](services/pihole) |
| 🆔 **Pocket ID** | A self-hosted decentralized identity (OIDC) solution for secure authentication. | [Details](services/pocket-id) |
| 🌐 **Rustdesk Server** | RustDesk is an open source remote control alternative for self-hosting and security. | [Details](services/rustdesk-server) |
| 🏰 **SOCFortress WAF** | Self-hosted Web Application Firewall with a modern admin UI (using Caddy + Coraza). | [Details](services/socfortress-waf) |
| 🔒 **Technitium DNS** | An open-source DNS server that can be used for self-hosted DNS services. | [Details](services/technitium) |
| 🌐 **Traefik** | A modern reverse proxy and load balancer for microservices. | [Details](services/traefik) |
| 🌐 **Tailscale App Connector Node** | Configure a device to act as a App connector node for your Tailscale network. | [Details](services/tailscale-app-connector-node) |
Expand Down Expand Up @@ -206,6 +207,7 @@ ScaleTail provides ready-to-run [Docker Compose](https://docs.docker.com/compose
| 🏠 Service | 📝 Description | 🔗 Link |
| -------------------- | ---------------------------------------------------------------------- | ---------------------------------- |
| 🏡 **Home Assistant** | An open-source home automation platform for controlling smart devices. | [Details](services/home-assistant) |
| **Home Bridge** | Homebridge is a lightweight Node.js server that emulates the HomeKit API. | [Details](services/homebridge) |

### 📱 Utilities

Expand Down
18 changes: 18 additions & 0 deletions services/homebridge/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# =============================================================================
# Homebridge + Tailscale Sidecar Environment
# Copy to .env and fill values. Keep TS_AUTHKEY secret!
# =============================================================================

# --- REQUIRED: Tailscale ---
# Get a reusable or ephemeral auth key from https://login.tailscale.com/admin/settings/keys
# Recommended: auth key with "reusable" + "ephemeral" for containers, tagged if using ACLs
TS_AUTHKEY=tskey-auth-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

# --- REQUIRED: Service identity ---
SERVICE=homebridge
IMAGE_URL=homebridge/homebridge:latest

# --- Common container settings ---
TZ=Europe/Amsterdam
PUID=1000
PGID=1000
77 changes: 77 additions & 0 deletions services/homebridge/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# homebridge with Tailscale Sidecar

This Docker Compose configuration sets up [Homebridge](https://github.com/homebridge/homebridge) with Tailscale as a sidecar container. Homebridge emulates the iOS HomeKit API, enabling HomeKit integration for non-Apple smart home devices. The Homebridge UI is reachable **only over your Tailnet** via HTTPS.

## ⚠️ Critical Limitation

**Homebridge requires `host` network mode for HomeKit mDNS discovery.** This sidecar configuration isolates Homebridge into a separate network namespace, which **breaks HomeKit discovery entirely**. HomeKit clients will not be able to find or control your devices.

**What works:**
- Access the Homebridge UI web interface over HTTPS via Tailnet: `https://homebridge.your-tailnet.ts.net`
- Install plugins, edit config, view logs
- Full remote administration

**What doesn't work:**
- HomeKit device discovery on your local network
- HomeKit app integration from iPhone/iPad
- Siri control via HomeKit

If you need HomeKit discovery, run Homebridge with `network_mode: host` instead (standard Docker setup, no Tailscale sidecar). If you only need the web UI for administration from Tailnet, proceed with this configuration.

## About Homebridge

Homebridge is a lightweight Node.js server that emulates the HomeKit API. It translates between HomeKit and non-HomeKit smart home devices (Philips Hue, IKEA TRADFRI, etc.), allowing them to integrate into the Apple Home ecosystem. This image includes FFmpeg with libfdk-aac for camera streaming support.

## Architecture

- `tailscale`: Runs the Tailscale client, obtains a Tailnet IP, serves HTTPS on :443 proxying to Homebridge UI on internal port 8581.
- `homebridge`: Runs in the **same network namespace** as the tailscale container. Configuration and plugins persist in a dedicated volume.

Access the UI at: `https://homebridge.your-tailnet.ts.net` (after auth and healthy state).

## Prerequisites

- Docker & Docker Compose v2.20+
- Tailscale account + ability to create auth keys
- Host user in `docker` group (or use sudo)
- Pre-create volume directories (recommended):
```bash
mkdir -p homebridge-data/config ts-homebridge-state
```

## First Run & Setup

On first boot, Homebridge generates a unique setup code in the logs. Find it with:
```bash
docker compose logs homebridge | grep -i "setup code"
```

You'll use this code if setting up HomeKit bridges (only if HomeKit discovery were enabled). For web UI access, no setup code is needed.

## Configuration

### Important Notes

- **ENABLE_AVAHI=0**: Avahi (mDNS daemon) is disabled since the sidecar network prevents proper mDNS broadcasts. HomeKit discovery is not functional in this setup.
- **Tailscale Serve**: The sidecar automatically proxies port 443 to the Homebridge UI on 8581. HTTPS certificates are auto-provisioned by Tailscale.
- **Plugins**: Install via the Homebridge UI. They persist in the mounted volume.

### Optional Environment Variables

Add to `compose.yml` under the `homebridge` service `environment:` block if needed:
- `HB_PORT=8581` — Web UI port (change if desired, but update compose.tailscale.yml Proxy port to match)

## Upstream Documentation

- [Homebridge GitHub](https://github.com/homebridge/homebridge)
- [Homebridge Docker Guide](https://github.com/homebridge/homebridge/wiki/Install-Homebridge-on-Docker)
- [Homebridge Plugins](https://www.npmjs.com/search?q=homebridge-plugin)
- [Homebridge UI](https://github.com/homebridge/homebridge-config-ui-x)
- [Tailscale Docker Guide](https://tailscale.com/blog/docker-tailscale-guide)

## Troubleshooting

- **Container unhealthy**: Check `docker compose logs tailscale` and `docker compose logs homebridge`
- **Can't reach UI over HTTPS**: Verify Tailscale connection with `docker compose exec tailscale tailscale status`. If not logged in, check TS_AUTHKEY.
- **UI timeout or 502**: Ensure Homebridge is running with `docker compose logs homebridge`. Check that internal port 8581 is correct.
- **HomeKit discovery not working**: This is expected. The sidecar breaks mDNS. Run with `network_mode: host` for full HomeKit support.
71 changes: 71 additions & 0 deletions services/homebridge/compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# =============================================================================
# Homebridge + Tailscale Sidecar Compose
# WARNING: This config breaks HomeKit discovery. See README for details.
# Always validate with: docker compose config
# =============================================================================
configs:
ts-serve:
content: |
{"TCP":{"443":{"HTTPS":true}},
"Web":{"$${TS_CERT_DOMAIN}:443":
{"Handlers":{"/":
{"Proxy":"http://127.0.0.1:8581"}}}},
"AllowFunnel":{"$${TS_CERT_DOMAIN}:443":false}}

services:
tailscale:
image: tailscale/tailscale:latest
container_name: ts-${SERVICE}
hostname: ${SERVICE}
environment:
- TS_AUTHKEY=${TS_AUTHKEY}
- TS_STATE_DIR=/var/lib/tailscale
- TS_SERVE_CONFIG=/config/serve.json # Tailscale Serve configuration to expose the web interface on your local Tailnet - remove this line if not required
#- TS_USERSPACE=false
- TS_ENABLE_HEALTH_CHECK=true # Enable healthcheck endpoint: "/healthz"
- TS_LOCAL_ADDR_PORT=127.0.0.1:41234 # The <addr>:<port> for the healthz endpoint
#- TS_ACCEPT_DNS=true # Uncomment when using MagicDNS
- TS_AUTH_ONCE=true
configs:
- source: ts-serve
target: /config/serve.json
volumes:
- ./config:/config # Config folder used to store Tailscale files - you may need to change the path
- ./ts/state:/var/lib/tailscale # Tailscale requirement - you may need to change the path
devices:
- /dev/net/tun:/dev/net/tun # Network configuration for Tailscale to work
cap_add:
- net_admin
- sys_module
restart: always
ports:
# - "5353:5353" #Receive mDNS but does not do anything for Apple HomeBridge
# - "443:443" #Uncomment for local acces
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1:41234/healthz"] # Check Tailscale has a Tailnet IP and is operational
interval: 1m # How often to perform the check
timeout: 10s # Time to wait for the check to succeed
retries: 3 # Number of retries before marking as unhealthy
start_period: 10s # Time to wait before starting health checks

homebridge:
image: ${IMAGE_URL}
network_mode: service:tailscale
container_name: app-${SERVICE}
environment:
- PUID=${PUID}
- PGID=${PGID}
- TZ=${TZ}
- ENABLE_AVAHI=0
volumes:
- ./${SERVICE}-data/config:/homebridge
depends_on:
tailscale:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://127.0.0.1:8581"]
interval: 60s
timeout: 10s
retries: 3
start_period: 30s
restart: always
55 changes: 55 additions & 0 deletions services/socfortress-waf/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# =============================================================================
# WAF Management Platform + Tailscale sidecar — Environment
# Copy to .env and fill every CHANGE_ME value. Never commit .env.
# =============================================================================

# --- Tailscale sidecar (admin UI over your Tailnet) ----------------------
# Reusable or ephemeral auth key: https://login.tailscale.com/admin/settings/keys
TS_AUTHKEY=tskey-auth-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# MagicDNS hostname -> admin UI lands at https://<TS_HOSTNAME>.<your-tailnet>.ts.net
TS_HOSTNAME=socfortressWAF
# Optional, e.g. if your tailnet uses ACL tags: --advertise-tags=tag:container
# TS_EXTRA_ARGS=

# --- Image version -------------------------------------------------------
# Pin a release tag (e.g. v1.0.0) for reproducible deploys; "latest" tracks newest.
WAF_IMAGE_TAG=latest

# --- GeoIP (MaxMind GeoLite2) --------------------------------------------
# Host path to your own GeoLite2-City.mmdb (see README -> GeoIP setup).
GEOIP_DB_PATH=./GeoLite2-City.mmdb

# --- PostgreSQL ----------------------------------------------------------
POSTGRES_DB=wafdb
POSTGRES_USER=wafuser
POSTGRES_PASSWORD=CHANGE_ME
POSTGRES_HOST=postgres
POSTGRES_PORT=5432

# --- Redis ---------------------------------------------------------------
REDIS_URL=redis://redis:6379/0

# --- API security --------------------------------------------------------
# python3 -c "import secrets; print(secrets.token_hex(32))"
SECRET_KEY=CHANGE_ME_at_least_32_random_characters
# python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
TOTP_ENCRYPTION_KEY=CHANGE_ME_generate_with_command_above

# CORS — MUST list the Tailnet URL you log in from (no wildcard allowed).
# Replace <your-tailnet> with your tailnet name (see README -> Gotchas).
ALLOWED_ORIGINS=https://waf-admin.<your-tailnet>.ts.net

# --- Caddy Admin API (container-internal) --------------------------------
CADDY_ADMIN_URL=http://caddy-waf:2019

# --- SMTP (optional, for alert notifications) ----------------------------
SMTP_HOST=
SMTP_PORT=587
SMTP_USER=
SMTP_PASSWORD=
SMTP_FROM=

# --- Bootstrap superadmin (seeded on first run only) ---------------------
# Change the password immediately after first login.
BOOTSTRAP_ADMIN_EMAIL=admin@example.com
BOOTSTRAP_ADMIN_PASSWORD=CHANGE_ME
83 changes: 83 additions & 0 deletions services/socfortress-waf/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# SOCFortress WAF with Tailscale (admin UI on your Tailnet)

This adds a Tailscale sidecar to the [SOCFortress WAF Management Platform](https://github.com/socfortress/waf-platform-public) so the **admin UI is reachable only over your Tailnet** at `https://<host>.<your-tailnet>.ts.net`, with a valid HTTPS certificate. The **WAF data plane is left exactly as upstream ships it** — `caddy-waf` still listens on host ports **80/443** so it can front your protected sites with automatic per-site Let's Encrypt certs and see real client IPs.

Funnel (public internet exposure) is **intentionally disabled**.

## About this setup

The WAF platform is a six-service stack (Caddy+Coraza engine, FastAPI admin API, React/Nginx admin UI, PostgreSQL, Redis, and a demo upstream). Only one piece — the **admin console** — benefits from being private-by-default, and that is exactly what a WAF operator wants: keep the control panel off the public internet, reach it securely from anywhere over Tailscale. The Tailscale container joins the stack's `waf-internal` network as a normal peer and reverse-proxies to `admin-ui:8080` by its Docker DNS name. Nothing about how the WAF protects traffic changes.

## Architecture

- `tailscale` (from `compose.tailscale.yml`): joins your Tailnet, terminates HTTPS on `:443` using the node's `*.ts.net` certificate, and proxies to the admin UI. **Tailnet-only** (`AllowFunnel: false`).
- `admin-ui`: serves the console on internal `:8080` over HTTPS with a self-signed cert. The sidecar reaches it via `https+insecure://admin-ui:8080` (the public-facing `*.ts.net` cert is valid, so your browser sees no warning).
- `caddy-waf` and the rest of the stack: unchanged. Data plane stays on host `80/443`.

Access the admin UI at: `https://<TS_HOSTNAME>.<your-tailnet>.ts.net` (after first auth + HTTPS enabled in your tailnet).

## Quick-ish Start

1. Drop these four files into the cloned `waf-platform` directory (next to the upstream `docker-compose.yml`):
- `.env`, `compose.yml`, `compose.tailscale.yml`, `README.md`
2. Copy and edit the env file:
```bash
cp .env .env # already named .env here; edit it in place
```
Fill every `CHANGE_ME`, set a real `TS_AUTHKEY`, and set `ALLOWED_ORIGINS` to your Tailnet URL (see [Gotchas](#important-notes--gotchas)). Generate secrets with:
```bash
openssl rand -hex 32 # POSTGRES_PASSWORD
python3 -c "import secrets; print(secrets.token_hex(32))" # SECRET_KEY
python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" # TOTP_ENCRYPTION_KEY
```
3. (Recommended) Make the admin UI **Tailnet-only** by removing its host port. In the upstream `docker-compose.yml`, comment out the two `ports:` lines under the `admin-ui` service:
```yaml
admin-ui:
...
# ports:
# - "8443:8080"
```
Leave them in if you also want LAN access on `https://<host>:8443`.
4. Validate (see [Linting](#linting--validation)), then start everything:
```bash
docker compose up -d
docker compose ps
docker compose logs -f tailscale # watch for "Logged in as..." and serve config applied
```
5. Find your node's URL (`tailscale status` inside the container, or the Tailscale admin console). It will be `https://<TS_HOSTNAME>.<your-tailnet>.ts.net`.
6. Make sure `ALLOWED_ORIGINS` in `.env` matches that exact URL, then apply it:
```bash
docker compose up -d admin-api
```
7. From any device on your Tailnet, open `https://<TS_HOSTNAME>.<your-tailnet>.ts.net`, log in with the bootstrap admin, change the password, and enroll TOTP.

## Important Notes & Gotchas

- **CORS is the #1 thing people miss.** The admin API rejects origins not in `ALLOWED_ORIGINS` (no wildcard). When you move login to the Tailnet URL, you **must** set `ALLOWED_ORIGINS=https://<TS_HOSTNAME>.<your-tailnet>.ts.net` and recreate `admin-api`. Symptoms if you forget: the page loads but login/API calls fail.
- **Enable HTTPS in your tailnet** (Admin console → DNS → MagicDNS + HTTPS Certificates). Without it, Tailscale Serve cannot provision the `*.ts.net` certificate and the serve config is skipped. Public DNS for a fresh tailnet name can take up to ~10 minutes.
- **Self-signed backend is expected.** The admin UI's internal cert is self-signed, so the sidecar uses `https+insecure://admin-ui:8080`. Your browser still sees a valid Let's Encrypt cert because Tailscale terminates TLS at the edge with the `*.ts.net` cert.
- **The data plane is still public.** `caddy-waf` keeps listening on host `80/443`. Point DNS for the sites you protect at this host as you normally would; Caddy's automatic per-site TLS and GeoIP/client-IP features are unaffected. Tailscale is only in front of the admin UI.
- **Updating:** `docker compose pull && docker compose up -d`. Data lives in named volumes and survives updates. The Tailscale node identity persists in the `tailscale-state` volume.

## Files

- `.env` — all WAF variables + the Tailscale section (`TS_AUTHKEY` is critical; keep private).
- `compose.yml` — orchestrator; `include`s the upstream stack and the sidecar. Auto-selected by `docker compose`.
- `compose.tailscale.yml` — the Tailscale sidecar service + inline serve config.
- `docker-compose.yml` — upstream, unchanged (provided by the cloned repo; not in this bundle).

## Upstream Documentation

- [SOCFortress WAF repo](https://github.com/socfortress/waf-platform-public)
- [Tailscale Serve](https://tailscale.com/kb/1242/tailscale-serve) · [Tailscale + Docker guide](https://tailscale.com/blog/docker-tailscale-guide)
- [Enabling HTTPS in your tailnet](https://tailscale.com/kb/1153/enabling-https)

## Troubleshooting

- **`serve proxy: no serve config ... skipping`** — HTTPS isn't enabled in your tailnet yet, or the config file path is wrong. Enable HTTPS, then `docker compose up -d tailscale`. If it persists, switch from the inline `configs:` block to a bind-mounted file: create `./config/serve.json` with the same JSON (use literal `${TS_CERT_DOMAIN}`), and mount `./config:/config` on the `tailscale` service.
- **Browser cert warning / no page** — the `*.ts.net` cert may still be provisioning (wait a few minutes), or HTTPS isn't enabled in the tailnet.
- **502 / connection refused at the `.ts.net` URL** — `admin-ui` not healthy yet (`docker compose ps`), or its internal port isn't 8080; check `docker compose logs admin-ui`.
- **Login/API fails after the page loads** — `ALLOWED_ORIGINS` doesn't match the Tailnet URL. Fix `.env` and `docker compose up -d admin-api`.
- **No Tailnet IP** — auth key expired/invalid, or ACL/tag restrictions; check `docker compose logs tailscale`.

---
Loading