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