feat: CLI-Skeleton mit version/serve/check und Makefile
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBHF4R9EAejDJUMdwr6C68
This commit is contained in:
parent
bb84607bf8
commit
fbfff90b38
8 changed files with 196 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
/dist/
|
||||||
|
/vpnportal
|
||||||
|
*.log
|
||||||
|
.DS_Store
|
||||||
44
Makefile
Normal file
44
Makefile
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
BINARY := vpnportal
|
||||||
|
PKG := git.ravensburg.dev/cabele/opnsense-portal/cmd/vpnportal
|
||||||
|
VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo dev)
|
||||||
|
COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo none)
|
||||||
|
DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||||
|
LDFLAGS := -s -w -X main.version=$(VERSION) -X main.commit=$(COMMIT) -X main.date=$(DATE)
|
||||||
|
|
||||||
|
# sha256sum gibt es auf macOS nicht; shasum ist dort das Äquivalent.
|
||||||
|
SHA256 := $(shell command -v sha256sum >/dev/null 2>&1 && echo sha256sum || echo "shasum -a 256")
|
||||||
|
|
||||||
|
export CGO_ENABLED := 0
|
||||||
|
|
||||||
|
.PHONY: all build test race vet lint release clean
|
||||||
|
|
||||||
|
all: vet test build
|
||||||
|
|
||||||
|
build:
|
||||||
|
go build -trimpath -ldflags '$(LDFLAGS)' -o dist/$(BINARY) $(PKG)
|
||||||
|
|
||||||
|
test:
|
||||||
|
go test ./...
|
||||||
|
|
||||||
|
race:
|
||||||
|
go test -race ./...
|
||||||
|
|
||||||
|
vet:
|
||||||
|
go vet ./...
|
||||||
|
|
||||||
|
lint: vet
|
||||||
|
@test -z "$$(gofmt -l . | tee /dev/stderr)" || (echo "gofmt-Fehler: bitte 'gofmt -w .' ausführen" && exit 1)
|
||||||
|
|
||||||
|
release: clean
|
||||||
|
@set -e; for arch in amd64 arm64; do \
|
||||||
|
echo "==> linux/$$arch"; \
|
||||||
|
mkdir -p dist/linux-$$arch; \
|
||||||
|
GOOS=linux GOARCH=$$arch go build -trimpath -ldflags '$(LDFLAGS)' \
|
||||||
|
-o dist/linux-$$arch/$(BINARY) $(PKG); \
|
||||||
|
cp deploy/config.example.yaml deploy/vpnportal.service README.md dist/linux-$$arch/; \
|
||||||
|
tar -czf dist/$(BINARY)-$(VERSION)-linux-$$arch.tar.gz -C dist/linux-$$arch .; \
|
||||||
|
done
|
||||||
|
cd dist && $(SHA256) *.tar.gz > SHA256SUMS
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf dist
|
||||||
14
cmd/vpnportal/buildinfo.go
Normal file
14
cmd/vpnportal/buildinfo.go
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Per -ldflags gesetzt; siehe Makefile.
|
||||||
|
var (
|
||||||
|
version = "dev"
|
||||||
|
commit = "none"
|
||||||
|
date = "unknown"
|
||||||
|
)
|
||||||
|
|
||||||
|
func versionString() string {
|
||||||
|
return fmt.Sprintf("vpnportal %s (commit %s, built %s)", version, commit, date)
|
||||||
|
}
|
||||||
17
cmd/vpnportal/check.go
Normal file
17
cmd/vpnportal/check.go
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
func runCheck(args []string, stdout, stderr io.Writer) int {
|
||||||
|
fs, path := configFlags("check", stderr)
|
||||||
|
testAuth := fs.String("test-auth", "", "Suche und Gruppenprüfung für einen Benutzer durchspielen")
|
||||||
|
if err := fs.Parse(args); err != nil {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
_ = testAuth
|
||||||
|
fmt.Fprintf(stderr, "Fehler: config %q kann noch nicht geladen werden (nicht implementiert)\n", *path)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
46
cmd/vpnportal/main.go
Normal file
46
cmd/vpnportal/main.go
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
const usage = `vpnportal — VPN-Konfig-Portal für OPNsense
|
||||||
|
|
||||||
|
Aufruf:
|
||||||
|
vpnportal serve [--config PFAD] Portal starten (Default)
|
||||||
|
vpnportal check [--config PFAD] [--test-auth BENUTZER]
|
||||||
|
vpnportal version
|
||||||
|
|
||||||
|
Optionen:
|
||||||
|
--config PFAD Pfad zur Konfigurationsdatei (Default: /etc/vpnportal/config.yaml)
|
||||||
|
`
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
os.Exit(run(os.Args[1:], os.Stdout, os.Stderr))
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(args []string, stdout, stderr io.Writer) int {
|
||||||
|
cmd := "serve"
|
||||||
|
rest := args
|
||||||
|
if len(args) > 0 && len(args[0]) > 0 && args[0][0] != '-' {
|
||||||
|
cmd, rest = args[0], args[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
switch cmd {
|
||||||
|
case "version":
|
||||||
|
fmt.Fprintln(stdout, versionString())
|
||||||
|
return 0
|
||||||
|
case "serve":
|
||||||
|
return runServe(rest, stdout, stderr)
|
||||||
|
case "check":
|
||||||
|
return runCheck(rest, stdout, stderr)
|
||||||
|
case "help", "-h", "--help":
|
||||||
|
fmt.Fprint(stdout, usage)
|
||||||
|
return 0
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(stderr, "Unbekanntes Kommando: %s\n\n%s", cmd, usage)
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
}
|
||||||
43
cmd/vpnportal/main_test.go
Normal file
43
cmd/vpnportal/main_test.go
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRunVersion(t *testing.T) {
|
||||||
|
var out, errOut bytes.Buffer
|
||||||
|
code := run([]string{"version"}, &out, &errOut)
|
||||||
|
if code != 0 {
|
||||||
|
t.Fatalf("exit code = %d, want 0 (stderr: %s)", code, errOut.String())
|
||||||
|
}
|
||||||
|
got := out.String()
|
||||||
|
for _, want := range []string{"vpnportal", "commit", "built"} {
|
||||||
|
if !strings.Contains(got, want) {
|
||||||
|
t.Errorf("output %q missing %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunUnknownCommand(t *testing.T) {
|
||||||
|
var out, errOut bytes.Buffer
|
||||||
|
if code := run([]string{"frobnicate"}, &out, &errOut); code != 2 {
|
||||||
|
t.Fatalf("exit code = %d, want 2", code)
|
||||||
|
}
|
||||||
|
if !strings.Contains(errOut.String(), "frobnicate") {
|
||||||
|
t.Errorf("stderr should name the unknown command, got %q", errOut.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunNoArgsDefaultsToServe(t *testing.T) {
|
||||||
|
var out, errOut bytes.Buffer
|
||||||
|
// serve ohne --config muss mit Fehler abbrechen, nicht mit "unknown command"
|
||||||
|
code := run(nil, &out, &errOut)
|
||||||
|
if code == 2 {
|
||||||
|
t.Fatalf("no args must default to serve, not unknown-command")
|
||||||
|
}
|
||||||
|
if !strings.Contains(errOut.String(), "config") {
|
||||||
|
t.Errorf("stderr should complain about missing config, got %q", errOut.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
25
cmd/vpnportal/serve.go
Normal file
25
cmd/vpnportal/serve.go
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultConfigPath = "/etc/vpnportal/config.yaml"
|
||||||
|
|
||||||
|
func configFlags(name string, stderr io.Writer) (*flag.FlagSet, *string) {
|
||||||
|
fs := flag.NewFlagSet(name, flag.ContinueOnError)
|
||||||
|
fs.SetOutput(stderr)
|
||||||
|
path := fs.String("config", defaultConfigPath, "Pfad zur Konfigurationsdatei")
|
||||||
|
return fs, path
|
||||||
|
}
|
||||||
|
|
||||||
|
func runServe(args []string, stdout, stderr io.Writer) int {
|
||||||
|
fs, path := configFlags("serve", stderr)
|
||||||
|
if err := fs.Parse(args); err != nil {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
fmt.Fprintf(stderr, "Fehler: config %q kann noch nicht geladen werden (nicht implementiert)\n", *path)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
3
go.mod
Normal file
3
go.mod
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
module git.ravensburg.dev/cabele/opnsense-portal
|
||||||
|
|
||||||
|
go 1.26.5
|
||||||
Loading…
Add table
Add a link
Reference in a new issue