From 3835b8ba8614152c414520a55ad385d459b64f6f Mon Sep 17 00:00:00 2001 From: Mans Ziesel Date: Sat, 14 Dec 2024 14:44:37 +0100 Subject: [PATCH] add postgres db --- Makefile | 33 +++++++++-- README.md | 4 ++ cmd/server/main.go | 9 ++- database/machine.go | 22 ++++++++ .../20241214113527_add_ulid_functions.sql | 13 +++++ .../20241214113904_add_machine_table.sql | 26 +++++++++ .../20241214133322_add_agent_table.sql | 17 ++++++ db/seed/seed.go | 42 ++++++++++++++ deployments/compose-dev.yaml | 27 +++++++++ go.mod | 9 ++- go.sum | 19 +++++-- internal/agent/collect.go | 2 +- internal/models/models.go | 56 +++++++------------ 13 files changed, 226 insertions(+), 53 deletions(-) create mode 100644 database/machine.go create mode 100644 db/migrations/20241214113527_add_ulid_functions.sql create mode 100644 db/migrations/20241214113904_add_machine_table.sql create mode 100644 db/migrations/20241214133322_add_agent_table.sql create mode 100644 db/seed/seed.go diff --git a/Makefile b/Makefile index 042f2e0..487975d 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,15 @@ -GO_FILES := $(shell find . -name '*.go' -not -path './build/*') +GO_FILES := $(shell find . -name '*.go' -not -path './build/*' -not -path './tmp/**') +GOOSE_DBSTRING := "user=zadmin_user password=s3cret! dbname=zadmin_dev sslmode=disable host=localhost" +GOOSE_MIGRATION_PATH := "./db/migrations/" +GOOSE_SEED_PATH := "./db/seed/" build-server: $(GO_FILES) mkdir -p build - go build -o build/zadmin-server ./cmd/server/main.go + CGO_ENABLED=0 go build -o build/zadmin-server ./cmd/server/main.go build-client: $(GO_FILES) mkdir -p build - go build -o build/zadmin-client ./cmd/client/main.go + CGO_ENABLED=0 go build -o build/zadmin-client ./cmd/client/main.go docker-up: mkdir -p ./tmp @@ -16,6 +19,7 @@ docker-down: docker compose -f ./deployments/compose-dev.yaml down docker-clean: docker-down + docker volume rm -f deployments_pg_data rm -rf ./tmp server: build-server @@ -27,4 +31,25 @@ client: build-client clean: rm -r build -.PHONY: clean run-server run-client docker-up +install-goose: + go install github.com/pressly/goose/v3/cmd/goose@latest + +db-status: + @GOOSE_DRIVER=postgres GOOSE_DBSTRING=$(GOOSE_DBSTRING) goose -dir=$(GOOSE_MIGRATION_PATH) status + +db-up: + @GOOSE_DRIVER=postgres GOOSE_DBSTRING=$(GOOSE_DBSTRING) goose -dir=$(GOOSE_MIGRATION_PATH) up + +db-reset: + @GOOSE_DRIVER=postgres GOOSE_DBSTRING=$(GOOSE_DBSTRING) goose -dir=$(GOOSE_MIGRATION_PATH) reset + +goose-custom: + @GOOSE_DRIVER=postgres GOOSE_DBSTRING=$(GOOSE_DBSTRING) goose -dir=$(GOOSE_MIGRATION_PATH) $(cmd) + +goose-seed-custom: + @GOOSE_DRIVER=postgres GOOSE_DBSTRING=$(GOOSE_DBSTRING) goose -dir=$(GOOSE_SEED_PATH) $(cmd) + +db-seed: + echo "TODO" + +.PHONY: clean run-server run-client docker-up docker-down docker-clean server client install-goose diff --git a/README.md b/README.md index 2885390..b61c22f 100644 --- a/README.md +++ b/README.md @@ -69,3 +69,7 @@ Ideas??? - The admin runs a installer with this credential - The installer enrolls the device in zadmin and generates a unique package for this machine - The installer places this package on the machine + +## Docs + +[goose](https://github.com/pressly/goose) is used for migrations diff --git a/cmd/server/main.go b/cmd/server/main.go index 66f14b5..6777aa3 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -3,6 +3,7 @@ package main import ( "encoding/json" "log" + "runtime" "git.mziesel.nl/mans/zadmin/internal/models" "github.com/nats-io/nats.go" @@ -37,7 +38,9 @@ func main() { } }) - shouldExit := false - for !shouldExit { - } + // shouldExit := false + // for !shouldExit { + // } + // keep running indefenitly + runtime.Goexit() } diff --git a/database/machine.go b/database/machine.go new file mode 100644 index 0000000..cf9d5b0 --- /dev/null +++ b/database/machine.go @@ -0,0 +1,22 @@ +package database + +import ( + "database/sql" + + "git.mziesel.nl/mans/zadmin/internal/models" + "github.com/oklog/ulid/v2" +) + +func CreateMachine(db *sql.DB, m *models.Machine) error { + query := "INSERT INTO machine (machine_name, os_type, os_arch) VALUES ($1, $2, $3)" + _, err := db.Exec(query, m.MachineName, m.OsType, m.GoArch) + if err != nil { + return err + } + return nil +} + +func GetMachine(db *sql.DB) *models.Machine { + ulid.Now() + return &models.Machine{} +} diff --git a/db/migrations/20241214113527_add_ulid_functions.sql b/db/migrations/20241214113527_add_ulid_functions.sql new file mode 100644 index 0000000..12db5e8 --- /dev/null +++ b/db/migrations/20241214113527_add_ulid_functions.sql @@ -0,0 +1,13 @@ +-- +goose Up +-- +goose StatementBegin +CREATE EXTENSION IF NOT EXISTS pgcrypto; +CREATE OR REPLACE FUNCTION generate_ulid() RETURNS uuid + AS $$ + SELECT (lpad(to_hex(floor(extract(epoch FROM clock_timestamp()) * 1000)::bigint), 12, '0') || encode(gen_random_bytes(10), 'hex'))::uuid; + $$ LANGUAGE SQL; +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +DROP FUNCTION generate_ulid(); +-- +goose StatementEnd diff --git a/db/migrations/20241214113904_add_machine_table.sql b/db/migrations/20241214113904_add_machine_table.sql new file mode 100644 index 0000000..a4175ae --- /dev/null +++ b/db/migrations/20241214113904_add_machine_table.sql @@ -0,0 +1,26 @@ +-- +goose Up +-- +goose StatementBegin +CREATE TABLE machine ( + id UUID NOT NULL DEFAULT generate_ulid(), + + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + machine_name VARCHAR(255) NOT NULL, + description text, + os_type VARCHAR(255) NOT NULL, + os_arch VARCHAR(255) NOT NULL, + hostname VARCHAR(255), + public_ipv4_address INET, + public_ipv6_address INET, + agent_version VARCHAR(255), + host_uptime VARCHAR(255), + logged_on_users VARCHAR(255), + os_version VARCHAR(255), + + PRIMARY KEY(id) +); +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +DROP TABLE machine; +-- +goose StatementEnd diff --git a/db/migrations/20241214133322_add_agent_table.sql b/db/migrations/20241214133322_add_agent_table.sql new file mode 100644 index 0000000..4431a6d --- /dev/null +++ b/db/migrations/20241214133322_add_agent_table.sql @@ -0,0 +1,17 @@ +-- +goose Up +-- +goose StatementBegin +CREATE TABLE agent ( + id UUID NOT NULL DEFAULT generate_ulid(), + machine_id UUID NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + + PRIMARY KEY(id), + FOREIGN KEY (machine_id) REFERENCES machine(id) +); +CREATE INDEX idx_agent_machine_id ON agent(machine_id); +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +DROP TABLE agent; +-- +goose StatementEnd diff --git a/db/seed/seed.go b/db/seed/seed.go new file mode 100644 index 0000000..d9274a7 --- /dev/null +++ b/db/seed/seed.go @@ -0,0 +1,42 @@ +package main + +import ( + "database/sql" + "fmt" + "log" + + "git.mziesel.nl/mans/zadmin/database" + "git.mziesel.nl/mans/zadmin/internal/models" + _ "github.com/lib/pq" + "github.com/oklog/ulid/v2" +) + +func main() { + db, err := sql.Open("postgres", "user=zadmin_user password=s3cret! dbname=zadmin_dev sslmode=disable host=localhost") + if err != nil { + log.Fatalf("goose: failed to open DB: %v\n", err) + } + + defer func() { + if err := db.Close(); err != nil { + log.Fatalf("failed to close DB: %v\n", err) + } + }() + + createTestMachines(db) +} + +func createTestMachines(db *sql.DB) { + machine := models.Machine{ + ID: ulid.Make(), + MachineName: "test-machine", + OsType: "linux", + GoArch: "amd64", + } + fmt.Println(models.PrettyFormatData(machine)) + + err := database.CreateMachine(db, &machine) + if err != nil { + log.Fatal(err) + } +} diff --git a/deployments/compose-dev.yaml b/deployments/compose-dev.yaml index 5e869c0..97d618e 100644 --- a/deployments/compose-dev.yaml +++ b/deployments/compose-dev.yaml @@ -1,6 +1,33 @@ services: nats-server: image: nats:latest + restart: unless-stopped ports: - 4222:4222 command: ["-DV"] + postgres: + image: postgres:16-alpine + restart: unless-stopped + ports: + - 5432:5432 + volumes: + - pg_data:/var/lib/postgresql/data + environment: + - POSTGRES_PASSWORD=s3cret! + - POSTGRES_USER=zadmin_user + - POSTGRES_DB=zadmin_dev + pgadmin: + image: dpage/pgadmin4 + restart: unless-stopped + ports: + - "8888:80" + environment: + PGADMIN_DEFAULT_EMAIL: mans@mziesel.com + PGADMIN_DEFAULT_PASSWORD: secret + PGADMIN_LISTEN_PORT: 80 + volumes: + - pgadmin_data:/var/lib/pgadmin + +volumes: + pg_data: {} + pgadmin_data: {} diff --git a/go.mod b/go.mod index 413a10e..9516f36 100644 --- a/go.mod +++ b/go.mod @@ -3,14 +3,17 @@ module git.mziesel.nl/mans/zadmin go 1.23.0 require ( + github.com/google/uuid v1.6.0 + github.com/lib/pq v1.10.9 github.com/mackerelio/go-osstat v0.2.5 github.com/nats-io/nats.go v1.37.0 - golang.org/x/sys v0.20.0 + github.com/oklog/ulid/v2 v2.1.0 ) require ( - github.com/klauspost/compress v1.17.2 // indirect + github.com/klauspost/compress v1.17.7 // indirect github.com/nats-io/nkeys v0.4.7 // indirect github.com/nats-io/nuid v1.0.1 // indirect - golang.org/x/crypto v0.18.0 // indirect + golang.org/x/crypto v0.31.0 // indirect + golang.org/x/sys v0.28.0 // indirect ) diff --git a/go.sum b/go.sum index 54a2b60..42d5ca0 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,9 @@ -github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= -github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= +github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mackerelio/go-osstat v0.2.5 h1:+MqTbZUhoIt4m8qzkVoXUJg1EuifwlAJSk4Yl2GXh+o= github.com/mackerelio/go-osstat v0.2.5/go.mod h1:atxwWF+POUZcdtR1wnsUcQxTytoHG4uhl2AKKzrOajY= github.com/nats-io/nats.go v1.37.0 h1:07rauXbVnnJvv1gfIyghFEo6lUcYRY0WXc3x7x0vUxE= @@ -8,7 +12,10 @@ github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU= +github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= +github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/internal/agent/collect.go b/internal/agent/collect.go index 0a6f813..c93392c 100644 --- a/internal/agent/collect.go +++ b/internal/agent/collect.go @@ -80,7 +80,7 @@ func GetMachineData(ac models.MachineAgentConfig) (models.MachineData, error) { data.PublicIPv6Address = ipResponseBody.Address // get machine uptime - uptime, err := uptime.GetUptime() + uptime, err := uptime.Get() if err != nil { LogError("failed to get machine uptime", err) return data, nil diff --git a/internal/models/models.go b/internal/models/models.go index 244357e..1568e22 100644 --- a/internal/models/models.go +++ b/internal/models/models.go @@ -1,47 +1,42 @@ package models import ( - "fmt" - "strings" "time" + + "github.com/oklog/ulid/v2" ) // Model for the user accounts -type AccountModel struct { - ID string `json:"id"` +type Account struct { + ID ulid.ULID `json:"id"` FirstName string `json:"first_name"` LastName string `json:"last_name"` PasswordHash string `json:"password_hash"` CreatedAt time.Time `json:"created_at"` } -type OrganizationModel struct { - ID string `json:"id"` +type Organization struct { + ID ulid.ULID `json:"id"` Name string `json:"name"` Description string `json:"description"` CreatedAt time.Time `json:"created_at"` } // Model for the hosts managed by zadmin -type MachineModel struct { - ID string `json:"id"` // machine ID - Organization OrganizationModel `json:"organization"` // Organization this machine belongs to - CreatedAt time.Time `json:"created_at"` // Time created in zadmin - Description string `json:"description"` // Description of machine - AgentConfig MachineAgentConfig `json:"agent_config"` // Config used by this machine - OsType string `json:"os_type"` // OS Type TODO: make constants - GoArch string `json:"go_arch"` // Go arch -} - -// Data of the machine -type MachineData struct { +type Machine struct { + ID ulid.ULID `json:"id"` // machine ID + MachineName string `json:"machine_name"` // Name of machine + Organization Organization `json:"organization"` // Organization this machine belongs to + CreatedAt time.Time `json:"created_at"` // Time created in zadmin + Description string `json:"description"` // Description of machine + OsType string `json:"os_type"` // OS Type TODO: make constants + GoArch string `json:"go_arch"` // Go arch FirstSeen time.Time `json:"first_seen"` // Time of first contact with zadmin LastSeen time.Time `json:"last_seen"` // Time of last contact with zadmin Hostname string `json:"hostname"` // Configured system hostname PublicIPv4Address string `json:"public_ipv4_address"` // Public IPv4 address PublicIPv6Address string `json:"public_ipv6_address"` // Public IPv6 address NetworkInterfaces []MachineInterfaceDetails `json:"machine_interfaces"` // Interface details - AgentVersion string `json:"agent_version"` // installed zadmin agent version AntivirusInfo AntivirusInfo `json:"antivirus_info"` // Antivirus info UsageStatistics UsageStatistics `json:"usage_statistics"` // Usage statistics of machine SoftwareCatalog []Software `json:"software_catalog"` // Software installed on machine @@ -50,23 +45,6 @@ type MachineData struct { OSVersion string `json:"os_version"` // OS version } -// String method for MachineData -func (m MachineData) String() string { - var sb strings.Builder - sb.WriteString(fmt.Sprintf("MachineData:\n")) - sb.WriteString(fmt.Sprintf(" FirstSeen: %s\n", m.FirstSeen)) - sb.WriteString(fmt.Sprintf(" LastSeen: %s\n", m.LastSeen)) - sb.WriteString(fmt.Sprintf(" Hostname: %s\n", m.Hostname)) - sb.WriteString(fmt.Sprintf(" PublicIPv4Address: %s\n", m.PublicIPv4Address)) - sb.WriteString(fmt.Sprintf(" PublicIPv6Address: %s\n", m.PublicIPv6Address)) - sb.WriteString(fmt.Sprintf(" AgentVersion: %s\n", m.AgentVersion)) - sb.WriteString(fmt.Sprintf(" UptimeSeconds: %d\n", m.UptimeSeconds)) - sb.WriteString(fmt.Sprintf(" OSVersion: %s\n", m.OSVersion)) - sb.WriteString(fmt.Sprintf(" LoggedOnUsers: %v\n", m.LoggedOnUsers)) - - return sb.String() -} - // Software installed on a Machine type Software struct { Name string @@ -110,6 +88,12 @@ type MachineInterfaceDetails struct { Addressess []string `json:"addressess"` // Interface IP addressess } +type Agent struct { + ID ulid.ULID `json:"id"` + Machine Machine `json:"machine"` + CreatedAt time.Time `json:"created_at"` // Time created in zadmin +} + // Configuration to be used by the agent on a machine type MachineAgentConfig struct { RESTServerHostname string `json:"rest_server_hostname"` // Hostname used for REST requests