Forgejo: OS GIT

Published: Dec 5, 2023 by Isaac Johnson

I came across Forgejo while searching for a different tool recently. The word is a bit unusual. According to their site it comes from the Esperanto word for “Forge” ( forĝejo ), their mantra being “We Forge”.

Forgejo visually looks like Gitea because it is based on a fork of Gitea. The fork was created when a for-profit “Gitea Ltd” company formed in 2022 and the OS community became worried Gitea would it would move off OS. However, in the year+ since it happened, Gitea really has remained Open Source to a large extent and kept the MIT license (quite a discussion when they went for-profit as seen here).

Today I wanted to setup Forgejo and see how well it works. We will first just use a Docker image, then move to an HA setup with Kubernetes, TLS ingress and MariaDB (MySQL) hosted on a NAS for a production instance. During the course, We’ll compare it with Gitea in parts (and actually marry the two together for some replication). We’ll also explore Gitea/Forgejo workflows. Let’s dig in!

Forgejo

To run with docker, we first need to create a directory then run

$ mkdir forgejo

$ docker run --name forgejotest -p 3300:3000 -p 222:22 -v $(pwd)/forgejo:/data -v /etc/timezone:/etc/timezone:ro -v /etc/localtime:/etc/localtime:ro -e USER_UID=1000 -e USER_GID=1000 codeberg.org/forgejo/forgejo:1.20
Unable to find image 'codeberg.org/forgejo/forgejo:1.20' locally
1.20: Pulling from forgejo/forgejo
96526aa774ef: Pull complete
70d6a3ceec46: Pull complete
3b12062cc7ad: Pull complete
a8f7e561dc2d: Pull complete
334bba0d8ebe: Pull complete
96bd3dd100d1: Pull complete
0891a06db1aa: Pull complete
63970971aebc: Pull complete
a512d230781b: Pull complete
4f4fb700ef54: Pull complete
70b92d8af2b8: Pull complete
Digest: sha256:d665129e66cc04fa72aa6a284eb98b17c0eee642aeaad7c88edec21f9edbf519
Status: Downloaded newer image for codeberg.org/forgejo/forgejo:1.20
Generating /data/ssh/ssh_host_ed25519_key...
Generating /data/ssh/ssh_host_rsa_key...
Generating /data/ssh/ssh_host_ecdsa_key...
Server listening on :: port 22.
Server listening on 0.0.0.0 port 22.
2023/11/20 18:52:27 cmd/web.go:223:runWeb() [I] Starting Gitea on PID: 15
2023/11/20 18:52:27 cmd/web.go:106:serveInstall() [I] Gitea version: 1.20.5+0 built with GNU Make 4.4.1, go1.20.8 : bindata, timetzdata, sqlite, sqlite_unlock_notify
2023/11/20 18:52:27 cmd/web.go:107:serveInstall() [I] App path: /usr/local/bin/gitea
2023/11/20 18:52:27 cmd/web.go:108:serveInstall() [I] Work path: /data/gitea
2023/11/20 18:52:27 cmd/web.go:109:serveInstall() [I] Custom path: /data/gitea
2023/11/20 18:52:27 cmd/web.go:110:serveInstall() [I] Config file: /data/gitea/conf/app.ini
2023/11/20 18:52:27 cmd/web.go:111:serveInstall() [I] Prepare to run install page
2023/11/20 18:52:27 cmd/web.go:285:listen() [I] Listen: http://0.0.0.0:3000
2023/11/20 18:52:27 cmd/web.go:289:listen() [I] AppURL(ROOT_URL): http://localhost:3000/
2023/11/20 18:52:27 ...s/graceful/server.go:62:NewServer() [I] Starting new Web server: tcp:0.0.0.0:3000 on PID: 15

This will launch us into a configuration page

/content/images/2023/12/misctools-03.png

I left most things default and started the install

/content/images/2023/12/misctools-04.png

which started the loading

/content/images/2023/12/misctools-05.png

Because I didn’t set the admin password in settings, the first created user became admin

/content/images/2023/12/misctools-06.png

There are a lot of nice admin features such as deactivating accounts and archiving repo

/content/images/2023/12/misctools-07.png

Right now we have no repos shared publicly

/content/images/2023/12/misctools-08.png

I’ll create a test public repo (TestPublic) by clicking “New Repository”

/content/images/2023/12/misctools-09.png

and giving it a name

/content/images/2023/12/misctools-10.png

I can see it’s public by checking in an Incognito window

/content/images/2023/12/misctools-11.png

I can now create an empty dir, add a file, and push

builder@LuiGi17:~/Workspaces$ mkdir TestPublic
builder@LuiGi17:~/Workspaces$ cd TestPublic/
builder@LuiGi17:~/Workspaces/TestPublic$ echo "# Test" > README.md
builder@LuiGi17:~/Workspaces/TestPublic$ git init
hint: Using 'master' as the name for the initial branch. This default branch name
hint: is subject to change. To configure the initial branch name to use in all
hint: of your new repositories, which will suppress this warning, call:
hint:
hint:   git config --global init.defaultBranch <name>
hint:
hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
hint: 'development'. The just-created branch can be renamed via this command:
hint:
hint:   git branch -m <name>
Initialized empty Git repository in /home/builder/Workspaces/TestPublic/.git/
builder@LuiGi17:~/Workspaces/TestPublic$ git checkout -b main
Switched to a new branch 'main'
builder@LuiGi17:~/Workspaces/TestPublic$ git add README.md
builder@LuiGi17:~/Workspaces/TestPublic$ git commit -m 'first commit'
[main (root-commit) 6f8e38f] first commit
 1 file changed, 1 insertion(+)
 create mode 100644 README.md
builder@LuiGi17:~/Workspaces/TestPublic$ git remote add origin http://192.168.1.100:3300/isaac/TestPublic.git
builder@LuiGi17:~/Workspaces/TestPublic$ git push -u origin
fatal: The current branch main has no upstream branch.
To push the current branch and set the remote as upstream, use

    git push --set-upstream origin main

builder@LuiGi17:~/Workspaces/TestPublic$ git push -u origin main
Username for 'http://192.168.1.100:3300': isaac@freshbrewed.science
Password for 'http://isaac@freshbrewed.science@192.168.1.100:3300':
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 220 bytes | 110.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
remote: . Processing 1 references
remote: Processed 1 references in total
To http://192.168.1.100:3300/isaac/TestPublic.git
 * [new branch]      main -> main
Branch 'main' set up to track remote branch 'main' from 'origin'.

And I can see the file there

/content/images/2023/12/misctools-12.png

Because we can reach it with the raw endpoint, we could easily pull files down with wget or curl as well

builder@LuiGi17:~/Workspaces$ wget http://192.168.1.100:3300/isaac/TestPublic/raw/branch/main/README.md
--2023-11-20 20:14:36--  http://192.168.1.100:3300/isaac/TestPublic/raw/branch/main/README.md
Connecting to 192.168.1.100:3300... connected.
HTTP request sent, awaiting response... 200 OK
Length: 7 [text/plain]
Saving to: ‘README.md’

README.md                             100%[=======================================================================>]       7  --.-KB/s    in 0s

2023-11-20 20:14:36 (187 KB/s) - ‘README.md’ saved [7/7]

builder@LuiGi17:~/Workspaces$ cat README.md
# Test

We could clone with SSH keys as well. First we need to add our private key by clicking “Add Key”

/content/images/2023/12/misctools-13.png

I’ll add the contents of my .pub file in ~.ssh

/content/images/2023/12/misctools-14.png

Which I can see now listed

/content/images/2023/12/misctools-15.png

It’s a bit different URL when cloning with a custom port, but it works

builder@LuiGi17:~/Workspaces$ git clone ssh://git@192.168.1.100:222/isaac/TestPublic.git Test2
Cloning into 'Test2'...
The authenticity of host '[192.168.1.100]:222 ([192.168.1.100]:222)' can't be established.
ED25519 key fingerprint is SHA256:LECyhFpc385yhunb07LWxCDtVrcxyvfxbks1C/x6Xf0.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '[192.168.1.100]:222' (ED25519) to the list of known hosts.
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (3/3), done.

Let’s create a wiki

/content/images/2023/12/misctools-16.png

I can then write some markdown content

/content/images/2023/12/misctools-17.png

I can save and see the rendered content

/content/images/2023/12/misctools-18.png

Then create a project

/content/images/2023/12/misctools-19.png

By default, we see just one Column in our Kanban page

/content/images/2023/12/misctools-20.png

I’ll create an Active column

/content/images/2023/12/misctools-21.png

I then created a few more

/content/images/2023/12/misctools-22.png

Then create a test issue

/content/images/2023/12/misctools-23.png

and see details

/content/images/2023/12/misctools-24.png

I can then move between states

/content/images/2023/12/misctools-25.png

Mysql/MariaDB Database

Before I launch an HA instance, I really want a proper backend database. I typically use PostgreSQL (which runs on a VM) or MariaDB which can run on either one of my NASes.

I’ll create a user on the NAS by SSH’ing (and after loading the MariaDB 10 package)

ijohnson@SassyNassy:~$ /usr/local/mariadb10/bin/mysql -u root -p
Enter password:
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 15
Server version: 10.3.32-MariaDB Source distribution

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> CREATE USER 'forgejo' IDENTIFIED BY 'n0tmypassw0rd';

older MySQL instances would have us use:

CREATE USER 'forgejo':'%' IDENTIFIED BY 'n0tmypassw0rd';

But I did not need that. In fact, I can be sure it worked by checking the Grants

MariaDB [(none)]> SHOW GRANTS FOR forgejo@'%';
+--------------------------------------------------------------------------------------------------------+
| Grants for forgejo@%                                                                                   |
+--------------------------------------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO `forgejo`@`%` IDENTIFIED BY PASSWORD '*9DF4E2EB962EC499A5907589C7E8B92E320397C8' |
+--------------------------------------------------------------------------------------------------------+
1 row in set (0.201 sec)

Now I’ll set some Grants and flush the privs

MariaDB [(none)]> CREATE DATABASE forgejodb CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_unicode_ci';
Query OK, 1 row affected (0.205 sec)

MariaDB [(none)]> GRANT ALL PRIVILEGES ON forgejodb.* TO 'forgejo';
Query OK, 0 rows affected (0.048 sec)

MariaDB [(none)]> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.060 sec)

I also went back and explicitly granted to all hosts in trying to debug connectivity

MariaDB [(none)]> GRANT ALL PRIVILEGES ON forgejodb.* TO 'forgejo'@'%' IDENTIFIED BY 'n0tmypassw0rd';
Query OK, 0 rows affected (0.320 sec)

I did find I needed to pass the MsSQL port which matched the settings on the NAS:

builder@DESKTOP-QADGF36:~$ mysql -u forgejo -h sassynassy -p forgejodb -P 3307
Enter password:
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 18
Server version: 10.3.32-MariaDB Source distribution

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [forgejodb]>

/content/images/2023/11/misctools-26.png

Helm Install

The helm chart is based on Gitea. We can use the same structure here

$ cat forgego.values
gitea:
  config:
    APP_NAME: 'Forgejo: With a cup of tea.'

gitea:
  config:
    database:
      DB_TYPE: mysql
      HOST: 192.168.1.129:3307
      NAME: forgejodb
      USER: forgejo
      PASSWD: 'asdfasdfasdfasdf'
      SCHEMA: gitea

postgresql:
  enabled: false

service:
  ssh:
    type: ClusterIP
    port: 222

I didn’t want to use default 22 for SSH as that makes a mess of things. Also, my MariaDB doesn’t run on the standard 3306, rather port 3307 so I assumed I could add port to the hostname.

I then created a namespace and launched the chart

$ kubectl create ns forgejo
namespace/forgejo created

$ helm install -n forgejo forgejo -f forgego.values oci://codeberg.org/forgejo-contrib/forgejo
Pulled: codeberg.org/forgejo-contrib/forgejo:0.14.0
Digest: sha256:6f510f4b1cb079eb6b321c2d868255d60fdb9a365e648b517c61b42a3334271a
coalesce.go:175: warning: skipped value for memcached.initContainers: Not a table.
NAME: forgejo
LAST DEPLOYED: Tue Nov 21 13:40:45 2023
NAMESPACE: forgejo
STATUS: deployed
REVISION: 1
NOTES:
1. Get the application URL by running these commands:
  echo "Visit http://127.0.0.1:3000 to use your application"
  kubectl --namespace forgejo port-forward svc/forgejo-http 3000:3000

As expected, I see pods and services now launched

$ kubectl get pods -n forgejo
NAME                                 READY   STATUS    RESTARTS   AGE
forgejo-memcached-6fd98dddbb-prx7g   1/1     Running   0          3m56s
forgejo-0                            1/1     Running   0          3m56s

$ kubectl get svc -n forgejo
NAME                TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)     AGE
forgejo-ssh         ClusterIP   None            <none>        222/TCP     4m9s
forgejo-http        ClusterIP   None            <none>        3000/TCP    4m9s
forgejo-memcached   ClusterIP   10.43.108.161   <none>        11211/TCP   4m9s

I’ll then create an A Record to point in for forgejo

$ cat r53-forgejo.json
{
    "Comment": "CREATE forgejo fb.s A record ",
    "Changes": [
      {
        "Action": "CREATE",
        "ResourceRecordSet": {
          "Name": "forgejo.freshbrewed.science",
          "Type": "A",
          "TTL": 300,
          "ResourceRecords": [
            {
              "Value": "75.73.224.240"
            }
          ]
        }
      }
    ]
  }

$ aws route53 change-resource-record-sets --hosted-zone-id Z39E8QFU0F9PZP --change-batch file://r53-forgejo.json
{
    "ChangeInfo": {
        "Id": "/change/C00925091P54X4SDFXR7U",
        "Status": "PENDING",
        "SubmittedAt": "2023-11-21T19:47:34.835Z",
        "Comment": "CREATE forgejo fb.s A record "
    }
}

The Forgejo ingress will solve HTTP/S but not SSH. Essentially, SSH will be limited to in cluster operations for now and those can go to the service (forgejo-ssh.forgejo.svc.cluster.local:222)

$ cat forgejo.ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    ingress.kubernetes.io/proxy-body-size: "0"
    ingress.kubernetes.io/ssl-redirect: "true"
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/proxy-body-size: "0"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.org/client-max-body-size: "0"
    nginx.org/proxy-connect-timeout: "600"
    nginx.org/proxy-read-timeout: "600"
  generation: 1
  labels:
    app: forgejo
    app.kubernetes.io/instance: forgejo
    app.kubernetes.io/name: forgejo
  name: forgejo
spec:
  rules:
  - host: forgejo.freshbrewed.science
    http:
      paths:
      - backend:
          service:
            name: forgejo-http
            port:
              number: 3000
        path: /
        pathType: ImplementationSpecific
  tls:
  - hosts:
    - forgejo.freshbrewed.science
    secretName: forgejo-tls

$ kubectl apply -f forgejo.ingress.yaml -n forgejo
ingress.networking.k8s.io/forgejo created

I can now login and register a new user (which will be the admin)

/content/images/2023/11/misctools-27.png

The downside was this user was not an admin!

I added the missing block for the super user account

$ cat ~/forgego.values
gitea:
  config:
    APP_NAME: 'Forgejo: With a cup of tea.'

gitea:
  config:
    database:
      DB_TYPE: mysql
      HOST: 192.168.1.129:3307
      NAME: forgejodb
      USER: forgejo
      PASSWD: 'asdfasdfasdf'
      SCHEMA: gitea
  admin:
    username: "builderadmin"
    password: "asdfasdfasfsadf"
    email: "isaac.johnson..@gmail.com"

postgresql:
  enabled: false

service:
  ssh:
    type: ClusterIP
    port: 222

I’m hoping I can upgrade

$ helm upgrade -n forgejo forgejo -f /home/builder/forgego.values oci://codeberg.org/forgejo-contrib/forgejo
Pulled: codeberg.org/forgejo-contrib/forgejo:0.14.0
Digest: sha256:6f510f4b1cb079eb6b321c2d868255d60fdb9a365e648b517c61b42a3334271a
coalesce.go:175: warning: skipped value for memcached.initContainers: Not a table.
Release "forgejo" has been upgraded. Happy Helming!
NAME: forgejo
LAST DEPLOYED: Tue Nov 21 14:00:50 2023
NAMESPACE: forgejo
STATUS: deployed
REVISION: 2
NOTES:
1. Get the application URL by running these commands:
  echo "Visit http://127.0.0.1:3000 to use your application"
  kubectl --namespace forgejo port-forward svc/forgejo-http 3000:3000

That worked!

/content/images/2023/11/misctools-28.png

I realized that since this is based on Gitea, I could then add mailer and server blocks to solve the URL complaints:

/content/images/2023/11/misctools-29.png

The new values

$ cat ~/forgego.values
gitea:
  config:
    APP_NAME: 'Forgejo: With a cup of tea.'

gitea:
  config:
    database:
      DB_TYPE: mysql
      HOST: 192.168.1.129:3307
      NAME: forgejodb
      USER: forgejo
      PASSWD: 'adsfasdfasdfasdfasdf'
      SCHEMA: gitea
    actions:
      ENABLED: true
    mailer:
      ENABLED: true
      FROM: isaac@freshbrewed.science
      MAILER_TYPE: smtp
      PASSWD: SG.asdfsdfasdfsadfasdfasdfasdfasdfasdfasdfasdfasdfasdf
      SMTP_ADDR: smtp.sendgrid.net
      SMTP_PORT: 465
      USER: apikey
    openid:
      ENABLE_OPENID_SIGNIN: false
      ENABLE_OPENID_SIGNUP: false
    server:
      DOMAIN: forgejo.freshbrewed.science
      ROOT_URL: https://forgejo.freshbrewed.science/
    service:
      DISABLE_REGISTRATION: true
  admin:
    username: "builderadmin"
    password: "adfasdfasdfasdfasdfasdf"
    email: "isaac.johnson@gmail.com"

postgresql:
  enabled: false

service:
  ssh:
    type: ClusterIP
    port: 222

It’s a little upset about the MAILER_TYPE setting, but other than that it’s good.

/content/images/2023/11/misctools-30.png

I now have sign-in, but not registration

/content/images/2023/11/misctools-31.png

Note: it’s sad really - i would love to just keep it open, but then spammers just abuse the snot out of it

I’ll make a new public repo

/content/images/2023/11/misctools-32.png

And now I can also enable actions (which are based on Github actions)

/content/images/2023/11/misctools-33.png

Runners

Get the token

/content/images/2023/12/misctools-34.png

On a host in our network we create a config.yaml

builder@builder-T100:~/Forgejo$ cat config.yaml
# Example configuration file, it's safe to copy this as the default config file without any modification.

# You don't have to copy this file to your instance,
# just run `./act_runner generate-config > config.yaml` to generate a config file.

log:
  # The level of logging, can be trace, debug, info, warn, error, fatal
  level: info

runner:
  # Where to store the registration result.
  file: .runner
  # Execute how many tasks concurrently at the same time.
  capacity: 1
  # Extra environment variables to run jobs.
  envs:
    A_TEST_ENV_NAME_1: a_test_env_value_1
    A_TEST_ENV_NAME_2: a_test_env_value_2
  # Extra environment variables to run jobs from a file.
  # It will be ignored if it's empty or the file doesn't exist.
  env_file: .env
  # The timeout for a job to be finished.
  # Please note that the Gitea instance also has a timeout (3h by default) for the job.
  # So the job could be stopped by the Gitea instance if it's timeout is shorter than this.
  timeout: 3h
  # Whether skip verifying the TLS certificate of the Gitea instance.
  insecure: false
  # The timeout for fetching the job from the Gitea instance.
  fetch_timeout: 5s
  # The interval for fetching the job from the Gitea instance.
  fetch_interval: 2s
  # The labels of a runner are used to determine which jobs the runner can run, and how to run them.
  # Like: ["macos-arm64:host", "ubuntu-latest:docker://node:16-bullseye", "ubuntu-22.04:docker://node:16-bullseye"]
  # If it's empty when registering, it will ask for inputting labels.
  # If it's empty when execute `deamon`, will use labels in `.runner` file.
  labels:
  - ubuntu-22.04:host

cache:
  # Enable cache server to use actions/cache.
  enabled: true
  # The directory to store the cache data.
  # If it's empty, the cache data will be stored in $HOME/.cache/actcache.
  dir: ""
  # The host of the cache server.
  # It's not for the address to listen, but the address to connect from job containers.
  # So 0.0.0.0 is a bad choice, leave it empty to detect automatically.
  host: ""
  # The port of the cache server.
  # 0 means to use a random available port.
  port: 0
  # The external cache server URL. Valid only when enable is true.
  # If it's specified, act_runner will use this URL as the ACTIONS_CACHE_URL rather than start a server by itself.
  # The URL should generally end with "/".
  external_server: ""

container:
  # Specifies the network to which the container will connect.
  # Could be host, bridge or the name of a custom network.
  # If it's empty, act_runner will create a network automatically.
  network: ""
  # Whether to use privileged mode or not when launching task containers (privileged mode is required for Docker-in-Docker).
  privileged: true
  # And other options to be used when the container is started (eg, --add-host=my.gitea.url:host-gateway).
  options:
  # The parent directory of a job's working directory.
  # If it's empty, /workspace will be used.
  workdir_parent:
  # Volumes (including bind mounts) can be mounted to containers. Glob syntax is supported, see https://github.com/gobwas/glob
  # You can specify multiple volumes. If the sequence is empty, no volumes can be mounted.
  # For example, if you only allow containers to mount the `data` volume and all the json files in `/src`, you should change the config to:
  # valid_volumes:
  #   - data
  #   - /src/*.json
  # If you want to allow any volume, please use the following configuration:
  # valid_volumes:
  #   - '**'
  valid_volumes: []
  # overrides the docker client host with the specified one.
  # If it's empty, act_runner will find an available docker host automatically.
  # If it's "-", act_runner will find an available docker host automatically, but the docker host won't be mounted to the job containers and service containers.
  # If it's not empty or "-", the specified docker host will be used. An error will be returned if it doesn't work.
  docker_host: ""

host:
  # The parent directory of a job's working directory.
  # If it's empty, $HOME/.cache/act/ will be used.
  workdir_parent:

We can then use the token to then launch a runner

builder@builder-T100:~/Forgejo$ docker run -v /home/builder/Forgejo/config.yaml:/config.yaml -v /home/builder/Forgejo/data:/data -v /var/run/docker.sock:/var/run/docker.sock -e CONFIG_FILE=/config.yaml -e GITEA_INSTANCE_URL=https://forgejo.freshbrewed.science/ -e GITEA_RUNNER_REGISTRATION_TOKEN=AjF1ERsEzZHMgGbXnuGUmki8fbVsXrZwsswZqvZs -e GITEA_RUNNER_NAME=buildert100 -e GITEA_RUNNER_LABELS=onprem --name buildert100gf -d gitea/act_runner:nightly
5abdc928e11dd96a83662ea2735b83673df1d66532829f94def97b79b477e8ce

Then we can see it launched

/content/images/2023/12/misctools-35.png

Testing the runner

We can create a workflow in much the same manner as Gitea

builder@LuiGi17:~/Workspaces/MyPublicRepo/.gitea/workflows$ ls
test2.yaml  test3.yaml
builder@LuiGi17:~/Workspaces/MyPublicRepo/.gitea/workflows$ cat test2.yaml
name: TestRunner2
run-name: $ building test
on: [push]

jobs:
  TestRunner:
    runs-on: ubuntu-22.04
    steps:
      - name: apt
        run: |
          hostname

          # if running as non-root, add sudo
          echo test
builder@LuiGi17:~/Workspaces/MyPublicRepo/.gitea/workflows$ cat test3.yaml
name: TestRunner3
run-name: $ building test
on: [push]

jobs:
  TestRunner:
    steps:
      - name: Check out repository code
        uses: actions/checkout@v3
      - name: Setup Docker
        run: |
          # if running as non-root, add sudo
          apt-get update && \
          apt-get install -y \
             ca-certificates \
             curl \
             gnupg \
             sudo \
             lsb-release

          curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

          echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu focal stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null

          apt-get update

          apt install -y docker-ce-cli

          docker run hello-world
          #apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
      - name: Test Docker
        run: |
          # if running as non-root, add sudo
          echo hi
          docker ps

Here we have two workflows. Test2 does effectively nothing and Test3 will setup docker on a current container and run a “Hello World” test image

We can see they run:

/content/images/2023/12/misctools-36.png

Test2 just shows the hostname

/content/images/2023/12/misctools-37.png

and Test3 will show it installs the Docker CLI and runs the Hello World image. This is possible because we mounted the docker sockets when we launched it on the host.

/content/images/2023/12/misctools-38.png

Mirroring

First, let’s setup a repo in Gitea that will be our sync target

/content/images/2023/12/misctools-39.png

I now have an uninitialized target repo we can use

/content/images/2023/12/misctools-40.png

I’ll now add that as the sync target in Forgejo

/content/images/2023/12/misctools-41.png

and once saved, see it confirmed

/content/images/2023/12/misctools-42.png

Clicking Sync Now tells me it’s in progress

/content/images/2023/12/misctools-43.png

Then synced

/content/images/2023/12/misctools-44.png

We can sync the other way as well.

Here, I’ll create a repo in Forgejo

/content/images/2023/12/misctools-45.png

And then use it as a Mirror destination in Gitea

/content/images/2023/12/misctools-46.png

With a push, I now have a full backup of the contents

/content/images/2023/12/misctools-47.png

Webhooks to Matrix

We can add a Matrix webhook for events

/content/images/2023/12/misctools-48.png

now events push notifications

/content/images/2023/12/misctools-49.png

Gitea vs Forgejo

They seem so similar, what really is the difference?

This stems from October 2022 when the Open-Source suite was transferred to a for-profit company. Forgejo, the Esperanto word for forge, was created.

Gitea has a commercial side

/content/images/2023/12/misctools-50.png

whereas Forgejo does not and it’s trademarks are held in trust by a German not-for-profit, Codeberg e.V. (eingetragener Verein)

Because Gitea has held MIT licensing, Forgejo is free to pull down the source (and in their opinion) improve upon it and secure it.

Summary

I really like Forgejo. I’m really stuck in picking between Forgejo and Gitea. Because Gitea dropped MySQL (MariaDB) support and my NAS doesnt natively carry PosgreSQL, I might well lean into Forgejo. I do think there is value in having dual GIT systems that sync to each other. I might even explore at a later date some kind of regular restorable offsite backups.

Even though both Gitea workflows and Forgejo workflows, in my demo, used the SAME Gitea container image, I routinely had better performance from Forgejo. It just worked. Gitea workflows, at least for me, often get stuck and i have to cancel and “re-run all jobs” to get them going again. However, it could just be a nuance of my particular version of Gitea.

Forgejo GIT Container Docker

Have something to add? Feedback? Try our new forums

Isaac Johnson

Isaac Johnson

Cloud Solutions Architect

Isaac is a CSA and DevOps engineer who focuses on cloud migrations and devops processes. He also is a dad to three wonderful daughters (hence the references to Princess King sprinkled throughout the blog).

Theme built by C.S. Rhymes