Personal NAS Setup Guide
0% complete
Beginner-safe · private by design

Personal NAS Setup Guide

Turn an old Dell laptop and your existing drives into a reliable private NAS. Every action is explained in plain language—from preparing the pen drive to remote access, backups, recovery, and safe everyday use.

Start the setup
21Complete chapters
214Trackable milestones
₹0 core stackNo paid software required
PrivateNo public port forwarding
No experience assumedFollow the chapters in order the first time; use search when you return.
Safety before speedWarnings and disk-erasure checkpoints stay visible at the exact step.
Progress stays privateYour checkboxes are saved only in this browser on this device.

Read this first

Before you begin

This is the source guide’s complete project summary, hardware scope, safety warning, and definition of the finished NAS.

This guide turns the following confirmed hardware into a private, remotely started on-demand NAS:

  • Dell Latitude E5400 with a 64-bit Intel Core 2 Duo P8700 and 4GB RAM (2×2GB mixed DDR2)
  • 128GB Zebronics ZEB-SD13 internal SSD for the operating system
  • 250GB Hitachi HTS545025B9A300 internal HDD for daily/catch-up critical-subset backups
  • 1TB SanDisk Extreme Portable SSD (SDSSDE61-1T00) for primary data
  • 1TB Toshiba Canvio Basics HDD (HDTP310) for disconnected weekly backups
  • Optional 250GB Seagate Barracuda 7200.12 HDD (ST3250318AS, certified repaired) for a tertiary copy or testing only
  • A confirmed 100Mbps wired Ethernet connection and a 75Mbps internet plan
  • A Wi-Fi smart plug used for remote startup after a clean operating-system shutdown

The finished NAS provides:

  • Normal SMB network drives at home in Windows, Android, macOS, and iPhone/iPad
  • Private remote access through Tailscale, with no router port forwarding
  • A browser-based file manager with image and video thumbnails
  • Separate private folders plus shared Family and Media folders
  • Daily/catch-up and weekly versioned backups
  • Email and health alerts while the NAS is running
  • An optional, later S3-compatible service for private development

The core setup uses no paid software, subscription, public domain, or router port forwarding. It relies on Tailscale's hosted Personal plan within its current free limits. A continuous external heartbeat is intentionally omitted because planned powered-off periods would create false alerts. Automatic phone-camera backup, live video transcoding, universal codec preview, RAID, and production-grade S3 durability are intentionally out of scope.

[!IMPORTANT] This procedure erases the four core NAS disks. The optional Seagate is erased only if you deliberately prepare it later. Read the complete guide once before starting. Copy every existing file you care about to storage that is not part of this NAS.

[!WARNING] An on-demand home NAS is not a guaranteed cloud service. It is unreachable while powered off, and a failed smart plug, vendor cloud, router, power adapter, USB cable, motherboard, or disk may require physical help. Backups reduce data-loss risk; they do not create guaranteed availability.

All chapters are expanded for continuous reading. Collapse anything you do not need.
Physical diskLabel used in this guidePurposeNormally connected?
128GB Zebronics ZEB-SD13 internal SSDSYSTEMOpenMediaVault, Debian, Docker, application settings, and thumbnail cacheYes
1TB SanDisk Extreme Portable SSD, SDSSDE61-1T00NAS_PRIMARYLive private files, shared files, media, and future app objectsYes
250GB Hitachi HTS545025B9A300 internal HDDNAS_CRITICALDaily/catch-up versioned backup of private and important shared filesWhenever NAS is on
1TB Toshiba Canvio Basics, HDTP310NAS_OFFLINEWeekly versioned backup of the complete primary diskOnly during backup
250GB Seagate ST3250318AS in powered USB 2.0 enclosureNAS_TERTIARY (optional)Extra manual copy or destructive testing; never a primary or sole backupNormally disconnected

Do not combine these disks into RAID or a storage pool. You receive about 1TB of primary usable space, while the other disks provide independent recovery copies. The Seagate label says Certified Repaired HDD, so it is deliberately excluded from the required backup chain even if its SMART tests pass.

The backup disks are smaller than, or equal in size to, the source. Version history also consumes space. The 250GB NAS_CRITICAL disk protects only the most important subset; it cannot hold a versioned copy of the complete 1TB primary disk. Until real retention growth is measured, keep the selected critical source below roughly 150-180GB and preferably lower. The 1TB Toshiba protects the complete primary dataset, but its Restic history also needs free space. If either backup repository approaches 80%, reduce the protected dataset, adjust retention only after confirming good recent snapshots, or replace the disk with a larger one. Never let irreplaceable primary data grow beyond what the offline repository can protect.

The battery report recorded 48,840mWh design capacity and only 2,020mWh full-charge capacity, about 4.1% remaining health. Treat it as an unreliable emergency shutdown buffer, not a UPS. A power guard added in Section 7 requests a clean shutdown after approximately 30-45 seconds without AC, and that timing is accepted only after a controlled test. Normal shutdown is always requested through OMV before the smart plug is switched off.

The SanDisk primary disk will contain:

text
NAS_PRIMARY/
├── Private/
│   ├── owner/
│   ├── family1/
│   └── family2/
├── Family/
├── Media/
└── AppObjects/
  • Private/<username> is visible only to that person and the FileBrowser service.
  • Family and Media are writable by all family accounts.
  • AppObjects is not exposed through SMB or FileBrowser. It is reserved for the optional Garage S3 service.

At home, SMB works with Tailscale turned off. Away from home, turn Tailscale on and use the saved remote connection. FileBrowser is intentionally available only through Tailscale, even when you are home, so its password never crosses the LAN using plain HTTP.

The confirmed network limits are acceptable for this personal NAS:

  • Ethernet consistently negotiates at 100Mbps and is an accepted router-side limit. The same result was reproduced on the Dell and a new Lenovo using separate Cat6 and short Cat5e cables. Expect roughly 8-11MB/s for large local transfers.
  • The 75Mbps internet plan theoretically tops out near 9.4MB/s before protocol, Tailscale, and routing overhead; actual remote speed also depends on the plan's upload rate.
  • The E5400 supports Gigabit Ethernet, but no Gigabit upgrade is required for this build. Its USB 2.0 ports would independently limit external storage below modern SSD speeds.
  • Use wired Ethernet for the server. After setup succeeds, disable Wi-Fi unless it is deliberately configured and documented as an emergency fallback.
  • Browser video playback depends on the original codec. This server will not transcode video live.

Common JPEG, PNG, GIF, WebP, HEIC (with conversion), MP4, and WebM files can have previews. Unusual formats should be downloaded or opened in an app such as VLC.

The smart plug supplies or removes AC power; it is not a safe operating-system shutdown control. To start the NAS remotely, turn the plug on and let the BIOS Power On when AC is restored setting boot the laptop. Allow approximately 2-5 minutes for OMV, storage, Docker, Tailscale, and FileBrowser to become ready.

To stop the NAS, first finish file transfers and backups, use the owner-only OMV page at System → Power Management → Shutdown, and wait until the NAS disappears from Tailscale. Wait at least another 60 seconds before switching the smart plug off. Never cut the plug while Linux is running, updating, checking a disk, pruning Restic, or writing files.

Because the NAS may be off at the nominal backup time, Section 14 uses a boot timer as well as a daily calendar timer. Every boot therefore catches up the critical backup instead of silently missing it. The Toshiba full backup remains a deliberate, physically present weekly operation.


  • Old laptop and its original or a safe replacement power adapter
  • Installed battery inspected for swelling or damage; do not assume it provides useful runtime
  • Short Cat5e Ethernet cable connected directly to the router; confirmed 100Mbps negotiation is accepted
  • Wi-Fi smart plug rated safely above the laptop adapter load and enrolled in the owner's protected account
  • 4GB-or-larger USB pen drive for the installer
  • 128GB Zebronics ZEB-SD13 system SSD installed in the laptop
  • A safe way to disconnect the internal 250GB Hitachi HDD during OS installation
  • 1TB SanDisk Extreme Portable SSD and its original or known-good USB cable
  • 1TB Toshiba Canvio Basics HDD and its known-good USB cable
  • Optional repaired 250GB Seagate and its externally powered USB 2.0 enclosure, kept outside the core design
  • Another computer running Windows to prepare the installer
  • Temporary storage containing a verified copy of all existing files

If the battery is swollen, cracked, unusually hot, or lifting the laptop case, stop. Remove it according to the laptop manufacturer's instructions and recycle it safely. Do not operate a damaged lithium battery unattended.

This guide uses examples. Replace them consistently.

ItemExampleYour value
NAS hostnamenas____________________
Owner Linux/SMB usernameowner____________________
Family username 1family1____________________
Family username 2family2____________________
Owner Tailscale loginowner@example.com____________________
Family Tailscale login 1family1@example.com____________________
Family Tailscale login 2family2@example.com____________________
Reserved local IP, if available192.168.1.50____________________

Use lowercase letters and numbers for Linux usernames. Do not use spaces.

Use a password manager. Create different strong passwords for:

  • OMV root
  • OMV web administrator
  • Owner Linux/SMB account
  • Each family SMB account
  • FileBrowser administrator
  • Each family FileBrowser account
  • Restic backup repository
  • Email SMTP app password, if needed

Also keep the Restic password on a paper emergency sheet or a second password manager. If the system SSD fails and this is the only copy, encrypted backups cannot be restored.

Never reuse an internet email password as an SMB or NAS password.


Perform these checks before erasing Windows.

  1. Press Windows + R.
  2. Type msinfo32 and press Enter.
  3. Find System Type.
  4. Continue only if it says x64-based PC.

Using 64-bit Windows previously is also strong evidence that the laptop is x86-64. OpenMediaVault 8's official ISO is AMD64 and BIOS-mode boot.

Open PowerShell as Administrator and run:

powershell
Get-CimInstance Win32_Processor | Select-Object Name,AddressWidth,NumberOfCores
Get-CimInstance Win32_ComputerSystem | Select-Object TotalPhysicalMemory
Get-NetAdapter | Select-Object Name,Status,LinkSpeed,MacAddress

Write down the Ethernet adapter MAC address. It helps create a router DHCP reservation later.

For this laptop, the confirmed Windows link results were 100Mbps Ethernet and 270Mbps Wi-Fi. A new Lenovo also negotiated at 100Mbps through the same router, and both a Cat6 cable and a short Cat5e cable produced the same result. This isolates the accepted limit to the router-side network path rather than the Dell BIOS or NIC. Internet-plan speed and LAN link speed are different measurements; the 75Mbps plan does not make the LAN link Gigabit. Use the short Cat5e cable and keep Ethernet as the normal NAS connection because it is more predictable than Wi-Fi.

In Administrator PowerShell:

powershell
powercfg /batteryreport /output "$env:USERPROFILE\Desktop\battery-report.html"

Open the report from the Desktop. Compare Design Capacity with Full Charge Capacity. The inspected report showed 48,840mWh design capacity and 2,020mWh full-charge capacity, or about 4.1% health. Regenerate the report after correcting the Windows date and time if its timestamp is stale. Record this battery as unreliable shutdown buffer only. Do not depend on its percentage estimate or deliberately run it flat. It must still sustain the laptop and connected SanDisk SSD through the controlled 30-second guard test and the remainder of a clean shutdown. A swollen or unstable battery must not be used.

  1. Press Windows + R.
  2. Run mdsched.exe.
  3. Choose Restart now and check for problems.
  4. Let the complete test finish.
  5. Do not continue if it reports memory errors. Reseat or replace the faulty RAM first.

The installed modules are mixed: one 2GB DDR2-667 module and one 2GB DDR2-800 module. They will normally operate at the slower compatible speed. This is acceptable only if an extended or overnight memory test completes with zero errors.

Connect one external drive at a time. In Administrator PowerShell, run:

powershell
Get-Disk | Sort-Object Number | Format-Table Number,FriendlyName,SerialNumber,PartitionStyle,OperationalStatus,@{N='SizeGB';E={[math]::Round($_.Size/1GB,1)}}
Get-PhysicalDisk | Format-Table FriendlyName,SerialNumber,MediaType,HealthStatus,@{N='SizeGB';E={[math]::Round($_.Size/1GB,1)}}

Photograph or save the output. Also photograph each disk's physical label. USB bridges sometimes hide serial numbers, so record model, capacity, cable, and USB port as well.

Before erasing Windows, use SanDisk's current official utility or support instructions to check the Extreme Portable SSD firmware. The published unexpected-disconnection advisory currently lists the 4TB SDSSDE61, not this confirmed 1TB SDSSDE61-1T00 model, but still record the installed firmware and complete the health tests. Back up and verify important data before applying any firmware update.

  • Open random photos, documents, and videos from the temporary copy.
  • Confirm the temporary copy is not on any core NAS disk or the optional Seagate.
  • Confirm cloud-only files have finished downloading if they must be preserved.
  • Export browser bookmarks, license keys, and anything stored only on Windows.
  • Shut Windows down completely.

Do not proceed until all five boxes are checked.


  1. Open the OpenMediaVault ISO installation page.
  2. Follow its official download link.
  3. Download the latest OpenMediaVault 8.x AMD64 ISO, not an ARM image and not OMV 7.
  4. Download the matching SHA-256 checksum file if it is provided beside the ISO.

Do not download an ISO from a blog, file mirror, video description, or random forum link.

Open PowerShell in the download folder:

powershell
Get-FileHash .\openmediavault_*.iso -Algorithm SHA256

Compare every character with the SHA-256 value published for that exact ISO. If it differs, delete the ISO and download it again.

  1. Download Balena Etcher from its official site.
  2. Insert the USB pen drive.
  3. Open Etcher.
  4. Choose Flash from file and select the verified OMV ISO.
  5. Choose the pen drive. Check its capacity twice.
  6. Select Flash.
  7. Wait for both flashing and validation to finish successfully.
  8. Safely eject the pen drive.

Everything previously on the pen drive is erased.


[!CAUTION] The OMV ISO installer selects the first available disk and consumes it completely. It does not provide a normal disk-selection workflow.

Before booting the installer:

  1. Disconnect the SanDisk SSD.
  2. Disconnect the Toshiba HDD.
  3. Disconnect the optional Seagate enclosure if present.
  4. Shut down and unplug the laptop.
  5. Disconnect or remove the internal 250GB Hitachi HDD.
  6. Leave only the 128GB Zebronics system SSD installed.
  7. Reconnect power, Ethernet, keyboard/display if needed, and the installer pen drive.

If you cannot disconnect or disable the internal 250GB Hitachi disk, stop. Do not gamble with the official ISO. Use the documented Debian 13 manual-install route instead or get help physically removing the disk.

Enter BIOS/Setup, commonly with F2, F10, F12, Delete, or Esc immediately after powering on.

Set what the laptop supports:

  • Boot mode: Legacy BIOS or CSM for the official OMV ISO
  • USB boot: enabled
  • First temporary boot device: the installer pen drive
  • SATA mode: AHCI, if changing it does not hide the SSD
  • Restore after AC power loss: Power On (confirmed enabled; required for smart-plug startup)
  • Wake on LAN: optional
  • Battery charge limit: 50-80%, if the BIOS offers it
  • Secure Boot: disabled, if present

Do not enable RAID mode.

The inspected laptop already runs Dell BIOS A19, Dell's final published E5400 BIOS release found for this guide. Do not reflash the same version, especially with the nearly exhausted battery.

  1. Boot from the USB pen drive.
  2. Choose the installer option.
  3. Select language, location, keyboard, and timezone.
  4. Set the strong root password prepared earlier.
  5. Allow the installer to use the 128GB Zebronics SSD.
  6. Keep Ethernet and internet connected throughout installation.
  7. Wait for installation to complete.
  8. Remove the installer pen drive when told to reboot.
  9. Boot from the 128GB Zebronics SSD.

The console login screen should show an IP address such as 192.168.1.123.

From another device on the same home network, open:

text
http://IP_ADDRESS_SHOWN_ON_THE_NAS

Use the initial web credentials:

text
Username: admin
Password: openmediavault

Immediately open the user/account menu and change the web administrator password.


Menu names can move slightly between OMV 8 point releases. Apply/save every yellow pending-configuration banner before moving on.

  1. Open System > Workbench and confirm the web session timeout is reasonable.
  2. Open Network > General.
  3. Set hostname to nas or the name selected earlier.
  4. Set the domain to home.arpa if OMV asks for one.
  5. Confirm Asia/Kolkata timezone and NTP/time synchronization.
  6. Apply changes.

Use home.arpa, not .local, as the configured private DNS domain. Devices may still discover nas.local through mDNS.

Open System > Update Management > Updates:

  1. Check for updates.
  2. Select all normal stable updates.
  3. Install them.
  4. Reboot if the kernel, systemd, or OMV requests it.
  5. Log in again and confirm no updates remain.

Do not add Debian Testing, Unstable, Ubuntu, or random package repositories.

Preferred method: reserve the current IP in the router.

  1. Find the router address using ipconfig on Windows; it is the Default Gateway.
  2. Open that address in a browser.
  3. Look for LAN, DHCP, Address Reservation, or Static Lease.
  4. Reserve the NAS's current IP for the Ethernet MAC address recorded earlier.
  5. Reboot the NAS and confirm it receives the same IP.

If router administration is unavailable, leave OMV on DHCP. Use the hostname at home and the stable Tailscale name remotely. Record the current IP from the OMV dashboard after every router replacement.

In Users > Users, create the owner account:

  • Name: your lowercase owner username
  • Shell: /bin/bash
  • Groups: _ssh and sudo
  • Strong password: the owner Linux/SMB password
  • SSH public key: add one later if you do not already have it

Do not add family accounts to _ssh, sudo, or openmediavault-admin.

From Windows PowerShell:

powershell
ssh owner@NAS_LOCAL_IP

Replace owner and NAS_LOCAL_IP. After login:

bash
sudo -v
hostnamectl
exit

Only after that succeeds:

  1. Open Services > SSH in OMV.
  2. Keep SSH enabled.
  3. Disable root login.
  4. Keep password login temporarily enabled.
  5. Apply changes.

Section 6.7 adds an SSH key and then disables password authentication. Never disable both working login methods at once.

SSH as the owner and run:

bash
uname -a
cat /etc/os-release
free -h
lsblk -o NAME,SIZE,MODEL,SERIAL,TYPE,FSTYPE,MOUNTPOINTS
ip -br address
ip route

Save the output in your password manager's NAS note, excluding anything sensitive.

On the owner's Windows computer, open PowerShell as the normal user:

powershell
ssh-keygen -t ed25519 -a 64
Get-Content "$env:USERPROFILE\.ssh\id_ed25519.pub" | ssh owner@NAS_LOCAL_IP "umask 077; mkdir -p ~/.ssh; cat >> ~/.ssh/authorized_keys"

Replace owner and NAS_LOCAL_IP. Protect the private key at id_ed25519; never copy it to the NAS or send it to anyone. Open a new PowerShell window and confirm ssh owner@NAS_LOCAL_IP works without the account password. Keep the first SSH session open during this test.

After the key works and a protected backup of it exists:

  1. In Services → SSH, disable password authentication.
  2. Keep public-key authentication and SSH enabled.
  3. Keep root login disabled.
  4. Apply changes.
  5. Open one more new SSH connection before closing the old one.

Disabling SSH password authentication does not disable the same user's SMB password.


SSH to the NAS and open the systemd login configuration:

bash
sudo systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target
sudo systemctl status sleep.target suspend.target hibernate.target hybrid-sleep.target

Then edit:

bash
sudo nano /etc/systemd/logind.conf.d/nas-lid.conf

Paste:

ini
[Login]
HandleLidSwitch=ignore
HandleLidSwitchExternalPower=ignore
HandleLidSwitchDocked=ignore
IdleAction=ignore

Save with Ctrl+O, Enter, then exit with Ctrl+X. Apply it:

bash
sudo systemctl restart systemd-logind

Your SSH session may close. Reconnect and confirm the NAS stays running with the lid closed for ten minutes.

Install the Debian sensor utilities:

bash
sudo apt update
sudo apt install --yes lm-sensors smartmontools curl nano
sudo sensors-detect
sensors

Accept safe default probes. Not every old laptop exposes sensors. Clean dust from vents, keep the laptop on a hard nonflammable surface, and never block its intake or exhaust.

During a large copy, run:

bash
watch -n 5 sensors

Sustained CPU temperatures near the laptop's thermal limit, repeated throttling, fan grinding, or unexpected shutdowns must be fixed before unattended use.

First inspect availability:

bash
ls -l /dev/watchdog* 2>/dev/null || true
systemd-analyze cat-config systemd/system.conf | grep -i watchdog

If /dev/watchdog0 exists, edit:

bash
sudo nano /etc/systemd/system.conf.d/watchdog.conf

Paste:

ini
[Manager]
RuntimeWatchdogSec=30s
RebootWatchdogSec=10min

Then run:

bash
sudo systemctl daemon-reexec
systemctl show --property=RuntimeWatchdogUSec --property=RebootWatchdogUSec

If no watchdog device exists, skip this setting. Do not install an unknown kernel watchdog module merely to make the path appear.

This battery retains only about 4.1% of its original measured capacity and can fall from an apparently safe percentage to empty without warning. This guard watches the AC adapter's Linux power-supply online state instead of battery percentage. It takes no action during a momentary flicker. With a 30-second grace and a 15-second polling interval, it normally requests a systemd shutdown approximately 30-45 seconds after AC loss.

First identify the adapter entry:

bash
for supply in /sys/class/power_supply/*; do
  printf '%s  type=%s  online=%s  status=%s\n' \
    "$supply" \
    "$(cat "$supply/type" 2>/dev/null || echo unknown)" \
    "$(cat "$supply/online" 2>/dev/null || echo n/a)" \
    "$(cat "$supply/status" 2>/dev/null || echo n/a)"
done

With AC connected, at least one adapter of type Mains, USB, USB_C, or USB_PD must report online=1. Unplug AC briefly and confirm that same entry changes to 0. If no reliable entry changes, do not enable the guard; the laptop firmware does not expose the signal this generic procedure needs.

Create /usr/local/sbin/nas-power-guard:

bash
sudo nano /usr/local/sbin/nas-power-guard
bash
#!/usr/bin/env bash
set -Eeuo pipefail
umask 077

STATE_FILE=/run/nas-ac-loss-start
GRACE_SECONDS=30
adapter_found=0
ac_online=0

for supply in /sys/class/power_supply/*; do
  [ -r "$supply/type" ] && [ -r "$supply/online" ] || continue
  kind=$(<"$supply/type")
  case "$kind" in
    Mains|USB|USB_C|USB_PD)
      adapter_found=1
      if [ "$(<"$supply/online")" = "1" ]; then
        ac_online=1
        break
      fi
      ;;
  esac
done

if [ "$adapter_found" -ne 1 ]; then
  logger -t nas-power-guard "No readable AC-adapter power_supply entry; refusing to shut down"
  exit 1
fi

if [ "$ac_online" -eq 1 ]; then
  if [ -e "$STATE_FILE" ]; then
    logger -t nas-power-guard "AC power restored during grace period"
    rm -f "$STATE_FILE"
  fi
  exit 0
fi

now=$(date +%s)
if [ ! -e "$STATE_FILE" ]; then
  printf '%s\n' "$now" > "$STATE_FILE"
  logger -t nas-power-guard "AC power lost; starting ${GRACE_SECONDS}-second shutdown grace period"
  exit 0
fi

start=$(cat "$STATE_FILE")
if ! [[ "$start" =~ ^[0-9]+$ ]]; then
  printf '%s\n' "$now" > "$STATE_FILE"
  logger -t nas-power-guard "Invalid state file replaced; grace period restarted"
  exit 1
fi

elapsed=$((now - start))
if [ "$elapsed" -ge "$GRACE_SECONDS" ]; then
  logger -t nas-power-guard "AC absent for ${elapsed} seconds; requesting clean poweroff"
  rm -f "$STATE_FILE"
  systemctl poweroff
fi

Install the script and service:

bash
sudo chmod 700 /usr/local/sbin/nas-power-guard
sudo nano /etc/systemd/system/nas-power-guard.service
ini
[Unit]
Description=Check AC power and request a clean shutdown after the grace period

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/nas-power-guard

Create the timer:

bash
sudo nano /etc/systemd/system/nas-power-guard.timer
ini
[Unit]
Description=Check laptop AC power every 15 seconds

[Timer]
OnBootSec=30s
OnUnitActiveSec=15s
AccuracySec=2s
Unit=nas-power-guard.service

[Install]
WantedBy=timers.target

With AC connected, test before enabling:

bash
sudo /usr/local/sbin/nas-power-guard
test ! -e /run/nas-ac-loss-start && echo "AC detection: OK"
sudo systemctl daemon-reload
sudo systemctl enable --now nas-power-guard.timer
systemctl list-timers nas-power-guard.timer

Perform two controlled tests while physically present and with no backup or file transfer running:

  1. Unplug AC for no more than 15 seconds. Confirm /run/nas-ac-loss-start appears, reconnect AC, and confirm it disappears without a shutdown.
  2. Unplug AC again and leave it disconnected. The laptop should request a clean poweroff after approximately 30-45 seconds and must finish shutting down before the weak battery is exhausted.
  3. Restore AC. Confirm the BIOS starts the laptop automatically without pressing the power button; otherwise the remote smart-plug design is not ready.
  4. After boot, inspect journalctl -b -1 -t nas-power-guard and journalctl -b -1 -p warning.
  5. Confirm SMB, FileBrowser, Tailscale, filesystems, and scheduled jobs recovered normally.

If the battery cannot complete this test with a safe margin, do not lengthen the grace period or treat the battery as protection. Replace/remove the unsafe battery or add a suitable UPS before relying on clean shutdown. Rare outages still carry filesystem and in-progress-write risk.

If a real outage interrupts Restic, the unfinished run should not publish a completed snapshot. After power returns, first confirm no Restic process is running, list snapshots, and run the appropriate lightweight restic check. Use restic unlock only when a stale lock is confirmed, then let the next scheduled backup replace the missed run. Never prune merely to clear an outage error.

Use the smart plug only as an AC source and remote startup mechanism. Restrict its app/account to the owner, use a unique password and multi-factor authentication if the vendor offers it, and do not share control with family NAS accounts. If the plug has a setting for behaviour after a building power outage, choose restore previous output state where available, then test the exact behaviour rather than assuming it.

Start the NAS

  1. Confirm the smart-plug output is off.
  2. Turn it on from the mobile app.
  3. The BIOS must start the laptop without opening the lid or pressing its power button.
  4. Wait approximately 2-5 minutes.
  5. Confirm the NAS appears in Tailscale, then open FileBrowser, SMB, or OMV as appropriate.
  6. If the plug was left on after an earlier OS shutdown, turn it off for at least 30 seconds and then on to create a new AC-restoration event.

The smart-plug app showing On proves only that AC was requested. It does not prove that Linux booted, the disks mounted, or services recovered. Tailscale and the service checks are the confirmation.

Stop the NAS

  1. Stop uploads, streams, updates, SMART tests, Restic jobs, and other writes.
  2. If important files changed, run the critical backup and confirm a new snapshot before shutdown.
  3. From the owner-only OMV administration page, select System → Power Management → Shutdown.
  4. Wait until OMV and Tailscale no longer reach the NAS.
  5. Wait at least another 60 seconds so the laptop and disks have completely stopped.
  6. Only then turn the smart-plug output off.

Never use the smart-plug Off button as the normal shutdown method. Cutting power while the NAS is running can lose recent writes, interrupt Restic, leave a stale repository lock, or require filesystem recovery. Use it as an emergency cutoff only for an already unrecoverable frozen system when physical intervention is unavailable.

Perform ten complete start/stop cycles while physically present. Keep the lid closed and Toshiba disconnected. Each cycle must boot without input, mount NAS_PRIMARY and NAS_CRITICAL, start SMB/FileBrowser/Tailscale, shut down from OMV, and remain fully off before plug power is removed. Record typical boot time and any failed cycle.

Before trusting the NAS while traveling:

  • Leave it powered for 72 hours on Ethernet.
  • Close the lid and confirm it stays online.
  • Reboot it at least three times.
  • Repeat the short-flicker test and confirm AC restoration cancels shutdown.
  • Repeat the 30-second-loss test and confirm a clean shutdown completes with margin.
  • Restore AC and confirm automatic startup without opening the lid or pressing the power button.
  • Check temperatures under a large file copy.
  • Check journalctl -p warning --since "72 hours ago" for repeated hardware errors.

Reconnect only one data disk at a time. This makes destructive identification much safer.

  1. Shut the NAS down from System > Power Management.
  2. Connect the SanDisk SSD.
  3. Boot the NAS.
  4. Open Storage > Disks.
  5. Match model, capacity, and serial against your record.
  6. SSH and confirm with:
bash
lsblk -o NAME,PATH,SIZE,MODEL,SERIAL,TRAN,FSTYPE,MOUNTPOINTS
lsusb -t

In lsusb -t, 480M indicates USB 2.0 and 5000M or higher indicates USB 3.x.

In Storage > S.M.A.R.T. > Settings, enable SMART monitoring. Add the disk under Devices and run a short self-test.

If OMV cannot read SMART, try only for diagnosis:

bash
sudo smartctl --scan-open
sudo smartctl -a /dev/sdX
sudo smartctl -d sat -a /dev/sdX

Replace /dev/sdX only after confirming it with lsblk. Some USB bridges do not pass SMART; that does not by itself mean the disk is bad.

Stop if SMART reports failing status, uncorrectable errors, or rapidly increasing media errors.

[!CAUTION] The following OMV actions permanently erase the selected disk. Confirm model, serial, and 1TB capacity again.

  1. Open Storage > Disks.
  2. Select only the SanDisk SSD.
  3. Choose Wipe > Quick.
  4. Open Storage > File Systems.
  5. Choose Create and mount a file system.
  6. Select the SanDisk disk.
  7. Choose EXT4.
  8. Set label to NAS_PRIMARY.
  9. Create, then mount it through OMV.
  10. Apply changes.

Never mount an OMV-managed data disk manually in /etc/fstab.

  1. Power down.
  2. Reconnect the internal Hitachi HTS545025B9A300 disk.
  3. Boot and identify it by model, serial, and approximately 250GB capacity.
  4. Run SMART short and long tests if supported.
  5. Wipe only that disk.
  6. Create an ext4 filesystem labeled NAS_CRITICAL.
  7. Mount it through OMV.

This disk appeared as a 232.89GiB Windows Dynamic Disk divided into several volumes. Those old volumes are not additional physical disks. Confirm that all required files have been copied elsewhere before wiping the complete Hitachi device.

  1. Connect the Toshiba HDD by itself while the NAS is running.
  2. Identify it carefully.
  3. Run SMART short and long tests. A long test may take hours.
  4. Wipe only the Toshiba disk.
  5. Create an ext4 filesystem labeled NAS_OFFLINE.
  6. Mount it through OMV.
  7. Apply changes.
  8. Unmount it from Storage > File Systems.
  9. Wait for the UI to show it unmounted.
  10. Disconnect it and store it safely.

The NAS must boot normally while NAS_OFFLINE is disconnected. Test that now.

The Toshiba label specifies up to 5V/1A and the E5400 provides USB 2.0 ports. Use a short known-good cable and connect it directly to the laptop during initial tests. Run a sustained backup while the SanDisk is also active. If either disk disconnects or resets, stop and use a good externally powered USB hub; do not accept intermittent USB errors.

The 250GB Seagate ST3250318AS is an old Certified Repaired HDD inside an externally powered USB 2.0 enclosure. Its external power supply will stop immediately during a room power outage even while the laptop battery is still running. It must not be mounted continuously, used as NAS_CRITICAL, or hold the only copy of anything.

If you choose to reuse it:

  1. Keep the SanDisk, Hitachi, and Toshiba data disks disconnected while identifying it the first time; leave the Zebronics system SSD installed so OMV can boot.
  2. Confirm the model, serial, and 250GB capacity.
  3. Run SMART short and long tests if the enclosure passes SMART commands.
  4. Run a complete read/write verification only after saving any existing contents elsewhere.
  5. If every test passes, format it ext4 with label NAS_TERTIARY.
  6. Use it only for occasional manual copies of configuration or selected important files.
  7. Unmount, switch off, disconnect, and store it separately after each use.

Any read error, pending/reallocated-sector growth, USB reset, unusual noise, or failed self-test retires this disk from NAS duty.

With each relevant disk mounted, run:

bash
findmnt -t ext4 -o SOURCE,TARGET,LABEL,UUID
lsblk -f

Record the exact OMV mount paths. They look similar to:

text
/srv/dev-disk-by-uuid-1111-2222  NAS_PRIMARY
/srv/dev-disk-by-uuid-3333-4444  NAS_CRITICAL
/srv/dev-disk-by-uuid-5555-6666  NAS_OFFLINE

Do not use /dev/sda or /dev/sdb in permanent configuration. Those names can change after reboot.

Debian normally runs periodic trim through fstrim.timer:

bash
systemctl status fstrim.timer --no-pager
lsblk -D

Enable the timer if it is disabled:

bash
sudo systemctl enable --now fstrim.timer

Some USB bridges do not pass discard/trim. A zero DISC-MAX for the SanDisk usually means the bridge does not expose it. Do not add continuous discard mount options or force unsupported trim commands. Keep at least 15-20% of the primary SSD free, use the 80% alert, and prioritize a stable cable and cool enclosure.


This design uses two permission layers:

  1. Linux filesystem ownership/ACLs protect the actual directories.
  2. SMB and FileBrowser permissions control what each service presents.

Configure both. OMV's shared-folder Permissions alone do not change underlying filesystem permissions.

In Users > Groups, create:

  • family — owner and all family people
  • naswebgrp — only the nasweb service account
  • nasapps — owner only; reserved for Garage

Create accounts through the OMV web UI, not with useradd.

  1. Create nasweb first:
    • Strong random password that is never shared
    • Shell /usr/sbin/nologin
    • Groups naswebgrp and family
    • Disallow user account modification
  2. Edit the owner account:
    • Keep _ssh and sudo
    • Add family and nasapps
  3. Create each family account:
    • Shell /usr/sbin/nologin
    • Group family
    • No administrative groups

Record the numeric IDs:

bash
id nasweb
getent group naswebgrp
getent group family
getent group nasapps

You will need the nasweb UID, naswebgrp GID, and family GID for Docker.

In Storage > Shared Folders, create:

OMV nameRelative pathPurpose
PrivatePrivate/Parent for private directories; do not share directly over SMB
Private-ownerPrivate/owner/Owner's private SMB share
Private-family1Private/family1/Family member 1 private SMB share
Private-family2Private/family2/Family member 2 private SMB share
FamilyFamily/Shared family files
MediaMedia/Shared photos, music, and video
AppObjectsAppObjects/Reserved; no SMB or FileBrowser share

Replace example usernames. If only two people use the NAS, create only two private folders.

For each folder, open ACL or Permissions/ACL in OMV.

Use these intended results:

  • Private parent: owner root, group naswebgrp, mode equivalent to 0751nasweb may list it, while normal users may traverse to a named personal directory but cannot list the parent.
  • Each Private/<user>: owner that user, group naswebgrp, owner and group read/write/execute, others none; enable default/inherited ACLs.
  • Family: owner root, group family, mode equivalent to 2770; inherit permissions.
  • Media: owner root, group family, mode equivalent to 2770; inherit permissions.
  • AppObjects: owner root, group nasapps, mode equivalent to 2770; inherit permissions.

Do not grant the broad built-in users group access. OMV's default root:users 2775 is too broad for private folders.

Verify from SSH. Substitute your primary mount path:

bash
PRIMARY_ROOT="/srv/dev-disk-by-uuid-REPLACE_WITH_PRIMARY_UUID"
sudo find "$PRIMARY_ROOT" -maxdepth 2 -printf '%M %u:%g %p\n' | sort
sudo getfacl "$PRIMARY_ROOT/Family"
sudo getfacl "$PRIMARY_ROOT/Private/owner"

Expected principles:

  • A family account can access Family and Media.
  • It can access only its own private directory.
  • nasweb can access all private folders plus shared folders.
  • No normal user can access AppObjects unless explicitly added to nasapps.

On NAS_CRITICAL, create a shared folder named ResticCritical with relative path restic-critical/. Give access only to administrators/root; do not expose it through SMB.

When the Toshiba disk is mounted, create ResticOffline on NAS_OFFLINE with relative path restic-full/. Do not expose it through SMB.


Open Services > SMB/CIFS > Settings:

  • Enabled: yes
  • Workgroup: WORKGROUP, unless your home already uses another
  • Time server: no
  • Home directories: no
  • SMB1/legacy protocol: disabled
  • Minimum protocol: SMB2 or newer if the UI offers the option
  • WSD/Windows discovery: enable if available

Apply changes.

Under Services > SMB/CIFS > Shares, add:

  • Private-owner
  • One private share per family account
  • Family
  • Media

For every share:

  • Public/guest access: No
  • Read only: No
  • Browseable: Yes
  • Inherit permissions/ACLs: Yes, where available
  • Recycle bin: optional; Restic is the authoritative version history

Never add Private parent, AppObjects, ResticCritical, or ResticOffline as SMB shares.

In OMV shared-folder Permissions:

  • Each private share: its matching user is Read/Write; everyone else No Access; nasweb does not need SMB permission.
  • Family: the family group is Read/Write.
  • Media: the family group is Read/Write.

On a Windows computer connected to home Wi-Fi or Ethernet, turn Tailscale off and enter in File Explorer:

text
\\nas\Family

If name discovery fails, try:

text
\\NAS_LOCAL_IP\Family

Sign in using an OMV family username and password. Create, rename, open, and delete a test file. Then try opening another user's private share and confirm access is denied.

Do not continue until isolation works correctly.


OpenMediaVault recommends isolating additional applications in containers. This guide uses Docker's official Debian 13 repository and pins FileBrowser Quantum to 1.5.0-stable. Do not use latest, beta, or the v2 preview on this low-maintenance NAS.

SSH as the owner and run each block separately:

bash
sudo apt update
sudo apt install --yes ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

Add the official repository:

bash
sudo nano /etc/apt/sources.list.d/docker.sources

Paste:

text
Types: deb
URIs: https://download.docker.com/linux/debian
Suites: trixie
Components: stable
Architectures: amd64
Signed-By: /etc/apt/keyrings/docker.asc

Install and test:

bash
sudo apt update
sudo apt install --yes docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker
sudo docker run --rm hello-world
sudo docker compose version

Do not add daily user accounts to the docker group. Membership is effectively root access; use sudo docker.

The 128GB system SSD is sufficient, but unbounded container logs could fill it. On this fresh installation, confirm Docker has no existing custom configuration:

bash
if [ -e /etc/docker/daemon.json ]; then sudo cat /etc/docker/daemon.json; else echo "No existing daemon.json"; fi

If the file does not exist, create it:

bash
sudo nano /etc/docker/daemon.json
json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

If a configuration already exists, merge these two log-opts into the existing JSON object instead of overwriting it. Validate and restart Docker before any long-running container is created:

bash
sudo dockerd --validate --config-file=/etc/docker/daemon.json
sudo systemctl restart docker
sudo systemctl is-active docker

These defaults apply to containers created afterward. Never schedule an automatic docker system prune; it can remove images or caches needed for rollback.

Substitute the real primary mount path and numeric IDs:

bash
PRIMARY_ROOT="/srv/dev-disk-by-uuid-REPLACE_WITH_PRIMARY_UUID"
FB_UID="REPLACE_WITH_NASWEB_UID"
FB_GID="REPLACE_WITH_NASWEBGRP_GID"

sudo install -d -m 0750 -o root -g root /opt/nas
sudo install -d -m 0750 -o root -g root /opt/nas/filebrowser
sudo install -d -m 0750 -o "$FB_UID" -g "$FB_GID" /opt/nas/filebrowser/data

Confirm that the service identity can read intended folders and not app objects:

bash
sudo -u nasweb test -r "$PRIMARY_ROOT/Family" && echo "Family: OK"
sudo -u nasweb test -r "$PRIMARY_ROOT/Media" && echo "Media: OK"
sudo -u nasweb test -r "$PRIMARY_ROOT/Private/owner" && echo "Private: OK"
sudo -u nasweb test ! -r "$PRIMARY_ROOT/AppObjects" && echo "AppObjects isolation: OK"

Fix ACLs in OMV if any test is wrong.

Open:

bash
sudo -u nasweb nano /opt/nas/filebrowser/data/config.yaml

Paste:

yaml
server:
  port: 80
  database: /home/filebrowser/data/database.db
  cacheDir: /home/filebrowser/data/tmp
  numImageProcessors: 1
  sources:
    - path: /srv/nas/Private
      name: Private
      config:
        defaultEnabled: false
        private: true
    - path: /srv/nas/Family
      name: Family
      config:
        defaultEnabled: true
    - path: /srv/nas/Media
      name: Media
      config:
        defaultEnabled: true

auth:
  adminUsername: fbadmin

userDefaults:
  showHidden: false
  viewMode: normal
  gallerySize: 3
  disablePreviewExt: ""
  preview:
    image: true
    video: true
    audio: true
    office: false
    folder: true
    popup: true
    motionVideoPreview: false
    autoplayMedia: false
  permissions:
    admin: false
    api: false
    share: false
    modify: true
    delete: true
    create: true
    download: true
  fileLoading:
    maxConcurrentUpload: 2
    uploadChunkSizeMb: 10

integrations:
  media:
    debug: false
    extractEmbeddedSubtitles: false
    convert:
      imagePreview:
        heic: true

The regular stable container includes FFmpeg. One image processor and disabled motion previews/subtitle extraction reduce load on the Core 2 Duo. This creates thumbnails but does not transcode video playback.

Generate three hex values:

bash
openssl rand -hex 24
openssl rand -hex 32
openssl rand -hex 32

Store all three in the password manager, then open:

bash
sudo nano /opt/nas/filebrowser/.env

Paste and replace every value:

dotenv
PRIMARY_ROOT=/srv/dev-disk-by-uuid-REPLACE_WITH_PRIMARY_UUID
FB_UID=REPLACE_WITH_NASWEB_UID
FB_GID=REPLACE_WITH_NASWEBGRP_GID
FAMILY_GID=REPLACE_WITH_FAMILY_GID
FILEBROWSER_ADMIN_PASSWORD=REPLACE_WITH_FIRST_HEX_VALUE
FILEBROWSER_JWT_TOKEN_SECRET=REPLACE_WITH_SECOND_HEX_VALUE
FILEBROWSER_TOTP_SECRET=REPLACE_WITH_THIRD_HEX_VALUE

Protect it:

bash
sudo chown root:root /opt/nas/filebrowser/.env
sudo chmod 0600 /opt/nas/filebrowser/.env

Open:

bash
sudo nano /opt/nas/filebrowser/compose.yaml

Paste:

yaml
services:
  filebrowser:
    image: ghcr.io/gtsteffaniak/filebrowser:1.5.0-stable
    container_name: filebrowser
    user: "${FB_UID}:${FB_GID}"
    group_add:
      - "${FAMILY_GID}"
    ports:
      - "127.0.0.1:8080:80"
    volumes:
      - ./data:/home/filebrowser/data
      - ${PRIMARY_ROOT}/Private:/srv/nas/Private
      - ${PRIMARY_ROOT}/Family:/srv/nas/Family
      - ${PRIMARY_ROOT}/Media:/srv/nas/Media
    environment:
      FILEBROWSER_ADMIN_PASSWORD: "${FILEBROWSER_ADMIN_PASSWORD}"
      FILEBROWSER_JWT_TOKEN_SECRET: "${FILEBROWSER_JWT_TOKEN_SECRET}"
      FILEBROWSER_TOTP_SECRET: "${FILEBROWSER_TOTP_SECRET}"
    read_only: true
    tmpfs:
      - /tmp:size=256m,mode=1777
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE
    security_opt:
      - no-new-privileges:true
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:80/health"]
      interval: 30s
      timeout: 3s
      start_period: 20s
      retries: 3

Validate without starting:

bash
cd /opt/nas/filebrowser
sudo docker compose config --quiet
sudo docker compose pull
sudo docker image inspect ghcr.io/gtsteffaniak/filebrowser:1.5.0-stable --format '{{index .RepoDigests 0}}'

Record the image digest in your NAS note.

bash
cd /opt/nas/filebrowser
sudo docker compose up -d
sudo docker compose ps
sudo docker compose logs --tail=100
curl -fsS http://127.0.0.1:8080/health && echo

The host port listens only on 127.0.0.1, so other LAN devices cannot bypass Tailscale HTTPS.

Remote HTTPS is configured in the next section. After it is available:

  1. Sign in as fbadmin with the generated administrator password.
  2. Enable two-factor authentication for fbadmin if offered.
  3. Create a FileBrowser account for each real person.
  4. Use different passwords from SMB.
  5. Give standard users create, modify, delete, and download permissions; do not grant Admin, API, or public Share permission.
  6. Give everyone Family and Media sources scoped to /.
  7. Give each person the Private source scoped only to /<their-username>.
  8. Confirm the matching private directory already exists on disk.
  9. Disable motion previews in each existing user's profile; defaults affect only newly created users.

Test one family account in a private browser window. It must not list another person's private path.

The complete /opt/nas/filebrowser directory is included in Restic backups later.

Before upgrading:

bash
cd /opt/nas/filebrowser
sudo docker compose stop
sudo cp -a data "data.pre-upgrade-$(date +%F)"
sudo docker compose start

Read the release notes and database migration notes. Never change to the stable, latest, beta, or v2 preview tag blindly. Upgrade to a reviewed full stable version, verify it, then remove the temporary copy only after a successful backup.

Tailscale creates an encrypted private network between approved devices. It does not make the NAS public, and it does not require router port forwarding. In this design:

  • SMB travels directly over Tailscale on TCP 445.
  • FileBrowser is published as private tailnet HTTPS on TCP 443.
  • OMV is published for the owner only on private tailnet HTTPS TCP 8443.
  • SSH is available to the owner on TCP 22.
  • Garage TCP 9443 is not enabled until Phase 2.

Do not enable Tailscale Funnel. Funnel is for public Internet exposure. This guide uses Tailscale Serve, which is tailnet-only.

  1. Create a Tailscale account with the long-term identity provider you control.
  2. Enable multi-factor authentication at that identity provider.
  3. In the Tailscale admin console, enable device approval if it is available in your account settings.
  4. Leave MagicDNS enabled.
  5. Record the tailnet DNS suffix shown on the DNS page. It resembles example-name.ts.net.
  6. Never share the owner account with family members. Invite each person separately.

The Personal plan is suitable for this family use case, subject to Tailscale's current user limit. Recheck the plan before onboarding more people.

From the NAS SSH session:

bash
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up --hostname=nas

Open the authorization URL, sign in as the owner, and approve the NAS. In the admin console, verify:

  • The machine is named nas.
  • It shows a current Tailscale IP.
  • It is approved.

Check locally:

bash
tailscale status
tailscale ip -4
systemctl is-enabled tailscaled
systemctl is-active tailscaled

Open Access controls in the Tailscale admin console. Replace the sample identities below with the actual login email addresses. This is HuJSON, so comments and trailing commas are allowed.

jsonc
{
  "groups": {
    "group:owner": [
      "owner@example.com",
    ],
    "group:family": [
      "family1@example.com",
      "family2@example.com",
    ],
  },

  "tagOwners": {
    "tag:nas": ["group:owner"],
  },

  "grants": [
    {
      "src": ["group:family"],
      "dst": ["tag:nas"],
      "ip": ["tcp:443", "tcp:445"],
    },
    {
      "src": ["group:owner"],
      "dst": ["tag:nas"],
      "ip": ["tcp:22", "tcp:443", "tcp:445", "tcp:8443"],
    },
  ],

  "tests": [
    {
      "src": "family1@example.com",
      "proto": "tcp",
      "accept": ["tag:nas:443", "tag:nas:445"],
      "deny": ["tag:nas:22", "tag:nas:8443", "tag:nas:9443"],
    },
    {
      "src": "owner@example.com",
      "proto": "tcp",
      "accept": [
        "tag:nas:22",
        "tag:nas:443",
        "tag:nas:445",
        "tag:nas:8443",
      ],
      "deny": ["tag:nas:9443"],
    },
  ],
}

If there is only one family user, remove the unused identity and its test. Save the policy only when the editor reports that all tests pass. When Garage is enabled later, add TCP 9443 only to the application identities that need it; do not add it to the family grant.

Now assign the server tag declared by that policy:

bash
sudo tailscale set --advertise-tags=tag:nas
tailscale status

In the admin console, verify the NAS is owned by tag:nas. Confirm key expiry is disabled for this tagged server if the console does not already treat tagged devices as non-expiring.

Save a copy of the final policy:

bash
sudo nano /opt/nas/recovery/tailscale-policy.hujson
sudo chmod 600 /opt/nas/recovery/tailscale-policy.hujson
sudo chown root:root /opt/nas/recovery/tailscale-policy.hujson

This encrypted-backup copy is for recreating policy after an account or system incident; the admin console remains the live source of truth.

Tailscale Serve obtains and renews the private certificates and proxies only inside the tailnet:

bash
sudo tailscale serve --bg --https=443 localhost:8080
sudo tailscale serve --bg --https=8443 localhost:80
sudo tailscale serve status

The status output shows the exact URLs. Normally they are:

text
https://nas.<your-tailnet-name>.ts.net/
https://nas.<your-tailnet-name>.ts.net:8443/

Save the first as NAS Files and the second as NAS Admin — owner only. Tailscale persists Serve configuration and restores it after reboot.

If OMV was configured to use its own HTTPS port, substitute that local port in the second command. Do not expose FileBrowser's 127.0.0.1:8080 or Garage with a router rule.

Tailscale grants protect TCP 8443, but OMV still listens directly on host TCP 80 by default. Add a small nftables rule after both the owner SSH connection and OMV-over-Tailscale URL work. It blocks only direct TCP 80; SMB, SSH, Tailscale Serve, and FileBrowser remain untouched.

Install nftables and create the helper:

bash
sudo apt install -y nftables
sudo nano /usr/local/sbin/nas-admin-firewall
bash
#!/usr/bin/env bash
set -Eeuo pipefail

case "${1:-}" in
  apply)
    nft delete table inet nas_admin_guard 2>/dev/null || true
    nft -f - <<'NFT'
table inet nas_admin_guard {
  chain input {
    type filter hook input priority 0; policy accept;
    iifname != "lo" tcp dport 80 drop
  }
}
NFT
    ;;
  remove)
    nft delete table inet nas_admin_guard 2>/dev/null || true
    ;;
  *)
    echo "Usage: $0 {apply|remove}" >&2
    exit 2
    ;;
esac

If the OMV Workbench uses a local port other than 80, replace 80 in that one rule. Create a service:

bash
sudo chmod 700 /usr/local/sbin/nas-admin-firewall
sudo nano /etc/systemd/system/nas-admin-firewall.service
ini
[Unit]
Description=Block direct access to the OMV web port
After=network-online.target tailscaled.service
Wants=network-online.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/sbin/nas-admin-firewall apply
ExecStop=/usr/local/sbin/nas-admin-firewall remove

[Install]
WantedBy=multi-user.target

Keep the current owner SSH session open, then enable and test:

bash
sudo systemctl daemon-reload
sudo systemctl enable --now nas-admin-firewall
sudo nft list table inet nas_admin_guard

Expected behavior:

  • http://NAS_LOCAL_IP no longer opens from another device.
  • https://nas.<tailnet>.ts.net:8443 still opens for the owner with Tailscale enabled.
  • Family tailnet identities cannot open TCP 8443 because of the grant policy.

If the private OMV URL fails, recover over owner SSH on the home LAN:

bash
sudo systemctl disable --now nas-admin-firewall

Never add a default-deny firewall policy in this beginner runbook; an incorrect rule could also lock out SMB, Tailscale, and SSH.

For one person at a time:

  1. Invite their own email identity to the tailnet.
  2. Have them install Tailscale from the platform's official app store.
  3. Sign in with the invited identity.
  4. Approve the new device in the admin console.
  5. Confirm it appears as that user, not as the owner.
  6. Open the NAS Files URL and sign in with that person's FileBrowser account.
  7. Test SMB using the MagicDNS name.
  8. Confirm SSH, OMV TCP 8443, and another person's private folder are inaccessible.

Do not place family phones under tag:nas; tags describe servers, not people.

Turn Wi-Fi off on a phone so it uses mobile data. Enable Tailscale and test:

text
Browser: https://nas.<your-tailnet-name>.ts.net/
SMB host: nas.<your-tailnet-name>.ts.net

Then inspect the router and confirm there is no port-forwarding rule for 22, 80, 443, 445, 8080, 8443, 9443, or 3900.

Use SMB on the home LAN because it behaves like a native disk. Use the same SMB account on every device owned by a person. FileBrowser is the consistent browser interface for thumbnails, previews, uploads, search, and occasional access.

SituationSMB serverFileBrowser
At home, Tailscale offnas or the reserved LAN IPEnable Tailscale and use the private HTTPS URL
Away from homeEnable Tailscale; use nas.<tailnet>.ts.netEnable Tailscale; use https://nas.<tailnet>.ts.net

Keep separate bookmarks named NAS Home and NAS Remote. If short hostname nas is unreliable on a device, use the DHCP-reserved LAN IP at home.

In File Explorer, enter one share at a time:

text
\\nas\Private-owner
\\nas\Family
\\nas\Media

Right-click This PC → Map network drive, select a drive letter, and enable reconnect at sign-in. For remote use, connect Tailscale and substitute the full MagicDNS hostname.

Windows normally allows only one credential set per server name in a login session. If it reports a credential conflict, disconnect existing NAS mappings and remove stale entries under Credential Manager → Windows Credentials, then reconnect using one personal account.

Install the free, open-source Material Files app from its official store listing or F-Droid:

  1. Open Add storage → LAN/SMB server.
  2. At home, enter nas or the reserved LAN IP.
  3. Away from home, enable Tailscale and enter the full MagicDNS hostname.
  4. Enter the person's SMB username and password; do not use Guest.
  5. Save Private-username, Family, and Media as favorites.

For the same web experience as other platforms, open the FileBrowser HTTPS URL in the browser and choose Add to Home screen.

In Finder choose Go → Connect to Server and enter:

text
smb://nas/Family

Use the full MagicDNS hostname while remote. Store the personal SMB credential in Keychain only on a trusted device.

In Apple's Files app:

  1. Open Browse → … → Connect to Server.
  2. Enter smb://nas at home.
  3. Choose Registered User and enter the personal SMB credential.
  4. While remote, first enable Tailscale and use smb://nas.<tailnet>.ts.net.

The Files app is the native file interface. Add the FileBrowser HTTPS site to the Home Screen when previews, thumbnails, or browser upload controls are preferable.

  • FileBrowser should thumbnail common JPEG, PNG, GIF, WebP, HEIC, MP4, and WebM content where its bundled media tools support the file.
  • Browser-native MP4/H.264 and WebM playback should work without server transcoding.
  • Some HEIC variants, RAW images, MKV files, unusual audio tracks, or licensed codecs will not preview in every browser.
  • For an unsupported video, download it or use the platform's Open in VLC action.
  • Static thumbnails are intentional. Motion previews and live transcoding remain disabled to protect the Core 2 Duo CPU.

The files remain ordinary ext4 files. If FileBrowser is ever replaced, SMB access and the data layout continue to work.

The 250GB internal Hitachi disk protects a selected critical subset whenever the daily or boot catch-up job runs. The disconnected 1TB Toshiba protects the complete primary disk weekly. The optional repaired Seagate is not part of either required schedule. Restic encrypts the two required repositories, keeps versions, detects corruption, and restores individual files.

A backup is usable only if its password exists elsewhere and restoration has been tested. Write the Restic password on paper and store it away from the laptop. Also keep it in a reputable password manager. Losing both copies makes every snapshot unrecoverable.

bash
sudo apt update
sudo apt install -y restic
restic version
sudo install -d -m 0700 -o root -g root /etc/nas-secrets /opt/nas/recovery /srv/restore
sudo sh -c 'umask 077; openssl rand -hex 32 > /etc/nas-secrets/restic-password'
sudo cat /etc/nas-secrets/restic-password

Record that password now, then clear the terminal screen with clear. Create the environment file, substituting the three real OMV mount paths:

bash
sudo nano /etc/nas-backup.env
bash
PRIMARY_ROOT=/srv/dev-disk-by-uuid-REPLACE_PRIMARY
CRITICAL_REPO=/srv/dev-disk-by-uuid-REPLACE_INTERNAL_BACKUP/restic-critical
OFFLINE_REPO=/srv/dev-disk-by-uuid-REPLACE_TOSHIBA/restic-full
RESTIC_PASSWORD_FILE=/etc/nas-secrets/restic-password

Protect and verify it:

bash
sudo chown root:root /etc/nas-backup.env
sudo chmod 600 /etc/nas-backup.env
sudo sh -c '. /etc/nas-backup.env; printf "%s\n%s\n%s\n" "$PRIMARY_ROOT" "$CRITICAL_REPO" "$OFFLINE_REPO"'

Every printed path must correspond to the intended disk. Do not continue if any path is blank or wrong.

Initialize the physically connected internal repository:

bash
sudo sh -c '. /etc/nas-backup.env; export RESTIC_PASSWORD_FILE; mountpoint -q "$(dirname "$CRITICAL_REPO")" && restic -r "$CRITICAL_REPO" init'

Connect the Toshiba disk, mount it from Storage → File Systems, and initialize it:

bash
sudo sh -c '. /etc/nas-backup.env; export RESTIC_PASSWORD_FILE; mountpoint -q "$(dirname "$OFFLINE_REPO")" && restic -r "$OFFLINE_REPO" init'

If mountpoint fails, stop and correct the mount path. Never initialize a repository on the system SSD by accident.

Create /usr/local/sbin/nas-export-recovery:

bash
sudo nano /usr/local/sbin/nas-export-recovery
bash
#!/usr/bin/env bash
set -Eeuo pipefail
umask 077

OUT=/opt/nas/recovery
install -d -m 0700 "$OUT"
cp -a /etc/openmediavault/config.xml "$OUT/openmediavault-config.xml.reference"
cp -a /etc/nas-backup.env "$OUT/"
install -d -m 0700 "$OUT/host-config"
for path in \
  /etc/systemd/logind.conf.d/nas-lid.conf \
  /etc/systemd/system.conf.d/watchdog.conf \
  /etc/systemd/system/nas-power-guard.service \
  /etc/systemd/system/nas-power-guard.timer \
  /etc/systemd/system/nas-backup-critical.service \
  /etc/systemd/system/nas-backup-critical.timer \
  /etc/systemd/system/nas-capacity-check.service \
  /etc/systemd/system/nas-capacity-check.timer \
  /etc/systemd/system/nas-admin-firewall.service \
  /etc/docker/daemon.json \
  /etc/apt/sources.list.d/docker.sources; do
  if [ -e "$path" ]; then cp -a --parents "$path" "$OUT/host-config"; fi
done
find /usr/local/sbin -maxdepth 1 -type f -name 'nas-*' \
  -exec cp -a --parents '{}' "$OUT/host-config" \;
getent passwd > "$OUT/passwd-inventory.txt"
getent group > "$OUT/group-inventory.txt"
findmnt --real --output SOURCE,TARGET,FSTYPE,OPTIONS > "$OUT/mounts.txt"
lsblk -o NAME,MODEL,SERIAL,SIZE,FSTYPE,LABEL,UUID,MOUNTPOINTS > "$OUT/disks.txt"
dpkg-query -W -f='${binary:Package}\t${Version}\n' > "$OUT/packages.txt"
docker version > "$OUT/docker-version.txt" 2>&1 || true
tailscale status > "$OUT/tailscale-status.txt" 2>&1 || true
date --iso-8601=seconds > "$OUT/generated-at.txt"

Make it executable and test it:

bash
sudo chmod 700 /usr/local/sbin/nas-export-recovery
sudo /usr/local/sbin/nas-export-recovery
sudo ls -la /opt/nas/recovery

openmediavault-config.xml.reference is rebuild evidence, not a supported one-click restore file. Do not copy an old config.xml over a new OMV installation.

bash
sudo nano /usr/local/sbin/nas-backup-critical
bash
#!/usr/bin/env bash
set -Eeuo pipefail
umask 077
. /etc/nas-backup.env
export RESTIC_PASSWORD_FILE

exec 9>/run/lock/nas-backup.lock
if ! flock -n 9; then
  echo "Another NAS backup or repository check is already running; nothing started"
  exit 75
fi

test -d "$PRIMARY_ROOT/Private"
test -d "$PRIMARY_ROOT/Family"
mountpoint -q "$PRIMARY_ROOT"
mountpoint -q "$(dirname "$CRITICAL_REPO")"
/usr/local/sbin/nas-export-recovery

filebrowser_was_running=0
restart_filebrowser() {
  if [ "$filebrowser_was_running" -eq 1 ]; then docker start filebrowser >/dev/null; fi
}
trap restart_filebrowser EXIT
if docker ps --format '{{.Names}}' | grep -qx filebrowser; then
  filebrowser_was_running=1
  docker stop filebrowser
fi

restic -r "$CRITICAL_REPO" backup \
  "$PRIMARY_ROOT/Private" \
  "$PRIMARY_ROOT/Family" \
  /opt/nas \
  /etc/nas-backup.env \
  /etc/nas-secrets \
  /etc/openmediavault/config.xml \
  --exclude /opt/nas/filebrowser/data/tmp \
  --tag critical

restart_filebrowser
trap - EXIT

# Pruning runs only because the backup above succeeded.
restic -r "$CRITICAL_REPO" forget \
  --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
bash
sudo chmod 700 /usr/local/sbin/nas-backup-critical
sudo /usr/local/sbin/nas-backup-critical
sudo sh -c '. /etc/nas-backup.env; export RESTIC_PASSWORD_FILE; restic -r "$CRITICAL_REPO" snapshots'

The first run can take time. FileBrowser pauses while Restic captures its database, then restarts before the potentially slower prune. Check that the snapshot contains private folders, Family, FileBrowser data/configuration, recovery inventory, and the encrypted copy of service secrets. The regenerable thumbnail cache is excluded.

bash
sudo nano /usr/local/sbin/nas-backup-offline
bash
#!/usr/bin/env bash
set -Eeuo pipefail
umask 077
. /etc/nas-backup.env
export RESTIC_PASSWORD_FILE

exec 9>/run/lock/nas-backup.lock
if ! flock -n 9; then
  echo "Another NAS backup or repository check is already running; nothing started"
  exit 75
fi

test -d "$PRIMARY_ROOT/Private"
mountpoint -q "$PRIMARY_ROOT"
mountpoint -q "$(dirname "$OFFLINE_REPO")"
/usr/local/sbin/nas-export-recovery

garage_was_running=0
filebrowser_was_running=0
restart_services() {
  if [ "$filebrowser_was_running" -eq 1 ]; then docker start filebrowser >/dev/null; fi
  if [ "$garage_was_running" -eq 1 ]; then docker start garage >/dev/null; fi
}
trap restart_services EXIT
if docker ps --format '{{.Names}}' | grep -qx garage; then
  garage_was_running=1
  docker stop garage
fi
if docker ps --format '{{.Names}}' | grep -qx filebrowser; then
  filebrowser_was_running=1
  docker stop filebrowser
fi

restic -r "$OFFLINE_REPO" backup \
  "$PRIMARY_ROOT" \
  /opt/nas \
  /etc/nas-backup.env \
  /etc/nas-secrets \
  /etc/openmediavault/config.xml \
  --exclude /opt/nas/filebrowser/data/tmp \
  --tag full-offline

restart_services
trap - EXIT

# Pruning runs only because the backup above succeeded.
restic -r "$OFFLINE_REPO" forget \
  --keep-weekly 8 --keep-monthly 6 --prune
restic -r "$OFFLINE_REPO" check
bash
sudo chmod 700 /usr/local/sbin/nas-backup-offline

Garage does not exist yet, so its test is harmless. FileBrowser and Garage pause only for the snapshot and restart before prune/check. This makes FileBrowser's database consistent and, later, Garage's metadata and objects mutually consistent.

An ordinary cron job is insufficient because it does not catch up while the NAS is intentionally powered off. Use a systemd timer that runs the critical backup ten minutes after every boot and also at 02:30 whenever the NAS happens to remain on.

Create the service:

bash
sudo nano /etc/systemd/system/nas-backup-critical.service
ini
[Unit]
Description=Create the encrypted critical NAS backup
After=local-fs.target docker.service

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/nas-backup-critical

Create the timer:

bash
sudo nano /etc/systemd/system/nas-backup-critical.timer
ini
[Unit]
Description=Run critical NAS backup after boot and daily while online

[Timer]
OnBootSec=10min
OnCalendar=*-*-* 02:30:00
AccuracySec=1min
Unit=nas-backup-critical.service

[Install]
WantedBy=timers.target

Enable and inspect it:

bash
sudo systemctl daemon-reload
sudo systemctl enable --now nas-backup-critical.timer
systemctl list-timers nas-backup-critical.timer

The boot run deliberately waits ten minutes so storage, Docker, and networking can settle. Restic deduplication makes repeated boot snapshots efficient, but the source scan still consumes CPU and disk I/O. Do not shut down or cut smart-plug power while the service is active. Check with:

bash
systemctl is-active nas-backup-critical.service
journalctl -u nas-backup-critical.service -n 100 --no-pager

Create the repository-check helper:

bash
sudo nano /usr/local/sbin/nas-check-backups
bash
#!/usr/bin/env bash
set -Eeuo pipefail
. /etc/nas-backup.env
export RESTIC_PASSWORD_FILE

exec 9>/run/lock/nas-backup.lock
if ! flock -n 9; then
  echo "Another NAS backup or repository check is already running" >&2
  exit 75
fi

case "${1:-}" in
  critical)      mountpoint -q "$(dirname "$CRITICAL_REPO")"; restic -r "$CRITICAL_REPO" check ;;
  critical-full) mountpoint -q "$(dirname "$CRITICAL_REPO")"; restic -r "$CRITICAL_REPO" check --read-data ;;
  offline)       mountpoint -q "$(dirname "$OFFLINE_REPO")"; restic -r "$OFFLINE_REPO" check ;;
  offline-full)  mountpoint -q "$(dirname "$OFFLINE_REPO")"; restic -r "$OFFLINE_REPO" check --read-data ;;
  *) echo "Usage: $0 {critical|critical-full|offline|offline-full}" >&2; exit 2 ;;
esac
bash
sudo chmod 700 /usr/local/sbin/nas-check-backups

In OMV, create a root scheduled task for /usr/local/sbin/nas-check-backups critical every Sunday at 04:30. If the NAS is off, run that lightweight check manually during the next maintenance session. Run /usr/local/sbin/nas-check-backups critical-full manually once a month. Full reads stress disks and can take hours, so never overlap them with a backup or interactive use.

For a manual pre-shutdown safety copy, open System → Scheduled Tasks, create a disabled/non-recurring owner-only root task named Backup critical now with command /usr/local/sbin/nas-backup-critical, and use its Run action when important files changed. Confirm the task succeeded and a new Restic snapshot exists before shutting down.

Do this once per week while physically present:

  1. Connect the Toshiba HDD directly to the laptop.
  2. In OMV, mount NAS_OFFLINE.
  3. Confirm its model, serial, label, and mount path.
  4. Run sudo /usr/local/sbin/nas-backup-offline.
  5. Read the output and confirm it created a snapshot and completed check.
  6. Once a month, additionally run sudo /usr/local/sbin/nas-check-backups offline-full.
  7. In OMV, unmount NAS_OFFLINE cleanly.
  8. Wait for activity to stop, unplug it, and store it away from the laptop.

Do not add NAS_OFFLINE to /etc/fstab, a Docker volume, or an automatic boot dependency. The NAS must remain healthy without it.

OMV filesystem notifications should be enabled in the next section. Add this simple explicit check as a second signal:

bash
sudo nano /usr/local/sbin/nas-capacity-check
bash
#!/usr/bin/env bash
set -Eeuo pipefail
. /etc/nas-backup.env
overall=0

check_path() {
  local path=$1 label=$2 warning=$3 critical=$4 used
  used=$(df -P "$path" | awk 'NR==2 {gsub(/%/,"",$5); print $5}')
  if [ "$used" -ge "$critical" ]; then
    echo "CRITICAL: $label is ${used}% full at $path"
    overall=2
  elif [ "$used" -ge "$warning" ]; then
    echo "WARNING: $label is ${used}% full at $path"
    if [ "$overall" -lt 1 ]; then overall=1; fi
  else
    echo "OK: $label is ${used}% full"
  fi
}

check_path / SYSTEM 70 80
check_path "$PRIMARY_ROOT" NAS_PRIMARY 80 90
check_path "$(dirname "$CRITICAL_REPO")" NAS_CRITICAL 80 90
if mountpoint -q "$(dirname "$OFFLINE_REPO")"; then
  check_path "$(dirname "$OFFLINE_REPO")" NAS_OFFLINE 80 90
fi
exit "$overall"
bash
sudo chmod 700 /usr/local/sbin/nas-capacity-check
sudo /usr/local/sbin/nas-capacity-check

Create a timer so the check runs 15 minutes after every boot and at 08:00 whenever the NAS remains on:

bash
sudo nano /etc/systemd/system/nas-capacity-check.service
ini
[Unit]
Description=Check NAS filesystem capacity
After=local-fs.target

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/nas-capacity-check
bash
sudo nano /etc/systemd/system/nas-capacity-check.timer
ini
[Unit]
Description=Check NAS capacity after boot and daily while online

[Timer]
OnBootSec=15min
OnCalendar=*-*-* 08:00:00
AccuracySec=2min
Unit=nas-capacity-check.service

[Install]
WantedBy=timers.target
bash
sudo systemctl daemon-reload
sudo systemctl enable --now nas-capacity-check.timer
systemctl list-timers nas-capacity-check.timer

The 128GB system SSD warns at 70% and becomes critical at 80%; data and backup disks warn at 80% and become critical at 90%. Review journalctl -u nas-capacity-check.service after boot and configure OMV filesystem notifications as the primary email signal.

Monitoring should tell you about a problem before both primary and backup copies are affected.

In System → Notification → Settings:

  1. Enable notifications.
  2. Enter the SMTP server, port, encryption mode, username, and an app-specific password supplied by your email provider.
  3. Set the sender and recipient addresses.
  4. Save, apply, and use Send test email.
  5. Enable notifications for filesystem usage, SMART errors, scheduled-task errors, software updates, and system events.

Do not enter a normal email password if the provider supports app-specific passwords. Store a recovery copy of the mail settings without the secret.

In Storage → S.M.A.R.T. → Settings, enable SMART monitoring. Add the internal disks and each USB disk that supports SMART passthrough.

Suggested tests:

  • Short self-test: weekly, at a time that does not overlap backups.
  • Long self-test: monthly, one disk at a time.
  • Temperature and health notifications: enabled.

Some USB-to-SATA bridges do not pass SMART commands. If OMV cannot read the SanDisk or Toshiba health, that is a bridge limitation, not proof that the disk is healthy. Continue Restic checks and inspect the vendor's diagnostic tool from another machine during maintenance.

Do not configure a continuous missed-heartbeat alert for this on-demand design. Intentional smart-plug shutdowns would look exactly like failures and create constant false alarms.

Use three separate signals instead:

  1. The smart-plug app reports whether AC output was requested; it does not prove the computer booted.
  2. Tailscale reports whether the NAS operating system is online.
  3. OMV email, SMART, filesystem, scheduled-job, and service notifications report problems only while the NAS is running.

After every remote start, wait approximately 2-5 minutes and confirm Tailscale connectivity plus either SMB or FileBrowser. If the plug says on but the NAS does not appear within its tested normal boot window plus a generous margin, do not cut power or repeatedly cycle the plug: Linux may be performing filesystem recovery or another slow boot operation. Physical inspection is required. During a building outage, the router's mini-UPS may remain online while the smart plug has no mains electricity, so an app-reported On request still cannot power the laptop until mains returns.

If the NAS is later converted back to an always-on or fixed-hours schedule, an external privacy-minimal heartbeat can be added at that time with an alert window matching the expected operating hours.

bash
systemctl is-enabled docker tailscaled smbd nas-admin-firewall nas-power-guard.timer nas-backup-critical.timer nas-capacity-check.timer
systemctl is-active docker tailscaled smbd nas-admin-firewall nas-power-guard.timer nas-backup-critical.timer nas-capacity-check.timer
sudo docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' filebrowser
sudo docker inspect -f '{{.State.Health.Status}}' filebrowser
sudo tailscale serve status

Expected results are enabled/active services and timers, FileBrowser policy unless-stopped, FileBrowser health healthy, and both HTTPS Serve mappings. Oneshot services are normally inactive between runs; their timers must be active.

The unless-stopped policy deliberately remembers a manual stop. After maintenance, explicitly start the container before rebooting.

If Section 7 confirmed a hardware watchdog, ensure the watchdog service is enabled and test it only while physically present. If no hardware watchdog exists, retain systemd's software watchdog and BIOS power-recovery settings; neither can repair a frozen power adapter or failed motherboard.

During the first week, run this during large transfers and thumbnail generation:

bash
watch -n 5 'sensors; echo; uptime; echo; df -hT | grep -E "Filesystem|NAS_|dev-disk"'

Stop and improve cooling if temperatures approach the CPU manufacturer's limit, the laptop shuts down, or the USB disks repeatedly disconnect.

Do not call the NAS complete until every applicable item below passes. Use disposable test files, not the only copy of real data.

  • Disconnect NAS_OFFLINE.
  • Close the lid and reboot from OMV or sudo systemctl reboot.
  • Confirm the machine returns without opening the lid.
  • Confirm OMV, SMB, Docker, FileBrowser, Tailscale, Tailscale Serve, notifications, and scheduled jobs are healthy.
  • Confirm nas-power-guard.timer, nas-backup-critical.timer, and nas-capacity-check.timer are enabled and active.
  • Confirm boot did not pause or report a failed Toshiba mount.
  • Confirm the reserved LAN address and MagicDNS name still work.

Evidence commands:

bash
systemctl --failed
systemctl is-active smbd docker tailscaled nas-admin-firewall nas-power-guard.timer nas-backup-critical.timer nas-capacity-check.timer
sudo docker compose -f /opt/nas/filebrowser/compose.yaml ps
sudo tailscale serve status
sudo journalctl -b -p warning --no-pager

Investigate unexpected warnings; USB or firmware chatter can be harmless, but disk I/O errors are not.

Turn Tailscale off on the Windows and Android test devices. On both platforms:

  • Run ethtool on the NAS and confirm Speed: 100Mb/s, full duplex, and link detected. This accepted result should not generate a fault report.
  • Browse Private-username, Family, and Media.
  • Upload, open, rename, move, and delete a small test file.
  • Upload a multi-gigabyte file, record the sustained rate, and compare its size at source and destination. Roughly 8-11MB/s is the expected ceiling for this 100Mbps link.
  • Copy a nested folder containing Unicode, spaces, long names, and mixed file types.
  • Stream a common MP4 from Media.
  • Interrupt a disposable upload and verify the result is either absent or clearly incomplete; delete the partial file.

For a strong integrity check, calculate a SHA-256 hash before and after a test transfer:

powershell
Get-FileHash .\large-test-file.bin -Algorithm SHA256

On the NAS:

bash
sha256sum "/path/to/copied/large-test-file.bin"

The hashes must match.

From mobile data or another network:

  • Enable Tailscale.
  • Open the private FileBrowser HTTPS URL and confirm a valid HTTPS connection.
  • Connect to SMB using the full MagicDNS hostname.
  • Upload and download through both interfaces.
  • Disable Tailscale and confirm both remote paths stop working.
  • Confirm the router still has no NAS port-forwarding rule.

Test as every family member, not only as the owner:

  • The user can see only their own Private-username SMB share.
  • The user can use their FileBrowser private scope, Family, and Media.
  • The user cannot list another person's private folder by changing a URL or path.
  • The user cannot reach SSH TCP 22 or OMV TCP 8443 over the tailnet.
  • Direct http://NAS_LOCAL_IP access to the OMV login page fails from a family device on the home LAN.
  • The user cannot reach future Garage TCP 9443.
  • The user has no FileBrowser administration controls.
  • Guest/anonymous SMB login fails.

Treat any failed isolation test as a release blocker. Correct both filesystem ACLs and the application's own scope, then retest.

Upload a representative set:

  • JPEG, PNG, GIF, WebP, and HEIC images.
  • MP4/H.264 and WebM videos.
  • One MKV or unusual-codec file expected to require VLC.
  • A multi-gigabyte video.

Confirm static thumbnails, image preview, and native browser playback where supported. Confirm the unsupported-codec fallback is a clean download or Open in VLC, not server transcoding. During thumbnail generation, verify the NAS remains responsive and FileBrowser uses only a modest number of processes.

  • Run the critical backup manually and see a new snapshot.
  • Reboot, wait ten minutes, and confirm the boot catch-up timer creates another snapshot.
  • Delete a disposable file, run another snapshot, and restore the older version.
  • Overwrite a disposable file and restore the prior content.
  • Connect Toshiba, run the full offline backup, and list its snapshot.
  • Restore a file from the Toshiba repository too.
  • Run a lightweight check against both repositories.
  • Complete one --read-data check against each repository.
  • Disconnect Toshiba and confirm normal operation.
  • Verify the Restic password from the off-NAS copy by opening the repository with it.

Never erase the real primary SSD for this test. Restore a small directory to the empty staging path:

bash
sudo install -d -m 0700 /srv/restore/primary-loss-test-YYYYMMDD
sudo sh -c '. /etc/nas-backup.env; export RESTIC_PASSWORD_FILE; restic -r "$OFFLINE_REPO" restore latest --target /srv/restore/primary-loss-test-YYYYMMDD --include "/srv/dev-disk-by-uuid-*/Family/**"'
sudo find /srv/restore/primary-loss-test-YYYYMMDD -maxdepth 5 -type f | head

Replace YYYYMMDD with today's date in all three commands. The explicit target is a new staging directory on the system disk. Never substitute a mount root. Inspect it after the test and remove it later only when you are certain it contains no needed restore.

  • OMV test email arrives.
  • A deliberately failing scheduled test command generates an alert; remove it afterward.
  • Temporarily lowered capacity thresholds produce an alert; restore system thresholds to 70/80 and data thresholds to 80/90.

Record the completion date and any accepted limitations in the inventory sheet.

  • With no backup or transfer running, disconnect AC for no more than 15 seconds and reconnect it.
  • Confirm the grace-state file is removed and the NAS remains online.
  • Disconnect AC again and confirm a shutdown request after approximately 30-45 seconds.
  • Confirm shutdown completes before battery exhaustion; otherwise stop treating the battery as a shutdown buffer.
  • Confirm the previous boot log contains the nas-power-guard shutdown message and no filesystem corruption.
  • Restore AC and confirm automatic BIOS startup without pressing the power button.
  • After startup, confirm primary and critical filesystems, SMB, FileBrowser, Tailscale, and Restic/capacity timers are healthy.

Repeat this complete sequence ten times while physically present:

  • Shut down from the OMV owner administration page; do not touch the smart-plug Off control first.
  • Confirm OMV and Tailscale disappear, wait another 60 seconds, and then turn the plug off.
  • Leave the plug off for at least 30 seconds.
  • Turn it on and confirm the closed-lid laptop boots without physical input.
  • Confirm the primary and critical filesystems mount and no Toshiba mount is required.
  • Confirm SMB, FileBrowser, Tailscale, timers, and OMV become ready within the recorded normal boot window.
  • Confirm no filesystem, USB-reset, Restic-lock, or unclean-shutdown warning appears in the previous/current boot logs.

One failed automatic boot, unexpected mount failure, or need to press the laptop power button must be resolved before relying on remote startup.

  • Smart-plug power restoration starts the laptop through the BIOS Power On setting.
  • Docker, Samba, Tailscale, and FileBrowser start automatically.
  • The critical Restic backup runs ten minutes after boot and again at 02:30 if the NAS remains on.
  • The capacity check runs 15 minutes after boot and again at 08:00 if the NAS remains on.
  • SMART monitoring operates while powered; calendar tests missed during downtime must be completed during maintenance.
  • The AC-loss guard checks every 15 seconds and requests shutdown approximately 30-45 seconds after accidental continuous AC loss.
  • No missed-heartbeat alert is used because powered-off periods are intentional.

Review an alert on the day it arrives. Never allow repeated backup failures to become background noise.

  1. Read OMV notifications and check systemctl --failed.
  2. Connect and mount the Toshiba disk.
  3. Run the complete offline backup and its lightweight check.
  4. Restore one small rotating test file.
  5. Unmount, disconnect, and store Toshiba separately.
  6. Inspect free space, SMART status, temperature history, and loose USB/Ethernet cables.
  • Run full restic check --read-data on each repository, at separate times.
  • Run one long SMART self-test per disk, staggered.
  • Install reviewed OMV/Debian security updates.
  • Review Tailscale users, devices, grants, and device approvals; remove stale devices.
  • Review FileBrowser accounts and failed login logs.
  • Run sudo docker system df and sudo du -sh /var/lib/docker /opt/nas/filebrowser/data/tmp; investigate unexpected growth instead of pruning automatically.
  • Reboot during a maintenance window and repeat the automatic-recovery checks.
  • Inspect the battery for swelling, heat, leakage, or chassis distortion.
  • Restore a folder containing several file types from each repository.
  • Verify the printed recovery sheet and off-NAS Restic password.
  • Export a fresh hardware/UID/group inventory.
  • Clean vents with the laptop powered down and unplugged; do not spin a fan with compressed air.
  • Check official release notes for OMV, Docker, Tailscale, FileBrowser, and Restic.
  1. Confirm the latest boot/daily critical backup succeeded.
  2. Connect Toshiba and make a current offline backup before a major upgrade.
  3. Read the official release notes and known issues.
  4. Update one layer at a time: OMV/Debian, reboot/test, then containers, then optional services.
  5. Avoid distribution release upgrades until OMV explicitly supports the target release.
  6. Never run unattended major upgrades or blindly update pinned container images.
  7. Repeat the relevant acceptance tests.

For a FileBrowser update, change only the pinned tag in /opt/nas/filebrowser/compose.yaml, then:

bash
cd /opt/nas/filebrowser
sudo docker compose pull
sudo docker compose up -d
sudo docker compose ps
sudo docker compose logs --tail=100

Keep the previous exact image tag in the maintenance log so rollback is possible.

Before switching off smart-plug output or unplugging the NAS laptop:

  1. Stop active file transfers and backups.
  2. If important files changed, run Backup critical now, confirm success, and verify the new snapshot.
  3. Unmount Toshiba if connected.
  4. Use System → Power Management → Shutdown, or sudo systemctl poweroff.
  5. Confirm OMV and Tailscale go offline, then wait at least another 60 seconds.
  6. Turn the smart plug off. Never reverse steps 4 and 6.

Before leaving home for a long trip, perform a Toshiba backup, disconnect Toshiba, and repeat one complete remote start plus OMV shutdown cycle from mobile data. Confirm the smart plug remains controllable, Tailscale appears after boot, and no physical input is required. The NAS still cannot recover remotely from a failed smart plug, vendor cloud, power adapter, router/ISP after its mini-UPS expires, loose cable, failed boot, or damaged battery.

Practice these procedures before real data is at risk.

For the critical repository:

bash
sudo -i
. /etc/nas-backup.env
export RESTIC_PASSWORD_FILE
restic -r "$CRITICAL_REPO" snapshots
restic -r "$CRITICAL_REPO" find 'part-of-filename'
exit

For Toshiba, connect and mount it first, then substitute $OFFLINE_REPO.

Always restore into staging first. Example for a Family/Documents folder:

bash
sudo install -d -m 0700 /srv/restore/item
sudo -i
. /etc/nas-backup.env
export RESTIC_PASSWORD_FILE
restic -r "$CRITICAL_REPO" restore latest \
  --target /srv/restore/item \
  --include '/srv/dev-disk-by-uuid-*/Family/Documents/**'
find /srv/restore/item -type f | less
exit

Locate the restored subtree, inspect the files, then copy only the required content back using rsync -aHAX --dry-run first. Remove the staging copy only after the user verifies the restored data.

To restore an older version, replace latest with the snapshot ID shown by restic find --show-pack-id or restic snapshots, and repeat the staged restore.

If NAS_PRIMARY fails:

  1. Power down and remove the failed SanDisk SSD.
  2. Keep it untouched; professional recovery remains possible if the data is uniquely valuable.
  3. Connect a replacement disk of at least the required capacity.
  4. In OMV, verify its serial, wipe it, create ext4 label NAS_PRIMARY_NEW, and mount it.
  5. Recreate Private, Family, Media, and AppObjects shared-folder definitions on the new filesystem.
  6. Connect and mount Toshiba.
  7. Restore the latest complete snapshot into a staging directory on the new disk, not directly over live shares.
  8. Inspect paths, then move the restored contents into the four data directories.
  9. Reapply the documented owners, groups, setgid bits, and ACLs.
  10. Update PRIMARY_ROOT in /etc/nas-backup.env and /opt/nas/filebrowser/.env to the new OMV UUID path.
  11. Recreate FileBrowser and Garage containers so their bind mounts use the new path.
  12. Run all SMB, isolation, preview, backup, and restore acceptance tests.

Example restore after setting a temporary explicit target path:

bash
sudo install -d -m 0700 /srv/dev-disk-by-uuid-NEW_UUID/restore-staging
sudo -i
. /etc/nas-backup.env
export RESTIC_PASSWORD_FILE
restic -r "$OFFLINE_REPO" restore latest \
  --target /srv/dev-disk-by-uuid-NEW_UUID/restore-staging
exit

Do not paste this until NEW_UUID has been replaced and independently verified with findmnt and lsblk.

The supported recovery is a clean OMV reinstall plus configuration recreation:

  1. Disconnect all data disks.
  2. Install the same supported OMV major version on a replacement system SSD using Sections 4–6.
  3. Update OMV and create the owner SSH account.
  4. Reconnect one data disk at a time. Mount existing ext4 filesystems; do not wipe or format them.
  5. Read the recovery inventory from the Restic repository on another trusted Linux machine if necessary.
  6. Recreate groups and users. Preserve their prior numeric UIDs/GIDs where practical, using passwd-inventory.txt and group-inventory.txt, so existing ownership remains correct.
  7. Recreate OMV shared folders by pointing them to the existing directories.
  8. Reapply ACLs and SMB shares.
  9. Install Docker and Tailscale from their official repositories.
  10. Restore /opt/nas/filebrowser, recovery configuration, and secrets from the latest critical or Toshiba snapshot into staging, inspect them, then place them on the rebuilt host.
  11. Update every UUID path if the mount paths changed.
  12. Recreate and test Tailscale Serve mappings.
  13. Recreate scheduled jobs, notifications, SMART schedules, on-demand backup/capacity timers, power guard, and watchdog settings.
  14. Run the full acceptance test.

Use openmediavault-config.xml.reference and the text inventories as a human-readable reference only. OMV does not offer a general supported configuration restore, and blindly replacing its new database can break the rebuilt system.

  • If the NAS copy exists, compare it to the protected off-NAS record by testing restic snapshots.
  • If the NAS died, enter the printed/password-manager value into a temporary root-only password file on the recovery machine.
  • If every password copy is lost, Restic's encryption cannot be bypassed. The snapshots are permanently inaccessible.

Never email the password to yourself or store the only copy beside the laptop.

  1. No power: check the adapter, outlet, battery condition, and power button.
  2. Stops in firmware: disconnect USB disks; confirm the 128GB system SSD is first in boot order.
  3. Boot waits for Toshiba: remove any manual Toshiba /etc/fstab entry and ensure no service requires its mount.
  4. Filesystem/emergency mode: photograph the exact error, do not format anything, and inspect disk health from a rescue system.
  5. Boots only with lid open: recheck BIOS power settings and Section 7 sleep/lid configuration.
  1. Keep AC connected and inspect every entry with the Section 7.4 discovery command.
  2. Confirm the real adapter consistently reports online=1 on AC and online=0 on battery.
  3. Read journalctl -u nas-power-guard.service --since today and journalctl -t nas-power-guard --since today.
  4. If the firmware exposes no reliable adapter entry, disable the timer with sudo systemctl disable --now nas-power-guard.timer; do not modify the script to guess from battery percentage.
  5. If AC detection is reliable but the battery cannot sustain the 30-second grace plus shutdown time, stop relying on it as a shutdown buffer; replace/remove the unsafe battery or add a suitable UPS.
  6. If the NAS shut down correctly but stayed off when power returned, physically verify BIOS Power On when AC is restored and the smart plug's post-outage output behaviour. Do not claim remote-start readiness until repeated automatic boots pass.
  1. Ping the reserved LAN IP.
  2. SSH as the owner and run systemctl --failed and df -h.
  3. Check systemctl status nginx php*-fpm openmediavault-engined.
  4. Run sudo omv-firstaid to repair network or Workbench settings when appropriate.
  5. If only tailnet TCP 8443 fails, check tailscale status, tailscale serve status, and the owner grant.
  6. Direct LAN HTTP is blocked by design. If Tailscale Serve must be repaired, use owner SSH and temporarily run sudo systemctl disable --now nas-admin-firewall.
  7. Do not reinstall or overwrite config.xml merely because the web UI is down.
  1. Determine whether the client is home-LAN or remote.
  2. Home: test the reserved IP instead of nas. Remote: enable Tailscale and use full MagicDNS.
  3. Test systemctl status smbd and sudo smbstatus.
  4. Confirm the share is enabled and Guest is off in OMV.
  5. Clear stale Windows credentials and reconnect using one account.
  6. Test filesystem access as the actual Unix user: sudo -u username ls /path/to/share.
  7. If reads work but writes fail, inspect owner/group, setgid bit, ACL, and read-only share setting.
  1. Run curl -v http://127.0.0.1:8080/health on the NAS.
  2. Check sudo docker compose -f /opt/nas/filebrowser/compose.yaml ps.
  3. Read sudo docker compose -f /opt/nas/filebrowser/compose.yaml logs --tail=200.
  4. If local health passes, inspect Tailscale Serve and grants.
  5. If thumbnails cause high CPU, confirm numImageProcessors: 1, motion previews off, and no transcoding.
  6. If a format alone fails, test a common MP4/JPEG and use VLC for unsupported codecs.
  7. If uploads fail, check primary free space, file permissions, and browser/network stability.
  1. Confirm both devices say connected in tailscale status.
  2. Confirm the client is signed in as the intended person and approved.
  3. Test the full MagicDNS name, then the NAS Tailscale IP.
  4. Check policy tests and grants for the exact source identity and destination port.
  5. Check Serve only for HTTPS; SMB TCP 445 goes directly to Samba.
  6. Confirm Funnel is off and do not “fix” the problem with router port forwarding.
  1. Stop writes and do not repeatedly reconnect it under load.
  2. Note dmesg --ctime | tail -n 100, lsblk, cable, USB port, and power conditions.
  3. Try a known-good short cable and a different compatible port while powered down.
  4. Check SMART/vendor diagnostics.
  5. If the primary disk is unstable, stop using it and restore to a replacement from Toshiba.
  6. If a backup disk is failing, replace it and create a new repository while the primary is still healthy.
  1. Read the first error, not only the final exit code.
  2. Verify the intended filesystem is mounted with findmnt; never let a missing mount redirect a backup to the system disk.
  3. Check free space and repository password permissions.
  4. Run restic unlock only if no Restic process is active and a stale lock is confirmed.
  5. Run restic check; follow the official troubleshooting guide before any repair command.
  6. Do not prune after a failed backup, and do not delete snapshots to hide corruption.
  1. Check uptime, top, iostat, sensors, docker stats, and active SMB sessions.
  2. Confirm Ethernet negotiated at the accepted 100Mbps speed with ethtool. This is the known baseline, not a fault by itself.
  3. Look for USB 2.0 attachment, cable errors, disk sleep/wake cycles, or thermal throttling.
  4. Pause full Restic checks and bulk thumbnail generation during interactive use.
  5. Remember that remote speed is capped by the slower of home upload, remote download, Tailscale path, CPU encryption, USB, and disk speed.

Do this later, after the NAS and both backup paths have passed at least one month of routine use. A single-node Garage instance on one laptop has no node redundancy and is for private development, CI experiments, and personal applications. It is not production object-storage durability and must not hold the only copy of important application data.

Garage objects live under ordinary primary storage and are covered by the disconnected Toshiba backup. Garage metadata lives on the system SSD and is included with server configuration. The weekly script stops Garage so metadata and object files are captured consistently.

Extend the policy from Section 12:

jsonc
{
  "tagOwners": {
    "tag:nas": ["group:owner"],
    "tag:app-dev": ["group:owner"],
  },
  "grants": [
    {
      "src": ["tag:app-dev"],
      "dst": ["tag:nas"],
      "ip": ["tcp:9443"],
    },
    {
      "src": ["group:owner"],
      "dst": ["tag:nas"],
      "ip": ["tcp:9443"],
    },
  ],
}

These entries are additions, not a replacement for the earlier groups and grants. Update tests so the owner and a tagged development device can reach 9443, while family identities still deny it. Do not give application devices SMB, SSH, FileBrowser, or OMV ports.

Use a tagged, reusable Tailscale authentication key only on infrastructure you control. A normal phone or family computer does not need tag:app-dev.

Substitute the real primary mount path:

bash
PRIMARY_ROOT=/srv/dev-disk-by-uuid-REPLACE_PRIMARY
sudo install -d -m 0700 -o root -g root /opt/nas/garage/meta
sudo install -d -m 0700 -o root -g root "$PRIMARY_ROOT/AppObjects/garage-data"
sudo sh -c 'umask 077; openssl rand -hex 32 > /etc/nas-secrets/garage-rpc-secret'
sudo sh -c 'umask 077; openssl rand -hex 32 > /etc/nas-secrets/garage-admin-token'
sudo install -d -m 0700 -o root -g root /opt/nas/garage

Create /opt/nas/garage/garage.toml. Replace both placeholders with the contents of their root-only secret files; do not include angle brackets:

toml
metadata_dir = "/var/lib/garage/meta"
data_dir = "/var/lib/garage/data"
db_engine = "sqlite"
replication_factor = 1

rpc_bind_addr = "[::]:3901"
rpc_public_addr = "127.0.0.1:3901"
rpc_secret = "REPLACE_WITH_64_HEX_RPC_SECRET"

[s3_api]
s3_region = "garage"
api_bind_addr = "[::]:3900"
root_domain = ".s3.garage.localhost"

[admin]
api_bind_addr = "[::]:3903"
admin_token = "REPLACE_WITH_ADMIN_TOKEN"
bash
sudo chmod 600 /opt/nas/garage/garage.toml
sudo chown root:root /opt/nas/garage/garage.toml

Create /opt/nas/garage/compose.yml:

yaml
services:
  garage:
    image: dxflrs/garage:v2.3.0
    container_name: garage
    command: ["/garage", "server", "--single-node"]
    restart: unless-stopped
    read_only: true
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    ports:
      - "127.0.0.1:3900:3900"
      - "127.0.0.1:3903:3903"
    volumes:
      - ./garage.toml:/etc/garage.toml:ro
      - ./meta:/var/lib/garage/meta
      - ${PRIMARY_ROOT}/AppObjects/garage-data:/var/lib/garage/data
    tmpfs:
      - /tmp:size=64m,mode=1777

Create /opt/nas/garage/.env:

dotenv
PRIMARY_ROOT=/srv/dev-disk-by-uuid-REPLACE_PRIMARY

Protect and start it:

bash
sudo chmod 600 /opt/nas/garage/.env
cd /opt/nas/garage
sudo docker compose config
sudo docker compose pull
sudo docker compose up -d
sudo docker compose ps
sudo docker compose logs --tail=100
sudo docker exec garage /garage status

Only loopback ports are published on the host. RPC TCP 3901 is not published because this is a single node.

bash
sudo tailscale serve --bg --https=9443 localhost:3900
sudo tailscale serve status

The private endpoint is:

text
https://nas.<your-tailnet-name>.ts.net:9443

The Garage administration API remains on host loopback TCP 3903 and is not exposed to application clients.

For an application named demo:

bash
sudo docker exec garage /garage bucket create app-demo
sudo docker exec garage /garage key create app-demo-key
sudo docker exec garage /garage bucket allow \
  --read --write --owner app-demo \
  --key app-demo-key

The key command prints an access key ID and secret key. Store them in that application's secret manager immediately; the secret is not a family password and must not be placed in source control. Never reuse a key between applications.

On a development device that can reach TCP 9443, configure a disposable AWS CLI profile:

bash
aws configure --profile garage-demo
aws configure set profile.garage-demo.region garage
aws configure set profile.garage-demo.s3.addressing_style path

Enter the Garage access and secret keys, then test:

bash
ENDPOINT=https://nas.REPLACE_TAILNET.ts.net:9443
printf 'garage test\n' > /tmp/garage-test.txt
aws --profile garage-demo --endpoint-url "$ENDPOINT" s3 cp /tmp/garage-test.txt s3://app-demo/
aws --profile garage-demo --endpoint-url "$ENDPOINT" s3 ls s3://app-demo/
aws --profile garage-demo --endpoint-url "$ENDPOINT" s3 cp s3://app-demo/garage-test.txt /tmp/garage-downloaded.txt
cmp /tmp/garage-test.txt /tmp/garage-downloaded.txt

For an application SDK, use the same four settings: the private HTTPS endpoint, region garage, the application's key pair, and path-style addressing. For example, AWS SDK for JavaScript v3 uses endpoint plus forcePathStyle: true; Python boto3.client("s3", ...) uses endpoint_url. Load keys from environment variables or a secret manager, never literals in code. Repeat put, list, get, delete, and denied-key tests through the actual SDK before considering the integration complete.

Also test all of these:

  • Create/list bucket as administrator.
  • Upload, list, download, and delete with the correct application key.
  • A key for a different bucket is denied.
  • A read-only key cannot upload or delete.
  • With Garage stopped, the endpoint fails closed while SMB and FileBrowser continue.
  • With Tailscale disabled, the endpoint is unreachable remotely.

For each application separately:

  1. Create a new Garage key.
  2. Grant it only the application's bucket permissions.
  3. Deploy the new access and secret keys to the application.
  4. Test read and write using the application.
  5. Disable or delete the old key using the current Garage CLI syntax.
  6. Confirm the old key is denied.
  7. Record the rotation date without recording the secret.

Rotation must overlap only long enough to update the application. If a credential leaks, revoke it immediately and inspect application and Garage logs.

Before an upgrade:

  1. Run the weekly offline backup; it automatically stops and restarts Garage.
  2. Record docker exec garage /garage status and the current exact image tag.
  3. Read Garage's official upgrade notes, including any required intermediate version.
  4. Change only the pinned image tag and run docker compose pull && docker compose up -d.
  5. Repeat bucket/key/API tests.

If the new version supports the old on-disk format and rollback is documented, restore the old tag and run docker compose up -d. If it migrated metadata incompatibly, stop the service and restore configuration, metadata, and object data from the same offline snapshot before starting the old version. Never mix metadata from one snapshot with object files from another.

To disable without deleting data:

bash
cd /opt/nas/garage
sudo docker compose down
sudo tailscale serve --https=9443 off

Confirm SMB, FileBrowser, and backups still work. Leave /opt/nas/garage and AppObjects/garage-data untouched until retention and legal/data requirements permit removal. When removal is eventually authorized, take a final backup and delete only those two explicitly verified paths.

Mark this reference chapter complete after you have reviewed its links and version notes.

This runbook was written on 2026-07-31 and updated with the inspected hardware on 2026-08-01. Software changes; read the official release notes before installation or upgrade.

Pinned versions in this guide are deliberate starting points, not an instruction to remain unpatched forever:

ComponentGuide baselineUpgrade rule
OpenMediaVault8.x on Debian 13Use OMV-supported updates; no unsupported Debian release upgrade
FileBrowser Quantum1.5.0-stableRead migration notes; pin a reviewed full stable tag
Garagev2.3.0, optionalRead every required intermediate-version and metadata migration note
Docker, Tailscale, ResticSupported repository packageApply reviewed security/stable updates during maintenance

The durable parts of this design are the ext4 data layout, personal Unix/SMB accounts, Tailscale least privilege, encrypted offline snapshots, and tested recovery procedure. Any individual web file manager or object-storage service can be replaced without converting the ordinary files in Private, Family, and Media.