-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdb.go
More file actions
144 lines (121 loc) · 4.34 KB
/
db.go
File metadata and controls
144 lines (121 loc) · 4.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package main
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
)
func doDBPull(envName string) {
_, env := loadEnv(envName)
if env.Database.Driver != "sqlite" {
logFatal("Only sqlite supported")
}
local := filepath.Clean(env.Database.Source)
remote := fmt.Sprintf("%s/%s", strings.TrimRight(env.Dir, "/"), env.Database.Source)
logInfo("📥 Pulling DB from %s...", env.Host)
// Backup Local DB if it exists
if _, err := os.Stat(local); err == nil {
if !confirm(fmt.Sprintf("Local file %s exists. Backup and overwrite?", local)) {
return
}
backup := local + ".bak"
logInfo("📦 Backing up local DB to %s...", backup)
if err := copyFile(local, backup); err != nil {
logFatal("Failed to backup local file: %v", err)
}
} else {
if !confirm(fmt.Sprintf("Download to %s?", local)) {
return
}
}
if !dryRun {
os.MkdirAll(filepath.Dir(local), 0755)
}
f, err := os.Create(local)
if err != nil {
logFatal("Failed to create local file: %v", err)
}
defer f.Close()
// Robust Backup Strategy
remoteScript := fmt.Sprintf(`
set -e
TEMP_DIR=$(mktemp -d)
trap "rm -rf $TEMP_DIR" EXIT
if ! command -v sqlite3 &> /dev/null; then
echo "sqlite3 not found on remote" >&2
exit 1
fi
sqlite3 '%s' ".backup '$TEMP_DIR/backup.db'"
cat "$TEMP_DIR/backup.db"
`, remote)
sshArgs := getSSHBaseArgs(env)
sshArgs = append(sshArgs, remoteScript)
cmd := exec.Command("ssh", sshArgs...)
cmd.Stdout = f
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
f.Close()
os.Remove(local)
logFatal("Pull failed: %v", err)
}
// Explicitly remove potential WAL/SHM files to ensure clean state with new DB
os.Remove(local + "-wal")
os.Remove(local + "-shm")
logSuccess("Synced to %s", local)
}
func doDBPush(envName string) {
_, env := loadEnv(envName)
local := filepath.Clean(env.Database.Source)
remote := fmt.Sprintf("%s/%s", strings.TrimRight(env.Dir, "/"), env.Database.Source)
// 1. Safety Check: Is service running?
// In dry-run, we skip this check because runSSH returns nil (success) which would trigger false positive.
if !dryRun {
// systemctl is-active returns 0 (success) if running, which means err == nil
err := runSSH(env, fmt.Sprintf("systemctl --user is-active -q %s.service", env.Quadlet.ServiceName))
if err == nil {
logFatal("⛔ Service '%s' is RUNNING on %s.\n You must manually stop it before pushing a database to prevent corruption.\n Run: deploy stop %s", env.Quadlet.ServiceName, env.Host, envName)
}
}
logWarn("🔥 OVERWRITING REMOTE DB on %s.", envName)
if !confirm("Are you sure?") {
return
}
// 2. Permission Fix (if needed) - Pre-transfer
if env.Quadlet.ContainerUID > 0 {
logInfo("🔧 Reclaiming file permissions...")
runSSH(env, fmt.Sprintf("podman unshare chown $(id -u):$(id -g) %s %s-wal %s-shm || true", remote, remote, remote))
}
// 3. Backup Remote
logInfo("📦 Creating remote backup...")
if err := runSSH(env, fmt.Sprintf("cp %s %s.bak || true", remote, remote)); err != nil {
logFatal("Remote backup failed: %v", err)
}
// Clean up WAL/SHM to ensure clean state
runSSH(env, fmt.Sprintf("rm -f %s-wal %s-shm", remote, remote))
// 4. Upload
logInfo("📤 Uploading...")
// Create a safe temporary backup for upload
tempBackup := local + ".temp_safecopy"
logInfo("⏳ Creating safe local snapshot...")
// cleanup existing temp file if any
os.Remove(tempBackup)
backupCmd := exec.Command("sqlite3", local, fmt.Sprintf(".backup '%s'", tempBackup))
if out, err := backupCmd.CombinedOutput(); err != nil {
logFatal("Failed to create safe local backup: %v\nOutput: %s", err, string(out))
}
defer os.Remove(tempBackup)
if err := runRsyncSafe(env, []string{tempBackup}, fmt.Sprintf("%s@%s:%s", env.User, env.Host, remote)); err != nil {
logError("Rsync failed: %v", err)
logInfo("Restoring from backup...")
runSSH(env, fmt.Sprintf("mv %s.bak %s", remote, remote))
logFatal("Upload failed and backup restored.")
}
// 5. Restore Permissions
if env.Quadlet.ContainerUID > 0 {
logInfo("🔧 Restoring container permissions...")
runSSH(env, fmt.Sprintf("podman unshare chown %d:%d %s %s.bak", env.Quadlet.ContainerUID, env.Quadlet.ContainerGID, remote, remote))
}
logSuccess("Database pushed successfully.")
logInfo("ℹ️ Service remains STOPPED. Run 'deploy start %s' or 'deploy release %s' when ready.", envName, envName)
}