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:
@@ -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.75 — Add rsync deploy script and wizard root-shell handoff for service install path
|
||||
> Last release update: v0.0.76 — Use 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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user