diff options
| author | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-02 18:51:45 +0300 |
|---|---|---|
| committer | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-02 18:51:45 +0300 |
| commit | d3261e64152397db2dca4d691a990c6bc2a6f4dd (patch) | |
| tree | fac2f7be638651181a72453d714f0f96675c2b8b /archived/projt-launcher/bootstrap | |
| parent | 31b9a8949ed0a288143e23bf739f2eb64fdc63be (diff) | |
| download | Project-Tick-d3261e64152397db2dca4d691a990c6bc2a6f4dd.tar.gz Project-Tick-d3261e64152397db2dca4d691a990c6bc2a6f4dd.zip | |
NOISSUE add archived projects
Signed-off-by: Mehmet Samet Duman <yongdohyun@projecttick.org>
Diffstat (limited to 'archived/projt-launcher/bootstrap')
| -rw-r--r-- | archived/projt-launcher/bootstrap/macos/Bootstrap.m | 423 | ||||
| -rw-r--r-- | archived/projt-launcher/bootstrap/macos/CMakeLists.txt | 38 | ||||
| -rw-r--r-- | archived/projt-launcher/bootstrap/macos/Info.plist.in | 32 |
3 files changed, 493 insertions, 0 deletions
diff --git a/archived/projt-launcher/bootstrap/macos/Bootstrap.m b/archived/projt-launcher/bootstrap/macos/Bootstrap.m new file mode 100644 index 0000000000..188b165a78 --- /dev/null +++ b/archived/projt-launcher/bootstrap/macos/Bootstrap.m @@ -0,0 +1,423 @@ +#import <Cocoa/Cocoa.h> +#include <CoreFoundation/CoreFoundation.h> +#include <libxml/parser.h> +#include <libxml/tree.h> +#include <spawn.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <unistd.h> + +#define APP_NAME "ProjT Launcher.app" +#define INSTALL_PATH "/Applications/" APP_NAME +#define FEED_URL BOOTSTRAP_FEED_URL +#define URL_TEMPLATE BOOTSTRAP_URL_TEMPLATE + +extern char **environ; + +static NSWindow *g_window = nil; +static NSTextField *g_statusLabel = nil; +static NSProgressIndicator *g_progress = nil; + +static void pump_events(void) { + @autoreleasepool { + NSEvent *event = nil; + while ((event = [NSApp nextEventMatchingMask:NSEventMaskAny + untilDate:[NSDate dateWithTimeIntervalSinceNow:0] + inMode:NSDefaultRunLoopMode + dequeue:YES])) { + [NSApp sendEvent:event]; + } + [NSApp updateWindows]; + } +} + +static void init_ui(void) { + @autoreleasepool { + [NSApplication sharedApplication]; + [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + + NSRect frame = NSMakeRect(0, 0, 520, 280); + g_window = [[NSWindow alloc] initWithContentRect:frame + styleMask:(NSWindowStyleMaskTitled | NSWindowStyleMaskClosable) + backing:NSBackingStoreBuffered + defer:NO]; + [g_window setTitle:@"ProjT Launcher Installer"]; + [g_window center]; + + NSView *content = [g_window contentView]; + + NSTextField *title = [[NSTextField alloc] initWithFrame:NSMakeRect(24, 210, 472, 40)]; + [title setStringValue:@"ProjT Launcher Installer"]; + [title setBezeled:NO]; + [title setDrawsBackground:NO]; + [title setEditable:NO]; + [title setSelectable:NO]; + [title setFont:[NSFont systemFontOfSize:18 weight:NSFontWeightSemibold]]; + [content addSubview:title]; + + g_statusLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(24, 165, 472, 30)]; + [g_statusLabel setStringValue:@"Preparing..."]; + [g_statusLabel setBezeled:NO]; + [g_statusLabel setDrawsBackground:NO]; + [g_statusLabel setEditable:NO]; + [g_statusLabel setSelectable:NO]; + [g_statusLabel setFont:[NSFont systemFontOfSize:13 weight:NSFontWeightRegular]]; + [content addSubview:g_statusLabel]; + + g_progress = [[NSProgressIndicator alloc] initWithFrame:NSMakeRect(24, 125, 472, 12)]; + [g_progress setIndeterminate:YES]; + [g_progress setStyle:NSProgressIndicatorStyleBar]; + [g_progress startAnimation:nil]; + [content addSubview:g_progress]; + + [g_window makeKeyAndOrderFront:nil]; + [NSApp activateIgnoringOtherApps:YES]; + pump_events(); + } +} + +static void update_status(const char *message) { + if (!g_statusLabel || !message) return; + @autoreleasepool { + NSString *text = [NSString stringWithUTF8String:message]; + [g_statusLabel setStringValue:text ?: @""]; + pump_events(); + } +} + +static void show_alert(const char *title, const char *message) { + @autoreleasepool { + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:(title ? [NSString stringWithUTF8String:title] : @"ProjT Launcher Installer")]; + [alert setInformativeText:(message ? [NSString stringWithUTF8String:message] : @"")]; + [alert addButtonWithTitle:@"OK"]; + [alert runModal]; + } +} + +static int run_cmd(char *const argv[]) { + pid_t pid = 0; + int status = 0; + if (posix_spawnp(&pid, argv[0], NULL, NULL, argv, environ) != 0) { + return -1; + } + if (waitpid(pid, &status, 0) < 0) { + return -1; + } + if (WIFEXITED(status)) return WEXITSTATUS(status); + return -1; +} + +static char *strdup_safe(const char *s) { + if (!s) return NULL; + size_t len = strlen(s); + char *out = (char *)malloc(len + 1); + if (!out) return NULL; + memcpy(out, s, len + 1); + return out; +} + +static char *extract_version(const char *text) { + if (!text) return NULL; + const char *p = text; + while (*p && (*p < '0' || *p > '9')) p++; + if (!*p) return NULL; + const char *start = p; + while (*p) { + if ((*p >= '0' && *p <= '9') || *p == '.' || *p == '-' ) { + p++; + continue; + } + break; + } + size_t len = (size_t)(p - start); + if (len == 0) return NULL; + char *out = (char *)malloc(len + 1); + if (!out) return NULL; + memcpy(out, start, len); + out[len] = '\0'; + return out; +} + +static int version_compare(const char *a, const char *b) { + if (!a && !b) return 0; + if (!a) return -1; + if (!b) return 1; + char *ca = strdup_safe(a); + char *cb = strdup_safe(b); + if (!ca || !cb) { + free(ca); + free(cb); + return 0; + } + const char *delim = "._-"; + char *sa = strtok(ca, delim); + char *sb = strtok(cb, delim); + while (sa || sb) { + long va = sa ? strtol(sa, NULL, 10) : 0; + long vb = sb ? strtol(sb, NULL, 10) : 0; + if (va < vb) { free(ca); free(cb); return -1; } + if (va > vb) { free(ca); free(cb); return 1; } + sa = sa ? strtok(NULL, delim) : NULL; + sb = sb ? strtok(NULL, delim) : NULL; + } + free(ca); + free(cb); + return 0; +} + +static char *get_installed_version(void) { + CFURLRef appURL = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, + (const UInt8 *)INSTALL_PATH, + strlen(INSTALL_PATH), + true); + if (!appURL) return NULL; + CFBundleRef bundle = CFBundleCreate(kCFAllocatorDefault, appURL); + CFRelease(appURL); + if (!bundle) return NULL; + CFStringRef version = CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("CFBundleShortVersionString")); + if (!version) { + CFRelease(bundle); + return NULL; + } + char buf[128]; + if (!CFStringGetCString(version, buf, sizeof(buf), kCFStringEncodingUTF8)) { + CFRelease(bundle); + return NULL; + } + CFRelease(bundle); + return strdup_safe(buf); +} + +typedef struct { + char *version; +} ReleaseInfo; + +static bool attr_matches(xmlAttr *attr, const char *prefix, const char *name, const char *value) { + if (!attr || !attr->name) return false; + if (strcmp((const char *)attr->name, name) != 0) return false; + if (prefix) { + if (!attr->ns || !attr->ns->prefix) return false; + if (strcmp((const char *)attr->ns->prefix, prefix) != 0) return false; + } + xmlChar *val = xmlNodeListGetString(attr->doc, attr->children, 1); + bool ok = val && strcmp((const char *)val, value) == 0; + if (val) xmlFree(val); + return ok; +} + +static xmlChar *get_child_content(xmlNode *node, const char *prefix, const char *name) { + for (xmlNode *c = node->children; c; c = c->next) { + if (c->type != XML_ELEMENT_NODE || !c->name) continue; + if (strcmp((const char *)c->name, name) != 0) continue; + if (prefix) { + if (!c->ns || !c->ns->prefix) continue; + if (strcmp((const char *)c->ns->prefix, prefix) != 0) continue; + } + return xmlNodeGetContent(c); + } + return NULL; +} + +static ReleaseInfo parse_feed(const char *path) { + ReleaseInfo info = {0}; + xmlDoc *doc = xmlReadFile(path, NULL, XML_PARSE_NOERROR | XML_PARSE_NOWARNING); + if (!doc) return info; + xmlNode *root = xmlDocGetRootElement(doc); + if (!root) { + xmlFreeDoc(doc); + return info; + } + for (xmlNode *channel = root->children; channel; channel = channel->next) { + if (channel->type != XML_ELEMENT_NODE) continue; + for (xmlNode *item = channel->children; item; item = item->next) { + if (item->type != XML_ELEMENT_NODE) continue; + if (strcmp((const char *)item->name, "item") != 0) continue; + + xmlChar *ver = get_child_content(item, "sparkle", "shortVersionString"); + if (!ver) ver = get_child_content(item, "sparkle", "version"); + if (!ver) { + xmlChar *title = get_child_content(item, NULL, "title"); + if (title) { + char *from_title = extract_version((const char *)title); + if (from_title) { + info.version = from_title; + } + xmlFree(title); + } + } + + if (ver && !info.version) { + info.version = strdup_safe((const char *)ver); + xmlFree(ver); + break; + } + if (ver) xmlFree(ver); + if (info.version) break; + } + if (info.version) break; + } + xmlFreeDoc(doc); + return info; +} + +static int download_file(const char *url, const char *out_path) { + char *argv[] = {"/usr/bin/curl", "-L", "--fail", "--silent", "--show-error", "-o", (char *)out_path, (char *)url, NULL}; + return run_cmd(argv); +} + +static int unzip_to(const char *zip_path, const char *dest_dir) { + char *argv[] = {"/usr/bin/ditto", "-x", "-k", (char *)zip_path, (char *)dest_dir, NULL}; + return run_cmd(argv); +} + +static int verify_codesign(const char *app_path) { + char *argv1[] = {"/usr/bin/codesign", "--verify", "--deep", "--strict", "--verbose=2", (char *)app_path, NULL}; + char *argv2[] = {"/usr/sbin/spctl", "--assess", "--type", "execute", "--verbose=2", (char *)app_path, NULL}; + if (run_cmd(argv1) != 0) return -1; + if (run_cmd(argv2) != 0) { + if (getenv("PROJT_BOOTSTRAP_STRICT")) return -1; + fprintf(stderr, "warning: spctl assessment failed; continuing (set PROJT_BOOTSTRAP_STRICT=1 to enforce).\n"); + } + return 0; +} + +static int install_app(const char *src_app) { + if (access(INSTALL_PATH, F_OK) == 0) { + char *rm_argv[] = {"/bin/rm", "-rf", INSTALL_PATH, NULL}; + if (run_cmd(rm_argv) != 0) { + // best effort; continue + } + } + + char *ditto_argv[] = {"/usr/bin/ditto", "--rsrc", (char *)src_app, INSTALL_PATH, NULL}; + if (run_cmd(ditto_argv) == 0) return 0; + + // Try with admin privileges + char cmd[2048]; + snprintf(cmd, sizeof(cmd), + "do shell script \"rm -rf '%s'; /usr/bin/ditto --rsrc '%s' '%s'\" with administrator privileges", + INSTALL_PATH, src_app, INSTALL_PATH); + char *osa_argv[] = {"/usr/bin/osascript", "-e", cmd, NULL}; + return run_cmd(osa_argv); +} + +static int launch_app(void) { + char *argv[] = {"/usr/bin/open", "-a", "ProjT Launcher", NULL}; + return run_cmd(argv); +} + +int main(void) { + init_ui(); + update_status("Checking for updates..."); + + const char *feed_url = FEED_URL; + if (!feed_url || strlen(feed_url) == 0) { + show_alert("ProjT Launcher Installer", "Feed URL is not configured."); + return 1; + } + + char tmpdir[] = "/tmp/projt-bootstrap-XXXXXX"; + if (!mkdtemp(tmpdir)) { + show_alert("ProjT Launcher Installer", "Failed to create temp directory."); + return 1; + } + + char feed_path[1024]; + snprintf(feed_path, sizeof(feed_path), "%s/feed.xml", tmpdir); + update_status("Downloading update feed..."); + if (download_file(feed_url, feed_path) != 0) { + show_alert("ProjT Launcher Installer", "Failed to download update feed."); + return 1; + } + + ReleaseInfo info = parse_feed(feed_path); + if (!info.version) { + show_alert("ProjT Launcher Installer", "Failed to parse update feed."); + return 1; + } + + char *download_url = NULL; + if (URL_TEMPLATE && strlen(URL_TEMPLATE) > 0) { + size_t needed = snprintf(NULL, 0, URL_TEMPLATE, info.version, info.version); + download_url = (char *)malloc(needed + 1); + if (!download_url) { + show_alert("ProjT Launcher Installer", "Out of memory while building URL."); + free(info.version); + return 1; + } + snprintf(download_url, needed + 1, URL_TEMPLATE, info.version, info.version); + } else { + show_alert("ProjT Launcher Installer", "Download URL template is not configured."); + free(info.version); + return 1; + } + + char *installed = get_installed_version(); + if (installed && version_compare(installed, info.version) >= 0) { + update_status("Launching ProjT Launcher..."); + launch_app(); + free(installed); + free(info.version); + free(download_url); + return 0; + } + free(installed); + + char zip_path[1024]; + snprintf(zip_path, sizeof(zip_path), "%s/launcher.zip", tmpdir); + update_status("Downloading latest version..."); + if (download_file(download_url, zip_path) != 0) { + show_alert("ProjT Launcher Installer", "Failed to download installer zip."); + free(info.version); + free(download_url); + return 1; + } + + char unpack_dir[1024]; + snprintf(unpack_dir, sizeof(unpack_dir), "%s/unpack", tmpdir); + mkdir(unpack_dir, 0755); + update_status("Extracting..."); + if (unzip_to(zip_path, unpack_dir) != 0) { + show_alert("ProjT Launcher Installer", "Failed to extract installer zip."); + free(info.version); + free(download_url); + return 1; + } + + char app_path[1024]; + snprintf(app_path, sizeof(app_path), "%s/%s", unpack_dir, APP_NAME); + if (access(app_path, F_OK) != 0) { + show_alert("ProjT Launcher Installer", "Downloaded archive did not contain the app bundle."); + free(info.version); + free(download_url); + return 1; + } + + update_status("Installing to /Applications..."); + if (install_app(app_path) != 0) { + show_alert("ProjT Launcher Installer", "Failed to install into /Applications."); + free(info.version); + free(download_url); + return 1; + } + + update_status("Verifying signature..."); + if (verify_codesign(INSTALL_PATH) != 0) { + show_alert("ProjT Launcher Installer", "Installed app failed signature verification."); + free(info.version); + free(download_url); + return 1; + } + + update_status("Launching ProjT Launcher..."); + launch_app(); + free(info.version); + free(download_url); + return 0; +} diff --git a/archived/projt-launcher/bootstrap/macos/CMakeLists.txt b/archived/projt-launcher/bootstrap/macos/CMakeLists.txt new file mode 100644 index 0000000000..28f496ab5b --- /dev/null +++ b/archived/projt-launcher/bootstrap/macos/CMakeLists.txt @@ -0,0 +1,38 @@ +find_package(LibXml2 REQUIRED) + +set(BOOTSTRAP_NAME "ProjT Launcher Installer") +set(BOOTSTRAP_ICNS "${CMAKE_SOURCE_DIR}/${Launcher_Branding_ICNS}") +get_filename_component(BOOTSTRAP_ICNS_NAME "${BOOTSTRAP_ICNS}" NAME) +set(BOOTSTRAP_BUNDLE_ID "org.projecttick.ProjTLauncherInstaller") + +add_executable(ProjTLauncherBootstrap MACOSX_BUNDLE + Bootstrap.m +) + +target_include_directories(ProjTLauncherBootstrap PRIVATE ${LIBXML2_INCLUDE_DIR}) +target_link_libraries(ProjTLauncherBootstrap PRIVATE ${LIBXML2_LIBRARIES}) +target_link_libraries(ProjTLauncherBootstrap PRIVATE "-framework CoreFoundation") +target_link_libraries(ProjTLauncherBootstrap PRIVATE "-framework Cocoa") + +target_compile_definitions(ProjTLauncherBootstrap PRIVATE + BOOTSTRAP_FEED_URL="${MACOSX_BOOTSTRAP_FEED_URL}" + BOOTSTRAP_URL_TEMPLATE="${MACOSX_BOOTSTRAP_URL_TEMPLATE}" +) + +set_target_properties(ProjTLauncherBootstrap PROPERTIES + OUTPUT_NAME "${BOOTSTRAP_NAME}" + MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in" + MACOSX_BUNDLE_BUNDLE_NAME "${BOOTSTRAP_NAME}" + MACOSX_BUNDLE_GUI_IDENTIFIER "${BOOTSTRAP_BUNDLE_ID}" + MACOSX_BUNDLE_BUNDLE_VERSION "${Launcher_VERSION_NAME}" + MACOSX_BUNDLE_SHORT_VERSION_STRING "${Launcher_VERSION_NAME}" + MACOSX_BUNDLE_LONG_VERSION_STRING "${Launcher_VERSION_NAME}" + MACOSX_BUNDLE_COPYRIGHT "${Launcher_Copyright_Mac}" + MACOSX_BUNDLE_INFO_STRING "${MACOSX_BUNDLE_INFO_STRING}" + MACOSX_BUNDLE_ICON_FILE "${BOOTSTRAP_ICNS_NAME}" +) + +target_sources(ProjTLauncherBootstrap PRIVATE "${BOOTSTRAP_ICNS}") +set_source_files_properties("${BOOTSTRAP_ICNS}" PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") + +install(TARGETS ProjTLauncherBootstrap BUNDLE DESTINATION "." COMPONENT launcher) diff --git a/archived/projt-launcher/bootstrap/macos/Info.plist.in b/archived/projt-launcher/bootstrap/macos/Info.plist.in new file mode 100644 index 0000000000..eb131339c0 --- /dev/null +++ b/archived/projt-launcher/bootstrap/macos/Info.plist.in @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>en</string> + <key>CFBundleExecutable</key> + <string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string> + <key>CFBundleIdentifier</key> + <string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>${MACOSX_BUNDLE_BUNDLE_NAME}</string> + <key>CFBundleIconFile</key> + <string>${MACOSX_BUNDLE_ICON_FILE}</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleShortVersionString</key> + <string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string> + <key>CFBundleVersion</key> + <string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string> + <key>LSMinimumSystemVersion</key> + <string>12.0</string> + <key>NSHighResolutionCapable</key> + <true/> + <key>NSHumanReadableCopyright</key> + <string>${MACOSX_BUNDLE_COPYRIGHT}</string> + <key>LSBackgroundOnly</key> + <true/> +</dict> +</plist> |
