Skip to content

Commit aa93c75

Browse files
authored
v2.8.0: Hermes, settable vere, status lifecycle fixes (#759)
* fix stuck toggles * precommit hook: * add config editors
1 parent 26260a9 commit aa93c75

13 files changed

Lines changed: 601 additions & 26 deletions

File tree

.githooks/pre-commit

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#!/usr/bin/env sh
2+
set -eu
3+
4+
repo_root="$(git rev-parse --show-toplevel)"
5+
goseg_dir="$repo_root/goseg"
6+
7+
if [ ! -d "$goseg_dir" ]; then
8+
echo "pre-commit: missing goseg directory at $goseg_dir" >&2
9+
exit 1
10+
fi
11+
12+
if ! command -v go >/dev/null 2>&1; then
13+
echo "pre-commit: go is not available on PATH" >&2
14+
exit 1
15+
fi
16+
17+
run_goimports() {
18+
go_files="$(git ls-files -- '*.go')"
19+
if [ -z "$go_files" ]; then
20+
return 0
21+
fi
22+
23+
if command -v goimports >/dev/null 2>&1; then
24+
printf '%s\n' "$go_files" | xargs goimports -w
25+
else
26+
printf '%s\n' "$go_files" | xargs go run golang.org/x/tools/cmd/goimports@v0.39.0 -w
27+
fi
28+
}
29+
30+
staged_go_files="$(git diff --cached --name-only --diff-filter=ACMR -- goseg | sed -n '/\.go$/p')"
31+
32+
if [ -z "$staged_go_files" ]; then
33+
exit 0
34+
fi
35+
36+
unstaged_go_files="$(git diff --name-only -- goseg | sed -n '/\.go$/p')"
37+
untracked_go_files="$(git ls-files --others --exclude-standard -- goseg | sed -n '/\.go$/p')"
38+
39+
if [ -n "$unstaged_go_files" ] || [ -n "$untracked_go_files" ]; then
40+
echo "pre-commit: go fix/fmt rewrites package files, but goseg has unstaged or untracked Go changes." >&2
41+
echo "pre-commit: stage or stash these files before committing:" >&2
42+
{
43+
printf '%s\n' "$unstaged_go_files"
44+
printf '%s\n' "$untracked_go_files"
45+
} | sed '/^$/d' | sed 's/^/ /' >&2
46+
exit 1
47+
fi
48+
49+
echo "pre-commit: running go fix ./... in goseg"
50+
(cd "$goseg_dir" && go fix ./...)
51+
52+
echo "pre-commit: cleaning Go imports in goseg"
53+
(cd "$goseg_dir" && run_goimports)
54+
55+
echo "pre-commit: running go fmt ./... in goseg"
56+
(cd "$goseg_dir" && go fmt ./...)
57+
58+
printf '%s\n' "$staged_go_files" | while IFS= read -r file; do
59+
[ -n "$file" ] && git add -- "$file"
60+
done
61+
62+
remaining_unstaged_go_files="$(git diff --name-only -- goseg | sed -n '/\.go$/p')"
63+
64+
if [ -n "$remaining_unstaged_go_files" ]; then
65+
echo "pre-commit: go fix/fmt changed additional Go files. Review and stage them before committing:" >&2
66+
printf '%s\n' "$remaining_unstaged_go_files" | sed 's/^/ /' >&2
67+
exit 1
68+
fi

goseg/config/config.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,26 @@ func UpdateConf(values map[string]any) error {
298298
return nil
299299
}
300300

301+
func ReplaceConfJSON(raw []byte) ([]byte, error) {
302+
confMutex.Lock()
303+
defer confMutex.Unlock()
304+
var configMap map[string]any
305+
if err := json.Unmarshal(raw, &configMap); err != nil {
306+
return nil, fmt.Errorf("invalid system config JSON: %v", err)
307+
}
308+
if len(configMap) == 0 {
309+
return nil, fmt.Errorf("refusing to persist empty system configuration")
310+
}
311+
formatted, err := json.MarshalIndent(configMap, "", " ")
312+
if err != nil {
313+
return nil, fmt.Errorf("error encoding system config: %v", err)
314+
}
315+
if err := persistConf(configMap); err != nil {
316+
return nil, fmt.Errorf("unable to persist system config: %v", err)
317+
}
318+
return formatted, nil
319+
}
320+
301321
func persistConf(configMap map[string]any) error {
302322
BasePath := getBasePath()
303323
confPath := filepath.Join(BasePath, "settings", "system.json")
@@ -326,9 +346,11 @@ func persistConf(configMap map[string]any) error {
326346
if fi.Size() == 0 {
327347
return fmt.Errorf("refusing to persist empty configuration file")
328348
}
329-
if err := json.Unmarshal(updatedJSON, &globalConfig); err != nil {
349+
var nextConfig structs.SysConfig
350+
if err := json.Unmarshal(updatedJSON, &nextConfig); err != nil {
330351
return fmt.Errorf("error updating global config: %v", err)
331352
}
353+
globalConfig = nextConfig
332354
if err := os.Rename(tmpPath, confPath); err != nil {
333355
return fmt.Errorf("error moving temp file: %v", err)
334356
}

goseg/config/urbit.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,73 @@ func UpdateUrbitConfig(inputConfig map[string]structs.UrbitDocker) error {
129129
return nil
130130
}
131131

132+
func ReplaceUrbitConfigJSON(pier string, raw []byte) ([]byte, error) {
133+
var configMap map[string]any
134+
if err := json.Unmarshal(raw, &configMap); err != nil {
135+
return nil, fmt.Errorf("invalid %s config JSON: %v", pier, err)
136+
}
137+
if len(configMap) == 0 {
138+
return nil, fmt.Errorf("refusing to persist empty configuration for pier %s", pier)
139+
}
140+
formatted, err := json.MarshalIndent(configMap, "", " ")
141+
if err != nil {
142+
return nil, fmt.Errorf("error encoding %s config: %v", pier, err)
143+
}
144+
var targetStruct structs.UrbitDocker
145+
if err := unmarshalUrbitDockerSafe(formatted, &targetStruct); err != nil {
146+
return nil, err
147+
}
148+
if targetStruct.PierName != "" && targetStruct.PierName != pier {
149+
return nil, fmt.Errorf("pier_name %q does not match %q", targetStruct.PierName, pier)
150+
}
151+
if targetStruct.StartramReminder == nil {
152+
targetStruct.StartramReminder = defaults.UrbitConfig.StartramReminder
153+
}
154+
if targetStruct.SnapTime == 0 {
155+
targetStruct.SnapTime = 60
156+
}
157+
structs.SyncCustomS3Domains(&targetStruct)
158+
159+
urbitMutex.Lock()
160+
defer urbitMutex.Unlock()
161+
path := filepath.Join(BasePath, "settings", "pier", pier+".json")
162+
if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
163+
return nil, err
164+
}
165+
tmpFile, err := os.CreateTemp(filepath.Dir(path), pier+".json.*")
166+
if err != nil {
167+
return nil, fmt.Errorf("error creating temp file: %v", err)
168+
}
169+
tmpPath := tmpFile.Name()
170+
defer os.Remove(tmpPath)
171+
if _, err := tmpFile.Write(formatted); err != nil {
172+
tmpFile.Close()
173+
return nil, fmt.Errorf("error writing temp file: %v", err)
174+
}
175+
if err := tmpFile.Close(); err != nil {
176+
return nil, fmt.Errorf("error closing temp file: %v", err)
177+
}
178+
if fi, err := os.Stat(tmpPath); err != nil {
179+
return nil, fmt.Errorf("error checking temp file: %v", err)
180+
} else if fi.Size() == 0 {
181+
return nil, fmt.Errorf("refusing to persist empty configuration for pier %s", pier)
182+
}
183+
UrbitsConfig[pier] = targetStruct
184+
if err := os.Rename(tmpPath, path); err != nil {
185+
return nil, fmt.Errorf("error moving temp file: %v", err)
186+
}
187+
return formatted, nil
188+
}
189+
190+
func unmarshalUrbitDockerSafe(data []byte, target *structs.UrbitDocker) (err error) {
191+
defer func() {
192+
if r := recover(); r != nil {
193+
err = fmt.Errorf("invalid urbit config: %v", r)
194+
}
195+
}()
196+
return json.Unmarshal(data, target)
197+
}
198+
132199
func UpdateUrbitConfigForPier(pier string, mutate func(*structs.UrbitDocker)) error {
133200
if err := LoadUrbitConfig(pier); err != nil {
134201
return err

goseg/config/urbit_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,38 @@ func TestLoadAndUpdateUrbitConfigKeepsLegacyCustomS3Field(t *testing.T) {
7878
t.Fatalf("expected remote field to persist, got %q", saved.CustomS3WebRemote)
7979
}
8080
}
81+
82+
func TestReplaceUrbitConfigJSONValidatesAndUpdatesMemory(t *testing.T) {
83+
oldBasePath := BasePath
84+
oldConfigs := UrbitsConfig
85+
t.Cleanup(func() {
86+
BasePath = oldBasePath
87+
UrbitsConfig = oldConfigs
88+
})
89+
90+
BasePath = t.TempDir()
91+
UrbitsConfig = make(map[string]structs.UrbitDocker)
92+
pier := "sampel-palnet"
93+
94+
if _, err := ReplaceUrbitConfigJSON(pier, []byte(`{"pier_name":"wrong-palnet"}`)); err == nil {
95+
t.Fatalf("expected mismatched pier_name to fail")
96+
}
97+
if _, err := ReplaceUrbitConfigJSON(pier, []byte(`{"minio_linked":"not-a-bool"}`)); err == nil {
98+
t.Fatalf("expected invalid field type to fail")
99+
}
100+
101+
formatted, err := ReplaceUrbitConfigJSON(pier, []byte(`{"pier_name":"sampel-palnet","http_port":8080,"ames_port":34343}`))
102+
if err != nil {
103+
t.Fatalf("failed to replace urbit config: %v", err)
104+
}
105+
if !json.Valid(formatted) {
106+
t.Fatalf("formatted config is not valid JSON: %s", string(formatted))
107+
}
108+
conf := UrbitConf(pier)
109+
if conf.PierName != pier {
110+
t.Fatalf("in-memory pier_name = %q, want %q", conf.PierName, pier)
111+
}
112+
if conf.HTTPPort != 8080 {
113+
t.Fatalf("in-memory http_port = %d, want 8080", conf.HTTPPort)
114+
}
115+
}

0 commit comments

Comments
 (0)