Autoinstall lays down a thin base (sshd, key, DHCP, Docker CE, /srv) and hands
off to this repo on first boot. Everything interesting stays in git so it is
reviewable and re-runnable, rather than frozen onto a USB nobody can diff.
Stages, all idempotent:
00-preflight asserts hardware/BIOS state, changes nothing. Catches a BIOS
update having silently re-enabled Secure Boot, which would stop
the NVIDIA DKMS module loading on a box with no keyboard.
10-secrets ADD-ONLY seeder for /srv/secrets/stacks.env. Never overwrites an
existing key. Verified against a pre-populated file: existing
values, unrelated keys, the operator tier and existing manifest
lines all survive byte-for-byte; a second run is a no-op.
20-cifs the 8 shares Plex actually uses (Share is excluded, it is not a
library root). \040 escaping, nofail + x-systemd.automount +
_netdev. Managed-block rewrite verified not to duplicate or to
drop the root fstab entry.
30-nvidia nvidia-driver-580 explicitly: 580 is the LAST branch supporting
Pascal, and the -open modules need Turing+. Pins against newer
branches. Not in late-commands because DKMS needs the installed
kernel, not the installer's.
40-shell-mcp builds the native MCP locally for amd64; refuses to finish
unless /sse returns 401 without a token.
50-plex run by hand: PLEX_CLAIM expires in 4 minutes. Refuses to start
against missing mounts and disables autoEmptyTrash, which with
read-write NAS credentials is the most dangerous default here.
shell-mcp was built on arm64 originally. It builds clean on amd64 (whole dep
tree resolves to prebuilt manylinux x86_64 wheels, no compiler needed), but
dependencies are now pinned - the original installed mcp/starlette/uvicorn
unpinned and starlette has since gone 1.x. Port moved to 8103 so NPM host 42
can simply be repointed, and the tool description now says media box rather
than arrsstack.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
157 lines
5.8 KiB
Bash
Executable File
157 lines
5.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# =============================================================================
|
|
# 20-cifs — NAS mounts for the 8 shares Plex actually uses.
|
|
#
|
|
# Shares confirmed live from the Windows box 2026-07-27 (exact names/casing):
|
|
# Audio Books · Education Videos · Health · Home Movies · media
|
|
# Music Organized · Pictures · Radio Shows (+ "Share" — NOT mounted)
|
|
#
|
|
# "Share" is mounted on Windows today but is not a Plex library root, so it is
|
|
# deliberately left out. Every entry below maps to a real library section.
|
|
#
|
|
# 11 Plex library roots resolve onto these 8 mounts:
|
|
# Audio Books -> /mnt/nas/audiobooks
|
|
# Music Organized -> /mnt/nas/music-organized
|
|
# Radio Shows -> /mnt/nas/radio-shows
|
|
# Pictures -> /mnt/nas/pictures/Plex Pictures
|
|
# Health -> /mnt/nas/health
|
|
# Home Movies -> /mnt/nas/home-movies
|
|
# Education Videos -> /mnt/nas/education-videos
|
|
# media -> /mnt/nas/media/{movies,tvshows,music,audiobooks}
|
|
#
|
|
# WHY EACH OPTION IS THERE — none of these are decoration:
|
|
# nofail an unreachable NAS must NOT drop a headless
|
|
# box into an emergency shell. There is no
|
|
# keyboard attached to type the root password.
|
|
# _netdev tells systemd this needs the network up.
|
|
# x-systemd.automount mount on first access rather than at boot, so
|
|
# a slow NAS never stretches boot time.
|
|
# x-systemd.mount-timeout=30 bounded failure instead of an indefinite hang.
|
|
# x-systemd.idle-timeout=600 unmount when idle; keeps stale handles rare.
|
|
# vers=3.1.1 dialect confirmed from the Windows box.
|
|
# uid/gid=3000 MUST match Plex's PUID/PGID or every file is
|
|
# permission-denied.
|
|
# \040 fstab field separator is whitespace; share
|
|
# names with spaces MUST escape them.
|
|
# =============================================================================
|
|
set -uo pipefail
|
|
|
|
CRED="/etc/cifs/korval.cred"
|
|
NAS="10.0.1.254"
|
|
MEDIA_UID=3000
|
|
MEDIA_GID=3000
|
|
MARK_BEGIN="# >>> mediabox NAS mounts (managed by 20-cifs.sh) >>>"
|
|
MARK_END="# <<< mediabox NAS mounts <<<"
|
|
|
|
OPTS="credentials=${CRED},vers=3.1.1,uid=${MEDIA_UID},gid=${MEDIA_GID},file_mode=0664,dir_mode=0775,iocharset=utf8,nofail,_netdev,x-systemd.automount,x-systemd.mount-timeout=30,x-systemd.idle-timeout=600"
|
|
|
|
# share-name-on-nas | local mount point
|
|
SHARES=(
|
|
"Audio Books|/mnt/nas/audiobooks"
|
|
"Education Videos|/mnt/nas/education-videos"
|
|
"Health|/mnt/nas/health"
|
|
"Home Movies|/mnt/nas/home-movies"
|
|
"media|/mnt/nas/media"
|
|
"Music Organized|/mnt/nas/music-organized"
|
|
"Pictures|/mnt/nas/pictures"
|
|
"Radio Shows|/mnt/nas/radio-shows"
|
|
)
|
|
|
|
# --- --verify mode: check mounts, change nothing ----------------------------
|
|
if [ "${1:-}" = "--verify" ]; then
|
|
echo " verifying NAS mounts"
|
|
rc=0
|
|
if [ ! -s "$CRED" ]; then
|
|
echo " [FAIL] $CRED is empty — write the NAS credentials first"
|
|
exit 1
|
|
fi
|
|
for entry in "${SHARES[@]}"; do
|
|
mp="${entry#*|}"
|
|
if ls "$mp" >/dev/null 2>&1 && mountpoint -q "$mp"; then
|
|
printf ' [ok] %-28s %s\n' "$(basename "$mp")" "$(df -h --output=size "$mp" 2>/dev/null | tail -1 | tr -d ' ')"
|
|
else
|
|
printf ' [FAIL] %-28s not mounted\n' "$(basename "$mp")"
|
|
rc=1
|
|
fi
|
|
done
|
|
exit $rc
|
|
fi
|
|
|
|
echo " writing fstab entries for ${#SHARES[@]} shares"
|
|
|
|
install -d -m 0700 /etc/cifs
|
|
[ -f "$CRED" ] || install -m 0600 /dev/null "$CRED"
|
|
chmod 600 "$CRED"
|
|
|
|
# The credentials file is created EMPTY by autoinstall on purpose. The real
|
|
# password is written post-boot over the MCP so it never rides on the USB.
|
|
if [ ! -s "$CRED" ]; then
|
|
cat <<'CREDNOTE'
|
|
NOTE: /etc/cifs/korval.cred is empty. That is expected at this stage.
|
|
The mounts will fail (harmlessly, thanks to nofail) until you write:
|
|
printf 'username=<user>\npassword=<pass>\ndomain=WORKGROUP\n' \
|
|
> /etc/cifs/korval.cred
|
|
chmod 600 /etc/cifs/korval.cred
|
|
No quotes. No spaces around '='. Trailing newline required.
|
|
CREDNOTE
|
|
fi
|
|
|
|
for entry in "${SHARES[@]}"; do
|
|
mp="${entry#*|}"
|
|
install -d -m 0755 "$mp"
|
|
chown ${MEDIA_UID}:${MEDIA_GID} "$mp"
|
|
done
|
|
|
|
# Rebuild only our managed block; never touch the rest of fstab.
|
|
tmp="$(mktemp)"
|
|
awk -v b="$MARK_BEGIN" -v e="$MARK_END" '
|
|
$0 == b { skip=1 }
|
|
!skip { print }
|
|
$0 == e { skip=0 }
|
|
' /etc/fstab > "$tmp"
|
|
|
|
{
|
|
printf '%s\n' "$MARK_BEGIN"
|
|
for entry in "${SHARES[@]}"; do
|
|
share="${entry%%|*}"
|
|
mp="${entry#*|}"
|
|
# escape spaces as \040 in BOTH fields (mount points here have none, but
|
|
# the escaping is applied uniformly so a future renamed mount is safe)
|
|
esc_share="${share// /\\040}"
|
|
esc_mp="${mp// /\\040}"
|
|
printf '//%s/%s %s cifs %s 0 0\n' "$NAS" "$esc_share" "$esc_mp" "$OPTS"
|
|
done
|
|
printf '%s\n' "$MARK_END"
|
|
} >> "$tmp"
|
|
|
|
# Sanity: never install an fstab that lost the root entry.
|
|
if ! awk '$2=="/" && $1 !~ /^#/' "$tmp" | grep -q .; then
|
|
echo " [FAIL] refusing to write fstab — root entry missing from generated file"
|
|
rm -f "$tmp"; exit 1
|
|
fi
|
|
|
|
cp -a /etc/fstab "/etc/fstab.bak.$(date +%Y%m%d%H%M%S)"
|
|
install -m 0644 "$tmp" /etc/fstab
|
|
rm -f "$tmp"
|
|
|
|
systemctl daemon-reload
|
|
|
|
echo " fstab updated (backup written alongside). Managed block:"
|
|
sed -n "/${MARK_BEGIN//\//\\/}/,/${MARK_END//\//\\/}/p" /etc/fstab | sed 's/^/ /'
|
|
|
|
if [ -s "$CRED" ]; then
|
|
echo " credentials present — attempting mounts"
|
|
for entry in "${SHARES[@]}"; do
|
|
mp="${entry#*|}"
|
|
if timeout 40 mount "$mp" 2>/dev/null || mountpoint -q "$mp"; then
|
|
printf ' [ok] %s\n' "$mp"
|
|
else
|
|
printf ' [warn] %s did not mount (check credentials / share name)\n' "$mp"
|
|
fi
|
|
done
|
|
else
|
|
echo " skipping mount attempts until credentials are written"
|
|
fi
|
|
|
|
exit 0
|