diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index 7705bb9..e29aa74 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -4,11 +4,50 @@ Two artifacts, one host. | Artifact | Built from | Output | |---|---|---| -| API | `UdyamLinc-Backend` | `target/udyamlinc-backend.jar` (~80 MB, executable) | +| API | `UdyamLinc-Backend` | `target/udyamlinc-backend.war` (~80 MB) | | UI | `UdyamLinc-UI` | `dist/` (static files, ~880 kB) | -The UI bundle is built with `VITE_UDYAM_API_BASE_URL=/api/v1/udyam` — a **relative** -path. It therefore contains no hostname, serves correctly from any domain, and is +**The war is also executable.** `spring-boot-maven-plugin` repackages it with the +provided-scope Tomcat under `WEB-INF/lib-provided`, so one artifact both runs on its own +and deploys into a container: + +```bash +java -jar udyamlinc-backend.war # embeds Tomcat 11 — recommended +# or drop it into a servlet container — see §2c +``` + +### Pointing the UI at the API without rebuilding + +The bundle reads its API base from **`config.js`**, served unbundled from the static +root. Deploy the backend first, then edit one line on the server — no rebuild, no +redeploy of the UI: + +```js +// config.js, at the web root next to index.html +window.__UDYAM_CONFIG__ = { + apiBaseUrl: '', // empty = same-origin /api/v1/udyam (the default) +} +``` + +Set it only when the API is on another host: + +```js +apiBaseUrl: 'https://udyamlinc-api.lincpay.tech/api/v1/udyam' +``` + +Resolution order is `config.js` → the build-time `VITE_UDYAM_API_BASE_URL` → +`/api/v1/udyam`. An empty string means "unset", so the shipped file needs no edit for a +same-origin deployment. + +Two things that bite: + +- **`config.js` must never be cached.** The nginx and httpd configs below send + `no-store` for it. Cached, it pins the old URL for everyone who has already loaded + the app, and nothing on the server side shows it. +- **A cross-origin value needs `UDYAM_CORS_ALLOWED_ORIGINS`** set to the UI's origin, or + the browser blocks every call while the API itself looks perfectly healthy. + +The UI bundle is otherwise built with a **relative** default. It therefore contains no hostname, serves correctly from any domain, and is same-origin with the API. That last point is the one worth keeping: same-origin means CORS never applies, and CORS is the single largest source of "worked locally, broken on the server" in a split deployment. @@ -153,7 +192,7 @@ Virtual host: Header set Cache-Control "public, max-age=2592000, immutable" - + Header set Cache-Control "no-cache, no-store, must-revalidate" @@ -187,21 +226,34 @@ RewriteRule ^(.*)$ /index.html The API still runs as its own `java -jar` process on 8082, and Tomcat proxies to it — or you put httpd/nginx in front of both. -### Deploying the API *as a WAR* into Tomcat — do not do this today +### Deploying the API as a WAR into Tomcat -It is possible but it is not a same-day change, and the constraint is hard rather than -stylistic: +Supported — the build produces a war and `UdyamLincApplication` extends +`SpringBootServletInitializer`. Two conditions, and both are hard: -- This project is **Spring Boot 4.0.1**, which is built on Jakarta EE 11 / Servlet 6.1. - That requires **Tomcat 11**. On Tomcat 9 or 10 the application does not start — - Tomcat 9 is `javax.*` and cannot load a `jakarta.*` application at all. -- It needs `war`, `spring-boot-starter-tomcat` moved to - `provided`, and `UdyamLincApplication` extending `SpringBootServletInitializer`. -- The context path moves from the configured `/api/v1/udyam` to the WAR's deployed - name, so the proxy rules above stop matching. +**1. Tomcat 11 or newer.** Spring Boot 4.0.1 is built on Jakarta EE 11 / Servlet 6.1. +Tomcat 9 is `javax.*` and cannot load a `jakarta.*` application at all; Tomcat 10 is +short of the servlet level. Check before deploying: -The jar already embeds Tomcat 11, so running it directly gives the same container with -none of that risk. **Recommendation: keep the jar.** +```bash +$CATALINA_HOME/bin/version.sh # or version.bat +``` + +**2. Deploy it as `api#v1#udyam.war`.** Under a container `server.servlet.context-path` +is ignored — the file name decides the path, and Tomcat reads `#` as a separator. So: + +```bash +cp udyamlinc-backend.war $CATALINA_HOME/webapps/api#v1#udyam.war +``` + +serves `/api/v1/udyam`, which is what the UI's relative API base expects. Deployed under +any other name every endpoint moves and the UI gets 404s that look like a broken build. + +Environment variables are read the same way; set them in `setenv.sh` or the service +definition rather than per-shell. + +**Still, running the war directly is the lower-risk option** — it embeds Tomcat 11, so +neither condition can bite, and the same file works both ways. --- diff --git a/pom.xml b/pom.xml index 729fcbd..46f63dd 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,18 @@ com.lincfusion udyamlinc-backend 0.0.1-SNAPSHOT - jar + + war udyamlinc-backend UdyamLinc — predictive cash-flow intelligence and early warning for rural microenterprises (NABARD Hackathon) @@ -31,6 +42,17 @@ spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + provided + + org.springframework.boot diff --git a/src/main/java/com/lincfusion/udyamlinc/UdyamLincApplication.java b/src/main/java/com/lincfusion/udyamlinc/UdyamLincApplication.java index 14c2f92..786b901 100644 --- a/src/main/java/com/lincfusion/udyamlinc/UdyamLincApplication.java +++ b/src/main/java/com/lincfusion/udyamlinc/UdyamLincApplication.java @@ -2,6 +2,8 @@ package com.lincfusion.udyamlinc; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; /** * UdyamLinc — predictive cash-flow intelligence and early warning for rural @@ -13,9 +15,36 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; * ingestion pipeline are separate components and are not implemented here. */ @SpringBootApplication -public class UdyamLincApplication { +public class UdyamLincApplication extends SpringBootServletInitializer { public static void main(String[] args) { SpringApplication.run(UdyamLincApplication.class, args); } + + /** + * Entry point when a servlet container starts this instead of {@code main}. + * + *

The artifact is packaged as a war so it can be deployed to Tomcat, but + * {@code spring-boot-maven-plugin} still repackages it as executable — the + * provided-scope container lands in {@code WEB-INF/lib-provided}, which the + * launcher puts on the classpath. So {@code java -jar udyamlinc-backend.war} + * and dropping the same file into {@code webapps/} are both supported, from + * one build. + * + *

Tomcat 11 or newer. Spring Boot 4 is built on Jakarta EE 11 and + * Servlet 6.1. Tomcat 9 is {@code javax.*} and cannot load this at all; + * Tomcat 10 is short of the servlet level. Running the war directly embeds + * Tomcat 11 and sidesteps the question entirely, which is why that remains + * the recommendation. + * + *

The context path moves. {@code server.servlet.context-path} is + * ignored under a container — the deployed file name decides it. Tomcat reads + * {@code #} as a path separator, so the war must be deployed as + * {@code api#v1#udyam.war} to serve {@code /api/v1/udyam}. Deployed under any + * other name every endpoint moves and the UI's relative API base misses. + */ + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { + return builder.sources(UdyamLincApplication.class); + } }