\chapter{Automated Manual Build} \ChapterLabel{AutomatedManualBuild} \index{Gitea Actions}\index{runner}\index{build artifact}\index{LaTeX}\index{continuous integration} \begin{devopsNote}[Automated build verification] The team demonstrated push-triggered PDF compilation, downloadable build artifacts, and a controlled failure followed by recovery using a self-managed Gitea Actions runner. The following sections record the observed run results and traceable source revisions. Jenkins merge checks are covered separately in Chapter 4. \end{devopsNote} \section{Source and trigger} The team maintains the complete buildable LaTeX source, bibliography, figures, and nonsecret build instructions in the Gitea repository: \url{https://git.137-184-97-250.nip.io/stevens-smartpark/smartpark-app}. The main document is \texttt{latex-manual/devopsAssignment.tex}; this chapter is \texttt{latex-manual/devopsAutomatedManualBuild.tex}. The version-controlled workflow is \texttt{.gitea/workflows/manual.yml}, and the build program is \texttt{ci/build\_manual.py}. The workflow uses the Git push event on \texttt{main}, \texttt{chapter3-build-automation}, and \texttt{chapter3-build-test}. The actual demonstration pushes were made to \texttt{chapter3-build-test}. Gitea Actions run 2 was triggered by a push from \texttt{vincentpenalosa} and succeeded for commit \texttt{9fec46a41ef1dcdffb40d0429e19475ba21af41c}: \url{https://git.137-184-97-250.nip.io/stevens-smartpark/smartpark-app/actions/runs/2}. Run 3 succeeded for the subsequent source commit \texttt{e16a6deab277621f860c8a057a80c9737b5eb4b1}: \url{https://git.137-184-97-250.nip.io/stevens-smartpark/smartpark-app/actions/runs/3}. These runs demonstrate the configured source-push-to-workflow connection. Git clients push over HTTPS to the public Gitea hostname on TCP port 443. Caddy terminates TLS and forwards the request to Gitea on private container port 3000. The runner VM reaches the public HTTPS Gitea hostname and does not need a public inbound build, compiler, or management port. The runner checks out source over the network and uploads the artifact through Gitea Actions; no shared filesystem between the VM and Gitea is needed. \begin{figure}[htbp] \centering \includegraphics[width=0.92\linewidth]{png/successful_run_1.png} \caption{October 2026: push-triggered Gitea Actions run 2 on \texttt{chapter3-build-test}, showing a successful job and one artifact.} \label{fig:ch3-push-run2} \end{figure} \section{Runner setup} The self-managed runner executes on Vincent's separate Ubuntu 22.04.5 LTS VirtualBox VM (\texttt{x86\_64}), rather than on the shared DigitalOcean Droplet hosting Gitea and Jenkins. The handoff records approximately 4 GB of allocated memory and a 25 GB virtual disk; these are historical observations, not current free-resource measurements. The installed Gitea runner version is reported as 2.3.0. Its configured name is \texttt{vincent-ubuntu-manual}, with a repository-scoped registration for the SmartPark repository, label \texttt{smartpark-latex:host}, and capacity one. The workflow selects the label through \texttt{runs-on: smartpark-latex}. The runner executes jobs in host mode, using TeX and Python utilities installed on Ubuntu rather than launching Docker job containers. A dedicated unprivileged \texttt{smartpark-runner} Linux account runs the service. The handoff identifies \texttt{smartpark-runner.service}, configuration at \texttt{/etc/smartpark-runner/config.yaml}, and a private runner identity at \texttt{/var/lib/smartpark-runner/.runner}. A supplied service screenshot shows the service active; successful jobs 2 and 3 additionally establish that the runner executed workflow work. The VM must remain running and network-connected to accept new jobs. No reboot-survival or current resource-availability test is claimed without separate evidence. \begin{figure}[htbp] \centering \includegraphics[width=0.92\linewidth]{png/runner_activated.png} \caption{October 2026: Ubuntu service evidence for the separate Gitea Actions runner VM. Service activity is not, by itself, proof of current Gitea online status.} \label{fig:ch3-runner-service} \end{figure} \section{Reproducible compilation} On a clean Git checkout, the documented entry point is \texttt{python3 ci/build\_manual.py}. This script copies the manual source to a fresh temporary work directory and compiles the main file with \texttt{latexmk -pdf}, stopping on LaTeX errors. It supports the document's \texttt{minted} highlighting with Pygments and shell escape, and coordinates bibliography, glossary, index, and repeated LaTeX passes. Source and workflow edit rights must be restricted because enabling shell escape executes helper programs. The expected deliverables are \texttt{build/manual/SmartPark-Manual.pdf} and \texttt{build/manual/build-info.txt}. The supplied successful artifact metadata records \texttt{latexmk} 4.76, \texttt{pdfTeX} 1.40.22 (TeX Live 2022/dev/Debian), and Pygments 2.11.2. The handoff also reports \texttt{makeglossaries} 4.49 and packages including \texttt{texlive-latex-extra}, \texttt{texlive-fonts-recommended}, \texttt{lmodern}, and \texttt{python3-pygments}. The build script removes old deliverables, checks command exit status and the expected PDF, and treats unresolved reference/citation summaries as errors. Therefore an old PDF cannot silently stand in for a newly successful build. The compiler runs on the runner VM, not in the Gitea application container. Run 2's successful \texttt{manual} job took 41 seconds; run 3 also completed successfully. Their run pages and downloadable \texttt{build-info.txt} files provide the logged build context: \url{https://git.137-184-97-250.nip.io/stevens-smartpark/smartpark-app/actions/runs/2} and \url{https://git.137-184-97-250.nip.io/stevens-smartpark/smartpark-app/actions/runs/3}. Run 4 is the controlled negative compilation test, with a failed workflow status. The run 4 screenshot records the negative test status; the job console is the authoritative source for the compiler diagnostic. \section{PDF version traceability} The team maintains the human-readable manual version separately from the machine-generated build identifier. The documented manual version for these runs is \texttt{1.9-draft}. For Gitea Actions builds, the identification page adds the triggering commit's short revision and the workflow run number. The metadata is generated during the temporary build and is not pushed back to the branch, avoiding a self-triggering push/build loop. Local builds use \texttt{local}; local modified trees can display \texttt{dirty}, so a local build is not substituted for Actions evidence. The downloaded PDF for run 2 visibly identifies \texttt{1.9-draft+build.2.9fec46a41ef1}, source revision \texttt{9fec46a41ef1}, and workflow run \texttt{2}. The downloaded PDF for run 3 displays \texttt{1.9-draft+build.3.e16a6deab277}, source revision \texttt{e16a6deab277}, and workflow run \texttt{3}. The matching full commits are recorded in Section 6. The respective build metadata timestamps are October 10, 2026 at 02:12 UTC and 02:26 UTC. The identifiers printed in the respective downloaded PDFs match the workflow run numbers and source revisions. \begin{figure}[htbp] \centering \includegraphics[width=0.90\linewidth]{png/successful_run_2.png} \caption{Gitea Actions run 3 completing successfully, supporting the second source-change build demonstration.} \label{fig:ch3-run3-success} \end{figure} \section{Downloadable artifact} The workflow names artifacts using the workflow run and triggering source commit. Run 2 uploaded a named downloadable artifact: \url{https://git.137-184-97-250.nip.io/stevens-smartpark/smartpark-app/actions/runs/2}. Its downloaded ZIP contains \texttt{SmartPark-Manual.pdf} and \texttt{build-info.txt}. The 82-page PDF was extracted and opened successfully. Its build identifier is \texttt{1.9-draft+build.2.9fec46a41ef1}, matching run 2 and commit \texttt{9fec46a41ef1dcdffb40d0429e19475ba21af41c}. Run 3 also produced a distinct successful PDF and matching metadata, rather than reusing the previous output. A team member obtains an artifact by opening the matching Gitea Actions run, choosing the named entry under Artifacts, downloading and extracting the ZIP, and visually inspecting the PDF and its identification page. The workflow requests a 30-day artifact retention period. The server's actually applied retention policy was not independently verified. Artifacts are downloadable build deliverables, not permanent backups. \begin{figure}[htbp] \centering \includegraphics[width=0.92\linewidth]{png/successful_run_1.png} \caption{October 2026: successful run 2 showing the named downloadable manual artifact in the Gitea Actions interface.} \label{fig:ch3-artifact-run2} \end{figure} \section{Change, failure, and repair evidence} The team performed the following source-change and repair sequence on \texttt{chapter3-build-test}. Expected results were compared with the actual Gitea Actions status, with links to each run. \begin{enumerate} \item \textbf{First successful source change:} run 2, ``Document first manual build demonstration,'' compiled commit \texttt{9fec46a41ef1dcdffb40d0429e19475ba21af41c}, generated the PDF identifier \texttt{1.9-draft+build.2.9fec46a41ef1}, and uploaded one named artifact. Expected: build and artifact succeed; actual: success. \url{https://git.137-184-97-250.nip.io/stevens-smartpark/smartpark-app/actions/runs/2}. \item \textbf{Second successful source change:} run 3, ``Document second manual build demonstration,'' compiled the next source change at commit \texttt{e16a6deab277621f860c8a057a80c9737b5eb4b1}. The downloaded PDF identifies \texttt{1.9-draft+build.3.e16a6deab277}. Expected: a different build/PDF tied to this source revision; actual: successful run and matching PDF metadata. \url{https://git.137-184-97-250.nip.io/stevens-smartpark/smartpark-app/actions/runs/3}. \item \textbf{Controlled compiler failure:} run 4, ``Demonstrate controlled LaTeX compilation failure,'' used the deliberate undefined command \verb|\SmartParkIntentionalBuildFailure|. Expected: nonzero LaTeX build status and no successfully published PDF. The supplied screenshot confirms failed workflow status and shows no artifact in that view. The run URL links to the associated commit and compiler job log. \url{https://git.137-184-97-250.nip.io/stevens-smartpark/smartpark-app/actions/runs/4}. \item \textbf{Repair:} run 5, ``Revert Demonstrate controlled LaTeX compilation failure,'' restored a working document. Expected: successful compile and artifact upload; actual: screenshot shows a successful job and one artifact. The run page identifies the repair revision and downloadable artifact. \url{https://git.137-184-97-250.nip.io/stevens-smartpark/smartpark-app/actions/runs/5}. \end{enumerate} \begin{figure}[htbp] \centering \includegraphics[width=0.88\linewidth]{png/latex_compilation_failure.png} \caption{October 2026: controlled LaTeX compilation failure recorded by Gitea Actions run 4. The matching job log records the compiler failure.} \label{fig:ch3-run4-failure} \end{figure} \begin{figure}[htbp] \centering \includegraphics[width=0.88\linewidth]{png/repaired_build.png} \caption{October 2026: Gitea Actions run 5 succeeding after the controlled error was reverted, with one downloadable artifact.} \label{fig:ch3-run5-repair} \end{figure} \section{Runner and secret boundary} The Chapter 3 runner uses Vincent's separate Ubuntu VirtualBox VM, not the shared DigitalOcean Droplet. Its configured repository-scoped registration, \texttt{smartpark-latex:host} execution label, capacity one, and unprivileged \texttt{smartpark-runner} service account limit the jobs' intended access. Host-mode jobs execute directly as that account and are \emph{not} isolated from each other by disposable Docker containers. Consequently, workflow and source changes targeting the runner should be restricted to reviewed team contributors. A push-only trigger and label do not themselves establish an access-control boundary. The service configuration resides at \texttt{/etc/smartpark-runner/config.yaml}; the private registration identity resides at \texttt{/var/lib/smartpark-runner/.runner}. Runner registration tokens, personal SSH keys, passwords and repository secrets must not appear in Git, job logs, screenshots, or generated PDFs. The operational Passwords table should contain only ownership and storage hints. No privileged Docker socket access was granted to the Chapter 3 job environment on the shared Droplet; doing so would expose the other Docker-hosted Gitea/Jenkins services to substantial risk. Only outbound HTTPS access to Gitea is required. The actual editor/branch permissions should be verified against repository settings; this report does not claim that every policy was technically enforced just because it is recommended. The service evidence is shown in Figure~\ref{fig:ch3-runner-service}; nonsecret configuration lives under \texttt{ci/} in the Gitea repository. \section{Manual quality and controls} The version-controlled source, reproducible workflow, host-mode runner, compiler checks, visible build identifiers, successful PDF artifacts, and controlled failure/repair are documented above. The manual version used for the demonstrated builds is \texttt{1.9-draft}. The final released manual version is maintained by the team separately from the build identifiers recorded for historical runs. For operational records, the Hosts table identifies the Ubuntu runner VM, its owner, role, outbound HTTPS access, and availability conditions. The Passwords table tracks only the runner credential custodian and storage hint, not the token or private runner identity. The team tracks Chapter 3 changes in the manual history and retains source, build, and artifact links for auditability. Index terms include runner, Gitea Actions, build artifact, and reproducible compilation. \textbf{Troubleshooting:} For queued jobs, check VM power/network access, runner service status, repository scope, label matching, and capacity. For checkout failures, check HTTPS/DNS and repository authorization. For compiler failures, read the first actual TeX error, check paths and dependencies, and rerun after a committed repair. For missing artifacts, check that \texttt{build/manual/SmartPark-Manual.pdf} exists and inspect the upload step. A green compiler step alone does not establish successful artifact upload. For failure/repair evidence, keep the exact commit, run link, timestamp, and outcome synchronized across the manual and screenshots. \textbf{Final submission check:} Commit the integrated LaTeX and PNG files, verify a new successful Gitea Actions build, download its PDF artifact, open and inspect every figure and build identifier, and submit that downloaded PDF. The submitted deliverable is the PDF downloaded from the final successful Gitea Actions run.