// Backup schema + retention data — the module-level constants and the // retention-preset detector extracted verbatim from backup-page.js. // Loaded before backup-page.js (feature scripts array, sequential) so the // BackupPage methods that reference these globals at runtime resolve them. // Retention presets — pick the persona that matches you. Each maps to the // five underlying restic --keep-* values. "Custom" reveals the raw fields. const BACKUP_RETENTION_PRESETS = { 'inherit-global': { last: '', daily: '', weekly: '', monthly: '', yearly: '' }, 'self-hosting': { last: '', daily: '30', weekly: '', monthly: '', yearly: '' }, 'personal': { last: '', daily: '30', weekly: '', monthly: '6', yearly: '' }, 'enterprise': { last: '', daily: '30', weekly: '', monthly: '12', yearly: '5' } }; const BACKUP_RETENTION_PRESET_META = { 'inherit-global': { label: 'Inherit global retention', hint: 'Use whatever the Configuration tab specifies. Pick something else here only when this location needs a different policy.' }, 'self-hosting': { label: 'Self-hosting', hint: '30 days of daily backups. Plenty for a homelab — covers accidental deletes and app screw-ups.' }, 'personal': { label: 'Personal', hint: '30 days of daily backups plus 6 monthly backups. Good for personal data where "what did this look like last summer" matters.' }, 'enterprise': { label: 'Enterprise', hint: '30 daily + 12 monthly + 5 yearly. Compliance-style retention with multi-year history.' }, 'custom': { label: 'Custom…', hint: 'Define each retention tier yourself.' } }; // Per-location field metadata. Configs.json doesn't carry titles for // CFG_BACKUP_LOC_N_* (locations are dynamic), so we provide them inline. // ConfigShared.generateField uses TITLE + key-based widget heuristics; the // regexes in config-options.js / config-shared.js already cover _TYPE, // _KEEP_*, _SECRET_KEY, _ACCOUNT_KEY for the right widgets. const BACKUP_LOC_FIELD_DEFS = { NAME: { title: 'Friendly name', description: 'Shown in lists and on the dashboard.' }, ENABLED: { title: 'Enabled', description: 'Push backups to this location.' }, ENGINE: { title: 'Engine', description: 'Backup engine used at this location.' }, TYPE: { title: 'Type', description: 'Backend the engine uses to talk to this location.' }, PATH_MODE: { title: 'Path Mode', description: 'Automatic uses the Default Backup Location from the Backup Engine config (one subfolder per location). Pick Custom to use a specific path (e.g. an attached drive or a NAS mount).' }, PATH: { title: 'Custom path', description: 'Filesystem path on this server. Used only when Path is set to Custom.' }, URI: { title: 'Repository URI (override)', description: 'Custom restic URI — leave blank to build from the fields below.' }, SSH_USER: { title: 'SSH user', description: '' }, SSH_HOST: { title: 'SSH host', description: '' }, SSH_PORT: { title: 'SSH port', description: '' }, SSH_PATH: { title: 'SSH remote path', description: 'Path on the remote host where the repo lives.' }, SSH_AUTH: { title: 'SSH authentication', description: 'Key auth uses ~/.ssh/id_rsa on this host. Password mode pipes via sshpass — restic + borg only; kopia requires keys.' }, SSH_PASS: { title: 'SSH password', description: 'Used only when SSH authentication is set to Password.' }, S3_ACCESS_KEY: { title: 'S3 access key', description: '' }, S3_SECRET_KEY: { title: 'S3 secret', description: '' }, B2_ACCOUNT_ID: { title: 'B2 account ID', description: '' }, B2_ACCOUNT_KEY: { title: 'B2 account key', description: '' }, APPEND_ONLY: { title: 'Append-only', description: 'Ransomware-safe — refuse forget/prune for this location even if LibrePortal itself is compromised. Trades off automatic retention cleanup.' }, CUSTOM_RETENTION: { title: 'Use custom retention', description: 'Otherwise this location inherits the global retention.' }, KEEP_LAST: { title: 'Keep last', description: 'Backups to always retain.' }, KEEP_DAILY: { title: 'Keep daily', description: 'One backup per day for this many days.' }, KEEP_WEEKLY: { title: 'Keep weekly', description: 'One backup per week for this many weeks.' }, KEEP_MONTHLY: { title: 'Keep monthly', description: 'One backup per month for this many months.' }, KEEP_YEARLY: { title: 'Keep yearly', description: 'One backup per year for this many years.' } }; // Fallback for the per-type field schema. The live source is the generator- // emitted data/backup/generated/schema.json (loaded into this.locSchema and // read via locFieldsForType); this map is only used if that fetch fails. // Type leads each list (it shapes the rest of the form); ENGINE stays in the // list but locFieldGroups folds it into the Advanced tab. const BACKUP_LOC_FIELDS_BY_TYPE = { local: ['TYPE', 'NAME', 'ENGINE', 'PATH_MODE', 'PATH', 'APPEND_ONLY'], sftp: ['TYPE', 'NAME', 'ENGINE', 'SSH_USER', 'SSH_HOST', 'SSH_PORT', 'SSH_PATH', 'SSH_AUTH', 'SSH_PASS', 'URI', 'APPEND_ONLY'], rest: ['TYPE', 'NAME', 'ENGINE', 'URI', 'APPEND_ONLY'], s3: ['TYPE', 'NAME', 'ENGINE', 'URI', 'S3_ACCESS_KEY', 'S3_SECRET_KEY', 'APPEND_ONLY'], b2: ['TYPE', 'NAME', 'ENGINE', 'URI', 'B2_ACCOUNT_ID', 'B2_ACCOUNT_KEY', 'APPEND_ONLY'], gs: ['TYPE', 'NAME', 'ENGINE', 'URI', 'APPEND_ONLY'], azure: ['TYPE', 'NAME', 'ENGINE', 'URI', 'APPEND_ONLY'], rclone: ['TYPE', 'NAME', 'ENGINE', 'URI', 'APPEND_ONLY'] }; // Suffixes that live in the editor's "Advanced" tab. configs.json can flag // more via a **ADVANCED** comment marker; this set keeps the known overrides // advanced even on legacy location.configs that predate the marker. Engine is // here too — the system picks a sensible default, so most users never touch it. const LOC_ADVANCED_SUFFIXES = new Set(['ENGINE', 'URI', 'SSH_PORT', 'APPEND_ONLY']); function backupRetentionDetectPreset(values, includeInherit = false) { const norm = (v) => (v == null ? '' : String(v).trim()); for (const [key, p] of Object.entries(BACKUP_RETENTION_PRESETS)) { if (key === 'inherit-global' && !includeInherit) continue; if (norm(values.last) === norm(p.last) && norm(values.daily) === norm(p.daily) && norm(values.weekly) === norm(p.weekly) && norm(values.monthly) === norm(p.monthly) && norm(values.yearly) === norm(p.yearly)) { return key; } } return 'custom'; }