alexei.dolgolyov ef62a41fc0
Build / build (push) Successful in 11m3s
test(static-plugin): cover pure helpers, build helpers, and state/env paths
Bring the previously-untested internal/workload/plugin/source/static/
package from 0% to 23.6% coverage with three new test files:

helpers_test.go (20 cases) - idShort/containerNameFor/imageTagFor/
siteVolumeKey shape + same-name-workload collision avoidance;
sanitizeError newline collapse, empty-token no-op, 240-byte cap, and
multi-byte UTF-8 validity at the cap; containerRowID determinism;
lockFor map semantics (same lock for same workload, distinct locks
for different workloads, real serialization under contention, safe
concurrent insertion); runtimeStateKeys exactly equals the JSON-tag
key set.

build_test.go (8 cases) - copyDir copies files + subdirs and
preserves modes on Unix; verifyDownloadInsideRoot accepts clean
trees and surfaces ErrNotExist for missing roots; both functions
reject symlinks (skipped cleanly on Windows non-admin where the
SeCreateSymbolicLink privilege is absent); prepareStaticBuild
writes the Dockerfile even for an empty source.

state_integration_test.go (12 cases) - loadState/saveState round-
trip on an in-memory SQLite store, including: unknown extra_json
keys (future writers) survive a save; clearing a typed field drops
the key; malformed extra_json is recovered from rather than
panicked on; concurrent writers exercise the per-workload mutex by
accumulating into state.LastError - the test verified to fail
loudly (15+ lost markers) when the mutex is disabled. buildEnv
returns plain values, decrypts encrypted ones, skips rows that
fail to decrypt without leaking ciphertext, and returns empty on
store failure without panicking.

Review followups from go-reviewer pass applied inline: H1 rewrite
to exercise actual lost-update race (verified against disabled
mutex), H2 workload-ID scoping by t.Name() so the package-global
saveLocks map cannot bleed across tests or -count=N runs, set-
based env-assertions, JSON tag-set equality check, multi-byte
truncation case, valid-JSON-on-recovery assertion, unique-keys
in concurrent map test, double-close cleanup.
2026-05-16 18:30:37 +03:00

Tinyforge

Self-hosted deployment platform with a web dashboard. Deploy Docker containers from registries with zero-downtime blue-green strategy, host static sites and Deno APIs directly from Git repositories, and manage reverse proxy configuration — all from a single binary.

Features

Container Deployments

  • Registry polling and webhook receiver for automatic deployments
  • Blue-green deploys with health checks and automatic rollback
  • Multi-stage projects (dev, staging, prod) with tag pattern matching
  • Real-time deploy logs via SSE streaming

Static Sites

Deploy static sites and Deno-powered APIs directly from Git repositories:

  • Git providers: Gitea/Forgejo, GitHub, and GitLab (public and private repos)
  • Static mode: Serves HTML/CSS/JS via nginx container
  • Deno mode: Full-stack with TypeScript API backend + static frontend — API routes are auto-discovered from /api folder using a naming convention (API_get_users, API_post_items, etc.)
  • Markdown rendering: Optionally converts .md files to styled HTML
  • Branch & folder picker: Select any branch and subfolder as the deployment root
  • Auto-sync: Trigger redeployment on push or tag events, or manually
  • Per-site secrets: Encrypted environment variables injected at runtime

Infrastructure

  • NPM / Traefik integration for automatic reverse proxy and SSL configuration
  • Cloudflare DNS sync for automatic DNS record management
  • Volume management: Create, browse, upload, and download Docker volumes
  • Stale container cleanup: Detect and remove unused containers
  • Image management: List and prune unused Docker images
  • Database backups: Scheduled and manual backups with one-click restore
  • Config export/import: YAML-based seed configuration for reproducible setups

Auth & Security

  • Local auth with bcrypt password hashing
  • OIDC/SSO support for single sign-on
  • Encrypted credential storage (AES-256-GCM)
  • Role-based access: Admin and user roles

Prerequisites

  • Docker with Docker Compose
  • A Docker network for deployed containers (e.g. staging-net)
  • Nginx Proxy Manager (optional, for automatic proxy configuration)
  • Wildcard DNS pointing to your server (for subdomain-based routing)

Quick Start

  1. Create the Docker network (containers will be attached to this):

    docker network create staging-net
    
  2. Create a .env file (see .env.example):

    cp .env.example .env
    # Edit .env and set ENCRYPTION_KEY and ADMIN_PASSWORD
    # Generate a key: openssl rand -hex 32
    
  3. Start Tinyforge:

    docker compose up -d
    
  4. Open the dashboard at http://localhost:8080 and log in with admin / your ADMIN_PASSWORD.

Configuration

Environment Variables

Variable Required Description
ENCRYPTION_KEY Yes AES-256 key for encrypting stored credentials. Use openssl rand -hex 32
ADMIN_PASSWORD Yes (first launch) Password for the default admin user
SEED_FILE No Path to YAML seed config (default: ./tinyforge.yaml)
DATA_DIR No SQLite database directory (default: ./data)
LISTEN_ADDR No HTTP listen address (default: :8080)
NPM_URL No Override NPM API URL (otherwise uses value from settings)
POLLING_INTERVAL No Registry polling interval, Go duration string e.g. 5m (default from settings)

Seed Config

On first launch, Tinyforge imports a YAML seed file to pre-configure registries, projects, and settings. See tinyforge.example.yaml for the full format.

Webhook Integration

After setup, find your webhook URL at Settings > Webhook URL in the dashboard. Configure your CI/CD (Gitea Actions, GitHub Actions) to POST to this URL on image push:

curl -X POST https://your-domain/api/webhook/<secret> \
  -H "Content-Type: application/json" \
  -d '{"image": "registry.example.com/org/app:v1.2.3"}'

OIDC Setup

  1. Go to Settings > Auth in the dashboard
  2. Switch auth mode to OIDC
  3. Enter your provider's Issuer URL, Client ID, and Client Secret
  4. Set the Redirect URL to https://your-domain/api/auth/oidc/callback

Development

# Build frontend
cd web && npm install && npm run build && cd ..

# Run backend (requires ENCRYPTION_KEY and ADMIN_PASSWORD env vars)
go run ./cmd/server

# Or use Make
make build
make dev

Architecture

CI/Registry --> Webhook/Poller --> Deployer --> Docker + NPM
                                      |
Git Repo ----> Static Sites -------> Docker + NPM
                                      |
                                  Event Bus --> SSE --> Web Dashboard
  • Backend: Go 1.24, chi router, SQLite (pure Go), Docker SDK
  • Frontend: SvelteKit 2, Tailwind CSS 4, TypeScript
  • Deployment: Single binary with embedded SPA, multi-stage Dockerfile
S
Description
No description provided
Readme 66 MiB
Languages
Go 56.7%
Svelte 37%
TypeScript 5.1%
CSS 1%