Author SHA1 Message Date
smartpark-ci 1a5f2b4288 Repair parking availability test (refs #9)
smartpark-app-ci/pipeline/pr-main This commit looks good
2026-10-08 16:24:10 -04:00
smartpark-ci 7040a00708 Demonstrate failing Jenkins check (refs #9)
smartpark-app-ci/pipeline/pr-main There was a failure building this commit
2026-10-08 16:12:43 -04:00
smartpark-ci 68613f38df Add simulated parking capacity and Jenkins CI (refs #9)
smartpark-app-ci/pipeline/pr-main This commit looks good
2026-10-08 11:24:57 -04:00
vincentpenalosa ba7eb30138 Merge pull request 'Expand SmartPark product README' (#7) from issue-2-smartpark-readme into main
Reviewed-on: https://git.137-184-97-250.nip.io/stevens-smartpark/smartpark-app/pulls/7
Reviewed-by: vincentpenalosa <vpenalos@stevens.edu>

good overview of the project
2026-09-30 23:38:48 +00:00
nicasaoi d601968d32 Removed extra spaces in Planned Parking Information 2026-09-30 23:37:08 +00:00
vincentpenalosa 9a5897e4a5 Merge pull request 'Document Gitea deployment and operations' (#6) from issue-1-gitea-operations into main
Reviewed-on: https://git.137-184-97-250.nip.io/stevens-smartpark/smartpark-app/pulls/6

.env: domain isn't a developer pack but professor said it wasn't necessary as long as ssl certificate is valid, doesn't expose any keys/passwords
compose.yaml: gitea image is correct, persistent storage is mounted at /data, gitea joins color-net, backend port isn't published properly
Caddyfile.example: hostname properly routes to gitea:3000
docs/gitea-operations.md:
- mentions the preservation of volumes when replacing containers
- mentioned a successful ACME issuance dry run
- mentions the ports caddy successfully exposes, mentions gitea staying private and admin ssh being restricted
- does not mention an unapproved-source SSH denial after firewall attachment

everything looks good! passes all the verification and checks
2026-09-30 23:07:54 +00:00
nicasaoi fa8fb79b63 Added Current Project Status for SmartPark product README 2026-09-30 22:25:37 +00:00
nicasaoi b7aaebc7d6 Expand SmartPark product README 2026-09-30 22:15:09 +00:00
jjradzz de01f8cd0f Update docs/gitea-operations.md 2026-09-29 22:02:56 +00:00
jjradzz 5d639aec2c issue-1-gitea-operations
Add example Gitea hostname setting
2026-09-29 21:21:17 +00:00
jjradzz ab15ed05f1 Upload files to "/"
Document Caddy HTTPS and redirect routes.
2026-09-29 21:20:03 +00:00
jjradzz d317824678 issue-1-gitea-operations
Add versioned Gitea Compose configuration
2026-09-29 21:18:44 +00:00
jjradzz 94ad7637ae Update docs/gitea-operations.md 2026-09-29 21:12:46 +00:00
jjradzz 9b72a1761d issue-1-gitea-operations
Document Gitea deployment and operations for issue #1
2026-09-29 21:06:51 +00:00
10 changed files with 403 additions and 2 deletions
+4
View File
@@ -0,0 +1,4 @@
# Temporary hostname for the deadline-driven Gitea deployment.
# It resolves to the existing Droplet but does not meet the Student Pack
# domain-offer rubric item. Replace it if the team later claims a domain.
GITEA_DOMAIN=git.137-184-97-250.nip.io
+2
View File
@@ -0,0 +1,2 @@
build/
*.class
+14
View File
@@ -0,0 +1,14 @@
# Current Caddy routes on the shared Droplet, recorded 2026-09-29.
# Replace the temporary nip.io names if the team later claims its own domain.
137-184-97-250.nip.io {
reverse_proxy color-buttons-site:3000
}
http://137.184.97.250 {
redir https://137-184-97-250.nip.io{uri} permanent
}
git.137-184-97-250.nip.io {
reverse_proxy gitea:3000
}
Vendored
+34
View File
@@ -0,0 +1,34 @@
pipeline {
agent any
options {
skipDefaultCheckout()
disableConcurrentBuilds()
timeout(time: 10, unit: 'MINUTES')
buildDiscarder(logRotator(numToKeepStr: '10'))
}
stages {
stage('Checkout') {
steps {
checkout scm
sh 'git rev-parse HEAD'
}
}
stage('Compile and lint') {
steps {
sh '''
mkdir -p build/classes
javac -Xlint:all -Werror -d build/classes \
src/main/java/edu/stevens/smartpark/ParkingLot.java \
src/test/java/edu/stevens/smartpark/ParkingLotTest.java
'''
}
}
stage('Unit tests') {
steps {
sh 'java -cp build/classes edu.stevens.smartpark.ParkingLotTest'
}
}
}
}
+84 -2
View File
@@ -1,3 +1,85 @@
# smartpark-app
# Stevens SmartPark
Campus parking information system
Stevens SmartPark is a proposed campus parking information system for Stevens Institute of Technology.
The goal of the project is to make it easier for students to view parking information for major campus parking locations before deciding where to park.
# Intended Users
The initial system is intended for Stevens students who need parking information for campus parking areas.
# Proposed Parking Locations
The project currently focuses on these five proposed locations:
- Babbio Garage
- River Lot
- 8th Street Lot
- Castle Point Hall Lot
- Howe Center Lot
# Planned Parking Information
For each parking location, the planned application may display information such as:
- available parking spaces
- total parking spaces
- whether the location is open or closed
- opening and closing times
- overnight parking rules
- handicapped parking availability
- electric-vehicle charging availability and, when applicable, pricing
# Simulated Parking Activity
Real-time Stevens parking data may not be available to the project, so the application is planned to support simulated parking activity.
For example, when a simulated vehicle enters a parking location, the available-space count can decrease by one. When a simulated vehicle leaves, the available-space count can increase by one.
Any parking capacity or availability values used during development are **sample data** and should not be interpreted as live Stevens parking information.
Example:
```text
Parking Location: Babbio Garage
Total Spaces: 120 # sample data
Available Spaces: 42 # sample data
Open: Yes
Opening Time: 7:00 AM
Closing Time: 11:00 PM
Overnight Parking: No
EV Charging: Available
```
# Current Project Status
The SmartPark application itself and live Stevens campus parking data are not currently deployed.
The team's currently deployed operational work hub is Gitea, which is being used for source control, issues, pull requests, reviews, and project history.
## Simulated parking-capacity core
`ParkingLot` is a small, in-memory Java 21 component for the proposed SmartPark
application. It records a lot name, total capacity, and available spaces.
`vehicleEnters()` decreases availability; `vehicleLeaves()` increases it. The
component rejects counts outside the range from zero to capacity and prevents
arrivals at a full lot or departures from an empty lot. It does not fetch live
campus data, save state, or expose a web interface.
The repository's `Jenkinsfile` compiles this component with all Java compiler
warnings treated as errors, then runs automated boundary and transition tests.
The only build dependency is JDK 21, already present in the selected Jenkins
image. A clean local run from the repository root is:
```bash
mkdir -p build/classes
javac -Xlint:all -Werror -d build/classes \
src/main/java/edu/stevens/smartpark/ParkingLot.java \
src/test/java/edu/stevens/smartpark/ParkingLotTest.java
java -cp build/classes edu.stevens.smartpark.ParkingLotTest
```
Expected test output is `ParkingLot tests passed`. A nonzero exit status means
the compile or test step failed. The generated `build/` directory is ignored by
Git. The Jenkins pipeline is intended for trusted team changes only; it runs
on the Jenkins controller because this small assignment deployment has no
separate build agent.
+18
View File
@@ -0,0 +1,18 @@
services:
jenkins:
image: jenkins/jenkins:2.580.1-jdk21
restart: unless-stopped
mem_limit: 768m
environment:
JAVA_OPTS: "-Xms128m -Xmx384m"
volumes:
- jenkins_home:/var/jenkins_home
networks:
- color-net
volumes:
jenkins_home:
networks:
color-net:
external: true
+22
View File
@@ -0,0 +1,22 @@
services:
gitea:
image: docker.gitea.com/gitea:1.27.3
restart: unless-stopped
environment:
GITEA__database__DB_TYPE: sqlite3
GITEA__server__DOMAIN: ${GITEA_DOMAIN:?Set GITEA_DOMAIN in .env}
GITEA__server__ROOT_URL: https://${GITEA_DOMAIN:?Set GITEA_DOMAIN in .env}/
GITEA__server__DISABLE_SSH: "true"
GITEA__service__DISABLE_REGISTRATION: "true"
volumes:
- gitea_data:/data
networks:
- color-net
# No host ports: Caddy reaches gitea:3000 on the private Docker network.
volumes:
gitea_data:
networks:
color-net:
external: true
+101
View File
@@ -0,0 +1,101 @@
# Stevens SmartPark Gitea operations
Status: deployed and checked on 2026-09-29. This document describes the Gitea
work hub, not a deployed SmartPark parking application. The earlier Two Buttons
site remains on the same Droplet as a separate exercise.
## Service and architecture
- Public work hub: https://git.137-184-97-250.nip.io/
- Product repository: https://git.137-184-97-250.nip.io/stevens-smartpark/smartpark-app
- Host: one DigitalOcean Ubuntu 22.04 Droplet, 1 vCPU, 1 GiB RAM, 25 GiB disk.
- Gitea image: `docker.gitea.com/gitea:1.27.3`, configured with Docker Compose.
- Caddy is the public reverse proxy. The Cloud Firewall admits TCP 443 and TCP
80 for HTTPS and HTTP-to-HTTPS redirect. SSH/TCP 22 is restricted to the
approved administrator IPv4 source. Gitea publishes no host port.
- Caddy terminates TLS and forwards privately to `gitea:3000` on Docker network
`color-net`. Gitea uses SQLite at `/data/gitea/gitea.db`; its `/data` directory
is backed by the named volume `gitea-foundation_gitea_data`.
- The `caddy_data` and `caddy_config` named volumes persist Caddy certificate
and configuration state. The live Caddyfile is mounted read-only from the
Droplet's `/root/color-buttons-app/Caddyfile`.
- The current `nip.io` hostname is a temporary address. It is **not** the
required GitHub Student Developer Pack domain offer; that requirement remains
outstanding for the team manual.
## Start and inspect
Sign in to the Droplet with the administrator's SSH key. Do not share the key,
passwords, tokens, or the Gitea database in the repository or report.
```bash
cd /root/gitea-foundation
docker compose config --quiet
docker compose up -d
docker compose ps
docker logs --tail 50 gitea-foundation-gitea-1
```
The expected Gitea container status is `Up`; its port listing should contain
only internal `22/tcp` and `3000/tcp`, without a host address mapping. Git over
SSH is disabled; HTTPS is the intended public access path. Check the public
repository URL in a browser and verify that the certificate is trusted.
## Restart and troubleshoot
```bash
cd /root/gitea-foundation
docker compose restart gitea
docker compose ps
docker logs --tail 100 gitea-foundation-gitea-1
docker exec color-buttons-caddy wget -S -O /dev/null http://gitea:3000/
docker exec color-buttons-caddy caddy validate --config /etc/caddy/Caddyfile
```
If the private `wget` check fails, inspect the Gitea logs, container status,
and `color-net` membership. If private access works but public HTTPS fails,
inspect Caddy's status and logs, its Caddyfile, the DNS lookup, and the Cloud
Firewall rules. Validate the Caddyfile before reloading it. Preserve the named
volumes when replacing containers; do not use `docker compose down -v` for a
routine restart or persistence test.
## Verification performed
On 2026-09-29, the team recorded the SmartPark repository and first issue,
recreated only the Gitea container with
`docker compose up -d --force-recreate gitea`, and verified that the same
repository and issue remained available over valid HTTPS afterward. This
demonstrates container-replacement persistence, **not** backup or restore.
After moving public TCP 80 from the old app container to Caddy, independent
external checks confirmed that both Gitea and the Two Buttons site redirect
HTTP to trusted HTTPS pages. Gitea's backend TCP 3000, database TCP 5432, and
remote Docker management TCP 2375/2376 were unreachable from the test source.
The DigitalOcean Cloud Firewall was then attached to the correct Droplet;
screenshots show its inbound rules and Droplet assignment. After attachment,
the approved administrator completed a fresh SSH login, public HTTP redirected
to working HTTPS, and external checks found TCP 3000 and 5432 unreachable.
An unapproved-source SSH denial was not retested after attachment. Record the
test sources and times in the manual.
The Gitea certificate was issued by Let's Encrypt for the temporary hostname
and was valid through 2026-12-28 UTC when checked. Caddy manages renewal and
its `/data` state persists. A separate staging-CA hostname successfully
completed an ACME issuance dry run on 2026-09-29; it returned HTTP 200 with a
matching staging certificate. The staging route was then removed and both
live HTTPS sites were rechecked. This proves the ACME challenge and issuance
path works; it does not claim an observed renewal of the live certificate.
## Recovery and records
A container replacement leaves the data volume in place but cannot recover
deleted or corrupt volume contents. On 2026-09-29, the operator briefly stopped
Gitea and archived the entire named data volume, including SQLite, application
configuration, and Git repositories. Gitea restarted normally. The archive was
restored into a separate volume and Gitea instance bound only to Droplet
localhost. The restored test repository and issue returned HTTP 200; the issue
retained its unique marker, and the restored Git endpoint returned the expected
commit. Original and restored browser screenshots were recorded. The backup
archive contains secrets and remains root-only on the Droplet; do not publish
it. Maintain dated, sanitized evidence in the team manual and update this page
after infrastructure changes.
@@ -0,0 +1,57 @@
package edu.stevens.smartpark;
/** A single parking location with simulated, in-memory availability. */
public final class ParkingLot {
private final String name;
private final int capacity;
private int availableSpaces;
public ParkingLot(String name, int capacity, int availableSpaces) {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("Parking lot name is required");
}
if (capacity < 0) {
throw new IllegalArgumentException("Capacity cannot be negative");
}
if (availableSpaces < 0 || availableSpaces > capacity) {
throw new IllegalArgumentException("Available spaces must be between zero and capacity");
}
this.name = name;
this.capacity = capacity;
this.availableSpaces = availableSpaces;
}
public String getName() {
return name;
}
public int getCapacity() {
return capacity;
}
public int getAvailableSpaces() {
return availableSpaces;
}
public int getOccupiedSpaces() {
return capacity - availableSpaces;
}
public boolean isFull() {
return availableSpaces == 0;
}
public void vehicleEnters() {
if (isFull()) {
throw new IllegalStateException("Parking lot is full");
}
availableSpaces--;
}
public void vehicleLeaves() {
if (availableSpaces == capacity) {
throw new IllegalStateException("Parking lot is already empty");
}
availableSpaces++;
}
}
@@ -0,0 +1,67 @@
package edu.stevens.smartpark;
/** Dependency-free tests that fail the process when parking rules break. */
public final class ParkingLotTest {
private ParkingLotTest() {
}
public static void main(String[] args) {
testArrivalsAndDepartures();
testFullAndEmptyBoundaries();
testInvalidStartingValues();
System.out.println("ParkingLot tests passed");
}
private static void testArrivalsAndDepartures() {
ParkingLot lot = new ParkingLot("Babbio Garage", 120, 42);
check(lot.getName().equals("Babbio Garage"), "name");
check(lot.getCapacity() == 120, "capacity");
check(lot.getOccupiedSpaces() == 78, "initial occupied spaces");
lot.vehicleEnters();
check(lot.getAvailableSpaces() == 41, "arrival decreases availability");
lot.vehicleLeaves();
check(lot.getAvailableSpaces() == 42, "departure restores availability");
}
private static void testFullAndEmptyBoundaries() {
ParkingLot full = new ParkingLot("River Lot", 1, 0);
check(full.isFull(), "zero available spaces means full");
expectIllegalState(full::vehicleEnters);
full.vehicleLeaves();
check(full.getAvailableSpaces() == 1, "departure from full lot");
expectIllegalState(full::vehicleLeaves);
full.vehicleEnters();
check(full.isFull(), "arrival fills the last space");
}
private static void testInvalidStartingValues() {
expectIllegalArgument(() -> new ParkingLot("", 10, 5));
expectIllegalArgument(() -> new ParkingLot("Lot", -1, 0));
expectIllegalArgument(() -> new ParkingLot("Lot", 10, -1));
expectIllegalArgument(() -> new ParkingLot("Lot", 10, 11));
}
private static void check(boolean condition, String description) {
if (!condition) {
throw new AssertionError("Failed: " + description);
}
}
private static void expectIllegalState(Runnable action) {
try {
action.run();
} catch (IllegalStateException expected) {
return;
}
throw new AssertionError("Expected IllegalStateException");
}
private static void expectIllegalArgument(Runnable action) {
try {
action.run();
} catch (IllegalArgumentException expected) {
return;
}
throw new AssertionError("Expected IllegalArgumentException");
}
}