diff --git a/.env.example b/.env.example index 1526740..dc686dc 100644 --- a/.env.example +++ b/.env.example @@ -18,7 +18,7 @@ VITE_APP_ID=open-seo # --- Docker self-hosting only (standalone local mode) --- # See SELF_HOSTING_DOCKER.md # Docker compose runs with BYPASS_GATEWAY_LOCAL_ONLY=true. -# Optional in .env.local because compose already sets it. +# Optional in .env because compose already sets it. # BYPASS_GATEWAY_LOCAL_ONLY=true # Optional override (defaults to open-seo): # VITE_APP_ID=open-seo diff --git a/.worktreeinclude b/.worktreeinclude deleted file mode 100644 index bb876dc..0000000 --- a/.worktreeinclude +++ /dev/null @@ -1,3 +0,0 @@ -.env.local -.wrangler -node_modules/ diff --git a/README.md b/README.md index d820b00..a959bdd 100644 --- a/README.md +++ b/README.md @@ -146,9 +146,14 @@ Now you're all set! Go back to the gateway, click on the OpenSEO app, and start ## Docker Self Hosting (Standalone OpenSEO) -If you want a single Docker Compose command that runs OpenSEO locally without Gateway auth, see [`SELF_HOSTING_DOCKER.md`](./SELF_HOSTING_DOCKER.md). +Quickstart: -This runtime uses local dev servers to emulate Cloudflare Worker bindings. It is intended for **local use only** — do not expose ports directly to the public internet. For remote access, use [Tailscale](https://tailscale.com/). For internet-facing deployments, use the Cloudflare deployment path above. See the [security and runtime caveats](./SELF_HOSTING_DOCKER.md#security-and-runtime-caveats) in the Docker guide for details. +1. `cp .env.example .env` +2. Set `DATAFORSEO_API_KEY` in `.env` +3. `docker compose up --build` +4. Open `http://localhost:3001` + +For runtime details, caveats, and troubleshooting, see [`SELF_HOSTING_DOCKER.md`](./SELF_HOSTING_DOCKER.md). ## Local Development diff --git a/SELF_HOSTING_DOCKER.md b/SELF_HOSTING_DOCKER.md index 59d1339..78825e4 100644 --- a/SELF_HOSTING_DOCKER.md +++ b/SELF_HOSTING_DOCKER.md @@ -10,22 +10,21 @@ In this mode, OpenSEO runs with `BYPASS_GATEWAY_LOCAL_ONLY=true`, so authenticat ## Security and runtime caveats -This stack runs local dev servers to emulate Cloudflare Worker bindings and is intended for local use only. +This stack is local-first and uses dev runtimes to emulate Cloudflare Worker bindings. - Do not expose these ports directly to the public internet. -- There is no Gateway auth in this mode. -- If you need remote access, use [Tailscale](https://tailscale.com/) instead of a public tunnel. -- For internet-facing deployments with auth, use the [Cloudflare deployment path](./README.md#self-hosting-deploy-on-cloudflare-5-10-minutes). +- There is no built-in Gateway auth in this mode. +- If you expose it beyond localhost, put it behind the same authentication layer you use for your other self-hosted services (or use the [Cloudflare deployment path](./README.md#self-hosting-deploy-on-cloudflare-5-10-minutes)). ## 1) Configure env values From the repository root: ```bash -cp .env.example .env.local +cp .env.example .env ``` -Set values as needed in `.env.local`. +Set values as needed in `.env`. Required: @@ -37,16 +36,10 @@ Optional: - `VITE_APP_ID` (defaults to `open-seo`) - `BYPASS_GATEWAY_LOCAL_ONLY=true` (Docker compose already sets this) -Validate env before startup: - -```bash -pnpm run docker:check-env -``` - ## 2) Start OpenSEO ```bash -pnpm run docker:up +docker compose up --build ``` URL: @@ -64,14 +57,14 @@ Boot behavior: - OpenSEO env values seem stale: restart OpenSEO: ```bash -docker compose -f self-host/docker-compose.yml --env-file .env.local up -d --build open-seo +docker compose up -d --build open-seo ``` - If migrations fail on first run, rebuild and retry: ```bash -pnpm run docker:down -pnpm run docker:up +docker compose down +docker compose up --build ``` ## Stop and cleanup @@ -79,11 +72,11 @@ pnpm run docker:up Stop stack: ```bash -pnpm run docker:down +docker compose down ``` Stop and remove Docker volumes: ```bash -docker compose -f self-host/docker-compose.yml --env-file .env.local down -v +docker compose down -v ``` diff --git a/self-host/docker-compose.yml b/compose.yaml similarity index 96% rename from self-host/docker-compose.yml rename to compose.yaml index efe11b0..cf37fe0 100644 --- a/self-host/docker-compose.yml +++ b/compose.yaml @@ -1,7 +1,7 @@ services: open-seo: build: - context: .. + context: . dockerfile: self-host/Dockerfile.selfhost working_dir: /app environment: @@ -22,7 +22,7 @@ services: ports: - "127.0.0.1:3001:3001" volumes: - - ..:/app + - .:/app - open_seo_node_modules:/app/node_modules - open_seo_pnpm_store:/pnpm/store diff --git a/package.json b/package.json index a8ea8d4..b55f8d8 100644 --- a/package.json +++ b/package.json @@ -20,9 +20,8 @@ "db:migrate:prod": "npx everyapp app remote-d1-shell -- drizzle-kit migrate --config=drizzle-prod.config.ts", "db:studio:local": "drizzle-kit studio", "db:studio:prod": "npx everyapp app remote-d1-shell -- drizzle-kit studio --config=drizzle-prod.config.ts", - "docker:check-env": "node ./self-host/scripts/validate-selfhost-env.mjs", - "docker:up": "pnpm run docker:check-env && docker compose -f self-host/docker-compose.yml --env-file .env.local up --build", - "docker:down": "docker compose -f self-host/docker-compose.yml --env-file .env.local down", + "docker:up": "docker compose up --build", + "docker:down": "docker compose down", "docker:generate-secrets": "node ./self-host/scripts/generate-selfhost-secrets.mjs", "knip": "knip", "ci": "prettier --check . && knip && tsc --noEmit && oxlint ." diff --git a/self-host/scripts/generate-selfhost-secrets.mjs b/self-host/scripts/generate-selfhost-secrets.mjs index 391ff0e..f771af0 100644 --- a/self-host/scripts/generate-selfhost-secrets.mjs +++ b/self-host/scripts/generate-selfhost-secrets.mjs @@ -12,7 +12,7 @@ const { privateKey, publicKey } = generateKeyPairSync("rsa", { }); console.log( - "# Copy these lines into .env.local\n# See SELF_HOSTING_DOCKER.md for setup instructions", + "# Copy these lines into .env\n# See SELF_HOSTING_DOCKER.md for setup instructions", ); console.log(`BETTER_AUTH_SECRET=${betterAuthSecret}`); console.log(`JWT_PRIVATE_KEY="${escapeForEnv(privateKey)}"`); diff --git a/self-host/scripts/validate-selfhost-env.mjs b/self-host/scripts/validate-selfhost-env.mjs deleted file mode 100644 index 586af15..0000000 --- a/self-host/scripts/validate-selfhost-env.mjs +++ /dev/null @@ -1,65 +0,0 @@ -import { existsSync, readFileSync } from "node:fs"; - -function parseEnvFile(path) { - const raw = readFileSync(path, "utf8"); - const out = {}; - - for (const line of raw.split(/\r?\n/)) { - const trimmed = line.trim(); - if (trimmed.length === 0 || trimmed.startsWith("#")) { - continue; - } - - const equalsIndex = trimmed.indexOf("="); - if (equalsIndex <= 0) { - continue; - } - - const key = trimmed.slice(0, equalsIndex).trim(); - let value = trimmed.slice(equalsIndex + 1).trim(); - - if ( - (value.startsWith('"') && value.endsWith('"')) || - (value.startsWith("'") && value.endsWith("'")) - ) { - value = value.slice(1, -1); - } - - out[key] = value.replace(/\\n/g, "\n"); - } - - return out; -} - -function isBlank(value) { - return typeof value !== "string" || value.trim().length === 0; -} - -const envPath = process.argv[2] || ".env.local"; - -if (!existsSync(envPath)) { - console.error(`Missing env file: ${envPath}`); - console.error("Create it with: cp .env.example .env.local"); - process.exit(1); -} - -const env = parseEnvFile(envPath); - -const requiredKeys = ["DATAFORSEO_API_KEY"]; - -const missingKeys = requiredKeys.filter((key) => isBlank(env[key])); -if (missingKeys.length > 0) { - console.error("Missing required keys in env file:"); - for (const key of missingKeys) { - console.error(`- ${key}`); - } - process.exit(1); -} - -if (isBlank(env.BYPASS_GATEWAY_LOCAL_ONLY)) { - console.warn( - "Warning: BYPASS_GATEWAY_LOCAL_ONLY is not set in .env.local. Docker sets it automatically.", - ); -} - -console.log(`Env validation passed: ${envPath}`);