diff --git a/.cursor/rules/coding-principles.mdc b/.cursor/rules/coding-principles.mdc
new file mode 100644
index 0000000..fdce377
--- /dev/null
+++ b/.cursor/rules/coding-principles.mdc
@@ -0,0 +1,22 @@
+---
+description: Follow DRY and SOLID programming principles in all code changes
+alwaysApply: true
+---
+
+# Coding Principles
+
+Follow DRY and SOLID principles in all code changes.
+
+## DRY — Don't Repeat Yourself
+
+- Extract repeated logic into shared functions or utilities.
+- If you copy-paste code, stop and refactor it into a single reusable piece.
+- Constants and config values belong in one place, not scattered across files.
+
+## SOLID
+
+- **Single Responsibility**: Each module, class, or function does one thing. If a function is growing large, split it.
+- **Open/Closed**: Extend behavior through new code (new functions, subclasses, config) rather than modifying existing working code.
+- **Liskov Substitution**: Subtypes must be usable wherever their parent type is expected without breaking behavior.
+- **Interface Segregation**: Don't force callers to depend on methods they don't use. Keep interfaces small and focused.
+- **Dependency Inversion**: Depend on abstractions, not concrete implementations. Pass dependencies in rather than hard-coding them.
diff --git a/.cursor/rules/verify-before-done.mdc b/.cursor/rules/verify-before-done.mdc
new file mode 100644
index 0000000..81bed40
--- /dev/null
+++ b/.cursor/rules/verify-before-done.mdc
@@ -0,0 +1,23 @@
+---
+description: Require CI-equivalent checks to pass before marking work as completed
+alwaysApply: true
+---
+
+# Verify Before Done
+
+Before declaring any task complete, run the relevant CI checks locally and confirm they pass.
+
+## CI checks (from `.github/workflows/ci.yml`)
+
+| Check | Command | When to run |
+|-------|---------|-------------|
+| Python lint | `ruff check api/` | After editing any `api/**/*.py` file |
+| Backend tests | `cd api && pytest` | After editing any `api/**/*.py` file |
+| TypeScript type check | `cd app && npx tsc --noEmit` | After editing any `app/**/*.{ts,tsx}` file |
+| Frontend tests | `cd app && npm run test` | After editing any `app/**/*.{ts,tsx}` file |
+
+## Rules
+
+- Run **all** checks that apply to the files you changed — not just one.
+- If a check fails, fix the issue before marking the task complete.
+- Do not skip checks to save time. A failing CI pipeline is worse than a slow response.
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 8c53981..db518a4 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -48,6 +48,10 @@ jobs:
run: |
pip install -r api/requirements.txt
pip install -r api/requirements-dev.txt
+ - name: Audit Python dependencies
+ run: pip install pip-audit && pip-audit -r api/requirements.txt
+ - name: Audit Python dependencies
+ run: pip install pip-audit && pip-audit -r api/requirements.txt
- name: Run tests with coverage
run: cd api && pytest --cov=. --cov-report=xml --cov-report=term-missing
env:
@@ -72,6 +76,8 @@ jobs:
cache-dependency-path: app/package-lock.json
- name: Install dependencies
run: cd app && npm ci
+ - name: Audit npm dependencies
+ run: cd app && npm audit --omit=dev
- name: Run tests
run: cd app && npm run test
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..3580924
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,17 @@
+# Security Policy
+
+## Reporting a Vulnerability
+
+If you discover a security vulnerability in PrintQue, **please do not open a public GitHub issue.**
+
+Instead, report it privately through our Discord server so we can address it before it becomes public:
+
+**Discord:** https://discord.gg/sm3FyFht
+
+When reporting, please include:
+
+- A description of the vulnerability
+- Steps to reproduce the issue
+- The potential impact
+
+We will acknowledge your report within 48 hours and work with you on a fix.
diff --git a/api/app.py b/api/app.py
index 8e43e3b..e216160 100644
--- a/api/app.py
+++ b/api/app.py
@@ -79,8 +79,7 @@
allowed_origins = [
"http://localhost:3000", "http://127.0.0.1:3000",
"http://localhost:5173", "http://127.0.0.1:5173",
- "http://localhost:5000", "http://127.0.0.1:5000", # Same-origin for packaged builds
- "*" # Allow all for packaged single-executable builds
+ "http://localhost:5000", "http://127.0.0.1:5000",
]
CORS(app, resources={
@@ -94,7 +93,16 @@
}
})
-socketio = SocketIO(app, async_mode='eventlet', cors_allowed_origins="*")
+socketio = SocketIO(app, async_mode='eventlet', cors_allowed_origins=allowed_origins)
+
+
+@app.after_request
+def set_security_headers(response):
+ """Add security headers to every response."""
+ response.headers['X-Content-Type-Options'] = 'nosniff'
+ response.headers['X-Frame-Options'] = 'DENY'
+ response.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'
+ return response
# Initialize state
initialize_state()
diff --git a/api/requirements.txt b/api/requirements.txt
index 07fcf48..c37a447 100644
--- a/api/requirements.txt
+++ b/api/requirements.txt
@@ -25,7 +25,6 @@ paho-mqtt>=1.6.1
# System utilities
psutil>=5.9.5
-python-dotenv>=1.0.0
# Security
cryptography>=40.0.1
diff --git a/api/routes/__init__.py b/api/routes/__init__.py
index 181ab47..ec1b46a 100644
--- a/api/routes/__init__.py
+++ b/api/routes/__init__.py
@@ -8,7 +8,6 @@
from .printers import register_printer_routes
from .orders import register_order_routes
from .system import register_misc_routes
-from .support import register_support_routes
from .history import register_history_routes
from .ejection_codes import register_ejection_codes_routes
from services.state import (
@@ -28,7 +27,6 @@
'register_printer_routes',
'register_order_routes',
'register_misc_routes',
- 'register_support_routes',
'register_history_routes',
'register_ejection_codes_routes',
]
@@ -37,7 +35,6 @@ def register_routes(app, socketio):
register_printer_routes(app, socketio)
register_order_routes(app, socketio)
register_misc_routes(app, socketio)
- register_support_routes(app, socketio)
register_history_routes(app, socketio)
register_ejection_codes_routes(app, socketio)
diff --git a/api/routes/support.py b/api/routes/support.py
deleted file mode 100644
index d7bbefa..0000000
--- a/api/routes/support.py
+++ /dev/null
@@ -1,210 +0,0 @@
-from flask import Blueprint, render_template, request, flash, redirect, url_for, send_file
-import smtplib
-import ssl
-import os
-import io
-from email.mime.text import MIMEText
-from email.mime.multipart import MIMEMultipart
-from datetime import datetime
-import logging
-from services.state import PRINTERS, printers_rwlock, ReadLock
-
-# Create a Blueprint for support-related routes
-support_bp = Blueprint('support', __name__, url_prefix='/support')
-
-def send_support_email(name, email, subject, message, license_info, system_info):
- """Send support email using Gmail SMTP"""
- try:
- # Email configuration using environment variables for security
- SMTP_SERVER = "smtp.gmail.com"
- SMTP_PORT = 587
- SENDER_EMAIL = os.environ.get('SMTP_EMAIL')
- SENDER_PASSWORD = os.environ.get('SMTP_PASSWORD')
- RECIPIENT_EMAIL = "info@hartleyprinting.com"
-
- # Check if credentials are configured
- if not SENDER_EMAIL or not SENDER_PASSWORD:
- logging.error("SMTP credentials not configured. Set SMTP_EMAIL and SMTP_PASSWORD environment variables.")
- return False, "Email configuration missing. Please contact administrator."
-
- # Create message
- msg = MIMEMultipart("alternative")
- msg["Subject"] = f"PrintQue Support: {subject}"
- msg["From"] = SENDER_EMAIL
- msg["To"] = RECIPIENT_EMAIL
- msg["Reply-To"] = email
-
- # Create email body
- email_body = f"""
-PrintQue Support Request
-========================
-
-Contact Information:
-- Name: {name}
-- Email: {email}
-- Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
-
-License Information:
-- Tier: {license_info.get('tier', 'Unknown')}
-- Valid: {license_info.get('valid', 'Unknown')}
-- Max Printers: {license_info.get('max_printers', 'Unknown')}
-- Features: {', '.join(license_info.get('features', []))}
-
-System Information:
-- Current Printers: {system_info['printer_count']}
-- Application: PrintQue
-
-Subject: {subject}
-
-Message:
-{message}
-
----
-This message was sent through the PrintQue support system.
-Reply directly to this email to respond to the user.
- """
-
- # Create HTML version
- html_body = f"""
-
-
- PrintQue Support Request
-
-
-
Contact Information
-
Name: {name}
-
Email: {email}
-
Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
-
-
-
-
License Information
-
Tier: {license_info.get('tier', 'Unknown')}
-
Valid: {license_info.get('valid', 'Unknown')}
-
Max Printers: {license_info.get('max_printers', 'Unknown')}
-
Features: {', '.join(license_info.get('features', []))}
-
-
-
-
System Information
-
Current Printers: {system_info['printer_count']}
-
Application: PrintQue
-
-
-
-
Subject
-
{subject}
-
-
Message
-
- {message.replace(chr(10), '
')}
-
-
-
-
-
- This message was sent through the PrintQue support system.
- Reply directly to this email to respond to the user.
-
-
-
- """
-
- # Attach both versions
- text_part = MIMEText(email_body, "plain")
- html_part = MIMEText(html_body, "html")
-
- msg.attach(text_part)
- msg.attach(html_part)
-
- # Send email
- context = ssl.create_default_context()
- with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as server:
- server.starttls(context=context)
- server.login(SENDER_EMAIL, SENDER_PASSWORD)
- server.sendmail(SENDER_EMAIL, RECIPIENT_EMAIL, msg.as_string())
-
- return True, "Email sent successfully"
-
- except Exception as e:
- logging.error(f"Failed to send support email: {str(e)}")
- return False, f"Failed to send email: {str(e)}"
-
-@support_bp.route('/', methods=['GET', 'POST'])
-def support_page():
- """Display the support page and handle form submission"""
-
- # Open Source Edition license info
- license_info = {
- 'tier': 'OPEN_SOURCE',
- 'valid': True,
- 'max_printers': -1,
- 'features': ['all']
- }
-
- if request.method == 'POST':
- # Get form data
- name = request.form.get('name', '').strip()
- email = request.form.get('email', '').strip()
- subject = request.form.get('subject', '').strip()
- message = request.form.get('message', '').strip()
-
- # Basic validation
- if not all([name, email, subject, message]):
- flash('Please fill in all fields.', 'error')
- return render_template('support.html')
-
- # Get current printer count
- with ReadLock(printers_rwlock):
- current_printer_count = len(PRINTERS)
-
- system_info = {
- 'printer_count': current_printer_count
- }
-
- # Send email
- success, error_msg = send_support_email(name, email, subject, message, license_info, system_info)
-
- if success:
- flash('Your support request has been sent successfully. We will respond to your email address shortly.', 'success')
- return redirect(url_for('support.support_page'))
- else:
- flash(f'Failed to send support request: {error_msg}', 'error')
-
- return render_template('support.html', license=license_info)
-
-@support_bp.route('/download-logs')
-def download_logs():
- """Download logs from the last 5 minutes"""
- try:
- # Import the logger module to access the function
- from utils.logger import get_recent_logs
-
- # Get logs from the last 5 minutes
- logs_content = get_recent_logs(minutes=5)
-
- # Create a BytesIO object to serve as a file
- log_buffer = io.BytesIO()
- log_buffer.write(logs_content.encode('utf-8'))
- log_buffer.seek(0)
-
- # Generate filename with timestamp
- timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
- filename = f'printque_logs_{timestamp}.txt'
-
- # Send the file
- return send_file(
- log_buffer,
- mimetype='text/plain',
- as_attachment=True,
- download_name=filename
- )
-
- except Exception as e:
- logging.error(f"Error downloading logs: {str(e)}")
- flash('Failed to download logs. Please try again.', 'error')
- return redirect(url_for('support.support_page'))
-
-def register_support_routes(app, socketio):
- """Register support routes with the Flask app"""
- app.register_blueprint(support_bp)
diff --git a/api/utils/config.py b/api/utils/config.py
index 97266b0..867aedc 100644
--- a/api/utils/config.py
+++ b/api/utils/config.py
@@ -1,28 +1,53 @@
import os
-from dotenv import load_dotenv
+import hashlib
+import base64
+import secrets
from cryptography.fernet import Fernet
# Import version from single source of truth
from __version__ import __version__
-load_dotenv()
+
+def _get_data_dir() -> str:
+ """Return the PrintQue data directory, creating it if needed."""
+ base = os.getenv('DATA_DIR', os.path.expanduser("~"))
+ data_dir = os.path.join(base, "PrintQueData")
+ os.makedirs(data_dir, exist_ok=True)
+ return data_dir
+
+
+def _load_or_create_secret_key() -> str:
+ """Load a persistent secret key from disk, or generate one on first run."""
+ key_path = os.path.join(_get_data_dir(), "secret.key")
+ if os.path.exists(key_path):
+ try:
+ with open(key_path, "r", encoding="utf-8") as f:
+ key = f.read().strip()
+ if key:
+ return key
+ except Exception:
+ pass # Fall through to regeneration
+
+ # Generate a new cryptographically-secure key and persist it
+ key = secrets.token_urlsafe(48)
+ try:
+ with open(key_path, "w", encoding="utf-8") as f:
+ f.write(key)
+ except Exception as e:
+ print(f"WARNING: Could not persist secret key to {key_path}: {e}")
+ return key
+
class Config:
# Application version (imported from __version__.py)
APP_VERSION = __version__
- SECRET_KEY = os.getenv('SECRET_KEY', 'default-secret-for-dev-only')
+ # Persistent secret key — auto-generated on first run, stored in DATA_DIR
+ SECRET_KEY = _load_or_create_secret_key()
- # CRITICAL FIX: Encryption key with fallback
- ENCRYPTION_KEY = os.getenv('ENCRYPTION_KEY')
- if not ENCRYPTION_KEY:
- # Generate a consistent fallback key based on SECRET_KEY
- import hashlib
- import base64
- # Create a consistent 32-byte key from SECRET_KEY
- key_material = hashlib.sha256(SECRET_KEY.encode()).digest()
- ENCRYPTION_KEY = base64.urlsafe_b64encode(key_material).decode()
- print("WARNING: Using generated encryption key. Set ENCRYPTION_KEY in environment for production.")
+ # Encryption key derived from SECRET_KEY (deterministic so existing data stays valid)
+ _key_material = hashlib.sha256(SECRET_KEY.encode()).digest()
+ ENCRYPTION_KEY = base64.urlsafe_b64encode(_key_material).decode()
# Validate encryption key format
try:
@@ -56,7 +81,6 @@ class Config:
DEFAULT_END_GCODE = "" # Default end G-code to pre-fill in UI
PORT = int(os.getenv('PORT', 5000)) # Added PORT with default 5000
- ADMIN_KEY = os.getenv('ADMIN_KEY', 'default-admin-key-change-me')
# BATCH PROCESSING: Optimized for better performance
STATUS_BATCH_SIZE = 5 # Reduced from 10 to 5 for faster processing
diff --git a/app/package-lock.json b/app/package-lock.json
index 3007954..00555ef 100644
--- a/app/package-lock.json
+++ b/app/package-lock.json
@@ -19,11 +19,8 @@
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-switch": "^1.2.6",
"@radix-ui/react-tabs": "^1.1.13",
- "@tanstack/react-devtools": "^0.7.0",
"@tanstack/react-query": "^5.90.18",
- "@tanstack/react-query-devtools": "^5.91.2",
"@tanstack/react-router": "^1.132.0",
- "@tanstack/react-router-devtools": "^1.132.0",
"@tanstack/react-router-ssr-query": "^1.131.7",
"@tanstack/react-start": "^1.132.0",
"@tanstack/react-table": "^8.21.3",
@@ -46,6 +43,9 @@
"@biomejs/biome": "^2.3.12",
"@tailwindcss/vite": "^4.1.18",
"@tanstack/devtools-vite": "^0.3.11",
+ "@tanstack/react-devtools": "^0.7.0",
+ "@tanstack/react-query-devtools": "^5.91.2",
+ "@tanstack/react-router-devtools": "^1.132.0",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.2.0",
@@ -3371,6 +3371,7 @@
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/@solid-primitives/event-listener/-/event-listener-2.4.3.tgz",
"integrity": "sha512-h4VqkYFv6Gf+L7SQj+Y6puigL/5DIi7x5q07VZET7AWcS+9/G3WfIE9WheniHWJs51OEkRB43w6lDys5YeFceg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@solid-primitives/utils": "^6.3.2"
@@ -3383,6 +3384,7 @@
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@solid-primitives/keyboard/-/keyboard-1.3.3.tgz",
"integrity": "sha512-9dQHTTgLBqyAI7aavtO+HnpTVJgWQA1ghBSrmLtMu1SMxLPDuLfuNr+Tk5udb4AL4Ojg7h9JrKOGEEDqsJXWJA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@solid-primitives/event-listener": "^2.4.3",
@@ -3397,6 +3399,7 @@
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@solid-primitives/resize-observer/-/resize-observer-2.1.3.tgz",
"integrity": "sha512-zBLje5E06TgOg93S7rGPldmhDnouNGhvfZVKOp+oG2XU8snA+GoCSSCz1M+jpNAg5Ek2EakU5UVQqL152WmdXQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@solid-primitives/event-listener": "^2.4.3",
@@ -3412,6 +3415,7 @@
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/@solid-primitives/rootless/-/rootless-1.5.2.tgz",
"integrity": "sha512-9HULb0QAzL2r47CCad0M+NKFtQ+LrGGNHZfteX/ThdGvKIg2o2GYhBooZubTCd/RTu2l2+Nw4s+dEfiDGvdrrQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@solid-primitives/utils": "^6.3.2"
@@ -3424,6 +3428,7 @@
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/@solid-primitives/static-store/-/static-store-0.1.2.tgz",
"integrity": "sha512-ReK+5O38lJ7fT+L6mUFvUr6igFwHBESZF+2Ug842s7fvlVeBdIVEdTCErygff6w7uR6+jrr7J8jQo+cYrEq4Iw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@solid-primitives/utils": "^6.3.2"
@@ -3436,6 +3441,7 @@
"version": "6.3.2",
"resolved": "https://registry.npmjs.org/@solid-primitives/utils/-/utils-6.3.2.tgz",
"integrity": "sha512-hZ/M/qr25QOCcwDPOHtGjxTD8w2mNyVAYvcfgwzBHq2RwNqHNdDNsMZYap20+ruRwW4A3Cdkczyoz0TSxLCAPQ==",
+ "dev": true,
"license": "MIT",
"peerDependencies": {
"solid-js": "^1.6.12"
@@ -3723,6 +3729,7 @@
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/@tanstack/devtools/-/devtools-0.7.0.tgz",
"integrity": "sha512-AlAoCqJhWLg9GBEaoV1g/j+X/WA1aJSWOsekxeuZpYeS2hdVuKAjj04KQLUMJhtLfNl2s2E+TCj7ZRtWyY3U4w==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@solid-primitives/event-listener": "^2.4.3",
@@ -3767,6 +3774,7 @@
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@tanstack/devtools-event-bus/-/devtools-event-bus-0.3.3.tgz",
"integrity": "sha512-lWl88uLAz7ZhwNdLH6A3tBOSEuBCrvnY9Fzr5JPdzJRFdM5ZFdyNWz1Bf5l/F3GU57VodrN0KCFi9OA26H5Kpg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"ws": "^8.18.3"
@@ -3797,6 +3805,7 @@
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/@tanstack/devtools-ui/-/devtools-ui-0.4.4.tgz",
"integrity": "sha512-5xHXFyX3nom0UaNfiOM92o6ziaHjGo3mcSGe2HD5Xs8dWRZNpdZ0Smd0B9ddEhy0oB+gXyMzZgUJb9DmrZV0Mg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"clsx": "^2.1.1",
@@ -3847,6 +3856,7 @@
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/@tanstack/devtools-client/-/devtools-client-0.0.3.tgz",
"integrity": "sha512-kl0r6N5iIL3t9gGDRAv55VRM3UIyMKVH83esRGq7xBjYsRLe/BeCIN2HqrlJkObUXQMKhy7i8ejuGOn+bDqDBw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@tanstack/devtools-event-client": "^0.3.3"
@@ -3863,6 +3873,7 @@
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@tanstack/devtools-event-client/-/devtools-event-client-0.3.5.tgz",
"integrity": "sha512-RL1f5ZlfZMpghrCIdzl6mLOFLTuhqmPNblZgBaeKfdtk5rfbjykurv+VfYydOFXj0vxVIoA2d/zT7xfD7Ph8fw==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
@@ -3899,6 +3910,7 @@
"version": "5.92.0",
"resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.92.0.tgz",
"integrity": "sha512-N8D27KH1vEpVacvZgJL27xC6yPFUy0Zkezn5gnB3L3gRCxlDeSuiya7fKge8Y91uMTnC8aSxBQhcK6ocY7alpQ==",
+ "dev": true,
"license": "MIT",
"funding": {
"type": "github",
@@ -3909,6 +3921,7 @@
"version": "0.7.11",
"resolved": "https://registry.npmjs.org/@tanstack/react-devtools/-/react-devtools-0.7.11.tgz",
"integrity": "sha512-a2Lmz8x+JoDrsU6f7uKRcyyY+k8mA/n5mb9h7XJ3Fz/y3+sPV9t7vAW1s5lyNkQyyDt6V1Oim99faLthoJSxMw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@tanstack/devtools": "0.7.0"
@@ -3947,6 +3960,7 @@
"version": "5.91.2",
"resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.91.2.tgz",
"integrity": "sha512-ZJ1503ay5fFeEYFUdo7LMNFzZryi6B0Cacrgr2h1JRkvikK1khgIq6Nq2EcblqEdIlgB/r7XDW8f8DQ89RuUgg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@tanstack/query-devtools": "5.92.0"
@@ -3989,6 +4003,7 @@
"version": "1.150.0",
"resolved": "https://registry.npmjs.org/@tanstack/react-router-devtools/-/react-router-devtools-1.150.0.tgz",
"integrity": "sha512-TlvTE+XK5XVCfYjazoMWkjyyPKe4kMw2nCA7EuWoYUJKOqRW5oKvBY7auViGWxp51FKDEjV3bbok3wPKBYwZww==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@tanstack/router-devtools-core": "1.150.0"
@@ -4175,6 +4190,7 @@
"version": "1.150.0",
"resolved": "https://registry.npmjs.org/@tanstack/router-devtools-core/-/router-devtools-core-1.150.0.tgz",
"integrity": "sha512-61V+4fq2fOPru/48cuojKvWhQx2h/nuj4nVHwzu9E7O8h391h4Hks6axxRbY98/rIz96mn5TCoc0aYuoga53bg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"clsx": "^2.1.1",
@@ -4689,6 +4705,7 @@
"version": "19.2.8",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.8.tgz",
"integrity": "sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg==",
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.2.2"
@@ -4698,6 +4715,7 @@
"version": "19.2.3",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
+ "devOptional": true,
"license": "MIT",
"peerDependencies": {
"@types/react": "^19.2.0"
@@ -5528,6 +5546,7 @@
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "devOptional": true,
"license": "MIT"
},
"node_modules/data-urls": {
@@ -6073,6 +6092,7 @@
"version": "2.1.18",
"resolved": "https://registry.npmjs.org/goober/-/goober-2.1.18.tgz",
"integrity": "sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw==",
+ "dev": true,
"license": "MIT",
"peerDependencies": {
"csstype": "^3.0.10"
@@ -8149,6 +8169,7 @@
"version": "1.9.10",
"resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.10.tgz",
"integrity": "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.1.0",
@@ -8160,6 +8181,7 @@
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/seroval/-/seroval-1.3.2.tgz",
"integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
@@ -8169,6 +8191,7 @@
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.3.3.tgz",
"integrity": "sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
@@ -9183,6 +9206,7 @@
"version": "8.19.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=10.0.0"
diff --git a/app/package.json b/app/package.json
index 29c41d5..a57bc53 100644
--- a/app/package.json
+++ b/app/package.json
@@ -32,11 +32,8 @@
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-switch": "^1.2.6",
"@radix-ui/react-tabs": "^1.1.13",
- "@tanstack/react-devtools": "^0.7.0",
"@tanstack/react-query": "^5.90.18",
- "@tanstack/react-query-devtools": "^5.91.2",
"@tanstack/react-router": "^1.132.0",
- "@tanstack/react-router-devtools": "^1.132.0",
"@tanstack/react-router-ssr-query": "^1.131.7",
"@tanstack/react-start": "^1.132.0",
"@tanstack/react-table": "^8.21.3",
@@ -58,6 +55,9 @@
"devDependencies": {
"@biomejs/biome": "^2.3.12",
"@tailwindcss/vite": "^4.1.18",
+ "@tanstack/react-devtools": "^0.7.0",
+ "@tanstack/react-query-devtools": "^5.91.2",
+ "@tanstack/react-router-devtools": "^1.132.0",
"@tanstack/devtools-vite": "^0.3.11",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
diff --git a/build.py b/build.py
index a96ac7d..1d19afd 100644
--- a/build.py
+++ b/build.py
@@ -319,7 +319,7 @@ def create_pyinstaller_spec():
'eventlet', 'eventlet.green', 'eventlet.hubs',
'dns', 'dns.resolver', 'cryptography',
'aiohttp', 'aiofiles', 'requests', 'urllib3', 'certifi',
- 'psutil', 'dotenv', 'simple_websocket', 'bidict', 'greenlet',
+ 'psutil', 'simple_websocket', 'bidict', 'greenlet',
'paho', 'paho.mqtt', 'paho.mqtt.client',
]
diff --git a/build_windows.py b/build_windows.py
index 55bf005..1aef6d5 100644
--- a/build_windows.py
+++ b/build_windows.py
@@ -104,7 +104,6 @@ def build_exe():
'aiohttp.connector',
'requests',
'psutil',
- 'dotenv',
'werkzeug',
'werkzeug.serving',
'jinja2',
@@ -235,27 +234,6 @@ def create_distribution():
f.write(batch_content)
print("Created Start_PrintQue.bat")
- # Create configuration template
- config_content = """# PrintQue Open Source Edition - Configuration Template
-# Copy this file to .env and modify as needed
-
-# Server Configuration
-PORT=5000
-HOST=0.0.0.0
-
-# Security Keys (CHANGE THESE for production!)
-SECRET_KEY=change-this-secret-key-to-something-random
-ENCRYPTION_KEY=change-this-encryption-key-to-something-random
-ADMIN_KEY=change-this-admin-key-to-something-random
-
-# Bambu Printer Settings
-BAMBU_CA_CERT=certs/bambu_ca.pem
-"""
-
- with open(os.path.join(dist_dir, 'config_template.env'), 'w') as f:
- f.write(config_content)
- print("Created config_template.env")
-
# Create README
readme_content = """PrintQue - Open Source Print Farm Manager
==========================================
@@ -270,23 +248,13 @@ def create_distribution():
- License: GPL v3
- GitHub: https://github.com/PrintQue/PrintQue
-Configuration:
-- Copy 'config_template.env' to '.env' and modify the settings
-- Default port is 5000 (change in .env if needed)
-
Data Storage:
-- Printer data: data/printers.json
-- Orders: data/orders.json
-- Uploaded files: uploads/
-- Certificates: certs/
-
-First Run:
-- The application will create all necessary data files automatically
-- A default encryption key will be generated (WARNING shown in console)
-- For production use, set your own ENCRYPTION_KEY in .env file
+- All data is stored in ~/PrintQueData/
+- Security keys are auto-generated on first run
+- No manual configuration is needed
Troubleshooting:
-- If port 5000 is in use, change PORT in .env file
+- If port 5000 is in use, the server will automatically try the next available port
- Check console window for error messages
- Report issues: https://github.com/PrintQue/PrintQue/issues
- Ensure firewall allows the application
diff --git a/complete_build.py b/complete_build.py
index 8a4e66c..fa3075e 100644
--- a/complete_build.py
+++ b/complete_build.py
@@ -53,7 +53,6 @@ def create_spec_file():
'urllib3',
'certifi',
'psutil',
- 'dotenv',
'simple_websocket',
'bidict',
'greenlet'
@@ -176,7 +175,7 @@ def build_exe():
packages = [
'flask', 'flask-socketio', 'eventlet', 'python-socketio',
'python-engineio', 'werkzeug', 'jinja2', 'cryptography',
- 'aiohttp', 'requests', 'psutil', 'python-dotenv',
+ 'aiohttp', 'requests', 'psutil',
'simple-websocket', 'dnspython', 'paho-mqtt'
]