v0.0.76 - Use sudo-prefixed privileged commands in setup wizard install flow with password prompting and remove root-shell handoff

This commit is contained in:
Your Name
2026-03-18 07:07:30 -04:00
parent 9a0e90dff0
commit 6b6e25c3f4
3 changed files with 157 additions and 131 deletions

View File

@@ -55,11 +55,11 @@ Skills compose by adoption-list order (`10123`) and trigger tags carry runtime e
Didactyl will support local inference, which is very privacy preserving. Remote inference does however have it's advantages, and in those cases Didactyl supports using Bitcoin Lightning and eCash inference providers.
## Current Status — v0.0.75
## Current Status — v0.0.76
**Active build — this project is barely working. Experiment at your own risk.**
> Last release update: v0.0.75Add rsync deploy script and wizard root-shell handoff for service install path
> Last release update: v0.0.76Use sudo-prefixed privileged commands in setup wizard install flow with password prompting and remove root-shell handoff
- Connects to configured relays with auto-reconnect and relay state transition logging
- Publishes configured startup events per relay as each relay becomes connected

View File

@@ -12,8 +12,8 @@
// Using DIDACTYL_ prefix to avoid conflicts with nostr_core_lib VERSION macros
#define DIDACTYL_VERSION_MAJOR 0
#define DIDACTYL_VERSION_MINOR 0
#define DIDACTYL_VERSION_PATCH 75
#define DIDACTYL_VERSION "v0.0.75"
#define DIDACTYL_VERSION_PATCH 76
#define DIDACTYL_VERSION "v0.0.76"
// Agent metadata
#define DIDACTYL_NAME "Didactyl"

View File

@@ -1235,12 +1235,6 @@ static int prompt_relay_configuration(didactyl_config_t* cfg) {
}
}
static int ensure_dir_exists_local(const char* path) {
if (!path || path[0] == '\0') return -1;
if (mkdir(path, 0755) == 0 || errno == EEXIST) return 0;
return -1;
}
static int run_command_local(char* const argv[]) {
pid_t pid = fork();
if (pid < 0) return -1;
@@ -1255,31 +1249,64 @@ static int run_command_local(char* const argv[]) {
return 0;
}
static int copy_file_local(const char* src, const char* dst) {
if (!src || !dst || src[0] == '\0' || dst[0] == '\0') return -1;
static int run_privileged_command_local(char* const argv[]) {
if (!argv || !argv[0]) return -1;
FILE* in = fopen(src, "rb");
if (!in) return -1;
if (geteuid() == 0) {
return run_command_local(argv);
}
FILE* out = fopen(dst, "wb");
if (!out) {
fclose(in);
char* sudo_argv[64] = {0};
sudo_argv[0] = "sudo";
int i = 0;
for (; argv[i] && i < 62; i++) {
sudo_argv[i + 1] = argv[i];
}
if (argv[i] != NULL) {
return -1;
}
char buf[16384];
size_t n = 0;
while ((n = fread(buf, 1, sizeof(buf), in)) > 0) {
if (fwrite(buf, 1, n, out) != n) {
fclose(in);
fclose(out);
return -1;
}
sudo_argv[i + 1] = NULL;
return run_command_local(sudo_argv);
}
static int write_file_privileged_local(const char* path, const char* content, unsigned int mode) {
if (!path || path[0] == '\0' || !content) return -1;
char tmp_template[] = "/tmp/didactyl-wizard-XXXXXX";
int fd = mkstemp(tmp_template);
if (fd < 0) {
return -1;
}
fclose(in);
fclose(out);
return 0;
FILE* fp = fdopen(fd, "w");
if (!fp) {
close(fd);
unlink(tmp_template);
return -1;
}
size_t len = strlen(content);
int ok = (fwrite(content, 1, len, fp) == len) ? 1 : 0;
if (fclose(fp) != 0) {
ok = 0;
}
if (!ok) {
unlink(tmp_template);
return -1;
}
char mode_buf[16] = {0};
snprintf(mode_buf, sizeof(mode_buf), "%04o", mode & 07777U);
char* install_argv[] = {"install", "-m", mode_buf, tmp_template, (char*)path, NULL};
int rc = run_privileged_command_local(install_argv);
unlink(tmp_template);
return rc;
}
static void sanitize_service_user_from_name(const char* name, char* out, size_t out_size) {
@@ -1332,37 +1359,44 @@ static int write_sudoers_file(const char* service_user) {
char sudoers_path[PATH_MAX] = {0};
snprintf(sudoers_path, sizeof(sudoers_path), "/etc/sudoers.d/%s", service_user);
FILE* fp = fopen(sudoers_path, "w");
if (!fp) {
fprintf(stderr, "%sFailed to write sudoers file at %s.%s\n", ANSI_RED, sudoers_path, ANSI_RESET);
char content[4096] = {0};
int n = snprintf(content,
sizeof(content),
"# Didactyl agent — scoped system maintenance privileges\n"
"# Generated by didactyl setup wizard\n"
"\n"
"# Service management\n"
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl status *\n"
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart *\n"
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl start *\n"
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl stop *\n"
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl enable *\n"
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl disable *\n"
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl is-active *\n"
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl is-enabled *\n"
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl list-units *\n"
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl daemon-reload\n"
"\n"
"# Log inspection\n"
"%s ALL=(ALL) NOPASSWD: /usr/bin/journalctl *\n",
service_user,
service_user,
service_user,
service_user,
service_user,
service_user,
service_user,
service_user,
service_user,
service_user,
service_user);
if (n < 0 || (size_t)n >= sizeof(content)) {
fprintf(stderr, "%sFailed to build sudoers content for %s.%s\n", ANSI_RED, service_user, ANSI_RESET);
return -1;
}
fprintf(fp,
"# Didactyl agent — scoped system maintenance privileges\n"
"# Generated by didactyl setup wizard\n"
"\n"
"# Service management\n"
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl status *\n"
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart *\n"
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl start *\n"
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl stop *\n"
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl enable *\n"
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl disable *\n"
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl is-active *\n"
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl is-enabled *\n"
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl list-units *\n"
"%s ALL=(ALL) NOPASSWD: /usr/bin/systemctl daemon-reload\n"
"\n"
"# Log inspection\n"
"%s ALL=(ALL) NOPASSWD: /usr/bin/journalctl *\n",
service_user, service_user, service_user, service_user,
service_user, service_user, service_user, service_user,
service_user, service_user, service_user);
fclose(fp);
if (chmod(sudoers_path, 0440) != 0) {
fprintf(stderr, "%sFailed to chmod sudoers file at %s.%s\n", ANSI_RED, sudoers_path, ANSI_RESET);
if (write_file_privileged_local(sudoers_path, content, 0440U) != 0) {
fprintf(stderr, "%sFailed to write sudoers file at %s.%s\n", ANSI_RED, sudoers_path, ANSI_RESET);
return -1;
}
@@ -1373,25 +1407,12 @@ static int install_system_service_with_dedicated_user(const didactyl_config_t* c
if (!cfg || cfg->keys.nsec[0] == '\0') return -1;
if (geteuid() != 0) {
fprintf(stderr, "%sSystem install requires root.%s\n", ANSI_RED, ANSI_RESET);
fprintf(stderr, " You can open a root shell now and then re-run this install step.\n");
print_option('o', "pen root shell now (sudo -i)");
print_option('r', "eturn without installing");
wizard_option_t root_opts[] = {{'o', ""}, {'r', ""}};
char root_choice = read_menu_choice(root_opts, 2);
if (root_choice == 'o') {
char* sudo_argv[] = {"sudo", "-i", NULL};
if (run_command_local(sudo_argv) != 0) {
fprintf(stderr, "%sFailed to open root shell via sudo.%s\n", ANSI_RED, ANSI_RESET);
} else {
fprintf(stderr, "%sReturned from root shell.%s\n", ANSI_YELLOW, ANSI_RESET);
}
fprintf(stderr,
" Re-run didactyl as root and choose install again to complete system service setup.\n");
fprintf(stderr, " Requesting sudo authentication for system install...\n");
char* sudo_validate_argv[] = {"sudo", "-v", NULL};
if (run_command_local(sudo_validate_argv) != 0) {
fprintf(stderr, "%sSudo authentication failed. System install canceled.%s\n", ANSI_RED, ANSI_RESET);
return -1;
}
return -1;
}
/* --- Derive service username from agent name --- */
@@ -1405,7 +1426,7 @@ static int install_system_service_with_dedicated_user(const didactyl_config_t* c
char comment[128] = {0};
snprintf(comment, sizeof(comment), "Didactyl Nostr Agent (%s)", agent_name);
char* useradd_argv[] = {"useradd", "-m", "-s", "/bin/bash", "-c", comment, service_user, NULL};
if (run_command_local(useradd_argv) != 0) {
if (run_privileged_command_local(useradd_argv) != 0) {
fprintf(stderr, "%sFailed to create system user '%s'.%s\n", ANSI_RED, service_user, ANSI_RESET);
return -1;
}
@@ -1424,11 +1445,6 @@ static int install_system_service_with_dedicated_user(const didactyl_config_t* c
return -1;
}
if (ensure_dir_exists_local(home) != 0) {
fprintf(stderr, "%sUnable to create/access home directory: %s%s\n", ANSI_RED, home, ANSI_RESET);
return -1;
}
/* --- Copy binary --- */
char self_path[PATH_MAX] = {0};
ssize_t self_len = readlink("/proc/self/exe", self_path, sizeof(self_path) - 1);
@@ -1441,19 +1457,23 @@ static int install_system_service_with_dedicated_user(const didactyl_config_t* c
char bin_path[PATH_MAX] = {0};
snprintf(bin_path, sizeof(bin_path), "%s/didactyl", home);
fprintf(stderr, " Copying binary to %s...\n", bin_path);
if (copy_file_local(self_path, bin_path) != 0) {
char* install_bin_argv[] = {"install", "-m", "0755", self_path, bin_path, NULL};
if (run_privileged_command_local(install_bin_argv) != 0) {
fprintf(stderr, "%sFailed to copy binary to %s.%s\n", ANSI_RED, bin_path, ANSI_RESET);
return -1;
}
if (chmod(bin_path, 0755) != 0) {
fprintf(stderr, "%sFailed to chmod binary at %s.%s\n", ANSI_RED, bin_path, ANSI_RESET);
return -1;
}
/* --- Set ownership on home directory and installed binary --- */
if (chown(home, pw->pw_uid, pw->pw_gid) != 0 ||
chmod(home, 0750) != 0 ||
chown(bin_path, pw->pw_uid, pw->pw_gid) != 0) {
char owner_spec[64] = {0};
snprintf(owner_spec, sizeof(owner_spec), "%lu:%lu", (unsigned long)pw->pw_uid, (unsigned long)pw->pw_gid);
char* chown_home_argv[] = {"chown", owner_spec, (char*)home, NULL};
char* chmod_home_argv[] = {"chmod", "0750", (char*)home, NULL};
char* chown_bin_argv[] = {"chown", owner_spec, bin_path, NULL};
if (run_privileged_command_local(chown_home_argv) != 0 ||
run_privileged_command_local(chmod_home_argv) != 0 ||
run_privileged_command_local(chown_bin_argv) != 0) {
fprintf(stderr, "%sFailed to set ownership/permissions for installed files.%s\n", ANSI_RED, ANSI_RESET);
return -1;
}
@@ -1476,60 +1496,66 @@ static int install_system_service_with_dedicated_user(const didactyl_config_t* c
char service_path[PATH_MAX] = {0};
snprintf(service_path, sizeof(service_path), "/etc/systemd/system/%s", service_name);
fprintf(stderr, " Writing systemd unit to %s...\n", service_path);
FILE* svc_fp = fopen(service_path, "w");
if (!svc_fp) {
fprintf(stderr, "%sFailed to write service file at %s.%s\n", ANSI_RED, service_path, ANSI_RESET);
char env_line[PATH_MAX + 64] = {0};
if (ca_bundle) {
snprintf(env_line, sizeof(env_line), "Environment=SSL_CERT_FILE=%s\\n", ca_bundle);
}
char service_content[8192] = {0};
int svc_len = snprintf(service_content,
sizeof(service_content),
"[Unit]\n"
"Description=Didactyl Nostr Agent (%s)\n"
"After=network-online.target\n"
"Wants=network-online.target\n\n"
"[Service]\n"
"Type=simple\n"
"User=%s\n"
"Group=%s\n"
"WorkingDirectory=%s\n"
"ExecStart=%s --nsec %s --debug 3\n"
"%s"
"SyslogIdentifier=%s\n"
"NoNewPrivileges=false\n"
"ProtectSystem=strict\n"
"ReadWritePaths=%s\n"
"ProtectHome=false\n"
"PrivateTmp=yes\n"
"Restart=on-failure\n"
"RestartSec=10\n"
"StandardOutput=journal\n"
"StandardError=journal\n\n"
"[Install]\n"
"WantedBy=multi-user.target\n",
service_user,
service_user,
service_user,
home,
bin_path,
cfg->keys.nsec,
env_line,
service_user,
home);
if (svc_len < 0 || (size_t)svc_len >= sizeof(service_content)) {
fprintf(stderr, "%sFailed to build service file content.%s\n", ANSI_RED, ANSI_RESET);
return -1;
}
fprintf(svc_fp,
"[Unit]\n"
"Description=Didactyl Nostr Agent (%s)\n"
"After=network-online.target\n"
"Wants=network-online.target\n\n"
"[Service]\n"
"Type=simple\n"
"User=%s\n"
"Group=%s\n"
"WorkingDirectory=%s\n"
"ExecStart=%s --nsec %s --debug 3\n",
service_user,
service_user,
service_user,
home,
bin_path,
cfg->keys.nsec);
if (ca_bundle) {
fprintf(svc_fp, "Environment=SSL_CERT_FILE=%s\n", ca_bundle);
if (write_file_privileged_local(service_path, service_content, 0644U) != 0) {
fprintf(stderr, "%sFailed to write service file at %s.%s\n", ANSI_RED, service_path, ANSI_RESET);
return -1;
}
fprintf(svc_fp,
"SyslogIdentifier=%s\n"
"NoNewPrivileges=false\n"
"ProtectSystem=strict\n"
"ReadWritePaths=%s\n"
"ProtectHome=false\n"
"PrivateTmp=yes\n"
"Restart=on-failure\n"
"RestartSec=10\n"
"StandardOutput=journal\n"
"StandardError=journal\n\n"
"[Install]\n"
"WantedBy=multi-user.target\n",
service_user,
home);
fclose(svc_fp);
/* --- Enable and start the service --- */
fprintf(stderr, " Enabling and starting %s...\n", service_name);
char* reload_argv[] = {"systemctl", "daemon-reload", NULL};
char* enable_argv[] = {"systemctl", "enable", service_name, NULL};
char* start_argv[] = {"systemctl", "start", service_name, NULL};
if (run_command_local(reload_argv) != 0 ||
run_command_local(enable_argv) != 0 ||
run_command_local(start_argv) != 0) {
if (run_privileged_command_local(reload_argv) != 0 ||
run_privileged_command_local(enable_argv) != 0 ||
run_privileged_command_local(start_argv) != 0) {
fprintf(stderr, "%sFailed to enable/start systemd service %s.%s\n", ANSI_RED, service_name, ANSI_RESET);
fprintf(stderr, " You can manually start with:\n");
fprintf(stderr, " sudo systemctl daemon-reload\n");