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
package install
import (
"context"
"encoding/json"
"os"
"path/filepath"
"strings"
"github.com/johnandersen777/socialweb-computer-kcp/internal/bootstrap"
"github.com/johnandersen777/socialweb-computer-kcp/internal/bootstrap/table"
"github.com/johnandersen777/socialweb-computer-kcp/internal/hostcontract"
)
type Enabler interface {
Enable(ctx context.Context, units []string) error
}
func BootstrapApps(role hostcontract.Role, prefix string, handles []string) []table.App {
if role == hostcontract.RoleGateway {
g := table.Gateway()
g.Prefix = prefix
return []table.App{g}
}
var out []table.App
if role == hostcontract.RoleRequester {
for _, n := range handles {
r := table.Requester(n, RequesterKey(n))
r.Prefix = prefix
out = append(out, r)
}
return out
}
for _, h := range handles {
b := table.Bidder(h, HandleID(h))
b.Prefix = prefix
out = append(out, b)
}
return out
}
func BootableUnits(role hostcontract.Role, prefix string, handles []string) ([]string, error) {
var out []string
for _, a := range BootstrapApps(role, prefix, handles) {
entries, err := bootstrap.Bootable(a)
if err != nil {
return nil, err
}
for _, e := range entries {
out = append(out, e.Unit)
}
}
return out, nil
}
func (in *installer) bootstrapFiles(dir string) ([]string, bool, error) {
var names []string
changed := false
uid, gid := in.acct.UID, in.acct.GID
front := in.configString("kcp-front-address")
for _, a := range BootstrapApps(in.o.Role, in.o.UnitPrefix, in.instances()) {
p := bootstrap.InstallParams(a, in.o.StateDir)
frontURL := ""
if front != "" && a.Role == table.RoleGateway {
p.FrontAddress = front
if !strings.HasSuffix(front, ":0") {
frontURL = "https://" + front
}
}
units, err := bootstrap.Installed(p, frontURL)
if err != nil {
return nil, false, err
}
for _, d := range append(in.accountParents(p.Dir), p.Dir, p.Run, filepath.Join(p.Dir, "bin")) {
if err := in.ensureDir(d, 0o700, uid, gid, false); err != nil {
return nil, false, err
}
}
for _, u := range units {
full := filepath.Join(dir, u.Path)
if strings.Contains(u.Path, "/") {
if err := in.ensureDir(filepath.Dir(full), 0o755, uid, gid, false); err != nil {
return nil, false, err
}
}
c, err := in.ensureFile(full, u.File.Unit, 0o644, uid, gid)
if err != nil {
return nil, false, err
}
in.rec.addUnitFile(full)
if c {
in.noteWritten(u.Path)
}
changed = changed || c
names = append(names, u.Path)
if u.File.EnvPath == "" || u.File.Env == nil {
continue
}
if u.Entry.Kind == table.KindSlot {
if _, err := os.Stat(in.path(u.File.EnvPath)); err == nil {
continue
}
}
ec, err := in.ensureFile(u.File.EnvPath, u.File.Env, 0o600, uid, gid)
if err != nil {
return nil, false, err
}
changed = changed || ec
}
}
return names, changed, nil
}
func (in *installer) configString(key string) string {
b, err := os.ReadFile(in.path(filepath.Join(in.o.StateDir, "config.json")))
if err != nil {
return ""
}
cfg := map[string]any{}
if json.Unmarshal(b, &cfg) != nil {
return ""
}
s, _ := cfg[key].(string)
return s
}
func (in *installer) enableBootstrap(ctx context.Context, um UserManager) error {
units, err := BootableUnits(in.o.Role, in.o.UnitPrefix, in.instances())
if err != nil || len(units) == 0 {
return err
}
e, ok := um.(Enabler)
if !ok {
return nil
}
return in.change("enable "+strings.Join(units, " ")+" as "+in.o.Account+" (started at boot, before the core)", func() error {
return e.Enable(ctx, units)
})
}
func (in *installer) accountParents(dir string) []string {
var out []string
for d := filepath.Dir(dir); d != in.o.StateDir && within(d, in.o.StateDir); d = filepath.Dir(d) {
out = append([]string{d}, out...)
}
return out
}