Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,3 @@ jobs:
- run: go build ./...

- run: go test ./...

- uses: golangci/golangci-lint-action@v8
12 changes: 12 additions & 0 deletions cmd/potokd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"log"
"log/slog"
"os"

nethttp "net/http"
Expand All @@ -14,19 +15,30 @@ import (
)

func main() {
slog.Info("Starting Potok server...")
cfg, err := config.LoadConfig()
if err != nil {
fmt.Fprintf(os.Stderr, "LoadConfig() error: %v\n", err)
os.Exit(1)
}
slog.Info("Config loaded successfully", "addr", cfg.Addr, "dataDir", cfg.DataDir, "databaseURL", cfg.DatabaseURL)

conn, err := store.Open(context.Background(), cfg.DatabaseURL)
if err != nil {
fmt.Fprintf(os.Stderr, "Open() error: %v\n", err)
os.Exit(1)
}
defer conn.Close()
slog.Info("Database connection established")

slog.Info("Running database migrations")
if err := conn.Migrate(); err != nil {
fmt.Fprintf(os.Stderr, "Migrate() error: %v\n", err)
os.Exit(1)
}
slog.Info("Database migrations completed successfully")

slog.Info("Starting HTTP server")
handler := httpapi.NewHandler(conn)
nethttp.HandleFunc("GET /health", handler.Health)
log.Fatal(nethttp.ListenAndServe(":3000", nil))
Expand Down
9 changes: 5 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ module github.com/michaeltukdev/Potok
go 1.25.0

require (
github.com/golang-migrate/migrate/v4 v4.18.3
github.com/golang-migrate/migrate/v4 v4.19.1
github.com/gorilla/mux v1.8.1
github.com/spf13/cobra v1.10.2
github.com/zalando/go-keyring v0.2.8
golang.org/x/crypto v0.40.0
golang.org/x/crypto v0.45.0
modernc.org/sqlite v1.38.1
)

Expand All @@ -30,6 +30,7 @@ require (
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/josephspurrier/goversioninfo v1.4.1 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.22 // indirect
Expand All @@ -41,10 +42,10 @@ require (
go.uber.org/atomic v1.7.0 // indirect
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
golang.org/x/image v0.20.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/term v0.39.0 // indirect
golang.org/x/text v0.29.0 // indirect
golang.org/x/text v0.31.0 // indirect
modernc.org/libc v1.66.3 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
Expand Down
9 changes: 9 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8
github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
github.com/golang-migrate/migrate/v4 v4.18.3/go.mod h1:99BKpIi6ruaaXRM1A77eqZ+FWPQ3cfRa+ZVy5bmWMaY=
github.com/golang-migrate/migrate/v4 v4.19.1 h1:OCyb44lFuQfYXYLx1SCxPZQGU7mcaZ7gH9yH4jSFbBA=
github.com/golang-migrate/migrate/v4 v4.19.1/go.mod h1:CTcgfjxhaUtsLipnLoQRWCrjYXycRz/g5+RWDuYgPrE=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
Expand All @@ -30,6 +32,8 @@ github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFr
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/josephspurrier/goversioninfo v1.4.1/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
Expand Down Expand Up @@ -57,17 +61,22 @@ github.com/zalando/go-keyring v0.2.8/go.mod h1:tsMo+VpRq5NGyKfxoBVjCuMrG47yj8cma
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
67 changes: 67 additions & 0 deletions internal/server/store/migrate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package store

import (
"errors"
"fmt"

"github.com/golang-migrate/migrate/v4"
"github.com/golang-migrate/migrate/v4/database/postgres"
"github.com/golang-migrate/migrate/v4/source/iofs"
"github.com/jackc/pgx/v5/stdlib"

"github.com/michaeltukdev/Potok/migrations"
)

func (s *Store) Migrate() error {
source, err := iofs.New(migrations.FS, ".")
if err != nil {
return fmt.Errorf("store: read migrations: %w", err)
}

db := stdlib.OpenDBFromPool(s.pool)
defer db.Close()

driver, err := postgres.WithInstance(db, &postgres.Config{})
if err != nil {
return fmt.Errorf("store: migration driver: %w", err)
}

m, err := migrate.NewWithInstance("iofs", source, "postgres", driver)
if err != nil {
return fmt.Errorf("store: migrator: %w", err)
}

if err := m.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) {
return fmt.Errorf("store: apply migrations: %w", err)
}
return nil
}

func (s *Store) Version() (version uint, dirty bool, err error) {
source, err := iofs.New(migrations.FS, ".")
if err != nil {
return 0, false, fmt.Errorf("store: read migrations: %w", err)
}

db := stdlib.OpenDBFromPool(s.pool)
defer db.Close()

driver, err := postgres.WithInstance(db, &postgres.Config{})
if err != nil {
return 0, false, fmt.Errorf("store: migration driver: %w", err)
}

m, err := migrate.NewWithInstance("iofs", source, "postgres", driver)
if err != nil {
return 0, false, fmt.Errorf("store: migrator: %w", err)
}

version, dirty, err = m.Version()
if errors.Is(err, migrate.ErrNilVersion) {
return 0, false, nil
}
if err != nil {
return 0, false, fmt.Errorf("store: read schema version: %w", err)
}
return version, dirty, nil
}
5 changes: 5 additions & 0 deletions migrations/0001_init.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
DROP INDEX IF EXISTS vaults_user_id_idx;
DROP TABLE IF EXISTS vaults;
DROP INDEX IF EXISTS api_keys_user_id_idx;
DROP TABLE IF EXISTS api_keys;
DROP TABLE IF EXISTS users;
32 changes: 32 additions & 0 deletions migrations/0001_init.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
is_admin BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE api_keys (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users (id) ON DELETE CASCADE,
label TEXT NOT NULL,
key_hash BYTEA NOT NULL UNIQUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
last_used_at TIMESTAMPTZ
);

CREATE INDEX api_keys_user_id_idx ON api_keys (user_id);

CREATE TABLE vaults (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users (id) ON DELETE CASCADE,
name TEXT NOT NULL,
wrapped_key BYTEA NOT NULL,
generation BIGINT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),

UNIQUE (user_id, name)
);

CREATE INDEX vaults_user_id_idx ON vaults (user_id);
2 changes: 2 additions & 0 deletions migrations/0002_manifests_and_blobs.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
DROP TABLE IF EXISTS blobs;
DROP TABLE IF EXISTS manifests;
15 changes: 15 additions & 0 deletions migrations/0002_manifests_and_blobs.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
CREATE TABLE manifests (
vault_id UUID PRIMARY KEY REFERENCES vaults (id) ON DELETE CASCADE,
generation BIGINT NOT NULL,
ciphertext BYTEA NOT NULL,
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE blobs (
vault_id UUID NOT NULL REFERENCES vaults (id) ON DELETE CASCADE,
id TEXT NOT NULL,
size_bytes BIGINT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),

PRIMARY KEY (vault_id, id)
);
6 changes: 6 additions & 0 deletions migrations/migrations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package migrations

import "embed"

//go:embed *.sql
var FS embed.FS
Loading