GIT – Servidor

Introducción

Me parece una excelente práctica tener todos mis proyectos y trabajos respaldados en un servidor remoto. Para mis proyectos personales generalmente uso GitHub, dejando los repositorios abiertos a la comunidad. Sin embargo, no quiero mezclar mis proyectos personales y Open Source con aquellos que corresponden a servicios que ofrezco a otros. Para este segundo caso, la mejor solución es usar mi propio servidor como servidor Git, haciendo los push directamente allí. Las ventajas que esto me ofrece son: una mayor autoridad sobre mis propios datos, un control de acceso personalizado por si en el futuro quiero colaborar, y una mayor personalización.

Repositorios Bare

Un repositorio bare es un repositorio Git sin working tree: no contiene una copia extraída de los archivos del proyecto, sino únicamente la base de datos de objetos y referencias que en un repositorio normal se encuentra dentro de .git/. En lugar de anidarse en un subdirectorio .git, ese contenido ocupa directamente la raíz del repositorio. Por convención, el nombre del directorio termina en .git (por ejemplo mi-proyecto.git), lo cual no es obligatorio pero sí el estándar. Un repositorio remoto que recibe push y pull debe ser bare.


Configurar Servidor Git

Primero es importante tener en cuenta que Git permite conectarse sin credenciales usando el protocolo git://, que usa el puerto 9418 por defecto. De esta manera cualquier usuario podría clonar los repositorios sin restricciones. Para el uso que yo le voy a dar no usaré nunca el protocolo git://, sino que en cambio usaré Git sobre ssh, que viene habilitado por defecto. Usando ssh no se necesita tener ningún daemon ejecutándose en segundo plano, ya que los comandos se ejecutan en el servidor vía ssh usando git-shell como intérprete, que es mucho más seguro que bash. Git también permite escuchar a través del protocolo https:// con credenciales, pero usar ssh resulta mucho más sencillo, por lo cual descartaré esa opción.

  1. Crear el usuario dedicado para Git. El hecho de que sea un usuario dedicado lo hace mucho más seguro. Además, al usar git-shell como intérprete en lugar de bash, no puede usarse para administrar el servidor remotamente.

    sudo useradd -m -s /usr/bin/git-shell git
  2. Crear la carpeta para las claves SSH y el archivo authorized_keys. Es importante que authorized_keys exista como archivo antes de añadir claves, de lo contrario algunos sistemas lo crearán como directorio y la autenticación no funcionará.

    mkdir /home/git/.ssh
    chmod 700 /home/git/.ssh
    touch /home/git/.ssh/authorized_keys
    chmod 600 /home/git/.ssh/authorized_keys
  3. Cambiar el propietario de /home/git/ para añadir una capa extra de seguridad, y crear la carpeta repos.

    sudo chown root:root /home/git
    sudo chmod 711 /home/git
    sudo mkdir /home/git/repos
    sudo chown git:git /home/git/repos
  4. Deshabilitar la autenticación por contraseña y habilitar la autenticación por clave pública editando /etc/ssh/sshd_config:

    PasswordAuthentication no
    PubkeyAuthentication yes

    Y reiniciar el servicio:

    sudo systemctl restart sshd
  5. Añadir las claves públicas de los usuarios autorizados al archivo authorized_keys:

    cat ~/.ssh/id_ed25519.pub >> /home/git/.ssh/authorized_keys

    (Nota: tener en cuenta que para copiar las claves puede ser necesario usar ssh desde el cliente al servidor)

A partir de aquí ya es posible crear repositorios bare y hacer push directamente al servidor:

git init --bare /home/git/repos/mi-proyecto.git

Nota: Es recomendable usar git config --system init.defaultBranch main antes de iniciar repositorios bare para que el HEAD apunte a main y no a master. En caso de no agregar esta configuración, al clonar el repositorio aparecerá un mensaje Remote branch 'HEAD' not found in upstream origin

Y desde el cliente:

git remote add origin ssh://git@mi-servidor/home/git/repos/mi-proyecto.git
git push -u origin main

Git-Shell

git-shell es un shell de inicio de sesión restringido que actúa como filtro entre sshd y los programas de transporte de Git: valida el comando solicitado por el cliente y, si está permitido, lo sustituye mediante exec por git-receive-pack, git-upload-pack o git-upload-archive, que son los que implementan el protocolo con el cliente.

Sin embargo, si se crea el directorio ~/git-shell-commands, es posible conectarse a la shell en modo interactivo. En ese modo, git-shell muestra un prompt git> desde el que solo pueden ejecutarse los ejecutables contenidos en ese directorio, además de los built-ins exit, quit, logout y bye. Si existe git-shell-commands/help, se ejecuta automáticamente al iniciar la sesión, y el usuario puede volver a invocarlo con help.

Git-shell-commands

Descripción

Los git-shell-commands son scripts ejecutables que extienden las capacidades de git-shell. Cuando un usuario se conecta vía SSH al servidor, git-shell actúa como intérprete restringido — solo permite operaciones git y los comandos personalizados que se hayan definido.

Comandos Built-in

Por defecto git-shell únicamente permite las operaciones básicas de git: clonar, hacer push y pull. Cualquier intento de ejecutar otros comandos es rechazado. Esto lo hace ideal para un servidor git dedicado, ya que el usuario no puede usar la conexión SSH para administrar el servidor.

Crear Personalizados

Se pueden crear comandos personalizados, creando scritps en el directorio ~/git-shell-commands/ del usuario git. Cada script se convierte en un comando disponible vía SSH:

ssh git@servidor nuevo-comando

Los scripts pueden estar escritos en cualquier lenguaje disponible en el servidor, aunque lo más habitual es usar bash o sh. Por ejemplo, el comando new que crea un repositorio bare:

#!/bin/sh
if [ -z "$1" ]; then
    echo "Usage: new <repo-name>"
    exit 1
fi
git init --bare /home/git/repos/$1.git
echo "Repo $1 created."

Servidor Git en Docker

Prefiero alojar el servidor Git dentro de un contenedor Docker, tanto por seguridad como por portabilidad. Para eso creé una imagen personalizada basada en Alpine Linux junto con un docker-compose que gestiona su configuración. La imagen incluye comandos de gestión de repositorios accesibles vía ssh (new, list, delete, etc.), datos persistentes mediante bind mounts, soporte para agregar claves ssh sin reiniciar el contenedor, y arranque automático con el sistema.

Está disponible en el siguiente repositorio:
https://github.com/maagenta/git-server-docker/tree/main