tonarchy

tonarchy

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