/* * bootstrap.c - Self-extracting archive bootstrap * * A small C program that boots neopig from an embedded tar.gz archive. * When compiled and concatenated with a tar.gz archive, it: * 1. Extracts neopig/*.py from the embedded tarball to /tmp * 2. Runs: python3 /tmp/neopig/serp.py * 3. The Python script serves directly from the tarball portion * * Build: * gcc -O2 -o bootstrap bootstrap.c -lz * * Create self-extracting archive: * cat bootstrap archive.tar.gz > archive.run * chmod +x archive.run * echo -n "NEOPIG" >> archive.run # Magic marker * printf '%016x' $(stat -c%s bootstrap) >> archive.run # Offset (hex) * * Or use: make run TARBALL=archive.tar.gz */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #define MAGIC "NEOPIG" #define MAGIC_LEN 6 #define OFFSET_LEN 16 #define TRAILER_LEN (MAGIC_LEN + OFFSET_LEN) #define CHUNK_SIZE 16384 /* Find python3 interpreter */ static const char *find_python(void) { static const char *pythons[] = { "/usr/bin/python3", "/usr/local/bin/python3", "/opt/homebrew/bin/python3", "python3", NULL }; for (int i = 0; pythons[i]; i++) { if (access(pythons[i], X_OK) == 0) { return pythons[i]; } } /* Try PATH lookup */ return "python3"; } /* Read trailer to get tarball offset */ static long read_trailer(const char *self_path) { FILE *f = fopen(self_path, "rb"); if (!f) { perror("fopen self"); return -1; } /* Seek to trailer */ if (fseek(f, -TRAILER_LEN, SEEK_END) != 0) { perror("fseek trailer"); fclose(f); return -1; } char trailer[TRAILER_LEN + 1]; if (fread(trailer, 1, TRAILER_LEN, f) != TRAILER_LEN) { perror("fread trailer"); fclose(f); return -1; } trailer[TRAILER_LEN] = '\0'; fclose(f); /* Check magic */ if (memcmp(trailer, MAGIC, MAGIC_LEN) != 0) { fprintf(stderr, "Error: Invalid archive (missing NEOPIG magic)\n"); fprintf(stderr, "This executable must be created with make-executable.sh\n"); return -1; } /* Parse hex offset */ long offset = strtol(trailer + MAGIC_LEN, NULL, 16); return offset; } /* Create directory recursively */ static int mkdir_p(const char *path) { char tmp[4096]; char *p = NULL; size_t len; snprintf(tmp, sizeof(tmp), "%s", path); len = strlen(tmp); if (tmp[len - 1] == '/') tmp[len - 1] = 0; for (p = tmp + 1; *p; p++) { if (*p == '/') { *p = 0; mkdir(tmp, 0755); *p = '/'; } } return mkdir(tmp, 0755); } /* Extract neopig/*.py from gzipped tarball */ static char *extract_neopig(const char *self_path, long tar_offset) { /* Create temp directory */ char *temp_dir = strdup("/tmp/neopig_XXXXXX"); if (!temp_dir) return NULL; if (!mkdtemp(temp_dir)) { perror("mkdtemp"); free(temp_dir); return NULL; } /* Open self and seek to tarball */ FILE *self = fopen(self_path, "rb"); if (!self) { perror("fopen self for tar"); free(temp_dir); return NULL; } if (fseek(self, tar_offset, SEEK_SET) != 0) { perror("fseek to tar"); fclose(self); free(temp_dir); return NULL; } /* Open gzip stream */ gzFile gz = gzdopen(dup(fileno(self)), "rb"); if (!gz) { fprintf(stderr, "gzdopen failed\n"); fclose(self); free(temp_dir); return NULL; } fclose(self); /* Read tar headers looking for neopig/*.py */ unsigned char header[512]; int found_any = 0; while (gzread(gz, header, 512) == 512) { /* Check for end of archive (all zeros) */ int all_zero = 1; for (int i = 0; i < 512 && all_zero; i++) { if (header[i] != 0) all_zero = 0; } if (all_zero) break; /* Get filename (first 100 bytes) */ char filename[101]; memcpy(filename, header, 100); filename[100] = '\0'; /* Get file size (octal, bytes 124-135) */ char size_str[13]; memcpy(size_str, header + 124, 12); size_str[12] = '\0'; long filesize = strtol(size_str, NULL, 8); /* Check if this is a neopig file we want to extract */ char *neopig_pos = strstr(filename, "/neopig/"); char *py_ext = strstr(filename, ".py"); char *req_file = strstr(filename, "/requirements.txt"); char *static_vendor = strstr(filename, "/neopig/static/vendor/"); char *css_ext = strstr(filename, ".css"); char *js_ext = strstr(filename, ".js"); int is_neopig_py = (neopig_pos && py_ext && py_ext > neopig_pos); int is_requirements = (req_file != NULL); int is_static_asset = (static_vendor && (css_ext || js_ext)); if (is_neopig_py || is_requirements || is_static_asset) { /* Extract this file */ char out_path[4096]; if (is_static_asset) { /* Create static/vendor/ subdirectory */ char vendor_dir[4096]; snprintf(vendor_dir, sizeof(vendor_dir), "%s/static/vendor", temp_dir); mkdir(vendor_dir, 0755); char static_dir[4096]; snprintf(static_dir, sizeof(static_dir), "%s/static", temp_dir); mkdir(static_dir, 0755); mkdir(vendor_dir, 0755); char *basename = static_vendor + 22; /* Skip "/neopig/static/vendor/" */ snprintf(out_path, sizeof(out_path), "%s/static/vendor/%s", temp_dir, basename); } else if (is_neopig_py) { char *basename = neopig_pos + 8; /* Skip "/neopig/" */ snprintf(out_path, sizeof(out_path), "%s/%s", temp_dir, basename); } else { snprintf(out_path, sizeof(out_path), "%s/requirements.txt", temp_dir); } int fd = open(out_path, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd >= 0) { unsigned char buf[CHUNK_SIZE]; long remaining = filesize; while (remaining > 0) { int to_read = remaining > CHUNK_SIZE ? CHUNK_SIZE : remaining; int got = gzread(gz, buf, to_read); if (got <= 0) break; write(fd, buf, got); remaining -= got; } close(fd); found_any = 1; if (is_static_asset) { char *basename = static_vendor + 22; printf(" Extracted: static/vendor/%s\n", basename); } else if (is_neopig_py) { char *basename = neopig_pos + 8; printf(" Extracted: %s\n", basename); } else { printf(" Extracted: requirements.txt\n"); } /* Skip padding to 512 boundary */ int pad = (512 - (filesize % 512)) % 512; if (pad > 0) { gzread(gz, buf, pad); } } else { /* Skip this file's content */ long blocks = (filesize + 511) / 512; for (long i = 0; i < blocks; i++) { gzread(gz, header, 512); } } } else { /* Skip this file's content (padded to 512 bytes) */ long blocks = (filesize + 511) / 512; for (long i = 0; i < blocks; i++) { if (gzread(gz, header, 512) != 512) break; } } } gzclose(gz); if (!found_any) { fprintf(stderr, "Error: No neopig/*.py files found in archive\n"); free(temp_dir); return NULL; } return temp_dir; } int main(int argc, char *argv[]) { /* Get path to self */ char self_path[4096]; ssize_t len = readlink("/proc/self/exe", self_path, sizeof(self_path) - 1); if (len < 0) { /* Fallback to argv[0] */ if (argv[0][0] == '/') { strncpy(self_path, argv[0], sizeof(self_path) - 1); } else { char *cwd = getcwd(NULL, 0); snprintf(self_path, sizeof(self_path), "%s/%s", cwd, argv[0]); free(cwd); } } else { self_path[len] = '\0'; } printf("neopig self-extracting archive\n"); printf("==============================\n"); /* Read trailer to get tarball offset */ long tar_offset = read_trailer(self_path); if (tar_offset < 0) { return 1; } printf("Archive offset: %ld bytes\n", tar_offset); /* Extract neopig directory */ printf("Extracting neopig...\n"); char *neopig_dir = extract_neopig(self_path, tar_offset); if (!neopig_dir) { return 1; } /* Build path to serp.py */ char serp_path[4096]; snprintf(serp_path, sizeof(serp_path), "%s/serp.py", neopig_dir); /* Find python */ const char *python = find_python(); /* Create virtualenv and install deps */ char venv_path[4096]; snprintf(venv_path, sizeof(venv_path), "%s/.venv", neopig_dir); char req_path[4096]; snprintf(req_path, sizeof(req_path), "%s/requirements.txt", neopig_dir); /* Check if requirements.txt exists */ if (access(req_path, F_OK) == 0) { printf("\nCreating virtualenv...\n"); char venv_cmd[4096]; snprintf(venv_cmd, sizeof(venv_cmd), "%s -m venv %s", python, venv_path); if (system(venv_cmd) != 0) { fprintf(stderr, "Failed to create virtualenv\n"); free(neopig_dir); return 1; } printf("Installing dependencies...\n"); char pip_cmd[8192]; snprintf(pip_cmd, sizeof(pip_cmd), "%s/.venv/bin/pip install -r %s", neopig_dir, req_path); if (system(pip_cmd) != 0) { fprintf(stderr, "Failed to install dependencies\n"); free(neopig_dir); return 1; } /* Use venv python */ char venv_python[4096]; snprintf(venv_python, sizeof(venv_python), "%s/.venv/bin/python", neopig_dir); printf("\nStarting server...\n"); printf("Command: %s %s %s\n\n", venv_python, serp_path, self_path); /* Fork and exec with venv python * IMPORTANT: Use full path as argv[0] for proper venv detection */ pid_t pid = fork(); if (pid < 0) { perror("fork"); free(neopig_dir); return 1; } if (pid == 0) { execl(venv_python, venv_python, serp_path, self_path, NULL); perror("execl venv python"); _exit(1); } int status; waitpid(pid, &status, 0); printf("\nCleaning up %s...\n", neopig_dir); char rm_cmd[4096]; snprintf(rm_cmd, sizeof(rm_cmd), "rm -rf %s", neopig_dir); system(rm_cmd); free(neopig_dir); return WIFEXITED(status) ? WEXITSTATUS(status) : 1; } /* No requirements.txt - use system python */ printf("\nStarting server (no venv)...\n"); printf("Command: %s %s %s\n\n", python, serp_path, self_path); /* Fork and exec */ pid_t pid = fork(); if (pid < 0) { perror("fork"); free(neopig_dir); return 1; } if (pid == 0) { /* Child - exec python (use full path as argv[0]) */ execl(python, python, serp_path, self_path, NULL); perror("execl python"); _exit(1); } /* Parent - wait for child */ int status; waitpid(pid, &status, 0); /* Cleanup temp directory */ printf("\nCleaning up %s...\n", neopig_dir); char rm_cmd[4096]; snprintf(rm_cmd, sizeof(rm_cmd), "rm -rf %s", neopig_dir); system(rm_cmd); free(neopig_dir); return WIFEXITED(status) ? WEXITSTATUS(status) : 1; }