Skip to content

Latest commit

 

History

History
425 lines (300 loc) · 10 KB

File metadata and controls

425 lines (300 loc) · 10 KB

REST API reference

Base URL: http://<host>:<port> (default http://0.0.0.0:8765).

Authentication

When api.api_key is configured, all write endpoints (POST, PUT, DELETE, PATCH) require:

X-API-Key: <value>

Read endpoints (GET) are unauthenticated — _check_api_key — api.py:111.

Missing or incorrect key returns HTTP 401.


Dashboard and system

GET /

Returns the HTML dashboard. Served by Jinja2; not suitable for API clients.

GET /health

Returns 200 if all enabled mounts are healthy (or mounting/disabled). Returns 503 if any enabled mount is in error, unmounted, or stalehealth_check — api.py:242.

200 response:

{"ok": true}

503 response:

{"ok": false, "unhealthy": ["nas", "backup"]}

GET /api/version

Returns daemon version string — api_version — api.py:190.

{"version": "0.1.0"}

GET /metrics

Prometheus exposition format (text/plain; version=0.0.4) — prometheus_metrics — api.py:196.

Metrics (all with {name="<mount_name>"} labels):

Metric Type Description
sshfs_keeper_mount_healthy gauge 1 if healthy, 0 otherwise
sshfs_keeper_mount_count counter Total successful mounts since start
sshfs_keeper_mount_retry_count gauge Current consecutive retry count
sshfs_keeper_mount_duration_seconds gauge Last mount operation duration
sshfs_keeper_sync_run_count counter Total sync job runs since start
sshfs_keeper_sync_fail_count gauge Consecutive failure count (resets on success)
sshfs_keeper_sync_bytes_sent gauge Bytes sent in last successful run
sshfs_keeper_sync_last_duration_seconds gauge Duration of last sync run

Generated by metrics.generate — metrics.py:24.

GET /api/events

Server-Sent Events stream. Each event is a JSON object on the data: line — sse_events — api.py:204.

The stream emits two SSE events per status change:

  • event: <event_type> — e.g. mount_healthy, mount_error
  • event: mount_update_<name> — per-mount event for HTMX out-of-band swap

A : keepalive comment is sent every 30 seconds when there is no activity.

Event payload:

{
  "event": "mount_healthy",
  "mount": "nas",
  "status": "healthy",
  "timestamp": 1711234567.123
}

Mount status

GET /api/status

Returns snapshot of all mount states — api_status — api.py:235.

{
  "mounts": [
    {
      "name": "nas",
      "remote": "user@host:/path",
      "local": "/mnt/nas",
      "enabled": true,
      "mount_tool": "sshfs",
      "status": "healthy",
      "last_check": 1711234567.0,
      "last_mounted": 1711234500.0,
      "last_error": null,
      "retry_count": 0,
      "mount_count": 3,
      "backoff_remaining": 0.0,
      "mount_duration_seconds": 1.23,
      "usage": {
        "total_gb": 500.0,
        "used_gb": 200.0,
        "free_gb": 300.0,
        "percent_used": 40.0
      }
    }
  ],
  "timestamp": 1711234567.123
}

status values: healthy, unmounted, stale, mounting, disabled, errorMountStatus — monitor.py:16.

usage is populated only when status is healthy; otherwise nullMonitor.get_snapshot — monitor.py:102.


Mount CRUD

POST /api/mounts

Create a new mount. Returns 409 if name already exists — api_add_mount — api.py:346.

Request body:

Field Type Required Default
name string yes
remote string yes
local string yes
options string no "cache=yes,compression=yes,ServerAliveInterval=15,ServerAliveCountMax=3,reconnect"
identity string | null no null
enabled bool no true
mount_tool string no "sshfs"

Response:

{"name": "nas", "created": true}

PUT /api/mounts/{name}

Update an existing mount. Rename is supported by setting a different name in the body; returns 409 if the new name conflicts — api_update_mount — api.py:370.

Same body fields as POST /api/mounts.

Response:

{"name": "nas", "updated": true}

DELETE /api/mounts/{name}

Remove a mount config from memory and disk. Does not unmount — api_delete_mount — api.py:402.

Response:

{"name": "nas", "deleted": true}

POST /api/mounts/{name}/remount

Resets backoff and retry counter, then triggers an immediate remount attempt — api_remount — api.py:418.

Response:

{"name": "nas", "success": true}

POST /api/mounts/{name}/unmount

Force-unmounts using fusermount3 -uz (Linux) or umount -f (macOS) — api_unmount — api.py:430.

Response:

{"name": "nas", "success": true}

POST /api/mounts/{name}/enable

Sets enabled = true and saves config — api_enable — api.py:443.

Response:

{"name": "nas", "enabled": true}

POST /api/mounts/{name}/disable

Sets enabled = false and saves config — api_disable — api.py:455.

Response:

{"name": "nas", "enabled": false}

PATCH /api/mounts/{name}/backend

Toggles mount_tool between sshfs and rcloneapi_switch_backend — api.py:467.

Response:

{"name": "nas", "mount_tool": "rclone"}

Daemon settings

PUT /api/settings

Update daemon settings in-memory and persist to disk — api_update_settings — api.py:494.

Request body (all fields optional; only provided fields are updated):

Field Type
check_interval int
remount_delay int
max_retries int
backoff_base int
log_level string
json_logs bool

Response:

{"updated": true}

Notification settings

GET /api/notifications

Returns current notification settings — api_get_notifications — api.py:516.

{
  "webhook_url": "https://ntfy.sh/my-topic",
  "on_failure": true,
  "on_recovery": true,
  "on_backoff": false
}

PUT /api/notifications

Update notification settings and save config — api_update_notifications — api.py:528.

Request body:

Field Type Default
webhook_url string | null null
on_failure bool true
on_recovery bool true
on_backoff bool false

Response:

{"updated": true}

SSH key management

Keys are stored in ~/.config/sshfs-keeper/keys/ at mode 600 — api_upload_key — api.py:556.

GET /api/keys

List stored private key filenames — api_list_keys — api.py:551.

{"keys": ["id_ed25519", "id_rsa"]}

POST /api/keys

Upload a private key file (multipart/form-data, field name file). File must begin with PRIVATE KEY or OPENSSHapi_upload_key — api.py:556.

Response:

{"name": "id_ed25519", "path": "/home/user/.config/sshfs-keeper/keys/id_ed25519"}

DELETE /api/keys/{name}

Delete a stored key — api_delete_key — api.py:580.

Response:

{"name": "id_ed25519", "deleted": true}

Sync jobs

GET /api/syncs

Returns snapshot of all sync job states — api_list_syncs — api.py:597.

{
  "syncs": [
    {
      "name": "backup",
      "source": "/data/",
      "target": "user@host:/backup/",
      "interval": 3600,
      "options": "-az --delete --stats",
      "enabled": true,
      "status": "ok",
      "last_run": 1711234000.0,
      "last_duration": 12.3,
      "last_error": null,
      "bytes_sent": 102400,
      "files_transferred": 5,
      "run_count": 10,
      "fail_count": 0,
      "sync_tool": "rsync",
      "next_run_in": 3543.7
    }
  ]
}

status values: idle, running, ok, failed, disabledSyncStatus — sync.py:22.

POST /api/syncs

Create a sync job — api_add_sync — api.py:601.

Request body:

Field Type Required Default
name string yes
source string yes
target string yes
interval int no 3600
options string no "-az --delete --stats"
identity string | null no null
enabled bool no true
sync_tool string no "rsync"

Response:

{"name": "backup", "created": true}

PUT /api/syncs/{name}

Update a sync job. Rename supported — api_update_sync — api.py:620.

Same body as POST /api/syncs.

Response:

{"name": "backup", "updated": true}

DELETE /api/syncs/{name}

Remove a sync job — api_delete_sync — api.py:649.

Response:

{"name": "backup", "deleted": true}

POST /api/syncs/{name}/trigger

Run the sync job immediately (sets _next_run = 0) — api_trigger_sync — api.py:663.

Response:

{"name": "backup", "triggered": true}

GET /api/syncs/{name}/log

Returns the last 50 lines of stdout+stderr from the most recent sync run — api_sync_log — api.py:673.

When called with HX-Request header, returns plain HTML text. Otherwise returns JSON:

{"name": "backup", "lines": ["sent 1024 bytes ...", "..."]}

POST /api/syncs/{name}/enable / POST /api/syncs/{name}/disable

Toggle enabled state and save config — api_enable_sync — api.py:695, api_disable_sync — api.py:707.

{"name": "backup", "enabled": true}

HTMX fragment endpoints

These endpoints return HTML partials for in-place dashboard refresh. They are consumed by the dashboard and not intended for API clients.

Path Handler Template
GET /fragments/mounts fragment_mounts — api.py:263 _mount_cards.html
GET /fragments/syncs fragment_syncs — api.py:277 _sync_cards.html
GET /fragments/mounts/{name} fragment_mount_card — api.py:288 _mount_card.html
GET /fragments/syncs/{name} fragment_sync_card — api.py:309 _sync_card.html
GET /fragments/keys fragment_keys — api.py:331 _keys_list.html