458 lines
23 KiB
TeX
458 lines
23 KiB
TeX
\chapter{Startup Foundation with Gitea}
|
|
\ChapterLabel{GiteaFoundation}
|
|
\index{Gitea}\index{SQLite}\index{Docker Compose}\index{Elastic IP}
|
|
|
|
This chapter records the Inviscid work hub on the existing Amazon EC2 server.
|
|
AWS is used in place of the assignment's DigitalOcean deployment. Victor
|
|
completed the domain, DNS, Gitea deployment, and account setup; Payton completed
|
|
HTTPS, the firewall, persistence testing, and the remaining workflow evidence.
|
|
|
|
\section{Student Pack Domain and DNS}
|
|
After GitHub Education approved Victor's Student Developer Pack application on
|
|
October 12, partner benefits activated on October 15. Victor then redeemed
|
|
Name.com's Student Pack offer. It covered selected extensions rather than the
|
|
preferred \texttt{inviscid.net}, so he registered \texttt{inviscid.dev} for one
|
|
year. No registrar-sold SSL product was needed, because Caddy obtains the
|
|
certificate (Table~\ref{Table:GiteaCertOptions}).
|
|
|
|
\begin{tabularx}{\linewidth}{L{3.8cm}Y}
|
|
\caption{Student Domain and DNS Record\label{Table:GiteaDomain}}\\
|
|
\toprule \textbf{Field} & \textbf{Recorded value}\\ \midrule
|
|
Provider and eligibility & Name.com, redeemed through the activated GitHub Student Developer Pack benefit\\
|
|
Registered domain & \texttt{inviscid.dev}\\
|
|
Registration date / term & October 15, 2026 / one year\\
|
|
Initial charge & \$0.00\\
|
|
Displayed renewal price & \$22.99; verify the current price before renewal\\
|
|
Renewal date & October 15, 2027\\
|
|
Renewal controls & Automatic renewal ON; WHOIS privacy ON; transfer lock enabled; Victor tracks renewal and keeps payment details current\\
|
|
DNS record & A, host \texttt{git}, answer \texttt{100.49.80.94} (Elastic IP), TTL 300 seconds\\
|
|
Public service URL & \url{https://git.inviscid.dev/}\\
|
|
\bottomrule
|
|
\end{tabularx}
|
|
|
|
\begin{figure}[htbp]
|
|
\centering
|
|
\includegraphics[width=\linewidth]{figures/gitea/domain-free-checkout.png}
|
|
\caption{October 15: one-year inviscid.dev registration at a checkout total of zero.}
|
|
\label{Figure:GiteaDomainCheckout}
|
|
\end{figure}
|
|
\begin{figure}[htbp]
|
|
\centering
|
|
\includegraphics[width=\linewidth]{figures/gitea/domain-renewal-details.png}
|
|
\caption{October 15: registered domain, renewal date and price, auto-renewal, privacy, and transfer-lock settings.}
|
|
\label{Figure:GiteaDomainRenewal}
|
|
\end{figure}
|
|
|
|
Victor added the A record in Name.com's DNS manager
|
|
(Figure~\ref{Figure:GiteaDNSRecord}) and verified it from outside EC2 at 19:17 EDT
|
|
on October 15: both Cloudflare (1.1.1.1) and Google (8.8.8.8) resolvers returned
|
|
\texttt{100.49.80.94} (Figure~\ref{Figure:GiteaDNSLookup}).
|
|
\begin{figure}[htbp]
|
|
\centering
|
|
\includegraphics[width=\linewidth]{figures/gitea/dns-a-record.png}
|
|
\caption{October 15: saved A record for git.inviscid.dev pointing to 100.49.80.94 with a 300-second TTL.}
|
|
\label{Figure:GiteaDNSRecord}
|
|
\end{figure}
|
|
\begin{figure}[htbp]
|
|
\centering
|
|
\includegraphics[width=\linewidth]{figures/gitea/dns-independent-lookups.png}
|
|
\caption{October 15: independent Cloudflare and Google resolver lookups both return the expected Elastic IP.}
|
|
\label{Figure:GiteaDNSLookup}
|
|
\end{figure}
|
|
|
|
\section{Pinned Gitea Deployment}
|
|
Gitea runs on the existing \texttt{ssw590-group-web} instance (Ubuntu 26.04 LTS,
|
|
\texttt{t3.micro}, \texttt{us-east-1}), whose Elastic IP keeps the DNS record
|
|
valid across restarts. Victor deployed Gitea privately on October 12. On
|
|
October 15, Payton added Caddy as the HTTPS endpoint and recreated the existing
|
|
Color Buttons container without a published port on \texttt{inviscid\_services},
|
|
so that Caddy could take port 80; the recreate command is in the deployment
|
|
README.
|
|
|
|
\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}\\
|
|
Public / private address & Elastic IP \texttt{100.49.80.94} / \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\\
|
|
Public URL setting & \url{https://git.inviscid.dev/}\\
|
|
TLS proxy & \texttt{caddy:2.10.2}, container \texttt{inviscid-caddy-1}; the only service publishing host ports 80 and 443\\
|
|
\bottomrule
|
|
\end{tabularx}
|
|
|
|
Both images are pinned to version tags rather than \texttt{latest}. The recorded
|
|
Gitea 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://git.inviscid.dev/Inviscid/SSW590-DevOps/src/branch/main/docker/gitea}{the team's versioned deployment directory}
|
|
in the Gitea repository. The deployed Compose file and Caddyfile follow.
|
|
\inputminted{yaml}{docker/gitea/compose.yaml}
|
|
\inputminted{text}{docker/gitea/Caddyfile}
|
|
|
|
To repeat the installation, copy the directory to the server and run, from it:
|
|
\begin{minted}{bash}
|
|
cd ~/gitea
|
|
sudo docker compose config --quiet
|
|
sudo docker compose pull
|
|
sudo docker compose up -d
|
|
sudo docker compose ps
|
|
\end{minted}
|
|
Validation returned without an error and the pinned image pulled
|
|
(Figure~\ref{Figure:GiteaPull}).
|
|
\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}
|
|
|
|
\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. SQLite suits this team's size but
|
|
may not fit a larger, higher-concurrency deployment.
|
|
|
|
\subsection{Container-Replacement Test}
|
|
On October 15 at 21:34 EDT, Payton replaced only the Gitea container, using
|
|
repository \texttt{Inviscid/SSW590-DevOps} and issue \#3 as the recorded data.
|
|
Volumes were not removed:
|
|
\begin{minted}{bash}
|
|
sudo docker compose up -d --force-recreate --no-deps gitea
|
|
\end{minted}
|
|
The container ID changed from \texttt{915c6b6d88d2} to \texttt{82ca41970439},
|
|
confirming that the container and its writable layer were replaced. The same
|
|
volume remained mounted at \path{/data}, the 2.2 MiB database file was still
|
|
present (Figure~\ref{Figure:GiteaPersistence}), and the repository and issue \#3
|
|
were unchanged and usable (Figure~\ref{Figure:GiteaPersistenceIssue}). The data
|
|
survived because Compose reattached the named volume. This is a persistence
|
|
test, not a backup; see the Architecture and Operation section.
|
|
\begin{figure}[htbp]
|
|
\centering
|
|
\includegraphics[width=\linewidth]{figures/gitea/persistencetest.png}
|
|
\caption{October 15: container ID, mount, replacement command, new container ID, and database file.}
|
|
\label{Figure:GiteaPersistence}
|
|
\end{figure}
|
|
\begin{figure}[htbp]
|
|
\centering
|
|
\begin{minipage}{0.49\linewidth}\centering
|
|
\includegraphics[width=\linewidth]{figures/gitea/beforeissue.png}\\ \small Before
|
|
\end{minipage}\hfill
|
|
\begin{minipage}{0.49\linewidth}\centering
|
|
\includegraphics[width=\linewidth]{figures/gitea/afterissue.png}\\ \small After
|
|
\end{minipage}
|
|
\caption{October 15: issue \#3 before and after the Gitea container replacement.}
|
|
\label{Figure:GiteaPersistenceIssue}
|
|
\end{figure}
|
|
|
|
\section{HTTPS and Certificate Renewal}
|
|
\index{Caddy}\index{Let's Encrypt}
|
|
\subsection{Certificate Options}
|
|
Both options below use Let's Encrypt, a free certificate authority that issues
|
|
90-day certificates through \gls{acme}. A paid or registrar-sold certificate was
|
|
rejected because it needs manual installation and renewal.
|
|
|
|
\begin{tabularx}{\linewidth}{L{2.8cm}YY}
|
|
\caption{Certificate Options\label{Table:GiteaCertOptions}}\\
|
|
\toprule \textbf{Criterion} & \textbf{Caddy automatic HTTPS (selected)} & \textbf{certbot with nginx}\\ \midrule
|
|
Issuance and renewal & Built into the proxy; no extra job & Separate certbot client and renewal timer\\
|
|
Configuration & One Caddyfile site block; HTTP-to-HTTPS redirect is automatic & nginx server blocks plus certbot plugin\\
|
|
Containers & One proxy container & Proxy plus certbot, or certbot on the host\\
|
|
Renewal test & No dry-run command; forced reissue used instead & \texttt{certbot renew --dry-run}\\
|
|
\bottomrule
|
|
\end{tabularx}
|
|
|
|
\Gls{caddy} was selected for fewer moving parts and secure defaults. Its main
|
|
drawback is the lack of a dry-run renewal command.
|
|
|
|
\subsection{TLS Termination and Evidence}
|
|
\gls{tls} terminates at Caddy. Caddy forwards plain HTTP to \texttt{gitea:3000}
|
|
over the private Docker network \texttt{inviscid\_services}; that traffic never
|
|
leaves the instance. Port 80 is open for HTTP-to-HTTPS redirects and ACME
|
|
validation.
|
|
|
|
Caddy obtained the Let's Encrypt certificate for \texttt{git.inviscid.dev} at
|
|
19:52 EDT on October 15; the current certificate is valid until January 13, 2027
|
|
(90 days). From Payton's laptop, \texttt{http://git.inviscid.dev} returned
|
|
\texttt{308 Permanent Redirect} to \url{https://git.inviscid.dev/}, and the HTTPS
|
|
URL returned HTTP/2 200 (Figure~\ref{Figure:GiteaExternalTests}). The
|
|
\texttt{.dev} top-level domain is on browsers' HSTS preload list, so browsers
|
|
never request plain HTTP for this host; the redirect matters for non-browser
|
|
clients and was tested with \texttt{curl}.
|
|
|
|
\subsection{Renewal Automation and Test}
|
|
Caddy renews managed certificates automatically in the background, by default
|
|
when about one third of the lifetime remains (around mid-December 2026 for
|
|
this certificate). No cron job or manual command is required. Certificate and
|
|
ACME account state persist in \texttt{inviscid\_caddy\_data}, so replacing the
|
|
Caddy container does not force reissuance.
|
|
|
|
Because Caddy has no dry-run command, Payton forced a reissue by deleting the
|
|
stored certificate and restarting Caddy. This exercises the same unattended
|
|
ACME path that renewal uses:
|
|
\begin{minted}{bash}
|
|
sudo docker compose exec caddy rm -rf \
|
|
/data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/git.inviscid.dev
|
|
sudo docker compose restart caddy
|
|
sudo docker compose logs --since 1m caddy | grep -i obtained
|
|
\end{minted}
|
|
Caddy logged \texttt{certificate obtained successfully} without manual
|
|
intervention, and the serial changed from \texttt{06ADFF\ldots F6A7} to
|
|
\texttt{06CA6A\ldots 034F} with a new validity window
|
|
(Figure~\ref{Figure:GiteaRenewal}). This was a forced reissue, not an observed
|
|
natural renewal, and was run once to stay within Let's Encrypt's
|
|
duplicate-certificate limit.
|
|
\begin{figure}[htbp]
|
|
\centering
|
|
\includegraphics[width=\linewidth]{figures/gitea/caddy.png}
|
|
\caption{October 15: certificate serial and validity before and after the forced reissue, with Caddy's log entry.}
|
|
\label{Figure:GiteaRenewal}
|
|
\end{figure}
|
|
|
|
\section{Firewall and External Exposure}
|
|
The only security group attached to the instance is \texttt{ssw590-web-sg}
|
|
(\texttt{sg-0a9eb6dcfc906e78b}); Figure~\ref{Figure:GiteaArchitecture} shows the
|
|
resulting network paths. Team source addresses are partially redacted.
|
|
\begin{tabularx}{\linewidth}{L{1.6cm}L{3.4cm}Y}
|
|
\toprule \textbf{Inbound TCP} & \textbf{Source} & \textbf{Purpose}\\ \midrule
|
|
443 & \texttt{0.0.0.0/0} & HTTPS to Caddy for \texttt{git.inviscid.dev}\\
|
|
80 & \texttt{0.0.0.0/0} & HTTP-to-HTTPS redirect, ACME validation, Color Buttons by IP\\
|
|
22 & \texttt{100.8.x.x/32} & Victor, home\\
|
|
22 & \texttt{155.246.x.x/32} & Victor, campus\\
|
|
22 & \texttt{108.35.x.x/32} & Payton\\
|
|
\bottomrule
|
|
\end{tabularx}
|
|
|
|
Outbound allows all traffic to \texttt{0.0.0.0/0}. The server needs outbound
|
|
access for package and image downloads, DNS, time synchronization, and Let's
|
|
Encrypt; the group is stateful, so replies to allowed inbound connections are
|
|
permitted. The instance has no IPv6 address, so there are no IPv6 rules. Docker
|
|
also binds ports 80 and 443 on IPv6, but they are unreachable without an IPv6
|
|
address. The subnet's network ACL (\texttt{acl-06bdf6081fd8b9d5d}) is the default
|
|
allow-all in both directions, so the security group is the effective filter
|
|
(Figures~\ref{Figure:GiteaFirewall} and~\ref{Figure:GiteaNACL}).
|
|
|
|
The Color Buttons app from an earlier assignment is still served over plain
|
|
HTTP by IP, which does not meet the hostname-based HTTPS rule. It is kept
|
|
because the instructor expects the instance and this app to stay reachable; it
|
|
no longer publishes its own port, and giving it an HTTPS hostname is future work.
|
|
|
|
\begin{figure}[htbp]
|
|
\centering
|
|
\includegraphics[width=\linewidth]{figures/gitea/security.png}
|
|
\caption{October 15: the single attached security group with final inbound and outbound rules.}
|
|
\label{Figure:GiteaFirewall}
|
|
\end{figure}
|
|
\begin{figure}[htbp]
|
|
\centering
|
|
\includegraphics[width=\linewidth]{figures/gitea/subnets.png}
|
|
\caption{October 15: default network ACL on the instance's subnet.}
|
|
\label{Figure:GiteaNACL}
|
|
\end{figure}
|
|
|
|
\subsection{Host and Container Exposure}
|
|
Docker-published ports can bypass host firewall (ufw) rules, so the security
|
|
group is the public boundary. Figure~\ref{Figure:GiteaPorts} shows that only
|
|
\texttt{docker-proxy} (Caddy) listens on 80 and 443 and \texttt{sshd} on 22.
|
|
Gitea's port 3000 listens on \texttt{127.0.0.1} only, and the Color Buttons
|
|
container and Caddy's admin port 2019 have no host mapping.
|
|
\begin{figure}[htbp]
|
|
\centering
|
|
\includegraphics[width=\linewidth]{figures/gitea/ports.png}
|
|
\caption{October 15: Docker port mappings and listening sockets on EC2.}
|
|
\label{Figure:GiteaPorts}
|
|
\end{figure}
|
|
|
|
\subsection{External Exposure Test}
|
|
Tests ran from Payton's laptop on October 15, outside EC2, using
|
|
\texttt{nc -vz -w 5} and \texttt{curl}:
|
|
\begin{tabularx}{\linewidth}{L{1.5cm}L{3.3cm}L{2.6cm}Y}
|
|
\toprule \textbf{Port} & \textbf{Source (EDT)} & \textbf{Expected} & \textbf{Actual}\\ \midrule
|
|
443 & \texttt{108.35.x.x}, 21:53 & open & connected; HTTP/2 200\\
|
|
80 & \texttt{108.35.x.x}, 21:53 & open, redirect & connected; 308 to HTTPS; IP returns 200 (Color Buttons)\\
|
|
22 & \texttt{108.35.x.x}, 21:53 & open (allowed) & connected\\
|
|
3000 & \texttt{108.35.x.x}, 21:53 & blocked & timed out\\
|
|
222 & \texttt{108.35.x.x}, 21:53 & blocked & timed out\\
|
|
2019 & \texttt{108.35.x.x}, 21:53 & blocked & timed out\\
|
|
22 & \texttt{174.206.x.x} (phone hotspot), 21:53--21:56 & blocked & timed out\\
|
|
\bottomrule
|
|
\end{tabularx}
|
|
All results matched expectations, so no corrections were needed. The laptop was
|
|
switched to the hotspot before the final SSH test; \texttt{checkip} afterwards
|
|
confirmed the hotspot address. Timeouts, rather than refusals, show that the
|
|
security group drops the traffic before it reaches the instance.
|
|
\begin{figure}[htbp]
|
|
\centering
|
|
\includegraphics[width=\linewidth]{figures/gitea/laptop.png}
|
|
\caption{October 15: external port, redirect, and HTTPS tests from an allowed source and from a non-allowed hotspot.}
|
|
\label{Figure:GiteaExternalTests}
|
|
\end{figure}
|
|
|
|
HTTPS encrypts connections that the firewall admits; it does not close any
|
|
port. Without the security group, Gitea's port, SSH, or any accidentally
|
|
published container port could be reached directly.
|
|
|
|
\section{Startup Team Workflow}
|
|
Victor initialized Gitea with site title \texttt{Inviscid}, created the
|
|
\texttt{victor} site administrator, and disabled public self-registration. He
|
|
created a separate regular \texttt{payton} account with a temporary password,
|
|
which Payton changed after first login; no shared Gitea account is used.
|
|
|
|
\subsection{Organization, Repository, and Roles}
|
|
Victor created organization \texttt{Inviscid} and added \texttt{payton} to its
|
|
Owners team, so both members administer the organization and its repositories
|
|
(Figure~\ref{Figure:GiteaOwners}). Victor leads domain, DNS, and deployment
|
|
work; Payton leads HTTPS, firewall, persistence, and repository workflow.
|
|
Victor then pushed the existing project history to the product repository,
|
|
\texttt{Inviscid/SSW590-DevOps}. Its root \texttt{README.md}, added in PR 7,
|
|
describes the product, repository layout, team roles, and contribution workflow
|
|
(Figure~\ref{Figure:GiteaReadme}). The organization and repository are at
|
|
\url{https://git.inviscid.dev/Inviscid} and
|
|
\url{https://git.inviscid.dev/Inviscid/SSW590-DevOps}.
|
|
\begin{figure}[htbp]
|
|
\centering
|
|
\includegraphics[width=\linewidth]{figures/gitea/organization-owners.png}
|
|
\caption{October 15: victor and payton are members of the Inviscid Owners team.}
|
|
\label{Figure:GiteaOwners}
|
|
\end{figure}
|
|
\begin{figure}[htbp]
|
|
\centering
|
|
\includegraphics[width=\linewidth]{figures/gitea/readme.png}
|
|
\caption{October 15: product repository README.}
|
|
\label{Figure:GiteaReadme}
|
|
\end{figure}
|
|
|
|
\subsection{Backlog}
|
|
The backlog contains five actionable issues, each with acceptance criteria:
|
|
\begin{tabularx}{\linewidth}{L{0.6cm}YL{2.1cm}L{2.5cm}}
|
|
\toprule \textbf{\#} & \textbf{Issue} & \textbf{Type} & \textbf{Status}\\ \midrule
|
|
1 & Document the initial Gitea deployment and team access & Operations & Closed (PR 2)\\
|
|
3 & Finalize README.md (product and deployment READMEs) & Product & Closed (PR 7)\\
|
|
4 & Enable HTTPS for git.inviscid.dev & Operations & Closed (PR 7)\\
|
|
5 & Configure EC2 firewall & Operations & Closed after tests\\
|
|
6 & Version the Color Buttons app in the repository & Product & Open\\
|
|
\bottomrule
|
|
\end{tabularx}
|
|
|
|
\subsection{Issue-Linked Pull Requests}
|
|
Gitea numbers issues and pull requests from one sequence, so the two PRs are
|
|
2 and 7.
|
|
|
|
\paragraph{PR 2 (Victor, reviewed by Payton).} Victor documented the initial
|
|
deployment on branch \texttt{codex/gitea-foundation-docs} and opened PR 2 with
|
|
\texttt{Closes \#1}, requesting Payton's review. Payton reviewed the changes,
|
|
approved, and merged the PR, which closed issue 1
|
|
(Figure~\ref{Figure:GiteaPR2}).
|
|
\begin{figure}[htbp]
|
|
\centering
|
|
\includegraphics[width=\linewidth]{figures/gitea/pr2merge.png}
|
|
\caption{October 15: PR 2 approved by Payton and merged, closing issue 1.}
|
|
\label{Figure:GiteaPR2}
|
|
\end{figure}
|
|
|
|
\paragraph{PR 7 (Payton, reviewed by Victor).} Payton authored PR 7 from
|
|
\texttt{payton/caddy-https}. It adds the root README, the Caddy service and
|
|
Caddyfile, the HTTPS URL settings, and the updated deployment README, with
|
|
\texttt{Closes \#3} and \texttt{Closes \#4}. Victor reviewed and approved it,
|
|
and Payton merged it as commit \texttt{1d7c528795}, which closed both issues
|
|
(Figure~\ref{Figure:GiteaPR7}). The initial \texttt{main} push is not counted
|
|
as a reviewed merge.
|
|
\begin{figure}[htbp]
|
|
\centering
|
|
\includegraphics[width=\linewidth]{figures/gitea/mypr.png}
|
|
\caption{October 15: PR 7 linked to issues 3 and 4, approved by Victor, and merged.}
|
|
\label{Figure:GiteaPR7}
|
|
\end{figure}
|
|
|
|
\section{Architecture and Operation}
|
|
Figure~\ref{Figure:GiteaArchitecture} shows the deployed request and data paths.
|
|
\begin{figure}[htbp]
|
|
\centering
|
|
\resizebox{\linewidth}{!}{\input{figures/devopsGiteaFoundation/giteaArchitecture}}
|
|
\caption{October 15: deployed architecture. The security group admits 443 and 80 from any source and 22 only from approved team addresses. Caddy is the only container publishing host ports; Gitea, the Color Buttons app, and both data volumes are private.}
|
|
\label{Figure:GiteaArchitecture}
|
|
\end{figure}
|
|
|
|
\paragraph{Where data lives.} Repositories, attachments, Gitea configuration,
|
|
and the SQLite database (\path{/data/gitea/gitea.db}) live in the named volume
|
|
\texttt{inviscid\_gitea\_data}. Certificates and the ACME account live in
|
|
\texttt{inviscid\_caddy\_data} (with \texttt{inviscid\_caddy\_config}). All
|
|
three volumes are on the instance's EBS disk, outside the disposable container
|
|
layers. Deployment files are versioned in \path{docker/gitea/} of the repository.
|
|
|
|
\paragraph{Public and private interfaces.} Public: TCP 443 (HTTPS to Caddy) and
|
|
TCP 80 (redirect to HTTPS, ACME validation, and the Color Buttons app by IP),
|
|
both from any source. Restricted: TCP 22 (administrative SSH) from approved team
|
|
addresses only. Private: Gitea's port 3000 (reachable only on the Compose network
|
|
and EC2 loopback, for SSH-tunnel administration), the Color Buttons container's
|
|
port 3000, Caddy's admin port 2019, and the SQLite file, which has no network port.
|
|
|
|
\paragraph{Restart and troubleshooting.} From \path{~/gitea} on EC2:
|
|
\begin{minted}{bash}
|
|
sudo docker compose ps
|
|
sudo docker compose logs --tail=40 caddy
|
|
sudo docker compose logs --tail=40 gitea
|
|
sudo docker compose restart gitea # or caddy; volumes are kept
|
|
\end{minted}
|
|
If \url{https://git.inviscid.dev} fails, check in order: (1) both services are
|
|
running; (2) the Caddy log shows no certificate or upstream errors; (3) the Gitea
|
|
log shows it listening on port 3000; (4) \texttt{curl -I http://127.0.0.1:3000/}
|
|
on EC2 returns 200, which isolates Gitea from Caddy; (5) DNS still resolves to
|
|
the Elastic IP and the security group still admits 443.
|
|
|
|
\paragraph{Persistence test versus backup/restore.} The container-replacement
|
|
test shows that data survives when the Gitea \emph{container} is discarded,
|
|
because the named volume is reattached. It does not protect against loss of the
|
|
volume, the EBS disk, or the instance, and it creates no copy elsewhere. A
|
|
backup/restore test would copy the volume and database to separate storage and
|
|
prove recovery into a different instance; that optional exercise was not
|
|
performed.
|
|
|
|
\paragraph{Costs and cleanup.} 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.
|
|
The domain renews automatically on October 15, 2027 unless auto-renewal is
|
|
turned off.
|
|
|
|
\section{Manual Quality and Controls}
|
|
This version updates the Hosts and Passwords tables, as recorded in the
|
|
document history. No passwords, private keys, database contents, or generated
|
|
\texttt{app.ini} are included, and team IP addresses are redacted. Material AI
|
|
help is recorded in \ChapterRef{AIPromptLog}.
|
|
|
|
\paragraph{Failed attempts and corrective actions.}
|
|
\begin{itemize}[nosep]
|
|
\item Victor's SSH from campus timed out after his public IP changed; a
|
|
campus-specific rule was added, and his later access succeeded.
|
|
\item Payton's first SSH login failed (\texttt{Permission denied (publickey)});
|
|
Victor authorized Payton's public key.
|
|
\item EC2 Instance Connect failed because the team's AWS role lacks
|
|
\texttt{ec2-instance-connect:SendSSHPublicKey}; the temporary security-group
|
|
rule added for it was removed immediately.
|
|
\item Caddy could not use port 80 while Color Buttons published it; the app was
|
|
moved behind Caddy on the private network.
|
|
\item The server's \texttt{compose.yaml} differed from the repository copy by a
|
|
stray Windows line ending; the file was rewritten with LF endings, and the
|
|
checksums now match.
|
|
\end{itemize}
|
|
|
|
\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://caddyserver.com/docs/running\#docker-compose}{Caddy with Docker Compose};
|
|
\href{https://caddyserver.com/docs/automatic-https}{Caddy automatic HTTPS};
|
|
\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}.
|