Forgejo: Tu propio servidor Git

TL;DR: El artículo narra la experiencia de configurar un servidor de Git usando Forgejo, un fork de Gitea, en un entorno Docker con PostgreSQL y SSH. Se describe cómo desplegar la aplicación mediante un archivodocker-compose.yml
, realizar la configuración inicial y ajustar parámetros de seguridad, como desactivar el registro público. Posteriormente, se agrega Drone para la integración continua, incluyendo cómo crear una aplicación OAuth en Forgejo y ajustar las variables de entorno en el contenedor de Drone. Finalmente, se introduce un runner de Drone para ejecutar las acciones automáticas, utilizando un archivo.drone.yml
para definir las tareas, lo que permite ejecutar pipelines en el nuevo servidor de Git.
Hoy me ha dado por empezar un nuevo proyecto en Angular, y por algún motivo me ha apetecido revivir mi vieja instancia de Gitea y alojarlo allí (para sorpresa de nadie, al final me he enfrascado con esto durante horas y no le he dedicado ni una línea al proyecto de Angular todavía)
Un amigo que estuvo montando algo similar recientemente, me mencionó Forgejo, un fork de Gitea que parece que ha tomado bastante inercia y no tiene mala pinta, así que he decidido probarlo, ya que me gusta bastante más su razón de ser.
Manos a la obra
Empezaremos por el despliegue del contenedor que albergará la aplicación. En este caso he decidido probar a usarlo sobre una base de datos PostgreSQL.
networks:
forgejo:
external: false
# proxy-network:
# external: true
services:
server:
image: codeberg.org/forgejo/forgejo:7
container_name: forgejo
environment:
- USER_UID=1000
- USER_GID=1000
- FORGEJO__database__DB_TYPE=postgres
- FORGEJO__database__HOST=db:5432
- FORGEJO__database__NAME=forgejo
- FORGEJO__database__USER=forgejo
- FORGEJO__database__PASSWD=forgejo
restart: always
ports:
- 127.0.0.1:2222:22
networks:
- forgejo
# - proxy-network
volumes:
- ./forgejo:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
# - /home/git/.ssh:/data/git/.ssh
depends_on:
- db
db:
image: postgres:14
restart: always
environment:
- POSTGRES_USER=forgejo
- POSTGRES_PASSWORD=forgejo
- POSTGRES_DB=forgejo
networks:
- forgejo
volumes:
- ./postgres:/var/lib/postgresql/data
docker-compose.yml
En mi caso particular dado que uso un proxy de nginx, no he publicado el puerto 3000
que tiene por defecto para acceder a la aplicación y también he conectado el contenedor a mi red proxy-network
. Lo que sí es relevante es la configuración del puerto 127.0.0.1:2222:22
para poder usar SSH, lo explicaré posteriormente.
Si todo ha ido bien, deberíamos ver la pantalla de configuración inicial:

Aquí solo hay que retocar los parámetros que creamos oportunos. En mi caso sólo he cambiado el dominio del servidor a mi dominio público, y el título de la aplicación. El resto recomiendo no tocarlo (como el usuario git
y el puerto 22
) si queremos seguir con esta configuración.
Pulsamos en "Instalar Forgejo" para que haga su magia y automáticamente nos llevará a la pantalla de inicio de sesión:

Pulsamos el botón de Registro en la esquina superior derecha y rellenaremos el pequeño formulario para registrarnos. Al ser el primer usuario, tendremos permisos administrativos.
Como se trata de un servidor para un homelab que está expuesto a internet, he decidido desactivar la opción de registro. En caso de querer agregar alguno se puede hacer manualmente como administrador. Para ello, iremos a la carpeta donde se encuentran los volúmenes de docker y editaremos el fichero forgejo/gitea/conf/app.ini
, concretamente cambiaremos el parámetro DISABLE_REGISTRATION
poniendo su valor a true
, también he aprovechado para desactivar el login mediante OpenID, con el parámetro ENABLE_OPENID_SIGNIN
ya que no voy a usarlo y me chirría ver la opción al hacer login.
Reiniciamos el servidor y llegados a este punto deberíamos tener acceso a la aplicación y al panel administrativo. Ya se puede usar como un servidor de Github sin mayor problema, y si era tu intención usarlo con HTTPS podríamos decir que ya hemos llegado al final de la guía, pero yo quiero endulzarlo un poco más 👇
Configuración de acceso mediante SSH
Para configurar el acceso por SSH he recurrido a la documentación de Gitea, donde explican varias formas de hacerlo. He optado por el primer método (SSHing Shim) y lo explicaré aquí de manera resumida.
En nuestro host (el servidor donde corre Docker con nuestra instancia de Forjego) deberemos crear un usuario git
que en resumidas cuentas hará de puente entre nuestro host y el contenedor. Es importante crearlo con con carpeta en home
ya que de lo contrario no podremos ejecutar el resto de comandos de la documentación. Para ello, en nuestro host lanzaremos el comando sudo useradd -m git
Ahora explicaré los puntos clave sobre el fichero docker-compose
:
services:
server:
...
environment:
...
- USER_UID=1001
- USER_GID=1001
# UID y GID del usuario git
# Puedes consultarlos haciendo sudo cat /etc/passwd en tu host
...
ports:
- 127.0.0.1:2222:22
# Redirecciona el puerto 22 del servicio al 2222 de nuestro host
...
volumes:
...
- /home/git/.ssh:/data/git/.ssh
# Monta la carpeta ssh del usuario git en un volumen
# para tener acceso en el contenedor
...
...
Es importante dejarlos configurados debidamente, de lo contrario no funcionará.
Una vez creado el usuario git
le crearemos una clave con el siguiente comando:
sudo -u git ssh-keygen -t rsa -b 4096 -C "Gitea Host Key"
Ahora hay que modificar el archivo authorized_keys
del usuario git
para que se comporte de la misma forma que en el contenedor:
sudo -u git cat /home/git/.ssh/id_rsa.pub | sudo -u git tee -a /home/git/.ssh/authorized_keys
sudo -u git chmod 600 /home/git/.ssh/authorized_keys
Tras eso, el archivo debería verse de forma similar a:
# SSH pubkey from git user
ssh-rsa <Gitea Host Key>
# other keys from users
command="/usr/local/bin/gitea --config=/data/gitea/conf/app.ini serv key-1",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty <user pubkey>
/home/git/.ssh/authorized_keys
(No pasa nada si no vemos ninguna clave de usuario, ya que no hemos agregado ninguna)
Por último, habrá que crear un comando gitea
fake que se encargará de redireccionar los comandos del host al contenedor. Con un usuario con permisos administrativos hay que ejecutar:
cat <<"EOF" | sudo tee /usr/local/bin/gitea
#!/bin/sh
ssh -p 2222 -o StrictHostKeyChecking=no git@127.0.0.1 "SSH_ORIGINAL_COMMAND=\"$SSH_ORIGINAL_COMMAND\" $0 $@"
EOF
sudo chmod +x /usr/local/bin/gitea
Con esto debería ser suficiente. Ahora podemos acceder a nuestra instancia de Forgejo, pulsar en nuestro avatar en la esquina superior derecha, posteriormente en Configuación y en Claves SSH/GPG para agregar nuestra clave pública.
Una vez configurada, podemos probar a hacer una conexión SSH desde cualquier sitio a nuestro host y éste debería dar la siguiente respuesta:

Esto significa que en nuestro host, seguimos teniendo el puerto 22 operativo para SSH, pero sin embargo cuando conectamos con el usuario git
la conexión va directa al contenedor de Docker.
Llegados a este punto, tenemos un servidor Git completamente funcional, con acceso mediante HTTPS y SSH.
Agregando Drone para Integración Continua
Como muchos echarán en falta las Github Actions, Drone es una buena alternativa para ejecutar tareas de integración contínua. Forgejo también tiene las Forgejo Actions, pero no he tenido tiempo de revisarme la documentación para plantear una forma sencilla y en un contenedor (por seguir la línea de la guía), así que prefiero hacerlo con Drone que es un viejo conocido.
Para empezar, hay que crear una aplicación OAuth en el panel de control de Forgejo. En la URL hay que poner el domino donde se desplegará Drone, seguido de un /login:

Esto nos dará un Client ID y Client Secret que deberemos usar posteriormente.
Se puede levantar el contenedor de varias formas, pero he preferido anexionarlo al docker-compose
ya existente:
drone:
image: drone/drone:2
container_name: drone
restart: always
networks:
- drone-network
volumes:
- /var/lib/drone:/data
environment:
DRONE_GITEA_SERVER: https://try.gitea.io
DRONE_GITEA_CLIENT_ID: 05136e57d80189bef462
DRONE_GITEA_CLIENT_SECRET: 7c229228a77d2cbddaa61ddc78d45e
DRONE_RPC_SECRET: super-duper-secret
DRONE_SERVER_HOST: drone.company.com
DRONE_SERVER_PROTO: https
ports:
- "80:80"
- "443:443"
depends_on:
- server
Debermos completar las variables de entorno con nuestra información:
DRONE_GITEA_SERVER
es el dominio donde se encuentra ForgejoDRONE_GITEA_CLIENT_ID
yDRONE_GITEA_CLIENT_SECRET
son los valores que obtuvimos al crear la aplicación OAuth en ForgejoDRONE_RPC_SECRET
es una clave compartida a la que podemos asignar cualquier valor, recomiendo generar una conopenssl rand -hex 16
DRONE_SERVER_HOST
yDRONE_SERVER_PROTO
será el dominio por donde accederemos a Drone y su protocolo.
En mi caso particular he retirado el mapeo de puertos y he añadido el contenedor a la red proxy-network
ya que tengo la aplicación tras un reverse-proxy.
Volvemos a recrear todo con un buen docker compose up -d --force-recreate
y nos dirigimos a la URL que hemos configurado para Drone para ver algo como esto:

Al pulsar Continuar, debería redirigir a nuestra instancia de Forgejo para finalizar la configuración:

Tras eso, rellenamos nuestros datos y ya tendríamos Drone configurado.
Agregando runners a Drone
Si estáis familiarizados con las Github Actions, sabréis que se necesitan runners para correr las acciones. En drone sucede lo mismo, así que vamos agregar uno para que se encargue de ello.
Para hacerlo de una forma sencilla agregaremos este nuevo contenedor a nuestro docker-compose
runner:
image: drone/drone-runner-docker:1
container_name: runner
restart: always
networks:
- drone-network
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
DRONE_RPC_PROTO: https
DRONE_RPC_HOST: drone.company.com
DRONE_RPC_SECRET: super-duper-secret
DRONE_RUNNER_CAPACITY: 2
DRONE_RUNNER_NAME: my-first-runner
ports:
- "3000:3000"
depends_on:
- drone
De nuevo, aconsejo repasar todas las variables de entorno con las que ya hemos introducido anteriormente para que se vincule correctamente.
Para asegurar que todo ha ido bien, he creado un pequeño proyecto de React y lo he subido a un nuevo repositorio en Forgejo. Luego he accedido a Drone para sincronizarlo y activarlo.
De forma similar a como se hace en Github Actions, aquí hemos de añadir un fichero .drone.yml
que se encargará las acciones. Este es el que he subido junto con el código:
kind: pipeline
name: default
steps:
- name: test
image: node
commands:
- npm install
- npm run build
La sintaxis es un poco diferente, pero en la documentación de Drone lo explican todo bastante bien.
Tras subir los cambios, drone detectará el fichero .drone.yml
y ejecutará las acciones pertinentes de forma automática. Podéis consultar los resultados tanto en el contenedor de Drone:

Listo, ya tenemos integración continua en nuestro servidor.
Espero que sea de ayuda y no haya sido demasiado pesado, yo dejo esto aquí como referencia para el futuro (siempre se me olvidan este tipo de cosas), y me dejo en el tintero la integración de Forgejo Actions y Renovate (un sustituto de dependabot), que dará para otro artículo.