1 Commits

Author SHA1 Message Date
urania 9587d11e21 fix: localectl
build-and-release / release (push) Successful in 2m38s
2026-06-22 22:22:36 +02:00
5 changed files with 35 additions and 24 deletions
+1 -2
View File
@@ -239,8 +239,7 @@ func runServer() {
meta.Register(api, mods)
meta.RegisterHealth(api, sessions)
meta.RegisterWhoami(api, sessions, roles, mods)
meta.ConfigPath = configPath
meta.RegisterUpdate(api)
meta.RegisterUpdate(api, configPath)
auth.RegisterLogin(api, sessions, auditStore, cfg.SecureCookie())
auth.RegisterLogout(api, sessions, cfg.SecureCookie())
+3 -2
View File
@@ -1,8 +1,9 @@
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
@@ -24,7 +25,7 @@ func serverCert(certPath, keyPath string) (tls.Certificate, error) {
}
func generateSelfSignedCert() (tls.Certificate, error) {
priv, err := rsa.GenerateKey(rand.Reader, 2048)
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return tls.Certificate{}, err
}
+15 -3
View File
@@ -110,13 +110,25 @@ func Load(path string) (*File, error) {
return nil, fmt.Errorf("parse config %s: %w", path, err)
}
// release_repo, when set, is downloaded over the wire and (for /api/update)
// executed. Reject http:// at the boundary so /install.sh and the updater
// never have to re-check.
// executed. Validate shape + scheme once here so /install.sh and the updater
// can use the string directly. Trim any trailing slash so downstream string
// concatenation produces a clean URL.
if f.Server.ReleaseRepo != "" {
f.Server.ReleaseRepo = strings.TrimRight(f.Server.ReleaseRepo, "/")
u, err := url.Parse(f.Server.ReleaseRepo)
if err != nil || u.Scheme != "https" {
if err != nil {
return nil, fmt.Errorf("server.release_repo: %w", err)
}
if u.Scheme != "https" {
return nil, fmt.Errorf("server.release_repo must use https:// (got %q)", f.Server.ReleaseRepo)
}
if u.Host == "" {
return nil, fmt.Errorf("server.release_repo missing host: %q", f.Server.ReleaseRepo)
}
parts := strings.Split(strings.Trim(u.Path, "/"), "/")
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
return nil, fmt.Errorf("server.release_repo must be https://host/owner/repo, got %q", f.Server.ReleaseRepo)
}
}
return &f, nil
}
+8 -9
View File
@@ -12,19 +12,18 @@ import (
"github.com/danielgtaylor/huma/v2"
)
// ConfigPath is set at startup so the update handler can re-load config and
// surface release_repo / parse errors to the caller instead of only stderr.
var ConfigPath string
// RegisterUpdate wires POST /api/meta/update. It runs the equivalent of
// RegisterUpdate wires POST /api/update. It runs the equivalent of
// `sudo nadir update` in a detached session and returns 202 immediately; the
// systemctl restart that ends the updater drops in-flight connections, so the
// caller should poll /api/health to confirm the new version is up.
//
// configPath is re-read by the handler so a missing release_repo (or any other
// config error) surfaces as 4xx/5xx to the caller, not as stderr only.
//
// Authorization: requires (meta, root). Only roles with a wildcard grant
// (the default admin role) match, since "meta" isn't a real module with a
// declared permission vocabulary.
func RegisterUpdate(api huma.API) {
func RegisterUpdate(api huma.API, configPath string) {
huma.Register(api, huma.Operation{
OperationID: "meta-update",
Method: "POST",
@@ -36,13 +35,13 @@ func RegisterUpdate(api huma.API) {
Errors: []int{400, 401, 403, 500},
DefaultStatus: 202,
}, func(ctx context.Context, _ *struct{}) (*oscmd.StatusOutput, error) {
if ConfigPath != "" {
cfg, err := config.Load(ConfigPath)
if configPath != "" {
cfg, err := config.Load(configPath)
if err != nil {
return nil, huma.Error500InternalServerError("config load failed", err)
}
if cfg.Server.ReleaseRepo == "" {
return nil, huma.Error400BadRequest("server.release_repo not set in " + ConfigPath)
return nil, huma.Error400BadRequest("server.release_repo not set in " + configPath)
}
}
exe, err := os.Executable()
+8 -8
View File
@@ -160,10 +160,11 @@ func registerLocale(api huma.API) {
Metadata: op("read"),
Errors: readErrors,
}, func(ctx context.Context, _ *struct{}) (*KeymapsOutput, error) {
keymaps, err := oscmd.RunLines("localectl", "list-keymaps")
if err != nil {
return nil, huma.Error500InternalServerError("localectl failed", err)
}
// ponytail: minimal servers ship without kbd / /usr/share/keymaps, so
// localectl errors instead of returning empty. Treat that as "no keymaps
// available" rather than a server fault — set-keymap stays unreachable
// because nothing will be in the allowlist.
keymaps, _ := oscmd.RunLines("localectl", "list-keymaps")
out := &KeymapsOutput{}
out.Body.Keymaps = keymaps
return out, nil
@@ -185,10 +186,9 @@ func registerLocale(api huma.API) {
if km == "" {
return nil, huma.Error400BadRequest("empty keymap")
}
keymaps, err := oscmd.RunLines("localectl", "list-keymaps")
if err != nil {
return nil, huma.Error500InternalServerError("localectl failed", err)
}
// list-keymaps failure means no keymap allowlist on this host (kbd absent);
// fall through to unknown-keymap 400 instead of 500.
keymaps, _ := oscmd.RunLines("localectl", "list-keymaps")
if !slices.Contains(keymaps, km) {
return nil, huma.Error400BadRequest("unknown keymap: " + km)
}