Reviewed-on: #6
Portal
Portal manages scripts, repositories, credentials, files, and scheduled jobs. Its Angular client is bundled into an executable Spring Boot JAR with embedded Tomcat.
Requirements
| Component | Configuration |
|---|---|
| Java | JDK 21; check the active runtime with mvn -v |
| Maven | Maven 3.9.x; Jenkins uses maven-3.9.16 |
| Backend | Spring Boot 4.1.1, embedded Tomcat 11.0.25 |
| Frontend | Angular 22; Node.js 24.20.0 and npm 12.0.2 provisioned by Maven |
| Default database | Persistent H2 in MySQL compatibility mode |
| External database | MySQL Server 8.4+ with Connector/J 26.7.0 |
Versions are defined in pom.xml and portal_client/package.json. The frontend lockfile pins the installed npm dependency tree. Building requires access to the Locusworks Nexus repository, npm registry, and dependency-check data services. A global Node.js or npm installation is not required for Maven builds.
Build and run
Add your SSH public key to your Gitea account, then clone and build:
git clone ssh://gitea@gitea.locusworks.net:7999/locusworks/portal-webapp.git
cd portal-webapp
mvn clean install
java -jar portal_webapp/target/portal_webapp-1.0.0-RELEASE.jar
Open http://localhost:8080/portal/. The entry page redirects to /portal/client/, which uses hash-based routing. Use the corresponding JAR filename if the project version has changed.
The build installs frontend dependencies with npm ci, builds and tests the client, compiles and tests the Java modules, runs dependency checks, packages the executable JAR, and installs Maven artifacts into the local repository. It does not publish to Nexus or start the application.
Maven settings
The POM defines the locusworks-public dependency/plugin repository and the nexus-release and nexus-snapshot deployment repositories. If authentication is required, configure matching server IDs in your user Maven settings (~/.m2/settings.xml, or %USERPROFILE%\.m2\settings.xml on Windows):
<settings xmlns="http://maven.apache.org/SETTINGS/1.2.0">
<servers>
<server>
<id>locusworks-public</id>
<username>${env.NEXUS_USERNAME}</username>
<password>${env.NEXUS_PASSWORD}</password>
</server>
</servers>
</settings>
Supply those environment variables before building. Add nexus-release and nexus-snapshot server entries when publishing. The Nexus base URL defaults to https://nexus.locusworks.net and can be overridden with -Dnexus.repo=https://your-nexus-host.
Application configuration
Portal resolves its writable home directory in this order:
- JVM property
-Dportal.home=/path/to/portal. - Environment variable
PORTAL_HOME. ${user.home}/.portal.
java -Dportal.home=/var/lib/portal -jar portal_webapp/target/portal_webapp-1.0.0-RELEASE.jar
On Windows, use an absolute path such as -Dportal.home=D:/Portal/data. Put JVM properties before -jar.
The home directory holds portal.properties, the AES seed, portal-loggers.properties, logs, temporary key files, and the default H2 database under data/. Keep it separate from build output and preserve it when replacing the JAR. Encrypted settings depend on the AES seed, so back them up together.
| File | Purpose |
|---|---|
| application.properties | Spring Boot context path, session, multipart, and JPA settings |
| portal.properties | Defaults used to initialize and reconcile the persistent Portal configuration |
For another HTTP port, append --server.port=8081 to the Java command. The frontend assumes the /portal context path; changing it also requires updating client paths and rebuilding.
Databases
New installations default to dbType=h2. H2 uses a persistent file database and needs no separate database server. Existing installations retain their configured dbType; there is no automatic production switch to MySQL.
For external MySQL, set dbType=mysql and configure dbHost, dbPort, dbUsername, dbPassword, dbRootUser, and dbRootPassword in the persistent Portal settings. Runtime migrations use the root connection; normal application access uses the application connection. H2 has separate h2Url, h2Username, and h2Password settings.
Nonblank database passwords in portal.properties are read as encrypted values. Use Portal's configuration facilities to save encrypted credentials instead of putting plaintext in those fields. Connector/J 26.7.0 requires MySQL Server 8.4 or newer; see the MySQL release notes.
Frontend development
Build the complete project once to provision the pinned tools. From portal_client, use the local executables to avoid picking up a different global npm version.
PowerShell:
.\node\node.exe .\node\node_modules\npm\bin\npm-cli.js ci
.\node\node.exe .\node\node_modules\npm\bin\npm-cli.js run build
.\node\node.exe .\node\node_modules\npm\bin\npm-cli.js test
Linux/macOS:
./node/node ./node/node_modules/npm/bin/npm-cli.js ci
./node/node ./node/node_modules/npm/bin/npm-cli.js run build
./node/node ./node/node_modules/npm/bin/npm-cli.js test
With matching Node.js and npm versions on PATH, the equivalent commands are npm ci, npm run build, and npm test. The start script runs the Angular development server, but this project has no backend proxy configuration; API calls and realtime connections expect the /portal backend on the same origin. Use the packaged application for an integrated local run.
Client output goes to portal_client/dist/ and is copied into the backend JAR at build time. Rebuild the backend to include changed client assets. Login, dashboard, and feature pages load on demand; the initial-bundle warning budget remains 500 kB.
The allowScripts entries in package.json approve specific versions of native build helpers. Review and renew them when upgrading those dependencies. SockJS is explicitly allowed as a CommonJS dependency for the existing transport. STOMP uses its shipped ESM entry through the TypeScript path mapping because its 7.3.0 browser export selects UMD. npm warnings and failures remain visible; routine npm notices are omitted.
Tests and dependency checks
Run the complete verification with mvn clean install. To run the datasource test and its prerequisite Java modules:
mvn -pl portal_common -am test
The datasource test uses isolated in-memory H2 through portal.database.* JVM overrides and clears them afterward. It does not test an external MySQL instance. Frontend tests use Vitest through Angular CLI, including lazy login rendering and authentication redirects. Java test results are written to each module's target/surefire-reports/ directory.
OWASP Dependency-Check runs during verify, which is included in install. HTML reports are written to target/dependency-check-report.html in the root and module directories. NVD, RetireJS, and npm lockfile auditing remain enabled. The npm audit includes development dependencies; the Node Package Analyzer skips them to avoid warnings about uninstalled native binaries for other platforms. The POM sets failOnError=false for Dependency-Check, so review its output and reports even when Maven succeeds.
Sonatype OSS Index requires credentials and is opt-in. Add a server named oss-index to Maven settings with your account username and API token as the password, then run:
mvn clean install -Dportal.ossIndexEnabled=true
Deployment
Deploy portal_webapp/target/portal_webapp-<version>.jar and run it with Java 21 under your service manager. A separately installed Tomcat is not required. Configure a persistent Portal home writable by the service account.
scripts/deploy-server.sh copies the built JAR to deploy/portal.jar by default. PORTAL_DEPLOY_DIR changes the destination; PORTAL_SERVICE requests a systemd service restart after copying. In this helper, PORTAL_HOME refers to the source checkout, unlike the application's runtime data directory. Use -Dportal.home in the Java service command to keep those locations distinct.
Reverse proxy
Terminate HTTPS at NGINX or Apache and preserve /portal/ when forwarding to the backend on port 8080. Realtime endpoints need WebSocket upgrades as well as ordinary HTTP forwarding.
For NGINX, place the map in the http context and the location inside your site's HTTPS server block:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
# Inside the HTTPS server block:
location /portal/ {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 600s;
}
Supply your hostname, TLS certificates, and HTTP-to-HTTPS redirect in the surrounding configuration. Match proxy upload limits to the application's limits (10 MB by default). See NGINX WebSocket proxying. For Apache, enable mod_proxy and mod_proxy_http and configure WebSocket forwarding for your installed version; see the Apache proxy documentation.
Jenkins
Jenkinsfile defines a multibranch pipeline that checks out source, assigns a build version, updates a shared OWASP cache, runs mvn clean verify, publishes Java test results, archives JARs, and conditionally publishes Maven artifacts to Nexus.
The Build LaTeX stage runs only on branches matching release/** and compiles docs/src/portal.tex with three pdflatex passes to resolve references and the table of contents. The agent needs pdflatex on PATH and the document's LaTeX packages installed; neither latexmk nor Perl is required. Compilation errors fail the stage. The same stage saves target/latex/portal.pdf with fingerprinting before workspace cleanup.
Every successful release/** build records a release with an immutable Git tag named portal-release-${BUILD_VERSION}. The Build LaTeX stage also generates and archives target/release-notes.md, listing commit subjects and hashes since the nearest earlier release tag reachable from the current commit. The first recorded release includes the full history; a rebuild without new commits reports no source changes. Before compiling, the same stage uses awk to generate docs/src/appendix/ReleaseNotes.tex, which portal.tex includes immediately after the Revisions appendix. The generated TeX is archived alongside the PDF and Markdown. Commit text is escaped for LaTeX. Local builds use a placeholder until release notes are generated. The agent needs awk on its path. Notes are generated from Git history, so their detail depends on the commit messages.
The final Tag release stage pushes the tag only after the preceding build, PDF, and applicable Maven deployment stages succeed. Notes archived before a later failure are build artifacts, not a completed release marker. Failed builds do not publish a new release tag. The helper fetches release tags and expands shallow history before comparison. Jenkins uses HTTPS checkout and the Git plugin's gitUsernamePassword binding to reuse the checkout SCM username/password credential for fetching and pushing release tags. The credential must have permission to push tags (a Gitea access token may be stored as its password). Git uses the SCM tool configuration, falling back to Default. No SSH Agent plugin is required. Tag conflicts fail the build; existing remote tags are never overwritten. Existing releases without this tag prefix are not comparison baselines.
| Branch | Version | Publish to Nexus |
|---|---|---|
release/<version> |
<version>.<build-number>-RELEASE |
Yes |
develop |
develop.<build-number>-SNAPSHOT |
Yes |
| Other branches | 0.0.<build-number>-<sanitized-branch>-SNAPSHOT |
No |
The Jenkins agent needs a shell environment, JDK 21, Maven installation maven-3.9.16, and managed settings configuration locusworks-settings. Dependency-Check uses the existing nvdApiKey configuration in pom.xml; no separate Jenkins NVD credential is required. The pipeline uses the owasp-nvd-cache lock. Its deploy stage publishes artifacts; it does not restart an application server.