Skip to content

Commit 6d10997

Browse files
Merge pull request #3 from CubicArnament/main
Full Refactoring of Apger, include Kubernetes support, transfer on Go and self hosted sytem
2 parents 27e05ae + db3481f commit 6d10997

265 files changed

Lines changed: 16403 additions & 481 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.ci/recipe.yaml

Lines changed: 0 additions & 27 deletions
This file was deleted.

.ci/scripts/post-install

Lines changed: 0 additions & 14 deletions
This file was deleted.

.ci/scripts/post-remove

Lines changed: 0 additions & 13 deletions
This file was deleted.

.ci/scripts/pre-install

Lines changed: 0 additions & 13 deletions
This file was deleted.

.ci/scripts/pre-remove

Lines changed: 0 additions & 13 deletions
This file was deleted.

.gitattributes

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
* text=auto eol=lf
2+
*.sh text eol=lf
3+
*.yml text eol=lf
4+
*.yaml text eol=lf
5+
*.go text eol=lf
6+
*.toml text eol=lf
7+
*.md text eol=lf
8+
*.txt text eol=lf
9+
*.json text eol=lf
10+
*.conf text eol=lf
11+
*.c text eol=lf
12+
*.h text eol=lf
13+
*.py text eol=lf
14+
*.nix text eol=lf
15+
*.lock text eol=lf
16+
Makefile text eol=lf
17+
LICENSE text eol=lf

.github/scripts/update-readme.py

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
#!/usr/bin/env python3
2+
"""
3+
update-readme.py — regenerates dynamic sections in README.md.
4+
5+
Sections updated:
6+
<!-- TREE_START --> ... <!-- TREE_END --> project file tree
7+
<!-- STATS_START --> ... <!-- STATS_END --> package stats table
8+
<!-- PACKAGES_START --> ... <!-- PACKAGES_END --> full package list
9+
10+
Run from repo root:
11+
python3 .github/scripts/update-readme.py
12+
"""
13+
14+
import re
15+
import sys
16+
from pathlib import Path
17+
18+
try:
19+
import tomllib # Python 3.11+
20+
except ImportError:
21+
try:
22+
import tomli as tomllib
23+
except ImportError:
24+
print("error: need Python 3.11+ or 'pip install tomli'", file=sys.stderr)
25+
sys.exit(1)
26+
27+
REPODATA = Path("repodata")
28+
README = Path("README.md")
29+
30+
REPO_PRIO = {"core": 0, "main": 1, "extra": 2}
31+
REPO_EMOJI = {"core": "🔵", "main": "🟢", "extra": "🟡"}
32+
33+
# ── file-tree generator ───────────────────────────────────────────────────────
34+
35+
# Directories to skip entirely in the tree
36+
TREE_SKIP_DIRS = {".git", "__pycache__", "node_modules", ".github"}
37+
38+
# Directories whose *contents* are collapsed to a summary line
39+
TREE_COLLAPSE_DIRS = {"repodata", "src/apgbuild", "src/apgbuild/libapg"}
40+
41+
# Per-path annotations shown after the entry
42+
ANNOTATIONS: dict[str, str] = {
43+
"apger.conf": "# build config (single source of truth)",
44+
"go.mod": "# Go module",
45+
"go.sum": "# Go checksums",
46+
"repodata/": "# package recipes (.toml)",
47+
"repodata/switch.toml": "# arch-switch helper",
48+
"examples/": "# example recipe / metadata files",
49+
"src/Meson.build": "# build system",
50+
"src/meson_options.txt": "# meson options",
51+
"k8s-manifest.yml": "# Kubernetes PVC + ConfigMap + Job + Pod",
52+
"src/cmd/apger/main.go": "# binary entry point",
53+
"src/core/config.go": "# Config struct, LoadConfig, FindConfig",
54+
"src/core/main.go": "# Run(), CLI flags, apger.conf wiring",
55+
"src/core/march.go": "# MArch type: normalization, x86_64 level table",
56+
"src/core/validate.go": "# OOMKill + march/CPUID pre-flight validation",
57+
"src/builder/orchestrator.go": "# Kubernetes Job lifecycle, multistage pipeline",
58+
"src/builder/split.go": "# SplitAnalyzer: libs/bins/dev file grouping",
59+
"src/builder/templates.go": "# build system templates (meson/cmake/autotools/…)",
60+
"src/builder/downloader.go": "# (legacy) HTTP downloader",
61+
"src/downloader/downloader.go": "# aria2c (tarballs) + go-git (git repos) + progress",
62+
"src/logger/build_logger.go": "# kbuild-style filter: CC/CXX/LD/AS/CARGO/GO/…",
63+
"src/k8s/generator.go": "# GenerateBuildJob, oomResources, pullPolicy",
64+
"src/k8s/client.go": "# Kubernetes client wrapper",
65+
"src/k8s/gen-krnl.go": "# kernel module job generator",
66+
"src/metadata/types.go": "# Recipe, RecipeSource, RecipeSplit, PackageMeta",
67+
"src/metadata/recipe_loader.go": "# LoadRecipe (.toml/.json), FindRecipes, template",
68+
"src/metadata/generator.go": "# GenerateMetadata, checksums, HashRecipe",
69+
"src/storage/store.go": "# Store interface + DB wrapper",
70+
"src/storage/packages_db_bbolt.go": "# bbolt backend (build tag: bbolt)",
71+
"src/storage/packages_db_sqlite.go": "# SQLite3 backend (build tag: sqlite)",
72+
"src/tui/main.go": "# Model, screens: Dashboard/FM/Editor/Build",
73+
"src/tui/icons.go": "# Nerd Font icons per build template",
74+
"src/tui/screen_credentials.go": "# credentials screen",
75+
"src/tui/screen_settings.go": "# settings screen",
76+
"src/credentials/manager.go": "# credential store",
77+
"src/credentials/github_app.go": "# GitHub App token exchange",
78+
"src/pgp/signer.go": "# PGP package signing",
79+
"src/publisher/github.go": "# publish .apg to GitHub Releases",
80+
"src/reporter/build_report.go": "# build report generation",
81+
"src/apgbuild/": "# APG package archiver (git submodule)",
82+
"src/apgbuild/libapg/": "# C library for APG format (git submodule)",
83+
}
84+
85+
86+
def _annotation(rel: str) -> str:
87+
return (" " + ANNOTATIONS[rel]) if rel in ANNOTATIONS else ""
88+
89+
90+
def build_tree(root: Path, prefix: str = "", rel_base: str = "") -> list[str]:
91+
"""Recursively build tree lines for *root*, skipping unwanted dirs."""
92+
try:
93+
entries = sorted(root.iterdir(), key=lambda p: (p.is_file(), p.name.lower()))
94+
except PermissionError:
95+
return []
96+
97+
lines = []
98+
visible = [
99+
e for e in entries
100+
if not (e.is_dir() and e.name in TREE_SKIP_DIRS)
101+
]
102+
103+
for i, entry in enumerate(visible):
104+
is_last = (i == len(visible) - 1)
105+
connector = "└── " if is_last else "├── "
106+
extension = " " if is_last else "│ "
107+
108+
rel = (rel_base + "/" + entry.name).lstrip("/")
109+
110+
if entry.is_dir():
111+
rel_dir = rel + "/"
112+
ann = _annotation(rel_dir)
113+
114+
# collapse certain subtrees
115+
if any(rel == c or rel.startswith(c.rstrip("/")) for c in TREE_COLLAPSE_DIRS):
116+
lines.append(f"{prefix}{connector}{entry.name}/{ann}")
117+
lines.append(f"{prefix}{extension}└── ...")
118+
continue
119+
120+
lines.append(f"{prefix}{connector}{entry.name}/{ann}")
121+
lines.extend(build_tree(entry, prefix + extension, rel))
122+
else:
123+
ann = _annotation(rel)
124+
lines.append(f"{prefix}{connector}{entry.name}{ann}")
125+
126+
return lines
127+
128+
129+
def render_tree() -> str:
130+
root = Path(".")
131+
lines = ["```", "apger/"]
132+
lines.extend(build_tree(root))
133+
lines.append("```")
134+
return "\n".join(lines) + "\n"
135+
136+
137+
# ── package collector ─────────────────────────────────────────────────────────
138+
139+
def collect() -> list[dict]:
140+
entries = []
141+
seen: set[str] = set()
142+
143+
for toml_path in sorted(REPODATA.rglob("*.toml")):
144+
parts = toml_path.parts # ('repodata', arch, repo, 'name.toml')
145+
if len(parts) < 4:
146+
continue
147+
148+
arch = parts[1]
149+
repo = parts[2]
150+
151+
try:
152+
data = tomllib.loads(toml_path.read_text(encoding="utf-8"))
153+
except Exception as e:
154+
print(f"warn: cannot parse {toml_path}: {e}", file=sys.stderr)
155+
continue
156+
157+
pkg = data.get("package", {})
158+
build = data.get("build", {})
159+
160+
name = pkg.get("name", "")
161+
version = pkg.get("version", "")
162+
if not name:
163+
continue
164+
165+
key = f"{name}@{version}"
166+
if key in seen:
167+
continue
168+
seen.add(key)
169+
170+
entries.append({
171+
"name": name,
172+
"version": version,
173+
"description": pkg.get("description", ""),
174+
"license": pkg.get("license", ""),
175+
"homepage": pkg.get("homepage", ""),
176+
"template": build.get("template", ""),
177+
"arch": arch,
178+
"repo": repo,
179+
})
180+
181+
entries.sort(key=lambda e: (REPO_PRIO.get(e["repo"], 9), e["name"]))
182+
return entries
183+
184+
185+
# ── section renderers ─────────────────────────────────────────────────────────
186+
187+
def render_stats(entries: list[dict]) -> str:
188+
total = len(entries)
189+
by_repo: dict[str, int] = {}
190+
by_arch: dict[str, int] = {}
191+
by_template: dict[str, int] = {}
192+
193+
for e in entries:
194+
by_repo[e["repo"]] = by_repo.get(e["repo"], 0) + 1
195+
by_arch[e["arch"]] = by_arch.get(e["arch"], 0) + 1
196+
if e["template"]:
197+
by_template[e["template"]] = by_template.get(e["template"], 0) + 1
198+
199+
lines = [
200+
"| Metric | Value |",
201+
"|--------|-------|",
202+
f"| Total packages | **{total}** |",
203+
f"| 🔵 core | {by_repo.get('core', 0)} |",
204+
f"| 🟢 main | {by_repo.get('main', 0)} |",
205+
f"| 🟡 extra | {by_repo.get('extra', 0)} |",
206+
f"| x86\\_64 | {by_arch.get('x86_64', 0)} |",
207+
f"| aarch64 | {by_arch.get('aarch64', 0)} |",
208+
]
209+
for tmpl, count in sorted(by_template.items(), key=lambda x: -x[1]):
210+
lines.append(f"| build: {tmpl} | {count} |")
211+
212+
return "\n".join(lines) + "\n"
213+
214+
215+
def render_packages(entries: list[dict]) -> str:
216+
lines = [
217+
"| Package | Version | Repo | Description | License | Build |",
218+
"|---------|---------|------|-------------|---------|-------|",
219+
]
220+
for e in entries:
221+
name = f"[{e['name']}]({e['homepage']})" if e["homepage"] else e["name"]
222+
desc = e["description"]
223+
if len(desc) > 60:
224+
desc = desc[:57] + "..."
225+
emoji = REPO_EMOJI.get(e["repo"], "⚪")
226+
lines.append(
227+
f"| {name} | `{e['version']}` | {emoji} {e['repo']} "
228+
f"| {desc} | {e['license']} | {e['template']} |"
229+
)
230+
return "\n".join(lines) + "\n"
231+
232+
233+
# ── README section replacer ───────────────────────────────────────────────────
234+
235+
def replace_section(content: str, tag: str, body: str) -> str:
236+
start = f"<!-- {tag}_START -->"
237+
end = f"<!-- {tag}_END -->"
238+
pattern = re.compile(re.escape(start) + r"[\s\S]*?" + re.escape(end))
239+
replacement = f"{start}\n{body}{end}"
240+
if pattern.search(content):
241+
return pattern.sub(replacement, content)
242+
return content.rstrip("\n") + f"\n\n{replacement}\n"
243+
244+
245+
# ── main ──────────────────────────────────────────────────────────────────────
246+
247+
def main() -> None:
248+
if not README.exists():
249+
print("error: README.md not found — run from repo root", file=sys.stderr)
250+
sys.exit(1)
251+
252+
content = README.read_text(encoding="utf-8")
253+
254+
# 1. file tree
255+
print("building file tree…")
256+
content = replace_section(content, "TREE", render_tree())
257+
258+
# 2. package stats + list
259+
if REPODATA.is_dir():
260+
entries = collect()
261+
print(f"collected {len(entries)} unique packages")
262+
content = replace_section(content, "STATS", render_stats(entries))
263+
content = replace_section(content, "PACKAGES", render_packages(entries))
264+
else:
265+
print("warn: repodata/ not found, skipping package sections", file=sys.stderr)
266+
267+
README.write_text(content, encoding="utf-8")
268+
print("✓ README.md updated")
269+
270+
271+
if __name__ == "__main__":
272+
main()

0 commit comments

Comments
 (0)