From 6a65505304843ed7e7ded191bad2482bd45d3d7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesko=20Ansch=C3=BCtz?= Date: Sun, 22 Mar 2026 13:42:00 +0100 Subject: [PATCH] Lege Entwicklungsleitfaden und Go-Gerueste an --- DEVELOPMENT.md | 179 ++++++++++++++++++++++ Makefile | 22 +++ README.md | 1 + ansible/README.md | 6 + player/README.md | 4 + player/agent/README.md | 16 ++ player/agent/cmd/agent/main.go | 23 +++ player/agent/go.mod | 3 + player/agent/internal/app/app.go | 28 ++++ player/agent/internal/config/config.go | 26 ++++ server/README.md | 4 + server/backend/README.md | 16 ++ server/backend/cmd/api/main.go | 23 +++ server/backend/go.mod | 3 + server/backend/internal/app/app.go | 29 ++++ server/backend/internal/config/config.go | 22 +++ server/backend/internal/httpapi/router.go | 33 ++++ 17 files changed, 438 insertions(+) create mode 100644 DEVELOPMENT.md create mode 100644 Makefile create mode 100644 player/agent/README.md create mode 100644 player/agent/cmd/agent/main.go create mode 100644 player/agent/go.mod create mode 100644 player/agent/internal/app/app.go create mode 100644 player/agent/internal/config/config.go create mode 100644 server/backend/README.md create mode 100644 server/backend/cmd/api/main.go create mode 100644 server/backend/go.mod create mode 100644 server/backend/internal/app/app.go create mode 100644 server/backend/internal/config/config.go create mode 100644 server/backend/internal/httpapi/router.go diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 0000000..4ba50ae --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,179 @@ +# Info-Board Neu - Entwicklung + +## Ziel + +Dieses Dokument soll den Einstieg auf einem anderen Entwicklungsrechner moeglichst reibungsfrei machen. + +Es beschreibt: + +- minimale Voraussetzungen +- wichtige Verzeichnisse +- Build- und Run-Kommandos +- aktuelle Annahmen fuer den lokalen Entwicklungsbetrieb + +## Repository + +- Remote: `git.az-it.net:az/morz-infoboard.git` +- Standard-Branch: `main` + +Projektwurzel: + +- `/srv/docker/info-board-neu` + +## Wichtige Verzeichnisse + +- `docs/` fuer Architektur- und Fachkonzepte +- `server/backend/` fuer das zentrale Go-Backend +- `player/agent/` fuer den Go-basierten Player-Agent +- `player/ui/` spaeter fuer die lokale Player-Oberflaeche +- `ansible/` spaeter fuer Deployment und Provisionierung +- `compose/` spaeter fuer den zentralen Server-Stack + +## Aktueller Entwicklungsstand + +Bereits vorhanden: + +- fachliche Architektur- und Betriebskonzepte +- relationaler Schema-Entwurf in `docs/SCHEMA.md` +- erstes Go-Geruest fuer `server/backend` +- erstes Go-Geruest fuer `player/agent` + +Noch nicht vorhanden: + +- produktive API-Endpunkte +- Datenbankanbindung +- MQTT-Anbindung +- Player-Sync +- Player-UI +- Compose-Stack fuer lokale Serverdienste + +## Voraussetzungen auf dem Entwicklungsrechner + +Mindestens empfohlen: + +- `git` +- `go` in Version `1.24.x` +- `make` + +Spaeter zusaetzlich sinnvoll: + +- `docker` und `docker compose` +- `postgresql-client` +- `mosquitto-clients` + +## Schnellstart + +Repository klonen: + +```bash +git clone git.az-it.net:az/morz-infoboard.git +cd morz-infoboard +``` + +Dokumentationsbasis lesen: + +1. `README.md` +2. `PLAN.md` +3. `TECH-STACK.md` +4. `docs/SCHEMA.md` +5. `docs/OFFENE-ARCHITEKTURFRAGEN.md` + +## Build-Kommandos + +### Backend bauen + +```bash +cd server/backend +go build ./... +``` + +### Agent bauen + +```bash +cd player/agent +go build ./... +``` + +### Alternativ ueber Makefile + +```bash +make build +``` + +## Lokaler Start + +### Backend lokal starten + +```bash +cd server/backend +go run ./cmd/api +``` + +Standard: + +- HTTP-Adresse: `:8080` +- Health-Endpunkt: `GET /healthz` +- Basis-Endpunkt: `GET /api/v1` + +Konfigurierbar ueber: + +- `MORZ_INFOBOARD_HTTP_ADDR` + +Beispiel: + +```bash +MORZ_INFOBOARD_HTTP_ADDR=:18080 go run ./cmd/api +``` + +### Agent lokal starten + +```bash +cd player/agent +go run ./cmd/agent +``` + +Standardwerte: + +- `MORZ_INFOBOARD_SCREEN_ID=unset-screen` +- `MORZ_INFOBOARD_SERVER_URL=http://127.0.0.1:8080` +- `MORZ_INFOBOARD_MQTT_BROKER=tcp://127.0.0.1:1883` + +Beispiel: + +```bash +MORZ_INFOBOARD_SCREEN_ID=info01-dev \ +MORZ_INFOBOARD_SERVER_URL=http://127.0.0.1:8080 \ +MORZ_INFOBOARD_MQTT_BROKER=tcp://127.0.0.1:1883 \ +go run ./cmd/agent +``` + +## Aktuelle Architekturentscheidungen mit direkter Auswirkung auf Entwicklung + +- `message_wall` wird serverseitig in konkrete Screen-Szenen aufgeloest +- Kampagnengruppen werden serverseitig in konkrete Screen-Assignments expandiert +- `playlist_items` haben im finalen Implementierungsschema keinen direkten `screen_id`-Fremdschluessel +- Provisionierung wird worker-/jumphost-faehig geplant +- API-Fehler sollen einen einheitlichen Fehlerumschlag nutzen + +## Empfohlene naechste Implementierungsschritte + +1. Backend: einheitliches Fehlerformat und Routing-Grundstruktur anlegen +2. Backend: Konfigurations- und App-Lifecycle stabilisieren +3. Agent: dateibasierte Konfiguration zusaetzlich zu Env vorbereiten +4. Agent: strukturierte Logs und Health-Modell einziehen +5. Danach erst DB-, MQTT- und API-Funktionalitaet ausbauen + +## Arbeitsweise + +Empfohlen: + +- kleine, klar umrissene Commits +- zuerst Konzepte anpassen, dann Code +- Schema und API-Vertrag synchron halten +- neue Fachentscheidungen immer in `docs/` dokumentieren + +## Hinweise fuer einen zweiten Entwicklungsrechner + +- vor Arbeitsbeginn `git pull --rebase` oder gleichwertig den aktuellen Stand holen +- bei paralleler Arbeit zuerst in den Dokumenten pruefen, ob neue Architekturentscheidungen getroffen wurden +- falls lokale Tools abweichen, mindestens `go version` und `make --version` pruefen diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d096e91 --- /dev/null +++ b/Makefile @@ -0,0 +1,22 @@ +BACKEND_DIR=server/backend +AGENT_DIR=player/agent + +.PHONY: build build-backend build-agent run-backend run-agent fmt + +build: build-backend build-agent + +build-backend: + cd $(BACKEND_DIR) && go build ./... + +build-agent: + cd $(AGENT_DIR) && go build ./... + +run-backend: + cd $(BACKEND_DIR) && go run ./cmd/api + +run-agent: + cd $(AGENT_DIR) && go run ./cmd/agent + +fmt: + cd $(BACKEND_DIR) && go fmt ./... + cd $(AGENT_DIR) && go fmt ./... diff --git a/README.md b/README.md index 93517c0..f3404c1 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Die Trennung von `/srv/docker/infoboard-netboot` ist sinnvoll, damit: - Schema-Entwurf: `docs/SCHEMA.md` - API- und MQTT-Vertrag: `API-MQTT-VERTRAG.md` - Technologieentscheidungen: `TECH-STACK.md` +- Entwicklungsleitfaden: `DEVELOPMENT.md` - Template-/Kampagnenkonzept: `docs/TEMPLATE-KONZEPT.md` - Provisionierungskonzept: `docs/PROVISIONIERUNGSKONZEPT.md` - Player-Konzept: `docs/PLAYER-KONZEPT.md` diff --git a/ansible/README.md b/ansible/README.md index 76c9abd..aa5defe 100644 --- a/ansible/README.md +++ b/ansible/README.md @@ -6,3 +6,9 @@ Hier liegen spaeter Rollen, Playbooks und Inventories fuer: - Updates bestehender Screens - Server-Deployment - Display-spezifische Konfigurationen + +Naechster geplanter Ausbau: + +- Rolle `signage_provision` +- Rolle `signage_player` +- Beispiel-Inventories fuer Wand- und Einzelanzeigen diff --git a/player/README.md b/player/README.md index 78224d8..b926f17 100644 --- a/player/README.md +++ b/player/README.md @@ -9,3 +9,7 @@ Geplant: - `systemd/` fuer Units - `config/` fuer Beispielkonfigurationen - `scripts/` fuer lokale Hilfsskripte + +Aktuell vorhanden: + +- `agent/` mit erstem Go-Geruest diff --git a/player/agent/README.md b/player/agent/README.md new file mode 100644 index 0000000..0c0bfbf --- /dev/null +++ b/player/agent/README.md @@ -0,0 +1,16 @@ +# Agent + +Dieses Verzeichnis enthaelt das erste Geruest fuer den `player-agent`. + +Ziel fuer die erste Ausbaustufe: + +- lokaler Dienst in Go +- Konfiguration laden +- Startfaehigkeit und klares Logging +- vorbereitete Struktur fuer Sync, MQTT, Cache und Kommandos + +Geplante Unterstruktur: + +- `cmd/agent/` fuer den Startpunkt +- `internal/app/` fuer Initialisierung und Laufzeit +- `internal/config/` fuer Konfiguration diff --git a/player/agent/cmd/agent/main.go b/player/agent/cmd/agent/main.go new file mode 100644 index 0000000..9e11cd4 --- /dev/null +++ b/player/agent/cmd/agent/main.go @@ -0,0 +1,23 @@ +package main + +import ( + "log" + "os" + + "git.az-it.net/az/morz-infoboard/player/agent/internal/app" +) + +func main() { + logger := log.New(os.Stdout, "agent ", log.LstdFlags|log.LUTC) + + application, err := app.New() + if err != nil { + logger.Fatalf("init agent: %v", err) + } + + logger.Printf("starting agent for screen %s", application.Config.ScreenID) + + if err := application.Run(); err != nil { + logger.Fatalf("run agent: %v", err) + } +} diff --git a/player/agent/go.mod b/player/agent/go.mod new file mode 100644 index 0000000..b958f71 --- /dev/null +++ b/player/agent/go.mod @@ -0,0 +1,3 @@ +module git.az-it.net/az/morz-infoboard/player/agent + +go 1.24.0 diff --git a/player/agent/internal/app/app.go b/player/agent/internal/app/app.go new file mode 100644 index 0000000..377be9a --- /dev/null +++ b/player/agent/internal/app/app.go @@ -0,0 +1,28 @@ +package app + +import ( + "fmt" + "time" + + "git.az-it.net/az/morz-infoboard/player/agent/internal/config" +) + +type App struct { + Config config.Config +} + +func New() (*App, error) { + cfg := config.Load() + + if cfg.ScreenID == "" { + return nil, fmt.Errorf("screen id is required") + } + + return &App{Config: cfg}, nil +} + +func (a *App) Run() error { + for { + time.Sleep(30 * time.Second) + } +} diff --git a/player/agent/internal/config/config.go b/player/agent/internal/config/config.go new file mode 100644 index 0000000..b1e6c7c --- /dev/null +++ b/player/agent/internal/config/config.go @@ -0,0 +1,26 @@ +package config + +import "os" + +type Config struct { + ScreenID string + ServerBaseURL string + MQTTBroker string +} + +func Load() Config { + return Config{ + ScreenID: getenv("MORZ_INFOBOARD_SCREEN_ID", "unset-screen"), + ServerBaseURL: getenv("MORZ_INFOBOARD_SERVER_URL", "http://127.0.0.1:8080"), + MQTTBroker: getenv("MORZ_INFOBOARD_MQTT_BROKER", "tcp://127.0.0.1:1883"), + } +} + +func getenv(key, fallback string) string { + value := os.Getenv(key) + if value == "" { + return fallback + } + + return value +} diff --git a/server/README.md b/server/README.md index cd7f928..036d384 100644 --- a/server/README.md +++ b/server/README.md @@ -8,3 +8,7 @@ Geplant: - `admin-ui/` fuer die Admin-Oberflaeche - `tenant-ui/` fuer die Firmenoberflaeche - `worker/` fuer Provisionierungs- und Hintergrundjobs + +Aktuell vorhanden: + +- `backend/` mit erstem Go-Geruest diff --git a/server/backend/README.md b/server/backend/README.md new file mode 100644 index 0000000..6f20860 --- /dev/null +++ b/server/backend/README.md @@ -0,0 +1,16 @@ +# Backend + +Dieses Verzeichnis enthaelt das erste Geruest fuer das zentrale Backend. + +Ziel fuer die erste Ausbaustufe: + +- HTTP-API in Go +- Health-Endpunkt +- saubere Projektstruktur fuer spaetere API-, Worker- und Datenbankmodule + +Geplante Unterstruktur: + +- `cmd/api/` fuer den API-Startpunkt +- `internal/app/` fuer App-Initialisierung +- `internal/httpapi/` fuer HTTP-Routing und Handler +- `internal/config/` fuer Konfiguration diff --git a/server/backend/cmd/api/main.go b/server/backend/cmd/api/main.go new file mode 100644 index 0000000..2fa0c04 --- /dev/null +++ b/server/backend/cmd/api/main.go @@ -0,0 +1,23 @@ +package main + +import ( + "log" + "os" + + "git.az-it.net/az/morz-infoboard/server/backend/internal/app" +) + +func main() { + logger := log.New(os.Stdout, "backend ", log.LstdFlags|log.LUTC) + + application, err := app.New() + if err != nil { + logger.Fatalf("init app: %v", err) + } + + logger.Printf("starting backend on %s", application.Config.HTTPAddress) + + if err := application.Run(); err != nil { + logger.Fatalf("run backend: %v", err) + } +} diff --git a/server/backend/go.mod b/server/backend/go.mod new file mode 100644 index 0000000..014633d --- /dev/null +++ b/server/backend/go.mod @@ -0,0 +1,3 @@ +module git.az-it.net/az/morz-infoboard/server/backend + +go 1.24.0 diff --git a/server/backend/internal/app/app.go b/server/backend/internal/app/app.go new file mode 100644 index 0000000..e2628ee --- /dev/null +++ b/server/backend/internal/app/app.go @@ -0,0 +1,29 @@ +package app + +import ( + "net/http" + + "git.az-it.net/az/morz-infoboard/server/backend/internal/config" + "git.az-it.net/az/morz-infoboard/server/backend/internal/httpapi" +) + +type App struct { + Config config.Config + server *http.Server +} + +func New() (*App, error) { + cfg := config.Load() + + return &App{ + Config: cfg, + server: &http.Server{ + Addr: cfg.HTTPAddress, + Handler: httpapi.NewRouter(), + }, + }, nil +} + +func (a *App) Run() error { + return a.server.ListenAndServe() +} diff --git a/server/backend/internal/config/config.go b/server/backend/internal/config/config.go new file mode 100644 index 0000000..eb1bdb3 --- /dev/null +++ b/server/backend/internal/config/config.go @@ -0,0 +1,22 @@ +package config + +import "os" + +type Config struct { + HTTPAddress string +} + +func Load() Config { + return Config{ + HTTPAddress: getenv("MORZ_INFOBOARD_HTTP_ADDR", ":8080"), + } +} + +func getenv(key, fallback string) string { + value := os.Getenv(key) + if value == "" { + return fallback + } + + return value +} diff --git a/server/backend/internal/httpapi/router.go b/server/backend/internal/httpapi/router.go new file mode 100644 index 0000000..1b246fb --- /dev/null +++ b/server/backend/internal/httpapi/router.go @@ -0,0 +1,33 @@ +package httpapi + +import ( + "encoding/json" + "net/http" +) + +func NewRouter() http.Handler { + mux := http.NewServeMux() + + mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, r *http.Request) { + writeJSON(w, http.StatusOK, map[string]string{ + "status": "ok", + "service": "morz-infoboard-backend", + }) + }) + + mux.HandleFunc("GET /api/v1", func(w http.ResponseWriter, r *http.Request) { + writeJSON(w, http.StatusOK, map[string]string{ + "name": "morz-infoboard-backend", + "version": "dev", + }) + }) + + return mux +} + +func writeJSON(w http.ResponseWriter, status int, payload any) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(status) + + _ = json.NewEncoder(w).Encode(payload) +}