Files
roa2web-service-auto/deployment/windows/docs/TWO-TIER-IIS-DEPLOYMENT.md
Marius Mutu 9008876b16 chore: Remove obsolete microservices directories and update all references
- Delete data-entry-app/ (1.6GB), reports-app/ (447MB), .auto-build-data/
- Saved ~1.4GB disk space (64% reduction: 2.2GB → 845MB)

Updated references across 38 files:
- .claude/rules/ paths: backend/modules/, src/modules/
- .claude/commands/validate.md: all validation paths
- docs/ (13 files): data-entry, telegram, README, CLAUDE.md
- scripts/ (3 files): backup-secrets, restore-secrets, test-docker
- security/ (2 files): git_cleanup, SECURITY_PROCEDURES
- deployment/ & shared/: updated all stale comments

All paths now reflect ultrathin monolith architecture:
- Backend: backend/modules/{reports,data_entry,telegram}/
- Frontend: src/modules/{reports,data-entry}/
- Shared: shared/{auth,database,routes}/

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-30 12:08:20 +02:00

11 KiB

Two-Tier IIS Deployment Architecture

Overview

ROA2WEB uses a 2-tier IIS architecture for production deployment:

Internet
    ↓
Public IIS Server (roa2web.romfast.ro)
    ↓ HTTPS reverse proxy
Internal IIS Server (10.0.20.36)
    ↓ API proxy
Backend Service (localhost:8000)
    ↓
Oracle Database

Architecture Components

Tier 1: Public IIS Server (Edge/Gateway)

Hostname: roa2web.romfast.ro IP Address: 10.0.20.122 Role: Public-facing reverse proxy Location: DMZ/Public network

Responsibilities:

  • SSL/TLS termination (HTTPS)
  • Reverse proxy to internal server
  • Security headers
  • Public DNS endpoint

Configuration (web.config pe serverul 10.0.20.122):

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
    <rewrite>
      <rules>
        <!-- Redirect root to /roa2web/ -->
        <rule name="Root to ROA2WEB" stopProcessing="true">
          <match url="^$" />
          <action type="Redirect" url="/roa2web/" redirectType="Permanent" />
        </rule>

        <!-- Reverse Proxy to internal server -->
        <rule name="ROA2WEB Reverse Proxy to HTTPS Backend" stopProcessing="true">
          <match url="(.*)" />
          <action type="Rewrite" url="https://10.0.20.36/{R:1}" />
          <serverVariables>
            <set name="HTTP_X_FORWARDED_PROTO" value="https" />
            <set name="HTTP_X_FORWARDED_HOST" value="{HTTP_HOST}" />
            <set name="HTTP_X_REAL_IP" value="{REMOTE_ADDR}" />
          </serverVariables>
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>

Key Features:

  • Root redirect: https://roa2web.romfast.ro/https://roa2web.romfast.ro/roa2web/
  • All requests proxied to: https://10.0.20.36/{REQUEST_PATH}
  • Forwards client IP and protocol headers

Tier 2: Internal IIS Server (Application Server)

IP Address: 10.0.20.36 Role: Application host + API proxy Location: Internal network

Responsibilities:

  • Serve Vue.js frontend static files
  • Proxy API requests to backend service
  • Handle uploads
  • IIS sub-application at /roa2web

Configuration (web.config pe serverul 10.0.20.36 - C:\inetpub\wwwroot\roa2web\web.config):

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
    <rewrite>
      <rules>
        <!-- Proxy all API requests to unified backend -->
        <rule name="Proxy Unified API" stopProcessing="true">
          <match url="^roa2web/api/(.*)" />
          <action type="Rewrite" url="http://localhost:8000/api/{R:1}" />
        </rule>

        <!-- Proxy uploads to unified backend -->
        <rule name="Proxy Uploads" stopProcessing="true">
          <match url="^roa2web/uploads/(.*)" />
          <action type="Rewrite" url="http://localhost:8000/uploads/{R:1}" />
        </rule>

        <!-- SPA fallback - all other routes serve index.html -->
        <rule name="SPA Fallback" stopProcessing="true">
          <match url="^roa2web/.*" />
          <conditions logicalGrouping="MatchAll">
            <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
            <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
          </conditions>
          <action type="Rewrite" url="/roa2web/index.html" />
        </rule>
      </rules>
    </rewrite>

    <!-- Static content configuration -->
    <staticContent>
      <mimeMap fileExtension=".webmanifest" mimeType="application/manifest+json" />
      <mimeMap fileExtension=".js" mimeType="application/javascript" />
      <mimeMap fileExtension=".json" mimeType="application/json" />
    </staticContent>

    <!-- Client cache for static assets (1 year) -->
    <httpProtocol>
      <customHeaders>
        <add name="Cache-Control" value="public, max-age=31536000" />
      </customHeaders>
    </httpProtocol>
  </system.webServer>
</configuration>

CRITICAL: The internal server web.config must handle the /roa2web/ prefix since requests arrive as:

  • https://10.0.20.36/roa2web/api/auth/login (NOT /api/auth/login)

Backend Service (FastAPI)

Host: localhost (internal server) Port: 8000 Type: Windows Service (NSSM) Name: ROA2WEB-Backend

Configuration (.env):

HOST=127.0.0.1
PORT=8000
ENVIRONMENT=production

Base Path: /api (NOT /roa2web/api)

The backend serves:

  • /api/auth/login
  • /api/companies
  • /api/calendar
  • etc.

Request Flow Example

Login Request Flow

  1. Client BrowserPOST https://roa2web.romfast.ro/roa2web/api/auth/login

  2. Public IIS (roa2web.romfast.ro):

    • Receives: /roa2web/api/auth/login
    • Proxies to: https://10.0.20.36/roa2web/api/auth/login
    • Sets headers: X-Forwarded-Proto: https, X-Forwarded-Host: roa2web.romfast.ro
  3. Internal IIS (10.0.20.36):

    • Receives: /roa2web/api/auth/login
    • Matches rule: ^roa2web/api/(.*)
    • Extracts: auth/login
    • Proxies to: http://localhost:8000/api/auth/login
  4. Backend Service (localhost:8000):

    • Receives: /api/auth/login
    • Processes request
    • Returns response

Frontend Configuration

Vite Build Configuration (vite.config.js)

export default defineConfig({
  // Base path for IIS sub-application
  base: process.env.NODE_ENV === 'production' ? '/roa2web/' : '/',

  // Development proxy (NOT used in production)
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:8000',
        changeOrigin: true
      }
    }
  }
})

IMPORTANT: In production, base: '/roa2web/' ensures:

  • All asset paths: /roa2web/assets/...
  • Router base: /roa2web/
  • API calls: /roa2web/api/... (via axios baseURL)

API Service Configuration (src/App.vue)

const authApi = axios.create({
  baseURL: import.meta.env.BASE_URL + 'api',  // Results in: '/roa2web/api'
  headers: { 'Content-Type': 'application/json' }
})

Common Issues & Troubleshooting

Issue: 404 on API calls

Symptoms:

  • Frontend loads correctly
  • API calls return 404
  • Browser console: POST https://roa2web.romfast.ro/roa2web/api/auth/login 404

Possible Causes:

  1. Internal server web.config missing /roa2web/ prefix in match rules

    WRONG:

    <match url="^api/(.*)" />
    

    CORRECT:

    <match url="^roa2web/api/(.*)" />
    
  2. Backend service not running

    Check on internal server (10.0.20.36):

    Get-Service ROA2WEB-Backend
    Invoke-WebRequest http://localhost:8000/health
    
  3. IIS ARR not enabled

    On internal server (10.0.20.36):

    # Install ARR
    Install-WindowsFeature -Name Web-ARR
    
    # Enable proxy
    Set-WebConfigurationProperty -PSPath "MACHINE/WEBROOT/APPHOST" `
        -Filter "system.webServer/proxy" `
        -Name "enabled" `
        -Value "True"
    
  4. IIS sub-application not configured at /roa2web

    The frontend must be deployed as IIS sub-application at path /roa2web, NOT as root site.

Issue: Frontend loads but shows blank page

Symptoms:

  • Browser shows white screen
  • Console error: Failed to load module script
  • Assets return 404

Solution: Check base in vite.config.js matches IIS sub-application path.

Issue: CORS errors

Symptoms:

  • API calls blocked by CORS policy
  • Console: Access-Control-Allow-Origin error

Solution: Backend should see requests as same-origin (via IIS proxy), so CORS shouldn't apply. If you see CORS errors, the proxy is misconfigured.


Deployment Checklist

Public Server (roa2web.romfast.ro)

  • SSL certificate installed and valid
  • IIS ARR (Application Request Routing) installed
  • web.config configured with reverse proxy to 10.0.20.36
  • Server variables enabled in IIS
  • Firewall allows HTTPS outbound to 10.0.20.36

Internal Server (10.0.20.36)

  • IIS installed and running
  • IIS ARR installed
  • IIS URL Rewrite module installed
  • Sub-application created at /roa2web
  • Frontend files deployed to C:\inetpub\wwwroot\roa2web\
  • web.config includes /roa2web/ prefix in match rules
  • Backend service (ROA2WEB-Backend) running
  • Backend accessible at http://localhost:8000/health
  • Firewall allows HTTPS inbound from public server

Backend Service

  • Windows Service created (NSSM)
  • Service set to auto-start
  • .env configured with correct Oracle credentials
  • Logs directory exists and writable
  • Health check returns 200 OK

Testing Procedure

1. Test Backend Directly (on 10.0.20.36)

# Health check
Invoke-WebRequest http://localhost:8000/health

# API test (without auth)
Invoke-WebRequest http://localhost:8000/api/health

2. Test Internal IIS Proxy (on 10.0.20.36)

# Should proxy to backend
Invoke-WebRequest https://localhost/roa2web/api/health

# Should serve frontend
Invoke-WebRequest https://localhost/roa2web/

3. Test Public Access (from any client)

# Frontend
Invoke-WebRequest https://roa2web.romfast.ro/roa2web/

# API (will fail without auth, but should return 401 not 404)
Invoke-WebRequest https://roa2web.romfast.ro/roa2web/api/health

4. Test with Playwright (comprehensive)

# Use Playwright to test full login flow
./start-playwright.sh

Monitoring & Logs

Public Server Logs

# IIS logs
Get-Content C:\inetpub\logs\LogFiles\W3SVC*\*.log -Tail 50

# Failed Request Tracing (if enabled)
Get-ChildItem C:\inetpub\logs\FailedReqLogFiles

Internal Server Logs

# IIS logs
Get-Content C:\inetpub\logs\LogFiles\W3SVC*\*.log -Tail 50

# Backend service logs
Get-Content C:\inetpub\wwwroot\roa2web\logs\backend-stdout.log -Tail 50 -Wait
Get-Content C:\inetpub\wwwroot\roa2web\logs\backend-stderr.log -Tail 50

Backend Application Logs

# Application log
Get-Content C:\inetpub\wwwroot\roa2web\backend\logs\app.log -Tail 100

Security Considerations

SSL/TLS

  • Public server handles SSL termination
  • Internal communication can use HTTPS (current) or HTTP (simpler)
  • Certificate management only needed on public server

Firewall Rules

Public Server:

  • Allow inbound: 443 (HTTPS)
  • Allow outbound: 443 to 10.0.20.36

Internal Server:

  • Allow inbound: 443 from public server IP only
  • No need to expose port 8000 externally (backend is localhost-only)

Headers

Public server sets forwarding headers:

  • X-Forwarded-Proto: https - Original protocol
  • X-Forwarded-Host: roa2web.romfast.ro - Original hostname
  • X-Real-IP: {CLIENT_IP} - Client IP address

Backend can use these for logging and security.


Version History

Version Date Changes
1.0.0 2025-12-30 Initial documentation

Last Updated: 2025-12-30 ROA2WEB Two-Tier IIS Deployment Architecture