From 42a2918665054fe222287f2252940d3fb9d41eae Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 16 Feb 2026 21:52:12 +0000 Subject: [PATCH] feat: add Hostinger VPS remote deployment tooling Add complete deployment infrastructure for Hostinger VPS servers: - setup-server.sh: initial VPS provisioning (Docker/binary, firewall, fail2ban) - deploy.sh: automated remote deploy via SSH (Docker or binary methods) - rollback.sh: quick rollback to previous version - status.sh: remote health and status monitoring - docker-compose.production.yml: production-ready compose with resource limits - picoclaw.service: systemd unit with security hardening - DEPLOY.md: step-by-step deployment guide (Portuguese) - Makefile targets: deploy-hostinger, deploy-hostinger-setup, status, rollback https://claude.ai/code/session_019vXaqxGmkdCjM8m3jp6rYj --- Makefile | 57 +++- deploy/hostinger/DEPLOY.md | 205 ++++++++++++ deploy/hostinger/deploy.sh | 255 +++++++++++++++ .../hostinger/docker-compose.production.yml | 48 +++ deploy/hostinger/picoclaw.service | 51 +++ deploy/hostinger/rollback.sh | 109 +++++++ deploy/hostinger/setup-server.sh | 304 ++++++++++++++++++ deploy/hostinger/status.sh | 98 ++++++ 8 files changed, 1122 insertions(+), 5 deletions(-) create mode 100644 deploy/hostinger/DEPLOY.md create mode 100755 deploy/hostinger/deploy.sh create mode 100644 deploy/hostinger/docker-compose.production.yml create mode 100644 deploy/hostinger/picoclaw.service create mode 100755 deploy/hostinger/rollback.sh create mode 100755 deploy/hostinger/setup-server.sh create mode 100755 deploy/hostinger/status.sh diff --git a/Makefile b/Makefile index 9786b30bb..6e5418de7 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: all build install uninstall clean help test +.PHONY: all build install uninstall clean help test deploy-hostinger deploy-hostinger-setup deploy-hostinger-status deploy-hostinger-rollback # Build variables BINARY_NAME=picoclaw @@ -148,6 +148,44 @@ check: deps fmt vet test run: build @$(BUILD_DIR)/$(BINARY_NAME) $(ARGS) +# ── Hostinger Deployment ───────────────────────────── +HOSTINGER_HOST?= +HOSTINGER_USER?=root +HOSTINGER_SSH_KEY?=$(HOME)/.ssh/id_rsa +HOSTINGER_SSH_PORT?=22 +HOSTINGER_DEPLOY_METHOD?=docker + +## deploy-hostinger-setup: Run initial server setup on Hostinger VPS +deploy-hostinger-setup: + @bash deploy/hostinger/setup-server.sh + +## deploy-hostinger: Deploy PicoClaw to Hostinger VPS +deploy-hostinger: + @bash deploy/hostinger/deploy.sh \ + -h "$(HOSTINGER_HOST)" \ + -u "$(HOSTINGER_USER)" \ + -k "$(HOSTINGER_SSH_KEY)" \ + -p "$(HOSTINGER_SSH_PORT)" \ + -m "$(HOSTINGER_DEPLOY_METHOD)" + +## deploy-hostinger-status: Check PicoClaw status on Hostinger VPS +deploy-hostinger-status: + @bash deploy/hostinger/status.sh \ + -h "$(HOSTINGER_HOST)" \ + -u "$(HOSTINGER_USER)" \ + -k "$(HOSTINGER_SSH_KEY)" \ + -p "$(HOSTINGER_SSH_PORT)" \ + -m "$(HOSTINGER_DEPLOY_METHOD)" + +## deploy-hostinger-rollback: Rollback PicoClaw on Hostinger VPS +deploy-hostinger-rollback: + @bash deploy/hostinger/rollback.sh \ + -h "$(HOSTINGER_HOST)" \ + -u "$(HOSTINGER_USER)" \ + -k "$(HOSTINGER_SSH_KEY)" \ + -p "$(HOSTINGER_SSH_PORT)" \ + -m "$(HOSTINGER_DEPLOY_METHOD)" + ## help: Show this help message help: @echo "picoclaw Makefile" @@ -162,12 +200,21 @@ help: @echo " make build # Build for current platform" @echo " make install # Install to ~/.local/bin" @echo " make uninstall # Remove from /usr/local/bin" - @echo " make install-skills # Install skills to workspace" + @echo "" + @echo "Hostinger Deployment:" + @echo " make deploy-hostinger-setup HOSTINGER_HOST=1.2.3.4 # Initial server setup" + @echo " make deploy-hostinger HOSTINGER_HOST=1.2.3.4 # Deploy to VPS" + @echo " make deploy-hostinger-status HOSTINGER_HOST=1.2.3.4 # Check status" + @echo " make deploy-hostinger-rollback HOSTINGER_HOST=1.2.3.4 # Rollback" @echo "" @echo "Environment Variables:" - @echo " INSTALL_PREFIX # Installation prefix (default: ~/.local)" - @echo " WORKSPACE_DIR # Workspace directory (default: ~/.picoclaw/workspace)" - @echo " VERSION # Version string (default: git describe)" + @echo " INSTALL_PREFIX # Installation prefix (default: ~/.local)" + @echo " WORKSPACE_DIR # Workspace directory (default: ~/.picoclaw/workspace)" + @echo " VERSION # Version string (default: git describe)" + @echo " HOSTINGER_HOST # Hostinger VPS IP address" + @echo " HOSTINGER_USER # SSH user (default: root)" + @echo " HOSTINGER_SSH_KEY # SSH key path (default: ~/.ssh/id_rsa)" + @echo " HOSTINGER_DEPLOY_METHOD # Deploy method: docker or binary (default: docker)" @echo "" @echo "Current Configuration:" @echo " Platform: $(PLATFORM)/$(ARCH)" diff --git a/deploy/hostinger/DEPLOY.md b/deploy/hostinger/DEPLOY.md new file mode 100644 index 000000000..91e2ebde1 --- /dev/null +++ b/deploy/hostinger/DEPLOY.md @@ -0,0 +1,205 @@ +# PicoClaw - Deploy na Hostinger VPS + +Guia completo para deploy do PicoClaw em um servidor VPS da Hostinger. + +## Pre-requisitos + +- VPS Hostinger com Ubuntu 22.04+ ou Debian 12+ (KVM) +- Acesso SSH configurado (chave SSH recomendado) +- API keys dos provedores que deseja usar (Anthropic, OpenAI, etc.) + +> **Nota:** Hospedagem compartilhada da Hostinger **nao** suporta PicoClaw. +> Voce precisa de um plano VPS ou Cloud com acesso root. + +## Metodo 1: Deploy via Docker (Recomendado) + +### Passo 1: Configurar acesso SSH + +```bash +# Gerar chave SSH (se ainda nao tem) +ssh-keygen -t ed25519 -C "picoclaw-hostinger" + +# Copiar chave para o servidor +ssh-copy-id -i ~/.ssh/id_ed25519 root@SEU_IP_VPS +``` + +### Passo 2: Setup inicial do servidor + +Execute uma unica vez para preparar o servidor: + +```bash +# Opcao A: Via make +make deploy-hostinger-setup HOSTINGER_HOST=SEU_IP_VPS + +# Opcao B: Direto via SSH +ssh root@SEU_IP_VPS 'bash -s' < deploy/hostinger/setup-server.sh +``` + +Isso instala: Docker, firewall (ufw), fail2ban, e cria a estrutura de diretorios. + +### Passo 3: Configurar API keys no servidor + +```bash +ssh root@SEU_IP_VPS + +# Editar chaves de API +nano /opt/picoclaw/config/.env + +# Editar configuracao do PicoClaw +nano /opt/picoclaw/config/config.json +``` + +Exemplo minimo do `.env`: +```bash +ANTHROPIC_API_KEY=sk-ant-sua-chave-aqui +TELEGRAM_BOT_TOKEN=123456:ABC-seu-token +TZ=America/Sao_Paulo +``` + +### Passo 4: Deploy + +```bash +# Deploy com Docker +make deploy-hostinger HOSTINGER_HOST=SEU_IP_VPS + +# Ou com variaveis de ambiente +export HOSTINGER_HOST=SEU_IP_VPS +export HOSTINGER_USER=root +make deploy-hostinger +``` + +### Passo 5: Verificar + +```bash +# Status completo +make deploy-hostinger-status HOSTINGER_HOST=SEU_IP_VPS + +# Health check rapido +curl http://SEU_IP_VPS:18790/health +``` + +## Metodo 2: Deploy via Binario (Menor consumo) + +Para VPS com pouca memoria (512MB-1GB), o deploy via binario usa menos recursos. + +### Setup do servidor + +```bash +ssh root@SEU_IP_VPS 'bash -s -- binary' < deploy/hostinger/setup-server.sh +``` + +### Deploy + +```bash +# Build local e upload (mais rapido se sua maquina e potente) +make deploy-hostinger HOSTINGER_HOST=SEU_IP_VPS HOSTINGER_DEPLOY_METHOD=binary + +# Ou build no servidor (nao precisa de Go local) +# O script sincroniza o codigo e compila no VPS +``` + +### Gerenciar servico + +```bash +ssh root@SEU_IP_VPS + +# Status +systemctl status picoclaw + +# Logs em tempo real +tail -f /opt/picoclaw/logs/picoclaw.log + +# Reiniciar +systemctl restart picoclaw + +# Parar +systemctl stop picoclaw +``` + +## Comandos Make + +| Comando | Descricao | +|---------|-----------| +| `make deploy-hostinger-setup` | Setup inicial do servidor | +| `make deploy-hostinger` | Deploy/atualizar PicoClaw | +| `make deploy-hostinger-status` | Verificar status | +| `make deploy-hostinger-rollback` | Reverter para versao anterior | + +### Variaveis de ambiente + +| Variavel | Padrao | Descricao | +|----------|--------|-----------| +| `HOSTINGER_HOST` | (obrigatorio) | IP ou hostname do VPS | +| `HOSTINGER_USER` | `root` | Usuario SSH | +| `HOSTINGER_SSH_KEY` | `~/.ssh/id_rsa` | Caminho da chave SSH | +| `HOSTINGER_SSH_PORT` | `22` | Porta SSH | +| `HOSTINGER_DEPLOY_METHOD` | `docker` | `docker` ou `binary` | + +## Estrutura no Servidor + +``` +/opt/picoclaw/ +├── bin/ # Binario do PicoClaw (metodo binary) +├── config/ +│ ├── .env # Chaves de API (chmod 600) +│ └── config.json # Configuracao do PicoClaw +├── workspace/ # Workspace persistente +│ ├── memory/ # Memoria de sessoes +│ └── skills/ # Skills customizadas +├── logs/ # Logs (metodo binary) +├── backups/ # Backups de versoes anteriores +└── src/ # Codigo fonte (para build remoto) +``` + +## Seguranca + +- Firewall (ufw) configurado: apenas SSH (22) e Gateway (18790) +- fail2ban ativo para protecao contra brute-force SSH +- Servico roda com usuario dedicado `picoclaw` (sem root) +- Hardening via systemd (ProtectSystem, NoNewPrivileges, etc.) +- Arquivos `.env` e `config.json` com permissao 600 + +## Troubleshooting + +### Container nao inicia (Docker) +```bash +ssh root@SEU_IP_VPS +docker compose -f /opt/picoclaw/docker-compose.yml logs picoclaw-gateway +``` + +### Servico nao inicia (Binary) +```bash +ssh root@SEU_IP_VPS +journalctl -u picoclaw -n 50 --no-pager +cat /opt/picoclaw/logs/picoclaw-error.log +``` + +### Health check falha +```bash +# Verificar se a porta esta aberta +ss -tlnp | grep 18790 + +# Verificar firewall +ufw status + +# Testar localmente no servidor +curl -v http://localhost:18790/health +``` + +### Memoria insuficiente +Se o VPS tem pouca RAM, use o metodo binary em vez de Docker: +```bash +make deploy-hostinger HOSTINGER_HOST=SEU_IP_VPS HOSTINGER_DEPLOY_METHOD=binary +``` + +## Atualizacoes + +Para atualizar o PicoClaw, basta rodar novamente: +```bash +make deploy-hostinger HOSTINGER_HOST=SEU_IP_VPS +``` + +Para reverter: +```bash +make deploy-hostinger-rollback HOSTINGER_HOST=SEU_IP_VPS +``` diff --git a/deploy/hostinger/deploy.sh b/deploy/hostinger/deploy.sh new file mode 100755 index 000000000..9ef326710 --- /dev/null +++ b/deploy/hostinger/deploy.sh @@ -0,0 +1,255 @@ +#!/usr/bin/env bash +# ============================================================ +# PicoClaw - Remote Deploy to Hostinger VPS +# ============================================================ +# Deploys PicoClaw to a remote Hostinger VPS via SSH. +# Supports both Docker and binary deployment methods. +# +# Usage: +# ./deploy/hostinger/deploy.sh [OPTIONS] +# +# Options: +# -h, --host HOST VPS IP or hostname (required, or set HOSTINGER_HOST) +# -u, --user USER SSH user (default: root) +# -k, --key KEY SSH key path (default: ~/.ssh/id_rsa) +# -m, --method METHOD Deploy method: "docker" or "binary" (default: docker) +# -p, --port PORT SSH port (default: 22) +# --build-local Build binary locally and upload (for binary method) +# --help Show this help +# ============================================================ + +set -euo pipefail + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +log() { echo -e "${GREEN}[DEPLOY]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +error() { echo -e "${RED}[ERROR]${NC} $*"; exit 1; } +info() { echo -e "${BLUE}[INFO]${NC} $*"; } + +# ── Default Configuration ──────────────────────────── +HOST="${HOSTINGER_HOST:-}" +USER="${HOSTINGER_USER:-root}" +SSH_KEY="${HOSTINGER_SSH_KEY:-${HOME}/.ssh/id_rsa}" +SSH_PORT="${HOSTINGER_SSH_PORT:-22}" +METHOD="${HOSTINGER_DEPLOY_METHOD:-docker}" +BUILD_LOCAL=false +REMOTE_DIR="/opt/picoclaw" + +# ── Parse Arguments ────────────────────────────────── +while [[ $# -gt 0 ]]; do + case $1 in + -h|--host) HOST="$2"; shift 2 ;; + -u|--user) USER="$2"; shift 2 ;; + -k|--key) SSH_KEY="$2"; shift 2 ;; + -m|--method) METHOD="$2"; shift 2 ;; + -p|--port) SSH_PORT="$2"; shift 2 ;; + --build-local) BUILD_LOCAL=true; shift ;; + --help) + head -20 "$0" | tail -15 + exit 0 + ;; + *) error "Unknown option: $1" ;; + esac +done + +# ── Validate ───────────────────────────────────────── +[ -z "${HOST}" ] && error "Host required. Use -h/--host or set HOSTINGER_HOST env var." +[ ! -f "${SSH_KEY}" ] && error "SSH key not found: ${SSH_KEY}" + +SSH_OPTS="-o StrictHostKeyChecking=accept-new -o ConnectTimeout=10 -p ${SSH_PORT} -i ${SSH_KEY}" +SSH_CMD="ssh ${SSH_OPTS} ${USER}@${HOST}" +SCP_CMD="scp ${SSH_OPTS}" + +# ── Verify Connection ──────────────────────────────── +log "Verifying SSH connection to ${USER}@${HOST}:${SSH_PORT}..." +${SSH_CMD} "echo 'Connection OK'" || error "Cannot connect to ${HOST}. Check SSH settings." + +# ── Get project root ───────────────────────────────── +PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "${PROJECT_ROOT}" + +log "Deploying PicoClaw to ${HOST} via ${METHOD} method" +echo "" + +# ── Deploy via Docker ──────────────────────────────── +deploy_docker() { + log "=== Docker Deployment ===" + + # 1. Sync project files to server + log "Syncing project files..." + rsync -avz --delete \ + -e "ssh ${SSH_OPTS}" \ + --exclude '.git' \ + --exclude 'build/' \ + --exclude '.env' \ + --exclude 'config/config.json' \ + --exclude '*.test' \ + "${PROJECT_ROOT}/" "${USER}@${HOST}:${REMOTE_DIR}/src/" + + # 2. Copy docker-compose production file + log "Copying Docker Compose production config..." + ${SCP_CMD} "${PROJECT_ROOT}/deploy/hostinger/docker-compose.production.yml" \ + "${USER}@${HOST}:${REMOTE_DIR}/docker-compose.yml" + + # 3. Build and restart on server + log "Building and starting containers on server..." + ${SSH_CMD} <<'REMOTEOF' +set -e +cd /opt/picoclaw + +# Build the image +echo "[REMOTE] Building Docker image..." +docker compose build --no-cache picoclaw-gateway + +# Stop existing container gracefully +echo "[REMOTE] Stopping existing container..." +docker compose down --timeout 30 2>/dev/null || true + +# Start new container +echo "[REMOTE] Starting PicoClaw gateway..." +docker compose up -d picoclaw-gateway + +# Wait and verify +sleep 5 +if docker compose ps picoclaw-gateway | grep -q "Up"; then + echo "[REMOTE] PicoClaw is running!" + docker compose ps +else + echo "[REMOTE] ERROR: Container failed to start" + docker compose logs --tail=50 picoclaw-gateway + exit 1 +fi + +# Health check +echo "[REMOTE] Checking health..." +sleep 3 +if curl -sf http://localhost:18790/health > /dev/null 2>&1; then + echo "[REMOTE] Health check PASSED" +else + echo "[REMOTE] Health check failed (may still be starting up)" +fi + +# Cleanup old images +echo "[REMOTE] Cleaning up old Docker images..." +docker image prune -f +REMOTEOF + + log "Docker deployment complete!" +} + +# ── Deploy via Binary ──────────────────────────────── +deploy_binary() { + log "=== Binary Deployment ===" + + if [ "${BUILD_LOCAL}" = true ]; then + # Build locally for linux/amd64 + log "Building binary locally for linux/amd64..." + cd "${PROJECT_ROOT}" + make generate + GOOS=linux GOARCH=amd64 go build -v \ + -ldflags "-X main.version=$(git describe --tags --always --dirty 2>/dev/null || echo dev) \ + -X main.gitCommit=$(git rev-parse --short=8 HEAD 2>/dev/null || echo dev) \ + -X main.buildTime=$(date +%FT%T%z)" \ + -o build/picoclaw-linux-amd64 ./cmd/picoclaw + BINARY_PATH="build/picoclaw-linux-amd64" + log "Binary built: ${BINARY_PATH}" + else + # Build on server + log "Syncing source code to server..." + rsync -avz --delete \ + -e "ssh ${SSH_OPTS}" \ + --exclude '.git' \ + --exclude 'build/' \ + --exclude '.env' \ + --exclude 'config/config.json' \ + "${PROJECT_ROOT}/" "${USER}@${HOST}:${REMOTE_DIR}/src/" + + log "Building on server..." + ${SSH_CMD} <<'REMOTEOF' +set -e +cd /opt/picoclaw/src +export PATH=$PATH:/usr/local/go/bin +make build +cp build/picoclaw /opt/picoclaw/bin/picoclaw +chmod +x /opt/picoclaw/bin/picoclaw +echo "[REMOTE] Build complete: $(/opt/picoclaw/bin/picoclaw version 2>/dev/null || echo 'built')" +REMOTEOF + fi + + if [ "${BUILD_LOCAL}" = true ]; then + # Upload binary + log "Uploading binary to server..." + ${SCP_CMD} "${BINARY_PATH}" "${USER}@${HOST}:${REMOTE_DIR}/bin/picoclaw" + ${SSH_CMD} "chmod +x ${REMOTE_DIR}/bin/picoclaw" + + # Initialize workspace if needed + ${SSH_CMD} < /dev/null 2>&1; then + echo "[REMOTE] Health check PASSED" +else + echo "[REMOTE] Health check failed (may still be starting up)" + tail -20 /opt/picoclaw/logs/picoclaw.log 2>/dev/null || true +fi +REMOTEOF + + log "Binary deployment complete!" +} + +# ── Execute Deploy ─────────────────────────────────── +DEPLOY_START=$(date +%s) + +case "${METHOD}" in + docker) deploy_docker ;; + binary) deploy_binary ;; + *) error "Unknown method: ${METHOD}. Use 'docker' or 'binary'." ;; +esac + +DEPLOY_END=$(date +%s) +DEPLOY_DURATION=$((DEPLOY_END - DEPLOY_START)) + +echo "" +log "==========================================" +log " Deployment successful!" +log " Duration: ${DEPLOY_DURATION}s" +log " Host: ${HOST}" +log " Method: ${METHOD}" +log "==========================================" +echo "" +info "Useful commands:" +echo " Check status: ${SSH_CMD} 'systemctl status picoclaw'" +echo " View logs: ${SSH_CMD} 'tail -f /opt/picoclaw/logs/picoclaw.log'" +echo " Health check: curl http://${HOST}:18790/health" +echo "" diff --git a/deploy/hostinger/docker-compose.production.yml b/deploy/hostinger/docker-compose.production.yml new file mode 100644 index 000000000..3109c3653 --- /dev/null +++ b/deploy/hostinger/docker-compose.production.yml @@ -0,0 +1,48 @@ +# ============================================================ +# PicoClaw - Production Docker Compose for Hostinger VPS +# ============================================================ +# Usage: +# docker compose up -d picoclaw-gateway +# docker compose logs -f picoclaw-gateway +# docker compose down +# ============================================================ + +services: + picoclaw-gateway: + build: + context: ./src + dockerfile: Dockerfile + container_name: picoclaw-gateway + restart: unless-stopped + ports: + - "18790:18790" + volumes: + # Configuration (read-only) + - /opt/picoclaw/config/config.json:/root/.picoclaw/config.json:ro + # Persistent workspace (sessions, memory, logs) + - picoclaw-workspace:/root/.picoclaw/workspace + env_file: + - /opt/picoclaw/config/.env + environment: + - TZ=${TZ:-America/Sao_Paulo} + healthcheck: + test: ["CMD", "wget", "-q", "--spider", "http://localhost:18790/health"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 10s + logging: + driver: json-file + options: + max-size: "10m" + max-file: "5" + deploy: + resources: + limits: + memory: 128M + reservations: + memory: 32M + +volumes: + picoclaw-workspace: + driver: local diff --git a/deploy/hostinger/picoclaw.service b/deploy/hostinger/picoclaw.service new file mode 100644 index 000000000..5f626450d --- /dev/null +++ b/deploy/hostinger/picoclaw.service @@ -0,0 +1,51 @@ +# ============================================================ +# PicoClaw - systemd Service File +# ============================================================ +# Install: sudo cp picoclaw.service /etc/systemd/system/ +# sudo systemctl daemon-reload +# sudo systemctl enable picoclaw +# sudo systemctl start picoclaw +# ============================================================ + +[Unit] +Description=PicoClaw AI Assistant Gateway +Documentation=https://github.com/agenciaspace/picoclaw +After=network-online.target +Wants=network-online.target +StartLimitIntervalSec=300 +StartLimitBurst=5 + +[Service] +Type=simple +User=picoclaw +Group=picoclaw +WorkingDirectory=/opt/picoclaw +ExecStart=/opt/picoclaw/bin/picoclaw gateway +ExecReload=/bin/kill -HUP $MAINPID +Restart=on-failure +RestartSec=10 +TimeoutStopSec=30 + +# Logging +StandardOutput=append:/opt/picoclaw/logs/picoclaw.log +StandardError=append:/opt/picoclaw/logs/picoclaw-error.log + +# Environment +EnvironmentFile=-/opt/picoclaw/config/.env +Environment=HOME=/opt/picoclaw +Environment=PICOCLAW_CONFIG=/opt/picoclaw/config/config.json + +# Security hardening +NoNewPrivileges=yes +ProtectSystem=strict +ProtectHome=yes +ReadWritePaths=/opt/picoclaw +PrivateTmp=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes +ProtectControlGroups=yes +RestrictSUIDSGID=yes +RestrictNamespaces=yes + +[Install] +WantedBy=multi-user.target diff --git a/deploy/hostinger/rollback.sh b/deploy/hostinger/rollback.sh new file mode 100755 index 000000000..8c4da3b70 --- /dev/null +++ b/deploy/hostinger/rollback.sh @@ -0,0 +1,109 @@ +#!/usr/bin/env bash +# ============================================================ +# PicoClaw - Rollback to Previous Version +# ============================================================ +# Usage: +# ./deploy/hostinger/rollback.sh -h YOUR_VPS_IP +# ============================================================ + +set -euo pipefail + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +log() { echo -e "${GREEN}[ROLLBACK]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +error() { echo -e "${RED}[ERROR]${NC} $*"; exit 1; } + +# Configuration +HOST="${HOSTINGER_HOST:-}" +USER="${HOSTINGER_USER:-root}" +SSH_KEY="${HOSTINGER_SSH_KEY:-${HOME}/.ssh/id_rsa}" +SSH_PORT="${HOSTINGER_SSH_PORT:-22}" +METHOD="${HOSTINGER_DEPLOY_METHOD:-docker}" + +while [[ $# -gt 0 ]]; do + case $1 in + -h|--host) HOST="$2"; shift 2 ;; + -u|--user) USER="$2"; shift 2 ;; + -k|--key) SSH_KEY="$2"; shift 2 ;; + -m|--method) METHOD="$2"; shift 2 ;; + -p|--port) SSH_PORT="$2"; shift 2 ;; + *) error "Unknown option: $1" ;; + esac +done + +[ -z "${HOST}" ] && error "Host required. Use -h/--host or set HOSTINGER_HOST env var." + +SSH_OPTS="-o StrictHostKeyChecking=accept-new -o ConnectTimeout=10 -p ${SSH_PORT} -i ${SSH_KEY}" +SSH_CMD="ssh ${SSH_OPTS} ${USER}@${HOST}" + +log "Rolling back PicoClaw on ${HOST}..." + +if [ "${METHOD}" = "docker" ]; then + ${SSH_CMD} <<'REMOTEOF' +set -e +cd /opt/picoclaw + +# List available images +echo "Available images:" +docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.CreatedAt}}" | grep picoclaw || true + +# Docker rollback: restart with previous image +echo "[REMOTE] Stopping current container..." +docker compose down --timeout 30 + +echo "[REMOTE] Starting previous version..." +# The previous image should still be cached +docker compose up -d picoclaw-gateway + +sleep 5 +if docker compose ps picoclaw-gateway | grep -q "Up"; then + echo "[REMOTE] Rollback successful!" + docker compose ps +else + echo "[REMOTE] Rollback failed!" + docker compose logs --tail=20 picoclaw-gateway + exit 1 +fi +REMOTEOF +else + ${SSH_CMD} <<'REMOTEOF' +set -e +BACKUP_DIR="/opt/picoclaw/backups" + +# Find latest backup +LATEST_BACKUP=$(ls -t ${BACKUP_DIR}/picoclaw-*.bak 2>/dev/null | head -1) + +if [ -z "${LATEST_BACKUP}" ]; then + echo "[REMOTE] No backup found to rollback to!" + exit 1 +fi + +echo "[REMOTE] Rolling back to: ${LATEST_BACKUP}" + +# Stop service +systemctl stop picoclaw + +# Restore binary +cp "${LATEST_BACKUP}" /opt/picoclaw/bin/picoclaw +chmod +x /opt/picoclaw/bin/picoclaw + +# Start service +systemctl start picoclaw + +sleep 3 +if systemctl is-active --quiet picoclaw; then + echo "[REMOTE] Rollback successful!" + systemctl status picoclaw --no-pager +else + echo "[REMOTE] Rollback failed!" + journalctl -u picoclaw --no-pager -n 20 + exit 1 +fi +REMOTEOF +fi + +log "Rollback complete!" diff --git a/deploy/hostinger/setup-server.sh b/deploy/hostinger/setup-server.sh new file mode 100755 index 000000000..999fcc43e --- /dev/null +++ b/deploy/hostinger/setup-server.sh @@ -0,0 +1,304 @@ +#!/usr/bin/env bash +# ============================================================ +# PicoClaw - Hostinger VPS Initial Setup +# ============================================================ +# Run this script ONCE on a fresh Hostinger VPS to prepare it +# for PicoClaw deployment. +# +# Usage: +# ssh root@YOUR_VPS_IP 'bash -s' < deploy/hostinger/setup-server.sh +# +# Or copy and run directly on the server: +# scp deploy/hostinger/setup-server.sh root@YOUR_VPS_IP:/tmp/ +# ssh root@YOUR_VPS_IP 'bash /tmp/setup-server.sh' +# ============================================================ + +set -euo pipefail + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +log() { echo -e "${GREEN}[SETUP]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +error() { echo -e "${RED}[ERROR]${NC} $*"; exit 1; } + +# ── Configuration ────────────────────────────────────── +PICOCLAW_USER="picoclaw" +PICOCLAW_HOME="/opt/picoclaw" +DEPLOY_METHOD="${1:-docker}" # "docker" or "binary" + +log "PicoClaw Hostinger VPS Setup" +log "Deploy method: ${DEPLOY_METHOD}" +echo "" + +# ── 1. System Update ────────────────────────────────── +log "Updating system packages..." +if command -v apt-get &>/dev/null; then + export DEBIAN_FRONTEND=noninteractive + apt-get update -qq + apt-get upgrade -y -qq + apt-get install -y -qq curl wget git ufw fail2ban unzip jq +elif command -v yum &>/dev/null; then + yum update -y -q + yum install -y -q curl wget git firewalld fail2ban unzip jq +elif command -v dnf &>/dev/null; then + dnf update -y -q + dnf install -y -q curl wget git firewalld fail2ban unzip jq +else + error "Unsupported package manager. This script supports apt, yum, and dnf." +fi + +# ── 2. Create dedicated user ───────────────────────── +if ! id "${PICOCLAW_USER}" &>/dev/null; then + log "Creating dedicated user: ${PICOCLAW_USER}" + useradd --system --create-home --home-dir "${PICOCLAW_HOME}" \ + --shell /bin/bash "${PICOCLAW_USER}" +else + log "User ${PICOCLAW_USER} already exists" +fi + +# ── 3. Create directory structure ───────────────────── +log "Creating directory structure..." +mkdir -p "${PICOCLAW_HOME}"/{bin,config,workspace,logs,backups} +chown -R "${PICOCLAW_USER}:${PICOCLAW_USER}" "${PICOCLAW_HOME}" + +# ── 4. Install Docker (if docker method) ───────────── +if [ "${DEPLOY_METHOD}" = "docker" ]; then + if ! command -v docker &>/dev/null; then + log "Installing Docker..." + curl -fsSL https://get.docker.com | sh + systemctl enable docker + systemctl start docker + usermod -aG docker "${PICOCLAW_USER}" + log "Docker installed successfully" + else + log "Docker already installed: $(docker --version)" + fi + + # Install Docker Compose plugin if not present + if ! docker compose version &>/dev/null; then + log "Installing Docker Compose plugin..." + apt-get install -y -qq docker-compose-plugin 2>/dev/null || \ + yum install -y -q docker-compose-plugin 2>/dev/null || \ + dnf install -y -q docker-compose-plugin 2>/dev/null || { + COMPOSE_VERSION=$(curl -s https://api.github.com/repos/docker/compose/releases/latest | jq -r .tag_name) + curl -fsSL "https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m)" \ + -o /usr/local/bin/docker-compose + chmod +x /usr/local/bin/docker-compose + } + fi + log "Docker Compose ready: $(docker compose version 2>/dev/null || docker-compose --version 2>/dev/null)" +fi + +# ── 5. Install Go (if binary method) ───────────────── +if [ "${DEPLOY_METHOD}" = "binary" ]; then + if ! command -v go &>/dev/null; then + log "Installing Go..." + GO_VERSION="1.23.4" + ARCH=$(uname -m) + case "${ARCH}" in + x86_64) GO_ARCH="amd64" ;; + aarch64) GO_ARCH="arm64" ;; + *) GO_ARCH="${ARCH}" ;; + esac + curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-${GO_ARCH}.tar.gz" -o /tmp/go.tar.gz + rm -rf /usr/local/go + tar -C /usr/local -xzf /tmp/go.tar.gz + rm /tmp/go.tar.gz + echo 'export PATH=$PATH:/usr/local/go/bin' > /etc/profile.d/go.sh + export PATH=$PATH:/usr/local/go/bin + log "Go installed: $(go version)" + else + log "Go already installed: $(go version)" + fi + + # Install make if not present + if ! command -v make &>/dev/null; then + apt-get install -y -qq make 2>/dev/null || \ + yum install -y -q make 2>/dev/null || \ + dnf install -y -q make 2>/dev/null + fi +fi + +# ── 6. Configure Firewall ──────────────────────────── +log "Configuring firewall..." +if command -v ufw &>/dev/null; then + ufw --force reset + ufw default deny incoming + ufw default allow outgoing + ufw allow ssh + ufw allow 18790/tcp comment "PicoClaw Gateway" + # Uncomment if you need webhook ports: + # ufw allow 18791/tcp comment "PicoClaw Line Webhook" + ufw --force enable + log "UFW firewall configured" +elif command -v firewall-cmd &>/dev/null; then + systemctl enable firewalld + systemctl start firewalld + firewall-cmd --permanent --add-service=ssh + firewall-cmd --permanent --add-port=18790/tcp + firewall-cmd --reload + log "firewalld configured" +fi + +# ── 7. Configure fail2ban ──────────────────────────── +log "Configuring fail2ban..." +systemctl enable fail2ban +systemctl start fail2ban + +# ── 8. Create environment template ─────────────────── +if [ ! -f "${PICOCLAW_HOME}/config/.env" ]; then + log "Creating environment template..." + cat > "${PICOCLAW_HOME}/config/.env" <<'ENVEOF' +# ============================================================ +# PicoClaw Production Environment +# ============================================================ +# Edit this file with your actual API keys and tokens. +# NEVER commit this file to version control. +# ============================================================ + +# ── LLM Provider (uncomment one) ────────────────────── +# ANTHROPIC_API_KEY=sk-ant-xxx +# OPENAI_API_KEY=sk-xxx +# OPENROUTER_API_KEY=sk-or-v1-xxx +# GEMINI_API_KEY=xxx + +# ── Chat Channel ───────────────────────────────────── +# TELEGRAM_BOT_TOKEN=123456:ABC... +# DISCORD_BOT_TOKEN=xxx + +# ── Web Search (optional) ──────────────────────────── +# BRAVE_SEARCH_API_KEY=BSA... + +# ── Timezone ───────────────────────────────────────── +TZ=America/Sao_Paulo +ENVEOF + chown "${PICOCLAW_USER}:${PICOCLAW_USER}" "${PICOCLAW_HOME}/config/.env" + chmod 600 "${PICOCLAW_HOME}/config/.env" +fi + +# ── 9. Create config.json template ─────────────────── +if [ ! -f "${PICOCLAW_HOME}/config/config.json" ]; then + log "Creating config.json template..." + cat > "${PICOCLAW_HOME}/config/config.json" <<'JSONEOF' +{ + "agents": { + "defaults": { + "workspace": "/opt/picoclaw/workspace", + "restrict_to_workspace": true, + "model": "claude-sonnet-4-20250514", + "max_tokens": 8192, + "temperature": 0.7, + "max_tool_iterations": 20 + } + }, + "channels": { + "telegram": { + "enabled": false, + "token": "", + "allow_from": [] + } + }, + "providers": { + "anthropic": { + "api_key": "", + "api_base": "" + } + }, + "heartbeat": { + "enabled": true, + "interval": 30 + }, + "gateway": { + "host": "0.0.0.0", + "port": 18790 + } +} +JSONEOF + chown "${PICOCLAW_USER}:${PICOCLAW_USER}" "${PICOCLAW_HOME}/config/config.json" + chmod 600 "${PICOCLAW_HOME}/config/config.json" +fi + +# ── 10. Install systemd service (for binary method) ── +if [ "${DEPLOY_METHOD}" = "binary" ]; then + log "Installing systemd service..." + cat > /etc/systemd/system/picoclaw.service < /etc/logrotate.d/picoclaw </dev/null || true + endscript +} +LOGEOF + +# ── Done ───────────────────────────────────────────── +echo "" +log "==========================================" +log " Server setup complete!" +log "==========================================" +echo "" +log "Next steps:" +echo " 1. Edit API keys: nano ${PICOCLAW_HOME}/config/.env" +echo " 2. Edit config: nano ${PICOCLAW_HOME}/config/config.json" +if [ "${DEPLOY_METHOD}" = "docker" ]; then + echo " 3. Deploy: Run 'make deploy-hostinger' from your local machine" +else + echo " 3. Deploy: Run 'make deploy-hostinger' from your local machine" + echo " 4. Start service: systemctl start picoclaw" + echo " 5. Check status: systemctl status picoclaw" + echo " 6. View logs: tail -f ${PICOCLAW_HOME}/logs/picoclaw.log" +fi +echo "" +log "Firewall ports open: SSH (22), PicoClaw Gateway (18790)" +echo "" diff --git a/deploy/hostinger/status.sh b/deploy/hostinger/status.sh new file mode 100755 index 000000000..095f152a4 --- /dev/null +++ b/deploy/hostinger/status.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash +# ============================================================ +# PicoClaw - Check Remote Server Status +# ============================================================ +# Usage: +# ./deploy/hostinger/status.sh -h YOUR_VPS_IP +# ============================================================ + +set -euo pipefail + +GREEN='\033[0;32m' +RED='\033[0;31m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +log() { echo -e "${GREEN}[STATUS]${NC} $*"; } +info() { echo -e "${BLUE}[INFO]${NC} $*"; } + +HOST="${HOSTINGER_HOST:-}" +USER="${HOSTINGER_USER:-root}" +SSH_KEY="${HOSTINGER_SSH_KEY:-${HOME}/.ssh/id_rsa}" +SSH_PORT="${HOSTINGER_SSH_PORT:-22}" +METHOD="${HOSTINGER_DEPLOY_METHOD:-docker}" + +while [[ $# -gt 0 ]]; do + case $1 in + -h|--host) HOST="$2"; shift 2 ;; + -u|--user) USER="$2"; shift 2 ;; + -k|--key) SSH_KEY="$2"; shift 2 ;; + -m|--method) METHOD="$2"; shift 2 ;; + -p|--port) SSH_PORT="$2"; shift 2 ;; + *) shift ;; + esac +done + +[ -z "${HOST}" ] && { echo "Usage: $0 -h HOST"; exit 1; } + +SSH_OPTS="-o StrictHostKeyChecking=accept-new -o ConnectTimeout=10 -p ${SSH_PORT} -i ${SSH_KEY}" +SSH_CMD="ssh ${SSH_OPTS} ${USER}@${HOST}" + +log "Checking PicoClaw status on ${HOST}..." +echo "" + +${SSH_CMD} </dev/null; then + echo "Method: Docker" + docker compose -f /opt/picoclaw/docker-compose.yml ps 2>/dev/null || echo "Container: not running" + echo "" + echo "── Recent Logs ─────────────────────────────" + docker compose -f /opt/picoclaw/docker-compose.yml logs --tail=10 picoclaw-gateway 2>/dev/null || true + else + echo "Docker not installed" + fi +else + echo "Method: Binary (systemd)" + if systemctl is-active --quiet picoclaw 2>/dev/null; then + echo "Status: RUNNING" + version=\$(/opt/picoclaw/bin/picoclaw version 2>/dev/null || echo "unknown") + echo "Version: \${version}" + else + echo "Status: STOPPED" + fi + systemctl status picoclaw --no-pager -l 2>/dev/null || true + echo "" + echo "── Recent Logs ─────────────────────────────" + tail -10 /opt/picoclaw/logs/picoclaw.log 2>/dev/null || echo "No logs found" +fi + +echo "" +echo "── Health Check ────────────────────────────" +if curl -sf http://localhost:18790/health > /dev/null 2>&1; then + echo "Gateway: HEALTHY (port 18790)" +else + echo "Gateway: UNREACHABLE" +fi + +echo "" +echo "── Firewall ────────────────────────────────" +if command -v ufw &>/dev/null; then + ufw status 2>/dev/null | head -10 +elif command -v firewall-cmd &>/dev/null; then + firewall-cmd --list-all 2>/dev/null | head -10 +fi +REMOTEOF + +echo "" +log "Status check complete"