Compare commits
71 Commits
3916954ef8
...
1.5.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aec6535391 | ||
|
|
0f4d0eea34 | ||
|
|
35022d434b | ||
|
|
7db43cf61e | ||
|
|
d7598a37c6 | ||
|
|
c57d76c74c | ||
|
|
dcc3654131 | ||
|
|
32f1be9599 | ||
|
|
e5fc607638 | ||
|
|
f72c64dcb3 | ||
|
|
3659aec4b2 | ||
|
|
4a501da27b | ||
|
|
025b88926b | ||
|
|
f5943cd636 | ||
|
|
03b9b4a598 | ||
|
|
ea8a66d449 | ||
|
|
991a35ef1a | ||
|
|
e20a9f4b32 | ||
|
|
1b6f142cb5 | ||
|
|
1daa365df9 | ||
|
|
afb38d628b | ||
|
|
53c88a79ac | ||
|
|
a43ddf79c3 | ||
|
|
699dbf6fac | ||
|
|
317699ff58 | ||
|
|
55446f7d9c | ||
|
|
88c78efcd9 | ||
|
|
fb3ac4291f | ||
|
|
15f14cac11 | ||
|
|
018d7c34fc | ||
|
|
1aee423120 | ||
|
|
a6b2f2a942 | ||
|
|
e33024db86 | ||
|
|
4ad15f0849 | ||
|
|
7bad0359af | ||
|
|
59a7c9f15a | ||
|
|
bc6d692f0a | ||
|
|
63c37c6334 | ||
|
|
fa2c06972e | ||
|
|
e4e6387b2a | ||
|
|
f2be9dca8d | ||
|
|
52dd425583 | ||
|
|
286399d79a | ||
|
|
4ab1a2ed22 | ||
|
|
459c0e21af | ||
|
|
98ef64211b | ||
|
|
0b4bb65546 | ||
|
|
47624f5019 | ||
|
|
2b9d7821c0 | ||
|
|
45f5108717 | ||
|
|
a7fe8b867e | ||
|
|
78214df9cc | ||
|
|
4245d5a582 | ||
|
|
9b5a387169 | ||
|
|
9377d5ce28 | ||
|
|
7489307db8 | ||
|
|
d41314e81d | ||
|
|
a1123d5451 | ||
|
|
93cf719454 | ||
|
|
0dc8f6c582 | ||
|
|
4b0219b85a | ||
|
|
8edbb25c16 | ||
|
|
49a24f0b68 | ||
|
|
8af3c8e2b3 | ||
|
|
3c321a202c | ||
|
|
1b7d8bc0dc | ||
|
|
23ccea538f | ||
|
|
6a54c05fbb | ||
|
|
006d3a1833 | ||
|
|
42fe066e1a | ||
|
|
ef542ec821 |
@@ -2,7 +2,7 @@ Package: fastapi-dls
|
||||
Version: 0.0
|
||||
Architecture: all
|
||||
Maintainer: Oscar Krause oscar.krause@collinwebdesigns.de
|
||||
Depends: python3, python3-fastapi, python3-uvicorn, python3-dotenv, python3-dateutil, python3-jose, python3-sqlalchemy, python3-pycryptodome, python3-markdown, python3-jinja2, uvicorn, openssl
|
||||
Depends: python3, python3-fastapi, python3-uvicorn, python3-dotenv, python3-dateutil, python3-josepy, python3-sqlalchemy, python3-pycryptodome, python3-markdown, uvicorn, openssl
|
||||
Recommends: curl
|
||||
Installed-Size: 10240
|
||||
Homepage: https://git.collinwebdesigns.de/oscar.krause/fastapi-dls
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
# https://packages.ubuntu.com
|
||||
fastapi==0.91.0
|
||||
uvicorn[standard]==0.15.0
|
||||
python-jose[pycryptodome]==3.3.0
|
||||
pycryptodome==3.11.0
|
||||
python-dateutil==2.8.2
|
||||
sqlalchemy==1.4.46
|
||||
markdown==3.4.3
|
||||
python-dotenv==0.21.0
|
||||
jinja2==3.1.2
|
||||
@@ -1,10 +0,0 @@
|
||||
# https://packages.ubuntu.com
|
||||
fastapi==0.101.0
|
||||
uvicorn[standard]==0.23.2
|
||||
python-jose[pycryptodome]==3.3.0
|
||||
pycryptodome==3.11.0
|
||||
python-dateutil==2.8.2
|
||||
sqlalchemy==1.4.47
|
||||
markdown==3.4.4
|
||||
python-dotenv==1.0.0
|
||||
jinja2==3.1.2
|
||||
10
.DEBIAN/requirements-ubuntu-24.10.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
# https://packages.ubuntu.com
|
||||
fastapi==0.110.3
|
||||
uvicorn[standard]==0.30.3
|
||||
python-jose[pycryptodome]==3.3.0
|
||||
pycryptodome==3.20.0
|
||||
python-dateutil==2.9.0
|
||||
sqlalchemy==2.0.32
|
||||
markdown==3.6
|
||||
python-dotenv==1.0.1
|
||||
jinja2==3.1.3
|
||||
@@ -8,7 +8,7 @@ pkgdesc='NVIDIA DLS server implementation with FastAPI'
|
||||
arch=('any')
|
||||
url='https://git.collinwebdesigns.de/oscar.krause/fastapi-dls'
|
||||
license=('MIT')
|
||||
depends=('python' 'python-jose' 'python-starlette' 'python-httpx' 'python-fastapi' 'python-dotenv' 'python-dateutil' 'python-sqlalchemy' 'python-pycryptodome' 'python-jinja' 'uvicorn' 'python-markdown' 'openssl')
|
||||
depends=('python' 'python-jose' 'python-starlette' 'python-httpx' 'python-fastapi' 'python-dotenv' 'python-dateutil' 'python-sqlalchemy' 'python-pycryptodome' 'uvicorn' 'python-markdown' 'openssl')
|
||||
provider=("$pkgname")
|
||||
install="$pkgname.install"
|
||||
backup=('etc/default/fastapi-dls')
|
||||
@@ -37,52 +37,17 @@ check() {
|
||||
}
|
||||
|
||||
package() {
|
||||
# create directories
|
||||
install -d "$pkgdir/usr/share/doc/$pkgname"
|
||||
install -d "$pkgdir/var/lib/$pkgname/cert"
|
||||
|
||||
# copy docs & static files
|
||||
cp -r "$srcdir/$pkgname/doc"/* "$pkgdir/usr/share/doc/$pkgname/"
|
||||
install -Dm644 "$srcdir/$pkgname/README.md" "$pkgdir/usr/share/doc/$pkgname/README.md"
|
||||
install -Dm644 "$srcdir/$pkgname/version.env" "$pkgdir/usr/share/doc/$pkgname/version.env"
|
||||
sed -i "s/README.md/\/usr\/share\/doc\/$pkgname\/README.md/g" "$srcdir/$pkgname/app/main.py"
|
||||
|
||||
# copy main app python files
|
||||
sed -i "s/README.md/\/usr\/share\/doc\/$pkgname\/README.md/g" "$srcdir/$pkgname/app/main.py"
|
||||
sed -i "s/join(dirname(__file__), 'cert\//join('\/var\/lib\/$pkgname', 'cert\//g" "$srcdir/$pkgname/app/main.py"
|
||||
install -Dm755 "$srcdir/$pkgname/app/main.py" "$pkgdir/opt/$pkgname/main.py"
|
||||
install -Dm755 "$srcdir/$pkgname/app/orm.py" "$pkgdir/opt/$pkgname/orm.py"
|
||||
install -Dm755 "$srcdir/$pkgname/app/util.py" "$pkgdir/opt/$pkgname/util.py"
|
||||
|
||||
# copy static asset files
|
||||
install -Dm755 "$srcdir/$pkgname/app/static/assets/css/bootstrap.min.css" "$pkgdir/opt/$pkgname/static/assets/css/bootstrap.min.css"
|
||||
install -Dm755 "$srcdir/$pkgname/app/static/assets/css/bootstrap-icons.min.css" "$pkgdir/opt/$pkgname/static/assets/css/bootstrap-icons.min.css"
|
||||
install -Dm755 "$srcdir/$pkgname/app/static/assets/css/custom.css" "$pkgdir/opt/$pkgname/static/assets/css/custom.css"
|
||||
install -Dm755 "$srcdir/$pkgname/app/static/assets/css/dashboard.css" "$pkgdir/opt/$pkgname/static/assets/css/dashboard.css"
|
||||
install -Dm755 "$srcdir/$pkgname/app/static/assets/fonts/bootstrap-icons.woff" "$pkgdir/opt/$pkgname/static/assets/fonts/bootstrap-icons.woff"
|
||||
install -Dm755 "$srcdir/$pkgname/app/static/assets/fonts/bootstrap-icons.woff2" "$pkgdir/opt/$pkgname/static/assets/fonts/bootstrap-icons.woff2"
|
||||
install -Dm755 "$srcdir/$pkgname/app/static/assets/img/favicons/android-chrome-192x192.png" "$pkgdir/opt/$pkgname/static/assets/img/favicons/android-chrome-192x192.png"
|
||||
install -Dm755 "$srcdir/$pkgname/app/static/assets/img/favicons/android-chrome-512x512.png" "$pkgdir/opt/$pkgname/static/assets/img/favicons/android-chrome-512x512.png"
|
||||
install -Dm755 "$srcdir/$pkgname/app/static/assets/img/favicons/apple-touch-icon.png" "$pkgdir/opt/$pkgname/static/assets/img/favicons/apple-touch-icon.png"
|
||||
install -Dm755 "$srcdir/$pkgname/app/static/assets/img/favicons/favicon.ico" "$pkgdir/opt/$pkgname/static/assets/img/favicons/favicon.ico"
|
||||
install -Dm755 "$srcdir/$pkgname/app/static/assets/img/favicons/favicon-16x16.png" "$pkgdir/opt/$pkgname/static/assets/img/favicons/favicon-16x16.png"
|
||||
install -Dm755 "$srcdir/$pkgname/app/static/assets/img/favicons/favicon-32x32.png" "$pkgdir/opt/$pkgname/static/assets/img/favicons/favicon-32x32.png"
|
||||
install -Dm755 "$srcdir/$pkgname/app/static/assets/img/favicons/manifest.json" "$pkgdir/opt/$pkgname/static/assets/img/favicons/manifest.json"
|
||||
install -Dm755 "$srcdir/$pkgname/app/static/assets/img/logo.png" "$pkgdir/opt/$pkgname/static/assets/img/logo.png"
|
||||
install -Dm755 "$srcdir/$pkgname/app/static/assets/js/bootstrap.min.js" "$pkgdir/opt/$pkgname/static/assets/js/bootstrap.min.js"
|
||||
install -Dm755 "$srcdir/$pkgname/app/static/assets/js/helper.js" "$pkgdir/opt/$pkgname/static/assets/js/helper.js"
|
||||
install -Dm755 "$srcdir/$pkgname/app/static/assets/js/popper.min.js" "$pkgdir/opt/$pkgname/static/assets/js/popper.min.js"
|
||||
install -Dm755 "$srcdir/$pkgname/app/templates/components/navbar.html" "$pkgdir/opt/$pkgname/templates/components/navbar.html"
|
||||
install -Dm755 "$srcdir/$pkgname/app/templates/components/sidebar.html" "$pkgdir/opt/$pkgname/templates/components/sidebar.html"
|
||||
install -Dm755 "$srcdir/$pkgname/app/templates/layouts/base.html" "$pkgdir/opt/$pkgname/templates/layouts/base.html"
|
||||
install -Dm755 "$srcdir/$pkgname/app/templates/layouts/bootstrap.html" "$pkgdir/opt/$pkgname/templates/layouts/bootstrap.html"
|
||||
install -Dm755 "$srcdir/$pkgname/app/templates/layouts/bootstrap-dashboard.html" "$pkgdir/opt/$pkgname/templates/layouts/bootstrap-dashboard.html"
|
||||
install -Dm755 "$srcdir/$pkgname/app/templates/views/dashboard.html" "$pkgdir/opt/$pkgname/templates/views/dashboard.html"
|
||||
install -Dm755 "$srcdir/$pkgname/app/templates/views/dashboard_leases.html" "$pkgdir/opt/$pkgname/templates/views/dashboard_leases.html"
|
||||
install -Dm755 "$srcdir/$pkgname/app/templates/views/dashboard_origins.html" "$pkgdir/opt/$pkgname/templates/views/dashboard_origins.html"
|
||||
install -Dm755 "$srcdir/$pkgname/app/templates/views/dashboard_readme.html" "$pkgdir/opt/$pkgname/templates/views/dashboard_readme.html"
|
||||
install -Dm755 "$srcdir/$pkgname/app/templates/views/index.html" "$pkgdir/opt/$pkgname/templates/views/index.html"
|
||||
|
||||
# copy service files
|
||||
install -Dm644 "$srcdir/$pkgname.default" "$pkgdir/etc/default/$pkgname"
|
||||
install -Dm644 "$srcdir/$pkgname.service" "$pkgdir/usr/lib/systemd/system/$pkgname.service"
|
||||
install -Dm644 "$srcdir/$pkgname.tmpfiles" "$pkgdir/usr/lib/tmpfiles.d/$pkgname.conf"
|
||||
|
||||
@@ -127,7 +127,7 @@ build:pacman:
|
||||
- "*.pkg.tar.zst"
|
||||
|
||||
test:
|
||||
image: $IMAGE
|
||||
image: python:3.12-slim-bookworm
|
||||
stage: test
|
||||
interruptible: true
|
||||
rules:
|
||||
@@ -142,14 +142,16 @@ test:
|
||||
DATABASE: sqlite:///../app/db.sqlite
|
||||
parallel:
|
||||
matrix:
|
||||
- IMAGE: [ 'python:3.11-slim-bookworm', 'python:3.12-slim-bullseye' ]
|
||||
REQUIREMENTS:
|
||||
- requirements.txt
|
||||
- .DEBIAN/requirements-bookworm-12.txt
|
||||
- .DEBIAN/requirements-ubuntu-23.10.txt
|
||||
- .DEBIAN/requirements-ubuntu-24.04.txt
|
||||
- REQUIREMENTS:
|
||||
- 'requirements.txt'
|
||||
# - '.DEBIAN/requirements-bookworm-12.txt'
|
||||
# - '.DEBIAN/requirements-ubuntu-24.04.txt'
|
||||
# - '.DEBIAN/requirements-ubuntu-24.10.txt'
|
||||
before_script:
|
||||
- apt-get update && apt-get install -y python3-dev gcc
|
||||
- apt-get update && apt-get install -y python3-dev python3-pip python3-venv gcc
|
||||
- python3 -m venv venv
|
||||
- source venv/bin/activate
|
||||
- pip install --upgrade pip
|
||||
- pip install -r $REQUIREMENTS
|
||||
- pip install pytest httpx
|
||||
- mkdir -p app/cert
|
||||
@@ -163,7 +165,7 @@ test:
|
||||
dotenv: version.env
|
||||
junit: ['**/report.xml']
|
||||
|
||||
.test:linux:
|
||||
.test:apt:
|
||||
stage: test
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
|
||||
@@ -202,15 +204,17 @@ test:
|
||||
- apt-get purge -qq -y fastapi-dls
|
||||
- apt-get autoremove -qq -y && apt-get clean -qq
|
||||
|
||||
test:debian:
|
||||
extends: .test:linux
|
||||
image: debian:bookworm-slim
|
||||
test:apt:
|
||||
extends: .test:apt
|
||||
image: $IMAGE
|
||||
parallel:
|
||||
matrix:
|
||||
- IMAGE:
|
||||
- debian:bookworm-slim # EOL: June 06, 2026
|
||||
- ubuntu:24.04 # EOL: April 2036
|
||||
- ubuntu:24.10
|
||||
|
||||
test:ubuntu:
|
||||
extends: .test:linux
|
||||
image: ubuntu:24.04
|
||||
|
||||
test:archlinux:
|
||||
test:pacman:archlinux:
|
||||
image: archlinux:base
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
|
||||
@@ -251,7 +255,7 @@ semgrep-sast:
|
||||
|
||||
test_coverage:
|
||||
# extends: test
|
||||
image: python:3.11-slim-bookworm
|
||||
image: python:3.12-slim-bookworm
|
||||
allow_failure: true
|
||||
stage: test
|
||||
rules:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM python:3.11-alpine
|
||||
FROM python:3.12-alpine
|
||||
|
||||
ARG VERSION
|
||||
ARG COMMIT=""
|
||||
@@ -10,7 +10,7 @@ RUN apk update \
|
||||
&& apk add --no-cache --virtual build-deps gcc g++ python3-dev musl-dev pkgconfig \
|
||||
&& apk add --no-cache curl postgresql postgresql-dev mariadb-dev sqlite-dev \
|
||||
&& pip install --no-cache-dir --upgrade uvicorn \
|
||||
&& pip install --no-cache-dir psycopg2==2.9.9 mysqlclient==2.2.4 pysqlite3==0.5.2 \
|
||||
&& pip install --no-cache-dir psycopg2==2.9.10 mysqlclient==2.2.7 pysqlite3==0.5.4 \
|
||||
&& pip install --no-cache-dir -r /tmp/requirements.txt \
|
||||
&& apk del build-deps
|
||||
|
||||
|
||||
143
README.md
@@ -2,7 +2,8 @@
|
||||
|
||||
Minimal Delegated License Service (DLS).
|
||||
|
||||
Compatibility tested with official NLS 2.0.1, 2.1.0, 3.1.0. For Driver compatibility see [here](#setup-client).
|
||||
Compatibility tested with official NLS 2.0.1, 2.1.0, 3.1.0, 3.3.1, 3.4.0. For Driver compatibility
|
||||
see [compatibility matrix](#vgpu-software-compatibility-matrix).
|
||||
|
||||
This service can be used without internet connection.
|
||||
Only the clients need a connection to this service on configured port.
|
||||
@@ -22,6 +23,7 @@ Only the clients need a connection to this service on configured port.
|
||||
* [NVIDIA vGPU Guide](https://gitlab.com/polloloco/vgpu-proxmox) - This document serves as a guide to install NVIDIA vGPU host drivers on the latest Proxmox VE version
|
||||
* [vgpu_unlock](https://github.com/DualCoder/vgpu_unlock) - Unlock vGPU functionality for consumer-grade Nvidia GPUs.
|
||||
* [vGPU_Unlock Wiki](https://docs.google.com/document/d/1pzrWJ9h-zANCtyqRgS7Vzla0Y8Ea2-5z2HEi4X75d2Q) - Guide for `vgpu_unlock`
|
||||
* [Proxmox 8 vGPU in VMs and LXC Containers](https://medium.com/@dionisievldulrincz/proxmox-8-vgpu-in-vms-and-lxc-containers-4146400207a3) - Install *Merged Drivers* for using in Proxmox VMs and LXCs
|
||||
* [Proxmox All-In-One Installer Script](https://wvthoog.nl/proxmox-vgpu-v3/) - Also known as `proxmox-installer.sh`
|
||||
|
||||
---
|
||||
@@ -42,6 +44,9 @@ Tested with Ubuntu 22.10 (EOL!) (from Proxmox templates), actually its consuming
|
||||
|
||||
- Make sure your timezone is set correct on you fastapi-dls server and your client
|
||||
|
||||
This guide does not show how to install vGPU host drivers! Look at the official documentation packed with the driver
|
||||
releases.
|
||||
|
||||
## Docker
|
||||
|
||||
Docker-Images are available here for Intel (x86), AMD (amd64) and ARM (arm64):
|
||||
@@ -326,11 +331,12 @@ Packages are available here:
|
||||
|
||||
Successful tested with:
|
||||
|
||||
- Debian 12 (Bookworm) (EOL: tba.)
|
||||
- Ubuntu 22.10 (Kinetic Kudu) (EOL: July 20, 2023)
|
||||
- Ubuntu 23.04 (Lunar Lobster) (EOL: January 2024)
|
||||
- Ubuntu 23.10 (Mantic Minotaur) (EOL: July 2024)
|
||||
- Ubuntu 24.04 (Noble Numbat) (EOL: April 2036)
|
||||
- **Debian 12 (Bookworm)** (EOL: June 06, 2026)
|
||||
- *Ubuntu 22.10 (Kinetic Kudu)* (EOL: July 20, 2023)
|
||||
- *Ubuntu 23.04 (Lunar Lobster)* (EOL: January 2024)
|
||||
- *Ubuntu 23.10 (Mantic Minotaur)* (EOL: July 2024)
|
||||
- **Ubuntu 24.04 (Noble Numbat)** (EOL: April 2036)
|
||||
- *Ubuntu 24.10 (Oracular Oriole)* (EOL: tba.)
|
||||
|
||||
Not working with:
|
||||
|
||||
@@ -388,6 +394,10 @@ Now you have to edit `/etc/default/fastapi-dls` as needed.
|
||||
|
||||
Continue [here](#unraid-guest) for docker guest setup.
|
||||
|
||||
## NixOS
|
||||
|
||||
Tanks to [@mrzenc](https://github.com/mrzenc) for [fastapi-dls-nixos](https://github.com/mrzenc/fastapi-dls-nixos).
|
||||
|
||||
## Let's Encrypt Certificate (optional)
|
||||
|
||||
If you're using installation via docker, you can use `traefik`. Please refer to their documentation.
|
||||
@@ -406,21 +416,21 @@ After first success you have to replace `--issue` with `--renew`.
|
||||
|
||||
# Configuration
|
||||
|
||||
| Variable | Default | Usage |
|
||||
|------------------------|----------------------------------------|------------------------------------------------------------------------------------------------------|
|
||||
| `DEBUG` | `false` | Toggles `fastapi` debug mode |
|
||||
| `DLS_URL` | `localhost` | Used in client-token to tell guest driver where dls instance is reachable |
|
||||
| `DLS_PORT` | `443` | Used in client-token to tell guest driver where dls instance is reachable |
|
||||
| `TOKEN_EXPIRE_DAYS` | `1` | Client auth-token validity (used for authenticate client against api, **not `.tok` file!**) |
|
||||
| `LEASE_EXPIRE_DAYS` | `90` | Lease time in days |
|
||||
| `LEASE_RENEWAL_PERIOD` | `0.15` | The percentage of the lease period that must elapse before a licensed client can renew a license \*1 |
|
||||
| `DATABASE` | `sqlite:///db.sqlite` | See [official SQLAlchemy docs](https://docs.sqlalchemy.org/en/14/core/engines.html) |
|
||||
| `CORS_ORIGINS` | `https://{DLS_URL}` | Sets `Access-Control-Allow-Origin` header (comma separated string) \*2 |
|
||||
| `SITE_KEY_XID` | `00000000-0000-0000-0000-000000000000` | Site identification uuid |
|
||||
| `INSTANCE_REF` | `10000000-0000-0000-0000-000000000001` | Instance identification uuid |
|
||||
| `ALLOTMENT_REF` | `20000000-0000-0000-0000-000000000001` | Allotment identification uuid |
|
||||
| `INSTANCE_KEY_RSA` | `<app-dir>/cert/instance.private.pem` | Site-wide private RSA key for singing JWTs \*3 |
|
||||
| `INSTANCE_KEY_PUB` | `<app-dir>/cert/instance.public.pem` | Site-wide public key \*3 |
|
||||
| Variable | Default | Usage |
|
||||
|--------------------------|----------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `DEBUG` | `false` | Toggles `fastapi` debug mode |
|
||||
| `DLS_URL` | `localhost` | Used in client-token to tell guest driver where dls instance is reachable |
|
||||
| `DLS_PORT` | `443` | Used in client-token to tell guest driver where dls instance is reachable |
|
||||
| `TOKEN_EXPIRE_DAYS` | `1` | Client auth-token validity (used for authenticate client against api, **not `.tok` file!**) |
|
||||
| `LEASE_EXPIRE_DAYS` | `90` | Lease time in days |
|
||||
| `LEASE_RENEWAL_PERIOD` | `0.15` | The percentage of the lease period that must elapse before a licensed client can renew a license \*1 |
|
||||
| `DATABASE` | `sqlite:///db.sqlite` | See [official SQLAlchemy docs](https://docs.sqlalchemy.org/en/14/core/engines.html) |
|
||||
| `CORS_ORIGINS` | `https://{DLS_URL}` | Sets `Access-Control-Allow-Origin` header (comma separated string) \*2 |
|
||||
| `SITE_KEY_XID` | `00000000-0000-0000-0000-000000000000` | Site identification uuid |
|
||||
| `INSTANCE_REF` | `10000000-0000-0000-0000-000000000001` | Instance identification uuid |
|
||||
| `ALLOTMENT_REF` | `20000000-0000-0000-0000-000000000001` | Allotment identification uuid |
|
||||
| `INSTANCE_KEY_RSA` | `<app-dir>/cert/instance.private.pem` | Site-wide private RSA key for singing JWTs \*3 |
|
||||
| `INSTANCE_KEY_PUB` | `<app-dir>/cert/instance.public.pem` | Site-wide public key \*3 |
|
||||
|
||||
\*1 For example, if the lease period is one day and the renewal period is 20%, the client attempts to renew its license
|
||||
every 4.8 hours. If network connectivity is lost, the loss of connectivity is detected during license renewal and the
|
||||
@@ -434,32 +444,8 @@ client has 19.2 hours in which to re-establish connectivity before its license e
|
||||
|
||||
**The token file has to be copied! It's not enough to C&P file contents, because there can be special characters.**
|
||||
|
||||
Successfully tested with this package versions:
|
||||
|
||||
| vGPU Suftware | Driver Branch | Linux vGPU Manager | Linux Driver | Windows Driver | Release Date | EOL Date |
|
||||
|:-------------:|:-------------:|--------------------|--------------|----------------|--------------:|--------------:|
|
||||
| `17.2` | R550 | `550.90.05` | `550.90.07` | `552.55` | June 2024 | February 2025 |
|
||||
| `17.1` | R550 | `550.54.16` | `550.54.15` | `551.78` | March 2024 | |
|
||||
| `17.0` | R550 | `550.54.10` | `550.54.14` | `551.61` | February 2024 | |
|
||||
| `16.6` | R535 | `535.183.04` | `535.183.01` | `538.67` | June 2024 | July 2026 |
|
||||
| `16.5` | R535 | `535.161.05` | `535.161.08` | `538.46` | February 2024 | |
|
||||
| `16.4` | R535 | `535.161.05` | `535.161.07` | `538.33` | February 2024 | |
|
||||
| `16.3` | R535 | `535.154.02` | `535.154.05` | `538.15` | January 2024 | |
|
||||
| `16.2` | R535 | `535.129.03` | `535.129.03` | `537.70` | October 2023 | |
|
||||
| `16.1` | R535 | `535.104.06` | `535.104.05` | `537.13` | August 2023 | |
|
||||
| `16.0` | R535 | `535.54.06` | `535.54.03` | `536.22` | July 2023 | |
|
||||
| `15.4` | R525 | `525.147.01` | `525.147.05` | `529.19` | June 2023 | October 2023 |
|
||||
| `15.3` | R525 | `525.125.03` | `525.125.06` | `529.11` | June 2023 | |
|
||||
| `15.2` | R525 | `525.105.14` | `525.105.17` | `528.89` | March 2023 | |
|
||||
| `15.1` | R525 | `525.85.07` | `525.85.05` | `528.24` | January 2023 | |
|
||||
| `15.0` | R525 | `525.60.12` | `525.60.13` | `527.41` | December 2022 | |
|
||||
| `14.4` | R510 | `510.108.03` | `510.108.03` | `514.08` | December 2022 | February 2023 |
|
||||
| `14.3` | R510 | `510.108.03` | `510.108.03` | `513.91` | November 2022 | |
|
||||
|
||||
- https://docs.nvidia.com/grid/index.html
|
||||
- https://docs.nvidia.com/grid/gpus-supported-by-vgpu.html
|
||||
|
||||
*To get the latest drivers, visit Nvidia or search in Discord-Channel `GPU Unlocking` (Server-ID: `829786927829745685`) on channel `licensing` `biggerthanshit`
|
||||
This guide does not show how to install vGPU guest drivers! Look at the official documentation packed with the driver
|
||||
releases.
|
||||
|
||||
## Linux
|
||||
|
||||
@@ -535,33 +521,32 @@ Done. For more information check [troubleshoot section](#troubleshoot).
|
||||
8. Set schedule to `At First Array Start Only`
|
||||
9. Click on Apply
|
||||
|
||||
|
||||
# Endpoints
|
||||
# API Endpoints
|
||||
|
||||
<details>
|
||||
<summary>show</summary>
|
||||
|
||||
### `GET /`
|
||||
**`GET /`**
|
||||
|
||||
Redirect to `/-/readme`.
|
||||
|
||||
### `GET /-/health`
|
||||
**`GET /-/health`**
|
||||
|
||||
Status endpoint, used for *healthcheck*.
|
||||
|
||||
### `GET /-/config`
|
||||
**`GET /-/config`**
|
||||
|
||||
Shows current runtime environment variables and their values.
|
||||
|
||||
### `GET /-/readme`
|
||||
**`GET /-/readme`**
|
||||
|
||||
HTML rendered README.md.
|
||||
|
||||
### `GET /-/manage`
|
||||
**`GET /-/manage`**
|
||||
|
||||
Shows a very basic UI to delete origins or leases.
|
||||
|
||||
### `GET /-/origins?leases=false`
|
||||
**`GET /-/origins?leases=false`**
|
||||
|
||||
List registered origins.
|
||||
|
||||
@@ -569,11 +554,11 @@ List registered origins.
|
||||
|-----------------|---------|--------------------------------------|
|
||||
| `leases` | `false` | Include referenced leases per origin |
|
||||
|
||||
### `DELETE /-/origins`
|
||||
**`DELETE /-/origins`**
|
||||
|
||||
Deletes all origins and their leases.
|
||||
|
||||
### `GET /-/leases?origin=false`
|
||||
**`GET /-/leases?origin=false`**
|
||||
|
||||
List current leases.
|
||||
|
||||
@@ -581,15 +566,15 @@ List current leases.
|
||||
|-----------------|---------|-------------------------------------|
|
||||
| `origin` | `false` | Include referenced origin per lease |
|
||||
|
||||
### `DELETE /-/lease/{lease_ref}`
|
||||
**`DELETE /-/lease/{lease_ref}`**
|
||||
|
||||
Deletes an lease.
|
||||
|
||||
### `GET /-/client-token`
|
||||
**`GET /-/client-token`**
|
||||
|
||||
Generate client token, (see [installation](#installation)).
|
||||
|
||||
### Others
|
||||
**Others**
|
||||
|
||||
There are many other internal api endpoints for handling authentication and lease process.
|
||||
</details>
|
||||
@@ -616,9 +601,9 @@ Logs are available in `C:\Users\Public\Documents\Nvidia\LoggingLog.NVDisplay.Con
|
||||
|
||||
## Linux
|
||||
|
||||
### `uvicorn.error:Invalid HTTP request received.`
|
||||
### Invalid HTTP request
|
||||
|
||||
This message can be ignored.
|
||||
This error message: `uvicorn.error:Invalid HTTP request received.` can be ignored.
|
||||
|
||||
- Ref. https://github.com/encode/uvicorn/issues/441
|
||||
|
||||
@@ -744,11 +729,42 @@ The error message can safely be ignored (since we have no license limitation :P)
|
||||
|
||||
</details>
|
||||
|
||||
# vGPU Software Compatibility Matrix
|
||||
|
||||
Successfully tested with this package versions.
|
||||
|
||||
| vGPU Suftware | Driver Branch | Linux vGPU Manager | Linux Driver | Windows Driver | Release Date | EOL Date |
|
||||
|:-------------:|:-------------:|--------------------|--------------|----------------|--------------:|--------------:|
|
||||
| `17.5` | R550 | `550.144.02` | `550.144.03` | `553.62` | January 2025 | June 2025 |
|
||||
| `17.4` | R550 | `550.127.06` | `550.127.05` | `553.24` | October 2024 | |
|
||||
| `17.3` | R550 | `550.90.05` | `550.90.07` | `552.74` | July 2024 | |
|
||||
| `17.2` | R550 | `550.90.05` | `550.90.07` | `552.55` | June 2024 | |
|
||||
| `17.1` | R550 | `550.54.16` | `550.54.15` | `551.78` | March 2024 | |
|
||||
| `17.0` | R550 | `550.54.10` | `550.54.14` | `551.61` | February 2024 | |
|
||||
| `16.9` | R535 | `535.230.02` | `535.216.01` | `539.19` | October 2024 | July 2026 |
|
||||
| `16.8` | R535 | `535.216.01` | `535.216.01` | `538.95` | October 2024 | |
|
||||
| `16.7` | R535 | `535.183.04` | `535.183.06` | `538.78` | July 2024 | |
|
||||
| `16.6` | R535 | `535.183.04` | `535.183.01` | `538.67` | June 2024 | |
|
||||
| `16.5` | R535 | `535.161.05` | `535.161.08` | `538.46` | February 2024 | |
|
||||
| `16.4` | R535 | `535.161.05` | `535.161.07` | `538.33` | February 2024 | |
|
||||
| `16.3` | R535 | `535.154.02` | `535.154.05` | `538.15` | January 2024 | |
|
||||
| `16.2` | R535 | `535.129.03` | `535.129.03` | `537.70` | October 2023 | |
|
||||
| `16.1` | R535 | `535.104.06` | `535.104.05` | `537.13` | August 2023 | |
|
||||
| `16.0` | R535 | `535.54.06` | `535.54.03` | `536.22` | July 2023 | |
|
||||
| `15.4` | R525 | `525.147.01` | `525.147.05` | `529.19` | June 2023 | December 2023 |
|
||||
| `14.4` | R510 | `510.108.03` | `510.108.03` | `514.08` | December 2022 | February 2023 |
|
||||
|
||||
- https://docs.nvidia.com/grid/index.html
|
||||
- https://docs.nvidia.com/grid/gpus-supported-by-vgpu.html
|
||||
|
||||
*To get the latest drivers, visit Nvidia or search in Discord-Channel `GPU Unlocking` (Server-ID: `829786927829745685`)
|
||||
on channel `licensing`
|
||||
|
||||
# Credits
|
||||
|
||||
Thanks to vGPU community and all who uses this project and report bugs.
|
||||
|
||||
Special thanks to
|
||||
Special thanks to:
|
||||
|
||||
- @samicrusader who created build file for **ArchLinux**
|
||||
- @cyrus who wrote the section for **openSUSE**
|
||||
@@ -757,5 +773,6 @@ Special thanks to
|
||||
- @DualCoder who creates the `vgpu_unlock` functionality [vgpu_unlock](https://github.com/DualCoder/vgpu_unlock)
|
||||
- Krutav Shah who wrote the [vGPU_Unlock Wiki](https://docs.google.com/document/d/1pzrWJ9h-zANCtyqRgS7Vzla0Y8Ea2-5z2HEi4X75d2Q/)
|
||||
- Wim van 't Hoog for the [Proxmox All-In-One Installer Script](https://wvthoog.nl/proxmox-vgpu-v3/)
|
||||
- @mrzenc who wrote [fastapi-dls-nixos](https://github.com/mrzenc/fastapi-dls-nixos)
|
||||
|
||||
And thanks to all people who contributed to all these libraries!
|
||||
|
||||
175
app/main.py
@@ -2,7 +2,7 @@ import logging
|
||||
from base64 import b64encode as b64enc
|
||||
from calendar import timegm
|
||||
from contextlib import asynccontextmanager
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, timedelta, UTC
|
||||
from hashlib import sha256
|
||||
from json import loads as json_loads
|
||||
from os import getenv as env
|
||||
@@ -18,9 +18,7 @@ from jose.constants import ALGORITHMS
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from starlette.middleware.cors import CORSMiddleware
|
||||
from starlette.responses import StreamingResponse, JSONResponse as JSONr, Response, RedirectResponse
|
||||
from starlette.staticfiles import StaticFiles
|
||||
from starlette.templating import Jinja2Templates
|
||||
from starlette.responses import StreamingResponse, JSONResponse as JSONr, HTMLResponse as HTMLr, Response, RedirectResponse
|
||||
|
||||
from orm import Origin, Lease, init as db_init, migrate
|
||||
from util import load_key, load_file
|
||||
@@ -56,6 +54,14 @@ CORS_ORIGINS = str(env('CORS_ORIGINS', '')).split(',') if (env('CORS_ORIGINS'))
|
||||
jwt_encode_key = jwk.construct(INSTANCE_KEY_RSA.export_key().decode('utf-8'), algorithm=ALGORITHMS.RS256)
|
||||
jwt_decode_key = jwk.construct(INSTANCE_KEY_PUB.export_key().decode('utf-8'), algorithm=ALGORITHMS.RS256)
|
||||
|
||||
# Logging
|
||||
LOG_LEVEL = logging.DEBUG if DEBUG else logging.INFO
|
||||
logging.basicConfig(format='[{levelname:^7}] [{module:^15}] {message}', style='{')
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(LOG_LEVEL)
|
||||
logging.getLogger('util').setLevel(LOG_LEVEL)
|
||||
logging.getLogger('NV').setLevel(LOG_LEVEL)
|
||||
|
||||
|
||||
# FastAPI
|
||||
@asynccontextmanager
|
||||
@@ -81,9 +87,6 @@ async def lifespan(_: FastAPI):
|
||||
|
||||
config = dict(openapi_url=None, docs_url=None, redoc_url=None) # dict(openapi_url='/-/openapi.json', docs_url='/-/docs', redoc_url='/-/redoc')
|
||||
app = FastAPI(title='FastAPI-DLS', description='Minimal Delegated License Service (DLS).', version=VERSION, lifespan=lifespan, **config)
|
||||
app.mount('/static', StaticFiles(directory=join(dirname(__file__), 'static'), html=True), name='static'),
|
||||
templates = Jinja2Templates(directory=join(dirname(__file__), 'templates'))
|
||||
|
||||
|
||||
app.debug = DEBUG
|
||||
app.add_middleware(
|
||||
@@ -94,14 +97,6 @@ app.add_middleware(
|
||||
allow_headers=['*'],
|
||||
)
|
||||
|
||||
# Logging
|
||||
LOG_LEVEL = logging.DEBUG if DEBUG else logging.INFO
|
||||
logging.basicConfig(format='[{levelname:^7}] [{module:^15}] {message}', style='{')
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(LOG_LEVEL)
|
||||
logging.getLogger('util').setLevel(LOG_LEVEL)
|
||||
logging.getLogger('NV').setLevel(LOG_LEVEL)
|
||||
|
||||
|
||||
# Helper
|
||||
def __get_token(request: Request) -> dict:
|
||||
@@ -109,8 +104,27 @@ def __get_token(request: Request) -> dict:
|
||||
token = authorization_header.split(' ')[1]
|
||||
return jwt.decode(token=token, key=jwt_decode_key, algorithms=ALGORITHMS.RS256, options={'verify_aud': False})
|
||||
|
||||
def __json_config() -> dict:
|
||||
return {
|
||||
|
||||
# Endpoints
|
||||
|
||||
@app.get('/', summary='Index')
|
||||
async def index():
|
||||
return RedirectResponse('/-/readme')
|
||||
|
||||
|
||||
@app.get('/-/', summary='* Index')
|
||||
async def _index():
|
||||
return RedirectResponse('/-/readme')
|
||||
|
||||
|
||||
@app.get('/-/health', summary='* Health')
|
||||
async def _health():
|
||||
return JSONr({'status': 'up'})
|
||||
|
||||
|
||||
@app.get('/-/config', summary='* Config', description='returns environment variables.')
|
||||
async def _config():
|
||||
return JSONr({
|
||||
'VERSION': str(VERSION),
|
||||
'COMMIT': str(COMMIT),
|
||||
'DEBUG': str(DEBUG),
|
||||
@@ -124,66 +138,52 @@ def __json_config() -> dict:
|
||||
'LEASE_RENEWAL_PERIOD': str(LEASE_RENEWAL_PERIOD),
|
||||
'CORS_ORIGINS': str(CORS_ORIGINS),
|
||||
'TZ': str(TZ),
|
||||
# static / calculated
|
||||
'LEASE_RENEWAL_DELTA': str(LEASE_RENEWAL_DELTA),
|
||||
'LEASE_CALCULATED_RENEWAL': str(Lease.calculate_renewal(LEASE_RENEWAL_PERIOD, LEASE_RENEWAL_DELTA)),
|
||||
'CLIENT_TOKEN_EXPIRE_DELTA': str(CLIENT_TOKEN_EXPIRE_DELTA),
|
||||
}
|
||||
|
||||
|
||||
# Endpoints
|
||||
|
||||
@app.get('/', summary='* Index')
|
||||
async def index():
|
||||
return RedirectResponse('/-/')
|
||||
|
||||
|
||||
@app.get('/-/', summary='* Index')
|
||||
async def _index(request: Request):
|
||||
return templates.TemplateResponse(name='views/index.html', context={'request': request, 'VERSION': VERSION})
|
||||
|
||||
|
||||
@app.get('/-/health', summary='* Health')
|
||||
async def _health():
|
||||
return JSONr({'status': 'up'})
|
||||
|
||||
|
||||
@app.get('/-/config', summary='* Config', description='returns environment variables.')
|
||||
async def _config():
|
||||
return JSONr(__json_config())
|
||||
})
|
||||
|
||||
|
||||
@app.get('/-/readme', summary='* Readme')
|
||||
async def _readme(request: Request):
|
||||
async def _readme():
|
||||
from markdown import markdown
|
||||
content = load_file(join(dirname(__file__), '../README.md')).decode('utf-8')
|
||||
markdown = markdown(text=content, extensions=['tables', 'fenced_code', 'md_in_html', 'nl2br', 'toc'])
|
||||
context = {'request': request, 'VERSION': VERSION, 'markdown': markdown }
|
||||
return templates.TemplateResponse(name='views/dashboard_readme.html', context=context)
|
||||
return HTMLr(markdown(text=content, extensions=['tables', 'fenced_code', 'md_in_html', 'nl2br', 'toc']))
|
||||
|
||||
|
||||
@app.get('/-/manage', summary='* Management UI')
|
||||
async def _manage(request: Request):
|
||||
context = {'request': request, 'VERSION': VERSION}
|
||||
return templates.TemplateResponse(name='views/manage.html', context=context)
|
||||
response = '''
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>FastAPI-DLS Management</title>
|
||||
</head>
|
||||
<body>
|
||||
<button onclick="deleteOrigins()">delete ALL origins and their leases</button>
|
||||
<button onclick="deleteLease()">delete specific lease</button>
|
||||
|
||||
<script>
|
||||
function deleteOrigins() {
|
||||
const response = confirm('Are you sure you want to delete all origins and their leases?');
|
||||
|
||||
|
||||
@app.get('/-/dashboard', summary='* Dashboard')
|
||||
async def _dashboard(request: Request):
|
||||
context = {'request': request, 'VERSION': VERSION, 'CONFIG': __json_config()}
|
||||
return templates.TemplateResponse(name='views/dashboard.html', context=context)
|
||||
|
||||
|
||||
@app.get('/-/dashboard/origins', summary='* Dashboard - Origins')
|
||||
async def _dashboard_origins(request: Request):
|
||||
context = {'request': request, 'VERSION': VERSION}
|
||||
return templates.TemplateResponse(name='views/dashboard_origins.html', context=context)
|
||||
|
||||
|
||||
@app.get('/-/dashboard/leases', summary='* Dashboard - Leases')
|
||||
async def _dashboard_origins(request: Request):
|
||||
context = {'request': request, 'VERSION': VERSION}
|
||||
return templates.TemplateResponse(name='views/dashboard_leases.html', context=context)
|
||||
if (response) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("DELETE", '/-/origins', true);
|
||||
xhr.send();
|
||||
}
|
||||
}
|
||||
function deleteLease(lease_ref) {
|
||||
if(lease_ref === undefined)
|
||||
lease_ref = window.prompt("Please enter 'lease_ref' which should be deleted");
|
||||
if(lease_ref === null || lease_ref === "")
|
||||
return
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("DELETE", `/-/lease/${lease_ref}`, true);
|
||||
xhr.send();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
return HTMLr(response)
|
||||
|
||||
|
||||
@app.get('/-/origins', summary='* Origins')
|
||||
@@ -206,19 +206,6 @@ async def _origins_delete(request: Request):
|
||||
return Response(status_code=201)
|
||||
|
||||
|
||||
@app.delete('/-/origins/expired', summary='* Delete all Origins without active Lease')
|
||||
async def _origins_delete_expired(request: Request):
|
||||
Origin.delete_expired(db)
|
||||
return Response(status_code=201)
|
||||
|
||||
|
||||
@app.delete('/-/origins/{origin_ref}', summary='* Delete specific Origin')
|
||||
async def _origins_delete_origin_ref(request: Request, origin_ref: str):
|
||||
if Origin.delete(db, [origin_ref]) == 1:
|
||||
return Response(status_code=201)
|
||||
return JSONr(status_code=404, content={'status': 404, 'detail': 'lease not found'})
|
||||
|
||||
|
||||
@app.get('/-/leases', summary='* Leases')
|
||||
async def _leases(request: Request, origin: bool = False):
|
||||
session = sessionmaker(bind=db)()
|
||||
@@ -235,13 +222,13 @@ async def _leases(request: Request, origin: bool = False):
|
||||
return JSONr(response)
|
||||
|
||||
|
||||
@app.delete('/-/leases/expired', summary='* Delete all expired Leases')
|
||||
@app.delete('/-/leases/expired', summary='* Leases')
|
||||
async def _lease_delete_expired(request: Request):
|
||||
Lease.delete_expired(db)
|
||||
return Response(status_code=201)
|
||||
|
||||
|
||||
@app.delete('/-/lease/{lease_ref}', summary='* Delete specific Lease')
|
||||
@app.delete('/-/lease/{lease_ref}', summary='* Lease')
|
||||
async def _lease_delete(request: Request, lease_ref: str):
|
||||
if Lease.delete(db, lease_ref) == 1:
|
||||
return Response(status_code=201)
|
||||
@@ -251,7 +238,7 @@ async def _lease_delete(request: Request, lease_ref: str):
|
||||
# venv/lib/python3.9/site-packages/nls_core_service_instance/service_instance_token_manager.py
|
||||
@app.get('/-/client-token', summary='* Client-Token', description='creates a new messenger token for this service instance')
|
||||
async def _client_token():
|
||||
cur_time = datetime.utcnow()
|
||||
cur_time = datetime.now(UTC)
|
||||
exp_time = cur_time + CLIENT_TOKEN_EXPIRE_DELTA
|
||||
|
||||
payload = {
|
||||
@@ -297,7 +284,7 @@ async def _client_token():
|
||||
# venv/lib/python3.9/site-packages/nls_services_auth/test/test_origins_controller.py
|
||||
@app.post('/auth/v1/origin', description='find or create an origin')
|
||||
async def auth_v1_origin(request: Request):
|
||||
j, cur_time = json_loads((await request.body()).decode('utf-8')), datetime.utcnow()
|
||||
j, cur_time = json_loads((await request.body()).decode('utf-8')), datetime.now(UTC)
|
||||
|
||||
origin_ref = j.get('candidate_origin_ref')
|
||||
logging.info(f'> [ origin ]: {origin_ref}: {j}')
|
||||
@@ -327,7 +314,7 @@ async def auth_v1_origin(request: Request):
|
||||
# venv/lib/python3.9/site-packages/nls_services_auth/test/test_origins_controller.py
|
||||
@app.post('/auth/v1/origin/update', description='update an origin evidence')
|
||||
async def auth_v1_origin_update(request: Request):
|
||||
j, cur_time = json_loads((await request.body()).decode('utf-8')), datetime.utcnow()
|
||||
j, cur_time = json_loads((await request.body()).decode('utf-8')), datetime.now(UTC)
|
||||
|
||||
origin_ref = j.get('origin_ref')
|
||||
logging.info(f'> [ update ]: {origin_ref}: {j}')
|
||||
@@ -354,7 +341,7 @@ async def auth_v1_origin_update(request: Request):
|
||||
# venv/lib/python3.9/site-packages/nls_core_auth/auth.py - CodeResponse
|
||||
@app.post('/auth/v1/code', description='get an authorization code')
|
||||
async def auth_v1_code(request: Request):
|
||||
j, cur_time = json_loads((await request.body()).decode('utf-8')), datetime.utcnow()
|
||||
j, cur_time = json_loads((await request.body()).decode('utf-8')), datetime.now(UTC)
|
||||
|
||||
origin_ref = j.get('origin_ref')
|
||||
logging.info(f'> [ code ]: {origin_ref}: {j}')
|
||||
@@ -386,10 +373,10 @@ async def auth_v1_code(request: Request):
|
||||
# venv/lib/python3.9/site-packages/nls_core_auth/auth.py - TokenResponse
|
||||
@app.post('/auth/v1/token', description='exchange auth code and verifier for token')
|
||||
async def auth_v1_token(request: Request):
|
||||
j, cur_time = json_loads((await request.body()).decode('utf-8')), datetime.utcnow()
|
||||
j, cur_time = json_loads((await request.body()).decode('utf-8')), datetime.now(UTC)
|
||||
|
||||
try:
|
||||
payload = jwt.decode(token=j.get('auth_code'), key=jwt_decode_key)
|
||||
payload = jwt.decode(token=j.get('auth_code'), key=jwt_decode_key, algorithms=ALGORITHMS.RS256)
|
||||
except JWTError as e:
|
||||
return JSONr(status_code=400, content={'status': 400, 'title': 'invalid token', 'detail': str(e)})
|
||||
|
||||
@@ -428,7 +415,7 @@ async def auth_v1_token(request: Request):
|
||||
# venv/lib/python3.9/site-packages/nls_services_lease/test/test_lease_multi_controller.py
|
||||
@app.post('/leasing/v1/lessor', description='request multiple leases (borrow) for current origin')
|
||||
async def leasing_v1_lessor(request: Request):
|
||||
j, token, cur_time = json_loads((await request.body()).decode('utf-8')), __get_token(request), datetime.utcnow()
|
||||
j, token, cur_time = json_loads((await request.body()).decode('utf-8')), __get_token(request), datetime.now(UTC)
|
||||
|
||||
try:
|
||||
token = __get_token(request)
|
||||
@@ -476,7 +463,7 @@ async def leasing_v1_lessor(request: Request):
|
||||
# venv/lib/python3.9/site-packages/nls_dal_service_instance_dls/schema/service_instance/V1_0_21__product_mapping.sql
|
||||
@app.get('/leasing/v1/lessor/leases', description='get active leases for current origin')
|
||||
async def leasing_v1_lessor_lease(request: Request):
|
||||
token, cur_time = __get_token(request), datetime.utcnow()
|
||||
token, cur_time = __get_token(request), datetime.now(UTC)
|
||||
|
||||
origin_ref = token.get('origin_ref')
|
||||
|
||||
@@ -496,7 +483,7 @@ async def leasing_v1_lessor_lease(request: Request):
|
||||
# venv/lib/python3.9/site-packages/nls_core_lease/lease_single.py
|
||||
@app.put('/leasing/v1/lease/{lease_ref}', description='renew a lease')
|
||||
async def leasing_v1_lease_renew(request: Request, lease_ref: str):
|
||||
token, cur_time = __get_token(request), datetime.utcnow()
|
||||
token, cur_time = __get_token(request), datetime.now(UTC)
|
||||
|
||||
origin_ref = token.get('origin_ref')
|
||||
logging.info(f'> [ renew ]: {origin_ref}: renew {lease_ref}')
|
||||
@@ -523,7 +510,7 @@ async def leasing_v1_lease_renew(request: Request, lease_ref: str):
|
||||
# venv/lib/python3.9/site-packages/nls_services_lease/test/test_lease_single_controller.py
|
||||
@app.delete('/leasing/v1/lease/{lease_ref}', description='release (return) a lease')
|
||||
async def leasing_v1_lease_delete(request: Request, lease_ref: str):
|
||||
token, cur_time = __get_token(request), datetime.utcnow()
|
||||
token, cur_time = __get_token(request), datetime.now(UTC)
|
||||
|
||||
origin_ref = token.get('origin_ref')
|
||||
logging.info(f'> [ return ]: {origin_ref}: return {lease_ref}')
|
||||
@@ -549,7 +536,7 @@ async def leasing_v1_lease_delete(request: Request, lease_ref: str):
|
||||
# venv/lib/python3.9/site-packages/nls_services_lease/test/test_lease_multi_controller.py
|
||||
@app.delete('/leasing/v1/lessor/leases', description='release all leases')
|
||||
async def leasing_v1_lessor_lease_remove(request: Request):
|
||||
token, cur_time = __get_token(request), datetime.utcnow()
|
||||
token, cur_time = __get_token(request), datetime.now(UTC)
|
||||
|
||||
origin_ref = token.get('origin_ref')
|
||||
|
||||
@@ -569,7 +556,7 @@ async def leasing_v1_lessor_lease_remove(request: Request):
|
||||
|
||||
@app.post('/leasing/v1/lessor/shutdown', description='shutdown all leases')
|
||||
async def leasing_v1_lessor_shutdown(request: Request):
|
||||
j, cur_time = json_loads((await request.body()).decode('utf-8')), datetime.utcnow()
|
||||
j, cur_time = json_loads((await request.body()).decode('utf-8')), datetime.now(UTC)
|
||||
|
||||
token = j.get('token')
|
||||
token = jwt.decode(token=token, key=jwt_decode_key, algorithms=ALGORITHMS.RS256, options={'verify_aud': False})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from datetime import datetime, timedelta, timezone, UTC
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from sqlalchemy import Column, VARCHAR, CHAR, ForeignKey, DATETIME, update, and_, inspect, text
|
||||
@@ -178,7 +178,7 @@ class Lease(Base):
|
||||
@staticmethod
|
||||
def delete_expired(engine: Engine) -> int:
|
||||
session = sessionmaker(bind=engine)()
|
||||
deletions = session.query(Lease).filter(Lease.lease_expires <= datetime.utcnow()).delete()
|
||||
deletions = session.query(Lease).filter(Lease.lease_expires <= datetime.now(UTC)).delete()
|
||||
session.commit()
|
||||
session.close()
|
||||
return deletions
|
||||
|
||||
6
app/static/assets/css/bootstrap.min.css
vendored
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
Original: #76b900
|
||||
Darken 1: #5DA000 (10%)
|
||||
Darken 2: #438600 (20%)
|
||||
Darken 3: #2A6D00 (30%)
|
||||
Darken 4: #105300 (40%)
|
||||
Darken 5: #003A00 (50%)
|
||||
*/
|
||||
|
||||
|
||||
.text-primary {
|
||||
color: #76b900 !important;
|
||||
}
|
||||
|
||||
.lead {
|
||||
color: #105300 !important;
|
||||
}
|
||||
|
||||
.navbar-green {
|
||||
background-color: #76b900 !important;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
background-color: transparent;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.navbar-brand:focus, .navbar-brand:hover {
|
||||
color: #fcfcfc;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #76b900 !important;
|
||||
border-color: #76b900 !important;
|
||||
}
|
||||
|
||||
.btn-primary:focus, .btn-primary:hover {
|
||||
background-color: #5DA000 !important;
|
||||
border-color: #5DA000 !important;
|
||||
}
|
||||
|
||||
code {
|
||||
color: #105300 !important;
|
||||
}
|
||||
|
||||
.sidebar .nav-link.active {
|
||||
color: #76b900 !important;
|
||||
}
|
||||
|
||||
.sidebar .nav-link:focus, .sidebar .nav-link:hover {
|
||||
color: #105300 !important;
|
||||
}
|
||||
|
||||
.navbar-nav .nav-item .nav-link {
|
||||
color: white !important;
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
body {
|
||||
font-size: .875rem;
|
||||
}
|
||||
|
||||
.feather {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sidebar
|
||||
*/
|
||||
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
/* rtl:raw:
|
||||
right: 0;
|
||||
*/
|
||||
bottom: 0;
|
||||
/* rtl:remove */
|
||||
left: 0;
|
||||
z-index: 100; /* Behind the navbar */
|
||||
padding: 48px 0 0; /* Height of navbar */
|
||||
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.sidebar {
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-sticky {
|
||||
position: relative;
|
||||
top: 0;
|
||||
height: calc(100vh - 48px);
|
||||
padding-top: .5rem;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
|
||||
}
|
||||
|
||||
.sidebar .nav-link {
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.sidebar .nav-link .feather {
|
||||
margin-right: 4px;
|
||||
color: #727272;
|
||||
}
|
||||
|
||||
.sidebar .nav-link.active {
|
||||
color: #2470dc;
|
||||
}
|
||||
|
||||
.sidebar .nav-link:hover .feather,
|
||||
.sidebar .nav-link.active .feather {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.sidebar-heading {
|
||||
font-size: .75rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/*
|
||||
* Navbar
|
||||
*/
|
||||
|
||||
.navbar-brand {
|
||||
padding-top: .75rem;
|
||||
padding-bottom: .75rem;
|
||||
font-size: 1rem;
|
||||
background-color: rgba(0, 0, 0, .25);
|
||||
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .25);
|
||||
}
|
||||
|
||||
.navbar .navbar-toggler {
|
||||
top: .25rem;
|
||||
right: 1rem;
|
||||
}
|
||||
|
||||
.navbar .form-control {
|
||||
padding: .75rem 1rem;
|
||||
border-width: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.form-control-dark {
|
||||
color: #fff;
|
||||
background-color: rgba(255, 255, 255, .1);
|
||||
border-color: rgba(255, 255, 255, .1);
|
||||
}
|
||||
|
||||
.form-control-dark:focus {
|
||||
border-color: transparent;
|
||||
box-shadow: 0 0 0 3px rgba(255, 255, 255, .25);
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 733 B |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 15 KiB |
@@ -1 +0,0 @@
|
||||
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
||||
|
Before Width: | Height: | Size: 73 KiB |
7
app/static/assets/js/bootstrap.min.js
vendored
@@ -1,139 +0,0 @@
|
||||
async function fetchConfig(element) {
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", '/-/config', true);
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
|
||||
element.innerHTML = JSON.stringify(JSON.parse(xhr.response),null,2);
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
async function fetchOriginsWithLeases(element) {
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", '/-/origins?leases=true', true);
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
|
||||
const x = JSON.parse(xhr.response)
|
||||
console.debug(x)
|
||||
|
||||
element.innerHTML = ''
|
||||
let table = document.createElement('table')
|
||||
table.classList.add('table', 'mt-4');
|
||||
let thead = document.createElement('thead');
|
||||
thead.innerHTML = `
|
||||
<tr>
|
||||
<th scope="col">origin</th>
|
||||
<th scope="col">hostname</th>
|
||||
<th scope="col">OS</th>
|
||||
<th scope="col">driver version</th>
|
||||
<th scope="col">leases</th>
|
||||
</tr>`
|
||||
table.appendChild(thead)
|
||||
let tbody = document.createElement('thead');
|
||||
x.sort((a, b) => a.hostname.localeCompare(b.hostname)).forEach((o) => {
|
||||
let row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td><code>${o.origin_ref}</code></td>
|
||||
<td>${o.hostname}</td>
|
||||
<td>${o.os_platform} (${o.os_version})</td>
|
||||
<td>${o.guest_driver_version}</td>
|
||||
<td>${o.leases.map(x => `<code title="expires: ${x.lease_expires}">${x.lease_ref}</code>`).join(', ')}</td>`
|
||||
tbody.appendChild(row);
|
||||
})
|
||||
table.appendChild(tbody)
|
||||
element.appendChild(table)
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
async function fetchLeases(element) {
|
||||
// datetime config
|
||||
const dtc = {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
timeZoneName: "short"
|
||||
}
|
||||
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", '/-/leases?origin=true', true);
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
|
||||
const x = JSON.parse(xhr.response)
|
||||
console.debug(x)
|
||||
|
||||
element.innerHTML = ''
|
||||
let table = document.createElement('table')
|
||||
table.classList.add('table', 'mt-4');
|
||||
let thead = document.createElement('thead');
|
||||
thead.innerHTML = `
|
||||
<tr>
|
||||
<th scope="col">lease</th>
|
||||
<th scope="col">created</th>
|
||||
<th scope="col">updated</th>
|
||||
<th scope="col">next renew</th>
|
||||
<th scope="col">expires</th>
|
||||
<th scope="col">origin</th>
|
||||
</tr>`
|
||||
table.appendChild(thead)
|
||||
let tbody = document.createElement('thead');
|
||||
x.sort((a, b) => new Date(a.lease_expires) - new Date(b.lease_expires)).forEach((o) => {
|
||||
let row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td><code>${o.lease_ref}</code></td>
|
||||
<td>${new Date(o.lease_created).toLocaleDateString('system', dtc)}</td>
|
||||
<td>${new Date(o.lease_updated).toLocaleDateString('system', dtc)}</td>
|
||||
<td>${new Date(o.lease_renewal).toLocaleDateString('system', dtc)}</td>
|
||||
<td>${new Date(o.lease_expires).toLocaleDateString('system', dtc)}</td>
|
||||
<td><code title="hostname: ${o.origin?.hostname}">${o.origin_ref}</code></td>`
|
||||
tbody.appendChild(row);
|
||||
})
|
||||
table.appendChild(tbody)
|
||||
element.appendChild(table)
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
async function deleteOrigins() {
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open("DELETE", '/-/origins', true);
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
async function deleteOrigin(origin_ref) {
|
||||
if (origin_ref === undefined)
|
||||
origin_ref = window.prompt("Please enter 'origin_ref' which should be deleted");
|
||||
if (origin_ref === null || origin_ref === "")
|
||||
return
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open("DELETE", `/-/origins/${origin_ref}`, true);
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
async function deleteExpiredOrigins() {
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open("DELETE", `/-/origins/expired`, true);
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
async function deleteLease(lease_ref) {
|
||||
if (lease_ref === undefined)
|
||||
lease_ref = window.prompt("Please enter 'lease_ref' which should be deleted");
|
||||
if (lease_ref === null || lease_ref === "")
|
||||
return
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open("DELETE", `/-/lease/${lease_ref}`, true);
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
async function deleteExpiredLeases() {
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open("DELETE", `/-/leases/expired`, true);
|
||||
xhr.send();
|
||||
}
|
||||
5
app/static/assets/js/popper.min.js
vendored
@@ -1,6 +0,0 @@
|
||||
<header class="navbar navbar-expand-md navbar-green sticky-top bg-dark flex-md-nowrap p-0 shadow">
|
||||
<a class="navbar-brand col-md-3 col-lg-2 me-0 px-3" href="/-/">FastAPI-DLS {{ VERSION }}</a>
|
||||
<button class="navbar-toggler position-absolute d-lg-none collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
</header>
|
||||
@@ -1,93 +0,0 @@
|
||||
<nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse">
|
||||
<div class="position-sticky pt-3">
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {{ 'active' if request.url.path == '/-/dashboard' }}" aria-current="page" href="/-/dashboard">
|
||||
<i class="bi-house-door"></i> Dashboard
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {{ 'active' if request.url.path == '/-/dashboard/origins' }}" aria-current="page" href="/-/dashboard/origins">
|
||||
<i class="bi-pc-display-horizontal"></i> Origins <span id="origin-cnt" class="badge text-bg-secondary"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {{ 'active' if request.url.path == '/-/dashboard/leases' }}" aria-current="page" href="/-/dashboard/leases">
|
||||
<i class="bi-layers"></i> Leases <span id="lease-cnt" class="badge text-bg-secondary"></span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<script type="application/javascript">
|
||||
function loadLOriginCnt() {
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", '/-/origins?leases=false', true);
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
|
||||
const x = JSON.parse(xhr.response)
|
||||
document.getElementById('origin-cnt').innerHTML = x.length
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
function loadLeaseCnt() {
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", '/-/leases?origin=false', true);
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
|
||||
const x = JSON.parse(xhr.response)
|
||||
document.getElementById('lease-cnt').innerHTML = x.length
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
// load initial
|
||||
loadLOriginCnt()
|
||||
loadLeaseCnt()
|
||||
|
||||
// refresh every 5 seconds
|
||||
setInterval(() => {
|
||||
loadLOriginCnt()
|
||||
loadLeaseCnt()
|
||||
}, 5000);
|
||||
</script>
|
||||
|
||||
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted text-uppercase">
|
||||
<span>Help</span>
|
||||
</h6>
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {{ 'active' if request.url.path == '/-/readme' }}" aria-current="page" href="/-/readme">
|
||||
<i class="bi-question-circle"></i> Readme
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" aria-current="page" href="https://git.collinwebdesigns.de/oscar.krause/fastapi-dls" target="_blank">
|
||||
<i class="bi-git"></i> Git Repo
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted text-uppercase">
|
||||
<span>Integrations</span>
|
||||
</h6>
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" aria-current="page" href="/-/doc" target="_blank">
|
||||
<i class="bi-file-text"></i> Swagger UI
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" aria-current="page" href="/-/redoc" target="_blank">
|
||||
<i class="bi-file-text"></i> Redoc
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" aria-current="page" href="/-/openapi.json" target="_blank">
|
||||
<i class="bi bi-filetype-json"></i> OpenAPI JSON
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -1,33 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="h-100">
|
||||
<head>
|
||||
{% block title %}
|
||||
<title>FastAPI-DLS</title>
|
||||
{% endblock %}
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
|
||||
|
||||
<link rel="icon" href="/static/assets/img/favicons/favicon-32x32.png" sizes="32x32" type="image/png">
|
||||
<link rel="icon" href="/static/assets/img/favicons/favicon-16x16.png" sizes="16x16" type="image/png">
|
||||
<link rel="manifest" href="/static/assets/img/favicons/manifest.json">
|
||||
<link rel="icon" href="/static/assets/img/favicons/favicon.ico">
|
||||
<link rel="apple-touch-icon" href="/static/assets/img/favicons/apple-touch-icon.png" sizes="180x180">
|
||||
|
||||
{% block styles %}
|
||||
{% endblock %}
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/static/assets/css/custom.css">
|
||||
</head>
|
||||
<body class="d-flex flex-column {% block body_class %}{% endblock %}">
|
||||
{% block body %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
<script src="/static/assets/js/helper.js"></script>
|
||||
|
||||
{% block scripts %}
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,16 +0,0 @@
|
||||
{% extends 'layouts/bootstrap.html' %}
|
||||
|
||||
{% block body %}
|
||||
{% include 'components/navbar.html' %}
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
{% include 'components/sidebar.html' %}
|
||||
|
||||
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,14 +0,0 @@
|
||||
{% extends 'layouts/base.html' %}
|
||||
|
||||
{% block styles %}
|
||||
{{ super() }}
|
||||
<link rel="stylesheet" type="text/css" href="/static/assets/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="/static/assets/css/bootstrap-icons.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="/static/assets/css/dashboard.css">
|
||||
|
||||
<script src="/static/assets/js/popper.min.js"></script>
|
||||
<script src="/static/assets/js/bootstrap.min.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
{% extends 'layouts/bootstrap-dashboard.html' %}
|
||||
|
||||
{% block title %}
|
||||
<title>Dashboard</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div>
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||
<h1 class="h2">Dashboard</h1>
|
||||
<div class="btn-toolbar mb-2 mb-md-0">
|
||||
<div class="btn-group me-2">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="downloadClientToken()">
|
||||
<i class="bi bi-download"></i>
|
||||
Client Token
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-5 mb-4 bg-light rounded-3">
|
||||
<div class="container-fluid py-5">
|
||||
<h1 class="display-5 fw-bold">FastAPI-DLS</h1>
|
||||
<p class="col-md-8 fs-4">Minimal Delegated License Service (DLS).</p>
|
||||
|
||||
<a href="https://git.collinwebdesigns.de/oscar.krause/fastapi-dls/-/releases" class="btn btn-primary btn-lg" target="_blank">
|
||||
Releases »
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Configuration</h5>
|
||||
<h6 class="card-subtitle mb-2 text-body-secondary">
|
||||
Using timezone: {{ CONFIG.TZ }}. Make sure this is correct and match your clients!
|
||||
</h6>
|
||||
<p class="card-text">
|
||||
Your clients renew their license every {{ CONFIG.LEASE_CALCULATED_RENEWAL }}.<br/>
|
||||
If the renewal fails, the license is {{ CONFIG.LEASE_RENEWAL_DELTA }} valid.<br/>
|
||||
<br/>
|
||||
Your client-token file (.tok) is valid for {{ CONFIG.CLIENT_TOKEN_EXPIRE_DELTA }}.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<pre id="config"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
{{ super() }}
|
||||
<script type="application/javascript">
|
||||
function downloadClientToken() {
|
||||
window.open('/-/client-token', "_blank")
|
||||
}
|
||||
|
||||
function load() {
|
||||
const config = document.getElementById('config')
|
||||
fetchConfig(config)
|
||||
}
|
||||
|
||||
load()
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,60 +0,0 @@
|
||||
{% extends 'layouts/bootstrap-dashboard.html' %}
|
||||
|
||||
{% block title %}
|
||||
<title>Origins</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div>
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||
<h1 class="h2">Leases <small>with origin</small></h1>
|
||||
<div class="btn-toolbar mb-2 mb-md-0">
|
||||
<div class="btn-group me-2">
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" onclick="deleteLease().finally(() => load())">
|
||||
delete lease
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" onclick="deleteExpiredLeases().finally(() => load())">
|
||||
delete all expired leases
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary me-2" onclick="load()" title="refresh">
|
||||
<i class="bi bi-arrow-clockwise"></i>
|
||||
</button>
|
||||
<button id="btn-auto-refresh" type="button" class="btn btn-sm active">auto-refresh</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="leases" class="mt-3"></div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
{{ super() }}
|
||||
<script type="application/javascript">
|
||||
let autoRefresh = true
|
||||
|
||||
function load() {
|
||||
const leases = document.getElementById('leases')
|
||||
fetchLeases(leases)
|
||||
}
|
||||
|
||||
load()
|
||||
|
||||
setInterval(() => {
|
||||
if(autoRefresh)
|
||||
load()
|
||||
}, 5000);
|
||||
|
||||
const btnAutoRefresh = document.getElementById('btn-auto-refresh')
|
||||
btnAutoRefresh.addEventListener("click", () => {
|
||||
if(btnAutoRefresh.classList.contains('active')) {
|
||||
autoRefresh = false
|
||||
btnAutoRefresh.classList.remove('active')
|
||||
} else {
|
||||
autoRefresh = false
|
||||
btnAutoRefresh.classList.add('active')
|
||||
}
|
||||
}, true);
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,70 +0,0 @@
|
||||
{% extends 'layouts/bootstrap-dashboard.html' %}
|
||||
|
||||
{% block title %}
|
||||
<title>Origins</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div>
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||
<h1 class="h2">Origins <small>with leases</small></h1>
|
||||
<div class="btn-toolbar mb-2 mb-md-0">
|
||||
<div class="btn-group me-2">
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" onclick="deleteOrigin().finally(() => load())">
|
||||
delete origin
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" onclick="deleteExpiredOrigins().finally(() => load())">
|
||||
delete all expired origins
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" onclick="deleteOriginsWrapper()">
|
||||
delete all
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary me-2" onclick="load()" title="refresh">
|
||||
<i class="bi bi-arrow-clockwise"></i>
|
||||
</button>
|
||||
<button id="btn-auto-refresh" type="button" class="btn btn-sm active">auto-refresh</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="origins" class="mt-3"></div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
{{ super() }}
|
||||
<script type="application/javascript">
|
||||
let autoRefresh = true
|
||||
|
||||
function load() {
|
||||
const origins = document.getElementById('origins')
|
||||
fetchOriginsWithLeases(origins)
|
||||
}
|
||||
|
||||
load()
|
||||
|
||||
function deleteOriginsWrapper() {
|
||||
const response = confirm('Are you sure you want to delete all origins and their leases?');
|
||||
|
||||
if (response)
|
||||
deleteOrigins().finally(() => load())
|
||||
}
|
||||
|
||||
setInterval(() => {
|
||||
if(autoRefresh)
|
||||
load()
|
||||
}, 5000);
|
||||
|
||||
const btnAutoRefresh = document.getElementById('btn-auto-refresh')
|
||||
btnAutoRefresh.addEventListener("click", () => {
|
||||
if(btnAutoRefresh.classList.contains('active')) {
|
||||
autoRefresh = false
|
||||
btnAutoRefresh.classList.remove('active')
|
||||
} else {
|
||||
autoRefresh = false
|
||||
btnAutoRefresh.classList.add('active')
|
||||
}
|
||||
}, true);
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,15 +0,0 @@
|
||||
{% extends 'layouts/bootstrap-dashboard.html' %}
|
||||
|
||||
{% block title %}
|
||||
<title>Origins</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div>
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||
<div class="overflow-hidden">
|
||||
{{ markdown|safe }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,26 +0,0 @@
|
||||
{% extends 'layouts/bootstrap.html' %}
|
||||
|
||||
{% block title %}
|
||||
<title>Index</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block body_class %}h-100{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<main class="flex-shrink-0">
|
||||
<div class="container">
|
||||
<h1 class="mt-5 text-primary">FastAPI-DLS</h1>
|
||||
<p class="lead">Minimal Delegated License Service (DLS).</p>
|
||||
<p>
|
||||
<a href="/-/dashboard">Dashboard</a>,
|
||||
<a href="/-/readme">Readme</a>
|
||||
</p>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="footer mt-auto py-3 bg-light">
|
||||
<div class="container">
|
||||
<span class="text-muted">FastAPI-DLS Version {{ VERSION }}</span>
|
||||
</div>
|
||||
</footer>
|
||||
{% endblock %}
|
||||
@@ -1,9 +1,8 @@
|
||||
fastapi==0.111.0
|
||||
uvicorn[standard]==0.29.0
|
||||
fastapi==0.115.6
|
||||
uvicorn[standard]==0.34.0
|
||||
python-jose==3.3.0
|
||||
pycryptodome==3.20.0
|
||||
pycryptodome==3.21.0
|
||||
python-dateutil==2.8.2
|
||||
sqlalchemy==2.0.30
|
||||
markdown==3.6
|
||||
sqlalchemy==2.0.37
|
||||
markdown==3.7
|
||||
python-dotenv==1.0.1
|
||||
jinja2==3.1.3
|
||||
|
||||
@@ -4,7 +4,7 @@ logging.basicConfig()
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
URL = 'https://docs.nvidia.com/grid/'
|
||||
URL = 'https://docs.nvidia.com/vgpu/index.html'
|
||||
|
||||
BRANCH_STATUS_KEY, SOFTWARE_BRANCH_KEY, = 'vGPU Branch Status', 'vGPU Software Branch'
|
||||
VGPU_KEY, GRID_KEY, DRIVER_BRANCH_KEY = 'vGPU Software', 'vGPU Software', 'Driver Branch'
|
||||
@@ -25,15 +25,15 @@ def __driver_versions(html: 'BeautifulSoup'):
|
||||
return _
|
||||
|
||||
# find wrapper for "DriverVersions" and find tables
|
||||
data = html.find('div', {'id': 'DriverVersions'})
|
||||
tables = data.findAll('table')
|
||||
for table in tables:
|
||||
# parse software-branch (e.g. "vGPU software 17 Releases" and remove " Releases" for "matrix_key")
|
||||
software_branch = table.parent.find_previous_sibling('button', {'class': 'accordion'}).text.strip()
|
||||
data = html.find('div', {'id': 'driver-versions'})
|
||||
items = data.findAll('bsp-accordion', {'class': 'Accordion-items-item'})
|
||||
for item in items:
|
||||
software_branch = item.find('div', {'class': 'Accordion-items-item-title'}).text.strip()
|
||||
software_branch = software_branch.replace(' Releases', '')
|
||||
matrix_key = software_branch.lower()
|
||||
|
||||
# driver version info from table-heads (ths) and table-rows (trs)
|
||||
table = item.find('table')
|
||||
ths, trs = table.find_all('th'), table.find_all('tr')
|
||||
headers, releases = [header.text.strip() for header in ths], []
|
||||
for trs in trs:
|
||||
@@ -50,7 +50,7 @@ def __driver_versions(html: 'BeautifulSoup'):
|
||||
|
||||
def __release_branches(html: 'BeautifulSoup'):
|
||||
# find wrapper for "AllReleaseBranches" and find table
|
||||
data = html.find('div', {'id': 'AllReleaseBranches'})
|
||||
data = html.find('div', {'id': 'all-release-branches'})
|
||||
table = data.find('table')
|
||||
|
||||
# branch releases info from table-heads (ths) and table-rows (trs)
|
||||
|
||||
16
test/main.py
@@ -1,7 +1,8 @@
|
||||
import sys
|
||||
from base64 import b64encode as b64enc
|
||||
from hashlib import sha256
|
||||
from calendar import timegm
|
||||
from datetime import datetime
|
||||
from datetime import datetime, UTC
|
||||
from hashlib import sha256
|
||||
from os.path import dirname, join
|
||||
from uuid import uuid4, UUID
|
||||
|
||||
@@ -9,7 +10,6 @@ from dateutil.relativedelta import relativedelta
|
||||
from jose import jwt, jwk
|
||||
from jose.constants import ALGORITHMS
|
||||
from starlette.testclient import TestClient
|
||||
import sys
|
||||
|
||||
# add relative path to use packages as they were in the app/ dir
|
||||
sys.path.append('../')
|
||||
@@ -59,8 +59,8 @@ def test_readme():
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_dashboard():
|
||||
response = client.get('/-/dashboard')
|
||||
def test_manage():
|
||||
response = client.get('/-/manage')
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
@@ -106,6 +106,7 @@ def test_auth_v1_origin():
|
||||
assert response.json().get('origin_ref') == ORIGIN_REF
|
||||
|
||||
|
||||
|
||||
def auth_v1_origin_update():
|
||||
payload = {
|
||||
"registration_pending": False,
|
||||
@@ -141,7 +142,7 @@ def test_auth_v1_code():
|
||||
|
||||
|
||||
def test_auth_v1_token():
|
||||
cur_time = datetime.utcnow()
|
||||
cur_time = datetime.now(UTC)
|
||||
access_expires_on = cur_time + relativedelta(hours=1)
|
||||
|
||||
payload = {
|
||||
@@ -153,8 +154,7 @@ def test_auth_v1_token():
|
||||
"kid": "00000000-0000-0000-0000-000000000000"
|
||||
}
|
||||
payload = {
|
||||
"auth_code": jwt.encode(payload, key=jwt_encode_key, headers={'kid': payload.get('kid')},
|
||||
algorithm=ALGORITHMS.RS256),
|
||||
"auth_code": jwt.encode(payload, key=jwt_encode_key, headers={'kid': payload.get('kid')}, algorithm=ALGORITHMS.RS256),
|
||||
"code_verifier": SECRET,
|
||||
}
|
||||
|
||||
|
||||