Add migration scripts and other fixes
This commit is contained in:
@@ -16,6 +16,7 @@ STALWART_SUBMISSION_PORT=587
|
||||
STALWART_IMAPS_PORT=993
|
||||
STALWART_ADMIN_BIND=127.0.0.1
|
||||
STALWART_ADMIN_PORT=8081
|
||||
STALWART_RECOVERY_ADMIN=
|
||||
|
||||
# Roundcube port bindings
|
||||
ROUNDCUBE_BIND=127.0.0.1
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
# EternityMail
|
||||
|
||||
Docker Compose deployment for Stalwart Mail and Roundcube.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Docker Engine with the Compose plugin
|
||||
- A Linux host for the production deployment
|
||||
- DNS records pointing the mail hostname to the server
|
||||
- The `ep-roundcube-skin` repository available as a sibling directory if the custom skin is enabled
|
||||
|
||||
## Configuration
|
||||
|
||||
Create the real environment file from the template:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Edit `.env` and set production values, especially:
|
||||
|
||||
- `MAIL_HOSTNAME` and `MAIL_DOMAIN`
|
||||
- `RC_DB_PASSWORD`
|
||||
- `RC_DB_ROOT_PASSWORD`
|
||||
- `RC_DES_KEY`
|
||||
- `STALWART_RECOVERY_ADMIN`
|
||||
|
||||
The real `.env` file is ignored by Git. Do not commit credentials or recovery settings.
|
||||
|
||||
## Generate a DES key
|
||||
|
||||
On Linux or macOS:
|
||||
|
||||
```bash
|
||||
chmod +x scripts/generate-des-key.sh
|
||||
scripts/generate-des-key.sh
|
||||
```
|
||||
|
||||
On Windows PowerShell:
|
||||
|
||||
```powershell
|
||||
.\scripts\generate-des-key.ps1
|
||||
```
|
||||
|
||||
Put the generated 24-character value in `RC_DES_KEY` in `.env`.
|
||||
|
||||
## Prepare Linux data directories
|
||||
|
||||
The Stalwart container runs as uid/gid `2000`. Create the bind-mounted directories and set the required ownership before starting the stack:
|
||||
|
||||
```bash
|
||||
chmod +x scripts/setup-data-dirs.sh
|
||||
sudo ./scripts/setup-data-dirs.sh
|
||||
```
|
||||
|
||||
The script uses `DATA_ROOT` from `.env` when it is exported in the shell; otherwise it defaults to `/opt/eternitymail`. To use the value from `.env` directly, run the setup command with it loaded or pass the path explicitly:
|
||||
|
||||
```bash
|
||||
sudo DATA_ROOT=/opt/eternitymail ./scripts/setup-data-dirs.sh
|
||||
```
|
||||
|
||||
## Custom Roundcube skin
|
||||
|
||||
The default configuration expects the skin at:
|
||||
|
||||
```text
|
||||
../ep-roundcube-skin/eternity
|
||||
```
|
||||
|
||||
relative to this repository. Override the location in `.env` with `ROUNDCUBE_SKIN_PATH` if needed. The skin directory is mounted read-only and selected with `ROUNDCUBE_SKIN_NAME`.
|
||||
|
||||
## Start and stop
|
||||
|
||||
Validate the rendered configuration:
|
||||
|
||||
```bash
|
||||
docker compose config --quiet
|
||||
```
|
||||
|
||||
Start the services:
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
View service status and logs:
|
||||
|
||||
```bash
|
||||
docker compose ps
|
||||
docker compose logs -f stalwart
|
||||
```
|
||||
|
||||
Stop the services without deleting persistent data:
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
```
|
||||
|
||||
Persistent data is stored under `DATA_ROOT` in the `stalwart`, `roundcube-db`, and `roundcube-config` directories.
|
||||
|
||||
## Network ports
|
||||
|
||||
The host bindings are configurable in `.env`:
|
||||
|
||||
- SMTP: `STALWART_SMTP_PORT` (default `25`)
|
||||
- Submission: `STALWART_SUBMISSION_PORT` (default `587`)
|
||||
- IMAPS: `STALWART_IMAPS_PORT` (default `993`)
|
||||
- Stalwart administration: `STALWART_ADMIN_BIND` and `STALWART_ADMIN_PORT` (defaults `127.0.0.1:8081`)
|
||||
- Roundcube: `ROUNDCUBE_BIND` and `ROUNDCUBE_PORT` (defaults `127.0.0.1:8091`)
|
||||
|
||||
The Stalwart and Roundcube containers communicate over the private `eternitymail` Docker network.
|
||||
@@ -13,6 +13,9 @@ services:
|
||||
cap_add:
|
||||
- NET_BIND_SERVICE
|
||||
|
||||
environment:
|
||||
STALWART_RECOVERY_ADMIN: ${STALWART_RECOVERY_ADMIN}
|
||||
|
||||
ports:
|
||||
- "${STALWART_SMTP_PORT:-25}:25/tcp"
|
||||
- "${STALWART_SUBMISSION_PORT:-587}:587/tcp"
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
#requires -Version 5.1
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Generates a stalwart-cli apply NDJSON plan (Domain + Account/User + aliases)
|
||||
from a migration accounts.csv file.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$CsvPath,
|
||||
[Parameter(Mandatory = $true)][string]$Domain,
|
||||
[Parameter(Mandatory = $true)][string]$OutFile
|
||||
)
|
||||
|
||||
$rows = Import-Csv -Path $CsvPath
|
||||
|
||||
$missing = $rows | Where-Object { -not $_.new_password }
|
||||
if ($missing) {
|
||||
throw "accounts.csv has $($missing.Count) row(s) with no new_password set. Fill them in before generating the plan."
|
||||
}
|
||||
|
||||
$lines = New-Object System.Collections.Generic.List[string]
|
||||
|
||||
# Domain is upserted so the plan works whether or not the wizard already created it.
|
||||
$domainOp = [ordered]@{
|
||||
"@type" = "upsert"
|
||||
object = "Domain"
|
||||
matchOn = @("name")
|
||||
value = @{ "dom" = @{ name = $Domain } }
|
||||
}
|
||||
$lines.Add(($domainOp | ConvertTo-Json -Compress -Depth 10))
|
||||
|
||||
$accounts = [ordered]@{}
|
||||
foreach ($row in $rows) {
|
||||
$localPart = $row.email.Split('@')[0]
|
||||
$clientId = "acct-$localPart"
|
||||
|
||||
$aliases = @{}
|
||||
if ($row.aliases) {
|
||||
foreach ($aliasLocal in ($row.aliases -split ';' | Where-Object { $_ })) {
|
||||
$aliases["alias-$aliasLocal"] = @{ name = $aliasLocal; domainId = "#dom" }
|
||||
}
|
||||
}
|
||||
|
||||
$quotas = @{}
|
||||
if ($row.quota_mb) {
|
||||
$quotas["maxDiskQuota"] = [int64]$row.quota_mb * 1MB
|
||||
}
|
||||
|
||||
$accounts[$clientId] = @{
|
||||
"@type" = "User"
|
||||
name = $localPart
|
||||
domainId = "#dom"
|
||||
credentials = @{ "pw" = @{ "@type" = "Password"; secret = $row.new_password } }
|
||||
memberGroupIds = @{}
|
||||
roles = @{ "@type" = "User" }
|
||||
permissions = @{ "@type" = "Inherit" }
|
||||
quotas = $quotas
|
||||
aliases = $aliases
|
||||
encryptionAtRest = @{ "@type" = "Disabled" }
|
||||
}
|
||||
}
|
||||
|
||||
$accountOp = [ordered]@{
|
||||
"@type" = "upsert"
|
||||
object = "Account"
|
||||
matchOn = @("name")
|
||||
value = $accounts
|
||||
}
|
||||
$lines.Add(($accountOp | ConvertTo-Json -Compress -Depth 10))
|
||||
|
||||
$lines | Set-Content -Path $OutFile -Encoding utf8
|
||||
Write-Host "Wrote $($lines.Count) operation(s) covering $($rows.Count) account(s) to $OutFile"
|
||||
@@ -0,0 +1,127 @@
|
||||
# Mailbox migration: docker-mailserver → Stalwart
|
||||
|
||||
Tools to move accounts, aliases, and mail from a `docker-mailserver` instance into
|
||||
this Stalwart deployment.
|
||||
|
||||
## Overview
|
||||
|
||||
| Step | Linux (bash) | Windows (PowerShell) | Purpose |
|
||||
|---|---|---|---|
|
||||
| 1 | `export-docker-mailserver.sh` | *(run on the Linux docker-mailserver host)* | Dumps accounts + aliases from docker-mailserver's config files into `accounts.csv` |
|
||||
| 2 | *(manual)* | *(manual)* | Fill in `new_password` (and `old_password` if not already known) in `accounts.csv` |
|
||||
| 3 | `generate-account-plan.sh` | `New-AccountPlan.ps1` | Turns `accounts.csv` into a `stalwart-cli apply` NDJSON plan that creates the domain, accounts, and aliases in Stalwart |
|
||||
| 4 | `stalwart-cli apply` | `stalwart-cli apply` | Applies the generated plan to Stalwart |
|
||||
| 5 | `sync-mailboxes.sh` | `Sync-Mailboxes.ps1` | Runs `imapsync` (via Docker) for every account, copying all folders/messages from docker-mailserver to Stalwart |
|
||||
| 6 | *(manual)* | *(manual)* | Verify, then repoint MX/DNS and decommission docker-mailserver |
|
||||
|
||||
Both old and new mail servers run Linux, so the bash scripts are the primary path;
|
||||
the PowerShell scripts are equivalent for driving the migration from a Windows
|
||||
workstation. Make the bash scripts executable once: `chmod +x migration/*.sh`.
|
||||
`generate-account-plan.sh` requires `jq` (`apt install jq` / `dnf install jq`).
|
||||
|
||||
docker-mailserver stores passwords as one-way hashes (`config/postfix-accounts.cf`),
|
||||
so they cannot be reused directly in Stalwart. Each account gets a **new** password
|
||||
during migration; `old_password` is only needed temporarily so `imapsync` can log in
|
||||
to the source server to copy mail.
|
||||
|
||||
## 1. Export accounts and aliases from docker-mailserver
|
||||
|
||||
Run on the docker-mailserver host (or `docker exec` into its container), pointing at
|
||||
its config directory:
|
||||
|
||||
```bash
|
||||
./export-docker-mailserver.sh /path/to/docker-mailserver/config > accounts.csv
|
||||
```
|
||||
|
||||
This produces `accounts.csv` with columns `email,new_password,old_password,aliases,quota_mb`,
|
||||
`new_password` and `old_password` left blank for you to fill in.
|
||||
|
||||
## 2. Fill in passwords
|
||||
|
||||
Edit `accounts.csv`:
|
||||
- `new_password`: the password the account will have on Stalwart. Generate strong
|
||||
random values; users can change them later via the WebUI.
|
||||
- `old_password`: the account's **current, plaintext** docker-mailserver password,
|
||||
needed only so `imapsync` can authenticate to the source during copy. If you don't
|
||||
have it, reset it temporarily on docker-mailserver with
|
||||
`setup email update <email> <temp-password>` and record that value here.
|
||||
- `aliases`: optional, `;`-separated list of extra local-parts on the same domain
|
||||
that should deliver to this mailbox (e.g. `info;sales`).
|
||||
- `quota_mb`: optional mailbox quota in MB (blank = unlimited).
|
||||
|
||||
Never commit a filled-in `accounts.csv`; it contains plaintext passwords.
|
||||
|
||||
## 3. Generate the Stalwart account plan
|
||||
|
||||
Linux:
|
||||
|
||||
```bash
|
||||
./generate-account-plan.sh -c accounts.csv -d eternityproject.fi -o accounts-plan.ndjson
|
||||
```
|
||||
|
||||
Windows:
|
||||
|
||||
```powershell
|
||||
./New-AccountPlan.ps1 -CsvPath accounts.csv -Domain eternityproject.fi -OutFile accounts-plan.ndjson
|
||||
```
|
||||
|
||||
This upserts the `Domain`, one `Account/User` per row (with a `Password` credential
|
||||
set to `new_password` and any `aliases`), so re-running after edits is safe.
|
||||
|
||||
## 4. Apply the plan to Stalwart
|
||||
|
||||
Linux:
|
||||
|
||||
```bash
|
||||
export STALWART_URL="https://mail.eternityproject.fi"
|
||||
export STALWART_USER="admin"
|
||||
export STALWART_PASSWORD="<admin password>"
|
||||
|
||||
stalwart-cli apply --file accounts-plan.ndjson --dry-run
|
||||
stalwart-cli apply --file accounts-plan.ndjson
|
||||
```
|
||||
|
||||
Windows:
|
||||
|
||||
```powershell
|
||||
$env:STALWART_URL = "https://mail.eternityproject.fi"
|
||||
$env:STALWART_USER = "admin"
|
||||
$env:STALWART_PASSWORD = "<admin password>"
|
||||
|
||||
stalwart-cli apply --file accounts-plan.ndjson --dry-run
|
||||
stalwart-cli apply --file accounts-plan.ndjson
|
||||
```
|
||||
|
||||
## 5. Copy mail with imapsync
|
||||
|
||||
Linux:
|
||||
|
||||
```bash
|
||||
./sync-mailboxes.sh -c accounts.csv -s mail.old-domain.example -t mail.eternityproject.fi -n
|
||||
```
|
||||
|
||||
Windows:
|
||||
|
||||
```powershell
|
||||
./Sync-Mailboxes.ps1 `
|
||||
-CsvPath accounts.csv `
|
||||
-SourceHost mail.old-domain.example `
|
||||
-DestHost mail.eternityproject.fi `
|
||||
-DryRun
|
||||
```
|
||||
|
||||
(`-n` / `-DryRun` runs imapsync in dry-run mode first; drop it to actually copy messages.)
|
||||
|
||||
Review the dry-run output, then re-run without `-DryRun` to actually copy messages.
|
||||
The script is safe to re-run: `imapsync` skips messages that already exist at the
|
||||
destination.
|
||||
|
||||
## 6. Cut over
|
||||
|
||||
- Spot-check folder counts/message counts on a few mailboxes in the Stalwart WebUI.
|
||||
- Run `Sync-Mailboxes.ps1` once more shortly before cutover to catch mail received
|
||||
during the migration window.
|
||||
- Update MX records to point at `mail.eternityproject.fi` (see the top-level
|
||||
[README.md](../README.md)).
|
||||
- Once confirmed, decommission docker-mailserver and rotate the temporary
|
||||
`old_password` values you set in step 2.
|
||||
@@ -0,0 +1,44 @@
|
||||
#requires -Version 5.1
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Runs imapsync (via the dockerized imapsync/imapsync image) for every account
|
||||
in a migration accounts.csv, copying mail from docker-mailserver to Stalwart.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$CsvPath,
|
||||
[Parameter(Mandatory = $true)][string]$SourceHost,
|
||||
[Parameter(Mandatory = $true)][string]$DestHost,
|
||||
[int]$SourcePort = 993,
|
||||
[int]$DestPort = 993,
|
||||
[switch]$DryRun
|
||||
)
|
||||
|
||||
$rows = Import-Csv -Path $CsvPath
|
||||
|
||||
$missing = $rows | Where-Object { -not $_.old_password -or -not $_.new_password }
|
||||
if ($missing) {
|
||||
throw "accounts.csv has $($missing.Count) row(s) missing old_password or new_password. Fill them in before syncing."
|
||||
}
|
||||
|
||||
foreach ($row in $rows) {
|
||||
Write-Host "==> Syncing $($row.email)"
|
||||
|
||||
$dockerArgs = @(
|
||||
"run", "--rm", "imapsync/imapsync",
|
||||
"--host1", $SourceHost, "--port1", $SourcePort, "--ssl1",
|
||||
"--user1", $row.email, "--password1", $row.old_password,
|
||||
"--host2", $DestHost, "--port2", $DestPort, "--ssl2",
|
||||
"--user2", $row.email, "--password2", $row.new_password,
|
||||
"--automap", "--syncinternaldates", "--skipcrossduplicates",
|
||||
"--no-modulesversion"
|
||||
)
|
||||
|
||||
if ($DryRun) {
|
||||
$dockerArgs += "--dry"
|
||||
}
|
||||
|
||||
docker @dockerArgs
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Warning "imapsync exited with code $LASTEXITCODE for $($row.email)"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
email,new_password,old_password,aliases,quota_mb
|
||||
alice@eternityproject.fi,,,,
|
||||
bob@eternityproject.fi,,,info;sales,2048
|
||||
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
# Dumps accounts + aliases from a docker-mailserver config directory into a
|
||||
# migration CSV skeleton (email,new_password,old_password,aliases,quota_mb).
|
||||
# Passwords are left blank: docker-mailserver only stores irreversible hashes.
|
||||
set -euo pipefail
|
||||
|
||||
CONFIG_DIR="${1:?Usage: $0 /path/to/docker-mailserver/config}"
|
||||
ACCOUNTS_FILE="$CONFIG_DIR/postfix-accounts.cf"
|
||||
ALIASES_FILE="$CONFIG_DIR/postfix-virtual.cf"
|
||||
|
||||
if [[ ! -f "$ACCOUNTS_FILE" ]]; then
|
||||
echo "postfix-accounts.cf not found under $CONFIG_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "email,new_password,old_password,aliases,quota_mb"
|
||||
|
||||
while IFS='|' read -r email _hash; do
|
||||
[[ -z "$email" || "$email" == \#* ]] && continue
|
||||
|
||||
aliases=""
|
||||
if [[ -f "$ALIASES_FILE" ]]; then
|
||||
# postfix-virtual.cf lines look like: alias@domain destination@domain
|
||||
aliases=$(awk -v dest="$email" '$2 == dest { sub(/@.*/, "", $1); printf "%s;", $1 }' "$ALIASES_FILE")
|
||||
aliases="${aliases%;}"
|
||||
fi
|
||||
|
||||
echo "$email,,,${aliases},"
|
||||
done < "$ACCOUNTS_FILE"
|
||||
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env bash
|
||||
# Generates a stalwart-cli apply NDJSON plan (Domain + Account/User + aliases)
|
||||
# from a migration accounts.csv file. Requires jq.
|
||||
set -euo pipefail
|
||||
|
||||
usage() { echo "Usage: $0 -c accounts.csv -d domain.tld -o accounts-plan.ndjson" >&2; exit 1; }
|
||||
|
||||
while getopts "c:d:o:" opt; do
|
||||
case "$opt" in
|
||||
c) CSV="$OPTARG" ;;
|
||||
d) DOMAIN="$OPTARG" ;;
|
||||
o) OUT="$OPTARG" ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
done
|
||||
[[ -z "${CSV:-}" || -z "${DOMAIN:-}" || -z "${OUT:-}" ]] && usage
|
||||
command -v jq >/dev/null || { echo "jq is required" >&2; exit 1; }
|
||||
|
||||
: > "$OUT"
|
||||
|
||||
jq -nc --arg domain "$DOMAIN" \
|
||||
'{"@type":"upsert","object":"Domain","matchOn":["name"],"value":{"dom":{"name":$domain}}}' >> "$OUT"
|
||||
|
||||
accounts_file=$(mktemp)
|
||||
trap 'rm -f "$accounts_file"' EXIT
|
||||
|
||||
# Process substitution keeps the loop in the current shell, so a missing
|
||||
# password can abort the whole script instead of just a pipeline subshell.
|
||||
while IFS=',' read -r email new_password old_password aliases quota_mb; do
|
||||
[[ -z "$email" ]] && continue
|
||||
|
||||
if [[ -z "$new_password" ]]; then
|
||||
echo "Missing new_password for $email" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local_part="${email%%@*}"
|
||||
|
||||
aliases_json=$(jq -n --arg aliases "$aliases" '
|
||||
($aliases | split(";") | map(select(length > 0))) as $names
|
||||
| reduce $names[] as $n ({}; . + {("alias-" + $n): {name: $n, domainId: "#dom"}})')
|
||||
|
||||
quotas_json="{}"
|
||||
if [[ -n "$quota_mb" ]]; then
|
||||
quotas_json=$(jq -n --argjson mb "$quota_mb" '{"maxDiskQuota": ($mb * 1048576)}')
|
||||
fi
|
||||
|
||||
jq -n --arg id "acct-$local_part" --arg name "$local_part" --arg secret "$new_password" \
|
||||
--argjson aliases "$aliases_json" --argjson quotas "$quotas_json" '
|
||||
{ ($id): {
|
||||
"@type": "User",
|
||||
name: $name,
|
||||
domainId: "#dom",
|
||||
credentials: { pw: { "@type": "Password", secret: $secret } },
|
||||
memberGroupIds: {},
|
||||
roles: { "@type": "User" },
|
||||
permissions: { "@type": "Inherit" },
|
||||
quotas: $quotas,
|
||||
aliases: $aliases,
|
||||
encryptionAtRest: { "@type": "Disabled" }
|
||||
}
|
||||
}' >> "$accounts_file"
|
||||
done < <(tail -n +2 "$CSV")
|
||||
|
||||
accounts_value=$(jq -s 'add' "$accounts_file")
|
||||
jq -nc --argjson value "$accounts_value" \
|
||||
'{"@type":"upsert","object":"Account","matchOn":["name"],"value":$value}' >> "$OUT"
|
||||
|
||||
echo "Wrote plan to $OUT"
|
||||
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env bash
|
||||
# Runs imapsync (via the dockerized imapsync/imapsync image) for every account
|
||||
# in a migration accounts.csv, copying mail from docker-mailserver to Stalwart.
|
||||
set -euo pipefail
|
||||
|
||||
SOURCE_PORT=993
|
||||
DEST_PORT=993
|
||||
DRY_RUN=false
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 -c accounts.csv -s source-host -t dest-host [-p source-port] [-q dest-port] [-n]" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
while getopts "c:s:t:p:q:n" opt; do
|
||||
case "$opt" in
|
||||
c) CSV="$OPTARG" ;;
|
||||
s) SOURCE_HOST="$OPTARG" ;;
|
||||
t) DEST_HOST="$OPTARG" ;;
|
||||
p) SOURCE_PORT="$OPTARG" ;;
|
||||
q) DEST_PORT="$OPTARG" ;;
|
||||
n) DRY_RUN=true ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
done
|
||||
[[ -z "${CSV:-}" || -z "${SOURCE_HOST:-}" || -z "${DEST_HOST:-}" ]] && usage
|
||||
|
||||
while IFS=',' read -r email new_password old_password aliases quota_mb; do
|
||||
[[ -z "$email" ]] && continue
|
||||
|
||||
if [[ -z "$old_password" || -z "$new_password" ]]; then
|
||||
echo "Missing old_password or new_password for $email" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> Syncing $email"
|
||||
|
||||
args=(run --rm imapsync/imapsync
|
||||
--host1 "$SOURCE_HOST" --port1 "$SOURCE_PORT" --ssl1 --user1 "$email" --password1 "$old_password"
|
||||
--host2 "$DEST_HOST" --port2 "$DEST_PORT" --ssl2 --user2 "$email" --password2 "$new_password"
|
||||
--automap --syncinternaldates --skipcrossduplicates --no-modulesversion)
|
||||
|
||||
[[ "$DRY_RUN" == true ]] && args+=(--dry)
|
||||
|
||||
if ! docker "${args[@]}"; then
|
||||
echo "warning: imapsync exited non-zero for $email" >&2
|
||||
fi
|
||||
done < <(tail -n +2 "$CSV")
|
||||
@@ -0,0 +1,81 @@
|
||||
# Utility Scripts
|
||||
|
||||
These scripts support the EternityMail Docker Compose deployment. Run them from the repository root.
|
||||
|
||||
## `generate-des-key.sh`
|
||||
|
||||
Generates a random 24-character alphanumeric value for Roundcube's `RC_DES_KEY` setting.
|
||||
|
||||
Requirements:
|
||||
|
||||
- Linux, macOS, or another Unix-like shell
|
||||
- `/dev/urandom`
|
||||
|
||||
Usage:
|
||||
|
||||
```bash
|
||||
chmod +x scripts/generate-des-key.sh
|
||||
scripts/generate-des-key.sh
|
||||
```
|
||||
|
||||
Copy the printed value into `.env`:
|
||||
|
||||
```dotenv
|
||||
RC_DES_KEY=<generated-value>
|
||||
```
|
||||
|
||||
Keep this value stable after Roundcube has been deployed. Changing it can make existing encrypted session data unreadable.
|
||||
|
||||
## `generate-des-key.ps1`
|
||||
|
||||
Generates the same type of 24-character key using PowerShell.
|
||||
|
||||
Requirements:
|
||||
|
||||
- PowerShell 5.1 or PowerShell 7+
|
||||
|
||||
Usage from PowerShell:
|
||||
|
||||
```powershell
|
||||
.\scripts\generate-des-key.ps1
|
||||
```
|
||||
|
||||
Copy the printed value into `RC_DES_KEY` in `.env`.
|
||||
|
||||
## `setup-data-dirs.sh`
|
||||
|
||||
Creates the host directories used by the Stalwart and Roundcube containers and assigns ownership of the Stalwart directories to uid/gid `2000`, which is the user used by the Stalwart image.
|
||||
|
||||
Requirements:
|
||||
|
||||
- Linux or another Unix-like host
|
||||
- Permission to create and change ownership under `DATA_ROOT`
|
||||
|
||||
The script does not read `.env` automatically. Set `DATA_ROOT` explicitly when running it:
|
||||
|
||||
```bash
|
||||
chmod +x scripts/setup-data-dirs.sh
|
||||
sudo DATA_ROOT=/opt/eternitymail ./scripts/setup-data-dirs.sh
|
||||
```
|
||||
|
||||
When `DATA_ROOT` is omitted, the script uses `/opt/eternitymail`.
|
||||
|
||||
The created directories are:
|
||||
|
||||
- `$DATA_ROOT/stalwart/etc`
|
||||
- `$DATA_ROOT/stalwart/data`
|
||||
- `$DATA_ROOT/stalwart/certs`
|
||||
- `$DATA_ROOT/roundcube-db`
|
||||
- `$DATA_ROOT/roundcube-config`
|
||||
|
||||
Only `$DATA_ROOT/stalwart` is changed to uid/gid `2000:2000`; the MariaDB container manages its own data-directory permissions.
|
||||
|
||||
## Typical order
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
scripts/generate-des-key.sh
|
||||
sudo DATA_ROOT=/opt/eternitymail ./scripts/setup-data-dirs.sh
|
||||
docker compose config --quiet
|
||||
docker compose up -d
|
||||
```
|
||||
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
# Creates the host data directories for stalwart/roundcube and sets ownership
|
||||
# expected by each container (stalwart runs as uid/gid 2000).
|
||||
set -euo pipefail
|
||||
|
||||
DATA_ROOT="${DATA_ROOT:-/opt/eternitymail}"
|
||||
|
||||
mkdir -p \
|
||||
"$DATA_ROOT/stalwart/etc" \
|
||||
"$DATA_ROOT/stalwart/data" \
|
||||
"$DATA_ROOT/stalwart/certs" \
|
||||
"$DATA_ROOT/roundcube-db" \
|
||||
"$DATA_ROOT/roundcube-config"
|
||||
|
||||
chown -R 2000:2000 "$DATA_ROOT/stalwart"
|
||||
|
||||
echo "Data directories ready under $DATA_ROOT"
|
||||
Reference in New Issue
Block a user