Files
SSW590-DevOps/chapters/devopsGiteaFoundation.tex
T

284 lines
15 KiB
TeX

\chapter{Startup Foundation with Gitea}
\ChapterLabel{GiteaFoundation}
\index{Gitea}\index{SQLite}\index{Docker Compose}\index{Elastic IP}\index{SSH tunnel}
This chapter records the initial Inviscid work hub on the existing Amazon EC2
server. AWS is used in place of the assignment's DigitalOcean deployment.
The dated screenshots record completed work; pending items below are not
represented as passed. This draft covers Victor's deployment and access setup.
\section{Student Pack Domain and DNS}
Victor submitted school enrollment evidence to GitHub Education on October 12,
2026. Academic status was approved that day, but the portal displayed
``Awaiting Benefits'' and stated that partner offers would activate after
72 hours (Figure~\ref{Figure:GiteaBenefits}). At the October 15 drafting
checkpoint, Victor reported that benefits were still unavailable.
\textbf{Pending:} redeem an eligible Student Pack domain offer; record the
provider, eligibility, first-year charge, renewal charge, expiration, and renewal
owner; configure the selected Gitea hostname and capture an independent DNS
lookup. \texttt{inviscid.net} is a preference, not a registered or verified
domain in this record. The selected offer must determine which extension is
eligible. The intended DNS A record will point the selected hostname to
\texttt{100.49.80.94}. Domain ownership and renewal will be recorded when the
offer is redeemed; no registration cost or expiry is invented here.
\begin{figure}[htbp]
\centering
\includegraphics[width=\linewidth]{figures/gitea/student-pack-approved-awaiting-benefits.png}
\caption{October 12: academic status approved, with partner benefits still awaiting activation. This establishes the dependency blocking domain redemption, not a completed registration.}
\label{Figure:GiteaBenefits}
\end{figure}
\section{Pinned Gitea Deployment}
The existing \texttt{ssw590-group-web} instance runs Ubuntu 26.04 LTS on a
\texttt{t3.micro} in \texttt{us-east-1}. The initial resource check recorded
908 MiB total memory, 468 MiB available memory, and 3.1 GiB available root disk.
Docker Compose reported version 5.6.0. The existing Color Buttons container
continued to serve host port 80 while Gitea was initialized privately.
\begin{tabularx}{\linewidth}{L{3.5cm}Y}
\caption{Recorded Gitea Deployment\label{Table:GiteaDeployment}}\\
\toprule \textbf{Setting} & \textbf{Value}\\ \midrule
Instance & \texttt{i-025a57432604c8d4b}, \texttt{ssw590-group-web}\\
Current public address & Elastic IP \texttt{100.49.80.94}\\
Private address & \texttt{172.31.19.91}\\
Official image & \texttt{docker.gitea.com/gitea:28.1.0}\\
Container & \texttt{inviscid-gitea-1}\\
Remote working directory & \path{/home/ubuntu/gitea}\\
Persistent volume & \texttt{inviscid\_gitea\_data}, mounted at \path{/data}\\
Docker network & \texttt{inviscid\_services}\\
Restart policy & \texttt{unless-stopped}\\
Published backend & \texttt{127.0.0.1:3000:3000}; no public backend mapping\\
Temporary public URL setting & \url{http://localhost:3000/}; SSH-tunnel access only\\
\bottomrule
\end{tabularx}
The official Docker installation guide supports persistent application storage
and SQLite initialization. We pinned a version rather than using a floating
\texttt{latest} tag. The recorded repository digest is:
\begin{minted}[fontsize=\scriptsize]{text}
docker.gitea.com/gitea@sha256:f505a0d3a41323b786e05942fc7a4075e07ae7c169fd9485649aa55ee543a21a
\end{minted}
The non-secret configuration is maintained in
\href{https://github.com/byterand/SSW590-DevOps/tree/main/docker/gitea}{the team's versioned deployment directory}.
The following file is the initial localhost-only configuration. Its URL settings
must be updated for the registered HTTPS hostname before final submission.
\inputminted{yaml}{docker/gitea/compose.yaml}
Victor copied the directory from the Windows workstation to EC2 using
\texttt{scp}. In the remote directory, he validated, pulled, and started it:
\begin{minted}{bash}
cd ~/gitea
sudo docker compose config --quiet
sudo docker compose pull
sudo docker compose up -d
sudo docker compose ps
sudo docker compose logs --tail=40 gitea
\end{minted}
Validation returned without an error, the pinned image pulled, and Compose
created the volume, network, and container. Startup logs showed Gitea 28.1.0
listening internally on port 3000 and preparing its installation page.
\begin{figure}[htbp]
\centering
\includegraphics[width=\linewidth]{figures/gitea/compose-validation-pull.png}
\caption{October 12: successful Compose validation and pull of the pinned official image.}
\label{Figure:GiteaPull}
\end{figure}
\begin{figure}[htbp]
\centering
\includegraphics[width=\linewidth]{figures/gitea/container-startup.png}
\caption{October 12: container startup and localhost port mapping. The image exposes an internal SSH port, but no host SSH mapping was published for Gitea.}
\label{Figure:GiteaStartup}
\end{figure}
Victor associated the Elastic IP with the same instance, replacing the earlier
\texttt{54.221.175.174} address. The containers remained running. SSH and tunnel
commands were updated to use the new address; the data and private address did
not change.
\begin{figure}[htbp]
\centering
\includegraphics[width=\linewidth]{figures/gitea/elastic-ip-association.png}
\caption{October 12: Elastic IP 100.49.80.94 associated with the existing instance.}
\label{Figure:GiteaElasticIP}
\end{figure}
\section{Database and Persistence}
SQLite was selected for the small two-person deployment. It requires no
separate database service or network port. The database is at
\path{/data/gitea/gitea.db}, within the same persistent named volume that holds
application configuration and repository data. The volume exists outside the
disposable container layer. Generated configuration, database contents, and
secrets are not stored in the Git repository.
The following checks were executed on October 12:
\begin{minted}{bash}
sudo docker inspect inviscid-gitea-1 --format '{{range .Mounts}}{{println .Type .Name .Destination}}{{end}}'
sudo docker compose exec -T gitea ls -lh /data/gitea/gitea.db
sudo docker stats --no-stream
free -h
df -h /
\end{minted}
The mount check showed \texttt{inviscid\_gitea\_data} at \path{/data}; the
SQLite file existed and was approximately 2.1 MiB. Gitea used approximately
187 MiB RAM in that snapshot, with 367 MiB system memory and 2.8 GiB disk
available. These are single observations, not load-test results.
\begin{figure}[htbp]
\centering
\includegraphics[width=\linewidth]{figures/gitea/storage-resources.png}
\caption{October 12: named-volume mount, database file, and resource checks. These verify storage configuration, not survival of a container replacement.}
\label{Figure:GiteaStorage}
\end{figure}
\textbf{Pending---Payton:} record a repository and issue, replace only the
Gitea container without deleting its volume, and show that both remain usable.
Include identifiers, exact command, before/after evidence, and conclusion.
This required test is distinct from the optional backup/restore exercise.
\section{HTTPS and Certificate Renewal}
\textbf{Pending---Payton:} compare two certificate options and justify one;
configure Caddy using the final hostname, persistent certificate state, and
private forwarding to \texttt{gitea:3000}; update Gitea's domain and public URL;
record issuer, expiry, redirect, renewal automation, and a successful renewal
test or dry run. The existing website's host-port-80 mapping must be resolved
before Caddy publishes that port. Public HTTPS is not claimed in this draft.
Initial access used an SSH tunnel from the workstation:
\begin{minted}{bash}
ssh -i /path/to/your-private-key -o ExitOnForwardFailure=yes -N -L 127.0.0.1:3000:127.0.0.1:3000 ubuntu@100.49.80.94
\end{minted}
The browser opened \url{http://localhost:3000/}; transport between workstation
and server was encrypted by SSH, but this is not hostname-based HTTPS and does
not satisfy the final certificate requirement. Each workstation needs its own
tunnel. The \texttt{-N} option keeps the connection open without a remote shell.
\section{Firewall and External Exposure}
The attached AWS security group is \texttt{ssw590-web-sg}, identifier
\texttt{sg-0a9eb6dcfc906e78b}. The October 15 screenshot records these IPv4 rules:
\begin{tabularx}{\linewidth}{L{2cm}L{4cm}Y}
\toprule \textbf{TCP port} & \textbf{Source} & \textbf{Purpose}\\ \midrule
22 & \texttt{100.8.28.98/32} & Victor's home SSH access\\
22 & \texttt{155.246.151.38/32} & Victor's campus source; connection still timed out during the reported attempt\\
22 & \texttt{108.35.228.38/32} & Payton's administrative SSH access\\
80 & \texttt{0.0.0.0/0} & Existing HTTP website; final proxy redirect/validation behavior pending\\
443 & \texttt{0.0.0.0/0} & Permitted for planned Caddy HTTPS; opening a rule does not verify a live HTTPS endpoint\\
\bottomrule
\end{tabularx}
No public IPv6 address was shown in the supplied EC2 summary. The provided
inbound-rule screenshot contains IPv4 rules only. Outbound rules, any other
attached security groups, and final external exposure require Payton's review.
\begin{figure}[htbp]
\centering
\includegraphics[width=\linewidth]{figures/gitea/security-group-team-access.png}
\caption{October 15: saved security-group rules, including Payton's source-specific SSH rule. This is configuration evidence, not an external port scan.}
\label{Figure:GiteaFirewall}
\end{figure}
During an October 13 campus connection attempt, SSH timed out. Victor's public
IP had changed from the home address; a campus-specific rule was added, but
that attempt still failed. Campus filtering was considered but not established.
On October 15 Victor reported successful SSH access; the exact network for
that successful session was not recorded. Rules should track current approved
source addresses rather than permitting public administrative SSH.
\textbf{Pending---Payton:} externally test 443, 80, restricted administrative
SSH, backend 3000, and database exposure; record source, time, expected and
actual results, and corrections. Document outbound rules and final Docker
publishing. HTTPS encrypts an allowed connection; it does not close unrelated
ports. Local HTTP 200 and loopback mappings do not independently prove public
firewall behavior.
\section{Startup Team Workflow}
Victor initialized Gitea with site title \texttt{Inviscid}, created the
\texttt{victor} administrator, and disabled public self-registration. A separate
\texttt{payton} regular account was created with a temporary password and
mandatory password change. A shared Gitea account is not used.
\begin{figure}[htbp]
\centering
\includegraphics[width=\linewidth]{figures/gitea/member-accounts.png}
\caption{October 12: separate victor and payton accounts; victor has the site-administrator role. This does not yet establish organization permissions or successful Payton login.}
\label{Figure:GiteaMembers}
\end{figure}
On October 15 Victor added Payton's public SSH key to the remote Ubuntu
account's authorized keys, preserving the existing key and using directory/file
permissions 700/600. His source IP was allowed separately. Victor subsequently
reported that Payton connected over SSH successfully; Gitea login testing was
still underway. Both currently use the Linux \texttt{ubuntu} account with
separate keys, so Linux account-level attribution remains shared.
\textbf{Pending---Payton and both authors:} confirm Payton's Gitea login and
password change; create the organization, product repository, README, member
roles, and five actionable issues. Link the organization and repository here.
Then each author completes an issue-linked branch and PR, receives peer review,
merges, and closes the issue. Capture both complete cycles. The initial GitHub
push is not counted as a reviewed Gitea PR.
\section{Architecture and Operation}
The current private initialization path is shown in
Figure~\ref{Figure:GiteaInitialPath}. Payton will add a final deployment diagram
showing browser HTTPS to Caddy and private forwarding to Gitea.
\begin{figure}[htbp]
\centering
\fbox{\parbox{0.92\linewidth}{\centering
Workstation browser: localhost:3000\\
$\downarrow$ local SSH tunnel\\
AWS security group: approved source to TCP 22\\
$\downarrow$ EC2: 100.49.80.94\\
EC2 loopback:3000 to Gitea container:3000\\
$\downarrow$ persistent named volume\\
\texttt{inviscid\_gitea\_data}: repositories, SQLite, configuration}}
\caption{Initial SSH-tunnel access path. Caddy and hostname-based HTTPS are not yet verified.}
\label{Figure:GiteaInitialPath}
\end{figure}
The completed local check \texttt{curl -I http://127.0.0.1:3000/} returned
HTTP 200. Logs, container status, and the image digest were collected without
printing configuration secrets.
\begin{figure}[htbp]
\centering
\includegraphics[width=\linewidth]{figures/gitea/http-image-digest.png}
\caption{October 12: local HTTP 200 and exact image digest.}
\label{Figure:GiteaHTTP}
\end{figure}
The following are operational commands, not additional claimed test results:
\begin{minted}{bash}
cd ~/gitea
sudo docker compose ps
sudo docker compose logs --tail=40 gitea
sudo docker compose restart gitea
\end{minted}
Closing a workstation tunnel does not stop Gitea on EC2. Reconnect the tunnel
for browser access. Container replacement should retain the named volume;
never delete that volume as part of the persistence check. Before destructive
cleanup, retain needed data. Victor owns the class AWS environment; compute,
EBS, and public IPv4 are billable and covered by budget alerts rather than a
spending cap. End-of-course cleanup must include the instance, unneeded storage,
and release of the Elastic IP; stopping the instance alone is not full cleanup.
\section{Manual Quality and Controls}
This revision adds the Gitea chapter, updates the Hosts inventory and Elastic IP
cleanup notes, and adds Gitea account and SSH-key handling hints to the Passwords
register. No passwords, private keys, database contents, or generated
\texttt{app.ini} are included. Existing security policies are distinguished
from observed deployment facts where necessary. Material AI help is recorded
in \ChapterRef{AIPromptLog}. The linked history entry identifies both table
updates. Optional backup/restore extra credit is outside this draft's scope.
\textbf{Pending before submission:} finish all pending sections, update DNS
and final Compose/proxy configuration, complete the required team workflow,
verify external exposure and renewal, compile the combined PDF, inspect its
pages and links, and submit only the PDF. No deadline time was supplied for
the professor's extension to Thursday, October 15, 2026; check Canvas.
\paragraph{Technical references.}
\href{https://docs.gitea.com/installation/install-with-docker/}{Gitea Docker installation};
\href{https://docs.gitea.com/administration/config-cheat-sheet/}{Gitea configuration};
\href{https://education.github.com/pack}{GitHub Student Developer Pack};
\href{https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/elastic-ip-addresses-eip.html}{AWS Elastic IP documentation}.