gh_sync_docs.html (6656B)
1 {% extends "base.html" %} 2 {% set page_title = 'GitHub Actions sync' %} 3 {% set page_description = "Push any repo's files to the CDN automatically on every git push." %} 4 {% block page_class %}prose{% endblock %} 5 6 {% block content %} 7 <h1>Auto-sync any repo to the CDN <span class="badge">GitHub Actions</span></h1> 8 <p>Add one workflow file to a repo. On every push, its contents land in the CDN under 9 <code>/<your-github-username>/<repo-name>/</code> — no PAT, no shared secret, and it's 10 physically impossible for your workflow to write into anyone else's folder.</p> 11 12 <h2>How it stays safe</h2> 13 <p>Instead of a token you paste in, your repo asks GitHub itself for a short-lived 14 <strong>OIDC identity token</strong> for the run. That token's "who and where" fields 15 (<code>repository_owner</code>, <code>repository</code>) are stamped by GitHub's own servers from the 16 real run context — a workflow file can't edit itself to lie about which repo it's running in. 17 The CDN backend verifies that token's signature, reads the owner/repo straight from it, and uses 18 <em>only that</em> to decide where your files go. Nothing in your request body or headers is trusted 19 for the destination path.</p> 20 <p>The same token also says <em>who</em> you are, by GitHub's permanent numeric account ID — renaming 21 your account, or someone registering a username you dropped, doesn't change that identity. 22 That's what the approval step below is based on.</p> 23 24 <h2>1. Add the workflow</h2> 25 <p>Create <code>.github/workflows/cdn-sync.yml</code> in the repo you want mirrored. The token is 26 short-lived and scoped to your repo already, but <code>core.setSecret()</code> tells GitHub to mask it 27 from the run's logs, and passing it through <code>env:</code> instead of straight into the shell 28 command keeps it out of the process list too — worth keeping both even though the token itself 29 is harmless if it leaks (it expires in minutes and only proves who you are, it can't push files on its own).</p> 30 <pre>{% raw %}name: Sync to CDN 31 32 on: 33 push: 34 branches: [ main ] 35 workflow_dispatch: 36 37 permissions: 38 id-token: write # required — this is what lets us prove who you are 39 contents: read 40 41 jobs: 42 sync: 43 runs-on: ubuntu-latest 44 steps: 45 - uses: actions/checkout@v7 46 47 - name: Request GitHub OIDC token 48 id: oidc 49 uses: actions/github-script@v7 50 with: 51 script: | 52 const token = await core.getIDToken('cdn.amit.is-a.dev') 53 core.setSecret(token) 54 core.setOutput('token', token) 55 56 - name: Package repo contents 57 run: tar --exclude='.git' -czf /tmp/payload.tar.gz . 58 59 - name: Push to CDN 60 env: 61 OIDC_TOKEN: ${{ steps.oidc.outputs.token }} 62 run: | 63 curl -sS --fail-with-body -X POST https://cdn.amit.is-a.dev/api/gh-sync \ 64 -F "token=$OIDC_TOKEN" \ 65 -F "archive=@/tmp/payload.tar.gz" 66 {% endraw %}</pre> 67 68 <h2>2. Push</h2> 69 <p>That's it — no repo secrets to create. On the next push, your files land at:</p> 70 <pre>https://cdn.amit.is-a.dev/<your-username>/<repo-name>/</pre> 71 <p>Need a raw link to one specific file instead of the folder listing? Append its path to the raw 72 domain: <code>https://raw.cdn.amit.is-a.dev/<your-username>/<repo-name>/<file></code>.</p> 73 74 <h2>Notes</h2> 75 <ul> 76 <li><strong>First sync is free, later ones need approval.</strong> The first sync from a new GitHub account 77 goes through immediately. After that, further syncs from that account are refused until the CDN owner 78 approves it, so after your first push, ask for approval. Approved accounts sync freely from then on. 79 Accounts that upload abusive content are banned.</li> 80 <li><strong>No videos.</strong> Any file type is accepted <em>except</em> video, which is detected by file 81 extension and by file contents (renaming won't help). If the repo contains one, the whole sync is 82 rejected and nothing is uploaded. Leave videos out of the archive, for example by adding 83 <code>--exclude='*.mp4'</code> to the <code>tar</code> command.</li> 84 <li><strong>No rate limit, but the compressed archive can be at most 200 MB.</strong> There's no limit on file count or on how often you sync. What protects it instead is your 85 sign-in identity, the approval rule, bans and the no-video rule. Keep the archive small by leaving build output out, for example 86 <code>--exclude='node_modules'</code> in the <code>tar</code> command. 87 <a href="/documentation#rate-limits">How rate limits work</a>.</li> 88 <li>Each sync fully replaces the contents of <em>your own</em> <code>username/repo</code> folder only 89 (delete-then-upload), so removed files disappear from the CDN too. No other folder is ever touched. 90 The storage keeps no version history, so a removed or overwritten file can't be brought back — keep the 91 original in your repo.</li> 92 <li>Change <code>branches: [ main ]</code> to whichever branch you want mirrored.</li> 93 <li>Want it on every branch/tag push instead? Adjust the <code>on:</code> block — the destination 94 folder is still locked to your username/repo either way.</li> 95 </ul> 96 97 <h2>If a sync is refused</h2> 98 <p>The step now shows the server's reason in the Actions log (that's what <code>--fail-with-body</code> is for).</p> 99 <ul> 100 <li><code>403</code> <em>"First sync from '<you>' was accepted; further syncs need approval…"</em> — your account 101 has already synced once and is waiting to be approved. Nothing is wrong with your workflow; ask the CDN owner to 102 approve the account, then re-run the job.</li> 103 <li><code>413</code> <em>"Archive too large…"</em> — the compressed archive is over 200 MB. Exclude build output and dependency folders (<code>--exclude</code>) and run it again.</li> 104 <li><code>403</code> <em>"…banned…"</em> — the account (or the network the job ran from) has been blocked.</li> 105 <li><code>415</code> <em>"Video files are not allowed…"</em> — the message names the file; remove it from the repo 106 or exclude it from the archive.</li> 107 <li><code>401</code> — the identity token was missing, expired or not for this service. Check that 108 <code>permissions: id-token: write</code> is set and that the audience is <code>cdn.amit.is-a.dev</code>.</li> 109 110 </ul> 111 <p>The full request/response details are in the <a href="/documentation#gh-sync">documentation</a>.</p> 112 {% endblock %}