- TypeScript 97.6%
- Svelte 2.4%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Core deprecated the fire-and-forget async route (core#21): long work is an
action the host owns, so it gets a persisted row, an actor, a lock and a
place in Activity instead of vanishing into a request that already returned.
/rescan re-probes host access and rebuilds every share from scratch — several
host processes plus a stat of each share root — so it qualifies. It becomes a
ctx.tasks.define action with two steps and a keyed progress row per share;
the route only starts the run and answers { runId }.
The action is `console: true` because it takes no arguments, which finally
gives the route a caller: nothing in the schema was bound to it, so until now
the only way to reach it was curl.
`expose` names entities and result only. Counts and share rows are structure;
host access failures name the probe strategy that failed, so they stay in the
withheld log.
No schedule and no onInterrupted retry: the 60s collector already refreshes
this state, so a schedule would duplicate it and re-running an interrupted
read would only add noise.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
| backend | ||
| frontend | ||
| CLAUDE.md | ||
| opsdeck.module.json | ||
| README.md | ||
OpsDeck module: Network Shares
Create and manage SMB and NFS shares on the OpsDeck host.
This is a management module, not a monitor. The activity views exist to answer "is anyone using this right now" before you change something — they are not the point.
The idea
A share is a directory that people can reach. SMB and NFS are two ways to reach it, not two separate objects.
Tools that model them separately (cockpit-file-sharing among them) list the
same directory in two tabs, configure it twice, and let the two definitions
drift until the same file is writable one way and read-only the other. Here
there is one share, with smb and nfs facets you toggle on it, and
contradictions between them are reported instead of hidden.
Three more consequences of that stance:
- Presets, not a wall of fields. "Private", "Household read-write", "Read-only on the LAN", "Media library", "Time Machine". Each expands to the eight parameters that actually implement it. Raw parameters remain editable.
- Effective access. Share config is a filter on top of POSIX, never an
override.
read only = noover a0755 root:rootdirectory is the single most common share bug, and the resulting error names neither Samba nor the directory. The share page states the contradiction in plain language and offers the exact fix (chgrp+chmod 2775+ setgid). - Connect strings. Every share shows its
\\host\name,smb://…andmount -t nfs …lines, ready to paste. - Samba passwords are not login passwords. Samba stores NT hashes in its
own database (
tdbsam), so a user whose SSH password works can still be refused by SMB with a bare "logon failure" — they simply have no Samba password. The Users page gives an existing unix account one, changes it, and enables, disables or removes it, and says plainly that the two are separate.rootandnobodyare offered alongside the real users — root for an administrative share, nobody becauseguest accountmaps to it — each labelled with what it means rather than quietly filtered out.
How it writes configuration
| mechanism | why | |
|---|---|---|
| SMB | net conf (registry) |
The host's smb.conf is [global] include = registry, so the registry is the config. Transactional, live pickup without a reload, no text file to parse or clobber. |
| NFS | /etc/exports.d/opsdeck.exports + exportfs -ra |
One file we own and regenerate wholesale. /etc/exports is never touched, so hand-written exports keep working. |
Reads (share lists, directory permissions, /etc/passwd) go through the
host-root bind mount and need no privilege. Writes and daemon control run in
the host's namespaces — see Requirements.
Requirements
The OpsDeck container needs the host-root bind mount (/ → /host/root),
writable — configuration is written on the host. Nothing else: the default
CAP_SYS_CHROOT is enough, and no pid: host.
At startup the module probes four ways onto the host and keeps the first that
works, testing whether the host's net/exportfs are actually reachable
rather than whether the wrapper merely exited 0:
| strategy | how | notes |
|---|---|---|
chroot |
chroot /host/root … |
what the console module already uses; works under Docker's default profile |
nsfile |
nsenter --mount=<hostProc>/1/ns/mnt … |
needs ptrace access to host PID 1, which default AppArmor denies |
pidns |
nsenter -t 1 … |
only for containers sharing the host PID namespace |
direct |
run it | bare metal |
If none work it degrades to read-only and says so on the overview page;
mutations answer 503 with the reason.
Optional: pid: host
Two features identify a process by pid, which means nothing from a container with its own PID namespace — and no chroot trick changes that, because the obstacle is peer credentials, not the filesystem:
| feature | without pid: host |
why |
|---|---|---|
| Disconnect clients | Can't find pid for destination 'smbd' |
smbcontrol signals smbd by pid |
| Start/stop Samba or NFS | Failed to connect to bus: No data available |
as root systemctl uses /run/systemd/private, and PID 1 rejects a peer whose credentials it cannot resolve; the dbus system bus authorises the same way |
Adding pid: host to the container fixes both, needs no extra capabilities,
and is otherwise unnecessary — everything else, including all configuration,
works without it. The module detects which case it is in and reports it as
"Service control" on the overview page.
On the host: samba (any version with net conf), and nfs-kernel-server if
you want NFS — the module offers to systemctl enable --now nfs-server when it
finds it installed but stopped.
API
| Method | Path | Purpose |
|---|---|---|
| GET | /summary |
counts, service states, whether management is available |
| GET | /shares |
every share, both protocols merged |
| GET | /share/:id |
one share + effective-access report + live sessions |
| GET | /presets, /principals, /users, /sessions |
pickers and activity |
| POST | /shares |
create — preset form, flat form keys, or full {smb, nfs} facets |
| POST | /share/:id |
update; a partial flat body edits, omitting a facet removes that protocol |
| POST | /share/:id/delete |
remove the share; the directory is left alone |
| POST | /share/:id/fix |
apply the suggested ownership/permission changes |
| POST | /share/:id/disconnect |
close every open handle on the share |
| POST | /preview |
dry run: the exact commands a change would execute |
| GET | /services, /unix-users |
the daemons this module can start and stop; unix accounts eligible for a Samba password |
| POST | /service/:id/enable, /service/:id/disable |
samba (smbd + nmbd) or nfs (nfs-server), persistently |
| POST | /users, /user/:name/password |
give a user a Samba password, or change it |
| POST | /user/:name/enable, /user/:name/disable, /user/:name/delete |
Samba account control |
All mutations require the admin role.
Pages
Everything renders from the portable UI schema, so the web shell and the Android app show the same thing — including creating and editing:
| page | what |
|---|---|
| `` | counts, service states, every share, "New share" |
new |
create form: name, folder, access preset, people, network |
share/:id |
facets, effective-access report, connect strings, live sessions, fix/disconnect/delete |
share/:id/edit |
protocol facets as flat controls, prefilled from the share |
clients |
SMB sessions, open files, NFS clients |
users, user/:name |
Samba accounts, enable/disable |
This needs core with the schema form block and navigating actions
({ label, page }) — both added alongside this module.
Development
cp -r . <core>/packages/modules/shares
cd <core>
OPSDECK_AUTH=disabled OPSDECK_MODULES=shares deno task start
curl localhost:8080/api/mod/shares/summary
curl localhost:8080/api/core/modules/shares/ui # schema; invalid = module disabled
Remove the copy afterwards — this repo is the source of truth.