Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@opencode-cloud/core

Package Overview
Dependencies
Maintainers
1
Versions
90
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@opencode-cloud/core - npm Package Compare versions

Comparing version
10.0.0
to
10.1.0
+214
src/docker/registry.rs
//! Docker registry API helpers.
//!
//! Provides lightweight helpers for querying registry manifests and configs
//! without pulling images.
use super::DockerError;
use reqwest::header::ACCEPT;
use serde::Deserialize;
use std::collections::HashMap;
#[derive(Deserialize)]
struct TokenResponse {
token: Option<String>,
access_token: Option<String>,
}
#[derive(Deserialize)]
struct ManifestConfig {
digest: String,
}
#[derive(Deserialize)]
struct Manifest {
config: ManifestConfig,
}
#[derive(Deserialize)]
struct ManifestList {
manifests: Vec<ManifestDescriptor>,
}
#[derive(Deserialize)]
struct ManifestDescriptor {
digest: String,
platform: Option<ManifestPlatform>,
}
#[derive(Deserialize)]
struct ManifestPlatform {
architecture: Option<String>,
os: Option<String>,
}
#[derive(Deserialize)]
#[serde(untagged)]
enum ManifestResponse {
Single(Manifest),
List(ManifestList),
}
#[derive(Deserialize)]
struct ImageConfig {
config: Option<ImageConfigDetails>,
}
#[derive(Deserialize)]
struct ImageConfigDetails {
#[serde(rename = "Labels")]
labels: Option<HashMap<String, String>>,
}
pub async fn fetch_registry_version(
registry_base: &str,
token_url: &str,
repo: &str,
tag: &str,
maybe_manifest_digest: Option<&str>,
label_key: &str,
) -> Result<Option<String>, DockerError> {
let client = reqwest::Client::new();
let token = fetch_registry_token(&client, token_url).await?;
let manifest = if let Some(digest) = maybe_manifest_digest {
match fetch_registry_manifest(&client, registry_base, repo, digest, &token).await {
Ok(manifest) => manifest,
Err(digest_err) => fetch_registry_manifest(&client, registry_base, repo, tag, &token)
.await
.map_err(|tag_err| {
DockerError::Connection(format!(
"Failed to fetch registry manifest. Digest error: {digest_err}. Tag error: {tag_err}"
))
})?,
}
} else {
fetch_registry_manifest(&client, registry_base, repo, tag, &token).await?
};
let image_config = fetch_registry_image_config(
&client,
registry_base,
repo,
&manifest.config.digest,
&token,
)
.await?;
let maybe_version = image_config
.config
.and_then(|details| details.labels)
.and_then(|labels| labels.get(label_key).cloned());
Ok(maybe_version)
}
async fn fetch_registry_token(
client: &reqwest::Client,
token_url: &str,
) -> Result<String, DockerError> {
let response = client
.get(token_url)
.send()
.await
.map_err(|e| DockerError::Connection(format!("Failed to fetch registry token: {e}")))?;
let token_response: TokenResponse = response
.json()
.await
.map_err(|e| DockerError::Connection(format!("Failed to decode registry token: {e}")))?;
let token = token_response
.token
.or(token_response.access_token)
.ok_or_else(|| DockerError::Connection("Registry token missing".to_string()))?;
Ok(token)
}
async fn fetch_registry_manifest(
client: &reqwest::Client,
registry_base: &str,
repo: &str,
reference: &str,
token: &str,
) -> Result<Manifest, DockerError> {
let mut current_reference = reference.to_string();
loop {
let manifest_url = format!("{registry_base}/v2/{repo}/manifests/{current_reference}");
let response = client
.get(&manifest_url)
.header(
ACCEPT,
"application/vnd.oci.image.manifest.v1+json, application/vnd.docker.distribution.manifest.v2+json, application/vnd.docker.distribution.manifest.list.v2+json",
)
.bearer_auth(token)
.send()
.await
.map_err(|e| {
DockerError::Connection(format!("Failed to fetch registry manifest: {e}"))
})?;
if response.status() == reqwest::StatusCode::NOT_FOUND {
return Err(DockerError::Connection(format!(
"Manifest not found for {repo}:{current_reference}"
)));
}
let manifest_response: ManifestResponse = response
.json()
.await
.map_err(|e| DockerError::Connection(format!("Failed to decode manifest: {e}")))?;
match manifest_response {
ManifestResponse::Single(manifest) => return Ok(manifest),
ManifestResponse::List(list) => {
let digest = select_manifest_digest(&list).ok_or_else(|| {
DockerError::Connection(format!(
"No manifests available for {repo}:{current_reference}"
))
})?;
current_reference = digest;
}
}
}
}
fn select_manifest_digest(list: &ManifestList) -> Option<String> {
let preferred = list.manifests.iter().find(|manifest| {
let Some(platform) = &manifest.platform else {
return false;
};
platform.os.as_deref() == Some("linux") && platform.architecture.as_deref() == Some("amd64")
});
preferred
.or_else(|| list.manifests.first())
.map(|manifest| manifest.digest.clone())
}
async fn fetch_registry_image_config(
client: &reqwest::Client,
registry_base: &str,
repo: &str,
digest: &str,
token: &str,
) -> Result<ImageConfig, DockerError> {
let config_url = format!("{registry_base}/v2/{repo}/blobs/{digest}");
let response = client
.get(&config_url)
.bearer_auth(token)
.send()
.await
.map_err(|e| DockerError::Connection(format!("Failed to fetch image config: {e}")))?;
if !response.status().is_success() {
return Err(DockerError::Connection(format!(
"Failed to fetch image config: HTTP {}",
response.status()
)));
}
let config = response
.json()
.await
.map_err(|e| DockerError::Connection(format!("Failed to decode image config: {e}")))?;
Ok(config)
}
+1
-1
[package]
name = "opencode-cloud-core"
version = "10.0.0"
version = "10.1.0"
edition = "2024"

@@ -5,0 +5,0 @@ rust-version = "1.88"

{
"name": "@opencode-cloud/core",
"version": "10.0.0",
"version": "10.1.0",
"description": "Core NAPI bindings for opencode-cloud (internal package)",

@@ -5,0 +5,0 @@ "main": "index.js",

@@ -199,2 +199,5 @@ # opencode-cloud

# Check for updates and choose what to update
occ update
# Update the opencode-cloud CLI binary

@@ -201,0 +204,0 @@ occ update cli

@@ -33,5 +33,5 @@ # =============================================================================

# -----------------------------------------------------------------------------
# Stage 1: Runtime
# Stage 1: Base
# -----------------------------------------------------------------------------
FROM ubuntu:24.04 AS runtime
FROM ubuntu:24.04 AS base

@@ -240,3 +240,8 @@ # OCI Labels for image metadata

# bun - self-managing installer, trusted to handle versions
RUN curl -fsSL https://bun.sh/install | bash -s "bun-v1.3.5" \
&& rm -rf /home/opencode/.bun/install/cache /home/opencode/.bun/cache /home/opencode/.cache/bun
ENV PATH="/home/opencode/.bun/bin:${PATH}"
# uv - self-managing installer, trusted to handle versions (fast Python package manager)

@@ -501,2 +506,7 @@ RUN curl -LsSf https://astral.sh/uv/install.sh | sh

# -----------------------------------------------------------------------------
# Stage 2: opencode build
# -----------------------------------------------------------------------------
FROM base AS opencode-build
# -----------------------------------------------------------------------------
# opencode Setup (Fork + Broker + Proxy)

@@ -510,7 +520,5 @@ # -----------------------------------------------------------------------------

# - opencode-broker build
# - opencode web build + runtime
# - PAM configuration + systemd services
# - opencode config file
#
# NOTE: This section switches between opencode and root users as needed.
# NOTE: This stage uses opencode user + sudo for privileged installs.
USER opencode

@@ -529,8 +537,5 @@ # -----------------------------------------------------------------------------

&& git checkout "${OPENCODE_COMMIT}" \
&& curl -fsSL https://bun.sh/install | bash -s "bun-v1.3.5" \
&& export PATH="/home/opencode/.bun/bin:${PATH}" \
&& bun install --frozen-lockfile \
&& cd packages/opencode \
&& bun run build-single-ui \
&& rm -rf /home/opencode/.bun/install/cache /home/opencode/.bun/cache /home/opencode/.cache/bun \
&& cd /tmp/opencode-repo \

@@ -544,26 +549,17 @@ && sudo mkdir -p /opt/opencode/bin /opt/opencode/ui \

&& sudo chmod +x /opt/opencode/bin/opencode \
&& /opt/opencode/bin/opencode --version
&& cd /tmp/opencode-repo/packages/opencode-broker \
&& cargo build --release \
&& sudo mkdir -p /usr/local/bin \
&& sudo cp target/release/opencode-broker /usr/local/bin/opencode-broker \
&& sudo chmod 4755 /usr/local/bin/opencode-broker \
&& rm -rf /tmp/opencode-repo \
&& rm -rf /home/opencode/.bun/install/cache /home/opencode/.bun/cache /home/opencode/.cache/bun
# Add opencode to PATH
ENV PATH="/opt/opencode/bin:${PATH}"
# -----------------------------------------------------------------------------
# opencode-broker Installation
# Stage 3: Runtime
# -----------------------------------------------------------------------------
# Build opencode-broker from source (Rust service for PAM authentication)
# The broker handles PAM authentication and user process spawning
# NOTE: Requires root privileges for setuid bit (chmod 4755) to allow broker
# to run with elevated privileges for PAM authentication
USER root
RUN cd /tmp/opencode-repo/packages/opencode-broker \
&& runuser -u opencode -- bash -c '. /home/opencode/.cargo/env && cargo build --release' \
&& mkdir -p /usr/local/bin \
&& cp target/release/opencode-broker /usr/local/bin/opencode-broker \
&& chmod 4755 /usr/local/bin/opencode-broker \
&& rm -rf /tmp/opencode-repo
FROM base AS runtime
# Verify broker binary exists and is executable
RUN ls -la /usr/local/bin/opencode-broker \
&& test -x /usr/local/bin/opencode-broker \
&& echo "Broker installed"
# Add opencode to PATH
ENV PATH="/opt/opencode/bin:${PATH}"

@@ -576,2 +572,3 @@ # -----------------------------------------------------------------------------

# NOTE: Requires root privileges to write to /etc/pam.d/
USER root
RUN printf '%s\n' \

@@ -635,15 +632,3 @@ '# PAM configuration for OpenCode authentication' \

USER opencode
# -----------------------------------------------------------------------------
# GSD Plugin Installation
# -----------------------------------------------------------------------------
# Install the GSD (Get Shit Done) plugin for opencode
# Note: If this fails in container builds due to "~" path resolution, retry with
# OPENCODE_CONFIG_DIR=/home/opencode/.config/opencode set explicitly.
RUN mkdir -p /home/opencode/.npm \
&& npx --yes get-shit-done-cc --opencode --global \
&& rm -rf /home/opencode/.npm/_cacache /home/opencode/.npm/_npx
# -----------------------------------------------------------------------------
# opencode systemd Service (2026-01-22)

@@ -653,3 +638,2 @@ # -----------------------------------------------------------------------------

# NOTE: Requires root privileges to write to /etc/systemd/system/
USER root
RUN printf '%s\n' \

@@ -667,3 +651,3 @@ '[Unit]' \

'RestartSec=5' \
'Environment=PATH=/opt/opencode/bin:/home/opencode/.local/bin:/home/opencode/.cargo/bin:/home/opencode/.local/share/mise/shims:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' \
'Environment=PATH=/opt/opencode/bin:/home/opencode/.local/bin:/home/opencode/.cargo/bin:/home/opencode/.local/share/mise/shims:/home/opencode/.bun/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' \
'' \

@@ -696,4 +680,2 @@ '[Install]' \

USER opencode
# -----------------------------------------------------------------------------

@@ -705,3 +687,2 @@ # Entrypoint Script (Hybrid Init Support)

# Note: Entrypoint runs as root to support both modes; tini mode drops to opencode user
USER root
RUN printf '%s\n' \

@@ -732,2 +713,30 @@ '#!/bin/bash' \

# -----------------------------------------------------------------------------
# opencode Artifacts
# -----------------------------------------------------------------------------
COPY --from=opencode-build /opt/opencode /opt/opencode
COPY --from=opencode-build /usr/local/bin/opencode-broker /usr/local/bin/opencode-broker
RUN chown -R opencode:opencode /opt/opencode \
&& chmod +x /opt/opencode/bin/opencode \
&& chmod 4755 /usr/local/bin/opencode-broker
# Verify broker binary exists and is executable
RUN ls -la /usr/local/bin/opencode-broker \
&& test -x /usr/local/bin/opencode-broker \
&& echo "Broker installed"
USER opencode
RUN /opt/opencode/bin/opencode --version
# -----------------------------------------------------------------------------
# GSD Plugin Installation
# -----------------------------------------------------------------------------
# Install the GSD (Get Shit Done) plugin for opencode
# Note: If this fails in container builds due to "~" path resolution, retry with
# OPENCODE_CONFIG_DIR=/home/opencode/.config/opencode set explicitly.
RUN mkdir -p /home/opencode/.npm \
&& npx --yes get-shit-done-cc --opencode --global \
&& rm -rf /home/opencode/.npm/_cacache /home/opencode/.npm/_npx
# -----------------------------------------------------------------------------
# Version File

@@ -734,0 +743,0 @@ # -----------------------------------------------------------------------------

@@ -24,2 +24,3 @@ //! Docker operations module

pub mod progress;
mod registry;
pub mod state;

@@ -51,3 +52,6 @@ pub mod update;

// Version detection
pub use version::{VERSION_LABEL, get_cli_version, get_image_version, versions_compatible};
pub use version::{
VERSION_LABEL, get_cli_version, get_image_version, get_registry_latest_version,
versions_compatible,
};

@@ -54,0 +58,0 @@ // Container exec operations

@@ -5,3 +5,4 @@ //! Docker image version detection

use super::{DockerClient, DockerError};
use super::registry::fetch_registry_version;
use super::{DockerClient, DockerError, IMAGE_NAME_DOCKERHUB, IMAGE_NAME_GHCR, IMAGE_TAG_DEFAULT};

@@ -42,2 +43,60 @@ /// Version label key in Docker image

pub async fn get_registry_latest_version(
client: &DockerClient,
) -> Result<Option<String>, DockerError> {
match fetch_ghcr_registry_version(client).await {
Ok(version) => Ok(version),
Err(ghcr_err) => fetch_dockerhub_registry_version(client).await.map_err(|dockerhub_err| {
DockerError::Connection(format!(
"Failed to fetch registry version. GHCR: {ghcr_err}. Docker Hub: {dockerhub_err}"
))
}),
}
}
async fn fetch_ghcr_registry_version(client: &DockerClient) -> Result<Option<String>, DockerError> {
let repo = IMAGE_NAME_GHCR
.strip_prefix("ghcr.io/")
.unwrap_or(IMAGE_NAME_GHCR);
let reference = format!("{IMAGE_NAME_GHCR}:{IMAGE_TAG_DEFAULT}");
let digest = fetch_registry_digest(client, &reference).await;
fetch_registry_version(
"https://ghcr.io",
&format!("https://ghcr.io/token?scope=repository:{repo}:pull"),
repo,
IMAGE_TAG_DEFAULT,
digest.as_deref(),
VERSION_LABEL,
)
.await
}
async fn fetch_dockerhub_registry_version(
client: &DockerClient,
) -> Result<Option<String>, DockerError> {
let repo = IMAGE_NAME_DOCKERHUB;
let reference = format!("{IMAGE_NAME_DOCKERHUB}:{IMAGE_TAG_DEFAULT}");
let digest = fetch_registry_digest(client, &reference).await;
fetch_registry_version(
"https://registry-1.docker.io",
&format!(
"https://auth.docker.io/token?service=registry.docker.io&scope=repository:{repo}:pull"
),
repo,
IMAGE_TAG_DEFAULT,
digest.as_deref(),
VERSION_LABEL,
)
.await
}
async fn fetch_registry_digest(client: &DockerClient, reference: &str) -> Option<String> {
client
.inner()
.inspect_registry_image(reference, None)
.await
.ok()
.and_then(|info| info.descriptor.digest)
}
/// CLI version from Cargo.toml

@@ -44,0 +103,0 @@ pub fn get_cli_version() -> &'static str {