\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 current school-enrollment evidence to GitHub Education on October 12, 2026. Academic status was approved that day, while partner benefits remained in ``Awaiting Benefits'' status with a stated 72-hour activation delay (Figure~\ref{Figure:GiteaBenefits}). On October 15 the benefits became available, and Victor redeemed Name.com's GitHub Student Developer Pack offer. The offer covered selected extensions rather than the preferred \texttt{inviscid.net}. Victor selected \texttt{inviscid.dev}, registered it for one year, and supplied the completed order and domain-management evidence. The checkout total was \$0.00, with the free Domain Safe option; Domain Safe Pro and optional hosting/email upgrades were not selected. No registrar-provided SSL product is needed for the planned Caddy deployment. \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 current price before renewal\\ Displayed renewal date & October 15, 2027\\ Account owner / renewal responsibility & Victor; keep payment/contact details current and track renewal before the anniversary\\ Renewal and protection settings & Automatic renewal ON; WHOIS privacy ON; transfer lock enabled\\ Gitea hostname & \texttt{git.inviscid.dev}\\ DNS hosting & Name.com's DNS management; exact authoritative nameserver values not yet captured\\ DNS record & A, host \texttt{git}, answer \texttt{100.49.80.94}, TTL 300 seconds\\ Intended public service URL & \url{https://git.inviscid.dev/}; DNS verified, HTTPS still pending in this record\\ \bottomrule \end{tabularx} Victor manages registration and renewal; the registrar's automatic-renewal setting and October 2027 anniversary provide the recorded renewal controls. Domain registration and DNS are independent of the class AWS account hosting the application. Prices can change, so the table records the displayed price, not a guarantee of the eventual renewal charge. \begin{figure}[htbp] \centering \includegraphics[width=\linewidth]{figures/gitea/student-pack-approved-awaiting-benefits.png} \caption{October 12: academic approval preceded partner-benefit activation. The delay was resolved before domain registration on October 15.} \label{Figure:GiteaBenefits} \end{figure} \begin{figure}[htbp] \centering \includegraphics[width=0.62\linewidth,height=0.60\textheight,keepaspectratio]{figures/gitea/student-pack-domain-offer.png} \caption{October 15: Name.com Student Pack offer for selected domain extensions.} \label{Figure:GiteaDomainOffer} \end{figure} \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, with paid Domain Safe Pro unselected.} \label{Figure:GiteaDomainCheckout} \end{figure} \begin{figure}[htbp] \centering \includegraphics[width=\linewidth]{figures/gitea/domain-renewal-details.png} \caption{October 15: registered domain, October 15, 2027 renewal date, displayed renewal price, auto-renewal, privacy, and transfer-lock settings.} \label{Figure:GiteaDomainRenewal} \end{figure} In Name.com's Manage DNS page, Victor added an A record with host \texttt{git}; the interface appended \texttt{.inviscid.dev}. The answer is the EC2 Elastic IP rather than the old auto-assigned address. The saved record and creation date are visible in Figure~\ref{Figure:GiteaDNSRecord}. \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} Victor independently verified the record from Windows PowerShell, outside EC2, using two public DNS resolvers: \begin{minted}{powershell} Get-Date -Format o Resolve-DnsName git.inviscid.dev -Type A -Server 1.1.1.1 Resolve-DnsName git.inviscid.dev -Type A -Server 8.8.8.8 \end{minted} The supplied timestamp is October 15, 2026 at 19:17:31, UTC offset -04:00. Both resolvers returned an A answer of \texttt{100.49.80.94}, TTL 300. This verifies public name resolution, not application availability or TLS. \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} 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 \texttt{git.inviscid.dev}, 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 and subsequently confirmed that his Gitea account worked. His password-change completion was not separately captured. Both currently use the Linux \texttt{ubuntu} account with separate keys, so Linux account-level attribution remains shared. \subsection{Organization, Repository, and Roles} Victor took responsibility for the Gitea organization/repository work while Payton focused on the other infrastructure tasks. Victor created organization \texttt{Inviscid} and added \texttt{payton} to its Owners team. Both \texttt{victor} and \texttt{payton} therefore administer the organization and its repositories; Payton's account is not a site-wide administrator. \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} Victor created \texttt{Inviscid/SSW590-DevOps} without initializing an unrelated README commit, then pushed the existing project's \texttt{main} history. The repository page showed 13 commits, including the initial Gitea deployment commit \texttt{2807081}. GitHub remains the \texttt{origin} remote; the new \texttt{gitea} remote points to the tunneled service. The observed commands were: \begin{minted}{bash} git remote add gitea http://localhost:3000/Inviscid/SSW590-DevOps.git git push gitea main:main \end{minted} The intended final organization and repository links are \url{https://git.inviscid.dev/Inviscid} and \url{https://git.inviscid.dev/Inviscid/SSW590-DevOps}. At the recorded checkpoint, access used the equivalent paths at \url{http://localhost:3000/} through an SSH tunnel; the final HTTPS links are not yet verified. These links must be checked before submission. \begin{figure}[htbp] \centering \includegraphics[width=\linewidth]{figures/gitea/repository-main-import.png} \caption{October 15: existing project files and history imported into the organization repository on main.} \label{Figure:GiteaRepository} \end{figure} \subsection{Issue 1 and Documentation Pull Request 2} Victor created issue \#1, ``Document the initial Gitea deployment and team access,'' assigned it to himself, and recorded acceptance criteria covering deployment facts, evidence, manual controls, pending work, and Overleaf review. Victor reported that he had uploaded the draft to Overleaf, compiled it, and visually reviewed it. This is author-reported validation of the earlier draft, not verification of later edits or the final combined submission. \begin{figure}[htbp] \centering \includegraphics[width=\linewidth]{figures/gitea/issue-1-documentation.png} \caption{October 15: open issue 1, acceptance criteria, and Victor's assignment. Checked criteria do not mean the issue has been merged or closed.} \label{Figure:GiteaIssueOne} \end{figure} The changes were made on \texttt{codex/gitea-foundation-docs}, staged and checked, then committed as \texttt{da6a369} and pushed: \begin{minted}{bash} git diff --cached --check git diff --cached --stat git commit -m "Document initial Gitea deployment and team access; refs #1" git push -u gitea codex/gitea-foundation-docs \end{minted} The commit recorded 12 changed files, 342 insertions, and 32 deletions. Victor opened PR \#2 targeting \texttt{main}, included \texttt{Closes \#1} in its description, and requested review from \texttt{payton}. Gitea uses a shared numbering sequence for issues and PRs: PR \#2 is the first PR, not a second completed workflow. \begin{figure}[htbp] \centering \includegraphics[width=\linewidth]{figures/gitea/pr-2-review-request.png} \caption{October 15: open PR 2 targets main, links issue 1, and requests Payton's review. Approval and merge are not shown.} \label{Figure:GiteaPROpen} \end{figure} \begin{figure}[htbp] \centering \includegraphics[width=\linewidth]{figures/gitea/pr-2-chapter-diff.png} \caption{October 15: Files Changed view exposes the documentation additions for peer review.} \label{Figure:GiteaPRDiff} \end{figure} The intended final links are \url{https://git.inviscid.dev/Inviscid/SSW590-DevOps/issues/1} and \url{https://git.inviscid.dev/Inviscid/SSW590-DevOps/pulls/2}. These were accessed through the SSH-tunnel hostname at this checkpoint. \textbf{Pending---Payton and both authors:} Payton's formal review, any requested corrections, merge, and issue closure; a separate issue-linked PR authored by Payton and reviewed by Victor; README and at least five actionable issues, including product and operations work. Only issue 1 and open PR 2 are fully evidenced here. The dashboard issue was proposed but its creation was not confirmed. Payton has taken ownership of the remaining Gitea tasks. The initial main push is not counted as a reviewed PR. \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} The initial October 12 access path, before Caddy was deployed, is kept for reference in Figure~\ref{Figure:GiteaInitialPath}. \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{October 12: initial SSH-tunnel-only access path, before Caddy and hostname-based HTTPS were deployed.} \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 logs --tail=40 caddy sudo docker compose restart gitea sudo docker compose restart caddy \end{minted} \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), the Color Buttons container's port 3000, Caddy's admin port 2019, and the SQLite file, which has no network port. \paragraph{Troubleshooting.} If \url{https://git.inviscid.dev} fails, check in order: (1) \texttt{docker compose ps} shows both services 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. Restarting one service leaves its volume intact. \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 (Section~2.8 of the assignment) was not performed. 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 the final Compose/proxy configuration for \texttt{git.inviscid.dev}, 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}.