main 6af0a61

config.go RawHistoryFolder

1package config
2
3import (
4	"bufio"
5	"fmt"
6	"os"
7	"strings"
8)
9
10const (
11	DefaultConfigPath = "/var/log/archup-install.conf"
12	DefaultLogPath    = "/var/log/archup-install.log"
13
14	// Install paths
15	DefaultInstallDir     = "/tmp/archup-install"
16	DefaultInstallRepoDir = "/tmp/archup-install/repo"
17	DefaultInstallPath    = ".local/share/archup/install"
18	BasePackagesFile      = "base.packages"
19	ExtraPackagesFile     = "extra.packages"
20	LimineConfigTemplate  = "configs/limine.conf.template"
21)
22
23// Encryption types
24const (
25	EncryptionNone    = "none"
26	EncryptionLUKS    = "luks"
27	EncryptionLUKSLVM = "luks-lvm"
28)
29
30// Bootloader types
31const (
32	BootloaderLimine = "limine"
33)
34
35// Kernel choices
36const (
37	KernelLinux         = "linux"
38	KernelLinuxZen      = "linux-zen"
39	KernelLinuxLTS      = "linux-lts"
40	KernelLinuxHardened = "linux-hardened"
41	KernelLinuxCachyOS  = "linux-cachyos"
42)
43
44// System paths
45const (
46	PathMnt = "/mnt"
47)
48
49// Mkinitcpio hooks
50const (
51	MkinitcpioHooksPlymouth  = "HOOKS=(base udev autodetect microcode modconf kms keyboard keymap consolefont block plymouth filesystems fsck)"
52	MkinitcpioHooksEncrypted = "HOOKS=(base udev autodetect microcode modconf kms keyboard keymap consolefont block plymouth encrypt filesystems fsck)"
53)
54
55// Kernel parameters
56const (
57	KernelParamsQuiet = "quiet splash loglevel=3 rd.udev.log_priority=3 systemd.show_status=auto"
58)
59
60// Limine configuration
61const (
62	LimineColor = "6"
63)
64
65// UEFI boot entry
66const (
67	UEFIBootLabel  = "Arch Linux"
68	UEFIBootLoader = "\\EFI\\limine\\BOOTX64.EFI"
69)
70
71// PostInstall paths
72const (
73	PathMntPlymouthThemes  = "/mnt/usr/share/plymouth/themes"
74	PathMntEtcPacmanDHooks = "/mnt/etc/pacman.d/hooks"
75	PathBootLimineConf     = "/boot/limine.conf"
76	PlymouthThemeName      = "archup"
77)
78
79// Zram configuration
80const (
81	ZramConfigContent = `[zram0]
82zram-size = min(ram / 2, 4096)
83compression-algorithm = zstd
84`
85	ZramSysctlContent = `vm.swappiness = 180
86vm.watermark_boost_factor = 0
87vm.watermark_scale_factor = 125
88vm.page-cluster = 0
89`
90)
91
92// Zram file names
93const (
94	FileZramGenerator    = "zram-generator.conf"
95	FileSysctlZramParams = "99-vm-zram-parameters.conf"
96)
97
98// Sudoers configuration
99const (
100	SudoersWheelContent = "%wheel ALL=(ALL:ALL) ALL\n"
101	SudoersWheelPerms   = 0440
102)
103
104// Plymouth files
105var PlymouthFiles = []string{
106	"archup.plymouth",
107	"archup.script",
108	"logo.png",
109	"bullet.png",
110	"entry.png",
111	"lock.png",
112	"progress_bar.png",
113	"progress_box.png",
114}
115
116// Post-boot paths and files
117const (
118	PathMntPostBoot      = "/mnt/usr/local/share/archup/post-boot"
119	PathMntSystemdSystem = "/mnt/etc/systemd/system"
120	PostBootServiceName  = "archup-first-boot.service"
121)
122
123// Post-boot script files to download
124var PostBootScripts = []string{
125	"all.sh",
126	"snapper.sh",
127	"firewalld.sh",
128	"ssh-keygen.sh",
129	"blesh.sh",
130	"cli-tools.sh",
131	"dms-opt-in.sh",
132}
133
134// Post-boot service template URL path
135const PostBootServiceTemplate = "install/mandatory/post-boot/archup-first-boot.service"
136
137// Config holds the installation configuration
138// This mirrors the shell script's config format for compatibility
139type Config struct {
140	// Preflight
141	Hostname        string
142	Username        string
143	UserPassword    string
144	RootPassword    string
145	Email           string // Optional, for git and SSH configuration
146	Keymap          string
147	Timezone        string
148	Locale          string
149	Bootloader      string // "limine" (only supported bootloader)
150	EncryptionType  string // "none", "luks", or "luks-lvm"
151	EncryptPassword string // Separate encryption password if different from user password
152
153	// Form-only fields (not persisted)
154	ConfirmPassword              string // Temporary field for password confirmation
155	UseSamePasswordForEncryption bool   // Checkbox state for using same password for encryption
156
157	// Partitioning
158	TargetDisk    string
159	BootPartition string
160	RootPartition string
161	SwapPartition string
162	EFIPartition  string
163	CryptDevice   string // /dev/mapper/cryptroot if encrypted
164
165	// Base
166	KernelChoice string // "linux", "linux-zen", "linux-lts", "linux-hardened"
167	Microcode    string // "intel-ucode" or "amd-ucode"
168	CPUVendor    string // "Intel", "AMD", or "Unknown"
169	AMDPState    string // "active", "passive", "guided", or empty
170
171	// Config
172	NetworkManager string // "NetworkManager" by default
173
174	// Repos
175	AURHelper      string // "paru" or "yay"
176	EnableMultilib bool
177
178	// Paths
179	ConfigPath  string
180	LogPath     string
181	InstallPath string // ~/.local/share/archup/install
182	RepoURL     string
183	RawURL      string
184}
185
186// NewConfig creates a new Config with sensible defaults.
187// version determines which git ref to use for downloads and bootstrap assets.
188// ENV=dev forces the dev ref regardless of the version string.
189func NewConfig(version string) *Config {
190	ref := bootstrapRef(version)
191
192	return &Config{
193		Hostname:                     "arch",
194		Locale:                       "en_US.UTF-8",
195		Timezone:                     "UTC",
196		Keymap:                       "us",
197		Bootloader:                   "limine",
198		EncryptionType:               "none",
199		KernelChoice:                 "linux",
200		NetworkManager:               "NetworkManager",
201		AURHelper:                    "paru",
202		EnableMultilib:               true,
203		UseSamePasswordForEncryption: true, // Default to using same password for encryption
204		ConfigPath:                   DefaultConfigPath,
205		LogPath:                      DefaultLogPath,
206		RepoURL:                      "https://github.com/bnema/archup",
207		RawURL:                       fmt.Sprintf("https://raw.githubusercontent.com/bnema/archup/%s", ref),
208	}
209}
210
211func bootstrapRef(version string) string {
212	if os.Getenv("ENV") == "dev" {
213		return "dev"
214	}
215
216	version = strings.TrimSpace(version)
217	switch {
218	case version == "" || version == "dev":
219		return "dev"
220	case strings.Contains(version, "-dev"):
221		return "dev"
222	case strings.Contains(version, "-next"):
223		return "dev"
224	default:
225		if !strings.HasPrefix(version, "v") {
226			return "v" + version
227		}
228		return version
229	}
230}
231
232// Branch returns the git ref to use for asset downloads and repo cloning.
233func (c *Config) Branch() string {
234	rawURL := strings.TrimSpace(c.RawURL)
235	if rawURL == "" {
236		return "main"
237	}
238	// Extract branch from RawURL: "https://raw.githubusercontent.com/bnema/archup/<branch>"
239	parts := strings.Split(rawURL, "/")
240	last := parts[len(parts)-1]
241	if last == "" {
242		return "main"
243	}
244	return last
245}
246
247// Load reads configuration from a shell-compatible config file
248func Load(path string, version string) (*Config, error) {
249	cfg := NewConfig(version)
250	cfg.ConfigPath = path
251
252	file, err := os.Open(path)
253	if err != nil {
254		if os.IsNotExist(err) {
255			return cfg, nil // Return defaults if file doesn't exist
256		}
257		return nil, fmt.Errorf("failed to open config: %w", err)
258	}
259
260	scanner := bufio.NewScanner(file)
261	for scanner.Scan() {
262		line := strings.TrimSpace(scanner.Text())
263
264		// Skip comments and empty lines
265		if line == "" || strings.HasPrefix(line, "#") {
266			continue
267		}
268
269		// Parse KEY=VALUE format
270		parts := strings.SplitN(line, "=", 2)
271		if len(parts) != 2 {
272			continue
273		}
274
275		key := strings.TrimSpace(parts[0])
276		value := strings.Trim(strings.TrimSpace(parts[1]), `"'`) // Remove quotes
277
278		cfg.setValue(key, value)
279	}
280
281	if err := scanner.Err(); err != nil {
282		return nil, fmt.Errorf("failed to read config: %w", err)
283	}
284
285	if err := file.Close(); err != nil {
286		return nil, fmt.Errorf("failed to close config file: %w", err)
287	}
288
289	return cfg, nil
290}
291
292// Save writes configuration to a shell-compatible config file
293func (c *Config) Save() error {
294	file, err := os.Create(c.ConfigPath)
295	if err != nil {
296		return fmt.Errorf("failed to create config: %w", err)
297	}
298	// Set restrictive permissions (600) since config contains passwords
299	if err := os.Chmod(c.ConfigPath, 0600); err != nil {
300		return fmt.Errorf("failed to set config permissions: %w", err)
301	}
302
303	writer := bufio.NewWriter(file)
304
305	// Write header
306	if _, err := fmt.Fprintln(writer, "# ArchUp Installation Configuration"); err != nil {
307		return fmt.Errorf("failed to write config header: %w", err)
308	}
309	if _, err := fmt.Fprintln(writer, "# This file is compatible with shell scripts"); err != nil {
310		return fmt.Errorf("failed to write config header: %w", err)
311	}
312	if _, err := fmt.Fprintln(writer); err != nil {
313		return fmt.Errorf("failed to write config header: %w", err)
314	}
315
316	// Write all values
317	entries := []struct {
318		key   string
319		value string
320	}{
321		{"ARCHUP_HOSTNAME", c.Hostname},
322		{"ARCHUP_USERNAME", c.Username},
323		{"ARCHUP_USER_PASSWORD", c.UserPassword},
324		{"ARCHUP_ROOT_PASSWORD", c.RootPassword},
325		{"ARCHUP_KEYMAP", c.Keymap},
326		{"ARCHUP_TIMEZONE", c.Timezone},
327		{"ARCHUP_LOCALE", c.Locale},
328		{"ARCHUP_BOOTLOADER", c.Bootloader},
329		{"ARCHUP_ENCRYPTION", c.EncryptionType},
330		{"ARCHUP_ENCRYPTION_PASSWORD", c.EncryptPassword},
331		{"ARCHUP_TARGET_DISK", c.TargetDisk},
332		{"ARCHUP_BOOT_PARTITION", c.BootPartition},
333		{"ARCHUP_ROOT_PARTITION", c.RootPartition},
334		{"ARCHUP_SWAP_PARTITION", c.SwapPartition},
335		{"ARCHUP_EFI_PARTITION", c.EFIPartition},
336		{"ARCHUP_KERNEL", c.KernelChoice},
337		{"ARCHUP_AMD_PSTATE", c.AMDPState},
338		{"ARCHUP_NETWORK_MANAGER", c.NetworkManager},
339		{"ARCHUP_AUR_HELPER", c.AURHelper},
340		{"ARCHUP_ENABLE_MULTILIB", boolToString(c.EnableMultilib)},
341	}
342
343	for _, entry := range entries {
344		if entry.value != "" {
345			if _, err := fmt.Fprintf(writer, "%s=\"%s\"\n", entry.key, entry.value); err != nil {
346				return fmt.Errorf("failed to write config entry: %w", err)
347			}
348		}
349	}
350
351	if err := writer.Flush(); err != nil {
352		return fmt.Errorf("failed to flush config writer: %w", err)
353	}
354
355	if err := file.Close(); err != nil {
356		return fmt.Errorf("failed to close config file: %w", err)
357	}
358
359	return nil
360}
361
362// setValue sets a config value based on key name
363func (c *Config) setValue(key, value string) {
364	switch key {
365	case "ARCHUP_HOSTNAME":
366		c.Hostname = value
367	case "ARCHUP_USERNAME":
368		c.Username = value
369	case "ARCHUP_USER_PASSWORD":
370		c.UserPassword = value
371	case "ARCHUP_ROOT_PASSWORD":
372		c.RootPassword = value
373	case "ARCHUP_KEYMAP":
374		c.Keymap = value
375	case "ARCHUP_TIMEZONE":
376		c.Timezone = value
377	case "ARCHUP_LOCALE":
378		c.Locale = value
379	case "ARCHUP_BOOTLOADER":
380		c.Bootloader = value
381	case "ARCHUP_ENCRYPTION":
382		c.EncryptionType = value
383	case "ARCHUP_ENCRYPTION_PASSWORD":
384		c.EncryptPassword = value
385	case "ARCHUP_TARGET_DISK":
386		c.TargetDisk = value
387	case "ARCHUP_BOOT_PARTITION":
388		c.BootPartition = value
389	case "ARCHUP_ROOT_PARTITION":
390		c.RootPartition = value
391	case "ARCHUP_SWAP_PARTITION":
392		c.SwapPartition = value
393	case "ARCHUP_EFI_PARTITION":
394		c.EFIPartition = value
395	case "ARCHUP_KERNEL":
396		c.KernelChoice = value
397	case "ARCHUP_AMD_PSTATE":
398		c.AMDPState = value
399	case "ARCHUP_NETWORK_MANAGER":
400		c.NetworkManager = value
401	case "ARCHUP_AUR_HELPER":
402		c.AURHelper = value
403	case "ARCHUP_ENABLE_MULTILIB":
404		c.EnableMultilib = stringToBool(value)
405	}
406}
407
408func boolToString(b bool) string {
409	if b {
410		return "true"
411	}
412	return "false"
413}
414
415func stringToBool(s string) bool {
416	return s == "true" || s == "1" || s == "yes"
417}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 0

    ↑↓ moveenter openesc close