refactor: aufgeraeumt — Pakete hinten, Widgets vorne

Go: store, auth, weather und config sind eigene Pakete unter internal/. Vorher
lagen fuenf Dateien in einem Topf, und jede kannte jede.

Web: os.js hatte 1425 Zeilen und alles darin. Jetzt core.js (Zustand, Server,
Einstellungen), glyphs.js (die Pixelraster), js/widgets/ mit einem Widget je
Datei, os.js nur noch die Ansichten. Stylesheet nach css/.

Die HoneyBar bleibt eine einzelne Datei ohne Importe — sie wird von fremden
Seiten mit einer Zeile eingebunden, da darf nichts nachladen.

Nebenbei: Das gebaute Binary lag mit 9,8 MB im Repo und ist jetzt ignoriert.
Und der Test gegen eine zweite Versionsnummer geht nicht mehr auf os.js, sondern
durch alle .js — sonst haette ihn dieser Umbau still ausgehebelt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
marcandClaude Opus 5 committed 2026-08-29 20:51:21 +02:00
1 parent d08b0a229c
commit 52deffa30e
20 files changed
+780 -624

No files matched your search

+32 -74
View File
@@ -6,6 +6,12 @@
package main
import (
"cmp"
"honeyos/internal/auth"
"honeyos/internal/config"
"honeyos/internal/store"
"honeyos/internal/weather"
"bytes"
"crypto/subtle"
"embed"
@@ -22,76 +28,35 @@ import (
//go:embed all:web
var webFS embed.FS
// localSub ist die Kennung im offenen Betrieb — dann gibt es genau einen Nutzer.
const localSub = "local"
// Version ist die einzige Stelle, an der sie steht. build.sh liest sie hier aus
// und vergibt denselben Docker-Tag; die Oberfläche holt sie über /api/public.
// Vorher stand sie zusätzlich in os.js und lief mit den Image-Tags auseinander.
const Version = "1.0"
type Config struct {
Addr string
PublicURL string
DataDir string
SessionSecret string
// Anmeldung — genau einer der beiden Wege:
//
// OIDCIssuer gesetzt → HoneyOS meldet selbst an, eigener Anmeldeschirm.
// ProxyAuth gesetzt → ein vorgelagerter Proxy (Pangolin o. ä.) hat das
// schon erledigt. HoneyOS fragt dann nicht nach.
//
// Keines von beiden heißt: offen. Das ist zum Entwickeln gedacht und sagt
// beim Start deutlich, dass es so nicht ins Netz gehört.
OIDCIssuer string
OIDCClientID string
OIDCClientSecret string
OIDCRedirect string
ButtonLabel string
AllowedUsers []string
ProxyAuth bool
// UserHeader nennt die Kopfzeile, in die der Proxy den Nutzernamen schreibt
// (z. B. Remote-User). Leer heißt: ein einziger Nutzer für alle.
UserHeader string
// AppsKey schützt die App-Liste, wenn ihr Pfad im Proxy von der Anmeldung
// befreit ist. Ohne Schlüssel liest sie jeder, der die Adresse kennt.
AppsKey string
}
func (c Config) RedirectURL() string {
if c.OIDCRedirect != "" {
return c.OIDCRedirect
}
return strings.TrimRight(c.PublicURL, "/") + "/api/auth/callback"
}
type Server struct {
cfg Config
auth *Auth
store *Store
wetter *wetterDienst
cfg config.Config
auth *auth.Auth
store *store.Store
wetter *weather.Dienst
}
func main() {
log.SetFlags(log.LstdFlags)
cfg := Config{
Addr: env("HONEYOS_ADDR", ":80"),
PublicURL: env("HONEYOS_PUBLIC_URL", ""),
DataDir: env("HONEYOS_DATA_DIR", "/data"),
SessionSecret: env("HONEYOS_SESSION_SECRET", ""),
OIDCIssuer: strings.TrimRight(env("HONEYOS_OIDC_ISSUER", ""), "/"),
OIDCClientID: env("HONEYOS_OIDC_CLIENT_ID", ""),
OIDCClientSecret: env("HONEYOS_OIDC_CLIENT_SECRET", ""),
OIDCRedirect: env("HONEYOS_OIDC_REDIRECT_URL", ""),
ButtonLabel: env("HONEYOS_BUTTON_LABEL", "Anmelden"),
AllowedUsers: splitList(env("HONEYOS_ALLOWED_USERS", "")),
ProxyAuth: env("HONEYOS_PROXY_AUTH", "") == "true",
UserHeader: env("HONEYOS_USER_HEADER", ""),
AppsKey: env("HONEYOS_APPS_KEY", ""),
cfg := config.Config{
Addr: config.Env("HONEYOS_ADDR", ":80"),
PublicURL: config.Env("HONEYOS_PUBLIC_URL", ""),
DataDir: config.Env("HONEYOS_DATA_DIR", "/data"),
SessionSecret: config.Env("HONEYOS_SESSION_SECRET", ""),
OIDCIssuer: strings.TrimRight(config.Env("HONEYOS_OIDC_ISSUER", ""), "/"),
OIDCClientID: config.Env("HONEYOS_OIDC_CLIENT_ID", ""),
OIDCClientSecret: config.Env("HONEYOS_OIDC_CLIENT_SECRET", ""),
OIDCRedirect: config.Env("HONEYOS_OIDC_REDIRECT_URL", ""),
ButtonLabel: config.Env("HONEYOS_BUTTON_LABEL", "Anmelden"),
AllowedUsers: splitList(config.Env("HONEYOS_ALLOWED_USERS", "")),
ProxyAuth: config.Env("HONEYOS_PROXY_AUTH", "") == "true",
UserHeader: config.Env("HONEYOS_USER_HEADER", ""),
AppsKey: config.Env("HONEYOS_APPS_KEY", ""),
}
if cfg.OIDCIssuer != "" {
@@ -125,12 +90,12 @@ func main() {
if err := os.MkdirAll(cfg.DataDir, 0o700); err != nil {
log.Fatalf("Datenverzeichnis %s: %v", cfg.DataDir, err)
}
store, err := OpenStore(dataPath(cfg.DataDir))
st, err := store.Open(store.DataPath(cfg.DataDir))
if err != nil {
log.Fatal(err)
}
s := &Server{cfg: cfg, auth: NewAuth(cfg), store: store, wetter: neuerWetterdienst()}
s := &Server{cfg: cfg, auth: auth.New(cfg), store: st, wetter: weather.Neu()}
srv := &http.Server{
Addr: cfg.Addr,
@@ -139,7 +104,7 @@ func main() {
IdleTimeout: 60 * time.Second,
}
log.Printf("HoneyOS %s lauscht auf %s (Daten: %s, Anmeldung: %s)",
Version, cfg.Addr, dataPath(cfg.DataDir), map[bool]string{true: cfg.OIDCIssuer, false: "aus"}[s.auth.Enabled()])
Version, cfg.Addr, store.DataPath(cfg.DataDir), map[bool]string{true: cfg.OIDCIssuer, false: "aus"}[s.auth.Enabled()])
log.Fatal(srv.ListenAndServe())
}
@@ -265,7 +230,7 @@ func (s *Server) appsReadable(next func(http.ResponseWriter, *http.Request, stri
given = r.URL.Query().Get("key")
}
if subtle.ConstantTimeCompare([]byte(given), []byte(key)) == 1 {
next(w, r, localSub)
next(w, r, store.LocalSub)
return
}
// Ohne eigene Anmeldung gibt es keine Sitzung, die den Schlüssel
@@ -288,7 +253,7 @@ func (s *Server) requireAuth(next func(http.ResponseWriter, *http.Request, strin
return
}
if r.Method != http.MethodGet {
if err := s.auth.checkCSRF(r); err != nil {
if err := s.auth.CheckCSRF(r); err != nil {
writeJSON(w, http.StatusForbidden, map[string]string{"error": err.Error()})
return
}
@@ -310,7 +275,7 @@ func (s *Server) handlePublic(w http.ResponseWriter, r *http.Request) {
}
func (s *Server) handleLogout(w http.ResponseWriter, r *http.Request) {
s.auth.clearSession(w, r)
s.auth.ClearSession(w, r)
w.WriteHeader(http.StatusNoContent)
}
@@ -318,7 +283,7 @@ func (s *Server) handleMe(w http.ResponseWriter, r *http.Request, sub string) {
u := s.store.User(sub)
writeJSON(w, http.StatusOK, map[string]any{
"sub": sub,
"name": firstNonEmpty(u.Name, "marc"),
"name": cmp.Or(u.Name, "marc"),
"picture": u.Picture,
"settings": u.Settings,
"oidc": s.auth.Enabled(),
@@ -388,7 +353,7 @@ func (s *Server) handleWeatherSearch(w http.ResponseWriter, r *http.Request, sub
}
func (s *Server) handleSetApps(w http.ResponseWriter, r *http.Request, sub string) {
var in []App
var in []store.App
if err := decode(r, &in); err != nil {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
return
@@ -414,13 +379,6 @@ func writeJSON(w http.ResponseWriter, code int, v any) {
_ = json.NewEncoder(w).Encode(v)
}
func env(key, fallback string) string {
if v := strings.TrimSpace(os.Getenv(key)); v != "" {
return v
}
return fallback
}
func splitList(s string) []string {
var out []string
for _, p := range strings.Split(s, ",") {