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
package bootstrap
import (
"context"
"errors"
"fmt"
"log/slog"
"os"
"path/filepath"
"slices"
"strconv"
"strings"
"github.com/johnandersen777/socialweb-computer-kcp/internal/hostos"
"github.com/johnandersen777/socialweb-computer-kcp/internal/hostsvc"
"github.com/johnandersen777/socialweb-computer-kcp/internal/kcpserver"
)
type SlotDrift struct {
Slot string `json:"slot"`
Unit string `json:"unit"`
PID int `json:"pid"`
Running []string `json:"running,omitempty"`
Want []string `json:"want,omitempty"`
RunningExe string `json:"runningExe,omitempty"`
WantExe string `json:"wantExe,omitempty"`
Refused string `json:"refused,omitempty"`
ArgvChanged bool `json:"argvChanged"`
ExeChanged bool `json:"exeChanged"`
}
func (d SlotDrift) Drifted() bool { return d.ArgvChanged || d.ExeChanged }
func ActiveSlotDrift(ctx context.Context, m hostsvc.Manager, p Params) (SlotDrift, bool, error) {
slots, err := kcpserver.ReadSlots(p.SlotsFile())
if errors.Is(err, os.ErrNotExist) {
return SlotDrift{}, false, nil
}
if err != nil {
return SlotDrift{}, false, err
}
d := SlotDrift{Slot: slots.Active, Unit: p.App.Names().Slot(slots.Active)}
st, err := m.Inspect(ctx, d.Unit)
if err != nil || st.State != hostsvc.Active || st.MainPID <= 0 {
return d, false, err
}
d.PID = st.MainPID
raw, err := os.ReadFile(filepath.Join("/proc", strconv.Itoa(d.PID), "cmdline"))
if err != nil {
return d, false, err
}
d.Running = strings.Split(strings.TrimRight(string(raw), "\x00"), "\x00")
spec, _, err := p.slotSpec(slots.Active, slots.Front.URL())
if err != nil {
return d, false, err
}
d.Want = spec.Exec
d.ArgvChanged = !slices.Equal(d.Running, d.Want)
if err := kcpserver.CheckArgv(d.Running, slots.Shape); err != nil {
d.Refused = err.Error()
}
d.RunningExe, _ = os.Readlink(filepath.Join("/proc", strconv.Itoa(d.PID), "exe"))
d.WantExe, _ = filepath.EvalSymlinks(p.KcpPath)
d.ExeChanged = d.RunningExe != "" && d.WantExe != "" && strings.TrimSuffix(d.RunningExe, " (deleted)") != d.WantExe
return d, true, nil
}
func RollDrifted(ctx context.Context, m hostsvc.Manager, p Params, o InstalledRoll) (SlotDrift, *InstalledRollReport, error) {
log := o.Log
if log == nil {
log = slog.Default()
}
d, ok, err := ActiveSlotDrift(ctx, m, p)
if err != nil || !ok || !d.Drifted() {
return d, nil, err
}
if d.ExeChanged {
from, err := kcpserver.ReadAsset(ctx, strings.TrimSuffix(d.RunningExe, " (deleted)"))
if err != nil {
return d, nil, fmt.Errorf("bootstrap: the running kcp %s of %s: %w", d.RunningExe, d.Unit, err)
}
to, err := kcpserver.ReadAsset(ctx, p.KcpPath)
if err != nil {
return d, nil, err
}
if _, err := kcpserver.CheckPair(from, to, o.Compat, o.PlannedOutage); err != nil {
return d, nil, fmt.Errorf("bootstrap: %s runs %s and the linked kcp is %s; not rolled: %w", d.Unit, from.Version, to.Version, err)
}
}
lf, err := os.OpenFile(p.RollLock(), os.O_CREATE|os.O_RDWR, 0o600)
if err != nil {
return d, nil, err
}
defer lf.Close()
if err := hostos.Lock(lf, hostos.LockExclusive|hostos.LockNonBlocking); err != nil {
return d, nil, fmt.Errorf("bootstrap: %s is held by another roll; %s keeps its old argv until that roll ends: %w", p.RollLock(), d.Unit, err)
}
log.Warn("the running kcp slot was started with another argv or kcp than this binary renders; rolling it",
"unit", d.Unit, "pid", d.PID, "argvChanged", d.ArgvChanged, "exeChanged", d.ExeChanged, "refused", d.Refused,
"running", strings.Join(d.Running, " "), "want", strings.Join(d.Want, " "))
rep, err := RollInstalled(ctx, m, p, o)
return d, &rep, err
}