mirror of
https://gitea.publichub.eu/oscar.krause/fastapi-dls.git
synced 2025-11-25 23:27:37 +00:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1bbd42b50 | ||
|
|
c1d541f7c6 | ||
|
|
4b58fe6e20 | ||
|
|
b36b49df11 | ||
|
|
a42b1c8cfb | ||
|
|
59152f95e6 | ||
|
|
62d347510d | ||
|
|
f540c4b25b | ||
|
|
70212e0edd | ||
|
|
616e8fba5e | ||
|
|
b905ab9dd9 | ||
|
|
9edc93653e | ||
|
|
f30e9237a5 | ||
|
|
f12dc28c42 | ||
|
|
02276d5440 | ||
|
|
9ebff8d6ca | ||
|
|
48eb6d6c64 | ||
|
|
f7ef8d76b6 | ||
|
|
bed24b56ce | ||
|
|
95427d430e | ||
|
|
c3ea0aa48c | ||
|
|
91be7b226c | ||
|
|
7045692958 | ||
|
|
38177fa259 | ||
|
|
9411759f6d | ||
|
|
48c37987b2 | ||
|
|
e3745d7fa8 | ||
|
|
5bb8f17679 | ||
|
|
de17b0f1b5 | ||
|
|
0ab5969d3a | ||
|
|
059a51fe74 | ||
|
|
bf858b38f4 | ||
|
|
f60f08d543 | ||
|
|
b2e6fab294 | ||
|
|
b09bb091a5 | ||
|
|
651af4cc82 | ||
|
|
70f7d3f483 | ||
|
|
1e4070a1ba | ||
|
|
d69d833923 | ||
|
|
7ef071f92b | ||
|
|
3c19fc9d5b | ||
|
|
164b5ebc44 | ||
|
|
742fa07ed4 | ||
|
|
a758d93970 |
@@ -1,2 +1 @@
|
|||||||
/etc/fastapi-dls/env
|
/etc/fastapi-dls/env
|
||||||
/etc/systemd/system/fastapi-dls.service
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
WORKING_DIR=/usr/share/fastapi-dls
|
WORKING_DIR=/usr/share/fastapi-dls
|
||||||
CONFIG_DIR=/etc/fastapi-dls
|
CONFIG_DIR=/etc/fastapi-dls
|
||||||
|
|
||||||
if [[ ! -f $CONFIG_DIR/instance.private.pem ]]; then
|
if [ ! -f $CONFIG_DIR/instance.private.pem ]; then
|
||||||
echo "> Create dls-instance keypair ..."
|
echo "> Create dls-instance keypair ..."
|
||||||
openssl genrsa -out $CONFIG_DIR/instance.private.pem 2048
|
openssl genrsa -out $CONFIG_DIR/instance.private.pem 2048
|
||||||
openssl rsa -in $CONFIG_DIR/instance.private.pem -outform PEM -pubout -out $CONFIG_DIR/instance.public.pem
|
openssl rsa -in $CONFIG_DIR/instance.private.pem -outform PEM -pubout -out $CONFIG_DIR/instance.public.pem
|
||||||
@@ -12,8 +12,8 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
[[ -f $CONFIG_DIR/webserver.key ]] && default_answer="N" || default_answer="Y"
|
[ -f $CONFIG_DIR/webserver.key ] && default_answer="N" || default_answer="Y"
|
||||||
[[ $default_answer == "Y" ]] && V="Y/n" || V="y/N"
|
[ $default_answer == "Y" ] && V="Y/n" || V="y/N"
|
||||||
read -p "> Do you wish to create self-signed webserver certificate? [${V}]" yn
|
read -p "> Do you wish to create self-signed webserver certificate? [${V}]" yn
|
||||||
yn=${yn:-$default_answer} # ${parameter:-word} If parameter is unset or null, the expansion of word is substituted. Otherwise, the value of parameter is substituted.
|
yn=${yn:-$default_answer} # ${parameter:-word} If parameter is unset or null, the expansion of word is substituted. Otherwise, the value of parameter is substituted.
|
||||||
case $yn in
|
case $yn in
|
||||||
@@ -27,7 +27,7 @@ while true; do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ -f $CONFIG_DIR/webserver.key ]]; then
|
if [ -f $CONFIG_DIR/webserver.key ]; then
|
||||||
echo "> Starting service ..."
|
echo "> Starting service ..."
|
||||||
systemctl start fastapi-dls.service
|
systemctl start fastapi-dls.service
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
if [[ -f /etc/systemd/system/fastapi-dls.service ]]; then
|
# is removed automatically
|
||||||
echo "> Removing service file."
|
#if [ "$1" = purge ] && [ -d /usr/share/fastapi-dls ]; then
|
||||||
rm /etc/systemd/system/fastapi-dls.service
|
# echo "> Removing app."
|
||||||
fi
|
# rm -r /usr/share/fastapi-dls
|
||||||
|
#fi
|
||||||
|
|
||||||
# todo
|
echo -e "> Done."
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
echo -e "> Starting uninstallation of 'fastapi-dls'!"
|
echo -e "> Starting uninstallation of 'fastapi-dls'!"
|
||||||
|
|
||||||
# todo
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ license=('MIT')
|
|||||||
depends=('python' 'python-jose' 'python-starlette' 'python-httpx' 'python-fastapi' 'python-dotenv' 'python-dateutil' 'python-sqlalchemy' 'python-pycryptodome' '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")
|
provider=("$pkgname")
|
||||||
install="$pkgname.install"
|
install="$pkgname.install"
|
||||||
|
backup=('etc/default/fastapi-dls')
|
||||||
source=('git+file:///builds/oscar.krause/fastapi-dls' # https://gitea.publichub.eu/oscar.krause/fastapi-dls.git
|
source=('git+file:///builds/oscar.krause/fastapi-dls' # https://gitea.publichub.eu/oscar.krause/fastapi-dls.git
|
||||||
"$pkgname.default"
|
"$pkgname.default"
|
||||||
"$pkgname.service"
|
"$pkgname.service"
|
||||||
|
|||||||
222
README.md
222
README.md
@@ -7,11 +7,31 @@ Compatibility tested with official DLS 2.0.1.
|
|||||||
This service can be used without internet connection.
|
This service can be used without internet connection.
|
||||||
Only the clients need a connection to this service on configured port.
|
Only the clients need a connection to this service on configured port.
|
||||||
|
|
||||||
|
**Official Links**
|
||||||
|
|
||||||
|
- https://git.collinwebdesigns.de/oscar.krause/fastapi-dls
|
||||||
|
- https://gitea.publichub.eu/oscar.krause/fastapi-dls
|
||||||
|
- Docker Image `collinwebdesigns/fastapi-dls:latest`
|
||||||
|
|
||||||
|
*All other repositories are forks! (which is no bad - just for information and bug reports)*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
[[_TOC_]]
|
[[_TOC_]]
|
||||||
|
|
||||||
|
|
||||||
# Setup (Service)
|
# Setup (Service)
|
||||||
|
|
||||||
|
**System requirements**
|
||||||
|
|
||||||
|
- 256mb ram
|
||||||
|
- 4gb hdd
|
||||||
|
|
||||||
|
Tested with Ubuntu 22.10 (from Proxmox templates), actually its consuming 100mb ram and 750mb hdd.
|
||||||
|
|
||||||
|
**Prepare your system**
|
||||||
|
|
||||||
|
- Make sure your timezone is set correct on you fastapi-dls server and your client
|
||||||
|
|
||||||
## Docker
|
## Docker
|
||||||
|
|
||||||
Docker-Images are available here:
|
Docker-Images are available here:
|
||||||
@@ -49,10 +69,12 @@ Goto [`docker-compose.yml`](docker-compose.yml) for more advanced example (with
|
|||||||
version: '3.9'
|
version: '3.9'
|
||||||
|
|
||||||
x-dls-variables: &dls-variables
|
x-dls-variables: &dls-variables
|
||||||
|
TZ: Europe/Berlin # REQUIRED, set your timezone correctly on fastapi-dls AND YOUR CLIENTS !!!
|
||||||
DLS_URL: localhost # REQUIRED, change to your ip or hostname
|
DLS_URL: localhost # REQUIRED, change to your ip or hostname
|
||||||
DLS_PORT: 443
|
DLS_PORT: 443
|
||||||
LEASE_EXPIRE_DAYS: 90
|
LEASE_EXPIRE_DAYS: 90 # 90 days is maximum
|
||||||
DATABASE: sqlite:////app/database/db.sqlite
|
DATABASE: sqlite:////app/database/db.sqlite
|
||||||
|
DEBUG: false
|
||||||
|
|
||||||
services:
|
services:
|
||||||
dls:
|
dls:
|
||||||
@@ -65,7 +87,12 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- /opt/docker/fastapi-dls/cert:/app/cert
|
- /opt/docker/fastapi-dls/cert:/app/cert
|
||||||
- dls-db:/app/database
|
- dls-db:/app/database
|
||||||
|
logging: # optional, for those who do not need logs
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-file: 5
|
||||||
|
max-size: 10m
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
dls-db:
|
dls-db:
|
||||||
```
|
```
|
||||||
@@ -74,6 +101,8 @@ volumes:
|
|||||||
|
|
||||||
Tested on `Debian 11 (bullseye)`, Ubuntu may also work.
|
Tested on `Debian 11 (bullseye)`, Ubuntu may also work.
|
||||||
|
|
||||||
|
**Make sure you are logged in as root.**
|
||||||
|
|
||||||
**Install requirements**
|
**Install requirements**
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
@@ -98,7 +127,7 @@ chown -R www-data:www-data $WORKING_DIR
|
|||||||
|
|
||||||
```shell
|
```shell
|
||||||
WORKING_DIR=/opt/fastapi-dls/app/cert
|
WORKING_DIR=/opt/fastapi-dls/app/cert
|
||||||
mkdir $WORKING_DIR
|
mkdir -p $WORKING_DIR
|
||||||
cd $WORKING_DIR
|
cd $WORKING_DIR
|
||||||
# create instance private and public key for singing JWT's
|
# create instance private and public key for singing JWT's
|
||||||
openssl genrsa -out $WORKING_DIR/instance.private.pem 2048
|
openssl genrsa -out $WORKING_DIR/instance.private.pem 2048
|
||||||
@@ -115,11 +144,14 @@ This is only to test whether the service starts successfully.
|
|||||||
```shell
|
```shell
|
||||||
cd /opt/fastapi-dls/app
|
cd /opt/fastapi-dls/app
|
||||||
su - www-data -c "/opt/fastapi-dls/venv/bin/uvicorn main:app --app-dir=/opt/fastapi-dls/app"
|
su - www-data -c "/opt/fastapi-dls/venv/bin/uvicorn main:app --app-dir=/opt/fastapi-dls/app"
|
||||||
|
# or
|
||||||
|
sudo -u www-data -c "/opt/fastapi-dls/venv/bin/uvicorn main:app --app-dir=/opt/fastapi-dls/app"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Create config file**
|
**Create config file**
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
mkdir /etc/fastapi-dls
|
||||||
cat <<EOF >/etc/fastapi-dls/env
|
cat <<EOF >/etc/fastapi-dls/env
|
||||||
DLS_URL=127.0.0.1
|
DLS_URL=127.0.0.1
|
||||||
DLS_PORT=443
|
DLS_PORT=443
|
||||||
@@ -164,6 +196,108 @@ EOF
|
|||||||
Now you have to run `systemctl daemon-reload`. After that you can start service
|
Now you have to run `systemctl daemon-reload`. After that you can start service
|
||||||
with `systemctl start fastapi-dls.service` and enable autostart with `systemctl enable fastapi-dls.service`.
|
with `systemctl start fastapi-dls.service` and enable autostart with `systemctl enable fastapi-dls.service`.
|
||||||
|
|
||||||
|
## openSUSE Leap (manual method using `git clone` and python virtual environment)
|
||||||
|
|
||||||
|
Tested on `openSUSE Leap 15.4`, openSUSE Tumbleweed may also work.
|
||||||
|
|
||||||
|
**Install requirements**
|
||||||
|
|
||||||
|
```shell
|
||||||
|
zypper in -y python310 python3-virtualenv python3-pip
|
||||||
|
```
|
||||||
|
|
||||||
|
**Install FastAPI-DLS**
|
||||||
|
|
||||||
|
```shell
|
||||||
|
BASE_DIR=/opt/fastapi-dls
|
||||||
|
SERVICE_USER=dls
|
||||||
|
mkdir -p ${BASE_DIR}
|
||||||
|
cd ${BASE_DIR}
|
||||||
|
git clone https://git.collinwebdesigns.de/oscar.krause/fastapi-dls .
|
||||||
|
python3.10 -m venv venv
|
||||||
|
source venv/bin/activate
|
||||||
|
pip install -r requirements.txt
|
||||||
|
deactivate
|
||||||
|
useradd -r ${SERVICE_USER} -M -d /opt/fastapi-dls
|
||||||
|
chown -R ${SERVICE_USER} ${BASE_DIR}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Create keypair and webserver certificate**
|
||||||
|
|
||||||
|
```shell
|
||||||
|
CERT_DIR=${BASE_DIR}/app/cert
|
||||||
|
SERVICE_USER=dls
|
||||||
|
mkdir ${CERT_DIR}
|
||||||
|
cd ${CERT_DIR}
|
||||||
|
# create instance private and public key for singing JWT's
|
||||||
|
openssl genrsa -out ${CERT_DIR}/instance.private.pem 2048
|
||||||
|
openssl rsa -in ${CERT_DIR}/instance.private.pem -outform PEM -pubout -out ${CERT_DIR}/instance.public.pem
|
||||||
|
# create ssl certificate for integrated webserver (uvicorn) - because clients rely on ssl
|
||||||
|
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout ${CERT_DIR}/webserver.key -out ${CERT_DIR}/webserver.crt
|
||||||
|
chown -R ${SERVICE_USER} ${CERT_DIR}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Test Service**
|
||||||
|
|
||||||
|
This is only to test whether the service starts successfully.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
BASE_DIR=/opt/fastapi-dls
|
||||||
|
SERVICE_USER=dls
|
||||||
|
cd ${BASE_DIR}
|
||||||
|
su - ${SERVICE_USER} -c "${BASE_DIR}/venv/bin/uvicorn main:app --app-dir=${BASE_DIR}/app"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Create config file**
|
||||||
|
|
||||||
|
```shell
|
||||||
|
BASE_DIR=/opt/fastapi-dls
|
||||||
|
cat <<EOF >/etc/fastapi-dls/env
|
||||||
|
# Adjust DSL_URL as needed (accessing from LAN won't work with 127.0.0.1)
|
||||||
|
DLS_URL=127.0.0.1
|
||||||
|
DLS_PORT=443
|
||||||
|
LEASE_EXPIRE_DAYS=90
|
||||||
|
DATABASE=sqlite:///${BASE_DIR}/app/db.sqlite
|
||||||
|
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
**Create service**
|
||||||
|
|
||||||
|
```shell
|
||||||
|
BASE_DIR=/opt/fastapi-dls
|
||||||
|
SERVICE_USER=dls
|
||||||
|
cat <<EOF >/etc/systemd/system/fastapi-dls.service
|
||||||
|
[Unit]
|
||||||
|
Description=Service for fastapi-dls vGPU licensing service
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User=${SERVICE_USER}
|
||||||
|
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||||
|
WorkingDirectory=${BASE_DIR}/app
|
||||||
|
EnvironmentFile=/etc/fastapi-dls/env
|
||||||
|
ExecStart=${BASE_DIR}/venv/bin/uvicorn main:app \\
|
||||||
|
--env-file /etc/fastapi-dls/env \\
|
||||||
|
--host \$DLS_URL --port \$DLS_PORT \\
|
||||||
|
--app-dir ${BASE_DIR}/app \\
|
||||||
|
--ssl-keyfile ${BASE_DIR}/app/cert/webserver.key \\
|
||||||
|
--ssl-certfile ${BASE_DIR}/app/cert/webserver.crt \\
|
||||||
|
--proxy-headers
|
||||||
|
Restart=always
|
||||||
|
KillSignal=SIGQUIT
|
||||||
|
Type=simple
|
||||||
|
NotifyAccess=all
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
Now you have to run `systemctl daemon-reload`. After that you can start service
|
||||||
|
with `systemctl start fastapi-dls.service` and enable autostart with `systemctl enable fastapi-dls.service`.
|
||||||
|
|
||||||
## Debian/Ubuntu (using `dpkg`)
|
## Debian/Ubuntu (using `dpkg`)
|
||||||
|
|
||||||
Packages are available here:
|
Packages are available here:
|
||||||
@@ -206,13 +340,17 @@ Packages are available here:
|
|||||||
```shell
|
```shell
|
||||||
pacman -Sy
|
pacman -Sy
|
||||||
FILENAME=/opt/fastapi-dls.pkg.tar.zst
|
FILENAME=/opt/fastapi-dls.pkg.tar.zst
|
||||||
url -o $FILENAME <download-url>
|
|
||||||
|
curl -o $FILENAME <download-url>
|
||||||
|
# or
|
||||||
|
wget -O $FILENAME <download-url>
|
||||||
|
|
||||||
pacman -U --noconfirm fastapi-dls.pkg.tar.zst
|
pacman -U --noconfirm fastapi-dls.pkg.tar.zst
|
||||||
```
|
```
|
||||||
|
|
||||||
Start with `systemctl start fastapi-dls.service` and enable autostart with `systemctl enable fastapi-dls.service`.
|
Start with `systemctl start fastapi-dls.service` and enable autostart with `systemctl enable fastapi-dls.service`.
|
||||||
|
|
||||||
## Let's Encrypt Certificate
|
## Let's Encrypt Certificate (optional)
|
||||||
|
|
||||||
If you're using installation via docker, you can use `traefik`. Please refer to their documentation.
|
If you're using installation via docker, you can use `traefik`. Please refer to their documentation.
|
||||||
|
|
||||||
@@ -266,26 +404,67 @@ Successfully tested with this package versions:
|
|||||||
|
|
||||||
## Linux
|
## Linux
|
||||||
|
|
||||||
|
Download *client-token* and place it into `/etc/nvidia/ClientConfigToken`:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
curl --insecure -L -X GET https://<dls-hostname-or-ip>/-/client-token -o /etc/nvidia/ClientConfigToken/client_configuration_token_$(date '+%d-%m-%Y-%H-%M-%S').tok
|
||||||
|
# or
|
||||||
|
wget --no-check-certificate -O /etc/nvidia/ClientConfigToken/client_configuration_token_$(date '+%d-%m-%Y-%H-%M-%S').tok https://<dls-hostname-or-ip>/-/client-token
|
||||||
|
```
|
||||||
|
|
||||||
|
Restart `nvidia-gridd` service:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
curl --insecure -L -X GET https://<dls-hostname-or-ip>/client-token -o /etc/nvidia/ClientConfigToken/client_configuration_token_$(date '+%d-%m-%Y-%H-%M-%S').tok
|
|
||||||
service nvidia-gridd restart
|
service nvidia-gridd restart
|
||||||
|
```
|
||||||
|
|
||||||
|
Check licensing status:
|
||||||
|
|
||||||
|
```shell
|
||||||
nvidia-smi -q | grep "License"
|
nvidia-smi -q | grep "License"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Windows
|
Output should be something like:
|
||||||
|
|
||||||
Download file and place it into `C:\Program Files\NVIDIA Corporation\vGPU Licensing\ClientConfigToken`.
|
```text
|
||||||
Now restart `NvContainerLocalSystem` service.
|
vGPU Software Licensed Product
|
||||||
|
License Status : Licensed (Expiry: YYYY-M-DD hh:mm:ss GMT)
|
||||||
**Power-Shell**
|
|
||||||
|
|
||||||
```Shell
|
|
||||||
curl.exe --insecure -L -X GET https://<dls-hostname-or-ip>/client-token -o "C:\Program Files\NVIDIA Corporation\vGPU Licensing\ClientConfigToken\client_configuration_token_$($(Get-Date).tostring('dd-MM-yy-hh-mm-ss')).tok"
|
|
||||||
Restart-Service NVDisplay.ContainerLocalSystem
|
|
||||||
'C:\Program Files\NVIDIA Corporation\NVSMI\nvidia-smi.exe' -q | Select-String "License"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Endpoints
|
Done. For more information check [troubleshoot section](#troubleshoot).
|
||||||
|
|
||||||
|
## Windows
|
||||||
|
|
||||||
|
**Power-Shell** (run as administrator!)
|
||||||
|
|
||||||
|
Download *client-token* and place it into `C:\Program Files\NVIDIA Corporation\vGPU Licensing\ClientConfigToken`:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
curl.exe --insecure -L -X GET https://<dls-hostname-or-ip>/-/client-token -o "C:\Program Files\NVIDIA Corporation\vGPU Licensing\ClientConfigToken\client_configuration_token_$($(Get-Date).tostring('dd-MM-yy-hh-mm-ss')).tok"
|
||||||
|
```
|
||||||
|
|
||||||
|
Restart `NvContainerLocalSystem` service:
|
||||||
|
|
||||||
|
```Shell
|
||||||
|
Restart-Service NVDisplay.ContainerLocalSystem
|
||||||
|
```
|
||||||
|
|
||||||
|
Check licensing status:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
& 'C:\Program Files\NVIDIA Corporation\NVSMI\nvidia-smi.exe' -q | Select-String "License"
|
||||||
|
```
|
||||||
|
|
||||||
|
Output should be something like:
|
||||||
|
|
||||||
|
```text
|
||||||
|
vGPU Software Licensed Product
|
||||||
|
License Status : Licensed (Expiry: YYYY-M-DD hh:mm:ss GMT)
|
||||||
|
```
|
||||||
|
|
||||||
|
Done. For more information check [troubleshoot section](#troubleshoot).
|
||||||
|
|
||||||
|
# Endpoints
|
||||||
|
|
||||||
### `GET /`
|
### `GET /`
|
||||||
|
|
||||||
@@ -303,10 +482,6 @@ Shows current runtime environment variables and their values.
|
|||||||
|
|
||||||
HTML rendered README.md.
|
HTML rendered README.md.
|
||||||
|
|
||||||
### `GET /-/docs`, `GET /-/redoc`
|
|
||||||
|
|
||||||
OpenAPI specifications rendered from `GET /-/openapi.json`.
|
|
||||||
|
|
||||||
### `GET /-/manage`
|
### `GET /-/manage`
|
||||||
|
|
||||||
Shows a very basic UI to delete origins or leases.
|
Shows a very basic UI to delete origins or leases.
|
||||||
@@ -491,5 +666,4 @@ The error message can safely be ignored (since we have no license limitation :P)
|
|||||||
|
|
||||||
Thanks to vGPU community and all who uses this project and report bugs.
|
Thanks to vGPU community and all who uses this project and report bugs.
|
||||||
|
|
||||||
Special thanks to @samicrusader who created build file for ArchLinux.
|
Special thanks to @samicrusader who created build file for ArchLinux and @cyrus who wrote the section for openSUSE.
|
||||||
|
|
||||||
|
|||||||
37
app/main.py
37
app/main.py
@@ -9,7 +9,7 @@ from dotenv import load_dotenv
|
|||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi.requests import Request
|
from fastapi.requests import Request
|
||||||
from json import loads as json_loads
|
from json import loads as json_loads
|
||||||
from datetime import datetime
|
from datetime import datetime, timedelta
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
from calendar import timegm
|
from calendar import timegm
|
||||||
from jose import jws, jwk, jwt, JWTError
|
from jose import jws, jwk, jwt, JWTError
|
||||||
@@ -22,12 +22,13 @@ from sqlalchemy.orm import sessionmaker
|
|||||||
from util import load_key, load_file
|
from util import load_key, load_file
|
||||||
from orm import Origin, Lease, init as db_init, migrate
|
from orm import Origin, Lease, init as db_init, migrate
|
||||||
|
|
||||||
logger = logging.getLogger()
|
|
||||||
load_dotenv('../version.env')
|
load_dotenv('../version.env')
|
||||||
|
|
||||||
|
TZ = datetime.now().astimezone().tzinfo
|
||||||
|
|
||||||
VERSION, COMMIT, DEBUG = env('VERSION', 'unknown'), env('COMMIT', 'unknown'), bool(env('DEBUG', False))
|
VERSION, COMMIT, DEBUG = env('VERSION', 'unknown'), env('COMMIT', 'unknown'), bool(env('DEBUG', False))
|
||||||
|
|
||||||
config = dict(openapi_url='/-/openapi.json', docs_url='/-/docs', redoc_url='/-/redoc')
|
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, **config)
|
app = FastAPI(title='FastAPI-DLS', description='Minimal Delegated License Service (DLS).', version=VERSION, **config)
|
||||||
db = create_engine(str(env('DATABASE', 'sqlite:///db.sqlite')))
|
db = create_engine(str(env('DATABASE', 'sqlite:///db.sqlite')))
|
||||||
db_init(db), migrate(db)
|
db_init(db), migrate(db)
|
||||||
@@ -43,6 +44,8 @@ INSTANCE_KEY_PUB = load_key(str(env('INSTANCE_KEY_PUB', join(dirname(__file__),
|
|||||||
TOKEN_EXPIRE_DELTA = relativedelta(days=int(env('TOKEN_EXPIRE_DAYS', 1)), hours=int(env('TOKEN_EXPIRE_HOURS', 0)))
|
TOKEN_EXPIRE_DELTA = relativedelta(days=int(env('TOKEN_EXPIRE_DAYS', 1)), hours=int(env('TOKEN_EXPIRE_HOURS', 0)))
|
||||||
LEASE_EXPIRE_DELTA = relativedelta(days=int(env('LEASE_EXPIRE_DAYS', 90)), hours=int(env('LEASE_EXPIRE_HOURS', 0)))
|
LEASE_EXPIRE_DELTA = relativedelta(days=int(env('LEASE_EXPIRE_DAYS', 90)), hours=int(env('LEASE_EXPIRE_HOURS', 0)))
|
||||||
LEASE_RENEWAL_PERIOD = float(env('LEASE_RENEWAL_PERIOD', 0.15))
|
LEASE_RENEWAL_PERIOD = float(env('LEASE_RENEWAL_PERIOD', 0.15))
|
||||||
|
LEASE_RENEWAL_DELTA = timedelta(days=int(env('LEASE_EXPIRE_DAYS', 90)), hours=int(env('LEASE_EXPIRE_HOURS', 0)))
|
||||||
|
CLIENT_TOKEN_EXPIRE_DELTA = relativedelta(years=12)
|
||||||
CORS_ORIGINS = str(env('CORS_ORIGINS', '')).split(',') if (env('CORS_ORIGINS')) else [f'https://{DLS_URL}']
|
CORS_ORIGINS = str(env('CORS_ORIGINS', '')).split(',') if (env('CORS_ORIGINS')) else [f'https://{DLS_URL}']
|
||||||
|
|
||||||
jwt_encode_key = jwk.construct(INSTANCE_KEY_RSA.export_key().decode('utf-8'), algorithm=ALGORITHMS.RS256)
|
jwt_encode_key = jwk.construct(INSTANCE_KEY_RSA.export_key().decode('utf-8'), algorithm=ALGORITHMS.RS256)
|
||||||
@@ -57,6 +60,8 @@ app.add_middleware(
|
|||||||
allow_headers=['*'],
|
allow_headers=['*'],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
logging.basicConfig()
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
logger.setLevel(logging.DEBUG if DEBUG else logging.INFO)
|
logger.setLevel(logging.DEBUG if DEBUG else logging.INFO)
|
||||||
|
|
||||||
|
|
||||||
@@ -96,6 +101,7 @@ async def _config():
|
|||||||
'LEASE_EXPIRE_DELTA': str(LEASE_EXPIRE_DELTA),
|
'LEASE_EXPIRE_DELTA': str(LEASE_EXPIRE_DELTA),
|
||||||
'LEASE_RENEWAL_PERIOD': str(LEASE_RENEWAL_PERIOD),
|
'LEASE_RENEWAL_PERIOD': str(LEASE_RENEWAL_PERIOD),
|
||||||
'CORS_ORIGINS': str(CORS_ORIGINS),
|
'CORS_ORIGINS': str(CORS_ORIGINS),
|
||||||
|
'TZ': str(TZ),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@@ -151,7 +157,8 @@ async def _origins(request: Request, leases: bool = False):
|
|||||||
for origin in session.query(Origin).all():
|
for origin in session.query(Origin).all():
|
||||||
x = origin.serialize()
|
x = origin.serialize()
|
||||||
if leases:
|
if leases:
|
||||||
x['leases'] = list(map(lambda _: _.serialize(), Lease.find_by_origin_ref(db, origin.origin_ref)))
|
serialize = dict(renewal_period=LEASE_RENEWAL_PERIOD, renewal_delta=LEASE_RENEWAL_DELTA)
|
||||||
|
x['leases'] = list(map(lambda _: _.serialize(**serialize), Lease.find_by_origin_ref(db, origin.origin_ref)))
|
||||||
response.append(x)
|
response.append(x)
|
||||||
session.close()
|
session.close()
|
||||||
return JSONr(response)
|
return JSONr(response)
|
||||||
@@ -168,10 +175,12 @@ async def _leases(request: Request, origin: bool = False):
|
|||||||
session = sessionmaker(bind=db)()
|
session = sessionmaker(bind=db)()
|
||||||
response = []
|
response = []
|
||||||
for lease in session.query(Lease).all():
|
for lease in session.query(Lease).all():
|
||||||
x = lease.serialize()
|
serialize = dict(renewal_period=LEASE_RENEWAL_PERIOD, renewal_delta=LEASE_RENEWAL_DELTA)
|
||||||
|
x = lease.serialize(**serialize)
|
||||||
if origin:
|
if origin:
|
||||||
# assume that each lease has a valid origin record
|
lease_origin = session.query(Origin).filter(Origin.origin_ref == lease.origin_ref).first()
|
||||||
x['origin'] = session.query(Origin).filter(Origin.origin_ref == lease.origin_ref).first().serialize()
|
if lease_origin is not None:
|
||||||
|
x['origin'] = lease_origin.serialize()
|
||||||
response.append(x)
|
response.append(x)
|
||||||
session.close()
|
session.close()
|
||||||
return JSONr(response)
|
return JSONr(response)
|
||||||
@@ -188,7 +197,7 @@ async def _lease_delete(request: Request, lease_ref: str):
|
|||||||
@app.get('/-/client-token', summary='* Client-Token', description='creates a new messenger token for this service instance')
|
@app.get('/-/client-token', summary='* Client-Token', description='creates a new messenger token for this service instance')
|
||||||
async def _client_token():
|
async def _client_token():
|
||||||
cur_time = datetime.utcnow()
|
cur_time = datetime.utcnow()
|
||||||
exp_time = cur_time + relativedelta(years=12)
|
exp_time = cur_time + CLIENT_TOKEN_EXPIRE_DELTA
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
"jti": str(uuid4()),
|
"jti": str(uuid4()),
|
||||||
@@ -525,6 +534,18 @@ async def leasing_v1_lessor_shutdown(request: Request):
|
|||||||
return JSONr(response)
|
return JSONr(response)
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_event('startup')
|
||||||
|
async def app_on_startup():
|
||||||
|
logger.info(f'''
|
||||||
|
Using timezone: {str(TZ)}. Make sure this is correct and match your clients!
|
||||||
|
|
||||||
|
Your clients renew their license every {str(Lease.calculate_renewal(LEASE_RENEWAL_PERIOD, LEASE_RENEWAL_DELTA))}.
|
||||||
|
If the renewal fails, the license is {str(LEASE_RENEWAL_DELTA)} valid.
|
||||||
|
|
||||||
|
Your client-token file (.tok) is valid for {str(CLIENT_TOKEN_EXPIRE_DELTA)}.
|
||||||
|
''')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
|
||||||
|
|||||||
46
app/orm.py
46
app/orm.py
@@ -1,9 +1,9 @@
|
|||||||
import datetime
|
from datetime import datetime, timedelta
|
||||||
|
from dateutil.relativedelta import relativedelta
|
||||||
|
|
||||||
from sqlalchemy import Column, VARCHAR, CHAR, ForeignKey, DATETIME, update, and_, inspect
|
from sqlalchemy import Column, VARCHAR, CHAR, ForeignKey, DATETIME, update, and_, inspect, text
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
|
||||||
from sqlalchemy.engine import Engine
|
from sqlalchemy.engine import Engine
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker, declarative_base
|
||||||
|
|
||||||
Base = declarative_base()
|
Base = declarative_base()
|
||||||
|
|
||||||
@@ -56,12 +56,12 @@ class Origin(Base):
|
|||||||
session.close()
|
session.close()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def delete(engine: Engine, origins: ["Origin"] = None) -> int:
|
def delete(engine: Engine, origin_refs: [str] = None) -> int:
|
||||||
session = sessionmaker(bind=engine)()
|
session = sessionmaker(bind=engine)()
|
||||||
if origins is None:
|
if origin_refs is None:
|
||||||
deletions = session.query(Origin).delete()
|
deletions = session.query(Origin).delete()
|
||||||
else:
|
else:
|
||||||
deletions = session.query(Origin).filter(Origin.origin_ref in origins).delete()
|
deletions = session.query(Origin).filter(Origin.origin_ref in origin_refs).delete()
|
||||||
session.commit()
|
session.commit()
|
||||||
session.close()
|
session.close()
|
||||||
return deletions
|
return deletions
|
||||||
@@ -81,7 +81,10 @@ class Lease(Base):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f'Lease(origin_ref={self.origin_ref}, lease_ref={self.lease_ref}, expires={self.lease_expires})'
|
return f'Lease(origin_ref={self.origin_ref}, lease_ref={self.lease_ref}, expires={self.lease_expires})'
|
||||||
|
|
||||||
def serialize(self) -> dict:
|
def serialize(self, renewal_period: float, renewal_delta: timedelta) -> dict:
|
||||||
|
lease_renewal = int(Lease.calculate_renewal(renewal_period, renewal_delta).total_seconds())
|
||||||
|
lease_renewal = self.lease_updated + relativedelta(seconds=lease_renewal)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'lease_ref': self.lease_ref,
|
'lease_ref': self.lease_ref,
|
||||||
'origin_ref': self.origin_ref,
|
'origin_ref': self.origin_ref,
|
||||||
@@ -89,6 +92,7 @@ class Lease(Base):
|
|||||||
'lease_created': self.lease_created.isoformat(),
|
'lease_created': self.lease_created.isoformat(),
|
||||||
'lease_expires': self.lease_expires.isoformat(),
|
'lease_expires': self.lease_expires.isoformat(),
|
||||||
'lease_updated': self.lease_updated.isoformat(),
|
'lease_updated': self.lease_updated.isoformat(),
|
||||||
|
'lease_renewal': lease_renewal.isoformat(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -133,7 +137,7 @@ class Lease(Base):
|
|||||||
return entity
|
return entity
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def renew(engine: Engine, lease: "Lease", lease_expires: datetime.datetime, lease_updated: datetime.datetime):
|
def renew(engine: Engine, lease: "Lease", lease_expires: datetime, lease_updated: datetime):
|
||||||
session = sessionmaker(bind=engine)()
|
session = sessionmaker(bind=engine)()
|
||||||
x = dict(lease_expires=lease_expires, lease_updated=lease_updated)
|
x = dict(lease_expires=lease_expires, lease_updated=lease_updated)
|
||||||
session.execute(update(Lease).where(and_(Lease.origin_ref == lease.origin_ref, Lease.lease_ref == lease.lease_ref)).values(**x))
|
session.execute(update(Lease).where(and_(Lease.origin_ref == lease.origin_ref, Lease.lease_ref == lease.lease_ref)).values(**x))
|
||||||
@@ -156,6 +160,28 @@ class Lease(Base):
|
|||||||
session.close()
|
session.close()
|
||||||
return deletions
|
return deletions
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def calculate_renewal(renewal_period: float, delta: timedelta) -> timedelta:
|
||||||
|
"""
|
||||||
|
import datetime
|
||||||
|
LEASE_RENEWAL_PERIOD=0.2 # 20%
|
||||||
|
delta = datetime.timedelta(days=1)
|
||||||
|
renew = delta.total_seconds() * LEASE_RENEWAL_PERIOD
|
||||||
|
renew = datetime.timedelta(seconds=renew)
|
||||||
|
expires = delta - renew # 19.2
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
LEASE_RENEWAL_PERIOD=0.15 # 15%
|
||||||
|
delta = datetime.timedelta(days=90)
|
||||||
|
renew = delta.total_seconds() * LEASE_RENEWAL_PERIOD
|
||||||
|
renew = datetime.timedelta(seconds=renew)
|
||||||
|
expires = delta - renew # 76 days, 12:00:00 hours
|
||||||
|
|
||||||
|
"""
|
||||||
|
renew = delta.total_seconds() * renewal_period
|
||||||
|
renew = timedelta(seconds=renew)
|
||||||
|
return renew
|
||||||
|
|
||||||
|
|
||||||
def init(engine: Engine):
|
def init(engine: Engine):
|
||||||
tables = [Origin, Lease]
|
tables = [Origin, Lease]
|
||||||
@@ -163,7 +189,7 @@ def init(engine: Engine):
|
|||||||
session = sessionmaker(bind=engine)()
|
session = sessionmaker(bind=engine)()
|
||||||
for table in tables:
|
for table in tables:
|
||||||
if not db.dialect.has_table(engine.connect(), table.__tablename__):
|
if not db.dialect.has_table(engine.connect(), table.__tablename__):
|
||||||
session.execute(str(table.create_statement(engine)))
|
session.execute(text(str(table.create_statement(engine))))
|
||||||
session.commit()
|
session.commit()
|
||||||
session.close()
|
session.close()
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
<<: *dls-variables
|
<<: *dls-variables
|
||||||
volumes:
|
volumes:
|
||||||
|
- /etc/timezone:/etc/timezone:ro
|
||||||
- /opt/docker/fastapi-dls/cert:/app/cert # instance.private.pem, instance.public.pem
|
- /opt/docker/fastapi-dls/cert:/app/cert # instance.private.pem, instance.public.pem
|
||||||
- db:/app/database
|
- db:/app/database
|
||||||
entrypoint: ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--app-dir", "/app", "--proxy-headers"]
|
entrypoint: ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--app-dir", "/app", "--proxy-headers"]
|
||||||
@@ -30,6 +31,7 @@ services:
|
|||||||
- "80:80" # for "/leasing/v1/lessor/shutdown" used by windows guests, can't be changed!
|
- "80:80" # for "/leasing/v1/lessor/shutdown" used by windows guests, can't be changed!
|
||||||
- "443:443" # first part must match "DLS_PORT"
|
- "443:443" # first part must match "DLS_PORT"
|
||||||
volumes:
|
volumes:
|
||||||
|
- /etc/timezone:/etc/timezone:ro
|
||||||
- /opt/docker/fastapi-dls/cert:/opt/cert
|
- /opt/docker/fastapi-dls/cert:/opt/cert
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "--insecure", "--fail", "https://localhost/-/health"]
|
test: ["CMD", "curl", "--insecure", "--fail", "https://localhost/-/health"]
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
fastapi==0.89.1
|
fastapi==0.89.1
|
||||||
uvicorn[standard]==0.20.0
|
uvicorn[standard]==0.20.0
|
||||||
python-jose==3.3.0
|
python-jose==3.3.0
|
||||||
pycryptodome==3.16.0
|
pycryptodome==3.17
|
||||||
python-dateutil==2.8.2
|
python-dateutil==2.8.2
|
||||||
sqlalchemy==1.4.46
|
sqlalchemy==2.0.0
|
||||||
markdown==3.4.1
|
markdown==3.4.1
|
||||||
python-dotenv==0.21.0
|
python-dotenv==0.21.1
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
VERSION=1.3.1
|
VERSION=1.3.5
|
||||||
|
|||||||
Reference in New Issue
Block a user