~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
110 lines · 2.5 KB Raw History
package kcpdatastore

import (
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"time"
)

type Role string

type Kind string

const (
	RoleGateway      Role = "gateway"
	RoleBidder       Role = "bidder"
	KindKineSqlite   Kind = "kine-sqlite"
	KindKinePostgres Kind = "kine-postgres"
)

const (
	SqliteFile      = "kine.db"
	BackupsDir      = "backups"
	BackupTimestamp = "20060102T150405Z"
)

var SqliteCompanions = []string{"-wal", "-shm"}

type Assets struct {
	Kcp  string
	Kine string
}

func StoreFor(role Role, profile string) (Kind, error) {
	switch profile {
	case "", "quick", "market-network", "prodlike":
	default:
		return "", fmt.Errorf("kcpdatastore: unknown profile %q", profile)
	}
	switch role {
	case RoleBidder:
		return KindKineSqlite, nil
	case RoleGateway:
		if profile == "quick" {
			return KindKineSqlite, nil
		}
		return KindKinePostgres, nil
	}
	return "", fmt.Errorf("kcpdatastore: unknown role %q", role)
}

func NeedsBackup(from, to Assets) bool {
	return from.Kcp != to.Kcp || from.Kine != to.Kine
}

func copyChecked(src, dst string) (int64, error) {
	in, err := os.Open(src)
	if err != nil {
		return 0, err
	}
	defer in.Close()
	out, err := os.OpenFile(dst, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o600)
	if err != nil {
		return 0, err
	}
	n, err := io.Copy(out, in)
	if err != nil {
		out.Close()
		return n, err
	}
	if err := out.Sync(); err != nil {
		out.Close()
		return n, err
	}
	return n, out.Close()
}

func BackupSqlite(dir string, at time.Time) (string, error) {
	db := filepath.Join(dir, SqliteFile)
	fi, err := os.Stat(db)
	if err != nil {
		return "", fmt.Errorf("kcpdatastore: no sqlite store to copy: %w", err)
	}
	if fi.Size() == 0 {
		return "", fmt.Errorf("kcpdatastore: %s is empty; refusing a backup that restores nothing", db)
	}
	out := filepath.Join(dir, BackupsDir, at.UTC().Format(BackupTimestamp))
	if err := os.MkdirAll(out, 0o700); err != nil {
		return "", err
	}
	n, err := copyChecked(db, filepath.Join(out, SqliteFile))
	if err != nil {
		return "", fmt.Errorf("kcpdatastore: copying %s: %w", db, err)
	}
	if n != fi.Size() || n == 0 {
		return "", fmt.Errorf("kcpdatastore: copied %d of %d bytes of %s", n, fi.Size(), db)
	}
	for _, suffix := range SqliteCompanions {
		src := db + suffix
		if _, err := os.Stat(src); errors.Is(err, os.ErrNotExist) {
			continue
		}
		if _, err := copyChecked(src, filepath.Join(out, SqliteFile+suffix)); err != nil {
			return "", fmt.Errorf("kcpdatastore: copying %s: %w", src, err)
		}
	}
	return out, nil
}