/* ============================================================================= * fleet-beacon.c — Multi-node Realtime Telemetry Beacon (Pure C POSIX) * Observatoř Telemetry Platform // Fleet Operations * * Broadcasts UDP telemetry packets (port 9850) every 1.5s. * Zero runtime dependencies, minimal RAM (< 1MB), 0.00% idle CPU. * ============================================================================= */ #define _POSIX_C_SOURCE 200809L #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define BEACON_PORT 9850 #define INTERVAL_US 1500000 static const char *TARGETS[] = { "255.255.255.255", "192.168.1.255", "192.168.1.100", /* t520 ETH */ "192.168.1.102", /* t520 WiFi */ "192.168.1.104", /* bazina */ "192.168.1.101", /* boxC */ "192.168.1.105", /* crostini */ "192.168.1.103", /* android */ "10.39.9.255", /* WireGuard Broadcast */ "10.39.9.1", /* boxC VPN */ "10.0.0.255", "10.0.0.18", "10.0.0.14", "91.139.118.43", "192.168.43.255", "192.168.43.1", "127.0.0.1", NULL }; static void resolve_identity(char *node, size_t n_len, char *arch, size_t a_len, char *sign, size_t s_len, char *raw_host, size_t h_len) { /* 1. Hostname */ char hbuf[128] = {0}; if (gethostname(hbuf, sizeof(hbuf) - 1) != 0) { snprintf(hbuf, sizeof(hbuf), "unknown"); } char *dot = strchr(hbuf, '.'); if (dot) *dot = '\0'; snprintf(raw_host, h_len, "%s", hbuf); /* 2. FLEET_NODE override / file probe */ const char *env_node = getenv("FLEET_NODE"); if (env_node && *env_node) { snprintf(node, n_len, "%s", env_node); } else { char line[256]; FILE *fp = fopen("/etc/fleet_id", "r"); if (!fp) { char home_f[256]; const char *home = getenv("HOME"); if (home) { snprintf(home_f, sizeof(home_f), "%s/.fleet_id", home); fp = fopen(home_f, "r"); } } if (fp) { while (fgets(line, sizeof(line), fp)) { char *p = line; while (*p == ' ' || *p == '\t') p++; if (*p == '#' || *p == '\n' || *p == '\r' || *p == '\0') continue; if (strncmp(p, "FLEET_NODE=", 11) == 0) { char *val = p + 11; if (*val == '"' || *val == '\'') val++; size_t l = strlen(val); while (l > 0 && (val[l-1] == '\n' || val[l-1] == '\r' || val[l-1] == '"' || val[l-1] == '\'' || val[l-1] == ' ')) val[--l] = '\0'; snprintf(node, n_len, "%s", val); } else if (strncmp(p, "FLEET_ARCH=", 11) == 0) { char *val = p + 11; if (*val == '"' || *val == '\'') val++; size_t l = strlen(val); while (l > 0 && (val[l-1] == '\n' || val[l-1] == '\r' || val[l-1] == '"' || val[l-1] == '\'' || val[l-1] == ' ')) val[--l] = '\0'; snprintf(arch, a_len, "%s", val); } else if (strncmp(p, "FLEET_SIGN=", 11) == 0) { char *val = p + 11; if (*val == '"' || *val == '\'') val++; size_t l = strlen(val); while (l > 0 && (val[l-1] == '\n' || val[l-1] == '\r' || val[l-1] == '"' || val[l-1] == '\'' || val[l-1] == ' ')) val[--l] = '\0'; snprintf(sign, s_len, "%s", val); } else if (node[0] == '\0') { size_t l = strlen(p); while (l > 0 && (p[l-1] == '\n' || p[l-1] == '\r' || p[l-1] == ' ')) p[--l] = '\0'; snprintf(node, n_len, "%s", p); } } fclose(fp); } if (node[0] == '\0') { if (access("/data/data/com.termux", F_OK) == 0 || getenv("TERMUX_VERSION") != NULL) { snprintf(node, n_len, "android"); } else if (strcmp(hbuf, "localhost") == 0 || strcmp(hbuf, "android") == 0) { snprintf(node, n_len, "android"); } else if (strcmp(hbuf, "penguin") == 0) { snprintf(node, n_len, "crostini"); } else { snprintf(node, n_len, "%s", hbuf); } } } /* 3. Arch */ const char *env_arch = getenv("FLEET_ARCH"); if (env_arch && *env_arch) { snprintf(arch, a_len, "%s", env_arch); } else { struct utsname uts; if (uname(&uts) == 0) { snprintf(arch, a_len, "%s", uts.machine); } else { snprintf(arch, a_len, "unknown"); } } /* 4. Sign */ const char *env_sign = getenv("FLEET_SIGN"); if (env_sign && *env_sign) { snprintf(sign, s_len, "%s", env_sign); } else { snprintf(sign, s_len, "[%s:%s]", node, arch); } } static double read_cpu(unsigned long long *prev_work, unsigned long long *prev_total) { FILE *fp = fopen("/proc/stat", "r"); if (!fp) return 0.0; char line[256]; if (!fgets(line, sizeof(line), fp)) { fclose(fp); return 0.0; } fclose(fp); unsigned long long user = 0, nice = 0, system = 0, idle = 0, iowait = 0, irq = 0, softirq = 0, steal = 0; if (sscanf(line, "cpu %llu %llu %llu %llu %llu %llu %llu %llu", &user, &nice, &system, &idle, &iowait, &irq, &softirq, &steal) < 4) { return 0.0; } unsigned long long work = user + nice + system + irq + softirq + steal; unsigned long long total = work + idle + iowait; double pct = 0.0; if (*prev_total > 0 && total > *prev_total) { unsigned long long d_work = work - *prev_work; unsigned long long d_total = total - *prev_total; pct = ((double)d_work / (double)d_total) * 100.0; if (pct < 0.0) pct = 0.0; if (pct > 100.0) pct = 100.0; } *prev_work = work; *prev_total = total; return pct; } static void read_memory(double *used_gb, double *total_gb) { *used_gb = 0.0; *total_gb = 0.0; FILE *fp = fopen("/proc/meminfo", "r"); if (!fp) return; char line[128]; double total_kb = 0.0, avail_kb = 0.0, free_kb = 0.0, buffers_kb = 0.0, cached_kb = 0.0; int have_avail = 0; while (fgets(line, sizeof(line), fp)) { if (strncmp(line, "MemTotal:", 9) == 0) { sscanf(line + 9, "%lf", &total_kb); } else if (strncmp(line, "MemAvailable:", 13) == 0) { sscanf(line + 13, "%lf", &avail_kb); have_avail = 1; } else if (strncmp(line, "MemFree:", 8) == 0) { sscanf(line + 8, "%lf", &free_kb); } else if (strncmp(line, "Buffers:", 8) == 0) { sscanf(line + 8, "%lf", &buffers_kb); } else if (strncmp(line, "Cached:", 7) == 0) { sscanf(line + 7, "%lf", &cached_kb); } } fclose(fp); if (!have_avail) { avail_kb = free_kb + buffers_kb + cached_kb; } *total_gb = total_kb / (1024.0 * 1024.0); *used_gb = (total_kb - avail_kb) / (1024.0 * 1024.0); if (*used_gb < 0.0) *used_gb = 0.0; } static void read_disk(const char *node, char *mount_out, size_t m_len, double *free_gb, double *total_gb) { const char *candidates[4] = { "/", NULL, NULL, NULL }; if (strcmp(node, "boxC") == 0) { candidates[0] = "/mnt/kof17"; candidates[1] = "/"; } else if (strcmp(node, "bazina") == 0) { candidates[0] = "/mnt/ssd"; candidates[1] = "/mnt/seagate"; candidates[2] = "/"; } for (int i = 0; candidates[i] != NULL; ++i) { struct statvfs st; if (statvfs(candidates[i], &st) == 0 && st.f_blocks > 0) { snprintf(mount_out, m_len, "%s", candidates[i]); *free_gb = ((double)st.f_bavail * (double)st.f_frsize) / (1024.0 * 1024.0 * 1024.0); *total_gb = ((double)st.f_blocks * (double)st.f_frsize) / (1024.0 * 1024.0 * 1024.0); return; } } snprintf(mount_out, m_len, "/"); *free_gb = 0.0; *total_gb = 0.0; } static void read_gpu(char *gpu_out, size_t g_len) { gpu_out[0] = '\0'; glob_t gl; if (glob("/sys/class/drm/card*/device/gpu_busy_percent", 0, NULL, &gl) == 0) { for (size_t i = 0; i < gl.gl_pathc; ++i) { FILE *fp = fopen(gl.gl_pathv[i], "r"); if (fp) { int busy = 0; if (fscanf(fp, "%d", &busy) == 1) { snprintf(gpu_out, g_len, "Navi10 %d%%", busy); fclose(fp); break; } fclose(fp); } } globfree(&gl); } } static int read_temp(void) { const char *paths[] = { "/sys/class/thermal/thermal_zone0/temp", "/sys/class/hwmon/hwmon0/temp1_input", "/sys/class/hwmon/hwmon1/temp1_input", NULL }; for (int i = 0; paths[i] != NULL; ++i) { FILE *fp = fopen(paths[i], "r"); if (fp) { long temp_raw = 0; if (fscanf(fp, "%ld", &temp_raw) == 1) { fclose(fp); return (int)(temp_raw / 1000); } fclose(fp); } } return 0; } static int read_battery(void) { glob_t gl; const char *patterns[] = { "/sys/class/power_supply/BAT*/capacity", "/sys/class/power_supply/*battery*/capacity", "/sys/class/power_supply/sbs-*/capacity", NULL }; for (int p = 0; patterns[p] != NULL; ++p) { if (glob(patterns[p], 0, NULL, &gl) == 0) { for (size_t i = 0; i < gl.gl_pathc; ++i) { FILE *fp = fopen(gl.gl_pathv[i], "r"); if (fp) { int cap = -1; if (fscanf(fp, "%d", &cap) == 1) { fclose(fp); globfree(&gl); return cap; } fclose(fp); } } globfree(&gl); } } return -1; } static void update_shm(const char *node, const char *payload) { const char *shm_path = "/dev/shm/fleet_telemetry.json"; char tmp_path[256]; snprintf(tmp_path, sizeof(tmp_path), "%s.tmp.%d", shm_path, (int)getpid()); /* Read existing file up to 64KB */ char in_buf[65536] = {0}; FILE *fp = fopen(shm_path, "r"); if (fp) { size_t n = fread(in_buf, 1, sizeof(in_buf) - 1, fp); in_buf[n] = '\0'; fclose(fp); } FILE *out = fopen(tmp_path, "w"); if (!out) return; /* Write simple JSON envelope */ if (in_buf[0] == '{') { /* Filter out existing node key and append new */ fprintf(out, "{\n"); int wrote_any = 0; /* Scan and copy other nodes */ const char *known[] = { "t520", "bazina", "boxC", "crostini", "penguin", "android", "phone", NULL }; for (int k = 0; known[k] != NULL; ++k) { if (strcmp(known[k], node) == 0) continue; char needle[64]; snprintf(needle, sizeof(needle), "\"%s\":", known[k]); char *pos = strstr(in_buf, needle); if (pos) { /* Find opening brace */ char *brace_start = strchr(pos, '{'); if (brace_start) { int depth = 1; char *ptr = brace_start + 1; while (*ptr && depth > 0) { if (*ptr == '{') depth++; else if (*ptr == '}') depth--; ptr++; } if (depth == 0) { if (wrote_any) fprintf(out, ",\n"); fprintf(out, " \"%s\": ", known[k]); fwrite(brace_start, 1, (size_t)(ptr - brace_start), out); wrote_any = 1; } } } } if (wrote_any) fprintf(out, ",\n"); fprintf(out, " \"%s\": %s\n}\n", node, payload); } else { fprintf(out, "{\n \"%s\": %s\n}\n", node, payload); } fclose(out); chmod(tmp_path, 0666); rename(tmp_path, shm_path); } static void send_wan_http(const char *payload) { int sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) return; struct timeval tv; tv.tv_sec = 1; tv.tv_usec = 500000; setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); struct sockaddr_in srv; memset(&srv, 0, sizeof(srv)); srv.sin_family = AF_INET; srv.sin_port = htons(80); inet_pton(AF_INET, "91.139.118.43", &srv.sin_addr); if (connect(sock, (struct sockaddr *)&srv, sizeof(srv)) == 0) { char req[4096]; int req_len = snprintf(req, sizeof(req), "POST /telemetry.php HTTP/1.1\r\n" "Host: 91.139.118.43\r\n" "Content-Type: application/json\r\n" "Content-Length: %zu\r\n" "Connection: close\r\n\r\n" "%s", strlen(payload), payload); if (req_len > 0) { write(sock, req, (size_t)req_len); } } close(sock); } int main(int argc, char **argv) { int run_once = 0; int print_json = 0; for (int i = 1; i < argc; ++i) { if (strcmp(argv[i], "--once") == 0 || strcmp(argv[i], "-1") == 0) run_once = 1; else if (strcmp(argv[i], "--json") == 0 || strcmp(argv[i], "-j") == 0) { run_once = 1; print_json = 1; } } char node[64], arch[65], sign[160], raw_host[64]; resolve_identity(node, sizeof(node), arch, sizeof(arch), sign, sizeof(sign), raw_host, sizeof(raw_host)); int udp_sock = socket(AF_INET, SOCK_DGRAM, 0); if (udp_sock >= 0) { int broadcast = 1; setsockopt(udp_sock, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast)); } if (!print_json) { printf("[fleet-beacon-c] Starting native telemetry beacon on %s %s (port %d)...\n", node, sign, BEACON_PORT); fflush(stdout); } unsigned long long prev_work = 0, prev_total = 0; while (1) { double cpu_pct = read_cpu(&prev_work, &prev_total); double mem_used = 0.0, mem_total = 0.0; read_memory(&mem_used, &mem_total); char mount[64] = "/"; double disk_free = 0.0, disk_total = 0.0; read_disk(node, mount, sizeof(mount), &disk_free, &disk_total); char gpu_str[64]; read_gpu(gpu_str, sizeof(gpu_str)); int temp_c = read_temp(); int bat_pct = read_battery(); char alert[256] = ""; if (strcmp(node, "boxC") == 0 && strcmp(mount, "/mnt/kof17") == 0 && disk_free < 3.0) { snprintf(alert, sizeof(alert), "kof17 CRITICAL: %.1fG free", disk_free); } else if ((strcmp(node, "crostini") == 0 || strcmp(node, "penguin") == 0) && disk_free < 1.0) { snprintf(alert, sizeof(alert), "crostini LOW DISK: %.1fG free", disk_free); } else if (bat_pct >= 0 && bat_pct <= 15) { snprintf(alert, sizeof(alert), "%s BATTERY CRITICAL: %d%%", node, bat_pct); } char payload[2048]; snprintf(payload, sizeof(payload), "{\"name\":\"%s\",\"hostname\":\"%s\",\"node\":\"%s\",\"arch\":\"%s\",\"sign\":\"%s\"," "\"timestamp\":%.3f,\"cpu_percent\":%.1f,\"mem_used_gb\":%.1f,\"mem_total_gb\":%.1f," "\"disk_mount\":\"%s\",\"disk_free_gb\":%.1f,\"disk_total_gb\":%.1f," "\"gpu_str\":\"%s\",\"temp_c\":%d,\"battery_percent\":%d,\"alert\":\"%s\"}", node, raw_host, node, arch, sign, (double)time(NULL), cpu_pct, mem_used, mem_total, mount, disk_free, disk_total, gpu_str, temp_c, bat_pct, alert); /* 1. Broadcast UDP */ if (udp_sock >= 0) { size_t payload_len = strlen(payload); for (int i = 0; TARGETS[i] != NULL; ++i) { struct sockaddr_in sin; memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = htons(BEACON_PORT); if (inet_pton(AF_INET, TARGETS[i], &sin.sin_addr) > 0) { sendto(udp_sock, payload, payload_len, 0, (struct sockaddr *)&sin, sizeof(sin)); } } } /* 2. Update local SHM cache */ update_shm(node, payload); /* 3. HTTP WAN Fallback */ if (strcmp(node, "boxC") != 0) { send_wan_http(payload); } if (print_json) { printf("%s\n", payload); fflush(stdout); } if (run_once) { break; } usleep(INTERVAL_US); } if (udp_sock >= 0) close(udp_sock); return 0; }