mirror of
https://github.com/fosslinux/live-bootstrap.git
synced 2026-03-02 01:18:08 +01:00
Add interactive "configurator"
This is a more UX-friendly glorified bootstrap.cfg editor. Somewhat inspired by kconfig.
This commit is contained in:
parent
e2d4782cc2
commit
e1077cbed3
6 changed files with 878 additions and 25 deletions
784
seed/configurator.c
Normal file
784
seed/configurator.c
Normal file
|
|
@ -0,0 +1,784 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 fosslinux <fosslinux@aussies.space>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#define MAX_STRING 2048
|
||||
#define MAX_SHORT 512
|
||||
#define MAX_ID 128
|
||||
#define MAX_VAR 128
|
||||
|
||||
#include <bootstrappable.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/utsname.h>
|
||||
|
||||
#define TRUE 1
|
||||
#define FALSE 0
|
||||
|
||||
#define KIND_NONE 0
|
||||
#define KIND_MENU 1
|
||||
#define KIND_OPTION 2
|
||||
|
||||
#define TYPE_NONE 0
|
||||
#define TYPE_BOOL 1
|
||||
#define TYPE_SIZE 2
|
||||
#define TYPE_STRING 3
|
||||
#define TYPE_INT 4
|
||||
|
||||
struct Entry {
|
||||
int kind; // either menu or option
|
||||
char *env_var; // name of the environment variable this option is stored in
|
||||
char *id; // the id of the configuration item
|
||||
char *short_desc; // short description of the config
|
||||
char *full_desc; // extended description of the config
|
||||
|
||||
int type; // the type of the configuration option
|
||||
char *validation; // any validation rules
|
||||
char *val;
|
||||
char *default_val;
|
||||
|
||||
struct Entry *children; // submenus
|
||||
struct Entry *parent;
|
||||
struct Entry *next;
|
||||
};
|
||||
typedef struct Entry Entry;
|
||||
|
||||
Entry *find_entry(Entry *head, char *id) {
|
||||
char *component = strchr(id, '/');
|
||||
if (component == NULL) {
|
||||
component = id + strlen(id);
|
||||
}
|
||||
|
||||
Entry *current;
|
||||
Entry *final;
|
||||
int len;
|
||||
while (1) {
|
||||
len = component - id;
|
||||
|
||||
current = head;
|
||||
while (current != NULL) {
|
||||
/* ensure that the id isn't just a substring of the component but actually is the component */
|
||||
if (strlen(current->id) == len && strncmp(id, current->id, len) == 0) {
|
||||
/* Found it! */
|
||||
final = current;
|
||||
head = current->children;
|
||||
break;
|
||||
}
|
||||
current = current->next;
|
||||
}
|
||||
if (current == NULL) {
|
||||
/* Did not find it */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (component[0] == '\0') {
|
||||
break;
|
||||
}
|
||||
|
||||
component += 1;
|
||||
component = strchr(component, '/');
|
||||
if (component == NULL) {
|
||||
component = id + strlen(id);
|
||||
}
|
||||
}
|
||||
|
||||
return final;
|
||||
}
|
||||
|
||||
Entry *get_parent(Entry *head, char *id) {
|
||||
char *parent_id = calloc(MAX_ID, sizeof(char));
|
||||
strcpy(parent_id, id);
|
||||
char *final_slash = strrchr(parent_id, '/');
|
||||
final_slash[0] = '\0';
|
||||
Entry *ret = find_entry(head, parent_id);
|
||||
free(parent_id);
|
||||
return ret;
|
||||
}
|
||||
|
||||
char read_string(FILE *f, char *out, int length) {
|
||||
int i = 0;
|
||||
char c = fgetc(f);
|
||||
while (c != ' ' && c != '\n' && c != EOF && i < length - 1) {
|
||||
out[i] = c;
|
||||
i += 1;
|
||||
c = fgetc(f);
|
||||
}
|
||||
if (i >= length - 1) {
|
||||
fputs("String too long!\n", stdout);
|
||||
fclose(f);
|
||||
exit(1);
|
||||
}
|
||||
out[i] = '\0';
|
||||
return c;
|
||||
}
|
||||
|
||||
int set_val(Entry *entry, char *val) {
|
||||
if (entry->type == TYPE_BOOL) {
|
||||
if (strcmp(val, "True") != 0 && strcmp(val, "False") != 0) {
|
||||
fputs("Invalid input: ", stdout);
|
||||
fputs(val, stdout);
|
||||
fputs(" is not a boolean value\n", stdout);
|
||||
return 1;
|
||||
}
|
||||
} else if (entry->type == TYPE_INT) {
|
||||
int intval = strtoint(val);
|
||||
if (intval == 0 && strcmp(val, "0") != 0) {
|
||||
fputs("Invalid input: ", stdout);
|
||||
fputs(val, stdout);
|
||||
fputs(" is not an integer\n", stdout);
|
||||
return 1;
|
||||
}
|
||||
} else if (entry->type == TYPE_SIZE) {
|
||||
/* We should have either a K, M, G, T, or no letter, at the end of the size. */
|
||||
char c = val[strlen(val) - 1];
|
||||
if (!(('0' <= c && c <= '9') || c == 'K' || c == 'M' || c == 'G' || c == 'T')) {
|
||||
fputs("Invalid input: ", stdout);
|
||||
fputc(c, stdout);
|
||||
fputs(" is not a valid suffix for a size\n", stdout);
|
||||
return 1;
|
||||
}
|
||||
/* Check it is an integer */
|
||||
char *final_char = val + strlen(val) - 1;
|
||||
if ('A' <= final_char[0] && final_char[0] <= 'Z') {
|
||||
final_char[0] = '\0';
|
||||
}
|
||||
int intval = strtoint(val);
|
||||
if (intval == 0 && strcmp(val, "0") != 0) {
|
||||
fputs("Invalid input: ", stdout);
|
||||
fputs(val, stdout);
|
||||
fputs(" is not a valid size\n", stdout);
|
||||
return 1;
|
||||
}
|
||||
final_char[0] = c;
|
||||
} else if (entry->type == TYPE_STRING) {
|
||||
/* Validation rules. */
|
||||
char *validation = entry->validation;
|
||||
char *next;
|
||||
int found = FALSE;
|
||||
while (validation != NULL) {
|
||||
if (validation[0] == '\0') {
|
||||
found = TRUE;
|
||||
break;
|
||||
}
|
||||
next = strchr(validation, '|');
|
||||
if (next == NULL) {
|
||||
if (strcmp(validation, val) == 0) {
|
||||
found = TRUE;
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
if (strncmp(validation, val, next - validation) == 0) {
|
||||
found = TRUE;
|
||||
}
|
||||
}
|
||||
validation = next + 1;
|
||||
}
|
||||
if (found == FALSE) {
|
||||
fputs("Invalid input: ", stdout);
|
||||
fputs(val, stdout);
|
||||
fputs(" does not match the validation rules ", stdout);
|
||||
fputs(entry->validation, stdout);
|
||||
fputc('\n', stdout);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
entry->val = calloc(strlen(val) + 1, sizeof(char));
|
||||
strcpy(entry->val, val);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void read_entry(char c, FILE *conf, Entry *head) {
|
||||
Entry *new = calloc(1, sizeof(Entry));
|
||||
|
||||
/* Read the kind */
|
||||
if (c == 'm') {
|
||||
new->kind = KIND_MENU;
|
||||
} else if (c == 'o') {
|
||||
new->kind = KIND_OPTION;
|
||||
} else {
|
||||
fputs("Invalid entry: kind ", stdout);
|
||||
fputc(c, stdout);
|
||||
fputc('\n', stdout);
|
||||
fclose(conf);
|
||||
exit(1);
|
||||
}
|
||||
fgetc(conf);
|
||||
|
||||
/* Read the id */
|
||||
new->id = calloc(MAX_ID, sizeof(char));
|
||||
c = read_string(conf, new->id, MAX_ID);
|
||||
if (c != ' ') {
|
||||
fputs("Invalid entry: no variable\n", stdout);
|
||||
fclose(conf);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Read the environment variable */
|
||||
new->env_var = calloc(MAX_VAR, sizeof(char));
|
||||
c = read_string(conf, new->env_var, MAX_VAR);
|
||||
if (c != ' ') {
|
||||
fputs("Invalid entry: no data type\n", stdout);
|
||||
fclose(conf);
|
||||
exit(1);
|
||||
}
|
||||
if (strcmp(new->env_var, "_") == 0) {
|
||||
free(new->env_var);
|
||||
new->env_var = NULL;
|
||||
}
|
||||
|
||||
/* Read the data type */
|
||||
char *data_type = calloc(MAX_ID, sizeof(char));
|
||||
read_string(conf, data_type, MAX_ID);
|
||||
if (c != ' ') {
|
||||
fputs("Invalid entry: no default value\n", stdout);
|
||||
fclose(conf);
|
||||
exit(1);
|
||||
}
|
||||
if (strcmp(data_type, "_") == 0) {
|
||||
new->type = TYPE_NONE;
|
||||
} else if (strcmp(data_type, "bool") == 0) {
|
||||
new->type = TYPE_BOOL;
|
||||
} else if (strcmp(data_type, "size") == 0) {
|
||||
new->type = TYPE_SIZE;
|
||||
} else if (strcmp(data_type, "int") == 0) {
|
||||
new->type = TYPE_INT;
|
||||
} else if (data_type[0] == '"') {
|
||||
new->type = TYPE_STRING;
|
||||
new->validation = data_type + 1;
|
||||
char *closing_quote = strrchr(data_type, '"');
|
||||
closing_quote[0] = '\0';
|
||||
} else {
|
||||
fputs("Invalid entry: unknown type: ", stdout);
|
||||
fputs(data_type, stdout);
|
||||
fputc('\n', stdout);
|
||||
fclose(conf);
|
||||
exit(1);
|
||||
}
|
||||
if (new->type != TYPE_STRING) {
|
||||
free(data_type);
|
||||
}
|
||||
|
||||
/* Read the default value */
|
||||
char *default_val = calloc(MAX_STRING, sizeof(char));
|
||||
read_string(conf, default_val, MAX_ID);
|
||||
if (strcmp(default_val, "_") != 0) {
|
||||
set_val(new, default_val);
|
||||
new->default_val = default_val;
|
||||
} else {
|
||||
new->default_val = NULL;
|
||||
}
|
||||
|
||||
/* Read the short description */
|
||||
new->short_desc = calloc(MAX_SHORT, sizeof(char));
|
||||
int i = 0;
|
||||
c = fgetc(conf);
|
||||
while (c != '\n' && c != EOF) {
|
||||
new->short_desc[i] = c;
|
||||
c = fgetc(conf);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
/* Read the long description */
|
||||
new->full_desc = calloc(MAX_STRING, sizeof(char));
|
||||
i = 0;
|
||||
c = fgetc(conf);
|
||||
char prev = '\0';
|
||||
while (!(c == '\n' && prev == '\n') && c != EOF) {
|
||||
new->full_desc[i] = c;
|
||||
prev = c;
|
||||
c = fgetc(conf);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
new->children = NULL;
|
||||
new->next = NULL;
|
||||
|
||||
Entry *parent = get_parent(head, new->id);
|
||||
new->parent = parent;
|
||||
if (parent->children == NULL) {
|
||||
parent->children = new;
|
||||
} else {
|
||||
Entry *current = parent->children;
|
||||
while (current->next != NULL) {
|
||||
current = current->next;
|
||||
}
|
||||
current->next = new;
|
||||
}
|
||||
}
|
||||
|
||||
Entry *read_config(char *filename) {
|
||||
FILE *conf = fopen(filename, "r");
|
||||
if (conf == NULL) {
|
||||
fputs("Unable to open ", stdout);
|
||||
fputs(filename, stdout);
|
||||
fputc('\n', stdout);
|
||||
exit(0);
|
||||
}
|
||||
char c = fgetc(conf);
|
||||
|
||||
Entry *head = calloc(1, sizeof(Entry));
|
||||
head->id = "";
|
||||
head->env_var = "";
|
||||
head->next = NULL;
|
||||
Entry *current = head;
|
||||
|
||||
while (c != EOF) {
|
||||
if (c == '#' || c == '\n') {
|
||||
/* Skip comments or empty lines. */
|
||||
while (c != '\n' && c != EOF) {
|
||||
c = fgetc(conf);
|
||||
}
|
||||
} else {
|
||||
read_entry(c, conf, head);
|
||||
}
|
||||
c = fgetc(conf);
|
||||
}
|
||||
|
||||
fclose(conf);
|
||||
|
||||
return head;
|
||||
}
|
||||
|
||||
Entry *get_env_var(Entry *head, char *var) {
|
||||
Entry *ret;
|
||||
Entry *current;
|
||||
for (current = head->children; current != NULL; current = current->next) {
|
||||
if (current->env_var != NULL) {
|
||||
if (strcmp(current->env_var, var) == 0) {
|
||||
return current;
|
||||
}
|
||||
}
|
||||
if (current->children != NULL) {
|
||||
ret = get_env_var(current, var);
|
||||
if (ret != NULL) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int set_cfg_varline(Entry *head, char *line) {
|
||||
char *var = calloc(strlen(line) + 1, sizeof(char));
|
||||
strcpy(var, line);
|
||||
char *val = strchr(var, '=');
|
||||
val[0] = '\0';
|
||||
val += 1;
|
||||
char *newline = strchr(val, '\n');
|
||||
if (newline != NULL) {
|
||||
newline[0] = '\0';
|
||||
}
|
||||
Entry *entry = get_env_var(head, var);
|
||||
if (entry != NULL) {
|
||||
int not_ok = set_val(entry, val);
|
||||
if (not_ok) {
|
||||
fputs("^ Originated from ", stdout);
|
||||
fputs(var, stdout);
|
||||
fputs("=", stdout);
|
||||
fputs(val, stdout);
|
||||
fputs("\n", stdout);
|
||||
}
|
||||
}
|
||||
return entry == NULL;
|
||||
}
|
||||
|
||||
char *set_cfg_values(Entry *head, char **envp) {
|
||||
int i = 0;
|
||||
|
||||
FILE *cfg = fopen("/steps/bootstrap.cfg", "r");
|
||||
if (cfg == NULL) {
|
||||
return "";
|
||||
}
|
||||
|
||||
char *extra = calloc(MAX_STRING, sizeof(char));
|
||||
char *line = calloc(MAX_STRING, sizeof(char));
|
||||
while (fgets(line, MAX_STRING, cfg) != NULL) {
|
||||
if (set_cfg_varline(head, line)) {
|
||||
if (strncmp("CONFIGURATOR=", line, 13) != 0) {
|
||||
strcat(extra, line);
|
||||
}
|
||||
}
|
||||
free(line);
|
||||
line = calloc(MAX_STRING, sizeof(char));
|
||||
}
|
||||
|
||||
fclose(cfg);
|
||||
|
||||
return extra;
|
||||
}
|
||||
|
||||
void write_cfg_list(Entry *head, FILE *cfg) {
|
||||
Entry *current;
|
||||
for (current = head->children; current != NULL; current = current->next) {
|
||||
if (current->kind == KIND_OPTION && current->val != NULL) {
|
||||
fputs(current->env_var, cfg);
|
||||
fputs("=", cfg);
|
||||
fputs(current->val, cfg);
|
||||
fputs("\n", cfg);
|
||||
}
|
||||
if (current->children != NULL) {
|
||||
write_cfg_list(current, cfg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void write_cfg_values(Entry *head, char *extra, int configurator_done) {
|
||||
FILE *cfg = fopen("/steps/bootstrap.cfg", "w");
|
||||
if (cfg == NULL) {
|
||||
fputs("Unable to open /steps/bootstrap.cfg", stderr);
|
||||
exit(1);
|
||||
}
|
||||
if (configurator_done == TRUE) {
|
||||
fputs("CONFIGURATOR=False\n", cfg);
|
||||
}
|
||||
fputs(extra, cfg);
|
||||
write_cfg_list(head, cfg);
|
||||
fclose(cfg);
|
||||
}
|
||||
|
||||
void print_short_desc(char *short_desc) {
|
||||
char *post_markers = strrchr(short_desc, ']');
|
||||
if (post_markers == NULL) {
|
||||
post_markers = short_desc;
|
||||
} else {
|
||||
post_markers += 1;
|
||||
while (post_markers[0] == ' ') {
|
||||
post_markers += 1;
|
||||
}
|
||||
}
|
||||
fputs(post_markers, stdout);
|
||||
}
|
||||
|
||||
void print_recursive_desc(Entry *entry) {
|
||||
if (entry->parent != NULL) {
|
||||
if (strcmp(entry->parent->id, "") != 0) {
|
||||
print_recursive_desc(entry->parent);
|
||||
fputs("/", stdout);
|
||||
print_short_desc(entry->short_desc);
|
||||
return;
|
||||
}
|
||||
}
|
||||
print_short_desc(entry->short_desc);
|
||||
}
|
||||
|
||||
int any_unset(Entry *head);
|
||||
|
||||
int check_set(Entry *entry, Entry *head) {
|
||||
int ret = 0;
|
||||
if (entry->kind == KIND_OPTION && entry->val == NULL) {
|
||||
fputs("The configuration option ", stdout);
|
||||
print_recursive_desc(entry);
|
||||
fputs(" is unset\n", stdout);
|
||||
ret = 1;
|
||||
}
|
||||
if (entry->children != NULL) {
|
||||
ret |= any_unset(entry);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int any_unset(Entry *head) {
|
||||
int ret = 0;
|
||||
Entry *current;
|
||||
for (current = head->children; current != NULL; current = current->next) {
|
||||
ret |= check_set(current, head);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void print_menu(Entry *menu, int is_toplevel) {
|
||||
if (!is_toplevel) {
|
||||
fputs("(0) [MENU] Go up\n", stdout);
|
||||
}
|
||||
int i = 1;
|
||||
Entry *current;
|
||||
for (current = menu->children; current != NULL; current = current->next) {
|
||||
fputs("(", stdout);
|
||||
fputs(int2str(i, 10, FALSE), stdout);
|
||||
fputs(") ", stdout);
|
||||
if (current->kind == KIND_MENU) {
|
||||
fputs("[MENU] ", stdout);
|
||||
}
|
||||
fputs(current->short_desc, stdout);
|
||||
fputc('\n', stdout);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Entry *get_nth_option(Entry *menu, int n) {
|
||||
int i = 1;
|
||||
Entry *current;
|
||||
for (current = menu->children; current != NULL && i < n; current = current->next) {
|
||||
i += 1;
|
||||
}
|
||||
if (current == NULL) {
|
||||
fputs("There is no option ", stdout);
|
||||
fputs(int2str(n, 10, FALSE), stdout);
|
||||
fputs("!\n", stdout);
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
void how_to_use(void) {
|
||||
fputs(
|
||||
"How to navigate around this configuration menu:\n"
|
||||
"h or help: at any time, will reprint this help message\n"
|
||||
"l or list: shows the current menu options\n"
|
||||
"o <num> or open <num>: open a (sub)menu\n"
|
||||
"? <num> or describe <num>: provides a more detailed description of an option or menu\n"
|
||||
"s <num> <val> or set <num> <val>: set the value of an option\n"
|
||||
"g <num> or get <num>: get the value of an option\n"
|
||||
"g all or get all: get the value of all options in the menu\n"
|
||||
"r <num> or reset <num>: reset the value of an option to the default (if there is one)\n"
|
||||
"e or exit: exits the program\n",
|
||||
stdout);
|
||||
}
|
||||
|
||||
Entry *extract_num(char **command, Entry *menu) {
|
||||
command[0] = strchr(command[0], ' ');
|
||||
if (command[0] == NULL) {
|
||||
fputs("Expected menu number to operate on!\n", stdout);
|
||||
}
|
||||
command[0] += 1;
|
||||
char *num = command[0];
|
||||
char *new = strchr(command[0], ' ');
|
||||
if (new == NULL) {
|
||||
new = strchr(command[0], '\n');
|
||||
}
|
||||
command[0] = new;
|
||||
command[0][0] = '\0';
|
||||
command[0] += 1;
|
||||
/* strtoint does not check if it is not a number */
|
||||
int i;
|
||||
for (i = 0; i < strlen(num); i += 1) {
|
||||
if (!('0' <= num[i] && num[i] <= '9')) {
|
||||
fputs(num, stdout);
|
||||
fputs(" is not a menu number!\n", stdout);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
int n = strtoint(num);
|
||||
return get_nth_option(menu, n);
|
||||
}
|
||||
|
||||
Entry *submenu(char *command, Entry *menu, Entry *head) {
|
||||
command = strchr(command, ' ');
|
||||
if (strlen(command) < 1) {
|
||||
fputs("Expected menu number to operate on!\n", stdout);
|
||||
}
|
||||
/* 0 is the "go up" menu option */
|
||||
if (command[1] == '0') {
|
||||
if (strcmp(menu->id, "") == 0) {
|
||||
fputs("There is no option 0!\n", stdout);
|
||||
return menu;
|
||||
}
|
||||
return menu->parent;
|
||||
}
|
||||
|
||||
Entry *new = extract_num(&command, menu);
|
||||
if (new == NULL) {
|
||||
return menu;
|
||||
}
|
||||
if (new->kind != KIND_MENU) {
|
||||
fputs("This is not a menu!\n", stdout);
|
||||
return menu;
|
||||
}
|
||||
return new;
|
||||
}
|
||||
|
||||
void print_description(char *command, Entry *menu) {
|
||||
Entry *opt = extract_num(&command, menu);
|
||||
if (opt != NULL) {
|
||||
fputs(opt->full_desc, stdout);
|
||||
}
|
||||
}
|
||||
|
||||
void set_opt_value(char *command, Entry *menu) {
|
||||
Entry *opt = extract_num(&command, menu);
|
||||
if (opt == NULL) {
|
||||
return;
|
||||
}
|
||||
if (opt->kind != KIND_OPTION) {
|
||||
fputs("Cannot set a menu's value!\n", stdout);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Remove the newline */
|
||||
char *newline = strchr(command, '\n');
|
||||
newline[0] = '\0';
|
||||
set_val(opt, command);
|
||||
}
|
||||
|
||||
void print_opt_value(Entry *opt) {
|
||||
print_short_desc(opt->short_desc);
|
||||
|
||||
fputs(": ", stdout);
|
||||
if (opt->val == NULL) {
|
||||
fputs("unset", stdout);
|
||||
} else {
|
||||
fputs(opt->val, stdout);
|
||||
}
|
||||
fputc('\n', stdout);
|
||||
}
|
||||
|
||||
void get_opt_value(char *command, Entry *menu) {
|
||||
Entry *opt = extract_num(&command, menu);
|
||||
if (opt == NULL) {
|
||||
return;
|
||||
}
|
||||
if (opt->kind != KIND_OPTION) {
|
||||
fputs("Cannot get a menu's value!\n", stdout);
|
||||
return;
|
||||
}
|
||||
|
||||
print_opt_value(opt);
|
||||
}
|
||||
|
||||
void get_all_values(Entry *menu) {
|
||||
Entry *current;
|
||||
for (current = menu->children; current != NULL; current = current->next) {
|
||||
if (current->kind == KIND_OPTION) {
|
||||
print_opt_value(current);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void reset_value(char *command, Entry *menu) {
|
||||
Entry *opt = extract_num(&command, menu);
|
||||
if (opt == NULL) {
|
||||
return;
|
||||
}
|
||||
if (opt->kind != KIND_OPTION) {
|
||||
fputs("Cannot reset a menu's value!\n", stdout);
|
||||
return;
|
||||
}
|
||||
opt->val = opt->default_val;
|
||||
}
|
||||
|
||||
void no_input(Entry *head) {
|
||||
fputs("You don't seem to be running under Fiwix or Linux currently.\n", stdout);
|
||||
fputs("Likely, you are currently running under builder-hex0.\n", stdout);
|
||||
fputs("That's ok! We're going to make some assumptions; namely, that you do need\n", stdout);
|
||||
fputs("the kernel bootstrap, and that you'll get a chance to configure later.\n", stdout);
|
||||
write_cfg_values(head, "KERNEL_BOOTSTRAP=True\n", FALSE);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv, char **envp) {
|
||||
/*
|
||||
* Check we are being non-interactive and bootstrap.cfg exists in
|
||||
* which case we do not need to do anything.
|
||||
*/
|
||||
char *interactivity = getenv("CONFIGURATOR");
|
||||
if (interactivity != NULL) {
|
||||
if (strcmp(interactivity, "False") == 0) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
FILE *bootstrap_cfg = fopen("/steps/bootstrap.cfg", "r");
|
||||
if (bootstrap_cfg != NULL) {
|
||||
char *line = calloc(MAX_STRING, sizeof(char));
|
||||
while (fgets(line, MAX_STRING, bootstrap_cfg) != NULL) {
|
||||
if (strcmp(line, "CONFIGURATOR=False\n") == 0) {
|
||||
fclose(bootstrap_cfg);
|
||||
return 0;
|
||||
}
|
||||
free(line);
|
||||
line = calloc(MAX_STRING, sizeof(char));
|
||||
}
|
||||
fclose(bootstrap_cfg);
|
||||
}
|
||||
|
||||
if (argc != 2) {
|
||||
fputs("Usage: ", stdout);
|
||||
fputs(argv[0], stdout);
|
||||
fputs(" <configuration>\n", stdout);
|
||||
exit(1);
|
||||
}
|
||||
Entry *head = read_config(argv[1]);
|
||||
char *extra = set_cfg_values(head, envp);
|
||||
|
||||
/*
|
||||
* Check if we are NOT running under fiwix or linux.
|
||||
* If we are not, and need configuration to occur, then we presume that
|
||||
* we will not be able to get any input from the user.
|
||||
*/
|
||||
struct utsname *kernel = calloc(1, sizeof(struct utsname));
|
||||
uname(kernel);
|
||||
if (kernel->sysname == NULL) {
|
||||
no_input(head);
|
||||
return 0;
|
||||
} else if (strcmp(kernel->sysname, "Linux") != 0 && strcmp(kernel->sysname, "Fiwix") != 0) {
|
||||
no_input(head);
|
||||
return 0;
|
||||
}
|
||||
|
||||
fputs("Welcome to live-bootstrap!\n", stdout);
|
||||
fputs("We need to do some brief configuration before continuing.\n\n", stdout);
|
||||
how_to_use();
|
||||
fputc('\n', stdout);
|
||||
|
||||
Entry *menu = head;
|
||||
print_menu(menu, menu == head);
|
||||
char *command = calloc(MAX_STRING, sizeof(char));
|
||||
fputs("\nCommand: ", stdout);
|
||||
fflush(stdout);
|
||||
command = fgets(command, MAX_STRING, stdin);
|
||||
while (command != NULL) {
|
||||
if (strcmp("h\n", command) == 0 || strcmp("help\n", command) == 0) {
|
||||
how_to_use();
|
||||
} else if (strcmp("l\n", command) == 0 || strcmp("list\n", command) == 0) {
|
||||
print_menu(menu, menu == head);
|
||||
} else if (strncmp("o ", command, 2) == 0 || strncmp("open ", command, 5) == 0) {
|
||||
menu = submenu(command, menu, head);
|
||||
print_menu(menu, menu == head);
|
||||
} else if (strncmp("? ", command, 2) == 0 || strncmp("describe ", command, 9) == 0) {
|
||||
print_description(command, menu);
|
||||
} else if (strcmp("g all\n", command) == 0 || strcmp("get all\n", command) == 0) {
|
||||
get_all_values(menu);
|
||||
} else if (strncmp("g ", command, 2) == 0 || strncmp("get ", command, 4) == 0) {
|
||||
get_opt_value(command, menu);
|
||||
} else if (strncmp("s ", command, 2) == 0 || strncmp("set ", command, 4) == 0) {
|
||||
set_opt_value(command, menu);
|
||||
} else if (strncmp("r ", command, 2) == 0 || strncmp("reset ", command, 6) == 0) {
|
||||
reset_value(command, menu);
|
||||
} else if (strcmp("e\n", command) == 0 || strcmp("exit\n", command) == 0) {
|
||||
if (!any_unset(head)) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
fputs("Unknown command ", stdout);
|
||||
fputs(command, stdout);
|
||||
}
|
||||
fputs("\nCommand: ", stdout);
|
||||
fflush(stdout);
|
||||
/*
|
||||
* M2-Planet's fgets does not properly terminate the buffer if there is
|
||||
* already data in it
|
||||
*/
|
||||
free(command);
|
||||
command = calloc(MAX_STRING, sizeof(char));
|
||||
command = fgets(command, MAX_STRING, stdin);
|
||||
}
|
||||
|
||||
if (any_unset(head)) {
|
||||
fputs(
|
||||
"Uh oh! You have left me in a tough position - you can't input further because you\n"
|
||||
"closed the input stream. But the inputs you gave me are not valid!\n"
|
||||
"I'm going to re-exec myself and hope you are able to start again from scratch.\n",
|
||||
stderr
|
||||
);
|
||||
execve(argv[0], argv, envp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
write_cfg_values(head, extra, TRUE);
|
||||
|
||||
fputs("\nThank you! We will now continue with the bootstrap.\n", stdout);
|
||||
|
||||
return 0;
|
||||
}
|
||||
1
seed/configurator.x86.checksums
Normal file
1
seed/configurator.x86.checksums
Normal file
|
|
@ -0,0 +1 @@
|
|||
bc373892becaa92c52bda2d0e8389931d0859e97848e53b3b9d10ebf7cba9651 configurator
|
||||
|
|
@ -176,30 +176,8 @@ char *get_var(char *name) {
|
|||
last = var;
|
||||
}
|
||||
|
||||
/* If the variable is unset, prompt the user. */
|
||||
if (variables == NULL) {
|
||||
variables = calloc(1, sizeof(Variable));
|
||||
var = variables;
|
||||
} else {
|
||||
last->next = calloc(1, sizeof(Variable));
|
||||
var = last->next;
|
||||
}
|
||||
var->name = calloc(strlen(name) + 1, sizeof(char));
|
||||
strcpy(var->name, name);
|
||||
var->val = calloc(MAX_STRING, sizeof(char));
|
||||
fputs("You have not set a value for ", stdout);
|
||||
fputs(name, stdout);
|
||||
fputs(" in bootstrap.cfg. Please set it now:\n", stdout);
|
||||
while (fgets(var->val, MAX_STRING, stdin) == 0 || var->val[0] == '\n') {
|
||||
fputs("Error inputting, try again:\n", stdout);
|
||||
}
|
||||
if (var->val[0] == 0) {
|
||||
fputs("You put in an EOF!\n", stderr);
|
||||
exit(1);
|
||||
}
|
||||
/* Trim the newline. */
|
||||
var->val[strlen(var->val)] = 0;
|
||||
return var->val;
|
||||
/* If the variable is unset, take it to be the empty string. */
|
||||
return "";
|
||||
}
|
||||
|
||||
/* Recursive descent interpreter. */
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
dc6106dbc02839cdc9e3e2348432242eb6d33d840ab74badfd63c3c9997462b9 script-generator
|
||||
475ee131b41f42f158b18dfa5461eb860d2a2e895bb1593b1957b7266035b14c script-generator
|
||||
|
|
|
|||
|
|
@ -64,6 +64,15 @@ MES_PKG=mes-0.26
|
|||
MES_PREFIX=${SRCDIR}/${MES_PKG}/build/${MES_PKG}
|
||||
GUILE_LOAD_PATH=${MES_PREFIX}/mes/module:${MES_PREFIX}/module:${SRCDIR}/${MES_PKG}/build/${NYACC_PKG}/module
|
||||
|
||||
M2-Mesoplanet --architecture ${ARCH} -f configurator.c -o configurator
|
||||
# Checksums
|
||||
if match x${UPDATE_CHECKSUMS} xTrue; then
|
||||
sha256sum -o configurator.${ARCH}.checksums configurator
|
||||
else
|
||||
sha256sum -c configurator.${ARCH}.checksums
|
||||
fi
|
||||
./configurator /steps/configurator
|
||||
|
||||
M2-Mesoplanet --architecture ${ARCH} -f script-generator.c -o script-generator
|
||||
# Checksums
|
||||
if match x${UPDATE_CHECKSUMS} xTrue; then
|
||||
|
|
@ -72,4 +81,5 @@ else
|
|||
sha256sum -c script-generator.${ARCH}.checksums
|
||||
fi
|
||||
./script-generator /steps/manifest
|
||||
|
||||
kaem --file /steps/0.sh
|
||||
|
|
|
|||
80
steps/configurator
Normal file
80
steps/configurator
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
# SPDX-FileCopyrightText: 2024 fosslinux <fosslinux@aussies.space>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# The structure of this file:
|
||||
# <option type> <id> <variable> <type> <default> <short description>
|
||||
# <long description>
|
||||
#
|
||||
# repeat
|
||||
|
||||
m /general _ _ _ General configuration
|
||||
Settings for how you would like live-bootstrap to run.
|
||||
|
||||
o /general/timestamps FORCE_TIMESTAMPS bool False Zero timestamps at end
|
||||
At the end of the bootstrap, set the timestamps of all files to 0 UNIX time.
|
||||
|
||||
o /general/swap SWAP_SIZE size 0 Swap size
|
||||
The size of a swapfile to be created once swap is supported in live-bootsrap.
|
||||
Intended for systems with low RAM.
|
||||
|
||||
o /general/kernel_bootstrap KERNEL_BOOTSTRAP bool True Use the kernel bootstrap
|
||||
This only applies if you are not in a chroot-like environment. You should
|
||||
already know whether you want this flag or not. If you booted using
|
||||
builder-hex0, this flag should be true; if you booted using your own Linux
|
||||
kernel, this flag should be false.
|
||||
|
||||
o /general/interactive INTERACTIVE bool True Drop to a shell post-bootstrap
|
||||
At the end of the bootstrap, drop to a shell which you can do further things
|
||||
in.
|
||||
|
||||
o /general/disk DISK "" _ [IMPORTANT] [DANGER] Disk
|
||||
live-bootstrap needs a disk which the final system will be installed onto.
|
||||
This should be in the form of the name of the device node, eg sda. If you
|
||||
have externally downloaded sources or the like, place in here the device
|
||||
node with the appropriate partition, eg sda1.
|
||||
Disclaimer for bare metal users:
|
||||
It is highly recommended that all disks other than this disk are removed
|
||||
from the system to avoid accidental data loss. It is vital that you choose
|
||||
this correctly, otherwise you risk overwriting an existing disk on your
|
||||
system. LIVE-BOOTSTRAP TAKES NO LIABILITY FOR ANY DATA LOSS RESULTING FROM
|
||||
ITS USE.
|
||||
|
||||
m /sysinfo _ _ _ System information
|
||||
Details about your specific system and the environment live-bootstrap is
|
||||
running in.
|
||||
|
||||
o /sysinfo/chroot CHROOT bool False Chroot-like environment
|
||||
Only set to True if live-bootstrap is running in a chroot-like environment.
|
||||
This might be in a chroot, on bubblewrap, or similar. QEMU is not a
|
||||
chroot-like environment.
|
||||
|
||||
o /sysinfo/baremetal BARE_METAL bool False Bare metal environment
|
||||
Only set to True if live-bootstrap is running directly on bare metal, without
|
||||
another kernel or virtualisation layer in between. A chroot, bubblewrap, or
|
||||
QEMU is not bare metal.
|
||||
|
||||
o /sysinfo/jobs JOBS int _ Number of jobs
|
||||
The number of jobs that packages should be compiled with. A sensible value
|
||||
would be the number of threads on your system.
|
||||
|
||||
m /sysinfo/internal _ _ _ [INTERNAL] Advanced configuration
|
||||
Internal configuration. You should not touch this unless you know what you
|
||||
are doing!
|
||||
|
||||
o /sysinfo/internal/arch ARCH "x86|amd64|riscv64" _ Architecture
|
||||
The architecture live-bootstrap is running on. This should already be correct!
|
||||
|
||||
o /sysinfo/internal/ci INTERNAL_CI bool False Internal CI
|
||||
If you are seeing this, it should not be set to true. (Flag for live-bootstrap
|
||||
CI).
|
||||
|
||||
m /dev _ _ _ [DEV] Development options
|
||||
Options intended for live-bootstrap development or debugging.
|
||||
|
||||
o /dev/update_checksums UPDATE_CHECKSUMS bool False [DEV] Update checksums of packages
|
||||
Rather than checking checksums of packages against the existing list, generate
|
||||
a new list for the checksums of packages.
|
||||
|
||||
o /dev/build_kernels BUILD_KERNELS bool _ [DEV] Build kernels
|
||||
Even when they are not required, still build kernels/kernel adjacent packages.
|
||||
Loading…
Add table
Add a link
Reference in a new issue