diff --git a/README.md b/README.md index 89df5b6..97465b3 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,7 @@ ScaleTail provides ready-to-run [Docker Compose](https://docs.docker.com/compose | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | | 💰 **Actual Budget** | A self-hosted personal finance and budgeting app focused on privacy and full data ownership. | [Details](services/actual-budget) | | ⚓ **Anchor** | An offline-first, self-hosted note-taking app with sync, attachments, sharing, and optional OIDC authentication. | [Details](services/anchor) | +| 📄 **BentoPDF** | A lightweight, self-hosted web app for viewing and managing PDF documents. | [Details](services/bentopdf) | | ✂️ **ClipCascade** | A self-hosted clipboard manager for syncing and organizing clipboard history. | [Details](services/clipcascade) | | 🗂️ **Copyparty** | A self-hosted file server with accelerated resumable uploads. | [Details](services/copyparty) | | ✅ **Donetick** | A self-hosted task and checklist manager for productivity. | [Details](services/donetick) | @@ -134,6 +135,7 @@ ScaleTail provides ready-to-run [Docker Compose](https://docs.docker.com/compose | 📝 **Excalidraw** | A virtual collaborative whiteboard tool. | [Details](services/excalidraw) | | 📝 **Flatnotes** | A simple, self-hosted note-taking app using Markdown files. | [Details](services/flatnotes) | | 👨🏼‍💻 **Forgejo** | A community-driven, self-hosted Git service. | [Details](services/forgejo) | +| 📋 **Formbricks** | A self-hosted, open-source platform for collecting user feedback, surveys, and NPS. | [Details](services/formbricks) | | 👨🏼‍💻 **Gitea** | A lightweight, self-hosted Git service with repository hosting, pull requests, and issue tracking. | [Details](services/gitea) | | ✍️ **Ghost** | A modern, open-source publishing platform for blogs and newsletters. | [Details](services/ghost) | | 🧑‍🧑‍🧒‍🧒 **Gramps Web** | A web-based genealogy platform for collaborative family tree browsing, editing, AI-powered chat, media tagging, mapping, charts, search, and reporting. | [Details](services/grampsweb) | @@ -152,8 +154,7 @@ ScaleTail provides ready-to-run [Docker Compose](https://docs.docker.com/compose | 🔄 **Resilio Sync** | A fast, reliable, and simple file sync and share solution. | [Details](services/resilio-sync) | | 📁 **Seafile** | A self-hosted file syncing and collaboration platform with file sharing, versioning, and team library support. | [Details](services/seafile) | | 🗂️ **Stirling-PDF** | A web application for managing and editing PDF files. | [Details](services/stirlingpdf) | -| 📄 **BentoPDF** | A lightweight, self-hosted web app for viewing and managing PDF documents. | [Details](services/bentopdf) | -| 📋 **Formbricks** | A self-hosted, open-source platform for collecting user feedback, surveys, and NPS. | [Details](services/formbricks) | +| 💰 **Sure Finance** | A self-hosted personal finance and budgeting app with optional AI insights. | [Details](services/sure) | | 🏦 **Subtrackr** | A self-hosted web app to track subscriptions, renewal dates, costs, and payment methods. | [Details](services/subtrackr) | | 🗃️ **Vaultwarden** | An unofficial Bitwarden server implementation written in Rust. | [Details](services/vaultwarden) | | ✅ **Vikunja** | A self-hosted task and project management platform with projects, reminders, labels, recurring tasks, and collaboration features. | [Details](services/vikunja) | diff --git a/services/sure/.env b/services/sure/.env new file mode 100755 index 0000000..0cd788e --- /dev/null +++ b/services/sure/.env @@ -0,0 +1,55 @@ +#version=1.1 +#URL=https://github.com/tailscale-dev/ScaleTail +#COMPOSE_PROJECT_NAME= # Optional: only use when running multiple deployments on the same infrastructure. + +# Service Configuration +SERVICE=sure # Service name (e.g., adguard). Used as hostname in Tailscale and for container naming (app-${SERVICE}). +IMAGE_URL=ghcr.io/we-promise/sure:stable # Docker image URL from container registry (e.g., adguard/adguard-home). + +# Network Configuration +SERVICEPORT=3000 # Port to expose to local network. Uncomment the "ports:" section in compose.yaml to enable. +DNS_SERVER=9.9.9.9 # Preferred DNS server for Tailscale. Uncomment the "dns:" section in compose.yaml to enable. + +# Tailscale Configuration +TS_AUTHKEY= # Auth key from https://tailscale.com/admin/authkeys. See: https://tailscale.com/kb/1085/auth-keys#generate-an-auth-key for instructions. + +# Optional Service variables +# PUID=1000 + +#Time Zone setting for containers +TZ=Europe/Amsterdam # See: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones + +# Any Container environment variables are declared below. See https://docs.docker.com/compose/how-tos/environment-variables/ + +# Database (PostgreSQL) +POSTGRES_USER=sure_user +POSTGRES_PASSWORD=sure_password +POSTGRES_DB=sure_production +DB_HOST=172.28.0.10 # Static IP of db on sure_net (web/worker share tailscale namespace where Docker DNS is overridden by MagicDNS) +DB_PORT=5432 +POSTGRES_HOST=db # Used by the backup service (on sure_net, Docker DNS works) + +# Redis +REDIS_URL=redis://172.28.0.11:6379/1 # Static IP of redis on sure_net (web/worker share tailscale namespace where Docker DNS is overridden by MagicDNS) + +# Sure Application +SECRET_KEY_BASE= #openssl rand -hex 64 +SELF_HOSTED=true +RAILS_FORCE_SSL=true +RAILS_ASSUME_SSL=true +PORT=8070 + +# OpenAI (Optional) +OPENAI_ACCESS_TOKEN= # NOTE: enabling OpenAI will incur costs when you use AI-related features in the app (chat, rules) + +# OIDC Authentication (Optional) +OIDC_ISSUER= # OIDC issuer URL for authentication +OIDC_CLIENT_ID= +OIDC_CLIENT_SECRET= +OIDC_REDIRECT_URI=https:///auth/openid_connect/callback + +# Backup Configuration +SCHEDULE=@daily +BACKUP_KEEP_DAYS=7 +BACKUP_KEEP_WEEKS=4 +BACKUP_KEEP_MONTHS=6 diff --git a/services/sure/README.md b/services/sure/README.md new file mode 100644 index 0000000..e271c3e --- /dev/null +++ b/services/sure/README.md @@ -0,0 +1,110 @@ +# Sure with Tailscale Sidecar Configuration + +This Docker Compose configuration sets up [Sure](https://github.com/we-promise/sure) with Tailscale as a sidecar container, enabling secure, private access to your self-hosted personal finance platform over your Tailnet. With this setup, your Sure instance is **not exposed to the public internet** and is only reachable from authorized devices connected via Tailscale. + +## Sure + +[**Sure**](https://sure.am) is a self-hosted personal finance application and a community-maintained fork of the now-archived Maybe Finance project. It is an all-in-one platform for tracking bank accounts, monitoring stocks and crypto holdings, and following your overall net worth — all from a single interface with multi-currency support. + +Sure is built on Ruby on Rails and is designed for individuals and households that want full ownership of their financial data instead of relying on cloud-hosted finance apps. It includes transaction and portfolio tracking alongside optional AI-assisted features, while remaining fully self-hostable with Docker. + +## Key Features + +- 📊 Net worth tracking across all of your accounts +- 💳 Bank, credit card, and cash transaction management +- 📈 Stocks and cryptocurrency portfolio monitoring +- 💱 Multi-currency support for international finances +- 🤖 Optional AI features (chat, rules) via OpenAI integration +- 🔐 Optional OpenID Connect (OIDC) single sign-on +- 🔑 WebAuthn MFA support for passkeys and hardware security keys +- 🛡️ Tailnet-only access when paired with the included Tailscale sidecar + +## Configuration Overview + +Sure is a **multi-container application**. This stack runs the following services on a dedicated `sure_net` Docker bridge network alongside the Tailscale sidecar: + +- **web** – the main Rails web application (`ghcr.io/we-promise/sure:stable`), serving the Sure UI on port `3000`. +- **worker** – a Sidekiq background worker (same image, run with `bundle exec sidekiq`) that processes asynchronous jobs. +- **db** – a PostgreSQL 16 database storing all Sure data. +- **redis** – a Redis instance used as the Sidekiq queue and cache. +- **backup** *(optional, behind the `backup` profile)* – a scheduled PostgreSQL backup container. + +The `tailscale` service authenticates to your Tailnet and provides the private network endpoint. The `web` and `worker` containers share the Tailscale container's network namespace using Docker's `network_mode: service:tailscale` pattern, so they reach the Tailnet directly and publish no ports to the host. + +### Hybrid networking + +This stack uses a hybrid networking approach so that Tailscale and Sure's support containers can coexist: + +- `web` and `worker` use `network_mode: service:tailscale` (Tailnet-only). +- `db`, `redis`, and `backup` run on the `sure_net` bridge network with static IPs. +- The `tailscale` container is attached to `sure_net` so its network namespace can reach the database and Redis. + +Because **MagicDNS is enabled** (`TS_ACCEPT_DNS=true`), Tailscale overrides Docker's embedded DNS inside the `web`/`worker` namespace. This means `web` and `worker` **cannot resolve container names** like `db` or `redis`, so they connect to the database and Redis using their static IPs (`DB_HOST=172.28.0.10`, `REDIS_URL=redis://172.28.0.11:6379/1`) defined in the `.env` file. The `backup` container sits directly on `sure_net`, so it can still use Docker DNS (`POSTGRES_HOST=db`). + +## Before You Start + +Review and edit the `.env` file before launching the stack. Several values must be set correctly for the app to run and to remain secure: + +- **`TS_AUTHKEY`** – required; generate an auth key at . +- **`SECRET_KEY_BASE`** – required and **empty by default**. Generate a unique value with `openssl rand -hex 64` and paste it. +- **`POSTGRES_USER` / `POSTGRES_PASSWORD` / `POSTGRES_DB`** – the defaults (`sure_user` / `sure_password` / `sure_production`) are placeholders and should be changed before deploying for production use. +- **`DB_HOST`, `REDIS_URL`, `POSTGRES_HOST`** – leave as-is unless you change the static IPs or network layout described above. +- **`TZ`** – set to your local time zone. + +### Bind mount directories + +The compose file bind-mounts several host directories. Pre-create them so Docker does not create them as root-owned: + +```bash +mkdir -p config ts/state app-storage postgres-data redis-data backups +``` + +## HTTPS / SSL Settings + +The `.env` ships with `RAILS_FORCE_SSL=true` and `RAILS_ASSUME_SSL=true`. Because Tailscale Serve terminates TLS in front of the app, `RAILS_ASSUME_SSL=true` is what tells Rails the connection is secure even though Serve forwards to it over plain HTTP on port `3000`. If you access Sure directly over HTTP through the Tailnet and encounter redirect issues, set both values back to `false`. + +## Troubleshooting + +If stock price or exchange-rate syncs fail with `Failed to open TCP connection to fc.yahoo.com`, DNS is likely resolving Yahoo Finance to IPv6 first, which the container can't reach. Upstream forces IPv4 DNS (`dns: [8.8.8.8, 1.1.1.1]`) on `web`/`worker`; this stack omits it because their DNS is handled by Tailscale (MagicDNS). For the recommended workarounds, see the note in the [Sure Docker self-hosting guide](https://github.com/we-promise/sure/blob/main/docs/hosting/docker.md). + +## Optional Integrations + +### OpenAI (AI features) + +Sure can use OpenAI for AI-powered features such as chat and rules. Set `OPENAI_ACCESS_TOKEN` in `.env` to enable it. **Enabling OpenAI will incur costs on your OpenAI account**, so set appropriate spend limits before adding it. See the [Sure AI documentation](https://github.com/we-promise/sure/blob/main/docs/hosting/ai.md). + +### OpenID Connect (OIDC) + +Sure supports OIDC for external authentication providers such as Google, GitHub, Keycloak, Authentik, Okta, or Azure AD. Fill in `OIDC_ISSUER`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET`, and `OIDC_REDIRECT_URI` in the `.env` file to enable it. The redirect URI should follow the pattern `https:///auth/openid_connect/callback`. See the [Sure OIDC documentation](https://github.com/we-promise/sure/blob/main/docs/hosting/oidc.md). + +### WebAuthn MFA (passkeys) + +If you enable passkeys, Touch ID, Windows Hello, or hardware security keys as MFA credentials, pin the WebAuthn relying party settings (`WEBAUTHN_RP_ID` and `WEBAUTHN_ALLOWED_ORIGINS`) in your environment before registering any passkeys. See the [WebAuthn configuration guide](https://github.com/we-promise/sure/blob/main/docs/hosting/webauthn.md). + +## Usage Notes + +Start the core stack with: + +```bash +docker compose up -d +``` + +On first launch, open the Sure URL on your Tailnet and click **create your account** to register the initial user. There are no default admin credentials — the first account you create becomes your login. + +To run the optional scheduled database backups, include the backup profile: + +```bash +docker compose --profile backup up -d +``` + +Backups are written to the `./backups` directory by default; adjust the retention settings (`SCHEDULE`, `BACKUP_KEEP_DAYS`, `BACKUP_KEEP_WEEKS`, `BACKUP_KEEP_MONTHS`) in `.env` as needed. + +## References + +- [Sure Website](https://sure.am) +- [Sure GitHub Repository](https://github.com/we-promise/sure) +- [Sure Docker Self-Hosting Guide](https://github.com/we-promise/sure/blob/main/docs/hosting/docker.md) +- [Sure OIDC Documentation](https://github.com/we-promise/sure/blob/main/docs/hosting/oidc.md) +- [Sure AI Documentation](https://github.com/we-promise/sure/blob/main/docs/hosting/ai.md) +- [Sure WebAuthn Documentation](https://github.com/we-promise/sure/blob/main/docs/hosting/webauthn.md) +- [Tailscale Docker Documentation](https://tailscale.com/kb/1282/docker) diff --git a/services/sure/compose.yaml b/services/sure/compose.yaml new file mode 100755 index 0000000..f92e681 --- /dev/null +++ b/services/sure/compose.yaml @@ -0,0 +1,169 @@ +configs: + ts-serve: + content: | + {"TCP":{"443":{"HTTPS":true}}, + "Web":{"$${TS_CERT_DOMAIN}:443": + {"Handlers":{"/": + {"Proxy":"http://127.0.0.1:3000"}}}}, + "AllowFunnel":{"$${TS_CERT_DOMAIN}:443":false}} + +services: +# Make sure you have updated/checked the .env file with the correct variables. +# All the ${ xx } need to be defined there. + # Tailscale Sidecar Configuration + tailscale: + image: tailscale/tailscale:latest # Image to be used + container_name: tailscale-${SERVICE} # Name for local container management + hostname: ${SERVICE} # Name used within your Tailscale environment + 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 : for the healthz endpoint + - TS_ACCEPT_DNS=true # MagicDNS enabled + - 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 # Tailscale requirement + 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 + restart: always + networks: + sure_net: + ipv4_address: 172.28.0.2 + + web: + image: ${IMAGE_URL} # Image to be used + container_name: app-${SERVICE}-web + network_mode: service:tailscale + volumes: + - ./app-storage:/rails/storage + restart: unless-stopped + environment: + - POSTGRES_USER=${POSTGRES_USER:-sure_user} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-sure_password} + - POSTGRES_DB=${POSTGRES_DB:-sure_production} + - SECRET_KEY_BASE=${SECRET_KEY_BASE} + - SELF_HOSTED=${SELF_HOSTED:-true} + - RAILS_FORCE_SSL=${RAILS_FORCE_SSL:-false} + - RAILS_ASSUME_SSL=${RAILS_ASSUME_SSL:-false} + - DB_HOST=${DB_HOST:-172.28.0.10} + - DB_PORT=${DB_PORT:-5432} + - REDIS_URL=${REDIS_URL:-redis://172.28.0.11:6379/1} + - OPENAI_ACCESS_TOKEN=${OPENAI_ACCESS_TOKEN} # NOTE: enabling OpenAI will incur costs when you use AI-related features in the app (chat, rules). Make sure you have set appropriate spend limits on your account before adding this. + - OIDC_ISSUER=${OIDC_ISSUER} # Optional: OIDC issuer URL for authentication (e.g., https://accounts.google.com). If not set, OIDC authentication will be disabled. + - OIDC_CLIENT_ID=${OIDC_CLIENT_ID} # Optional: OIDC client ID for authentication. Required if OIDC_ISSUER is set. + - OIDC_CLIENT_SECRET=${OIDC_CLIENT_SECRET} # Optional: OIDC client secret for authentication. Required if OIDC_ISSUER is set. + - OIDC_REDIRECT_URI=${OIDC_REDIRECT_URI} # Optional: OIDC redirect URI for authentication. Required if OIDC_ISSUER is set. + # BINDING: "::" # Uncomment for IPv6 dual-stack inside the container + depends_on: + tailscale: + condition: service_healthy + db: + condition: service_healthy + redis: + condition: service_healthy + + worker: + image: ${IMAGE_URL} # Image to be used + container_name: app-${SERVICE}-worker + network_mode: service:tailscale + command: bundle exec sidekiq + volumes: + - ./app-storage:/rails/storage + restart: unless-stopped + depends_on: + tailscale: + condition: service_healthy + db: + condition: service_healthy + redis: + condition: service_healthy + environment: + - POSTGRES_USER=${POSTGRES_USER:-sure_user} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-sure_password} + - POSTGRES_DB=${POSTGRES_DB:-sure_production} + - SECRET_KEY_BASE=${SECRET_KEY_BASE} + - SELF_HOSTED=${SELF_HOSTED:-true} + - RAILS_FORCE_SSL=${RAILS_FORCE_SSL:-false} + - RAILS_ASSUME_SSL=${RAILS_ASSUME_SSL:-false} + - DB_HOST=${DB_HOST:-172.28.0.10} + - DB_PORT=${DB_PORT:-5432} + - REDIS_URL=${REDIS_URL:-redis://172.28.0.11:6379/1} + - OPENAI_ACCESS_TOKEN=${OPENAI_ACCESS_TOKEN} # NOTE: enabling OpenAI will incur costs when you use AI-related features in the app (chat, rules). Make sure you have set appropriate spend limits on your account before adding this. + + db: + image: postgres:16 + container_name: app-${SERVICE}-db + restart: unless-stopped + volumes: + - ./postgres-data:/var/lib/postgresql/data + environment: + - POSTGRES_USER=${POSTGRES_USER:-sure_user} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-sure_password} + - POSTGRES_DB=${POSTGRES_DB:-sure_production} + healthcheck: + test: [ "CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB" ] + interval: 5s + timeout: 5s + retries: 5 + networks: + sure_net: + ipv4_address: 172.28.0.10 + + backup: + profiles: + - backup + image: prodrigestivill/postgres-backup-local + container_name: app-${SERVICE}-backup + restart: unless-stopped + volumes: + - ./backups:/backups # Change this path to your desired backup location on the host machine + environment: + - POSTGRES_HOST=${POSTGRES_HOST:-db} + - POSTGRES_DB=${POSTGRES_DB:-sure_production} + - POSTGRES_USER=${POSTGRES_USER:-sure_user} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-sure_password} # pipelock:ignore + - SCHEDULE=${SCHEDULE:-@daily} + - BACKUP_KEEP_DAYS=${BACKUP_KEEP_DAYS:-7} + - BACKUP_KEEP_WEEKS=${BACKUP_KEEP_WEEKS:-4} + - BACKUP_KEEP_MONTHS=${BACKUP_KEEP_MONTHS:-6} + depends_on: + - db + networks: + - sure_net + + redis: + image: redis:latest + container_name: app-${SERVICE}-redis + restart: unless-stopped + volumes: + - ./redis-data:/data + healthcheck: + test: [ "CMD", "redis-cli", "ping" ] + interval: 5s + timeout: 5s + retries: 5 + networks: + sure_net: + ipv4_address: 172.28.0.11 + +networks: + sure_net: + driver: bridge + ipam: + config: + - subnet: 172.28.0.0/16