#!/bin/bash # SPDX-License-Identifier: AGPL-3.0-or-later # # quickstart.sh — k8shell installation helper # # Installs k8shell into a Kubernetes cluster using Helm. Handles prerequisite # checks, cryptographic key generation, namespace setup, and prints connection # instructions on success. # # Version : 1.0.0 # Authors : k8shell Authors # License : GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later) # https://www.gnu.org/licenses/agpl-3.0.html # Source : https://github.com/k8shell-io/charts # Copyright (c) 2026 k8shell Authors. All rights reserved. set -euo pipefail # --------------------------------------------------------------------------- # k8shell quickstart # --------------------------------------------------------------------------- SCRIPT_VERSION="1.0.0" CHART_VERSION="" NAMESPACE="k8shell-system" TARGET_NAMESPACE="k8shell-workspaces" QUICKSTART_DIR="$HOME/k8shell-quickstart" GENERATED_SSH_KEY="$QUICKSTART_DIR/admin-ssh-key" NODE_PORT_ENABLED="true" NODE_PORT="30022" # --------------------------------------------------------------------------- # Usage # --------------------------------------------------------------------------- usage() { cat <&2; } warn() { echo "[WRN] $*" >&2; } error() { echo "[ERR] $*" >&2; exit 1; } check_prereqs() { info "Checking prerequisites..." local missing=() for cmd in helm openssl ssh-keygen kubectl; do if ! command -v "$cmd" &>/dev/null; then missing+=("$cmd") fi done if [ ${#missing[@]} -gt 0 ]; then error "Missing required tools: ${missing[*]}" fi # Helm version >= 3 local helm_major helm_major=$(helm version --short 2>/dev/null | grep -oP 'v\K[0-9]+' | head -1) if [ "${helm_major:-0}" -lt 3 ]; then error "Helm v3 or later is required (found: $(helm version --short 2>/dev/null))" fi # kubectl can reach a cluster if ! kubectl cluster-info &>/dev/null; then error "kubectl cannot reach a Kubernetes cluster. Check your kubeconfig." fi info "All prerequisites met." } describe_ec_key() { local file="$1" if [ -f "$file" ]; then echo "$file (exists)" else echo "$file (will be generated)" fi } ensure_ec_key() { local file="$1" if [ ! -f "$file" ]; then info "Generating EC key: $file" openssl ecparam -name prime256v1 -genkey -noout -out "$file" fi cat "$file" } resolve_admin_public_key() { # 1. Prefer ~/.ssh/id_rsa.pub if it exists local default_pub="$HOME/.ssh/id_rsa.pub" if [ -f "$default_pub" ]; then info "Using existing SSH public key: $default_pub" cat "$default_pub" return fi # 2. Also check common Ed25519 / ECDSA defaults for candidate in "$HOME/.ssh/id_ed25519.pub" "$HOME/.ssh/id_ecdsa.pub"; do if [ -f "$candidate" ]; then info "Using existing SSH public key: $candidate" cat "$candidate" return fi done # 3. Key will be generated after user approval — not yet return 1 } detect_admin_key_source() { for candidate in "$HOME/.ssh/id_rsa.pub" "$HOME/.ssh/id_ed25519.pub" "$HOME/.ssh/id_ecdsa.pub"; do if [ -f "$candidate" ]; then echo "$candidate" return fi done echo "(new Ed25519 key will be generated at $GENERATED_SSH_KEY)" } resolve_private_key_path() { for candidate in "$HOME/.ssh/id_rsa" "$HOME/.ssh/id_ed25519" "$HOME/.ssh/id_ecdsa"; do if [ -f "${candidate}.pub" ]; then echo "$candidate" return fi done echo "$GENERATED_SSH_KEY" } generate_admin_public_key() { # Returns existing key content, or generates a new pair and returns the public key for candidate in "$HOME/.ssh/id_rsa.pub" "$HOME/.ssh/id_ed25519.pub" "$HOME/.ssh/id_ecdsa.pub"; do if [ -f "$candidate" ]; then cat "$candidate" return fi done if [ -f "${GENERATED_SSH_KEY}.pub" ]; then info "Using previously generated SSH key: ${GENERATED_SSH_KEY}.pub" cat "${GENERATED_SSH_KEY}.pub" return fi info "Generating new Ed25519 SSH key pair: $GENERATED_SSH_KEY" ssh-keygen -t ed25519 -f "$GENERATED_SSH_KEY" -N "" -C "admin" >&2 info "Private key saved to: $GENERATED_SSH_KEY" info "Public key saved to: ${GENERATED_SSH_KEY}.pub" cat "${GENERATED_SSH_KEY}.pub" } # --------------------------------------------------------------------------- # Main # --------------------------------------------------------------------------- cat </dev/null; then targetNsStatus="exists" else targetNsStatus="will be created" fi if helm status k8shell --namespace "$NAMESPACE" &>/dev/null; then helmAction="upgrade" else helmAction="install" fi echo echo " Helm action : $helmAction" echo " Chart version : ${CHART_VERSION:-latest}" echo " Release namespace : $NAMESPACE (will be created if absent)" echo " Target namespace : $TARGET_NAMESPACE ($targetNsStatus)" echo " SSH NodePort : $([ "$NODE_PORT_ENABLED" = "true" ] && echo "enabled (port $NODE_PORT)" || echo "disabled")" echo " SSH proxy key : $serverKeyDesc" echo " JWT issuer key : $issuerKeyDesc" echo " Admin user : admin (sudo enabled, shell: /bin/bash)" echo " Admin SSH key : $adminKeySource" echo read -rp "Proceed with installation? [y/N] " confirm /dev/null || true) if [ -z "$nodeIp" ]; then nodeIp=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}' 2>/dev/null || true) fi fi info "Running helm $helmAction for k8shell ${CHART_VERSION:-latest}..." helm $helmAction k8shell oci://registry.k8shell.io/charts/k8shell \ ${CHART_VERSION:+--version "$CHART_VERSION"} \ --namespace "$NAMESPACE" \ --create-namespace \ --set provisioner.targetNamespace="$TARGET_NAMESPACE" \ --set sshProxy.serverKey.value="$serverKey" \ --set identity.jwtIssuer.privateKey.value="$issuerKey" \ --set identity.jwtIssuer.signingMethod.value="es256" \ --set identity.users[0].username=admin \ --set identity.users[0].uid=1001 \ --set identity.users[0].gid=1001 \ --set identity.users[0].publicKey="$adminKey" \ --set identity.users[0].sudo="true" \ --set identity.users[0].shell="/bin/bash" \ --set sshProxy.nodePort.enabled="$NODE_PORT_ENABLED" \ --set sshProxy.nodePort.port="$NODE_PORT" info "k8shell ${helmAction}ed successfully." cat <}" else echo "" echo " Start port-forwarding:" echo "" echo " kubectl port-forward svc/ssh-internal 2222:22 -n ${NAMESPACE}" echo "" echo " Connect:" echo "" echo " ssh -p 2222 -i ${privateKeyPath} admin~ubuntu@127.0.0.1" fi) EOF