From 7e95b2f52e4475a8f9c95f7ee1c20d9fba945d1e Mon Sep 17 00:00:00 2001 From: RED SOUL Date: Sun, 22 Feb 2026 15:12:46 +0700 Subject: [PATCH] Initial commit: OpenClaw CalDAV Installer Wizard --- README.md | 102 +++++++++++++ install.sh | 434 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 536 insertions(+) create mode 100644 README.md create mode 100755 install.sh diff --git a/README.md b/README.md new file mode 100644 index 0000000..41928e4 --- /dev/null +++ b/README.md @@ -0,0 +1,102 @@ +# 🦞 OpenClaw CalDAV Installer + +One-command deployment of **Radicale CalDAV/CardDAV** on Ubuntu/Debian, pre-configured for [OpenClaw](https://openclaw.dev) integration. + +## What It Does + +- Installs **Docker**, **Nginx**, **Certbot**, and **apache2-utils** (if not present) +- Deploys **Radicale** via Docker Compose with bcrypt authentication +- Configures **Nginx reverse proxy** with SSL termination +- Obtains a free **Let's Encrypt SSL certificate** +- Binds to `127.0.0.1` for secure OpenClaw localhost access + +## Prerequisites + +| Requirement | Details | +|---|---| +| **OS** | Ubuntu 20.04+ / Debian 11+ | +| **Access** | Root (sudo) | +| **Ports** | 80 and 443 open to the internet | +| **DNS** | A record pointing your domain to this server's IP | +| **RAM** | 512 MB minimum | + +## Quick Start + +```bash +# Download the installer +git clone https://github.com/openclaw/caldav-installer.git +cd caldav-installer + +# Run the wizard +sudo bash install.sh +``` + +The wizard will ask you for: +1. **Domain name** (e.g. `cal.example.com`) +2. **Email** (for Let's Encrypt SSL) +3. **CalDAV username & password** +4. **Port** (default: 5232) +5. **Install directory** (default: `~/caldav-docker`) + +## Client Setup + +### Apple Calendar (macOS / iOS) +1. Settings → Accounts → Add Account → Other → CalDAV +2. Server: `https://YOUR_DOMAIN` +3. Username & password as configured + +### Thunderbird / GNOME Calendar +- URL: `https://YOUR_DOMAIN/USERNAME/calendar.ics/` + +## OpenClaw Integration + +```bash +openclaw config set tools.calendar.provider "caldav" +openclaw config set tools.calendar.caldav.url "https://YOUR_DOMAIN" +openclaw config set tools.calendar.caldav.username "YOUR_USER" +openclaw config set tools.calendar.caldav.password "YOUR_PASSWORD" +``` + +OpenClaw connects internally via `http://localhost:5232`. + +## Architecture + +``` +Internet → Nginx (443/SSL) → 127.0.0.1:5232 → Radicale Container + ↕ +OpenClaw Gateway → localhost:5232 ──────────> /data/collections +``` + +## Management + +```bash +cd ~/caldav-docker + +# Start / Stop / Restart +docker compose up -d +docker compose down +docker compose restart + +# Add a new user +sudo htpasswd -B ./config/users newuser +docker compose restart + +# View logs +docker compose logs -f + +# Check status +docker compose ps +``` + +## Troubleshooting + +| Issue | Fix | +|---|---| +| SSL cert failed | Verify DNS A record; check ports 80/443 are open | +| 401 Unauthorized | Check `config/users` file has the correct user entry | +| Container won't start | Check logs: `docker compose logs radicale` | +| Can't sync calendar | Ensure client URL ends with `/username/calendar.ics/` | + +## License + +MIT — use freely, contribute back. 🦞 diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..59748f7 --- /dev/null +++ b/install.sh @@ -0,0 +1,434 @@ +#!/usr/bin/env bash +# ============================================================================ +# OpenClaw CalDAV (Radicale) Installer +# Deploys Radicale via Docker with Nginx + Let's Encrypt SSL +# Compatible with OpenClaw gateway via localhost +# +# Usage: sudo bash install.sh +# GitHub: https://github.com/openclaw/caldav-installer +# ============================================================================ + +set -euo pipefail + +# ── Colors ─────────────────────────────────────────────────────────────────── +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +BOLD='\033[1m' +NC='\033[0m' + +# ── Helpers ────────────────────────────────────────────────────────────────── +info() { echo -e "${CYAN}[INFO]${NC} $*"; } +success() { echo -e "${GREEN}[ OK]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +fail() { echo -e "${RED}[FAIL]${NC} $*"; exit 1; } + +banner() { + echo "" + echo -e "${BOLD}${CYAN}" + echo " ┌──────────────────────────────────────────────┐" + echo " │ 🦞 OpenClaw CalDAV Installer 🦞 │" + echo " │ Radicale + Docker + Nginx + SSL │" + echo " └──────────────────────────────────────────────┘" + echo -e "${NC}" + echo "" +} + +# ── Pre-flight checks ─────────────────────────────────────────────────────── +preflight() { + if [[ $EUID -ne 0 ]]; then + fail "This script must be run as root (sudo bash install.sh)" + fi + + if ! grep -qiE 'ubuntu|debian' /etc/os-release 2>/dev/null; then + fail "This installer supports Ubuntu/Debian only." + fi + + if ss -tlnp | grep -qE ':80\b' 2>/dev/null; then + warn "Port 80 is already in use. This is OK if Nginx is already running." + fi + + if ss -tlnp | grep -qE ':443\b' 2>/dev/null; then + warn "Port 443 is already in use. This is OK if Nginx is already running." + fi + + success "Pre-flight checks passed." +} + +# ── Interactive prompts ────────────────────────────────────────────────────── +gather_input() { + echo -e "${BOLD}Step 1: Configuration${NC}" + echo "" + + # Domain + while true; do + read -rp " Enter your CalDAV domain (e.g. cal.example.com): " CAL_DOMAIN + if [[ -z "$CAL_DOMAIN" ]]; then + warn "Domain cannot be empty." + elif [[ "$CAL_DOMAIN" == *" "* ]]; then + warn "Domain cannot contain spaces." + else + break + fi + done + + # Email for SSL + while true; do + read -rp " Enter your email (for Let's Encrypt SSL certificate): " CAL_EMAIL + if [[ -z "$CAL_EMAIL" ]]; then + warn "Email cannot be empty." + elif [[ "$CAL_EMAIL" != *"@"* ]]; then + warn "Please enter a valid email address." + else + break + fi + done + + # CalDAV username + while true; do + read -rp " CalDAV username (e.g. openclaw): " CAL_USER + if [[ -z "$CAL_USER" ]]; then + warn "Username cannot be empty." + else + break + fi + done + + # CalDAV password + while true; do + read -rsp " CalDAV password: " CAL_PASS + echo "" + if [[ -z "$CAL_PASS" ]]; then + warn "Password cannot be empty." + else + read -rsp " Confirm password: " CAL_PASS_CONFIRM + echo "" + if [[ "$CAL_PASS" != "$CAL_PASS_CONFIRM" ]]; then + warn "Passwords do not match." + else + break + fi + fi + done + + # Internal port + read -rp " Internal proxy port [5232]: " CAL_PORT + CAL_PORT=${CAL_PORT:-5232} + + # Install directory + read -rp " Installation directory [~/caldav-docker]: " INSTALL_DIR + INSTALL_DIR=${INSTALL_DIR:-"$HOME/caldav-docker"} + # Expand tilde + INSTALL_DIR="${INSTALL_DIR/#\~/$HOME}" + + echo "" + echo -e "${BOLD} Summary:${NC}" + echo " Domain: https://${CAL_DOMAIN}" + echo " Email: ${CAL_EMAIL}" + echo " Username: ${CAL_USER}" + echo " Port: 127.0.0.1:${CAL_PORT} → :5232" + echo " Directory: ${INSTALL_DIR}" + echo "" + read -rp " Proceed? (Y/n): " confirm + [[ "$confirm" =~ ^[Nn]$ ]] && { info "Aborted."; exit 0; } +} + +# ── Install dependencies ──────────────────────────────────────────────────── +install_deps() { + echo "" + echo -e "${BOLD}Step 2: Installing dependencies${NC}" + echo "" + + info "Updating package index..." + apt-get update -qq + + # Docker + if command -v docker &>/dev/null; then + success "Docker already installed: $(docker --version)" + else + info "Installing Docker..." + apt-get install -y -qq ca-certificates curl gnupg lsb-release + install -m 0755 -d /etc/apt/keyrings + curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg 2>/dev/null || true + chmod a+r /etc/apt/keyrings/docker.gpg + echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ + $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ + tee /etc/apt/sources.list.d/docker.list > /dev/null + apt-get update -qq + apt-get install -y -qq docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + success "Docker installed." + fi + + # Docker Compose + if docker compose version &>/dev/null; then + COMPOSE_CMD="docker compose" + success "Docker Compose (v2 plugin) available." + elif command -v docker-compose &>/dev/null; then + COMPOSE_CMD="docker-compose" + success "Docker Compose (v1 standalone) available." + else + info "Installing Docker Compose plugin..." + apt-get install -y -qq docker-compose-plugin + COMPOSE_CMD="docker compose" + success "Docker Compose installed." + fi + + # Nginx + if command -v nginx &>/dev/null; then + success "Nginx already installed." + else + info "Installing Nginx..." + apt-get install -y -qq nginx + systemctl enable nginx + systemctl start nginx + success "Nginx installed and started." + fi + + # Certbot + if command -v certbot &>/dev/null; then + success "Certbot already installed." + else + info "Installing Certbot..." + apt-get install -y -qq certbot python3-certbot-nginx + success "Certbot installed." + fi + + # apache2-utils (for htpasswd / bcrypt) + if command -v htpasswd &>/dev/null; then + success "htpasswd (apache2-utils) already installed." + else + info "Installing apache2-utils for bcrypt password hashing..." + apt-get install -y -qq apache2-utils + success "apache2-utils installed." + fi + + # UFW (optional) + if command -v ufw &>/dev/null; then + info "Configuring firewall (UFW)..." + ufw allow 'Nginx Full' >/dev/null 2>&1 || true + ufw allow OpenSSH >/dev/null 2>&1 || true + success "Firewall rules updated." + fi + + success "All dependencies ready." +} + +# ── Create Docker stack ────────────────────────────────────────────────────── +create_docker_stack() { + echo "" + echo -e "${BOLD}Step 3: Creating Docker stack${NC}" + echo "" + + mkdir -p "${INSTALL_DIR}/config" + mkdir -p "${INSTALL_DIR}/data" + + # Docker Compose + cat > "${INSTALL_DIR}/docker-compose.yml" < "${INSTALL_DIR}/config/config" < "${INSTALL_DIR}/.env" < "${NGINX_CONF}" </dev/null 2>&1; then + success "Radicale is responding on port ${CAL_PORT}!" + break + fi + sleep 5 + done + + # Show container status + echo "" + $COMPOSE_CMD ps + echo "" +} + +# ── Print summary ─────────────────────────────────────────────────────────── +print_summary() { + echo "" + echo -e "${BOLD}${GREEN}" + echo " ┌──────────────────────────────────────────────┐" + echo " │ ✅ Installation Complete! ✅ │" + echo " └──────────────────────────────────────────────┘" + echo -e "${NC}" + echo "" + echo -e "${BOLD} Access your CalDAV server:${NC}" + echo " 🌐 https://${CAL_DOMAIN}" + echo "" + echo -e "${BOLD} Internal (localhost) access:${NC}" + echo " 📡 http://127.0.0.1:${CAL_PORT}" + echo "" + echo -e "${BOLD} Credentials:${NC}" + echo " 👤 Username: ${CAL_USER}" + echo " 📄 Env file: ${INSTALL_DIR}/.env" + echo "" + echo -e "${BOLD} Management commands:${NC}" + echo " Start: cd ${INSTALL_DIR} && ${COMPOSE_CMD} up -d" + echo " Stop: cd ${INSTALL_DIR} && ${COMPOSE_CMD} down" + echo " Logs: cd ${INSTALL_DIR} && ${COMPOSE_CMD} logs -f" + echo " Status: cd ${INSTALL_DIR} && ${COMPOSE_CMD} ps" + echo "" + echo -e "${BOLD} Add a new CalDAV user:${NC}" + echo " sudo htpasswd -B ${INSTALL_DIR}/config/users " + echo " cd ${INSTALL_DIR} && ${COMPOSE_CMD} restart" + echo "" + echo -e "${BOLD}${CYAN} ── Client Setup ──${NC}" + echo "" + echo " Apple Calendar (macOS / iOS):" + echo " Settings → Accounts → Add Account → Other → CalDAV" + echo " Server: https://${CAL_DOMAIN}" + echo " Username: ${CAL_USER}" + echo " Password: (the password you entered)" + echo "" + echo " Thunderbird / GNOME Calendar:" + echo " Server URL: https://${CAL_DOMAIN}/${CAL_USER}/calendar.ics/" + echo "" + echo -e "${BOLD}${CYAN} ── OpenClaw Integration ──${NC}" + echo "" + echo " Configure OpenClaw to sync calendars:" + echo -e " ${YELLOW}openclaw config set tools.calendar.provider \"caldav\"${NC}" + echo -e " ${YELLOW}openclaw config set tools.calendar.caldav.url \"https://${CAL_DOMAIN}\"${NC}" + echo -e " ${YELLOW}openclaw config set tools.calendar.caldav.username \"${CAL_USER}\"${NC}" + echo -e " ${YELLOW}openclaw config set tools.calendar.caldav.password \"\"${NC}" + echo "" + echo " OpenClaw will connect internally via localhost:${CAL_PORT} 🦞" + echo "" +} + +# ── Main ───────────────────────────────────────────────────────────────────── +main() { + banner + preflight + gather_input + install_deps + create_docker_stack + configure_nginx + obtain_ssl + start_stack + print_summary +} + +main "$@"