diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..bf9c2c4e3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,40 @@ +node_modules/ +.env +.env.local +*.log +dist/ +.DS_Store +Thumbs.db + +# Windows reserved device names +nul +NUL + +# Rust build artifacts +target/ + +# Tauri build output +coeadapt-launcher/src-tauri/target/ +coeadapt-launcher/src-tauri/binaries/* +!coeadapt-launcher/src-tauri/binaries/.gitkeep + +# Lock files (platform-specific) +*.lock +!bun.lock + +# Secrets and license keys +*.key +!*.pub + +# Development artifacts +Brand/ +/*.png +.claude/ +.playwright-mcp/ +console-errors.log + +# AI prompt files, build prompts, and internal docs (never commit to public repo) +*-Prompt.md +*-prompt.md +CLAUDE.md +docs/COEADAPT_API.md diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ecdd7076e..047a28d49 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,1049 +1,90 @@ -image: docker +############ +# Settings # +############ +image: docker:24.0.6 services: - - docker:dind + - docker:24.0.6-dind +stages: + - template + - run variables: - KASM_RELEASE: "1.12.0" - DOCKER_AUTH_CONFIG: ${_DOCKER_AUTH_CONFIG} - PLATFORM: "linux/amd64" - ARM_BUILDS: ",chromium,firefox,gimp,remmina,terminal,ubuntu-bionic-desktop,ubuntu-focal-desktop,ubuntu-jammy-desktop,vlc,vs-code,doom,sublime-text,tor-browser,java-dev,telegram,opensuse-15-desktop,oracle-8-desktop,libre-office,thunderbird,audacity,deluge,filezilla,inkscape,pinta,qbittorrent,vivaldi,minetest,retroarch,super-tux-kart,ubuntu-focal-dind,ubuntu-focal-dind-rootless,ubuntu-jammy-dind,ubuntu-jammy-dind-rootless,almalinux-8-desktop,almalinux-9-desktop,alpine-317-desktop,debian-bullseye-desktop,fedora-37-desktop,kali-rolling-desktop,oracle-9-desktop,parrotos-5-desktop,rockylinux-8-desktop,rockylinux-9-desktop," - CORE_IMAGE_TAG: "develop" - CORE_IMAGE: "core-ubuntu-focal" + BASE_TAG: "develop" USE_PRIVATE_IMAGES: 0 - -###################### -# YAML level anchors # -###################### -.MULTI_ARCH_BUILDS: &MULTI_ARCH_BUILDS - - audacity - - chromium - - deluge - - doom - - filezilla - - firefox - - gimp - - inkscape - - java-dev - - libre-office - - opensuse-15-desktop - - oracle-8-desktop - - pinta - - qbittorrent - - remmina - - sublime-text - - telegram - - terminal - - thunderbird - - tor-browser - - ubuntu-focal-desktop - - ubuntu-jammy-desktop - - vlc - - vs-code - -.MULTI_ARCH_BUILDS2: &MULTI_ARCH_BUILDS2 - - almalinux-8-desktop - - almalinux-9-desktop - - alpine-317-desktop - - debian-bullseye-desktop - - fedora-37-desktop - - kali-rolling-desktop - - minetest - - oracle-9-desktop - - parrotos-5-desktop - - retroarch - - rockylinux-8-desktop - - rockylinux-9-desktop - - super-tux-kart - - ubuntu-focal-dind - - ubuntu-focal-dind-rootless - - ubuntu-jammy-dind - - ubuntu-jammy-dind-rootless - - vivaldi - -.SINGLE_ARCH_BUILDS: &SINGLE_ARCH_BUILDS - - atom - - blender - - brave - - centos-7-desktop - - chrome - - desktop - - desktop-deluxe - - discord - - edge - - hunchly - - insomnia - - maltego - - only-office - - oracle-7-desktop - - postman - - remnux-focal-desktop - - signal - - steam - - tracelabs - - unityhub - - zoom - - zsnes - -.BROWSER_IMAGES: &BROWSER_IMAGES - - brave - - chrome - - chromium - - edge - - firefox - - tor-browser - - vivaldi - -.APP_IMAGES: &APP_IMAGES - - atom - - audacity - - blender - - deluge - - discord - - filezilla - - gimp - - hunchly - - inkscape - - insomnia - - java-dev - - libre-office - - maltego - - only-office - - pinta - - postman - - qbittorrent - - remmina - - signal - - steam - - sublime-text - - telegram - - terminal - - thunderbird - - unityhub - - vlc - - vs-code - - zoom - - zsnes - -.UBUNTU_IMAGES: &UBUNTU_IMAGES - - desktop - - desktop-deluxe - - remnux-focal-desktop - - ubuntu-focal-desktop - - ubuntu-jammy-desktop - - ubuntu-focal-dind - - ubuntu-focal-dind-rootless - - ubuntu-jammy-dind - - ubuntu-jammy-dind-rootless - -.NON_UBUNTU_IMAGES: &NON_UBUNTU_IMAGES - - almalinux-8-desktop - - almalinux-9-desktop - - alpine-317-desktop - - centos-7-desktop - - debian-bullseye-desktop - - kali-rolling-desktop - - opensuse-15-desktop - - oracle-7-desktop - - oracle-8-desktop - - oracle-9-desktop - - parrotos-5-desktop - - rockylinux-8-desktop - - rockylinux-9-desktop - - tracelabs - -.GAME_IMAGES: &GAME_IMAGES - - doom - - minetest - - retroarch - - super-tux-kart - -stages: - - readme - - build - - manifest - - test - - linktests - + KASM_RELEASE: "1.18.0" + TEST_INSTALLER: "https://kasm-static-content.s3.amazonaws.com/kasm_release_1.18.0.09f70a.tar.gz" + MIRROR_ORG_NAME: "kasmtech" +default: + retry: 2 before_script: - - docker login --username $DOCKER_HUB_USERNAME --password $DOCKER_HUB_PASSWORD - export SANITIZED_BRANCH="$(echo $CI_COMMIT_REF_NAME | sed -r 's#^release/##' | sed 's/\//_/g')" - - export SANITIZED_ROLLING_BRANCH=${SANITIZED_BRANCH}-rolling - -############################################################################################### -# Jobs for the develop and release branches. They should push to the private and public repos # -############################################################################################### -build_browser_images: - stage: build - image: ${ORG_NAME}/docker-buildx-private:develop - script: - - BUILD_PLATFORM=$PLATFORM - - if [[ "${ARM_BUILDS}" == *",${KASM_IMAGE},"* ]]; then BUILD_PLATFORM="linux/amd64,linux/arm64"; fi; - - echo "Building ${KASM_IMAGE} based on ${CORE_IMAGE} for platforms ${BUILD_PLATFORM}" - # to get qemu ready - - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - # to prepare the buildx env - - docker buildx create --use - # Ensure readme and description files are present - - ls docs/$KASM_IMAGE/README.md - - ls docs/$KASM_IMAGE/description.txt - # Check for private variable to build against private core images - - if [[ $USE_PRIVATE_IMAGES -eq 1 ]]; then CORE_IMAGE=$CORE_IMAGE-private; fi; - - > - docker buildx build --push - --platform $BUILD_PLATFORM - -t ${ORG_NAME}/$KASM_IMAGE:$SANITIZED_BRANCH - -t ${ORG_NAME}/$KASM_IMAGE:$SANITIZED_ROLLING_BRANCH - -t ${ORG_NAME}/$KASM_IMAGE-private:$SANITIZED_BRANCH - -t ${ORG_NAME}/$KASM_IMAGE-private:$SANITIZED_ROLLING_BRANCH - --build-arg BASE_IMAGE=$CORE_IMAGE - --build-arg BASE_TAG=$CORE_IMAGE_TAG - -f dockerfile-kasm-$KASM_IMAGE . - only: - - develop - - /^release\/.*$/ - except: - - schedules - tags: - - aws-autoscale - parallel: - matrix: - - KASM_IMAGE: *BROWSER_IMAGES - -build_app_images: - stage: build - image: ${ORG_NAME}/docker-buildx-private:develop - script: - - BUILD_PLATFORM=$PLATFORM - - if [[ "${ARM_BUILDS}" == *",${KASM_IMAGE},"* ]]; then BUILD_PLATFORM="linux/amd64,linux/arm64"; fi; - - echo "Building ${KASM_IMAGE} based on ${CORE_IMAGE} for platforms ${BUILD_PLATFORM}" - # to get qemu ready - - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - # to prepare the buildx env - - docker buildx create --use - # Ensure readme and description files are present - - ls docs/$KASM_IMAGE/README.md - - ls docs/$KASM_IMAGE/description.txt - # Check for private variable to build against private core images - - if [[ $USE_PRIVATE_IMAGES -eq 1 ]]; then CORE_IMAGE=$CORE_IMAGE-private; fi; - - > - docker buildx build --push - --platform $BUILD_PLATFORM - -t ${ORG_NAME}/$KASM_IMAGE:$SANITIZED_BRANCH - -t ${ORG_NAME}/$KASM_IMAGE:$SANITIZED_ROLLING_BRANCH - -t ${ORG_NAME}/$KASM_IMAGE-private:$SANITIZED_BRANCH - -t ${ORG_NAME}/$KASM_IMAGE-private:$SANITIZED_ROLLING_BRANCH - --build-arg BASE_IMAGE=$CORE_IMAGE - --build-arg BASE_TAG=$CORE_IMAGE_TAG - -f dockerfile-kasm-$KASM_IMAGE . - only: - - develop - - /^release\/.*$/ - except: - - schedules - tags: - - aws-autoscale - parallel: - matrix: - - KASM_IMAGE: *APP_IMAGES - -build_ubuntu_desktop_images: - stage: build - image: ${ORG_NAME}/docker-buildx-private:develop - script: - - BUILD_PLATFORM=$PLATFORM - - if [[ "${ARM_BUILDS}" == *",${KASM_IMAGE},"* ]]; then BUILD_PLATFORM="linux/amd64,linux/arm64"; fi; - - echo "Building ${KASM_IMAGE} for platforms ${BUILD_PLATFORM}" - # to get qemu ready - - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - # to prepare the buildx env - - docker buildx create --use - # Ensure readme and description files are present - - ls docs/$KASM_IMAGE/README.md - - ls docs/$KASM_IMAGE/description.txt - # Check for private variable to build against private core images - - if [[ $KASM_IMAGE =~ 'remnux-focal-desktop' ]]; then CORE_IMAGE=core-remnux-focal; fi - - if [[ $KASM_IMAGE =~ 'ubuntu-jammy-desktop' ]]; then CORE_IMAGE=core-ubuntu-jammy; fi - - if [[ $KASM_IMAGE =~ 'ubuntu-jammy-dind' ]]; then CORE_IMAGE=core-ubuntu-jammy; fi - - if [[ $KASM_IMAGE =~ 'ubuntu-jammy-dind-rootless' ]]; then CORE_IMAGE=core-ubuntu-jammy; fi - # Check for private variable to build against private core images - - if [[ $USE_PRIVATE_IMAGES -eq 1 ]]; then CORE_IMAGE=$CORE_IMAGE-private; fi; - - > - docker buildx build --push - --platform $BUILD_PLATFORM - -t ${ORG_NAME}/$KASM_IMAGE:$SANITIZED_BRANCH - -t ${ORG_NAME}/$KASM_IMAGE:$SANITIZED_ROLLING_BRANCH - -t ${ORG_NAME}/$KASM_IMAGE-private:$SANITIZED_BRANCH - -t ${ORG_NAME}/$KASM_IMAGE-private:$SANITIZED_ROLLING_BRANCH - --build-arg BASE_IMAGE=$CORE_IMAGE - --build-arg BASE_TAG=$CORE_IMAGE_TAG - -f dockerfile-kasm-$KASM_IMAGE . - only: - - develop - - /^release\/.*$/ - except: - - schedules - tags: - - aws-autoscale - parallel: - matrix: - - KASM_IMAGE: *UBUNTU_IMAGES - -build_non_ubuntu: - stage: build - image: ${ORG_NAME}/docker-buildx-private:develop - script: - - BUILD_PLATFORM=$PLATFORM - - if [[ "${ARM_BUILDS}" == *",${KASM_IMAGE},"* ]]; then BUILD_PLATFORM="linux/amd64,linux/arm64"; fi; - - echo "Building ${KASM_IMAGE} for platforms ${BUILD_PLATFORM}" - # to get qemu ready - - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - # to prepare the buildx env - - docker buildx create --use - # Ensure readme and description files are present - - ls docs/$KASM_IMAGE/README.md - - ls docs/$KASM_IMAGE/description.txt - # Set base image based on kasm_image variable - - if [[ $KASM_IMAGE =~ 'almalinux-8-desktop' ]]; then CORE_IMAGE=core-almalinux-8; fi - - if [[ $KASM_IMAGE =~ 'almalinux-9-desktop' ]]; then CORE_IMAGE=core-almalinux-9; fi - - if [[ $KASM_IMAGE =~ 'alpine-317-desktop' ]]; then CORE_IMAGE=core-alpine-317; fi - - if [[ $KASM_IMAGE =~ 'centos-7-desktop' ]]; then CORE_IMAGE=core-centos-7; fi - - if [[ $KASM_IMAGE =~ 'debian-bullseye-desktop' ]]; then CORE_IMAGE=core-debian-bullseye; fi - - if [[ $KASM_IMAGE =~ 'fedora-37-desktop' ]]; then CORE_IMAGE=core-fedora-37; fi - - if [[ $KASM_IMAGE =~ 'kali-rolling-desktop' ]]; then CORE_IMAGE=core-kali-rolling; fi - - if [[ $KASM_IMAGE =~ 'opensuse-15-desktop' ]]; then CORE_IMAGE=core-opensuse-15; fi - - if [[ $KASM_IMAGE =~ 'oracle-7-desktop' ]]; then CORE_IMAGE=core-oracle-7; fi - - if [[ $KASM_IMAGE =~ 'oracle-8-desktop' ]]; then CORE_IMAGE=core-oracle-8; fi - - if [[ $KASM_IMAGE =~ 'oracle-9-desktop' ]]; then CORE_IMAGE=core-oracle-9; fi - - if [[ $KASM_IMAGE =~ 'parrotos-5-desktop' ]]; then CORE_IMAGE=core-parrotos-5; fi - - if [[ $KASM_IMAGE =~ 'rockylinux-8-desktop' ]]; then CORE_IMAGE=core-rockylinux-8; fi - - if [[ $KASM_IMAGE =~ 'rockylinux-9-desktop' ]]; then CORE_IMAGE=core-rockylinux-9; fi - - if [[ $KASM_IMAGE =~ 'tracelabs' ]]; then CORE_IMAGE=core-kali-rolling; fi - # Check for private variable to build against private core images - - if [[ $USE_PRIVATE_IMAGES -eq 1 ]]; then CORE_IMAGE=$CORE_IMAGE-private; fi; - - > - docker buildx build --push - --platform $BUILD_PLATFORM - -t ${ORG_NAME}/$KASM_IMAGE:$SANITIZED_BRANCH - -t ${ORG_NAME}/$KASM_IMAGE:$SANITIZED_ROLLING_BRANCH - -t ${ORG_NAME}/$KASM_IMAGE-private:$SANITIZED_BRANCH - -t ${ORG_NAME}/$KASM_IMAGE-private:$SANITIZED_ROLLING_BRANCH - --build-arg BASE_IMAGE=$CORE_IMAGE - --build-arg BASE_TAG=$CORE_IMAGE_TAG - -f dockerfile-kasm-$KASM_IMAGE . - only: - - develop - - /^release\/.*$/ - except: - - schedules - tags: - - aws-autoscale - parallel: - matrix: - - KASM_IMAGE: *NON_UBUNTU_IMAGES -build_fedora_37: - stage: build - script: - - > - docker build - -t ${ORG_NAME}/fedora-37-desktop:$(arch)-$SANITIZED_BRANCH - -f dockerfile-kasm-fedora-37-desktop . - - docker push ${ORG_NAME}/fedora-37-desktop:$(arch)-$SANITIZED_BRANCH - only: - - develop - - /^release\/.*$/ - except: - - schedules - tags: - - ${TAG} - parallel: - matrix: - - TAG: [ aws-autoscale, aws-autoscale-arm64 ] - -manifest_fedora_37: - stage: manifest - script: - - docker pull ${ORG_NAME}/fedora-37-desktop:x86_64-$SANITIZED_BRANCH - - docker pull ${ORG_NAME}/fedora-37-desktop:aarch64-$SANITIZED_BRANCH - - "docker manifest push --purge ${ORG_NAME}/fedora-37-desktop:$SANITIZED_BRANCH || :" - - docker manifest create ${ORG_NAME}/fedora-37-desktop:$SANITIZED_BRANCH ${ORG_NAME}/fedora-37-desktop:x86_64-$SANITIZED_BRANCH ${ORG_NAME}/fedora-37-desktop:aarch64-$SANITIZED_BRANCH - - docker manifest annotate ${ORG_NAME}/fedora-37-desktop:$SANITIZED_BRANCH ${ORG_NAME}/fedora-37-desktop:aarch64-$SANITIZED_BRANCH --os linux --arch arm64 --variant v8 - - docker manifest push --purge ${ORG_NAME}/fedora-37-desktop:$SANITIZED_BRANCH +####################### +# Build from template # +####################### +template: + stage: template + rules: + - when: always + script: + - apk add py3-jinja2 py3-yaml + - cd ci-scripts + - python3 template-gitlab.py + tags: + - oci-amd-scheduled + artifacts: + paths: + - gitlab-ci.yml + +pipeline: + stage: run + rules: + - if: '$README_USERNAME_RUN || $README_PASSWORD_RUN || $QUAY_API_KEY_RUN || $DOCKERHUB_REVERT_RUN || $REVERT_IS_ROLLING_RUN' + when: never + - when: on_success + variables: + PARENT_PIPELINE_SOURCE: "$CI_PIPELINE_SOURCE" + RUN_SET: "$RUN_SET" + trigger: + include: + - artifact: gitlab-ci.yml + job: template + +pipeline_readme: + stage: run only: - - develop - - /^release\/.*$/ - except: - - schedules - needs: [ build_fedora_37 ] - tags: - - aws-autoscale - -build_games: - stage: build - image: ${ORG_NAME}/docker-buildx-private:develop - script: - - BUILD_PLATFORM=$PLATFORM - - if [[ "${ARM_BUILDS}" == *",${KASM_IMAGE},"* ]]; then BUILD_PLATFORM="linux/amd64,linux/arm64"; fi; - - echo "Building ${KASM_IMAGE} for platforms ${BUILD_PLATFORM}" - # to get qemu ready - - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - # to prepare the buildx env - - docker buildx create --use - # Ensure readme and description files are present - - ls docs/$KASM_IMAGE/README.md - - ls docs/$KASM_IMAGE/description.txt - # Check for private variable to build against private core images - - if [[ $USE_PRIVATE_IMAGES -eq 1 ]]; then CORE_IMAGE=$CORE_IMAGE-private; fi; - - > - docker buildx build --push - --platform $BUILD_PLATFORM - -t ${ORG_NAME}/$KASM_IMAGE:$SANITIZED_BRANCH - -t ${ORG_NAME}/$KASM_IMAGE:$SANITIZED_ROLLING_BRANCH - -t ${ORG_NAME}/$KASM_IMAGE-private:$SANITIZED_BRANCH - -t ${ORG_NAME}/$KASM_IMAGE-private:$SANITIZED_ROLLING_BRANCH - --build-arg BASE_IMAGE=$CORE_IMAGE - --build-arg BASE_TAG=$CORE_IMAGE_TAG - -f dockerfile-kasm-$KASM_IMAGE . - only: - - develop - - /^release\/.*$/ - except: - - schedules - tags: - - aws-autoscale - parallel: - matrix: - - KASM_IMAGE: *GAME_IMAGES - -################################################################################################################################################################ -# These jobs should run on the feature/bugfix branches - anything that is not the develop or release branches. It should only push images to the private repos # -################################################################################################################################################################ -build_multi_arch_dev: - stage: build - image: ${ORG_NAME}/docker-buildx-private:develop - script: - # Ensure readme and description files are present - - ls docs/$KASM_IMAGE/README.md - - ls docs/$KASM_IMAGE/description.txt - # Set core image names - - if [[ $KASM_IMAGE =~ 'almalinux-8-desktop' ]]; then CORE_IMAGE=core-almalinux-8; fi - - if [[ $KASM_IMAGE =~ 'almalinux-9-desktop' ]]; then CORE_IMAGE=core-almalinux-9; fi - - if [[ $KASM_IMAGE =~ 'alpine-317-desktop' ]]; then CORE_IMAGE=core-alpine-317; fi - - if [[ $KASM_IMAGE =~ 'centos-7-desktop' ]]; then CORE_IMAGE=core-centos-7; fi - - if [[ $KASM_IMAGE =~ 'debian-bullseye-desktop' ]]; then CORE_IMAGE=core-debian-bullseye; fi - - if [[ $KASM_IMAGE =~ 'fedora-37-desktop' ]]; then CORE_IMAGE=core-fedora-37; fi - - if [[ $KASM_IMAGE =~ 'kali-rolling-desktop' ]]; then CORE_IMAGE=core-kali-rolling; fi - - if [[ $KASM_IMAGE =~ 'opensuse-15-desktop' ]]; then CORE_IMAGE=core-opensuse-15; fi - - if [[ $KASM_IMAGE =~ 'oracle-7-desktop' ]]; then CORE_IMAGE=core-oracle-7; fi - - if [[ $KASM_IMAGE =~ 'oracle-8-desktop' ]]; then CORE_IMAGE=core-oracle-8; fi - - if [[ $KASM_IMAGE =~ 'oracle-9-desktop' ]]; then CORE_IMAGE=core-oracle-9; fi - - if [[ $KASM_IMAGE =~ 'parrotos-5-desktop' ]]; then CORE_IMAGE=core-parrotos-5; fi - - if [[ $KASM_IMAGE =~ 'rockylinux-8-desktop' ]]; then CORE_IMAGE=core-rockylinux-8; fi - - if [[ $KASM_IMAGE =~ 'rockylinux-9-desktop' ]]; then CORE_IMAGE=core-rockylinux-9; fi - - if [[ $KASM_IMAGE =~ 'tracelabs' ]]; then CORE_IMAGE=core-kali-rolling; fi - - if [[ $KASM_IMAGE =~ 'ubuntu-jammy-desktop' ]]; then CORE_IMAGE=core-ubuntu-jammy; fi - - if [[ $KASM_IMAGE =~ 'ubuntu-jammy-dind-rootless' ]]; then CORE_IMAGE=core-ubuntu-jammy; fi - - if [[ $KASM_IMAGE =~ 'ubuntu-jammy-dind' ]]; then CORE_IMAGE=core-ubuntu-jammy; fi - # Check for private variable to build against private core images - - if [[ $USE_PRIVATE_IMAGES -eq 1 ]]; then CORE_IMAGE=$CORE_IMAGE-private; fi; - - > - docker build - -t ${ORG_NAME}/$KASM_IMAGE-private:$(arch)-$SANITIZED_BRANCH - --build-arg BASE_IMAGE=$CORE_IMAGE - --build-arg BASE_TAG=$CORE_IMAGE_TAG - -f dockerfile-kasm-$KASM_IMAGE . - - docker push ${ORG_NAME}/$KASM_IMAGE-private:$(arch)-$SANITIZED_BRANCH - except: - - develop - - /^release\/.*$/ - tags: - - ${TAG} - parallel: - matrix: - - TAG: [ aws-autoscale, aws-autoscale-arm64 ] - KASM_IMAGE: *MULTI_ARCH_BUILDS - -build_multi_arch_dev2: - stage: build - image: ${ORG_NAME}/docker-buildx-private:develop - script: - # Ensure readme and description files are present - - ls docs/$KASM_IMAGE/README.md - - ls docs/$KASM_IMAGE/description.txt - # Set core image names - - if [[ $KASM_IMAGE =~ 'almalinux-8-desktop' ]]; then CORE_IMAGE=core-almalinux-8; fi - - if [[ $KASM_IMAGE =~ 'almalinux-9-desktop' ]]; then CORE_IMAGE=core-almalinux-9; fi - - if [[ $KASM_IMAGE =~ 'alpine-317-desktop' ]]; then CORE_IMAGE=core-alpine-317; fi - - if [[ $KASM_IMAGE =~ 'centos-7-desktop' ]]; then CORE_IMAGE=core-centos-7; fi - - if [[ $KASM_IMAGE =~ 'debian-bullseye-desktop' ]]; then CORE_IMAGE=core-debian-bullseye; fi - - if [[ $KASM_IMAGE =~ 'fedora-37-desktop' ]]; then CORE_IMAGE=core-fedora-37; fi - - if [[ $KASM_IMAGE =~ 'kali-rolling-desktop' ]]; then CORE_IMAGE=core-kali-rolling; fi - - if [[ $KASM_IMAGE =~ 'opensuse-15-desktop' ]]; then CORE_IMAGE=core-opensuse-15; fi - - if [[ $KASM_IMAGE =~ 'oracle-7-desktop' ]]; then CORE_IMAGE=core-oracle-7; fi - - if [[ $KASM_IMAGE =~ 'oracle-8-desktop' ]]; then CORE_IMAGE=core-oracle-8; fi - - if [[ $KASM_IMAGE =~ 'oracle-9-desktop' ]]; then CORE_IMAGE=core-oracle-9; fi - - if [[ $KASM_IMAGE =~ 'parrotos-5-desktop' ]]; then CORE_IMAGE=core-parrotos-5; fi - - if [[ $KASM_IMAGE =~ 'rockylinux-8-desktop' ]]; then CORE_IMAGE=core-rockylinux-8; fi - - if [[ $KASM_IMAGE =~ 'rockylinux-9-desktop' ]]; then CORE_IMAGE=core-rockylinux-9; fi - - if [[ $KASM_IMAGE =~ 'tracelabs' ]]; then CORE_IMAGE=core-kali-rolling; fi - - if [[ $KASM_IMAGE =~ 'ubuntu-jammy-desktop' ]]; then CORE_IMAGE=core-ubuntu-jammy; fi - - if [[ $KASM_IMAGE =~ 'ubuntu-jammy-dind-rootless' ]]; then CORE_IMAGE=core-ubuntu-jammy; fi - - if [[ $KASM_IMAGE =~ 'ubuntu-jammy-dind' ]]; then CORE_IMAGE=core-ubuntu-jammy; fi - # Check for private variable to build against private core images - - if [[ $USE_PRIVATE_IMAGES -eq 1 ]]; then CORE_IMAGE=$CORE_IMAGE-private; fi; - - > - docker build - -t ${ORG_NAME}/$KASM_IMAGE-private:$(arch)-$SANITIZED_BRANCH - --build-arg BASE_IMAGE=$CORE_IMAGE - --build-arg BASE_TAG=$CORE_IMAGE_TAG - -f dockerfile-kasm-$KASM_IMAGE . - - docker push ${ORG_NAME}/$KASM_IMAGE-private:$(arch)-$SANITIZED_BRANCH - except: - - develop - - /^release\/.*$/ - tags: - - ${TAG} - parallel: - matrix: - - TAG: [ aws-autoscale, aws-autoscale-arm64 ] - KASM_IMAGE: *MULTI_ARCH_BUILDS2 - -build_single_arch_dev: - stage: build - image: ${ORG_NAME}/docker-buildx-private:develop - script: - # Ensure readme and description files are present - - ls docs/$KASM_IMAGE/README.md - - ls docs/$KASM_IMAGE/description.txt - # Set core image names - - if [[ $KASM_IMAGE =~ 'centos-7-desktop' ]]; then CORE_IMAGE=core-centos-7; fi - - if [[ $KASM_IMAGE =~ 'tracelabs' ]]; then CORE_IMAGE=core-kali-rolling; fi - - if [[ $KASM_IMAGE =~ 'oracle-7-desktop' ]]; then CORE_IMAGE=core-oracle-7; fi - - if [[ $KASM_IMAGE =~ 'remnux-focal-desktop' ]]; then CORE_IMAGE=core-remnux-focal; fi - # Check for private variable to build against private core images - - if [[ $USE_PRIVATE_IMAGES -eq 1 ]]; then CORE_IMAGE=$CORE_IMAGE-private; fi; - - > - docker build - -t ${ORG_NAME}/$KASM_IMAGE-private:$SANITIZED_BRANCH - --build-arg BASE_IMAGE=$CORE_IMAGE - --build-arg BASE_TAG=$CORE_IMAGE_TAG - -f dockerfile-kasm-$KASM_IMAGE . - - docker push ${ORG_NAME}/$KASM_IMAGE-private:$SANITIZED_BRANCH - except: - - develop - - /^release\/.*$/ - tags: - - aws-autoscale - parallel: - matrix: - - KASM_IMAGE: *SINGLE_ARCH_BUILDS - -test_multi_arch_dev: - stage: test - script: - - docker pull kasmweb/kasm-tester:1.13.0 - - > - docker run --rm --privileged - -e KASM_PORT=443 - -e KASM_PATH=/opt/kasm - -e KASM_PASSWORD=password123 - -e PUID=1000 - -e DOCKERUSER=$DOCKER_HUB_USERNAME - -e DOCKERPASS=$DOCKER_HUB_PASSWORD - -e TEST_IMAGE="${ORG_NAME}/${KASM_IMAGE}-private:$(arch)-$SANITIZED_BRANCH" - -e TEST_WEBFILTER="false" - -e AWS_KEY=${KASM_TEST_AWS_KEY} - -e AWS_SECRET="${KASM_TEST_AWS_SECRET}" - -e SLACK_TOKEN=${SLACK_TOKEN} - -e S3_BUCKET=kasm-ci - -e COMMIT=${CI_COMMIT_SHA} - -e REPO=workspaces-images - kasmweb/kasm-tester:1.13.0 - except: - - develop - - /^release\/.*$/ - needs: [ manifest_dev ] - tags: - - ${TAG} - parallel: - matrix: - - TAG: [ aws-autoscale, aws-autoscale-arm64 ] - KASM_IMAGE: *MULTI_ARCH_BUILDS - -test_multi_arch_dev2: - stage: test - script: - - docker pull kasmweb/kasm-tester:1.13.0 - - > - docker run --rm --privileged - -e KASM_PORT=443 - -e KASM_PATH=/opt/kasm - -e KASM_PASSWORD=password123 - -e PUID=1000 - -e DOCKERUSER=$DOCKER_HUB_USERNAME - -e DOCKERPASS=$DOCKER_HUB_PASSWORD - -e TEST_IMAGE="${ORG_NAME}/${KASM_IMAGE}-private:$(arch)-$SANITIZED_BRANCH" - -e TEST_WEBFILTER="false" - -e AWS_KEY=${KASM_TEST_AWS_KEY} - -e AWS_SECRET="${KASM_TEST_AWS_SECRET}" - -e SLACK_TOKEN=${SLACK_TOKEN} - -e S3_BUCKET=kasm-ci - -e COMMIT=${CI_COMMIT_SHA} - -e REPO=workspaces-images - kasmweb/kasm-tester:1.13.0 - except: - - develop - - /^release\/.*$/ - needs: [ manifest_dev2 ] - tags: - - ${TAG} - parallel: - matrix: - - TAG: [ aws-autoscale, aws-autoscale-arm64 ] - KASM_IMAGE: *MULTI_ARCH_BUILDS2 - -test_single_arch_dev: - stage: test - script: - - docker pull kasmweb/kasm-tester:1.13.0 - - > - docker run --rm --privileged - -e KASM_PORT=443 - -e KASM_PATH=/opt/kasm - -e KASM_PASSWORD=password123 - -e PUID=1000 - -e DOCKERUSER=$DOCKER_HUB_USERNAME - -e DOCKERPASS=$DOCKER_HUB_PASSWORD - -e TEST_IMAGE="${ORG_NAME}/${KASM_IMAGE}-private:$SANITIZED_BRANCH" - -e TEST_WEBFILTER="false" - -e AWS_KEY=${KASM_TEST_AWS_KEY} - -e AWS_SECRET="${KASM_TEST_AWS_SECRET}" - -e SLACK_TOKEN=${SLACK_TOKEN} - -e S3_BUCKET=kasm-ci - -e COMMIT=${CI_COMMIT_SHA} - -e REPO=workspaces-images - kasmweb/kasm-tester:1.13.0 - except: - - develop - - /^release\/.*$/ - needs: [ build_single_arch_dev ] - tags: - - aws-autoscale - parallel: - matrix: - - KASM_IMAGE: *SINGLE_ARCH_BUILDS - -manifest_dev: - stage: manifest - script: - - docker pull ${ORG_NAME}/${KASM_IMAGE}-private:x86_64-$SANITIZED_BRANCH - - docker pull ${ORG_NAME}/${KASM_IMAGE}-private:aarch64-$SANITIZED_BRANCH - - "docker manifest push --purge ${ORG_NAME}/${KASM_IMAGE}-private:$SANITIZED_BRANCH || :" - - docker manifest create ${ORG_NAME}/${KASM_IMAGE}-private:$SANITIZED_BRANCH ${ORG_NAME}/${KASM_IMAGE}-private:x86_64-$SANITIZED_BRANCH ${ORG_NAME}/${KASM_IMAGE}-private:aarch64-$SANITIZED_BRANCH - - docker manifest annotate ${ORG_NAME}/${KASM_IMAGE}-private:$SANITIZED_BRANCH ${ORG_NAME}/${KASM_IMAGE}-private:aarch64-$SANITIZED_BRANCH --os linux --arch arm64 --variant v8 - - docker manifest push --purge ${ORG_NAME}/${KASM_IMAGE}-private:$SANITIZED_BRANCH - except: - - develop - - /^release\/.*$/ - needs: [ build_multi_arch_dev ] - tags: - - aws-autoscale - parallel: - matrix: - - KASM_IMAGE: *MULTI_ARCH_BUILDS - -manifest_dev2: - stage: manifest - script: - - docker pull ${ORG_NAME}/${KASM_IMAGE}-private:x86_64-$SANITIZED_BRANCH - - docker pull ${ORG_NAME}/${KASM_IMAGE}-private:aarch64-$SANITIZED_BRANCH - - "docker manifest push --purge ${ORG_NAME}/${KASM_IMAGE}-private:$SANITIZED_BRANCH || :" - - docker manifest create ${ORG_NAME}/${KASM_IMAGE}-private:$SANITIZED_BRANCH ${ORG_NAME}/${KASM_IMAGE}-private:x86_64-$SANITIZED_BRANCH ${ORG_NAME}/${KASM_IMAGE}-private:aarch64-$SANITIZED_BRANCH - - docker manifest annotate ${ORG_NAME}/${KASM_IMAGE}-private:$SANITIZED_BRANCH ${ORG_NAME}/${KASM_IMAGE}-private:aarch64-$SANITIZED_BRANCH --os linux --arch arm64 --variant v8 - - docker manifest push --purge ${ORG_NAME}/${KASM_IMAGE}-private:$SANITIZED_BRANCH - except: - - develop - - /^release\/.*$/ - needs: [ build_multi_arch_dev2 ] - tags: - - aws-autoscale - parallel: - matrix: - - KASM_IMAGE: *MULTI_ARCH_BUILDS2 - -link_tests_single_arch_dev: - stage: linktests - script: - - apk add curl - - STATUS=$(curl -sL https://kasm-ci.s3.amazonaws.com/${CI_COMMIT_SHA}/x86_64/kasmweb/${KASM_IMAGE}-private/${SANITIZED_BRANCH}/ci-status.yml | awk -F'"' '{print $2}') - - if [ "${STATUS}" == "PASS" ]; then STATE=success; else STATE=failed; fi; - - curl --request POST --header "PRIVATE-TOKEN:${GITLAB_API_TOKEN}" "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/statuses/${CI_COMMIT_SHA}?state=${STATE}&name=${KASM_IMAGE}-private_x86_64&target_url=https://kasm-ci.s3.amazonaws.com/${CI_COMMIT_SHA}/x86_64/kasmweb/${KASM_IMAGE}-private/${SANITIZED_BRANCH}/index.html" - except: - - develop - - /^release\/.*$/ - needs: [ test_single_arch_dev ] - parallel: - matrix: - - KASM_IMAGE: *SINGLE_ARCH_BUILDS - -link_tests_multi_arch_dev: - stage: linktests - script: - - apk add curl - - STATUS=$(curl -sL https://kasm-ci.s3.amazonaws.com/${CI_COMMIT_SHA}/${ARCH}/kasmweb/${KASM_IMAGE}-private/${ARCH}-${SANITIZED_BRANCH}/ci-status.yml | awk -F'"' '{print $2}') - - if [ "${STATUS}" == "PASS" ]; then STATE=success; else STATE=failed; fi; - - curl --request POST --header "PRIVATE-TOKEN:${GITLAB_API_TOKEN}" "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/statuses/${CI_COMMIT_SHA}?state=${STATE}&name=${KASM_IMAGE}-private_${ARCH}&target_url=https://kasm-ci.s3.amazonaws.com/${CI_COMMIT_SHA}/${ARCH}/kasmweb/${KASM_IMAGE}-private/${ARCH}-${SANITIZED_BRANCH}/index.html" - except: - - develop - - /^release\/.*$/ - needs: [ test_multi_arch_dev ] - parallel: - matrix: - - ARCH: [ aarch64, x86_64 ] - KASM_IMAGE: *MULTI_ARCH_BUILDS - -link_tests_multi_arch_dev2: - stage: linktests - script: - - apk add curl - - STATUS=$(curl -sL https://kasm-ci.s3.amazonaws.com/${CI_COMMIT_SHA}/${ARCH}/kasmweb/${KASM_IMAGE}-private/${ARCH}-${SANITIZED_BRANCH}/ci-status.yml | awk -F'"' '{print $2}') - - if [ "${STATUS}" == "PASS" ]; then STATE=success; else STATE=failed; fi; - - curl --request POST --header "PRIVATE-TOKEN:${GITLAB_API_TOKEN}" "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/statuses/${CI_COMMIT_SHA}?state=${STATE}&name=${KASM_IMAGE}-private_${ARCH}&target_url=https://kasm-ci.s3.amazonaws.com/${CI_COMMIT_SHA}/${ARCH}/kasmweb/${KASM_IMAGE}-private/${ARCH}-${SANITIZED_BRANCH}/index.html" - except: - - develop - - /^release\/.*$/ - needs: [ test_multi_arch_dev2 ] - parallel: - matrix: - - ARCH: [ aarch64, x86_64 ] - KASM_IMAGE: *MULTI_ARCH_BUILDS2 - -######################################################################################################################################### -# These jobs are for the "rolling" release of the images. They should only run for scheduled jobs and should only push the rolling tags # -######################################################################################################################################### -build_schedules_browser_images: - image: ${ORG_NAME}/docker-buildx-private:develop - stage: build - script: - - BUILD_PLATFORM=$PLATFORM - - if [[ "${ARM_BUILDS}" == *",${KASM_IMAGE},"* ]]; then BUILD_PLATFORM="linux/amd64,linux/arm64"; fi; - - echo "Building ${KASM_IMAGE} based on ${CORE_IMAGE} for platforms ${BUILD_PLATFORM}" - # to get qemu ready - - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - # to prepare the buildx env - - docker buildx create --use - # Ensure readme and description files are present - - ls docs/$KASM_IMAGE/README.md - - ls docs/$KASM_IMAGE/description.txt - # Check for private variable to build against private core images - - if [[ $USE_PRIVATE_IMAGES -eq 1 ]]; then CORE_IMAGE=$CORE-IMAGE-private; fi; - # Equivalent to docker build and docker push. Builds amd64 natively uses qemu for arm64. - # The only way to push multiple architectures to the same tag is to use buildx. - - > - docker buildx build --push - --platform $BUILD_PLATFORM - -t ${ORG_NAME}/$KASM_IMAGE:$SANITIZED_ROLLING_BRANCH - -t ${ORG_NAME}/$KASM_IMAGE-private:$SANITIZED_ROLLING_BRANCH - --build-arg BASE_IMAGE=$CORE_IMAGE - --build-arg BASE_TAG="$SANITIZED_ROLLING_BRANCH" - -f dockerfile-kasm-$KASM_IMAGE . - only: - - schedules - tags: - - aws-autoscale - parallel: - matrix: - - KASM_IMAGE: *BROWSER_IMAGES - -build_schedules_app_images: - image: ${ORG_NAME}/docker-buildx-private:develop - stage: build - script: - - BUILD_PLATFORM=$PLATFORM - - if [[ "${ARM_BUILDS}" == *",${KASM_IMAGE},"* ]]; then BUILD_PLATFORM="linux/amd64,linux/arm64"; fi; - - echo "Building ${KASM_IMAGE} based on ${CORE_IMAGE} for platforms ${BUILD_PLATFORM}" - # to get qemu ready - - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - # to prepare the buildx env - - docker buildx create --use - # Ensure readme and description files are present - - ls docs/$KASM_IMAGE/README.md - - ls docs/$KASM_IMAGE/description.txt - # Check for private variable to build against private core images - - if [[ $USE_PRIVATE_IMAGES -eq 1 ]]; then CORE_IMAGE=$CORE_IMAGE-private; fi; - # Equivalent to docker build and docker push. Builds amd64 natively uses qemu for arm64. - # The only way to push multiple architectures to the same tag is to use buildx. - - > - docker buildx build --push - --platform $BUILD_PLATFORM - -t ${ORG_NAME}/$KASM_IMAGE:$SANITIZED_ROLLING_BRANCH - -t ${ORG_NAME}/$KASM_IMAGE-private:$SANITIZED_ROLLING_BRANCH - --build-arg BASE_IMAGE=$CORE_IMAGE - --build-arg BASE_TAG="$SANITIZED_ROLLING_BRANCH" - -f dockerfile-kasm-$KASM_IMAGE . - only: - - schedules - tags: - - aws-autoscale - parallel: - matrix: - - KASM_IMAGE: *APP_IMAGES - -build_schedules_ubuntu_desktop_images: - image: ${ORG_NAME}/docker-buildx-private:develop - stage: build - script: - - BUILD_PLATFORM=$PLATFORM - - if [[ "${ARM_BUILDS}" == *",${KASM_IMAGE},"* ]]; then BUILD_PLATFORM="linux/amd64,linux/arm64"; fi; - - echo "Building ${KASM_IMAGE} for platforms ${BUILD_PLATFORM}" - # to get qemu ready - - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - # to prepare the buildx env - - docker buildx create --use - # Ensure readme and description files are present - - ls docs/$KASM_IMAGE/README.md - - ls docs/$KASM_IMAGE/description.txt - # Set base image based on kasm_image variable - - if [[ $KASM_IMAGE =~ 'remnux-focal-desktop' ]]; then CORE_IMAGE=core-remnux-focal; fi - - if [[ $KASM_IMAGE =~ 'ubuntu-jammy-desktop' ]]; then CORE_IMAGE=core-ubuntu-jammy; fi - - if [[ $KASM_IMAGE =~ 'ubuntu-jammy-dind' ]]; then CORE_IMAGE=core-ubuntu-jammy; fi - - if [[ $KASM_IMAGE =~ 'ubuntu-jammy-dind-rootless' ]]; then CORE_IMAGE=core-ubuntu-jammy; fi - # Check for private variable to build against private core images - - if [[ $USE_PRIVATE_IMAGES -eq 1 ]]; then CORE_IMAGE=$CORE_IMAGE-private; fi; - # Equivalent to docker build and docker push. Builds amd64 natively uses qemu for arm64. - # The only way to push multiple architectures to the same tag is to use buildx. - - > - docker buildx build --push - --platform $BUILD_PLATFORM - -t ${ORG_NAME}/$KASM_IMAGE:$SANITIZED_ROLLING_BRANCH - -t ${ORG_NAME}/$KASM_IMAGE-private:$SANITIZED_ROLLING_BRANCH - --build-arg BASE_IMAGE=$CORE_IMAGE - --build-arg BASE_TAG="$SANITIZED_ROLLING_BRANCH" - -f dockerfile-kasm-$KASM_IMAGE . - only: - - schedules - tags: - - aws-autoscale - parallel: - matrix: - - KASM_IMAGE: *UBUNTU_IMAGES - -build_schedules_non_ubuntu: - image: ${ORG_NAME}/docker-buildx-private:develop - stage: build - script: - - BUILD_PLATFORM=$PLATFORM - - if [[ "${ARM_BUILDS}" == *",${KASM_IMAGE},"* ]]; then BUILD_PLATFORM="linux/amd64,linux/arm64"; fi; - - echo "Building ${KASM_IMAGE} for platforms ${BUILD_PLATFORM}" - # to get qemu ready - - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - # to prepare the buildx env - - docker buildx create --use - # Ensure readme and description files are present - - ls docs/$KASM_IMAGE/README.md - - ls docs/$KASM_IMAGE/description.txt - # Set base image based on kasm_image variable - - if [[ $KASM_IMAGE =~ 'almalinux-8-desktop' ]]; then CORE_IMAGE=core-almalinux-8; fi - - if [[ $KASM_IMAGE =~ 'almalinux-9-desktop' ]]; then CORE_IMAGE=core-almalinux-9; fi - - if [[ $KASM_IMAGE =~ 'alpine-317-desktop' ]]; then CORE_IMAGE=core-alpine-317; fi - - if [[ $KASM_IMAGE =~ 'centos-7-desktop' ]]; then CORE_IMAGE=core-centos-7; fi - - if [[ $KASM_IMAGE =~ 'debian-bullseye-desktop' ]]; then CORE_IMAGE=core-debian-bullseye; fi - - if [[ $KASM_IMAGE =~ 'fedora-37-desktop' ]]; then CORE_IMAGE=core-fedora-37; fi - - if [[ $KASM_IMAGE =~ 'kali-rolling-desktop' ]]; then CORE_IMAGE=core-kali-rolling; fi - - if [[ $KASM_IMAGE =~ 'opensuse-15-desktop' ]]; then CORE_IMAGE=core-opensuse-15; fi - - if [[ $KASM_IMAGE =~ 'oracle-7-desktop' ]]; then CORE_IMAGE=core-oracle-7; fi - - if [[ $KASM_IMAGE =~ 'oracle-8-desktop' ]]; then CORE_IMAGE=core-oracle-8; fi - - if [[ $KASM_IMAGE =~ 'oracle-9-desktop' ]]; then CORE_IMAGE=core-oracle-9; fi - - if [[ $KASM_IMAGE =~ 'parrotos-5-desktop' ]]; then CORE_IMAGE=core-parrotos-5; fi - - if [[ $KASM_IMAGE =~ 'rockylinux-8-desktop' ]]; then CORE_IMAGE=core-rockylinux-8; fi - - if [[ $KASM_IMAGE =~ 'rockylinux-9-desktop' ]]; then CORE_IMAGE=core-rockylinux-9; fi - - if [[ $KASM_IMAGE =~ 'tracelabs' ]]; then CORE_IMAGE=core-kali-rolling; fi - # Check for private variable to build against private core images - - if [[ $USE_PRIVATE_IMAGES -eq 1 ]]; then CORE_IMAGE=$CORE_IMAGE-private; fi; - # Equivalent to docker build and docker push. Builds amd64 natively uses qemu for arm64. - # The only way to push multiple architectures to the same tag is to use buildx. - - > - docker buildx build --push - --platform $BUILD_PLATFORM - -t ${ORG_NAME}/$KASM_IMAGE:$SANITIZED_ROLLING_BRANCH - -t ${ORG_NAME}/$KASM_IMAGE-private:$SANITIZED_ROLLING_BRANCH - --build-arg BASE_IMAGE=$CORE_IMAGE - --build-arg BASE_TAG="$SANITIZED_ROLLING_BRANCH" - -f dockerfile-kasm-$KASM_IMAGE . - only: - - schedules - tags: - - aws-autoscale - parallel: - matrix: - - KASM_IMAGE: *NON_UBUNTU_IMAGES - -build_fedora_37_schedules: - stage: build - script: - - > - docker build - -t ${ORG_NAME}/fedora-37-desktop:$(arch)-$SANITIZED_ROLLING_BRANCH - -f dockerfile-kasm-fedora-37-desktop . - - docker push ${ORG_NAME}/fedora-37-desktop:$(arch)-$SANITIZED_ROLLING_BRANCH - only: - - schedules - tags: - - ${TAG} - parallel: - matrix: - - TAG: [ aws-autoscale, aws-autoscale-arm64 ] - -manifest_fedora_37_schedules: - stage: manifest - script: - - docker pull ${ORG_NAME}/fedora-37-desktop:x86_64-$SANITIZED_ROLLING_BRANCH - - docker pull ${ORG_NAME}/fedora-37-desktop:aarch64-$SANITIZED_ROLLING_BRANCH - - "docker manifest push --purge ${ORG_NAME}/fedora-37-desktop:$SANITIZED_ROLLING_BRANCH || :" - - docker manifest create ${ORG_NAME}/fedora-37-desktop:$SANITIZED_ROLLING_BRANCH ${ORG_NAME}/fedora-37-desktop:x86_64-$SANITIZED_ROLLING_BRANCH ${ORG_NAME}/fedora-37-desktop:aarch64-$SANITIZED_ROLLING_BRANCH - - docker manifest annotate ${ORG_NAME}/fedora-37-desktop:$SANITIZED_ROLLING_BRANCH ${ORG_NAME}/fedora-37-desktop:aarch64-$SANITIZED_ROLLING_BRANCH --os linux --arch arm64 --variant v8 - - docker manifest push --purge ${ORG_NAME}/fedora-37-desktop:$SANITIZED_ROLLING_BRANCH - only: - - schedules - needs: [ build_fedora_37_schedules ] - tags: - - aws-autoscale - -build_schedules_games: - image: ${ORG_NAME}/docker-buildx-private:develop - stage: build - script: - - BUILD_PLATFORM=$PLATFORM - - if [[ "${ARM_BUILDS}" == *",${KASM_IMAGE},"* ]]; then BUILD_PLATFORM="linux/amd64,linux/arm64"; fi; - - echo "Building ${KASM_IMAGE} for platforms ${BUILD_PLATFORM}" - # to get qemu ready - - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - # to prepare the buildx env - - docker buildx create --use - # Ensure readme and description files are present - - ls docs/$KASM_IMAGE/README.md - - ls docs/$KASM_IMAGE/description.txt - # Check for private variable to build against private core images - - if [[ $USE_PRIVATE_IMAGES -eq 1 ]]; then CORE_IMAGE=$CORE_IMAGE-private; fi; - # Equivalent to docker build and docker push. Builds amd64 natively uses qemu for arm64. - # The only way to push multiple architectures to the same tag is to use buildx. - - > - docker buildx build --push - --platform $BUILD_PLATFORM - -t ${ORG_NAME}/$KASM_IMAGE:$SANITIZED_ROLLING_BRANCH - -t ${ORG_NAME}/$KASM_IMAGE-private:$SANITIZED_ROLLING_BRANCH - --build-arg BASE_IMAGE=$CORE_IMAGE - --build-arg BASE_TAG="$SANITIZED_ROLLING_BRANCH" - -f dockerfile-kasm-$KASM_IMAGE . - only: - - schedules - tags: - - aws-autoscale - parallel: - matrix: - - KASM_IMAGE: *GAME_IMAGES - -############################### -# Readme Updates in Dockerhub # -############################### -update_readmes: - stage: readme - script: - - > - docker run -v $PWD/docs:/docs - -e RELEASE="$KASM_RELEASE" - -e DOCKER_USERNAME="$README_USERNAME" - -e DOCKER_PASSWORD="$README_PASSWORD" - -e DOCKERHUB_REPOSITORY="${ORG_NAME}/${KASM_IMAGE}" - kasmweb/dockerhub-updater:develop + variables: + - $README_USERNAME_RUN + - $README_PASSWORD_RUN + variables: + README_USERNAME: $README_USERNAME_RUN + README_PASSWORD: $README_PASSWORD_RUN + trigger: + include: + - artifact: gitlab-ci.yml + job: template + +pipeline_readme_quay: + stage: run only: variables: - - $README_USERNAME - - $README_PASSWORD - tags: - - aws-autoscale - parallel: - matrix: - - KASM_IMAGE: - - almalinux-8-desktop - - almalinux-9-desktop - - alpine-317-desktop - - atom - - audacity - - blender - - brave - - centos-7-desktop - - chrome - - chromium - - debian-bullseye-desktop - - deluge - - desktop - - desktop-deluxe - - discord - - doom - - edge - - fedora-37-desktop - - filezilla - - firefox - - gimp - - hunchly - - inkscape - - insomnia - - java-dev - - kali-rolling-desktop - - libre-office - - maltego - - minetest - - only-office - - opensuse-15-desktop - - oracle-7-desktop - - oracle-8-desktop - - oracle-9-desktop - - parrotos-5-desktop - - pinta - - postman - - qbittorrent - - remmina - - retroarch - - rockylinux-8-desktop - - rockylinux-9-desktop - - signal - - steam - - sublime-text - - super-tux-kart - - teams - - telegram - - terminal - - thunderbird - -update_readmes2: - stage: readme - script: - - > - docker run -v $PWD/docs:/docs - -e RELEASE="$KASM_RELEASE" - -e DOCKER_USERNAME="$README_USERNAME" - -e DOCKER_PASSWORD="$README_PASSWORD" - -e DOCKERHUB_REPOSITORY="${ORG_NAME}/${KASM_IMAGE}" - kasmweb/dockerhub-updater:develop + - $QUAY_API_KEY_RUN + variables: + QUAY_API_KEY: $QUAY_API_KEY_RUN + trigger: + include: + - artifact: gitlab-ci.yml + job: template + +pipeline_revert: + stage: run only: variables: - - $README_USERNAME - - $README_PASSWORD - tags: - - aws-autoscale - parallel: - matrix: - - KASM_IMAGE: - - tor-browser - - tracelabs - - ubuntu-focal-desktop - - ubuntu-focal-dind - - ubuntu-focal-dind-rootless - - ubuntu-jammy-desktop - - ubuntu-jammy-dind - - ubuntu-jammy-dind-rootless - - unityhub - - vivaldi - - vlc - - vs-code - - zoom + - $DOCKERHUB_REVERT_RUN + - $REVERT_IS_ROLLING_RUN + variables: + DOCKERHUB_REVERT: $DOCKERHUB_REVERT_RUN + REVERT_IS_ROLLING: $REVERT_IS_ROLLING_RUN + trigger: + include: + - artifact: gitlab-ci.yml + job: template diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..7b7b8f875 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,93 @@ +# Contributing to Career-Box + +Thanks for your interest in contributing. Career-Box is an open-source project and we welcome contributions of all kinds — bug fixes, new workspace images, launcher improvements, documentation, and more. + +## Getting started + +### Prerequisites + +- [Docker Desktop](https://www.docker.com/products/docker-desktop/) or [Podman Desktop](https://podman-desktop.io/) +- [Bun](https://bun.sh/) (for the launcher frontend and MCP server) +- [Rust](https://rustup.rs/) (for the Tauri backend) +- [Git](https://git-scm.com/) + +### Setup + +```bash +# Clone the repo +git clone https://github.com/coeadapt/Career-Box.git +cd Career-Box + +# Install launcher dependencies +cd coeadapt-launcher +bun install +cd mcp-server && bun install && cd .. + +# Run the launcher in dev mode +bun run tauri dev +``` + +### Standalone mode (no CoeAdapt account needed) + +The default `.env` ships with a placeholder Clerk key, which automatically activates **standalone mode**. You can develop and test all workspace, container, and AI features without a CoeAdapt account. + +To develop CoeAdapt-specific features (Cora chat, career tracking, account management), you'll need a valid Clerk key. Contact the maintainers or sign up at [coeadapt.com](https://coeadapt.com). See the [Environment configuration](README.md#environment-configuration) section in the README for details. + +## What you can work on + +### Workspace images + +The `dockerfile-kasm-*` files and `src/*/install/` scripts define the containerized applications. To add or modify an image: + +1. Create or edit a `dockerfile-kasm-` in the repo root +2. Add install scripts in `src/ubuntu/install//` +3. Add documentation in `docs//README.md` +4. Test the build: `docker build -t kasmweb/:dev -f dockerfile-kasm- .` + +Follow the patterns in existing images. See Kasm's [image building guide](https://kasmweb.com/docs/latest/how_to/building_images.html) for details on how Kasm images work. + +### Coeadapt Launcher + +The launcher lives in `coeadapt-launcher/` and is built with Tauri v2 + React + TypeScript. See [coeadapt-launcher/README.md](coeadapt-launcher/README.md) for the full architecture and project structure. + +- **Frontend** (`src/`): React components, pages, hooks, and utilities +- **Backend** (`src-tauri/`): Rust commands for Docker management, disk monitoring, health checks +- **MCP Server** (`mcp-server/`): Node.js server exposing workspace tools via the Model Context Protocol + +### Documentation + +Improvements to READMEs, guides, and inline comments are always welcome. + +## Submitting changes + +1. Fork the repository +2. Create a feature branch from `develop`: `git checkout -b feature/my-change` +3. Make your changes +4. Test locally (build the launcher, build any modified Docker images) +5. Commit with a clear message describing what and why +6. Open a pull request against `develop` + +### Commit messages + +Use clear, descriptive commit messages. Prefix with the area of change: + +- `feat:` — new features +- `fix:` — bug fixes +- `docs:` — documentation changes +- `security:` — security patches +- `refactor:` — code restructuring without behavior change + +### Pull request guidelines + +- Keep PRs focused — one logical change per PR +- Include a description of what changed and why +- If adding a new workspace image, include a screenshot or description of what it provides +- Reference any related issues + +## Security + +If you discover a security vulnerability, please **do not** open a public issue. See [SECURITY.md](SECURITY.md) for responsible disclosure guidelines. + +## License + +By contributing, you agree that your contributions will be licensed under the [MIT License](LICENSE.md). diff --git a/Dockerfile.fastfix b/Dockerfile.fastfix new file mode 100644 index 000000000..93c600cb1 --- /dev/null +++ b/Dockerfile.fastfix @@ -0,0 +1,5 @@ +FROM career-box-60fps +USER root +COPY ./src/ubuntu/install/careerclaw/fix_startup.sh /tmp/fix_startup.sh +RUN sed -i 's/\r$//' /tmp/fix_startup.sh && bash /tmp/fix_startup.sh && rm /tmp/fix_startup.sh +USER 1000 diff --git a/LICENSE.md b/LICENSE.md index a26705a44..fed5ff258 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,11 +1,21 @@ -# Disclaimer +# License -This license applies only to the source code that is directly maintained in this git repository, it does not extend to dependencies from outside of this repository, to include other projects owned and/or maintained by Kasm Technologies. +This project contains work from multiple authors, all licensed under the MIT License. + +## Career-Box / Coeadapt -## License +Copyright 2025-2026 Coeadapt + +## Kasm Workspaces Images Copyright 2022 Kasm Technologies Inc +This license applies only to the source code that is directly maintained in this git repository, it does not extend to dependencies from outside of this repository, to include other projects owned and/or maintained by Kasm Technologies. + +--- + +## MIT License + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. diff --git a/README.md b/README.md index ae2ac9bee..16a80e6ee 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,370 @@ -![Logo][logo] -# Workspaces Images -This repository contains several example of desktop and application Workspaces images. -Administrators may leverage these images directly or use them as a starting point for their own custom images. -Each of these images is based off one of the [**Workspaces Core Images**](https://github.com/kasmtech/workspaces-core-images?utm_campaign=Github&utm_source=github) which contain the necessary wiring to work within the Kasm Workspaces platform. +# Career-Box +**A containerized career workspace powered by AI.** -For more information about building custom images please review the [**How To Guide**](https://kasmweb.com/docs/latest/how_to/building_images.html?utm_campaign=Github&utm_source=github) +Career-Box gives you a full Linux desktop pre-loaded with career development tools — browsers, IDEs, office suites, security tools, and more — all running in a Docker container on your machine. A desktop launcher manages everything so you never touch a terminal. CareerClaw, an AI agent running inside the workspace, connects to your career coach so it can see your screen, run commands, open applications, and help you do real work. -The Kasm team publishes applications and desktop images for use inside the platform. More information, including source can be found in the [**Default Images List**](https://kasmweb.com/docs/latest/guide/custom_images.html?utm_campaign=Github&utm_source=github) +No Docker knowledge. No Linux experience. No account required. Just launch and go. +

+ Setup wizard + Dashboard +

-# Manual Deployment +--- -To build the provided images: +## What is Career-Box? - sudo docker build -t kasmweb/firefox:dev -f dockerfile-kasm-firefox . +Career-Box is an open-source, containerized career development workspace. It gives you a full Linux desktop pre-loaded with 80+ career tools, managed by a desktop launcher, with an AI agent gateway that lets assistants interact with your workspace directly. +It brings together two powerful open-source projects: -While these image are primarily built to run inside the Workspaces platform, they can also be executed manually. Please note that certain functionality, such as audio, uploads, downloads, and microphone pass-through are only available within the Kasm platform. +- **[Kasm Workspaces](https://github.com/kasmtech/workspaces-images)** — a container streaming platform that delivers full Linux desktops and applications through your browser. Kasm provides the isolated, reproducible environment where career work happens. +- **[OpenClaw](https://github.com/openclaw/openclaw)** — an AI agent framework with built-in tools for shell execution, web search, browser automation, file management, and persistent memory. OpenClaw provides the intelligence layer that turns the workspace into an AI-powered career assistant. +**Why combine them?** Career development requires both *doing* and *thinking*. Kasm gives you a safe, disposable desktop where you can practice coding, build portfolios, and run tools without messing up your main machine. OpenClaw gives an AI assistant the ability to see your workspace, run commands, open applications, and interact with the tools inside it. Together, they create a career workspace where AI doesn't just advise — it *works alongside you*. + +### Optional: CoeAdapt platform integration + +> Career-Box works great on its own with any MCP-compatible AI assistant. For users who want more, it also integrates with the [CoeAdapt platform](https://coeadapt.com) for: +> +> - **Cora** — an AI career companion with career coaching, assessments, and personalized guidance +> - **Career tracking** — plans, tasks, goals, habits, job applications, portfolio, and skill verification +> - **Cloud sync** — your career data accessible from anywhere +> +> See [Connecting to CoeAdapt](#connecting-to-coeadapt) below. + +### How it works + +**Standalone mode** (default — no account needed): + +``` +┌────────────────────────────────────────────────────┐ +│ Your Machine │ +│ │ +│ ┌──────────────────────────────────────────────┐ │ +│ │ Career-Box Launcher (system tray) │ │ +│ │ Manages container lifecycle │ │ +│ └──────┬───────────────────────────────────────┘ │ +│ │ │ +│ ┌────▼──────────────────────────┐ │ +│ │ Docker Container │ │ +│ │ :6901 — Kasm Desktop │ │ +│ │ :18789 — CareerClaw Gateway │ │ +│ │ (OpenClaw MCP bridge) │ │ +│ └────────────────▲───────────────┘ │ +│ │ │ +│ ┌──────────┴──────────┐ │ +│ │ Claude Desktop / │ │ +│ │ Any MCP-compatible │ │ +│ │ AI assistant │ │ +│ └─────────────────────┘ │ +└────────────────────────────────────────────────────┘ +``` + +**With CoeAdapt** (optional — adds Cora, career tracking, cloud sync): + +``` +┌────────────────────────────────────────────────────────────┐ +│ Cora (coeadapt.com/cora) │ +│ AI career companion — powered by OpenClaw agent framework │ +└────────────────────┬───────────────────────────────────────┘ + │ Cloud API + │ + ┌────────────▼───────────────────────────────┐ + │ Your Machine │ + │ │ + │ ┌──────────────────────────────────────┐ │ + │ │ Coeadapt Launcher (system tray) │ │ + │ │ Manages container lifecycle │ │ + │ └──────┬──────────────────────────────┘ │ + │ │ │ + │ ┌────▼──────────────────────────┐ │ + │ │ Docker Container │ │ + │ │ :6901 — Kasm Desktop │ │ + │ │ :18789 — CareerClaw Gateway │ │ + │ │ (OpenClaw MCP bridge) │ │ + │ └────────────────────────────────┘ │ + └────────────────────────────────────────────┘ +``` + +The **Coeadapt Launcher** is a cross-platform desktop app (built with Tauri v2) that: +- Detects Docker/Podman and guides you through setup +- Pulls and manages the Kasm workspace container +- Lives in your system tray with start/stop/open controls + +Once the workspace is running, **CareerClaw** (the OpenClaw-based AI agent inside the container) provides the MCP gateway on port 18789 — giving AI assistants full access to the workspace tools. + +--- + +## What's inside the box + +Career-Box includes **80+ containerized application images** inherited from [Kasm Workspaces](https://kasmweb.com), each built to stream a desktop or application through your browser: + +| Category | Applications | +|----------|-------------| +| **Browsers** | Firefox, Chrome, Chromium, Brave, Edge, Vivaldi, Tor Browser | +| **Development** | VS Code, Atom, Sublime Text, Java Dev, Unity Hub | +| **Office & Productivity** | LibreOffice, OnlyOffice, Obsidian, Thunderbird | +| **Communication** | Slack, Discord, Teams, Telegram, Signal, Zoom | +| **Creative** | GIMP, Inkscape, Pinta, Blender, Audacity | +| **Security & OSINT** | Kali Linux, ParrotOS, SpiderFoot, Nessus, Hunchly, Maltego, Forensic-OSINT | +| **Desktops** | Ubuntu (Jammy/Noble), Debian, Fedora, AlmaLinux, Rocky, Alpine, openSUSE, Oracle | +| **Utilities** | Remmina, FileZilla, VLC, Deluge, qBittorrent | + +Each image is a self-contained Docker container with KasmVNC for browser-based access. The career workspace image bundles the most useful tools into a single desktop environment. + +--- + +## Career capabilities + +When connected to AI (Claude, Cora, or any MCP-compatible assistant), Career-Box becomes an intelligent workspace for: + +- **Resume building** — AI reads your drafts, tailors them to job postings, and writes cover letters +- **Interview prep** — Practice answers out loud, get feedback, run mock interviews +- **Job tracking** — Maintain application pipelines with company research auto-populated +- **Skill development** — Follow structured learning paths with hands-on practice in the workspace +- **Career journaling** — Structured reflection with AI-powered theme analysis +- **Portfolio building** — Build projects, generate documentation, publish to GitHub +- **Networking** — Draft outreach emails, track contacts, manage follow-ups +- **Market research** — Analyze job markets, salary benchmarks, and emerging opportunities + +All powered by CareerClaw's OpenClaw gateway — providing shell execution, browser automation, file management, web search, and persistent memory inside the workspace. + +--- + +## Quick start + +### Prerequisites + +- [Docker Desktop](https://www.docker.com/products/docker-desktop/) or [Podman Desktop](https://podman-desktop.io/) +- 15 GB free disk space (25 GB recommended) + +### Install + +Download the latest release for your platform from the [Releases](https://github.com/coeadapt/Career-Box/releases) page: + +| Platform | Download | +|----------|----------| +| Windows | `.msi` installer | +| macOS | `.dmg` | +| Linux | `.AppImage` or `.deb` | + +### Run (standalone) + +1. Launch **Career-Box** from your applications +2. The setup wizard walks you through everything (Docker check, image download, workspace start) +3. Click **Open Workspace** to access your career desktop in the browser +4. CareerClaw AI gateway starts automatically inside the workspace + +No account required. No sign-up. Just launch and go. + +### Run (with CoeAdapt) + +1. Create an account at [coeadapt.com](https://coeadapt.com) +2. Configure your Clerk key (see [Connecting to CoeAdapt](#connecting-to-coeadapt)) +3. Launch the app and sign in +4. Get access to Cora, career tracking, and cloud sync + +--- + +## Connecting to CoeAdapt + +> **This section is optional.** Career-Box works fully without CoeAdapt. + +To connect Career-Box to the CoeAdapt platform for Cora, career tracking, and cloud sync: + +1. Create an account at [coeadapt.com](https://coeadapt.com) +2. Copy your Clerk publishable key from your CoeAdapt dashboard +3. Edit `coeadapt-launcher/.env`: + ```env + VITE_CLERK_PUBLISHABLE_KEY=pk_live_YOUR_KEY_HERE + VITE_COEADAPT_API_URL=https://api.coeadapt.com + ``` +4. Restart the launcher + +This enables: +- Sign-in with your CoeAdapt account +- Cora AI career companion in the Dashboard +- Career tracking and progress sync +- Device token management for API access + +When no valid Clerk key is configured (the default), standalone mode activates automatically. See [Environment configuration](#environment-configuration) for details. + +--- + +## Project structure + +``` +Career-Box/ +├── coeadapt-launcher/ # Tauri v2 desktop launcher app +│ ├── src/ # React frontend (TypeScript + Tailwind) +│ └── src-tauri/ # Rust backend (container mgmt, health checks) +├── src/ +│ ├── ubuntu/install/ # Ubuntu application install scripts +│ ├── alpine/install/ # Alpine install scripts +│ ├── opensuse/install/ # openSUSE install scripts +│ └── common/ # Shared resources +├── docs/ # Per-application documentation (80+ READMEs) +├── ci-scripts/ # CI/CD pipeline scripts +├── dockerfile-kasm-* # Dockerfiles for each application image +├── CONTRIBUTING.md # Contributor guide +├── SECURITY.md # Security hardening documentation +└── LICENSE.md # MIT License +``` + +For detailed launcher documentation, see [coeadapt-launcher/README.md](coeadapt-launcher/README.md). + +--- + +## For developers + +Welcome. This project has two distinct halves, and you can contribute to either without understanding the other. + +### The launcher (`coeadapt-launcher/`) + +A Tauri v2 desktop app — React frontend, Rust backend. This is where most active development happens. If you've worked with React, TypeScript, or Rust, you'll feel at home here. + +**Setup:** + +```bash +cd coeadapt-launcher + +# Install dependencies +bun install + +# Run in dev mode (Vite HMR + Tauri window) +bun run tauri dev ``` -sudo docker run --rm -it --shm-size=512m -p 6901:6901 -e VNC_PW=password kasmweb/firefox:dev + +**Requirements:** [Bun](https://bun.sh/), [Rust](https://rustup.rs/), [Docker Desktop](https://www.docker.com/products/docker-desktop/) + +**Build for production:** + +```bash +# Build the Tauri app (produces platform installers in src-tauri/target/release/bundle/) +bun run tauri build ``` -The container is now accessible via a browser : `https://:6901` +For the full launcher architecture, see [coeadapt-launcher/README.md](coeadapt-launcher/README.md). + +### The workspace images (`src/`, `dockerfile-kasm-*`) + +80+ Dockerfiles and install scripts inherited from [Kasm Workspaces](https://github.com/kasmtech/workspaces-images). Each image defines a containerized application or desktop environment. + +**To build an image:** + +```bash +sudo docker build -t kasmweb/firefox:dev -f dockerfile-kasm-firefox . +``` + +**To run it standalone (browser access at `https://localhost:6901`):** + +```bash +sudo docker run --rm -it --shm-size=512m -p 6901:6901 -e VNC_PW=password kasmweb/firefox:dev +``` + +Each image has an install script in `src/ubuntu/install//` and documentation in `docs//README.md`. Follow the existing patterns when adding or modifying images. For the full image building guide, see Kasm's [How To Guide](https://kasmweb.com/docs/latest/how_to/building_images.html). + +### Environment configuration + +Career-Box has two modes, auto-detected from `.env`: + +| Mode | When | What you get | +|------|------|-------------| +| **Standalone** (default) | No Clerk key, or `pk_test_REPLACE_ME` | Workspace + CareerClaw AI gateway + any MCP-compatible AI | +| **CoeAdapt** | Valid Clerk publishable key | + Cora chat, career tracking, account management, cloud sync | + +The default `.env` ships with `pk_test_REPLACE_ME`, which activates standalone mode. You can develop and test all workspace, container, and AI features without a CoeAdapt account. + +To develop CoeAdapt-specific features (Cora chat, career tracking, account management), you'll need a valid Clerk key. Contact the maintainers or sign up at [coeadapt.com](https://coeadapt.com). + +The mode detection lives in `coeadapt-launcher/src/lib/mode.ts`. + +### Where to start + +| Interest | Start here | +|----------|-----------| +| Frontend / UI | `coeadapt-launcher/src/pages/` and `src/components/` — React + Tailwind | +| Backend / Systems | `coeadapt-launcher/src-tauri/src/` — Rust, Docker management, health checks | +| AI / CareerClaw | `src/ubuntu/install/careerclaw/` — OpenClaw integration and gateway config | +| Container images | `src/ubuntu/install/` — add new apps or fix existing install scripts | +| Security | [SECURITY.md](SECURITY.md) — review the audit, fix remaining issues | +| Documentation | `docs/` — 80+ app READMEs, or improve this README | + +--- + +## Built on open source + +Career-Box stands on the shoulders of two major open-source projects: + +### Kasm Workspaces + +This repository is a fork of [kasmtech/workspaces-images](https://github.com/kasmtech/workspaces-images) by [Kasm Technologies](https://kasmweb.com). Kasm Workspaces is a container streaming platform that delivers browser-based access to desktops and applications using [KasmVNC](https://github.com/kasmtech/KasmVNC). The 80+ Dockerfiles and install scripts in this repo come from Kasm's upstream project. + +- **Upstream repo:** [github.com/kasmtech/workspaces-images](https://github.com/kasmtech/workspaces-images) +- **Core images:** [github.com/kasmtech/workspaces-core-images](https://github.com/kasmtech/workspaces-core-images) +- **KasmVNC:** [github.com/kasmtech/KasmVNC](https://github.com/kasmtech/KasmVNC) +- **Docs:** [kasmweb.com/docs](https://kasmweb.com/docs/latest/how_to/building_images.html) + +### OpenClaw + +[OpenClaw](https://github.com/openclaw/openclaw) is an open-source AI agent framework that gives assistants real tools — shell execution, web search, browser automation, file system access, persistent memory, and proactive scheduling. Career-Box uses OpenClaw's architecture to power CareerClaw, the career-specific agent layer that turns the Kasm workspace into an intelligent career development environment. + +- **Upstream repo:** [github.com/openclaw/openclaw](https://github.com/openclaw/openclaw) +- **CareerClaw fork:** [github.com/alexander-acker/careerclaw](https://github.com/alexander-acker/careerclaw) +- **Skills hub:** [github.com/openclaw/clawhub](https://github.com/openclaw/clawhub) + +### What Career-Box adds + +- The **Career-Box Launcher** — a Tauri v2 desktop app for managing workspace containers without touching Docker +- **CareerClaw** — career-specific OpenClaw agent with gateway, skills for coaching, assessments, resume building, interview prep, and job tracking +- **Security hardening** — patches to upstream Kasm scripts (see [SECURITY.md](SECURITY.md) for the full audit) +- **Optional CoeAdapt integration** — connect to [coeadapt.com](https://coeadapt.com) for Cora AI coaching, career tracking, and cloud sync + +The Kasm workspace images and install scripts in this repository are used as-is or with security patches documented in SECURITY.md. + +--- + +## Tech stack + +| Component | Technology | +|-----------|-----------| +| Desktop launcher | Tauri v2, React 19, TypeScript, Tailwind CSS v4 | +| Launcher backend | Rust (tokio, reqwest, sysinfo) | +| AI agent | CareerClaw (OpenClaw fork), Node.js | +| Container runtime | Docker / Podman | +| Desktop streaming | KasmVNC (via Kasm Workspaces) | +| Package manager | Bun | + +--- + +## Why we're building this + +The world is adapting. AI is reshaping industries, automating tasks, and redefining what it means to have a career. Roles that existed for decades are changing overnight. New ones are appearing faster than anyone can track. + +This is not something to fear. But it is something we have a responsibility to face honestly. + +Not everyone has a software engineering background. Not everyone has a mentor, a network, or the time to figure out what's next on their own. But everyone deserves to be equipped for what's coming. No one should be left behind because they didn't have the right tools or the right guidance at the right moment. + +Tasks will be automated. Roles will change. But the deeply personal endeavour of contributing to something meaningful — of finding work that matters to you and building a life around it — that will never change. That's the part worth protecting. + +Career-Box exists because we believe AI should be the great equalizer, not the great divider. We're building a tool that puts an AI career coach and a fully equipped workspace in the hands of anyone who needs it — not just the people who already know how to code or already have access to the best opportunities. + +If you're here, you're part of that mission. Welcome to the community. What we're building together has the potential to genuinely change lives — to help people navigate the most uncertain career landscape in a generation, and to come out the other side doing work they care about. - - **User** : `kasm_user` - - **Password**: `password` +We're called to adapt. Let's make sure no one has to do it alone. +--- -# About Workspaces -Kasm Workspaces is a docker container streaming platform that enables you to deliver browser-based access to desktops, applications, and web services. Kasm uses a modern DevOps approach for programmatic delivery of services via Containerized Desktop Infrastructure (CDI) technology to create on-demand, disposable, docker containers that are accessible via web browser. The rendering of the graphical-based containers is powered by the open-source project [**KasmVNC**](https://github.com/kasmtech/KasmVNC?utm_campaign=Github&utm_source=github) +## Contributing -![Screenshot][Kasm_Workflow] +See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, guidelines, and how to submit changes. -Kasm Workspaces was developed to meet the most demanding secure collaboration requirements that is highly scalable, customizable, and easy to maintain. Most importantly, Kasm provides a solution, rather than a service, so it is infinitely customizable to your unique requirements and includes a developer API so that it can be integrated with, rather than replace, your existing applications and workflows. Kasm can be deployed in the cloud (Public or Private), on-premise (Including Air-Gapped Networks), or in a hybrid configuration. +## Security -# Live Demo -A self-guided on-demand demo is available at [**kasmweb.com**](https://www.kasmweb.com/demo.html?utm_campaign=Github&utm_source=github) +See [SECURITY.md](SECURITY.md) for the security audit, hardening documentation, and how to report vulnerabilities. +## License -[logo]: https://cdn2.hubspot.net/hubfs/5856039/dockerhub/kasm_logo.png "Kasm Logo" -[Kasm_Workflow]: https://cdn2.hubspot.net/hubfs/5856039/dockerhub/kasm_workflow_960.gif "Kasm Workflow" +[MIT License](LICENSE.md). Workspace image scripts originally by [Kasm Technologies Inc](https://kasmweb.com), with additional work by [Coeadapt](https://coeadapt.com). diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..ac6d64551 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,243 @@ +# Security Hardening + +Security audit and hardening patches applied to the Career-Box workspace environment. This document covers vulnerabilities found in upstream Kasm workspace scripts and in Career-Box's own components (Coeadapt Launcher, OpenClaw gateway integration), along with the fixes applied. + +All fixes preserve full functionality while closing security gaps. If you discover a vulnerability not listed here, please open a private security advisory on this repository rather than a public issue. + +--- + +## Critical Fixes + +### 1. Gateway auth bypass removed +**File:** `src/ubuntu/install/careerclaw/install_careerclaw.sh`, `src/ubuntu/install/careerclaw/custom_startup.sh` + +The `--allow-unconfigured` flag was passed to the OpenClaw gateway, allowing it to accept connections without requiring proper configuration or authentication setup. Removed from both the launcher script and the respawn loop. + +```diff +- ARGS=${APP_ARGS:-"gateway --allow-unconfigured"} ++ ARGS=${APP_ARGS:-"gateway"} +``` + +### 2. Passwordless sudo eliminated +**File:** `src/ubuntu/install/dind/install_dind.sh` + +`kasm-user` had unrestricted `NOPASSWD: ALL` sudo access — equivalent to full root. Replaced with a random password and scoped sudo to only the binaries that actually need it. + +```diff +- echo 'kasm-user:kasm-user' | chpasswd +- echo 'kasm-user ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers ++ KASM_PASS=$(openssl rand -base64 16) ++ echo "kasm-user:${KASM_PASS}" | chpasswd ++ echo 'kasm-user ALL=(ALL) NOPASSWD: /usr/bin/dockerd, /usr/local/bin/dind, /usr/local/bin/dockerd-entrypoint.sh, /usr/sbin/iptables, /usr/sbin/ip6tables' >> /etc/sudoers +``` + +### 3. VPN credentials secured +**File:** `src/ubuntu/install/vpn/start_vpn.sh` + +OpenVPN credentials were written to world-readable files. Now created with `chmod 600` before any secrets are written. + +```diff ++ install -m 600 -o kasm-user -g kasm-user /dev/null /home/kasm-user/vpn_credentials + echo ${USER} > /home/kasm-user/vpn_credentials + echo ${PASS} >> /home/kasm-user/vpn_credentials +``` + +--- + +## High-Priority Fixes + +### 4. TLS certificate validation enforced +**File:** `src/ubuntu/install/remmina/install_remmina.sh` + +Both VNC and RDP default connection profiles had `ignore-tls-errors=1`, disabling certificate validation and allowing man-in-the-middle attacks on remote desktop connections. + +```diff +- ignore-tls-errors=1 ++ ignore-tls-errors=0 +``` + +Applied to both `default.vnc.remmina` and `default.rdp.remmina` profiles. + +### 5. SMB locked to localhost +**File:** `src/ubuntu/install/smb/install_smb.sh` + +Samba was bound to all network interfaces with guest access enabled via `map to guest = bad user`. Three changes: + +```diff +- ; interfaces = 127.0.0.0/8 eth0 +- ; bind interfaces only = yes ++ interfaces = 127.0.0.0/8 ++ bind interfaces only = yes +``` +```diff +- map to guest = bad user ++ map to guest = never +``` +```diff +- usershare allow guests = yes ++ usershare allow guests = no +``` + +### 6. ADB port restricted to localhost +**File:** `src/ubuntu/install/redroid/custom_startup.sh` + +Android Debug Bridge was exposed on `0.0.0.0:5555`, giving any machine on the network a full shell into the Android emulator. + +```diff +- -p 5555:5555 \ ++ -p 127.0.0.1:5555:5555 \ +``` + +### 7. Tauri updater signature enforcement +**File:** `coeadapt-launcher/src-tauri/tauri.conf.json` + +The updater `pubkey` was empty, meaning update packages were not signature-verified. A placeholder now forces a real key to be set before release. + +```diff +- "pubkey": "" ++ "pubkey": "REPLACE_WITH_REAL_PUBKEY_BEFORE_RELEASE" +``` + +> **Action required:** Generate a real keypair with `tauri signer generate` and replace the placeholder before shipping. + +--- + +## Medium-Priority Fixes + +### 8. Pipe-to-bash patterns removed +**Files:** `src/ubuntu/install/careerclaw/install_careerclaw.sh`, `src/ubuntu/install/dind/install_dind.sh` + +`curl | bash` executes remote code without inspection. Changed to download-then-execute so the script can be audited or cached. + +```diff +- curl -fsSL https://deb.nodesource.com/setup_22.x | bash - ++ NODESOURCE_SCRIPT=$(mktemp) ++ curl -fsSL https://deb.nodesource.com/setup_22.x -o "$NODESOURCE_SCRIPT" ++ bash "$NODESOURCE_SCRIPT" ++ rm -f "$NODESOURCE_SCRIPT" +``` + +```diff +- wget -q -O - https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash ++ K3D_SCRIPT=$(mktemp) ++ wget -q -O "$K3D_SCRIPT" https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh ++ bash "$K3D_SCRIPT" ++ rm -f "$K3D_SCRIPT" +``` + +### 9. Gateway localhost binding enforced at runtime +**File:** `src/ubuntu/install/careerclaw/install_careerclaw.sh` + +The launcher now reads `openclaw.json` and refuses to start if `gateway.bind` is anything other than `127.0.0.1`. Prevents config tampering from exposing the gateway to the network. + +### 10. OpenClaw config directory hardened +**File:** `src/ubuntu/install/careerclaw/install_careerclaw.sh` + +- `~/.openclaw/` set to `chmod 700` (owner-only access) +- `~/.openclaw/openclaw.json` set to `chmod 600` (owner-only read/write) +- CORS restricted to `localhost:18789` and `127.0.0.1:18789` origins only + +### 11. Dev dependencies stripped from production image +**File:** `src/ubuntu/install/careerclaw/install_careerclaw.sh` + +Added `pnpm prune --prod` and `rm -rf .git` after build to reduce attack surface and image size. + +--- + +## Round 2 — Additional Findings + +### 12. Hunchly license key removed from repository +**File:** `src/ubuntu/install/hunchly/license.key` + +A license key file was committed to git history. Removed from tracking and added to `.gitignore`. The key still exists in git history — run `git filter-branch` or BFG Repo-Cleaner to purge it before making the repo public. + +### 13. CI script SSH key set to chmod 777 +**File:** `ci-scripts/test.sh` + +The SSH private key used for CI test instances was set world-readable/writable. Fixed to `chmod 600`. + +```diff +- chmod 777 $(dirname ${CI_PROJECT_DIR})/sshkey ++ chmod 600 $(dirname ${CI_PROJECT_DIR})/sshkey +``` + +### 14. CI script disabled SSH host key verification +**File:** `ci-scripts/test.sh` + +Six SSH calls used `StrictHostKeyChecking=no`, allowing MITM attacks on CI test infrastructure. Changed to `accept-new` (trusts first connect, rejects changed keys). + +```diff +- -oStrictHostKeyChecking=no ++ -oStrictHostKeyChecking=accept-new +``` + +### 15. Chrome repo on OpenSUSE used HTTP +**File:** `src/ubuntu/install/chrome/install_chrome.sh` + +The zypper repository for Chrome used unencrypted HTTP, enabling package tampering via MITM. + +```diff +- zypper ar http://dl.google.com/linux/chrome/rpm/stable/x86_64 Google-Chrome ++ zypper ar https://dl.google.com/linux/chrome/rpm/stable/x86_64 Google-Chrome +``` + +### 16. Remmina RDP gateway transport defaulted to HTTP +**File:** `src/ubuntu/install/remmina/install_remmina.sh` + +The default RDP profile forced gateway transport over unencrypted HTTP. Changed to `auto` which prefers HTTPS when available. + +```diff +- gwtransp=http ++ gwtransp=auto +``` + +### 17. CareerClaw bind check hardened against config tampering +**File:** `src/ubuntu/install/careerclaw/install_careerclaw.sh` + +The previous check refused to start if bind wasn't `127.0.0.1`, but a TOCTOU race could allow bypass. Now the launcher auto-repairs the config back to `127.0.0.1` before starting, closing the race window. + +### 18. OwnCloud HTTP package repository +**File:** `src/ubuntu/install/owncloud/install_owncloud.sh` + +Package repository used unencrypted HTTP, allowing MITM package injection. + +```diff +- deb http://download.opensuse.org/repositories/isv:/ownCloud:/desktop/Ubuntu_16.04/ / ++ deb https://download.opensuse.org/repositories/isv:/ownCloud:/desktop/Ubuntu_16.04/ / +``` + +--- + +## Known Remaining Issues (upstream / not patchable here) + +| Issue | Location | Notes | +|-------|----------|-------| +| Unverified binary downloads (no checksums) | blender, eclipse, gimp, horizon, postman, hunchly install scripts | Upstream Kasm scripts — add SHA256 checks when pinning versions | +| Pipe-to-gpg key imports | signal, terraform, vivaldi, sublime, atom, unityhub, dind install scripts | Standard distro packaging pattern — lower risk since GPG verifies the key itself; modern `signed-by` keyring already used where supported | +| AWS credentials passed as CLI args in CI | `ci-scripts/test.sh` | Visible in `ps aux` — migrate to IAM roles or CI secret masking | +| OwnCloud config points to `http://192.168.117.130:9999` | `src/ubuntu/install/owncloud/install_owncloud.cfg` | Test/template config — users should override with HTTPS endpoint | +| Deprecated `apt-key add` in 7 scripts | atom, dind, dind_rootless, sublime_text, unityhub, signal, terraform | Upstream Kasm convention; fallback for older Ubuntu; newer distros use keyring path | + +--- + +## Accepted Risks + +| Item | Reason | +|------|--------| +| `--no-sandbox` on Chrome/Chromium | Required inside Kasm containers — the container itself is the sandbox boundary | +| `--privileged` on Redroid container | Required for `binder_linux` kernel access; Android emulation won't function without it | +| KasmVNC on `0.0.0.0:6901` | Intentional — this is the user entry point to the remote desktop, protected by VNC password | +| Docker group membership for kasm-user | Required for DinD functionality; mitigated by scoped sudo and container isolation | + +--- + +## Port Map + +| Port | Service | Binding | Auth | +|------|---------|---------|------| +| 18789 | OpenClaw Gateway | `127.0.0.1` | Configured (no longer unconfigured) | +| 6901 | KasmVNC | `0.0.0.0` | VNC password | +| 5555 | ADB (Redroid) | `127.0.0.1` | None (localhost only) | +| 5000 | Cyberbro | `127.0.0.1` | None (localhost only) | +| 5002 | SpiderFoot | `127.0.0.1` | None (localhost only) | +| 8834 | Nessus | `localhost` | HTTPS + Nessus auth | diff --git a/ci-scripts/app-layer.sh b/ci-scripts/app-layer.sh new file mode 100644 index 000000000..c4f58f949 --- /dev/null +++ b/ci-scripts/app-layer.sh @@ -0,0 +1,94 @@ +#! /bin/bash +set -e + +# Ingest cli variables +## Parse input ## +NAME=$1 +TYPE=$2 +BASE=$3 +IS_ROLLING=$4 + +# Determine if this is a private or public build +if [[ "${CI_COMMIT_REF_NAME}" == release/* ]] || [[ "${CI_COMMIT_REF_NAME}" == "develop" ]]; then + ENDPOINT="${NAME}" + APPS="kasm-apps" +else + ENDPOINT="${NAME}-private" + APPS="kasm-apps-private" +fi + +# Determine if this is a rolling build +if [[ "${SCHEDULED}" != "NO" ]]; then + if [[ "${SCHEDULE_NAME}" == "NO" ]]; then + SANITIZED_BRANCH=${SANITIZED_BRANCH}-rolling + else + SANITIZED_BRANCH=${SANITIZED_BRANCH}-rolling-${SCHEDULE_NAME} + fi +fi + +# Create workspace and base dockerfile +mkdir -p applayer +cd applayer +echo "FROM scratch" > Dockerfile +echo "ADD ./layer.tar /" >> Dockerfile + +# Clean up layer tar to not include overlay info +clean_tar () { + mkdir cleantar + tar xf layer.tar -C cleantar/ --exclude="**.wh**" + rm layer.tar + cd cleantar + tar -cf layer.tar * + mv layer.tar ../ + cd .. + rm -Rf cleantar/ +} + +# Multi arch +if [ "${TYPE}" == "multi" ]; then + for ARCH in x86_64 aarch64; do + # Create image tarballs + docker save -o $ARCH.tar ${ORG_NAME}/${ENDPOINT}:${ARCH}-${SANITIZED_BRANCH} + # Pull out the layer we are looking for + mkdir $ARCH + mv $ARCH.tar $ARCH + cd $ARCH + tar xf $ARCH.tar + LAYER_FOLDER=$(du -sk * |sort -nr | sed '3q;d' | awk '{print $2}') + mv $LAYER_FOLDER/layer.tar ../ + cd ../ + rm -Rf $ARCH + clean_tar + # build the image based on this single layer + docker build -t ${ORG_NAME}/${APPS}:${ARCH}-${BASE}-${NAME}-${SANITIZED_BRANCH} . + docker push ${ORG_NAME}/${APPS}:${ARCH}-${BASE}-${NAME}-${SANITIZED_BRANCH} + rm -f layer.tar + done + # Manifest + docker manifest push --purge ${ORG_NAME}/${APPS}:${BASE}-${NAME}-${SANITIZED_BRANCH} || : + docker manifest create ${ORG_NAME}/${APPS}:${BASE}-${NAME}-${SANITIZED_BRANCH} ${ORG_NAME}/${APPS}:x86_64-${BASE}-${NAME}-${SANITIZED_BRANCH} ${ORG_NAME}/${APPS}:aarch64-${BASE}-${NAME}-${SANITIZED_BRANCH} + docker manifest annotate ${ORG_NAME}/${APPS}:${BASE}-${NAME}-${SANITIZED_BRANCH} ${ORG_NAME}/${APPS}:aarch64-${BASE}-${NAME}-${SANITIZED_BRANCH} --os linux --arch arm64 --variant v8 + docker manifest push --purge ${ORG_NAME}/${APPS}:${BASE}-${NAME}-${SANITIZED_BRANCH} +# Single arch +else + # Create image tarballs + docker save -o image.tar ${ORG_NAME}/${ENDPOINT}:${SANITIZED_BRANCH} + # Pull out the layer we are looking for + mkdir image + mv image.tar image + cd image + tar xf image.tar + LAYER_FOLDER=$(du -sk * |sort -nr | sed '3q;d' | awk '{print $2}') + mv $LAYER_FOLDER/layer.tar ../ + cd ../ + rm -Rf image + clean_tar + # build the image based on this single layer + docker build -t ${ORG_NAME}/${APPS}:${BASE}-${NAME}-${SANITIZED_BRANCH} . + docker push ${ORG_NAME}/${APPS}:${BASE}-${NAME}-${SANITIZED_BRANCH} + rm -f layer.tar +fi + +# Cleanup +cd .. +rm -Rf applayer diff --git a/ci-scripts/build.sh b/ci-scripts/build.sh new file mode 100755 index 000000000..73224ca82 --- /dev/null +++ b/ci-scripts/build.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +## Parse input ## +NAME=$1 +BASE=$2 +DOCKERFILE=$3 + +# Determine if we are using private images +if [ ${USE_PRIVATE_IMAGES} -eq 1 ]; then + BASE=${BASE}-private +fi + +## Build/Push image to cache endpoint by pipeline ID ## +docker build \ + -t ${ORG_NAME}/image-cache-private:$(arch)-${NAME}-${SANITIZED_BRANCH}-${CI_PIPELINE_ID} \ + --build-arg BASE_IMAGE="${BASE}" \ + --build-arg BASE_TAG="${BASE_TAG}" \ + -f ${DOCKERFILE} . +docker push ${ORG_NAME}/image-cache-private:$(arch)-${NAME}-${SANITIZED_BRANCH}-${CI_PIPELINE_ID} diff --git a/ci-scripts/gitlab-ci.template b/ci-scripts/gitlab-ci.template new file mode 100644 index 000000000..07411aa10 --- /dev/null +++ b/ci-scripts/gitlab-ci.template @@ -0,0 +1,347 @@ +############ +# Settings # +############ +image: docker:28.0.0 +services: + - docker:28.0.0-dind +stages: + - readme + - revert + - build + - test + - manifest +variables: + BASE_TAG: "{{ BASE_TAG }}" + USE_PRIVATE_IMAGES: {{ USE_PRIVATE_IMAGES }} + KASM_RELEASE: "{{ KASM_RELEASE }}" + DOCKER_HOST: tcp://docker:2375 + DOCKER_TLS_CERTDIR: "" + TEST_INSTALLER: "{{ TEST_INSTALLER }}" + MIRROR_ORG_NAME: "{{ MIRROR_ORG_NAME }}" +default: + retry: 2 +before_script: + - docker login --username $DOCKER_HUB_USERNAME --password $DOCKER_HUB_PASSWORD + - if [ "$CI_COMMIT_REF_PROTECTED" == "true" ]; then docker login --username $QUAY_USERNAME --password $QUAY_PASSWORD quay.io; fi + - if [ "$CI_COMMIT_REF_PROTECTED" == "true" ]; then docker login --username $GHCR_USERNAME --password $GHCR_PASSWORD ghcr.io; fi + - export SANITIZED_BRANCH="$(echo $CI_COMMIT_REF_NAME | sed -r 's#^release/##' | sed 's/\//_/g')" + +.run_rules: + rules: + - if: > + $README_USERNAME || + $README_PASSWORD || + $QUAY_API_KEY || + $DOCKERHUB_REVERT || + $REVERT_IS_ROLLING + when: never + +############################################### +# Build Containers and push to cache endpoint # +############################################### +{% for IMAGE in multiImages %} +build_{{ IMAGE.name }}: + stage: build + extends: .run_rules + rules: + - !reference [.run_rules, rules] + - if: $PARENT_PIPELINE_SOURCE == "schedule" && $RUN_SET != "{{ IMAGE.runset }}" + when: never + - if: $CI_COMMIT_REF_NAME == "develop" || $CI_COMMIT_REF_NAME =~ /^release\/.*$/ + when: always + - if: $PARENT_PIPELINE_SOURCE == "merge_request_event" + when: always + {% if FILE_LIMITS %}- changes: + {% for FILE in files %}- {{ FILE }} + {% endfor %}{% for FILE in IMAGE.changeFiles %}- {{ FILE }} + {% endfor %}{% endif %} + - when: never + script: + - apk add bash + - bash ci-scripts/build.sh "{{ IMAGE.name }}" "{{ IMAGE.base }}" "{{ IMAGE.dockerfile }}" + tags: + - ${TAG} + retry: 1 + parallel: + matrix: + - TAG: [ oci-amd-scheduled, oci-arm-scheduled ] +{% endfor %} + +{% for IMAGE in singleImages %} +build_{{ IMAGE.name }}: + stage: build + extends: .run_rules + rules: + - !reference [.run_rules, rules] + - if: $PARENT_PIPELINE_SOURCE == "schedule" && $RUN_SET != "{{ IMAGE.runset }}" + when: never + - if: $CI_COMMIT_REF_NAME == "develop" || $CI_COMMIT_REF_NAME =~ /^release\/.*$/ + when: always + - if: $PARENT_PIPELINE_SOURCE == "merge_request_event" + when: always + {% if FILE_LIMITS %}- changes: + {% for FILE in files %}- {{ FILE }} + {% endfor %}{% for FILE in IMAGE.changeFiles %}- {{ FILE }} + {% endfor %}{% endif %} + - when: never + script: + - apk add bash + - bash ci-scripts/build.sh "{{ IMAGE.name }}" "{{ IMAGE.base }}" "{{ IMAGE.dockerfile }}" + tags: + - oci-amd-scheduled + retry: 1 +{% endfor %} + +###################################### +# Test containers and upload results # +###################################### +{% for IMAGE in multiImages %} +test_{{ IMAGE.name }}: + stage: test + extends: .run_rules + rules: + - !reference [.run_rules, rules] + - if: $PARENT_PIPELINE_SOURCE == "schedule" && $RUN_SET != "{{ IMAGE.runset }}" + when: never + - if: $CI_COMMIT_REF_NAME == "develop" || $CI_COMMIT_REF_NAME =~ /^release\/.*$/ + when: always + - if: $PARENT_PIPELINE_SOURCE == "merge_request_event" + when: always + {% if FILE_LIMITS %}- changes: + {% for FILE in files %}- {{ FILE }} + {% endfor %}{% for FILE in IMAGE.changeFiles %}- {{ FILE }} + {% endfor %}{% endif %} + script: + - apk add bash + - bash ci-scripts/test.sh "{{ IMAGE.name }}" "{{ IMAGE.base }}" "{{ IMAGE.dockerfile }}" "${ARCH}" "${EC2_LAUNCHER_ID}" "${EC2_LAUNCHER_SECRET}" + needs: + - build_{{ IMAGE.name }} + tags: + - oci-amd-scheduled + retry: 1 + parallel: + matrix: + - ARCH: [ "x86_64", "aarch64" ] +{% endfor %} + +{% for IMAGE in singleImages %} +test_{{ IMAGE.name }}: + stage: test + extends: .run_rules + rules: + - !reference [.run_rules, rules] + - if: $PARENT_PIPELINE_SOURCE == "schedule" && $RUN_SET != "{{ IMAGE.runset }}" + when: never + - if: $CI_COMMIT_REF_NAME == "develop" || $CI_COMMIT_REF_NAME =~ /^release\/.*$/ + when: always + - if: $PARENT_PIPELINE_SOURCE == "merge_request_event" + when: always + {% if FILE_LIMITS %}- changes: + {% for FILE in files %}- {{ FILE }} + {% endfor %}{% for FILE in IMAGE.changeFiles %}- {{ FILE }} + {% endfor %}{% endif %} + script: + - apk add bash + - bash ci-scripts/test.sh "{{ IMAGE.name }}" "{{ IMAGE.base }}" "{{ IMAGE.dockerfile }}" "x86_64" "${EC2_LAUNCHER_ID}" "${EC2_LAUNCHER_SECRET}" + needs: + - build_{{ IMAGE.name }} + tags: + - oci-amd-scheduled + retry: 1 +{% endfor %} + +############################################ +# Manifest Containers if their test passed # +############################################ +{% for IMAGE in multiImages %} +manifest_{{ IMAGE.name }}: + stage: manifest + extends: .run_rules + rules: + - !reference [.run_rules, rules] + - if: $PARENT_PIPELINE_SOURCE == "schedule" && $RUN_SET != "{{ IMAGE.runset }}" + when: never + - if: $CI_COMMIT_REF_NAME == "develop" || $CI_COMMIT_REF_NAME =~ /^release\/.*$/ + when: always + - if: $PARENT_PIPELINE_SOURCE == "merge_request_event" + when: always + {% if FILE_LIMITS %}- changes: + {% for FILE in files %}- {{ FILE }} + {% endfor %}{% for FILE in IMAGE.changeFiles %}- {{ FILE }} + {% endfor %}{% endif %} + variables: + SCHEDULED: "{{ SCHEDULED }}" + SCHEDULE_NAME: "{{ SCHEDULE_NAME }}" + script: + - apk add bash tar + - bash ci-scripts/manifest.sh "{{ IMAGE.name }}" "multi" + # Disabling app layer build due to feature not being used + #{% if IMAGE.singleapp %} + #- bash ci-scripts/app-layer.sh "{{ IMAGE.name }}" "multi" "{{ IMAGE.base }}"{% endif %} + needs: + - test_{{ IMAGE.name }} + retry: 1 + tags: + - oci-amd-scheduled +{% endfor %} + +{% for IMAGE in singleImages %} +manifest_{{ IMAGE.name }}: + stage: manifest + extends: .run_rules + rules: + - !reference [.run_rules, rules] + - if: $PARENT_PIPELINE_SOURCE == "schedule" && $RUN_SET != "{{ IMAGE.runset }}" + when: never + - if: $CI_COMMIT_REF_NAME == "develop" || $CI_COMMIT_REF_NAME =~ /^release\/.*$/ + when: always + - if: $PARENT_PIPELINE_SOURCE == "merge_request_event" + when: always + {% if FILE_LIMITS %}- changes: + {% for FILE in files %}- {{ FILE }} + {% endfor %}{% for FILE in IMAGE.changeFiles %}- {{ FILE }} + {% endfor %}{% endif %} + variables: + SCHEDULED: "{{ SCHEDULED }}" + SCHEDULE_NAME: "{{ SCHEDULE_NAME }}" + script: + - apk add bash tar + - bash ci-scripts/manifest.sh "{{ IMAGE.name }}" "single" + # Disabling app layer build due to feature not being used + #{% if IMAGE.singleapp %} + #- bash ci-scripts/app-layer.sh "{{ IMAGE.name }}" "single" "{{ IMAGE.base }}"{% endif %} + needs: + - test_{{ IMAGE.name }} + retry: 1 + tags: + - oci-amd-scheduled +{% endfor %} + +############################# +# Manifest for Weekly Build # +############################# + +{% for IMAGE in multiImages %} +weekly_manifest_{{ IMAGE.name }}: + stage: manifest + extends: .run_rules + rules: + - !reference [.run_rules, rules] + - if: $PARENT_PIPELINE_SOURCE == "schedule" && $RUN_SET == "schedule" + when: always + - when: never + script: + - apk add bash tar + - bash ci-scripts/weekly-manifest.sh "{{ IMAGE.name }}" "multi" + retry: 1 + tags: + - oci-amd-scheduled +{% endfor %} + +{% for IMAGE in singleImages %} +weekly_manifest_{{ IMAGE.name }}: + stage: manifest + extends: .run_rules + rules: + - !reference [.run_rules, rules] + - if: $PARENT_PIPELINE_SOURCE == "schedule" && $RUN_SET == "schedule" + when: always + - when: never + script: + - apk add bash tar + - bash ci-scripts/weekly-manifest.sh "{{ IMAGE.name }}" "single" + retry: 1 + tags: + - oci-amd-scheduled +{% endfor %} + +#################### +# Helper Functions # +#################### + +## Update Readmes ## +{% for IMAGE in multiImages %} +update_readmes_{{ IMAGE.name }}: + stage: readme + rules: + - if: > + $README_USERNAME && + $README_PASSWORD + when: always + script: + - apk add bash + - bash ci-scripts/readme.sh "{{ IMAGE.name }}" + tags: + - oci-amd-scheduled +{% endfor %} + +{% for IMAGE in singleImages %} +update_readmes_{{ IMAGE.name }}: + stage: readme + rules: + - if: > + $README_USERNAME && + $README_PASSWORD + when: always + script: + - apk add bash + - bash ci-scripts/readme.sh "{{ IMAGE.name }}" + tags: + - oci-amd-scheduled +{% endfor %} + +## Update Quay Readmes ## +{% for IMAGE in multiImages %} +update_quay_readmes_{{ IMAGE.name }}: + stage: readme + rules: + - if: $QUAY_API_KEY + when: always + script: + - apk add bash + - bash ci-scripts/quay_readme.sh "{{ IMAGE.name }}" + tags: + - oci-amd-scheduled +{% endfor %} + +{% for IMAGE in singleImages %} +update_quay_readmes_{{ IMAGE.name }}: + stage: readme + rules: + - if: $QUAY_API_KEY + when: always + script: + - apk add bash + - bash ci-scripts/quay_readme.sh "{{ IMAGE.name }}" + tags: + - oci-amd-scheduled +{% endfor %} + +## Revert Images to specific build id ## +{% for IMAGE in multiImages %} +dockerhub_revert_{{ IMAGE.name }}: + stage: revert + rules: + - if: > + $DOCKERHUB_REVERT && + $REVERT_IS_ROLLING + when: always + script: + - /bin/bash ci-scripts/manifest.sh "{{ IMAGE.name }}" "multi" "${DOCKERHUB_REVERT}" "${REVERT_IS_ROLLING}" + tags: + - oci-amd-scheduled +{% endfor %} + +{% for IMAGE in singleImages %} +dockerhub_revert_{{ IMAGE.name }}: + stage: revert + rules: + - if: > + $DOCKERHUB_REVERT && + $REVERT_IS_ROLLING + when: always + script: + - /bin/bash ci-scripts/manifest.sh "{{ IMAGE.name }}" "single" "${DOCKERHUB_REVERT}" "${REVERT_IS_ROLLING}" + tags: + - oci-amd-scheduled +{% endfor %} diff --git a/ci-scripts/manifest.sh b/ci-scripts/manifest.sh new file mode 100755 index 000000000..ab7e1d188 --- /dev/null +++ b/ci-scripts/manifest.sh @@ -0,0 +1,145 @@ +#! /bin/bash +set -e + +# Globals +FAILED="false" +REGISTRY_MIRRORS=("quay.io" "ghcr.io") + +# Ingest cli variables +## Parse input ## +NAME=$1 +TYPE=$2 +REVERT_PIPELINE_ID=$3 +IS_ROLLING=$4 +PULL_BRANCH=${SANITIZED_BRANCH} + +# Determine if this is a private or public build +if [[ "${CI_COMMIT_REF_NAME}" == release/* ]] || [[ "${CI_COMMIT_REF_NAME}" == "develop" ]]; then + PUBLIC_BUILD="true" + ENDPOINT="${NAME}" +else + PUBLIC_BUILD="false" + ENDPOINT="${NAME}-private" +fi + +# Determine if this is a rolling build +if [[ "${SCHEDULED}" != "NO" ]]; then + if [[ "${SCHEDULE_NAME}" == "NO" ]]; then + SANITIZED_BRANCH=${SANITIZED_BRANCH}-rolling + else + SANITIZED_BRANCH=${SANITIZED_BRANCH}-rolling-${SCHEDULE_NAME} + fi +fi + +# Determine if we are doing a reversion +if [ ! -z "${REVERT_PIPELINE_ID}" ]; then + # If we are reverting modify the pipeline ID to the one passed + CI_PIPELINE_ID=${REVERT_PIPELINE_ID} + if [[ "${IS_ROLLING}" == "true" ]]; then + if [[ "${SCHEDULE_NAME}" == "NO" ]]; then + SANITIZED_BRANCH=${SANITIZED_BRANCH}-rolling + else + SANITIZED_BRANCH=${SANITIZED_BRANCH}-rolling-${SCHEDULE_NAME} + fi + fi +fi + +# Check test output +if [[ -z "${REVERT_PIPELINE_ID}" ]]; then + apk add curl + if [[ "${TYPE}" == "multi" ]]; then + ARCHES=("x86_64" "aarch64") + else + ARCHES=("x86_64") + fi + for ARCH in "${ARCHES[@]}"; do + + # Determine test status + STATUS=$(curl -sL https://kasm-ci.s3.amazonaws.com/${CI_COMMIT_SHA}/${ARCH}/kasmweb/image-cache-private/${ARCH}-${NAME}-${PULL_BRANCH}-${CI_PIPELINE_ID}/ci-status.yml | awk -F'"' '{print $2}') + if [ "${STATUS}" == "PASS" ]; then + STATE=success + else + STATE=failed + FAILED="true" + fi + + # Ping gitlab api with link output + curl --request POST --header "PRIVATE-TOKEN:${GITLAB_API_TOKEN}" "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/statuses/${CI_COMMIT_SHA}?state=${STATE}&name=${NAME}_${ARCH}&target_url=https://kasm-ci.s3.amazonaws.com/${CI_COMMIT_SHA}/${ARCH}/kasmweb/image-cache-private/${ARCH}-${NAME}-${PULL_BRANCH}-${CI_PIPELINE_ID}/index.html" + + done +fi + +# Fail job and go no further if tests did not pass +if [[ "${FAILED}" == "true" ]]; then + exit 1 +fi + +# Manifest for multi pull and push for single arch +if [[ "${TYPE}" == "multi" ]]; then + + # Pull images from cache repo + docker pull ${ORG_NAME}/image-cache-private:x86_64-${NAME}-${PULL_BRANCH}-${CI_PIPELINE_ID} + docker pull ${ORG_NAME}/image-cache-private:aarch64-${NAME}-${PULL_BRANCH}-${CI_PIPELINE_ID} + + # Tag images to live repo + docker tag \ + ${ORG_NAME}/image-cache-private:x86_64-${NAME}-${PULL_BRANCH}-${CI_PIPELINE_ID} \ + ${ORG_NAME}/${ENDPOINT}:x86_64-${SANITIZED_BRANCH} + docker tag \ + ${ORG_NAME}/image-cache-private:aarch64-${NAME}-${PULL_BRANCH}-${CI_PIPELINE_ID} \ + ${ORG_NAME}/${ENDPOINT}:aarch64-${SANITIZED_BRANCH} + + # Push arches to live repo + docker push ${ORG_NAME}/${ENDPOINT}:x86_64-${SANITIZED_BRANCH} + docker push ${ORG_NAME}/${ENDPOINT}:aarch64-${SANITIZED_BRANCH} + + # Manifest to meta tag + docker manifest push --purge ${ORG_NAME}/${ENDPOINT}:${SANITIZED_BRANCH} || : + docker manifest create ${ORG_NAME}/${ENDPOINT}:${SANITIZED_BRANCH} ${ORG_NAME}/${ENDPOINT}:x86_64-${SANITIZED_BRANCH} ${ORG_NAME}/${ENDPOINT}:aarch64-${SANITIZED_BRANCH} + docker manifest annotate ${ORG_NAME}/${ENDPOINT}:${SANITIZED_BRANCH} ${ORG_NAME}/${ENDPOINT}:aarch64-${SANITIZED_BRANCH} --os linux --arch arm64 --variant v8 + docker manifest push --purge ${ORG_NAME}/${ENDPOINT}:${SANITIZED_BRANCH} + + if [[ "${PUBLIC_BUILD}" == "true" ]]; then + for MIRROR in "${REGISTRY_MIRRORS[@]}"; do + docker tag \ + ${ORG_NAME}/image-cache-private:x86_64-${NAME}-${PULL_BRANCH}-${CI_PIPELINE_ID} \ + ${MIRROR}/${MIRROR_ORG_NAME}/${ENDPOINT}:x86_64-${SANITIZED_BRANCH} + docker tag \ + ${ORG_NAME}/image-cache-private:aarch64-${NAME}-${PULL_BRANCH}-${CI_PIPELINE_ID} \ + ${MIRROR}/${MIRROR_ORG_NAME}/${ENDPOINT}:aarch64-${SANITIZED_BRANCH} + + # Push arches to live repo + docker push ${MIRROR}/${MIRROR_ORG_NAME}/${ENDPOINT}:x86_64-${SANITIZED_BRANCH} + docker push ${MIRROR}/${MIRROR_ORG_NAME}/${ENDPOINT}:aarch64-${SANITIZED_BRANCH} + + # Manifest to meta tag + docker manifest push --purge ${MIRROR}/${MIRROR_ORG_NAME}/${ENDPOINT}:${SANITIZED_BRANCH} || : + docker manifest create ${MIRROR}/${MIRROR_ORG_NAME}/${ENDPOINT}:${SANITIZED_BRANCH} ${MIRROR}/${MIRROR_ORG_NAME}/${ENDPOINT}:x86_64-${SANITIZED_BRANCH} ${MIRROR}/${MIRROR_ORG_NAME}/${ENDPOINT}:aarch64-${SANITIZED_BRANCH} + docker manifest annotate ${MIRROR}/${MIRROR_ORG_NAME}/${ENDPOINT}:${SANITIZED_BRANCH} ${MIRROR}/${MIRROR_ORG_NAME}/${ENDPOINT}:aarch64-${SANITIZED_BRANCH} --os linux --arch arm64 --variant v8 + docker manifest push --purge ${MIRROR}/${MIRROR_ORG_NAME}/${ENDPOINT}:${SANITIZED_BRANCH} + done + fi +# Single arch image just pull and push +else + + # Pull image + docker pull ${ORG_NAME}/image-cache-private:x86_64-${NAME}-${PULL_BRANCH}-${CI_PIPELINE_ID} + + # Tage image + docker tag \ + ${ORG_NAME}/image-cache-private:x86_64-${NAME}-${PULL_BRANCH}-${CI_PIPELINE_ID} \ + ${ORG_NAME}/${ENDPOINT}:${SANITIZED_BRANCH} + + # Push image + docker push ${ORG_NAME}/${ENDPOINT}:${SANITIZED_BRANCH} + + if [[ "${PUBLIC_BUILD}" == "true" ]]; then + for MIRROR in "${REGISTRY_MIRRORS[@]}"; do + docker tag \ + ${ORG_NAME}/image-cache-private:x86_64-${NAME}-${PULL_BRANCH}-${CI_PIPELINE_ID} \ + ${MIRROR}/${MIRROR_ORG_NAME}/${ENDPOINT}:${SANITIZED_BRANCH} + + docker push ${MIRROR}/${MIRROR_ORG_NAME}/${ENDPOINT}:${SANITIZED_BRANCH} + done + fi +fi diff --git a/ci-scripts/quay_readme.sh b/ci-scripts/quay_readme.sh new file mode 100644 index 000000000..5f32ca36d --- /dev/null +++ b/ci-scripts/quay_readme.sh @@ -0,0 +1,11 @@ +#! /bin/bash + +## Parse input ## +NAME=$1 + +## Run readme updater ## +docker run -v $PWD/docs:/docs \ + -e RELEASE="$KASM_RELEASE" \ + -e QUAY_API_KEY="$QUAY_API_KEY" \ + -e QUAY_REPOSITORY="${MIRROR_ORG_NAME}/${NAME}" \ + kasmweb/dockerhub-updater:develop diff --git a/ci-scripts/readme.sh b/ci-scripts/readme.sh new file mode 100755 index 000000000..a9953ca75 --- /dev/null +++ b/ci-scripts/readme.sh @@ -0,0 +1,12 @@ +#! /bin/bash + +## Parse input ## +NAME=$1 + +## Run readme updater ## +docker run -v $PWD/docs:/docs \ + -e RELEASE="$KASM_RELEASE" \ + -e DOCKER_USERNAME="$README_USERNAME" \ + -e DOCKER_PASSWORD="$README_PASSWORD" \ + -e DOCKERHUB_REPOSITORY="${ORG_NAME}/${NAME}" \ + kasmweb/dockerhub-updater:develop diff --git a/ci-scripts/template-gitlab.py b/ci-scripts/template-gitlab.py new file mode 100644 index 000000000..d1fb3b6b7 --- /dev/null +++ b/ci-scripts/template-gitlab.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 + +from jinja2 import Template +import yaml +import os + +# Determine if this is a feature branch +fileLimits = True +scheduled = 'NO' +scheduleName = 'NO' +if os.getenv('SANITIZED_BRANCH').startswith('release') or os.getenv('SANITIZED_BRANCH') == 'develop': + fileLimits = False +if os.getenv('CI_PIPELINE_SOURCE') == 'schedule': + fileLimits = False + scheduled = 'YES' +if 'SCHEDULE_NAME' in os.environ: + scheduleName = os.getenv('SCHEDULE_NAME') +if os.getenv('USE_PRIVATE_IMAGES') == 1: + fileLimits = False + +# Read yaml file with variables +with open("template-vars.yaml", 'r') as stream: + templateVars = yaml.safe_load(stream) + templateVars['KASM_RELEASE'] = os.getenv('KASM_RELEASE') + templateVars['TEST_INSTALLER'] = os.getenv('TEST_INSTALLER') + templateVars['USE_PRIVATE_IMAGES'] = os.getenv('USE_PRIVATE_IMAGES') + templateVars['BASE_TAG'] = os.getenv('BASE_TAG') + templateVars['FILE_LIMITS'] = fileLimits + templateVars['SCHEDULED'] = scheduled + templateVars['SCHEDULE_NAME'] = scheduleName + +# Read template file +with open("gitlab-ci.template", 'r') as stream: + template = stream.read() + +# Template the variables in +jinjaTemplate = Template(template) +gitlabCi = jinjaTemplate.render(templateVars) + +# Write out the gitlab file +with open('../gitlab-ci.yml', 'w') as out: + out.write(gitlabCi + '\n') diff --git a/ci-scripts/template-vars.yaml b/ci-scripts/template-vars.yaml new file mode 100644 index 000000000..f840c6b30 --- /dev/null +++ b/ci-scripts/template-vars.yaml @@ -0,0 +1,930 @@ +files: &UNIVERSAL_CHANGE_FILES + - src/common/** + - ci-scripts/** + - .gitlab-ci.yml + +multiImages: + - name: audacity + runset: set-a + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-audacity + changeFiles: + - dockerfile-kasm-audacity + - src/ubuntu/install/audacity/** + - name: chromium + runset: set-b + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-chromium + changeFiles: + - dockerfile-kasm-chromium + - src/ubuntu/install/gtk/** + - src/ubuntu/install/chromium/** + - src/ubuntu/install/certificates/** + - name: cyberbro + runset: set-b + singleapp: true + base: core-ubuntu-noble + dockerfile: dockerfile-kasm-cyberbro + changeFiles: + - dockerfile-kasm-cyberbro + - src/ubuntu/install/firefox/** + - src/ubuntu/install/cyberbro/** + - name: deluge + runset: set-a + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-deluge + changeFiles: + - dockerfile-kasm-deluge + - src/ubuntu/install/deluge/** + - name: doom + runset: set-b + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-doom + changeFiles: + - dockerfile-kasm-doom + - src/ubuntu/install/doom/** + - name: filezilla + runset: set-a + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-filezilla + changeFiles: + - dockerfile-kasm-filezilla + - src/ubuntu/install/filezilla/** + - name: firefox + runset: set-b + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-firefox + changeFiles: + - dockerfile-kasm-firefox + - src/ubuntu/install/gtk/** + - src/ubuntu/install/firefox/** + - src/ubuntu/install/certificates/** + - name: gimp + runset: set-a + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-gimp + changeFiles: + - dockerfile-kasm-gimp + - src/ubuntu/install/gimp/** + - name: inkscape + runset: set-b + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-inkscape + changeFiles: + - dockerfile-kasm-inkscape + - src/ubuntu/install/inkscape/** + - name: java-dev + runset: set-a + singleapp: false + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-java-dev + changeFiles: + - dockerfile-kasm-java-dev + - src/ubuntu/install/vs_code/** + - src/ubuntu/install/tools/** + - src/ubuntu/install/sublime_text/** + - src/ubuntu/install/remmina/** + - src/ubuntu/install/misc/** + - src/ubuntu/install/firefox/** + - src/ubuntu/install/chrome/** + - src/ubuntu/install/eclipse/** + - name: kasmos-desktop + runset: set-b + singleapp: false + base: core-kasmos + dockerfile: dockerfile-kasmos-desktop + changeFiles: + - src/ubuntu/install/chrome/** + - src/ubuntu/install/chromium/** + - src/ubuntu/intall/only_office/** + - src/ubuntu/install/libre_office/** + - src/ubuntu/install/misc/** + - src/kasmos/install/browser/** + - src/ubuntu/install/vs_code/** + - src/ubuntu/install/nextcloud/** + - src/ubuntu/install/remmina/** + - src/kasmos/install/office/** + - src/ubuntu/install/zoom/** + - src/ubuntu/install/thunderbird/** + - src/ubuntu/install/slack/** + - src/ubuntu/install/gamepad_utils/** + - src/ubuntu/install/cleanup/** + - name: libre-office + runset: set-a + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-libre-office + changeFiles: + - dockerfile-kasm-libre-office + - src/ubuntu/install/libre_office/** + - name: nessus + runset: set-b + singleapp: false + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-nessus + changeFiles: + - dockerfile-kasm-nessus + - src/ubuntu/install/chromium/** + - src/ubuntu/install/nessus/** + - src/ubuntu/install/cleanup/** + - name: obsidian + runset: set-a + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-obsidian + changeFiles: + - dockerfile-kasm-obsidian + - src/ubuntu/install/obsidian/** + - src/ubuntu/install/chrome/** + - name: opensuse-15-desktop + runset: set-a + singleapp: false + base: core-opensuse-15 + dockerfile: dockerfile-kasm-opensuse-15-desktop + changeFiles: + - dockerfile-kasm-opensuse-15-desktop + - src/ubuntu/install/thunderbird/** + - src/ubuntu/install/remmina/** + - src/ubuntu/install/nextcloud/** + - src/ubuntu/install/langpacks/** + - src/ubuntu/install/firefox/** + - src/ubuntu/install/cleanup/** + - src/ubuntu/install/chromium/** + - src/ubuntu/install/chrome/** + - src/ubuntu/install/slack/** + - src/opensuse/install/** + - name: oracle-8-desktop + runset: set-b + singleapp: false + base: core-oracle-8 + dockerfile: dockerfile-kasm-oracle-8-desktop + changeFiles: + - dockerfile-kasm-oracle-8-desktop + - src/oracle/install/** + - src/ubuntu/install/thunderbird/** + - src/ubuntu/install/remmina/** + - src/ubuntu/install/nextcloud/** + - src/ubuntu/install/firefox/** + - src/ubuntu/install/cleanup/** + - src/ubuntu/install/chromium/** + - src/ubuntu/install/slack/** + - name: pinta + runset: set-a + singleapp: true + base: core-ubuntu-noble + dockerfile: dockerfile-kasm-pinta + changeFiles: + - dockerfile-kasm-pinta + - src/ubuntu/install/pinta/** + - name: qbittorrent + runset: set-b + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-qbittorrent + changeFiles: + - dockerfile-kasm-qbittorrent + - src/ubuntu/install/qbittorrent/** + - name: redroid + runset: set-a + singleapp: false + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-redroid + changeFiles: + - dockerfile-kasm-redroid + - src/ubuntu/install/redroid/** + - src/ubuntu/install/android_studio/** + - src/ubuntu/install/misc/** + - src/ubuntu/install/sublime_text/** + - src/ubuntu/install/vs_code/** + - src/ubuntu/install/chrome/** + - src/ubuntu/install/chromium/** + - src/ubuntu/install/tools/** + - src/ubuntu/install/cleanup/** + - name: remmina + runset: set-b + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-remmina + changeFiles: + - dockerfile-kasm-remmina + - src/ubuntu/install/remmina/** + - name: spiderfoot + runset: set-a + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-spiderfoot + changeFiles: + - dockerfile-kasm-spiderfoot + - src/ubuntu/install/spiderfoot/** + - src/ubuntu/install/firefox/** + - src/ubuntu/install/tools/** + - src/ubuntu/install/cleanup/** + - name: sublime-text + runset: set-b + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-sublime-text + changeFiles: + - dockerfile-kasm-sublime-text + - src/ubuntu/install/sublime_text/** + - name: telegram + runset: set-a + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-telegram + changeFiles: + - dockerfile-kasm-telegram + - src/ubuntu/install/telegram/** + - src/ubuntu/install/chrome/** + - name: terminal + runset: set-b + singleapp: false + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-terminal + changeFiles: + - dockerfile-kasm-terminal + - src/ubuntu/install/terraform/** + - src/ubuntu/install/ansible/** + - src/ubuntu/install/terminal/** + - name: thunderbird + runset: set-a + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-thunderbird + changeFiles: + - dockerfile-kasm-thunderbird + - src/ubuntu/install/thunderbird/** + - name: tor-browser + runset: set-b + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-tor-browser + changeFiles: + - dockerfile-kasm-tor-browser + - src/ubuntu/install/gtk/** + - src/ubuntu/install/torbrowser/** + - name: ubuntu-jammy-desktop + runset: set-a + singleapp: false + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-ubuntu-jammy-desktop + changeFiles: + - dockerfile-kasm-ubuntu-jammy-desktop + - src/ubuntu/install/zoom/** + - src/ubuntu/install/vs_code/** + - src/ubuntu/install/tools/** + - src/ubuntu/install/thunderbird/** + - src/ubuntu/install/terraform/** + - src/ubuntu/install/telegram/** + - src/ubuntu/install/sublime_text/** + - src/ubuntu/install/signal/** + - src/ubuntu/install/remmina/** + - src/ubuntu/install/only_office/** + - src/ubuntu/install/obs/** + - src/ubuntu/install/nextcloud/** + - src/ubuntu/install/misc/** + - src/ubuntu/install/gimp/** + - src/ubuntu/install/gamepad_utils/** + - src/ubuntu/install/firefox/** + - src/ubuntu/install/cleanup/** + - src/ubuntu/install/chromium/** + - src/ubuntu/install/ansible/** + - src/ubuntu/install/chrome/** + - src/ubuntu/install/slack/** + - name: ubuntu-jammy-desktop-vpn + runset: set-b + singleapp: false + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-ubuntu-jammy-desktop-vpn + changeFiles: + - dockerfile-kasm-ubuntu-jammy-desktop-vpn + - src/ubuntu/install/zoom/** + - src/ubuntu/install/vs_code/** + - src/ubuntu/install/tools/** + - src/ubuntu/install/thunderbird/** + - src/ubuntu/install/terraform/** + - src/ubuntu/install/telegram/** + - src/ubuntu/install/sublime_text/** + - src/ubuntu/install/signal/** + - src/ubuntu/install/remmina/** + - src/ubuntu/install/only_office/** + - src/ubuntu/install/obs/** + - src/ubuntu/install/nextcloud/** + - src/ubuntu/install/misc/** + - src/ubuntu/install/gimp/** + - src/ubuntu/install/gamepad_utils/** + - src/ubuntu/install/firefox/** + - src/ubuntu/install/cleanup/** + - src/ubuntu/install/chromium/** + - src/ubuntu/install/ansible/** + - src/ubuntu/install/chrome/** + - src/ubuntu/install/slack/** + - src/ubuntu/install/vpn/** + - name: ubuntu-noble-desktop + runset: set-a + singleapp: false + base: core-ubuntu-noble + dockerfile: dockerfile-kasm-ubuntu-noble-desktop + changeFiles: + - dockerfile-kasm-ubuntu-noble-desktop + - src/ubuntu/install/zoom/** + - src/ubuntu/install/vs_code/** + - src/ubuntu/install/tools/** + - src/ubuntu/install/thunderbird/** + - src/ubuntu/install/terraform/** + - src/ubuntu/install/telegram/** + - src/ubuntu/install/sublime_text/** + - src/ubuntu/install/signal/** + - src/ubuntu/install/remmina/** + - src/ubuntu/install/only_office/** + - src/ubuntu/install/obs/** + - src/ubuntu/install/nextcloud/** + - src/ubuntu/install/misc/** + - src/ubuntu/install/gimp/** + - src/ubuntu/install/gamepad_utils/** + - src/ubuntu/install/firefox/** + - src/ubuntu/install/cleanup/** + - src/ubuntu/install/chromium/** + - src/ubuntu/install/ansible/** + - src/ubuntu/install/chrome/** + - src/ubuntu/install/slack/** + - name: vlc + runset: set-b + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-vlc + changeFiles: + - dockerfile-kasm-vlc + - src/ubuntu/install/vlc/** + - name: vs-code + runset: set-a + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-vs-code + changeFiles: + - dockerfile-kasm-vs-code + - src/ubuntu/install/vs_code/** + - src/ubuntu/install/chrome/** + - name: almalinux-8-desktop + runset: set-b + singleapp: false + base: core-almalinux-8 + dockerfile: dockerfile-kasm-almalinux-8-desktop + changeFiles: + - dockerfile-kasm-almalinux-8-desktop + - src/oracle/install/** + - src/ubuntu/install/thunderbird/** + - src/ubuntu/install/remmina/** + - src/ubuntu/install/nextcloud/** + - src/ubuntu/install/firefox/** + - src/ubuntu/install/cleanup/** + - src/ubuntu/install/chromium/** + - src/ubuntu/install/slack/** + - name: almalinux-9-desktop + runset: set-a + singleapp: false + base: core-almalinux-9 + dockerfile: dockerfile-kasm-almalinux-9-desktop + changeFiles: + - dockerfile-kasm-almalinux-9-desktop + - src/oracle/install/** + - src/ubuntu/install/thunderbird/** + - src/ubuntu/install/remmina/** + - src/ubuntu/install/firefox/** + - src/ubuntu/install/cleanup/** + - src/ubuntu/install/chromium/** + - src/ubuntu/install/slack/** + - name: alpine-319-desktop + runset: set-b + singleapp: false + base: core-alpine-319 + dockerfile: dockerfile-kasm-alpine-319-desktop + changeFiles: + - dockerfile-kasm-alpine-319-desktop + - src/ubuntu/install/langpacks/** + - src/ubuntu/install/cleanup/** + - src/alpine/install/** + - name: alpine-320-desktop + runset: set-a + singleapp: false + base: core-alpine-320 + dockerfile: dockerfile-kasm-alpine-320-desktop + changeFiles: + - dockerfile-kasm-alpine-320-desktop + - src/ubuntu/install/langpacks/** + - src/ubuntu/install/cleanup/** + - src/alpine/install/** + - name: alpine-321-desktop + runset: set-b + singleapp: false + base: core-alpine-321 + dockerfile: dockerfile-kasm-alpine-321-desktop + changeFiles: + - dockerfile-kasm-alpine-321-desktop + - src/ubuntu/install/langpacks/** + - src/ubuntu/install/cleanup/** + - src/alpine/install/** + - name: brave + runset: set-a + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-brave + changeFiles: + - dockerfile-kasm-brave + - src/ubuntu/install/gtk/** + - src/ubuntu/install/brave/** + - name: debian-bullseye-desktop + runset: set-b + singleapp: false + base: core-debian-bullseye + dockerfile: dockerfile-kasm-debian-bullseye-desktop + changeFiles: + - dockerfile-kasm-debian-bullseye-desktop + - src/ubuntu/install/zoom/** + - src/ubuntu/install/vs_code/** + - src/ubuntu/install/tools/** + - src/ubuntu/install/thunderbird/** + - src/ubuntu/install/terraform/** + - src/ubuntu/install/telegram/** + - src/ubuntu/install/sublime_text/** + - src/ubuntu/install/signal/** + - src/ubuntu/install/remmina/** + - src/ubuntu/install/only_office/** + - src/ubuntu/install/obs/** + - src/ubuntu/install/gimp/** + - src/ubuntu/install/gamepad_utils/** + - src/ubuntu/install/firefox/** + - src/ubuntu/install/cleanup/** + - src/ubuntu/install/chromium/** + - src/ubuntu/install/ansible/** + - src/ubuntu/install/chrome/** + - src/ubuntu/install/slack/** + - name: debian-bookworm-desktop + runset: set-a + singleapp: false + base: core-debian-bookworm + dockerfile: dockerfile-kasm-debian-bookworm-desktop + changeFiles: + - dockerfile-kasm-debian-bookworm-desktop + - src/ubuntu/install/zoom/** + - src/ubuntu/install/vs_code/** + - src/ubuntu/install/tools/** + - src/ubuntu/install/thunderbird/** + - src/ubuntu/install/terraform/** + - src/ubuntu/install/telegram/** + - src/ubuntu/install/sublime_text/** + - src/ubuntu/install/signal/** + - src/ubuntu/install/remmina/** + - src/ubuntu/install/only_office/** + - src/ubuntu/install/obs/** + - src/ubuntu/install/gimp/** + - src/ubuntu/install/gamepad_utils/** + - src/ubuntu/install/firefox/** + - src/ubuntu/install/cleanup/** + - src/ubuntu/install/chromium/** + - src/ubuntu/install/ansible/** + - src/ubuntu/install/chrome/** + - src/ubuntu/install/slack/** + - name: debian-trixie-desktop + runset: set-a + singleapp: false + base: core-debian-trixie + dockerfile: dockerfile-kasm-debian-trixie-desktop + changeFiles: + - dockerfile-kasm-debian-trixie-desktop + - src/ubuntu/install/zoom/** + - src/ubuntu/install/vs_code/** + - src/ubuntu/install/tools/** + - src/ubuntu/install/thunderbird/** + - src/ubuntu/install/terraform/** + - src/ubuntu/install/telegram/** + - src/ubuntu/install/sublime_text/** + - src/ubuntu/install/signal/** + - src/ubuntu/install/remmina/** + - src/ubuntu/install/only_office/** + - src/ubuntu/install/obs/** + - src/ubuntu/install/gimp/** + - src/ubuntu/install/gamepad_utils/** + - src/ubuntu/install/firefox/** + - src/ubuntu/install/cleanup/** + - src/ubuntu/install/chromium/** + - src/ubuntu/install/ansible/** + - src/ubuntu/install/chrome/** + - src/ubuntu/install/slack/** + - name: fedora-39-desktop + runset: set-b + singleapp: false + base: core-fedora-39 + dockerfile: dockerfile-kasm-fedora-39-desktop + changeFiles: + - dockerfile-kasm-fedora-39-desktop + - src/oracle/install/** + - src/ubuntu/install/thunderbird/** + - src/ubuntu/install/remmina/** + - src/ubuntu/install/firefox/** + - src/ubuntu/install/cleanup/** + - src/ubuntu/install/chromium/** + - src/ubuntu/install/slack/** + - name: fedora-40-desktop + runset: set-a + singleapp: false + base: core-fedora-40 + dockerfile: dockerfile-kasm-fedora-40-desktop + changeFiles: + - dockerfile-kasm-fedora-40-desktop + - src/oracle/install/** + - src/ubuntu/install/thunderbird/** + - src/ubuntu/install/remmina/** + - src/ubuntu/install/firefox/** + - src/ubuntu/install/cleanup/** + - src/ubuntu/install/chromium/** + - src/ubuntu/install/slack/** + - name: fedora-41-desktop + runset: set-a + singleapp: false + base: core-fedora-41 + dockerfile: dockerfile-kasm-fedora-41-desktop + changeFiles: + - dockerfile-kasm-fedora-41-desktop + - src/oracle/install/** + - src/ubuntu/install/thunderbird/** + - src/ubuntu/install/remmina/** + - src/ubuntu/install/firefox/** + - src/ubuntu/install/cleanup/** + - src/ubuntu/install/chromium/** + - src/ubuntu/install/slack/** + - name: kali-rolling-desktop + runset: set-b + singleapp: false + base: core-kali-rolling + dockerfile: dockerfile-kasm-kali-rolling-desktop + changeFiles: + - dockerfile-kasm-kali-rolling-desktop + - src/ubuntu/install/firefox/** + - src/ubuntu/install/cleanup/** + - src/ubuntu/install/chromium/** + - name: maltego + runset: set-a + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-maltego + changeFiles: + - dockerfile-kasm-maltego + - src/ubuntu/install/maltego/** + - src/ubuntu/install/firefox/** + - name: minetest + runset: set-b + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-minetest + changeFiles: + - dockerfile-kasm-minetest + - src/ubuntu/install/minetest/** + - name: oracle-9-desktop + runset: set-a + singleapp: false + base: core-oracle-9 + dockerfile: dockerfile-kasm-oracle-9-desktop + changeFiles: + - dockerfile-kasm-oracle-9-desktop + - src/oracle/install/** + - src/ubuntu/install/thunderbird/** + - src/ubuntu/install/remmina/** + - src/ubuntu/install/firefox/** + - src/ubuntu/install/cleanup/** + - src/ubuntu/install/chromium/** + - src/ubuntu/install/slack/** + - name: rhel-9-desktop + runset: set-b + singleapp: false + base: core-rhel-9 + dockerfile: dockerfile-kasm-rhel-9-desktop + changeFiles: + - dockerfile-kasm-rhel-9-desktop + - src/oracle/install/** + - src/ubuntu/install/thunderbird/** + - src/ubuntu/install/remmina/** + - src/ubuntu/install/firefox/** + - src/ubuntu/install/cleanup/** + - src/ubuntu/install/chromium/** + - src/ubuntu/install/slack/** + - name: parrotos-6-desktop + runset: set-a + singleapp: false + base: core-parrotos-6 + dockerfile: dockerfile-kasm-parrotos-6-desktop + changeFiles: + - dockerfile-kasm-parrotos-6-desktop + - src/ubuntu/install/parrot/** + - src/ubuntu/install/firefox/** + - src/ubuntu/install/cleanup/** + - src/ubuntu/install/chromium/** + - name: retroarch + runset: set-b + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-retroarch + changeFiles: + - dockerfile-kasm-retroarch + - src/ubuntu/install/retroarch/** + - name: rockylinux-8-desktop + runset: set-a + singleapp: false + base: core-rockylinux-8 + dockerfile: dockerfile-kasm-rockylinux-8-desktop + changeFiles: + - dockerfile-kasm-rockylinux-8-desktop + - src/oracle/install/** + - src/ubuntu/install/thunderbird/** + - src/ubuntu/install/remmina/** + - src/ubuntu/install/nextcloud/** + - src/ubuntu/install/firefox/** + - src/ubuntu/install/cleanup/** + - src/ubuntu/install/chromium/** + - src/ubuntu/install/slack/** + - name: rockylinux-9-desktop + runset: set-b + singleapp: false + base: core-rockylinux-9 + dockerfile: dockerfile-kasm-rockylinux-9-desktop + changeFiles: + - dockerfile-kasm-rockylinux-9-desktop + - src/oracle/install/** + - src/ubuntu/install/thunderbird/** + - src/ubuntu/install/remmina/** + - src/ubuntu/install/firefox/** + - src/ubuntu/install/cleanup/** + - src/ubuntu/install/chromium/** + - src/ubuntu/install/slack/** + - name: super-tux-kart + runset: set-a + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-super-tux-kart + changeFiles: + - dockerfile-kasm-super-tux-kart + - src/ubuntu/install/super_tux_kart/** + - name: ubuntu-jammy-dind + runset: set-b + singleapp: false + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-ubuntu-jammy-dind + changeFiles: + - dockerfile-kasm-ubuntu-jammy-dind + - src/ubuntu/install/vs_code/** + - src/ubuntu/install/tools/** + - src/ubuntu/install/sublime_text/** + - src/ubuntu/install/misc/** + - src/ubuntu/install/dind/** + - src/ubuntu/install/cleanup/** + - src/ubuntu/install/chromium/** + - src/ubuntu/install/chrome/** + - name: ubuntu-jammy-dind-rootless + runset: set-a + singleapp: false + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-ubuntu-jammy-dind-rootless + changeFiles: + - dockerfile-kasm-ubuntu-jammy-dind-rootless + - src/ubuntu/install/vs_code/** + - src/ubuntu/install/vs_code/** + - src/ubuntu/install/tools/** + - src/ubuntu/install/sublime_text/** + - src/ubuntu/install/misc/** + - src/ubuntu/install/dind_rootless/** + - src/ubuntu/install/cleanup/** + - src/ubuntu/install/chromium/** + - src/ubuntu/install/chrome/** + - name: ubuntu-noble-dind + runset: set-b + singleapp: false + base: core-ubuntu-noble + dockerfile: dockerfile-kasm-ubuntu-noble-dind + changeFiles: + - dockerfile-kasm-ubuntu-noble-dind + - src/ubuntu/install/vs_code/** + - src/ubuntu/install/tools/** + - src/ubuntu/install/sublime_text/** + - src/ubuntu/install/misc/** + - src/ubuntu/install/dind/** + - src/ubuntu/install/cleanup/** + - src/ubuntu/install/chromium/** + - src/ubuntu/install/chrome/** + - name: ubuntu-noble-dind-rootless + runset: set-a + singleapp: false + base: core-ubuntu-noble + dockerfile: dockerfile-kasm-ubuntu-noble-dind-rootless + changeFiles: + - dockerfile-kasm-ubuntu-noble-dind-rootless + - src/ubuntu/install/vs_code/** + - src/ubuntu/install/vs_code/** + - src/ubuntu/install/tools/** + - src/ubuntu/install/sublime_text/** + - src/ubuntu/install/misc/** + - src/ubuntu/install/dind_rootless/** + - src/ubuntu/install/cleanup/** + - src/ubuntu/install/chromium/** + - src/ubuntu/install/chrome/** + - name: vivaldi + runset: set-b + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-vivaldi + changeFiles: + - dockerfile-kasm-vivaldi + - src/ubuntu/install/gtk/** + - src/ubuntu/install/certificates/** + - src/ubuntu/install/vivaldi/** +singleImages: + - name: blender + runset: set-a + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-blender + changeFiles: + - dockerfile-kasm-blender + - src/ubuntu/install/blender/** + - name: chrome + runset: set-b + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-chrome + changeFiles: + - dockerfile-kasm-chrome + - src/ubuntu/install/gtk/** + - src/ubuntu/install/certificates/** + - src/ubuntu/install/chrome/** + - name: desktop + runset: set-a + singleapp: false + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-desktop + changeFiles: + - dockerfile-kasm-desktop + - src/ubuntu/install/firefox/** + - src/ubuntu/install/certificates/** + - src/ubuntu/install/chrome/** + - name: desktop-deluxe + runset: set-b + singleapp: false + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-desktop-deluxe + changeFiles: + - dockerfile-kasm-desktop-deluxe + - src/ubuntu/install/zoom/** + - src/ubuntu/install/vs_code/** + - src/ubuntu/install/tools/** + - src/ubuntu/install/terraform/** + - src/ubuntu/install/telegram/** + - src/ubuntu/install/sublime_text/** + - src/ubuntu/install/signal/** + - src/ubuntu/install/remmina/** + - src/ubuntu/install/only_office/** + - src/ubuntu/install/obs/** + - src/ubuntu/install/nextcloud/** + - src/ubuntu/install/misc/** + - src/ubuntu/install/gimp/** + - src/ubuntu/install/firefox/** + - src/ubuntu/install/firefox/** + - src/ubuntu/install/ansible/** + - src/ubuntu/install/chrome/** + - name: discord + runset: set-b + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-discord + changeFiles: + - dockerfile-kasm-discord + - src/ubuntu/install/discord/** + - name: edge + runset: set-a + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-edge + changeFiles: + - dockerfile-kasm-edge + - src/ubuntu/install/gtk/** + - src/ubuntu/install/edge/** + - name: hunchly + runset: set-b + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-hunchly + changeFiles: + - dockerfile-kasm-hunchly + - src/ubuntu/install/chrome/** + - src/ubuntu/install/hunchly/** + - name: insomnia + runset: set-a + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-insomnia + changeFiles: + - dockerfile-kasm-insomnia + - src/ubuntu/install/insomnia/** + - name: only-office + runset: set-b + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-only-office + changeFiles: + - dockerfile-kasm-only-office + - name: postman + runset: set-a + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-postman + changeFiles: + - dockerfile-kasm-postman + - src/ubuntu/install/chrome/** + - src/ubuntu/install/postman/** + - name: signal + runset: set-b + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-signal + changeFiles: + - dockerfile-kasm-signal + - src/ubuntu/install/signal/** + - name: slack + runset: set-a + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-slack + changeFiles: + - dockerfile-kasm-slack + - src/ubuntu/install/slack/** + - src/ubuntu/install/chrome/** + - src/ubuntu/install/tools/** + - src/ubuntu/install/cleanup/** + - name: steam + runset: set-b + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-steam + changeFiles: + - dockerfile-kasm-steam + - src/ubuntu/install/steam/** + - name: tracelabs + runset: set-a + singleapp: false + base: core-kali-rolling + dockerfile: dockerfile-kasm-tracelabs + changeFiles: + - dockerfile-kasm-tracelabs + - src/ubuntu/install/kali/** + - src/ubuntu/install/firefox/** + - src/ubuntu/install/tracelabs/** + - name: unityhub + runset: set-b + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-unityhub + changeFiles: + - dockerfile-kasm-unityhub + - src/ubuntu/install/misc/** + - src/ubuntu/install/chrome/** + - src/ubuntu/install/unityhub/** + - name: zoom + runset: set-a + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-zoom + changeFiles: + - dockerfile-kasm-zoom + - src/ubuntu/install/zoom/** + - src/ubuntu/install/chrome/** + - name: zsnes + runset: set-b + singleapp: true + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-zsnes + changeFiles: + - dockerfile-kasm-zsnes + - src/ubuntu/install/zsnes/** + - name: forensic-osint + runset: set-a + singleapp: false + base: core-ubuntu-jammy + dockerfile: dockerfile-kasm-forensic-osint + changeFiles: + - dockerfile-kasm-forensic-osint + - src/ubuntu/install/forensic_osint/** + - src/ubuntu/install/misc/** + - src/ubuntu/install/chrome/** + - src/ubuntu/install/vs_code/** + - src/ubuntu/install/only_office/** + - src/ubuntu/install/signal/** + - src/ubuntu/install/telegram/** + - src/ubuntu/install/thunderbird/** + - src/ubuntu/install/torbrowser/** + - src/ubuntu/install/cleanup/** diff --git a/ci-scripts/test.sh b/ci-scripts/test.sh new file mode 100755 index 000000000..85ac179c3 --- /dev/null +++ b/ci-scripts/test.sh @@ -0,0 +1,222 @@ +#!/bin/bash +set -e + +## Parse input ## +NAME=$1 +BASE=$2 +DOCKERFILE=$3 +ARCH=$4 +AWS_ID=$5 +AWS_KEY=$6 + +# Setup aws cli +export AWS_ACCESS_KEY_ID="${AWS_ID}" +export AWS_SECRET_ACCESS_KEY="${AWS_KEY}" +export AWS_DEFAULT_REGION=us-east-1 + +# Install tools for testing +apk add \ + aws-cli \ + curl \ + jq \ + openssh-client + +## Functions ## +# Ami locater +getami () { +aws ec2 describe-images --filters \ + "Name=name,Values=$1*" \ + "Name=owner-id,Values=$2" \ + "Name=state,Values=available" \ + "Name=architecture,Values=$3" \ + "Name=virtualization-type,Values=hvm" \ + "Name=root-device-type,Values=ebs" \ + "Name=image-type,Values=machine" \ + --query 'sort_by(Images, &CreationDate)[-1].[ImageId]' \ + --output 'text' \ + --region us-east-1 +} +# Make sure deployment is ready +function ready_check() { + while :; do + sleep 2 + CHECK=$(curl --max-time 5 -sLk https://${IPS[0]}/api/__healthcheck || :) + if [[ "${CHECK}" =~ .*"true".* ]]; then + echo "Workspaces at "${IPS[0]}" ready for testing" + break + else + echo "Waiting for Workspaces at "${IPS[0]}" to be ready" + fi + done + sleep 30 +} + +# Determine deployment based on arch +if [[ "${ARCH}" == "x86_64" ]]; then + AMI=$(getami "ubuntu/images/hvm-ssd/ubuntu-jammy-22.04" 099720109477 x86_64) + TYPE=c5.large + USER=ubuntu +else + AMI=$(getami "ubuntu/images/hvm-ssd/ubuntu-jammy-22.04" 099720109477 arm64) + TYPE=c6g.large + USER=ubuntu +fi + +# Setup SSH Key +mkdir -p /root/.ssh +RAND=$(head /dev/urandom | tr -dc 'a-z0-9' | head -c36) +SSH_KEY=$(aws ec2 create-key-pair --key-name ${RAND} | jq -r '.KeyMaterial') +cat >/root/.ssh/id_rsa </root/user-data < /tmp/instance.json +INSTANCE=$(cat /tmp/instance.json | jq -r " .Instances[0].InstanceId") +INSTANCES+=("${INSTANCE}") +for INSTANCE_ID in "${INSTANCES[@]}"; do + echo $INSTANCE_ID +done + +# Determine IPs of instances +IPS=() +for INSTANCE_ID in "${INSTANCES[@]}"; do + while :; do + sleep 2 + IP=$(aws ec2 describe-instances \ + --instance-id ${INSTANCE_ID} \ + | jq -r '.Reservations[0].Instances[0].PublicIpAddress') + if [ "${IP}" == 'null' ]; then + echo "Waiting for Pub IP from instance ${INSTANCE_ID}" + else + echo "Instance ${INSTANCE_ID} IP=${IP}" + IPS+=("${IP}") + break + fi + done +done + +# Shutdown Instances function and trap +function turnoff() { + for IP in "${IPS[@]}"; do + ssh \ + -oConnectTimeout=4 \ + -oStrictHostKeyChecking=no \ + ${USER}@${IP} \ + "sudo poweroff" || : + done + aws ec2 delete-key-pair --key-name ${RAND} +} +trap turnoff ERR + +# Make sure the instance is up +for IP in "${IPS[@]}"; do + while :; do + sleep 2 + UPTIME=$(ssh \ + -oConnectTimeout=4 \ + -oStrictHostKeyChecking=no \ + ${USER}@${IP} \ + 'uptime'|| :) + if [ -z "${UPTIME}" ]; then + echo "Waiting for ${IP} to be up" + else + echo "${IP} up ${UPTIME}" + break + fi + done +done + +# Sleep here to ensure subsequent connections don't fail +sleep 30 + +# Double check we are up +for IP in "${IPS[@]}"; do + while :; do + sleep 2 + UPTIME=$(ssh \ + -oConnectTimeout=4 \ + -oStrictHostKeyChecking=no \ + ${USER}@${IP} \ + 'uptime'|| :) + if [ -z "${UPTIME}" ]; then + echo "Waiting for ${IP} to be up" + else + echo "${IP} up ${UPTIME}" + break + fi + done +done + +# Copy over docker auth +for IP in "${IPS[@]}"; do + scp \ + -oStrictHostKeyChecking=no \ + /root/.docker/config.json \ + ${USER}@${IP}:/tmp/ + ssh \ + -oConnectTimeout=10 \ + -oStrictHostKeyChecking=no \ + ${USER}@${IP} \ + "sudo mkdir -p /root/.docker && sudo mv /tmp/config.json /root/.docker/ && sudo chown root:root /root/.docker/config.json" +done + +# Install Kasm workspaces +ssh \ + -oConnectTimeout=4 \ + -oStrictHostKeyChecking=no \ + ${USER}@"${IPS[0]}" \ + "curl -L -o /tmp/installer.tar.gz ${TEST_INSTALLER} && cd /tmp && tar xf installer.tar.gz && sudo bash kasm_release/install.sh -H -u -I -e -P ${RAND} -U ${RAND}" + +# Ensure install is up and running +ready_check + +# Pull tester image +docker pull ${ORG_NAME}/kasm-tester:1.18.0 + +# Run test +cp /root/.ssh/id_rsa $(dirname ${CI_PROJECT_DIR})/sshkey +chmod 777 $(dirname ${CI_PROJECT_DIR})/sshkey +docker run --rm \ + -e TZ=US/Pacific \ + -e KASM_HOST=${IPS[0]} \ + -e KASM_PORT=443 \ + -e KASM_PASSWORD="${RAND}" \ + -e SSH_USER=$USER \ + -e DOCKERUSER=$DOCKER_HUB_USERNAME \ + -e DOCKERPASS=$DOCKER_HUB_PASSWORD \ + -e TEST_IMAGE="${ORG_NAME}/image-cache-private:${ARCH}-${NAME}-${SANITIZED_BRANCH}-${CI_PIPELINE_ID}" \ + -e AWS_KEY=${KASM_TEST_AWS_KEY} \ + -e AWS_SECRET="${KASM_TEST_AWS_SECRET}" \ + -e SLACK_TOKEN=${SLACK_TOKEN} \ + -e S3_BUCKET=kasm-ci \ + -e COMMIT=${CI_COMMIT_SHA} \ + -e REPO=workspaces-images \ + -e AUTOMATED=true \ + -v $(dirname ${CI_PROJECT_DIR})/sshkey:/sshkey:ro ${SLIM_FLAG} \ + kasmweb/kasm-tester:1.18.0 + +# Shutdown Instances +turnoff + +# Exit 1 if test failed or file does not exist +STATUS=$(curl -sL https://kasm-ci.s3.amazonaws.com/${CI_COMMIT_SHA}/${ARCH}/kasmweb/image-cache-private/${ARCH}-${NAME}-${SANITIZED_BRANCH}-${CI_PIPELINE_ID}/ci-status.yml | awk -F'"' '{print $2}') +if [ ! "${STATUS}" == "PASS" ]; then + exit 1 +fi diff --git a/ci-scripts/weekly-manifest.sh b/ci-scripts/weekly-manifest.sh new file mode 100644 index 000000000..8c6cfaf83 --- /dev/null +++ b/ci-scripts/weekly-manifest.sh @@ -0,0 +1,62 @@ +#!/bin/bash + +REGISTRY_MIRRORS=("quay.io" "ghcr.io") +NAME=$1 +TYPE=$2 +BASE=$3 +APPS="kasm-apps" +SANITIZED_BRANCH_DAILY=${SANITIZED_BRANCH}-rolling-daily +SANITIZED_BRANCH=${SANITIZED_BRANCH}-rolling-weekly + +tagImage() { + docker pull "$1" + docker tag "$1" "$2" + docker push "$2" +} + +manifest() { + docker manifest push --purge "$1" || : + docker manifest create "$1" "$2":x86_64-"$3" "$2":aarch64-"$3" + docker manifest annotate "$1" "$2":aarch64-"$3" --os linux --arch arm64 --variant v8 + docker manifest push --purge "$1" +} + +# Manifest for multi pull and push for single arch +# Will pull the daily rolling images and retag them to weekly +if [[ "${TYPE}" == "multi" ]]; then + # Pulling and retagging daily image + tagImage "${ORG_NAME}/${NAME}:x86_64-${SANITIZED_BRANCH_DAILY}" "${ORG_NAME}/${NAME}:x86_64-${SANITIZED_BRANCH}" + tagImage "${ORG_NAME}/${NAME}:aarch64-${SANITIZED_BRANCH_DAILY}" "${ORG_NAME}/${NAME}:aarch64-${SANITIZED_BRANCH}" + + # Manifest tag + manifest "${ORG_NAME}/${NAME}:${SANITIZED_BRANCH}" "${ORG_NAME}/${NAME}" "${SANITIZED_BRANCH}" + + for MIRROR in "${REGISTRY_MIRRORS[@]}"; do + tagImage "${ORG_NAME}/${NAME}:x86_64-${SANITIZED_BRANCH_DAILY}" "${MIRROR}/${MIRROR_ORG_NAME}/${NAME}:x86_64-${SANITIZED_BRANCH}" + tagImage "${ORG_NAME}/${NAME}:aarch64-${SANITIZED_BRANCH_DAILY}" "${MIRROR}/${MIRROR_ORG_NAME}/${NAME}:aarch64-${SANITIZED_BRANCH}" + + manifest "${MIRROR}/${MIRROR_ORG_NAME}/${NAME}:${SANITIZED_BRANCH}" "${MIRROR}/${MIRROR_ORG_NAME}/${NAME}" "${SANITIZED_BRANCH}" + done + + # Single App Layer Images + # Disabling Single App Layer due to functionality not being used currently + # if [ ! -z "${BASE}" ];then + # tagImage "${ORG_NAME}/${APPS}:x86_64-${BASE}-${NAME}-${SANITIZED_BRANCH_DAILY}" "${ORG_NAME}/${APPS}:x86_64-${BASE}-${NAME}-${SANITIZED_BRANCH}" + # tagImage "${ORG_NAME}/${APPS}:aarch64-${BASE}-${NAME}-${SANITIZED_BRANCH_DAILY}" "${ORG_NAME}/${APPS}:aarch64-${BASE}-${NAME}-${SANITIZED_BRANCH}" + + # manifest "${ORG_NAME}/${APPS}:${BASE}-${NAME}-${SANITIZED_BRANCH}" "${ORG_NAME}/${APPS}" "${BASE}-${NAME}-${SANITIZED_BRANCH}" + # fi +# Single arch image just pull and push +else + tagImage "${ORG_NAME}/${NAME}:${SANITIZED_BRANCH_DAILY}" "${ORG_NAME}/${NAME}:${SANITIZED_BRANCH}" + + for MIRROR in "${REGISTRY_MIRRORS[@]}"; do + tagImage "${ORG_NAME}/${NAME}:${SANITIZED_BRANCH_DAILY}" "${MIRROR}/${MIRROR_ORG_NAME}/${NAME}:${SANITIZED_BRANCH}" + done + + # Single App Layer Images + # Disabling Single App Layer due to functionality not being used currently + # if [ ! -z "${BASE}" ];then + # tagImage "${ORG_NAME}/${APPS}:${BASE}-${NAME}-${SANITIZED_BRANCH_DAILY}" "${ORG_NAME}/${APPS}:x86_64-${BASE}-${NAME}-${SANITIZED_BRANCH}" + # fi +fi \ No newline at end of file diff --git a/coeadapt-launcher/.gitignore b/coeadapt-launcher/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/coeadapt-launcher/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/coeadapt-launcher/.vscode/extensions.json b/coeadapt-launcher/.vscode/extensions.json new file mode 100644 index 000000000..24d7cc6de --- /dev/null +++ b/coeadapt-launcher/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"] +} diff --git a/coeadapt-launcher/README.md b/coeadapt-launcher/README.md new file mode 100644 index 000000000..a6ebbfa3c --- /dev/null +++ b/coeadapt-launcher/README.md @@ -0,0 +1,231 @@ +# Coeadapt Launcher + +Cross-platform desktop app that manages the Coeadapt career workspace. Built with Tauri v2 + React + TypeScript. + +> This is a subproject of [Career-Box](../README.md). See the root README for the full project overview. + +## What It Does + +- Detects Docker/Podman and guides non-technical users through setup +- Pulls and manages a Kasm Workspaces container (`coeadapt/workspace:latest`) +- Runs an MCP server (port 3100) so Claude can interact with the workspace +- Auto-configures Claude Desktop for one-click AI connection +- Lives in the system tray with start/stop/open controls + +## Architecture + +``` +Coeadapt Tauri App (system tray + window) +├── React UI (Vite + Tailwind v4) +│ ├── Setup wizard (onboarding) +│ ├── Dashboard (status + controls) +│ ├── Claude Setup (AI connection) +│ └── Settings (workspace, AI, account) +├── Rust Backend (Tauri commands) +│ ├── Docker/Podman detection +│ ├── Container lifecycle (pull/create/start/stop) +│ ├── Disk space monitoring +│ ├── Health checks (workspace + MCP) +│ └── Claude Desktop config injection +└── MCP Server (Node.js sidecar, port 3100) + ├── workspace_status + ├── run_command + ├── read_file / write_file / list_files + ├── take_screenshot + ├── open_application + └── get_user_progress +``` + +## Prerequisites + +- [Bun](https://bun.sh/) (package manager + bundler) +- [Rust](https://rustup.rs/) (for Tauri backend) +- [Docker Desktop](https://www.docker.com/products/docker-desktop/) or [Podman Desktop](https://podman-desktop.io/) + +## Development + +```bash +# Install frontend dependencies +bun install + +# Install MCP server dependencies +cd mcp-server && bun install && cd .. + +# Run in dev mode (Vite HMR + Tauri window) +bun run tauri dev +``` + +## Building + +```bash +# 1. Build MCP sidecar binary +cd mcp-server && bun run build && cd .. + +# 2. Build the Tauri app (produces platform installers) +bun run tauri build +``` + +### Build Outputs + +| Platform | Artifacts | +|----------|-----------| +| Windows | `.msi` installer, `.exe` (NSIS) | +| macOS | `.dmg` | +| Linux | `.AppImage`, `.deb` | + +## Project Structure + +``` +coeadapt-launcher/ +├── src/ # React frontend +│ ├── pages/ +│ │ ├── Setup.tsx # Multi-step onboarding wizard +│ │ ├── Dashboard.tsx # Workspace status + controls +│ │ ├── ClaudeSetup.tsx # Claude Desktop connection flow +│ │ └── Settings.tsx # Tabbed settings (Account, AI, Workspace, General) +│ ├── components/ +│ │ ├── DiskWarningBanner.tsx # Persistent low-space warning (<5GB) +│ │ ├── DiskUsage.tsx # Disk space progress bar +│ │ ├── ProgressBar.tsx # Image pull progress (indeterminate support) +│ │ ├── Spinner.tsx # Loading spinner (sm/md/lg) +│ │ ├── StatusIndicator.tsx # Colored dot + label +│ │ └── WorkspaceControls.tsx # Context-aware Start/Stop/Open buttons +│ ├── hooks/ +│ │ ├── useDocker.ts # Docker/Podman runtime detection +│ │ ├── useContainer.ts # Container lifecycle + status polling +│ │ ├── useClaudeConnection.ts # Claude Desktop detection + MCP health +│ │ └── useDiskSpace.ts # Disk monitoring + warning events +│ └── lib/ +│ ├── tauri.ts # 17 Tauri command wrappers +│ ├── types.ts # TypeScript interfaces (mirrors Rust structs) +│ └── constants.ts # User-facing strings (no jargon) +├── src-tauri/ # Rust backend +│ ├── src/ +│ │ ├── main.rs # Entry point +│ │ ├── lib.rs # App setup, tray, plugins, event loops +│ │ ├── state.rs # Serializable structs + constants +│ │ ├── commands.rs # 17 Tauri command handlers +│ │ ├── docker.rs # Docker CLI wrapper + streaming pull +│ │ ├── container.rs # Container create/start/stop/remove +│ │ ├── disk.rs # Disk space checks (sysinfo crate) +│ │ ├── health.rs # Workspace + MCP health polling +│ │ └── claude.rs # Claude Desktop config detection + injection +│ ├── Cargo.toml # Rust dependencies +│ ├── tauri.conf.json # App config, window, CSP, updater +│ └── capabilities/default.json # Tauri security permissions +└── mcp-server/ # Node.js MCP server (sidecar) + ├── src/ + │ ├── index.ts # HTTP server + MCP transport setup + │ ├── docker-exec.ts # docker exec wrapper (30s timeout) + │ └── tools/ # MCP tool implementations + │ ├── workspace.ts # workspace_status + │ ├── commands.ts # run_command + │ ├── filesystem.ts # read_file, write_file, list_files + │ ├── screenshot.ts # take_screenshot + │ ├── applications.ts # open_application + │ └── progress.ts # get_user_progress + ├── build.ts # Bun compile to sidecar binary + ├── package.json + └── tsconfig.json +``` + +## Tech Stack + +| Layer | Technology | +|-------|-----------| +| Framework | Tauri v2 (stable) | +| Frontend | React 19, TypeScript 5.8, Vite 7 | +| Styling | Tailwind CSS v4 (navy + coral theme) | +| Backend | Rust (2021 edition) | +| MCP Server | `@modelcontextprotocol/sdk` v1.12, Zod | +| HTTP Client | reqwest 0.12 (rustls-tls) | +| System Info | sysinfo 0.34 | + +### Tauri Plugins + +- `tauri-plugin-opener` - Open URLs in default browser +- `tauri-plugin-shell` - Execute shell commands +- `tauri-plugin-store` - Persistent key-value storage +- `tauri-plugin-updater` - Auto-update support +- `tauri-plugin-autostart` - Launch on system boot + +## Container Configuration + +| Setting | Value | +|---------|-------| +| Container name | `coeadapt-workspace` | +| Image | `coeadapt/workspace:latest` | +| Data volume | `coeadapt-data` (mounted at `/home/kasm-user`) | +| Workspace port | `6901` (KasmVNC) | +| MCP server port | `3100` | +| Shared memory | `512MB` | +| Restart policy | `unless-stopped` | + +## Disk Space Requirements + +| Threshold | Value | Behavior | +|-----------|-------|----------| +| Minimum | 15 GB free | Blocks setup | +| Recommended | 25 GB free | Warning shown | +| Low space | < 5 GB free | Persistent banner | + +## MCP Server + +The MCP server bridges Claude to the workspace container via `docker exec`. It exposes 8 tools: + +| Tool | Description | +|------|-------------| +| `workspace_status` | Check if workspace is running | +| `run_command` | Execute shell commands in the workspace | +| `read_file` | Read file contents from workspace | +| `write_file` | Write content to a file in workspace | +| `list_files` | List directory contents | +| `take_screenshot` | Capture desktop screenshot | +| `open_application` | Launch apps (firefox, terminal, vscode, etc.) | +| `get_user_progress` | Read career progress data | + +### Endpoints + +- `http://127.0.0.1:3100/mcp` - MCP Streamable HTTP transport +- `http://127.0.0.1:3100/health` - Health check (status, lastToolCall, uptime) + +## Claude Desktop Integration + +The app auto-detects Claude Desktop and injects MCP config into `claude_desktop_config.json`: + +```json +{ + "mcpServers": { + "coeadapt": { + "command": "npx", + "args": ["mcp-remote", "http://localhost:3100/mcp"], + "env": {} + } + } +} +``` + +Config file locations: +- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json` +- **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json` +- **Linux:** `~/.config/Claude/claude_desktop_config.json` + +A backup (`.json.bak`) is created before the first modification. + +## Current Status + +### Complete +- Docker/Podman detection and daemon monitoring +- Full container lifecycle (pull with streaming progress, create, start, stop, reset) +- Disk space monitoring with thresholds and warning banner +- Multi-step setup wizard with auto-advance +- Dashboard with workspace status, controls, and disk usage +- Claude Desktop auto-detection and config injection +- MCP server with all 8 tools +- System tray (start/stop/open/show/quit) +- Hide-to-tray on window close +- Settings page (AI Connection + Workspace tabs functional) + +### Roadmap + +See [GitHub Issues](https://github.com/coeadapt/Career-Box/issues) for planned work and known issues. diff --git a/coeadapt-launcher/bun.lock b/coeadapt-launcher/bun.lock new file mode 100644 index 000000000..6d1453b3a --- /dev/null +++ b/coeadapt-launcher/bun.lock @@ -0,0 +1,391 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "coeadapt-launcher", + "dependencies": { + "@tauri-apps/api": "^2", + "@tauri-apps/plugin-autostart": "^2.5.1", + "@tauri-apps/plugin-opener": "^2", + "@tauri-apps/plugin-shell": "^2.3.5", + "@tauri-apps/plugin-store": "^2.4.2", + "@tauri-apps/plugin-updater": "^2.10.0", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-router-dom": "^7.13.0", + }, + "devDependencies": { + "@tailwindcss/vite": "^4.1.18", + "@tauri-apps/cli": "^2", + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", + "@vitejs/plugin-react": "^4.6.0", + "tailwindcss": "^4.1.18", + "typescript": "~5.8.3", + "vite": "^7.0.4", + }, + }, + }, + "packages": { + "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], + + "@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="], + + "@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="], + + "@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="], + + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="], + + "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], + + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="], + + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="], + + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], + + "@babel/helpers": ["@babel/helpers@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="], + + "@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="], + + "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="], + + "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="], + + "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + + "@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="], + + "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.57.1", "", { "os": "android", "cpu": "arm" }, "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.57.1", "", { "os": "android", "cpu": "arm64" }, "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.57.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.57.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.57.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.57.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.57.1", "", { "os": "linux", "cpu": "arm" }, "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.57.1", "", { "os": "linux", "cpu": "arm" }, "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.57.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.57.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q=="], + + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA=="], + + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw=="], + + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.57.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w=="], + + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.57.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.57.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.57.1", "", { "os": "linux", "cpu": "x64" }, "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.57.1", "", { "os": "linux", "cpu": "x64" }, "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw=="], + + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.57.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw=="], + + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.57.1", "", { "os": "none", "cpu": "arm64" }, "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.57.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.57.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew=="], + + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA=="], + + "@tailwindcss/node": ["@tailwindcss/node@4.1.18", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.18" } }, "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ=="], + + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.18", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.18", "@tailwindcss/oxide-darwin-arm64": "4.1.18", "@tailwindcss/oxide-darwin-x64": "4.1.18", "@tailwindcss/oxide-freebsd-x64": "4.1.18", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", "@tailwindcss/oxide-linux-x64-musl": "4.1.18", "@tailwindcss/oxide-wasm32-wasi": "4.1.18", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" } }, "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A=="], + + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.18", "", { "os": "android", "cpu": "arm64" }, "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q=="], + + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.18", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A=="], + + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.18", "", { "os": "darwin", "cpu": "x64" }, "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw=="], + + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.18", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA=="], + + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18", "", { "os": "linux", "cpu": "arm" }, "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA=="], + + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw=="], + + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg=="], + + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.18", "", { "os": "linux", "cpu": "x64" }, "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g=="], + + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.18", "", { "os": "linux", "cpu": "x64" }, "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ=="], + + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.18", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.0", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA=="], + + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.18", "", { "os": "win32", "cpu": "arm64" }, "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA=="], + + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.18", "", { "os": "win32", "cpu": "x64" }, "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q=="], + + "@tailwindcss/vite": ["@tailwindcss/vite@4.1.18", "", { "dependencies": { "@tailwindcss/node": "4.1.18", "@tailwindcss/oxide": "4.1.18", "tailwindcss": "4.1.18" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA=="], + + "@tauri-apps/api": ["@tauri-apps/api@2.10.1", "", {}, "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw=="], + + "@tauri-apps/cli": ["@tauri-apps/cli@2.10.0", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.10.0", "@tauri-apps/cli-darwin-x64": "2.10.0", "@tauri-apps/cli-linux-arm-gnueabihf": "2.10.0", "@tauri-apps/cli-linux-arm64-gnu": "2.10.0", "@tauri-apps/cli-linux-arm64-musl": "2.10.0", "@tauri-apps/cli-linux-riscv64-gnu": "2.10.0", "@tauri-apps/cli-linux-x64-gnu": "2.10.0", "@tauri-apps/cli-linux-x64-musl": "2.10.0", "@tauri-apps/cli-win32-arm64-msvc": "2.10.0", "@tauri-apps/cli-win32-ia32-msvc": "2.10.0", "@tauri-apps/cli-win32-x64-msvc": "2.10.0" }, "bin": { "tauri": "tauri.js" } }, "sha512-ZwT0T+7bw4+DPCSWzmviwq5XbXlM0cNoleDKOYPFYqcZqeKY31KlpoMW/MOON/tOFBPgi31a2v3w9gliqwL2+Q=="], + + "@tauri-apps/cli-darwin-arm64": ["@tauri-apps/cli-darwin-arm64@2.10.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-avqHD4HRjrMamE/7R/kzJPcAJnZs0IIS+1nkDP5b+TNBn3py7N2aIo9LIpy+VQq0AkN8G5dDpZtOOBkmWt/zjA=="], + + "@tauri-apps/cli-darwin-x64": ["@tauri-apps/cli-darwin-x64@2.10.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-keDmlvJRStzVFjZTd0xYkBONLtgBC9eMTpmXnBXzsHuawV2q9PvDo2x6D5mhuoMVrJ9QWjgaPKBBCFks4dK71Q=="], + + "@tauri-apps/cli-linux-arm-gnueabihf": ["@tauri-apps/cli-linux-arm-gnueabihf@2.10.0", "", { "os": "linux", "cpu": "arm" }, "sha512-e5u0VfLZsMAC9iHaOEANumgl6lfnJx0Dtjkd8IJpysZ8jp0tJ6wrIkto2OzQgzcYyRCKgX72aKE0PFgZputA8g=="], + + "@tauri-apps/cli-linux-arm64-gnu": ["@tauri-apps/cli-linux-arm64-gnu@2.10.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-YrYYk2dfmBs5m+OIMCrb+JH/oo+4FtlpcrTCgiFYc7vcs6m3QDd1TTyWu0u01ewsCtK2kOdluhr/zKku+KP7HA=="], + + "@tauri-apps/cli-linux-arm64-musl": ["@tauri-apps/cli-linux-arm64-musl@2.10.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-GUoPdVJmrJRIXFfW3Rkt+eGK9ygOdyISACZfC/bCSfOnGt8kNdQIQr5WRH9QUaTVFIwxMlQyV3m+yXYP+xhSVA=="], + + "@tauri-apps/cli-linux-riscv64-gnu": ["@tauri-apps/cli-linux-riscv64-gnu@2.10.0", "", { "os": "linux", "cpu": "none" }, "sha512-JO7s3TlSxshwsoKNCDkyvsx5gw2QAs/Y2GbR5UE2d5kkU138ATKoPOtxn8G1fFT1aDW4LH0rYAAfBpGkDyJJnw=="], + + "@tauri-apps/cli-linux-x64-gnu": ["@tauri-apps/cli-linux-x64-gnu@2.10.0", "", { "os": "linux", "cpu": "x64" }, "sha512-Uvh4SUUp4A6DVRSMWjelww0GnZI3PlVy7VS+DRF5napKuIehVjGl9XD0uKoCoxwAQBLctvipyEK+pDXpJeoHng=="], + + "@tauri-apps/cli-linux-x64-musl": ["@tauri-apps/cli-linux-x64-musl@2.10.0", "", { "os": "linux", "cpu": "x64" }, "sha512-AP0KRK6bJuTpQ8kMNWvhIpKUkQJfcPFeba7QshOQZjJ8wOS6emwTN4K5g/d3AbCMo0RRdnZWwu67MlmtJyxC1Q=="], + + "@tauri-apps/cli-win32-arm64-msvc": ["@tauri-apps/cli-win32-arm64-msvc@2.10.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-97DXVU3dJystrq7W41IX+82JEorLNY+3+ECYxvXWqkq7DBN6FsA08x/EFGE8N/b0LTOui9X2dvpGGoeZKKV08g=="], + + "@tauri-apps/cli-win32-ia32-msvc": ["@tauri-apps/cli-win32-ia32-msvc@2.10.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-EHyQ1iwrWy1CwMalEm9z2a6L5isQ121pe7FcA2xe4VWMJp+GHSDDGvbTv/OPdkt2Lyr7DAZBpZHM6nvlHXEc4A=="], + + "@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.10.0", "", { "os": "win32", "cpu": "x64" }, "sha512-NTpyQxkpzGmU6ceWBTY2xRIEaS0ZLbVx1HE1zTA3TY/pV3+cPoPPOs+7YScr4IMzXMtOw7tLw5LEXo5oIG3qaQ=="], + + "@tauri-apps/plugin-autostart": ["@tauri-apps/plugin-autostart@2.5.1", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-zS/xx7yzveCcotkA+8TqkI2lysmG2wvQXv2HGAVExITmnFfHAdj1arGsbbfs3o6EktRHf6l34pJxc3YGG2mg7w=="], + + "@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.5.3", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-CCcUltXMOfUEArbf3db3kCE7Ggy1ExBEBl51Ko2ODJ6GDYHRp1nSNlQm5uNCFY5k7/ufaK5Ib3Du/Zir19IYQQ=="], + + "@tauri-apps/plugin-shell": ["@tauri-apps/plugin-shell@2.3.5", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-jewtULhiQ7lI7+owCKAjc8tYLJr92U16bPOeAa472LHJdgaibLP83NcfAF2e+wkEcA53FxKQAZ7byDzs2eeizg=="], + + "@tauri-apps/plugin-store": ["@tauri-apps/plugin-store@2.4.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-0ClHS50Oq9HEvLPhNzTNFxbWVOqoAp3dRvtewQBeqfIQ0z5m3JRnOISIn2ZVPCrQC0MyGyhTS9DWhHjpigQE7A=="], + + "@tauri-apps/plugin-updater": ["@tauri-apps/plugin-updater@2.10.0", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-ljN8jPlnT0aSn8ecYhuBib84alxfMx6Hc8vJSKMJyzGbTPFZAC44T2I1QNFZssgWKrAlofvJqCC6Rr472JWfkQ=="], + + "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], + + "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], + + "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], + + "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], + + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], + + "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], + + "@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="], + + "baseline-browser-mapping": ["baseline-browser-mapping@2.9.19", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg=="], + + "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001769", "", {}, "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg=="], + + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], + + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.286", "", {}, "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A=="], + + "enhanced-resolve": ["enhanced-resolve@5.19.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg=="], + + "esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="], + + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + + "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + + "react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], + + "react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], + + "react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="], + + "react-router": ["react-router@7.13.0", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw=="], + + "react-router-dom": ["react-router-dom@7.13.0", "", { "dependencies": { "react-router": "7.13.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g=="], + + "rollup": ["rollup@4.57.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.1", "@rollup/rollup-android-arm64": "4.57.1", "@rollup/rollup-darwin-arm64": "4.57.1", "@rollup/rollup-darwin-x64": "4.57.1", "@rollup/rollup-freebsd-arm64": "4.57.1", "@rollup/rollup-freebsd-x64": "4.57.1", "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", "@rollup/rollup-linux-arm-musleabihf": "4.57.1", "@rollup/rollup-linux-arm64-gnu": "4.57.1", "@rollup/rollup-linux-arm64-musl": "4.57.1", "@rollup/rollup-linux-loong64-gnu": "4.57.1", "@rollup/rollup-linux-loong64-musl": "4.57.1", "@rollup/rollup-linux-ppc64-gnu": "4.57.1", "@rollup/rollup-linux-ppc64-musl": "4.57.1", "@rollup/rollup-linux-riscv64-gnu": "4.57.1", "@rollup/rollup-linux-riscv64-musl": "4.57.1", "@rollup/rollup-linux-s390x-gnu": "4.57.1", "@rollup/rollup-linux-x64-gnu": "4.57.1", "@rollup/rollup-linux-x64-musl": "4.57.1", "@rollup/rollup-openbsd-x64": "4.57.1", "@rollup/rollup-openharmony-arm64": "4.57.1", "@rollup/rollup-win32-arm64-msvc": "4.57.1", "@rollup/rollup-win32-ia32-msvc": "4.57.1", "@rollup/rollup-win32-x64-gnu": "4.57.1", "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A=="], + + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], + + "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "tailwindcss": ["tailwindcss@4.1.18", "", {}, "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw=="], + + "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], + + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + + "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], + + "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], + + "vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="], + + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], + + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="], + + "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + + "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + } +} diff --git a/coeadapt-launcher/index.html b/coeadapt-launcher/index.html new file mode 100644 index 000000000..791e8ca50 --- /dev/null +++ b/coeadapt-launcher/index.html @@ -0,0 +1,16 @@ + + + + + + + Coeadapt + + + + + +
+ + + diff --git a/coeadapt-launcher/mcp-server/build.ts b/coeadapt-launcher/mcp-server/build.ts new file mode 100644 index 000000000..f0b9fe8e2 --- /dev/null +++ b/coeadapt-launcher/mcp-server/build.ts @@ -0,0 +1,47 @@ +import { execSync } from "node:child_process"; +import { mkdirSync, existsSync } from "node:fs"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const outDir = join(__dirname, "..", "src-tauri", "binaries"); + +// Determine target triple for Tauri sidecar naming +function getTargetTriple(): string { + const platform = process.platform; + const arch = process.arch; + + if (platform === "win32") { + return arch === "arm64" + ? "aarch64-pc-windows-msvc" + : "x86_64-pc-windows-msvc"; + } + if (platform === "darwin") { + return arch === "arm64" + ? "aarch64-apple-darwin" + : "x86_64-apple-darwin"; + } + // Linux + return arch === "arm64" + ? "aarch64-unknown-linux-gnu" + : "x86_64-unknown-linux-gnu"; +} + +const triple = getTargetTriple(); +const ext = process.platform === "win32" ? ".exe" : ""; +const outFile = join(outDir, `coeadapt-mcp-${triple}${ext}`); + +// Ensure output directory exists +if (!existsSync(outDir)) { + mkdirSync(outDir, { recursive: true }); +} + +console.log(`Building MCP server sidecar for ${triple}...`); +console.log(`Output: ${outFile}`); + +execSync( + `bun build src/index.ts --compile --outfile "${outFile}"`, + { cwd: __dirname, stdio: "inherit" }, +); + +console.log("Build complete!"); diff --git a/coeadapt-launcher/mcp-server/bun.lock b/coeadapt-launcher/mcp-server/bun.lock new file mode 100644 index 000000000..6f21ae691 --- /dev/null +++ b/coeadapt-launcher/mcp-server/bun.lock @@ -0,0 +1,269 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "coeadapt-mcp", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.12.0", + "zod": "^3.24.0", + }, + "devDependencies": { + "@types/node": "^22.0.0", + "tsx": "^4.19.0", + "typescript": "^5.8.0", + }, + }, + }, + "packages": { + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="], + + "@hono/node-server": ["@hono/node-server@1.19.9", "", { "peerDependencies": { "hono": "^4" } }, "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw=="], + + "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.26.0", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg=="], + + "@types/node": ["@types/node@22.19.11", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w=="], + + "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], + + "ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], + + "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], + + "body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="], + + "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], + + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], + + "content-disposition": ["content-disposition@1.0.1", "", {}, "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="], + + "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], + + "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], + + "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], + + "cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], + + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + + "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], + + "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], + + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + + "esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="], + + "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], + + "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], + + "eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="], + + "eventsource-parser": ["eventsource-parser@3.0.6", "", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="], + + "express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], + + "express-rate-limit": ["express-rate-limit@8.2.1", "", { "dependencies": { "ip-address": "10.0.1" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], + + "finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="], + + "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], + + "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + + "get-tsconfig": ["get-tsconfig@4.13.6", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw=="], + + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + + "hono": ["hono@4.11.9", "", {}, "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ=="], + + "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], + + "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "ip-address": ["ip-address@10.0.1", "", {}, "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA=="], + + "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], + + "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="], + + "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], + + "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="], + + "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + + "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], + + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], + + "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], + + "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], + + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + + "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="], + + "pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="], + + "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], + + "qs": ["qs@6.14.2", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q=="], + + "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], + + "raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="], + + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], + + "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="], + + "serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="], + + "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], + + "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], + + "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], + + "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], + + "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], + + "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], + + "tsx": ["tsx@4.21.0", "", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="], + + "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], + + "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + + "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + + "zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="], + } +} diff --git a/coeadapt-launcher/mcp-server/package.json b/coeadapt-launcher/mcp-server/package.json new file mode 100644 index 000000000..c4e5c4fa6 --- /dev/null +++ b/coeadapt-launcher/mcp-server/package.json @@ -0,0 +1,20 @@ +{ + "name": "coeadapt-mcp", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "tsx src/index.ts", + "build": "tsx build.ts", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.12.0", + "zod": "^3.24.0" + }, + "devDependencies": { + "typescript": "^5.8.0", + "tsx": "^4.19.0", + "@types/node": "^22.0.0" + } +} diff --git a/coeadapt-launcher/mcp-server/src/docker-exec.ts b/coeadapt-launcher/mcp-server/src/docker-exec.ts new file mode 100644 index 000000000..909ba4102 --- /dev/null +++ b/coeadapt-launcher/mcp-server/src/docker-exec.ts @@ -0,0 +1,29 @@ +import { execFile } from "node:child_process"; +import { promisify } from "node:util"; + +const execFileAsync = promisify(execFile); +const CONTAINER_NAME = "coeadapt-workspace"; + +export async function dockerExec( + command: string, +): Promise<{ stdout: string; stderr: string }> { + return execFileAsync( + "docker", + ["exec", CONTAINER_NAME, "bash", "-c", command], + { timeout: 30000, maxBuffer: 10 * 1024 * 1024 }, + ); +} + +export async function isContainerRunning(): Promise { + try { + const { stdout } = await execFileAsync("docker", [ + "inspect", + "-f", + "{{.State.Running}}", + CONTAINER_NAME, + ]); + return stdout.trim() === "true"; + } catch { + return false; + } +} diff --git a/coeadapt-launcher/mcp-server/src/index.ts b/coeadapt-launcher/mcp-server/src/index.ts new file mode 100644 index 000000000..91420f6d9 --- /dev/null +++ b/coeadapt-launcher/mcp-server/src/index.ts @@ -0,0 +1,144 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { createServer } from "node:http"; + +import { registerWorkspaceStatus } from "./tools/workspace.js"; +import { registerFilesystemTools } from "./tools/filesystem.js"; +import { registerRunCommand } from "./tools/commands.js"; +import { registerScreenshot } from "./tools/screenshot.js"; +import { registerOpenApplication } from "./tools/applications.js"; +import { registerGetProgress } from "./tools/progress.js"; +import { registerComputerUseTools } from "./tools/computer-use.js"; +import { dockerExec } from "./docker-exec.js"; + +const PORT = 3100; +const HOST = "127.0.0.1"; + +let lastToolCall = Date.now(); + +function onToolCall() { + lastToolCall = Date.now(); +} + +// Create MCP server +const server = new McpServer({ + name: "coeadapt", + version: "0.1.0", +}); + +// Register all tools +registerWorkspaceStatus(server, onToolCall); +registerFilesystemTools(server, onToolCall); +registerRunCommand(server, onToolCall); +registerScreenshot(server, onToolCall); +registerOpenApplication(server, onToolCall); +registerGetProgress(server, onToolCall); +registerComputerUseTools(server, onToolCall); + +// Create HTTP server with Streamable HTTP transport +const httpServer = createServer(async (req, res) => { + const url = new URL(req.url ?? "/", `http://${HOST}:${PORT}`); + + // Health endpoint + if (url.pathname === "/health") { + res.writeHead(200, { "Content-Type": "application/json" }); + res.end( + JSON.stringify({ + status: "ok", + lastToolCall, + uptime: process.uptime(), + }), + ); + return; + } + + // Progress summary proxy — fetches from in-VM progress tracker for the dashboard UI + if (url.pathname === "/progress-summary") { + try { + const { stdout } = await dockerExec( + "curl -sf -m 3 http://127.0.0.1:7700/progress/summary 2>/dev/null || " + + 'echo \'{"progress_percent":0,"streak_days":0,"total_activities":0,"total_goals":0,"completed_goals":0,"total_skills":0,"total_milestones":0,"last_activity_at":null}\'', + ); + res.writeHead(200, { + "Content-Type": "application/json", + "Access-Control-Allow-Origin": "*", + }); + res.end(stdout); + } catch { + res.writeHead(200, { + "Content-Type": "application/json", + "Access-Control-Allow-Origin": "*", + }); + res.end(JSON.stringify({ + progress_percent: 0, + streak_days: 0, + total_activities: 0, + total_goals: 0, + completed_goals: 0, + total_skills: 0, + total_milestones: 0, + last_activity_at: null, + })); + } + return; + } + + // Agent health proxy — reports status of in-VM services + if (url.pathname === "/agent-health") { + let progressOk = false; + let computerOk = false; + try { + const { stdout } = await dockerExec( + "curl -sf -m 2 http://127.0.0.1:7700/health >/dev/null 2>&1 && echo ok || echo down", + ); + progressOk = stdout.trim() === "ok"; + } catch {} + try { + const { stdout } = await dockerExec( + "curl -sf -m 2 http://127.0.0.1:7701/health >/dev/null 2>&1 && echo ok || echo down", + ); + computerOk = stdout.trim() === "ok"; + } catch {} + res.writeHead(200, { + "Content-Type": "application/json", + "Access-Control-Allow-Origin": "*", + }); + res.end(JSON.stringify({ + progress_tracker: progressOk ? "ok" : "down", + computer_use: computerOk ? "ok" : "down", + })); + return; + } + + // MCP endpoint + if (url.pathname === "/mcp") { + const transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: undefined, + }); + await server.connect(transport); + await transport.handleRequest(req, res); + return; + } + + // 404 for everything else + res.writeHead(404, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: "Not found" })); +}); + +httpServer.listen(PORT, HOST, () => { + console.log(`Coeadapt MCP server listening on http://${HOST}:${PORT}`); + console.log(` MCP endpoint: http://${HOST}:${PORT}/mcp`); + console.log(` Health check: http://${HOST}:${PORT}/health`); +}); + +// Graceful shutdown +process.on("SIGINT", () => { + console.log("Shutting down MCP server..."); + httpServer.close(); + process.exit(0); +}); + +process.on("SIGTERM", () => { + httpServer.close(); + process.exit(0); +}); diff --git a/coeadapt-launcher/mcp-server/src/tools/applications.ts b/coeadapt-launcher/mcp-server/src/tools/applications.ts new file mode 100644 index 000000000..055dc28c1 --- /dev/null +++ b/coeadapt-launcher/mcp-server/src/tools/applications.ts @@ -0,0 +1,48 @@ +import { z } from "zod"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { dockerExec } from "../docker-exec.js"; + +const APP_MAP: Record = { + firefox: "firefox", + chrome: "google-chrome", + "google-chrome": "google-chrome", + terminal: "xfce4-terminal", + "file-manager": "thunar", + "text-editor": "xfce4-terminal -e nano", + vscode: "code", + "vs-code": "code", + gimp: "gimp", + thunderbird: "thunderbird", + onlyoffice: "onlyoffice-desktopeditors", + vlc: "vlc", +}; + +export function registerOpenApplication( + server: McpServer, + onToolCall: () => void, +) { + server.tool( + "open_application", + "Launch an application in the workspace", + { app_name: z.string().describe("Application name (e.g., firefox, chrome, terminal, vscode)") }, + async ({ app_name }) => { + onToolCall(); + const cmd = APP_MAP[app_name.toLowerCase()] || app_name; + try { + await dockerExec(`DISPLAY=:1 nohup ${cmd} > /dev/null 2>&1 &`); + return { + content: [ + { type: "text" as const, text: `Launched ${app_name}` }, + ], + }; + } catch (err: any) { + return { + content: [ + { type: "text" as const, text: `Error launching ${app_name}: ${err.stderr || err.message}` }, + ], + isError: true, + }; + } + }, + ); +} diff --git a/coeadapt-launcher/mcp-server/src/tools/commands.ts b/coeadapt-launcher/mcp-server/src/tools/commands.ts new file mode 100644 index 000000000..054e4ffd2 --- /dev/null +++ b/coeadapt-launcher/mcp-server/src/tools/commands.ts @@ -0,0 +1,32 @@ +import { z } from "zod"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { dockerExec } from "../docker-exec.js"; + +export function registerRunCommand( + server: McpServer, + onToolCall: () => void, +) { + server.tool( + "run_command", + "Execute a shell command inside the workspace", + { command: z.string().describe("Shell command to execute") }, + async ({ command }) => { + onToolCall(); + try { + const { stdout, stderr } = await dockerExec(command); + const output = stdout + (stderr ? `\nSTDERR: ${stderr}` : ""); + return { content: [{ type: "text" as const, text: output }] }; + } catch (err: any) { + return { + content: [ + { + type: "text" as const, + text: `Error (exit ${err.code}): ${err.stderr || err.message}`, + }, + ], + isError: true, + }; + } + }, + ); +} diff --git a/coeadapt-launcher/mcp-server/src/tools/computer-use.ts b/coeadapt-launcher/mcp-server/src/tools/computer-use.ts new file mode 100644 index 000000000..dde583352 --- /dev/null +++ b/coeadapt-launcher/mcp-server/src/tools/computer-use.ts @@ -0,0 +1,398 @@ +import { z } from "zod"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { dockerExec } from "../docker-exec.js"; + +/** + * Helper to call the in-VM computer-use HTTP service. + * Falls back to direct xdotool commands if the service is unavailable. + */ +async function curlAgent( + method: "GET" | "POST", + path: string, + body?: Record, +): Promise { + const curlArgs = [ + "curl", "-sf", "-m", "10", + "-H", "Content-Type: application/json", + ]; + if (method === "POST" && body) { + curlArgs.push("-X", "POST", "-d", JSON.stringify(body)); + } + curlArgs.push(`http://127.0.0.1:7701${path}`); + const { stdout } = await dockerExec(curlArgs.join(" ")); + return stdout; +} + +export function registerComputerUseTools( + server: McpServer, + onToolCall: () => void, +) { + // ----------------------------------------------------------------------- + // Screenshot + // ----------------------------------------------------------------------- + server.tool( + "computer_screenshot", + "Capture a screenshot of the workspace desktop. Returns a base64-encoded PNG image.", + { + region: z + .object({ + x: z.number().describe("Left edge X coordinate"), + y: z.number().describe("Top edge Y coordinate"), + width: z.number().describe("Width in pixels"), + height: z.number().describe("Height in pixels"), + }) + .optional() + .describe("Optional region to capture. Omit for full screen."), + }, + async ({ region }) => { + onToolCall(); + try { + const body = region || {}; + const raw = await curlAgent("POST", "/screen/screenshot", body); + const parsed = JSON.parse(raw); + if (parsed.image) { + return { + content: [ + { + type: "image" as const, + data: parsed.image, + mimeType: "image/png", + }, + ], + }; + } + return { + content: [{ type: "text" as const, text: "Screenshot capture failed" }], + isError: true, + }; + } catch (err: any) { + // Fallback to direct import command + try { + await dockerExec( + "DISPLAY=:1 import -window root /tmp/screenshot.png 2>/dev/null", + ); + const { stdout } = await dockerExec( + "base64 -w 0 /tmp/screenshot.png 2>/dev/null", + ); + if (stdout && stdout !== "") { + return { + content: [ + { type: "image" as const, data: stdout, mimeType: "image/png" }, + ], + }; + } + } catch {} + return { + content: [ + { + type: "text" as const, + text: `Screenshot error: ${err.message || err}`, + }, + ], + isError: true, + }; + } + }, + ); + + // ----------------------------------------------------------------------- + // Screen info + // ----------------------------------------------------------------------- + server.tool( + "computer_screen_size", + "Get the screen dimensions (width, height) of the workspace desktop", + {}, + async () => { + onToolCall(); + try { + const raw = await curlAgent("GET", "/screen/size"); + return { content: [{ type: "text" as const, text: raw }] }; + } catch (err: any) { + return { + content: [ + { type: "text" as const, text: `Error: ${err.message || err}` }, + ], + isError: true, + }; + } + }, + ); + + // ----------------------------------------------------------------------- + // Mouse: move + // ----------------------------------------------------------------------- + server.tool( + "computer_mouse_move", + "Move the mouse cursor to the specified screen coordinates", + { + x: z.number().describe("X coordinate (pixels from left)"), + y: z.number().describe("Y coordinate (pixels from top)"), + }, + async ({ x, y }) => { + onToolCall(); + try { + const raw = await curlAgent("POST", "/mouse/move", { x, y }); + return { content: [{ type: "text" as const, text: raw }] }; + } catch (err: any) { + return { + content: [ + { type: "text" as const, text: `Error: ${err.message || err}` }, + ], + isError: true, + }; + } + }, + ); + + // ----------------------------------------------------------------------- + // Mouse: click + // ----------------------------------------------------------------------- + server.tool( + "computer_click", + "Click at the current mouse position or at specific coordinates", + { + x: z.number().optional().describe("X coordinate to click at (moves mouse first if provided)"), + y: z.number().optional().describe("Y coordinate to click at (moves mouse first if provided)"), + button: z + .enum(["left", "right", "middle"]) + .default("left") + .describe("Mouse button: left (1), right (3), or middle (2)"), + double: z + .boolean() + .default(false) + .describe("If true, perform a double-click"), + }, + async ({ x, y, button, double }) => { + onToolCall(); + const btn = button === "right" ? 3 : button === "middle" ? 2 : 1; + try { + if (x !== undefined && y !== undefined) { + const endpoint = double ? "/action/double_click_at" : "/action/click_at"; + const raw = await curlAgent("POST", endpoint, { x, y, button: btn }); + return { content: [{ type: "text" as const, text: raw }] }; + } + const endpoint = double ? "/mouse/double_click" : "/mouse/click"; + const raw = await curlAgent("POST", endpoint, { button: btn }); + return { content: [{ type: "text" as const, text: raw }] }; + } catch (err: any) { + return { + content: [ + { type: "text" as const, text: `Error: ${err.message || err}` }, + ], + isError: true, + }; + } + }, + ); + + // ----------------------------------------------------------------------- + // Mouse: scroll + // ----------------------------------------------------------------------- + server.tool( + "computer_scroll", + "Scroll the mouse wheel up or down", + { + direction: z.enum(["up", "down"]).describe("Scroll direction"), + clicks: z + .number() + .default(3) + .describe("Number of scroll clicks (default 3)"), + }, + async ({ direction, clicks }) => { + onToolCall(); + try { + const raw = await curlAgent("POST", "/mouse/scroll", { direction, clicks }); + return { content: [{ type: "text" as const, text: raw }] }; + } catch (err: any) { + return { + content: [ + { type: "text" as const, text: `Error: ${err.message || err}` }, + ], + isError: true, + }; + } + }, + ); + + // ----------------------------------------------------------------------- + // Mouse: drag + // ----------------------------------------------------------------------- + server.tool( + "computer_drag", + "Click-and-drag from one position to another", + { + x1: z.number().describe("Start X coordinate"), + y1: z.number().describe("Start Y coordinate"), + x2: z.number().describe("End X coordinate"), + y2: z.number().describe("End Y coordinate"), + }, + async ({ x1, y1, x2, y2 }) => { + onToolCall(); + try { + const raw = await curlAgent("POST", "/mouse/drag", { x1, y1, x2, y2 }); + return { content: [{ type: "text" as const, text: raw }] }; + } catch (err: any) { + return { + content: [ + { type: "text" as const, text: `Error: ${err.message || err}` }, + ], + isError: true, + }; + } + }, + ); + + // ----------------------------------------------------------------------- + // Keyboard: type text + // ----------------------------------------------------------------------- + server.tool( + "computer_type", + "Type text using the keyboard. For special keys, use computer_key_press instead.", + { + text: z.string().describe("Text to type"), + x: z.number().optional().describe("X coordinate to click before typing"), + y: z.number().optional().describe("Y coordinate to click before typing"), + }, + async ({ text, x, y }) => { + onToolCall(); + try { + if (x !== undefined && y !== undefined) { + const raw = await curlAgent("POST", "/action/type_at", { x, y, text }); + return { content: [{ type: "text" as const, text: raw }] }; + } + const raw = await curlAgent("POST", "/keyboard/type", { text }); + return { content: [{ type: "text" as const, text: raw }] }; + } catch (err: any) { + return { + content: [ + { type: "text" as const, text: `Error: ${err.message || err}` }, + ], + isError: true, + }; + } + }, + ); + + // ----------------------------------------------------------------------- + // Keyboard: press key(s) + // ----------------------------------------------------------------------- + server.tool( + "computer_key_press", + "Press a key or key combination. Examples: 'Return', 'ctrl+c', 'alt+F4', 'ctrl+shift+t', 'BackSpace', 'Tab', 'Escape'", + { + keys: z + .string() + .describe( + "Key combo using xdotool syntax (e.g. 'Return', 'ctrl+c', 'alt+Tab', 'super')", + ), + }, + async ({ keys }) => { + onToolCall(); + try { + const raw = await curlAgent("POST", "/keyboard/press", { keys }); + return { content: [{ type: "text" as const, text: raw }] }; + } catch (err: any) { + return { + content: [ + { type: "text" as const, text: `Error: ${err.message || err}` }, + ], + isError: true, + }; + } + }, + ); + + // ----------------------------------------------------------------------- + // Window: get active window info + // ----------------------------------------------------------------------- + server.tool( + "computer_active_window", + "Get information about the currently active (focused) window", + {}, + async () => { + onToolCall(); + try { + const raw = await curlAgent("GET", "/window/active"); + return { content: [{ type: "text" as const, text: raw }] }; + } catch (err: any) { + return { + content: [ + { type: "text" as const, text: `Error: ${err.message || err}` }, + ], + isError: true, + }; + } + }, + ); + + // ----------------------------------------------------------------------- + // Window: list windows + // ----------------------------------------------------------------------- + server.tool( + "computer_list_windows", + "List all visible windows on the desktop", + {}, + async () => { + onToolCall(); + try { + const raw = await curlAgent("GET", "/window/list"); + return { content: [{ type: "text" as const, text: raw }] }; + } catch (err: any) { + return { + content: [ + { type: "text" as const, text: `Error: ${err.message || err}` }, + ], + isError: true, + }; + } + }, + ); + + // ----------------------------------------------------------------------- + // Window: focus a specific window + // ----------------------------------------------------------------------- + server.tool( + "computer_focus_window", + "Bring a specific window to the foreground by its window ID", + { + window_id: z.string().describe("The window ID (from computer_list_windows)"), + }, + async ({ window_id }) => { + onToolCall(); + try { + const raw = await curlAgent("POST", "/window/focus", { window_id }); + return { content: [{ type: "text" as const, text: raw }] }; + } catch (err: any) { + return { + content: [ + { type: "text" as const, text: `Error: ${err.message || err}` }, + ], + isError: true, + }; + } + }, + ); + + // ----------------------------------------------------------------------- + // Mouse: get position + // ----------------------------------------------------------------------- + server.tool( + "computer_mouse_position", + "Get the current mouse cursor position", + {}, + async () => { + onToolCall(); + try { + const raw = await curlAgent("GET", "/mouse/position"); + return { content: [{ type: "text" as const, text: raw }] }; + } catch (err: any) { + return { + content: [ + { type: "text" as const, text: `Error: ${err.message || err}` }, + ], + isError: true, + }; + } + }, + ); +} diff --git a/coeadapt-launcher/mcp-server/src/tools/filesystem.ts b/coeadapt-launcher/mcp-server/src/tools/filesystem.ts new file mode 100644 index 000000000..2ddc41f0f --- /dev/null +++ b/coeadapt-launcher/mcp-server/src/tools/filesystem.ts @@ -0,0 +1,79 @@ +import { z } from "zod"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { dockerExec } from "../docker-exec.js"; + +export function registerFilesystemTools( + server: McpServer, + onToolCall: () => void, +) { + server.tool( + "read_file", + "Read a file from the workspace filesystem", + { path: z.string().describe("Absolute path to the file") }, + async ({ path }) => { + onToolCall(); + try { + const { stdout } = await dockerExec(`cat "${path}"`); + return { content: [{ type: "text" as const, text: stdout }] }; + } catch (err: any) { + return { + content: [ + { type: "text" as const, text: `Error: ${err.stderr || err.message}` }, + ], + isError: true, + }; + } + }, + ); + + server.tool( + "write_file", + "Write content to a file in the workspace", + { + path: z.string().describe("Absolute path to write"), + content: z.string().describe("Content to write"), + }, + async ({ path, content }) => { + onToolCall(); + try { + const b64 = Buffer.from(content).toString("base64"); + await dockerExec(`echo "${b64}" | base64 -d > "${path}"`); + return { + content: [{ type: "text" as const, text: `Written to ${path}` }], + }; + } catch (err: any) { + return { + content: [ + { type: "text" as const, text: `Error: ${err.stderr || err.message}` }, + ], + isError: true, + }; + } + }, + ); + + server.tool( + "list_files", + "List files and directories at a given path", + { + path: z + .string() + .describe("Directory path to list") + .default("/home/kasm-user"), + }, + async ({ path }) => { + onToolCall(); + try { + const { stdout } = await dockerExec(`ls -la "${path}"`); + return { content: [{ type: "text" as const, text: stdout }] }; + } catch (err: any) { + return { + content: [ + { type: "text" as const, text: `Error: ${err.stderr || err.message}` }, + ], + isError: true, + }; + } + }, + ); +} diff --git a/coeadapt-launcher/mcp-server/src/tools/progress.ts b/coeadapt-launcher/mcp-server/src/tools/progress.ts new file mode 100644 index 000000000..74f4daec9 --- /dev/null +++ b/coeadapt-launcher/mcp-server/src/tools/progress.ts @@ -0,0 +1,362 @@ +import { z } from "zod"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { dockerExec } from "../docker-exec.js"; + +/** + * Helper to call the in-VM progress tracker HTTP service. + * Falls back to direct file reads if the service is unavailable. + */ +async function progressApi( + method: "GET" | "POST" | "PUT", + path: string, + body?: Record, +): Promise { + const curlArgs = [ + "curl", "-sf", "-m", "5", + "-H", "Content-Type: application/json", + ]; + if (method === "POST" || method === "PUT") { + curlArgs.push("-X", method); + if (body) { + curlArgs.push("-d", JSON.stringify(body)); + } + } + curlArgs.push(`http://127.0.0.1:7700${path}`); + const { stdout } = await dockerExec(curlArgs.join(" ")); + return stdout; +} + +export function registerGetProgress( + server: McpServer, + onToolCall: () => void, +) { + // ----------------------------------------------------------------------- + // Get full progress data + // ----------------------------------------------------------------------- + server.tool( + "get_user_progress", + "Get the user's complete career development progress including activities, goals, skills, and milestones", + {}, + async () => { + onToolCall(); + try { + const raw = await progressApi("GET", "/progress"); + return { content: [{ type: "text" as const, text: raw }] }; + } catch { + // Fallback: read file directly + try { + const { stdout } = await dockerExec( + "cat /home/kasm-user/.coeadapt/progress.json 2>/dev/null || " + + 'echo \'{"activities":[],"assessments":[],"goals":[],"skills":[],"milestones":[],"progress_percent":0}\'', + ); + return { content: [{ type: "text" as const, text: stdout }] }; + } catch (err: any) { + return { + content: [ + { type: "text" as const, text: `Error: ${err.stderr || err.message}` }, + ], + isError: true, + }; + } + } + }, + ); + + // ----------------------------------------------------------------------- + // Get progress summary (lightweight) + // ----------------------------------------------------------------------- + server.tool( + "get_progress_summary", + "Get a lightweight summary of the user's career progress: completion percentage, streak, counts", + {}, + async () => { + onToolCall(); + try { + const raw = await progressApi("GET", "/progress/summary"); + return { content: [{ type: "text" as const, text: raw }] }; + } catch (err: any) { + return { + content: [ + { type: "text" as const, text: `Error: ${err.message || err}` }, + ], + isError: true, + }; + } + }, + ); + + // ----------------------------------------------------------------------- + // Log an activity + // ----------------------------------------------------------------------- + server.tool( + "log_activity", + "Log a career development activity (e.g. completed a tutorial, attended a workshop, practiced a skill)", + { + title: z.string().describe("Title of the activity"), + type: z + .enum([ + "tutorial", "workshop", "practice", "project", "assessment", + "reading", "networking", "application", "interview", "general", + ]) + .default("general") + .describe("Type of activity"), + description: z.string().optional().describe("Description of what was done"), + duration_minutes: z.number().optional().describe("How long the activity took in minutes"), + tags: z.array(z.string()).optional().describe("Tags/labels for the activity"), + }, + async ({ title, type, description, duration_minutes, tags }) => { + onToolCall(); + try { + const raw = await progressApi("POST", "/progress/activities", { + title, + type, + description: description || "", + duration_minutes: duration_minutes || 0, + tags: tags || [], + }); + return { content: [{ type: "text" as const, text: raw }] }; + } catch (err: any) { + return { + content: [ + { type: "text" as const, text: `Error: ${err.message || err}` }, + ], + isError: true, + }; + } + }, + ); + + // ----------------------------------------------------------------------- + // Create a goal + // ----------------------------------------------------------------------- + server.tool( + "create_goal", + "Create a new career development goal for the user to work toward", + { + title: z.string().describe("Goal title"), + description: z.string().optional().describe("Detailed description of the goal"), + category: z + .enum([ + "skill", "certification", "project", "job-search", + "networking", "education", "portfolio", "general", + ]) + .default("general") + .describe("Goal category"), + target_date: z.string().optional().describe("Target completion date (ISO 8601)"), + sub_goals: z + .array(z.string()) + .optional() + .describe("List of sub-goal descriptions"), + }, + async ({ title, description, category, target_date, sub_goals }) => { + onToolCall(); + try { + const raw = await progressApi("POST", "/progress/goals", { + title, + description: description || "", + category, + target_date, + sub_goals: sub_goals || [], + }); + return { content: [{ type: "text" as const, text: raw }] }; + } catch (err: any) { + return { + content: [ + { type: "text" as const, text: `Error: ${err.message || err}` }, + ], + isError: true, + }; + } + }, + ); + + // ----------------------------------------------------------------------- + // Update a goal + // ----------------------------------------------------------------------- + server.tool( + "update_goal", + "Update an existing goal's status, description, or details", + { + goal_id: z.number().describe("ID of the goal to update"), + status: z + .enum(["active", "completed", "paused", "abandoned"]) + .optional() + .describe("New goal status"), + title: z.string().optional().describe("Updated title"), + description: z.string().optional().describe("Updated description"), + }, + async ({ goal_id, status, title, description }) => { + onToolCall(); + const updates: Record = {}; + if (status) updates.status = status; + if (title) updates.title = title; + if (description) updates.description = description; + try { + const raw = await progressApi("PUT", `/progress/goals/${goal_id}`, updates); + return { content: [{ type: "text" as const, text: raw }] }; + } catch (err: any) { + return { + content: [ + { type: "text" as const, text: `Error: ${err.message || err}` }, + ], + isError: true, + }; + } + }, + ); + + // ----------------------------------------------------------------------- + // Record a skill + // ----------------------------------------------------------------------- + server.tool( + "record_skill", + "Record or update a skill the user has demonstrated or is developing", + { + name: z.string().describe("Skill name (e.g. 'Python', 'Public Speaking', 'React')"), + category: z + .enum([ + "technical", "soft-skill", "tool", "language", + "framework", "methodology", "domain", "general", + ]) + .default("general") + .describe("Skill category"), + level: z + .enum(["beginner", "intermediate", "advanced", "expert"]) + .default("beginner") + .describe("Current proficiency level"), + evidence: z + .array(z.string()) + .optional() + .describe("Evidence of the skill (project URLs, descriptions, etc.)"), + }, + async ({ name, category, level, evidence }) => { + onToolCall(); + try { + const raw = await progressApi("POST", "/progress/skills", { + name, + category, + level, + evidence: evidence || [], + }); + return { content: [{ type: "text" as const, text: raw }] }; + } catch (err: any) { + return { + content: [ + { type: "text" as const, text: `Error: ${err.message || err}` }, + ], + isError: true, + }; + } + }, + ); + + // ----------------------------------------------------------------------- + // Update a skill + // ----------------------------------------------------------------------- + server.tool( + "update_skill", + "Update a skill's level or add new evidence", + { + skill_id: z.number().describe("ID of the skill to update"), + level: z + .enum(["beginner", "intermediate", "advanced", "expert"]) + .optional() + .describe("Updated proficiency level"), + evidence: z + .array(z.string()) + .optional() + .describe("New evidence entries to add"), + verified: z.boolean().optional().describe("Mark as verified by assessment"), + }, + async ({ skill_id, level, evidence, verified }) => { + onToolCall(); + const updates: Record = {}; + if (level) updates.level = level; + if (evidence) updates.evidence = evidence; + if (verified !== undefined) updates.verified = verified; + try { + const raw = await progressApi("PUT", `/progress/skills/${skill_id}`, updates); + return { content: [{ type: "text" as const, text: raw }] }; + } catch (err: any) { + return { + content: [ + { type: "text" as const, text: `Error: ${err.message || err}` }, + ], + isError: true, + }; + } + }, + ); + + // ----------------------------------------------------------------------- + // Add a milestone + // ----------------------------------------------------------------------- + server.tool( + "add_milestone", + "Record a career milestone or achievement (e.g. 'Got first interview', 'Completed Python course')", + { + title: z.string().describe("Milestone title"), + description: z.string().optional().describe("Details about the milestone"), + achieved: z + .boolean() + .default(true) + .describe("Whether the milestone is already achieved"), + }, + async ({ title, description, achieved }) => { + onToolCall(); + try { + const raw = await progressApi("POST", "/progress/milestones", { + title, + description: description || "", + achieved, + }); + return { content: [{ type: "text" as const, text: raw }] }; + } catch (err: any) { + return { + content: [ + { type: "text" as const, text: `Error: ${err.message || err}` }, + ], + isError: true, + }; + } + }, + ); + + // ----------------------------------------------------------------------- + // Record an assessment result + // ----------------------------------------------------------------------- + server.tool( + "record_assessment", + "Record the result of a skills assessment or quiz", + { + skill: z.string().describe("Skill being assessed"), + score: z.number().describe("Score achieved"), + max_score: z.number().default(100).describe("Maximum possible score"), + type: z + .enum(["self", "quiz", "project-review", "peer", "ai"]) + .default("self") + .describe("Type of assessment"), + notes: z.string().optional().describe("Notes about the assessment"), + }, + async ({ skill, score, max_score, type, notes }) => { + onToolCall(); + try { + const raw = await progressApi("POST", "/progress/assessments", { + skill, + score, + max_score, + type, + notes: notes || "", + }); + return { content: [{ type: "text" as const, text: raw }] }; + } catch (err: any) { + return { + content: [ + { type: "text" as const, text: `Error: ${err.message || err}` }, + ], + isError: true, + }; + } + }, + ); +} diff --git a/coeadapt-launcher/mcp-server/src/tools/screenshot.ts b/coeadapt-launcher/mcp-server/src/tools/screenshot.ts new file mode 100644 index 000000000..82f8b9b1e --- /dev/null +++ b/coeadapt-launcher/mcp-server/src/tools/screenshot.ts @@ -0,0 +1,75 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { dockerExec } from "../docker-exec.js"; + +export function registerScreenshot( + server: McpServer, + onToolCall: () => void, +) { + server.tool( + "take_screenshot", + "Capture a screenshot of the current workspace desktop (simple version — use computer_screenshot for region capture)", + {}, + async () => { + onToolCall(); + try { + // Try the computer-use service first for better reliability + const { stdout: serviceResult } = await dockerExec( + 'curl -sf -m 5 http://127.0.0.1:7701/screen/screenshot 2>/dev/null || echo ""', + ); + if (serviceResult && serviceResult.startsWith("{")) { + const parsed = JSON.parse(serviceResult); + if (parsed.image) { + return { + content: [ + { + type: "image" as const, + data: parsed.image, + mimeType: "image/png", + }, + ], + }; + } + } + } catch { + // Service not available, fall through to direct capture + } + + try { + // Fallback: direct imagemagick capture + await dockerExec( + "DISPLAY=:1 import -window root /tmp/screenshot.png 2>/dev/null || " + + "DISPLAY=:1 xdotool key --delay 100 Print && sleep 1", + ); + const { stdout } = await dockerExec( + "base64 -w 0 /tmp/screenshot.png 2>/dev/null || echo 'NO_SCREENSHOT'", + ); + if (stdout === "NO_SCREENSHOT") { + return { + content: [ + { + type: "text" as const, + text: "Screenshot capture not available. Install imagemagick in the workspace.", + }, + ], + }; + } + return { + content: [ + { + type: "image" as const, + data: stdout, + mimeType: "image/png", + }, + ], + }; + } catch (err: any) { + return { + content: [ + { type: "text" as const, text: `Error: ${err.stderr || err.message}` }, + ], + isError: true, + }; + } + }, + ); +} diff --git a/coeadapt-launcher/mcp-server/src/tools/workspace.ts b/coeadapt-launcher/mcp-server/src/tools/workspace.ts new file mode 100644 index 000000000..46acd50a9 --- /dev/null +++ b/coeadapt-launcher/mcp-server/src/tools/workspace.ts @@ -0,0 +1,29 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { isContainerRunning } from "../docker-exec.js"; + +export function registerWorkspaceStatus( + server: McpServer, + onToolCall: () => void, +) { + server.tool( + "workspace_status", + "Check if the Coeadapt workspace is running and healthy", + {}, + async () => { + onToolCall(); + const running = await isContainerRunning(); + return { + content: [ + { + type: "text", + text: JSON.stringify({ + running, + url: running ? "https://localhost:6901" : null, + status: running ? "healthy" : "stopped", + }), + }, + ], + }; + }, + ); +} diff --git a/coeadapt-launcher/mcp-server/tsconfig.json b/coeadapt-launcher/mcp-server/tsconfig.json new file mode 100644 index 000000000..ac12238df --- /dev/null +++ b/coeadapt-launcher/mcp-server/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "bundler", + "esModuleInterop": true, + "strict": true, + "outDir": "dist", + "rootDir": "src", + "skipLibCheck": true + }, + "include": ["src/**/*"] +} diff --git a/coeadapt-launcher/package-lock.json b/coeadapt-launcher/package-lock.json new file mode 100644 index 000000000..44d9d4a63 --- /dev/null +++ b/coeadapt-launcher/package-lock.json @@ -0,0 +1,1343 @@ +{ + "name": "coeadapt-launcher", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "coeadapt-launcher", + "version": "0.1.0", + "dependencies": { + "@clerk/clerk-react": "^5", + "@tauri-apps/api": "^2", + "@tauri-apps/plugin-autostart": "^2.5.1", + "@tauri-apps/plugin-opener": "^2", + "@tauri-apps/plugin-shell": "^2.3.5", + "@tauri-apps/plugin-store": "^2.4.2", + "@tauri-apps/plugin-updater": "^2.10.0", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-router-dom": "^7.13.0" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.1.18", + "@tauri-apps/cli": "^2", + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", + "@vitejs/plugin-react": "^4.6.0", + "tailwindcss": "^4.1.18", + "typescript": "~5.8.3", + "vite": "^7.0.4" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@clerk/clerk-react": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@clerk/clerk-react/-/clerk-react-5.60.1.tgz", + "integrity": "sha512-Z7LAJKvcieGSFB3Q0f840o8Lh7VauvBjc+aDElIt6OHaJRjWcjhFcRiL2Fvg9YkRG942IZN8RHl7iKUzAU5SIQ==", + "license": "MIT", + "dependencies": { + "@clerk/shared": "^3.45.0", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=18.17.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0", + "react-dom": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0" + } + }, + "node_modules/@clerk/shared": { + "version": "3.45.0", + "resolved": "https://registry.npmjs.org/@clerk/shared/-/shared-3.45.0.tgz", + "integrity": "sha512-u4MlyEQy+QnGiQqwwqznplJ59el3k05tgaRyh9O3KSxWa84Br4JCXRuV9yYhA0+7bvgUPE7nLlX2byWmf7QOAA==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "csstype": "3.1.3", + "dequal": "2.0.3", + "glob-to-regexp": "0.4.1", + "js-cookie": "3.0.5", + "std-env": "^3.9.0", + "swr": "2.3.4" + }, + "engines": { + "node": ">=18.17.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0", + "react-dom": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@clerk/shared/node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.1", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.1", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.18", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.6.1", + "lightningcss": "1.30.2", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.18", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-x64": "4.1.18", + "@tailwindcss/oxide-freebsd-x64": "4.1.18", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-x64-musl": "4.1.18", + "@tailwindcss/oxide-wasm32-wasi": "4.1.18", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.18", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.1.18", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.1.18", + "@tailwindcss/oxide": "4.1.18", + "tailwindcss": "4.1.18" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7" + } + }, + "node_modules/@tauri-apps/api": { + "version": "2.10.1", + "license": "Apache-2.0 OR MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, + "node_modules/@tauri-apps/cli": { + "version": "2.10.0", + "dev": true, + "license": "Apache-2.0 OR MIT", + "bin": { + "tauri": "tauri.js" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + }, + "optionalDependencies": { + "@tauri-apps/cli-darwin-arm64": "2.10.0", + "@tauri-apps/cli-darwin-x64": "2.10.0", + "@tauri-apps/cli-linux-arm-gnueabihf": "2.10.0", + "@tauri-apps/cli-linux-arm64-gnu": "2.10.0", + "@tauri-apps/cli-linux-arm64-musl": "2.10.0", + "@tauri-apps/cli-linux-riscv64-gnu": "2.10.0", + "@tauri-apps/cli-linux-x64-gnu": "2.10.0", + "@tauri-apps/cli-linux-x64-musl": "2.10.0", + "@tauri-apps/cli-win32-arm64-msvc": "2.10.0", + "@tauri-apps/cli-win32-ia32-msvc": "2.10.0", + "@tauri-apps/cli-win32-x64-msvc": "2.10.0" + } + }, + "node_modules/@tauri-apps/cli-win32-x64-msvc": { + "version": "2.10.0", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/plugin-autostart": { + "version": "2.5.1", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } + }, + "node_modules/@tauri-apps/plugin-opener": { + "version": "2.5.3", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } + }, + "node_modules/@tauri-apps/plugin-shell": { + "version": "2.3.5", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.10.1" + } + }, + "node_modules/@tauri-apps/plugin-store": { + "version": "2.4.2", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } + }, + "node_modules/@tauri-apps/plugin-updater": { + "version": "2.10.0", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.10.1" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.14", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.19", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001769", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.286", + "dev": true, + "license": "ISC" + }, + "node_modules/enhanced-resolve": { + "version": "5.19.0", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/esbuild": { + "version": "0.27.3", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "license": "BSD-2-Clause" + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.6.1", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lightningcss": { + "version": "1.30.2", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.30.2", + "lightningcss-darwin-arm64": "1.30.2", + "lightningcss-darwin-x64": "1.30.2", + "lightningcss-freebsd-x64": "1.30.2", + "lightningcss-linux-arm-gnueabihf": "1.30.2", + "lightningcss-linux-arm64-gnu": "1.30.2", + "lightningcss-linux-arm64-musl": "1.30.2", + "lightningcss-linux-x64-gnu": "1.30.2", + "lightningcss-linux-x64-musl": "1.30.2", + "lightningcss-win32-arm64-msvc": "1.30.2", + "lightningcss-win32-x64-msvc": "1.30.2" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.2", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "19.2.4", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.13.0", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.13.0", + "license": "MIT", + "dependencies": { + "react-router": "7.13.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/rollup": { + "version": "4.57.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "license": "MIT" + }, + "node_modules/swr": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.4.tgz", + "integrity": "sha512-bYd2lrhc+VarcpkgWclcUi92wYCpOgMws9Sd1hG1ntAu0NEy+14CbotuFjshBU2kt9rYj9TSmDcybpxpeTU1fg==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.18", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.8.3", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/vite": { + "version": "7.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "dev": true, + "license": "ISC" + } + } +} diff --git a/coeadapt-launcher/package.json b/coeadapt-launcher/package.json new file mode 100644 index 000000000..1504a8c64 --- /dev/null +++ b/coeadapt-launcher/package.json @@ -0,0 +1,34 @@ +{ + "name": "coeadapt-launcher", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "tauri": "tauri" + }, + "dependencies": { + "@clerk/clerk-react": "^5", + "@tauri-apps/api": "^2", + "@tauri-apps/plugin-autostart": "^2.5.1", + "@tauri-apps/plugin-opener": "^2", + "@tauri-apps/plugin-shell": "^2.3.5", + "@tauri-apps/plugin-store": "^2.4.2", + "@tauri-apps/plugin-updater": "^2.10.0", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-router-dom": "^7.13.0" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.1.18", + "@tauri-apps/cli": "^2", + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", + "@vitejs/plugin-react": "^4.6.0", + "tailwindcss": "^4.1.18", + "typescript": "~5.8.3", + "vite": "^7.0.4" + } +} diff --git a/coeadapt-launcher/public/logo-color.png b/coeadapt-launcher/public/logo-color.png new file mode 100644 index 000000000..f5b4b72d4 Binary files /dev/null and b/coeadapt-launcher/public/logo-color.png differ diff --git a/coeadapt-launcher/public/logo-white.png b/coeadapt-launcher/public/logo-white.png new file mode 100644 index 000000000..ccab8ea62 Binary files /dev/null and b/coeadapt-launcher/public/logo-white.png differ diff --git a/coeadapt-launcher/public/tauri.svg b/coeadapt-launcher/public/tauri.svg new file mode 100644 index 000000000..31b62c928 --- /dev/null +++ b/coeadapt-launcher/public/tauri.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/coeadapt-launcher/public/vite.svg b/coeadapt-launcher/public/vite.svg new file mode 100644 index 000000000..e7b8dfb1b --- /dev/null +++ b/coeadapt-launcher/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/coeadapt-launcher/public/wordmark-color.png b/coeadapt-launcher/public/wordmark-color.png new file mode 100644 index 000000000..0d0d85f25 Binary files /dev/null and b/coeadapt-launcher/public/wordmark-color.png differ diff --git a/coeadapt-launcher/public/wordmark-white.png b/coeadapt-launcher/public/wordmark-white.png new file mode 100644 index 000000000..a09db1d09 Binary files /dev/null and b/coeadapt-launcher/public/wordmark-white.png differ diff --git a/coeadapt-launcher/src-tauri/.gitignore b/coeadapt-launcher/src-tauri/.gitignore new file mode 100644 index 000000000..b21bd681d --- /dev/null +++ b/coeadapt-launcher/src-tauri/.gitignore @@ -0,0 +1,7 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Generated by Tauri +# will have schema files for capabilities auto-completion +/gen/schemas diff --git a/coeadapt-launcher/src-tauri/Cargo.toml b/coeadapt-launcher/src-tauri/Cargo.toml new file mode 100644 index 000000000..26e3977f2 --- /dev/null +++ b/coeadapt-launcher/src-tauri/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "coeadapt-launcher" +version = "0.1.0" +description = "Coeadapt - Turn-key career workspace launcher" +authors = ["Coeadapt"] +edition = "2021" + +[lib] +name = "coeadapt_launcher_lib" +crate-type = ["staticlib", "cdylib", "rlib"] + +[build-dependencies] +tauri-build = { version = "2", features = [] } + +[dependencies] +tauri = { version = "2", features = ["tray-icon", "image-png"] } +tauri-plugin-opener = "2" +tauri-plugin-shell = "2" +tauri-plugin-store = "2" +tauri-plugin-updater = "2" +tauri-plugin-autostart = "2" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-features = false } +tokio = { version = "1", features = ["full"] } +dirs = "6" +sysinfo = "0.34" diff --git a/src/ubuntu/install/hunchly/license.key b/coeadapt-launcher/src-tauri/binaries/.gitkeep similarity index 100% rename from src/ubuntu/install/hunchly/license.key rename to coeadapt-launcher/src-tauri/binaries/.gitkeep diff --git a/coeadapt-launcher/src-tauri/build.rs b/coeadapt-launcher/src-tauri/build.rs new file mode 100644 index 000000000..d860e1e6a --- /dev/null +++ b/coeadapt-launcher/src-tauri/build.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_build::build() +} diff --git a/coeadapt-launcher/src-tauri/capabilities/default.json b/coeadapt-launcher/src-tauri/capabilities/default.json new file mode 100644 index 000000000..081f8d77e --- /dev/null +++ b/coeadapt-launcher/src-tauri/capabilities/default.json @@ -0,0 +1,18 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "Coeadapt default capabilities", + "windows": ["main"], + "permissions": [ + "core:default", + "opener:default", + "shell:allow-spawn", + "shell:allow-execute", + "shell:allow-open", + "store:default", + "updater:default", + "autostart:allow-enable", + "autostart:allow-disable", + "autostart:allow-is-enabled" + ] +} diff --git a/coeadapt-launcher/src-tauri/icons/128x128.png b/coeadapt-launcher/src-tauri/icons/128x128.png new file mode 100644 index 000000000..6be5e50e9 Binary files /dev/null and b/coeadapt-launcher/src-tauri/icons/128x128.png differ diff --git a/coeadapt-launcher/src-tauri/icons/128x128@2x.png b/coeadapt-launcher/src-tauri/icons/128x128@2x.png new file mode 100644 index 000000000..e81becee5 Binary files /dev/null and b/coeadapt-launcher/src-tauri/icons/128x128@2x.png differ diff --git a/coeadapt-launcher/src-tauri/icons/32x32.png b/coeadapt-launcher/src-tauri/icons/32x32.png new file mode 100644 index 000000000..a437dd517 Binary files /dev/null and b/coeadapt-launcher/src-tauri/icons/32x32.png differ diff --git a/coeadapt-launcher/src-tauri/icons/Square107x107Logo.png b/coeadapt-launcher/src-tauri/icons/Square107x107Logo.png new file mode 100644 index 000000000..0ca4f2719 Binary files /dev/null and b/coeadapt-launcher/src-tauri/icons/Square107x107Logo.png differ diff --git a/coeadapt-launcher/src-tauri/icons/Square142x142Logo.png b/coeadapt-launcher/src-tauri/icons/Square142x142Logo.png new file mode 100644 index 000000000..b81f82039 Binary files /dev/null and b/coeadapt-launcher/src-tauri/icons/Square142x142Logo.png differ diff --git a/coeadapt-launcher/src-tauri/icons/Square150x150Logo.png b/coeadapt-launcher/src-tauri/icons/Square150x150Logo.png new file mode 100644 index 000000000..624c7bfba Binary files /dev/null and b/coeadapt-launcher/src-tauri/icons/Square150x150Logo.png differ diff --git a/coeadapt-launcher/src-tauri/icons/Square284x284Logo.png b/coeadapt-launcher/src-tauri/icons/Square284x284Logo.png new file mode 100644 index 000000000..c021d2ba7 Binary files /dev/null and b/coeadapt-launcher/src-tauri/icons/Square284x284Logo.png differ diff --git a/coeadapt-launcher/src-tauri/icons/Square30x30Logo.png b/coeadapt-launcher/src-tauri/icons/Square30x30Logo.png new file mode 100644 index 000000000..621970023 Binary files /dev/null and b/coeadapt-launcher/src-tauri/icons/Square30x30Logo.png differ diff --git a/coeadapt-launcher/src-tauri/icons/Square310x310Logo.png b/coeadapt-launcher/src-tauri/icons/Square310x310Logo.png new file mode 100644 index 000000000..f9bc04839 Binary files /dev/null and b/coeadapt-launcher/src-tauri/icons/Square310x310Logo.png differ diff --git a/coeadapt-launcher/src-tauri/icons/Square44x44Logo.png b/coeadapt-launcher/src-tauri/icons/Square44x44Logo.png new file mode 100644 index 000000000..d5fbfb2ab Binary files /dev/null and b/coeadapt-launcher/src-tauri/icons/Square44x44Logo.png differ diff --git a/coeadapt-launcher/src-tauri/icons/Square71x71Logo.png b/coeadapt-launcher/src-tauri/icons/Square71x71Logo.png new file mode 100644 index 000000000..63440d798 Binary files /dev/null and b/coeadapt-launcher/src-tauri/icons/Square71x71Logo.png differ diff --git a/coeadapt-launcher/src-tauri/icons/Square89x89Logo.png b/coeadapt-launcher/src-tauri/icons/Square89x89Logo.png new file mode 100644 index 000000000..f3f705af2 Binary files /dev/null and b/coeadapt-launcher/src-tauri/icons/Square89x89Logo.png differ diff --git a/coeadapt-launcher/src-tauri/icons/StoreLogo.png b/coeadapt-launcher/src-tauri/icons/StoreLogo.png new file mode 100644 index 000000000..455638826 Binary files /dev/null and b/coeadapt-launcher/src-tauri/icons/StoreLogo.png differ diff --git a/coeadapt-launcher/src-tauri/icons/icon.icns b/coeadapt-launcher/src-tauri/icons/icon.icns new file mode 100644 index 000000000..12a5bcee2 Binary files /dev/null and b/coeadapt-launcher/src-tauri/icons/icon.icns differ diff --git a/coeadapt-launcher/src-tauri/icons/icon.ico b/coeadapt-launcher/src-tauri/icons/icon.ico new file mode 100644 index 000000000..b3636e4b2 Binary files /dev/null and b/coeadapt-launcher/src-tauri/icons/icon.ico differ diff --git a/coeadapt-launcher/src-tauri/icons/icon.png b/coeadapt-launcher/src-tauri/icons/icon.png new file mode 100644 index 000000000..e1cd2619e Binary files /dev/null and b/coeadapt-launcher/src-tauri/icons/icon.png differ diff --git a/coeadapt-launcher/src-tauri/src/claude.rs b/coeadapt-launcher/src-tauri/src/claude.rs new file mode 100644 index 000000000..fde3467fd --- /dev/null +++ b/coeadapt-launcher/src-tauri/src/claude.rs @@ -0,0 +1,137 @@ +use std::path::PathBuf; + +use crate::state::ClaudeStatus; + +pub fn claude_config_path() -> Option { + if cfg!(target_os = "macos") { + dirs::home_dir() + .map(|h| h.join("Library/Application Support/Claude/claude_desktop_config.json")) + } else if cfg!(target_os = "windows") { + std::env::var("APPDATA") + .ok() + .map(|a| PathBuf::from(a).join("Claude").join("claude_desktop_config.json")) + } else { + dirs::config_dir().map(|c| c.join("Claude/claude_desktop_config.json")) + } +} + +pub fn is_claude_installed() -> bool { + claude_config_path() + .map(|p| { + p.parent() + .map(|d| d.exists()) + .unwrap_or(false) + }) + .unwrap_or(false) +} + +pub fn is_coeadapt_configured() -> bool { + let Some(config_path) = claude_config_path() else { + return false; + }; + if !config_path.exists() { + return false; + } + let Ok(contents) = std::fs::read_to_string(&config_path) else { + return false; + }; + let Ok(config) = serde_json::from_str::(&contents) else { + return false; + }; + config + .get("mcpServers") + .and_then(|s| s.get("coeadapt")) + .is_some() +} + +pub fn inject_coeadapt_config() -> Result<(), String> { + let config_path = claude_config_path().ok_or("Cannot determine Claude config path")?; + + // Create backup before first edit + if config_path.exists() { + let backup = config_path.with_extension("json.bak"); + if !backup.exists() { + std::fs::copy(&config_path, &backup).map_err(|e| e.to_string())?; + } + } + + let mut config: serde_json::Value = if config_path.exists() { + let contents = std::fs::read_to_string(&config_path).map_err(|e| e.to_string())?; + serde_json::from_str(&contents).map_err(|e| { + format!( + "Claude config JSON parse error: {}. Not modifying file.", + e + ) + })? + } else { + // Create parent directory if needed + if let Some(parent) = config_path.parent() { + std::fs::create_dir_all(parent).map_err(|e| e.to_string())?; + } + serde_json::json!({}) + }; + + // Ensure mcpServers exists + if config.get("mcpServers").is_none() { + config["mcpServers"] = serde_json::json!({}); + } + + // Only add if not already present + if config["mcpServers"].get("coeadapt").is_none() { + config["mcpServers"]["coeadapt"] = serde_json::json!({ + "command": "npx", + "args": ["mcp-remote", "http://localhost:3100/mcp"], + "env": {} + }); + } + + let formatted = serde_json::to_string_pretty(&config).map_err(|e| e.to_string())?; + std::fs::write(&config_path, formatted).map_err(|e| e.to_string())?; + Ok(()) +} + +pub fn remove_coeadapt_config() -> Result<(), String> { + let config_path = claude_config_path().ok_or("Cannot determine Claude config path")?; + if !config_path.exists() { + return Ok(()); + } + + let contents = std::fs::read_to_string(&config_path).map_err(|e| e.to_string())?; + let mut config: serde_json::Value = + serde_json::from_str(&contents).map_err(|e| e.to_string())?; + + if let Some(servers) = config.get_mut("mcpServers") { + if let Some(obj) = servers.as_object_mut() { + obj.remove("coeadapt"); + } + } + + let formatted = serde_json::to_string_pretty(&config).map_err(|e| e.to_string())?; + std::fs::write(&config_path, formatted).map_err(|e| e.to_string())?; + Ok(()) +} + +pub fn get_claude_status() -> ClaudeStatus { + let installed = is_claude_installed(); + let config_path = claude_config_path().map(|p| p.to_string_lossy().to_string()); + let configured = is_coeadapt_configured(); + + ClaudeStatus { + is_installed: installed, + config_path, + is_configured: configured, + needs_restart: false, + } +} + +pub fn verify_and_repair_config() -> Result { + if !is_claude_installed() { + return Ok(false); + } + if is_coeadapt_configured() { + return Ok(false); // Already configured, no repair needed + } + // Config missing or coeadapt entry removed (e.g., Claude update) + inject_coeadapt_config()?; + Ok(true) // Repaired +} diff --git a/coeadapt-launcher/src-tauri/src/commands.rs b/coeadapt-launcher/src-tauri/src/commands.rs new file mode 100644 index 000000000..dc341e87f --- /dev/null +++ b/coeadapt-launcher/src-tauri/src/commands.rs @@ -0,0 +1,182 @@ +use crate::{claude, container, disk, docker, health, mcp, ssl}; +use std::time::Duration; + +// --- Docker Detection --- + +#[tauri::command] +pub fn detect_container_runtime() -> crate::state::DockerInfo { + docker::get_docker_info().unwrap_or(crate::state::DockerInfo { + runtime: crate::state::ContainerRuntime::None, + version: String::new(), + is_daemon_running: false, + }) +} + +#[tauri::command] +pub fn check_wsl2_status() -> bool { + docker::is_wsl2_enabled() +} + +// --- Disk Space --- + +#[tauri::command] +pub fn check_disk_space() -> crate::state::DiskStatus { + disk::check_disk_space() +} + +#[tauri::command] +pub fn get_docker_disk_usage() -> Result { + disk::get_docker_disk_usage() +} + +#[tauri::command] +pub fn prune_docker_images() -> Result { + container::prune_images() +} + +// --- Container Lifecycle --- + +#[tauri::command] +pub fn get_workspace_status() -> crate::state::ContainerStatus { + container::get_container_status() +} + +#[tauri::command] +pub fn check_image_exists() -> bool { + container::image_exists() +} + +#[tauri::command] +pub async fn pull_workspace_image(app: tauri::AppHandle) -> Result<(), String> { + // Run in a blocking thread since docker_pull_streaming uses std::process + let app_clone = app.clone(); + tokio::task::spawn_blocking(move || { + docker::docker_pull_streaming(crate::state::IMAGE_NAME, app_clone) + }) + .await + .map_err(|e| e.to_string())? +} + +#[tauri::command] +pub fn create_workspace(app: tauri::AppHandle) -> Result { + use tauri_plugin_store::StoreExt; + + let (memory_mb, vnc_password) = if let Ok(store) = app.store("settings.json") { + let mem = store + .get("containerMemoryMb") + .and_then(|v| v.as_u64()) + .unwrap_or(2048); + let pw = store + .get("vncPassword") + .and_then(|v| v.as_str().map(|s| s.to_string())) + .unwrap_or_else(|| "coeadapt".to_string()); + (mem, pw) + } else { + (2048, "coeadapt".to_string()) + }; + + container::create_container_with_config(memory_mb, &vnc_password) +} + +#[tauri::command] +pub fn start_workspace() -> Result<(), String> { + container::start_container() +} + +#[tauri::command] +pub fn stop_workspace() -> Result<(), String> { + container::stop_container() +} + +#[tauri::command] +pub fn reset_workspace() -> Result<(), String> { + container::remove_container()?; + container::remove_workspace_data()?; + Ok(()) +} + +// --- Health --- + +#[tauri::command] +pub async fn wait_for_workspace_ready(app: tauri::AppHandle) -> Result<(), String> { + health::wait_for_workspace(app, Duration::from_secs(120)).await +} + +// --- MCP --- + +#[tauri::command] +pub async fn check_mcp_status() -> bool { + health::check_mcp_health().await +} + +#[tauri::command] +pub async fn get_mcp_health() -> crate::state::McpHealthInfo { + health::get_mcp_health_info().await +} + +#[tauri::command] +pub fn start_mcp(app: tauri::AppHandle) -> Result<(), String> { + mcp::start_mcp_sidecar(&app) +} + +#[tauri::command] +pub fn stop_mcp() -> Result<(), String> { + mcp::stop_mcp_sidecar() +} + +// --- Claude Connection --- + +#[tauri::command] +pub fn get_claude_status() -> crate::state::ClaudeStatus { + claude::get_claude_status() +} + +#[tauri::command] +pub fn configure_claude() -> Result<(), String> { + claude::inject_coeadapt_config() +} + +// --- SSL / Certificate Trust --- + +#[tauri::command] +pub fn check_ssl_trust() -> bool { + ssl::is_ca_installed() +} + +#[tauri::command] +pub fn install_ssl_certificate() -> Result<(), String> { + ssl::install_ca_cert() +} + +#[tauri::command] +pub fn uninstall_ssl_certificate() -> Result<(), String> { + ssl::uninstall_ca_cert() +} + +// --- Workspace Browser --- + +#[tauri::command] +pub fn open_workspace_browser() -> Result<(), String> { + #[cfg(target_os = "windows")] + { + std::process::Command::new("cmd") + .args(["/C", "start", "https://localhost:6901"]) + .spawn() + .map_err(|e| e.to_string())?; + } + #[cfg(target_os = "macos")] + { + std::process::Command::new("open") + .arg("https://localhost:6901") + .spawn() + .map_err(|e| e.to_string())?; + } + #[cfg(target_os = "linux")] + { + std::process::Command::new("xdg-open") + .arg("https://localhost:6901") + .spawn() + .map_err(|e| e.to_string())?; + } + Ok(()) +} diff --git a/coeadapt-launcher/src-tauri/src/container.rs b/coeadapt-launcher/src-tauri/src/container.rs new file mode 100644 index 000000000..7db40a36d --- /dev/null +++ b/coeadapt-launcher/src-tauri/src/container.rs @@ -0,0 +1,125 @@ +use crate::docker::docker_cmd; +use crate::state::{ContainerState, ContainerStatus, CONTAINER_NAME, IMAGE_NAME, VOLUME_NAME}; + +pub fn get_container_status() -> ContainerStatus { + let result = docker_cmd(&[ + "inspect", + "--format", + "{{.State.Status}}|{{.Id}}|{{.State.StartedAt}}|{{.Config.Image}}", + CONTAINER_NAME, + ]); + + match result { + Ok(output) => { + let parts: Vec<&str> = output.split('|').collect(); + if parts.len() >= 4 { + let state = match parts[0] { + "running" => ContainerState::Running, + "exited" | "dead" => ContainerState::Stopped, + "created" | "restarting" => ContainerState::Starting, + _ => ContainerState::Stopped, + }; + let container_id = Some(parts[1][..12.min(parts[1].len())].to_string()); + let uptime = if state == ContainerState::Running { + Some(parts[2].to_string()) + } else { + None + }; + ContainerStatus { + state, + container_id, + uptime, + image: parts[3].to_string(), + } + } else { + ContainerStatus { + state: ContainerState::Error("Failed to parse container info".to_string()), + container_id: None, + uptime: None, + image: IMAGE_NAME.to_string(), + } + } + } + Err(_) => ContainerStatus { + state: ContainerState::NotFound, + container_id: None, + uptime: None, + image: IMAGE_NAME.to_string(), + }, + } +} + +pub fn create_container() -> Result { + create_container_with_config(2048, "coeadapt") +} + +pub fn create_container_with_config(memory_mb: u64, vnc_password: &str) -> Result { + let memory_flag = format!("--memory={}m", memory_mb); + let vnc_env = format!("VNC_PW={}", vnc_password); + + docker_cmd(&[ + "run", + "-d", + "--name", + CONTAINER_NAME, + "--shm-size=512m", + &memory_flag, + "-p", + "6901:6901", + "-v", + &format!("{}:/home/kasm-user", VOLUME_NAME), + "-e", + &vnc_env, + "--restart", + "unless-stopped", + IMAGE_NAME, + ]) +} + +pub fn start_container() -> Result<(), String> { + docker_cmd(&["start", CONTAINER_NAME])?; + Ok(()) +} + +pub fn stop_container() -> Result<(), String> { + docker_cmd(&["stop", CONTAINER_NAME])?; + Ok(()) +} + +pub fn remove_container() -> Result<(), String> { + // Stop first if running + let _ = docker_cmd(&["stop", CONTAINER_NAME]); + docker_cmd(&["rm", CONTAINER_NAME])?; + Ok(()) +} + +pub fn image_exists() -> bool { + docker_cmd(&["image", "inspect", IMAGE_NAME]).is_ok() +} + +pub fn check_for_image_update() -> Result { + // Get the current image digest + let _current_digest = docker_cmd(&[ + "image", + "inspect", + "--format", + "{{index .RepoDigests 0}}", + IMAGE_NAME, + ]) + .unwrap_or_default(); + + // Pull latest + let pull_output = docker_cmd(&["pull", IMAGE_NAME])?; + + // Check if "Status: Image is up to date" is in the output + Ok(!pull_output.contains("Image is up to date")) +} + +pub fn remove_workspace_data() -> Result<(), String> { + docker_cmd(&["volume", "rm", VOLUME_NAME])?; + Ok(()) +} + +pub fn prune_images() -> Result { + docker_cmd(&["image", "prune", "-f"]) +} diff --git a/coeadapt-launcher/src-tauri/src/disk.rs b/coeadapt-launcher/src-tauri/src/disk.rs new file mode 100644 index 000000000..13c7693d0 --- /dev/null +++ b/coeadapt-launcher/src-tauri/src/disk.rs @@ -0,0 +1,70 @@ +use sysinfo::Disks; + +use crate::docker::docker_cmd; +use crate::state::{DiskStatus, DockerDiskUsage}; + +pub fn check_disk_space() -> DiskStatus { + let disks = Disks::new_with_refreshed_list(); + + for disk in disks.list() { + let mount = disk.mount_point().to_str().unwrap_or(""); + + // On Windows, check C:\. On macOS/Linux, check / + let is_target = if cfg!(target_os = "windows") { + mount.starts_with("C:") + } else { + mount == "/" + }; + + if is_target { + let available_gb = disk.available_space() as f64 / 1_073_741_824.0; + let total_gb = disk.total_space() as f64 / 1_073_741_824.0; + return DiskStatus { + available_gb: (available_gb * 10.0).round() / 10.0, + total_gb: (total_gb * 10.0).round() / 10.0, + meets_minimum: available_gb >= 15.0, + meets_recommended: available_gb >= 25.0, + is_low: available_gb < 5.0, + }; + } + } + + // Fallback if we can't determine + DiskStatus { + available_gb: 100.0, + total_gb: 500.0, + meets_minimum: true, + meets_recommended: true, + is_low: false, + } +} + +pub fn get_docker_disk_usage() -> Result { + let output = docker_cmd(&["system", "df", "--format", "{{.Type}}\t{{.Size}}"])?; + + let mut images_size = String::from("0B"); + let mut containers_size = String::from("0B"); + let mut volumes_size = String::from("0B"); + + for line in output.lines() { + let parts: Vec<&str> = line.split('\t').collect(); + if parts.len() >= 2 { + match parts[0] { + "Images" => images_size = parts[1].to_string(), + "Containers" => containers_size = parts[1].to_string(), + "Local Volumes" => volumes_size = parts[1].to_string(), + _ => {} + } + } + } + + Ok(DockerDiskUsage { + total_size: format!( + "Images: {}, Containers: {}, Volumes: {}", + images_size, containers_size, volumes_size + ), + images_size, + containers_size, + volumes_size, + }) +} diff --git a/coeadapt-launcher/src-tauri/src/docker.rs b/coeadapt-launcher/src-tauri/src/docker.rs new file mode 100644 index 000000000..7b214f040 --- /dev/null +++ b/coeadapt-launcher/src-tauri/src/docker.rs @@ -0,0 +1,155 @@ +use std::io::{BufRead, BufReader}; +use std::process::{Command, Stdio}; +use tauri::Emitter; + +use crate::state::{ContainerRuntime, DockerInfo, PullProgress}; + +pub fn detect_runtime() -> ContainerRuntime { + if docker_cmd(&["info"]).is_ok() { + ContainerRuntime::Docker + } else if Command::new("podman") + .arg("info") + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .map(|s| s.success()) + .unwrap_or(false) + { + ContainerRuntime::Podman + } else { + ContainerRuntime::None + } +} + +pub fn get_docker_info() -> Result { + let runtime = detect_runtime(); + match runtime { + ContainerRuntime::None => Ok(DockerInfo { + runtime: ContainerRuntime::None, + version: String::new(), + is_daemon_running: false, + }), + _ => { + let version = docker_cmd(&["version", "--format", "{{.Server.Version}}"]) + .unwrap_or_else(|_| "unknown".to_string()); + Ok(DockerInfo { + runtime: runtime.clone(), + version, + is_daemon_running: runtime != ContainerRuntime::None, + }) + } + } +} + +pub fn docker_cmd(args: &[&str]) -> Result { + let output = Command::new("docker") + .args(args) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .map_err(|e| format!("Failed to execute docker: {}", e))?; + + if output.status.success() { + Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) + } else { + Err(String::from_utf8_lossy(&output.stderr).trim().to_string()) + } +} + +pub fn docker_pull_streaming( + image: &str, + app_handle: tauri::AppHandle, +) -> Result<(), String> { + let mut child = Command::new("docker") + .args(["pull", image]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .map_err(|e| format!("Failed to spawn docker pull: {}", e))?; + + let stdout = child.stdout.take().unwrap(); + let reader = BufReader::new(stdout); + + for line in reader.lines() { + if let Ok(line) = line { + let progress = parse_pull_line(&line); + let _ = app_handle.emit("docker-pull-progress", &progress); + } + } + + let status = child.wait().map_err(|e| e.to_string())?; + if !status.success() { + return Err("Image pull failed".to_string()); + } + Ok(()) +} + +fn parse_pull_line(line: &str) -> PullProgress { + // Docker pull output looks like: + // "abc123: Pulling fs layer" + // "abc123: Downloading [===> ] 12.5MB/100MB" + // "abc123: Pull complete" + // "Digest: sha256:..." + // "Status: Downloaded newer image for ..." + let percent = if line.contains("Pull complete") || line.contains("Already exists") { + 100.0 + } else if line.contains('/') && (line.contains("Downloading") || line.contains("Extracting")) { + // Try to parse "12.5MB/100MB" style progress + if let Some(bracket_start) = line.find(']') { + if let Some(sizes) = line[bracket_start..].split_whitespace().nth(1) { + let parts: Vec<&str> = sizes.split('/').collect(); + if parts.len() == 2 { + let current = parse_size(parts[0]); + let total = parse_size(parts[1]); + if total > 0.0 { + return PullProgress { + status: line.to_string(), + progress: Some(sizes.to_string()), + percent: (current / total * 100.0).min(100.0), + }; + } + } + } + } + -1.0 + } else { + -1.0 + }; + + PullProgress { + status: line.to_string(), + progress: None, + percent, + } +} + +fn parse_size(s: &str) -> f64 { + let s = s.trim(); + if let Some(num) = s.strip_suffix("GB") { + num.parse::().unwrap_or(0.0) * 1024.0 + } else if let Some(num) = s.strip_suffix("MB") { + num.parse::().unwrap_or(0.0) + } else if let Some(num) = s.strip_suffix("kB") { + num.parse::().unwrap_or(0.0) / 1024.0 + } else if let Some(num) = s.strip_suffix("B") { + num.parse::().unwrap_or(0.0) / 1024.0 / 1024.0 + } else { + s.parse::().unwrap_or(0.0) + } +} + +#[cfg(target_os = "windows")] +pub fn is_wsl2_enabled() -> bool { + Command::new("wsl") + .args(["--list", "--verbose"]) + .stdout(Stdio::piped()) + .stderr(Stdio::null()) + .output() + .map(|o| o.status.success()) + .unwrap_or(false) +} + +#[cfg(not(target_os = "windows"))] +pub fn is_wsl2_enabled() -> bool { + true // Not applicable on non-Windows +} diff --git a/coeadapt-launcher/src-tauri/src/health.rs b/coeadapt-launcher/src-tauri/src/health.rs new file mode 100644 index 000000000..cfc57d614 --- /dev/null +++ b/coeadapt-launcher/src-tauri/src/health.rs @@ -0,0 +1,76 @@ +use std::time::Duration; +use tauri::Emitter; + +pub async fn wait_for_workspace( + app: tauri::AppHandle, + timeout: Duration, +) -> Result<(), String> { + let client = reqwest::Client::builder() + .danger_accept_invalid_certs(true) + .timeout(Duration::from_secs(5)) + .build() + .map_err(|e| e.to_string())?; + + let start = std::time::Instant::now(); + let mut attempt = 0u32; + + while start.elapsed() < timeout { + attempt += 1; + let _ = app.emit( + "health-check", + serde_json::json!({ + "attempt": attempt, + "elapsed_secs": start.elapsed().as_secs(), + }), + ); + + match client.get("https://localhost:6901").send().await { + Ok(resp) if resp.status().is_success() || resp.status().is_redirection() => { + let _ = app.emit("workspace-ready", true); + return Ok(()); + } + _ => { + tokio::time::sleep(Duration::from_secs(2)).await; + } + } + } + + Err(format!( + "Workspace not ready after {}s", + timeout.as_secs() + )) +} + +pub async fn check_mcp_health() -> bool { + get_mcp_health_info().await.is_running +} + +pub async fn get_mcp_health_info() -> crate::state::McpHealthInfo { + let client = reqwest::Client::builder() + .timeout(Duration::from_secs(3)) + .build() + .unwrap_or_default(); + + match client.get("http://127.0.0.1:3100/health").send().await { + Ok(resp) if resp.status().is_success() => { + if let Ok(body) = resp.json::().await { + crate::state::McpHealthInfo { + is_running: true, + last_tool_call: body.get("lastToolCall").and_then(|v| v.as_u64()), + uptime_secs: body.get("uptime").and_then(|v| v.as_f64()), + } + } else { + crate::state::McpHealthInfo { + is_running: true, + last_tool_call: None, + uptime_secs: None, + } + } + } + _ => crate::state::McpHealthInfo { + is_running: false, + last_tool_call: None, + uptime_secs: None, + }, + } +} diff --git a/coeadapt-launcher/src-tauri/src/lib.rs b/coeadapt-launcher/src-tauri/src/lib.rs new file mode 100644 index 000000000..684a3de3d --- /dev/null +++ b/coeadapt-launcher/src-tauri/src/lib.rs @@ -0,0 +1,226 @@ +mod claude; +mod commands; +mod container; +mod disk; +mod docker; +mod health; +mod mcp; +mod ssl; +mod state; + +use tauri::{ + menu::{Menu, MenuItem, PredefinedMenuItem}, + tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent}, + Emitter, Manager, +}; + +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + tauri::Builder::default() + .plugin(tauri_plugin_opener::init()) + .plugin(tauri_plugin_shell::init()) + .plugin(tauri_plugin_store::Builder::new().build()) + .plugin(tauri_plugin_autostart::init( + tauri_plugin_autostart::MacosLauncher::LaunchAgent, + None, + )) + .setup(|app| { + // Build tray menu + let open_workspace = MenuItem::with_id(app, "open_workspace", "Open Workspace", true, None::<&str>)?; + let start = MenuItem::with_id(app, "start", "Start Workspace", true, None::<&str>)?; + let stop = MenuItem::with_id(app, "stop", "Stop Workspace", true, None::<&str>)?; + + let sep1 = PredefinedMenuItem::separator(app)?; + + let ai_status = MenuItem::with_id(app, "ai_status", "AI Copilot: Checking...", false, None::<&str>)?; + let reconnect_claude = MenuItem::with_id(app, "reconnect_claude", "Reconnect to Claude", true, None::<&str>)?; + + let sep2 = PredefinedMenuItem::separator(app)?; + + let settings = MenuItem::with_id(app, "settings", "Settings", true, None::<&str>)?; + let show_window = MenuItem::with_id(app, "show", "Show Dashboard", true, None::<&str>)?; + + let sep3 = PredefinedMenuItem::separator(app)?; + + let quit = MenuItem::with_id(app, "quit", "Quit Coeadapt", true, None::<&str>)?; + + let menu = Menu::with_items( + app, + &[ + &open_workspace, &start, &stop, + &sep1, + &ai_status, &reconnect_claude, + &sep2, + &settings, &show_window, + &sep3, + &quit, + ], + )?; + + // Clone ai_status for the polling task + let ai_status_item = ai_status.clone(); + + let _tray = TrayIconBuilder::new() + .icon(app.default_window_icon().unwrap().clone()) + .menu(&menu) + .tooltip("Coeadapt") + .on_menu_event(move |app, event| match event.id.as_ref() { + "open_workspace" => { + let _ = commands::open_workspace_browser(); + } + "start" => { + let status = container::get_container_status(); + match status.state { + state::ContainerState::NotFound => { + let _ = container::create_container(); + } + state::ContainerState::Stopped => { + let _ = container::start_container(); + } + _ => {} + } + } + "stop" => { + let _ = container::stop_container(); + } + "reconnect_claude" => { + let _ = claude::verify_and_repair_config(); + } + "settings" => { + if let Some(window) = app.get_webview_window("main") { + let _ = window.show(); + let _ = window.set_focus(); + let _ = app.emit("navigate", "/settings"); + } + } + "show" => { + if let Some(window) = app.get_webview_window("main") { + let _ = window.show(); + let _ = window.set_focus(); + } + } + "quit" => { + let _ = mcp::stop_mcp_sidecar(); + let _ = container::stop_container(); + app.exit(0); + } + _ => {} + }) + .on_tray_icon_event(|tray, event| { + if let TrayIconEvent::Click { + button: MouseButton::Left, + button_state: MouseButtonState::Up, + .. + } = event + { + let app = tray.app_handle(); + if let Some(window) = app.get_webview_window("main") { + let _ = window.show(); + let _ = window.set_focus(); + } + } + }) + .build(app)?; + + // Dynamic AI status in tray (every 15 seconds) + tauri::async_runtime::spawn(async move { + loop { + let healthy = health::check_mcp_health().await; + let text = if healthy { + "AI Copilot: Connected" + } else { + "AI Copilot: Disconnected" + }; + let _ = ai_status_item.set_text(text); + tokio::time::sleep(std::time::Duration::from_secs(15)).await; + } + }); + + // On launch: verify Claude config + let _ = claude::verify_and_repair_config(); + + // Start MCP sidecar + let handle_for_mcp = app.handle().clone(); + if let Err(e) = mcp::start_mcp_sidecar(&handle_for_mcp) { + eprintln!("[mcp] Warning: Failed to start MCP sidecar: {}", e); + } + + // Auto-start workspace if setting is enabled + { + use tauri_plugin_store::StoreExt; + let handle_for_autostart = app.handle().clone(); + if let Ok(store) = handle_for_autostart.store("settings.json") { + let auto_start = store + .get("autoStartWorkspace") + .and_then(|v| v.as_bool()) + .unwrap_or(false); + if auto_start { + tauri::async_runtime::spawn(async move { + // Delay to let Docker daemon initialize + tokio::time::sleep(std::time::Duration::from_secs(3)).await; + let status = container::get_container_status(); + match status.state { + crate::state::ContainerState::Stopped => { + let _ = container::start_container(); + } + crate::state::ContainerState::NotFound => { + if container::image_exists() { + let _ = container::create_container(); + } + } + _ => {} + } + }); + } + } + } + + // Start disk monitoring (every 30 min) + let handle = app.handle().clone(); + tauri::async_runtime::spawn(async move { + loop { + let status = disk::check_disk_space(); + if status.is_low { + let _ = handle.emit("disk-warning", &status); + } + tokio::time::sleep(std::time::Duration::from_secs(1800)).await; + } + }); + + Ok(()) + }) + .on_window_event(|window, event| { + // Hide to tray on close instead of quitting + if let tauri::WindowEvent::CloseRequested { api, .. } = event { + let _ = window.hide(); + api.prevent_close(); + } + }) + .invoke_handler(tauri::generate_handler![ + commands::detect_container_runtime, + commands::check_wsl2_status, + commands::check_disk_space, + commands::get_docker_disk_usage, + commands::prune_docker_images, + commands::get_workspace_status, + commands::check_image_exists, + commands::pull_workspace_image, + commands::create_workspace, + commands::start_workspace, + commands::stop_workspace, + commands::reset_workspace, + commands::wait_for_workspace_ready, + commands::check_mcp_status, + commands::get_mcp_health, + commands::get_claude_status, + commands::configure_claude, + commands::open_workspace_browser, + commands::check_ssl_trust, + commands::install_ssl_certificate, + commands::uninstall_ssl_certificate, + commands::start_mcp, + commands::stop_mcp, + ]) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} diff --git a/coeadapt-launcher/src-tauri/src/main.rs b/coeadapt-launcher/src-tauri/src/main.rs new file mode 100644 index 000000000..fa63e10fc --- /dev/null +++ b/coeadapt-launcher/src-tauri/src/main.rs @@ -0,0 +1,6 @@ +// Prevents additional console window on Windows in release, DO NOT REMOVE!! +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +fn main() { + coeadapt_launcher_lib::run() +} diff --git a/coeadapt-launcher/src-tauri/src/mcp.rs b/coeadapt-launcher/src-tauri/src/mcp.rs new file mode 100644 index 000000000..283722a57 --- /dev/null +++ b/coeadapt-launcher/src-tauri/src/mcp.rs @@ -0,0 +1,101 @@ +use std::sync::Mutex; +use tauri::Manager; +use tauri_plugin_shell::ShellExt; +use tauri_plugin_shell::process::{CommandChild, CommandEvent}; +use tauri_plugin_store::StoreExt; + +/// Global singleton holding the running sidecar child process. +/// Option so we can .take() it when killing (kill consumes self). +static MCP_CHILD: Mutex> = Mutex::new(None); + +/// Spawn the MCP sidecar binary. Idempotent — does nothing if already running. +pub fn start_mcp_sidecar(app: &tauri::AppHandle) -> Result<(), String> { + let mut guard = MCP_CHILD.lock().map_err(|e| e.to_string())?; + + // Already have a child handle — assume it's still running + if guard.is_some() { + return Ok(()); + } + + // Read device token and API URL from Tauri store for Cora API access + let device_token = app + .store("auth.json") + .ok() + .and_then(|store| store.get("deviceToken")) + .and_then(|v| v.as_str().map(|s| s.to_string())) + .unwrap_or_default(); + + let api_url = std::env::var("COEADAPT_API_URL") + .unwrap_or_else(|_| "https://api.coeadapt.com".to_string()); + + let sidecar_command = app + .shell() + .sidecar("coeadapt-mcp") + .map_err(|e| format!("Failed to create sidecar command: {}", e))? + .env("COEADAPT_DEVICE_TOKEN", &device_token) + .env("COEADAPT_API_URL", &api_url); + + let (mut rx, child) = sidecar_command + .spawn() + .map_err(|e| format!("Failed to spawn MCP sidecar: {}", e))?; + + eprintln!("[mcp] Sidecar started (pid {})", child.pid()); + + *guard = Some(child); + drop(guard); // Release lock before spawning the reader task + + // Drain stdout/stderr and auto-restart on termination + let app_handle = app.clone(); + tauri::async_runtime::spawn(async move { + while let Some(event) = rx.recv().await { + match event { + CommandEvent::Stdout(bytes) => { + let line = String::from_utf8_lossy(&bytes); + eprintln!("[mcp-stdout] {}", line.trim()); + } + CommandEvent::Stderr(bytes) => { + let line = String::from_utf8_lossy(&bytes); + eprintln!("[mcp-stderr] {}", line.trim()); + } + CommandEvent::Terminated(payload) => { + eprintln!( + "[mcp] Sidecar terminated (code: {:?}, signal: {:?})", + payload.code, payload.signal + ); + // Clear the stored child + if let Ok(mut guard) = MCP_CHILD.lock() { + *guard = None; + } + // Auto-restart after a short delay + tokio::time::sleep(std::time::Duration::from_secs(2)).await; + eprintln!("[mcp] Auto-restarting sidecar..."); + if let Err(e) = start_mcp_sidecar(&app_handle) { + eprintln!("[mcp] Auto-restart failed: {}", e); + } + break; + } + _ => {} + } + } + }); + + Ok(()) +} + +/// Stop the MCP sidecar. Idempotent — does nothing if not running. +pub fn stop_mcp_sidecar() -> Result<(), String> { + let mut guard = MCP_CHILD.lock().map_err(|e| e.to_string())?; + if let Some(child) = guard.take() { + child.kill().map_err(|e| format!("Failed to kill MCP sidecar: {}", e))?; + eprintln!("[mcp] Sidecar stopped"); + } + Ok(()) +} + +/// Check if the MCP sidecar child handle is present. +pub fn is_mcp_running() -> bool { + MCP_CHILD + .lock() + .map(|guard| guard.is_some()) + .unwrap_or(false) +} diff --git a/coeadapt-launcher/src-tauri/src/ssl.rs b/coeadapt-launcher/src-tauri/src/ssl.rs new file mode 100644 index 000000000..805bd0af9 --- /dev/null +++ b/coeadapt-launcher/src-tauri/src/ssl.rs @@ -0,0 +1,235 @@ +use std::path::PathBuf; +use std::process::Command; + +use crate::docker::docker_cmd; +use crate::state::CONTAINER_NAME; + +const CA_CONTAINER_PATH: &str = "/usr/share/coeadapt/ca.crt"; +const CA_CERT_FILENAME: &str = "coeadapt-workspace-ca.crt"; +const CA_SUBJECT_NAME: &str = "Coeadapt Workspace CA"; + +/// Local directory where we store the extracted CA cert on the host. +fn ca_data_dir() -> PathBuf { + let dir = dirs::data_local_dir() + .unwrap_or_else(|| PathBuf::from(".")) + .join("coeadapt-launcher"); + std::fs::create_dir_all(&dir).ok(); + dir +} + +/// Full path to the CA cert on the host. +fn ca_cert_path() -> PathBuf { + ca_data_dir().join(CA_CERT_FILENAME) +} + +/// Extract the CA certificate from the running container to the host. +pub fn extract_ca_cert() -> Result { + let dest = ca_cert_path(); + let dest_str = dest.to_str().ok_or("Invalid path")?; + let src = format!("{}:{}", CONTAINER_NAME, CA_CONTAINER_PATH); + + docker_cmd(&["cp", &src, dest_str])?; + + if dest.exists() { + Ok(dest) + } else { + Err("CA cert was not extracted".to_string()) + } +} + +/// Check whether the Coeadapt CA is already trusted by the host OS. +pub fn is_ca_installed() -> bool { + #[cfg(target_os = "windows")] + return is_ca_installed_windows(); + + #[cfg(target_os = "macos")] + return is_ca_installed_macos(); + + #[cfg(target_os = "linux")] + return is_ca_installed_linux(); + + #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))] + return false; +} + +/// Install the CA certificate into the host OS trust store. +/// On Windows this triggers a security confirmation dialog. +pub fn install_ca_cert() -> Result<(), String> { + let cert_path = ca_cert_path(); + + // Extract first if not already on disk + if !cert_path.exists() { + extract_ca_cert()?; + } + + #[cfg(any(target_os = "windows", target_os = "macos"))] + let cert_str = cert_path.to_str().ok_or("Invalid cert path")?; + + #[cfg(target_os = "windows")] + return install_ca_windows(cert_str); + + #[cfg(target_os = "macos")] + return install_ca_macos(cert_str); + + #[cfg(target_os = "linux")] + return install_ca_linux(&cert_path); + + #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))] + return Err("Unsupported platform".to_string()); +} + +/// Remove the Coeadapt CA from the host trust store. +pub fn uninstall_ca_cert() -> Result<(), String> { + #[cfg(target_os = "windows")] + uninstall_ca_windows()?; + + #[cfg(target_os = "macos")] + uninstall_ca_macos()?; + + #[cfg(target_os = "linux")] + uninstall_ca_linux(); + + // Remove local copy + let _ = std::fs::remove_file(ca_cert_path()); + Ok(()) +} + +// --- Platform-specific implementations --- + +#[cfg(target_os = "windows")] +fn is_ca_installed_windows() -> bool { + let output = Command::new("powershell") + .args([ + "-NoProfile", + "-Command", + &format!( + "Get-ChildItem Cert:\\CurrentUser\\Root | Where-Object {{ $_.Subject -like '*{}*' }} | Measure-Object | Select-Object -ExpandProperty Count", + CA_SUBJECT_NAME + ), + ]) + .output(); + + match output { + Ok(out) if out.status.success() => { + let count = String::from_utf8_lossy(&out.stdout).trim().to_string(); + count != "0" + } + _ => false, + } +} + +#[cfg(target_os = "windows")] +fn install_ca_windows(cert_str: &str) -> Result<(), String> { + let status = Command::new("certutil") + .args(["-addstore", "-user", "Root", cert_str]) + .status() + .map_err(|e| format!("Failed to run certutil: {}", e))?; + + if status.success() { + Ok(()) + } else { + Err("Certificate installation failed. Please accept the security prompt.".to_string()) + } +} + +#[cfg(target_os = "windows")] +fn uninstall_ca_windows() -> Result<(), String> { + let status = Command::new("powershell") + .args([ + "-NoProfile", + "-Command", + &format!( + "Get-ChildItem Cert:\\CurrentUser\\Root | Where-Object {{ $_.Subject -like '*{}*' }} | Remove-Item", + CA_SUBJECT_NAME + ), + ]) + .status() + .map_err(|e| format!("Failed to remove CA: {}", e))?; + + if status.success() { + Ok(()) + } else { + Err("Failed to remove CA certificate from trust store".to_string()) + } +} + +#[cfg(target_os = "macos")] +fn is_ca_installed_macos() -> bool { + let output = Command::new("security") + .args(["find-certificate", "-c", CA_SUBJECT_NAME, "-a"]) + .output(); + + matches!(output, Ok(out) if out.status.success()) +} + +#[cfg(target_os = "macos")] +fn install_ca_macos(cert_str: &str) -> Result<(), String> { + let home = std::env::var("HOME") + .map_err(|_| "HOME environment variable not set".to_string())?; + let keychain = format!("{}/Library/Keychains/login.keychain-db", home); + + let status = Command::new("security") + .args([ + "add-trusted-cert", + "-r", + "trustRoot", + "-k", + &keychain, + cert_str, + ]) + .status() + .map_err(|e| format!("Failed to install CA: {}", e))?; + + if status.success() { + Ok(()) + } else { + Err("Certificate installation failed".to_string()) + } +} + +#[cfg(target_os = "macos")] +fn uninstall_ca_macos() -> Result<(), String> { + let cert_path = ca_cert_path(); + if cert_path.exists() { + if let Some(cert_str) = cert_path.to_str() { + let _ = Command::new("security") + .args(["remove-trusted-cert", cert_str]) + .status(); + } + } + Ok(()) +} + +#[cfg(target_os = "linux")] +fn is_ca_installed_linux() -> bool { + std::path::Path::new(&format!( + "/usr/local/share/ca-certificates/{}", + CA_CERT_FILENAME + )) + .exists() +} + +#[cfg(target_os = "linux")] +fn install_ca_linux(cert_path: &std::path::Path) -> Result<(), String> { + let dest = format!("/usr/local/share/ca-certificates/{}", CA_CERT_FILENAME); + let cert_str = cert_path.to_str().unwrap_or_default(); + + // Use pkexec for privilege elevation (shows a graphical password prompt) + let status = Command::new("pkexec") + .args(["bash", "-c", &format!("cp '{}' '{}' && update-ca-certificates", cert_str, dest)]) + .status() + .map_err(|e| format!("Failed to install CA cert: {}", e))?; + + if status.success() { + Ok(()) + } else { + Err("Failed to install CA cert (authentication required)".to_string()) + } +} + +#[cfg(target_os = "linux")] +fn uninstall_ca_linux() { + let sys_cert = format!("/usr/local/share/ca-certificates/{}", CA_CERT_FILENAME); + let _ = std::fs::remove_file(&sys_cert); + let _ = Command::new("update-ca-certificates").status(); +} diff --git a/coeadapt-launcher/src-tauri/src/state.rs b/coeadapt-launcher/src-tauri/src/state.rs new file mode 100644 index 000000000..5d9060ed4 --- /dev/null +++ b/coeadapt-launcher/src-tauri/src/state.rs @@ -0,0 +1,76 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum ContainerRuntime { + Docker, + Podman, + None, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DockerInfo { + pub runtime: ContainerRuntime, + pub version: String, + pub is_daemon_running: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum ContainerState { + NotFound, + Running, + Stopped, + Starting, + Pulling, + Error(String), +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ContainerStatus { + pub state: ContainerState, + pub container_id: Option, + pub uptime: Option, + pub image: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DiskStatus { + pub available_gb: f64, + pub total_gb: f64, + pub meets_minimum: bool, + pub meets_recommended: bool, + pub is_low: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DockerDiskUsage { + pub images_size: String, + pub containers_size: String, + pub volumes_size: String, + pub total_size: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ClaudeStatus { + pub is_installed: bool, + pub config_path: Option, + pub is_configured: bool, + pub needs_restart: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PullProgress { + pub status: String, + pub progress: Option, + pub percent: f64, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct McpHealthInfo { + pub is_running: bool, + pub last_tool_call: Option, + pub uptime_secs: Option, +} + +pub const CONTAINER_NAME: &str = "coeadapt-workspace"; +pub const IMAGE_NAME: &str = "coeadapt/workspace:latest"; +pub const VOLUME_NAME: &str = "coeadapt-data"; diff --git a/coeadapt-launcher/src-tauri/tauri.conf.json b/coeadapt-launcher/src-tauri/tauri.conf.json new file mode 100644 index 000000000..3131b8695 --- /dev/null +++ b/coeadapt-launcher/src-tauri/tauri.conf.json @@ -0,0 +1,53 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "productName": "Coeadapt", + "version": "0.1.0", + "identifier": "com.coeadapt.launcher", + "build": { + "beforeDevCommand": "bun run dev", + "devUrl": "http://localhost:1420", + "beforeBuildCommand": "bun run build", + "frontendDist": "../dist" + }, + "app": { + "windows": [ + { + "title": "Coeadapt", + "width": 800, + "height": 600, + "resizable": true, + "visible": true + } + ], + "trayIcon": { + "iconPath": "icons/icon.png", + "iconAsTemplate": true, + "tooltip": "Coeadapt" + }, + "security": { + "csp": "default-src 'self'; connect-src 'self' https://localhost:6901 http://127.0.0.1:3100 https://api.coeadapt.com http://localhost:5000 https://*.clerk.accounts.dev https://*.clerk.com; img-src 'self' data: https://*.clerk.accounts.dev https://*.clerk.com https://img.clerk.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; frame-src https://*.clerk.accounts.dev https://*.clerk.com; script-src 'self' https://*.clerk.accounts.dev https://*.clerk.com" + } + }, + "bundle": { + "active": true, + "targets": "all", + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ], + "externalBin": [ + "binaries/coeadapt-mcp" + ] + }, + "plugins": { + "updater": { + "endpoints": [ + "https://releases.coeadapt.com/tauri/{{target}}/{{arch}}/{{current_version}}" + ], + "pubkey": "REPLACE_WITH_REAL_PUBKEY_BEFORE_RELEASE" + } + } +} diff --git a/coeadapt-launcher/src/App.css b/coeadapt-launcher/src/App.css new file mode 100644 index 000000000..e0598b63e --- /dev/null +++ b/coeadapt-launcher/src/App.css @@ -0,0 +1 @@ +/* Unused - styles are in index.css with Tailwind */ diff --git a/coeadapt-launcher/src/App.tsx b/coeadapt-launcher/src/App.tsx new file mode 100644 index 000000000..c333044d6 --- /dev/null +++ b/coeadapt-launcher/src/App.tsx @@ -0,0 +1,64 @@ +import { useEffect } from "react"; +import { BrowserRouter, Routes, Route, Navigate, useNavigate } from "react-router-dom"; +import { useAuth } from "@clerk/clerk-react"; +import { STANDALONE_MODE } from "./lib/mode"; +import { DiskWarningBanner } from "./components/DiskWarningBanner"; +import { AuthGuard } from "./components/AuthGuard"; +import { safeListen } from "./lib/tauri"; +import { setAuthProvider } from "./lib/api"; +import { useDeviceToken } from "./hooks/useDeviceToken"; +import Login from "./pages/Login"; +import Setup from "./pages/Setup"; +import Dashboard from "./pages/Dashboard"; +import ClaudeSetup from "./pages/ClaudeSetup"; +import Settings from "./pages/Settings"; +import Chat from "./pages/Chat"; + +/** Listens for "navigate" events from the Rust tray menu and routes accordingly. */ +function TrayNavigationListener() { + const navigate = useNavigate(); + useEffect(() => { + const unlisten = safeListen("navigate", (event) => { + navigate(event.payload); + }); + return () => { unlisten.then((fn) => fn()); }; + }, [navigate]); + return null; +} + +/** Wires Clerk auth into the API client and auto-generates device token. */ +function AuthWiring() { + const { getToken, isSignedIn } = useAuth(); + useDeviceToken(); // Auto-generates and stores device token after sign-in + + useEffect(() => { + if (isSignedIn) { + setAuthProvider(getToken); + } + }, [isSignedIn, getToken]); + + return null; +} + +function App() { + return ( + + + {!STANDALONE_MODE && } +
+ + + {!STANDALONE_MODE && } />} + } /> + } /> + } /> + } /> + {!STANDALONE_MODE && } />} + } /> + +
+
+ ); +} + +export default App; diff --git a/coeadapt-launcher/src/assets/react.svg b/coeadapt-launcher/src/assets/react.svg new file mode 100644 index 000000000..6c87de9bb --- /dev/null +++ b/coeadapt-launcher/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/coeadapt-launcher/src/components/AuthGuard.tsx b/coeadapt-launcher/src/components/AuthGuard.tsx new file mode 100644 index 000000000..0e24d3560 --- /dev/null +++ b/coeadapt-launcher/src/components/AuthGuard.tsx @@ -0,0 +1,32 @@ +import { useAuth } from "@clerk/clerk-react"; +import { Navigate } from "react-router-dom"; +import { STANDALONE_MODE } from "../lib/mode"; +import { Spinner } from "./Spinner"; + +/** + * In CoeAdapt mode, delegates to ClerkAuthGuard which uses the useAuth hook. + * In standalone mode, renders children immediately with no auth check. + */ +export function AuthGuard({ children }: { children: React.ReactNode }) { + if (STANDALONE_MODE) return <>{children}; + return {children}; +} + +/** Separated component so useAuth hook is always called (Rules of Hooks). */ +function ClerkAuthGuard({ children }: { children: React.ReactNode }) { + const { isSignedIn, isLoaded } = useAuth(); + + if (!isLoaded) { + return ( +
+ +
+ ); + } + + if (!isSignedIn) { + return ; + } + + return <>{children}; +} diff --git a/coeadapt-launcher/src/components/DiskUsage.tsx b/coeadapt-launcher/src/components/DiskUsage.tsx new file mode 100644 index 000000000..f3072dbb8 --- /dev/null +++ b/coeadapt-launcher/src/components/DiskUsage.tsx @@ -0,0 +1,33 @@ +import type { DiskStatus } from "../lib/types"; + +interface Props { + status: DiskStatus; +} + +export function DiskUsage({ status }: Props) { + const usedGb = status.total_gb - status.available_gb; + const usedPercent = (usedGb / status.total_gb) * 100; + + const barColor = status.is_low + ? "bg-danger" + : !status.meets_recommended + ? "bg-warning" + : "bg-success"; + + return ( +
+
+ Storage + + {status.available_gb} GB free of {status.total_gb} GB + +
+
+
+
+
+ ); +} diff --git a/coeadapt-launcher/src/components/DiskWarningBanner.tsx b/coeadapt-launcher/src/components/DiskWarningBanner.tsx new file mode 100644 index 000000000..42a83e9fc --- /dev/null +++ b/coeadapt-launcher/src/components/DiskWarningBanner.tsx @@ -0,0 +1,28 @@ +import { useDiskSpace } from "../hooks/useDiskSpace"; + +export function DiskWarningBanner() { + const { status, showWarning, dismissWarning } = useDiskSpace(); + + if (!showWarning || !status) return null; + + return ( +
+
+ + + + + Low disk space ({status.available_gb} GB remaining) + +
+ +
+ ); +} diff --git a/coeadapt-launcher/src/components/ProgressBar.tsx b/coeadapt-launcher/src/components/ProgressBar.tsx new file mode 100644 index 000000000..787682f41 --- /dev/null +++ b/coeadapt-launcher/src/components/ProgressBar.tsx @@ -0,0 +1,32 @@ +interface Props { + percent: number; + label?: string; + indeterminate?: boolean; +} + +export function ProgressBar({ percent, label, indeterminate }: Props) { + return ( +
+ {label && ( +
+ {label} + {!indeterminate && ( + + {Math.round(percent)}% + + )} +
+ )} +
+ {indeterminate ? ( +
+ ) : ( +
+ )} +
+
+ ); +} diff --git a/coeadapt-launcher/src/components/ProgressCard.tsx b/coeadapt-launcher/src/components/ProgressCard.tsx new file mode 100644 index 000000000..c33bcda1c --- /dev/null +++ b/coeadapt-launcher/src/components/ProgressCard.tsx @@ -0,0 +1,107 @@ +import type { ProgressSummary, AgentHealthInfo } from "../lib/types"; +import { ProgressBar } from "./ProgressBar"; +import { StatusIndicator } from "./StatusIndicator"; + +interface ProgressCardProps { + summary: ProgressSummary | null; + agentHealth: AgentHealthInfo | null; + loading: boolean; +} + +export function ProgressCard({ summary, agentHealth, loading }: ProgressCardProps) { + if (loading && !summary) { + return ( +
+
+
+ + + +
+
+

Career Progress

+

Loading...

+
+
+
+ ); + } + + if (!summary) { + return null; + } + + const hasActivity = summary.total_activities > 0 || summary.total_goals > 0; + + return ( +
+ {/* Header */} +
+
+ + + +
+
+

Career Progress

+ {hasActivity ? ( +

+ {summary.progress_percent}% complete +

+ ) : ( +

Get started with your first activity

+ )} +
+ {summary.streak_days > 0 && ( +
+ {summary.streak_days} +

day streak

+
+ )} +
+ + {/* Progress bar */} + {hasActivity && ( + + )} + + {/* Stats grid */} +
+ + + + +
+ + {/* Agent services status (compact) */} + {agentHealth && ( +
+ + +
+ )} + + {/* Empty state */} + {!hasActivity && ( +

+ Use the AI copilot to log activities, set goals, and track your career development. +

+ )} +
+ ); +} + +function StatBox({ label, value }: { label: string; value: string | number }) { + return ( +
+
{value}
+
{label}
+
+ ); +} diff --git a/coeadapt-launcher/src/components/Spinner.tsx b/coeadapt-launcher/src/components/Spinner.tsx new file mode 100644 index 000000000..bf7a12f58 --- /dev/null +++ b/coeadapt-launcher/src/components/Spinner.tsx @@ -0,0 +1,22 @@ +export function Spinner({ size = "md" }: { size?: "sm" | "md" | "lg" }) { + const dims = { sm: "w-5 h-5", md: "w-8 h-8", lg: "w-12 h-12" }; + return ( +
+ + + + + + + + + + +
+ ); +} diff --git a/coeadapt-launcher/src/components/StatusIndicator.tsx b/coeadapt-launcher/src/components/StatusIndicator.tsx new file mode 100644 index 000000000..9b896be13 --- /dev/null +++ b/coeadapt-launcher/src/components/StatusIndicator.tsx @@ -0,0 +1,25 @@ +interface Props { + status: "running" | "starting" | "stopped" | "error"; + label: string; +} + +export function StatusIndicator({ status, label }: Props) { + const dotColor = { + running: "bg-success", + starting: "bg-warning animate-pulse", + stopped: "bg-surface-500", + error: "bg-danger", + }; + + return ( +
+ + {status === "running" && ( + + )} + + + {label} +
+ ); +} diff --git a/coeadapt-launcher/src/components/ToggleSwitch.tsx b/coeadapt-launcher/src/components/ToggleSwitch.tsx new file mode 100644 index 000000000..a9c5707a9 --- /dev/null +++ b/coeadapt-launcher/src/components/ToggleSwitch.tsx @@ -0,0 +1,33 @@ +interface Props { + label: string; + description?: string; + checked: boolean; + onChange: (value: boolean) => void; + disabled?: boolean; +} + +export function ToggleSwitch({ label, description, checked, onChange, disabled }: Props) { + return ( +
+
+

{label}

+ {description &&

{description}

} +
+ +
+ ); +} diff --git a/coeadapt-launcher/src/components/WorkspaceControls.tsx b/coeadapt-launcher/src/components/WorkspaceControls.tsx new file mode 100644 index 000000000..db3317309 --- /dev/null +++ b/coeadapt-launcher/src/components/WorkspaceControls.tsx @@ -0,0 +1,58 @@ +interface Props { + isRunning: boolean; + isStopped: boolean; + loading: boolean; + onStart: () => void; + onStop: () => void; + onOpen: () => void; +} + +export function WorkspaceControls({ + isRunning, + isStopped, + loading, + onStart, + onStop, + onOpen, +}: Props) { + return ( +
+ {isRunning && ( + <> + + + + )} + {isStopped && ( + + )} +
+ ); +} diff --git a/coeadapt-launcher/src/hooks/useClaudeConnection.ts b/coeadapt-launcher/src/hooks/useClaudeConnection.ts new file mode 100644 index 000000000..41f508332 --- /dev/null +++ b/coeadapt-launcher/src/hooks/useClaudeConnection.ts @@ -0,0 +1,48 @@ +import { useState, useEffect, useCallback } from "react"; +import { tauri } from "../lib/tauri"; +import type { ClaudeStatus, McpHealthInfo } from "../lib/types"; + +export function useClaudeConnection() { + const [status, setStatus] = useState(null); + const [mcpConnected, setMcpConnected] = useState(false); + const [mcpHealth, setMcpHealth] = useState(null); + const [configuring, setConfiguring] = useState(false); + + const refresh = useCallback(async () => { + try { + const claudeStatus = await tauri.getClaudeStatus(); + setStatus(claudeStatus); + const health = await tauri.getMcpHealth(); + setMcpConnected(health.is_running); + setMcpHealth(health); + } catch { + // Ignore + } + }, []); + + useEffect(() => { + refresh(); + const interval = setInterval(refresh, 30000); + return () => clearInterval(interval); + }, [refresh]); + + const configureClaude = useCallback(async () => { + setConfiguring(true); + try { + await tauri.configureClaude(); + await refresh(); + } catch { + // Ignore + } finally { + setConfiguring(false); + } + }, [refresh]); + + // MCP is connected but no tool calls for 5+ minutes + const isIdle = + mcpConnected && + mcpHealth?.last_tool_call != null && + Date.now() - mcpHealth.last_tool_call > 5 * 60 * 1000; + + return { status, mcpConnected, mcpHealth, isIdle, configuring, configureClaude, refresh }; +} diff --git a/coeadapt-launcher/src/hooks/useContainer.ts b/coeadapt-launcher/src/hooks/useContainer.ts new file mode 100644 index 000000000..b4cf6410e --- /dev/null +++ b/coeadapt-launcher/src/hooks/useContainer.ts @@ -0,0 +1,141 @@ +import { useState, useEffect, useCallback } from "react"; +import { tauri, safeListen } from "../lib/tauri"; +import type { ContainerStatus, PullProgress } from "../lib/types"; + +export function useContainer() { + const [status, setStatus] = useState(null); + const [pullProgress, setPullProgress] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [sslTrusted, setSslTrusted] = useState(null); + const [sslInstalling, setSslInstalling] = useState(false); + + const refresh = useCallback(async () => { + try { + const s = await tauri.getWorkspaceStatus(); + setStatus(s); + setError(null); + } catch { + // Not in Tauri or command failed + } + }, []); + + const checkSsl = useCallback(async () => { + try { + const trusted = await tauri.checkSslTrust(); + setSslTrusted(trusted); + return trusted; + } catch { + setSslTrusted(null); + return false; + } + }, []); + + useEffect(() => { + refresh(); + + const unlistenPull = safeListen("docker-pull-progress", (event) => { + setPullProgress(event.payload); + }); + const unlistenReady = safeListen("workspace-ready", () => { + refresh(); + checkSsl(); + }); + + const interval = setInterval(refresh, 10000); + + return () => { + unlistenPull.then((fn) => fn()); + unlistenReady.then((fn) => fn()); + clearInterval(interval); + }; + }, [refresh, checkSsl]); + + const pullImage = useCallback(async () => { + setLoading(true); + setError(null); + try { + await tauri.pullWorkspaceImage(); + } catch (e) { + setError(String(e)); + } finally { + setLoading(false); + setPullProgress(null); + refresh(); + } + }, [refresh]); + + const createWorkspace = useCallback(async () => { + setLoading(true); + setError(null); + try { + await tauri.createWorkspace(); + refresh(); + } catch (e) { + setError(String(e)); + } finally { + setLoading(false); + } + }, [refresh]); + + const startWorkspace = useCallback(async () => { + setLoading(true); + setError(null); + try { + await tauri.startWorkspace(); + refresh(); + } catch (e) { + setError(String(e)); + } finally { + setLoading(false); + } + }, [refresh]); + + const stopWorkspace = useCallback(async () => { + setLoading(true); + try { + await tauri.stopWorkspace(); + refresh(); + } catch (e) { + setError(String(e)); + } finally { + setLoading(false); + } + }, [refresh]); + + const openWorkspace = useCallback(async () => { + try { + await tauri.openWorkspaceBrowser(); + } catch (e) { + setError(String(e)); + } + }, []); + + const installSslCertificate = useCallback(async () => { + setSslInstalling(true); + setError(null); + try { + await tauri.installSslCertificate(); + setSslTrusted(true); + } catch (e) { + setError(String(e)); + } finally { + setSslInstalling(false); + } + }, []); + + const isRunning = status?.state === "Running"; + const isStopped = status?.state === "Stopped" || status?.state === "NotFound"; + + // Check SSL trust when container becomes running + useEffect(() => { + if (isRunning) checkSsl(); + }, [isRunning, checkSsl]); + + return { + status, pullProgress, loading, error, isRunning, isStopped, + sslTrusted, sslInstalling, + pullImage, createWorkspace, startWorkspace, stopWorkspace, openWorkspace, refresh, + installSslCertificate, + }; +} diff --git a/coeadapt-launcher/src/hooks/useCoraChat.ts b/coeadapt-launcher/src/hooks/useCoraChat.ts new file mode 100644 index 000000000..12fce85e6 --- /dev/null +++ b/coeadapt-launcher/src/hooks/useCoraChat.ts @@ -0,0 +1,135 @@ +import { useState, useCallback, useRef } from "react"; +import { useAuth } from "@clerk/clerk-react"; +import { getDeviceToken } from "../lib/api"; + +const API_BASE = import.meta.env.VITE_COEADAPT_API_URL || "http://localhost:5000"; + +export interface ChatMessage { + id: string; + role: "user" | "assistant"; + content: string; + timestamp: string; +} + +export function useCoraChat(threadId: string = "default") { + const { getToken } = useAuth(); + const [messages, setMessages] = useState([]); + const [isStreaming, setIsStreaming] = useState(false); + const [error, setError] = useState(null); + const abortRef = useRef(null); + + const getAuthHeader = useCallback(async (): Promise => { + try { + const clerkToken = await getToken(); + if (clerkToken) return `Bearer ${clerkToken}`; + } catch { + // Fall through to device token + } + const deviceToken = getDeviceToken(); + if (deviceToken) return `Bearer ${deviceToken}`; + throw new Error("Not authenticated"); + }, [getToken]); + + const sendMessage = useCallback( + async (text: string) => { + setError(null); + + const userMsg: ChatMessage = { + id: `user-${Date.now()}`, + role: "user", + content: text, + timestamp: new Date().toISOString(), + }; + + const assistantMsg: ChatMessage = { + id: `assistant-${Date.now()}`, + role: "assistant", + content: "", + timestamp: new Date().toISOString(), + }; + + setMessages((prev) => [...prev, userMsg, assistantMsg]); + setIsStreaming(true); + + try { + const authHeader = await getAuthHeader(); + const controller = new AbortController(); + abortRef.current = controller; + + const response = await fetch(`${API_BASE}/api/chatbot/agent/stream?message=${encodeURIComponent(text)}&threadId=${encodeURIComponent(threadId)}`, { + headers: { Authorization: authHeader }, + signal: controller.signal, + }); + + if (!response.ok) { + const body = await response.json().catch(() => ({})); + throw new Error(body.error || body.message || `HTTP ${response.status}`); + } + + const reader = response.body?.getReader(); + const decoder = new TextDecoder(); + + if (!reader) throw new Error("No response body"); + + let buffer = ""; + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + buffer += decoder.decode(value, { stream: true }); + const lines = buffer.split("\n"); + buffer = lines.pop() || ""; + + for (const line of lines) { + if (!line.startsWith("data: ")) continue; + try { + const data = JSON.parse(line.slice(6)); + if (data.type === "content" || data.content) { + const chunk = data.content || data.text || ""; + setMessages((prev) => { + const updated = [...prev]; + const last = updated[updated.length - 1]; + if (last?.role === "assistant") { + last.content += chunk; + } + return updated; + }); + } else if (data.type === "error") { + setError(data.error || "An error occurred"); + } + } catch { + // Skip malformed SSE lines + } + } + } + } catch (err: any) { + if (err.name !== "AbortError") { + setError(err.message || "Failed to send message"); + // Remove empty assistant message on error + setMessages((prev) => { + const last = prev[prev.length - 1]; + if (last?.role === "assistant" && !last.content) { + return prev.slice(0, -1); + } + return prev; + }); + } + } finally { + setIsStreaming(false); + abortRef.current = null; + } + }, + [getAuthHeader, threadId], + ); + + const stopStreaming = useCallback(() => { + abortRef.current?.abort(); + }, []); + + const clearMessages = useCallback(() => { + setMessages([]); + setError(null); + }, []); + + return { messages, isStreaming, error, sendMessage, stopStreaming, clearMessages }; +} diff --git a/coeadapt-launcher/src/hooks/useDeviceToken.ts b/coeadapt-launcher/src/hooks/useDeviceToken.ts new file mode 100644 index 000000000..e02f759f1 --- /dev/null +++ b/coeadapt-launcher/src/hooks/useDeviceToken.ts @@ -0,0 +1,72 @@ +import { useAuth } from "@clerk/clerk-react"; +import { useState, useEffect, useCallback } from "react"; +import { api, setDeviceToken } from "../lib/api"; + +export function useDeviceToken() { + const { isSignedIn } = useAuth(); + const [deviceToken, setDeviceTokenState] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const loadOrGenerate = useCallback(async () => { + if (!isSignedIn) return; + setLoading(true); + setError(null); + + try { + // Try to load existing token from Tauri store + const { Store } = await import("@tauri-apps/plugin-store"); + const store = await Store.load("auth.json"); + const existing = await store.get("deviceToken"); + + if (existing) { + // Verify it's still valid + setDeviceToken(existing); + try { + const result = await api.verifyToken(); + if (result.valid) { + setDeviceTokenState(existing); + setLoading(false); + return; + } + } catch { + // Token invalid, fall through to generate new one + } + } + + // Generate new device token + const hostname = `Career-Box-${Date.now().toString(36)}`; + const result = await api.generateDeviceToken(hostname); + await store.set("deviceToken", result.token); + await store.save(); + setDeviceToken(result.token); + setDeviceTokenState(result.token); + } catch (err: any) { + console.error("Failed to setup device token:", err); + setError(err.message || "Failed to connect to Coeadapt"); + } finally { + setLoading(false); + } + }, [isSignedIn]); + + useEffect(() => { + loadOrGenerate(); + }, [loadOrGenerate]); + + const regenerate = useCallback(async () => { + // Clear existing token and generate fresh + try { + const { Store } = await import("@tauri-apps/plugin-store"); + const store = await Store.load("auth.json"); + await store.delete("deviceToken"); + await store.save(); + } catch { + // Ignore store errors + } + setDeviceTokenState(null); + setDeviceToken(null); + await loadOrGenerate(); + }, [loadOrGenerate]); + + return { deviceToken, loading, error, regenerate }; +} diff --git a/coeadapt-launcher/src/hooks/useDiskSpace.ts b/coeadapt-launcher/src/hooks/useDiskSpace.ts new file mode 100644 index 000000000..e7727bf0c --- /dev/null +++ b/coeadapt-launcher/src/hooks/useDiskSpace.ts @@ -0,0 +1,30 @@ +import { useState, useEffect, useCallback } from "react"; +import { tauri, safeListen } from "../lib/tauri"; +import type { DiskStatus } from "../lib/types"; + +export function useDiskSpace() { + const [status, setStatus] = useState(null); + const [showWarning, setShowWarning] = useState(false); + + const check = useCallback(async () => { + try { + const result = await tauri.checkDiskSpace(); + setStatus(result); + setShowWarning(result.is_low); + } catch { + // Not in Tauri or command failed + } + }, []); + + useEffect(() => { + check(); + const unlisten = safeListen("disk-warning", (event) => { + setStatus(event.payload); + setShowWarning(true); + }); + return () => { unlisten.then((fn) => fn()); }; + }, [check]); + + const dismissWarning = useCallback(() => setShowWarning(false), []); + return { status, showWarning, dismissWarning, refresh: check }; +} diff --git a/coeadapt-launcher/src/hooks/useDocker.ts b/coeadapt-launcher/src/hooks/useDocker.ts new file mode 100644 index 000000000..5f653021a --- /dev/null +++ b/coeadapt-launcher/src/hooks/useDocker.ts @@ -0,0 +1,28 @@ +import { useState, useEffect, useCallback } from "react"; +import { tauri } from "../lib/tauri"; +import type { DockerInfo } from "../lib/types"; + +export function useDocker() { + const [info, setInfo] = useState(null); + const [loading, setLoading] = useState(true); + + const check = useCallback(async () => { + setLoading(true); + try { + const result = await tauri.detectRuntime(); + setInfo(result); + } catch { + setInfo(null); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + check(); + }, [check]); + + const isAvailable = info?.runtime !== "None" && info?.is_daemon_running; + + return { info, loading, isAvailable, refresh: check }; +} diff --git a/coeadapt-launcher/src/hooks/useProgress.ts b/coeadapt-launcher/src/hooks/useProgress.ts new file mode 100644 index 000000000..98c95e0c2 --- /dev/null +++ b/coeadapt-launcher/src/hooks/useProgress.ts @@ -0,0 +1,81 @@ +import { useState, useEffect, useCallback } from "react"; +import type { ProgressSummary, AgentHealthInfo } from "../lib/types"; + +const PROGRESS_POLL_INTERVAL = 30_000; // 30 seconds + +/** + * Fetches progress summary from the in-VM agent via the MCP server health endpoint, + * or falls back to docker exec for direct reads. In both cases, the data comes + * from the progress tracker running inside the Kasm container. + * + * The MCP server is already running on the host at port 3100. We ask it to + * proxy the progress summary request to the container. + */ +async function fetchProgressSummary(): Promise { + try { + // The MCP health endpoint is always available on the host. + // We piggyback a progress fetch by hitting the progress tracker + // inside the container via the MCP server's tool mechanism. + // However, for the dashboard we use a simpler direct approach: + // hit the MCP server's health, then fetch progress via a lightweight + // sidecar endpoint. + const res = await fetch("http://127.0.0.1:3100/progress-summary", { + signal: AbortSignal.timeout(5000), + }); + if (res.ok) { + return res.json(); + } + } catch { + // MCP progress endpoint not available — this is expected before + // we add the proxy route. Return null to show "no data" state. + } + return null; +} + +async function fetchAgentHealth(): Promise { + try { + const res = await fetch("http://127.0.0.1:3100/agent-health", { + signal: AbortSignal.timeout(5000), + }); + if (res.ok) { + return res.json(); + } + } catch { + // Not available + } + return null; +} + +export function useProgress(isContainerRunning: boolean) { + const [summary, setSummary] = useState(null); + const [agentHealth, setAgentHealth] = useState(null); + const [loading, setLoading] = useState(false); + + const refresh = useCallback(async () => { + if (!isContainerRunning) { + setSummary(null); + setAgentHealth(null); + return; + } + setLoading(true); + try { + const [summaryData, healthData] = await Promise.all([ + fetchProgressSummary(), + fetchAgentHealth(), + ]); + setSummary(summaryData); + setAgentHealth(healthData); + } finally { + setLoading(false); + } + }, [isContainerRunning]); + + useEffect(() => { + refresh(); + if (!isContainerRunning) return; + const interval = setInterval(refresh, PROGRESS_POLL_INTERVAL); + return () => clearInterval(interval); + }, [refresh, isContainerRunning]); + + return { summary, agentHealth, loading, refresh }; +} diff --git a/coeadapt-launcher/src/hooks/useSettings.ts b/coeadapt-launcher/src/hooks/useSettings.ts new file mode 100644 index 000000000..77d74d60c --- /dev/null +++ b/coeadapt-launcher/src/hooks/useSettings.ts @@ -0,0 +1,112 @@ +import { useState, useEffect, useCallback } from "react"; + +interface Settings { + autoStartApp: boolean; + autoStartWorkspace: boolean; + autoUpdateImage: boolean; + containerMemoryMb: number; + vncPassword: string; +} + +const DEFAULTS: Settings = { + autoStartApp: false, + autoStartWorkspace: false, + autoUpdateImage: false, + containerMemoryMb: 2048, + vncPassword: "coeadapt", +}; + +let storeInstance: Awaited> | null = null; + +async function getStore() { + if (!storeInstance) { + const { Store } = await import("@tauri-apps/plugin-store"); + storeInstance = await Store.load("settings.json", { + defaults: { + autoStartWorkspace: false, + autoUpdateImage: false, + containerMemoryMb: 2048, + vncPassword: "coeadapt", + }, + autoSave: true, + }); + } + return storeInstance; +} + +export function useSettings() { + const [settings, setSettings] = useState(DEFAULTS); + const [loading, setLoading] = useState(true); + + const refresh = useCallback(async () => { + try { + // Autostart plugin + const { isEnabled } = await import("@tauri-apps/plugin-autostart"); + const autoStartApp = await isEnabled(); + + // Store-backed settings + const store = await getStore(); + const autoStartWorkspace = (await store.get("autoStartWorkspace")) ?? DEFAULTS.autoStartWorkspace; + const autoUpdateImage = (await store.get("autoUpdateImage")) ?? DEFAULTS.autoUpdateImage; + const containerMemoryMb = (await store.get("containerMemoryMb")) ?? DEFAULTS.containerMemoryMb; + const vncPassword = (await store.get("vncPassword")) ?? DEFAULTS.vncPassword; + + setSettings({ autoStartApp, autoStartWorkspace, autoUpdateImage, containerMemoryMb, vncPassword }); + } catch { + // Not in Tauri context + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + refresh(); + }, [refresh]); + + const setAutoStartApp = useCallback(async (value: boolean) => { + try { + const autostart = await import("@tauri-apps/plugin-autostart"); + if (value) { + await autostart.enable(); + } else { + await autostart.disable(); + } + setSettings((prev) => ({ ...prev, autoStartApp: value })); + } catch { /* ignore */ } + }, []); + + const setAutoStartWorkspace = useCallback(async (value: boolean) => { + const store = await getStore(); + await store.set("autoStartWorkspace", value); + setSettings((prev) => ({ ...prev, autoStartWorkspace: value })); + }, []); + + const setAutoUpdateImage = useCallback(async (value: boolean) => { + const store = await getStore(); + await store.set("autoUpdateImage", value); + setSettings((prev) => ({ ...prev, autoUpdateImage: value })); + }, []); + + const setContainerMemory = useCallback(async (value: number) => { + const store = await getStore(); + await store.set("containerMemoryMb", value); + setSettings((prev) => ({ ...prev, containerMemoryMb: value })); + }, []); + + const setVncPassword = useCallback(async (value: string) => { + const store = await getStore(); + await store.set("vncPassword", value); + setSettings((prev) => ({ ...prev, vncPassword: value })); + }, []); + + return { + settings, + loading, + refresh, + setAutoStartApp, + setAutoStartWorkspace, + setAutoUpdateImage, + setContainerMemory, + setVncPassword, + }; +} diff --git a/coeadapt-launcher/src/index.css b/coeadapt-launcher/src/index.css new file mode 100644 index 000000000..1511fce8a --- /dev/null +++ b/coeadapt-launcher/src/index.css @@ -0,0 +1,176 @@ +@import "tailwindcss"; + +@theme { + --color-brand-50: #eef2ff; + --color-brand-100: #e0e7ff; + --color-brand-200: #c7d2fe; + --color-brand-400: #818cf8; + --color-brand-500: #4f46e5; + --color-brand-600: #4338ca; + --color-brand-700: #3730a3; + + --color-surface-0: #0a0a0a; + --color-surface-50: #111111; + --color-surface-100: #171717; + --color-surface-200: #1e1e1e; + --color-surface-300: #262626; + --color-surface-400: #333333; + --color-surface-500: #404040; + + --color-text-primary: #ffffff; + --color-text-secondary: #a3a3a3; + --color-text-tertiary: #8a8a8a; + --color-text-muted: #737373; + --color-text-faint: #525252; + + --color-accent: #4f46e5; + + --color-success: #22c55e; + --color-warning: #f59e0b; + --color-danger: #ef4444; +} + +*, +*::before, +*::after { + box-sizing: border-box; +} + +html { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-rendering: optimizeLegibility; +} + +body { + margin: 0; + padding: 0; + font-family: "Inter", system-ui, -apple-system, sans-serif; + background-color: #0a0a0a; + color: #ffffff; + overflow: hidden; + min-height: 100vh; +} + +#root { + min-height: 100vh; +} + +::-webkit-scrollbar { width: 6px; } +::-webkit-scrollbar-track { background: transparent; } +::-webkit-scrollbar-thumb { background: #333; border-radius: 3px; } +::-webkit-scrollbar-thumb:hover { background: #404040; } + +@keyframes fade-in { + from { opacity: 0; transform: translateY(8px); } + to { opacity: 1; transform: translateY(0); } +} +@keyframes slide-up { + from { opacity: 0; transform: translateY(20px); } + to { opacity: 1; transform: translateY(0); } +} +@keyframes pulse-glow { + 0%, 100% { opacity: 0.4; } + 50% { opacity: 1; } +} +@keyframes shimmer { + 0% { transform: translateX(-100%); } + 100% { transform: translateX(400%); } +} +@keyframes breathe { + 0%, 100% { transform: scale(1); opacity: 0.6; } + 50% { transform: scale(1.05); opacity: 1; } +} + +.animate-fade-in { animation: fade-in 0.4s ease-out both; } +.animate-slide-up { animation: slide-up 0.5s ease-out both; } +.animate-breathe { animation: breathe 3s ease-in-out infinite; } +.delay-100 { animation-delay: 100ms; } +.delay-200 { animation-delay: 200ms; } +.delay-300 { animation-delay: 300ms; } +.delay-400 { animation-delay: 400ms; } + +.glass-card { + background: rgba(23, 23, 23, 0.7); + backdrop-filter: blur(12px); + border: 1px solid rgba(255, 255, 255, 0.06); + border-radius: 16px; +} + +.brand-gradient { + background: linear-gradient(135deg, #4338ca 0%, #3b82f6 100%); +} + +.brand-gradient-text { + background: linear-gradient(135deg, #818cf8 0%, #60a5fa 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.btn-primary { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 12px 28px; + background: linear-gradient(135deg, #4338ca 0%, #3b82f6 100%); + color: white; + font-weight: 600; + font-size: 15px; + border-radius: 12px; + border: none; + cursor: pointer; + transition: all 0.2s ease; +} +.btn-primary:hover { + transform: translateY(-1px); + box-shadow: 0 8px 25px rgba(67, 56, 202, 0.35); +} +.btn-primary:active { transform: translateY(0); } +.btn-primary:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none; + box-shadow: none; +} + +.btn-secondary { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 10px 20px; + background: rgba(255, 255, 255, 0.06); + color: #a3a3a3; + font-weight: 500; + font-size: 14px; + border-radius: 10px; + border: 1px solid rgba(255, 255, 255, 0.08); + cursor: pointer; + transition: all 0.2s ease; +} +.btn-secondary:hover { + background: rgba(255, 255, 255, 0.1); + color: white; + border-color: rgba(255, 255, 255, 0.15); +} +.btn-secondary:disabled { opacity: 0.4; cursor: not-allowed; } + +.btn-danger { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 10px 20px; + background: rgba(239, 68, 68, 0.12); + color: #ef4444; + font-weight: 500; + font-size: 14px; + border-radius: 10px; + border: 1px solid rgba(239, 68, 68, 0.2); + cursor: pointer; + transition: all 0.2s ease; +} +.btn-danger:hover { background: rgba(239, 68, 68, 0.2); } +.btn-danger:disabled { opacity: 0.4; cursor: not-allowed; } diff --git a/coeadapt-launcher/src/lib/api.ts b/coeadapt-launcher/src/lib/api.ts new file mode 100644 index 000000000..8a042866e --- /dev/null +++ b/coeadapt-launcher/src/lib/api.ts @@ -0,0 +1,170 @@ +/** + * Typed API client for Coeadapt platform communication. + * + * Automatically attaches auth headers (Clerk JWT or device token). + * Handles 401, 429, and 500+ errors with appropriate strategies. + */ + +const API_BASE = import.meta.env.VITE_COEADAPT_API_URL || "http://localhost:5000"; + +type GetTokenFn = () => Promise; + +let _getToken: GetTokenFn | null = null; +let _deviceToken: string | null = null; + +/** Called once from AuthWiring to inject Clerk's getToken function. */ +export function setAuthProvider(getToken: GetTokenFn) { + _getToken = getToken; +} + +/** Called after login to set the device token for background use. */ +export function setDeviceToken(token: string | null) { + _deviceToken = token; +} + +/** Get the current device token (for passing to MCP sidecar). */ +export function getDeviceToken(): string | null { + return _deviceToken; +} + +async function getAuthHeader(): Promise> { + // Prefer Clerk JWT for interactive requests + if (_getToken) { + try { + const token = await _getToken(); + if (token) { + return { Authorization: `Bearer ${token}` }; + } + } catch { + // Clerk not available, fall through + } + } + // Fall back to device token + if (_deviceToken) { + return { Authorization: `Bearer ${_deviceToken}` }; + } + return {}; +} + +export class ApiError extends Error { + constructor( + public status: number, + message: string, + public code?: string, + ) { + super(message); + this.name = "ApiError"; + } +} + +export async function apiFetch( + path: string, + options: RequestInit = {}, +): Promise { + const authHeaders = await getAuthHeader(); + const res = await fetch(`${API_BASE}${path}`, { + ...options, + headers: { + "Content-Type": "application/json", + ...authHeaders, + ...(options.headers as Record), + }, + }); + + if (!res.ok) { + const body = await res.json().catch(() => ({})); + throw new ApiError( + res.status, + body.error || body.message || res.statusText, + body.code, + ); + } + + return res.json(); +} + +// --------------------------------------------------------------------------- +// Typed API methods (mirrors COEADAPT_API.md) +// --------------------------------------------------------------------------- + +export const api = { + // Health & system + health: () => apiFetch<{ status: string; timestamp: string }>("/api/career-box/health"), + + // Token management + verifyToken: () => + apiFetch<{ valid: boolean; userId?: string; deviceName?: string; expiresAt?: string; reason?: string }>( + "/api/career-box/verify-token", + { method: "POST" }, + ), + generateDeviceToken: (deviceName: string) => + apiFetch<{ success: boolean; token: string; deviceName: string; expiresAt: string; expiresIn: number }>( + "/api/career-box/generate-token", + { method: "POST", body: JSON.stringify({ deviceName }) }, + ), + + // User profile + getUser: () => apiFetch("/api/auth/user"), + + // Plans + getPlans: () => apiFetch("/api/plans/me"), + getPlan: (id: string) => apiFetch(`/api/plans/${id}`), + getPlanTasks: (planId: string) => apiFetch(`/api/plans/${planId}/tasks`), + + // Tasks + getTasks: () => apiFetch("/api/tasks/me"), + getTask: (id: string) => apiFetch(`/api/tasks/${id}`), + updateTask: (id: string, updates: Record) => + apiFetch(`/api/tasks/${id}`, { method: "PUT", body: JSON.stringify(updates) }), + + // Evidence + submitEvidence: (taskId: string, evidence: Record) => + apiFetch(`/api/tasks/${taskId}/evidence`, { method: "POST", body: JSON.stringify(evidence) }), + + // Goals + getGoals: () => apiFetch("/api/goals/me"), + createGoal: (goal: Record) => + apiFetch("/api/goals", { method: "POST", body: JSON.stringify(goal) }), + updateGoal: (id: string, updates: Record) => + apiFetch(`/api/goals/${id}`, { method: "PATCH", body: JSON.stringify(updates) }), + + // Habits + getHabits: () => apiFetch("/api/habits"), + getHabitsToday: () => apiFetch("/api/habits/today"), + createHabit: (habit: Record) => + apiFetch("/api/habits", { method: "POST", body: JSON.stringify(habit) }), + completeHabit: (id: string) => + apiFetch(`/api/habits/${id}/complete`, { method: "POST" }), + getHabitStats: () => apiFetch("/api/habits/stats/overview"), + + // Jobs + getJobs: () => apiFetch("/api/jobs"), + discoverJobs: () => apiFetch("/api/jobs/discover"), + bookmarkJob: (jobId: string) => + apiFetch(`/api/jobs/${jobId}/bookmark`, { method: "POST" }), + getBookmarks: () => apiFetch("/api/jobs/bookmarks/me"), + + // Portfolio + getPortfolio: () => apiFetch("/api/portfolio/items"), + + // Skills + getVerifiedSkills: () => apiFetch("/api/skills/verified"), + + // Market / Radar + getMarketFit: () => apiFetch("/api/radar/market-fit"), + getSkillDeltas: () => apiFetch("/api/radar/skill-deltas"), + + // Subscription + getSubscription: () => + apiFetch<{ status: string; plan: string; features: Record }>("/api/subscription/status"), + + // Notifications + getNotifications: () => apiFetch("/api/notifications/me"), + + // Chat with Cora (non-streaming) + sendMessage: (message: string, threadId?: string) => + apiFetch<{ response: string; threadId: string; timestamp: string }>( + "/api/chatbot/agent", + { method: "POST", body: JSON.stringify({ message, threadId: threadId || "default" }) }, + ), +}; diff --git a/coeadapt-launcher/src/lib/constants.ts b/coeadapt-launcher/src/lib/constants.ts new file mode 100644 index 000000000..44c87b356 --- /dev/null +++ b/coeadapt-launcher/src/lib/constants.ts @@ -0,0 +1,31 @@ +// User-facing strings — NO technical jargon +export const STRINGS = { + APP_NAME: "Coeadapt", + WELCOME_TITLE: "Welcome to Coeadapt", + WELCOME_SUBTITLE: "Let's get your career workspace set up.", + SETUP_CHECKING_SYSTEM: "Checking your system...", + SETUP_DISK_OK: "Storage: Ready", + SETUP_DISK_LOW: "Your computer needs more free space", + SETUP_DISK_MINIMUM: "Coeadapt needs at least 15GB of free space.", + SETUP_DOCKER_CHECKING: "Checking for workspace engine...", + SETUP_DOCKER_FOUND: "Workspace engine: Ready", + SETUP_DOCKER_NOT_FOUND: + "Coeadapt needs to install a small helper app to run your workspace.", + SETUP_DOCKER_INSTALL: "Install Docker Desktop", + SETUP_DOCKER_STARTING: "Starting up your workspace engine...", + SETUP_PULLING: "Downloading your workspace...", + SETUP_PULLING_SUBTITLE: "This may take a few minutes on first launch (~5GB).", + SETUP_STARTING: "Starting your workspace...", + SETUP_READY: "Your workspace is ready!", + DASHBOARD_RUNNING: "Workspace Running", + DASHBOARD_STOPPED: "Workspace Stopped", + DASHBOARD_ERROR: "Something went wrong", + BTN_OPEN_WORKSPACE: "Open Workspace", + BTN_START: "Start Workspace", + BTN_STOP: "Stop Workspace", + BTN_GET_STARTED: "Get Started", + BTN_RETRY: "Try Again", + AI_CONNECTED: "AI Copilot: Connected", + AI_DISCONNECTED: "AI Copilot: Disconnected", + MCP_URL: "http://localhost:3100/mcp", +}; diff --git a/coeadapt-launcher/src/lib/mode.ts b/coeadapt-launcher/src/lib/mode.ts new file mode 100644 index 000000000..742068de5 --- /dev/null +++ b/coeadapt-launcher/src/lib/mode.ts @@ -0,0 +1,10 @@ +/** + * Standalone mode activates automatically when no valid Clerk key is configured. + * The default .env ships with "pk_test_REPLACE_ME", which triggers standalone mode. + * + * Standalone mode: workspace + MCP + Claude — no CoeAdapt account needed. + * CoeAdapt mode: + Cora chat, career tracking, cloud sync. + */ +export const STANDALONE_MODE = + !import.meta.env.VITE_CLERK_PUBLISHABLE_KEY || + import.meta.env.VITE_CLERK_PUBLISHABLE_KEY === "pk_test_REPLACE_ME"; diff --git a/coeadapt-launcher/src/lib/tauri.ts b/coeadapt-launcher/src/lib/tauri.ts new file mode 100644 index 000000000..4071f557f --- /dev/null +++ b/coeadapt-launcher/src/lib/tauri.ts @@ -0,0 +1,56 @@ +import type { + DockerInfo, + DiskStatus, + DockerDiskUsage, + ContainerStatus, + ClaudeStatus, + McpHealthInfo, +} from "./types"; + +function isTauri(): boolean { + return typeof window !== "undefined" && "__TAURI_INTERNALS__" in window; +} + +async function safeInvoke(cmd: string, args?: Record): Promise { + if (!isTauri()) { + throw new Error(`Not in Tauri context (tried to invoke "${cmd}")`); + } + const { invoke } = await import("@tauri-apps/api/core"); + return invoke(cmd, args); +} + +export async function safeListen( + event: string, + handler: (event: { payload: T }) => void, +): Promise<() => void> { + if (!isTauri()) return () => {}; + const { listen } = await import("@tauri-apps/api/event"); + return listen(event, handler); +} + +export const tauri = { + isTauri, + detectRuntime: () => safeInvoke("detect_container_runtime"), + checkWsl2: () => safeInvoke("check_wsl2_status"), + checkDiskSpace: () => safeInvoke("check_disk_space"), + getDockerDiskUsage: () => safeInvoke("get_docker_disk_usage"), + pruneImages: () => safeInvoke("prune_docker_images"), + getWorkspaceStatus: () => safeInvoke("get_workspace_status"), + checkImageExists: () => safeInvoke("check_image_exists"), + pullWorkspaceImage: () => safeInvoke("pull_workspace_image"), + createWorkspace: () => safeInvoke("create_workspace"), + startWorkspace: () => safeInvoke("start_workspace"), + stopWorkspace: () => safeInvoke("stop_workspace"), + resetWorkspace: () => safeInvoke("reset_workspace"), + waitForReady: () => safeInvoke("wait_for_workspace_ready"), + checkMcpStatus: () => safeInvoke("check_mcp_status"), + getMcpHealth: () => safeInvoke("get_mcp_health"), + getClaudeStatus: () => safeInvoke("get_claude_status"), + configureClaude: () => safeInvoke("configure_claude"), + openWorkspaceBrowser: () => safeInvoke("open_workspace_browser"), + checkSslTrust: () => safeInvoke("check_ssl_trust"), + installSslCertificate: () => safeInvoke("install_ssl_certificate"), + uninstallSslCertificate: () => safeInvoke("uninstall_ssl_certificate"), + startMcp: () => safeInvoke("start_mcp"), + stopMcp: () => safeInvoke("stop_mcp"), +}; diff --git a/coeadapt-launcher/src/lib/types.ts b/coeadapt-launcher/src/lib/types.ts new file mode 100644 index 000000000..1d5a987bc --- /dev/null +++ b/coeadapt-launcher/src/lib/types.ts @@ -0,0 +1,143 @@ +export interface DockerInfo { + runtime: "Docker" | "Podman" | "None"; + version: string; + is_daemon_running: boolean; +} + +export interface DiskStatus { + available_gb: number; + total_gb: number; + meets_minimum: boolean; + meets_recommended: boolean; + is_low: boolean; +} + +export interface DockerDiskUsage { + images_size: string; + containers_size: string; + volumes_size: string; + total_size: string; +} + +export type ContainerState = + | "NotFound" + | "Running" + | "Stopped" + | "Starting" + | "Pulling" + | { Error: string }; + +export interface ContainerStatus { + state: ContainerState; + container_id: string | null; + uptime: string | null; + image: string; +} + +export interface ClaudeStatus { + is_installed: boolean; + config_path: string | null; + is_configured: boolean; + needs_restart: boolean; +} + +export interface PullProgress { + status: string; + progress: string | null; + percent: number; +} + +export interface McpHealthInfo { + is_running: boolean; + last_tool_call: number | null; + uptime_secs: number | null; +} + +// --------------------------------------------------------------------------- +// Progress tracking types (mirrors the VM-side progress tracker data model) +// --------------------------------------------------------------------------- + +export interface ProgressActivity { + id: number; + type: string; + title: string; + description: string; + duration_minutes: number; + tags: string[]; + metadata: Record; + created_at: string; +} + +export interface ProgressGoal { + id: number; + title: string; + description: string; + category: string; + status: "active" | "completed" | "paused" | "abandoned"; + target_date: string | null; + sub_goals: string[]; + completed_at?: string; + created_at: string; + updated_at: string; +} + +export interface ProgressSkill { + id: number; + name: string; + category: string; + level: "beginner" | "intermediate" | "advanced" | "expert"; + evidence: string[]; + verified: boolean; + created_at: string; + updated_at: string; +} + +export interface ProgressMilestone { + id: number; + title: string; + description: string; + achieved: boolean; + achieved_at: string | null; + created_at: string; +} + +export interface ProgressAssessment { + id: number; + type: string; + skill: string; + score: number; + max_score: number; + notes: string; + created_at: string; +} + +export interface ProgressData { + version: number; + activities: ProgressActivity[]; + assessments: ProgressAssessment[]; + goals: ProgressGoal[]; + skills: ProgressSkill[]; + milestones: ProgressMilestone[]; + daily_log: { date: string; activity_id: number }[]; + progress_percent: number; + streak_days: number; + last_activity_at: string | null; + created_at: string | null; + updated_at: string | null; +} + +export interface ProgressSummary { + progress_percent: number; + streak_days: number; + total_activities: number; + total_goals: number; + completed_goals: number; + total_skills: number; + total_milestones: number; + last_activity_at: string | null; +} + +export interface AgentHealthInfo { + progress_tracker: "ok" | "down"; + computer_use: "ok" | "down"; +} diff --git a/coeadapt-launcher/src/main.tsx b/coeadapt-launcher/src/main.tsx new file mode 100644 index 000000000..199d1c446 --- /dev/null +++ b/coeadapt-launcher/src/main.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import { ClerkProvider } from "@clerk/clerk-react"; +import { STANDALONE_MODE } from "./lib/mode"; +import App from "./App"; +import "./index.css"; + +const CLERK_PUBLISHABLE_KEY = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY; + +if (!CLERK_PUBLISHABLE_KEY && !STANDALONE_MODE) { + console.warn("Missing VITE_CLERK_PUBLISHABLE_KEY — auth will not work"); +} + +ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( + + {STANDALONE_MODE ? ( + + ) : ( + + + + )} + , +); diff --git a/coeadapt-launcher/src/pages/Chat.tsx b/coeadapt-launcher/src/pages/Chat.tsx new file mode 100644 index 000000000..884943ae5 --- /dev/null +++ b/coeadapt-launcher/src/pages/Chat.tsx @@ -0,0 +1,127 @@ +import { useState, useRef, useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import { useCoraChat } from "../hooks/useCoraChat"; + + +export default function Chat() { + const navigate = useNavigate(); + const { messages, isStreaming, error, sendMessage, stopStreaming } = useCoraChat(); + const [input, setInput] = useState(""); + const messagesEndRef = useRef(null); + + useEffect(() => { + messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); + }, [messages]); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + const text = input.trim(); + if (!text || isStreaming) return; + setInput(""); + sendMessage(text); + }; + + return ( +
+ {/* Header */} +
+ +
+ + + +
+
+

Cora

+

AI Career Companion

+
+
+ + {/* Messages */} +
+ {messages.length === 0 && ( +
+
+ + + +
+

Hi, I'm Cora

+

+ Your AI career companion. Ask me about career paths, skill development, job strategies, or anything career-related. +

+
+ )} + + {messages.map((msg) => ( +
+
+ {msg.content || (isStreaming && msg.role === "assistant" ? ( + + Thinking + ... + + ) : null)} +
+
+ ))} + + {error && ( +
+

{error}

+
+ )} + +
+
+ + {/* Input */} +
+
+ setInput(e.target.value)} + placeholder="Ask Cora anything..." + className="flex-1 bg-surface-200 border border-surface-300 rounded-xl px-4 py-3 text-sm text-text-primary placeholder:text-text-faint focus:outline-none focus:border-brand-500 transition-colors" + disabled={isStreaming} + /> + {isStreaming ? ( + + ) : ( + + )} +
+
+
+ ); +} diff --git a/coeadapt-launcher/src/pages/ClaudeSetup.tsx b/coeadapt-launcher/src/pages/ClaudeSetup.tsx new file mode 100644 index 000000000..c393ed4f6 --- /dev/null +++ b/coeadapt-launcher/src/pages/ClaudeSetup.tsx @@ -0,0 +1,84 @@ +import { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { useClaudeConnection } from "../hooks/useClaudeConnection"; +import { STRINGS } from "../lib/constants"; + +export default function ClaudeSetup() { + const navigate = useNavigate(); + const claude = useClaudeConnection(); + const [copied, setCopied] = useState(false); + + const copyUrl = async () => { + await navigator.clipboard.writeText(STRINGS.MCP_URL); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( +
+
+
+
+ +
+
+
+
+ + + +
+

Connect your AI copilot

+

Your workspace is running. Let's connect your AI assistant.

+
+ + {claude.status?.is_installed && ( +
+
+ + Claude Desktop detected +
+ +
+ )} + +
+

+ {claude.status?.is_installed ? "Or connect manually:" : "Manual setup:"} +

+
    +
  1. Open Claude, ChatGPT, or your preferred AI
  2. +
  3. Go to Settings → Connectors
  4. +
  5. Add a custom MCP connector
  6. +
  7. Paste this URL:
  8. +
+
+ + {STRINGS.MCP_URL} + + +
+
+ + +
+
+
+ ); +} diff --git a/coeadapt-launcher/src/pages/Dashboard.tsx b/coeadapt-launcher/src/pages/Dashboard.tsx new file mode 100644 index 000000000..f19a04d1b --- /dev/null +++ b/coeadapt-launcher/src/pages/Dashboard.tsx @@ -0,0 +1,149 @@ +import { useNavigate } from "react-router-dom"; +import { useContainer } from "../hooks/useContainer"; +import { useDiskSpace } from "../hooks/useDiskSpace"; +import { useClaudeConnection } from "../hooks/useClaudeConnection"; +import { useProgress } from "../hooks/useProgress"; +import { STANDALONE_MODE } from "../lib/mode"; +import { StatusIndicator } from "../components/StatusIndicator"; +import { WorkspaceControls } from "../components/WorkspaceControls"; +import { DiskUsage } from "../components/DiskUsage"; +import { ProgressCard } from "../components/ProgressCard"; +import { STRINGS } from "../lib/constants"; +import type { ContainerState } from "../lib/types"; + +function stateToIndicator(state: ContainerState): "running" | "starting" | "stopped" | "error" { + if (state === "Running") return "running"; + if (state === "Starting" || state === "Pulling") return "starting"; + if (state === "Stopped" || state === "NotFound") return "stopped"; + return "error"; +} + +function stateLabel(state: ContainerState): string { + if (state === "Running") return STRINGS.DASHBOARD_RUNNING; + if (state === "Stopped" || state === "NotFound") return STRINGS.DASHBOARD_STOPPED; + if (state === "Starting") return "Starting..."; + if (state === "Pulling") return "Downloading..."; + if (typeof state === "object" && "Error" in state) return state.Error; + return "Unknown"; +} + +export default function Dashboard() { + const navigate = useNavigate(); + const container = useContainer(); + const disk = useDiskSpace(); + const claude = useClaudeConnection(); + const progress = useProgress(container.isRunning); + + const handleStart = async () => { + if (container.status?.state === "NotFound") await container.createWorkspace(); + else await container.startWorkspace(); + }; + + return ( +
+
+
+ + {STRINGS.APP_NAME} +
+ +
+ +
+
+ {/* Workspace */} +
+
+
+ +
+
+

Workspace

+ +
+
+ + {container.isRunning && container.sslTrusted === false && ( +
+

+ Your browser will show a security warning when opening the workspace. + Install the workspace certificate to fix this. +

+ +
+ )} + {container.error &&

{container.error}

} +
+ + {/* AI Connection */} +
+
+
+ +
+
+

AI Copilot

+ +
+
+ {claude.isIdle && ( +

+ No AI activity for a while. This is normal when you're not using AI tools. +

+ )} + {claude.status?.is_installed && !claude.status?.is_configured && ( + + )} +
+ + {/* Career Progress */} + {container.isRunning && ( + + )} + + {/* Cora - AI Career Companion (CoeAdapt mode only) */} + {!STANDALONE_MODE && ( +
+
+
+ +
+
+

Cora

+

AI Career Companion

+
+
+

+ Get career guidance, track goals, and build your mastery with Cora. +

+ +
+ )} + + {/* Disk */} + {disk.status && ( +
+ +
+ )} +
+
+
+ ); +} diff --git a/coeadapt-launcher/src/pages/Login.tsx b/coeadapt-launcher/src/pages/Login.tsx new file mode 100644 index 000000000..4fc773833 --- /dev/null +++ b/coeadapt-launcher/src/pages/Login.tsx @@ -0,0 +1,52 @@ +import { SignIn, useAuth } from "@clerk/clerk-react"; +import { useEffect } from "react"; +import { useNavigate } from "react-router-dom"; + +export default function Login() { + const { isSignedIn, isLoaded } = useAuth(); + const navigate = useNavigate(); + + useEffect(() => { + if (isLoaded && isSignedIn) { + navigate("/setup"); + } + }, [isLoaded, isSignedIn, navigate]); + + return ( +
+
+ + Coeadapt +
+

+ Sign in to connect your Career Box to Cora +

+
+ +
+
+ ); +} diff --git a/coeadapt-launcher/src/pages/Settings.tsx b/coeadapt-launcher/src/pages/Settings.tsx new file mode 100644 index 000000000..12c9ca81d --- /dev/null +++ b/coeadapt-launcher/src/pages/Settings.tsx @@ -0,0 +1,325 @@ +import { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { useUser, useClerk } from "@clerk/clerk-react"; +import { STANDALONE_MODE } from "../lib/mode"; +import { useDiskSpace } from "../hooks/useDiskSpace"; +import { useClaudeConnection } from "../hooks/useClaudeConnection"; +import { useSettings } from "../hooks/useSettings"; +import { useDeviceToken } from "../hooks/useDeviceToken"; +import { DiskUsage } from "../components/DiskUsage"; +import { StatusIndicator } from "../components/StatusIndicator"; +import { ToggleSwitch } from "../components/ToggleSwitch"; +import { STRINGS } from "../lib/constants"; +import { tauri } from "../lib/tauri"; + +type Tab = "account" | "ai" | "workspace" | "general"; + +export default function Settings() { + const navigate = useNavigate(); + const [tab, setTab] = useState(STANDALONE_MODE ? "ai" : "account"); + const disk = useDiskSpace(); + const claude = useClaudeConnection(); + const appSettings = useSettings(); + const { user } = STANDALONE_MODE ? { user: null } : useUser(); + const { signOut } = STANDALONE_MODE ? { signOut: () => {} } : useClerk(); + const { deviceToken, loading: tokenLoading, regenerate: regenerateToken } = STANDALONE_MODE + ? { deviceToken: null, loading: false, regenerate: () => {} } + : useDeviceToken(); + const [resetting, setResetting] = useState(false); + const [pruning, setPruning] = useState(false); + const [copied, setCopied] = useState(false); + + const tabs: { id: Tab; label: string }[] = [ + ...(!STANDALONE_MODE ? [{ id: "account" as Tab, label: "Account" }] : []), + { id: "ai", label: "AI Connection" }, + { id: "workspace", label: "Workspace" }, + { id: "general", label: "General" }, + ]; + + const handleReset = async () => { + if (!confirm("This will delete all your workspace data. Are you sure?")) return; + setResetting(true); + try { + await tauri.resetWorkspace(); + } catch { + // Ignore + } finally { + setResetting(false); + disk.refresh(); + } + }; + + const handlePrune = async () => { + setPruning(true); + try { + await tauri.pruneImages(); + } catch { + // Ignore + } finally { + setPruning(false); + disk.refresh(); + } + }; + + const copyUrl = async () => { + await navigator.clipboard.writeText(STRINGS.MCP_URL); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( +
+
+ + Settings +
+ +
+
+ {/* Tabs */} +
+ {tabs.map((t) => ( + + ))} +
+ + {/* Account Tab */} + {tab === "account" && ( +
+
+
+ {user?.imageUrl ? ( + + ) : ( +
+ + {user?.firstName?.[0] || "?"} + +
+ )} +
+

+ {user?.firstName} {user?.lastName} +

+

+ {user?.primaryEmailAddress?.emailAddress} +

+
+
+ +
+
+ Cora Connection + +
+
+ Device Token + + {deviceToken ? `${deviceToken.slice(0, 12)}...` : "None"} + +
+
+ +
+ + +
+
+
+ )} + + {/* AI Connection Tab */} + {tab === "ai" && ( +
+
+
+
+ +
+
+

AI Copilot

+ +
+
+ +
+
+ Claude Desktop + + {claude.status?.is_installed ? "Detected" : "Not found"} + +
+
+ Configuration + + {claude.status?.is_configured ? "Connected" : "Not configured"} + +
+
+ +
+ + +
+
+ +
+
+ + MCP endpoint: {STRINGS.MCP_URL} +
+
+
+ )} + + {/* Workspace Tab */} + {tab === "workspace" && ( +
+ {disk.status && ( +
+ +
+ )} + +
+
+

Configuration

+

Changes apply the next time you reset your workspace.

+
+ +
+ +
+ {[ + { label: "2 GB", value: 2048 }, + { label: "4 GB", value: 4096 }, + { label: "8 GB", value: 8192 }, + ].map((opt) => ( + + ))} +
+
+ +
+ + appSettings.setVncPassword(e.target.value)} + placeholder="Enter password" + className="w-full px-4 py-2 bg-surface-200 border border-surface-300 rounded-lg text-sm text-text-primary placeholder-text-faint focus:outline-none focus:border-accent" + /> +
+
+ +
+

Maintenance

+
+ + +
+
+
+ )} + + {/* General Tab */} + {tab === "general" && ( +
+
+

Startup

+ + + +
+ +
+

Application

+
+
+ Version + 0.1.0 +
+
+
+ +
+

About

+
+ +
+

{STRINGS.APP_NAME}

+

Adapting Together

+
+
+
+
+ )} +
+
+
+ ); +} diff --git a/coeadapt-launcher/src/pages/Setup.tsx b/coeadapt-launcher/src/pages/Setup.tsx new file mode 100644 index 000000000..ad4e05853 --- /dev/null +++ b/coeadapt-launcher/src/pages/Setup.tsx @@ -0,0 +1,261 @@ +import { useState, useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import { useDocker } from "../hooks/useDocker"; +import { useContainer } from "../hooks/useContainer"; +import { useDiskSpace } from "../hooks/useDiskSpace"; +import { ProgressBar } from "../components/ProgressBar"; +import { Spinner } from "../components/Spinner"; +import { STRINGS } from "../lib/constants"; +import { tauri } from "../lib/tauri"; + +type Step = "welcome" | "system" | "docker" | "pull" | "starting" | "ready"; +const STEPS: Step[] = ["welcome", "system", "docker", "pull", "starting", "ready"]; + +function CheckIcon() { + return ( + + + + ); +} + +export default function Setup() { + const [step, setStep] = useState("welcome"); + const [startError, setStartError] = useState(null); + const navigate = useNavigate(); + const docker = useDocker(); + const container = useContainer(); + const disk = useDiskSpace(); + const stepIndex = STEPS.indexOf(step); + + useEffect(() => { + if (step === "system" && disk.status) { + if (!disk.status.meets_minimum) return; + setStep("docker"); + } + }, [step, disk.status]); + + useEffect(() => { + if (step === "docker" && docker.isAvailable) setStep("pull"); + }, [step, docker.isAvailable]); + + useEffect(() => { + if (step === "docker" && !docker.isAvailable && !docker.loading) { + const interval = setInterval(() => docker.refresh(), 5000); + return () => clearInterval(interval); + } + }, [step, docker]); + + const handlePull = async () => { + try { + const exists = await tauri.checkImageExists(); + if (exists) { setStep("starting"); handleStart(); return; } + await container.pullImage(); + if (container.error) return; // Stay on pull step, error shown there + setStep("starting"); + handleStart(); + } catch (e) { + // container.error will be set by the hook - stay on pull step + console.error("[setup] pull failed:", e); + } + }; + + const handleStart = async () => { + setStartError(null); + try { + const status = await tauri.getWorkspaceStatus(); + if (status.state === "NotFound") { + await container.createWorkspace(); + // Check if create actually failed + if (container.error) { + setStartError(container.error); + return; + } + } else if (status.state === "Stopped") { + await container.startWorkspace(); + if (container.error) { + setStartError(container.error); + return; + } + } + await tauri.waitForReady(); + setStep("ready"); + } catch (e) { + const msg = String(e); + console.error("[setup] start failed:", msg); + if (msg.includes("not ready after")) { + setStartError("Your workspace is taking longer than expected. It may still be starting — try again in a moment."); + } else if (msg.includes("No such image") || msg.includes("not found")) { + setStartError("The workspace image wasn't found. The image may not have been downloaded correctly."); + } else { + setStartError(msg.replace(/^Error:\s*/i, "")); + } + } + }; + + const handleRetryStart = () => { + setStartError(null); + handleStart(); + }; + + useEffect(() => { if (step === "pull") handlePull(); }, [step]); + + return ( +
+ {/* Ambient glow */} +
+
+
+
+ + {/* Step progress */} + {step !== "welcome" && ( +
+
+ {STEPS.slice(1).map((s, i) => ( +
+ ))} +
+
+ )} + +
+
+ + {step === "welcome" && ( +
+ Coeadapt +
+

{STRINGS.WELCOME_TITLE}

+

Your AI-powered career workspace,
ready in minutes.

+
+ +

Adapting Together

+
+ )} + + {step === "system" && ( +
+
+

{STRINGS.SETUP_CHECKING_SYSTEM}

+

Making sure everything is ready

+
+
+ {disk.status ? ( + disk.status.meets_minimum ? ( +
+ +

Storage ready

{disk.status.available_gb} GB available

+
+ ) : ( +
+ +

Insufficient storage

Need 15 GB, only {disk.status.available_gb} GB available

+
+ ) + ) : ( +
Checking storage...
+ )} +
+
+ )} + + {step === "docker" && ( +
+
+

{STRINGS.SETUP_DOCKER_CHECKING}

+

We need Docker Desktop to run your workspace

+
+
+ {docker.loading ? ( +
Detecting...
+ ) : docker.isAvailable ? ( +
+

Docker Desktop found

{docker.info?.version}

+
+ ) : ( +
+

{STRINGS.SETUP_DOCKER_NOT_FOUND}

+ + Download Docker Desktop + + +
Waiting for Docker Desktop...
+
+ )} +
+
+ )} + + {step === "pull" && ( +
+
+

{STRINGS.SETUP_PULLING}

+

{STRINGS.SETUP_PULLING_SUBTITLE}

+
+
+ {container.pullProgress ? ( + + ) : ( + + )} +
+ {container.error && ( +

{container.error}

+ )} +
+ )} + + {step === "starting" && ( +
+

{STRINGS.SETUP_STARTING}

+ {startError ? ( +
+
+
+ +
+

Something went wrong

+

{startError}

+
+
+
+
+ + +
+
+ ) : ( + <> + +

This usually takes about 30 seconds

+ + )} +
+ )} + + {step === "ready" && ( +
+
+ +
+

{STRINGS.SETUP_READY}

+

Your workspace is running and ready to use.

+ +
+ )} +
+
+
+ ); +} diff --git a/coeadapt-launcher/src/vite-env.d.ts b/coeadapt-launcher/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/coeadapt-launcher/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/coeadapt-launcher/tsconfig.json b/coeadapt-launcher/tsconfig.json new file mode 100644 index 000000000..a7fc6fbf2 --- /dev/null +++ b/coeadapt-launcher/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/coeadapt-launcher/tsconfig.node.json b/coeadapt-launcher/tsconfig.node.json new file mode 100644 index 000000000..42872c59f --- /dev/null +++ b/coeadapt-launcher/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/coeadapt-launcher/vite.config.ts b/coeadapt-launcher/vite.config.ts new file mode 100644 index 000000000..d47fde9e0 --- /dev/null +++ b/coeadapt-launcher/vite.config.ts @@ -0,0 +1,27 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import tailwindcss from "@tailwindcss/vite"; + +// @ts-expect-error process is a nodejs global +const host = process.env.TAURI_DEV_HOST; + +// https://vite.dev/config/ +export default defineConfig(async () => ({ + plugins: [react(), tailwindcss()], + clearScreen: false, + server: { + port: 1420, + strictPort: true, + host: host || false, + hmr: host + ? { + protocol: "ws", + host, + port: 1421, + } + : undefined, + watch: { + ignored: ["**/src-tauri/**"], + }, + }, +})); diff --git a/dockerfile-kasm-almalinux-8-desktop b/dockerfile-kasm-almalinux-8-desktop index 53e38adf1..1667fd520 100644 --- a/dockerfile-kasm-almalinux-8-desktop +++ b/dockerfile-kasm-almalinux-8-desktop @@ -10,84 +10,47 @@ ENV STARTUPDIR /dockerstartup WORKDIR $HOME ### Envrionment config -ENV DEBIAN_FRONTEND noninteractive -ENV KASM_RX_HOME $STARTUPDIR/kasmrx -ENV INST_SCRIPTS $STARTUPDIR/install -ENV DONT_PROMPT_WSL_INSTALL "No_Prompt_please" - - -### Install Tools -COPY ./src/oracle/install/tools $INST_SCRIPTS/tools/ -RUN bash $INST_SCRIPTS/tools/install_tools_deluxe.sh && rm -rf $INST_SCRIPTS/tools/ - -# Install Utilities -COPY ./src/oracle/install/misc $INST_SCRIPTS/misc/ -RUN bash $INST_SCRIPTS/misc/install_tools.sh && rm -rf $INST_SCRIPTS/misc/ - -# Install Chromium -COPY ./src/ubuntu/install/chromium $INST_SCRIPTS/chromium/ -RUN bash $INST_SCRIPTS/chromium/install_chromium.sh && rm -rf $INST_SCRIPTS/chromium/ - -# Install Firefox -COPY ./src/ubuntu/install/firefox/ $INST_SCRIPTS/firefox/ -COPY ./src/ubuntu/install/firefox/firefox.desktop $HOME/Desktop/ -RUN bash $INST_SCRIPTS/firefox/install_firefox.sh && rm -rf $INST_SCRIPTS/firefox/ - -### Install Sublime Text -COPY ./src/oracle/install/sublime_text $INST_SCRIPTS/sublime_text/ -RUN bash $INST_SCRIPTS/sublime_text/install_sublime_text.sh && rm -rf $INST_SCRIPTS/sublime_text/ - -### Install NextCloud -COPY ./src/ubuntu/install/nextcloud $INST_SCRIPTS/nextcloud/ -RUN bash $INST_SCRIPTS/nextcloud/install_nextcloud.sh && rm -rf $INST_SCRIPTS/nextcloud/ - -### Install Remmina -COPY ./src/ubuntu/install/remmina $INST_SCRIPTS/remmina/ -RUN bash $INST_SCRIPTS/remmina/install_remmina.sh && rm -rf $INST_SCRIPTS/remmina/ - -### Install Only Office -COPY ./src/oracle/install/only_office $INST_SCRIPTS/only_office/ -RUN bash $INST_SCRIPTS/only_office/install_only_office.sh && rm -rf $INST_SCRIPTS/only_office/ - -### Install GIMP -COPY ./src/oracle/install/gimp $INST_SCRIPTS/gimp/ -RUN bash $INST_SCRIPTS/gimp/install_gimp.sh && rm -rf $INST_SCRIPTS/gimp/ - -### Install Zoom -COPY ./src/oracle/install/zoom $INST_SCRIPTS/zoom/ -RUN bash $INST_SCRIPTS/zoom/install_zoom.sh && rm -rf $INST_SCRIPTS/zoom/ - -### Install OBS Studio -COPY ./src/oracle/install/obs $INST_SCRIPTS/obs/ -RUN bash $INST_SCRIPTS/obs/install_obs.sh && rm -rf $INST_SCRIPTS/obs/ - -### Install Ansible -COPY ./src/oracle/install/ansible $INST_SCRIPTS/ansible/ -RUN bash $INST_SCRIPTS/ansible/install_ansible.sh && rm -rf $INST_SCRIPTS/ansible/ - -### Install Terraform -COPY ./src/oracle/install/terraform $INST_SCRIPTS/terraform/ -RUN bash $INST_SCRIPTS/terraform/install_terraform.sh && rm -rf $INST_SCRIPTS/terraform/ - -### Install Telegram -COPY ./src/oracle/install/telegram $INST_SCRIPTS/telegram/ -RUN bash $INST_SCRIPTS/telegram/install_telegram.sh && rm -rf $INST_SCRIPTS/telegram/ - -### Install Thunderbird -COPY ./src/ubuntu/install/thunderbird $INST_SCRIPTS/thunderbird/ -RUN bash $INST_SCRIPTS/thunderbird/install_thunderbird.sh && rm -rf $INST_SCRIPTS/thunderbird/ - -#ADD ./src/common/scripts $STARTUPDIR -RUN $STARTUPDIR/set_user_permission.sh $HOME - -RUN rm -f /etc/X11/xinit/Xclients - -RUN chown 1000:0 $HOME - +ENV SKIP_CLEAN=true \ + KASM_RX_HOME=$STARTUPDIR/kasmrx \ + DONT_PROMPT_WSL_INSTALL="No_Prompt_please" \ + INST_DIR=$STARTUPDIR/install \ + INST_SCRIPTS="/oracle/install/tools/install_tools_deluxe.sh \ + /oracle/install/misc/install_tools.sh \ + /ubuntu/install/chromium/install_chromium.sh \ + /ubuntu/install/firefox/install_firefox.sh \ + /oracle/install/sublime_text/install_sublime_text.sh \ + /ubuntu/install/nextcloud/install_nextcloud.sh \ + /oracle/install/vs_code/install_vs_code.sh \ + /ubuntu/install/remmina/install_remmina.sh \ + /oracle/install/only_office/install_only_office.sh \ + /oracle/install/gimp/install_gimp.sh \ + /oracle/install/zoom/install_zoom.sh \ + /oracle/install/ansible/install_ansible.sh \ + /oracle/install/terraform/install_terraform.sh \ + /oracle/install/telegram/install_telegram.sh \ + /oracle/install/obs/install_obs.sh \ + /ubuntu/install/thunderbird/install_thunderbird.sh \ + /ubuntu/install/slack/install_slack.sh \ + /ubuntu/install/cleanup/cleanup.sh" + +# Copy install scripts +COPY ./src/ $INST_DIR + +# Run installations +RUN \ + for SCRIPT in $INST_SCRIPTS; do \ + bash ${INST_DIR}${SCRIPT} || exit 1; \ + done && \ + $STARTUPDIR/set_user_permission.sh $HOME && \ + rm -f /etc/X11/xinit/Xclients && \ + chown 1000:0 $HOME && \ + mkdir -p /home/kasm-user && \ + chown -R 1000:0 /home/kasm-user && \ + rm -Rf ${INST_DIR} + +# Userspace Runtime ENV HOME /home/kasm-user WORKDIR $HOME -RUN mkdir -p $HOME && chown -R 1000:0 $HOME - USER 1000 CMD ["--tail-log"] diff --git a/dockerfile-kasm-almalinux-9-desktop b/dockerfile-kasm-almalinux-9-desktop index ae6d14dd9..3884d5ff5 100644 --- a/dockerfile-kasm-almalinux-9-desktop +++ b/dockerfile-kasm-almalinux-9-desktop @@ -10,79 +10,45 @@ ENV STARTUPDIR /dockerstartup WORKDIR $HOME ### Envrionment config -ENV DEBIAN_FRONTEND noninteractive -ENV KASM_RX_HOME $STARTUPDIR/kasmrx -ENV INST_SCRIPTS $STARTUPDIR/install -ENV DONT_PROMPT_WSL_INSTALL "No_Prompt_please" - -### Install Tools -COPY ./src/oracle/install/tools $INST_SCRIPTS/tools/ -RUN bash $INST_SCRIPTS/tools/install_tools_deluxe.sh && rm -rf $INST_SCRIPTS/tools/ - -# Install Utilities -COPY ./src/oracle/install/misc $INST_SCRIPTS/misc/ -RUN bash $INST_SCRIPTS/misc/install_tools.sh && rm -rf $INST_SCRIPTS/misc/ - -# Install Chromium -COPY ./src/ubuntu/install/chromium $INST_SCRIPTS/chromium/ -RUN bash $INST_SCRIPTS/chromium/install_chromium.sh && rm -rf $INST_SCRIPTS/chromium/ - -# Install Firefox -COPY ./src/ubuntu/install/firefox/ $INST_SCRIPTS/firefox/ -COPY ./src/ubuntu/install/firefox/firefox.desktop $HOME/Desktop/ -RUN bash $INST_SCRIPTS/firefox/install_firefox.sh && rm -rf $INST_SCRIPTS/firefox/ - -### Install Sublime Text -COPY ./src/oracle/install/sublime_text $INST_SCRIPTS/sublime_text/ -RUN bash $INST_SCRIPTS/sublime_text/install_sublime_text.sh && rm -rf $INST_SCRIPTS/sublime_text/ - -### Install Visual Studio Code -COPY ./src/oracle/install/vs_code $INST_SCRIPTS/vs_code/ -RUN bash $INST_SCRIPTS/vs_code/install_vs_code.sh && rm -rf $INST_SCRIPTS/vs_code/ - -### Install Remmina -COPY ./src/ubuntu/install/remmina $INST_SCRIPTS/remmina/ -RUN bash $INST_SCRIPTS/remmina/install_remmina.sh && rm -rf $INST_SCRIPTS/remmina/ - -### Install Only Office -COPY ./src/oracle/install/only_office $INST_SCRIPTS/only_office/ -RUN bash $INST_SCRIPTS/only_office/install_only_office.sh && rm -rf $INST_SCRIPTS/only_office/ - -### Install GIMP -COPY ./src/oracle/install/gimp $INST_SCRIPTS/gimp/ -RUN bash $INST_SCRIPTS/gimp/install_gimp.sh && rm -rf $INST_SCRIPTS/gimp/ - -### Install Zoom -COPY ./src/oracle/install/zoom $INST_SCRIPTS/zoom/ -RUN bash $INST_SCRIPTS/zoom/install_zoom.sh && rm -rf $INST_SCRIPTS/zoom/ - -### Install Ansible -COPY ./src/oracle/install/ansible $INST_SCRIPTS/ansible/ -RUN bash $INST_SCRIPTS/ansible/install_ansible.sh && rm -rf $INST_SCRIPTS/ansible/ - -### Install Terraform -COPY ./src/oracle/install/terraform $INST_SCRIPTS/terraform/ -RUN bash $INST_SCRIPTS/terraform/install_terraform.sh && rm -rf $INST_SCRIPTS/terraform/ - -### Install Telegram -COPY ./src/oracle/install/telegram $INST_SCRIPTS/telegram/ -RUN bash $INST_SCRIPTS/telegram/install_telegram.sh && rm -rf $INST_SCRIPTS/telegram/ - -### Install Thunderbird -COPY ./src/ubuntu/install/thunderbird $INST_SCRIPTS/thunderbird/ -RUN bash $INST_SCRIPTS/thunderbird/install_thunderbird.sh && rm -rf $INST_SCRIPTS/thunderbird/ - -#ADD ./src/common/scripts $STARTUPDIR -RUN $STARTUPDIR/set_user_permission.sh $HOME - -RUN rm -f /etc/X11/xinit/Xclients - -RUN chown 1000:0 $HOME - +ENV SKIP_CLEAN=true \ + KASM_RX_HOME=$STARTUPDIR/kasmrx \ + DONT_PROMPT_WSL_INSTALL="No_Prompt_please" \ + INST_DIR=$STARTUPDIR/install \ + INST_SCRIPTS="/oracle/install/tools/install_tools_deluxe.sh \ + /oracle/install/misc/install_tools.sh \ + /ubuntu/install/chromium/install_chromium.sh \ + /ubuntu/install/firefox/install_firefox.sh \ + /oracle/install/sublime_text/install_sublime_text.sh \ + /oracle/install/vs_code/install_vs_code.sh \ + /ubuntu/install/remmina/install_remmina.sh \ + /oracle/install/only_office/install_only_office.sh \ + /oracle/install/gimp/install_gimp.sh \ + /oracle/install/zoom/install_zoom.sh \ + /oracle/install/ansible/install_ansible.sh \ + /oracle/install/terraform/install_terraform.sh \ + /oracle/install/telegram/install_telegram.sh \ + /ubuntu/install/thunderbird/install_thunderbird.sh \ + /ubuntu/install/slack/install_slack.sh \ + /ubuntu/install/cleanup/cleanup.sh" + +# Copy install scripts +COPY ./src/ $INST_DIR + +# Run installations +RUN \ + for SCRIPT in $INST_SCRIPTS; do \ + bash ${INST_DIR}${SCRIPT} || exit 1; \ + done && \ + $STARTUPDIR/set_user_permission.sh $HOME && \ + rm -f /etc/X11/xinit/Xclients && \ + chown 1000:0 $HOME && \ + mkdir -p /home/kasm-user && \ + chown -R 1000:0 /home/kasm-user && \ + rm -Rf ${INST_DIR} + +# Userspace Runtime ENV HOME /home/kasm-user WORKDIR $HOME -RUN mkdir -p $HOME && chown -R 1000:0 $HOME - USER 1000 CMD ["--tail-log"] diff --git a/dockerfile-kasm-alpine-317-desktop b/dockerfile-kasm-alpine-317-desktop deleted file mode 100644 index 05b013c2a..000000000 --- a/dockerfile-kasm-alpine-317-desktop +++ /dev/null @@ -1,97 +0,0 @@ -ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-alpine-317" -FROM kasmweb/$BASE_IMAGE:$BASE_TAG - -USER root - -ENV DISTRO=alpine317 -ENV HOME /home/kasm-default-profile -ENV STARTUPDIR /dockerstartup -WORKDIR $HOME - -### Envrionment config -ENV INST_SCRIPTS $STARTUPDIR/install - -### Install Tools -COPY ./src/alpine/install/tools $INST_SCRIPTS/tools/ -RUN bash $INST_SCRIPTS/tools/install_tools_deluxe.sh && rm -rf $INST_SCRIPTS/tools/ - -# Install Utilities -COPY ./src/alpine/install/misc $INST_SCRIPTS/misc/ -RUN bash $INST_SCRIPTS/misc/install_tools.sh && rm -rf $INST_SCRIPTS/misc/ - -# Install Chromium -COPY ./src/alpine/install/chromium $INST_SCRIPTS/chromium/ -RUN bash $INST_SCRIPTS/chromium/install_chromium.sh && rm -rf $INST_SCRIPTS/chromium/ - -# Install Firefox -COPY ./src/alpine/install/firefox/ $INST_SCRIPTS/firefox/ -COPY ./src/alpine/install/firefox/firefox.desktop $HOME/Desktop/ -RUN bash $INST_SCRIPTS/firefox/install_firefox.sh && rm -rf $INST_SCRIPTS/firefox/ - -### Install Remmina -COPY ./src/alpine/install/remmina $INST_SCRIPTS/remmina/ -RUN bash $INST_SCRIPTS/remmina/install_remmina.sh && rm -rf $INST_SCRIPTS/remmina/ - -### Install GIMP -COPY ./src/alpine/install/gimp $INST_SCRIPTS/gimp/ -RUN bash $INST_SCRIPTS/gimp/install_gimp.sh && rm -rf $INST_SCRIPTS/gimp/ - -### Install Ansible -COPY ./src/alpine/install/ansible $INST_SCRIPTS/ansible/ -RUN bash $INST_SCRIPTS/ansible/install_ansible.sh && rm -rf $INST_SCRIPTS/ansible/ - -### Install Terraform -COPY ./src/alpine/install/terraform $INST_SCRIPTS/terraform/ -RUN bash $INST_SCRIPTS/terraform/install_terraform.sh && rm -rf $INST_SCRIPTS/terraform/ - -### Install Thunderbird -COPY ./src/alpine/install/thunderbird $INST_SCRIPTS/thunderbird/ -RUN bash $INST_SCRIPTS/thunderbird/install_thunderbird.sh && rm -rf $INST_SCRIPTS/thunderbird/ - -### Install Audacity -COPY ./src/alpine/install/audacity $INST_SCRIPTS/audacity/ -RUN bash $INST_SCRIPTS/audacity/install_audacity.sh && rm -rf $INST_SCRIPTS/audacity/ - -### Install Blender -COPY ./src/alpine/install/blender $INST_SCRIPTS/blender/ -RUN bash $INST_SCRIPTS/blender/install_blender.sh && rm -rf $INST_SCRIPTS/blender/ - -### Install Geany -COPY ./src/alpine/install/geany $INST_SCRIPTS/geany/ -RUN bash $INST_SCRIPTS/geany/install_geany.sh && rm -rf $INST_SCRIPTS/geany/ - -### Install Inkscape -COPY ./src/alpine/install/inkscape $INST_SCRIPTS/inkscape/ -RUN bash $INST_SCRIPTS/inkscape/install_inkscape.sh && rm -rf $INST_SCRIPTS/inkscape/ - -### Install LibreOffice -COPY ./src/alpine/install/libre_office $INST_SCRIPTS/libre_office/ -RUN bash $INST_SCRIPTS/libre_office/install_libre_office.sh && rm -rf $INST_SCRIPTS/libre_office/ - -### Install Pinta -COPY ./src/alpine/install/pinta $INST_SCRIPTS/pinta/ -RUN bash $INST_SCRIPTS/pinta/install_pinta.sh && rm -rf $INST_SCRIPTS/pinta/ - -### Install OBS -COPY ./src/alpine/install/obs $INST_SCRIPTS/obs/ -RUN bash $INST_SCRIPTS/obs/install_obs.sh && rm -rf $INST_SCRIPTS/obs/ - -### Install Filezilla -COPY ./src/alpine/install/filezilla $INST_SCRIPTS/filezilla/ -RUN bash $INST_SCRIPTS/filezilla/install_filezilla.sh && rm -rf $INST_SCRIPTS/filezilla/ - -#ADD ./src/common/scripts $STARTUPDIR -RUN $STARTUPDIR/set_user_permission.sh $HOME - -RUN rm -f /etc/X11/xinit/Xclients - -RUN chown 1000:0 $HOME - -ENV HOME /home/kasm-user -WORKDIR $HOME -RUN mkdir -p $HOME && chown -R 1000:0 $HOME - -USER 1000 - -CMD ["--tail-log"] diff --git a/dockerfile-kasm-alpine-319-desktop b/dockerfile-kasm-alpine-319-desktop new file mode 100644 index 000000000..491a73b83 --- /dev/null +++ b/dockerfile-kasm-alpine-319-desktop @@ -0,0 +1,55 @@ +ARG BASE_TAG="develop" +ARG BASE_IMAGE="core-alpine-319" +FROM kasmweb/$BASE_IMAGE:$BASE_TAG + +USER root + +ENV DISTRO=alpine319 +ENV HOME /home/kasm-default-profile +ENV STARTUPDIR /dockerstartup +WORKDIR $HOME + +### Envrionment config +ENV SKIP_CLEAN=true \ + INST_DIR=$STARTUPDIR/install \ + INST_SCRIPTS="/alpine/install/tools/install_tools_deluxe.sh \ + /alpine/install/misc/install_tools.sh \ + /alpine/install/firefox/install_firefox.sh \ + /alpine/install/remmina/install_remmina.sh \ + /alpine/install/gimp/install_gimp.sh \ + /alpine/install/ansible/install_ansible.sh \ + /alpine/install/terraform/install_terraform.sh \ + /alpine/install/thunderbird/install_thunderbird.sh \ + /alpine/install/audacity/install_audacity.sh \ + /alpine/install/blender/install_blender.sh \ + /alpine/install/geany/install_geany.sh \ + /alpine/install/inkscape/install_inkscape.sh \ + /alpine/install/libre_office/install_libre_office.sh \ + /alpine/install/pinta/install_pinta.sh \ + /alpine/install/obs/install_obs.sh \ + /alpine/install/filezilla/install_filezilla.sh \ + /alpine/install/chromium/install_chromium.sh \ + /ubuntu/install/langpacks/install_langpacks.sh \ + /ubuntu/install/cleanup/cleanup.sh" + +# Copy install scripts +COPY ./src/ $INST_DIR + +# Run installations +RUN \ + for SCRIPT in $INST_SCRIPTS; do \ + bash ${INST_DIR}${SCRIPT} || exit 1; \ + done && \ + $STARTUPDIR/set_user_permission.sh $HOME && \ + rm -f /etc/X11/xinit/Xclients && \ + chown 1000:0 $HOME && \ + mkdir -p /home/kasm-user && \ + chown -R 1000:0 /home/kasm-user && \ + rm -Rf ${INST_DIR} + +# Userspace Runtime +ENV HOME /home/kasm-user +WORKDIR $HOME +USER 1000 + +CMD ["--tail-log"] diff --git a/dockerfile-kasm-alpine-320-desktop b/dockerfile-kasm-alpine-320-desktop new file mode 100644 index 000000000..2a61d9ef8 --- /dev/null +++ b/dockerfile-kasm-alpine-320-desktop @@ -0,0 +1,55 @@ +ARG BASE_TAG="develop" +ARG BASE_IMAGE="core-alpine-320" +FROM kasmweb/$BASE_IMAGE:$BASE_TAG + +USER root + +ENV DISTRO=alpine320 +ENV HOME /home/kasm-default-profile +ENV STARTUPDIR /dockerstartup +WORKDIR $HOME + +### Envrionment config +ENV SKIP_CLEAN=true \ + INST_DIR=$STARTUPDIR/install \ + INST_SCRIPTS="/alpine/install/tools/install_tools_deluxe.sh \ + /alpine/install/misc/install_tools.sh \ + /alpine/install/firefox/install_firefox.sh \ + /alpine/install/remmina/install_remmina.sh \ + /alpine/install/gimp/install_gimp.sh \ + /alpine/install/ansible/install_ansible.sh \ + /alpine/install/terraform/install_terraform.sh \ + /alpine/install/thunderbird/install_thunderbird.sh \ + /alpine/install/audacity/install_audacity.sh \ + /alpine/install/blender/install_blender.sh \ + /alpine/install/geany/install_geany.sh \ + /alpine/install/inkscape/install_inkscape.sh \ + /alpine/install/libre_office/install_libre_office.sh \ + /alpine/install/pinta/install_pinta.sh \ + /alpine/install/obs/install_obs.sh \ + /alpine/install/filezilla/install_filezilla.sh \ + /alpine/install/chromium/install_chromium.sh \ + /ubuntu/install/langpacks/install_langpacks.sh \ + /ubuntu/install/cleanup/cleanup.sh" + +# Copy install scripts +COPY ./src/ $INST_DIR + +# Run installations +RUN \ + for SCRIPT in $INST_SCRIPTS; do \ + bash ${INST_DIR}${SCRIPT} || exit 1; \ + done && \ + $STARTUPDIR/set_user_permission.sh $HOME && \ + rm -f /etc/X11/xinit/Xclients && \ + chown 1000:0 $HOME && \ + mkdir -p /home/kasm-user && \ + chown -R 1000:0 /home/kasm-user && \ + rm -Rf ${INST_DIR} + +# Userspace Runtime +ENV HOME /home/kasm-user +WORKDIR $HOME +USER 1000 + +CMD ["--tail-log"] diff --git a/dockerfile-kasm-alpine-321-desktop b/dockerfile-kasm-alpine-321-desktop new file mode 100644 index 000000000..ca32934f9 --- /dev/null +++ b/dockerfile-kasm-alpine-321-desktop @@ -0,0 +1,55 @@ +ARG BASE_TAG="develop" +ARG BASE_IMAGE="core-alpine-321" +FROM kasmweb/$BASE_IMAGE:$BASE_TAG + +USER root + +ENV DISTRO=alpine321 +ENV HOME /home/kasm-default-profile +ENV STARTUPDIR /dockerstartup +WORKDIR $HOME + +### Envrionment config +ENV SKIP_CLEAN=true \ + INST_DIR=$STARTUPDIR/install \ + INST_SCRIPTS="/alpine/install/tools/install_tools_deluxe.sh \ + /alpine/install/misc/install_tools.sh \ + /alpine/install/firefox/install_firefox.sh \ + /alpine/install/remmina/install_remmina.sh \ + /alpine/install/gimp/install_gimp.sh \ + /alpine/install/ansible/install_ansible.sh \ + /alpine/install/terraform/install_terraform.sh \ + /alpine/install/thunderbird/install_thunderbird.sh \ + /alpine/install/audacity/install_audacity.sh \ + /alpine/install/blender/install_blender.sh \ + /alpine/install/geany/install_geany.sh \ + /alpine/install/inkscape/install_inkscape.sh \ + /alpine/install/libre_office/install_libre_office.sh \ + /alpine/install/pinta/install_pinta.sh \ + /alpine/install/obs/install_obs.sh \ + /alpine/install/filezilla/install_filezilla.sh \ + /alpine/install/chromium/install_chromium.sh \ + /ubuntu/install/langpacks/install_langpacks.sh \ + /ubuntu/install/cleanup/cleanup.sh" + +# Copy install scripts +COPY ./src/ $INST_DIR + +# Run installations +RUN \ + for SCRIPT in $INST_SCRIPTS; do \ + bash ${INST_DIR}${SCRIPT} || exit 1; \ + done && \ + $STARTUPDIR/set_user_permission.sh $HOME && \ + rm -f /etc/X11/xinit/Xclients && \ + chown 1000:0 $HOME && \ + mkdir -p /home/kasm-user && \ + chown -R 1000:0 /home/kasm-user && \ + rm -Rf ${INST_DIR} + +# Userspace Runtime +ENV HOME /home/kasm-user +WORKDIR $HOME +USER 1000 + +CMD ["--tail-log"] diff --git a/dockerfile-kasm-atom b/dockerfile-kasm-atom index 6575782b5..2118cb4a2 100644 --- a/dockerfile-kasm-atom +++ b/dockerfile-kasm-atom @@ -21,7 +21,7 @@ RUN chmod 755 $STARTUPDIR/custom_startup.sh # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel ######### End Customizations ########### diff --git a/dockerfile-kasm-audacity b/dockerfile-kasm-audacity index 72b703f4d..0106584f2 100644 --- a/dockerfile-kasm-audacity +++ b/dockerfile-kasm-audacity @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root @@ -21,7 +21,7 @@ RUN chmod 755 $STARTUPDIR/custom_startup.sh # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel ######### End Customizations ########### diff --git a/dockerfile-kasm-blender b/dockerfile-kasm-blender index 452a20071..a0da59786 100644 --- a/dockerfile-kasm-blender +++ b/dockerfile-kasm-blender @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root @@ -20,7 +20,7 @@ RUN chmod 755 $STARTUPDIR/custom_startup.sh # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel ######### End Customizations ########### diff --git a/dockerfile-kasm-brave b/dockerfile-kasm-brave index 3ef8f3271..2e876edf9 100644 --- a/dockerfile-kasm-brave +++ b/dockerfile-kasm-brave @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root @@ -17,9 +17,14 @@ RUN bash $INST_SCRIPTS/brave/install_brave.sh && rm -rf $INST_SCRIPTS/brave/ # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel +# Security modifications +COPY ./src/ubuntu/install/misc/single_app_security.sh $INST_SCRIPTS/misc/ +RUN bash $INST_SCRIPTS/misc/single_app_security.sh -t && rm -rf $INST_SCRIPTS/misc/ +COPY ./src/common/chrome-managed-policies/urlblocklist.json /etc/brave/policies/managed/urlblocklist.json + # Setup the custom startup script that will be invoked when the container starts #ENV LAUNCH_URL http://kasmweb.com @@ -29,6 +34,8 @@ RUN chmod +x $STARTUPDIR/custom_startup.sh ENV KASM_RESTRICTED_FILE_CHOOSER=1 COPY ./src/ubuntu/install/gtk/ $INST_SCRIPTS/gtk/ RUN bash $INST_SCRIPTS/gtk/install_restricted_file_chooser.sh +COPY ./src/ubuntu/install/close_browser_breakout_via_file_manager/ $INST_SCRIPTS/close_browser_breakout_via_file_manager/ +RUN bash $INST_SCRIPTS/close_browser_breakout_via_file_manager/replace_thunar_with_empty_script.sh ######### End Customizations ########### diff --git a/dockerfile-kasm-centos-7-desktop b/dockerfile-kasm-centos-7-desktop deleted file mode 100644 index f97ed9baf..000000000 --- a/dockerfile-kasm-centos-7-desktop +++ /dev/null @@ -1,41 +0,0 @@ -ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-centos-7" -FROM kasmweb/$BASE_IMAGE:$BASE_TAG -USER root - -ENV DISTRO=centos -ENV HOME /home/kasm-default-profile -ENV STARTUPDIR /dockerstartup -ENV INST_SCRIPTS $STARTUPDIR/install -WORKDIR $HOME - -######### Customize Container Here ########### - - -# Install Utilities -COPY ./src/ubuntu/install/misc $INST_SCRIPTS/misc/ -RUN bash $INST_SCRIPTS/misc/install_tools.sh && rm -rf $INST_SCRIPTS/misc/ - -# Install Google Chrome -COPY ./src/ubuntu/install/chrome $INST_SCRIPTS/chrome/ -RUN bash $INST_SCRIPTS/chrome/install_chrome.sh && rm -rf $INST_SCRIPTS/chrome/ - -# Install Firefox -COPY ./src/ubuntu/install/firefox/ $INST_SCRIPTS/firefox/ -COPY ./src/ubuntu/install/firefox/firefox.desktop $HOME/Desktop/ -RUN bash $INST_SCRIPTS/firefox/install_firefox.sh && rm -rf $INST_SCRIPTS/firefox/ - -# Install Thunderbird -COPY ./src/ubuntu/install/thunderbird $INST_SCRIPTS/thunderbird/ -RUN bash $INST_SCRIPTS/thunderbird/install_thunderbird.sh && rm -rf $INST_SCRIPTS/thunderbird/ - -######### End Customizations ########### - -RUN chown 1000:0 $HOME -RUN "$STARTUPDIR/set_user_permission.sh" $HOME - -ENV HOME /home/kasm-user -WORKDIR $HOME -RUN mkdir -p $HOME && chown -R 1000:0 $HOME - -USER 1000 diff --git a/dockerfile-kasm-chrome b/dockerfile-kasm-chrome index aaf404686..f427c81f8 100644 --- a/dockerfile-kasm-chrome +++ b/dockerfile-kasm-chrome @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root @@ -15,11 +15,19 @@ WORKDIR $HOME COPY ./src/ubuntu/install/chrome $INST_SCRIPTS/chrome/ RUN bash $INST_SCRIPTS/chrome/install_chrome.sh && rm -rf $INST_SCRIPTS/chrome/ +# Install unzip +RUN apt-get update && apt-get install -y unzip + # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel +# Security modifications +COPY ./src/ubuntu/install/misc/single_app_security.sh $INST_SCRIPTS/misc/ +RUN bash $INST_SCRIPTS/misc/single_app_security.sh -t && rm -rf $INST_SCRIPTS/misc/ +COPY ./src/common/chrome-managed-policies/urlblocklist.json /etc/opt/chrome/policies/managed/urlblocklist.json + # Setup the custom startup script that will be invoked when the container starts #ENV LAUNCH_URL http://kasmweb.com @@ -33,6 +41,8 @@ RUN chmod +x $STARTUPDIR/custom_startup.sh ENV KASM_RESTRICTED_FILE_CHOOSER=1 COPY ./src/ubuntu/install/gtk/ $INST_SCRIPTS/gtk/ RUN bash $INST_SCRIPTS/gtk/install_restricted_file_chooser.sh +COPY ./src/ubuntu/install/close_browser_breakout_via_file_manager/ $INST_SCRIPTS/close_browser_breakout_via_file_manager/ +RUN bash $INST_SCRIPTS/close_browser_breakout_via_file_manager/replace_thunar_with_empty_script.sh ######### End Customizations ########### diff --git a/dockerfile-kasm-chromium b/dockerfile-kasm-chromium index 5e843def6..552bbbab9 100644 --- a/dockerfile-kasm-chromium +++ b/dockerfile-kasm-chromium @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root @@ -14,11 +14,19 @@ WORKDIR $HOME COPY ./src/ubuntu/install/chromium $INST_SCRIPTS/chromium/ RUN bash $INST_SCRIPTS/chromium/install_chromium.sh && rm -rf $INST_SCRIPTS/chromium/ +# Install unzip +RUN apt-get update && apt-get install -y unzip + # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel +# Security modifications +COPY ./src/ubuntu/install/misc/single_app_security.sh $INST_SCRIPTS/misc/ +RUN bash $INST_SCRIPTS/misc/single_app_security.sh -t && rm -rf $INST_SCRIPTS/misc/ +COPY ./src/common/chrome-managed-policies/urlblocklist.json /etc/chromium/policies/managed/urlblocklist.json + # Setup the custom startup script that will be invoked when the container starts #ENV LAUNCH_URL http://kasmweb.com @@ -32,6 +40,8 @@ RUN chmod +x $STARTUPDIR/custom_startup.sh ENV KASM_RESTRICTED_FILE_CHOOSER=1 COPY ./src/ubuntu/install/gtk/ $INST_SCRIPTS/gtk/ RUN bash $INST_SCRIPTS/gtk/install_restricted_file_chooser.sh +COPY ./src/ubuntu/install/close_browser_breakout_via_file_manager/ $INST_SCRIPTS/close_browser_breakout_via_file_manager/ +RUN bash $INST_SCRIPTS/close_browser_breakout_via_file_manager/replace_thunar_with_empty_script.sh ######### End Customizations ########### diff --git a/dockerfile-kasm-cyberbro b/dockerfile-kasm-cyberbro new file mode 100644 index 000000000..6505857fc --- /dev/null +++ b/dockerfile-kasm-cyberbro @@ -0,0 +1,44 @@ +ARG BASE_TAG="develop" +ARG BASE_IMAGE="core-ubuntu-jammy" +FROM kasmweb/$BASE_IMAGE:$BASE_TAG +USER root + +ENV HOME /home/kasm-default-profile +ENV STARTUPDIR /dockerstartup +ENV INST_SCRIPTS $STARTUPDIR/install +WORKDIR $HOME + +######### Customize Container Here ########### + +# Cyberbro requires a browser, install Firefox +COPY ./src/ubuntu/install/firefox/ $INST_SCRIPTS/firefox/ +COPY ./src/ubuntu/install/firefox/firefox.desktop $HOME/Desktop/ +RUN bash $INST_SCRIPTS/firefox/install_firefox.sh && rm -rf $INST_SCRIPTS/firefox/ + +# Install Cyberbro +COPY ./src/ubuntu/install/cyberbro $INST_SCRIPTS/cyberbro/ +RUN bash $INST_SCRIPTS/cyberbro/install_cyberbro.sh && rm -rf $INST_SCRIPTS/cyberbro/ + +COPY ./src/ubuntu/install/cyberbro/custom_startup.sh $STARTUPDIR/custom_startup.sh +RUN chmod +x $STARTUPDIR/custom_startup.sh +RUN chmod 755 $STARTUPDIR/custom_startup.sh + + +# Update the desktop environment to be optimized for a single application +RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png +RUN apt-get remove -y xfce4-panel + + +######### End Customizations ########### + +#ADD ./src/common/scripts $STARTUPDIR +RUN $STARTUPDIR/set_user_permission.sh $HOME + +RUN chown 1000:0 $HOME + +ENV HOME /home/kasm-user +WORKDIR $HOME +RUN mkdir -p $HOME && chown -R 1000:0 $HOME + +USER 1000 diff --git a/dockerfile-kasm-debian-bookworm-desktop b/dockerfile-kasm-debian-bookworm-desktop new file mode 100644 index 000000000..9e289e5f4 --- /dev/null +++ b/dockerfile-kasm-debian-bookworm-desktop @@ -0,0 +1,58 @@ +ARG BASE_TAG="develop" +ARG BASE_IMAGE="core-debian-bookworm" +FROM kasmweb/$BASE_IMAGE:$BASE_TAG + +USER root + +ENV HOME /home/kasm-default-profile +ENV STARTUPDIR /dockerstartup +WORKDIR $HOME + +### Envrionment config +ENV DEBIAN_FRONTEND=noninteractive \ + SKIP_CLEAN=true \ + KASM_RX_HOME=$STARTUPDIR/kasmrx \ + DONT_PROMPT_WSL_INSTALL="No_Prompt_please" \ + INST_DIR=$STARTUPDIR/install \ + INST_SCRIPTS="/ubuntu/install/tools/install_tools_deluxe.sh \ + /ubuntu/install/misc/install_tools.sh \ + /ubuntu/install/chrome/install_chrome.sh \ + /ubuntu/install/chromium/install_chromium.sh \ + /ubuntu/install/firefox/install_firefox.sh \ + /ubuntu/install/sublime_text/install_sublime_text.sh \ + /ubuntu/install/vs_code/install_vs_code.sh \ + /ubuntu/install/remmina/install_remmina.sh \ + /ubuntu/install/only_office/install_only_office.sh \ + /ubuntu/install/signal/install_signal.sh \ + /ubuntu/install/gimp/install_gimp.sh \ + /ubuntu/install/zoom/install_zoom.sh \ + /ubuntu/install/obs/install_obs.sh \ + /ubuntu/install/ansible/install_ansible.sh \ + /ubuntu/install/terraform/install_terraform.sh \ + /ubuntu/install/telegram/install_telegram.sh \ + /ubuntu/install/thunderbird/install_thunderbird.sh \ + /ubuntu/install/slack/install_slack.sh \ + /ubuntu/install/gamepad_utils/install_gamepad_utils.sh \ + /ubuntu/install/cleanup/cleanup.sh" + +# Copy install scripts +COPY ./src/ $INST_DIR + +# Run installations +RUN \ + for SCRIPT in $INST_SCRIPTS; do \ + bash ${INST_DIR}${SCRIPT} || exit 1; \ + done && \ + $STARTUPDIR/set_user_permission.sh $HOME && \ + rm -f /etc/X11/xinit/Xclients && \ + chown 1000:0 $HOME && \ + mkdir -p /home/kasm-user && \ + chown -R 1000:0 /home/kasm-user && \ + rm -Rf ${INST_DIR} + +# Userspace Runtime +ENV HOME /home/kasm-user +WORKDIR $HOME +USER 1000 + +CMD ["--tail-log"] diff --git a/dockerfile-kasm-debian-bullseye-desktop b/dockerfile-kasm-debian-bullseye-desktop index f8ab66a13..ef3c4b70b 100644 --- a/dockerfile-kasm-debian-bullseye-desktop +++ b/dockerfile-kasm-debian-bullseye-desktop @@ -9,93 +9,50 @@ ENV STARTUPDIR /dockerstartup WORKDIR $HOME ### Envrionment config -ENV DEBIAN_FRONTEND noninteractive -ENV KASM_RX_HOME $STARTUPDIR/kasmrx -ENV INST_SCRIPTS $STARTUPDIR/install -ENV DONT_PROMPT_WSL_INSTALL "No_Prompt_please" - -### Install Tools -COPY ./src/ubuntu/install/tools $INST_SCRIPTS/tools/ -RUN bash $INST_SCRIPTS/tools/install_tools_deluxe.sh && rm -rf $INST_SCRIPTS/tools/ - -# Install Utilities -COPY ./src/ubuntu/install/misc $INST_SCRIPTS/misc/ -RUN bash $INST_SCRIPTS/misc/install_tools.sh && rm -rf $INST_SCRIPTS/misc/ - -# Install Google Chrome -COPY ./src/ubuntu/install/chrome $INST_SCRIPTS/chrome/ -RUN bash $INST_SCRIPTS/chrome/install_chrome.sh && rm -rf $INST_SCRIPTS/chrome/ - -# Install Chromium -COPY ./src/ubuntu/install/chromium $INST_SCRIPTS/chromium/ -RUN if [ "$(uname -m)" = "aarch64" ]; then bash $INST_SCRIPTS/chromium/install_chromium.sh; fi && rm -rf $INST_SCRIPTS/chromium/ - -# Install Firefox -COPY ./src/ubuntu/install/firefox/ $INST_SCRIPTS/firefox/ -COPY ./src/ubuntu/install/firefox/firefox.desktop $HOME/Desktop/ -RUN bash $INST_SCRIPTS/firefox/install_firefox.sh && rm -rf $INST_SCRIPTS/firefox/ - -### Install Sublime Text -COPY ./src/ubuntu/install/sublime_text $INST_SCRIPTS/sublime_text/ -RUN bash $INST_SCRIPTS/sublime_text/install_sublime_text.sh && rm -rf $INST_SCRIPTS/sublime_text/ - -### Install Visual Studio Code -COPY ./src/ubuntu/install/vs_code $INST_SCRIPTS/vs_code/ -RUN bash $INST_SCRIPTS/vs_code/install_vs_code.sh && rm -rf $INST_SCRIPTS/vs_code/ - -### Install Remmina -COPY ./src/ubuntu/install/remmina $INST_SCRIPTS/remmina/ -RUN bash $INST_SCRIPTS/remmina/install_remmina.sh && rm -rf $INST_SCRIPTS/remmina/ - -### Install Only Office -COPY ./src/ubuntu/install/only_office $INST_SCRIPTS/only_office/ -RUN bash $INST_SCRIPTS/only_office/install_only_office.sh && rm -rf $INST_SCRIPTS/only_office/ - -### Install Signal -COPY ./src/ubuntu/install/signal $INST_SCRIPTS/signal/ -RUN bash $INST_SCRIPTS/signal/install_signal.sh && rm -rf $INST_SCRIPTS/signal/ - -### Install GIMP -COPY ./src/ubuntu/install/gimp $INST_SCRIPTS/gimp/ -RUN bash $INST_SCRIPTS/gimp/install_gimp.sh && rm -rf $INST_SCRIPTS/gimp/ - -### Install Zoom -COPY ./src/ubuntu/install/zoom $INST_SCRIPTS/zoom/ -RUN bash $INST_SCRIPTS/zoom/install_zoom.sh && rm -rf $INST_SCRIPTS/zoom/ - -### Install OBS Studio -COPY ./src/ubuntu/install/obs $INST_SCRIPTS/obs/ -RUN bash $INST_SCRIPTS/obs/install_obs.sh && rm -rf $INST_SCRIPTS/obs/ - -### Install Ansible -COPY ./src/ubuntu/install/ansible $INST_SCRIPTS/ansible/ -RUN bash $INST_SCRIPTS/ansible/install_ansible.sh && rm -rf $INST_SCRIPTS/ansible/ - -### Install Terraform -COPY ./src/ubuntu/install/terraform $INST_SCRIPTS/terraform/ -RUN bash $INST_SCRIPTS/terraform/install_terraform.sh && rm -rf $INST_SCRIPTS/terraform/ - -### Install Telegram -COPY ./src/ubuntu/install/telegram $INST_SCRIPTS/telegram/ -RUN bash $INST_SCRIPTS/telegram/install_telegram.sh && rm -rf $INST_SCRIPTS/telegram/ - -### Install Thunderbird -COPY ./src/ubuntu/install/thunderbird $INST_SCRIPTS/thunderbird/ -RUN bash $INST_SCRIPTS/thunderbird/install_thunderbird.sh && rm -rf $INST_SCRIPTS/thunderbird/ - -# Install Gamepad Testing Utils -COPY ./src/ubuntu/install/gamepad_utils $INST_SCRIPTS/gamepad_utils/ -RUN bash $INST_SCRIPTS/gamepad_utils/install_gamepad_utils.sh && rm -rf $INST_SCRIPTS/gamepad_utils/ - -#ADD ./src/common/scripts $STARTUPDIR -RUN $STARTUPDIR/set_user_permission.sh $HOME - -RUN chown 1000:0 $HOME - +ENV DEBIAN_FRONTEND=noninteractive \ + SKIP_CLEAN=true \ + KASM_RX_HOME=$STARTUPDIR/kasmrx \ + DONT_PROMPT_WSL_INSTALL="No_Prompt_please" \ + INST_DIR=$STARTUPDIR/install \ + INST_SCRIPTS="/ubuntu/install/tools/install_tools_deluxe.sh \ + /ubuntu/install/misc/install_tools.sh \ + /ubuntu/install/chrome/install_chrome.sh \ + /ubuntu/install/chromium/install_chromium.sh \ + /ubuntu/install/firefox/install_firefox.sh \ + /ubuntu/install/sublime_text/install_sublime_text.sh \ + /ubuntu/install/vs_code/install_vs_code.sh \ + /ubuntu/install/remmina/install_remmina.sh \ + /ubuntu/install/only_office/install_only_office.sh \ + /ubuntu/install/signal/install_signal.sh \ + /ubuntu/install/gimp/install_gimp.sh \ + /ubuntu/install/zoom/install_zoom.sh \ + /ubuntu/install/obs/install_obs.sh \ + /ubuntu/install/ansible/install_ansible.sh \ + /ubuntu/install/terraform/install_terraform.sh \ + /ubuntu/install/telegram/install_telegram.sh \ + /ubuntu/install/thunderbird/install_thunderbird.sh \ + /ubuntu/install/slack/install_slack.sh \ + /ubuntu/install/gamepad_utils/install_gamepad_utils.sh \ + /ubuntu/install/cleanup/cleanup.sh" + +# Copy install scripts +COPY ./src/ $INST_DIR + +# Run installations +RUN \ + for SCRIPT in $INST_SCRIPTS; do \ + bash ${INST_DIR}${SCRIPT} || exit 1; \ + done && \ + $STARTUPDIR/set_user_permission.sh $HOME && \ + rm -f /etc/X11/xinit/Xclients && \ + chown 1000:0 $HOME && \ + mkdir -p /home/kasm-user && \ + chown -R 1000:0 /home/kasm-user && \ + rm -Rf ${INST_DIR} + +# Userspace Runtime ENV HOME /home/kasm-user WORKDIR $HOME -RUN mkdir -p $HOME && chown -R 1000:0 $HOME - USER 1000 -CMD ["--tail-log"] +CMD ["--tail-log"] diff --git a/dockerfile-kasm-debian-trixie-desktop b/dockerfile-kasm-debian-trixie-desktop new file mode 100644 index 000000000..6972da2f4 --- /dev/null +++ b/dockerfile-kasm-debian-trixie-desktop @@ -0,0 +1,58 @@ +ARG BASE_TAG="develop" +ARG BASE_IMAGE="core-debian-trixie" +FROM kasmweb/$BASE_IMAGE:$BASE_TAG + +USER root + +ENV HOME /home/kasm-default-profile +ENV STARTUPDIR /dockerstartup +WORKDIR $HOME + +### Envrionment config +ENV DEBIAN_FRONTEND=noninteractive \ + SKIP_CLEAN=true \ + KASM_RX_HOME=$STARTUPDIR/kasmrx \ + DONT_PROMPT_WSL_INSTALL="No_Prompt_please" \ + INST_DIR=$STARTUPDIR/install \ + INST_SCRIPTS="/ubuntu/install/tools/install_tools_deluxe.sh \ + /ubuntu/install/misc/install_tools.sh \ + /ubuntu/install/chrome/install_chrome.sh \ + /ubuntu/install/chromium/install_chromium.sh \ + /ubuntu/install/firefox/install_firefox.sh \ + /ubuntu/install/sublime_text/install_sublime_text.sh \ + /ubuntu/install/vs_code/install_vs_code.sh \ + /ubuntu/install/remmina/install_remmina.sh \ + /ubuntu/install/only_office/install_only_office.sh \ + /ubuntu/install/signal/install_signal.sh \ + /ubuntu/install/gimp/install_gimp.sh \ + /ubuntu/install/zoom/install_zoom.sh \ + /ubuntu/install/obs/install_obs.sh \ + /ubuntu/install/ansible/install_ansible.sh \ + /ubuntu/install/terraform/install_terraform.sh \ + /ubuntu/install/telegram/install_telegram.sh \ + /ubuntu/install/thunderbird/install_thunderbird.sh \ + /ubuntu/install/slack/install_slack.sh \ + /ubuntu/install/gamepad_utils/install_gamepad_utils.sh \ + /ubuntu/install/cleanup/cleanup.sh" + +# Copy install scripts +COPY ./src/ $INST_DIR + +# Run installations +RUN \ + for SCRIPT in $INST_SCRIPTS; do \ + bash ${INST_DIR}${SCRIPT} || exit 1; \ + done && \ + $STARTUPDIR/set_user_permission.sh $HOME && \ + rm -f /etc/X11/xinit/Xclients && \ + chown 1000:0 $HOME && \ + mkdir -p /home/kasm-user && \ + chown -R 1000:0 /home/kasm-user && \ + rm -Rf ${INST_DIR} + +# Userspace Runtime +ENV HOME /home/kasm-user +WORKDIR $HOME +USER 1000 + +CMD ["--tail-log"] diff --git a/dockerfile-kasm-deluge b/dockerfile-kasm-deluge index 1ffa754a3..786db77ac 100644 --- a/dockerfile-kasm-deluge +++ b/dockerfile-kasm-deluge @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root @@ -21,7 +21,7 @@ RUN chmod 755 $STARTUPDIR/custom_startup.sh # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel ######### End Customizations ########### diff --git a/dockerfile-kasm-desktop b/dockerfile-kasm-desktop index ec45dcf76..31c293608 100644 --- a/dockerfile-kasm-desktop +++ b/dockerfile-kasm-desktop @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root @@ -11,7 +11,7 @@ WORKDIR $HOME ######### Customize Container Here ########### # Add Kasm Branding -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN cp /usr/share/extra/icons/icon_kasm.png /usr/share/extra/icons/icon_default.png RUN sed -i 's/ubuntu-mono-dark/elementary-xfce/g' $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/xsettings.xml diff --git a/dockerfile-kasm-desktop-deluxe b/dockerfile-kasm-desktop-deluxe index 2102f5887..ab84ce6fa 100644 --- a/dockerfile-kasm-desktop-deluxe +++ b/dockerfile-kasm-desktop-deluxe @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root @@ -15,7 +15,7 @@ ENV INST_SCRIPTS $STARTUPDIR/install ENV DONT_PROMPT_WSL_INSTALL "No_Prompt_please" # Add Kasm Branding -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN cp /usr/share/extra/icons/icon_kasm.png /usr/share/extra/icons/icon_default.png RUN sed -i 's/ubuntu-mono-dark/elementary-xfce/g' $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/xsettings.xml diff --git a/dockerfile-kasm-discord b/dockerfile-kasm-discord index fc084c801..4ddfc999c 100644 --- a/dockerfile-kasm-discord +++ b/dockerfile-kasm-discord @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root @@ -21,7 +21,7 @@ RUN chmod 755 $STARTUPDIR/custom_startup.sh # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel diff --git a/dockerfile-kasm-doom b/dockerfile-kasm-doom index 4589f6508..f0f00a725 100644 --- a/dockerfile-kasm-doom +++ b/dockerfile-kasm-doom @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root @@ -20,7 +20,7 @@ RUN chmod 755 $STARTUPDIR/custom_startup.sh # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel ######### End Customizations ########### diff --git a/dockerfile-kasm-edge b/dockerfile-kasm-edge index 20a5b28fa..2b9e23b5f 100644 --- a/dockerfile-kasm-edge +++ b/dockerfile-kasm-edge @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root @@ -17,12 +17,19 @@ RUN bash $INST_SCRIPTS/edge/install_edge.sh && rm -rf $INST_SCRIPTS/edge/ # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel ENV KASM_RESTRICTED_FILE_CHOOSER=1 COPY ./src/ubuntu/install/gtk/ $INST_SCRIPTS/gtk/ RUN bash $INST_SCRIPTS/gtk/install_restricted_file_chooser.sh +COPY ./src/ubuntu/install/close_browser_breakout_via_file_manager/ $INST_SCRIPTS/close_browser_breakout_via_file_manager/ +RUN bash $INST_SCRIPTS/close_browser_breakout_via_file_manager/replace_thunar_with_empty_script.sh + +# Security modifications +COPY ./src/ubuntu/install/misc/single_app_security.sh $INST_SCRIPTS/misc/ +RUN bash $INST_SCRIPTS/misc/single_app_security.sh -t && rm -rf $INST_SCRIPTS/misc/ +COPY ./src/common/chrome-managed-policies/urlblocklist.json /etc/opt/edge/policies/managed/urlblocklist.json # Setup the custom startup script that will be invoked when the container starts #ENV LAUNCH_URL http://kasmweb.com diff --git a/dockerfile-kasm-fedora-37-desktop b/dockerfile-kasm-fedora-37-desktop deleted file mode 100644 index 9bc538452..000000000 --- a/dockerfile-kasm-fedora-37-desktop +++ /dev/null @@ -1,88 +0,0 @@ -ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-fedora-37" -FROM kasmweb/$BASE_IMAGE:$BASE_TAG - -USER root - -ENV DISTRO=fedora37 -ENV HOME /home/kasm-default-profile -ENV STARTUPDIR /dockerstartup -WORKDIR $HOME - -### Envrionment config -ENV DEBIAN_FRONTEND noninteractive -ENV KASM_RX_HOME $STARTUPDIR/kasmrx -ENV INST_SCRIPTS $STARTUPDIR/install -ENV DONT_PROMPT_WSL_INSTALL "No_Prompt_please" - -### Install Tools -COPY ./src/oracle/install/tools $INST_SCRIPTS/tools/ -RUN bash $INST_SCRIPTS/tools/install_tools_deluxe.sh && rm -rf $INST_SCRIPTS/tools/ - -# Install Utilities -COPY ./src/oracle/install/misc $INST_SCRIPTS/misc/ -RUN bash $INST_SCRIPTS/misc/install_tools.sh && rm -rf $INST_SCRIPTS/misc/ - -# Install Chromium -COPY ./src/ubuntu/install/chromium $INST_SCRIPTS/chromium/ -RUN bash $INST_SCRIPTS/chromium/install_chromium.sh && rm -rf $INST_SCRIPTS/chromium/ - -# Install Firefox -COPY ./src/ubuntu/install/firefox/ $INST_SCRIPTS/firefox/ -COPY ./src/ubuntu/install/firefox/firefox.desktop $HOME/Desktop/ -RUN bash $INST_SCRIPTS/firefox/install_firefox.sh && rm -rf $INST_SCRIPTS/firefox/ - -### Install Sublime Text -COPY ./src/oracle/install/sublime_text $INST_SCRIPTS/sublime_text/ -RUN bash $INST_SCRIPTS/sublime_text/install_sublime_text.sh && rm -rf $INST_SCRIPTS/sublime_text/ - -### Install Visual Studio Code -COPY ./src/oracle/install/vs_code $INST_SCRIPTS/vs_code/ -RUN bash $INST_SCRIPTS/vs_code/install_vs_code.sh && rm -rf $INST_SCRIPTS/vs_code/ - -### Install Remmina -COPY ./src/ubuntu/install/remmina $INST_SCRIPTS/remmina/ -RUN bash $INST_SCRIPTS/remmina/install_remmina.sh && rm -rf $INST_SCRIPTS/remmina/ - -### Install Only Office -COPY ./src/oracle/install/only_office $INST_SCRIPTS/only_office/ -RUN bash $INST_SCRIPTS/only_office/install_only_office.sh && rm -rf $INST_SCRIPTS/only_office/ - -### Install GIMP -COPY ./src/oracle/install/gimp $INST_SCRIPTS/gimp/ -RUN bash $INST_SCRIPTS/gimp/install_gimp.sh && rm -rf $INST_SCRIPTS/gimp/ - -### Install Zoom -COPY ./src/oracle/install/zoom $INST_SCRIPTS/zoom/ -RUN bash $INST_SCRIPTS/zoom/install_zoom.sh && rm -rf $INST_SCRIPTS/zoom/ - -### Install Ansible -COPY ./src/oracle/install/ansible $INST_SCRIPTS/ansible/ -RUN bash $INST_SCRIPTS/ansible/install_ansible.sh && rm -rf $INST_SCRIPTS/ansible/ - -### Install Terraform -COPY ./src/oracle/install/terraform $INST_SCRIPTS/terraform/ -RUN bash $INST_SCRIPTS/terraform/install_terraform.sh && rm -rf $INST_SCRIPTS/terraform/ - -### Install Telegram -COPY ./src/oracle/install/telegram $INST_SCRIPTS/telegram/ -RUN bash $INST_SCRIPTS/telegram/install_telegram.sh && rm -rf $INST_SCRIPTS/telegram/ - -### Install Thunderbird -COPY ./src/ubuntu/install/thunderbird $INST_SCRIPTS/thunderbird/ -RUN bash $INST_SCRIPTS/thunderbird/install_thunderbird.sh && rm -rf $INST_SCRIPTS/thunderbird/ - -#ADD ./src/common/scripts $STARTUPDIR -RUN $STARTUPDIR/set_user_permission.sh $HOME - -RUN rm -f /etc/X11/xinit/Xclients - -RUN chown 1000:0 $HOME - -ENV HOME /home/kasm-user -WORKDIR $HOME -RUN mkdir -p $HOME && chown -R 1000:0 $HOME - -USER 1000 - -CMD ["--tail-log"] diff --git a/dockerfile-kasm-fedora-39-desktop b/dockerfile-kasm-fedora-39-desktop new file mode 100644 index 000000000..c0c0efa94 --- /dev/null +++ b/dockerfile-kasm-fedora-39-desktop @@ -0,0 +1,54 @@ +ARG BASE_TAG="develop" +ARG BASE_IMAGE="core-fedora-39" +FROM kasmweb/$BASE_IMAGE:$BASE_TAG + +USER root + +ENV DISTRO=fedora39 +ENV HOME /home/kasm-default-profile +ENV STARTUPDIR /dockerstartup +WORKDIR $HOME + +### Envrionment config +ENV SKIP_CLEAN=true \ + KASM_RX_HOME=$STARTUPDIR/kasmrx \ + DONT_PROMPT_WSL_INSTALL="No_Prompt_please" \ + INST_DIR=$STARTUPDIR/install \ + INST_SCRIPTS="/oracle/install/tools/install_tools_deluxe.sh \ + /oracle/install/misc/install_tools.sh \ + /ubuntu/install/chromium/install_chromium.sh \ + /ubuntu/install/firefox/install_firefox.sh \ + /oracle/install/sublime_text/install_sublime_text.sh \ + /oracle/install/vs_code/install_vs_code.sh \ + /ubuntu/install/remmina/install_remmina.sh \ + /oracle/install/only_office/install_only_office.sh \ + /oracle/install/gimp/install_gimp.sh \ + /oracle/install/zoom/install_zoom.sh \ + /oracle/install/ansible/install_ansible.sh \ + /oracle/install/telegram/install_telegram.sh \ + /oracle/install/terraform/install_terraform.sh \ + /ubuntu/install/thunderbird/install_thunderbird.sh \ + /ubuntu/install/slack/install_slack.sh \ + /ubuntu/install/cleanup/cleanup.sh" + +# Copy install scripts +COPY ./src/ $INST_DIR + +# Run installations +RUN \ + for SCRIPT in $INST_SCRIPTS; do \ + bash ${INST_DIR}${SCRIPT} || exit 1; \ + done && \ + $STARTUPDIR/set_user_permission.sh $HOME && \ + rm -f /etc/X11/xinit/Xclients && \ + chown 1000:0 $HOME && \ + mkdir -p /home/kasm-user && \ + chown -R 1000:0 /home/kasm-user && \ + rm -Rf ${INST_DIR} + +# Userspace Runtime +ENV HOME /home/kasm-user +WORKDIR $HOME +USER 1000 + +CMD ["--tail-log"] diff --git a/dockerfile-kasm-fedora-40-desktop b/dockerfile-kasm-fedora-40-desktop new file mode 100644 index 000000000..bcbbcaa7c --- /dev/null +++ b/dockerfile-kasm-fedora-40-desktop @@ -0,0 +1,53 @@ +ARG BASE_TAG="develop" +ARG BASE_IMAGE="core-fedora-40" +FROM kasmweb/$BASE_IMAGE:$BASE_TAG + +USER root + +ENV DISTRO=fedora40 +ENV HOME /home/kasm-default-profile +ENV STARTUPDIR /dockerstartup +WORKDIR $HOME + +### Envrionment config +ENV SKIP_CLEAN=true \ + KASM_RX_HOME=$STARTUPDIR/kasmrx \ + DONT_PROMPT_WSL_INSTALL="No_Prompt_please" \ + INST_DIR=$STARTUPDIR/install \ + INST_SCRIPTS="/oracle/install/tools/install_tools_deluxe.sh \ + /oracle/install/misc/install_tools.sh \ + /ubuntu/install/chromium/install_chromium.sh \ + /ubuntu/install/firefox/install_firefox.sh \ + /oracle/install/sublime_text/install_sublime_text.sh \ + /oracle/install/vs_code/install_vs_code.sh \ + /ubuntu/install/remmina/install_remmina.sh \ + /oracle/install/only_office/install_only_office.sh \ + /oracle/install/gimp/install_gimp.sh \ + /oracle/install/zoom/install_zoom.sh \ + /oracle/install/ansible/install_ansible.sh \ + /oracle/install/telegram/install_telegram.sh \ + /ubuntu/install/thunderbird/install_thunderbird.sh \ + /ubuntu/install/slack/install_slack.sh \ + /ubuntu/install/cleanup/cleanup.sh" + +# Copy install scripts +COPY ./src/ $INST_DIR + +# Run installations +RUN \ + for SCRIPT in $INST_SCRIPTS; do \ + bash ${INST_DIR}${SCRIPT} || exit 1; \ + done && \ + $STARTUPDIR/set_user_permission.sh $HOME && \ + rm -f /etc/X11/xinit/Xclients && \ + chown 1000:0 $HOME && \ + mkdir -p /home/kasm-user && \ + chown -R 1000:0 /home/kasm-user && \ + rm -Rf ${INST_DIR} + +# Userspace Runtime +ENV HOME /home/kasm-user +WORKDIR $HOME +USER 1000 + +CMD ["--tail-log"] diff --git a/dockerfile-kasm-fedora-41-desktop b/dockerfile-kasm-fedora-41-desktop new file mode 100644 index 000000000..7f97ee1ae --- /dev/null +++ b/dockerfile-kasm-fedora-41-desktop @@ -0,0 +1,53 @@ +ARG BASE_TAG="develop" +ARG BASE_IMAGE="core-fedora-41" +FROM kasmweb/$BASE_IMAGE:$BASE_TAG + +USER root + +ENV DISTRO=fedora41 +ENV HOME /home/kasm-default-profile +ENV STARTUPDIR /dockerstartup +WORKDIR $HOME + +### Envrionment config +ENV SKIP_CLEAN=true \ + KASM_RX_HOME=$STARTUPDIR/kasmrx \ + DONT_PROMPT_WSL_INSTALL="No_Prompt_please" \ + INST_DIR=$STARTUPDIR/install \ + INST_SCRIPTS="/oracle/install/tools/install_tools_deluxe.sh \ + /oracle/install/misc/install_tools.sh \ + /ubuntu/install/chromium/install_chromium.sh \ + /ubuntu/install/firefox/install_firefox.sh \ + /oracle/install/sublime_text/install_sublime_text.sh \ + /oracle/install/vs_code/install_vs_code.sh \ + /ubuntu/install/remmina/install_remmina.sh \ + /oracle/install/only_office/install_only_office.sh \ + /oracle/install/gimp/install_gimp.sh \ + /oracle/install/zoom/install_zoom.sh \ + /oracle/install/ansible/install_ansible.sh \ + /oracle/install/telegram/install_telegram.sh \ + /ubuntu/install/thunderbird/install_thunderbird.sh \ + /ubuntu/install/slack/install_slack.sh \ + /ubuntu/install/cleanup/cleanup.sh" + +# Copy install scripts +COPY ./src/ $INST_DIR + +# Run installations +RUN \ + for SCRIPT in $INST_SCRIPTS; do \ + bash ${INST_DIR}${SCRIPT} || exit 1; \ + done && \ + $STARTUPDIR/set_user_permission.sh $HOME && \ + rm -f /etc/X11/xinit/Xclients && \ + chown 1000:0 $HOME && \ + mkdir -p /home/kasm-user && \ + chown -R 1000:0 /home/kasm-user && \ + rm -Rf ${INST_DIR} + +# Userspace Runtime +ENV HOME /home/kasm-user +WORKDIR $HOME +USER 1000 + +CMD ["--tail-log"] diff --git a/dockerfile-kasm-filezilla b/dockerfile-kasm-filezilla index c6027d39a..746012e59 100644 --- a/dockerfile-kasm-filezilla +++ b/dockerfile-kasm-filezilla @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root @@ -21,7 +21,7 @@ RUN chmod 755 $STARTUPDIR/custom_startup.sh # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel ######### End Customizations ########### diff --git a/dockerfile-kasm-firefox b/dockerfile-kasm-firefox index 5b118d8a7..e250da9bb 100644 --- a/dockerfile-kasm-firefox +++ b/dockerfile-kasm-firefox @@ -1,11 +1,11 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root -ENV HOME /home/kasm-default-profile -ENV STARTUPDIR /dockerstartup -ENV INST_SCRIPTS $STARTUPDIR/install +ENV HOME=/home/kasm-default-profile +ENV STARTUPDIR=/dockerstartup +ENV INST_SCRIPTS=$STARTUPDIR/install WORKDIR $HOME ######### Customize Container Here ########### @@ -18,9 +18,13 @@ RUN bash $INST_SCRIPTS/firefox/install_firefox.sh && rm -rf $INST_SCRIPTS/firefo # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel +# Security modifications +COPY ./src/ubuntu/install/misc/single_app_security.sh $INST_SCRIPTS/misc/ +RUN bash $INST_SCRIPTS/misc/single_app_security.sh -t && rm -rf $INST_SCRIPTS/misc/ + # Setup the custom startup script that will be invoked when the container starts #ENV LAUNCH_URL http://kasmweb.com @@ -34,13 +38,15 @@ RUN chmod +x $STARTUPDIR/custom_startup.sh ENV KASM_RESTRICTED_FILE_CHOOSER=1 COPY ./src/ubuntu/install/gtk/ $INST_SCRIPTS/gtk/ RUN bash $INST_SCRIPTS/gtk/install_restricted_file_chooser.sh +COPY ./src/ubuntu/install/close_browser_breakout_via_file_manager/ $INST_SCRIPTS/close_browser_breakout_via_file_manager/ +RUN bash $INST_SCRIPTS/close_browser_breakout_via_file_manager/replace_thunar_with_empty_script.sh ######### End Customizations ########### RUN chown 1000:0 $HOME RUN $STARTUPDIR/set_user_permission.sh $HOME -ENV HOME /home/kasm-user +ENV HOME=/home/kasm-user WORKDIR $HOME RUN mkdir -p $HOME && chown -R 1000:0 $HOME diff --git a/dockerfile-kasm-forensic-osint b/dockerfile-kasm-forensic-osint new file mode 100644 index 000000000..5fc2cfa0c --- /dev/null +++ b/dockerfile-kasm-forensic-osint @@ -0,0 +1,50 @@ +ARG BASE_TAG="develop" +ARG BASE_IMAGE="core-ubuntu-jammy" +FROM kasmweb/$BASE_IMAGE:$BASE_TAG + +USER root + +ENV HOME /home/kasm-default-profile +ENV STARTUPDIR /dockerstartup +WORKDIR $HOME + +### Envrionment config +ENV DEBIAN_FRONTEND=noninteractive \ + SKIP_CLEAN=true \ + KASM_RX_HOME=$STARTUPDIR/kasmrx \ + DONT_PROMPT_WSL_INSTALL="No_Prompt_please" \ + INST_DIR=$STARTUPDIR/install \ + INST_SCRIPTS="/ubuntu/install/misc/install_tools.sh \ + /ubuntu/install/chrome/install_chrome.sh \ + /ubuntu/install/vs_code/install_vs_code.sh \ + /ubuntu/install/only_office/install_only_office.sh \ + /ubuntu/install/signal/install_signal.sh \ + /ubuntu/install/telegram/install_telegram.sh \ + /ubuntu/install/thunderbird/install_thunderbird.sh \ + /ubuntu/install/torbrowser/install_torbrowser.sh \ + /ubuntu/install/forensic_osint/install_forensic_osint.sh \ + /ubuntu/install/forensic_osint/install_forensic_osint_background.sh \ + /ubuntu/install/forensic_osint/install_forensic_osint_custom_startup.sh \ + /ubuntu/install/cleanup/cleanup.sh" + +# Copy install scripts +COPY ./src/ $INST_DIR + +# Run installations +RUN \ + for SCRIPT in $INST_SCRIPTS; do \ + bash ${INST_DIR}${SCRIPT} || exit 1; \ + done && \ + $STARTUPDIR/set_user_permission.sh $HOME && \ + rm -f /etc/X11/xinit/Xclients && \ + chown 1000:0 $HOME && \ + mkdir -p /home/kasm-user && \ + chown -R 1000:0 /home/kasm-user && \ + rm -Rf ${INST_DIR} + +# Userspace Runtime +ENV HOME /home/kasm-user +WORKDIR $HOME +USER 1000 + +CMD ["--tail-log"] diff --git a/dockerfile-kasm-gimp b/dockerfile-kasm-gimp index 0ae0c9816..44a6f206c 100644 --- a/dockerfile-kasm-gimp +++ b/dockerfile-kasm-gimp @@ -1,6 +1,7 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-debian-trixie" FROM kasmweb/$BASE_IMAGE:$BASE_TAG + USER root ENV HOME /home/kasm-default-profile @@ -10,7 +11,7 @@ WORKDIR $HOME ######### Customize Container Here ########### - +# Install Gimp COPY ./src/ubuntu/install/gimp $INST_SCRIPTS/gimp/ RUN bash $INST_SCRIPTS/gimp/install_gimp.sh && rm -rf $INST_SCRIPTS/gimp/ @@ -21,7 +22,7 @@ RUN chmod 755 $STARTUPDIR/custom_startup.sh # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel ######### End Customizations ########### diff --git a/dockerfile-kasm-hunchly b/dockerfile-kasm-hunchly index 48481640c..650f27fdb 100644 --- a/dockerfile-kasm-hunchly +++ b/dockerfile-kasm-hunchly @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root diff --git a/dockerfile-kasm-inkscape b/dockerfile-kasm-inkscape index a5570b6d6..0762b8263 100644 --- a/dockerfile-kasm-inkscape +++ b/dockerfile-kasm-inkscape @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root @@ -21,7 +21,7 @@ RUN chmod 755 $STARTUPDIR/custom_startup.sh # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel ######### End Customizations ########### diff --git a/dockerfile-kasm-insomnia b/dockerfile-kasm-insomnia index 744b85805..49f5e6d38 100644 --- a/dockerfile-kasm-insomnia +++ b/dockerfile-kasm-insomnia @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root @@ -21,7 +21,7 @@ RUN chmod 755 $STARTUPDIR/custom_startup.sh # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel ######### End Customizations ########### diff --git a/dockerfile-kasm-java-dev b/dockerfile-kasm-java-dev index 32d97e690..6db7b39f6 100644 --- a/dockerfile-kasm-java-dev +++ b/dockerfile-kasm-java-dev @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root diff --git a/dockerfile-kasm-kali-rolling-desktop b/dockerfile-kasm-kali-rolling-desktop index 6a06744d9..6bb315367 100644 --- a/dockerfile-kasm-kali-rolling-desktop +++ b/dockerfile-kasm-kali-rolling-desktop @@ -5,31 +5,37 @@ USER root ENV HOME /home/kasm-default-profile ENV STARTUPDIR /dockerstartup -ENV INST_SCRIPTS $STARTUPDIR/install WORKDIR $HOME -######### Customize Container Here ########### - - -# Install Kali utils -COPY ./src/ubuntu/install/kali $INST_SCRIPTS/kali/ -RUN bash $INST_SCRIPTS/kali/install_kali.sh && rm -rf $INST_SCRIPTS/kali/ - -# Install Chromium -COPY ./src/ubuntu/install/chromium $INST_SCRIPTS/chromium/ -RUN bash $INST_SCRIPTS/chromium/install_chromium.sh && rm -rf $INST_SCRIPTS/chromium/ - -# Install Firefox -COPY ./src/ubuntu/install/firefox/ $INST_SCRIPTS/firefox/ -RUN bash $INST_SCRIPTS/firefox/install_firefox.sh && rm -rf $INST_SCRIPTS/firefox/ - -######### End Customizations ########### - -RUN chown 1000:0 $HOME -RUN $STARTUPDIR/set_user_permission.sh $HOME - +### Envrionment config +ENV DEBIAN_FRONTEND=noninteractive \ + SKIP_CLEAN=true \ + KASM_RX_HOME=$STARTUPDIR/kasmrx \ + DONT_PROMPT_WSL_INSTALL="No_Prompt_please" \ + INST_DIR=$STARTUPDIR/install \ + INST_SCRIPTS="/ubuntu/install/kali/install_kali.sh \ + /ubuntu/install/chromium/install_chromium.sh \ + /ubuntu/install/firefox/install_firefox.sh \ + /ubuntu/install/cleanup/cleanup.sh" + +# Copy install scripts +COPY ./src/ $INST_DIR + +# Run installations +RUN \ + for SCRIPT in $INST_SCRIPTS; do \ + bash ${INST_DIR}${SCRIPT} || exit 1; \ + done && \ + $STARTUPDIR/set_user_permission.sh $HOME && \ + rm -f /etc/X11/xinit/Xclients && \ + chown 1000:0 $HOME && \ + mkdir -p /home/kasm-user && \ + chown -R 1000:0 /home/kasm-user && \ + rm -Rf ${INST_DIR} + +# Userspace Runtime ENV HOME /home/kasm-user WORKDIR $HOME -RUN mkdir -p $HOME && chown -R 1000:0 $HOME - USER 1000 + +CMD ["--tail-log"] diff --git a/dockerfile-kasm-libre-office b/dockerfile-kasm-libre-office index 87b7f67fa..3fa10f6d2 100644 --- a/dockerfile-kasm-libre-office +++ b/dockerfile-kasm-libre-office @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root @@ -21,7 +21,7 @@ RUN chmod 755 $STARTUPDIR/custom_startup.sh # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel ######### End Customizations ########### diff --git a/dockerfile-kasm-maltego b/dockerfile-kasm-maltego index a4dfc6d1d..59886a832 100644 --- a/dockerfile-kasm-maltego +++ b/dockerfile-kasm-maltego @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root @@ -10,10 +10,9 @@ WORKDIR $HOME ######### Customize Container Here ########### -# Maltego wants a browser installed and the default is Firefox, Install Firefox -COPY ./src/ubuntu/install/firefox/ $INST_SCRIPTS/firefox/ -COPY ./src/ubuntu/install/firefox/firefox.desktop $HOME/Desktop/ -RUN bash $INST_SCRIPTS/firefox/install_firefox.sh && rm -rf $INST_SCRIPTS/firefox/ +# Install Chrome as the default browser +COPY ./src/ubuntu/install/chrome $INST_SCRIPTS/chrome/ +RUN bash $INST_SCRIPTS/chrome/install_chrome.sh && rm -rf $INST_SCRIPTS/chrome/ COPY ./src/ubuntu/install/maltego $INST_SCRIPTS/maltego/ RUN bash $INST_SCRIPTS/maltego/install_maltego.sh && rm -rf $INST_SCRIPTS/maltego/ @@ -25,7 +24,7 @@ RUN chmod 755 $STARTUPDIR/custom_startup.sh # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel diff --git a/dockerfile-kasm-minetest b/dockerfile-kasm-minetest index 1af465c20..2b05509f1 100644 --- a/dockerfile-kasm-minetest +++ b/dockerfile-kasm-minetest @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root @@ -20,7 +20,7 @@ RUN chmod 755 $STARTUPDIR/custom_startup.sh # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel ######### End Customizations ########### diff --git a/dockerfile-kasm-nessus b/dockerfile-kasm-nessus new file mode 100644 index 000000000..8324ee9b2 --- /dev/null +++ b/dockerfile-kasm-nessus @@ -0,0 +1,38 @@ +ARG BASE_TAG="develop" +ARG BASE_IMAGE="core-ubuntu-jammy" +FROM kasmweb/$BASE_IMAGE:$BASE_TAG +USER root + +ENV HOME /home/kasm-default-profile +ENV STARTUPDIR /dockerstartup +ENV INST_SCRIPTS $STARTUPDIR/install +WORKDIR $HOME + +######### Customize Container Here ########### + + +# Install Chromium +COPY ./src/ubuntu/install/chromium $INST_SCRIPTS/chromium/ +RUN bash $INST_SCRIPTS/chromium/install_chromium.sh && rm -rf $INST_SCRIPTS/chromium/ + +COPY ./src/ubuntu/install/nessus $INST_SCRIPTS/nessus/ +RUN bash $INST_SCRIPTS/nessus/install_nessus.sh && rm -rf $INST_SCRIPTS/nessus/ + +COPY ./src/ubuntu/install/cleanup $INST_SCRIPTS/cleanup/ +RUN bash $INST_SCRIPTS/cleanup/cleanup.sh && rm -rf $INST_SCRIPTS/cleanup/ + +COPY ./src/ubuntu/install/nessus/custom_startup.sh $STARTUPDIR/custom_startup.sh +RUN chmod +x $STARTUPDIR/custom_startup.sh +RUN chmod 755 $STARTUPDIR/custom_startup.sh + +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png + +######### End Customizations ########### + +RUN chown 1000:0 $HOME + +ENV HOME /home/kasm-user +WORKDIR $HOME +RUN mkdir -p $HOME && chown -R 1000:0 $HOME + +USER 1000 diff --git a/dockerfile-kasm-obsidian b/dockerfile-kasm-obsidian new file mode 100644 index 000000000..209541bd3 --- /dev/null +++ b/dockerfile-kasm-obsidian @@ -0,0 +1,41 @@ +ARG BASE_TAG="develop" +ARG BASE_IMAGE="core-ubuntu-jammy" +FROM kasmweb/$BASE_IMAGE:$BASE_TAG +# FROM kasmweb/core-ubuntu-jammy:1.17.0-rolling-daily +USER root + +ENV HOME /home/kasm-default-profile +ENV STARTUPDIR /dockerstartup +ENV INST_SCRIPTS $STARTUPDIR/install +WORKDIR $HOME + +######### Customize Container Here ########### + +# Install Obsidian +COPY ./src/ubuntu/install/obsidian $INST_SCRIPTS/obsidian/ +RUN bash $INST_SCRIPTS/obsidian/install_obsidian.sh && rm -rf $INST_SCRIPTS/obsidian/ + +# Install Google Chrome +COPY ./src/ubuntu/install/chrome $INST_SCRIPTS/chrome/ +RUN bash $INST_SCRIPTS/chrome/install_chrome.sh && rm -rf $INST_SCRIPTS/chrome/ + +COPY ./src/ubuntu/install/obsidian/custom_startup.sh $STARTUPDIR/custom_startup.sh +RUN chmod +x $STARTUPDIR/custom_startup.sh +RUN chmod 755 $STARTUPDIR/custom_startup.sh + + +# Update the desktop environment to be optimized for a single application +RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png +RUN apt-get remove -y xfce4-panel + + +######### End Customizations ########### + +RUN chown 1000:0 $HOME + +ENV HOME /home/kasm-user +WORKDIR $HOME +RUN mkdir -p $HOME && chown -R 1000:0 $HOME + +USER 1000 diff --git a/dockerfile-kasm-only-office b/dockerfile-kasm-only-office index b7e655d40..7d2a5d9b7 100644 --- a/dockerfile-kasm-only-office +++ b/dockerfile-kasm-only-office @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root @@ -21,7 +21,7 @@ RUN chmod 755 $STARTUPDIR/custom_startup.sh # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel ######### End Customizations ########### diff --git a/dockerfile-kasm-opensuse-15-desktop b/dockerfile-kasm-opensuse-15-desktop index 5356aab64..cedbe5089 100644 --- a/dockerfile-kasm-opensuse-15-desktop +++ b/dockerfile-kasm-opensuse-15-desktop @@ -10,81 +10,48 @@ ENV STARTUPDIR /dockerstartup WORKDIR $HOME ### Envrionment config -ENV DEBIAN_FRONTEND noninteractive -ENV KASM_RX_HOME $STARTUPDIR/kasmrx -ENV INST_SCRIPTS $STARTUPDIR/install -ENV DONT_PROMPT_WSL_INSTALL "No_Prompt_please" - -### Install Tools -COPY ./src/opensuse/install/tools $INST_SCRIPTS/tools/ -RUN bash $INST_SCRIPTS/tools/install_tools_deluxe.sh && rm -rf $INST_SCRIPTS/tools/ - -# Install Utilities -COPY ./src/opensuse/install/misc $INST_SCRIPTS/misc/ -RUN bash $INST_SCRIPTS/misc/install_tools.sh && rm -rf $INST_SCRIPTS/misc/ - -# Install Chrome -COPY ./src/ubuntu/install/chrome $INST_SCRIPTS/chrome/ -RUN bash $INST_SCRIPTS/chrome/install_chrome.sh && rm -rf $INST_SCRIPTS/chrome/ - -# Install Chromium -COPY ./src/ubuntu/install/chromium $INST_SCRIPTS/chromium/ -RUN if [ "$(uname -m)" == "aarch64" ]; then bash $INST_SCRIPTS/chromium/install_chromium.sh; fi && rm -rf $INST_SCRIPTS/chromium/ - -# Install Firefox -COPY ./src/ubuntu/install/firefox/ $INST_SCRIPTS/firefox/ -COPY ./src/ubuntu/install/firefox/firefox.desktop $HOME/Desktop/ -RUN bash $INST_SCRIPTS/firefox/install_firefox.sh && rm -rf $INST_SCRIPTS/firefox/ - -### Install Sublime Text -COPY ./src/opensuse/install/sublime_text $INST_SCRIPTS/sublime_text/ -RUN bash $INST_SCRIPTS/sublime_text/install_sublime_text.sh && rm -rf $INST_SCRIPTS/sublime_text/ - -### Install Visual Studio Code -COPY ./src/opensuse/install/vs_code $INST_SCRIPTS/vs_code/ -RUN bash $INST_SCRIPTS/vs_code/install_vs_code.sh && rm -rf $INST_SCRIPTS/vs_code/ - -### Install NextCloud -COPY ./src/ubuntu/install/nextcloud $INST_SCRIPTS/nextcloud/ -RUN bash $INST_SCRIPTS/nextcloud/install_nextcloud.sh && rm -rf $INST_SCRIPTS/nextcloud/ - -### Install Remmina -COPY ./src/ubuntu/install/remmina $INST_SCRIPTS/remmina/ -RUN bash $INST_SCRIPTS/remmina/install_remmina.sh && rm -rf $INST_SCRIPTS/remmina/ - -### Install Libre Office -COPY ./src/opensuse/install/libre_office $INST_SCRIPTS/libre_office/ -RUN bash $INST_SCRIPTS/libre_office/install_libre_office.sh && rm -rf $INST_SCRIPTS/libre_office/ - -### Install GIMP -COPY ./src/opensuse/install/gimp $INST_SCRIPTS/gimp/ -RUN bash $INST_SCRIPTS/gimp/install_gimp.sh && rm -rf $INST_SCRIPTS/gimp/ - -### Install Ansible -COPY ./src/opensuse/install/ansible $INST_SCRIPTS/ansible/ -RUN bash $INST_SCRIPTS/ansible/install_ansible.sh && rm -rf $INST_SCRIPTS/ansible/ - -### Install Terraform -COPY ./src/opensuse/install/terraform $INST_SCRIPTS/terraform/ -RUN bash $INST_SCRIPTS/terraform/install_terraform.sh && rm -rf $INST_SCRIPTS/terraform/ - -### Install Telegram -COPY ./src/opensuse/install/telegram $INST_SCRIPTS/telegram/ -RUN bash $INST_SCRIPTS/telegram/install_telegram.sh && rm -rf $INST_SCRIPTS/telegram/ - -### Install Thunderbird -COPY ./src/ubuntu/install/thunderbird $INST_SCRIPTS/thunderbird/ -RUN bash $INST_SCRIPTS/thunderbird/install_thunderbird.sh && rm -rf $INST_SCRIPTS/thunderbird/ - -#ADD ./src/common/scripts $STARTUPDIR -RUN $STARTUPDIR/set_user_permission.sh $HOME - -RUN chown 1000:0 $HOME - +ENV SKIP_CLEAN=true \ + KASM_RX_HOME=$STARTUPDIR/kasmrx \ + DONT_PROMPT_WSL_INSTALL="No_Prompt_please" \ + INST_DIR=$STARTUPDIR/install \ + INST_SCRIPTS="/opensuse/install/tools/install_tools_deluxe.sh \ + /opensuse/install/misc/install_tools.sh \ + /ubuntu/install/chrome/install_chrome.sh \ + /ubuntu/install/chromium/install_chromium.sh \ + /ubuntu/install/firefox/install_firefox.sh \ + /opensuse/install/sublime_text/install_sublime_text.sh \ + /opensuse/install/vs_code/install_vs_code.sh \ + /ubuntu/install/nextcloud/install_nextcloud.sh \ + /ubuntu/install/remmina/install_remmina.sh \ + /opensuse/install/libre_office/install_libre_office.sh \ + /opensuse/install/gimp/install_gimp.sh \ + /opensuse/install/ansible/install_ansible.sh \ + /opensuse/install/terraform/install_terraform.sh \ + /opensuse/install/telegram/install_telegram.sh \ + /ubuntu/install/thunderbird/install_thunderbird.sh \ + /ubuntu/install/slack/install_slack.sh \ + /ubuntu/install/langpacks/install_langpacks.sh \ + /ubuntu/install/cleanup/cleanup.sh" + +# Copy install scripts +COPY ./src/ $INST_DIR + +# Run installations +RUN \ + for SCRIPT in $INST_SCRIPTS; do \ + bash ${INST_DIR}${SCRIPT} || exit 1; \ + done && \ + $STARTUPDIR/set_user_permission.sh $HOME && \ + rm -f /etc/X11/xinit/Xclients && \ + chown 1000:0 $HOME && \ + mkdir -p /home/kasm-user && \ + chown -R 1000:0 /home/kasm-user && \ + rm -Rf ${INST_DIR} + +# Userspace Runtime ENV HOME /home/kasm-user WORKDIR $HOME -RUN mkdir -p $HOME && chown -R 1000:0 $HOME - USER 1000 CMD ["--tail-log"] + diff --git a/dockerfile-kasm-oracle-7-desktop b/dockerfile-kasm-oracle-7-desktop deleted file mode 100644 index 0e92b1527..000000000 --- a/dockerfile-kasm-oracle-7-desktop +++ /dev/null @@ -1,88 +0,0 @@ -ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-oracle-7" -FROM kasmweb/$BASE_IMAGE:$BASE_TAG - -USER root - -ENV DISTRO=centos -ENV HOME /home/kasm-default-profile -ENV STARTUPDIR /dockerstartup -WORKDIR $HOME - -### Envrionment config -ENV DEBIAN_FRONTEND noninteractive -ENV KASM_RX_HOME $STARTUPDIR/kasmrx -ENV INST_SCRIPTS $STARTUPDIR/install -ENV DONT_PROMPT_WSL_INSTALL "No_Prompt_please" - -### Install Tools -COPY ./src/oracle/install/tools $INST_SCRIPTS/tools/ -RUN bash $INST_SCRIPTS/tools/install_tools_deluxe.sh && rm -rf $INST_SCRIPTS/tools/ - -# Install Utilities -COPY ./src/oracle/install/misc $INST_SCRIPTS/misc/ -RUN bash $INST_SCRIPTS/misc/install_tools.sh && rm -rf $INST_SCRIPTS/misc/ - -# Install Google Chrome -COPY ./src/ubuntu/install/chrome $INST_SCRIPTS/chrome/ -RUN bash $INST_SCRIPTS/chrome/install_chrome.sh && rm -rf $INST_SCRIPTS/chrome/ - -# Install Firefox -COPY ./src/ubuntu/install/firefox/ $INST_SCRIPTS/firefox/ -COPY ./src/ubuntu/install/firefox/firefox.desktop $HOME/Desktop/ -RUN bash $INST_SCRIPTS/firefox/install_firefox.sh && rm -rf $INST_SCRIPTS/firefox/ - -### Install Sublime Text -COPY ./src/oracle/install/sublime_text $INST_SCRIPTS/sublime_text/ -RUN bash $INST_SCRIPTS/sublime_text/install_sublime_text.sh && rm -rf $INST_SCRIPTS/sublime_text/ - -### Install Visual Studio Code -COPY ./src/oracle/install/vs_code $INST_SCRIPTS/vs_code/ -RUN bash $INST_SCRIPTS/vs_code/install_vs_code.sh && rm -rf $INST_SCRIPTS/vs_code/ - -### Install Remmina -COPY ./src/ubuntu/install/remmina $INST_SCRIPTS/remmina/ -RUN bash $INST_SCRIPTS/remmina/install_remmina.sh && rm -rf $INST_SCRIPTS/remmina/ - -### Install Only Office -COPY ./src/oracle/install/only_office $INST_SCRIPTS/only_office/ -RUN bash $INST_SCRIPTS/only_office/install_only_office.sh && rm -rf $INST_SCRIPTS/only_office/ - -### Install GIMP -COPY ./src/oracle/install/gimp $INST_SCRIPTS/gimp/ -RUN bash $INST_SCRIPTS/gimp/install_gimp.sh && rm -rf $INST_SCRIPTS/gimp/ - -### Install Zoom -COPY ./src/oracle/install/zoom $INST_SCRIPTS/zoom/ -RUN bash $INST_SCRIPTS/zoom/install_zoom.sh && rm -rf $INST_SCRIPTS/zoom/ - -### Install Ansible -COPY ./src/oracle/install/ansible $INST_SCRIPTS/ansible/ -RUN bash $INST_SCRIPTS/ansible/install_ansible.sh && rm -rf $INST_SCRIPTS/ansible/ - -### Install Terraform -COPY ./src/oracle/install/terraform $INST_SCRIPTS/terraform/ -RUN bash $INST_SCRIPTS/terraform/install_terraform.sh && rm -rf $INST_SCRIPTS/terraform/ - -### Install Telegram -COPY ./src/oracle/install/telegram $INST_SCRIPTS/telegram/ -RUN bash $INST_SCRIPTS/telegram/install_telegram.sh && rm -rf $INST_SCRIPTS/telegram/ - -### Install Thunderbird -COPY ./src/ubuntu/install/thunderbird $INST_SCRIPTS/thunderbird/ -RUN bash $INST_SCRIPTS/thunderbird/install_thunderbird.sh && rm -rf $INST_SCRIPTS/thunderbird/ - -#ADD ./src/common/scripts $STARTUPDIR -RUN $STARTUPDIR/set_user_permission.sh $HOME - -RUN rm -f /etc/X11/xinit/Xclients - -RUN chown 1000:0 $HOME - -ENV HOME /home/kasm-user -WORKDIR $HOME -RUN mkdir -p $HOME && chown -R 1000:0 $HOME - -USER 1000 - -CMD ["--tail-log"] diff --git a/dockerfile-kasm-oracle-8-desktop b/dockerfile-kasm-oracle-8-desktop index 5214e9137..fcf426493 100644 --- a/dockerfile-kasm-oracle-8-desktop +++ b/dockerfile-kasm-oracle-8-desktop @@ -10,87 +10,47 @@ ENV STARTUPDIR /dockerstartup WORKDIR $HOME ### Envrionment config -ENV DEBIAN_FRONTEND noninteractive -ENV KASM_RX_HOME $STARTUPDIR/kasmrx -ENV INST_SCRIPTS $STARTUPDIR/install -ENV DONT_PROMPT_WSL_INSTALL "No_Prompt_please" - -### Install Tools -COPY ./src/oracle/install/tools $INST_SCRIPTS/tools/ -RUN bash $INST_SCRIPTS/tools/install_tools_deluxe.sh && rm -rf $INST_SCRIPTS/tools/ - -# Install Utilities -COPY ./src/oracle/install/misc $INST_SCRIPTS/misc/ -RUN bash $INST_SCRIPTS/misc/install_tools.sh && rm -rf $INST_SCRIPTS/misc/ - -# Install Chromium -COPY ./src/ubuntu/install/chromium $INST_SCRIPTS/chromium/ -RUN bash $INST_SCRIPTS/chromium/install_chromium.sh && rm -rf $INST_SCRIPTS/chromium/ - -# Install Firefox -COPY ./src/ubuntu/install/firefox/ $INST_SCRIPTS/firefox/ -COPY ./src/ubuntu/install/firefox/firefox.desktop $HOME/Desktop/ -RUN bash $INST_SCRIPTS/firefox/install_firefox.sh && rm -rf $INST_SCRIPTS/firefox/ - -### Install Sublime Text -COPY ./src/oracle/install/sublime_text $INST_SCRIPTS/sublime_text/ -RUN bash $INST_SCRIPTS/sublime_text/install_sublime_text.sh && rm -rf $INST_SCRIPTS/sublime_text/ - -### Install Visual Studio Code -COPY ./src/oracle/install/vs_code $INST_SCRIPTS/vs_code/ -RUN bash $INST_SCRIPTS/vs_code/install_vs_code.sh && rm -rf $INST_SCRIPTS/vs_code/ - -### Install NextCloud -COPY ./src/ubuntu/install/nextcloud $INST_SCRIPTS/nextcloud/ -RUN bash $INST_SCRIPTS/nextcloud/install_nextcloud.sh && rm -rf $INST_SCRIPTS/nextcloud/ - -### Install Remmina -COPY ./src/ubuntu/install/remmina $INST_SCRIPTS/remmina/ -RUN bash $INST_SCRIPTS/remmina/install_remmina.sh && rm -rf $INST_SCRIPTS/remmina/ - -### Install Only Office -COPY ./src/oracle/install/only_office $INST_SCRIPTS/only_office/ -RUN bash $INST_SCRIPTS/only_office/install_only_office.sh && rm -rf $INST_SCRIPTS/only_office/ - -### Install GIMP -COPY ./src/oracle/install/gimp $INST_SCRIPTS/gimp/ -RUN bash $INST_SCRIPTS/gimp/install_gimp.sh && rm -rf $INST_SCRIPTS/gimp/ - -### Install Zoom -COPY ./src/oracle/install/zoom $INST_SCRIPTS/zoom/ -RUN bash $INST_SCRIPTS/zoom/install_zoom.sh && rm -rf $INST_SCRIPTS/zoom/ - -### Install OBS Studio -COPY ./src/oracle/install/obs $INST_SCRIPTS/obs/ -RUN bash $INST_SCRIPTS/obs/install_obs.sh && rm -rf $INST_SCRIPTS/obs/ - -### Install Ansible -COPY ./src/oracle/install/ansible $INST_SCRIPTS/ansible/ -RUN bash $INST_SCRIPTS/ansible/install_ansible.sh && rm -rf $INST_SCRIPTS/ansible/ - -### Install Terraform -COPY ./src/oracle/install/terraform $INST_SCRIPTS/terraform/ -RUN bash $INST_SCRIPTS/terraform/install_terraform.sh && rm -rf $INST_SCRIPTS/terraform/ - -### Install Telegram -COPY ./src/oracle/install/telegram $INST_SCRIPTS/telegram/ -RUN bash $INST_SCRIPTS/telegram/install_telegram.sh && rm -rf $INST_SCRIPTS/telegram/ - -### Install Thunderbird -COPY ./src/ubuntu/install/thunderbird $INST_SCRIPTS/thunderbird/ -RUN bash $INST_SCRIPTS/thunderbird/install_thunderbird.sh && rm -rf $INST_SCRIPTS/thunderbird/ - -#ADD ./src/common/scripts $STARTUPDIR -RUN $STARTUPDIR/set_user_permission.sh $HOME - -RUN rm -f /etc/X11/xinit/Xclients - -RUN chown 1000:0 $HOME - +ENV SKIP_CLEAN=true \ + KASM_RX_HOME=$STARTUPDIR/kasmrx \ + DONT_PROMPT_WSL_INSTALL="No_Prompt_please" \ + INST_DIR=$STARTUPDIR/install \ + INST_SCRIPTS="/oracle/install/tools/install_tools_deluxe.sh \ + /oracle/install/misc/install_tools.sh \ + /ubuntu/install/chromium/install_chromium.sh \ + /ubuntu/install/firefox/install_firefox.sh \ + /oracle/install/sublime_text/install_sublime_text.sh \ + /oracle/install/vs_code/install_vs_code.sh \ + /ubuntu/install/nextcloud/install_nextcloud.sh \ + /ubuntu/install/remmina/install_remmina.sh \ + /oracle/install/only_office/install_only_office.sh \ + /oracle/install/gimp/install_gimp.sh \ + /oracle/install/zoom/install_zoom.sh \ + /oracle/install/obs/install_obs.sh \ + /oracle/install/ansible/install_ansible.sh \ + /oracle/install/terraform/install_terraform.sh \ + /oracle/install/telegram/install_telegram.sh \ + /ubuntu/install/thunderbird/install_thunderbird.sh \ + /ubuntu/install/slack/install_slack.sh \ + /ubuntu/install/cleanup/cleanup.sh" + +# Copy install scripts +COPY ./src/ $INST_DIR + +# Run installations +RUN \ + for SCRIPT in $INST_SCRIPTS; do \ + bash ${INST_DIR}${SCRIPT} || exit 1; \ + done && \ + $STARTUPDIR/set_user_permission.sh $HOME && \ + rm -f /etc/X11/xinit/Xclients && \ + chown 1000:0 $HOME && \ + mkdir -p /home/kasm-user && \ + chown -R 1000:0 /home/kasm-user && \ + rm -Rf ${INST_DIR} + +# Userspace Runtime ENV HOME /home/kasm-user WORKDIR $HOME -RUN mkdir -p $HOME && chown -R 1000:0 $HOME - USER 1000 CMD ["--tail-log"] diff --git a/dockerfile-kasm-oracle-9-desktop b/dockerfile-kasm-oracle-9-desktop index 66bc9b2f8..0e5b2d2c0 100644 --- a/dockerfile-kasm-oracle-9-desktop +++ b/dockerfile-kasm-oracle-9-desktop @@ -10,79 +10,46 @@ ENV STARTUPDIR /dockerstartup WORKDIR $HOME ### Envrionment config -ENV DEBIAN_FRONTEND noninteractive -ENV KASM_RX_HOME $STARTUPDIR/kasmrx -ENV INST_SCRIPTS $STARTUPDIR/install -ENV DONT_PROMPT_WSL_INSTALL "No_Prompt_please" - -### Install Tools -COPY ./src/oracle/install/tools $INST_SCRIPTS/tools/ -RUN bash $INST_SCRIPTS/tools/install_tools_deluxe.sh && rm -rf $INST_SCRIPTS/tools/ - -# Install Utilities -COPY ./src/oracle/install/misc $INST_SCRIPTS/misc/ -RUN bash $INST_SCRIPTS/misc/install_tools.sh && rm -rf $INST_SCRIPTS/misc/ - -# Install Chromium -COPY ./src/ubuntu/install/chromium $INST_SCRIPTS/chromium/ -RUN bash $INST_SCRIPTS/chromium/install_chromium.sh && rm -rf $INST_SCRIPTS/chromium/ - -# Install Firefox -COPY ./src/ubuntu/install/firefox/ $INST_SCRIPTS/firefox/ -COPY ./src/ubuntu/install/firefox/firefox.desktop $HOME/Desktop/ -RUN bash $INST_SCRIPTS/firefox/install_firefox.sh && rm -rf $INST_SCRIPTS/firefox/ - -### Install Sublime Text -COPY ./src/oracle/install/sublime_text $INST_SCRIPTS/sublime_text/ -RUN bash $INST_SCRIPTS/sublime_text/install_sublime_text.sh && rm -rf $INST_SCRIPTS/sublime_text/ - -### Install Visual Studio Code -COPY ./src/oracle/install/vs_code $INST_SCRIPTS/vs_code/ -RUN bash $INST_SCRIPTS/vs_code/install_vs_code.sh && rm -rf $INST_SCRIPTS/vs_code/ - -### Install Remmina -COPY ./src/ubuntu/install/remmina $INST_SCRIPTS/remmina/ -RUN bash $INST_SCRIPTS/remmina/install_remmina.sh && rm -rf $INST_SCRIPTS/remmina/ - -### Install Only Office -COPY ./src/oracle/install/only_office $INST_SCRIPTS/only_office/ -RUN bash $INST_SCRIPTS/only_office/install_only_office.sh && rm -rf $INST_SCRIPTS/only_office/ - -### Install GIMP -COPY ./src/oracle/install/gimp $INST_SCRIPTS/gimp/ -RUN bash $INST_SCRIPTS/gimp/install_gimp.sh && rm -rf $INST_SCRIPTS/gimp/ - -### Install Zoom -COPY ./src/oracle/install/zoom $INST_SCRIPTS/zoom/ -RUN bash $INST_SCRIPTS/zoom/install_zoom.sh && rm -rf $INST_SCRIPTS/zoom/ - -### Install Ansible -COPY ./src/oracle/install/ansible $INST_SCRIPTS/ansible/ -RUN bash $INST_SCRIPTS/ansible/install_ansible.sh && rm -rf $INST_SCRIPTS/ansible/ - -### Install Terraform -COPY ./src/oracle/install/terraform $INST_SCRIPTS/terraform/ -RUN bash $INST_SCRIPTS/terraform/install_terraform.sh && rm -rf $INST_SCRIPTS/terraform/ - -### Install Telegram -COPY ./src/oracle/install/telegram $INST_SCRIPTS/telegram/ -RUN bash $INST_SCRIPTS/telegram/install_telegram.sh && rm -rf $INST_SCRIPTS/telegram/ - -### Install Thunderbird -COPY ./src/ubuntu/install/thunderbird $INST_SCRIPTS/thunderbird/ -RUN bash $INST_SCRIPTS/thunderbird/install_thunderbird.sh && rm -rf $INST_SCRIPTS/thunderbird/ - -#ADD ./src/common/scripts $STARTUPDIR -RUN $STARTUPDIR/set_user_permission.sh $HOME - -RUN rm -f /etc/X11/xinit/Xclients - -RUN chown 1000:0 $HOME - +ENV SKIP_CLEAN=true \ + KASM_RX_HOME=$STARTUPDIR/kasmrx \ + DONT_PROMPT_WSL_INSTALL="No_Prompt_please" \ + INST_DIR=$STARTUPDIR/install \ + INST_SCRIPTS="/oracle/install/tools/install_tools_deluxe.sh \ + /oracle/install/misc/install_tools.sh \ + /ubuntu/install/chromium/install_chromium.sh \ + /ubuntu/install/firefox/install_firefox.sh \ + /oracle/install/sublime_text/install_sublime_text.sh \ + /oracle/install/vs_code/install_vs_code.sh \ + /ubuntu/install/remmina/install_remmina.sh \ + /oracle/install/only_office/install_only_office.sh \ + /oracle/install/gimp/install_gimp.sh \ + /oracle/install/zoom/install_zoom.sh \ + /oracle/install/obs/install_obs.sh \ + /oracle/install/ansible/install_ansible.sh \ + /oracle/install/terraform/install_terraform.sh \ + /oracle/install/telegram/install_telegram.sh \ + /ubuntu/install/thunderbird/install_thunderbird.sh \ + /ubuntu/install/slack/install_slack.sh \ + /ubuntu/install/cleanup/cleanup.sh" + +# Copy install scripts +COPY ./src/ $INST_DIR + +# Run installations +RUN \ + for SCRIPT in $INST_SCRIPTS; do \ + bash ${INST_DIR}${SCRIPT} || exit 1; \ + done && \ + $STARTUPDIR/set_user_permission.sh $HOME && \ + rm -f /etc/X11/xinit/Xclients && \ + chown 1000:0 $HOME && \ + mkdir -p /home/kasm-user && \ + chown -R 1000:0 /home/kasm-user && \ + rm -Rf ${INST_DIR} + +# Userspace Runtime ENV HOME /home/kasm-user WORKDIR $HOME -RUN mkdir -p $HOME && chown -R 1000:0 $HOME - USER 1000 CMD ["--tail-log"] diff --git a/dockerfile-kasm-parrotos-5-desktop b/dockerfile-kasm-parrotos-5-desktop deleted file mode 100644 index 6509ec9fd..000000000 --- a/dockerfile-kasm-parrotos-5-desktop +++ /dev/null @@ -1,35 +0,0 @@ -ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-parrotos-5" -FROM kasmweb/$BASE_IMAGE:$BASE_TAG -USER root - -ENV HOME /home/kasm-default-profile -ENV STARTUPDIR /dockerstartup -ENV INST_SCRIPTS $STARTUPDIR/install -ENV DONT_PROMPT_WSL_INSTALL "No_Prompt_please" -WORKDIR $HOME - -######### Customize Container Here ########### - -# Install Parrot utils -COPY ./src/ubuntu/install/parrot $INST_SCRIPTS/parrot/ -RUN bash $INST_SCRIPTS/parrot/install_parrot.sh && rm -rf $INST_SCRIPTS/parrot/ - -# Install Chromium -COPY ./src/ubuntu/install/chromium $INST_SCRIPTS/chromium/ -RUN bash $INST_SCRIPTS/chromium/install_chromium.sh && rm -rf $INST_SCRIPTS/chromium/ - -# Install Firefox -COPY ./src/ubuntu/install/firefox/ $INST_SCRIPTS/firefox/ -RUN bash $INST_SCRIPTS/firefox/install_firefox.sh && rm -rf $INST_SCRIPTS/firefox/ - -######### End Customizations ########### - -RUN chown 1000:0 $HOME -RUN $STARTUPDIR/set_user_permission.sh $HOME - -ENV HOME /home/kasm-user -WORKDIR $HOME -RUN mkdir -p $HOME && chown -R 1000:0 $HOME - -USER 1000 diff --git a/dockerfile-kasm-parrotos-6-desktop b/dockerfile-kasm-parrotos-6-desktop new file mode 100644 index 000000000..729ca0573 --- /dev/null +++ b/dockerfile-kasm-parrotos-6-desktop @@ -0,0 +1,42 @@ +ARG BASE_TAG="develop" +ARG BASE_IMAGE="core-parrotos-6" +FROM kasmweb/$BASE_IMAGE:$BASE_TAG +USER root + +ENV HOME /home/kasm-default-profile +ENV STARTUPDIR /dockerstartup +WORKDIR $HOME + +### Envrionment config +ENV DEBIAN_FRONTEND=noninteractive \ + SKIP_CLEAN=true \ + KASM_RX_HOME=$STARTUPDIR/kasmrx \ + DONT_PROMPT_WSL_INSTALL="No_Prompt_please" \ + INST_DIR=$STARTUPDIR/install \ + INST_SCRIPTS="/ubuntu/install/parrot/install_parrot.sh \ + /ubuntu/install/chromium/install_chromium.sh \ + /ubuntu/install/vs_code/install_vs_code.sh \ + /ubuntu/install/firefox/install_firefox.sh \ + /ubuntu/install/cleanup/cleanup.sh" + +# Copy install scripts +COPY ./src/ $INST_DIR + +# Run installations +RUN \ + for SCRIPT in $INST_SCRIPTS; do \ + bash ${INST_DIR}${SCRIPT} || exit 1; \ + done && \ + $STARTUPDIR/set_user_permission.sh $HOME && \ + rm -f /etc/X11/xinit/Xclients && \ + chown 1000:0 $HOME && \ + mkdir -p /home/kasm-user && \ + chown -R 1000:0 /home/kasm-user && \ + rm -Rf ${INST_DIR} + +# Userspace Runtime +ENV HOME /home/kasm-user +WORKDIR $HOME +USER 1000 + +CMD ["--tail-log"] diff --git a/dockerfile-kasm-pinta b/dockerfile-kasm-pinta index 6b4125123..467760065 100644 --- a/dockerfile-kasm-pinta +++ b/dockerfile-kasm-pinta @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-noble" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root @@ -21,7 +21,7 @@ RUN chmod 755 $STARTUPDIR/custom_startup.sh # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel ######### End Customizations ########### diff --git a/dockerfile-kasm-postman b/dockerfile-kasm-postman index 0ba9a288f..6abb02b5f 100644 --- a/dockerfile-kasm-postman +++ b/dockerfile-kasm-postman @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root @@ -25,7 +25,7 @@ RUN chmod 755 $STARTUPDIR/custom_startup.sh # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel diff --git a/dockerfile-kasm-qbittorrent b/dockerfile-kasm-qbittorrent index 4dfd5cdce..fa4f4f955 100644 --- a/dockerfile-kasm-qbittorrent +++ b/dockerfile-kasm-qbittorrent @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root @@ -21,7 +21,7 @@ RUN chmod 755 $STARTUPDIR/custom_startup.sh # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel ######### End Customizations ########### diff --git a/dockerfile-kasm-realvnc-vncviewer b/dockerfile-kasm-realvnc-vncviewer index 6a3200978..a6b8ab7bf 100644 --- a/dockerfile-kasm-realvnc-vncviewer +++ b/dockerfile-kasm-realvnc-vncviewer @@ -21,7 +21,7 @@ RUN chmod 755 $STARTUPDIR/custom_startup.sh # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel ######### End Customizations ########### diff --git a/dockerfile-kasm-redroid b/dockerfile-kasm-redroid new file mode 100644 index 000000000..69ff1eb80 --- /dev/null +++ b/dockerfile-kasm-redroid @@ -0,0 +1,55 @@ +ARG BASE_TAG="develop" +ARG BASE_IMAGE="core-ubuntu-jammy" +FROM kasmweb/$BASE_IMAGE:$BASE_TAG +USER root + +ENV HOME /home/kasm-default-profile +ENV STARTUPDIR /dockerstartup +WORKDIR $HOME + +### Envrionment config +ENV DEBUG=false \ + DEBIAN_FRONTEND=noninteractive \ + SKIP_CLEAN=true \ + KASM_RX_HOME=$STARTUPDIR/kasmrx \ + DONT_PROMPT_WSL_INSTALL="No_Prompt_please" \ + INST_DIR=$STARTUPDIR/install \ + INST_SCRIPTS="/ubuntu/install/dind/install_dind.sh \ + /ubuntu/install/tools/install_tools_deluxe.sh \ + /ubuntu/install/misc/install_tools.sh \ + /ubuntu/install/chrome/install_chrome.sh \ + /ubuntu/install/chromium/install_chromium.sh \ + /ubuntu/install/sublime_text/install_sublime_text.sh \ + /ubuntu/install/vs_code/install_vs_code.sh \ + /ubuntu/install/redroid/install_redroid.sh \ + /ubuntu/install/android_studio/install_android_studio.sh \ + /ubuntu/install/cleanup/cleanup.sh" + +# Startup Scripts +COPY ./src/ubuntu/install/redroid/custom_startup.sh $STARTUPDIR/custom_startup.sh +RUN chmod 755 $STARTUPDIR/custom_startup.sh +COPY ./src/ubuntu/install/dind/dockerd.conf /etc/supervisor/conf.d/ +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png + +# Copy install scripts +COPY ./src/ $INST_DIR + +# Run installations +RUN \ + for SCRIPT in $INST_SCRIPTS; do \ + bash ${INST_DIR}${SCRIPT} || exit 1; \ + done && \ + $STARTUPDIR/set_user_permission.sh $HOME && \ + rm -f /etc/X11/xinit/Xclients && \ + chown 1000:0 $HOME && \ + mkdir -p /home/kasm-user && \ + chown -R 1000:0 /home/kasm-user && \ + rm -Rf ${INST_DIR} + + +# Userspace Runtime +ENV HOME /home/kasm-user +WORKDIR $HOME +USER 1000 + +CMD ["--tail-log"] diff --git a/dockerfile-kasm-remmina b/dockerfile-kasm-remmina index a36dc243e..69d5f2a76 100644 --- a/dockerfile-kasm-remmina +++ b/dockerfile-kasm-remmina @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root @@ -25,7 +25,7 @@ RUN chown -R 1000:1000 $HOME/.config/remmina/ # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel ######### End Customizations ########### diff --git a/dockerfile-kasm-remnux-focal-desktop b/dockerfile-kasm-remnux-focal-desktop deleted file mode 100644 index bf2f4f95b..000000000 --- a/dockerfile-kasm-remnux-focal-desktop +++ /dev/null @@ -1,30 +0,0 @@ -ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-remnux-focal" -FROM kasmweb/$BASE_IMAGE:$BASE_TAG -USER root - -ENV HOME /home/kasm-default-profile -ENV STARTUPDIR /dockerstartup -ENV INST_SCRIPTS $STARTUPDIR/install -WORKDIR $HOME - -######### Customize Container Here ########### - -# Install Remnux Utils -COPY ./src/ubuntu/install/remnux $INST_SCRIPTS/remnux/ -RUN bash $INST_SCRIPTS/remnux/install_remnux.sh && rm -rf $INST_SCRIPTS/remnux/ - -# Install Firefox -COPY ./src/ubuntu/install/firefox/ $INST_SCRIPTS/firefox/ -RUN bash $INST_SCRIPTS/firefox/install_firefox.sh && rm -rf $INST_SCRIPTS/firefox/ - -######### End Customizations ########### - -RUN chown 1000:0 $HOME -RUN $STARTUPDIR/set_user_permission.sh $HOME - -ENV HOME /home/kasm-user -WORKDIR $HOME -RUN mkdir -p $HOME && chown -R 1000:0 $HOME - -USER 1000 diff --git a/dockerfile-kasm-retroarch b/dockerfile-kasm-retroarch index 7659eadd3..5666f690c 100644 --- a/dockerfile-kasm-retroarch +++ b/dockerfile-kasm-retroarch @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root @@ -20,7 +20,7 @@ RUN chmod 755 $STARTUPDIR/custom_startup.sh # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel ######### End Customizations ########### diff --git a/dockerfile-kasm-rhel-9-desktop b/dockerfile-kasm-rhel-9-desktop new file mode 100644 index 000000000..9c9a12327 --- /dev/null +++ b/dockerfile-kasm-rhel-9-desktop @@ -0,0 +1,55 @@ +ARG BASE_TAG="develop" +ARG BASE_IMAGE="core-rhel-9" +FROM kasmweb/$BASE_IMAGE:$BASE_TAG + +USER root + +ENV DISTRO=oracle9 +ENV HOME /home/kasm-default-profile +ENV STARTUPDIR /dockerstartup +WORKDIR $HOME + +### Envrionment config +ENV SKIP_CLEAN=true \ + KASM_RX_HOME=$STARTUPDIR/kasmrx \ + DONT_PROMPT_WSL_INSTALL="No_Prompt_please" \ + INST_DIR=$STARTUPDIR/install \ + INST_SCRIPTS="/oracle/install/tools/install_tools_deluxe.sh \ + /oracle/install/misc/install_tools.sh \ + /ubuntu/install/chromium/install_chromium.sh \ + /ubuntu/install/firefox/install_firefox.sh \ + /oracle/install/sublime_text/install_sublime_text.sh \ + /oracle/install/vs_code/install_vs_code.sh \ + /ubuntu/install/remmina/install_remmina.sh \ + /oracle/install/only_office/install_only_office.sh \ + /oracle/install/gimp/install_gimp.sh \ + /oracle/install/zoom/install_zoom.sh \ + /oracle/install/obs/install_obs.sh \ + /oracle/install/ansible/install_ansible.sh \ + /oracle/install/terraform/install_terraform.sh \ + /oracle/install/telegram/install_telegram.sh \ + /ubuntu/install/thunderbird/install_thunderbird.sh \ + /ubuntu/install/slack/install_slack.sh \ + /ubuntu/install/cleanup/cleanup.sh" + +# Copy install scripts +COPY ./src/ $INST_DIR + +# Run installations +RUN \ + for SCRIPT in $INST_SCRIPTS; do \ + bash ${INST_DIR}${SCRIPT} || exit 1; \ + done && \ + $STARTUPDIR/set_user_permission.sh $HOME && \ + rm -f /etc/X11/xinit/Xclients && \ + chown 1000:0 $HOME && \ + mkdir -p /home/kasm-user && \ + chown -R 1000:0 /home/kasm-user && \ + rm -Rf ${INST_DIR} + +# Userspace Runtime +ENV HOME /home/kasm-user +WORKDIR $HOME +USER 1000 + +CMD ["--tail-log"] diff --git a/dockerfile-kasm-rockylinux-8-desktop b/dockerfile-kasm-rockylinux-8-desktop index 6e28d4049..c2481c208 100644 --- a/dockerfile-kasm-rockylinux-8-desktop +++ b/dockerfile-kasm-rockylinux-8-desktop @@ -10,83 +10,47 @@ ENV STARTUPDIR /dockerstartup WORKDIR $HOME ### Envrionment config -ENV DEBIAN_FRONTEND noninteractive -ENV KASM_RX_HOME $STARTUPDIR/kasmrx -ENV INST_SCRIPTS $STARTUPDIR/install - - -### Install Tools -COPY ./src/oracle/install/tools $INST_SCRIPTS/tools/ -RUN bash $INST_SCRIPTS/tools/install_tools_deluxe.sh && rm -rf $INST_SCRIPTS/tools/ - -# Install Utilities -COPY ./src/oracle/install/misc $INST_SCRIPTS/misc/ -RUN bash $INST_SCRIPTS/misc/install_tools.sh && rm -rf $INST_SCRIPTS/misc/ - -# Install Chromium -COPY ./src/ubuntu/install/chromium $INST_SCRIPTS/chromium/ -RUN bash $INST_SCRIPTS/chromium/install_chromium.sh && rm -rf $INST_SCRIPTS/chromium/ - -# Install Firefox -COPY ./src/ubuntu/install/firefox/ $INST_SCRIPTS/firefox/ -COPY ./src/ubuntu/install/firefox/firefox.desktop $HOME/Desktop/ -RUN bash $INST_SCRIPTS/firefox/install_firefox.sh && rm -rf $INST_SCRIPTS/firefox/ - -### Install Sublime Text -COPY ./src/oracle/install/sublime_text $INST_SCRIPTS/sublime_text/ -RUN bash $INST_SCRIPTS/sublime_text/install_sublime_text.sh && rm -rf $INST_SCRIPTS/sublime_text/ - -### Install NextCloud -COPY ./src/ubuntu/install/nextcloud $INST_SCRIPTS/nextcloud/ -RUN bash $INST_SCRIPTS/nextcloud/install_nextcloud.sh && rm -rf $INST_SCRIPTS/nextcloud/ - -### Install Remmina -COPY ./src/ubuntu/install/remmina $INST_SCRIPTS/remmina/ -RUN bash $INST_SCRIPTS/remmina/install_remmina.sh && rm -rf $INST_SCRIPTS/remmina/ - -### Install Only Office -COPY ./src/oracle/install/only_office $INST_SCRIPTS/only_office/ -RUN bash $INST_SCRIPTS/only_office/install_only_office.sh && rm -rf $INST_SCRIPTS/only_office/ - -### Install GIMP -COPY ./src/oracle/install/gimp $INST_SCRIPTS/gimp/ -RUN bash $INST_SCRIPTS/gimp/install_gimp.sh && rm -rf $INST_SCRIPTS/gimp/ - -### Install Zoom -COPY ./src/oracle/install/zoom $INST_SCRIPTS/zoom/ -RUN bash $INST_SCRIPTS/zoom/install_zoom.sh && rm -rf $INST_SCRIPTS/zoom/ - -### Install OBS Studio -COPY ./src/oracle/install/obs $INST_SCRIPTS/obs/ -RUN bash $INST_SCRIPTS/obs/install_obs.sh && rm -rf $INST_SCRIPTS/obs/ - -### Install Ansible -COPY ./src/oracle/install/ansible $INST_SCRIPTS/ansible/ -RUN bash $INST_SCRIPTS/ansible/install_ansible.sh && rm -rf $INST_SCRIPTS/ansible/ - -### Install Terraform -COPY ./src/oracle/install/terraform $INST_SCRIPTS/terraform/ -RUN bash $INST_SCRIPTS/terraform/install_terraform.sh && rm -rf $INST_SCRIPTS/terraform/ - -### Install Telegram -COPY ./src/oracle/install/telegram $INST_SCRIPTS/telegram/ -RUN bash $INST_SCRIPTS/telegram/install_telegram.sh && rm -rf $INST_SCRIPTS/telegram/ - -### Install Thunderbird -COPY ./src/ubuntu/install/thunderbird $INST_SCRIPTS/thunderbird/ -RUN bash $INST_SCRIPTS/thunderbird/install_thunderbird.sh && rm -rf $INST_SCRIPTS/thunderbird/ - -#ADD ./src/common/scripts $STARTUPDIR -RUN $STARTUPDIR/set_user_permission.sh $HOME - -RUN rm -f /etc/X11/xinit/Xclients - -RUN chown 1000:0 $HOME - +ENV SKIP_CLEAN=true \ + KASM_RX_HOME=$STARTUPDIR/kasmrx \ + DONT_PROMPT_WSL_INSTALL="No_Prompt_please" \ + INST_DIR=$STARTUPDIR/install \ + INST_SCRIPTS="/oracle/install/tools/install_tools_deluxe.sh \ + /oracle/install/misc/install_tools.sh \ + /ubuntu/install/chromium/install_chromium.sh \ + /ubuntu/install/firefox/install_firefox.sh \ + /oracle/install/sublime_text/install_sublime_text.sh \ + /ubuntu/install/nextcloud/install_nextcloud.sh \ + /oracle/install/vs_code/install_vs_code.sh \ + /ubuntu/install/remmina/install_remmina.sh \ + /oracle/install/only_office/install_only_office.sh \ + /oracle/install/gimp/install_gimp.sh \ + /oracle/install/zoom/install_zoom.sh \ + /oracle/install/ansible/install_ansible.sh \ + /oracle/install/terraform/install_terraform.sh \ + /oracle/install/telegram/install_telegram.sh \ + /oracle/install/obs/install_obs.sh \ + /ubuntu/install/thunderbird/install_thunderbird.sh \ + /ubuntu/install/slack/install_slack.sh \ + /ubuntu/install/cleanup/cleanup.sh" + +# Copy install scripts +COPY ./src/ $INST_DIR + +# Run installations +RUN \ + for SCRIPT in $INST_SCRIPTS; do \ + bash ${INST_DIR}${SCRIPT} || exit 1; \ + done && \ + $STARTUPDIR/set_user_permission.sh $HOME && \ + rm -f /etc/X11/xinit/Xclients && \ + chown 1000:0 $HOME && \ + mkdir -p /home/kasm-user && \ + chown -R 1000:0 /home/kasm-user && \ + rm -Rf ${INST_DIR} + +# Userspace Runtime ENV HOME /home/kasm-user WORKDIR $HOME -RUN mkdir -p $HOME && chown -R 1000:0 $HOME - USER 1000 CMD ["--tail-log"] diff --git a/dockerfile-kasm-rockylinux-9-desktop b/dockerfile-kasm-rockylinux-9-desktop index 4ea76f42c..92d990710 100644 --- a/dockerfile-kasm-rockylinux-9-desktop +++ b/dockerfile-kasm-rockylinux-9-desktop @@ -10,79 +10,45 @@ ENV STARTUPDIR /dockerstartup WORKDIR $HOME ### Envrionment config -ENV DEBIAN_FRONTEND noninteractive -ENV KASM_RX_HOME $STARTUPDIR/kasmrx -ENV INST_SCRIPTS $STARTUPDIR/install -ENV DONT_PROMPT_WSL_INSTALL "No_Prompt_please" - -### Install Tools -COPY ./src/oracle/install/tools $INST_SCRIPTS/tools/ -RUN bash $INST_SCRIPTS/tools/install_tools_deluxe.sh && rm -rf $INST_SCRIPTS/tools/ - -# Install Utilities -COPY ./src/oracle/install/misc $INST_SCRIPTS/misc/ -RUN bash $INST_SCRIPTS/misc/install_tools.sh && rm -rf $INST_SCRIPTS/misc/ - -# Install Chromium -COPY ./src/ubuntu/install/chromium $INST_SCRIPTS/chromium/ -RUN bash $INST_SCRIPTS/chromium/install_chromium.sh && rm -rf $INST_SCRIPTS/chromium/ - -# Install Firefox -COPY ./src/ubuntu/install/firefox/ $INST_SCRIPTS/firefox/ -COPY ./src/ubuntu/install/firefox/firefox.desktop $HOME/Desktop/ -RUN bash $INST_SCRIPTS/firefox/install_firefox.sh && rm -rf $INST_SCRIPTS/firefox/ - -### Install Sublime Text -COPY ./src/oracle/install/sublime_text $INST_SCRIPTS/sublime_text/ -RUN bash $INST_SCRIPTS/sublime_text/install_sublime_text.sh && rm -rf $INST_SCRIPTS/sublime_text/ - -### Install Visual Studio Code -COPY ./src/oracle/install/vs_code $INST_SCRIPTS/vs_code/ -RUN bash $INST_SCRIPTS/vs_code/install_vs_code.sh && rm -rf $INST_SCRIPTS/vs_code/ - -### Install Remmina -COPY ./src/ubuntu/install/remmina $INST_SCRIPTS/remmina/ -RUN bash $INST_SCRIPTS/remmina/install_remmina.sh && rm -rf $INST_SCRIPTS/remmina/ - -### Install Only Office -COPY ./src/oracle/install/only_office $INST_SCRIPTS/only_office/ -RUN bash $INST_SCRIPTS/only_office/install_only_office.sh && rm -rf $INST_SCRIPTS/only_office/ - -### Install GIMP -COPY ./src/oracle/install/gimp $INST_SCRIPTS/gimp/ -RUN bash $INST_SCRIPTS/gimp/install_gimp.sh && rm -rf $INST_SCRIPTS/gimp/ - -### Install Zoom -COPY ./src/oracle/install/zoom $INST_SCRIPTS/zoom/ -RUN bash $INST_SCRIPTS/zoom/install_zoom.sh && rm -rf $INST_SCRIPTS/zoom/ - -### Install Ansible -COPY ./src/oracle/install/ansible $INST_SCRIPTS/ansible/ -RUN bash $INST_SCRIPTS/ansible/install_ansible.sh && rm -rf $INST_SCRIPTS/ansible/ - -### Install Terraform -COPY ./src/oracle/install/terraform $INST_SCRIPTS/terraform/ -RUN bash $INST_SCRIPTS/terraform/install_terraform.sh && rm -rf $INST_SCRIPTS/terraform/ - -### Install Telegram -COPY ./src/oracle/install/telegram $INST_SCRIPTS/telegram/ -RUN bash $INST_SCRIPTS/telegram/install_telegram.sh && rm -rf $INST_SCRIPTS/telegram/ - -### Install Thunderbird -COPY ./src/ubuntu/install/thunderbird $INST_SCRIPTS/thunderbird/ -RUN bash $INST_SCRIPTS/thunderbird/install_thunderbird.sh && rm -rf $INST_SCRIPTS/thunderbird/ - -#ADD ./src/common/scripts $STARTUPDIR -RUN $STARTUPDIR/set_user_permission.sh $HOME - -RUN rm -f /etc/X11/xinit/Xclients - -RUN chown 1000:0 $HOME - +ENV SKIP_CLEAN=true \ + KASM_RX_HOME=$STARTUPDIR/kasmrx \ + DONT_PROMPT_WSL_INSTALL="No_Prompt_please" \ + INST_DIR=$STARTUPDIR/install \ + INST_SCRIPTS="/oracle/install/tools/install_tools_deluxe.sh \ + /oracle/install/misc/install_tools.sh \ + /ubuntu/install/chromium/install_chromium.sh \ + /ubuntu/install/firefox/install_firefox.sh \ + /oracle/install/sublime_text/install_sublime_text.sh \ + /oracle/install/vs_code/install_vs_code.sh \ + /ubuntu/install/remmina/install_remmina.sh \ + /oracle/install/only_office/install_only_office.sh \ + /oracle/install/gimp/install_gimp.sh \ + /oracle/install/zoom/install_zoom.sh \ + /oracle/install/ansible/install_ansible.sh \ + /oracle/install/terraform/install_terraform.sh \ + /oracle/install/telegram/install_telegram.sh \ + /ubuntu/install/thunderbird/install_thunderbird.sh \ + /ubuntu/install/slack/install_slack.sh \ + /ubuntu/install/cleanup/cleanup.sh" + +# Copy install scripts +COPY ./src/ $INST_DIR + +# Run installations +RUN \ + for SCRIPT in $INST_SCRIPTS; do \ + bash ${INST_DIR}${SCRIPT} || exit 1; \ + done && \ + $STARTUPDIR/set_user_permission.sh $HOME && \ + rm -f /etc/X11/xinit/Xclients && \ + chown 1000:0 $HOME && \ + mkdir -p /home/kasm-user && \ + chown -R 1000:0 /home/kasm-user && \ + rm -Rf ${INST_DIR} + +# Userspace Runtime ENV HOME /home/kasm-user WORKDIR $HOME -RUN mkdir -p $HOME && chown -R 1000:0 $HOME - USER 1000 CMD ["--tail-log"] diff --git a/dockerfile-kasm-signal b/dockerfile-kasm-signal index 03a82bbc9..9cc434eb6 100644 --- a/dockerfile-kasm-signal +++ b/dockerfile-kasm-signal @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root @@ -21,7 +21,7 @@ RUN chmod 755 $STARTUPDIR/custom_startup.sh # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel diff --git a/dockerfile-kasm-slack b/dockerfile-kasm-slack index 20e8fbc39..fe5707f14 100644 --- a/dockerfile-kasm-slack +++ b/dockerfile-kasm-slack @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root @@ -24,7 +24,7 @@ RUN chmod 755 $STARTUPDIR/custom_startup.sh # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel ######### End Customizations ########### diff --git a/dockerfile-kasm-spiderfoot b/dockerfile-kasm-spiderfoot new file mode 100644 index 000000000..d6e360c3e --- /dev/null +++ b/dockerfile-kasm-spiderfoot @@ -0,0 +1,48 @@ +ARG BASE_TAG="develop" +ARG BASE_IMAGE="core-ubuntu-jammy" +FROM kasmweb/$BASE_IMAGE:$BASE_TAG + +USER root + +ENV HOME /home/kasm-default-profile +ENV STARTUPDIR /dockerstartup +ENV LAUNCH_URL http://127.0.0.1:5002 +WORKDIR $HOME + +### Envrionment config +ENV DEBIAN_FRONTEND=noninteractive \ + SKIP_CLEAN=true \ + KASM_RX_HOME=$STARTUPDIR/kasmrx \ + DONT_PROMPT_WSL_INSTALL="No_Prompt_please" \ + INST_DIR=$STARTUPDIR/install \ + INST_SCRIPTS="/ubuntu/install/tools/install_tools_deluxe.sh \ + /ubuntu/install/firefox/install_firefox.sh \ + /ubuntu/install/spiderfoot/install_spiderfoot.sh \ + /ubuntu/install/cleanup/cleanup.sh" + +# Copy install scripts +COPY ./src/ $INST_DIR + +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png +COPY ./src/ubuntu/install/spiderfoot/custom_startup.sh $STARTUPDIR/custom_startup.sh +RUN chmod +x $STARTUPDIR/custom_startup.sh +RUN chmod 755 $STARTUPDIR/custom_startup.sh + +# Run installations +RUN \ + for SCRIPT in $INST_SCRIPTS; do \ + bash ${INST_DIR}${SCRIPT} || exit 1; \ + done && \ + $STARTUPDIR/set_user_permission.sh $HOME && \ + rm -f /etc/X11/xinit/Xclients && \ + chown 1000:0 $HOME && \ + mkdir -p /home/kasm-user && \ + chown -R 1000:0 /home/kasm-user && \ + rm -Rf ${INST_DIR} + +# Userspace Runtime +ENV HOME /home/kasm-user +WORKDIR $HOME +USER 1000 + +CMD ["--tail-log"] diff --git a/dockerfile-kasm-steam b/dockerfile-kasm-steam index 612e5255c..982dd199a 100644 --- a/dockerfile-kasm-steam +++ b/dockerfile-kasm-steam @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root @@ -21,7 +21,7 @@ RUN chmod 755 $STARTUPDIR/custom_startup.sh # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel ######### End Customizations ########### diff --git a/dockerfile-kasm-sublime-text b/dockerfile-kasm-sublime-text index f7352999f..64535f665 100644 --- a/dockerfile-kasm-sublime-text +++ b/dockerfile-kasm-sublime-text @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root @@ -21,7 +21,7 @@ RUN chmod 755 $STARTUPDIR/custom_startup.sh # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel diff --git a/dockerfile-kasm-super-tux-kart b/dockerfile-kasm-super-tux-kart index 1e5759604..44bb633af 100644 --- a/dockerfile-kasm-super-tux-kart +++ b/dockerfile-kasm-super-tux-kart @@ -19,7 +19,7 @@ RUN chmod 755 $STARTUPDIR/custom_startup.sh # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel ######### End Customizations ########### diff --git a/dockerfile-kasm-teams b/dockerfile-kasm-teams index 59af37c2c..c9e1b604f 100644 --- a/dockerfile-kasm-teams +++ b/dockerfile-kasm-teams @@ -21,7 +21,7 @@ RUN chmod 755 $STARTUPDIR/custom_startup.sh # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel diff --git a/dockerfile-kasm-telegram b/dockerfile-kasm-telegram index b40f1ca42..9b5843721 100644 --- a/dockerfile-kasm-telegram +++ b/dockerfile-kasm-telegram @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root @@ -11,6 +11,11 @@ WORKDIR $HOME ######### Customize Container Here ########### +# Install Google Chrome +COPY ./src/ubuntu/install/chrome $INST_SCRIPTS/chrome/ +RUN bash $INST_SCRIPTS/chrome/install_chrome.sh && rm -rf $INST_SCRIPTS/chrome/ + + COPY ./src/ubuntu/install/telegram $INST_SCRIPTS/telegram/ RUN bash $INST_SCRIPTS/telegram/install_telegram.sh && rm -rf $INST_SCRIPTS/telegram/ @@ -21,7 +26,7 @@ RUN chmod 755 $STARTUPDIR/custom_startup.sh # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel diff --git a/dockerfile-kasm-terminal b/dockerfile-kasm-terminal index 0bbaa2659..a16e41c76 100644 --- a/dockerfile-kasm-terminal +++ b/dockerfile-kasm-terminal @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root @@ -35,7 +35,7 @@ RUN chmod 755 $STARTUPDIR/custom_startup.sh # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel diff --git a/dockerfile-kasm-thunderbird b/dockerfile-kasm-thunderbird index cdc197468..6c8615432 100644 --- a/dockerfile-kasm-thunderbird +++ b/dockerfile-kasm-thunderbird @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root @@ -21,7 +21,7 @@ RUN chmod 755 $STARTUPDIR/custom_startup.sh # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel ######### End Customizations ########### diff --git a/dockerfile-kasm-tor-browser b/dockerfile-kasm-tor-browser index 95307aae0..60bfdfe8a 100644 --- a/dockerfile-kasm-tor-browser +++ b/dockerfile-kasm-tor-browser @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root @@ -17,12 +17,18 @@ RUN bash $INST_SCRIPTS/torbrowser/install_torbrowser.sh && rm -rf $INST_SCRIPTS # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel +# Security modifications +COPY ./src/ubuntu/install/misc/single_app_security.sh $INST_SCRIPTS/misc/ +RUN bash $INST_SCRIPTS/misc/single_app_security.sh -t && rm -rf $INST_SCRIPTS/misc/ + ENV KASM_RESTRICTED_FILE_CHOOSER=1 COPY ./src/ubuntu/install/gtk/ $INST_SCRIPTS/gtk/ RUN bash $INST_SCRIPTS/gtk/install_restricted_file_chooser.sh +COPY ./src/ubuntu/install/close_browser_breakout_via_file_manager/ $INST_SCRIPTS/close_browser_breakout_via_file_manager/ +RUN bash $INST_SCRIPTS/close_browser_breakout_via_file_manager/replace_thunar_with_empty_script.sh # Setup the custom startup script that will be invoked when the container starts #ENV LAUNCH_URL about:blank diff --git a/dockerfile-kasm-tracelabs b/dockerfile-kasm-tracelabs index 4c0601c2d..d32d0d945 100644 --- a/dockerfile-kasm-tracelabs +++ b/dockerfile-kasm-tracelabs @@ -10,15 +10,14 @@ WORKDIR $HOME ######### Customize Container Here ########### +# Install Firefox first to avoid p11-kit version conflicts +COPY ./src/ubuntu/install/firefox/ $INST_SCRIPTS/firefox/ +RUN bash $INST_SCRIPTS/firefox/install_firefox.sh && rm -rf $INST_SCRIPTS/firefox/ # Install Tracelabs utils COPY ./src/ubuntu/install/tracelabs $INST_SCRIPTS/tracelabs/ RUN bash $INST_SCRIPTS/tracelabs/install_tracelabs.sh && rm -rf $INST_SCRIPTS/tracelabs/ -# Install Firefox -COPY ./src/ubuntu/install/firefox/ $INST_SCRIPTS/firefox/ -RUN bash $INST_SCRIPTS/firefox/install_firefox.sh && rm -rf $INST_SCRIPTS/firefox/ - ######### End Customizations ########### RUN chown 1000:0 $HOME diff --git a/dockerfile-kasm-ubuntu-focal-desktop b/dockerfile-kasm-ubuntu-focal-desktop deleted file mode 100644 index 4ee9c4c79..000000000 --- a/dockerfile-kasm-ubuntu-focal-desktop +++ /dev/null @@ -1,105 +0,0 @@ -ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" -FROM kasmweb/$BASE_IMAGE:$BASE_TAG - -USER root - -ENV HOME /home/kasm-default-profile -ENV STARTUPDIR /dockerstartup -WORKDIR $HOME - -### Envrionment config -ENV DEBIAN_FRONTEND noninteractive -ENV KASM_RX_HOME $STARTUPDIR/kasmrx -ENV INST_SCRIPTS $STARTUPDIR/install -ENV DONT_PROMPT_WSL_INSTALL "No_Prompt_please" - -### Install Tools -COPY ./src/ubuntu/install/tools $INST_SCRIPTS/tools/ -RUN bash $INST_SCRIPTS/tools/install_tools_deluxe.sh && rm -rf $INST_SCRIPTS/tools/ - -# Install Utilities -COPY ./src/ubuntu/install/misc $INST_SCRIPTS/misc/ -RUN bash $INST_SCRIPTS/misc/install_tools.sh && rm -rf $INST_SCRIPTS/misc/ - -# Install Google Chrome -COPY ./src/ubuntu/install/chrome $INST_SCRIPTS/chrome/ -RUN bash $INST_SCRIPTS/chrome/install_chrome.sh && rm -rf $INST_SCRIPTS/chrome/ - -# Install Chromium -COPY ./src/ubuntu/install/chromium $INST_SCRIPTS/chromium/ -RUN if [ "$(uname -m)" = "aarch64" ]; then bash $INST_SCRIPTS/chromium/install_chromium.sh; fi && rm -rf $INST_SCRIPTS/chromium/ - -# Install Firefox -COPY ./src/ubuntu/install/firefox/ $INST_SCRIPTS/firefox/ -COPY ./src/ubuntu/install/firefox/firefox.desktop $HOME/Desktop/ -RUN bash $INST_SCRIPTS/firefox/install_firefox.sh && rm -rf $INST_SCRIPTS/firefox/ - -### Install Sublime Text -COPY ./src/ubuntu/install/sublime_text $INST_SCRIPTS/sublime_text/ -RUN bash $INST_SCRIPTS/sublime_text/install_sublime_text.sh && rm -rf $INST_SCRIPTS/sublime_text/ - -### Install Visual Studio Code -COPY ./src/ubuntu/install/vs_code $INST_SCRIPTS/vs_code/ -RUN bash $INST_SCRIPTS/vs_code/install_vs_code.sh && rm -rf $INST_SCRIPTS/vs_code/ - -### Install NextCloud -COPY ./src/ubuntu/install/nextcloud $INST_SCRIPTS/nextcloud/ -RUN bash $INST_SCRIPTS/nextcloud/install_nextcloud.sh && rm -rf $INST_SCRIPTS/nextcloud/ - -### Install Remmina -COPY ./src/ubuntu/install/remmina $INST_SCRIPTS/remmina/ -RUN bash $INST_SCRIPTS/remmina/install_remmina.sh && rm -rf $INST_SCRIPTS/remmina/ - -### Install Only Office -COPY ./src/ubuntu/install/only_office $INST_SCRIPTS/only_office/ -RUN bash $INST_SCRIPTS/only_office/install_only_office.sh && rm -rf $INST_SCRIPTS/only_office/ - -### Install Signal -COPY ./src/ubuntu/install/signal $INST_SCRIPTS/signal/ -RUN bash $INST_SCRIPTS/signal/install_signal.sh && rm -rf $INST_SCRIPTS/signal/ - -### Install GIMP -COPY ./src/ubuntu/install/gimp $INST_SCRIPTS/gimp/ -RUN bash $INST_SCRIPTS/gimp/install_gimp.sh && rm -rf $INST_SCRIPTS/gimp/ - -### Install Zoom -COPY ./src/ubuntu/install/zoom $INST_SCRIPTS/zoom/ -RUN bash $INST_SCRIPTS/zoom/install_zoom.sh && rm -rf $INST_SCRIPTS/zoom/ - -### Install OBS Studio -COPY ./src/ubuntu/install/obs $INST_SCRIPTS/obs/ -RUN bash $INST_SCRIPTS/obs/install_obs.sh && rm -rf $INST_SCRIPTS/obs/ - -### Install Ansible -COPY ./src/ubuntu/install/ansible $INST_SCRIPTS/ansible/ -RUN bash $INST_SCRIPTS/ansible/install_ansible.sh && rm -rf $INST_SCRIPTS/ansible/ - -### Install Terraform -COPY ./src/ubuntu/install/terraform $INST_SCRIPTS/terraform/ -RUN bash $INST_SCRIPTS/terraform/install_terraform.sh && rm -rf $INST_SCRIPTS/terraform/ - -### Install Telegram -COPY ./src/ubuntu/install/telegram $INST_SCRIPTS/telegram/ -RUN bash $INST_SCRIPTS/telegram/install_telegram.sh && rm -rf $INST_SCRIPTS/telegram/ - -### Install Thunderbird -COPY ./src/ubuntu/install/thunderbird $INST_SCRIPTS/thunderbird/ -RUN bash $INST_SCRIPTS/thunderbird/install_thunderbird.sh && rm -rf $INST_SCRIPTS/thunderbird/ - -# Install Gamepad Testing Utils -COPY ./src/ubuntu/install/gamepad_utils $INST_SCRIPTS/gamepad_utils/ -RUN bash $INST_SCRIPTS/gamepad_utils/install_gamepad_utils.sh && rm -rf $INST_SCRIPTS/gamepad_utils/ - -#ADD ./src/common/scripts $STARTUPDIR -RUN $STARTUPDIR/set_user_permission.sh $HOME - -RUN chown 1000:0 $HOME - -ENV HOME /home/kasm-user -WORKDIR $HOME -RUN mkdir -p $HOME && chown -R 1000:0 $HOME - -USER 1000 - -CMD ["--tail-log"] diff --git a/dockerfile-kasm-ubuntu-focal-dind b/dockerfile-kasm-ubuntu-focal-dind deleted file mode 100644 index 74ab70bab..000000000 --- a/dockerfile-kasm-ubuntu-focal-dind +++ /dev/null @@ -1,61 +0,0 @@ -ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" -FROM kasmweb/$BASE_IMAGE:$BASE_TAG -USER root - -ENV HOME /home/kasm-default-profile -ENV STARTUPDIR /dockerstartup -ENV INST_SCRIPTS $STARTUPDIR/install -WORKDIR $HOME - -######### Customize Container Here ########### - -ENV DOCKER_CHANNEL=stable \ - DOCKER_VERSION=20.10.9 \ - DOCKER_COMPOSE_VERSION=1.29.2 \ - DEBUG=false \ - DONT_PROMPT_WSL_INSTALL="No_Prompt_please" - - -COPY ./src/ubuntu/install/dind $INST_SCRIPTS/dind/ -COPY ./src/ubuntu/install/dind/daemon.json /etc/docker/daemon.json - -RUN bash $INST_SCRIPTS/dind/install_dind.sh && rm -rf $INST_SCRIPTS/dind/ - -COPY ./src/ubuntu/install/dind/custom_startup.sh $STARTUPDIR/custom_startup.sh -RUN chmod +x $STARTUPDIR/custom_startup.sh -RUN chmod 755 $STARTUPDIR/custom_startup.sh - -COPY ./src/ubuntu/install/dind/modprobe /usr/local/bin/modprobe -RUN chmod +x /usr/local/bin/modprobe -COPY ./src/ubuntu/install/dind/dockerd.conf /etc/supervisor/conf.d/ - -### Install Tools -COPY ./src/ubuntu/install/tools $INST_SCRIPTS/tools/ -RUN bash $INST_SCRIPTS/tools/install_tools_deluxe.sh && rm -rf $INST_SCRIPTS/tools/ - -# Install Utilities -COPY ./src/ubuntu/install/misc $INST_SCRIPTS/misc/ -RUN bash $INST_SCRIPTS/misc/install_tools.sh && rm -rf $INST_SCRIPTS/misc/ - -### Install Sublime Text -COPY ./src/ubuntu/install/sublime_text $INST_SCRIPTS/sublime_text/ -RUN bash $INST_SCRIPTS/sublime_text/install_sublime_text.sh && rm -rf $INST_SCRIPTS/sublime_text/ - -### Install Visual Studio Code -COPY ./src/ubuntu/install/vs_code $INST_SCRIPTS/vs_code/ -RUN bash $INST_SCRIPTS/vs_code/install_vs_code.sh && rm -rf $INST_SCRIPTS/vs_code/ - -# Install Google Chrome -COPY ./src/ubuntu/install/chrome $INST_SCRIPTS/chrome/ -RUN bash $INST_SCRIPTS/chrome/install_chrome.sh && rm -rf $INST_SCRIPTS/chrome/ - -######### End Customizations ########### - -RUN chown 1000:0 $HOME - -ENV HOME /home/kasm-user -WORKDIR $HOME -RUN mkdir -p $HOME && chown -R 1000:0 $HOME - -USER 1000 diff --git a/dockerfile-kasm-ubuntu-focal-dind-rootless b/dockerfile-kasm-ubuntu-focal-dind-rootless deleted file mode 100644 index 1c41684de..000000000 --- a/dockerfile-kasm-ubuntu-focal-dind-rootless +++ /dev/null @@ -1,68 +0,0 @@ -ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" -FROM kasmweb/$BASE_IMAGE:$BASE_TAG -USER root - -ENV HOME /home/kasm-default-profile -ENV STARTUPDIR /dockerstartup -ENV INST_SCRIPTS $STARTUPDIR/install -WORKDIR $HOME - -######### Customize Container Here ########### - -ENV DOCKER_BIN=/usr/local/lib/docker \ - XDG_RUNTIME_DIR=/docker \ - DONT_PROMPT_WSL_INSTALL="No_Prompt_please" - -RUN mkdir -p $DOCKER_BIN && chown 1000:0 $DOCKER_BIN && \ - mkdir -p $XDG_RUNTIME_DIR && chown 1000:0 $XDG_RUNTIME_DIR - -ENV PATH=$DOCKER_BIN:$DOCKER_BIN/cli-plugins:$PATH \ - DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock - -COPY ./src/ubuntu/install/dind_rootless/install_dind_rootless_prerequisites.sh $INST_SCRIPTS/dind_rootless/ -RUN bash $INST_SCRIPTS/dind_rootless/install_dind_rootless_prerequisites.sh - -COPY ./src/ubuntu/install/dind_rootless/install_dind_rootless.sh $INST_SCRIPTS/dind_rootless/ -RUN chown 1000:1000 $INST_SCRIPTS/dind_rootless/install_dind_rootless.sh -# It's recommended that docker-rootless be installed by non root user -USER 1000 -RUN bash $INST_SCRIPTS/dind_rootless/install_dind_rootless.sh -USER root -RUN rm -rf $INST_SCRIPTS/dind_rootless - -COPY ./src/ubuntu/install/dind_rootless/custom_startup.sh $STARTUPDIR/custom_startup.sh -RUN chmod +x $STARTUPDIR/custom_startup.sh && chmod 755 $STARTUPDIR/custom_startup.sh - -COPY ./src/ubuntu/install/dind_rootless/modprobe /usr/local/bin/modprobe -RUN chmod +x /usr/local/bin/modprobe - -### Install Tools -COPY ./src/ubuntu/install/tools $INST_SCRIPTS/tools/ -RUN bash $INST_SCRIPTS/tools/install_tools_deluxe.sh && rm -rf $INST_SCRIPTS/tools/ - -# Install Utilities -COPY ./src/ubuntu/install/misc $INST_SCRIPTS/misc/ -RUN bash $INST_SCRIPTS/misc/install_tools.sh && rm -rf $INST_SCRIPTS/misc/ - -### Install Sublime Text -COPY ./src/ubuntu/install/sublime_text $INST_SCRIPTS/sublime_text/ -RUN bash $INST_SCRIPTS/sublime_text/install_sublime_text.sh && rm -rf $INST_SCRIPTS/sublime_text/ - -### Install Visual Studio Code -COPY ./src/ubuntu/install/vs_code $INST_SCRIPTS/vs_code/ -RUN bash $INST_SCRIPTS/vs_code/install_vs_code.sh && rm -rf $INST_SCRIPTS/vs_code/ - -# Install Google Chrome -COPY ./src/ubuntu/install/chrome $INST_SCRIPTS/chrome/ -RUN bash $INST_SCRIPTS/chrome/install_chrome.sh && rm -rf $INST_SCRIPTS/chrome/ - -######### End Customizations ########### - -RUN chown 1000:0 $HOME - -ENV HOME /home/kasm-user -WORKDIR $HOME -RUN mkdir -p $HOME && chown -R 1000:0 $HOME - -USER 1000 diff --git a/dockerfile-kasm-ubuntu-jammy-desktop b/dockerfile-kasm-ubuntu-jammy-desktop index eccb70dcf..8758d49b8 100644 --- a/dockerfile-kasm-ubuntu-jammy-desktop +++ b/dockerfile-kasm-ubuntu-jammy-desktop @@ -9,97 +9,51 @@ ENV STARTUPDIR /dockerstartup WORKDIR $HOME ### Envrionment config -ENV DEBIAN_FRONTEND noninteractive -ENV KASM_RX_HOME $STARTUPDIR/kasmrx -ENV INST_SCRIPTS $STARTUPDIR/install -ENV DONT_PROMPT_WSL_INSTALL "No_Prompt_please" - -### Install Tools -COPY ./src/ubuntu/install/tools $INST_SCRIPTS/tools/ -RUN bash $INST_SCRIPTS/tools/install_tools_deluxe.sh && rm -rf $INST_SCRIPTS/tools/ - -# Install Utilities -COPY ./src/ubuntu/install/misc $INST_SCRIPTS/misc/ -RUN bash $INST_SCRIPTS/misc/install_tools.sh && rm -rf $INST_SCRIPTS/misc/ - -# Install Google Chrome -COPY ./src/ubuntu/install/chrome $INST_SCRIPTS/chrome/ -RUN bash $INST_SCRIPTS/chrome/install_chrome.sh && rm -rf $INST_SCRIPTS/chrome/ - -# Install Chromium -COPY ./src/ubuntu/install/chromium $INST_SCRIPTS/chromium/ -RUN if [ "$(uname -m)" = "aarch64" ]; then bash $INST_SCRIPTS/chromium/install_chromium.sh; fi && rm -rf $INST_SCRIPTS/chromium/ - -# Install Firefox -COPY ./src/ubuntu/install/firefox/ $INST_SCRIPTS/firefox/ -COPY ./src/ubuntu/install/firefox/firefox.desktop $HOME/Desktop/ -RUN bash $INST_SCRIPTS/firefox/install_firefox.sh && rm -rf $INST_SCRIPTS/firefox/ - -### Install Sublime Text -COPY ./src/ubuntu/install/sublime_text $INST_SCRIPTS/sublime_text/ -RUN bash $INST_SCRIPTS/sublime_text/install_sublime_text.sh && rm -rf $INST_SCRIPTS/sublime_text/ - -### Install Visual Studio Code -COPY ./src/ubuntu/install/vs_code $INST_SCRIPTS/vs_code/ -RUN bash $INST_SCRIPTS/vs_code/install_vs_code.sh && rm -rf $INST_SCRIPTS/vs_code/ - -### Install NextCloud -COPY ./src/ubuntu/install/nextcloud $INST_SCRIPTS/nextcloud/ -RUN bash $INST_SCRIPTS/nextcloud/install_nextcloud.sh && rm -rf $INST_SCRIPTS/nextcloud/ - -### Install Remmina -COPY ./src/ubuntu/install/remmina $INST_SCRIPTS/remmina/ -RUN bash $INST_SCRIPTS/remmina/install_remmina.sh && rm -rf $INST_SCRIPTS/remmina/ - -### Install Only Office -COPY ./src/ubuntu/install/only_office $INST_SCRIPTS/only_office/ -RUN bash $INST_SCRIPTS/only_office/install_only_office.sh && rm -rf $INST_SCRIPTS/only_office/ - -### Install Signal -COPY ./src/ubuntu/install/signal $INST_SCRIPTS/signal/ -RUN bash $INST_SCRIPTS/signal/install_signal.sh && rm -rf $INST_SCRIPTS/signal/ - -### Install GIMP -COPY ./src/ubuntu/install/gimp $INST_SCRIPTS/gimp/ -RUN bash $INST_SCRIPTS/gimp/install_gimp.sh && rm -rf $INST_SCRIPTS/gimp/ - -### Install Zoom -COPY ./src/ubuntu/install/zoom $INST_SCRIPTS/zoom/ -RUN bash $INST_SCRIPTS/zoom/install_zoom.sh && rm -rf $INST_SCRIPTS/zoom/ - -### Install OBS Studio -COPY ./src/ubuntu/install/obs $INST_SCRIPTS/obs/ -RUN bash $INST_SCRIPTS/obs/install_obs.sh && rm -rf $INST_SCRIPTS/obs/ - -### Install Ansible -COPY ./src/ubuntu/install/ansible $INST_SCRIPTS/ansible/ -RUN bash $INST_SCRIPTS/ansible/install_ansible.sh && rm -rf $INST_SCRIPTS/ansible/ - -### Install Terraform -COPY ./src/ubuntu/install/terraform $INST_SCRIPTS/terraform/ -RUN bash $INST_SCRIPTS/terraform/install_terraform.sh && rm -rf $INST_SCRIPTS/terraform/ - -### Install Telegram -COPY ./src/ubuntu/install/telegram $INST_SCRIPTS/telegram/ -RUN bash $INST_SCRIPTS/telegram/install_telegram.sh && rm -rf $INST_SCRIPTS/telegram/ - -### Install Thunderbird -COPY ./src/ubuntu/install/thunderbird $INST_SCRIPTS/thunderbird/ -RUN bash $INST_SCRIPTS/thunderbird/install_thunderbird.sh && rm -rf $INST_SCRIPTS/thunderbird/ - -# Install Gamepad Testing Utils -COPY ./src/ubuntu/install/gamepad_utils $INST_SCRIPTS/gamepad_utils/ -RUN bash $INST_SCRIPTS/gamepad_utils/install_gamepad_utils.sh && rm -rf $INST_SCRIPTS/gamepad_utils/ - -#ADD ./src/common/scripts $STARTUPDIR -RUN $STARTUPDIR/set_user_permission.sh $HOME - -RUN chown 1000:0 $HOME - +ENV DEBIAN_FRONTEND=noninteractive \ + SKIP_CLEAN=true \ + KASM_RX_HOME=$STARTUPDIR/kasmrx \ + DONT_PROMPT_WSL_INSTALL="No_Prompt_please" \ + INST_DIR=$STARTUPDIR/install \ + INST_SCRIPTS="/ubuntu/install/tools/install_tools_deluxe.sh \ + /ubuntu/install/misc/install_tools.sh \ + /ubuntu/install/chrome/install_chrome.sh \ + /ubuntu/install/chromium/install_chromium.sh \ + /ubuntu/install/firefox/install_firefox.sh \ + /ubuntu/install/sublime_text/install_sublime_text.sh \ + /ubuntu/install/vs_code/install_vs_code.sh \ + /ubuntu/install/nextcloud/install_nextcloud.sh \ + /ubuntu/install/remmina/install_remmina.sh \ + /ubuntu/install/only_office/install_only_office.sh \ + /ubuntu/install/signal/install_signal.sh \ + /ubuntu/install/gimp/install_gimp.sh \ + /ubuntu/install/zoom/install_zoom.sh \ + /ubuntu/install/obs/install_obs.sh \ + /ubuntu/install/ansible/install_ansible.sh \ + /ubuntu/install/terraform/install_terraform.sh \ + /ubuntu/install/telegram/install_telegram.sh \ + /ubuntu/install/thunderbird/install_thunderbird.sh \ + /ubuntu/install/slack/install_slack.sh \ + /ubuntu/install/gamepad_utils/install_gamepad_utils.sh \ + /ubuntu/install/cleanup/cleanup.sh" + +# Copy install scripts +COPY ./src/ $INST_DIR + +# Run installations +RUN \ + for SCRIPT in $INST_SCRIPTS; do \ + bash ${INST_DIR}${SCRIPT} || exit 1; \ + done && \ + $STARTUPDIR/set_user_permission.sh $HOME && \ + rm -f /etc/X11/xinit/Xclients && \ + chown 1000:0 $HOME && \ + mkdir -p /home/kasm-user && \ + chown -R 1000:0 /home/kasm-user && \ + rm -Rf ${INST_DIR} + +# Userspace Runtime ENV HOME /home/kasm-user WORKDIR $HOME -RUN mkdir -p $HOME && chown -R 1000:0 $HOME - USER 1000 CMD ["--tail-log"] diff --git a/dockerfile-kasm-ubuntu-jammy-desktop-vpn b/dockerfile-kasm-ubuntu-jammy-desktop-vpn new file mode 100644 index 000000000..acfbf86c6 --- /dev/null +++ b/dockerfile-kasm-ubuntu-jammy-desktop-vpn @@ -0,0 +1,61 @@ +ARG BASE_TAG="develop" +ARG BASE_IMAGE="core-ubuntu-jammy" +FROM kasmweb/$BASE_IMAGE:$BASE_TAG + +USER root + +ENV HOME /home/kasm-default-profile +ENV STARTUPDIR /dockerstartup +WORKDIR $HOME + +### Envrionment config +ENV DEBIAN_FRONTEND=noninteractive \ + SKIP_CLEAN=true \ + KASM_RX_HOME=$STARTUPDIR/kasmrx \ + DONT_PROMPT_WSL_INSTALL="No_Prompt_please" \ + INST_DIR=$STARTUPDIR/install \ + INST_SCRIPTS="/ubuntu/install/tools/install_tools_deluxe.sh \ + /ubuntu/install/misc/install_tools.sh \ + /ubuntu/install/chrome/install_chrome.sh \ + /ubuntu/install/chromium/install_chromium.sh \ + /ubuntu/install/firefox/install_firefox.sh \ + /ubuntu/install/sublime_text/install_sublime_text.sh \ + /ubuntu/install/vs_code/install_vs_code.sh \ + /ubuntu/install/nextcloud/install_nextcloud.sh \ + /ubuntu/install/remmina/install_remmina.sh \ + /ubuntu/install/only_office/install_only_office.sh \ + /ubuntu/install/signal/install_signal.sh \ + /ubuntu/install/gimp/install_gimp.sh \ + /ubuntu/install/zoom/install_zoom.sh \ + /ubuntu/install/obs/install_obs.sh \ + /ubuntu/install/ansible/install_ansible.sh \ + /ubuntu/install/terraform/install_terraform.sh \ + /ubuntu/install/telegram/install_telegram.sh \ + /ubuntu/install/thunderbird/install_thunderbird.sh \ + /ubuntu/install/slack/install_slack.sh \ + /ubuntu/install/gamepad_utils/install_gamepad_utils.sh \ + /ubuntu/install/vpn/install_vpn.sh \ + /ubuntu/install/cleanup/cleanup.sh" + +# Copy install scripts +COPY ./src/ $INST_DIR + +# Run installations +RUN \ + for SCRIPT in $INST_SCRIPTS; do \ + bash ${INST_DIR}${SCRIPT} || exit 1; \ + done && \ + $STARTUPDIR/set_user_permission.sh $HOME && \ + rm -f /etc/X11/xinit/Xclients && \ + chown 1000:0 $HOME && \ + mkdir -p /home/kasm-user && \ + chown -R 1000:0 /home/kasm-user && \ + rm -Rf ${INST_DIR} + +# Userspace Runtime +ENV HOME /home/kasm-user +WORKDIR $HOME +USER 1000 + +CMD ["--tail-log"] + diff --git a/dockerfile-kasm-ubuntu-jammy-dind b/dockerfile-kasm-ubuntu-jammy-dind index 6186227fa..14b6fb349 100644 --- a/dockerfile-kasm-ubuntu-jammy-dind +++ b/dockerfile-kasm-ubuntu-jammy-dind @@ -5,56 +5,48 @@ USER root ENV HOME /home/kasm-default-profile ENV STARTUPDIR /dockerstartup -ENV INST_SCRIPTS $STARTUPDIR/install WORKDIR $HOME -######### Customize Container Here ########### - -ENV DOCKER_CHANNEL=stable \ - DOCKER_VERSION=20.10.9 \ - DOCKER_COMPOSE_VERSION=1.29.2 \ - DEBUG=false \ - DONT_PROMPT_WSL_INSTALL="No_Prompt_please" - -COPY ./src/ubuntu/install/dind $INST_SCRIPTS/dind/ -COPY ./src/ubuntu/install/dind/daemon.json /etc/docker/daemon.json - -RUN bash $INST_SCRIPTS/dind/install_dind.sh && rm -rf $INST_SCRIPTS/dind/ - +### Envrionment config +ENV DEBUG=false \ + DEBIAN_FRONTEND=noninteractive \ + SKIP_CLEAN=true \ + KASM_RX_HOME=$STARTUPDIR/kasmrx \ + DONT_PROMPT_WSL_INSTALL="No_Prompt_please" \ + INST_DIR=$STARTUPDIR/install \ + INST_SCRIPTS="/ubuntu/install/dind/install_dind.sh \ + /ubuntu/install/tools/install_tools_deluxe.sh \ + /ubuntu/install/misc/install_tools.sh \ + /ubuntu/install/chrome/install_chrome.sh \ + /ubuntu/install/chromium/install_chromium.sh \ + /ubuntu/install/sublime_text/install_sublime_text.sh \ + /ubuntu/install/vs_code/install_vs_code.sh \ + /ubuntu/install/cleanup/cleanup.sh" + +# Startup Scripts COPY ./src/ubuntu/install/dind/custom_startup.sh $STARTUPDIR/custom_startup.sh -RUN chmod +x $STARTUPDIR/custom_startup.sh RUN chmod 755 $STARTUPDIR/custom_startup.sh - -COPY ./src/ubuntu/install/dind/modprobe /usr/local/bin/modprobe -RUN chmod +x /usr/local/bin/modprobe COPY ./src/ubuntu/install/dind/dockerd.conf /etc/supervisor/conf.d/ +COPY ./src/ubuntu/install/dind/daemon.json /etc/docker/daemon.json -### Install Tools -COPY ./src/ubuntu/install/tools $INST_SCRIPTS/tools/ -RUN bash $INST_SCRIPTS/tools/install_tools_deluxe.sh && rm -rf $INST_SCRIPTS/tools/ - -# Install Utilities -COPY ./src/ubuntu/install/misc $INST_SCRIPTS/misc/ -RUN bash $INST_SCRIPTS/misc/install_tools.sh && rm -rf $INST_SCRIPTS/misc/ - -### Install Sublime Text -COPY ./src/ubuntu/install/sublime_text $INST_SCRIPTS/sublime_text/ -RUN bash $INST_SCRIPTS/sublime_text/install_sublime_text.sh && rm -rf $INST_SCRIPTS/sublime_text/ - -### Install Visual Studio Code -COPY ./src/ubuntu/install/vs_code $INST_SCRIPTS/vs_code/ -RUN bash $INST_SCRIPTS/vs_code/install_vs_code.sh && rm -rf $INST_SCRIPTS/vs_code/ - -# Install Google Chrome -COPY ./src/ubuntu/install/chrome $INST_SCRIPTS/chrome/ -RUN bash $INST_SCRIPTS/chrome/install_chrome.sh && rm -rf $INST_SCRIPTS/chrome/ - -######### End Customizations ########### - -RUN chown 1000:0 $HOME - +# Copy install scripts +COPY ./src/ $INST_DIR + +# Run installations +RUN \ + for SCRIPT in $INST_SCRIPTS; do \ + bash ${INST_DIR}${SCRIPT} || exit 1; \ + done && \ + $STARTUPDIR/set_user_permission.sh $HOME && \ + rm -f /etc/X11/xinit/Xclients && \ + chown 1000:0 $HOME && \ + mkdir -p /home/kasm-user && \ + chown -R 1000:0 /home/kasm-user && \ + rm -Rf ${INST_DIR} + +# Userspace Runtime ENV HOME /home/kasm-user WORKDIR $HOME -RUN mkdir -p $HOME && chown -R 1000:0 $HOME - USER 1000 + +CMD ["--tail-log"] diff --git a/dockerfile-kasm-ubuntu-jammy-dind-rootless b/dockerfile-kasm-ubuntu-jammy-dind-rootless index 72f6eb778..ee315eab5 100644 --- a/dockerfile-kasm-ubuntu-jammy-dind-rootless +++ b/dockerfile-kasm-ubuntu-jammy-dind-rootless @@ -8,61 +8,54 @@ ENV STARTUPDIR /dockerstartup ENV INST_SCRIPTS $STARTUPDIR/install WORKDIR $HOME -######### Customize Container Here ########### - -ENV DOCKER_BIN=/usr/local/lib/docker \ - XDG_RUNTIME_DIR=/docker \ - DONT_PROMPT_WSL_INSTALL="No_Prompt_please" - -RUN mkdir -p $DOCKER_BIN && chown 1000:0 $DOCKER_BIN && \ - mkdir -p $XDG_RUNTIME_DIR && chown 1000:0 $XDG_RUNTIME_DIR - -ENV PATH=$DOCKER_BIN:$DOCKER_BIN/cli-plugins:$PATH \ - DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock - -COPY ./src/ubuntu/install/dind_rootless/install_dind_rootless_prerequisites.sh $INST_SCRIPTS/dind_rootless/ -RUN bash $INST_SCRIPTS/dind_rootless/install_dind_rootless_prerequisites.sh - +# Rootless Dind COPY ./src/ubuntu/install/dind_rootless/install_dind_rootless.sh $INST_SCRIPTS/dind_rootless/ -RUN chown 1000:1000 $INST_SCRIPTS/dind_rootless/install_dind_rootless.sh -# It's recommended that docker-rootless be installed by non root user -USER 1000 RUN bash $INST_SCRIPTS/dind_rootless/install_dind_rootless.sh -USER root RUN rm -rf $INST_SCRIPTS/dind_rootless - COPY ./src/ubuntu/install/dind_rootless/custom_startup.sh $STARTUPDIR/custom_startup.sh RUN chmod +x $STARTUPDIR/custom_startup.sh && chmod 755 $STARTUPDIR/custom_startup.sh - COPY ./src/ubuntu/install/dind_rootless/modprobe /usr/local/bin/modprobe RUN chmod +x /usr/local/bin/modprobe - -### Install Tools -COPY ./src/ubuntu/install/tools $INST_SCRIPTS/tools/ -RUN bash $INST_SCRIPTS/tools/install_tools_deluxe.sh && rm -rf $INST_SCRIPTS/tools/ - -# Install Utilities -COPY ./src/ubuntu/install/misc $INST_SCRIPTS/misc/ -RUN bash $INST_SCRIPTS/misc/install_tools.sh && rm -rf $INST_SCRIPTS/misc/ - -### Install Sublime Text -COPY ./src/ubuntu/install/sublime_text $INST_SCRIPTS/sublime_text/ -RUN bash $INST_SCRIPTS/sublime_text/install_sublime_text.sh && rm -rf $INST_SCRIPTS/sublime_text/ - -### Install Visual Studio Code -COPY ./src/ubuntu/install/vs_code $INST_SCRIPTS/vs_code/ -RUN bash $INST_SCRIPTS/vs_code/install_vs_code.sh && rm -rf $INST_SCRIPTS/vs_code/ - -# Install Google Chrome -COPY ./src/ubuntu/install/chrome $INST_SCRIPTS/chrome/ -RUN bash $INST_SCRIPTS/chrome/install_chrome.sh && rm -rf $INST_SCRIPTS/chrome/ - -######### End Customizations ########### - -RUN chown 1000:0 $HOME - +ENV XDG_RUNTIME_DIR=/docker \ + DOCKER_HOST=unix:///docker/docker.sock +RUN mkdir -p $XDG_RUNTIME_DIR && chown 1000:0 $XDG_RUNTIME_DIR && \ + mkdir -p /home/kasm-user/.config/docker && \ + chown -R 1000:0 /home/kasm-user/.config +COPY ./src/ubuntu/install/dind_rootless/daemon.json /home/kasm-user/.config/docker/daemon.json +RUN chown 1000:0 /home/kasm-user/.config/docker/daemon.json + +### Envrionment config +ENV DEBIAN_FRONTEND=noninteractive \ + SKIP_CLEAN=true \ + KASM_RX_HOME=$STARTUPDIR/kasmrx \ + DONT_PROMPT_WSL_INSTALL="No_Prompt_please" \ + INST_DIR=$STARTUPDIR/install \ + INST_SCRIPTS="/ubuntu/install/tools/install_tools_deluxe.sh \ + /ubuntu/install/misc/install_tools.sh \ + /ubuntu/install/chrome/install_chrome.sh \ + /ubuntu/install/chromium/install_chromium.sh \ + /ubuntu/install/sublime_text/install_sublime_text.sh \ + /ubuntu/install/vs_code/install_vs_code.sh \ + /ubuntu/install/cleanup/cleanup.sh" + +# Copy install scripts +COPY ./src/ $INST_DIR + +# Run installations +RUN \ + for SCRIPT in $INST_SCRIPTS; do \ + bash ${INST_DIR}${SCRIPT} || exit 1; \ + done && \ + $STARTUPDIR/set_user_permission.sh $HOME && \ + rm -f /etc/X11/xinit/Xclients && \ + chown 1000:0 $HOME && \ + mkdir -p /home/kasm-user && \ + chown -R 1000:0 /home/kasm-user && \ + rm -Rf ${INST_DIR} + +# Userspace Runtime ENV HOME /home/kasm-user WORKDIR $HOME -RUN mkdir -p $HOME && chown -R 1000:0 $HOME - USER 1000 + +CMD ["--tail-log"] diff --git a/dockerfile-kasm-ubuntu-noble-desktop b/dockerfile-kasm-ubuntu-noble-desktop new file mode 100644 index 000000000..a36e192ba --- /dev/null +++ b/dockerfile-kasm-ubuntu-noble-desktop @@ -0,0 +1,57 @@ +ARG BASE_TAG="develop" +ARG BASE_IMAGE="core-ubuntu-noble" +FROM kasmweb/$BASE_IMAGE:$BASE_TAG + +USER root + +ENV HOME /home/kasm-default-profile +ENV STARTUPDIR /dockerstartup +WORKDIR $HOME + +### Envrionment config +ENV DEBIAN_FRONTEND=noninteractive \ + SKIP_CLEAN=true \ + KASM_RX_HOME=$STARTUPDIR/kasmrx \ + DONT_PROMPT_WSL_INSTALL="No_Prompt_please" \ + INST_DIR=$STARTUPDIR/install \ + INST_SCRIPTS="/ubuntu/install/tools/install_tools_deluxe.sh \ + /ubuntu/install/misc/install_tools.sh \ + /ubuntu/install/chrome/install_chrome.sh \ + /ubuntu/install/chromium/install_chromium.sh \ + /ubuntu/install/firefox/install_firefox.sh \ + /ubuntu/install/sublime_text/install_sublime_text.sh \ + /ubuntu/install/vs_code/install_vs_code.sh \ + /ubuntu/install/nextcloud/install_nextcloud.sh \ + /ubuntu/install/remmina/install_remmina.sh \ + /ubuntu/install/only_office/install_only_office.sh \ + /ubuntu/install/signal/install_signal.sh \ + /ubuntu/install/gimp/install_gimp.sh \ + /ubuntu/install/zoom/install_zoom.sh \ + /ubuntu/install/ansible/install_ansible.sh \ + /ubuntu/install/telegram/install_telegram.sh \ + /ubuntu/install/thunderbird/install_thunderbird.sh \ + /ubuntu/install/slack/install_slack.sh \ + /ubuntu/install/gamepad_utils/install_gamepad_utils.sh \ + /ubuntu/install/cleanup/cleanup.sh" + +# Copy install scripts +COPY ./src/ $INST_DIR + +# Run installations +RUN \ + for SCRIPT in $INST_SCRIPTS; do \ + bash ${INST_DIR}${SCRIPT} || exit 1; \ + done && \ + $STARTUPDIR/set_user_permission.sh $HOME && \ + rm -f /etc/X11/xinit/Xclients && \ + chown 1000:0 $HOME && \ + mkdir -p /home/kasm-user && \ + chown -R 1000:0 /home/kasm-user && \ + rm -Rf ${INST_DIR} + +# Userspace Runtime +ENV HOME /home/kasm-user +WORKDIR $HOME +USER 1000 + +CMD ["--tail-log"] diff --git a/dockerfile-kasm-ubuntu-noble-dind b/dockerfile-kasm-ubuntu-noble-dind new file mode 100644 index 000000000..bbcfc60b4 --- /dev/null +++ b/dockerfile-kasm-ubuntu-noble-dind @@ -0,0 +1,52 @@ +ARG BASE_TAG="develop" +ARG BASE_IMAGE="core-ubuntu-noble" +FROM kasmweb/$BASE_IMAGE:$BASE_TAG +USER root + +ENV HOME /home/kasm-default-profile +ENV STARTUPDIR /dockerstartup +WORKDIR $HOME + +### Envrionment config +ENV DEBUG=false \ + DEBIAN_FRONTEND=noninteractive \ + SKIP_CLEAN=true \ + KASM_RX_HOME=$STARTUPDIR/kasmrx \ + DONT_PROMPT_WSL_INSTALL="No_Prompt_please" \ + INST_DIR=$STARTUPDIR/install \ + INST_SCRIPTS="/ubuntu/install/dind/install_dind.sh \ + /ubuntu/install/tools/install_tools_deluxe.sh \ + /ubuntu/install/misc/install_tools.sh \ + /ubuntu/install/chrome/install_chrome.sh \ + /ubuntu/install/chromium/install_chromium.sh \ + /ubuntu/install/sublime_text/install_sublime_text.sh \ + /ubuntu/install/vs_code/install_vs_code.sh \ + /ubuntu/install/cleanup/cleanup.sh" + +# Startup Scripts +COPY ./src/ubuntu/install/dind/custom_startup.sh $STARTUPDIR/custom_startup.sh +RUN chmod 755 $STARTUPDIR/custom_startup.sh +COPY ./src/ubuntu/install/dind/dockerd.conf /etc/supervisor/conf.d/ +COPY ./src/ubuntu/install/dind/daemon.json /etc/docker/daemon.json + +# Copy install scripts +COPY ./src/ $INST_DIR + +# Run installations +RUN \ + for SCRIPT in $INST_SCRIPTS; do \ + bash ${INST_DIR}${SCRIPT} || exit 1; \ + done && \ + $STARTUPDIR/set_user_permission.sh $HOME && \ + rm -f /etc/X11/xinit/Xclients && \ + chown 1000:0 $HOME && \ + mkdir -p /home/kasm-user && \ + chown -R 1000:0 /home/kasm-user && \ + rm -Rf ${INST_DIR} + +# Userspace Runtime +ENV HOME /home/kasm-user +WORKDIR $HOME +USER 1000 + +CMD ["--tail-log"] diff --git a/dockerfile-kasm-ubuntu-noble-dind-rootless b/dockerfile-kasm-ubuntu-noble-dind-rootless new file mode 100644 index 000000000..4d9db8e88 --- /dev/null +++ b/dockerfile-kasm-ubuntu-noble-dind-rootless @@ -0,0 +1,61 @@ +ARG BASE_TAG="develop" +ARG BASE_IMAGE="core-ubuntu-noble" +FROM kasmweb/$BASE_IMAGE:$BASE_TAG +USER root + +ENV HOME /home/kasm-default-profile +ENV STARTUPDIR /dockerstartup +ENV INST_SCRIPTS $STARTUPDIR/install +WORKDIR $HOME + +# Rootless Dind +COPY ./src/ubuntu/install/dind_rootless/install_dind_rootless.sh $INST_SCRIPTS/dind_rootless/ +RUN bash $INST_SCRIPTS/dind_rootless/install_dind_rootless.sh +RUN rm -rf $INST_SCRIPTS/dind_rootless +COPY ./src/ubuntu/install/dind_rootless/custom_startup.sh $STARTUPDIR/custom_startup.sh +RUN chmod +x $STARTUPDIR/custom_startup.sh && chmod 755 $STARTUPDIR/custom_startup.sh +COPY ./src/ubuntu/install/dind_rootless/modprobe /usr/local/bin/modprobe +RUN chmod +x /usr/local/bin/modprobe +ENV XDG_RUNTIME_DIR=/docker \ + DOCKER_HOST=unix:///docker/docker.sock +RUN mkdir -p $XDG_RUNTIME_DIR && chown 1000:0 $XDG_RUNTIME_DIR && \ + mkdir -p /home/kasm-user/.config/docker && \ + chown -R 1000:0 /home/kasm-user/.config +COPY ./src/ubuntu/install/dind_rootless/daemon.json /home/kasm-user/.config/docker/daemon.json +RUN chown 1000:0 /home/kasm-user/.config/docker/daemon.json + +### Envrionment config +ENV DEBIAN_FRONTEND=noninteractive \ + SKIP_CLEAN=true \ + KASM_RX_HOME=$STARTUPDIR/kasmrx \ + DONT_PROMPT_WSL_INSTALL="No_Prompt_please" \ + INST_DIR=$STARTUPDIR/install \ + INST_SCRIPTS="/ubuntu/install/tools/install_tools_deluxe.sh \ + /ubuntu/install/misc/install_tools.sh \ + /ubuntu/install/chrome/install_chrome.sh \ + /ubuntu/install/chromium/install_chromium.sh \ + /ubuntu/install/sublime_text/install_sublime_text.sh \ + /ubuntu/install/vs_code/install_vs_code.sh \ + /ubuntu/install/cleanup/cleanup.sh" + +# Copy install scripts +COPY ./src/ $INST_DIR + +# Run installations +RUN \ + for SCRIPT in $INST_SCRIPTS; do \ + bash ${INST_DIR}${SCRIPT} || exit 1; \ + done && \ + $STARTUPDIR/set_user_permission.sh $HOME && \ + rm -f /etc/X11/xinit/Xclients && \ + chown 1000:0 $HOME && \ + mkdir -p /home/kasm-user && \ + chown -R 1000:0 /home/kasm-user && \ + rm -Rf ${INST_DIR} + +# Userspace Runtime +ENV HOME /home/kasm-user +WORKDIR $HOME +USER 1000 + +CMD ["--tail-log"] diff --git a/dockerfile-kasm-unityhub b/dockerfile-kasm-unityhub index 611b8bda8..7a36aa34b 100644 --- a/dockerfile-kasm-unityhub +++ b/dockerfile-kasm-unityhub @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root diff --git a/dockerfile-kasm-vivaldi b/dockerfile-kasm-vivaldi index 8f15bdb2a..53567ddb0 100644 --- a/dockerfile-kasm-vivaldi +++ b/dockerfile-kasm-vivaldi @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root @@ -17,9 +17,14 @@ RUN bash $INST_SCRIPTS/vivaldi/install_vivaldi.sh && rm -rf $INST_SCRIPTS/vival # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel +# Security modifications +COPY ./src/ubuntu/install/misc/single_app_security.sh $INST_SCRIPTS/misc/ +RUN bash $INST_SCRIPTS/misc/single_app_security.sh -t && rm -rf $INST_SCRIPTS/misc/ +COPY ./src/common/chrome-managed-policies/urlblocklist.json /etc/chromium/policies/managed/urlblocklist.json + # Setup the custom startup script that will be invoked when the container starts #ENV LAUNCH_URL http://kasmweb.com @@ -33,6 +38,8 @@ RUN chmod +x $STARTUPDIR/custom_startup.sh ENV KASM_RESTRICTED_FILE_CHOOSER=1 COPY ./src/ubuntu/install/gtk/ $INST_SCRIPTS/gtk/ RUN bash $INST_SCRIPTS/gtk/install_restricted_file_chooser.sh +COPY ./src/ubuntu/install/close_browser_breakout_via_file_manager/ $INST_SCRIPTS/close_browser_breakout_via_file_manager/ +RUN bash $INST_SCRIPTS/close_browser_breakout_via_file_manager/replace_thunar_with_empty_script.sh ######### End Customizations ########### diff --git a/dockerfile-kasm-vlc b/dockerfile-kasm-vlc index d65a56310..4c2412ab0 100644 --- a/dockerfile-kasm-vlc +++ b/dockerfile-kasm-vlc @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root @@ -21,7 +21,7 @@ RUN chmod 755 $STARTUPDIR/custom_startup.sh # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel diff --git a/dockerfile-kasm-vmware-horizon b/dockerfile-kasm-vmware-horizon index 71c81e7f6..0edf67caa 100644 --- a/dockerfile-kasm-vmware-horizon +++ b/dockerfile-kasm-vmware-horizon @@ -20,7 +20,7 @@ RUN chmod 755 $STARTUPDIR/custom_startup.sh # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel ######### End Customizations ########### diff --git a/dockerfile-kasm-vs-code b/dockerfile-kasm-vs-code index 76a0c29d3..64973bc47 100644 --- a/dockerfile-kasm-vs-code +++ b/dockerfile-kasm-vs-code @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root @@ -11,6 +11,11 @@ WORKDIR $HOME ######### Customize Container Here ########### +# Install Google Chrome +COPY ./src/ubuntu/install/chrome $INST_SCRIPTS/chrome/ +RUN bash $INST_SCRIPTS/chrome/install_chrome.sh && rm -rf $INST_SCRIPTS/chrome/ + + COPY ./src/ubuntu/install/vs_code $INST_SCRIPTS/vs_code/ RUN bash $INST_SCRIPTS/vs_code/install_vs_code.sh && rm -rf $INST_SCRIPTS/vs_code/ @@ -21,7 +26,7 @@ RUN chmod 755 $STARTUPDIR/custom_startup.sh # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel ######### End Customizations ########### diff --git a/dockerfile-kasm-zoom b/dockerfile-kasm-zoom index fdb7cdb5a..4cb9c8acd 100644 --- a/dockerfile-kasm-zoom +++ b/dockerfile-kasm-zoom @@ -10,21 +10,22 @@ WORKDIR $HOME ######### Customize Container Here ########### - +# Install Zoom COPY ./src/ubuntu/install/zoom $INST_SCRIPTS/zoom/ RUN bash $INST_SCRIPTS/zoom/install_zoom.sh && rm -rf $INST_SCRIPTS/zoom/ - COPY ./src/ubuntu/install/zoom/custom_startup.sh $STARTUPDIR/custom_startup.sh RUN chmod +x $STARTUPDIR/custom_startup.sh RUN chmod 755 $STARTUPDIR/custom_startup.sh +# Install Chrome +COPY ./src/ubuntu/install/chrome $INST_SCRIPTS/chrome/ +RUN bash $INST_SCRIPTS/chrome/install_chrome.sh && rm -rf $INST_SCRIPTS/chrome/ # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel - ######### End Customizations ########### RUN chown 1000:0 $HOME diff --git a/dockerfile-kasm-zorin b/dockerfile-kasm-zorin new file mode 100644 index 000000000..cf747660f --- /dev/null +++ b/dockerfile-kasm-zorin @@ -0,0 +1,70 @@ +ARG BASE_TAG="develop" +ARG BASE_IMAGE="core-ubuntu-jammy" +FROM kasmweb/$BASE_IMAGE:$BASE_TAG + +USER root + +ENV HOME=/home/kasm-default-profile +ENV STARTUPDIR=/dockerstartup +WORKDIR $HOME + +### Environment config +ENV DEBIAN_FRONTEND=noninteractive +ENV INST_SCRIPTS=$STARTUPDIR/install + +### KasmVNC quality: 60fps, balanced quality for smooth performance +ENV MAX_FRAME_RATE=60 +ENV VNCOPTIONS="-DynamicQualityMin 6 -DynamicQualityMax 9 -TreatLossless 9 -JpegVideoQuality 7 -WebpVideoQuality 7 -VideoScaling 2" + +######### Customize Container Here ########### + +# SSL: Generate CA-signed certs so browsers trust the HTTPS connection +COPY ./src/ubuntu/install/ssl $INST_SCRIPTS/ssl/ +RUN bash $INST_SCRIPTS/ssl/install_ssl.sh && rm -rf $INST_SCRIPTS/ssl/ + +# KasmVNC high-quality settings (1080p, 60fps, max quality) +COPY ./src/ubuntu/install/kasmvnc_settings $INST_SCRIPTS/kasmvnc_settings/ +RUN bash $INST_SCRIPTS/kasmvnc_settings/install_kasmvnc_settings.sh && rm -rf $INST_SCRIPTS/kasmvnc_settings/ + +# Install macOS-style theme (Colloid GTK + Plank dock + XFCE panel) +COPY ./src/ubuntu/install/zorin_theme $INST_SCRIPTS/zorin_theme/ +RUN bash $INST_SCRIPTS/zorin_theme/install_zorin_theme.sh && rm -rf $INST_SCRIPTS/zorin_theme/ + +# Install Wine (Windows app compatibility) +COPY ./src/ubuntu/install/wine $INST_SCRIPTS/wine/ +RUN bash $INST_SCRIPTS/wine/install_wine.sh && rm -rf $INST_SCRIPTS/wine/ + +# Install Utilities +COPY ./src/ubuntu/install/misc $INST_SCRIPTS/misc/ +RUN bash $INST_SCRIPTS/misc/install_tools.sh && rm -rf $INST_SCRIPTS/misc/ + +# Install Firefox +COPY ./src/ubuntu/install/firefox/ $INST_SCRIPTS/firefox/ +COPY ./src/ubuntu/install/firefox/firefox.desktop $HOME/Desktop/ +RUN bash $INST_SCRIPTS/firefox/install_firefox.sh && rm -rf $INST_SCRIPTS/firefox/ + +# Install Google Chrome +COPY ./src/ubuntu/install/chrome $INST_SCRIPTS/chrome/ +RUN bash $INST_SCRIPTS/chrome/install_chrome.sh && rm -rf $INST_SCRIPTS/chrome/ + +### Install CareerClaw AI Assistant (OpenClaw fork) +COPY ./src/ubuntu/install/careerclaw $INST_SCRIPTS/careerclaw/ +RUN bash $INST_SCRIPTS/careerclaw/install_careerclaw.sh && rm -rf $INST_SCRIPTS/careerclaw/ +# CareerClaw custom startup (gateway respawn loop) +COPY ./src/ubuntu/install/careerclaw/custom_startup.sh $STARTUPDIR/custom_startup.sh +RUN chmod +x $STARTUPDIR/custom_startup.sh +RUN chmod 755 $STARTUPDIR/custom_startup.sh + +######### End Customizations ########### + +RUN $STARTUPDIR/set_user_permission.sh $HOME +RUN chown 1000:0 $HOME + +ENV HOME=/home/kasm-user +WORKDIR $HOME +RUN mkdir -p $HOME && chown -R 1000:0 $HOME + +USER 1000 + +EXPOSE 18789 +CMD ["--tail-log"] diff --git a/dockerfile-kasm-zorin-deluxe b/dockerfile-kasm-zorin-deluxe new file mode 100644 index 000000000..676cec977 --- /dev/null +++ b/dockerfile-kasm-zorin-deluxe @@ -0,0 +1,112 @@ +ARG BASE_TAG="develop" +ARG BASE_IMAGE="core-ubuntu-jammy" +FROM kasmweb/$BASE_IMAGE:$BASE_TAG + +USER root + +ENV HOME=/home/kasm-default-profile +ENV STARTUPDIR=/dockerstartup +WORKDIR $HOME + +### Environment config +ENV DEBIAN_FRONTEND=noninteractive +ENV KASM_RX_HOME=$STARTUPDIR/kasmrx +ENV INST_SCRIPTS=$STARTUPDIR/install +ENV DONT_PROMPT_WSL_INSTALL="No_Prompt_please" + +### KasmVNC quality: 60fps, balanced quality for smooth performance +ENV MAX_FRAME_RATE=60 +ENV VNCOPTIONS="-DynamicQualityMin 6 -DynamicQualityMax 9 -TreatLossless 9 -JpegVideoQuality 7 -WebpVideoQuality 7 -VideoScaling 2" + +######### Customize Container Here ########### + +# SSL: Generate CA-signed certs so browsers trust the HTTPS connection +COPY ./src/ubuntu/install/ssl $INST_SCRIPTS/ssl/ +RUN bash $INST_SCRIPTS/ssl/install_ssl.sh && rm -rf $INST_SCRIPTS/ssl/ + +# KasmVNC high-quality settings (1080p, 60fps, max quality) +COPY ./src/ubuntu/install/kasmvnc_settings $INST_SCRIPTS/kasmvnc_settings/ +RUN bash $INST_SCRIPTS/kasmvnc_settings/install_kasmvnc_settings.sh && rm -rf $INST_SCRIPTS/kasmvnc_settings/ + +# Install macOS-style theme (Colloid GTK + Plank dock + XFCE panel) +COPY ./src/ubuntu/install/zorin_theme $INST_SCRIPTS/zorin_theme/ +RUN bash $INST_SCRIPTS/zorin_theme/install_zorin_theme.sh && rm -rf $INST_SCRIPTS/zorin_theme/ + +# Install Wine (Windows app compatibility) +COPY ./src/ubuntu/install/wine $INST_SCRIPTS/wine/ +RUN bash $INST_SCRIPTS/wine/install_wine.sh && rm -rf $INST_SCRIPTS/wine/ + +### Install Tools +COPY ./src/ubuntu/install/tools $INST_SCRIPTS/tools/ +RUN bash $INST_SCRIPTS/tools/install_tools_deluxe.sh && rm -rf $INST_SCRIPTS/tools/ + +# Install Utilities +COPY ./src/ubuntu/install/misc $INST_SCRIPTS/misc/ +RUN bash $INST_SCRIPTS/misc/install_tools.sh && rm -rf $INST_SCRIPTS/misc/ + +# Install Google Chrome +COPY ./src/ubuntu/install/chrome $INST_SCRIPTS/chrome/ +RUN bash $INST_SCRIPTS/chrome/install_chrome.sh && rm -rf $INST_SCRIPTS/chrome/ + +# Install Firefox +COPY ./src/ubuntu/install/firefox/ $INST_SCRIPTS/firefox/ +COPY ./src/ubuntu/install/firefox/firefox.desktop $HOME/Desktop/ +RUN bash $INST_SCRIPTS/firefox/install_firefox.sh && rm -rf $INST_SCRIPTS/firefox/ + +### Install Sublime Text +COPY ./src/ubuntu/install/sublime_text $INST_SCRIPTS/sublime_text/ +RUN bash $INST_SCRIPTS/sublime_text/install_sublime_text.sh && rm -rf $INST_SCRIPTS/sublime_text/ + +### Install Visual Studio Code +COPY ./src/ubuntu/install/vs_code $INST_SCRIPTS/vs_code/ +RUN bash $INST_SCRIPTS/vs_code/install_vs_code.sh && rm -rf $INST_SCRIPTS/vs_code/ + +### Install Only Office +COPY ./src/ubuntu/install/only_office $INST_SCRIPTS/only_office/ +RUN bash $INST_SCRIPTS/only_office/install_only_office.sh && rm -rf $INST_SCRIPTS/only_office/ + +### Install GIMP +COPY ./src/ubuntu/install/gimp $INST_SCRIPTS/gimp/ +RUN bash $INST_SCRIPTS/gimp/install_gimp.sh && rm -rf $INST_SCRIPTS/gimp/ + +### Install Thunderbird +COPY ./src/ubuntu/install/thunderbird $INST_SCRIPTS/thunderbird/ +RUN bash $INST_SCRIPTS/thunderbird/install_thunderbird.sh && rm -rf $INST_SCRIPTS/thunderbird/ + +### Install Remmina +COPY ./src/ubuntu/install/remmina $INST_SCRIPTS/remmina/ +RUN bash $INST_SCRIPTS/remmina/install_remmina.sh && rm -rf $INST_SCRIPTS/remmina/ + +### Install CareerClaw AI Assistant (OpenClaw fork) +COPY ./src/ubuntu/install/careerclaw $INST_SCRIPTS/careerclaw/ +RUN bash $INST_SCRIPTS/careerclaw/install_careerclaw.sh && rm -rf $INST_SCRIPTS/careerclaw/ + +# CareerClaw custom startup (gateway respawn loop) +COPY ./src/ubuntu/install/careerclaw/custom_startup.sh $STARTUPDIR/custom_startup.sh +RUN chmod +x $STARTUPDIR/custom_startup.sh +RUN chmod 755 $STARTUPDIR/custom_startup.sh + +# Add extra dock items for deluxe apps +RUN PLANK_DIR="$HOME/.config/plank/dock1/launchers" && \ + echo "[PlankDockItemPreferences]" > "$PLANK_DIR/chrome.dockitem" && \ + echo "Launcher=file:///usr/share/applications/google-chrome.desktop" >> "$PLANK_DIR/chrome.dockitem" && \ + echo "[PlankDockItemPreferences]" > "$PLANK_DIR/vscode.dockitem" && \ + echo "Launcher=file:///usr/share/applications/code.desktop" >> "$PLANK_DIR/vscode.dockitem" && \ + echo "[PlankDockItemPreferences]" > "$PLANK_DIR/gimp.dockitem" && \ + echo "Launcher=file:///usr/share/applications/gimp.desktop" >> "$PLANK_DIR/gimp.dockitem" && \ + sed -i 's/^DockItems=.*/DockItems=files.dockitem;firefox.dockitem;chrome.dockitem;vscode.dockitem;terminal.dockitem;gimp.dockitem;careerclaw.dockitem/' "$HOME/.config/plank/dock1/settings" + +######### End Customizations ########### + +RUN $STARTUPDIR/set_user_permission.sh $HOME + +RUN chown 1000:0 $HOME + +ENV HOME=/home/kasm-user +WORKDIR $HOME +RUN mkdir -p $HOME && chown -R 1000:0 $HOME + +USER 1000 + +EXPOSE 18789 +CMD ["--tail-log"] diff --git a/dockerfile-kasm-zsnes b/dockerfile-kasm-zsnes index 9444eb3ee..6844aee89 100644 --- a/dockerfile-kasm-zsnes +++ b/dockerfile-kasm-zsnes @@ -1,5 +1,5 @@ ARG BASE_TAG="develop" -ARG BASE_IMAGE="core-ubuntu-focal" +ARG BASE_IMAGE="core-ubuntu-jammy" FROM kasmweb/$BASE_IMAGE:$BASE_TAG USER root @@ -21,7 +21,7 @@ RUN chmod 755 $STARTUPDIR/custom_startup.sh # Update the desktop environment to be optimized for a single application RUN cp $HOME/.config/xfce4/xfconf/single-application-xfce-perchannel-xml/* $HOME/.config/xfce4/xfconf/xfce-perchannel-xml/ -RUN cp /usr/share/extra/backgrounds/bg_kasm.png /usr/share/extra/backgrounds/bg_default.png +RUN cp /usr/share/backgrounds/bg_kasm.png /usr/share/backgrounds/bg_default.png RUN apt-get remove -y xfce4-panel ######### End Customizations ########### diff --git a/dockerfile-kasmos-desktop b/dockerfile-kasmos-desktop new file mode 100644 index 000000000..1a1ff4a8b --- /dev/null +++ b/dockerfile-kasmos-desktop @@ -0,0 +1,52 @@ +ARG BASE_TAG="develop" +ARG BASE_IMAGE="core-kasmos" +FROM kasmweb/$BASE_IMAGE:$BASE_TAG + +USER root + +ENV HOME /home/kasm-default-profile +ENV STARTUPDIR /dockerstartup +WORKDIR $HOME + +### Envrionment config +ENV DEBIAN_FRONTEND=noninteractive \ + SKIP_CLEAN=true \ + KASM_RX_HOME=$STARTUPDIR/kasmrx \ + DONT_PROMPT_WSL_INSTALL="No_Prompt_please" \ + INST_DIR=$STARTUPDIR/install \ + INST_SCRIPTS="/ubuntu/install/misc/install_tools.sh \ + /kasmos/install/browser/install_browser.sh \ + /ubuntu/install/vs_code/install_vs_code.sh \ + /ubuntu/install/remmina/install_remmina.sh \ + /kasmos/install/office/install_office_app.sh \ + /ubuntu/install/zoom/install_zoom.sh \ + /ubuntu/install/thunderbird/install_thunderbird.sh \ + /ubuntu/install/slack/install_slack.sh \ + /ubuntu/install/gamepad_utils/install_gamepad_utils.sh \ + /ubuntu/install/cleanup/cleanup.sh" + +# Copy install scripts +COPY ./src/ $INST_DIR + +# Run installations +RUN \ + for SCRIPT in $INST_SCRIPTS; do \ + bash ${INST_DIR}${SCRIPT} || exit 1; \ + done && \ + $STARTUPDIR/set_user_permission.sh $HOME && \ + rm -f /etc/X11/xinit/Xclients && \ + chown 1000:0 $HOME && \ + mkdir -p /home/kasm-user && \ + chown -R 1000:0 /home/kasm-user && \ + rm -Rf ${INST_DIR} + +# Remove desktop shortcuts +RUN rm $HOME/Desktop/*.desktop \ + && sed -i 's#inode/directory;##g' /usr/share/applications/code.desktop + +# Userspace Runtime +ENV HOME /home/kasm-user +WORKDIR $HOME +USER 1000 + +CMD ["--tail-log"] \ No newline at end of file diff --git a/docs/alpine-317-desktop/description.txt b/docs/alpine-317-desktop/description.txt deleted file mode 100644 index bbdabab8e..000000000 --- a/docs/alpine-317-desktop/description.txt +++ /dev/null @@ -1 +0,0 @@ -Alpine 3.17 desktop for Kasm Workspaces diff --git a/docs/alpine-317-desktop/README.md b/docs/alpine-319-desktop/README.md similarity index 78% rename from docs/alpine-317-desktop/README.md rename to docs/alpine-319-desktop/README.md index 9989c0c53..aa37479cf 100644 --- a/docs/alpine-317-desktop/README.md +++ b/docs/alpine-319-desktop/README.md @@ -1,6 +1,6 @@ # About This Image -This Image contains a browser-accessible Alpine 3.17 Desktop with various productivity and development apps installed. +This Image contains a browser-accessible Alpine 3.19 Desktop with various productivity and development apps installed. ![Screenshot][Image_Screenshot] diff --git a/docs/alpine-317-desktop/demo.txt b/docs/alpine-319-desktop/demo.txt similarity index 100% rename from docs/alpine-317-desktop/demo.txt rename to docs/alpine-319-desktop/demo.txt diff --git a/docs/alpine-319-desktop/description.txt b/docs/alpine-319-desktop/description.txt new file mode 100644 index 000000000..ea19919cb --- /dev/null +++ b/docs/alpine-319-desktop/description.txt @@ -0,0 +1 @@ +Alpine 3.19 desktop for Kasm Workspaces diff --git a/docs/alpine-320-desktop/README.md b/docs/alpine-320-desktop/README.md new file mode 100644 index 000000000..18cf9d623 --- /dev/null +++ b/docs/alpine-320-desktop/README.md @@ -0,0 +1,7 @@ +# About This Image + +This Image contains a browser-accessible Alpine 3.20 Desktop with various productivity and development apps installed. + +![Screenshot][Image_Screenshot] + +[Image_Screenshot]: https://info.kasmweb.com/hubfs/dockerhub/image-screenshots/alpine-317-desktop.png "Image Screenshot" diff --git a/docs/fedora-37-desktop/demo.txt b/docs/alpine-320-desktop/demo.txt similarity index 100% rename from docs/fedora-37-desktop/demo.txt rename to docs/alpine-320-desktop/demo.txt diff --git a/docs/alpine-320-desktop/description.txt b/docs/alpine-320-desktop/description.txt new file mode 100644 index 000000000..428fba081 --- /dev/null +++ b/docs/alpine-320-desktop/description.txt @@ -0,0 +1 @@ +Alpine 3.20 desktop for Kasm Workspaces diff --git a/docs/alpine-321-desktop/README.md b/docs/alpine-321-desktop/README.md new file mode 100644 index 000000000..363171382 --- /dev/null +++ b/docs/alpine-321-desktop/README.md @@ -0,0 +1,7 @@ +# About This Image + +This Image contains a browser-accessible Alpine 3.21 Desktop with various productivity and development apps installed. + +![Screenshot][Image_Screenshot] + +[Image_Screenshot]: https://info.kasmweb.com/hubfs/dockerhub/image-screenshots/alpine-317-desktop.png "Image Screenshot" diff --git a/docs/parrotos-5-desktop/demo.txt b/docs/alpine-321-desktop/demo.txt similarity index 100% rename from docs/parrotos-5-desktop/demo.txt rename to docs/alpine-321-desktop/demo.txt diff --git a/docs/alpine-321-desktop/description.txt b/docs/alpine-321-desktop/description.txt new file mode 100644 index 000000000..048d733c9 --- /dev/null +++ b/docs/alpine-321-desktop/description.txt @@ -0,0 +1 @@ +Alpine 3.21 desktop for Kasm Workspaces diff --git a/docs/centos-7-desktop/README.md b/docs/centos-7-desktop/README.md deleted file mode 100644 index 9778b1a1b..000000000 --- a/docs/centos-7-desktop/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# About This Image - -This Image contains a browser-accessible CentOS 7 XFCE Desktop with Chrome and Firefox installed.. - -![Screenshot][Image_Screenshot] - -[Image_Screenshot]: https://f.hubspotusercontent30.net/hubfs/5856039/dockerhub/image-screenshots/centos-7-desktop.png "Image Screenshot" \ No newline at end of file diff --git a/docs/centos-7-desktop/demo.txt b/docs/centos-7-desktop/demo.txt deleted file mode 100644 index 5f13f4d21..000000000 --- a/docs/centos-7-desktop/demo.txt +++ /dev/null @@ -1,9 +0,0 @@ -# Live Demo - - - -**Launch a real-time demo in a new browser window:** Live Demo. - - - -∗*Note: Demo is limited to 3 minutes and has upload/downloads restricted for security purposes.* diff --git a/docs/centos-7-desktop/description.txt b/docs/centos-7-desktop/description.txt deleted file mode 100644 index 5bf64d8e5..000000000 --- a/docs/centos-7-desktop/description.txt +++ /dev/null @@ -1 +0,0 @@ -CentOS 7 desktop for Kasm Workspaces \ No newline at end of file diff --git a/docs/cyberbro/README.md b/docs/cyberbro/README.md new file mode 100644 index 000000000..8522702f2 --- /dev/null +++ b/docs/cyberbro/README.md @@ -0,0 +1,42 @@ +# About This Image + +This Image contains a browser-accessible version of [Cyberbro](https://github.com/stanfrbd/cyberbro). + +![Screenshot][Image_Screenshot] + +[Image_Screenshot]: https://github.com/user-attachments/assets/f6ffb648-e161-4c59-9359-51183b0b0ca0 "Image Screenshot" + +# Environment Variables + +## Firefox Configuration + +* `APP_ARGS` - Additional arguments to pass to firefox when launched (e.g `--no-sandbox`). + +## Cyberbro Configuration + +Here is a list of all available environment variables that can be used with examples: + +```bash +PROXY_URL=http://127.0.0.1:9000 +VIRUSTOTAL=api_key_here +ABUSEIPDB=api_key_here +IPINFO=api_key_here +GOOGLE_SAFE_BROWSING=api_key_here +MDE_TENANT_ID=api_key_here +MDE_CLIENT_ID=api_key_here +MDE_CLIENT_SECRET=api_key_here +SHODAN=api_key_here +OPENCTI_API_KEY=api_key_here +OPENCTI_URL=https://demo.opencti.io +API_PREFIX=my_api +GUI_ENABLED_ENGINES=reverse_dns,rdap,hudsonrock,mde,shodan,opencti,virustotal +CONFIG_PAGE_ENABLED=true +``` + +You can pass these environment variables to your Cyberbro Workspace with **Docker Run Config Override (JSON)** in your Workspace settings. + + +> Note: if you set `GUI_ENABLED_ENGINES` to `""` then all engines will be enabled in the GUI. \ +> By default, all **free engines** will be enabled in the GUI. + +Refer to [Cyberbro Wiki](https://github.com/stanfrbd/cyberbro/wiki) for more information. \ No newline at end of file diff --git a/docs/cyberbro/demo.txt b/docs/cyberbro/demo.txt new file mode 100644 index 000000000..f46463d73 --- /dev/null +++ b/docs/cyberbro/demo.txt @@ -0,0 +1,9 @@ +# Live Demo + + + +**Launch a real-time demo in a new browser window:** Live Demo. + + + +∗*Note: Demo is limited to 3 minutes and has upload/downloads restricted for security purposes.* diff --git a/docs/cyberbro/description.txt b/docs/cyberbro/description.txt new file mode 100644 index 000000000..a14e442ee --- /dev/null +++ b/docs/cyberbro/description.txt @@ -0,0 +1 @@ +Cyberbro for Kasm Workspaces \ No newline at end of file diff --git a/docs/debian-bookworm-desktop/README.md b/docs/debian-bookworm-desktop/README.md new file mode 100644 index 000000000..23f3db110 --- /dev/null +++ b/docs/debian-bookworm-desktop/README.md @@ -0,0 +1,18 @@ +# About This Image + +This Image contains a browser-accessible Debian Bookworm Desktop with various productivity and development apps installed. + +![Screenshot][Image_Screenshot] + +[Image_Screenshot]: https://info.kasmweb.com/hubfs/dockerhub/image-screenshots/debian-bullseye-desktop.png "Image Screenshot" + + +This image contains the Signal app pre-installed. Signal enforces strict security rules that block network access if it detects SSL inspection (MitM). To ensure Signal functions correctly when **WebFilter** is enabled, you must add these domains to the **SSL Bypass Domains** list in your **WebFilter configuration**: (Notice the preceding dot (.) that ensures all subdomains are also bypassed) +``` +.signal.org +.signal.art +.signal.tube +.signal.group +.signal.link +.signal.me +``` diff --git a/docs/remnux-focal-desktop/demo.txt b/docs/debian-bookworm-desktop/demo.txt similarity index 100% rename from docs/remnux-focal-desktop/demo.txt rename to docs/debian-bookworm-desktop/demo.txt diff --git a/docs/debian-bookworm-desktop/description.txt b/docs/debian-bookworm-desktop/description.txt new file mode 100644 index 000000000..eaf38aff2 --- /dev/null +++ b/docs/debian-bookworm-desktop/description.txt @@ -0,0 +1 @@ +Debian Bookworm desktop for Kasm Workspaces diff --git a/docs/debian-bullseye-desktop/README.md b/docs/debian-bullseye-desktop/README.md index b7a35f012..b162cdfb1 100644 --- a/docs/debian-bullseye-desktop/README.md +++ b/docs/debian-bullseye-desktop/README.md @@ -5,3 +5,13 @@ This Image contains a browser-accessible Debian Bullseye Desktop with various pr ![Screenshot][Image_Screenshot] [Image_Screenshot]: https://info.kasmweb.com/hubfs/dockerhub/image-screenshots/debian-bullseye-desktop.png "Image Screenshot" + +This image contains the Signal app pre-installed. Signal enforces strict security rules that block network access if it detects SSL inspection (MitM). To ensure Signal functions correctly when **WebFilter** is enabled, you must add these domains to the **SSL Bypass Domains** list in your **WebFilter configuration**: (Notice the preceding dot (.) that ensures all subdomains are also bypassed) +``` +.signal.org +.signal.art +.signal.tube +.signal.group +.signal.link +.signal.me +``` \ No newline at end of file diff --git a/docs/debian-trixie-desktop/README.md b/docs/debian-trixie-desktop/README.md new file mode 100644 index 000000000..48e789c19 --- /dev/null +++ b/docs/debian-trixie-desktop/README.md @@ -0,0 +1,18 @@ +# About This Image + +This Image contains a browser-accessible Debian Trixie Desktop with various productivity and development apps installed. + +![Screenshot][Image_Screenshot] + +[Image_Screenshot]: https://info.kasmweb.com/hubfs/dockerhub/image-screenshots/debian-bullseye-desktop.png "Image Screenshot" + + +This image contains the Signal app pre-installed. Signal enforces strict security rules that block network access if it detects SSL inspection (MitM). To ensure Signal functions correctly when **WebFilter** is enabled, you must add these domains to the **SSL Bypass Domains** list in your **WebFilter configuration**: (Notice the preceding dot (.) that ensures all subdomains are also bypassed) +``` +.signal.org +.signal.art +.signal.tube +.signal.group +.signal.link +.signal.me +``` diff --git a/docs/debian-trixie-desktop/demo.txt b/docs/debian-trixie-desktop/demo.txt new file mode 100644 index 000000000..0b606c7ea --- /dev/null +++ b/docs/debian-trixie-desktop/demo.txt @@ -0,0 +1,9 @@ +# Live Demo + + + +**Launch a real-time demo in a new browser window:** Live Demo. + + + +∗*Note: Demo is limited to 3 minutes and has upload/downloads restricted for security purposes.* diff --git a/docs/debian-trixie-desktop/description.txt b/docs/debian-trixie-desktop/description.txt new file mode 100644 index 000000000..28c081ae1 --- /dev/null +++ b/docs/debian-trixie-desktop/description.txt @@ -0,0 +1 @@ +Debian Trixie desktop for Kasm Workspaces diff --git a/docs/desktop-deluxe/README.md b/docs/desktop-deluxe/README.md index e15266812..915781bb4 100644 --- a/docs/desktop-deluxe/README.md +++ b/docs/desktop-deluxe/README.md @@ -1,7 +1,17 @@ # About This Image -This Image contains a browser-accessible Ubuntu Bionic Desktop with various productivity and development apps installed. +This Image contains a browser-accessible Ubuntu Jammy Desktop with various productivity and development apps installed. ![Screenshot][Image_Screenshot] -[Image_Screenshot]: https://f.hubspotusercontent30.net/hubfs/5856039/dockerhub/image-screenshots/desktop-deluxe.png "Image Screenshot" \ No newline at end of file +[Image_Screenshot]: https://f.hubspotusercontent30.net/hubfs/5856039/dockerhub/image-screenshots/desktop-deluxe.png "Image Screenshot" + +This image contains the Signal app pre-installed. Signal enforces strict security rules that block network access if it detects SSL inspection (MitM). To ensure Signal functions correctly when **WebFilter** is enabled, you must add these domains to the **SSL Bypass Domains** list in your **WebFilter configuration**: (Notice the preceding dot (.) that ensures all subdomains are also bypassed) +``` +.signal.org +.signal.art +.signal.tube +.signal.group +.signal.link +.signal.me +``` \ No newline at end of file diff --git a/docs/desktop/README.md b/docs/desktop/README.md index 181ca8fda..7a4bd4069 100644 --- a/docs/desktop/README.md +++ b/docs/desktop/README.md @@ -1,6 +1,6 @@ # About This Image -This Image contains a browser-accessible Ubuntu Bionic Desktop with Chrome and Firefox installed. +This Image contains a browser-accessible Ubuntu Jammy Desktop with Chrome and Firefox installed. ![Screenshot][Image_Screenshot] diff --git a/docs/fedora-37-desktop/description.txt b/docs/fedora-37-desktop/description.txt deleted file mode 100644 index 1b29538f5..000000000 --- a/docs/fedora-37-desktop/description.txt +++ /dev/null @@ -1 +0,0 @@ -Fedora 37 desktop for Kasm Workspaces diff --git a/docs/fedora-37-desktop/README.md b/docs/fedora-39-desktop/README.md similarity index 78% rename from docs/fedora-37-desktop/README.md rename to docs/fedora-39-desktop/README.md index b71943a81..5f371a0f9 100644 --- a/docs/fedora-37-desktop/README.md +++ b/docs/fedora-39-desktop/README.md @@ -1,6 +1,6 @@ # About This Image -This Image contains a browser-accessible Fedora 37 Desktop with various productivity and development apps installed. +This Image contains a browser-accessible Fedora 39 Desktop with various productivity and development apps installed. ![Screenshot][Image_Screenshot] diff --git a/docs/fedora-39-desktop/demo.txt b/docs/fedora-39-desktop/demo.txt new file mode 100644 index 000000000..0b606c7ea --- /dev/null +++ b/docs/fedora-39-desktop/demo.txt @@ -0,0 +1,9 @@ +# Live Demo + + + +**Launch a real-time demo in a new browser window:** Live Demo. + + + +∗*Note: Demo is limited to 3 minutes and has upload/downloads restricted for security purposes.* diff --git a/docs/fedora-39-desktop/description.txt b/docs/fedora-39-desktop/description.txt new file mode 100644 index 000000000..a649ce6c9 --- /dev/null +++ b/docs/fedora-39-desktop/description.txt @@ -0,0 +1 @@ +Fedora 39 desktop for Kasm Workspaces diff --git a/docs/fedora-40-desktop/README.md b/docs/fedora-40-desktop/README.md new file mode 100644 index 000000000..2ea974b18 --- /dev/null +++ b/docs/fedora-40-desktop/README.md @@ -0,0 +1,7 @@ +# About This Image + +This Image contains a browser-accessible Fedora 40 Desktop with various productivity and development apps installed. + +![Screenshot][Image_Screenshot] + +[Image_Screenshot]: https://info.kasmweb.com/hubfs/dockerhub/image-screenshots/fedora-37-desktop.png "Image Screenshot" diff --git a/docs/fedora-40-desktop/demo.txt b/docs/fedora-40-desktop/demo.txt new file mode 100644 index 000000000..0b606c7ea --- /dev/null +++ b/docs/fedora-40-desktop/demo.txt @@ -0,0 +1,9 @@ +# Live Demo + + + +**Launch a real-time demo in a new browser window:** Live Demo. + + + +∗*Note: Demo is limited to 3 minutes and has upload/downloads restricted for security purposes.* diff --git a/docs/fedora-40-desktop/description.txt b/docs/fedora-40-desktop/description.txt new file mode 100644 index 000000000..b88a8b7f8 --- /dev/null +++ b/docs/fedora-40-desktop/description.txt @@ -0,0 +1 @@ +Fedora 40 desktop for Kasm Workspaces diff --git a/docs/fedora-41-desktop/README.md b/docs/fedora-41-desktop/README.md new file mode 100644 index 000000000..7a5772242 --- /dev/null +++ b/docs/fedora-41-desktop/README.md @@ -0,0 +1,7 @@ +# About This Image + +This Image contains a browser-accessible Fedora 41 Desktop with various productivity and development apps installed. + +![Screenshot][Image_Screenshot] + +[Image_Screenshot]: https://info.kasmweb.com/hubfs/dockerhub/image-screenshots/fedora-37-desktop.png "Image Screenshot" diff --git a/docs/fedora-41-desktop/demo.txt b/docs/fedora-41-desktop/demo.txt new file mode 100644 index 000000000..0b606c7ea --- /dev/null +++ b/docs/fedora-41-desktop/demo.txt @@ -0,0 +1,9 @@ +# Live Demo + + + +**Launch a real-time demo in a new browser window:** Live Demo. + + + +∗*Note: Demo is limited to 3 minutes and has upload/downloads restricted for security purposes.* diff --git a/docs/fedora-41-desktop/description.txt b/docs/fedora-41-desktop/description.txt new file mode 100644 index 000000000..4c9e41e21 --- /dev/null +++ b/docs/fedora-41-desktop/description.txt @@ -0,0 +1 @@ +Fedora 41 desktop for Kasm Workspaces diff --git a/docs/forensic-osint/README.md b/docs/forensic-osint/README.md new file mode 100644 index 000000000..00aa0aecc --- /dev/null +++ b/docs/forensic-osint/README.md @@ -0,0 +1,20 @@ +# About This Image + +This Image contains an Ubuntu desktop with Google Chrome, and [Forensic OSINT](https://www.forensicosint.com/) Chrome Extension pre-configured. +![Screenshot][Image_Screenshot] + +[Image_Screenshot]: https://5856039.fs1.hubspotusercontent-na1.net/hubfs/5856039/dockerhub/image-screenshots/forensic-osint.png "Image Screenshot" + +This image contains the Signal app pre-installed. Signal enforces strict security rules that block network access if it detects SSL inspection (MitM). To ensure Signal functions correctly when **WebFilter** is enabled, you must add these domains to the **SSL Bypass Domains** list in your **WebFilter configuration**: (Notice the preceding dot (.) that ensures all subdomains are also bypassed) +``` +.signal.org +.signal.art +.signal.tube +.signal.group +.signal.link +.signal.me +``` + +# Environment Variables + +* `APP_ARGS` - Additional arguments to pass to the application when launched. \ No newline at end of file diff --git a/docs/forensic-osint/demo.txt b/docs/forensic-osint/demo.txt new file mode 100644 index 000000000..b1c682b87 --- /dev/null +++ b/docs/forensic-osint/demo.txt @@ -0,0 +1,8 @@ + +# Live Demo + +**Launch a real-time demo in a new browser window:** Live Demo. + + + +∗*Note: Demo is limited to 3 minutes and has upload/downloads restricted for security purposes.* diff --git a/docs/forensic-osint/description.txt b/docs/forensic-osint/description.txt new file mode 100644 index 000000000..721a9d4e2 --- /dev/null +++ b/docs/forensic-osint/description.txt @@ -0,0 +1 @@ +Ubuntu desktop with Google Chrome and Forensic OSINT Chrome Extension \ No newline at end of file diff --git a/docs/java-dev/README.md b/docs/java-dev/README.md index be61ba0c3..438744a28 100644 --- a/docs/java-dev/README.md +++ b/docs/java-dev/README.md @@ -1,6 +1,6 @@ # About This Image -This Image contains a browser-accessible Ubuntu Bionic Desktop with a Java development environment. +This Image contains a browser-accessible Ubuntu Jammy Desktop with a Java development environment. ![Screenshot][Image_Screenshot] diff --git a/docs/kasmos-desktop/README.md b/docs/kasmos-desktop/README.md new file mode 100644 index 000000000..cb730323f --- /dev/null +++ b/docs/kasmos-desktop/README.md @@ -0,0 +1,7 @@ +# About This Image + +This Image contains a browser-accessible KasmOS Desktop with various productivity and development apps installed. + +![Screenshot][Image_Screenshot] + +[Image_Screenshot]: https://5856039.fs1.hubspotusercontent-na1.net/hubfs/5856039/dockerhub/image-screenshots/core-kasmos.png "Image Screenshot" diff --git a/docs/kasmos-desktop/demo.txt b/docs/kasmos-desktop/demo.txt new file mode 100644 index 000000000..01bfa1e89 --- /dev/null +++ b/docs/kasmos-desktop/demo.txt @@ -0,0 +1,9 @@ +# Live Demo + +**Launch a real-time demo in a new browser window:** Live Demo. + + + +∗*This demo links to a KasmOS Desktop image to show the basic functionality of Kasm Workspaces.* + +∗*Note: Demo is limited to 3 minutes and has upload/downloads restricted for security purposes.* diff --git a/docs/kasmos-desktop/description.txt b/docs/kasmos-desktop/description.txt new file mode 100644 index 000000000..41d2ff743 --- /dev/null +++ b/docs/kasmos-desktop/description.txt @@ -0,0 +1 @@ +KasmOS is designed to provide a more familiar UI for general productivity use cases. \ No newline at end of file diff --git a/docs/nessus/README.md b/docs/nessus/README.md new file mode 100644 index 000000000..0e57f3d68 --- /dev/null +++ b/docs/nessus/README.md @@ -0,0 +1,11 @@ +# About This Image + +This Image contains a browser-accessible version of [Tennable Nessus](https://www.tenable.com/products/nessus). + +![Screenshot][Image_Screenshot] + +[Image_Screenshot]: https://5856039.fs1.hubspotusercontent-na1.net/hubfs/5856039/dockerhub/image-screenshots/nessus.png "Image Screenshot" + +# Environment Variables + +* `APP_ARGS` - Additional arguments to pass to the application when launched. diff --git a/docs/nessus/demo.txt b/docs/nessus/demo.txt new file mode 100644 index 000000000..ef43ce546 --- /dev/null +++ b/docs/nessus/demo.txt @@ -0,0 +1,9 @@ +# Live Demo + + + +**Launch a real-time demo in a new browser window:** Live Demo. + + + +∗*Note: Demo is limited to 3 minutes and has upload/downloads restricted for security purposes.* diff --git a/docs/nessus/description.txt b/docs/nessus/description.txt new file mode 100644 index 000000000..08d16515f --- /dev/null +++ b/docs/nessus/description.txt @@ -0,0 +1 @@ +Nessus Vulnerability Scanner for Kasm Workspaces diff --git a/docs/obsidian/README.md b/docs/obsidian/README.md new file mode 100644 index 000000000..6c4bc7462 --- /dev/null +++ b/docs/obsidian/README.md @@ -0,0 +1,7 @@ +# About This Image + +This Image contains a browser-accessible version of [Obsidian](https://obsidian.md/). + +![Screenshot][Image_Screenshot] + +[Image_Screenshot]: https://5856039.fs1.hubspotusercontent-na1.net/hubfs/5856039/dockerhub/image-screenshots/obsidian.png "Image Screenshot" \ No newline at end of file diff --git a/docs/obsidian/demo.txt b/docs/obsidian/demo.txt new file mode 100644 index 000000000..248dd4450 --- /dev/null +++ b/docs/obsidian/demo.txt @@ -0,0 +1,9 @@ +# Live Demo + + + +**Launch a real-time demo in a new browser window:** Live Demo. + + + +∗*Note: Demo is limited to 3 minutes and has upload/downloads restricted for security purposes.* diff --git a/docs/obsidian/description.txt b/docs/obsidian/description.txt new file mode 100644 index 000000000..9fa0c8378 --- /dev/null +++ b/docs/obsidian/description.txt @@ -0,0 +1 @@ +Obsidian for Kasm Workspaces diff --git a/docs/oracle-7-desktop/README.md b/docs/oracle-7-desktop/README.md deleted file mode 100644 index a5c268422..000000000 --- a/docs/oracle-7-desktop/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# About This Image - -This Image contains a browser-accessible Oracle Linux 7 Desktop with various productivity and development apps installed. - -![Screenshot][Image_Screenshot] - -[Image_Screenshot]: https://5856039.fs1.hubspotusercontent-na1.net/hubfs/5856039/dockerhub/oracle-7-desktop.png "Image Screenshot" diff --git a/docs/oracle-7-desktop/demo.txt b/docs/oracle-7-desktop/demo.txt deleted file mode 100644 index 4320df5bd..000000000 --- a/docs/oracle-7-desktop/demo.txt +++ /dev/null @@ -1,11 +0,0 @@ -# Live Demo - - - -**Launch a real-time demo in a new browser window:** Live Demo. - - - -∗*This demo is linked to Oracle Linux 8* - -∗*Note: Demo is limited to 3 minutes and has upload/downloads restricted for security purposes.* diff --git a/docs/oracle-7-desktop/description.txt b/docs/oracle-7-desktop/description.txt deleted file mode 100644 index 11d4ea6ee..000000000 --- a/docs/oracle-7-desktop/description.txt +++ /dev/null @@ -1 +0,0 @@ -Oracle Linux 7 desktop for Kasm Workspaces diff --git a/docs/parrotos-5-desktop/description.txt b/docs/parrotos-5-desktop/description.txt deleted file mode 100644 index fd1f48ae5..000000000 --- a/docs/parrotos-5-desktop/description.txt +++ /dev/null @@ -1 +0,0 @@ -Parrot OS 5 desktop for Kasm Workspaces diff --git a/docs/parrotos-5-desktop/README.md b/docs/parrotos-6-desktop/README.md similarity index 78% rename from docs/parrotos-5-desktop/README.md rename to docs/parrotos-6-desktop/README.md index 4397b12a4..1fd23ff16 100644 --- a/docs/parrotos-5-desktop/README.md +++ b/docs/parrotos-6-desktop/README.md @@ -1,6 +1,6 @@ # About This Image -This Image contains a browser-accessible Parrot OS 5 Desktop with various productivity and development apps installed. +This Image contains a browser-accessible Parrot OS 6 Desktop with various productivity and development apps installed. ![Screenshot][Image_Screenshot] diff --git a/docs/parrotos-6-desktop/demo.txt b/docs/parrotos-6-desktop/demo.txt new file mode 100644 index 000000000..0b606c7ea --- /dev/null +++ b/docs/parrotos-6-desktop/demo.txt @@ -0,0 +1,9 @@ +# Live Demo + + + +**Launch a real-time demo in a new browser window:** Live Demo. + + + +∗*Note: Demo is limited to 3 minutes and has upload/downloads restricted for security purposes.* diff --git a/docs/parrotos-6-desktop/description.txt b/docs/parrotos-6-desktop/description.txt new file mode 100644 index 000000000..0779e1e9c --- /dev/null +++ b/docs/parrotos-6-desktop/description.txt @@ -0,0 +1 @@ +Parrot OS 6 desktop for Kasm Workspaces diff --git a/docs/redroid/README.md b/docs/redroid/README.md new file mode 100644 index 000000000..564b635cc --- /dev/null +++ b/docs/redroid/README.md @@ -0,0 +1,58 @@ +# About This Image + +This experimental image contains a browser-accessible version of [Redroid](https://github.com/remote-android/redroid-doc). +redroid (Remote-Android) is a multi-arch, GPU enabled, Android in Cloud solution. + +The image utilizes Docker in Docker (DinD) to automate launching Redroid and [scrcpy docs](https://github.com/Genymobile/scrcpy). + +![Screenshot][Image_Screenshot] + +[Image_Screenshot]: https://f.hubspotusercontent30.net/hubfs/5856039/dockerhub/image-screenshots/redroid.png "Image Screenshot" +## Host Dependencies + +This image requires the "binder_linux" host level kernel modules installed and enabled. + +Below is an example for installing binder_linux on Ubuntu 22.04 LTS host +``` +sudo apt install linux-modules-extra-`uname -r` +sudo modprobe binder_linux devices="binder,hwbinder,vndbinder" +``` +See [Redroid Docs](https://github.com/remote-android/redroid-doc?tab=readme-ov-file#getting-started) for more details. + + +## Container Permissions + +Using this container requires the `--privileged` flag to power both the Docker in Docker processes and the permissions +needed by the redroid containers + +``` +sudo docker run --rm -it --privileged --shm-size=512m -p 6901:6901 -e VNC_PW=password kasmweb/redroid:develop +``` + +## Example for installing binder_linux on Ubuntu 22.04 LTS host +``` +sudo apt install linux-modules-extra-`uname -r` +sudo modprobe binder_linux devices="binder,hwbinder,vndbinder" +``` + +The left ALT key is mapped as the hotkey for scrcpy + +- Alt+R - rotate the android device +- Alt+F - fullscreen the android device +- Alt+Up/Alt+Down - Increase the volume of the device + +See [scrcpy docs](https://github.com/Genymobile/scrcpy) for more details. + + + +# Environment Variables + +* `REDROID_GPU_GUEST_MODE` - Used to instruct redroid to utilize GPU rendering. Options are `auto`, `guest`, and `host` +* `REDROID_FPS` - Set the maximum FPS for redroid and scrcpy. +* `REDROID_WIDTH` - Set the desired width of the redroid device. +* `REDROID_HEIGHT` - Set the desired height of the redroid device. +* `REDROID_DPI` - Set the desired DPI of the redroid device. +* `REDROID_SHOW_CONSOLE` - Display the scrcpy console after launching the redroid device. +* `REDROID_DISABLE_AUTOSTART` - If set to "1", the container will not automatically pull and start the redroid container and scrcpy. +* `REDROID_DISABLE_HOST_CHECKS` - If set to "1", the container will not check for the presence of required host level kernel modules. +* `ANDROID_VERSION` - The version of android (redroid) image to automatically load. Options are `14.0.0`, `13.0.0` (Default), `12.0.0`, `11.0.0`, `10.0.0`, `9.0.0`, `8.1.0`. diff --git a/docs/redroid/demo.txt b/docs/redroid/demo.txt new file mode 100644 index 000000000..152a94978 --- /dev/null +++ b/docs/redroid/demo.txt @@ -0,0 +1,9 @@ +# Live Demo + + + +**Launch a real-time demo in a new browser window:** Live Demo. + + + +∗*Note: Demo is limited to 3 minutes and has upload/downloads restricted for security purposes.* diff --git a/docs/redroid/description.txt b/docs/redroid/description.txt new file mode 100644 index 000000000..0a86cd8d9 --- /dev/null +++ b/docs/redroid/description.txt @@ -0,0 +1 @@ +Redroid (Remote-Android) for Kasm Workspaces diff --git a/docs/remnux-focal-desktop/README.md b/docs/remnux-focal-desktop/README.md deleted file mode 100644 index 6a74d2a07..000000000 --- a/docs/remnux-focal-desktop/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# About This Image - -This Image contains a browser-accessible Remnux Focal Desktop with various productivity and development apps installed. - -![Screenshot][Image_Screenshot] - -[Image_Screenshot]: https://info.kasmweb.com/hubfs/dockerhub/image-screenshots/remnux-focal-desktop.png "Image Screenshot" diff --git a/docs/remnux-focal-desktop/description.txt b/docs/remnux-focal-desktop/description.txt deleted file mode 100644 index 64b3eb18d..000000000 --- a/docs/remnux-focal-desktop/description.txt +++ /dev/null @@ -1 +0,0 @@ -Remnux Focal desktop for Kasm Workspaces diff --git a/docs/rhel-9-desktop/README.md b/docs/rhel-9-desktop/README.md new file mode 100644 index 000000000..c704f8d79 --- /dev/null +++ b/docs/rhel-9-desktop/README.md @@ -0,0 +1,7 @@ +# About This Image + +This Image contains a browser-accessible Red Hat Linux 9 Desktop with various productivity and development apps installed. + +![Screenshot][Image_Screenshot] + +[Image_Screenshot]: https://info.kasmweb.com/hubfs/dockerhub/rhel-9-desktop.png "Image Screenshot" diff --git a/docs/rhel-9-desktop/demo.txt b/docs/rhel-9-desktop/demo.txt new file mode 100644 index 000000000..0b606c7ea --- /dev/null +++ b/docs/rhel-9-desktop/demo.txt @@ -0,0 +1,9 @@ +# Live Demo + + + +**Launch a real-time demo in a new browser window:** Live Demo. + + + +∗*Note: Demo is limited to 3 minutes and has upload/downloads restricted for security purposes.* diff --git a/docs/rhel-9-desktop/description.txt b/docs/rhel-9-desktop/description.txt new file mode 100644 index 000000000..16e206023 --- /dev/null +++ b/docs/rhel-9-desktop/description.txt @@ -0,0 +1 @@ +Red Hat Linux 9 desktop for Kasm Workspaces diff --git a/docs/screenshots/claude-setup.png b/docs/screenshots/claude-setup.png new file mode 100644 index 000000000..338a6d56c Binary files /dev/null and b/docs/screenshots/claude-setup.png differ diff --git a/docs/screenshots/dashboard.png b/docs/screenshots/dashboard.png new file mode 100644 index 000000000..2df8fde74 Binary files /dev/null and b/docs/screenshots/dashboard.png differ diff --git a/docs/screenshots/setup-welcome.png b/docs/screenshots/setup-welcome.png new file mode 100644 index 000000000..aee762cd5 Binary files /dev/null and b/docs/screenshots/setup-welcome.png differ diff --git a/docs/signal/README.md b/docs/signal/README.md index c348792d1..944156c4a 100644 --- a/docs/signal/README.md +++ b/docs/signal/README.md @@ -6,6 +6,16 @@ This Image contains a browser-accessible version of [Signal](https://signal.org/ [Image_Screenshot]: https://f.hubspotusercontent30.net/hubfs/5856039/dockerhub/image-screenshots/signal.png "Image Screenshot" +This image contains the Signal app pre-installed. Signal enforces strict security rules that block network access if it detects SSL inspection (MitM). To ensure Signal functions correctly when **WebFilter** is enabled, you must add these domains to the **SSL Bypass Domains** list in your **WebFilter configuration**: (Notice the preceding dot (.) that ensures all subdomains are also bypassed) +``` +.signal.org +.signal.art +.signal.tube +.signal.group +.signal.link +.signal.me +``` + # Environment Variables * `APP_ARGS` - Additional arguments to pass to the application when launched. diff --git a/docs/spiderfoot/README.md b/docs/spiderfoot/README.md new file mode 100644 index 000000000..a0d019249 --- /dev/null +++ b/docs/spiderfoot/README.md @@ -0,0 +1,12 @@ +# About This Image + +This Image contains a browser-accessible version of [Spiderfoot](https://github.com/smicallef/spiderfoot). + +![Screenshot][Image_Screenshot] + +[Image_Screenshot]: https://5856039.fs1.hubspotusercontent-na1.net/hubfs/5856039/dockerhub/image-screenshots/spiderfoot.png "Image Screenshot" + +# Environment Variables + +* `SPIDERFOOT_APP_ARGS` - Additional arguments to pass to spiderfoot when launched. +* `FIREFOX_APP_ARGS` - Additional arguments to pass to firefox when launched. diff --git a/docs/spiderfoot/demo.txt b/docs/spiderfoot/demo.txt new file mode 100644 index 000000000..7e0362537 --- /dev/null +++ b/docs/spiderfoot/demo.txt @@ -0,0 +1,9 @@ +# Live Demo + + + +**Launch a real-time demo in a new browser window:** Live Demo. + + + +∗*Note: Demo is limited to 3 minutes and has upload/downloads restricted for security purposes.* diff --git a/docs/spiderfoot/description.txt b/docs/spiderfoot/description.txt new file mode 100644 index 000000000..ab3d1da83 --- /dev/null +++ b/docs/spiderfoot/description.txt @@ -0,0 +1 @@ +Spiderfoot for Kasm Workspaces \ No newline at end of file diff --git a/docs/ubuntu-focal-desktop/README.md b/docs/ubuntu-focal-desktop/README.md deleted file mode 100644 index 8e2d68180..000000000 --- a/docs/ubuntu-focal-desktop/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# About This Image - -This Image contains a browser-accessible Ubuntu Focal Desktop with various productivity and development apps installed. - -![Screenshot][Image_Screenshot] - -[Image_Screenshot]: https://f.hubspotusercontent30.net/hubfs/5856039/dockerhub/image-screenshots/ubuntu-focal-desktop.png "Image Screenshot" \ No newline at end of file diff --git a/docs/ubuntu-jammy-desktop-vpn/README.md b/docs/ubuntu-jammy-desktop-vpn/README.md new file mode 100644 index 000000000..a4231e558 --- /dev/null +++ b/docs/ubuntu-jammy-desktop-vpn/README.md @@ -0,0 +1,17 @@ +# About This Image + +This Image contains a browser-accessible Ubuntu Jammy Desktop with various productivity, development, and VPN apps installed. + +![Screenshot][Image_Screenshot] + +[Image_Screenshot]: https://5856039.fs1.hubspotusercontent-na1.net/hubfs/5856039/dockerhub/image-screenshots/ubuntu_jammy_desktop.png "Image Screenshot" + +This image contains the Signal app pre-installed. Signal enforces strict security rules that block network access if it detects SSL inspection (MitM). To ensure Signal functions correctly when **WebFilter** is enabled, you must add these domains to the **SSL Bypass Domains** list in your **WebFilter configuration**: (Notice the preceding dot (.) that ensures all subdomains are also bypassed) +``` +.signal.org +.signal.art +.signal.tube +.signal.group +.signal.link +.signal.me +``` diff --git a/docs/ubuntu-focal-desktop/demo.txt b/docs/ubuntu-jammy-desktop-vpn/demo.txt similarity index 82% rename from docs/ubuntu-focal-desktop/demo.txt rename to docs/ubuntu-jammy-desktop-vpn/demo.txt index 5f8e2fdb3..4d5807e69 100644 --- a/docs/ubuntu-focal-desktop/demo.txt +++ b/docs/ubuntu-jammy-desktop-vpn/demo.txt @@ -1,9 +1,9 @@ # Live Demo - + **Launch a real-time demo in a new browser window:** Live Demo. - + ∗*Note: Demo is limited to 3 minutes and has upload/downloads restricted for security purposes.* diff --git a/docs/ubuntu-jammy-desktop-vpn/description.txt b/docs/ubuntu-jammy-desktop-vpn/description.txt new file mode 100644 index 000000000..f977b2aa0 --- /dev/null +++ b/docs/ubuntu-jammy-desktop-vpn/description.txt @@ -0,0 +1 @@ +Ubuntu productivity desktop for Kasm Workspaces with tools for connecting to a VPN provider diff --git a/docs/ubuntu-jammy-desktop/README.md b/docs/ubuntu-jammy-desktop/README.md index 3698b48b0..b375ac940 100644 --- a/docs/ubuntu-jammy-desktop/README.md +++ b/docs/ubuntu-jammy-desktop/README.md @@ -5,3 +5,13 @@ This Image contains a browser-accessible Ubuntu Jammy Desktop with various produ ![Screenshot][Image_Screenshot] [Image_Screenshot]: https://5856039.fs1.hubspotusercontent-na1.net/hubfs/5856039/dockerhub/image-screenshots/ubuntu_jammy_desktop.png "Image Screenshot" + +This image contains the Signal app pre-installed. Signal enforces strict security rules that block network access if it detects SSL inspection (MitM). To ensure Signal functions correctly when **WebFilter** is enabled, you must add these domains to the **SSL Bypass Domains** list in your **WebFilter configuration**: (Notice the preceding dot (.) that ensures all subdomains are also bypassed) +``` +.signal.org +.signal.art +.signal.tube +.signal.group +.signal.link +.signal.me +``` diff --git a/docs/ubuntu-noble-desktop/README.md b/docs/ubuntu-noble-desktop/README.md new file mode 100644 index 000000000..0a4008119 --- /dev/null +++ b/docs/ubuntu-noble-desktop/README.md @@ -0,0 +1,17 @@ +# About This Image + +This Image contains a browser-accessible Ubuntu Noble Desktop with various productivity and development apps installed. + +![Screenshot][Image_Screenshot] + +[Image_Screenshot]: https://5856039.fs1.hubspotusercontent-na1.net/hubfs/5856039/dockerhub/image-screenshots/ubuntu_jammy_desktop.png "Image Screenshot" + +This image contains the Signal app pre-installed. Signal enforces strict security rules that block network access if it detects SSL inspection (MitM). To ensure Signal functions correctly when **WebFilter** is enabled, you must add these domains to the **SSL Bypass Domains** list in your **WebFilter configuration**: (Notice the preceding dot (.) that ensures all subdomains are also bypassed) +``` +.signal.org +.signal.art +.signal.tube +.signal.group +.signal.link +.signal.me +``` diff --git a/docs/ubuntu-noble-desktop/demo.txt b/docs/ubuntu-noble-desktop/demo.txt new file mode 100644 index 000000000..0b606c7ea --- /dev/null +++ b/docs/ubuntu-noble-desktop/demo.txt @@ -0,0 +1,9 @@ +# Live Demo + + + +**Launch a real-time demo in a new browser window:** Live Demo. + + + +∗*Note: Demo is limited to 3 minutes and has upload/downloads restricted for security purposes.* diff --git a/docs/ubuntu-focal-desktop/description.txt b/docs/ubuntu-noble-desktop/description.txt similarity index 100% rename from docs/ubuntu-focal-desktop/description.txt rename to docs/ubuntu-noble-desktop/description.txt diff --git a/docs/ubuntu-focal-dind-rootless/README.md b/docs/ubuntu-noble-dind-rootless/README.md similarity index 71% rename from docs/ubuntu-focal-dind-rootless/README.md rename to docs/ubuntu-noble-dind-rootless/README.md index 82c114254..5e7bc795d 100644 --- a/docs/ubuntu-focal-dind-rootless/README.md +++ b/docs/ubuntu-noble-dind-rootless/README.md @@ -4,7 +4,7 @@ This Image contains a browser-accessible version of [Docker](https://www.docker. ![Screenshot][Image_Screenshot] -[Image_Screenshot]: https://f.hubspotusercontent30.net/hubfs/5856039/dockerhub/ubuntu_dind.jpg "Image Screenshot" +[Image_Screenshot]: https://5856039.fs1.hubspotusercontent-na1.net/hubfs/5856039/dockerhub/image-screenshots/ubuntu-jammy-dind-rootless.png "Image Screenshot" See [Kasm Docs](https://kasmweb.com/docs/latest/how_to/docker_in_kasm.html) for additional setup instructions. diff --git a/docs/ubuntu-focal-dind/demo.txt b/docs/ubuntu-noble-dind-rootless/demo.txt similarity index 65% rename from docs/ubuntu-focal-dind/demo.txt rename to docs/ubuntu-noble-dind-rootless/demo.txt index 7bc9cba67..b218e24ee 100644 --- a/docs/ubuntu-focal-dind/demo.txt +++ b/docs/ubuntu-noble-dind-rootless/demo.txt @@ -1,8 +1,8 @@ # Live Demo -**Launch a real-time demo in a new browser window:** Live Demo. +**Launch a real-time demo in a new browser window:** Live Demo. - + ∗*Docker will not be functional in the demo for security reasons.* diff --git a/docs/ubuntu-focal-dind-rootless/description.txt b/docs/ubuntu-noble-dind-rootless/description.txt similarity index 100% rename from docs/ubuntu-focal-dind-rootless/description.txt rename to docs/ubuntu-noble-dind-rootless/description.txt diff --git a/docs/ubuntu-focal-dind/README.md b/docs/ubuntu-noble-dind/README.md similarity index 70% rename from docs/ubuntu-focal-dind/README.md rename to docs/ubuntu-noble-dind/README.md index 5abddde4b..dff80282a 100644 --- a/docs/ubuntu-focal-dind/README.md +++ b/docs/ubuntu-noble-dind/README.md @@ -4,7 +4,7 @@ This Image contains a browser-accessible version of [Docker](https://www.docker. ![Screenshot][Image_Screenshot] -[Image_Screenshot]: https://f.hubspotusercontent30.net/hubfs/5856039/dockerhub/ubuntu_dind.jpg "Image Screenshot" +[Image_Screenshot]: https://5856039.fs1.hubspotusercontent-na1.net/hubfs/5856039/dockerhub/image-screenshots/ubuntu-jammy-dind.png "Image Screenshot" See [Kasm Docs](https://kasmweb.com/docs/latest/how_to/docker_in_kasm.html) for additional setup instructions. diff --git a/docs/ubuntu-focal-dind-rootless/demo.txt b/docs/ubuntu-noble-dind/demo.txt similarity index 65% rename from docs/ubuntu-focal-dind-rootless/demo.txt rename to docs/ubuntu-noble-dind/demo.txt index 4cb0adce2..ed5ae836f 100644 --- a/docs/ubuntu-focal-dind-rootless/demo.txt +++ b/docs/ubuntu-noble-dind/demo.txt @@ -1,8 +1,8 @@ # Live Demo -**Launch a real-time demo in a new browser window:** Live Demo. +**Launch a real-time demo in a new browser window:** Live Demo. - + ∗*Docker will not be functional in the demo for security reasons.* diff --git a/docs/ubuntu-focal-dind/description.txt b/docs/ubuntu-noble-dind/description.txt similarity index 100% rename from docs/ubuntu-focal-dind/description.txt rename to docs/ubuntu-noble-dind/description.txt diff --git a/docs/zsnes/demo.txt b/docs/zsnes/demo.txt new file mode 100644 index 000000000..0b606c7ea --- /dev/null +++ b/docs/zsnes/demo.txt @@ -0,0 +1,9 @@ +# Live Demo + + + +**Launch a real-time demo in a new browser window:** Live Demo. + + + +∗*Note: Demo is limited to 3 minutes and has upload/downloads restricted for security purposes.* diff --git a/src/alpine/install/chromium/install_chromium.sh b/src/alpine/install/chromium/install_chromium.sh index d9871bc48..9f33bd84a 100644 --- a/src/alpine/install/chromium/install_chromium.sh +++ b/src/alpine/install/chromium/install_chromium.sh @@ -6,11 +6,6 @@ CHROME_ARGS="--password-store=basic --no-sandbox --ignore-gpu-blocklist --user- apk add --no-cache \ chromium -if [ "$(arch)" == "x86_64" ]; then - apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing \ - virtualgl -fi - REAL_BIN=chromium cp /usr/share/applications/chromium.desktop $HOME/Desktop/ @@ -21,13 +16,8 @@ cat >/usr/bin/chromium-browser </usr/lib/firefox/browser/defaults/preferences/vendor.js <|)' '/href.*xpi/ {print $2}' | tr '\n' ' ') +EXTENSION_DIR=/usr/lib/firefox-addons/distribution/extensions/ +mkdir -p ${EXTENSION_DIR} +for LANG in ${LANGS}; do + LANGCODE=$(echo ${LANG} | sed 's/\.xpi//g') + echo "Downloading ${LANG} Language pack" + curl -o \ + ${EXTENSION_DIR}langpack-${LANGCODE}@firefox.mozilla.org.xpi -Ls \ + ${RELEASE_URL}${LANG} +done # Creating a default profile firefox -headless -CreateProfile "kasm $HOME/.mozilla/firefox/kasm" + +# For alpine 3.20 and later, firefox version shows a security nag. Silence it.. +if [ "$(printf '%s\n' 3.20 $(cat /etc/alpine-release) | sort -V | head -n 1)" = "3.20" ]; then + echo 'user_pref("security.sandbox.warn_unprivileged_namespaces", false);' > $HOME/.mozilla/firefox/kasm/user.js + chown 1000:1000 $HOME/.mozilla/firefox/kasm/user.js +fi + + # Generate a certdb to be detected on squid start HOME=/root firefox --headless & mkdir -p /root/.mozilla diff --git a/src/alpine/install/terraform/install_terraform.sh b/src/alpine/install/terraform/install_terraform.sh index 804757f30..81bb08c66 100644 --- a/src/alpine/install/terraform/install_terraform.sh +++ b/src/alpine/install/terraform/install_terraform.sh @@ -1,5 +1,10 @@ #!/usr/bin/env bash set -ex -apk add --no-cache \ - terraform +if grep -q v3.19 /etc/os-release || grep -q v3.20 /etc/os-release || grep -q v3.21 /etc/os-release; then + apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing/ \ + opentofu +else + apk add --no-cache \ + terraform +fi diff --git a/src/common/chrome-managed-policies/urlblocklist.json b/src/common/chrome-managed-policies/urlblocklist.json new file mode 100644 index 000000000..b14804066 --- /dev/null +++ b/src/common/chrome-managed-policies/urlblocklist.json @@ -0,0 +1,3 @@ +{ + "URLBlocklist": ["file://*"] +} \ No newline at end of file diff --git a/src/common/resources/images/bg_remnux.png b/src/common/resources/images/bg_remnux.png new file mode 100644 index 000000000..ab55b4dcb Binary files /dev/null and b/src/common/resources/images/bg_remnux.png differ diff --git a/src/kasmos/install/browser/install_browser.sh b/src/kasmos/install/browser/install_browser.sh new file mode 100644 index 000000000..095029f66 --- /dev/null +++ b/src/kasmos/install/browser/install_browser.sh @@ -0,0 +1,16 @@ +ARCH=$(arch | sed 's/aarch64/arm64/g' | sed 's/x86_64/amd64/g') +if [ "$ARCH" == "amd64" ] ; then + bash ${INST_DIR}/ubuntu/install/chrome/install_chrome.sh + + # Remove default app launchers + rm -f $HOME/Desktop/google-chrome.desktop + rm -f /usr/share/applications/browser.desktop + mv /usr/share/applications/google-chrome.desktop /usr/share/applications/browser.desktop +else + bash ${INST_DIR}/ubuntu/install/chromium/install_chromium.sh + + rm -f $HOME/Desktop/chromium.desktop + rm -f /usr/share/applications/browser.desktop + mv /usr/share/applications/chromium.desktop /usr/share/applications/browser.desktop + +fi \ No newline at end of file diff --git a/src/kasmos/install/office/install_office_app.sh b/src/kasmos/install/office/install_office_app.sh new file mode 100644 index 000000000..f14fc96d7 --- /dev/null +++ b/src/kasmos/install/office/install_office_app.sh @@ -0,0 +1,17 @@ +ARCH=$(arch | sed 's/aarch64/arm64/g' | sed 's/x86_64/amd64/g') +if [ "$ARCH" == "amd64" ] ; then + bash ${INST_DIR}/ubuntu/install/only_office/install_only_office.sh + + # Remove default app launchers + rm -f $HOME/Desktop/onlyoffice-desktopeditors.desktop + rm -f /usr/share/applications/onlyoffice-desktopeditors.desktop + + cp ${INST_DIR}/kasmos/resources/onlyoffice/*.desktop /usr/share/applications/ +else + apt update + apt install -y libreoffice + # Replace built in launcher app shortcuts to launch libreoffice apps + sed -i "s/^Exec=.*/Exec=libreoffice --writer/g" /usr/share/applications/docs-editor.desktop + sed -i "s/^Exec=.*/Exec=libreoffice --calc/g" /usr/share/applications/sheets-editor.desktop + sed -i "s/^Exec=.*/Exec=libreoffice --impress/g" /usr/share/applications/present-editor.desktop +fi \ No newline at end of file diff --git a/src/kasmos/resources/onlyoffice/docs-editor.desktop b/src/kasmos/resources/onlyoffice/docs-editor.desktop new file mode 100644 index 000000000..0b1d5fd9f --- /dev/null +++ b/src/kasmos/resources/onlyoffice/docs-editor.desktop @@ -0,0 +1,21 @@ +[Desktop Entry] +Version=1.0 +Name=Docs +GenericName=Document Editor +Comment=Document Editor +Type=Application +Exec=/usr/bin/onlyoffice-desktopeditors --new=word %U +Terminal=false +Icon=application-msword +Keywords=Text;Document;OpenDocument Text;Microsoft Word;Microsoft Works;odt;doc;docx;rtf; +Categories=Office;WordProcessor;Spreadsheet; +MimeType=application/vnd.oasis.opendocument.text;application/vnd.oasis.opendocument.text-template;application/vnd.oasis.opendocument.text-web;application/vnd.oasis.opendocument.text-master;application/vnd.sun.xml.writer;application/vnd.sun.xml.writer.template;application/vnd.sun.xml.writer.global;application/msword;application/vnd.ms-word;application/x-doc;application/rtf;text/rtf;application/vnd.wordperfect;application/wordperfect;application/vnd.openxmlformats-officedocument.wordprocessingml.document;application/vnd.ms-word.document.macroenabled.12;application/vnd.openxmlformats-officedocument.wordprocessingml.template;application/vnd.ms-word.template.macroenabled.12;application/vnd.oasis.opendocument.spreadsheet;application/vnd.oasis.opendocument.spreadsheet-template;application/vnd.sun.xml.calc;application/vnd.sun.xml.calc.template;application/msexcel;application/vnd.ms-excel;application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;application/vnd.ms-excel.sheet.macroenabled.12;application/vnd.openxmlformats-officedocument.spreadsheetml.template;application/vnd.ms-excel.template.macroenabled.12;application/vnd.ms-excel.sheet.binary.macroenabled.12;text/csv;text/spreadsheet;application/csv;application/excel;application/x-excel;application/x-msexcel;application/x-ms-excel;text/comma-separated-values;text/tab-separated-values;text/x-comma-separated-values;text/x-csv;application/vnd.oasis.opendocument.presentation;application/vnd.oasis.opendocument.presentation-template;application/vnd.sun.xml.impress;application/vnd.sun.xml.impress.template;application/mspowerpoint;application/vnd.ms-powerpoint;application/vnd.openxmlformats-officedocument.presentationml.presentation;application/vnd.ms-powerpoint.presentation.macroenabled.12;application/vnd.openxmlformats-officedocument.presentationml.template;application/vnd.ms-powerpoint.template.macroenabled.12;application/vnd.openxmlformats-officedocument.presentationml.slide;application/vnd.openxmlformats-officedocument.presentationml.slideshow;application/vnd.ms-powerpoint.slideshow.macroEnabled.12;x-scheme-handler/oo-office;text/docxf;text/oform; +Actions=NewDocument; + +[Desktop Action NewDocument] +Name=New Document +Name[de]=Neues Dokument +Name[fr]=Nouveau document +Name[es]=Documento nuevo +Name[ru]=Создать документ +Exec=/usr/bin/onlyoffice-desktopeditors --new:word diff --git a/src/kasmos/resources/onlyoffice/present-editor.desktop b/src/kasmos/resources/onlyoffice/present-editor.desktop new file mode 100644 index 000000000..0839ea705 --- /dev/null +++ b/src/kasmos/resources/onlyoffice/present-editor.desktop @@ -0,0 +1,21 @@ +[Desktop Entry] +Version=1.0 +Name=Slides +GenericName=Slide Deck Editor +Comment=Slide Deck Editor +Type=Application +Exec=/usr/bin/onlyoffice-desktopeditors --new=slide %U +Terminal=false +Icon=application-mspowerpoint +Keywords=Text;Document;OpenDocument Text;Microsoft Word;Microsoft Works;odt;doc;docx;rtf; +Categories=Office; +MimeType=application/vnd.oasis.opendocument.text;application/vnd.oasis.opendocument.text-template;application/vnd.oasis.opendocument.text-web;application/vnd.oasis.opendocument.text-master;application/vnd.sun.xml.writer;application/vnd.sun.xml.writer.template;application/vnd.sun.xml.writer.global;application/msword;application/vnd.ms-word;application/x-doc;application/rtf;text/rtf;application/vnd.wordperfect;application/wordperfect;application/vnd.openxmlformats-officedocument.wordprocessingml.document;application/vnd.ms-word.document.macroenabled.12;application/vnd.openxmlformats-officedocument.wordprocessingml.template;application/vnd.ms-word.template.macroenabled.12;application/vnd.oasis.opendocument.spreadsheet;application/vnd.oasis.opendocument.spreadsheet-template;application/vnd.sun.xml.calc;application/vnd.sun.xml.calc.template;application/msexcel;application/vnd.ms-excel;application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;application/vnd.ms-excel.sheet.macroenabled.12;application/vnd.openxmlformats-officedocument.spreadsheetml.template;application/vnd.ms-excel.template.macroenabled.12;application/vnd.ms-excel.sheet.binary.macroenabled.12;text/csv;text/spreadsheet;application/csv;application/excel;application/x-excel;application/x-msexcel;application/x-ms-excel;text/comma-separated-values;text/tab-separated-values;text/x-comma-separated-values;text/x-csv;application/vnd.oasis.opendocument.presentation;application/vnd.oasis.opendocument.presentation-template;application/vnd.sun.xml.impress;application/vnd.sun.xml.impress.template;application/mspowerpoint;application/vnd.ms-powerpoint;application/vnd.openxmlformats-officedocument.presentationml.presentation;application/vnd.ms-powerpoint.presentation.macroenabled.12;application/vnd.openxmlformats-officedocument.presentationml.template;application/vnd.ms-powerpoint.template.macroenabled.12;application/vnd.openxmlformats-officedocument.presentationml.slide;application/vnd.openxmlformats-officedocument.presentationml.slideshow;application/vnd.ms-powerpoint.slideshow.macroEnabled.12;x-scheme-handler/oo-office;text/docxf;text/oform; +Actions=NewPresentation; + +[Desktop Action NewPresentation] +Name=New Presentation +Name[de]=Neue Präsentation +Name[fr]=Nouvelle présentation +Name[es]=Presentación nueva +Name[ru]=Создать презентацию +Exec=/usr/bin/onlyoffice-desktopeditors --new:slide diff --git a/src/kasmos/resources/onlyoffice/sheets-editor.desktop b/src/kasmos/resources/onlyoffice/sheets-editor.desktop new file mode 100644 index 000000000..9a44e0612 --- /dev/null +++ b/src/kasmos/resources/onlyoffice/sheets-editor.desktop @@ -0,0 +1,21 @@ +[Desktop Entry] +Version=1.0 +Name=Sheets +GenericName=Spreadsheet Editor +Comment=Spreadsheet Editor +Type=Application +Exec=/usr/bin/onlyoffice-desktopeditors --new=cell %U +Terminal=false +Icon=application-x-excel +Keywords=Text;Document;OpenDocument Text;Microsoft Word;Microsoft Works;odt;doc;docx;rtf; +Categories=Office;WordProcessor;Spreadsheet; +MimeType=application/vnd.oasis.opendocument.text;application/vnd.oasis.opendocument.text-template;application/vnd.oasis.opendocument.text-web;application/vnd.oasis.opendocument.text-master;application/vnd.sun.xml.writer;application/vnd.sun.xml.writer.template;application/vnd.sun.xml.writer.global;application/msword;application/vnd.ms-word;application/x-doc;application/rtf;text/rtf;application/vnd.wordperfect;application/wordperfect;application/vnd.openxmlformats-officedocument.wordprocessingml.document;application/vnd.ms-word.document.macroenabled.12;application/vnd.openxmlformats-officedocument.wordprocessingml.template;application/vnd.ms-word.template.macroenabled.12;application/vnd.oasis.opendocument.spreadsheet;application/vnd.oasis.opendocument.spreadsheet-template;application/vnd.sun.xml.calc;application/vnd.sun.xml.calc.template;application/msexcel;application/vnd.ms-excel;application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;application/vnd.ms-excel.sheet.macroenabled.12;application/vnd.openxmlformats-officedocument.spreadsheetml.template;application/vnd.ms-excel.template.macroenabled.12;application/vnd.ms-excel.sheet.binary.macroenabled.12;text/csv;text/spreadsheet;application/csv;application/excel;application/x-excel;application/x-msexcel;application/x-ms-excel;text/comma-separated-values;text/tab-separated-values;text/x-comma-separated-values;text/x-csv;application/vnd.oasis.opendocument.presentation;application/vnd.oasis.opendocument.presentation-template;application/vnd.sun.xml.impress;application/vnd.sun.xml.impress.template;application/mspowerpoint;application/vnd.ms-powerpoint;application/vnd.openxmlformats-officedocument.presentationml.presentation;application/vnd.ms-powerpoint.presentation.macroenabled.12;application/vnd.openxmlformats-officedocument.presentationml.template;application/vnd.ms-powerpoint.template.macroenabled.12;application/vnd.openxmlformats-officedocument.presentationml.slide;application/vnd.openxmlformats-officedocument.presentationml.slideshow;application/vnd.ms-powerpoint.slideshow.macroEnabled.12;x-scheme-handler/oo-office;text/docxf;text/oform; +Actions=NewSpreadsheet; + +[Desktop Action NewSpreadsheet] +Name=New Spreadsheet +Name[de]=Neues Tabellendokument +Name[fr]=Nouveau classeur +Name[es]=Hoja de cálculo nueva +Name[ru]=Создать эл.таблицу +Exec=/usr/bin/onlyoffice-desktopeditors --new:cell diff --git a/src/opensuse/install/ansible/install_ansible.sh b/src/opensuse/install/ansible/install_ansible.sh index d566daec0..0aa800dae 100644 --- a/src/opensuse/install/ansible/install_ansible.sh +++ b/src/opensuse/install/ansible/install_ansible.sh @@ -2,4 +2,6 @@ set -ex zypper install -yn ansible -zypper clean --all +if [ -z ${SKIP_CLEAN+x} ]; then + zypper clean --all +fi diff --git a/src/opensuse/install/gimp/install_gimp.sh b/src/opensuse/install/gimp/install_gimp.sh index e0a686f1e..3e59929ce 100644 --- a/src/opensuse/install/gimp/install_gimp.sh +++ b/src/opensuse/install/gimp/install_gimp.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash zypper install -yn gimp -zypper clean --all +if [ -z ${SKIP_CLEAN+x} ]; then + zypper clean --all +fi cp /usr/share/applications/gimp.desktop $HOME/Desktop/ chmod +x $HOME/Desktop/gimp.desktop diff --git a/src/opensuse/install/libre_office/install_libre_office.sh b/src/opensuse/install/libre_office/install_libre_office.sh index e63e3d2da..27c29e247 100644 --- a/src/opensuse/install/libre_office/install_libre_office.sh +++ b/src/opensuse/install/libre_office/install_libre_office.sh @@ -9,7 +9,9 @@ zypper install -yn \ libreoffice-impress \ libreoffice-math \ libreoffice-writer -zypper clean --all +if [ -z ${SKIP_CLEAN+x} ]; then + zypper clean --all +fi cp /usr/share/applications/libreoffice-startcenter.desktop $HOME/Desktop/ chmod +x $HOME/Desktop/libreoffice-startcenter.desktop chown 1000:1000 $HOME/Desktop/libreoffice-startcenter.desktop diff --git a/src/opensuse/install/misc/install_tools.sh b/src/opensuse/install/misc/install_tools.sh index 66396851c..01cb75848 100644 --- a/src/opensuse/install/misc/install_tools.sh +++ b/src/opensuse/install/misc/install_tools.sh @@ -2,4 +2,7 @@ set -ex zypper install -yn nano zip wget xdotool -zypper clean --all + +if [ -z ${SKIP_CLEAN+x} ]; then + zypper clean --all +fi diff --git a/src/opensuse/install/slack/install_slack.sh b/src/opensuse/install/slack/install_slack.sh index b692c9157..a48c24a9f 100644 --- a/src/opensuse/install/slack/install_slack.sh +++ b/src/opensuse/install/slack/install_slack.sh @@ -23,7 +23,9 @@ version=4.12.2 wget -q https://downloads.slack-edge.com/releases/linux/${version}/prod/x64/slack-${version}-0.1.fc21.x86_64.rpm -O slack.rpm zypper install -yn libXss1 libsecret-1-0 libappindicator3-1 rpm -i --nodeps slack.rpm -zypper clean --all +if [ -z ${SKIP_CLEAN+x} ]; then + zypper clean --all +fi rm slack.rpm sed -i 's,/usr/bin/slack,/usr/bin/slack --no-sandbox,g' /usr/share/applications/slack.desktop cp /usr/share/applications/slack.desktop $HOME/Desktop/ diff --git a/src/opensuse/install/sublime_text/install_sublime_text.sh b/src/opensuse/install/sublime_text/install_sublime_text.sh index 55cf331af..effc7c389 100644 --- a/src/opensuse/install/sublime_text/install_sublime_text.sh +++ b/src/opensuse/install/sublime_text/install_sublime_text.sh @@ -10,7 +10,9 @@ rpm -v --import https://download.sublimetext.com/sublimehq-rpm-pub.gpg zypper addrepo -g -f https://download.sublimetext.com/rpm/stable/x86_64/sublime-text.repo zypper install -yn sublime-text -zypper clean --all +if [ -z ${SKIP_CLEAN+x} ]; then + zypper clean --all +fi cp /usr/share/applications/sublime_text.desktop $HOME/Desktop/ chmod +x $HOME/Desktop/sublime_text.desktop chown 1000:1000 $HOME/Desktop/sublime_text.desktop diff --git a/src/opensuse/install/telegram/install_telegram.sh b/src/opensuse/install/telegram/install_telegram.sh index e400e1df6..79ab84e38 100644 --- a/src/opensuse/install/telegram/install_telegram.sh +++ b/src/opensuse/install/telegram/install_telegram.sh @@ -9,7 +9,9 @@ if [ "${ARCH}" == "arm64" ] ; then fi zypper install -yn xz -zypper clean --all +if [ -z ${SKIP_CLEAN+x} ]; then + zypper clean --all +fi wget -q https://telegram.org/dl/desktop/linux -O /tmp/telegram.tgz tar -xvf /tmp/telegram.tgz -C /opt/ diff --git a/src/opensuse/install/terraform/install_terraform.sh b/src/opensuse/install/terraform/install_terraform.sh index 67db846f2..36892ea5c 100644 --- a/src/opensuse/install/terraform/install_terraform.sh +++ b/src/opensuse/install/terraform/install_terraform.sh @@ -6,6 +6,7 @@ zypper install -yn \ terraform-provider-aws \ terraform-provider-azurerm \ terraform-provider-google \ - terraform-provider-kubernetes \ - terraform-provider-openstack -zypper clean --all + terraform-provider-kubernetes +if [ -z ${SKIP_CLEAN+x} ]; then + zypper clean --all +fi diff --git a/src/opensuse/install/tools/install_tools_deluxe.sh b/src/opensuse/install/tools/install_tools_deluxe.sh index 664d1fc27..a43bb0698 100644 --- a/src/opensuse/install/tools/install_tools_deluxe.sh +++ b/src/opensuse/install/tools/install_tools_deluxe.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash set -ex -sed -i 's/download.opensuse.org/mirrorcache-us.opensuse.org/g' /etc/zypp/repos.d/*.repo zypper install -yn vlc git tmux -zypper clean --all +if [ -z ${SKIP_CLEAN+x} ]; then + zypper clean --all +fi diff --git a/src/opensuse/install/vs_code/install_vs_code.sh b/src/opensuse/install/vs_code/install_vs_code.sh index b49432841..6f4ad9764 100644 --- a/src/opensuse/install/vs_code/install_vs_code.sh +++ b/src/opensuse/install/vs_code/install_vs_code.sh @@ -15,4 +15,6 @@ chown 1000:1000 $HOME/Desktop/code.desktop # Conveniences for python development zypper install -yn python3-setuptools python3-virtualenv -zypper clean --all +if [ -z ${SKIP_CLEAN+x} ]; then + zypper clean --all +fi diff --git a/src/opensuse/install/zoom/install_zoom.sh b/src/opensuse/install/zoom/install_zoom.sh index fb10d65c6..755567977 100755 --- a/src/opensuse/install/zoom/install_zoom.sh +++ b/src/opensuse/install/zoom/install_zoom.sh @@ -12,7 +12,9 @@ wget -O /tmp/package-signing-key.pub https://zoom.us/linux/download/pubkey rpm --import /tmp/package-signing-key.pub rm -f /tmp/package-signing-key.pub zypper install -yn --allow-unsigned-rpm zoom_openSUSE_$(arch).rpm -zypper clean --all +if [ -z ${SKIP_CLEAN+x} ]; then + zypper clean --all +fi rm zoom_openSUSE_$(arch).rpm sed -i 's,/usr/bin/zoom,/usr/bin/zoom --no-sandbox,g' /usr/share/applications/Zoom.desktop cp /usr/share/applications/Zoom.desktop $HOME/Desktop/ diff --git a/src/oracle/install/ansible/install_ansible.sh b/src/oracle/install/ansible/install_ansible.sh index b733e24fc..7ac058670 100644 --- a/src/oracle/install/ansible/install_ansible.sh +++ b/src/oracle/install/ansible/install_ansible.sh @@ -1,10 +1,14 @@ #!/usr/bin/env bash set -ex -if [[ "${DISTRO}" == @(oracle8|rockylinux9|rockylinux8|oracle9|almalinux9|almalinux8|fedora37) ]]; then +if [[ "${DISTRO}" == @(oracle8|rockylinux9|rockylinux8|oracle9|rhel9|almalinux9|almalinux8|fedora39|fedora40|fedora41) ]]; then dnf install -y ansible - dnf clean all + if [ -z ${SKIP_CLEAN+x} ]; then + dnf clean all + fi else yum install -y ansible - yum clean all + if [ -z ${SKIP_CLEAN+x} ]; then + yum clean all + fi fi diff --git a/src/oracle/install/gimp/install_gimp.sh b/src/oracle/install/gimp/install_gimp.sh index a1ef1ce92..d5fde8592 100644 --- a/src/oracle/install/gimp/install_gimp.sh +++ b/src/oracle/install/gimp/install_gimp.sh @@ -1,11 +1,15 @@ #!/usr/bin/env bash set -ex -if [[ "${DISTRO}" == @(oracle8|rockylinux9|rockylinux8|oracle9|almalinux9|almalinux8|fedora37) ]]; then +if [[ "${DISTRO}" == @(oracle8|rockylinux9|rockylinux8|oracle9|rhel9|almalinux9|almalinux8|fedora39|fedora40|fedora41) ]]; then dnf install -y gimp - dnf clean all + if [ -z ${SKIP_CLEAN+x} ]; then + dnf clean all + fi else yum install -y gimp - yum clean all + if [ -z ${SKIP_CLEAN+x} ]; then + yum clean all + fi fi cp /usr/share/applications/gimp.desktop $HOME/Desktop/ chmod +x $HOME/Desktop/gimp.desktop diff --git a/src/oracle/install/libre_office/install_libre_office.sh b/src/oracle/install/libre_office/install_libre_office.sh index 733c7bcf5..e7177f48d 100644 --- a/src/oracle/install/libre_office/install_libre_office.sh +++ b/src/oracle/install/libre_office/install_libre_office.sh @@ -7,14 +7,16 @@ if [ "$ARCH" == "amd64" ] ; then exit 0 fi -if [[ "${DISTRO}" == @(oracle8|rockylinux9|rockylinux8|oracle9|almalinux9|almalinux8|fedora37) ]]; then +if [[ "${DISTRO}" == @(oracle8|rockylinux9|rockylinux8|oracle9|rhel9|almalinux9|almalinux8|fedora39|fedora40|fedora41) ]]; then dnf install -y \ libreoffice-core \ libreoffice-writer \ libreoffice-impress \ libreoffice-calc \ libreoffice-base - dnf clean all + if [ -z ${SKIP_CLEAN+x} ]; then + dnf clean all + fi else yum install -y \ libreoffice-core \ @@ -22,7 +24,9 @@ else libreoffice-impress \ libreoffice-calc \ libreoffice-base - yum clean all + if [ -z ${SKIP_CLEAN+x} ]; then + yum clean all + fi fi cp /usr/share/applications/libreoffice-startcenter.desktop $HOME/Desktop/ chmod +x $HOME/Desktop/libreoffice-startcenter.desktop diff --git a/src/oracle/install/misc/install_tools.sh b/src/oracle/install/misc/install_tools.sh index 623bc4ada..f152d786d 100644 --- a/src/oracle/install/misc/install_tools.sh +++ b/src/oracle/install/misc/install_tools.sh @@ -3,8 +3,12 @@ set -ex if [ -f /usr/bin/dnf ]; then dnf install -y nano zip wget xdotool - dnf clean all + if [ -z ${SKIP_CLEAN+x} ]; then + dnf clean all + fi else yum install -y nano zip wget xdotool - yum clean all + if [ -z ${SKIP_CLEAN+x} ]; then + yum clean all + fi fi diff --git a/src/oracle/install/obs/install_obs.sh b/src/oracle/install/obs/install_obs.sh index 6e947daac..dea60650c 100644 --- a/src/oracle/install/obs/install_obs.sh +++ b/src/oracle/install/obs/install_obs.sh @@ -9,10 +9,14 @@ fi if [[ "${DISTRO}" == @(oracle8|rockylinux8|almalinux8) ]]; then dnf install -y obs-studio - dnf clean all + if [ -z ${SKIP_CLEAN+x} ]; then + dnf clean all + fi else yum install -y obs-studio - yum clean all + if [ -z ${SKIP_CLEAN+x} ]; then + yum clean all + fi fi cp /usr/share/applications/com.obsproject.Studio.desktop $HOME/Desktop/ diff --git a/src/oracle/install/only_office/install_only_office.sh b/src/oracle/install/only_office/install_only_office.sh index 4574981b0..0e673031a 100644 --- a/src/oracle/install/only_office/install_only_office.sh +++ b/src/oracle/install/only_office/install_only_office.sh @@ -6,14 +6,22 @@ if [ "$ARCH" == "arm64" ] ; then echo "Only Office is not supported on arm64, skipping Only Office installation" exit 0 fi - -curl -L -o only_office.rpm "https://download.onlyoffice.com/install/desktop/editors/linux/onlyoffice-desktopeditors.$(arch).rpm" -if [[ "${DISTRO}" == @(oracle8|rockylinux9|rockylinux8|oracle9|almalinux9|almalinux8|fedora37) ]]; then +curl -L -o only_office.rpm "https://github.com/ONLYOFFICE/DesktopEditors/releases/latest/download/onlyoffice-desktopeditors.$(arch).rpm" +if [[ "${DISTRO}" == @(oracle8|rockylinux9|rockylinux8|oracle9|rhel9|almalinux9|almalinux8|fedora39|fedora40) ]]; then dnf localinstall -y only_office.rpm - dnf clean all + if [ -z ${SKIP_CLEAN+x} ]; then + dnf clean all + fi +elif [[ "${DISTRO}" == "fedora41" ]]; then + dnf install -y only_office.rpm + if [ -z ${SKIP_CLEAN+x} ]; then + dnf clean all + fi else yum localinstall -y only_office.rpm - yum clean all + if [ -z ${SKIP_CLEAN+x} ]; then + yum clean all + fi fi rm -rf only_office.rpm diff --git a/src/oracle/install/slack/install_slack.sh b/src/oracle/install/slack/install_slack.sh index 7b210a5df..e2a20ce8c 100644 --- a/src/oracle/install/slack/install_slack.sh +++ b/src/oracle/install/slack/install_slack.sh @@ -21,12 +21,16 @@ version=4.12.2 # This path may not be accurate once arm64 support arrives. Specifically I don't know if it will still be under x64 wget -q https://downloads.slack-edge.com/releases/linux/${version}/prod/x64/slack-${version}-0.1.fc21.x86_64.rpm -O slack.rpm -if [[ "${DISTRO}" == @(oracle8|rockylinux9|rockylinux8|oracle9|almalinux9|almalinux8|fedora37) ]]; then +if [[ "${DISTRO}" == @(oracle8|rockylinux9|rockylinux8|oracle9|rhel9|almalinux9|almalinux8|fedora39|fedora40|fedora41) ]]; then dnf localinstall -y slack.rpm - dnf clean all + if [ -z ${SKIP_CLEAN+x} ]; then + dnf clean all + fi else yum localinstall -y slack.rpm - yum clean all + if [ -z ${SKIP_CLEAN+x} ]; then + yum clean all + fi fi rm slack.rpm sed -i 's,/usr/bin/slack,/usr/bin/slack --no-sandbox,g' /usr/share/applications/slack.desktop diff --git a/src/oracle/install/sublime_text/install_sublime_text.sh b/src/oracle/install/sublime_text/install_sublime_text.sh index 0f0d01ca0..f22821e3b 100644 --- a/src/oracle/install/sublime_text/install_sublime_text.sh +++ b/src/oracle/install/sublime_text/install_sublime_text.sh @@ -6,16 +6,50 @@ if [ "$(arch)" == "aarch64" ] ; then exit 0 fi +if [[ "${DISTRO}" == @(rhel9|almalinux9|oracle9|rockylinux9) ]]; then + # Temporarily enable SHA1 in crypto policies to allow importing Sublime's GPG key (can remove this when the gpg key is updated with SHA256 or stronger digest) + # Start of SHA1 policy workaround + SHA1_POLICY_ORIGINAL="" + SHA1_POLICY_ENABLED=0 + if command -v update-crypto-policies >/dev/null 2>&1; then + SHA1_POLICY_ORIGINAL=$(update-crypto-policies --show | tr -d '\n') + if [[ -n "${SHA1_POLICY_ORIGINAL}" && "${SHA1_POLICY_ORIGINAL}" != *":SHA1"* ]]; then + update-crypto-policies --set "${SHA1_POLICY_ORIGINAL}:SHA1" + SHA1_POLICY_ENABLED=1 + fi + fi + + cleanup_sha1_policy() { + if [[ ${SHA1_POLICY_ENABLED} -eq 1 ]]; then + update-crypto-policies --set "${SHA1_POLICY_ORIGINAL}" + fi + } + trap cleanup_sha1_policy EXIT + # End of SHA1 policy workaround +fi + rpm -v --import https://download.sublimetext.com/sublimehq-rpm-pub.gpg -if [[ "${DISTRO}" == @(oracle8|rockylinux9|rockylinux8|oracle9|almalinux9|almalinux8|fedora37) ]]; then - dnf config-manager --add-repo https://download.sublimetext.com/rpm/stable/$(arch)/sublime-text.repo +if [[ "${DISTRO}" == @(oracle8|rockylinux9|rockylinux8|oracle9|rhel9|almalinux9|almalinux8|fedora39|fedora40|fedora41) ]]; then + if [[ "${DISTRO}" == "fedora41" ]]; then + dnf config-manager addrepo --from-repofile=https://download.sublimetext.com/rpm/stable/$(arch)/sublime-text.repo + else + dnf config-manager --add-repo https://download.sublimetext.com/rpm/stable/$(arch)/sublime-text.repo + fi + # Remove the gpgkey line from repo file since we manually imported the key + sed -i '/^gpgkey=/d' /etc/yum.repos.d/sublime-text.repo dnf install -y sublime-text - dnf clean all + if [ -z ${SKIP_CLEAN+x} ]; then + dnf clean all + fi else yum-config-manager --add-repo https://download.sublimetext.com/rpm/stable/$(arch)/sublime-text.repo + # Remove the gpgkey line from repo file since we manually imported the key + sed -i '/^gpgkey=/d' /etc/yum.repos.d/sublime-text.repo yum install -y sublime-text - yum clean all + if [ -z ${SKIP_CLEAN+x} ]; then + yum clean all + fi fi cp /usr/share/applications/sublime_text.desktop $HOME/Desktop/ chmod +x $HOME/Desktop/sublime_text.desktop diff --git a/src/oracle/install/teams/install_teams.sh b/src/oracle/install/teams/install_teams.sh index edd73bc69..472255e5f 100644 --- a/src/oracle/install/teams/install_teams.sh +++ b/src/oracle/install/teams/install_teams.sh @@ -11,12 +11,16 @@ fi if [ "${DISTRO}" == "oracle8" ]; then curl -L -o teams.rpm "https://go.microsoft.com/fwlink/p/?LinkID=2112907&clcid=0x409&culture=en-us&country=US" dnf localinstall -y teams.rpm - dnf clean all + if [ -z ${SKIP_CLEAN+x} ]; then + dnf clean all + fi else # el7 needs to be pinned to a previous version for libc deps curl -L -o teams.rpm "https://packages.microsoft.com/yumrepos/ms-teams/teams-1.3.00.30857-1.x86_64.rpm" yum localinstall -y teams.rpm - yum clean all + if [ -z ${SKIP_CLEAN+x} ]; then + yum clean all + fi fi rm teams.rpm sed -i "s/Exec=teams/Exec=teams --no-sandbox/g" /usr/share/applications/teams.desktop diff --git a/src/oracle/install/telegram/install_telegram.sh b/src/oracle/install/telegram/install_telegram.sh index 5a73590bb..e21dd26a7 100644 --- a/src/oracle/install/telegram/install_telegram.sh +++ b/src/oracle/install/telegram/install_telegram.sh @@ -10,7 +10,9 @@ fi if [ "${DISTRO}" == "oracle8" ]; then dnf install -y xz - dnf clean all + if [ -z ${SKIP_CLEAN+x} ]; then + dnf clean all + fi fi wget -q https://telegram.org/dl/desktop/linux -O /tmp/telegram.tgz diff --git a/src/oracle/install/terraform/install_terraform.sh b/src/oracle/install/terraform/install_terraform.sh index c500dcf4b..754f66ff2 100644 --- a/src/oracle/install/terraform/install_terraform.sh +++ b/src/oracle/install/terraform/install_terraform.sh @@ -8,16 +8,27 @@ if [ "${ARCH}" == "arm64" ] ; then exit 0 fi -if [[ "${DISTRO}" == @(oracle8|rockylinux9|rockylinux8|oracle9|almalinux9|almalinux8) ]]; then +if [[ "${DISTRO}" == @(oracle8|rockylinux9|rockylinux8|oracle9|rhel9|almalinux9|almalinux8) ]]; then dnf config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo dnf install -y terraform - dnf clean all -elif [ "${DISTRO}" == "fedora37" ]; then + if [ -z ${SKIP_CLEAN+x} ]; then + dnf clean all + fi +elif [[ "${DISTRO}" == @(fedora40|fedora41) ]]; then dnf config-manager --add-repo https://rpm.releases.hashicorp.com/fedora/hashicorp.repo + # use fedora40 hashicorp packages for terraform + sed -i 's/$releasever/40/g' /etc/yum.repos.d/hashicorp.repo dnf install -y terraform - dnf clean all + if [ -z ${SKIP_CLEAN+x} ]; then + dnf clean all + fi +elif [[ "${DISTRO}" == @(fedora38|fedora39) ]]; then + # skip installation for fedora38 and fedora39 + echo "Skipping terraform install for ${DISTRO}, as it is not officially supported by HashiCorp." else yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo yum install -y terraform - yum clean all + if [ -z ${SKIP_CLEAN+x} ]; then + yum clean all + fi fi diff --git a/src/oracle/install/tools/install_tools_deluxe.sh b/src/oracle/install/tools/install_tools_deluxe.sh index 8c852dcbf..63794006f 100644 --- a/src/oracle/install/tools/install_tools_deluxe.sh +++ b/src/oracle/install/tools/install_tools_deluxe.sh @@ -3,10 +3,14 @@ set -ex if [ -f /usr/bin/dnf ]; then dnf install -y vlc git tmux xz glibc-locale-source glibc-langpack-en - dnf clean all + if [ -z ${SKIP_CLEAN+x} ]; then + dnf clean all + fi else yum-config-manager --enable ol7_optional_latest yum install -y vlc git tmux - yum clean all + if [ -z ${SKIP_CLEAN+x} ]; then + yum clean all + fi fi diff --git a/src/oracle/install/vs_code/install_vs_code.sh b/src/oracle/install/vs_code/install_vs_code.sh index 2ed097305..f637ac5d1 100644 --- a/src/oracle/install/vs_code/install_vs_code.sh +++ b/src/oracle/install/vs_code/install_vs_code.sh @@ -2,12 +2,22 @@ set -ex ARCH=$(arch | sed 's/aarch64/arm64/g' | sed 's/x86_64/x64/g') +# if arch is arm64 and distro is oracle8 or rockylinux8 or almalinux8, skip installation +if [[ "${ARCH}" == "arm64" && "${DISTRO}" == @(oracle8|rockylinux8|almalinux8) ]]; then + echo "Skipping VS Code installation for arm64 architecture on ${DISTRO}" + exit 0 +fi + wget -q https://update.code.visualstudio.com/latest/linux-rpm-${ARCH}/stable -O vs_code.rpm -if [[ "${DISTRO}" == @(oracle8|rockylinux9|rockylinux8|oracle9|almalinux9|almalinux8|fedora37) ]]; then +if [[ "${DISTRO}" == @(oracle8|rockylinux9|rockylinux8|oracle9|rhel9|almalinux9|almalinux8|fedora39|fedora40|fedora41) ]]; then wget -q https://update.code.visualstudio.com/latest/linux-rpm-${ARCH}/stable -O vs_code.rpm - dnf localinstall -y vs_code.rpm + if [[ "${DISTRO}" == "fedora41" ]]; then + dnf install -y vs_code.rpm + else + dnf localinstall -y vs_code.rpm + fi else - wget -q https://packages.microsoft.com/yumrepos/vscode/code-1.65.2-1646927812.el7.x86_64.rpm -O vs_code.rpm + wget -q https://packages.microsoft.com/yumrepos/vscode/code-1.85.1-1702462241.el7.x86_64.rpm -O vs_code.rpm yum localinstall -y vs_code.rpm fi mkdir -p /usr/share/icons/hicolor/apps @@ -20,10 +30,14 @@ chown 1000:1000 $HOME/Desktop/code.desktop rm vs_code.rpm # Conveniences for python development -if [[ "${DISTRO}" == @(oracle8|rockylinux9|rockylinux8|oracle9|almalinux9|almalinux8|fedora37) ]]; then +if [[ "${DISTRO}" == @(oracle8|rockylinux9|rockylinux8|oracle9|rhel9|almalinux9|almalinux8|fedora39|fedora40|fedora41) ]]; then dnf install -y python3-setuptools python3-virtualenv - dnf clean all + if [ -z ${SKIP_CLEAN+x} ]; then + dnf clean all + fi else yum install -y python3-setuptools python3-virtualenv - yum clean all + if [ -z ${SKIP_CLEAN+x} ]; then + yum clean all + fi fi diff --git a/src/oracle/install/zoom/install_zoom.sh b/src/oracle/install/zoom/install_zoom.sh index 0072e90d1..786ad2652 100644 --- a/src/oracle/install/zoom/install_zoom.sh +++ b/src/oracle/install/zoom/install_zoom.sh @@ -8,12 +8,21 @@ fi wget -q https://zoom.us/client/latest/zoom_$(arch).rpm -if [[ "${DISTRO}" == @(oracle8|rockylinux9|rockylinux8|oracle9|almalinux9|almalinux8|fedora37) ]]; then +if [[ "${DISTRO}" == @(oracle8|rockylinux9|rockylinux8|oracle9|rhel9|almalinux9|almalinux8|fedora39|fedora40) ]]; then dnf localinstall -y zoom_$(arch).rpm - dnf clean all + if [ -z ${SKIP_CLEAN+x} ]; then + dnf clean all + fi +elif [[ "${DISTRO}" == "fedora41" ]]; then + dnf install -y zoom_$(arch).rpm + if [ -z ${SKIP_CLEAN+x} ]; then + dnf clean all + fi else yum localinstall -y zoom_$(arch).rpm - yum clean all + if [ -z ${SKIP_CLEAN+x} ]; then + yum clean all + fi fi rm zoom_$(arch).rpm sed -i 's,/usr/bin/zoom,/usr/bin/zoom --no-sandbox,g' /usr/share/applications/Zoom.desktop diff --git a/src/ubuntu/install/android_studio/install_android_studio.sh b/src/ubuntu/install/android_studio/install_android_studio.sh new file mode 100644 index 000000000..b8ee382e7 --- /dev/null +++ b/src/ubuntu/install/android_studio/install_android_studio.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +set -ex + + +ARCH=$(arch | sed 's/aarch64/arm64/g' | sed 's/x86_64/amd64/g') +if [ "$ARCH" == "arm64" ] ; then + echo "Android studio not supported on arm64, skipping installation" + exit 0 +fi + +apt-get update +apt-get install -y libc6:i386 libncurses5:i386 libstdc++6:i386 lib32z1 libbz2-1.0:i386 openjdk-18-jdk + +# https://developer.android.com/studio/archive +# curl https://developer.android.com/studio | grep android-studio | grep -i "linux.tar.gz" | grep ide-zips | cut -d '"' -f2 +ANDROID_STUDIO_DOWNLOAD_URL="https://redirector.gvt1.com/edgedl/android/studio/ide-zips/2023.1.1.26/android-studio-2023.1.1.26-linux.tar.gz" + +curl -o /tmp/android_studio.tar.gz -L "${ANDROID_STUDIO_DOWNLOAD_URL}" + +mkdir -p /opt +cd /tmp +tar -zxvf /tmp/android_studio.tar.gz -C /opt/ +rm /tmp/android_studio.tar.gz +ln -sf /opt/android-studio/bin/studio.sh /bin/android-studio +chown 1000:1000 /opt/android-studio + + +cat >/usr/share/applications/android-studio.desktop < /etc/apt/sources.list.d/atom.list @@ -8,5 +9,18 @@ apt-get update apt-get install -y atom # Desktop Icon +sed -i 's#/usr/bin/atom#/usr/bin/atom --no-sandbox#g' /usr/share/applications/atom.desktop cp /usr/share/applications/atom.desktop $HOME/Desktop/ chmod +x $HOME/Desktop/atom.desktop + +# Cleanup for app layer +chown -R 1000:0 $HOME +find /usr/share/ -name "icon-theme.cache" -exec rm -f {} \; +if [ -z ${SKIP_CLEAN+x} ]; then + apt-get autoclean + rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* \ + /tmp/* +fi + diff --git a/src/ubuntu/install/audacity/install_audacity.sh b/src/ubuntu/install/audacity/install_audacity.sh index 193649aa1..1b285555f 100644 --- a/src/ubuntu/install/audacity/install_audacity.sh +++ b/src/ubuntu/install/audacity/install_audacity.sh @@ -1,7 +1,8 @@ #!/usr/bin/env bash set -ex -apt-get update +# Install Audacity +apt-get update apt-get install -y audacity rm -rf \ /var/lib/apt/lists/* \ @@ -12,3 +13,14 @@ mkdir -p $HOME/.audacity-data/ cp /dockerstartup/install/audacity/audacity.cfg $HOME/.audacity-data/ cp /usr/share/applications/audacity.desktop $HOME/Desktop/ chmod +x $HOME/Desktop/audacity.desktop + +# Cleanup for app layer +chown -R 1000:0 $HOME +find /usr/share/ -name "icon-theme.cache" -exec rm -f {} \; +if [ -z ${SKIP_CLEAN+x} ]; then + apt-get autoclean + rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* \ + /tmp/* +fi diff --git a/src/ubuntu/install/blender/install_blender.sh b/src/ubuntu/install/blender/install_blender.sh index 89dd7a926..d5279aa26 100644 --- a/src/ubuntu/install/blender/install_blender.sh +++ b/src/ubuntu/install/blender/install_blender.sh @@ -17,14 +17,14 @@ ln -s libOpenCL.so.1 /usr/lib/x86_64-linux-gnu/libOpenCL.so mkdir /blender if [ -z ${BLENDER_VERSION+x} ] then - BLENDER_VERSION=$(curl -sL https://mirror.clarkson.edu/blender/source/ \ - | awk -F'"|/"' '/blender-[0-9]*\.[0-9]*\.[0-9]*\.tar\.xz/ && !/md5sum/ {print $2}' \ + BLENDER_VERSION=$(curl -sL https://mirrors.iu13.net/blender/source/ \ + | awk -F'"|/"' '/blender-[0-9]*\.[0-9]*\.[0-9]*\.tar\.xz/ && !/md5sum/ {print $8}' \ | tail -1 \ | sed 's|blender-||' \ - | sed 's|\.tar\.xz||') + | sed 's|\.tar\.xz||'); fi BLENDER_FOLDER=$(echo "Blender${BLENDER_VERSION}" | sed -r 's|(Blender[0-9]*\.[0-9]*)\.[0-9]*|\1|') -curl -o /tmp/blender.tar.xz -L "https://mirror.clarkson.edu/blender/release/${BLENDER_FOLDER}/blender-${BLENDER_VERSION}-linux-x64.tar.xz" +curl -o /tmp/blender.tar.xz -L "https://mirrors.iu13.net/blender/release/${BLENDER_FOLDER}/blender-${BLENDER_VERSION}-linux-x64.tar.xz" tar xf /tmp/blender.tar.xz -C /blender/ --strip-components=1 cat >/usr/bin/blender </usr/bin/brave-browser </dev/null 2>&1 || return 1 + + # Look for any non-CPU device + DISPLAY= vulkaninfo --summary 2>/dev/null | + grep -qE 'PHYSICAL_DEVICE_TYPE_(INTEGRATED_GPU|DISCRETE_GPU|VIRTUAL_GPU)' +} + sed -i 's/"exited_cleanly":false/"exited_cleanly":true/' ~/.config/BraveSoftware/Brave-Browser/Default/Preferences sed -i 's/"exit_type":"Crashed"/"exit_type":"None"/' ~/.config/BraveSoftware/Brave-Browser/Default/Preferences + +VULKAN_FLAGS= +if supports_vulkan; then + VULKAN_FLAGS="--use-angle=vulkan" + echo 'vulkan supported' +fi + if [ -f /opt/VirtualGL/bin/vglrun ] && [ ! -z "\${KASM_EGL_CARD}" ] && [ ! -z "\${KASM_RENDERD}" ] && [ -O "\${KASM_RENDERD}" ] && [ -O "\${KASM_EGL_CARD}" ] ; then echo "Starting Brave with GPU Acceleration on EGL device \${KASM_EGL_CARD}" - vglrun -d "\${KASM_EGL_CARD}" /opt/brave.com/brave/brave-browser ${CHROME_ARGS} "\$@" + vglrun -d "\${KASM_EGL_CARD}" /opt/brave.com/brave/brave-browser ${CHROME_ARGS} "\${VULKAN_FLAGS}" "\$@" else echo "Starting Brave" - /opt/brave.com/brave/brave-browser ${CHROME_ARGS} "\$@" + /opt/brave.com/brave/brave-browser ${CHROME_ARGS} "\${VULKAN_FLAGS}" "\$@" fi EOL chmod +x /usr/bin/brave-browser cp /usr/bin/brave-browser /usr/bin/brave -sed -i 's@exec -a "$0" "$HERE/brave" "$\@"@@g' /usr/bin/x-www-browser +sed -i 's@exec -a "$0" "$HERE/brave-browser" "$\@"@@g' /usr/bin/x-www-browser cat >>/usr/bin/x-www-browser <>/etc/brave/policies/managed/default_managed_policy.json <>/etc/brave/policies/managed/disable_tor.json <&2; exit 1;; + esac +done +shift + +kasm_exec() { + /usr/bin/filter_ready + /usr/bin/desktop_ready + + # Open the gateway UI in the default browser + set +e + xdg-open "http://localhost:18789" & + set -e +} + +kasm_startup() { + if [ -n "$DISABLE_CUSTOM_STARTUP" ]; then + echo "CareerClaw custom startup disabled" + return + fi + + # Respawn loop: keep the gateway running + while true; do + # Check if gateway is already running on port 18789 + if ! ss -tlnp 2>/dev/null | grep -q ":18789"; then + /usr/bin/filter_ready + /usr/bin/desktop_ready + + echo "Starting CareerClaw gateway..." + set +e + $START_COMMAND $ARGS >> "$GATEWAY_LOG" 2>&1 & + set -e + + # Wait for the gateway to be ready + for i in $(seq 1 30); do + if ss -tlnp 2>/dev/null | grep -q ":18789"; then + echo "CareerClaw gateway is ready on port 18789" + break + fi + sleep 1 + done + fi + sleep 5 + done +} + +if [ -n "$GO" ] || [ -n "$ASSIGN" ]; then + kasm_exec +else + kasm_startup +fi diff --git a/src/ubuntu/install/careerclaw/fix_startup.sh b/src/ubuntu/install/careerclaw/fix_startup.sh new file mode 100644 index 000000000..e50d987c2 --- /dev/null +++ b/src/ubuntu/install/careerclaw/fix_startup.sh @@ -0,0 +1,16 @@ +#!/bin/bash +set -ex + +# Fix for "Grey Screen" issue: manually create xstartup to force XFCE launch +VNC_DIR="/home/kasm-user/.vnc" +mkdir -p "$VNC_DIR" + +cat > "$VNC_DIR/xstartup" <<'EOF' +#!/bin/bash +unset SESSION_MANAGER +unset DBUS_SESSION_BUS_ADDRESS +exec startxfce4 +EOF + +chmod +x "$VNC_DIR/xstartup" +chown 1000:0 "$VNC_DIR/xstartup" diff --git a/src/ubuntu/install/careerclaw/install_careerclaw.sh b/src/ubuntu/install/careerclaw/install_careerclaw.sh new file mode 100644 index 000000000..713cfec40 --- /dev/null +++ b/src/ubuntu/install/careerclaw/install_careerclaw.sh @@ -0,0 +1,161 @@ +#!/usr/bin/env bash +set -ex + +# Tell pnpm/node we're in a non-interactive CI environment (no TTY) +export CI=true + +# Install dependencies including python3 (for gateway launcher) and Node.js 22 +apt-get update +apt-get install -y python3 python3-pip git curl + +# Download and install Node.js 22 +NODESOURCE_SCRIPT=$(mktemp) +curl -fsSL https://deb.nodesource.com/setup_22.x -o "$NODESOURCE_SCRIPT" || { echo "Failed to download Node.js setup script"; exit 1; } +bash "$NODESOURCE_SCRIPT" +rm -f "$NODESOURCE_SCRIPT" +apt-get install -y nodejs + +# Enable corepack for pnpm +corepack enable +corepack prepare pnpm@latest --activate + +# Clone CareerClaw (OpenClaw fork) +CAREERCLAW_DIR="/opt/careerclaw" +if [ -d "$CAREERCLAW_DIR" ]; then + rm -rf "$CAREERCLAW_DIR" +fi + +echo "Cloning CareerClaw repository..." +git clone --depth 1 https://github.com/alexander-acker/careerclaw.git "$CAREERCLAW_DIR" || { echo "Failed to clone CareerClaw repo"; exit 1; } + +# Build CareerClaw +cd "$CAREERCLAW_DIR" +echo "Installing dependencies..." +pnpm install --frozen-lockfile || { echo "Failed to install dependencies"; exit 1; } + +echo "Building CareerClaw..." +pnpm build || { echo "Failed to build CareerClaw"; exit 1; } +pnpm ui:build || { echo "Failed to build UI"; exit 1; } + +# Slim down: drop dev dependencies and git history to save ~300-500 MB +echo "Pruning development dependencies..." +CI=true pnpm prune --prod +rm -rf .git + +# Create CLI wrapper so 'openclaw' is available system-wide +cat > /usr/local/bin/openclaw <<'WRAPPER' +#!/usr/bin/env bash +exec node /opt/careerclaw/openclaw.mjs "$@" +WRAPPER +chmod +x /usr/local/bin/openclaw + +# Create gateway launcher script (used by autostart) +cat > /usr/local/bin/careerclaw-gateway <<'LAUNCHER' +#!/usr/bin/env bash +GATEWAY_LOG="$HOME/.openclaw/gateway.log" +mkdir -p "$HOME/.openclaw" +chmod 700 "$HOME/.openclaw" + +# Don't start if already running +if ss -tlnp 2>/dev/null | grep -q ":18789"; then + echo "CareerClaw gateway already running" >> "$GATEWAY_LOG" + exit 0 +fi + +# Enforce localhost binding — overwrite config if tampered (defense-in-depth) +CONFIG="$HOME/.openclaw/openclaw.json" +if [ -f "$CONFIG" ]; then + BIND_ADDR=$(python3 -c "import json; print(json.load(open('$CONFIG')).get('gateway',{}).get('bind',''))" 2>/dev/null || echo "") + if [ "$BIND_ADDR" != "loopback" ]; then + echo "[$(date)] SECURITY: bind was '$BIND_ADDR', forcing to loopback" >> "$GATEWAY_LOG" + python3 -c " +import json +c = json.load(open('$CONFIG')) +c.setdefault('gateway',{})['bind'] = 'loopback' +json.dump(c, open('$CONFIG','w'), indent=2) +" 2>/dev/null + fi +fi + +echo "[$(date)] Starting CareerClaw gateway..." >> "$GATEWAY_LOG" +exec /usr/local/bin/openclaw gateway >> "$GATEWAY_LOG" 2>&1 +LAUNCHER +chmod +x /usr/local/bin/careerclaw-gateway + +# Create default config directory for the Kasm default profile +OPENCLAW_STATE="$HOME/.openclaw" +mkdir -p "$OPENCLAW_STATE/workspace" +mkdir -p "$OPENCLAW_STATE/agents/main/sessions" +mkdir -p "$OPENCLAW_STATE/credentials" + +cat > "$OPENCLAW_STATE/openclaw.json" <<'CONF' +{ + "agents": { + "defaults": { + "model": { + "primary": "anthropic/claude-sonnet-4-5-20250929" + } + } + }, + "gateway": { + "mode": "local", + "port": 18789, + "bind": "loopback" + } +} +CONF +chmod 600 "$OPENCLAW_STATE/openclaw.json" + +# Create desktop icon +mkdir -p /usr/share/icons/hicolor/apps +cp "$CAREERCLAW_DIR/assets/icon.png" /usr/share/icons/hicolor/apps/careerclaw.png 2>/dev/null || \ + wget -q -O /usr/share/icons/hicolor/apps/careerclaw.png \ + "https://raw.githubusercontent.com/alexander-acker/careerclaw/main/assets/icon.png" 2>/dev/null || \ + echo "No icon found, using default" + +# Desktop shortcut to open the gateway web UI +cat > /usr/share/applications/careerclaw.desktop <<'DESKTOP' +[Desktop Entry] +Version=1.0 +Type=Application +Name=CareerClaw AI +Comment=AI Career Assistant - OpenClaw Gateway +Exec=xdg-open http://localhost:18789 +Icon=/usr/share/icons/hicolor/apps/careerclaw.png +Terminal=false +Categories=Utility;Network; +StartupNotify=true +DESKTOP + +cp /usr/share/applications/careerclaw.desktop "$HOME/Desktop/" +chmod +x "$HOME/Desktop/careerclaw.desktop" +chown 1000:1000 "$HOME/Desktop/careerclaw.desktop" + +# XFCE autostart: launch gateway automatically at desktop login +mkdir -p /etc/xdg/autostart +cat > /etc/xdg/autostart/careerclaw-gateway.desktop <<'AUTOSTART' +[Desktop Entry] +Type=Application +Name=CareerClaw Gateway +Comment=Start CareerClaw AI gateway in background +Exec=/usr/local/bin/careerclaw-gateway +Hidden=false +NoDisplay=true +X-GNOME-Autostart-enabled=true +AUTOSTART + +# Set ownership +chown -R 1000:0 "$CAREERCLAW_DIR" +chown -R 1000:0 "$OPENCLAW_STATE" + +# Cleanup for app layer +chown -R 1000:0 $HOME +find /usr/share/ -name "icon-theme.cache" -exec rm -f {} \; + +if [ -z ${SKIP_CLEAN+x} ]; then + apt-get autoclean + rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* \ + /tmp/* +fi diff --git a/src/ubuntu/install/chrome/install_chrome.sh b/src/ubuntu/install/chrome/install_chrome.sh index bd9f97531..94ed51b7f 100644 --- a/src/ubuntu/install/chrome/install_chrome.sh +++ b/src/ubuntu/install/chrome/install_chrome.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -ex -CHROME_ARGS="--password-store=basic --no-sandbox --ignore-gpu-blocklist --user-data-dir --no-first-run --simulate-outdated-no-au='Tue, 31 Dec 2099 23:59:59 GMT'" +CHROME_ARGS="--password-store=basic --no-sandbox --ignore-gpu-blocklist --user-data-dir --no-first-run --disable-search-engine-choice-screen --simulate-outdated-no-au='Tue, 31 Dec 2099 23:59:59 GMT'" CHROME_VERSION=$1 ARCH=$(arch | sed 's/aarch64/arm64/g' | sed 's/x86_64/amd64/g') @@ -10,27 +10,33 @@ if [ "$ARCH" == "arm64" ] ; then exit 0 fi -if [[ "${DISTRO}" == @(centos|oracle8|rockylinux9|rockylinux8|oracle9|almalinux9|almalinux8) ]]; then +if [[ "${DISTRO}" == @(centos|oracle8|rockylinux9|rockylinux8|oracle9|rhel9|almalinux9|almalinux8) ]]; then if [ ! -z "${CHROME_VERSION}" ]; then wget https://dl.google.com/linux/chrome/rpm/stable/x86_64/google-chrome-stable-${CHROME_VERSION}.x86_64.rpm -O chrome.rpm else wget https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm -O chrome.rpm fi - if [[ "${DISTRO}" == @(oracle8|rockylinux9|rockylinux8|oracle9|almalinux9|almalinux8) ]]; then + if [[ "${DISTRO}" == @(oracle8|rockylinux9|rockylinux8|oracle9|rhel9|almalinux9|almalinux8) ]]; then dnf localinstall -y chrome.rpm - dnf clean all + if [ -z ${SKIP_CLEAN+x} ]; then + dnf clean all + fi else yum localinstall -y chrome.rpm - yum clean all + if [ -z ${SKIP_CLEAN+x} ]; then + yum clean all + fi fi rm chrome.rpm elif [ "${DISTRO}" == "opensuse" ]; then - zypper ar http://dl.google.com/linux/chrome/rpm/stable/x86_64 Google-Chrome + zypper ar https://dl.google.com/linux/chrome/rpm/stable/x86_64 Google-Chrome wget https://dl.google.com/linux/linux_signing_key.pub rpm --import linux_signing_key.pub rm linux_signing_key.pub zypper install -yn google-chrome-stable - zypper clean --all + if [ -z ${SKIP_CLEAN+x} ]; then + zypper clean --all + fi else apt-get update if [ ! -z "${CHROME_VERSION}" ]; then @@ -40,6 +46,12 @@ else fi apt-get install -y ./chrome.deb rm chrome.deb + if [ -z ${SKIP_CLEAN+x} ]; then + apt-get autoclean + rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* + fi fi sed -i 's/-stable//g' /usr/share/applications/google-chrome.desktop @@ -51,20 +63,40 @@ chmod +x $HOME/Desktop/google-chrome.desktop mv /usr/bin/google-chrome /usr/bin/google-chrome-orig cat >/usr/bin/google-chrome </dev/null 2>&1 || return 1 + + # Look for any non-CPU device + DISPLAY= vulkaninfo --summary 2>/dev/null | + grep -qE 'PHYSICAL_DEVICE_TYPE_(INTEGRATED_GPU|DISCRETE_GPU|VIRTUAL_GPU)' +} + +if ! pgrep chrome > /dev/null;then + rm -f \$HOME/.config/google-chrome/Singleton* +fi sed -i 's/"exited_cleanly":false/"exited_cleanly":true/' ~/.config/google-chrome/Default/Preferences sed -i 's/"exit_type":"Crashed"/"exit_type":"None"/' ~/.config/google-chrome/Default/Preferences + +VULKAN_FLAGS= +if supports_vulkan; then + VULKAN_FLAGS="--use-angle=vulkan" + echo 'vulkan supported' +fi + if [ -f /opt/VirtualGL/bin/vglrun ] && [ ! -z "\${KASM_EGL_CARD}" ] && [ ! -z "\${KASM_RENDERD}" ] && [ -O "\${KASM_RENDERD}" ] && [ -O "\${KASM_EGL_CARD}" ] ; then echo "Starting Chrome with GPU Acceleration on EGL device \${KASM_EGL_CARD}" - vglrun -d "\${KASM_EGL_CARD}" /opt/google/chrome/google-chrome ${CHROME_ARGS} "\$@" + vglrun -d "\${KASM_EGL_CARD}" /opt/google/chrome/google-chrome ${CHROME_ARGS} "\${VULKAN_FLAGS}" "\$@" else echo "Starting Chrome" - /opt/google/chrome/google-chrome ${CHROME_ARGS} "\$@" + /opt/google/chrome/google-chrome ${CHROME_ARGS} "\${VULKAN_FLAGS}" "\$@" fi EOL chmod +x /usr/bin/google-chrome cp /usr/bin/google-chrome /usr/bin/chrome -if [[ "${DISTRO}" == @(centos|oracle8|rockylinux9|rockylinux8|oracle9|almalinux9|almalinux8|opensuse) ]]; then +if [[ "${DISTRO}" == @(centos|oracle8|rockylinux9|rockylinux8|oracle9|rhel9|almalinux9|almalinux8|opensuse) ]]; then cat >> $HOME/.config/mimeapps.list <>/usr/bin/x-www-browser <>/etc/opt/chrome/policies/managed/default_managed_policy.json <.*//p' <<< "${chromium_codecs_data}") - echo "Chromium codec deb to download: ${chromium_codecs_data}" - - chromium_data=$(curl ${chrome_url}) - chromium_data=$(grep "chromium-browser_" <<< "${chromium_data}") - chromium_data=$(grep "18\.04" <<< "${chromium_data}") - chromium_data=$(grep "${ARCH}" <<< "${chromium_data}") - chromium_data=$(sed -n 's/.*.*//p' <<< "${chromium_data}") - echo "Chromium browser deb to download: ${chromium_data}" - - echo "The things to download" - echo "${chrome_url}${chromium_codecs_data}" - echo "${chrome_url}${chromium_data}" - - wget "${chrome_url}${chromium_codecs_data}" - wget "${chrome_url}${chromium_data}" - - apt-get install -y ./"${chromium_codecs_data}" - apt-get install -y ./"${chromium_data}" - - rm "${chromium_codecs_data}" - rm "${chromium_data}" fi -if grep -q "ID=debian" /etc/os-release || grep -q "ID=kali" /etc/os-release || grep -q "ID=parrot" /etc/os-release; then +if grep -q "ID=debian" /etc/os-release || grep -q "ID=kali" /etc/os-release || grep -q "ID=parrot" /etc/os-release || grep -q "ID=ubuntu" /etc/os-release; then REAL_BIN=chromium else REAL_BIN=chromium-browser @@ -82,23 +74,43 @@ fi mv /usr/bin/${REAL_BIN} /usr/bin/${REAL_BIN}-orig cat >/usr/bin/${REAL_BIN} </dev/null 2>&1 || return 1 + + # Look for any non-CPU device + DISPLAY= vulkaninfo --summary 2>/dev/null | + grep -qE 'PHYSICAL_DEVICE_TYPE_(INTEGRATED_GPU|DISCRETE_GPU|VIRTUAL_GPU)' +} + +if ! pgrep chromium > /dev/null;then + rm -f \$HOME/.config/chromium/Singleton* +fi sed -i 's/"exited_cleanly":false/"exited_cleanly":true/' ~/.config/chromium/Default/Preferences sed -i 's/"exit_type":"Crashed"/"exit_type":"None"/' ~/.config/chromium/Default/Preferences + +VULKAN_FLAGS= +if supports_vulkan; then + VULKAN_FLAGS="--use-angle=vulkan" + echo 'vulkan supported' +fi + if [ -f /opt/VirtualGL/bin/vglrun ] && [ ! -z "\${KASM_EGL_CARD}" ] && [ ! -z "\${KASM_RENDERD}" ] && [ -O "\${KASM_RENDERD}" ] && [ -O "\${KASM_EGL_CARD}" ] ; then echo "Starting Chrome with GPU Acceleration on EGL device \${KASM_EGL_CARD}" - vglrun -d "\${KASM_EGL_CARD}" /usr/bin/${REAL_BIN}-orig ${CHROME_ARGS} "\$@" + vglrun -d "\${KASM_EGL_CARD}" /usr/bin/${REAL_BIN}-orig ${CHROME_ARGS} "\${VULKAN_FLAGS}" "\$@" else echo "Starting Chrome" - /usr/bin/${REAL_BIN}-orig ${CHROME_ARGS} "\$@" + /usr/bin/${REAL_BIN}-orig ${CHROME_ARGS} "\${VULKAN_FLAGS}" "\$@" fi EOL chmod +x /usr/bin/${REAL_BIN} -if [ "${DISTRO}" != "opensuse" ] && ! grep -q "ID=debian" /etc/os-release && ! grep -q "ID=kali" /etc/os-release && ! grep -q "ID=parrot" /etc/os-release; then +if [ "${DISTRO}" != "opensuse" ] && ! grep -q "ID=debian" /etc/os-release && ! grep -q "ID=kali" /etc/os-release && ! grep -q "ID=parrot" /etc/os-release && ! grep -q "ID=ubuntu" /etc/os-release; then cp /usr/bin/chromium-browser /usr/bin/chromium fi -if [[ "${DISTRO}" == @(centos|oracle8|rockylinux9|rockylinux8|oracle9|almalinux9|almalinux8|opensuse|fedora37) ]]; then +if [[ "${DISTRO}" == @(oracle8|rockylinux9|rockylinux8|oracle9|rhel9|almalinux9|almalinux8|opensuse|fedora39|fedora40|fedora41) ]]; then cat >> $HOME/.config/mimeapps.list <>/usr/bin/x-www-browser <>/etc/chromium/policies/managed/default_managed_policy.json < tuple[str, str, int]: + """Run a subprocess and return (stdout, stderr, returncode).""" + env = {**os.environ, "DISPLAY": DISPLAY} + try: + proc = subprocess.run( + cmd, capture_output=True, text=True, timeout=timeout, env=env + ) + return proc.stdout.strip(), proc.stderr.strip(), proc.returncode + except subprocess.TimeoutExpired: + return "", "timeout", 1 + except FileNotFoundError: + return "", f"command not found: {cmd[0]}", 127 + + +def screenshot_png() -> bytes | None: + """Capture the entire screen as PNG bytes.""" + with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as f: + path = f.name + try: + # Try import (ImageMagick) first + stdout, stderr, rc = _run(["import", "-window", "root", path]) + if rc != 0: + # Fallback: xwd -> convert + xwd_path = path.replace(".png", ".xwd") + _run(["xwd", "-root", "-out", xwd_path]) + _run(["convert", xwd_path, path]) + if os.path.exists(xwd_path): + os.unlink(xwd_path) + if os.path.exists(path) and os.path.getsize(path) > 0: + with open(path, "rb") as f: + return f.read() + return None + finally: + if os.path.exists(path): + os.unlink(path) + + +def screenshot_region_png(x: int, y: int, w: int, h: int) -> bytes | None: + """Capture a specific region of the screen.""" + with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as f: + path = f.name + try: + geometry = f"{w}x{h}+{x}+{y}" + _run(["import", "-window", "root", "-crop", geometry, path]) + if os.path.exists(path) and os.path.getsize(path) > 0: + with open(path, "rb") as f: + return f.read() + return None + finally: + if os.path.exists(path): + os.unlink(path) + + +def get_screen_size() -> tuple[int, int]: + """Return (width, height) of the primary display.""" + stdout, _, rc = _run(["xdpyinfo"]) + if rc == 0: + for line in stdout.splitlines(): + if "dimensions:" in line: + # e.g. " dimensions: 1920x1080 pixels (508x285 millimeters)" + dim = line.split("dimensions:")[1].strip().split()[0] + w, h = dim.split("x") + return int(w), int(h) + return 1920, 1080 # Default fallback + + +def mouse_move(x: int, y: int) -> tuple[str, int]: + stdout, stderr, rc = _run(["xdotool", "mousemove", str(x), str(y)]) + return stderr if rc else "ok", rc + + +def mouse_click(button: int = 1) -> tuple[str, int]: + stdout, stderr, rc = _run(["xdotool", "click", str(button)]) + return stderr if rc else "ok", rc + + +def mouse_double_click(button: int = 1) -> tuple[str, int]: + stdout, stderr, rc = _run(["xdotool", "click", "--repeat", "2", "--delay", "100", str(button)]) + return stderr if rc else "ok", rc + + +def mouse_down(button: int = 1) -> tuple[str, int]: + stdout, stderr, rc = _run(["xdotool", "mousedown", str(button)]) + return stderr if rc else "ok", rc + + +def mouse_up(button: int = 1) -> tuple[str, int]: + stdout, stderr, rc = _run(["xdotool", "mouseup", str(button)]) + return stderr if rc else "ok", rc + + +def mouse_scroll(direction: str = "down", clicks: int = 3) -> tuple[str, int]: + btn = "5" if direction == "down" else "4" + stdout, stderr, rc = _run(["xdotool", "click", "--repeat", str(clicks), btn]) + return stderr if rc else "ok", rc + + +def get_mouse_position() -> tuple[int, int]: + stdout, _, rc = _run(["xdotool", "getmouselocation"]) + if rc == 0: + # "x:123 y:456 screen:0 window:12345678" + parts = dict(p.split(":") for p in stdout.split() if ":" in p) + return int(parts.get("x", 0)), int(parts.get("y", 0)) + return 0, 0 + + +def key_type(text: str) -> tuple[str, int]: + """Type text using xdotool, handling special characters.""" + stdout, stderr, rc = _run(["xdotool", "type", "--clearmodifiers", "--delay", "12", text]) + return stderr if rc else "ok", rc + + +def key_press(keys: str) -> tuple[str, int]: + """Press a key combination like 'ctrl+c', 'Return', 'alt+F4'.""" + stdout, stderr, rc = _run(["xdotool", "key", "--clearmodifiers", keys]) + return stderr if rc else "ok", rc + + +def key_down(key: str) -> tuple[str, int]: + stdout, stderr, rc = _run(["xdotool", "keydown", key]) + return stderr if rc else "ok", rc + + +def key_up(key: str) -> tuple[str, int]: + stdout, stderr, rc = _run(["xdotool", "keyup", key]) + return stderr if rc else "ok", rc + + +def get_active_window() -> dict: + """Get info about the currently active window.""" + stdout, _, rc = _run(["xdotool", "getactivewindow"]) + if rc != 0: + return {"error": "no active window"} + window_id = stdout.strip() + + name_out, _, _ = _run(["xdotool", "getactivewindow", "getwindowname"]) + geo_out, _, _ = _run(["xdotool", "getactivewindow", "getwindowgeometry"]) + + result = {"id": window_id, "name": name_out} + if geo_out: + for line in geo_out.splitlines(): + if "Position:" in line: + pos = line.split("Position:")[1].strip().split("(")[0].strip() + x, y = pos.split(",") + result["x"] = int(x) + result["y"] = int(y) + if "Geometry:" in line: + geo = line.split("Geometry:")[1].strip() + w, h = geo.split("x") + result["width"] = int(w) + result["height"] = int(h) + return result + + +def list_windows() -> list[dict]: + """List all visible windows.""" + stdout, _, rc = _run(["xdotool", "search", "--onlyvisible", "--name", ""]) + if rc != 0: + return [] + windows = [] + for wid in stdout.splitlines(): + wid = wid.strip() + if not wid: + continue + name_out, _, _ = _run(["xdotool", "getwindowname", wid]) + if name_out: + windows.append({"id": wid, "name": name_out}) + return windows[:50] # Cap to avoid huge responses + + +def focus_window(window_id: str) -> tuple[str, int]: + stdout, stderr, rc = _run(["xdotool", "windowactivate", window_id]) + return stderr if rc else "ok", rc + + +def drag(x1: int, y1: int, x2: int, y2: int) -> tuple[str, int]: + """Click-drag from (x1,y1) to (x2,y2).""" + _run(["xdotool", "mousemove", str(x1), str(y1)]) + _run(["xdotool", "mousedown", "1"]) + _run(["xdotool", "mousemove", "--delay", "10", str(x2), str(y2)]) + stdout, stderr, rc = _run(["xdotool", "mouseup", "1"]) + return stderr if rc else "ok", rc + + +# --------------------------------------------------------------------------- +# HTTP handler +# --------------------------------------------------------------------------- + +class ComputerUseHandler(BaseHTTPRequestHandler): + + def log_message(self, format, *args): + pass + + def _json(self, status: int, body: dict): + payload = json.dumps(body).encode() + self.send_response(status) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(payload))) + self.end_headers() + self.wfile.write(payload) + + def _image(self, data: bytes): + self.send_response(200) + self.send_header("Content-Type", "image/png") + self.send_header("Content-Length", str(len(data))) + self.end_headers() + self.wfile.write(data) + + def _body(self) -> dict: + length = int(self.headers.get("Content-Length", 0)) + if length == 0: + return {} + return json.loads(self.rfile.read(length)) + + def do_GET(self): + path = self.path.split("?")[0].rstrip("/") + + if path == "/health": + self._json(200, {"status": "ok", "service": "computer-use"}) + return + + if path == "/screen/size": + w, h = get_screen_size() + self._json(200, {"width": w, "height": h}) + return + + if path == "/screen/screenshot": + data = screenshot_png() + if data: + b64 = base64.b64encode(data).decode() + self._json(200, {"image": b64, "format": "png"}) + else: + self._json(500, {"error": "screenshot failed"}) + return + + if path == "/mouse/position": + x, y = get_mouse_position() + self._json(200, {"x": x, "y": y}) + return + + if path == "/window/active": + self._json(200, get_active_window()) + return + + if path == "/window/list": + self._json(200, {"windows": list_windows()}) + return + + self._json(404, {"error": "not found"}) + + def do_POST(self): + path = self.path.rstrip("/") + body = self._body() + + if path == "/screen/screenshot": + if "x" in body and "y" in body and "width" in body and "height" in body: + data = screenshot_region_png( + body["x"], body["y"], body["width"], body["height"] + ) + else: + data = screenshot_png() + if data: + b64 = base64.b64encode(data).decode() + self._json(200, {"image": b64, "format": "png"}) + else: + self._json(500, {"error": "screenshot failed"}) + return + + if path == "/mouse/move": + x, y = body.get("x", 0), body.get("y", 0) + msg, rc = mouse_move(x, y) + self._json(200 if rc == 0 else 500, {"result": msg}) + return + + if path == "/mouse/click": + button = body.get("button", 1) + msg, rc = mouse_click(button) + self._json(200 if rc == 0 else 500, {"result": msg}) + return + + if path == "/mouse/double_click": + button = body.get("button", 1) + msg, rc = mouse_double_click(button) + self._json(200 if rc == 0 else 500, {"result": msg}) + return + + if path == "/mouse/down": + button = body.get("button", 1) + msg, rc = mouse_down(button) + self._json(200 if rc == 0 else 500, {"result": msg}) + return + + if path == "/mouse/up": + button = body.get("button", 1) + msg, rc = mouse_up(button) + self._json(200 if rc == 0 else 500, {"result": msg}) + return + + if path == "/mouse/scroll": + direction = body.get("direction", "down") + clicks = body.get("clicks", 3) + msg, rc = mouse_scroll(direction, clicks) + self._json(200 if rc == 0 else 500, {"result": msg}) + return + + if path == "/mouse/drag": + msg, rc = drag( + body.get("x1", 0), body.get("y1", 0), + body.get("x2", 0), body.get("y2", 0), + ) + self._json(200 if rc == 0 else 500, {"result": msg}) + return + + if path == "/keyboard/type": + text = body.get("text", "") + msg, rc = key_type(text) + self._json(200 if rc == 0 else 500, {"result": msg}) + return + + if path == "/keyboard/press": + keys = body.get("keys", "Return") + msg, rc = key_press(keys) + self._json(200 if rc == 0 else 500, {"result": msg}) + return + + if path == "/keyboard/down": + key = body.get("key", "") + msg, rc = key_down(key) + self._json(200 if rc == 0 else 500, {"result": msg}) + return + + if path == "/keyboard/up": + key = body.get("key", "") + msg, rc = key_up(key) + self._json(200 if rc == 0 else 500, {"result": msg}) + return + + if path == "/window/focus": + wid = body.get("window_id", "") + msg, rc = focus_window(wid) + self._json(200 if rc == 0 else 500, {"result": msg}) + return + + # Composite action: move + click in one call + if path == "/action/click_at": + x, y = body.get("x", 0), body.get("y", 0) + button = body.get("button", 1) + mouse_move(x, y) + time.sleep(0.05) + msg, rc = mouse_click(button) + self._json(200 if rc == 0 else 500, {"result": msg, "x": x, "y": y}) + return + + # Composite action: move + double-click + if path == "/action/double_click_at": + x, y = body.get("x", 0), body.get("y", 0) + mouse_move(x, y) + time.sleep(0.05) + msg, rc = mouse_double_click() + self._json(200 if rc == 0 else 500, {"result": msg, "x": x, "y": y}) + return + + # Composite action: move + type text (click at location, then type) + if path == "/action/type_at": + x, y = body.get("x", 0), body.get("y", 0) + text = body.get("text", "") + mouse_move(x, y) + time.sleep(0.05) + mouse_click(1) + time.sleep(0.1) + msg, rc = key_type(text) + self._json(200 if rc == 0 else 500, {"result": msg}) + return + + self._json(404, {"error": "not found"}) + + +# --------------------------------------------------------------------------- +# Entry point +# --------------------------------------------------------------------------- + +def main(): + host = "127.0.0.1" + port = 7701 + server = HTTPServer((host, port), ComputerUseHandler) + print(f"Computer-use service listening on http://{host}:{port}") + try: + server.serve_forever() + except KeyboardInterrupt: + server.shutdown() + + +if __name__ == "__main__": + main() diff --git a/src/ubuntu/install/coeadapt-agent/agent/progress_tracker.py b/src/ubuntu/install/coeadapt-agent/agent/progress_tracker.py new file mode 100644 index 000000000..ccb23ae7c --- /dev/null +++ b/src/ubuntu/install/coeadapt-agent/agent/progress_tracker.py @@ -0,0 +1,415 @@ +""" +Coeadapt Progress Tracker — runs inside the Kasm VM. + +Maintains a local JSON store of the user's career development activities, +assessments, goals, and skill evidence. Exposes a lightweight HTTP API +on 127.0.0.1:7700 that the MCP server (running on the host) reaches +via `docker exec` or port-forward. + +Data is persisted to ~/.coeadapt/progress.json so it survives container +restarts (volume-mounted home directory). +""" + +import json +import os +import time +import threading +from http.server import HTTPServer, BaseHTTPRequestHandler +from pathlib import Path +from datetime import datetime, timezone + +DATA_DIR = Path.home() / ".coeadapt" +PROGRESS_FILE = DATA_DIR / "progress.json" +LOCK = threading.Lock() + +# --------------------------------------------------------------------------- +# Data helpers +# --------------------------------------------------------------------------- + +def _default_data() -> dict: + return { + "version": 1, + "activities": [], + "assessments": [], + "goals": [], + "skills": [], + "milestones": [], + "daily_log": [], + "progress_percent": 0, + "streak_days": 0, + "last_activity_at": None, + "created_at": _now(), + "updated_at": _now(), + } + + +def _now() -> str: + return datetime.now(timezone.utc).isoformat() + + +def _load() -> dict: + if PROGRESS_FILE.exists(): + try: + return json.loads(PROGRESS_FILE.read_text()) + except (json.JSONDecodeError, OSError): + pass + return _default_data() + + +def _save(data: dict) -> None: + DATA_DIR.mkdir(parents=True, exist_ok=True) + data["updated_at"] = _now() + tmp = PROGRESS_FILE.with_suffix(".tmp") + tmp.write_text(json.dumps(data, indent=2)) + tmp.replace(PROGRESS_FILE) + + +def _next_id(items: list) -> int: + if not items: + return 1 + return max(item.get("id", 0) for item in items) + 1 + + +def _recalc_progress(data: dict) -> None: + """Recalculate overall progress_percent from goals and milestones.""" + goals = data.get("goals", []) + if not goals: + data["progress_percent"] = 0 + return + completed = sum(1 for g in goals if g.get("status") == "completed") + data["progress_percent"] = round((completed / len(goals)) * 100) + + +def _update_streak(data: dict) -> None: + """Update streak_days based on daily_log entries.""" + log = data.get("daily_log", []) + if not log: + data["streak_days"] = 0 + return + today = datetime.now(timezone.utc).strftime("%Y-%m-%d") + dates = sorted(set(entry.get("date", "") for entry in log), reverse=True) + if not dates or dates[0] != today: + # Check if yesterday is present (still counts) + from datetime import timedelta + yesterday = (datetime.now(timezone.utc) - timedelta(days=1)).strftime("%Y-%m-%d") + if not dates or dates[0] != yesterday: + data["streak_days"] = 0 + return + streak = 0 + from datetime import timedelta + check_date = datetime.now(timezone.utc).date() + date_set = set(dates) + while check_date.strftime("%Y-%m-%d") in date_set: + streak += 1 + check_date -= timedelta(days=1) + data["streak_days"] = streak + + +# --------------------------------------------------------------------------- +# HTTP request handler +# --------------------------------------------------------------------------- + +class ProgressHandler(BaseHTTPRequestHandler): + """Minimal JSON API for progress tracking.""" + + def log_message(self, format, *args): + # Quiet logging + pass + + def _json_response(self, status: int, body: dict) -> None: + payload = json.dumps(body).encode() + self.send_response(status) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(payload))) + self.end_headers() + self.wfile.write(payload) + + def _read_body(self) -> dict: + length = int(self.headers.get("Content-Length", 0)) + if length == 0: + return {} + raw = self.rfile.read(length) + return json.loads(raw) + + # --- Routing --- + + def do_GET(self): + path = self.path.rstrip("/") + + if path == "/health": + self._json_response(200, {"status": "ok", "uptime": time.monotonic()}) + return + + if path == "/progress": + with LOCK: + data = _load() + self._json_response(200, data) + return + + if path == "/progress/summary": + with LOCK: + data = _load() + summary = { + "progress_percent": data.get("progress_percent", 0), + "streak_days": data.get("streak_days", 0), + "total_activities": len(data.get("activities", [])), + "total_goals": len(data.get("goals", [])), + "completed_goals": sum( + 1 for g in data.get("goals", []) if g.get("status") == "completed" + ), + "total_skills": len(data.get("skills", [])), + "total_milestones": len(data.get("milestones", [])), + "last_activity_at": data.get("last_activity_at"), + } + self._json_response(200, summary) + return + + if path == "/progress/activities": + with LOCK: + data = _load() + self._json_response(200, {"activities": data.get("activities", [])}) + return + + if path == "/progress/goals": + with LOCK: + data = _load() + self._json_response(200, {"goals": data.get("goals", [])}) + return + + if path == "/progress/skills": + with LOCK: + data = _load() + self._json_response(200, {"skills": data.get("skills", [])}) + return + + if path == "/progress/milestones": + with LOCK: + data = _load() + self._json_response(200, {"milestones": data.get("milestones", [])}) + return + + self._json_response(404, {"error": "not found"}) + + def do_POST(self): + path = self.path.rstrip("/") + + if path == "/progress/activities": + body = self._read_body() + with LOCK: + data = _load() + activity = { + "id": _next_id(data["activities"]), + "type": body.get("type", "general"), + "title": body.get("title", "Untitled"), + "description": body.get("description", ""), + "duration_minutes": body.get("duration_minutes", 0), + "tags": body.get("tags", []), + "metadata": body.get("metadata", {}), + "created_at": _now(), + } + data["activities"].append(activity) + data["last_activity_at"] = activity["created_at"] + # Log daily entry + today = datetime.now(timezone.utc).strftime("%Y-%m-%d") + data.setdefault("daily_log", []) + data["daily_log"].append({"date": today, "activity_id": activity["id"]}) + _update_streak(data) + _save(data) + self._json_response(201, activity) + return + + if path == "/progress/goals": + body = self._read_body() + with LOCK: + data = _load() + goal = { + "id": _next_id(data["goals"]), + "title": body.get("title", "Untitled Goal"), + "description": body.get("description", ""), + "category": body.get("category", "general"), + "status": "active", + "target_date": body.get("target_date"), + "sub_goals": body.get("sub_goals", []), + "created_at": _now(), + "updated_at": _now(), + } + data["goals"].append(goal) + _recalc_progress(data) + _save(data) + self._json_response(201, goal) + return + + if path == "/progress/skills": + body = self._read_body() + with LOCK: + data = _load() + skill = { + "id": _next_id(data["skills"]), + "name": body.get("name", "Unknown Skill"), + "category": body.get("category", "general"), + "level": body.get("level", "beginner"), + "evidence": body.get("evidence", []), + "verified": False, + "created_at": _now(), + "updated_at": _now(), + } + data["skills"].append(skill) + _save(data) + self._json_response(201, skill) + return + + if path == "/progress/milestones": + body = self._read_body() + with LOCK: + data = _load() + milestone = { + "id": _next_id(data["milestones"]), + "title": body.get("title", "Untitled Milestone"), + "description": body.get("description", ""), + "achieved": body.get("achieved", False), + "achieved_at": _now() if body.get("achieved") else None, + "created_at": _now(), + } + data["milestones"].append(milestone) + _save(data) + self._json_response(201, milestone) + return + + if path == "/progress/assessments": + body = self._read_body() + with LOCK: + data = _load() + assessment = { + "id": _next_id(data.get("assessments", [])), + "type": body.get("type", "self"), + "skill": body.get("skill", ""), + "score": body.get("score", 0), + "max_score": body.get("max_score", 100), + "notes": body.get("notes", ""), + "created_at": _now(), + } + data.setdefault("assessments", []).append(assessment) + _save(data) + self._json_response(201, assessment) + return + + self._json_response(404, {"error": "not found"}) + + def do_PUT(self): + path = self.path.rstrip("/") + + # PUT /progress/goals/ + if path.startswith("/progress/goals/"): + try: + goal_id = int(path.split("/")[-1]) + except ValueError: + self._json_response(400, {"error": "invalid goal id"}) + return + body = self._read_body() + with LOCK: + data = _load() + for goal in data.get("goals", []): + if goal.get("id") == goal_id: + for key in ("title", "description", "category", "status", "target_date"): + if key in body: + goal[key] = body[key] + goal["updated_at"] = _now() + if goal.get("status") == "completed" and not goal.get("completed_at"): + goal["completed_at"] = _now() + _recalc_progress(data) + _save(data) + self._json_response(200, goal) + return + self._json_response(404, {"error": "goal not found"}) + return + + # PUT /progress/skills/ + if path.startswith("/progress/skills/"): + try: + skill_id = int(path.split("/")[-1]) + except ValueError: + self._json_response(400, {"error": "invalid skill id"}) + return + body = self._read_body() + with LOCK: + data = _load() + for skill in data.get("skills", []): + if skill.get("id") == skill_id: + for key in ("name", "category", "level", "evidence", "verified"): + if key in body: + skill[key] = body[key] + skill["updated_at"] = _now() + _save(data) + self._json_response(200, skill) + return + self._json_response(404, {"error": "skill not found"}) + return + + self._json_response(404, {"error": "not found"}) + + +# --------------------------------------------------------------------------- +# Auto-sync thread (optional platform sync) +# --------------------------------------------------------------------------- + +class PlatformSyncer(threading.Thread): + """Periodically syncs local progress to the Coeadapt platform API.""" + + daemon = True + + def __init__(self, api_url: str | None = None, token: str | None = None): + super().__init__() + self.api_url = api_url or os.environ.get("COEADAPT_API_URL") + self.token = token or os.environ.get("COEADAPT_TOKEN") + + def run(self): + if not self.api_url or not self.token: + return # No platform configured — local-only mode + import urllib.request + while True: + time.sleep(300) # Sync every 5 minutes + try: + with LOCK: + data = _load() + payload = json.dumps({"progress": data}).encode() + req = urllib.request.Request( + f"{self.api_url}/api/career-box/sync-progress", + data=payload, + headers={ + "Content-Type": "application/json", + "Authorization": f"Bearer {self.token}", + }, + method="POST", + ) + urllib.request.urlopen(req, timeout=10) + except Exception: + pass # Best-effort sync + + +# --------------------------------------------------------------------------- +# Entry point +# --------------------------------------------------------------------------- + +def main(): + DATA_DIR.mkdir(parents=True, exist_ok=True) + + # Ensure progress file exists + if not PROGRESS_FILE.exists(): + _save(_default_data()) + + # Start platform syncer + syncer = PlatformSyncer() + syncer.start() + + host = "127.0.0.1" + port = 7700 + server = HTTPServer((host, port), ProgressHandler) + print(f"Progress tracker listening on http://{host}:{port}") + try: + server.serve_forever() + except KeyboardInterrupt: + server.shutdown() + + +if __name__ == "__main__": + main() diff --git a/src/ubuntu/install/coeadapt-agent/custom_startup.sh b/src/ubuntu/install/coeadapt-agent/custom_startup.sh new file mode 100644 index 000000000..3ff00c6b5 --- /dev/null +++ b/src/ubuntu/install/coeadapt-agent/custom_startup.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash + +# Coeadapt Agent startup script — keeps services running via respawn loop. +# Pattern matches other Kasm custom_startup.sh scripts. + +PGREP="python3" +AGENT_LOG="$HOME/.coeadapt/logs/agent.log" + +mkdir -p "$HOME/.coeadapt/logs" + +options=$(getopt -o gau: -l go,assign,url: -n "$0" -- "$@") || exit +eval set -- "$options" + +while [[ $1 != -- ]]; do + case $1 in + -g|--go) GO='true'; shift 1;; + -a|--assign) ASSIGN='true'; shift 1;; + -u|--url) OPT_URL=$2; shift 2;; + *) echo "bad option: $1" >&2; exit 1;; + esac +done +shift + +kasm_exec() { + /usr/bin/filter_ready + /usr/bin/desktop_ready +} + +kasm_startup() { + if [ -n "$DISABLE_CUSTOM_STARTUP" ]; then + echo "Coeadapt agent custom startup disabled" + return + fi + + # Respawn loop: keep agent services running + while true; do + if ! ss -tlnp 2>/dev/null | grep -q ":7700" || ! ss -tlnp 2>/dev/null | grep -q ":7701"; then + /usr/bin/filter_ready + /usr/bin/desktop_ready + + echo "[$(date)] Starting Coeadapt agent services..." >> "$AGENT_LOG" + /usr/local/bin/coeadapt-agent >> "$AGENT_LOG" 2>&1 + fi + sleep 5 + done +} + +if [ -n "$GO" ] || [ -n "$ASSIGN" ]; then + kasm_exec +else + kasm_startup +fi diff --git a/src/ubuntu/install/coeadapt-agent/install_coeadapt_agent.sh b/src/ubuntu/install/coeadapt-agent/install_coeadapt_agent.sh new file mode 100644 index 000000000..efb09549f --- /dev/null +++ b/src/ubuntu/install/coeadapt-agent/install_coeadapt_agent.sh @@ -0,0 +1,129 @@ +#!/usr/bin/env bash +set -ex + +# --------------------------------------------------------------------------- +# Install the Coeadapt VM Agent Services +# +# Two lightweight Python services that run inside the Kasm workspace container: +# 1. Progress Tracker (port 7700) — career progress persistence & API +# 2. Computer-Use (port 7701) — mouse, keyboard, screen control +# +# Both bind to 127.0.0.1 only (defense-in-depth: not reachable from outside). +# The MCP server on the host reaches them via `docker exec`. +# --------------------------------------------------------------------------- + +AGENT_DIR="/opt/coeadapt-agent" +STATE_DIR="/home/kasm-user/.coeadapt" + +# --- System dependencies --- +apt-get update +apt-get install -y --no-install-recommends \ + python3 \ + python3-pip \ + xdotool \ + imagemagick \ + x11-utils + +# --- Install agent code --- +mkdir -p "$AGENT_DIR" +cp -r "$(dirname "$0")/agent/"*.py "$AGENT_DIR/" +chmod 644 "$AGENT_DIR"/*.py + +# --- Create state directory --- +mkdir -p "$STATE_DIR" + +# Initialize progress.json if it doesn't exist +if [ ! -f "$STATE_DIR/progress.json" ]; then + cat > "$STATE_DIR/progress.json" <<'JSON' +{ + "version": 1, + "activities": [], + "assessments": [], + "goals": [], + "skills": [], + "milestones": [], + "daily_log": [], + "progress_percent": 0, + "streak_days": 0, + "last_activity_at": null, + "created_at": null, + "updated_at": null +} +JSON +fi + +# --- Systemd-style launcher (runs as kasm-user) --- +cat > /usr/local/bin/coeadapt-agent <<'LAUNCHER' +#!/usr/bin/env bash +# Start both agent services in the background +AGENT_DIR="/opt/coeadapt-agent" +LOG_DIR="$HOME/.coeadapt/logs" +mkdir -p "$LOG_DIR" + +# Don't start if already running +if ss -tlnp 2>/dev/null | grep -q ":7700"; then + echo "Progress tracker already running" +else + echo "[$(date)] Starting progress tracker..." >> "$LOG_DIR/progress.log" + DISPLAY=${DISPLAY:-:1} python3 "$AGENT_DIR/progress_tracker.py" >> "$LOG_DIR/progress.log" 2>&1 & +fi + +if ss -tlnp 2>/dev/null | grep -q ":7701"; then + echo "Computer-use service already running" +else + echo "[$(date)] Starting computer-use service..." >> "$LOG_DIR/computer_use.log" + DISPLAY=${DISPLAY:-:1} python3 "$AGENT_DIR/computer_use.py" >> "$LOG_DIR/computer_use.log" 2>&1 & +fi + +echo "Coeadapt agent services started" +LAUNCHER +chmod +x /usr/local/bin/coeadapt-agent + +# --- Stop script --- +cat > /usr/local/bin/coeadapt-agent-stop <<'STOP' +#!/usr/bin/env bash +# Gracefully stop agent services +pkill -f "progress_tracker.py" 2>/dev/null || true +pkill -f "computer_use.py" 2>/dev/null || true +echo "Coeadapt agent services stopped" +STOP +chmod +x /usr/local/bin/coeadapt-agent-stop + +# --- Health check script --- +cat > /usr/local/bin/coeadapt-agent-health <<'HEALTH' +#!/usr/bin/env bash +# Check health of both services +progress=$(curl -sf http://127.0.0.1:7700/health 2>/dev/null && echo "ok" || echo "down") +computer=$(curl -sf http://127.0.0.1:7701/health 2>/dev/null && echo "ok" || echo "down") +echo "{\"progress_tracker\": \"$progress\", \"computer_use\": \"$computer\"}" +HEALTH +chmod +x /usr/local/bin/coeadapt-agent-health + +# --- XFCE autostart entry --- +mkdir -p /etc/xdg/autostart +cat > /etc/xdg/autostart/coeadapt-agent.desktop <<'AUTOSTART' +[Desktop Entry] +Type=Application +Name=Coeadapt Agent +Comment=Start Coeadapt progress tracker and computer-use services +Exec=/usr/local/bin/coeadapt-agent +Hidden=false +NoDisplay=true +X-GNOME-Autostart-enabled=true +AUTOSTART + +# --- Set ownership --- +chown -R 1000:0 "$AGENT_DIR" +chown -R 1000:0 "$STATE_DIR" + +# --- Cleanup --- +chown -R 1000:0 /home/kasm-user +find /usr/share/ -name "icon-theme.cache" -exec rm -f {} \; + +if [ -z ${SKIP_CLEAN+x} ]; then + apt-get autoclean + rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* \ + /tmp/* +fi diff --git a/src/ubuntu/install/cyberbro/custom_startup.sh b/src/ubuntu/install/cyberbro/custom_startup.sh new file mode 100644 index 000000000..56445385d --- /dev/null +++ b/src/ubuntu/install/cyberbro/custom_startup.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +set -ex +START_COMMAND="cyberbro" +PGREP="firefox" +export MAXIMIZE="true" +export MAXIMIZE_NAME="Mozilla Firefox" +MAXIMIZE_SCRIPT=$STARTUPDIR/maximize_window.sh +DEFAULT_ARGS="" +ARGS=${APP_ARGS:-$DEFAULT_ARGS} + + +# Check if GUI_ENABLED_ENGINES is set else apply default +if [ -z ${GUI_ENABLED_ENGINES+x} ]; then + # Add all engines by default + GUI_ENABLED_ENGINES="" +fi + +# Make GUI_ENABLED_ENGINES an environment variable +export GUI_ENABLED_ENGINES + +# Process non-option arguments. +for arg; do + echo "arg! $arg" +done + +FORCE=$2 + +# run with vgl if GPU is available +if [ -f /opt/VirtualGL/bin/vglrun ] && [ ! -z "${KASM_EGL_CARD}" ] && [ ! -z "${KASM_RENDERD}" ] && [ -O "${KASM_RENDERD}" ] && [ -O "${KASM_EGL_CARD}" ] ; then + START_COMMAND="/opt/VirtualGL/bin/vglrun -d ${KASM_EGL_CARD} $START_COMMAND" +fi + + +kasm_startup() { + if [ -n "$KASM_URL" ] ; then + URL=$KASM_URL + elif [ -z "$URL" ] ; then + URL=$LAUNCH_URL + fi + + if [ -z "$DISABLE_CUSTOM_STARTUP" ] || [ -n "$FORCE" ] ; then + + echo "Entering process startup loop" + set +x + while true + do + if ! pgrep -x $PGREP > /dev/null + then + /usr/bin/filter_ready + /usr/bin/desktop_ready + set +e + bash ${MAXIMIZE_SCRIPT} & + $START_COMMAND $ARGS $URL + set -e + fi + sleep 1 + done + set -x + + fi +} + +kasm_startup \ No newline at end of file diff --git a/src/ubuntu/install/cyberbro/install_cyberbro.sh b/src/ubuntu/install/cyberbro/install_cyberbro.sh new file mode 100644 index 000000000..a0c44cb79 --- /dev/null +++ b/src/ubuntu/install/cyberbro/install_cyberbro.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +set -xe + +# Get latest Cyberbro version +CYBERBRO_VERSION=$(curl -sX GET "https://api.github.com/repos/stanfrbd/cyberbro/releases/latest" | awk '/tag_name/{print $4;exit}' FS='[""]') + +# Install Cyberbro +echo "Install Cyberbro" +apt-get update +apt-get install -y python3-pip git virtualenv +CYBERBRO_HOME=/opt/cyberbro +CYBERBRO_SERVER="http://127.0.0.1:5000" +mkdir -p $CYBERBRO_HOME +cd $CYBERBRO_HOME +wget https://github.com/stanfrbd/cyberbro/archive/${CYBERBRO_VERSION}.tar.gz +tar zxvf ${CYBERBRO_VERSION}.tar.gz +rm ${CYBERBRO_VERSION}.tar.gz +cd cyberbro-* + +# Enter virtualenv to avoid conflicts with system packages +virtualenv venv +source venv/bin/activate +pip3 install -r requirements.txt +deactivate + +# Set appropriate permissions +chown -R 1000:0 $CYBERBRO_HOME + +# Create a launch script +LAUNCH_SCRIPT="$CYBERBRO_HOME/cyberbro-launch.sh" +cat < "$LAUNCH_SCRIPT" +#!/usr/bin/env bash +set -ex + +check_web_server() { + curl -s -o /dev/null ${CYBERBRO_SERVER} && return 0 || return 1 +} + +# Launch Cyberbro server +cd ${CYBERBRO_HOME}/cyberbro-* +source venv/bin/activate +gunicorn -b 127.0.0.1:5000 app:app & + +retries=5 +count=0 +while ! check_web_server && [ \$count -lt \$retries ]; do + echo "Waiting for web server to start..." + sleep 1 + count=\$((count + 1)) +done + +if ! check_web_server; then + echo "Web server did not start within the expected time." + exit 1 +fi + +if [[ "\$#" -gt 0 ]]; then + firefox ${CYBERBRO_SERVER} "\$@" +else + firefox ${CYBERBRO_SERVER} +fi +EOF + + +chmod +x $LAUNCH_SCRIPT +mv $LAUNCH_SCRIPT /usr/local/bin/cyberbro + +# Cleanup for app layer +chown -R 1000:0 $HOME +find /usr/share/ -name "icon-theme.cache" -exec rm -f {} \; +if [ -z ${SKIP_CLEAN+x} ]; then + apt-get autoclean + rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* \ + /tmp/* +fi + diff --git a/src/ubuntu/install/deluge/install_deluge.sh b/src/ubuntu/install/deluge/install_deluge.sh index 4eed8b5b5..5034cc364 100644 --- a/src/ubuntu/install/deluge/install_deluge.sh +++ b/src/ubuntu/install/deluge/install_deluge.sh @@ -1,13 +1,21 @@ #!/usr/bin/env bash set -ex -apt-get update -apt-get install -y software-properties-common - -add-apt-repository -y ppa:deluge-team/stable +# Install Deluge apt-get update apt-get install -y deluge # Desktop Icon cp /usr/share/applications/deluge.desktop $HOME/Desktop/ chmod +x $HOME/Desktop/deluge.desktop + +# Cleanup for app layer +chown -R 1000:0 $HOME +find /usr/share/ -name "icon-theme.cache" -exec rm -f {} \; +if [ -z ${SKIP_CLEAN+x} ]; then + apt-get autoclean + rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* \ + /tmp/* +fi diff --git a/src/ubuntu/install/dind/custom_startup.sh b/src/ubuntu/install/dind/custom_startup.sh index 1f723ebae..b9167926a 100644 --- a/src/ubuntu/install/dind/custom_startup.sh +++ b/src/ubuntu/install/dind/custom_startup.sh @@ -2,8 +2,6 @@ set -ex START_COMMAND="/usr/bin/supervisord" PGREP="supervisord" -MAXIMIZE="false" -MAXIMIZE_SCRIPT=$STARTUPDIR/maximize_window.sh DEFAULT_ARGS="-n" ARGS=${APP_ARGS:-$DEFAULT_ARGS} @@ -27,25 +25,6 @@ done FORCE=$2 -kasm_exec() { - if [ -n "$OPT_URL" ] ; then - URL=$OPT_URL - elif [ -n "$1" ] ; then - URL=$1 - fi - - # Since we are execing into a container that already has the browser running from startup, - # when we don't have a URL to open we want to do nothing. Otherwise a second browser instance would open. - if [ -n "$URL" ] ; then - /usr/bin/filter_ready - /usr/bin/desktop_ready - bash ${MAXIMIZE_SCRIPT} & - sudo /usr/bin/supervisord -n & - else - echo "No URL specified for exec command. Doing nothing." - fi -} - kasm_startup() { if [ -n "$KASM_URL" ] ; then URL=$KASM_URL @@ -64,7 +43,6 @@ kasm_startup() { /usr/bin/filter_ready /usr/bin/desktop_ready set +e - bash ${MAXIMIZE_SCRIPT} & sudo /usr/bin/supervisord -n & set -e fi @@ -76,8 +54,4 @@ kasm_startup() { } -if [ -n "$GO" ] || [ -n "$ASSIGN" ] ; then - kasm_exec -else - kasm_startup -fi +kasm_startup diff --git a/src/ubuntu/install/dind/daemon.json b/src/ubuntu/install/dind/daemon.json index de9249ce7..514d027b9 100644 --- a/src/ubuntu/install/dind/daemon.json +++ b/src/ubuntu/install/dind/daemon.json @@ -1,3 +1,3 @@ { - "storage-driver": "vfs" + "storage-driver": "fuse-overlayfs" } diff --git a/src/ubuntu/install/dind/dockerd.conf b/src/ubuntu/install/dind/dockerd.conf index 087678937..efad6d8af 100644 --- a/src/ubuntu/install/dind/dockerd.conf +++ b/src/ubuntu/install/dind/dockerd.conf @@ -1,5 +1,5 @@ [program:dockerd] -command=/usr/local/bin/dockerd +command=/usr/local/bin/dockerd-entrypoint.sh autostart=true autorestart=true stderr_logfile=/var/log/dockerd.err.log diff --git a/src/ubuntu/install/dind/install_dind.sh b/src/ubuntu/install/dind/install_dind.sh index 100849302..6adf47dfa 100644 --- a/src/ubuntu/install/dind/install_dind.sh +++ b/src/ubuntu/install/dind/install_dind.sh @@ -1,58 +1,63 @@ #!/usr/bin/env bash set -ex +ARCH=$(arch | sed 's/aarch64/arm64/g' | sed 's/x86_64/amd64/g') +# Enable Docker repo +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - +echo "deb [arch=${ARCH}] https://download.docker.com/linux/ubuntu "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" > \ + /etc/apt/sources.list.d/docker.list && \ + +# Install deps apt-get update apt-get install -y \ ca-certificates \ curl \ dbus-user-session \ + docker-buildx-plugin \ + docker-ce \ + docker-ce-cli \ + docker-compose-plugin \ fuse-overlayfs \ - kmod \ iptables \ + kmod \ openssh-client \ sudo \ supervisor \ uidmap \ wget -rm -rf /var/lib/apt/list/* - -mkdir -p /var/log/supervisor -chown -R 1000:1000 /var/log/supervisor - -arch="$(uname --m)"; -case "$arch" in - # amd64 - x86_64) dockerArch='x86_64' ;; - # arm32v6 - armhf) dockerArch='armel' ;; - # arm32v7 - armv7) dockerArch='armhf' ;; - # arm64v8 - aarch64) dockerArch='aarch64' ;; - *) echo >&2 "error: unsupported architecture ($arch)"; exit 1 ;; -esac; - -curl -o docker.tgz "https://download.docker.com/linux/static/${DOCKER_CHANNEL}/${dockerArch}/docker-${DOCKER_VERSION}.tgz" - -tar --extract \ - --file docker.tgz \ - --strip-components 1 \ - --directory /usr/local/bin/ -rm docker.tgz - -dockerd --version -docker --version - -echo "Installing Docker Compose" -mkdir -p /usr/local/lib/docker/cli-plugins -COMPOSE_RELEASE=$(curl -sX GET "https://api.github.com/repos/docker/compose/releases/latest" \ - | awk '/tag_name/{print $4;exit}' FS='[""]'); -COMPOSE_OS=$(uname -s) -curl -L https://github.com/docker/compose/releases/download/${COMPOSE_RELEASE}/docker-compose-${COMPOSE_OS,,}-$(uname -m) -o /usr/local/lib/docker/cli-plugins/docker-compose -chmod +x /usr/local/lib/docker/cli-plugins/docker-compose +# Install dind init and hacks +useradd -U dockremap +usermod -G dockremap dockremap +echo 'dockremap:165536:65536' >> /etc/subuid +echo 'dockremap:165536:65536' >> /etc/subgid +curl -o \ + /usr/local/bin/dind -L \ + https://raw.githubusercontent.com/moby/moby/master/hack/dind +chmod +x /usr/local/bin/dind +curl -o \ + /usr/local/bin/dockerd-entrypoint.sh -L \ + https://kasm-ci.s3.amazonaws.com/dockerd-entrypoint.sh +chmod +x /usr/local/bin/dockerd-entrypoint.sh +echo 'hosts: files dns' > /etc/nsswitch.conf +usermod -aG docker kasm-user + +# Install k3d tools +wget -q -O - https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash +curl -o \ + /usr/local/bin/kubectl -L \ + "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/${ARCH}/kubectl" +chmod +x /usr/local/bin/kubectl + +# Passwordless Sudo echo 'kasm-user:kasm-user' | chpasswd echo 'kasm-user ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers -groupadd docker -adduser kasm-user docker +# Cleanup +if [ -z ${SKIP_CLEAN+x} ]; then + apt-get autoclean + rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* \ + /tmp/* +fi diff --git a/src/ubuntu/install/dind/modprobe b/src/ubuntu/install/dind/modprobe deleted file mode 100644 index b357d893f..000000000 --- a/src/ubuntu/install/dind/modprobe +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh -set -eu - -# "modprobe" without modprobe -# https://twitter.com/lucabruno/status/902934379835662336 - -# this isn't 100% fool-proof, but it'll have a much higher success rate than simply using the "real" modprobe - -# Docker often uses "modprobe -va foo bar baz" -# so we ignore modules that start with "-" -for module; do - if [ "${module#-}" = "$module" ]; then - ip link show "$module" || true - lsmod | grep "$module" || true - fi -done - -# remove /usr/local/... from PATH so we can exec the real modprobe as a last resort -export PATH='/usr/sbin:/usr/bin:/sbin:/bin' -exec modprobe "$@" diff --git a/src/ubuntu/install/dind_rootless/custom_startup.sh b/src/ubuntu/install/dind_rootless/custom_startup.sh index 840b75c27..e9606bb4d 100644 --- a/src/ubuntu/install/dind_rootless/custom_startup.sh +++ b/src/ubuntu/install/dind_rootless/custom_startup.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash set -ex -START_COMMAND="$DOCKER_BIN/dockerd-rootless.sh" +START_COMMAND="dockerd-rootless.sh" PGREP="dockerd" export MAXIMIZE="false" MAXIMIZE_SCRIPT=$STARTUPDIR/maximize_window.sh diff --git a/src/ubuntu/install/dind_rootless/daemon.json b/src/ubuntu/install/dind_rootless/daemon.json new file mode 100644 index 000000000..514d027b9 --- /dev/null +++ b/src/ubuntu/install/dind_rootless/daemon.json @@ -0,0 +1,3 @@ +{ + "storage-driver": "fuse-overlayfs" +} diff --git a/src/ubuntu/install/dind_rootless/install_dind_rootless.sh b/src/ubuntu/install/dind_rootless/install_dind_rootless.sh index a7c1751f4..130736a04 100644 --- a/src/ubuntu/install/dind_rootless/install_dind_rootless.sh +++ b/src/ubuntu/install/dind_rootless/install_dind_rootless.sh @@ -1,21 +1,53 @@ #!/usr/bin/env bash set -ex -# This script should be executed as a non-root user. -# User verification: deny running as root -if [ "$(id -u)" = "0" ]; then - >&2 echo "Refusing to install rootless Docker as the root user"; exit 1 -fi -echo "Installing Docker" -curl -fsSL https://get.docker.com/rootless | sh +# Enable Docker repo +ARCH=$(arch | sed 's/aarch64/arm64/g' | sed 's/x86_64/amd64/g') +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - +echo "deb [arch=${ARCH}] https://download.docker.com/linux/ubuntu "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" > \ + /etc/apt/sources.list.d/docker.list + +# Install deps +apt-get update +apt-get install -y \ + ca-certificates \ + curl \ + dbus-user-session \ + docker-buildx-plugin \ + docker-ce \ + docker-ce-cli \ + docker-compose-plugin \ + fuse-overlayfs \ + iptables \ + kmod \ + openssh-client \ + sudo \ + supervisor \ + uidmap \ + wget + +# URLs +STABLE_LATEST=$(curl -sL https://get.docker.com/rootless | awk -F'="' '/STABLE_LATEST=/ {print substr($2, 1, length($2)-1)}') +STATIC_RELEASE_ROOTLESS_URL="https://download.docker.com/linux/static/stable/$(uname -m)/docker-rootless-extras-${STABLE_LATEST}.tgz" -dockerd --version -docker --version +# User settings +echo 'hosts: files dns' > /etc/nsswitch.conf -echo "Installing Docker Compose" -mkdir -p "${DOCKER_BIN}"/cli-plugins -COMPOSE_RELEASE=$(curl -sX GET "https://api.github.com/repos/docker/compose/releases/latest" \ - | awk '/tag_name/{print $4;exit}' FS='[""]'); -COMPOSE_OS=$(uname -s) -curl -L https://github.com/docker/compose/releases/download/"${COMPOSE_RELEASE}"/docker-compose-"${COMPOSE_OS,,}"-"$(uname -m)" -o "${DOCKER_BIN}"/cli-plugins/docker-compose -chmod +x "${DOCKER_BIN}"/cli-plugins/docker-compose +# Install rootless extras +curl -o \ + /tmp/rootless.tgz -L \ + "${STATIC_RELEASE_ROOTLESS_URL}" +tar -xf \ + /tmp/rootless.tgz \ + --strip-components 1 \ + --directory /usr/local/bin/ \ + 'docker-rootless-extras/vpnkit' + +# Cleanup +if [ -z ${SKIP_CLEAN+x} ]; then + apt-get autoclean + rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* \ + /tmp/* +fi diff --git a/src/ubuntu/install/dind_rootless/install_dind_rootless_prerequisites.sh b/src/ubuntu/install/dind_rootless/install_dind_rootless_prerequisites.sh deleted file mode 100644 index b5a563c5d..000000000 --- a/src/ubuntu/install/dind_rootless/install_dind_rootless_prerequisites.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env bash -set -ex - -apt-get update && apt-get install -y \ - ca-certificates \ - curl \ - dbus-user-session \ - fuse-overlayfs \ - kmod \ - iptables \ - openssh-client \ - uidmap \ - wget \ - slirp4netns \ - pigz \ - xz-utils \ - iproute2 \ - xfsprogs \ - btrfs-progs \ - e2fsprogs && \ -rm -rf /var/lib/apt/list/* \ No newline at end of file diff --git a/src/ubuntu/install/discord/install_discord.sh b/src/ubuntu/install/discord/install_discord.sh index 131226ebb..7504f6503 100644 --- a/src/ubuntu/install/discord/install_discord.sh +++ b/src/ubuntu/install/discord/install_discord.sh @@ -1,10 +1,30 @@ #!/usr/bin/env bash set -ex -apt-get update +# Install Discord from deb +apt-get update curl -L -o discord.deb "https://discord.com/api/download?platform=linux&format=deb" apt-get install -y ./discord.deb rm discord.deb + +# Default config values +mkdir -p $HOME/.config/discord/ +echo '{"SKIP_HOST_UPDATE": true}' > $HOME/.config/discord/settings.json + +# Desktop file setup sed -i "s@Exec=/usr/share/discord/Discord@Exec=/usr/share/discord/Discord --no-sandbox@g" /usr/share/applications/discord.desktop cp /usr/share/applications/discord.desktop $HOME/Desktop/ -chmod +x $HOME/Desktop/discord.desktop \ No newline at end of file +chmod +x $HOME/Desktop/discord.desktop + +# Cleanup +if [ -z ${SKIP_CLEAN+x} ]; then + apt-get autoclean + rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* \ + /tmp/* +fi + +# Cleanup for app layer +chown -R 1000:0 $HOME +find /usr/share/ -name "icon-theme.cache" -exec rm -f {} \; diff --git a/src/ubuntu/install/doom/install_doom.sh b/src/ubuntu/install/doom/install_doom.sh index 3ae99f98b..cb52541d6 100644 --- a/src/ubuntu/install/doom/install_doom.sh +++ b/src/ubuntu/install/doom/install_doom.sh @@ -1,8 +1,11 @@ #!/usr/bin/env bash set -ex + +# Install Doom apt-get update apt-get install -y chocolate-doom doom-wad-shareware prboom-plus freedoom +# Custom settings mkdir -p $HOME/.local/share/chocolate-doom cat >$HOME/.local/share/chocolate-doom/chocolate-doom.cfg </usr/bin/desktop_ready </usr/bin/microsoft-edge-dev </usr/bin/microsoft-edge-stable </dev/null 2>&1 || return 1 + + # Look for any non-CPU device + DISPLAY= vulkaninfo --summary 2>/dev/null | + grep -qE 'PHYSICAL_DEVICE_TYPE_(INTEGRATED_GPU|DISCRETE_GPU|VIRTUAL_GPU)' +} + +sed -i 's/"exited_cleanly":false/"exited_cleanly":true/' ~/.config/microsoft-edge/Default/Preferences +sed -i 's/"exit_type":"Crashed"/"exit_type":"None"/' ~/.config/microsoft-edge/Default/Preferences + +VULKAN_FLAGS= +if supports_vulkan; then + VULKAN_FLAGS="--use-angle=vulkan" + echo 'vulkan supported' +fi + if [ -f /opt/VirtualGL/bin/vglrun ] && [ ! -z "\${KASM_EGL_CARD}" ] && [ ! -z "\${KASM_RENDERD}" ] && [ -O "\${KASM_RENDERD}" ] && [ -O "\${KASM_EGL_CARD}" ] ; then echo "Starting Edge with GPU Acceleration on EGL device \${KASM_EGL_CARD}" - vglrun -d "\${KASM_EGL_CARD}" /opt/microsoft/msedge-dev/microsoft-edge ${CHROME_ARGS} "\$@" + vglrun -d "\${KASM_EGL_CARD}" /opt/microsoft/msedge/microsoft-edge ${CHROME_ARGS} "\${VULKAN_FLAGS}" "\$@" else echo "Starting Edge" - /opt/microsoft/msedge-dev/microsoft-edge ${CHROME_ARGS} "\$@" + /opt/microsoft/msedge/microsoft-edge ${CHROME_ARGS} "\${VULKAN_FLAGS}" "\$@" fi EOL -chmod +x /usr/bin/microsoft-edge-dev +chmod +x /usr/bin/microsoft-edge-stable sed -i 's@exec -a "$0" "$HERE/microsoft-edge" "$\@"@@g' /usr/bin/x-www-browser cat >>/usr/bin/x-www-browser < /etc/apt/preferences.d/mozilla-firefox fi apt-get install -y firefox p11-kit-modules -elif grep -q "ID=debian" /etc/os-release || grep -q "ID=kali" /etc/os-release || grep -q "ID=parrot" /etc/os-release; then - echo \ - "deb http://deb.debian.org/debian/ unstable main contrib non-free" >> \ - /etc/apt/sources.list -cat > /etc/apt/preferences.d/99pin-unstable < /etc/apt/keyrings/packages.mozilla.org.asc + echo "deb [signed-by=/etc/apt/keyrings/packages.mozilla.org.asc] https://packages.mozilla.org/apt mozilla main" > /etc/apt/sources.list.d/mozilla.list +echo ' +Package: * +Pin: origin packages.mozilla.org +Pin-Priority: 1000 +' > /etc/apt/preferences.d/mozilla + apt-get update + apt-get install -y firefox p11-kit-modules + else + apt-get update + apt-get install -y firefox-esr p11-kit-modules + rm -f $HOME/Desktop/firefox.desktop + cp \ + /usr/share/applications/firefox-esr.desktop \ + $HOME/Desktop/ + chmod +x $HOME/Desktop/firefox-esr.desktop + fi else apt-mark unhold firefox || : apt-get remove firefox @@ -48,36 +63,64 @@ else apt-get install -y firefox p11-kit-modules fi -if [[ "${DISTRO}" == @(centos|oracle8|rockylinux9|rockylinux8|oracle9|almalinux9|almalinux8|fedora37) ]]; then - if [[ "${DISTRO}" == @(oracle8|rockylinux9|rockylinux8|oracle9|almalinux9|almalinux8|fedora37) ]]; then +# Add Langpacks +FIREFOX_VERSION=$(curl -sI https://download.mozilla.org/?product=firefox-latest | awk -F '(releases/|/win32)' '/Location/ {print $2}') +RELEASE_URL="https://releases.mozilla.org/pub/firefox/releases/${FIREFOX_VERSION}/win64/xpi/" +LANGS=$(curl -Ls ${RELEASE_URL} | awk -F '(xpi">|)' '/href.*xpi/ {print $2}' | tr '\n' ' ') +EXTENSION_DIR=/usr/lib/firefox-addons/distribution/extensions/ +mkdir -p ${EXTENSION_DIR} +for LANG in ${LANGS}; do + LANGCODE=$(echo ${LANG} | sed 's/\.xpi//g') + echo "Downloading ${LANG} Language pack" + curl -o \ + ${EXTENSION_DIR}langpack-${LANGCODE}@firefox.mozilla.org.xpi -Ls \ + ${RELEASE_URL}${LANG} +done + +# Cleanup and install flash if supported +if [[ "${DISTRO}" == @(oracle8|rockylinux9|rockylinux8|oracle9|rhel9|almalinux9|almalinux8|fedora39|fedora40|fedora41) ]]; then + if [ -z ${SKIP_CLEAN+x} ]; then dnf clean all - else - yum clean all fi elif [ "${DISTRO}" == "opensuse" ]; then - zypper clean --all + if [ -z ${SKIP_CLEAN+x} ]; then + zypper clean --all + fi else if [ "$ARCH" == "arm64" ] && [ "$(lsb_release -cs)" == "focal" ] ; then echo "Firefox flash player not supported on arm64 Ubuntu Focal Skipping" elif grep -q "ID=debian" /etc/os-release || grep -q "ID=kali" /etc/os-release || grep -q "ID=parrot" /etc/os-release; then echo "Firefox flash player not supported on Debian" - elif ! grep -q Jammy /etc/os-release; then - # Plugin to support running flash videos for sites like vimeo + elif grep -q Focal /etc/os-release; then + # Plugin to support running flash videos for sites like vimeo apt-get update apt-get install -y browser-plugin-freshplayer-pepperflash apt-mark hold firefox - apt-get clean -y + if [ -z ${SKIP_CLEAN+x} ]; then + apt-get autoclean + rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* + fi fi fi -if [[ "${DISTRO}" != @(centos|oracle8|rockylinux9|rockylinux8|oracle9|almalinux9|almalinux8|opensuse|fedora37) ]]; then +if [[ "${DISTRO}" != @(oracle8|rockylinux9|rockylinux8|oracle9|rhel9|almalinux9|almalinux8|opensuse|fedora39|fedora40|fedora41) ]]; then # Update firefox to utilize the system certificate store instead of the one that ships with firefox - rm -f /usr/lib/firefox/libnssckbi.so - ln /usr/lib/$(arch)-linux-gnu/pkcs11/p11-kit-trust.so /usr/lib/firefox/libnssckbi.so + if grep -q "ID=debian" /etc/os-release || grep -q "ID=kali" /etc/os-release || grep -q "ID=parrot" /etc/os-release && [ "${ARCH}" == "arm64" ]; then + rm -f /usr/lib/firefox-esr/libnssckbi.so + ln /usr/lib/$(arch)-linux-gnu/pkcs11/p11-kit-trust.so /usr/lib/firefox-esr/libnssckbi.so + elif grep -q "ID=kali" /etc/os-release && [ "${ARCH}" == "amd64" ]; then + rm -f /usr/lib/firefox-esr/libnssckbi.so + ln /usr/lib/$(arch)-linux-gnu/pkcs11/p11-kit-trust.so /usr/lib/firefox-esr/libnssckbi.so + else + rm -f /usr/lib/firefox/libnssckbi.so + ln /usr/lib/$(arch)-linux-gnu/pkcs11/p11-kit-trust.so /usr/lib/firefox/libnssckbi.so + fi fi -if [[ "${DISTRO}" == @(centos|oracle8|rockylinux9|rockylinux8|oracle9|almalinux9|almalinux8|fedora37) ]]; then - if [ "${DISTRO}" == "fedora37" ]; then +if [[ "${DISTRO}" == @(oracle8|rockylinux9|rockylinux8|oracle9|rhel9|almalinux9|almalinux8|fedora39|fedora40|fedora41) ]]; then + if [[ "${DISTRO}" == @(fedora39|fedora40|fedora41) ]]; then preferences_file=/usr/lib64/firefox/browser/defaults/preferences/firefox-redhat-default-prefs.js else preferences_file=/usr/lib64/firefox/browser/defaults/preferences/all-redhat.js @@ -85,10 +128,20 @@ if [[ "${DISTRO}" == @(centos|oracle8|rockylinux9|rockylinux8|oracle9|almalinux9 sed -i -e '/homepage/d' "$preferences_file" elif [ "${DISTRO}" == "opensuse" ]; then preferences_file=/usr/lib64/firefox/browser/defaults/preferences/firefox.js +elif grep -q "ID=kali" /etc/os-release; then + preferences_file=/usr/lib/firefox-esr/defaults/pref/firefox.js +elif grep -q "ID=debian" /etc/os-release || grep -q "ID=parrot" /etc/os-release; then + if [ "${ARCH}" == "amd64" ]; then + preferences_file=/usr/lib/firefox/defaults/pref/firefox.js + else + preferences_file=/usr/lib/firefox-esr/defaults/pref/firefox.js + fi else preferences_file=/usr/lib/firefox/browser/defaults/preferences/firefox.js fi -# Disabling default first run URL + +# Disabling default first run URL for Debian based images +if [[ "${DISTRO}" != @(oracle8|rockylinux9|rockylinux8|oracle9|rhel9|almalinux9|almalinux8|opensuse|fedora39|fedora40|fedora41) ]]; then cat >"$preferences_file" < $HOME/.mozilla/firefox/kasm/user.js +chown 1000:1000 $HOME/.mozilla/firefox/kasm/user.js + +# configure smartcard support +# note: some firefox versions don't read from the global pkcs11.txt when creating profiles +if [[ ${KASM_SVC_SMARTCARD:-1} == 1 ]] && [ -f "$HOME/.pki/nssdb/pkcs11.txt" ]; then + cp $HOME/.pki/nssdb/pkcs11.txt $HOME/.mozilla/firefox/kasm/pkcs11.txt + chown 1000:1000 $HOME/.mozilla/firefox/kasm/pkcs11.txt +fi + +if [[ "${DISTRO}" == @(oracle8|rockylinux9|rockylinux8|oracle9|rhel9|almalinux9|almalinux8|opensuse|fedora39|fedora40|fedora41) ]]; then set_desktop_icon fi # Starting with version 67, Firefox creates a unique profile mapping per installation which is hash generated # based off the installation path. Because that path will be static for our deployments we can assume the hash # and thus assign our profile to the default for the installation - -if [[ "${DISTRO}" != @(centos|oracle8|rockylinux9|rockylinux8|oracle9|almalinux9|almalinux8|opensuse|fedora37) ]]; then +if grep -q "ID=kali" /etc/os-release; then +cat >>$HOME/.mozilla/firefox/profiles.ini <>$HOME/.mozilla/firefox/profiles.ini <>$HOME/.mozilla/firefox/profiles.ini <>$HOME/.mozilla/firefox/profiles.ini <>$HOME/.mozilla/firefox/profiles.ini <&2; exit 1;; + esac +done +shift + +# Process non-option arguments. +for arg; do + echo "arg! $arg" +done + +FORCE=$2 + +kasm_exec() { + if [ -n "$OPT_URL" ] ; then + URL=$OPT_URL + elif [ -n "$1" ] ; then + URL=$1 + fi + + # Since we are execing into a container that already has the browser running from startup, + # when we don't have a URL to open we want to do nothing. Otherwise a second browser instance would open. + if [ -n "$URL" ] ; then + /usr/bin/filter_ready + /usr/bin/desktop_ready + $START_COMMAND $ARGS $OPT_URL + else + echo "No URL specified for exec command. Doing nothing." + fi +} + +kasm_startup() { + if [ -n "$KASM_URL" ] ; then + URL=$KASM_URL + elif [ -z "$URL" ] ; then + URL=$LAUNCH_URL + fi + + /usr/bin/filter_ready + /usr/bin/desktop_ready + set +e + $START_COMMAND $ARGS $URL + set -e + +} + +if [ -n "$GO" ] || [ -n "$ASSIGN" ] ; then + kasm_exec +else + kasm_startup +fi diff --git a/src/ubuntu/install/forensic_osint/install_forensic_osint.sh b/src/ubuntu/install/forensic_osint/install_forensic_osint.sh new file mode 100644 index 000000000..3badf28d3 --- /dev/null +++ b/src/ubuntu/install/forensic_osint/install_forensic_osint.sh @@ -0,0 +1,17 @@ +#!/bin/bash +set -ex + +# Install the Forensic OSINT extension +cat >/etc/opt/chrome/policies/managed/forensic_osint.json </opt/gimp-3/squashfs-root/launcher </usr/lib/hunchly/Hunchly </etc/opt/chrome/policies/managed/hunchly_extension.json < 60 +sed -i 's/max_frame_rate: [0-9]*/max_frame_rate: 60/' "${KASMVNC_YAML}" + +# Rect encoding quality: raise min/max +sed -i '/rect_encoding_mode:/,/video_encoding_mode:/ { + s/min_quality: [0-9]*/min_quality: 8/ + s/max_quality: [0-9]*/max_quality: 9/ + s/consider_lossless_quality: [0-9]*/consider_lossless_quality: 9/ +}' "${KASMVNC_YAML}" + +# Video encoding quality: raise JPEG and WebP +sed -i '/video_encoding_mode:/,/compare_framebuffer:/ { + s/jpeg_quality: -\?[0-9]*/jpeg_quality: 8/ + s/webp_quality: -\?[0-9]*/webp_quality: 8/ +}' "${KASMVNC_YAML}" + +# Resolution: set default to 1920x1080 +sed -i '/desktop:/,/network:/ { + s/width: [0-9]*/width: 1920/ + s/height: [0-9]*/height: 1080/ +}' "${KASMVNC_YAML}" + +echo "KasmVNC patched: 1920x1080 @ 60fps, quality 8-9/9" diff --git a/src/ubuntu/install/langpacks/install_langpacks.sh b/src/ubuntu/install/langpacks/install_langpacks.sh new file mode 100644 index 000000000..4bc75e5ab --- /dev/null +++ b/src/ubuntu/install/langpacks/install_langpacks.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -ex + +if [ "${DISTRO}" == "opensuse" ]; then + zypper search -t package "*-lang" | awk '{print $2}' > /tmp/lang-packages + rpm -qa --queryformat "%{NAME}\n" > /tmp/installed-packages + to_install="" + while read p; do + if grep -qw "^${p}-lang$" /tmp/lang-packages; then + to_install="$to_install ${p}-lang" + fi + done /dev/null + then + /usr/bin/filter_ready + /usr/bin/desktop_ready + set +e + bash ${MAXIMIZE_SCRIPT} & + $START_COMMAND $ARGS $URL & + sleep 3 + chromium-browser https://localhost:8834 --start-maximized & + set -e + fi + sleep 1 + done + set -x + + fi + +} + +kasm_startup diff --git a/src/ubuntu/install/nessus/install_nessus.sh b/src/ubuntu/install/nessus/install_nessus.sh new file mode 100644 index 000000000..156f432f1 --- /dev/null +++ b/src/ubuntu/install/nessus/install_nessus.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -ex + +ARCH=$(arch | sed 's/x86_64/amd64/g') + +apt-get update +apt-get install -y jq + +NESSUS_URL=$(curl --request GET --url https://www.tenable.com/downloads/api/v2/pages/nessus --header 'accept: application/json' | jq -r '.releases.latest[][] | select(.file_url | contains("ubuntu")) | .file_url' | grep $ARCH) +NESSUS_UPDATES_URL=$(curl --request GET --url https://www.tenable.com/downloads/api/v2/pages/nessus --header 'accept: application/json' | jq -r '.releases.latest[][] | select(.file_url | contains("nessus-updates-latest")) | .file_url') + +cd /tmp + +curl --request GET \ + --url "${NESSUS_URL}" \ + --output 'nessus.deb' + +curl --request GET \ + --url ${NESSUS_UPDATES_URL} \ + --output 'nessus-updates-latest.tar.gz' + +apt-get install -y ./nessus.deb + +rm nessus.deb + +/opt/nessus/sbin/nessuscli update /tmp/nessus-updates-latest.tar.gz + +rm nessus-updates-latest.tar.gz diff --git a/src/ubuntu/install/nextcloud/install_nextcloud.sh b/src/ubuntu/install/nextcloud/install_nextcloud.sh index 5a6dc6f03..5bf5003fd 100644 --- a/src/ubuntu/install/nextcloud/install_nextcloud.sh +++ b/src/ubuntu/install/nextcloud/install_nextcloud.sh @@ -1,19 +1,35 @@ #!/usr/bin/env bash set -ex -if [[ "${DISTRO}" == @(oracle8|rockylinux9|rockylinux8|oracle9|almalinux9|almalinux8) ]]; then +if [[ "${DISTRO}" == @(oracle8|rockylinux9|rockylinux8|oracle9|rhel9|almalinux9|almalinux8) ]]; then dnf install -y nextcloud-client - dnf clean all + if [ -z ${SKIP_CLEAN+x} ]; then + dnf clean all + fi elif [ "${DISTRO}" == "opensuse" ]; then zypper install -yn nextcloud-desktop - zypper clean --all + if [ -z ${SKIP_CLEAN+x} ]; then + zypper clean --all + fi elif grep -q "ID=debian" /etc/os-release; then apt-get update apt-get install -y nextcloud-desktop + if [ -z ${SKIP_CLEAN+x} ]; then + apt-get autoclean + rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* + fi else apt-get install -y software-properties-common add-apt-repository -y ppa:nextcloud-devs/client apt update - apt install -y nextcloud-client + apt install -y nextcloud-desktop + if [ -z ${SKIP_CLEAN+x} ]; then + apt-get autoclean + rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* + fi fi cat >$HOME/Desktop/nextcloud.desktop <&2; exit 1;; + esac +done +shift + +# Process non-option arguments. +for arg; do + echo "arg! $arg" +done + +FORCE=$2 + +kasm_exec() { + if [ -n "$OPT_URL" ] ; then + URL=$OPT_URL + elif [ -n "$1" ] ; then + URL=$1 + fi + + # Since we are execing into a container that already has the browser running from startup, + # when we don't have a URL to open we want to do nothing. Otherwise a second browser instance would open. + if [ -n "$URL" ] ; then + /usr/bin/filter_ready + /usr/bin/desktop_ready + bash ${MAXIMIZE_SCRIPT} & + $START_COMMAND $ARGS $OPT_URL + else + echo "No URL specified for exec command. Doing nothing." + fi +} + +kasm_startup() { + if [ -n "$KASM_URL" ] ; then + URL=$KASM_URL + elif [ -z "$URL" ] ; then + URL=$LAUNCH_URL + fi + + if [ -z "$DISABLE_CUSTOM_STARTUP" ] || [ -n "$FORCE" ] ; then + + echo "Entering process startup loop" + set +x + while true + do + if ! pgrep -x $PGREP > /dev/null + then + /usr/bin/filter_ready + /usr/bin/desktop_ready + set +e + bash ${MAXIMIZE_SCRIPT} & + $START_COMMAND $ARGS $URL + set -e + fi + sleep 1 + done + set -x + + fi + +} + +if [ -n "$GO" ] || [ -n "$ASSIGN" ] ; then + kasm_exec +else + kasm_startup +fi diff --git a/src/ubuntu/install/obsidian/install_obsidian.sh b/src/ubuntu/install/obsidian/install_obsidian.sh new file mode 100644 index 000000000..d38b42fe2 --- /dev/null +++ b/src/ubuntu/install/obsidian/install_obsidian.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +set -ex + +ARCH=$(arch | sed 's/aarch64/arm64/g' | sed 's/x86_64/amd64/g') + +apt-get update +apt-get install -y jq + +# Use GitHub API to get latest stable release of Obsidian +LATEST_RELEASE=$(curl -s https://api.github.com/repos/obsidianmd/obsidian-releases/releases/latest | jq -r .tag_name) + +# Use GitHub API to get download URL for amd64 +if [ "$ARCH" == "amd64" ]; then + DOWNLOAD_URL=$(curl -s https://api.github.com/repos/obsidianmd/obsidian-releases/releases/latest | jq -r '.assets[] | select(.name | test("AppImage$") and (contains("arm64") | not)) | .browser_download_url') +else + apt-get install -y zlib1g-dev libfuse2 + DOWNLOAD_URL=$(curl -s https://api.github.com/repos/obsidianmd/obsidian-releases/releases/latest | jq -r '.assets[] | select(.name | test("arm64") and test("AppImage$")) | .browser_download_url') +fi + +# Download App Image +mkdir -p /opt/Obsidian +cd /opt/Obsidian +wget -q $DOWNLOAD_URL -O Obsidian.AppImage +chmod +x Obsidian.AppImage + +# Extract and create launcher +./Obsidian.AppImage --appimage-extract +rm Obsidian.AppImage +chown -R 1000:1000 /opt/Obsidian + +cat >/opt/Obsidian/squashfs-root/launcher < /etc/apt/sources.list.d/isv:ownCloud:desktop.list" +sh -c "echo 'deb https://download.opensuse.org/repositories/isv:/ownCloud:/desktop/Ubuntu_16.04/ /' > /etc/apt/sources.list.d/isv:ownCloud:desktop.list" apt-get update apt-get install -y owncloud-client mkdir -p $HOME/.config/ownCloud diff --git a/src/ubuntu/install/parrot/install_parrot.sh b/src/ubuntu/install/parrot/install_parrot.sh index 800668286..2c9050229 100644 --- a/src/ubuntu/install/parrot/install_parrot.sh +++ b/src/ubuntu/install/parrot/install_parrot.sh @@ -21,14 +21,16 @@ apt-get install -y \ parrot-tools-forensics \ parrot-tools-reversing \ parrot-tools-cloud \ - powershell-empire- + powershell-empire- \ + codium- # Disable power manager rm -f /usr/share/xfce4/panel/plugins/power-manager-plugin.desktop # Cleanup -rm -rf \ - /var/lib/apt/lists/* \ - /var/tmp/* \ - /tmp/* - +if [ -z ${SKIP_CLEAN+x} ]; then + apt-get autoclean + rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* +fi diff --git a/src/ubuntu/install/pinta/install_pinta.sh b/src/ubuntu/install/pinta/install_pinta.sh index 6b93a64a7..9dd73e7ad 100644 --- a/src/ubuntu/install/pinta/install_pinta.sh +++ b/src/ubuntu/install/pinta/install_pinta.sh @@ -1,12 +1,64 @@ #!/usr/bin/env bash set -ex -apt-get update -apt-get install -y pinta -rm -rf \ - /var/lib/apt/lists/* \ - /var/tmp/* +# Install Pinta +# For Jammy, build pinta from source because standard package is buggy +if grep -q Jammy /etc/os-release || grep -q Noble /etc/os-release; then + # install requirements for building pinta from source + apt update -y + apt-get install -y dotnet-sdk-8.0 + apt-get install -y libgtk-3-dev + apt install -y autotools-dev autoconf-archive gettext intltool libadwaita-1-dev jq build-essential + # download and install pinta latest non-beta version from source. Use GitHub API to get the latest non-beta release + # PINTA_VERSION=$(curl -s https://api.github.com/repos/PintaProject/Pinta/releases | jq -r '[.[] | select(.prerelease == false and (.name | test("(?i)beta") | not))][0].tag_name') + # PINTA_DOWNLOAD_URL=$(curl -s https://api.github.com/repos/PintaProject/Pinta/releases | jq -r '[.[] | select(.prerelease == false and (.name | test("(?i)beta") | not))][0].assets[] | select(.name | endswith(".tar.gz")).browser_download_url') -# Default settings and desktop icon -cp /usr/share/applications/pinta.desktop $HOME/Desktop/ -chmod +x $HOME/Desktop/pinta.desktop + # Pin pinta version to 3.0.5 to avoid dependency issues with libadwaita-1 on Noble and Jammy + PINTA_VERSION="3.0.5" + PINTA_DOWNLOAD_URL="https://github.com/PintaProject/Pinta/releases/download/3.0.5/pinta-3.0.5.tar.gz" + + + wget -q ${PINTA_DOWNLOAD_URL} -O /tmp/pinta-${PINTA_VERSION}.tar.gz + tar -xvzf /tmp/pinta-${PINTA_VERSION}.tar.gz -C /tmp + cd /tmp/pinta-${PINTA_VERSION} + ./configure --prefix=/usr/local + make install + + # cleanup to reduce image size + rm -rf /tmp/pinta-${PINTA_VERSION}.tar.gz /tmp/pinta-${PINTA_VERSION} + apt remove -y libgtk-3-dev autotools-dev autoconf-archive gettext intltool libadwaita-1-dev jq build-essential + apt autoremove -y + + # create desktop file + cat >/usr/share/applications/pinta.desktop </usr/share/applications/postman.desktop < /dev/null; then + echo "$process_match is running, continuing..." + break + fi + sleep $interval + elapsed_time=$((elapsed_time + interval)) + done + + if ! pgrep -x $process_match > /dev/null + then + echo "Did not find process $process_match" + fi + +} + +kasm_startup() { + if [ -n "$KASM_URL" ] ; then + URL=$KASM_URL + elif [ -z "$URL" ] ; then + URL=$LAUNCH_URL + fi + + if [ -z "$DISABLE_CUSTOM_STARTUP" ] || [ -n "$FORCE" ] ; then + + echo "Entering process startup loop" + set +x + while true + do + if ! pgrep -x $DOCKER_PGREP > /dev/null + then + set +e + sudo /usr/bin/supervisord -n & + set -e + if [ "$REDROID_DISABLE_AUTOSTART" == "0" ]; then + start_android + fi + fi + if [ "$REDROID_DISABLE_AUTOSTART" == "0" ]; then + if ! pgrep -x $SCRCPY_PGREP > /dev/null + then + start_scrcpy + fi + fi + sleep 1 + done + set -x + + fi + +} + +/usr/bin/filter_ready +/usr/bin/desktop_ready + + +if [ "$REDROID_DISABLE_HOST_CHECKS" == "0" ]; then + check_modules +fi +kasm_startup diff --git a/src/ubuntu/install/redroid/install_redroid.sh b/src/ubuntu/install/redroid/install_redroid.sh new file mode 100644 index 000000000..198cf367f --- /dev/null +++ b/src/ubuntu/install/redroid/install_redroid.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -ex + +ARCH=$(arch | sed 's/x86_64/amd64/g') + +apt-get update +apt-get install -y android-tools-adb android-tools-fastboot \ + ffmpeg libsdl2-2.0-0 adb wget \ + gcc git pkg-config meson ninja-build libsdl2-dev \ + libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev \ + libswresample-dev libusb-1.0-0 libusb-1.0-0-dev jq + + +mkdir -p /opt/ +cd /opt/ +git clone https://github.com/Genymobile/scrcpy +cd scrcpy +./install_release.sh \ No newline at end of file diff --git a/src/ubuntu/install/remmina/install_remmina.sh b/src/ubuntu/install/remmina/install_remmina.sh index 4a0d9df2a..94adb22e5 100644 --- a/src/ubuntu/install/remmina/install_remmina.sh +++ b/src/ubuntu/install/remmina/install_remmina.sh @@ -1,29 +1,44 @@ #!/usr/bin/env bash set -ex -if [[ "${DISTRO}" == @(centos|oracle7|oracle8|oracle9|rockylinux9|rockylinux8|almalinux9|almalinux8|fedora37) ]]; then +if [[ "${DISTRO}" == @(oracle8|oracle9|rhel9|rockylinux9|rockylinux8|almalinux9|almalinux8|fedora39|fedora40|fedora41) ]]; then if [[ "${DISTRO}" == @(oracle8|rockylinux8|almalinux8) ]]; then dnf install -y remmina remmina-plugins-rdp remmina-plugins-secret remmina-plugins-spice xdotool - dnf clean all - elif [[ "${DISTRO}" == @(rockylinux9|oracle9|almalinux9|fedora37) ]]; then + if [ -z ${SKIP_CLEAN+x} ]; then + dnf clean all + fi + elif [[ "${DISTRO}" == @(rockylinux9|oracle9|rhel9|almalinux9|fedora39|fedora40|fedora41) ]]; then dnf install -y remmina remmina-plugins-rdp remmina-plugins-secret xdotool - dnf clean all - else - yum install -y remmina remmina-plugins-rdp remmina-plugins-secret remmina-plugins-spice xdotool - yum clean all + if [ -z ${SKIP_CLEAN+x} ]; then + dnf clean all + fi fi elif [ "${DISTRO}" == "opensuse" ]; then zypper install -yn remmina remmina-plugin-rdp remmina-plugin-secret remmina-plugin-spice xdotool - zypper clean --all -elif grep -q "ID=debian" /etc/os-release; then + if [ -z ${SKIP_CLEAN+x} ]; then + zypper clean --all + fi +elif grep -q "ID=debian" /etc/os-release || grep -q "VERSION_CODENAME=noble" /etc/os-release; then apt-get update - apt-get install -y remmina remmina-plugin-rdp remmina-plugin-secret remmina-plugin-spice xdotool + apt-get install -y remmina remmina-plugin-rdp remmina-plugin-secret xdotool + if [ -z ${SKIP_CLEAN+x} ]; then + apt-get autoclean + rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* + fi else apt-get update apt-get install -y software-properties-common apt-add-repository -y ppa:remmina-ppa-team/remmina-next apt-get update apt-get install -y remmina remmina-plugin-rdp remmina-plugin-secret remmina-plugin-spice xdotool + if [ -z ${SKIP_CLEAN+x} ]; then + apt-get autoclean + rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* + fi fi cp /usr/share/applications/org.remmina.Remmina.desktop $HOME/Desktop/ chmod +x $HOME/Desktop/org.remmina.Remmina.desktop @@ -68,7 +83,7 @@ window_width=640 ssh_tunnel_server= protocol=VNC disableserverinput=0 -ignore-tls-errors=1 +ignore-tls-errors=0 disableclipboard=0 EOF @@ -93,13 +108,13 @@ group= enable-autostart=0 ssh_tunnel_enabled=0 smartcardname= -gwtransp=http +gwtransp=auto domain= serialname= ssh_tunnel_auth=0 ssh_tunnel_server= loadbalanceinfo= -ignore-tls-errors=1 +ignore-tls-errors=0 clientname= base-cred-for-gw=0 sound=off @@ -161,4 +176,7 @@ shareparallel=0 viewmode=4 EOF +# Cleanup for app layer +chown -R 1000:0 $HOME +find /usr/share/ -name "icon-theme.cache" -exec rm -f {} \; chown -R 1000:1000 $DEFAULT_PROFILE_DIR diff --git a/src/ubuntu/install/remnux/install_remnux.sh b/src/ubuntu/install/remnux/install_remnux.sh index 66db9fb0f..929127d01 100644 --- a/src/ubuntu/install/remnux/install_remnux.sh +++ b/src/ubuntu/install/remnux/install_remnux.sh @@ -1,5 +1,14 @@ #!/bin/bash -set -ex +set -x + + +mkdir -p /etc/apt/keyrings +curl -fsSL https://packages.broadcom.com/artifactory/api/security/keypair/SaltProjectKey/public | sudo tee /etc/apt/keyrings/salt-archive-keyring.pgp +curl -fsSL https://github.com/saltstack/salt-install-guide/releases/latest/download/salt.sources | sudo tee /etc/apt/sources.list.d/salt.sources +apt-get update +apt-get install -y salt-common +git clone https://github.com/REMnux/salt-states.git /srv/salt + # Install remnux tools export HOME=/root @@ -14,8 +23,12 @@ apt-get purge -y \ rm -f /usr/share/xfce4/panel/plugins/power-manager-plugin.desktop rm -rf \ /root \ - /var/lib/apt/lists/* \ - /var/tmp/* \ /tmp/* mkdir /root export HOME=/home/kasm-default-profile +if [ -z ${SKIP_CLEAN+x} ]; then + apt-get autoclean + rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* +fi diff --git a/src/ubuntu/install/retroarch/install_retroarch.sh b/src/ubuntu/install/retroarch/install_retroarch.sh index 0a71c9433..5221eb966 100644 --- a/src/ubuntu/install/retroarch/install_retroarch.sh +++ b/src/ubuntu/install/retroarch/install_retroarch.sh @@ -1,32 +1,46 @@ #!/usr/bin/env bash set -ex + +# Install Retroarch SCRIPT_PATH="$( cd "$(dirname "$0")" ; pwd -P )" -add-apt-repository -y ppa:libretro/testing +add-apt-repository -y ppa:libretro/stable apt-get update -apt-get install -y retroarch -cp /usr/share/applications/retroarch.desktop $HOME/Desktop/ -chmod +x $HOME/Desktop/retroarch.desktop +apt-get install -y retroarch unzip retroarch-assets libretro-core-info + +# Desktop icon +cat >/usr/share/applications/RetroArch.desktop </usr/bin/retroarch < /dev/null + echo 'deb [arch=amd64 signed-by=/usr/share/keyrings/signal-desktop-keyring.gpg] https://updates.signal.org/desktop/apt xenial main' |\ + tee /etc/apt/sources.list.d/signal-xenial.list +else + wget -O- https://updates.signal.org/desktop/apt/keys.asc | apt-key add - + echo "deb [arch=${ARCH}] https://updates.signal.org/desktop/apt xenial main" | tee -a /etc/apt/sources.list.d/signal-xenial.list +fi +apt-get update apt-get install -y signal-desktop + +# Desktop icon +# Modify the desktop file to include --no-sandbox +sed -i 's|Exec=/opt/Signal/signal-desktop %U|Exec=/opt/Signal/signal-desktop --no-sandbox %U|' /usr/share/applications/signal-desktop.desktop cp /usr/share/applications/signal-desktop.desktop $HOME/Desktop/ chmod +x $HOME/Desktop/signal-desktop.desktop + + + +# Cleanup for app layer +chown -R 1000:0 $HOME +find /usr/share/ -name "icon-theme.cache" -exec rm -f {} \; +if [ -z ${SKIP_CLEAN+x} ]; then + apt-get autoclean + rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* \ + /tmp/* +fi diff --git a/src/ubuntu/install/slack/install_slack.sh b/src/ubuntu/install/slack/install_slack.sh index b4f4e2354..6c12de28b 100644 --- a/src/ubuntu/install/slack/install_slack.sh +++ b/src/ubuntu/install/slack/install_slack.sh @@ -7,25 +7,56 @@ if [ "${ARCH}" == "arm64" ] ; then exit 0 fi -# This might prove fragile depending on how often slack changes it's -# website though they don't have a link to always getting the latest version. -# Perhaps a python script that parses the XML could be more robust -#slack_data=$(curl "https://slack.com/downloads/linux") -#version_data=$(grep -oPm1 '(?<=)[^<]+' <<< $slack_data) -#version=$(sed -n -e 's/Version //p' <<< $version_data) -#echo "Determined slack latest version to be: ${version}" +# This might prove fragile depending on how often slack changes it's website. +version=$(curl -q https://slack.com/downloads/linux | grep page-downloads__hero__meta-text__version | sed 's/.*Version //g' | cut -d "<" -f1 | head -1) +echo Detected slack version $version -# slack latest does not run with --no-sandbox, so we have to hard code to an older version. -version=4.12.2 +if [[ "${DISTRO}" == @(oracle8|rockylinux9|rockylinux8|oracle9|rhel9|almalinux9|almalinux8|fedora39|fedora40|fedora41|opensuse) ]]; then + + wget -q https://downloads.slack-edge.com/desktop-releases/linux/x64/${version}/slack-${version}-0.1.el8.x86_64.rpm + + if [[ "${DISTRO}" == @(oracle8|rockylinux9|rockylinux8|oracle9|rhel9|almalinux9|almalinux8|fedora39|fedora40) ]]; then + dnf localinstall -y slack-${version}-0.1.el8.x86_64.rpm + if [ -z ${SKIP_CLEAN+x} ]; then + dnf clean all + fi + elif [[ "${DISTRO}" == "fedora41" ]]; then + dnf install -y slack-${version}-0.1.el8.x86_64.rpm + if [ -z ${SKIP_CLEAN+x} ]; then + dnf clean all + fi + elif [[ "${DISTRO}" == "opensuse" ]]; then + wget https://slack.com/gpg/slack_pubkey_20251016.gpg + rpm --import slack_pubkey_20251016.gpg + zypper install -yn slack-${version}-0.1.el8.x86_64.rpm + if [ -z ${SKIP_CLEAN+x} ]; then + zypper clean --all + fi + fi + + rm slack-${version}-0.1.el8.x86_64.rpm + +else + wget -q https://downloads.slack-edge.com/desktop-releases/linux/x64/${version}/slack-desktop-${version}-amd64.deb + apt-get update + apt-get install -y ./slack-desktop-${version}-${ARCH}.deb + rm slack-desktop-${version}-${ARCH}.deb + if [ -z ${SKIP_CLEAN+x} ]; then + apt-get autoclean + rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* \ + /tmp/* + fi +fi -# This path may not be accurate once arm64 support arrives. Specifically I don't know if it will still be under x64 -wget -q https://downloads.slack-edge.com/releases/linux/${version}/prod/x64/slack-desktop-${version}-${ARCH}.deb -apt-get update -apt-get install -y ./slack-desktop-${version}-${ARCH}.deb -rm slack-desktop-${version}-${ARCH}.deb sed -i 's,/usr/bin/slack,/usr/bin/slack --no-sandbox,g' /usr/share/applications/slack.desktop cp /usr/share/applications/slack.desktop $HOME/Desktop/ chmod +x $HOME/Desktop/slack.desktop chown 1000:1000 $HOME/Desktop/slack.desktop + +# Cleanup for app layer +chown -R 1000:0 $HOME +find /usr/share/ -name "icon-theme.cache" -exec rm -f {} \; diff --git a/src/ubuntu/install/smb/install_smb.sh b/src/ubuntu/install/smb/install_smb.sh index 4edf0e92b..b550e79dc 100644 --- a/src/ubuntu/install/smb/install_smb.sh +++ b/src/ubuntu/install/smb/install_smb.sh @@ -53,16 +53,9 @@ client max protocol = SMB3 #### Networking #### # The specific set of interfaces / networks to bind to -# This can be either the interface name or an IP address/netmask; -# interface names are normally preferred -; interfaces = 127.0.0.0/8 eth0 - -# Only bind to the named interfaces and/or networks; you must use the -# 'interfaces' option above to use this. -# It is recommended that you enable this feature if your Samba machine is -# not protected by a firewall or is a firewall itself. However, this -# option cannot handle dynamic or non-broadcast interfaces correctly. -; bind interfaces only = yes +# Restricted to localhost for security — only local file sharing + interfaces = 127.0.0.0/8 + bind interfaces only = yes @@ -125,7 +118,7 @@ client max protocol = SMB3 # This option controls how unsuccessful authentication attempts are mapped # to anonymous connections - map to guest = bad user + map to guest = never ########## Domains ########### @@ -191,7 +184,7 @@ client max protocol = SMB3 # Allow users who've been granted usershare privileges to create # public shares, not just authenticated ones - usershare allow guests = yes + usershare allow guests = no #======================= Share Definitions ======================= diff --git a/src/ubuntu/install/spiderfoot/custom_startup.sh b/src/ubuntu/install/spiderfoot/custom_startup.sh new file mode 100644 index 000000000..3d7087958 --- /dev/null +++ b/src/ubuntu/install/spiderfoot/custom_startup.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +set -ex +START_COMMAND="firefox" +PGREP="firefox" +export MAXIMIZE="false" +export MAXIMIZE_NAME="Mozilla Firefox" +MAXIMIZE_SCRIPT=$STARTUPDIR/maximize_window.sh +DEFAULT_FIREFOX_ARGS="" +FIREFOX_ARGS=${FIREFOX_APP_ARGS:-$DEFAULT_FIREFOX_ARGS} + +SPIDERFOOT_SERVER="127.0.0.1:5002" +DEFAULT_SPIDERFOOT_ARGS="-l $SPIDERFOOT_SERVER" +SPIDERFOOT_ARGS=${SPIDERFOOT_APP_ARGS:-$DEFAULT_SPIDERFOOT_ARGS} + +options=$(getopt -o gau: -l go,assign,url: -n "$0" -- "$@") || exit +eval set -- "$options" + +while [[ $1 != -- ]]; do + case $1 in + -g|--go) GO='true'; shift 1;; + -a|--assign) ASSIGN='true'; shift 1;; + -u|--url) OPT_URL=$2; shift 2;; + *) echo "bad option: $1" >&2; exit 1;; + esac +done +shift + +# Process non-option arguments. +for arg; do + echo "arg! $arg" +done + +FORCE=$2 + +# run with vgl if GPU is available +if [ -f /opt/VirtualGL/bin/vglrun ] && [ ! -z "${KASM_EGL_CARD}" ] && [ ! -z "${KASM_RENDERD}" ] && [ -O "${KASM_RENDERD}" ] && [ -O "${KASM_EGL_CARD}" ] ; then + START_COMMAND="/opt/VirtualGL/bin/vglrun -d ${KASM_EGL_CARD} $START_COMMAND" +fi + +check_web_server() { + curl -s -o /dev/null http://$SPIDERFOOT_SERVER && return 0 || return 1 +} + +kasm_startup() { + if [ -n "$KASM_URL" ] ; then + URL=$KASM_URL + elif [ -z "$URL" ] ; then + URL=$LAUNCH_URL + fi + + if [ -z "$DISABLE_CUSTOM_STARTUP" ] || [ -n "$FORCE" ] ; then + + echo "Entering process startup loop" + set +x + while true + do + if ! pgrep -x $PGREP > /dev/null + then + /usr/bin/filter_ready + /usr/bin/desktop_ready + cd $HOME/spiderfoot/spiderfoot-4.0/ + xfce4-terminal -x python3 sf.py $SPIDERFOOT_ARGS & + while ! check_web_server; do + sleep 1 + done + set +e + bash ${MAXIMIZE_SCRIPT} & + $START_COMMAND $FIREFOX_ARGS $URL + set -e + fi + sleep 1 + done + set -x + + fi +} + +kasm_startup diff --git a/src/ubuntu/install/spiderfoot/install_spiderfoot.sh b/src/ubuntu/install/spiderfoot/install_spiderfoot.sh new file mode 100644 index 000000000..13a2b697f --- /dev/null +++ b/src/ubuntu/install/spiderfoot/install_spiderfoot.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -xe + +SPIDERFOOT_VERSION=$(curl -sX GET "https://api.github.com/repos/smicallef/spiderfoot/releases/latest" | awk '/tag_name/{print $4;exit}' FS='[""]') + +# Install Spiderfoot +echo "Install Spiderfoot" +apt-get update +apt-get install -y python3-pip +SPIDERFOOT_HOME=$HOME/spiderfoot +mkdir -p $SPIDERFOOT_HOME +cd $SPIDERFOOT_HOME +wget https://github.com/smicallef/spiderfoot/archive/${SPIDERFOOT_VERSION}.tar.gz +tar zxvf ${SPIDERFOOT_VERSION}.tar.gz +rm ${SPIDERFOOT_VERSION}.tar.gz +cd spiderfoot-* +pip3 install -r requirements.txt + +# Cleanup for app layer +chown -R 1000:0 $HOME +find /usr/share/ -name "icon-theme.cache" -exec rm -f {} \; +if [ -z ${SKIP_CLEAN+x} ]; then + apt-get autoclean + rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* \ + /tmp/* +fi + diff --git a/src/ubuntu/install/ssl/install_ssl.sh b/src/ubuntu/install/ssl/install_ssl.sh new file mode 100644 index 000000000..9832b41c9 --- /dev/null +++ b/src/ubuntu/install/ssl/install_ssl.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +set -ex + +# ============================================================================ +# Coeadapt Workspace SSL Certificate Generator +# Generates a local CA + server certificate at Docker build time so +# KasmVNC serves HTTPS that browsers trust (after CA installation on host). +# +# Replaces the default self-signed "snakeoil" certs with a proper +# CA-signed certificate for localhost / 127.0.0.1. +# ============================================================================ + +SSL_DIR="/etc/ssl/coeadapt" +CA_DIR="${SSL_DIR}/ca" +CERT_DIR="${SSL_DIR}/server" +EXPORT_DIR="/usr/share/coeadapt" + +mkdir -p "${CA_DIR}" "${CERT_DIR}" "${EXPORT_DIR}" + +# --- Generate Certificate Authority (10-year validity) --- +openssl genrsa -out "${CA_DIR}/ca.key" 2048 + +openssl req -x509 -new -nodes \ + -key "${CA_DIR}/ca.key" \ + -sha256 -days 3650 \ + -out "${CA_DIR}/ca.crt" \ + -subj "/C=US/ST=Local/L=Localhost/O=Coeadapt/OU=Workspace/CN=Coeadapt Workspace CA" + +# --- Generate Server Certificate signed by the CA (5-year validity) --- +openssl genrsa -out "${CERT_DIR}/server.key" 2048 + +openssl req -new \ + -key "${CERT_DIR}/server.key" \ + -out "${CERT_DIR}/server.csr" \ + -subj "/C=US/ST=Local/L=Localhost/O=Coeadapt/OU=Workspace/CN=localhost" + +# SAN extension — browsers require Subject Alternative Names +cat > "${CERT_DIR}/server.ext" << 'EXTEOF' +authorityKeyIdentifier=keyid,issuer +basicConstraints=CA:FALSE +keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment +subjectAltName = @alt_names + +[alt_names] +DNS.1 = localhost +IP.1 = 127.0.0.1 +IP.2 = ::1 +EXTEOF + +openssl x509 -req \ + -in "${CERT_DIR}/server.csr" \ + -CA "${CA_DIR}/ca.crt" \ + -CAkey "${CA_DIR}/ca.key" \ + -CAcreateserial \ + -out "${CERT_DIR}/server.crt" \ + -days 1825 \ + -sha256 \ + -extfile "${CERT_DIR}/server.ext" + +# --- Replace KasmVNC default snakeoil certs --- +cp "${CERT_DIR}/server.crt" /etc/ssl/certs/ssl-cert-snakeoil.pem +cp "${CERT_DIR}/server.key" /etc/ssl/private/ssl-cert-snakeoil.key +chmod 644 /etc/ssl/certs/ssl-cert-snakeoil.pem +chmod 640 /etc/ssl/private/ssl-cert-snakeoil.key + +# --- Export CA cert for host trust-store installation --- +cp "${CA_DIR}/ca.crt" "${EXPORT_DIR}/ca.crt" +chmod 644 "${EXPORT_DIR}/ca.crt" + +# --- Cleanup intermediate files --- +rm -f "${CERT_DIR}/server.csr" "${CERT_DIR}/server.ext" "${CA_DIR}/ca.srl" + +echo "=== Coeadapt SSL setup complete ===" +echo " CA cert (export to host): ${EXPORT_DIR}/ca.crt" +echo " Server cert: /etc/ssl/certs/ssl-cert-snakeoil.pem" +echo " Server key: /etc/ssl/private/ssl-cert-snakeoil.key" diff --git a/src/ubuntu/install/steam/install_steam.sh b/src/ubuntu/install/steam/install_steam.sh index 2a4f4a559..4a9791bfe 100644 --- a/src/ubuntu/install/steam/install_steam.sh +++ b/src/ubuntu/install/steam/install_steam.sh @@ -1,9 +1,22 @@ #!/usr/bin/env bash set -ex + +# Install Steam dpkg --add-architecture i386 apt-get update - apt-get install -y steam-installer + +# Desktop icon cp /usr/share/applications/steam.desktop $HOME/Desktop/ chmod +x $HOME/Desktop/steam.desktop -chown 1000:1000 $HOME/Desktop/steam.desktop + +# Cleanup for app layer +chown -R 1000:0 $HOME +find /usr/share/ -name "icon-theme.cache" -exec rm -f {} \; +if [ -z ${SKIP_CLEAN+x} ]; then + apt-get autoclean + rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* \ + /tmp/* +fi diff --git a/src/ubuntu/install/sublime_text/install_sublime_text.sh b/src/ubuntu/install/sublime_text/install_sublime_text.sh index c81114244..1213eaed0 100644 --- a/src/ubuntu/install/sublime_text/install_sublime_text.sh +++ b/src/ubuntu/install/sublime_text/install_sublime_text.sh @@ -1,14 +1,34 @@ #!/usr/bin/env bash set -ex +# Install Sublime Text apt-get update -wget -qO - https://download.sublimetext.com/sublimehq-pub.gpg | apt-key add - apt-get install -y apt-transport-https -echo "deb https://download.sublimetext.com/ apt/stable/" | tee /etc/apt/sources.list.d/sublime-text.list -apt-get update -apt-get install -y sublime-text +# apt-key is deprecated in trixie and later, use keyrings instead +if grep -q "trixie" /etc/os-release; then + mkdir -p /usr/share/keyrings + wget -qO - https://download.sublimetext.com/sublimehq-pub.gpg | tee /etc/apt/keyrings/sublimehq-pub.asc > /dev/null + echo -e 'Types: deb\nURIs: https://download.sublimetext.com/\nSuites: apt/stable/\nSigned-By: /etc/apt/keyrings/sublimehq-pub.asc' | tee /etc/apt/sources.list.d/sublime-text.sources + apt-get update && apt-get install -y sublime-text +else + wget -qO - https://download.sublimetext.com/sublimehq-pub.gpg | apt-key add - + echo "deb https://download.sublimetext.com/ apt/stable/" | tee /etc/apt/sources.list.d/sublime-text.list + apt-get update + apt-get install -y sublime-text +fi +# Desktop icon cp /usr/share/applications/sublime_text.desktop $HOME/Desktop/ chmod +x $HOME/Desktop/sublime_text.desktop -chown 1000:1000 $HOME/Desktop/sublime_text.desktop + +# Cleanup for app layer +chown -R 1000:0 $HOME +find /usr/share/ -name "icon-theme.cache" -exec rm -f {} \; +if [ -z ${SKIP_CLEAN+x} ]; then + apt-get autoclean + rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* \ + /tmp/* +fi diff --git a/src/ubuntu/install/super_tux_kart/install_super_tux_kart.sh b/src/ubuntu/install/super_tux_kart/install_super_tux_kart.sh index 2d2f6f44e..ffe5a1973 100644 --- a/src/ubuntu/install/super_tux_kart/install_super_tux_kart.sh +++ b/src/ubuntu/install/super_tux_kart/install_super_tux_kart.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash set -ex + +# VERSION="1.3" ARCH=$(uname -m | sed 's/aarch64/arm64/g' | sed 's/x86_64/amd64/g') build=64bit @@ -49,3 +51,14 @@ cat >/usr/bin/desktop_ready </usr/share/applications/telegram.desktop < /dev/null + echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com bookworm main" \ + > /etc/apt/sources.list.d/hashicorp.list +else + curl -fsSL https://apt.releases.hashicorp.com/gpg | apt-key add - + echo \ + "deb [arch=$(dpkg --print-architecture)] https://apt.releases.hashicorp.com $(lsb_release -cs) main" \ + > /etc/apt/sources.list.d/hashicorp.list +fi apt-get update apt-get install -y terraform + +# Cleanup +chown -R 1000:0 $HOME +find /usr/share/ -name "icon-theme.cache" -exec rm -f {} \; +if [ -z ${SKIP_CLEAN+x} ]; then + apt-get autoclean + rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* \ + /tmp/* +fi diff --git a/src/ubuntu/install/thunderbird/install_thunderbird.sh b/src/ubuntu/install/thunderbird/install_thunderbird.sh index 37c0a94c3..3cc3044f0 100644 --- a/src/ubuntu/install/thunderbird/install_thunderbird.sh +++ b/src/ubuntu/install/thunderbird/install_thunderbird.sh @@ -2,20 +2,25 @@ set -ex # Install -if [[ "${DISTRO}" == @(centos|oracle8|rockylinux9|rockylinux8|oracle9|almalinux9|almalinux8|fedora37) ]]; then - if [[ "${DISTRO}" == @(oracle8|rockylinux9|rockylinux8|oracle9|almalinux9|almalinux8|fedora37) ]]; then - dnf install -y thunderbird +if [[ "${DISTRO}" == @(oracle8|rockylinux9|rockylinux8|oracle9|rhel9|almalinux9|almalinux8|fedora39|fedora40|fedora41) ]]; then + dnf install -y thunderbird + if [ -z ${SKIP_CLEAN+x} ]; then dnf clean all - else - yum install -y thunderbird - yum clean all fi elif [ "${DISTRO}" == "opensuse" ]; then zypper install -yn MozillaThunderbird - zypper clean --all + if [ -z ${SKIP_CLEAN+x} ]; then + zypper clean --all + fi elif grep -q "ID=debian" /etc/os-release; then apt-get update apt-get install -y thunderbird + if [ -z ${SKIP_CLEAN+x} ]; then + apt-get autoclean + rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* + fi else apt-get update if [ ! -f '/etc/apt/preferences.d/mozilla-firefox' ]; then @@ -27,16 +32,29 @@ Pin-Priority: 1001 ' > /etc/apt/preferences.d/mozilla-firefox fi apt-get install -y thunderbird + if [ -z ${SKIP_CLEAN+x} ]; then + apt-get autoclean rm -rf \ /var/lib/apt/lists/* \ /var/tmp/* + fi fi # Desktop icon -if [ "${DISTRO}" == "fedora37" ]; then +if [[ "${DISTRO}" == "fedora39" ]]; then cp /usr/share/applications/mozilla-thunderbird.desktop $HOME/Desktop/ chmod +x $HOME/Desktop/mozilla-thunderbird.desktop +elif [[ "${DISTRO}" == @(fedora40|fedora41) ]]; then + cp /usr/share/applications/net.thunderbird.Thunderbird.desktop $HOME/Desktop/ + chmod +x $HOME/Desktop/net.thunderbird.Thunderbird.desktop +elif [[ "${DISTRO}" == "opensuse" ]]; then + cp /usr/share/applications/thunderbird-esr.desktop $HOME/Desktop/ + chmod +x $HOME/Desktop/thunderbird-esr.desktop else cp /usr/share/applications/thunderbird.desktop $HOME/Desktop/ chmod +x $HOME/Desktop/thunderbird.desktop fi + +# Cleanup for app layer +chown -R 1000:0 $HOME +find /usr/share/ -name "icon-theme.cache" -exec rm -f {} \; diff --git a/src/ubuntu/install/tools/install_tools_deluxe.sh b/src/ubuntu/install/tools/install_tools_deluxe.sh index edf169d37..0d1acbf47 100644 --- a/src/ubuntu/install/tools/install_tools_deluxe.sh +++ b/src/ubuntu/install/tools/install_tools_deluxe.sh @@ -1,5 +1,6 @@ #!/usr/bin/env bash set -ex + apt-get update apt-get install -y vlc git tmux diff --git a/src/ubuntu/install/torbrowser/install_torbrowser.sh b/src/ubuntu/install/torbrowser/install_torbrowser.sh index 76550097f..9c6baf919 100644 --- a/src/ubuntu/install/torbrowser/install_torbrowser.sh +++ b/src/ubuntu/install/torbrowser/install_torbrowser.sh @@ -7,9 +7,9 @@ TOR_HOME=$HOME/tor-browser/ mkdir -p $TOR_HOME if [ "$(arch)" == "aarch64" ]; then SF_VERSION=$(curl -sI https://sourceforge.net/projects/tor-browser-ports/files/latest/download | awk -F'(ports/|/tor)' '/location/ {print $3}') - FULL_TOR_URL="https://downloads.sourceforge.net/project/tor-browser-ports/${SF_VERSION}/tor-browser-linux-arm64-${SF_VERSION}_ALL.tar.xz" + FULL_TOR_URL="https://downloads.sourceforge.net/project/tor-browser-ports/${SF_VERSION}/tor-browser-linux-arm64-${SF_VERSION}.tar.xz" else - TOR_URL=$(curl -q https://www.torproject.org/download/ | grep downloadLink | grep linux64 | sed 's/.*href="//g' | cut -d '"' -f1 | head -1) + TOR_URL=$(curl -q https://www.torproject.org/download/ | grep downloadLink | grep linux | sed 's/.*href="//g' | cut -d '"' -f1 | head -1) FULL_TOR_URL="https://www.torproject.org/${TOR_URL}" fi wget --quiet "${FULL_TOR_URL}" -O /tmp/torbrowser.tar.xz @@ -51,3 +51,14 @@ chown -R 1000:0 $TOR_HOME/ cp $TOR_HOME/tor-browser/start-tor-browser.desktop $HOME/Desktop/ chown 1000:0 $HOME/Desktop/start-tor-browser.desktop + +# Cleanup for app layer +chown -R 1000:0 $HOME +find /usr/share/ -name "icon-theme.cache" -exec rm -f {} \; +if [ -z ${SKIP_CLEAN+x} ]; then + apt-get autoclean + rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* \ + /tmp/* +fi diff --git a/src/ubuntu/install/tracelabs/install_tracelabs.sh b/src/ubuntu/install/tracelabs/install_tracelabs.sh index ece7b35a2..33d4c0224 100644 --- a/src/ubuntu/install/tracelabs/install_tracelabs.sh +++ b/src/ubuntu/install/tracelabs/install_tracelabs.sh @@ -2,6 +2,24 @@ set -e set -x +# Install kali tools +apt-get update +apt-get install -y \ + kali-tools-top10 \ + autopsy \ + cutycapt \ + dirbuster \ + faraday \ + fern-wifi-cracker \ + guymager \ + hydra \ + legion \ + ophcrack \ + ophcrack-cli \ + python3-greenlet \ + python3-zope.event \ + sqlitebrowser + cd /tmp/ git clone https://github.com/tracelabs/tlosint-live.git cd /tmp/tlosint-live/ @@ -14,17 +32,24 @@ rsync -aviu kali-config/common/includes.chroot/usr/ /usr/ mv /etc/skel/Desktop/*.pdf $HOME/Desktop/ -#### Install all tracelabs image packages #### -# rm lines with # | Delete Empty lines | -cat kali-config/variant-tracelabs/package-lists/kali.list.chroot | sed '/^#/d' | sed '/^$/d' | xargs --no-run-if-empty apt-get install -y + +#### Install all tracelabs image packages #### +cat kali-config/variant-tracelabs/package-lists/kali.list.chroot \ + | sed '/^#/d' \ + | sed '/^$/d' \ + | sed '/firefox-esr/d' \ + | sed '/kali-desktop-xfce/d' \ + | sed '/outguess/d' \ + | xargs --no-run-if-empty apt-get install -y + +sed -i '/m4ll0k/,+3d' kali-config/common/hooks/normal/osint-packages.chroot sh kali-config/common/hooks/normal/osint-packages.chroot chown -R 1000:1000 \ /usr/share/phoneinfoga \ /usr/share/Spiderpig \ /usr/share/DumpsterDiver \ - /usr/share/Infoga \ /usr/share/LittleBrother \ /usr/share/sn0int \ /usr/share/buster \ @@ -37,40 +62,24 @@ chown -R 1000:1000 \ apt-get install -y python3-pip -pip3 install --break-system-packages --force-reinstall zope.event - sed -i 's/sudo //g' /usr/share/applications/tl*.desktop ### Remove stuff we install later properly apt-get purge -y \ - firefox-esr \ chromium -rm -f /usr/share/xfce4/panel/plugins/power-manager-plugin.desktop -# Install kali tools -apt-get update -apt-get install -y \ - kali-tools-top10 \ - autopsy \ - cutycapt \ - dirbuster \ - faraday \ - fern-wifi-cracker \ - guymager \ - hydra-gtk \ - king-phisher \ - legion \ - ophcrack \ - ophcrack-cli \ - sqlitebrowser +### Install Pulseaudio once again to remove pipewire +apt-get install -y pulseaudio ### Cleanup echo "exit 0" > /usr/bin/blueman-applet rm -f /usr/share/xfce4/panel/plugins/power-manager-plugin.desktop -rm -rf \ - /var/lib/apt/lists/* \ - /var/tmp/* \ - /tmp/* +if [ -z ${SKIP_CLEAN+x} ]; then + apt-get autoclean + rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* +fi rm -Rf /root mkdir -p /root rm -rf /tmp/tlosint-live diff --git a/src/ubuntu/install/unityhub/install_unityhub.sh b/src/ubuntu/install/unityhub/install_unityhub.sh index 2bb67d6da..bc03ebbd4 100644 --- a/src/ubuntu/install/unityhub/install_unityhub.sh +++ b/src/ubuntu/install/unityhub/install_unityhub.sh @@ -1,5 +1,6 @@ #!/usr/bin/env bash +# Install UnityHub # Adapted from https://docs.unity3d.com/hub/manual/InstallHub.html#install-hub-linux SCRIPT_PATH="$( cd "$(dirname "$0")" ; pwd -P )" set -ex @@ -8,14 +9,23 @@ wget -qO - https://hub.unity3d.com/linux/keys/public | apt-key add - apt-get update apt-get install -y unityhub +# Desktop icon sed -i 's,/opt/unityhub/unityhub,/opt/unityhub/unityhub --no-sandbox,g' /usr/share/applications/unityhub.desktop - - cp /usr/share/applications/unityhub.desktop $HOME/Desktop/ chmod +x $HOME/Desktop/unityhub.desktop -chown 1000:1000 $HOME/Desktop/unityhub.desktop +# Cleanup for app layer +chown -R 1000:0 $HOME +find /usr/share/ -name "icon-theme.cache" -exec rm -f {} \; +if [ -z ${SKIP_CLEAN+x} ]; then + apt-get autoclean + rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* \ + /tmp/* +fi + # Example for pre-installing a unity Editor #mkdir -p $HOME/Unity/Hub/Editor/2021.3.6f1 #cd /tmp/ @@ -23,4 +33,4 @@ chown 1000:1000 $HOME/Desktop/unityhub.desktop #chmod +x ./UnitySetup #yes | ./UnitySetup -u -l $HOME/Unity/Hub/Editor/2021.3.6f1 #rm /tmp/UnitySetup -#chown -R 1000:1000 $HOME/Unity \ No newline at end of file +#chown -R 1000:1000 $HOME/Unity diff --git a/src/ubuntu/install/vivaldi/install_vivaldi.sh b/src/ubuntu/install/vivaldi/install_vivaldi.sh index 44e84e2ae..383feb057 100644 --- a/src/ubuntu/install/vivaldi/install_vivaldi.sh +++ b/src/ubuntu/install/vivaldi/install_vivaldi.sh @@ -21,6 +21,7 @@ set -e # Add Desktop Icon cp /usr/share/applications/vivaldi-stable.desktop $HOME/Desktop/ chown 1000:1000 $HOME/Desktop/vivaldi-stable.desktop +chmod +x $HOME/Desktop/vivaldi-stable.desktop # Use wrapper to launch application mv /opt/vivaldi/vivaldi /opt/vivaldi/vivaldi-orig @@ -51,8 +52,13 @@ cat >>/etc/opt/chrome/policies/managed/default_managed_policy.json </dev/null + curl -fsSL https://pkgs.tailscale.com/stable/${ID}/${FLAVOR}.tailscale-keyring.list | tee /etc/apt/sources.list.d/tailscale.list + apt-get update + apt-get install -y --no-install-recommends tailscale +else + if [[ "${VERSION}" == "8" ]] || [[ "${VERSION}" = "8*" ]]; then + dnf install -y 'dnf-command(config-manager)' + dnf config-manager --add-repo https://pkgs.tailscale.com/stable/centos/8/tailscale.repo + dnf install -y tailscale + elif [[ "${VERSION}" == "9" ]] || [[ "${VERSION}" = "9*" ]]; then + dnf install -y 'dnf-command(config-manager)' + dnf config-manager --add-repo https://pkgs.tailscale.com/stable/centos/9/tailscale.repo + dnf install -y tailscale + elif [[ "${ID}" == "fedora" ]]; then + dnf install -y 'dnf-command(config-manager)' + dnf config-manager --add-repo https://pkgs.tailscale.com/stable/fedora/${VERSION2}/tailscale.repo + dnf install -y tailscale + elif [[ "${ID}" == "\"opensuse-leap\"" ]]; then + zypper ar -g -r https://pkgs.tailscale.com/stable/opensuse/leap/15.5/tailscale.repo + zypper --gpg-auto-import-keys ref + zypper install -ny tailscale + fi +fi + +# Tweaks to wg-up +sed -i '/cmd sysctl -q/d' $(which wg-quick) + +# Copy startup script +cp ${INST_DIR}/ubuntu/install/vpn/start_vpn.sh /dockerstartup/start_vpn.sh +chmod +x /dockerstartup/start_vpn.sh diff --git a/src/ubuntu/install/vpn/start_vpn.sh b/src/ubuntu/install/vpn/start_vpn.sh new file mode 100644 index 000000000..d2504a261 --- /dev/null +++ b/src/ubuntu/install/vpn/start_vpn.sh @@ -0,0 +1,223 @@ +#!/usr/bin/env bash +set -x + +ICON_SUCCESS="/usr/share/icons/ubuntu-mono-dark/status/22/nm-signal-100-secure.svg" +ICON_ERROR="/usr/share/icons/ubuntu-mono-dark/status/22/network-error.svg" +ICON_OFFLINE="/usr/share/icons/ubuntu-mono-dark/status/22/network-offline.svg" +OPENVPN_STATUS_LOG="/tmp/openvpn-status.log" +DEFAULT_OPENVPN_CONFIG="/dockerstartup/openvpn.conf" +DEFAULT_OPENVPN_CREDS="/dockerstartup/openvpn-auth" +DEFAULT_WIREGUARD_CONFIG="/dockerstartup/wireguard.conf" + +SHOW_VPN_STATUS_DEFAULT="1" +SHOW_VPN_STAT=${SHOW_VPN_STATUS:-$SHOW_VPN_STATUS_DEFAULT} + +SHOW_IP_STATUS_DEFAULT="1" +SHOW_IP_STAT=${SHOW_IP_STATUS:-$SHOW_IP_STATUS_DEFAULT} + +# Logging and trap +LOGFILE="/dockerstartup/vpn_start.log" +function notify_err() { + local exit_code=$? + local failed_command="$BASH_COMMAND" + msg="An error occurred with exit code $exit_code while executing: $failed_command.\n\nPlease review the log at ${LOGFILE}" + echo msg + notify-send -u critical -t 0 -i "${ICON_ERROR}" "VPN Configuration Failed" "${msg}" + exit $exit_code + +} +function cleanup_log() { + rm -f ${LOGFILE} +} +trap notify_err ERR +exec &> >(tee ${LOGFILE}) + +# If user input is needed for openvpn +function get_set_creds() { + CREDENTIALS=$(zenity --forms --title="VPN credentials" --text="Enter your VPN auth credentials" --add-entry="Username" --add-password="Password" --separator ",,,,,,") + USER=$(awk -F',,,,,,' '{print $1}' <<<$CREDENTIALS) + PASS=$(awk -F',,,,,,' '{print $2}' <<<$CREDENTIALS) + echo ${USER} > /home/kasm-user/vpn_credentials + echo ${PASS} >> /home/kasm-user/vpn_credentials + chown kasm-user:kasm-user /home/kasm-user/vpn_credentials + cp ${VPN_CONFIG} /home/kasm-user/vpn.ovpn + chown kasm-user:kasm-user /home/kasm-user/vpn.ovpn + sed -i "s#auth-user-pass#auth-user-pass /home/kasm-user/vpn_credentials#g" /home/kasm-user/vpn.ovpn + VPN_CONFIG=/home/kasm-user/vpn.ovpn +} + +function tailscale_status() { + if [ "$SHOW_VPN_STAT" == "1" ]; then + tailscale_status=$(tailscale status) + notify-send -u critical -t 0 -i "${ICON_SUCCESS}" "Tailscale Status" "${tailscale_status}" + fi +} + +function ip_status(){ + if [ "$SHOW_IP_STAT" == "1" ]; then + ip_status=$(curl https://ipinfo.io/json) + notify-send -u critical -t 0 -i "${ICON_SUCCESS}" "Public IP Status" "${ip_status}" + fi +} + +function process_tailscale(){ + + local tailscale_key=$1 + + if [ ! -c /dev/net/tun ]; then + mkdir -p /dev/net + mknod /dev/net/tun c 10 200 + fi + tailscaled & + sleep 2 + set +e + tailscale up --authkey=${tailscale_key} + if [ $? -ne 0 ]; then + msg="Failed to establish tailscale connection. Please review the log at ${LOGFILE}" + echo msg + notify-send -u critical -t 0 -i "${ICON_ERROR}" "VPN Configuration Failed" "${msg}" + exit 1 + fi + set -e + + notify-send -u critical -t 0 -i "${ICON_SUCCESS}" "VPN Connected!" "Tailscale VPN Connected Successfully" + tailscale_status + ip_status + cleanup_log + exit 0 +} + + +function openvpn_status() { + if [ "$SHOW_VPN_STAT" == "1" ]; then + openvpn_status=$(cat ${OPENVPN_STATUS_LOG}) + notify-send -u critical -t 0 -i "${ICON_SUCCESS}" "OpenVPN Status" "${openvpn_status}" + fi +} + + +function process_openvpn() { + local openvpn_config=$1 + + # Create tun device + if [ ! -c /dev/net/tun ]; then + mkdir -p /dev/net + mknod /dev/net/tun c 10 200 + fi + if which resolvconf; then + openvpn --pull-filter ignore route-ipv6 --pull-filter ignore ifconfig-ipv6 --config "${openvpn_config}" --status ${OPENVPN_STATUS_LOG} & + sleep 10 + if ! pgrep openvpn; then + msg="An error has occurred starting the VPN please review the log at ${LOGFILE}" + echo msg + notify-send -u critical -t 0 -i "${ICON_ERROR}" "VPN Configuration Failed" "${msg}" + exit 1 + else + notify-send -u critical -t 0 -i "${ICON_SUCCESS}" "VPN Connected!" "OpenVPN Connected Successfully" + openvpn_status + ip_status + cleanup_log + exit 0 + fi + else + msg="Resolvconf is not found on this system this container is not compatible with OpenVPN" + echo msg + notify-send -u critical -t 0 -i "${ICON_ERROR}" "VPN Configuration Failed" "${msg}" + exit 1 + fi +} + +function wireguard_status() { + if [ "$SHOW_VPN_STAT" == "1" ]; then + wg_show=$(wg show) + notify-send -u critical -t 0 -i "${ICON_SUCCESS}" "WireGuard Status" "${wg_show}" + fi +} + + +function process_wireguard() { + + local wireguard_conf=$1 + echo "WireGuard config detected checking for support" + if ip link add dev test type wireguard; then + echo "WireGuard kernel module is present on this host continuing" + ip link del dev test + else + msg="WireGuard kernel module is not present on this host and a WireGuard config was passed will not continue" + echo msg + notify-send -u critical -t 0 -i "${ICON_ERROR}" "VPN Configuration Failed" "${msg}" + exit 1 + fi + + wg-quick up ${wireguard_conf} + if [ $? -ne 0 ]; then + msg="Failed to establish WireGuard connection. Please review the log at ${LOGFILE}" + echo msg + notify-send -u critical -t 0 -i "${ICON_ERROR}" "VPN Configuration Failed" "${msg}" + exit 1 + fi + notify-send -u critical -t 0 -i "${ICON_SUCCESS}" "VPN Connected!" "WireGuard VPN Connected Successfully" + wireguard_status + ip_status + cleanup_log + exit 0 + +} + +notify-send -u critical -t 0 -i "${ICON_OFFLINE}" "Connecting to VPN" "Please wait while the VPN connection is being established..." + +VPN_LAUNCH_CONFIG='/dockerstartup/launch_selections.json' +# Launch Config Based Workflow +if [ -e ${VPN_LAUNCH_CONFIG} ]; then + + VPN_SERVICE="$(jq -r '.vpn_service' ${VPN_LAUNCH_CONFIG})" + + if [ "${VPN_SERVICE}" == "tailscale" ]; then + ts_key="$(jq -r '.tailscale_key' ${VPN_LAUNCH_CONFIG})" + process_tailscale $ts_key + + elif [ "${VPN_SERVICE}" == "openvpn" ]; then + OPENVPN_USERNAME="$(jq -r '.openvpn_username' ${VPN_LAUNCH_CONFIG})" + OPENVPN_PASSWORD="$(jq -r '.openvpn_password' ${VPN_LAUNCH_CONFIG})" + echo ${OPENVPN_USERNAME} > ${DEFAULT_OPENVPN_CREDS} + echo ${OPENVPN_PASSWORD} >> ${DEFAULT_OPENVPN_CREDS} + jq -r '.openvpn_config' ${VPN_LAUNCH_CONFIG} > ${DEFAULT_OPENVPN_CONFIG} + sed -i "s#auth-user-pass#auth-user-pass ${DEFAULT_OPENVPN_CREDS}#g" ${DEFAULT_OPENVPN_CONFIG} + process_openvpn $DEFAULT_OPENVPN_CONFIG + + elif [ "${VPN_SERVICE}" == "wireguard" ]; then + jq -r '.wireguard_config' ${VPN_LAUNCH_CONFIG} > /dockerstartup/wireguard.conf + process_wireguard "/dockerstartup/wireguard.conf" + else + notify-send -u critical -t 0 -i "${ICON_ERROR}" "VPN Configuration Failed" "Unknown or missing vpn_service" + exit 1 + fi + +else + +# File-Mapping/Env Based Workflow + + ### Tailscale ### + if [ "${TAILSCALE_KEY:0:5}" == "tskey" ]; then + process_tailscale $TAILSCALE_KEY + + ### WireGuard ### + elif [ -e ${DEFAULT_WIREGUARD_CONFIG} ]; then + process_wireguard $DEFAULT_WIREGUARD_CONFIG + + ### OpenVPN ### + elif [ -e ${DEFAULT_OPENVPN_CONFIG} ]; then + VPN_CONFIG=$DEFAULT_OPENVPN_CONFIG + # Check if we need user credentials + if grep -x auth-user-pass ${VPN_CONFIG}; then + get_set_creds + fi + process_openvpn $VPN_CONFIG + + else + msg="VPN Config File or TAILSCALE_KEY is not defined" + echo msg + notify-send -u critical -t 0 -i "${ICON_ERROR}" "VPN Configuration Failed" "${msg}" + exit 1 + fi +fi diff --git a/src/ubuntu/install/vs_code/install_vs_code.sh b/src/ubuntu/install/vs_code/install_vs_code.sh index 065678549..0709da3fc 100644 --- a/src/ubuntu/install/vs_code/install_vs_code.sh +++ b/src/ubuntu/install/vs_code/install_vs_code.sh @@ -1,9 +1,13 @@ #!/usr/bin/env bash set -ex + +# Install vsCode ARCH=$(arch | sed 's/aarch64/arm64/g' | sed 's/x86_64/x64/g') wget -q https://update.code.visualstudio.com/latest/linux-deb-${ARCH}/stable -O vs_code.deb apt-get update apt-get install -y ./vs_code.deb + +# Desktop icon mkdir -p /usr/share/icons/hicolor/apps wget -O /usr/share/icons/hicolor/apps/vscode.svg https://kasm-static-content.s3.amazonaws.com/icons/vscode.svg sed -i '/Icon=/c\Icon=/usr/share/icons/hicolor/apps/vscode.svg' /usr/share/applications/code.desktop @@ -19,9 +23,13 @@ apt-get install -y python3-setuptools \ python3-venv \ python3-virtualenv -# Cleanup -apt-get autoclean -rm -rf \ +# Cleanup for app layer +chown -R 1000:0 $HOME +find /usr/share/ -name "icon-theme.cache" -exec rm -f {} \; +if [ -z ${SKIP_CLEAN+x} ]; then + apt-get autoclean + rm -rf \ /var/lib/apt/lists/* \ /var/tmp/* \ /tmp/* +fi diff --git a/src/ubuntu/install/wine/install_wine.sh b/src/ubuntu/install/wine/install_wine.sh index 6582369ab..1b6b99507 100644 --- a/src/ubuntu/install/wine/install_wine.sh +++ b/src/ubuntu/install/wine/install_wine.sh @@ -1,9 +1,115 @@ -#!/bin/bash +#!/usr/bin/env bash +set -ex -# This script currently supports Ubuntu focal only +# ============================================================================ +# Wine + Winetricks for Kasm Workspaces +# Supports Ubuntu Jammy (22.04) and Noble (24.04) +# Uses modern DEB822 .sources format (no deprecated apt-key) +# ============================================================================ + +UBUNTU_CODENAME=$(grep VERSION_CODENAME /etc/os-release | cut -d= -f2) +echo "Installing Wine on Ubuntu ${UBUNTU_CODENAME}" + +# -------------------------------------------------------------------------- +# 1. Enable 32-bit architecture (required for most Windows apps) +# -------------------------------------------------------------------------- dpkg --add-architecture i386 -apt update -wget -qO- https://dl.winehq.org/wine-builds/winehq.key | apt-key add - -apt install software-properties-common -apt-add-repository "deb http://dl.winehq.org/wine-builds/ubuntu/ $(lsb_release -cs) main" -apt install -y --install-recommends winehq-stable winetricks + +# -------------------------------------------------------------------------- +# 2. Add WineHQ repository using modern signed-by method +# -------------------------------------------------------------------------- +apt-get update +apt-get install -y software-properties-common wget + +mkdir -pm755 /etc/apt/keyrings +wget -O /etc/apt/keyrings/winehq-archive.key https://dl.winehq.org/wine-builds/winehq.key + +# Download the official .sources file for this Ubuntu release +wget -NP /etc/apt/sources.list.d/ \ + "https://dl.winehq.org/wine-builds/ubuntu/dists/${UBUNTU_CODENAME}/winehq-${UBUNTU_CODENAME}.sources" + +apt-get update + +# -------------------------------------------------------------------------- +# 3. Install Wine Stable + Winetricks +# -------------------------------------------------------------------------- +apt-get install -y --install-recommends winehq-stable || \ + apt-get install -y --install-recommends winehq-devel + +apt-get install -y winetricks + +# -------------------------------------------------------------------------- +# 4. Initialize Wine prefix and install core fonts +# This avoids a slow first-run experience for users +# -------------------------------------------------------------------------- +export WINEPREFIX="/home/kasm-default-profile/.wine" +export WINEDLLOVERRIDES="mscoree,mshtml=" +export DISPLAY=:1 + +# Initialize the Wine prefix (silent, no GUI) +wineboot --init 2>/dev/null || true +sleep 2 + +# Install core Windows fonts via winetricks (improves app rendering) +winetricks -q corefonts 2>/dev/null || true + +# -------------------------------------------------------------------------- +# 5. Create .desktop file for Wine configuration +# -------------------------------------------------------------------------- +mkdir -p /home/kasm-default-profile/Desktop +cat > /home/kasm-default-profile/Desktop/wine-config.desktop << 'DEOF' +[Desktop Entry] +Version=1.0 +Type=Application +Name=Wine Configuration +Comment=Configure Wine Windows Compatibility +Exec=winecfg +Icon=wine +Terminal=false +Categories=System; +DEOF +chmod +x /home/kasm-default-profile/Desktop/wine-config.desktop + +# -------------------------------------------------------------------------- +# 6. Set up .exe file association so double-clicking opens with Wine +# -------------------------------------------------------------------------- +mkdir -p /home/kasm-default-profile/.local/share/applications +cat > /home/kasm-default-profile/.local/share/applications/wine.desktop << 'AEOF' +[Desktop Entry] +Version=1.0 +Type=Application +Name=Wine Windows Program Loader +MimeType=application/x-ms-dos-executable;application/x-msdos-program;application/x-executable; +Exec=wine %f +Icon=wine +Terminal=false +NoDisplay=true +AEOF + +mkdir -p /home/kasm-default-profile/.local/share/mime/packages +cat > /home/kasm-default-profile/.local/share/mime/packages/wine.xml << 'MEOF' + + + + Windows Executable + + + +MEOF + +update-mime-database /home/kasm-default-profile/.local/share/mime 2>/dev/null || true + +# -------------------------------------------------------------------------- +# 7. Cleanup +# -------------------------------------------------------------------------- +chown -R 1000:0 /home/kasm-default-profile + +if [ -z "${SKIP_CLEAN+x}" ]; then + apt-get autoclean + rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* \ + /tmp/* +fi + +echo "Wine installation complete: $(wine --version 2>/dev/null || echo 'version check failed')" diff --git a/src/ubuntu/install/zoom/custom_startup.sh b/src/ubuntu/install/zoom/custom_startup.sh index 06befaef7..402570b48 100644 --- a/src/ubuntu/install/zoom/custom_startup.sh +++ b/src/ubuntu/install/zoom/custom_startup.sh @@ -5,7 +5,7 @@ PGREP="zoom" export MAXIMIZE="true" export MAXIMIZE_NAME="Zoom" MAXIMIZE_SCRIPT=$STARTUPDIR/maximize_window.sh -DEFAULT_ARGS="--no-sandbox" +DEFAULT_ARGS="" ARGS=${APP_ARGS:-$DEFAULT_ARGS} options=$(getopt -o gau: -l go,assign,url: -n "$0" -- "$@") || exit diff --git a/src/ubuntu/install/zoom/install_zoom.sh b/src/ubuntu/install/zoom/install_zoom.sh index ae1ca1adf..da95913ce 100644 --- a/src/ubuntu/install/zoom/install_zoom.sh +++ b/src/ubuntu/install/zoom/install_zoom.sh @@ -1,18 +1,40 @@ #!/usr/bin/env bash set -ex +# Install Zoom ARCH=$(arch | sed 's/aarch64/arm64/g' | sed 's/x86_64/amd64/g') - if [ "${ARCH}" == "arm64" ] ; then echo "Zoom for arm64 currently not supported, skipping install" exit 0 fi - - wget -q https://zoom.us/client/latest/zoom_${ARCH}.deb apt-get update apt-get install -y ./zoom_${ARCH}.deb rm zoom_amd64.deb -sed -i 's#/usr/bin/zoom#/usr/bin/zoom --no-sandbox##' /usr/share/applications/Zoom.desktop + +# Desktop icon cp /usr/share/applications/Zoom.desktop $HOME/Desktop/ chmod +x $HOME/Desktop/Zoom.desktop + +# Add wrapper to detect seccomp +rm -f /usr/bin/zoom +cat > /usr/bin/zoom </dev/null || true + fi + done +fi + +cd /tmp && rm -rf Colloid-gtk-theme + +# Verify installation +COLLOID_THEME="Colloid-Dark" +if [ ! -d "/usr/share/themes/${COLLOID_THEME}" ]; then + # Fallback: try the exact name that was generated + COLLOID_THEME=$(ls -d /usr/share/themes/Colloid*Dark* 2>/dev/null | head -1 | xargs basename 2>/dev/null || echo "Colloid-Dark") + echo "Using theme: ${COLLOID_THEME}" +fi + +# -------------------------------------------------------------------------- +# 3. Install Colloid icon theme +# -------------------------------------------------------------------------- + +cd /tmp +git clone --depth 1 https://github.com/vinceliuice/Colloid-icon-theme.git +cd Colloid-icon-theme + +# Install system-wide +./install.sh -d /usr/share/icons + +cd /tmp && rm -rf Colloid-icon-theme + +COLLOID_ICONS="Colloid-dark" +if [ ! -d "/usr/share/icons/${COLLOID_ICONS}" ]; then + COLLOID_ICONS=$(ls -d /usr/share/icons/Colloid*dark* 2>/dev/null | head -1 | xargs basename 2>/dev/null || echo "Colloid-dark") + echo "Using icons: ${COLLOID_ICONS}" +fi + +# -------------------------------------------------------------------------- +# 4. Set wallpaper +# -------------------------------------------------------------------------- + +# Use a dark gradient wallpaper — Kasm hardcodes bg_default.png +# Try Zorin wallpapers first (if PPA was previously installed), then fall back +WALLPAPER="" +for candidate in \ + /usr/share/backgrounds/Zorin-Dark.jpg \ + /usr/share/backgrounds/Zorin.jpg \ + /usr/share/backgrounds/bg_kasm.png; do + if [ -f "${candidate}" ]; then + WALLPAPER="${candidate}" + break + fi +done + +if [ -n "${WALLPAPER}" ]; then + cp "${WALLPAPER}" /usr/share/backgrounds/bg_default.png + echo "Set wallpaper: ${WALLPAPER}" +else + echo "Keeping default Kasm wallpaper" +fi + +# -------------------------------------------------------------------------- +# 5. Configure XFCE — Colloid Dark theme +# -------------------------------------------------------------------------- + +XFCE_CONF="$HOME/.config/xfce4/xfconf/xfce-perchannel-xml" +mkdir -p "${XFCE_CONF}" + +# GTK theme + icon theme via xsettings +if [ -f "${XFCE_CONF}/xsettings.xml" ]; then + sed -i "s|||g" "${XFCE_CONF}/xsettings.xml" + sed -i "s|||g" "${XFCE_CONF}/xsettings.xml" + sed -i "s|||g" "${XFCE_CONF}/xsettings.xml" + sed -i "s|||g" "${XFCE_CONF}/xsettings.xml" + sed -i 's|||g' "${XFCE_CONF}/xsettings.xml" + sed -i 's|||g' "${XFCE_CONF}/xsettings.xml" +else + cat > "${XFCE_CONF}/xsettings.xml" << XSEOF + + + + + + + + + + + + +XSEOF +fi + +# Window manager theme +if [ -f "${XFCE_CONF}/xfwm4.xml" ]; then + sed -i "s|||g" "${XFCE_CONF}/xfwm4.xml" + sed -i "s|||g" "${XFCE_CONF}/xfwm4.xml" + sed -i 's|||g' "${XFCE_CONF}/xfwm4.xml" +else + cat > "${XFCE_CONF}/xfwm4.xml" << XWEOF + + + + + + + +XWEOF +fi + +# -------------------------------------------------------------------------- +# 6. Configure macOS-style top panel (menu bar) +# -------------------------------------------------------------------------- + +# macOS layout: slim top panel (menu bar) + Plank dock at bottom +# Top panel: [App Menu | ... spacer ... | System Tray | Clock] +cat > "${XFCE_CONF}/xfce4-panel.xml" << 'PANELEOF' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +PANELEOF + +# -------------------------------------------------------------------------- +# 7. Configure Plank dock (macOS-style bottom dock) +# -------------------------------------------------------------------------- + +PLANK_CONF="$HOME/.config/plank/dock1" +mkdir -p "${PLANK_CONF}/launchers" + +# Plank settings — transparent theme, bottom position, decent icon size +cat > "${PLANK_CONF}/settings" << 'PLANKEOF' +[PlankDockPreferences] +#shared settings +HideMode=0 +UnhideDelay=0 +HideDelay=0 +Monitor= +Position=3 +Offset=0 +Alignment=3 +IconSize=48 +ZoomEnabled=true +ZoomPercent=150 +Theme=Transparent +DockItems=files.dockitem;firefox.dockitem;terminal.dockitem;careerclaw.dockitem +PinnedOnly=false +LockItems=false +PressureReveal=false +CurrentWorkspaceOnly=false +PLANKEOF + +# Create dock item launchers +cat > "${PLANK_CONF}/launchers/files.dockitem" << 'EOF' +[PlankDockItemPreferences] +Launcher=file:///usr/share/applications/thunar.desktop +EOF + +cat > "${PLANK_CONF}/launchers/firefox.dockitem" << 'EOF' +[PlankDockItemPreferences] +Launcher=file:///usr/share/applications/firefox.desktop +EOF + +cat > "${PLANK_CONF}/launchers/terminal.dockitem" << 'EOF' +[PlankDockItemPreferences] +Launcher=file:///usr/share/applications/xfce4-terminal.desktop +EOF + +cat > "${PLANK_CONF}/launchers/careerclaw.dockitem" << 'EOF' +[PlankDockItemPreferences] +Launcher=file:///usr/share/applications/careerclaw.desktop +EOF + +# Autostart Plank at login +mkdir -p /etc/xdg/autostart +cat > /etc/xdg/autostart/plank-dock.desktop << 'AUTOSTART' +[Desktop Entry] +Type=Application +Name=Plank Dock +Comment=macOS-style application dock +Exec=plank +Hidden=false +NoDisplay=true +X-GNOME-Autostart-enabled=true +X-GNOME-Autostart-Delay=2 +AUTOSTART + +# -------------------------------------------------------------------------- +# 8. Install Inter font (clean UI font) +# -------------------------------------------------------------------------- + +apt-get install -y fonts-inter 2>/dev/null || { + mkdir -p /usr/share/fonts/truetype/inter + cd /tmp + wget -q "https://github.com/rsms/inter/releases/download/v4.1/Inter-4.1.zip" -O inter.zip || true + if [ -f inter.zip ]; then + unzip -o inter.zip -d inter_font + cp inter_font/Inter*.ttf /usr/share/fonts/truetype/inter/ 2>/dev/null || \ + cp inter_font/extras/ttf/*.ttf /usr/share/fonts/truetype/inter/ 2>/dev/null || true + fc-cache -f + rm -rf inter.zip inter_font + fi +} + +# -------------------------------------------------------------------------- +# 9. Cleanup +# -------------------------------------------------------------------------- + +chown -R 1000:0 $HOME + +if [ -z "${SKIP_CLEAN+x}" ]; then + apt-get autoclean + rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* \ + /tmp/* +fi + +echo "Career-Box macOS-style theme installation complete." +echo " GTK theme: ${COLLOID_THEME}" +echo " Icon theme: ${COLLOID_ICONS}" +echo " Dock: Plank (Transparent theme)" +echo " Panel: XFCE top menu bar" diff --git a/src/ubuntu/install/zsnes/install_zsnes.sh b/src/ubuntu/install/zsnes/install_zsnes.sh index 885aeae4f..5c760f846 100644 --- a/src/ubuntu/install/zsnes/install_zsnes.sh +++ b/src/ubuntu/install/zsnes/install_zsnes.sh @@ -1,13 +1,29 @@ #!/usr/bin/env bash set -ex + +# Install zsnes dpkg --add-architecture i386 apt-get update apt-get install -y zsnes +# Input tweaks mkdir $HOME/.zsnes - SCRIPT_PATH="$( cd "$(dirname "$0")" ; pwd -P )" SCRIPT_PATH="$(realpath $SCRIPT_PATH)" cp ${SCRIPT_PATH}/zinput.cfg $HOME/.zsnes/zinput.cfg +chown -R 1000:1000 $HOME/.zsnes + +# Desktop Icon +cp /usr/share/applications/zsnes.desktop $HOME/Desktop/ +chmod +x $HOME/Desktop/zsnes.desktop -chown -R 1000:1000 $HOME/.zsnes \ No newline at end of file +# Cleanup for app layer +chown -R 1000:0 $HOME +find /usr/share/ -name "icon-theme.cache" -exec rm -f {} \; +if [ -z ${SKIP_CLEAN+x} ]; then + apt-get autoclean + rm -rf \ + /var/lib/apt/lists/* \ + /var/tmp/* \ + /tmp/* +fi