Add uname, /uname and scp (key or masked password) in the Shell
CI / build (pull_request) Failing after 1m59s
Site / build (pull_request) Successful in 9s

- uname in the console and /uname in IRC: the firmware, its version and the chip
- scp: one file between the card and a trusted server, over SSH, with the
  device's key or a password asked (masked) in the Shell
- shared libssh helpers in src/platform/libssh_util
- README, user guide, command reference and a how-to updated

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0162FokPdvY2KsS4NBfwyWPk
This commit is contained in:
2026-10-09 19:01:36 +02:00
co-authored by Claude Sonnet 5.5
parent e707a5f2d4
commit 18285c3212
25 changed files with 730 additions and 34 deletions
+26 -2
View File
@@ -14,6 +14,7 @@
#include "apps/shell_app.h"
#include "apps/ssh_app.h"
#include "services/net_tools.h"
#include "services/scp_service.h"
#include "services/ssh_service.h"
#include "services/vpn_service.h"
#include "services/web_share.h"
@@ -63,6 +64,7 @@
#include "storage_paths.h"
#include "ui/notifier.h"
#include "ui/screen.h"
#include "scp_args.h"
#include "version.h"
using namespace roro;
@@ -95,6 +97,8 @@ static VpnService* vpnService; // not in Safe Mode
static NetTools netTools; // ping, nslookup and the rest (issue #90)
static SshService* sshService; // not in Safe Mode (issue #2)
static SshApp* sshApp;
static ShellApp* shellApp;
static ScpService* scpService; // not in Safe Mode
static Notifier* notifier;
static LauncherApp launcher;
static AppManager* apps;
@@ -235,8 +239,13 @@ void setup() {
apps->registerApp({"notes", "Notes", false, new NotesApp(*fileOps, *storageService, *clockService, *power)});
sshService = new SshService(settings);
sshApp = new SshApp(*sshService, settings, *storageService);
scpService = new ScpService(settings, *storageService, *fileOps);
apps->registerApp({"ssh", "SSH", false, sshApp});
apps->registerApp({"shell", "Shell", false, new ShellApp(shellRun, shellProbe, shellList, shellCount, helpText(), *apps)});
shellApp = new ShellApp(shellRun, shellProbe, shellList, shellCount, helpText(), *apps);
apps->registerApp({"shell", "Shell", false, shellApp});
shellApp->asking = [] { return scpService->asking(); };
shellApp->answer = [](const std::string& password) { scpService->answer(password); };
shellApp->abort = [] { scpService->cancel(); };
// Leaving the foreground App makes it save: a note being typed, when the device is powered off.
power->beforePowerOff = []() { apps->home(); };
netTools.ntpServer = []() { return settings.getString(Setting::Ntp1); };
@@ -688,6 +697,8 @@ static const char* const kHelp =
"ping <host> [count] [size] | nslookup <name> [server] | port <host> <port> | traceroute <host> | cancel is it there, does its name resolve, is its port open, which way; one at a time\n"
"tls <host> [port] | ntp [server] a TLS handshake: who the certificate is for, by whom, until when, and whether this device trusts it; a time server's clock against this one\n"
"ifconfig | arp | netstat the interfaces (Wi-Fi and the VPN), their addresses, the default route and the DNS servers; the neighbours heard; what listens and what is connected\n"
"uname the firmware, its version and the chip it is built for\n"
"scp [-f] [-P port] <file on the card> user@host:path | scp [-f] [-P port] user@host:path <file on the card> one file over SSH, with this device's key or, in the Shell, a password, to a server the SSH App already trusts; -f replaces a file on the card; `cancel` stops it\n"
"ssh user@host[:port] | ssh status | ssh stop a terminal on another machine, in the SSH App; the password is asked there, never here\n"
"vpn status | vpn up [seconds] | vpn down | vpn import [path] | vpn forget | vpn auto on|off the WireGuard tunnel (Settings > VPN); import reads /vpn/wg0.conf; with seconds, it goes down by itself\n"
"debug status | debug off [seconds] the Debug Console over Wi-Fi (Settings > Debug Console); with seconds, it comes back\n"
@@ -771,6 +782,16 @@ static void sshCommand(const String& arg) {
}
}
// `scp ...`: see ScpService. It needs the key and the trusted server the SSH App sets up.
static void scpCommand(const String& arg) {
if (!scpService) return (void)console.println("scp: not available in Safe Mode");
term::ScpArgs args;
std::string why = term::parseScp(arg.c_str(), args);
if (!why.empty()) return (void)console.printf("scp: %s\n", why.c_str());
why = scpService->start(args, sshService && sshService->state() != SshService::State::Idle && sshService->state() != SshService::State::Ended);
if (!why.empty()) console.printf("scp: error %s\n", why.c_str());
}
// `vpn ...` (issue #8). Nothing here prints a key.
static void vpnCommand(const String& arg) {
if (!vpnService) return (void)console.println("vpn: not available in Safe Mode");
@@ -840,7 +861,7 @@ static void screenshotStep() {
// Commands that only touch what Safe Mode starts.
static bool safeModeCommand(const String& line) {
return line == "help" || line == "info" || line == "tasks" || line == "net" || line == "reboot" || line == "boot other" ||
return line == "help" || line == "uname" || line == "info" || line == "tasks" || line == "net" || line == "reboot" || line == "boot other" ||
line.startsWith("log level ") || line.startsWith("crash") || line.startsWith("coredump") ||
line == "wifi status" || line.startsWith("wifi add ") || line.startsWith("debug ");
}
@@ -905,6 +926,9 @@ static void runCommand(String line, bool fromSerial = false) {
}
if (netTools.command(line.c_str())) return;
if (line == "cancel") netTools.cancel(); // and a copy or a delete, below
if (line == "uname" || line == "uname -a") return (void)console.println(unameString().c_str());
if (line.startsWith("scp ")) return scpCommand(line.substring(4));
if (line == "cancel" && scpService) scpService->cancel();
if (line == "ssh" || line.startsWith("ssh ")) return sshCommand(line.length() > 4 ? line.substring(4) : String("status"));
if (line == "vpn" || line.startsWith("vpn ")) return vpnCommand(line.length() > 4 ? line.substring(4) : String("status"));
if (line.startsWith("debug ")) return debugCommand(line.substring(6), fromSerial);