10 KiB
name, description, tags
| name | description | tags | ||||||
|---|---|---|---|---|---|---|---|---|
| seafile-api | Seafile Cloud administration: API interaction (token auth, repos, dirs, file ops) and Docker server administration (fsck, MySQL volume recovery, startup debugging, image-version-change pitfalls). Covers Seafile 12.x–13.x MC Docker edition. |
|
Seafile Cloud Administration
This skill covers two domains:
- API Interaction — token auth, repo/dir/file operations via REST API
- Docker Server Administration —
seaf-fsck, MySQL volume recovery, startup debugging, selective restore from PBS backups
For Docker server administration, recovery procedures, and fsck troubleshooting, see references/seafile-docker-recovery.md.
For backup strategy analysis (offsite/PBS/S3/native), see references/seafile-immich-backup-strategy.md.
API: Authentication
Seafile uses token-based API auth. Never use cookie/session login — it doesn't work reliably.
# Get API token
curl -ks -H 'Content-Type: application/json' \
-d '{"username":"user@example.com","password":"password"}' \
"https://seafile.yourdomain.com/api2/auth-token/"
# → {"token":"abc123..."}
API: List Repositories
curl -ks -H "Authorization: Token {token}" \
-H "X-Requested-With: XMLHttpRequest" \
"https://seafile.yourdomain.com/api2/repos/"
Returns array of repos with type ("repo" = personal, "grepo" = group-shared).
API: List Directory Contents
curl -ks -H "Authorization: Token {token}" \
-H "X-Requested-With: XMLHttpRequest" \
"https://seafile.yourdomain.com/api2/repos/{repo-id}/dir/?p=%2F"
Returns array of items: {type:"dir"/"file", name, size, mtime, id, permission}.
API: Download File
Seafile 13.x (Pro/Plus) — file download via /api2/repos/.../files/ returns 404. This endpoint only works on older Seafile versions.
Workarounds for Seafile 13.x:
- Use
requests.Session()to log in via the web UI (/accounts/login/) and navigate to the file page, then extract the download URL from the React SPA's network calls. - Use WebDAV:
curl -k -u user:token https://server/dav/path/to/file - Use
seafile-cliif available. - Manually download via browser/CLI and copy to the server.
# Old endpoint (ONLY works on Seafile < 13.x):
curl -ks -H "Authorization: Token {token}" \
-H "X-Requested-With: XMLHttpRequest" \
"https://seafile.yourdomain.com/api2/repos/{repo-id}/files/?p=%2Fpath%2Fto%2Ffile.txt" \
-o output.txt
For login + download via Python requests:
import requests, re, urllib.parse
API = "https://seafile.yourdomain.com"
REPO = "repo-id-here"
FILE = "/path/to/file.pdf"
session = requests.Session()
session.headers.update({"User-Agent": "Mozilla/5.0 ..."})
# 1. Get CSRF token
resp = session.get(API, allow_redirects=True, timeout=15)
csrf = re.search(r'name="csrfmiddlewaretoken"[^>]*value="([^"]*)"', resp.text).group(1)
# 2. POST login
session.post(resp.url, data={
"login": "user@example.com", "password": "pw",
"csrfmiddlewaretoken": csrf, "next": "/",
}, allow_redirects=True, timeout=15)
# 3. Access file page — the SPA loads download via JS; inspect page source for download patterns
file_url = f"{API}/library/{REPO}/docs/?p={urllib.parse.quote(FILE, safe='/')}"
resp = session.get(file_url, timeout=15)
# Look for 'download' patterns in resp.text — no static <a> tags exist
API: Create Directory
curl -ks -X PUT -H "Authorization: Token {token}" \
-H "X-Requested-With: XMLHttpRequest" \
"https://seafile.yourdomain.com/api/v2.1/repos/{repo-id}/directory/" \
--data-urlencode "path=/new_folder"
API: Upload File
Seafile v2.1 Basic Edition (default) — ALL API upload endpoints return 404. The following endpoints are ALL disabled:
/api/v2.1/repos/{repo-id}/upload//api/v2.0/repos/{repo-id}/upload//api/v1/repos/{repo-id}/upload//api/repos/{repo-id}/upload//api/upload//api/v1/upload//seafhttp/upload/
Fallback: Playwright Web UI Automation
The React web UI upload button uses selector [class*="upload-file"]. It sits in a dropdown, so use JS click + file chooser:
from playwright.sync_api import sync_playwright
import json, subprocess, glob, time
SEAF_URL = "https://cloud.familie-schoen.com"
repo_id = "737eb5b4-731e-4bfe-8a42-8edd62b04c6e" # from /api2/repos/
TARGET_DIR = "/Amazon"
creds = json.loads(subprocess.run(
'op item get "cloud.familie-schoen.com" --vault "Hermes" --format json',
shell=True, capture_output=True, text=True).stdout)
username, password = creds['fields'][0]['value'], creds['fields'][1]['value']
files = sorted(glob.glob("/tmp/amazon-invoices-2025/Amazon_Rechnung_*.pdf"))
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
context = browser.new_context(viewport={"width": 1280, "height": 800}, ignore_https_errors=True)
page = context.new_page()
# Login
page.goto(f"{SEAF_URL}/accounts/login/", wait_until="domcontentloaded", timeout=30000)
page.fill('input[name="login"]', username)
page.fill('input[name="password"]', password)
page.click('button[type="submit"]')
page.wait_for_url(lambda u: "login" not in u.lower(), timeout=10000)
# Navigate to target directory
page.goto(f"{SEAF_URL}/library/{repo_id}/docs/?p=%2F{TARGET_DIR.lstrip('/')}", wait_until="domcontentloaded", timeout=30000)
time.sleep(2)
# Trigger file chooser via JS click (button is in dropdown, not directly clickable)
with page.expect_file_chooser() as fc:
page.evaluate("""() => {
document.querySelector('[class*="upload-file"]')?.click();
}""")
time.sleep(1)
file_chooser = fc.value
file_chooser.set_files(files)
time.sleep(20) # Wait for upload to complete
browser.close()
Key details:
- Selector:
[class*="upload-file"]— matches.sf3-font-upload-files sf3-font mr-2 dropdown-item-icon - Must use JS click (
page.evaluate) because the button is in a hidden dropdown (visibility: hidden → click timeout) - Use
page.expect_file_chooser()context manager to catch the file dialog file_chooser.set_files(files)accepts a list for batch upload
API: Delete File
curl -ks -X DELETE -H "Authorization: Token {token}" \
-H "X-Requested-With: XMLHttpRequest" \
"https://seafile.yourdomain.com/api/v2.1/repos/{repo-id}/files/" \
-d "p=/path/to/file"
Docker Server Administration
Running seaf-fsck
# Find the seafile-server version directory
docker exec seafile bash -lc 'ls /opt/seafile/ | grep seafile-server'
# → seafile-server-12.0.14
# Run fsck
docker exec seafile bash -lc '/opt/seafile/seafile-server-12.0.14/seaf-fsck.sh'
The fsck checks consistency between MySQL metadata (Repo table, commits) and the file block storage. If MySQL tables are missing or the seafile DB user doesn't exist, fsck will report warnings but exit cleanly without actually checking anything.
fsck Silent Death (OOM Under Heavy I/O)
Pitfall: seaf-fsck can die silently under heavy I/O competition
(e.g. concurrent Ceph recovery + RBD copy on the same OSDs). The process
gets OOM-killed or I/O-starved, leaving no error in the fsck output log.
The log simply stops at the last repo being checked, with no "finished"
or error message.
Detection: Don't trust the log output alone — check if the process is still alive:
# Inside the CT:
ps aux | grep seaf-fsck | grep -v grep | wc -l
# 0 = process died, needs restart
# 3+ = process running (multiple threads)
Restart: Simply re-run the fsck command. Already-checked repos are processed quickly (seconds), so progress jumps forward rapidly after restart. Large photo repos ("Fotos", "Lightroom Bilder") are the usual stalling points — they have tens of thousands of commits and millions of file blocks.
Large repos: Repos with 24000-34000+ commits take hours per repo
under normal I/O. Under I/O contention they may appear hung for 1+ hour
per repo. The process is alive (state Dl = disk I/O wait) but barely
progressing. Serializing I/O (pausing competing operations) dramatically
speeds up fsck.
Recovery from Lost MySQL Volume
See references/seafile-docker-recovery.md for the full debugging procedure covering:
wait_for_mysql()infinite loop diagnosisinit_seafile_server()skipping schema creation- Selective restore from PBS backup (MySQL volume + conf only, skip seafile-data)
- Manual DB init via
docker runsetup script (Option B) - Manual DB/User recreation and SQL schema import (Option B2)
- Repo reconstruction from commit storage (Option C) — when DBs are fresh but
file blocks are intact, scan commit JSON objects to find HEAD commits and
register repos in
Repo/Branch/RepoOwner/RepoInfotables. Seereferences/seafile-repo-reconstruction.mdfor the full procedure.
API Pitfalls
- Wrong API paths —
/api/v2.1/repos/{repo}/directory/and/api/v2.0/return HTML error pages. Use/api2/repos/{repo-id}/dir/for listing. - Missing header —
X-Requested-With: XMLHttpRequestis required for most API calls, or the server redirects to the login page. - Cookie login does NOT work — The AJAX form POST (
/seahub/ajax/login/) renders an error page. Always use token auth. - Web UI login returns 403 but sets cookie — The Seafile login POST (
/accounts/login/) may return HTTP 403 despite successfully setting the session cookie. This is expected on Seafile 13.x; subsequent requests to the same session work. - URL encoding — Paths must be URL-encoded for
p=parameter (space→%20,/→%2F). - Seafile 13.x (Pro/Plus) — The
/api/v2.1/repos/{repo}/files/,/seafhttp/, and WebDAV endpoints all return 404/400. Token auth works for repo listing (/api2/repos/) and dir listing (/api2/repos/{repo}/dir/), but file download is broken via API. Use web login + requests.Session() workaround or manual download. - WebDAV disabled — Seafile may have WebDAV disabled on newer versions. Check with
curl -k -o /dev/null -w '%{http_code}' -X PROPFIND "https://server/dav/". - Auth'd 500 vs unauth'd 403 — If authenticated calls (
Authorization: Token ...) return HTTP 500 (HTML "Page unavailable") while unauthenticated calls return HTTP 403 (valid JSON), the backend is in a broken state. Get a fresh token, and if all endpoints return 500, the server needs a restart.