Skip to content

Commit 9e4fbd4

Browse files
committed
feat(simulator): add release packaging and installer
1 parent 2c37d72 commit 9e4fbd4

6 files changed

Lines changed: 478 additions & 1 deletion

File tree

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# SPDX-FileCopyrightText: © 2026 Phala Network <[email protected]>
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
name: Simulator Release
6+
7+
on:
8+
workflow_dispatch:
9+
inputs:
10+
version:
11+
description: 'Release version (for example: 0.5.8)'
12+
required: true
13+
type: string
14+
push:
15+
tags:
16+
- 'simulator-v*'
17+
18+
permissions:
19+
contents: write
20+
21+
jobs:
22+
build-and-release:
23+
runs-on: ubuntu-latest
24+
env:
25+
TARGET_TRIPLE: x86_64-unknown-linux-musl
26+
steps:
27+
- name: Checkout repository
28+
uses: actions/checkout@v4
29+
30+
- name: Resolve version and tag
31+
run: |
32+
if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
33+
VERSION="${{ github.event.inputs.version }}"
34+
else
35+
VERSION="${GITHUB_REF#refs/tags/simulator-v}"
36+
fi
37+
VERSION="${VERSION#simulator-v}"
38+
TAG="simulator-v${VERSION}"
39+
echo "VERSION=${VERSION}" >> "$GITHUB_ENV"
40+
echo "TAG=${TAG}" >> "$GITHUB_ENV"
41+
echo "Resolved release version: ${VERSION}"
42+
43+
- name: Install musl toolchain
44+
run: |
45+
sudo apt-get update
46+
sudo apt-get install -y musl-tools
47+
48+
- name: Set up Rust toolchain
49+
uses: dtolnay/rust-toolchain@stable
50+
with:
51+
targets: ${{ env.TARGET_TRIPLE }}
52+
53+
- name: Cache Rust build artifacts
54+
uses: Swatinem/rust-cache@v2
55+
56+
- name: Build musl simulator binary
57+
run: cargo build --locked --release --target "${TARGET_TRIPLE}" -p dstack-guest-agent-simulator
58+
59+
- name: Package release bundle
60+
run: ./guest-agent-simulator/package-release.sh "${VERSION}" "${TARGET_TRIPLE}"
61+
62+
- name: GitHub Release
63+
uses: softprops/action-gh-release@v1
64+
with:
65+
tag_name: ${{ env.TAG }}
66+
name: "Simulator Release v${{ env.VERSION }}"
67+
files: |
68+
guest-agent-simulator/dist/dstack-simulator-${{ env.VERSION }}-${{ env.TARGET_TRIPLE }}.tar.gz
69+
guest-agent-simulator/dist/dstack-simulator-${{ env.VERSION }}-${{ env.TARGET_TRIPLE }}.tar.gz.sha256
70+
guest-agent-simulator/install-systemd.sh
71+
body: |
72+
## Release Assets
73+
74+
- `dstack-simulator-${{ env.VERSION }}-${{ env.TARGET_TRIPLE }}.tar.gz`
75+
- `dstack-simulator-${{ env.VERSION }}-${{ env.TARGET_TRIPLE }}.tar.gz.sha256`
76+
- `install-systemd.sh`
77+
78+
The tarball contains the musl-linked `dstack-simulator` binary together with the default
79+
simulator config, fixture data, and a systemd unit template.
80+
81+
## Quick Start
82+
83+
Download and run directly:
84+
85+
```bash
86+
curl -LO https://github.com/${{ github.repository }}/releases/download/${{ env.TAG }}/dstack-simulator-${{ env.VERSION }}-${{ env.TARGET_TRIPLE }}.tar.gz
87+
tar -xzf dstack-simulator-${{ env.VERSION }}-${{ env.TARGET_TRIPLE }}.tar.gz
88+
cd dstack-simulator-${{ env.VERSION }}-${{ env.TARGET_TRIPLE }}
89+
./dstack-simulator -c dstack.toml
90+
```
91+
92+
Install to systemd:
93+
94+
```bash
95+
curl -fsSL https://raw.githubusercontent.com/${{ github.repository }}/${{ env.TAG }}/guest-agent-simulator/install-systemd.sh | sudo bash -s -- --version ${{ env.VERSION }}
96+
```

guest-agent-simulator/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
dist/
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[Unit]
2+
Description=dstack Simulator Service
3+
After=network.target
4+
5+
[Service]
6+
Type=simple
7+
WorkingDirectory=@INSTALL_DIR@
8+
ExecStart=@INSTALL_DIR@/dstack-simulator -c @INSTALL_DIR@/dstack.toml
9+
Restart=on-failure
10+
RestartSec=2s
11+
User=@USER@
12+
Group=@GROUP@
13+
Environment=RUST_LOG=@RUST_LOG@
14+
StandardOutput=journal
15+
StandardError=journal
16+
17+
[Install]
18+
WantedBy=multi-user.target
Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
#!/bin/bash
2+
3+
# SPDX-FileCopyrightText: © 2026 Phala Network <[email protected]>
4+
#
5+
# SPDX-License-Identifier: Apache-2.0
6+
7+
set -euo pipefail
8+
9+
REPO="Dstack-TEE/dstack"
10+
TARGET="x86_64-unknown-linux-musl"
11+
INSTALL_ROOT="/opt/dstack-simulator"
12+
SERVICE_NAME="dstack-simulator"
13+
SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service"
14+
BIN_LINK="/usr/local/bin/dstack-simulator"
15+
RUN_USER="root"
16+
RUN_GROUP="root"
17+
RUST_LOG="info"
18+
VERSION=""
19+
TARBALL=""
20+
SKIP_SYSTEMD=0
21+
22+
usage() {
23+
cat <<'EOF'
24+
Usage: install-systemd.sh [options]
25+
26+
Install dstack-simulator from a GitHub release tarball and register it as a systemd service.
27+
28+
Options:
29+
--version <version> Release version to install (e.g. 0.5.8). Defaults to latest simulator release.
30+
--tarball <path-or-url> Install from a local tarball or explicit URL.
31+
--repo <owner/repo> GitHub repository to download from. Default: Dstack-TEE/dstack
32+
--target <triple> Target triple asset to download. Default: x86_64-unknown-linux-musl
33+
--install-root <path> Installation root. Default: /opt/dstack-simulator
34+
--service-name <name> systemd service name. Default: dstack-simulator
35+
--service-file <path> systemd unit path. Default: /etc/systemd/system/dstack-simulator.service
36+
--bin-link <path> Binary symlink path. Default: /usr/local/bin/dstack-simulator
37+
--user <user> Service user. Default: root
38+
--group <group> Service group. Default: root
39+
--rust-log <level> RUST_LOG value for the systemd unit. Default: info
40+
--skip-systemd Install files but skip systemd daemon-reload/enable/start.
41+
-h, --help Show this help text.
42+
EOF
43+
}
44+
45+
while [[ $# -gt 0 ]]; do
46+
case "$1" in
47+
--version)
48+
VERSION="$2"
49+
shift 2
50+
;;
51+
--tarball)
52+
TARBALL="$2"
53+
shift 2
54+
;;
55+
--repo)
56+
REPO="$2"
57+
shift 2
58+
;;
59+
--target)
60+
TARGET="$2"
61+
shift 2
62+
;;
63+
--install-root)
64+
INSTALL_ROOT="$2"
65+
shift 2
66+
;;
67+
--service-name)
68+
SERVICE_NAME="$2"
69+
SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service"
70+
shift 2
71+
;;
72+
--service-file)
73+
SERVICE_FILE="$2"
74+
shift 2
75+
;;
76+
--bin-link)
77+
BIN_LINK="$2"
78+
shift 2
79+
;;
80+
--user)
81+
RUN_USER="$2"
82+
shift 2
83+
;;
84+
--group)
85+
RUN_GROUP="$2"
86+
shift 2
87+
;;
88+
--rust-log)
89+
RUST_LOG="$2"
90+
shift 2
91+
;;
92+
--skip-systemd)
93+
SKIP_SYSTEMD=1
94+
shift
95+
;;
96+
-h|--help)
97+
usage
98+
exit 0
99+
;;
100+
*)
101+
echo "Unknown argument: $1" >&2
102+
usage >&2
103+
exit 1
104+
;;
105+
esac
106+
done
107+
108+
if [[ $EUID -ne 0 ]]; then
109+
echo "Please run as root." >&2
110+
exit 1
111+
fi
112+
113+
need_cmd() {
114+
command -v "$1" >/dev/null 2>&1 || {
115+
echo "Missing required command: $1" >&2
116+
exit 1
117+
}
118+
}
119+
120+
need_cmd curl
121+
need_cmd tar
122+
need_cmd python3
123+
124+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
125+
LOCAL_BUNDLE_DIR=""
126+
if [[ -f "$SCRIPT_DIR/dstack-simulator" && -f "$SCRIPT_DIR/dstack.toml" ]]; then
127+
LOCAL_BUNDLE_DIR="$SCRIPT_DIR"
128+
fi
129+
130+
cleanup() {
131+
if [[ -n "${TMP_DIR:-}" && -d "${TMP_DIR:-}" ]]; then
132+
rm -rf "$TMP_DIR"
133+
fi
134+
}
135+
trap cleanup EXIT
136+
137+
normalize_version() {
138+
local value="$1"
139+
value="${value#refs/tags/}"
140+
value="${value#simulator-v}"
141+
echo "$value"
142+
}
143+
144+
latest_version() {
145+
curl -fsSL "https://api.github.com/repos/${REPO}/releases?per_page=100" | python3 -c '
146+
import json, sys
147+
for release in json.load(sys.stdin):
148+
if release.get("draft") or release.get("prerelease"):
149+
continue
150+
tag = release.get("tag_name", "")
151+
if tag.startswith("simulator-v"):
152+
print(tag[len("simulator-v"):])
153+
break
154+
else:
155+
raise SystemExit("No simulator release found")
156+
'
157+
}
158+
159+
guess_local_version() {
160+
local base
161+
base="$(basename "$LOCAL_BUNDLE_DIR")"
162+
base="${base#dstack-simulator-}"
163+
base="${base%-${TARGET}}"
164+
if [[ "$base" == "dstack-simulator" || -z "$base" ]]; then
165+
return 1
166+
fi
167+
echo "$base"
168+
}
169+
170+
if [[ -z "$VERSION" ]]; then
171+
if [[ -n "$LOCAL_BUNDLE_DIR" ]]; then
172+
VERSION="$(guess_local_version || true)"
173+
fi
174+
if [[ -z "$VERSION" ]]; then
175+
VERSION="$(latest_version)"
176+
fi
177+
fi
178+
VERSION="$(normalize_version "$VERSION")"
179+
180+
ASSET_NAME="dstack-simulator-${VERSION}-${TARGET}.tar.gz"
181+
TAG="simulator-v${VERSION}"
182+
TMP_DIR="$(mktemp -d)"
183+
184+
fetch_to_file() {
185+
local source="$1"
186+
local dest="$2"
187+
if [[ "$source" =~ ^https?:// ]]; then
188+
curl -fsSL "$source" -o "$dest"
189+
else
190+
cp "$source" "$dest"
191+
fi
192+
}
193+
194+
extract_tarball() {
195+
local tarball_path="$1"
196+
local dest_dir="$2"
197+
tar -xzf "$tarball_path" -C "$dest_dir"
198+
}
199+
200+
BUNDLE_DIR=""
201+
if [[ -n "$LOCAL_BUNDLE_DIR" && -z "$TARBALL" ]]; then
202+
BUNDLE_DIR="$LOCAL_BUNDLE_DIR"
203+
else
204+
TARBALL_PATH="$TMP_DIR/$ASSET_NAME"
205+
CHECKSUM_PATH="$TMP_DIR/${ASSET_NAME}.sha256"
206+
207+
if [[ -n "$TARBALL" ]]; then
208+
echo "Fetching simulator tarball from: $TARBALL"
209+
fetch_to_file "$TARBALL" "$TARBALL_PATH"
210+
else
211+
local_url="https://github.com/${REPO}/releases/download/${TAG}/${ASSET_NAME}"
212+
checksum_url="${local_url}.sha256"
213+
echo "Downloading simulator release ${TAG}"
214+
fetch_to_file "$local_url" "$TARBALL_PATH"
215+
if curl -fsSL "$checksum_url" -o "$CHECKSUM_PATH"; then
216+
(
217+
cd "$TMP_DIR"
218+
sha256sum -c "$(basename "$CHECKSUM_PATH")"
219+
)
220+
else
221+
echo "Warning: checksum asset not found, skipping checksum verification." >&2
222+
fi
223+
fi
224+
225+
extract_tarball "$TARBALL_PATH" "$TMP_DIR"
226+
BUNDLE_DIR="$TMP_DIR/dstack-simulator-${VERSION}-${TARGET}"
227+
fi
228+
229+
if [[ ! -f "$BUNDLE_DIR/dstack-simulator" || ! -f "$BUNDLE_DIR/dstack.toml" ]]; then
230+
echo "Bundle directory is missing expected simulator files: $BUNDLE_DIR" >&2
231+
exit 1
232+
fi
233+
234+
VERSION_DIR="$INSTALL_ROOT/releases/$VERSION"
235+
CURRENT_DIR="$INSTALL_ROOT/current"
236+
237+
mkdir -p "$INSTALL_ROOT/releases"
238+
rm -rf "$VERSION_DIR"
239+
mkdir -p "$VERSION_DIR"
240+
cp -a "$BUNDLE_DIR/." "$VERSION_DIR/"
241+
ln -sfn "$VERSION_DIR" "$CURRENT_DIR"
242+
243+
chown -R "$RUN_USER:$RUN_GROUP" "$VERSION_DIR"
244+
ln -sfn "$CURRENT_DIR/dstack-simulator" "$BIN_LINK"
245+
246+
python3 - "$BUNDLE_DIR/dstack-simulator.service" "$SERVICE_FILE" "$CURRENT_DIR" "$RUN_USER" "$RUN_GROUP" "$RUST_LOG" <<'PY'
247+
from pathlib import Path
248+
import sys
249+
250+
template_path, output_path, install_dir, user, group, rust_log = sys.argv[1:]
251+
template = Path(template_path).read_text()
252+
rendered = (
253+
template
254+
.replace("@INSTALL_DIR@", install_dir)
255+
.replace("@USER@", user)
256+
.replace("@GROUP@", group)
257+
.replace("@RUST_LOG@", rust_log)
258+
)
259+
Path(output_path).write_text(rendered)
260+
PY
261+
262+
echo "Installed dstack-simulator ${VERSION} to ${CURRENT_DIR}"
263+
echo "Binary symlink: ${BIN_LINK}"
264+
echo "Service file: ${SERVICE_FILE}"
265+
266+
if [[ "$SKIP_SYSTEMD" -eq 0 ]]; then
267+
need_cmd systemctl
268+
UNIT_NAME="$(basename "$SERVICE_FILE")"
269+
systemctl daemon-reload
270+
systemctl enable --now "$UNIT_NAME"
271+
echo "systemd service enabled and started: ${UNIT_NAME}"
272+
else
273+
echo "Skipping systemd enable/start (--skip-systemd)."
274+
fi

0 commit comments

Comments
 (0)