Construcción de nuestro propio servidor de Git

Aquí casi todos conocemos el software, o prácticamente protocolo, desarrollado en inicio por Linus Torvalds, Git.

Git es un sistema de control de versiones, que se constituye como una herramienta indispensable, a día de hoy, para cualquier programador; especialmente en el software libre. Git funciona como cliente o como servidor, y cada cliente aloja en local una copia del software que sincronizará con el servidor en función de lo que el cliente convenga. Los despliegues de Git más conocidos son Github o Gitlab, donde éstos te ofrecen una interfaz gráfica pulida y almacenamiento de servidor para todos los proyectos que se deseen incluir. Por ello, la duda razonable que debe asaltar al lector sería: ¿por qué querría crear mi propio servidor de Git?

Veamos, una buena razón para alojar tus propios repositorios Git es tener y mantener el control sobre tu propia infraestructura informática, tanto a nivel de sistema como a nivel de código, guardando incluso el secreto de aquellos códigos que quieras mantener sin publicar (y que de otra forma lo estarías cediendo al ordenador de otro, confiando ciegamente en que verdaderamente se encuentre oculto). Considero que los servidores de Git comúnmente utilizados como Github, Gitlab, Bitbucket… No son de fiar, en cuanto son propietarios desconocidos y sostenedores de tu código. No es secreto que estos servicios analizan el código, aplican telemetría e inteligencia sobre ellos, analizan las conexiones de sus usuarios y aplican restricciones y limitaciones. Al fin y al cabo son empresas con fines comerciales, y a menudo con fines ocultos. En el caso de Microsoft, como propietario de Github es mucho más lamentable y peligrosa la situación.

En cuanto a economía doméstica, estos servicios también tienen diferentes planes de precios y varias restricciones (arbitrarias en muchos casos). Sin embargo cuando usted mismo lo aloja, las restricciones son los límites de recursos del sistema y su conexión, por lo tanto, es una solución mucho más flexible, adaptada y convergente con el usuario.


Ahora entraremos en el barro técnico, ¿cómo creo mi servidor de Git?.

Lo primero que debemos hacer es tener Git instalado. Debido a la gran cantidad de distribuciones y formas variopintas de realizar la instalación, voy a obviarla. No es dificil instalar Git y se encuentra disponible, que yo sepa, en todos los repositorios oficiales de todas las distros. No obstante, si alguien tuviera cualquier problema siéntase libre de escribir un comentario para que le podamos ayudar.

También voy a obviar el uso general de Git. No es objetivo de esta entrada explicar cómo realizar cambios en el software, realizar commits, status, push y pull, revisar el histórico de cambios, etcétera. Si me animo podría escribir un post a parte a modo de curso, del uso de Git a nivel de cliente. Pero en este caso me centro en el servidor.

Para ello, el primer paso sería crear el usuario git (a nivel de sistema operativo), encargado de gestionar todos los procesos referentes a los repositorios y al servidor. Además, con el objetivo de dotar acceso mediante SSH añadimos authorized_keys y añadimos al fichero las claves públicas de los clientes.

# adduser git
# su git
$ cd
$ mkdir .ssh && chmod 700 .ssh
$ touch .ssh/authorized_keys 
$ chmod 600 .ssh/authorized_keys

Los repositorios pueden estar en el directorio que se quiera. En este caso, se encontrarán en el directorio git en la raíz del sistema. Para lo que tendremos que crearlo. Por cada repositorio, se crea un directorio (aconsejable es que terminen con .git). Para iniciar un repositorio como server usamos el parámetro init --bare.

$ mkdir /git
$ cd /git
$ mkdir code.git
$ chown git:git code.git
$ cd code.git
$ git init --bare
Initialized empty Git repository in /git/code.git/

De esta simple forma, cualquier equipo que tenga acceso al servidor podría, a través de Git y SSH, clonar repositorios. En el lado del cliente, configuraríamos nuestros parámetros (globales, si no sabes lo que haces):

$ git config --global user.name "ssh"
$ git config --global user.email ssh@linuxchad.org        
$ git config --global core.editor vim

O directamente modificando el fichero ~/.gitconfig:

[user]
email = ssh@linuxchad.org
name = ssh
[core]
editor = vim

La descarga mediante SSH [1] se hará tal que:

$ git clone git@linuxchad.org:/git/code.git

Publicación de repositorios

Quien haya sido un poco sagaz, se habrá dado cuenta que de esta forma el único que puede acceder a dichos repositorios es aquel que conozca la contraseña del usuario git, o bien que su clave pública esté alojada en el servidor para que sea reconocido. Es decir, sólo podría acceder al repositorio el que conozca personalmente al propietario del servidor.

Por ello tenemos la opción de publicar el repositorio, de forma que pueda ser clonado por todos (al igual que en Github y similares).

Para ello empleamos git-daemon, indicándole el directorio donde se encuentran los repositorios.

$ git daemon --reuseaddr --base-path=/git/ /git/

Esto no basta para publicar el repositorio, ya que el demonio de git iterará en el directorio anteriormente pasado en busca de repositorios exportables, éstos son aquellos que tengan el fichero git-daemon-export-ok en su raíz.

$ touch /git/code.git/git-daemon-export-ok

De esta forma, cualquier cliente podría clonar el repositorio usando la siguiente dirección:

$ git clone git://linuxchad.org/code.git

  1. Si configuramos la conexión cliente-servidor de SSH para permitir y establecer un par de claves RSA, ni si quiera nos pedirá la contraseña y las comunicaciones serán más ágiles y seguras. ↩︎

11 Me gusta

Esto es algo que todo el mundo acá debería aprender.
Excelente aporte :ok_hand:t4:

1 me gusta

Hola Codeberg también es una opción posible y es libre

Tambien exíste Sourcehut.

Igual, para el Git no creo que se pueda manejar tan eficientemente los pull request sin hacer un script que los gestione, obviando también el tema de las IPs y las conexiones de las que se suele hablar aquí.

1 me gusta

Precisamente lo que hacen las plataformas como Github y similares con los pull request son llamadas a Git por detrás. No sólo es más preciso sino que sí que es más eficiente hacerlo directamente con Git. Otra cosa, que no discuto en absoluto, es que requiera un poco más de conocimiento. Los servidores comerciales de Git te ofrecen mayor sencillez y una interfaz gráfica pulida a cambio de tener menos control y menos eficiencia en los procesos de tu código.


Por ejemplo, voy a exponer un caso de despliegue de una aplicación web usando simplemente Git y la terminal:

La idea es configurar una web de forma que, cuando alguien haga un commit y suba dichos cambios al servidor, éste automáticamente actualice los directorios del servidor con los últimos cambios.

Para ello, simplemente debemos modificar o crear el script post-receive (que se ejecuta automáticamente al recibir cualquier cambio). Éste se encuentra en el directorio hooks del directorio raíz del servidor Git. O sea, siguiendo con el ejemplo: /git/code.git/hooks/post-receive.

El contenido del script sería:

#!/bin/sh
echo "Desplegando sitio web"
git --work-tree=/srv/http --git-dir=/git/code.git checkout -f master

Con este sencillísimo script, cada vez que se haga un commit y un push contra el servidor, será actualizado el directorio /srv/http con el contenido actualizado, redesplegando el contenido.

Por supuesto, los permisos del directorio htmlroot deben tener los permisos para git.

drwxr-xr-x  3 git  git  4096 Dec 14 15:53 http
1 me gusta

En este post me he centrado en utilizar para todo la más noble de las interfaces de la computación: la interfaz de texto.

Ahora, viendo que hay cierta incertidumbre al no tener una visualización web, como el resto de servidores comerciales, he decidido exponer brevemente una opción extremadamente sencilla y útil para mantener una interfaz web sincronizada con los repositorios.


A veces es agradable navegar por el registro del historial de git en un navegador web o en algún otro programa sin tener que mirar el repositorio local. Es además una forma de mantener un registro de los repositorios que sean públicos, a modo de currículum o expositor de código.

Para ello uso stagit, de codemadness, que es un static page generator para git. Su uso es simple. Manualmente consta de dos partes, que son la creación de directorios html (que deben ser accesibles mediante servidor web) y su enlace con el directorio del servidor git:

$ mkdir -p /srv/git/htmlrepo1 && cd /srv/git/htmlrepo1
$ stagit /git/gitrepo1

Y la formación del índice y el htmlroot:

$ cd /srv/git
$ stagit-index /git/gitrepo1 /git/gitrepo2 /git/gitrepo3 > index.html

Esto genera repositorios de git como los propios de codemadness o los de suckless.

Una interfaz muy pulida, muy simple y minimalista; y apropiada para acceder mediante navegadores de terminal.


Si quisiéramos automatizar la actualización del directorio en función de los cambios de repositorios, añadiríamos el script siguiente al htmlroot (/srv/git, en mi caso):

# /srv/git/create.sh 
reposdir="/git"
curdir="$(pwd)"

# make index.
stagit-index "${reposdir}/"*/ > "${curdir}/index.html"

# make files per repo.
for dir in "${reposdir}/"*/; do
    # strip .git suffix.
    r=$(basename "${dir}")
    d=$(basename "${dir}" ".git")
    printf "%s... " "${d}"

    mkdir -p "${curdir}/${d}"
    cd "${curdir}/${d}" || continue
    stagit -c ".cache" -u "/git/$d/" "${reposdir}/${r}"

    # symlinks
    ln -sf log.html index.html
    ln -sf ../style.css style.css
    ln -sf ../logo.png logo.png
    ln -sf ../favicon.png favicon.png

    echo "done"
done

Por supuesto, el script anterior debe contar con permisos de ejecución; y el directorio htmlroot debe tener permisos adecuados para su manipulación, que siendo en este caso el usuario git el que genere de nuevo los HTML, debería éste tener permisos totales.

drwxr-xr-x 19 git  git  4096 Dec 14 15:46 git

Además, por cada repositorio del servidor debemos añadir el hook post-receive tal que:

#!/bin/sh
# /git/code.git/hooks/post-receive 

export LC_CTYPE="en_US.UTF-8"

name="$1"
if test "${name}" = ""; then
    name=$(basename "$(pwd)")
fi

# config
# paths must be absolute.
reposdir="/git"
dir="${reposdir}/${name}"
htmldir="/srv/git"
stagitdir="/"
destdir="${htmldir}${stagitdir}"
cachefile=".htmlcache"
# /config

if ! test -d "${dir}"; then
    echo "${dir} does not exist" >&2
    exit 1
fi
cd "${dir}" || exit 1

# detect git push -f
force=0
while read -r old new ref; do
    test "${old}" = "0000000000000000000000000000000000000000" && continue
    test "${new}" = "0000000000000000000000000000000000000000" && continue

    hasrevs=$(git rev-list "${old}" "^${new}" | sed 1q)
    if test -n "${hasrevs}"; then
            force=1
            break
    fi
done

# strip .git suffix.
r=$(basename "${name}")
d=$(basename "${name}" ".git")
printf "[%s] stagit HTML pages... " "${d}"

mkdir -p "${destdir}/${d}"
cd "${destdir}/${d}" || exit 1

# remove commits and ${cachefile} on git push -f, this recreated later on.
if test "${force}" = "1"; then
    rm -f "${cachefile}"
    rm -rf "commit"
fi

# make index.
stagit-index "${reposdir}/"*/ > "${destdir}/index.html"

# make pages.
stagit -c "${cachefile}" -u "/git/$d/" "${reposdir}/${r}"

ln -sf log.html index.html
ln -sf ../style.css style.css
ln -sf ../logo.png logo.png

echo "done"

FInalmente, aclarar que para una correcta impresión de los datos del repositorio en la interfaz web, cada directorio de repositorio debe tener en su raíz los ficheros owner, description y url:

$ cd /git/code.git/
$ echo "Linuchad" > owner
$ echo "Descripción del repositorio" > description
$ echo "git://linuxchad.org/code.git" > url

Las imágenes por defecto empleadas son favicon.png y logo.png, que irán en la raíz de htmlroot (/srv/git). También style.css.

$ cp stagit/style.css /srv/git/
$ cp stagit/favicon.png /srv/git/
$ cp stagit/logo.png /srv/git/
1 me gusta

Hablando de git, tambien existe server – forge – Lindenii Project Forge que es un servidor git pequeño como sourcehut. No lo he probado, pero es algo liviano ;).

1 me gusta