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"
|
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()):
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user