Gitea Pi
Preface
In combination with the Raspberry Pi OS for a server and Using XCA to create private SSL certificates and PiSQL articles, this article describes setting up a Raspberry Pi Model B+ as a private Gitea (lightweight Git hosting) server. It assumes a separate PostgreSQL server is available on the network and does not use external storage (just a reasonably large SD card). Finally, this is intended only for internal use and is not expected to be publicly facing.
A caveat
This will work well for ordinary repositories, but for very large repositories like the Linux Kernel (>1 GB) the Pi is simply not powerful enough.
This is a limitation of Git rather than Gitea (even bare git operations on kernel repos do not fare well on a Pi).
Preliminaries
Prepare the server and certificates
- Setup a Pi following the Raspberry Pi OS for a server article. For this server the author skipped the external storage related steps and packages as he is operating the server entirely from a large and fast enough SD card.
- Create internal SSL CA and server certificate and key by following the Using XCA to create private SSL certificates article. (As with the PiSQL article, we require server certificates for the server (e.g. gitea.example.com); this time we do not need to generate a CA (Certificate Authority) but can use the existing one).
- We also need to generate a ‘client’ certificate for this host, which uses the same procedure, except that instead of selecting the ‘[default] TLS_server’ template, we select the ‘[default] TLS_client’ template. For the client certificate I recommend using a name such gitea@gitea.example.com for the CN — you can still add the server DNS name as Subject Alternative Names (x509v3 SAN). Note that on the client certificate at one SAN and/or the CN must be the username gitea uses to connect to the database.
- Increase the available amount of swap:
sudo dd if=/dev/zero of=/var/swap2.img bs=1M count=1024
sudo chmod 600 /var/swap2.img
sudo mkswap /var/swap2.img
sudoedit /etc/fstab
and add a line such as:/var/swap2.img none swap defaults 0 0
Enable the swap:
sudo swapon -a
Verify it is enabled:
cat /proc/swaps
Prepare your clients to use SSL to the server
Because we are using a private CA your desktop and other Git clients need to be told to trust the private CA.
On any Debian/Ubuntu workstation that needs to access the private CA, copy the private CA certificate (e.g.
ca-private.example.com
) to/usr/local/share/ca-certificates
and executeupdate-ca-certificates
Also on any Debian/Ubuntu workstation for which Firefox needs to access the server:
mkdir -p /etc/firefox/policies sudoedit /etc/firefox/policies/policies.json
In
policies.json
add:{ "policies": { "Certificates": { "Install": [ "/usr/local/share/ca-certificates/ca-private.example.com.crt" ] } } }
On any Windows workstation that needs to access the private CA,
Install the private CA into the system certificate store
For making the CA available for recent Firefox system-wide:
Create a directory called
C:\\ProgramData\\FirefoxCertificates
Copy
ca-private.example.com.crt
toC:\\ProgramData\\FirefoxCertificates
Create a directory called
distribution
inC:\\Program Files\\Mozilla Firefox
, and in thedistribution
directory add a file calledpolicies.json
containing:{ "policies": { "Certificates": { "Install": [ "C:\\ProgramData\\FirefoxCertificates\\ca-private.example.com.crt" ] } } }
Obtain, verify and ‘install’ Gitea
From the Gitea download area, select the current release and linux-arm-6 binaries. At the present time we want the following files: https://dl.gitea.io/gitea/1.14.3/gitea-1.14.3-linux-arm-6.xz, https://dl.gitea.io/gitea/1.14.3/gitea-1.14.3-linux-arm-6.xz.asc, and https://dl.gitea.io/gitea/1.14.3/gitea-1.14.3-linux-arm-6.xz.sha256.
You could for example use the following trio of commands:
wget https://dl.gitea.io/gitea/1.14.3/gitea-1.14.3-linux-arm-6.xz wget https://dl.gitea.io/gitea/1.14.3/gitea-1.14.3-linux-arm-6.xz.asc wget https://dl.gitea.io/gitea/1.14.3/gitea-1.14.3-linux-arm-6.xz.sha256
Now verify the correctness of the download:
Execute
sha256sum --ignore-missing -c gitea-1.14.3-linux-arm-6.xz.sha256
And that it it matches the version intended by the authors (signed by them)
gpg --keyserver keyserver.ubuntu.com --recv-keys CC64B1DB67ABBEECAB24B6455FC346329753F4B0 gpg --verify gitea-1.14.3-linux-arm-6.xz.asc gitea-1.14.3-linux-arm-6.xz
As long as the report is of a good signature, you can ignore:
gpg: WARNING: This key is not certified with a trusted signature! gpg: There is no indication that the signature belongs to the owner.
as the so-called Web of Trust which would resolve that message has not come to exist.
Now decompress the gitea binary:
xz -d gitea-1.14.3-linux-arm-6.xz
And copy it to where you will use it and make it executable
sudo cp gitea-1.14.3-linux-arm-6 /usr/local/bin/gitea sudo chmod 755 /usr/local/bin/gitea
You can now remove the
gitea-1.1.4.3-linux-arm-6*
files.
Prepare the environment
Create a Gitea user
sudo addgroup --system gitea
sudo adduser --system --home /srv/gitea/home --shell /bin/bash --gecos "" --ingroup gitea --disabled-password --disabled-login gitea
Add the PostgreSQL client (optional)
We use this to create and prepare the database in a way that verifies that connections will work for the gitea process (i.e. when going live).
sudo apt install -y postgresql-client
Copy the SSL client keys to the Gitea user for PostgreSQL use
Assuming you have copied your client gitea@gitea.example.com.crt and gitea@gitea.example.com.pem to your admin user, as well as the ca certificate (which we will call ‘root.crt’), and that you are currently in your admin user account:
sudo cp gitea@gitea.example.com* root.crt /srv/gitea/
sudo chown gitea:gitea /srv/gitea/gitea@gitea.example.com* /srv/gitea/root.crt
- You can now remove the client cert and key from your admin user account:
rm gitea@gitea.example.com*
sudo -u gitea -sH
cd ~
chmod 700 .
mkdir .postgresql
chmod 700 .postgresql
mv gitea@gitea.example.com* root.crt .postgresql
cd .postgresql
mv gitea@gitea.example.com.crt postgresql.crt
mv gitea@gitea.example.com.pem postgresql.key
cp root.crt /usr/local/share/ca-certificates/ca-private.gitea.internal.com
update-ca-certificates
cd ..
NB Keep this session open, we’ll come back to it
On the Database server, create user and database for Gitea
Login as your admin user to your database server
sudo su - postgres
createuser -P gitea@gitea.example.com
Enter a new strong password when prompted (you will need to enter it twice).
For C.UTF-8 below substitute the appropriate UTF-8 locale:
createdb -O gitea@gitea.example.com --encoding UTF8 --locale C.UTF-8 --template template0 giteadb
exit
exit
See also: Gitea documentation on setting up PostgreSQL for Gitea
Back on the Gitea server, test DB connection
In the session as user gitea
from above and assuming your db server is named
pisql.example.com
:
psql "postgres://gitea%40gitea.example.com@pisql.example.com/giteadb?sslmode=verify-full"
(%40 is the URL encoding of the ‘@’ symbol in the username)
Enter the database user’s password when prompted, and you should get a standard ‘psql’ prompt, such as:
psql (11.12 (Raspbian 11.12-0+deb10u1), server 11.11 (Raspbian 11.11-0+deb10u1))
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.
giteadb=>
Enter \l
to see the list of databases, and \q
to quit.
Back to admin, create the needed directories
exit
the gitea user shell so that you are back into your admin user shell.
sudo mkdir -p /srv/gitea/data/{custom,data,log,lfs,repositories}
sudo mkdir -p /etc/gitea
sudo chown -R gitea:gitea /srv/gitea/data /etc/gitea
sudo chmod 750 /srv/gitea/data /etc/gitea
sudo chown root /etc/gitea
sudo touch /etc/gitea/app.ini
sudo chmod 640 /etc/gitea/app.ini
sudo chown root:gitea /etc/gitea/app.ini
Configure Gitea
Create the config file
Create and record the secrets you will need:
gitea generate secret SECRET_KEY
gitea generate secret INTERNAL_TOKEN
gitea generate secret LFS_JWT_SECRET
sudoedit /srv/gitea-config/app.ini
Copy your server certificate and key to
/etc/gitea
:Assuming they are in your admin user’s home directory:
sudo cp gitea.example.com.crt /etc/gitea/ sudo cp gitea.example.com.key /etc/gitea/ sudo -sH cd /etc/gitea chown root:gitea gitea.example.com.crt chown root:gitea gitea.example.com.key chmod 640 gitea.example.com.key chmod 644 gitea.example.com.crt
Add a config such as (substituting the secrets above, and the database password):
APP_NAME = Example Gitea RUN_USER = gitea RUN_MODE = prod [security] INSTALL_LOCK = true PASSWORD_HASH_ALGO = pbkdf2 SECRET_KEY = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX INTERNAL_TOKEN = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX [database] DB_TYPE = postgres HOST = pisql.example.com:5432 NAME = giteadb USER = gitea@gitea.example.com PASSWD = XXXXXXXXXXXXXXXXXXXXXXX ; USE `XXXXXXXXXXXXXXX` if special characters appear in PASSWD SSL_MODE = verify-full CHARSET = utf8 LOG_SQL = false [repository] ROOT = /srv/gitea-data/repositories DEFAULT_PUSH_CREATE_PRIVATE = false ENABLE_PUSH_CREATE_ORG = false ENABLE_PUSH_CREATE_USER = false DEFAULT_BRANCH = main [server] SSH_DOMAIN = gitea.example.com DOMAIN = gitea.example.com REDIRECT_OTHER_PORT = true PORT_TO_REDIRECT = 80 HTTP_PORT = 443 PROTOCOL = https ROOT_URL = https://gitea.example.com/ DISABLE_SSH = false SSH_PORT = 22 LFS_START_SERVER = true LFS_CONTENT_PATH = /srv/gitea-data/lfs LFS_JWT_SECRET = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX OFFLINE_MODE = true START_SSH_SERVER = true CERT_FILE = /srv/gitea-config/gitea.example.com.crt KEY_FILE = /srv/gitea-config/gitea.example.com.key [mailer] ENABLED = false [service] REGISTER_EMAIL_CONFIRM = false ENABLE_NOTIFY_MAIL = false DISABLE_REGISTRATION = true ALLOW_ONLY_EXTERNAL_REGISTRATION = false ENABLE_CAPTCHA = false REQUIRE_SIGNIN_VIEW = false DEFAULT_KEEP_EMAIL_PRIVATE = false DEFAULT_ALLOW_CREATE_ORGANIZATION = false DEFAULT_ENABLE_TIMETRACKING = false NO_REPLY_ADDRESS = noreply.example.com [oauth2] JWT_SECRET = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX [session] PROVIDER = db [indexer] ISSUE_INDEXER_TYPE = db [log] MODE = console LEVEL = info ROOT_PATH = /srv/gitea-data/log COLORIZE = false # Router is quite noisy and not usually needed on a private server # Log to a file instead of journal (via console) ROUTER = file DISABLE_GRAVATAR = true ENABLE_FEDERATED_AVATAR = false [openid] ENABLE_OPENID_SIGNIN = false ENABLE_OPENID_SIGNUP = false [other] SHOW_FOOTER_VERSION = true SHOW_FOOTER_TEMPLATE_LOAD_TIME = true
If you omit PostgreSQL and use SQLite3
Replace the [database]
section above with:
[database]
DB_TYPE = sqlite3
NAME = gitea
LOG_SQL = false
Add Fail2Ban configs (optional)
Create
/etc/fail2ban/filter.d/gitea.conf
# gitea.conf [Definition] failregex = .*(Failed authentication attempt|invalid credentials|Attempted access of unknown user).* from <HOST> ignoreregex =
Create
/etc/fail2ban/jail.d/gitea.conf
[gitea] enabled = true filter = gitea logpath = /var/log/syslog maxretry = 10 findtime = 3600 bantime = 900 action = iptables-allports
Restart fail2ban
sudo systemctl restart fail2ban
Verify fail2ban accepted your changes
sudo systemctl status -l fail2ban
Add systemd service file
Modified from Gitea example systemd service file
Copy the following file to /etc/systemd/system/gitea.service
[Unit]
Description=Gitea (Git with a cup of tea)
After=syslog.target
After=network.target
###
#
#Requires=memcached.service
#Requires=redis.service
#
[Service]
# Modify these two values and uncomment them if you have
# repos with lots of files and get an HTTP error 500 because
# of that
###
#LimitMEMLOCK=infinity
#LimitNOFILE=65535
RestartSec=2s
Type=simple
User=gitea
Group=gitea
WorkingDirectory=/srv/gitea/data/
# If using Unix socket: tells systemd to create the /run/gitea folder, which will contain the gitea.sock file
# (manually creating /run/gitea doesn't work, because it would not persist across reboots)
#RuntimeDirectory=gitea
ExecStart=/usr/local/bin/gitea web --config /etc/gitea/app.ini
Restart=always
Environment=USER=gitea HOME=/srv/gitea/home GITEA_WORK_DIR=/srv/gitea/data
###
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
###
[Install]
WantedBy=multi-user.target
Create a wrapper script for CLI usage
Copy the following file to
/usr/local/bin/gitea-wrapper
sudo chmod 755 /usr/local/bin/gitea-wrapper
#!/bin/bash export USER=gitea export HOME=/srv/gitea/home export GITEA_WORK_DIR=/srv/gitea/data if [ "$(id -un)" != "gitea" ]; then echo "Must be run as user 'gitea'" exit 1 fi exec /usr/local/bin/gitea --config /etc/gitea/app.ini "$@"
Initialize the database
sudo -u gitea gitea-wrapper migrate
Create an admin user
sudo -u gitea gitea-wrapper admin user create --username giteadmin --email you@example.com --admin --must-change-password --random-password --random-password-length 20
A random password will be generated for first login (at which time the user must change their password).
Configure firewall to allow alternate SSH port
sudo ufw allow in on eth0 proto tcp from any to any port 22172
Configure sshd to use alternate SSH port
We’re going to use port 22 for Git SSH, so make admin SSH a separate port.
sudoedit /etc/ssh/sshd_config
- Add the line
Port 22172
and save and exit. sudo systemctl restart ssh
— From now on, to administer the host we will need to tell SSH to use port 22172 (e.g.ssh -p 22172 pi@gitea.example.com
).exit
- Log back in as admin using the new port
ssh -p 22172 pi@gitea.example.com
Configure firewall to allow HTTP(S) connections
sudo ufw allow in on eth0 from any to any app "WWW Full"
Enable and launch Gitea
sudo systemctl enable --now gitea
Verify Gitea status
sudo systemctl status -l gitea
ss -ltn
It could be some minutes before Gitea is ready to accept connections, at which point ports 80, 443, and 22 will be accepting connections.
When Gitea is ready, login as admin user.
Gitea operational
You should now have a working Gitea Server
Configure backups
Assuming the use of restic as in the Pi OS for a server guide you could
sudo -u gitea -sH
cd ~
mkdir restic-files
cd restic-files
chmod 700 .
touch password-file
chmod 600 password-file
sensible-editor password-file
In the editor, add a strong password (e.g. 30 alphanumeric and special characters), then save and close (having a file with the password not ideal, but avoiding it is rather complicated, and out of scope for this article).
If using SFTP for backups, create a passwordless SSH keypair using:
ssh-keygen -t rsa -f restic-gitea@piserver -C restic-gitea@piserver -N ''
- Copy the contents of
restic-gitea@piserver.pub
to your destination’sauthorized_keys
file.
(assuming you have configured,
~/.ssh/config
so thatrestic-gitea@backupserver.example.com
uses therestic-gitea@piserver
created above:/usr/local/bin/gitea-wrapper dump --custom-path /srv/gitea/data/custom --work-path /srv/gitea/data --type tar.gz -f - 2>/dev/null | restic -r sftp:restic-gitea@backupserver.example.com:/path/to/repo --password-file ~/restic-files/password-file backup --stdin --stdin-filename /gitea-dump.tar.gz
Now create a crontab entry to do this every four hours (you could of course adjust the frequency for your needs):
crontab -e
Add an entry such as:
23 */4 * * * /usr/local/bin/gitea-wrapper dump --custom-path /srv/gitea/data/custom --work-path /srv/gitea/data --type tar.gz -f - 2>/dev/null | restic -r sftp:restic-gitea@backupserver.example.com:/path/to/repo --quiet --password-file ~/restic-files/password-file backup --stdin --stdin-filename /gitea-dump.tar.gz 2>&1 | logger -t restic
Save and exit the editor
exit