diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..3cb040a --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: [ai-dock, robballantyne] diff --git a/.github/workflows/clear-cache.yml b/.github/workflows/clear-cache.yml new file mode 100644 index 0000000..2d1cd89 --- /dev/null +++ b/.github/workflows/clear-cache.yml @@ -0,0 +1,55 @@ +# https://stackoverflow.com/a/73556714 +name: Clear Cache + +on: + workflow_dispatch: + +permissions: + actions: write + +jobs: + clear-cache: + runs-on: ubuntu-latest + steps: + - name: Clear cache + uses: actions/github-script@v6 + with: + script: | + console.log("About to clear") + const response = await github.rest.actions.getActionsCacheList({ + owner: context.repo.owner, + repo: context.repo.repo, + page: 1, + per_page: 100 + }); + + const pages = (function() { + if (typeof response.headers.link !== 'undefined') { + return response.headers.link.split(">").slice(-2)[0].split('=').slice(-1)[0] + } + return 1; + })(); + + console.log("Total pages: " + pages); + + for (let page = pages; page >= 1; page--) { + console.log("Processing page " + page) + + const response = await github.rest.actions.getActionsCacheList({ + owner: context.repo.owner, + repo: context.repo.repo, + page: page, + per_page: 100 + }); + + for (const cache of response.data.actions_caches) { + console.log(cache) + github.rest.actions.deleteActionsCacheById({ + owner: context.repo.owner, + repo: context.repo.repo, + cache_id: cache.id, + }) + } + } + + console.log("Clear completed") \ No newline at end of file diff --git a/.github/workflows/delete-old-images.yml b/.github/workflows/delete-old-images.yml new file mode 100644 index 0000000..773d65f --- /dev/null +++ b/.github/workflows/delete-old-images.yml @@ -0,0 +1,110 @@ +name: Delete Old Packages + +env: + PER_PAGE: 100 + +on: + workflow_dispatch: + inputs: + age: + type: choice + required: true + description: Delete older than + options: + - 1 Hour + - 12 Hours + - 1 Day + - 1 Week + - 2 Weeks + - 1 Month + - 6 Months + - 1 Year + - 2 Years + - 3 Years + - 4 Years + - 5 Years + - All Packages + +jobs: + delete-old-packages: + runs-on: ubuntu-latest + steps: + - + run: | + echo "PACKAGE_NAME=${GITHUB_REPOSITORY,,}" >> ${GITHUB_ENV} + echo "OWNER=orgs/${GITHUB_REPOSITORY_OWNER,,}" >> ${GITHUB_ENV} + - + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.DELETE_PACKAGES_TOKEN }} + script: | + const delete_age = (function() { + switch ("${{ github.event.inputs.age }}") { + case "All Packages": + return 0; + case "1 Hour": + return 60; + case "12 Hours": + return 720; + case "1 Day": + return 1440; + case "1 Week": + return 10080; + case "2 Weeks": + return 20160; + case "1 Month": + return 43800; + case "6 Months": + return 262800; + case "1 Year": + return 525600; + case "2 Years": + return 525600 * 2; + case "3 Years": + return 525600 * 3; + case "4 Years": + return 525600 * 4; + case "5 Years": + return 525600 * 5; + default: + return 157680000; + } + })(); + + const now = new Date(); + const epoch_minutes = Math.round(now.getTime() / 1000 / 60); + + const response = await github.request("GET /${{ env.OWNER }}/packages/container/${{ github.event.repository.name }}/versions", + { per_page: ${{ env.PER_PAGE }} + }); + + const pages = (function() { + if (typeof response.headers.link !== 'undefined') { + return response.headers.link.split(">").slice(-2)[0].split('=').slice(-1)[0] + } + return 1; + })(); + + console.log("Total pages: " + pages); + + for (let page = pages; page >= 1; page--) { + console.log("Processing page " + page) + + const response = await github.request("GET /${{ env.OWNER }}/packages/container/${{ github.event.repository.name }}/versions", + { + per_page: ${{ env.PER_PAGE }}, + page: page + }); + + console.log("Deleting packages updated more than " + delete_age + " minutes ago...") + for (version of response.data) { + let updated_at = new Date(version.updated_at) + let minutes_old = epoch_minutes - Math.round(updated_at.getTime() / 1000 / 60); + console.log("Package is " + minutes_old + " minutes old") + if (minutes_old > delete_age) { + console.log("delete " + version.id) + const deleteResponse = await github.request("DELETE /${{ env.OWNER }}/packages/container/${{ github.event.repository.name }}/versions/" + version.id, { }); + console.log("status " + deleteResponse.status) + } + } + } diff --git a/.github/workflows/delete-untagged-images.yml b/.github/workflows/delete-untagged-images.yml new file mode 100644 index 0000000..017ab09 --- /dev/null +++ b/.github/workflows/delete-untagged-images.yml @@ -0,0 +1,56 @@ +name: Delete Untagged Packages + +env: + PER_PAGE: 100 + +on: + workflow_dispatch: + workflow_run: + workflows: ["Docker Build"] + types: + - completed + +jobs: + delete-untagged: + runs-on: ubuntu-latest + steps: + - + run: | + echo "PACKAGE_NAME=${GITHUB_REPOSITORY,,}" >> ${GITHUB_ENV} + echo "OWNER=orgs/${GITHUB_REPOSITORY_OWNER,,}" >> ${GITHUB_ENV} + - + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.DELETE_PACKAGES_TOKEN }} + script: | + const response = await github.request("GET /${{ env.OWNER }}/packages/container/${{ github.event.repository.name }}/versions", + { per_page: ${{ env.PER_PAGE }} + }); + + const pages = (function() { + if (typeof response.headers.link !== 'undefined') { + return response.headers.link.split(">").slice(-2)[0].split('=').slice(-1)[0] + } + return 1; + })(); + + console.log("Total pages: " + pages); + + for (let page = pages; page >= 1; page--) { + console.log("Processing page " + page) + + const response = await github.request("GET /${{ env.OWNER }}/packages/container/${{ github.event.repository.name }}/versions", + { + per_page: ${{ env.PER_PAGE }}, + page: page + }); + + for (version of response.data) { + if (version.metadata.container.tags.length == 0) { + console.log("delete " + version.id) + const deleteResponse = await github.request("DELETE /${{ env.OWNER }}/packages/container/${{ github.event.repository.name }}/versions/" + version.id, { }); + console.log("status " + deleteResponse.status) + } + } + } + diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml new file mode 100644 index 0000000..851d58c --- /dev/null +++ b/.github/workflows/docker-build.yml @@ -0,0 +1,193 @@ +name: Docker Build + +on: + workflow_dispatch: + push: + branches: [ "main" ] + +env: + UBUNTU_VERSION: 22.04 + BUILDX_NO_DEFAULT_ATTESTATIONS: 1 + LATEST_CUDA: "cuda-12.2.0-cudnn8-runtime-22.04" + LATEST_ROCM: "rocm-5.6-runtime-22.04" + LATEST_CPU: "cpu-22.04" + +jobs: + cpu-base: + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - + name: Free Space + run: | + df -h + rm -rf /usr/share/dotnet + rm -rf /opt/ghc + rm -rf /usr/local/share/boost + rm -rf "$AGENT_TOOLSDIRECTORY" + df -h + - + name: Env Setter + run: | + echo "PACKAGE_NAME=${GITHUB_REPOSITORY,,}" >> ${GITHUB_ENV} + - + name: Checkout + uses: actions/checkout@v3 + - + name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - + name: Set tags + run: | + img_path="ghcr.io/${{ env.PACKAGE_NAME }}" + ver_tag="cpu-${{ env.UBUNTU_VERSION }}" + + if [[ $ver_tag == ${{ env.LATEST_CPU }} ]]; then + TAGS="${img_path}:latest-cpu, ${img_path}:$ver_tag" + else + TAGS="${img_path}:$ver_tag" + fi + echo "TAGS=${TAGS}" >> ${GITHUB_ENV} + - + name: Build and push + uses: docker/build-push-action@v4 + with: + context: build + build-args: | + IMAGE_BASE=ghcr.io/ai-dock/base-image:cpu-${{ env.UBUNTU_VERSION }} + push: true + # Avoids unknown/unknown architecture and extra metadata + provenance: false + tags: ${{ env.TAGS }} + + nvidia-base: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + cuda: + - 11.7.1 + - 11.8.0 + - 12.1.0 + - 12.2.0 + level: + - "base" + - "runtime" + - "devel" + - "cudnn8-runtime" + - "cudnn8-devel" + exclude: + - cuda: 12.2.0 + level: "cudnn8-runtime" # Not yet available + - cuda: 12.2.0 + level: "cudnn8-devel" # Not yet available + steps: + - + name: Free Space + run: | + df -h + rm -rf /usr/share/dotnet + rm -rf /opt/ghc + rm -rf /usr/local/share/boost + rm -rf "$AGENT_TOOLSDIRECTORY" + df -h + - + name: Env Setter + run: | + echo "PACKAGE_NAME=${GITHUB_REPOSITORY,,}" >> ${GITHUB_ENV} + - + name: Checkout + uses: actions/checkout@v3 + - + name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - + name: Set tags + run: | + img_path="ghcr.io/${{ env.PACKAGE_NAME }}" + ver_tag="cuda-${{ matrix.cuda }}-${{ matrix.level }}-${{ env.UBUNTU_VERSION }}" + + if [[ $ver_tag == ${{ env.LATEST_CUDA }} ]]; then + TAGS="${img_path}:latest, ${img_path}:latest-cuda, ${img_path}:$ver_tag" + else + TAGS="${img_path}:$ver_tag" + fi + echo "TAGS=${TAGS}" >> ${GITHUB_ENV} + - + name: Build and push + uses: docker/build-push-action@v4 + with: + context: build + build-args: | + IMAGE_BASE=ghcr.io/ai-dock/base-image:cuda-${{ matrix.cuda }}-${{ matrix.level }}-${{ env.UBUNTU_VERSION }} + push: true + provenance: false + tags: ${{ env.TAGS }} + + amd-base: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + rocm: + - 5.4.2 + - 5.6 + level: + - "core" + - "runtime" + - "devel" + steps: + - + name: Free Space + run: | + df -h + rm -rf /usr/share/dotnet + rm -rf /opt/ghc + rm -rf /usr/local/share/boost + rm -rf "$AGENT_TOOLSDIRECTORY" + df -h + - + name: Env Setter + run: | + echo "PACKAGE_NAME=${GITHUB_REPOSITORY,,}" >> ${GITHUB_ENV} + - + name: Checkout + uses: actions/checkout@v3 + - + name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - + name: Set tags + run: | + img_path="ghcr.io/${{ env.PACKAGE_NAME }}" + ver_tag="rocm-${{ matrix.rocm }}-${{ matrix.level }}-${{ env.UBUNTU_VERSION }}" + + if [[ $ver_tag == ${{ env.LATEST_ROCM }} ]]; then + TAGS="${img_path}:latest-rocm, ${img_path}:$ver_tag" + else + TAGS="${img_path}:$ver_tag" + fi + echo "TAGS=${TAGS}" >> ${GITHUB_ENV} + - + name: Build and push + uses: docker/build-push-action@v4 + with: + context: build + build-args: | + IMAGE_BASE=ghcr.io/ai-dock/base-image:rocm-${{ matrix.rocm }}-${{ matrix.level }}-${{ env.UBUNTU_VERSION }} + push: true + provenance: false + tags: ${{ env.TAGS }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0386930 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +workspace +build/COPY_ROOT_EXTRA +config/authorized_keys +config/rclone +.env diff --git a/build/COPY_ROOT/etc/apt/preferences.d/nosnap.pref b/build/COPY_ROOT/etc/apt/preferences.d/nosnap.pref new file mode 100644 index 0000000..52fa273 --- /dev/null +++ b/build/COPY_ROOT/etc/apt/preferences.d/nosnap.pref @@ -0,0 +1,3 @@ +Package: snapd +Pin: release a=* +Pin-Priority: -10 \ No newline at end of file diff --git a/build/COPY_ROOT/etc/supervisor/supervisord/conf.d/.gitkeep b/build/COPY_ROOT/etc/supervisor/supervisord/conf.d/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/build/COPY_ROOT/etc/supervisor/supervisord/conf.d/dbus.conf b/build/COPY_ROOT/etc/supervisor/supervisord/conf.d/dbus.conf new file mode 100644 index 0000000..ea3e8e7 --- /dev/null +++ b/build/COPY_ROOT/etc/supervisor/supervisord/conf.d/dbus.conf @@ -0,0 +1,19 @@ +[program:dbus] +command=supervisor-dbus.sh +process_name=%(program_name)s +numprocs=1 +directory=/root +priority=10 +autostart=true +startsecs=5 +startretries=3 +autorestart=unexpected +stopsignal=TERM +stopwaitsecs=10 +stopasgroup=true +killasgroup=true +stdout_logfile=/var/log/supervisor/dbus.log +stdout_logfile_maxbytes=10MB +stdout_logfile_backups=1 +redirect_stderr=true +environment=PROC_NAME="%(program_name)s" diff --git a/build/COPY_ROOT/etc/supervisor/supervisord/conf.d/kde-plasma.conf b/build/COPY_ROOT/etc/supervisor/supervisord/conf.d/kde-plasma.conf new file mode 100644 index 0000000..e3ce20f --- /dev/null +++ b/build/COPY_ROOT/etc/supervisor/supervisord/conf.d/kde-plasma.conf @@ -0,0 +1,19 @@ +[program:KDE-Plasma] +command=supervisor-kde-plasma.sh +process_name=%(program_name)s +numprocs=1 +directory=/tmp/runtime-user +priority=100 +autostart=true +startsecs=5 +startretries=3 +autorestart=unexpected +stopsignal=TERM +stopwaitsecs=10 +stopasgroup=true +killasgroup=true +stdout_logfile=/var/log/supervisor/kde-plasma.log +stdout_logfile_maxbytes=10MB +stdout_logfile_backups=1 +redirect_stderr=true +environment=PROC_NAME="%(program_name)s" diff --git a/build/COPY_ROOT/etc/supervisor/supervisord/conf.d/pulseaudio.conf b/build/COPY_ROOT/etc/supervisor/supervisord/conf.d/pulseaudio.conf new file mode 100644 index 0000000..519a030 --- /dev/null +++ b/build/COPY_ROOT/etc/supervisor/supervisord/conf.d/pulseaudio.conf @@ -0,0 +1,19 @@ +[program:pulseaudio] +command=supervisor-pulseaudio.sh +process_name=%(program_name)s +numprocs=1 +directory=/root +priority=10 +autostart=true +startsecs=5 +startretries=3 +autorestart=unexpected +stopsignal=TERM +stopwaitsecs=10 +stopasgroup=true +killasgroup=true +stdout_logfile=/var/log/supervisor/pulseaudio.log +stdout_logfile_maxbytes=10MB +stdout_logfile_backups=1 +redirect_stderr=true +environment=PROC_NAME="%(program_name)s" diff --git a/build/COPY_ROOT/etc/supervisor/supervisord/conf.d/selkies-gstreamer.conf b/build/COPY_ROOT/etc/supervisor/supervisord/conf.d/selkies-gstreamer.conf new file mode 100644 index 0000000..0ffc038 --- /dev/null +++ b/build/COPY_ROOT/etc/supervisor/supervisord/conf.d/selkies-gstreamer.conf @@ -0,0 +1,19 @@ +[program:selkies-gstreamer] +command=supervisor-selkies-gstreamer.sh +process_name=%(program_name)s +numprocs=1 +directory=/tmp +priority=100 +autostart=true +startsecs=5 +startretries=3 +autorestart=unexpected +stopsignal=TERM +stopwaitsecs=10 +stopasgroup=true +killasgroup=true +stdout_logfile=/var/log/supervisor/selkies-gstreamer.log +stdout_logfile_maxbytes=10MB +stdout_logfile_backups=1 +redirect_stderr=true +environment=PROC_NAME="%(program_name)s" diff --git a/build/COPY_ROOT/etc/xdg/kdeglobals b/build/COPY_ROOT/etc/xdg/kdeglobals new file mode 100644 index 0000000..abc0310 --- /dev/null +++ b/build/COPY_ROOT/etc/xdg/kdeglobals @@ -0,0 +1,6 @@ +[KDE] +SingleClick=false + +[KDE Action Restrictions] +action/lock_screen=false +logout=false \ No newline at end of file diff --git a/build/COPY_ROOT/etc/xdg/kscreenlockerrc b/build/COPY_ROOT/etc/xdg/kscreenlockerrc new file mode 100644 index 0000000..8a52bc5 --- /dev/null +++ b/build/COPY_ROOT/etc/xdg/kscreenlockerrc @@ -0,0 +1,3 @@ +[Daemon] +Autolock=false +LockOnResume=false diff --git a/build/COPY_ROOT/opt/ai-dock/bin/build/layer0/amd.sh b/build/COPY_ROOT/opt/ai-dock/bin/build/layer0/amd.sh new file mode 100755 index 0000000..be49a89 --- /dev/null +++ b/build/COPY_ROOT/opt/ai-dock/bin/build/layer0/amd.sh @@ -0,0 +1,3 @@ +#!/bin/false + +# For ROCm specific logic diff --git a/build/COPY_ROOT/opt/ai-dock/bin/build/layer0/clean.sh b/build/COPY_ROOT/opt/ai-dock/bin/build/layer0/clean.sh new file mode 100755 index 0000000..1a21b42 --- /dev/null +++ b/build/COPY_ROOT/opt/ai-dock/bin/build/layer0/clean.sh @@ -0,0 +1,6 @@ +#!/bin/false + +# Tidy up and keep image small +apt-get clean -y +micromamba clean -ay +rm -rf /tmp/* \ No newline at end of file diff --git a/build/COPY_ROOT/opt/ai-dock/bin/build/layer0/common.sh b/build/COPY_ROOT/opt/ai-dock/bin/build/layer0/common.sh new file mode 100755 index 0000000..6cfb13a --- /dev/null +++ b/build/COPY_ROOT/opt/ai-dock/bin/build/layer0/common.sh @@ -0,0 +1,250 @@ +#!/bin/false + +source /opt/ai-dock/etc/environment.sh + +build_common_main() { + build_common_install_xorg + build_common_install_virtualgl + build_common_install_kde + build_common_install_packages + build_common_install_selkies +} + +function build_common_install_xorg() { + # Minimal xorg installation + $APT_INSTALL \ + alsa-base \ + alsa-utils \ + clinfo \ + cups-browsed \ + cups-bsd \ + cups-common \ + cups-filters \ + cups-pdf \ + dbus-user-session \ + dbus-x11 \ + fonts-dejavu \ + fonts-freefont-ttf \ + fonts-hack \ + fonts-liberation \ + fonts-noto \ + fonts-noto-cjk \ + fonts-noto-cjk-extra \ + fonts-noto-color-emoji \ + fonts-noto-extra \ + fonts-noto-hinted \ + fonts-noto-mono \ + fonts-noto-unhinted \ + fonts-opensymbol \ + fonts-symbola \ + fonts-ubuntu \ + im-config \ + lame \ + libavcodec-extra \ + libdbus-c++-1-0v5 \ + libegl1 \ + libegl1:i386 \ + libgles2 \ + libgles2:i386 \ + libgl1 \ + libgl1:i386 \ + libglu1 \ + libglu1:i386 \ + libglvnd0 \ + libglvnd0:i386 \ + libopenjp2-7 \ + libopus0 \ + libpulse0 \ + libsm6 \ + libsm6:i386 \ + libva2 \ + libva2:i386 \ + libvulkan-dev \ + libvulkan-dev:i386 \ + libxcb1 \ + libxcb1:i386 \ + libxau6 \ + libxau6:i386 \ + libx11-6 \ + libx11-6:i386 \ + libxext6 \ + libxext6:i386 \ + libxkbcommon0 \ + libxrandr-dev \ + libxdmcp6 \ + libxdmcp6:i386 \ + libxv1 \ + libxv1:i386 \ + libxtst6 \ + libxtst6:i386 \ + mesa-utils \ + mesa-utils-extra \ + mesa-vulkan-drivers \ + mesa-vulkan-drivers:i386 \ + net-tools \ + ocl-icd-libopencl1 \ + packagekit-tools \ + pulseaudio \ + python3 \ + python3-cups \ + ubuntu-drivers-common \ + vainfo \ + va-driver-all \ + va-driver-all:i386 \ + vdpau-driver-all \ + vdpau-driver-all:i386 \ + vdpauinfo \ + vulkan-tools \ + whoopsie \ + wmctrl \ + x11-apps \ + x11-utils \ + x11-xkb-utils \ + x11-xserver-utils \ + x264 \ + xauth \ + xbitmaps \ + xclip \ + xcvt \ + cups-bsd \ + xdg-user-dirs \ + xdg-utils \ + xfonts-base \ + xfonts-scalable \ + xinit \ + xkb-data \ + xsettingsd \ + xserver-xorg-input-all \ + xserver-xorg-input-wacom \ + xserver-xorg-video-all \ + xserver-xorg-video-qxl \ + xvfb +} + +function build_common_install_virtualgl() { + cd /tmp + curl -fsSL -O "https://github.com/VirtualGL/virtualgl/releases/download/${VIRTUALGL_VERSION}/virtualgl_${VIRTUALGL_VERSION}_amd64.deb" + curl -fsSL -O "https://github.com/VirtualGL/virtualgl/releases/download/${VIRTUALGL_VERSION}/virtualgl32_${VIRTUALGL_VERSION}_amd64.deb" + + $APT_INSTALL \ + /tmp/virtualgl_${VIRTUALGL_VERSION}_amd64.deb \ + /tmp/virtualgl32_${VIRTUALGL_VERSION}_amd64.deb + + #chmod u+s /usr/lib/libvglfaker.so + #chmod u+s /usr/lib/libdlfaker.so + #chmod u+s /usr/lib32/libvglfaker.so + #chmod u+s /usr/lib32/libdlfaker.so + #chmod u+s /usr/lib/i386-linux-gnu/libvglfaker.so + #chmod u+s /usr/lib/i386-linux-gnu/libdlfaker.so +} + +function build_common_install_kde() { + # Essentials for KDE to start without issues + $APT_INSTALL \ + kde-plasma-desktop \ + frameworkintegration \ + kmouth \ + ksshaskpass \ + ktimer \ + kwayland-integration \ + kwin-addons \ + kwin-x11 \ + libdbusmenu-glib4 \ + libdbusmenu-gtk3-4 \ + libgail-common \ + libgdk-pixbuf2.0-bin \ + libgtk2.0-bin \ + libgtk-3-bin \ + libkf5baloowidgets-bin \ + libkf5dbusaddons-bin \ + libkf5iconthemes-bin \ + libkf5kdelibs4support5-bin \ + libkf5khtml-bin \ + libkf5parts-plugins \ + libqt5multimedia5-plugins \ + librsvg2-common \ + media-player-info \ + okular \ + okular-extra-backends \ + partitionmanager \ + plasma-browser-integration \ + plasma-calendar-addons \ + plasma-dataengines-addons \ + plasma-discover \ + plasma-integration \ + plasma-runners-addons \ + plasma-widgets-addons \ + policykit-desktop-privileges \ + polkit-kde-agent-1 \ + print-manager \ + qapt-deb-installer \ + qml-module-org-kde-runnermodel \ + qml-module-org-kde-qqc2desktopstyle \ + qml-module-qtgraphicaleffects \ + qml-module-qtquick-xmllistmodel \ + qt5-gtk-platformtheme \ + qt5-image-formats-plugins \ + qt5-style-plugins \ + qtspeech5-flite-plugin \ + qtvirtualkeyboard-plugin \ + software-properties-qt \ + sonnet-plugins \ + systemsettings \ + xdg-desktop-portal-kde \ + xdg-user-dirs +} + +function build_common_install_packages() { + $APT_INSTALL \ + vlc +} + +function build_common_install_selkies() { + # Install latest Selkies-GStreamer (https://github.com/selkies-project/selkies-gstreamer) build, Python application, and web application, should be consistent with selkies-gstreamer documentation + $APT_INSTALL \ + python3-pip \ + python3-dev \ + python3-gi \ + python3-setuptools \ + python3-wheel \ + libgl-dev \ + libgles-dev \ + libglvnd-dev \ + libgudev-1.0-0 \ + wayland-protocols \ + libwayland-dev \ + libsrtp2-1 \ + libwebrtc-audio-processing1 \ + libcairo-gobject2 \ + libpangocairo-1.0-0 \ + libgirepository-1.0-1 \ + libjpeg-dev \ + libwebp-dev \ + libvpx-dev \ + zlib1g-dev \ + i965-va-driver-shaders \ + intel-media-va-driver-non-free \ + intel-gpu-tools \ + radeontop + + if [[ -z $SELKIES_VERSION || ${SELKIES_VERSION,,} == 'latest' ]]; then + SELKIES_VERSION="$(curl -fsSL "https://api.github.com/repos/selkies-project/selkies-gstreamer/releases/latest" \ + | jq -r '.tag_name' | sed 's/[^0-9\.\-]*//g')" + fi + + cd /opt + curl -fsSL "https://github.com/selkies-project/selkies-gstreamer/releases/download/v${SELKIES_VERSION}/selkies-gstreamer-v${SELKIES_VERSION}-ubuntu$(grep VERSION_ID= /etc/os-release | cut -d= -f2 | tr -d '\"').tgz" | tar -zxf - + + cd /tmp + curl -fsSL -O "https://github.com/selkies-project/selkies-gstreamer/releases/download/v${SELKIES_VERSION}/selkies_gstreamer-${SELKIES_VERSION}-py3-none-any.whl" + pip3 install "selkies_gstreamer-${SELKIES_VERSION}-py3-none-any.whl" + + cd /opt + curl -fsSL "https://github.com/selkies-project/selkies-gstreamer/releases/download/v${SELKIES_VERSION}/selkies-gstreamer-web-v${SELKIES_VERSION}.tgz" | tar -zxf - + + cd /tmp + curl -fsSL -o selkies-js-interposer.deb "https://github.com/selkies-project/selkies-gstreamer/releases/download/v${SELKIES_VERSION}/selkies-js-interposer-v${SELKIES_VERSION}-ubuntu$(grep VERSION_ID= /etc/os-release | cut -d= -f2 | tr -d '\"').deb" + $APT_INSTALL ./selkies-js-interposer.deb +} + +build_common_main "$@" \ No newline at end of file diff --git a/build/COPY_ROOT/opt/ai-dock/bin/build/layer0/cpu.sh b/build/COPY_ROOT/opt/ai-dock/bin/build/layer0/cpu.sh new file mode 100755 index 0000000..188d2be --- /dev/null +++ b/build/COPY_ROOT/opt/ai-dock/bin/build/layer0/cpu.sh @@ -0,0 +1,13 @@ +#!/bin/false + +# For CPU specific processes. Includes Intel gfx for now +build_cpu_main() { + $APT_INSTALL \ + i965-va-driver-shaders \ + i965-va-driver-shaders:i386 \ + intel-media-va-driver-non-free \ + intel-media-va-driver-non-free:i386 \ + xserver-xorg-video-intel \ +} + +build_cpu_main "$@" \ No newline at end of file diff --git a/build/COPY_ROOT/opt/ai-dock/bin/build/layer0/init.sh b/build/COPY_ROOT/opt/ai-dock/bin/build/layer0/init.sh new file mode 100755 index 0000000..d943313 --- /dev/null +++ b/build/COPY_ROOT/opt/ai-dock/bin/build/layer0/init.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# Must exit and fail to build if any command fails +set -eo pipefail + +source /opt/ai-dock/bin/build/layer0/common.sh + +if [[ "$XPU_TARGET" == "NVIDIA_GPU" ]]; then + source /opt/ai-dock/bin/build/layer0/nvidia.sh +elif [[ "$XPU_TARGET" == "AMD_GPU" ]]; then + source /opt/ai-dock/bin/build/layer0/amd.sh +elif [[ "$XPU_TARGET" == "CPU" ]]; then + source /opt/ai-dock/bin/build/layer0/cpu.sh +else + printf "No valid XPU_TARGET specified\n" >&2 + exit 1 +fi + +source /opt/ai-dock/bin/build/layer0/clean.sh \ No newline at end of file diff --git a/build/COPY_ROOT/opt/ai-dock/bin/build/layer0/nvidia.sh b/build/COPY_ROOT/opt/ai-dock/bin/build/layer0/nvidia.sh new file mode 100755 index 0000000..7f48fc1 --- /dev/null +++ b/build/COPY_ROOT/opt/ai-dock/bin/build/layer0/nvidia.sh @@ -0,0 +1,37 @@ +#!/bin/false + +# For CUDA specific logic + +build_nvidia_main() { + echo "/usr/local/nvidia/lib" >> /etc/ld.so.conf.d/nvidia.conf + echo "/usr/local/nvidia/lib64" >> /etc/ld.so.conf.d/nvidia.conf + # Configure OpenCL manually + mkdir -pm755 /etc/OpenCL/vendors && echo "libnvidia-opencl.so.1" > /etc/OpenCL/vendors/nvidia.icd + # Configure Vulkan manually + VULKAN_API_VERSION=$(dpkg -s libvulkan1 | grep -oP 'Version: [0-9|\.]+' | grep -oP '[0-9]+(\.[0-9]+)(\.[0-9]+)') + mkdir -pm755 /etc/vulkan/icd.d/ && echo "{\n\ + \"file_format_version\" : \"1.0.0\",\n\ + \"ICD\": {\n\ + \"library_path\": \"libGLX_nvidia.so.0\",\n\ + \"api_version\" : \"${VULKAN_API_VERSION}\"\n\ + }\n\ +}" > /etc/vulkan/icd.d/nvidia_icd.json && \ + # Configure EGL manually + mkdir -pm755 /usr/share/glvnd/egl_vendor.d/ && echo "{\n\ + \"file_format_version\" : \"1.0.0\",\n\ + \"ICD\": {\n\ + \"library_path\": \"libEGL_nvidia.so.0\"\n\ + }\n\ +}" > /usr/share/glvnd/egl_vendor.d/10_nvidia.json + + # Extract NVRTC dependency, https://developer.download.nvidia.com/compute/cuda/redist/cuda_nvrtc/LICENSE.txt + cd /tmp + curl -fsSL -o nvidia_cuda_nvrtc_linux_x86_64.whl "https://developer.download.nvidia.com/compute/redist/nvidia-cuda-nvrtc/nvidia_cuda_nvrtc-11.0.221-cp36-cp36m-linux_x86_64.whl" + unzip -joq -d ./nvrtc nvidia_cuda_nvrtc_linux_x86_64.whl + cd nvrtc + chmod 755 libnvrtc* + find . -maxdepth 1 -type f -name "*libnvrtc.so.*" -exec sh -c 'ln -snf $(basename {}) libnvrtc.so' \; + mv -f libnvrtc* /opt/gstreamer/lib/x86_64-linux-gnu/ +} + +build_nvidia_main "$@" \ No newline at end of file diff --git a/build/COPY_ROOT/opt/ai-dock/bin/preflight.sh b/build/COPY_ROOT/opt/ai-dock/bin/preflight.sh new file mode 100755 index 0000000..7613d93 --- /dev/null +++ b/build/COPY_ROOT/opt/ai-dock/bin/preflight.sh @@ -0,0 +1,13 @@ +#!/bin/false + +# This file will be sourced in init.sh + +function preflight_main() { + preflight_do_something +} + +function preflight_do_something() { + printf "Empty preflight.sh...\n" +} + +preflight_main "$@" \ No newline at end of file diff --git a/build/COPY_ROOT/opt/ai-dock/bin/supervisor-dbus.sh b/build/COPY_ROOT/opt/ai-dock/bin/supervisor-dbus.sh new file mode 100755 index 0000000..94ad52a --- /dev/null +++ b/build/COPY_ROOT/opt/ai-dock/bin/supervisor-dbus.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +SERVICE_NAME=dbus +PIDFILE=/run/dbus/pid + +trap cleanup EXIT + +function cleanup() { + kill $(jobs -p) > /dev/null 2>&1 +} + +# todo improve this +function start() { + if [[ ${SERVERLESS,,} = "true" ]]; then + printf "Refusing to start $SERVICE_NAME in serverless mode\n" + exec sleep 10 + fi + + printf "Starting %s...\n" "$SERVICE_NAME" + mkdir -p /run/dbus + chown messagebus.messagebus /run/dbus + + # Ensure it's not running + if [[ -e $pidfile ]]; then + start-stop-daemon \ + --stop \ + --retry 5 \ + --quiet \ + --oknodo \ + --pidfile $PIDFILE \ + --user messagebus > /dev/null 2>&1 + fi + # Make really sure + pkill dbus-daemon > /dev/null 2>&1 + rm -f $PIDFILE + + dbus-uuidgen --ensure + + exec start-stop-daemon \ + --start \ + --pidfile $PIDFILE \ + --exec /usr/bin/dbus-daemon -- \ + --system \ + --nofork +} + +start 2>&1 \ No newline at end of file diff --git a/build/COPY_ROOT/opt/ai-dock/bin/supervisor-kde-plasma.sh b/build/COPY_ROOT/opt/ai-dock/bin/supervisor-kde-plasma.sh new file mode 100755 index 0000000..b0bb7a6 --- /dev/null +++ b/build/COPY_ROOT/opt/ai-dock/bin/supervisor-kde-plasma.sh @@ -0,0 +1,75 @@ +#!/bin/bash + +# We don't have full control because of dbus +# Best effort to allow stopping and restarting + +trap cleanup EXIT + +SERVICE_NAME="KDE Plasma Desktop" + +function cleanup() { + kill $(jobs -p) > /dev/null 2>&1 & + wait -n + pkill plasma_session > /dev/null 2>&1 + pkill plasmashell > /dev/null 2>&1 + rm -rf /tmp/.X* +} + +function start() { + if [[ ${SERVERLESS,,} = "true" ]]; then + printf "Refusing to start $SERVICE_NAME in serverless mode\n" + exec sleep 10 + fi + + printf "Starting ${SERVICE_NAME}...\n" + + until [[ -S /run/dbus/system_bus_socket ]]; do + printf "Waiting for dbus socket...\n" + sleep 1 + done + + # Start X server + pkill Xvfb + rm -rf /tmp/.X + + /usr/bin/Xvfb \ + "${DISPLAY:-:0}" \ + -ac \ + -screen "0" "8192x4096x${CDEPTH:-24}" \ + -dpi "${DPI:-96}" \ + +extension "RANDR" \ + +extension "GLX" \ + +iglx \ + +extension \ + "MIT-SHM" \ + +render \ + -nolisten "tcp" \ + -noreset \ + -shmem & + + # Kill some processes that hang around (for restart) + pkill plasma_session > /dev/null 2>&1 + pkill plasmashell > /dev/null 2>&1 + + rm -rf /root/.cache + rm -rf /home/${USER_NAME}/.cache + rm -rf /tmp/runtime-user + mkdir -pm700 /tmp/runtime-user + chown ${USER_NAME}:${USER_NAME} /tmp/runtime-user + + until [ -S "/tmp/.X11-unix/X${DISPLAY/:/}" ]; do + printf "Waiting for X11 socket...\n" + sleep 1 + done + + # Start KDE + # Use VirtualGL to run the KDE desktop environment with OpenGL if the GPU is available, otherwise use OpenGL with llvmpipe + export VGL_DISPLAY="${VGL_DISPLAY:-egl}" + export VGL_REFRESHRATE="$REFRESH" + su - $USER_NAME -w VirtualGL VGL_DISPLAY -c \ + '/usr/bin/vglrun +wm /usr/bin/dbus-launch --exit-with-session /usr/bin/startplasma-x11' & + + wait +} + +start 2>&1 \ No newline at end of file diff --git a/build/COPY_ROOT/opt/ai-dock/bin/supervisor-pulseaudio.sh b/build/COPY_ROOT/opt/ai-dock/bin/supervisor-pulseaudio.sh new file mode 100755 index 0000000..a7e3313 --- /dev/null +++ b/build/COPY_ROOT/opt/ai-dock/bin/supervisor-pulseaudio.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +trap cleanup EXIT + +SERVICE_NAME="PulseAudio" + +function cleanup() { + kill $(jobs -p) > /dev/null 2>&1 +} + +function start() { + if [[ ${SERVERLESS,,} = "true" ]]; then + printf "Refusing to start $SERVICE_NAME in serverless mode\n" + exec sleep 10 + fi + + printf "Starting ${SERVICE_NAME}...\n" + + if pulseaudio --check >/dev/null 2>&1; then + pulseaudio -k & + wait -n + fi + + until [ -S /run/dbus/system_bus_socket ]; do + printf "Waiting for dbus socket...\n" + sleep 1 + done + + exec pulseaudio \ + --system \ + --realtime=true \ + --disallow-exit \ + -L 'module-native-protocol-tcp auth-ip-acl=127.0.0.0/8 port=4713 auth-anonymous=1' +} + +start 2>&1 \ No newline at end of file diff --git a/build/COPY_ROOT/opt/ai-dock/bin/supervisor-selkies-gstreamer.sh b/build/COPY_ROOT/opt/ai-dock/bin/supervisor-selkies-gstreamer.sh new file mode 100755 index 0000000..e3135ae --- /dev/null +++ b/build/COPY_ROOT/opt/ai-dock/bin/supervisor-selkies-gstreamer.sh @@ -0,0 +1,80 @@ +#!/bin/bash + +trap cleanup EXIT + +LISTEN_PORT=${SELKIES_PORT_LOCAL:-18080} +METRICS_PORT=${SELKIES_METRICS_PORT:-28080} +PROXY_PORT=${SELKIES_PORT_HOST:-8080} +PROXY_SECURE=true +SERVICE_NAME="KDE Plasma Desktop (WebRTC)" + +function cleanup() { + kill $(jobs -p) > /dev/null 2>&1 + rm /run/http_ports/$PROXY_PORT > /dev/null 2>&1 +} + +# todo improve this +function start() { + if [[ ${SERVERLESS,,} = "true" ]]; then + printf "Refusing to start $SERVICE_NAME in serverless mode\n" + exec sleep 10 + fi + + file_content="$( + jq --null-input \ + --arg listen_port "${LISTEN_PORT}" \ + --arg metrics_port "${METRICS_PORT}" \ + --arg proxy_port "${PROXY_PORT}" \ + --arg proxy_secure "${PROXY_SECURE,,}" \ + --arg service_name "${SERVICE_NAME}" \ + '$ARGS.named' + )" + + printf "%s\n" "$file_content" > /run/http_ports/$PROXY_PORT + + printf "Starting ${SERVICE_NAME}...\n" + kill $(lsof -t -i:$LISTEN_PORT) > /dev/null 2>&1 & + wait -n + + until [[ -S /run/dbus/system_bus_socket ]]; do + printf "Waiting for dbus socket...\n" + sleep 1 + done + + until [ -S "/tmp/.X11-unix/X${DISPLAY/:/}" ]; do + printf "Waiting for X11 socket...\n" + sleep 1 + done + + pkill selkies-gstreamer > /dev/null 2>&1 & + wait + + mkdir -pm755 /dev/input + touch /dev/input/{js0,js1,js2,js3} + + export PWA_APP_NAME="AI-Dock Desktop (selkies)" + export PWA_APP_SHORT_NAME="desktop" + export PWA_START_URL="/index.html" + sed -i \ + -e "s|PWA_APP_NAME|${PWA_APP_NAME}|g" \ + -e "s|PWA_APP_SHORT_NAME|${PWA_APP_SHORT_NAME}|g" \ + -e "s|PWA_START_URL|${PWA_START_URL}|g" \ + /opt/gst-web/manifest.json && \ + sed -i \ + -e "s|PWA_CACHE|${PWA_APP_SHORT_NAME}-webrtc-pwa|g" \ + /opt/gst-web/sw.js + + # Clear the cache registry + rm -rf /home/user/.cache/gstreamer-1.0 + + # Start the selkies-gstreamer WebRTC HTML5 remote desktop application + export LISTEN_PORT + exec su - $USER_NAME -w LISTEN_PORT -c '\ + source /opt/gstreamer/gst-env && \ + export ENABLE_BASIC_AUTH=false && \ + /usr/local/bin/selkies-gstreamer-resize ${SIZEW}x${SIZEH} && \ + selkies-gstreamer --addr="127.0.0.1" --port="${LISTEN_PORT}" \ + ' +} + +start 2>&1 \ No newline at end of file diff --git a/build/COPY_ROOT/root/.gitkeep b/build/COPY_ROOT/root/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/build/COPY_ROOT/usr/.gitkeep b/build/COPY_ROOT/usr/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/build/Dockerfile b/build/Dockerfile new file mode 100644 index 0000000..6239dc4 --- /dev/null +++ b/build/Dockerfile @@ -0,0 +1,83 @@ +# For build automation - Allows building from any ai-dock base image +ARG IMAGE_BASE="ghcr.io/ai-dock/base-image:cuda-11.8.0-base-22.04" +FROM ${IMAGE_BASE} + +LABEL org.opencontainers.image.source https://github.com/ai-dock/python + +LABEL org.opencontainers.image.description "Docker desktop environment" + +LABEL maintainer="Rob Ballantyne " + +ENV USER_GROUPS=$USER_GROUPS,pulse-access + +ENV DISPLAY :0 +ENV XDG_RUNTIME_DIR /tmp/runtime-user +ENV PULSE_SERVER unix:/run/pulse/native + +# Expose NVIDIA libraries and paths +ENV PATH /usr/local/nvidia/bin:/opt/VirtualGL/bin:${PATH} +ENV LD_LIBRARY_PATH /usr/lib/x86_64-linux-gnu:/usr/lib/i386-linux-gnu:/usr/local/nvidia/lib:/usr/local/nvidia/lib64 +# Make all NVIDIA GPUs visible by default +ENV NVIDIA_VISIBLE_DEVICES all +# All NVIDIA driver capabilities should preferably be used, check `NVIDIA_DRIVER_CAPABILITIES` inside the container if things do not work +ENV NVIDIA_DRIVER_CAPABILITIES all +# Disable VSYNC for NVIDIA GPUs +ENV __GL_SYNC_TO_VBLANK 0 + +# Default environment variables (password is "mypasswd") + +ENV SIZEW 1920 +ENV SIZEH 1080 +ENV REFRESH 60 +ENV DPI 96 +ENV CDEPTH 24 +ENV VGL_DISPLAY egl +ENV PASSWD mypasswd +ENV NOVNC_ENABLE false +ENV WEBRTC_ENCODER x264enc +ENV WEBRTC_ENABLE_RESIZE false + +# Set versions for components that should be manually checked before upgrading, other component versions are automatically determined by fetching the version online +ARG VIRTUALGL_VERSION=3.1 +ENV VIRTUALGL_VERSION=${VIRTUALGL_VERSION} +ARG NOVNC_VERSION=1.4.0 +ENV NOVNC_VERSION=${NOVNC_VERSION} + +# Install KDE and other GUI packages +ENV XDG_CURRENT_DESKTOP KDE +ENV XDG_SESSION_DESKTOP KDE +ENV XDG_SESSION_TYPE x11 +ENV DESKTOP_SESSION plasma +ENV KDE_FULL_SESSION true +ENV KWIN_COMPOSE N +ENV KWIN_X11_NO_SYNC_TO_VBLANK 1 +# Use sudoedit to change protected files instead of using sudo on kate +ENV SUDO_EDITOR kate + +# Enable AppImage execution in containers +ENV APPIMAGE_EXTRACT_AND_RUN 1 + +# Add new paths at front +#ENV PATH=/opt/another/bin:$PATH + +# Copy early so we can use scripts in the build - Changes to these files will invalidate the cache and cause a rebuild. +COPY ./COPY_ROOT/ / + +# Define the startup mamba environment for interactive sessions. +# ENV for inheritance + +ENV OPT_SYNC=$OPT_SYNC +ENV IMAGE_SLUG="desktop" +# Use build scripts to ensure we can build all targets from one Dockerfile in a single layer. +# Don't put anything heavy in here - We can use multi-stage building above if necessary. + +RUN set -eo pipefail && /opt/ai-dock/bin/build/layer0/init.sh | tee /var/log/build.log +ENV LD_PRELOAD /usr/local/lib/selkies-js-interposer/joystick_interposer.so${LD_PRELOAD:+:${LD_PRELOAD}} +ENV SDL_JOYSTICK_DEVICE /dev/input/js0 + +# Copy overrides and new files into a final layer for fast rebuilds. Uncomment below +COPY ./COPY_ROOT_EXTRA/ / +RUN set -eo pipefail && /opt/ai-dock/bin/build/layer1/init.sh | tee -a /var/log/build.log + +# Keep init.sh as-is and place additional logic in /opt/ai-dock/bin/preflight.sh +CMD ["init.sh"] diff --git a/config/provisioning/default.sh b/config/provisioning/default.sh new file mode 100755 index 0000000..cf4cb1a --- /dev/null +++ b/config/provisioning/default.sh @@ -0,0 +1,8 @@ +#!/bin/false + +# This file will be sourced in init.sh +# You can edit below here and make it do something useful + +printf "Hello world!\n" + +sleep 2 \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..2cdc741 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,71 @@ +version: "3.8" +# Compose file build variables set in .env +services: + supervisor: + build: + context: ./build + args: + IMAGE_BASE: ${IMAGE_BASE:-ghcr.io/ai-dock/base-image:cuda-11.8.0-base-22.04} + tags: + - "ghcr.io/ai-dock/desktop:${IMAGE_TAG:-cuda-11.8.0-base-22.04}" + + image: ghcr.io/ai-dock/desktop:${IMAGE_TAG:-cuda-11.8.0-base-22.04} + + ## For Nvidia GPU's - You probably want to uncomment this + #deploy: + # resources: + # reservations: + # devices: + # - driver: nvidia + # count: all + # capabilities: [gpu] + + devices: + - "/dev/dri:/dev/dri" + # For AMD GPU + #- "/dev/kfd:/dev/kfd" + + group_add: + - video + # - render + + volumes: + # For Rclone mount + #- /etc/passwd:/etc/passwd:ro + #- /etc/group:/etc/group:ro + - ./config/rclone:/etc/rclone + # Workspace + - ./workspace:${WORKSPACE:-/workspace}:rshared + # Will echo to root-owned authorized_keys file; + # Avoids changing local file owner + - ./config/authorized_keys:/root/.ssh/authorized_keys_mount + - ./config/provisioning/default.sh:/opt/ai-dock/bin/provisioning.sh + + ports: + # SSH available on host machine port 2222 to avoid conflict. Change to suit + - ${SSH_PORT_HOST:-2222}:${SSH_PORT_LOCAL:-22} + # Web UI for easy service access + - ${SERVICEPORTAL_PORT_HOST:-1111}:${SERVICEPORTAL_PORT_HOST:-1111} + # Gstreamer + - 8080:8080 + # Rclone webserver for interactive configuration + - ${RCLONE_PORT_HOST:-53682}:${RCLONE_PORT_HOST:-53682} + + environment: + # Don't enclose values in quotes + - DIRECT_ADDRESS=${DIRECT_ADDRESS:-127.0.0.1} + - DIRECT_ADDRESS_GET_WAN=${DIRECT_ADDRESS_GET_WAN:-false} + - WORKSPACE=${WORKSPACE:-/workspace} + - WORKSPACE_SYNC=${WORKSPACE_SYNC:-true} + - CF_TUNNEL_TOKEN=${CF_TUNNEL_TOKEN:-} + - CF_QUICK_TUNNELS=${CF_QUICK_TUNNELS:-true} + - WEB_ENABLE_AUTH=${WEB_ENABLE_AUTH:-true} + - WEB_USER=${WEB_USER:-user} + - WEB_PASSWORD=${WEB_PASSWORD:-password} + - SSH_PORT_HOST=${SSH_PORT_HOST:-2222} + - SSH_PORT_LOCAL=${SSH_PORT_LOCAL:-22} + - SERVICEPORTAL_PORT_HOST=${SERVICEPORTAL_PORT_HOST:-1111} + - SERVICEPORTAL_PORT_LOCAL=${SERVICEPORTAL_PORT_LOCAL:-11111} + - SERVICEPORTAL_METRICS_PORT=${SERVICEPORTAL_METRICS_PORT:-21111} + - SERVERLESS=${SERVERLESS:-false} + #- PROVISIONING_SCRIPT=https://raw.githubusercontent.com/ai-dock/python/main/config/provisioning/default.sh \ No newline at end of file