Gitea Part 2: Actions, Runners, Mailer and more

Published: Aug 3, 2023 by Isaac Johnson

In our last post we covered Gitea setup on a temporary cluster and explored the basics of Organizations and Repositories.

Today we’ll look at Actions, Runners and Branch Protections. We’ll also take the time to create a properly exposed Gitea instance using helm with a supported non-containerized backend database. We’ll also demonstrate setting up the Mailer block to use Sendgrid.

Gitea Actions

Much like Github Actions, Gitea Actions are a method of running workflows directly out of our Gitea code repository. It is still a relatively new feature (going live just in 1.19) and is disabled by default.

$ helm upgrade gitea --set gitea.config.actions.ENABLED=true gitea-charts/gitea
coalesce.go:175: warning: skipped value for memcached.initContainers: Not a table.
Release "gitea" has been upgraded. Happy Helming!
NAME: gitea
LAST DEPLOYED: Mon Jul 17 14:01:40 2023
NAMESPACE: default
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 default port-forward svc/gitea-http 3000:3000

I can see the change reflected in the inline config

$ kubectl get secret gitea-inline-config -o yaml | head -n4
apiVersion: v1
data:
  _generals_: ""
  actions: RU5BQkxFRD10cnVl

$ echo RU5BQkxFRD10cnVl | base64 --decode

I can now go to site adminstration and see a Runners section. There I can create a new runner with a token

/content/images/2023/08/gitea2-01.png

To add the runner, I’ll want to add a DinD version. We can see an example here which I modified to run two runners and use local-path storage

$ cat gitea-dind.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: act-runner-vol
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: local-path
---
apiVersion: v1
data:
  token: YTRGdDZ2WG9GZm1Pd3pKM3pDOGFrbTVFazlJVFpCV1NGeUJpaTZ1ZQ==
kind: Secret
metadata:
  name: runner-secret
type: Opaque
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: act-runner
  name: act-runner
spec:
  replicas: 2
  selector:
    matchLabels:
      app: act-runner
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: act-runner
    spec:
      restartPolicy: Always
      volumes:
      - name: docker-certs
        emptyDir: {}
      - name: runner-data
        persistentVolumeClaim:
          claimName: act-runner-vol
      containers:
      - name: runner
        image: gitea/act_runner:nightly
        command: ["sh", "-c", "while ! nc -z localhost 2376 </dev/null; do echo 'waiting for docker daemon...'; sleep 5; done; /sbin/tini -- /opt/act/run.sh"]
        env:
        - name: DOCKER_HOST
          value: tcp://localhost:2376
        - name: DOCKER_CERT_PATH
          value: /certs/client
        - name: DOCKER_TLS_VERIFY
          value: "1"
        - name: GITEA_INSTANCE_URL
          value: http://gitea-http.default.svc.cluster.local:3000
        - name: GITEA_RUNNER_REGISTRATION_TOKEN
          valueFrom:
            secretKeyRef:
              name: runner-secret
              key: token
        volumeMounts:
        - name: docker-certs
          mountPath: /certs
        - name: runner-data
          mountPath: /data
      - name: daemon
        image: docker:23.0.6-dind
        env:
        - name: DOCKER_TLS_CERTDIR
          value: /certs
        securityContext:
          privileged: true
        volumeMounts:
        - name: docker-certs
          mountPath: /certs

I applied and then checked on the PVC and runner creation

$ kubectl apply -f gitea-dind.yaml
persistentvolumeclaim/act-runner-vol created
secret/runner-secret created
deployment.apps/act-runner created

$ kubectl get pvc
NAME                      STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
data-gitea-0              Bound    pvc-bffada16-6a38-4968-be79-604ede967b13   10Gi       RWO            local-path     8h
data-gitea-postgresql-0   Bound    pvc-1c317391-7d2c-405d-a505-d8fe42c44f03   10Gi       RWO            local-path     8h
act-runner-vol            Bound    pvc-b15a377c-8d9f-427f-9e52-d9b6cf457dc0   1Gi        RWO            local-path     7s

$ kubectl get pods
NAME                                                READY   STATUS              RESTARTS      AGE
nginx-78cc4c645b-lxpbc                              1/1     Running             0             5d6h
ngrok-669dd5fdd8-lxdkg                              1/1     Running             2 (28h ago)   7d6h
vote-front-azure-vote-1688994153-6fdc76bdd9-vk92z   1/1     Running             1 (28h ago)   5d6h
vote-back-azure-vote-1688994153-7b76fb69b9-xxzgc    1/1     Running             1 (28h ago)   7d6h
gitea-memcached-8666cf9db5-s2p4q                    1/1     Running             0             8h
gitea-postgresql-0                                  1/1     Running             0             8h
gitea-0                                             1/1     Running             0             20m
act-runner-84f56cb5c4-9nsnm                         2/2     Running   0             19s
act-runner-84f56cb5c4-rdtwq                         2/2     Running   0             17s

I could now see one runner already registered

/content/images/2023/08/gitea2-02.png

Let’s create a simple hello-world action

builder@DESKTOP-QADGF36:~/Workspaces/PublicShared$ mkdir -p .gitea/workflows
builder@DESKTOP-QADGF36:~/Workspaces/PublicShared$ touch .gitea/workflows/hello.yaml
builder@DESKTOP-QADGF36:~/Workspaces/PublicShared$ vi .gitea/workflows/hello.yaml

$ cat .gitea/workflows/hello.yaml
name: Gitea Actions Demo
run-name: $ is testing out Gitea Actions 🚀
on: [push]
jobs:
  Explore-Gitea-Actions:
    runs-on: ubuntu-latest
    steps:
      - run: echo "🎉 The job was automatically triggered by a $ event."
      - run: echo "🐧 This job is now running on a $ server hosted by Gitea!"
      - run: echo "🔎 The name of your branch is $ and your repository is $."
      - name: Check out repository code
        uses: actions/checkout@v3
      - run: echo "💡 The $ repository has been cloned to the runner."
      - run: echo "🖥️ The workflow is now ready to test your code on the runner."
      - name: List files in the repository
        run: |
          ls $
      - run: echo "🍏 This job's status is $."

I’ll now add and push

builder@DESKTOP-QADGF36:~/Workspaces/PublicShared$ git commit -m "first try"
[feature 41dfeef] first try
 1 file changed, 18 insertions(+)
 create mode 100644 .gitea/workflows/hello.yaml
builder@DESKTOP-QADGF36:~/Workspaces/PublicShared$ git push
Enumerating objects: 6, done.
Counting objects: 100% (6/6), done.
Delta compression using up to 16 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (5/5), 784 bytes | 784.00 KiB/s, done.
Total 5 (delta 1), reused 0 (delta 0)
remote:
remote: Create a new pull request for 'feature':
remote:   http://git.example.com/FreshBrewed/PublicShared/compare/main...feature
remote:
remote: . Processing 1 references
remote: Processed 1 references in total
To http://localhost:3000/FreshBrewed/PublicShared.git
 * [new branch]      feature -> feature

I put it in a feature branch which I would not expect to execute. I’ll need to create a PR

/content/images/2023/08/gitea2-03.png

I merged then removed the branch

/content/images/2023/08/gitea2-04.png

Initially I didn’t see any actions run. This was because I needed to enable actions on the repository

/content/images/2023/08/gitea2-05.png

I can see the actions, but it’s set to trigger on a push

/content/images/2023/08/gitea2-06.png

I’ll switch to main and push an empty commit to trigger a run

builder@DESKTOP-QADGF36:~/Workspaces/PublicShared$ git checkout main
Switched to branch 'main'
Your branch is up to date with 'origin/main'.
builder@DESKTOP-QADGF36:~/Workspaces/PublicShared$ git pull
remote: Enumerating objects: 6, done.
remote: Counting objects: 100% (6/6), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 4 (delta 1), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (4/4), 893 bytes | 893.00 KiB/s, done.
From http://localhost:3000/FreshBrewed/PublicShared
   4e71d4c..5966983  main       -> origin/main
 * [new tag]         0.1.0      -> 0.1.0
Updating 4e71d4c..5966983
Fast-forward
 .gitea/workflows/hello.yaml | 18 ++++++++++++++++++
 Feature.md                  |  1 +
 2 files changed, 19 insertions(+)
 create mode 100644 .gitea/workflows/hello.yaml
 create mode 100644 Feature.md
builder@DESKTOP-QADGF36:~/Workspaces/PublicShared$ git commit --allow-empty -m "push an empty commit to trigger an action run"
[main df3f35c] push an empty commit to trigger an action run
builder@DESKTOP-QADGF36:~/Workspaces/PublicShared$ git push
Enumerating objects: 1, done.
Counting objects: 100% (1/1), done.
Writing objects: 100% (1/1), 211 bytes | 211.00 KiB/s, done.
Total 1 (delta 0), reused 0 (delta 0)
remote: . Processing 1 references
remote: Processed 1 references in total
To http://localhost:3000/FreshBrewed/PublicShared.git
   5966983..df3f35c  main -> main

I can now see it running

/content/images/2023/08/gitea2-07.png

The first step took a bit but the rest were quick

/content/images/2023/08/gitea2-08.png

I can expand some of the steps to see the outputs

/content/images/2023/08/gitea2-09.png

Secrets

Say our Runner needs to do something that requires a secret, for instance, update an S3 bucket or GCP cloud bucket. We would need AWS Secret Access Keys and secrets or a GCP SA json.

This is where Secrets come to play

We can create a new secret in Secrets

/content/images/2023/08/gitea2-10.png

I can now see the entry under Secrets

/content/images/2023/08/gitea2-11.png

I’ll try and show the secret

$ cat .gitea/workflows/hello.yaml
name: Gitea Actions Demo
run-name: $ is testing out Gitea Actions 🚀
on: [push]
jobs:
  Explore-Gitea-Actions:
    runs-on: ubuntu-latest
    steps:
      - run: echo "🎉 The job was automatically triggered by a $ event."
      - run: echo "🐧 This job is now running on a $ server hosted by Gitea!"
      - run: echo "🔎 The name of your branch is $ and your repository is $."
      - name: Check out repository code
        uses: actions/checkout@v3
      - run: echo "💡 The $ repository has been cloned to the runner."
      - run: echo "🖥️ The workflow is now ready to test your code on the runner."
      - name: secretstep
        run: echo "$MYSECVAL -- $MYSECRET"
        env:
          MYSECVAL: $
      - name: List files in the repository
        run: |
          export
          ls $
        env:
          MYSECVAL: $
      - run: echo "🍏 This job's status is $."
      - run: echo "🍏 This job's status is $."

We can see that it is properly exposed as an env var

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

Branch protections

You can tell I pushed directly to main when I made my most recent change. Generally we want to ensure PRs are created on restricted branches.

An easy way to do this is to use Branch Protection.

We can go to “Branches” and click “Add New Rule” to create a proper branch branch protection rule

/content/images/2023/08/gitea2-13.png

I would like to make sure only gitea_admin can push. I’ll require approvals from the admin, block on rejected reviews, dismiss stale approvals and block if the PR is outdated.

/content/images/2023/08/gitea2-14.png

Since I’m locally logged in as ‘builder’, we can see my direct push to main was rejected

builder@DESKTOP-QADGF36:~/Workspaces/PublicShared$ vi .gitea/workflows/hello.yaml
builder@DESKTOP-QADGF36:~/Workspaces/PublicShared$ git add .gitea/workflows/hello.yaml
builder@DESKTOP-QADGF36:~/Workspaces/PublicShared$ git commit -m 'remove a debug step'
[main 535ecc7] remove a debug step
 1 file changed, 4 deletions(-)
builder@DESKTOP-QADGF36:~/Workspaces/PublicShared$ git push
Enumerating objects: 9, done.
Counting objects: 100% (9/9), done.
Delta compression using up to 16 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (5/5), 400 bytes | 400.00 KiB/s, done.
Total 5 (delta 2), reused 0 (delta 0)
remote:
remote: Gitea: Not allowed to push to protected branch main
To http://localhost:3000/FreshBrewed/PublicShared.git
 ! [remote rejected] main -> main (pre-receive hook declined)
error: failed to push some refs to 'http://localhost:3000/FreshBrewed/PublicShared.git'

I’ll switch to a feature branch and try again

builder@DESKTOP-QADGF36:~/Workspaces/PublicShared$ git checkout -b feature-remove-debug-step-in-workflow
Switched to a new branch 'feature-remove-debug-step-in-workflow'
builder@DESKTOP-QADGF36:~/Workspaces/PublicShared$ git push
fatal: The current branch feature-remove-debug-step-in-workflow has no upstream branch.
To push the current branch and set the remote as upstream, use

    git push --set-upstream origin feature-remove-debug-step-in-workflow

builder@DESKTOP-QADGF36:~/Workspaces/PublicShared$ darf
git push --set-upstream origin feature-remove-debug-step-in-workflow [enter/↑/↓/ctrl+c]
Enumerating objects: 9, done.
Counting objects: 100% (9/9), done.
Delta compression using up to 16 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (5/5), 400 bytes | 400.00 KiB/s, done.
Total 5 (delta 2), reused 0 (delta 0)
remote:
remote: Create a new pull request for 'feature-remove-debug-step-in-workflow':
remote:   http://git.example.com/FreshBrewed/PublicShared/compare/main...feature-remove-debug-step-in-workflow
remote:
remote: . Processing 1 references
remote: Processed 1 references in total
To http://localhost:3000/FreshBrewed/PublicShared.git
 * [new branch]      feature-remove-debug-step-in-workflow -> feature-remove-debug-step-in-workflow
Branch 'feature-remove-debug-step-in-workflow' set up to track remote branch 'feature-remove-debug-step-in-workflow' from 'origin'.

I can now make a new PR

/content/images/2023/08/gitea2-15.png

If I am logged in as gitea_admin, there really are no blocks. So I logged out and came back as builder to create the PR

Unfortunately I am an admin as builder as well so I could force a merge

/content/images/2023/08/gitea2-16.png

Make it live

We’ve had some fun on a demo cluster. I can clearly see this is something I’m going to want to keep. So let’s move this to production.

I’ll create and apply an A record

$ cat r53-gitea.json 
{
    "Comment": "CREATE gitea fb.s A record ",
    "Changes": [
      {
        "Action": "CREATE",
        "ResourceRecordSet": {
          "Name": "gitea.freshbrewed.science",
          "Type": "A",
          "TTL": 300,
          "ResourceRecords": [
            {
              "Value": "75.72.238.228"
            }
          ]
        }
      }
    ]
  }
$ aws route53 change-resource-record-sets --hosted-zone-id Z39E8QFU0F9PZP --change-batch file://r53-gitea.json
{
    "ChangeInfo": {
        "Id": "/change/C01488602LUSLC1J41O82",
        "Status": "PENDING",
        "SubmittedAt": "2023-07-18T10:45:41.728Z",
        "Comment": "CREATE gitea fb.s A record "
    }
}

I thought about keeping it simple with a series of set commands and adding Ingress after the fact, but let’s try and use a proper values file

$ cat values.yaml
gitea:
  admin:
    username: "builder"
    password: "NOTMYPASSWORDCLEARLY"
    email: "isaac.johnson@gmail.com"
  config:
    actions:
      ENABLED: true
    server:
      DOMAIN: gitea.freshbrewed.science
      ROOT_URL: https://gitea.freshbrewed.science/
  metrics:
    enabled: true

global:
  storageClass: "managed-nfs-storage"

ingress:
  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"
  enabled: true
  hosts:
  - host: gitea.freshbrewed.science
    paths:
    - path: /
      pathType: ImplementationSpecific
  tls:
  - hosts:
    - gitea.freshbrewed.science
    secretName: gitea-tls

Then install

$ helm upgrade --install gitea -f values.yaml gitea-charts/gitea
coalesce.go:175: warning: skipped value for memcached.initContainers: Not a table.
Release "gitea" has been upgraded. Happy Helming!
NAME: gitea
LAST DEPLOYED: Tue Jul 18 06:10:59 2023
NAMESPACE: default
STATUS: deployed
REVISION: 2
NOTES:
1. Get the application URL by running these commands:
  https://gitea.freshbrewed.science/

It took a full three and half minutes to get the cert through, but when it was complete, I could see the instance

/content/images/2023/08/gitea2-17.png

I can login with the admin account I specified earlier

/content/images/2023/08/gitea2-18.png

I realized one consequence is the built-in containerized database

$ kubectl get pods | grep gitea
gitea-memcached-5bd6f8f89d-lvbxt                         1/1     Running   0                13m
gitea-postgresql-0                                       1/1     Running   0                13m
gitea-0                                                  1/1     Running   0                13m

If I’m going to put source code here, I need a more permanent DB

While PostgreSQL isn’t in the app list for Synology, MariaDB (MySQL) is.

/content/images/2023/08/gitea2-19.png

I’ll install (again, using the new DS220+ NAS)

/content/images/2023/08/gitea2-20.png

I’ll need to enable TCP/IP on 3306 if I want Kubernetes to be able to access it

/content/images/2023/08/gitea2-21.png

I’ll want to setup an account for gitea. As a forcing function to ensure it is listening on 3306, I’ll do the steps locally.

First I need to get mysql client binaries (I do a lot more PSQL than MySQL)


Reading package lists... Done
Building dependency tree
Reading state information... Done
The following packages were automatically installed and are no longer required:
  libappstream-glib8 liblttng-ust-ctl4 liblttng-ust0 python3-crcmod xdg-dbus-proxy
Use 'sudo apt autoremove' to remove them.
The following additional packages will be installed:
  mariadb-common mysql-common
The following NEW packages will be installed:
  mariadb-client-core-10.3 mariadb-common mysql-common
0 upgraded, 3 newly installed, 0 to remove and 318 not upgraded.
Need to get 5878 kB of archives.
After this operation, 28.4 MB of additional disk space will be used.
Do you want to continue? [Y/n] Y
Get:1 http://archive.ubuntu.com/ubuntu focal/main amd64 mysql-common all 5.8+1.0.5ubuntu2 [7496 B]
Get:2 http://archive.ubuntu.com/ubuntu focal-updates/universe amd64 mariadb-common all 1:10.3.38-0ubuntu0.20.04.1 [15.9 kB]
Get:3 http://archive.ubuntu.com/ubuntu focal-updates/universe amd64 mariadb-client-core-10.3 amd64 1:10.3.38-0ubuntu0.20.04.1 [5855 kB]
Fetched 5878 kB in 1s (9156 kB/s)
Selecting previously unselected package mysql-common.
(Reading database ... 210532 files and directories currently installed.)
Preparing to unpack .../mysql-common_5.8+1.0.5ubuntu2_all.deb ...
Unpacking mysql-common (5.8+1.0.5ubuntu2) ...
Selecting previously unselected package mariadb-common.
Preparing to unpack .../mariadb-common_1%3a10.3.38-0ubuntu0.20.04.1_all.deb ...
Unpacking mariadb-common (1:10.3.38-0ubuntu0.20.04.1) ...
Selecting previously unselected package mariadb-client-core-10.3.
Preparing to unpack .../mariadb-client-core-10.3_1%3a10.3.38-0ubuntu0.20.04.1_amd64.deb ...
Unpacking mariadb-client-core-10.3 (1:10.3.38-0ubuntu0.20.04.1) ...
Setting up mysql-common (5.8+1.0.5ubuntu2) ...
update-alternatives: using /etc/mysql/my.cnf.fallback to provide /etc/mysql/my.cnf (my.cnf) in auto mode
Setting up mariadb-common (1:10.3.38-0ubuntu0.20.04.1) ...
update-alternatives: using /etc/mysql/mariadb.cnf to provide /etc/mysql/my.cnf (my.cnf) in auto mode
Setting up mariadb-client-core-10.3 (1:10.3.38-0ubuntu0.20.04.1) ...
Processing triggers for man-db (2.9.1-1) ...
...

While, from a networking perspective, I can connect. MariaDB doesn’t like remote access from my desktop

builder@DESKTOP-QADGF36:~/Workspaces/ansible-playbooks$ mysql -u root -p --host=192.168.1.117
Enter password:
ERROR 1130 (HY000): Host 'DESKTOP-QADGF36' is not allowed to connect to this MariaDB server

I’ll need to do the commands locally for now (but I’ll test again back on the other desktop)

builder@DESKTOP-QADGF36:~/Workspaces/ansible-playbooks$ ssh ijohnson@192.168.1.117
ijohnson@192.168.1.117's password:

Using terminal commands to modify system configs, execute external binary
files, add files, or install unauthorized third-party apps may lead to system
damages or unexpected behavior, or cause data loss. Make sure you are aware of
the consequences of each command and proceed at your own risk.

Warning: Data should only be stored in shared folders. Data stored elsewhere
may be deleted when the system is updated/restarted.

ijohnson@sirnasilot:~$ mysql -u root -p
Enter password:
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 10
Server version: 10.3.37-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)]>

My MariaDB has password rules so I cannot do the simple passwords

MariaDB [(none)]> CREATE USER 'gitea'@'%' IDENTIFIED BY 'gitea1234';
ERROR 1819 (HY000): Your password does not satisfy the current policy requirements: [Minimal password length 10, Include mixed case, Include special characters, Exclude name of user from password]

I used a more painfully long complicated password (rather than fight MariaDB).

The end result (with a more complicated password, of course):

ijohnson@sirnasilot:~$ mysql -u root -p
Enter password:
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 10
Server version: 10.3.37-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)]> set old_passwords=0;
Query OK, 0 rows affected (0.000 sec)

MariaDB [(none)]> CREATE USER 'gitea'@'%' IDENTIFIED BY 'NOTTHEPASSWORDIUSED';
Query OK, 0 rows affected (0.024 sec)

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

MariaDB [(none)]> GRANT ALL PRIVILEGES ON giteadb.* TO 'gitea'@'%';
Query OK, 0 rows affected (0.000 sec)

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

I can now test on the workstation

builder@DESKTOP-QADGF36:~/Workspaces/ansible-playbooks$ mysql -u gitea -p --host=192.168.1.117
Enter password:
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 12
Server version: 10.3.37-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)]>

I’ll now update my values.yaml file (again passwords are there but replaced below)

$ cat values.yaml
gitea:
  admin:
    username: "builder"
    password: "NOPENOTMYPASSWORD"
    email: "isaac.johnson@gmail.com"
  config:
    actions:
      ENABLED: true
    database:
      DB_TYPE: mysql
      HOST: 192.168.1.117
      NAME: giteadb
      USER: gitea
      PASSWD: THEDBPASSWORDIUSED
      SCHEMA: gitea
    server:
      DOMAIN: gitea.freshbrewed.science
      ROOT_URL: https://gitea.freshbrewed.science/
  metrics:
    enabled: true

postgresql:
  enabled: false

global:
  storageClass: "managed-nfs-storage"

ingress:
  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"
  enabled: true
  hosts:
  - host: gitea.freshbrewed.science
    paths:
    - path: /
      pathType: ImplementationSpecific
  tls:
  - hosts:
    - gitea.freshbrewed.science
    secretName: gitea-tls

Let’s upgrade to use the chart

$ helm upgrade --install gitea -f values.yaml gitea-charts/gitea
Release "gitea" has been upgraded. Happy Helming!
NAME: gitea
LAST DEPLOYED: Tue Jul 18 06:46:50 2023
NAMESPACE: default
STATUS: deployed
REVISION: 3
NOTES:
1. Get the application URL by running these commands:
  https://gitea.freshbrewed.science/

I had some errors connecting. I granted more access rights to the gitea user to see if that would help (as this mariadb is just for gitea for now)

ijohnson@sirnasilot:~$ mysql -u root -p
Enter password:
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 23
Server version: 10.3.37-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)]> GRANT ALL PRIVILEGES ON *.* TO 'gitea'@'%' WITH GRANT OPTION;
Query OK, 0 rows affected (0.000 sec)

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

MariaDB [(none)]> exit;
Bye

The startup was taking time

/content/images/2023/08/gitea2-22.png

$ kubectl get pods gitea-0
NAME      READY   STATUS     RESTARTS   AGE
gitea-0   0/1     Init:2/3   0          4m12s

So I checked logs. Indeed, it just slowly builds and populates the database

$ kubectl logs gitea-0 configure-gitea | tail -n5
2023/07/18 11:58:28 models/db/engine.go:125:SyncAllTables() [I] [SQL] CREATE INDEX `IDX_gpg_key_key_id` ON `gpg_key` (`key_id`) [] - 2.189529088s
2023/07/18 11:58:30 models/db/engine.go:125:SyncAllTables() [I] [SQL] CREATE TABLE IF NOT EXISTS `gpg_key_import` (`key_id` CHAR(16) PRIMARY KEY NOT NULL, `content` MEDIUMTEXT NOT NULL) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 ROW_FORMAT=DYNAMIC [] - 1.925290483s
2023/07/18 11:58:32 models/db/engine.go:125:SyncAllTables() [I] [SQL] CREATE TABLE IF NOT EXISTS `public_key` (`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL, `owner_id` BIGINT(20) NOT NULL, `name` VARCHAR(255) NOT NULL, `fingerprint` VARCHAR(255) NOT NULL, `content` MEDIUMTEXT NOT NULL, `mode` INT DEFAULT 2 NOT NULL, `type` INT DEFAULT 1 NOT NULL, `login_source_id` BIGINT(20) DEFAULT 0 NOT NULL, `created_unix` BIGINT(20) NULL, `updated_unix` BIGINT(20) NULL, `verified` TINYINT(1) DEFAULT false NOT NULL) ENGINE=InnoDB DEFAULT CHARSET utf8mb4 ROW_FORMAT=DYNAMIC [] - 1.224519258s
2023/07/18 11:58:33 models/db/engine.go:125:SyncAllTables() [I] [SQL] CREATE INDEX `IDX_public_key_fingerprint` ON `public_key` (`fingerprint`) [] - 1.41758808s
2023/07/18 11:58:34 models/db/engine.go:125:SyncAllTables() [I] [SQL] CREATE INDEX `IDX_public_key_owner_id` ON `public_key` (`owner_id`) [] - 1.26717571s

Maybe my MariaDB is non-performant, but in the end it took nearly 14m to come up

$ kubectl get pods gitea-0
NAME      READY   STATUS    RESTARTS   AGE
gitea-0   1/1     Running   0          15m

I signed in and found it to be just as performant as before.

My first step was to make a public organization

/content/images/2023/08/gitea2-23.png

At the moment, it is enabled to allow anyone to create a Gitea account. That could spell trouble

/content/images/2023/08/gitea2-24.png

I updated the values to add both a Sendgrid mailer, enable OpenID and disable self registration

$ cat values.yaml
gitea:
  admin:
    username: "builder"
    password: "STILLNOTTHEREALPASSWORD"
    email: "isaac.johnson@gmail.com"
  config:
    actions:
      ENABLED: true
    database:
      DB_TYPE: mysql
      HOST: 192.168.1.117
      NAME: giteadb
      USER: gitea
      PASSWD: STILLNOTTHEREALPASSWORD
      SCHEMA: gitea
    mailer:
      ENABLED: true
      FROM: isaac@freshbrewed.science
      MAILER_TYPE: smtp
      SMTP_ADDR: smtp.sendgrid.net
      SMTP_PORT: 465
      USER: apikey
      PASSWD: SG.STILLNOTTHEREALPASSWORD
    openid:
      ENABLE_OPENID_SIGNIN: false
      ENABLE_OPENID_SIGNUP: false
    server:
      DOMAIN: gitea.freshbrewed.science
      ROOT_URL: https://gitea.freshbrewed.science/
    service:
      DISABLE_REGISTRATION: true

  metrics:
    enabled: true

postgresql:
  enabled: false

global:
  storageClass: "managed-nfs-storage"

ingress:
  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"
  enabled: true
  hosts:
  - host: gitea.freshbrewed.science
    paths:
    - path: /
      pathType: ImplementationSpecific
  tls:
  - hosts:
    - gitea.freshbrewed.science
    secretName: gitea-tls

This time it took less than 20s for the pod to come up after i did a helm upgrade meaning the DB init steps really are just one time.

I now see this for registration

/content/images/2023/08/gitea2-25.png

With sendgrid enabled, i can now properly handle password resets

/content/images/2023/08/gitea2-26.png

Actions setup

Since I now have a proper instance, I best add some Actions Workers

I’ll create a new runner token

/content/images/2023/08/gitea2-27.png

I really have a choice on the INSTANCE URL.

Since the runner is local to the cluster, I could just use the external name

        - name: GITEA_INSTANCE_URL
          value: https://gitea.freshbrewed.science

However, since it is in the same namespace, I’ll just do as before and go right to the service

        - name: GITEA_INSTANCE_URL
          value: http://gitea-http.default.svc.cluster.local:3000

I would need to former to launch this in a different cluster

$ kubectl apply -f gitea-dind-final.yaml
persistentvolumeclaim/act-runner-vol created
secret/runner-secret created
deployment.apps/act-runner created

I can now see two workers

$ kubectl get pods -l app=act-runner
NAME                          READY   STATUS    RESTARTS   AGE
act-runner-658c549869-fkskt   2/2     Running   0          62s
act-runner-658c549869-n8wr8   2/2     Running   0          62s

and the same in Gitea

/content/images/2023/08/gitea2-28.png

Summary

In Part 2 we have explored actions, using the Docker in Docker (DinD) yaml to launch local runners, and tested workflows. We looked at passing secrets to workflows and applying branch protections to ensure PRs on our Repositories.

We then pivoted to creating a production on-prem version in Kubernetes using an external MariaDB (MySQL) Database hosted on a NAS. Once setup, we updated our chart to disable user signup, add a properly mailer configuration for SendGrid and create the first public repo (live now at https://gitea.freshbrewed.science/FreshbrewedPublic).

Kubernetes Git Gitea

Have something to add? Feedback? You can use the feedback form

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