Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
37 changes: 17 additions & 20 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
# ═══════════════════════════════════════════════════════════
# CandyConnect - Environment Configuration
# Copy this file to .env and customize as needed
# ═══════════════════════════════════════════════════════════

# Panel port (the main web UI + API port)
CC_DATA_DIR=/opt/candyconnect
CC_REDIS_PASSWORD=change-me-generate-with-openssl-rand
CC_REDIS_URL=redis://:change-me-generate-with-openssl-rand@127.0.0.1:6379/0
CC_JWT_SECRET=
CC_PANEL_PORT=8443

# Panel URL path
CC_PANEL_PATH=/candyconnect

# Default admin credentials (CHANGE THESE!)
CC_DOMAIN=vpn.example.com
CC_ADMIN_USER=admin
CC_ADMIN_PASS=admin123

# JWT secret for token signing (auto-generated if empty)
# Generate one with: python3 -c "import secrets; print(secrets.token_urlsafe(48))"
CC_JWT_SECRET=

# Redis connection URL
# For Docker: redis://redis:6379/0 (uses Docker service name)
# For bare-metal: redis://127.0.0.1:6379/0
CC_REDIS_URL=redis://redis:6379/0
# Required on first startup — no default password is accepted by the server.
CC_ADMIN_PASS=change-me-before-first-start
# Optional TLS for uvicorn (prefer reverse proxy in production)
CC_SSL_CERT=/etc/ssl/certs/candyconnect.pem
CC_SSL_KEY=/etc/ssl/private/candyconnect.key
# Comma-separated browser origins allowed to call the API with credentials
CC_CORS_ORIGINS=https://vpn.example.com
CC_JWT_CLIENT_EXPIRE_HOURS=168
# Set to 1 only when you intentionally want install.sh to wipe Redis
CC_WIPE_REDIS=0
# Set to 1 to force-reset admin password from CC_ADMIN_PASS on startup
CC_FORCE_ADMIN_SYNC=0
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ ENV CC_JWT_SECRET=""
ENV CC_PANEL_PORT=8443
ENV CC_PANEL_PATH=/candyconnect
ENV CC_ADMIN_USER=admin
ENV CC_ADMIN_PASS=admin123
ENV CC_ADMIN_PASS=

EXPOSE 8443

Expand Down
11 changes: 9 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ services:
image: redis:7-alpine
container_name: candyconnect-redis
restart: unless-stopped
command: redis-server --requirepass ${CC_REDIS_PASSWORD:?Set CC_REDIS_PASSWORD in .env}
environment:
- REDIS_PASSWORD=${CC_REDIS_PASSWORD}
volumes:
- redis_data:/data
healthcheck:
Expand Down Expand Up @@ -46,12 +49,16 @@ services:
# - "9443:9443" # TrustTunnel
environment:
- CC_DATA_DIR=/opt/candyconnect
- CC_REDIS_URL=redis://redis:6379/0
- CC_REDIS_URL=redis://:${CC_REDIS_PASSWORD}@redis:6379/0
- CC_REDIS_PASSWORD=${CC_REDIS_PASSWORD}
- CC_JWT_SECRET=${CC_JWT_SECRET:-}
- CC_PANEL_PORT=${CC_PANEL_PORT:-8443}
- CC_PANEL_PATH=${CC_PANEL_PATH:-/candyconnect}
- CC_ADMIN_USER=${CC_ADMIN_USER:-admin}
- CC_ADMIN_PASS=${CC_ADMIN_PASS:-admin123}
- CC_ADMIN_PASS=${CC_ADMIN_PASS:?Set CC_ADMIN_PASS in .env before first run}
- CC_CORS_ORIGINS=${CC_CORS_ORIGINS:-}
- CC_SSL_CERT=${CC_SSL_CERT:-}
- CC_SSL_KEY=${CC_SSL_KEY:-}
volumes:
- candyconnect_data:/opt/candyconnect/cores
- candyconnect_backups:/opt/candyconnect/backups
Expand Down
40 changes: 32 additions & 8 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,9 @@ install_dependencies() {
systemctl enable redis-server || true
systemctl start redis-server || true

# Wipe database to ensure fresh config (per user request)
if command -v redis-cli &>/dev/null; then
log "Wiping existing Redis database..."
# Wipe database only when explicitly requested (destructive).
if [ "${CC_WIPE_REDIS:-0}" = "1" ] && command -v redis-cli &>/dev/null; then
warn "CC_WIPE_REDIS=1 — wiping Redis database..."
redis-cli FLUSHALL >/dev/null 2>&1 || true
fi

Expand Down Expand Up @@ -209,26 +209,49 @@ ask_domain() {
generate_secrets() {
info "Generating secrets..."
JWT_SECRET=$(python3 -c "import secrets; print(secrets.token_urlsafe(48))")
REDIS_PASSWORD=$(python3 -c "import secrets; print(secrets.token_urlsafe(24))")
ADMIN_PASS=$(python3 -c "import secrets; print(secrets.token_urlsafe(16))")

# Write env file (only if it doesn't exist, to preserve user customizations)
if [ -f "$CC_DIR/.env" ]; then
warn ".env file already exists. Backing up to .env.bak"
cp "$CC_DIR/.env" "$CC_DIR/.env.bak"
fi

cat > "$CC_DIR/.env" << EOF
CC_DATA_DIR=$CC_DIR
CC_REDIS_URL=redis://127.0.0.1:6379/0
CC_REDIS_URL=redis://:${REDIS_PASSWORD}@127.0.0.1:6379/0
CC_REDIS_PASSWORD=${REDIS_PASSWORD}
CC_JWT_SECRET=$JWT_SECRET
CC_PANEL_PORT=$CC_PORT
CC_PANEL_PATH=/candyconnect
CC_DOMAIN=$CC_DOMAIN
CC_ADMIN_USER=admin
CC_ADMIN_PASS=admin123
CC_ADMIN_PASS=${ADMIN_PASS}
EOF

chmod 600 "$CC_DIR/.env"
log "Secrets generated"
GENERATED_ADMIN_PASS="$ADMIN_PASS"
}

configure_redis_auth() {
if [ ! -f "$CC_DIR/.env" ]; then
return
fi
# shellcheck disable=SC1090
source "$CC_DIR/.env"
if [ -z "${CC_REDIS_PASSWORD:-}" ]; then
return
fi
info "Configuring Redis authentication..."
redis-cli CONFIG SET requirepass "$CC_REDIS_PASSWORD" >/dev/null 2>&1 || true
if [ -f /etc/redis/redis.conf ]; then
if ! grep -q "^requirepass " /etc/redis/redis.conf; then
echo "requirepass $CC_REDIS_PASSWORD" >> /etc/redis/redis.conf
fi
fi
Comment on lines +248 to +252

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Redis password won't update on reinstall if requirepass line already exists.

The grep -q "^requirepass " check prevents duplicate entries, but if the password changes on a subsequent install, the old password in redis.conf won't be updated. The runtime CONFIG SET (line 247) takes effect immediately, but after a Redis restart, the stale password from the config file will be used.

Proposed fix: Replace existing line or update in place
     if [ -f /etc/redis/redis.conf ]; then
-        if ! grep -q "^requirepass " /etc/redis/redis.conf; then
-            echo "requirepass $CC_REDIS_PASSWORD" >> /etc/redis/redis.conf
-        fi
+        if grep -q "^requirepass " /etc/redis/redis.conf; then
+            sed -i "s/^requirepass .*/requirepass $CC_REDIS_PASSWORD/" /etc/redis/redis.conf
+        else
+            echo "requirepass $CC_REDIS_PASSWORD" >> /etc/redis/redis.conf
+        fi
     fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if [ -f /etc/redis/redis.conf ]; then
if ! grep -q "^requirepass " /etc/redis/redis.conf; then
echo "requirepass $CC_REDIS_PASSWORD" >> /etc/redis/redis.conf
fi
fi
if [ -f /etc/redis/redis.conf ]; then
if grep -q "^requirepass " /etc/redis/redis.conf; then
sed -i "s/^requirepass .*/requirepass $CC_REDIS_PASSWORD/" /etc/redis/redis.conf
else
echo "requirepass $CC_REDIS_PASSWORD" >> /etc/redis/redis.conf
fi
fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@install.sh` around lines 248 - 252, The current block that only appends
"requirepass $CC_REDIS_PASSWORD" if no "^requirepass " line exists must be
changed to update the existing line when present so the file reflects the new
password on reinstall; replace the grep-then-echo logic with a safe in-place
edit such as using sed to substitute the line (e.g., sed -i 's/^requirepass
.*/requirepass '"$CC_REDIS_PASSWORD"'/ /etc/redis/redis.conf') and fall back to
appending if the substitution reports no match, ensuring you operate on
/etc/redis/redis.conf and still use the CC_REDIS_PASSWORD variable (the runtime
CONFIG SET call can remain as-is for immediate effect).

systemctl restart redis-server 2>/dev/null || true
log "Redis authentication enabled"
}

create_systemd_service() {
Expand Down Expand Up @@ -324,9 +347,9 @@ print_summary() {
echo -e " ${BOLD}API URL:${NC} http://${SERVER_IP}:${CC_PORT}/api"
echo -e " ${BOLD}Client API:${NC} http://${SERVER_IP}:${CC_PORT}/client-api"
echo -e " ${BOLD}Admin User:${NC} admin"
echo -e " ${BOLD}Admin Pass:${NC} admin123"
echo -e " ${BOLD}Admin Pass:${NC} ${GENERATED_ADMIN_PASS:-see $CC_DIR/.env}"
echo ""
echo -e " ${YELLOW}⚠ Change the default password immediately!${NC}"
echo -e " ${YELLOW}⚠ Save the admin password now — it is stored only in $CC_DIR/.env${NC}"
echo ""
echo -e " ${CYAN}Service commands:${NC}"
echo -e " systemctl status ${CC_SERVICE}"
Expand All @@ -350,6 +373,7 @@ main() {
install_panel
ask_domain
generate_secrets
configure_redis_auth
setup_firewall
create_systemd_service
print_summary
Expand Down
28 changes: 25 additions & 3 deletions server/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,25 @@
CORE_DIR = os.path.join(DATA_DIR, "cores")

# ── Redis ──
REDIS_URL = os.environ.get("CC_REDIS_URL", "redis://127.0.0.1:6379/0")
def _build_redis_url() -> str:
explicit = os.environ.get("CC_REDIS_URL")
if explicit:
return explicit
password = os.environ.get("CC_REDIS_PASSWORD", "")
if password:
return f"redis://:{password}@127.0.0.1:6379/0"
return "redis://127.0.0.1:6379/0"


REDIS_URL = _build_redis_url()

# ── HTTP / CORS ──
CORS_ORIGINS = [
origin.strip()
for origin in os.environ.get("CC_CORS_ORIGINS", "").split(",")
if origin.strip()
]
FORCE_ADMIN_SYNC = os.environ.get("CC_FORCE_ADMIN_SYNC", "").lower() in ("1", "true", "yes")

# ── JWT (persist secret across restarts unless provided via env) ──

Expand Down Expand Up @@ -45,7 +63,7 @@ def _load_or_create_jwt_secret() -> str:
JWT_SECRET = _load_or_create_jwt_secret()
JWT_ALGORITHM = "HS256"
JWT_ADMIN_EXPIRE_HOURS = 24
JWT_CLIENT_EXPIRE_HOURS = 720 # 30 days
JWT_CLIENT_EXPIRE_HOURS = int(os.environ.get("CC_JWT_CLIENT_EXPIRE_HOURS", "168")) # 7 days

# ── Panel ──
PANEL_PORT = int(os.environ.get("CC_PANEL_PORT", "8443"))
Expand All @@ -56,7 +74,11 @@ def _load_or_create_jwt_secret() -> str:

# ── Default Admin ──
DEFAULT_ADMIN_USER = os.environ.get("CC_ADMIN_USER", "admin")
DEFAULT_ADMIN_PASS = os.environ.get("CC_ADMIN_PASS", "admin123")
DEFAULT_ADMIN_PASS = os.environ.get("CC_ADMIN_PASS", "")

# ── TLS (optional uvicorn termination) ──
SSL_CERTFILE = os.environ.get("CC_SSL_CERT", "")
SSL_KEYFILE = os.environ.get("CC_SSL_KEY", "")

# ── Protocols ──
SUPPORTED_PROTOCOLS = [
Expand Down
Loading