Skip to content

Commit 9bc2418

Browse files
committed
feat(install): prefer Snap on Linux, add preflight checks and tests
Add snap install support: resolve_linux_install_method auto-selects snap when snapd is available, with OPENSHELL_INSTALL_METHOD=classic fallback. Add preflight checks for Docker conflicts and interface connections. Update test suite to cover has_snapd, resolve_snap_channel, and resolve_linux_install_method functions. Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
1 parent f061b1d commit 9bc2418

2 files changed

Lines changed: 221 additions & 14 deletions

File tree

‎install.sh‎

Lines changed: 143 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
#
55
# Install OpenShell from a GitHub release.
66
#
7-
# Linux installs either the Debian or RPM packages from the selected release.
8-
# Apple Silicon macOS installs the generated Homebrew formula, so Homebrew owns
9-
# the binary layout and launchd service lifecycle.
7+
# Linux prefers the Snap path when snapd is available. Otherwise Linux installs
8+
# the Debian or RPM packages from the selected release. Apple Silicon macOS
9+
# installs the generated Homebrew formula, so Homebrew owns the binary layout
10+
# and launchd service lifecycle.
1011
#
1112
set -e
1213

@@ -21,6 +22,12 @@ HOMEBREW_FORMULA_NAME="openshell"
2122
BREAKING_RELEASE_VERSION="0.0.37"
2223
LINUX_PACKAGE_GLIBC_MIN_VERSION="2.31"
2324
UPGRADE_NOTICE_ACK="${OPENSHELL_ACK_BREAKING_UPGRADE:-}"
25+
SNAP_PACKAGE_NAME="openshell"
26+
SNAP_DOCKER_SLOT="docker:docker-daemon"
27+
SNAP_DEFAULT_CHANNEL="latest/stable"
28+
SNAP_DEV_CHANNEL="latest/edge"
29+
LINUX_INSTALL_METHOD_SNAP="snap"
30+
LINUX_INSTALL_METHOD_CLASSIC="classic"
2431

2532
info() {
2633
printf '%s: %s\n' "$APP_NAME" "$*" >&2
@@ -54,13 +61,27 @@ ENVIRONMENT VARIABLES:
5461
OPENSHELL_ACK_BREAKING_UPGRADE
5562
Set to 1 only after backing up and cleaning up a
5663
pre-v0.0.37 installation.
64+
OPENSHELL_INSTALL_METHOD
65+
Linux only. Selects snap or classic. Accepted values:
66+
snap install from the Snap Store
67+
classic install via dpkg or rpm
68+
When unset, snap is used when snapd is available and
69+
running, otherwise the classic package manager is used.
70+
Set OPENSHELL_INSTALL_METHOD=classic on distros where
71+
snapd is not desired.
5772
5873
NOTES:
5974
When OPENSHELL_VERSION is unset, this resolves the latest tagged release
6075
from ${GITHUB_URL}/releases/latest.
6176
62-
Linux installs the Debian package on amd64/arm64 or the RPM packages on
63-
x86_64/aarch64, depending on the host package manager.
77+
Linux prefers the snap method when snapd is available. The Snap channel
78+
follows OPENSHELL_VERSION: 'dev' selects ${SNAP_DEV_CHANNEL:-latest/edge},
79+
tagged releases select ${SNAP_DEFAULT_CHANNEL:-latest/stable}. snapd
80+
manages the gateway as the 'openshell.gateway' service.
81+
82+
On systems without snapd, Linux installs the Debian package on
83+
amd64/arm64 or the RPM packages on x86_64/aarch64, depending on the
84+
host package manager.
6485
macOS installs the release Homebrew formula on Apple Silicon and starts a
6586
brew services-backed local gateway.
6687
EOF
@@ -477,6 +498,69 @@ linux_package_method() {
477498
fi
478499
}
479500

501+
has_snapd() {
502+
if [ "${OPENSHELL_INSTALL_SH_TEST:-0}" = "1" ]; then
503+
case "${OPENSHELL_TEST_SNAPD_AVAILABLE:-0}" in
504+
1) return 0 ;;
505+
0) return 1 ;;
506+
esac
507+
fi
508+
509+
command -v snap >/dev/null 2>&1 || return 1
510+
[ -S /run/snapd.socket ] || [ -S /var/run/snapd.socket ] || return 1
511+
return 0
512+
}
513+
514+
host_supports_native_package() {
515+
has_cmd dpkg || has_cmd rpm
516+
}
517+
518+
check_docker_not_installed_natively() {
519+
if ! command -v docker >/dev/null 2>&1; then
520+
return 0
521+
fi
522+
523+
_docker_path="$(command -v docker)"
524+
case "$_docker_path" in
525+
/snap/bin/docker | /var/lib/snapd/snap/bin/docker)
526+
return 0
527+
;;
528+
esac
529+
530+
error "Docker is installed via a non-snap package. The Docker snap cannot be installed alongside a native Docker installation. Please uninstall the native Docker package and try again, or install OpenShell via dpkg/rpm instead of snap."
531+
}
532+
533+
resolve_linux_install_method() {
534+
_method="${OPENSHELL_INSTALL_METHOD:-}"
535+
if [ -n "$_method" ]; then
536+
case "$_method" in
537+
"$LINUX_INSTALL_METHOD_SNAP" | "$LINUX_INSTALL_METHOD_CLASSIC")
538+
echo "$_method"
539+
return 0
540+
;;
541+
*)
542+
error "OPENSHELL_INSTALL_METHOD must be '${LINUX_INSTALL_METHOD_SNAP}' or '${LINUX_INSTALL_METHOD_CLASSIC}' (got: ${_method})"
543+
;;
544+
esac
545+
fi
546+
547+
if has_snapd; then
548+
echo "$LINUX_INSTALL_METHOD_SNAP"
549+
elif host_supports_native_package; then
550+
echo "$LINUX_INSTALL_METHOD_CLASSIC"
551+
else
552+
error "OpenShell Linux installs require either snapd or dpkg/rpm"
553+
fi
554+
}
555+
556+
resolve_snap_channel() {
557+
if [ "${RELEASE_TAG:-}" = "dev" ]; then
558+
echo "$SNAP_DEV_CHANNEL"
559+
else
560+
echo "$SNAP_DEFAULT_CHANNEL"
561+
fi
562+
}
563+
480564
set_linux_target_runtime_dir() {
481565
if [ "$(id -u)" -eq "$TARGET_UID" ] && [ -n "${XDG_RUNTIME_DIR:-}" ]; then
482566
TARGET_RUNTIME_DIR="$XDG_RUNTIME_DIR"
@@ -866,6 +950,44 @@ print_gateway_add_output() {
866950
done
867951
}
868952

953+
install_linux_snap() {
954+
_channel="$(resolve_snap_channel)"
955+
956+
info "installing ${APP_NAME} from the Snap Store (channel: ${_channel})..."
957+
as_root snap install "$SNAP_PACKAGE_NAME" --channel="$_channel"
958+
959+
# TODO: Remove this check once snapd 2.76 is released, which lifts the
960+
# restriction preventing snap packages from being installed alongside a
961+
# native Docker installation. At that point the docker snap can be
962+
# pre-installed unconditionally.
963+
check_docker_not_installed_natively
964+
965+
info "pre-installing the Docker snap..."
966+
as_root snap install docker
967+
968+
info "connecting required interfaces..."
969+
as_root snap connect "${SNAP_PACKAGE_NAME}:docker" "$SNAP_DOCKER_SLOT" || \
970+
warn "could not connect ${SNAP_PACKAGE_NAME}:docker to ${SNAP_DOCKER_SLOT}; the Docker driver will not work until this is fixed"
971+
for _plug in log-observe system-observe ssh-keys; do
972+
as_root snap connect "${SNAP_PACKAGE_NAME}:${_plug}" || \
973+
warn "could not connect ${SNAP_PACKAGE_NAME}:${_plug}; some features may be limited"
974+
done
975+
976+
info "installed ${APP_NAME} from the Snap Store (${_channel})"
977+
info "snapd manages the OpenShell gateway as the 'openshell.gateway' service."
978+
info ""
979+
info "Next steps:"
980+
info " snap services openshell"
981+
info " openshell status"
982+
info " openshell gateway add https://127.0.0.1:17670 --local --name openshell"
983+
if ! command -v snap >/dev/null 2>&1; then
984+
info ""
985+
info "note: the 'openshell' binary is provided by the snap."
986+
info "Make sure snap is in your PATH, or invoke openshell with the"
987+
info "absolute path, before running the next-step commands."
988+
fi
989+
}
990+
869991
install_linux_deb() {
870992
check_linux_deb_platform
871993
set_linux_target_runtime_dir
@@ -1033,16 +1155,23 @@ main() {
10331155

10341156
case "$PLATFORM" in
10351157
linux)
1036-
require_linux_package_glibc
1037-
case "$(linux_package_method)" in
1038-
deb)
1039-
install_linux_deb
1040-
;;
1041-
rpm)
1042-
install_linux_rpm
1158+
case "$(resolve_linux_install_method)" in
1159+
"$LINUX_INSTALL_METHOD_SNAP")
1160+
install_linux_snap
10431161
;;
1044-
*)
1045-
error "unsupported Linux package method"
1162+
"$LINUX_INSTALL_METHOD_CLASSIC")
1163+
require_linux_package_glibc
1164+
case "$(linux_package_method)" in
1165+
deb)
1166+
install_linux_deb
1167+
;;
1168+
rpm)
1169+
install_linux_rpm
1170+
;;
1171+
*)
1172+
error "unsupported Linux package method"
1173+
;;
1174+
esac
10461175
;;
10471176
esac
10481177
;;

‎tasks/scripts/test-install-sh.sh‎

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,82 @@ assert_glibc_preflight_fails \
9999
"OpenShell Linux packages require glibc >= 2.31; detected musl or unsupported libc." \
100100
setup_ldd_musl
101101

102+
assert_eq() {
103+
local name=$1
104+
local expected=$2
105+
local actual=$3
106+
if [ "$expected" != "$actual" ]; then
107+
echo "FAIL: ${name}: expected '${expected}', got '${actual}'" >&2
108+
exit 1
109+
fi
110+
}
111+
112+
# has_snapd: with the test-mode override, OPENSHELL_TEST_SNAPD_AVAILABLE selects
113+
# the result. In real environments the function probes for `snap` and the snapd
114+
# socket, which the test cannot exercise without root or a real snapd.
115+
(export OPENSHELL_TEST_SNAPD_AVAILABLE=1; has_snapd) || {
116+
echo "FAIL: has_snapd with OPENSHELL_TEST_SNAPD_AVAILABLE=1" >&2
117+
exit 1
118+
}
119+
(export OPENSHELL_TEST_SNAPD_AVAILABLE=0; has_snapd) && {
120+
echo "FAIL: has_snapd with OPENSHELL_TEST_SNAPD_AVAILABLE=0 should fail" >&2
121+
exit 1
122+
}
123+
124+
# resolve_snap_channel
125+
export RELEASE_TAG=""
126+
assert_eq "resolve_snap_channel default" "latest/stable" "$(resolve_snap_channel)"
127+
export RELEASE_TAG="dev"
128+
assert_eq "resolve_snap_channel dev" "latest/edge" "$(resolve_snap_channel)"
129+
export RELEASE_TAG="v0.0.37"
130+
assert_eq "resolve_snap_channel tagged" "latest/stable" "$(resolve_snap_channel)"
131+
export RELEASE_TAG=""
132+
unset RELEASE_TAG
133+
134+
# resolve_linux_install_method: explicit env var wins over the probe
135+
export OPENSHELL_TEST_SNAPD_AVAILABLE=1
136+
export OPENSHELL_INSTALL_METHOD=snap
137+
assert_eq "resolve_linux_install_method snap explicit" "snap" "$(resolve_linux_install_method)"
138+
export OPENSHELL_INSTALL_METHOD=classic
139+
assert_eq "resolve_linux_install_method classic explicit overrides snap probe" "classic" "$(resolve_linux_install_method)"
140+
export OPENSHELL_TEST_SNAPD_AVAILABLE=0
141+
export OPENSHELL_INSTALL_METHOD=classic
142+
assert_eq "resolve_linux_install_method classic explicit" "classic" "$(resolve_linux_install_method)"
143+
unset OPENSHELL_INSTALL_METHOD
144+
145+
# resolve_linux_install_method: snap when available
146+
export OPENSHELL_TEST_SNAPD_AVAILABLE=1
147+
unset OPENSHELL_INSTALL_METHOD
148+
assert_eq "resolve_linux_install_method snap auto" "snap" "$(resolve_linux_install_method)"
149+
150+
# resolve_linux_install_method: invalid env var exits non-zero
151+
export OPENSHELL_INSTALL_METHOD=invalid
152+
_snap_err="${tmpdir}/snap-err"
153+
if (resolve_linux_install_method) >/dev/null 2>"$_snap_err"; then
154+
echo "FAIL: resolve_linux_install_method should fail on invalid value" >&2
155+
exit 1
156+
fi
157+
if ! grep -Fq "OPENSHELL_INSTALL_METHOD must be" "$_snap_err"; then
158+
echo "FAIL: resolve_linux_install_method: missing expected error message" >&2
159+
cat "$_snap_err" >&2 || true
160+
exit 1
161+
fi
162+
unset OPENSHELL_INSTALL_METHOD
163+
164+
# resolve_linux_install_method: snap absent + clean PATH (no dpkg/rpm) exits non-zero
165+
export OPENSHELL_TEST_SNAPD_AVAILABLE=0
166+
_real_path="$PATH"
167+
_clean_path="${tmpdir}/clean-path"
168+
mkdir -p "$_clean_path"
169+
PATH="$_clean_path"
170+
unset OPENSHELL_INSTALL_METHOD
171+
if (resolve_linux_install_method) >/dev/null 2>"$_snap_err"; then
172+
PATH="$_real_path"
173+
echo "FAIL: resolve_linux_install_method should fail without snap or native" >&2
174+
exit 1
175+
fi
176+
PATH="$_real_path"
177+
export OPENSHELL_TEST_SNAPD_AVAILABLE=1
178+
unset OPENSHELL_INSTALL_METHOD
179+
102180
echo "install.sh libc preflight tests passed"

0 commit comments

Comments
 (0)