feat: add web layer (internal/web) and wire it into main.go
Routing, html/template layout/content pattern, and the Pre-Publish check flow: POST /pruefen runs extraction (Stufe 1) then rules.Evaluate (Stufe 2) and renders the result as an htmx fragment. Nothing is persisted yet — that's the next step (wiring internal/store in). The needsClarification case is rendered explicitly as a request for more information rather than "no findings", matching the core principle. Every result carries the legal-advice disclaimer required by CLAUDE.md's guardrails. Server depends on a narrow Extractor interface rather than *extract. Client directly, so tests inject a fake instead of calling the real API — internal/web's test suite never touches the network. htmx is vendored locally (internal/web/static/htmx.min.js) instead of loaded from a CDN, keeping the UI usable without runtime internet access. cmd/deklarix/main.go now wires all of this together: reads ANTHROPIC_API_KEY (required) and RULES_DIR (default "rules"), builds the extract client and loads the rule set, and serves web.Server instead of the old inline health-only mux. This exposed the same crash-loop risk fixed earlier for DATABASE_URL: postinst's start guard only checked DATABASE_URL, so a fresh install would now crash-loop on a missing ANTHROPIC_API_KEY instead. The guard checks both. scripts/build.sh also now ships rules/*.yaml into the .deb under /usr/share/deklarix/rules (not a conffile — rules are updated via the release pipeline, never hand-edited on a server), and deklarix.env.example points RULES_DIR there by default. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -330,7 +330,7 @@ git push origin main
|
||||
sudo systemctl start deklarix
|
||||
sudo systemctl status deklarix
|
||||
|
||||
# Config: /etc/deklarix/deklarix.env (DATABASE_URL, PORT, ANTHROPIC_API_KEY)
|
||||
# Config: /etc/deklarix/deklarix.env (DATABASE_URL, PORT, ANTHROPIC_API_KEY, RULES_DIR)
|
||||
|
||||
# Logs prüfen
|
||||
journalctl -u deklarix -f
|
||||
|
||||
@@ -2,12 +2,14 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/netcell-it/deklarix/internal/extract"
|
||||
"github.com/netcell-it/deklarix/internal/rules"
|
||||
"github.com/netcell-it/deklarix/internal/store"
|
||||
"github.com/netcell-it/deklarix/internal/web"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -26,20 +28,38 @@ func main() {
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
apiKey := os.Getenv("ANTHROPIC_API_KEY")
|
||||
if apiKey == "" {
|
||||
log.Fatal("ANTHROPIC_API_KEY is required")
|
||||
}
|
||||
extractor, err := extract.NewClient(apiKey)
|
||||
if err != nil {
|
||||
log.Fatalf("extract client: %v", err)
|
||||
}
|
||||
|
||||
rulesDir := os.Getenv("RULES_DIR")
|
||||
if rulesDir == "" {
|
||||
rulesDir = "rules"
|
||||
}
|
||||
ruleSet, err := rules.Load(os.DirFS(rulesDir))
|
||||
if err != nil {
|
||||
log.Fatalf("load rules from %s: %v", rulesDir, err)
|
||||
}
|
||||
log.Printf("%d Regeln aus %s geladen", len(ruleSet), rulesDir)
|
||||
|
||||
server, err := web.NewServer(extractor, ruleSet)
|
||||
if err != nil {
|
||||
log.Fatalf("web server: %v", err)
|
||||
}
|
||||
|
||||
port := os.Getenv("PORT")
|
||||
if port == "" {
|
||||
port = "8080"
|
||||
}
|
||||
addr := ":" + port
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `{"ok":true}`)
|
||||
})
|
||||
|
||||
log.Printf("Deklarix server starting on %s", addr)
|
||||
if err := http.ListenAndServe(addr, mux); err != nil {
|
||||
if err := http.ListenAndServe(addr, server); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
83
internal/web/handlers.go
Normal file
83
internal/web/handlers.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/netcell-it/deklarix/internal/extract"
|
||||
"github.com/netcell-it/deklarix/internal/rules"
|
||||
)
|
||||
|
||||
func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprint(w, `{"ok":true}`)
|
||||
}
|
||||
|
||||
type indexData struct {
|
||||
Title string
|
||||
}
|
||||
|
||||
func (s *Server) handleIndex(w http.ResponseWriter, r *http.Request) {
|
||||
if err := s.templates.ExecuteTemplate(w, "layout", indexData{Title: "Pre-Publish-Prüfung"}); err != nil {
|
||||
http.Error(w, "Seite konnte nicht gerendert werden", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
type findingView struct {
|
||||
RuleID string
|
||||
Version int
|
||||
Severity string
|
||||
Title string
|
||||
Fix string
|
||||
Sources []string
|
||||
}
|
||||
|
||||
type resultData struct {
|
||||
NeedsClarification bool
|
||||
Findings []findingView
|
||||
}
|
||||
|
||||
// handleCheck führt die Pre-Publish-Prüfung aus: Extraktion (Stufe 1)
|
||||
// gefolgt von der Regelauswertung (Stufe 2). Speichert noch nichts —
|
||||
// das ist der nächste Schritt (Verdrahtung über internal/store).
|
||||
func (s *Server) handleCheck(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, "ungültiges Formular", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
platform := r.FormValue("platform")
|
||||
caption := r.FormValue("caption")
|
||||
if platform == "" || caption == "" {
|
||||
http.Error(w, "Plattform und Caption sind Pflichtfelder", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
facts, err := s.extractor.Extract(r.Context(), extract.Input{
|
||||
Platform: platform,
|
||||
Jurisdiction: "DE",
|
||||
Caption: caption,
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, "Extraktion fehlgeschlagen: "+err.Error(), http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
findings, needsClarification := rules.Evaluate(s.ruleSet, facts)
|
||||
|
||||
data := resultData{NeedsClarification: needsClarification}
|
||||
for _, f := range findings {
|
||||
data.Findings = append(data.Findings, findingView{
|
||||
RuleID: f.RuleID,
|
||||
Version: f.RuleVersion,
|
||||
Severity: string(f.Severity),
|
||||
Title: f.Title,
|
||||
Fix: f.Fix,
|
||||
Sources: f.Sources,
|
||||
})
|
||||
}
|
||||
|
||||
if err := s.templates.ExecuteTemplate(w, "result", data); err != nil {
|
||||
http.Error(w, "Ergebnis konnte nicht gerendert werden", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
65
internal/web/server.go
Normal file
65
internal/web/server.go
Normal file
@@ -0,0 +1,65 @@
|
||||
// Package web ist die HTTP-Schicht: Routing, Templates, Handler.
|
||||
// Bewusst html/template + htmx, kein Frontend-Build (siehe CLAUDE.md,
|
||||
// Stack). Persistenz (Submission/Finding/Evidence speichern) ist noch
|
||||
// nicht angebunden — das ist der nächste Schritt (Verdrahtung über
|
||||
// internal/store). Dieser Server führt Pre-Publish-Prüfungen aus
|
||||
// (extrahieren + bewerten) und zeigt das Ergebnis an, ohne es
|
||||
// aufzubewahren.
|
||||
package web
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
|
||||
"github.com/netcell-it/deklarix/internal/extract"
|
||||
"github.com/netcell-it/deklarix/internal/rules"
|
||||
)
|
||||
|
||||
//go:embed templates/*.html
|
||||
var templatesFS embed.FS
|
||||
|
||||
//go:embed static/*
|
||||
var staticFS embed.FS
|
||||
|
||||
// Extractor ist die Schnittstelle, die der Server für Stufe 1 braucht.
|
||||
// *extract.Client erfüllt sie; Tests injizieren einen Fake statt echte
|
||||
// Claude-API-Aufrufe zu machen.
|
||||
type Extractor interface {
|
||||
Extract(ctx context.Context, in extract.Input) (rules.Facts, error)
|
||||
}
|
||||
|
||||
// Server bündelt Routing und Abhängigkeiten der Web-Schicht.
|
||||
type Server struct {
|
||||
mux *http.ServeMux
|
||||
extractor Extractor
|
||||
ruleSet []rules.Rule
|
||||
templates *template.Template
|
||||
}
|
||||
|
||||
// NewServer erstellt den Server. ruleSet kommt von rules.Load und wird
|
||||
// einmal beim Start geladen, nicht pro Request.
|
||||
func NewServer(extractor Extractor, ruleSet []rules.Rule) (*Server, error) {
|
||||
tmpl, err := template.ParseFS(templatesFS, "templates/*.html")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("web: templates parsen: %w", err)
|
||||
}
|
||||
|
||||
s := &Server{extractor: extractor, ruleSet: ruleSet, templates: tmpl}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("GET /health", s.handleHealth)
|
||||
mux.HandleFunc("GET /{$}", s.handleIndex)
|
||||
mux.HandleFunc("POST /pruefen", s.handleCheck)
|
||||
mux.Handle("GET /static/", http.FileServerFS(staticFS))
|
||||
s.mux = mux
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// ServeHTTP macht Server zu einem http.Handler.
|
||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
s.mux.ServeHTTP(w, r)
|
||||
}
|
||||
183
internal/web/server_test.go
Normal file
183
internal/web/server_test.go
Normal file
@@ -0,0 +1,183 @@
|
||||
package web_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/netcell-it/deklarix/internal/extract"
|
||||
"github.com/netcell-it/deklarix/internal/rules"
|
||||
"github.com/netcell-it/deklarix/internal/web"
|
||||
)
|
||||
|
||||
type fakeExtractor struct {
|
||||
facts rules.Facts
|
||||
err error
|
||||
}
|
||||
|
||||
func (f fakeExtractor) Extract(ctx context.Context, in extract.Input) (rules.Facts, error) {
|
||||
return f.facts, f.err
|
||||
}
|
||||
|
||||
func loadRealRules(t *testing.T) []rules.Rule {
|
||||
t.Helper()
|
||||
rs, err := rules.Load(os.DirFS("../../rules"))
|
||||
if err != nil {
|
||||
t.Fatalf("rules.Load: %v", err)
|
||||
}
|
||||
return rs
|
||||
}
|
||||
|
||||
func newTestServer(t *testing.T, ex web.Extractor) *web.Server {
|
||||
t.Helper()
|
||||
s, err := web.NewServer(ex, loadRealRules(t))
|
||||
if err != nil {
|
||||
t.Fatalf("NewServer: %v", err)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func TestHandleHealth(t *testing.T) {
|
||||
s := newTestServer(t, fakeExtractor{})
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/health", nil)
|
||||
w := httptest.NewRecorder()
|
||||
s.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want 200", w.Code)
|
||||
}
|
||||
if w.Body.String() != `{"ok":true}` {
|
||||
t.Fatalf("body = %q, want {\"ok\":true}", w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleIndexRendersForm(t *testing.T) {
|
||||
s := newTestServer(t, fakeExtractor{})
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
w := httptest.NewRecorder()
|
||||
s.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want 200", w.Code)
|
||||
}
|
||||
body := w.Body.String()
|
||||
for _, want := range []string{`name="platform"`, `name="caption"`, `hx-post="/pruefen"`} {
|
||||
if !strings.Contains(body, want) {
|
||||
t.Errorf("index body missing %q\nbody: %s", want, body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleStaticServesHTMX(t *testing.T) {
|
||||
s := newTestServer(t, fakeExtractor{})
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/static/htmx.min.js", nil)
|
||||
w := httptest.NewRecorder()
|
||||
s.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want 200", w.Code)
|
||||
}
|
||||
if w.Body.Len() < 1000 {
|
||||
t.Fatalf("htmx.min.js suspiciously small: %d bytes", w.Body.Len())
|
||||
}
|
||||
}
|
||||
|
||||
func postCheck(t *testing.T, s *web.Server, form url.Values) *httptest.ResponseRecorder {
|
||||
t.Helper()
|
||||
req := httptest.NewRequest(http.MethodPost, "/pruefen", strings.NewReader(form.Encode()))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
w := httptest.NewRecorder()
|
||||
s.ServeHTTP(w, req)
|
||||
return w
|
||||
}
|
||||
|
||||
func TestHandleCheckRejectsMissingFields(t *testing.T) {
|
||||
s := newTestServer(t, fakeExtractor{})
|
||||
|
||||
w := postCheck(t, s, url.Values{"platform": {"instagram"}})
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("status = %d, want 400 for missing caption", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleCheckPropagatesExtractionError(t *testing.T) {
|
||||
s := newTestServer(t, fakeExtractor{err: errTest})
|
||||
|
||||
w := postCheck(t, s, url.Values{"platform": {"instagram"}, "caption": {"x"}})
|
||||
if w.Code != http.StatusBadGateway {
|
||||
t.Fatalf("status = %d, want 502 when extraction fails", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleCheckRendersFinding(t *testing.T) {
|
||||
s := newTestServer(t, fakeExtractor{facts: rules.Facts{
|
||||
Platform: "instagram",
|
||||
Jurisdiction: "DE",
|
||||
Consideration: rules.ConsiderationPaid,
|
||||
DisclosurePresent: true,
|
||||
DisclosureWording: "Werbung",
|
||||
DisclosureBeforeCut: false,
|
||||
}})
|
||||
|
||||
w := postCheck(t, s, url.Values{"platform": {"instagram"}, "caption": {"Werbung, schaut mal..."}})
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want 200", w.Code)
|
||||
}
|
||||
body := w.Body.String()
|
||||
if !strings.Contains(body, "WK-004") {
|
||||
t.Errorf("expected WK-004 finding in result, got: %s", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleCheckRendersNeedsClarification(t *testing.T) {
|
||||
s := newTestServer(t, fakeExtractor{facts: rules.Facts{
|
||||
Platform: "instagram",
|
||||
Jurisdiction: "DE",
|
||||
Consideration: rules.ConsiderationUnclear,
|
||||
}})
|
||||
|
||||
w := postCheck(t, s, url.Values{"platform": {"instagram"}, "caption": {"..."}})
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want 200", w.Code)
|
||||
}
|
||||
body := w.Body.String()
|
||||
if strings.Contains(body, "WK-") {
|
||||
t.Errorf("expected no rule findings for an unclear extraction, got: %s", body)
|
||||
}
|
||||
if !strings.Contains(body, "nicht sicher bestimmt") {
|
||||
t.Errorf("expected a clarification message, got: %s", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleCheckCleanCaseHasNoFindings(t *testing.T) {
|
||||
s := newTestServer(t, fakeExtractor{facts: rules.Facts{
|
||||
Platform: "instagram",
|
||||
Jurisdiction: "DE",
|
||||
Consideration: rules.ConsiderationNone,
|
||||
}})
|
||||
|
||||
w := postCheck(t, s, url.Values{"platform": {"instagram"}, "caption": {"ein ganz normaler Post"}})
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want 200", w.Code)
|
||||
}
|
||||
body := w.Body.String()
|
||||
if strings.Contains(body, "WK-") {
|
||||
t.Errorf("expected no findings for an organic post, got: %s", body)
|
||||
}
|
||||
if !strings.Contains(body, "Keine Kennzeichnungsrisiken") {
|
||||
t.Errorf("expected the no-findings message, got: %s", body)
|
||||
}
|
||||
}
|
||||
|
||||
var errTest = extractError("simulierter Extraktionsfehler")
|
||||
|
||||
type extractError string
|
||||
|
||||
func (e extractError) Error() string { return string(e) }
|
||||
1
internal/web/static/htmx.min.js
vendored
Normal file
1
internal/web/static/htmx.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
19
internal/web/templates/index.html
Normal file
19
internal/web/templates/index.html
Normal file
@@ -0,0 +1,19 @@
|
||||
{{define "content"}}
|
||||
<h1>Pre-Publish-Prüfung</h1>
|
||||
<p>Caption und Plattform eingeben, um auf Kennzeichnungsrisiken zu prüfen.</p>
|
||||
|
||||
<form hx-post="/pruefen" hx-target="#ergebnis" hx-swap="innerHTML">
|
||||
<label for="platform">Plattform</label>
|
||||
<select id="platform" name="platform" required>
|
||||
<option value="instagram">Instagram</option>
|
||||
<option value="tiktok">TikTok</option>
|
||||
</select>
|
||||
|
||||
<label for="caption">Caption</label>
|
||||
<textarea id="caption" name="caption" rows="6" required></textarea>
|
||||
|
||||
<button type="submit">Prüfen</button>
|
||||
</form>
|
||||
|
||||
<div id="ergebnis"></div>
|
||||
{{end}}
|
||||
13
internal/web/templates/layout.html
Normal file
13
internal/web/templates/layout.html
Normal file
@@ -0,0 +1,13 @@
|
||||
{{define "layout"}}<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{{.Title}} — Deklarix</title>
|
||||
<script src="/static/htmx.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
{{template "content" .}}
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
27
internal/web/templates/result.html
Normal file
27
internal/web/templates/result.html
Normal file
@@ -0,0 +1,27 @@
|
||||
{{define "result"}}
|
||||
{{if .NeedsClarification}}
|
||||
<p class="rueckfrage">
|
||||
Die Gegenleistung konnte nicht sicher bestimmt werden. Bitte Angaben
|
||||
präzisieren (z. B. ob ein Produkt oder eine Zahlung im Zusammenhang mit
|
||||
dem Beitrag stand) — es wurde bewusst keine Bewertung abgegeben.
|
||||
</p>
|
||||
{{else if .Findings}}
|
||||
<ul class="findings">
|
||||
{{range .Findings}}
|
||||
<li class="finding finding-{{.Severity}}">
|
||||
<strong>{{.RuleID}} v{{.Version}}</strong> ({{.Severity}}) — {{.Title}}
|
||||
<p>Korrektur: {{.Fix}}</p>
|
||||
{{if .Sources}}
|
||||
<p>Fundstellen: {{range $i, $s := .Sources}}{{if $i}}; {{end}}{{$s}}{{end}}</p>
|
||||
{{end}}
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
{{else}}
|
||||
<p class="keine-findings">Keine Kennzeichnungsrisiken nach aktuellem Regelwerk gefunden.</p>
|
||||
{{end}}
|
||||
<p class="disclaimer">
|
||||
Diese Prüfung ist keine Rechtsberatung und ersetzt keine anwaltliche
|
||||
Prüfung im Einzelfall.
|
||||
</p>
|
||||
{{end}}
|
||||
@@ -45,17 +45,19 @@ case "$1" in
|
||||
systemctl daemon-reload 2>/dev/null || true
|
||||
systemctl enable deklarix.service >/dev/null 2>&1 || true
|
||||
|
||||
# Nicht blind starten — ohne gesetzte DATABASE_URL würde der Dienst
|
||||
# nur in eine Restart-Schleife laufen (main.go bricht sonst bewusst
|
||||
# mit log.Fatal ab, siehe CLAUDE.md "keine stillen Fallbacks"). Die
|
||||
# Vorlage liefert DATABASE_URL auskommentiert aus — ein Treffer hier
|
||||
# bedeutet also wirklich "vom Admin gesetzt", nicht den Platzhalter.
|
||||
if grep -qE '^DATABASE_URL=.+' "$CONFIG_DIR/deklarix.env" 2>/dev/null; then
|
||||
# Nicht blind starten — ohne gesetzte DATABASE_URL/ANTHROPIC_API_KEY
|
||||
# würde der Dienst nur in eine Restart-Schleife laufen (main.go
|
||||
# bricht sonst bewusst mit log.Fatal ab, siehe CLAUDE.md "keine
|
||||
# stillen Fallbacks"). Die Vorlage liefert beide auskommentiert aus —
|
||||
# ein Treffer hier bedeutet also wirklich "vom Admin gesetzt", nicht
|
||||
# den Platzhalter.
|
||||
if grep -qE '^DATABASE_URL=.+' "$CONFIG_DIR/deklarix.env" 2>/dev/null \
|
||||
&& grep -qE '^ANTHROPIC_API_KEY=.+' "$CONFIG_DIR/deklarix.env" 2>/dev/null; then
|
||||
systemctl restart deklarix.service
|
||||
else
|
||||
echo ""
|
||||
echo " → Deklarix installiert, aber noch nicht gestartet."
|
||||
echo " DATABASE_URL in $CONFIG_DIR/deklarix.env setzen, dann:"
|
||||
echo " DATABASE_URL und ANTHROPIC_API_KEY in $CONFIG_DIR/deklarix.env setzen, dann:"
|
||||
echo " systemctl start deklarix"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
@@ -5,11 +5,14 @@
|
||||
|
||||
PORT=8080
|
||||
|
||||
# Pflicht — der Dienst startet nicht ohne gültige DATABASE_URL. Auskommentiert
|
||||
# lassen, bis eine echte Postgres-Verbindung eingetragen ist: postinst prüft
|
||||
# genau diese Zeile, um den Dienst nicht blind in eine Restart-Schleife gegen
|
||||
# einen Platzhalter-Host laufen zu lassen.
|
||||
#DATABASE_URL=postgres://user:password@host:5432/deklarix?sslmode=require
|
||||
# Wo internal/rules die YAML-Regeln findet — vom .deb-Paket nach
|
||||
# /usr/share/deklarix/rules installiert (siehe scripts/build.sh).
|
||||
RULES_DIR=/usr/share/deklarix/rules
|
||||
|
||||
# Für internal/extract (Claude-API-Extraktion), sobald angebunden.
|
||||
# Pflicht — der Dienst startet nicht ohne gültige DATABASE_URL und
|
||||
# ANTHROPIC_API_KEY. Beide auskommentiert lassen, bis echte Werte
|
||||
# eingetragen sind: postinst prüft genau diese beiden Zeilen, um den
|
||||
# Dienst nicht blind in eine Restart-Schleife gegen Platzhalter laufen
|
||||
# zu lassen.
|
||||
#DATABASE_URL=postgres://user:password@host:5432/deklarix?sslmode=require
|
||||
#ANTHROPIC_API_KEY=
|
||||
|
||||
@@ -44,6 +44,7 @@ for ARCH in "${ARCHS[@]}"; do
|
||||
log "Baue Binary ($ARCH)..."
|
||||
mkdir -p "$DEB_DIR/DEBIAN" \
|
||||
"$DEB_DIR/usr/bin" \
|
||||
"$DEB_DIR/usr/share/deklarix/rules" \
|
||||
"$DEB_DIR/etc/systemd/system" \
|
||||
"$DEB_DIR/etc/deklarix"
|
||||
CGO_ENABLED=0 GOARCH=$ARCH GOOS=linux go build \
|
||||
@@ -62,6 +63,7 @@ for ARCH in "${ARCHS[@]}"; do
|
||||
|
||||
cp "$PACKAGING_DIR/etc/systemd/system/deklarix.service" "$DEB_DIR/etc/systemd/system/"
|
||||
cp "$PACKAGING_DIR/etc/deklarix/deklarix.env.example" "$DEB_DIR/etc/deklarix/"
|
||||
cp "$REPO_DIR"/rules/*.yaml "$DEB_DIR/usr/share/deklarix/rules/"
|
||||
|
||||
dpkg-deb --build --root-owner-group "$DEB_DIR" "$REPO_DIR/dist/$DEB_NAME.deb"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user