diff --git a/CHANGES.md b/CHANGES.md new file mode 100755 index 0000000..39bf26d --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,138 @@ +# Changes — SMB Network Share Support + +## Overview +Updated the AME Remote Job Manager to support automatic SMB share mounting on container startup. The Docker container can now mount `//172.18.210.5/ame` using credentials configured through the Settings GUI. + +## Files Changed + +### 1. `entrypoint.sh` (NEW) +Shell script that runs when the container starts. Responsibilities: +- Read SMB credentials from `/data/settings.json` (populated by the Settings API) +- Mount the SMB share at `/mnt/smb-share` using `mount -t cifs` +- Create subdirectories on the SMB share: `watch`, `output`, `logs` +- Bind-mount these to `/watch`, `/output`, `/ame-logs` inside the container +- Fall back gracefully if SMB mount fails (uses Docker volumes as fallback) +- Start the Node.js application + +**Key features:** +- Reads settings from the persistent `/data` volume +- Handles missing credentials gracefully +- Provides clear logging of mount success/failure +- Automatically creates required subdirectories + +### 2. `Dockerfile` (UPDATED) +Changes: +- Added `cifs-utils` and `util-linux` packages for SMB mounting support +- Added `bash` package for entrypoint script execution +- Copy `entrypoint.sh` into the image and make it executable +- Changed `CMD` to use `ENTRYPOINT` with the shell script + +**Before:** +```dockerfile +FROM node:20-alpine +WORKDIR /app +COPY package*.json ./ +RUN npm install --production +COPY . . +EXPOSE 3100 +CMD ["node", "server.js"] +``` + +**After:** +```dockerfile +FROM node:20-alpine +RUN apk add --no-cache cifs-utils bash util-linux +WORKDIR /app +COPY package*.json ./ +RUN npm install --production +COPY . . +COPY entrypoint.sh ./ +RUN chmod +x ./entrypoint.sh +EXPOSE 3100 +ENTRYPOINT ["./entrypoint.sh"] +``` + +### 3. `docker-compose.yml` (UPDATED) +Changes: +- Added `cap_add: [SYS_ADMIN]` — required for mounting filesystems inside container +- Added `security_opt: [apparmor=unconfined]` — allows mount operations +- Changed volume setup to use Docker-managed volumes as fallback (entrypoint.sh will bind-mount SMB to these) +- Added `DATA_DIR=/data` environment variable for settings persistence +- Updated comments to clarify the SMB mounting flow + +**Key additions:** +```yaml +cap_add: + - SYS_ADMIN +security_opt: + - apparmor=unconfined +``` + +### 4. `README.md` (UPDATED) +Added comprehensive new section: "SMB Network Share Configuration" + +**New content includes:** +- How automatic SMB mounting works (3-step process) +- Step-by-step configuration instructions (5 steps) +- Instructions for creating subdirectories on the SMB share +- Troubleshooting guide for common mount issues +- Alternative approach for pre-mounted SMB shares +- Updated Prerequisites section with SMB requirements + +## How It Works (User Flow) + +1. **Container starts** → `entrypoint.sh` runs +2. **Script reads** → `/data/settings.json` for SMB credentials +3. **If credentials present** → `mount -t cifs //172.18.210.5/ame /mnt/smb-share` with auth +4. **Creates subdirectories** → `watch`, `output`, `logs` on the mounted share +5. **Bind-mounts** → `/mnt/smb-share/watch → /watch`, etc. +6. **Node.js starts** → sees `/watch`, `/output`, `/ame-logs` as local paths +7. **User configures SMB** → through Settings GUI in web UI +8. **User restarts container** → mounts SMB on next startup + +## Backward Compatibility + +✅ **Fully backward compatible:** +- If no SMB credentials are configured, the entrypoint falls back to Docker-managed volumes +- Existing deployments that bind-mount SMB at the host level continue to work +- The entrypoint checks `if [ -f "$SETTINGS_FILE" ]` before reading — graceful on first boot + +## Security Notes + +- **Passwords never exposed**: SMB password stored server-side in `/data/settings.json`, never sent to browser +- **Settings API masks password**: Returns `smbPasswordSet: true/false` indicator, never the actual password +- **Credentials at rest**: Only stored in the `/data` volume (which should be on secure storage) +- **Network transmission**: Use HTTPS/TLS in production to protect credentials in transit + +## Testing Checklist + +- [ ] Build Docker image: `docker compose build` +- [ ] Start container: `docker compose up -d` +- [ ] Check logs: `docker logs ame-job-manager` (look for mount messages) +- [ ] Access UI: `http://localhost:3100` +- [ ] Open Settings, enter SMB credentials +- [ ] Save settings +- [ ] Restart container: `docker compose restart` +- [ ] Verify SMB mount: `docker exec ame-job-manager mount | grep cifs` +- [ ] Verify bind-mounts: `docker exec ame-job-manager ls -la /watch /output /ame-logs` +- [ ] Upload a `.prproj` file and verify it appears on the SMB share + +## Deployment Instructions + +1. Pull the latest code +2. Update `docker-compose.yml` with any local customizations +3. Ensure the SMB machine has subdirectories created: + - `\\172.18.210.5\ame\watch` + - `\\172.18.210.5\ame\output` + - `\\172.18.210.5\ame\logs` +4. Start/restart the container: `docker compose up -d` +5. Configure SMB credentials in Settings → save → restart container + +## Rollback + +If you need to revert to the previous version: +1. Checkout the previous commit +2. Comment out the `cap_add` and `security_opt` lines in `docker-compose.yml` +3. Rebuild and restart + +All changes are additive and don't break existing functionality. diff --git a/Dockerfile b/Dockerfile index b027478..ffd714b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,18 @@ FROM node:20-alpine + +# Install cifs-utils and bash for SMB mounting +RUN apk add --no-cache cifs-utils bash util-linux + WORKDIR /app + COPY package*.json ./ RUN npm install --production + COPY . . +COPY entrypoint.sh ./ +RUN chmod +x ./entrypoint.sh + EXPOSE 3100 -CMD ["node", "server.js"] + +# Entrypoint script handles SMB mounting before starting Node +ENTRYPOINT ["./entrypoint.sh"] diff --git a/README.md b/README.md index 947da45..7cd86c3 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,13 @@ A web-based job submission and monitoring tool for Adobe Media Encoder (AME) in ## Prerequisites -- Docker and Docker Compose +- Docker and Docker Compose (with `SYS_ADMIN` capability enabled for SMB mounting) - Adobe Media Encoder running on a Windows machine with a configured watch folder -- The watch folder and output folder accessible as Docker volume mounts (e.g. via SMB) +- SMB network share accessible at `//172.18.210.5/ame` containing: + - `watch` — folder where .prproj files are picked up by AME + - `output` — folder where AME writes encoded files + - `logs` — folder where AME writes `AMEEncodingLog.txt` +- Network connectivity from Docker host to SMB server - `.prproj` files that reference `.gves` proxy media on the AMPP platform ## Quick Start @@ -82,6 +86,55 @@ volumes: job-data: ``` +## SMB Network Share Configuration + +The Docker container can automatically mount an SMB share on startup using credentials stored in the Settings GUI. This is useful when the watch folder, output folder, and logs are on a remote network share. + +### How It Works + +1. The `entrypoint.sh` script reads SMB credentials from `settings.json` (persisted in `/data`) +2. On container startup, it mounts the SMB share at `/mnt/smb-share` using `mount -t cifs` +3. It then bind-mounts subdirectories to `/watch`, `/output`, and `/ame-logs` +4. The Node.js app accesses these paths as if they were local + +### Configuration Steps + +1. **Start the container** (it will run with fallback Docker volumes if no SMB credentials yet) + +2. **Open the Settings panel** in the web UI (⚙️ icon in header) + +3. **Fill in SMB credentials:** + - **SMB Username**: e.g., `encoder` or `DOMAIN\encoder` + - **SMB Password**: Network password (stored server-side, never exposed to browser) + - **SMB Domain/Workgroup**: Optional, e.g., `WORKGROUP` or `BMG` + - **Notes**: Optional reference, e.g., `\\172.18.210.5\ame` + +4. **Create subdirectories on the SMB share** (if they don't exist): + ``` + \\172.18.210.5\ame\ + ├── watch\ (AME's watch folder) + ├── output\ (AME's output folder) + └── logs\ (Contains AMEEncodingLog.txt) + ``` + +5. **Restart the container** — it will mount the SMB share on next startup + +### Troubleshooting SMB Mount Issues + +- **Mount failed**: Check that credentials are correct and the SMB server is reachable +- **Permission denied**: Verify the SMB user has read/write access to the share +- **Container falls back to local volumes**: Check Docker logs for mount errors: `docker logs ame-job-manager` +- **Shares already mounted locally**: If the watch/output/logs folders are already mounted on the Docker host via `/etc/fstab`, use bind-mounts in `docker-compose.yml` instead: + +```yaml +volumes: + - /mnt/host-smb-share/watch:/watch + - /mnt/host-smb-share/output:/output + - /mnt/host-smb-share/logs:/ame-logs +``` + +In this case, you don't need to configure SMB credentials in the app settings. + ## Job Lifecycle | Status | Meaning | diff --git a/docker-compose.yml b/docker-compose.yml index 9b9548b..4c4492d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,3 +1,5 @@ +version: '3.8' + services: ame-job-manager: build: . @@ -9,16 +11,15 @@ services: - PORT=3100 - AUTH_USER=Admin - AUTH_PASS=BertAndErnieVPM2026 - # Watch folder: mount AME's watch folder here + # Container-local mount points (not directly used — entrypoint.sh manages these) - WATCH_FOLDER=/watch - # Output folder: mount AME's output/render folder here - OUTPUT_FOLDER=/output + - AME_LOG_DIR=/ame-logs + - DATA_DIR=/data # Polling interval for folder monitoring (ms) - POLL_INTERVAL_MS=5000 # Job timeout before marking as stuck (ms) — default 1 hour - JOB_TIMEOUT_MS=3600000 - # AME log directory (mount the folder containing AMEEncodingLog.txt) - - AME_LOG_DIR=/ame-logs # S3 config (same creds as framelightx-uploader, encoder bucket) - S3_ENDPOINT=https://broadcastmgmt.cloud - S3_ACCESS_KEY=c9L2fu581a3gnH7rSdBJ @@ -26,23 +27,19 @@ services: - S3_BUCKET=encoder - S3_REGION=us-east-1 volumes: + # Persistent data store (settings, job DB, sessions) - app_data:/data + # Temporary upload storage - upload_tmp:/tmp/uploads - # Mount your AME watch folder and output folder: - # Windows example: - # - C:\Users\Editor\AME\Watch:/watch - # - C:\Users\Editor\AME\Output:/output - # macOS example: - # - /Users/editor/AME/Watch:/watch - # - /Users/editor/AME/Output:/output + # Local volumes as fallback (used if SMB mount fails) - watch_folder:/watch - output_folder:/output - # AME log directory — mount the folder containing AMEEncodingLog.txt - # macOS example (update and ): - # - /Users//Documents/Adobe/Adobe Media Encoder/:/ame-logs:ro - # Windows example: - # - C:\Users\\Documents\Adobe\Adobe Media Encoder\:/ame-logs:ro - ame_logs:/ame-logs + # Required capabilities for SMB mounting + cap_add: + - SYS_ADMIN + security_opt: + - apparmor=unconfined volumes: app_data: diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 0000000..6bb3d4b --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,78 @@ +#!/bin/bash +set -e + +echo "=== AME Remote Job Manager — Entrypoint ===" + +# Create mount directories +mkdir -p /watch /output /ame-logs /mnt/smb-share + +# Settings file path +SETTINGS_FILE="/data/settings.json" +SMB_SHARE_PATH="//172.18.210.5/ame" + +# Function to get setting value from JSON +get_setting() { + local key=$1 + local default=$2 + if [ -f "$SETTINGS_FILE" ]; then + value=$(grep -o "\"$key\":\"[^\"]*\"" "$SETTINGS_FILE" 2>/dev/null | cut -d'"' -f4) + if [ -n "$value" ]; then + echo "$value" + return + fi + fi + echo "$default" +} + +# Read SMB credentials from settings.json (or use env vars as fallback) +SMB_USERNAME=$(get_setting 'smbUsername' "${SMB_USERNAME:-}") +SMB_PASSWORD=$(get_setting 'smbPassword' "${SMB_PASSWORD:-}") +SMB_DOMAIN=$(get_setting 'smbDomain' "${SMB_DOMAIN:-}") + +# Try to mount SMB share only if credentials are provided +if [ -n "$SMB_USERNAME" ] && [ -n "$SMB_PASSWORD" ]; then + echo "Mounting SMB share with credentials..." + + # Build mount options + MOUNT_OPTS="username=$SMB_USERNAME,password=$SMB_PASSWORD" + + if [ -n "$SMB_DOMAIN" ]; then + MOUNT_OPTS="$MOUNT_OPTS,domain=$SMB_DOMAIN" + fi + + # Add standard options for Linux mounts + MOUNT_OPTS="$MOUNT_OPTS,uid=1000,gid=1000,file_mode=0755,dir_mode=0755,vers=3.0" + + if mount -t cifs "$SMB_SHARE_PATH" /mnt/smb-share -o "$MOUNT_OPTS" 2>&1; then + echo "✓ SMB share mounted at /mnt/smb-share" + else + echo "⚠ Failed to mount SMB share. Check credentials and network connectivity." + echo " Will continue with local volumes. Mount SMB and restart container to use network share." + fi +else + echo "⚠ No SMB credentials found in settings. Skipping SMB mount." + echo " Configure SMB credentials in the settings GUI and restart the container." +fi + +# Bind mount the SMB directories to container paths (if mount succeeded) +if mountpoint -q /mnt/smb-share; then + echo "Binding SMB subdirectories..." + mkdir -p /mnt/smb-share/watch /mnt/smb-share/output /mnt/smb-share/logs + + mount --bind /mnt/smb-share/watch /watch 2>/dev/null || echo "⚠ Could not bind watch folder" + mount --bind /mnt/smb-share/output /output 2>/dev/null || echo "⚠ Could not bind output folder" + mount --bind /mnt/smb-share/logs /ame-logs 2>/dev/null || echo "⚠ Could not bind logs folder" + + echo "✓ Mount points configured" +else + echo "⚠ SMB share not mounted. Using local docker volumes as fallback." +fi + +# Verify watch folder exists +if [ ! -d "/watch" ]; then + mkdir -p /watch + echo "Created /watch directory" +fi + +echo "Starting Node.js application..." +exec node server.js