diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..b589103 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,38 @@ +# Version control +.git +.gitignore + +# Python build artefacts +__pycache__ +*.py[cod] +*.egg-info +dist/ +.venv/ +.uv/ + +# Node build artefacts +ui/node_modules/ +ui/.next/ +ui/out/ + +# Generated UI assets (rebuilt inside the Docker build) +src/authsome/ui/web/ + +# Tests and CI +tests/ +.github/ +.pre-commit-config.yaml + +# Docs and assets +docs/ +assets/ +*.md +!README.md + +# Editor / tooling +.ruff_cache/ +.mypy_cache/ +.pytest_cache/ +.vscode/ +.idea/ +*.egg diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..eb0e8b5 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,27 @@ +name: Docker build + +on: + push: + branches: + - '**' + pull_request: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build image + uses: docker/build-push-action@v6 + with: + context: . + push: false + tags: authsome:ci + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1cd0010 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,37 @@ +# Stage 1: Build the Next.js static UI +FROM node:24-slim AS ui-builder +WORKDIR /app +COPY ui/ ./ui/ +RUN npm install -g pnpm@10 --silent && \ + pnpm --dir ui install --frozen-lockfile && \ + pnpm --dir ui build + +# Stage 2: Build the Python wheel +FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim AS py-builder +WORKDIR /app +COPY . . +# Inject UI assets built in stage 1 before packaging the wheel +COPY --from=ui-builder /app/ui/out ./ui/out +RUN mkdir -p src/authsome/ui/web && \ + cp -R ui/out/. src/authsome/ui/web/ && \ + uv build --wheel --out-dir /dist + +# Stage 3: Minimal runtime image +FROM python:3.13-slim AS runtime + +RUN groupadd -r authsome && \ + useradd -r -g authsome -d /home/authsome -m -s /sbin/nologin authsome + +COPY --from=py-builder /dist /dist +RUN pip install --no-cache-dir /dist/*.whl && rm -rf /dist + +ENV AUTHSOME_HOME=/data/authsome + +EXPOSE 7998 + +VOLUME ["/data/authsome"] + +USER authsome + +ENTRYPOINT ["authsome", "daemon", "serve"] +CMD ["--host", "0.0.0.0", "--port", "7998"] diff --git a/README.md b/README.md index 5c4d69a..a1e8704 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,17 @@ Requires Python 3.13+. uv tool install authsome ``` +## Self-hosting + +Run a persistent daemon in Docker — no Python required on the host: + +```bash +docker compose up -d +export AUTHSOME_DAEMON_URL=http://localhost:7998 +``` + +See the [self-hosting guide](docs/guides/self-hosting.md) for volume backup, TLS termination, and environment variable reference. + ## Quick Start Add the authsome skill to your agent (claude, codex, cursor, hermes, etc.): diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..08fe4cf --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,28 @@ +services: + authsome: + build: . + image: authsome:latest + restart: unless-stopped + ports: + - "7998:7998" + volumes: + - authsome-data:/data/authsome + environment: + AUTHSOME_HOME: /data/authsome + # Set this to the public URL of this server so OAuth callbacks resolve correctly. + # Example: AUTHSOME_SERVER_BASE_URL: https://auth.example.com + AUTHSOME_SERVER_BASE_URL: "" + # Encryption mode: "local_key" (default) or "keyring" + AUTHSOME_ENCRYPTION_MODE: local_key + AUTHSOME_LOG_LEVEL: info + # Uncomment to use a pre-built image from a registry instead of building locally: + # image: ghcr.io/agentrhq/authsome:latest + + # ── Optional: Caddy reverse proxy for TLS ───────────────────────── + # Uncomment the block below and add a caddy service to terminate TLS. + # labels: + # caddy: auth.example.com + # caddy.reverse_proxy: "{{upstreams 7998}}" + +volumes: + authsome-data: diff --git a/docs/guides/self-hosting.md b/docs/guides/self-hosting.md new file mode 100644 index 0000000..aa5f9bc --- /dev/null +++ b/docs/guides/self-hosting.md @@ -0,0 +1,125 @@ +# Self-hosting Authsome + +Run a persistent Authsome daemon in a container — useful for CI runners, shared agent hosts, or any environment where installing Python tooling is inconvenient. + +## Quick start + +```bash +# Clone the repo (or copy docker-compose.yml) +git clone https://github.com/agentrhq/authsome.git +cd authsome + +# Start the daemon +docker compose up -d + +# Verify it's running +curl http://localhost:7998/health +``` + +The daemon is now available at `http://localhost:7998`. + +Point agents at it by setting: + +```bash +export AUTHSOME_DAEMON_URL=http://localhost:7998 +``` + +## Environment variables + +| Variable | Default | Description | +|---|---|---| +| `AUTHSOME_HOME` | `/data/authsome` | Root directory for credentials, keys, and the database | +| `AUTHSOME_HOST` | `0.0.0.0` | Interface the daemon binds to inside the container | +| `AUTHSOME_PORT` | `7998` | TCP port | +| `AUTHSOME_SERVER_BASE_URL` | _(derived from host:port)_ | Public URL used to build OAuth callback URLs. **Must be set when behind a reverse proxy.** | +| `AUTHSOME_ENCRYPTION_MODE` | `local_key` | `local_key` stores the master key on disk; `keyring` uses the OS keyring (not available in containers) | +| `AUTHSOME_LOG_LEVEL` | `info` | Uvicorn log level (`debug`, `info`, `warning`, `error`) | +| `AUTHSOME_ANALYTICS` | `1` | Set to `0` to disable telemetry | + +## Volume + +All credentials and keys live at `AUTHSOME_HOME` (`/data/authsome` by default), which is declared as a Docker named volume. + +``` +/data/authsome/ + server/ + authsome.db # SQLite database (identities, principals, vaults) + master.key # Vault encryption key — back this up + kv_store/ # Encrypted credential blobs + client/ + logs/ +``` + +> **Keep `master.key` safe.** Without it, stored credentials cannot be decrypted. + +## Upgrading + +```bash +docker compose pull # fetch the latest image +docker compose up -d # restart with zero downtime (data volume is preserved) +``` + +## Backup and restore + +```bash +# Backup +docker run --rm -v authsome-data:/data/authsome -v $(pwd):/backup \ + busybox tar czf /backup/authsome-backup.tar.gz -C /data/authsome . + +# Restore +docker run --rm -v authsome-data:/data/authsome -v $(pwd):/backup \ + busybox tar xzf /backup/authsome-backup.tar.gz -C /data/authsome +``` + +## TLS with Caddy + +Add a Caddy sidecar to the compose file for automatic HTTPS: + +```yaml +services: + authsome: + image: authsome:latest + restart: unless-stopped + expose: + - "7998" + environment: + AUTHSOME_SERVER_BASE_URL: https://auth.example.com + volumes: + - authsome-data:/data/authsome + + caddy: + image: caddy:2-alpine + restart: unless-stopped + ports: + - "80:80" + - "443:443" + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + - caddy-data:/data + depends_on: + - authsome + +volumes: + authsome-data: + caddy-data: +``` + +`Caddyfile`: + +``` +auth.example.com { + reverse_proxy authsome:7998 +} +``` + +## Building the image locally + +```bash +docker build -t authsome:local . +``` + +The build is multi-stage: + +1. **`ui-builder`** — Node 24 + pnpm compiles the Next.js dashboard to static HTML. +2. **`py-builder`** — uv bundles the Python package (including the built UI) into a wheel. +3. **`runtime`** — Slim Python 3.13 image; installs the wheel, runs as a non-root `authsome` user.