1 · Prerequisites
Objective — verify the local toolchain, then create the private GitHub repo, hosting sites, and databases (and wire passwordless SSH to both servers) so nothing later in the phase depends on infrastructure that isn’t there yet.
Background
Section titled “Background”Two kinds of prerequisite gate this phase: tools on your machine and infrastructure in the cloud (repo, hosting, databases, SSH). Verify both before you git init — a missing PHP version or an unprovisioned database surfaces far more painfully three steps later.
1. Verify the local toolchain
Section titled “1. Verify the local toolchain”Confirm versions before anything depends on them. CodeCanyon Laravel apps are typically pinned to a PHP/Composer range, and a mismatch fails at composer install (Phase 3), not here.
-
Print every tool version in one pass.
Terminal window echo "PHP: $(php -v 2>/dev/null | head -1 || echo 'NOT INSTALLED')"echo "Composer: $(composer -V 2>/dev/null | head -1 || echo 'NOT INSTALLED')"echo "Node: $(node -v 2>/dev/null || echo 'NOT INSTALLED')"echo "NPM: $(npm -v 2>/dev/null || echo 'NOT INSTALLED')"echo "Git: $(git --version 2>/dev/null || echo 'NOT INSTALLED')"echo "Herd: $(herd --version 2>/dev/null || echo 'NOT INSTALLED')"# Expected: a version line for each tool — no "NOT INSTALLED"- ✅ Each tool prints a version, not
NOT INSTALLED.
- ✅ Each tool prints a version, not
-
Install only what’s missing, matching the app’s supported range (reference table below).
- ✅ Every Required tool is present and within the app’s supported range.
| Tool | Minimum | Required? | If missing |
|---|---|---|---|
| PHP | 8.1 | Yes | herd use php@8.3 or brew install php@8.3 — match composer.json require.php |
| Composer | 2.x | Yes | composer self-update (from 1.x), or the getcomposer.org installer |
| Node | 18 | Yes | nvm install 20 && nvm use 20 |
| NPM | 9 | Yes | ships with Node |
| Git | 2.x | Yes | xcode-select --install |
| Herd (or Valet/Sail/XAMPP) | any | Yes | download from herd.laravel.com |
| Atlas / Docker | any | Optional | schema tooling only — free alternatives: mysqldump --no-data, TablePlus, DBeaver, php artisan migrate:status |
2. Install editor extensions (optional)
Section titled “2. Install editor extensions (optional)”Editor extensions are a machine-wide install, covered once — canonically — in Machine setup, including the editor-CLI guard and the single-PHP-LSP rule. The repo also commits that same set as .vscode/extensions.json (Create the project), so opening the folder prompts you to install anything missing — there is one canonical list, not a second one to drift.
If you skipped AI System setup, install the canonical set now (idempotent), then continue:
CODE="$(command -v code || command -v cursor)"for ext in bmewburn.vscode-intelephense-client onecentlin.laravel-blade \ onecentlin.laravel5-snippets amiralizadeh9480.laravel-extra-intellisense \ editorconfig.editorconfig; do "$CODE" --install-extension "$ext"done# Expected: each reports "already installed" or installs cleanly- ✅ The one canonical set is installed (PHP IntelliSense via Intelephense, Blade, snippets, Laravel IntelliSense, EditorConfig).
Skip this freely — none of it blocks setup; the terminal tools work regardless.
3. Create remote infrastructure
Section titled “3. Create remote infrastructure”Provision the repo, hosting sites, and databases now so later steps have somewhere to push and run. Repo creation is agent-first (gh); site creation is agent-first when your host exposes an MCP server.
-
Create the private, empty GitHub repo (default:
ghCLI).Vendor code is licensed, not yours to publish — use Title Case for the repo name (
Waraq, notwaraq). The remote must stay empty (no README,.gitignore, or license) or your first push collides.Terminal window gh auth status # Expected: Logged in to github.com — if not, run gh auth login (Machine setup §1)gh repo create "<ORG>/Waraq" --privategh repo view "<ORG>/Waraq" --json sshUrl -q .sshUrl# Expected: git@github.com:<ORG>/Waraq.git — repo exists with zero commits/files- ✅ A private, completely empty repo exists and you have its SSH URL.
-
Create the staging + production sites (default: hosting MCP when wired).
Option 1 — 🤖 agent + hosting MCP (default when available). Use the MCP server for your provider (e.g. Hostinger MCP in Cursor/Claude global config — run
mcp_authfirst if tools return 401):- List hosting orders / existing sites (
hosting_listOrdersV1,hosting_listWebsitesV1) — confirm the MCP responds. - Production —
hosting_createWebsiteV1with<DOMAIN>+ the hosting planorder_id(anddatacenter_codeon the first site of a new plan). - Staging —
hosting_createWebsiteSubdomainV1with subdomainstagingon that website (username,domain,subdomain). - Enable/confirm SSL in the panel if the MCP does not auto-issue — Hostinger usually provisions Let’s Encrypt once DNS points at the account.
Terminal window dig +short staging.<DOMAIN> Adig +short <DOMAIN> Acurl -I "https://staging.<DOMAIN>"curl -I "https://<DOMAIN>"# Expected: both A records return the hosting IP; HTTPS responds 200 or 301- ✅ Both sites resolve over HTTPS; both IPs recorded.
- List hosting orders / existing sites (
-
Create the databases. One empty DB + a user per environment (local / staging / production) with distinct 20+ char passwords; grant
ALL PRIVILEGES; store creds in your password manager immediately.- ✅ Three empty databases exist, each with its own user/password saved to the vault.
A public repo of CodeCanyon source violates the license, and an initialized remote causes a non-fast-forward collision on your first push — so the repo must be both private and empty.
4. Configure SSH access (you → hosting, hosting → GitHub)
Section titled “4. Configure SSH access (you → hosting, hosting → GitHub)”This section wires two separate SSH paths — not GitHub login on your laptop (that is gh auth login in Machine setup for repo creation above).
| Hop | Who connects to whom | Why |
|---|---|---|
| A — Hosting SSH | Your machine → staging + production servers | Deploy, scp, ServerSync, and manual server work without passwords |
| B — GitHub SSH (on the server) | Each server → git@github.com | git pull / Deployer on the host can reach the private repo |
Steps 1–4 set up hop A (~/.ssh/config, ssh-copy-id). Step 5 verifies hop B (and adds a server-side deploy key in GitHub if needed). Repeat the block for production after staging.
Audit ~/.ssh/config first — a prior project’s host block may already point at the same IP.
-
Check for, or generate, a key.
Terminal window ls -la ~/.ssh/id_ed25519.pub 2>/dev/null \|| ssh-keygen -t ed25519 -C "your-email@example.com" # Enter for default path# Expected: the .pub path prints, or a new ed25519 key pair is created- ✅
~/.ssh/id_ed25519.pubexists.
- ✅
-
Audit
~/.ssh/configfor existing host blocks before adding new ones — prior CodeCanyon projects often left reusable aliases on the same VPS or shared host.Terminal window test -f ~/.ssh/config || touch ~/.ssh/config && chmod 600 ~/.ssh/configgrep -nE '^Host ' ~/.ssh/configgrep -B1 -A6 -E '^Host ' ~/.ssh/config 2>/dev/null \| grep -E '^Host |HostName|User|Port|IdentityFile'# Match against the staging + production IPs you recorded in step 3.2grep -nE '<STAGING_IP>|<PRODUCTION_IP>' ~/.ssh/config 2>/dev/null || true# Expected: either no hits (add fresh blocks next) or a Host block already pointing at those IPsWhat you find Action Block matches this project’s IP, user, port, and key Reuse that Hostname as<STAGING_SSH>/<PRODUCTION_SSH>in_CUSTOMIZATIONS.mdand deploy config — do not duplicate the stanza.Alias exists, wrong IP (same server re-provisioned) Update HostName(andPort/Userif needed) in place.Alias exists for another live project Add a project-scoped alias (e.g. waraq-staging) so both projects keep working.No matching block Proceed to the next step and add new entries. - ✅ You know whether to reuse, update, or create host aliases — and the chosen names are recorded for later steps.
-
Add or update host entries in
~/.ssh/config(one per server, only when step 2 did not already cover them), then lock the file down.Host <STAGING_SSH>HostName <STAGING_IP>User <SSH_USER>Port <SSH_PORT>IdentityFile ~/.ssh/id_ed25519AddKeysToAgent yesTerminal window chmod 600 ~/.ssh/config# Expected: no output (permissions set)- ✅
ssh <STAGING_SSH>resolves the alias without a full host string.
- ✅
-
Copy your key to the server, then confirm it no longer prompts for a password.
Terminal window ssh-copy-id -i ~/.ssh/id_ed25519.pub -p <SSH_PORT> <SSH_USER>@<STAGING_IP>ssh <STAGING_SSH> "echo 'Staging SSH OK' && php -v | head -1"# Expected: "Staging SSH OK" + a PHP version — and NO password prompt- ✅ The server responds without asking for a password.
-
Verify the server can reach GitHub (needed for
git pullon deploy).Terminal window ssh <STAGING_SSH> "ssh -T git@github.com 2>&1 | head -2"# Expected: "Hi <name>! You've successfully authenticated..."-
✅ GitHub authenticates. If it returns
Permission denied, generate a server-side deploy key and have the user add only the printed public key in GitHub → Settings → SSH keys:Terminal window ssh <STAGING_SSH> 'test -f ~/.ssh/id_ed25519.pub || ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N "" -C "<project>-staging"'ssh <STAGING_SSH> 'cat ~/.ssh/id_ed25519.pub'# Expected: one ssh-ed25519 public key; the user adds it in GitHub -> Settings -> SSH keysThen re-run the
ssh -T git@github.comcheck from the server.
-
Repeat for the production server.
Checklist
Section titled “Checklist”Do not mark this step done until every box below is checked.
- 🤖 Toolchain verified —
php,composer,node,gitall report supported versions. - 🤖 Repo created — a private, empty GitHub repo exists (
gh repo createor dashboard fallback) and you have its SSH URL. - 🤖 Sites reachable — staging + production both resolve over HTTPS, IPs recorded (hosting MCP default, or panel Option 2).
- 👤 Databases created — empty local + staging + production DBs, creds in the password manager (distinct per env).
- 🔀 SSH works both ways — hop A: passwordless from your machine to both servers; hop B: both servers can
ssh -T git@github.com.