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
This commit is contained in:
Claude 2026-02-16 21:52:12 +00:00
parent 13e4028d42
commit 42a2918665
No known key found for this signature in database
8 changed files with 1122 additions and 5 deletions

View file

@ -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 # Build variables
BINARY_NAME=picoclaw BINARY_NAME=picoclaw
@ -148,6 +148,44 @@ check: deps fmt vet test
run: build run: build
@$(BUILD_DIR)/$(BINARY_NAME) $(ARGS) @$(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: Show this help message
help: help:
@echo "picoclaw Makefile" @echo "picoclaw Makefile"
@ -162,12 +200,21 @@ help:
@echo " make build # Build for current platform" @echo " make build # Build for current platform"
@echo " make install # Install to ~/.local/bin" @echo " make install # Install to ~/.local/bin"
@echo " make uninstall # Remove from /usr/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 ""
@echo "Environment Variables:" @echo "Environment Variables:"
@echo " INSTALL_PREFIX # Installation prefix (default: ~/.local)" @echo " INSTALL_PREFIX # Installation prefix (default: ~/.local)"
@echo " WORKSPACE_DIR # Workspace directory (default: ~/.picoclaw/workspace)" @echo " WORKSPACE_DIR # Workspace directory (default: ~/.picoclaw/workspace)"
@echo " VERSION # Version string (default: git describe)" @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 ""
@echo "Current Configuration:" @echo "Current Configuration:"
@echo " Platform: $(PLATFORM)/$(ARCH)" @echo " Platform: $(PLATFORM)/$(ARCH)"

205
deploy/hostinger/DEPLOY.md Normal file
View file

@ -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
```

255
deploy/hostinger/deploy.sh Executable file
View file

@ -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} <<REMOTEOF
set -e
if [ ! -d "${REMOTE_DIR}/workspace/memory" ]; then
echo "[REMOTE] Initializing workspace..."
sudo -u picoclaw ${REMOTE_DIR}/bin/picoclaw onboard
fi
REMOTEOF
fi
# Restart service
log "Restarting PicoClaw service..."
${SSH_CMD} <<'REMOTEOF'
set -e
echo "[REMOTE] Restarting picoclaw service..."
systemctl restart picoclaw
sleep 3
if systemctl is-active --quiet picoclaw; then
echo "[REMOTE] PicoClaw is running!"
systemctl status picoclaw --no-pager
else
echo "[REMOTE] ERROR: Service failed to start"
journalctl -u picoclaw --no-pager -n 30
exit 1
fi
# Health check
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)"
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 ""

View file

@ -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

View file

@ -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

109
deploy/hostinger/rollback.sh Executable file
View file

@ -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!"

304
deploy/hostinger/setup-server.sh Executable file
View file

@ -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 <<SVCEOF
[Unit]
Description=PicoClaw AI Assistant Gateway
After=network-online.target
Wants=network-online.target
StartLimitIntervalSec=300
StartLimitBurst=5
[Service]
Type=simple
User=${PICOCLAW_USER}
Group=${PICOCLAW_USER}
WorkingDirectory=${PICOCLAW_HOME}
ExecStart=${PICOCLAW_HOME}/bin/picoclaw gateway
Restart=on-failure
RestartSec=10
StandardOutput=append:${PICOCLAW_HOME}/logs/picoclaw.log
StandardError=append:${PICOCLAW_HOME}/logs/picoclaw-error.log
# Environment
EnvironmentFile=-${PICOCLAW_HOME}/config/.env
Environment=HOME=${PICOCLAW_HOME}
Environment=PICOCLAW_CONFIG=${PICOCLAW_HOME}/config/config.json
# Security hardening
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=${PICOCLAW_HOME}
PrivateTmp=yes
ProtectKernelModules=yes
ProtectKernelTunables=yes
[Install]
WantedBy=multi-user.target
SVCEOF
systemctl daemon-reload
systemctl enable picoclaw
log "systemd service installed and enabled"
fi
# ── 11. Create logrotate config ──────────────────────
cat > /etc/logrotate.d/picoclaw <<LOGEOF
${PICOCLAW_HOME}/logs/*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 0640 ${PICOCLAW_USER} ${PICOCLAW_USER}
postrotate
systemctl reload picoclaw 2>/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 ""

98
deploy/hostinger/status.sh Executable file
View file

@ -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} <<REMOTEOF
echo "── System ──────────────────────────────────"
echo "Hostname: \$(hostname)"
echo "Uptime: \$(uptime -p)"
echo "Memory: \$(free -h | awk '/Mem:/ {print \$3 "/" \$2}')"
echo "Disk: \$(df -h / | awk 'NR==2 {print \$3 "/" \$2 " (" \$5 " used)"}')"
echo "Load: \$(cat /proc/loadavg | awk '{print \$1, \$2, \$3}')"
echo ""
echo "── PicoClaw ────────────────────────────────"
if [ "${METHOD}" = "docker" ]; then
if command -v docker &>/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"