From 6ab330f583cfdf0374e6e9ee5e3741fc372b640d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Wed, 20 May 2026 13:47:07 +0200 Subject: [PATCH] new fonts --- INSTALL.md | 199 +++++++++++++++++++++++++- pytorrent/services/frontend_assets.py | 32 +++++ pytorrent/services/preferences.py | 15 +- pytorrent/static/styles.css | 55 ++++++- pytorrent/static/styles.original.css | 55 ++++++- pytorrent/templates/index.html | 1 + pytorrent/templates/login.html | 1 + scripts/download_frontend_libs.py | 72 +++++++++- 8 files changed, 423 insertions(+), 7 deletions(-) mode change 120000 => 100644 INSTALL.md diff --git a/INSTALL.md b/INSTALL.md deleted file mode 120000 index 6e7a5fb..0000000 --- a/INSTALL.md +++ /dev/null @@ -1 +0,0 @@ -scripts/INSTALL.md \ No newline at end of file diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000..d4ba1c0 --- /dev/null +++ b/INSTALL.md @@ -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. diff --git a/pytorrent/services/frontend_assets.py b/pytorrent/services/frontend_assets.py index 4a3fc51..ff66927 100644 --- a/pytorrent/services/frontend_assets.py +++ b/pytorrent/services/frontend_assets.py @@ -13,6 +13,33 @@ FLAG_ICONS_VERSION = "7.2.3" SWAGGER_UI_VERSION = "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 = ( "default", "flatly", @@ -39,6 +66,10 @@ STATIC_ASSETS = { "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", }, + "font_css": { + "local": f"{LIBS_STATIC_DIR}/fonts/google-fonts.css", + "cdn": google_fonts_css_url(), + }, "socket_io_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", @@ -87,6 +118,7 @@ def missing_offline_paths() -> list[Path]: 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/1x1", + LIBS_DIR / "fonts/files", ] for directory in required_dirs: if not directory.is_dir() or not any(directory.iterdir()): diff --git a/pytorrent/services/preferences.py b/pytorrent/services/preferences.py index 7f21020..11fe842 100644 --- a/pytorrent/services/preferences.py +++ b/pytorrent/services/preferences.py @@ -20,11 +20,22 @@ BOOTSTRAP_THEMES = { FONT_FAMILIES = { "default": "Theme default", - "adwaita-mono": "Adwaita Mono", + "system-ui": "System UI / Apple-like", + "figtree": "Figtree", "inter": "Inter", - "system-ui": "System UI", + "geist": "Geist", + "manrope": "Manrope", + "dm-sans": "DM Sans", "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", + "adwaita-mono": "Adwaita Mono", } # Note: Backend owns the recommended torrent table layout so frontend builds do not duplicate presets. diff --git a/pytorrent/static/styles.css b/pytorrent/static/styles.css index 1c2ec05..4015b58 100644 --- a/pytorrent/static/styles.css +++ b/pytorrent/static/styles.css @@ -33,13 +33,66 @@ html[data-app-font="inter"] { } html[data-app-font="system-ui"] { --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"] { --app-font-family: "Source Sans 3", "Source Sans Pro", system-ui, -apple-system, Segoe UI, 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"] { --app-font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, diff --git a/pytorrent/static/styles.original.css b/pytorrent/static/styles.original.css index 293e4d2..7e260ef 100644 --- a/pytorrent/static/styles.original.css +++ b/pytorrent/static/styles.original.css @@ -33,13 +33,66 @@ html[data-app-font="inter"] { } html[data-app-font="system-ui"] { --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"] { --app-font-family: "Source Sans 3", "Source Sans Pro", system-ui, -apple-system, Segoe UI, 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"] { --app-font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, diff --git a/pytorrent/templates/index.html b/pytorrent/templates/index.html index 7b95c1d..27d5c7e 100644 --- a/pytorrent/templates/index.html +++ b/pytorrent/templates/index.html @@ -8,6 +8,7 @@ + diff --git a/pytorrent/templates/login.html b/pytorrent/templates/login.html index eaeb07d..8146098 100644 --- a/pytorrent/templates/login.html +++ b/pytorrent/templates/login.html @@ -8,6 +8,7 @@ + diff --git a/scripts/download_frontend_libs.py b/scripts/download_frontend_libs.py index d1ef6cc..892ec1e 100755 --- a/scripts/download_frontend_libs.py +++ b/scripts/download_frontend_libs.py @@ -3,7 +3,7 @@ from __future__ import annotations import re from pathlib import Path -from urllib.parse import urljoin +from urllib.parse import urljoin, urlparse from urllib.request import Request, urlopen ROOT = Path(__file__).resolve().parents[1] @@ -14,6 +14,33 @@ FONTAWESOME_VERSION = "6.5.2" FLAG_ICONS_VERSION = "7.2.3" SWAGGER_UI_VERSION = "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 = ( "default", "flatly", @@ -43,6 +70,10 @@ STATIC_ASSETS = { "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", }, + "font_css": { + "local": f"{LIBS_STATIC_DIR}/fonts/google-fonts.css", + "cdn": google_fonts_css_url(), + }, "swagger_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", @@ -53,6 +84,7 @@ STATIC_ASSETS = { }, } 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]: @@ -97,13 +129,49 @@ def download_css_with_assets(url: str, dest: Path) -> None: 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: items = list(STATIC_ASSETS.values()) items.extend(bootstrap_css_asset(theme) for theme in BOOTSTRAP_THEMES) for item in items: url = item["cdn"] 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) else: download(url, dest)