feat: usb-automount feature — udev+systemd auto-mount of USB sticks, flag-gated like autostart

features/usb-automount.sh (installed via ./install.sh --feature, flag
usb-automount) mounts every unmounted removable block device at
/media/<label> — world-writable via -o umask=000 (fallback plain mount),
label-collision bump -2/-3, no-label -> usb-<name>. First root run
self-installs the hotplug udev rule (/etc/udev/rules.d/99-usb-automount.rules,
SYSTEMD_WANTS=usb-automount.service) + udevadm reload/trigger; an existing
rule is never overwritten. systemd/usb-automount.service (Type=oneshot,
WantedBy=multi-user.target) covers boot + hotplug + manual start, gated in
postinstall.sh's systemd loop exactly like autostart. Purpose: a plugged-in
stick is ready for pos system backup's post-verify USB copy without manual
mounting. Docs: SYSTEMD/SCRIPTS/README/AGENT_Context (tree, filetable —
postinstall.sh count corrected 152->163, it was already stale). Verified:
stub suite /tmp/opencode/usb-automount-test 47/47 green; make gen && make check.
This commit is contained in:
Your Name
2026-08-13 03:40:13 -04:00
parent d4d38ad901
commit a3c01a0394
8 changed files with 217 additions and 19 deletions
+1
View File
@@ -16,6 +16,7 @@ summary (newest last).
## Done
- **2026-08-13** — `usb-automount` feature, integrated exactly like `autostart`: `features/usb-automount.sh` (root-guard re-exec via sudo; first-root-run self-install of udev rule `/etc/udev/rules.d/99-usb-automount.rules``ACTION=="add", KERNEL=="sd[a-z]*", SUBSYSTEM=="block", ENV{ID_BUS}=="usb", TAG+="systemd", SYSTEMD_WANTS="usb-automount.service"` — + `udevadm control --reload` + `trigger --subsystem-match=block`; an existing/edited rule is never overwritten; scans `lsblk -J` for unmounted removable partitions/raw whole-disk filesystems, mounts each at `/media/<label>` — vfat/exfat/ntfs world-writable via `-o umask=000`, fallback plain mount, label-collision bump `-2`/`-3`, no label → `usb-<name>`, logs `${HOME:-/root}/.usb-automount.log`) + `systemd/usb-automount.service` (`Type=oneshot`, `WantedBy=multi-user.target` — boot + hotplug + manual `systemctl start usb-automount`), gated in postinstall.sh's systemd loop exactly like autostart (`flag_is_set usb-automount` → skip with hint). Purpose: a plugged-in stick is auto-mounted world-writable, ready for `pos system backup`'s post-verify USB copy. Docs: SYSTEMD.md (service section + gating code block), SCRIPTS.md (feature section + systemd bullet + TOC), AGENT_Context tree + filetable rows (postinstall.sh count corrected 152→163 — it was already 6 lines stale), README index rows. Verified with a stub suite (`/tmp/opencode/usb-automount-test` — lsblk JSON fixtures, mount/mountpoint/udevadm/sudo stubs, `MOUNT_BASE`/`UDEV_RULES_DIR` seams, HOME isolation): 47/47 green. `make gen && make check` green. Gotcha learned: `${VAR:-{...}}` with a `{` inside the parameter-expansion default mis-parses in bash (emits a stray `}` — printf of a multi-line value showed `}}`); avoid braces in `:-` defaults.
- **2026-08-13** — `pos system backup` copies the finished backup to a USB stick. Detection runs **after** the archive verifies (so a stick plugged in while the backup ran is found; if none is mounted, one re-scan prompt before giving up — `s` skips, EOF from cron skips silently, rc stays 0). Single stick → y/N confirm; several → numbered pick (0 = skip). Copy lands in `<usb>/backups/` (mkdir -p; `chmod 600` best-effort — vfat chmod failures warn, never fail), and the transfer is proven 100% by **sha256 source-vs-copy** before any success is announced: mismatch → warn with both hashes + `notify_send "USB copy FAILED…"` + rc=1 (the ERR trap is re-armed mid-script so a USB-phase failure no longer notifies "Backup FAILED"). Detection: `lsblk -J` → recursive jq filter (rm==true && mounted && type part|disk, space-safe via JSON) or pinned `BACKUP_USB_ROOT` seam (= `<root>/backups/`, skips detection — also the test seam). Docs: usage() Environment, POS.md backup row, howto/system.md (USB section + env table + mismatch troubleshooting), DEV.md system.env list. Verified with a stub suite (`/tmp/opencode/backup-test` — sudo/gpg/lsblk/sender stubs, HOME isolation, per-test lsblk JSON fixtures, corrupting-cp + vfat-chmod override stubs): 40/40 green (skip s/EOF, seam y/n, detect single, multi pick 2/0, re-scan after replug, corrupt copy rc=1 + honest notify, vfat tolerance). `make gen && make check` green.
- **2026-08-13** — `pos share smb-server share` now guards the two common `NT_STATUS_ACCESS_DENIED` causes at share time (warnings only): `--users` entries missing from the Samba passdb (`pdbedit -L`, cut to user column, `grep -qxF` per user — pointer to `pos share smb-server adduser <user>`), and ancestors of the share path lacking `other:+x` traversal (sticky dirs like `/tmp` count as traversable via the `t` slot; fix hint `chmod o+x <dir>`). Both wired into the `share` case after `require_root_dir`; howto/share.md SMB section + troubleshooting updated. Rooted in `reports/bug-report-smb-server-access-denied.md` (committed as the spec). Verified with a stub-PATH suite (`/tmp/opencode/smb-test` — pdbedit/systemctl/smbcontrol/testparm/smbpasswd stubs, `SMB_CONF` seam): 16/16 green.
- **2026-08-13** — Docs hardening from the schedule-session review (sole-developer call: terse, session-learned). DEV.md §7 env-seam registry now lists `USER_SYSTEMD_DIR` (`bin/pos-network-download`, `bin/pos-communication-{telegram,matrix}-listener`, `lib/scheduler-lib.sh`) + the scheduler's `SCHEDULE_*` seams, and documents the missing-`:-`-guard gotcha (a `VAR="${XDG…:-…}"` without leading `VAR:-` overrides the seam — stub runs then silently write to the real `$HOME`; fix: `USER_SYSTEMD_DIR="${USER_SYSTEMD_DIR:-…}"`). New-tool test checklist gains an env-seam review step (grep for unguarded config writes + prove with `VAR=/tmp/x`). §7 notes stub harnesses are throwaway by design — build in `/tmp/opencode/<tool>-test/`, leave there, keep only the pattern. howto/schedule.md documents that `migrate` copies the rule LHS **verbatim** as `COMMAND` (old tool never had `disk root`/`loadavg` shorthands — rewrite those jobs with real commands). `make check` green.
+17 -15
View File
@@ -10,19 +10,19 @@
<!-- GEN:START docmap -->
| ## 1. Project Overview | 2843 |
| ## 2. Directory Structure | 44189 |
| ## 3. Installation Flow | 190242 |
| ## 4. The `pos` CLI System | 243312 |
| ## 5. Shared Library — `lib/common.sh` | 313344 |
| ## 6. Docker Compose / ScaleTail | 345387 |
| ## 7. Optional Apps (`apps/`) | 388417 |
| ## 8. Entertainment Module | 418431 |
| ## 9. Systemd Services | 432444 |
| ## 10. Configuration Files | 445471 |
| ## 11. Coding Conventions | 472504 |
| ## 12. Development Workflow | 505557 |
| ## 13. Key File Quick Reference | 558614 |
| ## 14. Common Tasks for Agents | 615644 |
| ## 2. Directory Structure | 44190 |
| ## 3. Installation Flow | 191243 |
| ## 4. The `pos` CLI System | 244313 |
| ## 5. Shared Library — `lib/common.sh` | 314345 |
| ## 6. Docker Compose / ScaleTail | 346388 |
| ## 7. Optional Apps (`apps/`) | 389418 |
| ## 8. Entertainment Module | 419432 |
| ## 9. Systemd Services | 433445 |
| ## 10. Configuration Files | 446472 |
| ## 11. Coding Conventions | 473505 |
| ## 12. Development Workflow | 506558 |
| ## 13. Key File Quick Reference | 559616 |
| ## 14. Common Tasks for Agents | 617646 |
<!-- GEN:END docmap -->
## 1. Project Overview
@@ -100,7 +100,8 @@ Linux_post_install/
│ └── ssh-load-all # Legacy wrapper → pos ssh load-keys
├── features/ # User-customizable scripts (installed via --feature)
── autostart.sh # Boot-time script (via systemd, flag-gated)
── autostart.sh # Boot-time script (via systemd, flag-gated)
│ └── usb-automount.sh # USB automount (udev rule + flag-gated service)
├── entertainment/ # Public-API plugins for pos entertainment send (→ /usr/local/bin)
│ ├── weather.sh # Current weather via Open-Meteo (no API key)
@@ -561,7 +562,7 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`
|------|-------|---------|
| `install.sh` | 206 | Main orchestrator — 4 phases with CLI flags, `--feature`, prebuilt arch bins |
| `preinstall.sh` | 73 | System packages + hotspot deps + yt-dlp + fail2ban |
| `postinstall.sh` | 152 | fail2ban config, PATH, bash completion, systemd (flag-gated) |
| `postinstall.sh` | 163 | fail2ban config, PATH, bash completion, systemd (flag-gated) |
| `lib/common.sh` | 144 | Shared library (log/warn/err/run/spawn, dry-run aware, `load_system_env`) |
| `lib/flags.sh` | 60 | Feature flag store (set/clear/is_set/value/list/status) |
| `lib/notify.sh` | 76 | Multi-platform alerting (`notify_send`) — opt-in source, silent-fails |
@@ -571,6 +572,7 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`
| `bin/flag-set` | 21 | Set a flag (optionally with a value) |
| `bin/flag-clear` | 21 | Unset a flag |
| `features/autostart.sh` | 14 | Boot-time feature (moved from `bin/`, flag-gated service) |
| `features/usb-automount.sh` | 134 | USB automount feature (udev rule + flag-gated service) |
<!-- GEN:START filetable -->
| `bin/pos` | 292 | CLI dispatcher with smart arg matching + logging + category help |
| `bin/pos-ai-gemini` | 311 | Chat with Google Gemini (ask, chat, models, sessions) |
+2 -2
View File
@@ -5,10 +5,10 @@ Everything in this folder is reference material for the `Linux_post_install` pro
| Document | What it covers |
|----------|----------------|
| [HOWTO.md](HOWTO.md) | Hands-on guides per category (ai, network, docker, media, system, ssh, share, communication, entertainment): flags, recipes, config, troubleshooting |
| [SCRIPTS.md](SCRIPTS.md) | Core installer scripts: `install.sh`, `preinstall.sh`, `postinstall.sh`, `lib/common.sh`, `lib/flags.sh`, `lib/entertainment-lib.sh`, `features/autostart.sh` — purpose, how each works, configuration |
| [SCRIPTS.md](SCRIPTS.md) | Core installer scripts: `install.sh`, `preinstall.sh`, `postinstall.sh`, `lib/common.sh`, `lib/flags.sh`, `lib/entertainment-lib.sh`, `features/autostart.sh`, `features/usb-automount.sh` — purpose, how each works, configuration |
| [POS.md](POS.md) | The `pos` CLI: dispatcher, every `pos-*` command, Docker Compose / ScaleTail config, legacy wrappers, flag CLIs |
| [APPS.md](APPS.md) | Optional apps: `apps/install.sh` picker, installer conventions, full app catalog |
| [SYSTEMD.md](SYSTEMD.md) | Systemd units (`autostart.service`, `ssh-agent.service`), feature-flag gating, bash completion |
| [SYSTEMD.md](SYSTEMD.md) | Systemd units (`autostart.service`, `usb-automount.service`, `ssh-agent.service`), feature-flag gating, bash completion |
| [DEV.md](DEV.md) | Developer guide: architecture, conventions, how to add tools/apps/features, commit guidelines |
| [AGENT_Context_Project.md](AGENT_Context_Project.md) | Single-source context doc for AI agents working on the repo |
| [algorithm.md](algorithm.md) | ASCII diagrams: install flow, `pos` dispatch, compose `up`, config cascade, logging, vbox lifecycle |
+22 -1
View File
@@ -13,6 +13,7 @@ Everything that runs during the bootstrap install: `install.sh`, `preinstall.sh`
- [lib/flags.sh — feature flags](#libflagssh--feature-flags)
- [lib/notify.sh — multi-platform alerting](#libnotifysh--multi-platform-alerting)
- [features/autostart.sh — boot-time feature](#featuresautostartsh--boot-time-feature)
- [features/usb-automount.sh — USB automount feature](#featuresusb-automountsh--usb-automount-feature)
- [x64_bin/ — precompiled binaries](#x64_bin--precompiled-binaries)
---
@@ -108,7 +109,7 @@ Add or remove package names here. `nmap` and `fail2ban` are used later by `pos n
2. **PATH** — appends a `PATH` line to `~/.bashrc` if not already present.
3. **pos bash completion** — installs `completions/pos.bash` to `/usr/local/share/bash-completion/completions/` and sources it from `~/.bashrc`.
4. **SSH authorized keys** — if `config/authorized_keys` exists, appends missing keys to `~/.ssh/authorized_keys` (skips comments and duplicates, chmod 600).
5. **systemd services** — copies `systemd/*.service` to `/etc/systemd/system/`, daemon-reloads, then enables each service. **`autostart.service` is only enabled when the `autostart` feature flag is set** (see [lib/flags.sh](#libflagssh--feature-flags)); otherwise it's skipped with a hint to run `./install.sh --feature`.
5. **systemd services** — copies `systemd/*.service` to `/etc/systemd/system/`, daemon-reloads, then enables each service. **`autostart.service` is only enabled when the `autostart` feature flag is set**, and **`usb-automount.service` only when the `usb-automount` flag is set** (see [lib/flags.sh](#libflagssh--feature-flags)); otherwise they're skipped with a hint to run `./install.sh --feature`.
### Configuration
@@ -221,6 +222,26 @@ Appends timestamped lines to `~/.autostart.log`:
---
## features/usb-automount.sh — USB automount feature
**File:** `features/usb-automount.sh` (installed to `/usr/local/bin/usb-automount.sh` by `./install.sh --feature`)
**Purpose:** mounts USB storage automatically — at boot and on hotplug — so a plugged-in stick is immediately ready for `pos system backup`'s post-verify USB copy without manual mounting.
### How it works
1. Every run (boot via `usb-automount.service`, hotplug via its self-installed udev rule, or manual `sudo usb-automount.sh`) scans `lsblk -J` for unmounted removable block devices — partitions and raw whole-disk filesystems — and mounts each at `/media/<label>`.
2. vfat/exfat/ntfs mounts are world-writable (`-o umask=000`) so a non-root user can write; filesystems that reject `umask` fall back to a plain mount.
3. Idempotent: already-mounted devices are skipped, and repeated runs are no-ops.
4. First root run installs the hotplug rule `/etc/udev/rules.d/99-usb-automount.rules` (`SYSTEMD_WANTS="usb-automount.service"`) and reloads udev — an edited rule is never overwritten.
### Configuration
- Log file: `$HOME/.usb-automount.log` (edit the `LOG` variable at the top).
- Mount point: `/media/<label>` (bumps to `-2`, `-3` when the label is already in use as a mountpoint); mount options are tuned in `mount_one`.
- The script is user-customizable like any feature — a plain reinstall never overwrites your edits.
---
## x64_bin/ — precompiled binaries
**Folder:** `x64_bin/` (future: `arm64_bin/`)
+26 -1
View File
@@ -4,6 +4,7 @@ The units installed and enabled by `postinstall.sh`, plus the `pos` bash complet
- [Services](#services)
- [`autostart.service`](#autostartservice)
- [`usb-automount.service`](#usb-automountservice)
- [`ssh-agent.service`](#ssh-agentservice)
- [`pos-health.service`](#pos-healthservice)
- [Per-user units (`pos network download`)](#per-user-units-pos-network-download)
@@ -38,6 +39,25 @@ WantedBy=multi-user.target
**Configuration:** point `ExecStart` at your boot script. Because the target script is a *feature*, this unit is only **enabled** when the `autostart` flag is set — the file is still copied, but a skipped feature leaves the unit present-but-disabled.
### usb-automount.service
**Purpose:** mount USB storage automatically — at boot (via `WantedBy=multi-user.target`) and on hotplug (a udev rule installed by the feature script triggers this unit). Each run mounts every unmounted removable block device at `/media/<label>`, world-writable (`-o umask=000`), so `pos system backup`'s post-verify USB copy finds the stick and the unprivileged user can write to it.
```ini
[Unit]
Description=Auto-mount USB storage (usb-automount feature)
After=local-fs.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/usb-automount.sh
[Install]
WantedBy=multi-user.target
```
**Configuration:** `Type=oneshot` — each start (boot, hotplug, manual `systemctl start usb-automount`) does one idempotent scan. `features/usb-automount.sh` self-installs its udev rule (`/etc/udev/rules.d/99-usb-automount.rules`, `SYSTEMD_WANTS="usb-automount.service"`) on first root run and reloads udev, so plugging in a stick fires the mount with no extra setup; an edited rule is never overwritten. Because the target script is a *feature*, this unit is only **enabled** when the `usb-automount` flag is set — the file is still copied, but a skipped feature leaves the unit present-but-disabled. Hotplug is restricted to USB (`ENV{ID_BUS}=="usb"`); the boot scan covers all removable media.
### ssh-agent.service
**Purpose:** a system-wide SSH agent, one shared socket for all sessions (so `pos ssh load-keys` and everyday ssh work without per-login agents).
@@ -116,19 +136,24 @@ enable-linger` warning so the user units survive logout.
## Feature-flag gating
The systemd loop in `postinstall.sh` special-cases two units:
The systemd loop in `postinstall.sh` special-cases three units:
```bash
if [ "$svc_name" = "autostart.service" ] && ! flag_is_set autostart; then
warn "autostart feature not installed — skipping autostart.service (run ./install.sh --feature)"
continue
fi
if [ "$svc_name" = "usb-automount.service" ] && ! flag_is_set usb-automount; then
warn "usb-automount feature not installed — skipping usb-automount.service (run ./install.sh --feature)"
continue
fi
if [ "$svc_name" = "pos-health.service" ]; then
# substitute User= and enable pos-health.timer only if Telegram is configured
fi
```
- `autostart.service` is **enabled** only when the `autostart` feature flag is set (`./install.sh --feature` or `flag-set autostart`). See [SCRIPTS.md → lib/flags.sh](SCRIPTS.md#libflagssh--feature-flags).
- `usb-automount.service` is **enabled** only when the `usb-automount` feature flag is set — same mechanism.
- `pos-health.service` is **not** enabled at all — `postinstall.sh` enables `pos-health.timer` instead, and only when a Telegram config already exists.
---
+134
View File
@@ -0,0 +1,134 @@
#!/usr/bin/env bash
set -euo pipefail
# ────────────────────────────────────────────────────────────────
# USB automount feature — auto-mounts removable USB storage.
#
# Installed on demand with: ./install.sh --feature
# → copied to /usr/local/bin/usb-automount.sh (chmod 755)
# → flag "usb-automount" is set
#
# Triggers:
# - boot + hotplug via systemd/usb-automount.service (the udev
# rule below is self-installed on first root run)
# - manual: sudo usb-automount.sh
#
# Behavior: mounts every unmounted removable block device at
# /media/<label>, world-writable (-o umask=000) so non-root users
# can write — this is what lets `pos system backup` copy the
# verified archive onto a stick. Idempotent: already-mounted
# devices are skipped, repeated runs are no-ops.
# ────────────────────────────────────────────────────────────────
# Log — root/systemd runs have no $HOME (same convention as autostart.sh).
LOG="${HOME:-/root}/.usb-automount.log"
# Test-only seams (DEV.md env-seam registry):
# AUTOMOUNT_SKIP_ROOT_GUARD=1 skip the sudo re-exec (stub suites)
# UDEV_RULES_DIR=<dir> where the udev rule goes
# UDEVADM=<path> udevadm binary (or stub)
# MOUNT_BASE=<dir> mount point base (stub suites use a tmpdir)
RULES_DIR="${UDEV_RULES_DIR:-/etc/udev/rules.d}"
RULE_FILE="$RULES_DIR/99-usb-automount.rules"
UDEV_RULE='ACTION=="add", KERNEL=="sd[a-z]*", SUBSYSTEM=="block", ENV{ID_BUS}=="usb", TAG+="systemd", SYSTEMD_WANTS="usb-automount.service"'
MOUNT_BASE="${MOUNT_BASE:-/media}"
usage() {
cat <<EOF
Usage: usb-automount.sh [options]
Auto-mount USB storage: every unmounted removable block device is
mounted at /media/<label> (world-writable). Idempotent.
Triggers:
- boot and hotplug via usb-automount.service (udev rule installed
on first root run, service flag-gated in postinstall.sh)
- manual: sudo usb-automount.sh
Installed via: ./install.sh --feature
Flag: usb-automount
Log: ${HOME:-/root}/.usb-automount.log
EOF
exit 0
}
case "${1:-}" in
-h|--help) usage ;;
esac
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG" 2>/dev/null || true
}
# ── dependency guard ────────────────────────────────────────────
for dep in lsblk jq mount mountpoint; do
command -v "$dep" &>/dev/null || {
log "missing dependency: $dep"
exit 1
}
done
# ── root guard: mounting + udev need root ───────────────────────
if [ "$(id -u)" -ne 0 ] && [ "${AUTOMOUNT_SKIP_ROOT_GUARD:-0}" != "1" ]; then
exec sudo "$0" "$@"
fi
# ── udev rule: hotplug trigger, self-installed once ─────────────
# Installed on first root run only — an existing (possibly
# user-edited) rule is never overwritten.
install_udev_rule() {
mkdir -p "$RULES_DIR"
if [ ! -f "$RULE_FILE" ]; then
printf '%s\n' "$UDEV_RULE" > "$RULE_FILE"
"${UDEVADM:-udevadm}" control --reload
"${UDEVADM:-udevadm}" trigger --subsystem-match=block
log "installed udev rule: $RULE_FILE"
fi
}
# ── mount one device at /media/<label> (bump -2, -3 on collision)
mount_one() {
local dev="$1" label="$2" mp n
label="${label//\//_}"
mp="$MOUNT_BASE/${label:-usb-$(basename "$dev")}"
# Label already in use as a mountpoint (another stick) → bump.
if [ -d "$mp" ] && mountpoint -q "$mp" 2>/dev/null; then
n=2
while [ -d "${mp}-${n}" ] && mountpoint -q "${mp}-${n}" 2>/dev/null; do
n=$((n + 1))
done
mp="${mp}-${n}"
fi
mkdir -p "$mp"
if mount -o umask=000 "$dev" "$mp" 2>/dev/null; then
log "mounted $dev -> $mp (umask=000, world-writable)"
elif mount "$dev" "$mp" 2>/dev/null; then
log "mounted $dev -> $mp (plain)"
else
log "FAILED to mount $dev -> $mp"
fi
}
# ── scan: unmounted removable partitions / raw whole-disk FS ────
mount_removable() {
local out
out="$(lsblk -J -o NAME,PATH,LABEL,MOUNTPOINT,RM,TYPE 2>/dev/null)" || {
log "lsblk failed"
return 1
}
while IFS=$'\t' read -r dev label; do
[ -n "$dev" ] || continue
mount_one "$dev" "$label"
done < <(printf '%s' "$out" | jq -r '
.. | objects
| select(.rm == true and .mountpoint == null and
(.type == "part" or (.type == "disk" and ((.children // []) | length) == 0)))
| [.path, (.label // "")] | @tsv')
}
install_udev_rule
mount_removable
exit 0
+5
View File
@@ -147,6 +147,11 @@ if [ -d systemd ] && [ -n "$(ls -A systemd/*.service 2>/dev/null)" ]; then
warn "autostart feature not installed — skipping autostart.service (run ./install.sh --feature)"
continue
fi
# usb-automount.service runs features/usb-automount.sh — same gating
if [ "$svc_name" = "usb-automount.service" ] && ! flag_is_set usb-automount; then
warn "usb-automount feature not installed — skipping usb-automount.service (run ./install.sh --feature)"
continue
fi
run sudo systemctl enable --now "$svc_name" 2>/dev/null || \
run sudo systemctl enable "$svc_name"
done
+10
View File
@@ -0,0 +1,10 @@
[Unit]
Description=Auto-mount USB storage (usb-automount feature)
After=local-fs.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/usb-automount.sh
[Install]
WantedBy=multi-user.target