feat: Add smbfs mount manager, Jellyfin READMEs, Glances host visibility, planning updates
New secubox-app-smbfs package for SMB/CIFS remote directory management with smbfsctl CLI (add/remove/mount/umount/test/status), UCI config, auto-mount init script, and Jellyfin/Lyrion media path integration. Glances LXC: host bind mounts (/rom, /overlay, /boot, /srv), Docker socket fix (symlink loop), fs plugin @exit_after patch, hostname/OS identity, pre-generated /etc/mtab. KISS READMEs for secubox-app-jellyfin and luci-app-jellyfin. Planning files updated with Domoticz IoT, AI Gateway strategy, App Store P2P emancipation, and v2 roadmap items. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
eebc84d0b9
commit
79bb3c43f4
@ -132,3 +132,7 @@ _Last updated: 2026-02-04_
|
||||
- `streamlit`: Chunked upload to bypass uhttpd 64KB JSON limit, UTF-8 `.py` file upload fix, auto-install requirements from ZIP, non-standard filename support.
|
||||
- `crowdsec-dashboard`: Decisions list fix (wrong RPC expect key).
|
||||
- RPCD: BusyBox ash `local` keyword compatibility fix (wrap call handlers in function).
|
||||
- `glances`: Full host system visibility — LXC bind mounts for `/rom`, `/overlay`, `/boot`, `/srv`, Docker socket at `/run/docker.sock` (symlink loop fix), `@exit_after` fs plugin patch (multiprocessing fails in LXC), host hostname via `lxc.uts.name`, OpenWrt OS identity from `/etc/openwrt_release`, pre-generated `/etc/mtab` from host `/proc/mounts`.
|
||||
- `zigbee2mqtt`: Direct `/dev/ttyUSB0` passthrough (socat TCP bridge fails ASH protocol), adapter `ezsp`→`ember` (z2m 2.x), `ZIGBEE2MQTT_DATA` env var, `mosquitto-nossl` dependency.
|
||||
- `smbfs`: New SMB/CIFS remote mount manager package — UCI config, `smbfsctl` CLI (add/remove/mount/umount/test/status), auto-mount init script, credentials storage, Jellyfin+Lyrion integration, catalog entry.
|
||||
- `jellyfin`: KISS READMEs for both backend and LuCI packages.
|
||||
|
||||
@ -5,6 +5,8 @@ _Last updated: 2026-02-04_
|
||||
## Resolved
|
||||
|
||||
- ~~Expose cyberpunk option inside SecuBox Settings~~ — Done: `THEME_CHOICES` now includes `cyberpunk` in `settings.js`.
|
||||
- ~~Glances full system monitoring~~ — Done: LXC host bind mounts, Docker socket, fs plugin patch, hostname/OS identity (2026-02-04).
|
||||
- ~~Zigbee2MQTT dongle connection~~ — Done: adapter `ezsp`→`ember`, `ZIGBEE2MQTT_DATA` env var, direct `/dev/ttyUSB0` passthrough (2026-02-04).
|
||||
|
||||
## Open
|
||||
|
||||
@ -41,3 +43,65 @@ _Last updated: 2026-02-04_
|
||||
9. **Testing**
|
||||
- Capture screenshot baselines for dark/light/cyberpunk themes.
|
||||
- Automate browser cache busting (append `?v=<git sha>` to view URLs).
|
||||
|
||||
10. **SMB/CIFS Shared Remote Directories**
|
||||
- Implement smbfs/cifs mount management for shared remote directories.
|
||||
- Media handling: backups, sources, Lyrion music library, Jellyfin media paths.
|
||||
- UCI config + LuCI UI for mount management (credentials, auto-mount, mount points).
|
||||
- Integration hooks for media apps (Jellyfin, Lyrion, backup scripts).
|
||||
|
||||
11. **Metablogizer Upload Failures**
|
||||
- Investigate and fix failed file uploads in Metablogizer.
|
||||
- May be related to uhttpd 64KB JSON limit (similar to Streamlit fix).
|
||||
|
||||
12. **SecuBox v2 Roadmap & Objectives**
|
||||
- EnigmaBox integration evaluation (community vote?).
|
||||
- VoIP integration (SIP/WebRTC).
|
||||
- Domoticz home automation integration.
|
||||
- SSMTP / mail host / MX record management.
|
||||
- Reverse MWAN WireGuard peers (multi-WAN failover over mesh).
|
||||
- Nextcloud self-hosted cloud storage.
|
||||
- Version v2 release planning and feature prioritization.
|
||||
|
||||
**AI Management Layer** (ref: `SecuBox_LocalAI_Strategic_Analysis.html`):
|
||||
- Phase 1 (v0.18): Upgrade LocalAI → 3.9, MCP Server, Threat Analyst agent, DNS Guard migration.
|
||||
- Phase 2 (v0.19): CVE Triage + Network Anomaly agents, LocalRecall memory, AI Insights dashboard.
|
||||
- Phase 3 (v1.0): Config Advisor (ANSSI prep), P2P Mesh Intelligence, Factory auto-provisioning.
|
||||
- Hybrid approach: Ollama (inference) + LocalAI (orchestrator) + LocalAGI (agents) + LocalRecall (memory).
|
||||
- MCP tools: crowdsec.alerts, waf.logs, dns.queries, network.flows, system.metrics, wireguard.status, uci.config.
|
||||
|
||||
**AI Gateway Hybrid Architecture** (ref: `SecuBox_AI_Gateway_Hybrid_Architecture.html`):
|
||||
- `secubox-ai-gateway` package: LiteLLM Proxy (port 4000) + Data Classifier + MCP Server.
|
||||
- Data classification: LOCAL ONLY (raw network data) / SANITIZED (IPs scrubbed) / CLOUD DIRECT (generic).
|
||||
- Providers: Mistral (EU sovereign, priority 1) > Claude > GPT > Gemini > xAI (all opt-in).
|
||||
- Offline resilience: Local tier always active, cloud is bonus not dependency.
|
||||
- Budget cap: configurable monthly cloud spend limit via LiteLLM.
|
||||
- ANSSI CSPN: Data Classifier + Mistral EU + offline mode = triple sovereignty proof.
|
||||
|
||||
13. **Punk Exposure Multi-Domain DNS**
|
||||
- Multi-domain DNS with P2P exposure and Tor endpoints.
|
||||
- Classical HTTPS endpoint (DNS provider API: OVH, Gandi, Cloudflare).
|
||||
- Administrable DNS provider API integration via `dnsctl`.
|
||||
- Mapped to local services, mesh-federated, locally tweakable.
|
||||
- Follows Peek / Poke / Emancipate model (see `PUNK-EXPOSURE.md`).
|
||||
|
||||
14. **Jellyfin Post-Install**
|
||||
- Complete startup wizard (media library configuration).
|
||||
- ~~README documentation~~ — Done (2026-02-04).
|
||||
|
||||
15. **Domoticz IoT Integration & SecuBox Peering**
|
||||
- Create dedicated `luci-app-domoticz` (currently no LuCI app — only generic vhost-manager).
|
||||
- MQTT auto-bridge: auto-configure Domoticz ↔ zigbee2mqtt via Mosquitto broker.
|
||||
- Zigbee device discovery: expose z2m device list in Domoticz setup wizard.
|
||||
- SecuBox P2P mesh: register Domoticz as a mesh service (`secubox-p2p register-service`).
|
||||
- Tor/DNS exposure channels: add to exposure scanner and Punk Exposure model.
|
||||
- USB device passthrough: document `/srv/devices` for additional IoT dongles.
|
||||
- Backup integration: include `/srv/domoticz/config` in secubox-recovery.
|
||||
- Service registry: add Domoticz to `secubox-p2p` catalog and health checks.
|
||||
|
||||
16. **App Store P2P Emancipation**
|
||||
- Emancipate the app store WebUI as a remote P2P/torrent endpoint.
|
||||
- Generative remote IPK distribution (like master-link dynamic join IPK generation).
|
||||
- Decentralized package distribution across mesh nodes.
|
||||
- Compatible with existing bonus-feed and secubox-feed infrastructure.
|
||||
- Torrent-style swarming for large IPK downloads across mesh peers.
|
||||
|
||||
@ -2,35 +2,39 @@
|
||||
|
||||
## Active Threads
|
||||
|
||||
- **SMB/CIFS Remote Mount Manager**
|
||||
Status: DONE — package created (2026-02-04)
|
||||
Notes: New `secubox-app-smbfs` package with `smbfsctl` CLI, UCI config, init script, catalog entry.
|
||||
Integrates with Jellyfin and Lyrion media paths.
|
||||
|
||||
- **Jellyfin README**
|
||||
Status: DONE (2026-02-04)
|
||||
Notes: KISS READMEs created for both `secubox-app-jellyfin` and `luci-app-jellyfin`.
|
||||
|
||||
- **Glances Full System Monitoring**
|
||||
Status: COMPLETE (2026-02-04)
|
||||
Notes: LXC host bind mounts, Docker socket, fs plugin patch, hostname/OS identity.
|
||||
|
||||
- **Zigbee2mqtt LXC Rewrite**
|
||||
Status: COMPLETE (2026-02-04)
|
||||
Notes: Rewritten from Docker to LXC Alpine container. Feed rebuilt.
|
||||
Deploy fix (2026-02-04): adapter `ezsp`→`ember` (z2m 2.x rename), added `ZIGBEE2MQTT_DATA` env var to start script, added `mosquitto-nossl` dependency. Direct `/dev/ttyUSB0` passthrough works; socat TCP bridge does NOT work (ASH RSTACK timeout).
|
||||
Notes: Direct `/dev/ttyUSB0` passthrough, adapter `ezsp`→`ember`, `ZIGBEE2MQTT_DATA` env var.
|
||||
|
||||
- **Jellyfin Media Server**
|
||||
Status: COMPLETE (2026-02-04)
|
||||
Notes: New secubox-app-jellyfin + luci-app-jellyfin with LXC, HAProxy integration, uninstall/update/backup.
|
||||
## Strategic Documents Received
|
||||
|
||||
- **Device Intel & DNS Provider**
|
||||
Status: COMPLETE (2026-02-04)
|
||||
Notes: New packages added. BusyBox compatibility, OUI emoji display, SDK build pattern aligned.
|
||||
|
||||
- **Exposure KISS Redesign**
|
||||
Status: COMPLETE (2026-02-04)
|
||||
Notes: Enriched service names, vhost integration, DNS domain sorting, toggle switch fix.
|
||||
|
||||
- **Streamlit Upload Fixes**
|
||||
Status: COMPLETE (2026-02-04)
|
||||
Notes: Chunked upload (uhttpd 64KB limit), UTF-8 fix, ZIP requirements auto-install, rename support.
|
||||
- `SecuBox_LocalAI_Strategic_Analysis.html` — AI Management Layer roadmap (LocalAI 3.9 + LocalAGI + MCP).
|
||||
- `SecuBox_AI_Gateway_Hybrid_Architecture.html` — Hybrid Local/Cloud architecture (LiteLLM + Data Classifier + multi-provider).
|
||||
|
||||
## Next Up
|
||||
|
||||
- Port the chip header layout to remaining SecuBox derivative apps (client-guardian, auth-guardian) — still pending, neither has `sh-page-header` pattern.
|
||||
- Rebuild bonus feed with all 2026-02-04 changes (partially done — zigbee2mqtt and device-intel included, verify completeness).
|
||||
- Commit uncommitted working tree changes (bonus-feed IPKs, zigbee2mqttctl).
|
||||
1. **Domoticz IoT Integration** — LuCI app, MQTT auto-bridge, zigbee2mqtt integration, P2P mesh.
|
||||
2. **Metablogizer Upload Fixes** — Investigate failed uploads.
|
||||
3. **App Store P2P Emancipation** — Remote P2P/torrent endpoint, generative IPK distribution.
|
||||
4. Port chip header layout to client-guardian, auth-guardian.
|
||||
5. Rebuild bonus feed with all 2026-02-04 changes.
|
||||
6. Commit uncommitted working tree changes (bonus-feed IPKs, glancesctl, zigbee2mqttctl, smbfs, jellyfin READMEs).
|
||||
|
||||
## Blockers / Risks
|
||||
|
||||
- Cyberpunk theme is now exposed in Settings UI (dark/light/system/cyberpunk) — previous blocker resolved.
|
||||
- No automated regression tests for LuCI views; manual verification required after each SCP deploy.
|
||||
- `zigbee2mqttctl` has uncommitted changes in working tree.
|
||||
- Glances + Zigbee2MQTT + SMB/CIFS source changes uncommitted in working tree.
|
||||
- Strategic AI documents noted but not yet implemented (v0.18+ roadmap).
|
||||
|
||||
48
package/secubox/luci-app-jellyfin/README.md
Normal file
48
package/secubox/luci-app-jellyfin/README.md
Normal file
@ -0,0 +1,48 @@
|
||||
# LuCI Jellyfin Dashboard
|
||||
|
||||
Web interface for managing Jellyfin media server with real-time status, container controls, and integration management.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
opkg install luci-app-jellyfin
|
||||
```
|
||||
|
||||
## Access
|
||||
|
||||
LuCI menu: **Services -> Jellyfin**
|
||||
|
||||
## Sections
|
||||
|
||||
- **Service Status** -- Container state (running/stopped/not installed), uptime, Docker health, disk usage
|
||||
- **Integration Status** -- HAProxy (disabled/pending/configured), Mesh P2P, Firewall WAN
|
||||
- **Actions** -- Install, Start, Stop, Restart, Update, Backup, Uninstall, Open Web UI
|
||||
- **Configuration** -- Port, image, data path, timezone, domain, HAProxy SSL, media paths, GPU transcoding, mesh toggle
|
||||
- **Logs** -- Live container log viewer (last 50 lines)
|
||||
|
||||
## RPCD Methods
|
||||
|
||||
Backend: `luci.jellyfin`
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `status` | Full service status, config, and integrations |
|
||||
| `start` | Start Jellyfin container |
|
||||
| `stop` | Stop Jellyfin container |
|
||||
| `restart` | Restart Jellyfin container |
|
||||
| `install` | Pull image and create container |
|
||||
| `uninstall` | Remove container and data |
|
||||
| `update` | Pull latest image and recreate |
|
||||
| `configure_haproxy` | Register HAProxy vhost |
|
||||
| `backup` | Create config/data backup |
|
||||
| `restore` | Restore from backup archive |
|
||||
| `logs` | Fetch container logs |
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `luci-base`
|
||||
- `secubox-app-jellyfin`
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0
|
||||
@ -5,7 +5,7 @@ config glances 'main'
|
||||
option api_port '61209'
|
||||
option web_host '0.0.0.0'
|
||||
option refresh_rate '3'
|
||||
option memory_limit '128M'
|
||||
option memory_limit '256M'
|
||||
|
||||
config monitoring 'monitoring'
|
||||
option monitor_docker '1'
|
||||
|
||||
@ -164,6 +164,9 @@ lxc_create_docker_rootfs() {
|
||||
# Configure container
|
||||
echo "nameserver 8.8.8.8" > "$rootfs/etc/resolv.conf"
|
||||
mkdir -p "$rootfs/var/log/glances" "$rootfs/etc/glances" "$rootfs/tmp"
|
||||
mkdir -p "$rootfs/host" "$rootfs/rom" "$rootfs/overlay" "$rootfs/boot" "$rootfs/srv"
|
||||
mkdir -p "$rootfs/run"
|
||||
touch "$rootfs/run/docker.sock"
|
||||
|
||||
# Ensure /bin/sh exists
|
||||
if [ ! -x "$rootfs/bin/sh" ]; then
|
||||
@ -177,37 +180,46 @@ lxc_create_docker_rootfs() {
|
||||
fi
|
||||
fi
|
||||
|
||||
# Create startup script
|
||||
cat > "$rootfs/opt/start-glances.sh" << 'START'
|
||||
#!/bin/sh
|
||||
export PATH="/usr/local/bin:/usr/bin:/bin:$PATH"
|
||||
export PYTHONPATH="/app:$PYTHONPATH"
|
||||
cd /
|
||||
# Patch Glances: disable @exit_after on fs plugin (multiprocessing
|
||||
# fails inside LXC containers, causing all disk_usage calls to return None)
|
||||
local fs_init="$rootfs/app/glances/plugins/fs/__init__.py"
|
||||
if [ -f "$fs_init" ]; then
|
||||
sed -i 's/^@exit_after(/#@exit_after(/' "$fs_init"
|
||||
log_info "Patched Glances fs plugin for LXC compatibility"
|
||||
fi
|
||||
|
||||
# Setup hostname resolution (required for socket.gethostbyname to work)
|
||||
REAL_HOSTNAME=$(hostname 2>/dev/null || echo glances)
|
||||
cat > /etc/hosts << EOF
|
||||
127.0.0.1 localhost $REAL_HOSTNAME glances
|
||||
::1 localhost ip6-localhost ip6-loopback
|
||||
EOF
|
||||
# Generate /etc/mtab from host mounts (psutil reads this for disk_partitions)
|
||||
grep -E '^(/dev/|overlayfs:)' /proc/mounts | \
|
||||
grep -v '/srv/docker/overlay' > "$rootfs/etc/mtab" 2>/dev/null
|
||||
|
||||
# Read environment variables
|
||||
WEB_PORT="${GLANCES_WEB_PORT:-61208}"
|
||||
WEB_HOST="${GLANCES_WEB_HOST:-0.0.0.0}"
|
||||
REFRESH="${GLANCES_REFRESH:-3}"
|
||||
|
||||
# Build args for web server mode
|
||||
# Use -B for bind address and -p for port separately
|
||||
# Disable autodiscover and check-update to avoid DNS resolution issues
|
||||
ARGS="-w -B $WEB_HOST -p $WEB_PORT -t $REFRESH --disable-autodiscover --disable-check-update"
|
||||
|
||||
# Disable plugins if configured
|
||||
[ "$GLANCES_NO_DOCKER" = "1" ] && ARGS="$ARGS --disable-plugin docker"
|
||||
[ "$GLANCES_NO_SENSORS" = "1" ] && ARGS="$ARGS --disable-plugin sensors"
|
||||
|
||||
echo "Starting Glances web server on $WEB_HOST:$WEB_PORT..."
|
||||
exec /venv/bin/python -m glances $ARGS
|
||||
START
|
||||
# Create startup script (written via file, not heredoc, to preserve shebang)
|
||||
printf '%s\n' '#!/bin/sh' \
|
||||
'export PATH="/usr/local/bin:/usr/bin:/bin:$PATH"' \
|
||||
'export PYTHONPATH="/app:$PYTHONPATH"' \
|
||||
'cd /' \
|
||||
'' \
|
||||
'# Set hostname from host kernel (proc:mixed exposes this)' \
|
||||
'REAL_HOSTNAME=$(cat /proc/sys/kernel/hostname 2>/dev/null || echo glances)' \
|
||||
'hostname "$REAL_HOSTNAME" 2>/dev/null' \
|
||||
'printf "127.0.0.1\tlocalhost %s glances\n::1\t\tlocalhost ip6-localhost\n" "$REAL_HOSTNAME" > /etc/hosts' \
|
||||
'' \
|
||||
'# Copy host os-release so Glances reports OpenWrt, not Alpine' \
|
||||
'if [ -f /host/openwrt_release ]; then' \
|
||||
" RELEASE=\$(grep DISTRIB_RELEASE /host/openwrt_release 2>/dev/null | cut -d\"'\" -f2)" \
|
||||
' printf '"'"'NAME="OpenWrt"\nVERSION="%s"\nID=openwrt\nPRETTY_NAME="OpenWrt %s"\n'"'"' "$RELEASE" "$RELEASE" > /etc/os-release' \
|
||||
'fi' \
|
||||
'' \
|
||||
'WEB_PORT="${GLANCES_WEB_PORT:-61208}"' \
|
||||
'WEB_HOST="${GLANCES_WEB_HOST:-0.0.0.0}"' \
|
||||
'REFRESH="${GLANCES_REFRESH:-3}"' \
|
||||
'' \
|
||||
'ARGS="-w -B $WEB_HOST -p $WEB_PORT -t $REFRESH --disable-autodiscover --disable-check-update"' \
|
||||
'[ "$GLANCES_NO_DOCKER" = "1" ] && ARGS="$ARGS --disable-plugin docker"' \
|
||||
'[ "$GLANCES_NO_SENSORS" = "1" ] && ARGS="$ARGS --disable-plugin sensors"' \
|
||||
'' \
|
||||
'echo "Starting Glances on ${WEB_HOST}:${WEB_PORT} host=${REAL_HOSTNAME}..."' \
|
||||
'exec /venv/bin/python -m glances $ARGS' \
|
||||
> "$rootfs/opt/start-glances.sh"
|
||||
chmod +x "$rootfs/opt/start-glances.sh"
|
||||
|
||||
log_info "Glances Docker image extracted successfully"
|
||||
@ -216,19 +228,33 @@ START
|
||||
lxc_create_config() {
|
||||
load_config
|
||||
|
||||
cat > "$LXC_CONFIG" << EOF
|
||||
# Glances LXC Configuration
|
||||
lxc.uts.name = $LXC_NAME
|
||||
local hostname=$(cat /proc/sys/kernel/hostname 2>/dev/null || echo glances)
|
||||
local mem_bytes=$(echo "$memory_limit" | sed 's/M/000000/')
|
||||
|
||||
# Root filesystem
|
||||
cat > "$LXC_CONFIG" << EOF
|
||||
# Glances LXC Configuration — full host visibility
|
||||
lxc.uts.name = $hostname
|
||||
lxc.rootfs.path = dir:$LXC_ROOTFS
|
||||
|
||||
# Network - use host network for full system visibility
|
||||
# Network - host network for full visibility
|
||||
lxc.net.0.type = none
|
||||
|
||||
# Mounts - give access to host system info via proc:mixed
|
||||
# Auto-mounts
|
||||
lxc.mount.auto = proc:mixed sys:ro cgroup:mixed
|
||||
|
||||
# Host filesystem bind mounts (read-only) for disk monitoring
|
||||
lxc.mount.entry = /rom rom none bind,ro,create=dir 0 0
|
||||
lxc.mount.entry = /overlay overlay none bind,ro,create=dir 0 0
|
||||
lxc.mount.entry = /boot boot none bind,ro,create=dir 0 0
|
||||
lxc.mount.entry = /srv srv none bind,ro,create=dir 0 0
|
||||
|
||||
# Host info for OS identification
|
||||
lxc.mount.entry = /etc/openwrt_release host/openwrt_release none bind,ro,create=file 0 0
|
||||
lxc.mount.entry = /etc/openwrt_version host/openwrt_version none bind,ro,create=file 0 0
|
||||
|
||||
# Docker socket for container monitoring (mount at /run, not /var/run which is a symlink)
|
||||
lxc.mount.entry = /var/run/docker.sock run/docker.sock none bind,create=file 0 0
|
||||
|
||||
# Environment variables
|
||||
lxc.environment = GLANCES_WEB_PORT=$web_port
|
||||
lxc.environment = GLANCES_WEB_HOST=$web_host
|
||||
@ -236,11 +262,11 @@ lxc.environment = GLANCES_REFRESH=$refresh_rate
|
||||
lxc.environment = GLANCES_NO_DOCKER=$([ "$monitor_docker" = "0" ] && echo 1 || echo 0)
|
||||
lxc.environment = GLANCES_NO_SENSORS=$([ "$monitor_sensors" = "0" ] && echo 1 || echo 0)
|
||||
|
||||
# Capabilities - minimal for monitoring
|
||||
lxc.cap.drop = sys_admin sys_module mac_admin mac_override sys_rawio
|
||||
# Capabilities
|
||||
lxc.cap.drop = sys_module mac_admin mac_override sys_rawio
|
||||
|
||||
# cgroups limits
|
||||
lxc.cgroup.memory.limit_in_bytes = $memory_limit
|
||||
lxc.cgroup2.memory.max = $mem_bytes
|
||||
|
||||
# Init
|
||||
lxc.init.cmd = /opt/start-glances.sh
|
||||
@ -271,6 +297,10 @@ lxc_run() {
|
||||
# Regenerate config to pick up any UCI changes
|
||||
lxc_create_config
|
||||
|
||||
# Regenerate /etc/mtab from host mounts (psutil reads this)
|
||||
grep -E '^(/dev/|overlayfs:)' /proc/mounts | \
|
||||
grep -v '/srv/docker/overlay' > "$LXC_ROOTFS/etc/mtab" 2>/dev/null
|
||||
|
||||
log_info "Starting Glances LXC container..."
|
||||
log_info "Web interface: http://0.0.0.0:$web_port"
|
||||
exec lxc-start -n "$LXC_NAME" -F -f "$LXC_CONFIG"
|
||||
|
||||
93
package/secubox/secubox-app-jellyfin/README.md
Normal file
93
package/secubox/secubox-app-jellyfin/README.md
Normal file
@ -0,0 +1,93 @@
|
||||
# SecuBox Jellyfin Media Server
|
||||
|
||||
Free media server for streaming movies, TV shows, music, and photos. Runs Jellyfin inside Docker on SecuBox-powered OpenWrt systems.
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
opkg install secubox-app-jellyfin
|
||||
jellyfinctl install
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
UCI config file: `/etc/config/jellyfin`
|
||||
|
||||
```
|
||||
config jellyfin 'main'
|
||||
option enabled '0'
|
||||
option image 'jellyfin/jellyfin:latest'
|
||||
option data_path '/srv/jellyfin'
|
||||
option port '8096'
|
||||
option timezone 'Europe/Paris'
|
||||
|
||||
config jellyfin 'media'
|
||||
list media_path '/mnt/media/movies'
|
||||
list media_path '/mnt/media/music'
|
||||
|
||||
config jellyfin 'network'
|
||||
option domain 'jellyfin.secubox.local'
|
||||
option haproxy '0'
|
||||
option firewall_wan '0'
|
||||
|
||||
config jellyfin 'transcoding'
|
||||
option hw_accel '0'
|
||||
|
||||
config jellyfin 'mesh'
|
||||
option enabled '0'
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
# Service control
|
||||
/etc/init.d/jellyfin start
|
||||
/etc/init.d/jellyfin stop
|
||||
|
||||
# Controller CLI
|
||||
jellyfinctl install # Pull Docker image and create container
|
||||
jellyfinctl status # Show container and integration status
|
||||
jellyfinctl update # Pull latest image and recreate container
|
||||
jellyfinctl logs # Show container logs (-f to follow)
|
||||
jellyfinctl shell # Open shell inside container
|
||||
jellyfinctl backup # Backup config and data
|
||||
jellyfinctl restore <file> # Restore from backup archive
|
||||
jellyfinctl uninstall # Stop and remove container and data
|
||||
|
||||
# Integrations
|
||||
jellyfinctl configure-haproxy # Register HAProxy vhost with SSL
|
||||
jellyfinctl remove-haproxy # Remove HAProxy vhost
|
||||
jellyfinctl configure-fw # Open WAN firewall port
|
||||
jellyfinctl remove-fw # Close WAN firewall port
|
||||
jellyfinctl register-mesh # Register in SecuBox P2P mesh
|
||||
jellyfinctl unregister-mesh # Remove from mesh registry
|
||||
```
|
||||
|
||||
Web UI: `http://<device-ip>:8096`
|
||||
|
||||
## Features
|
||||
|
||||
- Docker-based Jellyfin with full lifecycle management
|
||||
- Multi-path media libraries (movies, music, photos, shows)
|
||||
- Hardware GPU transcoding support
|
||||
- HAProxy reverse proxy with SSL/ACME integration
|
||||
- Firewall WAN port exposure
|
||||
- SecuBox P2P mesh service registration
|
||||
- Full config and data backup/restore
|
||||
- Container shell access and log streaming
|
||||
|
||||
## Files
|
||||
|
||||
- `/etc/config/jellyfin` -- UCI configuration
|
||||
- `/etc/init.d/jellyfin` -- procd service script
|
||||
- `/usr/sbin/jellyfinctl` -- controller CLI
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `dockerd`
|
||||
- `docker`
|
||||
- `containerd`
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0
|
||||
64
package/secubox/secubox-app-smbfs/Makefile
Normal file
64
package/secubox/secubox-app-smbfs/Makefile
Normal file
@ -0,0 +1,64 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=secubox-app-smbfs
|
||||
PKG_VERSION:=1.0.0
|
||||
PKG_RELEASE:=1
|
||||
PKG_ARCH:=all
|
||||
PKG_MAINTAINER:=CyberMind Studio <contact@cybermind.fr>
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
PKG_FLAGS:=nonshared
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/secubox-app-smbfs
|
||||
SECTION:=utils
|
||||
CATEGORY:=Utilities
|
||||
PKGARCH:=all
|
||||
SUBMENU:=SecuBox Apps
|
||||
TITLE:=SecuBox SMB/CIFS remote mount manager
|
||||
DEPENDS:=+kmod-fs-cifs +cifsmount
|
||||
endef
|
||||
|
||||
define Package/secubox-app-smbfs/description
|
||||
SMB/CIFS remote directory mount manager for SecuBox. Manages shared
|
||||
network mounts for media servers (Jellyfin, Lyrion), backups, and
|
||||
general-purpose remote storage over SMB/CIFS protocol.
|
||||
endef
|
||||
|
||||
define Package/secubox-app-smbfs/conffiles
|
||||
/etc/config/smbfs
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
endef
|
||||
|
||||
define Package/secubox-app-smbfs/install
|
||||
$(INSTALL_DIR) $(1)/etc/config
|
||||
$(INSTALL_CONF) ./files/etc/config/smbfs $(1)/etc/config/smbfs
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/init.d
|
||||
$(INSTALL_BIN) ./files/etc/init.d/smbfs $(1)/etc/init.d/smbfs
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/sbin
|
||||
$(INSTALL_BIN) ./files/usr/sbin/smbfsctl $(1)/usr/sbin/smbfsctl
|
||||
endef
|
||||
|
||||
define Package/secubox-app-smbfs/postinst
|
||||
#!/bin/sh
|
||||
[ -n "$${IPKG_INSTROOT}" ] || {
|
||||
echo ""
|
||||
echo "============================================"
|
||||
echo " SecuBox SMB/CIFS Mount Manager Installed"
|
||||
echo "============================================"
|
||||
echo ""
|
||||
echo "Quick Start:"
|
||||
echo " 1. Add share: smbfsctl add myshare //server/share /mnt/smb/myshare"
|
||||
echo " 2. Set creds: smbfsctl credentials myshare user password"
|
||||
echo " 3. Mount: smbfsctl mount myshare"
|
||||
echo " 4. Auto-mount: smbfsctl enable myshare"
|
||||
echo ""
|
||||
}
|
||||
exit 0
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,secubox-app-smbfs))
|
||||
101
package/secubox/secubox-app-smbfs/README.md
Normal file
101
package/secubox/secubox-app-smbfs/README.md
Normal file
@ -0,0 +1,101 @@
|
||||
# SecuBox SMB/CIFS Remote Mount Manager
|
||||
|
||||
Manages SMB/CIFS network shares for media servers (Jellyfin, Lyrion), backups, and general-purpose remote storage.
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
opkg install secubox-app-smbfs
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
UCI config file: `/etc/config/smbfs`
|
||||
|
||||
```
|
||||
config smbfs 'global'
|
||||
option enabled '1'
|
||||
option mount_base '/mnt/smb'
|
||||
option cifs_version '3.0'
|
||||
option timeout '10'
|
||||
|
||||
config mount 'movies'
|
||||
option enabled '1'
|
||||
option server '//192.168.1.100/movies'
|
||||
option mountpoint '/mnt/smb/movies'
|
||||
option username 'media'
|
||||
option _password 'secret'
|
||||
option read_only '1'
|
||||
option auto_mount '1'
|
||||
option description 'NAS movie library'
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
# Add a share
|
||||
smbfsctl add movies //nas/movies /mnt/smb/movies
|
||||
|
||||
# Set credentials
|
||||
smbfsctl credentials movies user password
|
||||
|
||||
# Set options
|
||||
smbfsctl set movies read_only 1
|
||||
smbfsctl set movies description 'Movie library'
|
||||
|
||||
# Test connectivity
|
||||
smbfsctl test movies
|
||||
|
||||
# Mount / unmount
|
||||
smbfsctl mount movies
|
||||
smbfsctl umount movies
|
||||
|
||||
# Enable auto-mount at boot
|
||||
smbfsctl enable movies
|
||||
|
||||
# List all shares
|
||||
smbfsctl list
|
||||
|
||||
# Show detailed mount status
|
||||
smbfsctl status
|
||||
|
||||
# Mount all enabled shares
|
||||
smbfsctl mount-all
|
||||
```
|
||||
|
||||
## Integration with Media Apps
|
||||
|
||||
```sh
|
||||
# Jellyfin: add mounted share as media library
|
||||
uci add_list jellyfin.media.media_path='/mnt/smb/movies'
|
||||
uci commit jellyfin
|
||||
|
||||
# Lyrion: point music library to mounted share
|
||||
uci set lyrion.main.media_path='/mnt/smb/music'
|
||||
uci commit lyrion
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- UCI-based share configuration with credentials storage
|
||||
- Auto-mount at boot for enabled shares
|
||||
- Read-only or read-write mount modes
|
||||
- CIFS protocol version selection (2.0, 2.1, 3.0)
|
||||
- Connectivity test before mounting
|
||||
- Mount status with disk usage reporting
|
||||
- Integration with Jellyfin and Lyrion media paths
|
||||
|
||||
## Files
|
||||
|
||||
- `/etc/config/smbfs` -- UCI configuration
|
||||
- `/etc/init.d/smbfs` -- procd init script (auto-mount)
|
||||
- `/usr/sbin/smbfsctl` -- controller CLI
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `kmod-fs-cifs` -- CIFS kernel module
|
||||
- `cifsmount` -- mount.cifs utility
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0
|
||||
21
package/secubox/secubox-app-smbfs/files/etc/config/smbfs
Normal file
21
package/secubox/secubox-app-smbfs/files/etc/config/smbfs
Normal file
@ -0,0 +1,21 @@
|
||||
# SecuBox SMB/CIFS Remote Mount Manager
|
||||
# Each 'mount' section defines a remote SMB share
|
||||
|
||||
config smbfs 'global'
|
||||
option enabled '1'
|
||||
option mount_base '/mnt/smb'
|
||||
option cifs_version '3.0'
|
||||
option timeout '10'
|
||||
|
||||
# Example share (disabled by default):
|
||||
# config mount 'media'
|
||||
# option enabled '0'
|
||||
# option server '//192.168.1.100/media'
|
||||
# option mountpoint '/mnt/smb/media'
|
||||
# option username 'guest'
|
||||
# option _password ''
|
||||
# option domain ''
|
||||
# option cifs_version '3.0'
|
||||
# option read_only '1'
|
||||
# option auto_mount '1'
|
||||
# option description 'Media library share'
|
||||
27
package/secubox/secubox-app-smbfs/files/etc/init.d/smbfs
Normal file
27
package/secubox/secubox-app-smbfs/files/etc/init.d/smbfs
Normal file
@ -0,0 +1,27 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
# SecuBox SMB/CIFS mount manager init script
|
||||
|
||||
START=90
|
||||
STOP=15
|
||||
USE_PROCD=1
|
||||
|
||||
start_service() {
|
||||
local enabled
|
||||
enabled="$(uci -q get smbfs.global.enabled)"
|
||||
[ "$enabled" = "1" ] || return 0
|
||||
|
||||
/usr/sbin/smbfsctl mount-all
|
||||
}
|
||||
|
||||
stop_service() {
|
||||
/usr/sbin/smbfsctl umount-all
|
||||
}
|
||||
|
||||
reload_service() {
|
||||
stop_service
|
||||
start_service
|
||||
}
|
||||
|
||||
service_triggers() {
|
||||
procd_add_reload_trigger "smbfs"
|
||||
}
|
||||
504
package/secubox/secubox-app-smbfs/files/usr/sbin/smbfsctl
Normal file
504
package/secubox/secubox-app-smbfs/files/usr/sbin/smbfsctl
Normal file
@ -0,0 +1,504 @@
|
||||
#!/bin/sh
|
||||
# SecuBox SMB/CIFS remote mount manager
|
||||
# Copyright (C) 2025-2026 CyberMind.fr
|
||||
|
||||
CONFIG="smbfs"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: smbfsctl <command> [args]
|
||||
|
||||
Share Management:
|
||||
add <name> <server> <mountpoint> Add a new SMB share
|
||||
remove <name> Remove a share definition
|
||||
enable <name> Enable auto-mount for a share
|
||||
disable <name> Disable auto-mount for a share
|
||||
credentials <name> <user> <pass> Set credentials for a share
|
||||
set <name> <key> <value> Set a share option
|
||||
list List all configured shares
|
||||
|
||||
Mount Operations:
|
||||
mount <name> Mount a specific share
|
||||
mount-all Mount all enabled shares
|
||||
umount <name> Unmount a specific share
|
||||
umount-all Unmount all shares
|
||||
status Show mount status of all shares
|
||||
test <name> Test connectivity to a share
|
||||
|
||||
Examples:
|
||||
smbfsctl add movies //nas/movies /mnt/smb/movies
|
||||
smbfsctl credentials movies user mypass
|
||||
smbfsctl set movies read_only 1
|
||||
smbfsctl mount movies
|
||||
smbfsctl enable movies
|
||||
EOF
|
||||
}
|
||||
|
||||
require_root() { [ "$(id -u)" -eq 0 ] || { echo "[ERROR] Root required" >&2; exit 1; }; }
|
||||
|
||||
log_info() { echo "[INFO] $*"; }
|
||||
log_warn() { echo "[WARN] $*" >&2; }
|
||||
log_error() { echo "[ERROR] $*" >&2; }
|
||||
|
||||
uci_get() { uci -q get ${CONFIG}.$1; }
|
||||
uci_set() { uci set ${CONFIG}.$1="$2" && uci commit ${CONFIG}; }
|
||||
|
||||
# Load global config
|
||||
load_global() {
|
||||
mount_base="$(uci_get global.mount_base || echo /mnt/smb)"
|
||||
default_vers="$(uci_get global.cifs_version || echo 3.0)"
|
||||
timeout="$(uci_get global.timeout || echo 10)"
|
||||
}
|
||||
|
||||
# Get list of configured share section names
|
||||
get_shares() {
|
||||
uci -q show "$CONFIG" | grep "=${CONFIG}\[" | sed "s/.*\.\(.*\)=.*/\1/" | sort -u
|
||||
uci -q show "$CONFIG" | grep "=mount" | sed "s/${CONFIG}\.\(.*\)=mount/\1/" | sort -u
|
||||
}
|
||||
|
||||
# Check if share section exists
|
||||
share_exists() {
|
||||
local type
|
||||
type="$(uci -q get ${CONFIG}.$1)"
|
||||
[ "$type" = "mount" ]
|
||||
}
|
||||
|
||||
# Build mount options for a share
|
||||
build_mount_opts() {
|
||||
local name="$1"
|
||||
local username password domain vers ro opts
|
||||
|
||||
username="$(uci_get ${name}.username)"
|
||||
password="$(uci_get ${name}._password)"
|
||||
domain="$(uci_get ${name}.domain)"
|
||||
vers="$(uci_get ${name}.cifs_version || echo "$default_vers")"
|
||||
ro="$(uci_get ${name}.read_only)"
|
||||
|
||||
opts="vers=${vers}"
|
||||
|
||||
if [ -n "$username" ] && [ "$username" != "guest" ]; then
|
||||
opts="${opts},username=${username}"
|
||||
[ -n "$password" ] && opts="${opts},password=${password}"
|
||||
[ -n "$domain" ] && opts="${opts},domain=${domain}"
|
||||
else
|
||||
opts="${opts},guest"
|
||||
fi
|
||||
|
||||
[ "$ro" = "1" ] && opts="${opts},ro" || opts="${opts},rw"
|
||||
|
||||
# Reasonable defaults for network mounts
|
||||
opts="${opts},iocharset=utf8,noperm,noserverino"
|
||||
|
||||
echo "$opts"
|
||||
}
|
||||
|
||||
# Check if a share is currently mounted
|
||||
is_mounted() {
|
||||
local mountpoint="$1"
|
||||
grep -q " ${mountpoint} cifs " /proc/mounts 2>/dev/null
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# SHARE MANAGEMENT
|
||||
# =============================================================================
|
||||
|
||||
cmd_add() {
|
||||
local name="$1" server="$2" mountpoint="$3"
|
||||
|
||||
[ -z "$name" ] || [ -z "$server" ] || [ -z "$mountpoint" ] && {
|
||||
echo "Usage: smbfsctl add <name> <server> <mountpoint>" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
if share_exists "$name"; then
|
||||
log_error "Share '$name' already exists"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
load_global
|
||||
|
||||
uci add ${CONFIG} mount >/dev/null
|
||||
# Rename the unnamed section to the given name
|
||||
local idx
|
||||
idx=$(uci -q show ${CONFIG} | grep "=mount$" | tail -1 | sed "s/${CONFIG}\.\(.*\)=mount/\1/")
|
||||
uci rename ${CONFIG}.${idx}="${name}"
|
||||
|
||||
uci set ${CONFIG}.${name}.enabled='0'
|
||||
uci set ${CONFIG}.${name}.server="$server"
|
||||
uci set ${CONFIG}.${name}.mountpoint="$mountpoint"
|
||||
uci set ${CONFIG}.${name}.username='guest'
|
||||
uci set ${CONFIG}.${name}._password=''
|
||||
uci set ${CONFIG}.${name}.domain=''
|
||||
uci set ${CONFIG}.${name}.cifs_version="$default_vers"
|
||||
uci set ${CONFIG}.${name}.read_only='1'
|
||||
uci set ${CONFIG}.${name}.auto_mount='0'
|
||||
uci set ${CONFIG}.${name}.description=''
|
||||
uci commit ${CONFIG}
|
||||
|
||||
log_info "Share '$name' added: $server -> $mountpoint"
|
||||
log_info "Set credentials: smbfsctl credentials $name <user> <pass>"
|
||||
}
|
||||
|
||||
cmd_remove() {
|
||||
local name="$1"
|
||||
[ -z "$name" ] && { echo "Usage: smbfsctl remove <name>" >&2; exit 1; }
|
||||
|
||||
if ! share_exists "$name"; then
|
||||
log_error "Share '$name' not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Unmount first if mounted
|
||||
local mountpoint
|
||||
mountpoint="$(uci_get ${name}.mountpoint)"
|
||||
if [ -n "$mountpoint" ] && is_mounted "$mountpoint"; then
|
||||
umount "$mountpoint" 2>/dev/null || umount -l "$mountpoint" 2>/dev/null
|
||||
fi
|
||||
|
||||
uci delete ${CONFIG}.${name}
|
||||
uci commit ${CONFIG}
|
||||
|
||||
log_info "Share '$name' removed"
|
||||
}
|
||||
|
||||
cmd_enable() {
|
||||
local name="$1"
|
||||
[ -z "$name" ] && { echo "Usage: smbfsctl enable <name>" >&2; exit 1; }
|
||||
share_exists "$name" || { log_error "Share '$name' not found"; exit 1; }
|
||||
|
||||
uci_set ${name}.enabled '1'
|
||||
uci_set ${name}.auto_mount '1'
|
||||
log_info "Share '$name' enabled for auto-mount"
|
||||
}
|
||||
|
||||
cmd_disable() {
|
||||
local name="$1"
|
||||
[ -z "$name" ] && { echo "Usage: smbfsctl disable <name>" >&2; exit 1; }
|
||||
share_exists "$name" || { log_error "Share '$name' not found"; exit 1; }
|
||||
|
||||
uci_set ${name}.enabled '0'
|
||||
uci_set ${name}.auto_mount '0'
|
||||
log_info "Share '$name' disabled"
|
||||
}
|
||||
|
||||
cmd_credentials() {
|
||||
local name="$1" user="$2" pass="$3"
|
||||
|
||||
[ -z "$name" ] || [ -z "$user" ] && {
|
||||
echo "Usage: smbfsctl credentials <name> <user> <pass>" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
share_exists "$name" || { log_error "Share '$name' not found"; exit 1; }
|
||||
|
||||
uci_set ${name}.username "$user"
|
||||
uci_set ${name}._password "$pass"
|
||||
|
||||
log_info "Credentials set for '$name' (user: $user)"
|
||||
}
|
||||
|
||||
cmd_set() {
|
||||
local name="$1" key="$2" value="$3"
|
||||
|
||||
[ -z "$name" ] || [ -z "$key" ] && {
|
||||
echo "Usage: smbfsctl set <name> <key> <value>" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
share_exists "$name" || { log_error "Share '$name' not found"; exit 1; }
|
||||
|
||||
uci_set ${name}.${key} "$value"
|
||||
log_info "Set ${name}.${key} = $value"
|
||||
}
|
||||
|
||||
cmd_list() {
|
||||
load_global
|
||||
|
||||
local found=0
|
||||
local shares
|
||||
shares="$(get_shares)"
|
||||
|
||||
if [ -z "$shares" ]; then
|
||||
echo "No SMB shares configured."
|
||||
echo "Add one: smbfsctl add <name> //server/share /mnt/smb/name"
|
||||
return
|
||||
fi
|
||||
|
||||
printf "%-12s %-8s %-28s %-24s %s\n" "NAME" "STATUS" "SERVER" "MOUNTPOINT" "USER"
|
||||
printf "%-12s %-8s %-28s %-24s %s\n" "----" "------" "------" "----------" "----"
|
||||
|
||||
for name in $shares; do
|
||||
local enabled server mountpoint username status
|
||||
|
||||
enabled="$(uci_get ${name}.enabled)"
|
||||
server="$(uci_get ${name}.server)"
|
||||
mountpoint="$(uci_get ${name}.mountpoint)"
|
||||
username="$(uci_get ${name}.username)"
|
||||
|
||||
if [ -n "$mountpoint" ] && is_mounted "$mountpoint"; then
|
||||
status="mounted"
|
||||
elif [ "$enabled" = "1" ]; then
|
||||
status="enabled"
|
||||
else
|
||||
status="disabled"
|
||||
fi
|
||||
|
||||
printf "%-12s %-8s %-28s %-24s %s\n" "$name" "$status" "$server" "$mountpoint" "${username:-guest}"
|
||||
found=1
|
||||
done
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# MOUNT OPERATIONS
|
||||
# =============================================================================
|
||||
|
||||
cmd_mount() {
|
||||
require_root
|
||||
local name="$1"
|
||||
[ -z "$name" ] && { echo "Usage: smbfsctl mount <name>" >&2; exit 1; }
|
||||
share_exists "$name" || { log_error "Share '$name' not found"; exit 1; }
|
||||
|
||||
load_global
|
||||
|
||||
local server mountpoint
|
||||
server="$(uci_get ${name}.server)"
|
||||
mountpoint="$(uci_get ${name}.mountpoint)"
|
||||
|
||||
[ -z "$server" ] && { log_error "No server configured for '$name'"; exit 1; }
|
||||
[ -z "$mountpoint" ] && { log_error "No mountpoint configured for '$name'"; exit 1; }
|
||||
|
||||
if is_mounted "$mountpoint"; then
|
||||
log_info "'$name' already mounted at $mountpoint"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Create mountpoint
|
||||
mkdir -p "$mountpoint"
|
||||
|
||||
local opts
|
||||
opts="$(build_mount_opts "$name")"
|
||||
|
||||
log_info "Mounting $server -> $mountpoint"
|
||||
if mount -t cifs "$server" "$mountpoint" -o "$opts" 2>&1; then
|
||||
log_info "Mounted '$name' successfully"
|
||||
else
|
||||
log_error "Failed to mount '$name'"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_mount_all() {
|
||||
require_root
|
||||
load_global
|
||||
|
||||
local shares count=0 fail=0
|
||||
shares="$(get_shares)"
|
||||
|
||||
for name in $shares; do
|
||||
local enabled auto_mount
|
||||
enabled="$(uci_get ${name}.enabled)"
|
||||
auto_mount="$(uci_get ${name}.auto_mount)"
|
||||
|
||||
[ "$enabled" = "1" ] && [ "$auto_mount" = "1" ] || continue
|
||||
|
||||
if cmd_mount_single "$name"; then
|
||||
count=$((count + 1))
|
||||
else
|
||||
fail=$((fail + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
log_info "Mounted $count share(s), $fail failure(s)"
|
||||
}
|
||||
|
||||
# Internal: mount a single share (no arg validation)
|
||||
cmd_mount_single() {
|
||||
local name="$1"
|
||||
local server mountpoint
|
||||
|
||||
server="$(uci_get ${name}.server)"
|
||||
mountpoint="$(uci_get ${name}.mountpoint)"
|
||||
|
||||
[ -z "$server" ] || [ -z "$mountpoint" ] && return 1
|
||||
|
||||
if is_mounted "$mountpoint"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
mkdir -p "$mountpoint"
|
||||
|
||||
local opts
|
||||
opts="$(build_mount_opts "$name")"
|
||||
|
||||
mount -t cifs "$server" "$mountpoint" -o "$opts" 2>/dev/null
|
||||
}
|
||||
|
||||
cmd_umount() {
|
||||
require_root
|
||||
local name="$1"
|
||||
[ -z "$name" ] && { echo "Usage: smbfsctl umount <name>" >&2; exit 1; }
|
||||
share_exists "$name" || { log_error "Share '$name' not found"; exit 1; }
|
||||
|
||||
local mountpoint
|
||||
mountpoint="$(uci_get ${name}.mountpoint)"
|
||||
|
||||
if [ -n "$mountpoint" ] && is_mounted "$mountpoint"; then
|
||||
umount "$mountpoint" 2>/dev/null || umount -l "$mountpoint" 2>/dev/null
|
||||
log_info "Unmounted '$name' from $mountpoint"
|
||||
else
|
||||
log_info "'$name' is not mounted"
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_umount_all() {
|
||||
require_root
|
||||
load_global
|
||||
|
||||
local shares
|
||||
shares="$(get_shares)"
|
||||
|
||||
for name in $shares; do
|
||||
local mountpoint
|
||||
mountpoint="$(uci_get ${name}.mountpoint)"
|
||||
if [ -n "$mountpoint" ] && is_mounted "$mountpoint"; then
|
||||
umount "$mountpoint" 2>/dev/null || umount -l "$mountpoint" 2>/dev/null
|
||||
log_info "Unmounted '$name'"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
cmd_status() {
|
||||
load_global
|
||||
|
||||
echo "=== SMB/CIFS Mount Status ==="
|
||||
echo ""
|
||||
|
||||
local shares
|
||||
shares="$(get_shares)"
|
||||
|
||||
if [ -z "$shares" ]; then
|
||||
echo "No shares configured."
|
||||
return
|
||||
fi
|
||||
|
||||
for name in $shares; do
|
||||
local enabled server mountpoint desc
|
||||
|
||||
enabled="$(uci_get ${name}.enabled)"
|
||||
server="$(uci_get ${name}.server)"
|
||||
mountpoint="$(uci_get ${name}.mountpoint)"
|
||||
desc="$(uci_get ${name}.description)"
|
||||
|
||||
printf "Share: %s" "$name"
|
||||
[ -n "$desc" ] && printf " (%s)" "$desc"
|
||||
echo ""
|
||||
|
||||
printf " Server: %s\n" "$server"
|
||||
printf " Mountpoint: %s\n" "$mountpoint"
|
||||
printf " Enabled: %s\n" "$([ "$enabled" = "1" ] && echo "yes" || echo "no")"
|
||||
|
||||
if [ -n "$mountpoint" ] && is_mounted "$mountpoint"; then
|
||||
# Get mount stats
|
||||
local usage
|
||||
usage=$(df -h "$mountpoint" 2>/dev/null | tail -1)
|
||||
printf " Status: MOUNTED\n"
|
||||
if [ -n "$usage" ]; then
|
||||
local size used avail pct
|
||||
size=$(echo "$usage" | awk '{print $2}')
|
||||
used=$(echo "$usage" | awk '{print $3}')
|
||||
avail=$(echo "$usage" | awk '{print $4}')
|
||||
pct=$(echo "$usage" | awk '{print $5}')
|
||||
printf " Disk: %s used / %s total (%s free, %s)\n" "$used" "$size" "$avail" "$pct"
|
||||
fi
|
||||
else
|
||||
printf " Status: NOT MOUNTED\n"
|
||||
fi
|
||||
echo ""
|
||||
done
|
||||
}
|
||||
|
||||
cmd_test() {
|
||||
local name="$1"
|
||||
[ -z "$name" ] && { echo "Usage: smbfsctl test <name>" >&2; exit 1; }
|
||||
share_exists "$name" || { log_error "Share '$name' not found"; exit 1; }
|
||||
|
||||
load_global
|
||||
|
||||
local server
|
||||
server="$(uci_get ${name}.server)"
|
||||
|
||||
# Extract hostname from //host/share
|
||||
local host
|
||||
host=$(echo "$server" | sed 's|^//||; s|/.*||')
|
||||
|
||||
log_info "Testing connectivity to $host..."
|
||||
|
||||
# Test network reachability
|
||||
if ping -c 1 -W "$timeout" "$host" >/dev/null 2>&1; then
|
||||
log_info "Host $host is reachable"
|
||||
else
|
||||
log_error "Host $host is not reachable"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Test SMB port (445)
|
||||
local smb_ok=0
|
||||
if [ -f /proc/net/tcp ]; then
|
||||
# Try a TCP connection via shell
|
||||
if (echo > /dev/tcp/"$host"/445) 2>/dev/null; then
|
||||
smb_ok=1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Fallback: try netstat or just attempt mount
|
||||
if [ "$smb_ok" = "1" ]; then
|
||||
log_info "SMB port 445 is open on $host"
|
||||
else
|
||||
log_warn "Could not verify SMB port 445 (will attempt mount anyway)"
|
||||
fi
|
||||
|
||||
# Try a test mount
|
||||
require_root
|
||||
local mountpoint="/tmp/smbfs-test-$$"
|
||||
mkdir -p "$mountpoint"
|
||||
|
||||
local opts
|
||||
opts="$(build_mount_opts "$name")"
|
||||
|
||||
if mount -t cifs "$server" "$mountpoint" -o "$opts" 2>&1; then
|
||||
log_info "Test mount successful — share is accessible"
|
||||
local count
|
||||
count=$(ls -1 "$mountpoint" 2>/dev/null | wc -l)
|
||||
log_info "Contents: $count items visible"
|
||||
umount "$mountpoint" 2>/dev/null
|
||||
else
|
||||
log_error "Test mount failed — check server, credentials, or share name"
|
||||
rmdir "$mountpoint" 2>/dev/null
|
||||
return 1
|
||||
fi
|
||||
|
||||
rmdir "$mountpoint" 2>/dev/null
|
||||
log_info "Test complete: share '$name' is working"
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# MAIN
|
||||
# =============================================================================
|
||||
|
||||
case "${1:-}" in
|
||||
add) shift; cmd_add "$@" ;;
|
||||
remove) shift; cmd_remove "$@" ;;
|
||||
enable) shift; cmd_enable "$@" ;;
|
||||
disable) shift; cmd_disable "$@" ;;
|
||||
credentials) shift; cmd_credentials "$@" ;;
|
||||
set) shift; cmd_set "$@" ;;
|
||||
list) shift; cmd_list "$@" ;;
|
||||
mount) shift; cmd_mount "$@" ;;
|
||||
mount-all) shift; cmd_mount_all "$@" ;;
|
||||
umount) shift; cmd_umount "$@" ;;
|
||||
umount-all) shift; cmd_umount_all "$@" ;;
|
||||
status) shift; cmd_status "$@" ;;
|
||||
test) shift; cmd_test "$@" ;;
|
||||
help|--help|-h|'') usage ;;
|
||||
*) echo "Unknown command: $1" >&2; usage >&2; exit 1 ;;
|
||||
esac
|
||||
@ -2393,6 +2393,62 @@
|
||||
"refresh_interval": 30,
|
||||
"metrics": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "secubox-app-smbfs",
|
||||
"name": "SMB/CIFS Mounts",
|
||||
"version": "1.0.0",
|
||||
"category": "system",
|
||||
"runtime": "native",
|
||||
"description": "SMB/CIFS remote directory mount manager for media servers, backups, and shared storage",
|
||||
"author": "CyberMind.fr",
|
||||
"license": "Apache-2.0",
|
||||
"icon": "\ud83d\udcc1",
|
||||
"tags": [
|
||||
"storage",
|
||||
"smb",
|
||||
"cifs",
|
||||
"nas",
|
||||
"media",
|
||||
"backup"
|
||||
],
|
||||
"packages": {
|
||||
"required": [
|
||||
"secubox-app-smbfs",
|
||||
"kmod-fs-cifs",
|
||||
"cifsmount"
|
||||
]
|
||||
},
|
||||
"capabilities": [
|
||||
"storage",
|
||||
"nas",
|
||||
"media-library"
|
||||
],
|
||||
"requirements": {
|
||||
"min_ram_mb": 16,
|
||||
"min_storage_mb": 1
|
||||
},
|
||||
"status": "stable",
|
||||
"notes": "Manages SMB/CIFS network mounts. Integrates with Jellyfin and Lyrion media paths.",
|
||||
"pkg_version": "1.0.0-1",
|
||||
"app_version": "1.0.0",
|
||||
"changelog": {
|
||||
"1.0.0": {
|
||||
"date": "2026-02-04",
|
||||
"changes": [
|
||||
"Initial release",
|
||||
"UCI-based share management",
|
||||
"Auto-mount at boot",
|
||||
"Jellyfin and Lyrion integration"
|
||||
]
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"enabled": false,
|
||||
"template": "default",
|
||||
"refresh_interval": 60,
|
||||
"metrics": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"featured_sections": {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user