feat(platform): add cmsctl and document newsletter and shell updates
Generated with Codebuff 🤖 Co-Authored-By: Codebuff <noreply@codebuff.com>
This commit is contained in:
1 parent
0f8ed97878
commit
fea6eb51b1
7 files changed
+2408
No files matched your search
@@ -0,0 +1,140 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultBaseURL = "http://localhost:8080"
|
||||
configEnv = "CMSCTL_CONFIG"
|
||||
)
|
||||
|
||||
// Config is deliberately small and JSON-compatible so operators can inspect
|
||||
// or move it between machines. The file is written with mode 0600.
|
||||
type Config struct {
|
||||
BaseURL string `json:"base_url,omitempty"`
|
||||
FinderURL string `json:"finder_url,omitempty"`
|
||||
AccessToken string `json:"access_token,omitempty"`
|
||||
TokenType string `json:"token_type,omitempty"`
|
||||
ExpiresAt time.Time `json:"expires_at,omitempty"`
|
||||
}
|
||||
|
||||
func defaultConfig() Config {
|
||||
return Config{BaseURL: defaultBaseURL, TokenType: "Bearer"}
|
||||
}
|
||||
|
||||
func configPath(override string) (string, error) {
|
||||
if override != "" {
|
||||
return override, nil
|
||||
}
|
||||
if p := os.Getenv(configEnv); p != "" {
|
||||
return p, nil
|
||||
}
|
||||
// UserConfigDir follows the platform convention: $XDG_CONFIG_HOME or
|
||||
// ~/.config on Unix, %AppData% on Windows, and ~/Library/Application
|
||||
// Support on macOS. Keep the explicit XDG check for callers that set it
|
||||
// in environments where UserConfigDir cannot resolve it yet.
|
||||
if dir := os.Getenv("XDG_CONFIG_HOME"); dir != "" {
|
||||
return filepath.Join(dir, "cmsctl", "config.json"), nil
|
||||
}
|
||||
if dir, err := os.UserConfigDir(); err == nil && dir != "" {
|
||||
return filepath.Join(dir, "cmsctl", "config.json"), nil
|
||||
}
|
||||
// Last-resort Unix-compatible fallback for minimal environments without a
|
||||
// discoverable platform config directory.
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("find user config directory: %w", err)
|
||||
}
|
||||
return filepath.Join(home, ".config", "cmsctl", "config.json"), nil
|
||||
}
|
||||
|
||||
func loadConfig(path string) (Config, error) {
|
||||
cfg := defaultConfig()
|
||||
body, err := os.ReadFile(path)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return cfg, nil
|
||||
}
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("read config %s: %w", path, err)
|
||||
}
|
||||
if err := json.Unmarshal(body, &cfg); err != nil {
|
||||
return Config{}, fmt.Errorf("parse config %s: %w", path, err)
|
||||
}
|
||||
if cfg.BaseURL == "" {
|
||||
cfg.BaseURL = defaultBaseURL
|
||||
}
|
||||
if cfg.TokenType == "" {
|
||||
cfg.TokenType = "Bearer"
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func saveConfig(path string, cfg Config) error {
|
||||
if cfg.BaseURL == "" {
|
||||
cfg.BaseURL = defaultBaseURL
|
||||
}
|
||||
if cfg.TokenType == "" {
|
||||
cfg.TokenType = "Bearer"
|
||||
}
|
||||
body, err := json.MarshalIndent(cfg, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("encode config: %w", err)
|
||||
}
|
||||
body = append(body, '\n')
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
|
||||
return fmt.Errorf("create config directory: %w", err)
|
||||
}
|
||||
tmp, err := os.CreateTemp(filepath.Dir(path), ".config-*")
|
||||
if err != nil {
|
||||
return fmt.Errorf("create config temp file: %w", err)
|
||||
}
|
||||
tmpName := tmp.Name()
|
||||
defer os.Remove(tmpName)
|
||||
if err := tmp.Chmod(0o600); err != nil {
|
||||
tmp.Close()
|
||||
return fmt.Errorf("protect config temp file: %w", err)
|
||||
}
|
||||
if _, err := tmp.Write(body); err != nil {
|
||||
tmp.Close()
|
||||
return fmt.Errorf("write config: %w", err)
|
||||
}
|
||||
if err := tmp.Sync(); err != nil {
|
||||
tmp.Close()
|
||||
return fmt.Errorf("sync config: %w", err)
|
||||
}
|
||||
if err := tmp.Close(); err != nil {
|
||||
return fmt.Errorf("close config: %w", err)
|
||||
}
|
||||
if err := os.Rename(tmpName, path); err != nil {
|
||||
return fmt.Errorf("replace config: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func clearToken(cfg *Config) {
|
||||
cfg.AccessToken = ""
|
||||
cfg.TokenType = "Bearer"
|
||||
cfg.ExpiresAt = time.Time{}
|
||||
}
|
||||
|
||||
func effectiveBaseURL(cfg Config) string {
|
||||
base := strings.TrimRight(cfg.BaseURL, "/")
|
||||
if base == "" {
|
||||
return defaultBaseURL
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
func effectiveFinderURL(cfg Config) string {
|
||||
if cfg.FinderURL != "" {
|
||||
return strings.TrimRight(cfg.FinderURL, "/")
|
||||
}
|
||||
return effectiveBaseURL(cfg) + "/find"
|
||||
}
|
||||
Reference in new issue
Block a user