Final Stack: Go Proxy on 7860
Browse files- Dockerfile +25 -23
- static/status.html +7 -20
Dockerfile
CHANGED
|
@@ -1,49 +1,51 @@
|
|
| 1 |
-
# 1.
|
| 2 |
FROM golang:1.24-alpine AS builder
|
| 3 |
WORKDIR /app
|
| 4 |
COPY . .
|
| 5 |
RUN go mod download
|
| 6 |
RUN CGO_ENABLED=0 GOOS=linux go build -o /app/cliproxy ./cmd/server/
|
| 7 |
|
| 8 |
-
# 2.
|
| 9 |
-
FROM
|
| 10 |
-
|
| 11 |
-
# Cache Buster
|
| 12 |
-
ENV BUILD_ID=123456789
|
| 13 |
|
| 14 |
USER root
|
| 15 |
|
| 16 |
-
# Install dependencies
|
| 17 |
-
RUN
|
|
|
|
|
|
|
| 18 |
|
| 19 |
-
#
|
| 20 |
COPY --from=builder /app/cliproxy /usr/local/bin/cliproxy
|
| 21 |
COPY config.yaml /etc/cliproxy/config.yaml
|
| 22 |
COPY static /etc/cliproxy/static
|
| 23 |
|
| 24 |
-
# Free APIs Proxy
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
|
| 29 |
-
#
|
| 30 |
ENV MANAGEMENT_STATIC_PATH=/etc/cliproxy/static
|
| 31 |
|
| 32 |
WORKDIR /app
|
| 33 |
RUN mkdir -p /app/auth && chmod 777 /app/auth
|
| 34 |
|
| 35 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
RUN echo "#!/bin/bash" > /start.sh && \
|
| 37 |
-
echo "# Fix config for local usage in HF Space" >> /start.sh && \
|
| 38 |
echo "sed -i 's/freeapis-proxy:5001/localhost:5001/g' /etc/cliproxy/config.yaml" >> /start.sh && \
|
| 39 |
-
echo "echo '
|
| 40 |
-
echo "
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
echo "Starting
|
| 44 |
-
echo "exec /usr/local/bin/cliproxy -config /etc/cliproxy/config.yaml -port
|
| 45 |
chmod +x /start.sh
|
| 46 |
|
| 47 |
-
EXPOSE
|
| 48 |
|
| 49 |
ENTRYPOINT ["/start.sh"]
|
|
|
|
| 1 |
+
# 1. Build Go Proxy
|
| 2 |
FROM golang:1.24-alpine AS builder
|
| 3 |
WORKDIR /app
|
| 4 |
COPY . .
|
| 5 |
RUN go mod download
|
| 6 |
RUN CGO_ENABLED=0 GOOS=linux go build -o /app/cliproxy ./cmd/server/
|
| 7 |
|
| 8 |
+
# 2. Runtime (Puter Base)
|
| 9 |
+
FROM heyputer/puter:latest
|
|
|
|
|
|
|
|
|
|
| 10 |
|
| 11 |
USER root
|
| 12 |
|
| 13 |
+
# Install dependencies for Python proxy
|
| 14 |
+
RUN if command -v apt-get >/dev/null; then \
|
| 15 |
+
apt-get update && apt-get install -y python3-pip curl procps && rm -rf /var/lib/apt/lists/*; \
|
| 16 |
+
fi
|
| 17 |
|
| 18 |
+
# Copy Go Proxy & Assets
|
| 19 |
COPY --from=builder /app/cliproxy /usr/local/bin/cliproxy
|
| 20 |
COPY config.yaml /etc/cliproxy/config.yaml
|
| 21 |
COPY static /etc/cliproxy/static
|
| 22 |
|
| 23 |
+
# Setup Free APIs Proxy
|
| 24 |
+
COPY freeapis-proxy /app/freeapis-proxy
|
| 25 |
+
WORKDIR /app/freeapis-proxy
|
| 26 |
+
RUN pip3 install --break-system-packages -r requirements.txt || pip3 install -r requirements.txt
|
| 27 |
|
| 28 |
+
# Environment Variables
|
| 29 |
ENV MANAGEMENT_STATIC_PATH=/etc/cliproxy/static
|
| 30 |
|
| 31 |
WORKDIR /app
|
| 32 |
RUN mkdir -p /app/auth && chmod 777 /app/auth
|
| 33 |
|
| 34 |
+
# Startup Script
|
| 35 |
+
# 1. Fix config to use localhost
|
| 36 |
+
# 2. Start FreeAPIs Proxy (Background, Port 5001)
|
| 37 |
+
# 3. Start Puter (Background, Port 8081 - Internal)
|
| 38 |
+
# 4. Start Go Proxy (Foreground, Port 7860 - Public)
|
| 39 |
RUN echo "#!/bin/bash" > /start.sh && \
|
|
|
|
| 40 |
echo "sed -i 's/freeapis-proxy:5001/localhost:5001/g' /etc/cliproxy/config.yaml" >> /start.sh && \
|
| 41 |
+
echo "echo 'Starting Free APIs Proxy (5001)...'" >> /start.sh && \
|
| 42 |
+
echo "cd /app/freeapis-proxy && PORT=5001 python3 server.py > /tmp/freeapis.log 2>&1 &" >> /start.sh && \
|
| 43 |
+
echo "echo 'Starting Puter (8081)...'" >> /start.sh && \
|
| 44 |
+
echo "PORT=8081 python3 /opt/puter/puter/server.py --port 8081 > /tmp/puter.log 2>&1 &" >> /start.sh && \
|
| 45 |
+
echo "echo 'Starting Go Proxy (7860)...'" >> /start.sh && \
|
| 46 |
+
echo "exec /usr/local/bin/cliproxy -config /etc/cliproxy/config.yaml -port 7860" >> /start.sh && \
|
| 47 |
chmod +x /start.sh
|
| 48 |
|
| 49 |
+
EXPOSE 7860
|
| 50 |
|
| 51 |
ENTRYPOINT ["/start.sh"]
|
static/status.html
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
<!DOCTYPE html>
|
| 2 |
<html lang="en">
|
| 3 |
<head>
|
|
@@ -88,7 +89,7 @@
|
|
| 88 |
.modal-overlay {
|
| 89 |
position: fixed;
|
| 90 |
top: 0; left: 0; right: 0; bottom: 0;
|
| 91 |
-
background: rgba(0,0,0,0.9);
|
| 92 |
display: flex;
|
| 93 |
align-items: center;
|
| 94 |
justify-content: center;
|
|
@@ -184,22 +185,18 @@
|
|
| 184 |
</div>
|
| 185 |
|
| 186 |
<script>
|
|
|
|
| 187 |
const API_BASE = "/v0/management";
|
| 188 |
|
| 189 |
function getKey() {
|
| 190 |
return localStorage.getItem("managementKey");
|
| 191 |
}
|
| 192 |
|
| 193 |
-
// Initialize: Check auth immediately
|
| 194 |
function init() {
|
| 195 |
const key = getKey();
|
| 196 |
if (key) {
|
| 197 |
-
// If we have a key, hide the login screen and fetch data
|
| 198 |
document.getElementById('login-modal').classList.add('hidden');
|
| 199 |
fetchData();
|
| 200 |
-
} else {
|
| 201 |
-
// No key? The login modal is visible by default.
|
| 202 |
-
console.log("No auth key found, waiting for login...");
|
| 203 |
}
|
| 204 |
}
|
| 205 |
|
|
@@ -214,7 +211,6 @@
|
|
| 214 |
btn.innerText = "Checking...";
|
| 215 |
btn.disabled = true;
|
| 216 |
|
| 217 |
-
// Test the key
|
| 218 |
try {
|
| 219 |
const res = await fetch(API_BASE + "/config", {
|
| 220 |
headers: { "Authorization": "Bearer " + key }
|
|
@@ -230,7 +226,7 @@
|
|
| 230 |
}
|
| 231 |
} catch (err) {
|
| 232 |
console.error(err);
|
| 233 |
-
showLoginError("Connection failed
|
| 234 |
} finally {
|
| 235 |
btn.innerText = originalText;
|
| 236 |
btn.disabled = false;
|
|
@@ -251,26 +247,21 @@
|
|
| 251 |
|
| 252 |
async function fetchData() {
|
| 253 |
const key = getKey();
|
| 254 |
-
if (!key) return;
|
| 255 |
|
| 256 |
const headers = { "Authorization": "Bearer " + key };
|
| 257 |
|
| 258 |
try {
|
| 259 |
-
// Parallel fetch
|
| 260 |
const [configRes, usageRes] = await Promise.all([
|
| 261 |
fetch(API_BASE + "/config", { headers }),
|
| 262 |
fetch(API_BASE + "/usage", { headers })
|
| 263 |
]);
|
| 264 |
|
| 265 |
if (configRes.status === 401 || usageRes.status === 401) {
|
| 266 |
-
handleLogout();
|
| 267 |
return;
|
| 268 |
}
|
| 269 |
|
| 270 |
-
if (!configRes.ok || !usageRes.ok) {
|
| 271 |
-
throw new Error("API Error");
|
| 272 |
-
}
|
| 273 |
-
|
| 274 |
const config = await configRes.json();
|
| 275 |
const usage = await usageRes.json();
|
| 276 |
|
|
@@ -308,7 +299,6 @@
|
|
| 308 |
function renderProviders(config, usage) {
|
| 309 |
let html = "<table><thead><tr><th>Provider / Model</th><th>Type</th><th>Key Configured</th></tr></thead><tbody>";
|
| 310 |
|
| 311 |
-
// OpenAI Compatibility
|
| 312 |
if (config.openaiCompatibility) {
|
| 313 |
config.openaiCompatibility.forEach(p => {
|
| 314 |
html += `<tr>
|
|
@@ -319,7 +309,6 @@
|
|
| 319 |
});
|
| 320 |
}
|
| 321 |
|
| 322 |
-
// Gemini
|
| 323 |
if (config.geminiApiKeys) {
|
| 324 |
html += `<tr>
|
| 325 |
<td>Gemini (Google)</td>
|
|
@@ -328,7 +317,6 @@
|
|
| 328 |
</tr>`;
|
| 329 |
}
|
| 330 |
|
| 331 |
-
// Free APIs
|
| 332 |
if (config.freeapis && config.freeapis.enabled) {
|
| 333 |
html += `<tr>
|
| 334 |
<td>Free APIs (g4f/Cohere)</td>
|
|
@@ -345,8 +333,7 @@
|
|
| 345 |
document.getElementById("activity-log").innerHTML = "<p>Check logs for detailed activity.</p>";
|
| 346 |
}
|
| 347 |
|
| 348 |
-
// Start everything
|
| 349 |
document.addEventListener('DOMContentLoaded', init);
|
| 350 |
</script>
|
| 351 |
</body>
|
| 352 |
-
</html>
|
|
|
|
| 1 |
+
<!-- Build Trigger: 1 -->
|
| 2 |
<!DOCTYPE html>
|
| 3 |
<html lang="en">
|
| 4 |
<head>
|
|
|
|
| 89 |
.modal-overlay {
|
| 90 |
position: fixed;
|
| 91 |
top: 0; left: 0; right: 0; bottom: 0;
|
| 92 |
+
background: rgba(0,0,0,0.9);
|
| 93 |
display: flex;
|
| 94 |
align-items: center;
|
| 95 |
justify-content: center;
|
|
|
|
| 185 |
</div>
|
| 186 |
|
| 187 |
<script>
|
| 188 |
+
// Serving from /v0/management/status.html
|
| 189 |
const API_BASE = "/v0/management";
|
| 190 |
|
| 191 |
function getKey() {
|
| 192 |
return localStorage.getItem("managementKey");
|
| 193 |
}
|
| 194 |
|
|
|
|
| 195 |
function init() {
|
| 196 |
const key = getKey();
|
| 197 |
if (key) {
|
|
|
|
| 198 |
document.getElementById('login-modal').classList.add('hidden');
|
| 199 |
fetchData();
|
|
|
|
|
|
|
|
|
|
| 200 |
}
|
| 201 |
}
|
| 202 |
|
|
|
|
| 211 |
btn.innerText = "Checking...";
|
| 212 |
btn.disabled = true;
|
| 213 |
|
|
|
|
| 214 |
try {
|
| 215 |
const res = await fetch(API_BASE + "/config", {
|
| 216 |
headers: { "Authorization": "Bearer " + key }
|
|
|
|
| 226 |
}
|
| 227 |
} catch (err) {
|
| 228 |
console.error(err);
|
| 229 |
+
showLoginError("Connection failed");
|
| 230 |
} finally {
|
| 231 |
btn.innerText = originalText;
|
| 232 |
btn.disabled = false;
|
|
|
|
| 247 |
|
| 248 |
async function fetchData() {
|
| 249 |
const key = getKey();
|
| 250 |
+
if (!key) return;
|
| 251 |
|
| 252 |
const headers = { "Authorization": "Bearer " + key };
|
| 253 |
|
| 254 |
try {
|
|
|
|
| 255 |
const [configRes, usageRes] = await Promise.all([
|
| 256 |
fetch(API_BASE + "/config", { headers }),
|
| 257 |
fetch(API_BASE + "/usage", { headers })
|
| 258 |
]);
|
| 259 |
|
| 260 |
if (configRes.status === 401 || usageRes.status === 401) {
|
| 261 |
+
handleLogout();
|
| 262 |
return;
|
| 263 |
}
|
| 264 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 265 |
const config = await configRes.json();
|
| 266 |
const usage = await usageRes.json();
|
| 267 |
|
|
|
|
| 299 |
function renderProviders(config, usage) {
|
| 300 |
let html = "<table><thead><tr><th>Provider / Model</th><th>Type</th><th>Key Configured</th></tr></thead><tbody>";
|
| 301 |
|
|
|
|
| 302 |
if (config.openaiCompatibility) {
|
| 303 |
config.openaiCompatibility.forEach(p => {
|
| 304 |
html += `<tr>
|
|
|
|
| 309 |
});
|
| 310 |
}
|
| 311 |
|
|
|
|
| 312 |
if (config.geminiApiKeys) {
|
| 313 |
html += `<tr>
|
| 314 |
<td>Gemini (Google)</td>
|
|
|
|
| 317 |
</tr>`;
|
| 318 |
}
|
| 319 |
|
|
|
|
| 320 |
if (config.freeapis && config.freeapis.enabled) {
|
| 321 |
html += `<tr>
|
| 322 |
<td>Free APIs (g4f/Cohere)</td>
|
|
|
|
| 333 |
document.getElementById("activity-log").innerHTML = "<p>Check logs for detailed activity.</p>";
|
| 334 |
}
|
| 335 |
|
|
|
|
| 336 |
document.addEventListener('DOMContentLoaded', init);
|
| 337 |
</script>
|
| 338 |
</body>
|
| 339 |
+
</html>
|