tonarchy

tonarchy

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