Initial commit: Hermes Agent Skills collection
This commit is contained in:
@@ -0,0 +1,498 @@
|
||||
# Seafile Docker Recovery: MySQL Volume Loss & Selective PBS Restore
|
||||
|
||||
## Scenario
|
||||
|
||||
Seafile MC Docker edition on CT 111 (n5pro, 10.0.20.91). The `.env` was changed
|
||||
(image downgraded from `seafile-mc:13.0-latest` to `seafile-mc:12.0-latest`,
|
||||
hostname changed from `cloud.familie-schoen.com` to `seafile.familie-schoen.com`),
|
||||
which triggered MySQL volume reinitialization — wiping all Seafile databases
|
||||
(`seafile_db`, `ccnet_db`, `seahub_db`) and the `seafile` MySQL user. The file
|
||||
block storage (`/opt/seafile-data/`) remained intact. A PBS backup of CT 111 from
|
||||
2026-07-02 existed on `noris_s3` storage. The original `/opt/seafile/` config
|
||||
directory was preserved as a ZIP by the user.
|
||||
|
||||
## Symptoms
|
||||
|
||||
### Symptom 1: Container "Up" but not actually serving
|
||||
|
||||
```bash
|
||||
docker ps # → seafile Up 7 hours
|
||||
docker logs seafile --tail 50 # → "waiting for mysql server to be ready: mysql is not ready" (repeating forever)
|
||||
```
|
||||
|
||||
The container's entrypoint (`/scripts/start.py`) calls `wait_for_mysql()` before
|
||||
proceeding to `init_seafile_server()`. If MySQL connection fails, it loops forever.
|
||||
The container stays "Up" (PID 1 alive) but Seafile never starts.
|
||||
|
||||
### Symptom 2: seaf-fsck warns about DB connection
|
||||
|
||||
```bash
|
||||
docker exec seafile bash -lc '/opt/seafile/seafile-server-12.0.14/seaf-fsck.sh'
|
||||
# → [WARNING] Failed to connect to MySQL: Access denied for user 'seafile'@'172.18.0.5'
|
||||
# → seaf-fsck run done
|
||||
# → Done.
|
||||
```
|
||||
|
||||
Fsck exits 0 but didn't actually check anything — the DB connection failed.
|
||||
|
||||
### Symptom 3: MySQL has no Seafile databases
|
||||
|
||||
```bash
|
||||
docker exec seafile-mysql mysql -uroot -p'PASSWORD' -e 'SHOW DATABASES;'
|
||||
# → information_schema, mysql, performance_schema, sys (NO seafile_db, ccnet_db, seahub_db)
|
||||
```
|
||||
|
||||
### Symptom 4: No seafile MySQL user
|
||||
|
||||
```bash
|
||||
docker exec seafile-mysql mysql -uroot -p'PASSWORD' \
|
||||
-e "SELECT user,host FROM mysql.user WHERE user LIKE '%sea%';"
|
||||
# → Empty set
|
||||
```
|
||||
|
||||
## Root Cause Analysis
|
||||
|
||||
### How `wait_for_mysql()` works
|
||||
|
||||
File: `/scripts/utils.py` inside the seafile container:
|
||||
|
||||
```python
|
||||
def wait_for_mysql():
|
||||
db_host = get_conf('DB_HOST', '127.0.0.1')
|
||||
db_user = 'root'
|
||||
db_passwd = get_conf('DB_ROOT_PASSWD', '')
|
||||
|
||||
# BUT: if seafile.conf exists, it overrides with the seafile user!
|
||||
seafile_conf_path = join(topdir, 'conf', 'seafile.conf')
|
||||
if exists(seafile_conf_path):
|
||||
cp = ConfigParser()
|
||||
cp.read(seafile_conf_path)
|
||||
db_host = cp.get('database', 'host') # → 'db'
|
||||
db_user = cp.get('database', 'user') # → 'seafile'
|
||||
db_passwd = cp.get('database', 'password')
|
||||
db_port = int(cp.get('database', 'port'))
|
||||
|
||||
while True:
|
||||
try:
|
||||
connection = pymysql.connect(host=db_host, port=db_port,
|
||||
user=db_user, passwd=db_passwd)
|
||||
except Exception as e:
|
||||
# prints "mysql is not ready" and sleeps
|
||||
```
|
||||
|
||||
**Critical insight**: Once `seafile.conf` exists (from a prior successful install),
|
||||
`wait_for_mysql()` uses the `seafile` user — NOT root. If the MySQL volume was
|
||||
wiped, the `seafile` user doesn't exist, and this loop runs forever.
|
||||
|
||||
### Why `init_seafile_server()` doesn't fix it
|
||||
|
||||
Even after manually recreating the `seafile` user and databases, restarting the
|
||||
container produces:
|
||||
|
||||
```
|
||||
Skip running setup-seafile-mysql.py because there is existing seafile-data folder.
|
||||
```
|
||||
|
||||
The bootstrap script (`/scripts/bootstrap.py`) skips DB initialization when
|
||||
`seafile-data/` already exists. So the MySQL tables are never created, and fsck
|
||||
reports `Table 'seafile_db.Repo' doesn't exist`.
|
||||
|
||||
## Recovery Procedure
|
||||
|
||||
### Option A: Full PBS Restore of MySQL Volume (Preferred)
|
||||
|
||||
When a PBS backup exists, restoring the entire MySQL data directory is cleanest
|
||||
because it includes all users, grants, schemas, and data.
|
||||
|
||||
⚠️ **Before attempting PBS restore, verify the backup is restorable** (learned
|
||||
the hard way 2026-07-06):
|
||||
|
||||
1. Check verification state in `pvesh get` output — if `{"state":"failed"}`,
|
||||
the backup has missing/corrupt chunks and may not restore.
|
||||
2. Check snapshot directory on PBS server for `.didx` vs `.tmp_didx` —
|
||||
only `.didx` snapshots are complete and restorable.
|
||||
3. Check for `.bad` chunk files on PBS server — if any chunk referenced by
|
||||
the backup is `.bad` (0 bytes), restore aborts immediately with no option
|
||||
to skip. Files dependent on that chunk are permanently lost.
|
||||
|
||||
See `proxmox-ve-administration` skill → `references/pbs-lxc-setup-2026-07.md`
|
||||
pitfalls #13–16 for PBS credential lookup, verification checking, corrupted
|
||||
chunk diagnosis, and incomplete backup detection.
|
||||
|
||||
If PBS restore fails due to corrupted chunks, fall back to Option B (manual
|
||||
recreation) and use `seaf-fsck.sh --repair` to rebuild repo metadata from
|
||||
the intact `/opt/seafile-data/` file blocks.
|
||||
|
||||
#### Step 1: Locate the PBS backup
|
||||
|
||||
```bash
|
||||
# On the PVE node hosting the CT (n5pro = 10.0.20.91):
|
||||
pvesh get /nodes/$(hostname)/storage/noris_s3/content --content-type backup 2>&1 \
|
||||
| awk -F'│' '{print $3}' \
|
||||
| grep 'ct/111'
|
||||
# → noris_s3:backup/ct/111/2026-07-02T21:00:00Z
|
||||
```
|
||||
|
||||
**Note**: `pvesh get` outputs a Unicode box-drawing table, not JSON. Parse with
|
||||
`awk -F'│'` (Unicode pipe character U+2502). Column 3 contains the volid.
|
||||
|
||||
#### Step 2: Stop Seafile containers
|
||||
|
||||
```bash
|
||||
# On the PVE node:
|
||||
pct exec 111 -- docker stop seafile seafile-mysql seafile-memcached
|
||||
```
|
||||
|
||||
#### Step 3: Rename broken MySQL volume
|
||||
|
||||
```bash
|
||||
pct exec 111 -- mv /opt/seafile-mysql/db /opt/seafile-mysql/db.broken-$(date +%Y%m%d)
|
||||
```
|
||||
|
||||
#### Step 4: Restore from PBS
|
||||
|
||||
Use `pct restore` with `--storage` pointing to the CT's root storage, then
|
||||
selectively extract only `/opt/seafile-mysql/db/` from the restored backup.
|
||||
|
||||
Alternatively, use `proxmox-backup-client` to restore specific files:
|
||||
|
||||
```bash
|
||||
# Map the PBS backup to a temp location
|
||||
# PBS restore maps the CT filesystem
|
||||
pct exec 111 -- bash -c '
|
||||
mkdir -p /tmp/restore
|
||||
# Use pb-client to restore just the mysql db path
|
||||
# (requires PBS credentials)
|
||||
'
|
||||
```
|
||||
|
||||
#### Step 5: Verify MySQL data
|
||||
|
||||
```bash
|
||||
pct exec 111 -- docker start seafile-mysql
|
||||
sleep 10
|
||||
pct exec 111 -- docker exec seafile-mysql mysql -uroot -p'PASSWORD' \
|
||||
-e "SELECT user,host FROM mysql.user WHERE user='seafile'; SHOW DATABASES;"
|
||||
# Should show: seafile@%, seafile_db, ccnet_db, seahub_db
|
||||
```
|
||||
|
||||
#### Step 6: Start Seafile
|
||||
|
||||
```bash
|
||||
pct exec 111 -- docker start seafile
|
||||
sleep 30
|
||||
pct exec 111 -- docker logs seafile --tail 20
|
||||
# Should show: "Seafile server started", "Seahub is started"
|
||||
```
|
||||
|
||||
#### Step 7: Run fsck
|
||||
|
||||
```bash
|
||||
pct exec 111 -- docker exec seafile bash -lc \
|
||||
'/opt/seafile/seafile-server-12.0.14/seaf-fsck.sh'
|
||||
```
|
||||
|
||||
With restored DB, fsck should connect and check all repos.
|
||||
|
||||
### Option B: Manual DB Init via `docker run` Setup Script (Preferred over SQL import)
|
||||
|
||||
When PBS restore fails and you need fresh DBs, the cleanest approach is running
|
||||
the official `setup-seafile-mysql.sh` in a throwaway container with proper env
|
||||
vars. This creates all schemas, users, and config files correctly.
|
||||
|
||||
**Critical**: The `bootstrap.py` in the main container skips `init_seafile_server()`
|
||||
when `/shared/seafile/seafile-data/` exists. To force fresh DB init:
|
||||
|
||||
1. Stop the seafile container
|
||||
2. Rename `seafile-data` temporarily: `mv .../seafile-data .../seafile-data.bak`
|
||||
3. Clear `conf/`: `rm -rf .../conf/*`
|
||||
4. Drop any half-created databases: `DROP DATABASE ccnet_db, seafile_db, seahub_db`
|
||||
5. Run setup in a throwaway container:
|
||||
|
||||
```bash
|
||||
docker run --rm --name seafile-setup --network seafile-net \
|
||||
-e SERVER_NAME=seafile \
|
||||
-e SERVER_IP=cloud.familie-schoen.com \
|
||||
-e MYSQL_USER=seafile \
|
||||
-e MYSQL_USER_PASSWD='SEAFILE_PW' \
|
||||
-e MYSQL_USER_HOST=% \
|
||||
-e MYSQL_HOST=db \
|
||||
-e MYSQL_PORT=3306 \
|
||||
-e MYSQL_ROOT_PASSWD='ROOT_PW' \
|
||||
-e CCNET_DB=ccnet_db \
|
||||
-e SEAFILE_DB=seafile_db \
|
||||
-e SEAHUB_DB=seahub_db \
|
||||
-e SEAFILE_SERVER_HOSTNAME=cloud.familie-schoen.com \
|
||||
-e SEAFILE_SERVER_PROTOCOL=https \
|
||||
-e INIT_SEAFILE_ADMIN_EMAIL=admin@example.com \
|
||||
-e INIT_SEAFILE_ADMIN_PASSWORD=password \
|
||||
-e TIME_ZONE=Europe/Berlin \
|
||||
-v /opt/seafile-data:/shared \
|
||||
seafileltd/seafile-mc:13.0-latest \
|
||||
/opt/seafile/seafile-server-13.0.19/setup-seafile-mysql.sh auto -n seafile
|
||||
```
|
||||
|
||||
⚠️ The env var names matter: `MYSQL_ROOT_PASSWD` (not `MYSQL_ROOT_PASSWORD`),
|
||||
`MYSQL_USER_PASSWD` (not `MYSQL_USER_PASSWORD`). The setup script reads
|
||||
`get_param_val(args.mysql_root_passwd, 'MYSQL_ROOT_PASSWD')` from `os.environ`.
|
||||
|
||||
6. Restore original `seafile-data` and `conf`:
|
||||
```bash
|
||||
rm -rf .../seafile-data && mv .../seafile-data.bak .../seafile-data
|
||||
# Restore conf from backup (contains correct DB credentials, SERVICE_URL, etc.)
|
||||
cp -a .../conf.bak/* .../conf/
|
||||
```
|
||||
|
||||
7. Update `SERVICE_URL` in `seahub_settings.py` if hostname changed:
|
||||
```bash
|
||||
sed -i 's|old.domain.com|new.domain.com|g' .../conf/seahub_settings.py
|
||||
```
|
||||
|
||||
8. Start the container normally — it will run upgrade scripts (12.0→13.0 etc.)
|
||||
and start Seafile.
|
||||
|
||||
### Option B2: Manual SQL Schema Import (Legacy Approach)
|
||||
|
||||
If `docker run` setup doesn't work, manually recreate databases and import schemas.
|
||||
|
||||
#### Step 1: Create databases and user
|
||||
|
||||
```bash
|
||||
docker exec seafile-mysql mysql -uroot -p'ROOT_PW' -e "
|
||||
CREATE DATABASE IF NOT EXISTS ccnet_db CHARACTER SET utf8 COLLATE utf8_general_ci;
|
||||
CREATE DATABASE IF NOT EXISTS seafile_db CHARACTER SET utf8 COLLATE utf8_general_ci;
|
||||
CREATE DATABASE IF NOT EXISTS seahub_db CHARACTER SET utf8 COLLATE utf8_general_ci;
|
||||
CREATE USER 'seafile'@'%' IDENTIFIED BY 'SEAFILE_PW';
|
||||
GRANT ALL PRIVILEGES ON ccnet_db.* TO 'seafile'@'%';
|
||||
GRANT ALL PRIVILEGES ON seafile_db.* TO 'seafile'@'%';
|
||||
GRANT ALL PRIVILEGES ON seahub_db.* TO 'seafile'@'%';
|
||||
FLUSH PRIVILEGES;
|
||||
"
|
||||
```
|
||||
|
||||
#### Step 2: Import SQL schemas
|
||||
|
||||
The seafile container has no `mysql` client. Use the seafile-mysql container:
|
||||
|
||||
```bash
|
||||
SQLDIR=/opt/seafile/seafile-server-12.0.14/sql/mysql
|
||||
|
||||
# ccnet schema
|
||||
docker exec seafile-mysql mysql -uroot -p'ROOT_PW' ccnet_db \
|
||||
< <(docker exec seafile cat $SQLDIR/ccnet.sql)
|
||||
|
||||
# seafile schema
|
||||
docker exec seafile-mysql mysql -uroot -p'ROOT_PW' seafile_db \
|
||||
< <(docker exec seafile cat $SQLDIR/seafile.sql)
|
||||
|
||||
# seahub schema (located in seahub/scripts/upgrade/sql/)
|
||||
docker exec seafile-mysql mysql -uroot -p'ROOT_PW' seahub_db \
|
||||
< <(docker exec seafile cat /opt/seafile/seafile-server-12.0.14/seahub/scripts/upgrade/sql/12.0.0/mysql/seahub.sql)
|
||||
```
|
||||
|
||||
⚠️ **This only recreates empty tables.** Existing repo metadata, users, and
|
||||
library mappings are lost. File blocks in `/opt/seafile-data/` remain but are
|
||||
orphaned (no DB entries pointing to them). The admin account must be recreated
|
||||
via `check_init_admin.py` or the Seafile web UI.
|
||||
|
||||
#### Step 3: Restart and run fsck
|
||||
|
||||
```bash
|
||||
docker restart seafile
|
||||
sleep 30
|
||||
docker exec seafile bash -lc '/opt/seafile/seafile-server-12.0.14/seaf-fsck.sh'
|
||||
```
|
||||
|
||||
### Option C: Reconstruct Repo Registrations from Commit Storage
|
||||
|
||||
When DBs are freshly initialized but `/opt/seafile-data/` (file blocks + commits)
|
||||
is intact, repos can be reconstructed by scanning the commit storage and
|
||||
registering them in the DB. See `references/seafile-repo-reconstruction.md` for
|
||||
the full procedure with Python scripts.
|
||||
|
||||
**Overview of the technique:**
|
||||
|
||||
1. **Scan commit storage**: `storage/commits/<repo_id>/<XX>/<YYYY...>/` — each
|
||||
file is a JSON object with `commit_id`, `root_id`, `parent_id`,
|
||||
`second_parent_id`, `repo_name`, `repo_desc`, `ctime`, `creator_name`.
|
||||
2. **Find HEAD commit**: The commit NOT referenced as `parent_id` by any other
|
||||
commit in the same repo. For repos with many commits (>1000), use
|
||||
`find -printf '%T@ %p\n' | sort -rn | head -20` to read only the newest 20.
|
||||
3. **Register in DB**: Insert into `Repo`, `Branch`, `RepoOwner`, and `RepoInfo`
|
||||
tables. The `Repo` table only needs `repo_id`; `Branch` needs `(name, repo_id,
|
||||
commit_id)`; `RepoOwner` needs `(repo_id, owner_id)`; `RepoInfo` needs
|
||||
`(repo_id, name, ...)`.
|
||||
4. **Run `seaf-fsck.sh --repair`**: After registering repos, fsck verifies
|
||||
integrity and repairs inconsistencies.
|
||||
|
||||
⚠️ **Encrypted repo commit IDs may have suffixes** (e.g.,
|
||||
`e155972728d6a3205726b94243078ae5f0aaaa3a.FYMWR3`) that exceed the 40-char
|
||||
`commit_id` column. Truncate to 40 chars before inserting.
|
||||
|
||||
⚠️ **`seaf-fsck.sh --repair` only checks registered repos** — it does NOT discover
|
||||
or register new repos. All repos must be registered in the DB first.
|
||||
|
||||
⚠️ **`seaf-fsck.sh --export` also only exports registered repos** — it cannot
|
||||
recover unregistered repos from the filesystem.
|
||||
|
||||
## Key Files in Seafile Docker Container
|
||||
|
||||
| Path | Purpose |
|
||||
|------|---------|
|
||||
| `/opt/seafile/conf/seafile.conf` | Main config — DB host, user, password, fileserver port |
|
||||
| `/opt/seafile/seafile-server-VERSION/` | Server installation (scripts, SQL, binaries) |
|
||||
| `/opt/seafile/seafile-server-VERSION/seaf-fsck.sh` | Filesystem consistency checker |
|
||||
| `/opt/seafile/seafile-server-VERSION/seaf-gc.sh` | Garbage collector for orphaned blocks |
|
||||
| `/opt/seafile/seafile-server-VERSION/sql/mysql/` | SQL schema files (ccnet.sql, seafile.sql) |
|
||||
| `/opt/seafile/seafile-server-VERSION/seahub/scripts/upgrade/sql/VERSION/mysql/seahub.sql` | Seahub DB schema |
|
||||
| `/scripts/start.py` | Container entrypoint — calls `wait_for_mysql()` + `init_seafile_server()` |
|
||||
| `/scripts/utils.py` | `wait_for_mysql()` implementation |
|
||||
| `/scripts/bootstrap.py` | `init_seafile_server()` — skips if `seafile-data/` exists |
|
||||
|
||||
## Docker-Compose Layout (seafile-server.yml)
|
||||
|
||||
```yaml
|
||||
services:
|
||||
db:
|
||||
image: mariadb:10.11
|
||||
container_name: seafile-mysql
|
||||
volumes:
|
||||
- "${SEAFILE_MYSQL_VOLUME:-/opt/seafile-mysql/db}:/var/lib/mysql"
|
||||
# ...
|
||||
seafile:
|
||||
image: seafileltd/seafile-mc:12.0-latest
|
||||
container_name: seafile
|
||||
environment:
|
||||
- DB_HOST=db
|
||||
- DB_USER=seafile
|
||||
- DB_ROOT_PASSWD=${INIT_SEAFILE_MYSQL_ROOT_PASSWORD}
|
||||
- DB_PASSWORD=${SEAFILE_MYSQL_DB_PASSWORD}
|
||||
volumes:
|
||||
- "${SEAFILE_VOLUME:-/opt/seafile-data}:/shared"
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
```
|
||||
|
||||
The `seafile` container's `/shared` mount corresponds to `/opt/seafile-data` on the
|
||||
host. Inside the container, `/opt/seafile/` is symlinked from `/shared/seafile/`.
|
||||
|
||||
## Network Architecture
|
||||
|
||||
```
|
||||
seafile-net (bridge, 172.18.0.0/16):
|
||||
├── seafile-mysql 172.18.0.3 (hostname: db)
|
||||
├── seafile-memcached 172.18.0.2
|
||||
├── seafile-caddy 172.18.0.4
|
||||
├── seafile 172.18.0.5
|
||||
└── seadoc 172.18.0.6
|
||||
```
|
||||
|
||||
`seafile.conf` references `host = db` which resolves via Docker DNS to the
|
||||
`seafile-mysql` container.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
1. **`wait_for_mysql()` uses `seafile` user, not `root`** — Once `seafile.conf`
|
||||
exists, the startup script connects as the `seafile` MySQL user. If that user
|
||||
doesn't exist (volume wipe), the loop runs forever. Container shows "Up" but
|
||||
Seafile never starts.
|
||||
|
||||
2. **`init_seafile_server()` skips when `seafile-data/` exists** — Even after
|
||||
recreating the MySQL user and empty databases, the bootstrap won't create
|
||||
tables because it sees existing data. Must import SQL schemas manually or
|
||||
restore the MySQL volume from backup.
|
||||
|
||||
3. **`seaf-fsck.sh` exits 0 even on DB failure** — It catches the DB connection
|
||||
error, prints a WARNING, and exits cleanly. Don't rely on exit code; check
|
||||
stderr for `[WARNING]` lines.
|
||||
|
||||
4. **No `mysql` client in seafile container** — Use `docker exec seafile-mysql
|
||||
mysql ...` for all MySQL operations. The seafile container only has Python
|
||||
and Seafile binaries.
|
||||
|
||||
5. **`seafile-data/` is large and usually intact** — When recovering, don't
|
||||
restore `/opt/seafile-data/` from backup unless corruption is suspected. It
|
||||
contains all file blocks (potentially hundreds of GB). Restoring only the
|
||||
MySQL volume + conf is much faster.
|
||||
|
||||
6. **PBS backup listing via `pvesh`** — `pvesh get /nodes/$(hostname)/storage/
|
||||
<storage>/content` outputs a Unicode table, not JSON. Parse with
|
||||
`awk -F'│'` to extract volids. See `proxmox-ve-administration` skill for
|
||||
PBS backup listing details.
|
||||
|
||||
7. **MariaDB vs MySQL image** — Seafile Docker uses `mariadb:10.11` but the
|
||||
config says `type = mysql`. MariaDB is compatible. The `MARIADB_AUTO_UPGRADE=1`
|
||||
env var handles schema upgrades on container start.
|
||||
|
||||
8. **Image version change can wipe MySQL volume** — Changing the Seafile Docker
|
||||
image (e.g., downgrading `seafile-mc:13.0-latest` → `seafile-mc:12.0-latest`
|
||||
or vice versa) can trigger MySQL volume reinitialization. The container detects
|
||||
a version mismatch and reinitializes the data directory, wiping all databases
|
||||
and users. **Always backup the MySQL volume (`/opt/seafile-mysql/db`) before
|
||||
changing the image version in `.env`.**
|
||||
|
||||
9. **Compare `.env` before restoring** — When recovering, always diff the current
|
||||
`.env` against any backup config. Key fields that differ between versions:
|
||||
- `SEAFILE_IMAGE` (e.g., `13.0-latest` vs `12.0-latest`)
|
||||
- `SEAFILE_SERVER_HOSTNAME` (e.g., `cloud.familie-schoen.com` vs
|
||||
`seafile.familie-schoen.com`)
|
||||
- `INIT_SEAFILE_ADMIN_EMAIL`
|
||||
- `JWT_PRIVATE_KEY`
|
||||
- `COMPOSE_FILE` list (e.g., `caddy.yml` may be added/removed)
|
||||
|
||||
Align the config BEFORE starting containers with a restored DB, otherwise
|
||||
Seafile may reinitialize or fail to match the restored data.
|
||||
|
||||
10. **Telegram file transfer corrupts large ZIPs** — Sending large binary archives
|
||||
(e.g., a MySQL data directory ZIP) via Telegram can truncate or corrupt the
|
||||
file. The ZIP arrives with valid local file headers but no central directory
|
||||
and invalid size fields (`0xFFFFFFFF`). For transferring server data between
|
||||
PVE nodes, use `scp`, `rsync`, or PBS restore instead of Telegram file
|
||||
attachments. If a received ZIP fails to open, verify with
|
||||
`python3 -c "import zipfile; zipfile.ZipFile('file.zip').namelist()"` — if it
|
||||
raises `BadZipFile`, the transfer corrupted it.
|
||||
|
||||
11. **`bootstrap.py` ENV variable substitution bug** — When `bootstrap.py`
|
||||
calls `setup-seafile-mysql.py` via `subprocess.call(cmd, env={...})`,
|
||||
the `env=` dict **replaces** the entire process environment rather than
|
||||
augmenting it. The `env=` dict includes `MYSQL_ROOT_PASSWD` (mapped from
|
||||
`INIT_SEAFILE_MYSQL_ROOT_PASSWORD`), but if the mapping is missing or
|
||||
misnamed, the subprocess gets `MYSQL_ROOT_PASSWD=''` (empty string),
|
||||
producing the error `using password: NO` from MySQL.
|
||||
|
||||
**Symptom**: `setup-seafile-mysql.py` fails with `Access denied for
|
||||
'root'@'localhost' (using password: NO)` even though
|
||||
`INIT_SEAFILE_MYSQL_ROOT_PASSWORD` is correctly set in the container env.
|
||||
|
||||
**Fix**: Run `setup-seafile-mysql.py` manually inside the container with
|
||||
all required ENV vars explicitly exported:
|
||||
```bash
|
||||
docker exec -e MYSQL_ROOT_PASSWD='<root_pw>' \
|
||||
-e MYSQL_USER='seafile' -e MYSQL_USER_PASSWD='<seafile_pw>' \
|
||||
-e MYSQL_HOST=db -e MYSQL_PORT=3306 \
|
||||
-e CCNET_DB=ccnet_db -e SEAFILE_DB=seafile_db -e SEAHUB_DB=seahub_db \
|
||||
-e SEAFILE_SERVER_HOSTNAME=cloud.example.com \
|
||||
-e INIT_SEAFILE_ADMIN_EMAIL=admin@example.com \
|
||||
-e INIT_SEAFILE_ADMIN_PASSWORD='<admin_pw>' \
|
||||
seafile /opt/seafile/seafile-server-13.0.19/setup-seafile-mysql.sh auto -n seafile
|
||||
```
|
||||
This bypasses `bootstrap.py`'s broken `env=` substitution and gives the
|
||||
setup script direct access to all required variables.
|
||||
|
||||
12. **PBS corrupted chunk blocks ALL file restoration** — A single 0-byte `.bad`
|
||||
chunk file on the PBS server (destroyed by garbage collection) makes the
|
||||
ENTIRE backup unrestorable. PBS aborts on the first file referencing the
|
||||
bad chunk, cleans up, and exits. There is no `--skip-bad-chunks` option.
|
||||
Files alphabetically before the affected file MAY be partially restored,
|
||||
but anything after is lost. The chunk path format is
|
||||
`/mnt/datastore/<ds>/.chunks/<XX>/<hash>.0.bad`.
|
||||
|
||||
13. **Multiple backup formats can ALL be unusable** — In this incident, THREE
|
||||
separate backup sources were tried and ALL failed:
|
||||
- PBS backup: corrupted chunk (above)
|
||||
- `seafile-mysql.zip` (Telegram-transferred): BadZipFile, no central directory
|
||||
- `seafile-mysql.tar.zst`: truncated, unexpected EOF after 4 entries
|
||||
- Offsite PBS: no CT111 backup existed at all
|
||||
Lesson: never assume ANY backup is restorable without verifying. Always
|
||||
test-restore before declaring a backup strategy viable.
|
||||
Reference in New Issue
Block a user