Files

10 KiB
Raw Permalink Blame History

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.x13.x MC Docker edition.
file-storage
cloud
api
seafile
docker
recovery

Seafile Cloud Administration

This skill covers two domains:

  1. API Interaction — token auth, repo/dir/file operations via REST API
  2. Docker Server Administrationseaf-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:

  1. 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.
  2. Use WebDAV: curl -k -u user:token https://server/dav/path/to/file
  3. Use seafile-cli if available.
  4. 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 diagnosis
  • init_seafile_server() skipping schema creation
  • Selective restore from PBS backup (MySQL volume + conf only, skip seafile-data)
  • Manual DB init via docker run setup 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/RepoInfo tables. See references/seafile-repo-reconstruction.md for 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 headerX-Requested-With: XMLHttpRequest is 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.