# Seafile & Immich Backup Strategy Analysis

## When to Use

When planning offsite/backup strategy for Seafile (558 GB) or Immich
on bandwidth-limited remote PBS, and deciding what to back up vs.
what to leave on Ceph-only redundancy.

## Seafile Backup Strategies (6 Options)

### Context

- Seafile Pro 13.0.19 MC Docker edition on CT111
- File blocks: `/opt/seafile-data/seafile/seafile-data/storage/blocks/` (intrinsically deduplicated, content-addressable)
- Config: `/opt/seafile/.env`, `seafile.conf`, `seahub_settings.py`
- DB: MySQL (`seafile_db`, `ccnet_db`, `seahub_db`) — currently in `seafile-mysql` container, migration to Galera planned
- Total size: ~558 GB (mostly file blocks)
- Ceph redundancy: blocks stored on Ceph EC4+1 (5 HDD OSDs, 1-failure tolerance)

### Option Matrix

| # | Strategy | Offsite Volume | Pros | Cons |
|---|----------|---------------|------|------|
| 1 | **PBS Full (current)** | 558 GB init, then delta | Simple, everything included | Huge for offsite, slow restore |
| 2 | **Config+DB only** | <1 GB | Fast daily offsite, trivial restore | Blocks only in Ceph — total Ceph loss = all files gone |
| 3 | **SeafGC + PBS exclude blocks** | ~2 GB | Clean, regularly garbage-collected | Blocks only in Ceph, needs GC discipline |
| 4 | **Seafile Native Backup** | Variable | Official way, incremental | Needs second Seafile server (not S3) |
| 5 | **RBD Snapshot + PBS** | 558 GB init, delta like #1 | Point-in-time consistent | Ceph pool must support snapshots |
| 6 | **Blocks on S3 (Pro feature)** | Rootfs ~2 GB offsite, blocks on S3 | Clean separation, Ceph relieved | Migration effort, S3 costs |

### Recommendation

- **Short-term**: Option 2 (Config+DB offsite) + regular `seaf-gc.sh`. Blocks
  are redundant in Ceph EC4+1. For DR against total Ceph loss: Option 4 as
  second pillar.
- **Long-term**: Option 6 (S3 storage backend) — moves blocks out of Ceph
  entirely, making PBS offsite trivial (~2 GB) and Ceph only hosts config+DB.

## Seafile Pro S3 Storage Backend

Seafile Pro supports S3 as **primary storage backend** (not backup target).
All file blocks are stored directly on S3 instead of local disk/Ceph.

### Configuration (in `seafile.conf`)

```ini
[storage_backend]
name = s3
bucket = seafile-data
key_id = XXX
key = XXX
region = eu-central-1
endpoint = https://s3.example.com
```

### Key Distinctions

- **Primary storage** — replaces local/Ceph block storage entirely
- **NOT a backup target** — S3 is where live data lives, not a backup copy
- **Migration required** — existing blocks must be migrated to S3
- **Ceph relief** — once on S3, Ceph only holds config+DB (~2 GB), making
  PBS offsite backup trivial and fast

### When to Choose S3 Backend

- Ceph capacity is constrained (OSDs near full)
- Offsite backup of 558 GB blocks is impractical (bandwidth-limited)
- S3 storage is cheaper than maintaining Ceph OSDs
- Want clean separation: config+DB on Ceph (small, fast), blocks on S3 (large, cheap)

## Seafile Native Backup (`seaf-backup-cmd`)

### How It Works

- Uses Seafile's internal RPC API to synchronize libraries to a **second
  Seafile server**
- Incremental — only changed commits/blocks are transferred
- The second server must be a full Seafile server (same version)
- Commands: `seaf-backup-cmd status`, `seaf-backup-cmd sync <repo-id>`

### Limitations

- **Needs a second Seafile server** — not a simple file/rsync target
- **Not S3-compatible** — target must be Seafile server with storage backend
- **Setup complexity** — must configure backup server in `seafile.conf`
- **Per-library sync** — no global "backup everything" by default

### Location

```bash
# In the seafile container:
docker exec seafile /opt/seafile/seafile-server-latest/seahub/scripts/seaf-backup-cmd.sh
# Error without proper SEAFILE_DATA_DIR env — needs server running
```

### When to Choose

- Need true DR replica (second running Seafile instance)
- Have infrastructure for a second server
- Want incremental, deduplicated replication (not full copies)

## Immich Backup Considerations

### Architecture

- CT115 (immich): stopped, 20 GB rootfs — upload directory location unclear
- CT117 (immich-postgresql): running, 914 MB DB on proxmox6
- Photos/videos are NOT deduplicated (unlike Seafile blocks)

### Strategy Options

| # | Strategy | Offsite Volume | Notes |
|---|----------|---------------|-------|
| 1 | PBS Full both CTs | ~21 GB | Simple but may grow |
| 2 | DB dump + upload-dir rsync | DB ~1 GB + uploads | Selective |
| 3 | DB only offsite | <1 GB | Photos only in Ceph |
| 4 | Uploads on Ceph, DB on Galera | DB offsite <1 GB | Clean separation |

### Key Question

Where are the actual photo/video uploads stored? CT115 is stopped —
uploads may be on a mount point, external volume, or within the
PostgreSQL CT. Must identify before choosing a strategy.

## Bandwidth-Aware Offsite Backup Prioritization

When adding services to a bandwidth-limited offsite PBS:

### Tier 1: Small + Critical Infrastructure (add first)

- Traefik (~3 GB) — reverse proxy config, all routes
- Gitea (~1.2 GB) — code repositories
- Authelia, DNS — tiny but critical
- Paperless (~16 GB) — documents (medium size, high value)

### Tier 2: Medium + Important

- Litellm (~3.4 GB) — API config
- Seafile config+DB (~2 GB) — without blocks

### Tier 3: Evaluate Separately

- Seafile blocks (~558 GB) — consider S3 backend or native backup
- Immich uploads — identify storage location first

### Skip

- Frigate (~40 GB) — rewritable video, low value
- High-churn data that's easily regenerated

## PBS Offsite Job Configuration

### Adding CTs to Offsite Job

```bash
# Edit /etc/pve/jobs.cfg from any PVE node (pmxcfs replicates)
# Current: vmid 106
# Target: vmid 106,108,104,99999
sed -i '/offsite-ha-daily/,/^$/{s/vmid 106/vmid 106,108,104,99999/}' /etc/pve/jobs.cfg
```

### Initial Full Backup (Manual)

Must run `vzdump` on the node where each CT actually runs (see
`proxmox-ve-administration` → `references/pbs-lxc-setup-2026-07.md`
pitfall #17 for the silent-skip issue):

```bash
# Determine node for each CT first
pvesh get /cluster/resources --type vm --output-format json | python3 -c "
import json,sys
for v in json.load(sys.stdin):
    if v.get('vmid') in [108,99999,104]:
        print(f\"CT{v['vmid']:5d} node={v['node']}\")"

# Run on correct nodes
ssh root@10.0.20.60 "vzdump 108 --storage noris_offsite --mode snapshot --compress zstd"
ssh root@10.0.20.70 "vzdump 99999 --storage noris_offsite --mode snapshot --compress zstd"
ssh root@10.0.20.91 "vzdump 104 --storage noris_offsite --mode snapshot --compress zstd"
```

### Verification

```bash
# Check backups arrived at PBS (may need content cache refresh)
pvesh get /nodes/proxmox1/storage/noris_offsite/content --output-format json | \
  python3 -c "
import json,sys,datetime
for b in sorted(json.load(sys.stdin), key=lambda x: x.get('ctime',0), reverse=True)[:10]:
    print(f\"{b['volid']:55s} {b.get('size',0)//1024//1024:>8} MB  {datetime.datetime.fromtimestamp(b.get('ctime',0)).strftime('%Y-%m-%d %H:%M')}\")"
```

## Session Context (2026-07-07)

- Offsite job expanded: `vmid 106` → `vmid 106,108,104,99999`
- Initial full backups triggered manually on correct nodes
- CT111 (Seafile) backup showed 0 MB on noris_s3 — empty/failed backup
  (previous valid: 558 GB on 2026-07-06)
- Frigate excluded from all backups per user decision
- Seafile backup strategy discussion: user exploring alternatives to
  558 GB full backup, considering S3 storage backend (Pro feature)
- Immich: CT115 stopped, upload location unidentified
