mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-09-23 10:44:16 +00:00
The scanner shared the hold's 1 GB host and thrashed it twice: 11 hours on 2026-09-12 and again on the 13th (644 MiB resident plus 1.9 GB of swap, 504 on every repo page). It is memory-bound, not CPU-bound, so it now gets a dedicated STARTER-2xCPU-4GB server: own state entry, own plan flag (pinned name, shape match if UpCloud renames the tier again, picker last), own cloud-init, firewall, `update scanner`, `ssh scanner`, status, backup and teardown. Its config reaches the hold over the private network and its unit sets MemorySwapMax=0 so an overshoot is an OOM kill and a restart, not a wedged host. The hold's cloud-init and update paths no longer carry it. Three defects the first provision run exposed, all fixed here: - Frontend HTTP/2 defaulted to on and was reconciled onto the LB every run. Re-enabling it on the 12th stranded the appview<->hold connections for 25 minutes. Default is now off and reconciled off, with a guard test. - The TLS step requested Let's Encrypt bundles for every registry domain, re-adding the .cr ones that were removed when those moved behind Bunny. It now skips any domain whose DNS does not resolve to the LB. - Each prompt built its own bufio.Scanner on stdin, so the first swallowed every piped answer and the second read EOF and took the default, which re-ran cloud-init on the production hold. One shared reader, and no answer now means skip. Also: STARTER- plans take standard storage (maxiops fails with TIER_INVALID), and the cloud-init wait polls for up to 20 minutes instead of one SSH call capped at five, which a first boot with npm exceeds. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hho5da4daoCoPBJ9tCrL7s
96 lines
2.6 KiB
Go
96 lines
2.6 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
)
|
|
|
|
// InfraState persists infrastructure resource UUIDs between commands.
|
|
type InfraState struct {
|
|
Zone string `json:"zone"`
|
|
ClientName string `json:"client_name,omitempty"`
|
|
RepoBranch string `json:"repo_branch,omitempty"`
|
|
Network StateRef `json:"network"`
|
|
Appview ServerState `json:"appview"`
|
|
Hold ServerState `json:"hold"`
|
|
Scanner ServerState `json:"scanner,omitempty"`
|
|
LB StateRef `json:"loadbalancer"`
|
|
ObjectStorage ObjectStorageState `json:"object_storage"`
|
|
ScannerEnabled bool `json:"scanner_enabled,omitempty"`
|
|
ScannerSecret string `json:"scanner_secret,omitempty"`
|
|
LabelerEnabled bool `json:"labeler_enabled,omitempty"`
|
|
}
|
|
|
|
// Naming returns a Naming helper, defaulting to "seamark" if ClientName is empty.
|
|
func (s *InfraState) Naming() Naming {
|
|
name := s.ClientName
|
|
if name == "" {
|
|
name = "seamark"
|
|
}
|
|
return Naming{ClientName: name}
|
|
}
|
|
|
|
// Branch returns the repo branch, defaulting to "main" if empty.
|
|
func (s *InfraState) Branch() string {
|
|
if s.RepoBranch == "" {
|
|
return "main"
|
|
}
|
|
return s.RepoBranch
|
|
}
|
|
|
|
type StateRef struct {
|
|
UUID string `json:"uuid"`
|
|
}
|
|
|
|
type ServerState struct {
|
|
UUID string `json:"server_uuid"`
|
|
PublicIP string `json:"public_ip"`
|
|
PrivateIP string `json:"private_ip"`
|
|
}
|
|
|
|
type ObjectStorageState struct {
|
|
UUID string `json:"uuid"`
|
|
Endpoint string `json:"endpoint"`
|
|
Region string `json:"region"`
|
|
Bucket string `json:"bucket"`
|
|
AccessKeyID string `json:"access_key_id"`
|
|
}
|
|
|
|
func statePath() string {
|
|
_, thisFile, _, _ := runtime.Caller(0)
|
|
return filepath.Join(filepath.Dir(thisFile), "state.json")
|
|
}
|
|
|
|
func loadState() (*InfraState, error) {
|
|
data, err := os.ReadFile(statePath())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read state.json: %w (run 'provision' first)", err)
|
|
}
|
|
var st InfraState
|
|
if err := json.Unmarshal(data, &st); err != nil {
|
|
return nil, fmt.Errorf("parse state.json: %w", err)
|
|
}
|
|
return &st, nil
|
|
}
|
|
|
|
func saveState(st *InfraState) error {
|
|
data, err := json.MarshalIndent(st, "", " ")
|
|
if err != nil {
|
|
return fmt.Errorf("marshal state: %w", err)
|
|
}
|
|
if err := os.WriteFile(statePath(), data, 0644); err != nil {
|
|
return fmt.Errorf("write state.json: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func deleteState() error {
|
|
if err := os.Remove(statePath()); err != nil && !os.IsNotExist(err) {
|
|
return fmt.Errorf("remove state.json: %w", err)
|
|
}
|
|
return nil
|
|
}
|