Public Access
Add uname, /uname and scp (key or masked password) in the Shell
- 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:
@@ -360,10 +360,12 @@ void IrcSession::command(int b, const std::string& text, int64_t utc) {
|
||||
} else if (verb == "quit") {
|
||||
quit_ = true;
|
||||
send(IrcMessage::serialize("QUIT", {rest.empty() ? "roro9stack" : rest}));
|
||||
} else if (verb == "uname") { // shown here, not said to anyone
|
||||
info(b, unameString(), utc);
|
||||
} else if (verb == "raw" || verb == "quote") {
|
||||
if (!rest.empty()) send(rest);
|
||||
} else {
|
||||
info(b, "Unknown command /" + verb + " (try /join or /j, /part /msg /me /nick /topic /names /quit /raw)", utc);
|
||||
info(b, "Unknown command /" + verb + " (try /join or /j, /part /msg /me /nick /topic /names /uname /quit /raw)", utc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
#include "scp_args.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace roro::term {
|
||||
|
||||
namespace {
|
||||
std::vector<std::string> words(const std::string& text) {
|
||||
std::vector<std::string> out;
|
||||
for (size_t at = 0; at < text.size();) {
|
||||
size_t end = text.find(' ', at);
|
||||
if (end == std::string::npos) end = text.size();
|
||||
if (end > at) out.push_back(text.substr(at, end - at));
|
||||
at = end + 1;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// user@host:path, as opposed to a path on the card.
|
||||
bool isRemote(const std::string& w) {
|
||||
if (w.empty() || w[0] == '/') return false;
|
||||
size_t at = w.find('@'), colon = w.find(':');
|
||||
return at != std::string::npos && colon != std::string::npos && at < colon;
|
||||
}
|
||||
|
||||
std::string baseName(const std::string& path) {
|
||||
size_t slash = path.rfind('/');
|
||||
return slash == std::string::npos ? path : path.substr(slash + 1);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::string parseScp(const std::string& args, ScpArgs& out) {
|
||||
static const char* kUsage = "scp [-f] [-P port] <file on the card> user@host:path | scp [-f] [-P port] user@host:path <file on the card>";
|
||||
std::vector<std::string> w = words(args);
|
||||
long port = 0;
|
||||
bool replace = false;
|
||||
while (!w.empty() && w[0][0] == '-') {
|
||||
if (w[0] == "-f") {
|
||||
replace = true;
|
||||
w.erase(w.begin());
|
||||
} else if (w[0] == "-P" && w.size() >= 2) {
|
||||
port = 0;
|
||||
for (char c : w[1]) {
|
||||
if (c < '0' || c > '9' || port > 65535) return "a port from 1 to 65535";
|
||||
port = port * 10 + (c - '0');
|
||||
}
|
||||
if (port < 1 || port > 65535) return "a port from 1 to 65535";
|
||||
w.erase(w.begin(), w.begin() + 2);
|
||||
} else {
|
||||
return kUsage;
|
||||
}
|
||||
}
|
||||
if (w.size() != 2) return kUsage;
|
||||
|
||||
bool firstRemote = isRemote(w[0]), secondRemote = isRemote(w[1]);
|
||||
if (firstRemote == secondRemote) return firstRemote ? "one side has to be on the card, not both on servers" : kUsage;
|
||||
|
||||
ScpArgs a;
|
||||
a.upload = secondRemote;
|
||||
a.replace = replace;
|
||||
const std::string& remote = a.upload ? w[1] : w[0];
|
||||
std::string local = a.upload ? w[0] : w[1];
|
||||
size_t colon = remote.find(':');
|
||||
std::string why = parseSshTarget(remote.substr(0, colon), a.target);
|
||||
if (!why.empty()) return why;
|
||||
if (port) a.target.port = static_cast<uint16_t>(port);
|
||||
a.remote = remote.substr(colon + 1);
|
||||
if (a.remote.empty()) return "say where on " + a.target.host + ": user@host:path";
|
||||
if (local.empty() || local[0] != '/') return "a path on the card starts with a slash";
|
||||
if (local.back() == '/') {
|
||||
if (a.upload) return "that's a folder: scp copies one file";
|
||||
std::string name = baseName(a.remote);
|
||||
if (name.empty()) return "name the file to fetch";
|
||||
local += name;
|
||||
}
|
||||
a.local = local;
|
||||
out = a;
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace roro::term
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ssh_hosts.h"
|
||||
|
||||
namespace roro::term {
|
||||
|
||||
// What `scp` was asked: one file to the card from a server, or from the card to a server.
|
||||
// scp [-f] [-P port] /notes/a.txt user@host:path (upload)
|
||||
// scp [-f] [-P port] user@host:path /notes/a.txt (download; a destination ending in / gets the remote file's name)
|
||||
// The card's paths start with a slash; the server's are the server's own (relative ones start at the home folder).
|
||||
struct ScpArgs {
|
||||
bool upload = false;
|
||||
bool replace = false; // -f: a download may replace a file on the card
|
||||
SshTarget target;
|
||||
std::string local, remote;
|
||||
};
|
||||
|
||||
// "" or what is wrong with it.
|
||||
std::string parseScp(const std::string& args, ScpArgs& out);
|
||||
|
||||
} // namespace roro::term
|
||||
@@ -8,8 +8,26 @@
|
||||
#define RORO_VERSION "unknown"
|
||||
#endif
|
||||
|
||||
#if __has_include("sdkconfig.h")
|
||||
#include "sdkconfig.h"
|
||||
#endif
|
||||
|
||||
namespace roro {
|
||||
|
||||
const char* versionString() { return RORO_VERSION; }
|
||||
|
||||
const char* architectureString() {
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
return "xtensa-lx7 esp32s3";
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32)
|
||||
return "xtensa-lx6 esp32";
|
||||
#elif defined(CONFIG_IDF_TARGET)
|
||||
return CONFIG_IDF_TARGET;
|
||||
#else
|
||||
return "host";
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string unameString() { return std::string(kProductName) + " " + versionString() + " " + architectureString(); }
|
||||
|
||||
} // namespace roro
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace roro {
|
||||
|
||||
constexpr const char* kProductName = "roro9stack";
|
||||
@@ -9,4 +11,10 @@ constexpr const char* kProductName = "roro9stack";
|
||||
// that one file, and everything else comes from the build cache (issue #74).
|
||||
const char* versionString();
|
||||
|
||||
// The chip this firmware is built for: "xtensa-lx7 esp32s3". "host" in the native tests.
|
||||
const char* architectureString();
|
||||
|
||||
// What `uname` and IRC's /uname report: "roro9stack v0.22.0 xtensa-lx7 esp32s3".
|
||||
std::string unameString();
|
||||
|
||||
} // namespace roro
|
||||
|
||||
Reference in New Issue
Block a user