#!/bin/sh # Install and run Windshift with Docker or Podman on macOS or Linux. # Usage: curl -fsSL https://windshift.sh/install.sh | sh set -eu IMAGE_REPOSITORY="ghcr.io/windshiftapp/windshift" CONTAINER_NAME="${WINDSHIFT_CONTAINER_NAME:-windshift}" PORT="${WINDSHIFT_PORT:-8080}" VERSION="${WINDSHIFT_VERSION:-latest}" INSTALL_DIR="${WINDSHIFT_INSTALL_DIR:-$HOME/.windshift}" SECRET_FILE="$INSTALL_DIR/.sso-secret" DATA_VOLUME="${WINDSHIFT_DATA_VOLUME:-windshift-data}" DATA_PATH="${WINDSHIFT_DATA_PATH:-}" RUNTIME="${WINDSHIFT_CONTAINER_RUNTIME:-}" IMAGE="$IMAGE_REPOSITORY:$VERSION" say() { printf '%s\n' "$*" } fail() { printf 'Windshift install: %s\n' "$*" >&2 exit 1 } prompt() { prompt_text=$1 if [ -r /dev/tty ]; then printf '%s' "$prompt_text" > /dev/tty IFS= read -r prompt_value < /dev/tty || prompt_value='' printf '%s' "$prompt_value" else printf '' fi } command -v openssl >/dev/null 2>&1 || fail "OpenSSL is required to generate the SSO secret." if [ -z "$RUNTIME" ]; then if command -v podman >/dev/null 2>&1 && podman info >/dev/null 2>&1; then RUNTIME=podman elif command -v docker >/dev/null 2>&1 && docker info >/dev/null 2>&1; then RUNTIME=docker else fail "Podman or Docker is required. Start Podman or Docker, or install one of them, then run this script again." fi else case "$RUNTIME" in docker|podman) ;; *) fail "WINDSHIFT_CONTAINER_RUNTIME must be docker or podman." ;; esac command -v "$RUNTIME" >/dev/null 2>&1 || fail "$RUNTIME is not installed." "$RUNTIME" info >/dev/null 2>&1 || fail "$RUNTIME is not running. Start it and run this script again." fi if "$RUNTIME" container inspect "$CONTAINER_NAME" >/dev/null 2>&1; then if [ "$($RUNTIME inspect -f '{{.State.Running}}' "$CONTAINER_NAME")" = "true" ]; then say "Windshift is already running at http://localhost:$PORT" else "$RUNTIME" start "$CONTAINER_NAME" >/dev/null say "Started the existing Windshift container at http://localhost:$PORT" fi exit 0 fi mkdir -p "$INSTALL_DIR" chmod 700 "$INSTALL_DIR" if [ -s "$SECRET_FILE" ]; then SSO_SECRET=$(cat "$SECRET_FILE") else umask 077 SSO_SECRET=$(openssl rand -hex 32) || fail "Could not generate SSO_SECRET." secret_tmp="$SECRET_FILE.$$" printf '%s\n' "$SSO_SECRET" > "$secret_tmp" chmod 600 "$secret_tmp" mv "$secret_tmp" "$SECRET_FILE" fi if [ -z "$DATA_PATH" ]; then DATA_PATH=$(prompt "Data directory (leave blank for $RUNTIME volume $DATA_VOLUME): ") fi USER_ARGS='' if [ -n "$DATA_PATH" ]; then mkdir -p "$DATA_PATH" DATA_MOUNT="$DATA_PATH:/data" USER_ARGS="--user $(id -u):$(id -g)" if [ "$RUNTIME" = "podman" ]; then DATA_MOUNT="$DATA_PATH:/data:Z" fi STORAGE_MESSAGE="host directory $DATA_PATH" else "$RUNTIME" volume create "$DATA_VOLUME" >/dev/null DATA_MOUNT="$DATA_VOLUME:/data" STORAGE_MESSAGE="$RUNTIME volume $DATA_VOLUME" fi say "Using $RUNTIME" say "Pulling $IMAGE" "$RUNTIME" pull "$IMAGE" say "Starting Windshift with SQLite in $STORAGE_MESSAGE" # USER_ARGS is used only for a host bind mount, so files belong to the user # who supplied the path. The default named volume keeps the image's UID 65534. # shellcheck disable=SC2086 "$RUNTIME" run -d \ --name "$CONTAINER_NAME" \ --restart unless-stopped \ -p "$PORT:8080" \ --tmpfs /tmp:exec,size=64M \ -v "$DATA_MOUNT" \ $USER_ARGS \ -e BASE_URL="http://localhost:$PORT" \ -e WEBAUTHN_RP_ID=localhost \ -e SSO_SECRET="$SSO_SECRET" \ -e DB_PATH=/data/windshift.db \ -e ATTACHMENT_PATH=/data/attachments \ "$IMAGE" >/dev/null say '' say 'Windshift is running at http://localhost:'"$PORT" say 'SSO_SECRET is stored in: '"$SECRET_FILE" say 'View logs with: '"$RUNTIME logs -f $CONTAINER_NAME" say 'Readiness check: curl http://localhost:'"$PORT"'/readyz' say '' say 'This installer is for local evaluation. Read the production guide before exposing Windshift to other users:' say 'https://windshift.sh/self-hosting/02-deployment/06-production-ready-self-hosting'