feat(rewrite): Start rewrite
This commit is contained in:
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
# PYTHON_ARGCOMPLETE_OK
|
||||
|
||||
# TODO: Re-export the config stuff
|
||||
|
||||
if __name__ == "__main__":
|
||||
import app as _app
|
||||
|
||||
_app.run()
|
||||
Executable → Regular
+5
-8
@@ -1,8 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
# PYTHON_ARGCOMPLETE_OK
|
||||
|
||||
# The main app
|
||||
from typing import cast
|
||||
import cli.args as cliargs
|
||||
import cli as cliargs
|
||||
|
||||
import commands.commit as commit
|
||||
import commands.config as config
|
||||
@@ -11,10 +9,9 @@ import commands.pull as pull
|
||||
import commands.push as push
|
||||
import commands.prepare as setup
|
||||
import commands.show as show
|
||||
from config import load_config
|
||||
from presets import ArchMgrTemplates
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
def run():
|
||||
args, ap = cliargs.add_cli_args()
|
||||
|
||||
if args.cmd == None:
|
||||
@@ -33,7 +30,7 @@ if __name__ == "__main__":
|
||||
\\___/
|
||||
""")
|
||||
|
||||
conf = load_config("config.yml")
|
||||
conf = {}
|
||||
try:
|
||||
if args.cmd == "commit":
|
||||
commit.commit(conf, args.force, args.no_render)
|
||||
@@ -1,7 +1,8 @@
|
||||
# CLI argument parsing and setup for it
|
||||
import argparse
|
||||
import argcomplete
|
||||
|
||||
from cli.commands import commit, config, init, pull, push, show
|
||||
from args import commit, init, pull, push, show
|
||||
|
||||
|
||||
def add_cli_args():
|
||||
@@ -22,7 +23,7 @@ def add_cli_args():
|
||||
# └ ┘
|
||||
sp.add_parser("setup", help="Do initial setup, like installing required tools")
|
||||
commit.add_parser(sp)
|
||||
config.add_parser(sp)
|
||||
sp.add_parser("config", help="prints information about your config")
|
||||
init.add_parser(sp)
|
||||
pull.add_parser(sp)
|
||||
push.add_parser(sp)
|
||||
@@ -1,21 +0,0 @@
|
||||
import argparse as ap
|
||||
|
||||
|
||||
def add_parser(sp: ap._SubParsersAction[ap.ArgumentParser]):
|
||||
config = sp.add_parser(
|
||||
"config", help="prints information about your config and change some of them"
|
||||
)
|
||||
conf_sp = config.add_subparsers(title="config options", dest="conf", required=True)
|
||||
conf_sp.add_parser("show", help="show config options")
|
||||
config_update = conf_sp.add_parser(
|
||||
"update", help="update the configuration from various"
|
||||
)
|
||||
conf_update_sp = config_update.add_subparsers(
|
||||
title="Options for automatic configuration updates",
|
||||
dest="conf_update",
|
||||
required=True,
|
||||
)
|
||||
conf_update_sp.add_parser(
|
||||
"pkgs",
|
||||
help="Update the config to reflect the package state of the current system",
|
||||
)
|
||||
@@ -1,23 +0,0 @@
|
||||
from commands.util import pacman
|
||||
from commands.util.diff import pkg_diff
|
||||
from commands.util.input_mgr import confirm, password
|
||||
|
||||
from commands.util.printing.diff import print_diff
|
||||
from config.dtype import ArchMgrConfig
|
||||
|
||||
|
||||
def commit(config: ArchMgrConfig, force: bool = False, no_render: bool = False):
|
||||
"""Commit the changes to the system
|
||||
|
||||
Args:
|
||||
force: Apply, overriding any changes since the last commit without confirming
|
||||
no_render: Don't rerender the templates (use cached version).
|
||||
Will be ignored if cache is unavailable (and prompt the user)
|
||||
"""
|
||||
# TODO: Make sure we don't uninstall critical system packages by accident (i.e. prompt user)
|
||||
# Probably do that check in the pacman util lib tho
|
||||
add, remove = pkg_diff(config["pkgs"]["individual"] or [], pacman.list_explicitly_installed())
|
||||
print_diff(add, remove)
|
||||
if confirm(False, "Do you really want to proceed?"):
|
||||
pacman.install_package_list(add)
|
||||
pacman.uninstall_package_list(remove)
|
||||
@@ -1,13 +0,0 @@
|
||||
import argparse
|
||||
from config.dtype import ArchMgrConfig
|
||||
|
||||
|
||||
def config(args: argparse.Namespace, config: ArchMgrConfig):
|
||||
if args.conf == "show":
|
||||
print("""
|
||||
Your config can be found at
|
||||
""")
|
||||
print(config)
|
||||
elif args.conf_update == "pkgs":
|
||||
print("Updating config's package list")
|
||||
# TODO: Config saver
|
||||
@@ -1,30 +0,0 @@
|
||||
import os
|
||||
import shutil
|
||||
import commands.util.git as git
|
||||
from commands.util.input_mgr import confirm
|
||||
|
||||
|
||||
def init(force: bool = False):
|
||||
dir = os.getcwd()
|
||||
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
if force:
|
||||
if confirm(
|
||||
False,
|
||||
"Do you really want to IRREVERSIBLY DELETE the contents of this folder and redo setup?",
|
||||
):
|
||||
[os.remove(file) for file in os.listdir(dir)]
|
||||
git.init(dir)
|
||||
os.mkdir(dir + "/config")
|
||||
os.mkdir(dir + "/system")
|
||||
os.mkdir(dir + "/includes")
|
||||
shutil.copy(script_dir + "/templates/config.yml", dir + "/config.yml")
|
||||
shutil.copy(script_dir + "/templates/system/", dir + "/includes/system/")
|
||||
shutil.copy(script_dir + "/templates/templates/", dir + "/includes/templates/")
|
||||
shutil.copy(script_dir + "/templates/README.md", dir + "/README.md")
|
||||
print("Initialized a new archmgr repository")
|
||||
# TODO: Instead of copying things over, clone the template repo, then copy its contents to the new repo?
|
||||
# TODO: For the files, store the permissions in a db
|
||||
# TODO: Warn user to not delete .config/archmgr repo
|
||||
# TODO: Set up that repo (where to put it? /usr/share? or .config?)
|
||||
# TODO: Consider collecting function -> If no files present, will only collect the pkgs, else also the files
|
||||
# TODO: Also store the folder name of the config folder in that repo (needs to be easily changeable for user!)
|
||||
@@ -1,30 +0,0 @@
|
||||
import subprocess
|
||||
|
||||
from commands.util import pacman
|
||||
from commands.util.input_mgr import confirm
|
||||
|
||||
|
||||
def setup():
|
||||
print("==> Installing required packages")
|
||||
if not pacman.install_package_list(["git"]):
|
||||
print("Git installation failed")
|
||||
return
|
||||
|
||||
subprocess.run(
|
||||
["git", "clone", "https://aur.archlinux.org/yay.git"],
|
||||
cwd="/tmp",
|
||||
capture_output=True,
|
||||
)
|
||||
yay_install = subprocess.run(
|
||||
["makepkg", "-si"], cwd="/tmp/yay", capture_output=True
|
||||
)
|
||||
|
||||
if yay_install.returncode != 0:
|
||||
print("==> Installation of yay failed")
|
||||
if confirm(True, "Do you wish to view the logs?"):
|
||||
print(yay_install.stdout, "\n", yay_install.stderr)
|
||||
return
|
||||
|
||||
print("==> Installation completed")
|
||||
|
||||
# TODO: Check if yay is available before installing
|
||||
@@ -1,5 +0,0 @@
|
||||
from config.dtype import ArchMgrConfig
|
||||
|
||||
|
||||
def pull(config: ArchMgrConfig, rebase: bool = False, apply: bool = False):
|
||||
print("pull")
|
||||
@@ -1,2 +0,0 @@
|
||||
def push(force: bool = False):
|
||||
print("push")
|
||||
@@ -1,21 +0,0 @@
|
||||
import argparse
|
||||
|
||||
from config.dtype import ArchMgrConfig
|
||||
from templates import ArchMgrTemplates
|
||||
|
||||
|
||||
# TODO: Templates data
|
||||
def show(
|
||||
config: ArchMgrConfig,
|
||||
spec: ArchMgrTemplates,
|
||||
args: argparse.Namespace,
|
||||
):
|
||||
"""Show details / information about things like contents pkg bundles
|
||||
|
||||
Args:
|
||||
config: The configuration
|
||||
spec: The archmgr templates that were loaded
|
||||
kind: The type of information to get
|
||||
arg: The arg for the command (e.g. bundle name)
|
||||
"""
|
||||
print(args)
|
||||
@@ -1,34 +0,0 @@
|
||||
from typing import List
|
||||
|
||||
|
||||
def pkg_diff(target: List[str], actual: List[str]) -> tuple[List[str], List[str]]:
|
||||
"""Compute a diff between target packages and installed packages
|
||||
|
||||
Args:
|
||||
target: The target packages
|
||||
actual: The actually installed packages
|
||||
|
||||
Returns:
|
||||
A tuple with first the missing (not installed) packages
|
||||
and second the extraneous (to be uninstalled) packages
|
||||
"""
|
||||
removed = []
|
||||
diffed_out = []
|
||||
for pkg in actual:
|
||||
try:
|
||||
diffed_out.append(target.index(pkg))
|
||||
except Exception:
|
||||
removed.append(pkg)
|
||||
|
||||
diffed_out.reverse()
|
||||
new_pkgs = []
|
||||
if len(diffed_out) > 0:
|
||||
curr = diffed_out.pop()
|
||||
for i, pkg in enumerate(target):
|
||||
if i != curr:
|
||||
new_pkgs.append(pkg)
|
||||
else:
|
||||
if len(diffed_out) > 0:
|
||||
curr = diffed_out.pop()
|
||||
|
||||
return (new_pkgs, removed)
|
||||
@@ -1,105 +0,0 @@
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
|
||||
def init(dirname: str) -> bool:
|
||||
"""Initialize a new git repo in the directory
|
||||
|
||||
Args:
|
||||
dirname: The directory to create it in
|
||||
|
||||
Returns:
|
||||
True on success, False on error
|
||||
"""
|
||||
return subprocess.call("git init", cwd=dirname) == 0
|
||||
|
||||
|
||||
def commit(msg: str):
|
||||
"""Create a new commit
|
||||
|
||||
Args:
|
||||
msg: The commit message
|
||||
|
||||
Returns:
|
||||
True on success, False on error
|
||||
"""
|
||||
dir = os.getcwd()
|
||||
return subprocess.call('git commit -am "' + msg + '"', cwd=dir) == 0
|
||||
|
||||
|
||||
def branch_add(name: str):
|
||||
"""Create a new git branch
|
||||
|
||||
Args:
|
||||
name: The name of the branch to create
|
||||
|
||||
Returns:
|
||||
True on success, False on error
|
||||
"""
|
||||
dir = os.getcwd()
|
||||
return subprocess.call("git branch " + name, cwd=dir) == 0
|
||||
|
||||
|
||||
def branch_switch(name: str):
|
||||
"""Switch to a git branch
|
||||
|
||||
Args:
|
||||
name: The name of the branch to switch to
|
||||
|
||||
Returns:
|
||||
True on success, False on error
|
||||
"""
|
||||
dir = os.getcwd()
|
||||
return subprocess.call("git checkout " + name, cwd=dir) == 0
|
||||
|
||||
|
||||
def branch_show():
|
||||
"""Create a new git branch
|
||||
|
||||
Args:
|
||||
name: The name of the branch to create
|
||||
|
||||
Returns:
|
||||
True on success, False on error
|
||||
"""
|
||||
dir = os.getcwd()
|
||||
return subprocess.call("git branch", cwd=dir) == 0
|
||||
|
||||
|
||||
def branch_delete(name: str):
|
||||
"""Delete a git branch
|
||||
|
||||
Args:
|
||||
name: The name of the branch to delete
|
||||
|
||||
Returns:
|
||||
True on success, False on error
|
||||
"""
|
||||
dir = os.getcwd()
|
||||
return subprocess.call("git branch -d " + name, cwd=dir) == 0
|
||||
|
||||
|
||||
def pull(rebase: bool = False):
|
||||
"""Pull git changes
|
||||
|
||||
Args:
|
||||
rebase: Whether to rebase or not
|
||||
|
||||
Returns:
|
||||
True on success, False on error
|
||||
"""
|
||||
dir = os.getcwd()
|
||||
return subprocess.call("git pull" + (" --rebase" if rebase else ""), cwd=dir) == 0
|
||||
|
||||
|
||||
def push(force: bool = False):
|
||||
"""Push git changes
|
||||
|
||||
Args:
|
||||
force: Whether to force push
|
||||
|
||||
Returns:
|
||||
True on success, False on error
|
||||
"""
|
||||
dir = os.getcwd()
|
||||
return subprocess.call("git push" + (" --force" if force else ""), cwd=dir) == 0
|
||||
@@ -1,44 +0,0 @@
|
||||
from typing import Optional
|
||||
|
||||
from commands.util.password_manager import PasswordManager
|
||||
|
||||
|
||||
def choice(default: str, options: str, msg: str) -> str:
|
||||
default = default.lower()
|
||||
formatted_options = "".join(
|
||||
[(opt + "/" if opt != default else default.upper() + "/") for opt in options]
|
||||
)[:-1]
|
||||
choice = input(msg + " [" + formatted_options + "] ")
|
||||
if choice == "":
|
||||
return default
|
||||
else:
|
||||
return choice.lower()
|
||||
|
||||
|
||||
def confirm(is_true_default: bool, msg: Optional[str] = ""):
|
||||
return (
|
||||
choice(
|
||||
"y" if is_true_default else "n",
|
||||
"yn",
|
||||
(msg if msg != "" and msg != None else "Do you really want to continue?"),
|
||||
)
|
||||
== "y"
|
||||
)
|
||||
|
||||
|
||||
def confirm_overwrite(msg: Optional[str] = ""):
|
||||
return confirm(
|
||||
False, (msg if msg != "" else "Do you really want to overwrite your changes?")
|
||||
)
|
||||
|
||||
|
||||
# Use class to store the password
|
||||
pw_manager = PasswordManager()
|
||||
|
||||
|
||||
def password():
|
||||
return pw_manager.get()
|
||||
|
||||
|
||||
def unlock_sudo():
|
||||
return pw_manager.validate()
|
||||
@@ -1,64 +0,0 @@
|
||||
import subprocess as sp
|
||||
from typing import List
|
||||
|
||||
from commands.util.input_mgr import password
|
||||
|
||||
pkg_manager = ["yay", "--noconfirm"]
|
||||
|
||||
|
||||
def list_explicitly_installed() -> List[str]:
|
||||
"""List all packages explicitly installed
|
||||
|
||||
Returns:
|
||||
The package list
|
||||
"""
|
||||
return run_pkg_manager_cmd(["-Qeq"]).stdout.split()
|
||||
|
||||
|
||||
def remove_orphans() -> bool:
|
||||
"""Removes all orphan packages
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
return (
|
||||
sp.run(
|
||||
["pacman", "-Qdtq", "|", "sudo", "pacman", "-Rns", "--noconfirm", "-"],
|
||||
capture_output=True,
|
||||
).returncode
|
||||
== 0
|
||||
)
|
||||
|
||||
|
||||
def uninstall_package_list(pkgs: List[str]) -> bool:
|
||||
"""Uninstall all packages in the list
|
||||
|
||||
Args:
|
||||
pkgs: A list of packages to uninstall
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
# TODO: Add guard to protect against uninstalling archmgr and confirm uninstalling crucial pkgs
|
||||
# pkgs.index("archmgr")
|
||||
# TODO: Consider if we want to print out stdout and stderr on err
|
||||
return run_pkg_manager_cmd(["-Rs", *pkgs], True).returncode == 0
|
||||
|
||||
|
||||
def install_package_list(pkgs: List[str]) -> bool:
|
||||
"""Install all packages in the list
|
||||
|
||||
Args:
|
||||
pkgs: A list of packages to install
|
||||
"""
|
||||
return run_pkg_manager_cmd(["-S", *pkgs], True).returncode == 0
|
||||
|
||||
|
||||
def run_pkg_manager_cmd(
|
||||
args: List[str], use_sudo: bool = False
|
||||
) -> sp.CompletedProcess[str]:
|
||||
if use_sudo:
|
||||
pw = password()
|
||||
return sp.run([*pkg_manager, *args], capture_output=True, text=True, input=pw)
|
||||
else:
|
||||
return sp.run([*pkg_manager, *args], capture_output=True, text=True)
|
||||
@@ -1,70 +0,0 @@
|
||||
import getpass
|
||||
import time
|
||||
import colorama
|
||||
import subprocess as sp
|
||||
|
||||
|
||||
class PasswordManager:
|
||||
_pw = ""
|
||||
_wrong_cnt = 0
|
||||
_valid = False
|
||||
|
||||
def get(self, msg: str = ""):
|
||||
"""Get the user's password (uses cached password if PW is valid)
|
||||
Otherwise prompts user
|
||||
|
||||
Args:
|
||||
msg: The message to use for the password prompt
|
||||
|
||||
Returns:
|
||||
The user's password
|
||||
"""
|
||||
if self._valid or self.validate():
|
||||
return self._pw
|
||||
|
||||
if msg != "":
|
||||
self._pw = getpass.getpass(msg)
|
||||
else:
|
||||
self._pw = getpass.getpass()
|
||||
if self._pw != "":
|
||||
if not self.validate():
|
||||
print(
|
||||
colorama.Fore.RED + "Error:",
|
||||
colorama.Style.RESET_ALL + "Invalid Password. Please try again",
|
||||
)
|
||||
return self.get(msg)
|
||||
return self._pw
|
||||
else:
|
||||
time.sleep(1)
|
||||
print(
|
||||
colorama.Fore.RED + "Error:",
|
||||
colorama.Style.RESET_ALL + "Password cannot be empty",
|
||||
)
|
||||
return self.get(msg)
|
||||
|
||||
def validate(self) -> bool:
|
||||
"""Validate that the password is correct by running sudo command
|
||||
|
||||
Returns:
|
||||
True if password is valid, False otherwise
|
||||
"""
|
||||
|
||||
def helper():
|
||||
if self._pw == "":
|
||||
return False
|
||||
try:
|
||||
sp.run(
|
||||
["sudo", "-k"], capture_output=True, text=True
|
||||
).check_returncode()
|
||||
sp.run(
|
||||
["sudo", "-S", "echo"],
|
||||
capture_output=True,
|
||||
input=self._pw,
|
||||
text=True,
|
||||
).check_returncode()
|
||||
except Exception:
|
||||
return False
|
||||
return True
|
||||
|
||||
self._valid = helper()
|
||||
return self._valid
|
||||
@@ -1,64 +0,0 @@
|
||||
from typing import List
|
||||
import colorama as cl
|
||||
|
||||
from commands.util.printing.list import print_list
|
||||
|
||||
|
||||
def print_diff(add: List[str], remove: List[str]):
|
||||
if len(add) == 0 and len(remove) == 0:
|
||||
print(
|
||||
cl.Fore.BLUE
|
||||
+ "-->"
|
||||
+ cl.Style.DIM
|
||||
+ cl.Fore.GREEN
|
||||
+ " No packages changes"
|
||||
+ cl.Style.RESET_ALL,
|
||||
)
|
||||
# Packages to be installed
|
||||
if len(add) == 0:
|
||||
print(
|
||||
cl.Fore.BLUE
|
||||
+ "-->"
|
||||
+ cl.Style.DIM
|
||||
+ cl.Fore.GREEN
|
||||
+ " No packages to be installed"
|
||||
+ cl.Style.RESET_ALL,
|
||||
)
|
||||
else:
|
||||
print(
|
||||
cl.Fore.GREEN + "==>",
|
||||
cl.Style.RESET_ALL + "Packages that will be",
|
||||
cl.Fore.GREEN + "installed" + cl.Style.RESET_ALL,
|
||||
)
|
||||
print_list(add)
|
||||
print()
|
||||
|
||||
# Packages to be removed
|
||||
if len(remove) == 0:
|
||||
print(
|
||||
cl.Fore.BLUE
|
||||
+ "-->"
|
||||
+ cl.Style.DIM
|
||||
+ cl.Fore.GREEN
|
||||
+ " No packages to be uninstalled"
|
||||
+ cl.Style.RESET_ALL,
|
||||
)
|
||||
else:
|
||||
print(
|
||||
cl.Fore.GREEN + "==>",
|
||||
cl.Style.RESET_ALL + "Packages that will be",
|
||||
cl.Fore.RED + "uninstalled" + cl.Style.RESET_ALL,
|
||||
)
|
||||
print_list(remove)
|
||||
print()
|
||||
|
||||
# Ask user to confirm
|
||||
print(
|
||||
cl.Fore.GREEN + "==>",
|
||||
cl.Style.RESET_ALL
|
||||
+ f"Transaction (packages): {cl.Fore.BLUE + str(len(add)) + cl.Style.RESET_ALL}",
|
||||
cl.Fore.GREEN + "installed",
|
||||
cl.Style.RESET_ALL
|
||||
+ f"and {cl.Fore.BLUE + str(len(remove)) + cl.Style.RESET_ALL}",
|
||||
cl.Fore.RED + "uninstalled" + cl.Style.RESET_ALL,
|
||||
)
|
||||
@@ -1,20 +0,0 @@
|
||||
from typing import Any, List
|
||||
import colorama as cl
|
||||
|
||||
|
||||
def count_digits(number: int):
|
||||
return len(str(number))
|
||||
|
||||
|
||||
def print_list(list: List[Any]):
|
||||
digit_count = count_digits(len(list))
|
||||
for i, pkg in enumerate(list):
|
||||
print(
|
||||
" "
|
||||
+ cl.Fore.BLUE
|
||||
+ cl.Style.DIM
|
||||
+ (" " * (digit_count - count_digits(i + 1)))
|
||||
+ str(1 + i)
|
||||
+ cl.Style.RESET_ALL + " ",
|
||||
pkg,
|
||||
)
|
||||
@@ -1,58 +0,0 @@
|
||||
# yaml-language-server: $schema=config.schema.json
|
||||
# TODO: Change the above to an import URL instead
|
||||
requires:
|
||||
- path/to/other/configs/relative/to/this # Reads the other configs after finishing this one
|
||||
|
||||
pkgs:
|
||||
individual:
|
||||
- pkg_name
|
||||
bundles:
|
||||
- name: hyprland
|
||||
repos:
|
||||
reflector:
|
||||
enabled: false
|
||||
countries:
|
||||
- Switzerland
|
||||
|
||||
users:
|
||||
- username: username
|
||||
groups:
|
||||
- group_1
|
||||
home_dir: True
|
||||
|
||||
boot:
|
||||
managed: True
|
||||
bootloader: grub
|
||||
esp_dir: /boot/
|
||||
theme_folder: ~/.path/to/theme/
|
||||
os_prober: False
|
||||
# Also copies over the /etc/default/grub config or equivalent for other supported bootloaders
|
||||
|
||||
# TODO: Desktops, login managers, full disk encryption etc configuration?
|
||||
|
||||
themes:
|
||||
gtk: theme_name
|
||||
qt: theme_name # or use_gtk to use the gtk theme instaed
|
||||
font: Comfortaa 11 # the font name to be used (also needs to be installed)
|
||||
icon_theme: candy-icons # The icon theme to use (also needs to be installed)
|
||||
cursor_theme: oreo_spark_blue_cursors # TODO: Consider if GTK settings file should just be copied
|
||||
|
||||
git:
|
||||
creds:
|
||||
manager: git-credential-manager # or none
|
||||
repos:
|
||||
- url: https://github.com/janishutz/janishutz
|
||||
clone_path: ~/projects/ # Project location will be clone_path/<repo name>
|
||||
- url: git@git.janishutz.com:janishutz/nvim
|
||||
clone_path: ~/projects/ # Project location will be clone_path/<repo name>
|
||||
|
||||
template_data:
|
||||
- name: template_data_name
|
||||
data: the_data
|
||||
|
||||
cmds:
|
||||
always:
|
||||
- cmd: "cmd to run every time archmgr is run"
|
||||
once:
|
||||
- name: "cmd1"
|
||||
cmd: "cmd to run on first execution of archmgr (or if not executed previously)"
|
||||
@@ -1,460 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"requires": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"pattern": "^([a-zA-Z0-9.-_]+\\/)+",
|
||||
"description": "Path to other configs, relative to this file (e.g. config/pkgs.yaml will expand to dirname(this_file)/config/pkgs.yaml)"
|
||||
},
|
||||
"description": "Imports for other config files that will be merged into this one. Precedence order is bottom up (i.e. lowest has highest precedence)"
|
||||
},
|
||||
"pkgs": {
|
||||
"type": "object",
|
||||
"description": "The packages to be installed",
|
||||
"properties": {
|
||||
"individual": {
|
||||
"type:": "array",
|
||||
"description": "the packages to be installed, by their package name",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z0-9-._]+(?=[a-z0-9]$)"
|
||||
}
|
||||
},
|
||||
"repos": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"reflector": {
|
||||
"type": "object",
|
||||
"description": "Use reflector to update the mirrors",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"interval": {
|
||||
"type": "number",
|
||||
"description": "The number of days to elapse between reflector reruns"
|
||||
},
|
||||
"countries": {
|
||||
"type": "array",
|
||||
"description": "The countries in which the should be located (only applies for the main arch repos)",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"pattern": "^[A-Z][a-z]*[a-z]$"
|
||||
},
|
||||
"maxItems": 5,
|
||||
"minItems": 1
|
||||
},
|
||||
"count": {
|
||||
"type": "number",
|
||||
"description": "The number of mirrors to add to the list",
|
||||
"maximum": 20,
|
||||
"minimum": 3
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"enabled"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"bundles": {
|
||||
"type": "array",
|
||||
"description": "Bundled packages, installing all the recommended extra software for them (such as hyprland and nvim)",
|
||||
"maxItems": 1,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The bundle name",
|
||||
"pattern": "^[a-z0-9-._]+(?=[a-z0-9]$)"
|
||||
},
|
||||
"ignored_pkgs": {
|
||||
"type": "array",
|
||||
"description": "List of packages from the bundle that should not be installed",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z0-9-._]+(?=[a-z0-9]$)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"name"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"boot": {
|
||||
"type": "object",
|
||||
"description": "Settings for the bootloader, such as theme, using os-prober, etc",
|
||||
"properties": {
|
||||
"managed": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not to manage the bootloader using archmgr"
|
||||
},
|
||||
"bootloader": {
|
||||
"type": "string",
|
||||
"description": "The bootloader to use (more coming eventually)",
|
||||
"pattern": "^(grub)"
|
||||
},
|
||||
"esp_dir": {
|
||||
"type": "string",
|
||||
"description": "The directory for the bootloader files. Has to end in slash",
|
||||
"pattern": "^(^(~|\\.|\\.\\.)?\\/)?([\\w\\/.-]+(?!.*[^\\w\\/.-]+))\\/$"
|
||||
},
|
||||
"theme_folder": {
|
||||
"type": "string",
|
||||
"description": "Where the folder for the theme is found. Can be relative to the config repo or file system and has to end in slash",
|
||||
"pattern": "^(^(~|\\.|\\.\\.)?\\/)?([\\w\\/.-]+(?!.*[^\\w\\/.-]+))\\/$"
|
||||
},
|
||||
"os_prober": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to enable OS prober to search for other operating systems"
|
||||
},
|
||||
"kernel_params": {
|
||||
"type": "string",
|
||||
"description": "The kernel arguments to pass in"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"managed"
|
||||
]
|
||||
},
|
||||
"themes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"gtk": {
|
||||
"type": "string",
|
||||
"description": "The GTK theme to use. To use the custom generated theme, set this to Adaptive-Theme and set the Qt theme to gtk3"
|
||||
},
|
||||
"qt": {
|
||||
"type": "string",
|
||||
"description": "The Qt theme to use. Write gtk3 to use the GTK theme instead",
|
||||
"default": "gtk3"
|
||||
},
|
||||
"font": {
|
||||
"type": "string",
|
||||
"description": "The font (and size) to use for the interface. Needs to be installed to work",
|
||||
"default": "Comfortaa 11"
|
||||
},
|
||||
"icon_theme": {
|
||||
"type": "string",
|
||||
"description": "Which icon theme to use. Needs to be installed to work"
|
||||
},
|
||||
"cursor_theme": {
|
||||
"type": "string",
|
||||
"description": "The cursor to use. Needs to be installed to work"
|
||||
},
|
||||
"selected_custom_theme_color": {
|
||||
"type": "string",
|
||||
"description": "The custom theme color to use. archmgr comes with 5 defaults than can be used without custom_theme_colors defined",
|
||||
"pattern": "^(nordic|deep-dark|material|light|bright|.*)"
|
||||
},
|
||||
"color_source": {
|
||||
"type": "string",
|
||||
"description": "Where to get the colours from",
|
||||
"pattern": "^(wallpaper|default)",
|
||||
"default": "wallpaper"
|
||||
},
|
||||
"wallpaper": {
|
||||
"type": "string",
|
||||
"description": "Path to the wallpaper to use. If supported by manager for wallpaper, can also be folder. Please note that in that case the theme colours will fall back to the default values and will not use the wallpaper"
|
||||
},
|
||||
"custom_theme_colors": {
|
||||
"type": "array",
|
||||
"description": "Templates for a custom theme.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the color theme. This can be used as the"
|
||||
},
|
||||
"base": {
|
||||
"type": "string",
|
||||
"description": "The name of the custom_theme to base it off of. Defaults to deep-dark, which is a default theme provided by archmgr",
|
||||
"default": "deep-dark"
|
||||
},
|
||||
"variables": {
|
||||
"type": "object",
|
||||
"description": "The definition of the colors and other variables that override any set in template_data if present there",
|
||||
"properties": {
|
||||
"color-fore-primary": {
|
||||
"type": "string"
|
||||
},
|
||||
"color-fore-accent-1": {
|
||||
"type": "string"
|
||||
},
|
||||
"color-fore-accent-2": {
|
||||
"type": "string"
|
||||
},
|
||||
"color-fore-accent-3": {
|
||||
"type": "string"
|
||||
},
|
||||
"color-back-primary": {
|
||||
"type": "string"
|
||||
},
|
||||
"color-back-accent-1": {
|
||||
"type": "string"
|
||||
},
|
||||
"color-back-accent-2": {
|
||||
"type": "string"
|
||||
},
|
||||
"color-shadow": {
|
||||
"type": "string"
|
||||
},
|
||||
"color-fore-inactive": {
|
||||
"type": "string"
|
||||
},
|
||||
"color-back-inactive": {
|
||||
"type": "string"
|
||||
},
|
||||
"font-primary": {
|
||||
"type": "string"
|
||||
},
|
||||
"font-accent": {
|
||||
"type": "string"
|
||||
},
|
||||
"font-mono": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"git": {
|
||||
"type": "object",
|
||||
"description": "Automatically set up credential manager and clone repos",
|
||||
"properties": {
|
||||
"repos": {
|
||||
"type": "array",
|
||||
"description": "Which repos to clone (removing one from here doesn't delete it from the system and only pulls if folder does not exist)",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"url": {
|
||||
"type": "string",
|
||||
"description": "git clone URL (ssh or HTTP(S))",
|
||||
"pattern": "^((https?):\\/\\/(([a-z0-9-]+)((?=\\.))\\.)+[a-z]+(?=\\/)\\/([\\w\\-?.=]+(?=\\/[\\w\\-?.=])\\/)*([\\w\\-?&.=\\/]+(?=[\\w\\-.=\\/]$)))|([a-zA-Z0-9\\-._]+(?=[a-zA-Z0-9])[a-zA-Z0-9])@(([a-z0-9\\-]+(?=\\.))\\.)+[a-z]+(?=:):([a-zA-Z0-9\\-._]+(?=\\/)\\/)+([a-zA-Z0-9\\-._]+(?=[a-zA-Z0-9]$))"
|
||||
},
|
||||
"clone_path": {
|
||||
"type": "string",
|
||||
"description": "The location on the local system where to put it. Must be absolute path. Parent folders will be created if they don't exist",
|
||||
"pattern": "^(^(~|\\.|\\.\\.)?\\/)?([\\w\\/.-]+(?!.*[^\\w\\/.-]+))\\/$"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"url",
|
||||
"clone_path"
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"template_data": {
|
||||
"type": "array",
|
||||
"description": "The data to be inserted into the templates",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"string",
|
||||
"array"
|
||||
],
|
||||
"description": "The type of data included. Can be omitted, which then defaults to string",
|
||||
"default": "string"
|
||||
}
|
||||
},
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
]
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"string",
|
||||
"array"
|
||||
],
|
||||
"description": "The type of data included. Can be omitted, which then defaults to string",
|
||||
"default": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name that appears in the template"
|
||||
},
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"template": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"items",
|
||||
"template",
|
||||
"name"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"else": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name that appears in the template"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"string",
|
||||
"array"
|
||||
],
|
||||
"description": "The type of data included. Can be omitted, which then defaults to string",
|
||||
"default": "string"
|
||||
},
|
||||
"data": {
|
||||
"type": "string",
|
||||
"description": "The data that is to be inserted"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"data"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
]
|
||||
}
|
||||
},
|
||||
"symlinks": {
|
||||
"type": "array",
|
||||
"description": "Symlinks to create",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"destination": {
|
||||
"type": "string",
|
||||
"description": "The directory the link should point to",
|
||||
"pattern": "^(^(~|\\.|\\.\\.)?\\/)?([\\w\\/.-]+(?!.*[^\\w\\/.-]+))"
|
||||
},
|
||||
"location": {
|
||||
"type": "string",
|
||||
"description": "What to call the link",
|
||||
"pattern": "^(^(~|\\.|\\.\\.)?\\/)?([\\w\\/.-]+(?!.*[^\\w\\/.-]+))"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cmds": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"once": {
|
||||
"type": "array",
|
||||
"description": "Commands to run on only once (uses the name property to determine if it needs to run)",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "This name is used to track if a command was run before."
|
||||
},
|
||||
"cmd": {
|
||||
"type": "string"
|
||||
},
|
||||
"hook": {
|
||||
"type": "string",
|
||||
"description": "Where in the execution of archmgr to run",
|
||||
"default": "end",
|
||||
"pattern": "^(pre-pkg|post-pkg|pre-git|post-git|end)"
|
||||
},
|
||||
"user": {
|
||||
"type": "string",
|
||||
"default": "root",
|
||||
"pattern": "^[a-zA-Z0-9\\-._]{2,19}(?=[a-zA-Z0-9]$)",
|
||||
"description": "The user to run as. Be aware that only the current user's password is available, unless capture_output is set to false"
|
||||
},
|
||||
"capture_output": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Whether or not to hide the output from the user"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"name",
|
||||
"cmd"
|
||||
]
|
||||
}
|
||||
},
|
||||
"always": {
|
||||
"type": "array",
|
||||
"description": "Commands to run on each apply",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Used to indicate to user what command is executing. If omitted, will be truncated cmd"
|
||||
},
|
||||
"cmd": {
|
||||
"type": "string"
|
||||
},
|
||||
"hook": {
|
||||
"type": "string",
|
||||
"description": "Where in the execution of archmgr to run",
|
||||
"default": "end",
|
||||
"pattern": "^(pre-pkg|post-pkg|pre-git|post-git|end)"
|
||||
},
|
||||
"user": {
|
||||
"type": "string",
|
||||
"default": "root",
|
||||
"pattern": "^[a-zA-Z0-9\\-._]{2,19}(?=[a-zA-Z0-9]$)",
|
||||
"description": "The user to run as. Be aware that only the current user's password is available, unless capture_output is set to false"
|
||||
},
|
||||
"capture_output": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Whether or not to hide the output from the user"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"pkgs",
|
||||
"boot"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
# yaml-language-server: $schema=config.schema.json
|
||||
# TODO: Change the above to an import URL instead
|
||||
requires:
|
||||
- ./config-include-test.yml # Reads the other configs after finishing this one
|
||||
# - path/to/other/configs/relative/to/this # Reads the other configs after finishing this one
|
||||
|
||||
pkgs:
|
||||
individual:
|
||||
- acpi
|
||||
- tk
|
||||
- tldr
|
||||
bundles:
|
||||
- name: hyprland
|
||||
repos:
|
||||
reflector:
|
||||
enabled: false
|
||||
countries:
|
||||
- Switzerland
|
||||
|
||||
boot:
|
||||
managed: True
|
||||
bootloader: grub
|
||||
esp_dir: /boot/
|
||||
theme_folder: ~/.path/to/theme/ # This could then be used with git to clone it in
|
||||
os_prober: False
|
||||
# Also copies over the /etc/default/grub config or equivalent for other supported bootloaders
|
||||
|
||||
# TODO: Desktops, login managers, full disk encryption etc configuration?
|
||||
|
||||
themes:
|
||||
gtk: theme_name
|
||||
qt: theme_name # or use_gtk to use the gtk theme instaed
|
||||
font: Comfortaa 11 # the font name to be used (also needs to be installed)
|
||||
icon_theme: candy-icons # The icon theme to use (also needs to be installed)
|
||||
cursor_theme: oreo_spark_blue_cursors # TODO: Consider if GTK settings file should just be copied
|
||||
# TODO: Cursor theme needs more flexibility
|
||||
|
||||
git:
|
||||
repos:
|
||||
- url: https://github.com/janishutz/janishutz
|
||||
clone_path: ~/projects/ # Project location will be clone_path/<repo name>
|
||||
- url: git@git.janishutz.com:janishutz/nvim
|
||||
clone_path: ~/projects/ # Project location will be clone_path/<repo name>
|
||||
|
||||
template_data:
|
||||
- name: template_data_name
|
||||
type: array
|
||||
template: |
|
||||
test {{ data }}
|
||||
items:
|
||||
- test
|
||||
|
||||
cmds:
|
||||
always:
|
||||
- cmd: "cmd to run every time archmgr is run"
|
||||
once:
|
||||
- name: "cmd1"
|
||||
cmd: "cmd to run on first execution of archmgr (or if not executed previously)"
|
||||
@@ -1,108 +0,0 @@
|
||||
from typing import Any, cast
|
||||
import yaml
|
||||
|
||||
from config import validator
|
||||
from config.dtype import ArchMgrConfig
|
||||
from config.merger import merge_configs
|
||||
|
||||
|
||||
def load_config_file(file: str):
|
||||
"""Load and parse the config file.
|
||||
No verification is done and is not cast
|
||||
|
||||
Args:
|
||||
file: The path to the file to be loaded
|
||||
|
||||
Returns:
|
||||
The loaded and parsed file.
|
||||
"""
|
||||
with open(file, "r") as f:
|
||||
parsed = yaml.load(f, Loader=yaml.FullLoader)
|
||||
return parsed
|
||||
|
||||
|
||||
def default_config() -> ArchMgrConfig:
|
||||
"""Get the default configuration
|
||||
|
||||
Returns:
|
||||
The default config
|
||||
"""
|
||||
return {
|
||||
"pkgs": {
|
||||
"individual": [],
|
||||
"bundles": [],
|
||||
"repos": {
|
||||
"enabled_repos": [
|
||||
{
|
||||
"name": "core",
|
||||
"setup_cmds": [],
|
||||
"mirrors": {"use_default": True, "extra_mirrors": []},
|
||||
}
|
||||
],
|
||||
"reflector": {
|
||||
"enabled": False,
|
||||
"count": 0,
|
||||
"countries": [],
|
||||
"interval": 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
"boot": {
|
||||
"managed": False,
|
||||
"bootloader": "grub",
|
||||
"esp_dir": "/boot",
|
||||
"os_prober": False,
|
||||
"theme_folder": "/usr/share/themes/grub",
|
||||
},
|
||||
"cmds": {"always": [], "once": []},
|
||||
"git": {
|
||||
"repos": [],
|
||||
"creds": {"manager": "git-credential-manager"},
|
||||
},
|
||||
"users": [],
|
||||
"symlinks": [],
|
||||
"template_data": [],
|
||||
"themes": {
|
||||
"cursor_theme": "oreo_spark_blue_cursor",
|
||||
"font": "Comfortaa 11",
|
||||
"gtk": "Adaptive-Theme",
|
||||
"qt": "gtk3",
|
||||
"icon_theme": "candy-icons",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
requires_list: list[str] = []
|
||||
source_list = {} # for each setting, in which files it appears
|
||||
|
||||
|
||||
def load_config(file: str) -> ArchMgrConfig:
|
||||
"""Load the configuration from the specified file path
|
||||
|
||||
Args:
|
||||
file: Path to the file to be loaded
|
||||
|
||||
Returns:
|
||||
The loaded, validated and parsed config
|
||||
"""
|
||||
global requires_list, source_list
|
||||
# Load and validate initial config
|
||||
try:
|
||||
loaded_conf = load_config_file(file)
|
||||
except Exception:
|
||||
return default_config()
|
||||
if not validator.validate(loaded_conf):
|
||||
return default_config()
|
||||
|
||||
configuration = cast(dict[str, Any], loaded_conf)
|
||||
requires = cast(list[str], configuration.pop("requires"))
|
||||
|
||||
# Keep track of all files
|
||||
requires_list += requires
|
||||
conf = cast(ArchMgrConfig, configuration)
|
||||
|
||||
# Recursively load files
|
||||
for conf_file in requires:
|
||||
conf = merge_configs(conf, load_config(conf_file))
|
||||
|
||||
return conf
|
||||
@@ -1,23 +0,0 @@
|
||||
from typing import Optional, TypedDict
|
||||
|
||||
from config.dtype.cmds import ArchMgrCmdsConfig
|
||||
from config.dtype.git import ArchMgrGitConfig
|
||||
from config.dtype.others import (
|
||||
ArchMgrBootConfig,
|
||||
ArchMgrSymlinkConfig,
|
||||
ArchMgrTemplateData,
|
||||
ArchMgrThemeConfig,
|
||||
ArchMgrUserConfig,
|
||||
)
|
||||
from config.dtype.pkgs import ArchMgrPkgConfig
|
||||
|
||||
|
||||
class ArchMgrConfig(TypedDict):
|
||||
pkgs: ArchMgrPkgConfig
|
||||
users: Optional[list[ArchMgrUserConfig]]
|
||||
boot: ArchMgrBootConfig
|
||||
themes: Optional[ArchMgrThemeConfig]
|
||||
git: Optional[ArchMgrGitConfig]
|
||||
template_data: Optional[list[ArchMgrTemplateData]]
|
||||
symlinks: Optional[list[ArchMgrSymlinkConfig]]
|
||||
cmds: Optional[ArchMgrCmdsConfig]
|
||||
@@ -1,14 +0,0 @@
|
||||
from typing import Optional, TypedDict
|
||||
|
||||
|
||||
class ArchMgrCmdsConfig(TypedDict):
|
||||
always: Optional[list[ArchMgrCommand]]
|
||||
once: Optional[list[ArchMgrCommand]]
|
||||
|
||||
|
||||
class ArchMgrCommand(TypedDict):
|
||||
cmd: str
|
||||
name: Optional[str]
|
||||
capture_output: Optional[bool]
|
||||
hook: Optional[str]
|
||||
user: Optional[str]
|
||||
@@ -1,15 +0,0 @@
|
||||
from typing import Optional, TypedDict
|
||||
|
||||
|
||||
class ArchMgrGitConfig(TypedDict):
|
||||
creds: Optional[ArchMgrGitCredsConfig]
|
||||
repos: Optional[list[ArchMgrGitRepoConfig]]
|
||||
|
||||
|
||||
class ArchMgrGitCredsConfig(TypedDict):
|
||||
manager: Optional[str]
|
||||
|
||||
|
||||
class ArchMgrGitRepoConfig(TypedDict):
|
||||
clone_path: str
|
||||
url: str
|
||||
@@ -1,34 +0,0 @@
|
||||
from typing import Optional, TypedDict
|
||||
|
||||
|
||||
class ArchMgrUserConfig(TypedDict):
|
||||
username: str
|
||||
groups: Optional[list[str]]
|
||||
home_dir: Optional[bool]
|
||||
sudo_user: Optional[bool]
|
||||
|
||||
|
||||
class ArchMgrBootConfig(TypedDict):
|
||||
managed: bool
|
||||
bootloader: Optional[str]
|
||||
esp_dir: Optional[str]
|
||||
theme_folder: Optional[str]
|
||||
os_prober: Optional[bool]
|
||||
|
||||
|
||||
class ArchMgrThemeConfig(TypedDict):
|
||||
gtk: Optional[str]
|
||||
qt: Optional[str]
|
||||
font: Optional[str]
|
||||
icon_theme: Optional[str]
|
||||
cursor_theme: Optional[str]
|
||||
|
||||
|
||||
class ArchMgrTemplateData(TypedDict):
|
||||
name: str
|
||||
data: str
|
||||
|
||||
|
||||
class ArchMgrSymlinkConfig(TypedDict):
|
||||
destination: str
|
||||
location: str
|
||||
@@ -1,35 +0,0 @@
|
||||
from typing import Optional, TypedDict
|
||||
|
||||
|
||||
class ArchMgrPkgConfig(TypedDict):
|
||||
individual: Optional[list[str]]
|
||||
repos: Optional[ArchMgrReposConfig]
|
||||
bundles: Optional[list[ArchMgrBundleConfig]]
|
||||
|
||||
|
||||
class ArchMgrReposConfig(TypedDict):
|
||||
enabled_repos: Optional[list[ArchMgrRepoSettings]]
|
||||
reflector: Optional[ArchMgrReflectorConfig]
|
||||
|
||||
|
||||
class ArchMgrReflectorConfig(TypedDict):
|
||||
enabled: bool
|
||||
interval: Optional[int]
|
||||
countries: Optional[list[str]]
|
||||
count: Optional[int]
|
||||
|
||||
|
||||
class ArchMgrBundleConfig(TypedDict):
|
||||
name: str
|
||||
ignored_pkgs: Optional[list[str]]
|
||||
|
||||
|
||||
class ArchMgrRepoSettings(TypedDict):
|
||||
name: str
|
||||
setup_cmds: Optional[list[str]]
|
||||
mirrors: ArchMgrRepoMirrors
|
||||
|
||||
|
||||
class ArchMgrRepoMirrors(TypedDict):
|
||||
use_default: bool
|
||||
extra_mirrors: Optional[list[str]]
|
||||
@@ -1,45 +0,0 @@
|
||||
from typing import cast
|
||||
from config.dtype import ArchMgrConfig
|
||||
|
||||
|
||||
def merge_configs(config: ArchMgrConfig, new_config: ArchMgrConfig) -> ArchMgrConfig:
|
||||
"""Merge two configs, with the new_config taking precedence over the config
|
||||
in the conflicting fields with arrays and dicts merged
|
||||
|
||||
Args:
|
||||
config: Base config
|
||||
new_config: Config to merge into the base config
|
||||
|
||||
Returns:
|
||||
The merged config
|
||||
"""
|
||||
if len(new_config) == 0 or len(config) == 0:
|
||||
return config
|
||||
|
||||
def combine(a: dict, b: dict):
|
||||
combined = {}
|
||||
for key in b:
|
||||
val = b[key]
|
||||
try:
|
||||
a[key]
|
||||
if isinstance(val, dict):
|
||||
combined[key] = combine(val, a[key])
|
||||
elif isinstance(val, list):
|
||||
combined[key] = val
|
||||
for v in a[key]:
|
||||
combined[key].append(v)
|
||||
else:
|
||||
combined[key] = val
|
||||
except KeyError:
|
||||
combined[key] = val
|
||||
|
||||
for key in a:
|
||||
try:
|
||||
b[key]
|
||||
except KeyError:
|
||||
combined[key] = a[key]
|
||||
|
||||
return combined
|
||||
|
||||
# Merge configs (using nasty casts)
|
||||
return cast(ArchMgrConfig, combine(cast(dict, config), cast(dict, new_config)))
|
||||
@@ -1,26 +0,0 @@
|
||||
import json
|
||||
import jsonschema
|
||||
|
||||
with open("config.schema.json") as file:
|
||||
schema = json.load(file)
|
||||
|
||||
|
||||
def validate(config: dict | list):
|
||||
"""Validate the specified config
|
||||
|
||||
Args:
|
||||
config: The raw configuration
|
||||
|
||||
Returns:
|
||||
True if the config is valid, False otherwise
|
||||
"""
|
||||
try:
|
||||
jsonschema.validate(config, schema)
|
||||
except jsonschema.SchemaError:
|
||||
print("Schema invalid")
|
||||
return False
|
||||
except jsonschema.ValidationError:
|
||||
print("Config invalid")
|
||||
return False
|
||||
|
||||
return True
|
||||
@@ -1 +0,0 @@
|
||||
# Import the configuration preset templates
|
||||
@@ -1 +0,0 @@
|
||||
# TODO: Material colors creator
|
||||
@@ -1,2 +0,0 @@
|
||||
def update_users_and_groups():
|
||||
pass
|
||||
@@ -1,84 +1,7 @@
|
||||
# Concept for tracking changes
|
||||
- Create new commit (if possible)
|
||||
- If force is unset: Diff system's files against current state by copying them into the current state branch
|
||||
- Then, diff the package state against the state branch by dumping it
|
||||
- Else, or if no diff, continue
|
||||
- Render and copy config files into correct directories
|
||||
- Create config files from presets
|
||||
- Retrieve explicitly installed packages and remove those that are not present in goal and install those that are not present in current state
|
||||
# Concepts
|
||||
## Config
|
||||
In python, using functions and args for them
|
||||
|
||||
|
||||
# Concept for updating config automatically
|
||||
1. Track which files contain the setting
|
||||
2. If there are multiple and is no array, then simply overwrite the last
|
||||
3. If there are multiple and is array, then remove from the file where it is present and add to last file in requires list
|
||||
-> To enable this, update the loader to keep more metadata (list of all requires, list of files for each setting)
|
||||
|
||||
|
||||
# Ideas
|
||||
- [X] function to collect new configs -> Not smart because templates
|
||||
- [ ] Templates foreach (array of arrays or something like it)
|
||||
- [ ] Improved base GTK theme
|
||||
- [ ] config options for users and groups -> Omitted for now
|
||||
- [ ] Wallpaper settings for other WM than hyprland
|
||||
- [ ] presets for things like desktops (like Hyprland)
|
||||
- [ ] config options for the template rendering
|
||||
- [ ] config options for themes
|
||||
- [ ] grub config
|
||||
- [X] Dynamic selection of more configs (i.e. require syntax)
|
||||
- [ ] Conditional require
|
||||
- [X] Own config syntax? -> just use yaml
|
||||
- [ ] Autocompletion
|
||||
- [ ] Basic arch install how? -> Probably manual (or semi-automatic)
|
||||
- [ ] Mounts?
|
||||
|
||||
|
||||
# REGEX
|
||||
TODO: Improve the below (especially file can be shortened with positive lookahead)
|
||||
- File: `^(^(~|\\.|\\.\\.)?\\/)?([\\w\\/.-]+(?!.*[^\\w\\/.-]+))`
|
||||
- Folder: `^(^(~|\\.|\\.\\.)?\\/)?([\\w\\/.-]+(?!.*[^\\w\\/.-]+))\\/$`
|
||||
- URL (just domain): `^(https?):\\/\\/(([a-z0-9-]+)((?=\\.))\\.)+[a-z]+(?=[a-z]$)`
|
||||
- Full URL: `^(https?):\\/\\/(([a-z0-9-]+)((?=\\.))\\.)+[a-z]+(?=\\/)\\/([\\w\\-?.=]+(?=\\/[\\w\\-?.=])\\/)*([\\w\\-?&.=\\/]+(?=[\\w\\-.=\\/]$))`
|
||||
- UNIX username and groups: `^[a-zA-Z0-9\\-._]{2,19}(?=[a-zA-Z0-9]$)`
|
||||
- Git SSH: `([a-zA-Z0-9\\-._]+(?=[a-zA-Z0-9])[a-zA-Z0-9])@(([a-z0-9\\-]+(?=\\.))\\.)+[a-z]+(?=:):([a-zA-Z0-9\\-._]+(?=\\/)\\/)+([a-zA-Z0-9\\-._]+(?=[a-zA-Z0-9]$))`
|
||||
|
||||
|
||||
## User config schema
|
||||
This may or may not ever be a thing
|
||||
```json
|
||||
"users": {
|
||||
"type": "array",
|
||||
"description": "Users to add, including groups. Users will be diffed and removed if they are removed from here. No files are deleted",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"username": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-zA-Z0-9\\-._]{2,19}(?=[a-zA-Z0-9]$)"
|
||||
},
|
||||
"groups": {
|
||||
"type": "array",
|
||||
"description": "The groups to add the user to. Groups are created if they don't exist. User's own group doesn't have to be listed explicitly",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-zA-Z0-9\\-._]{2,19}(?=[a-zA-Z0-9]$)"
|
||||
}
|
||||
},
|
||||
"sudo_user": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Whether a user can use sudo or not. Same as appending them to the `wheel` group"
|
||||
},
|
||||
"home_dir": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to create a home directory for the user or not",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"username"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
```
|
||||
## Init
|
||||
- Copy
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
from config.dtype.others import ArchMgrTemplateData
|
||||
|
||||
|
||||
class ArchMgrTemplates:
|
||||
pkg_bundles: dict[str, list[str]]
|
||||
bootloader_config: list[BootLoaderSettings]
|
||||
theme_templates: dict[str, list[ArchMgrTemplateData]]
|
||||
|
||||
|
||||
class BootLoaderSettings:
|
||||
# Used to render the included template files
|
||||
templates: list[ArchMgrTemplateData]
|
||||
theme_folder: str
|
||||
conf_build_cmd: str
|
||||
name: str
|
||||
|
||||
|
||||
def load_templates():
|
||||
pass
|
||||
@@ -1,7 +0,0 @@
|
||||
grub:
|
||||
name: grub
|
||||
templates:
|
||||
- name: "GFX_MODE"
|
||||
data: "COMP:SCREENWIDTHxCOMP:SCREENHEIGHT"
|
||||
theme_folder: "ESP_DIR/grub/themes/"
|
||||
conf_build_cmd: "grub-mkconfig -o ESP_DIR/grub/grub.cfg"
|
||||
@@ -1,55 +0,0 @@
|
||||
hyprland:
|
||||
- hyprland
|
||||
- hyprlock
|
||||
- hypridle
|
||||
- hyprshutdown
|
||||
- grimblast
|
||||
- grim
|
||||
- xdg-desktop-portal-hyprland
|
||||
- hyprpolkitagent
|
||||
|
||||
hyrpland-extra:
|
||||
- hyprpaper
|
||||
- wl-clipboard
|
||||
- cliphist
|
||||
- hyprlauncher
|
||||
- hyprpwcenter
|
||||
- hyprtoolkit
|
||||
|
||||
pipewire:
|
||||
- pipewire
|
||||
- pipewire-alsa
|
||||
- pipewire-pulse
|
||||
- pipewire-jack
|
||||
- wireplumber
|
||||
|
||||
neovim:
|
||||
- neovim
|
||||
- lua
|
||||
- lua-language-server
|
||||
- tree-sitter
|
||||
- tree-sitter-cli
|
||||
- stylua
|
||||
|
||||
archives:
|
||||
- zip
|
||||
- unzip
|
||||
|
||||
bluetooth:
|
||||
- blueman
|
||||
- bluez
|
||||
- bluez-utils
|
||||
|
||||
utils:
|
||||
- fastfetch
|
||||
- tldr
|
||||
- gdu
|
||||
- dig
|
||||
- glances
|
||||
- bashtop
|
||||
|
||||
# TODO: For all, make it possible to be pkgs plus configs?
|
||||
yazi:
|
||||
- yazi
|
||||
- xdg-desktop-portal-termfilechooser-hunkyburrito-git
|
||||
- ouch
|
||||
@@ -1,2 +0,0 @@
|
||||
nordic:
|
||||
deep-dark:
|
||||
@@ -1,9 +0,0 @@
|
||||
# archmgr data folder
|
||||
archmgr is a nixos-inspired package and config manager for Arch Linux.
|
||||
|
||||
To function, it needs both a configuration file (or multiple) and templates for its provided, preset configurations.
|
||||
The latter reside in this folder and are a stripped-down version of the default config file.
|
||||
|
||||
## Template config folder
|
||||
This folder contains the template configs for archmgr to copy into the user's newly created config repo
|
||||
using the init function
|
||||
@@ -1,6 +0,0 @@
|
||||
# Templates folder
|
||||
By convention, put here any yaml files that contain template information.
|
||||
|
||||
Ideally, each file in this folder contains config options for one of your programs.
|
||||
|
||||
Included you can find an example file for hyprland
|
||||
Reference in New Issue
Block a user