From 13c2f9cffa624fdf498f3b61fab9d809b92e026e Mon Sep 17 00:00:00 2001 From: bndw Date: Sun, 28 Dec 2025 09:21:08 -0800 Subject: init --- internal/state/state.go | 146 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 internal/state/state.go (limited to 'internal/state/state.go') diff --git a/internal/state/state.go b/internal/state/state.go new file mode 100644 index 0000000..c7c7dd4 --- /dev/null +++ b/internal/state/state.go @@ -0,0 +1,146 @@ +package state + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" +) + +// State represents the entire local deployment state +type State struct { + Hosts map[string]*Host `json:"hosts"` +} + +// Host represents deployment state for a single VPS +type Host struct { + NextPort int `json:"next_port"` + Apps map[string]*App `json:"apps"` +} + +// App represents a deployed application or static site +type App struct { + Type string `json:"type"` // "app" or "static" + Domain string `json:"domain"` + Port int `json:"port,omitempty"` // only for type="app" + Env map[string]string `json:"env,omitempty"` // only for type="app" +} + +const ( + startPort = 8001 +) + +// Load reads state from ~/.config/deploy/state.json +func Load() (*State, error) { + path := statePath() + + // If file doesn't exist, return empty state + if _, err := os.Stat(path); os.IsNotExist(err) { + return &State{ + Hosts: make(map[string]*Host), + }, nil + } + + data, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("failed to read state file: %w", err) + } + + var state State + if err := json.Unmarshal(data, &state); err != nil { + return nil, fmt.Errorf("failed to parse state file: %w", err) + } + + // Initialize maps if nil + if state.Hosts == nil { + state.Hosts = make(map[string]*Host) + } + + return &state, nil +} + +// Save writes state to ~/.config/deploy/state.json +func (s *State) Save() error { + path := statePath() + + // Ensure directory exists + dir := filepath.Dir(path) + if err := os.MkdirAll(dir, 0755); err != nil { + return fmt.Errorf("failed to create config directory: %w", err) + } + + data, err := json.MarshalIndent(s, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal state: %w", err) + } + + if err := os.WriteFile(path, data, 0600); err != nil { + return fmt.Errorf("failed to write state file: %w", err) + } + + return nil +} + +// GetHost returns the host state, creating it if it doesn't exist +func (s *State) GetHost(host string) *Host { + if s.Hosts[host] == nil { + s.Hosts[host] = &Host{ + NextPort: startPort, + Apps: make(map[string]*App), + } + } + if s.Hosts[host].Apps == nil { + s.Hosts[host].Apps = make(map[string]*App) + } + return s.Hosts[host] +} + +// AllocatePort returns the next available port for a host +func (s *State) AllocatePort(host string) int { + h := s.GetHost(host) + port := h.NextPort + h.NextPort++ + return port +} + +// AddApp adds or updates an app in the state +func (s *State) AddApp(host, name string, app *App) { + h := s.GetHost(host) + h.Apps[name] = app +} + +// RemoveApp removes an app from the state +func (s *State) RemoveApp(host, name string) error { + h := s.GetHost(host) + if _, exists := h.Apps[name]; !exists { + return fmt.Errorf("app %s not found", name) + } + delete(h.Apps, name) + return nil +} + +// GetApp returns an app from the state +func (s *State) GetApp(host, name string) (*App, error) { + h := s.GetHost(host) + app, exists := h.Apps[name] + if !exists { + return nil, fmt.Errorf("app %s not found", name) + } + return app, nil +} + +// ListApps returns all apps for a host +func (s *State) ListApps(host string) map[string]*App { + h := s.GetHost(host) + return h.Apps +} + +// statePath returns the path to the state file +func statePath() string { + home, err := os.UserHomeDir() + if err != nil { + // Fallback to current directory (should rarely happen) + return ".deploy-state.json" + } + return filepath.Join(home, ".config", "deploy", "state.json") +} -- cgit v1.2.3