Forgejo Git Forge for Apache NuttX RTOS (Experimental)

📝 12 Jan 2025

Forgejo Git Forge for Apache NuttX RTOS (Experimental)

Life Without GitHub: What’s it like? Today we talk about Forgejo Git Forge, and whether Apache NuttX RTOS could possibly switch from GitHub to a self-hosted Git Forge.

What’s this Forgejo? Why is it a “Git Forge”?

Think GitHub… But Open-Source and Self-Hosted! (GoLang Web + PostgreSQL Database)

Forgejo is a Git Forge, the server code that will publicly host and share a Git Repo. (Including our NuttX Repo)

Why explore Forgejo for NuttX?

NuttX On Forgejo

§1 NuttX On Forgejo

Installing our own Git Forge: Is it easy?

Yep! Here’s our experiment of NuttX on Forgejo (pic below)

https://nuttx-forge.org

Bringing up our Forgejo Server was plain-sailing (especially on Docker)

## Download the Forgejo Docker Image
## And our Docker Compose Config
docker pull codeberg.org/forgejo/forgejo:9
git clone https://nuttx-forge.org/lupyuen/nuttx-forgejo

## Up the Forgejo Server
cd nuttx-forgejo
sudo docker compose up

## To Configure: Browse to http://localhost:3002

Our Git Forge is running on Plain Old SQLite database. Later we might Upgrade to PostgreSQL.

Forgejo Git Forge for Apache NuttX RTOS (Experimental)

§2 Works The Same

Is it easy to use our own Git Forge?

Yes Forgejo is pleasantly Gittish-Hubbish. Inside Forgejo: Pull Requests and Issues look familiar…

Forgejo Pull Request and Issue

(More on Pull Requests and Issues)

Forgejo is fully compatible with our Git Command-Line Tools (and VSCode)

## Download the NuttX Mirror Repo
## From our Forgejo Server
git clone \
  https://nuttx-forge.org/nuttx/nuttx-mirror

## Also works for SSH (instead of HTTPS)
## But SSH isn't enabled on our server yet
git clone \
  [email protected]:nuttx/nuttx-mirror

Have we seen this somewhere?

Coexist With GitHub

§3 Coexist With GitHub

Will our Git Forge coexist with GitHub?

Ah now it gets tricky. Ideally we should allow GitHub to coexist with our Git Forge, synced both ways (pic above)

Forgejo works great for syncing NuttX Repo from GitHub. We configured Forgejo to Auto-Sync from GitHub every hour

Auto-Sync from GitHub every hour

Oops Forgejo creates a Read-Only Mirror. That won’t allow Pull Requests!

Read-Only Mirror won’t allow Pull Requests

Thus we create our own Read-Write Mirror of NuttX Repo…

Read-Write Mirror OK with Pull Requests

Forgejo won’t auto-sync our Read-Write Repo. We wrote our own Syncing Script, that works without GitHub.

(More about the Sync Script in a while)

Pull Requests shall be synced back to GitHub

But Pull Requests shall be synced back to GitHub?

Indeed, we’ll probably call GitHub API to send the Pull Requests back to GitHub. (Pic above)

With this setup, we can’t allow Pull Requests to be locally merged at Forgejo. Instead, Pull Requests shall be merged at GitHub. Then Forgejo shall auto-sync the updates into our Git Forge.

That’s why we need Two Mirror Repos: Read-Only and Read-Write…

nuttx-mirrornuttx-update
Read-Only MirrorRead-Write Mirror
Auto-Sync by ForgejoManual-Sync by Our Script
Can’t create PRsCan create PRs
Can’t migrate PRs and IssuesCan migrate PRs and Issues
(but ran into problems)
(Explained here)(Explained here)

(Blocked by Corporate Firewall? Git Mirroring might help)

Forgejo will import GitHub Actions Workflows and execute them

§4 Continuous Integration

Will our Git Forge run CI Checks on Pull Requests?

GitHub Actions CI (Continuous Integration) becomes a Sticky Issue with Forgejo…

CI Server needs to be Ubuntu x64, hardened for security

Any special requirement for CI Server?

Our CI Server needs to be Ubuntu x64, hardened for security.

Why? During PR Submission: Our CI Workflow might execute the scripts and code submitted by NuttX Devs.

If we don’t secure our CI Server, we might create Security Problems in our server.

Securing our CI Server is probably the toughest part of our Git Forge Migration. That’s why GitHub is expensive!

(Shall we move NuttX Scripts to a More Secure Repo?)

§5 Sync our Read-Write Mirror

Forgejo won’t Auto-Sync our Read-Write Mirror. How to sync it?

nuttx-mirrornuttx-update
Read-Only MirrorRead-Write Mirror
Auto-Sync by ForgejoManual-Sync by Our Script
Can’t create PRsCan create PRs
Can’t migrate PRs and IssuesCan migrate PRs and Issues
(but ran into problems)
(Explained here)(Explained here)

We run a script to Sync the Git Commits

## Sync Once: From Read-Only Mirror to Read-Write Mirror
git clone https://nuttx-forge.org/lupyuen/nuttx-forgejo
cd nuttx-forgejo
./sync-mirror-to-update.sh

## Or to Sync Periodically
## ./run.sh

(See the Sync Log)

Sync our Read-Write Mirror

Our script begins like this: sync-mirror-to-update.sh

## Sync the Git Commits
## From our Read-Only Mirror Repo (nuttx-mirror)
## To the Read-Write Mirror Repo (nuttx-update)
set -e  ## Exit when any command fails

## Checkout the Upstream and Downstream Repos
## Upstream is Read-Only Mirror
## Downstream is Read-Write Mirror
tmp_dir=/tmp/sync-mirror-to-update
rm -rf $tmp_dir ; mkdir $tmp_dir ; cd $tmp_dir
git clone \
  git@nuttx-forge:nuttx/nuttx-mirror \
  upstream
git clone \
  git@nuttx-forge:nuttx/nuttx-update \
  downstream

## Find the First Commit to Sync
pushd upstream
upstream_commit=$(git rev-parse HEAD)
git --no-pager log -1
popd
pushd downstream
downstream_commit=$(git rev-parse HEAD)
git --no-pager log -1
popd

## If no new Commits to Sync: Quit
if [[ "$downstream_commit" == "$upstream_commit" ]]; then
  echo "No New Commits to Sync" ; exit
fi

## Up Next: Sync Downstream Repo with Upstream

How to Sync the Two Repos? Git Pull will do! sync-mirror-to-update.sh

## Apply the Upstream Commits to Downstream Repo
pushd downstream
git pull \
  git@nuttx-forge:nuttx/nuttx-mirror \
  master
git status
popd

## Commit the Downstream Repo
pushd downstream
git push -f
popd

## Verify that Upstream and Downstream Commits are identical
echo "Updated Downstream Commit"
pushd downstream
git pull
downstream_commit2=$(git rev-parse HEAD)
git --no-pager log -1
popd

## If Not Identical: We have a problem, need to Hard-Revert the Commits
if [[ "$downstream_commit2" != "$upstream_commit" ]]; then
  echo "Sync Failed: Upstream and Downstream Commits don't match!" ; exit 1
fi

(See the Sync Log)

What if we accidentally Merge a PR downstream? And our Read-Write Mirror goes out of sync?

If our Read-Write Mirror goes out of sync: We Hard-Revert the Commits in our Read-Write Mirror. Which will keep it in sync again with the Read-Only Mirror…

## Download our Read-Write Mirror
## Which contains Conflicting Commits
$ git clone git@nuttx-forge:nuttx/nuttx-update
$ cd nuttx-update

## Repeat for all Conflicting Commits:
## Revert the Commit
$ git reset --hard HEAD~1
HEAD is now at 7d6b2e48044 gcov/script: gcov.sh is implemented using Python

## We have reverted One Conflicting Commit
$ git status
Your branch is behind 'origin/master' by 1 commit, and can be fast-forwarded.

## Push it to our Read-Write Mirror
## Our Read-Write Mirror is now back in sync with Read-Only Mirror!
$ git push -f
To nuttx-forge:nuttx/nuttx-update
e26e8bda0e9...7d6b2e48044 master -> master (forced update)

What if we really need to Accept Pull Requests in our Read-Write Mirror?

We’ll probably call GitHub API to ship the Pull Requests back to GitHub. (Pic below)

Right now we can’t allow Pull Requests to be locally merged at Forgejo. Instead, we’ll merge Pull Requests at GitHub. Then wait for Forgejo to auto-sync the updates back into our Git Forge.

Which explains why we need Two Mirror Repos: Read-Only Mirror and Read-Write Mirror.

nuttx-mirrornuttx-update
Read-Only MirrorRead-Write Mirror
Auto-Sync by ForgejoManual-Sync by Our Script
Can’t create PRsCan create PRs
Can’t migrate PRs and IssuesCan migrate PRs and Issues
(but ran into problems)
(Explained here)(Explained here)

What’s Next

§6 What’s Next

Next Article: Why Sync-Build-Ingest is super important for NuttX CI. And how we monitor it with our Magic Disco Light.

After That: Since we can Rewind NuttX Builds and automatically Git Bisect… Can we create a Bot that will fetch the Failed Builds from NuttX Dashboard, identify the Breaking PR, and escalate to the right folks?

Many Thanks to the awesome NuttX Admins and NuttX Devs! And My Sponsors, for sticking with me all these years.

Got a question, comment or suggestion? Create an Issue or submit a Pull Request here…

lupyuen.org/src/forgejo.md

Forgejo Git Forge for Apache NuttX RTOS (Experimental)

§7 Appendix: Install our Forgejo Server

Here are the steps to install our own Forgejo Server (pic above) on Docker Engine

(Derived from Official Docs)

(Tested on macOS Rancher Desktop)

## Download the Forgejo Docker Image
## And our Modified Docker Compose Config
docker pull codeberg.org/forgejo/forgejo:9
cd $HOME
git clone https://nuttx-forge.org/lupyuen/nuttx-forgejo
cd nuttx-forgejo

## docker-compose.yml: Points to `forgejo-data` as the Data Volume (instead of Local Filesystem)
## Because Rancher Desktop won't set permissions correctly for Local Filesystem (see secton below)
## services:
##   server:
##     volumes:
##       - forgejo-data:/data
## volumes:
##   forgejo-data:

## Up the Forgejo Server
## (Is `sudo` needed?)
sudo docker compose up

## If It Quits To Command-Line:
## Run a second time to get it up
sudo docker compose up

## Always restart Forgejo Server
## Whenever it crashes
sudo docker update --restart always forgejo

## If we need to down the Forgejo Server:
## sudo docker compose down

(See the Install Log)

(Fixing the Docker Compose Config)

Back to the Forgejo Configuration: This is how we specify the Forgejo Database

Forgejo Configuration: Database

And the Server Domain

Forgejo Configuration: Server Domain

Finally our Admin User

Forgejo Configuration: Admin User

Forgejo’s Default Page: How to change it?

We copy out the Forgejo Configuration from Docker…

sudo docker cp \
  forgejo:/data/gitea/conf/app.ini \
  .

Edit app.init and set…

[server]
LANDING_PAGE = explore

Then copy it back to Docker…

sudo docker cp \
  app.ini \
  forgejo:/data/gitea/conf/app.ini
sudo docker compose down
sudo docker compose up

The Default Page for our Forgejo Server…

Original Default Page

Now becomes a little more helpful…

Default Page becomes Explore

(Popolon has plenty more tips for Forgejo Server)

Read-Only Mirror

§8 Appendix: Read-Only Mirror

Now that Forgejo is up: Let’s create a Read-Only Mirror of the NuttX Repo at GitHub.

Forgejo shall auto-sync our repo (every hour), but it won’t allow Pull Requests in our Read-Only Mirror…

nuttx-mirrornuttx-update
Read-Only MirrorRead-Write Mirror
Auto-Sync by ForgejoManual-Sync by Our Script
Can’t create PRsCan create PRs
Can’t migrate PRs and IssuesCan migrate PRs and Issues
(but ran into problems)
(Explained here)(Explained here)
  1. At Top Right: Select + > New Migration

    New Migration

  2. Select GitHub

    GitHub Migration

  3. Enter the GitHub URL of NuttX Repo

    Fill in the Access Token

    Check “This Repo Will Be A Mirror”, Migrate LFS Files and Wiki

    Set the Repo Name to nuttx-mirror

    Repo Name

  4. This will create a Read-Only Mirror: nuttx-mirror

    Forgejo won’t migrate the other items: Issues, Pull Requests, Labels, Milestones, Releases (pic above)

    (Read-Write Mirror will be more useful, see the next section)

  5. And Forgejo dutifully creates our Read-Only Mirror!

    (Live Site)

    Read-Only Mirror

  6. By Default: Forgejo syncs every 8 hours. We change the Mirror Interval to 1 hour

    That’s under Settings > Repository > Mirror Settings

    Mirror Interval

  7. Forgejo has helpfully migrated our Template for NuttX Issues

    (Live Site)

    Template for NuttX Issues

  8. Forgejo has ported over our GitHub Actions Workflows. But they won’t run because we don’t have a CI Server for Ubuntu x64.

    (Live Site)

    CI Workflow

  9. NuttX Commits look very familiar in Forgejo

    Commit Hashes are identical to GitHub

    (Live Site)

    NuttX Commits

  10. So cool to watch Forgejo Auto-Sync our GitHub Repo

    (Live Site)

    Auto-Sync our NuttX Repo

  11. Auto-Sync may trigger CI Workflows. But we don’t have CI Servers to run them (yet).

    (Live Site)

    No CI Servers in Forgejo

  12. That’s why the CI Jobs will wait forever

    (Live Site)

    CI Jobs waiting for CI Server

  13. GitHub Reusable Workflows are Not Supported in Forgejo.

    This means the NuttX Build Rules (arch.yml) probably won’t run on Forgejo.

  14. Git Command-Line Tools will work great with our Forgejo Server

    ## Download the NuttX Mirror Repo
    ## From our Forgejo Server
    git clone \
      https://nuttx-forge.org/nuttx/nuttx-mirror
    
    ## Also works for SSH (instead of HTTPS)
    ## But SSH isn't enabled on our server yet
    git clone \
      [email protected]:nuttx/nuttx-mirror

Read-Write Mirror

§9 Appendix: Read-Write Mirror

Earlier we created a Read-Only Mirror. But it doesn’t allow Pull Requests!

Now we create a Read-Write Mirror of the NuttX Repo at GitHub, which will allow Pull Requests. Forgejo won’t auto-sync our repo, instead we’ll run a script to sync the repo…

nuttx-mirrornuttx-update
Read-Only MirrorRead-Write Mirror
Auto-Sync by ForgejoManual-Sync by Our Script
Can’t create PRsCan create PRs
Can’t migrate PRs and IssuesCan migrate PRs and Issues
(but ran into problems)
(Explained here)(Explained here)
  1. At Top Right: Select + > New Migration

    New Migration

  2. Select GitHub

    GitHub Migration

  3. Enter the GitHub URL of NuttX Repo

    Fill in the Access Token

    Uncheck “This Repo Will Be A Mirror”

    Check the following: Migrate LFS Files, Wiki, Labels, Milestones, Releases

    Set the Repo Name to nuttx-update

    Repo Name

  4. This will create a Read-Write Mirror: nuttx-update

    Forgejo won’t auto-sync our repo. But it will migrate the other items: Labels, Milestones, Releases (pic above)

    Don’t select Issues and Pull Requests! Forgejo will hang forever, hitting errors. (Probably due to the sheer volume)

    Don’t select Issues and Pull Requests! Forgejo will hang forever, hitting errors

  5. Assuming we didn’t select Issues and Pull Requests…

    Forgejo creates our Read-Write Mirror

    (Live Site)

    Read-Write Mirror

  6. How to sync the Read-Write Mirror? We run this script…

    sync-mirror-to-update.sh

§10 Appendix: Pull Request in Forgejo

How different are Forgejo Pull Requests from GitHub?

Let’s find out!

  1. We create a Fork of our NuttX Read-Write Mirror

    (Live Site)

    Fork our Read-Write Mirror

  2. Create a new branch: test-branch. Edit a file in our new branch.

    (Live Site)

    Edit A File

  3. Save the file to our new branch

    Save The File

  4. Click “New Pull Request”

    New Pull Request

  5. Again click “New Pull Request”

    New Pull Request Again

  6. Remember the NuttX Template for Pull Requests? It appears in Forgejo

    NuttX Template for Pull Requests

  7. Click “Create Pull Request”

    Create Pull Request

  8. And we’ll see our New Pull Request

    (Live Site)

    New Pull Request

  9. Indeed, no surprises! Everything works the same.

    Everything works the same

  10. Merging a Pull Request will trigger the exact same CI Workflow. Which won’t run because we haven’t configured the CI Servers.

    (Live Site)

    Merging a Pull Request will trigger CI Workflow

  11. Will Forgejo handle Large Pull Requests? Yep here’s a (potential) Pull Request with 217 NuttX Commits

    (Live Site)

    217 NuttX Commits

  12. Let’s try not to Merge any Pull Request into our Read-Write Mirror. We should keep it in sync with our Read-Only Mirror!

    “Sync our Read-Write Mirror”

§11 Appendix: SSH Access in Forgejo

This section explains how we tested SSH Access in our Forgejo Server.

Note: SSH Port for our Forgejo Server is not exposed to the internet (for security reasons).

We generate the SSH Key

$ ssh-keygen -t ed25519 -a 100
## Save it to ~/.ssh/nuttx-forge

Edit ~/.ssh/config and add…

Host nuttx-forge
  HostName localhost
  Port 222
  IdentityFile ~/.ssh/nuttx-forge

(localhost will change to the External IP of our Forgejo Server)

In Forgejo Web:

Manage SSH Keys

Finally we test the SSH Access

$ ssh -T git@nuttx-forge  

Hi there, nuttx! You've successfully authenticated with the key named nuttx-forge (luppy@localhost), but Forgejo does not provide shell access.
If this is unexpected, please log in with password and setup Forgejo under another user.

We create a Test Repo in our Forgejo Server…

Test Repo

And we Commit over SSH to the Test Repo…

git clone git@nuttx-forge:nuttx/test.git
cd test
echo Testing >test.txt
git add .
git commit --all --message="Test Commit"
git push -u origin main

We should see the Test Commit. Yay!

Test Repo with Test Commit

§12 Appendix: SSH vs Docker Filesystem

Why did we change the Docker Filesystem for Forgejo?

Based on the Official Docs: Forgejo should be configured to use a Local Docker Filesystem, ./forgejo

## Officially: Docker mounts Local Filesystem
## `./forgejo` as `/data`
services:
  server:
    volumes:
      - ./forgejo:/data

Let’s try it on macOS Rancher Desktop and watch what happens…

## Connect to Forgejo Server over SSH
ssh -T -p 222 git@localhost

Forgejo Server Log says…

Authentication refused: 
  bad ownership or modes for 
  file /data/git/.ssh/authorized_keys

Connection closed by authenticating
  user git 172.22.0.1 port 47768 [preauth]

We check the SSH Filesystem in macOS

$ ls -ld $HOME/nuttx-forgejo/forgejo/git/.ssh                
drwx------@ 4 luppy  staff  128 Dec 20 13:45 /Users/luppy/nuttx-forgejo/forgejo/git/.ssh

$ ls -l $HOME/nuttx-forgejo/forgejo/git/.ssh/authorized_keys 
-rw-------@ 1 luppy  staff  279 Dec 21 11:13 /Users/luppy/nuttx-forgejo/forgejo/git/.ssh/authorized_keys

Do the same Inside Docker

## Connect to the Docker Console
$ sudo docker exec \
  -it \
  forgejo \
  /bin/bash

## Check the SSH Filesystem inside Docker
$ ls -ld /data/git/.ssh
drwx------    1 501  dialout  128 Dec 20 13:45 /data/git/.ssh

$ ls -l /data/git/.ssh/authorized_keys
-rw-------    1 501  dialout  279 Dec 21 11:13 /data/git/.ssh/authorized_keys

Aha! User ID should be git, not 501! (Some kinda jeans?)

Too bad chown won’t work…

## Nope! Won't work in Rancher Desktop
exec su-exec root chown -R git /data/git/.ssh

Sadly, macOS Rancher Desktop won’t set permissions correctly for the Local Filesystem.

And that’s why our docker-compose.yml points to Data Volume forgejo-data (instead of Local Filesystem)

## Our Tweak: Docker mounts Data Volume
## `forgejo-data` as `/data`
## (Instead of Local Filesystem `./forgejo`)
services:
  server:
    volumes:
      - forgejo-data:/data

## Declare the Data Volume `forgejo-data`
volumes:
  forgejo-data: