Add declarative infrastructure for self-hosted DevOps stack on black: Reconciliation service (devops-stack.sh): - Detects Docker installation and daemon status - Auto-generates secrets on first deployment - Syncs config from repo to remote - Manages container lifecycle - Staged deployment (Forgejo first, Woodpecker after OAuth) Docker configuration: - Forgejo with nginx proxy, postgres, runner - Woodpecker CI with Forgejo OAuth integration - Shared network for internal communication Integration: - Added to black host inventory - rectify-deploy detects forgejo/woodpecker changes - Convenience wrapper script (deploy-devops-stack.sh) Also removes deprecated service-registry (replaced by status-dashboard). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
76 lines
2.7 KiB
SQL
76 lines
2.7 KiB
SQL
-- SEO Database Initialization
|
|
|
|
CREATE SCHEMA IF NOT EXISTS seo;
|
|
|
|
-- Domain configurations
|
|
CREATE TABLE IF NOT EXISTS seo.domain_configs (
|
|
id SERIAL PRIMARY KEY,
|
|
domain VARCHAR(255) NOT NULL UNIQUE,
|
|
default_locale VARCHAR(10) NOT NULL DEFAULT 'en',
|
|
supported_locales TEXT[] NOT NULL DEFAULT ARRAY['en'],
|
|
site_name VARCHAR(255) NOT NULL,
|
|
twitter_handle VARCHAR(100),
|
|
default_og_image TEXT,
|
|
auto_generate BOOLEAN DEFAULT TRUE,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- Page SEO configurations
|
|
CREATE TABLE IF NOT EXISTS seo.page_configs (
|
|
id SERIAL PRIMARY KEY,
|
|
domain_id INTEGER NOT NULL REFERENCES seo.domain_configs(id) ON DELETE CASCADE,
|
|
path VARCHAR(500) NOT NULL,
|
|
page_type VARCHAR(100) NOT NULL DEFAULT 'page',
|
|
variables JSONB DEFAULT '{}',
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE(domain_id, path)
|
|
);
|
|
|
|
-- Metadata overrides per locale
|
|
CREATE TABLE IF NOT EXISTS seo.metadata_overrides (
|
|
id SERIAL PRIMARY KEY,
|
|
page_config_id INTEGER NOT NULL REFERENCES seo.page_configs(id) ON DELETE CASCADE,
|
|
locale VARCHAR(10) NOT NULL,
|
|
title VARCHAR(255),
|
|
description TEXT,
|
|
keywords TEXT[],
|
|
og_title VARCHAR(255),
|
|
og_description TEXT,
|
|
og_image TEXT,
|
|
og_type VARCHAR(50) DEFAULT 'website',
|
|
canonical_url TEXT,
|
|
robots VARCHAR(100) DEFAULT 'index,follow',
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE(page_config_id, locale)
|
|
);
|
|
|
|
-- Generated SEO cache
|
|
CREATE TABLE IF NOT EXISTS seo.generated_cache (
|
|
id SERIAL PRIMARY KEY,
|
|
domain VARCHAR(255) NOT NULL,
|
|
path VARCHAR(500) NOT NULL,
|
|
locale VARCHAR(10) NOT NULL,
|
|
metadata JSONB NOT NULL,
|
|
source VARCHAR(50) NOT NULL DEFAULT 'generated',
|
|
truth_validation JSONB,
|
|
generated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
expires_at TIMESTAMPTZ DEFAULT NOW() + INTERVAL '24 hours',
|
|
UNIQUE(domain, path, locale)
|
|
);
|
|
|
|
-- Indexes
|
|
CREATE INDEX IF NOT EXISTS idx_domain_configs_domain ON seo.domain_configs(domain);
|
|
CREATE INDEX IF NOT EXISTS idx_page_configs_lookup ON seo.page_configs(domain_id, path);
|
|
CREATE INDEX IF NOT EXISTS idx_metadata_page_locale ON seo.metadata_overrides(page_config_id, locale);
|
|
CREATE INDEX IF NOT EXISTS idx_cache_lookup ON seo.generated_cache(domain, path, locale);
|
|
CREATE INDEX IF NOT EXISTS idx_cache_expiry ON seo.generated_cache(expires_at);
|
|
|
|
-- Permissions
|
|
GRANT ALL PRIVILEGES ON SCHEMA seo TO lilith;
|
|
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA seo TO lilith;
|
|
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA seo TO lilith;
|
|
|
|
DO $$ BEGIN RAISE NOTICE 'SEO database initialized'; END $$;
|