From a6a8a384a4f23d7ba19af043190263950cd0b917 Mon Sep 17 00:00:00 2001 From: ohgeecee Date: Sun, 4 Oct 2026 07:54:23 +0000 Subject: [PATCH 01/11] Add self-hosted CRM intake and service request flow --- .github/workflows/deploy.yml | 77 +++ .gitignore | 6 + DEPLOY.md | 121 +---- book.html | 110 ++-- build/generate.mjs | 76 ++- content/guides.json | 50 ++ ...ellite-internet-slow-starlink-hughesnet.md | 1 + intake.js | 89 +++ ops/crm/.env.example | 10 + ops/crm/README.md | 55 ++ ops/crm/compose.yaml | 90 ++++ ops/crm/deploy.sh | 30 ++ ops/crm/ohgeec-crm-update.service | 12 + ops/crm/ohgeec-crm-update.timer | 10 + privacy.html | 25 + services/intake/Dockerfile | 9 + services/intake/package.json | 20 + services/intake/pnpm-lock.yaml | 508 ++++++++++++++++++ services/intake/sql/001_schema.sql | 171 ++++++ services/intake/sql/020-create-api-role.sh | 10 + services/intake/src/server.mjs | 122 +++++ sitemap.xml | 6 + .../index.html | 37 +- .../index.html | 37 +- tech-support/connectivity/index.html | 29 +- .../index.html | 37 +- .../index.html | 37 +- tech-support/hardware/index.html | 29 +- .../smart-tv-streaming-weak-wifi/index.html | 37 +- tech-support/index.html | 29 +- tech-support/onsite-montana/index.html | 138 +++++ tech-support/security/index.html | 29 +- .../index.html | 37 +- .../index.html | 37 +- .../clear-cache-cookies-popups/index.html | 37 +- tech-support/software/index.html | 29 +- .../speed-up-slow-computer/index.html | 37 +- .../index.html | 37 +- 38 files changed, 2006 insertions(+), 255 deletions(-) create mode 100644 .github/workflows/deploy.yml create mode 100644 intake.js create mode 100644 ops/crm/.env.example create mode 100644 ops/crm/README.md create mode 100644 ops/crm/compose.yaml create mode 100644 ops/crm/deploy.sh create mode 100644 ops/crm/ohgeec-crm-update.service create mode 100644 ops/crm/ohgeec-crm-update.timer create mode 100644 privacy.html create mode 100644 services/intake/Dockerfile create mode 100644 services/intake/package.json create mode 100644 services/intake/pnpm-lock.yaml create mode 100644 services/intake/sql/001_schema.sql create mode 100644 services/intake/sql/020-create-api-role.sh create mode 100644 services/intake/src/server.mjs create mode 100644 tech-support/onsite-montana/index.html diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..23ac51f --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,77 @@ +name: Build and deploy portfolio + intake API + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ohgeec-pages-${{ github.ref }} + cancel-in-progress: true + +jobs: + site: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version: 22 + - name: Generate guide pages + run: node build/generate.mjs + - name: Assemble public Pages artifact + run: | + set -eu + mkdir -p site + cp index.html support.html book.html thanks.html privacy.html tech-support.html \ + ogc-network.js intake.js portfolio.css portfolio.js fonts.css og-image.png \ + apple-touch-icon.png robots.txt sitemap.xml CNAME site/ + cp -R assets fonts tech-support site/ + - uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3 + if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' + with: + path: site + + deploy-pages: + if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' + needs: site + runs-on: ubuntu-latest + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5 + - id: deployment + uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4 + + publish-intake: + if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 + - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 + with: + context: ./services/intake + push: true + tags: | + ghcr.io/ohgeeceee/network-intake:stable + ghcr.io/ohgeeceee/network-intake:sha-${{ github.sha }} diff --git a/.gitignore b/.gitignore index c304e7f..8b59a66 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,9 @@ verify2.js hub.js .DS_Store Thumbs.db + +# Local site artifact, CRM secrets, and database backups +/site/ +/ops/crm/.env +/ops/crm/*.dump* +/ops/crm/*.age diff --git a/DEPLOY.md b/DEPLOY.md index a30bd67..f8a42fa 100644 --- a/DEPLOY.md +++ b/DEPLOY.md @@ -1,112 +1,41 @@ -# Deploying the ohgeeceee network hub (free) +# Deploying ohgeec.com and the private CRM -The hub is three static files — `index.html`, `support.html`, `ogc-network.js` (plus -`CNAME`). No build step, no server. Any static host works; below is **GitHub Pages** -(what you asked for) and one alternative worth knowing about. +## Public portfolio ---- +The portfolio and support guides remain static on GitHub Pages. `.github/workflows/deploy.yml` builds the guide pages, assembles only public site files, deploys them to Pages, and builds/publishes the private intake service image to GHCR from commits to `main`. Pull requests run the static guide generator but do not publish images or deploy Pages. -## Recommended: GitHub Pages +To switch from the current branch-based Pages source to the workflow: -Best fit here — it's free, gives you HTTPS on your own domain, and lives next to your -existing `github.com/ohgeeceee` repos. +1. In the repository, open **Settings → Pages** and change **Build and deployment → Source** to **GitHub Actions**. +2. Check that the repository's custom domain is still `ohgeec.com` and HTTPS is enforced. +3. Merge the workflow to `main`. Confirm the `Build and deploy portfolio + intake API` workflow succeeds and the live site loads `book.html`, `/privacy.html`, the guide hub, and `/tech-support/onsite-montana/`. +4. The workflow excludes source content, service source, CRM operations files, and credentials from the Pages artifact. The guides and their generated HTML remain public as intended. -### 1. Make the repo +GitHub Actions uses GitHub-hosted runners only. It publishes `ghcr.io/ohgeeceee/network-intake:stable` and an immutable `sha-` image tag. The home server pulls the image; GitHub Actions never connects into the home network. -1. Create a new **public** repo, e.g. `github.com/ohgeeceee/ohgeec`. -2. Add these files to the repo root (drag-and-drop in the browser is fine): - - `index.html` - - `support.html` - - `ogc-network.js` - - `CNAME` ← already contains `ohgeec.com`, this is what binds the domain -3. Commit to the `main` branch. +## DNS and public API -### 2. Turn on Pages +Keep `ohgeec.com` pointed at GitHub Pages. Add `api.ohgeec.com` as the only public home-server route, through Cloudflare Tunnel. For the tunnel's custom hostname, manage the zone in the same Cloudflare account or configure the required CNAME at the authoritative DNS provider. If moving DNS to Cloudflare, recreate the existing GitHub Pages apex A/AAAA and `www` CNAME records as DNS-only records; keep only the tunnel hostname proxied. The tunnel makes outbound connections, so no router port-forward or public home IP is needed. -Repo → **Settings → Pages** → under *Build and deployment*, set **Source: Deploy from a -branch**, **Branch: `main` / `/ (root)`** → Save. Give it a minute; it'll show a live URL. +In Cloudflare Zero Trust, create a tunnel on the home server and configure one published application: -### 3. Point ohgeec.com at GitHub +- Hostname: `api.ohgeec.com` +- Service: `http://intake:3000` +- No other public hostname routes -At your domain registrar's DNS, add these records for the **apex** (`ohgeec.com`): +The public form sends JSON to `https://api.ohgeec.com/v1/intake`. The receiver validates an allowlisted schema and writes the contact, ticket, and first event in one PostgreSQL function call. CORS only allows the portfolio's exact origins; it is not treated as authentication. The form contains no shared secret. -| Type | Name | Value | -|------|------|-------| -| A | `@` | `185.199.108.153` | -| A | `@` | `185.199.109.153` | -| A | `@` | `185.199.110.153` | -| A | `@` | `185.199.111.153` | -| AAAA | `@` | `2606:50c0:8000::153` | -| AAAA | `@` | `2606:50c0:8001::153` | -| AAAA | `@` | `2606:50c0:8002::153` | -| AAAA | `@` | `2606:50c0:8003::153` | +## Private CRM host -And for the `www` version: +Follow [ops/crm/README.md](ops/crm/README.md) on the Linux server. Directus listens on `127.0.0.1:8055`; use Tailscale Serve for the owner's tailnet access. PostgreSQL has no host port. Never create Cloudflare routes for Directus or PostgreSQL and never mount the Docker socket into application containers. -| Type | Name | Value | -|------|------|-------| -| CNAME | `www` | `ohgeeceee.github.io` | +The private host needs Docker Compose v2, outbound DNS/HTTPS, a Tailscale client, GHCR `read:packages` access, a protected `ops/crm/.env`, and a separate encrypted backup target. Image digests in `.env` must be set to reviewed versions before starting services. The compose stack is not live until those host-specific values, DNS, tunnel route, and initial Directus account are configured. -(The four A records are the important ones; the AAAA records add IPv6 and are optional -but recommended. Always confirm the current IPs in GitHub's docs — link at the bottom.) +## Update flow -### 4. Lock in the domain + HTTPS +1. A merge to `main` deploys the new static site and publishes the intake container. +2. The home server's systemd timer runs `ops/crm/deploy.sh` every five minutes. +3. The deploy script pulls the `stable` image, restarts only the intake service, waits for its health check, and restores the previous local image tag if the new container does not become healthy. +4. Schema changes require an encrypted backup and a reviewed forward migration before the application image is rolled out. -Back in **Settings → Pages**: the *Custom domain* box should already read `ohgeec.com` -(from the `CNAME` file). Once DNS propagates (minutes to a few hours), tick -**Enforce HTTPS**. Done — `https://ohgeec.com` is live. - -### Bandwidth note - -GitHub Pages has a ~100 GB/month soft limit. Since every site in the network loads -`ogc-network.js` from here, that's the one file with real traffic — but it's tiny -(~14 KB, and browsers cache it), so you're nowhere near the limit at personal scale. - ---- - -## Alternative worth considering: Cloudflare Pages - -If you'd rather not think about bandwidth at all, **Cloudflare Pages** is the one I'd -point you to. Same price (free), same drag-or-git deploy, but: - -- **Unlimited bandwidth** — nice precisely because `ogc-network.js` is served to all - your other domains. -- Faster global CDN and instant cache purges. -- Free custom domains + automatic HTTPS. - -Trade-off: you manage the domain's DNS inside Cloudflare (which is itself free and, if -you move `ohgeec.com`'s nameservers there, also simplifies the DNS steps above). If your -whole world is already on GitHub, Pages is the lower-friction pick. Both are genuinely -fine. - -Netlify and Vercel are also free and excellent for static sites; they're just heavier -than you need for three files. Any of the four will serve this correctly. - ---- - -## After the hub is live - -Add one line before `` on each site so the network bar + Support button appear: - -```html - -``` - -Add `data-ogc-theme="dark"` on the dark sites (beemuu, Manners, GarageRoute): - -```html - -``` - -Because all sites load the registry from `ohgeec.com`, later changes (a new site, or -flipping Idaho Blotter from "soon" to "live") are a one-line edit in `ogc-network.js` -here — every site updates automatically, no redeploys elsewhere. - ---- - -### Quick sanity check once live -- `https://ohgeec.com` shows the hub with all six cards. -- `https://ohgeec.com/ogc-network.js` loads the script (not a 404). -- On any site with the snippet, the network bar appears at the bottom. - -Source for the DNS values: GitHub Docs — *Managing a custom domain for your GitHub Pages site*. +For a manual update on the host, run `ops/crm/deploy.sh`. Keep deployment credentials read-only for GHCR and do not run untrusted pull-request workflows on the home server. diff --git a/book.html b/book.html index bd29015..ae2ddf4 100644 --- a/book.html +++ b/book.html @@ -3,8 +3,8 @@ -Remote Tech Support — Book a Session - +Remote & On-Site Tech Support — ohgeeceee +