Compare commits

..
18 Commits
Author SHA1 Message Date
mfriedma1 af8b58f33c test commit to trigger workflow 2026-10-11 21:16:18 -04:00
mfriedma1 45cc5f747b Update manual.tex 2026-10-12 01:12:06 +00:00
mfriedma1 f6f2eedb76 adjusted texlive sha
Build LaTeX Manual / build-manual (push) Failing after 2m59s
2026-10-11 21:06:51 -04:00
mfriedma1 531e6896fb added the workflow
Build LaTeX Manual / build-manual (push) Failing after 29s
2026-10-11 20:09:05 -04:00
mfriedma1 e9a3ccecb2 Upload files to "/" 2026-10-11 21:35:39 +00:00
alexn41 ef547eb442 Upload files to "/" 2026-10-10 01:25:37 +00:00
alexn41 668f4f3068 Upload files to "/" 2026-10-10 01:23:12 +00:00
alexn41 bcc9a272a0 Upload files to "/" 2026-10-10 01:22:55 +00:00
alexn41 d02d6a5431 Upload files to "/" 2026-10-10 01:20:35 +00:00
alexn41 c8b5d150d7 Upload files to "/" 2026-10-10 01:19:44 +00:00
alexn41 23186bca75 Upload files to "/" 2026-10-10 01:18:56 +00:00
alexn41 deb16f7302 Upload files to "/" 2026-10-10 01:17:04 +00:00
alexn41 d2167d642f Delete devops-group-overleaf-main (2).zip 2026-10-10 01:13:57 +00:00
alexn41 7c4295b7fe Upload files to "/" 2026-10-10 01:13:47 +00:00
alexn41 485439af1d Merge pull request 'Add repository validation tests (#2)' (#7) from feature/issue-2-tests into main
Reviewed-on: https://git.ssw590.tech/ssw590-startup/product/pulls/7
2026-09-30 23:31:01 +00:00
mfriedma1 22201af746 Add repository validation tests
Closes #2
2026-09-30 23:30:04 +00:00
alexn41 1fb093841e Merge pull request 'Implement product landing page (#1)' (#6) from feature/issue-1-landing-page into main
Reviewed-on: https://git.ssw590.tech/ssw590-startup/product/pulls/6
2026-09-30 23:23:35 +00:00
mfriedma1 892f4ce08b Implement product landing page (#1)
Implements the initial product landing page structure described in Issue #1.

Closes #1
2026-09-30 23:19:15 +00:00
31 changed files with 4296 additions and 0 deletions
+84
View File
@@ -0,0 +1,84 @@
name: Build LaTeX Manual
on:
push:
branches:
- main # Change to the branch you want to build
run-name: Manual PDF - ${{ gitea.run_number }}
jobs:
build-manual:
runs-on: ubuntu-latest
# The compiler runs inside this container, not in the Gitea
# server container. Pin the image to a reviewed SHA-256 digest.
container:
image: texlive/texlive@sha256:a7ae4dfa9d521b5db14446872fa488b839021d1f604d0a3c74461784895f2a67
options: --user 1000:1000
steps:
- name: Check out triggering revision
uses: actions/checkout@v4
with:
ref: ${{ gitea.sha }}
persist-credentials: false
- name: Record toolchain and verify inputs
shell: bash
env:
BUILD_COMMIT: ${{ gitea.sha }}
BUILD_NUMBER: ${{ gitea.run_number }}
run: |
set -eu
echo "Commit: ${BUILD_COMMIT}"
echo "Workflow run: ${BUILD_NUMBER}"
echo "Compiler environment:"
cat /etc/os-release
pdflatex --version
latexmk --version
test -f manual.tex || {
echo "::error::Expected source manual.tex was not found."
exit 1
}
mkdir -p build
cat > build/build-version.tex <<EOF
\newcommand{\BuildCommit}{${BUILD_COMMIT}}
\newcommand{\BuildRunNumber}{${BUILD_NUMBER}}
EOF
- name: Compile manual
shell: bash
env:
BUILD_COMMIT: ${{ gitea.sha }}
BUILD_NUMBER: ${{ gitea.run_number }}
run: |
set -eu
# A nonzero compiler exit status fails the job.
latexmk -pdf -halt-on-error -file-line-error \
-interaction=nonstopmode \
-outdir=build manual.tex || {
echo "::error::LaTeX compilation failed."
exit 1
}
if [ ! -s build/manual.pdf ]; then
echo "::error::Compilation reported success, but build/manual.pdf is missing or empty."
exit 1
fi
echo "PDF created successfully:"
ls -lh build/manual.pdf
- name: Upload compiled PDF
uses: actions/upload-artifact@v4
with:
name: manual-pdf-${{ gitea.run_number }}
path: build/manual.pdf
if-no-files-found: error
retention-days: 90
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

+2365
View File
File diff suppressed because it is too large Load Diff
+5
View File
@@ -0,0 +1,5 @@
This assignment guides students through a reproducible DevOps activity. It
defines the learning objectives, environment boundary, workflow, evidence,
reflection, and submission criteria. Replace bracketed text with assignment-
specific facts before release. Students should be able to recreate the work
from the submitted automation and runbook rather than from undocumented manual steps.
+367
View File
@@ -0,0 +1,367 @@
%%
%% This is file `cornell.cls',
%% generated with the docstrip utility.
%%
%% The original source files were:
%%
%% cornell.dtx (with options: `class')
%%
%% IMPORTANT NOTICE:
%%
%% For the copyright see the source file.
%%
%% Any modified versions of this file must be renamed
%% with new filenames distinct from cornell.cls.
%%
%% For distribution of the original source see the terms
%% for copying and modification in the file cornell.dtx.
%%
%% This generated file may be distributed as long as the
%% original source files, as listed above, are part of the
%% same distribution. (The sources need not necessarily be
%% in the same archive or directory.)
%% Document class `cornell' to use with LaTeX 2e.
%% Based on `crnlphd' class. The following is from the file `crnlphd.dtx':
%% Based loosely on old cuphdthesis style file.
%% Found to give good results, according to the thesis secretary.
%% File: crnlphd.dtx Copyright (C) 1994 Sergio Gelato
%% Made available without any warranty, express or implied.
%% Modifications
%% 03/26/98 Mike Heisler
%% Conform to Eric's final version. Margins are subject to
%% The whim of the printer.
%% 10/10/97 Mike Heisler and Eric Dahl
%% Altered footnotesize and changed the Acknowledgement and
%% Biographical titles per thesis secretary guideline that
%% they must match the rest of the document, i.e. flush left
%% and capitalized rather than all caps
%% 05/21/97 Mike Heisler
%% Added a 'papers' option to handle bibliographis as sections
%% at the end of each chapter. Reduced top margin.
%%
%% $Id$
%% \CharacterTable
%% {Upper-case \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z
%% Lower-case \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z
%% Digits \0\1\2\3\4\5\6\7\8\9
%% Exclamation \! Double quote \" Hash (number) \#
%% Dollar \$ Percent \% Ampersand \&
%% Acute accent \' Left paren \( Right paren \)
%% Asterisk \* Plus \+ Comma \,
%% Minus \- Point \. Solidus \/
%% Colon \: Semicolon \; Less than \<
%% Equals \= Greater than \> Question mark \?
%% Commercial at \@ Left bracket \[ Backslash \\
%% Right bracket \] Circumflex \^ Underscore \_
%% Grave accent \` Left brace \{ Vertical bar \|
%% Right brace \} Tilde \~}
%%
\NeedsTeXFormat{LaTeX2e}[1995/12/01]
\ProvidesClass{cornell}
[1998/03/26 v2.0
Cornell University thesis class]
\newif\ifphd
\DeclareOption{phd}{\phdtrue}
\DeclareOption{master}{\phdfalse}
\ExecuteOptions{phd}
\newif\ifpapers
\DeclareOption{papers}{\paperstrue}
\DeclareOption{10pt}{%
\OptionNotUsed%
\ClassWarningNoLine{cornell}{Font size 10pt not allowed; using 12pt}%
}
\DeclareOption{11pt}{%
\OptionNotUsed%
\ClassWarningNoLine{cornell}{Font size 11pt not allowed; using 12pt}%
}
\newcommand{\prelim@contents}[1]{}
\DeclareOption{tocprelim}{%
\renewcommand{\prelim@contents}[1]{\addcontentsline{toc}{section}{#1}}
}
\DeclareOption*{%
\PassOptionsToClass{\CurrentOption}{report}%
}
\ProcessOptions
\LoadClass[12pt]{report}[1994/06/01]
\renewcommand{\defaultscriptratio}{.75}
\renewcommand{\defaultscriptscriptratio}{.6}
\setlength\headsep{25\p@}
\if@twocolumn
\else
\setlength\oddsidemargin{0in}
\setlength\evensidemargin{0in}
\setlength\textwidth{6.5in}
\setlength\topmargin{-0.5in}
\setlength\headheight{0.2in}
\setlength\headsep{0.35in}
\setlength\textheight{8.85in}
\setlength\footskip{0.5in}
\setlength\marginparwidth{0.85in}
\setlength\marginparsep{0.125in}
\fi
\if@compatibility
\setlength\topmargin{0\p@}
\else
\fi
\newcommand{\ps@cornell}{%
\def\@oddhead{{\slshape\rightmark}\hfil\thepage}%
\def\@oddfoot{}%
\def\@evenhead{\thepage\hfil{\slshape\leftmark}}%
\def\@evenfoot{}%
\def\chaptermark##1{}%
\def\sectionmark##1{}%
}
\newcommand{\ps@cornellc}{%
\def\@oddhead{\hfil\thepage\hfil}%
\def\@oddfoot{}%
\def\@evenhead{\@oddhead}%
\def\@evenfoot{}%
\def\chaptermark##1{}%
\def\sectionmark##1{}%
}
\raggedbottom
\newcommand{\@prolformat}[1]{{\begin{flushleft} \Huge #1 \end{flushleft}} }
\newcommand{\ackname}{\@prolformat{Acknowledgements}}
\newcommand{\bioname}{\@prolformat{Biographical Sketch}}
\renewcommand{\contentsname}{\@prolformat{Table of Contents}}
\renewcommand{\listtablename}{\@prolformat{List of Tables}}
\renewcommand{\listfigurename}{\@prolformat{List of Figures}}
\renewcommand{\l@figure}{\@dottedtocline{1}{1.5em}{2.6em}}
\let\l@table\l@figure
\renewenvironment{thebibliography}[1]
{\ifpapers \clearpage \section*{\bibname%
\@mkboth{\uppercase{\bibname}}{\uppercase{\bibname}}}%
\else%
\chapter*{\bibname%
\@mkboth{\uppercase{\bibname}}{\uppercase{\bibname}}}%
\fi%
\list{\@biblabel{\arabic{enumiv}}}%
{\settowidth\labelwidth{\@biblabel{#1}}%
\leftmargin\labelwidth
\advance\leftmargin\labelsep
\@openbib@code
\usecounter{enumiv}%
\let\p@enumiv\@empty
\renewcommand\theenumiv{\arabic{enumiv}}}%
\sloppy\clubpenalty4000\widowpenalty4000%
\sfcode`\.\@m\relax
\ifpapers%
\addcontentsline{toc}{section}{\bibname}%
\else%
\addcontentsline{toc}{chapter}{\bibname}%
\fi
\singlespacing}
{\def\@noitemerr
{\@latex@warning{Empty `thebibliography' environment}}%
\normalspacing
\endlist}
\newcommand{\@titlesize}{\Large}
\newcommand{\@subtitlesize}{\large}
\newcommand{\@authorsize}{\normalsize} % No Pharaoh he.
\newcommand{\@conferralmonth}{January}
\newcommand{\@conferralyear}{}
\newcommand{\titlesize}[1]{\renewcommand{\@titlesize}{#1}}
\newcommand{\subtitlesize}[1]{\renewcommand{\@subtitlesize}{#1}}
\newcommand{\authorsize}[1]{\renewcommand{\@authorsize}{#1}}
\newcommand{\conferraldate}[2]{%
\renewcommand{\@conferralmonth}{#1}
\renewcommand{\@conferralyear}{#2}
}
\newcommand{\@degreefield}{Master of Science} %CHANGED
\newcommand{\degreefield}[1]{\renewcommand{\@degreefield}{#1}} %CHANGED
\renewcommand{\maketitle}{%
\begin{titlepage}%
\vspace*{-0.24in}
\begin{center}%
\@titlesize \@title
\end{center}
\vfill
%\begin{center}
%\@subtitlesize
%A \ifphd Dissertation \else Thesis \fi \\ %CHANGED
%Presented to the Faculty of the Graduate School \\
%of Cornell University \\
%in Partial Fulfillment of the Requirements for the Degree of \\
%\ifphd Doctor of Philosophy \else \@degreefield \fi %CHANGED
%\end{center}
\vfill
\begin{center}
\@authorsize
by \\
\@author \\
\@conferralmonth\ \@conferralyear
\end{center}
\vspace*{0.26in}
\end{titlepage}
}
\renewenvironment{titlepage}{%
\typeout{Title page}
\pagestyle{empty}
\doublespacing
}{%
\normalspacing\clearpage
}
\newenvironment{copyrightpage}{%
\typeout{Copyleft page}
\pagestyle{empty}
\null\vfil
\begin{center}
}{%
\end{center}
\vfil\null
\clearpage
}
\newcommand{\makecopyright}{%
\begin{copyrightpage}
\copyright\ \@copyrightholder\ \@copyrightyear \\
ALL RIGHTS RESERVED \\[.5in]
\end{copyrightpage}
}
\newcommand{\@copyrightholder}{\@author}
\newcommand{\@copyrightyear}{\@conferralyear}
\newcommand{\copyrightholder}[1]{\renewcommand{\@copyrightholder}{#1}}
\newcommand{\copyrightyear}[1]{\renewcommand{\@copyrightyear}{#1}}
\newlength{\@abhdskip}
\renewenvironment{abstract}{%
\typeout{Abstract}
\singlespacing
\ifphd %CHANGED
\setlength{\@abhdskip}{.25in}
\addtolength{\@abhdskip}{-\topmargin}
\vspace*{\@abhdskip}
\begin{center}
\@abstracttitle \\
\ \\
\@author \\
\@conferralyear
\end{center}
\vspace{.25in}
\else %CHANGED
\chapter*{%
\begin{center}
\normalsize
ABSTRACT
\end{center}
}
\par
\fi %CHANGED
\pagestyle{empty}
\thispagestyle{empty}
}{%
\clearpage
}
\newcommand{\@abstracttitle}{\@title}
\newcommand{\abstracttitle}[1]{\renewcommand{\@abstracttitle}{#1}}
\newenvironment{biosketch}{%
\typeout{Vita}
\pagestyle{plain}
\pagenumbering{roman}
\setcounter{page}{3}
\prelim@contents{Biographical Sketch}
\chapter*{%
\bioname
}
\par
}{%
\clearpage
}
\newenvironment{dedication}{%
\prelim@contents{Dedication}
\null\vfil
\begin{center}
}{
\end{center}
\vfil\null
\clearpage
}
\newenvironment{acknowledgements}{%
\prelim@contents{Acknowledgments}
\chapter*{%
\ackname
}
\par
}{%
\cleardoublepage
}
\newcommand{\contentspage}{%
\singlespacing
\label{toc}
\prelim@contents{\contentsname}
\tableofcontents
\normalspacing
\clearpage
}
\newcommand{\tablelistpage}{%
\singlespacing
\label{lot}
\prelim@contents{\listtablename}
\listoftables
\normalspacing
\clearpage
}
\newcommand{\figurelistpage}{%
\singlespacing
\label{lof}
\prelim@contents{\listfigurename}
\listoffigures
\normalspacing
\clearpage
}
\newcommand{\abbrlist}{%
\singlespacing
\prelim@contents{List of Abbreviations}
\chapter*{List of Abbreviations}
}
\newcommand{\symlist}{%
\singlespacing
\prelim@contents{List of Symbols}
\chapter*{List of Symbols}
}
\newcommand{\preface}{%
\singlespacing
\prelim@contents{Preface}
\chapter*{Preface}
}
\newcommand{\singlespacing}{%
\let\CS=\@currsize\renewcommand{\baselinestretch}{1.0}\tiny\CS}
\newcommand{\singlespacingplus}{%
\let\CS=\@currsize\renewcommand{\baselinestretch}{1.25}\tiny\CS}
\newcommand{\doublespacing}{%
\let\CS=\@currsize\renewcommand{\baselinestretch}{1.75}\tiny\CS}
\newcommand{\realdoublespacing}{%
\let\CS=\@currsize\renewcommand{\baselinestretch}{2}\tiny\CS}
\newcommand{\normalspacing}{\doublespacing}
\newcommand{\footnotespacing}{\singlespacing}
\newcommand{\changespacing}[2]{%
\renewcommand{#1}{%
\let\CS=\@currsize\renewcommand{\baselinestretch}{#2}\tiny\CS}%
}
\newcommand{\changenormalspacing}[1]{\renewcommand{\normalspacing}{#1}}
\normalspacing
\endinput
%%
%% End of file `cornell.cls'.
+14
View File
@@ -0,0 +1,14 @@
{
"WARNING": "This file is automatically generated by act-runner. Do not edit it manually unless you know what you are doing. Removing this file will cause act runner to re-register as a new runner.",
"id": 1,
"uuid": "9eb340f2-0711-454c-bc43-0a29602e36ca",
"name": "latex-compile-runner",
"token": "0caaccbb606f75a9de7b775963455c97c98c0484",
"address": "https://git.ssw590.tech/",
"labels": [
"ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest",
"ubuntu-24.04:docker://docker.gitea.com/runner-images:ubuntu-24.04",
"ubuntu-22.04:docker://docker.gitea.com/runner-images:ubuntu-22.04"
],
"ephemeral": false
}
+107
View File
@@ -0,0 +1,107 @@
\chapter{AI Agent Usage Guide}
\ChapterLabel{AIAgentGuide}
\index{AI agent}
\index{AI agent!DevOps workflow}
\index{Appendix!AI Agent Usage Guide}
\section{How to Use This Appendix}
An AI agent that creates, reviews, or maintains an assignment shall read this
file first. It shall identify the task in the routing table, read the minimum
authoritative sources, and expand context only for a discovered dependency or
inconsistency. This guide is maintained in LaTeX so it is visible, versioned,
and reviewable with the assignment.
\section{Governing Principles}
The authored LaTeX files, versioned automation, and approved assignment
materials are authoritative. The PDF is a review artifact and shall not be
edited directly. AI may draft, explain, analyze, check consistency, and assist
with builds. It shall not invent completion, evidence, access, costs, grades,
approvals, command output, security posture, or operational results.
\section{Chapter File Convention}
\index{AI agent!chapter file convention}
Every assignment chapter shall be stored in exactly one file named with the
\texttt{devops} prefix, a PascalCase chapter name, and the \texttt{.tex}
suffix. For example, Chapter~2 is fully contained in
\path{devopsExampleAssignment.tex}. A chapter file shall begin with its
\texttt{\string\chapter\{...\}} command and contain all of that chapter's
sections and subsections. Do not split one chapter across files, create
section-only \texttt{.tex} files, or use \texttt{\string\input} or
\texttt{\string\include} to assemble a chapter from smaller fragments.
Supporting non-chapter files may use a different name only when they are not
chapter content: \texttt{prologue.tex}, \texttt{abstract.tex},
\texttt{devopsGlossary.tex}, and \texttt{devopsReferences.bib} are examples.
Appendix chapters follow the same chapter-file convention.
\section{AI Quick Start}
\begin{enumerate}
\item Read this guide and identify the requested outcome.
\item Select the closest task below and load only its listed sources.
\item Inspect supplied facts and report consequential gaps or assumptions.
\item Make narrowly scoped changes; preserve stable IDs, glossary terms, and history.
\item Never request, print, store, or commit credentials, tokens, personal data, or production data.
\item Build the complete PDF, inspect the log, visually inspect affected pages, and record material AI use in \ChapterRef{AIPromptLog}.
\end{enumerate}
\section{Task Routing}
\small
\begin{tabularx}{\linewidth}{L{4.0cm}Y}
\caption{AI Task-Routing Table\label{Table:AITaskRouting}}\\
\toprule \textbf{Task} & \textbf{Minimum authoritative sources}\\ \midrule
Initialize an assignment & \texttt{devopsAssignment.tex}, \texttt{prologue.tex}, \texttt{devopsIntroduction.tex}, \texttt{devopsExampleAssignment.tex}, and the instructor's assignment facts.\\
Develop a workflow & \texttt{devopsExampleAssignment.tex}, source repository materials, and authorized environment constraints.\\
Define evidence & \texttt{devopsExampleAssignment.tex} and controlled execution outputs.\\
Review a retrospective & \texttt{devopsExampleAssignment.tex} and the submitted workflow or runbook.\\
Edit AI guidance & \texttt{devopsAIAgentGuide.tex}, \texttt{devopsAIPromptLog.tex}, \texttt{devopsDocumentGuide.tex}, and \texttt{devopsAssignment.tex}.\\
Compile or troubleshoot & \texttt{devopsAssignment.tex}, \texttt{devopsDocumentGuide.tex}, the affected chapter, and the build log.\\ \bottomrule
\end{tabularx}
\normalsize
\section{DevOps-Specific Rules}
\begin{enumerate}
\item Prefer versioned, repeatable automation over undocumented manual steps.
\item Treat deployment output, monitoring data, and screenshots as evidence only when they reflect an actual execution.
\item Design failure exercises to be isolated, reversible, and authorized.
\item Apply least privilege and never place secrets in LaTeX, source control, prompts, screenshots, or logs.
\item State expected cloud cost, cleanup action, and resource ownership for each remote-environment activity.
\item Identify whether a recommendation is a proposal, an instructor directive, or demonstrated evidence.
\end{enumerate}
\section{Example AI Requests}
\subsection{Create an Assignment Brief}
\begin{verbatim}
Read devopsAIAgentGuide.tex and use the Initialize an Assignment context.
From the supplied SSW 590 objective and constraints, fill only supported
placeholders in the introduction and activity brief. Propose clear, observable
completion criteria, a safe cleanup action, and a rubric. Do not invent cloud
access, due dates, policy, costs, or approved tools. Update history, compile,
and report instructor decisions still needed.
\end{verbatim}
\subsection{Review a Student Workflow}
\begin{verbatim}
Read devopsAIAgentGuide.tex and the workflow, evidence, and retrospective
chapters. Review the supplied repository and sanitized evidence for
reproducibility, least privilege, observability, rollback, and cleanup.
Separate verified facts from gaps and recommendations. Do not claim that an
unexecuted command passed, and do not request credentials or operate resources.
\end{verbatim}
\section{Human Decisions and Information Boundaries}
The instructor retains authority over assignment scope, grading, due dates,
approved tooling, access, budgets, security posture, exceptions, and policy.
An AI agent shall surface alternatives and evidence, not represent a decision
as approved. Record material prompts, assumptions, corrections, and unresolved
issues in \ChapterRef{AIPromptLog}.
\section{Completion Checklist}
\begin{itemize}
\item Scope, deliverables, environment, constraints, and cleanup are clear.
\item Activity identifiers and evidence references are valid and unique.
\item Examples contain no secrets, personal data, or unsafe production actions.
\item New controlled terms are added to the glossary and indexed.
\item The newest approved change is recorded in document history.
\item The complete build succeeds with no unresolved references or missing files.
\item A human reviews all decisions requiring instructional or operational authority.
\end{itemize}
+14
View File
@@ -0,0 +1,14 @@
\chapter{AI Prompt Log}
\ChapterLabel{AIPromptLog}
\index{AI prompt log}
\section{How to Use This Appendix}
Use this log when AI assistance materially affects assignment wording,
automation, evidence interpretation, troubleshooting, or feedback. Record
enough context for an instructor or reviewer to understand the input, output,
assumptions, and human corrections. Do not record credentials or personal data.
\section{Example Record}
Date: \textit{[timestamp]}; Tool and model: \textit{[name]}; Task:
\textit{[requested outcome]}; Inputs: \textit{[sanitized constraints and
evidence]}; Output used: \textit{[summary]}; Human review and corrections:
\textit{[summary]}; Unresolved assumptions: \textit{[list or none]}.
+142
View File
@@ -0,0 +1,142 @@
\chapter{Document Authoring Guide}
\ChapterLabel{DocumentGuide}
\index{LaTeX}
\index{document conventions}
\index{Appendix!Document Authoring Guide}
\section{How to Use This Appendix}
Use this appendix to maintain the template's file conventions, build process,
and review rules. Update it when the document structure or toolchain changes.
\section{Template Provenance}
This template was developed by \textbf{Darian Muresan, Ph.D.}, in conjunction
with \textbf{OpenAI Codex} and \textbf{Anthropic Claude Code}. Keep this
attribution distinct from the assignment-specific instructor metadata.
\section{File Naming and Responsibilities}
Every chapter file shall use the \texttt{devops} prefix, a PascalCase chapter
name, and the \texttt{.tex} suffix. Each such file shall contain one complete
chapter, beginning with \texttt{\string\chapter\{...\}}, and all of its
sections and subsections. Do not split one chapter into section-level
\texttt{.tex} files. For example, all of Chapter~2 is contained in
\path{devopsExampleAssignment.tex}. Do not add requirements or
architecture-design chapters; this template records a DevOps learning activity
and its operational evidence.
\section{Synonyms and Controlled Terms}
\index{synonyms}
Use one preferred term for each important concept. If an assignment uses
equivalent terms, record them here before release. For example, state whether
``continuous delivery'' refers to \gls{cd} generally or specifically excludes
automatic production deployment. Add important terms to
\texttt{devopsGlossary.tex} and index them where used.
\section{Building the PDF}
The template requires a LaTeX distribution with \texttt{pdflatex},
\texttt{bibtex}, \texttt{makeglossaries}, and \texttt{makeindex}.
Install the tools using the platform instructions below, then run one of the
following platform builds.
\subsection{macOS and Linux Build}
\begin{verbatim}
make -f makefileMacLinux pdf
\end{verbatim}
\subsection{Windows PowerShell Build}
\begin{verbatim}
.\makefilePowerShell.ps1 -Action pdf
\end{verbatim}
The completed build regenerates references, bibliography, glossary, and index.
Review the log and visually inspect affected PDF pages. Do not edit generated
auxiliary files or the PDF directly.
\section{Install LaTeX Tools on macOS}
\index{macOS}
\index{MacTeX}
\subsection{Recommended Installation}
Install the current full \href{https://www.tug.org/mactex/}{MacTeX}
distribution. It includes TeX Live, \texttt{pdflatex}, \texttt{bibtex},
\texttt{latexmk}, \texttt{makeglossaries}, \texttt{makeindex}, and useful
applications such as TeXShop and TeX Live Utility. The full distribution is the
least troublesome choice for this template because it includes the packages
used by the glossary, table, listing, color, and hyperlink features.
Either download the signed installer from the MacTeX web site and complete the
macOS installation dialog, or, if Homebrew is already installed, run:
\begin{verbatim}
brew install --cask mactex
\end{verbatim}
Close and reopen Terminal after installation. If the TeX commands are not
found, add the MacTeX binary directory to the current shell session:
\begin{verbatim}
export PATH="/Library/TeX/texbin:$PATH"
\end{verbatim}
Do not place this command in a shell profile until it has been verified on the
local computer.
\subsection{Verification and Compilation}
From the template directory, verify the tools:
\begin{verbatim}
command -v pdflatex bibtex latexmk makeglossaries makeindex
\end{verbatim}
Each command should print a path. Then create the PDF:
\begin{verbatim}
make -f makefileMacLinux pdf
\end{verbatim}
To remove generated intermediate files while keeping the PDF, run:
\begin{verbatim}
make -f makefileMacLinux clean
\end{verbatim}
\section{Install LaTeX Tools on Windows}
\index{Windows}
\index{MiKTeX}
\subsection{Recommended Installation}
Install the current Basic MiKTeX distribution using the official
\href{https://miktex.org/howto/install-miktex}{MiKTeX Windows installation
guide}. During installation, choose a per-user installation when appropriate
and set missing-package installation to \emph{Ask me first} or \emph{Yes}.
The latter is convenient for a student machine with Internet access; the former
makes each package installation explicit.
Open MiKTeX Console after installation, apply available updates, and install
the \texttt{latexmk} package if the verification command below cannot find it.
On an organization-managed computer, obtain approval before installing software
or changing execution policy.
\subsection{Verification and Compilation}
Open a new PowerShell window in the template directory and verify:
\begin{verbatim}
Get-Command pdflatex,bibtex,latexmk,makeglossaries,makeindex
\end{verbatim}
Each command should display a command record. Build the PDF with:
\begin{verbatim}
.\makefilePowerShell.ps1 -Action pdf
\end{verbatim}
If PowerShell blocks the script under a managed execution policy, ask the
system administrator for the approved procedure. Do not bypass institutional
security policy. To remove intermediate build files, run:
\begin{verbatim}
.\makefilePowerShell.ps1 -Action clean
\end{verbatim}
\section{Common Build Problems}
\begin{tabularx}{\linewidth}{L{3.0cm}Y}
\toprule \textbf{Symptom} & \textbf{Resolution}\\ \midrule
Command not found & Close and reopen the terminal. On macOS, verify the TeX
binary path; on Windows, verify MiKTeX completed installation and is on the user PATH.\\
Missing LaTeX package & Use MiKTeX Console or its approved on-the-fly package
installation prompt on Windows. On macOS, install the full MacTeX distribution
or use TeX Live Utility to add the missing package.\\
References or glossary incomplete & Run the supplied platform build instead of
a single \texttt{pdflatex} command; it runs BibTeX, the glossary tool, and the
index tool in the required order.\\
Script is blocked & Follow the institution's approved PowerShell policy or run
the documented build commands with an approved administrator.\\ \bottomrule
\end{tabularx}
\section{Authoring and Review Rules}
Replace bracketed placeholders with assignment facts. Keep operational examples
safe, bounded, and free of credentials. Link activities to evidence, retain
history, and state unknowns or instructor decisions explicitly. Rebuild after
structural, cross-reference, table, glossary, or index changes.
+22
View File
@@ -0,0 +1,22 @@
\chapter*{Document History}
\addcontentsline{toc}{chapter}{Document History}
\index{document history}
\section*{How to Use This Chapter}
Add the newest approved change at the top. Retain every approved entry and
link each entry to the changed chapter, table, figure, or controlled artifact.
\begin{tabularx}{\linewidth}{L{3.1cm}L{2.2cm}YL{2.0cm}}
\caption{Document Update History\label{Table:DocumentHistory}}\\
\toprule \textbf{Date / Version} & \textbf{Author Initials} & \textbf{Change} & \textbf{Changed At}\\ \midrule
\endfirsthead
\multicolumn{4}{l}{\small\itshape Document Update History (continued)}\\
\toprule \textbf{Date / Version} & \textbf{Author Initials} & \textbf{Change} & \textbf{Changed At}\\ \midrule
\endhead \bottomrule \endlastfoot
September 24, 2026 / 1.5 & MF, AN, SS, MN & Created the Digital Ocean Docker Deployment, LaTeX, and Project Proposal chapters. & \ChapterRef{DigitalOceanDocker}, \ChapterRef{LatexDocker}, \ChapterRef{ProjectProposal}\\
September 16, 2026 / 1.4 & MF, AN, SS, MN & Created the Linux Commands chapter at \texttt{linuxCommands.tex}. & \ChapterRef{LinuxCommands}\\
September 2, 2026 / 1.3 & DDM, CDX & Established the one-file-per-chapter convention, added it to the AI Agent Usage Guide, and consolidated the complete example assignment chapter into \texttt{devopsExampleAssignment.tex}. & \ChapterRef{ExampleAssignment}, \ChapterRef{AIAgentGuide}, \ChapterRef{DocumentGuide}\\
September 2, 2026 / 1.2 & DDM, CDX & Added macOS MacTeX and Windows MiKTeX installation, verification, compilation, cleanup, and troubleshooting instructions to the Document Authoring Guide appendix. & \ChapterRef{DocumentGuide}\\
September 2, 2026 / 1.1 & DDM, CDX & Reorganized the example assignment as one chapter with activity, workflow, evidence, retrospective, and submission sections so each homework can follow the same chapter pattern. & \ChapterRef{ExampleAssignment}\\
September 2, 2026 / 1.0 & DDM, CDX & Created the reusable SSW 590 DevOps assignment template from the architecture template infrastructure; replaced requirements and design chapters with activity, workflow, evidence, retrospective, and submission chapters. & \ChapterRef{Introduction}, \ChapterRef{ExampleAssignment}, \ChapterRef{AIAgentGuide}\\
\textit{[date / version]} & \textit{[initials]} & \textit{[approved change and rationale]} & \textit{[link]}\\
\end{tabularx}
+126
View File
@@ -0,0 +1,126 @@
\chapter{Example Assignment}
\ChapterLabel{ExampleAssignment}
\index{activity brief}
\index{workflow}
\index{automation}
\index{infrastructure as code}
\index{evidence}
\index{observability}
\index{rollback}
\index{retrospective}
\index{continuous improvement}
\index{submission}
\index{cleanup}
\section{Activity Brief}
\subsection{Scenario}
Describe the operational scenario and why it matters. Use a bounded,
non-production system. \textit{Example: A team must deploy a small service
reliably to a remote Linux host and recover from a failed release.}
\subsection{Activities}
Assign stable IDs to each assessed activity. Add rows as needed.
\begin{tabularx}{\linewidth}{L{2.1cm}L{3.1cm}Y}
\caption{Activity Register\label{Table:ActivityRegister}}\\
\toprule \textbf{ID} & \textbf{Activity} & \textbf{Completion criterion}\\ \midrule
\ActivityID{ACT-01} & \textit{[Prepare environment]} & \textit{[repeatable environment baseline is present]}\\
\ActivityID{ACT-02} & \textit{[Automate workflow]} & \textit{[workflow runs from documented inputs]}\\
\ActivityID{ACT-03} & \textit{[Observe and recover]} & \textit{[evidence shows monitoring and recovery]}\\ \bottomrule
\end{tabularx}
\subsection{Success Criteria}
State observable outcomes, not implementation prescriptions. Include security,
cost, cleanup, and reproducibility criteria where applicable.
\subsection{Assessment Rubric}
\begin{tabularx}{\linewidth}{L{3.2cm}L{2.0cm}Y}
\toprule \textbf{Criterion} & \textbf{Weight} & \textbf{Evidence expected}\\ \midrule
Reproducible workflow & \textit{[points]} & \textit{[automation and clean-run proof]}\\
Operational quality & \textit{[points]} & \textit{[observability, rollback, security]}\\
Evidence and explanation & \textit{[points]} & \textit{[commands, outputs, rationale]}\\
Reflection & \textit{[points]} & \textit{[failure analysis and improvement]}\\ \bottomrule
\end{tabularx}
\section{Workflow and Implementation}
\subsection{Environment Baseline}
Specify the authorized environment, prerequisites, access model, versioned
inputs, and teardown procedure. Record commands in a repository script or
infrastructure-as-code definition; do not rely on a sequence of undocumented
console clicks.
\subsection{Procedure}
For each activity, state the goal, input, command or automation entry point,
expected result, and rollback or cleanup action. Link the corresponding
activity identifier and evidence identifier.
\begin{lstlisting}[language=bash,caption={Replace with a safe, repeatable workflow entry point.}]
# ./scripts/provision.sh --environment sandbox
# ./scripts/deploy.sh --version <tag>
# ./scripts/destroy.sh --environment sandbox
\end{lstlisting}
\subsection{Operational Controls}
Describe least-privilege access, secret handling, logging, monitoring, change
control, budget guardrails, backup or rollback, and resource cleanup. A secret
shall be referenced by its approved storage location, never pasted into this
document or committed to the repository.
\subsection{Failure Exercise}
Define one safe failure or degradation scenario. State the trigger, expected
signal, diagnosis path, recovery action, and evidence required. Never direct
students to disrupt shared or production infrastructure.
\section{Evidence Register}
\subsection{How to Use This Section}
Evidence demonstrates an actual execution; an expected result is not evidence.
Use sanitized command output, logs, screenshots, links to versioned files, or
recorded metrics. Redact secrets and personal information.
\begin{tabularx}{\linewidth}{L{1.7cm}L{2.2cm}L{2.6cm}Y}
\caption{Evidence Register\label{Table:EvidenceRegister}}\\
\toprule \textbf{ID} & \textbf{Activity} & \textbf{Evidence type} & \textbf{Location and interpretation}\\ \midrule
\EvidenceID{EVD-01} & \ActivityRef{ACT-01} & \textit{[log / script / image]} & \textit{[repository path or sanitized URL; what it demonstrates]}\\
\EvidenceID{EVD-02} & \ActivityRef{ACT-02} & \textit{[pipeline output]} & \textit{[location; result and relevant timestamp]}\\
\EvidenceID{EVD-03} & \ActivityRef{ACT-03} & \textit{[monitoring / recovery output]} & \textit{[location; failure signal and recovery result]}\\ \bottomrule
\end{tabularx}
\subsection{Evidence Quality Checks}
Evidence should identify the executed version, environment, time, command or
automation entry point, outcome, and any deviation from the expected result.
State failures plainly; they are valuable inputs to the retrospective.
\section{Retrospective}
\subsection{Outcome}
Summarize what happened, including completed work, gaps, and unexpected
behavior. Distinguish facts from interpretation.
\subsection{Flow and Feedback}
Identify queues, handoffs, manual steps, slow feedback, and sources of rework.
Explain which changes improved delivery flow or made failures visible sooner.
This emphasis on flow, feedback, and continual learning is consistent with the
DevOps framing introduced in \cite{PhoenixProject}.
\subsection{Improvement Experiments}
Propose one or more small, testable improvements. State the hypothesis, change,
measurement, expected result, and risk. Do not represent a proposal as an
approved production change.
\subsection{Team Contributions}
For team assignments, concisely identify each member's meaningful contribution
and any collaboration constraints.
\section{Submission and Cleanup}
\subsection{Submission Checklist}
\begin{itemize}
\item Versioned automation, configuration, and runbook are committed.
\item Evidence links resolve and contain no secrets or personal data.
\item The retrospective is complete and distinguishes results from proposals.
\item Remote resources have been destroyed or explicitly transferred as directed.
\item The repository identifies the final commit or release tag.
\end{itemize}
\subsection{Academic Integrity and Responsible Use}
Follow the course policy for collaboration and AI assistance. Cite external
code, tutorials, and AI-generated material as required by the instructor.
Students remain responsible for understanding, testing, and explaining all
submitted work.
+13
View File
@@ -0,0 +1,13 @@
\newacronym{ai}{AI}{artificial intelligence}
\newacronym{ci}{CI}{continuous integration}
\newacronym{cd}{CD}{continuous delivery or continuous deployment}
\newacronym{iac}{IaC}{infrastructure as code}
\newacronym{slo}{SLO}{service-level objective}
\newglossaryentry{automation}{name={automation},description={A repeatable, version-controlled procedure executed by software rather than undocumented manual steps}}
\newglossaryentry{deployment}{name={deployment},description={The controlled delivery of a versioned change to an environment}}
\newglossaryentry{evidence}{name={evidence},description={A retained record of an actual execution, observation, or review}}
\newglossaryentry{observability}{name={observability},description={The ability to infer system behavior from signals such as logs, metrics, traces, and health checks}}
\newglossaryentry{pipeline}{name={pipeline},description={An automated sequence that builds, tests, packages, and/or deploys a change}}
\newglossaryentry{rollback}{name={rollback},description={A controlled return to a known acceptable version or state after a failed or harmful change}}
\newglossaryentry{runbook}{name={runbook},description={A documented operational procedure for a repeatable task or incident response}}
\newglossaryentry{synonyms}{name={synonyms},description={Equivalent terms used in this assignment; record preferred terms to avoid ambiguity in instructions and evidence}}
+47
View File
@@ -0,0 +1,47 @@
\chapter{Introduction}
\ChapterLabel{Introduction}
\index{introduction}
\index{DevOps}
\section{How to Use This Assignment}
Replace bracketed text with approved assignment facts. Delete instructional
examples before release, but retain the stable identifiers, history, glossary,
index, appendices, and evidence structure. This document is an assignment
brief and operational record, not a requirements or software-design document.
\section{Purpose and Learning Objectives}
State the DevOps capability students will practice and the observable learning
outcomes. \textit{Example: Students will provision a minimal Linux service,
automate its deployment, collect operational evidence, and demonstrate a safe
rollback.}
\section{Scope and Constraints}
State the permitted cloud or local environment, team size, required tools,
budget limits, source-control repository, timebox, and prohibited actions.
Do not include credentials, access tokens, student records, or production data.
\section{Deliverables and Due Date}
\begin{tabularx}{\linewidth}{L{3.2cm}Y}
\toprule \textbf{Item} & \textbf{Expectation}\\ \midrule
Due date & \DueDate\\
Repository & \textit{[repository URL or LMS location]}\\
Required deliverables & \textit{[automation, runbook, evidence, reflection]}\\
Team policy & \textit{[individual or team; contribution expectations]}\\ \bottomrule
\end{tabularx}
\section{Using This Template with AI Agents}
\index{AI agent!DevOps workflow}
AI may assist with explanation, drafting, troubleshooting, and reviewing
automation. It does not replace student understanding or authorize an agent to
operate accounts, approve work, invent results, or receive secrets. Before
editing, an AI agent shall read \texttt{devopsAIAgentGuide.tex}, load only the
relevant assignment files, identify assumptions, and preserve evidence
boundaries. Record material AI assistance in \ChapterRef{AIPromptLog}.
\begin{verbatim}
Read devopsAIAgentGuide.tex first. For this SSW 590 assignment, identify the
requested task and read only the listed sources. Use supplied evidence only;
do not invent commands, deployment results, approvals, credentials, or costs.
Preserve stable IDs and history. Make the smallest appropriate change, build
the PDF, and report assumptions, risks, and actions requiring human approval.
\end{verbatim}
+6
View File
@@ -0,0 +1,6 @@
@book{PhoenixProject,
author = {Kim, Gene and Behr, Kevin and Spafford, George},
title = {The Phoenix Project: A Novel about IT, DevOps, and Helping Your Business Win},
publisher = {IT Revolution Press},
year = {2018}
}
+228
View File
@@ -0,0 +1,228 @@
\chapter{Digital Ocean Docker Deployment}
\section{Overview}
This chapter documents the deployment of the group's Docker-based website to a DigitalOcean Droplet. The application is a Node.js and Express website containing two buttons. The buttons change the background color of the webpage to either blue or red.
The website was modified to use a class-based JavaScript implementation. The application was then deployed inside a Docker container running on an Ubuntu DigitalOcean Droplet.
\section{DigitalOcean Droplet Setup}
A DigitalOcean Droplet was created using Ubuntu as the operating system. Password authentication was selected during Droplet creation. The Droplet was accessed remotely using SSH with the root account and the password configured during creation.
The Droplet was accessed with the following command:
\begin{minted}{bash}
ssh root@<DROPLET-IP>
\end{minted}
After connecting to the Droplet, the Ubuntu package lists were updated and available packages were upgraded.
\begin{minted}{bash}
sudo apt update
sudo apt upgrade -y
\end{minted}
\section{Installing Docker}
Docker Engine was installed on the Ubuntu Droplet using Docker's official Ubuntu package repository.
First, the required packages were installed:
\begin{minted}{bash}
sudo apt install ca-certificates curl -y
\end{minted}
The Docker repository signing key was then installed:
\begin{minted}{bash}
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
-o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
\end{minted}
The Docker repository was added:
\begin{minted}{bash}
sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
\end{minted}
The package list was updated and Docker was installed:
\begin{minted}{bash}
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin -y
\end{minted}
Docker was checked to verify that the service was running:
\begin{minted}{bash}
sudo systemctl status docker
\end{minted}
The installation was tested using Docker's Hello World image:
\begin{minted}{bash}
sudo docker run hello-world
\end{minted}
The successful execution of the Hello World container verified that Docker was installed and functioning on the Droplet.
\section{Website Application}
The application uses Node.js and Express. The Docker project contains the following files:
\begin{itemize}
\item \texttt{Dockerfile} -- defines how the Docker image is built.
\item \texttt{package.json} -- defines the Node.js application and its dependencies.
\item \texttt{server.js} -- starts the Express web server.
\item \texttt{public/index.html} -- contains the webpage.
\item \texttt{public/script.js} -- contains the class-based JavaScript implementation.
\end{itemize}
The Dockerfile uses Node.js 20 Alpine as its base image and exposes port 3000.
\section{Class-Based JavaScript}
The website was changed from individual button event handlers to a reusable \texttt{ColorButton} class.
\begin{minted}{javascript}
class ColorButton {
constructor(buttonId, color) {
this.button = document.getElementById(buttonId);
this.color = color;
this.initialize();
}
initialize() {
this.button.addEventListener("click", () => this.handleClick());
}
handleClick() {
document.body.style.backgroundColor = this.color;
}
}
const blueButton = new ColorButton("blueBtn", "blue");
const redButton = new ColorButton("redBtn", "red");
\end{minted}
The \texttt{ColorButton} class contains the button element and its associated color. The constructor receives the HTML element ID and the color, and \texttt{initialize()} registers the click event. When the button is clicked, \texttt{handleClick()} changes the webpage background color.
Two objects are created from the class:
\begin{itemize}
\item \texttt{blueButton} uses the \texttt{blueBtn} element and the color \texttt{blue}.
\item \texttt{redButton} uses the \texttt{redBtn} element and the color \texttt{red}.
\end{itemize}
\section{UML Class Diagram}
Figure~\ref{fig:color-button-uml} shows the class-based design. Both button objects are instances of the \texttt{ColorButton} class.
\begin{figure}[htbp]
\centering
\includegraphics[width=0.75\textwidth]{ColorButtonUML.png}
\caption{UML class diagram for the class-based ColorButton implementation}
\label{fig:color-button-uml}
\end{figure}
\section{Transferring the Application to the Droplet}
The application files were transferred to the DigitalOcean Droplet. For example, the project directory can be copied from the local computer with:
\begin{minted}{bash}
scp -r color-buttons-digitalocean root@<DROPLET-IP>:/root/
\end{minted}
After the files were transferred, an SSH connection was used to access the project:
\begin{minted}{bash}
ssh root@<DROPLET-IP>
cd /root/color-buttons-digitalocean
\end{minted}
\section{Building the Docker Image}
The Docker image was built from the project's Dockerfile:
\begin{minted}{bash}
docker build -t color-buttons-app .
\end{minted}
The resulting Docker image can be viewed with:
\begin{minted}{bash}
docker images
\end{minted}
\section{Running the Docker Container}
The application container was started with the following command:
\begin{minted}{bash}
docker run -d --name color-buttons-app -p 80:3000 color-buttons-app
\end{minted}
The \texttt{-d} option runs the container in detached mode. The \texttt{--name} option gives the container the name \texttt{color-buttons-app}. The \texttt{-p 80:3000} option maps port 80 on the DigitalOcean Droplet to port 3000 inside the Docker container.
The resulting connection is:
\begin{center}
\texttt{Internet -> Droplet port 80 -> Docker port 3000 -> Express server}
\end{center}
The running container was verified with:
\begin{minted}{bash}
docker ps
\end{minted}
The port mapping should appear similar to:
\begin{minted}{text}
0.0.0.0:80->3000/tcp
\end{minted}
\section{Deployment Verification}
The deployed website was accessed using the public IP address assigned to the DigitalOcean Droplet.
\begin{center}
\url{http://<DROPLET-IP>}
\end{center}
The website was tested by selecting both buttons. Selecting the blue button changed the webpage background to blue. Selecting the red button changed the webpage background to red.
\begin{figure}[htbp]
\centering
\includegraphics[width=0.90\textwidth]{digitalOceanWebsite.png}
\caption{Website running from the DigitalOcean Docker deployment}
\label{fig:digitalocean-website}
\end{figure}
\section{Deployment Summary}
The deployment process consisted of the following steps:
\begin{enumerate}
\item A DigitalOcean Ubuntu Droplet was created.
\item The Droplet was accessed using SSH and password authentication.
\item Ubuntu was updated.
\item Docker Engine was installed and tested.
\item The class-based JavaScript website was transferred to the Droplet.
\item A Docker image named \texttt{color-buttons-app} was built.
\item A Docker container was created from the image.
\item Port 80 on the Droplet was mapped to port 3000 in the container.
\item The website was accessed through the Droplet's public IP address.
\item The blue and red buttons were tested to verify the deployed application.
\end{enumerate}
Binary file not shown.

After

Width:  |  Height:  |  Size: 586 KiB

+13
View File
@@ -0,0 +1,13 @@
version: '3.8'
services:
gitea-runner:
image: gitea/act_runner:latest
restart: always
environment:
- GITEA_INSTANCE_URL=https://git.ssw590.tech/
- GITEA_RUNNER_REGISTRATION_TOKEN=UDRiZQW3uEUZDd3nSbDy848WY6pPnBNbRcAT9m6Y
- GITEA_RUNNER_NAME=latex-compile-runner
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./data:/data
Binary file not shown.

After

Width:  |  Height:  |  Size: 371 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

+472
View File
@@ -0,0 +1,472 @@
% 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.
+24
View File
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Product</title>
</head>
<body>
<header>
<h1>Product</h1>
<p>Product landing page.</p>
</header>
<main>
<section>
<h2>About the Product</h2>
<p>
This page provides the initial landing-page structure
for the startup's product.
</p>
</section>
</main>
</body>
</html>
+49
View File
@@ -0,0 +1,49 @@
\chapter{LaTeX Docker}
\ChapterLabel{LatexDocker}
To compile a LaTeX file through a Docker container, the first step was to create a Dockerfile. Using the texlive and latexmk frameworks, we created this Dockerfile:
\begin{minted}{javascript}
FROM debian:bookworm-slim
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \
texlive-latex-base \
texlive-latex-recommended \
texlive-latex-extra \
texlive-fonts-recommended \
latexmk \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /data
CMD ["latexmk", "-pdf", "main.tex"]
\end{minted}
The file uses a lightweight Debian base to install the requird tools from texlive and latexmk. From there, it establishes /data as the working directory of the container, and runs a command to compile a LaTeX file named "main.tex".
\newpage
From there, we build the container by placing the Dockerfile inside the directory of the LaTeX project and running:
\begin{minted}{bash}
docker build -t my-latex-compiler .
\end{minted}
This sets up the container, named "my-latex-compiler" for use. To start the container and compile the LaTeX project, we run:
\begin{minted}{bash}
docker run --rm -v "$(pwd):/data" my-latex-compiler
\end{minted}
Once the command runs, a main.pdf file is created inside the LaTeX project directory, containing the compiled project. To test this, we ran the container on a barebones sample document containing some text, an image, and a table. The compiler created an identical result to downloading the document as a pdf from Overleaf, proving its success. The only downsides are that the primary .tex file of the project being compiled must be named main.tex, and that LaTeX errors that Overleaf can still compile through cannot be ignored by our compiler. These can be overcome by changing the expected name in the Dockerfile and being more thorough in error fixing when manually editing the LaTeX.
\begin{figure}[hbt!]
\centering
\includegraphics[width=1\linewidth]{sample.pdf}
\caption{The pdf result of the Docker compiler on our sample document, with whitespace cropped out for readability.}
\label{fig:placeholder}
\end{figure}
+133
View File
@@ -0,0 +1,133 @@
% SSW 590 DevOps Assignment Template
% Developed by Darian Muresan, Ph.D., with OpenAI Codex and Anthropic Claude Code.
% AI agents: read devopsAIAgentGuide.tex before editing this template.
\documentclass[12pt]{cornell}
\usepackage[T1]{fontenc}
\usepackage{lmodern}
\usepackage{xspace}
\usepackage{xcolor}
\usepackage{graphicx}
\usepackage{fancyhdr}
\usepackage{amsmath,amssymb}
\usepackage{array}
\usepackage{booktabs}
\usepackage{longtable}
\usepackage{tabularx}
\usepackage{ltablex}
\keepXColumns
\usepackage{enumitem}
\usepackage{listings}
\usepackage{tcolorbox}
\usepackage{makeidx}
\usepackage[colorlinks=true,citecolor=blue,linkcolor=blue,urlcolor=blue]{hyperref}
\usepackage[acronym,toc]{glossaries}
\usepackage{minted}
\makeindex
\makeglossaries
\input{devopsGlossary.tex}
\definecolor{devopsBlue}{RGB}{31,78,121}
\definecolor{devopsGreen}{RGB}{46,125,50}
\definecolor{devopsRed}{RGB}{183,28,28}
\definecolor{devopsGray}{RGB}{90,90,90}
\definecolor{devopsLightBlue}{RGB}{232,240,247}
% Replace these values for each assignment.
\newcommand{\AssignmentTitle}{Linux Commands}
\newcommand{\AssignmentNumber}{2}
\newcommand{\DocumentVersion}{1.4}
\newcommand{\DocumentStatus}{}
\newcommand{\CourseNumber}{SSW 590}
\newcommand{\CourseName}{DevOps}
\newcommand{\Instructor}{Maxwell Friedman, Alex Nemirovsky, Sofia Stoeckel, and Magdola Nagy}
\newcommand{\Organization}{Stevens Institute of Technology}
\newcommand{\DocumentDate}{September 16th, 2026}
\newcommand{\DueDate}{}
\newcommand{\VERSION}{Version \DocumentVersion}
\hypersetup{
pdftitle={\CourseNumber\ --- Assignment \AssignmentNumber: \AssignmentTitle},
pdfauthor={\Instructor},
pdfcreator={Darian Muresan, Ph.D., with OpenAI Codex and Anthropic Claude Code},
pdfsubject={Reusable DevOps course assignment template},
pdfkeywords={DevOps, automation, delivery pipeline, infrastructure as code, evidence}
}
\newcolumntype{Y}{>{\raggedright\arraybackslash}X}
\newcolumntype{L}[1]{>{\raggedright\arraybackslash}p{#1}}
\setlength{\tabcolsep}{4pt}
\renewcommand{\arraystretch}{1.12}
\setlength{\LTpre}{6pt}
\setlength{\LTpost}{6pt}
% Stable IDs make activity, evidence, and artifact references durable.
\newcommand{\ArtifactLabel}[2]{\phantomsection\label{#1:#2}}
\newcommand{\ArtifactID}[2]{\texttt{#2}\ArtifactLabel{#1}{#2}\index{#1!#2}}
\newcommand{\ArtifactRef}[2]{\hyperref[#1:#2]{\texttt{#2}}}
\newcommand{\ActivityID}[1]{\ArtifactID{Activity}{#1}}
\newcommand{\ActivityRef}[1]{\ArtifactRef{Activity}{#1}}
\newcommand{\EvidenceID}[1]{\ArtifactID{Evidence}{#1}}
\newcommand{\EvidenceRef}[1]{\ArtifactRef{Evidence}{#1}}
\newcommand{\ChapterLabel}[1]{\label{Chapter:#1}}
\newcommand{\ChapterRef}[1]{\hyperref[Chapter:#1]{Chapter~\ref*{Chapter:#1}}}
\newcommand{\StatusPass}{\textbf{\footnotesize\textcolor{devopsGreen}{PASS}}}
\newcommand{\StatusPending}{\textbf{\footnotesize\textcolor{devopsGray}{PENDING}}}
\newcommand{\StatusFail}{\textbf{\footnotesize\textcolor{devopsRed}{FAIL}}}
\newtcolorbox{devopsNote}[1][]{
colback=devopsLightBlue, colframe=devopsBlue, boxrule=0.6pt, arc=1mm, title=#1
}
\lstset{
backgroundcolor=\color{white}, basicstyle=\ttfamily\footnotesize,
breaklines=true, frame=single, keywordstyle=\color{blue},
commentstyle=\color{devopsGreen}, numbers=left, numbersep=6pt,
numberstyle=\tiny\color{devopsGray}, showstringspaces=false,
stringstyle=\color{devopsRed}, tabsize=2
}
\changenormalspacing{\singlespacing}
\begin{document}
\pagenumbering{roman}
\singlespacing
\include{prologue}
\include{devopsIntroduction}
\include{linuxCommands}
\include{digitalOceanDocker}
\include{latexDocker}
\include{projectProposal}
\include{giteaStartup}
\setcounter{page}{1}
\pagenumbering{arabic}
\pagestyle{fancy}
\fancyhf{}
\renewcommand{\chaptermark}[1]{\markboth{#1}{}}
\lhead{\small Assignment \AssignmentNumber}
\chead{\small\nouppercase{\leftmark}}
\rhead{\small\thepage}
\lfoot{\small\CourseNumber}
\cfoot{\small\copyright\ \Organization}
\rfoot{\small\VERSION}
\renewcommand{\headrulewidth}{0.4pt}
\renewcommand{\footrulewidth}{0.4pt}
\bibliographystyle{IEEEtran}
\bibliography{devopsReferences}
\appendix
\include{devopsDocumentGuide}
\include{devopsAIAgentGuide}
\include{devopsAIPromptLog}
\glsaddall
\printglossaries
\cleardoublepage
\phantomsection
\ChapterLabel{Index}
\addcontentsline{toc}{chapter}{Index}
\markboth{Index}{Index}
\printindex
\end{document}
+26
View File
@@ -0,0 +1,26 @@
\chapter{Project Proposal}
\ChapterLabel{ProjectProposal}
\section{SecureURL: A Containerized URL Shortener}
This project will develop a simple web-based URL shortening application that allows users to enter a long URL and receive a shortened URL that redirects to the original address. The application will also store URLs and basic usage information in a database and may provide statistics such as the number of times a shortened URL has been accessed.
\section{DevOps Tools Used}
\begin{longtable}{|p{7cm}|p{7cm}|}
\caption{DevOps Tools} \label{tab:devopstools}\\
\hline
\textbf{Application} & Node.js/Express\\
\hline
\textbf{Source Control} & Git/GitHub \\
\hline
\textbf{CI/CD} & GitHub Actions \\
\hline
\textbf{CI/CD Comparison} & Jenkins \\
\hline
\textbf{Testing} & Jest \\
\hline
\textbf{Containerization} & Docker \\
\hline
\textbf{Database} & PostgreSQL \\
\hline
\textbf{Cloud Hosting} & DigitalOcean Droplet \\
\hline
\end{longtable}
+27
View File
@@ -0,0 +1,27 @@
\title{\CourseNumber\ --- \CourseName\\[0.5em]\large Assignment \AssignmentNumber: \AssignmentTitle\\[1.5em]\normalsize\VERSION\\\DocumentStatus}
\author{\Instructor\\\Organization}
\conferraldate{}{\DocumentDate}
\copyrightholder{\Organization}
\copyrightyear{2026}
\maketitle
\makecopyright
\cleardoublepage
\phantomsection
\chapter*{Template Attribution}
\ChapterLabel{TemplateAttribution}
\addcontentsline{toc}{chapter}{Template Attribution}
\markboth{Template Attribution}{Template Attribution}
\index{template attribution}
\index{Muresan, Darian}
This DevOps assignment template was developed by \textbf{Darian Muresan, Ph.D.},
in conjunction with \textbf{OpenAI Codex} and \textbf{Anthropic Claude Code}.
\clearpage
\chapter*{ABSTRACT}
\input{abstract.tex}
\clearpage
\input{devopsDocumentHistory.tex}
\contentspage
\tablelistpage
\figurelistpage
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

+12
View File
@@ -0,0 +1,12 @@
#!/bin/bash
set -e
echo "Checking repository files..."
test -f README.md
test -f index.html
echo "README.md: OK"
echo "index.html: OK"
echo "All repository checks passed."