Script your way into a fresh Gitea on K8S

Martien van den Akker
Nerd For Tech
Published in
9 min readNov 11, 2024

Now, this may sound as a crash-course in hacking. But, it’s not exactly.
In my current task, I created a bootstrap that installs MySQL-Cluster and Gitea into Oracle Kubernetes Engine cluster. In that OKE cluster, Istio, External Secrets Operator, and Cert-Manager all are pre-configured. So, after installing Gitea properly, I can access it through both ssh and https. But, only through a proxy. For the course of this article, I assume that Gitea, backed with a MySQL Cluster as a database, is already installed.

So, that means I have to:

  • Create a user in Gitea
  • Configure the repository in Git to be added to the remote user in Gitea
  • Clone the repository into a local folder, using either HTTPs over a socks5 proxy, or SSH.

During the writing of this article I’m going to investigate the way to get a repository created in Gitea for a new user using either SSH or HTTPs.

This is all part of automating the setup of ArgoCD referencing a repository in Gitea. So, in the following, I’ll add snippets of my larger bootstrap script, in the form of bash functions.

Create a system user in Gitea

This is quite easy assuming that you have a Kube context set to the OKE cluster. And Gitea is created and running of course. Then execute a gitea command in one of the gitea pods.

To create a system user I have a few functions:

NS=gitea
CONTAINER=gitea
CONFIG=/data/gitea/conf/app.ini

function get_pod_name(){
local pod_name=$(kubectl -n $NS get pod -l app=gitea --no-headers -o custom-columns=":metadata.name" |head -n 1)
echo $pod_name
}
#
function exec_gitea_cmd(){
local command="gitea --config $CONFIG $1"
local pod_name=$(get_pod_name)
echo "Gitea POD: $NS/$pod_name:$CONTAINER"
kubectl -n $NS exec $pod_name -c $CONTAINER -it -- $command
}

#
# Create a gitea user.
function create_gitea_user(){
local username=$1
local password=$2
local email=$3
local is_admin=${4:-false}
local gitea_command="admin user create --username $username --password $password --email $email"
if [[ "$is_admin" == "true" ]]; then
echo "Create user $username as admin"
gitea_command+=" --admin"
else
echo "Create user $username as regular user"
fi
exec_gitea_cmd "$gitea_command"
if [[ "$is_admin" == "true" ]]; then
echo "Regenerate keys"
exec_gitea_cmd "admin regenerate keys"
echo "Regenerate hooks"
exec_gitea_cmd "admin regenerate hooks"
fi
}

#
# Create a gitea system user.
function create_gitea_system_user(){
local username=$1
local password=$2
local email=$3
local gitea_command="admin user create --username $username --password $password --email $email --must-change-password=false"
echo "Create user $username as system user"
exec_gitea_cmd "$gitea_command"
}

The function get_pod_name() uses the $NS environment variable to get the first of the pods that are created from the gitea deployment, based on the app-label. This is used in the exec_gitea_cmd() function, together with the $CONTAINER variable to execute a gitea command in the Gitea pod-container, using a config as defined in the $CONFIG variable.

The create_gitea_user() uses exec_gitea_cmd() to create a regular user, that can also be an admin user. It assembles an “admin user create” command, using the supplied username, password, and email arguments. If the fourth argument “is_admin” is provided as true, the user is created with the “ — admin” flag, making it an admin user. In that case the keys and hooks are regenerated.

The function create_gitea_system_user(), is similar. But it does not provide the possibility to create an admin user. And, it supplies the “ — must-change-password=false” option. This disables the obligation to change the password upon first login. This is important, because later on, we want to use this user to clone a git repository. And make commits, without needing to change the password.

Assume SSH access

In this section I assume that I’ll go with SSH, since that is my preferred way to checkout a Git repository.

And I assume the following variables are set:

GIT_BASE_DIR=$(git rev-parse --show-toplevel)
CLUSTER_NAME="mycluster"
PROXY_URL="localhost:8080"
GIT_HOSTNAME="gitea"
DNS_ZONE_NAME="mydomain.internal"
GIT_HOST_URL=$GIT_HOSTNAME.$CLUSTER_NAME.$DNS_ZONE_NAME

ARGOCD_USERNAME="argocd"
ARGOCD_EMAIL="info@mailbox.mydomain.internal"
ARGOCD_GIT_PFX="-argocd-git"
ARGOCD_REPOSITORY_NAME="argocd-repo"
ARGOCD_REPOSITORY_DESCR="ArgoCD Repository"
INITIAL_BRANCH="main"
SSH_LOC=~/.ssh
SSH_CONFIG=$SSH_LOC/config

Some of these may be global constants. like $GIT_HOSTNAME. Some of them may be set externally with a script initializing variables for a certain environment like DNS_ZONE_NAME. And some may be arguments to the script, like CLUSTER_NAME and DNS_ZONE_NAME.

  • GIT_BASE_DIR: My script is placed in it’s own Git repository. To reference the root of this Git repo, I found this statement to get the root of the current repo. This variable helps cloning into a folder next to this repo’s root folder.
  • CLUSTER_NAME: Name of the cluster, also used as DNS Sub Domain
  • PROXY_URL: host + port to the proxy server or ssh-tunnel
  • GIT_HOSTNAME: Host name of the Gitea user, assumed to be ”gitea”
  • DNS_ZONE_NAME: DNS zone or domain name, for instance “oracle.internal”
  • GIT_HOST_URL: actual DNS domain name of the Gitea server.
  • ARGOCD_USERNAME: Name of the ArgoCD user to create in Gitea
  • ARGOCD_EMAIL: email to use in the SSH Key
  • ARGOCD_GIT_PFX: post-fix for the SSH Key name
  • ARGOCD_REPOSITORY_NAME: Name of the ArgoCD Git Repository
  • ARGOCD_REPOSITORY_DESCR: Desription to set on the ArgoCD Repository
  • INITIAL_BRANCH: Initial Master/Main Git branch
  • SSH_LOC=~/.ssh
  • SSH_CONFIG: SSH Configuration file

I first assume that I would be using SSH. So, I need to generate a SSH key first.

Generate a SSH Key

An SSH key can be generated using the following bash function:

#
# Generate an SSH Key
function generate_ssh_key(){
local cluster_name=$1
local email_address=$2
local ssh_key_name=$cluster_name$ARGOCD_GIT_PFX
if [ -f $SSH_LOC/$ssh_key_name ]; then
loginfo "Key $SSH_LOC/$ssh_key_name for $email_address already exists; skipping it."
else
loginfo "Generate rsa key $SSH_LOC/$ssh_key_name"
ssh-keygen -t rsa -C "$email_address" -f $SSH_LOC/$ssh_key_name -P ""
fi
}

You can call it like:

generate_ssh_key $CLUSTER_NAME $ARGOCD_EMAIL

Add an SSH key to the new Gitea user

Above, I showed how to connect to a Gitea pod, and run the gitea command line interface in it to create a user. Unfortunately, this CLI does not allow adding an SSH-key to the user, or other more advanced actions.

To add a key to a new Gitea user, you’ll need to invoke a REST service, using cURL. Here we need to use a SOCKS5 proxy with hostname resolution. I assume an SSH tunnel is running on port 8080. I created a little script for this, since it happens that in the office, my SSH tunnel may be killed:

#!/bin/bash
###############################################################################
# Create a tunnel and guard it.
#
# Oracle Consulting Netherlands
#
# History
# -------
# 2024-08-22, M. van den Akker, Initial Creation
#
###############################################################################
export TUNNEL_HOST=vm-bastion
export TUNNEL_PORT=8080
export TUNNEL_USER=opc
export RSA_KEY=~/.ssh/id_rsa_makker
export SLEEP_INTERVAL=10
while true
do
ssh_ps=$(ps -ef |grep ssh | grep $TUNNEL_HOST | grep $TUNNEL_PORT)
exit_code=$?
ssh_pid=$(echo $ssh_ps | tr -s ' ' | cut -d' ' -f2)
if [[ "$exit_code" == 0 ]]; then
echo "SSH Tunnel for $TUNNEL_USER@$TUNNEL_HOST is already running with pid $ssh_pid"
else
echo "Start SSH Tunnel for $TUNNEL_USER@$TUNNEL_HOST with key $RSA_KEY"
ssh -D $TUNNEL_PORT -q -fN -i $RSA_KEY $TUNNEL_USER@$TUNNEL_HOST
fi
sleep $SLEEP_INTERVAL
done

But, how to use a SOCKS5 proxy with cUrl? I found the answer in performing HTTP requests with cURL (using PROXY), where the --socks5-hostname is mentioned. Read more on it in SOCKS proxy.

So with this I could create my function:

# Add a Public Key as SSH key to Gitea user
function add_ssh_key_to_gitea_user(){
local cluster_name=$1
local gitea_admin_username="$2"
local gitea_admin_password="$3"
local gitea_username="$4"
local key_name=$cluster_name$ARGOCD_GIT_PFX
local pub_key=$key_name.pub
loginfo "Get PKCS8 key: $SSH_LOC/$pub_key for $gitea_username"
local gitea_user_pub_key=$(ssh-keygen -f $SSH_LOC/$pub_key -e -m pkcs8);
logdebug "Assemble payload with public key"
local payload=$(jq -n '{key: $ARGS.named["key"], read_only: true, title: $ARGS.named["title"]}' --arg key "$gitea_user_pub_key" --arg title "key-$gitea_username");
logdebug "Add key to $GIT_HOSTNAME.$cluster_name.$DNS_ZONE_NAME/api/v1/admin/users/$gitea_username/keys"
curl -X POST -k \
-H "Content-Type: application/json" \
--socks5-hostname $PROXY_URL \
-d "$payload" \
-u "$gitea_admin_username:$gitea_admin_password" \
$GIT_HOSTNAME.$cluster_name.$DNS_ZONE_NAME/api/v1/admin/users/$gitea_username/keys
}

This function first gets the contents of the public key as a PKCS8 content into $gitea_user_pub_key. Then using jq this is assembled into a JSON-payload. And using that, the key is added using CURL, invoking the /api/v1/admin/users/{gitea_username}/keys API. This is described in Add a public key on behalf of a user.

Using the global environment variables $GIT_HOSTNAME, $DNS_ZONE_NAME, and the argument $cluster_name the URL to the Gitea host is determined. Here the $cluster_name is used as the subdomain of $DNS_ZONE_NAME, because we can have several environments. It is expected that this URL is

To add an SSH Key to the ArgoCD user that is in the $ARGOCD_USERNAME variable, you can call this function using:

add_ssh_key_to_gitea_user $CLUSTER_NAME $gitea_admin_username $gitea_admin_password $ARGOCD_USERNAME

With the following extra arguments being:

  • $gitea_admin_username: username of a Gitea Admin user.
  • $gitea_admin_password: password of the Gitea Admin User
  • $ARGOCD_USERNAME: username of the ArgoCD user that I want to be created.

Create a Git repository

I’ve investigated several options. Initializing a folder and trying to add that as a remote origin repository leads to a “permission denied” exception. The repository needs to be created for the user first. Just like adding an SSH-key to the user, also a repository can be created using an API: Create a repository on behalf of a user. So, I created the following function:

# Add a Repository to Gitea user
function add_repository_to_user(){
local cluster_name=$1
local gitea_admin_username="$2"
local gitea_admin_password="$3"
local gitea_username="$4"
local repo_name=$5
local default_branch="$6"
local description="$7"
logdebug "Assemble payload for repository"
jq_json='{ auto_init: true,
default_branch: $ARGS.named["default_branch"],
description: $ARGS.named["description"],
name: $ARGS.named["name"],
private: true
}'
logdebug "JSON: $jq_json"
local payload=$(jq -n "$jq_json" \
--arg default_branch "$default_branch" \
--arg description "$description" \
--arg name "$repo_name" \
);

logdebug "Add repository $repo_name to $GIT_HOST_URL/api/v1/admin/users/$gitea_username/repos through proxy $PROXY_URL"
curl -X POST -k \
-H "Content-Type: application/json" \
--socks5-hostname $PROXY_URL \
-d "$payload" \
-u "$gitea_admin_username:$gitea_admin_password" \
https://$GIT_HOSTNAME.$cluster_name.$DNS_ZONE_NAME/api/v1/admin/users/$gitea_username/repos
}

This works exactly the same. However, it has additional arguments like:

  • repo_name: name of the repository
  • default_branch: name of the main/master branch, like “main”
  • description: Description to be set to the repository

You can call it it like:

        add_repository_to_user $CLUSTER_NAME \
"$gitea_admin_username" \
"$gitea_admin_password" \
"$ARGOCD_USERNAME" \
"$ARGOCD_REPOSITORY_NAME" \
"$INITIAL_BRANCH" \
"$ARGOCD_REPOSITORY_DESCR"

Assuming that you have a tunnel running, you would need to add a snippet to your ~/.ssh/config to have ssh use the tunnel for the $GIT_HOST_URL.

For that I created the function:

# Add Gitea host to SSH Config
function add_gitea_host_to_ssh_config(){
local cluster_name=$1
local gitea_user_name=$2
local ssh_key_name=$cluster_name$ARGOCD_GIT_PFX
if grep -q "Host $GIT_HOST_URL" $SSH_CONFIG; then
echo "Host $GIT_HOST_URL already added to $SSH_CONFIG"
else
echo "Add host $GIT_HOST_URL to $SSH_CONFIG using key $ssh_key_name";
cat <<EOT >> $SSH_CONFIG
# Gitea server on $
Host $GIT_HOST_URL $cluster_name
ServerAliveInterval 60
HostName $GIT_HOST_URL
IdentityFile $SSH_LOC/$ssh_key_name
User $gitea_user_name
# StrictHostKeyChecking no
ProxyCommand ncat --proxy $PROXY_URL --proxy-type socks5 --proxy-dns remote %h %p
EOT
fi
}

It can be called like:

add_gitea_host_to_ssh_config $CLUSTER_NAME $ARGOCD_USERNAME

This function checks if the $GIT_HOST_URL isn’t already added to the config. If not, it adds a snippet like:


# Gitea server on mycluster
Host gitea.mycluster.mydomain.internal
ServerAliveInterval 60
HostName gitea.mycluster.mydomain.internal
IdentityFile /home/oracle/.ssh/mycluster-argocd-git
User argocd
ProxyCommand ncat --proxy localhost:8080 --proxy-type socks5 --proxy-dns remote %h %p

It uses the ArgoCD Username to be identified as the SSH user in Gitea.

Clone the repository

Having the code above in place, it should be possible to clone the repository with the following function

# Add a Public Key as SSH key to Gitea user
function clone_repository(){
local cluster_name=$1
local gitea_username="$2"
local repo_name=$3
local argocd_clone_dir="$GIT_BASE_DIR/../$cluster_name/$repo_name"
if [ -d $argocd_clone_dir/.git ]; then
logwarn "Folder $argocd_clone_dir already contains a Git repository, so skip clone."
else
loginfo "Clone $gitea_username/$repo_name into $argocd_clone_dir"
mkdir -p $argocd_clone_dir
git clone git@$GIT_HOST_URL:$gitea_username/$repo_name.git $argocd_clone_dir
fi
}

The function uses the $GIT_BASE_DIR to create a folder next to the folder of this Git Repository’s folder. The folder is called after the Cluster and will contain a folder called like the repository name. Then it checks if the .git folder within this folder already exists, meaning it already is a Git repo folder. If not, then it makes the directory and does a git clone into this folder.

You invoke the function as:

clone_repository $CLUSTER_NAME \
"$ARGOCD_USERNAME" \
"$ARGOCD_REPOSITORY_NAME"

When using HTTPs

When using HTTPs, you would need to configure a HTTP/HTTPs proxy to your Git repository. The point here is that you would need to direct Git to do a DNS lookup over the proxy, not only direct the traffic through the proxy.

I found the solution for this in: Using a socks proxy with git for the http transport, and especially in this remark:

Using a — socks5-hostname proxy in Git

So, you would need to add the following to your git config:

$ git config --local http.proxy socks5h://localhost:8080
$ git config --local https.proxy socks5h://localhost:8080

Like in:

$ git config --local --list
core.repositoryformatversion=0
core.filemode=true
core.bare=false
core.logallrefupdates=true
http.proxy=socks5h://localhost:8080
https.proxy=socks5h://localhost:8080
remote.origin.url=https://gitea.mycluster.mydomain.internal/argocd/argocd-repo.git
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*

Conclusion

The functions that I described in this article can be used to create a main function that orchestrates the creation of a Gitea user with SSH key, add a repository and clone it. In the end after some struggling I was able use SSH to clone the repository. Since that was my preferred protocol, I went for that. But, with the http(s).proxy settings in the config of the Git repo (or global in Git), it would be just as easy.

The proxy server should be running of course.

In my setup I already had scripts to generate Helm-application-charts with value files to do an end2end setup MySQL-Cluster and Gitea and applying them to the OKE Cluster. With the functions above, it is possible to do a Gitea setup, from scratch, ending up with a pre-defined Git repository cloned into a local folder.

Next stop: extending it with an ArgoCD setup, that is using the same repository.

--

--

Nerd For Tech
Nerd For Tech

Published in Nerd For Tech

NFT is an Educational Media House. Our mission is to bring the invaluable knowledge and experiences of experts from all over the world to the novice. To know more about us, visit https://www.nerdfortech.org/.

Martien van den Akker
Martien van den Akker

Written by Martien van den Akker

Technology Architect at Oracle Netherlands. The views expressed on this blog are my own and do not necessarily reflect the views of Oracle

No responses yet