From a4849616d9f8e1b053adfafe46ee7f788f66b2cc Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 11 Aug 2026 16:15:02 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20pos=20share=20smb=20server=20+=20smb=20?= =?UTF-8?q?client=20=E2=80=94=20Samba=20share=20tools=20(samba/cifs-utils?= =?UTF-8?q?=20deps,=20systemd=20automount=20units)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 2 +- AGENT_TODO.md | 2 + DOC/AGENT_Context_Project.md | 35 +++--- DOC/HOWTO.md | 2 +- DOC/POS.md | 4 +- DOC/howto/share.md | 103 +++++++++++++++- bin/pos | 6 +- bin/pos-share-smb-client | 183 ++++++++++++++++++++++++++++ bin/pos-share-smb-server | 226 +++++++++++++++++++++++++++++++++++ preinstall.sh | 1 + 10 files changed, 545 insertions(+), 19 deletions(-) create mode 100755 bin/pos-share-smb-client create mode 100755 bin/pos-share-smb-server diff --git a/AGENTS.md b/AGENTS.md index 7c04fa7..f87d403 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,7 +15,7 @@ CRITICAL: real guidance lives in DOC/. When you encounter a reference below, use ## Quick facts - **Tool model:** `bin/pos--`, or **category-less** `bin/pos-` for dispatcher/dev-level commands that fit no category (`pos-config`, `pos-tree`) — they dispatch like any tool and show with an empty category in the generated tables. `bin/pos` dispatches by longest-prefix arg matching. New tools are auto-discovered but must be executable (`100755`) and carry a `# POS: ` header right after the shebang; `make gen` only uses the text after the first `— ` (the leading words are convention-only), so keep the one-line description concise. `# POS_FLAGS:` / `# POS_SUBCMDS:` / `# POS_CONFIG:` headers feed tab-completion and the `pos config` scope registry. A missing `# POS:` header hard-fails `make gen`. Legacy `bin/wr-*`, `mp3`, `mp4`, `vbox`, `ssh-load-all` are thin forwarders to `pos` — keep them that way. -- **Categories:** `ai`, `communication`, `docker`, `entertainment`, `media`, `network`, `share` (usb, nfs — smb planned), `ssh`, `system`, plus category-less `config`/`tree`. `pos tree` (bin/pos-tree) is the authoritative structure — it derives the hierarchy from `bin/pos-*` filenames + `# POS:`/`# POS_SUBCMDS:` headers. +- **Categories:** `ai`, `communication`, `docker`, `entertainment`, `media`, `network`, `share` (usb, nfs, smb), `ssh`, `system`, plus category-less `config`/`tree`. `pos tree` (bin/pos-tree) is the authoritative structure — it derives the hierarchy from `bin/pos-*` filenames + `# POS:`/`# POS_SUBCMDS:` headers. - **Generated code:** blocks between `GEN:START`/`GEN:END` markers in `DOC/AGENT_Context_Project.md` (tree, dispatch, selfcontained, filetable, docmap) and `completions/pos.bash` (flags, subcmds, config scopes) are `make gen` output — never hand-edit them. After touching `bin/pos-*`, run `make gen` then `make check` (bash -n + exec-bit check + doc-sync gate + dispatch smoke; definition of done). Hand-maintained, not gen-checked: `DOC/POS.md`, the line-count rows above the filetable marker (e.g. `lib/common.sh`), `bin/pos` usage() EXAMPLES, root README. There is no CI — `make check` (`scripts/check-sync.sh`) is the only gate. - **Stdin gotcha:** any tool that reads stdin must be added to `INTERACTIVE_CMDS` in `bin/pos` — otherwise the logging `tee` pipe hangs on (or swallows) the prompt. - **Deps:** apt packages → `PACKAGES` array in `preinstall.sh`; non-apt/manual installers (e.g. `usbsrv`) → `command -v || err "…"` guard inside the tool, never in PACKAGES. diff --git a/AGENT_TODO.md b/AGENT_TODO.md index 057549a..b4b7ba7 100644 --- a/AGENT_TODO.md +++ b/AGENT_TODO.md @@ -16,6 +16,8 @@ summary (newest last). ## Done +- **2026-08-11** — `share` category grows SMB: `pos share smb server` (`bin/pos-share-smb-server`) + `pos share smb client` (`bin/pos-share-smb-client`), completing the share trio (usb/nfs/smb). **Server:** `status`/`share`/`unshare`/`list`/`adduser`/`deluser`/`reload`/`enable`/`disable`; idempotent marker blocks in `/etc/samba/smb.conf` (`# >>> pos-managed share: ` … `# <<< end pos-managed share` — hand edits outside markers survive; `inblock==1`-guarded awk so removing one block never eats another's end marker), `testparm` validation before apply + `smbcontrol smbd reload-config` hot reload; `--read-only`/`--guest`/`--users u1,u2` flags with unrestricted-share warnings; `smbpasswd` user management (prompts, requires system user first). **Client:** `mount`/`unmount`/`list`/`persist`/`unpersist`; password prompt via `/dev/tty`, throwaway chmod-600 credentials for one-shot mounts, persistent creds at `/etc/samba/credentials/` (chmod 600); `persist` writes a systemd `.mount` unit (`systemd-escape`) with `x-systemd.automount` + `_netdev` — mounts on first access, never blocks boot. Both source `lib/notify.sh` for mutations; added to `INTERACTIVE_CMDS` (prompting subcommands). Deps: `samba` + `cifs-utils` added to preinstall PACKAGES. `SMB_CONF`/`SMB_CREDS_DIR`/`UNIT_DIR` env-overridable for tests (FLAGS_DIR precedent). Docs: POS.md share rows, howto/share.md SMB sections, HOWTO index row, AGENT_Context Common Tasks, AGENTS.md categories. `make gen && make check` green; logic tested via stubbed PATH + temp config (marker idempotency, guest + user persist flows). + - **2026-08-11** — `pos network checkport` nmap overhaul: two-pass engine — pass 1 = fast `-Pn -T4 --max-retries 1` scan of only the asked ports (was: all 65535) with per-port state + nmap service names; pass 2 (`--versions`, opt-in) = `-sV --version-light` on open ports only (generous host-timeout — version probing a silent service otherwise made nmap skip the host entirely), fallback fast banner probe for open TCP with no version info; TCP fast path ~2s for 3 ports. Unprivileged UDP now falls back to the nc engine (Debian nmap `-sU` requires root and quit outright); IPv6 hosts get `-6`; `no output`/filtered states set rc=1; `--timeout` scales nmap host-timeouts. New `--versions` flag in `# POS_FLAGS:` (completions regenerated) + usage text; port-metadata fallback retained. `make gen && make check` green. - **2026-08-11** — `pos communication matrix sender login` error reporting: captures HTTP status + Matrix `errcode`/`error` from the JSON body (temp file, not stdout) instead of a generic "wrong credentials?" message — distinguishes unreachable homeserver from rejected credentials; auto-prepends `@` when `--user` is bare (e.g. `--user alice:example.org` → `@alice:example.org`). diff --git a/DOC/AGENT_Context_Project.md b/DOC/AGENT_Context_Project.md index f53d08c..8207355 100644 --- a/DOC/AGENT_Context_Project.md +++ b/DOC/AGENT_Context_Project.md @@ -10,19 +10,19 @@ | ## 1. Project Overview | 28–43 | -| ## 2. Directory Structure | 44–186 | -| ## 3. Installation Flow | 187–239 | -| ## 4. The `pos` CLI System | 240–306 | -| ## 5. Shared Library — `lib/common.sh` | 307–338 | -| ## 6. Docker Compose / ScaleTail | 339–381 | -| ## 7. Optional Apps (`apps/`) | 382–411 | -| ## 8. Entertainment Module | 412–425 | -| ## 9. Systemd Services | 426–438 | -| ## 10. Configuration Files | 439–465 | -| ## 11. Coding Conventions | 466–498 | -| ## 12. Development Workflow | 499–551 | -| ## 13. Key File Quick Reference | 552–605 | -| ## 14. Common Tasks for Agents | 606–632 | +| ## 2. Directory Structure | 44–188 | +| ## 3. Installation Flow | 189–241 | +| ## 4. The `pos` CLI System | 242–310 | +| ## 5. Shared Library — `lib/common.sh` | 311–342 | +| ## 6. Docker Compose / ScaleTail | 343–385 | +| ## 7. Optional Apps (`apps/`) | 386–415 | +| ## 8. Entertainment Module | 416–429 | +| ## 9. Systemd Services | 430–442 | +| ## 10. Configuration Files | 443–469 | +| ## 11. Coding Conventions | 470–502 | +| ## 12. Development Workflow | 503–555 | +| ## 13. Key File Quick Reference | 556–611 | +| ## 14. Common Tasks for Agents | 612–639 | ## 1. Project Overview @@ -81,6 +81,8 @@ Linux_post_install/ │ ├── pos-network-scan # Parallel ping sweep of CIDR │ ├── pos-share-nfs-client # Mount NFS shares (ephemeral or persistent systemd mount units) │ ├── pos-share-nfs-server # Manage the NFS kernel server (status, share/unshare exports, enable/disable) +│ ├── pos-share-smb-client # Mount SMB/CIFS shares (ephemeral or persistent systemd mount units) +│ ├── pos-share-smb-server # Manage the Samba server (status, share/unshare exports, users, enable/disable) │ ├── pos-share-usb-server # USB Redirector server control (--ls, --share; prompts when args omitted) │ ├── pos-ssh-load-keys # Load all SSH keys into the agent │ ├── pos-system-backup # Encrypted (AES-256) folder snapshots (tar + gpg) @@ -279,6 +281,8 @@ All non-interactive `pos` commands log output to `~/.local/share/linux_post_inst | network | scan | `pos-network-scan` | Parallel ping sweep of CIDR | | share | nfs-client | `pos-share-nfs-client` | Mount NFS shares (ephemeral or persistent systemd mount units) | | share | nfs-server | `pos-share-nfs-server` | Manage the NFS kernel server (status, share/unshare exports, enable/disable) | +| share | smb-client | `pos-share-smb-client` | Mount SMB/CIFS shares (ephemeral or persistent systemd mount units) | +| share | smb-server | `pos-share-smb-server` | Manage the Samba server (status, share/unshare exports, users, enable/disable) | | share | usb-server | `pos-share-usb-server` | USB Redirector server control (--ls, --share; prompts when args omitted) | | ssh | load-keys | `pos-ssh-load-keys` | Load all SSH keys into the agent | | system | backup | `pos-system-backup` | Encrypted (AES-256) folder snapshots (tar + gpg) | @@ -566,7 +570,7 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:` | `bin/flag-clear` | 21 | Unset a flag | | `features/autostart.sh` | 14 | Boot-time feature (moved from `bin/`, flag-gated service) | -| `bin/pos` | 286 | CLI dispatcher with smart arg matching + logging + category help | +| `bin/pos` | 290 | CLI dispatcher with smart arg matching + logging + category help | | `bin/pos-ai-gemini` | 311 | Chat with Google Gemini (ask, chat, models, sessions) | | `bin/pos-communication-matrix-listener` | 565 | Matrix listener: map /command → bash, run them on room messages | | `bin/pos-communication-matrix-sender` | 224 | Send messages to a Matrix room via the client-server API (send, test, login) | @@ -590,6 +594,8 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:` | `bin/pos-network-scan` | 271 | Parallel ping sweep of CIDR | | `bin/pos-share-nfs-client` | 138 | Mount NFS shares (ephemeral or persistent systemd mount units) | | `bin/pos-share-nfs-server` | 134 | Manage the NFS kernel server (status, share/unshare exports, enable/disable) | +| `bin/pos-share-smb-client` | 183 | Mount SMB/CIFS shares (ephemeral or persistent systemd mount units) | +| `bin/pos-share-smb-server` | 226 | Manage the Samba server (status, share/unshare exports, users, enable/disable) | | `bin/pos-share-usb-server` | 218 | USB Redirector server control (--ls, --share; prompts when args omitted) | | `bin/pos-ssh-load-keys` | 31 | Load all SSH keys into the agent | | `bin/pos-system-backup` | 126 | Encrypted (AES-256) folder snapshots (tar + gpg) | @@ -624,6 +630,7 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:` | Modify vbox (Docker VM) logic | Edit `bin/pos-docker-vbox` | | Modify USB forwarding logic | Edit `bin/pos-share-usb-server` | | Modify NFS share logic | Edit `bin/pos-share-nfs-server` / `bin/pos-share-nfs-client` | +| Modify SMB share logic | Edit `bin/pos-share-smb-server` / `bin/pos-share-smb-client` | | Modify AI/Gemini logic | Edit `bin/pos-ai-gemini` (config scope `ai` via `pos config ai`; `AI_GEMINI_API_KEY`/`AI_GEMINI_MODEL` in `~/.config/linux_post_install/ai.env`) | | Modify UFW/firewall logic | Edit `bin/pos-system-firewall` | | Modify pos logging | Edit log setup in `bin/pos` | diff --git a/DOC/HOWTO.md b/DOC/HOWTO.md index a15191c..0096f05 100644 --- a/DOC/HOWTO.md +++ b/DOC/HOWTO.md @@ -16,7 +16,7 @@ authoritative one-line reference (every command + flag), see | `pos system` | Backups, firewall, health dashboard | [system](howto/system.md) | | `pos system event-trigger` | Threshold-rule monitors that alert on crossing | [event-trigger](howto/event-trigger.md) | | `pos ssh` | Load keys into the agent | [ssh](howto/ssh.md) | -| `pos share` | Share USB devices & filesystems over the network (USB, NFS) | [share](howto/share.md) | +| `pos share` | Share USB devices & filesystems over the network (USB, NFS, SMB) | [share](howto/share.md) | | `pos communication` | Send Telegram/Matrix messages & alerts, /command listeners | [communication](howto/communication.md) | | `pos entertainment` | Scheduled auto-messages from public APIs | [entertainment](howto/entertainment.md) | diff --git a/DOC/POS.md b/DOC/POS.md index 8c4a784..f76dbd8 100644 --- a/DOC/POS.md +++ b/DOC/POS.md @@ -197,7 +197,7 @@ The standalone `vbox` command still works and forwards to `pos docker vbox` (see ### share -Share files and devices over the network (USB over network, NFS; SMB planned). +Share files and devices over the network (USB over network, NFS, SMB/Samba). **File:** `bin/pos-share-usb-server` **Purpose:** control the USB Redirector server (`usbsrv`) — share local USB devices over the network and manage connected clients. Requires `usbsrv` (manual install from incentivespro.com — not in `PACKAGES`). @@ -224,6 +224,8 @@ Subcommands that need input prompt interactively when args are omitted. |---------|------|---------|---------------| | `pos share nfs server ` | `bin/pos-share-nfs-server` | Manage the NFS kernel server: `status`, `share [client]`, `unshare `, `list`, `reload`, `enable`, `disable` | Requires `nfs-kernel-server` (added to `preinstall.sh` PACKAGES). Exports live in `/etc/exports`; `share` is idempotent (replaces any existing line for the path) and runs `exportfs -ra`. Default client `*(rw,sync,no_subtree_check)` — the tool warns you to restrict it; help prints Tailscale CGNAT (`100.64.0.0/10`), WireGuard (`10.10.0.0/24`) and LAN examples. Mutating commands announce via `lib/notify.sh` | | `pos share nfs client ` | `bin/pos-share-nfs-client` | Mount and manage NFS shares: `mount `, `unmount `, `list`, `persist `, `unpersist ` | Requires `nfs-common` (added to `preinstall.sh` PACKAGES). `persist` writes a systemd `.mount` unit (`systemd-escape --path --suffix=mount`) with `After=network-online.target` / `Wants=network-online.target` — mounts only once all interfaces are up, no fstab edits to break boot — then `daemon-reload` + `enable --now`. `unpersist` stops/disables/removes the unit. `mount`/`persist` announce via `lib/notify.sh` | +| `pos share smb server ` | `bin/pos-share-smb-server` | Manage the Samba server: `status`, `share [name] [--read-only|--guest|--users u1,u2]`, `unshare `, `list`, `adduser `, `deluser `, `reload`, `enable`, `disable` | Requires `samba` (added to `preinstall.sh` PACKAGES). Shares are idempotent marker blocks (`# >>> pos-managed share: ` … `# <<< end pos-managed share`) in `/etc/samba/smb.conf` — hand edits outside the markers survive; `share` validates with `testparm` before applying and hot-reloads via `smbcontrol smbd reload-config`. Defaults rw + browsable; warns when unrestricted (guest or no `valid users`). `adduser`/`deluser` manage Samba accounts via `smbpasswd`. Mutating commands announce via `lib/notify.sh` | +| `pos share smb client ` | `bin/pos-share-smb-client` | Mount and manage SMB/CIFS shares: `mount [user]`, `unmount `, `list`, `persist [user]`, `unpersist ` | Requires `cifs-utils` (added to `preinstall.sh` PACKAGES). With a user you are prompted for the Samba password — one-shot mounts use a throwaway chmod-600 credentials file, `persist` keeps one at `/etc/samba/credentials/` (chmod 600). `persist` writes a systemd `.mount` unit (`systemd-escape --path --suffix=mount`) with `x-systemd.automount` + `_netdev` — mounts on first access, never blocks boot — then `daemon-reload` + `enable --now`. `unpersist` stops/disables/removes the unit + credentials. `mount`/`persist` announce via `lib/notify.sh` | ### communication diff --git a/DOC/howto/share.md b/DOC/howto/share.md index 93f1826..5a49242 100644 --- a/DOC/howto/share.md +++ b/DOC/howto/share.md @@ -1,13 +1,15 @@ # How-To: `pos share` Share files and devices over the network: USB devices via the USB Redirector -server, filesystems via NFS (SMB planned). Tools: `usb`, `nfs`. +server, filesystems via NFS and SMB/Samba. Tools: `usb`, `nfs`, `smb`. | Tool | What it does | |------|--------------| | `pos share usb server` | Control `usbsrv`: share USB devices, manage clients, callbacks, nicknames | | `pos share nfs server` | Manage the NFS kernel server (exports, enable/disable) | | `pos share nfs client` | Mount NFS shares (ephemeral or persistent systemd units) | +| `pos share smb server` | Manage the Samba server (shares, users, enable/disable) | +| `pos share smb client` | Mount SMB/CIFS shares (ephemeral or persistent systemd units) | --- @@ -149,6 +151,105 @@ up — a down/unreachable NFS server can't break boot (with fstab it could). --- +## `pos share smb server` — Samba server + +Requires `samba` (in `preinstall.sh` PACKAGES). Writes idempotent share blocks +to `/etc/samba/smb.conf` (between `# >>> pos-managed share: ` / +`# <<< end pos-managed share` markers — anything outside the markers survives), +validates with `testparm`, and hot-reloads via `smbcontrol smbd reload-config`. +Mutating commands announce via `notify_send`. + +```bash +pos share smb server status # smbd active? + shares + users +pos share smb server share /mnt/hdd media # share (default name: basename) +pos share smb server share /mnt/hdd media --users bob,alice # restrict to Samba users +pos share smb server share /mnt/hdd/backups --read-only # read-only +pos share smb server share /mnt/public --guest # guest access (warns) +pos share smb server list # current shares +pos share smb server unshare media # remove a share +pos share smb server adduser bob # create a Samba user (prompts) +pos share smb server deluser bob # remove a Samba user +pos share smb server reload # validate + reload after hand edits +pos share smb server enable / disable # start + boot-persist smbd / stop it +``` + +New shares default to read-write + browsable. `--guest` and shares without +`--users` both **warn** — any Samba account (or any network user with guest) +can then access them; print the restricted form with `--users`. + +SMB shares need Samba accounts, not just system users: `adduser ` +(prompts for the password via `smbpasswd -a`) after the system user exists. + +**Recipes:** +- **Share the media drive to the tailnet (users bob + alice):** + ```bash + sudo adduser bob # system user first + pos share smb server adduser bob # then a Samba password + pos share smb server share /mnt/hdd media --users bob,alice + pos share smb server enable + ``` +- **Public read-only download share:** `pos share smb server share /srv/pub pub --read-only --guest` +- **Change a share's access later:** re-run `share` with the same name — the + block is replaced, not duplicated. + +**Troubleshooting:** +- "smbd not found" → `samba` isn't installed; `sudo apt install samba` +- Windows can't connect → check the client is in `--users` / has a Samba + password (`adduser`), and that `smbd` is running (`status`) +- `valid users` users can't log in → their Samba password differs from the + system one; re-run `pos share smb server adduser ` +- After editing `/etc/samba/smb.conf` by hand, run `pos share smb server reload` +- SMB is blocked → allow Samba in `pos system firewall` (or `ufw allow samba`) + +--- + +## `pos share smb client` — mount SMB/CIFS shares + +Requires `cifs-utils` (in `preinstall.sh` PACKAGES). + +```bash +pos share smb client mount //100.100.100.1/media /mnt/smb/media # guest +pos share smb client mount //100.100.100.1/media /mnt/smb/media bob # prompts for password +pos share smb client persist //100.100.100.1/media /mnt/smb/media bob # persistent (systemd) +pos share smb client list # active SMB mounts +pos share smb client unmount /mnt/smb/media +pos share smb client unpersist /mnt/smb/media # remove the unit +``` + +With no user, a **guest** mount is attempted (only works if the server allows +guest access). With a user you are prompted for the Samba password: one-shot +mounts use a throwaway chmod-600 credentials file, `persist` keeps one at +`/etc/samba/credentials/` (chmod 600) and references it from the unit. + +**Persistent mounts use systemd, not fstab.** `persist` writes a +`/etc/systemd/system/.mount` unit (`systemd-escape`) with +`x-systemd.automount` + `_netdev`: the share is mounted **on first access** +instead of at boot, so an unreachable SMB server can never hang boot (with +fstab it could). `enable --now` arms the automount immediately. + +**Recipes:** +- **Mount the server's media share and keep it across reboots:** + ```bash + pos share smb client persist //100.100.100.1/media /mnt/smb/media bob + ``` +- **One-off guest mount (no persistence):** + `pos share smb client mount //10.0.0.5/pub /mnt/pub` +- **Check what a server shares before mounting:** `smbclient -L //10.0.0.5 -N` + (or with `-U bob`) + +**Troubleshooting:** +- "mount.cifs not found" → `cifs-utils` isn't installed; `sudo apt install cifs-utils` +- Mount fails with `Permission denied` / `NT_STATUS_LOGON_FAILURE` → wrong Samba + user/password; verify the account with `pos share smb server list` on the + server and re-run with the right user +- Mount fails with `NT_STATUS_ACCESS_DENIED` on a guest mount → the server + share has no `guest ok`; use a user or add `--guest` on the server +- Persistent mount doesn't appear until accessed → intended (`x-systemd.automount`); + `pos share smb client list` only shows actively mounted shares, access the + directory to trigger the mount + +--- + ## Related - Reference: [DOC/POS.md → share](../POS.md) diff --git a/bin/pos b/bin/pos index d964a58..e67fdea 100755 --- a/bin/pos +++ b/bin/pos @@ -157,6 +157,10 @@ EXAMPLES pos share nfs server share /mnt/hdd Share a folder via NFS pos share nfs client persist 10.0.0.5:/srv/data /mnt/nfs/data Persistent NFS mount (systemd) + pos share smb server share /mnt/hdd media --users bob + Share a folder via SMB (Samba) + pos share smb client persist //10.0.0.5/media /mnt/smb/media bob + Persistent SMB mount (automount) pos ssh load-keys Load all SSH keys into agent @@ -250,7 +254,7 @@ MAIN_LOG="$LOG_DIR/pos.log" log_cmd() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $* → exit $2" >> "$MAIN_LOG"; } # Commands that read from stdin interactively — only log invocation -INTERACTIVE_CMDS="system-firewall media-mp4 system-backup share-usb-server communication-telegram-listener communication-matrix-listener ai-gemini system-event-trigger config" +INTERACTIVE_CMDS="system-firewall media-mp4 system-backup share-usb-server share-smb-server share-smb-client communication-telegram-listener communication-matrix-listener ai-gemini system-event-trigger config" for ((i=n-1; i>=0; i--)); do cmd="pos" diff --git a/bin/pos-share-smb-client b/bin/pos-share-smb-client new file mode 100755 index 0000000..aae6eb6 --- /dev/null +++ b/bin/pos-share-smb-client @@ -0,0 +1,183 @@ +#!/usr/bin/env bash +set -euo pipefail +# POS: share smb-client — Mount SMB/CIFS shares (ephemeral or persistent systemd mount units) + +source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh" +source "$(dirname "$0")/../lib/notify.sh" 2>/dev/null || source "$(dirname "$0")/notify.sh" + +SMB_CREDS_DIR="${SMB_CREDS_DIR:-/etc/samba/credentials}" +UNIT_DIR="${UNIT_DIR:-/etc/systemd/system}" +command -v mount.cifs &>/dev/null || err "mount.cifs not found (install cifs-utils)" +command -v systemd-escape &>/dev/null || err "systemd-escape not found" + +usage() { + cat < [args] + +Mount and manage SMB/CIFS shares from remote servers (cifs-utils). + +Commands: + mount [user] One-shot mount (creates local-dir if needed) + unmount Unmount the share + list Show active SMB mounts + persist [user] Persistent mount via a systemd .mount unit + (automount — never blocks boot) + unpersist Stop, disable and remove the mount unit + +With no user, guest access is attempted. With a user, you are prompted for +the Samba password — one-shot mounts use a throwaway chmod-600 credentials +file; persistent mounts keep one at /etc/samba/credentials/. + +Examples: + pos share smb-client mount //100.100.100.1/media /mnt/smb/media + pos share smb-client persist //100.100.100.1/media /mnt/smb/media bob + pos share smb-client list + pos share smb-client unmount /mnt/smb/media +EOF + exit 0 +} + +cmd="${1:-}" +case "$cmd" in + -h|--help) usage ;; + mount|unmount|list|persist|unpersist) ;; + "") err "Missing command (mount|unmount|list|persist|unpersist)" ;; + *) err "Unknown command '$cmd' (see --help)" ;; +esac + +validate_share() { + case "$1" in + //*) ;; + *) err "Invalid share '$1' — expected (e.g. //10.0.0.5/media)" ;; + esac +} + +validate_dir() { + case "$1" in + /*) ;; + *) err "Mount point must be an absolute path: $1" ;; + esac +} + +# Prompt for the Samba password (no echo, via /dev/tty) and write a fresh +# chmod-600 credentials file; prints its path, caller removes it. +make_creds() { + local user="$1" pw tmp + read -rsp "Samba password for $user: " pw &2 + [ -n "$pw" ] || err "empty password" + tmp="$(mktemp)" + chmod 600 "$tmp" + printf 'username=%s\npassword=%s\n' "$user" "$pw" > "$tmp" + printf '%s' "$tmp" +} + +mount_opts() { + local user="$1" + printf 'uid=%s,gid=%s' "$(id -u)" "$(id -g)" +} + +case "$cmd" in + mount) + what="${2:-}" + where="${3:-}" + user="${4:-}" + [ -n "$what" ] && [ -n "$where" ] || err "Usage: pos share smb-client mount [user]" + validate_share "$what" + validate_dir "$where" + + sudo mkdir -p "$where" + if [ -n "$user" ]; then + creds="$(make_creds "$user")" + trap 'rm -f "$creds"' EXIT + sudo mount -t cifs "$what" "$where" -o "credentials=$creds,$(mount_opts "$user")" + else + warn "No user — attempting guest mount (works only if the server allows guest access)" + sudo mount -t cifs "$what" "$where" -o "guest,$(mount_opts "")" + fi + log "Mounted $what at $where" + ;; + + unmount) + where="${2:-}" + [ -n "$where" ] || err "Usage: pos share smb-client unmount " + validate_dir "$where" + + sudo umount "$where" + log "Unmounted $where" + ;; + + list) + if findmnt -t cifs >/dev/null 2>&1; then + findmnt -t cifs + else + echo "No SMB mounts" + fi + ;; + + persist) + what="${2:-}" + where="${3:-}" + user="${4:-}" + [ -n "$what" ] && [ -n "$where" ] || err "Usage: pos share smb-client persist [user]" + validate_share "$what" + validate_dir "$where" + + unit="$(systemd-escape --path --suffix=mount "$where")" + unit_file="${UNIT_DIR}/${unit}" + opts="$(mount_opts "$user"),_netdev,noexec,x-systemd.automount" + if [ -n "$user" ]; then + creds_file="$SMB_CREDS_DIR/$(basename "$where")" + sudo mkdir -p "$SMB_CREDS_DIR" + tmp="$(make_creds "$user")" + sudo install -m 600 "$tmp" "$creds_file" + rm -f "$tmp" + opts="credentials=$creds_file,$opts" + else + warn "No user — persisting a guest mount (works only if the server allows guest access)" + opts="guest,$opts" + fi + + sudo mkdir -p "$where" + cat </dev/null +[Unit] +Description=SMB mount of ${what} at ${where} +After=network-online.target +Wants=network-online.target + +[Mount] +What=${what} +Where=${where} +Type=cifs +Options=${opts} +UNIT + sudo systemctl daemon-reload + sudo systemctl enable --now "$unit" + log "Persistent SMB mount: ${what} → ${where} (${unit})" + notify_send "SMB mount persisted: ${what} → ${where}" + ;; + + unpersist) + where="${2:-}" + [ -n "$where" ] || err "Usage: pos share smb-client unpersist " + validate_dir "$where" + + unit="$(systemd-escape --path --suffix=mount "$where")" + unit_file="${UNIT_DIR}/${unit}" + + if [ ! -f "$unit_file" ]; then + warn "No systemd mount unit for $where (${unit})" + exit 0 + fi + + sudo systemctl disable "$unit" 2>/dev/null || true + sudo systemctl stop "$unit" 2>/dev/null || true + sudo systemctl stop "${unit%.mount}.automount" 2>/dev/null || true + sudo rm -f "$unit_file" + sudo rm -f "$SMB_CREDS_DIR/$(basename "$where")" + sudo rmdir "$SMB_CREDS_DIR" 2>/dev/null || true + sudo systemctl daemon-reload + log "Removed persistent SMB mount: $where" + notify_send "SMB persistent mount removed: $where" + ;; +esac diff --git a/bin/pos-share-smb-server b/bin/pos-share-smb-server new file mode 100755 index 0000000..14f525d --- /dev/null +++ b/bin/pos-share-smb-server @@ -0,0 +1,226 @@ +#!/usr/bin/env bash +set -euo pipefail +# POS: share smb-server — Manage the Samba server (status, share/unshare exports, users, enable/disable) + +source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh" +source "$(dirname "$0")/../lib/notify.sh" 2>/dev/null || source "$(dirname "$0")/notify.sh" + +command -v smbd &>/dev/null || err "smbd not found (install samba)" +command -v smbcontrol &>/dev/null || err "smbcontrol not found (install samba)" +command -v testparm &>/dev/null || err "testparm not found (install samba)" +command -v smbpasswd &>/dev/null || err "smbpasswd not found (install samba)" + +usage() { + cat < [args] + +Manage the Samba server (smbd, /etc/samba/smb.conf). + +Commands: + status Show server status + current shares + users + share [name] Add or update a share (default name: folder basename) + unshare Remove a share and reload + list Show current shares + adduser Create a Samba user (prompts for password) + deluser Remove a Samba user + reload Validate smb.conf and reload the server + enable Start smbd and enable it on boot + disable Stop smbd and disable it on boot + +Options for share: + --read-only Export read-only + --guest Allow guest access (ANY user on the network — warns) + --users Restrict to these Samba users (comma-separated) + +Examples: + pos share smb-server share /mnt/hdd media --users bob,alice + pos share smb-server share /mnt/hdd/media --read-only + pos share smb-server adduser bob + pos share smb-server list + pos share smb-server unshare media +EOF + exit 0 +} + +SMB_CONF="${SMB_CONF:-/etc/samba/smb.conf}" + +cmd="${1:-}" +case "$cmd" in + -h|--help) usage ;; + status|share|unshare|list|adduser|deluser|reload|enable|disable) ;; + "") err "Missing command (status|share|unshare|list|adduser|deluser|reload|enable|disable)" ;; + *) err "Unknown command '$cmd' (see --help)" ;; +esac + +require_root_dir() { + local path="$1" + case "$path" in + /*) ;; + *) err "Path must be absolute: $path" ;; + esac + [ -d "$path" ] || err "Path not found: $path" +} + +validate_share_name() { + case "$1" in + *[!A-Za-z0-9._-]*) err "Invalid share name '$1' — use letters, digits, . _ -" ;; + esac +} + +# Validate smb.conf with testparm, then hot-reload smbd if it is running. +reload_config() { + testparm -s "$SMB_CONF" >/dev/null || err "smb.conf is invalid — changes not loaded (fix with 'pos share smb-server reload')" + if systemctl is-active --quiet smbd 2>/dev/null; then + sudo smbcontrol smbd reload-config + log "smbd config reloaded" + else + warn "smbd not running — config validated but not loaded (enable with 'pos share smb-server enable')" + fi +} + +list_shares() { + local out + out="$(testparm -s "$SMB_CONF" 2>/dev/null | grep -F '[' || true)" + if [ -z "$out" ]; then + echo " (none)" + return + fi + printf '%s\n' "$out" | grep -Fv '[global]' | sed 's/\[\(.*\)\]/ \1/' +} + +case "$cmd" in + status) + if systemctl is-active --quiet smbd 2>/dev/null; then + ok "smbd: running" + else + warn "smbd: not running (enable with 'pos share smb-server enable')" + fi + echo + section "Shares" + list_shares + echo + section "Users" + sudo pdbedit -L 2>/dev/null | cut -d: -f1 | sed 's/^/ /' || echo " (none)" + ;; + + share) + path="${2:-}" + [ -n "$path" ] || err "Usage: pos share smb-server share [name] [--read-only|--guest|--users u1,u2]" + case "$path" in + -*) err "Usage: pos share smb-server share [name] [--read-only|--guest|--users u1,u2]" ;; + esac + require_root_dir "$path" + [ -f "$SMB_CONF" ] || err "No smb.conf at $SMB_CONF (is samba installed?)" + + shift 2 + name="" ro=0 guest=0 users="" + while [ $# -gt 0 ]; do + case "$1" in + --read-only) ro=1; shift ;; + --guest) guest=1; shift ;; + --users) [ $# -ge 2 ] || err "--users needs a value"; users="$2"; shift 2 ;; + -*) err "unknown option '$1'" ;; + *) name="$1"; shift ;; + esac + done + name="${name:-$(basename "$path")}" + validate_share_name "$name" + + if [ "$guest" -eq 1 ]; then + warn "guest access on — ANY network user can access $path. Restrict with --users." + elif [ -z "$users" ]; then + warn "No valid users — any Samba account can access $path. Restrict with --users u1,u2." + fi + + ro_val=no; [ "$ro" -eq 1 ] && ro_val=yes + guest_val=no; [ "$guest" -eq 1 ] && guest_val=yes + block="[$name] + path = $path + browseable = yes + read only = $ro_val + guest ok = $guest_val" + [ -n "$users" ] && block="$block + valid users = $users" + + tmp="$(mktemp)" + awk -v s="# >>> pos-managed share: $name" -v e="# <<< end pos-managed share" ' + $0 == s {inblock=1} + $0 == e && inblock == 1 {inblock=0; next} + !inblock {print} + ' "$SMB_CONF" > "$tmp" + { + echo + echo "# >>> pos-managed share: $name" + printf '%s\n' "$block" + echo "# <<< end pos-managed share" + } >> "$tmp" + + testparm -s "$tmp" >/dev/null || { rm -f "$tmp"; err "Invalid smb.conf — changes not applied (see testparm -s $SMB_CONF)"; } + sudo cp "$tmp" "$SMB_CONF" + rm -f "$tmp" + reload_config + log "Share added: [$name] → $path" + notify_send "SMB share added: $name ($path)" + ;; + + unshare) + name="${2:-}" + [ -n "$name" ] || err "Usage: pos share smb-server unshare " + validate_share_name "$name" + [ -f "$SMB_CONF" ] || err "No smb.conf at $SMB_CONF (is samba installed?)" + + if ! grep -Fq "# >>> pos-managed share: $name" "$SMB_CONF"; then + warn "No share '$name' found in $SMB_CONF" + exit 0 + fi + + tmp="$(mktemp)" + awk -v s="# >>> pos-managed share: $name" -v e="# <<< end pos-managed share" ' + $0 == s {inblock=1} + $0 == e && inblock == 1 {inblock=0; next} + !inblock {print} + ' "$SMB_CONF" > "$tmp" + sudo cp "$tmp" "$SMB_CONF" + rm -f "$tmp" + reload_config + log "Removed share: $name" + notify_send "SMB share removed: $name" + ;; + + list) + list_shares + ;; + + adduser) + user="${2:-}" + [ -n "$user" ] || err "Usage: pos share smb-server adduser " + id -u "$user" >/dev/null 2>&1 || err "No system user '$user' — create it first (e.g. sudo adduser $user)" + sudo smbpasswd -a "$user" + log "Samba user added: $user" + notify_send "SMB user added: $user" + ;; + + deluser) + user="${2:-}" + [ -n "$user" ] || err "Usage: pos share smb-server deluser " + sudo smbpasswd -x "$user" + log "Samba user removed: $user" + notify_send "SMB user removed: $user" + ;; + + reload) + reload_config + ;; + + enable) + sudo systemctl enable --now smbd + log "smbd enabled (starts on boot)" + notify_send "SMB server enabled" + ;; + + disable) + sudo systemctl disable --now smbd + log "smbd disabled (will not start on boot)" + notify_send "SMB server disabled" + ;; +esac diff --git a/preinstall.sh b/preinstall.sh index c78c859..90099fe 100755 --- a/preinstall.sh +++ b/preinstall.sh @@ -31,6 +31,7 @@ PACKAGES=( net-tools iputils-ping traceroute tcpdump nmap openssh-client openssh-server ufw fail2ban nfs-common nfs-kernel-server + samba cifs-utils hostapd dnsmasq iptables iw ca-certificates gnupg lsb-release lm-sensors smartmontools nvme-cli hdparm