Differences From
Artifact [54eca1a845]:
18 18 local subcmds = {
19 19 }
20 20
21 21 local ctlcmds = {
22 22 { 'start', 'start a new instance of the server' };
23 23 { 'stop', 'stop a running instance' };
24 24 { 'attach', 'capture log output from a running instance' };
25 - { 'db init <domain>', 'initialize backend databases (or a single specified database) with the necessary schema and structures for the given FQDN' };
26 - { 'db vacuum', 'delete old remote content from the database' };
27 - { 'db extract (<artifact>|<post>/<attachment number>)', 'extracts an attachment artifact from the database and prints it to standard out' };
28 - { 'db excise <artifact>', 'extracts an attachment artifact from the database and prints it to standard out' };
29 - { 'db obliterate', 'completely purge all parsav-related content and structure from the database, destroying all user content (requires confirmation)' };
30 - { 'db insert', 'reads a file from standard in and inserts it into the attachment database, printing the resulting ID' };
25 + { 'db', 'set up and manage the database' };
26 + { 'user', 'manage users, privileges, and credentials'};
31 27 { 'mkroot <handle>', 'establish a new root user with the given handle' };
32 - { 'user <handle> auth <type> new', '(where applicable, managed auth only) create a new authentication token of the given type for a user' };
33 - { 'user <handle> auth <type> reset', '(where applicable, managed auth only) delete all of a user\'s authentication tokens of the given type and issue a new one' };
34 - { 'user <handle> auth (<type>|all) purge', 'delete all credentials that would allow this user to log in (where possible)' };
35 - { 'user <handle> (grant|revoke) (<priv>|all)', 'grant or revoke a specific power to or from a user' };
36 - { 'user <handle> emasculate', 'strip all administrative powers from a user' };
37 - { 'user <handle> suspend [<timespec>]', '(e.g. \27[1muser jokester suspend 5d 6h 7m 3s\27[m to suspend "jokester" for five days, six hours, seven minutes, and three seconds) suspend a user'};
38 28 { 'actor <xid> purge-all', 'remove all traces of a user from the database (except local user credentials -- use \27[1mauth all purge\27[m to prevent a user from accessing the instance)' };
39 29 { 'actor <xid> create', 'instantiate a new actor' };
40 30 { 'actor <xid> bestow <epithet>', 'bestow an epithet upon an actor' };
41 - { 'conf set <setting> <value>', 'add or a change a server configuration parameter to the database' };
42 - { 'conf get <setting>', 'report the value of a server setting' };
43 - { 'conf reset <setting>', 'reset a server setting to its default value' };
44 - { 'conf refresh', 'instruct an instance to refresh its configuration cache' };
45 - { 'conf chsec', 'reset the server secret, invalidating all authentication cookies' };
31 + { 'conf', 'manage the server configuration'};
46 32 { 'serv dl', 'initiate an update cycle over foreign actors' };
47 33 { 'tl', 'print the current local timeline to standard out' };
48 34 { 'be pgsql setup-auth (managed|unmanaged)', '(PGSQL backends) select the authentication strategy to use' };
49 35 }
50 36
51 -local ctlcmdhelp = 'commands:\n'
52 -for _, v in ipairs(ctlcmds) do
53 - ctlcmdhelp = ctlcmdhelp .. string.format (
54 - ' \27[1m%s\27[m: %s\n', v[1]:gsub('(<%w+>)','\27[36m%1\27[;1m'), v[2]
55 - )
37 +local cmdhelp = function(tbl)
38 + local str = '\ncommands:\n'
39 + for _, v in ipairs(tbl) do
40 + str = str .. string.format (
41 + ' \27[1m%s\27[m: %s\n',
42 + v[1]
43 + :gsub('([%(%)|%[%]])', '\27[34m%1\27[;1m')
44 + :gsub('(<.->)','\27[36m%1\27[;1m'),
45 + v[2]
46 + )
47 + end
48 + return str
56 49 end
57 50
58 51 local struct idelegate {
59 52 all: bool
60 53 src: &lib.store.source
61 54 srv: &lib.srv.overlord
62 55 }
................................................................................
100 93 end
101 94 lib.dbg('assigning temporary password')
102 95 dlg:auth_create_pw(uid, reset, pstr {
103 96 ptr = [rawstring](tmppw), ct = 32
104 97 })
105 98 end
106 99
100 +local emp = lib.ipc.global_emperor
107 101 local terra entry_mgtool(argc: int, argv: &rawstring): int
108 102 if argc < 1 then lib.bail('bad invocation!') end
109 103
110 - lib.noise_init(2)
104 + lib.noise.init(2)
111 105 [lib.init]
112 106
113 107 var srv: lib.srv.overlord
114 108 var dlg = idelegate { srv = &srv, src = nil }
115 109
116 110 var mode: ctloptions
117 111 mode:parse(argc,argv) defer mode:free()
118 112 if mode.version then version() return 0 end
119 113 if mode.help then
120 - [ lib.emit(false, 1, 'usage: ', `argv[0], ' ', ctloptions.helptxt.flags, ' <cmd> [<args>…]', ctloptions.helptxt.opts, ctlcmdhelp) ]
114 + [ lib.emit(false, 1, 'usage: ', `argv[0], ' ', ctloptions.helptxt.flags, ' <cmd> [<args>…]', ctloptions.helptxt.opts, cmdhelp(ctlcmds)) ]
121 115 return 0
122 116 end
117 + if mode.quiet then lib.noise.level = 0 end
123 118 var cnf: rawstring
124 119 if mode.backend_file ~= nil
125 120 then cnf = @mode.backend_file
126 121 else cnf = lib.proc.getenv('parsav_backend_file')
127 122 end
128 - if cnf == nil then cnf = "backend.conf" end
123 + if cnf == nil then cnf = [config.prefix_conf .. "/backend.conf"] end
129 124 if mode.all then dlg.all = true else
130 125 -- iterate through and pick the right backend
131 126 end
132 127
133 128 if mode.arglist.ct == 0 then lib.bail('no command') return 1 end
129 +
130 + if lib.str.cmp(mode.arglist(0),'start') ~= 0 then
131 + -- hack to save us some pain around forking
132 + emp = lib.ipc.emperor.mk(false)
133 + end
134 + defer emp:release()
135 +
134 136 if lib.str.cmp(mode.arglist(0),'attach') == 0 then
135 137 elseif lib.str.cmp(mode.arglist(0),'start') == 0 then
138 + mode.arglist(0) = "-";
139 + var chargv = mode.arglist.ptr
140 + var lsr = lib.ipc.listener.mk()
141 + var chpid = lib.proc.fork()
142 + if chpid == 0 then
143 + lsr:release()
144 + --lib.proc.daemonize(1,0)
145 + lib.io.close(0) lib.io.close(1) lib.io.close(2)
146 + lib.proc.exec([config.prefix_bin .. '/parsavd'], chargv)
147 + lib.proc.execp([config.prefix_bin .. '/parsavd'], chargv)
148 + lib.ipc.notify_parent(lib.ipc.signals.state_fail_find)
149 + lib.bail('cannot find parsav program')
150 + else
151 + lib.report('starting parsav daemon')
152 + while true do
153 + var sig = lsr:block()
154 + if sig.system then lib.dbg('got system signal') end
155 + if sig.from == chpid then
156 + if sig.system and sig.sig == lib.ipc.signals.sys_child then
157 + lib.warn('parsavd failed to start')
158 + return 0
159 + elseif sig.sig == lib.ipc.signals.notify_state_change and
160 + sig.event == lib.ipc.signals.state_success then
161 + lib.report('parsavd successfully started')
162 + return 0
163 + elseif sig.sig == lib.ipc.signals.notify_state_change and
164 + sig.event == lib.ipc.signals.state_fail_find then
165 + lib.bail('parsavd could not be found')
166 + else lib.warn('got unrecognized signal, ignoring')
167 + end
168 + end
169 + end
170 + lsr:release() -- just because i feel distinctly uncomfortable leaving it out
171 + end
136 172 elseif lib.str.cmp(mode.arglist(0),'stop') == 0 then
173 + var acks = emp:mallack()
174 + emp:decree(0,nil, lib.ipc.cmd.stop, 0, &acks(0)) -- TODO targeting
175 + for i=0,acks.ct do
176 + if acks(i).success then
177 + lib.io.fmt('instance %llu successfully stepped down\n', acks(i).clid)
178 + else
179 + lib.io.fmt('instance %llu reports failure to halt\n', acks(i).clid)
180 + end
181 + end
182 + acks:free()
137 183 else
138 184 if lib.str.cmp(mode.arglist(0),'db') == 0 then
139 185 var dbmode: pbasic dbmode:parse(mode.arglist.ct, &mode.arglist(0))
140 186 if dbmode.help then
141 - [ lib.emit(false, 1, 'usage: ', `argv[0], ' db ', dbmode.type.helptxt.flags, ' <cmd> [<args>…]', dbmode.type.helptxt.opts) ]
187 + [ lib.emit(false, 1, 'usage: ', `argv[0], ' db ', dbmode.type.helptxt.flags, ' <cmd> [<args>…]', dbmode.type.helptxt.opts, cmdhelp {
188 + { 'db init <domain>', 'initialize backend databases (or a single specified database) with the necessary schema and structures for the given FQDN' };
189 + { 'db vacuum', 'delete old remote content from the database' };
190 + { 'db extract (<artifact>|<post>/<attachment number>)', 'extracts an attachment artifact from the database and prints it to standard out' };
191 + { 'db excise (<artifact>|<post>/<attachment number>)', 'removes an undesirable artifact from the database' };
192 + { 'db obliterate [<confirmation code>]', 'completely purge all parsav-related content and structure from the database, destroying all user content (requires confirmation)' };
193 + { 'db insert', 'reads a file from standard in and inserts it into the attachment database, printing the resulting ID' };
194 + }) ]
142 195 return 1
143 196 end
144 197 if dbmode.arglist.ct < 1 then goto cmderr end
145 198
146 199 srv:setup(cnf)
147 200 if lib.str.cmp(dbmode.arglist(0),'init') == 0 and dbmode.arglist.ct == 2 then
148 201 lib.report('initializing new database structure for domain ', dbmode.arglist(1))
................................................................................
157 210 var confirmstrs = array(
158 211 'alpha', 'beta', 'gamma', 'delta', 'epsilon', 'eta', 'nu', 'kappa'
159 212 )
160 213 var cfmstr: int8[64] cfmstr[0] = 0
161 214 var tdx = lib.osclock.time(nil) / 60
162 215 for i=0,3 do
163 216 if i ~= 0 then lib.str.cat(&cfmstr[0], '-') end
164 - lib.str.cat(&cfmstr[0], confirmstrs[(tdx + 49*i) % [confirmstrs.type.N]])
217 + lib.str.cat(&cfmstr[0], confirmstrs[(tdx ^ (173*i)) % [confirmstrs.type.N]])
165 218 end
166 219
167 220 if dbmode.arglist.ct == 1 then
168 221 lib.bail('you are attempting to completely obliterate all data! make sure you have selected your target correctly. if you really want to do this, pass the confirmation string ', &cfmstr[0])
169 222 elseif dbmode.arglist.ct == 2 then
170 223 if lib.str.cmp(dbmode.arglist(1), cfmstr) == 0 then
171 224 lib.warn('completely obliterating all data!')
................................................................................
182 235 srv:conprep(lib.store.prepmode.conf)
183 236 var cfmode: lib.cmdparse {
184 237 help = {'h','display this list'};
185 238 no_notify = {'n', "don't instruct the server to refresh its configuration cache after making changes; useful for \"transactional\" configuration changes."};
186 239 }
187 240 cfmode:parse(mode.arglist.ct, &mode.arglist(0))
188 241 if cfmode.help then
189 - [ lib.emit(false, 1, 'usage: ', `argv[0], ' conf ', cfmode.type.helptxt.flags, ' <cmd> [<args>…]', cfmode.type.helptxt.opts) ]
242 + [ lib.emit(false, 1, 'usage: ', `argv[0], ' conf ', cfmode.type.helptxt.flags, ' <cmd> [<args>…]', cfmode.type.helptxt.opts, cmdhelp {
243 + { 'conf set <setting> <value>', 'add or a change a server configuration parameter to the database' };
244 + { 'conf get <setting>', 'report the value of a server setting' };
245 + { 'conf reset <setting>', 'reset a server setting to its default value' };
246 + { 'conf refresh', 'instruct an instance to refresh its configuration cache' };
247 + { 'conf chsec', 'reset the server secret, invalidating all authentication cookies' };
248 + }) ]
190 249 return 1
191 250 end
192 251 if cfmode.arglist.ct < 1 then goto cmderr end
193 252
194 253 if cfmode.arglist.ct == 1 then
195 254 if lib.str.cmp(cfmode.arglist(0),'chsec') == 0 then
196 255 var sec: int8[65] gensec(&sec[0])
................................................................................
245 304 pwset(dlg, &tmppw, ruid, false)
246 305 lib.report('temporary root pw: ', {&tmppw[0], 32})
247 306 end
248 307 else goto cmderr end
249 308 elseif lib.str.cmp(mode.arglist(0),'user') == 0 then
250 309 var umode: pbasic umode:parse(mode.arglist.ct, &mode.arglist(0))
251 310 if umode.help then
252 - [ lib.emit(false, 1, 'usage: ', `argv[0], ' user ', umode.type.helptxt.flags, ' <handle> <cmd> [<args>…]', umode.type.helptxt.opts) ]
311 + [ lib.emit(false, 1, 'usage: ', `argv[0], ' user ', umode.type.helptxt.flags, ' <handle> <cmd> [<args>…]', umode.type.helptxt.opts, cmdhelp {
312 + { 'user <handle> auth <type> new', '(where applicable, managed auth only) create a new authentication token of the given type for a user' };
313 + { 'user <handle> auth <type> reset', '(where applicable, managed auth only) delete all of a user\'s authentication tokens of the given type and issue a new one' };
314 + { 'user <handle> auth (<type>|all) purge', 'delete all credentials that would allow this user to log in (where possible)' };
315 + { 'user <handle> (grant|revoke) (<priv>|all)', 'grant or revoke a specific power to or from a user' };
316 + { 'user <handle> emasculate', 'strip all administrative powers from a user' };
317 + { 'user <handle> forgive', 'restore all default powers to a user' };
318 + { 'user <handle> suspend [<timespec>]', '(e.g. \27[1muser jokester suspend 5d 6h 7m 3s\27[m to suspend "jokester" for five days, six hours, seven minutes, and three seconds) suspend a user'};
319 + }) ]
253 320 return 1
254 321 end
255 322 if umode.arglist.ct >= 3 then
256 323 var grant = lib.str.cmp(umode.arglist(1),'grant') == 0
257 324 var handle = umode.arglist(0)
258 325 var usr = dlg:actor_fetch_xid(pstr {ptr=handle, ct=lib.str.sz(handle)})
259 - if not usr then lib.bail('unknown handle') end
260 326 if grant or lib.str.cmp(umode.arglist(1),'revoke') == 0 then
327 + if not usr then lib.bail('unknown handle') end
261 328 var newprivs = usr.ptr.rights.powers
262 329 var map = array([lib.store.privmap])
263 330 if umode.arglist.ct == 3 and lib.str.cmp(umode.arglist(2),'all') == 0 then
264 331 if grant
265 332 then newprivs:fill()
266 333 else newprivs:clear()
267 334 end
................................................................................
285 352 end
286 353
287 354 usr.ptr.rights.powers = newprivs
288 355 dlg:actor_save_privs(usr.ptr)
289 356 elseif lib.str.cmp(umode.arglist(1),'auth') == 0 and umode.arglist.ct == 4 then
290 357 var reset = lib.str.cmp(umode.arglist(3),'reset') == 0
291 358 if reset or lib.str.cmp(umode.arglist(3),'new') == 0 then
359 + -- FIXME enable resetting pws for users who have
360 + -- not logged in yet
361 + if not usr then lib.bail('unknown handle') end
292 362 if lib.str.cmp(umode.arglist(2),'pw') == 0 then
293 363 var tmppw: int8[33]
294 364 pwset(dlg, &tmppw, usr.ptr.id, reset)
295 365 lib.report('new temporary password for ',usr.ptr.handle,': ', {&tmppw[0], 32})
296 366 else lib.bail('unknown credential type') end
297 367 elseif lib.str.cmp(umode.arglist(3),'purge') == 0 then
368 + var uid: uint64 = 0
369 + if usr:ref() then uid = usr(0).id end
370 + if lib.str.cmp(umode.arglist(2),'pw') == 0 then
371 + dlg:auth_purge_pw(uid, handle)
372 + elseif lib.str.cmp(umode.arglist(2),'otp') == 0 then
373 + dlg:auth_purge_otp(uid, handle)
374 + elseif lib.str.cmp(umode.arglist(2),'trust') == 0 then
375 + dlg:auth_purge_trust(uid, handle)
376 + else lib.bail('unknown credential type') end
298 377 else goto cmderr end
299 378 else goto cmderr end
300 379 else goto cmderr end
301 380 elseif lib.str.cmp(mode.arglist(0),'actor') == 0 then
302 381 elseif lib.str.cmp(mode.arglist(0),'tl') == 0 then
303 382 elseif lib.str.cmp(mode.arglist(0),'serv') == 0 then
304 383 else goto cmderr end