diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..fa09098 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,16 @@ +.git +.gitignore +node_modules +dist +src/ui/dist +.env +.env.dev +.env.test +.env.age +facebook_posts +reels +shorts +tts_previews +post_media +tmp_voice.wav +*.wav diff --git a/.env.age b/.env.age new file mode 100644 index 0000000..e216a98 Binary files /dev/null and b/.env.age differ diff --git a/.env.example b/.env.example index 0a07402..3f4f3fd 100644 --- a/.env.example +++ b/.env.example @@ -1,12 +1,15 @@ OPENAI_API_KEY=your_openai_api_key NODE_ENV=production +UI_PORT=3333 # Use .env.dev for development and .env.test for tests (see package.json scripts) +# Scheduler cron expression (default every 4 hours) +CRON=0 */4 * * * -# Postgres connection string +# Postgres connection string (host-based dev default; prod compose overrides to postgres service) DATABASE_URL=postgres://ai_farm_brain:ai_farm_brain_password@localhost:5432/funrang_dev -# Redis (BullMQ) +# Redis (host-based dev default; prod compose overrides to redis service) REDIS_URL=redis://localhost:6379 # RSS ingest concurrency diff --git a/.gitignore b/.gitignore index bb7fc7c..a62c59a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ post_media .env .env.dev .env.test -reels +/reels/ dist shorts *.wav diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0d3c0c9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,34 @@ +FROM node:20-bookworm-slim AS builder + +WORKDIR /app + +COPY package.json package-lock.json ./ +RUN npm ci + +COPY . . +RUN npm run build + +FROM node:20-bookworm-slim AS runtime + +RUN apt-get update \ + && apt-get install -y --no-install-recommends ffmpeg python3 python3-pip ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# Local STT runtime for REEL_STT_PROVIDER=local. +RUN python3 -m pip install --no-cache-dir faster-whisper + +WORKDIR /app + +COPY package.json package-lock.json ./ +RUN npm ci --omit=dev + +COPY --from=builder /app/dist ./dist +COPY --from=builder /app/migrations ./migrations +COPY --from=builder /app/src/ui ./src/ui +COPY --from=builder /app/scripts/faster_whisper_words.py ./scripts/faster_whisper_words.py + +RUN mkdir -p facebook_posts reels shorts tts_previews post_media + +EXPOSE 3333 + +CMD ["sh", "-lc", "NODE_ENV=production node dist/scripts/migrate.js && NODE_ENV=production node dist/src/server.js"] diff --git a/README.md b/README.md index b7cc649..92dc65a 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,63 @@ Funrang is a modern social content engine for social media managers. It discover ## Status Local MVP with working UI, job system, post review, and Facebook publishing. +## One-command setup (Docker, production-style) +### Prereqs +- Docker + Docker Compose plugin (`docker compose`) + +### 1) Create env +```bash +cp .env.example .env +``` +- Set at least `OPENAI_API_KEY` in `.env`. +- Keep `DATABASE_URL`/`REDIS_URL` as-is for Compose; `docker-compose.prod.yml` overrides them to internal service URLs. + +### 2) Start everything +```bash +docker compose -f docker-compose.prod.yml up --build -d +``` +- App: `http://localhost:3333` +- The production image compiles TypeScript during build and runs emitted JavaScript (`node`), not `tsx`. + +### 3) Stop everything +```bash +docker compose -f docker-compose.prod.yml down +``` + +### Optional ops tooling +- Start pgAdmin too: +```bash +docker compose -f docker-compose.prod.yml --profile ops up -d +``` +- pgAdmin: `http://localhost:5050` + ## Get up and running (dev) ### Prereqs -- Node 18+ -- Docker (for Postgres + pgAdmin + Redis) +- Node.js 18+ (Node 20+ recommended) +- Docker + Docker Compose plugin (`docker compose`) +- `ffmpeg` and `ffprobe` on your PATH (required for reels/shorts generation) +- OpenAI API key + +### OS-level dependencies +- Ubuntu/Debian: +```bash +sudo apt-get update +sudo apt-get install -y ffmpeg python3 python3-pip +``` +- macOS (Homebrew): +```bash +brew install ffmpeg python +``` +- Windows: +1. Install FFmpeg (make sure `ffmpeg`/`ffprobe` are on PATH). +2. Install Python 3 (with `pip` and PATH enabled). +3. Install Docker Desktop. + +### Optional (only if `REEL_STT_PROVIDER=local`) +- Install local Whisper runtime: +```bash +python3 -m pip install --user faster-whisper +``` ### 1) Install + env ```bash @@ -34,6 +87,7 @@ cp .env.example .env cp .env.example .env.dev cp .env.example .env.test ``` +- Set at least `OPENAI_API_KEY` in `.env.dev`. ### 2) Start local services ```bash @@ -49,13 +103,46 @@ npm run migrate:dev ```bash npm run ui ``` +- Open `http://localhost:3333` ### 5) Tests ```bash npm test ``` +### Quick checks (recommended) +```bash +ffmpeg -version +ffprobe -version +docker compose ps +``` + ### Notes - `.env.dev` is used by `npm run ui` - `.env.test` is used by `npm test` -- `docker-compose.yml` brings up Postgres (`funrang_dev`), pgAdmin, and Redis +- `.env` is used by `docker-compose.prod.yml` for app secrets/config. +- `docker-compose.yml` is the development infra stack (Postgres + pgAdmin + Redis, DB: `funrang_dev`). +- `docker-compose.prod.yml` is the production-style stack (app + Postgres + Redis, DB: `funrang_prod`, and pgAdmin when `--profile ops` is used). +- In prod compose, `DATABASE_URL`/`REDIS_URL` are set to `postgres`/`redis` service hosts automatically. +- If OAuth callbacks are needed from real providers, use a public callback URL (for example via ngrok) and set `FACEBOOK_REDIRECT_URI` / `YOUTUBE_REDIRECT_URI`. + +### Share `.env` securely with `age` +- Install `age`: +```bash +# Ubuntu/Debian +sudo apt-get install -y age + +# macOS +brew install age +``` +- Encrypt `.env` before sending: +```bash +age -p -o .env.age .env +``` +- Decrypt on receiver side: +```bash +age -d -o .env .env.age +``` +- Safety: send `.env.age` and passphrase in different channels. +- Safety: use a one-time strong passphrase. +- Safety: never commit `.env` or decrypted secrets to git. diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..8f77514 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,94 @@ +services: + app: + build: + context: . + dockerfile: Dockerfile + container_name: ai-farm-brain-app + restart: unless-stopped + ports: + - "3333:3333" + env_file: + - .env + environment: + NODE_ENV: production + UI_PORT: 3333 + DATABASE_URL: postgres://ai_farm_brain:ai_farm_brain_password@postgres:5432/funrang_prod + REDIS_URL: redis://redis:6379 + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + healthcheck: + test: + [ + "CMD-SHELL", + "node -e \"fetch('http://127.0.0.1:3333').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\"" + ] + interval: 10s + timeout: 5s + retries: 20 + volumes: + - facebook_posts_data:/app/facebook_posts + - reels_data:/app/reels + - shorts_data:/app/shorts + - tts_previews_data:/app/tts_previews + - post_media_data:/app/post_media + + postgres: + image: postgres:16 + container_name: ai-farm-brain-postgres + restart: unless-stopped + environment: + POSTGRES_USER: ai_farm_brain + POSTGRES_PASSWORD: ai_farm_brain_password + POSTGRES_DB: funrang_prod + volumes: + - pgdata:/var/lib/postgresql/data + - ./pg-init.sql:/docker-entrypoint-initdb.d/00-init.sql:ro + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ai_farm_brain -d funrang_prod"] + interval: 5s + timeout: 5s + retries: 10 + + redis: + image: redis:7-alpine + container_name: ai-farm-brain-redis + restart: unless-stopped + command: ["redis-server", "--save", "60", "1"] + volumes: + - redis_data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 3s + retries: 20 + + pgadmin: + image: dpage/pgadmin4:8 + container_name: ai-farm-brain-pgadmin + profiles: ["ops"] + restart: unless-stopped + ports: + - "5050:80" + environment: + PGADMIN_DEFAULT_EMAIL: admin@local.dev + PGADMIN_DEFAULT_PASSWORD: admin123 + PGADMIN_CONFIG_SERVER_MODE: "False" + depends_on: + postgres: + condition: service_healthy + volumes: + - pgadmin_data:/var/lib/pgadmin + - ./pgadmin/servers.prod.json:/pgadmin4/servers.json:ro + +volumes: + pgdata: + pgadmin_data: + redis_data: + facebook_posts_data: + reels_data: + shorts_data: + tts_previews_data: + post_media_data: diff --git a/docs/CNAME b/docs/CNAME new file mode 100644 index 0000000..f758282 --- /dev/null +++ b/docs/CNAME @@ -0,0 +1 @@ +docs.funrang.com \ No newline at end of file diff --git a/docs/index.html b/docs/index.html index c33b7f4..5ad0525 100644 --- a/docs/index.html +++ b/docs/index.html @@ -24,7 +24,7 @@
Support: fardin4work@gmail.com
- Website: https://funrang.app
+ Website: https://funrang.com
Tune classification limits and manage custom templates for this org.
-{{text}}
- {{factPack}}
- No posts yet.
- {{/posts}} -Tune classification limits and manage custom templates for this org.
+{{ templatesStatus }}
++ LLM classification is budgeted per org, per day. When the limit is reached, the + rule-based filter is used. +
+{{ templatesStatus }}
+{{ text.text }}
+ {{ post.factPack }}
+ No posts yet.
+