Compare commits

2 Commits

Author SHA1 Message Date
janishutz 36d3c6f992 feat(rewrite): Config preparations 2026-05-16 15:51:10 +02:00
janishutz 217ccadc74 feat(rewrite): Start rewrite 2026-05-16 15:00:09 +02:00
68 changed files with 93 additions and 1218 deletions
+9
View File
@@ -0,0 +1,9 @@
#!/usr/bin/env python3
# PYTHON_ARGCOMPLETE_OK
# TODO: Re-export the config stuff
if __name__ == "__main__":
from app import run as _run
_run()
Executable → Regular
+5 -8
View File
@@ -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)
+3 -2
View File
@@ -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
View File
@@ -0,0 +1 @@
# Declare a file explicitly if it has different permissions
+2
View File
@@ -0,0 +1,2 @@
def clone_git_repo(repo_url: str, clone_path: str, branch: str = "DEFAULT"):
pass
+6
View File
@@ -0,0 +1,6 @@
def add_packages(pkgs: list[str]):
pass
def add_package_bundles(pkgs: list[str]):
pass
+2
View File
@@ -0,0 +1,2 @@
def enable_reflector():
pass
+2
View File
@@ -0,0 +1,2 @@
def enable_repo(name: str):
pass
+36
View File
@@ -0,0 +1,36 @@
def add_template_data(name: str, data: str):
"""Replace all occurrences of variable specified by `name` by
the specified data
Args:
name: The variable to replace
data: The data to replace it with
"""
pass
def add_template_array(name: str, template: str, data: list[str]):
"""Replace all occurrences of variable specified by `name` in the files with
the specified template, where {{ data }} is replaced with the each element
of the data argument
Args:
name: The name of the variable to replace
template:
Template syntax, with {{ data }} as variable, see example below or docs.
Recursive replacement is supported up to one layer deep
data: A list of data that is substituted into the {{ data }} variable in the template
Example:
```python
add_template_array("test", "- Hello World {{ data }}\\n", [0, 1])
```
for example replaces {{ test }} in a file with
```
- Hello World 0
- Hello World 1
```
"""
# TODO: Recursive replacement
pass
+14
View File
@@ -0,0 +1,14 @@
from typing import Literal
def add_custom_theme():
pass
def custom_theme_color_source(src: Literal["wallpaper"] | Literal["default"]):
pass
# TODO: For desktop environments, what command??
def set_wallpaper(path: str):
pass
+2
View File
@@ -0,0 +1,2 @@
def set_gtk_theme():
pass
View File
View File
View File
View File
View File
@@ -1,6 +1,6 @@
from typing import Optional
from commands.util.password_manager import PasswordManager
from password_mgr import PasswordManager
def choice(default: str, options: str, msg: str) -> str:
@@ -1,7 +1,7 @@
import subprocess as sp
from typing import List
from commands.util.input_mgr import password
from util.input import password
pkg_manager = ["yay", "--noconfirm"]
+2
View File
@@ -0,0 +1,2 @@
import list
import diff
@@ -1,7 +1,7 @@
from typing import List
import colorama as cl
from commands.util.printing.list import print_list
from list import print_list
def print_diff(add: List[str], remove: List[str]):
View File
View File
-21
View File
@@ -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",
)
-23
View File
@@ -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)
-13
View File
@@ -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
-30
View File
@@ -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!)
-30
View File
@@ -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
-5
View File
@@ -1,5 +0,0 @@
from config.dtype import ArchMgrConfig
def pull(config: ArchMgrConfig, rebase: bool = False, apply: bool = False):
print("pull")
-2
View File
@@ -1,2 +0,0 @@
def push(force: bool = False):
print("push")
-21
View File
@@ -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)
-58
View File
@@ -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)"
-460
View File
@@ -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
View File
@@ -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)"
-108
View File
@@ -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
-23
View File
@@ -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]
-14
View File
@@ -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]
-15
View File
@@ -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
-34
View File
@@ -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
-35
View File
@@ -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]]
-45
View File
@@ -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)))
-26
View File
@@ -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
View File
@@ -1 +0,0 @@
# Import the configuration preset templates
-1
View File
@@ -1 +0,0 @@
# TODO: Material colors creator
-2
View File
@@ -1,2 +0,0 @@
def update_users_and_groups():
pass
+5 -82
View File
@@ -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
-19
View File
@@ -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
-7
View File
@@ -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"
-55
View File
@@ -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
-2
View File
@@ -1,2 +0,0 @@
nordic:
deep-dark:
-9
View File
@@ -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
-6
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
import archmgr