|
| 1 | +#!/usr/bin/env python3 |
| 2 | +# SPDX-License-Identifier: MIT |
| 3 | +# Copyright (C) 2022 Nathan Chancellor |
| 4 | + |
| 5 | +from argparse import ArgumentParser |
| 6 | +import os |
| 7 | +from pathlib import Path |
| 8 | +import re |
| 9 | +import shutil |
| 10 | +import subprocess |
| 11 | +import time |
| 12 | + |
| 13 | +import lib_root |
| 14 | + |
| 15 | + |
| 16 | +def parse_arguments(): |
| 17 | + parser = ArgumentParser(description='Set up an Alpine installation') |
| 18 | + |
| 19 | + parser.add_argument('-n', |
| 20 | + '--user-name', |
| 21 | + default=lib_root.get_user(), |
| 22 | + help='Name of user account') |
| 23 | + parser.add_argument('-p', '--user-password', help='Password for user account', required=True) |
| 24 | + |
| 25 | + return parser.parse_args() |
| 26 | + |
| 27 | + |
| 28 | +def enable_community_repo(): |
| 29 | + repo_conf = Path('/etc/apk/repositories') |
| 30 | + repo_txt = repo_conf.read_text(encoding='utf-8') |
| 31 | + |
| 32 | + # Get the repository URL to create the community repo from (build from the |
| 33 | + # first uncommented line ending in main). |
| 34 | + if not (repo_url := re.search('^([^#].*/)main$', repo_txt, flags=re.M).groups()[0]): |
| 35 | + raise Exception(f"Could not find main repo in {repo_conf}?") |
| 36 | + community_repo = repo_url + 'community' |
| 37 | + |
| 38 | + # If the community repo is already enabled (uncommented), we do not need to |
| 39 | + # do anything. |
| 40 | + if (match := re.search(f"^#{community_repo}$", repo_txt, flags=re.M)): |
| 41 | + repo_conf.write_text(re.sub(match.group(0), community_repo, repo_txt), encoding='utf-8') |
| 42 | + |
| 43 | + |
| 44 | +def update_and_install_packages(): |
| 45 | + packages = [ |
| 46 | + # Development |
| 47 | + 'hyperfine', |
| 48 | + 'podman', |
| 49 | + |
| 50 | + # env |
| 51 | + 'curl', |
| 52 | + 'fish', |
| 53 | + 'fzf', |
| 54 | + 'jq', |
| 55 | + 'neofetch', |
| 56 | + 'stow', |
| 57 | + 'tmux', |
| 58 | + 'vim', |
| 59 | + 'zoxide', |
| 60 | + |
| 61 | + # git |
| 62 | + 'delta', |
| 63 | + 'git', |
| 64 | + |
| 65 | + # Nicer GNU utilities |
| 66 | + 'bat', |
| 67 | + 'exa', |
| 68 | + 'fd', |
| 69 | + 'ripgrep', |
| 70 | + |
| 71 | + # System management |
| 72 | + 'doas', |
| 73 | + ] |
| 74 | + lib_root.apk(['update']) |
| 75 | + lib_root.apk(['upgrade']) |
| 76 | + lib_root.apk(['add', *packages]) |
| 77 | + |
| 78 | + |
| 79 | +def setup_user(user_name, user_password): |
| 80 | + if not lib_root.user_exists(user_name): |
| 81 | + useradd_cmd = [ |
| 82 | + 'adduser', |
| 83 | + '--disabled-password', |
| 84 | + '--gecos', 'Nathan Chancellor', |
| 85 | + '--shell', shutil.which('fish'), |
| 86 | + user_name |
| 87 | + ] # yapf: disable |
| 88 | + subprocess.run(useradd_cmd, check=True) |
| 89 | + lib_root.chpasswd(user_name, user_password) |
| 90 | + |
| 91 | + user_groups = [ |
| 92 | + # Default setup-alpine |
| 93 | + 'audio', |
| 94 | + 'netdev', |
| 95 | + 'video', |
| 96 | + # Mine |
| 97 | + 'kvm', |
| 98 | + 'wheel', |
| 99 | + ] |
| 100 | + for group in user_groups: |
| 101 | + subprocess.run(['addgroup', user_name, group], check=True) |
| 102 | + |
| 103 | + # Setup doas |
| 104 | + doas_conf = Path('/etc/doas.d/doas.conf') |
| 105 | + doas_wheel = 'permit persist :wheel' |
| 106 | + if not re.search(doas_wheel, doas_conf.read_text(encoding='utf-8')): |
| 107 | + with open(doas_conf, 'a', encoding='utf-8') as file: |
| 108 | + file.write(doas_wheel + '\n') |
| 109 | + |
| 110 | + # Authorize my ssh key |
| 111 | + if not (ssh_authorized_keys := Path('/home', user_name, '.ssh', 'authorized_keys')).exists(): |
| 112 | + old_umask = os.umask(0o077) |
| 113 | + ssh_authorized_keys.parent.mkdir(exist_ok=True, parents=True) |
| 114 | + ssh_key = subprocess.run(['wget', '-q', '-O-', 'https://github.com/nathanchance.keys'], |
| 115 | + capture_output=True, |
| 116 | + check=True).stdout |
| 117 | + ssh_authorized_keys.write_bytes(ssh_key) |
| 118 | + os.umask(old_umask) |
| 119 | + lib_root.chown(user_name, ssh_authorized_keys.parent) |
| 120 | + |
| 121 | + |
| 122 | +# https://wiki.alpinelinux.org/wiki/Podman |
| 123 | +def setup_podman(user_name): |
| 124 | + # Set up cgroupsv2 |
| 125 | + rc_conf = Path('/etc/rc.conf') |
| 126 | + rc_conf_txt = rc_conf.read_text(encoding='utf-8') |
| 127 | + rc_cgroup_mode = 'rc_cgroup_mode="unified"' |
| 128 | + if not re.search(f"^{rc_cgroup_mode}$", rc_conf_txt, flags=re.M): |
| 129 | + rc_cgroup_mode_line = re.search('^#?rc_cgroup_mode=.*$', rc_conf_txt, flags=re.M).group(0) |
| 130 | + rc_conf.write_text(re.sub(rc_cgroup_mode_line, rc_cgroup_mode, rc_conf_txt), |
| 131 | + encoding='utf-8') |
| 132 | + |
| 133 | + subprocess.run(['rc-update', 'add', 'cgroups'], check=True) |
| 134 | + subprocess.run(['rc-service', 'cgroups', 'start'], check=True) |
| 135 | + |
| 136 | + subprocess.run(['modprobe', 'tun'], check=True) |
| 137 | + etc_modules = Path('/etc/modules') |
| 138 | + if not re.search('tun', etc_modules.read_text(encoding='utf-8')): |
| 139 | + with open(etc_modules, mode='a', encoding='utf-8') as file: |
| 140 | + file.write('tun\n') |
| 141 | + |
| 142 | + lib_root.podman_setup(user_name) |
| 143 | + |
| 144 | + |
| 145 | +if __name__ == '__main__': |
| 146 | + args = parse_arguments() |
| 147 | + |
| 148 | + lib_root.check_root() |
| 149 | + enable_community_repo() |
| 150 | + update_and_install_packages() |
| 151 | + setup_user(args.user_name, args.user_password) |
| 152 | + setup_podman(args.user_name) |
| 153 | + lib_root.setup_sudo_symlink() |
| 154 | + lib_root.setup_initial_fish_config(args.user_name) |
| 155 | + |
| 156 | + print("[INFO] Powering off machine in 10 seconds, hit Ctrl-C to cancel...") |
| 157 | + time.sleep(10) |
| 158 | + subprocess.run('poweroff', check=True) |
0 commit comments