tonarchy

tonarchy

https://git.tonybtw.com/tonarchy.git git://git.tonybtw.com/tonarchy.git
53,707 bytes raw
1
#include "tonarchy.h"
2
#include <string.h>
3
#include <ctype.h>
4
5
static FILE *log_file = NULL;
6
static const char *level_strings[] = {"DEBUG", "INFO", "WARN", "ERROR"};
7
static struct termios orig_termios;
8
9
static void part_path(char *out, size_t size, const char *disk, int part) {
10
    if (isdigit(disk[strlen(disk) - 1])) {
11
        snprintf(out, size, "/dev/%sp%d", disk, part);
12
    } else {
13
        snprintf(out, size, "/dev/%s%d", disk, part);
14
    }
15
}
16
17
enum Install_Option {
18
    BEGINNER = 0,
19
    OXIDIZED = 1
20
};
21
22
enum Terminal_Size {
23
    TERM_SMALL = 0,
24
    TERM_MEDIUM = 1,
25
    TERM_LARGE = 2
26
};
27
28
static const char *XFCE_PACKAGES = "base base-devel linux linux-firmware linux-headers networkmanager git vim neovim curl wget htop btop man-db man-pages openssh sudo xorg-server xorg-xinit xorg-xrandr xorg-xset xfce4 xfce4-goodies xfce4-session xfce4-whiskermenu-plugin thunar thunar-archive-plugin file-roller firefox alacritty vlc evince eog fastfetch rofi ripgrep fd ttf-iosevka-nerd ttf-jetbrains-mono-nerd pavucontrol";
29
30
static const char *OXWM_PACKAGES = "base base-devel linux linux-firmware linux-headers networkmanager git vim neovim curl wget htop btop man-db man-pages openssh sudo xorg-server xorg-xinit xorg-xsetroot xorg-xrandr xorg-xset libx11 libxft libxinerama freetype2 fontconfig pkg-config lua firefox alacritty vlc evince eog zig ttf-iosevka-nerd ttf-jetbrains-mono-nerd picom xclip xwallpaper maim rofi pulseaudio pulseaudio-alsa pavucontrol alsa-utils fastfetch ripgrep fd pcmanfm lxappearance papirus-icon-theme gnome-themes-extra";
31
32
static int is_uefi_system(void) {
33
    struct stat st;
34
    return stat("/sys/firmware/efi", &st) == 0;
35
}
36
37
void logger_init(const char *log_path) {
38
    log_file = fopen(log_path, "a");
39
    if (log_file) {
40
        time_t now = time(NULL);
41
        char *timestamp = ctime(&now);
42
        timestamp[strlen(timestamp) - 1] = '\0';
43
        fprintf(log_file, "\n=== Tonarchy Installation Log - %s ===\n", timestamp);
44
        fflush(log_file);
45
    }
46
}
47
48
void logger_close(void) {
49
    if (log_file) {
50
        fclose(log_file);
51
        log_file = NULL;
52
    }
53
}
54
55
void log_msg(Log_Level level, const char *fmt, ...) {
56
    if (!log_file) return;
57
58
    time_t now = time(NULL);
59
    struct tm *t = localtime(&now);
60
61
    fprintf(
62
        log_file,
63
        "[%02d:%02d:%02d] [%s] ",
64
        t->tm_hour,
65
        t->tm_min,
66
        t->tm_sec,
67
        level_strings[level]
68
    );
69
70
    va_list args;
71
    va_start(args, fmt);
72
    vfprintf(log_file, fmt, args);
73
    va_end(args);
74
75
    fprintf(log_file, "\n");
76
    fflush(log_file);
77
}
78
79
int write_file(const char *path, const char *content) {
80
    LOG_INFO("Writing file: %s", path);
81
    FILE *fp = fopen(path, "w");
82
    if (!fp) {
83
        LOG_ERROR("Failed to open file for writing: %s", path);
84
        return 0;
85
    }
86
87
    if (fprintf(fp, "%s", content) < 0) {
88
        LOG_ERROR("Failed to write to file: %s", path);
89
        fclose(fp);
90
        return 0;
91
    }
92
93
    fclose(fp);
94
    LOG_DEBUG("Successfully wrote file: %s", path);
95
    return 1;
96
}
97
98
int write_file_fmt(const char *path, const char *fmt, ...) {
99
    char content[MAX_CMD_SIZE];
100
    va_list args;
101
102
    va_start(args, fmt);
103
    vsnprintf(content, sizeof(content), fmt, args);
104
    va_end(args);
105
106
    return write_file(path, content);
107
}
108
109
int set_file_perms(const char *path, mode_t mode, const char *owner, const char *group) {
110
    LOG_INFO("Setting permissions for %s: mode=%o owner=%s group=%s", path, mode, owner, group);
111
112
    if (chmod(path, mode) != 0) {
113
        LOG_ERROR("Failed to chmod %s", path);
114
        return 0;
115
    }
116
117
    char chown_cmd[512];
118
    if (strncmp(path, CHROOT_PATH, strlen(CHROOT_PATH)) == 0) {
119
        const char *chroot_path = path + strlen(CHROOT_PATH);
120
        snprintf(chown_cmd, sizeof(chown_cmd),
121
            "arch-chroot %s chown %s:%s %s 2>> /tmp/tonarchy-install.log",
122
            CHROOT_PATH, owner, group, chroot_path);
123
    } else {
124
        snprintf(chown_cmd, sizeof(chown_cmd), "chown %s:%s %s", owner, group, path);
125
    }
126
127
    if (system(chown_cmd) != 0) {
128
        LOG_ERROR("Failed to chown %s", path);
129
        return 0;
130
    }
131
132
    return 1;
133
}
134
135
int create_directory(const char *path, mode_t mode) {
136
    LOG_INFO("Creating directory: %s", path);
137
    char cmd[512];
138
    snprintf(cmd, sizeof(cmd), "mkdir -p %s", path);
139
140
    if (system(cmd) != 0) {
141
        LOG_ERROR("Failed to create directory: %s", path);
142
        return 0;
143
    }
144
145
    if (chmod(path, mode) != 0) {
146
        LOG_WARN("Failed to set permissions on directory: %s", path);
147
    }
148
149
    return 1;
150
}
151
152
int chroot_exec(const char *cmd) {
153
    char full_cmd[MAX_CMD_SIZE];
154
    snprintf(full_cmd, sizeof(full_cmd),
155
        "arch-chroot %s /bin/bash -c '%s' >> /tmp/tonarchy-install.log 2>&1",
156
        CHROOT_PATH, cmd);
157
158
    LOG_INFO("Executing in chroot: %s", cmd);
159
    LOG_DEBUG("Full command: %s", full_cmd);
160
161
    int result = system(full_cmd);
162
    if (result != 0) {
163
        LOG_ERROR("Chroot command failed (exit %d): %s", result, cmd);
164
        return 0;
165
    }
166
167
    LOG_DEBUG("Chroot command succeeded: %s", cmd);
168
    return 1;
169
}
170
171
int chroot_exec_fmt(const char *fmt, ...) {
172
    char cmd[MAX_CMD_SIZE];
173
    va_list args;
174
175
    va_start(args, fmt);
176
    vsnprintf(cmd, sizeof(cmd), fmt, args);
177
    va_end(args);
178
179
    return chroot_exec(cmd);
180
}
181
182
int chroot_exec_as_user(const char *username, const char *cmd) {
183
    char full_cmd[MAX_CMD_SIZE * 2];
184
    snprintf(full_cmd, sizeof(full_cmd), "sudo -u %s %s", username, cmd);
185
    return chroot_exec(full_cmd);
186
}
187
188
int chroot_exec_as_user_fmt(const char *username, const char *fmt, ...) {
189
    char cmd[MAX_CMD_SIZE];
190
    va_list args;
191
192
    va_start(args, fmt);
193
    vsnprintf(cmd, sizeof(cmd), fmt, args);
194
    va_end(args);
195
196
    return chroot_exec_as_user(username, cmd);
197
}
198
199
int git_clone_as_user(const char *username, const char *repo_url, const char *dest_path) {
200
    LOG_INFO("Cloning %s to %s as user %s", repo_url, dest_path, username);
201
    return chroot_exec_as_user_fmt(username, "git clone %s %s", repo_url, dest_path);
202
}
203
204
int make_clean_install(const char *build_dir) {
205
    LOG_INFO("Building and installing from %s", build_dir);
206
    return chroot_exec_fmt("cd %s && make clean install", build_dir);
207
}
208
209
int create_user_dotfile(const char *username, const Dotfile *dotfile) {
210
    char full_path[512];
211
    snprintf(full_path, sizeof(full_path), "%s/home/%s/%s", CHROOT_PATH, username, dotfile->filename);
212
213
    LOG_INFO("Creating dotfile %s for user %s", dotfile->filename, username);
214
215
    if (!write_file(full_path, dotfile->content)) {
216
        return 0;
217
    }
218
219
    if (!set_file_perms(full_path, dotfile->permissions, username, username)) {
220
        return 0;
221
    }
222
223
    return 1;
224
}
225
226
int setup_systemd_override(const Systemd_Override *override) {
227
    char dir_path[1024];
228
    char file_path[2048];
229
230
    snprintf(dir_path, sizeof(dir_path), "%s/etc/systemd/system/%s", CHROOT_PATH, override->drop_in_dir);
231
    snprintf(file_path, sizeof(file_path), "%s/%s", dir_path, override->drop_in_file);
232
233
    LOG_INFO("Setting up systemd override: %s/%s", override->drop_in_dir, override->drop_in_file);
234
235
    if (!create_directory(dir_path, 0755)) {
236
        return 0;
237
    }
238
239
    FILE *fp = fopen(file_path, "w");
240
    if (!fp) {
241
        LOG_ERROR("Failed to create systemd override file: %s", file_path);
242
        return 0;
243
    }
244
245
    for (size_t i = 0; i < override->entry_count; i++) {
246
        if (override->entries[i].value[0] == '\0') {
247
            fprintf(fp, "%s\n", override->entries[i].key);
248
        } else {
249
            fprintf(fp, "%s=%s\n", override->entries[i].key, override->entries[i].value);
250
        }
251
    }
252
253
    fclose(fp);
254
    LOG_INFO("Successfully created systemd override");
255
    return 1;
256
}
257
258
259
static void disable_raw_mode(void) {
260
    tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios);
261
}
262
263
static void enable_raw_mode(void) {
264
    tcgetattr(STDIN_FILENO, &orig_termios);
265
    atexit(disable_raw_mode);
266
267
    struct termios raw = orig_termios;
268
    raw.c_lflag &= ~(ECHO | ICANON);
269
    raw.c_cc[VMIN] = 1;
270
    raw.c_cc[VTIME] = 0;
271
272
    tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
273
}
274
275
static void get_terminal_size(int *rows, int *cols) {
276
    struct winsize ws;
277
    ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws);
278
    *rows = ws.ws_row;
279
    *cols = ws.ws_col;
280
}
281
282
static enum Terminal_Size get_terminal_size_category(int cols) {
283
    if (cols < 45) return TERM_SMALL;
284
    if (cols < 75) return TERM_MEDIUM;
285
    return TERM_LARGE;
286
}
287
288
static int get_logo_width(int cols) {
289
    enum Terminal_Size size = get_terminal_size_category(cols);
290
    if (size == TERM_LARGE) return 70;
291
    if (size == TERM_MEDIUM) return 25;
292
    return 8;
293
}
294
295
static int get_logo_start(int cols) {
296
    int width = get_logo_width(cols);
297
    int start = (cols - width) / 2;
298
    return (start < 1) ? 1 : start;
299
}
300
301
static int get_menu_start_row(int cols) {
302
    enum Terminal_Size size = get_terminal_size_category(cols);
303
    if (size == TERM_LARGE) return 10;
304
    if (size == TERM_MEDIUM) return 7;
305
    return 4;
306
}
307
308
static void clear_screen(void) {
309
    printf("\033[2J\033[H");
310
    fflush(stdout);
311
}
312
313
static void draw_logo(int cols) {
314
    const char *logo_large[] = {
315
        "████████╗ ██████╗ ███╗   ██╗ █████╗ ██████╗  ██████╗██╗  ██╗██╗   ██╗",
316
        "╚══██╔══╝██╔═══██╗████╗  ██║██╔══██╗██╔══██╗██╔════╝██║  ██║╚██╗ ██╔╝",
317
        "   ██║   ██║   ██║██╔██╗ ██║███████║██████╔╝██║     ███████║ ╚████╔╝ ",
318
        "   ██║   ██║   ██║██║╚██╗██║██╔══██║██╔══██╗██║     ██╔══██║  ╚██╔╝  ",
319
        "   ██║   ╚██████╔╝██║ ╚████║██║  ██║██║  ██║╚██████╗██║  ██║   ██║   ",
320
        "   ╚═╝    ╚═════╝ ╚═╝  ╚═══╝╚═╝  ╚═╝╚═╝  ╚═╝ ╚═════╝╚═╝  ╚═╝   ╚═╝   "
321
    };
322
323
    const char *logo_medium[] = {
324
        "╔╦╗╔═╗╔╗╔╔═╗╦═╗╔═╗╦ ╦╦ ╦",
325
        " ║ ║ ║║║║╠═╣╠╦╝║  ╠═╣╚╦╝",
326
        " ╩ ╚═╝╝╚╝╩ ╩╩╚═╚═╝╩ ╩ ╩ "
327
    };
328
329
    const char *logo_small[] = {
330
        "TONARCHY"
331
    };
332
333
    enum Terminal_Size size = get_terminal_size_category(cols);
334
    const char **logo;
335
    int logo_height;
336
    int logo_width;
337
338
    switch (size) {
339
        case TERM_LARGE:
340
            logo = logo_large;
341
            logo_height = 6;
342
            logo_width = 70;
343
            break;
344
        case TERM_MEDIUM:
345
            logo = logo_medium;
346
            logo_height = 3;
347
            logo_width = 25;
348
            break;
349
        default:
350
            logo = logo_small;
351
            logo_height = 1;
352
            logo_width = 8;
353
            break;
354
    }
355
356
    int logo_start = (cols - logo_width) / 2;
357
    if (logo_start < 1) logo_start = 1;
358
359
    printf("\033[1;32m");
360
    for (int i = 0; i < logo_height; i++) {
361
        printf("\033[%d;%dH%s", i + 2, logo_start, logo[i]);
362
    }
363
    printf("\033[0m");
364
}
365
366
static int draw_menu(const char **items, int count, int selected) {
367
    int rows, cols;
368
    get_terminal_size(&rows, &cols);
369
370
    clear_screen();
371
    draw_logo(cols);
372
373
    int logo_start = get_logo_start(cols);
374
    int menu_start_row = get_menu_start_row(cols);
375
376
    for (int i = 0; i < count; i++) {
377
        printf("\033[%d;%dH", menu_start_row + i, logo_start + 2);
378
        if (i == selected) {
379
            printf("\033[1;34m> %s\033[0m", items[i]);
380
        } else {
381
            printf("\033[37m  %s\033[0m", items[i]);
382
        }
383
    }
384
385
    printf("\033[%d;%dH", menu_start_row + count + 2, logo_start);
386
    printf("\033[33mj/k Navigate  Enter Select\033[0m");
387
388
    fflush(stdout);
389
    return 0;
390
}
391
392
static int select_from_menu(const char **items, int count) {
393
    int selected = 0;
394
395
    enable_raw_mode();
396
    draw_menu(items, count, selected);
397
398
    char c;
399
    while (read(STDIN_FILENO, &c, 1) == 1) {
400
        if (c == 'q' || c == 27) {
401
            disable_raw_mode();
402
            return -1;
403
        }
404
405
        if (c == 'j' || c == 66) {
406
            if (selected < count - 1) {
407
                selected++;
408
                draw_menu(items, count, selected);
409
            }
410
        }
411
412
        if (c == 'k' || c == 65) {
413
            if (selected > 0) {
414
                selected--;
415
                draw_menu(items, count, selected);
416
            }
417
        }
418
419
        if (c == '\r' || c == '\n') {
420
            disable_raw_mode();
421
            return selected;
422
        }
423
    }
424
425
    disable_raw_mode();
426
    return -1;
427
}
428
429
void show_message(const char *message) {
430
    int rows, cols;
431
    get_terminal_size(&rows, &cols);
432
433
    clear_screen();
434
    draw_logo(cols);
435
436
    int logo_start = get_logo_start(cols);
437
    int msg_row = get_menu_start_row(cols);
438
439
    printf("\033[%d;%dH", msg_row, logo_start);
440
    printf("\033[37m%s\033[0m", message);
441
    fflush(stdout);
442
443
    sleep(2);
444
}
445
446
static int check_internet_connection(void) {
447
    int result = system("ping -c 1 -W 2 1.1.1.1 > /dev/null 2>&1");
448
    return result == 0;
449
}
450
451
static int list_wifi_networks(char networks[][256], char ssids[][128], int max_networks) {
452
    FILE *fp = popen("nmcli -t -f SSID,SIGNAL,SECURITY device wifi list | head -20", "r");
453
    if (!fp) {
454
        return 0;
455
    }
456
457
    int count = 0;
458
    char line[512];
459
    while (count < max_networks && fgets(line, sizeof(line), fp) != NULL) {
460
        line[strcspn(line, "\n")] = '\0';
461
462
        char ssid[128], signal[32], security[64];
463
        char *token = strtok(line, ":");
464
        if (token) strncpy(ssid, token, sizeof(ssid) - 1);
465
        token = strtok(NULL, ":");
466
        if (token) strncpy(signal, token, sizeof(signal) - 1);
467
        token = strtok(NULL, ":");
468
        if (token) strncpy(security, token, sizeof(security) - 1);
469
470
        if (strlen(ssid) > 0 && strcmp(ssid, "--") != 0) {
471
            snprintf(ssids[count], 128, "%s", ssid);
472
            snprintf(networks[count], 255, "%s (%s%%) [%s]", ssid, signal,
473
                     strlen(security) > 0 ? security : "Open");
474
            count++;
475
        }
476
    }
477
    pclose(fp);
478
    return count;
479
}
480
481
static int connect_to_wifi(const char *ssid) {
482
    int rows, cols;
483
    get_terminal_size(&rows, &cols);
484
485
    clear_screen();
486
    draw_logo(cols);
487
488
    int logo_start = get_logo_start(cols);
489
    printf("\033[%d;%dH\033[37mConnecting to: %s\033[0m", 10, logo_start, ssid);
490
    printf("\033[%d;%dH\033[37mEnter password (leave empty if open): \033[0m", 12, logo_start);
491
    fflush(stdout);
492
493
    char password[256] = "";
494
    struct termios old_term;
495
    tcgetattr(STDIN_FILENO, &old_term);
496
    struct termios new_term = old_term;
497
    new_term.c_lflag &= ~ECHO;
498
    new_term.c_lflag |= ICANON;
499
    tcsetattr(STDIN_FILENO, TCSAFLUSH, &new_term);
500
501
    if (fgets(password, sizeof(password), stdin) != NULL) {
502
        password[strcspn(password, "\n")] = '\0';
503
    }
504
505
    tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_term);
506
507
    clear_screen();
508
    draw_logo(cols);
509
    printf("\033[%d;%dH\033[37mConnecting...\033[0m", 10, logo_start);
510
    fflush(stdout);
511
512
    char cmd[512];
513
    if (strlen(password) > 0) {
514
        snprintf(cmd, sizeof(cmd), "nmcli device wifi connect '%s' password '%s' > /dev/null 2>&1", ssid, password);
515
    } else {
516
        snprintf(cmd, sizeof(cmd), "nmcli device wifi connect '%s' > /dev/null 2>&1", ssid);
517
    }
518
519
    int result = system(cmd);
520
    sleep(2);
521
522
    if (result == 0 && check_internet_connection()) {
523
        show_message("Connected successfully!");
524
        return 1;
525
    } else {
526
        show_message("Connection failed. Please try again.");
527
        return 0;
528
    }
529
}
530
531
static int setup_wifi_if_needed(void) {
532
    if (check_internet_connection()) {
533
        return 1;
534
    }
535
536
    int rows, cols;
537
    get_terminal_size(&rows, &cols);
538
539
    clear_screen();
540
    draw_logo(cols);
541
542
    int logo_start = get_logo_start(cols);
543
    printf("\033[%d;%dH\033[37mNo internet connection detected.\033[0m", 10, logo_start);
544
    printf("\033[%d;%dH\033[37mScanning for WiFi networks...\033[0m", 11, logo_start);
545
    fflush(stdout);
546
547
    system("nmcli radio wifi on > /dev/null 2>&1");
548
    sleep(1);
549
550
    char networks[32][256];
551
    char ssids[32][128];
552
    int network_count = list_wifi_networks(networks, ssids, 32);
553
554
    if (network_count == 0) {
555
        show_message("No WiFi networks found. Please check your connection.");
556
        return 0;
557
    }
558
559
    const char *network_ptrs[32];
560
    for (int i = 0; i < network_count; i++) {
561
        network_ptrs[i] = networks[i];
562
    }
563
564
    int selected = select_from_menu(network_ptrs, network_count);
565
    if (selected < 0) {
566
        show_message("WiFi setup cancelled. Installation requires internet.");
567
        return 0;
568
    }
569
570
    return connect_to_wifi(ssids[selected]);
571
}
572
573
static void draw_form(
574
        const char *username,
575
        const char *password,
576
        const char *confirmed_password,
577
        const char *hostname,
578
        const char *keyboard,
579
        const char *timezone,
580
        int current_field
581
    ) {
582
    int rows, cols;
583
    get_terminal_size(&rows, &cols);
584
585
    clear_screen();
586
    draw_logo(cols);
587
588
    int logo_start = get_logo_start(cols);
589
    int form_row = 10;
590
591
    printf(ANSI_CURSOR_POS ANSI_WHITE "Setup your system:" ANSI_RESET, form_row, logo_start);
592
    form_row += 2;
593
594
    Tui_Field fields[] = {
595
        {"Username",         username,           NULL,       0},
596
        {"Password",         password,           NULL,       1},
597
        {"Confirm Password", confirmed_password, NULL,       1},
598
        {"Hostname",         hostname,           "tonarchy", 0},
599
        {"Keyboard",         keyboard,           "us",       0},
600
        {"Timezone",         timezone,           NULL,       0},
601
    };
602
    int num_fields = (int)(sizeof(fields) / sizeof(fields[0]));
603
604
    for (int i = 0; i < num_fields; i++) {
605
        printf(ANSI_CURSOR_POS, form_row + i, logo_start);
606
607
        if (current_field == i) {
608
            printf(ANSI_BLUE_BOLD ">" ANSI_RESET " ");
609
        } else {
610
            printf("  ");
611
        }
612
613
        printf(ANSI_WHITE "%s: " ANSI_RESET, fields[i].label);
614
615
        if (strlen(fields[i].value) > 0) {
616
            printf(ANSI_GREEN "%s" ANSI_RESET, fields[i].is_password ? "********" : fields[i].value);
617
        } else if (current_field != i) {
618
            if (fields[i].default_display) {
619
                printf(ANSI_GRAY "%s" ANSI_RESET, fields[i].default_display);
620
            } else {
621
                printf(ANSI_GRAY "[not set]" ANSI_RESET);
622
            }
623
        }
624
    }
625
626
    fflush(stdout);
627
}
628
629
static int validate_alphanumeric(const char *s) {
630
    for (int i = 0; s[i]; i++) {
631
        if (!isalnum(s[i]) && s[i] != '-' && s[i] != '_')
632
            return 0;
633
    }
634
    return 1;
635
}
636
637
static int read_line(char *buf, int size, int echo) {
638
    struct termios old_term;
639
    tcgetattr(STDIN_FILENO, &old_term);
640
    struct termios new_term = old_term;
641
    if (echo)
642
        new_term.c_lflag |= ECHO;
643
    else
644
        new_term.c_lflag &= ~ECHO;
645
    new_term.c_lflag |= ICANON;
646
    new_term.c_lflag &= ~ISIG;
647
    tcsetattr(STDIN_FILENO, TCSAFLUSH, &new_term);
648
649
    int result = (fgets(buf, size, stdin) != NULL);
650
    if (result)
651
        buf[strcspn(buf, "\n")] = '\0';
652
653
    tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_term);
654
    return result;
655
}
656
657
static int fzf_select(char *dest, const char *cmd, const char *default_val) {
658
    clear_screen();
659
    FILE *fp = popen(cmd, "r");
660
    if (fp == NULL)
661
        return 0;
662
663
    char buf[256];
664
    if (fgets(buf, sizeof(buf), fp) != NULL) {
665
        buf[strcspn(buf, "\n")] = '\0';
666
        if (strlen(buf) > 0)
667
            strcpy(dest, buf);
668
    }
669
    pclose(fp);
670
671
    if (strlen(dest) == 0 && default_val)
672
        strcpy(dest, default_val);
673
674
    return 1;
675
}
676
677
static int handle_password_entry(
678
        char *password,
679
        char *confirmed_password,
680
        int form_row,
681
        int logo_start,
682
        char *username,
683
        char *hostname,
684
        char *keyboard,
685
        char *timezone
686
    ) {
687
    char temp_input[256];
688
    char password_confirm[256];
689
690
    printf(ANSI_CURSOR_POS, form_row + 1, logo_start + 13);
691
    fflush(stdout);
692
693
    if (!read_line(temp_input, sizeof(temp_input), 0))
694
        return -1;
695
696
    if (strlen(temp_input) == 0) {
697
        show_message("Password cannot be empty");
698
        return 0;
699
    }
700
701
    strcpy(password, temp_input);
702
703
    draw_form(username, password, confirmed_password, hostname, keyboard, timezone, 2);
704
    printf(ANSI_CURSOR_POS, form_row + 2, logo_start + 20);
705
    fflush(stdout);
706
707
    if (!read_line(password_confirm, sizeof(password_confirm), 0))
708
        return -1;
709
710
    if (strcmp(password, password_confirm) == 0) {
711
        strcpy(confirmed_password, password_confirm);
712
        return 1;
713
    } else {
714
        show_message("Passwords do not match");
715
        return 0;
716
    }
717
}
718
719
static int get_form_input(
720
        char *username,
721
        char *password,
722
        char *confirmed_password,
723
        char *hostname,
724
        char *keyboard,
725
        char *timezone
726
    ) {
727
    char temp_input[256];
728
    int rows, cols;
729
    get_terminal_size(&rows, &cols);
730
    int logo_start = get_logo_start(cols);
731
    int form_row = 12;
732
733
    Form_Field fields[] = {
734
        {username, NULL,       INPUT_TEXT,         13, "Username must be alphanumeric"},
735
        {password, NULL,       INPUT_PASSWORD,     13, NULL},
736
        {confirmed_password, NULL, INPUT_PASSWORD, 20, NULL},
737
        {hostname, "tonarchy", INPUT_TEXT,         13, "Hostname must be alphanumeric"},
738
        {keyboard, "us",       INPUT_FZF_KEYMAP,   0,  NULL},
739
        {timezone, NULL,       INPUT_FZF_TIMEZONE, 0,  "Timezone is required"},
740
    };
741
    int num_fields = (int)(sizeof(fields) / sizeof(fields[0]));
742
743
    int current_field = 0;
744
    while (current_field < num_fields) {
745
        draw_form(username, password, confirmed_password, hostname, keyboard, timezone, current_field);
746
        Form_Field *f = &fields[current_field];
747
748
        if (f->type == INPUT_FZF_KEYMAP) {
749
            fzf_select(keyboard, "localectl list-keymaps | fzf --height=40% --reverse --prompt='Keyboard: ' --header='Start typing to filter, Enter to select' --query='us'", "us");
750
            current_field++;
751
        } else if (f->type == INPUT_FZF_TIMEZONE) {
752
            fzf_select(timezone, "timedatectl list-timezones | fzf --height=40% --reverse --prompt='Timezone: ' --header='Type your city/timezone, Enter to select'", NULL);
753
            if (strlen(timezone) == 0) {
754
                show_message("Timezone is required");
755
            } else {
756
                current_field++;
757
            }
758
        } else if (current_field == 1) {
759
            int result = handle_password_entry(
760
                password,
761
                confirmed_password,
762
                form_row,
763
                logo_start,
764
                username,
765
                hostname,
766
                keyboard,
767
                timezone
768
            );
769
            if (result == -1) return 0;
770
            if (result == 1) current_field = 3;
771
        } else if (current_field == 2) {
772
            current_field = 1;
773
        } else {
774
            printf(ANSI_CURSOR_POS, form_row + current_field, logo_start + f->cursor_offset);
775
            fflush(stdout);
776
777
            if (!read_line(temp_input, sizeof(temp_input), 1))
778
                return 0;
779
780
            if (strlen(temp_input) == 0) {
781
                if (f->default_val) {
782
                    strcpy(f->dest, f->default_val);
783
                    current_field++;
784
                }
785
            } else if (validate_alphanumeric(temp_input)) {
786
                strcpy(f->dest, temp_input);
787
                current_field++;
788
            } else {
789
                show_message(f->error_msg);
790
            }
791
        }
792
    }
793
794
    while (1) {
795
        draw_form(username, password, confirmed_password, hostname, keyboard, timezone, 6);
796
797
        get_terminal_size(&rows, &cols);
798
        logo_start = get_logo_start(cols);
799
800
        printf(ANSI_CURSOR_POS ANSI_YELLOW "Press Enter to continue, or field number to edit (0-5)" ANSI_RESET, 20, logo_start);
801
        fflush(stdout);
802
803
        enable_raw_mode();
804
        char c;
805
        if (read(STDIN_FILENO, &c, 1) == 1) {
806
            if (c == '\r' || c == '\n') {
807
                disable_raw_mode();
808
                return 1;
809
            }
810
            if (c == 'q' || c == 27) {
811
                disable_raw_mode();
812
                return 0;
813
            }
814
            if (c >= '0' && c <= '5') {
815
                disable_raw_mode();
816
                int edit_field = c - '0';
817
                Form_Field *f = &fields[edit_field];
818
819
                if (f->type == INPUT_FZF_KEYMAP) {
820
                    fzf_select(keyboard, "localectl list-keymaps | fzf --height=40% --reverse --prompt='Keyboard: ' --header='Start typing to filter, Enter to select' --query='us'", "us");
821
                } else if (f->type == INPUT_FZF_TIMEZONE) {
822
                    fzf_select(timezone, "timedatectl list-timezones | fzf --height=40% --reverse --prompt='Timezone: ' --header='Type your city/timezone, Enter to select'", NULL);
823
                    if (strlen(timezone) == 0)
824
                        show_message("Timezone is required");
825
                } else if (edit_field == 1 || edit_field == 2) {
826
                    draw_form(username, password, confirmed_password, hostname, keyboard, timezone, 1);
827
                    handle_password_entry(password, confirmed_password,
828
                                          form_row, logo_start,
829
                                          username, hostname, keyboard, timezone);
830
                } else {
831
                    draw_form(username, password, confirmed_password, hostname, keyboard, timezone, edit_field);
832
                    printf(ANSI_CURSOR_POS, form_row + edit_field, logo_start + f->cursor_offset);
833
                    fflush(stdout);
834
835
                    if (read_line(temp_input, sizeof(temp_input), 1)) {
836
                        if (strlen(temp_input) == 0 && f->default_val) {
837
                            strcpy(f->dest, f->default_val);
838
                        } else if (strlen(temp_input) > 0) {
839
                            if (validate_alphanumeric(temp_input))
840
                                strcpy(f->dest, temp_input);
841
                            else
842
                                show_message(f->error_msg);
843
                        }
844
                    }
845
                }
846
                continue;
847
            }
848
        }
849
        disable_raw_mode();
850
    }
851
852
    return 1;
853
}
854
855
static int select_disk(char *disk_name) {
856
    clear_screen();
857
858
    FILE *fp = popen("lsblk -d -n -o NAME,SIZE,MODEL,TYPE | grep -E '(disk|nvme)' | grep -v -E '(loop|rom|airoot)' | awk '{printf \"%s (%s) %s\\n\", $1, $2, substr($0, index($0,$3))}'", "r");
859
    if (fp == NULL) {
860
        show_message("Failed to list disks");
861
        return 0;
862
    }
863
864
    char disks[32][256];
865
    char names[32][64];
866
    int disk_count = 0;
867
868
    while (disk_count < 32 && fgets(disks[disk_count], sizeof(disks[0]), fp) != NULL) {
869
        disks[disk_count][strcspn(disks[disk_count], "\n")] = '\0';
870
        sscanf(disks[disk_count], "%s", names[disk_count]);
871
        disk_count++;
872
    }
873
    pclose(fp);
874
875
    if (disk_count == 0) {
876
        show_message("No disks found");
877
        return 0;
878
    }
879
880
    const char *disk_ptrs[32];
881
    for (int i = 0; i < disk_count; i++) {
882
        disk_ptrs[i] = disks[i];
883
    }
884
885
    int selected = select_from_menu(disk_ptrs, disk_count);
886
    if (selected < 0) {
887
        return 0;
888
    }
889
890
    strcpy(disk_name, names[selected]);
891
892
    int rows, cols;
893
    get_terminal_size(&rows, &cols);
894
    clear_screen();
895
    draw_logo(cols);
896
897
    int logo_start = get_logo_start(cols);
898
    printf("\033[%d;%dH\033[37mWARNING: All data on \033[31m/dev/%s\033[37m will be destroyed!\033[0m", 10, logo_start, disk_name);
899
    printf("\033[%d;%dH\033[37mType 'yes' to confirm: \033[0m", 12, logo_start);
900
    fflush(stdout);
901
902
    char confirm[256];
903
    struct termios old_term;
904
    tcgetattr(STDIN_FILENO, &old_term);
905
    struct termios new_term = old_term;
906
    new_term.c_lflag |= (ECHO | ICANON);
907
    new_term.c_lflag &= ~ISIG;
908
    tcsetattr(STDIN_FILENO, TCSAFLUSH, &new_term);
909
910
    if (fgets(confirm, sizeof(confirm), stdin) == NULL) {
911
        tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_term);
912
        return 0;
913
    }
914
    confirm[strcspn(confirm, "\n")] = '\0';
915
    tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_term);
916
917
    if (strcmp(confirm, "yes") != 0) {
918
        show_message("Installation cancelled");
919
        return 0;
920
    }
921
922
    return 1;
923
}
924
925
static int partition_disk(const char *disk) {
926
    char cmd[1024];
927
    int rows, cols;
928
    get_terminal_size(&rows, &cols);
929
930
    clear_screen();
931
    draw_logo(cols);
932
933
    int logo_start = get_logo_start(cols);
934
    int uefi = is_uefi_system();
935
936
    char part1[64], part2[64], part3[64];
937
    part_path(part1, sizeof(part1), disk, 1);
938
    part_path(part2, sizeof(part2), disk, 2);
939
    part_path(part3, sizeof(part3), disk, 3);
940
941
    printf("\033[%d;%dH\033[37mPartitioning /dev/%s (%s mode)...\033[0m", 10, logo_start, disk, uefi ? "UEFI" : "BIOS");
942
    fflush(stdout);
943
944
    LOG_INFO("Starting disk partitioning: /dev/%s (mode: %s)", disk, uefi ? "UEFI" : "BIOS");
945
946
    snprintf(cmd, sizeof(cmd), "wipefs -af /dev/%s 2>> /tmp/tonarchy-install.log", disk);
947
    if (system(cmd) != 0) {
948
        LOG_ERROR("Failed to wipe disk: /dev/%s", disk);
949
        show_message("Failed to wipe disk");
950
        return 0;
951
    }
952
    LOG_INFO("Wiped disk");
953
954
    if (uefi) {
955
        snprintf(cmd, sizeof(cmd), "sgdisk --zap-all /dev/%s 2>> /tmp/tonarchy-install.log", disk);
956
        if (system(cmd) != 0) {
957
            LOG_ERROR("Failed to zap disk: /dev/%s", disk);
958
            show_message("Failed to zap disk");
959
            return 0;
960
        }
961
        LOG_INFO("Zapped disk (GPT)");
962
963
        snprintf(cmd, sizeof(cmd),
964
            "sgdisk --clear "
965
            "--new=1:0:+1G --typecode=1:ef00 --change-name=1:EFI "
966
            "--new=2:0:+4G --typecode=2:8200 --change-name=2:swap "
967
            "--new=3:0:0 --typecode=3:8300 --change-name=3:root "
968
            "/dev/%s 2>> /tmp/tonarchy-install.log", disk);
969
        if (system(cmd) != 0) {
970
            LOG_ERROR("Failed to create partitions on /dev/%s", disk);
971
            show_message("Failed to create partitions");
972
            return 0;
973
        }
974
        LOG_INFO("Created partitions (EFI, swap, root)");
975
    } else {
976
        snprintf(cmd, sizeof(cmd),
977
            "parted -s /dev/%s mklabel msdos "
978
            "mkpart primary linux-swap 1MiB 4GiB "
979
            "mkpart primary ext4 4GiB 100%% "
980
            "set 2 boot on 2>> /tmp/tonarchy-install.log", disk);
981
        if (system(cmd) != 0) {
982
            LOG_ERROR("Failed to create MBR partitions on /dev/%s", disk);
983
            show_message("Failed to create partitions");
984
            return 0;
985
        }
986
        LOG_INFO("Created MBR partitions (swap, root)");
987
    }
988
989
    printf("\033[%d;%dH\033[37mFormatting partitions...\033[0m", 11, logo_start);
990
    fflush(stdout);
991
992
    if (uefi) {
993
        snprintf(cmd, sizeof(cmd), "mkfs.fat -F32 %s 2>> /tmp/tonarchy-install.log", part1);
994
        if (system(cmd) != 0) {
995
            LOG_ERROR("Failed to format EFI partition: %s", part1);
996
            show_message("Failed to format EFI partition");
997
            return 0;
998
        }
999
        LOG_INFO("Formatted EFI partition");
1000
1001
        snprintf(cmd, sizeof(cmd), "mkswap %s 2>> /tmp/tonarchy-install.log", part2);
1002
        if (system(cmd) != 0) {
1003
            LOG_ERROR("Failed to format swap: %s", part2);
1004
            show_message("Failed to format swap partition");
1005
            return 0;
1006
        }
1007
        LOG_INFO("Formatted swap partition");
1008
1009
        snprintf(cmd, sizeof(cmd), "mkfs.ext4 -F %s 2>> /tmp/tonarchy-install.log", part3);
1010
        if (system(cmd) != 0) {
1011
            LOG_ERROR("Failed to format root: %s", part3);
1012
            show_message("Failed to format root partition");
1013
            return 0;
1014
        }
1015
        LOG_INFO("Formatted root partition");
1016
    } else {
1017
        snprintf(cmd, sizeof(cmd), "mkswap %s 2>> /tmp/tonarchy-install.log", part1);
1018
        if (system(cmd) != 0) {
1019
            LOG_ERROR("Failed to format swap: %s", part1);
1020
            show_message("Failed to format swap partition");
1021
            return 0;
1022
        }
1023
        LOG_INFO("Formatted swap partition");
1024
1025
        snprintf(cmd, sizeof(cmd), "mkfs.ext4 -F %s 2>> /tmp/tonarchy-install.log", part2);
1026
        if (system(cmd) != 0) {
1027
            LOG_ERROR("Failed to format root: %s", part2);
1028
            show_message("Failed to format root partition");
1029
            return 0;
1030
        }
1031
        LOG_INFO("Formatted root partition");
1032
    }
1033
1034
    printf("\033[%d;%dH\033[37mMounting partitions...\033[0m", 12, logo_start);
1035
    fflush(stdout);
1036
1037
    if (uefi) {
1038
        snprintf(cmd, sizeof(cmd), "mount %s /mnt 2>> /tmp/tonarchy-install.log", part3);
1039
        if (system(cmd) != 0) {
1040
            LOG_ERROR("Failed to mount root: %s", part3);
1041
            show_message("Failed to mount root partition");
1042
            return 0;
1043
        }
1044
        LOG_INFO("Mounted root partition");
1045
1046
        snprintf(cmd, sizeof(cmd), "mkdir -p /mnt/boot 2>> /tmp/tonarchy-install.log");
1047
        system(cmd);
1048
1049
        snprintf(cmd, sizeof(cmd), "mount %s /mnt/boot 2>> /tmp/tonarchy-install.log", part1);
1050
        if (system(cmd) != 0) {
1051
            LOG_ERROR("Failed to mount EFI: %s", part1);
1052
            show_message("Failed to mount EFI partition");
1053
            return 0;
1054
        }
1055
        LOG_INFO("Mounted EFI partition");
1056
1057
        snprintf(cmd, sizeof(cmd), "swapon %s 2>> /tmp/tonarchy-install.log", part2);
1058
        if (system(cmd) != 0) {
1059
            LOG_ERROR("Failed to enable swap: %s", part2);
1060
            show_message("Failed to enable swap");
1061
            return 0;
1062
        }
1063
    } else {
1064
        snprintf(cmd, sizeof(cmd), "mount %s /mnt 2>> /tmp/tonarchy-install.log", part2);
1065
        if (system(cmd) != 0) {
1066
            LOG_ERROR("Failed to mount root: %s", part2);
1067
            show_message("Failed to mount root partition");
1068
            return 0;
1069
        }
1070
        LOG_INFO("Mounted root partition");
1071
1072
        snprintf(cmd, sizeof(cmd), "swapon %s 2>> /tmp/tonarchy-install.log", part1);
1073
        if (system(cmd) != 0) {
1074
            LOG_ERROR("Failed to enable swap: %s", part1);
1075
            show_message("Failed to enable swap");
1076
            return 0;
1077
        }
1078
    }
1079
    LOG_INFO("Enabled swap");
1080
    LOG_INFO("Disk partitioning completed successfully");
1081
1082
    show_message("Disk prepared successfully!");
1083
    return 1;
1084
}
1085
1086
static int install_packages_impl(const char *package_list) {
1087
    int rows, cols;
1088
    get_terminal_size(&rows, &cols);
1089
1090
    clear_screen();
1091
    draw_logo(cols);
1092
1093
    int logo_start = get_logo_start(cols);
1094
    printf("\033[%d;%dH\033[37mInstalling system packages...\033[0m", 10, logo_start);
1095
    printf("\033[%d;%dH\033[37mThis will take several minutes.\033[0m", 11, logo_start);
1096
    fflush(stdout);
1097
1098
    LOG_INFO("Starting package installation");
1099
    LOG_INFO("Packages: %s", package_list);
1100
1101
    char cmd[4096];
1102
    snprintf(cmd, sizeof(cmd), "pacstrap -K /mnt %s 2>> /tmp/tonarchy-install.log", package_list);
1103
1104
    int result = system(cmd);
1105
    if (result != 0) {
1106
        LOG_ERROR("pacstrap failed with exit code %d", result);
1107
        show_message("Failed to install packages");
1108
        return 0;
1109
    }
1110
1111
    LOG_INFO("Package installation completed successfully");
1112
    show_message("Packages installed successfully!");
1113
    return 1;
1114
}
1115
1116
static int configure_system_impl(
1117
        const char *username,
1118
        const char *password,
1119
        const char *hostname,
1120
        const char *keyboard,
1121
        const char *timezone,
1122
        const char *disk,
1123
        int use_dm
1124
    ) {
1125
    (void)disk;
1126
    int rows, cols;
1127
    get_terminal_size(&rows, &cols);
1128
1129
    clear_screen();
1130
    draw_logo(cols);
1131
1132
    int logo_start = get_logo_start(cols);
1133
    printf("\033[%d;%dH\033[37mConfiguring system...\033[0m", 10, logo_start);
1134
    printf("\033[%d;%dH\033[90m(Logging to /tmp/tonarchy-install.log)\033[0m", 11, logo_start);
1135
    fflush(stdout);
1136
1137
    LOG_INFO("Starting system configuration");
1138
    LOG_INFO("User: %s, Hostname: %s, Timezone: %s, Keyboard: %s", username, hostname, timezone, keyboard);
1139
1140
    CHECK_OR_FAIL(
1141
        system("genfstab -U /mnt >> /mnt/etc/fstab 2>> /tmp/tonarchy-install.log") == 0,
1142
        "Failed to generate fstab - check /tmp/tonarchy-install.log"
1143
    );
1144
1145
    CHECK_OR_FAIL(
1146
        chroot_exec_fmt("ln -sf /usr/share/zoneinfo/%s /etc/localtime", timezone),
1147
        "Failed to configure timezone"
1148
    );
1149
1150
    if (!chroot_exec("hwclock --systohc")) {
1151
        LOG_WARN("Failed to set hardware clock");
1152
    }
1153
1154
    CHECK_OR_FAIL(
1155
        write_file("/mnt/etc/locale.gen", "en_US.UTF-8 UTF-8\n"),
1156
        "Failed to write locale.gen"
1157
    );
1158
1159
    CHECK_OR_FAIL(
1160
        chroot_exec("locale-gen"),
1161
        "Failed to generate locales"
1162
    );
1163
1164
    CHECK_OR_FAIL(
1165
        write_file("/mnt/etc/locale.conf", "LANG=en_US.UTF-8\n"),
1166
        "Failed to write locale.conf"
1167
    );
1168
1169
    CHECK_OR_FAIL(
1170
        write_file_fmt("/mnt/etc/vconsole.conf", "KEYMAP=%s\n", keyboard),
1171
        "Failed to write vconsole.conf"
1172
    );
1173
1174
    CHECK_OR_FAIL(
1175
        write_file_fmt("/mnt/etc/hostname", "%s\n", hostname),
1176
        "Failed to write hostname"
1177
    );
1178
1179
    char hosts_content[512];
1180
    snprintf(hosts_content, sizeof(hosts_content),
1181
             "127.0.0.1   localhost\n"
1182
             "::1         localhost\n"
1183
             "127.0.1.1   %s.localdomain %s\n",
1184
             hostname, hostname);
1185
1186
    CHECK_OR_FAIL(
1187
        write_file("/mnt/etc/hosts", hosts_content),
1188
        "Failed to write hosts file"
1189
    );
1190
1191
    CHECK_OR_FAIL(
1192
        chroot_exec_fmt("useradd -m -G wheel -s /bin/bash %s", username),
1193
        "Failed to create user"
1194
    );
1195
1196
    FILE *chpasswd_pipe = popen("arch-chroot /mnt chpasswd 2>> /tmp/tonarchy-install.log", "w");
1197
    if (!chpasswd_pipe) {
1198
        show_message("Failed to open chpasswd");
1199
        return 0;
1200
    }
1201
    fprintf(chpasswd_pipe, "%s:%s\n", username, password);
1202
    fprintf(chpasswd_pipe, "root:%s\n", password);
1203
    int chpasswd_status = pclose(chpasswd_pipe);
1204
    CHECK_OR_FAIL(
1205
        chpasswd_status == 0,
1206
        "Failed to set passwords"
1207
    );
1208
1209
    create_directory("/mnt/etc/sudoers.d", 0750);
1210
    CHECK_OR_FAIL(
1211
        write_file("/mnt/etc/sudoers.d/wheel", "%wheel ALL=(ALL:ALL) ALL\n"),
1212
        "Failed to configure sudo"
1213
    );
1214
    chmod("/mnt/etc/sudoers.d/wheel", 0440);
1215
1216
    CHECK_OR_FAIL(
1217
        chroot_exec("systemctl enable NetworkManager"),
1218
        "Failed to enable NetworkManager"
1219
    );
1220
1221
    CHECK_OR_FAIL(
1222
        chroot_exec("systemctl enable dbus"),
1223
        "Failed to enable dbus"
1224
    );
1225
1226
    if (use_dm) {
1227
        CHECK_OR_FAIL(
1228
            chroot_exec("systemctl enable lightdm"),
1229
            "Failed to enable display manager"
1230
        );
1231
    }
1232
1233
    LOG_INFO("System configuration completed successfully");
1234
    show_message("System configured successfully!");
1235
    return 1;
1236
}
1237
1238
static int get_root_uuid(const char *disk, char *uuid_out, size_t uuid_size) {
1239
    char cmd[512];
1240
    char root_part[64];
1241
1242
    part_path(root_part, sizeof(root_part), disk, 3);
1243
    snprintf(cmd, sizeof(cmd), "blkid -s UUID -o value %s", root_part);
1244
1245
    FILE *fp = popen(cmd, "r");
1246
    if (!fp) {
1247
        LOG_ERROR("Failed to get UUID for %s", root_part);
1248
        return 0;
1249
    }
1250
1251
    if (fgets(uuid_out, uuid_size, fp) == NULL) {
1252
        pclose(fp);
1253
        LOG_ERROR("Failed to read UUID for %s", root_part);
1254
        return 0;
1255
    }
1256
1257
    uuid_out[strcspn(uuid_out, "\n")] = '\0';
1258
    pclose(fp);
1259
1260
    if (strlen(uuid_out) == 0) {
1261
        LOG_ERROR("Empty UUID for %s", root_part);
1262
        return 0;
1263
    }
1264
1265
    LOG_INFO("Root partition UUID: %s", uuid_out);
1266
    return 1;
1267
}
1268
1269
static int install_bootloader(const char *disk) {
1270
    char cmd[2048];
1271
    int rows, cols;
1272
    get_terminal_size(&rows, &cols);
1273
1274
    clear_screen();
1275
    draw_logo(cols);
1276
1277
    int logo_start = get_logo_start(cols);
1278
    int uefi = is_uefi_system();
1279
1280
    printf("\033[%d;%dH\033[37mInstalling bootloader (%s)...\033[0m", 10, logo_start, uefi ? "systemd-boot" : "GRUB");
1281
    fflush(stdout);
1282
1283
    if (uefi) {
1284
        LOG_INFO("Installing systemd-boot");
1285
1286
        if (!chroot_exec("bootctl install")) {
1287
            LOG_ERROR("bootctl install failed");
1288
            show_message("Failed to install bootloader");
1289
            return 0;
1290
        }
1291
1292
        char uuid[128];
1293
        if (!get_root_uuid(disk, uuid, sizeof(uuid))) {
1294
            show_message("Failed to get root partition UUID");
1295
            return 0;
1296
        }
1297
1298
        LOG_INFO("Creating loader.conf");
1299
        if (!write_file("/mnt/boot/loader/loader.conf",
1300
            "default arch.conf\n"
1301
            "timeout 3\n"
1302
            "console-mode max\n"
1303
            "editor no\n")) {
1304
            LOG_ERROR("Failed to write loader.conf");
1305
            show_message("Failed to create loader config");
1306
            return 0;
1307
        }
1308
1309
        if (!create_directory("/mnt/boot/loader/entries", 0755)) {
1310
            LOG_ERROR("Failed to create boot entries directory");
1311
            return 0;
1312
        }
1313
1314
        char boot_entry[512];
1315
        snprintf(boot_entry, sizeof(boot_entry),
1316
            "title   Tonarchy\n"
1317
            "linux   /vmlinuz-linux\n"
1318
            "initrd  /initramfs-linux.img\n"
1319
            "options root=UUID=%s rw\n",
1320
            uuid);
1321
1322
        LOG_INFO("Creating boot entry");
1323
        if (!write_file("/mnt/boot/loader/entries/arch.conf", boot_entry)) {
1324
            LOG_ERROR("Failed to write boot entry");
1325
            show_message("Failed to create boot entry");
1326
            return 0;
1327
        }
1328
1329
        struct stat st;
1330
        if (stat("/mnt/boot/loader/entries/arch.conf", &st) != 0) {
1331
            LOG_ERROR("Boot entry file missing after creation");
1332
            show_message("Boot entry verification failed");
1333
            return 0;
1334
        }
1335
1336
        LOG_INFO("Syncing filesystem");
1337
        sync();
1338
        sleep(1);
1339
1340
        if (!chroot_exec("sync")) {
1341
            LOG_WARN("Failed to sync in chroot");
1342
        }
1343
1344
        LOG_INFO("systemd-boot installation completed");
1345
    } else {
1346
        snprintf(cmd, sizeof(cmd),
1347
            "arch-chroot /mnt pacman -S --noconfirm grub 2>> /tmp/tonarchy-install.log");
1348
        if (system(cmd) != 0) {
1349
            show_message("Failed to install GRUB package");
1350
            return 0;
1351
        }
1352
1353
        snprintf(cmd, sizeof(cmd),
1354
            "arch-chroot /mnt grub-install --target=i386-pc /dev/%s 2>> /tmp/tonarchy-install.log",
1355
            disk);
1356
        if (system(cmd) != 0) {
1357
            show_message("Failed to install GRUB");
1358
            return 0;
1359
        }
1360
1361
        snprintf(cmd, sizeof(cmd),
1362
            "arch-chroot /mnt grub-mkconfig -o /boot/grub/grub.cfg 2>> /tmp/tonarchy-install.log");
1363
        if (system(cmd) != 0) {
1364
            show_message("Failed to generate GRUB config");
1365
            return 0;
1366
        }
1367
    }
1368
1369
    show_message("Bootloader installed successfully!");
1370
    return 1;
1371
}
1372
1373
static int setup_common_configs(const char *username) {
1374
    char cmd[4096];
1375
1376
    create_directory("/mnt/usr/share/wallpapers", 0755);
1377
    system("cp /usr/share/wallpapers/wall1.jpg /mnt/usr/share/wallpapers/wall1.jpg");
1378
1379
    create_directory("/mnt/usr/share/tonarchy", 0755);
1380
    system("cp /usr/share/tonarchy/favicon.png /mnt/usr/share/tonarchy/favicon.png");
1381
1382
    create_directory("/mnt/usr/share/themes", 0755);
1383
    system("cp -r /usr/share/tonarchy/Tokyonight-Dark /mnt/usr/share/themes/");
1384
1385
    LOG_INFO("Setting up Firefox profile");
1386
    snprintf(cmd, sizeof(cmd), "/mnt/home/%s/.config/firefox", username);
1387
    create_directory(cmd, 0755);
1388
1389
    snprintf(cmd, sizeof(cmd), "cp -r /usr/share/tonarchy/firefox/default-release/* /mnt/home/%s/.config/firefox/", username);
1390
    system(cmd);
1391
1392
    snprintf(cmd, sizeof(cmd), "arch-chroot /mnt chown -R %s:%s /home/%s/.config/firefox", username, username, username);
1393
    system(cmd);
1394
1395
    create_directory("/mnt/usr/lib/firefox/distribution", 0755);
1396
    system("cp /usr/share/tonarchy/firefox-policies/policies.json /mnt/usr/lib/firefox/distribution/");
1397
1398
    create_directory("/mnt/usr/share/applications", 0755);
1399
    write_file("/mnt/usr/share/applications/firefox.desktop",
1400
        "[Desktop Entry]\n"
1401
        "Name=Firefox\n"
1402
        "GenericName=Web Browser\n"
1403
        "Exec=sh -c 'firefox --profile $HOME/.config/firefox'\n"
1404
        "Type=Application\n"
1405
        "Icon=firefox\n"
1406
        "Categories=Network;WebBrowser;\n"
1407
        "MimeType=text/html;text/xml;application/xhtml+xml;application/vnd.mozilla.xul+xml;\n"
1408
    );
1409
1410
    snprintf(cmd, sizeof(cmd), "/mnt/home/%s/.config", username);
1411
    create_directory(cmd, 0755);
1412
1413
    snprintf(cmd, sizeof(cmd), "arch-chroot /mnt chown -R %s:%s /home/%s/.config", username, username, username);
1414
    system(cmd);
1415
1416
    snprintf(cmd, sizeof(cmd), "cp -r /usr/share/tonarchy/alacritty /mnt/home/%s/.config/alacritty", username);
1417
    system(cmd);
1418
1419
    snprintf(cmd, sizeof(cmd), "cp -r /usr/share/tonarchy/rofi /mnt/home/%s/.config/rofi", username);
1420
    system(cmd);
1421
1422
    snprintf(cmd, sizeof(cmd), "cp -r /usr/share/tonarchy/fastfetch /mnt/home/%s/.config/fastfetch", username);
1423
    system(cmd);
1424
1425
    snprintf(cmd, sizeof(cmd), "cp -r /usr/share/tonarchy/picom /mnt/home/%s/.config/picom", username);
1426
    system(cmd);
1427
1428
    char nvim_path[256];
1429
    snprintf(nvim_path, sizeof(nvim_path), "/home/%s/.config/nvim", username);
1430
    git_clone_as_user(username, "https://github.com/tonybanters/nvim", nvim_path);
1431
1432
    snprintf(cmd, sizeof(cmd), "arch-chroot /mnt chown -R %s:%s /home/%s/.config", username, username, username);
1433
    system(cmd);
1434
1435
    return 1;
1436
}
1437
1438
static int setup_autologin(const char *username) {
1439
    char autologin_exec[512];
1440
    snprintf(autologin_exec, sizeof(autologin_exec),
1441
             "ExecStart=-/sbin/agetty -o \"-p -f -- \\\\u\" --noclear --autologin %s %%I $TERM",
1442
             username);
1443
1444
    Config_Entry autologin_entries[] = {
1445
        {"[Service]", ""},
1446
        {"ExecStart=", ""},
1447
        {autologin_exec, ""}
1448
    };
1449
1450
    Systemd_Override autologin = {
1451
        "getty@tty1.service",
1452
        "getty@tty1.service.d",
1453
        "autologin.conf",
1454
        autologin_entries,
1455
        3
1456
    };
1457
1458
    if (!setup_systemd_override(&autologin)) {
1459
        LOG_ERROR("Failed to setup autologin");
1460
        return 0;
1461
    }
1462
1463
    return 1;
1464
}
1465
1466
static const char *BASHRC_CONTENT =
1467
    "export PATH=\"$HOME/.local/bin:$PATH\"\n"
1468
    "export EDITOR=\"nvim\"\n"
1469
    "\n"
1470
    "alias ls='ls --color=auto'\n"
1471
    "alias la='ls -a'\n"
1472
    "alias ll='ls -la'\n"
1473
    "alias ..='cd ..'\n"
1474
    "alias ...='cd ../..'\n"
1475
    "alias grep='grep --color=auto'\n"
1476
    "\n"
1477
    "export PS1=\"\\[\\e[38;5;75m\\]\\u@\\h \\[\\e[38;5;113m\\]\\w \\[\\e[38;5;189m\\]\\$ \\[\\e[0m\\]\"\n"
1478
    "\n"
1479
    "fastfetch\n";
1480
1481
static const char *BASH_PROFILE_CONTENT =
1482
    "if [ -z $DISPLAY ] && [ $XDG_VTNR = 1 ]; then\n"
1483
    "  exec startx\n"
1484
    "fi\n";
1485
1486
static int configure_xfce(const char *username) {
1487
    char cmd[4096];
1488
    int rows, cols;
1489
    get_terminal_size(&rows, &cols);
1490
1491
    clear_screen();
1492
    draw_logo(cols);
1493
1494
    int logo_start = get_logo_start(cols);
1495
    printf("\033[%d;%dH\033[37mConfiguring XFCE...\033[0m", 10, logo_start);
1496
    fflush(stdout);
1497
1498
    setup_common_configs(username);
1499
1500
    snprintf(cmd, sizeof(cmd), "cp -r /usr/share/tonarchy/xfce4 /mnt/home/%s/.config/xfce4", username);
1501
    system(cmd);
1502
1503
    snprintf(cmd, sizeof(cmd), "arch-chroot /mnt chown -R %s:%s /home/%s/.config/xfce4", username, username, username);
1504
    system(cmd);
1505
1506
    Dotfile dotfiles[] = {
1507
        { ".xinitrc", "exec startxfce4\n", 0755 },
1508
        { ".bash_profile", BASH_PROFILE_CONTENT, 0644 },
1509
        { ".bashrc", BASHRC_CONTENT, 0644 }
1510
    };
1511
1512
    for (size_t i = 0; i < sizeof(dotfiles) / sizeof(dotfiles[0]); i++) {
1513
        if (!create_user_dotfile(username, &dotfiles[i])) {
1514
            LOG_ERROR("Failed to create dotfile: %s", dotfiles[i].filename);
1515
            return 0;
1516
        }
1517
    }
1518
1519
    if (!setup_autologin(username)) {
1520
        return 0;
1521
    }
1522
1523
    return 1;
1524
}
1525
1526
static int configure_oxwm(const char *username) {
1527
    char cmd[4096];
1528
    int rows, cols;
1529
    get_terminal_size(&rows, &cols);
1530
1531
    clear_screen();
1532
    draw_logo(cols);
1533
1534
    int logo_start = get_logo_start(cols);
1535
    printf("\033[%d;%dH\033[37mConfiguring OXWM...\033[0m", 10, logo_start);
1536
    printf("\033[%d;%dH\033[37mCloning and building from source...\033[0m", 11, logo_start);
1537
    fflush(stdout);
1538
1539
    LOG_INFO("Starting OXWM installation for user: %s", username);
1540
1541
    char oxwm_path[256];
1542
    snprintf(oxwm_path, sizeof(oxwm_path), "/home/%s/oxwm", username);
1543
1544
    if (!git_clone_as_user(username, "https://github.com/tonybanters/oxwm", oxwm_path)) {
1545
        LOG_ERROR("Failed to clone oxwm");
1546
        show_message("Failed to clone OXWM");
1547
        return 0;
1548
    }
1549
1550
    if (!chroot_exec_fmt("cd %s && zig build -Doptimize=ReleaseSmall", oxwm_path)) {
1551
        LOG_ERROR("Failed to build oxwm");
1552
        show_message("Failed to build OXWM");
1553
        return 0;
1554
    }
1555
1556
    if (!chroot_exec_fmt("cp %s/zig-out/bin/oxwm /usr/bin/oxwm", oxwm_path)) {
1557
        LOG_ERROR("Failed to install oxwm binary");
1558
        show_message("Failed to install OXWM");
1559
        return 0;
1560
    }
1561
1562
    chroot_exec("chmod 755 /usr/bin/oxwm");
1563
1564
    setup_common_configs(username);
1565
1566
    snprintf(cmd, sizeof(cmd), "cp -r /usr/share/tonarchy/gtk-3.0 /mnt/home/%s/.config/gtk-3.0", username);
1567
    system(cmd);
1568
1569
    snprintf(cmd, sizeof(cmd), "cp -r /usr/share/tonarchy/gtk-4.0 /mnt/home/%s/.config/gtk-4.0", username);
1570
    system(cmd);
1571
1572
    snprintf(cmd, sizeof(cmd), "cp /usr/share/tonarchy/gtkrc-2.0 /mnt/home/%s/.gtkrc-2.0", username);
1573
    system(cmd);
1574
1575
    snprintf(cmd, sizeof(cmd), "/mnt/home/%s/.config/oxwm", username);
1576
    create_directory(cmd, 0755);
1577
1578
    snprintf(cmd, sizeof(cmd), "cp /mnt%s/templates/tonarchy-config.lua /mnt/home/%s/.config/oxwm/config.lua", oxwm_path, username);
1579
    system(cmd);
1580
1581
    snprintf(cmd, sizeof(cmd), "arch-chroot /mnt chown -R %s:%s /home/%s/.config", username, username, username);
1582
    system(cmd);
1583
1584
    Dotfile dotfiles[] = {
1585
        { ".xinitrc", "export GTK_THEME=Adwaita-dark\nxset r rate 200 35 &\npicom --config ~/.config/picom/picom.conf &\nxwallpaper --zoom /usr/share/wallpapers/wall1.jpg &\nexec oxwm\n", 0755 },
1586
        { ".bash_profile", BASH_PROFILE_CONTENT, 0644 },
1587
        { ".bashrc", BASHRC_CONTENT, 0644 }
1588
    };
1589
1590
    for (size_t i = 0; i < sizeof(dotfiles) / sizeof(dotfiles[0]); i++) {
1591
        if (!create_user_dotfile(username, &dotfiles[i])) {
1592
            LOG_ERROR("Failed to create dotfile: %s", dotfiles[i].filename);
1593
            return 0;
1594
        }
1595
    }
1596
1597
    if (!setup_autologin(username)) {
1598
        return 0;
1599
    }
1600
1601
    LOG_INFO("OXWM installation completed successfully");
1602
    show_message("OXWM installed successfully!");
1603
    return 1;
1604
}
1605
1606
int main(void) {
1607
    logger_init("/tmp/tonarchy-install.log");
1608
    LOG_INFO("Tonarchy installer started");
1609
1610
    if (!setup_wifi_if_needed()) {
1611
        logger_close();
1612
        return 1;
1613
    }
1614
1615
    char username[256] = "";
1616
    char password[256] = "";
1617
    char confirmed_password[256] = "";
1618
    char hostname[256] = "";
1619
    char keyboard[256] = "";
1620
    char timezone[256] = "";
1621
1622
    if (!get_form_input(username, password, confirmed_password, hostname, keyboard, timezone)) {
1623
        logger_close();
1624
        return 1;
1625
    }
1626
1627
    const char *levels[] = {
1628
        "Beginner (XFCE desktop - perfect for starters)",
1629
        "Oxidized (OXWM Beta)"
1630
    };
1631
1632
    int level = select_from_menu(levels, 2);
1633
    if (level < 0) {
1634
        LOG_INFO("Installation cancelled by user at level selection");
1635
        logger_close();
1636
        return 1;
1637
    }
1638
1639
    LOG_INFO("Installation level selected: %d", level);
1640
1641
    char disk[64] = "";
1642
    if (!select_disk(disk)) {
1643
        LOG_INFO("Installation cancelled by user at disk selection");
1644
        logger_close();
1645
        return 1;
1646
    }
1647
1648
    LOG_INFO("Selected disk: %s", disk);
1649
1650
    if (level == BEGINNER) {
1651
        CHECK_OR_FAIL(partition_disk(disk), "Failed to partition disk");
1652
        CHECK_OR_FAIL(install_packages_impl(XFCE_PACKAGES), "Failed to install packages");
1653
        CHECK_OR_FAIL(configure_system_impl(username, password, hostname, keyboard, timezone, disk, 0), "Failed to configure system");
1654
        CHECK_OR_FAIL(install_bootloader(disk), "Failed to install bootloader");
1655
        configure_xfce(username);
1656
    } else {
1657
        CHECK_OR_FAIL(partition_disk(disk), "Failed to partition disk");
1658
        CHECK_OR_FAIL(install_packages_impl(OXWM_PACKAGES), "Failed to install packages");
1659
        CHECK_OR_FAIL(configure_system_impl(username, password, hostname, keyboard, timezone, disk, 0), "Failed to configure system");
1660
        CHECK_OR_FAIL(install_bootloader(disk), "Failed to install bootloader");
1661
        configure_oxwm(username);
1662
    }
1663
1664
    system("cp /tmp/tonarchy-install.log /mnt/var/log/tonarchy-install.log");
1665
1666
    clear_screen();
1667
    int rows, cols;
1668
    get_terminal_size(&rows, &cols);
1669
    draw_logo(cols);
1670
1671
    int logo_start = get_logo_start(cols);
1672
    printf("\033[%d;%dH\033[1;32mInstallation complete!\033[0m\n", 10, logo_start);
1673
    printf("\033[%d;%dH\033[37mPress Enter to reboot...\033[0m\n", 12, logo_start);
1674
    fflush(stdout);
1675
1676
    char c;
1677
    enable_raw_mode();
1678
    while (read(STDIN_FILENO, &c, 1) == 1) {
1679
        if (c == '\r' || c == '\n') {
1680
            break;
1681
        }
1682
    }
1683
    disable_raw_mode();
1684
1685
    LOG_INFO("Tonarchy installer finished - scheduling reboot");
1686
    logger_close();
1687
1688
    sync();
1689
    system("sleep 2 && reboot &");
1690
1691
    exit(0);
1692
}