Restore a captured volume snapshot onto an image workload's live host-bind
data volumes, then redeploy — the most destructive workload action, built to
the adversarially-reviewed design (C1–C6) with all data-loss guards.
- Engine.Restore (engine-owned): all-or-nothing pre-flight re-resolution from
the workload's CURRENT config (never the tamperable manifest), per-filesystem
disk pre-check, per-workload lock, container quiesce, extract-to-tmp, durable
pre-restore snapshot, write-ahead journal, atomic rename swap, redeploy, and
crash-recovery sweep (RecoverInterruptedRestores) wired before serving.
- internal/keyedmutex: shared per-key lock; deployer now serializes every
deploy entrypoint per workload via DispatchPlugin (+ LockWorkload/RedeployLocked
for the restore re-dispatch, no deadlock).
- Untrusted-archive extractor: zip-slip containment, type allow-list (reg/dir
only), decompression-bomb cap, manifest-index bounds.
- POST /api/workloads/{id}/snapshots/{sid}/restore: admin, X-Confirm-Restore
header (CSRF), per-workload single-flight (409).
- WebUI: Restore button + danger ConfirmDialog + busy state + i18n (en/ru).
Scope: image-source only; scopes absolute/stage/project (driven off the same
supportedScopes constant capture uses).
Plan-reviewed before coding; per-phase go/security/ts reviews; final review
READY TO MERGE. Security review caught + fixed a CRITICAL manifest-Source path
traversal (re-derive target from current config + base containment).
Plan: plans/volume-snapshot-restore/
A dockerfile or static workload can opt in to reading its deploy config from a
.tinyforge.yml in its own repo. Tinyforge fetches the file, shows field-level
drift vs the live config, and an admin applies it with an explicit Sync. The
repo becomes the source of truth for the declared fields. Manual-sync only;
no auto-apply on deploy, no multi-workload reconcile, no create/delete in v1.
Scope is deliberately source-aware and source_config-resident: dockerfile
declares port/healthcheck/deploy_strategy, static declares deploy_strategy.
The file never carries repo coords or secrets (those stay in the encrypted
DB), which keeps credentials out of the repo.
Backend:
- internal/gitops: Spec/ParseSpec (KnownFields rejects unknown keys), a
source-aware ApplyPlan/BuildPlan, MergeAndValidate (omitted-field-preserving
deep merge + validate-the-merged-result-then-commit — never a partial
config), declared-only Drift with normalization, and Fetch with
ok/no_file/fetch_failed/invalid statuses and token-redacted messages.
- staticsite: DownloadFile added to GitProvider + Gitea/GitHub/GitLab impls,
reusing each provider's SSRF-safe client; 64 KiB cap; ErrFileNotFound.
- store: 4 additive gitops_* columns + setters (disjoint from UpdateWorkload
so the edit-form save and a sync never clobber each other).
- api: GET /workloads/{id}/gitops (status + raw + live drift + managed_fields),
PUT /gitops (admin, enable/path, traversal-safe), POST /gitops/sync (admin,
per-workload locked read->merge->validate->write, audited to event_log).
Frontend:
- GitOpsPanel.svelte: status pill, a purpose-built field-level drift view,
.tinyforge.yml preview, enable ToggleSwitch, Sync via ConfirmDialog; all five
statuses handled, admin affordances gated on the real viewer role.
- GitOps-managed badge (list + detail hero) and a read-only edit-form banner.
- api.ts fetchers + types; i18n apps.detail.gitops.* (en + ru parity).
Built phase-by-phase with an adversarial plan review (caught 5 design flaws
pre-implementation) and an independent review per phase (go / security / ts /
final) — all APPROVE, 0 CRITICAL/HIGH. docs/gitops.md documents the schema and
what's intentionally out of v1. Plan: plans/gitops/.
Add backup/restore functionality for the SQLite database. Users can
trigger manual backups, configure automatic backups on an interval
with retention policies, list/download/delete backups, and restore
from any backup.
- Backup engine using VACUUM INTO (safe with WAL mode)
- Backup metadata tracked in DB, files stored in DATA_DIR/backups/
- Settings: backup_enabled, backup_interval_hours, backup_retention_count
- API: POST/GET/DELETE /api/backups, download, restore endpoints
- Autobackup via cron scheduler with configurable interval
- Retention: prune on startup, after each backup (manual and auto)
- Orphan cleanup: removes backup files without metadata on startup
- Restore: replaces DB and triggers graceful server shutdown
- Settings UI: /settings/backup with toggle, interval, retention config
- Backup list with download, delete, restore actions
- i18n: English and Russian translations
Add flexible DNS management to Docker Watcher. By default, wildcard DNS
is assumed (current behavior). When disabled, users can configure a
Cloudflare DNS provider with API token and zone selection. DNS A records
are automatically created/updated/deleted in sync with proxy consumers
(deployed instances and standalone proxies).
- Settings: wildcard_dns toggle, dns_provider, cloudflare credentials
- Cloudflare client: Provider interface with EnsureRecord/DeleteRecord/ListRecords
- DNS lifecycle hooks in deployer and proxy manager (best-effort)
- Settings UI: DNS config section with provider picker, zone selector, test button
- DNS Records page at /dns with filtering, sync status, reconciliation
- Records visible in both wildcard and managed modes
- Cleanup on provider change: removes old records when switching modes
Add database foundation for observability features:
- event_log table with severity/source filtering and pagination
- standalone_proxies table for user-created reverse proxies
- stale_threshold_days setting (default 7 days)
- Auto-persist warn/error events from event bus to database
- SSE broadcast of persistent events for real-time UI updates
- Frontend types and API functions for downstream UI phases
Design system with CSS custom properties (light/dark themes).
38 Lucide SVG icon components. Dark mode with system preference.
EN/RU localization with i18n store. Skeleton loaders, empty states,
toggle switches, micro-interactions. Responsive sidebar with
mobile hamburger menu. All pages polished with consistent styling.
Per-stage env var overrides with encryption for secrets.
Volume mounts with shared/isolated modes (isolated appends
/{stage}-{tag}/ to source path). Store CRUD, API endpoints,
and frontend editors for both. Env merge during deploy.
Embed SvelteKit static build in Go binary via go:embed. Event bus
for pub/sub with deploy log, instance status, and deploy status events.
SSE endpoints for real-time streaming. Frontend SSE client with
exponential backoff reconnection. Makefile for build pipeline.
Update Phase 12 auth plan with OAuth2/OIDC support.
Quick deploy with 3-step flow (paste URL, inspect, deploy).
Settings pages for global config, registries CRUD with test connection,
NPM credentials with masked display. Toast notification system.
Mount Toast component in root layout.
Remove webhook secret from logs and API response.
Add auth-pending note to router. Fix decrypt fallback that
would use ciphertext as auth token on decrypt failure.
All REST endpoints wired with chi router: projects, stages, instances,
deploys, registries, settings, quick deploy, webhook. Full main.go
wiring with graceful shutdown. Consistent JSON envelope responses.
Sensitive fields stripped from API responses.
Secret UUID-based webhook endpoint for CI image push notifications.
Project/stage matching via glob patterns, auto-creation of unknown
projects from image inspection. Fix JSON response injection.
Gitea registry client with tag listing and pattern matching, cron-based
polling scheduler with first-poll safety, poll state persistence.
DeployTriggerer interface for decoupled deploy triggering.
AES-256-GCM encryption for credential storage, YAML seed config
parser with validation, and transactional import into SQLite.
Credentials (registry tokens, NPM password) encrypted before storage.
Initialize Go module, directory structure, and full SQLite store layer:
- 7-table schema (projects, stages, registries, settings, instances,
deploys, deploy_logs) with auto-migration
- CRUD operations for all entities with proper error handling
- ErrNotFound sentinel for distinguishing 404 from 500 in handlers
- WAL mode, foreign keys, busy timeout pragmas
Create structured plan files with 12 phases covering the full
implementation: scaffold, store, crypto, Docker/NPM clients,
registry poller, webhook, deployer, API layer, SvelteKit frontend,
embedding, and hardening.