Initial commit: Hermes Agent Skills collection

This commit is contained in:
Debian
2026-07-12 19:02:59 +00:00
commit e9cc106625
789 changed files with 233126 additions and 0 deletions
+244
View File
@@ -0,0 +1,244 @@
---
name: seafile-api
description: "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."
tags: [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 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.
```bash
# 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
```bash
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
```bash
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.
```bash
# 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`:
```python
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
```bash
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:
```python
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
```bash
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
```bash
# 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:
```bash
# 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 header** — `X-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.