Add steps for domain purchase and DNS configuration, set up HTTPS with nginx-proxy and Let's Encrypt, and install Gitea as a self-hosted Git server. Remove temporary database files and update assembly info for API project.
This commit is contained in:
@@ -0,0 +1,325 @@
|
||||
% ============================================
|
||||
% 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}
|
||||
Reference in New Issue
Block a user