Initial commit: full Phases 1-6 implementation
CI / Lint + build + test (push) Has been cancelled

Post-repair hardware validation pipeline for Proxmox cluster hosts.
Go orchestrator + in-image agent + mkosi live image + bundled dnsmasq
PXE + SQLite + HTMX/SSE UI + notify registry + janitor + full docs.
This commit is contained in:
2026-04-17 21:32:10 -04:00
commit 9bb4b09a04
98 changed files with 11960 additions and 0 deletions
+64
View File
@@ -0,0 +1,64 @@
package auth
import (
"net/http"
)
// RequireSession redirects unauthenticated requests to /login.
func (m *Manager) RequireSession(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if err := m.Validate(r); err != nil {
if acceptsHTML(r) {
http.Redirect(w, r, "/login?next="+r.URL.RequestURI(), http.StatusSeeOther)
return
}
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
func acceptsHTML(r *http.Request) bool {
accept := r.Header.Get("Accept")
if accept == "" {
return true
}
for _, part := range splitComma(accept) {
if part == "text/html" || part == "*/*" {
return true
}
}
return false
}
func splitComma(s string) []string {
var out []string
start := 0
for i := 0; i < len(s); i++ {
if s[i] == ',' {
out = append(out, trimSpace(s[start:i]))
start = i + 1
} else if s[i] == ';' {
out = append(out, trimSpace(s[start:i]))
for i < len(s) && s[i] != ',' {
i++
}
start = i + 1
}
}
if start < len(s) {
out = append(out, trimSpace(s[start:]))
}
return out
}
func trimSpace(s string) string {
for len(s) > 0 && (s[0] == ' ' || s[0] == '\t') {
s = s[1:]
}
for len(s) > 0 && (s[len(s)-1] == ' ' || s[len(s)-1] == '\t') {
s = s[:len(s)-1]
}
return s
}
+100
View File
@@ -0,0 +1,100 @@
package auth
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"errors"
"fmt"
"net/http"
"strconv"
"strings"
"time"
"golang.org/x/crypto/bcrypt"
)
const cookieName = "vetting_session"
type Manager struct {
PasswordHash string
Secret []byte
TTL time.Duration
}
func (m *Manager) VerifyPassword(password string) bool {
if m.PasswordHash == "" {
return false
}
return bcrypt.CompareHashAndPassword([]byte(m.PasswordHash), []byte(password)) == nil
}
// Issue writes a signed session cookie valid for m.TTL.
func (m *Manager) Issue(w http.ResponseWriter, r *http.Request) {
expiry := time.Now().Add(m.TTL).Unix()
payload := strconv.FormatInt(expiry, 10)
sig := m.sign(payload)
value := payload + "." + sig
http.SetCookie(w, &http.Cookie{
Name: cookieName,
Value: value,
Path: "/",
HttpOnly: true,
Secure: r.TLS != nil,
SameSite: http.SameSiteLaxMode,
Expires: time.Unix(expiry, 0),
})
}
func (m *Manager) Clear(w http.ResponseWriter) {
http.SetCookie(w, &http.Cookie{
Name: cookieName,
Value: "",
Path: "/",
HttpOnly: true,
MaxAge: -1,
})
}
var errInvalidSession = errors.New("invalid session")
// Validate returns nil if the request's cookie is present, signed, and not expired.
func (m *Manager) Validate(r *http.Request) error {
c, err := r.Cookie(cookieName)
if err != nil {
return errInvalidSession
}
parts := strings.SplitN(c.Value, ".", 2)
if len(parts) != 2 {
return errInvalidSession
}
payload, sig := parts[0], parts[1]
expected := m.sign(payload)
if !hmac.Equal([]byte(sig), []byte(expected)) {
return errInvalidSession
}
expiry, err := strconv.ParseInt(payload, 10, 64)
if err != nil {
return errInvalidSession
}
if time.Now().Unix() >= expiry {
return errInvalidSession
}
return nil
}
func (m *Manager) sign(payload string) string {
mac := hmac.New(sha256.New, m.Secret)
_, _ = mac.Write([]byte(payload))
return base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
}
// BcryptHash is a helper used by the gen-admin-password tool.
func BcryptHash(password string) (string, error) {
b, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", fmt.Errorf("bcrypt: %w", err)
}
return string(b), nil
}