Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Let an account be a bot

29a795b9d5b79cf5c8f2b7f3b4f6572f7cdff444 · Keenan Nemetz · 6d ago · parent e7aa0785

Let an account be a bot

An automation needs an identity: something to assign an issue to, mention in a
comment, and name in an approval and the audit log. An account already gives all
of that, so this adds only the invariants a bot needs and the word for it.

A bot is created with `vole admin account create --bot`, with the repository role
the admin decides it should have. It has no browser sign-in — `account invite` and
adding a passkey are refused — and it cannot hold the instance-admin role, because
Admin reaches every repository and every instance surface while a bot is a
repository automation. It is marked as a bot in the user directory and on its
profile.

Everything else is deliberately unchanged. A bot obeys the rules of its role: a
contribute bot pushes only its own contributor branch and its CI waits for a
maintainer to allow the tip; a maintain bot pushes shared branches, runs CI
immediately, and its approval counts. The kind is fixed at creation, like the name,
and a roster that predates it reads every account as a person.

This is the first of three slices of #165. The per-run credential that lets a
workflow act as a bot without a long-lived key in a repository secret is next; this
slice changes no push rule and no permission.

cmd/vole/account.go +19−6

@@ -16,7 +16,7 @@ import (
16 16 // the shortest useful summary in adminUsage; this is what `-h` expands.
17 17 const accountUsage = `usage: vole admin account <command> [options] [args]
18 18
19 create [--key <file|->] [--role <role>] <name>
19 create [--key <file|->] [--role <role>] [--bot] <name>
20 20 invite <name>
21 21 role <name> <none|read|contribute|maintain|admin>
22 22 credentials <name>
@@ -31,7 +31,7 @@ const accountUsage = `usage: vole admin account <command> [options] [args]
31 31 list
32 32 backup <path>`
33 33
34 const accountUsageCreate = "usage: vole admin account create [--key <file|->] [--role <none|read|contribute|maintain|admin>] <name>"
34 const accountUsageCreate = "usage: vole admin account create [--key <file|->] [--role <none|read|contribute|maintain|admin>] [--bot] <name>"
35 35
36 36 // readKeyInput reads one authorized_keys line from a file, or from stdin when
37 37 // src is empty or "-".
@@ -77,7 +77,7 @@ func cmdAccount(args []string) error {
77 77 sub := args[0]
78 78 f, err := parseFlags(args[1:], configFlag(),
79 79 flagSpec{long: "key", short: "k"}, flagSpec{long: "all", boolean: true},
80 flagSpec{long: "role"})
80 flagSpec{long: "role"}, flagSpec{long: "bot", boolean: true})
81 81 if err != nil {
82 82 return err
83 83 }
@@ -91,7 +91,7 @@ func cmdAccount(args []string) error {
91 91 return nil
92 92 }
93 93 accountFlags := map[string][]string{
94 "create": {"config", "key", "role"},
94 "create": {"config", "key", "role", "bot"},
95 95 "invite": {"config"},
96 96 "role": {"config"},
97 97 "credentials": {"config"},
@@ -138,13 +138,20 @@ func cmdAccount(args []string) error {
138 138 if err != nil {
139 139 return err
140 140 }
141 // A bot and a person are created the same way except for one thing: a bot
142 // has no browser sign-in, so it can never be given an enrollment link. The
143 // create functions have the same shape, so the kind picks which one.
144 create := st.Identity.CreateWithRole
145 if f.has("bot") {
146 create = st.Identity.CreateBot
147 }
141 148 if !f.given("key") && !stdinIsTerminal() {
142 149 key, err := readKeyInput("")
143 150 if err != nil {
144 151 return err
145 152 }
146 153 if strings.TrimSpace(key) != "" {
147 a, err := st.Identity.CreateWithRole(f.rest[0], role, key, actor)
154 a, err := create(f.rest[0], role, key, actor)
148 155 if err != nil {
149 156 return err
150 157 }
@@ -156,6 +163,12 @@ func cmdAccount(args []string) error {
156 163 }
157 164 }
158 165 if !f.given("key") {
166 if f.has("bot") {
167 // An enrollment link is the only way a keyless account gets in, and
168 // a bot cannot use one. Refuse rather than create an account nobody
169 // can act as.
170 return usageErr{msg: "a bot account has no browser sign-in, so it needs a key: pass --key <file|->"}
171 }
159 172 // No key means a browser-only account: create it with a one-time
160 173 // enrollment link so the name is never claimed by an account that has
161 174 // no way in.
@@ -171,7 +184,7 @@ func cmdAccount(args []string) error {
171 184 if err != nil {
172 185 return err
173 186 }
174 a, err := st.Identity.CreateWithRole(f.rest[0], role, key, actor)
187 a, err := create(f.rest[0], role, key, actor)
175 188 if err != nil {
176 189 return err
177 190 }