Leaving GitHub: A Decentralized Git Alternative on Nostr
GitHub is one company, one jurisdiction and one takedown notice away from your project being a 404. That is why we moved Armada off it and onto Nostr. Here is what actually replaced it, what it cost us, and why the daily loop is still just git push.
1. The case against GitHub
On July 23, India's Cyber Crime Coordination Centre gave GitHub three hours to disable three repositories. The repositories were bitchat, a messaging app that works over Bluetooth mesh without an internet connection. The notice came from a body under the Ministry of Home Affairs, cited Section 79(3)(b) of the Information Technology Act, 2000, and carried the standard consequence: comply or lose the intermediary protection that keeps a platform from being liable for what its users post. The stated reason was that bitchat keeps working "even during network restrictions" and "internet shutdowns," which lets people "circumvent lawful restrictions" and hampers "lawful interception, attribution, and traceability." Protesters at Jantar Mantar, where mobile internet had been shut off, had started using it.
Here is what I take from it. A ministry in one country put a three-hour clock on three repositories, and the only thing standing between that clock and the code was one American company's willingness to absorb the liability. That willingness is real. It is also a judgment call, made by people who could decide differently next time, under a different government, in a different jurisdiction, about a project with less press attention than one associated with Jack Dorsey. The Internet Freedom Foundation made the sharper point about the order itself: "The order also fails on its own terms as deleting a repository does not delete the application from any phone that carries it." True. But it would have deleted the place everyone goes to get it, and the issues, and the pull requests, and the history of how it was built.
We know what that looks like, because the call has gone the other way before.
In October 2020 the RIAA sent GitHub a DMCA notice claiming youtube-dl violated Section 1201, the anti-circumvention provision. GitHub took the repositories down on October 23. It reinstated them on November 16, after a letter from the EFF, and went further than it had to: it revised how it handles 1201 claims and put a million dollars into a developer defense fund. That is a genuinely good outcome. It also took three and a half weeks, and for three and a half weeks the canonical home of one of the most widely used tools on the internet was a 404.
In August 2022, OFAC added Tornado Cash to the sanctions list. GitHub took down the repository and suspended two contributors' accounts. Not the project's accounts. The developers' accounts. The repository was later restored in read-only form once OFAC clarified that copying open-source code was not prohibited, and the sanctions themselves were lifted in March 2025. Reversed again, eventually. In the meantime, people lost the account their professional identity lived in because of a listing decision they had no part in and no process to contest.
Both of those ended fine. That is precisely the thing I want you to notice. The question was never whether the code survives, because git is distributed and the code always survives on someone's laptop. The question is who decides, how fast they can be made to decide, and what your project looks like during the interval while it is being decided.
The other half
The takedown story is the dramatic one. The one that actually costs us time is that GitHub keeps falling over.
I am not going to quote you an uptime percentage, because GitHub does not publish one. Its availability reports count incidents, not availability. So here are the incidents, in GitHub's own words, from its own reports.
May 2026: nine incidents. On May 15, "42% of Actions runs failed." On May 6, "approximately 17.1% of jobs requesting a standard runner failed" over two and a half hours. also on May 6, creating new pull request review threads failed for close to four hours, because a 32-bit integer key in a Vitess lookup table hit its maximum value.
June 2026: six incidents. On June 8, "roughly 34% of requests" for pull requests, issues, releases and patch diffs errored for unauthenticated users. On June 10, the REST and GraphQL APIs returned authentication failures on about 9% of requests. On June 4, Copilot code review hit a "peak failure rate of 93.9%."
None of that is mysterious, and GitHub has been open about why. By The Register's reporting, GitHub handled one billion commits in all of last year and now takes 1.4 billion every month, and a planned tenfold capacity expansion was revised to thirtyfold in February. The same June report notes that monolith traffic in Azure "peaked at 45% in Central US this month" and git in Azure "grew from 30% to a peak of 43%." They are rebuilding the engine at speed, while the load grows faster than the plan.

That chart is not GitHub's number and I am not presenting it as one. It is what you get when someone outside the company counts the incidents themselves over a rolling window. Your own experience of GitHub over the last few months probably sits closer to that line than to the four nines you would assume.
One company, one jurisdiction, one takedown
I want to be fair to GitHub here, because the fair version is the more damning one.
GitHub handles government takedowns about as well as a company in its position can. It publishes the notices it receives in a public repository so the record exists. Its stated policy is that when a notice arrives from "a relevant, official government agency that identifies illegal content and specifies the source of the illegality," it notifies the affected users, and that it will "limit the geographic scope of the takedown when possible." It fought the RIAA and it funded the defense of developers it did not have to fund.
None of that is the problem. The problem is the shape.
Your repository has one canonical location. That location resolves to one company. That company has one legal address, in one country, and it holds accounts, issues, pull requests, releases and CI for tens of millions of projects at that single address. Every government with reach into that address gets a lever, and the lever works on everything at once.
Good policy makes the lever harder to pull. It does not remove the lever.
Git solved this for the code twenty years ago. Every clone is a complete copy of the history, and that part was never at risk. What we handed back to a single company was everything around the code: where it lives, what it is called, how people find it, where the conversation happens, and what runs when you push.
So that is the part worth taking back. What we moved to is not another GitHub, and that difference is most of what the rest of this post is about.
2. What replaces it
ngit is the tool. It installs two binaries. ngit is the command you run, and git-remote-nostr is a git remote helper, which is the piece that matters most and gets the least attention. A remote helper teaches git a new URL scheme. Once it is on your PATH, nostr:// URLs work with plain git clone, plain git fetch, plain git push. No wrapper, no special client, no ngit-specific command in the daily loop. Armada's origin is a nostr:// URL and everything downstream of it is ordinary git.
NIP-34 is the specification underneath. It defines a repository announcement, a repository state event that carries branches and tags, and event kinds for patches, pull requests, issues, and their statuses. It is marked draft and optional, so it is not a finished standard. It is a working one, and that is the only claim I can support.
The idea in NIP-34 I find genuinely clever is how a project is identified. Not by a URL, and not by a name someone owns. By the commit ID of the earliest unique commit in its history, tagged euc. The spec's own words: it exists "to identify it among forks and group it with other repositories hosted elsewhere that may represent essentially the same project." Your repository's identity is a fact about your history, which means nobody assigns it and nobody can take it away. Anyone can announce your project. Announcements that share an euc are understood to be the same project.
GRASP is the hosting shape. A GRASP server is a git host and a Nostr relay at the same hostname. NIP-34 does not define it with a paragraph, it defines it with a detection rule: a host is a GRASP server for a repository if the same hostname appears in the announcement's clone tag as an HTTPS git URL and in its relays tag as a websocket URL. When you look at an announcement and see the same domain listed twice, that is not sloppy duplication. That is the declaration.
GRASP's own site puts the consequence more bluntly than the spec does: git servers do not have to be trusted. What you check is the signature on the state event, not the word of whichever host handed you the objects. That is what makes an extra mirror free. It is one more copy, not one more party you are now depending on.

Armada announces three of them: git.shakespeare.diy, relay.ngit.dev, and git.nostrhub.io. Three hosts, three separate operators, all serving the same repository, all discoverable from one event. If one of them goes down or decides it does not want to carry the project, the other two do not care and neither do you. That is the whole argument compressed into one tag.
Which is where the words in the title start pulling against each other, and they should. Self-hosting normally means one box you own and one address everyone depends on, and a thing that is yours to run is also yours to lose. Here it describes where you publish from, not where the repository stops. You push to your own host, and the same push is carried by hosts run by other people who never asked you and never needed to. The screenshot below names five GRASP servers where Armada announces three. gitnostr.com and ngit.danconwaydev.com are carrying this repository and neither appears in its announcement. On GitHub a fork is a separate repository at a separate URL. Here it is the same project, and it stays reachable whether or not the box with your name on it is still up.
Be precise about what that redundancy buys. It protects you against losing a host. It does not protect you against your own state event, which every one of those hosts follows without question. That is in section 3, and it is the part of this design I would most want to know before moving a project I cared about.
Armada is our proof that this works on a real project rather than a demo.

3. How it actually works
Cloning
Clone Armada over Nostr
git clone nostr://npub10qdp2fc9ta6vraczxrcs8prqnv69fru2k6s2dj48gqjcylulmtjsg9arpj/relay.ngit.dev/armadaThat is Armada. It pulls 13,084 objects, 6.81 MiB, resolves 9,311 deltas, and exits zero. The npub is the maintainer identity, relay.ngit.dev is one of the GRASP servers, armada is the repository ID. No account needed to clone.
Look at what you get back. 29 remote refs, 112 tags, and 14 branches under pr/. Those pr/ branches are the open pull requests. They are not hidden in a web UI you have to log into. They are remote-tracking branches, visible to git branch -r, checkoutable with plain git.
Pull requests
This is the part I most expected to be complicated and it is the part I was most wrong about. You do not open a pull request with an ngit command. You push a branch:
Open a pull request
git push -u origin pr/my-branchThe pr/ prefix is the whole mechanism. Title and description ride along as git push options:
Title and description as push options
git push -o 'title=My PR' -o 'description=line1\n\nline2' -u origin pr/branchngit --help says it plainly: "remote branches beginning with pr/ are open PRs from contributors."
Under the hood ngit picks the encoding by size. Small changes go out as patch events, larger ones as a pull request event, and the threshold ngit's own help names is about 60 kb per commit. ngit send exposes --force-pr and --force-patch to override it, with one asymmetry worth knowing: you cannot force a patch once a proposal already exists as a PR, because "downgrades are not possible."
ngit pr covers the rest of the lifecycle: list, view, checkout, apply, send, close, reopen, ready, draft, comment, merge, label, set-subject, set-cover-note. ngit issue mirrors it: list, view, create, close, resolved, reopen, comment, label, set-subject, set-cover-note.
Merging is two steps, and the second one is the one that publishes. ngit merge <pr-id> checks out main and makes a real two-parent no-ff merge commit locally. It does not push. The subsequent git push origin main is what publishes the kind 1631 event flipping the PR to applied. If you come from a green merge button that did both, you will forget the second one at least once.
Pass the PR id explicitly, from the ID column of ngit pr list. Bare ngit merge is documented to work while you are standing on a pr/ branch, and on a branch we had pushed ourselves it did not:
What bare ngit merge said instead
Error: branch 'pr/my-first-proposal' does not encode a PR id and no PR you
authored matches it; specify a PR event-id or neventThe branch has to encode the id, which is what ngit pr checkout produces. That is why the pr/ branches in a cloned repository look like pr/channel-ordering(0c9048a5).
The state event is the authority, and that cuts both ways
There are two things that claim to know where your branches point: the git servers, and the Nostr state event. When they disagree, the state event wins, and it wins everywhere at once.
We tested this deliberately. We published a state event moving main back to the root commit and then ran no ngit command at all. Within ten seconds all three GRASP servers had rewound main to match, walking a merge commit off the branch. We published a state event moving it forward again and all three followed again, also inside ten seconds. Nobody pushed anything. The servers watch the state event and apply it.
That is the design working as intended, and it is the sharpest edge in the whole model. Anyone who can sign for the maintainer npub can rewind a project's default branch on every host simultaneously. In our testing nothing appeared to enforce monotonicity, so replaying an old state event had the same effect as publishing a new one.
Your signing key is not just your identity here. It is the write authority over every copy of the repository.
Guard the key accordingly
There is no per-host force-push protection standing behind it the way a branch protection rule stands behind GitHub. Use ngit account login --local so a machine-wide identity is not silently publishing for you, and treat maintainer keys with the seriousness you would give a signing key rather than a password.
The practical version of this shows up in ngit sync, which has a direction and it is worth knowing which way it points before you run it:
Which direction sync points
ngit sync # servers <- state event (state wins)
ngit sync -t # state event <- servers (servers win)Bare ngit sync is the destructive direction. -t, whose help text reads "trust git server(s) that are fast-forward ahead of nostr state and update nostr state to match; only applies to clean fast-forwards, diverged refs must be resolved manually," is the one you want when the servers are ahead and correct.
Drift does happen. As I write this, Armada's state event says main is at v0.44.0 while the GRASP server is serving v0.44.1. One command fixes it, and you have to know which direction to run it in. I cannot tell you how it got that way. We tried to reproduce the condition deliberately and could not. GitHub never made you think about any of this, because GitHub was the only copy.
The numbers on Armada today
Run inside a fresh nostr:// clone:
| Query | Result |
|---|---|
ngit pr list (open and draft) | 14 |
ngit pr list --status open,draft,closed,applied | 35 |
ngit issue list (open) | 40 |
ngit issue list --status open,closed,resolved | 93 |
14 open pull requests, matching the 14 pr/ branches exactly. 40 open issues with labels and comment threads. Real ones, not test data: "Add full i18n support with en/ru/uk locales", "Encode relay hints in GitWorkshop links", "Improvements to GIF implementation" with three comments, "Armada doesn't open as a share target."
CI without GitHub
Armada has no .github/ directory, instead the workflows live in .ngit/act/workflows/. There are four: test.yml, deploy-web.yml, desktop.yml, release.yml.
Open one and it is a GitHub Actions file. on:, jobs:, runs-on: ubuntu-latest, steps:. The syntax did not change. What changed is what reads it. ngit-ci is a self-hosted, Nostr-native coordinator that watches the repository announcement and runs those workflows with act, one Linux container per job, publishing results and build artifacts back to Nostr where they show up on gitworkshop.dev against the commit or the PR.

e8cb0ff, rendered in the channel from the events the coordinator published. No GitHub Actions tab was involved in producing this.The easiest part of the move
That is the single lowest-friction part of this migration.
One nice detail: test.yml's pull_request: trigger fires on Nostr PR events, not on a GitHub PR. The trigger name is the same, the thing triggering it is a Nostr event.
The cost lands somewhere, and it lands on you: you are the runner operator now. ngit-ci maps runs-on: ubuntu-latest to an image you maintain, and ours bakes in the JDK and Node toolcaches, the Android SDK, ruby and fastlane, the zsp binary, wine, and warm ~/.gradle, ~/.npm and electron caches. That is not tuning. The workflow's own header comment records what happens without it: "every run re-downloaded ~700 MB of toolchain+deps and blew the coordinator's 30-min default job timeout." There is also no GITHUB_TOKEN. Nothing is injected for you, secrets are provisioned per repository by the ngit-ci operator, and they are only populated on maintainer-authored triggers, so a third-party PR runs with empty secrets.
And the coordinator is a machine someone runs. Ours is a box with Docker, a local image registry, and the coordinator process holding its own Nostr key and watching the repository announcement. Our image build script says so in its first line, "Build and publish the armada-ci runner image on the ngit-ci coordinator host", and pushes the result to localhost:5000 on that same box. We did not find a shared or public coordinator you can sign up for. If you adopt this, budget a build server.
Releases
The version lives in the git tag and nowhere else. package.json still says 0.1.0 and is not a release input. Cutting a release is two steps: one commit that edits CHANGELOG.md, then push a tag vX.Y.Z. Everything after the tag is automatic.
release.yml derives both Android version fields from the tag:
release.yml derives the Android version from the tag
VERSION_NAME="${TAG#v}" # v0.44.1 -> 0.44.1
VERSION_CODE="$(( MAJOR * 1000000 + MINOR * 1000 + PATCH ))" # v0.44.1 -> 44001That arithmetic is a scar. On GitLab the version code was $CI_PIPELINE_IID, a number the CI provider hands you. ngit-ci has no pipeline counter, so the first port used git rev-list --count HEAD. But ngit-ci's runner shallow-fetches only the tagged commit, so the count was always 1. Every release got the same version code, and Zapstore silently dropped them as duplicates. Deriving the number from the semver tag made it deterministic and independent of checkout depth.
That is the flavor of problem you get. Not "this is impossible," but "the ambient thing your CI provider was quietly giving you is gone, and you did not know you were using it."
Where the artifacts go, none of them GitHub Releases:
| Artifact | Destination |
|---|---|
| Signed Android APK and AAB | ngit-ci run artifacts, visible on gitworkshop.dev |
| Signed APK | Zapstore, via zsp --json publish |
| AAB | Google Play production track, via fastlane supply |
| Linux AppImage and deb, Windows NSIS and portable | ngit-ci run artifacts |
| Web client | rsync over SSH to armada.buzz |
You can verify the Zapstore half yourself. Query the Zapstore relay for release events by our publishing identity and you get real releases from this pipeline:
Verify the Zapstore releases yourself
$ nak req -k 30063 -a 781a1527...9fdae5 -l 5 wss://relay.zapstore.dev
2026-07-30T01:46:50 buzz.armada.app@0.44.0
2026-07-29T18:58:48 buzz.armada.app@0.43.0
2026-07-28T06:27:12 buzz.armada.app@0.42.24. What Buzz adds
Everything above is NIP-34 and works with any client. Buzz sits on top and adds three things I have not found elsewhere.
A pull request knows which conversation produced it. buzz pr open takes a --channel flag: "Channel where this pull request originated." The PR event carries a tag pointing back at the chat channel where the work was decided. Six months later, the question is never "what does this diff do," it is "why did we do it this way," and the answer is usually in a conversation nobody linked. This links it at creation time.
Branch and tag protection. buzz repos protect manages branch and tag protection rules on your repositories. NIP-34 has no concept of protection at all, which is the gap behind the state event problem in section 3: nothing in the spec stands between a signed state event and every host applying it. This is Buzz-specific and it is the right shape of answer. I will not claim it closes that hole, because we have not tested it against a rewound state event. When we have, I will say so.
The PR surface is explicit about the wire format. buzz pr open publishes kind 1618, buzz pr update publishes 1619, and buzz pr status sets kinds 1630 through 1633 for open, merged, closed, and draft. The help text names the kinds itself, so you are never guessing what a command wrote to the network.
One rough edge, since I have been listing them. buzz repos create has no --euc flag. Announce a repository through Buzz and the announcement will not carry the earliest-unique-commit tag, which means it will not group with announcements made by ngit for the same project. We have exactly that situation on Armada right now: an announcement that is perfectly valid, reachable through buzz repos get, and invisible to an ngit euc query. If you announce with both tools, expect two views of your project until this is fixed.

euc tag, which is the rough edge above in dialog form.
5. Reading a repository inside Armada
Everything so far is a command line. Armada is the other half: our own client, where a repository stops being a thing you query and becomes a thing your team is sitting in.

Connecting a repo to a channel
You connect a repository to a channel, not to a community. Either at the moment you create the channel, or afterwards from the community info panel under "Connected repositories." Both open the same picker, which takes a search over the public repository directory or a pasted address. That address is either an naddr1… or the nostr:// URL ngit already wrote into your .git/config. If you give it a NIP-05 owner instead of an npub, it resolves it properly rather than trusting a profile field.


naddr1… identifies a repository without naming a host to fetch it from.
It writes no new event kind. The coordinate goes into the channel's own metadata record, which is why it needs the manage-channels permission and nothing else. What it stores is not a flag but an interval: attached at this time, detached at that one. Disconnect a repository later and the interval closes rather than disappearing, so the activity that happened while it was connected keeps rendering in the window it belongs to. A channel holds one repository, deliberately. Two would blend two projects into one timeline.
What shows up
Four things, in the channel, in line with the conversation: an issue or pull request opening, comments on it, status changes, and CI runs from ngit-ci. The comments are live rather than a mirror. You can reply to an issue from the channel, edit or delete your own comment, attach a file, and set a ticket's status without leaving the chat. Every card also links out to gitworkshop.dev for the public view.

Now the gap, because it is the obvious expectation. Commits do not appear. NIP-34's repository state event, kind 30618, is the one that carries branch and tag tips, and Armada does not read it yet. There is no push row, no branch row, no commit feed. The repository announcement is read too, for the repo's name and clone URLs and for deciding whose status changes to trust, but it is not itself a message: connecting a repository does not announce itself in the channel. And the CI kinds are an experimental NIP-34 extension whose trust model is, in the code's own words, none. Anyone can publish a run result. We render it the way gitworkshop does and I would not build a merge gate on it.
The Projects view
Once any channel in a community has a repository connected, a Projects entry appears in the sidebar. It folds every channel's repositories into one view and, unlike the channel timeline, it is not gated on those attachment intervals. This is where a repository's full history lives.
Four tabs: Overview, Repositories, Pull Requests, Issues. Counts for repositories, pull requests, issues and people; a contribution graph; a people roster; an activity feed; repository cards with clone URLs. Buzz workspaces get the same view at the relay level, covering every NIP-34 repository on the relay rather than the ones your channels happen to be connected to.

And on the web
nostrhub.io is a web viewer, and it is where you send someone who does not have the app. A repository page there has four tabs. Code, Issues, Merge requests, Discussion. git.nostrhub.io, one of Armada's three GRASP hosts from section 2, is the same operator wearing its other hat.
The Code tab is the one worth explaining, because of how it works. There is no server rendering your files. Your browser shallow-clones the repository over git smart-HTTP, preferring the GRASP mirrors because those serve CORS headers, and then browses the result locally. You get a file tree with breadcrumbs, a branch picker and a tag picker, the current commit SHA, a file search palette, and a viewer with line numbers, a raw toggle, rendered Markdown, copy, download, and a link to the exact file you are looking at. The README renders under the listing, the way you expect. Issues and merge requests carry their full comment threads, a status pill, the patch as a colored diff, and an ahead-behind count against the target branch, which is computed with a partial clone that fetches commits without their file contents so the number is cheap on a large repository.

6. Moving your project
Here is the whole thing, in order. It is about thirty minutes for a repository you already control.
1. Install ngit. This is the line ngit prints for itself:
curl -Ls https://ngit.dev/install.sh | bashYou get ngit and git-remote-nostr. Confirm both are on your PATH. The remote helper is the one that makes nostr:// work in plain git, so if git clone nostr://... says the protocol is unknown, that binary is not where git can see it.
2. Log in to the repository, not the machine.
ngit account login --localsaved login details to local git config. you are only logged in to this local repository.
logged in as npub1r7mxut3ju6...Use --local. Without it, ngit writes your login into your global git config, where it becomes the identity every repository on that machine publishes under. ngit account whoami --json shows local, global and which one is active; local wins. ngit account also has connect, logout, export-keys, create, and whoami.
3. Try it read-only first, on a repository that is already there. Do this before you announce anything. It costs you one clone and it tells you whether the whole model makes sense to you:
git clone nostr://npub10qdp2fc9ta6vraczxrcs8prqnv69fru2k6s2dj48gqjcylulmtjsg9arpj/relay.ngit.dev/armada
cd armada
git branch -r | grep pr/
ngit pr list
ngit issue listWhat a working install looks like
You should see 14 pr/ branches, 14 open PRs, 40 open issues. If you do, your install is correct and you have just read a live project's collaboration history without an account anywhere.
4. Demote your old host first. ngit init in the next step claims the name origin for itself, so get your old remote out of the way before you run it. Do not delete anything:
git remote rename origin githubArmada runs this way today: origin is the nostr:// URL, github and gitlab are named mirrors.
5. Announce your repository. From inside your existing checkout:
ngit init -g relay.ngit.dev -g git.shakespeare.diy -g git.nostrhub.io \
--name my-project --identifier my-project --description "..."-g names your GRASP servers, "where your git+nostr data is hosted." Use more than one. That redundancy is the entire point and it costs nothing. --relay adds relays beyond your GRASP servers, and --clone adds git server URLs beyond them.
This one command does four things: publishes your repository announcement, publishes your first state event, sets origin to the nostr:// URL, and pushes main to every GRASP server you named. It ends by printing your share link and clone URL:
set remote origin to nostr url
push: git.shakespeare.diy/npub1.../my-project.git over https (unauthenticated)...
* [new branch] main
...
share your repository: https://gitworkshop.dev/npub1.../relay.ngit.dev/my-project
clone url: nostr://npub1.../relay.ngit.dev/my-project6. Move your CI, do not rewrite it.
git mv .github/workflows .ngit/act/workflowsThe files stay GitHub Actions syntax. ngit-ci reads them from the new path and runs them with act. Before you trust it, go back to the runner image in section 3. If your builds are heavier than a plain Node project, budget an afternoon for a pre-baked image and expect the first few runs to teach you something.
7. Open your first pull request the ordinary way.
git checkout -b pr/my-first-proposal
git push -o 'title=My first proposal' -u origin pr/my-first-proposal
ngit pr list8. Merge, then publish. Two steps, and pass the PR id.
ngit merge <pr-event-id>
git push origin mainmerge commit created on main. don't forget to pushTake the id from the ID column of ngit pr list. On a branch you created and pushed yourself, bare ngit merge will refuse it, for the reason in section 3. The merge is local; the git push is what publishes the kind 1631 event flipping the PR to applied.
9. Know which way sync points before you run it.
ngit sync # servers <- state event (state wins)
ngit sync -t # state event <- servers (servers win)Bare ngit sync is the destructive direction: it makes your git servers conform to the state event, which can walk commits off a branch. -t ("trust git server(s) that are fast-forward ahead of nostr state") is the one you want when the servers are ahead and correct. Reach for -t by default and use bare ngit sync only when you are certain the state event is the version you want to keep.
10. Tell people where it is. Your announcement carries a web tag, and gitworkshop.dev will render your repository, its PRs, its issues, and your CI run artifacts from the Nostr events. That is the link you put in your README.

Then keep going.
7. Now is the time to do this
What we proved is narrow, and it is enough. Armada's repository is not a place on one company's disk. It is a set of Nostr events, and the three git servers carrying the code are three separate operators with no single one of them load-bearing. The merge requests, the issues, the CI runs and the releases are events too, readable by anything that speaks the protocol.
The reason to start now rather than next year is that the pieces exist and you just watched each one do its job. ngit makes nostr:// work in plain git, with no wrapper around your daily loop. gitworkshop.dev and nostrhub.io give your project a URL you can send to someone, and nostrhub browses the code in the reader's own browser. ngit-ci runs the GitHub Actions files you already have, on a machine you run. Buzz keeps the conversation attached to the pull request it produced. Armada puts the issues, the merge requests and the CI runs in the channel where your team is already talking.

All of the tools are here and ready to use. Find the ones that work for you and take the plunge.
Soapbox is funded by grants and donations, not ads or data sales.
Everything we build is open source and belongs to the community. Help us keep it that way.
