commit ff53f88f355d77333f119495a62ec22f20373e50 Author: pan Date: Sun Jul 5 22:38:59 2026 +0200 Create Thüringen branding edition on shared core diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..488d65b --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +__pycache__/ +*.py[cod] +*.egg-info/ +.pytest_cache/ +.venv/ +build/ +dist/ +.env diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..eba137c --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "vendor/synadm-tui"] + path = vendor/synadm-tui + url = https://git.blackwall.ipv64.de/pan/synadm-tui.git diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8b3bf1e --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +GNU GENERAL PUBLIC LICENSE +Version 3, 29 June 2007 + +Copyright (C) 2026 synadm-tui contributors + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program. The complete license text is available at: +https://www.gnu.org/licenses/gpl-3.0.txt + +SPDX-License-Identifier: GPL-3.0-or-later diff --git a/README.md b/README.md new file mode 100644 index 0000000..63f3f98 --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +# synadm TUI – Thüringen Edition + +Die Thüringen Edition ergänzt den unveränderten Anwendungskern von [`synadm-tui`](https://git.blackwall.ipv64.de/pan/synadm-tui) um das Thüringen-Thema, das Wappen und getrennte Release-Pakete. + +Programmlogik, Navigation, Assistenten und Sicherheitsfunktionen werden nicht kopiert. Das Repository bindet einen geprüften `synadm-tui`-Stand als Git-Submodul ein; dadurch bleiben beide Editionen funktional identisch. + +## Quellcode testen + +```bash +git clone --recurse-submodules https://git.blackwall.ipv64.de/pan/synadm-tui-thueringen.git +cd synadm-tui-thueringen +PYTHONPATH=vendor/synadm-tui python3 -m unittest discover -v +PYTHONPATH=vendor/synadm-tui python3 -m synadm_tui_thueringen --version +``` + +## Native Datei und Pakete bauen + +```bash +python3 -m pip install pyinstaller +python3 scripts/build_native.py +python3 scripts/build_packages.py +cd dist/packages && sha256sum --check SHA256SUMS +``` + +## Pakete ohne Runner veröffentlichen + +```bash +python3 scripts/publish_packages.py --tea-login Building --dry-run +python3 scripts/publish_packages.py --tea-login Building +``` + +DEB-Pakete werden in die Distribution `thueringen`, RPM-Pakete in die Gruppe `thueringen` veröffentlicht. Dadurch bleiben auch die Paketquellen von der Standard Edition getrennt. + +## Voraussetzungen + +- Linux x86-64 oder ARM64 +- Python 3.10+ für den Quellstart +- PyInstaller für native Builds +- `dpkg-deb` und `rpmbuild` für Systempakete +- separat installiertes `synadm`, wahlweise über den integrierten Installationsassistenten + +## Lizenz + +GPL-3.0-or-later, siehe `LICENSE`. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..b731b30 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,29 @@ +[build-system] +requires = ["setuptools>=68"] +build-backend = "setuptools.build_meta" + +[project] +name = "synadm-tui-thueringen" +version = "0.15" +description = "Thüringen-Edition der Terminal-Oberfläche für synadm" +readme = "README.md" +requires-python = ">=3.10" +license = { text = "GPL-3.0-or-later" } +authors = [{ name = "synadm-tui contributors" }] +dependencies = ["synadm-tui==0.15"] +classifiers = [ + "Environment :: Console :: Curses", + "Programming Language :: Python :: 3", +] + +[project.scripts] +synadm-tui-thueringen = "synadm_tui_thueringen.cli:main" + +[tool.setuptools.packages.find] +include = ["synadm_tui_thueringen*"] + +[tool.setuptools.package-data] +synadm_tui_thueringen = ["assets/*.png", "assets/*.block.json"] + +[tool.unittest] +start-directory = "tests" diff --git a/scripts/build_block_art.py b/scripts/build_block_art.py new file mode 100644 index 0000000..ef58dd5 --- /dev/null +++ b/scripts/build_block_art.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +"""Regenerate the portable 256-color crest data.""" + +from __future__ import annotations + +import json +import re +import subprocess +from pathlib import Path + + +ROOT = Path(__file__).resolve().parent.parent +ASSETS = ROOT / "synadm_tui_thueringen" / "assets" +PIXEL = re.compile(r"#([0-9A-Fa-f]{8})\s") + + +def main() -> int: + process = subprocess.run( + ["convert", str(ASSETS / "thueringen-wappen.png"), "-filter", "Lanczos", "-resize", "18x20!", "txt:-"], + check=True, + text=True, + stdout=subprocess.PIPE, + ) + pixels = [match.group(1).lower() for line in process.stdout.splitlines() if (match := PIXEL.search(line))] + if len(pixels) != 18 * 20: + raise RuntimeError(f"360 Pixel erwartet, {len(pixels)} erhalten") + target = ASSETS / "thuringia.block.json" + target.write_text( + json.dumps({"width": 18, "height": 20, "pixels": pixels}, separators=(",", ":")) + "\n", + encoding="utf-8", + ) + print(target) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/build_native.py b/scripts/build_native.py new file mode 100644 index 0000000..4d77db7 --- /dev/null +++ b/scripts/build_native.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +"""Build the Thüringen binary against the pinned synadm-tui core.""" + +from __future__ import annotations + +import hashlib +import os +from pathlib import Path + +import PyInstaller.__main__ + + +ROOT = Path(__file__).resolve().parent.parent +CORE = Path(os.environ.get("SYNADM_TUI_CORE", ROOT / "vendor" / "synadm-tui")).resolve() + + +def main() -> int: + if not (CORE / "synadm_tui" / "app.py").is_file(): + raise SystemExit("synadm-tui-Core fehlt; git submodule update --init ausführen.") + arguments = [ + str(ROOT / "scripts" / "standalone_entry.py"), + "--name=synadm-tui-thueringen", + "--onefile", + "--clean", + f"--paths={ROOT}", + f"--paths={CORE}", + f"--distpath={ROOT / 'dist'}", + f"--workpath={ROOT / 'build' / 'pyinstaller'}", + f"--specpath={ROOT / 'build'}", + ] + for asset in ( + "retro-cyberspace.png", + "hacker-terminal.png", + "cyberspace.block.json", + "hacker.block.json", + ): + arguments.append(f"--add-data={CORE / 'synadm_tui' / 'assets' / asset}:synadm_tui/assets") + for asset in ("thueringen-wappen.png", "thuringia.block.json"): + arguments.append( + f"--add-data={ROOT / 'synadm_tui_thueringen' / 'assets' / asset}:synadm_tui_thueringen/assets" + ) + PyInstaller.__main__.run(arguments) + artifact = ROOT / "dist" / "synadm-tui-thueringen" + checksum = hashlib.sha256(artifact.read_bytes()).hexdigest() + (ROOT / "dist" / "SHA256SUMS").write_text( + f"{checksum} synadm-tui-thueringen\n", encoding="utf-8" + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/build_packages.py b/scripts/build_packages.py new file mode 100644 index 0000000..32f5c3e --- /dev/null +++ b/scripts/build_packages.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +"""Build DEB and RPM packages by reusing the pinned core's packaging code.""" + +from __future__ import annotations + +import importlib.util +import os +import sys +from pathlib import Path + + +ROOT = Path(__file__).resolve().parent.parent +CORE = Path(os.environ.get("SYNADM_TUI_CORE", ROOT / "vendor" / "synadm-tui")).resolve() +SPEC = importlib.util.spec_from_file_location("synadm_core_build_packages", CORE / "scripts" / "build_packages.py") +if not SPEC or not SPEC.loader: + raise SystemExit("Paketierungsmodul des synadm-tui-Core fehlt.") +module = importlib.util.module_from_spec(SPEC) +sys.modules[SPEC.name] = module +SPEC.loader.exec_module(module) +module.ROOT = ROOT +module.DIST = ROOT / "dist" +module.OUTPUT = module.DIST / "packages" +module.HOMEPAGE = "https://git.blackwall.ipv64.de/pan/synadm-tui-thueringen" +module.EDITIONS = ( + module.Edition( + "synadm-tui-thueringen", + "synadm-tui-thueringen", + "Thüringen-Edition der synadm Terminal-Oberfläche", + "Regionales Branding auf dem unveränderten Anwendungskern von synadm-tui.", + ), +) + + +raise SystemExit(module.main()) diff --git a/scripts/publish_packages.py b/scripts/publish_packages.py new file mode 100644 index 0000000..3463614 --- /dev/null +++ b/scripts/publish_packages.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +"""Publish Thüringen packages via the pinned core's secure uploader.""" + +from __future__ import annotations + +import importlib.util +import os +import sys +from pathlib import Path + + +ROOT = Path(__file__).resolve().parent.parent +CORE = Path(os.environ.get("SYNADM_TUI_CORE", ROOT / "vendor" / "synadm-tui")).resolve() +SPEC = importlib.util.spec_from_file_location("synadm_core_publish_packages", CORE / "scripts" / "publish_packages.py") +if not SPEC or not SPEC.loader: + raise SystemExit("Uploadmodul des synadm-tui-Core fehlt.") +module = importlib.util.module_from_spec(SPEC) +sys.modules[SPEC.name] = module +SPEC.loader.exec_module(module) +module.ROOT = ROOT +module.PACKAGE_DIR = ROOT / "dist" / "packages" +module.DEFAULT_DISTRIBUTION = "thueringen" +module.DEFAULT_RPM_GROUP = "thueringen" + + +raise SystemExit(module.main()) diff --git a/scripts/standalone_entry.py b/scripts/standalone_entry.py new file mode 100644 index 0000000..2fb411c --- /dev/null +++ b/scripts/standalone_entry.py @@ -0,0 +1,6 @@ +"""PyInstaller entry point for the Thüringen edition.""" + +from synadm_tui_thueringen.cli import main + + +raise SystemExit(main()) diff --git a/synadm_tui_thueringen/__init__.py b/synadm_tui_thueringen/__init__.py new file mode 100644 index 0000000..1fe327d --- /dev/null +++ b/synadm_tui_thueringen/__init__.py @@ -0,0 +1,3 @@ +"""Branding extension for the Thüringen edition of synadm TUI.""" + +__version__ = "0.15" diff --git a/synadm_tui_thueringen/__main__.py b/synadm_tui_thueringen/__main__.py new file mode 100644 index 0000000..faaa63b --- /dev/null +++ b/synadm_tui_thueringen/__main__.py @@ -0,0 +1,4 @@ +from .cli import main + + +raise SystemExit(main()) diff --git a/synadm_tui_thueringen/assets/thueringen-wappen.png b/synadm_tui_thueringen/assets/thueringen-wappen.png new file mode 100644 index 0000000..6ac1afa Binary files /dev/null and b/synadm_tui_thueringen/assets/thueringen-wappen.png differ diff --git a/synadm_tui_thueringen/assets/thuringia.block.json b/synadm_tui_thueringen/assets/thuringia.block.json new file mode 100644 index 0000000..5a50c0f --- /dev/null +++ b/synadm_tui_thueringen/assets/thuringia.block.json @@ -0,0 +1 @@ +{"width":18,"height":20,"pixels":["15678fff","1875a4ff","18729fff","1774a2ff","076899ff","106f9fff","1874a4ff","206c8bff","1b6e96ff","296e80ff","1f6f91ff","277087ff","1072a4ff","076796ff","1472a1ff","1775a4ff","1678a8ff","166891ff","1878a7ff","1c89c0ff","1c87beff","1e81b3ff","86a9bbff","4b90b2ff","1081c0ff","318893ff","66853eff","97a32bff","6c873bff","33858aff","3187b9ff","96b1bdff","3186b1ff","1e7bacff","217fb0ff","177bacff","1a739fff","1d85b4ff","0f80b0ff","267aa0ff","d4ceceff","759bafff","0083c1ff","566075ff","d03c06ff","dd470cff","ce3a05ff","5d4958ff","3f8cb0ff","dad2ceff","2290bfff","7c4546ff","cb3010ff","10719fff","1276a4ff","505c5cff","8d341cff","206887ff","0d7bb3ff","1d7babff","37607dff","902f1eff","9d1200ff","d92607ff","f32000ff","78494aff","0687c4ff","136995ff","64505bff","bc311bff","665a68ff","1178a9ff","176f93ff","5f7473ff","c69f9aff","8facbdff","3187b2ff","1281b8ff","275f84ff","4f6779ff","8a959bff","d5bdb8ff","e7c4baff","7c94a4ff","127cb0ff","a4abb1ff","636777ff","286e94ff","1386bcff","1877a7ff","1774a3ff","257993ff","5a8691ff","f7eeecff","d5d5d4ff","6f95a7ff","5fa2c1ff","4891b2ff","a2bdc9ff","f0f8f9ff","f4fbfbff","6c9cb2ff","91a4afff","94bacbff","0076b4ff","cdccccff","a1b3beff","096a9aff","1976a5ff","0b80bbff","186691ff","a34635ff","b93f25ff","ca4527ff","ea512eff","de4b2aff","d6482aff","d2482bff","e24422ff","795051ff","3281a8ff","a73f2cff","30698dff","549cbdff","5091b2ff","1272a2ff","106c9aff","83a6b8ff","74a3baff","145d86ff","a52006ff","e12300ff","e42500ff","e42500ff","e62500ff","e52800ff","f52100ff","744749ff","0086c8ff","375876ff","b0331bff","2a5d7fff","0e81b9ff","1976a4ff","0e6a99ff","9fb9c9ff","95b0bdff","006ea6ff","868d97ff","c6a39bff","c6aca7ff","ceaba2ff","ceb0aaff","bda39eff","cfa79dff","53829dff","4a8bacff","87b4c7ff","2e698bff","afa1a1ff","b4c1c7ff","3f7895ff","19749dff","257a9aff","1c78a3ff","89acbeff","eff3f3ff","e7e9e9ff","dadadaff","a4c8d7ff","ccdadeff","e0ebeeff","ebefeeff","4f91afff","5398b9ff","d0d1d0ff","097db2ff","93b7c7ff","4f8cabff","1d719aff","147093ff","7a4d37ff","d43b1fff","b6523cff","b6462fff","ae4f3eff","426b84ff","435f7aff","795554ff","c54a30ff","d75033ff","ce472bff","525769ff","1e6f9aff","605766ff","864943ff","0f86bfff","1777a7ff","1d728fff","5e5247ff","78412fff","37565eff","235e81ff","235171ff","9b2e1eff","ee2300ff","ef2900ff","ce1f00ff","d11c00ff","eb2200ff","e92600ff","a03a2bff","834441ff","346c8eff","1c84b7ff","1976a4ff","1e739dfd","1285bbff","107499ff","097eb7ff","4391b6ff","cdbbb7ff","e8b4a7ff","d4b4aeff","7f7b83ff","988b8dff","ddbdb6ff","dabab3ff","b19b9aff","1679abff","0e81b7ff","d4d4d3ff","8caabaff","0a6b9cfe","1974a1e3","2a80abff","cbc9ccfe","5d93aeff","1278acff","659fbbff","ecfcffff","bfd9e2ff","007bb9ff","579cbdff","fff5f0ff","fcfcfbff","b8d5dfff","107cafff","117bafff","6694acfe","458eb2ff","176f9ae6","1d688da4","237fadff","a1b4bdfc","4c8eb2ff","048acbff","236e97ff","cb462aff","da4a2bff","8b3f37ff","326281ff","845957ff","9a564dff","dc4f30ff","bf3d22ff","8d392eff","0a74a9fc","1382b9ff","23698ca7","304e5e37","1a79a9fe","097ab4fc","227490fe","34647dff","7a3e3fff","fc2300ff","dd2600ff","685059ff","1682b7ff","1181baff","156d9cff","ca1e00ff","fd2500ff","82423dfe","1785bafc","1e7ba9ff","2b4c5d3a","1286b400","24627f93","1683beff","4a7569f9","cc9579fe","e6b1a3ff","95817eff","266082ff","0b7bafff","1880b5ff","1d82b2ff","4289acff","d5b8b6ff","9e8170fe","1173a4f9","1a86bbff","22618297","0b8dbe00","12749e03","5d000006","1c678fb5","237895ff","64928dff","6799a4fc","298298fd","197fb5ff","d6ccc7ff","6e9ebbff","1b6a7cff","c9b596fd","e7e4dbfc","4d8d92ff","147eb0ff","22678bb9","41000007","12739d03","2b4a5b01","00ffff00","8d000003","1c5e8483","116e9df0","0e79b0ff","1883bbff","1e80b0ff","85a9bbff","4d92b6ff","147bacff","487e85ff","337686ff","1472aaf1","235d7a85","6f000004","00ffff00","2a4a5c01","244e6400","363e4101","00ffff00","0b85c900","39383926","265f7c84","1f6c92ca","1c74a1f0","0a6a99fe","1170a0fe","1e75a0f1","116999cb","145e8985","38362d27","1483b800","ff000000","2f404a01","244f6600"]} diff --git a/synadm_tui_thueringen/cli.py b/synadm_tui_thueringen/cli.py new file mode 100644 index 0000000..5310392 --- /dev/null +++ b/synadm_tui_thueringen/cli.py @@ -0,0 +1,11 @@ +"""Entry point for the Thüringen edition.""" + +from __future__ import annotations + +from synadm_tui.cli import main as core_main + +from .theme import THURINGIA_EDITION, THURINGIA_THEME + + +def main(argv: list[str] | None = None) -> int: + return core_main(argv, edition=THURINGIA_EDITION, extra_themes=(THURINGIA_THEME,)) diff --git a/synadm_tui_thueringen/theme.py b/synadm_tui_thueringen/theme.py new file mode 100644 index 0000000..88ac106 --- /dev/null +++ b/synadm_tui_thueringen/theme.py @@ -0,0 +1,52 @@ +"""Thüringen profile and visual assets; application behavior comes from synadm-tui.""" + +from __future__ import annotations + +import curses +from pathlib import Path + +from synadm_tui.app import Theme +from synadm_tui.edition import Edition + + +ASSETS = Path(__file__).resolve().parent / "assets" + +THURINGIA_CREST = ( + " .-============-.", + " / * * * * \\", + " | /\\_/\\ |", + " | ___/ o o \\___ |", + " | / |==^==| \\ |", + " | /|===|\\ |", + " \\ * * * * /", + " '============'", +) + +THURINGIA_THEME = Theme( + "thuringia", + "Freistaat Thüringen", + "THÜRINGEN", + ( + (curses.COLOR_RED, -1), + (curses.COLOR_WHITE, curses.COLOR_RED), + (curses.COLOR_CYAN, -1), + (curses.COLOR_RED, -1), + (curses.COLOR_YELLOW, -1), + (curses.COLOR_WHITE, -1), + (curses.COLOR_BLUE, -1), + (curses.COLOR_WHITE, curses.COLOR_BLUE), + (curses.COLOR_RED, curses.COLOR_WHITE), + ), + THURINGIA_CREST, + "FREISTAAT THÜRINGEN // SYSTEM BEREIT", + image_path=ASSETS / "thueringen-wappen.png", + block_path=ASSETS / "thuringia.block.json", +) + +THURINGIA_EDITION = Edition( + key="thuringia", + name="Thüringen Edition", + binary_name="synadm-tui-thueringen", + default_theme="thuringia", + theme_keys=("thuringia", "cyberspace", "matrix", "hacker", "high-contrast", "monochrome"), +) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e68eb40 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""Tests for the Thüringen branding overlay.""" diff --git a/tests/test_edition.py b/tests/test_edition.py new file mode 100644 index 0000000..4ce7797 --- /dev/null +++ b/tests/test_edition.py @@ -0,0 +1,28 @@ +from __future__ import annotations + +import unittest + +import synadm_tui +from synadm_tui.app import App +from synadm_tui.runner import SynadmRunner +from synadm_tui_thueringen import __version__ +from synadm_tui_thueringen.theme import THURINGIA_EDITION, THURINGIA_THEME + + +class EditionTests(unittest.TestCase): + def test_core_and_overlay_versions_match(self) -> None: + self.assertEqual(__version__, synadm_tui.__version__) + + def test_theme_assets_are_external_and_complete(self) -> None: + self.assertTrue(THURINGIA_THEME.image_path.is_file()) + self.assertTrue(THURINGIA_THEME.block_path.is_file()) + self.assertEqual(len(THURINGIA_THEME.palette), 9) + + def test_core_accepts_external_edition(self) -> None: + app = App(SynadmRunner("not-installed"), THURINGIA_EDITION, (THURINGIA_THEME,)) + self.assertEqual(app.theme.key, "thuringia") + self.assertEqual(app.edition.binary_name, "synadm-tui-thueringen") + + +if __name__ == "__main__": + unittest.main() diff --git a/vendor/synadm-tui b/vendor/synadm-tui new file mode 160000 index 0000000..c1483aa --- /dev/null +++ b/vendor/synadm-tui @@ -0,0 +1 @@ +Subproject commit c1483aae71e41588f4b2c5f731f77eb3735cc46b