Add backups with S3, daily cost push, automation templates and a browser tool

- Daily backups (database + config) into <data_dir>/backups, keep N,
  optional upload to any S3-compatible bucket (SigV4, bucket pruning);
  settings panel with run-now, list and download
- Daily cost summary push at 20:00 (opt-in per user, preview button)
- Automations from templates (dependency check, security review, test
  health, daily summary, TODO report, docs freshness) via POST /jobs/create
- browser tool: headless Chromium over CDP – click, type, keys, waits,
  JavaScript, navigation, screenshots, console errors; requests to private
  network addresses are blocked (sub-resources and redirects included)
- Secrets masked in the config view: S3 secret key, metrics token
- Replace the remaining text arrows/refresh glyphs with SVG icons

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-09 06:07:03 +01:00
1 parent 873447ed17
commit 01c1484033
25 files changed
+1720 -47

No files matched your search

+43 -2
View File
@@ -30,10 +30,49 @@ pub struct Config {
pub sandbox: SandboxConfig,
pub search: SearchConfig,
pub lsp: LspConfig,
pub backup: BackupConfig,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub hooks: Vec<HookConfig>,
}
/// Automatic daily backups (database + config) on disk and optionally in S3.
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(default)]
pub struct BackupConfig {
pub enabled: bool,
/// Local hour (0-23) of the daily run.
pub hour: u32,
/// Number of local backups to keep.
pub keep: usize,
/// Directory for the zip files (default: <data_dir>/backups).
#[serde(skip_serializing_if = "String::is_empty")]
pub dir: String,
pub s3: S3Config,
}
impl Default for BackupConfig {
fn default() -> Self {
Self { enabled: true, hour: 3, keep: 30, dir: String::new(), s3: S3Config::default() }
}
}
/// S3-compatible bucket (AWS, MinIO, Garage, Backblaze B2, Hetzner, …).
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
#[serde(default)]
pub struct S3Config {
/// e.g. https://s3.eu-central-1.amazonaws.com or https://minio.example.com; empty = no upload.
pub endpoint: String,
pub region: String,
pub bucket: String,
/// Key prefix, e.g. "codecraft/".
pub prefix: String,
pub access_key: String,
/// Supports ${ENV_VAR}.
pub secret_key: String,
/// Old backups beyond this number are deleted from the bucket (0 = keep all).
pub keep: usize,
}
/// A shell command run on an agent event (see hooks.rs).
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct HookConfig {
@@ -560,8 +599,10 @@ pub fn redacted_json(cfg: &Config) -> serde_json::Value {
if let Some(t) = v.pointer_mut("/images/admin_key") {
mask(t);
}
if let Some(t) = v.pointer_mut("/search/api_key") {
mask(t);
for p in ["/search/api_key", "/backup/s3/secret_key", "/server/metrics_token"] {
if let Some(t) = v.pointer_mut(p) {
mask(t);
}
}
v
}