Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a5f2b4288 | ||
|
|
7040a00708 | ||
|
|
68613f38df | ||
|
|
ba7eb30138 | ||
|
|
d601968d32 | ||
|
|
9a5897e4a5 | ||
|
|
fa8fb79b63 | ||
|
|
b7aaebc7d6 | ||
|
|
de01f8cd0f | ||
|
|
5d639aec2c | ||
|
|
ab15ed05f1 | ||
|
|
d317824678 | ||
|
|
94ad7637ae | ||
|
|
9b72a1761d |
@@ -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
|
||||
@@ -0,0 +1,2 @@
|
||||
build/
|
||||
*.class
|
||||
@@ -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
@@ -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'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user