Compare commits

..

1 Commits

18 changed files with 333 additions and 920 deletions

View File

@ -1,11 +0,0 @@
# https://packages.debian.org/hu/
fastapi==0.92.0
uvicorn[standard]==0.17.6
python-jose[pycryptodome]==3.3.0
pycryptodome==3.11.0
python-dateutil==2.8.2
sqlalchemy==1.4.46
markdown==3.4.1
python-dotenv==0.21.0
jinja2==3.1.2
httpx==0.23.3

View File

@ -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

View File

@ -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

View File

@ -1,10 +0,0 @@
# https://packages.ubuntu.com
fastapi==0.101.0
uvicorn[standard]==0.27.1
python-jose[pycryptodome]==3.3.0
pycryptodome==3.20.0
python-dateutil==2.8.2
sqlalchemy==1.4.50
markdown==3.5.2
python-dotenv==1.0.1
jinja2==3.1.2

View File

@ -12,7 +12,7 @@ depends=('python' 'python-jose' 'python-starlette' 'python-httpx' 'python-fastap
provider=("$pkgname") provider=("$pkgname")
install="$pkgname.install" install="$pkgname.install"
backup=('etc/default/fastapi-dls') backup=('etc/default/fastapi-dls')
source=("git+file://${CI_PROJECT_DIR}" 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"
"$pkgname.tmpfiles") "$pkgname.tmpfiles")
@ -22,9 +22,8 @@ sha256sums=('SKIP'
'3dc60140c08122a8ec0e7fa7f0937eb8c1288058890ba09478420fc30ce9e30c') '3dc60140c08122a8ec0e7fa7f0937eb8c1288058890ba09478420fc30ce9e30c')
pkgver() { pkgver() {
echo -e "VERSION=$VERSION\nCOMMIT=$CI_COMMIT_SHA" > $srcdir/$pkgname/version.env
source $srcdir/$pkgname/version.env source $srcdir/$pkgname/version.env
echo $VERSION echo ${VERSION}
} }
check() { check() {

View File

@ -1,48 +0,0 @@
<?xml version="1.0"?>
<Container version="2">
<Name>FastAPI-DLS</Name>
<Repository>collinwebdesigns/fastapi-dls:latest</Repository>
<Registry>https://hub.docker.com/r/collinwebdesigns/fastapi-dls</Registry>
<Network>br0</Network>
<MyIP></MyIP>
<Shell>sh</Shell>
<Privileged>false</Privileged>
<Support/>
<Project/>
<Overview>Source:&#xD;
https://git.collinwebdesigns.de/oscar.krause/fastapi-dls#docker&#xD;
&#xD;
Make sure you create these certificates before starting the container for the first time:&#xD;
```&#xD;
# Check https://git.collinwebdesigns.de/oscar.krause/fastapi-dls/-/tree/main/#docker for more information:&#xD;
WORKING_DIR=/mnt/user/appdata/fastapi-dls/cert&#xD;
mkdir -p $WORKING_DIR&#xD;
cd $WORKING_DIR&#xD;
# create instance private and public key for singing JWT's&#xD;
openssl genrsa -out $WORKING_DIR/instance.private.pem 2048 &#xD;
openssl rsa -in $WORKING_DIR/instance.private.pem -outform PEM -pubout -out $WORKING_DIR/instance.public.pem&#xD;
# create ssl certificate for integrated webserver (uvicorn) - because clients rely on ssl&#xD;
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout $WORKING_DIR/webserver.key -out $WORKING_DIR/webserver.crt&#xD;
```&#xD;
</Overview>
<Category/>
<WebUI>https://[IP]:[PORT:443]</WebUI>
<TemplateURL/>
<Icon>https://git.collinwebdesigns.de/uploads/-/system/project/avatar/106/png-transparent-nvidia-grid-logo-business-nvidia-electronics-text-trademark.png?width=64</Icon>
<ExtraParams>--restart always</ExtraParams>
<PostArgs/>
<CPUset/>
<DateInstalled>1679161568</DateInstalled>
<DonateText/>
<DonateLink/>
<Requires/>
<Config Name="HTTPS Port" Target="" Default="443" Mode="tcp" Description="Same as DLS Port below." Type="Port" Display="always-hide" Required="true" Mask="false">443</Config>
<Config Name="App Cert" Target="/app/cert" Default="/mnt/user/appdata/fastapi-dls/cert" Mode="rw" Description="[REQUIRED] Read the description above to make this folder. &#13;&#10;&#13;&#10;You do not need to change the path." Type="Path" Display="always-hide" Required="true" Mask="false">/mnt/user/appdata/fastapi-dls/cert</Config>
<Config Name="DLS Port" Target="DSL_PORT" Default="443" Mode="" Description="Choose port you want to use. Make sure to change the HTTPS port above to match it." Type="Variable" Display="always-hide" Required="true" Mask="false">443</Config>
<Config Name="App database" Target="/app/database" Default="/mnt/user/appdata/fastapi-dls/data" Mode="rw" Description="[REQUIRED] Read the description above to make this folder. &#13;&#10;&#13;&#10;You do not need to change the path." Type="Path" Display="always-hide" Required="true" Mask="false">/mnt/user/appdata/fastapi-dls/data</Config>
<Config Name="DSL IP" Target="DLS_URL" Default="localhost" Mode="" Description="Put your container's IP (or your host's IP if it's shared)." Type="Variable" Display="always-hide" Required="true" Mask="false"></Config>
<Config Name="Time Zone" Target="TZ" Default="" Mode="" Description="Format example: America/New_York. MUST MATCH YOUR CURRENT TIMEZONE AND THE GUEST VMS TIMEZONE! Otherwise you'll get into issues, read the guide above." Type="Variable" Display="always-hide" Required="true" Mask="false"></Config>
<Config Name="Database" Target="DATABASE" Default="sqlite:////app/database/db.sqlite" Mode="" Description="Set to sqlite:////app/database/db.sqlite" Type="Variable" Display="advanced-hide" Required="true" Mask="false">sqlite:////app/database/db.sqlite</Config>
<Config Name="Debug" Target="DEBUG" Default="true" Mode="" Description="true to enable debugging, false to disable them." Type="Variable" Display="advanced-hide" Required="false" Mask="false">true</Config>
<Config Name="Lease" Target="LEASE_EXPIRE_DAYS" Default="90" Mode="" Description="90 days is the maximum value." Type="Variable" Display="advanced" Required="false" Mask="false">90</Config>
</Container>

View File

@ -1,197 +0,0 @@
#!/bin/bash
# This script automates the licensing of the vGPU guest driver
# on Unraid boot. Set the Schedule to: "At Startup of Array".
#
# Relies on FastAPI-DLS for the licensing.
# It assumes FeatureType=1 (vGPU), change it as you see fit in line <114>
#
# Requires `eflutils` to be installed in the system for `nvidia-gridd` to run
# To Install it:
# 1) You might find it here: https://packages.slackware.com/ (choose the 64bit version of Slackware)
# 2) Download the package and put it in /boot/extra to be installed on boot
# 3) a. Reboot to install it, OR
# b. Run `upgradepkg --install-new /boot/extra/elfutils*`
# [i]: Make sure to have only one version of elfutils, otherwise you might run into issues
# Sources and docs:
# https://docs.nvidia.com/grid/15.0/grid-vgpu-user-guide/index.html#configuring-nls-licensed-client-on-linux
#
################################################
# MAKE SURE YOU CHANGE THESE VARIABLES #
################################################
###### CHANGE ME!
# IP and PORT of FastAPI-DLS
DLS_IP=192.168.0.123
DLS_PORT=443
# Token folder, must be on a filesystem that supports
# linux filesystem permissions (eg: ext4,xfs,btrfs...)
TOKEN_PATH=/mnt/user/system/nvidia
PING=$(which ping)
# Check if the License is applied
if [[ "$(nvidia-smi -q | grep "Expiry")" == *Expiry* ]]; then
echo " [i] Your vGPU Guest drivers are already licensed."
echo " [i] $(nvidia-smi -q | grep "Expiry")"
echo " [<] Exiting..."
exit 0
fi
# Check if the FastAPI-DLS server is reachable
# Check if the License is applied
MAX_RETRIES=30
for i in $(seq 1 $MAX_RETRIES); do
echo -ne "\r [>] Attempt $i to connect to $DLS_IP."
if ping -c 1 $DLS_IP >/dev/null 2>&1; then
echo -e "\n [*] Connection successful."
break
fi
if [ $i -eq $MAX_RETRIES ]; then
echo -e "\n [!] Connection failed after $MAX_RETRIES attempts."
echo -e "\n [<] Exiting..."
exit 1
fi
sleep 1
done
# Check if the token folder exists
if [ -d "${TOKEN_PATH}" ]; then
echo " [*] Token Folder exists. Proceeding..."
else
echo " [!] Token Folder does not exists or not ready yet. Exiting."
echo " [!] Token Folder Specified: ${TOKEN_PATH}"
exit 1
fi
# Check if elfutils are installed, otherwise nvidia-gridd service
# wont start
if [ "$(grep -R "elfutils" /var/log/packages/* | wc -l)" != 0 ]; then
echo " [*] Elfutils is installed, proceeding..."
else
echo " [!] Elfutils is not installed, downloading and installing..."
echo " [!] Downloading elfutils to /boot/extra"
echo " [i] This script will download elfutils from slackware64-15.0 repository."
echo " [i] If you have a different version of Unraid (6.11.5), you might want to"
echo " [i] download and install a suitable version manually from the slackware"
echo " [i] repository, and put it in /boot/extra to be install on boot."
echo " [i] You may also install it by running: "
echo " [i] upgradepkg --install-new /path/to/elfutils-*.txz"
echo ""
echo " [>] Downloading elfutils from slackware64-15.0 repository:"
wget -q -nc --show-progress --progress=bar:force:noscroll -P /boot/extra https://slackware.uk/slackware/slackware64-15.0/slackware64/l/elfutils-0.186-x86_64-1.txz 2>/dev/null \
|| { echo " [!] Error while downloading elfutils, please download it and install it manually."; exit 1; }
echo ""
if upgradepkg --install-new /boot/extra/elfutils-0.186-x86_64-1.txz
then
echo " [*] Elfutils installed and will be installed automatically on boot"
else
echo " [!] Error while installing, check logs..."
exit 1
fi
fi
echo " [~] Sleeping for 60 seconds before continuing..."
echo " [i] The script is waiting until the boot process settles down."
for i in {60..1}; do
printf "\r [~] %d seconds remaining" "$i"
sleep 1
done
printf "\n"
create_token () {
echo " [>] Creating new token..."
if ${PING} -c1 ${DLS_IP} > /dev/null 2>&1
then
# curl --insecure -L -X GET https://${DLS_IP}:${DLS_PORT}/-/client-token -o ${TOKEN_PATH}/client_configuration_token_"$(date '+%d-%m-%Y-%H-%M-%S')".tok || { echo " [!] Could not get the token, please check the server."; exit 1;}
wget -q -nc -4c --no-check-certificate --show-progress --progress=bar:force:noscroll -O "${TOKEN_PATH}"/client_configuration_token_"$(date '+%d-%m-%Y-%H-%M-%S')".tok https://${DLS_IP}:${DLS_PORT}/-/client-token \
|| { echo " [!] Could not get the token, please check the server."; exit 1;}
chmod 744 "${TOKEN_PATH}"/*.tok || { echo " [!] Could not chmod the tokens."; exit 1; }
echo ""
echo " [*] Token downloaded and stored in ${TOKEN_PATH}."
else
echo " [!] Could not get token, DLS server unavailable ."
exit 1
fi
}
setup_run () {
echo " [>] Setting up gridd.conf"
cp /etc/nvidia/gridd.conf.template /etc/nvidia/gridd.conf || { echo " [!] Error configuring gridd.conf, did you install the drivers correctly?"; exit 1; }
sed -i 's/FeatureType=0/FeatureType=1/g' /etc/nvidia/gridd.conf
echo "ClientConfigTokenPath=${TOKEN_PATH}" >> /etc/nvidia/gridd.conf
echo " [>] Creating /var/lib/nvidia folder structure"
mkdir -p /var/lib/nvidia/GridLicensing
echo " [>] Starting nvidia-gridd"
if pgrep nvidia-gridd >/dev/null 2>&1; then
echo " [!] nvidia-gridd service is running. Closing."
sh /usr/lib/nvidia/sysv/nvidia-gridd stop
stop_exit_code=$?
if [ $stop_exit_code -eq 0 ]; then
echo " [*] nvidia-gridd service stopped successfully."
else
echo " [!] Error while stopping nvidia-gridd service."
exit 1
fi
# Kill the service if it does not close
if pgrep nvidia-gridd >/dev/null 2>&1; then
kill -9 "$(pgrep nvidia-gridd)" || {
echo " [!] Error while closing nvidia-gridd service"
exit 1
}
fi
echo " [*] Restarting nvidia-gridd service."
sh /usr/lib/nvidia/sysv/nvidia-gridd start
if pgrep nvidia-gridd >/dev/null 2>&1; then
echo " [*] Service started, PID: $(pgrep nvidia-gridd)"
else
echo -e " [!] Error while starting nvidia-gridd service. Use strace -f nvidia-gridd to debug.\n [i] Check if elfutils is installed.\n [i] strace is not installed by default."
exit 1
fi
else
sh /usr/lib/nvidia/sysv/nvidia-gridd start
if pgrep nvidia-gridd >/dev/null 2>&1; then
echo " [*] Service started, PID: $(pgrep nvidia-gridd)"
else
echo -e " [!] Error while starting nvidia-gridd service. Use strace -f nvidia-gridd to debug.\n [i] Check if elfutils is installed.\n [i] strace is not installed by default."
exit 1
fi
fi
}
for token in "${TOKEN_PATH}"/*; do
if [ "${token: -4}" == ".tok" ]
then
echo " [*] Tokens found..."
setup_run
else
echo " [!] No Tokens found..."
create_token
setup_run
fi
done
while true; do
if nvidia-smi -q | grep "Expiry" >/dev/null 2>&1; then
echo " [>] vGPU licensed!"
echo " [i] $(nvidia-smi -q | grep "Expiry")"
break
else
echo -ne " [>] vGPU not licensed yet... Checking again in 5 seconds\c"
for i in {1..5}; do
sleep 1
echo -ne ".\c"
done
echo -ne "\r\c"
fi
done
echo " [>] Done..."
exit 0

View File

@ -1,9 +1,7 @@
version: "2"
plugins: plugins:
bandit: bandit:
enabled: true enabled: true
sonar-python: sonar-python:
enabled: true enabled: true
config: pylint:
tests_patterns: enabled: true
- test/**

View File

@ -1,175 +1,120 @@
include: # You can override the included template(s) by including variable overrides
- template: Jobs/Code-Quality.gitlab-ci.yml # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
- template: Jobs/Secret-Detection.gitlab-ci.yml # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings
- template: Jobs/SAST.gitlab-ci.yml # Dependency Scanning customization: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-the-dependency-scanning-settings
- template: Jobs/Container-Scanning.gitlab-ci.yml # Container Scanning customization: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings
- template: Jobs/Dependency-Scanning.gitlab-ci.yml # Note that environment variables can be set in several places
# See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence
cache: cache:
key: one-key-to-rule-them-all key: one-key-to-rule-them-all
variables:
DOCKER_BUILDX_PLATFORM: "linux/amd64,linux/arm64"
build:docker: build:docker:
image: docker:dind image: docker:dind
interruptible: true interruptible: true
stage: build stage: build
rules: rules:
- if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH - if: "$CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH"
changes: changes:
- app/**/* - app/**/*
- Dockerfile - Dockerfile
- if: $CI_PIPELINE_SOURCE == 'merge_request_event' - if: "$CI_PIPELINE_SOURCE == 'merge_request_event'"
tags: [ docker ] tags:
- docker
before_script: before_script:
- docker buildx inspect - echo "COMMIT=${CI_COMMIT_SHA}" >> version.env
- docker buildx create --use
script: script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- IMAGE=$CI_REGISTRY/$CI_PROJECT_PATH/$CI_COMMIT_REF_NAME:$CI_COMMIT_SHA - docker build . --tag ${CI_REGISTRY}/${CI_PROJECT_PATH}/${CI_BUILD_REF_NAME}:${CI_BUILD_REF}
- docker buildx build --progress=plain --platform $DOCKER_BUILDX_PLATFORM --build-arg VERSION=$CI_COMMIT_REF_NAME --build-arg COMMIT=$CI_COMMIT_SHA --tag $IMAGE --push . - docker push ${CI_REGISTRY}/${CI_PROJECT_PATH}/${CI_BUILD_REF_NAME}:${CI_BUILD_REF}
- docker buildx imagetools inspect $IMAGE
- echo "CS_IMAGE=$IMAGE" > container_scanning.env
artifacts:
reports:
dotenv: container_scanning.env
build:apt: build:apt:
image: debian:bookworm-slim image: debian:bookworm-slim
interruptible: true interruptible: true
stage: build stage: build
rules: rules:
- if: $CI_COMMIT_TAG - if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"
variables: - if: "$CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH"
VERSION: $CI_COMMIT_REF_NAME
- if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
changes: changes:
- app/**/* - app/**/*
- .DEBIAN/**/* - ".DEBIAN/**/*"
- .gitlab-ci.yml - if: "$CI_PIPELINE_SOURCE == 'merge_request_event'"
variables:
VERSION: "0.0.1"
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
variables:
VERSION: "0.0.1"
before_script: before_script:
- echo -e "VERSION=$VERSION\nCOMMIT=$CI_COMMIT_SHA" > version.env - echo "COMMIT=${CI_COMMIT_SHA}" >> version.env
# install build dependencies - source version.env
- apt-get update -qq && apt-get install -qq -y build-essential - apt-get update -qq && apt-get install -qq -y build-essential
# create build directory for .deb sources
- mkdir build - mkdir build
# copy install instructions
- cp -r .DEBIAN build/DEBIAN - cp -r .DEBIAN build/DEBIAN
- chmod -R 0775 build/DEBIAN - chmod -R 0775 build/DEBIAN
# copy app into "/usr/share/fastapi-dls" as "/usr/share/fastapi-dls/app" & copy README.md and version.env
- mkdir -p build/usr/share/fastapi-dls - mkdir -p build/usr/share/fastapi-dls
- cp -r app build/usr/share/fastapi-dls - cp -r app build/usr/share/fastapi-dls
- cp README.md version.env build/usr/share/fastapi-dls - cp README.md version.env build/usr/share/fastapi-dls
# create conf file
- mkdir -p build/etc/fastapi-dls - mkdir -p build/etc/fastapi-dls
- cp .DEBIAN/env.default build/etc/fastapi-dls/env - cp .DEBIAN/env.default build/etc/fastapi-dls/env
# create service file
- mkdir -p build/etc/systemd/system - mkdir -p build/etc/systemd/system
- cp .DEBIAN/fastapi-dls.service build/etc/systemd/system/fastapi-dls.service - cp .DEBIAN/fastapi-dls.service build/etc/systemd/system/fastapi-dls.service
# cd into "build/"
- cd build/ - cd build/
script: script:
# set version based on value in "$CI_COMMIT_REF_NAME"
- sed -i -E 's/(Version\:\s)0.0/\1'"$VERSION"'/g' DEBIAN/control - sed -i -E 's/(Version\:\s)0.0/\1'"$VERSION"'/g' DEBIAN/control
# build
- dpkg -b . build.deb - dpkg -b . build.deb
- dpkg -I build.deb - dpkg -I build.deb
artifacts: artifacts:
expire_in: 1 week expire_in: 1 week
paths: paths:
- build/build.deb - build/build.deb
build:pacman: build:pacman:
image: archlinux:base-devel image: archlinux:base-devel
interruptible: true interruptible: true
stage: build stage: build
rules: rules:
- if: $CI_COMMIT_TAG - if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"
variables: - if: "$CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH"
VERSION: $CI_COMMIT_REF_NAME
- if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
changes: changes:
- app/**/* - app/**/*
- .PKGBUILD/**/* - ".PKGBUILD/**/*"
- .gitlab-ci.yml - if: "$CI_PIPELINE_SOURCE == 'merge_request_event'"
variables:
VERSION: "0.0.1"
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
variables:
VERSION: "0.0.1"
before_script: before_script:
#- echo -e "VERSION=$VERSION\nCOMMIT=$CI_COMMIT_SHA" > version.env - echo "COMMIT=${CI_COMMIT_SHA}" >> version.env
# install build dependencies
- pacman -Syu --noconfirm git - pacman -Syu --noconfirm git
# create a build-user because "makepkg" don't like root user
- useradd --no-create-home --shell=/bin/false build && usermod -L build - useradd --no-create-home --shell=/bin/false build && usermod -L build
- 'echo "build ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers' - 'echo "build ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers'
- 'echo "root ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers' - 'echo "root ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers'
- chown -R build:build . - chown -R build:build .
# move .PKGBUILD contents to root directory
- mv .PKGBUILD/* . - mv .PKGBUILD/* .
script: script:
- pwd - pwd
# download dependencies - source PKGBUILD && pacman -Syu --noconfirm --needed --asdeps "${makedepends[@]}"
- source PKGBUILD && pacman -Syu --noconfirm --needed --asdeps "${makedepends[@]}" "${depends[@]}" "${depends[@]}"
# build - sudo -u build makepkg -s
- sudo --preserve-env -u build makepkg -s
artifacts: artifacts:
expire_in: 1 week expire_in: 1 week
paths: paths:
- "*.pkg.tar.zst" - "*.pkg.tar.zst"
test: test:
image: $IMAGE image: python:3.11-slim-bullseye
stage: test stage: test
interruptible: true
rules: rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - if: "$CI_COMMIT_BRANCH"
- if: $CI_COMMIT_TAG
- if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
changes:
- app/**/*
- test/**/*
variables: variables:
DATABASE: sqlite:///../app/db.sqlite 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
before_script: before_script:
- apt-get update && apt-get install -y python3-dev gcc - pip install -r requirements.txt
- pip install -r $REQUIREMENTS
- pip install pytest httpx - pip install pytest httpx
- mkdir -p app/cert - mkdir -p app/cert
- openssl genrsa -out app/cert/instance.private.pem 2048 - openssl genrsa -out app/cert/instance.private.pem 2048
- openssl rsa -in app/cert/instance.private.pem -outform PEM -pubout -out app/cert/instance.public.pem - openssl rsa -in app/cert/instance.private.pem -outform PEM -pubout -out app/cert/instance.public.pem
- cd test - cd test
script: script:
- python -m pytest main.py --junitxml=report.xml - pytest main.py
artifacts: artifacts:
reports: reports:
dotenv: version.env dotenv: version.env
junit: ['**/report.xml'] ".test:linux":
.test:linux:
stage: test stage: test
rules: rules:
- if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH - if: "$CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH"
changes: changes:
- app/**/* - app/**/*
- .DEBIAN/**/* - ".DEBIAN/**/*"
- if: $CI_PIPELINE_SOURCE == 'merge_request_event' - if: "$CI_PIPELINE_SOURCE == 'merge_request_event'"
needs: needs:
- job: build:apt - job: build:apt
artifacts: true artifacts: true
@ -178,197 +123,113 @@ test:
before_script: before_script:
- apt-get update -qq && apt-get install -qq -y jq curl - apt-get update -qq && apt-get install -qq -y jq curl
script: script:
# test installation
- apt-get install -q -y ./build/build.deb --fix-missing - apt-get install -q -y ./build/build.deb --fix-missing
- openssl req -x509 -newkey rsa:2048 -nodes -out /etc/fastapi-dls/webserver.crt -keyout /etc/fastapi-dls/webserver.key -days 7 -subj "/C=DE/O=GitLab-CI/OU=Test/CN=localhost" - openssl req -x509 -newkey rsa:2048 -nodes -out /etc/fastapi-dls/webserver.crt
# copy example config from GitLab-CI-Variables -keyout /etc/fastapi-dls/webserver.key -days 7 -subj "/C=DE/O=GitLab-CI/OU=Test/CN=localhost"
#- cat ${EXAMPLE_CONFIG} > /etc/fastapi-dls/env
# start service in background
- cd /usr/share/fastapi-dls/app - cd /usr/share/fastapi-dls/app
- uvicorn main:app - uvicorn main:app --host 127.0.0.1 --port 443 --app-dir /usr/share/fastapi-dls/app
--host 127.0.0.1 --port 443 --ssl-keyfile /etc/fastapi-dls/webserver.key --ssl-certfile /etc/fastapi-dls/webserver.crt
--app-dir /usr/share/fastapi-dls/app
--ssl-keyfile /etc/fastapi-dls/webserver.key
--ssl-certfile /etc/fastapi-dls/webserver.crt
--proxy-headers & --proxy-headers &
- FASTAPI_DLS_PID=$! - FASTAPI_DLS_PID=$!
- echo "Started service with pid $FASTAPI_DLS_PID" - echo "Started service with pid $FASTAPI_DLS_PID"
- cat /etc/fastapi-dls/env - cat /etc/fastapi-dls/env
# testing service - if [ "`curl --insecure -s https://127.0.0.1/-/health | jq .status`" != "up" ];
- if [ "`curl --insecure -s https://127.0.0.1/-/health | jq .status`" != "up" ]; then echo "Success"; else "Error"; fi then echo "Success"; else "Error"; fi
# cleanup
- kill $FASTAPI_DLS_PID - kill $FASTAPI_DLS_PID
- apt-get purge -qq -y fastapi-dls - apt-get purge -qq -y fastapi-dls
- apt-get autoremove -qq -y && apt-get clean -qq - apt-get autoremove -qq -y && apt-get clean -qq
test:debian: test:debian:
extends: .test:linux extends: ".test:linux"
image: debian:bookworm-slim image: debian:bookworm-slim
test:ubuntu: test:ubuntu:
extends: .test:linux extends: ".test:linux"
image: ubuntu:24.04 image: ubuntu:22.10
test:archlinux: test:archlinux:
image: archlinux:base image: archlinux:base
rules: rules:
- if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH - if: "$CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH"
changes: changes:
- app/**/* - app/**/*
- .PKGBUILD/**/* - ".PKGBUILD/**/*"
- if: $CI_PIPELINE_SOURCE == 'merge_request_event' - if: "$CI_PIPELINE_SOURCE == 'merge_request_event'"
needs: needs:
- job: build:pacman - job: build:pacman
artifacts: true artifacts: true
script: script:
- pacman -Sy - pacman -Sy
- pacman -U --noconfirm *.pkg.tar.zst - pacman -U --noconfirm *.pkg.tar.zst
".deploy":
code_quality:
variables:
SOURCE_CODE: app
rules: rules:
- if: $CODE_QUALITY_DISABLED - if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"
- if: "$CI_COMMIT_TAG"
when: never when: never
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
secret_detection:
rules:
- if: $SECRET_DETECTION_DISABLED
when: never
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
before_script:
- git config --global --add safe.directory $CI_PROJECT_DIR
semgrep-sast:
rules:
- if: $SAST_DISABLED
when: never
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
test_coverage:
# extends: test
image: python:3.11-slim-bookworm
allow_failure: true
stage: test
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
variables:
DATABASE: sqlite:///../app/db.sqlite
before_script:
- apt-get update && apt-get install -y python3-dev gcc
- pip install -r requirements.txt
- pip install pytest httpx
- mkdir -p app/cert
- openssl genrsa -out app/cert/instance.private.pem 2048
- openssl rsa -in app/cert/instance.private.pem -outform PEM -pubout -out app/cert/instance.public.pem
- cd test
script:
- pip install pytest pytest-cov
- coverage run -m pytest main.py
- coverage report
- coverage xml
coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/'
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: '**/coverage.xml'
container_scanning:
dependencies: [ build:docker ]
rules:
- if: $CONTAINER_SCANNING_DISABLED
when: never
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
gemnasium-python-dependency_scanning:
rules:
- if: $DEPENDENCY_SCANNING_DISABLED
when: never
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
.deploy:
rules:
- if: $CI_COMMIT_TAG
deploy:docker: deploy:docker:
extends: .deploy extends: ".deploy"
image: docker:dind
stage: deploy stage: deploy
tags: [ docker ] rules:
- if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"
before_script: before_script:
- echo "Building docker image for commit $CI_COMMIT_SHA with version $CI_COMMIT_REF_NAME" - echo "COMMIT=${CI_COMMIT_SHA}" >> version.env
- docker buildx inspect - source version.env
- docker buildx create --use - echo "Building docker image for commit ${COMMIT} with version ${VERSION}"
script: script:
- echo "========== GitLab-Registry ==========" - echo "GitLab-Registry"
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- IMAGE=$CI_REGISTRY/$CI_PROJECT_PATH - docker build . --tag ${CI_REGISTRY}/${CI_PROJECT_PATH}/${CI_BUILD_REF_NAME}:${VERSION}
- docker buildx build --progress=plain --platform $DOCKER_BUILDX_PLATFORM --build-arg VERSION=$CI_COMMIT_REF_NAME --build-arg COMMIT=$CI_COMMIT_SHA --tag $IMAGE:$CI_COMMIT_REF_NAME --push . - docker build . --tag ${CI_REGISTRY}/${CI_PROJECT_PATH}/${CI_BUILD_REF_NAME}:latest
- docker buildx build --progress=plain --platform $DOCKER_BUILDX_PLATFORM --build-arg VERSION=$CI_COMMIT_REF_NAME --build-arg COMMIT=$CI_COMMIT_SHA --tag $IMAGE:latest --push . - docker push ${CI_REGISTRY}/${CI_PROJECT_PATH}/${CI_BUILD_REF_NAME}:${VERSION}
- echo "========== Docker-Hub ==========" - docker push ${CI_REGISTRY}/${CI_PROJECT_PATH}/${CI_BUILD_REF_NAME}:latest
- echo "Docker-Hub"
- docker login -u $PUBLIC_REGISTRY_USER -p $PUBLIC_REGISTRY_TOKEN - docker login -u $PUBLIC_REGISTRY_USER -p $PUBLIC_REGISTRY_TOKEN
- IMAGE=$PUBLIC_REGISTRY_USER/$CI_PROJECT_NAME - docker build . --tag $PUBLIC_REGISTRY_USER/${CI_PROJECT_NAME}:${VERSION}
- docker buildx build --progress=plain --platform $DOCKER_BUILDX_PLATFORM --build-arg VERSION=$CI_COMMIT_REF_NAME --build-arg COMMIT=$CI_COMMIT_SHA --tag $IMAGE:$CI_COMMIT_REF_NAME --push . - docker build . --tag $PUBLIC_REGISTRY_USER/${CI_PROJECT_NAME}:latest
- docker buildx build --progress=plain --platform $DOCKER_BUILDX_PLATFORM --build-arg VERSION=$CI_COMMIT_REF_NAME --build-arg COMMIT=$CI_COMMIT_SHA --tag $IMAGE:latest --push . - docker push $PUBLIC_REGISTRY_USER/${CI_PROJECT_NAME}:${VERSION}
- docker push $PUBLIC_REGISTRY_USER/${CI_PROJECT_NAME}:latest
deploy:apt: deploy:apt:
# doc: https://git.collinwebdesigns.de/help/user/packages/debian_repository/index.md#install-a-package extends: ".deploy"
extends: .deploy
image: debian:bookworm-slim image: debian:bookworm-slim
stage: deploy stage: deploy
rules:
- if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"
needs: needs:
- job: build:apt - job: build:apt
artifacts: true artifacts: true
before_script: before_script:
- apt-get update -qq && apt-get install -qq -y curl lsb-release - apt-get update -qq && apt-get install -qq -y curl lsb-release
# create distribution initial
- CODENAME=`lsb_release -cs` - CODENAME=`lsb_release -cs`
# create repo if not exists - 'if [ "`curl -s -o /dev/null -w "%{http_code}" --header "JOB-TOKEN: $CI_JOB_TOKEN"
- 'if [ "`curl -s -o /dev/null -w "%{http_code}" --header "JOB-TOKEN: $CI_JOB_TOKEN" -s ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/debian_distributions/${CODENAME}/key.asc`" != "200" ]; then curl --request POST --header "JOB-TOKEN: $CI_JOB_TOKEN" "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/debian_distributions?codename=${CODENAME}"; fi' -s ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/debian_distributions/${CODENAME}/key.asc`"
!= "200" ]; then curl --request POST --header "JOB-TOKEN: $CI_JOB_TOKEN" "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/debian_distributions?codename=${CODENAME}";
fi'
script: script:
# Naming format: <name>_<version>-<release>_<arch>.deb - BUILD_NAME=build/build.deb
# Version is the version number of the app being packaged
# Release number is the version number of the *packaging* itself.
# The release number might increment if the package maintainer
# updated the packaging, while the version number of the application
# being packaged did not change.
- BUILD_NAME=build/build.deb # inherited by build-stage
- PACKAGE_NAME=`dpkg -I ${BUILD_NAME} | grep "Package:" | awk '{ print $2 }'` - PACKAGE_NAME=`dpkg -I ${BUILD_NAME} | grep "Package:" | awk '{ print $2 }'`
- PACKAGE_VERSION=`dpkg -I ${BUILD_NAME} | grep "Version:" | awk '{ print $2 }'` - PACKAGE_VERSION=`dpkg -I ${BUILD_NAME} | grep "Version:" | awk '{ print $2 }'`
- PACKAGE_ARCH=amd64 - PACKAGE_ARCH=amd64
#- EXPORT_NAME="${PACKAGE_NAME}_${PACKAGE_VERSION}-0_${PACKAGE_ARCH}.deb"
- EXPORT_NAME="${PACKAGE_NAME}_${PACKAGE_VERSION}_${PACKAGE_ARCH}.deb" - EXPORT_NAME="${PACKAGE_NAME}_${PACKAGE_VERSION}_${PACKAGE_ARCH}.deb"
- mv ${BUILD_NAME} ${EXPORT_NAME} - mv ${BUILD_NAME} ${EXPORT_NAME}
- 'echo "PACKAGE_NAME: ${PACKAGE_NAME}"' - 'echo "PACKAGE_NAME: ${PACKAGE_NAME}"'
- 'echo "PACKAGE_VERSION: ${PACKAGE_VERSION}"' - 'echo "PACKAGE_VERSION: ${PACKAGE_VERSION}"'
- 'echo "PACKAGE_ARCH: ${PACKAGE_ARCH}"' - 'echo "PACKAGE_ARCH: ${PACKAGE_ARCH}"'
- 'echo "EXPORT_NAME: ${EXPORT_NAME}"' - 'echo "EXPORT_NAME: ${EXPORT_NAME}"'
# https://docs.gitlab.com/14.3/ee/user/packages/debian_repository/index.html
- URL="${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/debian/${EXPORT_NAME}" - URL="${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/debian/${EXPORT_NAME}"
- 'echo "URL: ${URL}"' - 'echo "URL: ${URL}"'
#- 'curl --request PUT --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file ${EXPORT_NAME} ${URL}'
# using generic-package-registry until debian-registry is GA
# https://docs.gitlab.com/ee/user/packages/generic_packages/index.html#publish-a-generic-package-by-using-cicd
- 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file ${EXPORT_NAME} "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${PACKAGE_NAME}/${PACKAGE_VERSION}/${EXPORT_NAME}"' - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file ${EXPORT_NAME} "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${PACKAGE_NAME}/${PACKAGE_VERSION}/${EXPORT_NAME}"'
deploy:pacman: deploy:pacman:
extends: .deploy extends: ".deploy"
image: archlinux:base-devel image: archlinux:base-devel
stage: deploy stage: deploy
rules:
- if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"
needs: needs:
- job: build:pacman - job: build:pacman
artifacts: true artifacts: true
script: script:
- source .PKGBUILD/PKGBUILD - source .PKGBUILD/PKGBUILD
# fastapi-dls-1.0-1-any.pkg.tar.zst - source version.env
- BUILD_NAME=${pkgname}-${CI_COMMIT_REF_NAME}-${pkgrel}-any.pkg.tar.zst - BUILD_NAME=${pkgname}-${VERSION}-${pkgrel}-any.pkg.tar.zst
- PACKAGE_NAME=${pkgname} - PACKAGE_NAME=${pkgname}
- PACKAGE_VERSION=${CI_COMMIT_REF_NAME} - PACKAGE_VERSION=${VERSION}
- PACKAGE_ARCH=any - PACKAGE_ARCH=any
- EXPORT_NAME=${BUILD_NAME} - EXPORT_NAME=${BUILD_NAME}
- 'echo "PACKAGE_NAME: ${PACKAGE_NAME}"' - 'echo "PACKAGE_NAME: ${PACKAGE_NAME}"'
@ -376,23 +237,28 @@ deploy:pacman:
- 'echo "PACKAGE_ARCH: ${PACKAGE_ARCH}"' - 'echo "PACKAGE_ARCH: ${PACKAGE_ARCH}"'
- 'echo "EXPORT_NAME: ${EXPORT_NAME}"' - 'echo "EXPORT_NAME: ${EXPORT_NAME}"'
- 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file ${EXPORT_NAME} "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${PACKAGE_NAME}/${PACKAGE_VERSION}/${EXPORT_NAME}"' - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file ${EXPORT_NAME} "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${PACKAGE_NAME}/${PACKAGE_VERSION}/${EXPORT_NAME}"'
release: release:
image: registry.gitlab.com/gitlab-org/release-cli:latest image: registry.gitlab.com/gitlab-org/release-cli:latest
stage: .post stage: ".post"
needs: [ test ] needs:
- job: test
artifacts: true
rules: rules:
- if: $CI_COMMIT_TAG - if: "$CI_COMMIT_TAG"
when: never
- if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"
script: script:
- echo "Running release-job for $CI_COMMIT_TAG" - echo "Running release-job for $VERSION"
release: release:
name: $CI_PROJECT_TITLE $CI_COMMIT_TAG name: "$CI_PROJECT_TITLE $VERSION"
description: Release of $CI_PROJECT_TITLE version $CI_COMMIT_TAG description: Release of $CI_PROJECT_TITLE version $VERSION
tag_name: $CI_COMMIT_TAG tag_name: "$VERSION"
ref: $CI_COMMIT_SHA ref: "$CI_COMMIT_SHA"
assets: assets:
links: links:
- name: 'Package Registry' - name: Package Registry
url: 'https://git.collinwebdesigns.de/oscar.krause/fastapi-dls/-/packages' url: https://git.collinwebdesigns.de/oscar.krause/fastapi-dls/-/packages
- name: 'Container Registry' - name: Container Registry
url: 'https://git.collinwebdesigns.de/oscar.krause/fastapi-dls/container_registry/40' url: https://git.collinwebdesigns.de/oscar.krause/fastapi-dls/container_registry/40
include:
- template: Auto-DevOps.gitlab-ci.yml

View File

@ -1,20 +1,17 @@
FROM python:3.11-alpine FROM python:3.11-alpine
ARG VERSION
ARG COMMIT=""
RUN echo -e "VERSION=$VERSION\nCOMMIT=$COMMIT" > /version.env
COPY requirements.txt /tmp/requirements.txt COPY requirements.txt /tmp/requirements.txt
RUN apk update \ RUN apk update \
&& apk add --no-cache --virtual build-deps gcc g++ python3-dev musl-dev pkgconfig \ && apk add --no-cache --virtual build-deps gcc g++ python3-dev musl-dev \
&& apk add --no-cache curl postgresql postgresql-dev mariadb-dev sqlite-dev \ && apk add --no-cache curl postgresql postgresql-dev mariadb-connector-c-dev sqlite-dev \
&& pip install --no-cache-dir --upgrade uvicorn \ && 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.5 mysqlclient==2.1.1 pysqlite3==0.5.0 \
&& pip install --no-cache-dir -r /tmp/requirements.txt \ && pip install --no-cache-dir -r /tmp/requirements.txt \
&& apk del build-deps && apk del build-deps
COPY app /app COPY app /app
COPY version.env /version.env
COPY README.md /README.md COPY README.md /README.md
HEALTHCHECK --start-period=30s --interval=10s --timeout=5s --retries=3 CMD curl --insecure --fail https://localhost/-/health || exit 1 HEALTHCHECK --start-period=30s --interval=10s --timeout=5s --retries=3 CMD curl --insecure --fail https://localhost/-/health || exit 1

142
README.md
View File

@ -2,28 +2,19 @@
Minimal Delegated License Service (DLS). 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 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** **Official Links**
* https://git.collinwebdesigns.de/oscar.krause/fastapi-dls (Private Git) - https://git.collinwebdesigns.de/oscar.krause/fastapi-dls
* https://gitea.publichub.eu/oscar.krause/fastapi-dls (Public Git) - https://gitea.publichub.eu/oscar.krause/fastapi-dls
* https://hub.docker.com/r/collinwebdesigns/fastapi-dls (Docker-Hub `collinwebdesigns/fastapi-dls:latest`) - Docker Image `collinwebdesigns/fastapi-dls:latest`
*All other repositories are forks! (which is no bad - just for information and bug reports)* *All other repositories are forks! (which is no bad - just for information and bug reports)*
[Releases & Release Notes](https://git.collinwebdesigns.de/oscar.krause/fastapi-dls/-/releases)
**Further Reading**
* [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 All-In-One Installer Script](https://wvthoog.nl/proxmox-vgpu-v3/) - Also known as `proxmox-installer.sh`
--- ---
[[_TOC_]] [[_TOC_]]
@ -34,9 +25,8 @@ Only the clients need a connection to this service on configured port.
- 256mb ram - 256mb ram
- 4gb hdd - 4gb hdd
- *maybe IPv6 must be disabled*
Tested with Ubuntu 22.10 (EOL!) (from Proxmox templates), actually its consuming 100mb ram and 750mb hdd. Tested with Ubuntu 22.10 (from Proxmox templates), actually its consuming 100mb ram and 750mb hdd.
**Prepare your system** **Prepare your system**
@ -44,12 +34,10 @@ Tested with Ubuntu 22.10 (EOL!) (from Proxmox templates), actually its consuming
## Docker ## Docker
Docker-Images are available here for Intel (x86), AMD (amd64) and ARM (arm64): Docker-Images are available here:
- [Docker-Hub](https://hub.docker.com/repository/docker/collinwebdesigns/fastapi-dls): `collinwebdesigns/fastapi-dls:latest` - [Docker-Hub](https://hub.docker.com/repository/docker/collinwebdesigns/fastapi-dls): `collinwebdesigns/fastapi-dls:latest`
- [GitLab-Registry](https://git.collinwebdesigns.de/oscar.krause/fastapi-dls/container_registry): `registry.git.collinwebdesigns.de/oscar.krause/fastapi-dls:latest` - [GitLab-Registry](https://git.collinwebdesigns.de/oscar.krause/fastapi-dls/container_registry): `registry.git.collinwebdesigns.de/oscar.krause/fastapi-dls/main:latest`
The images include database drivers for `postgres`, `mariadb` and `sqlite`.
**Run this on the Docker-Host** **Run this on the Docker-Host**
@ -75,9 +63,7 @@ docker run -e DLS_URL=`hostname -i` -e DLS_PORT=443 -p 443:443 -v $WORKING_DIR:/
**Docker-Compose / Deploy stack** **Docker-Compose / Deploy stack**
See [`examples`](examples) directory for more advanced examples (with reverse proxy usage). Goto [`docker-compose.yml`](docker-compose.yml) for more advanced example (with reverse proxy usage).
> Adjust *REQUIRED* variables as needed
```yaml ```yaml
version: '3.9' version: '3.9'
@ -111,10 +97,9 @@ volumes:
dls-db: dls-db:
``` ```
## Debian / Ubuntu / macOS (manual method using `git clone` and python virtual environment) ## Debian/Ubuntu (manual method using `git clone` and python virtual environment)
Tested on `Debian 11 (bullseye)`, `Debian 12 (bookworm)` and `macOS Ventura (13.6)`, Ubuntu may also work. Tested on `Debian 11 (bullseye)`, Ubuntu may also work.
**Please note that setup on macOS differs from Debian based systems.**
**Make sure you are logged in as root.** **Make sure you are logged in as root.**
@ -158,15 +143,13 @@ This is only to test whether the service starts successfully.
```shell ```shell
cd /opt/fastapi-dls/app cd /opt/fastapi-dls/app
sudo -u www-data /opt/fastapi-dls/venv/bin/uvicorn main:app --app-dir=/opt/fastapi-dls/app
# or
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**
> Adjust `DLS_URL` as needed (accessing from LAN won't work with 127.0.0.1)
```shell ```shell
mkdir /etc/fastapi-dls mkdir /etc/fastapi-dls
cat <<EOF >/etc/fastapi-dls/env cat <<EOF >/etc/fastapi-dls/env
@ -262,18 +245,15 @@ This is only to test whether the service starts successfully.
BASE_DIR=/opt/fastapi-dls BASE_DIR=/opt/fastapi-dls
SERVICE_USER=dls SERVICE_USER=dls
cd ${BASE_DIR} cd ${BASE_DIR}
sudo -u ${SERVICE_USER} ${BASE_DIR}/venv/bin/uvicorn main:app --app-dir=${BASE_DIR}/app
# or
su - ${SERVICE_USER} -c "${BASE_DIR}/venv/bin/uvicorn main:app --app-dir=${BASE_DIR}/app" su - ${SERVICE_USER} -c "${BASE_DIR}/venv/bin/uvicorn main:app --app-dir=${BASE_DIR}/app"
``` ```
**Create config file** **Create config file**
> Adjust `DLS_URL` as needed (accessing from LAN won't work with 127.0.0.1)
```shell ```shell
BASE_DIR=/opt/fastapi-dls BASE_DIR=/opt/fastapi-dls
cat <<EOF >/etc/fastapi-dls/env 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_URL=127.0.0.1
DLS_PORT=443 DLS_PORT=443
LEASE_EXPIRE_DAYS=90 LEASE_EXPIRE_DAYS=90
@ -318,7 +298,7 @@ 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`.
## Debian / Ubuntu (using `dpkg` / `apt`) ## Debian/Ubuntu (using `dpkg`)
Packages are available here: Packages are available here:
@ -326,11 +306,8 @@ Packages are available here:
Successful tested with: Successful tested with:
- Debian 12 (Bookworm) (EOL: tba.) - Debian 12 (Bookworm) (works but not recommended because it is currently in *testing* state)
- Ubuntu 22.10 (Kinetic Kudu) (EOL: July 20, 2023) - Ubuntu 22.10 (Kinetic Kudu)
- Ubuntu 23.04 (Lunar Lobster) (EOL: January 2024)
- Ubuntu 23.10 (Mantic Minotaur) (EOL: July 2024)
- Ubuntu 24.04 (Noble Numbat) (EOL: April 2036)
Not working with: Not working with:
@ -351,7 +328,6 @@ apt-get install -f --fix-missing
``` ```
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`.
Now you have to edit `/etc/fastapi-dls/env` as needed.
## ArchLinux (using `pacman`) ## ArchLinux (using `pacman`)
@ -373,20 +349,6 @@ 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`.
Now you have to edit `/etc/default/fastapi-dls` as needed.
## unRAID
1. Download [this xml file](.UNRAID/FastAPI-DLS.xml)
2. Put it in /boot/config/plugins/dockerMan/templates-user/
3. Go to Docker page, scroll down to `Add Container`, click on Template list and choose `FastAPI-DLS`
4. Open terminal/ssh, follow the instructions in overview description
5. Setup your container `IP`, `Port`, `DLS_URL` and `DLS_PORT`
6. Apply and let it boot up
*Unraid users must also make sure they have Host access to custom networks enabled if unraid is the vgpu guest*.
Continue [here](#unraid-guest) for docker guest setup.
## Let's Encrypt Certificate (optional) ## Let's Encrypt Certificate (optional)
@ -428,7 +390,7 @@ client has 19.2 hours in which to re-establish connectivity before its license e
\*2 Always use `https`, since guest-drivers only support secure connections! \*2 Always use `https`, since guest-drivers only support secure connections!
\*3 If you recreate your instance keys you need to **recreate client-token for each guest**! \*3 If you recreate instance keys you need to **recreate client-token for each guest**!
# Setup (Client) # Setup (Client)
@ -436,30 +398,9 @@ client has 19.2 hours in which to re-establish connectivity before its license e
Successfully tested with this package versions: Successfully tested with this package versions:
| vGPU Suftware | Driver Branch | Linux vGPU Manager | Linux Driver | Windows Driver | Release Date | EOL Date | - `14.3` (Linux-Host: `510.108.03`, Linux-Guest: `510.108.03`, Windows-Guest: `513.91`)
|:-------------:|:-------------:|--------------------|--------------|----------------|--------------:|--------------:| - `14.4` (Linux-Host: `510.108.03`, Linux-Guest: `510.108.03`, Windows-Guest: `514.08`)
| `17.2` | R550 | `550.90.05` | `550.90.07` | `552.55` | June 2024 | February 2025 | - `15.0` (Linux-Host: `525.60.12`, Linux-Guest: `525.60.13`, Windows-Guest: `527.41`)
| `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`
## Linux ## Linux
@ -511,7 +452,7 @@ Restart-Service NVDisplay.ContainerLocalSystem
Check licensing status: Check licensing status:
```shell ```shell
& 'nvidia-smi' -q | Select-String "License" & 'C:\Program Files\NVIDIA Corporation\NVSMI\nvidia-smi.exe' -q | Select-String "License"
``` ```
Output should be something like: Output should be something like:
@ -523,24 +464,8 @@ vGPU Software Licensed Product
Done. For more information check [troubleshoot section](#troubleshoot). Done. For more information check [troubleshoot section](#troubleshoot).
## unRAID Guest
1. Make sure you create a folder in a linux filesystem (BTRFS/XFS/EXT4...), I recommend `/mnt/user/system/nvidia` (this is where docker and libvirt preferences are saved, so it's a good place to have that)
2. Edit the script to put your `DLS_IP`, `DLS_PORT` and `TOKEN_PATH`, properly
3. Install `User Scripts` plugin from *Community Apps* (the Apps page, or google User Scripts Unraid if you're not using CA)
4. Go to `Settings > Users Scripts > Add New Script`
5. Give it a name (the name must not contain spaces preferably)
6. Click on the *gear icon* to the left of the script name then edit script
7. Paste the script and save
8. Set schedule to `At First Array Start Only`
9. Click on Apply
# Endpoints # Endpoints
<details>
<summary>show</summary>
### `GET /` ### `GET /`
Redirect to `/-/readme`. Redirect to `/-/readme`.
@ -592,18 +517,11 @@ Generate client token, (see [installation](#installation)).
### Others ### Others
There are many other internal api endpoints for handling authentication and lease process. There are many other internal api endpoints for handling authentication and lease process.
</details>
# Troubleshoot / Debug # Troubleshoot
**Please make sure that fastapi-dls and your guests are on the same timezone!** **Please make sure that fastapi-dls and your guests are on the same timezone!**
Maybe you have to disable IPv6 on the machine you are running FastAPI-DLS.
## Docker
Logs are available with `docker logs <container>`. To get the correct container-id use `docker container ls` or `docker ps`.
## Linux ## Linux
Logs are available with `journalctl -u nvidia-gridd -f`. Logs are available with `journalctl -u nvidia-gridd -f`.
@ -661,7 +579,7 @@ only
gets a valid local license. gets a valid local license.
<details> <details>
<summary>Log example</summary> <summary>Log</summary>
**Display-Container-LS** **Display-Container-LS**
@ -727,7 +645,7 @@ The error message can safely be ignored (since we have no license limitation :P)
<0>:End Logging <0>:End Logging
``` ```
#### log with nginx as reverse proxy (see [docker-compose-http-and-https.yml](examples/docker-compose-http-and-https.yml)) #### log with nginx as reverse proxy (see [docker-compose.yml](docker-compose.yml))
``` ```
<1>:NLS initialized <1>:NLS initialized
@ -748,14 +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 Special thanks to @samicrusader who created build file for ArchLinux and @cyrus who wrote the section for openSUSE.
- @samicrusader who created build file for **ArchLinux**
- @cyrus who wrote the section for **openSUSE**
- @midi who wrote the section for **unRAID**
- @polloloco who wrote the *[NVIDIA vGPU Guide](https://gitlab.com/polloloco/vgpu-proxmox)*
- @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/)
And thanks to all people who contributed to all these libraries!

View File

@ -1,27 +0,0 @@
# Roadmap
I am planning to implement the following features in the future.
## HA - High Availability
Support Failover-Mode (secondary ip address) as in official DLS.
**Note**: There is no Load-Balancing / Round-Robin HA Mode supported! If you want to use that, consider to use
Docker-Swarm with shared/cluster database (e.g. postgres).
*See [ha branch](https://git.collinwebdesigns.de/oscar.krause/fastapi-dls/-/tree/ha) for current status.*
## UI - User Interface
Add a user interface to manage origins and leases.
*See [ui branch](https://git.collinwebdesigns.de/oscar.krause/fastapi-dls/-/tree/ui) for current status.*
## Config Database
Instead of using environment variables, configuration files and manually create certificates, store configs and
certificates in database (like origins and leases). Also, there should be provided a startup assistant to prefill
required attributes and create instance-certificates. This is more user-friendly and should improve fist setup.

View File

@ -82,7 +82,7 @@ async def _index():
@app.get('/-/health', summary='* Health') @app.get('/-/health', summary='* Health')
async def _health(): async def _health(request: Request):
return JSONr({'status': 'up'}) return JSONr({'status': 'up'})
@ -186,12 +186,6 @@ async def _leases(request: Request, origin: bool = False):
return JSONr(response) return JSONr(response)
@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='* Lease') @app.delete('/-/lease/{lease_ref}', summary='* Lease')
async def _lease_delete(request: Request, lease_ref: str): async def _lease_delete(request: Request, lease_ref: str):
if Lease.delete(db, lease_ref) == 1: if Lease.delete(db, lease_ref) == 1:

View File

@ -160,14 +160,6 @@ class Lease(Base):
session.close() session.close()
return deletions return deletions
@staticmethod
def delete_expired(engine: Engine) -> int:
session = sessionmaker(bind=engine)()
deletions = session.query(Lease).filter(Lease.lease_expires <= datetime.utcnow()).delete()
session.commit()
session.close()
return deletions
@staticmethod @staticmethod
def calculate_renewal(renewal_period: float, delta: timedelta) -> timedelta: def calculate_renewal(renewal_period: float, delta: timedelta) -> timedelta:
""" """

View File

@ -1,10 +1,9 @@
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 # must match nginx listen & exposed port
LEASE_EXPIRE_DAYS: 90 # 90 days is maximum LEASE_EXPIRE_DAYS: 90
DATABASE: sqlite:////app/database/db.sqlite DATABASE: sqlite:////app/database/db.sqlite
DEBUG: false DEBUG: false
@ -14,16 +13,108 @@ services:
restart: always restart: always
environment: environment:
<<: *dls-variables <<: *dls-variables
ports:
- "443:443"
volumes: volumes:
- /opt/docker/fastapi-dls/cert:/app/cert - /etc/timezone:/etc/timezone:ro
- dls-db:/app/database - /opt/docker/fastapi-dls/cert:/app/cert # instance.private.pem, instance.public.pem
logging: # optional, for those who do not need logs - db:/app/database
driver: "json-file" entrypoint: ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--app-dir", "/app", "--proxy-headers"]
options: healthcheck:
max-file: 5 test: ["CMD", "curl", "--fail", "http://localhost:8000/-/health"]
max-size: 10m interval: 10s
timeout: 5s
retries: 3
start_period: 30s
proxy:
image: nginx
ports:
# thees are ports where nginx (!) is listen to
- "80:80" # for "/leasing/v1/lessor/shutdown" used by windows guests, can't be changed!
- "443:443" # first part must match "DLS_PORT"
volumes:
- /etc/timezone:/etc/timezone:ro
- /opt/docker/fastapi-dls/cert:/opt/cert
healthcheck:
test: ["CMD", "curl", "--insecure", "--fail", "https://localhost/-/health"]
interval: 10s
timeout: 5s
retries: 3
start_period: 30s
command: |
bash -c "bash -s <<\"EOF\"
cat > /etc/nginx/nginx.conf <<\"EON\"
daemon off;
user root;
worker_processes auto;
events {
worker_connections 1024;
}
http {
gzip on;
gzip_disable "msie6";
include /etc/nginx/mime.types;
upstream dls-backend {
server dls:8000; # must match dls listen port
}
server {
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;
root /var/www/html;
index index.html;
server_name _;
ssl_certificate "/opt/cert/webserver.crt";
ssl_certificate_key "/opt/cert/webserver.key";
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 10m;
ssl_protocols TLSv1.3 TLSv1.2;
# ssl_ciphers "ECDHE-ECDSA-CHACHA20-POLY1305";
# ssl_ciphers PROFILE=SYSTEM;
ssl_prefer_server_ciphers on;
location / {
proxy_set_header Host $$http_host;
proxy_set_header X-Real-IP $$remote_addr;
proxy_set_header X-Forwarded-For $$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $$scheme;
proxy_pass http://dls-backend$$request_uri;
}
location = /-/health {
access_log off;
add_header 'Content-Type' 'application/json';
return 200 '{\"status\":\"up\",\"service\":\"nginx\"}';
}
}
server {
listen 80;
listen [::]:80;
root /var/www/html;
index index.html;
server_name _;
location /leasing/v1/lessor/shutdown {
proxy_set_header Host $$http_host;
proxy_set_header X-Real-IP $$remote_addr;
proxy_set_header X-Forwarded-For $$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $$scheme;
proxy_pass http://dls-backend/leasing/v1/lessor/shutdown;
}
location / {
return 301 https://$$host$$request_uri;
}
}
}
EON
nginx
EOF"
volumes: volumes:
dls-db: db:

View File

@ -1,120 +0,0 @@
version: '3.9'
x-dls-variables: &dls-variables
DLS_URL: localhost # REQUIRED, change to your ip or hostname
DLS_PORT: 443 # must match nginx listen & exposed port
LEASE_EXPIRE_DAYS: 90
DATABASE: sqlite:////app/database/db.sqlite
DEBUG: false
services:
dls:
image: collinwebdesigns/fastapi-dls:latest
restart: always
environment:
<<: *dls-variables
volumes:
- /etc/timezone:/etc/timezone:ro
- /opt/docker/fastapi-dls/cert:/app/cert # instance.private.pem, instance.public.pem
- db:/app/database
entrypoint: ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--app-dir", "/app", "--proxy-headers"]
healthcheck:
test: ["CMD", "curl", "--fail", "http://localhost:8000/-/health"]
interval: 10s
timeout: 5s
retries: 3
start_period: 30s
proxy:
image: nginx
ports:
# thees are ports where nginx (!) is listen to
- "80:80" # for "/leasing/v1/lessor/shutdown" used by windows guests, can't be changed!
- "443:443" # first part must match "DLS_PORT"
volumes:
- /etc/timezone:/etc/timezone:ro
- /opt/docker/fastapi-dls/cert:/opt/cert
healthcheck:
test: ["CMD", "curl", "--insecure", "--fail", "https://localhost/-/health"]
interval: 10s
timeout: 5s
retries: 3
start_period: 30s
command: |
bash -c "bash -s <<\"EOF\"
cat > /etc/nginx/nginx.conf <<\"EON\"
daemon off;
user root;
worker_processes auto;
events {
worker_connections 1024;
}
http {
gzip on;
gzip_disable "msie6";
include /etc/nginx/mime.types;
upstream dls-backend {
server dls:8000; # must match dls listen port
}
server {
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;
root /var/www/html;
index index.html;
server_name _;
ssl_certificate "/opt/cert/webserver.crt";
ssl_certificate_key "/opt/cert/webserver.key";
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 10m;
ssl_protocols TLSv1.3 TLSv1.2;
# ssl_ciphers "ECDHE-ECDSA-CHACHA20-POLY1305";
# ssl_ciphers PROFILE=SYSTEM;
ssl_prefer_server_ciphers on;
location / {
proxy_set_header Host $$http_host;
proxy_set_header X-Real-IP $$remote_addr;
proxy_set_header X-Forwarded-For $$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $$scheme;
proxy_pass http://dls-backend$$request_uri;
}
location = /-/health {
access_log off;
add_header 'Content-Type' 'application/json';
return 200 '{\"status\":\"up\",\"service\":\"nginx\"}';
}
}
server {
listen 80;
listen [::]:80;
root /var/www/html;
index index.html;
server_name _;
location /leasing/v1/lessor/shutdown {
proxy_set_header Host $$http_host;
proxy_set_header X-Real-IP $$remote_addr;
proxy_set_header X-Forwarded-For $$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $$scheme;
proxy_pass http://dls-backend/leasing/v1/lessor/shutdown;
}
location / {
return 301 https://$$host$$request_uri;
}
}
}
EON
nginx
EOF"
volumes:
db:

View File

@ -1,8 +1,8 @@
fastapi==0.111.0 fastapi==0.89.1
uvicorn[standard]==0.29.0 uvicorn[standard]==0.20.0
python-jose==3.3.0 python-jose==3.3.0
pycryptodome==3.20.0 pycryptodome==3.17
python-dateutil==2.8.2 python-dateutil==2.8.2
sqlalchemy==2.0.30 sqlalchemy==2.0.0
markdown==3.6 markdown==3.4.1
python-dotenv==1.0.1 python-dotenv==0.21.1

1
version.env Normal file
View File

@ -0,0 +1 @@
VERSION=1.3.5