diff --git a/docs/DISTRIBUTION.md b/docs/DISTRIBUTION.md index 6cd8565..3c77f3f 100644 --- a/docs/DISTRIBUTION.md +++ b/docs/DISTRIBUTION.md @@ -14,42 +14,66 @@ On first run, bundled packages are automatically installed — workflows, surfac ## Bundled Packages -The production Docker image ships with pre-built packages that are auto-installed on first boot: +The production Docker image ships with pre-built packages. A **curated default set** is auto-installed on first boot; additional packages ship in the image and can be opted-in via `BUNDLED_PACKAGES`. + +#### Default Set (auto-installed) | Package | Type | Description | |---------|------|-------------| +| notes | surface | Markdown notes with graph view | +| chat-core | library | Conversations, messages, read cursors | +| workflow-chat | extension | Chat integration for workflow stages | +| dashboard | surface | Dashboard surface | +| workflow-demo | surface | Interactive walkthrough with diagrams | | bug-report-triage | workflow | Public entry, severity routing, SLA timers | | content-approval | workflow | Multi-party signoff example | | employee-onboarding | workflow | Automated provisioning + manager signoff | -| webhook-notifier | workflow | HTTP outbound via connections + Starlark | -| workflow-demo | surface | Interactive walkthrough with diagrams | -| hello-dashboard | surface | Welcome/getting started surface | -| schedules | surface | Cron task management UI | + +#### Opt-in (ship in image, require `BUNDLED_PACKAGES` to enable) + +| Package | Type | Description | +|---------|------|-------------| | tasks | full | Kanban/list task manager with webhooks | +| schedules | surface | Cron task management UI | +| editor | surface | Rich markdown editor | +| hello-dashboard | surface | Welcome/getting started surface | | team-activity-log | surface | Team activity feed | +| webhook-notifier | workflow | HTTP outbound via connections + Starlark | +| csv-table | extension | CSV table renderer | +| diff-viewer | extension | Diff visualization | +| git-board | surface | Git board interface | | gitea-client | library | Gitea API integration library | +| js-sandbox | extension | JavaScript sandbox | +| katex-renderer | extension | LaTeX rendering | +| mermaid-renderer | extension | Diagram rendering | +| regex-tester | extension | Regex testing tool | | icd-test-runner | surface | E2E API test suite | | sdk-test-runner | surface | SDK feature test suite | ### Behavior -- **First boot**: All bundled packages are installed and enabled automatically. +- **First boot**: Curated default packages are installed and enabled automatically. - **Subsequent boots**: No-op — already-installed packages are skipped. - **Admin uninstalls a package**: It stays uninstalled. Bundled packages are never force-reinstalled. - **To re-install**: Delete the package from the database, then restart the container. ### Selecting Packages (Allowlist) -Set `BUNDLED_PACKAGES` to a comma-separated list of package IDs to install only a subset: +Set `BUNDLED_PACKAGES` to control which packages are installed: ```bash -# K8s / Helm — install only core surfaces, no demo/test packages +# Install ALL packages (everything in the image) docker run -p 8080:80 \ - -e BUNDLED_PACKAGES="tasks,schedules,hello-dashboard" \ + -e BUNDLED_PACKAGES="*" \ + ghcr.io/switchboard-core/switchboard-core:latest + +# Install specific packages only +docker run -p 8080:80 \ + -e BUNDLED_PACKAGES="notes,tasks,schedules" \ ghcr.io/switchboard-core/switchboard-core:latest ``` -Empty (default) means install all bundled packages. This is useful for Helm charts where different environments need different packages. +Empty (default) installs the curated default set. Use `*` to install all packages. This is useful for Helm charts where different environments need different packages. ### Disabling Auto-Install @@ -149,7 +173,7 @@ docker build -t my-switchboard . | `STORAGE_PATH` | `/data/storage` | PVC mount point | | `BASE_PATH` | | URL prefix (e.g. `/switchboard`) | | `SKIP_BUNDLED_PACKAGES` | `false` | Disable auto-install of bundled packages | -| `BUNDLED_PACKAGES` | (empty = all) | Comma-separated allowlist of package IDs to install | +| `BUNDLED_PACKAGES` | (empty = defaults) | `""` curated defaults, `"*"` all, or comma-separated IDs | | `BUNDLED_PACKAGES_DIR` | `/app/bundled-packages` | Override bundled packages location | | `LOG_FORMAT` | `text` | `text` or `json` | | `LOG_LEVEL` | `info` | `debug`, `info`, `warn`, `error` | diff --git a/server/config/config.go b/server/config/config.go index 1b05acb..2b87845 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -63,11 +63,13 @@ type Config struct { MTLSAutoActivate bool // auto-activate new users (default true) MTLSDefaultTeam string // team ID for auto-provisioned users (optional) - // Bundled packages (v0.3.8) + // Bundled packages (v0.3.8, curated v0.5.4) // SKIP_BUNDLED_PACKAGES: set true to disable auto-install of bundled packages on first run. // BUNDLED_PACKAGES_DIR: directory containing pre-built .pkg archives (default /app/bundled-packages). - // BUNDLED_PACKAGES: comma-separated allowlist of package IDs to install (empty = all). - // e.g. "tasks,schedules,hello-dashboard" installs only those three. + // BUNDLED_PACKAGES: controls which bundled packages are auto-installed: + // empty → curated default set (notes, chat-core, workflow-chat, dashboard, demo workflows) + // "*" → install all .pkg archives found in the directory + // "a,b" → comma-separated allowlist of specific package IDs SkipBundledPackages bool BundledPackagesDir string BundledPackages string diff --git a/server/handlers/packages_bundled.go b/server/handlers/packages_bundled.go index df25ce7..97f7d32 100644 --- a/server/handlers/packages_bundled.go +++ b/server/handlers/packages_bundled.go @@ -22,13 +22,28 @@ import ( "switchboard-core/triggers" ) +// defaultBundledPackages is the curated set of packages installed by default. +// Other packages still ship in the Docker image but require BUNDLED_PACKAGES +// to be set explicitly (or "*" for all). +var defaultBundledPackages = map[string]bool{ + "notes": true, + "chat-core": true, + "workflow-chat": true, + "dashboard": true, + "workflow-demo": true, + "bug-report-triage": true, + "content-approval": true, + "employee-onboarding": true, +} + // InstallBundledPackages scans bundledDir for .pkg archives and installs // any that don't already exist in the database. Called once at startup // (unless SKIP_BUNDLED_PACKAGES=true). // -// allowlist is an optional comma-separated list of package IDs to install. -// Empty string means install all. This allows Helm/K8s deployments to -// select a subset: e.g. "tasks,schedules,hello-dashboard". +// allowlist controls which packages are installed: +// - Empty string → install the curated default set (see defaultBundledPackages) +// - "*" → install all .pkg archives found in the directory +// - Comma-separated IDs → install only those (e.g. "tasks,schedules") // // Design: install-once, skip-if-present. If an admin uninstalls a bundled // package, it won't be re-installed on the next restart. @@ -43,10 +58,14 @@ func InstallBundledPackages(bundledDir, packagesDir, allowlist string, stores st return } - // Parse allowlist into a set (empty = allow all) + // Parse allowlist: "" → curated defaults, "*" → all, "a,b" → explicit list allowed := parseBundleAllowlist(allowlist) - if len(allowed) > 0 { - log.Printf("[bundled] Allowlist: %v", allowlist) + if allowed != nil { + if allowlist == "" || allowlist == " " { + log.Printf("[bundled] Using curated default set (%d packages)", len(defaultBundledPackages)) + } else { + log.Printf("[bundled] Allowlist: %v", allowlist) + } } var installed, skipped, filtered int @@ -87,10 +106,16 @@ func InstallBundledPackages(bundledDir, packagesDir, allowlist string, stores st } // parseBundleAllowlist parses a comma-separated string into a set of -// package IDs. Returns nil for empty input (meaning "allow all"). +// package IDs. Returns: +// - nil for "*" input (meaning "install all") +// - defaultBundledPackages for empty input +// - explicit set for comma-separated list func parseBundleAllowlist(s string) map[string]bool { s = strings.TrimSpace(s) if s == "" { + return defaultBundledPackages + } + if s == "*" { return nil } parts := strings.Split(s, ",")