From 5880b9cd585c21f6cc235d70b251a4471b2fdd25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Kr=C3=BCger?= Date: Tue, 25 Aug 2026 16:55:29 +0200 Subject: [PATCH] Add sexy stack (Bluetooth toy control console) Pulls dev.pivoine.art/valknar/sexy:latest, publicly reachable at sexy.pivoine.art (no vpn-only middleware - app has its own shared-secret login gate). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01W8WkFF5ppURBAB918593Eb --- README.md | 1 + sexy/.env.example | 10 ++++++++++ sexy/compose.yml | 43 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 sexy/.env.example create mode 100644 sexy/compose.yml diff --git a/README.md b/README.md index 282fef7..07753fd 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ Each stack is independently deployable with its own `compose.yml` and `.env`. Al | `headscale` | Self-hosted Tailscale control server (WireGuard mesh VPN) | headscale | | `obsidian` | CouchDB sync backend for the Obsidian LiveSync plugin | obsidian | | `pulsenode` | Infrastructure dashboard - monitors all the stacks above | pulsenode | +| `sexy` | Bluetooth toy control console (Buttplug protocol) | sexy | ## Config files diff --git a/sexy/.env.example b/sexy/.env.example new file mode 100644 index 0000000..60f467e --- /dev/null +++ b/sexy/.env.example @@ -0,0 +1,10 @@ +# Network +NETWORK_NAME=falcon_network +TRAEFIK_HOST=sexy.pivoine.art + +# Application secrets +# ACCESS_PASSWORD: shared secret typed into the login form. +# AUTH_SECRET: session-signing key, at least 16 chars, must differ from ACCESS_PASSWORD. +# Generate one with: openssl rand -base64 32 +ACCESS_PASSWORD=change_me +AUTH_SECRET=change_me_too_at_least_16_chars diff --git a/sexy/compose.yml b/sexy/compose.yml new file mode 100644 index 0000000..44ba1ab --- /dev/null +++ b/sexy/compose.yml @@ -0,0 +1,43 @@ +services: + sexy: + image: dev.pivoine.art/valknar/sexy:latest + container_name: sexy + read_only: true + tmpfs: + - /tmp + cap_drop: + - ALL + security_opt: + - no-new-privileges:true + environment: + ACCESS_PASSWORD: ${ACCESS_PASSWORD} + AUTH_SECRET: ${AUTH_SECRET} + DATABASE_PATH: /app/data/app.db + volumes: + - ../.data/sexy/data:/app/data + restart: always + healthcheck: + test: ["CMD", "node", "healthcheck.mjs"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 15s + labels: + - "traefik.enable=true" + - "traefik.http.routers.sexy-web.rule=Host(`${TRAEFIK_HOST}`)" + - "traefik.http.routers.sexy-web.entrypoints=web" + - "traefik.http.routers.sexy-web.middlewares=sexy-redirect-web-secure" + - "traefik.http.middlewares.sexy-redirect-web-secure.redirectscheme.scheme=https" + - "traefik.http.routers.sexy-web-secure.rule=Host(`${TRAEFIK_HOST}`)" + - "traefik.http.routers.sexy-web-secure.entrypoints=web-secure" + - "traefik.http.routers.sexy-web-secure.tls.certresolver=resolver" + - "traefik.http.routers.sexy-web-secure.middlewares=security-headers@file,no-index@file" + - "traefik.http.services.sexy-web-secure.loadbalancer.server.port=3000" + - "traefik.docker.network=${NETWORK_NAME}" + networks: + - compose_network + +networks: + compose_network: + name: ${NETWORK_NAME} + external: true