Persist Postfix queue and ship self-contained full backups.
Move the mail queue under /data so recreate no longer drops deferred mail, and archive data/, compose, .env, and certs/ together for restore on a fresh host. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -20,6 +20,7 @@ type Config struct {
|
||||
MailLogPath string
|
||||
DataDir string
|
||||
DBPath string
|
||||
DeployRoot string
|
||||
Version string
|
||||
TLSCertFile string
|
||||
OpenDKIMSocket string
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mixeme/selfpost/internal/backup"
|
||||
@@ -70,6 +71,11 @@ func (h *Handlers) HandleBackup(w http.ResponseWriter, r *http.Request) {
|
||||
h.renderBackupPageWith(w, r, http.StatusBadRequest, "", pwErr)
|
||||
return
|
||||
}
|
||||
if err := backup.ValidateDeployRoot(h.cfg.DeployRoot); err != nil {
|
||||
logf("panel: full backup: %v", err)
|
||||
h.renderBackupPageWith(w, r, http.StatusBadRequest, "", deployBackupErr(err))
|
||||
return
|
||||
}
|
||||
|
||||
stamp := time.Now().UTC().Format("20060102-150405")
|
||||
filename := fmt.Sprintf("selfpost-backup-%s.tar.gz", stamp)
|
||||
@@ -105,9 +111,13 @@ func (h *Handlers) HandleBackup(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if err := backup.Create(sink, backup.Params{
|
||||
DataDir: h.cfg.DataDir,
|
||||
DBPath: h.cfg.DBPath,
|
||||
Version: h.cfg.Version,
|
||||
DataDir: h.cfg.DataDir,
|
||||
DBPath: h.cfg.DBPath,
|
||||
Version: h.cfg.Version,
|
||||
DeployRoot: h.cfg.DeployRoot,
|
||||
OnWarn: func(msg string) {
|
||||
logf("panel: full backup: %s", msg)
|
||||
},
|
||||
}); err != nil {
|
||||
logf("panel: full backup failed: %v", err)
|
||||
return
|
||||
@@ -310,6 +320,14 @@ func decryptErrorMessage(err error) string {
|
||||
}
|
||||
}
|
||||
|
||||
// deployBackupErr phrases a pre-flight backup failure for the operator.
|
||||
func deployBackupErr(err error) string {
|
||||
if strings.Contains(err.Error(), "DeployRoot") || strings.Contains(err.Error(), "deploy root") {
|
||||
return "Full backup needs the project directory mounted read-only at /selfpost-deploy — add <code>.:/selfpost-deploy:ro</code> to docker-compose.yml and recreate the container."
|
||||
}
|
||||
return "Could not create the backup: " + err.Error()
|
||||
}
|
||||
|
||||
// importErrorMessage maps a domain-import failure (already logged by the caller)
|
||||
// to an HTTP status and a user-facing message. Duplicate domain/login are called
|
||||
// out specifically; other failures — validation errors describing what is wrong
|
||||
|
||||
@@ -3,13 +3,15 @@
|
||||
|
||||
<div class="card">
|
||||
<h2>Full backup</h2>
|
||||
<p class="muted">Download a full backup of all persistent state — the database,
|
||||
every domain's DKIM key and the application credentials. Use it to move the
|
||||
whole server to a new machine: restore it into a container of the
|
||||
<strong>same SelfPost version</strong>, with the same data mount, before first
|
||||
start. TLS certificates and the mail queue are not included.</p>
|
||||
<p class="muted">Download a self-contained backup of the whole instance —
|
||||
<code>data/</code> (database, DKIM keys, application credentials, and the
|
||||
Postfix queue), <code>docker-compose.yml</code>, <code>.env</code>, and
|
||||
<code>certs/</code>. Extract it into an empty project directory on a new
|
||||
machine, adjust hostname or proxy settings if needed, and start a container of
|
||||
the <strong>same SelfPost version</strong> before first boot. The reverse-proxy
|
||||
vhost is not included — set that up separately on the new host.</p>
|
||||
<p class="muted"><strong>The backup file is a secret</strong> (it contains
|
||||
private keys and credentials). Store and transfer it securely and delete it
|
||||
private keys, TLS material, and credentials). Store and transfer it securely and delete it
|
||||
once the restore succeeds. Encrypting it below is the simplest way to do that:
|
||||
the download is then a <code>.spbk</code> file (SelfPost backup) that only
|
||||
the password opens.</p>
|
||||
|
||||
+8
-5
@@ -40,11 +40,13 @@ type Config struct {
|
||||
// the log-tailer role follows in cmd/panel.
|
||||
MailLogPath string
|
||||
// DataDir and DBPath locate the persistent state a full backup archives
|
||||
// (architecture.md § Persistence); Version is stamped into the backup
|
||||
// manifest. They mirror the panel's own configuration.
|
||||
DataDir string
|
||||
DBPath string
|
||||
Version string
|
||||
// (architecture.md § Persistence); DeployRoot is the operator project
|
||||
// directory (docker-compose.yml, .env, certs/); Version is stamped into
|
||||
// the backup manifest. They mirror the panel's own configuration.
|
||||
DataDir string
|
||||
DBPath string
|
||||
DeployRoot string
|
||||
Version string
|
||||
// TrustedProxyCIDRs are the reverse-proxy addresses allowed to supply
|
||||
// X-Forwarded-For (env TRUSTED_PROXY_CIDR). A request whose
|
||||
// direct peer (RemoteAddr) is not in this list never has its XFF header
|
||||
@@ -106,6 +108,7 @@ func New(st *store.Store, domains *domain.Service, apps *app.Service, cfg Config
|
||||
MailLogPath: cfg.MailLogPath,
|
||||
DataDir: cfg.DataDir,
|
||||
DBPath: cfg.DBPath,
|
||||
DeployRoot: cfg.DeployRoot,
|
||||
Version: cfg.Version,
|
||||
TLSCertFile: cfg.TLSCertFile,
|
||||
OpenDKIMSocket: cfg.OpenDKIMSocket,
|
||||
|
||||
Reference in New Issue
Block a user