Skip to content

Commit 3309122

Browse files
committed
Deduplicate runtime assets and harden cargo package CI
1 parent 726ebd0 commit 3309122

7 files changed

Lines changed: 164 additions & 12 deletions

File tree

.github/workflows/ci.yml

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,88 @@ jobs:
7474
--tag opencode-cloud-sandbox:ci-check \
7575
.
7676
77+
cargo-package-verify:
78+
name: Cargo Package Verify
79+
runs-on: ubuntu-latest
80+
steps:
81+
- uses: actions/checkout@v6
82+
83+
- name: Install Rust
84+
uses: dtolnay/rust-toolchain@stable
85+
86+
- name: Package opencode-cloud-core
87+
run: cargo package -p opencode-cloud-core --allow-dirty
88+
89+
- name: Determine core version and publication status
90+
id: core-publication
91+
run: |
92+
set -euo pipefail
93+
94+
core_version="$(python3 -c 'import pathlib, tomllib; print(tomllib.loads(pathlib.Path("packages/core/Cargo.toml").read_text())["package"]["version"])')"
95+
echo "core_version=${core_version}" >> "$GITHUB_OUTPUT"
96+
97+
if ./scripts/check-crate-version-published.sh opencode-cloud-core "${core_version}"; then
98+
echo "published=true" >> "$GITHUB_OUTPUT"
99+
else
100+
check_exit=$?
101+
if [ "${check_exit}" -eq 1 ]; then
102+
echo "published=false" >> "$GITHUB_OUTPUT"
103+
else
104+
exit "${check_exit}"
105+
fi
106+
fi
107+
108+
- name: Package opencode-cloud (PR no-verify archive)
109+
if: github.event_name == 'pull_request'
110+
run: cargo package -p opencode-cloud --allow-dirty --no-verify
111+
112+
- name: Verify packaged opencode-cloud against local core (PR)
113+
if: github.event_name == 'pull_request'
114+
run: |
115+
set -euo pipefail
116+
117+
crate_archive="$(ls -t target/package/opencode-cloud-*.crate | head -n1)"
118+
if [ -z "${crate_archive}" ]; then
119+
echo "Error: opencode-cloud package archive not found."
120+
exit 1
121+
fi
122+
123+
crate_basename="$(basename "${crate_archive}" .crate)"
124+
unpack_dir="$(mktemp -d)"
125+
config_file="$(mktemp)"
126+
trap 'rm -rf "${unpack_dir}" "${config_file}"' EXIT
127+
128+
tar -xzf "${crate_archive}" -C "${unpack_dir}"
129+
crate_dir="${unpack_dir}/${crate_basename}"
130+
manifest_path="${crate_dir}/Cargo.toml"
131+
if [ ! -f "${manifest_path}" ]; then
132+
echo "Error: unpacked package manifest not found at ${manifest_path}"
133+
exit 1
134+
fi
135+
136+
cat > "${config_file}" <<EOF
137+
[patch.crates-io]
138+
opencode-cloud-core = { path = "${GITHUB_WORKSPACE}/packages/core" }
139+
EOF
140+
141+
cargo check --manifest-path "${manifest_path}" --config "${config_file}"
142+
143+
- name: Package opencode-cloud (main push, published core)
144+
if: github.event_name == 'push' && steps.core-publication.outputs.published == 'true'
145+
run: cargo package -p opencode-cloud --allow-dirty
146+
147+
- name: Skip opencode-cloud package verify (main push, core unpublished)
148+
if: github.event_name == 'push' && steps.core-publication.outputs.published != 'true'
149+
run: |
150+
echo "Skipping opencode-cloud packaging verify on main push."
151+
echo "Reason: opencode-cloud-core ${{ steps.core-publication.outputs.core_version }} is not yet published on crates.io."
152+
{
153+
echo "## Cargo Package Verify"
154+
echo ""
155+
echo "Skipped \`cargo package -p opencode-cloud\` on push to main."
156+
echo "Reason: dependency \`opencode-cloud-core ${{ steps.core-publication.outputs.core_version }}\` is not yet published on crates.io."
157+
} >> "$GITHUB_STEP_SUMMARY"
158+
77159
# Build, lint, and test on multiple platforms
78160
# Clippy/build/test must run on each platform because #[cfg(target_os)] causes
79161
# different code to compile (e.g., systemd.rs on Linux, launchd.rs on macOS)

packages/cli-rust/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ documentation.workspace = true
1010
keywords.workspace = true
1111
categories.workspace = true
1212
description = "CLI for managing opencode as a persistent cloud service"
13-
readme = "../../README.md"
13+
readme = "README.md"
1414
default-run = "opencode-cloud"
1515

1616
[[bin]]

packages/cli-rust/src/commands/runtime_shared/drift.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
//! running container copies to detect stale local-dev/container mismatches.
55
66
use opencode_cloud_core::docker::{
7-
CONTAINER_NAME, DockerClient, container_is_running, exec_command_with_status,
7+
CONTAINER_NAME, DockerClient, ENTRYPOINT_SH, HEALTHCHECK_SH, OPENCODE_CLOUD_BOOTSTRAP_SH,
8+
container_is_running, exec_command_with_status,
89
};
910

1011
pub const REBUILD_CACHED_COMMAND: &str = "occ start --cached-rebuild-sandbox-image";
@@ -37,19 +38,17 @@ const TRACKED_RUNTIME_ASSETS: &[RuntimeAsset] = &[
3738
RuntimeAsset {
3839
name: "bootstrap helper",
3940
container_path: "/usr/local/bin/opencode-cloud-bootstrap",
40-
expected_bytes: include_bytes!(
41-
"../../../../core/src/docker/files/opencode-cloud-bootstrap.sh"
42-
),
41+
expected_bytes: OPENCODE_CLOUD_BOOTSTRAP_SH,
4342
},
4443
RuntimeAsset {
4544
name: "entrypoint",
4645
container_path: "/usr/local/bin/entrypoint.sh",
47-
expected_bytes: include_bytes!("../../../../core/src/docker/files/entrypoint.sh"),
46+
expected_bytes: ENTRYPOINT_SH,
4847
},
4948
RuntimeAsset {
5049
name: "healthcheck",
5150
container_path: "/usr/local/bin/healthcheck.sh",
52-
expected_bytes: include_bytes!("../../../../core/src/docker/files/healthcheck.sh"),
51+
expected_bytes: HEALTHCHECK_SH,
5352
},
5453
];
5554

packages/core/src/docker/assets.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
//! Canonical embedded Docker runtime assets shared across crates.
2+
//!
3+
//! Keep these constants as the single source of truth for runtime drift checks
4+
//! and Docker build-context packaging.
5+
6+
pub const OPENCODE_CLOUD_BOOTSTRAP_SH: &[u8] = include_bytes!("files/opencode-cloud-bootstrap.sh");
7+
pub const ENTRYPOINT_SH: &[u8] = include_bytes!("files/entrypoint.sh");
8+
pub const HEALTHCHECK_SH: &[u8] = include_bytes!("files/healthcheck.sh");

packages/core/src/docker/image.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
66
use super::progress::ProgressReporter;
77
use super::{
8-
CONTAINER_NAME, DOCKERFILE, DockerClient, DockerError, IMAGE_NAME_DOCKERHUB, IMAGE_NAME_GHCR,
9-
IMAGE_TAG_DEFAULT, active_resource_names, remap_image_tag,
8+
CONTAINER_NAME, DOCKERFILE, DockerClient, DockerError, ENTRYPOINT_SH, HEALTHCHECK_SH,
9+
IMAGE_NAME_DOCKERHUB, IMAGE_NAME_GHCR, IMAGE_TAG_DEFAULT, OPENCODE_CLOUD_BOOTSTRAP_SH,
10+
active_resource_names, remap_image_tag,
1011
};
1112
use bollard::moby::buildkit::v1::StatusResponse as BuildkitStatusResponse;
1213
use bollard::models::BuildInfoAux;
@@ -1028,19 +1029,19 @@ fn create_build_context_with_repo_root(
10281029
append_bytes(
10291030
&mut tar,
10301031
"packages/core/src/docker/files/entrypoint.sh",
1031-
include_bytes!("files/entrypoint.sh"),
1032+
ENTRYPOINT_SH,
10321033
0o644,
10331034
)?;
10341035
append_bytes(
10351036
&mut tar,
10361037
"packages/core/src/docker/files/opencode-cloud-bootstrap.sh",
1037-
include_bytes!("files/opencode-cloud-bootstrap.sh"),
1038+
OPENCODE_CLOUD_BOOTSTRAP_SH,
10381039
0o644,
10391040
)?;
10401041
append_bytes(
10411042
&mut tar,
10421043
"packages/core/src/docker/files/healthcheck.sh",
1043-
include_bytes!("files/healthcheck.sh"),
1044+
HEALTHCHECK_SH,
10441045
0o644,
10451046
)?;
10461047
append_bytes(

packages/core/src/docker/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
//! - User management operations (create, delete, lock/unlock users)
1313
//! - Image update and rollback operations
1414
15+
mod assets;
1516
mod client;
1617
pub mod container;
1718
mod dockerfile;
@@ -40,6 +41,7 @@ pub use health::{
4041
};
4142

4243
// Dockerfile constants
44+
pub use assets::{ENTRYPOINT_SH, HEALTHCHECK_SH, OPENCODE_CLOUD_BOOTSTRAP_SH};
4345
pub use dockerfile::{DOCKERFILE, IMAGE_NAME_DOCKERHUB, IMAGE_NAME_GHCR, IMAGE_TAG_DEFAULT};
4446

4547
// Image operations
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
if [ "$#" -ne 2 ]; then
5+
echo "Usage: $0 <crate-name> <version>" >&2
6+
exit 2
7+
fi
8+
9+
crate_name="$1"
10+
crate_version="$2"
11+
api_url="https://crates.io/api/v1/crates/${crate_name}/${crate_version}"
12+
13+
tmp_response="$(mktemp)"
14+
trap 'rm -f "${tmp_response}"' EXIT
15+
16+
http_status="$(
17+
curl \
18+
--silent \
19+
--show-error \
20+
--location \
21+
--output "${tmp_response}" \
22+
--write-out '%{http_code}' \
23+
"${api_url}"
24+
)"
25+
26+
case "${http_status}" in
27+
200)
28+
parsed_version="$(
29+
python3 - "${tmp_response}" <<'PY'
30+
import json
31+
import pathlib
32+
import sys
33+
34+
path = pathlib.Path(sys.argv[1])
35+
data = json.loads(path.read_text())
36+
print(data.get("version", {}).get("num", ""))
37+
PY
38+
)"
39+
if [ "${parsed_version}" != "${crate_version}" ]; then
40+
echo "Error: crates.io returned unexpected version for ${crate_name}." >&2
41+
echo "Expected: ${crate_version}" >&2
42+
echo "Actual: ${parsed_version:-<empty>}" >&2
43+
exit 2
44+
fi
45+
echo "Published on crates.io: ${crate_name} ${crate_version}"
46+
exit 0
47+
;;
48+
404)
49+
echo "Not yet published on crates.io: ${crate_name} ${crate_version}"
50+
exit 1
51+
;;
52+
*)
53+
echo "Error: Unexpected crates.io response (${http_status}) for ${crate_name} ${crate_version}" >&2
54+
echo "URL: ${api_url}" >&2
55+
echo "Body (first 400 chars):" >&2
56+
head -c 400 "${tmp_response}" >&2 || true
57+
echo >&2
58+
exit 2
59+
;;
60+
esac

0 commit comments

Comments
 (0)