# Cloud Deployment Guide

Goal: the optimizer runs 24/7 in the cloud, audits on schedule, proposes fixes, emails you an approval link, and — only after you click Approve — applies changes to your site over FTP/SFTP with backup and verification.

## What auto-fix now covers (and what it deliberately doesn't)

Newest additions, all off by default until you review one sample fix:
- **Title tag** — fills in a `<title>` only when one is truly missing. It
  never rewrites a title you already wrote — that's a content decision, not
  a deterministic fix.
- **Breadcrumb schema** — built from your real URL folder structure, so
  nothing is invented.
- **sitemap.xml** — generated automatically the first time one doesn't exist.
- **Alt text (auto)** — off by default. Guesses alt text from the image
  *filename* only (e.g. `team-photo.jpg` → "Team photo"). It's deliberately
  dumb rather than guessing at what's in the picture — turn it on once
  you've checked a sample of your filenames actually describe the image.
- **Auto-blog** — a new toggle in Content Updates. Publishes a new page
  under `/blog/` on your own site, over the same FTP/SFTP connection as
  everything else, gated by the same approval email. The text is built
  entirely from your own keywords/locations/business name — no invented
  facts — so review the draft before approving, same as any other change.
- **FAQ schema** — two ways now: `faq_schema_visible` reads a Q&A already
  visible on the page (a heading ending in "?" with an answer underneath)
  and just marks it up — no data entry needed. `faq_from_facts` uses real
  Q&A you type into Settings → Business Facts if the page doesn't already
  have one. Either way, the schema always matches real, visible content.
- **Speed — the parts that ARE safe to automate**: `lazy_images` adds
  `loading="lazy"` to images (skipping the first/hero image, so it never
  slows down what's above the fold) — a pure HTML attribute, no server
  config, always safe. `htaccess_speed` adds browser-caching/compression
  rules to an *existing* `.htaccess` (Apache/cPanel only, wrapped in
  `<IfModule>` so it's a no-op if a module isn't installed, and it never
  creates a new `.htaccess` from scratch — only appends to one that's
  already there and working).
- **IndexNow** — pings Bing/Yandex directly after real changes so they
  re-crawl in minutes instead of weeks. Google doesn't participate in
  IndexNow, so this doesn't change Google specifically, but it's free.
- **A real call/WhatsApp bar** — `cta_bar_from_facts` (off by default)
  adds one above the fold, but only once you've put a real phone number
  in Business Facts. No number, no bar.

**Deliberately not automated**, with the reasoning:
- **Internal linking** — auto-inserting links into arbitrary page HTML
  risks breaking layout or dropping links in the wrong place without
  understanding the page's actual content. Stays a recommendation in your
  audit report, not an auto-write.
- **Image compression/renaming** — renaming an image means finding and
  rewriting every reference to it across every page, atomically, without
  ever breaking one. That's real engineering that needs its own testing
  pass, not a quick add.
- **Rewritten headings, answer-first paragraphs, CTA button copy** — these
  are wording/judgment calls where an AI-written guess is a worse outcome
  than a clear "you decide" flag.
- **Deep speed work (CDN, server-level tuning, image re-encoding)** — genuinely
  requires host-specific, hands-on decisions that can break a site if done
  blindly. The two speed items that *are* safe (see above) are automated;
  the rest stays a recommendation.

## What's new in this version

- **Dashboard layout**: Settings (Auto-fix connection, Content updates, AI citations, Email &
  notifications) now live in one place, in a left-hand Settings tab, instead of scattered among the
  main audit cards. A light/dark theme toggle is in the sidebar (saved per browser).
- **Email & notifications** are now configurable from the dashboard itself (writes to your local
  `.env`) instead of only via file editing. On Render/cloud, environment variables still need to be set
  in that platform's own dashboard — that's a hosting-platform rule, not something any app can bypass.
- **Blog posts** are now wrapped in your site's own real header, footer, and CSS (fetched live from
  your homepage) instead of a bare template — falls back to a plain clean template only if it can't
  confidently find a `<header>`/`<footer>`/stylesheet on your homepage.
- **Social posts now include a generated image** — a branded text/color card built locally with
  Pillow (free, no paid image-generation API). Attached automatically to Facebook and Instagram posts.
- **Instagram Business** can now auto-publish for real (image + caption), not just draft — needs your
  own Instagram Business account linked to a Facebook Page, an access token, and Meta's own app review
  for content publishing (a one-time approval process on Meta's side, similar to Google Business
  Profile's).
- **LinkedIn Page** can now auto-publish for real (text only, no image yet) — needs your own LinkedIn
  Marketing API app, reviewed by LinkedIn separately.
- **A note on "free AI"**: the AI Citation Tracker calls OpenAI/Anthropic/Perplexity's own paid,
  metered APIs — there's no free programmatic equivalent to ChatGPT/Claude/Perplexity themselves for
  automated checking at any volume. It stays fully optional and off unless you add a key; typical cost
  is a fraction of a cent per question with default models.

## Option C — cPanel shared hosting (no Docker, no VPS)

If your host gives you cPanel with "Setup Python App" (most shared hosting
does), you don't need Docker or Render at all:

1. Upload this whole folder to your account, **outside** `public_html`
   (e.g. a sibling folder like `seo-tool/`) \u2014 putting it inside your
   web root makes your backups, database, and hosting password
   downloadable by anyone who guesses the URL. The dashboard checks for
   this on every audit and warns you loudly if it detects it.
2. cPanel \u2192 Setup Python App \u2192 Create Application. Point the
   "Application startup file" at `passenger_wsgi.py` (already included).
3. Set environment variables there: `APP_PASSWORD`, `PUBLIC_URL`, and
   optionally `APP_SECRET`.
4. If the tool runs on the same server as your site, set the Auto-fix
   connection to **Local folder** and point it at your site's real path
   \u2014 no FTP host/username/password needs to be stored anywhere.
5. For the scheduler to run reliably under Passenger (which can recycle
   idle processes), set up a cPanel cron job to hit
   `https://yourtool.yourdomain.com/cron/<a-secret-key-you-choose>`
   every hour or so, instead of relying only on the in-app scheduler
   toggle.

## IndexNow \u2014 instant re-crawl pings (Bing/Yandex, not Google)

When a fix changes your pages, the tool can ping IndexNow-participating
engines directly so they re-crawl within minutes instead of weeks. Turn
it on in Settings \u2192 IndexNow, and pick how the one-time verification
key file gets onto your server: automatically, manually (you upload it
once), or as its own approval-gated fix like everything else.

## Real current news in blog posts

Each auto-generated blog post now includes a "What's happening right now"
section pulling up to 3 real, dated, linked headlines relevant to your
keywords/location, via Google News RSS \u2014 free, no API key, no signup.
Only the real headline is quoted (short and factual) with a link to the
real source; the article body is never reproduced. If the fetch fails or
turns up nothing relevant, that section is silently skipped \u2014 the
rest of the post is written to reliably clear 500 words on its own either
way.

## PHP sites that build <head> via an include (fixed)

If your pages use `<?php include('header.php'); ?>` and the real `<head>`
lives in that included file rather than in the page itself, two bugs are
now fixed:
1. Your real pages (like `index.php`) were being wrongly skipped entirely,
   as if they weren't real pages — now they're correctly recognized as
   real via your last audit's crawl data, and body-level fixes (like the
   multiple-H1 fix) work on them normally.
2. Fixes that need a `<head>` to write into (canonical, title, meta
   description, schema) were being silently reported as "fixed" even when
   nothing was actually written — now they're honestly reported as not
   applied, with a clear one-time explanation of why (writing them into a
   *shared* header file would incorrectly put the same title on every page
   that includes it — that needs a small change to your header file
   itself, not something safe to automate blindly).

## Social platforms — one clear choice each

Each of Facebook, Instagram, and LinkedIn now has a single dropdown: **Off
/ Manual (email me a caption to paste) / Automatic (post it for me)** —
replacing the old split between a toggle and a separate "draft only"
checklist that lived in two different places. X stays manual-only (a
checkbox), since its posting API's pricing/rules change too often to
reliably automate against.

## "Slow response" and similar hosting issues

Response-time and HTTPS issues aren't something a code fixer can resolve
— they're server/hosting questions. These are now labeled distinctly
("hosting issue, not a code fix") instead of lumped in with content work,
and the dashboard has a **"Copy message for my host"** button that builds
a ready-to-send support message from your actual findings — no file to
edit, just forward it to your host's support.

## Environment variables (the security backbone)

Set these on your cloud host — never put secrets in config files you upload:

| Variable | Required | Purpose |
|---|---|---|
| `APP_PASSWORD` | **Yes** | Locks the whole app behind a login prompt (username can be anything). Without it, anyone who finds the URL controls your site fixes. |
| `APP_SECRET` | **Yes** | Signs approval links (32+ random chars). Generate: `openssl rand -base64 32` |
| `PUBLIC_URL` | **Yes** | Your app's public address, e.g. `https://seo.yourdomain.com` — used to build approval links. |
| `WEBHOOK_SECRET` | Recommended | CI must send this in an `X-Webhook-Secret` header to `/webhook/deploy`. |
| `SMTP_HOST` `SMTP_PORT` `SMTP_USER` `SMTP_PASS` `MAIL_FROM` `MAIL_TO` | For email links | Any SMTP works (Gmail app-password, Zoho, SES). `MAIL_TO` is where approval links go. |
| `SLACK_WEBHOOK` | Alternative | Approval links to a Slack channel instead of / as well as email. |
| `DATABASE_URL` | When scaling | Postgres connection string; omit to use SQLite. |

FTP/SFTP credentials stay in `remediate.yaml` on the server's persistent disk. Better: create a dedicated FTP account in your hosting panel scoped to `public_html` only, so even a worst-case leak can't touch email or databases. Prefer SFTP or FTPS (`use_tls: true`) — never plain FTP over the internet.

## Option A — Render / Railway (easiest, ~15 min)

1. Push the project to a private GitHub repo (the Dockerfile is included).
2. Render: New → Web Service → connect repo → it detects the Dockerfile. Railway: New Project → Deploy from repo.
3. Add a **persistent disk/volume** mounted at `/app` (holds `data.db`, `backups/`, `reports/`, `config.yaml`, `remediate.yaml`). Without a volume your history and backups vanish on every redeploy.
4. Set the environment variables above. HTTPS is automatic on both platforms.
5. Open the app URL, log in with your `APP_PASSWORD`, configure your site, flip the scheduler switch on.

## Option B — Your own VPS (₹400–800/month, full control)

```bash
# on a fresh Ubuntu VPS
sudo apt update && sudo apt install -y docker.io caddy
git clone <your-repo> && cd seo-ai-optimizer
sudo docker build -t seo-optimizer .
sudo docker run -d --restart unless-stopped -p 127.0.0.1:8765:8765 \
  -v /opt/seo-data:/app \
  -e APP_PASSWORD='...' -e APP_SECRET='...' \
  -e PUBLIC_URL='https://seo.yourdomain.com' \
  -e SMTP_HOST='smtp.gmail.com' -e SMTP_PORT=587 \
  -e SMTP_USER='you@gmail.com' -e SMTP_PASS='app-password' \
  -e MAIL_FROM='you@gmail.com' -e MAIL_TO='you@gmail.com' \
  seo-optimizer
```

Caddy gives you automatic HTTPS in two lines — `/etc/caddy/Caddyfile`:
```
seo.yourdomain.com {
    reverse_proxy 127.0.0.1:8765
}
```
Point a DNS A-record for `seo.yourdomain.com` at the VPS IP, `sudo systemctl reload caddy`, done.

## The automated flow, end to end

1. **Scheduler** re-audits your site every N hours.
2. After each audit, the **remediation planner** dry-runs: if auto-fixable issues exist, it stores the exact diff as a pending approval.
3. You get an **email/Slack message** with a signed link (valid 72h, single-use).
4. The link shows a summary + full unified diff with **Approve & apply / Reject** buttons — works fine from your phone.
5. On Approve: full backup → fixes applied over FTP/SFTP → **verification audit** runs automatically; the change-intelligence feed confirms every element that improved.
6. Duplicate protection: while one proposal awaits your decision, no new ones are created; applied fixes are idempotent so nothing ever double-applies.

## Content updates (Google Business Profile + social) — new

Same approve-first model as the FTP fixes, in a separate `content_updates.yaml`.
It can draft (and, once you configure credentials, publish):
- **Google Business Profile**: description updates + new posts. Real
  auto-publish requires a Google Cloud project + OAuth client + a **manual
  API access request Google reviews** (needs a GBP verified 60+ days and a
  live business website) — this is not instant, budget a few days. Until
  approved, it still runs, just drafting the text into your approval email.
- **Facebook Page**: real auto-publish via a Page Access Token you generate
  in Meta Business Suite — no waiting period.
- **Instagram / LinkedIn / X**: draft-only. It writes ready-to-paste caption
  text into the approval email; nothing is posted automatically, since each
  of those needs its own separate business-API approval that's out of scope
  for now.

Setup:
1. On first run the app creates `content_updates.yaml` next to `config.yaml`
   with everything off. Set `enabled: true` and turn on the channels you want.
2. For GBP, fill in `client_id`, `client_secret`, `refresh_token`,
   `location_id` once you have them from Google.
3. For Facebook, fill in `page_id` and `page_access_token`.
4. Leave the rest under `draft_only` if you just want caption text emailed
   to you to paste in yourself.
5. It runs automatically after every scheduled audit, proposes what's due,
   and — like FTP fixes — waits for your Approve click before publishing
   anything.

## Go-live checklist (the steps people miss)

- [ ] `APP_PASSWORD` + `APP_SECRET` set, HTTPS confirmed (padlock in browser)
- [ ] Dedicated FTP account scoped to `public_html`, FTPS/SFTP not plain FTP
- [ ] Persistent volume mounted — check `data.db` survives a redeploy
- [ ] Send yourself a test approval (run an audit, click the emailed link)
- [ ] Test `--rollback` once on purpose, so the first real rollback isn't your first ever
- [ ] Spend limit set on your Anthropic/OpenAI accounts (Console → Billing)
- [ ] Uptime monitor pointed at the app URL (free: UptimeRobot) — the watcher needs watching
- [ ] Offsite backup: schedule a weekly copy of `/opt/seo-data` (or platform disk snapshot)
- [ ] Hosting-panel alert email confirmed current — if FTP credentials are ever rotated, update `remediate.yaml`
- [ ] If using content updates: `content_updates.yaml` reviewed, GBP API access request submitted (if wanted), Facebook Page Access Token doesn't expire silently (use a long-lived token)
