-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathconfig.go
More file actions
212 lines (194 loc) · 6.43 KB
/
Copy pathconfig.go
File metadata and controls
212 lines (194 loc) · 6.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
// SPDX-License-Identifier: AGPL-3.0-or-later
package skillinject
import (
"encoding/json"
"os"
"path/filepath"
)
// configFileName is the per-user pilot config file. The same file the
// rest of pilotctl reads from. We only touch known subkeys.
const configFileName = "config.json"
// configUpdateKey is the subkey under which we store the update settings.
const configUpdateKey = "update"
// configKey is the subkey under which we store the mode flag.
const configKey = "skill_inject"
// Mode names for the tri-state skill_inject.mode setting.
const (
// ModeManual: install once + refresh on Pilot update, no 15-min ticker.
ModeManual = "manual"
// ModeAuto: current always-live behaviour (15-min reconcile ticker).
ModeAuto = "auto"
// ModeDisabled: remove skills + no ticks.
ModeDisabled = "disabled"
)
// defaultMode is the mode used when no persisted value exists.
const defaultMode = ModeManual
// ModeFlag describes the persisted tri-state mode. Stored at
// ~/.pilot/config.json under "skill_inject" → {"mode": "manual"|"auto"|"disabled"}.
type ModeFlag struct {
Mode string `json:"mode,omitempty"`
}
// EnabledFlag describes the persisted opt-out state (legacy format).
// Stored at ~/.pilot/config.json under "skill_inject" → {"enabled": bool}.
// Deprecated: use ModeFlag with GetMode/SetMode instead.
type EnabledFlag struct {
Enabled bool `json:"enabled"`
}
func configFilePath(home string) string {
return filepath.Join(home, ".pilot", configFileName)
}
// GetMode returns the current skill_inject mode. Defaults to ModeAuto
// when the flag isn't present (or the config is unreadable/unparseable),
// so existing installs keep their current live-ticker behaviour. New
// installs are set to ModeManual explicitly on the first call to SetMode
// (configured by the daemon's startup path) — we never flip an existing
// install off the live ticker by silently changing the absent-config
// default.
func GetMode(home string) string {
f, err := os.Open(configFilePath(home))
if err != nil {
return ModeAuto // existing behaviour — live ticker
}
defer f.Close()
var raw map[string]json.RawMessage
if err := json.NewDecoder(f).Decode(&raw); err != nil {
return ModeAuto
}
sub, ok := raw[configKey]
if !ok {
return ModeAuto
}
// Try the new mode-based format first.
var modeFlag ModeFlag
if err := json.Unmarshal(sub, &modeFlag); err == nil && modeFlag.Mode != "" {
switch modeFlag.Mode {
case ModeManual, ModeAuto, ModeDisabled:
return modeFlag.Mode
}
// Unknown mode value — fall through to legacy.
}
// Fall back to the legacy enabled-based format.
var enabledFlag EnabledFlag
if err := json.Unmarshal(sub, &enabledFlag); err == nil {
if enabledFlag.Enabled {
return ModeAuto
}
return ModeDisabled
}
return ModeAuto
}
// SetMode persists the skill_inject mode. Reads the existing config (if
// any), updates only the skill_inject key, writes back atomically.
// Accepts ModeManual, ModeAuto, or ModeDisabled. Empty string is treated
// as ModeAuto for backward compatibility.
func SetMode(home, mode string) error {
switch mode {
case "":
mode = ModeAuto
case ModeManual, ModeAuto, ModeDisabled:
// valid
default:
mode = ModeAuto
}
p := configFilePath(home)
if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
return err
}
raw := map[string]json.RawMessage{}
if b, err := os.ReadFile(p); err == nil {
_ = json.Unmarshal(b, &raw)
}
flagJSON, _ := json.Marshal(ModeFlag{Mode: mode})
raw[configKey] = flagJSON
out, _ := json.MarshalIndent(raw, "", " ")
tmp := p + ".tmp"
if err := os.WriteFile(tmp, out, 0o600); err != nil {
return err
}
if err := os.Rename(tmp, p); err != nil {
_ = os.Remove(tmp)
return err
}
return nil
}
// IsEnabled returns whether skill injection is on. Returns true for
// ModeManual and ModeAuto, false for ModeDisabled. Defaults to true
// (opt-out, not opt-in) when the flag isn't present, so fresh installs
// get the feature without any extra step.
//
// Deprecated: use GetMode to get the full tri-state.
func IsEnabled(home string) bool {
return GetMode(home) != ModeDisabled
}
// SetEnabled persists the opt-out flag. Maps true → ModeAuto, false →
// ModeDisabled. Keeps existing Manual mode unless explicitly toggled.
//
// Deprecated: use SetMode for the full tri-state control.
func SetEnabled(home string, enabled bool) error {
mode := ModeAuto
if !enabled {
mode = ModeDisabled
}
return SetMode(home, mode)
}
// --- update config ---
// UpdateConfig describes the runtime-tunable auto-update settings.
// Stored at ~/.pilot/config.json under "update" → {...}.
// Zero values leave the updater default in place.
type UpdateConfig struct {
// Auto controls whether the updater checks for and applies new
// versions. True (default) = auto-update enabled.
Auto *bool `json:"auto,omitempty"`
// Pin locks the updater to a specific release tag (e.g. "v1.10.5").
// Empty string (default) means no pin — follow latest stable.
Pin string `json:"pin,omitempty"`
// Interval is the minimum time between update checks, in Go duration
// string format ("15m", "1h"). Empty string (default) means use the
// updater's built-in default check interval.
Interval string `json:"interval,omitempty"`
}
// GetUpdateConfig reads the update subkey from ~/.pilot/config.json.
// Returns a zero-value UpdateConfig when the subkey is absent, so the
// caller can apply its own defaults.
func GetUpdateConfig(home string) UpdateConfig {
f, err := os.Open(configFilePath(home))
if err != nil {
return UpdateConfig{}
}
defer f.Close()
var raw map[string]json.RawMessage
if err := json.NewDecoder(f).Decode(&raw); err != nil {
return UpdateConfig{}
}
sub, ok := raw[configUpdateKey]
if !ok {
return UpdateConfig{}
}
var cfg UpdateConfig
_ = json.Unmarshal(sub, &cfg)
return cfg
}
// SetUpdateConfig persists the update subkey. Reads the existing config
// (if any), updates only the update key, writes back atomically.
func SetUpdateConfig(home string, cfg UpdateConfig) error {
p := configFilePath(home)
if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
return err
}
raw := map[string]json.RawMessage{}
if b, err := os.ReadFile(p); err == nil {
_ = json.Unmarshal(b, &raw)
}
cfgJSON, _ := json.Marshal(cfg)
raw[configUpdateKey] = cfgJSON
out, _ := json.MarshalIndent(raw, "", " ")
tmp := p + ".tmp"
if err := os.WriteFile(tmp, out, 0o600); err != nil {
return err
}
if err := os.Rename(tmp, p); err != nil {
_ = os.Remove(tmp)
return err
}
return nil
}