Compare commits
25 Commits
043b2618a3
..
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 36d3c6f992 | |||
| 217ccadc74 | |||
| d391c53c6c | |||
| 24c52c1bba | |||
| 80d7b3d86e | |||
| 72b477381f | |||
| e8d8429bc9 | |||
| afe8d29340 | |||
| 78eecfc81a | |||
| e24eb647ca | |||
| 72a8ceb741 | |||
| ecb2952a7e | |||
| b7218c2a82 | |||
| 7b1dfe6ebc | |||
| 190fb86758 | |||
| f9e6120910 | |||
| 31426c006b | |||
| 2fd69cc595 | |||
| 541a876307 | |||
| 5126e0373f | |||
| 299ac49b40 | |||
| b6901b59e6 | |||
| bcd0339d88 | |||
| 4017e0263b | |||
| b71d18cdc9 |
@@ -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
|
||||
}
|
||||
|
||||
@@ -3,3 +3,9 @@
|
||||
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)
|
||||
|
||||
## 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
|
||||
|
||||
Executable
+9
@@ -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
+12
-8
@@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
# PYTHON_ARGCOMPLETE_OK
|
||||
|
||||
import cli.args as cliargs
|
||||
# The main app
|
||||
from typing import cast
|
||||
import cli as cliargs
|
||||
|
||||
import commands.commit as commit
|
||||
import commands.config as config
|
||||
@@ -9,8 +8,10 @@ import commands.init as init
|
||||
import commands.pull as pull
|
||||
import commands.push as push
|
||||
import commands.prepare as setup
|
||||
import commands.show as show
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
def run():
|
||||
args, ap = cliargs.add_cli_args()
|
||||
|
||||
if args.cmd == None:
|
||||
@@ -29,19 +30,22 @@ if __name__ == "__main__":
|
||||
\\___/
|
||||
""")
|
||||
|
||||
conf = {}
|
||||
try:
|
||||
if args.cmd == "commit":
|
||||
commit.commit(args.force, args.no_render)
|
||||
commit.commit(conf, args.force, args.no_render)
|
||||
elif args.cmd == "config":
|
||||
config.config()
|
||||
config.config(args, conf)
|
||||
elif args.cmd == "init":
|
||||
init.init(args.force)
|
||||
elif args.cmd == "setup":
|
||||
setup.setup()
|
||||
elif args.cmd == "pull":
|
||||
pull.pull(args.rebase, args.apply)
|
||||
pull.pull(conf, args.rebase, args.apply)
|
||||
elif args.cmd == "push":
|
||||
push.push(args.force)
|
||||
elif args.cmd == "show":
|
||||
show.show(conf, cast(ArchMgrTemplates, {}), args)
|
||||
except KeyboardInterrupt as e:
|
||||
exit(130)
|
||||
except Exception as e:
|
||||
@@ -0,0 +1,34 @@
|
||||
# CLI argument parsing and setup for it
|
||||
import argparse
|
||||
import argcomplete
|
||||
|
||||
from args import commit, init, pull, push, show
|
||||
|
||||
|
||||
def add_cli_args():
|
||||
ap = argparse.ArgumentParser(
|
||||
"archmgr",
|
||||
description="A nixos-like declarative config and package manager for Arch Linux (or any other distro, with some tweaks)",
|
||||
usage="archmgr [command] [options]",
|
||||
)
|
||||
ap.add_argument("-v", "--version", action="version", version="%(prog)s V0.0.1")
|
||||
sp = ap.add_subparsers(
|
||||
title="commands",
|
||||
metavar="Use 'archmgr [command] --help' to see help for each command",
|
||||
dest="cmd",
|
||||
)
|
||||
|
||||
# ┌ ┐
|
||||
# │ Subcommands │
|
||||
# └ ┘
|
||||
sp.add_parser("setup", help="Do initial setup, like installing required tools")
|
||||
commit.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)
|
||||
show.add_parser(sp)
|
||||
|
||||
argcomplete.autocomplete(ap)
|
||||
|
||||
return ap.parse_args(), ap
|
||||
@@ -0,0 +1,13 @@
|
||||
import argparse as ap
|
||||
|
||||
|
||||
def add_parser(sp: ap._SubParsersAction[ap.ArgumentParser]):
|
||||
commit = sp.add_parser(
|
||||
"commit", help="apply pending changes and commit to git repo"
|
||||
)
|
||||
commit.add_argument(
|
||||
"-f", "--force", help="force apply (skips prompts)", action="store_true"
|
||||
)
|
||||
commit.add_argument(
|
||||
"--no-render", "-r", help="do not re-render renderables", action="store_true"
|
||||
)
|
||||
@@ -0,0 +1,11 @@
|
||||
import argparse as ap
|
||||
|
||||
|
||||
def add_parser(sp: ap._SubParsersAction[ap.ArgumentParser]):
|
||||
init = sp.add_parser("init", help="initialize a new archmgr repository")
|
||||
init.add_argument(
|
||||
"--force",
|
||||
"-f",
|
||||
help="resets the git repository and initializes new archmgr repo",
|
||||
action="store_true",
|
||||
)
|
||||
@@ -0,0 +1,12 @@
|
||||
import argparse as ap
|
||||
|
||||
|
||||
def add_parser(sp: ap._SubParsersAction[ap.ArgumentParser]):
|
||||
pull = sp.add_parser("pull", help="pull changes from git remote")
|
||||
pull.add_argument("--rebase", "-r", help="execute rebase", action="store_true")
|
||||
pull.add_argument(
|
||||
"--apply",
|
||||
"-a",
|
||||
help="also apply the changes to the local system",
|
||||
action="store_true",
|
||||
)
|
||||
@@ -0,0 +1,11 @@
|
||||
import argparse as ap
|
||||
|
||||
|
||||
def add_parser(sp: ap._SubParsersAction[ap.ArgumentParser]):
|
||||
push = sp.add_parser("push", help="push pending changes to git remote")
|
||||
push.add_argument(
|
||||
"--force",
|
||||
"-f",
|
||||
help="force push (overriding possible remote changes)",
|
||||
action="store_true",
|
||||
)
|
||||
@@ -0,0 +1,13 @@
|
||||
import argparse as ap
|
||||
|
||||
|
||||
def add_parser(sp: ap._SubParsersAction[ap.ArgumentParser]):
|
||||
show = sp.add_parser("show", help="get information about configuration presets")
|
||||
show_sp = show.add_subparsers(
|
||||
title="config presets to show details on",
|
||||
dest="show",
|
||||
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.add_argument("show_pkg")
|
||||
@@ -0,0 +1 @@
|
||||
# Declare a file explicitly if it has different permissions
|
||||
@@ -0,0 +1,2 @@
|
||||
def clone_git_repo(repo_url: str, clone_path: str, branch: str = "DEFAULT"):
|
||||
pass
|
||||
@@ -0,0 +1,6 @@
|
||||
def add_packages(pkgs: list[str]):
|
||||
pass
|
||||
|
||||
|
||||
def add_package_bundles(pkgs: list[str]):
|
||||
pass
|
||||
@@ -0,0 +1,2 @@
|
||||
def enable_reflector():
|
||||
pass
|
||||
@@ -0,0 +1,2 @@
|
||||
def enable_repo(name: str):
|
||||
pass
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,2 @@
|
||||
def set_gtk_theme():
|
||||
pass
|
||||
@@ -0,0 +1,34 @@
|
||||
from typing import List
|
||||
|
||||
|
||||
def pkg_diff(target: List[str], actual: List[str]) -> tuple[List[str], List[str]]:
|
||||
"""Compute a diff between target packages and installed packages
|
||||
|
||||
Args:
|
||||
target: The target packages
|
||||
actual: The actually installed packages
|
||||
|
||||
Returns:
|
||||
A tuple with first the missing (not installed) packages
|
||||
and second the extraneous (to be uninstalled) packages
|
||||
"""
|
||||
removed = []
|
||||
diffed_out = []
|
||||
for pkg in actual:
|
||||
try:
|
||||
diffed_out.append(target.index(pkg))
|
||||
except Exception:
|
||||
removed.append(pkg)
|
||||
|
||||
diffed_out.reverse()
|
||||
new_pkgs = []
|
||||
if len(diffed_out) > 0:
|
||||
curr = diffed_out.pop()
|
||||
for i, pkg in enumerate(target):
|
||||
if i != curr:
|
||||
new_pkgs.append(pkg)
|
||||
else:
|
||||
if len(diffed_out) > 0:
|
||||
curr = diffed_out.pop()
|
||||
|
||||
return (new_pkgs, removed)
|
||||
@@ -1,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"]
|
||||
|
||||
@@ -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]):
|
||||
-61
@@ -1,61 +0,0 @@
|
||||
import argparse
|
||||
import argcomplete
|
||||
|
||||
|
||||
def add_cli_args():
|
||||
ap = argparse.ArgumentParser(
|
||||
"archmgr",
|
||||
description="A nixos-like declarative config and package manager for Arch Linux (or any other distro, with some tweaks)",
|
||||
usage="archmgr [command] [options]",
|
||||
)
|
||||
ap.add_argument("-v", "--version", action="version", version="%(prog)s V0.0.1")
|
||||
sp = ap.add_subparsers(
|
||||
title="commands",
|
||||
metavar="Use 'archmgr [command] --help' to see help for each command",
|
||||
dest="cmd",
|
||||
)
|
||||
|
||||
# ┌ ┐
|
||||
# │ Subcommands │
|
||||
# └ ┘
|
||||
commit = sp.add_parser(
|
||||
"commit", help="apply pending changes and commit to git repo"
|
||||
)
|
||||
commit.add_argument("-f", "--force", help="force apply (skips prompts)", action="store_true")
|
||||
commit.add_argument(
|
||||
"--no-render", "-r", help="do not re-render renderables", action="store_true"
|
||||
)
|
||||
|
||||
# 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.add_argument(
|
||||
"--force",
|
||||
"-f",
|
||||
help="resets the git repository and initializes new archmgr repo",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
push = sp.add_parser("push", help="push pending changes to git remote")
|
||||
push.add_argument(
|
||||
"--force",
|
||||
"-f",
|
||||
help="force push (overriding possible remote changes)",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
pull = sp.add_parser("pull", help="pull changes from git remote")
|
||||
pull.add_argument("--rebase", "-r", help="execute rebase", action="store_true")
|
||||
pull.add_argument(
|
||||
"--apply",
|
||||
"-a",
|
||||
help="also apply the changes to the local system",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
argcomplete.autocomplete(ap)
|
||||
|
||||
return ap.parse_args(), ap
|
||||
@@ -1,21 +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
|
||||
|
||||
|
||||
def commit(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([], pacman.list_explicitly_installed())
|
||||
print_diff(add, remove)
|
||||
if confirm(True, "Do you really want to proceed?"):
|
||||
pw = password()
|
||||
@@ -1,4 +0,0 @@
|
||||
def config():
|
||||
print("""
|
||||
Your config can be found at
|
||||
""")
|
||||
@@ -1,19 +0,0 @@
|
||||
import os
|
||||
import commands.util.git as git
|
||||
|
||||
|
||||
def init(force: bool = False):
|
||||
dir = os.getcwd()
|
||||
if force:
|
||||
[os.remove(dir) for dir in os.listdir(dir)]
|
||||
git.init(dir)
|
||||
os.mkdir(dir + "/config")
|
||||
os.mkdir(dir + "/etc")
|
||||
with open(dir + "/config.yaml") as file:
|
||||
file.write("")
|
||||
print("Initialized a new archmgr repository")
|
||||
# TODO: Warn user to not delete .config/archmgr repo
|
||||
# TODO: Set up that repo (where to put it? /usr/share?)
|
||||
# TODO: Consider collecting function
|
||||
# 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!)
|
||||
@@ -1,28 +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")
|
||||
@@ -1,2 +0,0 @@
|
||||
def pull(rebase: bool = False, apply: bool = False):
|
||||
print("pull")
|
||||
@@ -1,2 +0,0 @@
|
||||
def push(force: bool = False):
|
||||
print("push")
|
||||
@@ -1,22 +0,0 @@
|
||||
from typing import List
|
||||
|
||||
|
||||
def pkg_diff(target: List[str], actual: List[str]) -> tuple[List[str], List[str]]:
|
||||
"""Compute a diff between target packages and installed packages
|
||||
|
||||
Args:
|
||||
target: The target packages
|
||||
actual: The actually installed packages
|
||||
|
||||
Returns:
|
||||
A tuple with first the missing (not installed) packages
|
||||
and second the extraneous (to be uninstalled) packages
|
||||
"""
|
||||
for i, pkg in enumerate(actual):
|
||||
try:
|
||||
idx = target.index(pkg)
|
||||
target.pop(idx)
|
||||
actual.pop(i)
|
||||
except Exception:
|
||||
pass
|
||||
return (target, actual)
|
||||
@@ -1,20 +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
|
||||
- Copy normal config files into correct directories
|
||||
- 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
|
||||
# Concepts
|
||||
## Config
|
||||
In python, using functions and args for them
|
||||
|
||||
|
||||
# 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
|
||||
## Init
|
||||
- Copy
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pyyaml
|
||||
pylette
|
||||
argcomplete
|
||||
inquirer
|
||||
|
||||
Reference in New Issue
Block a user