Files
Containr/.env.example
T
Tomáš DvořákandDevin 5eac4a382b feat: activity feed — enriched audit events + global/project timelines (#45)
* feat(branding): white-label — public /branding, admin write, shell theming

app_settings branding.* keys (product name, logo/favicon URLs, accent
color, docs/support links). Public GET /branding so login and shell
theme pre-auth; admin writes via PUT /settings.

useBranding() applies document.title, accent CSS var and favicon;
BrandWordmark replaces hardcoded wordmarks; admin Platform settings
gain a Branding form. MCP containr_branding_get + settings_update.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* feat(invites): team invite links — admin create/revoke, public accept

Admins mint single-use invite links (sha256-hashed token at rest,
optional email binding, configurable TTL). Public endpoints let the
accept page validate the link and register — working even while
public signup is disabled.

- migration + sqlc queries/model for user_invites
- POST /admin/invites (returns one-time token+url), GET list, DELETE revoke
- GET /auth/invites/:token public validation, POST /auth/accept-invite
- CLI containr invites list|create|revoke; MCP invites tools
- /auth/accept-invite page (expired/used/invalid states, bound-email lock)
- Admin page Team invites section: create, copy link, revoke
- OpenAPI + generated types + CLI guide

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* feat(admin): impersonate user — 15-min token, audit-logged

POST /admin/users/:id/impersonate mints a short-lived bearer token
(impersonated_by claim) for support/debugging. Admin accounts cannot
be impersonated; every mint is audit-logged with actor + target.

- CLI: containr admin impersonate <user-id> (works via CONTAINR_TOKEN)
- MCP: containr_admin_impersonate
- Admin UI: per-user Impersonate action → token modal with usage hint
- OpenAPI + generated types

Verified live: token resolves to target, admin target 403, audit row.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* feat(git): GitHub App manifest self-provisioning + app-level webhook

Admins create the instance GitHub App in-browser instead of hand-
setting GITHUB_APP_* env vars:

- POST /admin/git/github-app/manifest builds the manifest + URL;
  the frontend auto-submits it to github.com
- /git/github-app/callback page exchanges the code via
  POST /admin/git/github-app/convert; credentials persist in
  app_settings (precedence over env)
- GET /admin/git/github-app reports configured/source
- POST /api/git/github-app/webhook — single app-level push receiver,
  HMAC-verified, dispatches via extracted dispatchPushToServices
- install-url + installation-token paths resolve settings→env
- CLI: containr admin github-app status|manifest|convert; MCP tools
- Git providers UI shows "Set up GitHub App" for admins when
  unconfigured

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* feat: activity feed — enriched audit events, global + per-project timelines

audit_logs gains severity/category/label columns derived from
resource+action at write time; legacy rows are classified on read and
backfilled at boot in batches.

- GET /activity: global feed scoped to the caller's visible projects
  (admins see all); severity/category/resource/project_id/since
  filters, page+limit
- GET /projects/:id/activity: timeline correlated to the project's
  services, deployments, databases and the project record; access
  mirrors project read rules
- Retention worker prunes rows older than AUDIT_RETENTION_DAYS
  (default 90, 0 disables), running once at boot then daily
- project.create/delete now audited so timelines aren't empty
- CLI `containr activity` with filter flags; MCP tools
  containr_activity_list + containr_project_activity
- Frontend: /activity page (severity chips, icon per category) under
  Operate nav; project workspace gains an Activity view tab
- OpenAPI + generated types + CLI docs

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-09 10:52:15 +02:00

165 lines
6.6 KiB
Bash

# Local development env file. install.sh writes ENVIRONMENT=production for
# self-hosted installs - keep development here only for the checkout workflow.
# Domain Configuration
DOMAIN=localhost
ACME_EMAIL=admin@localhost
ENVIRONMENT=development
# Published host ports (change if they collide with other services)
HTTP_PORT=3000
API_PORT=8082
POSTGRES_PORT=5432
REDIS_PORT=6379
# Image tag to pull from GHCR (e.g. latest, 0.1.1). Use CONTAINR_BUILD=1
# with install.sh to build from source instead.
CONTAINR_VERSION=latest
# Database Configuration
POSTGRES_DB=containr
POSTGRES_USER=containr_user
POSTGRES_PASSWORD=your_secure_postgres_password
DATABASE_URL=postgres://containr_user:your_secure_postgres_password@localhost:5432/containr?sslmode=disable
MAX_CONNECTIONS=25
MAX_IDLE_CONNECTIONS=5
CONN_MAX_LIFETIME=5m
CONN_MAX_IDLE_TIME=5m
AUTO_MIGRATE=true
MIGRATION_LOCK_TIMEOUT=2m
SEED_DATA_ON_START=false
# Dragonfly Configuration (Redis-compatible cache - REDIS_* names kept for compatibility)
REDIS_PASSWORD=your_secure_redis_password
REDIS_URL=redis://:your_secure_redis_password@localhost:6379/0
# DRAGONFLY_VERSION=latest
# DRAGONFLY_THREADS=1
# DRAGONFLY_MAXMEMORY=256mb
# Application Configuration
# In production this must be a strong value with at least 32 characters.
JWT_SECRET=SuperSecretJWTKey123456789!@#$%^&*()
# Encrypts secret env vars at rest. Defaults to JWT_SECRET when unset;
# setting it explicitly survives a JWT_SECRET rotation.
SECRETS_KEY=
# Shared secret for node-agent registration/heartbeat auth.
CONTAINR_AGENT_AUTH_TOKEN=replace_with_strong_agent_secret
# Optional rotation list (comma-separated). When set, this takes precedence.
# CONTAINR_AGENT_AUTH_TOKENS=current_secret,next_secret
CORS_ORIGINS=http://localhost:3000,http://localhost:8080
# Optional build-time API URL overrides. Leave empty for the default
# same-origin setup - the frontend nginx proxies /api to the backend, which
# works from any host or IP without rebuilding the image.
# VITE_API_URL=https://api.example.com
# VITE_AUTH_URL=https://api.example.com/api/auth
AUTH_PORT=3001
# In production this must be true.
COOKIE_SECURE=false
MAX_REQUEST_BODY_BYTES=10485760
# Better Auth (embedded in backend container, exposed via backend /api/auth proxy)
BETTER_AUTH_URL=http://localhost:8082
BETTER_AUTH_SECRET=PLACEHOLDER_BETTER_AUTH_SECRET_CHANGE_ME_32CHARS_MIN
BETTER_AUTH_AUTO_MIGRATE=true
BETTER_AUTH_INTERNAL_TOKEN=PLACEHOLDER_INTERNAL_AUTH_TOKEN
BETTER_AUTH_INTERNAL_URL=http://127.0.0.1:3001/internal/session
BETTER_AUTH_PROXY_URL=http://127.0.0.1:3001
# Extra trusted origins (comma-separated). Same-origin traffic is trusted
# automatically, so this only needs entries for cross-origin callers.
BETTER_AUTH_TRUSTED_ORIGINS=http://localhost:3000,http://localhost:8082
# Platform owner bootstrap — creates/promotes the admin account on startup.
# Keep the real values in a local untracked file, never in committed env files.
ADMIN_EMAIL=[email protected]
ADMIN_PASSWORD=
ADMIN_NAME=Platform Admin
# Optional explicit auth DB settings (recommended when password contains URL special chars)
DB_HOST=postgres
DB_PORT=5432
DB_NAME=containr
DB_USER=containr_user
DB_PASSWORD=your_secure_postgres_password
FRONTEND_URL=http://localhost:3000
BACKEND_URL=http://localhost:8082
# OAuth (user auth) - GitHub/Google sign-in is reserved for the managed
# offering. Self-hosted installs use email/password only; PLACEHOLDER_*
# values are treated as unset and the buttons stay hidden.
GITHUB_CLIENT_ID=PLACEHOLDER_GITHUB_OAUTH_CLIENT_ID
GITHUB_CLIENT_SECRET=PLACEHOLDER_GITHUB_OAUTH_CLIENT_SECRET
GITLAB_CLIENT_ID=PLACEHOLDER_GITLAB_CLIENT_ID
GITLAB_CLIENT_SECRET=PLACEHOLDER_GITLAB_CLIENT_SECRET
GITLAB_OAUTH_AUTHORIZE_URL=https://gitlab.com/oauth/authorize
GITLAB_OAUTH_TOKEN_URL=https://gitlab.com/oauth/token
GITLAB_OAUTH_USERINFO_URL=https://gitlab.com/api/v4/user
BITBUCKET_CLIENT_ID=PLACEHOLDER_BITBUCKET_CLIENT_ID
BITBUCKET_CLIENT_SECRET=PLACEHOLDER_BITBUCKET_CLIENT_SECRET
BITBUCKET_OAUTH_AUTHORIZE_URL=https://bitbucket.org/site/oauth2/authorize
BITBUCKET_OAUTH_TOKEN_URL=https://bitbucket.org/site/oauth2/access_token
BITBUCKET_OAUTH_USERINFO_URL=https://api.bitbucket.org/2.0/user
BITBUCKET_OAUTH_EMAILS_URL=https://api.bitbucket.org/2.0/user/emails
GITEA_CLIENT_ID=PLACEHOLDER_GITEA_CLIENT_ID
GITEA_CLIENT_SECRET=PLACEHOLDER_GITEA_CLIENT_SECRET
GITEA_OAUTH_AUTHORIZE_URL=https://gitea.example.com/login/oauth/authorize
GITEA_OAUTH_TOKEN_URL=https://gitea.example.com/login/oauth/access_token
GITEA_OAUTH_USERINFO_URL=https://gitea.example.com/api/v1/user
# GitHub App (repo sync)
GITHUB_APP_ID=PLACEHOLDER_GITHUB_APP_ID
GITHUB_APP_SLUG=PLACEHOLDER_GITHUB_APP_SLUG
GITHUB_APP_PRIVATE_KEY=PLACEHOLDER_GITHUB_APP_PRIVATE_KEY_PEM_ESCAPED
GITHUB_APP_BASE_URL=https://api.github.com
GITLAB_API_URL=https://gitlab.com/api/v4
GITLAB_BASE_URL=https://gitlab.com
BITBUCKET_API_URL=https://api.bitbucket.org/2.0
BITBUCKET_BASE_URL=https://bitbucket.org
GITEA_BASE_URL=https://gitea.example.com
# Backward compatibility (optional)
CORS_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:8080
# Demo Mode Configuration
# true: Limited features for testing/evaluation
# false: Full features with no limits
DEMO_MODE=false
# Traefik Authentication (Basic Auth for dashboard)
# Generate with: htpasswd -nb username password
TRAEFIK_AUTH=admin:$$apr1$$b8mh8c8v$$KkR8hQZQZQZQZQZQZQZQZ/
# Development convenience: true in local dev, false in production
TRAEFIK_API_INSECURE=true
# Optional: Cloudflare Tunnel (alternative to domain). Can also be set in-app
# under Settings > Platform (in-app value overrides this).
# Get token from: https://dash.cloudflare.com/argotunnel
CLOUDFLARED_TOKEN=your_cloudflare_tunnel_token_here
# Optional: reopen public registration after the owner account exists.
# Manageable in-app under Settings > Platform (in-app value overrides this).
# SIGNUP_ENABLED=false
# Docker socket gid AS SEEN INSIDE containers - lets the backend manage
# containers (deployments, cloudflared sidecar). install.sh detects it with a
# throwaway container. Modern Docker maps the socket to root: use 0.
# Older daemons expose the host docker gid: stat -c %g /var/run/docker.sock
DOCKER_GID=0
# Optional: Custom Docker Registry
# DOCKER_REGISTRY=your-registry.com
# DOCKER_USERNAME=your_username
# DOCKER_PASSWORD=your_password
# Optional: External Services
# SENTRY_DSN=https://your-sentry-dsn
# SLACK_WEBHOOK_URL=https://hooks.slack.com/services/your/webhook/url
# Optional: Debug
# DEBUG=true
# Optional: trust reverse proxy CIDR (default local Docker bridge used by Traefik)
TRUSTED_PROXY_CIDR=172.20.0.0/16
# Optional: audit log retention in days (default 90; 0 disables pruning)
# AUDIT_RETENTION_DAYS=90