240 lines
9.6 KiB
JSON
240 lines
9.6 KiB
JSON
{
|
|
"meta": {
|
|
"author": "Andre Cobham / Arising Media",
|
|
"updated": "2026-06-09",
|
|
"version": "3.0"
|
|
},
|
|
"stack": {
|
|
"base_image": "php:8.3-fpm-alpine",
|
|
"process_manager": "supervisord",
|
|
"web_server": "nginx",
|
|
"database": "SQLite (pdo_sqlite)",
|
|
"email": "SMTP via msmtp or Resend API",
|
|
"js": "vanilla (no frameworks)",
|
|
"css": "tokens.css + main.css",
|
|
"hosting": "Coolify (Docker) or cPanel (shared hosting)"
|
|
},
|
|
"databases": {
|
|
"header.db": ["nav_items"],
|
|
"footer.db": ["footer_columns", "footer_links", "footer_legal"],
|
|
"pages.db": ["pages", "page_sections"],
|
|
"blog.db": ["posts", "post_images", "post_schema", "post_stats", "related_posts", "linkedin_drafts", "subscribers"],
|
|
"one_db_per_domain": true,
|
|
"never_monolithic": "Do not combine unrelated content domains in one database"
|
|
},
|
|
"deployment": {
|
|
"docker": {
|
|
"build_command": "docker compose build",
|
|
"run_command": "docker compose up -d",
|
|
"process_manager": "supervisord",
|
|
"platforms": ["VPS", "DigitalOcean", "Linode", "Coolify", "custom servers"]
|
|
},
|
|
"cpanel": {
|
|
"requires": [".htaccess", ".cpanel.yml"],
|
|
"repo_path": "/home/{username}/repositories/{domain}/ (empty)",
|
|
"webroot": "/home/{username}/public_html/{domain}/",
|
|
"platforms": ["cPanel", "Bluehost", "HostGator", "SiteGround"]
|
|
}
|
|
},
|
|
"content_update": {
|
|
"new_page": [
|
|
"Insert row in pages.db: slug, title, hero fields, sections_json",
|
|
"Re-seed: python3 build/seed_databases.py",
|
|
"Rebuild Docker: docker build -t {domain}:local .",
|
|
"For new URL patterns: add location block to infra/nginx.conf"
|
|
],
|
|
"edit_content": [
|
|
"All body copy lives in sections_json column of pages.db",
|
|
"Never put body copy in PHP template files",
|
|
"Edit CSV source or build/seed_databases.py and re-run"
|
|
],
|
|
"build_scripts": "Use JSON + template + Python for 4+ similar pages (location pages, service pages)"
|
|
},
|
|
"security": {
|
|
"required_headers": [
|
|
"X-Frame-Options: SAMEORIGIN",
|
|
"X-Content-Type-Options: nosniff",
|
|
"Referrer-Policy: strict-origin-when-cross-origin",
|
|
"Permissions-Policy: camera=(), microphone=(), geolocation=()",
|
|
"Strict-Transport-Security: max-age=31536000; includeSubDomains",
|
|
"Cross-Origin-Opener-Policy: same-origin",
|
|
"Cross-Origin-Resource-Policy: same-origin",
|
|
"Content-Security-Policy (tight, project-specific)"
|
|
],
|
|
"php_hardening": [
|
|
"expose_php = Off",
|
|
"display_errors = Off",
|
|
"open_basedir = /var/www/html:/var/www/data",
|
|
"disable_functions = exec,passthru,shell_exec,system,proc_open,popen,pcntl_exec"
|
|
],
|
|
"php_fpm": "clear_env = no (CRITICAL: required for getenv())",
|
|
"blocking_sensitive_paths": [
|
|
"location ~ /\\. { deny all; return 404; }",
|
|
"location ~* \\.(env|conf|yml|md|sh|py|sql|bak|log|dockerfile)$ { deny all; return 404; }"
|
|
]
|
|
},
|
|
"forms": {
|
|
"spam_protection": "Altcha (self-hosted, proof-of-work SHA-256, no third-party)",
|
|
"altcha_key_generation": "openssl rand -hex 32",
|
|
"altcha_csp_requirement": "worker-src 'self' blob: (CRITICAL)",
|
|
"rate_limiting": {
|
|
"nginx": "5 requests/min per IP, burst 3",
|
|
"php": "5/10min per IP, file-backed (/tmp/form-rate-limit/)"
|
|
},
|
|
"security_layers": [
|
|
"nginx rate limit: 5r/min per IP",
|
|
"PHP rate limit: 5/10min per IP",
|
|
"honeypot field: hidden 'website' input",
|
|
"time-on-page check: <3s = [REVIEW] in subject",
|
|
"Altcha proof-of-work: SHA-256 + HMAC verification",
|
|
"server-side validation: all fields checked, HTML-escaped",
|
|
"32KB body cap: reject oversized payloads"
|
|
],
|
|
"email_providers": {
|
|
"resend": "REST API (transactional, PHP curl)",
|
|
"msmtp": "SMTP relay (for cPanel + existing SMTP)"
|
|
}
|
|
},
|
|
"seo": {
|
|
"required_meta_tags": [
|
|
"charset UTF-8",
|
|
"viewport width=device-width, initial-scale=1.0",
|
|
"title (under 60 chars)",
|
|
"description (150-160 chars)",
|
|
"canonical link",
|
|
"Open Graph: og:type, og:url, og:title, og:description, og:image, og:site_name",
|
|
"Twitter: card, url, title, description, image",
|
|
"robots index, follow",
|
|
"theme-color (mobile)",
|
|
"favicon.svg, favicon-32.png, apple-touch-icon"
|
|
],
|
|
"required_files": [
|
|
"/robots.txt (Disallow /api/, include Sitemap)",
|
|
"/sitemap.xml (one <url> per page with <lastmod>)",
|
|
"/llms.txt (llmstxt.org standard)"
|
|
],
|
|
"schema_required": [
|
|
"LocalBusiness (home page, location pages)",
|
|
"Service (service detail pages)",
|
|
"BreadcrumbList (every page)",
|
|
"FAQPage (FAQ pages only)"
|
|
],
|
|
"og_image": "1200x630px, brand colors, under 200KB",
|
|
"title_rules": "{Service} | {Brand} | {City}, {State}",
|
|
"description_rules": "150-160 chars, action-oriented, include city + service"
|
|
},
|
|
"images": {
|
|
"format": "AVIF + JPG fallback",
|
|
"picture_element_required": true,
|
|
"conversion_command": "convert input.jpg -resize 1920x -quality 80 -define avif:speed=6 output.avif",
|
|
"size_targets": {
|
|
"portrait": "original width, 80 quality, 50-120KB",
|
|
"hero": "1920px max, 80 quality, 150-350KB",
|
|
"og_social": "1200px, 85 quality, under 150KB"
|
|
},
|
|
"hero_naming": "hero-{page-slug}.avif + .jpg fallback",
|
|
"attributes_required": ["alt text or alt=\"\"", "loading=\"lazy\" (except hero)", "width and height"]
|
|
},
|
|
"mobile": {
|
|
"breakpoints": {
|
|
"320": "mobile-first base",
|
|
"360": "iPhone SE portrait",
|
|
"480": "small phones",
|
|
"600": "phones",
|
|
"768": "tablets (main mobile breakpoint)",
|
|
"900": "small laptops, tablet landscape",
|
|
"1023": "IMPORTANT: switch to mobile menu here",
|
|
"1024": "sub-desktop"
|
|
},
|
|
"header_nav_switch_at": "max-width: 1023px (not 768px)",
|
|
"touch_targets_minimum": "44x44px (Apple HIG, WCAG)",
|
|
"grid_mobile_override": "grid-template-columns: 1fr !important at 900px",
|
|
"overflow_protection": "overflow-x: clip; max-width: 100%"
|
|
},
|
|
"cookie_consent": {
|
|
"option1": {
|
|
"name": "orestbida/cookieconsent",
|
|
"stars": "4600+",
|
|
"size": "23KB UMD + 32KB CSS",
|
|
"license": "MIT",
|
|
"recommendation": "default choice"
|
|
},
|
|
"option2": {
|
|
"name": "Osano Cookie Consent",
|
|
"stars": "3500+",
|
|
"size": "30KB bundle",
|
|
"license": "MIT"
|
|
},
|
|
"when_not_needed": "GDPR Article 4(11): strictly necessary cookies (session, CSRF, form state) exempt from consent"
|
|
},
|
|
"testing": {
|
|
"build_verification": "grep -rn '{{' site/ -- result: empty",
|
|
"container_health": "docker compose ps, docker logs, curl / returns 200",
|
|
"url_surface": "public paths 200, sensitive paths 404",
|
|
"mobile_responsive": "zero horizontal overflow at 320, 360, 390, 768, 900, 1023, 1024, 1200",
|
|
"form_e2e": "submit real form, verify email arrives",
|
|
"rate_limit": "request 6 returns 429 Too Many Requests",
|
|
"seo_surface": "all pages have title, canonical, og:, schema JSON-LD",
|
|
"cache_busting": "main.css?v=<unix-timestamp> changes on deploy",
|
|
"pre_launch_gates": [
|
|
"All public URLs 200",
|
|
"All sensitive URLs 404",
|
|
"No sensitive files in container",
|
|
"Zero mobile overflow",
|
|
"Form submits, email arrives",
|
|
"Rate limit triggers",
|
|
"All pages have required meta tags",
|
|
"robots.txt and sitemap.xml exist",
|
|
"Zero em-dashes in HTML/JSON",
|
|
"Resend domain fully verified",
|
|
"Test email lands in primary inbox",
|
|
"Tested on real iPhone and Android",
|
|
"Lighthouse scores 90+ on all categories"
|
|
]
|
|
},
|
|
"never_use": [
|
|
"Node.js / npm on frontend",
|
|
"WordPress for new builds",
|
|
"CSS frameworks (Bootstrap, Tailwind, Bulma)",
|
|
"JS frameworks (React, Vue, Angular, Svelte)",
|
|
"jQuery, Lodash, Moment, axios, utility libraries",
|
|
"CSS-in-JS, styled-components",
|
|
"Build tools requiring node_modules",
|
|
"Tracking pixels (except client-explicitly-requested)",
|
|
"Single monolithic database",
|
|
"Hardcoded copy in PHP templates",
|
|
"Docker Compose for arisingmedia.us stack",
|
|
"Google Drive / Sheets for content (evaluated and reverted 2026-06)"
|
|
],
|
|
"secure_app_features": {
|
|
"when_to_use": "File conversion, encryption, payment processing, user authentication, rate-limited APIs",
|
|
"php_hardening": [
|
|
"CSRF token on every POST endpoint",
|
|
"Rate limiting (nginx + PHP file-backed)",
|
|
"Altcha proof-of-work verification",
|
|
"At-rest encryption (sodium_crypto_secretbox + secretstream)",
|
|
"Signed download tokens (never expose file paths)",
|
|
"Server-side validation + HTML-escaped output",
|
|
"Session httponly + secure flags",
|
|
"storage/ directory access denied via .htaccess"
|
|
],
|
|
"database": "SQLite with pdo_sqlite, schema migrations in try/catch, BEGIN IMMEDIATE transactions",
|
|
"environment": {
|
|
"clear_env": "no (php-fpm-pool.conf)",
|
|
"secrets": ".env (never hardcoded, never committed)",
|
|
"trust_proxy": "1 when behind reverse proxy",
|
|
"encryption_key": "32-byte hex QC_ENCRYPTION_KEY",
|
|
"altcha_key": "32-byte hex ALTCHA_HMAC_KEY"
|
|
},
|
|
"reference": "quickconvert.us"
|
|
},
|
|
"directory_structure": {
|
|
"src/api/": "PHP router, contact handler, templates, components, data (SQLite)",
|
|
"src/assets/": "CSS (tokens.css + main.css), JS (vanilla only), images (AVIF+JPG), altcha, cookieconsent",
|
|
"build/": "seed_databases.py",
|
|
"infra/": "nginx.conf, supervisord.conf, php-fpm-pool.conf, entrypoint.sh",
|
|
".planning/": "not served, not in Docker image",
|
|
"root_files": "Dockerfile, docker-compose.yml, .dockerignore, .htaccess, .cpanel.yml, .gitignore, .env (ignored)"
|
|
}
|
|
}
|