Author SHA1 Message Date
vpenalosa 4ac97909df test
Build SmartPark manual / manual (push) Successful in 2m43s
2026-10-14 20:01:57 -04:00
vpenalosa 00336deb06 Merge branch 'chapter3-build-test' of https://git.137-184-97-250.nip.io/stevens-smartpark/smartpark-app into chapter3-build-test
Build SmartPark manual / manual (push) Successful in 1m0s
2026-10-09 23:56:16 -04:00
vpenalosa fdbafea0e2 Update Assignment 5 title and document history 2026-10-09 23:55:32 -04:00
nicasaoi d8db2a5bc5 fixed image
Build SmartPark manual / manual (push) Successful in 1m2s
2026-10-10 03:54:08 +00:00
nicasaoi 36842058bc Update latex-manual/devopsAutomatedManualBuild.tex
Build SmartPark manual / manual (push) Failing after 15s
2026-10-10 03:45:02 +00:00
vpenalosa ae0bfa28a1 Merge branch 'chapter3-build-test' of https://git.137-184-97-250.nip.io/stevens-smartpark/smartpark-app into chapter3-build-test
Build SmartPark manual / manual (push) Failing after 15s
2026-10-09 23:40:44 -04:00
vpenalosa ae48b2844d reuploading the screenshots 2026-10-09 23:40:41 -04:00
nicasaoi 9aa1d6b883 runner screenshots
Build SmartPark manual / manual (push) Failing after 15s
2026-10-10 03:37:55 +00:00
nicasaoi 7f9364c72a Update latex-manual/devopsAutomatedManualBuild.tex
Build SmartPark manual / manual (push) Failing after 16s
2026-10-10 03:28:17 +00:00
nicasaoi 30eeaf9c93 Complete all eight manual sections
Build SmartPark manual / manual (push) Failing after 16s
2026-10-10 03:24:23 +00:00
nicasaoi a0d512ec6e Upload files to "latex-manual/png"
Build SmartPark manual / manual (push) Successful in 51s
2026-10-10 02:48:11 +00:00
nicasaoi d39aca16d9 Update latex-manual/devopsAutomatedManualBuild.tex
Build SmartPark manual / manual (push) Successful in 50s
2026-10-10 02:45:41 +00:00
vincentpenalosa fccb25e82f Revert "Demonstrate controlled LaTeX compilation failure"
Build SmartPark manual / manual (push) Successful in 46s
This reverts commit 0bb378bb09.
2026-10-09 22:33:05 -04:00
vincentpenalosa 0bb378bb09 Demonstrate controlled LaTeX compilation failure
Build SmartPark manual / manual (push) Failing after 34s
2026-10-09 22:31:24 -04:00
vincentpenalosa e16a6deab2 Document second manual build demonstration
Build SmartPark manual / manual (push) Successful in 37s
2026-10-09 22:20:19 -04:00
vincentpenalosa 9fec46a41e Document first manual build demonstration
Build SmartPark manual / manual (push) Successful in 41s
2026-10-09 22:12:11 -04:00
vincentpenalosa efe92994f6 Prepare automated manual build and Chapter 3 draft
Build SmartPark manual / manual (push) Successful in 51s
2026-10-09 21:01:49 -04:00
22 changed files with 913 additions and 3 deletions
+36
View File
@@ -0,0 +1,36 @@
name: Build SmartPark manual
on:
push:
branches:
- main
- chapter3-build-automation
- chapter3-build-test
jobs:
manual:
runs-on: smartpark-latex
timeout-minutes: 20
steps:
- name: Check out the triggering revision
uses: https://github.com/actions/checkout@v4.2.2
with:
ref: ${{ github.sha }}
fetch-depth: 1
persist-credentials: false
- name: Build PDF and record source identity
env:
MANUAL_CI: 'true'
MANUAL_COMMIT: ${{ github.sha }}
MANUAL_RUN_NUMBER: ${{ github.run_number }}
run: python3 ci/build_manual.py
- name: Publish PDF
# v3 uses the legacy artifact protocol commonly supported by Gitea.
# Actual upload compatibility and retention must be verified on this server.
uses: https://github.com/actions/upload-artifact@v3.1.3
with:
name: smartpark-manual-${{ github.run_number }}-${{ github.sha }}
path: |
build/manual/SmartPark-Manual.pdf
build/manual/build-info.txt
if-no-files-found: error
retention-days: 30
+5
View File
@@ -0,0 +1,5 @@
# Chapter 3 generated outputs and private runner identity
/build/
.runner
/latex-manual/build-metadata.tex
+86
View File
@@ -0,0 +1,86 @@
#!/usr/bin/env python3
"""Build only from a fresh temporary copy; publish output only on success."""
from pathlib import Path
import datetime, os, re, shutil, subprocess, tempfile
ROOT = Path(__file__).resolve().parents[1]
SOURCE = ROOT / 'latex-manual'
OUT = ROOT / 'build' / 'manual'
def run(args, cwd=ROOT):
print('+ ' + ' '.join(args), flush=True)
subprocess.run(args, cwd=cwd, check=True)
def git(*args):
return subprocess.check_output(['git', *args], cwd=ROOT, text=True).strip()
def main():
OUT.mkdir(parents=True, exist_ok=True)
# Only remove this script's two fixed output files, so a failed run cannot
# leave a stale deliverable looking successful.
for name in ('SmartPark-Manual.pdf', 'build-info.txt'):
(OUT / name).unlink(missing_ok=True)
sha = git('rev-parse', 'HEAD')
expected = os.environ.get('MANUAL_COMMIT', '')
ci = os.environ.get('MANUAL_CI', '') == 'true'
dirty = bool(git('status', '--porcelain', '--untracked-files=normal'))
if ci and (not expected or expected != sha or dirty):
raise RuntimeError('CI checkout must be clean and match MANUAL_COMMIT')
number = os.environ.get('MANUAL_RUN_NUMBER', 'local')
if ci and not number.isdigit():
raise RuntimeError('Missing numeric Gitea run number')
if not re.fullmatch(r'[A-Za-z0-9.-]+', number):
raise RuntimeError('Unsafe run identifier')
revision = sha[:12] + ('-dirty' if dirty else '')
build_time = datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M UTC')
for tool in ('latexmk', 'pdflatex', 'bibtex', 'makeglossaries', 'makeindex', 'pygmentize'):
if shutil.which(tool) is None:
raise RuntimeError('Missing tool: ' + tool)
with tempfile.TemporaryDirectory(prefix='manual-', dir=ROOT / 'build') as temp:
work = Path(temp)
shutil.copytree(SOURCE, work, dirs_exist_ok=True,
ignore=shutil.ignore_patterns('*.aux', '*.log', '*.pdf', '*.toc', '*.out',
'*.bbl', '*.blg', '*.fdb_latexmk', '*.fls', '*.idx', '*.ind', '*.ilg',
'*.glo', '*.gls', '*.glg', '*.ist', '*.acn', '*.acr', '*.alg',
'*.lof', '*.lot', '_minted-*', 'build-metadata.tex'))
# Do not discard legitimate PDF figures: restore tracked/source PDFs
# except the old root document output.
for source_pdf in SOURCE.rglob('*.pdf'):
if source_pdf == SOURCE / 'devopsAssignment.pdf':
continue
target = work / source_pdf.relative_to(SOURCE)
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(source_pdf, target)
(work / 'build-metadata.tex').write_text(
'\\renewcommand{\\BuildRevision}{' + revision + '}\n' +
'\\renewcommand{\\BuildRun}{' + number + '}\n' +
'\\renewcommand{\\BuildUTC}{' + build_time + '}\n', encoding='utf-8')
command = ['latexmk', '-pdf', '-interaction=nonstopmode', '-halt-on-error',
'-file-line-error', '-shell-escape', 'devopsAssignment.tex']
run(command, work)
run(['makeglossaries', 'devopsAssignment'], work)
if (work / 'devopsAssignment.idx').stat().st_size:
run(['makeindex', 'devopsAssignment.idx'], work)
run(command, work)
pdf = work / 'devopsAssignment.pdf'
if not pdf.is_file() or pdf.stat().st_size < 5 or pdf.read_bytes()[:5] != b'%PDF-':
raise RuntimeError('Expected nonempty PDF was not produced')
log = (work / 'devopsAssignment.log').read_text(errors='replace')
if 'There were undefined references' in log or 'There were undefined citations' in log:
raise RuntimeError('PDF still contains unresolved references/citations; inspect source')
shutil.copy2(pdf, OUT / 'SmartPark-Manual.pdf')
info = f'commit={sha}\nrevision={revision}\nrun={number}\nutc={build_time}\n'
info += 'mode=' + ('Gitea Actions' if ci else 'LOCAL TEST - not Actions evidence') + '\n'
for tool, flag in [('latexmk','-v'), ('pdflatex','--version'), ('pygmentize','-V')]:
result = subprocess.check_output([tool, flag], text=True)
info += result
packages = subprocess.check_output(['dpkg-query', '-W',
'-f=${Package}\t${Version}\n', 'latexmk', 'texlive-base',
'texlive-latex-extra', 'texlive-fonts-recommended', 'lmodern', 'python3-pygments'], text=True)
info += '\nUbuntu package versions:\n' + packages
(OUT / 'build-info.txt').write_text(info, encoding='utf-8')
print(info)
print('SUCCESS: ' + str(OUT / 'SmartPark-Manual.pdf'))
if __name__ == '__main__':
main()
+13
View File
@@ -0,0 +1,13 @@
log:
level: info
runner:
file: /var/lib/smartpark-runner/.runner
capacity: 1
timeout: 20m
shutdown_timeout: 25m
labels:
- smartpark-latex:host
cache:
enabled: false
host:
workdir_parent: /var/lib/smartpark-runner/work
+46
View File
@@ -0,0 +1,46 @@
#!/usr/bin/env bash
# Run as your normal Ubuntu user, not as root. Uses sudo for installation.
set -euo pipefail
[[ $(uname -m) == x86_64 ]] || { echo 'This installer targets x86_64 only'; exit 1; }
base=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
[[ ! -e /etc/smartpark-runner/config.yaml ]] || { echo 'Runner already configured; inspect it rather than overwrite.'; exit 1; }
sudo apt-get update
sudo apt-get install --no-install-recommends curl ca-certificates xz-utils
downloads=$(mktemp -d)
cd "$downloads"
# Resolve the current Node 22 release from its official checksum list, then
# install and record that exact release. No downloaded install script is run.
curl -fSLO https://nodejs.org/dist/latest-v22.x/SHASUMS256.txt
node_archive=$(awk '$2 ~ /^node-v22\.[0-9]+\.[0-9]+-linux-x64\.tar\.xz$/ {print $2}' SHASUMS256.txt)
[[ -n "$node_archive" && "$node_archive" != *$'\n'* ]]
node_version=${node_archive#node-}
node_version=${node_version%-linux-x64.tar.xz}
curl -fSLO "https://nodejs.org/dist/$node_version/$node_archive"
awk -v f="$node_archive" '$2 == f' SHASUMS256.txt | sha256sum -c -
tar -xf "$node_archive"
[[ ! -e /opt/smartpark-node ]] || { echo '/opt/smartpark-node exists; inspect before continuing'; exit 1; }
sudo mv "${node_archive%.tar.xz}" /opt/smartpark-node
sudo chown -R root:root /opt/smartpark-node
runner_version=2.3.0
runner_file="gitea-runner-$runner_version-linux-amd64"
curl -fSLO "https://dl.gitea.com/gitea-runner/$runner_version/$runner_file"
curl -fSLO "https://dl.gitea.com/gitea-runner/$runner_version/$runner_file.sha256"
sha256sum -c "$runner_file.sha256"
sudo install -m 0755 "$runner_file" /usr/local/bin/gitea-runner
if id smartpark-runner >/dev/null 2>&1; then
echo 'smartpark-runner account already exists; inspect its permissions before continuing.'
exit 1
fi
sudo useradd --system --create-home --home-dir /var/lib/smartpark-runner --shell /usr/sbin/nologin smartpark-runner
sudo chmod 700 /var/lib/smartpark-runner
sudo install -d -o smartpark-runner -g smartpark-runner -m 700 /var/lib/smartpark-runner/work
sudo install -d -o root -g smartpark-runner -m 750 /etc/smartpark-runner
sudo install -o root -g smartpark-runner -m 640 "$base/runner-config.yaml" /etc/smartpark-runner/config.yaml
sudo install -m 644 "$base/smartpark-runner.service" /etc/systemd/system/smartpark-runner.service
sudo systemctl daemon-reload
sudo -u smartpark-runner /opt/smartpark-node/bin/node --version
sudo -u smartpark-runner /usr/local/bin/gitea-runner --version
sudo -u smartpark-runner latexmk -v
echo 'Installed, but NOT registered or started. Registration requires an authorized token.'
echo "Verified downloads remain in $downloads; remove those files later using the Files app if desired."
+23
View File
@@ -0,0 +1,23 @@
[Unit]
Description=SmartPark Gitea manual runner
After=network-online.target
Wants=network-online.target
[Service]
User=smartpark-runner
Group=smartpark-runner
WorkingDirectory=/var/lib/smartpark-runner
Environment=HOME=/var/lib/smartpark-runner
Environment=PATH=/opt/smartpark-node/bin:/usr/local/bin:/usr/bin:/bin
ExecStart=/usr/local/bin/gitea-runner daemon --config /etc/smartpark-runner/config.yaml
Restart=on-failure
RestartSec=10
UMask=0077
NoNewPrivileges=true
PrivateTmp=true
ProtectHome=true
ProtectSystem=full
TimeoutStopSec=30min
[Install]
WantedBy=multi-user.target
+304
View File
@@ -0,0 +1,304 @@
# SmartPark Chapter 3: local preparation and Gitea handoff
Prepared October 9, 2026. This kit is a draft implementation, not proof that
Gitea Actions is working. The original documentation was reported to compile
successfully in Vincent's Ubuntu VM. The new build script and edited manual
must be tested there. Registration, checkout in Actions, metadata, artifact
upload, retention, and required demonstrations remain pending.
## 1. Apply to Vincent's clean feature branch
Copy this ZIP into Ubuntu's Downloads folder and extract it there. The folder
must contain apply_to_repo.py and payload/. Hidden .gitea files are included
by the installer; do not copy the payload manually.
```bash
cd ~/smartpark-ch3-work/smartpark-app
python3 ~/Downloads/smartpark-ch3-kit/apply_to_repo.py "$PWD"
git diff --stat
git status --short
python3 ci/build_manual.py
```
The installer expects branch chapter3-build-automation, a clean checkout,
and the original version 1.8 source. It refuses changed layouts or existing
target files rather than overwriting them. If it refuses, preserve your work
and inspect the message. It is intended to run once.
Open build/manual/SmartPark-Manual.pdf. Check the new build identification
page, all original chapters, the new eight-section chapter, references,
glossary and index. A first local build should say local and dirty because
your new edits are not committed. It must not show a fabricated Actions run.
The new chapter number follows the existing manual; it addresses assignment
Chapter 3 even if it becomes Chapter 7 in your team's document.
The script removes only its two fixed prior deliverables and builds in a
fresh temporary directory. TeX failures return nonzero, leaving no prior
successful PDF in the publish directory. Console output includes the error;
temporary TeX files are removed after the attempt. Preserve terminal output
if troubleshooting is needed. Do not fix an error by ignoring its exit code.
## 2. Commit locally
Review all new files, including the workflow, before committing. If Git asks
for your identity, set your real name and your chosen Git commit email with
git config user.name and git config user.email (repository-local settings).
```bash
git diff --check
git add .gitignore .gitea/workflows/manual.yml ci docs/ch3-evidence.md docs/CH3-HANDOFF.md latex-manual/devopsAssignment.tex latex-manual/devopsDocumentHistory.tex latex-manual/devopsAutomatedManualBuild.tex
git commit -m "Prepare automated manual build and Chapter 3 draft"
python3 ci/build_manual.py
git status --short
```
After committing, the local PDF should identify that commit without dirty.
git status should be empty. The PDF remains local evidence only.
If using a log with tee, preserve failures with `set -o pipefail` first.
## 3. Prepare a runner (optional now; can be done by Nica later)
This installer targets only the dedicated Ubuntu x86_64 VM. It does not
configure the shared DigitalOcean Droplet. It downloads Node 22 from the
official release server, verifies its listed checksum, and records its exact
version in terminal output. It installs Gitea Runner 2.3.0 with its official
checksum. It creates an unprivileged service account, host-mode label
smartpark-latex:host, capacity one, and a system service. It neither registers
nor starts the runner. Node remains at /opt/smartpark-node/bin/node, not on
your normal user's PATH; the service PATH includes it.
Read ci/setup_runner.sh, then run:
```bash
bash ci/setup_runner.sh
df -h /
```
No Docker, exposed runner port, or Droplet login is needed. If setup fails,
stop at the actual error. It deliberately refuses to replace existing
accounts/configurations, so a partial installation must be inspected before
retrying. Do not blindly delete an existing service account or registration.
The source build tools installed by Vincent are:
```bash
sudo apt-get update
sudo apt-get install --no-install-recommends git python3 latexmk texlive-latex-extra texlive-fonts-recommended lmodern python3-pygments
```
Nica needs those packages if reproducing on her own dedicated Ubuntu host.
Record the actual installed package versions from build-info.txt. A fresh
apt installation may differ from Vincent's; rebuild and verify accordingly.
## 4. Registration: only after authorized Gitea admin help
Have the repository administrator confirm Actions is enabled and provide a
new repository-scoped registration token securely, or assist interactively.
Do not use a personal access token in place of a runner registration token.
Do not share the old Droplet's .runner identity. Registration is performed
on the machine that will execute jobs, with its own new identity.
Run in the Ubuntu VM:
```bash
sudo -u smartpark-runner sh -c 'umask 077; cd /var/lib/smartpark-runner && /usr/local/bin/gitea-runner --config /etc/smartpark-runner/config.yaml register'
```
Interactive answers:
- Instance URL: https://git.137-184-97-250.nip.io (not the repository URL).
- Token: enter it privately at the prompt; do not include it in screenshots.
- Name: vincent-ubuntu-manual (or the actual replacement owner's name).
- Labels: smartpark-latex:host. The supplied config already specifies it.
After successful registration:
```bash
sudo chmod 600 /var/lib/smartpark-runner/.runner
sudo systemctl enable --now smartpark-runner
sudo systemctl status smartpark-runner --no-pager
```
The administrator should confirm the runner appears online and is scoped to
smartpark-app. Review all workflows that may target this runner: push-only
triggers in this file do not prevent another workflow from using its label.
Host-mode jobs are not mutually isolated and can read the service account's
runner identity. Restrict workflow authors to trusted teammates. The service
account has no sudo or Docker group. Do not grant either just to fix a build.
NoNewPrivileges and other service restrictions help limit the account but
are not a sandbox against hostile jobs. Use a dedicated VM without personal
shared folders/keys. Workflow job tokens also require suitable permissions.
## 5. Push the prepared branch
```bash
cd ~/smartpark-ch3-work/smartpark-app
git push -u origin chapter3-build-automation
```
This requires branch write access, not repository Settings access. If denied,
use the handoff in section 9. If a push happened while Actions was disabled,
make a real source correction and push again once enabled. A push-triggered
build is the required evidence, not merely pressing a rerun button.
The workflow must be present on the branch that receives the push. If the
Actions page opens on main, select the branch/run list for your feature
branch. Do not assume an empty main view proves feature workflows are absent.
PR #8 does not have to be merged first. Coordinate eventual merge order so
the documentation and automation are both retained.
In the Actions run, verify checkout SHA, successful compile, and successful
artifact upload. Download the artifact, extract its PDF, and inspect the
visible run number and short SHA against the run and commit. The workflow
requests 30-day retention; ask the administrator what was actually applied.
If upload fails, the assignment is not complete even if the compile is green.
The workflow records checkout v4.2.2 and upload-artifact v3.1.3 as exact tags.
Tags are not immutable commit-SHA pins. The upload action uses the legacy
artifact protocol; its compatibility with this server still needs an actual
test. Do not change blindly to GitHub upload-artifact v4 to fix an error:
check the server's supported protocol/action and preserve the failure log.
## 6. Required success, failure and repair sequence
Use only the dedicated test branch. Start with a clean working tree and a
committed successful preparation. The following changes are intentional,
reviewable source changes. Record each run BEFORE the next push.
```bash
git switch -c chapter3-build-test
printf '\nFirst automated source-change demonstration.\\par\n' >> latex-manual/devopsAutomatedManualBuild.tex
git add latex-manual/devopsAutomatedManualBuild.tex
git commit -m "Document first manual build demonstration"
git push -u origin chapter3-build-test
```
Wait for success. Download PDF #1 and record the commit/run/version/artifact.
```bash
printf '\nSecond automated source-change demonstration.\\par\n' >> latex-manual/devopsAutomatedManualBuild.tex
git add latex-manual/devopsAutomatedManualBuild.tex
git commit -m "Document second manual build demonstration"
git push
```
Wait for success. Download PDF #2 and record matching evidence. Now create
the controlled error on this test branch:
```bash
printf '\n\\SmartParkIntentionalBuildFailure\n' >> latex-manual/devopsAutomatedManualBuild.tex
git add latex-manual/devopsAutomatedManualBuild.tex
git commit -m "Demonstrate controlled LaTeX compilation failure"
git push
```
Wait for the failed compile, preserve the error/run/commit and confirm the
upload step did not publish a successful PDF. Then repair by reverting only
that latest failure commit. Run the following ONLY while that failure commit
is still HEAD and the working tree is clean:
```bash
git log -1 --oneline
git status --short
git revert --no-edit HEAD
git push
```
Capture the repaired successful run and PDF. Fill docs/ch3-evidence.md with
real results and transfer them into the manual chapter. Replace demonstration
sentences with the actual findings in a later editorial commit.
## 7. Complete the manual
The eight sections are skeletons. Keep earlier chapters and fill actual
runner, network and security details, source/run/artifact links, dated
screenshots, expected/actual outcomes, and failed-build troubleshooting.
Update Hosts and Passwords-hints tables and explicitly record their changes.
Review document version, date, title and assignment number (the older title
and assignment number were deliberately preserved for team decision), add
bibliography/glossary entries, and log AI assistance per team conventions.
The added history row is a draft with human review pending.
Arrange peer review and merge through the normal process. Do not bypass
Jenkins required checks or branch protections. After integration into main,
confirm its push-triggered run and download the final artifact. Submit only
the PDF extracted from that successful Actions artifact, not this kit or a
local PDF. The source/script preparation alone is not submission-ready.
## 8. Availability and troubleshooting
- A user service terminal is not needed: systemd starts the runner when Ubuntu
boots. The VM and Windows computer must still be awake and connected.
- Disable automatic suspend in Ubuntu's Power settings while running jobs;
keep Windows awake during agreed build/grading periods. A laptop is not
an always-on server unless you actually keep it available.
- Reboot the VM after initial success, check service status and online state,
and push a harmless source correction to verify recovery.
- Queued: check online status, repository scope and exact smartpark-latex label.
- Checkout: check network/TLS and job-token repository permissions. Do not
disable certificate checks or insert a personal token into the workflow.
- JavaScript/action error: confirm the service's /opt/smartpark-node/bin/node
exists; inspect the action's compatibility and runner logs.
- TeX: fix the first real error. Undefined references may be inherited source
problems; resolve them rather than remove the quality check.
- Artifact: inspect the separate upload step, server protocol and access.
- Logs: sudo journalctl -u smartpark-runner -n 80 --no-pager (review/redact
before sharing). Never dump all environment variables or the .runner file.
- Stop: sudo systemctl stop smartpark-runner. Retire: disable service and ask
an administrator to remove the registration; never send identity files.
## 9. Handoff to Nica if access is unavailable
Commit the prepared files before packaging. Use the branch containing the
latest completed, repaired work. This archives tracked source only and
excludes runner credentials, build output and your Git login settings:
```bash
git status --short
git log -1 --oneline
git archive --format=zip --output="$HOME/Downloads/smartpark-ch3-source-handoff.zip" HEAD
```
Also create a Git bundle of the current branch so Nica can retain commit
identity (the build script requires a Git checkout):
```bash
git bundle create "$HOME/Downloads/smartpark-ch3.git.bundle" HEAD
git bundle verify "$HOME/Downloads/smartpark-ch3.git.bundle"
```
Send that ZIP and Git bundle plus a short factual status: commit ID, actual local test
result, who will host the runner, what needs registration, and which Gitea
checks remain pending. The ZIP includes docs/CH3-HANDOFF.md and the evidence
worksheet. If Nica will host it, she follows the same setup and registers a
new identity on her host; do not copy /var/lib/smartpark-runner/.runner.
If branch access works, prefer a pushed branch and PR link for review.
If Nica receives only these files, she can start a NEW checkout (destination
must not already exist) from the bundle without depending on your laptop:
```bash
git clone ~/Downloads/smartpark-ch3.git.bundle ~/smartpark-ch3-takeover
cd ~/smartpark-ch3-takeover
git switch -c chapter3-nica-takeover
git remote set-url origin https://git.137-184-97-250.nip.io/stevens-smartpark/smartpark-app.git
```
This already includes the applied changes; do not run the kit installer again.
She must add chapter3-nica-takeover to the workflow's push branch list if
testing pushes on that branch. She should inspect the bundle/source contents
before running scripts. The source ZIP is for browsing/import only; a plain
unpacked ZIP does not provide the Git commit information the build requires.
## Official references and verification limits
- https://docs.gitea.com/runner/2/installation/binary/
- https://docs.gitea.com/runner/2/registration/
- https://docs.gitea.com/runner/2/labels/
- https://docs.gitea.com/usage/actions/artifacts/
- https://nodejs.org/dist/latest-v22.x/
The local Ubuntu VM is separate from the assistant's Windows workspace.
This kit was prepared there; actual TeX compilation, Linux installation,
service operation and Gitea behavior must be verified in the real environment.
+18
View File
@@ -0,0 +1,18 @@
# Chapter 3 evidence worksheet
Prepared 2026-10-09. This table is deliberately unfilled. Record real results.
| Date/time | Branch | Full commit | Run URL and number | Expected | Actual | PDF visible ID | Artifact/download proof |
|---|---|---|---|---|---|---|---|
| Pending | chapter3-build-test | Pending | Pending | Success 1 | Pending | Pending | Pending |
| Pending | chapter3-build-test | Pending | Pending | Success 2 | Pending | Pending | Pending |
| Pending | chapter3-build-test | Pending | Pending | Controlled TeX failure | Pending | No new successful PDF | Pending |
| Pending | chapter3-build-test | Pending | Pending | Repair success | Pending | Pending | Pending |
Also retain: runner online/assigned job, nonsecret config, package versions,
actual retention policy, resource measurements, service restart test,
permissions review, PDF visual review, review/merge links and final artifact.
User reported the original source compiled locally on Ubuntu with latexmk
4.76, pdfTeX 1.40.22, Pygments 2.11.2 and makeglossaries 4.49.
That is not proof of this new script or any Gitea execution.
+15
View File
@@ -52,6 +52,21 @@ platform.'' Source inspection confirmed eight section headings and the new
chapter's position after the five existing chapters. Complete project chapter's position after the five existing chapters. Complete project
compilation and final layout verification remain pending in Overleaf}. compilation and final layout verification remain pending in Overleaf}.
\section{Assignment 5 Title and Document History Update}
Date: \textit{October 9, 2026};
Tool: \textit{OpenAI Codex};
Task: \textit{Update the assignment number to 5 and title to ``LaTeX
Auto-Compilation with Gitea,'' and summarize Nica Saoi's work across all
eight Automated Manual Build sections in the document history};
Inputs: \textit{The user's requested title, number, and author attribution,
and the existing Automated Manual Build chapter};
Output used: \textit{Updated assignment metadata and a new history entry
covering all eight section topics while retaining prior history};
Human review and corrections: \textit{Review pending};
Unresolved assumptions: \textit{The chapter's operational results were
summarized from the existing text, not independently reverified during this
editorial update}.
\section{Linux Commands Assignment Record} \section{Linux Commands Assignment Record}
Scope: \textit{The existing record below covers the earlier Linux Commands formatting work; the separate record for Questions 31--40 documents Vincent Penalosa's AI use.}; Scope: \textit{The existing record below covers the earlier Linux Commands formatting work; the separate record for Questions 31--40 documents Vincent Penalosa's AI use.};
+21 -3
View File
@@ -39,9 +39,16 @@
\definecolor{devopsLightBlue}{RGB}{232,240,247} \definecolor{devopsLightBlue}{RGB}{232,240,247}
% Replace these values for each assignment. % Replace these values for each assignment.
\newcommand{\AssignmentTitle}{Domain Names, SSL, and Versioning} \newcommand{\AssignmentTitle}{LaTeX Auto-Compilation with Gitea}
\newcommand{\AssignmentNumber}{\#4} \newcommand{\AssignmentNumber}{\#5}
\newcommand{\DocumentVersion}{1.8} \newcommand{\DocumentVersion}{1.9-draft}
% Generated only in the disposable build workspace; never committed.
\newcommand{\BuildRevision}{unrecorded}
\newcommand{\BuildRun}{local}
\newcommand{\BuildUTC}{unrecorded}
\InputIfFileExists{build-metadata.tex}{}{}
\newcommand{\DocumentStatus}{Draft} \newcommand{\DocumentStatus}{Draft}
\newcommand{\CourseNumber}{SSW 590} \newcommand{\CourseNumber}{SSW 590}
\newcommand{\CourseName}{DevOps} \newcommand{\CourseName}{DevOps}
@@ -97,6 +104,16 @@
\pagenumbering{roman} \pagenumbering{roman}
\singlespacing \singlespacing
\include{prologue} \include{prologue}
\clearpage
\section*{Build identification}
\noindent Manual version: \DocumentVersion\par
\noindent Build identifier: \texttt{\DocumentVersion+build.\BuildRun.\BuildRevision}\par
\noindent Source revision: \texttt{\BuildRevision}\par
\noindent Workflow run: \texttt{\BuildRun}\par
\noindent Built: \BuildUTC\par
\noindent A run marked local or a revision marked dirty is a local test,
not evidence of a successful Gitea Actions run.\par
\clearpage
\setcounter{page}{1} \setcounter{page}{1}
\pagenumbering{arabic} \pagenumbering{arabic}
@@ -119,6 +136,7 @@
\include{devopsLaTeXDocker} \include{devopsLaTeXDocker}
\include{devopsProjectProposal} \include{devopsProjectProposal}
\include{devopsGitea} \include{devopsGitea}
\include{devopsAutomatedManualBuild}
+272
View File
@@ -0,0 +1,272 @@
\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.
+18
View File
@@ -22,6 +22,24 @@ link each entry to the changed chapter, table, figure, or controlled artifact.
\bottomrule \bottomrule
\endlastfoot \endlastfoot
October 9, 2026 / 1.9-draft & VP, CDX &
Updated the assignment to \#5, ``LaTeX Auto-Compilation with Gitea.''
Recorded Nica Saoi's documentation of all eight sections of Automated
Manual Build: source and trigger; runner setup; reproducible compilation;
PDF version traceability; downloadable artifact; change, failure, and repair
evidence; runner and secret boundary; and manual quality and controls.
The chapter documents the successful Gitea Actions builds, downloadable PDFs
and matching build identifiers, and the controlled failure and repair sequence. &
\ChapterRef{AutomatedManualBuild}\\
October 9, 2026 / 1.9-draft & CDX; VP review pending &
Prepared host-mode manual build automation, generated build identification
and an eight-section Automated Manual Build draft. Execution and Gitea
evidence remain pending. Existing Hosts and Passwords tables are unchanged;
runner entries and credential hints still require review. &
\ChapterRef{AutomatedManualBuild}\\
September 30, 2026 / 1.8 & NS, VP, CDX & September 30, 2026 / 1.8 & NS, VP, CDX &
Merged the team's supplied Gitea Sections 6.1--6.4 with the documented Merged the team's supplied Gitea Sections 6.1--6.4 with the documented
Sections 6.5--6.8. Preserved the reported Student Pack approval and pending Sections 6.5--6.8. Preserved the reported Student Pack approval and pending
Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

+28
View File
@@ -0,0 +1,28 @@
commit=9fec46a41ef1dcdffb40d0429e19475ba21af41c
revision=9fec46a41ef1
run=2
utc=2026-10-10 02:12 UTC
mode=Gitea Actions
Latexmk, John Collins, 20 November 2021. Version 4.76
pdfTeX 3.141592653-2.6-1.40.22 (TeX Live 2022/dev/Debian)
kpathsea version 6.3.4/dev
Copyright 2021 Han The Thanh (pdfTeX) et al.
There is NO warranty. Redistribution of this software is
covered by the terms of both the pdfTeX copyright and
the Lesser GNU General Public License.
For more information about these matters, see the file
named COPYING and the pdfTeX source.
Primary author of pdfTeX: Han The Thanh (pdfTeX) et al.
Compiled with libpng 1.6.37; using libpng 1.6.37
Compiled with zlib 1.2.11; using zlib 1.2.11
Compiled with xpdf version 4.03
Pygments version 2.11.2, (c) 2006-2021 by Georg Brandl, Matthäus Chajdas and contributors.
Ubuntu package versions:
latexmk 1:4.76-1
lmodern 2.004.5-6.1
python3-pygments 2.11.2+dfsg-2ubuntu0.1
texlive-base 2021.20220204-1
texlive-fonts-recommended 2021.20220204-1
texlive-latex-extra 2021.20220204-1
+28
View File
@@ -0,0 +1,28 @@
commit=e16a6deab277621f860c8a057a80c9737b5eb4b1
revision=e16a6deab277
run=3
utc=2026-10-10 02:26 UTC
mode=Gitea Actions
Latexmk, John Collins, 20 November 2021. Version 4.76
pdfTeX 3.141592653-2.6-1.40.22 (TeX Live 2022/dev/Debian)
kpathsea version 6.3.4/dev
Copyright 2021 Han The Thanh (pdfTeX) et al.
There is NO warranty. Redistribution of this software is
covered by the terms of both the pdfTeX copyright and
the Lesser GNU General Public License.
For more information about these matters, see the file
named COPYING and the pdfTeX source.
Primary author of pdfTeX: Han The Thanh (pdfTeX) et al.
Compiled with libpng 1.6.37; using libpng 1.6.37
Compiled with zlib 1.2.11; using zlib 1.2.11
Compiled with xpdf version 4.03
Pygments version 2.11.2, (c) 2006-2021 by Georg Brandl, Matthäus Chajdas and contributors.
Ubuntu package versions:
latexmk 1:4.76-1
lmodern 2.004.5-6.1
python3-pygments 2.11.2+dfsg-2ubuntu0.1
texlive-base 2021.20220204-1
texlive-fonts-recommended 2021.20220204-1
texlive-latex-extra 2021.20220204-1
Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB