add rpm generation code
Some checks failed
continuous-integration/drone/push Build was killed

This commit is contained in:
2026-01-13 20:09:19 +11:00
parent a81613a8c2
commit 3ceba1a117
22 changed files with 388 additions and 290 deletions

View File

@@ -17,6 +17,7 @@ steps:
mount:
- pkg.mod
- pkg.build
- pkg.tools
volumes:
- name: cache
path: /go
@@ -36,13 +37,27 @@ steps:
- go install github.com/a-h/templ/cmd/templ@latest
- go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
- go install github.com/swaggo/swag/cmd/swag@latest
- go install github.com/goreleaser/nfpm/v2/cmd/nfpm@latest
- sqlc generate
- templ generate -path ./components
- swag init --exclude "pkg.mod,pkg.build,pkg.tools" -o server/router/docs
- chmod +x ./scripts/*.sh
- ./scripts/update-swagger-ui.sh
- ./scripts/drone.sh
- cp ./build/cbs-linux-amd64 /shared/
- cp ./build/vctp-linux-amd64 /shared/
- name: rpm
image: ghcr.io/goreleaser/nfpm
environment:
TZ: UTC
volumes:
- name: shared
path: /shared
commands:
- cp /shared/vctp-linux-amd64 ./build/vctp-linux-amd64
#- find .
- nfpm package --config vctp.yml --packager rpm --target ./build/
- ls -lah ./build/
- name: dell-sftp-deploy
image: hypervtechnics/drone-sftp
@@ -76,6 +91,7 @@ steps:
mount:
- pkg.mod
- pkg.build
- pkg.tools
volumes:
- name: cache
path: /go

232
README.md
View File

@@ -1,110 +1,14 @@
# Go + HTMX Template
# Initial setup
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
Clone https://github.com/Piszmog/go-htmx-template
Once cloned, run the `update_module.sh` script to change the module to your module name.
## Pre-requisite tools
```shell
./update_module my-new-module
go install github.com/a-h/templ/cmd/templ@latest
go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
go install github.com/swaggo/swag/cmd/swag@latest
```
## Technologies
A few different technologies are configured to help getting off the ground easier.
- [sqlc](https://sqlc.dev/) for database layer
- Stubbed to use SQLite
- This can be easily swapped with [sqlx](https://jmoiron.github.io/sqlx/)
- The script `upgrade_sqlc.sh` is available to upgrade GitHub Workflow files to latest sqlc version
- [Tailwind CSS](https://tailwindcss.com/) for styling
- Output is generated with the [CLI](https://tailwindcss.com/docs/installation)
- [templ](https://templ.guide/) for creating HTML
- 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
- [goose](https://github.com/pressly/goose) for DB migrations
TODO: investigate https://github.com/DATA-DOG/go-sqlmock for testing
Technologies we're no longer using:
- [golang migrate](https://github.com/golang-migrate/migrate) for DB migrations
- [playwright-go](https://github.com/playwright-community/playwright-go) for E2E testing.
Everything else uses the standard library.
## Structure
(Now out of date)
```text
.
├── Makefile
├── components
│   ├── core
│   │   └── html.templ
│   └── home
│   └── home.templ
├── db
│   ├── db.go
│   ├── local.go
│   ├── migrations
│   │   ├── 20240407203525_init.down.sql
│   │   └── 20240407203525_init.up.sql
│   └── queries
│   └── query.sql
├── db.sqlite3
├── dist
│   ├── assets
│   │   └── js
│   │   └── htmx@1.9.10.min.js
│   └── dist.go
├── e2e
│   ├── e2e_test.go
│   ├── home_test.go
│   └── testdata
│   └── seed.sql
├── go.mod
├── go.sum
├── log
│   └── log.go
├── main.go
├── server
│   ├── handler
│   │   ├── handler.go
│   │   └── home.go
│   ├── middleware
│   │   ├── cache.go
│   │   ├── logging.go
│   │   └── middleware.go
│   ├── router
│   │   └── router.go
│   └── server.go
├── sqlc.yml
├── styles
│   └── input.css
├── tailwind.config.js
└── version
└── version.go
```
### Components
This is where `templ` files live. Anything you want to render to the user goes here. Note, all
`*.go` files will be ignored by `git` (configured in `.gitignore`).
### DB
This is the directory that `sqlc` generates to. Update `queries.sql` to build
your database operations. The schema for `sqlc` lives in `db/schema.sql`.
Once `queries.sql` is updated, run `make generate-sql` to update the generated models
#### DB Migrations
## Database
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`
@@ -114,6 +18,16 @@ Create a new up/down migration file with this command
goose -dir db/migrations sqlite3 ./db.sqlite3 create init sql
```
```shell
sqlc generate
```
## HTML templates
Run `templ generate -path ./components` to generate code based on template files
## Documentation
Run `swag init --exclude "pkg.mod,pkg.build,pkg.tools" -o server/router/docs`
#### Database Configuration
By default the app uses SQLite and creates/opens `db.sqlite3`. You can opt into PostgreSQL
by setting environment variables:
@@ -138,117 +52,3 @@ Hourly and daily snapshot table retention can be configured with environment var
- `HOURLY_SNAPSHOT_MAX_AGE_DAYS` (default: 60)
- `DAILY_SNAPSHOT_MAX_AGE_MONTHS` (default: 12)
### Dist
This is where your assets live. Any Javascript, images, or styling needs to go in the
`dist/assets` directory. The directory will be embedded into the application.
Note, the `dist/assets/css` will be ignored by `git` (configured in `.gitignore`) since the
files that are written to this directory are done by the Tailwind CSS CLI. Custom styles should
go in the `styles/input.css` file.
### E2E
To test the UI, the `e2e` directory contains the Go tests for performing End to end testing. To
run the tests, run the command
```shell
go test -v ./... -tags=e2e
```
The end to end tests, will start up the app, on a random port, seeding the database using the
`seed.sql` file. Once the tests are complete, the app will be stopped.
The E2E tests use Playwright (Go) for better integration into the Go tooling.
### Log
This contains helper function to create a `slog.Logger`. Log level and output type can be set
with then environment variables `LOG_LEVEL` and `LOG_OUTPUT`. The logger will write to
`stdout`.
### Server
This contains everything related to the HTTP server. It comes with a graceful shutdown handler
that handles `SIGINT`.
#### Router
This package sets up the routing for the application, such as the `/assets/` path and `/` path.
It uses the standard libraries mux for routing. You can easily swap out for other HTTP
routers such as [gorilla/mux](https://github.com/gorilla/mux).
#### Middleware
This package contains any middleware to configured with routes.
#### Handler
This package contains the handler to handle the actual routes.
#### Styles
This contains the `input.css` that the Tailwind CSS CLI uses to generate your output CSS.
Update `input.css` with any custom CSS you need and it will be included in the output CSS.
#### Version
This package allows you to set a version at build time. If not set, the version defaults to
`dev`. To set the version run the following command,
```shell
go build -o ./app -ldflags="-X version.Value=1.0.0"
```
See the `Makefile` for building the application.
## Run
There are a couple builtin ways to run the application - using `air` or the `Makefile` helper
commands.
### Prerequisites
- 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)
#### tailwindcss
```shell
# 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
You can also run with the provided `Makefile`. There are commands to generate `templ` files and
tailwind output css.
```shell
# Generate and watch templ
make generate-templ-watch
# Genrate and watch tailwindcss
make generate-tailwind-watch
# Run application
make run
```
## Github Workflow
The repository comes with two Github workflows as well. One called `ci.yml` that lints and
tests your code. The other called `release.yml` that creates a tag, GitHub Release, and
attaches the Linux binary to the Release.
Note, the version of `github.com/a-h/templ/cmd/templ` matches the version in `go.mod`. If these
do not match, the build will fail. When upgrading your `templ` version, make sure to update
`ci.yml` and `release.yml`.
### GoReleaser
If you need to compile for more than Linux, see [GoReleaser](https://goreleaser.com/) for a
better release process.

View File

@@ -10,5 +10,78 @@ templ Header() {
<title>vCTP API</title>
<script src="/assets/js/htmx@v2.0.2.min.js"></script>
<link href={ "/assets/css/output@" + version.Value + ".css" } rel="stylesheet"/>
<style>
:root {
--web2-blue: #3b82f6;
--web2-cyan: #22d3ee;
--web2-slate: #0f172a;
--web2-card: #ffffff;
--web2-shadow: 0 20px 40px rgba(15, 23, 42, 0.15);
--web2-soft-shadow: 0 8px 20px rgba(15, 23, 42, 0.12);
}
body {
font-family: "Trebuchet MS", "Lucida Grande", "Verdana", sans-serif;
color: var(--web2-slate);
}
.web2-bg {
background: radial-gradient(circle at top left, #e0f2fe 0%, #f8fafc 45%, #e2e8f0 100%);
}
.web2-shell {
max-width: 1100px;
margin: 0 auto;
padding: 2rem 1.5rem 4rem;
}
.web2-header {
background: linear-gradient(135deg, #0ea5e9 0%, #6366f1 100%);
color: #fff;
border-radius: 22px;
box-shadow: var(--web2-shadow);
padding: 1.5rem 2rem;
}
.web2-card {
background: var(--web2-card);
border-radius: 18px;
box-shadow: var(--web2-soft-shadow);
padding: 1.5rem 1.75rem;
}
.web2-pill {
display: inline-flex;
align-items: center;
gap: 0.4rem;
background: rgba(255, 255, 255, 0.2);
border: 1px solid rgba(255, 255, 255, 0.35);
padding: 0.35rem 0.8rem;
border-radius: 999px;
font-size: 0.85rem;
letter-spacing: 0.02em;
backdrop-filter: blur(6px);
}
.web2-link {
color: var(--web2-blue);
text-decoration: none;
font-weight: 600;
}
.web2-link:hover {
text-decoration: underline;
}
.web2-button {
background: linear-gradient(180deg, #60a5fa 0%, #2563eb 100%);
color: #fff;
padding: 0.5rem 1rem;
border-radius: 999px;
box-shadow: 0 6px 16px rgba(37, 99, 235, 0.35);
font-weight: 600;
text-decoration: none;
}
.web2-button:hover {
filter: brightness(1.05);
}
.web2-list li {
background: rgba(255, 255, 255, 0.9);
border-radius: 14px;
padding: 0.85rem 1.1rem;
box-shadow: 0 6px 16px rgba(15, 23, 42, 0.08);
}
</style>
</head>
}

View File

@@ -38,13 +38,13 @@ func Header() templ.Component {
var templ_7745c5c3_Var2 templ.SafeURL
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinURLErrs("/assets/css/output@" + version.Value + ".css")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/core/header.templ`, Line: 12, Col: 61}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/header.templ`, Line: 12, Col: 61}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" rel=\"stylesheet\"></head>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" rel=\"stylesheet\"><style>\n\t\t\t:root {\n\t\t\t\t--web2-blue: #3b82f6;\n\t\t\t\t--web2-cyan: #22d3ee;\n\t\t\t\t--web2-slate: #0f172a;\n\t\t\t\t--web2-card: #ffffff;\n\t\t\t\t--web2-shadow: 0 20px 40px rgba(15, 23, 42, 0.15);\n\t\t\t\t--web2-soft-shadow: 0 8px 20px rgba(15, 23, 42, 0.12);\n\t\t\t}\n\t\t\tbody {\n\t\t\t\tfont-family: \"Trebuchet MS\", \"Lucida Grande\", \"Verdana\", sans-serif;\n\t\t\t\tcolor: var(--web2-slate);\n\t\t\t}\n\t\t\t.web2-bg {\n\t\t\t\tbackground: radial-gradient(circle at top left, #e0f2fe 0%, #f8fafc 45%, #e2e8f0 100%);\n\t\t\t}\n\t\t\t.web2-shell {\n\t\t\t\tmax-width: 1100px;\n\t\t\t\tmargin: 0 auto;\n\t\t\t\tpadding: 2rem 1.5rem 4rem;\n\t\t\t}\n\t\t\t.web2-header {\n\t\t\t\tbackground: linear-gradient(135deg, #0ea5e9 0%, #6366f1 100%);\n\t\t\t\tcolor: #fff;\n\t\t\t\tborder-radius: 22px;\n\t\t\t\tbox-shadow: var(--web2-shadow);\n\t\t\t\tpadding: 1.5rem 2rem;\n\t\t\t}\n\t\t\t.web2-card {\n\t\t\t\tbackground: var(--web2-card);\n\t\t\t\tborder-radius: 18px;\n\t\t\t\tbox-shadow: var(--web2-soft-shadow);\n\t\t\t\tpadding: 1.5rem 1.75rem;\n\t\t\t}\n\t\t\t.web2-pill {\n\t\t\t\tdisplay: inline-flex;\n\t\t\t\talign-items: center;\n\t\t\t\tgap: 0.4rem;\n\t\t\t\tbackground: rgba(255, 255, 255, 0.2);\n\t\t\t\tborder: 1px solid rgba(255, 255, 255, 0.35);\n\t\t\t\tpadding: 0.35rem 0.8rem;\n\t\t\t\tborder-radius: 999px;\n\t\t\t\tfont-size: 0.85rem;\n\t\t\t\tletter-spacing: 0.02em;\n\t\t\t\tbackdrop-filter: blur(6px);\n\t\t\t}\n\t\t\t.web2-link {\n\t\t\t\tcolor: var(--web2-blue);\n\t\t\t\ttext-decoration: none;\n\t\t\t\tfont-weight: 600;\n\t\t\t}\n\t\t\t.web2-link:hover {\n\t\t\t\ttext-decoration: underline;\n\t\t\t}\n\t\t\t.web2-button {\n\t\t\t\tbackground: linear-gradient(180deg, #60a5fa 0%, #2563eb 100%);\n\t\t\t\tcolor: #fff;\n\t\t\t\tpadding: 0.5rem 1rem;\n\t\t\t\tborder-radius: 999px;\n\t\t\t\tbox-shadow: 0 6px 16px rgba(37, 99, 235, 0.35);\n\t\t\t\tfont-weight: 600;\n\t\t\t\ttext-decoration: none;\n\t\t\t}\n\t\t\t.web2-button:hover {\n\t\t\t\tfilter: brightness(1.05);\n\t\t\t}\n\t\t\t.web2-list li {\n\t\t\t\tbackground: rgba(255, 255, 255, 0.9);\n\t\t\t\tborder-radius: 14px;\n\t\t\t\tpadding: 0.85rem 1.1rem;\n\t\t\t\tbox-shadow: 0 6px 16px rgba(15, 23, 42, 0.08);\n\t\t\t}\n\t\t</style></head>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

View File

@@ -14,14 +14,37 @@ templ Index(info BuildInfo) {
<!DOCTYPE html>
<html lang="en">
@core.Header()
<body class="flex flex-col min-h-screen">
<main class="flex-grow">
<body class="flex flex-col min-h-screen web2-bg">
<main class="flex-grow web2-shell space-y-8">
<section class="web2-header">
<div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div>
<h1 class="text-5xl font-bold">Build Information</h1>
<p class="mt-4"><strong>Build Time:</strong> {info.BuildTime}</p>
<p class="mt-4"><strong>SHA1 Version:</strong> {info.SHA1Ver}</p>
<p class="mt-4"><strong>Go Runtime Version:</strong> {info.GoVersion}</p>
<div class="web2-pill">vCTP Console</div>
<h1 class="mt-3 text-4xl font-bold">Build Intelligence Dashboard</h1>
<p class="mt-2 text-sm opacity-90">A glossy, snapshot-ready view of what is running.</p>
</div>
<div class="flex flex-wrap gap-3">
<a class="web2-button" href="/snapshots/hourly">Hourly Snapshots</a>
<a class="web2-button" href="/snapshots/daily">Daily Snapshots</a>
<a class="web2-button" href="/snapshots/monthly">Monthly Snapshots</a>
</div>
</div>
</section>
<section class="grid gap-6 md:grid-cols-3">
<div class="web2-card">
<p class="text-xs uppercase tracking-[0.2em] text-slate-400">Build Time</p>
<p class="mt-3 text-xl font-semibold">{info.BuildTime}</p>
</div>
<div class="web2-card">
<p class="text-xs uppercase tracking-[0.2em] text-slate-400">SHA1 Version</p>
<p class="mt-3 text-xl font-semibold">{info.SHA1Ver}</p>
</div>
<div class="web2-card">
<p class="text-xs uppercase tracking-[0.2em] text-slate-400">Go Runtime</p>
<p class="mt-3 text-xl font-semibold">{info.GoVersion}</p>
</div>
</section>
</main>
</body>
@core.Footer()

View File

@@ -47,46 +47,46 @@ func Index(info BuildInfo) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<body class=\"flex flex-col min-h-screen\"><main class=\"flex-grow\"><div><h1 class=\"text-5xl font-bold\">Build Information</h1><p class=\"mt-4\"><strong>Build Time:</strong> ")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<body class=\"flex flex-col min-h-screen web2-bg\"><main class=\"flex-grow web2-shell space-y-8\"><section class=\"web2-header\"><div class=\"flex flex-col gap-4 md:flex-row md:items-center md:justify-between\"><div><div class=\"web2-pill\">vCTP Console</div><h1 class=\"mt-3 text-4xl font-bold\">Build Intelligence Dashboard</h1><p class=\"mt-2 text-sm opacity-90\">A glossy, snapshot-ready view of what is running.</p></div><div class=\"flex flex-wrap gap-3\"><a class=\"web2-button\" href=\"/snapshots/hourly\">Hourly Snapshots</a> <a class=\"web2-button\" href=\"/snapshots/daily\">Daily Snapshots</a> <a class=\"web2-button\" href=\"/snapshots/monthly\">Monthly Snapshots</a></div></div></section><section class=\"grid gap-6 md:grid-cols-3\"><div class=\"web2-card\"><p class=\"text-xs uppercase tracking-[0.2em] text-slate-400\">Build Time</p><p class=\"mt-3 text-xl font-semibold\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(info.BuildTime)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/index.templ`, Line: 21, Col: 80}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/index.templ`, Line: 37, Col: 59}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</p><p class=\"mt-4\"><strong>SHA1 Version:</strong> ")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</p></div><div class=\"web2-card\"><p class=\"text-xs uppercase tracking-[0.2em] text-slate-400\">SHA1 Version</p><p class=\"mt-3 text-xl font-semibold\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(info.SHA1Ver)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/index.templ`, Line: 22, Col: 80}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/index.templ`, Line: 41, Col: 57}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</p><p class=\"mt-4\"><strong>Go Runtime Version:</strong> ")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</p></div><div class=\"web2-card\"><p class=\"text-xs uppercase tracking-[0.2em] text-slate-400\">Go Runtime</p><p class=\"mt-3 text-xl font-semibold\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(info.GoVersion)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/index.templ`, Line: 23, Col: 88}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/index.templ`, Line: 45, Col: 59}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</p></div></main></body>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</p></div></section></main></body>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

View File

@@ -25,21 +25,33 @@ templ SnapshotListPage(title string, subtitle string, entries []SnapshotEntry) {
<!DOCTYPE html>
<html lang="en">
@core.Header()
<body class="flex flex-col min-h-screen">
<main class="flex-grow">
<body class="flex flex-col min-h-screen web2-bg">
<main class="flex-grow web2-shell space-y-8">
<section class="web2-header">
<div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div>
<h1 class="text-4xl font-bold">{title}</h1>
<p class="mt-2 text-gray-600">{subtitle}</p>
<ul class="mt-6 space-y-2">
<div class="web2-pill">Snapshot Library</div>
<h1 class="mt-3 text-4xl font-bold">{title}</h1>
<p class="mt-2 text-sm opacity-90">{subtitle}</p>
</div>
<a class="web2-button" href="/">Back to Dashboard</a>
</div>
</section>
<section class="web2-card">
<div class="flex items-center justify-between">
<h2 class="text-lg font-semibold">Available Exports</h2>
<span class="text-xs uppercase tracking-[0.2em] text-slate-400">{len(entries)} files</span>
</div>
<ul class="mt-6 space-y-3 web2-list">
for _, entry := range entries {
<li>
<a class="text-blue-600 hover:underline" href={entry.Link}>
{entry.Label}
</a>
<li class="flex items-center justify-between gap-4">
<span class="text-sm font-semibold text-slate-700">{entry.Label}</span>
<a class="web2-link" href={entry.Link}>Download XLSX</a>
</li>
}
</ul>
</div>
</section>
</main>
</body>
@core.Footer()

View File

@@ -133,69 +133,82 @@ func SnapshotListPage(title string, subtitle string, entries []SnapshotEntry) te
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<body class=\"flex flex-col min-h-screen\"><main class=\"flex-grow\"><div><h1 class=\"text-4xl font-bold\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<body class=\"flex flex-col min-h-screen web2-bg\"><main class=\"flex-grow web2-shell space-y-8\"><section class=\"web2-header\"><div class=\"flex flex-col gap-4 md:flex-row md:items-center md:justify-between\"><div><div class=\"web2-pill\">Snapshot Library</div><h1 class=\"mt-3 text-4xl font-bold\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/snapshots.templ`, Line: 31, Col: 42}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 34, Col: 49}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</h1><p class=\"mt-2 text-gray-600\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</h1><p class=\"mt-2 text-sm opacity-90\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(subtitle)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/snapshots.templ`, Line: 32, Col: 44}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 35, Col: 51}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</p><ul class=\"mt-6 space-y-2\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</p></div><a class=\"web2-button\" href=\"/\">Back to Dashboard</a></div></section><section class=\"web2-card\"><div class=\"flex items-center justify-between\"><h2 class=\"text-lg font-semibold\">Available Exports</h2><span class=\"text-xs uppercase tracking-[0.2em] text-slate-400\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, entry := range entries {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<li><a class=\"text-blue-600 hover:underline\" href=\"")
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(len(entries))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 templ.SafeURL
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinURLErrs(entry.Link)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/snapshots.templ`, Line: 36, Col: 65}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 44, Col: 83}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, " files</span></div><ul class=\"mt-6 space-y-3 web2-list\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, entry := range entries {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<li class=\"flex items-center justify-between gap-4\"><span class=\"text-sm font-semibold text-slate-700\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/snapshots.templ`, Line: 37, Col: 21}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 49, Col: 71}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</a></li>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</span> <a class=\"web2-link\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 templ.SafeURL
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinURLErrs(entry.Link)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/snapshots.templ`, Line: 50, Col: 45}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\">Download XLSX</a></li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</ul></div></main></body>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</ul></section></main></body>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -203,7 +216,7 @@ func SnapshotListPage(title string, subtitle string, entries []SnapshotEntry) te
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</html>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

View File

@@ -10,10 +10,10 @@ import (
type Querier interface {
CleanupUpdates(ctx context.Context, arg queries.CleanupUpdatesParams) error
CleanupUpdatesNullVm(ctx context.Context) error
CreateEvent(ctx context.Context, arg queries.CreateEventParams) (queries.Events, error)
CreateEvent(ctx context.Context, arg queries.CreateEventParams) (queries.Event, error)
CreateInventory(ctx context.Context, arg queries.CreateInventoryParams) (queries.Inventory, error)
CreateInventoryHistory(ctx context.Context, arg queries.CreateInventoryHistoryParams) (queries.InventoryHistory, error)
CreateUpdate(ctx context.Context, arg queries.CreateUpdateParams) (queries.Updates, error)
CreateUpdate(ctx context.Context, arg queries.CreateUpdateParams) (queries.Update, error)
GetInventoryByName(ctx context.Context, name string) ([]queries.Inventory, error)
GetInventoryByVcenter(ctx context.Context, vcenter string) ([]queries.Inventory, error)
GetInventoryEventId(ctx context.Context, cloudid sql.NullString) (queries.Inventory, error)
@@ -21,15 +21,15 @@ type Querier interface {
GetInventoryVmId(ctx context.Context, arg queries.GetInventoryVmIdParams) (queries.Inventory, error)
GetInventoryVmUuid(ctx context.Context, arg queries.GetInventoryVmUuidParams) (queries.Inventory, error)
GetReportInventory(ctx context.Context) ([]queries.Inventory, error)
GetReportUpdates(ctx context.Context) ([]queries.Updates, error)
GetVmUpdates(ctx context.Context, arg queries.GetVmUpdatesParams) ([]queries.Updates, error)
GetReportUpdates(ctx context.Context) ([]queries.Update, error)
GetVmUpdates(ctx context.Context, arg queries.GetVmUpdatesParams) ([]queries.Update, error)
InventoryCleanup(ctx context.Context, arg queries.InventoryCleanupParams) error
InventoryCleanupTemplates(ctx context.Context) error
InventoryCleanupVcenter(ctx context.Context, vc string) error
InventoryMarkDeleted(ctx context.Context, arg queries.InventoryMarkDeletedParams) error
InventoryUpdate(ctx context.Context, arg queries.InventoryUpdateParams) error
ListEvents(ctx context.Context) ([]queries.Events, error)
ListEvents(ctx context.Context) ([]queries.Event, error)
ListInventory(ctx context.Context) ([]queries.Inventory, error)
ListUnprocessedEvents(ctx context.Context, eventtime sql.NullInt64) ([]queries.Events, error)
ListUnprocessedEvents(ctx context.Context, eventtime sql.NullInt64) ([]queries.Event, error)
UpdateEventsProcessed(ctx context.Context, eid int64) error
}

View File

@@ -4,6 +4,7 @@
export CGO_ENABLED=0
package_name=vctp
package=./
commit=$(git rev-parse HEAD)
buildtime=$(date +%Y-%m-%dT%T%z)
#Extract the version from yml
@@ -16,6 +17,7 @@ echo Building::
echo - Version $package_version
echo - Commit $commit
echo - Build Time $buildtime
mkdir -p build
for platform in "${platforms[@]}"
do
platform_split=(${platform//\// })
@@ -38,6 +40,6 @@ do
#sha256sum build/${output_name}.gz > build/${output_name}_checksum.txt
done
#nfpm package --config $package_name.yml --packager rpm --target build/
nfpm package --config $package_name.yml --packager rpm --target build/
ls -lah build

View File

@@ -97,5 +97,4 @@ func (h *Handler) VmImport(w http.ResponseWriter, r *http.Request) {
"status": "OK",
"message": fmt.Sprintf("Successfully processed import request for VM '%s'", inData.Name),
})
return
}

View File

@@ -73,7 +73,7 @@ func (h *Handler) VmModifyEvent(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusAccepted)
json.NewEncoder(w).Encode(map[string]string{
"status": "OK",
"message": fmt.Sprintf("Received update event successfully but no config changes were found"),
"message": "Received update event successfully but no config changes were found",
})
return
} else {
@@ -178,7 +178,7 @@ func (h *Handler) VmModifyEvent(w http.ResponseWriter, r *http.Request) {
} else {
h.Logger.Debug("Matched regex", "disk_owner", matches[1])
if strings.ToLower(matches[1]) == strings.ToLower(event.CloudEvent.Data.VM.Name) {
if strings.EqualFold(matches[1], event.CloudEvent.Data.VM.Name) {
h.Logger.Debug("This disk belongs to this VM")
changeFound = true
diskChangeFound = true
@@ -278,7 +278,7 @@ func (h *Handler) VmModifyEvent(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{
"status": "OK",
"message": fmt.Sprintf("Successfully processed vm modify event"),
"message": "Successfully processed vm modify event",
})
return
}
@@ -304,7 +304,7 @@ func (h *Handler) VmModifyEvent(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{
"status": "OK",
"message": fmt.Sprintf("Successfully processed vm modify event"),
"message": "Successfully processed vm modify event",
})
return
}

View File

@@ -65,7 +65,7 @@ func (h *Handler) VmMoveEvent(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(map[string]string{
"status": "ERROR",
"message": fmt.Sprintf("CloudEvent missing resource pool data"),
"message": "CloudEvent missing resource pool data",
})
return
}
@@ -151,7 +151,7 @@ func (h *Handler) VmMoveEvent(w http.ResponseWriter, r *http.Request) {
//fmt.Fprintf(w, "Processed update event: %v\n", result)
json.NewEncoder(w).Encode(map[string]string{
"status": "OK",
"message": fmt.Sprintf("Successfully processed move event"),
"message": "Successfully processed move event",
})
return
}

View File

@@ -1,11 +1,13 @@
version: 2
sql:
- engine: sqlite
queries:
- db/queries/query.sql
schema:
- db/schema.sql
queries:
- db/queries/query.sql
gen:
go:
package: queries
out: db/queries
emit_json_tags: true
emit_db_tags: true

10
src/postinstall.sh Normal file
View File

@@ -0,0 +1,10 @@
#!/bin/bash
if command -v systemctl >/dev/null 2>&1; then
systemctl daemon-reload || :
if [ "$1" -eq 1 ]; then
systemctl enable --now vctp.service || :
else
systemctl try-restart vctp.service || :
fi
fi

8
src/postremove.sh Normal file
View File

@@ -0,0 +1,8 @@
#!/bin/bash
if command -v systemctl >/dev/null 2>&1; then
systemctl daemon-reload || :
if [ "$1" -ge 1 ]; then
systemctl try-restart vctp.service || :
fi
fi

45
src/preinstall.sh Normal file
View File

@@ -0,0 +1,45 @@
#!/bin/bash
USER="vctp"
GROUP="dtms"
# Path to the custom sudoers file
SUDOERS_FILE="/etc/sudoers.d/${USER}"
# create a group & user if not exists
getent group "$GROUP" >/dev/null || groupadd -r "$GROUP"; /bin/true
getent passwd "$USER" >/dev/null || useradd -r -g "$GROUP" -m -s /bin/bash -c "vctp service" "$USER"
getent passwd tftp >/dev/null || useradd -r -g tftp -s /sbin/nologin tftp
# create vctp config directory if it doesn't exist
[ -d /etc/dtms ] || mkdir -p /etc/dtms
# set group ownership on vctp config directory if not already done
[ "$(stat -c "%G" /etc/dtms)" = "$GROUP" ] || chgrp -R "$GROUP" /etc/dtms
# set permissions on vctp config directory if not already done
[ "$(stat -c "%a" /etc/dtms)" = "774" ] || chmod -R 774 /etc/dtms
# create vctp data directory if it doesn't exist
[ -d /var/lib/vctp ] || mkdir -p /var/lib/vctp
# set user ownership on vctp data directory if not already done
[ "$(stat -c "%U" /var/lib/vctp)" = "$USER" ] || chown -R "$USER" /var/lib/vctp
# set group ownership on vctp data directory if not already done
[ "$(stat -c "%G" /var/lib/vctp)" = "$GROUP" ] || chgrp -R "$GROUP" /var/lib/vctp
# Check if firewalld is installed and active
if command -v systemctl >/dev/null 2>&1 && systemctl is-enabled firewalld >/dev/null 2>&1 && systemctl is-active firewalld >/dev/null 2>&1; then
echo "Firewalld is enabled and running. Adding necessary ports..."
# Open HTTPS port (443/tcp)
firewall-cmd --permanent --add-service=https >/dev/null 2>&1
# Open custom application port (9443/tcp)
firewall-cmd --permanent --add-port=9443/tcp >/dev/null 2>&1
# Reload firewalld to apply changes
firewall-cmd --reload >/dev/null 2>&1
else
echo "Firewalld is not running or not enabled. Skipping firewall configuration."
fi

8
src/preremove.sh Normal file
View File

@@ -0,0 +1,8 @@
#!/bin/bash
if command -v systemctl >/dev/null 2>&1; then
if [ "$1" -eq 0 ]; then
systemctl stop vctp.service || :
systemctl disable vctp.service || :
fi
fi

1
src/vctp.default Normal file
View File

@@ -0,0 +1 @@
CPE_OPTS='-config /etc/dtms/vctp.yml -log-level info -log-output text'

21
src/vctp.service Normal file
View File

@@ -0,0 +1,21 @@
[Unit]
Description=vCTP monitors VMware VM inventory and event data to build chargeback reports
Documentation=https://gitlab.dell.com/
ConditionPathExists=/usr/bin/vctp-linux-amd64
After=network.target
[Service]
Type=simple
EnvironmentFile=/etc/default/vctp
User=vctp
ExecStart=/usr/bin/vctp-linux-amd64 $CPE_OPTS
ExecStartPost=/usr/bin/sleep 3
Restart=always
RestartSec=10s
#LimitNOFILE=65536
SyslogIdentifier=vctp
#CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_FOWNER CAP_DAC_OVERRIDE CAP_SETUID CAP_SETGID CAP_SYS_RESOURCE CAP_AUDIT_WRITE
#AmbientCapabilities=CAP_NET_BIND_SERVICE
[Install]
WantedBy=multi-user.target

25
src/vctp.yml Normal file
View File

@@ -0,0 +1,25 @@
settings:
data_location: "/var/lib/cbs"
kickstart_location: "/var/lib/cbs/ks"
database_filename: "/var/lib/cbs/cbs.db"
bind_ip:
bind_port: 443
bind_disable_tls: false
tls_cert_filename: "/etc/dtms/cbs.crt"
tls_key_filename: "/etc/dtms/cbs.key"
tftp_root_directory: "/var/lib/tftpboot"
tftp_images_subdirectory: "images"
replacements:
omapi:
key_name: "OMAPI"
key_secret:
special_files:
ldap_groups:
ldap_bind_address: ""
ldap_base_dn: ""
ldap_trust_cert_file: ""
ldap_disable_validation: false
ldap_insecure: false
auth_token_lifespan_hours: 2
auth_api_key: ""

40
vctp.yml Normal file
View File

@@ -0,0 +1,40 @@
name: "vctp"
arch: "amd64"
platform: "linux"
version: "v26.1.1"
version_schema: semver
description: vCTP monitors VMware VM inventory and event data to build chargeback reports
maintainer: "@coadn"
vendor: "Dell Technologies Managed Services"
homepage: "https://gitlab.dell.com/"
license: "Apache-2.0"
contents:
- src: build/vctp-linux-amd64
dst: /usr/bin/vctp-linux-amd64
- src: src/vctp.yml
dst: /etc/dtms/vctp.yml
type: config|noreplace
file_info:
mode: 0644
owner: vctp
group: dtms
- src: src/vctp.service
dst: /usr/lib/systemd/system/vctp.service
- src: src/vctp.default
dst: /etc/default/vctp
type: config|noreplace
scripts:
preinstall: src/preinstall.sh
postinstall: src/postinstall.sh
preremove: src/preremove.sh
postremove: src/postremove.sh
depends:
- systemd
- tftp-server
- dhcp-server