diff options
Diffstat (limited to 'internal/state')
| -rw-r--r-- | internal/state/state.go | 83 |
1 files changed, 6 insertions, 77 deletions
diff --git a/internal/state/state.go b/internal/state/state.go index c9aa21d..9b06179 100644 --- a/internal/state/state.go +++ b/internal/state/state.go | |||
| @@ -8,38 +8,18 @@ import ( | |||
| 8 | "regexp" | 8 | "regexp" |
| 9 | ) | 9 | ) |
| 10 | 10 | ||
| 11 | // State represents the entire local deployment state | 11 | // State represents the local ship configuration |
| 12 | type State struct { | 12 | type State struct { |
| 13 | DefaultHost string `json:"default_host,omitempty"` | 13 | DefaultHost string `json:"default_host,omitempty"` |
| 14 | Hosts map[string]*Host `json:"hosts"` | 14 | Hosts map[string]*Host `json:"hosts"` |
| 15 | } | 15 | } |
| 16 | 16 | ||
| 17 | // Host represents deployment state for a single VPS | 17 | // Host represents configuration for a single VPS |
| 18 | type Host struct { | 18 | type Host struct { |
| 19 | NextPort int `json:"next_port"` | 19 | BaseDomain string `json:"base_domain,omitempty"` |
| 20 | BaseDomain string `json:"base_domain,omitempty"` | 20 | GitSetup bool `json:"git_setup,omitempty"` |
| 21 | GitSetup bool `json:"git_setup,omitempty"` | ||
| 22 | Apps map[string]*App `json:"apps"` | ||
| 23 | } | 21 | } |
| 24 | 22 | ||
| 25 | // App represents a deployed application or static site | ||
| 26 | type App struct { | ||
| 27 | Type string `json:"type"` // "app", "static", "git-app", or "git-static" | ||
| 28 | Domain string `json:"domain"` | ||
| 29 | Port int `json:"port,omitempty"` // only for type="app" or "git-app" | ||
| 30 | Repo string `json:"repo,omitempty"` // only for git types, e.g. "/srv/git/foo.git" | ||
| 31 | Public bool `json:"public,omitempty"` // only for git types, enables HTTP clone access | ||
| 32 | Env map[string]string `json:"env,omitempty"` // only for type="app" or "git-app" | ||
| 33 | Args string `json:"args,omitempty"` // only for type="app" | ||
| 34 | Files []string `json:"files,omitempty"` // only for type="app" | ||
| 35 | Memory string `json:"memory,omitempty"` // only for type="app" | ||
| 36 | CPU string `json:"cpu,omitempty"` // only for type="app" | ||
| 37 | } | ||
| 38 | |||
| 39 | const ( | ||
| 40 | startPort = 8001 | ||
| 41 | ) | ||
| 42 | |||
| 43 | var validName = regexp.MustCompile(`^[a-z][a-z0-9-]{0,62}$`) | 23 | var validName = regexp.MustCompile(`^[a-z][a-z0-9-]{0,62}$`) |
| 44 | 24 | ||
| 45 | // ValidateName checks that a name is safe for use in shell commands, | 25 | // ValidateName checks that a name is safe for use in shell commands, |
| @@ -55,7 +35,6 @@ func ValidateName(name string) error { | |||
| 55 | func Load() (*State, error) { | 35 | func Load() (*State, error) { |
| 56 | path := statePath() | 36 | path := statePath() |
| 57 | 37 | ||
| 58 | // If file doesn't exist, return empty state | ||
| 59 | if _, err := os.Stat(path); os.IsNotExist(err) { | 38 | if _, err := os.Stat(path); os.IsNotExist(err) { |
| 60 | return &State{ | 39 | return &State{ |
| 61 | Hosts: make(map[string]*Host), | 40 | Hosts: make(map[string]*Host), |
| @@ -72,7 +51,6 @@ func Load() (*State, error) { | |||
| 72 | return nil, fmt.Errorf("failed to parse state file: %w", err) | 51 | return nil, fmt.Errorf("failed to parse state file: %w", err) |
| 73 | } | 52 | } |
| 74 | 53 | ||
| 75 | // Initialize maps if nil | ||
| 76 | if state.Hosts == nil { | 54 | if state.Hosts == nil { |
| 77 | state.Hosts = make(map[string]*Host) | 55 | state.Hosts = make(map[string]*Host) |
| 78 | } | 56 | } |
| @@ -84,7 +62,6 @@ func Load() (*State, error) { | |||
| 84 | func (s *State) Save() error { | 62 | func (s *State) Save() error { |
| 85 | path := statePath() | 63 | path := statePath() |
| 86 | 64 | ||
| 87 | // Ensure directory exists | ||
| 88 | dir := filepath.Dir(path) | 65 | dir := filepath.Dir(path) |
| 89 | if err := os.MkdirAll(dir, 0755); err != nil { | 66 | if err := os.MkdirAll(dir, 0755); err != nil { |
| 90 | return fmt.Errorf("failed to create config directory: %w", err) | 67 | return fmt.Errorf("failed to create config directory: %w", err) |
| @@ -102,60 +79,14 @@ func (s *State) Save() error { | |||
| 102 | return nil | 79 | return nil |
| 103 | } | 80 | } |
| 104 | 81 | ||
| 105 | // GetHost returns the host state, creating it if it doesn't exist | 82 | // GetHost returns the host config, creating it if it doesn't exist |
| 106 | func (s *State) GetHost(host string) *Host { | 83 | func (s *State) GetHost(host string) *Host { |
| 107 | if s.Hosts[host] == nil { | 84 | if s.Hosts[host] == nil { |
| 108 | s.Hosts[host] = &Host{ | 85 | s.Hosts[host] = &Host{} |
| 109 | NextPort: startPort, | ||
| 110 | Apps: make(map[string]*App), | ||
| 111 | } | ||
| 112 | } | ||
| 113 | if s.Hosts[host].Apps == nil { | ||
| 114 | s.Hosts[host].Apps = make(map[string]*App) | ||
| 115 | } | 86 | } |
| 116 | return s.Hosts[host] | 87 | return s.Hosts[host] |
| 117 | } | 88 | } |
| 118 | 89 | ||
| 119 | // AllocatePort returns the next available port for a host | ||
| 120 | func (s *State) AllocatePort(host string) int { | ||
| 121 | h := s.GetHost(host) | ||
| 122 | port := h.NextPort | ||
| 123 | h.NextPort++ | ||
| 124 | return port | ||
| 125 | } | ||
| 126 | |||
| 127 | // AddApp adds or updates an app in the state | ||
| 128 | func (s *State) AddApp(host, name string, app *App) { | ||
| 129 | h := s.GetHost(host) | ||
| 130 | h.Apps[name] = app | ||
| 131 | } | ||
| 132 | |||
| 133 | // RemoveApp removes an app from the state | ||
| 134 | func (s *State) RemoveApp(host, name string) error { | ||
| 135 | h := s.GetHost(host) | ||
| 136 | if _, exists := h.Apps[name]; !exists { | ||
| 137 | return fmt.Errorf("app %s not found", name) | ||
| 138 | } | ||
| 139 | delete(h.Apps, name) | ||
| 140 | return nil | ||
| 141 | } | ||
| 142 | |||
| 143 | // GetApp returns an app from the state | ||
| 144 | func (s *State) GetApp(host, name string) (*App, error) { | ||
| 145 | h := s.GetHost(host) | ||
| 146 | app, exists := h.Apps[name] | ||
| 147 | if !exists { | ||
| 148 | return nil, fmt.Errorf("app %s not found", name) | ||
| 149 | } | ||
| 150 | return app, nil | ||
| 151 | } | ||
| 152 | |||
| 153 | // ListApps returns all apps for a host | ||
| 154 | func (s *State) ListApps(host string) map[string]*App { | ||
| 155 | h := s.GetHost(host) | ||
| 156 | return h.Apps | ||
| 157 | } | ||
| 158 | |||
| 159 | // GetDefaultHost returns the default host, or empty string if not set | 90 | // GetDefaultHost returns the default host, or empty string if not set |
| 160 | func (s *State) GetDefaultHost() string { | 91 | func (s *State) GetDefaultHost() string { |
| 161 | return s.DefaultHost | 92 | return s.DefaultHost |
| @@ -166,11 +97,9 @@ func (s *State) SetDefaultHost(host string) { | |||
| 166 | s.DefaultHost = host | 97 | s.DefaultHost = host |
| 167 | } | 98 | } |
| 168 | 99 | ||
| 169 | // statePath returns the path to the state file | ||
| 170 | func statePath() string { | 100 | func statePath() string { |
| 171 | home, err := os.UserHomeDir() | 101 | home, err := os.UserHomeDir() |
| 172 | if err != nil { | 102 | if err != nil { |
| 173 | // Fallback to current directory (should rarely happen) | ||
| 174 | return ".ship-state.json" | 103 | return ".ship-state.json" |
| 175 | } | 104 | } |
| 176 | return filepath.Join(home, ".config", "ship", "state.json") | 105 | return filepath.Join(home, ".config", "ship", "state.json") |
