Skip to content

Commit fd45844

Browse files
authored
Initial commit - AppZ Hosting Frappe app
Complete managed hosting platform with: - DocTypes: AppZ Server, Deployment Template, Service Plan, Hosted Service, Service Backup Config, Service Backup - Core modules: deployer.py, backup.py, ai_advisor.py, caddy.py, clickstack.py, monitoring.py - Templates: WordPress, n8n, Ghost with docker-compose and backup/restore scripts - Web portal: my-services, services, request-service pages - Service management, monitoring, and automated deployments
0 parents  commit fd45844

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+4993
-0
lines changed

README.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# AppZ Hosting
2+
3+
White-label Managed Hosting Platform for AppZ Studio.
4+
5+
## Features
6+
7+
- **Template Library**: Pre-tested Docker Compose templates for WordPress, n8n, Ghost, and more
8+
- **AI Capacity Advisor**: Intelligent server capacity planning
9+
- **Automated Backups**: Daily backups to S3-compatible storage
10+
- **ClickStack Integration**: Optional observability addon for customers
11+
- **Customer Portal**: Self-service dashboard for customers
12+
- **Multi-Currency Billing**: USD, INR, BTC via ERPNext integration
13+
14+
## Installation
15+
16+
```bash
17+
bench get-app https://github.com/Simbotix/appz_hosting
18+
bench --site your-site install-app appz_hosting
19+
```
20+
21+
## DocTypes
22+
23+
- **AppZ Server**: Server registry with capacity tracking
24+
- **Deployment Template**: Tested docker-compose templates
25+
- **Service Plan**: Customer-facing pricing plans
26+
- **Hosted Service**: Customer service instances
27+
- **Service Backup Config**: Backup configuration per service
28+
- **Service Backup**: Individual backup records
29+
- **Service Observability**: ClickStack integration settings
30+
31+
## Configuration
32+
33+
Add to your `site_config.json`:
34+
35+
```json
36+
{
37+
"hetzner_s3_endpoint": "https://fsn1.your-objectstorage.com",
38+
"hetzner_s3_access_key": "your-access-key",
39+
"hetzner_s3_secret_key": "your-secret-key",
40+
"hetzner_s3_bucket": "appz-backups",
41+
"clickstack_endpoint": "https://otel.appz.studio"
42+
}
43+
```
44+
45+
## License
46+
47+
MIT

appz_hosting/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__version__ = "0.0.1"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# AppZ Hosting Module
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# DocTypes
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# AppZ Server DocType
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
{
2+
"actions": [],
3+
"autoname": "field:server_name",
4+
"creation": "2025-01-15 10:00:00.000000",
5+
"doctype": "DocType",
6+
"engine": "InnoDB",
7+
"field_order": [
8+
"server_name",
9+
"provider",
10+
"server_type",
11+
"ip_address",
12+
"ssh_key",
13+
"status",
14+
"column_break_1",
15+
"location",
16+
"monthly_cost",
17+
"notes",
18+
"resources_section",
19+
"total_ram_gb",
20+
"total_cpu_cores",
21+
"total_storage_gb",
22+
"column_break_2",
23+
"used_ram_gb",
24+
"used_cpu_cores",
25+
"used_storage_gb",
26+
"capacity_percent",
27+
"thresholds_section",
28+
"max_ram_percent",
29+
"max_cpu_percent",
30+
"column_break_3",
31+
"service_count",
32+
"last_health_check"
33+
],
34+
"fields": [
35+
{
36+
"fieldname": "server_name",
37+
"fieldtype": "Data",
38+
"in_list_view": 1,
39+
"label": "Server Name",
40+
"reqd": 1,
41+
"unique": 1
42+
},
43+
{
44+
"fieldname": "provider",
45+
"fieldtype": "Select",
46+
"in_list_view": 1,
47+
"label": "Provider",
48+
"options": "Hetzner\nVultr\nOVHCloud\nOther",
49+
"reqd": 1
50+
},
51+
{
52+
"fieldname": "server_type",
53+
"fieldtype": "Data",
54+
"in_list_view": 1,
55+
"label": "Server Type",
56+
"description": "e.g., AX52, CX32"
57+
},
58+
{
59+
"fieldname": "ip_address",
60+
"fieldtype": "Data",
61+
"label": "IP Address",
62+
"reqd": 1
63+
},
64+
{
65+
"fieldname": "ssh_key",
66+
"fieldtype": "Data",
67+
"label": "SSH Key Path",
68+
"description": "Path to private SSH key"
69+
},
70+
{
71+
"fieldname": "status",
72+
"fieldtype": "Select",
73+
"in_list_view": 1,
74+
"label": "Status",
75+
"options": "Active\nMaintenance\nRetired",
76+
"default": "Active"
77+
},
78+
{
79+
"fieldname": "column_break_1",
80+
"fieldtype": "Column Break"
81+
},
82+
{
83+
"fieldname": "location",
84+
"fieldtype": "Data",
85+
"label": "Location",
86+
"description": "e.g., Falkenstein, Germany"
87+
},
88+
{
89+
"fieldname": "monthly_cost",
90+
"fieldtype": "Currency",
91+
"label": "Monthly Cost (EUR)"
92+
},
93+
{
94+
"fieldname": "notes",
95+
"fieldtype": "Small Text",
96+
"label": "Notes"
97+
},
98+
{
99+
"fieldname": "resources_section",
100+
"fieldtype": "Section Break",
101+
"label": "Resources"
102+
},
103+
{
104+
"fieldname": "total_ram_gb",
105+
"fieldtype": "Int",
106+
"label": "Total RAM (GB)",
107+
"default": 64
108+
},
109+
{
110+
"fieldname": "total_cpu_cores",
111+
"fieldtype": "Int",
112+
"label": "Total CPU Cores",
113+
"default": 8
114+
},
115+
{
116+
"fieldname": "total_storage_gb",
117+
"fieldtype": "Int",
118+
"label": "Total Storage (GB)",
119+
"default": 2000
120+
},
121+
{
122+
"fieldname": "column_break_2",
123+
"fieldtype": "Column Break"
124+
},
125+
{
126+
"fieldname": "used_ram_gb",
127+
"fieldtype": "Float",
128+
"label": "Used RAM (GB)",
129+
"read_only": 1
130+
},
131+
{
132+
"fieldname": "used_cpu_cores",
133+
"fieldtype": "Float",
134+
"label": "Used CPU Cores",
135+
"read_only": 1
136+
},
137+
{
138+
"fieldname": "used_storage_gb",
139+
"fieldtype": "Float",
140+
"label": "Used Storage (GB)",
141+
"read_only": 1
142+
},
143+
{
144+
"fieldname": "capacity_percent",
145+
"fieldtype": "Percent",
146+
"label": "Capacity Used",
147+
"read_only": 1
148+
},
149+
{
150+
"fieldname": "thresholds_section",
151+
"fieldtype": "Section Break",
152+
"label": "Thresholds"
153+
},
154+
{
155+
"fieldname": "max_ram_percent",
156+
"fieldtype": "Int",
157+
"label": "Max RAM %",
158+
"default": 80,
159+
"description": "Don't allocate beyond this"
160+
},
161+
{
162+
"fieldname": "max_cpu_percent",
163+
"fieldtype": "Int",
164+
"label": "Max CPU %",
165+
"default": 70,
166+
"description": "Don't allocate beyond this"
167+
},
168+
{
169+
"fieldname": "column_break_3",
170+
"fieldtype": "Column Break"
171+
},
172+
{
173+
"fieldname": "service_count",
174+
"fieldtype": "Int",
175+
"label": "Service Count",
176+
"read_only": 1
177+
},
178+
{
179+
"fieldname": "last_health_check",
180+
"fieldtype": "Datetime",
181+
"label": "Last Health Check",
182+
"read_only": 1
183+
}
184+
],
185+
"index_web_pages_for_search": 0,
186+
"links": [],
187+
"modified": "2025-01-15 10:00:00.000000",
188+
"modified_by": "Administrator",
189+
"module": "AppZ Hosting",
190+
"name": "AppZ Server",
191+
"naming_rule": "By fieldname",
192+
"owner": "Administrator",
193+
"permissions": [
194+
{
195+
"create": 1,
196+
"delete": 1,
197+
"email": 1,
198+
"export": 1,
199+
"print": 1,
200+
"read": 1,
201+
"report": 1,
202+
"role": "System Manager",
203+
"share": 1,
204+
"write": 1
205+
}
206+
],
207+
"sort_field": "modified",
208+
"sort_order": "DESC",
209+
"states": [],
210+
"track_changes": 1
211+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import frappe
2+
from frappe.model.document import Document
3+
4+
5+
class AppZServer(Document):
6+
def validate(self):
7+
self.update_capacity()
8+
9+
def update_capacity(self):
10+
"""Calculate capacity based on hosted services"""
11+
services = frappe.get_all(
12+
"Hosted Service",
13+
filters={"server": self.name, "status": ["in", ["Active", "Provisioning"]]},
14+
fields=["actual_ram_mb", "actual_cpu_percent", "actual_storage_gb"]
15+
)
16+
17+
total_ram = sum(s.actual_ram_mb or 0 for s in services) / 1024 # Convert to GB
18+
total_cpu = sum(s.actual_cpu_percent or 0 for s in services)
19+
total_storage = sum(s.actual_storage_gb or 0 for s in services)
20+
21+
self.used_ram_gb = round(total_ram, 2)
22+
self.used_cpu_cores = round(total_cpu, 2)
23+
self.used_storage_gb = round(total_storage, 2)
24+
self.service_count = len(services)
25+
26+
# Calculate overall capacity (weighted average)
27+
ram_pct = (total_ram / self.total_ram_gb * 100) if self.total_ram_gb else 0
28+
cpu_pct = (total_cpu / self.total_cpu_cores * 100) if self.total_cpu_cores else 0
29+
storage_pct = (total_storage / self.total_storage_gb * 100) if self.total_storage_gb else 0
30+
31+
self.capacity_percent = round(max(ram_pct, cpu_pct, storage_pct), 1)
32+
33+
def can_fit(self, ram_mb, cpu_cores, storage_gb):
34+
"""Check if this server can fit additional resources"""
35+
new_ram_pct = ((self.used_ram_gb + ram_mb / 1024) / self.total_ram_gb * 100)
36+
new_cpu_pct = ((self.used_cpu_cores + cpu_cores) / self.total_cpu_cores * 100)
37+
new_storage_pct = ((self.used_storage_gb + storage_gb) / self.total_storage_gb * 100)
38+
39+
return (
40+
new_ram_pct <= self.max_ram_percent and
41+
new_cpu_pct <= self.max_cpu_percent and
42+
new_storage_pct <= 90
43+
)
44+
45+
@frappe.whitelist()
46+
def refresh_stats(self):
47+
"""Refresh server stats from actual Docker usage"""
48+
from appz_hosting.core.deployer import Deployer
49+
50+
try:
51+
deployer = Deployer(self.name)
52+
stats = deployer.get_server_stats()
53+
54+
self.used_ram_gb = stats.get("used_ram_gb", 0)
55+
self.used_cpu_cores = stats.get("used_cpu_cores", 0)
56+
self.used_storage_gb = stats.get("used_storage_gb", 0)
57+
self.last_health_check = frappe.utils.now()
58+
self.save()
59+
60+
return {"success": True, "stats": stats}
61+
except Exception as e:
62+
frappe.log_error(f"Failed to refresh stats for {self.name}: {e}")
63+
return {"success": False, "error": str(e)}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Deployment Template DocType

0 commit comments

Comments
 (0)