DB-유

Running Scripts at Boot on Synology

· synology, scripting, linux

A DSM update wiped my docker-compose symlink one too many times. That's when I sat down to find a proper place for a boot script. Every Synology I manage ends up needing something DSM doesn't do out of the box: a symlink, a remote mount, a sysctl value, a container with options the GUI can't express, a custom nginx snippet. There's no /etc/rc.local, and systemd units under /usr/lib/systemd get overwritten by updates.

Two methods work for me: Task Scheduler's boot trigger, and a script in the rc.d directory. Task Scheduler is the right answer for almost everyone. I use DSM 6.1 or 6.2, an administrator account, SSH for testing, and /volume1/scripts, a shared folder with admin-only access, for the scripts.

Write the script, run it by hand first

Keep it POSIX shell; DSM's /bin/sh is BusyBox ash, and bash exists at /bin/bash too. An example that fixes three common things after an update:

#!/bin/bash
# /volume1/scripts/on-boot.sh
LOG=/volume1/scripts/on-boot.log
echo "$(date '+%F %T') boot script start" >> "$LOG"

# 1. restore docker-compose symlink lost on DSM update
if [ ! -x /usr/local/bin/docker-compose ]; then
  ln -sf /volume1/docker/bin/docker-compose /usr/local/bin/docker-compose
  echo "restored docker-compose symlink" >> "$LOG"
fi

# 2. kernel tuning for many connections
sysctl -w net.core.somaxconn=1024 >> "$LOG" 2>&1

# 3. custom nginx snippet, then reload
if [ ! -f /usr/local/etc/nginx/conf.d/upload-size.conf ]; then
  cp /volume1/scripts/nginx/upload-size.conf /usr/local/etc/nginx/conf.d/
  synoservice --reload nginx
  echo "restored nginx snippet" >> "$LOG"
fi

echo "$(date '+%F %T') boot script end" >> "$LOG"

Make it executable and run it once as root to shake out errors:

chmod 755 /volume1/scripts/on-boot.sh
sudo /volume1/scripts/on-boot.sh
cat /volume1/scripts/on-boot.log

Write to a log file. A boot script has no terminal, and without the log you are blind when it silently fails.

Task Scheduler, boot-up trigger

Control Panel → Task Scheduler → Create → Triggered Task → User-defined script.

  • General: Task name on-boot, User root, Event Boot-up, Enabled ticked.
  • Task Settings: tick "Send run details by email" and "Send run details only when the script terminates abnormally" once you trust it. In "User-defined script" enter:
bash /volume1/scripts/on-boot.sh

Click OK. Task Scheduler runs boot-up tasks late in the boot sequence, after volumes are mounted and after most packages, including Docker, have started. That is exactly what you want for anything touching containers or files on a volume. The other trigger, Shutdown, runs your script on the way down and is useful for stopping containers gracefully or writing a marker file.

You can also run the task manually from the same screen with Run, which is the quickest way to test the exact environment it will have at boot.

The alternative: an rc.d script

DSM executes scripts in /usr/local/etc/rc.d/ at boot with the argument start, and at shutdown with stop. This location is preserved across DSM updates on DSM 6, unlike /etc/rc.d. It runs earlier than Task Scheduler, roughly when packages are starting, which is sometimes what you need and sometimes too early.

sudo vi /usr/local/etc/rc.d/S99custom.sh
#!/bin/sh
case "$1" in
  start)
    /volume1/scripts/on-boot.sh
    ;;
  stop)
    /volume1/scripts/on-shutdown.sh
    ;;
esac
exit 0
sudo chmod 755 /usr/local/etc/rc.d/S99custom.sh

Anything in this directory that is not executable or lacks the .sh suffix is ignored. Use this method when the script must run before packages, such as creating a mount point a package expects; otherwise I use Task Scheduler because it's visible in the GUI and survives being forgotten.

Timing

Some things are not ready even at Task Scheduler time: a network mount whose server is still booting, or a container that depends on another. Poll rather than sleep a fixed time:

for i in $(seq 1 30); do
  docker inspect -f '{{.State.Running}}' nextcloud-db 2>/dev/null | grep -q true && break
  sleep 5
done

Reboot and look

Reboot the NAS from the GUI. After it comes back, check the log file for the start and end lines with a timestamp after the reboot, and check the effects:

tail -5 /volume1/scripts/on-boot.log
sysctl net.core.somaxconn
ls -l /usr/local/bin/docker-compose

In Control Panel → Task Scheduler, select the task → Action → View Result to see the exit code and captured output of the last run.

Things that bit me

  • The task shows exit code 127 in View Result: a command in the script was not found. Task Scheduler runs with a minimal PATH; use absolute paths such as /usr/local/bin/docker-compose and /usr/syno/bin/synoservice, or set PATH at the top of the script.
  • The script works when run manually but not at boot: it depends on something not up yet. Add the polling loop, or move the logic from rc.d to Task Scheduler.
  • Permission denied on a script in a shared folder: the folder is on a volume mounted with Windows ACLs and the file lost its execute bit when copied from a PC. Run chmod 755 again after each copy, or call it as bash /path/script.sh so the bit does not matter.
  • The rc.d script runs but docker commands fail with "Cannot connect to the Docker daemon": too early. Docker starts as a package after rc.d. Use Task Scheduler for container work.

Keep the script idempotent so running it twice is harmless, and log everything. That habit alone made DSM updates a lot less stressful for me.