Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
afe2924
chore: bump ioFog Go SDK and component pins for v3.9.0-rc.1
emirhandurmus Sep 17, 2026
0251c31
fix: remove HAL and BLE agent configuration fields
emirhandurmus Sep 17, 2026
0c2534f
feat: adopt registry type, ca, and insecure in YAML and tables
emirhandurmus Sep 17, 2026
3ea1209
feat: deploy Model, RuntimeClass, and MicroserviceTemplate from YAML
emirhandurmus Sep 17, 2026
4342700
feat: attach and detach fleet models and runtime classes to agents
emirhandurmus Sep 17, 2026
bc2ee0f
feat: add get, describe, and delete for models, runtime classes, and …
emirhandurmus Sep 17, 2026
dd7e661
feat: microservice template, models catalog, and deploy patch-model flag
emirhandurmus Sep 17, 2026
ff42a8b
fix: show only active Kubernetes controller pods and add models to ge…
emirhandurmus Sep 17, 2026
de7570b
docs: document v3.9.0-rc.1 breaking changes and new deploy flags
emirhandurmus Sep 17, 2026
c0ebfd7
docs: regenerate CLI reference for attach, get, describe, and delete …
emirhandurmus Sep 17, 2026
8c195a9
fix(ci): pin goreleaser v2.17.1 (latest requires Go 1.27.1)
emirhandurmus Sep 17, 2026
fc78f09
fix go releaser tag
emirhandurmus Sep 17, 2026
616e281
feat: show microservice crash extras and bump v3.9.0-rc.2 pins
emirhandurmus Sep 18, 2026
402216c
feat: add microservice volume scope and bump v3.9.0-rc.3 pins
emirhandurmus Sep 19, 2026
dbbb121
chore: bump iofog-go-sdk to v3.9.0-rc.6 and pin controller and edgelet.
emirhandurmus Sep 23, 2026
049318d
feat: deploy Knowledge from YAML and add --patch-knowledge.
emirhandurmus Sep 23, 2026
706c76c
feat: attach and detach Knowledge to agents.
emirhandurmus Sep 23, 2026
0b0033f
feat: add get, describe, and delete for Knowledge.
emirhandurmus Sep 23, 2026
43d3833
feat: deploy microservice Knowledge catalog.
emirhandurmus Sep 23, 2026
3c98295
feat: describe agent Knowledge, host metrics, and cpuUsage in cores.
emirhandurmus Sep 23, 2026
b302b48
feat: drain the data plane before replacing the edgelet binary on fat…
emirhandurmus Sep 23, 2026
c78e87f
docs: document Knowledge commands and v3.9.0-rc.4 notes.
emirhandurmus Sep 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
run: sudo apt-get update && sudo apt-get install -y gcc-aarch64-linux-gnu gcc-arm-linux-gnueabihf

- name: Install goreleaser
run: go install github.com/goreleaser/goreleaser/v2@latest
run: go install github.com/goreleaser/goreleaser/v2@v2.17.1

- name: Release
env:
Expand Down
56 changes: 56 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,62 @@ All notable changes to potctl / iofogctl are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [v3.9.0-rc.4] - 22-09-2026

### Added

- Fleet Knowledge: `deploy -f`, attach/detach, get/describe/delete
- Microservice `spec.knowledge`; `deploy --patch-knowledge` (combinable with `--patch-model`)
- Describe agent knowledge fields (`knowledgeStatus`, `activeKnowledge`, `knowledgeLastUpdate`); `get all` knowledge table

### Fixed

- Describe agent `modelLastUpdate` treated as Unix milliseconds (same RFC3339 conversion as `lastStatusTime`)

### Changed

- Pin **iofog-go-sdk** to **v3.9.0-rc.6**
- Describe agent status uses a stable field order and prints host fog metrics (`systemCpus`, `systemTotalMemory`, `systemTotalDisk`, OS/kernel). `cpuUsage` is cores (100 = 1 core) for agent and microservice; `systemTotalCPU` stays host utilization %.

## [v3.9.0-rc.3] - 20-09-2026

### Added

- Microservice volume mapping `scope` (`private` default, or `shared`) when `type` is `volume`.
`deploy -f` accepts it; `describe` prints Controller’s value. Omit on write → Controller stores `private`.

### Changed

- Bump **iofog-go-sdk** to **v3.9.0-rc.4**
- Component pins: controller **3.9.0-rc.5**, edgelet binary **v1.1.0-rc.3** / image **1.1.0-rc.3**

## [v3.9.0-rc.2] - 18-09-2026

### Added

- `get` / `describe` microservice: print Controller `lastError`, `lastErrorAt`, and `restartCount`. `get` adds a compact crash summary when those fields are set; `describe` always prints them (`lastErrorAt` as a timestamp). Empty extras stay quiet on `get` for older Controllers. Distinct from current `errorMessage`.

## [v3.9.0-rc.1] - 17-09-2026

### Breaking

- Remove agent HAL/BLE fields `deviceScanFrequency`, `bluetoothEnabled`, and `abstractedHardwareEnabled`. Keep `edgeGuardFrequency`.
- Registry YAML: drop `isSecure`, `requiresCert`, and `certificate`. Use `type` (`oci` or `hf`), `ca`, and `insecure`. Email is optional.

### Added

- `deploy -f` for `kind: Model`, `RuntimeClass`, and `MicroserviceTemplate`
- `attach` / `detach` model and runtimeclass to agents by name
- `get` / `describe` / `delete` for model, runtimeclass, and microservice-template
- Microservice `spec.template` and `spec.models`; `deploy --patch-model` patches the catalog only (Controller decides rebuild)
- Describe microservice: models catalog, `podId`, and container 3.9 fields (`runAsGroup`, `entrypoint`, `tmpfs`, `devices`, …)
- Describe agent: parsed `runtimeClasses`, `availableCdiDevices`, `modelStatus`, `activeModels`, and `modelLastUpdate`
- `get all` includes a models table

### Fixed

- `get controllers` lists Running/Pending Kubernetes controller pods only (skips Failed, Succeeded, and terminating) and no longer persists those pods in namespace config

## [v3.8.3-rc.2] — September 2026

### Fixed
Expand Down
30 changes: 11 additions & 19 deletions assets/edgelet/scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -139,22 +139,20 @@ if [ "$ACTION" = "rollback" ]; then
else
curl -fsSL -o "$_staged" "$_purl" || die "Failed to download rollback binary"
fi
_old_embed=""
if [ "$OS" = "linux" ]; then
_old_embed=$(installed_embed_hash)
fi
[ "$OS" = "linux" ] && stop_edgelet_service "$INIT"
stop_edgelet_daemon_desktop "$OS"
install_binary_file "$_staged" "$BINARY_PATH"
replace_staged_edgelet_binary "$_staged"
install_dirs_for_os "$OS"
if [ "$FORCE_CONFIG" != true ] && [ -f "$_cfgbak" ]; then
maybe_sudo install -m 640 "$_cfgbak" "$CONFIG_FILE"
fi
record_restart_data_plane_decision "$_old_embed"
_sha=$(sha256_file "$BINARY_PATH")
write_install_receipt "$EDGELET_VERSION" "$_pos" "$_parch" "$CONTAINER_ENGINE" "$_purl" "$_sha" "rollback"
"$SCRIPT_DIR/bundled.sh" || true
stage_pending_install_receipt "$EDGELET_VERSION" "$_pos" "$_parch" "$CONTAINER_ENGINE" "$_purl" "$_sha" "rollback"
if [ "$SKIP_START" = true ]; then
info "Skipping daemon start (--skip-start); use start_edgelet.sh"
exit 0
fi
_run_post_install
commit_pending_install_receipt
info "Rollback to ${EDGELET_VERSION} complete."
exit 0
fi
Expand All @@ -177,29 +175,23 @@ if [ "$ACTION" = "upgrade" ]; then
_cfg_backup="${BACKUP_DIR}/config.yaml.$(date +%Y%m%d%H%M%S 2>/dev/null || date +%s)"
cp "$CONFIG_FILE" "$_cfg_backup" 2>/dev/null || true
cache_binary "$_cur_ver" "$_cur_os" "$_cur_arch" "$BINARY_PATH"
write_previous_release "$_cur_ver" "$_cur_os" "$_cur_arch" "$_cur_eng" "$_cur_src" "$_cur_sha" "$_cfg_backup"
_old_embed=""
if [ "$OS" = "linux" ]; then
_old_embed=$(installed_embed_hash)
fi
[ "$OS" = "linux" ] && stop_edgelet_service "$INIT"
stop_edgelet_daemon_desktop "$OS"
_staged="${TMPDIR}/edgelet-bin"
download_or_stage_binary "$_staged"
verify_binary_checksum "$_staged"
install_binary_file "$_staged" "$BINARY_PATH"
replace_staged_edgelet_binary "$_staged"
write_previous_release "$_cur_ver" "$_cur_os" "$_cur_arch" "$_cur_eng" "$_cur_src" "$_cur_sha" "$_cfg_backup"
install_dirs_for_os "$OS"
_sha=$(sha256_file "$BINARY_PATH")
_method="upgrade"
[ "$AIRGAP" = true ] && _method="upgrade-airgap"
write_install_receipt "$EDGELET_VERSION" "$OS" "$ARCH" "$CONTAINER_ENGINE" "$(compute_source_url)" "$_sha" "$_method"
"$SCRIPT_DIR/bundled.sh" || true
record_restart_data_plane_decision "$_old_embed"
stage_pending_install_receipt "$EDGELET_VERSION" "$OS" "$ARCH" "$CONTAINER_ENGINE" "$(compute_source_url)" "$_sha" "$_method"
if [ "$SKIP_START" = true ]; then
info "Skipping daemon start (--skip-start); use start_edgelet.sh"
exit 0
fi
_run_post_install
commit_pending_install_receipt
info "Upgrade to ${EDGELET_VERSION} complete."
exit 0
fi
Expand Down
2 changes: 2 additions & 0 deletions assets/edgelet/scripts/install_init_units.sh
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ StartLimitBurst=5
Type=simple
ExecStartPre=/bin/sh -c 'mountpoint -q /sys/fs/bpf || mount -t bpf bpf /sys/fs/bpf 2>/dev/null || true'
ExecStart=/usr/local/bin/edgelet runtime-bootstrap
# Reap only after a verified data-plane drain (receipt under /run/edgelet).
ExecStopPost=-/usr/local/bin/edgelet runtime reap-orphans
Restart=always
RestartSec=5s
Expand Down Expand Up @@ -315,6 +316,7 @@ stop() {
fi
fi
rm -f "${pidfile}" 2>/dev/null || true
# Reap only after a verified data-plane drain (receipt under /run/edgelet).
/usr/local/bin/edgelet runtime reap-orphans || ewarn "orphan reap exited non-zero"
eend 0
}
Expand Down
105 changes: 94 additions & 11 deletions assets/edgelet/scripts/lib/embed.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/bin/sh
# Embed-hash helpers for conditional edgelet-containerd restart (upstream install.sh 9a9ded8 parity).
# Embed-hash helpers for fat OTA drain before binary replace.
# Requires lib/common.sh (maybe_sudo, info) when sourced.

restart_data_plane_marker_path() {
Expand Down Expand Up @@ -42,11 +42,44 @@ consume_restart_data_plane_marker() {
[ "$_requested" = true ]
}

# installed_embed_hash returns the basename of data/current (embed SHA256 prefix), or empty.
edgelet_data_root() {
if [ -n "${EDGELET_DATA_DIR:-}" ]; then
echo "${EDGELET_DATA_DIR}"
return 0
fi
echo "/var/lib/edgelet"
}

# embed_bundle_ready is true when dir has an executable fat runtime and the
# same companion files the extract path requires before it will run.
embed_bundle_ready() {
_dir="$1"
[ -n "$_dir" ] || return 1
_fat="${_dir}/bin/edgelet"
[ -f "$_fat" ] && [ -x "$_fat" ] || return 1
_magic=$(od -An -tx1 -N 4 "$_fat" 2>/dev/null | tr -d ' \n' || true)
[ "$_magic" = "7f454c46" ] || return 1
[ -f "${_dir}/bin/containerd-shim-runc-v2" ] || return 1
[ -f "${_dir}/bin/aux/xtables-legacy-multi" ] || return 1
[ -f "${_dir}/bin/ip" ] || return 1
[ -f "${_dir}/bin/busybox" ] || return 1
[ -L "${_dir}/bin/aux/iptables" ] || return 1
[ "$(readlink "${_dir}/bin/aux/iptables" 2>/dev/null || true)" = "xtables-legacy-multi" ]
}

# installed_embed_hash prints the ready data/current bundle hash, or nothing
# when the symlink is missing or the fat runtime is not ready.
installed_embed_hash() {
_link="/var/lib/edgelet/data/current"
_link="$(edgelet_data_root)/data/current"
[ -L "$_link" ] || return 0
basename "$(readlink -f "$_link" 2>/dev/null || readlink "$_link")"
_target=$(readlink "$_link" 2>/dev/null || true)
[ -n "$_target" ] || return 0
case "$_target" in
/*) ;;
*) _target="$(CDPATH= cd -- "$(dirname "$_link")" && pwd)/${_target}" ;;
esac
embed_bundle_ready "$_target" || return 0
basename "$_target"
}

# binary_embed_hash reads embed hash from a linux thin binary (edgelet version --verbose).
Expand All @@ -58,8 +91,9 @@ binary_embed_hash() {
| head -1
}

# should_restart_data_plane is true when containerEngine=edgelet and the embed hash changed
# (or data/current is not yet set). Returns false for docker/podman or lite builds without embed.
# should_restart_data_plane is true when containerEngine=edgelet, the new embed
# hash is set, and the ready current bundle is missing or different.
# Returns false for docker/podman or a thin binary with no embed hash.
should_restart_data_plane() {
_eng="$1"
_old="$2"
Expand All @@ -70,15 +104,61 @@ should_restart_data_plane() {
[ "$_old" != "$_new" ]
}

# quiesce_data_plane_for_replace drains labeled workloads through CRI using the
# staged binary. Control stays up. It does not use edgelet.sock and does not
# fall back to the installed binary. Non-zero leaves that binary in place.
quiesce_data_plane_for_replace() {
_bin="$1"
if [ -z "$_bin" ] || [ ! -x "$_bin" ]; then
return 1
fi
info "Draining data plane before embed replace"
"$_bin" --quiet runtime drain --direct
}

# replace_staged_edgelet_binary installs a staged thin binary.
# Fat OTA drains and verifies while control is still up, stops the data plane,
# then replaces the binary. A matching ready hash, or docker/podman, stops
# control only and does not stop edgelet-containerd.
# Verify failure exits non-zero and does not replace the binary.
# Sets OTA_RESTART_DATA_PLANE=true or false and writes the start-layer marker.
replace_staged_edgelet_binary() {
_staged="$1"
OTA_RESTART_DATA_PLANE=false
[ -f "$_staged" ] || die "Staged binary missing: ${_staged}"
chmod 755 "$_staged" || die "Cannot prepare staged binary: ${_staged}"
if [ "$OS" = "linux" ]; then
_old_embed=$(installed_embed_hash)
_new_embed=$(binary_embed_hash "$_staged")
if should_restart_data_plane "$CONTAINER_ENGINE" "$_old_embed" "$_new_embed"; then
OTA_RESTART_DATA_PLANE=true
info "Embed hash: ${_old_embed:-<none>} -> ${_new_embed}; data-plane restart required"
if quiesce_data_plane_for_replace "$_staged"; then
stop_edgelet_containerd_unit "$INIT"
elif ! edgelet_containerd_unit_active \
&& [ "$(binary_embed_hash "$BINARY_PATH")" = "$_new_embed" ]; then
info "Data plane already stopped after a previous verified drain; binary replace continues"
else
die "Data-plane drain did not verify; binary was not replaced"
fi
else
stop_edgelet_service "$INIT"
fi
else
stop_edgelet_daemon_desktop "$OS"
fi
install_binary_file "$_staged" "$BINARY_PATH"
write_restart_data_plane_marker "$OTA_RESTART_DATA_PLANE"
}

start_edgelet_containerd_unit() {
_init="$1"
_restart="$2"
case "${_init}" in
systemd)
maybe_sudo systemctl enable edgelet-containerd 2>/dev/null || true
if [ "$_restart" = true ]; then
info "Embedded bundle hash changed; restarting edgelet-containerd (data plane)"
maybe_sudo systemctl stop edgelet-containerd 2>/dev/null || true
info "Embedded bundle hash changed; starting edgelet-containerd (data plane)"
maybe_sudo systemctl reset-failed edgelet-containerd 2>/dev/null || true
maybe_sudo systemctl start edgelet-containerd
else
Expand All @@ -88,8 +168,7 @@ start_edgelet_containerd_unit() {
openrc)
maybe_sudo rc-update add edgelet-containerd default 2>/dev/null || true
if [ "$_restart" = true ]; then
info "Embedded bundle hash changed; restarting edgelet-containerd (data plane)"
maybe_sudo rc-service edgelet-containerd stop 2>/dev/null || true
info "Embedded bundle hash changed; starting edgelet-containerd (data plane)"
maybe_sudo rc-service edgelet-containerd start 2>/dev/null || true
else
maybe_sudo rc-service edgelet-containerd start 2>/dev/null || true
Expand All @@ -98,11 +177,15 @@ start_edgelet_containerd_unit() {
esac
}

# record_restart_data_plane_decision compares a ready embed hash to a binary.
# Pass the staged binary as $2. The default is the installed path, which is
# only valid before that path has been replaced.
record_restart_data_plane_decision() {
_old_embed="$1"
_bin="${2:-$BINARY_PATH}"
_restart_dp=false
if [ "$OS" = "linux" ]; then
_new_embed=$(binary_embed_hash "$BINARY_PATH")
_new_embed=$(binary_embed_hash "$_bin")
if should_restart_data_plane "$CONTAINER_ENGINE" "$_old_embed" "$_new_embed"; then
_restart_dp=true
info "Embed hash: ${_old_embed:-<none>} -> ${_new_embed}; data-plane restart required"
Expand Down
40 changes: 31 additions & 9 deletions assets/edgelet/scripts/lib/receipt.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,17 @@ kv_get() {
echo "$_line" | sed "s/^${_key}=//"
}

write_install_receipt() {
_ver="$1"
_os="$2"
_arch="$3"
_eng="$4"
_url="$5"
_sha="$6"
_method="$7"
PENDING_RECEIPT_FILE="${PENDING_RECEIPT_FILE:-${BACKUP_DIR}/pending-install-receipt}"

_write_receipt_body() {
_dest="$1"
_ver="$2"
_os="$3"
_arch="$4"
_eng="$5"
_url="$6"
_sha="$7"
_method="$8"
maybe_sudo mkdir -p "$BACKUP_DIR"
_ts=$(date -u '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || date -u)
{
Expand All @@ -38,8 +41,27 @@ write_install_receipt() {
printf 'installed_at=%s\n' "$_ts"
printf 'install_method=%s\n' "$_method"
printf 'binary_sha256=%s\n' "$_sha"
} | maybe_sudo tee "$RECEIPT_FILE" >/dev/null
} | maybe_sudo tee "$_dest" >/dev/null
maybe_sudo chmod 600 "$_dest" 2>/dev/null || true
}

write_install_receipt() {
_write_receipt_body "$RECEIPT_FILE" "$@"
}

# stage_pending_install_receipt records a receipt that becomes current only
# after start_edgelet.sh brings the units back up.
stage_pending_install_receipt() {
_write_receipt_body "$PENDING_RECEIPT_FILE" "$@"
}

commit_pending_install_receipt() {
[ -f "$PENDING_RECEIPT_FILE" ] || return 0
maybe_sudo mkdir -p "$BACKUP_DIR"
maybe_sudo cp "$PENDING_RECEIPT_FILE" "$RECEIPT_FILE"
maybe_sudo chmod 600 "$RECEIPT_FILE" 2>/dev/null || true
maybe_sudo rm -f "$PENDING_RECEIPT_FILE"
info "Install receipt written after services started"
}

write_previous_release() {
Expand Down
Loading
Loading