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' ]