This skill should be used when users want to expose local web applications publicly through the Distiller platform's built-in reverse proxy, or when fixing path issues (CSS/JS not loading, API 404s)...
The Distiller platform provides a built-in reverse proxy capability that exposes local web applications to the internet via HTTPS without requiring tunnel services, port forwarding, or proxy configuration. This skill addresses the common issue of absolute paths breaking behind reverse proxies and provides tools to fix these issues automatically.
βΉοΈ You might also see frpc/frps mentioned elsewhere. Those tunnels are optional: the Distiller proxy works entirely on-device. Use the FRP context in this doc only if your deployment already relies on
/etc/frp/frpc.tomlto advertise a hostname such astest.devices.pamir.ai.
Key Pattern:
Local App (port 5000) β Distiller Proxy β Public HTTPS URL
http://localhost:5000 β https://subdomain.devices.pamir.ai/distiller/proxy/5000/
Use this skill when users:
Step 1: Run the app locally
# Any web server on any port
python app.py # Flask on 5000
npm run dev # Vite on 3000
python -m http.server 8080 # HTTP server on 8080
Step 2: Access via proxy URL
https://{subdomain}.devices.pamir.ai/distiller/proxy/{PORT}/
Where do I get the subdomain?
/etc/frp/frpc.toml:cat /etc/frp/frpc.toml | grep subdomain
# Example output: subdomain = "test"
# Your URL: https://test.devices.pamir.ai/distiller/proxy/5000/
Step 3: Fix path issues if needed
# Check for issues
./scripts/check-paths.sh /path/to/app
# Auto-fix common patterns
./scripts/fix-paths.sh /path/to/app
Root cause: Absolute paths (starting with /) resolve to domain root, breaking when behind reverse proxies.
Symptoms:
Examples:
β Broken (absolute paths):
<link rel="stylesheet" href="/styles.css">
<script src="/main.js"></script>
Resolves to: https://domain.com/styles.css (wrong - not behind proxy path)
β Working (relative paths):
<link rel="stylesheet" href="styles.css">
<script src="main.js"></script>
Resolves to: https://domain.com/distiller/proxy/5000/styles.css (correct)
Use the provided script to automatically fix common patterns:
# Dry run (preview changes)
./scripts/fix-paths.sh /path/to/app --dry-run
# Apply fixes
./scripts/fix-paths.sh /path/to/app
The script fixes:
href="/..." β href="..."src="/..." β src="..."API_BASE = '/api' β API_BASE = 'api'fetch('/api/...') β fetch('api/...')β οΈ The fixer is intentionally conservative but still performs in-place edits. Run with
--dry-runfirst, keep backups (e.g., via git), and review the diffβprojects sometimes rely on intentional absolute URLs.
HTML Files:
<!-- BEFORE -->
<link rel="stylesheet" href="/styles.css">
<script src="/main.js"></script>
<img src="/logo.png">
<!-- AFTER -->
<link rel="stylesheet" href="styles.css">
<script src="main.js"></script>
<img src="logo.png">
JavaScript Files:
// BEFORE
const API_BASE = '/api';
fetch('/api/data');
// AFTER
const API_BASE = 'api';
fetch('api/data');
Exception: Keep absolute paths for external resources:
<!-- These are fine (external URLs) -->
<script src="https://cdn.example.com/library.js"></script>
/watchdog)Sometimes you must keep the proxy path segment (such as /distiller/proxy/5000/watchdog) instead of stripping the /. The pattern below lets your app live under any prefix without code rewrites each time.
Server-side base path variable
# systemd / shell
export BASE_PATH=/watchdog
// Express example
const basePath = (process.env.BASE_PATH || '/').replace(/\/$/, '');
app.use(`${basePath}/static`, express.static('public'));
app.use(basePath, router);
app.get(`${basePath}/health`, handler);
Inject the base path into rendered HTML
<script>window.BASE_PATH = "{{ basePath }}";</script>
<link rel="stylesheet" href="{{ basePath }}/static/dashboard.css">
<script src="{{ basePath }}/static/app.js"></script>
Teach the front-end to respect it
const BASE = window.BASE_PATH || '';
await fetch(`${BASE}/api/status`);
With those three pieces, requests to https://subdomain.devices.pamir.ai/distiller/proxy/5000/watchdog/β¦ stay scoped to /watchdog on both server and client, preventing 404s while still allowing the app to run at / locally.
Works well by default. Only fix HTML templates.
# No changes needed to Flask code
app = Flask(__name__, static_folder='static')
@app.route('/api/data')
def data():
return jsonify({'status': 'ok'})
Fix: Change paths in HTML templates from absolute to relative.
Add base path configuration:
// vite.config.js
export default defineConfig({
base: './', // Use relative base path
server: {
host: '0.0.0.0',
port: 3000
}
})
Update package.json:
{
"homepage": "."
}
const express = require('express');
const app = express();
app.use(express.static('public'));
app.get('/api/data', (req, res) => {
res.json({ status: 'ok' });
});
app.listen(5000, '0.0.0.0');
Fix: HTML files to use relative paths.
Cause: MIME type mismatch
Solution:
href="styles.css" not href="/styles.css"Cause: Flask returning index.html for missing routes
Solution:
# Make sure API routes come BEFORE catch-all
@app.route('/api/data')
def data():
return jsonify({'status': 'ok'})
# This should be last
@app.route('/<path:path>')
def serve_static(path):
return send_from_directory(app.static_folder, path)
Cause: Client-side routing needs server fallback
Solution:
@app.errorhandler(404)
def not_found(e):
# For SPA with client-side routing
if request.path.startswith('/api/'):
return jsonify(error='Not found'), 404
return send_from_directory(app.static_folder, 'index.html')
Cause: Long-running operations (>30s) don't work well through proxy
Symptoms:
Solution: Use background tasks with immediate response
import threading
def run_long_task():
"""Background task"""
# Do expensive work here
time.sleep(60)
# Save results to file/database
@app.route('/api/process', methods=['POST'])
def process_data():
# Start task in background
thread = threading.Thread(target=run_long_task, daemon=True)
thread.start()
# Return immediately
return jsonify({
'success': True,
'message': 'Processing started. Check back in 1-2 minutes.'
})
# Frontend polls for updates
// JavaScript
async function startProcess() {
const res = await fetch('./api/process', { method: 'POST' });
const data = await res.json();
// Poll every 10 seconds for updates
const interval = setInterval(async () => {
const status = await fetch('./api/status');
const result = await status.json();
if (result.complete) {
clearInterval(interval);
// Update UI with results
}
}, 10000);
}
Why: The proxy can't maintain connections for long-running synchronous requests. Background tasks + polling is the standard pattern for web apps.
Use relative paths everywhere
href="style.css"href="/style.css"Namespace API routes
/api/users, /api/data/users (conflicts with static files)Support a base path
/watchdog (or similar) when the proxy requires it.BASE_PATH into templates and prepend it inside JavaScript fetch calls.Test locally first
http://localhost:5000/Document the public URL
Public: https://subdomain.devices.pamir.ai/distiller/proxy/5000/
Local: http://localhost:5000/
# See all listening ports
netstat -tuln | grep LISTEN
# Check specific port
lsof -i :5000
| Port | Typical Use |
|---|---|
| 5000 | Flask apps |
| 3000 | Vite/React dev servers |
| 8000 | Django apps |
| 8080 | Generic web servers |
lsof -i :5000
kill -9 {PID}
# Or one-liner
pkill -f "python app.py"
check-paths.sh - Scans project files for absolute path issues and reports potential problems (uses ripgrep when available, falls back to grep). Accepts a project root and reports findings without modifying files.
fix-paths.sh - Automatically fixes common absolute path patterns in HTML and JavaScript files. Supports --dry-run to preview changes; always review the diff afterwards if you run it for real.
Usage examples are shown in the "Fixing Path Issues" section above.
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β Distiller Proxy Quick Reference β
βββββββββββββββββββββββββββββββββββββββββββββββββββ€
β URL Pattern: β
β https://{subdomain}.devices.pamir.ai/ β
β distiller/proxy/{PORT}/ β
β β
β Fix Checklist: β
β β‘ Remove leading / from href/src β
β β‘ Change API calls to relative paths β
β β‘ Test locally first β
β β‘ Hard refresh browser (Ctrl+Shift+R) β
β β
β Common Fixes: β
β href="/style.css" β href="style.css" β
β src="/main.js" β src="main.js" β
β fetch('/api/...') β fetch('api/...') β
βββββββββββββββββββββββββββββββββββββββββββββββββββ