diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 68c6570..15afeb4 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -3,17 +3,17 @@ name: Deploy to server on: # Runs on pushes targeting the default branch - push: - branches: ["main"] - paths: - - "backend/**" - - "frontend/**" - - "deployment/**" - - "**/package.json" - - ".nvmrc" - - ".github/workflows/deploy.yml" - - "!frontend/electron/**" - - "!**.d.ts" + # push: + # branches: ["main"] + # paths: + # - "backend/**" + # - "frontend/**" + # - "deployment/**" + # - "**/package.json" + # - ".nvmrc" + # - ".github/workflows/deploy.yml" + # - "!frontend/electron/**" + # - "!**.d.ts" workflow_dispatch: # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. diff --git a/.husky/post-push b/.husky/post-push new file mode 100755 index 0000000..7a4e6af --- /dev/null +++ b/.husky/post-push @@ -0,0 +1,116 @@ +#!/bin/sh +# Post-push hook: opens deploy command in system's native terminal +# Cross-platform support: macOS, Linux, Windows, WSL + +# Get the project root directory +PROJECT_ROOT="$(git rev-parse --show-toplevel)" +cd "$PROJECT_ROOT" || exit 1 + +# Command to run in terminal (deploy.sh will load .env from project root) +COMMAND="npm run -s deploy" + +# Detect OS and open appropriate terminal +detect_and_open_terminal() { + # Detect WSL + if [ -n "${WSL_DISTRO_NAME:-}" ] || [ -f /proc/version ] && grep -qi microsoft /proc/version 2>/dev/null; then + # WSL detected - try to open Windows Terminal, fallback to Linux terminals + if command -v wt.exe >/dev/null 2>&1; then + # Windows Terminal (preferred for WSL) + ESCAPED_PATH=$(echo "$PROJECT_ROOT" | sed "s/'/'\"'\"'/g") + ESCAPED_CMD=$(echo "$COMMAND" | sed "s/'/'\"'\"'/g") + wt.exe bash -c "cd '$ESCAPED_PATH' && set +x && echo 'Post-push: Running deploy...' && $ESCAPED_CMD && echo '' && echo 'Deploy completed. Press Enter to close...' && read -r && exit" + elif command -v wsl.exe >/dev/null 2>&1; then + # Fallback: use wsl.exe to open cmd + WINDOWS_PATH=$(wslpath -w "$PROJECT_ROOT" 2>/dev/null || echo "$PROJECT_ROOT") + cmd.exe /c "start cmd /k \"cd /d $WINDOWS_PATH && $COMMAND\"" + else + # Fallback to Linux terminal + open_linux_terminal + fi + # macOS + elif [ "$(uname)" = "Darwin" ]; then + # macOS - use .command file with open command + # Clean up old script files and create a new one + rm -f /tmp/post-push-deploy-*.command 2>/dev/null + SCRIPT_FILE=$(mktemp /tmp/post-push-deploy-XXXXXX.command 2>/dev/null) + if [ -z "$SCRIPT_FILE" ] || [ ! -f "$SCRIPT_FILE" ]; then + # Fallback if mktemp fails + SCRIPT_FILE="/tmp/post-push-deploy-$$.command" + fi + { + echo "#!/bin/bash" + echo "clear" + echo "cd '$PROJECT_ROOT'" + echo "export PS1=''" + echo "set +x" + # Export DEPLOYMENT_SERVER if it was set in the hook environment + if [ -n "$DEPLOYMENT_SERVER_VALUE" ]; then + echo "export DEPLOYMENT_SERVER='$DEPLOYMENT_SERVER_VALUE'" + fi + echo "$COMMAND" + echo "echo ''" + echo "echo 'Press Enter to close...'" + echo "read -r" + echo "osascript -e 'tell application \"Terminal\" to close front window' &" + } > "$SCRIPT_FILE" + chmod +x "$SCRIPT_FILE" + # Use open command to launch .command file - opens only one Terminal window + open "$SCRIPT_FILE" + # Windows (Git Bash or similar) + elif [ -n "${MSYSTEM:-}" ] || [ -n "${MINGW64:-}" ] || [ -n "${MINGW32:-}" ]; then + # Git Bash on Windows + if command -v wt.exe >/dev/null 2>&1; then + # Windows Terminal + ESCAPED_PATH=$(echo "$PROJECT_ROOT" | sed "s/'/'\"'\"'/g") + ESCAPED_CMD=$(echo "$COMMAND" | sed "s/'/'\"'\"'/g") + wt.exe bash -c "cd '$ESCAPED_PATH' && set +x && echo 'Post-push: Running deploy...' && $ESCAPED_CMD && echo '' && echo 'Deploy completed. Press Enter to close...' && read -r && exit" + elif command -v cmd.exe >/dev/null 2>&1; then + # Command Prompt - convert path to Windows format + WINDOWS_PATH=$(echo "$PROJECT_ROOT" | sed 's|^/\([a-z]\)|\1:|' | sed 's|/|\\|g' | sed 's|\\|\\\\|g') + cmd.exe /c "start cmd /k \"cd /d $WINDOWS_PATH && $COMMAND\"" + else + # Fallback + ESCAPED_PATH=$(echo "$PROJECT_ROOT" | sed "s/'/'\"'\"'/g") + start "Deploy" bash -c "cd '$ESCAPED_PATH' && $COMMAND; exec bash" + fi + # Linux + else + open_linux_terminal + fi +} + +open_linux_terminal() { + # Escape path for use in shell commands + ESCAPED_PATH=$(echo "$PROJECT_ROOT" | sed "s/'/'\"'\"'/g") + + ESCAPED_CMD=$(echo "$COMMAND" | sed "s/'/'\"'\"'/g") + # Try different Linux terminal emulators + if command -v gnome-terminal >/dev/null 2>&1; then + gnome-terminal -- bash -c "cd '$ESCAPED_PATH' && set +x && echo 'Post-push: Running deploy...' && $ESCAPED_CMD && echo '' && echo 'Deploy completed. Press Enter to close...' && read -r && exit" + elif command -v x-terminal-emulator >/dev/null 2>&1; then + x-terminal-emulator -e bash -c "cd '$ESCAPED_PATH' && set +x && echo 'Post-push: Running deploy...' && $ESCAPED_CMD && echo '' && echo 'Deploy completed. Press Enter to close...' && read -r && exit" + elif command -v konsole >/dev/null 2>&1; then + konsole -e bash -c "cd '$ESCAPED_PATH' && set +x && echo 'Post-push: Running deploy...' && $ESCAPED_CMD && echo '' && echo 'Deploy completed. Press Enter to close...' && read -r && exit" + elif command -v xterm >/dev/null 2>&1; then + xterm -e bash -c "cd '$ESCAPED_PATH' && set +x && echo 'Post-push: Running deploy...' && $ESCAPED_CMD && echo '' && echo 'Deploy completed. Press Enter to close...' && read -r && exit" + elif command -v alacritty >/dev/null 2>&1; then + alacritty -e bash -c "cd '$ESCAPED_PATH' && set +x && echo 'Post-push: Running deploy...' && $ESCAPED_CMD && echo '' && echo 'Deploy completed. Press Enter to close...' && read -r && exit" + elif command -v kitty >/dev/null 2>&1; then + kitty bash -c "cd '$ESCAPED_PATH' && set +x && echo 'Post-push: Running deploy...' && $ESCAPED_CMD && echo '' && echo 'Deploy completed. Press Enter to close...' && read -r && exit" + elif command -v tilix >/dev/null 2>&1; then + tilix -e bash -c "cd '$ESCAPED_PATH' && set +x && echo 'Post-push: Running deploy...' && $ESCAPED_CMD && echo '' && echo 'Deploy completed. Press Enter to close...' && read -r && exit" + else + # Last resort: try to find any terminal + TERMINAL=$(command -v x-terminal-emulator gnome-terminal konsole xterm alacritty kitty tilix 2>/dev/null | head -1) + if [ -n "$TERMINAL" ]; then + "$TERMINAL" -e bash -c "cd '$ESCAPED_PATH' && set +x && echo 'Post-push: Running deploy...' && $ESCAPED_CMD && echo '' && echo 'Deploy completed. Press Enter to close...' && read -r && exit" + else + echo "Could not find a terminal emulator. Please run manually: cd '$PROJECT_ROOT' && $COMMAND" + fi + fi +} + +# Run in background so git push doesn't wait +# Add a small delay to ensure git push completes first +(sleep 0.5 && detect_and_open_terminal) & + diff --git a/.vscode/settings.json b/.vscode/settings.json index 06d3519..b7fe892 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,7 +3,7 @@ "**/__pycache__": true, "**/package-lock.json": true, "**/*.module.scss.d.ts": true, - "**/.husky": true, + "**/.husky/_": true, "**/.venv": true, "**/node_modules": true } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 9dd345a..e11d5b1 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -33,7 +33,7 @@ "panel": "shared" }, "group": { - "kind": "build", + "kind": "build" }, "isBackground": true }, @@ -87,6 +87,19 @@ "focus": false, "panel": "shared" } + }, + { + "label": "Pre-commit checks", + "type": "shell", + "command": "npm run frontend:typecheck", + "presentation": { + "echo": true, + "reveal": "always", + "focus": true, + "panel": "dedicated", + "clear": true + }, + "problemMatcher": "$tsc" } ] } \ No newline at end of file diff --git a/package.json b/package.json index 5a46993..b679225 100644 --- a/package.json +++ b/package.json @@ -27,9 +27,11 @@ "preview": "cd deployment && docker compose up --build --watch", "preview:clean": "cd deployment && docker compose down -v", "clean": "npm run backend:clean && npm run frontend:clean && npm run preview:clean", - "install": "npm run backend:dependencies && npm run generate:env", + "install": "npm run backend:dependencies && if [[ ! -f deployment/.env ]]; then npm run generate:env; fi && npm run install:pussh", + "install:pussh": "bash ./scripts/install:pussh.sh", "prepare": "husky", - "generate:env": "bash ./scripts/generate:env.sh" + "generate:env": "bash ./scripts/generate:env.sh", + "deploy": "bash ./scripts/deploy.sh" }, "files": [ "frontend/build/electron" diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100755 index 0000000..53ebe28 --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,364 @@ +#!/bin/bash +set -e + +# Complete deployment script: build and push to server +# Usage: ./scripts/deploy.sh [server_user@server_host] [deployment_path] [platform] +# Example: ./scripts/deploy.sh user@example.com /home/user/fromchat linux/arm64 + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +MAGENTA='\033[0;35m' +NC='\033[0m' # No Color +BOLD='\033[1m' + +# Helper functions +info() { echo -e "${BLUE}ℹ${NC} $1"; } +success() { echo -e "${GREEN}✓${NC} $1"; } +warning() { echo -e "${YELLOW}⚠${NC} $1"; } +error() { echo -e "${RED}✗${NC} $1"; exit 1; } +step() { echo -e "${CYAN}${BOLD}→${NC} ${BOLD}$1${NC}"; } +substep() { + if [ "$2" = "-n" ]; then + echo -n -e " ${GREEN}•${NC} $1" + else + echo -e " ${GREEN}•${NC} $1" + fi +} + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +DEPLOYMENT_DIR="$PROJECT_ROOT/deployment" +ENV_FILE="$DEPLOYMENT_DIR/.env" + +# Load .env file if it exists +if [ -f "$ENV_FILE" ]; then + # Export variables from .env file (ignore comments and empty lines) + set -a + while IFS= read -r line || [ -n "$line" ]; do + # Skip comments and empty lines + case "$line" in + \#*|'') continue ;; + *) + # Export the variable + export "$line" 2>/dev/null || true + ;; + esac + done < "$ENV_FILE" + set +a +fi + +# Read server from environment variable (from .env), command line argument, or fallback +SERVER="${1:-${DEPLOYMENT_SERVER:-}}" +DEPLOY_PATH="${2:-${DEPLOY_PATH:-/home/denis0001-dev/actions-runner/_work/FromChat/FromChat}}" +PLATFORM="${3:-linux/arm64}" + +# Check if server is provided +if [ -z "$SERVER" ]; then + error "Server not specified. Usage: $0 [user@host] [deployment_path] [platform]" + echo " Or set DEPLOYMENT_SERVER in $ENV_FILE or as an environment variable" + echo "" + echo "Example:" + echo " $0 user@example.com /home/user/fromchat linux/arm64" + echo " Or add to $ENV_FILE: DEPLOYMENT_SERVER=user@example.com" + echo " Or: DEPLOYMENT_SERVER=user@example.com $0" + exit 1 +fi + +echo -e "${MAGENTA}${BOLD}🚀 Deployment${NC}\n" + +# ============================================================================ +# BUILD PHASE +# ============================================================================ + +echo -e "${MAGENTA}${BOLD}🔨 Building Docker images${NC}" + +# Determine project name +if [ -n "$SERVER" ]; then + COMPOSE_DIR=$(ssh "$SERVER" "dirname $DEPLOY_PATH/deployment/docker-compose.yml" 2>/dev/null || echo "$DEPLOY_PATH/deployment") + PROJECT_NAME=$(ssh "$SERVER" "basename $COMPOSE_DIR" 2>/dev/null || echo "deployment") +else + PROJECT_NAME=$(basename "$DEPLOYMENT_DIR") +fi + +# Check buildx +if ! docker buildx version > /dev/null 2>&1; then + error "Docker buildx not available. Install Docker Desktop." +fi + +# Setup buildx builder +step "Setting up buildx builder" +BUILDER_NAME="fromchat-builder" +BUILDER_EXISTS=false + +if docker buildx inspect "$BUILDER_NAME" > /dev/null 2>&1; then + BUILDER_EXISTS=true + if ! docker buildx use "$BUILDER_NAME" > /dev/null 2>&1; then + substep "Recreating builder..." + docker buildx rm "$BUILDER_NAME" > /dev/null 2>&1 || true + BUILDER_EXISTS=false + elif ! docker buildx inspect "$BUILDER_NAME" > /dev/null 2>&1; then + substep "Recreating builder (inspection failed)..." + docker buildx rm "$BUILDER_NAME" > /dev/null 2>&1 || true + BUILDER_EXISTS=false + fi +fi + +if [ "$BUILDER_EXISTS" = false ]; then + substep "Creating builder with persistent cache..." + docker buildx create \ + --name "$BUILDER_NAME" \ + --driver docker-container \ + --driver-opt image=moby/buildkit:latest \ + --use \ + --bootstrap > /dev/null 2>&1 +fi + +docker buildx use "$BUILDER_NAME" > /dev/null 2>&1 + +# Detect services +step "Detecting services" +cd "$DEPLOYMENT_DIR" +SERVICES=$(docker compose -f docker-compose.yml config --services 2>/dev/null) + +if [ -z "$SERVICES" ]; then + error "No services found in docker-compose.yml" +fi + +BUILT_IMAGES=() + +for SERVICE in $SERVICES; do + HAS_BUILD=$(docker compose -f docker-compose.yml config 2>/dev/null | \ + grep -A 30 "^[[:space:]]*${SERVICE}:" | \ + grep -q "build:" && echo "yes" || echo "no") + + if [ "$HAS_BUILD" != "yes" ]; then + continue + fi + + IMAGE_TAG="${PROJECT_NAME}-${SERVICE}:latest" + + substep "Building ${CYAN}$SERVICE${NC} -> ${CYAN}$IMAGE_TAG${NC}..." + + BUILD_OUTPUT=$(docker compose -f docker-compose.yml config 2>/dev/null | \ + grep -A 15 "^[[:space:]]*${SERVICE}:" | \ + grep -A 10 "build:") + + DOCKERFILE_REL=$(echo "$BUILD_OUTPUT" | grep "dockerfile:" | \ + sed 's/.*dockerfile:[[:space:]]*\(.*\)/\1/' | \ + tr -d '"' | tr -d "'" | xargs) + + CONTEXT_REL=$(echo "$BUILD_OUTPUT" | grep "context:" | \ + sed 's/.*context:[[:space:]]*\(.*\)/\1/' | \ + tr -d '"' | tr -d "'" | xargs) + + if [ -z "$CONTEXT_REL" ]; then + CONTEXT_REL=".." + fi + + if [[ "$CONTEXT_REL" == ".." ]]; then + BUILD_CONTEXT="$PROJECT_ROOT" + elif [[ "$CONTEXT_REL" == /* ]]; then + BUILD_CONTEXT="$CONTEXT_REL" + else + BUILD_CONTEXT="$DEPLOYMENT_DIR/$CONTEXT_REL" + fi + + if [ -n "$DOCKERFILE_REL" ]; then + if [[ "$DOCKERFILE_REL" == /* ]]; then + DOCKERFILE="$DOCKERFILE_REL" + else + if [[ "$CONTEXT_REL" == ".." ]] || [[ "$BUILD_CONTEXT" == "$PROJECT_ROOT" ]]; then + DOCKERFILE="$PROJECT_ROOT/$DOCKERFILE_REL" + else + DOCKERFILE="$BUILD_CONTEXT/$DOCKERFILE_REL" + fi + fi + else + if [ -f "$DEPLOYMENT_DIR/Dockerfile.$SERVICE" ]; then + DOCKERFILE="$DEPLOYMENT_DIR/Dockerfile.$SERVICE" + elif [ -f "$DEPLOYMENT_DIR/$SERVICE/Dockerfile" ]; then + DOCKERFILE="$DEPLOYMENT_DIR/$SERVICE/Dockerfile" + else + error "Could not determine Dockerfile for $SERVICE" + fi + fi + + if docker buildx build \ + --platform "$PLATFORM" \ + --file "$DOCKERFILE" \ + --tag "$IMAGE_TAG" \ + --load \ + "$BUILD_CONTEXT"; then + echo -e " ${GREEN}✓${NC} Built ${CYAN}$SERVICE${NC}" + BUILT_IMAGES+=("$IMAGE_TAG") + echo "" + else + error "Build failed for $SERVICE" + fi +done + +success "Build complete! ${#BUILT_IMAGES[@]} image(s) ready" + +# ============================================================================ +# DEPLOY PHASE +# ============================================================================ + +echo -e "\n${MAGENTA}${BOLD}🚀 Deploying to ${SERVER}${NC}\n" + +# Ask for sudo password at the beginning +step "Authentication" +SUDO_PASSWORD="" +while true; do + substep "Sudo password: " -n + read -sp "" SUDO_PASSWORD + echo "" + + if [ -z "$SUDO_PASSWORD" ]; then + warning "No password provided - assuming passwordless sudo" + break + fi + + if echo "$SUDO_PASSWORD" | ssh "$SERVER" "sudo -S -v" > /dev/null 2>&1; then + export SUDO_PASSWORD + break + else + error "Invalid password, please try again" + fi +done + +# Check SSH connection (silent) +if ! ssh -o BatchMode=yes -o ConnectTimeout=5 "$SERVER" "echo" > /dev/null 2>&1; then + warning "SSH key auth not available, will prompt when needed" +fi + +# Check docker pussh +if ! docker pussh --help > /dev/null 2>&1; then + error "docker pussh plugin not installed" + echo " Install: npm run install:pussh" +fi + +# Detect images +IMAGES=($(docker images --format "{{.Repository}}:{{.Tag}}" | grep "^${PROJECT_NAME}-" || true)) + +if [ ${#IMAGES[@]} -eq 0 ]; then + error "No ${PROJECT_NAME} images found" +fi + +# Pre-pull unregistry image if needed +UNREGISTRY_IMAGE="ghcr.io/psviderski/unregistry:0.3.1" +if ! ssh "$SERVER" "docker images --format '{{.Repository}}:{{.Tag}}' | grep -q '^${UNREGISTRY_IMAGE}$'" 2>/dev/null; then + substep "Pulling unregistry image (one-time setup)..." + ssh "$SERVER" "docker pull ${UNREGISTRY_IMAGE}" > /dev/null 2>&1 || true +fi + +# Transfer images +step "Transferring images" +PUSH_FAILED=0 +for IMAGE in "${IMAGES[@]}"; do + substep "Pushing ${CYAN}$IMAGE${NC}..." + if docker pussh "$IMAGE" "$SERVER"; then + echo "" + else + echo -e " ${RED}✗${NC} Failed to push ${CYAN}$IMAGE${NC}" + PUSH_FAILED=1 + echo "" + fi +done + +if [ $PUSH_FAILED -eq 1 ]; then + error "Image transfer failed" +fi + +# Transfer files +step "Transferring deployment files" +TEMP_DIR="/tmp/fromchat-deploy-$$" +ssh "$SERVER" "mkdir -p $TEMP_DIR" > /dev/null 2>&1 + +# Copy docker-compose.yml +if scp "$DEPLOYMENT_DIR/docker-compose.yml" "$SERVER:$TEMP_DIR/docker-compose.yml" > /dev/null 2>&1; then + if [ -n "$SUDO_PASSWORD" ]; then + ssh "$SERVER" bash << REMOTE_SUDO_SCRIPT > /dev/null 2>&1 +set -e +echo '$SUDO_PASSWORD' | sudo -S -p '' mkdir -p $DEPLOY_PATH/deployment 2>/dev/null || true +echo '$SUDO_PASSWORD' | sudo -S -p '' cp $TEMP_DIR/docker-compose.yml $DEPLOY_PATH/deployment/ 2>/dev/null || true +echo '$SUDO_PASSWORD' | sudo -S -p '' chown \$(whoami):\$(whoami) $DEPLOY_PATH/deployment/docker-compose.yml 2>/dev/null || true +REMOTE_SUDO_SCRIPT + else + ssh "$SERVER" "sudo mkdir -p $DEPLOY_PATH/deployment && sudo cp $TEMP_DIR/docker-compose.yml $DEPLOY_PATH/deployment/ && sudo chown \$(whoami):\$(whoami) $DEPLOY_PATH/deployment/docker-compose.yml" > /dev/null 2>&1 || true + fi +fi + +# Copy service file +scp "$DEPLOYMENT_DIR/fromchat.service" "$SERVER:$TEMP_DIR/fromchat.service" > /dev/null 2>&1 || { + error "Failed to copy fromchat.service" +} + +if [ -n "$SUDO_PASSWORD" ]; then + ssh "$SERVER" bash << REMOTE_SUDO_SCRIPT > /dev/null 2>&1 +set -e +echo '$SUDO_PASSWORD' | sudo -S -p '' mkdir -p $DEPLOY_PATH/deployment 2>/dev/null +echo '$SUDO_PASSWORD' | sudo -S -p '' cp $TEMP_DIR/fromchat.service $DEPLOY_PATH/deployment/ 2>/dev/null +echo '$SUDO_PASSWORD' | sudo -S -p '' chown \$(whoami):\$(whoami) $DEPLOY_PATH/deployment/fromchat.service 2>/dev/null +REMOTE_SUDO_SCRIPT +else + ssh "$SERVER" "sudo mkdir -p $DEPLOY_PATH/deployment && sudo cp $TEMP_DIR/fromchat.service $DEPLOY_PATH/deployment/ && sudo chown \$(whoami):\$(whoami) $DEPLOY_PATH/deployment/fromchat.service" > /dev/null 2>&1 || { + error "Failed to copy fromchat.service" + } +fi + +ssh "$SERVER" "rm -rf $TEMP_DIR" > /dev/null 2>&1 || true + +# Deploy on server +step "Deploying on server" +ssh "$SERVER" SUDO_PASSWORD="$SUDO_PASSWORD" DEPLOY_PATH="$DEPLOY_PATH" bash << 'REMOTE_SCRIPT' +set -e + +REMOTE_SUDO_PASS="${SUDO_PASSWORD:-}" +REMOTE_DEPLOY_PATH="${DEPLOY_PATH:-}" +export SUDO_PROMPT="" + +sudo_cmd() { + if [ -n "$REMOTE_SUDO_PASS" ]; then + echo "$REMOTE_SUDO_PASS" | sudo -S -p '' "$@" 2>/dev/null + else + sudo "$@" 2>/dev/null + fi +} + +if [ -z "$REMOTE_DEPLOY_PATH" ]; then + echo "❌ DEPLOY_PATH is not set" + exit 1 +fi + +mkdir -p "$REMOTE_DEPLOY_PATH/deployment" +cd "$REMOTE_DEPLOY_PATH/deployment" + +if [ ! -f "$REMOTE_DEPLOY_PATH/deployment/.env" ]; then + echo "⚠️ Warning: .env file not found" +fi + +if systemctl is-active --quiet fromchat; then + sudo_cmd systemctl stop fromchat +fi + +docker compose down > /dev/null 2>&1 || true + +sudo_cmd cp -f "$REMOTE_DEPLOY_PATH/deployment/fromchat.service" /etc/systemd/system/fromchat.service +sudo_cmd systemctl daemon-reload +sudo_cmd systemctl restart fromchat + +sleep 3 +if systemctl is-active --quiet fromchat; then + echo "✅ Service started" +else + echo "❌ Service failed to start" + sudo_cmd journalctl --no-pager -xeu fromchat -n 30 + exit 1 +fi +REMOTE_SCRIPT + +success "Deployment complete!" diff --git a/scripts/generate:env.sh b/scripts/generate:env.sh index 31a890a..a4070ef 100755 --- a/scripts/generate:env.sh +++ b/scripts/generate:env.sh @@ -8,4 +8,5 @@ cat >> deployment/.env < TURN_SECRET= +DEPLOYMENT_SERVER= EOF diff --git a/scripts/install:pussh.sh b/scripts/install:pussh.sh new file mode 100755 index 0000000..361f9ed --- /dev/null +++ b/scripts/install:pussh.sh @@ -0,0 +1,79 @@ +#!/bin/bash +# Don't use set -e here - we want to continue if Homebrew installation fails + +# Install docker-pussh plugin for Docker CLI +# This script installs the unregistry docker-pussh plugin + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +echo "📦 Installing docker-pussh plugin..." + +# Check if Docker is installed +if ! command -v docker > /dev/null 2>&1; then + echo "⚠️ Docker is not installed. Skipping docker-pussh installation." + exit 0 +fi + +# Create docker plugins directory if it doesn't exist +PLUGIN_DIR="$HOME/.docker/cli-plugins" +mkdir -p "$PLUGIN_DIR" + +# Check if already installed +if [ -f "$PLUGIN_DIR/docker-pussh" ] && docker pussh --help > /dev/null 2>&1; then + echo " ✓ docker-pussh is already installed" + docker pussh --version 2>/dev/null || true + exit 0 +fi + +# Try installing via Homebrew first +if command -v brew > /dev/null 2>&1; then + echo " Attempting installation via Homebrew..." + if brew install psviderski/tap/docker-pussh 2>/dev/null; then + # Create symlink to use as Docker CLI plugin + BREW_PREFIX=$(brew --prefix 2>/dev/null || echo "/opt/homebrew") + if [ -f "$BREW_PREFIX/bin/docker-pussh" ]; then + mkdir -p "$PLUGIN_DIR" + ln -sf "$BREW_PREFIX/bin/docker-pussh" "$PLUGIN_DIR/docker-pussh" 2>/dev/null || true + + # Verify installation + if docker pussh --help > /dev/null 2>&1; then + echo " ✓ docker-pussh installed successfully via Homebrew" + docker pussh --version 2>/dev/null || true + exit 0 + fi + fi + fi + echo " ⚠️ Homebrew installation failed or incomplete, trying direct download..." +fi + +# Fallback: Download and install docker-pussh directly (using latest from main branch) +echo " Downloading docker-pussh from unregistry..." +if curl -sSL https://raw.githubusercontent.com/psviderski/unregistry/main/docker-pussh \ + -o "$PLUGIN_DIR/docker-pussh" 2>/dev/null; then + chmod +x "$PLUGIN_DIR/docker-pussh" + + # Verify installation + if docker pussh --help > /dev/null 2>&1; then + echo " ✓ docker-pussh installed successfully" + docker pussh --version 2>/dev/null || true + else + echo " ⚠️ Installation completed but plugin verification failed" + echo " You may need to restart your terminal or Docker Desktop" + fi +else + echo " ⚠️ Failed to download docker-pussh" + echo " You can install it manually:" + echo "" + echo " Via Homebrew:" + echo " brew install psviderski/tap/docker-pussh" + echo " mkdir -p ~/.docker/cli-plugins" + echo " ln -sf \$(brew --prefix)/bin/docker-pussh ~/.docker/cli-plugins/docker-pussh" + echo "" + echo " Or via direct download:" + echo " mkdir -p ~/.docker/cli-plugins" + echo " curl -sSL https://raw.githubusercontent.com/psviderski/unregistry/main/docker-pussh \\" + echo " -o ~/.docker/cli-plugins/docker-pussh" + echo " chmod +x ~/.docker/cli-plugins/docker-pussh" + exit 0 +fi + diff --git a/scripts/transfer:unregistry.sh b/scripts/transfer:unregistry.sh new file mode 100755 index 0000000..8e5b8c5 --- /dev/null +++ b/scripts/transfer:unregistry.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# Transfer unregistry image from local machine to server +# Usage: ./scripts/transfer:unregistry.sh [server_user@server_host] + +set -e + +SERVER="${1:-${DEPLOY_SERVER:-}}" + +if [ -z "$SERVER" ]; then + echo "❌ Server not specified. Usage: $0 [user@host]" + echo " Or set DEPLOY_SERVER environment variable" + exit 1 +fi + +echo "📦 Transferring unregistry image to $SERVER..." + +# Pull the image locally if not already present +if ! docker images ghcr.io/psviderski/unregistry:0.3.1 --format "{{.Repository}}:{{.Tag}}" | grep -q "unregistry:0.3.1"; then + echo " Pulling unregistry:0.3.1 locally..." + docker pull ghcr.io/psviderski/unregistry:0.3.1 +fi + +# Save and transfer the image +echo " Saving and transferring image to server..." +docker save ghcr.io/psviderski/unregistry:0.3.1 | ssh "$SERVER" "docker load" + +echo " ✓ Unregistry image transferred successfully" +echo "" +echo " Now you can run on the server:" +echo " docker run -d \\" +echo " --name unregistry \\" +echo " -p 5000:5000 \\" +echo " -v /run/containerd/containerd.sock:/run/containerd/containerd.sock \\" +echo " --restart unless-stopped \\" +echo " ghcr.io/psviderski/unregistry:0.3.1" + +