From 799da75f4818809ec7efef48420cbec640cb5feb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Mar 2026 15:59:19 +0000 Subject: [PATCH 1/2] Initial plan From 68006211e03ef38801c576ea1d2c16d0c9705a2a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Mar 2026 16:17:06 +0000 Subject: [PATCH 2/2] Add dozzle.dev devcontainer feature Co-authored-by: sebst <592313+sebst@users.noreply.github.com> --- .github/workflows/test.yaml | 2 + src/dozzle.dev/devcontainer-feature.json | 17 +++ src/dozzle.dev/install.sh | 145 +++++++++++++++++++++++ test/dozzle.dev/test.sh | 19 +++ 4 files changed, 183 insertions(+) create mode 100644 src/dozzle.dev/devcontainer-feature.json create mode 100755 src/dozzle.dev/install.sh create mode 100755 test/dozzle.dev/test.sh diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 6b185fa..f05e008 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -22,6 +22,7 @@ jobs: - charmbracelet-gum - chezmoi.io - deno.com + - dozzle.dev - feature-installer - jj-vcs.dev - neovim.io @@ -66,6 +67,7 @@ jobs: - charmbracelet-gum - chezmoi.io - deno.com + - dozzle.dev - feature-installer - jj-vcs.dev - neovim.io diff --git a/src/dozzle.dev/devcontainer-feature.json b/src/dozzle.dev/devcontainer-feature.json new file mode 100644 index 0000000..c642f18 --- /dev/null +++ b/src/dozzle.dev/devcontainer-feature.json @@ -0,0 +1,17 @@ +{ + "name": "dozzle.dev", + "id": "dozzle.dev", + "version": "1.0.0", + "description": "Install \"dozzle\" binary", + "documentationURL": "https://github.com/devcontainer-community/devcontainer-features/tree/main/src/dozzle.dev", + "options": { + "version": { + "type": "string", + "default": "latest", + "proposals": [ + "latest" + ], + "description": "Version of \"dozzle\" to install." + } + } +} diff --git a/src/dozzle.dev/install.sh b/src/dozzle.dev/install.sh new file mode 100755 index 0000000..866053f --- /dev/null +++ b/src/dozzle.dev/install.sh @@ -0,0 +1,145 @@ +#!/bin/bash +set -o errexit +set -o pipefail +set -o noclobber +set -o nounset +set -o allexport +readonly dockerRepository='amir20/dozzle' +readonly binaryName='dozzle' +readonly binaryTargetFolder='/usr/local/bin' +apt_get_update() { + if [ "$(find /var/lib/apt/lists/* | wc -l)" = "0" ]; then + echo "Running apt-get update..." + apt-get update -y + fi +} +apt_get_checkinstall() { + if ! dpkg -s "$@" >/dev/null 2>&1; then + apt_get_update + DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends --no-install-suggests --option 'Debug::pkgProblemResolver=true' --option 'Debug::pkgAcquire::Worker=1' "$@" + fi +} +apt_get_cleanup() { + apt-get clean + rm -rf /var/lib/apt/lists/* +} +check_curl_tar_installed() { + declare -a requiredAptPackagesMissing=() + if ! [ -r '/etc/ssl/certs/ca-certificates.crt' ]; then + requiredAptPackagesMissing+=('ca-certificates') + fi + if ! command -v curl >/dev/null 2>&1; then + requiredAptPackagesMissing+=('curl') + fi + if ! command -v tar >/dev/null 2>&1; then + requiredAptPackagesMissing+=('tar') + fi + declare -i requiredAptPackagesMissingCount=${#requiredAptPackagesMissing[@]} + if [ $requiredAptPackagesMissingCount -gt 0 ]; then + apt_get_update + apt_get_checkinstall "${requiredAptPackagesMissing[@]}" + apt_get_cleanup + fi +} +debian_get_arch() { + echo "$(dpkg --print-architecture)" +} +echo_banner() { + local text="$1" + echo -e "\e[1m\e[97m\e[41m$text\e[0m" +} +dockerhub_get_token() { + local repo="$1" + curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:${repo}:pull" \ + | grep -Po '"token":"\K[^"]+' +} +dockerhub_get_arch_digest() { + local repo="$1" + local tag="$2" + local arch="$3" + local token="$4" + local manifest + manifest=$(curl -s \ + -H "Authorization: Bearer $token" \ + -H "Accept: application/vnd.oci.image.index.v1+json,application/vnd.docker.distribution.manifest.list.v2+json" \ + "https://registry-1.docker.io/v2/${repo}/manifests/${tag}" \ + | tr -d ' \n\t') + echo "$manifest" \ + | grep -oP '"digest":"sha256:[a-f0-9]+","size":\d+,"platform":\{"architecture":"'"$arch"'"' \ + | grep -oP 'sha256:[a-f0-9]+' +} +dockerhub_get_binary_layer_digest() { + local repo="$1" + local arch_digest="$2" + local token="$3" + local arch_manifest + arch_manifest=$(curl -s \ + -H "Authorization: Bearer $token" \ + -H "Accept: application/vnd.oci.image.manifest.v1+json,application/vnd.docker.distribution.manifest.v2+json" \ + "https://registry-1.docker.io/v2/${repo}/manifests/${arch_digest}" \ + | tr -d ' \n\t') + local config_digest + config_digest=$(echo "$arch_manifest" \ + | grep -oP '"config":\{"[^}]+\}' \ + | grep -oP '"digest":"sha256:\K[a-f0-9]+') + echo "$arch_manifest" \ + | grep -oP '"digest":"sha256:[a-f0-9]+"' \ + | grep -oP 'sha256:[a-f0-9]+' \ + | grep -v "sha256:${config_digest}" \ + | tail -1 +} +dockerhub_download_binary_layer() { + local repo="$1" + local layer_digest="$2" + local token="$3" + local target_dir="$4" + curl -sL \ + --connect-timeout 30 \ + -H "Authorization: Bearer $token" \ + "https://registry-1.docker.io/v2/${repo}/blobs/${layer_digest}" \ + | tar -xz -C "$target_dir" "$binaryName" +} +utils_check_version() { + local version=$1 + if ! [[ "${version:-}" =~ ^(latest|[0-9]+\.[0-9]+\.[0-9]+)$ ]]; then + printf >&2 '=== [ERROR] Option "version" (value: "%s") is not "latest" or valid semantic version format "X.Y.Z" !\n' \ + "$version" + exit 1 + fi +} +install() { + utils_check_version "$VERSION" + check_curl_tar_installed + local tag + if [ "$VERSION" == 'latest' ] || [ -z "$VERSION" ]; then + tag='latest' + else + tag="v${VERSION}" + fi + readonly architecture="$(debian_get_arch)" + echo "Fetching dozzle ${tag} for ${architecture}..." + local token + token="$(dockerhub_get_token "$dockerRepository")" + if [ -z "$token" ]; then + printf >&2 '=== [ERROR] Could not obtain Docker Hub auth token!\n' + exit 1 + fi + local arch_digest + arch_digest="$(dockerhub_get_arch_digest "$dockerRepository" "$tag" "$architecture" "$token")" + if [ -z "$arch_digest" ]; then + printf >&2 '=== [ERROR] Could not find image manifest for architecture "%s" and tag "%s"!\n' "$architecture" "$tag" + exit 1 + fi + local layer_digest + layer_digest="$(dockerhub_get_binary_layer_digest "$dockerRepository" "$arch_digest" "$token")" + if [ -z "$layer_digest" ]; then + printf >&2 '=== [ERROR] Could not find binary layer digest!\n' + exit 1 + fi + dockerhub_download_binary_layer "$dockerRepository" "$layer_digest" "$token" "$binaryTargetFolder" + chmod 755 "${binaryTargetFolder}/${binaryName}" +} +echo_banner "devcontainer.community" +echo "Installing $binaryName..." +install "$@" +echo "(*) Done!" diff --git a/test/dozzle.dev/test.sh b/test/dozzle.dev/test.sh new file mode 100755 index 0000000..7b548f7 --- /dev/null +++ b/test/dozzle.dev/test.sh @@ -0,0 +1,19 @@ +#!/bin/bash + + +set -e + +# Optional: Import test library bundled with the devcontainer CLI +# See https://github.com/devcontainers/cli/blob/HEAD/docs/features/test.md#dev-container-features-test-lib +# Provides the 'check' and 'reportResults' commands. +source dev-container-features-test-lib + +# Feature-specific tests +# The 'check' command comes from the dev-container-features-test-lib. Syntax is... +# check