diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 36f5fae5..c1c7a0a5 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -94,7 +94,7 @@ "setup-dev": "./setup-dev.sh --disable-venv", "npm-install": "npm install", "apt": "sudo apt update && sudo apt install -y shellcheck jq unzip", - "tflint": "TFLINT_VERSION=v0.61.0 && curl -sSfL \"https://raw.githubusercontent.com/terraform-linters/tflint/${TFLINT_VERSION}/install_linux.sh\" | bash && tflint --init", + "tflint": "TFLINT_VERSION=v0.61.0 && TFLINT_SHA256=ca4e4e8cb7cc3436f2b6979e9c4fd4e2623a66fcca1ad1fe12f8669967636ae2 && curl -sSfL \"https://github.com/terraform-linters/tflint/releases/download/${TFLINT_VERSION}/tflint_linux_amd64.zip\" -o /tmp/tflint.zip && echo \"${TFLINT_SHA256} /tmp/tflint.zip\" | sha256sum -c --quiet - && sudo unzip -o /tmp/tflint.zip -d /usr/local/bin && rm /tmp/tflint.zip && tflint --init", "actionlint": "ACTIONLINT_VERSION=1.7.10 && ACTIONLINT_SHA256=f4c76b71db5755a713e6055cbb0857ed07e103e028bda117817660ebadb4386f && curl -sSfL \"https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz\" -o /tmp/actionlint.tar.gz && echo \"${ACTIONLINT_SHA256} /tmp/actionlint.tar.gz\" | sha256sum -c --quiet - && sudo tar -xzf /tmp/actionlint.tar.gz -C /usr/local/bin actionlint && rm /tmp/actionlint.tar.gz", "golangci-lint": "GOLANGCI_LINT_VERSION=2.11.4 && GOLANGCI_LINT_SHA256=200c5b7503f67b59a6743ccf32133026c174e272b930ee79aa2aa6f37aca7ef1 && curl -fsSL \"https://github.com/golangci/golangci-lint/releases/download/v${GOLANGCI_LINT_VERSION}/golangci-lint-${GOLANGCI_LINT_VERSION}-linux-amd64.tar.gz\" -o /tmp/golangci-lint.tar.gz && echo \"${GOLANGCI_LINT_SHA256} /tmp/golangci-lint.tar.gz\" | sha256sum -c --quiet - && sudo tar -xzf /tmp/golangci-lint.tar.gz -C /usr/local/bin --strip-components=1 \"golangci-lint-${GOLANGCI_LINT_VERSION}-linux-amd64/golangci-lint\" && rm /tmp/golangci-lint.tar.gz", "update-bashrc": "echo 'export PATH=\"${containerWorkspaceFolder}/scripts:${containerWorkspaceFolder}/node_modules/.bin:$PATH\"' | sudo tee -a ~/.bashrc", diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 3014e575..b6e5f04d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -235,3 +235,43 @@ updates: commit-message: prefix: "chore" include: "scope" + + # Docker base image digests + - package-ecosystem: "docker" + directory: "/data-management/viewer/backend" + schedule: + interval: "weekly" + day: "monday" + open-pull-requests-limit: 5 + labels: + - "dependencies" + - "docker" + commit-message: + prefix: "chore" + include: "scope" + + - package-ecosystem: "docker" + directory: "/data-management/viewer/frontend" + schedule: + interval: "weekly" + day: "monday" + open-pull-requests-limit: 5 + labels: + - "dependencies" + - "docker" + commit-message: + prefix: "chore" + include: "scope" + + - package-ecosystem: "docker" + directory: "/evaluation/sil/docker" + schedule: + interval: "weekly" + day: "monday" + open-pull-requests-limit: 5 + labels: + - "dependencies" + - "docker" + commit-message: + prefix: "chore" + include: "scope" diff --git a/.github/workflows/check-binary-freshness.yml b/.github/workflows/check-binary-freshness.yml new file mode 100644 index 00000000..76c346c7 --- /dev/null +++ b/.github/workflows/check-binary-freshness.yml @@ -0,0 +1,268 @@ +# yaml-lint disable +# cspell:ignore stale + +name: Binary Dependency Freshness + +on: + schedule: + # Weekly: Mondays at 08:00 UTC (before weekly-validation 09:00) + - cron: "0 8 * * 1" + workflow_dispatch: + +permissions: + contents: read + issues: write + +concurrency: + group: check-binary-freshness + cancel-in-progress: false + +defaults: + run: + shell: pwsh + +jobs: + check-freshness: + name: Check Binary Dependency Freshness + runs-on: ubuntu-latest + permissions: + contents: read + issues: write + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Check pinned versions against latest releases + id: check + env: + GH_TOKEN: ${{ github.token }} + run: | + # Docker base image digest freshness is monitored by Dependabot (docker ecosystem). + # See .github/dependabot.yml for Docker monitoring config. + $tools = @( + @{ + Name = 'uv' + Repo = 'astral-sh/uv' + Pattern = 'UV_VERSION="([^"]+)"' + Files = @( + 'setup-dev.sh' + 'training/rl/scripts/train.sh' + 'infrastructure/setup/optional/isaac-sim-vm/scripts/install-dev-deps.sh' + ) + } + @{ + Name = 'terraform-docs' + Repo = 'terraform-docs/terraform-docs' + Pattern = 'TERRAFORM_DOCS_VERSION="([^"]+)"' + Files = @('setup-dev.sh') + } + @{ + Name = 'tflint' + Repo = 'terraform-linters/tflint' + Pattern = 'TFLINT_VERSION=v?([0-9][^\s&"]+)' + Files = @('.devcontainer/devcontainer.json') + } + @{ + Name = 'actionlint' + Repo = 'rhysd/actionlint' + Pattern = 'ACTIONLINT_VERSION=([0-9][^\s&"]+)' + Files = @('.devcontainer/devcontainer.json') + } + @{ + Name = 'golangci-lint' + Repo = 'golangci/golangci-lint' + Pattern = 'GOLANGCI_LINT_VERSION=([0-9][^\s&"]+)' + Files = @('.devcontainer/devcontainer.json') + } + ) + + $results = @() + + foreach ($tool in $tools) { + $pinnedVersions = @() + + foreach ($file in $tool.Files) { + if (-not (Test-Path $file)) { + Write-Warning "File not found: $file" + continue + } + + $content = Get-Content $file -Raw + if ($content -match $tool.Pattern) { + $pinnedVersions += @{ File = $file; Version = $Matches[1] } + } else { + Write-Warning "Could not extract version for $($tool.Name) from $file" + } + } + + if ($pinnedVersions.Count -eq 0) { + Write-Warning "No versions found for $($tool.Name), skipping" + continue + } + + $pinnedVersion = $pinnedVersions[0].Version + $inconsistent = @($pinnedVersions | Where-Object { $_.Version -ne $pinnedVersion }).Count -gt 0 + + $latestTag = gh api "repos/$($tool.Repo)/releases/latest" --jq '.tag_name' 2>$null + if (-not $latestTag) { + throw "Could not fetch latest release for $($tool.Repo) — freshness check cannot produce reliable results" + } + + $latestVersion = $latestTag.TrimStart('v') + $isStale = $pinnedVersion -ne $latestVersion + + $results += [ordered]@{ + Name = $tool.Name + Repo = $tool.Repo + PinnedVersion = $pinnedVersion + LatestVersion = $latestVersion + LatestTag = $latestTag + IsStale = $isStale + Inconsistent = $inconsistent + Files = $tool.Files + } + + $status = if ($isStale) { '⚠️' } else { '✅' } + $extra = if ($inconsistent) { ' [INCONSISTENT]' } else { '' } + Write-Host "$status $($tool.Name): pinned=$pinnedVersion latest=$latestVersion$extra" + } + + $staleCount = @($results | Where-Object { $_.IsStale }).Count + Write-Host "`nStale: $staleCount / $($results.Count)" + + "stale-count=$staleCount" >> $env:GITHUB_OUTPUT + "total-count=$($results.Count)" >> $env:GITHUB_OUTPUT + $results | ConvertTo-Json -Depth 5 | Set-Content 'freshness-results.json' + + - name: Create or update tracking issue + if: steps.check.outputs.stale-count != '0' + env: + GH_TOKEN: ${{ github.token }} + STALE_COUNT: ${{ steps.check.outputs.stale-count }} + run: | + $results = @(Get-Content 'freshness-results.json' | ConvertFrom-Json) + + $tableRows = foreach ($r in $results) { + $status = if ($r.IsStale) { '⚠️ Update available' } else { '✅ Current' } + $latest = "[$($r.LatestVersion)](https://github.com/$($r.Repo)/releases/tag/$($r.LatestTag))" + "| $($r.Name) | $($r.PinnedVersion) | $latest | $status |" + } + + $staleTools = @($results | Where-Object { $_.IsStale }) + $fileLines = foreach ($r in $staleTools) { + foreach ($f in $r.Files) { + "- ``$f`` — $($r.Name) $($r.PinnedVersion) → $($r.LatestVersion)" + } + } + + $warnings = @($results | Where-Object { $_.Inconsistent }) | ForEach-Object { + "- **$($_.Name)**: version mismatch across files ($($_.Files -join ', '))" + } + $warningSection = if ($warnings) { + "`n### ⚠️ Version Inconsistencies`n`n$($warnings -join "`n")" + } else { '' } + + $marker = '' + $runUrl = "$env:GITHUB_SERVER_URL/$env:GITHUB_REPOSITORY/actions/runs/$env:GITHUB_RUN_ID" + $checkDate = Get-Date -Format 'yyyy-MM-dd' -AsUTC + + $body = @" + ## Binary Dependency Freshness Report + + | Tool | Pinned | Latest | Status | + |------|--------|--------|--------| + $($tableRows -join "`n") + + ### Files Requiring Updates + + $($fileLines -join "`n") + $warningSection + + ### Update Instructions + + Each pinned binary requires: + 1. Version bump in source file(s) + 2. Download new release asset and compute SHA256 hash + 3. Update hash value in source file(s) + 4. For multi-architecture tools (uv, terraform-docs): update all architecture variants + + --- + **Workflow Run:** $runUrl + **Detection Date:** $checkDate + + $marker + "@ + + $existing = gh issue list ` + --search "in:body automation:binary-freshness is:open" ` + --limit 1 ` + --json number ` + --jq '.[0].number // empty' + $existing = if ($existing) { $existing.Trim() } else { $null } + + $title = "security: binary dependency updates available ($env:STALE_COUNT stale)" + + if ($existing) { + Write-Host "Updating existing issue #$existing" + gh issue edit $existing --title $title --body $body + gh issue comment $existing ` + --body "🔄 Weekly scan: $env:STALE_COUNT tool(s) stale as of $checkDate. [Workflow run]($runUrl)." + } else { + Write-Host "Creating new tracking issue" + gh issue create ` + --title $title ` + --body $body ` + --label "dependencies,automated,needs-triage" + } + + - name: Close resolved tracking issue + if: steps.check.outputs.stale-count == '0' + env: + GH_TOKEN: ${{ github.token }} + run: | + $existing = gh issue list ` + --search "in:body automation:binary-freshness is:open" ` + --limit 1 ` + --json number ` + --jq '.[0].number // empty' + $existing = if ($existing) { $existing.Trim() } else { $null } + + if ($existing) { + $checkDate = Get-Date -Format 'yyyy-MM-dd' -AsUTC + Write-Host "All tools current — closing issue #$existing" + gh issue close $existing ` + --comment "✅ All binary dependencies are current as of $checkDate." + } else { + Write-Host "All tools current, no open tracking issue." + } + + - name: Write job summary + if: always() + run: | + if (-not (Test-Path 'freshness-results.json')) { + "## Binary Dependency Freshness`n`n⚠️ Check did not complete." >> $env:GITHUB_STEP_SUMMARY + return + } + + $results = @(Get-Content 'freshness-results.json' | ConvertFrom-Json) + + $rows = foreach ($r in $results) { + $status = if ($r.IsStale) { '⚠️ Stale' } else { '✅ Current' } + "| $($r.Name) | $($r.PinnedVersion) | $($r.LatestVersion) | $status |" + } + + $staleCount = @($results | Where-Object { $_.IsStale }).Count + + $summary = @" + ## Binary Dependency Freshness + + | Tool | Pinned | Latest | Status | + |------|--------|--------|--------| + $($rows -join "`n") + + **Stale:** $staleCount / $($results.Count) + "@ + + $summary >> $env:GITHUB_STEP_SUMMARY diff --git a/data-management/viewer/backend/Dockerfile b/data-management/viewer/backend/Dockerfile index 91443327..6eb86c1f 100644 --- a/data-management/viewer/backend/Dockerfile +++ b/data-management/viewer/backend/Dockerfile @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -FROM python:3.11-slim AS base +FROM python:3.11-slim@sha256:b1b81d67b8df73bf6067191790c233ef9e598b863fc1ccf447956138ca466d99 AS base WORKDIR /app @@ -9,7 +9,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends ffmpeg \ && rm -rf /var/lib/apt/lists/* # Install uv -RUN pip install --no-cache-dir uv +RUN pip install --no-cache-dir uv==0.10.9 # Copy dependency manifests COPY pyproject.toml uv.lock ./ diff --git a/data-management/viewer/frontend/Dockerfile b/data-management/viewer/frontend/Dockerfile index d8004506..c14d2a9d 100644 --- a/data-management/viewer/frontend/Dockerfile +++ b/data-management/viewer/frontend/Dockerfile @@ -1,7 +1,7 @@ # syntax=docker/dockerfile:1 # ─── Build stage ───────────────────────────────────────────────────────────── -FROM node:24.14.1-slim AS build +FROM node:24.14.1-slim@sha256:b506e7321f176aae77317f99d67a24b272c1f09f1d10f1761f2773447d8da26c AS build WORKDIR /app @@ -23,7 +23,7 @@ ENV VITE_AZURE_TENANT_ID=${VITE_AZURE_TENANT_ID} RUN npm run build # ─── Serve stage ───────────────────────────────────────────────────────────── -FROM nginx:1.27-alpine AS serve +FROM nginx:1.27-alpine@sha256:65645c7bb6a0661892a8b03b89d0743208a18dd2f3f17a54ef4b76fb8e2f2a10 AS serve # Remove default nginx page RUN rm -rf /usr/share/nginx/html/* diff --git a/evaluation/sil/docker/Dockerfile.lerobot-eval b/evaluation/sil/docker/Dockerfile.lerobot-eval index 831d0aa8..f9eee30a 100644 --- a/evaluation/sil/docker/Dockerfile.lerobot-eval +++ b/evaluation/sil/docker/Dockerfile.lerobot-eval @@ -5,7 +5,7 @@ RUN apt-get update -qq && \ apt-get clean && rm -rf /var/lib/apt/lists/* COPY requirements-lerobot-infer.txt . -RUN pip install --no-cache-dir "uv>=0.6.0,<1.0.0" && \ +RUN pip install --no-cache-dir "uv==0.10.9" && \ uv pip install -r requirements-lerobot-infer.txt --system # Pre-download ResNet18 backbone weights (required by ACT policy, no internet at runtime) diff --git a/infrastructure/setup/optional/isaac-sim-vm/scripts/install-dev-deps.sh b/infrastructure/setup/optional/isaac-sim-vm/scripts/install-dev-deps.sh index 1595b5f1..01ea7df7 100755 --- a/infrastructure/setup/optional/isaac-sim-vm/scripts/install-dev-deps.sh +++ b/infrastructure/setup/optional/isaac-sim-vm/scripts/install-dev-deps.sh @@ -148,7 +148,14 @@ EOF apt_get update apt_get install -y --no-install-recommends nodejs -curl -LsSf https://astral.sh/uv/install.sh | sudo env UV_INSTALL_DIR="/usr/local/bin" sh +UV_VERSION="0.10.9" +UV_SHA256="20d79708222611fa540b5c9ed84f352bcd3937740e51aacc0f8b15b271c57594" +curl -LsSf "https://github.com/astral-sh/uv/releases/download/${UV_VERSION}/uv-x86_64-unknown-linux-gnu.tar.gz" -o /tmp/uv.tar.gz +echo "${UV_SHA256} /tmp/uv.tar.gz" | sha256sum -c --quiet - +tar -xzf /tmp/uv.tar.gz -C /tmp +sudo install -m 0755 /tmp/uv-x86_64-unknown-linux-gnu/uv /usr/local/bin/uv +sudo install -m 0755 /tmp/uv-x86_64-unknown-linux-gnu/uvx /usr/local/bin/uvx +rm -rf /tmp/uv.tar.gz /tmp/uv-x86_64-unknown-linux-gnu if ! command -v uv >/dev/null 2>&1; then echo "uv installation failed or is not on PATH" >&2 exit 1 diff --git a/setup-dev.sh b/setup-dev.sh index 04d22b68..b1ad39ca 100755 --- a/setup-dev.sh +++ b/setup-dev.sh @@ -46,8 +46,19 @@ section "UV Package Manager Setup" if ! command -v uv &>/dev/null; then info "Installing uv package manager..." - curl -LsSf https://astral.sh/uv/0.10.9/install.sh | sh - export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$PATH" + UV_VERSION="0.10.9" + UV_ARCH=$(uname -m) + case "${UV_ARCH}" in + x86_64) UV_TRIPLE="x86_64-unknown-linux-gnu"; UV_SHA256="20d79708222611fa540b5c9ed84f352bcd3937740e51aacc0f8b15b271c57594" ;; + aarch64) UV_TRIPLE="aarch64-unknown-linux-gnu"; UV_SHA256="cc0c5a8573e7d6d78aecb954e0a62b5c0d18217bb81f1e19363b428c57a9962a" ;; + *) error "Unsupported architecture for uv: ${UV_ARCH}"; exit 1 ;; + esac + curl -LsSf "https://github.com/astral-sh/uv/releases/download/${UV_VERSION}/uv-${UV_TRIPLE}.tar.gz" -o /tmp/uv.tar.gz + echo "${UV_SHA256} /tmp/uv.tar.gz" | sha256sum -c --quiet - + tar -xzf /tmp/uv.tar.gz -C /tmp + sudo install -m 0755 "/tmp/uv-${UV_TRIPLE}/uv" /usr/local/bin/uv + sudo install -m 0755 "/tmp/uv-${UV_TRIPLE}/uvx" /usr/local/bin/uvx + rm -rf /tmp/uv.tar.gz "/tmp/uv-${UV_TRIPLE}" fi info "Using uv: $(uv --version)" @@ -61,8 +72,8 @@ TERRAFORM_DOCS_VERSION="0.21.0" ARCH=$(uname -m) case "${ARCH}" in - x86_64) ARCH="amd64" ;; - aarch64|arm64) ARCH="arm64" ;; + x86_64) ARCH="amd64"; TERRAFORM_DOCS_SHA256="2fdd81b8d21ff1498cd559af0dcc5d155835f84600db06d3923e217124fc735a" ;; + aarch64|arm64) ARCH="arm64"; TERRAFORM_DOCS_SHA256="35b2e6846268841484e6eea7d00d7dfe2c94b4725e52cfe19aa6c26a86c32edc" ;; *) error "Unsupported architecture: ${ARCH}"; exit 1 ;; esac @@ -72,6 +83,7 @@ else info "Installing terraform-docs v${TERRAFORM_DOCS_VERSION}..." curl -sSLo /tmp/terraform-docs.tar.gz \ "https://github.com/terraform-docs/terraform-docs/releases/download/v${TERRAFORM_DOCS_VERSION}/terraform-docs-v${TERRAFORM_DOCS_VERSION}-$(uname -s | tr '[:upper:]' '[:lower:]')-${ARCH}.tar.gz" + echo "${TERRAFORM_DOCS_SHA256} /tmp/terraform-docs.tar.gz" | sha256sum -c --quiet - tar -xzf /tmp/terraform-docs.tar.gz -C /tmp terraform-docs sudo mv /tmp/terraform-docs /usr/local/bin/terraform-docs sudo chmod +x /usr/local/bin/terraform-docs diff --git a/training/rl/scripts/train.sh b/training/rl/scripts/train.sh index 6186034b..a03e6445 100755 --- a/training/rl/scripts/train.sh +++ b/training/rl/scripts/train.sh @@ -54,9 +54,16 @@ run_python() { if ! command -v uv &>/dev/null; then echo "Installing uv package manager..." - if curl -LsSf https://astral.sh/uv/0.10.9/install.sh | sh 2>/dev/null; then - export PATH="${HOME}/.local/bin:${PATH}" - fi + UV_VERSION="0.10.9" + UV_SHA256="20d79708222611fa540b5c9ed84f352bcd3937740e51aacc0f8b15b271c57594" + curl -LsSf "https://github.com/astral-sh/uv/releases/download/${UV_VERSION}/uv-x86_64-unknown-linux-gnu.tar.gz" -o /tmp/uv.tar.gz + echo "${UV_SHA256} /tmp/uv.tar.gz" | sha256sum -c --quiet - + tar -xzf /tmp/uv.tar.gz -C /tmp + mkdir -p "${HOME}/.local/bin" + install -m 0755 /tmp/uv-x86_64-unknown-linux-gnu/uv "${HOME}/.local/bin/uv" + install -m 0755 /tmp/uv-x86_64-unknown-linux-gnu/uvx "${HOME}/.local/bin/uvx" + rm -rf /tmp/uv.tar.gz /tmp/uv-x86_64-unknown-linux-gnu + export PATH="${HOME}/.local/bin:${PATH}" fi configure_uv