Deploy: make a no-SSH install possible from the DSM GUI
The install guide was SSH-first and left the GUI as a footnote, but the two things it could not do were both fixable rather than inherent. - `init` takes --data-root / --domain / --tz / --port / --bind, so configuring is one command instead of hand-editing a dotfile that File Station hides by default. Re-running with flags updates an existing .env.prod. - `show-key` prints MASTER_KEY, so it can be backed up without opening that hidden file — intended to be redirected to a file, copied into a password manager, and deleted. - usage() printed a hardcoded line range that spilled into the code once the header grew; it now prints the leading comment block however long it gets. - docs §2.5 is a complete GUI walkthrough: Gitea "Download ZIP" + File Station (with the chmod a zip download needs), two one-off Task Scheduler jobs for config and key backup, then the Container Manager project on the repo root named webmail-prod. Plus a table for running update/rollback/backup/restore /doctor/reindex as Task Scheduler jobs, and why the UI's own Build action is not a substitute for `update` (no pre-backup, no health gate, no rollback). Verified by running the documented sequence verbatim: flag-driven init, show-key, then `docker compose up -d --wait` from the project folder as the GUI does — all services healthy, account created, backup and doctor tasks working against the result. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nux3LCu4zP9nih4BRhPyHw
This commit is contained in:
+55
-1
@@ -13,6 +13,7 @@
|
||||
# ./deploy/webmail.sh reindex [args] rebuild the search index from Postgres
|
||||
# ./deploy/webmail.sh psql open a database shell
|
||||
# ./deploy/webmail.sh shell [svc] shell inside a container (default: api)
|
||||
# ./deploy/webmail.sh show-key print MASTER_KEY so you can back it up
|
||||
# ./deploy/webmail.sh doctor check the install before it bites you
|
||||
set -euo pipefail
|
||||
|
||||
@@ -89,7 +90,34 @@ record_version() {
|
||||
|
||||
# --- commands ----------------------------------------------------------------
|
||||
|
||||
apply_init_flags() {
|
||||
[ -n "$set_data_root" ] && set_env_value DATA_ROOT "$set_data_root" && echo " DATA_ROOT = $set_data_root"
|
||||
[ -n "$set_domain" ] && set_env_value APP_DOMAIN "$set_domain" && echo " APP_DOMAIN = $set_domain"
|
||||
[ -n "$set_tz" ] && set_env_value TZ "$set_tz" && echo " TZ = $set_tz"
|
||||
[ -n "$set_port" ] && set_env_value WEB_PORT "$set_port" && echo " WEB_PORT = $set_port"
|
||||
[ -n "$set_bind" ] && set_env_value WEB_BIND "$set_bind" && echo " WEB_BIND = $set_bind"
|
||||
return 0
|
||||
}
|
||||
|
||||
cmd_init() {
|
||||
# Settings can be passed as flags so the whole install can be driven from a
|
||||
# NAS GUI (DSM Task Scheduler / QTS cron), with no editing of a hidden
|
||||
# dotfile in File Station. Re-running with flags updates those values.
|
||||
local set_data_root="" set_domain="" set_tz="" set_port="" set_bind=""
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--data-root) set_data_root="${2:-}"; shift 2 ;;
|
||||
--domain) set_domain="${2:-}"; shift 2 ;;
|
||||
--tz) set_tz="${2:-}"; shift 2 ;;
|
||||
--port) set_port="${2:-}"; shift 2 ;;
|
||||
--bind) set_bind="${2:-}"; shift 2 ;;
|
||||
-h|--help)
|
||||
echo "usage: webmail.sh init [--data-root PATH] [--domain DOMAIN] [--tz ZONE] [--port N] [--bind IP]"
|
||||
return 0 ;;
|
||||
*) die "init: unknown option $1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ ! -f "$ENV_FILE" ]; then
|
||||
cp "$ENV_EXAMPLE" "$ENV_FILE"
|
||||
chmod 600 "$ENV_FILE"
|
||||
@@ -109,11 +137,22 @@ cmd_init() {
|
||||
ylw " Without it, saved mail-account passwords cannot be decrypted even"
|
||||
ylw " from a perfect database backup."
|
||||
ylw ""
|
||||
apply_init_flags
|
||||
if [ -n "$set_data_root" ]; then
|
||||
grn "configured from flags; you can go straight to: webmail.sh up"
|
||||
else
|
||||
echo "Then edit $ENV_FILE and set at least: DATA_ROOT, APP_DOMAIN, TZ, WEB_PORT."
|
||||
echo "(or re-run: webmail.sh init --data-root ... --domain ... --tz ... --port ...)"
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -n "${set_data_root}${set_domain}${set_tz}${set_port}${set_bind}" ]; then
|
||||
apply_init_flags
|
||||
grn "updated $ENV_FILE"
|
||||
else
|
||||
ylw "$ENV_FILE already exists; leaving it alone."
|
||||
fi
|
||||
require_env
|
||||
mkdir -p "${DATA_ROOT}"/{postgres,blobs,redis,backups,meili,state} 2>/dev/null \
|
||||
|| ylw "could not create every directory under ${DATA_ROOT} (the init container will)"
|
||||
@@ -374,11 +413,25 @@ cmd_restore() {
|
||||
grn "restored. If the blob store was lost too, mail bodies will refill on next sync."
|
||||
}
|
||||
|
||||
# Print the master key so it can be backed up without opening a hidden dotfile
|
||||
# in File Station. Intended for a one-off DSM Task Scheduler job:
|
||||
# bash deploy/webmail.sh show-key > /volume1/docker/webmail/MASTER_KEY.txt
|
||||
# copy it into your password manager, then delete that file.
|
||||
cmd_show_key() {
|
||||
require_env
|
||||
echo "MASTER_KEY=${MASTER_KEY}"
|
||||
echo
|
||||
echo "Copy this into your password manager, then DELETE the file you wrote"
|
||||
echo "it to. Without this key, saved mail-account passwords cannot be"
|
||||
echo "decrypted from any backup."
|
||||
}
|
||||
|
||||
cmd_reindex() { require_env; compose run --rm api reindex "$@"; }
|
||||
cmd_psql() { require_env; compose exec postgres psql -U "${POSTGRES_USER:-webmail}" -d "${POSTGRES_DB:-webmail}"; }
|
||||
cmd_shell() { require_env; compose exec "${1:-api}" /bin/sh; }
|
||||
|
||||
usage() { sed -n '2,20p' "${BASH_SOURCE[0]}" | sed 's|^# \?||'; }
|
||||
# Print the header comment block, however long it grows.
|
||||
usage() { awk 'NR>1 { if ($0 ~ /^#/) { sub(/^# ?/, ""); print } else exit }' "${BASH_SOURCE[0]}"; }
|
||||
|
||||
cmd="${1:-}"; shift || true
|
||||
case "$cmd" in
|
||||
@@ -394,6 +447,7 @@ case "$cmd" in
|
||||
backup) cmd_backup "$@" ;;
|
||||
restore) cmd_restore "$@" ;;
|
||||
reindex) cmd_reindex "$@" ;;
|
||||
show-key) cmd_show_key "$@" ;;
|
||||
psql) cmd_psql "$@" ;;
|
||||
shell) cmd_shell "$@" ;;
|
||||
""|-h|--help|help) usage ;;
|
||||
|
||||
+74
-44
@@ -106,62 +106,92 @@ is small or you'd rather not tie it up, build on your PC instead — see §6.3.
|
||||
Open `http://your-nas:8088/`, create your account, and add a mail account
|
||||
through the wizard.
|
||||
|
||||
### 2.5 Synology Container Manager (GUI instead of SSH)
|
||||
### 2.5 Installing entirely from the DSM GUI (no SSH)
|
||||
|
||||
Yes, this runs from the GUI. *Container Manager → Project → Create*, point it at
|
||||
the **repo root** (`/volume1/docker/webmail/src`), and name the project
|
||||
`webmail-prod`.
|
||||
The whole install can be done without ever enabling SSH. The trick is **Task
|
||||
Scheduler**: it runs any shell command as root from the DSM web UI, which covers
|
||||
the three things the Container Manager UI cannot do on its own.
|
||||
|
||||
Point it at the repo root, **not** at `docker/`. Both folders contain a
|
||||
`docker-compose.yml`: the one in `docker/` is the throwaway *dev* service stack
|
||||
(Postgres/Redis/Meilisearch/GreenMail on dev ports) and starting it does not
|
||||
give you a webmail server. The root one is production.
|
||||
**1. Get the code onto the NAS.** In Gitea, open the repository and use
|
||||
*Download ZIP* (the `<>` Code button). In DSM, open *File Station*, create
|
||||
`docker/webmail/`, upload the zip into it, then right-click → *Extract here*.
|
||||
Rename the extracted folder to `src`, so you end up with
|
||||
`/volume1/docker/webmail/src/docker-compose.yml`.
|
||||
|
||||
Name the project `webmail-prod` too — the name you type in the UI overrides the
|
||||
one in the compose file, and keeping them the same means `deploy/webmail.sh`
|
||||
and the GUI manage the same stack instead of two rival copies.
|
||||
|
||||
**You still need a shell once**, to create `docker/.env.prod` with a generated
|
||||
`MASTER_KEY` — there is no way to generate a random key in the GUI. Either SSH
|
||||
in and run `./deploy/webmail.sh init`, or, if you would rather not enable SSH at
|
||||
all, use *Control Panel → Task Scheduler → Create → Scheduled Task → User-defined
|
||||
script*, run as `root`, with:
|
||||
**2. Configure it — one Task Scheduler job.** *Control Panel → Task Scheduler →
|
||||
Create → Scheduled Task → User-defined script*. Set **User: root**, leave it
|
||||
unscheduled, and paste this as the script (adjust the four values):
|
||||
|
||||
```bash
|
||||
cd /volume1/docker/webmail/src && ./deploy/webmail.sh init
|
||||
cd /volume1/docker/webmail/src
|
||||
chmod +x deploy/*.sh docker/*.sh
|
||||
bash deploy/webmail.sh init \
|
||||
--data-root /volume1/docker/webmail/data \
|
||||
--domain mail.home.lan \
|
||||
--tz Europe/Warsaw \
|
||||
--port 8088
|
||||
```
|
||||
|
||||
Run it once, then edit `docker/.env.prod` in DSM's Text Editor (`init` also puts
|
||||
a `.env` symlink in the repo root, which is what Container Manager reads — one
|
||||
file, one place to edit).
|
||||
Save, select the task, and click *Run*. The `chmod` line matters: a zip download
|
||||
loses the executable bit. This generates `MASTER_KEY`, a database password, and
|
||||
the `.env` that Container Manager will read — with no hidden files to edit.
|
||||
|
||||
**What you get in the GUI:** start/stop, live logs, per-container resource use,
|
||||
automatic restart after reboot, and the nightly database backups (the `backup`
|
||||
service is an ordinary container, so it runs regardless of how you started the
|
||||
stack).
|
||||
**3. Back up the master key — a second one-off task.** Same recipe, script:
|
||||
|
||||
**What the GUI cannot do:**
|
||||
```bash
|
||||
cd /volume1/docker/webmail/src
|
||||
bash deploy/webmail.sh show-key > /volume1/docker/webmail/MASTER_KEY.txt
|
||||
```
|
||||
|
||||
| | Why | Instead |
|
||||
Run it, open `MASTER_KEY.txt` in File Station, copy the key into your password
|
||||
manager, then **delete the file**. Skipping this is the one mistake with no
|
||||
recovery: without that key, no backup can decrypt your saved mail-account
|
||||
passwords.
|
||||
|
||||
**4. Create the project.** *Container Manager → Project → Create*:
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Project name | `webmail-prod` |
|
||||
| Path | `/volume1/docker/webmail/src` |
|
||||
| Source | it will find `docker-compose.yml` — choose to use the existing file |
|
||||
|
||||
Pick the **repo root**, not `docker/`. Both contain a `docker-compose.yml`, and
|
||||
the one in `docker/` is the throwaway *dev* service stack — starting it gives
|
||||
you containers but no webmail server. Use the project name `webmail-prod`
|
||||
exactly: the name typed here overrides the one in the file, and matching them
|
||||
means the GUI and `deploy/webmail.sh` drive the same stack.
|
||||
|
||||
Click through, and it builds. **First build takes 5-15 minutes and wants ~2 GB
|
||||
of RAM** — it compiles the frontend on the NAS. This is the one place the GUI
|
||||
route is meaningfully worse than SSH, where you can build on your PC and copy
|
||||
the images over (§6.3).
|
||||
|
||||
**5. Open `http://your-nas:8088/`** and register your account.
|
||||
|
||||
**What you will see in Container Manager:** `init` and `migrate` sitting at
|
||||
**Exited (0)**. That is correct — they are one-shot jobs that did their work and
|
||||
stopped, and everything else waited on them.
|
||||
|
||||
#### Running the maintenance commands without a shell
|
||||
|
||||
Task Scheduler is not just for setup — it runs any of these, and can schedule
|
||||
them. Each is a user-defined script, user **root**, starting with
|
||||
`cd /volume1/docker/webmail/src`:
|
||||
|
||||
| Task | Script line | When |
|
||||
|---|---|---|
|
||||
| Start Meilisearch | compose profiles aren't exposed in the UI | `./deploy/webmail.sh up` — or delete the `profiles:` line from `docker/docker-compose.prod.yml` |
|
||||
| Health-gated updates | it just pulls and restarts; no gate, no rollback | `./deploy/webmail.sh update` |
|
||||
| Backup before migrating | — | `./deploy/webmail.sh update` does it automatically |
|
||||
| Restore a dump | — | `./deploy/webmail.sh restore <file>` |
|
||||
| Pre-flight checks, reindex | — | `./deploy/webmail.sh doctor` / `reindex` |
|
||||
| Update safely | `bash deploy/webmail.sh update` | after pulling new code |
|
||||
| Roll back | `bash deploy/webmail.sh rollback` | if an update misbehaves |
|
||||
| Backup now | `bash deploy/webmail.sh backup` | before anything risky |
|
||||
| Restore | `echo RESTORE \| bash deploy/webmail.sh restore <file>` | disaster recovery |
|
||||
| Health check | `bash deploy/webmail.sh doctor` | when something looks wrong |
|
||||
| Rebuild search | `bash deploy/webmail.sh reindex --purge` | after a restore |
|
||||
|
||||
**Two things that look wrong but aren't:** `init` and `migrate` sit there as
|
||||
**Exited (0)** — they are one-shot jobs that did their work and stopped, and
|
||||
every other service waited for them. And the first start builds both images on
|
||||
the NAS, which is slow and wants ~2 GB of RAM; §6.3 shows how to build them on
|
||||
your PC instead.
|
||||
|
||||
After editing `.env`, containers keep their old values until they are recreated
|
||||
— `./deploy/webmail.sh up` does that cleanly.
|
||||
|
||||
The honest recommendation: install and update over SSH, and use the GUI for
|
||||
day-to-day watching, starting and stopping. The update path is where all the
|
||||
safety lives, and it is the one thing the GUI has no equivalent for.
|
||||
Updating *through the Container Manager UI instead* (project → Build) does work
|
||||
and does apply migrations — but it takes no backup first, has no health gate,
|
||||
and will not roll back if the new version fails to start. The `update` task
|
||||
above does all three. Use it.
|
||||
|
||||
### 2.6 Starting automatically after a reboot
|
||||
|
||||
|
||||
Reference in New Issue
Block a user