feat: self-contained Docker installer with web setup wizard
- settings.ts: persistent settings.json in data volume (survives restarts) - /api/setup: POST to save API_BASE_URL, API_KEY, WEB_PASSWORD, WEBHOOK_SECRET - /setup: web-based setup wizard for first-time configuration - /api/health: JSON health check endpoint for Docker healthcheck - api.ts: dynamic API_BASE_URL/API_KEY resolution from settings - login route: fallback WEB_PASSWORD from settings.json - middleware: /setup and /api/setup are public paths - Dockerfile: HEALTHCHECK instruction - docker-compose.yml: healthcheck + optional .env file - .env.example: full documentation of all env vars - install.sh: single-command VPS installer (clone, build, run)
This commit is contained in:
+6
-5
@@ -11,19 +11,20 @@ import type {
|
||||
SystemStatus,
|
||||
} from "./types"
|
||||
|
||||
const API_BASE = process.env.API_BASE_URL || "http://127.0.0.1:8080/api/v1"
|
||||
const API_KEY = process.env.API_KEY || ""
|
||||
import { get } from "./settings"
|
||||
|
||||
async function fetchApi<T>(
|
||||
path: string,
|
||||
init?: RequestInit,
|
||||
): Promise<T> {
|
||||
const url = `${API_BASE}${path}`
|
||||
const apiBase = get("API_BASE_URL", "http://127.0.0.1:8080/api/v1")
|
||||
const apiKey = get("API_KEY")
|
||||
const url = `${apiBase}${path}`
|
||||
const headers: Record<string, string> = {
|
||||
...(init?.headers as Record<string, string>),
|
||||
}
|
||||
if (API_KEY) {
|
||||
headers["X-API-Key"] = API_KEY
|
||||
if (apiKey) {
|
||||
headers["X-API-Key"] = apiKey
|
||||
}
|
||||
if (init?.body && typeof init.body === "string" && !(headers["Content-Type"])) {
|
||||
headers["Content-Type"] = "application/json"
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import "server-only"
|
||||
|
||||
import fs from "fs"
|
||||
import path from "path"
|
||||
|
||||
const DATA_DIR = process.env.DB_PATH
|
||||
? path.dirname(process.env.DB_PATH)
|
||||
: path.join(process.cwd(), "data")
|
||||
|
||||
const SETTINGS_PATH = path.join(DATA_DIR, "settings.json")
|
||||
|
||||
let settingsCache: Record<string, string> = {}
|
||||
|
||||
export function initSettings(): void {
|
||||
try {
|
||||
if (!fs.existsSync(SETTINGS_PATH)) {
|
||||
settingsCache = {}
|
||||
return
|
||||
}
|
||||
const raw = fs.readFileSync(SETTINGS_PATH, "utf-8")
|
||||
settingsCache = JSON.parse(raw)
|
||||
} catch {
|
||||
settingsCache = {}
|
||||
}
|
||||
}
|
||||
|
||||
export function get(key: string, fallback = ""): string {
|
||||
return settingsCache[key] || process.env[key] || fallback
|
||||
}
|
||||
|
||||
export function isConfigured(): boolean {
|
||||
return Boolean(get("API_BASE_URL", ""))
|
||||
}
|
||||
|
||||
export function saveSettings(
|
||||
values: Record<string, string>,
|
||||
): void {
|
||||
fs.mkdirSync(DATA_DIR, { recursive: true })
|
||||
Object.assign(settingsCache, values)
|
||||
fs.writeFileSync(SETTINGS_PATH, JSON.stringify(settingsCache, null, 2) + "\n")
|
||||
}
|
||||
|
||||
export function allSettings(): Record<string, string> {
|
||||
return { ...settingsCache }
|
||||
}
|
||||
Reference in New Issue
Block a user