% ============================================
% STEP 06: GITEA INSTALLATION & SERVER-ÜBERSICHT
% ============================================

\section{Gitea Installation und Server-Übersicht}
\label{sec:step06}

In diesem Schritt installieren wir \textbf{Gitea} – einen selbst gehosteten Git-Server mit Web-Oberfläche – als Ersatz für OneDev, das sich leider als fehlerhaft erwies. Am Ende dieses Kapitels hast du einen vollständigen Überblick über deinen Server: welche Container laufen, welche Ports offen sind, wo die Daten liegen und wie alles zusammenhängt.

\subsection{Warum ein eigener Git-Server?}

Bisher haben wir den Code manuell gebaut, als Docker-Image exportiert und per \texttt{scp} auf den Server kopiert – ein umständlicher Prozess, der bei jedem Update wiederholt werden muss. Ein eigener Git-Server mit CI/CD-Pipeline automatisiert diesen Ablauf:

\begin{itemize}
    \item \textbf{Push = Deploy:} Code auf den Server pushen, Pipeline baut automatisch Docker-Images und startet Container neu
    \item \textbf{Versionierung:} Alle Änderungen sind nachvollziehbar, kein „das ging gestern noch"-Problem
    \item \textbf{Teamarbeit:} Mehrere Entwickler können gleichzeitig am Projekt arbeiten
    \item \textbf{Backup:} Das Repository liegt auf deinem Server, nicht bei GitHub/GitLab
\end{itemize}

\subsection{OneDev: Der gescheiterte Versuch}

Ursprünglich wollten wir \textbf{OneDev} installieren – eine All-in-One-Lösung aus Git-Server, CI/CD-Pipeline, Issue-Tracker und Paket-Registry. OneDev bietet deutlich mehr Funktionen als Gitea, erwies sich jedoch als problematisch:

\subsubsection{Installationsversuch mit Docker (Version \texttt{1dev/server:latest})}

\begin{lstlisting}[language=YAML, caption={docker-compose.yml für OneDev}]
services:
  onedev:
    image: 1dev/server:latest
    container_name: onedev
    restart: unless-stopped
    ports:
      - "6610:6610"
      - "6611:6611"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - onedev_data:/opt/onedev

volumes:
  onedev_data:
\end{lstlisting}

\textbf{Aufgetretene Probleme:}

\begin{enumerate}
    \item \textbf{Container stoppt sofort:} Nach dem Start erschien in den Logs nur „INFO - Stopping application". Der Container beendete sich ohne Fehlermeldung.
    
    \item \textbf{Workaround mit fester Version:} Statt \texttt{latest} nutzten wir \texttt{1dev/server:11.6.13} – gleiches Problem.
    
    \item \textbf{Datenbank-Fehler:} Die entscheidende Fehlermeldung in \texttt{/opt/onedev/logs/server.log}:
    \begin{lstlisting}[language=Bash, caption={Kritischer Fehler beim OneDev-Start}]
java.lang.IllegalStateException: EntityManagerFactory is closed
    at org.hibernate.internal.SessionFactoryImpl.validateNotClosed(...)
    \end{lstlisting}
    
    \textbf{Ursache:} OneDevs interne H2-Datenbank wurde beim ersten Start korrupt. Der Befehl \texttt{upgrade /opt/onedev} im Start-Script versuchte, eine nicht existierende Datenbank zu aktualisieren statt sie neu anzulegen. Selbst komplettes Löschen des Volumes und Neustart behoben das Problem nicht – die Logs wurden nach dem ersten Crash nicht mehr aktualisiert, der Container hing fest.
    
    \item \textbf{Versuch mit direkter Java-Installation:} Nachdem Docker scheiterte, luden wir die JAR-Datei herunter (57 Bytes – defekter Download) und stellten fest, dass Java nicht installiert war. Zu diesem Zeitpunkt war klar: Der Aufwand für OneDev steht in keinem Verhältnis zum Nutzen.
\end{enumerate}

\textbf{Fazit:} OneDev ist ein mächtiges Tool, aber die Docker-Images sind instabil. Für einen produktiven Einsatz wäre eine native Installation mit externer PostgreSQL-Datenbank empfehlenswert – für unsere Zwecke überdimensioniert.

\subsection{Gitea: Die schlanke Alternative}

\textbf{Gitea} ist ein in Go geschriebener Git-Server, der sich durch Einfachheit und geringe Ressourcenanforderungen auszeichnet. Im Vergleich zu OneDev:

\begin{table}[h]
\centering
\caption{Vergleich OneDev vs. Gitea}
\begin{tabular}{@{}p{3cm}ll@{}}
\toprule
\textbf{Merkmal} & \textbf{OneDev} & \textbf{Gitea} \\
\midrule
Größe Image   & $\sim$500 MB                    & $\sim$100 MB \\
Startzeit     & 2–5 Minuten                     & $\sim$5 Sekunden \\
CI/CD         & h Eingebaut                    & x Extern (z.B. Woodpecker CI) \\
Datenbank     & H2 (intern, fehleranfällig)      & SQLite3, PostgreSQL, MySQL \\
Komplexität   & Hoch                            & Gering \\
Installation  & Fehlgeschlagen                  & h In 10 Sekunden \\
\bottomrule
\end{tabular}
\end{table}

\subsection{Gitea mit Docker installieren}

\subsubsection{Schritt 1: OneDev rückstandslos entfernen}

\begin{lstlisting}[language=Bash, caption={OneDev komplett löschen}]
cd /opt/onedev
docker compose down 2>/dev/null    # Container stoppen
cd /
rm -rf /opt/onedev                 # Verzeichnis löschen
docker system prune -af            # Ungenutzte Images/Volumes entfernen
\end{lstlisting}

\texttt{docker system prune -af} löscht:
\begin{itemize}
    \item Alle gestoppten Container
    \item Alle ungenutzten Netzwerke
    \item Alle ungenutzten Images
    \item Alle ungenutzten Build-Caches
\end{itemize}

Insgesamt wurden 806 MB Speicher freigegeben!

\subsubsection{Schritt 2: Gitea-Verzeichnis und docker-compose.yml anlegen}

\begin{lstlisting}[language=Bash, caption={Gitea-Verzeichnis vorbereiten}]
mkdir -p /opt/gitea
nano /opt/gitea/docker-compose.yml
\end{lstlisting}

\begin{lstlisting}[language=YAML, caption={docker-compose.yml für Gitea}]
services:
  gitea:
    image: gitea/gitea:latest
    container_name: gitea
    restart: unless-stopped
    ports:
      - "3000:3000"    # Web-UI
      - "2222:22"      # SSH für Git-Operationen
    volumes:
      - gitea_data:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro

volumes:
  gitea_data:
\end{lstlisting}

\textbf{Erklärung der Konfiguration:}
\begin{itemize}
    \item \texttt{ports: "3000:3000"} – Die Web-Oberfläche ist auf Port 3000 erreichbar
    \item \texttt{ports: "2222:22"} – Git-Operationen per SSH laufen auf Port 2222 (weil Port 22 bereits vom Host-SSH belegt ist)
    \item \texttt{gitea\_data:/data} – Alle Gitea-Daten (Repositories, Konfiguration, Datenbank) liegen in diesem Volume
    \item \texttt{/etc/timezone:/etc/timezone:ro} – Übergibt die Zeitzone des Hosts an den Container (\texttt{ro} = read-only)
    \item \texttt{/etc/localtime:/etc/localtime:ro} – Übergibt die lokale Zeit
\end{itemize}

\subsubsection{Schritt 3: Container starten}

\begin{lstlisting}[language=Bash, caption={Gitea starten}]
cd /opt/gitea
docker compose up -d
\end{lstlisting}

Ausgabe:
\begin{lstlisting}[language=Bash, caption={Erfolgreicher Start}]
[+] up 9/9
 h Image gitea/gitea:latest  Pulled
 h Network gitea_default     Created
 h Volume gitea_gitea_data   Created
 h Container gitea           Started
\end{lstlisting}

\subsubsection{Schritt 4: Firewall öffnen}

\begin{lstlisting}[language=Bash, caption={Port 3000 freigeben}]
ufw allow 3000/tcp
\end{lstlisting}

\subsubsection{Schritt 5: Gitea im Browser einrichten}

\textbf{URL:} \texttt{http://185.209.229.167:3000}

Beim ersten Aufruf erscheint die Installationsseite. Folgende Einstellungen sind vorausgefüllt und können übernommen werden:

\begin{itemize}
    \item \textbf{Datenbanktyp:} SQLite3 (einfachste Option, perfekt für kleine Teams)
    \item \textbf{Pfad:} \texttt{/data/gitea/gitea.db}
    \item \textbf{Server-Domain:} \texttt{185.209.229.167}
    \item \textbf{Gitea-Basis-URL:} \texttt{http://185.209.229.167:3000/}
    \item \textbf{SSH-Server-Port:} 22
    \item \textbf{Gitea-HTTP-Listen-Port:} 3000
\end{itemize}

\textbf{Wichtig:} Unter „Administratoreninstellungen" (ganz unten aufklappen) musst du einen Admin-Account anlegen:

\begin{itemize}
    \item \textbf{Benutzername:} robert
    \item \textbf{Passwort:} [dein sicheres Passwort]
    \item \textbf{E-Mail:} robert@robre.de
\end{itemize}

Dann auf \textbf{„Gitea installieren"} klicken. Nach wenigen Sekunden ist Gitea einsatzbereit.

\subsection{Vollständige Server-Übersicht}

Nach diesem Schritt ergibt sich folgendes Gesamtbild deines Servers:

\subsubsection{Laufende Docker-Container}

\begin{table}[h]
\centering
\caption{Alle laufenden Container auf dem Server}
\begin{tabular}{@{}llll@{}}
\toprule
\textbf{Container} & \textbf{Image} & \textbf{Ports} & \textbf{Aufgabe} \\
\midrule
\texttt{gitea} & \texttt{gitea/gitea:latest} & 3000, 2222→22 & Git-Server mit Web-UI \\
\texttt{nginx-proxy} & \texttt{nginxproxy/nginx-proxy} & 80, 443 & Reverse Proxy \\
\texttt{acme-companion} & \texttt{nginxproxy/acme-companion} & – & SSL-Zertifikate (Let's Encrypt) \\
\texttt{fitness-web} & \texttt{fitness-web:latest} & 80 (intern) & React-Frontend \\
\texttt{fitness-api} & \texttt{fitness-api:latest} & 5000 (intern) & .NET 8 Backend \\
\bottomrule
\end{tabular}
\end{table}

\subsubsection{Docker-Volumes (persistente Datenspeicher)}

\begin{table}[h]
\centering
\caption{Volumes und ihre Inhalte}
\begin{tabular}{@{}ll@{}}
\toprule
\textbf{Volume} & \textbf{Inhalt} \\
\midrule
\texttt{gitea\_gitea\_data} & Gitea: Repositories, Datenbank, Konfiguration \\
\texttt{fitness\_fitness-data} & Fitness-App: SQLite-Datenbank (\texttt{/app/data}) \\
\texttt{fitness\_certs} & SSL-Zertifikate von Let's Encrypt \\
\texttt{fitness\_html} & Challenge-Dateien für Let's Encrypt \\
\texttt{fitness\_vhost} & Nginx-Konfiguration pro Virtual Host \\
\texttt{fitness\_acme} & ACME-Kontodaten \\
\bottomrule
\end{tabular}
\end{table}

\subsubsection{Firewall (nur diese Ports sind offen!)}

\begin{table}[h]
\centering
\caption{Geöffnete Ports}
\begin{tabular}{@{}cll@{}}
\toprule
\textbf{Port} & \textbf{Dienst} & \textbf{Begründung} \\
\midrule
22   & SSH        & Server-Verwaltung \\
80   & HTTP       & Fitness-App, leitet auf HTTPS um \\
443  & HTTPS      & Fitness-App (verschlüsselt), PWA-Pflicht! \\
3000 & Gitea Web  & Git-Server-Oberfläche \\
\bottomrule
\end{tabular}
\end{table}

\subsubsection{Installierte Systempakete}

\begin{itemize}
    \item \texttt{docker-ce}, \texttt{docker-ce-cli}, \texttt{docker-compose-plugin} – Docker-Plattform
    \item \texttt{fail2ban} – Bruteforce-Schutz für SSH
    \item \texttt{git} – Git (von Gitea genutzt)
\end{itemize}

Der Server ist bewusst schlank gehalten – keine überflüssigen Dienste, keine unnötigen Pakete.

\subsubsection{Verzeichnisstruktur unter /opt}

\begin{lstlisting}[language=Bash, caption={Projektverzeichnisse auf dem Server}]
/opt/
  containerd/     # Docker-Laufzeitumgebung
  fitness/        # docker-compose.yml fuer Fitness-App
    docker-compose.yml
  gitea/          # docker-compose.yml fuer Gitea
    docker-compose.yml
\end{lstlisting}

\subsection{Wie Gitea in die Infrastruktur passt}

\textbf{Aktueller Datenfluss:}

\textbf{Aktueller Datenfluss:}

\begin{verbatim}
Du (lokal)                  Server (testserver)
---------                   ------------------
git push ---------------->  Gitea (Port 3000/2222)
                                |
                                | (spaeter: CI/CD-Pipeline)
                                v
                            Docker-Images bauen
                                |
                                v
                            Container neustarten
                                |
                                v
                            nginx-proxy (Port 80/443)
                                |
                        +-------+-------+
                        |               |
                        v               v
                   fitness-web     fitness-api
                   (Port 80)       (Port 5000)
\end{verbatim}

\textbf{Was noch fehlt:} Die CI/CD-Pipeline in Gitea (z.B. mit Gitea Actions oder Woodpecker CI), die automatisch bei jedem Push Docker-Images baut und die Container aktualisiert. Das folgt in einem späteren Schritt.

\subsection{Zusammenfassung}

Nach diesem Schritt haben wir:

\begin{itemize}
    \item OneDev aufgrund von Docker-Inkompatibilitäten verworfen
    \item Gitea erfolgreich mit Docker installiert (in unter 1 Minute!)
    \item Einen vollständigen Überblick über alle Container, Volumes, Ports und Verzeichnisse
    \item Die Grundlage für eine spätere CI/CD-Pipeline geschaffen
\end{itemize}

\textbf{Nützliche Befehle für die tägliche Verwaltung:}

\begin{lstlisting}[language=Bash, caption={Server-Cockpit – wichtige Befehle}]
# Alle laufenden Container
docker ps

# Alle Volumes
docker volume ls

# Firewall-Status
ufw status verbose

# Installierte Pakete
apt list --installed | grep -E "docker|fail2ban"

# Verzeichnisse unter /opt
ls -la /opt/
\end{lstlisting}