new fonts

This commit is contained in:
Mateusz Gruszczyński
2026-05-20 13:47:07 +02:00
parent 07c23a8d25
commit 6ab330f583
8 changed files with 423 additions and 7 deletions

View File

@@ -1 +0,0 @@
scripts/INSTALL.md

198
INSTALL.md Normal file
View File

@@ -0,0 +1,198 @@
# pyTorrent stack installer
This document describes the one-command installer for installing **rTorrent + pyTorrent** from a clean server.
The installer is split into two layers:
- `scripts/install_stack.sh` - public bootstrap script intended to be downloaded directly from Git.
- `scripts/stack_installers/` - OS-specific installers and helper scripts used by the bootstrap script.
## Quick install
Run as root or through `sudo`:
```bash
curl -fsSL https://git.linuxiarz.pl/gru/pyTorrent/raw/branch/master/scripts/install_stack.sh | sudo bash
```
The bootstrap script downloads the current pyTorrent repository, detects the operating system family, and runs the matching installer:
- Debian / Ubuntu: `scripts/stack_installers/install_stack_debian_ubuntu.sh`
- RHEL-compatible systems: `scripts/stack_installers/install_stack_rhel.sh`
Supported RHEL-compatible systems include RHEL, Rocky Linux, AlmaLinux, CentOS Stream, and Fedora-like systems where `dnf` or `yum` is available.
## What gets installed
Default installation includes:
- rTorrent `v0.16.11`
- libtorrent `v0.16.11`
- minimal rTorrent build without c-ares/custom curl
- rTorrent system user: `rtorrent`
- rTorrent SCGI endpoint: `scgi://127.0.0.1:5000`
- rTorrent incoming BitTorrent port: `51300`
- pyTorrent application directory: `/opt/pytorrent`
- pyTorrent HTTP port: `8090`
- pyTorrent profile configured through the HTTP API
The installer creates or updates a pyTorrent rTorrent profile through API after both services are installed.
## Recommended usage with overrides
Environment variables must be passed to the `sudo bash` process.
Example:
```bash
curl -fsSL https://git.linuxiarz.pl/gru/pyTorrent/raw/branch/master/scripts/install_stack.sh \
| sudo PYTORRENT_PORT=8091 RTORRENT_SCGI_PORT=5001 bash
```
Another example with a custom profile name:
```bash
curl -fsSL https://git.linuxiarz.pl/gru/pyTorrent/raw/branch/master/scripts/install_stack.sh \
| sudo PYTORRENT_PROFILE_NAME="Local rTorrent" PYTORRENT_PORT=8090 bash
```
## Bootstrap parameters
These variables are used by `scripts/install_stack.sh`.
| Variable | Default | Description |
| --- | --- | --- |
| `PYTORRENT_REPO_URL` | `https://git.linuxiarz.pl/gru/pyTorrent` | Git repository base URL. |
| `PYTORRENT_REPO_BRANCH` | `master` | Branch used to download the repository archive. |
| `PYTORRENT_ARCHIVE_URL` | derived from repo URL and branch | Custom repository archive URL. |
| `PYTORRENT_BOOTSTRAP_DIR` | `/tmp/pytorrent-stack-installer` | Temporary directory used by the bootstrap script. |
| `PYTORRENT_KEEP_BOOTSTRAP_DIR` | `0` | Set to `1` to keep the temporary directory after installation. |
Example using a different branch:
```bash
curl -fsSL https://git.linuxiarz.pl/gru/pyTorrent/raw/branch/master/scripts/install_stack.sh \
| sudo PYTORRENT_REPO_BRANCH=develop bash
```
## rTorrent parameters
These variables are used by both stack installers.
| Variable | Default | Description |
| --- | --- | --- |
| `RTORRENT_USER` | `rtorrent` | System user used to run rTorrent. |
| `RTORRENT_HOME` | `/home/${RTORRENT_USER}` | Home directory for the rTorrent user. |
| `RTORRENT_BASE_DIR` | `/opt/rtorrent_build` | Build and install directory for xmlrpc-c, libtorrent and rTorrent. |
| `RTORRENT_SCGI_PORT` | `5000` | Local SCGI port for rTorrent XMLRPC/SCGI. |
| `RTORRENT_TORRENT_PORT` | `51300` | Incoming BitTorrent listen port. |
| `RTORRENT_REF` | `v0.16.11` | rTorrent Git tag, branch, or commit. |
| `LIBTORRENT_REF` | `v0.16.11` | libtorrent Git tag, branch, or commit. |
Example:
```bash
curl -fsSL https://git.linuxiarz.pl/gru/pyTorrent/raw/branch/master/scripts/install_stack.sh \
| sudo RTORRENT_USER=rtorrent RTORRENT_SCGI_PORT=5001 RTORRENT_TORRENT_PORT=51400 bash
```
## pyTorrent parameters
| Variable | Default | Description |
| --- | --- | --- |
| `PYTORRENT_APP_DIR` | `/opt/pytorrent` | pyTorrent installation directory. |
| `PYTORRENT_PORT` | `8090` | HTTP port used by the pyTorrent service. |
| `PYTORRENT_BASE_URL` | `http://127.0.0.1:${PYTORRENT_PORT}` | Base URL used by the API configurator. |
| `PYTORRENT_PROFILE_NAME` | `Local rTorrent` | Name of the rTorrent profile created in pyTorrent. |
| `PYTORRENT_API_TOKEN` | empty | Bearer token used when pyTorrent API authentication is enabled. |
| `PYTORRENT_SERVICE_NAME` | `pytorrent` | systemd service name for pyTorrent. |
| `PYTORRENT_RTORRENT_SCGI_URL` | `scgi://127.0.0.1:${RTORRENT_SCGI_PORT}` | SCGI URL saved in the pyTorrent rTorrent profile. |
Example with API token:
```bash
curl -fsSL https://git.linuxiarz.pl/gru/pyTorrent/raw/branch/master/scripts/install_stack.sh \
| sudo PYTORRENT_API_TOKEN="pt_xxx" bash
```
## API configurator parameters
The API configurator can be run manually:
```bash
/opt/pytorrent/venv/bin/python /opt/pytorrent/scripts/stack_installers/configure_pytorrent_api.py \
--base-url http://127.0.0.1:8090 \
--profile-name "Local rTorrent" \
--scgi-url scgi://127.0.0.1:5000
```
CLI options:
| Option | Environment variable | Default | Description |
| --- | --- | --- | --- |
| `--base-url` | `PYTORRENT_BASE_URL` | `http://127.0.0.1:8090` | pyTorrent API base URL. |
| `--api-token` | `PYTORRENT_API_TOKEN` | empty | Bearer token for authenticated API calls. |
| `--profile-name` | `PYTORRENT_RTORRENT_PROFILE_NAME` | `Local rTorrent` | Profile name to create or update. |
| `--scgi-url` | `PYTORRENT_RTORRENT_SCGI_URL` | `scgi://127.0.0.1:5000` | rTorrent SCGI URL. |
| `--timeout` | `PYTORRENT_RTORRENT_TIMEOUT` | `10` | rTorrent request timeout in seconds. |
| `--wait` | `PYTORRENT_API_WAIT_SECONDS` | `90` | Time to wait for the pyTorrent API to become available. |
| `--remote` | `PYTORRENT_RTORRENT_REMOTE` | `0` | Mark profile as remote. Accepts `1`, `true`, `yes`, `on`. |
## Local installation without bootstrap
If the repository is already cloned:
Debian / Ubuntu:
```bash
sudo bash scripts/stack_installers/install_stack_debian_ubuntu.sh
```
RHEL-compatible systems:
```bash
sudo bash scripts/stack_installers/install_stack_rhel.sh
```
## Installed service hints
Check services:
```bash
systemctl status pytorrent
systemctl status rtorrent@rtorrent.service
```
Check logs:
```bash
tail -f /data/logs/app.log /data/logs/error.log
journalctl -u pytorrent -f
journalctl -u rtorrent@rtorrent.service -f
```
## Notes
- The default rTorrent build is intentionally minimal.
- c-ares and custom curl are not enabled by the stack installer defaults.
- The rTorrent installer overwrites the generated `.rtorrent.rc` because the stack installer passes `--force-config`.
- pyTorrent is configured through the HTTP API after the service starts.
- If API authentication is enabled before profile configuration, pass `PYTORRENT_API_TOKEN`.
## Build logs and troubleshooting
The stack installer writes quiet build output to `/var/log/pytorrent-installer` by default.
Override it with:
```bash
PYTORRENT_STACK_LOG_DIR=/tmp/pytorrent-build-logs
```
For full command output during rTorrent/libtorrent compilation, run with:
```bash
PYTORRENT_DEBUG_INSTALL=1
```
On RHEL-compatible systems the installer also tries to enable CRB/PowerTools and installs `libcurl-devel`, `redhat-rpm-config`, `patch`, `diffutils`, `findutils`, `file`, and `libstdc++-devel`, because minimal Alma/Rocky images often do not include enough build tooling.

View File

@@ -13,6 +13,33 @@ FLAG_ICONS_VERSION = "7.2.3"
SWAGGER_UI_VERSION = "5" SWAGGER_UI_VERSION = "5"
SOCKET_IO_VERSION = "4.7.5" SOCKET_IO_VERSION = "4.7.5"
GOOGLE_FONT_FAMILIES = (
"DM Sans",
"Figtree",
"Geist",
"IBM Plex Sans",
"Inter",
"JetBrains Mono",
"Lato",
"Manrope",
"Montserrat",
"Nunito Sans",
"Open Sans",
"Poppins",
"Roboto",
"Source Sans 3",
)
GOOGLE_FONT_WEIGHTS = "400;500;600;700;800"
def google_fonts_css_url() -> str:
families = "&".join(
f"family={name.replace(' ', '+')}:wght@{GOOGLE_FONT_WEIGHTS}"
for name in GOOGLE_FONT_FAMILIES
)
return f"https://fonts.googleapis.com/css2?{families}&display=swap"
BOOTSTRAP_THEMES = ( BOOTSTRAP_THEMES = (
"default", "default",
"flatly", "flatly",
@@ -39,6 +66,10 @@ STATIC_ASSETS = {
"local": f"{LIBS_STATIC_DIR}/flag-icons/{FLAG_ICONS_VERSION}/css/flag-icons.min.css", "local": f"{LIBS_STATIC_DIR}/flag-icons/{FLAG_ICONS_VERSION}/css/flag-icons.min.css",
"cdn": f"https://cdn.jsdelivr.net/gh/lipis/flag-icons@{FLAG_ICONS_VERSION}/css/flag-icons.min.css", "cdn": f"https://cdn.jsdelivr.net/gh/lipis/flag-icons@{FLAG_ICONS_VERSION}/css/flag-icons.min.css",
}, },
"font_css": {
"local": f"{LIBS_STATIC_DIR}/fonts/google-fonts.css",
"cdn": google_fonts_css_url(),
},
"socket_io_js": { "socket_io_js": {
"local": f"{LIBS_STATIC_DIR}/socket.io/{SOCKET_IO_VERSION}/socket.io.min.js", "local": f"{LIBS_STATIC_DIR}/socket.io/{SOCKET_IO_VERSION}/socket.io.min.js",
"cdn": f"https://cdn.socket.io/{SOCKET_IO_VERSION}/socket.io.min.js", "cdn": f"https://cdn.socket.io/{SOCKET_IO_VERSION}/socket.io.min.js",
@@ -87,6 +118,7 @@ def missing_offline_paths() -> list[Path]:
LIBS_DIR / f"fontawesome/{FONTAWESOME_VERSION}/webfonts", LIBS_DIR / f"fontawesome/{FONTAWESOME_VERSION}/webfonts",
LIBS_DIR / f"flag-icons/{FLAG_ICONS_VERSION}/flags/4x3", LIBS_DIR / f"flag-icons/{FLAG_ICONS_VERSION}/flags/4x3",
LIBS_DIR / f"flag-icons/{FLAG_ICONS_VERSION}/flags/1x1", LIBS_DIR / f"flag-icons/{FLAG_ICONS_VERSION}/flags/1x1",
LIBS_DIR / "fonts/files",
] ]
for directory in required_dirs: for directory in required_dirs:
if not directory.is_dir() or not any(directory.iterdir()): if not directory.is_dir() or not any(directory.iterdir()):

View File

@@ -20,11 +20,22 @@ BOOTSTRAP_THEMES = {
FONT_FAMILIES = { FONT_FAMILIES = {
"default": "Theme default", "default": "Theme default",
"adwaita-mono": "Adwaita Mono", "system-ui": "System UI / Apple-like",
"figtree": "Figtree",
"inter": "Inter", "inter": "Inter",
"system-ui": "System UI", "geist": "Geist",
"manrope": "Manrope",
"dm-sans": "DM Sans",
"source-sans-3": "Source Sans 3", "source-sans-3": "Source Sans 3",
"open-sans": "Open Sans",
"roboto": "Roboto",
"lato": "Lato",
"nunito-sans": "Nunito Sans",
"poppins": "Poppins",
"montserrat": "Montserrat",
"ibm-plex-sans": "IBM Plex Sans",
"jetbrains-mono": "JetBrains Mono", "jetbrains-mono": "JetBrains Mono",
"adwaita-mono": "Adwaita Mono",
} }
# Note: Backend owns the recommended torrent table layout so frontend builds do not duplicate presets. # Note: Backend owns the recommended torrent table layout so frontend builds do not duplicate presets.

View File

@@ -33,13 +33,66 @@ html[data-app-font="inter"] {
} }
html[data-app-font="system-ui"] { html[data-app-font="system-ui"] {
--app-font-family: --app-font-family:
system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; system-ui, -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI",
Roboto, Arial, sans-serif;
}
html[data-app-font="figtree"] {
--app-font-family:
Figtree, Inter, system-ui, -apple-system, "Segoe UI", Roboto, Arial,
sans-serif;
}
html[data-app-font="geist"] {
--app-font-family:
Geist, Inter, system-ui, -apple-system, "Segoe UI", Roboto, Arial,
sans-serif;
}
html[data-app-font="manrope"] {
--app-font-family:
Manrope, Inter, system-ui, -apple-system, "Segoe UI", Roboto, Arial,
sans-serif;
}
html[data-app-font="dm-sans"] {
--app-font-family:
"DM Sans", Inter, system-ui, -apple-system, "Segoe UI", Roboto, Arial,
sans-serif;
} }
html[data-app-font="source-sans-3"] { html[data-app-font="source-sans-3"] {
--app-font-family: --app-font-family:
"Source Sans 3", "Source Sans Pro", system-ui, -apple-system, Segoe UI, "Source Sans 3", "Source Sans Pro", system-ui, -apple-system, Segoe UI,
Roboto, Arial, sans-serif; Roboto, Arial, sans-serif;
} }
html[data-app-font="open-sans"] {
--app-font-family:
"Open Sans", system-ui, -apple-system, "Segoe UI", Roboto, Arial,
sans-serif;
}
html[data-app-font="roboto"] {
--app-font-family:
Roboto, system-ui, -apple-system, "Segoe UI", Arial, sans-serif;
}
html[data-app-font="lato"] {
--app-font-family:
Lato, system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
}
html[data-app-font="nunito-sans"] {
--app-font-family:
"Nunito Sans", system-ui, -apple-system, "Segoe UI", Roboto, Arial,
sans-serif;
}
html[data-app-font="poppins"] {
--app-font-family:
Poppins, system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
}
html[data-app-font="montserrat"] {
--app-font-family:
Montserrat, system-ui, -apple-system, "Segoe UI", Roboto, Arial,
sans-serif;
}
html[data-app-font="ibm-plex-sans"] {
--app-font-family:
"IBM Plex Sans", system-ui, -apple-system, "Segoe UI", Roboto, Arial,
sans-serif;
}
html[data-app-font="jetbrains-mono"] { html[data-app-font="jetbrains-mono"] {
--app-font-family: --app-font-family:
"JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas,

View File

@@ -33,13 +33,66 @@ html[data-app-font="inter"] {
} }
html[data-app-font="system-ui"] { html[data-app-font="system-ui"] {
--app-font-family: --app-font-family:
system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; system-ui, -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI",
Roboto, Arial, sans-serif;
}
html[data-app-font="figtree"] {
--app-font-family:
Figtree, Inter, system-ui, -apple-system, "Segoe UI", Roboto, Arial,
sans-serif;
}
html[data-app-font="geist"] {
--app-font-family:
Geist, Inter, system-ui, -apple-system, "Segoe UI", Roboto, Arial,
sans-serif;
}
html[data-app-font="manrope"] {
--app-font-family:
Manrope, Inter, system-ui, -apple-system, "Segoe UI", Roboto, Arial,
sans-serif;
}
html[data-app-font="dm-sans"] {
--app-font-family:
"DM Sans", Inter, system-ui, -apple-system, "Segoe UI", Roboto, Arial,
sans-serif;
} }
html[data-app-font="source-sans-3"] { html[data-app-font="source-sans-3"] {
--app-font-family: --app-font-family:
"Source Sans 3", "Source Sans Pro", system-ui, -apple-system, Segoe UI, "Source Sans 3", "Source Sans Pro", system-ui, -apple-system, Segoe UI,
Roboto, Arial, sans-serif; Roboto, Arial, sans-serif;
} }
html[data-app-font="open-sans"] {
--app-font-family:
"Open Sans", system-ui, -apple-system, "Segoe UI", Roboto, Arial,
sans-serif;
}
html[data-app-font="roboto"] {
--app-font-family:
Roboto, system-ui, -apple-system, "Segoe UI", Arial, sans-serif;
}
html[data-app-font="lato"] {
--app-font-family:
Lato, system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
}
html[data-app-font="nunito-sans"] {
--app-font-family:
"Nunito Sans", system-ui, -apple-system, "Segoe UI", Roboto, Arial,
sans-serif;
}
html[data-app-font="poppins"] {
--app-font-family:
Poppins, system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
}
html[data-app-font="montserrat"] {
--app-font-family:
Montserrat, system-ui, -apple-system, "Segoe UI", Roboto, Arial,
sans-serif;
}
html[data-app-font="ibm-plex-sans"] {
--app-font-family:
"IBM Plex Sans", system-ui, -apple-system, "Segoe UI", Roboto, Arial,
sans-serif;
}
html[data-app-font="jetbrains-mono"] { html[data-app-font="jetbrains-mono"] {
--app-font-family: --app-font-family:
"JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas,

View File

@@ -8,6 +8,7 @@
<link rel="shortcut icon" href="{{ static_url('favicon.svg') }}" type="image/svg+xml"> <link rel="shortcut icon" href="{{ static_url('favicon.svg') }}" type="image/svg+xml">
<link id="bootstrapThemeStylesheet" href="{{ bootstrap_theme_url(prefs.bootstrap_theme if prefs else 'default') }}" rel="stylesheet"> <link id="bootstrapThemeStylesheet" href="{{ bootstrap_theme_url(prefs.bootstrap_theme if prefs else 'default') }}" rel="stylesheet">
<link href="{{ frontend_asset_url('fontawesome_css') }}" rel="stylesheet"> <link href="{{ frontend_asset_url('fontawesome_css') }}" rel="stylesheet">
<link href="{{ frontend_asset_url('font_css') }}" rel="stylesheet">
<link href="{{ frontend_asset_url('flag_icons_css') }}" rel="stylesheet"> <link href="{{ frontend_asset_url('flag_icons_css') }}" rel="stylesheet">
<link href="{{ static_url('styles.css') }}" rel="stylesheet"> <link href="{{ static_url('styles.css') }}" rel="stylesheet">
</head> </head>

View File

@@ -8,6 +8,7 @@
<link rel="shortcut icon" href="{{ static_url('favicon.svg') }}" type="image/svg+xml"> <link rel="shortcut icon" href="{{ static_url('favicon.svg') }}" type="image/svg+xml">
<link href="{{ bootstrap_theme_url('default') }}" rel="stylesheet"> <link href="{{ bootstrap_theme_url('default') }}" rel="stylesheet">
<link href="{{ frontend_asset_url('fontawesome_css') }}" rel="stylesheet"> <link href="{{ frontend_asset_url('fontawesome_css') }}" rel="stylesheet">
<link href="{{ frontend_asset_url('font_css') }}" rel="stylesheet">
<link href="{{ static_url('styles.css') }}" rel="stylesheet"> <link href="{{ static_url('styles.css') }}" rel="stylesheet">
</head> </head>
<body class="auth-page"> <body class="auth-page">

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
import re import re
from pathlib import Path from pathlib import Path
from urllib.parse import urljoin from urllib.parse import urljoin, urlparse
from urllib.request import Request, urlopen from urllib.request import Request, urlopen
ROOT = Path(__file__).resolve().parents[1] ROOT = Path(__file__).resolve().parents[1]
@@ -14,6 +14,33 @@ FONTAWESOME_VERSION = "6.5.2"
FLAG_ICONS_VERSION = "7.2.3" FLAG_ICONS_VERSION = "7.2.3"
SWAGGER_UI_VERSION = "5" SWAGGER_UI_VERSION = "5"
SOCKET_IO_VERSION = "4.7.5" SOCKET_IO_VERSION = "4.7.5"
GOOGLE_FONT_FAMILIES = (
"DM Sans",
"Figtree",
"Geist",
"IBM Plex Sans",
"Inter",
"JetBrains Mono",
"Lato",
"Manrope",
"Montserrat",
"Nunito Sans",
"Open Sans",
"Poppins",
"Roboto",
"Source Sans 3",
)
GOOGLE_FONT_WEIGHTS = "400;500;600;700;800"
def google_fonts_css_url() -> str:
families = "&".join(
f"family={name.replace(' ', '+')}:wght@{GOOGLE_FONT_WEIGHTS}"
for name in GOOGLE_FONT_FAMILIES
)
return f"https://fonts.googleapis.com/css2?{families}&display=swap"
BOOTSTRAP_THEMES = ( BOOTSTRAP_THEMES = (
"default", "default",
"flatly", "flatly",
@@ -43,6 +70,10 @@ STATIC_ASSETS = {
"local": f"{LIBS_STATIC_DIR}/flag-icons/{FLAG_ICONS_VERSION}/css/flag-icons.min.css", "local": f"{LIBS_STATIC_DIR}/flag-icons/{FLAG_ICONS_VERSION}/css/flag-icons.min.css",
"cdn": f"https://cdn.jsdelivr.net/gh/lipis/flag-icons@{FLAG_ICONS_VERSION}/css/flag-icons.min.css", "cdn": f"https://cdn.jsdelivr.net/gh/lipis/flag-icons@{FLAG_ICONS_VERSION}/css/flag-icons.min.css",
}, },
"font_css": {
"local": f"{LIBS_STATIC_DIR}/fonts/google-fonts.css",
"cdn": google_fonts_css_url(),
},
"swagger_css": { "swagger_css": {
"local": f"{LIBS_STATIC_DIR}/swagger-ui/{SWAGGER_UI_VERSION}/swagger-ui.css", "local": f"{LIBS_STATIC_DIR}/swagger-ui/{SWAGGER_UI_VERSION}/swagger-ui.css",
"cdn": f"https://cdn.jsdelivr.net/npm/swagger-ui-dist@{SWAGGER_UI_VERSION}/swagger-ui.css", "cdn": f"https://cdn.jsdelivr.net/npm/swagger-ui-dist@{SWAGGER_UI_VERSION}/swagger-ui.css",
@@ -53,6 +84,7 @@ STATIC_ASSETS = {
}, },
} }
URL_RE = re.compile(r"url\((['\"]?)(?!data:)(?!https?:)([^)'\"]+)\1\)") URL_RE = re.compile(r"url\((['\"]?)(?!data:)(?!https?:)([^)'\"]+)\1\)")
ANY_URL_RE = re.compile(r"url\((['\"]?)(?!data:)([^)'\"]+)\1\)")
def bootstrap_css_asset(theme: str) -> dict[str, str]: def bootstrap_css_asset(theme: str) -> dict[str, str]:
@@ -97,13 +129,49 @@ def download_css_with_assets(url: str, dest: Path) -> None:
download(asset_url, asset_dest) download(asset_url, asset_dest)
def download_google_fonts_css(url: str, dest: Path) -> None:
dest.parent.mkdir(parents=True, exist_ok=True)
req = Request(
url,
headers={
"User-Agent": "Mozilla/5.0 pyTorrent installer",
"Accept": "text/css,*/*;q=0.1",
},
)
with urlopen(req, timeout=60) as response:
css = response.read().decode("utf-8", errors="ignore")
if not css.strip():
raise RuntimeError(f"Empty response for {url}")
def replace_url(match: re.Match[str]) -> str:
quote = match.group(1) or ""
asset_url = match.group(2)
parsed = urlparse(asset_url)
if parsed.scheme not in {"http", "https"}:
return match.group(0)
filename = Path(parsed.path).name
if not filename:
return match.group(0)
asset_dest = dest.parent / "files" / filename
if not asset_dest.exists():
download(asset_url, asset_dest)
return f"url({quote}files/{filename}{quote})"
rewritten = ANY_URL_RE.sub(replace_url, css)
tmp = dest.with_suffix(dest.suffix + ".tmp")
tmp.write_text(rewritten, encoding="utf-8")
tmp.replace(dest)
print(f"OK {dest.relative_to(ROOT)}")
def main() -> None: def main() -> None:
items = list(STATIC_ASSETS.values()) items = list(STATIC_ASSETS.values())
items.extend(bootstrap_css_asset(theme) for theme in BOOTSTRAP_THEMES) items.extend(bootstrap_css_asset(theme) for theme in BOOTSTRAP_THEMES)
for item in items: for item in items:
url = item["cdn"] url = item["cdn"]
dest = ROOT / "pytorrent" / "static" / item["local"] dest = ROOT / "pytorrent" / "static" / item["local"]
if dest.suffix == ".css": if item.get("local") == STATIC_ASSETS["font_css"]["local"]:
download_google_fonts_css(url, dest)
elif dest.suffix == ".css":
download_css_with_assets(url, dest) download_css_with_assets(url, dest)
else: else:
download(url, dest) download(url, dest)