Compare commits
20 Commits
6e497fdfd2
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 2fd69cc595 | |||
| 541a876307 | |||
| 5126e0373f | |||
| 299ac49b40 | |||
| b6901b59e6 | |||
| bcd0339d88 | |||
| 4017e0263b | |||
| b71d18cdc9 | |||
| 043b2618a3 | |||
| c264a5bea2 | |||
| bb123c23a1 | |||
| 414c065df4 | |||
| 35c976fcac | |||
| b8e2d68469 | |||
| 5734c0d524 | |||
| f5386d0e98 | |||
| ead9e3a3f7 | |||
| 711b89a0d6 | |||
| 0697bef7d5 | |||
| 6018d6256d |
@@ -0,0 +1,28 @@
|
|||||||
|
# Maintainer: Janis Hutz <development@janishutz.com>
|
||||||
|
|
||||||
|
pkgname=archmgr-git
|
||||||
|
pkgver=0.0.0
|
||||||
|
pkgrel=1
|
||||||
|
pkgdesc='A nixos-like declarative config and package manager for Arch Linux'
|
||||||
|
arch=('any')
|
||||||
|
url="https://github.com/janishutz/archmgr"
|
||||||
|
license=('GPL3')
|
||||||
|
depends=('python', 'python-pyaml')
|
||||||
|
makedepends=('git')
|
||||||
|
provides=('archmgr')
|
||||||
|
conflicts=('archmgr')
|
||||||
|
source=("$pkgname"::git+${url}.git)
|
||||||
|
sha256sums=('SKIP') # TODO: Add?
|
||||||
|
|
||||||
|
pkgver() {
|
||||||
|
cd "${pkgname}"
|
||||||
|
|
||||||
|
# TODO: For the non-git pkgbuild, need to use different output
|
||||||
|
printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)"
|
||||||
|
}
|
||||||
|
|
||||||
|
package() {
|
||||||
|
cd "${pkgname}"
|
||||||
|
|
||||||
|
# TODO: Need to finish
|
||||||
|
}
|
||||||
|
|||||||
Regular → Executable
+17
-5
@@ -1,3 +1,6 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# PYTHON_ARGCOMPLETE_OK
|
||||||
|
|
||||||
import cli.args as cliargs
|
import cli.args as cliargs
|
||||||
|
|
||||||
import commands.commit as commit
|
import commands.commit as commit
|
||||||
@@ -5,7 +8,10 @@ import commands.config as config
|
|||||||
import commands.init as init
|
import commands.init as init
|
||||||
import commands.pull as pull
|
import commands.pull as pull
|
||||||
import commands.push as push
|
import commands.push as push
|
||||||
|
import commands.prepare as setup
|
||||||
|
from config import load_config
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
args, ap = cliargs.add_cli_args()
|
args, ap = cliargs.add_cli_args()
|
||||||
|
|
||||||
if args.cmd == None:
|
if args.cmd == None:
|
||||||
@@ -13,8 +19,7 @@ if args.cmd == None:
|
|||||||
print("\nSpecify a subcommand, '-h' or '-v'")
|
print("\nSpecify a subcommand, '-h' or '-v'")
|
||||||
exit(64)
|
exit(64)
|
||||||
|
|
||||||
print(
|
print("""
|
||||||
"""
|
|
||||||
_
|
_
|
||||||
( )
|
( )
|
||||||
_ _ _ __ ___| |__ ___ ___ __ _ __
|
_ _ _ __ ___| |__ ___ ___ __ _ __
|
||||||
@@ -23,16 +28,23 @@ print(
|
|||||||
\\__ _)_) \\____)_) (_)_) (_) (_)\\__ |_)
|
\\__ _)_) \\____)_) (_)_) (_) (_)\\__ |_)
|
||||||
( )_) |
|
( )_) |
|
||||||
\\___/
|
\\___/
|
||||||
"""
|
""")
|
||||||
)
|
|
||||||
|
|
||||||
|
print(load_config("config.yml"))
|
||||||
|
try:
|
||||||
if args.cmd == "commit":
|
if args.cmd == "commit":
|
||||||
commit.commit(args.dry_run, args.force, args.no_commit)
|
commit.commit(args.force, args.no_render)
|
||||||
elif args.cmd == "config":
|
elif args.cmd == "config":
|
||||||
config.config()
|
config.config()
|
||||||
elif args.cmd == "init":
|
elif args.cmd == "init":
|
||||||
init.init(args.force)
|
init.init(args.force)
|
||||||
|
elif args.cmd == "setup":
|
||||||
|
setup.setup()
|
||||||
elif args.cmd == "pull":
|
elif args.cmd == "pull":
|
||||||
pull.pull(args.rebase, args.apply)
|
pull.pull(args.rebase, args.apply)
|
||||||
elif args.cmd == "push":
|
elif args.cmd == "push":
|
||||||
push.push(args.force)
|
push.push(args.force)
|
||||||
|
except KeyboardInterrupt as e:
|
||||||
|
exit(130)
|
||||||
|
except Exception as e:
|
||||||
|
raise e
|
||||||
|
|||||||
+5
-8
@@ -21,18 +21,15 @@ def add_cli_args():
|
|||||||
commit = sp.add_parser(
|
commit = sp.add_parser(
|
||||||
"commit", help="apply pending changes and commit to git repo"
|
"commit", help="apply pending changes and commit to git repo"
|
||||||
)
|
)
|
||||||
commit.add_argument("-f", "--force", help="force apply", action="store_true")
|
commit.add_argument("-f", "--force", help="force apply (skips prompts)", action="store_true")
|
||||||
commit.add_argument(
|
commit.add_argument(
|
||||||
"--no-render", "-r", help="do not re-render renderables", action="store_true"
|
"--no-render", "-r", help="do not re-render renderables", action="store_true"
|
||||||
)
|
)
|
||||||
commit.add_argument(
|
|
||||||
"--dry-run",
|
|
||||||
"-d",
|
|
||||||
help="print out files that would be changed",
|
|
||||||
action="store_true",
|
|
||||||
)
|
|
||||||
|
|
||||||
sp.add_parser("config", help="prints information about your config")
|
# TODO: Allow changing things like config path
|
||||||
|
sp.add_parser("config", help="prints information about your config and change some of them")
|
||||||
|
|
||||||
|
sp.add_parser("setup", help="Do initial setup, like installing required tools")
|
||||||
|
|
||||||
init = sp.add_parser("init", help="initialize a new archmgr repository")
|
init = sp.add_parser("init", help="initialize a new archmgr repository")
|
||||||
init.add_argument(
|
init.add_argument(
|
||||||
|
|||||||
+20
-6
@@ -1,8 +1,22 @@
|
|||||||
from commands.util.choice import confirm_overwrite
|
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
|
||||||
|
|
||||||
|
|
||||||
def commit(dry_run: bool = False, force: bool = False):
|
def commit(force: bool = False, no_render: bool = False):
|
||||||
if dry_run:
|
"""Commit the changes to the system
|
||||||
pass
|
|
||||||
print("Commit, force:", force)
|
Args:
|
||||||
print(confirm_overwrite())
|
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([], 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)
|
||||||
|
|||||||
+2
-4
@@ -1,6 +1,4 @@
|
|||||||
def config():
|
def config():
|
||||||
print(
|
print("""
|
||||||
"""
|
|
||||||
Your config can be found at
|
Your config can be found at
|
||||||
"""
|
""")
|
||||||
)
|
|
||||||
|
|||||||
+18
-4
@@ -1,14 +1,28 @@
|
|||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import commands.util.git as git
|
import commands.util.git as git
|
||||||
|
from commands.util.input_mgr import confirm
|
||||||
|
|
||||||
|
|
||||||
def init(force: bool = False):
|
def init(force: bool = False):
|
||||||
dir = os.getcwd()
|
dir = os.getcwd()
|
||||||
|
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||||
if force:
|
if force:
|
||||||
[os.remove(dir) for dir in os.listdir(dir)]
|
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)
|
git.init(dir)
|
||||||
os.mkdir(dir + "/config")
|
os.mkdir(dir + "/config")
|
||||||
os.mkdir(dir + "/etc")
|
os.mkdir(dir + "/db")
|
||||||
with open(dir + "/config.yaml") as file:
|
os.mkdir(dir + "/system")
|
||||||
file.write("")
|
os.mkdir(dir + "/includes")
|
||||||
|
shutil.copy(script_dir + "/templates/config/config.yml", dir + "/config.yml")
|
||||||
|
shutil.copy(script_dir + "/templates/config/system/", dir + "/includes/system/")
|
||||||
|
shutil.copy(script_dir + "/templates/config/templates/", dir + "/includes/templates/")
|
||||||
|
shutil.copy(script_dir + "/templates/README.md", dir + "/README.md")
|
||||||
print("Initialized a new archmgr repository")
|
print("Initialized a new archmgr repository")
|
||||||
|
# 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?)
|
||||||
|
# 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!)
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
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
|
||||||
+18
-9
@@ -1,13 +1,22 @@
|
|||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
def compute_diff(paths: List[str]):
|
def pkg_diff(target: List[str], actual: List[str]) -> tuple[List[str], List[str]]:
|
||||||
pass
|
"""Compute a diff between target packages and installed packages
|
||||||
|
|
||||||
|
Args:
|
||||||
def file_diff(path: str):
|
target: The target packages
|
||||||
pass
|
actual: The actually installed packages
|
||||||
|
|
||||||
|
Returns:
|
||||||
def pkg_diff():
|
A tuple with first the missing (not installed) packages
|
||||||
|
and second the extraneous (to be uninstalled) packages
|
||||||
|
"""
|
||||||
|
for i, pkg in enumerate(actual):
|
||||||
|
try:
|
||||||
|
idx = target.index(pkg)
|
||||||
|
target.pop(idx)
|
||||||
|
actual.pop(i)
|
||||||
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
return (target, actual)
|
||||||
|
|||||||
+102
-2
@@ -1,5 +1,105 @@
|
|||||||
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
def init(dirname: str):
|
def init(dirname: str) -> bool:
|
||||||
subprocess.call("git init", cwd=dirname)
|
"""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,5 +1,7 @@
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from commands.util.password_manager import PasswordManager
|
||||||
|
|
||||||
|
|
||||||
def choice(default: str, options: str, msg: str) -> str:
|
def choice(default: str, options: str, msg: str) -> str:
|
||||||
default = default.lower()
|
default = default.lower()
|
||||||
@@ -28,3 +30,15 @@ def confirm_overwrite(msg: Optional[str] = ""):
|
|||||||
return confirm(
|
return confirm(
|
||||||
False, (msg if msg != "" else "Do you really want to overwrite your changes?")
|
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()
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
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)
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
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,
|
||||||
|
)
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
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,
|
||||||
|
)
|
||||||
@@ -0,0 +1,384 @@
|
|||||||
|
{
|
||||||
|
"$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": {
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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
|
||||||
|
},
|
||||||
|
"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": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Settings for the bootloader, such as theme, using os-prober, etc",
|
||||||
|
"properties": {
|
||||||
|
"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": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Configuration for the bootloader theme",
|
||||||
|
"properties": {
|
||||||
|
"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\\/.-]+))\\/$"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"folder"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"os_prober": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether to enable OS prober to search for other operating systems"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"bootloader",
|
||||||
|
"esp_dir"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"themes": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {}
|
||||||
|
},
|
||||||
|
"git": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Automatically set up credential manager and clone repos",
|
||||||
|
"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": {
|
||||||
|
"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": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name that appears in the template"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The data that is to be inserted"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"name",
|
||||||
|
"data"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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
@@ -0,0 +1,58 @@
|
|||||||
|
# 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:
|
||||||
|
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)"
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
from typing import Any, cast
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from config import validator
|
||||||
|
from config.merger import merge_configs
|
||||||
|
|
||||||
|
|
||||||
|
def _load_config_file(file: str):
|
||||||
|
with open(file, "r") as f:
|
||||||
|
parsed = yaml.load(f, Loader=yaml.FullLoader)
|
||||||
|
return parsed
|
||||||
|
|
||||||
|
|
||||||
|
def load_config(file: str):
|
||||||
|
# Load and validate initial config
|
||||||
|
try:
|
||||||
|
loaded_conf = _load_config_file(file)
|
||||||
|
except Exception:
|
||||||
|
return {}
|
||||||
|
if not validator.validate(loaded_conf):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
conf = cast(dict[str, Any], loaded_conf)
|
||||||
|
requires = cast(list[str], conf["requires"])
|
||||||
|
conf.pop("requires")
|
||||||
|
|
||||||
|
# Recursively load files
|
||||||
|
for conf_file in requires:
|
||||||
|
conf = merge_configs(conf, load_config(conf_file))
|
||||||
|
|
||||||
|
return conf
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
def merge_configs(config: dict, new_config: dict):
|
||||||
|
if len(new_config) == 0 or len(config) == 0:
|
||||||
|
return config
|
||||||
|
|
||||||
|
# Merge configs
|
||||||
|
return config
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import json
|
||||||
|
import jsonschema
|
||||||
|
|
||||||
|
with open("config.schema.json") as file:
|
||||||
|
schema = json.load(file)
|
||||||
|
|
||||||
|
|
||||||
|
def validate(config):
|
||||||
|
try:
|
||||||
|
jsonschema.validate(config, schema)
|
||||||
|
except jsonschema.SchemaError:
|
||||||
|
print("Schema invalid")
|
||||||
|
return False
|
||||||
|
except jsonschema.ValidationError:
|
||||||
|
print("Config invalid")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
@@ -6,3 +6,27 @@
|
|||||||
- Copy normal config files into correct directories
|
- Copy normal config files into correct directories
|
||||||
- Render and copy renderables
|
- Render and copy renderables
|
||||||
- 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
|
||||||
|
|
||||||
|
|
||||||
|
# Ideas
|
||||||
|
- [ ] function to collect new configs
|
||||||
|
- [ ] config options for users and groups
|
||||||
|
- [ ] presets for things like desktops (like Hyprland)
|
||||||
|
- [ ] config options for the template rendering
|
||||||
|
- [ ] config options for themes
|
||||||
|
- [ ] grub config
|
||||||
|
- [ ] Dynamic selection of more configs (i.e. require syntax)
|
||||||
|
- [ ] Own config syntax?
|
||||||
|
- [ ] 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]$))`
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
pyyaml
|
||||||
pylette
|
pylette
|
||||||
argcomplete
|
argcomplete
|
||||||
inquirer
|
inquirer
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# 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
|
||||||
Reference in New Issue
Block a user