Featured image

tl;dr skip to Snap Installation

Introduction Link to heading

It was my plan from the start to set up a Gitea instance on this blog. I like Gitea a lot as an independent open-source, self-hosted Git instance. I also like Snaps, I know they get a lot of flack, mostly due to the proprietary backend (🤮), common misconfigurations (leading to no-access outside $HOME), and other problems. However, I’ve also had a lot of success with Snaps on Ubuntu and I think it’s a noble attempt to improve security and resolve dynamic-linking problems in software.

Anyway, when I checked Gitea’s list of official installation methods they listed Snap as an option: Screenshot of the Canonical Snap installation procedure.

So, I thought, brilliant I’ll use that, easy. However, you might notice, the lack of accompanying documentation…
I’m not cutting anything out of the picture here, that’s literally all I’ve been given snap install gitea.
No indication of connections available, configurations used, database method, or even what the snap actually installs, does it host a web-server? What port is it on? There is no info that I can find, which is the main reason I’ve written this article.

To make matters worse, Snap is missing some key features on their website. This web-page is beautifully designed and painfully useless. This is partly the fault of Gitea who included no documentation about this snap, but there’s also a big chunk missing from Canonical here. At the bare minimum, I expect the Snapcraft website to tell me:

  • The entire snapcraft.yaml contents.
  • Commands and apps.
  • Connections, plugs and sockets.
  • Supported architectures.
  • Services.

This info is already known by Snap because it has to be specified in the snapcraft.yaml, you can even query it through their API! Other than that, so far as I can tell it’s impossible to find out without actually installing the snap first.

Figuring it out. Link to heading

So anyway, we’re on our own, OK whatever we can figure this out ourselves!

The snapcraft.yaml Link to heading

First, let’s take a look at the snapcraft.yaml, for those not aware, when you build a snap you create a file called snapcraft.yaml that defines everything inside, what it does, where it goes what permissions it needs etc. Luckily, I already know that snaps are stored in /snap and the YAML will be located under /snap/<app name>/current/snap/snapcraft.yaml

cat /snap/gitea/current/snap/snapcraft.yaml 
name: gitea
summary: Gitea - A painless self-hosted Git service
description: |
  The goal of this project is to make the easiest, fastest, and most painless
  way of setting up a self-hosted Git service. With Go, this can be done with
  an independent binary distribution across ALL platforms that Go supports,
  including Linux, Mac OS X, Windows and ARM.  

icon: public/img/logo.png
confinement: strict
base: core18
adopt-info: gitea

architectures:
  - build-on: armhf
  - build-on: amd64
  - build-on: arm64

environment:
  GITEA_CUSTOM: "$SNAP_COMMON"
  GITEA_WORK_DIR: "$SNAP_COMMON"
  GIT_TEMPLATE_DIR: "$SNAP/usr/share/git-core/templates"
  GIT_EXEC_PATH: "$SNAP/usr/lib/git-core"

apps:
  gitea:
    command: gitea
    plugs: [network, network-bind, removable-media]
  web:
    command: gitea web
    daemon: simple
    plugs: [network, network-bind, removable-media]
  dump:
    command: gitea dump
    plugs: [home, removable-media]
  version:
    command: gitea --version
  sqlite:
    command: usr/bin/sqlite3

parts:
  gitea:
    plugin: make
    source: .
    stage-packages: [ git, sqlite3, openssh-client ]
    build-packages: [ git, libpam0g-dev, libsqlite3-dev, build-essential]
    build-snaps: [ go, node/18/stable ]
    build-environment:
      - LDFLAGS: ""
    override-pull: |
      snapcraftctl pull
      
      last_committed_tag="$(git for-each-ref --sort=taggerdate --format '%(tag)' refs/tags | tail -n 1)"
      last_released_tag="$(snap info gitea | awk '$1 == "latest/candidate:" { print $2 }')"
      # If the latest tag from the upstream project has not been released to
      # stable, build that tag instead of master.
      if [ "${last_committed_tag}" != "${last_released_tag}" ]; then
        git fetch
        git checkout "${last_committed_tag}"
      fi      

OK, lots of useful info in here, for example we can see this snap provides the apps, gitea, web, dump, version and sqlite. This means once the snap is installed we can run these as commands using:

snap run gitea.<app name>`, helpfully some of these commands have help files:
```plaintext
snap run gitea.web --help
NAME:
   gitea web - Start Gitea web server
...

snap run gitea.gitea --help
NAME:
   Gitea - A painless self-hosted Git service
...

snap run gitea.dump --help
NAME:
   gitea dump - Dump Gitea files and database
...

snap run gitea.sqlite --help
Usage: /snap/gitea/6473/usr/bin/sqlite3 [OPTIONS] FILENAME [SQL]
...

Useful, now we have some info as to what’s going on. Some of these commands can also be run without the preceding snap run

gitea <app name>
gitea web
gitea dump

Some of the commands also do not have a help page, such as version, but we can see from the YAML that it just runs gitea version which prints the version of our Gitea instance… although it doesn’t seem to work, possibly they are still working on this one, or it might be used internally by Gitea.

The Web Server Link to heading

So, we know there is a web-server bundled. We also know that it’s running on port 3000, because when I run gitea it complains that port 3000 is in-use. But how? Where is the service that defines this web-server? Well, turns out it’s systemd and I found it by running sudo systemctl status *gitea*. This returned info on three services:

  • snap.gitea.web.service
  • run-snapd-ns-gitea.mnt.mount
  • snap-gitea-6473.mount

Of which only the first one is actually a service, the others are magical systemd components we don’t need to write down. Let’s take a look at what’s inside the service file:

cat /etc/systemd/system/snap.gitea.web.service
[Unit]
# Auto-generated, DO NOT EDIT
Description=Service for snap application gitea.web
Requires=snap-gitea-6473.mount
Wants=network.target
After=snap-gitea-6473.mount network.target snapd.apparmor.service
X-Snappy=yes

[Service]
EnvironmentFile=-/etc/environment
ExecStart=/usr/bin/snap run gitea.web
SyslogIdentifier=gitea.web
Restart=on-failure
WorkingDirectory=/var/snap/gitea/6473
TimeoutStopSec=30
Type=simple

[Install]
WantedBy=multi-user.target

Yep, that’s definitely our snap, if I knew more about how the snapcraft.yaml worked then I could’ve worked this out from the daemon: simple indicator under the web app definition.

This Systemd service hasn’t actually told us much information, but you know what? I don’t care. We actually have everything we need now:

  • We know what commands are available.
  • We know that the snap is running a web-server on port 3000.
  • We know it’s designed to use SQLite as the database.

These key pieces of information are everything we need to know that we need to set up a reverse-proxy with Nginx and to use SQLite as the right database engine when configuring Gitea.

Now, onto the actual setup procedure. 🚀

Snap Installation Link to heading

  1. Just go right ahead and install Snappy if you don’t have it:
    sudo apt update
    sudo apt install snapd
    
  2. Install Gitea:
    sudo snap install gitea
    
    Once Gitea finishes installing it will host a web-server on port 3000 that is your Gitea instance. No need to worry about how this works, snap will take care of everything from maintenance to updates (probably).

Nginx setup Link to heading

This guide assumes you already have an NGINX web server setup and running, including the server blocks configuration; if not, see this great guide from Digital Ocean.

HTTPS (Encrypted) Link to heading

If you’ve also setup Nginx for SSL encryption over HTTPS then follow this section. If you want to use HTTPS but haven’t set it up yet, then Digital Ocean has another great guide to follow. Otherwise, skip to the HTTP guide

  1. Create your new server-block for Gitea:

    sudo nano /etc/nginx/sites-available/gitea.conf
    
  2. Paste in the following:

    server {
        listen 80;
        listen [::]:80;
    
        server_name <site name>;
    
        ### SSL CONFIGURATION ###
        location /.well-known/ {
            default_type "text/plain";
            allow all;
            root /var/www/html/;
        }
    
        location / {
            return 301 https://<site name>$request_uri;
        }
    
        access_log /var/log/nginx/gitea-access.log;
        error_log /var/log/nginx/gitea-error.log;
    
    }
    
    server {
    
        listen 443 ssl http2;
        listen [::]:443 ssl http2;
    
        include /etc/nginx/snippets/ssl-<Site Name>.conf;
        include /etc/nginx/snippets/ssl-params.conf;
    
        server_name <site name>;
    
        access_log /var/log/nginx/gitea-access.log;
        error_log /var/log/nginx/gitea-error.log;
    
        ### SSL CONFIGURATION ###
        location /.well-known/ {
            default_type "text/plain";
            allow all;
            root /var/www/html/;
        }
    
        location = /robots.txt {
            add_header Content-Type text/plain;
            return 200 "User-agent: *\nDisallow: /\n";
        }
    
        location / {
            proxy_pass http://localhost:3000;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            client_max_body_size 16M;
        }
    
    }
    

    Some parts of this configuration are not necessary, if you’re already familiar with using NGINX configs and have you own standard format then feel free to just include the reverse-proxy section and ignore everything else.

    • The first server block is just for redirecting HTTP to HTTPS.
      • The ### SSL CONFIGURATION ### part allows access for Let’s Encrypt certbot challenges to work even while SSL is disabled.
    • The second server block is what serves the actual site content.
      • The two include parameters import SSL configurations from the snippets folder, see below for detail.
      • The same ### SSL CONFIGURATION ### as before helps with certbot challenges.
      • The robots.txt section tells NGINX to serve a “disallow all” robots file at this URL. This is useful for telling search engines to not index this webpage. If you want your Gitea instance to appear in search results then you can remove this section.
      • The final location block is the reverse-proxy configuration, taken from the official Gitea documentation.
      • This configuration stores all access and error logs inside /var/log/nginx/, separating logs between sites helps with troubleshooting and analytics.
  3. Go through the config and replace any instances of <site name> with your website address, e.g. mine is gitea.hugolee.xyz

  4. Create the two snippet files if you don’t have them already. In Nginx “snippets” are portions of config files that you can create once and then include into multiple different configs. This makes it easy to standardise settings across lots of sites at once. Here, I’ve used snippets to configure SSL parameters. There are two separate files in use:

    • /etc/nginx/snippets/ssl-<Site Name>.conf. This snippet contains only two lines:

      ssl_certificate /etc/letsencrypt/live/<Site Name>/fullchain.pem;
      ssl_certificate_key /etc/letsencrypt/live/<Site Name>/privkey.pem;
      

      These two directives tell NGINX where my SSL Certificate and Key is located, for me these files were created automatically by certbot when I set up SSL. Your configuration may differ but, I recommend keeping to the same format as it’s very useful.

      nano /etc/nginx/snippets/ssl-<Site Name>.conf
      

      Paste in the contents from above, change the file path to the location of your certificate and key. If you used cert-bot you can double-check the name of the directory:

      sudo ls -al /etc/letsencrypt/live
      
    • include /etc/nginx/snippets/ssl-params.conf. This snippet contains a bunch of configurations for secure SSL setup:

      ssl_protocols TLSv1.3 TLSv1.2;# Requires nginx >= 1.13.0 else use TLSv1.2
      ssl_prefer_server_ciphers on; 
      ssl_dhparam /etc/ssl/certs/dhparam.pem; # openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096
      ssl_ciphers EECDH+AESGCM:EDH+AESGCM;
      ssl_ecdh_curve secp384r1; # Requires nginx >= 1.1.0
      ssl_session_timeout  10m;
      ssl_session_cache shared:SSL:10m;
      ssl_session_tickets off; # Requires nginx >= 1.5.9
      ssl_stapling on; # Requires nginx >= 1.3.7
      ssl_stapling_verify on; # Requires nginx => 1.3.7
      resolver 51.158.108.203 195.10.195.195 valid=300s;
      resolver_timeout 5s; 
      add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
      add_header X-Frame-Options SAMEORIGIN;
      add_header X-Content-Type-Options nosniff;
      add_header X-XSS-Protection "1; mode=block";
      

      You can probably use this file as-is, just make note of the dhparam command below:

      nano /etc/nginx/snippets/ssl-params.conf
      

      Paste in the contents from above, These settings change how NGINX behaves in a number of ways, it’s based on this useful site:

      • ssl_protocols is telling Nginx to use only versions 1.3 and 1.2.
      • ssl_prefer_server_ciphers, tells Nginx to prefer server ciphers over client ciphers. Enabling this allows us to ensure strong encryption is being used wherever possible.
      • ssl_dhparam, specifies which files of DH Parameters to use for DH key-exchanges. More info here.
        You might not have this file already, in which case you can easily make one by running this command:
        sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096
        
        Beware it can take a long time to complete.
      • ssl_ciphers, specifies which encryption ciphers NGINX will use. This is always a balance in security and compatibility, some old browsers won’t support newer and stronger ciphers.
      • ssl_ecdh_curve, a mechanism of ECDH.
      • ssl_session_timeout, after this timeout is complete, the client must renegotiate encryption with the server.
      • ssl_session_cache, tells nginx the type of session cache to use and how long to keep it.
      • ssl_session_tickets, disables TLS session tickets, which are probably insecure.
      • ssl_stapling, part of ensuring SSL certificates are valid.
      • ssl_stapling_verify, tells Nginx to verify that valid SSL certificates are definitely valid.
      • resolver, tells NGINX what DNS servers to use to resolve domain names, here it’s configured to use OpenNIC’s DNS servers. You could also use Cloudflare’s 1.1.1.1 or Google’s 8.8.8.8 if you prefer.
      • add_header, adds additional headers to each reply:
  5. Link your new site config into the sites-enabled directory:

    sudo ln -s /etc/nginx/sites-available/gitea.conf /etc/nginx/sites-enabled/
    
  6. Test that the configuration is correct:

    sudo nginx -t
    

    If there are any issues NGINX will tell you about them here.
    If it complains that I can’t find dhparam.pem then you just need to run:

    sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096
    
  7. Assuming there are no issues, go ahead and reload NGINX, then the site will be live:

    sudo systemctl reload nginx
    

HTTP (Unencrypted) Link to heading

If you’re just using HTTP with no encryption then use this guide.

  1. Create your new server-block for Gitea:

    sudo nano /etc/nginx/sites-available/gitea.conf
    
  2. Paste in the following:

    server {
    
        listen 80;
        listen [::]:80;
    
        server_name <site name>;
    
        access_log /var/log/nginx/gitea-access.log;
        error_log /var/log/nginx/gitea-error.log;
    
        location = /robots.txt {
            add_header Content-Type text/plain;
            return 200 "User-agent: *\nDisallow: /\n";
        }
    
        location / {
            proxy_pass http://localhost:3000;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            client_max_body_size 16M;
        }
    
    }
    
    • The robots.txt section tells NGINX to serve a “disallow all” robots file at this URL. This is useful for telling search engines to not index this webpage. If you want your Gitea instance to appear in search results then you can remove this section.
    • The final location block is the reverse-proxy configuration, taken from the official Gitea documentation.
    • This configuration stores all access and error logs inside /var/log/nginx/, separating logs between sites helps with troubleshooting and analytics.
  3. Link your new site config into the sites-enabled directory:

    sudo ln -s /etc/nginx/sites-available/gitea.conf /etc/nginx/sites-enabled
    
  4. Test that the configuration is correct:

    sudo nginx -t
    

    If there are any issues NGINX will tell you about them here.

  5. Assuming there are no issues, go ahead and reload NGINX, then the site will be live:

    sudo systemctl reload nginx
    

Gitea Initial Configuration Link to heading

Now that your site is live you just need to complete the initial configuration, and you’re good to go!

  1. Navigate to your Gitea site, e.g. mine is https://gitea.hugolee.xyz

  2. You will be presented with the initial configuration page, in the first section you need to configure the Database Type, set this to SQLite3. It will automatically configure the correct Path for you: Alt

  3. In the next section you can configure the General Settings, feel free to change the Site Title, I do not recommend modifying any other settings: Alt text

  4. In the Optional Settings section you can configure Email, server and third-party settings, and create an Administrator account.

    • If you have email configured already then enter your SMTP settings here, DigitalOcean has a good guide on how you can configure Postfix for email.
    • I recommend enabling Local Mode which disables reliance on third-party features like Gravatar which can be used to track your usage.
    • I would also recommend disabling Self-Registration, OpenID Sign-In and OpenID Self-Registration to prevent people from signing up to your site and creating repositories themselves.
    • You should create and Administrator account now to save having to do it later.

    Picture showing the finals settings configuration, including Email, Server and Third-Party Service Settings, and Administrator Account Settings. Server and Third-Party Service Settings is expanded, only Enable Local Mode, Disable Gravatar, Allow Creation of Organizations by Default and Enable Time Tracking by Default are ticked.

  5. Finally, click Install Gitea and wait for it to complete!

SSH setup Link to heading

By default, Gitea will try to use the built-in SSH server to authenticate users as they try to push, pull or execute other git commands that need authorisation. But, this isn’t going to work…

Instead, you’ll need to enable Gitea’s built-in SSH server and have it use a port other than 22.

  1. Open the Gitea app.ini file for editing:

    sudo nano /var/snap/gitea/common/conf/app.ini
    
  2. Locate the line SSH_PORT = 22 within the [SERVER] heading, and change it to SSH_PORT = 2002, other another port you like; just be sure it’s not in use already.

  3. Underneath SSH_PORT add this line:

    START_SSH_SERVER = true
    
  4. Now, restart the Gitea server:

    sudo systemctl restart snap.gitea.web.service
    
  5. If you’ve enabled a firewall you’ll also need to add an exception for this new port:

    sudo ufw allow 2002
    

That’s it, Gitea will now automatically add this port to the URL of remote repositories. If you’ve already added a git remote to your project, you’ll need to remove it and re-add it, for example:

git remote remove origin
git remote add origin root@<site name>:<port number>/<username>/<repo name>

You’re done! Congratulations on taking a step towards independence from centralised services with your very-own self-hosted git site 🎉 🎊

Useful information Link to heading

A lot of people aren’t familiar yet with where Snap stores its files, so here you can find a list of useful locations and configuration files for Gitea.

Config files and storage paths Link to heading

First off, if you navigate to https://<site>/admin/config you’ll get a list of Gitea’s configuration, including the below file locations.

  • Gitea App Configuration file:
    /var/snap/gitea/common/conf/app.ini
    
  • App Data Path:
    /var/snap/gitea/common/data
    
  • Repository Root Path:
    /var/snap/gitea/common/data/gitea-repositories
    
  • Custom File Root Path:
    /var/snap/gitea/common
    
  • Log Path:
    /var/snap/gitea/common/log
    
  • LFS Content Path:
    /var/snap/gitea/common/data/lfs
    
  • SQLite Database location:
    /var/snap/gitea/common/data/gitea.db
    

Creating Gitea backups. Link to heading

With the Gitea snap you have two options either:

  • Create a Snap Snapshot, which will include all Gitea files, databases, directories etc.

  • Manually backup important Gitea files.

Snapshots Link to heading

The Snap architecture includes a built-in method of saving Snaps and their data. This guidance was built upon this blog from Igor Ljubuncic at Canonical.

  1. Run the snap save command:
sudo snap save gitea

That’s it. You can view your saves by running:

sudo snap saved

Delete them by running:

sudo snap forget <Set number> gitea

To restore a backup run:

sudo snap restore <Set number> gitea

Manual Backup Link to heading

This method is the more traditional approach, and will be more useful if you need to restore data to a different Gitea instance at a later date, as well as potentially moving to a non-snap installation in future. It’s based on this official guide.