diff --git a/.github/workflows/generate-registry.yml b/.github/workflows/generate-registry.yml new file mode 100644 index 0000000..99e2e54 --- /dev/null +++ b/.github/workflows/generate-registry.yml @@ -0,0 +1,35 @@ +name: Generate registry + +on: + push: + branches: + - main + paths: + - "services/**" + - "README.md" + - "scripts/generate_registry.py" + workflow_dispatch: + +permissions: + contents: write + +jobs: + generate: + if: github.actor != 'github-actions[bot]' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Generate registry.json + run: python3 scripts/generate_registry.py --ref "${{ github.ref_name }}" + - name: Commit registry.json + run: | + if git diff --quiet; then + echo "No registry changes" + exit 0 + fi + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add registry.json + git commit -m "chore: update registry.json" + git push diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..057419d --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,225 @@ +# AGENTS.md - ScaleTail Development Guide + +This document provides guidelines for agentic coding agents working in the ScaleTail repository. + +## Project Overview + +ScaleTail is a collection of Docker Compose configurations that set up various self-hosted services with Tailscale as a sidecar container. Each service directory contains: +- `compose.yaml` - Docker Compose configuration +- `.env` - Environment variables (no secrets) +- `README.md` - Service documentation + +## Build/Lint/Test Commands + +### Linting (Markdown) + +```bash +# Run markdown linting locally using the config +markdownlint ./services/**/*.md + +# Or with Docker +docker run -v $PWD:/work avto-dev/markdown-lint@v1.5.0 --config .markdownlint.yml ./services/**/*.md +``` + +### Generate Registry + +```bash +# Generate registry.json from services (Python 3 required) +python3 scripts/generate_registry.py + +# With custom repo and ref +python3 scripts/generate_registry.py --repo owner/repo --ref main +``` + +### Docker Compose Validation + +```bash +# Validate a service's compose.yaml +docker compose -f services//compose.yaml config +``` + +### Running Tests + +There are no automated unit tests in this repository. Validation is done via: +1. Markdown linting (CI runs on push/PR) +2. Docker Compose config validation +3. Registry generation (CI runs on push to main) + +## Code Style Guidelines + +### File Organization + +- Service directories go in `services//` +- Each service must have exactly three files: + - `compose.yaml` + - `.env` + - `README.md` +- Use the template in `templates/service-template/` when adding new services + +### Docker Compose Conventions + +1. **Tailscale container naming**: Must be `tailscale-` +2. **Application container naming**: Must be `app-` +3. **Network mode**: Application must use `network_mode: service:tailscale` +4. **Health checks**: Both containers require health checks +5. **depends_on**: Must reference Tailscale with `condition: service_healthy` +6. **Ports**: Keep `ports` section commented unless LAN exposure is explicitly required +7. **Volumes**: Pre-create bind-mount paths to avoid root-owned folders + +### Compose.yaml Structure + +```yaml +configs: + ts-serve: + content: | + {"TCP":{"443":{"HTTPS":true}}, + "Web":{"$${TS_CERT_DOMAIN}:443": + {"Handlers":{"/": + {"Proxy":"http://127.0.0.1:"}}}}, + "AllowFunnel":{"$${TS_CERT_DOMAIN}:443":false}} + +services: + tailscale: + image: tailscale/tailscale:latest + container_name: tailscale-${SERVICE} + hostname: ${SERVICE} + environment: + - TS_AUTHKEY=${TS_AUTHKEY} + - TS_STATE_DIR=/var/lib/tailscale + - TS_SERVE_CONFIG=/config/serve.json # Remove if not using Serve + - TS_USERSPACE=false + - TS_ENABLE_HEALTH_CHECK=true + - TS_LOCAL_ADDR_PORT=127.0.0.1:41234 + volumes: + - ./config:/config + - ./ts/state:/var/lib/tailscale + devices: + - /dev/net/tun:/dev/net/tun + cap_add: + - net_admin + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1:41234/healthz"] + interval: 1m + timeout: 10s + retries: 3 + start_period: 10s + restart: always + + application: + image: ${IMAGE_URL} + network_mode: service:tailscale + container_name: app-${SERVICE} + environment: + - + volumes: + - ./${SERVICE}-data/:/container/path + depends_on: + tailscale: + condition: service_healthy + healthcheck: + test: ["CMD", ""] + interval: 1m + timeout: 10s + retries: 3 + start_period: 30s + restart: always +``` + +### .env File Structure + +```bash +#version=1.1 +#URL=https://github.com/tailscale-dev/ScaleTail + +# Service Configuration +SERVICE= +IMAGE_URL= + +# Network Configuration +SERVICEPORT= +DNS_SERVER=9.9.9.9 + +# Tailscale Configuration +TS_AUTHKEY= + +# Optional Service variables +TAILNET_NAME= +``` + +### README.md Structure + +```markdown +# with Tailscale Sidecar Configuration + +Brief description of the service and why Tailscale helps. + +## + +More detailed description from upstream project. + +## Configuration Overview + +Explain the sidecar setup and how traffic is routed. +``` + +### Markdown Conventions + +- Use ATX-style headers (`#`, `##`, `###`) +- Keep line length reasonable (markdownlint default rules apply) +- No inline HTML unless necessary +- Use fenced code blocks with language identifiers + +### Naming Conventions + +- **Service directories**: lowercase with hyphens (e.g., `qbittorrent`, `home-assistant`) +- **Environment variables**: uppercase with underscores (e.g., `TS_AUTHKEY`, `SERVICEPORT`) +- **Container names**: use `${SERVICE}` variable (e.g., `tailscale-${SERVICE}`) +- **Docker images**: Use `${IMAGE_URL}` variable; prefer `:latest` tag + +### Variable Usage in compose.yaml + +- Use `${VARIABLE}` syntax for environment variables +- Use `$$` prefix for literal dollar signs (e.g., `$${TS_CERT_DOMAIN}`) +- Always define all variables in `.env` file + +### Error Handling + +- Always validate compose files with `docker compose config` +- Ensure health checks are properly configured for both containers +- Use `restart: always` for production reliability +- Pre-create directories for bind mounts to avoid root-owned folders + +### Adding New Services + +1. Copy `templates/service-template/` to `services//` +2. Rename files and update content +3. Update `compose.yaml`: + - Set correct `SERVICEPORT` and `IMAGE_URL` in `.env` + - Update `Proxy` port in `TS_SERVE_CONFIG` to match internal app port + - Remove `TS_SERVE_CONFIG` if not using Tailscale Serve/Funnel + - Add required volumes, devices, or capabilities +4. Fill in README with service description and any gotchas +5. Validate with `docker compose config` +6. Run markdown lint before committing + +### Registry Generation + +The `scripts/generate_registry.py` script: +- Scans `services/` for all `compose.yaml` files +- Requires each service to have a corresponding `.env` file +- Extracts service name from README frontmatter or first heading +- Generates `registry.json` for the ScaleTail catalog + +### Git Workflow + +- Create feature branches for changes +- Run markdown linting before submitting PRs +- Ensure `docker compose config` passes for modified services +- Update `registry.json` by running the generation script (or let CI do it) + +### Important Notes + +- **Never commit real secrets** - use placeholder values in `.env` +- **Keep Tailscale health check** - ensures app starts after Tailscale is ready +- **Document port exposures** - explain why any `ports:` section is uncommented +- **Test locally first** - use `docker compose up -d` to verify before submitting diff --git a/registry.json b/registry.json new file mode 100644 index 0000000..f074282 --- /dev/null +++ b/registry.json @@ -0,0 +1,1283 @@ +{ + "name": "ScaleTail Templates", + "description": "Curated Tailscale sidecar Docker configurations for self-hosted services.", + "version": "1.0.0", + "author": "ScaleTail", + "url": "https://github.com/jackspiering/ScaleTail-testing", + "templates": [ + { + "id": "adguardhome", + "name": "AdGuard Home", + "description": "ScaleTail configuration for AdGuard Home running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/adguardhome/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/adguardhome/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/adguardhome/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "adguardhome-sync", + "name": "AdGuardHome Sync", + "description": "ScaleTail configuration for AdGuardHome Sync running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/adguardhome-sync/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/adguardhome-sync/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/adguardhome-sync/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "audiobookshelf", + "name": "Audiobookshelf", + "description": "ScaleTail configuration for Audiobookshelf running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/audiobookshelf/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/audiobookshelf/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/audiobookshelf/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "bazarr", + "name": "Bazarr", + "description": "ScaleTail configuration for Bazarr running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/bazarr/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/bazarr/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/bazarr/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "bentopdf", + "name": "BentoPDF", + "description": "ScaleTail configuration for BentoPDF running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/bentopdf/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/bentopdf/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/bentopdf/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "beszel-agent", + "name": "Beszel Agent", + "description": "ScaleTail configuration for Beszel Agent running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/beszel/agent/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/beszel/agent/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/beszel/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "beszel-hub", + "name": "Beszel Hub", + "description": "ScaleTail configuration for Beszel Hub running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/beszel/hub/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/beszel/hub/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/beszel/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "booklore", + "name": "BookLore", + "description": "ScaleTail configuration for BookLore running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/booklore/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/booklore/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/booklore/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "caddy", + "name": "Caddy", + "description": "ScaleTail configuration for Caddy running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/caddy/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/caddy/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/caddy/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "changedetection", + "name": "ChangeDetection.io", + "description": "ScaleTail configuration for ChangeDetection.io running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/changedetection/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/changedetection/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/changedetection/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "clipcascade", + "name": "ClipCascade", + "description": "ScaleTail configuration for ClipCascade running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/clipcascade/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/clipcascade/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/clipcascade/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "coder", + "name": "Coder", + "description": "ScaleTail configuration for Coder running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/coder/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/coder/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/coder/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "configarr", + "name": "Configarr", + "description": "ScaleTail configuration for Configarr running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/configarr/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/configarr/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/configarr/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "convertx", + "name": "ConvertX", + "description": "ScaleTail configuration for ConvertX running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/convertx/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/convertx/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/convertx/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "copyparty", + "name": "Copyparty", + "description": "ScaleTail configuration for Copyparty running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/copyparty/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/copyparty/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/copyparty/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "cyberchef", + "name": "CyberChef", + "description": "ScaleTail configuration for CyberChef running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/cyberchef/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/cyberchef/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/cyberchef/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "ddns-updater", + "name": "DDNS Updater", + "description": "ScaleTail configuration for DDNS Updater running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/ddns-updater/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/ddns-updater/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/ddns-updater/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "docmost", + "name": "Docmost", + "description": "ScaleTail configuration for Docmost running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/docmost/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/docmost/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/docmost/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "donetick", + "name": "Donetick", + "description": "ScaleTail configuration for Donetick running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/donetick/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/donetick/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/donetick/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "dozzle", + "name": "Dozzle", + "description": "ScaleTail configuration for Dozzle running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/dozzle/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/dozzle/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/dozzle/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "dumbdo", + "name": "DumbDo", + "description": "ScaleTail configuration for DumbDo running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/dumbdo/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/dumbdo/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/dumbdo/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "eigenfocus", + "name": "Eigenfocus", + "description": "ScaleTail configuration for Eigenfocus running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/eigenfocus/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/eigenfocus/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/eigenfocus/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "excalidraw", + "name": "Excalidraw", + "description": "ScaleTail configuration for Excalidraw running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/excalidraw/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/excalidraw/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/excalidraw/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "flatnotes", + "name": "Flatnotes", + "description": "ScaleTail configuration for Flatnotes running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/flatnotes/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/flatnotes/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/flatnotes/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "forgejo", + "name": "Forgejo", + "description": "ScaleTail configuration for Forgejo running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/forgejo/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/forgejo/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/forgejo/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "formbricks", + "name": "Formbricks", + "description": "ScaleTail configuration for Formbricks running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/formbricks/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/formbricks/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/formbricks/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "fossflow", + "name": "FossFLOW", + "description": "ScaleTail configuration for FossFLOW running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/fossflow/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/fossflow/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/fossflow/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "ghost", + "name": "Ghost", + "description": "ScaleTail configuration for Ghost running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/ghost/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/ghost/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/ghost/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "gitsave", + "name": "GitSave", + "description": "ScaleTail configuration for GitSave running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/gitsave/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/gitsave/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/gitsave/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "glance", + "name": "Glance", + "description": "ScaleTail configuration for Glance running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/glance/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/glance/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/glance/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "gokapi", + "name": "Gokapi", + "description": "ScaleTail configuration for Gokapi running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/gokapi/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/gokapi/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/gokapi/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "gotify", + "name": "Gotify", + "description": "ScaleTail configuration for Gotify running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/gotify/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/gotify/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/gotify/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "grampsweb", + "name": "Gramps Web", + "description": "ScaleTail configuration for Gramps Web running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/grampsweb/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/grampsweb/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/grampsweb/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "haptic", + "name": "Haptic", + "description": "ScaleTail configuration for Haptic running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/haptic/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/haptic/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/haptic/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "hemmelig", + "name": "Hemmelig.app", + "description": "ScaleTail configuration for Hemmelig.app running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/hemmelig/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/hemmelig/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/hemmelig/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "homarr", + "name": "Homarr", + "description": "ScaleTail configuration for Homarr running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/homarr/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/homarr/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/homarr/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "home-assistant", + "name": "Home Assistant", + "description": "ScaleTail configuration for Home Assistant running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/home-assistant/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/home-assistant/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/home-assistant/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "homebox", + "name": "Homebox", + "description": "ScaleTail configuration for Homebox running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/homebox/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/homebox/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/homebox/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "homepage", + "name": "Homepage", + "description": "ScaleTail configuration for Homepage running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/homepage/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/homepage/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/homepage/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "hytale", + "name": "Hytale Server", + "description": "ScaleTail configuration for Hytale Server running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/hytale/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/hytale/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/hytale/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "immich", + "name": "Immich", + "description": "ScaleTail configuration for Immich running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/immich/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/immich/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/immich/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "isley", + "name": "Isley", + "description": "ScaleTail configuration for Isley running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/isley/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/isley/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/isley/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "it-tools", + "name": "IT-Tools", + "description": "ScaleTail configuration for IT-Tools running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/it-tools/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/it-tools/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/it-tools/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "jellyfin", + "name": "Jellyfin", + "description": "ScaleTail configuration for Jellyfin running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/jellyfin/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/jellyfin/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/jellyfin/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "jellyseerr", + "name": "Jellyseerr", + "description": "ScaleTail configuration for Jellyseerr running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/jellyseerr/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/jellyseerr/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/jellyseerr/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "kaneo", + "name": "Kaneo", + "description": "ScaleTail configuration for Kaneo running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/kaneo/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/kaneo/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/kaneo/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "karakeep", + "name": "Karakeep", + "description": "ScaleTail configuration for Karakeep running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/karakeep/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/karakeep/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/karakeep/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "kavita", + "name": "Kavita", + "description": "ScaleTail configuration for Kavita running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/kavita/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/kavita/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/kavita/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "languagetool", + "name": "LanguageTool", + "description": "ScaleTail configuration for LanguageTool running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/languagetool/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/languagetool/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/languagetool/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "linkding", + "name": "Linkding", + "description": "ScaleTail configuration for Linkding running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/linkding/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/linkding/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/linkding/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "lube-logger", + "name": "LubeLogger", + "description": "ScaleTail configuration for LubeLogger running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/lube-logger/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/lube-logger/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/lube-logger/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "mattermost", + "name": "Mattermost", + "description": "ScaleTail configuration for Mattermost running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/mattermost/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/mattermost/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/mattermost/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "mealie", + "name": "Mealie", + "description": "ScaleTail configuration for Mealie running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/mealie/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/mealie/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/mealie/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "metube", + "name": "Metube", + "description": "ScaleTail configuration for Metube running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/metube/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/metube/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/metube/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "miniqr", + "name": "Mini-QR", + "description": "ScaleTail configuration for Mini-QR running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/miniqr/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/miniqr/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/miniqr/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "nanote", + "name": "Nanote", + "description": "ScaleTail configuration for Nanote running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/nanote/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/nanote/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/nanote/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "navidrome", + "name": "Navidrome", + "description": "ScaleTail configuration for Navidrome running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/navidrome/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/navidrome/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/navidrome/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "nessus", + "name": "Nessus", + "description": "ScaleTail configuration for Nessus running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/nessus/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/nessus/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/nessus/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "netbox", + "name": "Netbox", + "description": "ScaleTail configuration for Netbox running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/netbox/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/netbox/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/netbox/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "nextcloud", + "name": "Nextcloud Server", + "description": "ScaleTail configuration for Nextcloud Server running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/nextcloud/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/nextcloud/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/nextcloud/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "nodered", + "name": "Node-RED", + "description": "ScaleTail configuration for Node-RED running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/nodered/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/nodered/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/nodered/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "ntfy", + "name": "ntfy", + "description": "ScaleTail configuration for ntfy running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/ntfy/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/ntfy/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/ntfy/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "overseerr", + "name": "Overseerr", + "description": "ScaleTail configuration for Overseerr running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/overseerr/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/overseerr/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/overseerr/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "picard", + "name": "MusicBrainz Picard", + "description": "ScaleTail configuration for MusicBrainz Picard running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/picard/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/picard/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/picard/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "pihole", + "name": "Pi-hole", + "description": "ScaleTail configuration for Pi-hole running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/pihole/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/pihole/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/pihole/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "pingvin-share", + "name": "Pingvin Share", + "description": "ScaleTail configuration for Pingvin Share running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/pingvin-share/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/pingvin-share/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/pingvin-share/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "plex", + "name": "Plex", + "description": "ScaleTail configuration for Plex running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/plex/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/plex/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/plex/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "pocket-id", + "name": "Pocket ID", + "description": "ScaleTail configuration for Pocket ID running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/pocket-id/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/pocket-id/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/pocket-id/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "portainer", + "name": "Portainer", + "description": "ScaleTail configuration for Portainer running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/portainer/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/portainer/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/portainer/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "portracker", + "name": "Portracker", + "description": "ScaleTail configuration for Portracker running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/portracker/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/portracker/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/portracker/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "posterizarr", + "name": "Posterizarr", + "description": "ScaleTail configuration for Posterizarr running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/posterizarr/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/posterizarr/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/posterizarr/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "prowlarr", + "name": "Prowlarr", + "description": "ScaleTail configuration for Prowlarr running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/prowlarr/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/prowlarr/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/prowlarr/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "qbittorrent", + "name": "qBittorrent", + "description": "ScaleTail configuration for qBittorrent running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/qbittorrent/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/qbittorrent/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/qbittorrent/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "radarr", + "name": "Radarr", + "description": "ScaleTail configuration for Radarr running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/radarr/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/radarr/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/radarr/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "recyclarr", + "name": "Recyclarr", + "description": "ScaleTail configuration for Recyclarr running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/recyclarr/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/recyclarr/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/recyclarr/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "resilio-sync", + "name": "Resilio Sync", + "description": "ScaleTail configuration for Resilio Sync running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/resilio-sync/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/resilio-sync/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/resilio-sync/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "searxng", + "name": "searXNG", + "description": "ScaleTail configuration for searXNG running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/searxng/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/searxng/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/searxng/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "slink", + "name": "Slink", + "description": "ScaleTail configuration for Slink running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/slink/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/slink/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/slink/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "sonarr", + "name": "Sonarr", + "description": "ScaleTail configuration for Sonarr running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/sonarr/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/sonarr/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/sonarr/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "speedtest-tracker", + "name": "Speedtest Tracker", + "description": "ScaleTail configuration for Speedtest Tracker running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/speedtest-tracker/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/speedtest-tracker/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/speedtest-tracker/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "stirlingpdf", + "name": "Stirling-PDF", + "description": "ScaleTail configuration for Stirling-PDF running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/stirlingpdf/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/stirlingpdf/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/stirlingpdf/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "subtrackr", + "name": "Subtrackr", + "description": "ScaleTail configuration for Subtrackr running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/subtrackr/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/subtrackr/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/subtrackr/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "swingmx", + "name": "Swing Music", + "description": "ScaleTail configuration for Swing Music running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/swingmx/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/swingmx/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/swingmx/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "tailscale-exit-node", + "name": "Tailscale Exit Node Configuration", + "description": "ScaleTail configuration for Tailscale Exit Node Configuration running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/tailscale-exit-node/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/tailscale-exit-node/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/tailscale-exit-node/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "tandoor", + "name": "Tandoor Recipes", + "description": "ScaleTail configuration for Tandoor Recipes running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/tandoor/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/tandoor/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/tandoor/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "tautulli", + "name": "Tautulli", + "description": "ScaleTail configuration for Tautulli running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/tautulli/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/tautulli/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/tautulli/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "technitium", + "name": "Technitium DNS server", + "description": "ScaleTail configuration for Technitium DNS server running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/technitium/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/technitium/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/technitium/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "traefik", + "name": "Traefik", + "description": "ScaleTail configuration for Traefik running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/traefik/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/traefik/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/traefik/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "uptime-kuma", + "name": "Uptime Kuma", + "description": "ScaleTail configuration for Uptime Kuma running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/uptime-kuma/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/uptime-kuma/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/uptime-kuma/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "vaultwarden", + "name": "Vaultwarden", + "description": "ScaleTail configuration for Vaultwarden running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/vaultwarden/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/vaultwarden/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/vaultwarden/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + }, + { + "id": "wallos", + "name": "Wallos", + "description": "ScaleTail configuration for Wallos running a Tailscale sidecar.", + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/wallos/compose.yaml", + "env_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/wallos/.env", + "documentation_url": "https://raw.githubusercontent.com/jackspiering/ScaleTail-testing/main/services/wallos/README.md", + "tags": [ + "ScaleTail", + "Tailscale" + ] + } + ] +} diff --git a/scripts/generate_registry.py b/scripts/generate_registry.py new file mode 100755 index 0000000..f50b8f9 --- /dev/null +++ b/scripts/generate_registry.py @@ -0,0 +1,315 @@ +#!/usr/bin/env python3 +"""Generate Arcane registry.json from /services.""" +from __future__ import annotations + +import argparse +import json +import os +import re +import subprocess +from pathlib import Path +from typing import Dict, List, Optional, Tuple + +REPO_ROOT = Path(__file__).resolve().parents[1] +SERVICES_DIR = REPO_ROOT / "services" +REPO_SLUG_RE = re.compile(r"^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$") +REF_RE = re.compile(r"^[A-Za-z0-9._/-]+$") +TAG_MAX_LEN = 32 +NAME_MAX_LEN = 80 +TAG_SANITIZE_RE = re.compile(r"[^A-Za-z0-9 _.-]") +NAME_SANITIZE_RE = re.compile(r"[\x00-\x1f\x7f<>]") + + +def title_from_id(value: str) -> str: + parts = re.split(r"[-_]+", value) + return " ".join(p.capitalize() for p in parts if p) + + +def strip_tailscale_suffix(value: str) -> str: + base = value.strip() + patterns = [ + r"\s+with\s+Tailscale\s+Sidecar\s+Configuration\s*$", + r"\s+with\s+Tailscale\s+Sidecar\s*$", + r"\s+with\s+Tailscale\s+Configuration\s*$", + r"\s+with\s+Tailscale\s*$", + r"\s+Tailscale\s+Sidecar\s+Configuration\s*$", + r"\s+Tailscale\s+Sidecar\s*$", + r"\s+Sidecar\s+Configuration\s*$", + ] + for pattern in patterns: + base = re.sub(pattern, "", base, flags=re.IGNORECASE) + base = base.strip(" -") + if not base: + base = value.strip() + return base + + +def normalize_service_name(value: str) -> str: + base = strip_tailscale_suffix(value) + return base + + +def first_heading(text: str) -> Optional[str]: + for line in text.splitlines(): + line = line.strip() + if line.startswith("# "): + return line[2:].strip() + return None + + +def sanitize_name(value: str) -> str: + cleaned = NAME_SANITIZE_RE.sub("", value) + cleaned = re.sub(r"\s+", " ", cleaned).strip() + if not cleaned: + cleaned = "Service" + return cleaned[:NAME_MAX_LEN] + + +def sanitize_tag(value: str) -> Optional[str]: + cleaned = TAG_SANITIZE_RE.sub("", value) + cleaned = re.sub(r"\s+", " ", cleaned).strip() + if not cleaned: + return None + return cleaned[:TAG_MAX_LEN] + + +def _parse_tag_values(raw: str) -> List[str]: + value = raw.strip() + if value.startswith(("'", '"')) and value.endswith(("'", '"')) and len(value) > 1: + value = value[1:-1].strip() + if value.startswith("[") and value.endswith("]") and len(value) > 1: + value = value[1:-1].strip() + parts = [part.strip() for part in value.split(",")] + tags: List[str] = [] + for part in parts: + if not part: + continue + if part.startswith(("'", '"')) and part.endswith(("'", '"')) and len(part) > 1: + part = part[1:-1].strip() + cleaned = sanitize_tag(part) + if cleaned: + tags.append(cleaned) + return tags + + +def extract_frontmatter_tags(text: str) -> List[str]: + lines = text.splitlines() + idx = 0 + while idx < len(lines) and not lines[idx].strip(): + idx += 1 + if idx >= len(lines) or lines[idx].strip() != "---": + return [] + idx += 1 + tags_value: Optional[str] = None + tag_value: Optional[str] = None + while idx < len(lines) and lines[idx].strip() != "---": + if tags_value is None: + match_tags = re.match( + r"^tags\s*:\s*(.+)\s*$", lines[idx], flags=re.IGNORECASE + ) + if match_tags: + tags_value = match_tags.group(1).strip() + if tag_value is None: + match_tag = re.match(r"^tag\s*:\s*(.+)\s*$", lines[idx], flags=re.IGNORECASE) + if match_tag: + tag_value = match_tag.group(1).strip() + idx += 1 + if tags_value: + return _parse_tag_values(tags_value) + if tag_value: + return _parse_tag_values(tag_value) + return [] + + +def dedupe_tags(tags: List[str]) -> List[str]: + seen: set[str] = set() + result: List[str] = [] + for tag in tags: + key = tag.lower() + if key in seen: + continue + seen.add(key) + result.append(tag) + return result + + +def ensure_scaletail_tag(tags: List[str]) -> List[str]: + mandatory = ["ScaleTail", "Tailscale"] + existing_lower = {tag.lower() for tag in tags} + for tag in mandatory: + if tag.lower() not in existing_lower: + tags.append(tag) + return dedupe_tags(tags) + + +def read_text(path: Path) -> Optional[str]: + if not path.exists(): + return None + return path.read_text(encoding="utf-8") + + +def validate_repo_slug(repo: str) -> str: + if not REPO_SLUG_RE.match(repo): + raise SystemExit(f"Invalid repo slug '{repo}'; expected owner/name") + return repo + + +def validate_ref(ref: str) -> str: + if not ref or not REF_RE.match(ref): + raise SystemExit(f"Invalid ref '{ref}'") + if ref.startswith("/") or ref.endswith("/") or ".." in ref or "//" in ref: + raise SystemExit(f"Invalid ref '{ref}'") + return ref + + +def infer_repo_slug(repo_arg: Optional[str]) -> Optional[str]: + if repo_arg: + return validate_repo_slug(repo_arg) + env_repo = os.environ.get("GITHUB_REPOSITORY") + if env_repo: + return validate_repo_slug(env_repo) + try: + url = ( + subprocess.check_output( + ["git", "remote", "get-url", "origin"], + cwd=REPO_ROOT, + text=True, + ) + .strip() + .rstrip("/") + ) + except Exception: + return None + if url.startswith("git@"): + # git@github.com:owner/repo.git + repo = url.split(":", 1)[-1] + else: + # https://github.com/owner/repo.git + repo = url.split("github.com/", 1)[-1] + if repo.endswith(".git"): + repo = repo[:-4] + return validate_repo_slug(repo) + + +def resolve_output_path(output_arg: str) -> Path: + output_path = Path(output_arg) + if not output_path.is_absolute(): + output_path = REPO_ROOT / output_path + resolved = output_path.resolve() + try: + resolved.relative_to(REPO_ROOT) + except ValueError: + raise SystemExit( + f"Output path '{resolved}' must be inside repository {REPO_ROOT}" + ) + return resolved + + +def build_raw_base(repo: str, ref: str) -> str: + return f"https://raw.githubusercontent.com/{repo}/{ref}" + + +def pick_readme(compose_dir: Path) -> Tuple[Optional[Path], bool]: + local_readme = compose_dir / "README.md" + if local_readme.exists(): + return local_readme, False + parent_readme = compose_dir.parent / "README.md" + if parent_readme.exists(): + return parent_readme, True + return None, False + + +def build_template( + compose_path: Path, + repo: str, + ref: str, +) -> Dict[str, object]: + rel_compose = compose_path.relative_to(REPO_ROOT).as_posix() + service_rel = compose_path.parent.relative_to(SERVICES_DIR).as_posix() + template_id = service_rel.replace("/", "-") + name = sanitize_name(title_from_id(template_id)) + + readme_path, parent_readme = pick_readme(compose_path.parent) + tag_values = ["ScaleTail"] + if readme_path: + readme_text = read_text(readme_path) + if readme_text: + heading = first_heading(readme_text) + if heading and not (parent_readme and "/" in service_rel): + name = sanitize_name(heading) + tags = extract_frontmatter_tags(readme_text) + if tags: + tag_values = tags + tag_values = ensure_scaletail_tag(tag_values) + + name = normalize_service_name(name) + description_name = strip_tailscale_suffix(name) + description = ( + f"ScaleTail configuration for {description_name} running a Tailscale sidecar." + ) + + env_path = compose_path.parent / ".env" + rel_env = env_path.relative_to(REPO_ROOT).as_posix() + + documentation_url = None + if readme_path: + documentation_url = ( + build_raw_base(repo, ref) + "/" + readme_path.relative_to(REPO_ROOT).as_posix() + ) + + template = { + "id": template_id, + "name": name, + "description": description, + "version": "1.0.0", + "author": "ScaleTail", + "compose_url": build_raw_base(repo, ref) + "/" + rel_compose, + "env_url": build_raw_base(repo, ref) + "/" + rel_env, + "documentation_url": documentation_url + or build_raw_base(repo, ref) + "/" + rel_compose, + "tags": tag_values, + } + return template + + +def main() -> int: + parser = argparse.ArgumentParser(description="Generate registry.json") + parser.add_argument("--repo", help="GitHub repo in owner/name format") + parser.add_argument("--ref", default=os.environ.get("GITHUB_REF_NAME", "main")) + parser.add_argument( + "--output", + default=str(REPO_ROOT / "registry.json"), + help="Output path for registry.json", + ) + args = parser.parse_args() + + repo = infer_repo_slug(args.repo) + if not repo: + raise SystemExit("Unable to determine repo slug; pass --repo owner/name") + ref = validate_ref(args.ref) + + templates: List[Dict[str, object]] = [] + for compose_path in sorted(SERVICES_DIR.rglob("compose.yaml")): + env_path = compose_path.parent / ".env" + if not env_path.exists(): + raise SystemExit(f"Missing .env for {compose_path}") + templates.append(build_template(compose_path, repo, args.ref)) + + templates.sort(key=lambda t: str(t["id"])) + + registry = { + "name": "ScaleTail Templates", + "description": "Curated Tailscale sidecar Docker configurations for self-hosted services.", + "version": "1.0.0", + "author": "ScaleTail", + "url": f"https://github.com/{repo}", + "templates": templates, + } + + output_path = resolve_output_path(args.output) + output_path.write_text(json.dumps(registry, indent=2) + "\n", encoding="utf-8") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())