diff --git a/README.md b/README.md index c09000f..2c6998e 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,19 @@ A simple, rsync-based PocketBase deployment tool. ### `init` -Start a new PocketBase project (optionally provide a service name via `pb init `) +Start a new PocketBase project (optionally provide a service name via `pb init `) with a `pb.toml`: + +```toml +[server] +ip = '127.0.0.1' +port = 8090 +domain = 'example.com' + +[pocketbase] +version = '0.35.1' +service = 'rusty-dusty' +volume = 'pb_data' +``` ### `dev` @@ -27,7 +39,7 @@ Provision a remote PocketBase server. This will: ### `logs` -Connects to the configured server and streams `/root/pb/{service}/{service}.log` via `tail -n 100 -F`. +Streams the remote logs (`/root/pb/{service}/{service}.log`). ### `secrets` diff --git a/main.go b/main.go index 9efe1eb..ce9818d 100644 --- a/main.go +++ b/main.go @@ -15,6 +15,7 @@ import ( "net/http" "os" "os/exec" + "path" "path/filepath" "runtime" "sort" @@ -285,7 +286,10 @@ func runDev() error { return cmd.Run() } -const defaultPocketbaseVersion = "0.35.1" +const ( + defaultPocketbaseVersion = "0.35.1" + defaultPocketbaseVolume = "pb_data" +) func init() { rand.Seed(time.Now().UnixNano()) @@ -297,6 +301,7 @@ func writePBConfig(path, serviceName string) error { const tmpl = `[pocketbase] version = "%s" service = "%s" +volume = "%s" [server] ip = "127.0.0.1" @@ -304,7 +309,7 @@ port = 8090 domain = "example.com" ` - return os.WriteFile(path, []byte(fmt.Sprintf(tmpl, defaultPocketbaseVersion, serviceName)), 0o644) + return os.WriteFile(path, []byte(fmt.Sprintf(tmpl, defaultPocketbaseVersion, serviceName, defaultPocketbaseVolume)), 0o644) } func generateServiceName() string { @@ -914,6 +919,7 @@ type serverConfig struct { type pocketBaseConfig struct { Version string `toml:"version"` ServiceName string `toml:"service"` + Volume string `toml:"volume"` } type deploymentContext struct { @@ -926,6 +932,8 @@ type deploymentContext struct { envFile string unitServiceDir string unitEnvFile string + volume string + unitVolume string configPath string } @@ -970,6 +978,12 @@ func buildDeploymentContext() (*deploymentContext, error) { envFile := renderServiceTemplate(defaultEnvFileTemplate, serviceName) unitServiceDir := renderServiceTemplate(defaultServiceDirTemplate, "%i") unitEnvFile := renderServiceTemplate(defaultEnvFileTemplate, "%i") + volumeTemplate := cfg.PocketBase.Volume + if volumeTemplate == "" { + volumeTemplate = defaultPocketbaseVolume + } + volume := resolveVolumePath(volumeTemplate, serviceDir, serviceName) + unitVolume := resolveVolumePath(volumeTemplate, unitServiceDir, "%i") return &deploymentContext{ serverIP: serverIP, @@ -981,6 +995,8 @@ func buildDeploymentContext() (*deploymentContext, error) { envFile: envFile, unitServiceDir: unitServiceDir, unitEnvFile: unitEnvFile, + volume: volume, + unitVolume: unitVolume, configPath: configPath, }, nil } @@ -1041,13 +1057,13 @@ func performSetup(ctx *deploymentContext) error { step++ printStep(step, totalSetupSteps, "deploying PocketBase binary") - if err := runSSHCommand(ctx.serverIP, pocketbaseSetupScript(ctx.serviceDir, ctx.envFile, ctx.version, assetURL, ctx.port)); err != nil { + if err := runSSHCommand(ctx.serverIP, pocketbaseSetupScript(ctx.serviceDir, ctx.envFile, ctx.version, assetURL, ctx.volume, ctx.port)); err != nil { return fmt.Errorf("PocketBase setup failed: %w", err) } step++ printStep(step, totalSetupSteps, "configuring systemd service") - if err := runSSHCommand(ctx.serverIP, systemdScript(ctx.unitServiceDir, ctx.unitEnvFile, ctx.serviceName)); err != nil { + if err := runSSHCommand(ctx.serverIP, systemdScript(ctx.unitServiceDir, ctx.unitEnvFile, ctx.unitVolume, ctx.serviceName)); err != nil { return fmt.Errorf("systemd setup failed: %w", err) } @@ -1402,6 +1418,17 @@ func renderServiceTemplate(tpl, serviceName string) string { return strings.ReplaceAll(tpl, "{service}", serviceName) } +func resolveVolumePath(volumeTemplate, baseDir, serviceName string) string { + value := renderServiceTemplate(volumeTemplate, serviceName) + if value == "" { + return baseDir + } + if strings.HasPrefix(value, "/") { + return value + } + return path.Join(baseDir, value) +} + func translateMachineArch(value string) (string, error) { machine := strings.TrimSpace(strings.ToLower(value)) @@ -1496,7 +1523,7 @@ systemctl reload-or-restart caddy.service `, serviceName, domain, port) } -func pocketbaseSetupScript(serviceDir, envFile, version, assetURL string, port int) string { +func pocketbaseSetupScript(serviceDir, envFile, version, assetURL, volume string, port int) string { return fmt.Sprintf(`set -euo pipefail service_dir="%s" mkdir -p "$service_dir" @@ -1511,19 +1538,23 @@ if [ ! -x "$binary" ]; then rm -f "$tmp" fi env_file="%s" +data_dir="%s" current_port="" if [ -f "$env_file" ]; then current_port=$(grep '^PORT=' "$env_file" | head -n 1 | cut -d= -f2) fi +if [ -n "$data_dir" ]; then + mkdir -p "$data_dir" +fi if [ "$current_port" != "%d" ]; then cat <<'EOF' > "$env_file" PORT=%d EOF fi -`, serviceDir, serviceDir, assetURL, envFile, port, port) +`, serviceDir, serviceDir, assetURL, envFile, volume, port, port) } -func systemdScript(serviceDir, envFile, serviceName string) string { +func systemdScript(serviceDir, envFile, volume, serviceName string) string { return fmt.Sprintf(`set -euo pipefail cat <<'EOF' > /etc/systemd/system/pb@.service [Unit] @@ -1541,7 +1572,7 @@ StandardOutput = append:%s/%%i.log StandardError = append:%s/%%i.log WorkingDirectory = %s EnvironmentFile = %s -ExecStart = %s/pocketbase serve --http="127.0.0.1:${PORT}" +ExecStart = %s/pocketbase serve --dir=%s --http="127.0.0.1:${PORT}" [Install] WantedBy = multi-user.target @@ -1549,7 +1580,7 @@ EOF systemctl daemon-reload systemctl --no-block enable --now pb@%s systemctl --no-block restart pb@%s -`, serviceDir, serviceDir, serviceDir, envFile, serviceDir, serviceName, serviceName) +`, serviceDir, serviceDir, serviceDir, envFile, serviceDir, volume, serviceName, serviceName) } func systemdRestartScript(serviceName string) string {