new fonts
This commit is contained in:
@@ -1 +0,0 @@
|
||||
scripts/INSTALL.md
|
||||
198
INSTALL.md
Normal file
198
INSTALL.md
Normal 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.
|
||||
@@ -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()):
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<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 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="{{ static_url('styles.css') }}" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<link rel="shortcut icon" href="{{ static_url('favicon.svg') }}" type="image/svg+xml">
|
||||
<link href="{{ bootstrap_theme_url('default') }}" 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">
|
||||
</head>
|
||||
<body class="auth-page">
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user