Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
19 changes: 19 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ on:
- "**/Cargo.lock"
- "rust-toolchain.toml"
- "scripts/install.sh"
- "scripts/install-cli.sh"
- "scripts/install-completions.sh"
- "scripts/tests/verify-completion-install.sh"
- "scripts/run-with-capabilities.sh"
- "scripts/tests/setup-ublk-access.sh"
- "scripts/tests/verify-capability-runner.sh"
Expand All @@ -24,6 +27,9 @@ on:
- "**/Cargo.lock"
- "rust-toolchain.toml"
- "scripts/install.sh"
- "scripts/install-cli.sh"
- "scripts/install-completions.sh"
- "scripts/tests/verify-completion-install.sh"
- "scripts/run-with-capabilities.sh"
- "scripts/tests/setup-ublk-access.sh"
- "scripts/tests/verify-capability-runner.sh"
Expand Down Expand Up @@ -63,3 +69,16 @@ jobs:
run: sudo scripts/tests/setup-ublk-access.sh "$(id -un)" "$(id -gn)"
- name: Unit tests
run: make test-unit PROFILE=debug

completion-install:
name: Completion installer checks
runs-on: ubuntu-22.04
timeout-minutes: 5
permissions:
contents: read
steps:
- uses: actions/checkout@v6
- name: Shell syntax
run: bash -n scripts/install-completions.sh scripts/install-cli.sh scripts/install.sh scripts/tests/verify-completion-install.sh
- name: Functional checks
run: bash scripts/tests/verify-completion-install.sh
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ TARGET_PROFILE_DIR = $${CARGO_TARGET_DIR:-$$(pwd)/target}/$(PROFILE)
build-server build-server-release \
build-snapshot-image \
build-aenv build-aenv-release install-aenv uninstall-aenv \
check-completion-install \
build-ublk install-ublk \
fmt clippy \
mutants coverage \
Expand Down Expand Up @@ -91,12 +92,17 @@ build-aenv-release:
install-aenv: build-aenv-release
$(AENV_INSTALL_SUDO) install -d "$(AENV_INSTALL_DIR)"
$(AENV_INSTALL_SUDO) install -m 0755 "$${CARGO_TARGET_DIR:-$$(pwd)/target}/release/aenv" "$(AENV_INSTALL_DIR)/aenv"
$(AENV_INSTALL_SUDO) scripts/install-completions.sh install --prefix="$(AENV_INSTALL_PREFIX)" --binary="$(AENV_INSTALL_DIR)/aenv"
@echo "Installed aenv to $(AENV_INSTALL_DIR)/aenv"

uninstall-aenv:
$(AENV_INSTALL_SUDO) scripts/install-completions.sh uninstall --prefix="$(AENV_INSTALL_PREFIX)" --binary="$(AENV_INSTALL_DIR)/aenv"
$(AENV_INSTALL_SUDO) rm -f "$(AENV_INSTALL_DIR)/aenv"
@echo "Removed $(AENV_INSTALL_DIR)/aenv"

check-completion-install:
bash scripts/tests/verify-completion-install.sh

fmt:
$(CARGO) fmt --all -- --check

Expand All @@ -119,6 +125,7 @@ test-unit:
$(CAPABILITY_TEST_ENV) $(CAPABILITY_RUNNER) $(CARGO) test -p uvm-ublk -p uvm-ublk-daemon --lib
bash scripts/tests/verify-capability-runner.sh
bash scripts/tests/verify-install-service.sh
bash scripts/tests/verify-completion-install.sh

test-integration: test-agent-integration test-envd test-ublk

Expand Down
12 changes: 12 additions & 0 deletions docs/src/getting-started/aenv-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,3 +274,15 @@ aenv snapshot list --sandbox-id <sandbox-id>
The table output includes an `IMAGE REF` column (`-` when no image was published); JSON output includes the optional `imageRef` field.

To delete a snapshot, use `aenv template delete <snapshot-id>` or `aenv template delete <name>` — snapshots share the same underlying store as templates and are deleted through the same command.

## Automatic shell completion installation

The CLI installers install completion loaders into standard shell completion directories. `make install-aenv`, `scripts/install-cli.sh`, and the full `scripts/install.sh` installer manage only the completion files and never modify `.bashrc`, `.zshrc`, or Fish configuration files. `make uninstall-aenv` removes files created by the Make-based install while preserving unmanaged files.

For a user-local Make install, use `AENV_INSTALL_PREFIX=~/.local AENV_INSTALL_SUDO= make install-aenv`. If Zsh does not find the installed user completion directory, add this before `compinit`:

```zsh
fpath=(~/.local/share/zsh/site-functions $fpath)
autoload -Uz compinit
compinit
```
97 changes: 97 additions & 0 deletions scripts/install-cli.sh
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,103 @@ fi

echo "Installed: ${DEST}"

install_completion_files() {
local marker='# managed by aenv completion installer'
local prefix home user_mode=0 quoted_binary
local bash_path zsh_path fish_path
prefix="$(dirname "$INSTALL_DIR")"
home="${HOME:-}"
canonical_dir() {
local path="$1" parent name
[[ "$path" == /* ]] || path="$PWD/$path"
if [[ -d "$path" ]]; then
(cd "$path" && pwd -P)
return
fi
parent="${path%/*}"
name="${path##*/}"
[[ "$parent" == "$path" ]] && parent="."
parent="$(cd "$parent" 2>/dev/null && pwd -P)" || return 1
printf '%s/%s\n' "$parent" "$name"
}
prefix="$(canonical_dir "$prefix")" || { echo "warning: could not resolve completion prefix; skipping" >&2; return 0; }
home_real=""
if [[ -n "$home" ]]; then
home_real="$(canonical_dir "$home")" || home_real=""
fi
if [[ -n "$home_real" && ( "$prefix" == "$home_real" || "$prefix" == "$home_real"/* ) ]]; then
user_mode=1
fi
if ((user_mode)); then
[[ -n "$home" ]] || { echo "warning: HOME is unset; skipping completion setup" >&2; return 0; }
bash_path="$home/.local/share/bash-completion/completions/aenv"
zsh_path="$home/.local/share/zsh/site-functions/_aenv"
fish_path="$home/.config/fish/completions/aenv.fish"
else
bash_path="$prefix/share/bash-completion/completions/aenv"
zsh_path="$prefix/share/zsh/site-functions/_aenv"
fish_path="$prefix/share/fish/vendor_completions.d/aenv.fish"
fi
if [[ "$DEST" == *"'"* || "$DEST" == *$'\n'* || "$DEST" == *$'\r'* ]]; then
echo "warning: completion binary path contains unsupported characters; skipping" >&2
return 0
fi
quoted_binary="'$DEST'"

put_loader() {
local path="$1" body="$2" dir tmp
dir="${path%/*}"
if [[ -L "$path" ]]; then
echo "warning: refusing to replace symlink ${path}" >&2
return 0
fi
if [[ -e "$path" ]] && ! grep -Fqx "$marker" "$path" 2>/dev/null; then
echo "warning: leaving unmanaged completion file ${path} untouched" >&2
return 0
fi
local runner=()
if ! (mkdir -p "$dir" 2>/dev/null && [[ -w "$dir" ]]); then
runner=(run_privileged)
fi
"${runner[@]}" mkdir -p "$dir" || { echo "warning: could not create ${dir}" >&2; return 0; }
tmp="$("${runner[@]}" mktemp "$dir/.aenv-completion.XXXXXX")" || {
echo "warning: could not stage ${path}" >&2
return 0
}
if [[ "$path" == "$zsh_path" ]]; then
if ! printf '%s\n%s\n' "$body" "$marker" | "${runner[@]}" tee "$tmp" >/dev/null; then
"${runner[@]}" rm -f "$tmp"
echo "warning: could not stage ${path}" >&2
return 0
fi
else
if ! printf '%s\n%s\n' "$marker" "$body" | "${runner[@]}" tee "$tmp" >/dev/null; then
"${runner[@]}" rm -f "$tmp"
echo "warning: could not stage ${path}" >&2
return 0
fi
fi
if ! "${runner[@]}" chmod 0644 "$tmp" || ! "${runner[@]}" mv -f "$tmp" "$path"; then
"${runner[@]}" rm -f "$tmp"
echo "warning: could not install ${path}" >&2
fi
}

put_loader "$bash_path" "if [[ -x $quoted_binary ]]; then source <($quoted_binary completion bash); fi"
zsh_body="#compdef aenv
if [[ -x $quoted_binary ]]; then eval \"\$($quoted_binary completion zsh)\"; fi"
put_loader "$zsh_path" "$zsh_body"
put_loader "$fish_path" "if test -x $quoted_binary; $quoted_binary completion fish | source; end"
if ((user_mode)); then
echo "If Zsh does not find the completion, add this before compinit:"
echo " fpath=(~/.local/share/zsh/site-functions \$fpath)"
echo " autoload -Uz compinit"
echo " compinit"
fi
}

install_completion_files

if ! command -v aenv &>/dev/null; then
echo ""
echo "Note: ${INSTALL_DIR} is not on your PATH."
Expand Down
137 changes: 137 additions & 0 deletions scripts/install-completions.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
#!/usr/bin/env bash
# Install or remove aenv completion loaders without editing shell startup files.
set -euo pipefail

MARKER='# managed by aenv completion installer'

usage() {
printf 'Usage: %s <install|uninstall> --prefix=<prefix> --binary=<aenv>\n' "$0" >&2
}

action="${1:-}"
shift || true
prefix=""
binary=""
while (($#)); do
case "$1" in
--prefix=*) prefix="${1#--prefix=}" ;;
--binary=*) binary="${1#--binary=}" ;;
*) usage; exit 2 ;;
esac
shift
done

if [[ "$action" != install && "$action" != uninstall ]] || [[ -z "$prefix" || -z "$binary" ]]; then
usage
exit 2
fi

home="${HOME:-}"
user_mode=0
canonical_dir() {
local path="$1" parent name
[[ "$path" == /* ]] || path="$PWD/$path"
if [[ -d "$path" ]]; then
(cd "$path" && pwd -P)
return
fi
parent="${path%/*}"
name="${path##*/}"
[[ "$parent" == "$path" ]] && parent="."
parent="$(cd "$parent" 2>/dev/null && pwd -P)" || return 1
printf '%s/%s\n' "$parent" "$name"
}

prefix_real="$(canonical_dir "$prefix")" || {
printf 'warning: could not resolve completion prefix %s; skipping\n' "$prefix" >&2
exit 0
}
home_real=""
if [[ -n "$home" ]]; then
home_real="$(canonical_dir "$home")" || home_real=""
fi
if [[ -n "$home_real" && ( "$prefix_real" == "$home_real" || "$prefix_real" == "$home_real"/* ) ]]; then
user_mode=1
fi

if ((user_mode)); then
[[ -n "$home" ]] || { printf 'warning: HOME is unset; skipping completion setup\n' >&2; exit 0; }
bash_path="$home/.local/share/bash-completion/completions/aenv"
zsh_path="$home/.local/share/zsh/site-functions/_aenv"
fish_path="$home/.config/fish/completions/aenv.fish"
else
bash_path="$prefix/share/bash-completion/completions/aenv"
zsh_path="$prefix/share/zsh/site-functions/_aenv"
fish_path="$prefix/share/fish/vendor_completions.d/aenv.fish"
fi

if [[ "$binary" == *"'"* || "$binary" == *$'\n'* || "$binary" == *$'\r'* ]]; then
printf 'warning: completion binary path contains unsupported characters; skipping\n' >&2
exit 0
fi
quoted_binary="'$binary'"

write_loader() {
local path="$1" body="$2" dir tmp
dir="${path%/*}"
mkdir -p "$dir" 2>/dev/null || {
printf 'warning: could not create completion directory %s\n' "$dir" >&2
return 0
}
if [[ -L "$path" ]]; then
printf 'warning: refusing to replace symlink %s\n' "$path" >&2
return 0
fi
if [[ -e "$path" ]] && ! grep -Fqx "$MARKER" "$path" 2>/dev/null; then
printf 'warning: leaving unmanaged completion file %s untouched\n' "$path" >&2
return 0
fi
tmp="$(mktemp "$dir/.aenv-completion.XXXXXX")" || {
printf 'warning: could not stage completion file %s\n' "$path" >&2
return 0
}
if [[ "$path" == "$zsh_path" ]]; then
if ! printf '%s\n%s\n' "$body" "$MARKER" >"$tmp"; then
rm -f "$tmp"
printf 'warning: could not stage completion file %s\n' "$path" >&2
return 0
fi
else
if ! printf '%s\n%s\n' "$MARKER" "$body" >"$tmp"; then
rm -f "$tmp"
printf 'warning: could not stage completion file %s\n' "$path" >&2
return 0
fi
fi
if ! { chmod 0644 "$tmp" && mv -f "$tmp" "$path"; }; then
rm -f "$tmp"
printf 'warning: could not install completion file %s\n' "$path" >&2
fi
}

remove_loader() {
local path="$1"
[[ -e "$path" ]] || return 0
if [[ -L "$path" ]] || ! grep -Fqx "$MARKER" "$path" 2>/dev/null; then
printf 'warning: leaving unmanaged completion file %s untouched\n' "$path" >&2
return 0
fi
rm -f "$path" || printf 'warning: could not remove completion file %s\n' "$path" >&2
}

if [[ "$action" == install ]]; then
write_loader "$bash_path" "if [[ -x $quoted_binary ]]; then source <($quoted_binary completion bash); fi"
zsh_body="#compdef aenv
if [[ -x $quoted_binary ]]; then eval \"\$($quoted_binary completion zsh)\"; fi"
write_loader "$zsh_path" "$zsh_body"
write_loader "$fish_path" "if test -x $quoted_binary; $quoted_binary completion fish | source; end"
if ((user_mode)); then
printf '\nIf Zsh does not find the completion, add this before compinit:\n'
printf " fpath=(~/.local/share/zsh/site-functions \$fpath)\n"
printf ' autoload -Uz compinit\n compinit\n'
fi
else
remove_loader "$bash_path"
remove_loader "$zsh_path"
remove_loader "$fish_path"
fi
52 changes: 52 additions & 0 deletions scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,58 @@ echo "Downloading aenv CLI ..."
download_release_asset "aenv-linux-${ARCH_TAG}" "$tmp_cli"
sudo install -m 0755 "$tmp_cli" "${INSTALL_DIR}/aenv"

install_completion_files() {
local marker='# managed by aenv completion installer'
local binary="${INSTALL_DIR}/aenv" quoted_binary
local bash_path="/usr/local/share/bash-completion/completions/aenv"
local zsh_path="/usr/local/share/zsh/site-functions/_aenv"
local fish_path="/usr/local/share/fish/vendor_completions.d/aenv.fish"
quoted_binary="'$binary'"

put_loader() {
local path="$1" body="$2" dir tmp
dir="${path%/*}"
if [[ -L "$path" ]]; then
echo "warning: refusing to replace symlink ${path}" >&2
return 0
fi
if [[ -e "$path" ]] && ! grep -Fqx "$marker" "$path" 2>/dev/null; then
echo "warning: leaving unmanaged completion file ${path} untouched" >&2
return 0
fi
sudo mkdir -p "$dir" || { echo "warning: could not create ${dir}" >&2; return 0; }
tmp="$(sudo mktemp "$dir/.aenv-completion.XXXXXX")" || {
echo "warning: could not stage ${path}" >&2
return 0
}
if [[ "$path" == "$zsh_path" ]]; then
if ! printf '%s\n%s\n' "$body" "$marker" | sudo tee "$tmp" >/dev/null; then
sudo rm -f "$tmp"
echo "warning: could not stage ${path}" >&2
return 0
fi
else
if ! printf '%s\n%s\n' "$marker" "$body" | sudo tee "$tmp" >/dev/null; then
sudo rm -f "$tmp"
echo "warning: could not stage ${path}" >&2
return 0
fi
fi
if ! sudo chmod 0644 "$tmp" || ! sudo mv -f "$tmp" "$path"; then
sudo rm -f "$tmp"
echo "warning: could not install ${path}" >&2
fi
}

put_loader "$bash_path" "if [[ -x $quoted_binary ]]; then source <($quoted_binary completion bash); fi"
zsh_body="#compdef aenv
if [[ -x $quoted_binary ]]; then eval \"\$($quoted_binary completion zsh)\"; fi"
put_loader "$zsh_path" "$zsh_body"
put_loader "$fish_path" "if test -x $quoted_binary; $quoted_binary completion fish | source; end"
}

install_completion_files

# ---------------------------------------------------------------------------
# 2. Install the server
# ---------------------------------------------------------------------------
Expand Down
Loading