Complete Phase 4: security hardening, player/playlist/admin UI, docs
Security: - New internal/netguard package: podcast feed/enclosure fetching now refuses to dial non-public IPs (loopback/RFC1918/link-local/cloud metadata/CGNAT), validated per-connection including across redirects, closing an SSRF gap where a malicious or compromised feed URL could probe the server's internal network. Configurable via VIBESONIC_PODCASTS_ALLOW_PRIVATE_NETWORKS for intentional LAN feeds. - Podcast episode downloads capped at 4GB (was unbounded, disk-exhaustion risk) - Cover art decoding now rejects images above a 40-megapixel budget before full decode (decompression-bomb guard) - HTTP server sets ReadHeaderTimeout (slowloris hardening) - Admin folder-scan redirect now validated as same-site instead of trusting the raw Referer header (open-redirect fix) - Legacy Subsonic token/salt auth now uses a constant-time comparison Web UI completion: - Persistent player: shuffle and repeat (off/all/one) controls, wired into queue advance/prev logic and persisted like other player state - Admin user-permissions form: adminRole toggle (with a guard against demoting the last remaining admin) and per-user music-folder access checkboxes, backed by existing schema/enforcement that was previously unreachable from the UI - Playlist M3U8 import/export Docs: added docs/client-compatibility.md (honest tracking template — no client testing has been performed), docs/deployment.md, docs/security.md, docs/backup.md; refreshed README and cross-links; added a footnote in docs/subsonic-compatibility.md distinguishing spec-standard from VibeSonic-original OpenSubsonic extensions. Also added Go fuzz tests for the external-auth CIDR/IP parser and the LRC lyrics parser. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014r98AtcBbevpscWLsfwVvv
This commit is contained in:
+111
@@ -0,0 +1,111 @@
|
||||
# Backup
|
||||
|
||||
VibeSonic keeps all of its state under one directory,
|
||||
`VIBESONIC_DATA_DIR` (default `/var/lib/vibesonic`, or `/data` in the
|
||||
provided Docker image). Everything that must be backed up lives there:
|
||||
|
||||
```
|
||||
<DataDir>/
|
||||
vibesonic.db SQLite database — users, library index,
|
||||
playlists, favorites, sessions, API keys,
|
||||
podcast subscriptions, shares, ...
|
||||
vibesonic.db-wal SQLite write-ahead log (see below)
|
||||
vibesonic.db-shm SQLite shared-memory index for the WAL
|
||||
cache/covers/ resized cover-art cache (safe to delete —
|
||||
regenerated on demand from source images)
|
||||
podcasts/ downloaded podcast episodes
|
||||
```
|
||||
|
||||
Your music library itself (the folders configured via
|
||||
`VIBESONIC_MUSIC_DIRS` / the admin "Music Folders" page) is not inside
|
||||
`DataDir` and has its own backup story — it's your source media, and
|
||||
VibeSonic never modifies it.
|
||||
|
||||
## What must be backed up
|
||||
|
||||
1. **`vibesonic.db`** (plus `-wal`/`-shm` if present — see below). This
|
||||
is the only thing whose loss is unrecoverable: it holds every user
|
||||
account, password/API-key hash, the entire scanned library index,
|
||||
playlists, favorites/ratings, play history, podcast subscriptions,
|
||||
share links, and configuration stored in the `settings` table.
|
||||
2. **`podcasts/`**, if you don't want to re-download subscribed
|
||||
episodes after a restore.
|
||||
3. Anything you passed via `VIBESONIC_*` environment variables
|
||||
(systemd unit, Docker Compose file, `.env`) — these aren't stored in
|
||||
the database and won't come back from a database restore alone.
|
||||
|
||||
## What does not need to be backed up
|
||||
|
||||
- **`cache/covers/`** — purely a derived cache, regenerated
|
||||
automatically the first time each cover art size is requested again.
|
||||
Safe to delete at any time, including while VibeSonic is running.
|
||||
- **Your music files** — back these up through whatever process you
|
||||
already use for your media library; VibeSonic treats them as
|
||||
read-only input, never as its own state.
|
||||
|
||||
## Correct SQLite backup: mind the WAL
|
||||
|
||||
VibeSonic runs SQLite in **WAL (write-ahead log) mode** for concurrency
|
||||
(`internal/database/database.go`: `PRAGMA journal_mode=WAL`). In WAL
|
||||
mode, recently-committed data can live in the `vibesonic.db-wal` file
|
||||
rather than in `vibesonic.db` itself, and isn't merged back
|
||||
("checkpointed") into the main file on any fixed schedule.
|
||||
|
||||
**Do not just `cp vibesonic.db` from a live server.** A plain file copy
|
||||
of `vibesonic.db` alone, taken while VibeSonic is running, can miss data
|
||||
that only exists in the `-wal` file, or copy `vibesonic.db` and
|
||||
`vibesonic.db-wal` at slightly different points in time and produce an
|
||||
inconsistent pair. Either failure mode can leave you with a backup that
|
||||
looks fine (opens without error) but is silently missing recent writes
|
||||
or is outright corrupt.
|
||||
|
||||
Use one of these instead:
|
||||
|
||||
### Option A — stop the server, then copy (simplest, requires downtime)
|
||||
|
||||
```sh
|
||||
systemctl stop vibesonic
|
||||
cp /var/lib/vibesonic/vibesonic.db* /path/to/backup/
|
||||
systemctl start vibesonic
|
||||
```
|
||||
|
||||
On a clean shutdown SQLite checkpoints and closes the WAL cleanly, so a
|
||||
plain copy of the (possibly WAL-less) database directory taken while the
|
||||
process is stopped is always consistent.
|
||||
|
||||
### Option B — the SQLite online backup API, via the `sqlite3` CLI (no downtime)
|
||||
|
||||
`sqlite3`'s `.backup` command uses SQLite's own backup API, which
|
||||
correctly handles an in-progress WAL and produces a consistent snapshot
|
||||
without stopping VibeSonic or blocking writers for more than the brief
|
||||
final copy step:
|
||||
|
||||
```sh
|
||||
sqlite3 /var/lib/vibesonic/vibesonic.db ".backup /path/to/backup/vibesonic.db"
|
||||
```
|
||||
|
||||
Do this on a schedule (cron/systemd timer) and rotate the output.
|
||||
|
||||
### Option C — filesystem snapshot (LVM, ZFS, Btrfs)
|
||||
|
||||
A crash-consistent filesystem snapshot of the whole `DataDir` (taken
|
||||
atomically, e.g. `zfs snapshot` or an LVM snapshot) is also safe — SQLite
|
||||
in WAL mode is designed to recover cleanly from a snapshot equivalent to
|
||||
an ungraceful power loss, the same way it recovers on the next open
|
||||
after a crash. Plain `rsync`/`cp` while the server is running is **not**
|
||||
equivalent to this, since it isn't atomic across the `.db`/`-wal`/`-shm`
|
||||
trio.
|
||||
|
||||
## Restoring
|
||||
|
||||
1. Stop VibeSonic.
|
||||
2. Replace `vibesonic.db` (and `-wal`/`-shm` if you backed those up too)
|
||||
with the backup copy.
|
||||
3. Restore `podcasts/` if you backed it up.
|
||||
4. Start VibeSonic. It runs pending migrations automatically on
|
||||
startup, same as any other launch.
|
||||
|
||||
If you only have a `vibesonic.db` from Option B/C without matching
|
||||
`-wal`/`-shm` files, that's fine — `.backup` and filesystem snapshots
|
||||
both produce a database file that's already fully checkpointed/consistent
|
||||
on its own.
|
||||
Reference in New Issue
Block a user