Skip to content

API Reference

github-actions[bot] edited this page Oct 20, 2026 · 22 revisions

API Reference

All endpoints are served at http://<host>:8080. The API is JSON-over-HTTP; all request and response bodies are application/json.

Endpoint overview

Method Path Description
GET /api/sysinfo Host and process info
GET /api/network Network interfaces (name, state, MAC, MTU, IPs, speed, RX/TX)
GET /api/version OpenZFS version string
GET /api/pools List all pools with usage stats
GET /api/poolstatus Detailed pool status with vdev tree
GET /api/datasets List all datasets and volumes
GET /api/dataset-props/{name} Editable properties for a dataset
GET /api/snapshots List all snapshots
GET /api/snapshots/diff Files changed between snapshots (zfs diff)
GET /api/iostat Pool I/O statistics (1-second sample)
GET /api/smart S.M.A.R.T. health per disk
GET /api/events Server-Sent Events stream
GET /metrics Prometheus text exposition
GET /api/schema Dataset property definitions and system metadata for the current OS
POST /api/datasets Create a dataset or volume
PATCH /api/datasets/{name} Update dataset properties
DELETE /api/datasets/{name} Destroy a dataset or volume
POST /api/datasets/rename Rename a dataset or volume ({"name","new_name"}, same parent)
POST /api/rewrite/{name} Rewrite existing blocks (zfs rewrite, background job)
GET /api/userspace/{name} Per-user/group space usage (?kind=user|group)
POST /api/userquota/{name} Set or remove a per-user/group quota
POST /api/snapshots Create a snapshot
POST /api/snapshots/clone Clone a snapshot to a new dataset
POST /api/snapshots/send Send a snapshot to a target dataset (local or remote SSH)
POST /api/snapshots/delete-batch Destroy several snapshots ({"snapshots":[...]})
DELETE /api/snapshots/{name} Destroy a snapshot
GET /api/users List local users
POST /api/users Create a local user
PUT /api/users/{name} Edit user (shell, groups, password)
DELETE /api/users/{name} Delete user and home directory
GET /api/users/{name}/sshkeys List a user's SSH authorized keys
POST /api/users/{name}/sshkeys Add an SSH authorized key
DELETE /api/users/{name}/sshkeys Remove an SSH authorized key ({"key":"ssh-ed25519 …"})
GET /api/groups List local groups
POST /api/groups Create a local group
PUT /api/groups/{name} Edit group (name, GID, members)
DELETE /api/groups/{name} Delete a local group
GET /api/chown/{dataset} Get mountpoint owner and group
POST /api/chown/{dataset} Set mountpoint owner and/or group
GET /api/acl-status ACL presence map (dataset → bool)
GET /api/acl/{dataset} Get ACL entries for a dataset
POST /api/acl/{dataset} Add or modify an ACL entry
DELETE /api/acl/{dataset} Remove an ACL entry
GET /api/smb-shares List active Samba usershares
POST /api/smb-share/{dataset} Create or update a Samba usershare
DELETE /api/smb-share/{dataset} Remove a Samba usershare
GET /api/smb-users List users registered in smbpasswd
POST /api/smb-users/{name} Add a user to smbpasswd
DELETE /api/smb-users/{name} Remove a user from smbpasswd
GET /api/smb/status Samba init state, conf path, OS, wsdd status
POST /api/smb/init Initialise Samba (back up existing config, render smb.conf)
POST /api/smb/wsdd Render wsdd options from smb.conf (take over wsdd)
GET /api/smb/homes Get current SMB [homes] config
POST /api/smb/homes Enable/update SMB [homes] section
DELETE /api/smb/homes Disable/remove SMB [homes] section
GET /api/smb/timemachine List all Time Machine shares
POST /api/smb/timemachine Create/update a Time Machine share
DELETE /api/smb/timemachine/{name} Remove a Time Machine share
GET /api/devices List physical block devices (vdev candidates, in-use flag)
POST /api/pools Create a pool (zpool create)
GET /api/pools/importable List pools available for import
POST /api/pools/import Import a pool (zpool import [-f])
POST /api/pools/{pool}/export Export a pool (zpool export)
POST /api/pools/{pool}/vdevs Add a data vdev (zpool add)
POST /api/pools/{pool}/cache Add an L2ARC cache device
POST /api/pools/{pool}/log Add a SLOG log device (optionally mirrored)
POST /api/pools/{pool}/spare Add a hot spare
DELETE /api/pools/{pool}/devices/{device} Remove a cache/log/spare device (zpool remove)
POST /api/pools/{pool}/replace Replace a pool device (starts resilver)
POST /api/pools/{pool}/offline Take a pool device offline
POST /api/pools/{pool}/online Bring a pool device online
POST /api/scrub/{pool} Start a pool scrub
DELETE /api/scrub/{pool} Cancel a running pool scrub
GET /api/scrub-schedules List periodic scrub schedule config
PUT /api/scrub-schedule/{pool} Add pool to periodic scrub schedule
DELETE /api/scrub-schedule/{pool} Remove pool from periodic scrub schedule
GET /api/auto-snapshot/{dataset} Get auto-snapshot property values for a dataset
PUT /api/auto-snapshot/{dataset} Set auto-snapshot properties for a dataset
GET /api/auto-snapshot/status Auto-snapshot ownership (OS daemon vs dumpstore)
POST /api/auto-snapshot/takeover Disable the OS daemon and run snapshots from dumpstore
POST /api/auto-snapshot/release Re-enable the OS daemon and stop dumpstore's scheduler
GET /api/auto-snapshot-schedules Auto-snapshot properties for all datasets
GET /api/iscsi-targets List all iSCSI targets
POST /api/iscsi-targets Create an iSCSI target for a zvol
DELETE /api/iscsi-targets Remove an iSCSI target
GET /api/services List status of all managed services
POST /api/services/{name}/{action} Control a service (start/stop/restart/enable/disable)
GET /api/replication List scheduled replication tasks
POST /api/replication Create a replication task
PATCH /api/replication/{id} Update a replication task
DELETE /api/replication/{id} Delete a replication task
POST /api/replication/{id}/run Fire a replication task immediately (returns 202 + job_id)
GET /api/replication/{id}/history Recent run records for a task
GET /api/jobs List background jobs
GET /api/jobs/{id} Single job status
POST /api/jobs/{id}/cancel Cancel a running job (SIGTERM, then SIGKILL)
DELETE /api/jobs/{id} Remove a finished job record
GET /api/tls/status TLS configuration and certificate metadata
POST /api/tls/gencert Generate a self-signed certificate
PATCH /api/tls/config Use an existing certificate + key from disk
POST /api/tls/acme/issue Issue a certificate via ACME (lego)
POST /api/tls/acme/renew Renew the ACME certificate
GET /login Login page
POST /auth/login Log in (form: username, password); sets the session cookie
POST /auth/logout Log out
GET /api/whoami Current identity (session or trusted-proxy X-Remote-User)
GET /api/auth/config Configured username and session TTL
POST /api/auth/change-password Change the login password
POST /api/auth/change-username Change the login username

Datasets

POST /api/datasets

Create a filesystem or volume.

{
  "name": "tank/data",
  "type": "filesystem",
  "compression": "lz4",
  "quota": "50G",
  "mountpoint": "/mnt/data",
  "recordsize": "128K",
  "atime": "off",
  "exec": "on",
  "sync": "standard",
  "dedup": "off",
  "copies": "1",
  "xattr": "sa"
}

For volumes, use "type": "volume" and add "volsize": "10G". Optional: "volblocksize", "sparse": true.

PATCH /api/datasets/{name}

Update dataset properties. Any subset of editable properties may be sent. An empty string value resets the property to inherited; a non-empty value sets it explicitly. Unknown properties are ignored.

{
  "compression": "zstd",
  "quota": "",
  "readonly": "on"
}

Editable properties: compression, quota, mountpoint, recordsize, atime, exec, sync, dedup, copies, xattr, readonly, acltype, sharenfs, sharesmb.

POST /api/rewrite/{name}

Rewrite the existing blocks of a mounted filesystem via zfs rewrite so that updated properties (compression, checksum, dedup, copies) apply to already-stored data. Runs as a background job — returns 202 Accepted with a job_id; progress and output appear in the Jobs tab (GET /api/jobs).

{
  "recursive": true,
  "skip_snapshot_shared": true,
  "skip_clone_shared": false
}
  • recursive → -r (recurse into directories; crosses into child dataset mountpoints)
  • skip_snapshot_shared → -S (don't duplicate blocks shared with snapshots)
  • skip_clone_shared → -C (don't duplicate blocks shared via block cloning)

Caveats: recordsize changes are not applied by rewrite; rewriting blocks shared with snapshots/clones duplicates them (pool usage can increase) unless skipped; rewritten blocks appear as modified in incremental send streams. Volumes are rejected with 400 — rewrite operates through the mounted filesystem. Requires OpenZFS with zfs rewrite support (2.3+).

GET /api/userspace/{name}

Per-user or per-group space consumption and quotas for a dataset, via zfs userspace -Hp / zfs groupspace -Hp. Query parameter kind is user (default) or group.

{
  "dataset": "tank/data",
  "kind": "user",
  "rows": [
    { "name": "alice", "used": 1073741824, "quota": 10737418240 },
    { "name": "root", "used": 53248, "quota": 0 }
  ]
}

quota: 0 means no quota is set.

POST /api/userquota/{name}

Set or remove a per-user/per-group quota (userquota@<principal> / groupquota@<principal>).

{ "kind": "user", "principal": "alice", "quota": "10G" }

"quota": "none" removes the limit. Returns Ansible task steps.

DELETE /api/datasets/{name}

Destroy a dataset or volume. Append ?recursive=true to also destroy all child datasets and snapshots.

Pool roots (e.g. tank) cannot be deleted via this endpoint — use zpool destroy.


Snapshots

POST /api/snapshots

{
  "dataset": "tank/data",
  "snapname": "2024-01-15_backup",
  "recursive": false
}

POST /api/snapshots/send

Dispatch zfs send | zfs recv as a background job. Returns 202 Accepted immediately with the job metadata; live status is published on the jobs.update SSE topic and visible in the Jobs tab. Local target:

{
  "snapshot": "tank/data@2026-05-06",
  "target": "backup/data",
  "incremental_from": "tank/data@2026-05-05",
  "raw": false
}

Add "remote": "user@host" to receive over SSH (ssh -o BatchMode=yes); the dumpstore service account must have SSH keys pre-configured for host. incremental_from and raw are optional. Response shape:

{
  "job_id": "8f3c…",
  "type": "snapshot.send",
  "started_at": "2026-05-06T15:21:33Z",
  "snapshot": "tank/data@2026-05-06",
  "target": "user@host:backup/data"
}

Use GET /api/jobs/{id} to poll status, or subscribe to the jobs.update SSE topic.

GET /api/snapshots/diff

Show files changed between two snapshots, or between a snapshot and the live dataset, via zfs diff -H.

Query parameters:

  • from (required) — the older snapshot (dataset@label)
  • to (optional) — a later snapshot of the same dataset; omit to diff against the current filesystem state
{
  "from": "tank/data@before",
  "to": "tank/data@after",
  "truncated": false,
  "entries": [
    { "change": "+", "path": "/tank/data/new.txt" },
    { "change": "-", "path": "/tank/data/gone.txt" },
    { "change": "M", "path": "/tank/data/report.pdf" },
    { "change": "R", "path": "/tank/data/old.txt", "new_path": "/tank/data/renamed.txt" }
  ]
}

Entries are capped at 10 000 (truncated: true when more changed). The dataset must be mounted for zfs diff to work.


Jobs

Long-running data-plane operations (currently snapshot send/receive) run as direct child processes outside Ansible, since they may run for hours. Each job has a status (running, success, failed, cancelled, interrupted), the captured argv, bounded stdout/stderr tails (last 64 KiB each), and timestamps. Records are persisted to disk so status survives a restart; any job left in running at shutdown is rewritten to interrupted on next boot.

GET /api/jobs

Returns the list of known jobs, newest first.

GET /api/jobs/{id}

Returns a single job's full record (including stdout/stderr tail).

POST /api/jobs/{id}/cancel

Sends SIGTERM to the job's process group, escalating to SIGKILL after a 10 s grace. Returns 204 No Content on success, 400 if the job is already terminal.

DELETE /api/jobs/{id}

Removes a terminal job from the manager and deletes its on-disk record. Returns 204 No Content on success, 400 if the job is still running (cancel it first).


Scheduled replication

Cron-scheduled replication tasks. Each fire creates a dumpstore-repl-<UTC> snapshot on the source, holds it for the duration of the transfer, picks the most recent common dumpstore-repl-* snapshot for an incremental send, dispatches the pipeline through the jobs runner, releases the hold, and prunes destination replication snapshots beyond the retention count.

GET /api/replication

Returns all replication tasks (without last_runs). Use /history to retrieve run records.

POST /api/replication

Create a task. enabled and retention_count default to true and 7 respectively.

{
  "name": "nightly-backup",
  "source": "tank/data",
  "target": "backup/data",
  "remote": "user@host",
  "schedule": "0 3 * * *",
  "retention_count": 7,
  "raw": false,
  "recursive": false,
  "enabled": true
}

schedule is a standard 5-field cron expression (minute, hour, day-of-month, month, day-of-week) with 1-minute resolution and no catch-up on missed firings. remote is optional; when empty the task replicates between local pools.

PATCH /api/replication/{id}

Partial update; any subset of the above fields is accepted. Re-registers the task with the scheduler.

DELETE /api/replication/{id}

Unregister and remove the task. Replicated snapshots on the destination, and dumpstore-repl-* snapshots on the source, are not deleted.

POST /api/replication/{id}/run

Fire the task immediately, off-schedule. Returns 202 Accepted with the job_id of the dispatched send/recv pipeline. The body of the running task continues asynchronously; subscribe to jobs.update for completion.

GET /api/replication/{id}/history

Returns the recent RunRecord list (capped at 20):

[
  {
    "job_id": "8f3c…",
    "snapshot": "tank/data@dumpstore-repl-20260524T030000Z",
    "started_at": "2026-05-24T03:00:00Z",
    "finished_at": "2026-05-24T03:11:42Z",
    "status": "success"
  }
]

DELETE /api/snapshots/{dataset}@{snapname}

Append ?recursive=true to also destroy clones.


Pool lifecycle

POST /api/pools

Create a new pool via zpool create. Destroys all existing data on the selected devices — the UI gates this behind confirm-by-typing.

{
  "name": "tank",
  "vdev_type": "mirror",
  "devices": ["/dev/sdb", "/dev/sdc"],
  "ashift": "12",
  "compression": "zstd"
}

vdev_type: single (stripe, no redundancy keyword), mirror, raidz1, raidz2, raidz3, draid, draid2, draid3. Minimum device counts are enforced (mirror ≥ 2, raidzN ≥ N+1, draidN ≥ N+1). ashift (9–16) and compression are optional. Returns 409 if a pool with that name already exists. Returns Ansible task steps.

GET /api/pools/importable

Scans with zpool import (no pool name) and returns pools available for import.

[
  { "name": "tank", "id": "11536261005894085344", "state": "ONLINE" },
  { "name": "backup", "id": "9183745610293847561", "state": "DEGRADED", "status": "One or more devices are missing from the system." }
]

POST /api/pools/import

{ "pool": "tank", "force": false }

force: true passes -f — needed for pools that were not cleanly exported or belonged to another system. Returns Ansible task steps.

POST /api/pools/{pool}/export

Export the pool via zpool export. Fails when the pool is busy (open files, active shares, running jobs) — the error is surfaced in the op-log. Returns Ansible task steps.

POST /api/pools/{pool}/vdevs

Add a data vdev via zpool add. Irreversible on most pool layouts — the UI gates this behind confirm-by-typing.

{ "vdev_type": "mirror", "devices": ["/dev/sdd", "/dev/sde"] }

vdev_type: single, mirror, raidz1, raidz2, raidz3 (minimum device counts enforced). Returns Ansible task steps.

POST /api/pools/{pool}/cache · /log · /spare

Add auxiliary devices: L2ARC cache, SLOG log, or hot spare.

{ "devices": ["/dev/nvme0n1"] }

For /log, an optional "mirror": true mirrors the log devices (requires ≥ 2). Returns Ansible task steps.

DELETE /api/pools/{pool}/devices/{device}

Remove a cache, log, or spare device via zpool remove. zpool itself rejects removals that aren't legal for the given vdev. Returns Ansible task steps.


Devices & drive replacement

GET /api/devices

Returns the physical block devices on the host (Linux: /sys/block, skipping loop/ram/zvol/dm pseudo-devices; FreeBSD: geom disk list). in_use_by carries the pool name when the device currently backs a vdev (best-effort matching: by-id/by-path symlinks resolved, partition suffixes stripped).

[
  { "name": "sdb", "path": "/dev/sdb", "size_bytes": 4000787030016, "model": "WDC WD40EFRX", "in_use_by": "tank" },
  { "name": "sdc", "path": "/dev/sdc", "size_bytes": 4000787030016, "model": "WDC WD40EFRX", "in_use_by": "" }
]

POST /api/pools/{pool}/replace

Replace a vdev with a new device via zpool replace. A resilver onto the new device starts automatically; progress is visible in the pool's scan field (GET /api/poolstatus, poolstatus SSE topic).

{ "old_device": "sdb", "new_device": "/dev/sdc" }

old_device accepts a device name, path, or the numeric guid zpool status prints for missing devices. Returns Ansible task steps.

POST /api/pools/{pool}/offline

Take a device offline for maintenance via zpool offline. Body: { "device": "sdb" }. Returns Ansible task steps.

POST /api/pools/{pool}/online

Bring an offlined device back via zpool online. Body: { "device": "sdb" }. Returns Ansible task steps.


Pool scrub

POST /api/scrub/{pool}

Start a scrub on the named pool. Returns Ansible task steps.

DELETE /api/scrub/{pool}

Cancel a running scrub on the named pool. Returns Ansible task steps.

GET /api/scrub-schedules

Returns the current periodic scrub configuration for all pools.

{
  "mode": "zfsutils",
  "schedules": [
    { "pool": "tank" }
  ]
}

mode is "zfsutils" on Linux (managed via ZFS_SCRUB_POOLS in /etc/default/zfs) or "periodic" on FreeBSD (managed via daily_scrub_zfs_pools in /etc/periodic.conf). On FreeBSD, threshold_days is also returned (default 35). An empty schedules array means all pools are scrubbed by the platform default.

PUT /api/scrub-schedule/{pool}

Add a pool to the periodic scrub schedule. On FreeBSD, an optional threshold_days body field sets how many days must elapse before a scrub is triggered.

{ "threshold_days": 35 }

Returns Ansible task steps.

DELETE /api/scrub-schedule/{pool}

Remove a pool from the periodic scrub schedule. Returns Ansible task steps.


Auto-snapshot scheduling

Manages com.sun:auto-snapshot* ZFS user properties per dataset. These properties are consumed by zfs-auto-snapshot (Linux) or zfstools (FreeBSD) to automatically create and rotate snapshots. dumpstore sets/clears the properties; the external daemon handles snapshot creation.

Default behaviour — important

zfs-auto-snapshot uses an opt-out model: any dataset where com.sun:auto-snapshot is not explicitly set is snapshotted by default. Setting the property to false is how you exclude a dataset.

The recommended pattern for snapshotting only specific datasets:

# 1. Opt the entire pool out
zfs set com.sun:auto-snapshot=false tank

# 2. Opt specific datasets back in
zfs set com.sun:auto-snapshot=true tank/data
zfs set com.sun:auto-snapshot=true tank/home

Inspect current config via CLI

# All datasets, all 6 properties
zfs get com.sun:auto-snapshot,com.sun:auto-snapshot:frequent,com.sun:auto-snapshot:hourly,com.sun:auto-snapshot:daily,com.sun:auto-snapshot:weekly,com.sun:auto-snapshot:monthly -t filesystem,volume

# Recursively from a pool root
zfs get -r com.sun:auto-snapshot tank

# Only locally-set values (excludes inherited/default)
zfs get -r -s local com.sun:auto-snapshot tank

GET /api/auto-snapshot/{dataset}

Returns the current com.sun:auto-snapshot* property values and their source (local/inherited/default) for the given dataset.

{
  "com.sun:auto-snapshot":          { "value": "true",  "source": "local" },
  "com.sun:auto-snapshot:frequent": { "value": "4",     "source": "local" },
  "com.sun:auto-snapshot:hourly":   { "value": "24",    "source": "local" },
  "com.sun:auto-snapshot:daily":    { "value": "7",     "source": "local" },
  "com.sun:auto-snapshot:weekly":   { "value": "4",     "source": "local" },
  "com.sun:auto-snapshot:monthly":  { "value": "-",     "source": "default" }
}

A value of "-" with source of "default" means the property is not set (inherits system default).

PUT /api/auto-snapshot/{dataset}

Set or clear com.sun:auto-snapshot* properties on a dataset. Returns Ansible task steps.

Request body — any combination of these keys; omitted keys are left unchanged:

Key Values
com.sun:auto-snapshot "true", "false", or "" (inherit)
com.sun:auto-snapshot:frequent integer 1–9999, or "" (inherit)
com.sun:auto-snapshot:hourly integer 1–9999, or "" (inherit)
com.sun:auto-snapshot:daily integer 1–9999, or "" (inherit)
com.sun:auto-snapshot:weekly integer 1–9999, or "" (inherit)
com.sun:auto-snapshot:monthly integer 1–9999, or "" (inherit)

Empty string ("") triggers zfs inherit on the property (clears the local value).

{
  "com.sun:auto-snapshot": "true",
  "com.sun:auto-snapshot:daily": "7",
  "com.sun:auto-snapshot:monthly": "3"
}

iSCSI targets

Expose ZFS volumes as iSCSI targets. Uses targetcli/LIO on Linux or ctld on FreeBSD. Endpoints return 501 if no backend is detected.

GET /api/iscsi-targets

List all iSCSI targets backed by ZFS volumes.

[
  {
    "iqn": "iqn.2024-03.io.dumpstore:tank-vms-win11",
    "zvol_name": "tank/vms/win11",
    "zvol_device": "/dev/zvol/tank/vms/win11",
    "lun": 0,
    "portals": ["0.0.0.0:3260"],
    "auth_mode": "none",
    "initiators": []
  }
]

POST /api/iscsi-targets

Create an iSCSI target for a ZFS volume.

{
  "zvol": "tank/vms/win11",
  "iqn": "iqn.2024-03.io.dumpstore:tank-vms-win11",
  "portal_ip": "0.0.0.0",
  "portal_port": "3260",
  "auth_mode": "none",
  "chap_user": "",
  "chap_password": "",
  "initiators": []
}
Field Required Description
zvol yes ZFS volume name, must contain /
iqn yes RFC 3720 iSCSI Qualified Name (iqn.YYYY-MM.domain:name)
portal_ip no Listen IP, defaults to 0.0.0.0
portal_port no Listen port, defaults to 3260
auth_mode yes "none" or "chap"
chap_user when chap CHAP username
chap_password when chap CHAP password
initiators no Array of allowed initiator IQNs; empty = allow all

Returns Ansible task steps.

DELETE /api/iscsi-targets?iqn=<iqn>&zvol=<zvol>

Remove an iSCSI target and its backstore. Both query parameters are required.

Returns Ansible task steps.


Services

Manage the sharing daemons dumpstore controls (Samba, NFS, iSCSI). Status reads happen directly via systemctl/service — no Ansible overhead. Mutations (start/stop/restart/enable/disable) go through playbooks with op-log.

Logical service names: samba, nfs, iscsi, wsdd.

GET /api/services

Returns the status of all managed services.

[
  {
    "name": "samba",
    "display_name": "Samba (SMB)",
    "unit_name": "smbd",
    "active": true,
    "enabled": true,
    "state": "active",
    "installed": true
  }
]

state values: active, inactive, failed, unknown. installed is false when the unit file (Linux) or rc.d script (FreeBSD) is absent — unit_name is then the preferred candidate name.

POST /api/services/{name}/{action}

Control a service. Valid actions: start, stop, restart, enable, disable.

Returns Ansible task steps. 409 if the service is not installed on this host.


ACLs

GET /api/acl/{dataset}

Returns the ACL type and entries for the dataset's mountpoint.

{
  "dataset": "tank/data",
  "mountpoint": "/mnt/data",
  "acl_type": "posix",
  "entries": [
    { "tag": "user",  "qualifier": "",      "perms": "rwx", "default": false },
    { "tag": "user",  "qualifier": "alice", "perms": "r-x", "default": false },
    { "tag": "group", "qualifier": "",      "perms": "r-x", "default": false },
    { "tag": "mask",  "qualifier": "",      "perms": "rwx", "default": false },
    { "tag": "other", "qualifier": "",      "perms": "---", "default": false }
  ]
}

acl_type is one of "posix", "nfsv4", or "off".

For NFSv4 datasets each entry has the form:

{ "tag": "A", "flags": "fd", "qualifier": "OWNER@", "perms": "rwaDxtTnNcCoy" }

POST /api/acl/{dataset}

Add or modify an ACL entry. The ace string format depends on the dataset's acltype:

  • POSIX: setfacl -m spec — "user:alice:rwx", "group:storage:r-x", "default:user:alice:rwx"
  • NFSv4: full ACE string — "A::alice@localdomain:rwaDxtTnNcCoy"
{ "ace": "user:alice:rwx", "recursive": false }

recursive (POSIX only) applies setfacl -R to all files inside the mountpoint.

DELETE /api/acl/{dataset}?entry=<spec>

Remove an ACL entry. The entry query parameter:

  • POSIX: user:alice, default:group:storage
  • NFSv4: full ACE string to match

Append &recursive=true (POSIX only) to remove recursively.


SMB Home Shares

Manage the Samba [homes] section in smb.conf. When enabled, each authenticated user automatically gets a personal share mapped to a subdirectory under the configured base path.

GET /api/smb/homes

Returns the current [homes] configuration. If the section is not present, enabled is false and all fields are empty.

{
  "enabled": true,
  "path": "/tank/homes/%S",
  "browseable": "yes",
  "read_only": "no",
  "create_mask": "0644",
  "directory_mask": "0755"
}

POST /api/smb/homes

Enable or update the [homes] section. Returns Ansible task steps.

{
  "path": "/tank/homes/%S",
  "dataset": "tank/homes",
  "browseable": "yes",
  "read_only": "no",
  "create_mask": "0644",
  "directory_mask": "0755"
}
Field Required Description
path yes Base path for home directories (may include %S for username substitution)
dataset no ZFS dataset to use as base path (alternative to specifying path directly)
browseable yes "yes" or "no" — whether the share is visible in browse lists
read_only yes "yes" or "no"
create_mask no File creation mask (default "0644")
directory_mask no Directory creation mask (default "0755")

DELETE /api/smb/homes

Remove the [homes] section from smb.conf. Returns Ansible task steps.


Windows Discovery (wsdd)

wsdd announces the host in the Windows Network view. It advertises only the host and its workgroup, so dumpstore derives everything from smb.conf — there are no separate settings. GET /api/smb/status carries a wsdd object:

Field Description
installed wsdd unit (Linux) / rc.d script (FreeBSD) present
install_hint Distro install command (only when not installed)
unit, state, active, enabled Service status (wsdd-server on Debian/Ubuntu, else wsdd)
conf_path Options file dumpstore renders — the unit's EnvironmentFile; "" on FreeBSD
manageable Linux: the unit expands $WSDD_PARAMS; always true on FreeBSD
managed Options file carries dumpstore's header (FreeBSD: always true — rc.d reads smb4.conf)
workgroup Workgroup announced ("" → wsdd's default WORKGROUP; names with spaces/shell characters are not passed)

On Linux every Samba config write (smb_apply.yml) also renders the options file and restarts wsdd only if it was running and the file changed.

POST /api/smb/wsdd

Re-applies the current Samba config so the wsdd options file is rendered — for hosts where wsdd was installed after Samba was initialised. Returns Ansible task steps. 409 if Samba is not initialised, or wsdd is not installed / its unit does not read $WSDD_PARAMS.


Time Machine Shares

Manage Samba shares configured as macOS Time Machine backup targets using vfs_fruit with catia and streams_xattr VFS modules.

GET /api/smb/timemachine

List all Time Machine shares. Parses smb.conf for sections with fruit:time machine = yes.

[
  {
    "sharename": "timemachine",
    "path": "/tank/backups/timemachine",
    "max_size": "1T",
    "valid_users": "alice bob"
  }
]

POST /api/smb/timemachine

Create or update a Time Machine share. Returns Ansible task steps.

{
  "sharename": "timemachine",
  "dataset": "tank/backups",
  "path": "/tank/backups/timemachine",
  "max_size": "1T",
  "valid_users": "alice bob"
}
Field Required Description
sharename yes Samba share name
dataset no ZFS dataset (alternative to specifying path directly)
path yes Filesystem path for the share
max_size no Maximum size quota for Time Machine backups
valid_users no Space-separated list of allowed users

DELETE /api/smb/timemachine/{sharename}

Remove a Time Machine share from smb.conf. Returns Ansible task steps.


Server-Sent Events

GET /api/events

Subscribe to live data updates. The server pushes named events whenever data changes.

Query parameter: topics — comma-separated list of topics to subscribe to.

Available topics:

Topic Data Cadence
pool.query Same JSON as GET /api/pools Every 10 s on change
poolstatus Same JSON as GET /api/poolstatus Every 10 s on change
dataset.query Same JSON as GET /api/datasets Every 10 s on change
snapshot.query Same JSON as GET /api/snapshots Every 10 s on change
autosnapshot.query Same JSON as GET /api/auto-snapshot-schedules Every 10 s on change
iostat Same JSON as GET /api/iostat Every 10 s
user.query Same JSON as GET /api/users Every 10 s on change + after writes
group.query Same JSON as GET /api/groups Every 10 s on change + after writes
service.query Same JSON as GET /api/services Every 10 s on change
jobs.update Single job object (as GET /api/jobs/{id}) On every job state change
replication.update Same JSON as GET /api/replication On task create/update/delete + after each scheduled run
autosnap.status Same JSON as GET /api/auto-snapshot/status After takeover / release
ansible.progress Single TaskStep object Streamed during every write (Ansible and ops lanes)

Unknown topic names are ignored. The browser UI subscribes to all of them and falls back to 30 s REST polling (retrying SSE after 5 s) if the stream drops.

event: pool.query
data: [{"name":"tank","health":"ONLINE",...}]

event: iostat
data: [{"pool":"tank","read_ops":0,"write_ops":443,...}]

Example — watch pool health and I/O live:

curl -N 'http://localhost:8080/api/events?topics=pool.query,iostat'

Error responses

Non-2xx responses return:

{
  "error": "human-readable message",
  "tasks": [ ... ]
}

tasks is populated for Ansible-backed operations and contains the step results up to the point of failure.

Clone this wiki locally