Files
Ansible/scripts/addons/webgui/deploy/fetch_assets.py
T
2026-09-22 19:23:17 +02:00

79 lines
3.0 KiB
Python

#!/usr/bin/env python3
"""Fetch pinned upstream assets ONCE; browsers only load local copies.
Supply --from-directory for air-gapped deployment. Integrity checks are identical.
No npm, Node, CDN requests from the browser, or mutable 'latest' URLs.
"""
from __future__ import annotations
import argparse
import base64
import hashlib
import os
from pathlib import Path
import tempfile
from urllib.request import urlopen
ASSETS = {
'bootstrap.min.css': (
'https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css',
'sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB'),
'htmx.min.js': (
'https://cdn.jsdelivr.net/npm/htmx.org@2.0.10/dist/htmx.min.js',
'H5SrcfygHmAuTDZphMHqBJLc3FhssKjG7w/CeCpFReSfwBWDTKpkzPP8c+cLsK+V'),
}
def valid(data: bytes, expected: str) -> bool:
return base64.b64encode(hashlib.sha384(data).digest()).decode('ascii') == expected
def prepare(destination: Path, offline: Path | None = None, *, reuse: Path | None = None) -> None:
destination.mkdir(parents=True, exist_ok=True)
for name, (url, integrity) in ASSETS.items():
target = destination / name
if target.is_file() and valid(target.read_bytes(), integrity):
print('Verified local asset:', name)
continue
if offline:
data = (offline / name).read_bytes()
elif reuse and (reuse / name).is_file() and not (reuse / name).is_symlink():
candidate = (reuse / name).read_bytes()
if valid(candidate, integrity):
data = candidate
print('Reusing verified installed asset:', name)
else:
with urlopen(url, timeout=30) as response:
data = response.read(2_000_001)
else:
with urlopen(url, timeout=30) as response:
data = response.read(2_000_001)
if len(data) > 2_000_000 or not valid(data, integrity):
raise ValueError(f'Upstream integrity mismatch: {name}. No asset was installed.')
fd, filename = tempfile.mkstemp(prefix='.asset-', dir=destination)
temporary = Path(filename)
try:
with os.fdopen(fd, 'wb') as stream:
stream.write(data)
stream.flush()
os.fsync(stream.fileno())
temporary.chmod(0o644)
os.replace(temporary, target)
finally:
temporary.unlink(missing_ok=True)
print('Installed verified local asset:', name)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--from-directory', type=Path)
parser.add_argument('--destination', type=Path, default=Path(__file__).resolve().parents[1] / 'src/aim_webgui/static/vendor')
args = parser.parse_args()
try:
prepare(args.destination, args.from_directory)
except Exception as exc:
parser.exit(1, f'Assets not prepared: {exc}\nUse --from-directory with the exact pinned upstream files for offline installation.\n')
if __name__ == '__main__':
main()