WINEJS WINEJSvARCHITECTURE

https://igiteam.github.io/sh_terminal/ WineJS+VsCodeServer+ForgejoGitLFS+Pufferpanel
Windows Web App Platform - Technical Overview
🎯 THE WINE PATTERN

We'll have:
  • KasmVNC instances (ports 6901-6999) → Each running one Windows app
  • App Registry (stores app paths, configs)
  • App Translator → The bridge that routes /appname to the right KasmVNC instance

───────────────────────────────────────────────────────────────

📝 THE APP TRANSLATOR SERVICE

// /opt/app-translator/index.js
const express = require('express');
const httpProxy = require('http-proxy');
const fs = require('fs').promises;
const path = require('path');
const app = express();
const proxy = httpProxy.createProxyServer();

const APPS_DIR = '/opt/apps';
const PORT = process.env.PORT || 3000;

// App registry - maps app names to ports and configs
let appRegistry = {};

// Load app registry
async function loadApps() {
    const apps = await fs.readdir(APPS_DIR);
    let port = 6901;

    for (const app of apps) {
        const appPath = path.join(APPS_DIR, app);
        const stat = await fs.stat(appPath);

        if (stat.isDirectory()) {
            // Check if this app has a launch config
            try {
                const config = JSON.parse(
                    await fs.readFile(path.join(appPath, 'config.json'), 'utf8')
                );

                appRegistry[app] = {
                    port: port,
                    name: config.name || app,
                    executable: config.executable,
                    path: appPath,
                    installType: config.installType || 'portable' // or 'installer'
                };

                port++;
            } catch (err) {
                console.log(`No config for ${app}, skipping`);
            }
        }
    }

    console.log('✅ App registry loaded:', appRegistry);
}

// Health check
app.get('/health', (req, res) => {
    res.json({
        status: 'OK',
        apps: Object.keys(appRegistry).length
    });
});

// List available apps
app.get('/apps', (req, res) => {
    res.json(appRegistry);
});

// Main translator - routes /appname to the right KasmVNC
app.get('/:appName/*?', async (req, res) => {
    const appName = req.params.appName;
    const app = appRegistry[appName];

    if (!app) {
        return res.status(404).send(`App '${appName}' not found`);
    }

    // Check if KasmVNC instance is running
    const target = `http://127.0.0.1:${app.port}`;

    try {
        // Test if instance is up
        await fetch(`${target}/health`);

        // Proxy the request to KasmVNC
        proxy.web(req, res, { target });
    } catch (err) {
        // Instance not running - try to start it
        console.log(`Starting KasmVNC for ${appName} on port ${app.port}`);

        // Start the Docker container
        const { exec } = require('child_process');
        exec(`cd /opt/kasmvnc-instances/${appName} && docker-compose up -d`);

        // Wait for it to start
        setTimeout(() => {
            proxy.web(req, res, { target });
        }, 5000);
    }
});

// WebSocket support for VNC
app.on('upgrade', (req, socket, head) => {
    const appName = req.url.split('/')[1];
    const app = appRegistry[appName];

    if (app) {
        proxy.ws(req, socket, head, { target: `http://127.0.0.1:${app.port}` });
    }
});

// Start server
loadApps().then(() => {
    app.listen(PORT, '0.0.0.0', () => {
        console.log(`✅ App Translator running on port ${PORT}`);
        console.log(`   Registered apps: ${Object.keys(appRegistry).length}`);
    });
});

───────────────────────────────────────────────────────────────

📁 APP CONFIGURATION FORMAT

Each app gets a config.json in its folder:

// /opt/apps/milkshape/config.json
{
    "name": "MilkShape 3D",
    "executable": "ms3d.exe",
    "installType": "portable",
    "wineVersion": "wine",
    "winePrefix": "/home/kasm-user/.wine",
    "workingDir": "/app",
    "args": [],
    "env": {
        "WINEDLLOVERRIDES": "mscoree,mshtml=n"
    }
}

// /opt/apps/gimp/config.json (if it needs installation)
{
    "name": "GIMP",
    "installer": "gimp-installer.exe",
    "installType": "installer",
    "executable": "gimp.exe",
    "installPath": "C:\\Program Files\\GIMP 2",
    "wineVersion": "wine",
    "postInstall": [
        "wine regedit /s settings.reg"
    ]
}

───────────────────────────────────────────────────────────────

🐳 KASMVNC INSTANCE TEMPLATE

# /opt/kasmvnc-instances/[appname]/docker-compose.yml
version: '3.8'

services:
  kasmvnc-${APP_NAME}:
    image: kasmvnc-wine-base:latest
    container_name: kasmvnc-${APP_NAME}
    restart: unless-stopped
    ports:
      - "127.0.0.1:${APP_PORT}:6901"
    shm_size: "512m"
    environment:
      - VNC_PW=${APP_PASSWORD}
      - STARTUP_CMD=/app/launch.sh
      - APP_NAME=${APP_NAME}
      - APP_CONFIG=/app/config.json
    volumes:
      - /opt/apps/${APP_NAME}:/app:ro
      - /var/www/uploads:/uploads:ro
      - /opt/kasmvnc-instances/${APP_NAME}/vnc:/home/kasm-user/.vnc
      - /opt/kasmvnc-instances/common/wine-prefixes/${APP_NAME}:/home/kasm-user/.wine
    devices:
      - /dev/dri:/dev/dri  # GPU passthrough if available
    networks:
      - kasmvnc-net

networks:
  kasmvnc-net:
    driver: bridge

───────────────────────────────────────────────────────────────

🌐 NGINX CONFIGURATION

# /etc/nginx/sites-available/wine-domain

# Wildcard SSL certificate
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;

# Main domain - App Translator
server {
    listen 80;
    server_name wine.yourdomain.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name wine.yourdomain.com;

    # Proxy all requests to App Translator
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        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;

        # WebSocket support for VNC
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        # Long timeouts for VNC sessions
        proxy_read_timeout 86400s;
        proxy_send_timeout 86400s;
    }
}

# Upload portal
server {
    listen 80;
    server_name upload.yourdomain.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name upload.yourdomain.com;

    location / {
        proxy_pass http://127.0.0.1:3100;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

# Download portal
server {
    listen 80;
    server_name fileserver.yourdomain.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name fileserver.yourdomain.com;

    location / {
        proxy_pass http://127.0.0.1:3200;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

───────────────────────────────────────────────────────────────

📦 PM2 ECOSYSTEM (EXACTLY LIKE YOUR SCRIPT)

// /opt/ecosystem.config.js
module.exports = {
    apps: [
        {
            name: 'app-translator',
            cwd: '/opt/app-translator',
            script: 'index.js',
            watch: false,
            instances: 1,
            exec_mode: 'fork',
            max_memory_restart: '200M',
            env: {
                NODE_ENV: 'production',
                PORT: 3000
            }
        },
        {
            name: 'dumbdrop',
            cwd: '/opt/dumbdrop',
            script: 'server.js',
            watch: false,
            instances: 1,
            max_memory_restart: '200M',
            env: {
                PORT: 3100,
                UPLOAD_DIR: '/var/www/uploads'
            }
        },
        {
            name: 'fileserver',
            cwd: '/opt/fileserver',
            script: 'app.js',
            watch: false,
            instances: 1,
            max_memory_restart: '200M',
            env: {
                PORT: 3200,
                DOWNLOAD_DIR: '/var/www/uploads',
                PASSWORD: process.env.FILESERVER_PASS
            }
        }
    ]
};

───────────────────────────────────────────────────────────────

🎯 USER EXPERIENCE

┌─────────────────────────────────┬────────────────────────────────┐
│ URL                             │ What Happens                  │
├─────────────────────────────────┼────────────────────────────────┤
│ https://wine.domain/milkshape   │ Launches MilkShape 3D fullscreen │
│ https://wine.domain/gimp        │ Launches GIMP fullscreen       │
│ https://wine.domain/photoshop   │ Launches Photoshop fullscreen  │
│ https://upload.domain           │ Upload files (installers, models) │
│ https://fileserver.domain       │ Download saved files (password) │
└─────────────────────────────────┴────────────────────────────────┘

───────────────────────────────────────────────────────────────

🔄 HOW IT WORKS

  1️⃣ User visits https://wine.domain/milkshape
  2️⃣ App Translator checks if MilkShape's KasmVNC is running
  3️⃣ If not running, starts the Docker container for MilkShape
  4️⃣ Proxies the request to the right KasmVNC instance (port 6901)
  5️⃣ User gets fullscreen MilkShape 3D in their browser
  6️⃣ Uploads go to shared /var/www/uploads
  7️⃣ Downloads served by FileServer with password

───────────────────────────────────────────────────────────────

📊 PORT ALLOCATION

┌─────────────────┬────────┬──────────────────────────────┐
│ Service         │ Port   │ Purpose                      │
├─────────────────┼────────┼──────────────────────────────┤
│ App Translator  │ 3000   │ Main entry point             │
│ DumbDrop        │ 3100   │ Upload portal                │
│ FileServer      │ 3200   │ Download portal              │
│ KasmVNC App 1   │ 6901   │ MilkShape 3D                 │
│ KasmVNC App 2   │ 6902   │ GIMP                         │
│ KasmVNC App 3   │ 6903   │ Photoshop                    │
│ ...             │ ...    │ ...                          │
│ KasmVNC App 100 │ 7000   │ App #100                     │
└─────────────────┴────────┴──────────────────────────────┘

───────────────────────────────────────────────────────────────

✅ ADVANTAGES OF THIS PATTERN

  • 🎯 Single domain - wine.domain/appname instead of subdomains
  • 🚀 Auto-start - Apps start on-demand, stop when idle
  • 💰 Resource efficient - Only run apps people actually use
  • 📈 Scalable - Add 500 apps easily
  • 🔄 Same pattern as your working screenshot script
  • 📤📥 Upload/Download integrated exactly as you wanted

───────────────────────────────────────────────────────────────

WINEDROP v1.0 • Windows App Streaming Platform • GPU Accelerated • 500+ Apps Ready