Compare commits

..

6 Commits

Author SHA1 Message Date
janishutz d391c53c6c chore: notes 2026-05-16 14:28:24 +02:00
janishutz 24c52c1bba feat(schema): Add array templates 2026-05-16 12:01:40 +02:00
janishutz 80d7b3d86e feat(config): Clean up, add themes options 2026-05-16 11:41:00 +02:00
janishutz 72b477381f chore: more ideas 2026-05-15 17:00:41 +02:00
janishutz e8d8429bc9 feat(config): Prepare for more metadata 2026-05-15 16:58:19 +02:00
janishutz afe8d29340 feat(cli): More argument handling 2026-05-15 16:58:00 +02:00
26 changed files with 369 additions and 197 deletions
+6
View File
@@ -3,3 +3,9 @@
A nixos-like declarative config and package manager for Arch Linux (or any other distro, with some tweaks). A nixos-like declarative config and package manager for Arch Linux (or any other distro, with some tweaks).
See the [Wiki](https://git.janishutz.com/janishutz/archmgr/wiki) See the [Wiki](https://git.janishutz.com/janishutz/archmgr/wiki)
## WIP
This project is very much Work In Progress.
The configs will likely move to python-based configs from the current yaml-based ones to provide you with more flexibility.
`archmgr` will automatically create a basic setup if you run `archmgr init`, so you can get up and running quickly
+1 -1
View File
@@ -12,7 +12,7 @@ import commands.push as push
import commands.prepare as setup import commands.prepare as setup
import commands.show as show import commands.show as show
from config import load_config from config import load_config
from templates import ArchMgrTemplates from presets import ArchMgrTemplates
if __name__ == "__main__": if __name__ == "__main__":
args, ap = cliargs.add_cli_args() args, ap = cliargs.add_cli_args()
+1
View File
@@ -8,5 +8,6 @@ def add_parser(sp: ap._SubParsersAction[ap.ArgumentParser]):
dest="show", dest="show",
required=True, required=True,
) )
show_sp.add_parser("config", help="show details about your configuration. Alias of config show")
pkgs = show_sp.add_parser("pkgs", help="show details on package presets") pkgs = show_sp.add_parser("pkgs", help="show details on package presets")
pkgs.add_argument("show_pkg") pkgs.add_argument("show_pkg")
+3
View File
@@ -8,3 +8,6 @@ def config(args: argparse.Namespace, config: ArchMgrConfig):
Your config can be found at Your config can be found at
""") """)
print(config) print(config)
elif args.conf_update == "pkgs":
print("Updating config's package list")
# TODO: Config saver
+9 -7
View File
@@ -8,21 +8,23 @@ def init(force: bool = False):
dir = os.getcwd() dir = os.getcwd()
script_dir = os.path.dirname(os.path.realpath(__file__)) script_dir = os.path.dirname(os.path.realpath(__file__))
if force: if force:
if confirm(False, "Do you really want to IRREVERSIBLY DELETE the contents of this folder and redo setup?"): 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)] [os.remove(file) for file in os.listdir(dir)]
git.init(dir) git.init(dir)
os.mkdir(dir + "/config") os.mkdir(dir + "/config")
os.mkdir(dir + "/db")
os.mkdir(dir + "/system") os.mkdir(dir + "/system")
os.mkdir(dir + "/includes") os.mkdir(dir + "/includes")
shutil.copy(script_dir + "/templates/config/config.yml", dir + "/config.yml") shutil.copy(script_dir + "/templates/config.yml", dir + "/config.yml")
shutil.copy(script_dir + "/templates/config/system/", dir + "/includes/system/") shutil.copy(script_dir + "/templates/system/", dir + "/includes/system/")
shutil.copy(script_dir + "/templates/config/templates/", dir + "/includes/templates/") shutil.copy(script_dir + "/templates/templates/", dir + "/includes/templates/")
shutil.copy(script_dir + "/templates/README.md", dir + "/README.md") shutil.copy(script_dir + "/templates/README.md", dir + "/README.md")
print("Initialized a new archmgr repository") 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: For the files, store the permissions in a db
# TODO: Warn user to not delete .config/archmgr repo # TODO: Warn user to not delete .config/archmgr repo
# TODO: Set up that repo (where to put it? /usr/share?) # 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: Consider collecting function -> If no files present, will only collect the pkgs, else also the files
# TODO: Config folder instead of single config file
# TODO: Also store the folder name of the config folder in that repo (needs to be easily changeable for user!) # TODO: Also store the folder name of the config folder in that repo (needs to be easily changeable for user!)
+174 -94
View File
@@ -26,50 +26,6 @@
"repos": { "repos": {
"type": "object", "type": "object",
"properties": { "properties": {
"enabled_repos": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"description": "The repos to set up",
"properties": {
"name": {
"type": "string",
"description": "The repositories to set up",
"pattern": "^(core|extra|core-testing|extra-testing|multilib|multilib-testing|[a-z0-9-]+(?=[a-z0-9]$))"
},
"setup_cmds": {
"type": "array",
"description": "The commands to run to set it up (optional if any of the explicitly supported ones)",
"items": {
"type": "string",
"description": "Command to run"
}
},
"mirrors": {
"type": "object",
"description": "Configure the mirrors to use",
"properties": {
"use_default": {
"type": "boolean",
"description": "Whether to use the default mirrors or not",
"default": true
},
"extra_mirrors": {
"type": "array",
"description": "Any extra mirrors you want to add. At least one mirror needs to be put here if use_default is false. Order matters",
"items": {
"type": "string"
}
}
}
}
},
"required": [
"name"
]
}
},
"reflector": { "reflector": {
"type": "object", "type": "object",
"description": "Use reflector to update the mirrors", "description": "Use reflector to update the mirrors",
@@ -137,41 +93,6 @@
}, },
"additionalProperties": false "additionalProperties": false
}, },
"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
}
},
"boot": { "boot": {
"type": "object", "type": "object",
"description": "Settings for the bootloader, such as theme, using os-prober, etc", "description": "Settings for the bootloader, such as theme, using os-prober, etc",
@@ -198,6 +119,10 @@
"os_prober": { "os_prober": {
"type": "boolean", "type": "boolean",
"description": "Whether to enable OS prober to search for other operating systems" "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, "additionalProperties": false,
@@ -207,25 +132,114 @@
}, },
"themes": { "themes": {
"type": "object", "type": "object",
"properties": {} "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": { "git": {
"type": "object", "type": "object",
"description": "Automatically set up credential manager and clone repos", "description": "Automatically set up credential manager and clone repos",
"properties": { "properties": {
"creds": {
"type": "object",
"description": "Which git services to log into",
"properties": {
"manager": {
"type": "string",
"description": "The git credential manager to use. Set to none if you don't want one (default)",
"pattern": "^(git-credential-manager|none)",
"default": "none"
}
},
"additionalProperties": false
},
"repos": { "repos": {
"type": "array", "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)", "description": "Which repos to clone (removing one from here doesn't delete it from the system and only pulls if folder does not exist)",
@@ -259,20 +273,86 @@
"items": { "items": {
"type": "object", "type": "object",
"properties": { "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": { "name": {
"type": "string", "type": "string",
"description": "The name that appears in the template" "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": { "data": {
"type": "string", "type": "string",
"description": "The data that is to be inserted" "description": "The data that is to be inserted"
} }
}, },
"required": [ "required": [
"name",
"data" "data"
], ],
"additionalProperties": false "additionalProperties": false
},
"required": [
"name"
]
} }
}, },
"symlinks": { "symlinks": {
+5 -9
View File
@@ -17,12 +17,6 @@ pkgs:
countries: countries:
- Switzerland - Switzerland
users:
- username: username
groups:
- group_1
home_dir: True
boot: boot:
managed: True managed: True
bootloader: grub bootloader: grub
@@ -42,8 +36,6 @@ themes:
# TODO: Cursor theme needs more flexibility # TODO: Cursor theme needs more flexibility
git: git:
creds:
manager: git-credential-manager # or none
repos: repos:
- url: https://github.com/janishutz/janishutz - url: https://github.com/janishutz/janishutz
clone_path: ~/projects/ # Project location will be clone_path/<repo name> clone_path: ~/projects/ # Project location will be clone_path/<repo name>
@@ -52,7 +44,11 @@ git:
template_data: template_data:
- name: template_data_name - name: template_data_name
data: the_data type: array
template: |
test {{ data }}
items:
- test
cmds: cmds:
always: always:
+21 -4
View File
@@ -6,7 +6,16 @@ from config.dtype import ArchMgrConfig
from config.merger import merge_configs from config.merger import merge_configs
def _load_config_file(file: str): 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: with open(file, "r") as f:
parsed = yaml.load(f, Loader=yaml.FullLoader) parsed = yaml.load(f, Loader=yaml.FullLoader)
return parsed return parsed
@@ -58,11 +67,15 @@ def default_config() -> ArchMgrConfig:
"font": "Comfortaa 11", "font": "Comfortaa 11",
"gtk": "Adaptive-Theme", "gtk": "Adaptive-Theme",
"qt": "gtk3", "qt": "gtk3",
"icon_theme": "candy-icons" "icon_theme": "candy-icons",
}, },
} }
requires_list: list[str] = []
source_list = {} # for each setting, in which files it appears
def load_config(file: str) -> ArchMgrConfig: def load_config(file: str) -> ArchMgrConfig:
"""Load the configuration from the specified file path """Load the configuration from the specified file path
@@ -72,9 +85,10 @@ def load_config(file: str) -> ArchMgrConfig:
Returns: Returns:
The loaded, validated and parsed config The loaded, validated and parsed config
""" """
global requires_list, source_list
# Load and validate initial config # Load and validate initial config
try: try:
loaded_conf = _load_config_file(file) loaded_conf = load_config_file(file)
except Exception: except Exception:
return default_config() return default_config()
if not validator.validate(loaded_conf): if not validator.validate(loaded_conf):
@@ -82,6 +96,9 @@ def load_config(file: str) -> ArchMgrConfig:
configuration = cast(dict[str, Any], loaded_conf) configuration = cast(dict[str, Any], loaded_conf)
requires = cast(list[str], configuration.pop("requires")) requires = cast(list[str], configuration.pop("requires"))
# Keep track of all files
requires_list += requires
conf = cast(ArchMgrConfig, configuration) conf = cast(ArchMgrConfig, configuration)
# Recursively load files # Recursively load files
+1
View File
@@ -0,0 +1 @@
# TODO: Material colors creator
+2
View File
@@ -0,0 +1,2 @@
def update_users_and_groups():
pass
+56 -4
View File
@@ -8,15 +8,26 @@
- Retrieve explicitly installed packages and remove those that are not present in goal and install those that are not present in current state - Retrieve explicitly installed packages and remove those that are not present in goal and install those that are not present in current state
# 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 # Ideas
- [ ] function to collect new configs - [X] function to collect new configs -> Not smart because templates
- [ ] config options for users and groups - [ ] 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) - [ ] presets for things like desktops (like Hyprland)
- [ ] config options for the template rendering - [ ] config options for the template rendering
- [ ] config options for themes - [ ] config options for themes
- [ ] grub config - [ ] grub config
- [ ] Dynamic selection of more configs (i.e. require syntax) - [X] Dynamic selection of more configs (i.e. require syntax)
- [ ] Own config syntax? - [ ] Conditional require
- [X] Own config syntax? -> just use yaml
- [ ] Autocompletion - [ ] Autocompletion
- [ ] Basic arch install how? -> Probably manual (or semi-automatic) - [ ] Basic arch install how? -> Probably manual (or semi-automatic)
- [ ] Mounts? - [ ] Mounts?
@@ -30,3 +41,44 @@ TODO: Improve the below (especially file can be shortened with positive lookahea
- Full URL: `^(https?):\\/\\/(([a-z0-9-]+)((?=\\.))\\.)+[a-z]+(?=\\/)\\/([\\w\\-?.=]+(?=\\/[\\w\\-?.=])\\/)*([\\w\\-?&.=\\/]+(?=[\\w\\-.=\\/]$))` - 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]$)` - 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]$))` - 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
}
},
```
@@ -3,13 +3,13 @@ from config.dtype.others import ArchMgrTemplateData
class ArchMgrTemplates: class ArchMgrTemplates:
pkg_bundles: dict[str, list[str]] pkg_bundles: dict[str, list[str]]
bootloader_config: BootLoaderSettings bootloader_config: list[BootLoaderSettings]
theme_templates: ArchMgrTemplateData theme_templates: dict[str, list[ArchMgrTemplateData]]
repo_config: None # TODO: Define a good dtype here
class BootLoaderSettings: class BootLoaderSettings:
templates: ArchMgrTemplateData # Used to render the included template files
templates: list[ArchMgrTemplateData]
theme_folder: str theme_folder: str
conf_build_cmd: str conf_build_cmd: str
name: str name: str
+7
View File
@@ -0,0 +1,7 @@
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
@@ -0,0 +1,55 @@
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
@@ -0,0 +1,2 @@
nordic:
deep-dark:
+3 -2
View File
@@ -4,5 +4,6 @@ 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. 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. The latter reside in this folder and are a stripped-down version of the default config file.
## Template config file ## Template config folder
TODO: Restructure this folder This folder contains the template configs for archmgr to copy into the user's newly created config repo
using the init function
-52
View File
@@ -1,52 +0,0 @@
hyprland:
- hyprland
- hyprlock
- hypridle
- grimblast
- grim
- xdg-desktop-portal-hyprland
- polkit-kde-agent
hyrpland-extra:
- hyprpaper
- wl-clipboard
- cliphist
- rofi
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
-7
View File
@@ -1,7 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"type": "object",
"properties": {
"": {}
}
}
+6
View File
@@ -0,0 +1,6 @@
# 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