The deep-dive companion to the GitHub plan: how the idata repo auto-deploys to cPanel. No FileZilla, no drag-and-drop, no chmod 0644 ritual — a merged pull request publishes the site.
main→
GitHub Action FTPS-syncs the diff→
Live on idata.ienergia.cl
Today the iris pages are PHP files hand-uploaded to a cPanel server. The deploy docs (Iris_Kepler_Deploy_Playbook, DEPLOY_2026-05-31.md) are full of "upload file X → set 0644 → repeat". Once the app lives in GitHub, nobody uploads anything — deploy becomes a side-effect of merging.
Dropbox/…/iData/agent/web/*.php./public_html/ — guessing which ones actually changed.DEPLOY_*.md..bak-truncated or stale copy.GitHub Actions does the FTP on the server's behalf, in the cloud, the instant a PR lands on main.
Each task is sven/iris-rules, daniel/prism-cover. Claude works its own branch and opens the PR.
main is protected. One approval is the deploy gate — a bad iris_master.php can't reach customers unreviewed.
A path filter means only **.php / **.html changes trigger it — editing a README never redeploys.
The action keeps a state file on the server and uploads only changed files. Credentials live in GitHub Secrets, never on a laptop.
A 1:1 mapping is the single highest-leverage decision. It deletes the path-translation tables, the .bak sprawl, and the permissions bug in one move — deploy becomes a dumb mirror with zero translation.
idata/ # repo root → /public_html/ ├─ iris_master.php → /public_html/iris_master.php ├─ lens.php prism_*.php header.inc.php ├─ _agent/ │ ├─ modes/eval_watchlist.php → /public_html/_agent/modes/... │ ├─ modes/refresh_inventory.php │ └─ migrate.php # applies pending sql/ files, idempotent ├─ sql/28_iris_kepler.sql # versioned, NOT auto-run blindly ├─ .gitignore # *.bak *.zip config.php .idata/ __pycache__/ └─ .github/workflows/deploy.yml
Live config sits outside the docroot at /home/ienergia/.idata/config.php and /home/ienergia/.auth/config.php. That means no credential ever touches git — the repo holds code only. Keep it that way; .gitignore any local config copy.
FTPS, diff-only, secrets in GitHub. The same file (parameterized) deploys Auth, Prism, BEAM and every other cPanel app — one reusable template across the org.
name: Deploy iData / iris to cPanel on: push: branches: [main] paths: ['**.php', '**.html', '**.htaccess'] # docs-only changes don't redeploy jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: SamKirkland/FTP-Deploy-Action@v4.3.5 with: server: ${{ secrets.CPANEL_FTP_HOST }} username: ${{ secrets.CPANEL_FTP_USER }} password: ${{ secrets.CPANEL_FTP_PASS }} protocol: ftps local-dir: ./ server-dir: /public_html/ exclude: ['**/.git*', '**/*.bak', '**/*.zip', 'sql/**', 'README*'] # optional: apply pending DB migrations after files land - run: curl -fsS "https://idata.ienergia.cl/_agent/migrate.php?key=${{ secrets.MIGRATE_KEY }}"
Why this action: it stores .ftp-deploy-sync-state.json on the server and compares hashes, so after the first run it uploads only what changed — faster than any full mirror, and the FTP password lives in GitHub Secrets rather than a .bat on someone's desk.
Tuned to this exact setup — shared cPanel host, a Windows team of mixed technical depth, files currently scattered across Dropbox with hand-made backups.
No path translation, no per-file mapping table. Deploy is a straight mirror. Kills the DEPLOY_*.md "local→server" tables.
State file on the server → only changed-hash files upload. A one-line edit ships in seconds, not a full re-mirror.
Config already lives outside docroot in .idata/ & .auth/. FTP creds go to GitHub Secrets. The repo is code-only.
Numbered sql/NN_*.sql applied by an idempotent migrate.php?key= the workflow curls. Removes the manual phpMyAdmin step.
main protected, 1 approval required. The PR review is the deploy gate for customer-facing iris pages.
develop → idata-staging.ienergia.cl; main → prod. Cheap insurance before a page reaches Daniel's morning review.
A reusable workflow_call parameterized by server-dir. Auth, Prism, BEAM all deploy identically. That's the real leverage.
But keep a versioned crons.txt in the repo so the schedule is reviewable even though cPanel holds the live copy.
All three end with "merge auto-deploys". They differ in where the moving parts sit. The reachability probe already confirmed the host has Terminal/SSH + crontab, so all three are technically available.
| Mechanism | How it works | Trade-off | Fit |
|---|---|---|---|
| FTPS via GitHub Actions recommended |
Action FTPS-syncs the diff using your existing FTP creds, stored as GitHub Secrets. | Credentials centralized in GitHub; full CI gate before deploy; no server-side setup. | Best for this team |
| cPanel Git Version Control | The server pulls from GitHub and runs .cpanel.yml copy rules. |
No FTP creds in CI, but the server needs outbound GitHub access and you lose the pre-deploy CI gate. | Workable |
SSH rsync |
Action rsyncs over SSH with a deploy key — cleanest diffs, preserves permissions natively. | Most efficient, but requires generating + managing an SSH deploy key per repo. | Power option |
Go FTPS via GitHub Actions. For a team of mixed technical depth it has the least server-side magic, centralizes the credentials in GitHub, works today with the FTP login you already use in FileZilla, and keeps the full CI gate. Graduate to rsync only if deploys ever feel slow.
This is the one dangerous step. The Dropbox copy holds .bak-truncated files and may be ahead or behind production. One-time lftp mirror (download) the live /public_html, commit that as the baseline, then reconcile your local edits on top. Otherwise the first auto-deploy could push stale Dropbox files over good production.
ienergia/idataUnder the org that already exists. Lay the root out to mirror the docroot 1:1.
lftp mirror down the real server → commit as "production baseline" before touching anything.
Store CPANEL_FTP_* in GitHub Secrets; drop in deploy.yml; add the .gitignore.
Require 1 PR approval. Add David, Sven, Daniel, Sebastián. First test PR → watch it deploy.
This is the cPanel lane of the repos-by-deploy-target model: idata → cPanel (this page), spark-dashboards & Solus → Cloudflare Pages, data-pipelines → local/scheduled, and the 2.9 GB of data → Cloudflare R2, never git. Same git-for-code rule, one deploy mechanism per target. See the full proposal →