Choosing a Source Control System and Host
Over the last 10 years I've used a handful of source control systems, however the world seems to mostly have converged on git as the source control system of choice for open source projects. It's also the source control system that I'm most familiar with, so it's the system I'll focus on.
With that decided, let's focus on the options for self hosting a git repository. Gitlab CE is what I run on the local home-lab, however it's a little heavy for what I want to run outside the home. So, that leaves Gogs, Gitea, Phabicator, cgit, or gitbucket. There are other alternatives out there, but these are the ones I evaluated.
- Gogs - Is a GitHub clone that is written in Go and seems to be focused on being lightweight and functional.
- Gitea - Is a fork of Gogs, and seems to recieve more regular updates and features over Gogs.
- Phabricator - Is a suite of tools, one of which is a git repostory management and public exposure. I'm not sure I want to use all the baked in tools of the suite, it does look like a nice set of tools.
- cgit - Is a pretty basic git web frontend, though I don't think it exposes some of the functionality I'm looking for. Namely collaboration features for the projects where I'm working with someone else on.
- GitBucket - Is a Bitbucket clone that is written in Scala and while not as lightweight as Gogs, it does seem to compete on the features that I'm looking for.
Of the systems I looked at above, I decided on giving Gitea a try. Hosting it is fairly straightforward as the application is a single binary. With that said I run as many of my services in containers as I can. Running Gitea is a docker container is fairly straightforward.
The docker-compose.yml for the service looks like this.
version: "3"
networks:
gitea:
external: false
services:
server:
image: gitea/gitea:1.10
environment:
- USER_UID=${USER_UID}
- USER_GID=${USER_GID}
- DB_TYPE=postgres
- DB_HOST=db:5432
- DB_NAME=${DB_NAME}
- DB_USER=${DB_USER}
- DB_PASSWD=${DB_PASSWORD}
restart: always
networks:
- gitea
volumes:
- ${GITEA_DATA_DIRECTORY}:/data:Z
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "${GITEA_HTTP_PORT}:3000"
- "${GITEA_SSH_PORT}:22"
depends_on:
- db
db:
image: postgres:12-alpine
restart: always
environment:
- POSTGRES_USER=${DB_USER}
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_DB=${DB_NAME}
networks:
- gitea
volumes:
- ${DB_DATA_DIRECTORY}:/var/lib/postgresql/data:Z
smtp:
image: docker.io/namshi/smtp
restart: unless-stopped
environment:
- SMARTHOST_ADDRESS=${SMTP_HOST_ADDRESS}
- SMARTHOST_PORT=${SMTP_HOST_PORT}
- SMARTHOST_USER=${SMTP_HOST_USER}
- SMARTHOST_PASSWORD=${SMTP_HOST_PASSWORD}
- SMARTHOST_ALIASES=${SMTP_HOST_ALIASES}
networks:
- gitea
There's an accompanying .env file for the variables above. Running the service is as simple as docker-compose pull && docker-compose down && docker-compose up -d
The only thing remaining is to expose the service to the world through a reverse proxy. I personally use nginx for most of my simple reverse proxying needs. Thaat config looks a little like this.
upstream git_backend {
server 127.0.0.1:<locally exposed port>;
}
server {
listen <server ip>:80;
server_name <server name>;
location ^~ /.well-known/acme-challenge/ {
# Set correct content type. According to this:
# https://community.letsencrypt.org/t/using-the-webroot-domain-verification-method/1445/29
# Current specification requires "text/plain" or no content header at all.
# It seems that "text/plain" is a safe option.
default_type "text/plain";
# This directory must be the same as in /etc/letsencrypt/cli.ini
# as "webroot-path" parameter. Also don't forget to set "authenticator" parameter
# there to "webroot".
# Do NOT use alias, use root! Target directory is located here:
# /var/www/<server name>/.well-known/acme-challenge/
root /var/www/<server name>;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen <server ip>:443 ssl http2;
server_name <server name>;
server_tokens off; ## Don't show the nginx version number, a security best practice
ssl on;
ssl_certificate /etc/letsencrypt/live/<server name>/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/<server name>/privkey.pem;
ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 5m;
add_header Strict-Transport-Security max-age=31536000;
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Robots-Tag none;
add_header X-Download-Options noopen;
add_header X-Permitted-Cross-Domain-Policies none;
error_log /var/log/nginx/<server name>_error.log warn;
access_log /var/log/nginx/<server name>_access.log;
client_max_body_size 10M;
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://git_backend;
}
}
With this we expose the http endpoint for an ACME certificate provider, and redirect all other http traffic to the https endpoint. Nginx terminates the SSL connection and passes the traffic to the local backend. This is the general setup for most of my simple https termination and reverse proxying.
With these two configurations in place, Gitea can be brought up and the service can be accessed publicly on the server.
␀