Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
apps/
packages/
docs/
node_modules/
bun.lock
.git/
.gitignore
CLAUDE.md
tsconfig.json
biome.json
*.ts
*.tsx
53 changes: 53 additions & 0 deletions Dockerfile
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dockerfile not working, it is showing a Not Found page when accessing localhost:8080.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, do not hardcode the API key in the Dockerfile, it is a very bad security practice.

Copy link
Author

@voarsh2 voarsh2 Aug 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, Mr. Not Reviewer. It's a draft, and it's taken a long time to get any of my PR's merged or worked on...

Dockerfile not working, it is showing a Not Found page when accessing localhost:8080.

It does work.
You didn't specify the path correctly. The default needs: /ccflare-default-key/ in your browser. But if you knew how to code properly you'd see that trailing issue

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, don't feel attacked when receiving feedback, we're all trying to make ccflare better.

As a matter of fact, security by obscurity is not the best of the ideas, which in this PR, seems to be your suggestion. There is no real security barrier when someone finally discovers the path. There should be a real authentication, access control, or something similar.

Finally, you are changing the path of how someone would access their already working ccflare instance, which is considered a breaking change.

You are suggesting changes on a publicly accessible platform, expect comments from other "Not Reviewers".

Thanks!

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will spend some time when I have a minute to go over this (and do some clean up; docs; etc.) & get it merged.

Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Multi-stage build for ccflare
FROM oven/bun:1-alpine AS builder

WORKDIR /app

# Copy package files for dependency caching
COPY package.json bun.lock* ./

# Copy all source code (required for workspace dependencies)
COPY . .

# Install dependencies
RUN bun install --frozen-lockfile

# Build the project
RUN bun run build

# Production stage
FROM oven/bun:1-alpine AS runner

WORKDIR /app

# Install SQLite tools for database repair and debugging
RUN apk add --no-cache sqlite

# Create non-root user
RUN addgroup -g 1001 -S ccflare && \
adduser -S ccflare -u 1001 -G ccflare

# Copy built application
COPY --from=builder --chown=ccflare:ccflare /app .

# Copy repair scripts
COPY --chown=ccflare:ccflare scripts/ /app/scripts/
RUN find /app/scripts -name '*.sh' -type f -exec chmod +x {} + 2>/dev/null || true

# Create data directory for SQLite database
RUN mkdir -p /app/data && chown ccflare:ccflare /app/data

# Switch to non-root user
USER ccflare

# Set API key for authentication (change this in production!)
ENV API_KEY=ccflare-default-key

# Set database path to persistent volume mount
ENV ccflare_DB_PATH=/app/data/ccflare.db

# Expose port
EXPOSE 8080

# Start the server (not TUI)
CMD ["bun", "run", "server"]
53 changes: 47 additions & 6 deletions apps/server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,24 +196,65 @@ export default function startServer(options?: {
return apiResponse;
}

// Check API key for auth protection
const apiKey = process.env.API_KEY;

// Dashboard routes (only if enabled)
if (withDashboard) {
if (url.pathname === "/" || url.pathname === "/dashboard") {
// Dashboard routes with API key protection
if (url.pathname === "/" || url.pathname === "/dashboard" ||
(apiKey && url.pathname === `/${apiKey}/`)) {

// If API key is required, only allow /{key}/ access
if (apiKey && url.pathname !== `/${apiKey}/`) {
return new Response("Not Found", { status: HTTP_STATUS.NOT_FOUND });
}

return serveDashboardFile("/index.html", "text/html");
}

// Serve dashboard static assets
if ((dashboardManifest as Record<string, string>)[url.pathname]) {
// Serve dashboard static assets with auth protection
let assetPathname = url.pathname;
let isAuthenticatedAssetRequest = false;

// If API key is set, check for auth-prefixed asset paths
if (apiKey && url.pathname.startsWith(`/${apiKey}/`)) {
// Strip the key prefix for asset lookup
assetPathname = url.pathname.substring(`/${apiKey}`.length);
isAuthenticatedAssetRequest = true;
}

if ((dashboardManifest as Record<string, string>)[assetPathname]) {
// If API key is required but request is not authenticated, block access
if (apiKey && !isAuthenticatedAssetRequest) {
return new Response("Not Found", { status: HTTP_STATUS.NOT_FOUND });
}

return serveDashboardFile(
url.pathname,
assetPathname,
undefined,
CACHE.CACHE_CONTROL_STATIC,
);
}
}

// All other paths go to proxy
return handleProxy(req, url, proxyContext);
// Handle API authentication and proxying
if (apiKey) {
// Auth required - check for /key/v1/ format
const pathParts = url.pathname.split('/').filter(Boolean);
if (pathParts[0] === apiKey && pathParts[1] === 'v1') {
// Valid auth - rewrite path and proxy
url.pathname = '/' + pathParts.slice(1).join('/');
return handleProxy(req, url, proxyContext);
}
return new Response("Not Found", { status: HTTP_STATUS.NOT_FOUND });
} else {
// No auth required - allow direct /v1/ access
if (!url.pathname.startsWith("/v1/")) {
return new Response("Not Found", { status: HTTP_STATUS.NOT_FOUND });
}
return handleProxy(req, url, proxyContext);
}
},
});

Expand Down
59 changes: 59 additions & 0 deletions deploy/k8-yaml/k8s-deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: ccflare-data
namespace: coder
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
storageClassName: ceph-filesystem
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: ccflare
namespace: coder
labels:
app: ccflare
spec:
replicas: 1
selector:
matchLabels:
app: ccflare
template:
metadata:
labels:
app: ccflare
spec:
securityContext:
runAsUser: 1001
runAsGroup: 1001
fsGroup: 1001
containers:
- name: ccflare
image: 192.168.96.61:30009/library/ccflare-fork:latest
ports:
- containerPort: 8080
volumeMounts:
- name: ccflare-data
mountPath: /app/data
volumes:
- name: ccflare-data
persistentVolumeClaim:
claimName: ccflare-data
---
apiVersion: v1
kind: Service
metadata:
name: ccflare-service
namespace: coder
spec:
selector:
app: ccflare
ports:
- port: 8080
targetPort: 8080
type: ClusterIP
3 changes: 3 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// ccflare - Claude load balancer proxy
// Placeholder package - implementation coming soon
module.exports = {};
Loading