chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
FROM denoland/deno:alpine-2.0.6
|
||||
|
||||
# Create non-root user
|
||||
RUN addgroup -S deno && adduser -S deno -G deno
|
||||
|
||||
WORKDIR /app/functions
|
||||
|
||||
COPY --chown=deno:deno functions /app/functions
|
||||
|
||||
USER deno
|
||||
@@ -0,0 +1,4 @@
|
||||
FROM ghcr.io/insforge/postgres:v15.13.4
|
||||
COPY deploy/docker-init/db/postgresql.conf /etc/postgresql/postgresql.conf
|
||||
COPY deploy/docker-init/db/db-init.sql /docker-entrypoint-initdb.d/01-init.sql
|
||||
COPY deploy/docker-init/db/jwt.sql /docker-entrypoint-initdb.d/02-jwt.sql
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.7 KiB |
@@ -0,0 +1,76 @@
|
||||
# PostgreSQL Configuration (optional - defaults shown)
|
||||
POSTGRES_USER=postgres
|
||||
POSTGRES_PASSWORD=postgres
|
||||
POSTGRES_DB=insforge
|
||||
|
||||
# Port Configuration
|
||||
# Change these to run multiple instances on the same host
|
||||
POSTGRES_PORT=5432
|
||||
POSTGREST_PORT=5430
|
||||
APP_PORT=7130
|
||||
AUTH_PORT=7131
|
||||
DENO_PORT=7133
|
||||
|
||||
API_BASE_URL=http://localhost:7130
|
||||
VITE_API_BASE_URL=http://localhost:7130
|
||||
|
||||
# Authentication
|
||||
JWT_SECRET=your-secret-key-here-must-be-32-char-or-above
|
||||
ROOT_ADMIN_USERNAME=admin
|
||||
ROOT_ADMIN_PASSWORD=change-this-password
|
||||
|
||||
# Encryption key for secrets and database encryption (will use JWT_SECRET if not provided)
|
||||
ENCRYPTION_KEY=
|
||||
|
||||
# Worker timeout in milliseconds (default: 60000 ms)
|
||||
WORKER_TIMEOUT_MS=60000
|
||||
|
||||
# OpenRouter Configuration
|
||||
# Get your API key from https://openrouter.ai/keys
|
||||
OPENROUTER_API_KEY=
|
||||
|
||||
# Anonymous Telemetry
|
||||
# Set INSFORGE_TELEMETRY_DISABLED=1 to disable anonymous self-host telemetry.
|
||||
INSFORGE_TELEMETRY_DISABLED=
|
||||
|
||||
# Stripe Payments Configuration (Optional)
|
||||
# Get your secret keys from https://dashboard.stripe.com/apikeys
|
||||
STRIPE_LIVE_SECRET_KEY=
|
||||
STRIPE_TEST_SECRET_KEY=
|
||||
|
||||
# Deployment Configuration (Optional)
|
||||
# Required for self-hosted site deployments and custom domains.
|
||||
VERCEL_TOKEN=
|
||||
VERCEL_TEAM_ID=
|
||||
VERCEL_PROJECT_ID=
|
||||
|
||||
# OAuth Configuration (Optional)
|
||||
# Google OAuth - Get credentials from https://console.cloud.google.com/
|
||||
GOOGLE_CLIENT_ID=
|
||||
GOOGLE_CLIENT_SECRET=
|
||||
|
||||
# GitHub OAuth - Get credentials from https://github.com/settings/developers
|
||||
GITHUB_CLIENT_ID=
|
||||
GITHUB_CLIENT_SECRET=
|
||||
|
||||
#Microsoft OAuth - Get credentials from https://portal.azure.com/
|
||||
MICROSOFT_CLIENT_ID=
|
||||
MICROSOFT_CLIENT_SECRET=
|
||||
|
||||
# Discord OAuth - Get credentials from https://discord.com/developers/applications
|
||||
DISCORD_CLIENT_ID=
|
||||
DISCORD_CLIENT_SECRET=
|
||||
|
||||
# LinkedIn OAuth - Get credentials from https://www.linkedin.com/developers/apps
|
||||
LINKEDIN_CLIENT_ID=
|
||||
LINKEDIN_CLIENT_SECRET=
|
||||
|
||||
# X OAuth - Get credentials from https://developer.twitter.com/en/portal/dashboard
|
||||
X_CLIENT_ID=
|
||||
X_CLIENT_SECRET=
|
||||
|
||||
# Apple OAuth - Get credentials from https://developer.apple.com/account/resources/identifiers/list
|
||||
# APPLE_CLIENT_ID is your Services ID (e.g., com.yourapp.service)
|
||||
# APPLE_CLIENT_SECRET must be a JSON string with teamId, keyId, and privateKey (PKCS#8 PEM format from .p8 file)
|
||||
APPLE_CLIENT_ID=
|
||||
APPLE_CLIENT_SECRET=
|
||||
@@ -0,0 +1,167 @@
|
||||
services:
|
||||
postgres:
|
||||
image: ghcr.io/insforge/postgres-all:latest
|
||||
|
||||
environment:
|
||||
- POSTGRES_USER=${POSTGRES_USER:-postgres}
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-postgres}
|
||||
- POSTGRES_DB=${POSTGRES_DB:-insforge}
|
||||
- ENCRYPTION_KEY=${ENCRYPTION_KEY:-${JWT_SECRET:-dev-secret-please-change-in-production}}
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "${POSTGRES_PORT:-5432}:5432"
|
||||
networks:
|
||||
- insforge-network
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
postgrest:
|
||||
image: postgrest/postgrest:v12.2.12
|
||||
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
PGRST_DB_URI: postgres://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@postgres:5432/${POSTGRES_DB:-insforge}
|
||||
PGRST_OPENAPI_SERVER_PROXY_URI: http://localhost:3000
|
||||
PGRST_DB_SCHEMA: public
|
||||
PGRST_DB_ANON_ROLE: anon
|
||||
PGRST_JWT_SECRET: ${JWT_SECRET:-dev-secret-please-change-in-production}
|
||||
# Enable schema reloading via NOTIFY
|
||||
PGRST_DB_CHANNEL_ENABLED: true
|
||||
PGRST_DB_CHANNEL: pgrst
|
||||
ports:
|
||||
- "${POSTGREST_PORT:-5430}:3000"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "bash -c '</dev/tcp/localhost/3000'"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
networks:
|
||||
- insforge-network
|
||||
|
||||
insforge:
|
||||
image: ghcr.io/insforge/insforge-oss:v1.5.0
|
||||
|
||||
working_dir: /app
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
postgrest:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "${APP_PORT:-7130}:7130"
|
||||
- "${AUTH_PORT:-7131}:7131"
|
||||
environment:
|
||||
- PORT=7130
|
||||
- PROJECT_ROOT=/app
|
||||
- API_BASE_URL=${API_BASE_URL:-}
|
||||
- VITE_API_BASE_URL=${VITE_API_BASE_URL:-}
|
||||
- JWT_SECRET=${JWT_SECRET:-dev-secret-please-change-in-production}
|
||||
- ENCRYPTION_KEY=${ENCRYPTION_KEY:-${JWT_SECRET:-dev-secret-please-change-in-production}}
|
||||
- ROOT_ADMIN_USERNAME=${ROOT_ADMIN_USERNAME:-${ADMIN_EMAIL:-admin}}
|
||||
- ROOT_ADMIN_PASSWORD=${ROOT_ADMIN_PASSWORD:-${ADMIN_PASSWORD:-change-this-password}}
|
||||
- ADMIN_EMAIL=${ROOT_ADMIN_USERNAME:-${ADMIN_EMAIL:-admin}}
|
||||
- ADMIN_PASSWORD=${ROOT_ADMIN_PASSWORD:-${ADMIN_PASSWORD:-change-this-password}}
|
||||
# PostgreSQL connection
|
||||
- POSTGRES_HOST=postgres
|
||||
- POSTGRES_PORT=5432
|
||||
- POSTGRES_DB=${POSTGRES_DB:-insforge}
|
||||
- POSTGRES_USER=${POSTGRES_USER:-postgres}
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-postgres}
|
||||
- DATABASE_URL=postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@postgres:5432/${POSTGRES_DB:-insforge}
|
||||
- POSTGREST_BASE_URL=http://postgrest:3000
|
||||
# LLM Model API keys
|
||||
- OPENROUTER_API_KEY=${OPENROUTER_API_KEY:-}
|
||||
# Stripe Payments Configuration
|
||||
- STRIPE_TEST_SECRET_KEY=${STRIPE_TEST_SECRET_KEY:-}
|
||||
- STRIPE_LIVE_SECRET_KEY=${STRIPE_LIVE_SECRET_KEY:-}
|
||||
# Deployment Configuration
|
||||
- VERCEL_TOKEN=${VERCEL_TOKEN:-}
|
||||
- VERCEL_TEAM_ID=${VERCEL_TEAM_ID:-}
|
||||
- VERCEL_PROJECT_ID=${VERCEL_PROJECT_ID:-}
|
||||
# OAuth Configuration
|
||||
- GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID:-}
|
||||
- GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET:-}
|
||||
- GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID:-}
|
||||
- GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET:-}
|
||||
- DISCORD_CLIENT_ID=${DISCORD_CLIENT_ID:-}
|
||||
- DISCORD_CLIENT_SECRET=${DISCORD_CLIENT_SECRET:-}
|
||||
- MICROSOFT_CLIENT_ID=${MICROSOFT_CLIENT_ID:-}
|
||||
- MICROSOFT_CLIENT_SECRET=${MICROSOFT_CLIENT_SECRET:-}
|
||||
- LINKEDIN_CLIENT_ID=${LINKEDIN_CLIENT_ID:-}
|
||||
- LINKEDIN_CLIENT_SECRET=${LINKEDIN_CLIENT_SECRET:-}
|
||||
- X_CLIENT_ID=${X_CLIENT_ID:-}
|
||||
- X_CLIENT_SECRET=${X_CLIENT_SECRET:-}
|
||||
- APPLE_CLIENT_ID=${APPLE_CLIENT_ID:-}
|
||||
- APPLE_CLIENT_SECRET=${APPLE_CLIENT_SECRET:-}
|
||||
# Logs directory
|
||||
- LOGS_DIR=/insforge-logs
|
||||
# Anonymous telemetry (set INSFORGE_TELEMETRY_DISABLED=1 to opt out)
|
||||
- INSFORGE_TELEMETRY_DISABLED=${INSFORGE_TELEMETRY_DISABLED:-}
|
||||
# Storage directory (for local file storage when S3 is not configured)
|
||||
- STORAGE_DIR=/insforge-storage
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- storage-data:/insforge-storage
|
||||
- insforge-logs:/insforge-logs
|
||||
networks:
|
||||
- insforge-network
|
||||
|
||||
# Deno serverless runtime for edge functions
|
||||
deno:
|
||||
image: ghcr.io/insforge/deno-runtime:latest
|
||||
|
||||
working_dir: /app
|
||||
depends_on:
|
||||
- postgres
|
||||
- postgrest
|
||||
ports:
|
||||
- "${DENO_PORT:-7133}:7133"
|
||||
environment:
|
||||
- PORT=7133
|
||||
- DENO_ENV=development
|
||||
- DENO_DIR=/deno-dir
|
||||
# PostgreSQL connection
|
||||
- POSTGRES_HOST=postgres
|
||||
- POSTGRES_PORT=5432
|
||||
- POSTGRES_DB=${POSTGRES_DB:-insforge}
|
||||
- POSTGRES_USER=${POSTGRES_USER:-postgres}
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-postgres}
|
||||
- POSTGREST_BASE_URL=http://postgrest:3000
|
||||
# Worker timeout (60 seconds default)
|
||||
- WORKER_TIMEOUT_MS=${WORKER_TIMEOUT_MS:-60000}
|
||||
# Encryption keys for decrypting function secrets
|
||||
- ENCRYPTION_KEY=${ENCRYPTION_KEY:-${JWT_SECRET:-dev-secret-please-change-in-production}}
|
||||
- JWT_SECRET=${JWT_SECRET:-dev-secret-please-change-in-production}
|
||||
volumes:
|
||||
- deno_cache:/deno-dir
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:7133/health"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 15s
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- insforge-network
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
driver: local
|
||||
deno_cache:
|
||||
driver: local
|
||||
storage-data:
|
||||
driver: local
|
||||
insforge-logs:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
insforge-network:
|
||||
driver: bridge
|
||||
@@ -0,0 +1,125 @@
|
||||
# Deploy InsForge with Docker
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Docker and Docker Compose installed on your machine
|
||||
|
||||
## Setup InsForge
|
||||
|
||||
### Step 1: Download the Docker Compose file
|
||||
|
||||
```bash
|
||||
wget https://raw.githubusercontent.com/insforge/insforge/main/deploy/docker-compose/docker-compose.yml
|
||||
wget https://raw.githubusercontent.com/insforge/insforge/main/deploy/docker-compose/.env.example
|
||||
mv .env.example .env
|
||||
```
|
||||
|
||||
### Step 2: Start InsForge
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### Step 3: Access InsForge
|
||||
|
||||
Open your browser and navigate to `http://localhost:7130`, you can see the InsForge dashboard as below:
|
||||
|
||||
<div align="center">
|
||||
<img src="../assets/signin.png" alt="InsForge Dashboard" width="600">
|
||||
</div>
|
||||
|
||||
## Running Multiple Instances
|
||||
|
||||
You can run multiple InsForge projects on the same host by using different ports and project names.
|
||||
|
||||
### Step 1: Create a separate env file for each project
|
||||
|
||||
```bash
|
||||
cp .env.example .env.project1
|
||||
cp .env.example .env.project2
|
||||
```
|
||||
|
||||
### Step 2: Edit each env file with unique ports
|
||||
|
||||
**.env.project1** (default ports):
|
||||
```
|
||||
POSTGRES_PORT=5432
|
||||
POSTGREST_PORT=5430
|
||||
APP_PORT=7130
|
||||
AUTH_PORT=7131
|
||||
DENO_PORT=7133
|
||||
```
|
||||
|
||||
**.env.project2** (different ports):
|
||||
```
|
||||
POSTGRES_PORT=5442
|
||||
POSTGREST_PORT=5440
|
||||
APP_PORT=7230
|
||||
AUTH_PORT=7231
|
||||
DENO_PORT=7233
|
||||
```
|
||||
|
||||
Make sure each project has its own `JWT_SECRET` and `ROOT_ADMIN_PASSWORD`.
|
||||
|
||||
### Step 3: Start each project with a unique name
|
||||
|
||||
```bash
|
||||
docker compose --env-file .env.project1 -p project1 up -d
|
||||
docker compose --env-file .env.project2 -p project2 up -d
|
||||
```
|
||||
|
||||
The `-p` flag gives each project isolated containers, volumes, and networks. The `--env-file` flag assigns unique ports so they don't conflict.
|
||||
|
||||
### Managing multiple instances
|
||||
|
||||
```bash
|
||||
# Check status
|
||||
docker compose --env-file .env.project1 -p project1 ps
|
||||
|
||||
# View logs
|
||||
docker compose --env-file .env.project1 -p project1 logs -f
|
||||
|
||||
# Stop an instance
|
||||
docker compose --env-file .env.project1 -p project1 down
|
||||
|
||||
# Stop and remove all data
|
||||
docker compose --env-file .env.project1 -p project1 down -v
|
||||
```
|
||||
|
||||
Each project has its own database, storage, and configuration. They are completely independent.
|
||||
|
||||
---
|
||||
|
||||
## Start using InsForge
|
||||
|
||||
### 1. Connect InsForge MCP
|
||||
|
||||
Open [InsForge Dashboard](http://localhost:7130), Follow the steps to connect InsForge MCP Server:
|
||||
|
||||
<div align="center">
|
||||
<img src="../assets/connect.png" alt="Connect InsForge MCP" width="600">
|
||||
</div>
|
||||
|
||||
### 2. Verify installation
|
||||
|
||||
To verify the connection, send the following prompt to your agent:
|
||||
```
|
||||
I'm using InsForge as my backend platform, call InsForge MCP's fetch-docs tool to learn about InsForge instructions.
|
||||
```
|
||||
|
||||
### 3. Start building your project
|
||||
|
||||
Build your next todo app, Instagram clone, or online platform in seconds!
|
||||
|
||||
Sample Project Prompt:
|
||||
|
||||
```
|
||||
Build an app similar to Reddit with community-based discussion threads using InsForge as the backend platform that has these features:
|
||||
|
||||
- Has a "Communities" list where users can browse or create communities
|
||||
- Each community has its own posts feed
|
||||
- Users can create posts with a title and body (text or image upload to InsForge storage)
|
||||
- Users can comment on posts and reply to other comments
|
||||
- Allows upvoting and downvoting for both posts and comments
|
||||
- Shows vote counts and comment counts for each post
|
||||
```
|
||||
@@ -0,0 +1,97 @@
|
||||
-- init.sql
|
||||
-- Create role for anonymous user
|
||||
CREATE ROLE anon NOLOGIN;
|
||||
|
||||
-- Create role for authenticator
|
||||
CREATE ROLE authenticated NOLOGIN;
|
||||
|
||||
-- Create project admin role for admin users
|
||||
CREATE ROLE project_admin NOLOGIN;
|
||||
|
||||
GRANT USAGE ON SCHEMA public TO anon;
|
||||
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO anon;
|
||||
GRANT USAGE ON SCHEMA public TO authenticated;
|
||||
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO authenticated;
|
||||
GRANT USAGE ON SCHEMA public TO project_admin;
|
||||
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO project_admin;
|
||||
|
||||
-- Grant permissions to roles
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO anon, authenticated, project_admin;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO anon, authenticated, project_admin;
|
||||
-- Create function to automatically create RLS policies for new tables
|
||||
CREATE OR REPLACE FUNCTION public.create_default_policies()
|
||||
RETURNS event_trigger AS $$
|
||||
DECLARE
|
||||
obj record;
|
||||
table_schema text;
|
||||
table_name text;
|
||||
has_rls boolean;
|
||||
BEGIN
|
||||
FOR obj IN SELECT * FROM pg_event_trigger_ddl_commands() WHERE command_tag = 'CREATE TABLE'
|
||||
LOOP
|
||||
-- Extract schema and table name from object_identity
|
||||
-- Handle quoted identifiers by removing quotes
|
||||
SELECT INTO table_schema, table_name
|
||||
split_part(obj.object_identity, '.', 1),
|
||||
trim(both '"' from split_part(obj.object_identity, '.', 2));
|
||||
-- Check if RLS is enabled on the table
|
||||
SELECT INTO has_rls
|
||||
rowsecurity
|
||||
FROM pg_tables
|
||||
WHERE schemaname = table_schema
|
||||
AND tablename = table_name;
|
||||
-- Only create policies if RLS is enabled
|
||||
IF has_rls THEN
|
||||
-- Create policy for project_admin role only
|
||||
-- Users must define their own policies for anon and authenticated roles
|
||||
EXECUTE format('CREATE POLICY "project_admin_policy" ON %s FOR ALL TO project_admin USING (true) WITH CHECK (true)', obj.object_identity);
|
||||
END IF;
|
||||
END LOOP;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Create event trigger to run the function when new tables are created
|
||||
CREATE EVENT TRIGGER create_policies_on_table_create
|
||||
ON ddl_command_end
|
||||
WHEN TAG IN ('CREATE TABLE')
|
||||
EXECUTE FUNCTION public.create_default_policies();
|
||||
|
||||
-- Create function to handle RLS enablement
|
||||
CREATE OR REPLACE FUNCTION public.create_policies_after_rls()
|
||||
RETURNS event_trigger AS $$
|
||||
DECLARE
|
||||
obj record;
|
||||
table_schema text;
|
||||
table_name text;
|
||||
BEGIN
|
||||
FOR obj IN SELECT * FROM pg_event_trigger_ddl_commands() WHERE command_tag = 'ALTER TABLE'
|
||||
LOOP
|
||||
-- Extract schema and table name
|
||||
-- Handle quoted identifiers by removing quotes
|
||||
SELECT INTO table_schema, table_name
|
||||
split_part(obj.object_identity, '.', 1),
|
||||
trim(both '"' from split_part(obj.object_identity, '.', 2));
|
||||
-- Check if table has RLS enabled and no policies yet
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM pg_tables
|
||||
WHERE schemaname = table_schema
|
||||
AND tablename = table_name
|
||||
AND rowsecurity = true
|
||||
) AND NOT EXISTS (
|
||||
SELECT 1 FROM pg_policies
|
||||
WHERE schemaname = table_schema
|
||||
AND tablename = table_name
|
||||
) THEN
|
||||
-- Create policy for project_admin role only
|
||||
-- Users must define their own policies for anon and authenticated roles
|
||||
EXECUTE format('CREATE POLICY "project_admin_policy" ON %s FOR ALL TO project_admin USING (true) WITH CHECK (true)', obj.object_identity);
|
||||
END IF;
|
||||
END LOOP;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Create event trigger for ALTER TABLE commands
|
||||
CREATE EVENT TRIGGER create_policies_on_rls_enable
|
||||
ON ddl_command_end
|
||||
WHEN TAG IN ('ALTER TABLE')
|
||||
EXECUTE FUNCTION public.create_policies_after_rls();
|
||||
@@ -0,0 +1,5 @@
|
||||
\set jwt_secret `echo "$JWT_SECRET"`
|
||||
\set jwt_exp `echo "$JWT_EXP"`
|
||||
|
||||
ALTER DATABASE postgres SET "app.settings.jwt_secret" TO :'jwt_secret';
|
||||
ALTER DATABASE postgres SET "app.settings.jwt_exp" TO :'jwt_exp';
|
||||
@@ -0,0 +1,29 @@
|
||||
# PostgreSQL configuration for InsForge
|
||||
# Enable logical replication for Logflare
|
||||
|
||||
# Listen on all interfaces to allow container connections
|
||||
listen_addresses = '*'
|
||||
|
||||
# Set WAL level to logical for Logflare replication
|
||||
wal_level = logical
|
||||
|
||||
# Set max replication slots (needed for logical replication)
|
||||
max_replication_slots = 10
|
||||
|
||||
# Set max WAL senders
|
||||
max_wal_senders = 10
|
||||
|
||||
# Shared preload libraries
|
||||
shared_preload_libraries = 'pg_cron,http,pgcrypto,insforge_pg_utils'
|
||||
|
||||
insforge.policy_grant_role = 'project_admin'
|
||||
insforge.policy_grant_tables = 'storage.objects,realtime.channels,realtime.messages,payments.stripe_checkout_sessions,payments.stripe_customer_portal_sessions,payments.razorpay_orders,payments.razorpay_subscriptions'
|
||||
|
||||
# InsForge-internal schemas that must never be exposed to the REST data API.
|
||||
# Read by system.is_exposed_schema (migration 056) to decide PostgREST's
|
||||
# db_schemas allowlist. Comma-separated, no spaces. Postgres internals (pg_*,
|
||||
# information_schema) and extension-owned schemas are handled separately and
|
||||
# need not be listed here. Edit + `SELECT pg_reload_conf()` to apply at runtime.
|
||||
insforge.internal_schemas = 'ai,auth,compute,deployments,email,functions,memory,payments,realtime,schedules,storage,system'
|
||||
|
||||
cron.database_name = 'insforge' # Add this line to specify the database for pg_cron
|
||||
@@ -0,0 +1,236 @@
|
||||
api:
|
||||
enabled: true
|
||||
address: 0.0.0.0:7135
|
||||
|
||||
sources:
|
||||
docker_host:
|
||||
type: docker_logs
|
||||
exclude_containers:
|
||||
- insforge-vector
|
||||
include_containers:
|
||||
- insforge
|
||||
- insforge-deno
|
||||
- insforge-postgrest
|
||||
- insforge-postgres
|
||||
|
||||
transforms:
|
||||
project_logs:
|
||||
type: remap
|
||||
inputs:
|
||||
- docker_host
|
||||
source: |-
|
||||
.project = "default"
|
||||
.event_message = del(.message)
|
||||
# Remove leading slash from container name if present
|
||||
container = to_string!(del(.container_name))
|
||||
if starts_with(container, "/") {
|
||||
.appname = slice!(container, 1)
|
||||
} else {
|
||||
.appname = container
|
||||
}
|
||||
del(.container_created_at)
|
||||
del(.container_id)
|
||||
del(.source_type)
|
||||
del(.stream)
|
||||
del(.label)
|
||||
del(.image)
|
||||
del(.host)
|
||||
del(.stream)
|
||||
|
||||
router:
|
||||
type: route
|
||||
inputs:
|
||||
- project_logs
|
||||
route:
|
||||
insforge: '.appname == "insforge"'
|
||||
deno: '.appname == "insforge-deno"'
|
||||
rest: '.appname == "insforge-postgrest"'
|
||||
db: '.appname == "insforge-postgres"'
|
||||
realtime: '.appname == "insforge-realtime"'
|
||||
# PostgREST logs with proper parsing
|
||||
rest_logs:
|
||||
type: remap
|
||||
inputs:
|
||||
- router.rest
|
||||
source: |-
|
||||
# .metadata.host = .project
|
||||
# Parse PostgREST log format: DD/Mon/YYYY:HH:MM:SS +ZZZZ: message
|
||||
parsed, err = parse_regex(.event_message, r'^(?P<time>\d{2}/\w{3}/\d{4}:\d{2}:\d{2}:\d{2} [+-]\d{4}): (?P<msg>.*)$')
|
||||
|
||||
if err == null && parsed != null {
|
||||
.event_message = parsed.msg
|
||||
.metadata.level = "info"
|
||||
} else {
|
||||
# If parsing fails, keep original message
|
||||
.metadata.level = "info"
|
||||
}
|
||||
# InsForge logs are structured JSON, parse and handle both request logs and application logs
|
||||
insforge_logs:
|
||||
type: remap
|
||||
inputs:
|
||||
- router.insforge
|
||||
source: |-
|
||||
# Remove the [backend] prefix if present
|
||||
.event_message = replace!(.event_message, r'^\[backend\] ', "")
|
||||
|
||||
req, err = parse_json(.event_message)
|
||||
if err == null {
|
||||
# Set timestamp from log
|
||||
if req.timestamp != null {
|
||||
.timestamp = to_timestamp!(req.timestamp)
|
||||
}
|
||||
|
||||
# Set log level
|
||||
.metadata.level = req.level
|
||||
|
||||
# Check if this is a request log (has duration field)
|
||||
if req.duration != null {
|
||||
# Flatten request log fields to top level
|
||||
.user_agent = req.userAgent
|
||||
.ip = req.ip
|
||||
.method = req.method
|
||||
.path = req.path
|
||||
.protocol = "HTTP/1.1"
|
||||
.status_code = req.status
|
||||
.size = req.size
|
||||
.duration = req.duration
|
||||
# Format as nginx-style log line
|
||||
.event_message = join!([req.method, req.path, to_string!(req.status), to_string!(req.size), req.duration, "-", req.ip, "-", req.userAgent], " ")
|
||||
.log_type = "request"
|
||||
} else {
|
||||
# This is an application log
|
||||
.event_message = join!([req.level, req.message], " - ")
|
||||
.log_type = "application"
|
||||
if req.error != null {
|
||||
.error = req.error
|
||||
}
|
||||
if req.stack != null {
|
||||
.stack = req.stack
|
||||
}
|
||||
}
|
||||
}
|
||||
realtime_logs:
|
||||
type: remap
|
||||
inputs:
|
||||
- router.realtime
|
||||
source: |-
|
||||
parsed, err = parse_regex(.event_message, r'^(?P<time>\d+:\d+:\d+\.\d+) \[(?P<level>\w+)\] (?P<msg>.*)$')
|
||||
if err == null {
|
||||
.event_message = parsed.msg
|
||||
.metadata.level = parsed.level
|
||||
}
|
||||
deno_logs:
|
||||
type: remap
|
||||
inputs:
|
||||
- router.deno
|
||||
source: |-
|
||||
parsed, err = parse_regex(.event_message, r'^(?P<time>\d+:\d+:\d+\.\d+) \[(?P<level>\w+)\] (?P<msg>.*)$')
|
||||
if err == null {
|
||||
.event_message = parsed.msg
|
||||
.timestamp = to_timestamp!(parsed.time)
|
||||
.metadata.level = upcase!(parsed.level)
|
||||
# .metadata.host = .project
|
||||
}
|
||||
# Postgres logs with proper parsing
|
||||
db_logs:
|
||||
type: remap
|
||||
inputs:
|
||||
- router.db
|
||||
source: |-
|
||||
# .metadata.host = "db-default"
|
||||
# Parse PostgreSQL log format: YYYY-MM-DD HH:MM:SS.SSS TZ [PID] LEVEL: message
|
||||
parsed, err = parse_regex(.event_message, r'^(?P<time>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}) (?P<tz>\w+) \[(?P<pid>\d+)\] (?P<level>LOG|ERROR|WARNING|INFO|NOTICE|FATAL|PANIC|STATEMENT|DETAIL): (?P<msg>.*)$')
|
||||
|
||||
if err == null && parsed != null {
|
||||
.metadata.parsed.pid = parsed.pid
|
||||
.metadata.level = upcase!(parsed.level)
|
||||
.event_message = parsed.msg
|
||||
|
||||
# Normalize STATEMENT and DETAIL to appropriate log levels
|
||||
if .metadata.level == "STATEMENT" {
|
||||
.metadata.level = "INFO"
|
||||
}
|
||||
if .metadata.level == "DETAIL" {
|
||||
.metadata.level = "INFO"
|
||||
}
|
||||
} else {
|
||||
# If parsing fails, keep original message and set default severity
|
||||
.metadata.level = "LOG"
|
||||
}
|
||||
|
||||
sinks:
|
||||
# File sinks - JSONL format for FileProvider (always enabled)
|
||||
file_insforge:
|
||||
type: 'file'
|
||||
inputs:
|
||||
- insforge_logs
|
||||
path: '/insforge-logs/insforge.logs.jsonl'
|
||||
encoding:
|
||||
codec: 'json'
|
||||
file_deno:
|
||||
type: 'file'
|
||||
inputs:
|
||||
- deno_logs
|
||||
path: '/insforge-logs/function.logs.jsonl'
|
||||
encoding:
|
||||
codec: 'json'
|
||||
file_rest:
|
||||
type: 'file'
|
||||
inputs:
|
||||
- rest_logs
|
||||
path: '/insforge-logs/postgrest.logs.jsonl'
|
||||
encoding:
|
||||
codec: 'json'
|
||||
file_db:
|
||||
type: 'file'
|
||||
inputs:
|
||||
- db_logs
|
||||
path: '/insforge-logs/postgres.logs.jsonl'
|
||||
encoding:
|
||||
codec: 'json'
|
||||
|
||||
# CloudWatch sinks (ignored if AWS_REGION not set)
|
||||
cw_insforge:
|
||||
type: aws_cloudwatch_logs
|
||||
inputs:
|
||||
- insforge_logs
|
||||
region: ${AWS_REGION:-skip}
|
||||
group_name: /insforge/local
|
||||
stream_name: ${HOSTNAME_OVERRIDE:-local}-insforge-vector
|
||||
encoding:
|
||||
codec: json
|
||||
healthcheck:
|
||||
enabled: false
|
||||
cw_rest:
|
||||
type: aws_cloudwatch_logs
|
||||
inputs:
|
||||
- rest_logs
|
||||
region: ${AWS_REGION:-skip}
|
||||
group_name: /insforge/local
|
||||
stream_name: ${HOSTNAME_OVERRIDE:-local}-postgrest-vector
|
||||
encoding:
|
||||
codec: json
|
||||
healthcheck:
|
||||
enabled: false
|
||||
cw_deno:
|
||||
type: aws_cloudwatch_logs
|
||||
inputs:
|
||||
- deno_logs
|
||||
region: ${AWS_REGION:-skip}
|
||||
group_name: /insforge/local
|
||||
stream_name: ${HOSTNAME_OVERRIDE:-local}-function-vector
|
||||
encoding:
|
||||
codec: json
|
||||
healthcheck:
|
||||
enabled: false
|
||||
cw_db:
|
||||
type: aws_cloudwatch_logs
|
||||
inputs:
|
||||
- db_logs
|
||||
region: ${AWS_REGION:-skip}
|
||||
group_name: /insforge/local
|
||||
stream_name: ${HOSTNAME_OVERRIDE:-local}-postgres-vector
|
||||
encoding:
|
||||
codec: json
|
||||
healthcheck:
|
||||
enabled: false
|
||||
@@ -0,0 +1,26 @@
|
||||
# InsForge Zeabur Template
|
||||
|
||||
Internal template for one-click InsForge deployment on Zeabur.
|
||||
|
||||
## CLI Authentication
|
||||
|
||||
```bash
|
||||
npx zeabur@latest auth login
|
||||
npx zeabur@latest auth logout
|
||||
```
|
||||
|
||||
## Deploy
|
||||
|
||||
```bash
|
||||
npx zeabur@latest template deploy -f template.yml
|
||||
```
|
||||
|
||||
## Update
|
||||
|
||||
```bash
|
||||
npx zeabur@latest template update -c Q82M3Y -f template.yml
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
- Zeabur docs: https://zeabur.com/docs/en-US/template/template-in-code
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user