From 18a2b7227e2027f6a7a887e898f500e21265bc47 Mon Sep 17 00:00:00 2001 From: Nathan Coad Date: Thu, 12 Sep 2024 11:59:41 +1000 Subject: [PATCH] structure appears to work --- .drone.sh | 35 ++++++ .drone.yml | 40 ++++++ .github/workflows/ci.yml | 92 ++++++++++++++ .github/workflows/release.yml | 77 ++++++++++++ .gitignore | 71 +++++++++++ 20240912012703_init.sql | 9 ++ Makefile | 2 +- README.md | 54 ++++---- db/db.go | 49 +++++--- db/migrations/20240407203525_init.down.txt | 0 db/migrations/20240407203525_init.up.txt | 7 -- db/migrations/20240912012927_init.sql | 38 ++++++ db/migrations/20240912985300_init_up.sql | 0 db/queries/query.sql | 28 +---- db/queries/query.txt | 25 ++++ go.mod | 24 ++-- go.sum | 39 +++--- internal/utils/certOperations.go | 139 +++++++++++++++++++++ internal/utils/utils.go | 55 ++++++++ main.go | 77 ++++++++++-- server/handler/home.go | 2 +- server/handler/vmCreate.go | 21 ++++ server/handler/vmUpdate.go | 21 ++++ server/router/router.go | 6 +- server/server.go | 55 +++++++- 25 files changed, 841 insertions(+), 125 deletions(-) create mode 100644 .drone.sh create mode 100644 .drone.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml create mode 100644 .gitignore create mode 100644 20240912012703_init.sql delete mode 100644 db/migrations/20240407203525_init.down.txt delete mode 100644 db/migrations/20240407203525_init.up.txt create mode 100644 db/migrations/20240912012927_init.sql delete mode 100644 db/migrations/20240912985300_init_up.sql create mode 100644 db/queries/query.txt create mode 100644 internal/utils/certOperations.go create mode 100644 internal/utils/utils.go create mode 100644 server/handler/vmCreate.go create mode 100644 server/handler/vmUpdate.go diff --git a/.drone.sh b/.drone.sh new file mode 100644 index 0000000..867ca55 --- /dev/null +++ b/.drone.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +# disable CGO for cross-compiling +export CGO_ENABLED=0 + +commit=$(git rev-parse HEAD) +#tag=$(git describe --tags --abbrev=0) +buildtime=$(TZ=Australia/Sydney date +%Y-%m-%dT%T%z) +git_version=$(git describe --tags --always --long --dirty) +package_name=vctp + +platforms=("linux/amd64" "darwin/amd64") + +echo Building $package_name with git version: $git_version +for platform in "${platforms[@]}" +do + platform_split=(${platform//\// }) + GOOS=${platform_split[0]} + GOARCH=${platform_split[1]} + output_name=$package_name'-'$GOOS'-'$GOARCH + if [ $GOOS = "windows" ]; then + output_name+='.exe' + fi + + echo "build commences" + env GOOS=$GOOS GOARCH=$GOARCH go build -trimpath -ldflags="-X main.sha1ver=$commit -X main.buildTime=$buildtime" -o build/$output_name $package + if [ $? -ne 0 ]; then + echo 'An error has occurred! Aborting the script execution...' + exit 1 + fi + echo "build complete at $buildtime : $output_name" + sha256sum build/$output_name > build/${output_name}_checksum.txt +done + +ls -lah build diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..0f0c54f --- /dev/null +++ b/.drone.yml @@ -0,0 +1,40 @@ +kind: pipeline +type: docker +name: default + +steps: +- name: build + image: golang + environment: + CGO_ENABLED: 0 + GOMODCACHE: '/drone/src/pkg.mod' + GOCACHE: '/drone/src/pkg.build' + volumes: + - name: shared + path: /shared + commands: + #- cp /shared/index.html ./www/ + - sh ./.drone.sh + +- name: dell-sftp-deploy + image: hypervtechnics/drone-sftp + settings: + host: deft.dell.com + username: + from_secret: DELLFTP_USER + password: + from_secret: DELLFTP_PASS + port: 22 + source: ./build + filter: vctp* + clean: false + target: / + overwrite: true + verbose: true + +volumes: +- name: shared + temp: {} +#- name: cache +# host: +# path: /var/lib/cache \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9d69038 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,92 @@ +name: CI +on: + push: + branches: + - main + paths-ignore: + - '.github/**' + pull_request: + branches: + - main +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: 1.22.x + - run: go mod download + - run: go install github.com/a-h/templ/cmd/templ@v0.2.771 + - run: make generate-templ + - uses: sqlc-dev/setup-sqlc@v4 + with: + sqlc-version: '1.27.0' + - run: sqlc vet + - run: sqlc generate + - name: Lint + uses: golangci/golangci-lint-action@v3 + with: + version: v1.54 + skip-pkg-cache: true + test: + name: Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: 1.22.x + - run: go mod download + - run: go install github.com/a-h/templ/cmd/templ@v0.2.771 + - run: make generate-templ + - uses: sqlc-dev/setup-sqlc@v4 + with: + sqlc-version: '1.27.0' + - run: sqlc generate + - name: Test + run: go test -race ./... + e2e: + name: End-to-End + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: 1.22.x + - run: go mod download + - run: go install github.com/a-h/templ/cmd/templ@v0.2.771 + - run: templ generate -path ./components + - uses: sqlc-dev/setup-sqlc@v4 + with: + sqlc-version: '1.27.0' + - run: sqlc generate + - run: go test ./... -tags=e2e + docker-publish: + name: Publish Docker + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + needs: + - lint + - test + - e2e + steps: + - uses: actions/checkout@v4 + - uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - uses: docker/metadata-action@v5 + id: meta + with: + images: ghcr.io/piszmog/vctp + - uses: docker/build-push-action@v5 + with: + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..c083c8f --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,77 @@ +name: Release +on: + workflow_dispatch: + inputs: + version: + description: The version to release (e.g. v1.0.0) + required: true + type: string + +jobs: + release: + name: Release + permissions: + contents: write + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: 1.22.x + - run: go mod download + - run: go install github.com/a-h/templ/cmd/templ@v0.2.771 + - name: Generate Templ Files + run: make generate-templ + - name: Generate CSS + run: | + curl -sLO https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64 + chmod +x tailwindcss-linux-x64 + mv tailwindcss-linux-x64 tailwindcss + ./tailwindcss -i ./styles/input.css -o ./dist/assets/css/output@${{ github.event.inputs.version }}.css --minify + - uses: sqlc-dev/setup-sqlc@v4 + with: + sqlc-version: '1.27.0' + - run: sqlc generate + - name: Build Application + run: go build -o ./app -ldflags="-s -w -X version.Value=${{ github.event.inputs.version }}" + - name: Create Tag + uses: piszmog/create-tag@v1 + with: + version: ${{ github.event.inputs.version }} + token: ${{ secrets.GITHUB_TOKEN }} + - name: Release + uses: softprops/action-gh-release@v2 + with: + name: ${{ github.event.inputs.version }} + tag_name: ${{ github.event.inputs.version }} + generate_release_notes: true + files: app + publish: + name: Publish Docker + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + needs: + - release + steps: + - uses: actions/checkout@v4 + - uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - uses: docker/metadata-action@v5 + id: meta + with: + images: ghcr.io/piszmog/my-app + tags: | + type=raw,value=${{ github.event.inputs.version }} + - uses: docker/build-push-action@v5 + with: + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + VERSION=$${{ github.event.inputs.version }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fff0d9e --- /dev/null +++ b/.gitignore @@ -0,0 +1,71 @@ +### Go template +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +vctp +build/ + +# Certificates +*.pem + +# Environment files +.env + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work + +### AppEngine template +# Google App Engine generated folder +appengine-generated/ + +/components/*/*.go +/components/*/*.txt +.idea +*.iml +dist/assets/css/ +*.sqlite3 +tmp/ +pb_data/ + +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk +/db/queries/*.go diff --git a/20240912012703_init.sql b/20240912012703_init.sql new file mode 100644 index 0000000..b9c449e --- /dev/null +++ b/20240912012703_init.sql @@ -0,0 +1,9 @@ +-- +goose Up +-- +goose StatementBegin +SELECT 'up SQL query'; +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +SELECT 'down SQL query'; +-- +goose StatementEnd diff --git a/Makefile b/Makefile index ec88389..72d8dbc 100644 --- a/Makefile +++ b/Makefile @@ -18,4 +18,4 @@ run: @go run main.go build: @echo "Building..." - @go build -o ./app -ldflags="-s -w -X version.Value=1.0.0" + @go build -o ./build/vctp -ldflags="-s -w -X version.Value=1.0.0" diff --git a/README.md b/README.md index e7a469e..f296176 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,17 @@ # Go + HTMX Template -This is a template repository that comes with everything you need to build a Web Application using Go (templ) and HTMX. +This is built from the template https://github.com/Piszmog/go-htmx-template that comes with everything you need to build a Web Application using Go (templ) and HTMX. The template comes with a basic structure of using a SQL DB (`sqlc`), E2E testing (playwright), and styling (tailwindcss). ## Getting Started -In the top right, select the dropdown __Use this template__ and select __Create a new repository__. +Clone https://github.com/Piszmog/go-htmx-template Once cloned, run the `update_module.sh` script to change the module to your module name. ```shell -./update_module vctpule +./update_module my-new-module ``` ## Technologies @@ -28,8 +28,8 @@ A few different technologies are configured to help getting off the ground easie - The script `upgrade_templ.sh` is available to make upgrading easier - [HTMX](https://htmx.org/) for HTML interaction - The script `upgrade_htmx.sh` is available to make upgrading easier -- [air](https://github.com/cosmtrek/air) for live reloading of the application. -- [golang migrate](https://github.com/golang-migrate/migrate) for DB migrations. + +- [golang migrate](https://github.com/golang-migrate/migrate) for DB migrations, TODO replace with [goose](https://github.com/pressly/goose) - [playwright-go](https://github.com/playwright-community/playwright-go) for E2E testing. Everything else uses the standard library. @@ -97,7 +97,17 @@ This is where `templ` files live. Anything you want to render to the user goes h This is the directory that `sqlc` generates to. Update `queries.sql` to build your database operations. -This project uses [golang migrate](https://github.com/golang-migrate/migrate) for DB +#### DB Migrations +This project now uses [goose](https://github.com/pressly/goose) for DB migrations. + +Install via `brew install goose` on a mac, or install via golang with command `go install github.com/pressly/goose/v3/cmd/goose@latest` + +```shell +goose -dir db/migrations sqlite3 ./db.sqlite3 create init sql +``` + +#### Deprecated +This project no longer uses [golang migrate](https://github.com/golang-migrate/migrate) for DB migrations. `sqlc` uses the `db/migrations` directory to generating DB tables. Call `db.Migrate(..)` to automatically migrate your database to the latest version. To add migration call the following command, @@ -108,6 +118,7 @@ migrate create -ext sql -dir db/migrations This package can be easily update to use `sqlx` as well. + ### Dist This is where your assets live. Any Javascript, images, or styling needs to go in the @@ -179,35 +190,16 @@ commands. ### Prerequisites -- Install [templ](https://templ.guide/quick-start/installation) +- Install [templ](https://templ.guide/quick-start/installation) - `go install github.com/a-h/templ/cmd/templ@latest` - Install [sqlc](https://docs.sqlc.dev/en/stable/overview/install.html) - Install [tailwindcss CLI](https://tailwindcss.com/docs/installation) -- Install [air](https://github.com/cosmtrek/air#installation) - -### air - -`air` has been configured with the file `.air.toml` to allow live reloading of the application -when a file changes. - -To run, install `air` +#### tailwindcss ```shell -go install github.com/cosmtrek/air@latest -``` - -Then simply run the command - -```shell -air -``` - -#### Address Already In Use Error - -Sometimes, you may run into the issue _address already in use_. If this is the case, you -can run this command to find the PID to kill it. - -```shell -ps aux | grep tmp/main +# Example for macOS arm64 +curl -sLO https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-macos-arm64 +chmod +x tailwindcss-macos-arm64 +sudo mv tailwindcss-macos-arm64 /usr/local/bin/tailwindcss ``` ### Makefile diff --git a/db/db.go b/db/db.go index 6306e3d..aa6bdc9 100644 --- a/db/db.go +++ b/db/db.go @@ -3,13 +3,10 @@ package db import ( "database/sql" "embed" - "fmt" - "vctp/db/queries" "log/slog" + "vctp/db/queries" - "github.com/golang-migrate/migrate/v4" - "github.com/golang-migrate/migrate/v4/database/sqlite3" - "github.com/golang-migrate/migrate/v4/source/iofs" + "github.com/pressly/goose/v3" ) //go:embed migrations/*.sql @@ -35,21 +32,37 @@ func New(logger *slog.Logger, url string) (Database, error) { // Migrate runs the migrations on the database. Assumes the database is SQLite. func Migrate(db Database) error { - driver, err := sqlite3.WithInstance(db.DB(), &sqlite3.Config{}) - if err != nil { - return fmt.Errorf("failed to create database driver: %w", err) + + goose.SetBaseFS(migrations) + + if err := goose.SetDialect("sqlite3"); err != nil { + panic(err) } - iofsDriver, err := iofs.New(migrations, "migrations") - if err != nil { - return fmt.Errorf("failed to create iofs: %w", err) - } - defer iofsDriver.Close() - - m, err := migrate.NewWithInstance("iofs", iofsDriver, "sqlite3", driver) - if err != nil { - return fmt.Errorf("failed to create migration: %w", err) + if err := goose.Up(db.DB(), "migrations"); err != nil { + panic(err) } - return m.Up() + // TODO - replace with goose + /* + driver, err := sqlite3.WithInstance(db.DB(), &sqlite3.Config{}) + if err != nil { + return fmt.Errorf("failed to create database driver: %w", err) + } + + iofsDriver, err := iofs.New(migrations, "migrations") + if err != nil { + return fmt.Errorf("failed to create iofs: %w", err) + } + defer iofsDriver.Close() + + m, err := migrate.NewWithInstance("iofs", iofsDriver, "sqlite3", driver) + if err != nil { + return fmt.Errorf("failed to create migration: %w", err) + } + + return m.Up() + */ + + return nil } diff --git a/db/migrations/20240407203525_init.down.txt b/db/migrations/20240407203525_init.down.txt deleted file mode 100644 index e69de29..0000000 diff --git a/db/migrations/20240407203525_init.up.txt b/db/migrations/20240407203525_init.up.txt deleted file mode 100644 index bee06bc..0000000 --- a/db/migrations/20240407203525_init.up.txt +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TABLE IF NOT EXISTS authors ( - id INTEGER PRIMARY KEY, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, - name TEXT NOT NULL, - bio TEXT -); - diff --git a/db/migrations/20240912012927_init.sql b/db/migrations/20240912012927_init.sql new file mode 100644 index 0000000..e7f7660 --- /dev/null +++ b/db/migrations/20240912012927_init.sql @@ -0,0 +1,38 @@ +-- +goose Up +-- +goose StatementBegin +CREATE TABLE IF NOT EXISTS "Inventory" ( + "Iid" INTEGER UNIQUE, + "Name" TEXT, + "Vcenter" TEXT, + "VmId" TEXT, + "EventKey" TEXT, + "EventId" TEXT, + "CreationTime" TEXT, + "DeletionTime" TEXT, + "ResourcePool" TEXT, + "VmType" TEXT, + "Datacenter" TEXT, + "Cluster" TEXT, + "Folder" TEXT, + "ProvisionedDisk" REAL, + "InitialVcpus" INTEGER, + "InitialRam" INTEGER, + "SrmPlaceholder" INTEGER +); + +CREATE TABLE IF NOT EXISTS "Updates" ( + "Uid" INTEGER UNIQUE, + "InventoryId" INTEGER, + "UpdateTime" TEXT, + "UpdateType" TEXT, + "NewVcpus" INTEGER, + "NewRam" INTEGER, + "NewResourcePool" TEXT +) +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +DROP TABLE "Inventory"; +DROP TABLE "Updates"; +-- +goose StatementEnd diff --git a/db/migrations/20240912985300_init_up.sql b/db/migrations/20240912985300_init_up.sql deleted file mode 100644 index e69de29..0000000 diff --git a/db/queries/query.sql b/db/queries/query.sql index b3eff66..249f358 100644 --- a/db/queries/query.sql +++ b/db/queries/query.sql @@ -1,25 +1,3 @@ --- name: GetAuthor :one -SELECT * FROM authors -WHERE id = ? LIMIT 1; - --- name: ListAuthors :many -SELECT * FROM authors -ORDER BY name; - --- name: CreateAuthor :one -INSERT INTO authors ( - name, bio -) VALUES ( - ?, ? -) -RETURNING *; - --- name: UpdateAuthor :exec -UPDATE authors -SET name = ?, -bio = ? -WHERE id = ?; - --- name: DeleteAuthor :exec -DELETE FROM authors -WHERE id = ?; +-- name: ListInventory :many +SELECT * FROM "Inventory" +ORDER BY "Name"; \ No newline at end of file diff --git a/db/queries/query.txt b/db/queries/query.txt new file mode 100644 index 0000000..b3eff66 --- /dev/null +++ b/db/queries/query.txt @@ -0,0 +1,25 @@ +-- name: GetAuthor :one +SELECT * FROM authors +WHERE id = ? LIMIT 1; + +-- name: ListAuthors :many +SELECT * FROM authors +ORDER BY name; + +-- name: CreateAuthor :one +INSERT INTO authors ( + name, bio +) VALUES ( + ?, ? +) +RETURNING *; + +-- name: UpdateAuthor :exec +UPDATE authors +SET name = ?, +bio = ? +WHERE id = ?; + +-- name: DeleteAuthor :exec +DELETE FROM authors +WHERE id = ?; diff --git a/go.mod b/go.mod index 5b8ea69..3de7ef0 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,13 @@ module vctp go 1.23.1 require ( - github.com/a-h/templ v0.2.771 - github.com/golang-migrate/migrate/v4 v4.17.1 + github.com/a-h/templ v0.2.778 + github.com/joho/godotenv v1.5.1 github.com/playwright-community/playwright-go v0.4201.1 - github.com/stretchr/testify v1.8.4 - github.com/tursodatabase/libsql-client-go v0.0.0-20240812094001-348a4e45b535 - modernc.org/sqlite v1.32.0 + github.com/pressly/goose/v3 v3.22.0 + github.com/stretchr/testify v1.9.0 + github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d + modernc.org/sqlite v1.33.0 ) require ( @@ -20,22 +21,21 @@ require ( github.com/go-jose/go-jose/v3 v3.0.1 // indirect github.com/go-stack/stack v1.8.1 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/kr/text v0.2.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-sqlite3 v1.14.22 // indirect + github.com/mfridman/interpolate v0.0.2 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - go.uber.org/atomic v1.11.0 // indirect + github.com/sethvargo/go-retry v0.3.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 // indirect - golang.org/x/sys v0.24.0 // indirect + golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.25.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect modernc.org/gc/v3 v3.0.0-20240801135723-a856999a2e4a // indirect - modernc.org/libc v1.59.9 // indirect + modernc.org/libc v1.60.1 // indirect modernc.org/mathutil v1.6.0 // indirect modernc.org/memory v1.8.0 // indirect modernc.org/strutil v1.2.0 // indirect diff --git a/go.sum b/go.sum index ff09c1e..8956d50 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/a-h/templ v0.2.771 h1:4KH5ykNigYGGpCe0fRJ7/hzwz72k3qFqIiiLLJskbSo= github.com/a-h/templ v0.2.771/go.mod h1:lq48JXoUvuQrU0VThrK31yFwdRjTCnIE5bcPCM9IP1w= +github.com/a-h/templ v0.2.778 h1:VzhOuvWECrwOec4790lcLlZpP4Iptt5Q4K9aFxQmtaM= +github.com/a-h/templ v0.2.778/go.mod h1:lq48JXoUvuQrU0VThrK31yFwdRjTCnIE5bcPCM9IP1w= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo= @@ -16,8 +18,6 @@ github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkc github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= -github.com/golang-migrate/migrate/v4 v4.17.1 h1:4zQ6iqL6t6AiItphxJctQb3cFqWiSpMnX7wLTPnnYO4= -github.com/golang-migrate/migrate/v4 v4.17.1/go.mod h1:m8hinFyWBn0SA4QKHuKh175Pm9wjmxj3S2Mia7dbXzM= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -25,23 +25,18 @@ github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlG github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= 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/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -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-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= -github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= +github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= @@ -50,26 +45,33 @@ github.com/playwright-community/playwright-go v0.4201.1 h1:fFX/02r3wrL+8NB132Rcd github.com/playwright-community/playwright-go v0.4201.1/go.mod h1:hpEOnUo/Kgb2lv5lEY29jbW5Xgn7HaBeiE+PowRad8k= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pressly/goose/v3 v3.22.0 h1:wd/7kNiPTuNAztWun7iaB98DrhulbWPrzMAaw2DEZNw= +github.com/pressly/goose/v3 v3.22.0/go.mod h1:yJM3qwSj2pp7aAaCvso096sguezamNb2OBgxCnh/EYg= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= +github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tursodatabase/libsql-client-go v0.0.0-20240812094001-348a4e45b535 h1:iLjJLq2A5J6L9zrhyNn+fpmxFvtEpYB4XLMr0rX3epI= github.com/tursodatabase/libsql-client-go v0.0.0-20240812094001-348a4e45b535/go.mod h1:l8xTsYB90uaVdMHXMCxKKLSgw5wLYBwBKKefNIUnm9s= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d h1:dOMI4+zEbDI37KGb0TI44GUAwxHF9cMsIoDTJ7UmgfU= +github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d/go.mod h1:l8xTsYB90uaVdMHXMCxKKLSgw5wLYBwBKKefNIUnm9s= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA= golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= @@ -78,6 +80,8 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= @@ -92,6 +96,7 @@ modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= modernc.org/ccgo/v4 v4.20.7 h1:skrinQsjxWfvj6nbC3ztZPJy+NuwmB3hV9zX/pthNYQ= modernc.org/ccgo/v4 v4.20.7/go.mod h1:UOkI3JSG2zT4E2ioHlncSOZsXbuDCZLvPi3uMlZT5GY= +modernc.org/ccgo/v4 v4.21.0 h1:kKPI3dF7RIag8YcToh5ZwDcVMIv6VGa0ED5cvh0LMW4= modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= modernc.org/gc/v2 v2.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M= @@ -100,6 +105,8 @@ modernc.org/gc/v3 v3.0.0-20240801135723-a856999a2e4a h1:CfbpOLEo2IwNzJdMvE8aiRbP modernc.org/gc/v3 v3.0.0-20240801135723-a856999a2e4a/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= modernc.org/libc v1.59.9 h1:k+nNDDakwipimgmJ1D9H466LhFeSkaPPycAs1OZiDmY= modernc.org/libc v1.59.9/go.mod h1:EY/egGEU7Ju66eU6SBqCNYaFUDuc4npICkMWnU5EE3A= +modernc.org/libc v1.60.1 h1:at373l8IFRTkJIkAU85BIuUoBM4T1b51ds0E1ovPG2s= +modernc.org/libc v1.60.1/go.mod h1:xJuobKuNxKH3RUatS7GjR+suWj+5c2K7bi4m/S5arOY= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= @@ -110,6 +117,8 @@ modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.33.0 h1:WWkA/T2G17okiLGgKAj4/RMIvgyMT19yQ038160IeYk= +modernc.org/sqlite v1.33.0/go.mod h1:9uQ9hF/pCZoYZK73D/ud5Z7cIRIILSZI8NdIemVMTX8= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/internal/utils/certOperations.go b/internal/utils/certOperations.go new file mode 100644 index 0000000..7455992 --- /dev/null +++ b/internal/utils/certOperations.go @@ -0,0 +1,139 @@ +package utils + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "log" + "math/big" + "net" + "os" + "path/filepath" + "time" +) + +func GenerateCerts(tlsCert string, tlsKey string) { + // @see https://shaneutt.com/blog/golang-ca-and-signed-cert-go/ + // @see https://golang.org/src/crypto/tls/generate_cert.go + validFrom := "" + validFor := 365 * 24 * time.Hour + isCA := true + + // Get the hostname + hostname, err := os.Hostname() + if err != nil { + panic(err) + } + + // Check that the directory exists + relativePath := filepath.Dir(tlsCert) + log.Printf("GenerateCerts relative path for file creation is '%s'\n", relativePath) + _, err = os.Stat(relativePath) + if os.IsNotExist(err) { + log.Printf("Certificate path does not exist, creating %s before generating certificate\n", relativePath) + os.MkdirAll(relativePath, os.ModePerm) + } + + // Generate a private key + priv, err := rsa.GenerateKey(rand.Reader, rsaBits) + if err != nil { + log.Fatalf("Failed to generate private key: %v", err) + } + + var notBefore time.Time + if len(validFrom) == 0 { + notBefore = time.Now() + } else { + notBefore, err = time.Parse("Jan 2 15:04:05 2006", validFrom) + if err != nil { + log.Fatalf("Failed to parse creation date: %v", err) + } + } + + notAfter := notBefore.Add(validFor) + + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + log.Fatalf("Failed to generate serial number: %v", err) + } + + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{"DTMS"}, + }, + NotBefore: notBefore, + NotAfter: notAfter, + + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + + template.DNSNames = append(template.DNSNames, hostname) + + // Add in all the non-local IPs + ifaces, err := net.Interfaces() + + if err != nil { + log.Printf("Error enumerating interfaces: %v\n", err) + } + + for _, i := range ifaces { + addrs, err := i.Addrs() + if err != nil { + log.Printf("Oops: %v\n", err) + } + + for _, address := range addrs { + // check the address type and if it is not a loopback then add it to the list + if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { + if ipnet.IP.To4() != nil { + template.IPAddresses = append(template.IPAddresses, ipnet.IP) + } + } + } + } + + if isCA { + template.IsCA = true + template.KeyUsage |= x509.KeyUsageCertSign + } + + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) + if err != nil { + log.Fatalf("Failed to create certificate: %v", err) + } + + certOut, err := os.Create(tlsCert) + if err != nil { + log.Fatalf("Failed to open %s for writing: %v", tlsCert, err) + } + if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { + log.Fatalf("Failed to write data to %s: %v", tlsCert, err) + } + if err := certOut.Close(); err != nil { + log.Fatalf("Error closing %s: %v", tlsCert, err) + } + log.Printf("wrote %s\n", tlsCert) + + keyOut, err := os.OpenFile(tlsKey, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + log.Fatalf("Failed to open %s for writing: %v", tlsKey, err) + return + } + privBytes, err := x509.MarshalPKCS8PrivateKey(priv) + if err != nil { + log.Fatalf("Unable to marshal private key: %v", err) + } + if err := pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil { + log.Fatalf("Failed to write data to %s: %v", tlsKey, err) + } + if err := keyOut.Close(); err != nil { + log.Fatalf("Error closing %s: %v", tlsKey, err) + } + log.Printf("wrote %s\n", tlsKey) +} diff --git a/internal/utils/utils.go b/internal/utils/utils.go new file mode 100644 index 0000000..fd2e074 --- /dev/null +++ b/internal/utils/utils.go @@ -0,0 +1,55 @@ +package utils + +import ( + "log" + "log/slog" + "net" + "os" + "path/filepath" +) + +const rsaBits = 4096 + +func GetFilePath(path string) string { + // Check for empty filename + if len(path) == 0 { + return "" + } + + // check if filename exists + if _, err := os.Stat(path); os.IsNotExist((err)) { + slog.Info("File '%s' not found, searching in same directory as binary", path) + // if not, check that it exists in the same directory as the currently executing binary + ex, err2 := os.Executable() + if err2 != nil { + slog.Error("Error determining binary path : '%s'", err) + return "" + } + binaryPath := filepath.Dir(ex) + path = filepath.Join(binaryPath, path) + } + return path +} + +// Get preferred outbound ip of this machine +// @see https://stackoverflow.com/questions/23558425/how-do-i-get-the-local-ip-address-in-go +func GetOutboundIP() net.IP { + conn, err := net.Dial("udp", "8.8.8.8:80") + if err != nil { + log.Fatal(err) + } + defer conn.Close() + + localAddr := conn.LocalAddr().(*net.UDPAddr) + + return localAddr.IP +} + +// Check if a file exists from https://stackoverflow.com/questions/12518876/how-to-check-if-a-file-exists-in-go +func FileExists(filename string) bool { + info, err := os.Stat(filename) + if os.IsNotExist(err) { + return false + } + return !info.IsDir() +} diff --git a/main.go b/main.go index 649db54..9c498a6 100644 --- a/main.go +++ b/main.go @@ -1,22 +1,36 @@ package main import ( - "errors" + "fmt" + "log/slog" + "os" "vctp/db" + utils "vctp/internal/utils" "vctp/log" "vctp/server" "vctp/server/router" - "os" - "github.com/golang-migrate/migrate/v4" + "github.com/joho/godotenv" +) + +var ( + bindDisableTls bool ) func main() { + // Load data from environment file + envFilename := utils.GetFilePath(".env") + err := godotenv.Load(envFilename) + if err != nil { + panic("Error loading .env file") + } + logger := log.New( log.GetLevel(), log.GetOutput(), ) + // Configure database database, err := db.New(logger, "./db.sqlite3") if err != nil { logger.Error("Failed to create database", "error", err) @@ -24,21 +38,68 @@ func main() { } defer database.Close() - if err = db.Migrate(database); err != nil && !errors.Is(err, migrate.ErrNoChange) { + /* + if err = db.Migrate(database); err != nil && !errors.Is(err, migrate.ErrNoChange) { + logger.Error("failed to migrate database", "error", err) + return + } + */ + + if err = db.Migrate(database); err != nil { logger.Error("failed to migrate database", "error", err) return } - port := os.Getenv("PORT") - if port == "" { - port = "8080" + // Determine bind IP + bindIP := os.Getenv("BIND_IP") + if bindIP == "" { + bindIP = utils.GetOutboundIP().String() + } + // Determine bind port + bindPort := os.Getenv("BIND_PORT") + if bindPort == "" { + bindPort = "9443" + } + bindAddress := fmt.Sprint(bindIP, ":", bindPort) + slog.Info("Will listen on address", "ip", bindIP, "port", bindPort) + + // Determine bind disable TLS + bindDisableTlsEnv := os.Getenv("BIND_DISABLE_TLS") + if bindDisableTlsEnv == "true" { + bindDisableTls = true } + // Get file names for TLS cert/key + tlsCertFilename := os.Getenv("TLS_CERT_FILE") + if tlsCertFilename != "" { + tlsCertFilename = utils.GetFilePath(tlsCertFilename) + } else { + tlsCertFilename = "./cert.pem" + } + + tlsKeyFilename := os.Getenv("TLS_KEY_FILE") + if tlsKeyFilename != "" { + tlsKeyFilename = utils.GetFilePath(tlsKeyFilename) + } else { + tlsKeyFilename = "./privkey.pem" + } + + // Generate certificate if required + if !(utils.FileExists(tlsCertFilename) && utils.FileExists(tlsKeyFilename)) { + slog.Warn("Specified TLS certificate or private key do not exist", "certificate", tlsCertFilename, "tls-key", tlsKeyFilename) + utils.GenerateCerts(tlsCertFilename, tlsKeyFilename) + } + + // Start server svr := server.New( logger, - ":"+port, + bindAddress, server.WithRouter(router.New(logger, database)), ) + svr.DisableTls(bindDisableTls) + svr.SetCertificate(tlsCertFilename) + svr.SetPrivateKey(tlsKeyFilename) + svr.StartAndWait() } diff --git a/server/handler/home.go b/server/handler/home.go index f3cbda5..c6ee346 100644 --- a/server/handler/home.go +++ b/server/handler/home.go @@ -1,9 +1,9 @@ package handler import ( + "net/http" "vctp/components/core" "vctp/components/home" - "net/http" ) // Home handles the home page. diff --git a/server/handler/vmCreate.go b/server/handler/vmCreate.go new file mode 100644 index 0000000..7e5688c --- /dev/null +++ b/server/handler/vmCreate.go @@ -0,0 +1,21 @@ +package handler + +import ( + "fmt" + "io" + "net/http" +) + +// VmCreate receives the CloudEvent for a VM creation +func (h *Handler) VmCreate(w http.ResponseWriter, r *http.Request) { + reqBody, err := io.ReadAll(r.Body) + if err != nil { + fmt.Fprintf(w, "Invalid data received") + w.WriteHeader(http.StatusInternalServerError) + return + } + + h.Logger.Debug("received create request", "body", string(reqBody)) + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, "Create Request (%d): %v\n", len(reqBody), string(reqBody)) +} diff --git a/server/handler/vmUpdate.go b/server/handler/vmUpdate.go new file mode 100644 index 0000000..20d7a70 --- /dev/null +++ b/server/handler/vmUpdate.go @@ -0,0 +1,21 @@ +package handler + +import ( + "fmt" + "io" + "net/http" +) + +// VmUpdate receives the CloudEvent for a VM modification or move +func (h *Handler) VmUpdate(w http.ResponseWriter, r *http.Request) { + reqBody, err := io.ReadAll(r.Body) + if err != nil { + fmt.Fprintf(w, "Invalid data received") + w.WriteHeader(http.StatusInternalServerError) + return + } + + h.Logger.Debug("received update request", "body", string(reqBody)) + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, "Update Request (%d): %v\n", len(reqBody), string(reqBody)) +} diff --git a/server/router/router.go b/server/router/router.go index 9d90322..2b8ead4 100644 --- a/server/router/router.go +++ b/server/router/router.go @@ -1,12 +1,12 @@ package router import ( + "log/slog" + "net/http" "vctp/db" "vctp/dist" "vctp/server/handler" "vctp/server/middleware" - "log/slog" - "net/http" ) func New(logger *slog.Logger, database db.Database) http.Handler { @@ -19,6 +19,8 @@ func New(logger *slog.Logger, database db.Database) http.Handler { mux.Handle("/assets/", middleware.CacheMiddleware(http.FileServer(http.FS(dist.AssetsDir)))) mux.HandleFunc("/", h.Home) + mux.HandleFunc("/api/event/vm/create", h.VmCreate) + mux.HandleFunc("/api/event/vm/update", h.VmUpdate) return middleware.NewLoggingMiddleware(logger, mux) } diff --git a/server/server.go b/server/server.go index 8593928..d6ca773 100644 --- a/server/server.go +++ b/server/server.go @@ -2,6 +2,7 @@ package server import ( "context" + "crypto/tls" "log/slog" "net/http" "os" @@ -11,16 +12,36 @@ import ( // Server represents an HTTP server. type Server struct { - srv *http.Server - logger *slog.Logger + srv *http.Server + logger *slog.Logger + disableTls bool + tlsCertFilename string + tlsKeyFilename string } // New creates a new server with the given logger, address and options. func New(logger *slog.Logger, addr string, opts ...Option) *Server { + + // Set some options for TLS + tlsConfig := &tls.Config{ + MinVersion: tls.VersionTLS12, + CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256}, + PreferServerCipherSuites: true, + InsecureSkipVerify: true, + CipherSuites: []uint16{ + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + tls.TLS_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_RSA_WITH_AES_256_CBC_SHA, + }, + } + srv := &http.Server{ Addr: addr, WriteTimeout: 15 * time.Second, ReadTimeout: 15 * time.Second, + TLSConfig: tlsConfig, + TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)), } for _, opt := range opts { opt(&Server{srv: srv}) @@ -56,6 +77,21 @@ func WithRouter(handler http.Handler) Option { } } +// DisableTls sets the disable tls value +func (s *Server) DisableTls(disableTls bool) { + s.disableTls = disableTls +} + +// SetCertificate sets the path to the certificate used for TLS, in PEM format +func (s *Server) SetCertificate(tlsCertFilename string) { + s.tlsCertFilename = tlsCertFilename +} + +// SetPrivateKey sets the path to the private key used for TLS, in PEM format +func (s *Server) SetPrivateKey(tlsKeyFilename string) { + s.tlsKeyFilename = tlsKeyFilename +} + // StartAndWait starts the server and waits for a signal to shut down. func (s *Server) StartAndWait() { s.Start() @@ -64,11 +100,20 @@ func (s *Server) StartAndWait() { // Start starts the server. func (s *Server) Start() { + go func() { - s.logger.Info("starting server", "port", s.srv.Addr) - if err := s.srv.ListenAndServe(); err != nil { - s.logger.Warn("failed to start server", "error", err) + if s.disableTls { + s.logger.Info("starting server", "port", s.srv.Addr) + if err := s.srv.ListenAndServe(); err != nil { + s.logger.Warn("failed to start server", "error", err) + } + } else { + s.logger.Info("starting TLS server", "port", s.srv.Addr, "cert", s.tlsCertFilename, "key", s.tlsKeyFilename) + if err := s.srv.ListenAndServeTLS(s.tlsCertFilename, s.tlsKeyFilename); err != nil { + s.logger.Warn("failed to start server", "error", err) + } } + }() }