tonarchy

tonarchy

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