From 2372a67518507eca54ae13262c5ed591217ccb99 Mon Sep 17 00:00:00 2001 From: Maphikza Date: Thu, 3 Jul 2025 14:44:26 +0200 Subject: [PATCH 1/4] Fix production build API configuration - Fix baseURL in production to use environment variable instead of window.location.origin - This prevents API calls from being intercepted by the static server - Update .env.production with proper configuration --- .env.production | 5 +++-- src/config/config.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.env.production b/.env.production index 257932a1..d5857b4d 100644 --- a/.env.production +++ b/.env.production @@ -1,7 +1,8 @@ REACT_APP_BASE_URL=http://localhost:9002 -# REACT_APP_BASE_URL=https://9ee32ecaf5a1.ngrok.app +REACT_APP_WALLET_BASE_URL=http://localhost:9003 REACT_APP_ASSETS_BUCKET=http://localhost +REACT_APP_DEMO_MODE=false -# more info https://create-react-app.dev/docs/advanced-configuration +# More info https://create-react-app.dev/docs/advanced-configuration ESLINT_NO_DEV_ERRORS=true TSC_COMPILE_ON_ERROR=true diff --git a/src/config/config.ts b/src/config/config.ts index 7b3636d9..f569f204 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -3,7 +3,7 @@ const config = { baseURL: process.env.REACT_APP_DEMO_MODE === 'true' ? 'http://localhost:10002' : process.env.NODE_ENV === 'production' - ? window.location.origin || 'http://localhost:9002' + ? process.env.REACT_APP_BASE_URL || 'http://localhost:9002' : process.env.REACT_APP_BASE_URL || 'http://localhost:9002', isDemoMode: process.env.REACT_APP_DEMO_MODE === 'true', walletBaseURL: process.env.REACT_APP_WALLET_BASE_URL?.trim() || 'http://localhost:9003', From a9b01ec24ab237bf9ab062623d8df59d66dc35ef Mon Sep 17 00:00:00 2001 From: Maphikza Date: Thu, 3 Jul 2025 16:51:42 +0200 Subject: [PATCH 2/4] Configure production environment variables and router basename - Add configurable REACT_APP_BASENAME for flexible deployment paths - Update BrowserRouter to use environment variable for basename - Fix wallet auth to use config.walletBaseURL instead of hardcoded URLs - Remove .env.production from tracking and add .env.production.example - Reset .env.development to localhost URLs for development --- .env.development | 1 + .env.production | 8 -------- .env.production.example | 23 +++++++++++++++++++++++ .gitignore | 1 + src/components/router/AppRouter.tsx | 2 +- src/hooks/useWalletAuth.ts | 4 ++-- 6 files changed, 28 insertions(+), 11 deletions(-) delete mode 100644 .env.production create mode 100644 .env.production.example diff --git a/.env.development b/.env.development index d5857b4d..678f66cd 100644 --- a/.env.development +++ b/.env.development @@ -2,6 +2,7 @@ REACT_APP_BASE_URL=http://localhost:9002 REACT_APP_WALLET_BASE_URL=http://localhost:9003 REACT_APP_ASSETS_BUCKET=http://localhost REACT_APP_DEMO_MODE=false +REACT_APP_BASENAME= # More info https://create-react-app.dev/docs/advanced-configuration ESLINT_NO_DEV_ERRORS=true diff --git a/.env.production b/.env.production deleted file mode 100644 index d5857b4d..00000000 --- a/.env.production +++ /dev/null @@ -1,8 +0,0 @@ -REACT_APP_BASE_URL=http://localhost:9002 -REACT_APP_WALLET_BASE_URL=http://localhost:9003 -REACT_APP_ASSETS_BUCKET=http://localhost -REACT_APP_DEMO_MODE=false - -# More info https://create-react-app.dev/docs/advanced-configuration -ESLINT_NO_DEV_ERRORS=true -TSC_COMPILE_ON_ERROR=true diff --git a/.env.production.example b/.env.production.example new file mode 100644 index 00000000..d5963d33 --- /dev/null +++ b/.env.production.example @@ -0,0 +1,23 @@ +# Production Environment Configuration Example +# Copy this file to .env.production and update with your actual values + +# API Base URLs - Update these to your actual backend URLs +REACT_APP_BASE_URL=https://your-domain.com/panel +REACT_APP_WALLET_BASE_URL=https://your-domain.com/wallet + +# Asset serving configuration +REACT_APP_ASSETS_BUCKET=https://your-domain.com + +# Demo mode (set to true for demo, false for production) +REACT_APP_DEMO_MODE=false + +# Router basename - where your React app will be served from +# Use empty string for root path, or /admin, /dashboard, etc. +REACT_APP_BASENAME=/front + +# Public URL for static assets - should match REACT_APP_BASENAME +PUBLIC_URL=/front + +# More info https://create-react-app.dev/docs/advanced-configuration +ESLINT_NO_DEV_ERRORS=true +TSC_COMPILE_ON_ERROR=true \ No newline at end of file diff --git a/.gitignore b/.gitignore index 339bf993..12f24f8a 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ .env.development.local .env.test.local .env.production.local +.env.production npm-debug.log* yarn-debug.log* diff --git a/src/components/router/AppRouter.tsx b/src/components/router/AppRouter.tsx index 64afd016..02c72a66 100644 --- a/src/components/router/AppRouter.tsx +++ b/src/components/router/AppRouter.tsx @@ -143,7 +143,7 @@ const LogoutFallback = withLoading(Logout); export const AppRouter: React.FC = () => { return ( - + {/* Public routes */} {/* children if any */}}> diff --git a/src/hooks/useWalletAuth.ts b/src/hooks/useWalletAuth.ts index b5027b7a..aef31dbc 100644 --- a/src/hooks/useWalletAuth.ts +++ b/src/hooks/useWalletAuth.ts @@ -43,7 +43,7 @@ const useWalletAuth = () => { const npub = await window.nostr.getPublicKey(); // Fetch the challenge from the server - const challengeResponse = await fetch('http://localhost:9003/challenge', { method: 'GET' }); + const challengeResponse = await fetch(`${config.walletBaseURL}/challenge`, { method: 'GET' }); // Check if the response is valid JSON if (!challengeResponse.ok) { @@ -64,7 +64,7 @@ const useWalletAuth = () => { }); // Send the signed challenge to the backend for verification - const verifyResponse = await fetch('http://localhost:9003/verify', { + const verifyResponse = await fetch(`${config.walletBaseURL}/verify`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ From fdbaf1806ab3d8771aff21a0c27d5e8ee185c4cd Mon Sep 17 00:00:00 2001 From: Maphikza Date: Thu, 3 Jul 2025 17:33:38 +0200 Subject: [PATCH 3/4] Add comprehensive setup documentation to README - Preserve all original content and credits - Add detailed architecture explanation with ASCII diagrams - Document reverse proxy benefits and setup rationale - Provide step-by-step installation and deployment guides - Include both reverse proxy and direct access scenarios - Add environment configuration examples and explanations - Document ngrok tunneling setup with custom domain support - Add comprehensive troubleshooting section - Include security considerations and best practices - Maintain respect for original authors and template credits --- README.md | 350 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 346 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a00fa037..5ff8c753 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -![nostr Badge](https://img.shields.io/badge/nostr-8e30eb?style=flat) ![Go Badge](https://img.shields.io/badge/Go-00ADD8?logo=go&logoColor=white) +![nostr Badge](https://img.shields.io/badge/nostr-8e30eb?style=flat) ![Go Badge](https://img.shields.io/badge/Go-00ADD8?logo=go&logoColor=white) ![React Badge](https://img.shields.io/badge/React-61DAFB?logo=react&logoColor=black) ![TypeScript Badge](https://img.shields.io/badge/TypeScript-3178C6?logo=typescript&logoColor=white) # Dashboard Panel for H.O.R.N.E.T Storage Nostr Relay -This repository is home to the hornet storage panal which is a typescript / react web application designed for managing a hornet storage nostr multimedia relay which can be found here: https://github.com/HORNET-Storage/HORNETS-Nostr-Relay +This repository is home to the hornet storage panel which is a typescript / react web application designed for managing a hornet storage nostr multimedia relay which can be found here: https://github.com/HORNET-Storage/HORNETS-Nostr-Relay ### Live Demo We have a live demo that can be found at http://hornetstorage.net for anyone that wants to see what the panel looks like. @@ -24,9 +24,322 @@ We have a live demo that can be found at http://hornetstorage.net for anyone tha ![image](https://github.com/HORNET-Storage/hornet-storage-panel/assets/138120736/ff763518-d399-408b-b0b4-487292ef57d6) +--- + +# πŸ—οΈ Advanced Setup Guide + +## Project Architecture + +The HORNETS Relay Panel is built with a microservices architecture comprising: + +### Services +- **Frontend (React App)**: Port 3000 (dev) - The admin dashboard interface +- **Panel API**: Port 9002 - Backend service for panel operations +- **Relay Service**: Port 9001 - WebSocket service for Nostr relay functionality +- **Wallet Service**: Port 9003 - Backend service for wallet operations +- **Transcribe API**: Port 8000 - Service for transcription features + +### Reverse Proxy Architecture +``` +Client Request + ↓ +Nginx (Port 80/443) + ↓ +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Route Distribution: β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ / β†’ Relay β”‚ β”‚ /front/ β†’ React β”‚ β”‚ /panel/ β†’ APIβ”‚ β”‚ +β”‚ β”‚ (Port 9001) β”‚ β”‚ (Port 3000) β”‚ β”‚ (Port 9002) β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ /wallet/ β†’ Walletβ”‚ β”‚/transcribe/ β†’ APIβ”‚ β”‚ +β”‚ β”‚ (Port 9003) β”‚ β”‚ (Port 8000) β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +## πŸ”§ Why Use a Reverse Proxy? + +### Benefits of Reverse Proxy Setup (Recommended) +1. **Security**: Single entry point with centralized security headers +2. **SSL/TLS Termination**: Handle HTTPS certificates at the proxy level +3. **Load Balancing**: Distribute traffic across service instances +4. **Clean URLs**: User-friendly paths (`/front/`, `/panel/`) instead of ports +5. **Single Domain**: All services accessible from one domain +6. **WebSocket Support**: Proper handling of WebSocket connections for the relay +7. **Tunnel Compatibility**: Works seamlessly with ngrok and other tunneling services + +### Direct Access (Development Only) +While possible, direct port access has limitations: +- Multiple ports to manage +- CORS issues between services +- No unified SSL certificate +- Poor user experience with port numbers in URLs + +## πŸ“‹ Prerequisites + +### Required Software +- [Node.js](https://nodejs.org/en/) version **>=16.0.0** +- [Yarn](https://yarnpkg.com/) package manager +- [Git](https://git-scm.com/) for version control + +### Optional (For Production) +- [Nginx](https://nginx.org/) for reverse proxy +- SSL certificate (Let's Encrypt recommended) +- Domain name + +## πŸ› οΈ Installation & Setup + +### 1. Clone the Repository +```bash +git clone https://github.com/HORNET-Storage/HORNETS-Relay-Panel.git +cd HORNETS-Relay-Panel +``` + +### 2. Install Dependencies +```bash +yarn install +``` + +### 3. Environment Configuration + +#### Development Setup +For development, you can use the default configuration or create `.env.development`: +```bash +# Optional - defaults work for local development +REACT_APP_BASE_URL=http://localhost:9002 +REACT_APP_WALLET_BASE_URL=http://localhost:9003 +REACT_APP_DEMO_MODE=false +``` + +#### Production Setup +Copy the example environment file and customize: +```bash +cp .env.production.example .env.production +``` + +Edit `.env.production` with your actual values: +```env +# Production Environment Configuration +REACT_APP_BASE_URL=https://your-domain.com/panel +REACT_APP_WALLET_BASE_URL=https://your-domain.com/wallet +REACT_APP_ASSETS_BUCKET=https://your-domain.com +REACT_APP_DEMO_MODE=false + +# Router configuration for reverse proxy +REACT_APP_BASENAME=/front +PUBLIC_URL=/front + +# Development optimizations +ESLINT_NO_DEV_ERRORS=true +TSC_COMPILE_ON_ERROR=true +``` + +### 4. Start Development Server + +#### Using yarn (standard) +```bash +yarn start +``` + +#### Using provided script (handles Node.js compatibility) +```bash +./start-app.sh # Linux/macOS +start.bat # Windows +``` + +The development server will start on `http://localhost:3000` + +## πŸš€ Deployment + +### Scenario 1: With Reverse Proxy (Recommended) + +#### Step 1: Build the Application +```bash +# Production build +yarn build + +# Using provided script (handles Node.js compatibility) +./build.bat # Windows +yarn build # Linux/macOS +``` + +#### Step 2: Configure Nginx +Use the provided configuration as a starting point: +```bash +# Copy the configuration file +sudo cp hornet_services_updated.conf /etc/nginx/sites-available/hornets-relay +sudo ln -s /etc/nginx/sites-available/hornets-relay /etc/nginx/sites-enabled/ +``` + +#### Step 3: Serve Built Files +Copy the built files to your web server: +```bash +# Example for nginx +sudo cp -r build/* /var/www/html/front/ +``` + +#### Step 4: Start Services +Ensure all backend services are running: +```bash +# Start in order of dependency +./relay-service & # Port 9001 +./panel-api & # Port 9002 +./wallet-service & # Port 9003 +./transcribe-api & # Port 8000 +``` + +#### Step 5: Start Nginx +```bash +sudo systemctl start nginx +sudo systemctl enable nginx +``` + +### Scenario 2: Direct Access (Development/Testing) + +#### Step 1: Build with Root Path +Update `.env.production`: +```env +REACT_APP_BASENAME= +PUBLIC_URL= +REACT_APP_BASE_URL=http://localhost:9002 +REACT_APP_WALLET_BASE_URL=http://localhost:9003 +``` + +#### Step 2: Build and Serve +```bash +yarn build +# Serve the build folder (default port 3000) +serve -s build +``` + +#### Step 3: Configure CORS +Ensure your backend services accept requests from the frontend origin. + +## 🌐 Tunneling & Remote Access + +### Using ngrok +```bash +# Install ngrok +npm install -g ngrok + +# With reverse proxy setup (random domain) +ngrok http 80 + +# With reverse proxy setup (custom domain - requires ngrok pro) +ngrok http --url=your-domain.ngrok.io 80 + +# Direct access to React app (without reverse proxy) +ngrok http 3000 + +# Example output: +# Forwarding https://abc123.ngrok.io -> http://localhost:80 +``` + +### Environment Variables for Tunneling +When using tunnels, update your `.env.production`: +```env +REACT_APP_BASE_URL=https://your-tunnel-url.com/panel +REACT_APP_WALLET_BASE_URL=https://your-tunnel-url.com/wallet +``` + +## πŸ”§ Configuration Options + +### REACT_APP_BASENAME +Controls where the React app is served from: +- `/front` - App accessible at `https://domain.com/front/` +- `/admin` - App accessible at `https://domain.com/admin/` +- `` (empty) - App accessible at `https://domain.com/` + +### Service URLs +- **REACT_APP_BASE_URL**: Panel API endpoint +- **REACT_APP_WALLET_BASE_URL**: Wallet service endpoint +- **REACT_APP_ASSETS_BUCKET**: Static assets URL + +### Demo Mode +Set `REACT_APP_DEMO_MODE=true` to enable demo functionality with mock data. + +## πŸ› Troubleshooting + +### Common Issues + +#### 1. Node.js Compatibility +**Error**: `digital envelope routines::unsupported` +**Solution**: Scripts include `NODE_OPTIONS=--openssl-legacy-provider` + +#### 2. Build Memory Issues +**Error**: `JavaScript heap out of memory` +**Solution**: Increase memory allocation: +```bash +export NODE_OPTIONS="--openssl-legacy-provider --max-old-space-size=4096" +``` + +#### 3. API Connection Issues +**Error**: Network errors or 404s +**Solution**: Verify service URLs in environment variables and ensure backend services are running. + +#### 4. Routing Issues with Reverse Proxy +**Error**: 404 on refresh or direct URL access +**Solution**: Configure nginx to handle React Router: +```nginx +location /front/ { + try_files $uri $uri/ /front/index.html; +} +``` + +#### 5. WebSocket Connection Failures +**Error**: WebSocket connection refused +**Solution**: Ensure proper WebSocket configuration in nginx: +```nginx +proxy_set_header Upgrade $http_upgrade; +proxy_set_header Connection $connection_upgrade; +``` + +### Service Dependencies +Start services in this order: +1. Relay Service (Port 9001) - Core WebSocket functionality +2. Panel API (Port 9002) - Main backend +3. Wallet Service (Port 9003) - Payment processing +4. Transcribe API (Port 8000) - Additional features +5. Frontend (Port 3000) - User interface + +### Health Checks +- Nginx health: `curl http://localhost/health` +- Individual services: `curl http://localhost:PORT/health` + +## πŸ“š Development vs Production + +### Development +- Hot reloading enabled +- Source maps included +- Verbose error messages +- Direct API calls to localhost ports + +### Production +- Optimized builds with minification +- Source maps excluded +- Error boundaries for user-friendly errors +- Proxied API calls through reverse proxy + +## πŸ”’ Security Considerations + +### Production Security +- Use HTTPS in production +- Configure proper CORS policies +- Implement rate limiting +- Regular security headers via nginx +- Keep dependencies updated + +### Environment Variables +- Never commit `.env.production` to version control +- Use secure random values for secrets +- Regularly rotate API keys and tokens + +--- ## Developer Information -- 🐜 This panel relies heavily on the [Ant Design](https://ant.design/) component library with some modifications + +### Basic Development Commands Development mode ``` @@ -38,12 +351,41 @@ Production mode yarn install && yarn build ``` -*.bat and .sh files are included for starting the panel ind dev mode and for creating a production build if needed* +*.bat and .sh files are included for starting the panel in dev mode and for creating a production build if needed* #### Requirements - [Node.js](https://nodejs.org/en/) version _>=16.0.0_ - [yarn](https://yarnpkg.com/) - [git](https://git-scm.com/) +## 🀝 Contributing + +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Test thoroughly +5. Submit a pull request + +## πŸ“„ License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +## πŸ™ Acknowledgments + +- 🐜 This panel relies heavily on the [Ant Design](https://ant.design/) component library with some modifications +- Based on the [Lightence](https://github.com/altence/lightence-ant-design-react-template) template +- Part of the HORNETS Storage ecosystem + ### Credit This panel was created using the lightence template which can be found [here](https://github.com/altence/lightence-ant-design-react-template) + +## πŸ“ž Support + +For issues and support: +- GitHub Issues: Report bugs and request features +- Community: Join our discussions +- Documentation: Check the wiki for detailed guides + +--- + +**Note**: This panel is designed to work with the [HORNETS Nostr Relay](https://github.com/HORNET-Storage/HORNETS-Nostr-Relay). Ensure you have the relay service running for full functionality. \ No newline at end of file From 61822ba060529d346db5a41d891e211062c539bc Mon Sep 17 00:00:00 2001 From: Maphikza Date: Thu, 3 Jul 2025 19:14:51 +0200 Subject: [PATCH 4/4] Update README with self-contained nginx configuration - Replace reference to external hornet_services_updated.conf file - Add complete nginx configuration example directly in README - Include WebSocket support, health checks, and all service routing - Remove dependency on external configuration files - Users can now follow setup instructions completely self-contained --- README.md | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 69 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5ff8c753..90d7d13e 100644 --- a/README.md +++ b/README.md @@ -165,11 +165,77 @@ yarn build # Linux/macOS ``` #### Step 2: Configure Nginx -Use the provided configuration as a starting point: +Create an nginx configuration file: +```bash +# Create the configuration file +sudo nano /etc/nginx/sites-available/hornets-relay +``` + +Add this configuration (adjust domains and paths as needed): +```nginx +# WebSocket connection upgrade mapping +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +server { + listen 80; + server_name your-domain.com; # Replace with your domain + + # Forward client IP and protocol + 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; + + # Panel API + location /panel/ { + rewrite ^/panel/(.*)$ /$1 break; + proxy_pass http://127.0.0.1:9002; + } + + # Wallet service + location /wallet/ { + rewrite ^/wallet/(.*)$ /$1 break; + proxy_pass http://127.0.0.1:9003; + } + + # Frontend React app + location /front/ { + rewrite ^/front/(.*)$ /$1 break; + proxy_pass http://127.0.0.1:3000; # Or serve static files + } + + # Default location - Relay service with WebSocket support + location / { + proxy_pass http://127.0.0.1:9001; + + # WebSocket headers + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + + # Extended timeouts for WebSocket connections + proxy_read_timeout 86400s; + proxy_send_timeout 86400s; + proxy_connect_timeout 60s; + } + + # Health check endpoint + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } +} +``` + +Enable the configuration: ```bash -# Copy the configuration file -sudo cp hornet_services_updated.conf /etc/nginx/sites-available/hornets-relay sudo ln -s /etc/nginx/sites-available/hornets-relay /etc/nginx/sites-enabled/ +sudo nginx -t # Test configuration ``` #### Step 3: Serve Built Files