<?php
/*
 * Owner-only guide: Community Updates (offline signed package).
 * Served by modules/Help/HelpController (topic = "community_updates").
 * Gated to the platform owner alone (OWNER_TOPICS).
 */
?>

<h1><i class="bi bi-arrow-repeat"></i> Owner: Community Updates</h1>
<p class="lead">Update your <strong>deployed community forum</strong> in place — no reinstall — with a
<strong>signed update package</strong>. Because the forum is hosted separately from this console, you
<strong>download</strong> the package here and an admin <strong>uploads</strong> it into the forum.
Nothing is served from this app, and the forum needs no connection back to us.</p>

<div class="alert alert-info">
    <strong>Who does what?</strong> You (the owner) <em>generate &amp; download</em> a package from
    <strong>Deliverables → Community Installer</strong>. The forum's <strong>admin</strong> uploads it under
    <strong>Admin → Software updates</strong>. Both controls are hidden from everyone else.
</div>

<div class="card border-0 shadow-sm my-3" style="background:var(--pp-surface-soft,#f8fafc)">
  <div class="card-body">
    <h5 class="mb-3"><i class="bi bi-lightning-charge-fill text-warning"></i> The routine — every release, in 5 steps</h5>
    <ol class="mb-0 ps-3" style="font-size:.95rem">
      <li>Make your changes in <code>Extra/CommunityPortal/custom/</code> (or the console's Custom Files editor).</li>
      <li>Bump the version in <code>Extra/CommunityPortal/custom/forum/version.php</code> — e.g. <code>1.0.0</code> → <code>1.0.1</code>.</li>
      <li><strong>Deliverables → Community Installer → Generate update package</strong> (add release notes).</li>
      <li><strong>Download</strong> the <code>.zip</code>, then send it to the forum's admin (or upload it yourself).</li>
      <li>On the forum: <strong>Admin → Software updates → Upload &amp; apply</strong>. Delete the package here when you're done.</li>
    </ol>
    <p class="mb-0 mt-3" style="font-size:.86rem"><i class="bi bi-shield-check text-success"></i>
      The package is signed with your key; the forum verifies it before changing a single file, and saves a restore point first.</p>
  </div>
</div>

<h2>Contents</h2>
<ul>
    <li><a href="#how">How it works (30-second version)</a></li>
    <li><a href="#setup">One-time setup — enabling updates on a forum</a></li>
    <li><a href="#generate">Generating &amp; downloading a package (you)</a></li>
    <li><a href="#apply">Uploading &amp; applying it (the forum admin)</a></li>
    <li><a href="#safe">What changes — and what is never touched</a></li>
    <li><a href="#security">Why it's safe: signing &amp; the pinned key</a></li>
    <li><a href="#trouble">Troubleshooting</a></li>
</ul>

<h2 id="how">How it works (30-second version)</h2>
<ul>
    <li>Generating a package builds the forum, then wraps two things into one <code>.zip</code>:
        a <strong>signed manifest</strong> (version + payload checksum, signed with your Ed25519 key)
        and the <strong>payload</strong> (the forum files to overlay).</li>
    <li>Each forum ships with your <strong>public key pinned</strong> in a file. On upload it
        <strong>verifies your signature</strong>, checks the payload's checksum against the signed manifest,
        and confirms the version is newer — before touching anything.</li>
    <li>It then <strong>backs up</strong> the files it will replace and overlays the new code, skipping all
        data and config.</li>
</ul>
<p>A random or tampered ZIP can't be applied: without your secret key, nobody can produce a package whose
signature the forum accepts, and any change to the payload breaks its checksum.</p>

<h2 id="setup">One-time setup — enabling updates on a forum</h2>
<p>Updates work once a forum has <strong>your public key baked in</strong>. New downloads get it
automatically; a forum installed <em>before</em> this feature needs a one-time refresh to receive the key.</p>

<div class="alert alert-warning">
    <strong>You need a signing key first.</strong> If you've ever issued a licence, you already have one.
    Otherwise create it in <strong>Licensing → Signing key</strong> before generating a package — the key's
    public half is what gets pinned into every forum.
</div>

<h4 class="mt-3">A brand-new forum</h4>
<ol>
    <li>Set the version in <code>forum/version.php</code>, then <strong>Deliverables → Community Installer → Rebuild</strong>.</li>
    <li><strong>Download ZIP</strong> (the installer) and install it. The download bakes your public key into
        <code>forum/update-key.php</code>, so updates are enabled from day one.</li>
</ol>

<h4>A forum that's already live (installed before this feature)</h4>
<ol>
    <li>Rebuild + Download a fresh installer ZIP — this copy carries your pinned key.</li>
    <li>Re-deploy it over the live forum <strong>once</strong>, preserving <code>config/</code>,
        <code>uploads/</code>, <code>storage/</code> and the database. After this one manual step, it accepts
        uploaded update packages forever after.</li>
</ol>

<div class="alert alert-secondary">
    <i class="bi bi-info-circle"></i> A forum with no key pinned shows
    <em>“Updates aren't enabled for this install”</em> under Admin → Software updates — it never breaks, it
    just waits for a keyed build.
</div>

<h2 id="generate">Generating &amp; downloading a package (you)</h2>
<ol>
    <li><strong>Make your changes</strong> in <code>Extra/CommunityPortal/custom/</code> or the console's
        <strong>Custom Files</strong> editor.</li>
    <li><strong>Bump the version.</strong> Edit <code>forum/version.php</code> and return the new number, e.g.
        <code>return '1.0.1';</code>. This is the single source of truth — a forum accepts a package only when
        its version is higher than the forum's own.</li>
    <li>Open <strong>Deliverables → Community Installer</strong>. In the
        <strong>“Signed update package”</strong> card, write short <strong>release notes</strong> and click
        <strong>Generate update package</strong>.</li>
    <li>The server rebuilds, packages, signs it, and lists it under the button as
        <code>community-update-<em>&lt;version&gt;</em>.zip</code>. Click <strong>Download</strong>.</li>
    <li>Hand the file to the forum's admin (email, chat, a file drop — anything). When it's applied, click
        <strong>Delete</strong> to remove the package from this server.</li>
</ol>

<div class="alert alert-info">
    <strong>Where packages live.</strong> They're held in <code>storage/community-updates/</code> — a private
    folder that is <strong>never web-served</strong>. They don't touch your hosting or the marketing site, and
    the Delete button clears them whenever you like.
</div>

<h2 id="apply">Uploading &amp; applying it (the forum admin)</h2>
<p>On the deployed forum, an administrator:</p>
<ol>
    <li>Opens <strong>Admin → Software updates</strong> (the moderation dashboard).</li>
    <li>Chooses the <code>.zip</code> you sent and clicks <strong>Upload &amp; apply update</strong>.</li>
    <li>The forum verifies your signature and the checksum, confirms it's newer, saves a
        <strong>restore point</strong>, and overlays the new files. The version bumps itself.</li>
</ol>
<p>One page, no shell access, no reinstall, no connection back to your console.</p>

<h2 id="safe">What changes — and what is never touched</h2>
<div class="table-responsive">
<table class="table table-bordered align-middle">
    <thead><tr><th>Updated by a package</th><th>Never touched (your live data &amp; config)</th></tr></thead>
    <tbody>
        <tr>
            <td>
                <ul class="mb-0 ps-3">
                    <li><code>forum/</code> pages &amp; helpers</li>
                    <li><code>core/</code>, <code>app/</code>, <code>generated/</code>, <code>vendor/</code></li>
                    <li><code>forum/version.php</code> (arrives in the payload → self-bumps)</li>
                </ul>
            </td>
            <td>
                <ul class="mb-0 ps-3">
                    <li><code>config/</code> — DB credentials &amp; <code>installed.lock</code></li>
                    <li><code>uploads/</code> — attachments / avatars</li>
                    <li><code>storage/</code> — backups &amp; temp</li>
                    <li>the database — members, questions, answers, settings</li>
                    <li><code>*.sqlite</code>, <code>database.sql</code>, <code>.htaccess</code></li>
                    <li><code>forum/update-key.php</code> — the pinned trust key</li>
                </ul>
            </td>
        </tr>
    </tbody>
</table>
</div>
<div class="alert alert-success">
    <i class="bi bi-hdd-fill"></i> All the forum's <strong>live state lives in the database</strong>
    (including admin-editable Site settings), so a code overlay can't lose it. Every apply also writes a
    timestamped copy of the files it replaced to <code>storage/backups/community-&lt;date-time&gt;/</code>.
</div>

<h2 id="security">Why it's safe: signing &amp; the pinned key</h2>
<ul>
    <li><strong>Signed manifest.</strong> Inside the package, <code>update.json</code> is a
        <code>PPCU1.&lt;payload&gt;.&lt;signature&gt;</code> token — an Ed25519 signature made with your platform
        signing key's <em>secret</em> half, which never leaves this server.</li>
    <li><strong>Pinned public key.</strong> Each forum stores your <em>public</em> half in
        <code>forum/update-key.php</code>. It's a file, not a setting — and an update can never overwrite it, so
        the trust anchor can't be swapped.</li>
    <li><strong>Checksum binding.</strong> The signed manifest carries the payload's SHA-256; the forum refuses
        any package whose payload doesn't match — so a tampered ZIP is rejected.</li>
    <li><strong>Safe extraction.</strong> The payload is unzipped to a staging folder with every entry checked
        for path-traversal before anything is copied into place.</li>
</ul>

<h2 id="trouble">Troubleshooting</h2>
<div class="table-responsive">
<table class="table table-bordered align-middle">
    <thead><tr><th>Symptom</th><th>Cause &amp; fix</th></tr></thead>
    <tbody>
        <tr>
            <td>Forum says <em>“updates aren't enabled (no publisher key)”</em></td>
            <td>That install predates the feature (or was built before you had a signing key). Rebuild + re-deploy a fresh installer once — see <a href="#setup">One-time setup</a>.</td>
        </tr>
        <tr>
            <td>Generate fails: <em>“No signing key exists”</em></td>
            <td>Create one in <strong>Licensing → Signing key</strong>, then generate again.</td>
        </tr>
        <tr>
            <td>Generate fails: <em>“no valid forum/version.php”</em></td>
            <td>Add <code>&lt;?php return '1.0.1';</code> to the community source's <code>forum/version.php</code> and Rebuild.</td>
        </tr>
        <tr>
            <td>Upload: <em>“could not be verified against the pinned key”</em></td>
            <td>The forum's pinned key doesn't match the key that signed the package (e.g. the signing key was regenerated). Re-deploy a fresh keyed installer to that forum.</td>
        </tr>
        <tr>
            <td>Upload: <em>“already on this version”</em></td>
            <td>The package's version isn't higher than the forum's. Bump <code>forum/version.php</code>, regenerate, and upload the new package.</td>
        </tr>
        <tr>
            <td>Upload: <em>“larger than this server allows”</em></td>
            <td>Raise <code>upload_max_filesize</code> / <code>post_max_size</code> on the forum host, or apply the package by hand (unzip <code>bundle.zip</code> and overlay, keeping config/uploads/storage/database).</td>
        </tr>
        <tr>
            <td>Upload: <em>“install directory is not writable”</em></td>
            <td>PHP can't write the forum's files on that host. Fix directory permissions, or overlay by hand as above.</td>
        </tr>
    </tbody>
</table>
</div>

<div class="alert alert-secondary mt-4">
    <i class="bi bi-lightbulb"></i> <strong>Rolling back.</strong> Each apply saves the replaced files under
    <code>storage/backups/community-&lt;date-time&gt;/</code> on the forum's server. To undo an update, copy that
    folder's contents back over the install (your data is untouched either way).
</div>
