Fix autosync toggle disable functionality
- Fix service instance management to use global instance for stopping autosync - Add automatic saving when toggle is changed (no manual save required) - Fix validation issue where custom sync type without cron expression caused 400 error - Add comprehensive debugging and error handling - Ensure .env file is properly updated with AUTO_SYNC_ENABLED value - Improve service lifecycle management with proper state cleanup - Add fallback logic for invalid sync interval configurations Resolves issue where disabling autosync in GUI didn't update .env file or stop service
This commit is contained in:
@@ -27,3 +27,12 @@ AUTH_ENABLED=false
|
|||||||
AUTH_SETUP_COMPLETED=false
|
AUTH_SETUP_COMPLETED=false
|
||||||
JWT_SECRET=
|
JWT_SECRET=
|
||||||
DATABASE_URL="file:/opt/ProxmoxVE-Local/data/settings.db"
|
DATABASE_URL="file:/opt/ProxmoxVE-Local/data/settings.db"
|
||||||
|
AUTO_SYNC_ENABLED=false
|
||||||
|
SYNC_INTERVAL_TYPE=
|
||||||
|
SYNC_INTERVAL_PREDEFINED=
|
||||||
|
AUTO_DOWNLOAD_NEW=
|
||||||
|
AUTO_UPDATE_EXISTING=
|
||||||
|
NOTIFICATION_ENABLED=
|
||||||
|
APPRISE_URLS=
|
||||||
|
LAST_AUTO_SYNC=
|
||||||
|
SYNC_INTERVAL_CRON=
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
"documentation": "https://docs.bunkerweb.io/latest/",
|
"documentation": "https://docs.bunkerweb.io/latest/",
|
||||||
"website": "https://www.bunkerweb.io/",
|
"website": "https://www.bunkerweb.io/",
|
||||||
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons/webp/bunkerweb.webp",
|
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons/webp/bunkerweb.webp",
|
||||||
"config_path": "/opt/bunkerweb/variables.env",
|
"config_path": "/etc/bunkerweb/variables.env",
|
||||||
"description": "BunkerWeb is a security-focused web server that enhances web application protection. It guards against common web vulnerabilities like SQL injection, XSS, and CSRF. It features simple setup and configuration using a YAML file, customizable security rules, and provides detailed logs for traffic monitoring and threat detection.",
|
"description": "BunkerWeb is a security-focused web server that enhances web application protection. It guards against common web vulnerabilities like SQL injection, XSS, and CSRF. It features simple setup and configuration using a YAML file, customizable security rules, and provides detailed logs for traffic monitoring and threat detection.",
|
||||||
"install_methods": [
|
"install_methods": [
|
||||||
{
|
{
|
||||||
|
|||||||
48
scripts/json/execute.json
Normal file
48
scripts/json/execute.json
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"name": "PVE LXC Execute Command",
|
||||||
|
"slug": "lxc-execute",
|
||||||
|
"categories": [
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"date_created": "2025-09-18",
|
||||||
|
"type": "pve",
|
||||||
|
"updateable": false,
|
||||||
|
"privileged": false,
|
||||||
|
"interface_port": null,
|
||||||
|
"documentation": null,
|
||||||
|
"website": null,
|
||||||
|
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons/webp/proxmox.webp",
|
||||||
|
"config_path": "",
|
||||||
|
"description": "This script allows administrators to execute a custom command inside one or multiple LXC containers on a Proxmox VE node. Containers can be selectively excluded via an interactive checklist. If a container is stopped, the script will automatically start it, run the command, and then shut it down again. Only Debian and Ubuntu based containers are supported.",
|
||||||
|
"install_methods": [
|
||||||
|
{
|
||||||
|
"type": "default",
|
||||||
|
"script": "tools/pve/execute.sh",
|
||||||
|
"resources": {
|
||||||
|
"cpu": null,
|
||||||
|
"ram": null,
|
||||||
|
"hdd": null,
|
||||||
|
"os": null,
|
||||||
|
"version": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default_credentials": {
|
||||||
|
"username": null,
|
||||||
|
"password": null
|
||||||
|
},
|
||||||
|
"notes": [
|
||||||
|
{
|
||||||
|
"text": "Execute within the Proxmox shell.",
|
||||||
|
"type": "info"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Non-Debian/Ubuntu containers will be skipped automatically.",
|
||||||
|
"type": "info"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Stopped containers will be started temporarily to run the command, then shut down again.",
|
||||||
|
"type": "warning"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
"ram": 2048,
|
"ram": 2048,
|
||||||
"hdd": 10,
|
"hdd": 10,
|
||||||
"os": "debian",
|
"os": "debian",
|
||||||
"version": "12"
|
"version": "13"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"documentation": "https://github.com/HydroshieldMKII/Guardian/blob/main/README.md",
|
"documentation": "https://github.com/HydroshieldMKII/Guardian/blob/main/README.md",
|
||||||
"config_path": "/opt/guardian/.env",
|
"config_path": "/opt/guardian/.env",
|
||||||
"website": "https://github.com/HydroshieldMKII/Guardian",
|
"website": "https://github.com/HydroshieldMKII/Guardian",
|
||||||
"logo": null,
|
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons/webp/guardian-plex.webp",
|
||||||
"description": "Guardian is a lightweight companion app for Plex that lets you monitor, approve or block devices in real time. It helps you enforce per-user or global policies, stop unwanted sessions automatically and grant temporary access - all through a simple web interface.",
|
"description": "Guardian is a lightweight companion app for Plex that lets you monitor, approve or block devices in real time. It helps you enforce per-user or global policies, stop unwanted sessions automatically and grant temporary access - all through a simple web interface.",
|
||||||
"install_methods": [
|
"install_methods": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
"resources": {
|
"resources": {
|
||||||
"cpu": 2,
|
"cpu": 2,
|
||||||
"ram": 2048,
|
"ram": 2048,
|
||||||
"hdd": 8,
|
"hdd": 16,
|
||||||
"os": "ubuntu",
|
"os": "ubuntu",
|
||||||
"version": "24.04"
|
"version": "24.04"
|
||||||
}
|
}
|
||||||
|
|||||||
40
scripts/json/jotty.json
Normal file
40
scripts/json/jotty.json
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"name": "jotty",
|
||||||
|
"slug": "jotty",
|
||||||
|
"categories": [
|
||||||
|
12
|
||||||
|
],
|
||||||
|
"date_created": "2025-10-21",
|
||||||
|
"type": "ct",
|
||||||
|
"updateable": true,
|
||||||
|
"privileged": false,
|
||||||
|
"interface_port": 3000,
|
||||||
|
"documentation": "https://github.com/fccview/jotty/blob/main/README.md",
|
||||||
|
"website": "https://github.com/fccview/jotty",
|
||||||
|
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons/webp/jotty.webp",
|
||||||
|
"config_path": "/opt/jotty/.env",
|
||||||
|
"description": "A simple, self-hosted app for your checklists and notes. Tired of bloated, cloud-based to-do apps? jotty is a lightweight alternative for managing your personal checklists and notes. It's built with Next.js 14, is easy to deploy, and keeps all your data on your own server.",
|
||||||
|
"install_methods": [
|
||||||
|
{
|
||||||
|
"type": "default",
|
||||||
|
"script": "ct/jotty.sh",
|
||||||
|
"resources": {
|
||||||
|
"cpu": 2,
|
||||||
|
"ram": 3072,
|
||||||
|
"hdd": 6,
|
||||||
|
"os": "debian",
|
||||||
|
"version": "13"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default_credentials": {
|
||||||
|
"username": null,
|
||||||
|
"password": null
|
||||||
|
},
|
||||||
|
"notes": [
|
||||||
|
{
|
||||||
|
"text": "jotty was previously named rwMarkable",
|
||||||
|
"type": "info"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
"ram": 2048,
|
"ram": 2048,
|
||||||
"hdd": 8,
|
"hdd": 8,
|
||||||
"os": "debian",
|
"os": "debian",
|
||||||
"version": "12"
|
"version": "13"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,186 +1,186 @@
|
|||||||
{
|
{
|
||||||
"categories": [
|
"categories": [
|
||||||
{
|
{
|
||||||
"name": "Proxmox & Virtualization",
|
"name": "Proxmox & Virtualization",
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"sort_order": 1.0,
|
"sort_order": 1.0,
|
||||||
"description": "Tools and scripts to manage Proxmox VE and virtualization platforms effectively.",
|
"description": "Tools and scripts to manage Proxmox VE and virtualization platforms effectively.",
|
||||||
"icon": "server"
|
"icon": "server"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Operating Systems",
|
"name": "Operating Systems",
|
||||||
"id": 2,
|
"id": 2,
|
||||||
"sort_order": 2.0,
|
"sort_order": 2.0,
|
||||||
"description": "Scripts for deploying and managing various operating systems.",
|
"description": "Scripts for deploying and managing various operating systems.",
|
||||||
"icon": "monitor"
|
"icon": "monitor"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Containers & Docker",
|
"name": "Containers & Docker",
|
||||||
"id": 3,
|
"id": 3,
|
||||||
"sort_order": 3.0,
|
"sort_order": 3.0,
|
||||||
"description": "Solutions for containerization using Docker and related technologies.",
|
"description": "Solutions for containerization using Docker and related technologies.",
|
||||||
"icon": "box"
|
"icon": "box"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Network & Firewall",
|
"name": "Network & Firewall",
|
||||||
"id": 4,
|
"id": 4,
|
||||||
"sort_order": 4.0,
|
"sort_order": 4.0,
|
||||||
"description": "Enhance network security and configure firewalls with ease.",
|
"description": "Enhance network security and configure firewalls with ease.",
|
||||||
"icon": "shield"
|
"icon": "shield"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Adblock & DNS",
|
"name": "Adblock & DNS",
|
||||||
"id": 5,
|
"id": 5,
|
||||||
"sort_order": 5.0,
|
"sort_order": 5.0,
|
||||||
"description": "Optimize your network with DNS and ad-blocking solutions.",
|
"description": "Optimize your network with DNS and ad-blocking solutions.",
|
||||||
"icon": "ban"
|
"icon": "ban"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Authentication & Security",
|
"name": "Authentication & Security",
|
||||||
"id": 6,
|
"id": 6,
|
||||||
"sort_order": 6.0,
|
"sort_order": 6.0,
|
||||||
"description": "Secure your infrastructure with authentication and security tools.",
|
"description": "Secure your infrastructure with authentication and security tools.",
|
||||||
"icon": "lock"
|
"icon": "lock"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Backup & Recovery",
|
"name": "Backup & Recovery",
|
||||||
"id": 7,
|
"id": 7,
|
||||||
"sort_order": 7.0,
|
"sort_order": 7.0,
|
||||||
"description": "Reliable backup and recovery scripts to protect your data.",
|
"description": "Reliable backup and recovery scripts to protect your data.",
|
||||||
"icon": "archive"
|
"icon": "archive"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Databases",
|
"name": "Databases",
|
||||||
"id": 8,
|
"id": 8,
|
||||||
"sort_order": 8.0,
|
"sort_order": 8.0,
|
||||||
"description": "Deploy and manage robust database systems with ease.",
|
"description": "Deploy and manage robust database systems with ease.",
|
||||||
"icon": "database"
|
"icon": "database"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Monitoring & Analytics",
|
"name": "Monitoring & Analytics",
|
||||||
"id": 9,
|
"id": 9,
|
||||||
"sort_order": 9.0,
|
"sort_order": 9.0,
|
||||||
"description": "Monitor system performance and analyze data seamlessly.",
|
"description": "Monitor system performance and analyze data seamlessly.",
|
||||||
"icon": "bar-chart"
|
"icon": "bar-chart"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Dashboards & Frontends",
|
"name": "Dashboards & Frontends",
|
||||||
"id": 10,
|
"id": 10,
|
||||||
"sort_order": 10.0,
|
"sort_order": 10.0,
|
||||||
"description": "Create interactive dashboards and user-friendly frontends.",
|
"description": "Create interactive dashboards and user-friendly frontends.",
|
||||||
"icon": "layout"
|
"icon": "layout"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Files & Downloads",
|
"name": "Files & Downloads",
|
||||||
"id": 11,
|
"id": 11,
|
||||||
"sort_order": 11.0,
|
"sort_order": 11.0,
|
||||||
"description": "Manage file sharing and downloading solutions efficiently.",
|
"description": "Manage file sharing and downloading solutions efficiently.",
|
||||||
"icon": "download"
|
"icon": "download"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Documents & Notes",
|
"name": "Documents & Notes",
|
||||||
"id": 12,
|
"id": 12,
|
||||||
"sort_order": 12.0,
|
"sort_order": 12.0,
|
||||||
"description": "Organize and manage documents and note-taking tools.",
|
"description": "Organize and manage documents and note-taking tools.",
|
||||||
"icon": "file-text"
|
"icon": "file-text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Media & Streaming",
|
"name": "Media & Streaming",
|
||||||
"id": 13,
|
"id": 13,
|
||||||
"sort_order": 13.0,
|
"sort_order": 13.0,
|
||||||
"description": "Stream and manage media effortlessly across devices.",
|
"description": "Stream and manage media effortlessly across devices.",
|
||||||
"icon": "play"
|
"icon": "play"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "*Arr Suite",
|
"name": "*Arr Suite",
|
||||||
"id": 14,
|
"id": 14,
|
||||||
"sort_order": 14.0,
|
"sort_order": 14.0,
|
||||||
"description": "Automated media management with the popular *Arr suite tools.",
|
"description": "Automated media management with the popular *Arr suite tools.",
|
||||||
"icon": "tv"
|
"icon": "tv"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "NVR & Cameras",
|
"name": "NVR & Cameras",
|
||||||
"id": 15,
|
"id": 15,
|
||||||
"sort_order": 15.0,
|
"sort_order": 15.0,
|
||||||
"description": "Manage network video recorders and camera setups.",
|
"description": "Manage network video recorders and camera setups.",
|
||||||
"icon": "camera"
|
"icon": "camera"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "IoT & Smart Home",
|
"name": "IoT & Smart Home",
|
||||||
"id": 16,
|
"id": 16,
|
||||||
"sort_order": 16.0,
|
"sort_order": 16.0,
|
||||||
"description": "Control and automate IoT devices and smart home systems.",
|
"description": "Control and automate IoT devices and smart home systems.",
|
||||||
"icon": "home"
|
"icon": "home"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "ZigBee, Z-Wave & Matter",
|
"name": "ZigBee, Z-Wave & Matter",
|
||||||
"id": 17,
|
"id": 17,
|
||||||
"sort_order": 17.0,
|
"sort_order": 17.0,
|
||||||
"description": "Solutions for ZigBee, Z-Wave, and Matter-based device management.",
|
"description": "Solutions for ZigBee, Z-Wave, and Matter-based device management.",
|
||||||
"icon": "radio"
|
"icon": "radio"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "MQTT & Messaging",
|
"name": "MQTT & Messaging",
|
||||||
"id": 18,
|
"id": 18,
|
||||||
"sort_order": 18.0,
|
"sort_order": 18.0,
|
||||||
"description": "Set up reliable messaging and MQTT-based communication systems.",
|
"description": "Set up reliable messaging and MQTT-based communication systems.",
|
||||||
"icon": "message-circle"
|
"icon": "message-circle"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Automation & Scheduling",
|
"name": "Automation & Scheduling",
|
||||||
"id": 19,
|
"id": 19,
|
||||||
"sort_order": 19.0,
|
"sort_order": 19.0,
|
||||||
"description": "Automate tasks and manage scheduling with powerful tools.",
|
"description": "Automate tasks and manage scheduling with powerful tools.",
|
||||||
"icon": "clock"
|
"icon": "clock"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "AI / Coding & Dev-Tools",
|
"name": "AI / Coding & Dev-Tools",
|
||||||
"id": 20,
|
"id": 20,
|
||||||
"sort_order": 20.0,
|
"sort_order": 20.0,
|
||||||
"description": "Leverage AI and developer tools for smarter coding workflows.",
|
"description": "Leverage AI and developer tools for smarter coding workflows.",
|
||||||
"icon": "code"
|
"icon": "code"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Webservers & Proxies",
|
"name": "Webservers & Proxies",
|
||||||
"id": 21,
|
"id": 21,
|
||||||
"sort_order": 21.0,
|
"sort_order": 21.0,
|
||||||
"description": "Deploy and configure web servers and proxy solutions.",
|
"description": "Deploy and configure web servers and proxy solutions.",
|
||||||
"icon": "globe"
|
"icon": "globe"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Bots & ChatOps",
|
"name": "Bots & ChatOps",
|
||||||
"id": 22,
|
"id": 22,
|
||||||
"sort_order": 22.0,
|
"sort_order": 22.0,
|
||||||
"description": "Enhance collaboration with bots and ChatOps integrations.",
|
"description": "Enhance collaboration with bots and ChatOps integrations.",
|
||||||
"icon": "bot"
|
"icon": "bot"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Finance & Budgeting",
|
"name": "Finance & Budgeting",
|
||||||
"id": 23,
|
"id": 23,
|
||||||
"sort_order": 23.0,
|
"sort_order": 23.0,
|
||||||
"description": "Track expenses and manage budgets efficiently.",
|
"description": "Track expenses and manage budgets efficiently.",
|
||||||
"icon": "dollar-sign"
|
"icon": "dollar-sign"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Gaming & Leisure",
|
"name": "Gaming & Leisure",
|
||||||
"id": 24,
|
"id": 24,
|
||||||
"sort_order": 24.0,
|
"sort_order": 24.0,
|
||||||
"description": "Scripts for gaming servers and leisure-related tools.",
|
"description": "Scripts for gaming servers and leisure-related tools.",
|
||||||
"icon": "gamepad-2"
|
"icon": "gamepad-2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Business & ERP",
|
"name": "Business & ERP",
|
||||||
"id": 25,
|
"id": 25,
|
||||||
"sort_order": 25.0,
|
"sort_order": 25.0,
|
||||||
"description": "Streamline business operations with ERP and management tools.",
|
"description": "Streamline business operations with ERP and management tools.",
|
||||||
"icon": "building"
|
"icon": "building"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Miscellaneous",
|
"name": "Miscellaneous",
|
||||||
"id": 0,
|
"id": 0,
|
||||||
"sort_order": 99.0,
|
"sort_order": 99.0,
|
||||||
"description": "General scripts and tools that don't fit into other categories.",
|
"description": "General scripts and tools that don't fit into other categories.",
|
||||||
"icon": "more-horizontal"
|
"icon": "more-horizontal"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
"ram": 1024,
|
"ram": 1024,
|
||||||
"hdd": 4,
|
"hdd": 4,
|
||||||
"os": "debian",
|
"os": "debian",
|
||||||
"version": "13"
|
"version": "12"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
"ram": 2048,
|
"ram": 2048,
|
||||||
"hdd": 8,
|
"hdd": 8,
|
||||||
"os": "debian",
|
"os": "debian",
|
||||||
"version": "13"
|
"version": "12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
"ram": 2048,
|
"ram": 2048,
|
||||||
"hdd": 6,
|
"hdd": 6,
|
||||||
"os": "debian",
|
"os": "debian",
|
||||||
"version": "13"
|
"version": "12"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
"ram": 1024,
|
"ram": 1024,
|
||||||
"hdd": 4,
|
"hdd": 4,
|
||||||
"os": "debian",
|
"os": "debian",
|
||||||
"version": "13"
|
"version": "12"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
40
scripts/json/open-archiver.json
Normal file
40
scripts/json/open-archiver.json
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"name": "Open-Archiver",
|
||||||
|
"slug": "open-archiver",
|
||||||
|
"categories": [
|
||||||
|
7
|
||||||
|
],
|
||||||
|
"date_created": "2025-10-18",
|
||||||
|
"type": "ct",
|
||||||
|
"updateable": true,
|
||||||
|
"privileged": false,
|
||||||
|
"interface_port": 3000,
|
||||||
|
"documentation": "https://docs.openarchiver.com/",
|
||||||
|
"config_path": "/opt/openarchiver/.env",
|
||||||
|
"website": "https://openarchiver.com/",
|
||||||
|
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons/webp/open-archiver.webp",
|
||||||
|
"description": "Open Archiver is a secure, self-hosted email archiving solution, and it's completely open source. Get an email archiver that enables full-text search across email and attachments. Create a permanent, searchable, and compliant mail archive from Google Workspace, Microsoft 35, and any IMAP server.",
|
||||||
|
"install_methods": [
|
||||||
|
{
|
||||||
|
"type": "default",
|
||||||
|
"script": "ct/open-archiver.sh",
|
||||||
|
"resources": {
|
||||||
|
"cpu": 2,
|
||||||
|
"ram": 3072,
|
||||||
|
"hdd": 8,
|
||||||
|
"os": "debian",
|
||||||
|
"version": "13"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default_credentials": {
|
||||||
|
"username": null,
|
||||||
|
"password": null
|
||||||
|
},
|
||||||
|
"notes": [
|
||||||
|
{
|
||||||
|
"text": "Data directory is: `/opt/openarchiver-data`. If you have a lot of email, you might consider mounting external storage to this directory.",
|
||||||
|
"type": "info"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
"ram": 8192,
|
"ram": 8192,
|
||||||
"hdd": 25,
|
"hdd": 25,
|
||||||
"os": "debian",
|
"os": "debian",
|
||||||
"version": "13"
|
"version": "12"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -19,8 +19,8 @@
|
|||||||
"type": "default",
|
"type": "default",
|
||||||
"script": "ct/paperless-ai.sh",
|
"script": "ct/paperless-ai.sh",
|
||||||
"resources": {
|
"resources": {
|
||||||
"cpu": 2,
|
"cpu": 4,
|
||||||
"ram": 2048,
|
"ram": 4096,
|
||||||
"hdd": 20,
|
"hdd": 20,
|
||||||
"os": "debian",
|
"os": "debian",
|
||||||
"version": "13"
|
"version": "13"
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
"ram": 1024,
|
"ram": 1024,
|
||||||
"hdd": 4,
|
"hdd": 4,
|
||||||
"os": "debian",
|
"os": "debian",
|
||||||
"version": "13"
|
"version": "12"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
"ram": 512,
|
"ram": 512,
|
||||||
"hdd": 2,
|
"hdd": 2,
|
||||||
"os": "debian",
|
"os": "debian",
|
||||||
"version": "13"
|
"version": "12"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -23,7 +23,7 @@
|
|||||||
"ram": 2048,
|
"ram": 2048,
|
||||||
"hdd": 5,
|
"hdd": 5,
|
||||||
"os": "Debian",
|
"os": "Debian",
|
||||||
"version": "12"
|
"version": "13"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -322,31 +322,18 @@ export function GeneralSettingsModal({ isOpen, onClose }: GeneralSettingsModalPr
|
|||||||
setIsSaving(true);
|
setIsSaving(true);
|
||||||
setMessage(null);
|
setMessage(null);
|
||||||
|
|
||||||
|
console.log('Saving auto-sync settings:', {
|
||||||
|
autoSyncEnabled,
|
||||||
|
syncIntervalType,
|
||||||
|
syncIntervalPredefined,
|
||||||
|
syncIntervalCron,
|
||||||
|
autoDownloadNew,
|
||||||
|
autoUpdateExisting,
|
||||||
|
notificationEnabled,
|
||||||
|
appriseUrls
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Validate cron expression if custom
|
|
||||||
if (syncIntervalType === 'custom' && syncIntervalCron) {
|
|
||||||
const response = await fetch('/api/settings/auto-sync', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
autoSyncEnabled,
|
|
||||||
syncIntervalType,
|
|
||||||
syncIntervalPredefined,
|
|
||||||
syncIntervalCron,
|
|
||||||
autoDownloadNew,
|
|
||||||
autoUpdateExisting,
|
|
||||||
notificationEnabled,
|
|
||||||
appriseUrls: appriseUrls
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorData = await response.json();
|
|
||||||
setMessage({ type: 'error', text: errorData.error ?? 'Failed to save auto-sync settings' });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch('/api/settings/auto-sync', {
|
const response = await fetch('/api/settings/auto-sync', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
@@ -362,11 +349,16 @@ export function GeneralSettingsModal({ isOpen, onClose }: GeneralSettingsModalPr
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('API response status:', response.status);
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
|
const result = await response.json();
|
||||||
|
console.log('API response data:', result);
|
||||||
setMessage({ type: 'success', text: 'Auto-sync settings saved successfully!' });
|
setMessage({ type: 'success', text: 'Auto-sync settings saved successfully!' });
|
||||||
setTimeout(() => setMessage(null), 3000);
|
setTimeout(() => setMessage(null), 3000);
|
||||||
} else {
|
} else {
|
||||||
const errorData = await response.json();
|
const errorData = await response.json();
|
||||||
|
console.error('API error:', errorData);
|
||||||
setMessage({ type: 'error', text: errorData.error ?? 'Failed to save auto-sync settings' });
|
setMessage({ type: 'error', text: errorData.error ?? 'Failed to save auto-sync settings' });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -824,7 +816,46 @@ export function GeneralSettingsModal({ isOpen, onClose }: GeneralSettingsModalPr
|
|||||||
</div>
|
</div>
|
||||||
<Toggle
|
<Toggle
|
||||||
checked={autoSyncEnabled}
|
checked={autoSyncEnabled}
|
||||||
onCheckedChange={setAutoSyncEnabled}
|
onCheckedChange={async (checked) => {
|
||||||
|
console.log('Toggle changed to:', checked);
|
||||||
|
setAutoSyncEnabled(checked);
|
||||||
|
|
||||||
|
// Auto-save when toggle changes
|
||||||
|
try {
|
||||||
|
// If syncIntervalType is custom but no cron expression, fallback to predefined
|
||||||
|
const effectiveSyncIntervalType = (syncIntervalType === 'custom' && !syncIntervalCron)
|
||||||
|
? 'predefined'
|
||||||
|
: syncIntervalType;
|
||||||
|
|
||||||
|
const response = await fetch('/api/settings/auto-sync', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
autoSyncEnabled: checked,
|
||||||
|
syncIntervalType: effectiveSyncIntervalType,
|
||||||
|
syncIntervalPredefined: effectiveSyncIntervalType === 'predefined' ? syncIntervalPredefined : undefined,
|
||||||
|
syncIntervalCron: effectiveSyncIntervalType === 'custom' ? syncIntervalCron : undefined,
|
||||||
|
autoDownloadNew,
|
||||||
|
autoUpdateExisting,
|
||||||
|
notificationEnabled,
|
||||||
|
appriseUrls: appriseUrls
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
console.log('Auto-sync toggle saved successfully');
|
||||||
|
// Update local state to reflect the effective sync interval type
|
||||||
|
if (effectiveSyncIntervalType !== syncIntervalType) {
|
||||||
|
setSyncIntervalType(effectiveSyncIntervalType);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const errorData = await response.json();
|
||||||
|
console.error('Failed to save auto-sync toggle:', errorData);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving auto-sync toggle:', error);
|
||||||
|
}
|
||||||
|
}}
|
||||||
disabled={isSaving}
|
disabled={isSaving}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { isValidCron } from 'cron-validator';
|
|||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const settings = await request.json();
|
const settings = await request.json();
|
||||||
|
console.log('Received auto-sync settings:', settings);
|
||||||
|
|
||||||
if (!settings || typeof settings !== 'object') {
|
if (!settings || typeof settings !== 'object') {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
@@ -45,11 +46,14 @@ export async function POST(request: NextRequest) {
|
|||||||
|
|
||||||
// Validate sync interval type
|
// Validate sync interval type
|
||||||
if (!['predefined', 'custom'].includes(settings.syncIntervalType)) {
|
if (!['predefined', 'custom'].includes(settings.syncIntervalType)) {
|
||||||
|
console.log('Invalid syncIntervalType:', settings.syncIntervalType);
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'syncIntervalType must be "predefined" or "custom"' },
|
{ error: 'syncIntervalType must be "predefined" or "custom"' },
|
||||||
{ status: 400 }
|
{ status: 400 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('Sync interval validation - type:', settings.syncIntervalType, 'cron:', settings.syncIntervalCron);
|
||||||
|
|
||||||
// Validate predefined interval
|
// Validate predefined interval
|
||||||
if (settings.syncIntervalType === 'predefined') {
|
if (settings.syncIntervalType === 'predefined') {
|
||||||
@@ -64,14 +68,13 @@ export async function POST(request: NextRequest) {
|
|||||||
|
|
||||||
// Validate custom cron expression
|
// Validate custom cron expression
|
||||||
if (settings.syncIntervalType === 'custom') {
|
if (settings.syncIntervalType === 'custom') {
|
||||||
if (!settings.syncIntervalCron || typeof settings.syncIntervalCron !== 'string') {
|
if (!settings.syncIntervalCron || typeof settings.syncIntervalCron !== 'string' || settings.syncIntervalCron.trim() === '') {
|
||||||
return NextResponse.json(
|
console.log('Custom sync interval type but no cron expression provided, falling back to predefined');
|
||||||
{ error: 'Custom cron expression is required when syncIntervalType is "custom"' },
|
// Fallback to predefined if custom is selected but no cron expression
|
||||||
{ status: 400 }
|
settings.syncIntervalType = 'predefined';
|
||||||
);
|
settings.syncIntervalPredefined = settings.syncIntervalPredefined || '1hour';
|
||||||
}
|
settings.syncIntervalCron = '';
|
||||||
|
} else if (!isValidCron(settings.syncIntervalCron, { seconds: false })) {
|
||||||
if (!isValidCron(settings.syncIntervalCron, { seconds: false })) {
|
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'Invalid cron expression' },
|
{ error: 'Invalid cron expression' },
|
||||||
{ status: 400 }
|
{ status: 400 }
|
||||||
@@ -156,23 +159,33 @@ export async function POST(request: NextRequest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write back to .env file
|
// Write back to .env file
|
||||||
|
console.log('Writing to .env file:', envPath);
|
||||||
|
console.log('New .env content:', envContent);
|
||||||
fs.writeFileSync(envPath, envContent);
|
fs.writeFileSync(envPath, envContent);
|
||||||
|
console.log('Successfully wrote to .env file');
|
||||||
|
|
||||||
// Reschedule auto-sync service with new settings
|
// Reschedule auto-sync service with new settings
|
||||||
try {
|
try {
|
||||||
const { getAutoSyncService } = await import('../../../../server/lib/autoSyncInit.js');
|
const { getAutoSyncService, setAutoSyncService } = await import('../../../../server/lib/autoSyncInit.js');
|
||||||
let autoSyncService = getAutoSyncService();
|
let autoSyncService = getAutoSyncService();
|
||||||
|
|
||||||
// If no global instance exists, create one
|
// If no global instance exists, create one
|
||||||
if (!autoSyncService) {
|
if (!autoSyncService) {
|
||||||
const { AutoSyncService } = await import('../../../../server/services/autoSyncService.js');
|
const { AutoSyncService } = await import('../../../../server/services/autoSyncService.js');
|
||||||
autoSyncService = new AutoSyncService();
|
autoSyncService = new AutoSyncService();
|
||||||
|
setAutoSyncService(autoSyncService);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the global service instance with new settings
|
||||||
|
console.log('Updating global service instance with settings:', settings);
|
||||||
|
autoSyncService.saveSettings(settings);
|
||||||
|
|
||||||
if (settings.autoSyncEnabled) {
|
if (settings.autoSyncEnabled) {
|
||||||
|
console.log('Enabling auto-sync...');
|
||||||
autoSyncService.scheduleAutoSync();
|
autoSyncService.scheduleAutoSync();
|
||||||
console.log('Auto-sync rescheduled with new settings');
|
console.log('Auto-sync rescheduled with new settings');
|
||||||
} else {
|
} else {
|
||||||
|
console.log('Disabling auto-sync...');
|
||||||
autoSyncService.stopAutoSync();
|
autoSyncService.stopAutoSync();
|
||||||
console.log('Auto-sync stopped');
|
console.log('Auto-sync stopped');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -504,22 +504,26 @@ export const scriptsRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
try {
|
try {
|
||||||
// Use the global auto-sync service instance
|
// Use the global auto-sync service instance
|
||||||
const { getAutoSyncService } = await import('~/server/lib/autoSyncInit');
|
const { getAutoSyncService, setAutoSyncService } = await import('~/server/lib/autoSyncInit');
|
||||||
let autoSyncService = getAutoSyncService();
|
let autoSyncService = getAutoSyncService();
|
||||||
|
|
||||||
// If no global instance exists, create one
|
// If no global instance exists, create one
|
||||||
if (!autoSyncService) {
|
if (!autoSyncService) {
|
||||||
const { AutoSyncService } = await import('~/server/services/autoSyncService');
|
const { AutoSyncService } = await import('~/server/services/autoSyncService');
|
||||||
autoSyncService = new AutoSyncService();
|
autoSyncService = new AutoSyncService();
|
||||||
|
setAutoSyncService(autoSyncService);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save settings to both .env file and service instance
|
||||||
autoSyncService.saveSettings(input);
|
autoSyncService.saveSettings(input);
|
||||||
|
|
||||||
// Reschedule auto-sync if enabled
|
// Reschedule auto-sync if enabled
|
||||||
if (input.autoSyncEnabled) {
|
if (input.autoSyncEnabled) {
|
||||||
autoSyncService.scheduleAutoSync();
|
autoSyncService.scheduleAutoSync();
|
||||||
|
console.log('Auto-sync rescheduled with new settings');
|
||||||
} else {
|
} else {
|
||||||
autoSyncService.stopAutoSync();
|
autoSyncService.stopAutoSync();
|
||||||
|
console.log('Auto-sync stopped');
|
||||||
}
|
}
|
||||||
|
|
||||||
return { success: true, message: 'Auto-sync settings saved successfully' };
|
return { success: true, message: 'Auto-sync settings saved successfully' };
|
||||||
|
|||||||
@@ -9,13 +9,16 @@ export function initializeAutoSync() {
|
|||||||
try {
|
try {
|
||||||
console.log('Initializing auto-sync service...');
|
console.log('Initializing auto-sync service...');
|
||||||
autoSyncService = new AutoSyncService();
|
autoSyncService = new AutoSyncService();
|
||||||
|
console.log('AutoSyncService instance created');
|
||||||
|
|
||||||
// Load settings and schedule if enabled
|
// Load settings and schedule if enabled
|
||||||
const settings = autoSyncService.loadSettings();
|
const settings = autoSyncService.loadSettings();
|
||||||
|
console.log('Settings loaded:', settings);
|
||||||
|
|
||||||
if (settings.autoSyncEnabled) {
|
if (settings.autoSyncEnabled) {
|
||||||
console.log('Auto-sync is enabled, scheduling cron job...');
|
console.log('Auto-sync is enabled, scheduling cron job...');
|
||||||
autoSyncService.scheduleAutoSync();
|
autoSyncService.scheduleAutoSync();
|
||||||
|
console.log('Cron job scheduled');
|
||||||
} else {
|
} else {
|
||||||
console.log('Auto-sync is disabled');
|
console.log('Auto-sync is disabled');
|
||||||
}
|
}
|
||||||
@@ -23,6 +26,7 @@ export function initializeAutoSync() {
|
|||||||
console.log('Auto-sync service initialized successfully');
|
console.log('Auto-sync service initialized successfully');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to initialize auto-sync service:', error);
|
console.error('Failed to initialize auto-sync service:', error);
|
||||||
|
console.error('Error stack:', error.stack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,6 +53,13 @@ export function getAutoSyncService() {
|
|||||||
return autoSyncService;
|
return autoSyncService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the auto-sync service instance (for external management)
|
||||||
|
*/
|
||||||
|
export function setAutoSyncService(service) {
|
||||||
|
autoSyncService = service;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Graceful shutdown handler
|
* Graceful shutdown handler
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -49,6 +49,13 @@ export function getAutoSyncService(): AutoSyncService | null {
|
|||||||
return autoSyncService;
|
return autoSyncService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the auto-sync service instance (for external management)
|
||||||
|
*/
|
||||||
|
export function setAutoSyncService(service: AutoSyncService | null): void {
|
||||||
|
autoSyncService = service;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Graceful shutdown handler
|
* Graceful shutdown handler
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -173,6 +173,7 @@ export class AutoSyncService {
|
|||||||
const key = trimmedLine.substring(0, equalIndex).trim();
|
const key = trimmedLine.substring(0, equalIndex).trim();
|
||||||
if (key && key in settingsMap) {
|
if (key && key in settingsMap) {
|
||||||
// Replace existing setting
|
// Replace existing setting
|
||||||
|
// @ts-ignore - Dynamic property access is safe here
|
||||||
newLines.push(`${key}=${settingsMap[key]}`);
|
newLines.push(`${key}=${settingsMap[key]}`);
|
||||||
existingKeys.add(key);
|
existingKeys.add(key);
|
||||||
} else {
|
} else {
|
||||||
@@ -256,7 +257,10 @@ export class AutoSyncService {
|
|||||||
if (this.cronJob) {
|
if (this.cronJob) {
|
||||||
this.cronJob.stop();
|
this.cronJob.stop();
|
||||||
this.cronJob = null;
|
this.cronJob = null;
|
||||||
|
this.isRunning = false;
|
||||||
console.log('Auto-sync cron job stopped');
|
console.log('Auto-sync cron job stopped');
|
||||||
|
} else {
|
||||||
|
console.log('No active cron job to stop');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,8 +289,8 @@ export class AutoSyncService {
|
|||||||
|
|
||||||
const results = {
|
const results = {
|
||||||
jsonSync: syncResult,
|
jsonSync: syncResult,
|
||||||
newScripts: /** @type {string[]} */ ([]),
|
newScripts: /** @type {any[]} */ ([]),
|
||||||
updatedScripts: /** @type {string[]} */ ([]),
|
updatedScripts: /** @type {any[]} */ ([]),
|
||||||
errors: /** @type {string[]} */ ([])
|
errors: /** @type {string[]} */ ([])
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user