May 16, 2021 by Daniel F Dickinson10 minutes
This article describes setting up a Raspberry Pi Model B+ as a private Gitea (lightweight Git hosting) server.
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.
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).
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
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 execute update-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
to
C:\\ProgramData\\FirefoxCertificates
Create a directory called distribution
in
C:\\Program Files\\Mozilla Firefox
, and in the distribution
directory add a file called policies.json
containing:
{
"policies": {
"Certificates": {
"Install": [
"C:\\ProgramData\\FirefoxCertificates\\ca-private.example.com.crt"
]
}
}
}
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.
sudo addgroup --system gitea
sudo adduser --system --home /srv/gitea/home --shell /bin/bash --gecos "" --ingroup gitea --disabled-password --disabled-login gitea
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
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
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
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
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.
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
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
Replace the [database]
section above with:
[database]
DB_TYPE = sqlite3
NAME = gitea
LOG_SQL = false
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
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
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 "$@"
sudo -u gitea gitea-wrapper migrate
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).
sudo ufw allow in on eth0 proto tcp from any to any port 22172
We’re going to use port 22 for Git SSH, so make admin SSH a separate port.
sudoedit /etc/ssh/sshd_config
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
ssh -p 22172 pi@gitea.example.com
sudo ufw allow in on eth0 from any to any app "WWW Full"
sudo systemctl enable --now gitea
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.
You should now have a working Gitea Server
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 ''
restic-gitea@piserver.pub
to your destination’s
authorized_keys
file.(assuming you have configured, ~/.ssh/config
so that
restic-gitea@backupserver.example.com
uses the restic-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