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:
1 parent
d08b0a229c
commit
52deffa30e
20 files changed
+780
-624
No files matched your search
@@ -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, ",") {
|
||||
|
||||
Reference in new issue
Block a user