IDK :) userman or smt.. and inital commit
This commit is contained in:
Generated
+5
@@ -0,0 +1,5 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
Generated
+7
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.14 (userman)" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.14 (userman)" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
Generated
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/userman.iml" filepath="$PROJECT_DIR$/.idea/userman.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+10
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Python 3.14 (userman)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,30 @@
|
||||
import configparser
|
||||
import platform
|
||||
from pathlib import Path
|
||||
|
||||
CONFIG_FILE = Path.home() / ".config/userman/userman.conf"
|
||||
|
||||
def load_config():
|
||||
config = configparser.ConfigParser(allow_no_value=True)
|
||||
config.read(str(CONFIG_FILE))
|
||||
|
||||
options = config["options"] if "options" in config else {}
|
||||
|
||||
return {
|
||||
"db_path": Path(options.get("DBPath", "~/.local/share/userman/db")).expanduser(),
|
||||
"cache_dir": Path(options.get("CacheDir", "~/.cache/userman/pkg")).expanduser(),
|
||||
"gpg_dir": Path(options.get("GPGDir", "~/.config/userman/gnupg")).expanduser(),
|
||||
"hook_dir": Path(options.get("HookDir", "~/.config/userman/hooks")).expanduser(),
|
||||
"log_file": Path(options.get("LogFile", "~/.local/share/userman/pacman.log")).expanduser(),
|
||||
"root_dir": Path(options.get("RootDir", "~")).expanduser(),
|
||||
"arch": options.get("Architecture", "auto").replace("auto", platform.machine()),
|
||||
}
|
||||
|
||||
def get_repos() -> dict:
|
||||
config = configparser.ConfigParser(allow_no_value=True)
|
||||
config.read(str(CONFIG_FILE))
|
||||
return {
|
||||
section: config[section]["server"]
|
||||
for section in config.sections()
|
||||
if section.lower() != "options" and "server" in config[section]
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import subprocess
|
||||
import tarfile
|
||||
import os
|
||||
|
||||
from config import load_config, CONFIG_FILE, get_repos
|
||||
|
||||
DB_PATH = os.path.expanduser(load_config()["db_path"])
|
||||
|
||||
def get_package_meta(pkgname: str) -> dict | None:
|
||||
for dbfile in os.listdir(DB_PATH):
|
||||
if not dbfile.endwith(".db"):
|
||||
continue
|
||||
meta = _search_db(os.path.join(DB_PATH, dbfile), pkgname)
|
||||
if meta:
|
||||
return meta
|
||||
return None
|
||||
|
||||
def _search_db(dbfile: str, pkgname: str) -> dict | None:
|
||||
with tarfile.open(dbfile, "r:gz") as tar:
|
||||
for member in tar.getmembers():
|
||||
parts = member.name.split("/")
|
||||
if len(parts) != 2 or parts[1] != "desc":
|
||||
continue
|
||||
entry_name = parts[0].rsplit("-", 2)[0]
|
||||
if entry_name != pkgname:
|
||||
continue
|
||||
f = tar.extractfile(member)
|
||||
if f:
|
||||
return _parse_desc(f.read().decode("utf-8"))
|
||||
|
||||
def _parse_desc(content: str) -> dict | None:
|
||||
meta = {}
|
||||
lines = content.strip().split("\n")
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
if lines[i].startswith("%") and lines[i].endswith("%"):
|
||||
key = lines[i].strip("%").lower()
|
||||
i += 1
|
||||
values = []
|
||||
while i < len(lines) and lines[i] != "":
|
||||
values.append(lines[i])
|
||||
i += 1
|
||||
meta[key] = values[0] if len(values) == 1 else values
|
||||
i += 1
|
||||
return meta
|
||||
|
||||
def ensure_db():
|
||||
sync_dir = load_config()["db_path"] / "sync"
|
||||
|
||||
if not sync_dir.exists() or not any(sync_dir.glob("*.db")):
|
||||
print(":: First run detected, syncing databases...")
|
||||
_sync_db()
|
||||
|
||||
def _sync_db():
|
||||
sync_dir = load_config()["db_path"] / "sync"
|
||||
sync_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
for repo_name, server in get_repos().items():
|
||||
url = resolve_server_url(server, repo_name)
|
||||
db_url = f"{url}/{repo_name}.db"
|
||||
dest = sync_dir / f"{repo_name}.db"
|
||||
|
||||
print(f":: Syncing {repo_name}...")
|
||||
try:
|
||||
subprocess.run(["curl", "-L", "-o", str(dest), db_url], check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f":: Probobly failed to find repo '{repo_name}' at {db_url}")
|
||||
|
||||
def resolve_server_url(template: str, repo: str, arch: str = load_config()["arch"]) -> str:
|
||||
return (template
|
||||
.replace("$repo", repo)
|
||||
.replace("$arch", arch)
|
||||
.replace("${repo}", repo)
|
||||
.replace("${arch}", arch))
|
||||
@@ -0,0 +1,25 @@
|
||||
import subprocess
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from config import load_config
|
||||
|
||||
CACHE_DIR = os.path.expanduser(load_config()["cache_dir"])
|
||||
|
||||
def fetch_package(meta: dict) -> tuple[Path, Path]:
|
||||
CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
url = meta["url"]
|
||||
filename = meta["filename"]
|
||||
sig_url = url + ".sig"
|
||||
|
||||
pkg_path = CACHE_DIR / filename
|
||||
sig_path = CACHE_DIR / (filename + ".sig")
|
||||
|
||||
_curl(url, pkg_path)
|
||||
_curl(sig_url, sig_path)
|
||||
|
||||
return pkg_path, sig_path
|
||||
|
||||
def _curl(url: str, dest: Path):
|
||||
print(f":: Fetching {url}")
|
||||
subprocess.run(["curl", "-L", "-o", str(dest), url], check=True)
|
||||
@@ -0,0 +1,6 @@
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
def delegate_to_pacman(pkgname: str):
|
||||
result = subprocess.run(["sudo", "pacman", "-S", pkgname])
|
||||
sys.exit(result.returncode)
|
||||
@@ -0,0 +1,43 @@
|
||||
#! /bin/env python
|
||||
import sys
|
||||
from db import get_package_meta, ensure_db
|
||||
from handoff import delegate_to_pacman
|
||||
from fetch import fetch_package
|
||||
from verify import verify_sig
|
||||
|
||||
def main():
|
||||
|
||||
ensure_db()
|
||||
|
||||
if len(sys.argv) < 3:
|
||||
print("usage: userman -S <package>")
|
||||
sys.exit(1)
|
||||
|
||||
action = sys.argv[1]
|
||||
pkgname = sys.argv[2]
|
||||
|
||||
if action == "-S":
|
||||
install(pkgname)
|
||||
|
||||
def install(pkgname: str):
|
||||
meta = get_package_meta(pkgname)
|
||||
|
||||
if meta is None:
|
||||
print(f"error: package '{pkgname}' not found is userman db")
|
||||
sys.exit(1)
|
||||
|
||||
if meta.get("install_mode", "system") != "user":
|
||||
print(f":: Handing off '{pkgname}' to pacman")
|
||||
delegate_to_pacman(pkgname)
|
||||
return
|
||||
|
||||
pkg_path, sig_path = fetch_package(meta)
|
||||
|
||||
if not verify_sig(pkg_path, sig_path):
|
||||
print("error: GPG verification failed, aborting")
|
||||
sys.exit(1)
|
||||
|
||||
# Send to pacman with userman flags
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,82 @@
|
||||
#
|
||||
# ~/.config/userman/userman.conf
|
||||
#
|
||||
# See the pacman.conf(5) manpage for option and repository directives
|
||||
|
||||
#
|
||||
# GENERAL OPTIONS
|
||||
#
|
||||
[options]
|
||||
# The following paths are commented out with their default values listed.
|
||||
# If you wish to use different paths, uncomment and update the paths.
|
||||
#RootDir = ~
|
||||
#DBPath = ~/.local/share/userman/db
|
||||
#CacheDir = ~/.cache/userman/pkg
|
||||
#LogFile = ~/.local/share/userman/pacman.log
|
||||
#GPGDir = ~/.config/userman/gnupg
|
||||
#HookDir = ~/.config/userman/hooks
|
||||
HoldPkg =
|
||||
#XferCommand = /usr/bin/curl -L -C - -f -o %o %u
|
||||
#XferCommand = /usr/bin/wget --passive-ftp -c -O %o %u
|
||||
#CleanMethod = KeepInstalled
|
||||
Architecture = auto
|
||||
|
||||
# Pacman won't upgrade packages listed in IgnorePkg and members of IgnoreGroup
|
||||
#IgnorePkg =
|
||||
#IgnoreGroup =
|
||||
|
||||
#NoUpgrade =
|
||||
#NoExtract =
|
||||
|
||||
# Misc options
|
||||
#UseSyslog
|
||||
#Color
|
||||
#NoProgressBar
|
||||
CheckSpace
|
||||
#VerbosePkgLists
|
||||
ParallelDownloads = 5
|
||||
DownloadUser = alpm
|
||||
#DisableSandboxFilesystem
|
||||
#DisableSandboxSyscalls
|
||||
|
||||
# By default, pacman accepts packages signed by keys that its local keyring
|
||||
# trusts (see pacman-key and its man page), as well as unsigned packages.
|
||||
SigLevel = Required DatabaseOptional
|
||||
LocalFileSigLevel = Optional
|
||||
#RemoteFileSigLevel = Required
|
||||
|
||||
# NOTE: You must run `pacman-key --init` before first using pacman; the local
|
||||
# keyring can then be populated with the keys of all official Arch Linux
|
||||
# packagers with `pacman-key --populate archlinux`.
|
||||
|
||||
#
|
||||
# REPOSITORIES
|
||||
# - can be defined here or included from another file
|
||||
# - pacman will search repositories in the order defined here
|
||||
# - local/custom mirrors can be added here or in separate files
|
||||
# - repositories listed first will take precedence when packages
|
||||
# have identical names, regardless of version number
|
||||
# - URLs will have $repo replaced by the name of the current repo
|
||||
# - URLs will have $arch replaced by the name of the architecture
|
||||
#
|
||||
# Repository entries are of the format:
|
||||
# [repo-name]
|
||||
# Server = ServerName
|
||||
# Include = IncludePath
|
||||
#
|
||||
# The header [repo-name] is crucial - it must be present and
|
||||
# uncommented to enable the repo.
|
||||
#
|
||||
|
||||
# The testing repositories are disabled by default. To enable, uncomment the
|
||||
# repo name header and Include lines. You can add preferred servers immediately
|
||||
# after the header, and they will be used before the default mirrors.
|
||||
|
||||
[hearth]
|
||||
Server = file:///home/zacharias/Documents/userman-repo
|
||||
|
||||
# An example of a custom package repository. See the pacman manpage for
|
||||
# tips on creating your own repositories.
|
||||
#[custom]
|
||||
#SigLevel = Optional TrustAll
|
||||
#Server = file:///home/custompkgs
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
#! /bin/env bash
|
||||
|
||||
## The --root points to ~/ as a userman package is expected to follow this
|
||||
# pacman dir | userman dir | comment
|
||||
# /etc/<pkg> | ~/.local/share/<pkg> or ~/.config/<pkg> | I have seen where /etc contains things less config like and more runtime stuff wise so there for both
|
||||
# /usr/bin | ~/.local/bin
|
||||
# etc
|
||||
# etc
|
||||
#
|
||||
# The isea is to replace UNIX root conventiosn with XDG conventions
|
||||
|
||||
pacman --config ~/.config/userman/pacman.conf \
|
||||
--root ~/ \
|
||||
--dbpath ~/.local/share/userman/db \
|
||||
--cachedir ~/.cache/userman/pkg \
|
||||
--gpgdir ~/.config/userman/gnupg \
|
||||
--hookdir ~/.config/userman/hooks \
|
||||
--logfile ~/.local/share/userman/pacman.log
|
||||
@@ -0,0 +1,15 @@
|
||||
import subprocess
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from config import load_config
|
||||
|
||||
GPGDIR = os.path.expanduser(load_config()["gpg_dir"])
|
||||
|
||||
def verify_sig(pkg_path: Path, sig_path: Path) -> bool:
|
||||
result = subprocess.run([
|
||||
"gpg",
|
||||
"--homedir", str(GPGDIR),
|
||||
"--verify", str(sig_path), str(pkg_path)
|
||||
], capture_output=True)
|
||||
return result.returncode == 0
|
||||
Reference in New Issue
Block a user