Files
StianNOR-ZSHkit/portainerup.sh
2026-03-25 15:19:34 +01:00

351 lines
14 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# =============================================================================
# 🐳 StianNOR — PORTAINER INSTALLER v2.1
# =============================================================================
set -euo pipefail
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
BLUE='\033[0;34m'; CYAN='\033[0;36m'; BOLD='\033[1m'; RESET='\033[0m'
LINE="────────────────────────────────────────────────────────────────────────────"
info() { echo -e "${CYAN} $*${RESET}"; }
success() { echo -e "${GREEN}$*${RESET}"; }
warn() { echo -e "${YELLOW}$*${RESET}"; }
error() { echo -e "${RED}$*${RESET}"; }
step() { echo -e "\n${BOLD}${BLUE}$*${RESET}"; echo -e "${BLUE}${LINE}${RESET}"; }
die() { error "$*"; exit 1; }
FORCE_UPDATE=false
[[ "${1:-}" == "--update" ]] && FORCE_UPDATE=true
# =============================================================================
# SUDO — cache + keepalive
# =============================================================================
step "Checking sudo access"
sudo -v || die "sudo required"
( while true; do sudo -n true; sleep 55; done ) &
KEEPALIVE_PID=$!
trap 'kill "$KEEPALIVE_PID" 2>/dev/null || true' EXIT INT TERM
# =============================================================================
# DETECT DISTRO
# =============================================================================
step "Detecting Linux distribution"
[[ ! -f /etc/os-release ]] && die "Cannot detect distro — /etc/os-release missing"
source /etc/os-release
DISTRO_ID="${ID,,}"
DISTRO_CODENAME="${VERSION_CODENAME:-}"
[[ -z "$DISTRO_CODENAME" ]] && DISTRO_CODENAME=$(lsb_release -cs 2>/dev/null || echo "")
[[ -z "$DISTRO_CODENAME" && "$DISTRO_ID" == "ubuntu" ]] && DISTRO_CODENAME="jammy"
[[ -z "$DISTRO_CODENAME" && "$DISTRO_ID" == "debian" ]] && DISTRO_CODENAME="bookworm"
# Map to distro family
DISTRO_FAMILY="$DISTRO_ID"
case "$DISTRO_ID" in
ubuntu|debian|linuxmint|raspbian|pop) DISTRO_FAMILY="debian" ;;
fedora) DISTRO_FAMILY="fedora" ;;
centos|rhel|rocky|almalinux|ol) DISTRO_FAMILY="rhel" ;;
arch|manjaro|endeavouros|garuda|artix|arcolinux) DISTRO_FAMILY="arch" ;;
opensuse*|suse|sles) DISTRO_FAMILY="suse" ;;
alpine) DISTRO_FAMILY="alpine" ;;
*)
# Try ID_LIKE fallback
case "${ID_LIKE:-}" in
*debian*|*ubuntu*) DISTRO_FAMILY="debian" ;;
*rhel*|*fedora*) DISTRO_FAMILY="fedora" ;;
*arch*) DISTRO_FAMILY="arch" ;;
*suse*) DISTRO_FAMILY="suse" ;;
esac ;;
esac
success "Detected: ${NAME} (family: ${DISTRO_FAMILY})"
# =============================================================================
# INSTALL DOCKER
# =============================================================================
install_docker() {
step "Installing Docker CE"
case "$DISTRO_FAMILY" in
debian)
sudo apt-get update -qq
sudo apt-get install -y -qq ca-certificates curl gnupg lsb-release
sudo install -m0755 -d /etc/apt/keyrings
curl -fsSL "https://download.docker.com/linux/${DISTRO_ID}/gpg" \
| sudo gpg --dearmor --yes -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/${DISTRO_ID} ${DISTRO_CODENAME} stable" \
| sudo tee /etc/apt/sources.list.d/docker.list >/dev/null
sudo apt-get update -qq
sudo apt-get install -y \
docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
;;
fedora)
sudo dnf -y remove podman-docker 2>/dev/null || true
sudo dnf -y install dnf-plugins-core
sudo dnf config-manager \
--add-repo https://download.docker.com/linux/fedora/docker-ce.repo
sudo dnf -y install \
docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
;;
rhel)
sudo yum install -y yum-utils
sudo yum-config-manager \
--add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo yum install -y \
docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
;;
arch)
sudo pacman -Sy --noconfirm --needed docker docker-compose
;;
suse)
sudo zypper install -y docker docker-compose
;;
alpine)
sudo apk add --update docker docker-compose
sudo rc-update add docker boot
sudo service docker start
success "Docker installed (Alpine)"
return
;;
*)
die "Unsupported distro: $DISTRO_ID — see https://docs.docker.com/engine/install/"
;;
esac
sudo systemctl enable --now docker
success "Docker installed and started"
}
# =============================================================================
# FIX DOCKER SOCKET PERMISSIONS
# The real fix for "permission denied on /var/run/docker.sock":
# 1. Add user to docker group
# 2. Fix socket group ownership so the group can actually access it
# 3. Tell the user to run `newgrp docker` in their shell — we cannot
# push group changes to the parent shell process, that's a kernel limit.
# =============================================================================
fix_docker_permissions() {
step "Fixing Docker permissions for $USER"
# Ensure docker group exists
getent group docker >/dev/null 2>&1 || sudo groupadd docker
# Add user to docker group
if ! groups "$USER" | grep -qw docker; then
sudo usermod -aG docker "$USER"
success "Added $USER to docker group"
else
success "$USER already in docker group"
fi
# Fix socket group ownership and permissions
# This is what actually makes docker work without sudo RIGHT NOW
if [[ -S /var/run/docker.sock ]]; then
sudo chown root:docker /var/run/docker.sock
sudo chmod 660 /var/run/docker.sock
success "Fixed /var/run/docker.sock permissions (root:docker 660)"
fi
# Fix ~/.docker ownership
sudo mkdir -p "$HOME/.docker"
sudo chown -R "$USER:$USER" "$HOME/.docker"
success "Fixed ~/.docker ownership"
# Make socket permissions persist across reboots via systemd override
OVERRIDE_DIR="/etc/systemd/system/docker.socket.d"
if [[ -d /etc/systemd/system ]] && ! [[ -f "$OVERRIDE_DIR/permissions.conf" ]]; then
sudo mkdir -p "$OVERRIDE_DIR"
sudo tee "$OVERRIDE_DIR/permissions.conf" >/dev/null <<'OVERRIDE'
[Socket]
SocketMode=0660
SocketGroup=docker
OVERRIDE
sudo systemctl daemon-reload
sudo systemctl restart docker.socket docker 2>/dev/null || true
success "Persistent socket permission override installed"
fi
}
# =============================================================================
# ACTIVATE GROUP IN CURRENT SESSION
# sg/newgrp cannot push groups back to the parent shell.
# Best we can do: detect if THIS script process already has docker group,
# and if not, use `sg` to run the rest of the script with the group active.
# =============================================================================
activate_docker_group() {
# Check if docker group is already active in this process
if id -nG "$USER" 2>/dev/null | grep -qw docker && \
[[ "$(stat -c '%G' /var/run/docker.sock 2>/dev/null)" == "docker" ]]; then
# Try running docker without sudo
if docker info >/dev/null 2>&1; then
success "Docker accessible without sudo ✓"
DOCKER_CMD="docker"
return 0
fi
fi
# Socket fix applied above — try again with newgrp trick
# We use `sg docker` to get the group active for the REST of this script
if [[ "${_DOCKER_GROUP_EXEC:-}" != "1" ]]; then
export _DOCKER_GROUP_EXEC=1
info "Activating docker group for this session via sg..."
exec sg docker -c "export _DOCKER_GROUP_EXEC=1; bash '$0' ${1:-}"
fi
# If still failing, use sudo docker for the rest of the script
if sudo docker info >/dev/null 2>&1; then
warn "Using sudo docker for this session."
warn "To use docker without sudo in YOUR terminal, run:"
warn " newgrp docker"
warn "Or log out and back in."
DOCKER_CMD="sudo docker"
else
die "Docker daemon is not accessible even with sudo. Check: sudo systemctl status docker"
fi
}
# =============================================================================
# MAIN — INSTALL IF NEEDED
# =============================================================================
if ! command -v docker >/dev/null 2>&1; then
install_docker
else
success "Docker already installed: $(docker --version)"
fi
fix_docker_permissions
activate_docker_group
# =============================================================================
# DOCKER COMPOSE CHECK
# =============================================================================
step "Checking Docker Compose"
if $DOCKER_CMD compose version >/dev/null 2>&1; then
success "Docker Compose plugin: $($DOCKER_CMD compose version --short 2>/dev/null || echo installed)"
elif command -v docker-compose >/dev/null 2>&1; then
success "Docker Compose standalone: $(docker-compose --version)"
else
info "Installing Docker Compose standalone..."
sudo curl -fsSL \
"https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" \
-o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
success "Docker Compose installed: $(/usr/local/bin/docker-compose --version)"
fi
# =============================================================================
# ENSURE DOCKER IS RUNNING
# =============================================================================
step "Ensuring Docker service is running"
if [[ "$DISTRO_FAMILY" == "alpine" ]]; then
sudo service docker status 2>/dev/null | grep -q started \
|| { sudo service docker start; sleep 3; }
else
if ! sudo systemctl is-active --quiet docker; then
sudo systemctl start docker
sleep 3
fi
fi
success "Docker service running"
# =============================================================================
# PORTAINER
# =============================================================================
step "Installing Portainer CE"
PORTAINER_EXISTS=false
$DOCKER_CMD ps -a --format '{{.Names}}' 2>/dev/null | grep -q "^portainer$" \
&& PORTAINER_EXISTS=true
if [[ "$PORTAINER_EXISTS" == true ]]; then
if [[ "$FORCE_UPDATE" == true ]]; then
info "Removing existing Portainer for update..."
$DOCKER_CMD stop portainer 2>/dev/null || true
$DOCKER_CMD rm portainer 2>/dev/null || true
else
PORTAINER_STATUS=$($DOCKER_CMD inspect --format '{{.State.Status}}' portainer 2>/dev/null || echo "unknown")
success "Portainer already installed (status: $PORTAINER_STATUS)"
info "Run with --update to force reinstall"
IP=$(hostname -I 2>/dev/null | awk '{print $1}')
[[ -z "$IP" ]] && IP=$(ip route get 1.1.1.1 2>/dev/null | awk '{print $7; exit}')
[[ -z "$IP" ]] && IP="localhost"
echo -e "\n${LINE}"
echo -e " ${BOLD}${CYAN}🌐 Portainer:${RESET} ${GREEN}https://${IP}:9443${RESET}"
echo -e "${LINE}"
exit 0
fi
fi
info "Pulling latest Portainer CE image..."
$DOCKER_CMD pull portainer/portainer-ce:latest
# SELinux detection
SELINUX_FLAG=""
if command -v getenforce >/dev/null 2>&1 && [[ "$(getenforce 2>/dev/null)" == "Enforcing" ]]; then
SELINUX_FLAG="--security-opt label=disable"
info "SELinux enforcing — adding label=disable"
fi
info "Starting Portainer container..."
$DOCKER_CMD run -d \
$SELINUX_FLAG \
-p 8000:8000 \
-p 9443:9443 \
--name portainer \
--restart=always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v portainer_data:/data \
portainer/portainer-ce:latest
success "Portainer container started"
# =============================================================================
# DONE
# =============================================================================
IP=$(hostname -I 2>/dev/null | awk '{print $1}')
[[ -z "$IP" ]] && IP=$(ip route get 1.1.1.1 2>/dev/null | awk '{print $7; exit}')
[[ -z "$IP" ]] && IP="localhost"
# Check if docker still needs newgrp in the user's shell
NEEDS_NEWGRP=false
if ! docker info >/dev/null 2>&1; then
NEEDS_NEWGRP=true
fi
echo ""
echo -e "${LINE}"
echo -e "${BOLD}${GREEN} ✅ Portainer CE installed successfully!${RESET}"
echo -e "${LINE}"
echo -e " ${CYAN}Dashboard:${RESET} ${GREEN}https://${IP}:9443${RESET}"
echo -e " ${CYAN}Tunnel:${RESET} ${BLUE}http://${IP}:8000${RESET}"
echo -e " ${YELLOW}Note:${RESET} Accept the self-signed certificate on first visit."
echo -e " ${YELLOW}Note:${RESET} Create your admin account within 5 minutes of starting."
echo -e "${LINE}"
if [[ "$NEEDS_NEWGRP" == true ]]; then
echo ""
echo -e "${LINE}"
echo -e " ${BOLD}${YELLOW}⚠ ONE MORE STEP — activate docker group in your shell:${RESET}"
echo -e "${LINE}"
echo -e " Docker is installed and working, but your CURRENT terminal"
echo -e " session doesn't have the docker group active yet."
echo -e ""
echo -e " Run this in your terminal now:"
echo -e " ${BOLD}${CYAN} newgrp docker${RESET}"
echo -e ""
echo -e " Or simply log out and back in."
echo -e " After that, ${CYAN}docker ps${RESET} will work without sudo."
echo -e "${LINE}"
fi
echo ""