% Insert this file into the group report with: % \input{giteaStartup} % % Required packages in the main document: % \usepackage{graphicx} % \usepackage{minted} % \usepackage{hyperref} % % This chapter intentionally does not use minted's caption option. Captions % are written as ordinary LaTeX text so it remains compatible with the % minted configuration already used by the group report. \chapter{Startup Foundation with Gitea} \section{Overview} The startup's initial operational work hub was deployed on a DigitalOcean Droplet using Gitea. The deployment provides a private Git service for the team, including repositories, issues, pull requests, and collaborative code review. The public Git service is available at: \begin{center} \url{https://git.ssw590.tech} \end{center} The domain used for the service is \texttt{ssw590.tech}, and the Gitea hostname is \texttt{git.ssw590.tech}. The service runs on an Ubuntu DigitalOcean Droplet with Docker Compose. Gitea uses the official \texttt{gitea/gitea} container image pinned to version \texttt{1.27.3}. \section{Domain and GitHub Student Developer Pack} The domain \texttt{ssw590.tech} was obtained through the GitHub Student Developer Pack using Name.com. GitHub's current Student Developer Pack lists Name.com as providing a free domain from a selection of extensions, and separately lists the .TECH offer as one standard .TECH domain free for one year. Thus, the initial registration used for this project was free under the student offer. The project domain is: \begin{itemize} \item Domain: \texttt{ssw590.tech} \item Registrar: Name.com \item Gitea hostname: \texttt{git.ssw590.tech} \item Initial student-offer term: one year \item Automatic renewal date shown by Name.com: September 30, 2027 \end{itemize} The renewal price is not a fixed GitHub Student Developer Pack price. Name.com states that renewals are charged at the then-current renewal fee. The automatic-renewal setting is enabled for this domain. This means the team must maintain a valid payment method if the domain is to remain active after the student-offer term. \section{DNS Configuration} The DNS record for the Gitea service points the hostname \texttt{git.ssw590.tech} to the DigitalOcean Droplet's public IPv4 address: \begin{center} \texttt{157.230.10.81} \end{center} The hostname was verified independently by accessing it from outside the Droplet. The HTTPS request succeeded, demonstrating that the hostname resolved to the deployed service and that the service was reachable through the public Internet. A command-line DNS lookup can also be recorded in the final submission: \begin{minted}{bash} dig +short git.ssw590.tech A \end{minted} The expected result is the Droplet's public IPv4 address. \section{DigitalOcean Droplet and Docker} The DigitalOcean Droplet runs Ubuntu and was configured for password-based SSH authentication. The Droplet was accessed with: \begin{minted}{bash} ssh root@157.230.10.81 \end{minted} Docker Engine and Docker Compose were installed on the Droplet. The final Gitea deployment was managed from: \begin{minted}{bash} cd ~/gitea \end{minted} The Gitea Compose configuration pins the official image to version \texttt{1.27.3} rather than using an unversioned \texttt{latest} tag. \begin{minted}{yaml} services: gitea: image: gitea/gitea:1.27.3 container_name: gitea restart: unless-stopped ports: - "3000:3000" volumes: - ./data/gitea:/data networks: - gitea networks: gitea: driver: bridge \end{minted} The official image source is the Gitea container repository: \url{https://hub.docker.com/r/gitea/gitea}. The image version was verified with: \begin{minted}{bash} docker compose ps \end{minted} The resulting service showed: \begin{minted}{text} gitea/gitea:1.27.3 \end{minted} \section{SQLite Database} SQLite was selected for the Gitea database because the deployment is a small startup work hub and does not require a separate database service for this assignment. The Gitea configuration contains: \begin{minted}{text} DB_TYPE = sqlite3 PATH = /data/gitea/gitea.db \end{minted} The database file exists inside the persistent Docker bind mount. It was verified on the Droplet with: \begin{minted}{bash} ls -lh ~/gitea/data/gitea/gitea/gitea.db \end{minted} The file was approximately 2.1 MB at the time of verification. The corresponding file inside the container was also verified: \begin{minted}{bash} docker exec gitea ls -lh /data/gitea/gitea.db \end{minted} \section{Persistent Storage} The Docker Compose configuration maps the host directory \texttt{./data/gitea} to \texttt{/data} inside the Gitea container: \begin{minted}{yaml} volumes: - ./data/gitea:/data \end{minted} This is important because Gitea stores its database, repositories, SSH configuration, attachments, package data, indexes, and other application state below \texttt{/data}. The database was observed on the host at: \begin{minted}{text} /root/gitea/data/gitea/gitea/gitea.db \end{minted} The mount itself was inspected with: \begin{minted}{bash} docker inspect gitea --format '{{json .Mounts}}' \end{minted} The result showed the host path \texttt{/root/gitea/data/gitea} mounted read/write at \texttt{/data}. \section{Container Replacement and Persistence Test} Persistence was explicitly tested by recording the existing container, recreating it, and then checking that Gitea data remained available. Before replacement, the container identity was recorded with: \begin{minted}{bash} docker inspect gitea --format '{{.Id}}' \end{minted} The persistent mount was also recorded with: \begin{minted}{bash} docker inspect gitea --format '{{json .Mounts}}' \end{minted} The Gitea service was then force-recreated: \begin{minted}{bash} docker compose up -d --force-recreate gitea \end{minted} After the replacement, the running services were verified with: \begin{minted}{bash} docker compose ps \end{minted} The new container continued to use the same host-backed data directory. The SQLite database remained present: \begin{minted}{bash} ls -lh ~/gitea/data/gitea/gitea/gitea.db \end{minted} The Gitea repository and issue used for the persistence test also remained available after the replacement. This demonstrates that the application state was stored in the persistent volume rather than only in the container's writable layer. \section{Persistence Evidence} Figure~\ref{fig:gitea-persistence-repository} shows the \texttt{persistence-test} repository after the container replacement. \begin{figure}[htbp] \centering \includegraphics[width=0.90\textwidth]{gitea-persistence-repository.png} \caption{Gitea persistence-test repository after container replacement} \label{fig:gitea-persistence-repository} \end{figure} Figure~\ref{fig:gitea-persistence-issue} shows issue \#1 from the same repository after the replacement. \begin{figure}[htbp] \centering \includegraphics[width=0.90\textwidth]{gitea-persistence-issue-1.png} \caption{Persistence verification issue \#1 after container replacement} \label{fig:gitea-persistence-issue} \end{figure} \section{Startup Repository and Team Collaboration} The startup repository used for the development workflow is \texttt{ssw590-startup}. No separate Gitea organization was required for the assignment, so the repository itself is used as the team's shared workspace. Team members were added to the Gitea project using their individual accounts. The usernames used for the team accounts are: \begin{itemize} \item \texttt{mfriedma1} \item \texttt{alexn41} \item \texttt{so.stoeckel} \end{itemize} Passwords are intentionally not included in the report. The repository README was completed, and five issues were created and assigned as part of the team's issue-driven development workflow. \section{Issue and Pull Request Workflow} The first implementation task was tracked as issue \#1, ``Implement product landing page.'' A feature branch named \texttt{feature/issue-1-landing-page} was used for the implementation. The work was submitted as a pull request and reviewed before being merged into \texttt{main}. Figure~\ref{fig:gitea-pr-issue-1} shows the merged pull request for issue \#1. The screenshot records the assignment, commit, review comment, merge, and deletion of the feature branch. \begin{figure}[htbp] \centering \includegraphics[width=0.90\textwidth]{gitea-pr-issue-1-merged.png} \caption{Merged pull request implementing issue \#1} \label{fig:gitea-pr-issue-1} \end{figure} The second implementation task was tracked as issue \#2 and used the feature branch \texttt{feature/issue-2-tests}. The pull request added the repository validation tests and was merged into \texttt{main}. Figure~\ref{fig:gitea-pr-issue-2} shows the completed merge. \begin{figure}[htbp] \centering \includegraphics[width=0.90\textwidth]{gitea-pr-issue-2-merged.png} \caption{Merged pull request for issue \#2 repository validation tests} \label{fig:gitea-pr-issue-2} \end{figure} Figure~\ref{fig:gitea-activity} shows the Gitea activity view containing the repository and issue activity. \begin{figure}[htbp] \centering \includegraphics[width=0.90\textwidth]{gitea-admin-activity.png} \caption{Gitea repository and issue activity} \label{fig:gitea-activity} \end{figure} \section{HTTPS with Caddy and Let's Encrypt} Gitea is not directly exposed to the public Internet on port 3000 in the final deployment. A Caddy reverse proxy is used in front of Gitea. The final service layout is: \begin{center} \texttt{Internet -> Caddy :443 -> Gitea :3000} \end{center} Caddy publishes ports 80 and 443 on the Droplet while Gitea's port 3000 is kept internal to the Docker network. The Gitea container therefore shows port 3000 as an internal container port rather than as a host-published port. The current service state was verified with: \begin{minted}{bash} docker compose ps \end{minted} The output showed Caddy publishing ports 80 and 443 and Gitea exposing port 3000 only to the Docker network. Gitea's public URL was configured as: \begin{minted}{text} DOMAIN = git.ssw590.tech ROOT_URL = https://git.ssw590.tech \end{minted} \section{Let's Encrypt Certificate Verification} Caddy automatically obtained a Let's Encrypt certificate for \texttt{git.ssw590.tech}. The Caddy log showed successful ACME authorization and certificate issuance. The live HTTPS service was verified externally with: \begin{minted}{bash} curl -4 -I https://git.ssw590.tech \end{minted} The response was HTTP/2 200. Plain HTTP was also tested: \begin{minted}{bash} curl -4 -I http://git.ssw590.tech \end{minted} The response was: \begin{minted}{text} HTTP/1.1 308 Permanent Redirect Location: https://git.ssw590.tech/ \end{minted} This demonstrates that HTTP requests are redirected to HTTPS. The certificate itself was inspected with: \begin{minted}{bash} echo | openssl s_client \ -connect git.ssw590.tech:443 \ -servername git.ssw590.tech 2>/dev/null \ | openssl x509 -noout -issuer -subject -dates \end{minted} The observed certificate information was: \begin{minted}{text} issuer=... Let's Encrypt ... subject=CN = git.ssw590.tech notBefore=Sep 30 17:50:34 2026 GMT notAfter=Dec 29 17:50:33 2026 GMT \end{minted} Caddy's automatic certificate management is also responsible for renewal, so a separate manual renewal job is not required. \section{Firewall Configuration} The DigitalOcean Cloud Firewall was configured to allow the ports required by the deployment: \begin{center} \begin{tabular}{lll} \textbf{Protocol} & \textbf{Port} & \textbf{Source} \\ TCP & 22 & All IPv4 \\ TCP & 80 & All IPv4 and All IPv6 \\ TCP & 443 & All IPv4 and All IPv6 \end{tabular} \end{center} Port 80 is required for normal HTTP access and HTTP-to-HTTPS redirection, while port 443 provides the public HTTPS service. SSH on port 22 is used for administration. Port 3000 is not opened in the DigitalOcean firewall. An external connection test to the public address on port 3000 timed out, while external access to ports 80 and 443 succeeded. This confirms that Gitea is reached through Caddy rather than directly through its application port. \section{Restart Behavior} The Compose service uses: \begin{minted}{yaml} restart: unless-stopped \end{minted} This instructs Docker to restart Gitea after an unexpected container or Docker daemon restart, while still allowing an intentional manual stop to remain stopped. The deployment can therefore be brought back up with: \begin{minted}{bash} docker compose up -d \end{minted} and its state checked with: \begin{minted}{bash} docker compose ps \end{minted} \section{Deployment Summary} The completed startup work hub has the following architecture: \begin{enumerate} \item \texttt{ssw590.tech} was registered using the GitHub Student Developer Pack offer through Name.com. \item \texttt{git.ssw590.tech} was configured to resolve to the DigitalOcean Droplet. \item Gitea \texttt{1.27.3} was deployed using the official \texttt{gitea/gitea} image. \item SQLite was selected for the Gitea database. \item Gitea application data was stored in the persistent \texttt{./data/gitea:/data} bind mount. \item Container replacement was tested and the repository and issue data persisted. \item The \texttt{ssw590-startup} repository was created for team collaboration. \item Team members were added and issues were assigned. \item Pull requests were used to review and merge work into \texttt{main}. \item Caddy was placed in front of Gitea as a reverse proxy. \item Let's Encrypt provided the live TLS certificate for \texttt{git.ssw590.tech}. \item HTTP was redirected to HTTPS. \item Port 3000 remained closed to external clients. \end{enumerate} The resulting deployment provides the team with a persistent, version-pinned, HTTPS-protected Git service suitable for the startup's initial development workflow.