Replace shell scripts with TypeScript server configuration
- Dominant language
- TypeScript
- Stars
- 481
- Forks
- 108
- Avg merge
- 8d 9h
- Merged PRs (30d)
- 7
Description
## Problem Statement
Graph Explorer's server configuration is split across three shell scripts (`process-environment.sh`, `docker-entrypoint.sh`, `setup-ssl.sh`) that parse environment variables, read `config.json`, generate `defaultConnection.json`, set up SSL certificates, and orchestrate server startup. This creates several problems:
- **Untestable**: Shell scripts are tested by shelling out from TypeScript tests, making them slow and brittle
- **Fragile parsing**: `config.json` is parsed with `grep`/`cut`, which silently drops documented fields like `GRAPH_EXP_FETCH_REQUEST_TIMEOUT` and `GRAPH_EXP_NODE_EXPANSION_LIMIT`
- **Dead code**: `GRAPH_EXP_HTTPS_CONNECTION` and `NEPTUNE_NOTEBOOK` are written to `.env` but never read by any TypeScript code
- **Indirect communication**: Shell scripts append values to `.env` so the Node process can read them via dotenv — an unnecessary filesystem round-trip
- **Hard to extend**: Adding new configuration options requires changes across shell scripts, Zod schemas, and TypeScript code
This is a prerequisite for both #1618 (simplify client-server relationship) and #1660 (simplify deployment configuration). Moving configuration logic into TypeScript provides a testable, type-safe foundation for those changes.
## Solution
Move all configuration parsing, default connection generation, and SSL setup from shell scripts into TypeScript within the Node proxy server. The Dockerfile entrypoint changes from `./docker-entrypoint.sh` to `node dist/node-server.js`. All three shell scripts are deleted. From the user's perspective, nothing changes — every environment variable, `config.json` field, and endpoint behaves identically.
## User Stories
1. As a Docker deployer, I want every existing environment variable to work exactly as before, so that upgrading does not require any configuration changes
2. As a Docker deployer using `config.json`, I want my mounted config file to work exactly as before, so that upgrading is seamless
3. As a Docker deployer using `config.json`, I want `GRAPH_EXP_FETCH_REQUEST_TIMEOUT` and `GRAPH_EXP_NODE_EXPANSION_LIMIT` to be respected when set in `config.json`, so that the documented behavior actually works
4. As a Neptune Notebook deployer, I want the `NEPTUNE_NOTEBOOK` build arg to produce the same image behavior as before, so that my SageMaker deployment continues to function
5. As a deployer using HTTPS with self-signed certs, I want cert auto-generation to work as before, so that my TLS setup is not disrupted
6. As a deployer using HTTPS with custom certs, I want to provide my own cert files as before, so that my TLS setup is not disrupted
7. As a developer, I want configuration parsing to be unit-testable TypeScript functions, so that I can verify behavior without shelling out to bash
8. As a developer, I want a single configuration pipeline (env vars → config.json → computed values), so that the precedence rules are clear and centralized
9. As a developer, I want dead configuration paths removed (`GRAPH_EXP_HTTPS_CONNECTION`, `NEPTUNE_NOTEBOOK` in `.env`), so that the codebase is easier to understand
10. As a developer, I want the `defaultConnection` endpoint to return in-memory computed JSON, so that there is no file I/O side effect at startup
11. As a developer, I want the Docker entrypoint to be `node dist/node-server.js` directly, so that signal handling (SIGTERM, SIGINT) works correctly without a shell wrapper
12. As a developer, I want the existing shell script test cases preserved as TypeScript unit tests, so that behavioral parity is verified
## Implementation Decisions
### Configuration Pipeline
A new TypeScript module (e.g., `startup-config.ts`) runs at server startup before the Express app is created. It:
1. Loads `.env` via dotenv (provides defaults like `LOG_LEVEL=info`)
2. Reads `process.env` (Docker ENV vars override dotenv values)
3. If `config.json` exists at `CONFIGURATION_FOLDER_PATH`, parses it and overlays values onto the environment (config.json wins over env vars for connection-related fields, preserving current shell script precedence)
4. If `NEPTUNE_NOTEBOOK` is truthy, forces `PROXY_SERVER_HTTPS_CONNECTION=false`
5. Passes the resolved values to the existing `parseEnvironmentValues()` Zod pipeline
### config.json Parsing
The shell scripts parse `config.json` with `grep`/`cut`, which tolerates JS-style comments (`//`) and trailing commas. Users may have config files with these based on the documentation examples. The TypeScript parser must strip single-line comments and trailing commas before calling `JSON.parse()`. No new dependency needed — a simple regex pre-processor suffices.
The parser extracts ALL documented fields, fixing the existing bug where `GRAPH_EXP_FETCH_REQUEST_TIMEOUT` and `GRAPH_EXP_NODE_EXPANSION_LIMIT` are silently dropped.
### Default Connection Endpoint
Replace the `express.static("/defaultConnection", ...)` mount with a `GET /defaultConnection` route handler that returns the in-memory computed default connection data as JSON. Same response shape, same field names (`GRAPH_EXP_PUBLIC_OR_PROXY_ENDPOINT`, `GRAPH_EXP_SERVICE_TYPE`, etc.). Returns 404 if no default connection is configured.
### SSL Certificate Generation
Shell out to `openssl` via `execFileSync` from Node, replicating the exact same cert chain structure:
- Root CA: self-signed RSA 2048, SHA-256, 356-day validity
- Server key: RSA 2048
- Server CSR: with SAN entries from `csr.conf`
- Server cert: signed by root CA, 365-day validity
The `csr.conf` and `cert.conf` templates are modified in-memory (string replacement instead of `sed`) before being written to temp files for openssl. When `HOST` is not set, existing certs are validated and reused (same behavior as current `setup-ssl.sh`).
### NEPTUNE_NOTEBOOK
`NEPTUNE_NOTEBOOK` remains a Docker build ARG that sets `GRAPH_EXP_ENV_ROOT_FOLDER`, `PROXY_SERVER_HTTP_PORT`, and `LOG_STYLE` at build time. These are build-time decisions because `GRAPH_EXP_ENV_ROOT_FOLDER` is baked into client assets by Vite. The runtime effect (forcing HTTPS off) moves to TypeScript.
### Dockerfile Changes
- Entrypoint changes from `["./docker-entrypoint.sh"]` to `["node", "packages/graph-explorer-proxy-server/dist/node-server.js"]`
- Remove `RUN chmod +x` for shell scripts
- Delete `docker-entrypoint.sh`, `process-environment.sh`, `setup-ssl.sh`
- Delete dead `packages/graph-explorer-proxy-server/docker-startup.sh`
- Keep openssl in the image (needed for cert generation)
- Keep the `NEPTUNE_NOTEBOOK` ARG → ENV propagation
### .env File
The shipped `.env` at `packages/graph-explorer/.env` stays unchanged. It provides build-time defaults for Vite (`GRAPH_EXP_FEEDBACK_URL`, `GRAPH_EXP_FETCH_REQUEST_TIMEOUT`) and a runtime default for the server (`LOG_LEVEL=info`). The shell scripts currently append `PROXY_SERVER_HTTPS_CONNECTION`, `GRAPH_EXP_HTTPS_CONNECTION`, and `NEPTUNE_NOTEBOOK` to this file — that stops. The server reads `PROXY_SERVER_HTTPS_CONNECTION` from `process.env` directly.
### Precedence Rules (preserved from current behavior)
For connection-related values (used to build `defaultConnection` response):
1. `config.json` values (highest priority)
2. Environment variables
3. Hardcoded defaults
For server configuration (port, HTTPS, logging):
1. Environment variables (highest priority)
2. `.env` file defaults (via dotenv)
3. Zod schema defaults
## Testing Decisions
Tests should verify the same behaviors currently tested in the shell script test files (`process-environment.test.ts`, `docker-entrypoint.test.ts`, `setup-ssl.test.ts`, `config-pipeline.test.ts`), but calling TypeScript functions directly instead of shelling out.
- **Config pipeline**: Test the full flow from env vars + config.json through to resolved server config. Test precedence (config.json overrides env vars for connection fields). Test `NEPTUNE_NOTEBOOK` forcing HTTPS off.
- **config.json parsing**: Test valid JSON, JSON with comments, JSON with trailing commas, malformed JSON (clear error), missing file (no error, empty config).
- **Default connection generation**: Test that all documented fields are included. Test conditional `GRAPH_EXP_GRAPH_TYPE` inclusion (omitted when not set, auto-detected for `neptune-graph`). Test 404 when no connection configured.
- **SSL cert generation**: Test cert generation when `HOST` is set. Test cert reuse when `HOST` is not set. Test error when certs are missing and `HOST` is not set.
- **Boolean parsing**: Test case-insensitive boolean strings (`true`, `True`, `TRUE`, `false`).
Prior art: `packages/graph-explorer-proxy-server/src/__tests__/` contains the existing shell script tests. `packages/graph-explorer/src/core/defaultConnection.test.ts` tests the client-side default connection parsing.
## Out of Scope
- Changing any environment variable names (that is #1618 / #1660 territory)
- Changing the `defaultConnection` JSON response shape
- Changing the `config.json` field names or format
- Moving API routes under `/api/` (that is #1618)
- Changing the base path from `/explorer` to `/` (that is #1618)
- Introducing `graph-explorer.config.json` (that is #1660)
- Changing the Docker base image to `node:24-alpine` (that is #1660)
- Removing self-signed cert generation (that is #1660)
- Any client-side changes
## Further Notes
This is a pure infrastructure refactor with no user-facing behavior changes. The only observable difference is that the Docker entrypoint is `node` instead of a shell script, which improves signal handling. All existing env vars, `config.json` fields, and endpoints continue to work identically.
The one minor improvement is fixing the `config.json` parsing bug where `GRAPH_EXP_FETCH_REQUEST_TIMEOUT` and `GRAPH_EXP_NODE_EXPANSION_LIMIT` are silently dropped. This is a bug fix, not a behavior change — the documentation already says these fields are supported.
## Related Issues
- Blocker for #1618
- Blocker for #1660
Contributor guide
Assessment
This issue has not been assessed yet.