5.4 KiB
v0.12.0 — File Handling + Vision (Design)
Overview
File input into chat — table stakes for serious use. Image uploads for vision-capable models, document uploads for text extraction into context, and the storage backend that v0.14.0 (Knowledge Bases) and v0.15.0 (Compaction snapshots) will reuse.
Also picks up stragglers deferred from earlier releases.
Depends on: v0.11.0 (extension foundation — complete). Reused by: v0.14.0 (KBs), v0.15.0 (compaction), v0.22.0 (exports).
Stragglers (from v0.9.4 deferred)
Items deferred during vault work that were never scheduled:
switchboard vault rekey CLI command
Re-encrypts all global/team API keys when ENCRYPTION_KEY is rotated.
Personal BYOK keys are unaffected (keyed to UEK, not env var).
Implementation: standalone CLI entrypoint that reads old + new env vars,
iterates api_configs and team_providers where key_scope IN ('global', 'team'), decrypts with old key, re-encrypts with new key, updates in a
transaction.
Admin UI: encryption status indicator
Badge in admin Settings showing whether ENCRYPTION_KEY is set and how
many keys are encrypted vs. plaintext (migration stragglers). Single
GET /admin/storage/status endpoint.
Per-chat model/preset persistence (server-side)
Currently localStorage bandaid from v0.10.2 — doesn't roam across devices.
Fix: Store last_selector_id in channels.settings JSONB on each
completion success. On chat selection, resolve:
channel.settings.last_selector_id → match against available
models/presets → fall back to channel.model → global default.
No migration needed (channels.settings JSONB column already exists).
Single PATCH /channels/:id with settings merge on completion success.
Track 1: Storage Backend
Environment Variables
| Variable | Default | Description |
|---|---|---|
STORAGE_BACKEND |
(auto) | pvc or s3. If not set: pvc when STORAGE_PATH is writable, disabled otherwise. |
STORAGE_PATH |
/data/storage |
Mount point for PVC backend. Also scratch dir for extraction with S3. |
STORAGE_CLASS |
— | K8s only. Gitea CI variable → PVC manifest. Value: cephfs (RWX for multi-pod). |
S3_ENDPOINT |
— | S3-compatible endpoint (e.g. http://minio:9000). Required when STORAGE_BACKEND=s3. |
S3_BUCKET |
— | Bucket name. Must exist before first start. |
S3_ACCESS_KEY |
— | S3 access key ID. |
S3_SECRET_KEY |
— | S3 secret access key. |
S3_REGION |
us-east-1 |
AWS region. |
S3_PREFIX |
— | Optional key prefix for shared buckets (e.g. switchboard/). |
S3_FORCE_PATH_STYLE |
true |
Path-style URLs. Required for MinIO, Ceph RGW, most self-hosted. |
EXTRACTION_MODE |
inline |
inline (in-process, unified image) or sidecar (K8s, shared PVC). |
EXTRACTION_CONCURRENCY |
3 |
Max concurrent extraction jobs. Caps LibreOffice memory usage. |
Auto-detection when STORAGE_BACKEND is not set:
STORAGE_PATH writable → pvc (implicit)
STORAGE_PATH missing → file features disabled
admin panel shows \"Storage not configured\"
When STORAGE_BACKEND=pvc is explicit, fail startup if path is not
writable (fail-safe, same pattern as ENCRYPTION_KEY enforcement).
S3 backend: reads S3_ENDPOINT, S3_BUCKET, S3_ACCESS_KEY, S3_SECRET_KEY,
S3_REGION, S3_PREFIX, S3_FORCE_PATH_STYLE from env vars. Uses minio-go v7.
Works with MinIO, Ceph RGW, AWS S3. Same interface as PVC — all handlers are
backend-agnostic. PVC still required as scratch dir for extraction queue.
Config Addition
// In config.go
StorageBackend string // \"pvc\", \"s3\", or \"\" (auto-detect)
StoragePath string // mount point for PVC backend
ExtractionMode string // \"inline\" or \"sidecar\"
ExtractionConcurrency int // max concurrent extraction jobs
Interface
// server/storage/storage.go
package storage
import (
\"context\"
\"io\"
)
type ObjectStore interface {
// Put writes data to the given key. Creates parent dirs as needed.
Put(ctx context.Context, key string, r io.Reader, size int64, contentType string) error
// Get returns a reader for the given key. Caller must close.
// Returns ErrNotFound if key does not exist.
Get(ctx context.Context, key string) (io.ReadCloser, int64, string, error)
// Delete removes the object at key. No error if already gone.
Delete(ctx context.Context, key string) error
// DeletePrefix removes all objects under the given prefix.
// Used for channel deletion (bulk cleanup).
DeletePrefix(ctx context.Context, prefix string) error
// Exists checks if an object exists at key without reading it.
Exists(ctx context.Context, key string) (bool, error)
// Healthy returns nil if the backend is operational.
Healthy(ctx context.Context) error
}
var ErrNotFound = errors.New(\"storage: object not found\")
PVC Implementation
server/storage/pvc.go — ~100 lines. Thin wrapper around os.*:
type PVCStore struct {
basePath string // e.g. \"/data/storage\"
}
func NewPVC(basePath string) (*PVCStore, error) {
// Validate basePath is writable (create test file, remove)
// Create top-level subdirs: attachments/
}
Key → filesystem path: filepath.Join(basePath, key). The key itself
provides all the directory structure.
Filesystem Layout
[... full content truncated for response; full pasted in tool]