diff --git a/README.md b/README.md index 3c75f82..51a8eb3 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ The script guides you through a simplified menu to perform the following: | Action | Description | | :--- | :--- | -| **Install Docker Engine** | Downloads the official installer to a temp file, then runs it (never `curl \| sh`) | +| **Install Docker Engine** | On Alpine, installs via `apk` from the community repo; elsewhere downloads the official installer to a temp file and runs it (never `curl \| sh`) | | **User Configuration** | Creates a new user specifically for Docker or adds your current user | | **Security Groups** | Adds the selected user to the `docker` group for non-root command execution | | **Portainer Deployment** | Pulls and runs Portainer CE pinned to a verified image digest from the LTS release channel on port 9443 | diff --git a/install.sh b/install.sh index 6d4f6a5..3dd0f78 100644 --- a/install.sh +++ b/install.sh @@ -438,6 +438,132 @@ execute_script() { fi } +# detect_distro_id returns the OS ID from /etc/os-release (e.g. alpine, ubuntu). +detect_distro_id() { + if [[ -f /etc/os-release ]]; then + awk -F= '/^ID=/{gsub(/"/, "", $2); print $2; exit}' /etc/os-release + else + printf '%s\n' "unknown" + fi +} + +# enable_docker_service enables and starts the Docker daemon via systemd or OpenRC. +# Returns nonzero if enable/start fails or neither init system is available. +enable_docker_service() { + local SUDO="${1:-}" + + if command -v systemctl >/dev/null 2>&1; then + if ! $SUDO systemctl enable docker; then + print_error "Failed to enable Docker service." + return 1 + fi + if ! $SUDO systemctl start docker; then + print_error "Failed to start Docker service." + return 1 + fi + print_success "Docker service enabled and started." + return 0 + fi + + if command -v rc-update >/dev/null 2>&1; then + if ! $SUDO rc-update add docker default; then + print_error "Failed to enable Docker service." + return 1 + fi + if command -v rc-service >/dev/null 2>&1; then + if ! $SUDO rc-service docker start; then + print_error "Failed to start Docker service." + return 1 + fi + elif command -v service >/dev/null 2>&1; then + if ! $SUDO service docker start; then + print_error "Failed to start Docker service." + return 1 + fi + else + print_error "No OpenRC service command found to start Docker." + return 1 + fi + print_success "Docker service enabled and started." + return 0 + fi + + print_error "Could not detect systemd or OpenRC; start Docker manually." + return 1 +} + +# install_docker_alpine installs Docker Engine and Compose from Alpine community packages. +install_docker_alpine() { + local SUDO="${1:-}" + local alpine_version repo_url exit_code + + print_status "Alpine detected — using apk (get.docker.com does not support Alpine)." + print_line "-" "$BLUE" + + if [[ ! -f /etc/alpine-release ]]; then + print_error "Alpine release file not found." + return 1 + fi + + alpine_version=$(cut -d'.' -f1,2 /etc/alpine-release) + repo_url="https://dl-cdn.alpinelinux.org/alpine/v${alpine_version}/community" + + if ! grep -qE '/alpine/(v[^/]+|edge)/community' /etc/apk/repositories 2>/dev/null; then + print_status "Enabling Alpine community repository..." + echo "$repo_url" | $SUDO tee -a /etc/apk/repositories >/dev/null + fi + + print_status "Installing Docker & Compose via apk..." + exit_code=0 + $SUDO apk update || exit_code=$? + if [[ $exit_code -eq 0 ]]; then + $SUDO apk add docker docker-cli-compose || exit_code=$? + fi + + if [[ $exit_code -ne 0 ]]; then + print_error "Docker installation failed." + return "$exit_code" + fi + + print_success "Docker installed successfully!" + if ! enable_docker_service "$SUDO"; then + return 1 + fi + return 0 +} + +# install_docker_official downloads get.docker.com to a temp file, then executes that file. +install_docker_official() { + local SUDO="${1:-}" + local installer_path exit_code download_status + + print_status "Using official Docker installation script..." + print_line "-" "$BLUE" + + installer_path=$(mktemp "${TMPDIR:-/tmp}/get-docker.XXXXXXXX.sh") + if curl -fsSL https://get.docker.com -o "$installer_path"; then + exit_code=0 + $SUDO sh "$installer_path" || exit_code=$? + rm -f "$installer_path" + + if [[ $exit_code -eq 0 ]]; then + print_success "Docker installed successfully!" + if ! enable_docker_service "$SUDO"; then + return 1 + fi + return 0 + fi + + print_error "Docker installation failed." + return "$exit_code" + fi + + download_status=$? + rm -f "$installer_path" + print_error "Failed to download Docker installation script." + return "$download_status" +} + install_docker() { echo "" print_status "Installing Docker Engine..." @@ -479,37 +605,19 @@ install_docker() { SUDO="sudo" fi - print_status "Using official Docker installation script..." - print_line "-" "$BLUE" + local distro_id exit_code + distro_id=$(detect_distro_id) + exit_code=0 - # Download the installer to a temp file, then execute that file. - local installer_path exit_code download_status - installer_path=$(mktemp "${TMPDIR:-/tmp}/get-docker.XXXXXXXX.sh") - if curl -fsSL https://get.docker.com -o "$installer_path"; then - exit_code=0 - $SUDO sh "$installer_path" || exit_code=$? - rm -f "$installer_path" - - if [[ $exit_code -eq 0 ]]; then - print_success "Docker installed successfully!" - - # Enable and start Docker - if command -v systemctl >/dev/null 2>&1; then - $SUDO systemctl enable docker 2>/dev/null || true - $SUDO systemctl start docker 2>/dev/null || true - print_success "Docker service enabled and started." - fi - else - print_error "Docker installation failed." - pause - return "$exit_code" - fi + if [[ "$distro_id" == "alpine" ]]; then + install_docker_alpine "$SUDO" || exit_code=$? else - download_status=$? - rm -f "$installer_path" - print_error "Failed to download Docker installation script." + install_docker_official "$SUDO" || exit_code=$? + fi + + if [[ $exit_code -ne 0 ]]; then pause - return "$download_status" + return "$exit_code" fi pause @@ -555,14 +663,28 @@ add_user_to_docker_group() { local SUDO="" fi + local distro_id + distro_id=$(detect_distro_id) + # Ensure docker group exists if ! getent group docker >/dev/null 2>&1; then print_status "Creating docker group..." - $SUDO groupadd docker + if [[ "$distro_id" == "alpine" ]]; then + $SUDO addgroup docker + else + $SUDO groupadd docker + fi + fi + + # Add user to group (Alpine uses addgroup; others use usermod) + local group_ok=0 + if [[ "$distro_id" == "alpine" ]]; then + $SUDO addgroup "$current_user" docker && group_ok=1 + else + $SUDO usermod -aG docker "$current_user" && group_ok=1 fi - # Add user to group - if $SUDO usermod -aG docker "$current_user"; then + if [[ $group_ok -eq 1 ]]; then print_success "User '$current_user' added to docker group." echo "" print_warn "You must log out and back in for this to take effect." diff --git a/tests/security-checks.sh b/tests/security-checks.sh index 97d38a3..fb9ff95 100755 --- a/tests/security-checks.sh +++ b/tests/security-checks.sh @@ -57,6 +57,22 @@ if ! grep -Eq '^VERSION="2\.[0-9]+\.[0-9]+"' install.sh; then exit 1 fi +if ! grep -q 'apk add docker docker-cli-compose' install.sh; then + echo "install.sh Alpine path must install docker and docker-cli-compose via apk." >&2 + exit 1 +fi + +# Alpine / non-Alpine branches must call the matching installer (not merely define it). +if ! rg -U -q '\[\[ "\$distro_id" == "alpine" \]\]; then[[:space:]]+install_docker_alpine "\$SUDO"' install.sh; then + echo 'Alpine distro_id branch must call install_docker_alpine "$SUDO".' >&2 + exit 1 +fi + +if ! rg -U --multiline-dotall -q '\[\[ "\$distro_id" == "alpine" \]\]; then[[:space:]]+install_docker_alpine "\$SUDO".*else[[:space:]]+install_docker_official "\$SUDO"' install.sh; then + echo 'Non-Alpine distro_id branch must call install_docker_official "$SUDO".' >&2 + exit 1 +fi + if rg -n 'git clean[[:space:]]+-[^[:space:]]*f' install.sh; then echo "Broad destructive git clean is forbidden." >&2 exit 1