feat: rename Docker Watcher to Tinyforge
Build / build (push) Successful in 12m20s

Rebrand the project as Tinyforge to reflect its evolution from a Docker
container watcher into a self-hosted mini CI/deployment platform.

Rename covers: Go module path, Docker labels, DB/config filenames,
JWT issuer, Dockerfile binary, docker-compose, CI workflows, frontend
i18n, README with static sites docs, and all code comments.
This commit is contained in:
2026-04-12 21:30:23 +03:00
parent 8d2c5a063b
commit 791cd4d6af
68 changed files with 512 additions and 224 deletions
+3 -3
View File
@@ -10,9 +10,9 @@ import (
"github.com/go-chi/chi/v5"
"github.com/alexei/docker-watcher/internal/auth"
"github.com/alexei/docker-watcher/internal/crypto"
"github.com/alexei/docker-watcher/internal/store"
"github.com/alexei/tinyforge/internal/auth"
"github.com/alexei/tinyforge/internal/crypto"
"github.com/alexei/tinyforge/internal/store"
)
// rateLimitedLogin wraps the login handler with per-IP rate limiting.
+1 -1
View File
@@ -9,7 +9,7 @@ import (
"strings"
"time"
"github.com/alexei/docker-watcher/internal/store"
"github.com/alexei/tinyforge/internal/store"
"github.com/go-chi/chi/v5"
)
+2 -2
View File
@@ -4,7 +4,7 @@ import (
"log/slog"
"net/http"
"github.com/alexei/docker-watcher/internal/config"
"github.com/alexei/tinyforge/internal/config"
)
// exportConfig handles GET /api/config/export — downloads current state as YAML.
@@ -17,7 +17,7 @@ func (s *Server) exportConfig(w http.ResponseWriter, r *http.Request) {
}
w.Header().Set("Content-Type", "application/x-yaml")
w.Header().Set("Content-Disposition", "attachment; filename=docker-watcher.yaml")
w.Header().Set("Content-Disposition", "attachment; filename=tinyforge.yaml")
w.WriteHeader(http.StatusOK)
w.Write(data)
}
+2 -2
View File
@@ -6,8 +6,8 @@ import (
"strconv"
"strings"
"github.com/alexei/docker-watcher/internal/docker"
"github.com/alexei/docker-watcher/internal/store"
"github.com/alexei/tinyforge/internal/docker"
"github.com/alexei/tinyforge/internal/store"
)
// listDeploys handles GET /api/deploys.
+3 -3
View File
@@ -6,9 +6,9 @@ import (
"net/http"
"strings"
"github.com/alexei/docker-watcher/internal/crypto"
"github.com/alexei/docker-watcher/internal/dns"
"github.com/alexei/docker-watcher/internal/store"
"github.com/alexei/tinyforge/internal/crypto"
"github.com/alexei/tinyforge/internal/dns"
"github.com/alexei/tinyforge/internal/store"
"github.com/go-chi/chi/v5"
)
+2 -2
View File
@@ -11,7 +11,7 @@ import (
"github.com/go-chi/chi/v5"
"github.com/alexei/docker-watcher/internal/store"
"github.com/alexei/tinyforge/internal/store"
)
// listProjectImages handles GET /api/projects/{id}/images.
@@ -220,7 +220,7 @@ func (s *Server) unusedImageStats(w http.ResponseWriter, r *http.Request) {
}
// pruneImages handles POST /api/docker/prune-images.
// Only removes images that belong to Docker Watcher projects (not all system images).
// Only removes images that belong to Tinyforge projects (not all system images).
func (s *Server) pruneImages(w http.ResponseWriter, r *http.Request) {
if s.docker == nil {
respondError(w, http.StatusServiceUnavailable, "Docker is not available")
+1 -1
View File
@@ -7,7 +7,7 @@ import (
"github.com/go-chi/chi/v5"
"github.com/alexei/docker-watcher/internal/store"
"github.com/alexei/tinyforge/internal/store"
)
// listEventLog handles GET /api/events/log.
+1 -1
View File
@@ -9,7 +9,7 @@ import (
"github.com/go-chi/chi/v5"
"github.com/alexei/docker-watcher/internal/store"
"github.com/alexei/tinyforge/internal/store"
)
// listInstances handles GET /api/projects/{id}/stages/{stage}/instances.
+2 -2
View File
@@ -7,8 +7,8 @@ import (
"github.com/go-chi/chi/v5"
"github.com/alexei/docker-watcher/internal/events"
"github.com/alexei/docker-watcher/internal/store"
"github.com/alexei/tinyforge/internal/events"
"github.com/alexei/tinyforge/internal/store"
)
// projectRequest is the expected JSON body for creating/updating a project.
+3 -3
View File
@@ -8,9 +8,9 @@ import (
"github.com/go-chi/chi/v5"
"github.com/alexei/docker-watcher/internal/crypto"
"github.com/alexei/docker-watcher/internal/registry"
"github.com/alexei/docker-watcher/internal/store"
"github.com/alexei/tinyforge/internal/crypto"
"github.com/alexei/tinyforge/internal/registry"
"github.com/alexei/tinyforge/internal/store"
)
// registryRequest is the expected JSON body for creating/updating a registry.
+12 -12
View File
@@ -7,18 +7,18 @@ import (
"github.com/go-chi/chi/v5"
"github.com/alexei/docker-watcher/internal/auth"
"github.com/alexei/docker-watcher/internal/backup"
"github.com/alexei/docker-watcher/internal/crypto"
"github.com/alexei/docker-watcher/internal/dns"
"github.com/alexei/docker-watcher/internal/docker"
"github.com/alexei/docker-watcher/internal/events"
"github.com/alexei/docker-watcher/internal/npm"
"github.com/alexei/docker-watcher/internal/proxy"
"github.com/alexei/docker-watcher/internal/stale"
"github.com/alexei/docker-watcher/internal/staticsite"
"github.com/alexei/docker-watcher/internal/store"
"github.com/alexei/docker-watcher/internal/webhook"
"github.com/alexei/tinyforge/internal/auth"
"github.com/alexei/tinyforge/internal/backup"
"github.com/alexei/tinyforge/internal/crypto"
"github.com/alexei/tinyforge/internal/dns"
"github.com/alexei/tinyforge/internal/docker"
"github.com/alexei/tinyforge/internal/events"
"github.com/alexei/tinyforge/internal/npm"
"github.com/alexei/tinyforge/internal/proxy"
"github.com/alexei/tinyforge/internal/stale"
"github.com/alexei/tinyforge/internal/staticsite"
"github.com/alexei/tinyforge/internal/store"
"github.com/alexei/tinyforge/internal/webhook"
)
// DNSProviderChangedFunc is called when DNS settings change so the caller can
+8 -8
View File
@@ -7,14 +7,14 @@ import (
"path/filepath"
"strings"
"github.com/alexei/docker-watcher/internal/crypto"
"github.com/alexei/docker-watcher/internal/dns"
"github.com/alexei/docker-watcher/internal/docker"
"github.com/alexei/docker-watcher/internal/npm"
"github.com/alexei/docker-watcher/internal/proxy"
"github.com/alexei/docker-watcher/internal/store"
"github.com/alexei/docker-watcher/internal/volume"
"github.com/alexei/docker-watcher/internal/webhook"
"github.com/alexei/tinyforge/internal/crypto"
"github.com/alexei/tinyforge/internal/dns"
"github.com/alexei/tinyforge/internal/docker"
"github.com/alexei/tinyforge/internal/npm"
"github.com/alexei/tinyforge/internal/proxy"
"github.com/alexei/tinyforge/internal/store"
"github.com/alexei/tinyforge/internal/volume"
"github.com/alexei/tinyforge/internal/webhook"
)
// settingsRequest is the expected JSON body for updating settings.
+2 -2
View File
@@ -10,8 +10,8 @@ import (
"github.com/go-chi/chi/v5"
"github.com/alexei/docker-watcher/internal/events"
"github.com/alexei/docker-watcher/internal/store"
"github.com/alexei/tinyforge/internal/events"
"github.com/alexei/tinyforge/internal/store"
)
// streamDeployLogs handles GET /api/deploys/{id}/logs.
+2 -2
View File
@@ -7,8 +7,8 @@ import (
"github.com/go-chi/chi/v5"
"github.com/alexei/docker-watcher/internal/crypto"
"github.com/alexei/docker-watcher/internal/store"
"github.com/alexei/tinyforge/internal/crypto"
"github.com/alexei/tinyforge/internal/store"
)
// stageEnvRequest is the expected JSON body for creating/updating a stage env override.
+2 -2
View File
@@ -7,8 +7,8 @@ import (
"github.com/go-chi/chi/v5"
"github.com/alexei/docker-watcher/internal/events"
"github.com/alexei/docker-watcher/internal/store"
"github.com/alexei/tinyforge/internal/events"
"github.com/alexei/tinyforge/internal/store"
)
// stageRequest is the expected JSON body for creating/updating a stage.
+3 -3
View File
@@ -7,9 +7,9 @@ import (
"github.com/go-chi/chi/v5"
"github.com/alexei/docker-watcher/internal/events"
"github.com/alexei/docker-watcher/internal/stale"
"github.com/alexei/docker-watcher/internal/store"
"github.com/alexei/tinyforge/internal/events"
"github.com/alexei/tinyforge/internal/stale"
"github.com/alexei/tinyforge/internal/store"
)
// listStaleContainers handles GET /api/containers/stale.
+2 -2
View File
@@ -8,8 +8,8 @@ import (
"github.com/go-chi/chi/v5"
"github.com/alexei/docker-watcher/internal/crypto"
"github.com/alexei/docker-watcher/internal/store"
"github.com/alexei/tinyforge/internal/crypto"
"github.com/alexei/tinyforge/internal/store"
)
// ── List / Get ─────────────────────────────────────────────────────────
+1 -1
View File
@@ -7,7 +7,7 @@ import (
"github.com/go-chi/chi/v5"
"github.com/alexei/docker-watcher/internal/store"
"github.com/alexei/tinyforge/internal/store"
)
// getInstanceStats handles GET /api/projects/{id}/stages/{stage}/instances/{iid}/stats.
+2 -2
View File
@@ -11,8 +11,8 @@ import (
"github.com/go-chi/chi/v5"
"github.com/alexei/docker-watcher/internal/store"
"github.com/alexei/docker-watcher/internal/volume"
"github.com/alexei/tinyforge/internal/store"
"github.com/alexei/tinyforge/internal/volume"
)
// sanitizeFilename removes characters unsafe for Content-Disposition headers.
+2 -2
View File
@@ -10,8 +10,8 @@ import (
"github.com/go-chi/chi/v5"
"github.com/alexei/docker-watcher/internal/store"
"github.com/alexei/docker-watcher/internal/volume"
"github.com/alexei/tinyforge/internal/store"
"github.com/alexei/tinyforge/internal/volume"
)
// safeNamePattern restricts volume names to alphanumeric, dash, underscore, and dot.
+2 -2
View File
@@ -41,7 +41,7 @@ type LocalAuth struct {
// using HMAC-SHA256.
func NewLocalAuth(encKey [32]byte) *LocalAuth {
mac := hmac.New(sha256.New, encKey[:])
mac.Write([]byte("docker-watcher-jwt-secret"))
mac.Write([]byte("tinyforge-jwt-secret"))
la := &LocalAuth{
jwtSecret: mac.Sum(nil),
blacklist: make(map[string]time.Time),
@@ -110,7 +110,7 @@ func (la *LocalAuth) GenerateToken(claims Claims) (SessionToken, error) {
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(expiresAt),
IssuedAt: jwt.NewNumericDate(time.Now()),
Issuer: "docker-watcher",
Issuer: "tinyforge",
},
UserID: claims.UserID,
Username: claims.Username,
+2 -2
View File
@@ -8,7 +8,7 @@ import (
"sync"
"time"
"github.com/alexei/docker-watcher/internal/store"
"github.com/alexei/tinyforge/internal/store"
)
// Engine manages database backup operations.
@@ -52,7 +52,7 @@ func (e *Engine) CreateBackup(backupType string) (store.Backup, error) {
defer e.mu.Unlock()
timestamp := time.Now().UTC().Format("20060102-150405")
filename := fmt.Sprintf("docker-watcher-%s-%s.db", backupType, timestamp)
filename := fmt.Sprintf("tinyforge-%s-%s.db", backupType, timestamp)
destPath := filepath.Join(e.backupDir, filename)
// VACUUM INTO creates a clean, standalone copy of the database.
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"encoding/json"
"fmt"
"github.com/alexei/docker-watcher/internal/store"
"github.com/alexei/tinyforge/internal/store"
"gopkg.in/yaml.v3"
)
+2 -2
View File
@@ -6,8 +6,8 @@ import (
"log/slog"
"os"
"github.com/alexei/docker-watcher/internal/crypto"
"github.com/alexei/docker-watcher/internal/store"
"github.com/alexei/tinyforge/internal/crypto"
"github.com/alexei/tinyforge/internal/store"
"github.com/google/uuid"
)
+2 -2
View File
@@ -5,8 +5,8 @@ import (
"fmt"
"log/slog"
"github.com/alexei/docker-watcher/internal/docker"
"github.com/alexei/docker-watcher/internal/store"
"github.com/alexei/tinyforge/internal/docker"
"github.com/alexei/tinyforge/internal/store"
"github.com/google/uuid"
)
+9 -9
View File
@@ -9,15 +9,15 @@ import (
"sync"
"sync/atomic"
"github.com/alexei/docker-watcher/internal/crypto"
"github.com/alexei/docker-watcher/internal/dns"
"github.com/alexei/docker-watcher/internal/docker"
"github.com/alexei/docker-watcher/internal/events"
"github.com/alexei/docker-watcher/internal/health"
"github.com/alexei/docker-watcher/internal/notify"
"github.com/alexei/docker-watcher/internal/proxy"
"github.com/alexei/docker-watcher/internal/store"
"github.com/alexei/docker-watcher/internal/volume"
"github.com/alexei/tinyforge/internal/crypto"
"github.com/alexei/tinyforge/internal/dns"
"github.com/alexei/tinyforge/internal/docker"
"github.com/alexei/tinyforge/internal/events"
"github.com/alexei/tinyforge/internal/health"
"github.com/alexei/tinyforge/internal/notify"
"github.com/alexei/tinyforge/internal/proxy"
"github.com/alexei/tinyforge/internal/store"
"github.com/alexei/tinyforge/internal/volume"
"github.com/moby/moby/api/types/mount"
"github.com/google/uuid"
)
+1 -1
View File
@@ -3,7 +3,7 @@ package deployer
import (
"fmt"
"github.com/alexei/docker-watcher/internal/store"
"github.com/alexei/tinyforge/internal/store"
)
// validatePromoteFrom checks that a tag is running in the promote_from stage
+4 -4
View File
@@ -7,11 +7,11 @@ import (
"github.com/moby/moby/client"
)
// Labels applied to all containers managed by docker-watcher.
// Labels applied to all containers managed by Tinyforge.
const (
LabelProject = "docker-watcher.project"
LabelStage = "docker-watcher.stage"
LabelInstanceID = "docker-watcher.instance-id"
LabelProject = "tinyforge.project"
LabelStage = "tinyforge.stage"
LabelInstanceID = "tinyforge.instance-id"
)
// Client wraps the Docker Engine API client.
+8 -8
View File
@@ -36,16 +36,16 @@ type ContainerConfig struct {
NetworkID string
// Labels are additional labels to apply to the container.
// docker-watcher management labels are added automatically via Project, Stage, and InstanceID.
// Tinyforge management labels are added automatically via Project, Stage, and InstanceID.
Labels map[string]string
// Project is the docker-watcher project name (used for labelling).
// Project is the Tinyforge project name (used for labelling).
Project string
// Stage is the docker-watcher stage name (used for labelling).
// Stage is the Tinyforge stage name (used for labelling).
Stage string
// InstanceID is the docker-watcher instance ID (used for labelling).
// InstanceID is the Tinyforge instance ID (used for labelling).
InstanceID string
// Mounts is a list of bind mounts to attach to the container.
@@ -88,7 +88,7 @@ func (c *Client) CreateContainer(ctx context.Context, cfg ContainerConfig) (stri
}
}
// Merge docker-watcher labels with any additional labels.
// Merge Tinyforge labels with any additional labels.
labels := make(map[string]string)
for k, v := range cfg.Labels {
labels[k] = v
@@ -198,7 +198,7 @@ func (c *Client) RestartContainer(ctx context.Context, containerID string, timeo
return nil
}
// ManagedContainer holds summary information about a container managed by docker-watcher.
// ManagedContainer holds summary information about a container managed by Tinyforge.
type ManagedContainer struct {
ID string
Name string
@@ -212,12 +212,12 @@ type ManagedContainer struct {
}
// ListContainers returns all containers matching the given label filters.
// Pass nil or an empty map to list all docker-watcher managed containers.
// Pass nil or an empty map to list all Tinyforge managed containers.
// Label filters are key=value pairs applied as Docker label filters.
func (c *Client) ListContainers(ctx context.Context, labelFilters map[string]string) ([]ManagedContainer, error) {
filterArgs := make(client.Filters)
// Always filter by the docker-watcher project label to only return managed containers.
// Always filter by the Tinyforge project label to only return managed containers.
filterArgs.Add("label", LabelProject)
for k, v := range labelFilters {
+1 -1
View File
@@ -32,7 +32,7 @@ func (c *Client) EnsureNetwork(ctx context.Context, networkName string) (string,
resp, err := c.api.NetworkCreate(ctx, networkName, client.NetworkCreateOptions{
Driver: "bridge",
Labels: map[string]string{
LabelProject: "docker-watcher",
LabelProject: "tinyforge",
},
})
if err != nil {
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"fmt"
"strconv"
"github.com/alexei/docker-watcher/internal/npm"
"github.com/alexei/tinyforge/internal/npm"
)
// NpmProvider wraps the NPM client behind the Provider interface.
+2 -2
View File
@@ -7,8 +7,8 @@ import (
"sync"
"time"
"github.com/alexei/docker-watcher/internal/crypto"
"github.com/alexei/docker-watcher/internal/store"
"github.com/alexei/tinyforge/internal/crypto"
"github.com/alexei/tinyforge/internal/store"
"github.com/robfig/cron/v3"
)
+3 -3
View File
@@ -8,9 +8,9 @@ import (
"sync"
"time"
"github.com/alexei/docker-watcher/internal/docker"
"github.com/alexei/docker-watcher/internal/events"
"github.com/alexei/docker-watcher/internal/store"
"github.com/alexei/tinyforge/internal/docker"
"github.com/alexei/tinyforge/internal/events"
"github.com/alexei/tinyforge/internal/store"
"github.com/robfig/cron/v3"
)
+1 -1
View File
@@ -126,7 +126,7 @@ func parseAPIFunctionName(funcName, baseRoute, importPath string) (RouteEntry, b
}
// routerTemplate is the Deno router entrypoint template.
var routerTemplate = template.Must(template.New("router").Parse(`// Auto-generated by Docker Watcher — do not edit manually.
var routerTemplate = template.Must(template.New("router").Parse(`// Auto-generated by Tinyforge — do not edit manually.
import { serveDir } from "https://deno.land/std/http/file_server.ts";
{{- range .Imports}}
+2 -2
View File
@@ -7,8 +7,8 @@ import (
"sync"
"time"
"github.com/alexei/docker-watcher/internal/docker"
"github.com/alexei/docker-watcher/internal/store"
"github.com/alexei/tinyforge/internal/docker"
"github.com/alexei/tinyforge/internal/store"
"github.com/robfig/cron/v3"
)
+10 -10
View File
@@ -10,12 +10,12 @@ import (
"strconv"
"time"
"github.com/alexei/docker-watcher/internal/crypto"
"github.com/alexei/docker-watcher/internal/docker"
"github.com/alexei/docker-watcher/internal/events"
"github.com/alexei/docker-watcher/internal/proxy"
"github.com/alexei/docker-watcher/internal/staticsite/deno"
"github.com/alexei/docker-watcher/internal/store"
"github.com/alexei/tinyforge/internal/crypto"
"github.com/alexei/tinyforge/internal/docker"
"github.com/alexei/tinyforge/internal/events"
"github.com/alexei/tinyforge/internal/proxy"
"github.com/alexei/tinyforge/internal/staticsite/deno"
"github.com/alexei/tinyforge/internal/store"
)
// Manager orchestrates the static site deployment pipeline.
@@ -207,8 +207,8 @@ func (m *Manager) Deploy(ctx context.Context, siteID string, force bool) error {
NetworkName: networkName,
NetworkID: networkID,
Labels: map[string]string{
"docker-watcher.static-site": site.ID,
"docker-watcher.static-site-name": site.Name,
"tinyforge.static-site": site.ID,
"tinyforge.static-site-name": site.Name,
},
Project: "static-site",
Stage: site.Name,
@@ -230,8 +230,8 @@ func (m *Manager) Deploy(ctx context.Context, siteID string, force bool) error {
NetworkName: networkName,
NetworkID: networkID,
Labels: map[string]string{
"docker-watcher.static-site": site.ID,
"docker-watcher.static-site-name": site.Name,
"tinyforge.static-site": site.ID,
"tinyforge.static-site-name": site.Name,
},
Project: "static-site",
Stage: site.Name,
+2 -2
View File
@@ -110,7 +110,7 @@ func (s *Store) runMigrations() error {
`ALTER TABLE settings ADD COLUMN traefik_network TEXT NOT NULL DEFAULT ''`,
`ALTER TABLE settings ADD COLUMN traefik_api_url TEXT NOT NULL DEFAULT ''`,
// Set default network for existing databases with empty network.
`UPDATE settings SET network = 'docker-watcher' WHERE network = ''`,
`UPDATE settings SET network = 'tinyforge' WHERE network = ''`,
// NPM remote mode: forward to server_ip instead of container name.
`ALTER TABLE settings ADD COLUMN npm_remote INTEGER NOT NULL DEFAULT 0`,
// Resource limits per stage.
@@ -219,7 +219,7 @@ CREATE TABLE IF NOT EXISTS settings (
domain TEXT NOT NULL DEFAULT '',
server_ip TEXT NOT NULL DEFAULT '',
public_ip TEXT NOT NULL DEFAULT '',
network TEXT NOT NULL DEFAULT 'docker-watcher',
network TEXT NOT NULL DEFAULT 'tinyforge',
subdomain_pattern TEXT NOT NULL DEFAULT 'stage-{stage}-{project}',
notification_url TEXT NOT NULL DEFAULT '',
npm_url TEXT NOT NULL DEFAULT '',
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"path/filepath"
"strings"
"github.com/alexei/docker-watcher/internal/store"
"github.com/alexei/tinyforge/internal/store"
)
// ResolveParams holds the parameters needed to resolve a volume's host path.
+2 -2
View File
@@ -6,8 +6,8 @@ import (
"log/slog"
"strings"
"github.com/alexei/docker-watcher/internal/docker"
"github.com/alexei/docker-watcher/internal/store"
"github.com/alexei/tinyforge/internal/docker"
"github.com/alexei/tinyforge/internal/store"
)
// AutoCreateProject creates a new project and a default "dev" stage from an
+2 -2
View File
@@ -12,8 +12,8 @@ import (
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
"github.com/alexei/docker-watcher/internal/docker"
"github.com/alexei/docker-watcher/internal/store"
"github.com/alexei/tinyforge/internal/docker"
"github.com/alexei/tinyforge/internal/store"
)
// DeployTriggerer is called when a webhook determines a deploy should happen.
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"fmt"
"path"
"github.com/alexei/docker-watcher/internal/store"
"github.com/alexei/tinyforge/internal/store"
)
// FindProjectAndStage searches for a project whose image matches the parsed