210 lines
6.2 KiB
Lua
210 lines
6.2 KiB
Lua
local createCloud = require('/apis/libcloud');
|
|
local createVersion = require('/apis/libversion');
|
|
local createEventLoop = require('/apis/eventloop');
|
|
|
|
local URL_SETTING = 'cloud.url';
|
|
local PASSWORD_SETTING = 'cloud.password';
|
|
local RECONNECT_EVENT = 'trapos_cloud_reconnect';
|
|
local VERIFY_TIMEOUT = 12; -- one hello attempt; daemon helloTimeout is 10s
|
|
|
|
local cloudApi = createCloud();
|
|
|
|
local rawArgs = table.pack(...);
|
|
local command = rawArgs[1];
|
|
|
|
local function isBlank(value)
|
|
return type(value) ~= 'string' or value == '';
|
|
end
|
|
|
|
local function printUsage()
|
|
print('cloud usage:');
|
|
print();
|
|
print(' cloud status');
|
|
print(' cloud login [--force|-f] [password]');
|
|
print(' cloud --version');
|
|
print(' cloud --help');
|
|
print();
|
|
print('settings required:');
|
|
print(' cloud.url (gateway origin, e.g. ws://host:4444)');
|
|
print('settings optional:');
|
|
print(' cloud.password (shared secret for the hello handshake)');
|
|
end
|
|
|
|
local function readPassword()
|
|
write('Password: ');
|
|
if type(read) == 'function' then return read('*'); end
|
|
if io and type(io.read) == 'function' then return io.read(); end
|
|
return '';
|
|
end
|
|
|
|
local function parseLoginArgs(args)
|
|
local force = false;
|
|
local password = nil;
|
|
|
|
for i = 2, args.n do
|
|
local arg = args[i];
|
|
if arg == '--force' or arg == '-f' then
|
|
force = true;
|
|
elseif arg and string.sub(arg, 1, 1) == '-' then
|
|
return nil, 'unknown option: ' .. arg;
|
|
elseif not password then
|
|
password = arg;
|
|
else
|
|
return nil, 'unexpected argument: ' .. tostring(arg);
|
|
end
|
|
end
|
|
|
|
return { force = force, password = password }, nil;
|
|
end
|
|
|
|
if command == '--version' or command == '-version' or command == 'version' or command == '-v' then
|
|
print('v' .. createVersion().forSelf());
|
|
return;
|
|
end
|
|
|
|
if command == '--help' or command == '-help' or command == 'help' or command == '-h' then
|
|
printUsage();
|
|
return;
|
|
end
|
|
|
|
if command == 'status' then
|
|
local url = settings.get(URL_SETTING);
|
|
if isBlank(url) then
|
|
print('set cloud.url first');
|
|
return;
|
|
end
|
|
|
|
local ok, result = cloudApi.request('probe_server');
|
|
if not ok then
|
|
local code = (type(result) == 'table' and result.code) or 'internal';
|
|
local message = (type(result) == 'table' and result.message) or '';
|
|
print('cloud status failed: ' .. tostring(code) .. ' ' .. tostring(message));
|
|
return;
|
|
end
|
|
|
|
print('serverOk: ' .. tostring(result.serverOk));
|
|
print('opencodeOk: ' .. tostring(result.opencodeOk));
|
|
if not result.opencodeOk and result.opencodeDetail then
|
|
print('opencode: ' .. tostring(result.opencodeDetail));
|
|
end
|
|
return;
|
|
end
|
|
|
|
if command == 'login' then
|
|
local opts, parseError = parseLoginArgs(rawArgs);
|
|
if parseError then
|
|
print(parseError);
|
|
printUsage();
|
|
return;
|
|
end
|
|
|
|
if not opts.force and not isBlank(settings.get(PASSWORD_SETTING)) then
|
|
print('cloud.password is already set; use cloud login --force to replace it');
|
|
return;
|
|
end
|
|
|
|
if isBlank(settings.get(URL_SETTING)) then
|
|
print('set cloud.url first');
|
|
return;
|
|
end
|
|
|
|
-- Ask the daemon to reconnect with `candidate` (never persisted) and wait for the handshake
|
|
-- verdict carrying our token. Returns 'ok' | 'unauthorized' | 'timeout' | 'terminate'. Other
|
|
-- daemon verdicts (tokenless or for a different token) are ignored.
|
|
local function verifyPassword(candidate)
|
|
local token = cloudApi.uuid();
|
|
local result = nil;
|
|
local el = createEventLoop();
|
|
|
|
local function finish(value)
|
|
if result ~= nil then return; end
|
|
result = value;
|
|
el.stopLoop();
|
|
end
|
|
|
|
el.register('trapos_cloud_connected', function(_traposId, eventToken)
|
|
if eventToken == token then finish('ok'); end
|
|
end);
|
|
el.register('trapos_cloud_unauthorized', function(_traposId, _err, eventToken)
|
|
if eventToken == token then finish('unauthorized'); end
|
|
end);
|
|
-- terminate stops the default loop on its own; just record it for the caller.
|
|
el.register('terminate', function() result = 'terminate'; end);
|
|
el.setTimeout(function() finish('timeout'); end, VERIFY_TIMEOUT);
|
|
|
|
os.queueEvent(RECONNECT_EVENT, 'login-verify', candidate, token);
|
|
el.runLoop();
|
|
return result;
|
|
end
|
|
|
|
local function restore()
|
|
os.queueEvent(RECONNECT_EVENT, 'login-restore');
|
|
end
|
|
|
|
-- Persist only after a verified password. Roll the live setting back if the save fails so the
|
|
-- daemon (which reads live settings) never keeps an unsaved secret. No reconnect on success:
|
|
-- the live socket already authenticated with this secret.
|
|
local function savePassword(password)
|
|
local old = settings.get(PASSWORD_SETTING);
|
|
settings.set(PASSWORD_SETTING, password);
|
|
if not settings.save() then
|
|
if old ~= nil then
|
|
settings.set(PASSWORD_SETTING, old);
|
|
else
|
|
settings.unset(PASSWORD_SETTING);
|
|
end
|
|
restore();
|
|
error('cloud login failed: could not save settings', 0);
|
|
end
|
|
print('logged in');
|
|
end
|
|
|
|
if opts.password == nil then
|
|
-- Interactive: re-prompt on a wrong password; ctrl+t cancels.
|
|
while true do
|
|
local candidate = readPassword();
|
|
if isBlank(candidate) then
|
|
print('password cannot be empty');
|
|
else
|
|
local result = verifyPassword(candidate);
|
|
if result == 'ok' then
|
|
savePassword(candidate);
|
|
return;
|
|
elseif result == 'unauthorized' then
|
|
restore();
|
|
print('invalid password, try again (ctrl+t to cancel)');
|
|
elseif result == 'timeout' then
|
|
restore();
|
|
print('cloud login failed: could not reach gateway');
|
|
return;
|
|
else -- terminate
|
|
restore();
|
|
error('Terminated', 0);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Non-interactive: a wrong password fails with a non-zero exit.
|
|
if isBlank(opts.password) then
|
|
error('cloud login failed: password cannot be empty', 0);
|
|
end
|
|
|
|
local result = verifyPassword(opts.password);
|
|
if result == 'ok' then
|
|
savePassword(opts.password);
|
|
return;
|
|
elseif result == 'unauthorized' then
|
|
restore();
|
|
error('cloud login failed: invalid password', 0);
|
|
elseif result == 'timeout' then
|
|
restore();
|
|
error('cloud login failed: could not reach gateway', 0);
|
|
else -- terminate
|
|
restore();
|
|
error('Terminated', 0);
|
|
end
|
|
end
|
|
|
|
printUsage();
|