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

import (
	"errors"
	"fmt"
	"path/filepath"
	"syscall"
	"time"

	"github.com/johnandersen777/socialweb-computer-kcp/internal/hostsvc"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/pgunit"
)

const (
	KineStopTimeout = 10 * time.Second
	MaxSocketPath   = 107
	PostgresUser    = "kcp"
	KineDatabase    = "kine"

	SqliteParams = "_journal_mode=WAL&_synchronous=NORMAL&_busy_timeout=30000&_txlock=immediate&_stmt_cache_size=20&_auto_vacuum=incremental"
)

var PostgresSettings = []string{"autovacuum=on", "track_counts=on"}

type KineConfig struct {
	Unit              string
	Slice             string
	Exec              string
	Dir               string
	Socket            string
	PostgresSocketDir string
}

type PostgresConfig struct {
	Unit      string
	Slice     string
	Install   pgunit.Install
	Data      string
	SocketDir string
}

func (c PostgresConfig) Server() pgunit.Server {
	return pgunit.Server{Data: c.Data, SocketDir: c.SocketDir, User: PostgresUser, Settings: PostgresSettings}
}

func checkSocket(path string) error {
	if !filepath.IsAbs(path) {
		return fmt.Errorf("kcpdatastore: socket path %q must be absolute", path)
	}
	if len(path) > MaxSocketPath {
		return fmt.Errorf("kcpdatastore: socket path %s is %d bytes, over the %d-byte sun_path limit", path, len(path), MaxSocketPath)
	}
	return nil
}

func kineArgv(c KineConfig, endpoint string) ([]string, error) {
	if !filepath.IsAbs(c.Exec) {
		return nil, fmt.Errorf("kcpdatastore: the kine executable must be an absolute path, got %q (dev builds take it from kine-path, never PATH)", c.Exec)
	}
	if !filepath.IsAbs(c.Dir) {
		return nil, errors.New("kcpdatastore: the kine directory must be absolute")
	}
	if err := checkSocket(c.Socket); err != nil {
		return nil, err
	}
	return []string{c.Exec,
		"--endpoint", endpoint,
		"--listen-address", "unix://" + c.Socket,
		"--metrics-bind-address=0",
	}, nil
}

func KineSqliteArgv(c KineConfig) ([]string, error) {
	return kineArgv(c, "sqlite://"+filepath.Join(c.Dir, SqliteFile)+"?"+SqliteParams)
}

func PostgresDSN(socketDir string) string {
	return pgunit.DSN(socketDir, PostgresUser, KineDatabase)
}

func KinePostgresArgv(c KineConfig) ([]string, error) {
	if err := pgunit.CheckSocketDir(c.PostgresSocketDir); err != nil {
		return nil, err
	}
	return kineArgv(c, PostgresDSN(c.PostgresSocketDir))
}

const KineMemoryMax = 512 << 20

func kineSpec(c KineConfig, argv []string, desc string) hostsvc.UnitSpec {
	return hostsvc.UnitSpec{
		Name:        c.Unit,
		Description: desc,
		Exec:        argv,
		WorkDir:     c.Dir,
		Slice:       c.Slice,
		Restart:     hostsvc.RestartOnFailure,
		KillSignal:  syscall.SIGTERM,
		StopTimeout: KineStopTimeout,
		Limits:      hostsvc.Limits{MemoryMax: KineMemoryMax},
	}
}

func KineSqliteSpec(c KineConfig) (hostsvc.UnitSpec, error) {
	argv, err := KineSqliteArgv(c)
	if err != nil {
		return hostsvc.UnitSpec{}, err
	}
	return kineSpec(c, argv, "kine on sqlite for one application instance's kcp"), nil
}

func KinePostgresSpec(c KineConfig, store string) (hostsvc.UnitSpec, error) {
	argv, err := KinePostgresArgv(c)
	if err != nil {
		return hostsvc.UnitSpec{}, err
	}
	spec := kineSpec(c, argv, "kine on the application instance's postgres for one kcp slot")
	spec.After, spec.Wants = []string{store}, []string{store}
	return spec, nil
}

func PostgresSpec(c PostgresConfig) (hostsvc.UnitSpec, error) {
	if c.Install.Bin == "" || !filepath.IsAbs(c.Install.Bin) {
		return hostsvc.UnitSpec{}, fmt.Errorf("kcpdatastore: the postgres asset tree is unset or relative (%q)", c.Install.Root)
	}
	return c.Install.UnitSpec(c.Unit, c.Slice, "postgres holding one application instance's kcp store (unix socket only)", c.Server())
}