Skip to content

Self-hosted Happy Server 实现远程手机 App 或网页端控制服务器 AI Agent 自主编程与实验运行

客户端配置

手机端

按照官方仓库说明 https://github.com/slopus/happy 下载手机客户端。

进入 APP 之后在右上角点击服务器配置,输入自定义服务器 https://happy.iomics.pro

其余内容与官方仓库配置相同。

网页端

访问 https://app.happy.engineering/server,配置同上。

CLI 执行端

按照官方仓库说明 https://github.com/slopus/happy-cli 配置即可,但注意,需要配置环境变量 HAPPY_SERVER_URL 为自定义服务器

export HAPPY_SERVER_URL="https://happy.iomics.pro"

服务器搭建

docker-compress 负责了 happy-server 的主体与数据库缓存等组件的编排,nginx 复用了现有系统,集中进行了管理。需要注意的是 happy-server 刚需 https 配置。本篇仅记录关键步骤。

happy-server 官网给的 docker-compose 是跑不起来的,缺失了对 minio 的配置以及数据库的初始化。这部分借助 AI 阅读了源码大概花了 2-3 个小时才摸索成功。

docker-compose

# docker-compose.yml

version: '3.8'
services:
  happy-server:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: happy-server
    ports:
      - "3005:3005"
    restart: unless-stopped
    environment:
      - JWT_SECRET="<YOUR-SECRET>"
      - JWT_EXPIRES_IN="7d"
      - NODE_ENV=production
      - DATABASE_URL=postgresql://postgres:<YOUR-SECRET>@postgres:5432/happy-server
      - REDIS_URL=redis://redis:6379
      - SEED=<YOUR-SECRET>
      - HOST=0.0.0.0
      - PORT=3005
      - S3_HOST=minio
      - S3_PORT=9000
      - S3_PUBLIC_URL=http://localhost:9500/happy-files
      - S3_ACCESS_KEY=minioadmin
      - S3_SECRET_KEY=<YOUR-SECRET>
      - S3_BUCKET=happy-files
      - S3_USE_SSL=false
      - HANDY_MASTER_SECRET=<YOUR-SECRET>
    depends_on:
      - postgres
      - redis
      - minio
      - minio-init

  minio:
    image: minio/minio
    container_name: happy-minio
    restart: unless-stopped
    command: server /data --console-address ":9001"
    environment:
      - MINIO_ROOT_USER=minioadmin
      - MINIO_ROOT_PASSWORD=<YOUR-SECRET>
    ports:
      - "9500:9000"  # API端口
      - "9501:9001"  # 控制台端口
    volumes:
      - ./data/minio:/data
    healthcheck:
      test: ["CMD", "mc", "ready", "local"]
      interval: 10s
      timeout: 5s
      retries: 5

  minio-init:
    image: minio/mc:latest
    depends_on:
      minio:
        condition: service_healthy
    entrypoint: >
      /bin/sh -c "
      mc alias set local http://minio:9000 minioadmin <YOUR-SECRET>;
      mc mb -p local/happy-files || true;
      mc anonymous set download local/happy-files;
      exit 0;
      "

  postgres:
    image: postgres:15
    environment:
      - POSTGRES_DB=happy-server
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=<YOUR-SECRET>
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres -d happy-server"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - ./data/redis:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

database init & migrate

root@web02:~/happy-server# docker run -it --rm --network happy-server_default  -v .:/app node:20 bash
root@cd626eedaf22:/# cd app
root@cd626eedaf22:/app# npm config set registry https://registry.npmmirror.com/
root@cd626eedaf22:/app# npx dotenv -e .env.dev -- npx prisma migrate deploy
Prisma schema loaded from prisma/schema.prisma
Datasource "db": PostgreSQL database "happy-server", schema "public" at "postgres:5432"

36 migrations found in prisma/migrations

No pending migrations to apply.

nginx conf

# nginx conf
server {
    listen 80;
    listen [::]:80;
    server_name happy.iomics.pro;
    return 301 https://$host$request_uri;
    location ~ /.well-known/acme-challenge {
        proxy_set_header Host $host;
        proxy_set_header X-Real_IP $remote_addr;
        proxy_set_header X-Forwarded-For $remote_addr:$remote_port;
        proxy_pass http://127.0.0.1:9180;
    }
}
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name happy.iomics.pro;
    ssl_certificate /etc/nginx/ssl/happy.iomics.pro_2048/fullchain.cer;
    ssl_certificate_key /etc/nginx/ssl/happy.iomics.pro_2048/private.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    location / {
        proxy_pass http://127.0.0.1:3005;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        # === 以下是WebSocket统一升级配置 ===
        # 检测请求是否为WebSocket(通过请求头)
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Sec-WebSocket-Key $http_sec_websocket_key;
        proxy_set_header Sec-WebSocket-Version $http_sec_websocket_version;
        # (可选)WebSocket长连接超时配置(避免被Nginx主动断开)
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
        proxy_pass_request_body on;
        proxy_pass_request_headers on;

        # 禁止 Nginx 缓冲请求体(避免大请求截断)
        proxy_buffering off;
    }
    location ~ /.well-known/acme-challenge {
        proxy_set_header Host $host;
        proxy_set_header X-Real_IP $remote_addr;
        proxy_set_header X-Forwarded-For $remote_addr:$remote_port;
        proxy_pass http://127.0.0.1:9180;
    }
}
本文阅读量  次
本站总访问量  次
Authors: Haohao Zhang