diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 20ae5f0..fc06da2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,7 +1,34 @@ cache: key: one-key-to-rule-them-all -build: +build:debian: + # debian:bullseye-slim + image: debian:bookworm-slim # just to get "python3-jose" working + stage: build + before_script: + - apt-get update -qq && apt-get install -qq -y build-essential + - chmod 0755 -R . + # create build directory for .deb sources + - mkdir build + # copy install instructions + - cp -r DEBIAN build/ + # 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 + - cp -r app build/usr/share/fastapi-dls + - cp README.md version.env build/usr/share/fastapi-dls + # create conf file + - mkdir -p build/etc/fastapi-dls + - touch build/etc/fastapi-dls/env + # cd into "build/" + - cd build/ + script: + - dpkg -b . build.deb + artifacts: + expire_in: 1 week + paths: + - build/build.deb + +build:docker: image: docker:dind interruptible: true stage: build @@ -30,7 +57,37 @@ test: script: - pytest main.py -deploy: +test:debian: + image: debian:bookworm-slim + stage: test + variables: + DEBIAN_FRONTEND: noninteractive + needs: + - job: build:debian + artifacts: true + before_script: + - apt-get update -qq && apt-get install -qq -y jq + script: + # test installation + - apt-get install -q -y ./build/build.deb --fix-missing + # copy example config from GitLab-CI-Variables + #- cat ${EXAMPLE_CONFIG} > /etc/fastapi-dls/env + # start service in background + - uvicorn --host 127.0.0.1 --port 443 + --app-dir /usr/share/fastapi-dls/app + --ssl-keyfile /etc/fastapi-dls/webserver.key + --ssl-certfile /opt/fastapi-dls/webserver.crt + --proxy-headers & + - FASTAPI_DLS_PID=$! + - echo "Started service with pid $FASTAPI_DLS_PID" + # testing service + - if [ "`curl --insecure -s https://127.0.0.1/status | jq .status`" != "up" ]; then echo "Success"; else "Error"; fi + # cleanup + - kill $FASTAPI_DLS_PID + - apt-get purge -qq -y fastapi-dls + - apt-get autoremove -qq -y && apt-get clean -qq + +deploy:docker: stage: deploy rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH @@ -51,3 +108,44 @@ deploy: - docker build . --tag $PUBLIC_REGISTRY_USER/${CI_PROJECT_NAME}:latest - docker push $PUBLIC_REGISTRY_USER/${CI_PROJECT_NAME}:${VERSION} - docker push $PUBLIC_REGISTRY_USER/${CI_PROJECT_NAME}:latest + +deploy:debian: + # doc: https://git.collinwebdesigns.de/help/user/packages/debian_repository/index.md#install-a-package + image: debian:bookworm-slim + stage: deploy + rules: + - if: $CI_COMMIT_BRANCH == "debian" # $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + needs: + - job: build:debian + artifacts: true + before_script: + - apt-get update -qq && apt-get install -qq -y curl lsb-release + # create distribution initial + - CODENAME=`lsb_release -cs` + # create repo if not exists + - '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' + script: + # Naming format: _-_.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_VERSION=`dpkg -I ${BUILD_NAME} | grep "Version:" | awk '{ print $2 }'` + - PACKAGE_ARCH=amd64 + #- EXPORT_NAME="${PACKAGE_NAME}_${PACKAGE_VERSION}-0_${PACKAGE_ARCH}.deb" + - EXPORT_NAME="${PACKAGE_NAME}_${PACKAGE_VERSION}_${PACKAGE_ARCH}.deb" + - mv ${BUILD_NAME} ${EXPORT_NAME} + - 'echo "PACKAGE_NAME: ${PACKAGE_NAME}"' + - 'echo "PACKAGE_VERSION: ${PACKAGE_VERSION}"' + - 'echo "PACKAGE_ARCH: ${PACKAGE_ARCH}"' + - '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}" + - '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}"' diff --git a/DEBIAN/conffiles b/DEBIAN/conffiles new file mode 100644 index 0000000..008d731 --- /dev/null +++ b/DEBIAN/conffiles @@ -0,0 +1 @@ +/etc/fastapi-dls/env diff --git a/DEBIAN/control b/DEBIAN/control new file mode 100644 index 0000000..1eeb2b2 --- /dev/null +++ b/DEBIAN/control @@ -0,0 +1,9 @@ +Package: fastapi-dls +Version: 0.6.0 +Architecture: all +Maintainer: Oscar Krause oscar.krause@collinwebdesigns.de +Depends: python3, python3-fastapi, python3-uvicorn, python3-dotenv, python3-dateutil, python3-jose, python3-sqlalchemy, python3-pycryptodome, python3-markdown, uvicorn, openssl +Recommends: curl +Installed-Size: 10240 +Homepage: https://git.collinwebdesigns.de/oscar.krause/fastapi-dls +Description: Minimal Delegated License Service (DLS). diff --git a/DEBIAN/postinst b/DEBIAN/postinst new file mode 100644 index 0000000..2311b35 --- /dev/null +++ b/DEBIAN/postinst @@ -0,0 +1,101 @@ +#!/bin/bash + +WORKING_DIR=/usr/share/fastapi-dls +CONFIG_DIR=/etc/fastapi-dls + +echo "> Create config directory ..." +mkdir -p $CONFIG_DIR + +echo "> Install service ..." +cat </etc/systemd/system/fastapi-dls.service +[Unit] +Description=Service for fastapi-dls +After=network.target + +[Service] +User=www-data +Group=www-data +AmbientCapabilities=CAP_NET_BIND_SERVICE +WorkingDirectory=$WORKING_DIR/app +EnvironmentFile=$CONFIG_DIR/env +ExecStart=uvicorn main:app \\ + --env-file /etc/fastapi-dls/env \\ + --host \$DLS_URL --port \$DLS_PORT \\ + --app-dir $WORKING_DIR/app \\ + --ssl-keyfile /etc/fastapi-dls/webserver.key \\ + --ssl-certfile /etc/fastapi-dls/webserver.crt \\ + --proxy-headers +Restart=always +KillSignal=SIGQUIT +Type=simple +NotifyAccess=all + +[Install] +WantedBy=multi-user.target + +EOF + +systemctl daemon-reload + +if [[ ! -f $CONFIG_DIR/env ]]; then + echo "> Writing initial config ..." + touch $CONFIG_DIR/env + cat <$CONFIG_DIR/env +DLS_URL=127.0.0.1 +DLS_PORT=443 +LEASE_EXPIRE_DAYS=90 +DATABASE=sqlite:///$CONFIG_DIR/db.sqlite +INSTANCE_KEY_RSA=$CONFIG_DIR/instance.private.pem +INSTANCE_KEY_PUB=$CONFIG_DIR/instance.public.pem + +EOF +fi + +echo "> Create dls-instance keypair ..." +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 + +while true; do + read -p "> Do you wish to create self-signed webserver certificate? [Y/n]" yn + yn=${yn:-y} # ${parameter:-word} If parameter is unset or null, the expansion of word is substituted. Otherwise, the value of parameter is substituted. + case $yn in + [Yy]*) + openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout $CONFIG_DIR/webserver.key -out $CONFIG_DIR/webserver.crt + break + ;; + [Nn]*) break ;; + *) echo "Please answer [y] or [n]." ;; + esac +done + +if [[ -f $CONFIG_DIR/webserver.key ]]; then + echo "> Starting service ..." + systemctl start fastapi-dls.service + + if [ -x "$(command -v curl)" ]; then + echo "> Testing API ..." + source $CONFIG_DIR/env + curl --insecure -X GET https://$DLS_URL:$DLS_PORT/status + else + echo "> Testing API failed, curl not available. Please test manually!" + fi +fi + +chown -R www-data:www-data $CONFIG_DIR +chown -R www-data:www-data $WORKING_DIR + +cat < Removing service file." + rm /etc/systemd/system/fastapi-dls.service +fi + +# todo diff --git a/DEBIAN/prerm b/DEBIAN/prerm new file mode 100755 index 0000000..296c995 --- /dev/null +++ b/DEBIAN/prerm @@ -0,0 +1,5 @@ +#!/bin/bash + +echo -e "> Starting uninstallation of 'fastapi-dls'!" + +# todo diff --git a/README.md b/README.md index 120feed..4eee1e0 100644 --- a/README.md +++ b/README.md @@ -192,7 +192,29 @@ EOF ``` Now you have to run `systemctl daemon-reload`. After that you can start service -with `systemctl start fastapi-dls.service`. +with `systemctl start fastapi-dls.service` and enable autostart with `systemctl enable fastapi-dls.service`. + +## Debian/Ubuntu (using `dpkg`) + +Packages are available here: + +- [GitLab-Registry](https://git.collinwebdesigns.de/oscar.krause/fastapi-dls/-/packages/63) + +Successful tested with: +- Debian 12 (Bookworm) +- Ubuntu 22.10 (Kinetic Kudu) + +**Run this on your server instance** + +```shell +apt-get update +FILENAME=/opt/fastapi-dls.deb +wget -O $FILENAME https://git.collinwebdesigns.de/oscar.krause/fastapi-dls/-/package_files/148/download +dpkg -i $FILENAME +apt-get install -f --fix-missing +``` + +Start with `systemctl start fastapi-dls.service` and enable autostart with `systemctl enable fastapi-dls.service`. ## Let's Encrypt Certificate diff --git a/app/main.py b/app/main.py index 5afa0f6..e3778ef 100644 --- a/app/main.py +++ b/app/main.py @@ -19,8 +19,14 @@ from starlette.middleware.cors import CORSMiddleware from starlette.responses import StreamingResponse, JSONResponse, HTMLResponse from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker -from Crypto.PublicKey import RSA -from Crypto.PublicKey.RSA import RsaKey + +try: + # Crypto | Cryptodome on Debian + from Crypto.PublicKey import RSA + from Crypto.PublicKey.RSA import RsaKey +except ModuleNotFoundError: + from Cryptodome.PublicKey import RSA + from Cryptodome.PublicKey.RSA import RsaKey from orm import Origin, Lease, init as db_init