calagoapi 1.0.dev1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- calagoapi/__init__.py +190 -0
- calagoapi/account/__init__.py +14 -0
- calagoapi/account/account.py +292 -0
- calagoapi/account/auth.py +72 -0
- calagoapi/account/models.py +221 -0
- calagoapi/admin/__init__.py +48 -0
- calagoapi/admin/admin.py +341 -0
- calagoapi/admin/managers.py +490 -0
- calagoapi/admin/resources.py +970 -0
- calagoapi/aio/__init__.py +9 -0
- calagoapi/aio/console.py +289 -0
- calagoapi/aio/panel.py +176 -0
- calagoapi/api/__init__.py +63 -0
- calagoapi/api/account.py +195 -0
- calagoapi/api/admin.py +402 -0
- calagoapi/api/attachments.py +27 -0
- calagoapi/api/auth.py +52 -0
- calagoapi/api/backups.py +99 -0
- calagoapi/api/client.py +198 -0
- calagoapi/api/databases.py +217 -0
- calagoapi/api/files.py +210 -0
- calagoapi/api/network.py +84 -0
- calagoapi/api/public.py +13 -0
- calagoapi/api/schedules.py +79 -0
- calagoapi/api/servers.py +114 -0
- calagoapi/api/users.py +27 -0
- calagoapi/api/versions.py +29 -0
- calagoapi/backups/__init__.py +8 -0
- calagoapi/backups/backup.py +292 -0
- calagoapi/backups/manager.py +177 -0
- calagoapi/console/__init__.py +6 -0
- calagoapi/console/console.py +392 -0
- calagoapi/databases/__init__.py +12 -0
- calagoapi/databases/database.py +445 -0
- calagoapi/databases/explorer.py +159 -0
- calagoapi/databases/manager.py +122 -0
- calagoapi/files/__init__.py +9 -0
- calagoapi/files/file.py +196 -0
- calagoapi/files/manager.py +437 -0
- calagoapi/network/__init__.py +8 -0
- calagoapi/network/allocation.py +99 -0
- calagoapi/network/manager.py +132 -0
- calagoapi/nodes/__init__.py +6 -0
- calagoapi/nodes/node.py +477 -0
- calagoapi/panel.py +162 -0
- calagoapi/schedules/__init__.py +14 -0
- calagoapi/schedules/builders.py +461 -0
- calagoapi/schedules/manager.py +107 -0
- calagoapi/schedules/schedule.py +214 -0
- calagoapi/servers/__init__.py +13 -0
- calagoapi/servers/admin.py +430 -0
- calagoapi/servers/generic.py +785 -0
- calagoapi/servers/manager.py +167 -0
- calagoapi/servers/minecraft.py +207 -0
- calagoapi/tunnels/__init__.py +5 -0
- calagoapi/tunnels/tunnel.py +112 -0
- calagoapi/users/__init__.py +11 -0
- calagoapi/users/activity.py +45 -0
- calagoapi/users/subuser.py +127 -0
- calagoapi/users/user.py +267 -0
- calagoapi/utils/__init__.py +43 -0
- calagoapi/utils/convert.py +113 -0
- calagoapi/utils/exceptions.py +72 -0
- calagoapi/utils/model.py +142 -0
- calagoapi/utils/resource.py +100 -0
- calagoapi/utils/time.py +68 -0
- calagoapi-1.0.dev1.dist-info/METADATA +266 -0
- calagoapi-1.0.dev1.dist-info/RECORD +71 -0
- calagoapi-1.0.dev1.dist-info/WHEEL +5 -0
- calagoapi-1.0.dev1.dist-info/licenses/LICENSE +674 -0
- calagoapi-1.0.dev1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,785 @@
|
|
|
1
|
+
from calagoapi.utils.model import Model, idOf
|
|
2
|
+
from calagoapi.utils.convert import formatDuration
|
|
3
|
+
from calagoapi.utils.exceptions import InvalidActionError
|
|
4
|
+
from calagoapi.utils import time as _time
|
|
5
|
+
from calagoapi.files import FileManager
|
|
6
|
+
from calagoapi.backups import BackupManager
|
|
7
|
+
from calagoapi.databases import DatabaseManager
|
|
8
|
+
from calagoapi.schedules import ScheduleManager
|
|
9
|
+
from calagoapi.users import SubuserManager, Activity
|
|
10
|
+
from calagoapi.network import NetworkManager, Allocation
|
|
11
|
+
from calagoapi.tunnels import TunnelManager
|
|
12
|
+
from calagoapi.console import Console
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
POWER_ACTIONS = ('start', 'restart', 'stop', 'kill')
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class GenericServer(Model):
|
|
19
|
+
'''
|
|
20
|
+
A Calagopus server.
|
|
21
|
+
|
|
22
|
+
Usually obtained with `panel.servers.getServer()`. Every feature of a
|
|
23
|
+
server is reachable from here, either directly (power, console, status,
|
|
24
|
+
resources, settings) or through a sub-manager:
|
|
25
|
+
|
|
26
|
+
- `server.files` FileManager
|
|
27
|
+
- `server.backups` BackupManager
|
|
28
|
+
- `server.databases` DatabaseManager
|
|
29
|
+
- `server.schedules` ScheduleManager
|
|
30
|
+
- `server.users` SubuserManager
|
|
31
|
+
- `server.network` NetworkManager (allocations, subdomains, firewall)
|
|
32
|
+
- `server.tunnel` TunnelManager
|
|
33
|
+
- `server.console` Console (live console over websocket)
|
|
34
|
+
|
|
35
|
+
The most common operations are also available directly on the server,
|
|
36
|
+
with the same names HungerLib uses (createBackup, listFiles, ...).
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
panel: The Panel the server lives on.
|
|
40
|
+
server_id: Server UUID (or short id).
|
|
41
|
+
name: Optional display name. Defaults to the name on the panel.
|
|
42
|
+
data: Optional server data, if already fetched.
|
|
43
|
+
|
|
44
|
+
Example:
|
|
45
|
+
server = panel.servers.getServer('abc123')
|
|
46
|
+
|
|
47
|
+
server.restart()
|
|
48
|
+
server.sendCommand('say Hello')
|
|
49
|
+
|
|
50
|
+
if server.isOnline():
|
|
51
|
+
print(server.getStatus(), server.getRAM(), server.getUptime(formatted=True))
|
|
52
|
+
|
|
53
|
+
backup = server.createBackup('before-update')
|
|
54
|
+
'''
|
|
55
|
+
def __init__(self, panel, server_id: str = None, name: str = None, data: dict = None):
|
|
56
|
+
super().__init__(panel, data)
|
|
57
|
+
self.server_id = server_id or self.data.get('uuid')
|
|
58
|
+
if not self.server_id:
|
|
59
|
+
raise ValueError('A server_id is required')
|
|
60
|
+
self.name = name or self.data.get('name') or self.server_id
|
|
61
|
+
|
|
62
|
+
self._cached_resources = None
|
|
63
|
+
|
|
64
|
+
# managers
|
|
65
|
+
self.files = FileManager(self)
|
|
66
|
+
self.backups = BackupManager(self)
|
|
67
|
+
self.databases = DatabaseManager(self)
|
|
68
|
+
self.schedules = ScheduleManager(self)
|
|
69
|
+
self.users = SubuserManager(self)
|
|
70
|
+
self.network = NetworkManager(self)
|
|
71
|
+
self.tunnel = TunnelManager(self)
|
|
72
|
+
self.console = Console(self)
|
|
73
|
+
|
|
74
|
+
def __repr__(self):
|
|
75
|
+
return f'<{self.__class__.__name__} {self.name!r} ({self.server_id})>'
|
|
76
|
+
|
|
77
|
+
def __str__(self):
|
|
78
|
+
return f'{self.name} ({self.server_id})'
|
|
79
|
+
|
|
80
|
+
def __eq__(self, other):
|
|
81
|
+
if not isinstance(other, GenericServer):
|
|
82
|
+
return NotImplemented
|
|
83
|
+
return self.getUUID() == other.getUUID()
|
|
84
|
+
|
|
85
|
+
def __hash__(self):
|
|
86
|
+
return hash(self.getUUID())
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def api(self):
|
|
90
|
+
return self.panel.api.servers
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# server details
|
|
94
|
+
def details(self) -> dict:
|
|
95
|
+
'''Returns cached server details, fetching them on first use.'''
|
|
96
|
+
if not self.data.get('uuid'):
|
|
97
|
+
self.refreshDetails()
|
|
98
|
+
return self.data
|
|
99
|
+
|
|
100
|
+
def refreshDetails(self) -> dict:
|
|
101
|
+
'''Forces a refetch of the server details, caches, then returns them.'''
|
|
102
|
+
self.data = self.api.get(self.server_id)
|
|
103
|
+
return self.data
|
|
104
|
+
|
|
105
|
+
def get(self, key: str, default=None):
|
|
106
|
+
'''Returns a raw field from the server details.'''
|
|
107
|
+
return self.details().get(key, default)
|
|
108
|
+
|
|
109
|
+
def getUUID(self) -> str:
|
|
110
|
+
'''Returns the full server UUID.'''
|
|
111
|
+
return self.data.get('uuid') or self.server_id
|
|
112
|
+
|
|
113
|
+
def getShortId(self) -> str:
|
|
114
|
+
'''Returns the short server id shown in the panel URL.'''
|
|
115
|
+
return self.get('uuid_short')
|
|
116
|
+
|
|
117
|
+
def getName(self) -> str:
|
|
118
|
+
'''Returns the server name from the panel.'''
|
|
119
|
+
return self.get('name')
|
|
120
|
+
|
|
121
|
+
def getDescription(self) -> str:
|
|
122
|
+
'''Returns the server description.'''
|
|
123
|
+
return self.get('description')
|
|
124
|
+
|
|
125
|
+
def getNodeName(self) -> str:
|
|
126
|
+
'''Returns the name of the node the server runs on.'''
|
|
127
|
+
return self.get('node_name')
|
|
128
|
+
|
|
129
|
+
def getNodeId(self) -> str:
|
|
130
|
+
'''Returns the UUID of the node the server runs on.'''
|
|
131
|
+
return self.get('node_uuid')
|
|
132
|
+
|
|
133
|
+
def getLocationName(self) -> str:
|
|
134
|
+
'''Returns the name of the server's location.'''
|
|
135
|
+
return self.get('location_name')
|
|
136
|
+
|
|
137
|
+
def getEgg(self) -> dict:
|
|
138
|
+
'''Returns the egg (server type) information.'''
|
|
139
|
+
return self.get('egg', {})
|
|
140
|
+
|
|
141
|
+
def getEggName(self) -> str:
|
|
142
|
+
'''Returns the egg name, e.g. "Paper".'''
|
|
143
|
+
return self.getEgg().get('name')
|
|
144
|
+
|
|
145
|
+
def getLimits(self) -> dict:
|
|
146
|
+
'''Returns the resource limits (cpu, memory, swap, disk, ...).'''
|
|
147
|
+
return self.get('limits', {})
|
|
148
|
+
|
|
149
|
+
def getFeatureLimits(self) -> dict:
|
|
150
|
+
'''Returns the feature limits (allocations, databases, backups, schedules, ...).'''
|
|
151
|
+
return self.get('feature_limits', {})
|
|
152
|
+
|
|
153
|
+
def getDockerImage(self) -> str:
|
|
154
|
+
'''Returns the docker image the server runs in.'''
|
|
155
|
+
return self.get('image')
|
|
156
|
+
|
|
157
|
+
def getDockerImages(self) -> dict:
|
|
158
|
+
'''Returns the docker images the egg allows, as {name: image}.'''
|
|
159
|
+
return self.getEgg().get('docker_images', {})
|
|
160
|
+
|
|
161
|
+
def getStartupCommand(self) -> str:
|
|
162
|
+
'''Returns the startup command.'''
|
|
163
|
+
return self.get('startup')
|
|
164
|
+
|
|
165
|
+
def getTimezone(self) -> str:
|
|
166
|
+
'''Returns the server timezone, or None for the node default.'''
|
|
167
|
+
return self.get('timezone')
|
|
168
|
+
|
|
169
|
+
def getAutoKill(self) -> dict:
|
|
170
|
+
'''Returns the auto-kill settings.'''
|
|
171
|
+
return self.get('auto_kill')
|
|
172
|
+
|
|
173
|
+
def getAutoStartBehavior(self) -> str:
|
|
174
|
+
'''Returns "always", "unless_stopped" or "never".'''
|
|
175
|
+
return self.get('auto_start_behavior')
|
|
176
|
+
|
|
177
|
+
def getSftpHost(self) -> str:
|
|
178
|
+
'''Returns the SFTP host.'''
|
|
179
|
+
return self.get('sftp_host')
|
|
180
|
+
|
|
181
|
+
def getSftpPort(self) -> int:
|
|
182
|
+
'''Returns the SFTP port.'''
|
|
183
|
+
return self.get('sftp_port')
|
|
184
|
+
|
|
185
|
+
def getPermissions(self) -> list:
|
|
186
|
+
'''Returns the permissions the current API key has on this server.'''
|
|
187
|
+
return self.get('permissions', [])
|
|
188
|
+
|
|
189
|
+
def hasPermission(self, permission: str) -> bool:
|
|
190
|
+
'''Returns True if the current API key has the permission (owners and admins have "*").'''
|
|
191
|
+
permissions = self.getPermissions()
|
|
192
|
+
return '*' in permissions or permission in permissions
|
|
193
|
+
|
|
194
|
+
def getPrimaryAllocation(self) -> Allocation:
|
|
195
|
+
'''Returns the primary allocation, or None.'''
|
|
196
|
+
data = self.get('allocation')
|
|
197
|
+
return Allocation(self, data) if data else None
|
|
198
|
+
|
|
199
|
+
def getAddress(self) -> str:
|
|
200
|
+
'''
|
|
201
|
+
Returns the address players connect to, e.g. "play.example.com:25565".
|
|
202
|
+
|
|
203
|
+
Uses the IP alias when one is set.
|
|
204
|
+
'''
|
|
205
|
+
allocation = self.getPrimaryAllocation()
|
|
206
|
+
return allocation.getAddress() if allocation else None
|
|
207
|
+
|
|
208
|
+
def getInstallStatus(self) -> str:
|
|
209
|
+
'''
|
|
210
|
+
Returns the server's lifecycle status.
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
None when installed and ready, otherwise a status such as
|
|
214
|
+
'installing', 'install_failed', 'reinstall_failed' or 'restoring_backup'.
|
|
215
|
+
'''
|
|
216
|
+
return self.refreshDetails().get('status')
|
|
217
|
+
|
|
218
|
+
def getNodeResources(self) -> dict:
|
|
219
|
+
'''Returns resource usage of the node the server runs on.'''
|
|
220
|
+
return self.api.nodeResources(self.getNodeId())
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
# resource and status
|
|
224
|
+
def refresh(self) -> dict:
|
|
225
|
+
'''Forces refetch of resources, caches, then returns them.'''
|
|
226
|
+
self._cached_resources = self.api.resources(self.server_id)
|
|
227
|
+
return self._cached_resources
|
|
228
|
+
|
|
229
|
+
def resources(self) -> dict:
|
|
230
|
+
'''Returns cached resources (fetched on first use, see refresh()).'''
|
|
231
|
+
return self._cached_resources or self.refresh()
|
|
232
|
+
|
|
233
|
+
def getResources(self) -> dict:
|
|
234
|
+
'''
|
|
235
|
+
Returns fresh resource usage.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
dict with memory_bytes, memory_limit_bytes, disk_bytes, cpu_absolute,
|
|
239
|
+
cpu_limit_absolute, network (rx_bytes, tx_bytes, ...), uptime and state.
|
|
240
|
+
'''
|
|
241
|
+
return self.refresh()
|
|
242
|
+
|
|
243
|
+
@staticmethod
|
|
244
|
+
def _size(value, rounding, gb):
|
|
245
|
+
if value is None:
|
|
246
|
+
return None
|
|
247
|
+
div = 1024 * 1024 * (1024 if gb else 1)
|
|
248
|
+
return round(value / div, rounding)
|
|
249
|
+
|
|
250
|
+
def getRAM(self, rounding: int = 2, gb: bool = False) -> float:
|
|
251
|
+
'''Returns current RAM usage in MiB (or GiB with gb=True).'''
|
|
252
|
+
return self._size(self.resources().get('memory_bytes'), rounding, gb)
|
|
253
|
+
|
|
254
|
+
def getRAMLimit(self, rounding: int = 2, gb: bool = False) -> float:
|
|
255
|
+
'''Returns the RAM limit in MiB (or GiB with gb=True).'''
|
|
256
|
+
return self._size(self.resources().get('memory_limit_bytes'), rounding, gb)
|
|
257
|
+
|
|
258
|
+
def getCPU(self, rounding: int = 2) -> float:
|
|
259
|
+
'''Returns current CPU usage in percent (100 = one core).'''
|
|
260
|
+
cpu = self.resources().get('cpu_absolute')
|
|
261
|
+
return round(cpu, rounding) if cpu is not None else None
|
|
262
|
+
|
|
263
|
+
def getCPULimit(self) -> float:
|
|
264
|
+
'''Returns the CPU limit in percent (0 = unlimited).'''
|
|
265
|
+
return self.resources().get('cpu_limit_absolute')
|
|
266
|
+
|
|
267
|
+
def getDisk(self, rounding: int = 2, gb: bool = False) -> float:
|
|
268
|
+
'''Returns current disk usage in MiB (or GiB with gb=True).'''
|
|
269
|
+
return self._size(self.resources().get('disk_bytes'), rounding, gb)
|
|
270
|
+
|
|
271
|
+
def getDiskLimit(self, rounding: int = 2, gb: bool = False) -> float:
|
|
272
|
+
'''Returns the disk limit in MiB (or GiB with gb=True), 0 means unlimited.'''
|
|
273
|
+
disk = self.getLimits().get('disk')
|
|
274
|
+
if disk is None:
|
|
275
|
+
return None
|
|
276
|
+
return round(disk / 1024, rounding) if gb else disk
|
|
277
|
+
|
|
278
|
+
def getNetworkIn(self, rounding: int = 2, gb: bool = False) -> float:
|
|
279
|
+
'''Returns received network traffic in MiB (or GiB with gb=True).'''
|
|
280
|
+
return self._size((self.resources().get('network') or {}).get('rx_bytes'), rounding, gb)
|
|
281
|
+
|
|
282
|
+
def getNetworkOut(self, rounding: int = 2, gb: bool = False) -> float:
|
|
283
|
+
'''Returns sent network traffic in MiB (or GiB with gb=True).'''
|
|
284
|
+
return self._size((self.resources().get('network') or {}).get('tx_bytes'), rounding, gb)
|
|
285
|
+
|
|
286
|
+
def getUptime(self, formatted: bool = False):
|
|
287
|
+
'''
|
|
288
|
+
Returns current uptime.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
formatted: Return a string like "3h 12m" instead of milliseconds.
|
|
292
|
+
'''
|
|
293
|
+
uptime = self.resources().get('uptime')
|
|
294
|
+
if uptime is None or not formatted:
|
|
295
|
+
return uptime
|
|
296
|
+
return formatDuration(uptime)
|
|
297
|
+
|
|
298
|
+
def getStatus(self) -> str:
|
|
299
|
+
'''
|
|
300
|
+
Returns the server's current power state (always fresh).
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
'running', 'offline', 'starting' or 'stopping'.
|
|
304
|
+
|
|
305
|
+
Example:
|
|
306
|
+
print(server.getStatus())
|
|
307
|
+
'''
|
|
308
|
+
return self.refresh().get('state')
|
|
309
|
+
|
|
310
|
+
def getPowerState(self) -> str:
|
|
311
|
+
'''Alias of getStatus().'''
|
|
312
|
+
return self.getStatus()
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
# state helpers
|
|
316
|
+
def isOnline(self) -> bool:
|
|
317
|
+
'''Returns True if the server is running.'''
|
|
318
|
+
return self.getStatus() == 'running'
|
|
319
|
+
|
|
320
|
+
def isOffline(self) -> bool:
|
|
321
|
+
'''Returns True if the server is fully offline.'''
|
|
322
|
+
return self.getStatus() == 'offline'
|
|
323
|
+
|
|
324
|
+
def isRunning(self) -> bool:
|
|
325
|
+
'''Alias of isOnline().'''
|
|
326
|
+
return self.isOnline()
|
|
327
|
+
|
|
328
|
+
def isStopped(self) -> bool:
|
|
329
|
+
'''Alias of isOffline().'''
|
|
330
|
+
return self.isOffline()
|
|
331
|
+
|
|
332
|
+
def isStarting(self) -> bool:
|
|
333
|
+
'''Returns True while the server is starting.'''
|
|
334
|
+
return self.getStatus() == 'starting'
|
|
335
|
+
|
|
336
|
+
def isStopping(self) -> bool:
|
|
337
|
+
'''Returns True while the server is stopping.'''
|
|
338
|
+
return self.getStatus() == 'stopping'
|
|
339
|
+
|
|
340
|
+
def isInstalled(self) -> bool:
|
|
341
|
+
'''Returns True when installation has finished and the server is usable.'''
|
|
342
|
+
return self.getInstallStatus() is None
|
|
343
|
+
|
|
344
|
+
def isInstalling(self) -> bool:
|
|
345
|
+
'''Returns True while the server is (re)installing.'''
|
|
346
|
+
return self.getInstallStatus() == 'installing'
|
|
347
|
+
|
|
348
|
+
def isInstallFailed(self) -> bool:
|
|
349
|
+
'''Returns True if the last (re)install failed.'''
|
|
350
|
+
return self.getInstallStatus() in ('install_failed', 'reinstall_failed')
|
|
351
|
+
|
|
352
|
+
def isRestoringBackup(self) -> bool:
|
|
353
|
+
'''Returns True while a backup is being restored.'''
|
|
354
|
+
return self.getInstallStatus() == 'restoring_backup'
|
|
355
|
+
|
|
356
|
+
def isSuspended(self) -> bool:
|
|
357
|
+
'''Returns True if the server is suspended.'''
|
|
358
|
+
return bool(self.refreshDetails().get('is_suspended'))
|
|
359
|
+
|
|
360
|
+
def isTransferring(self) -> bool:
|
|
361
|
+
'''Returns True while the server is being moved to another node.'''
|
|
362
|
+
return bool(self.refreshDetails().get('is_transferring'))
|
|
363
|
+
|
|
364
|
+
def isOwner(self) -> bool:
|
|
365
|
+
'''Returns True if the API key's user owns this server.'''
|
|
366
|
+
return bool(self.get('is_owner'))
|
|
367
|
+
|
|
368
|
+
def isNodeInMaintenance(self) -> bool:
|
|
369
|
+
'''Returns True if the server's node is in maintenance mode.'''
|
|
370
|
+
return bool(self.refreshDetails().get('node_maintenance_enabled'))
|
|
371
|
+
|
|
372
|
+
def waitForState(self, state: str, timeout: int = 60, interval: int = 2) -> bool:
|
|
373
|
+
'''Blocks until the server reaches a power state. Returns False on timeout.'''
|
|
374
|
+
return _time.waitForState(self, state, timeout, interval)
|
|
375
|
+
|
|
376
|
+
def waitForOnline(self, timeout: int = 60, interval: int = 2) -> bool:
|
|
377
|
+
'''
|
|
378
|
+
Blocks until the server is running. Returns False on timeout.
|
|
379
|
+
|
|
380
|
+
Example:
|
|
381
|
+
server.start()
|
|
382
|
+
server.waitForOnline(timeout=120)
|
|
383
|
+
'''
|
|
384
|
+
return _time.waitForOnline(self, timeout, interval)
|
|
385
|
+
|
|
386
|
+
def waitForOffline(self, timeout: int = 60, interval: int = 2) -> bool:
|
|
387
|
+
'''Blocks until the server is offline. Returns False on timeout.'''
|
|
388
|
+
return _time.waitForOffline(self, timeout, interval)
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
# power actions
|
|
392
|
+
def powerAction(self, action: str) -> None:
|
|
393
|
+
'''
|
|
394
|
+
Executes a power action on the server. Valid actions:
|
|
395
|
+
- start
|
|
396
|
+
- restart
|
|
397
|
+
- stop
|
|
398
|
+
- kill
|
|
399
|
+
'''
|
|
400
|
+
if action not in POWER_ACTIONS:
|
|
401
|
+
raise InvalidActionError(f'Invalid power action "{action}", expected one of: {", ".join(POWER_ACTIONS)}')
|
|
402
|
+
return self.api.power(self.server_id, action)
|
|
403
|
+
|
|
404
|
+
def start(self) -> None:
|
|
405
|
+
'''Starts the server.'''
|
|
406
|
+
return self.powerAction('start')
|
|
407
|
+
|
|
408
|
+
def restart(self) -> None:
|
|
409
|
+
'''Restarts the server.'''
|
|
410
|
+
return self.powerAction('restart')
|
|
411
|
+
|
|
412
|
+
def stop(self) -> None:
|
|
413
|
+
'''Stops the server gracefully.'''
|
|
414
|
+
return self.powerAction('stop')
|
|
415
|
+
|
|
416
|
+
def kill(self) -> None:
|
|
417
|
+
'''Kills the server process immediately.'''
|
|
418
|
+
return self.powerAction('kill')
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
# console
|
|
422
|
+
def sendCommand(self, command: str) -> None:
|
|
423
|
+
'''
|
|
424
|
+
Sends a command to the server console.
|
|
425
|
+
|
|
426
|
+
Example:
|
|
427
|
+
server.sendCommand('say Hello')
|
|
428
|
+
'''
|
|
429
|
+
return self.api.command(self.server_id, command)
|
|
430
|
+
|
|
431
|
+
def sendCommands(self, commands: list) -> None:
|
|
432
|
+
'''Sends several commands in order.'''
|
|
433
|
+
for command in commands:
|
|
434
|
+
self.sendCommand(command)
|
|
435
|
+
|
|
436
|
+
def getLogs(self, lines: int = None) -> str:
|
|
437
|
+
'''
|
|
438
|
+
Returns the recent console output.
|
|
439
|
+
|
|
440
|
+
Args:
|
|
441
|
+
lines: Number of lines to return.
|
|
442
|
+
'''
|
|
443
|
+
return self.api.logs(self.server_id, lines)
|
|
444
|
+
|
|
445
|
+
def getWebsocket(self) -> dict:
|
|
446
|
+
'''
|
|
447
|
+
Returns credentials for the live console websocket.
|
|
448
|
+
|
|
449
|
+
You rarely need this, `server.console` handles the websocket for you.
|
|
450
|
+
|
|
451
|
+
Returns:
|
|
452
|
+
dict with `url` and `token`.
|
|
453
|
+
'''
|
|
454
|
+
return self.api.websocket(self.server_id)
|
|
455
|
+
|
|
456
|
+
def streamConsole(self, history: bool = True, timeout: float = None):
|
|
457
|
+
'''
|
|
458
|
+
Yields console lines live, as they are printed.
|
|
459
|
+
|
|
460
|
+
Args:
|
|
461
|
+
history: Start with the recent lines the daemon still has.
|
|
462
|
+
timeout: Stop when nothing is printed for this many seconds. None streams forever.
|
|
463
|
+
|
|
464
|
+
Example:
|
|
465
|
+
for line in server.streamConsole():
|
|
466
|
+
if 'joined the game' in line:
|
|
467
|
+
print(line)
|
|
468
|
+
'''
|
|
469
|
+
with Console(self) as console:
|
|
470
|
+
yield from console.lines(history, timeout)
|
|
471
|
+
|
|
472
|
+
def onConsoleLine(self, callback, history: bool = False) -> Console:
|
|
473
|
+
'''
|
|
474
|
+
Calls `callback(line)` for every console line, in a background thread.
|
|
475
|
+
|
|
476
|
+
Reconnects automatically. Call `server.console.stop()` to end it.
|
|
477
|
+
|
|
478
|
+
Example:
|
|
479
|
+
server.onConsoleLine(print)
|
|
480
|
+
'''
|
|
481
|
+
self.console.onConsoleLine(callback)
|
|
482
|
+
return self.console.listen(history=history)
|
|
483
|
+
|
|
484
|
+
def getActivity(self, search: str = None, user=None, page: int = 1, per_page: int = 50):
|
|
485
|
+
'''
|
|
486
|
+
Returns the server activity log as a Page of Activity objects.
|
|
487
|
+
|
|
488
|
+
Args:
|
|
489
|
+
user: Only show events of this user (UUID).
|
|
490
|
+
page: Page number (1 = newest). Pass None for every entry.
|
|
491
|
+
'''
|
|
492
|
+
return self.api.activity(self.server_id, idOf(user), search, page, per_page).map(
|
|
493
|
+
lambda data: Activity(self.panel, data)
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
def getAnnouncements(self) -> list:
|
|
497
|
+
'''Returns active panel announcements relevant to this server.'''
|
|
498
|
+
return self.api.announcements(self.server_id)
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
# settings
|
|
502
|
+
def rename(self, name: str = None, description: str = None) -> 'GenericServer':
|
|
503
|
+
'''Changes the server name and/or description.'''
|
|
504
|
+
self.api.rename(self.server_id, name, description)
|
|
505
|
+
if name:
|
|
506
|
+
self.name = name
|
|
507
|
+
return self._update({'name': name, 'description': description})
|
|
508
|
+
|
|
509
|
+
def setName(self, name: str) -> 'GenericServer':
|
|
510
|
+
'''Changes the server name.'''
|
|
511
|
+
return self.rename(name=name)
|
|
512
|
+
|
|
513
|
+
def setDescription(self, description: str) -> 'GenericServer':
|
|
514
|
+
'''Changes the server description.'''
|
|
515
|
+
return self.rename(description=description)
|
|
516
|
+
|
|
517
|
+
def setTimezone(self, timezone: str) -> 'GenericServer':
|
|
518
|
+
'''Sets the server timezone, e.g. "Europe/Berlin". None resets to the node default.'''
|
|
519
|
+
self.api.setTimezone(self.server_id, timezone)
|
|
520
|
+
self.data['timezone'] = timezone
|
|
521
|
+
return self
|
|
522
|
+
|
|
523
|
+
def setAutoKill(self, enabled: bool, seconds: int = None) -> None:
|
|
524
|
+
'''
|
|
525
|
+
Configures auto-kill: kill the server if it takes too long to stop.
|
|
526
|
+
|
|
527
|
+
Args:
|
|
528
|
+
enabled: Turn auto-kill on or off.
|
|
529
|
+
seconds: Grace period before killing.
|
|
530
|
+
'''
|
|
531
|
+
return self.api.setAutoKill(self.server_id, enabled, seconds)
|
|
532
|
+
|
|
533
|
+
def setAutoStart(self, behavior: str) -> None:
|
|
534
|
+
'''Sets auto-start after node reboots: "always", "unless_stopped" or "never".'''
|
|
535
|
+
return self.api.setAutoStart(self.server_id, behavior)
|
|
536
|
+
|
|
537
|
+
def reinstall(self, truncate_directory: bool = False, start_on_completion: bool = None) -> None:
|
|
538
|
+
'''
|
|
539
|
+
Reinstalls the server using its egg's install script.
|
|
540
|
+
|
|
541
|
+
Args:
|
|
542
|
+
truncate_directory: Delete every file first.
|
|
543
|
+
start_on_completion: Start the server once installed.
|
|
544
|
+
'''
|
|
545
|
+
return self.api.reinstall(self.server_id, truncate_directory, start_on_completion)
|
|
546
|
+
|
|
547
|
+
def cancelInstall(self) -> None:
|
|
548
|
+
'''Cancels a running (re)install.'''
|
|
549
|
+
return self.api.cancelInstall(self.server_id)
|
|
550
|
+
|
|
551
|
+
def unlockInstall(self) -> None:
|
|
552
|
+
'''Clears a failed install state so the server can be used again.'''
|
|
553
|
+
return self.api.unlockInstall(self.server_id)
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
# startup variables
|
|
557
|
+
def getStartupVariables(self) -> list:
|
|
558
|
+
'''
|
|
559
|
+
Returns the startup variables.
|
|
560
|
+
|
|
561
|
+
Each variable is a dict with name, env_variable, value, default_value,
|
|
562
|
+
is_editable, is_secret and rules.
|
|
563
|
+
'''
|
|
564
|
+
return self.api.startupVariables(self.server_id)
|
|
565
|
+
|
|
566
|
+
def getStartupVariable(self, key: str) -> str:
|
|
567
|
+
'''
|
|
568
|
+
Returns the value of one startup variable.
|
|
569
|
+
|
|
570
|
+
Example:
|
|
571
|
+
server.getStartupVariable('SERVER_JARFILE')
|
|
572
|
+
'''
|
|
573
|
+
for variable in self.getStartupVariables():
|
|
574
|
+
if variable.get('env_variable') == key:
|
|
575
|
+
return variable.get('value')
|
|
576
|
+
return None
|
|
577
|
+
|
|
578
|
+
def updateStartupVariable(self, key: str, value: str) -> None:
|
|
579
|
+
'''
|
|
580
|
+
Changes a startup variable.
|
|
581
|
+
|
|
582
|
+
Example:
|
|
583
|
+
server.updateStartupVariable('MINECRAFT_VERSION', '1.21.1')
|
|
584
|
+
'''
|
|
585
|
+
return self.api.updateStartupVariables(self.server_id, [{'env_variable': key, 'value': str(value)}])
|
|
586
|
+
|
|
587
|
+
def updateStartupVariables(self, variables: dict) -> None:
|
|
588
|
+
'''Changes several startup variables at once, given as {env_variable: value}.'''
|
|
589
|
+
return self.api.updateStartupVariables(
|
|
590
|
+
self.server_id, [{'env_variable': k, 'value': str(v)} for k, v in variables.items()]
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
def setStartupCommand(self, command: str) -> None:
|
|
594
|
+
'''Changes the startup command.'''
|
|
595
|
+
return self.api.setStartupCommand(self.server_id, command)
|
|
596
|
+
|
|
597
|
+
def setDockerImage(self, image: str) -> None:
|
|
598
|
+
'''Changes the docker image, see getDockerImages() for allowed values.'''
|
|
599
|
+
return self.api.setDockerImage(self.server_id, image)
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
# mounts and devices
|
|
603
|
+
def getMounts(self, search: str = None, page: int = None, per_page: int = None):
|
|
604
|
+
'''Returns the mounts available to this server.'''
|
|
605
|
+
return self.panel.api.attachments.mounts(self.server_id, search, page, per_page)
|
|
606
|
+
|
|
607
|
+
def attachMount(self, mount) -> None:
|
|
608
|
+
'''Attaches a mount (UUID or dict) to the server.'''
|
|
609
|
+
return self.panel.api.attachments.attachMount(self.server_id, _uuid(mount))
|
|
610
|
+
|
|
611
|
+
def detachMount(self, mount) -> None:
|
|
612
|
+
'''Detaches a mount from the server.'''
|
|
613
|
+
return self.panel.api.attachments.detachMount(self.server_id, _uuid(mount))
|
|
614
|
+
|
|
615
|
+
def getDevices(self, search: str = None, page: int = None, per_page: int = None):
|
|
616
|
+
'''Returns the devices available to this server.'''
|
|
617
|
+
return self.panel.api.attachments.devices(self.server_id, search, page, per_page)
|
|
618
|
+
|
|
619
|
+
def attachDevice(self, device) -> None:
|
|
620
|
+
'''Attaches a device (UUID or dict) to the server.'''
|
|
621
|
+
return self.panel.api.attachments.attachDevice(self.server_id, _uuid(device))
|
|
622
|
+
|
|
623
|
+
def detachDevice(self, device) -> None:
|
|
624
|
+
'''Detaches a device from the server.'''
|
|
625
|
+
return self.panel.api.attachments.detachDevice(self.server_id, _uuid(device))
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
# file manager shortcuts
|
|
629
|
+
def listFiles(self, directory: str = '/'):
|
|
630
|
+
'''Lists a directory. See `server.files.listFiles()`.'''
|
|
631
|
+
return self.files.listFiles(directory)
|
|
632
|
+
|
|
633
|
+
def readFile(self, path: str) -> str:
|
|
634
|
+
'''Returns a text file's contents. See `server.files.readFile()`.'''
|
|
635
|
+
return self.files.readFile(path)
|
|
636
|
+
|
|
637
|
+
def writeFile(self, path: str, content) -> None:
|
|
638
|
+
'''Creates or replaces a file. See `server.files.writeFile()`.'''
|
|
639
|
+
return self.files.writeFile(path, content)
|
|
640
|
+
|
|
641
|
+
def downloadFile(self, path: str, destination: str = None):
|
|
642
|
+
'''Downloads a file. See `server.files.downloadFile()`.'''
|
|
643
|
+
return self.files.downloadFile(path, destination)
|
|
644
|
+
|
|
645
|
+
def uploadFile(self, directory: str, file_data, name: str = None):
|
|
646
|
+
'''Uploads a file. See `server.files.uploadFile()`.'''
|
|
647
|
+
return self.files.uploadFile(directory, file_data, name)
|
|
648
|
+
|
|
649
|
+
def deleteFiles(self, root: str, files: list) -> None:
|
|
650
|
+
'''Deletes files from a directory. See `server.files.deleteFiles()`.'''
|
|
651
|
+
return self.files.deleteFiles(root, files)
|
|
652
|
+
|
|
653
|
+
def renameFiles(self, root: str, files: list) -> None:
|
|
654
|
+
'''Renames files. See `server.files.renameFiles()`.'''
|
|
655
|
+
return self.files.renameFiles(root, files)
|
|
656
|
+
|
|
657
|
+
def copyFiles(self, root: str, files: list):
|
|
658
|
+
'''Copies files. See `server.files.copyFiles()`.'''
|
|
659
|
+
return self.files.copyFiles(root, files)
|
|
660
|
+
|
|
661
|
+
def moveFiles(self, root: str, files: list) -> None:
|
|
662
|
+
'''Moves files. See `server.files.moveFiles()`.'''
|
|
663
|
+
return self.files.moveFiles(root, files)
|
|
664
|
+
|
|
665
|
+
def createFolder(self, directory: str, name: str) -> None:
|
|
666
|
+
'''Creates a directory. See `server.files.createFolder()`.'''
|
|
667
|
+
return self.files.createFolder(directory, name)
|
|
668
|
+
|
|
669
|
+
def compress(self, root: str, files: list, format: str = 'tar_gz', name: str = None):
|
|
670
|
+
'''Compresses files into an archive. See `server.files.compress()`.'''
|
|
671
|
+
return self.files.compress(root, files, format, name)
|
|
672
|
+
|
|
673
|
+
def decompress(self, file_path: str) -> None:
|
|
674
|
+
'''Extracts an archive. See `server.files.decompress()`.'''
|
|
675
|
+
return self.files.decompress(file_path)
|
|
676
|
+
|
|
677
|
+
|
|
678
|
+
# backup shortcuts
|
|
679
|
+
def listBackups(self):
|
|
680
|
+
'''Returns every backup. See `server.backups.getBackups()`.'''
|
|
681
|
+
return self.backups.getBackups()
|
|
682
|
+
|
|
683
|
+
def getBackups(self, search: str = None):
|
|
684
|
+
'''Returns every backup. See `server.backups.getBackups()`.'''
|
|
685
|
+
return self.backups.getBackups(search)
|
|
686
|
+
|
|
687
|
+
def getBackup(self, backup_id: str):
|
|
688
|
+
'''Returns one backup. See `server.backups.getBackup()`.'''
|
|
689
|
+
return self.backups.getBackup(backup_id)
|
|
690
|
+
|
|
691
|
+
def createBackup(self, name: str = None, ignored_files: list = None, group=None):
|
|
692
|
+
'''
|
|
693
|
+
Starts a new backup and returns it.
|
|
694
|
+
|
|
695
|
+
Example:
|
|
696
|
+
backup = server.createBackup('before-update')
|
|
697
|
+
backup.waitUntilComplete()
|
|
698
|
+
'''
|
|
699
|
+
return self.backups.createBackup(name, ignored_files, group)
|
|
700
|
+
|
|
701
|
+
def deleteBackup(self, backup_id: str) -> None:
|
|
702
|
+
'''Deletes a backup.'''
|
|
703
|
+
return self.backups.deleteBackup(backup_id)
|
|
704
|
+
|
|
705
|
+
def restoreBackup(self, backup_id: str, truncate_directory: bool = False) -> None:
|
|
706
|
+
'''Restores a backup. See `server.backups.restoreBackup()`.'''
|
|
707
|
+
return self.backups.restoreBackup(backup_id, truncate_directory)
|
|
708
|
+
|
|
709
|
+
def downloadBackup(self, backup_id: str, destination: str = None):
|
|
710
|
+
'''Downloads a backup. See `server.backups.downloadBackup()`.'''
|
|
711
|
+
return self.backups.downloadBackup(backup_id, destination)
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
# database shortcuts
|
|
715
|
+
def listDatabases(self):
|
|
716
|
+
'''Returns every database. See `server.databases.getDatabases()`.'''
|
|
717
|
+
return self.databases.getDatabases()
|
|
718
|
+
|
|
719
|
+
def getDatabases(self, include_password: bool = False):
|
|
720
|
+
'''Returns every database. See `server.databases.getDatabases()`.'''
|
|
721
|
+
return self.databases.getDatabases(include_password=include_password)
|
|
722
|
+
|
|
723
|
+
def createDatabase(self, name: str, host=None):
|
|
724
|
+
'''Creates a database. See `server.databases.createDatabase()`.'''
|
|
725
|
+
return self.databases.createDatabase(name, host)
|
|
726
|
+
|
|
727
|
+
def rotateDatabasePassword(self, db_id: str) -> str:
|
|
728
|
+
'''Generates a new database password and returns it.'''
|
|
729
|
+
return self.databases.rotateDatabasePassword(db_id)
|
|
730
|
+
|
|
731
|
+
def deleteDatabase(self, db_id: str) -> None:
|
|
732
|
+
'''Deletes a database.'''
|
|
733
|
+
return self.databases.deleteDatabase(db_id)
|
|
734
|
+
|
|
735
|
+
|
|
736
|
+
# schedule shortcuts
|
|
737
|
+
def listSchedules(self):
|
|
738
|
+
'''Returns every schedule. See `server.schedules.getSchedules()`.'''
|
|
739
|
+
return self.schedules.getSchedules()
|
|
740
|
+
|
|
741
|
+
def getSchedules(self):
|
|
742
|
+
'''Returns every schedule. See `server.schedules.getSchedules()`.'''
|
|
743
|
+
return self.schedules.getSchedules()
|
|
744
|
+
|
|
745
|
+
def getSchedule(self, schedule_id: str):
|
|
746
|
+
'''Returns a schedule by UUID or name. See `server.schedules.getSchedule()`.'''
|
|
747
|
+
return self.schedules.getSchedule(schedule_id)
|
|
748
|
+
|
|
749
|
+
def createSchedule(self, name: str, triggers: list = None, condition: dict = None, steps: list = None, enabled: bool = True):
|
|
750
|
+
'''Creates a schedule. See `server.schedules.createSchedule()`.'''
|
|
751
|
+
return self.schedules.createSchedule(name, triggers, condition, steps, enabled)
|
|
752
|
+
|
|
753
|
+
def updateSchedule(self, schedule_id: str, **fields) -> None:
|
|
754
|
+
'''Updates a schedule (name, enabled, triggers, condition).'''
|
|
755
|
+
return self.schedules.updateSchedule(schedule_id, **fields)
|
|
756
|
+
|
|
757
|
+
def deleteSchedule(self, schedule_id: str) -> None:
|
|
758
|
+
'''Deletes a schedule.'''
|
|
759
|
+
return self.schedules.deleteSchedule(schedule_id)
|
|
760
|
+
|
|
761
|
+
def runSchedule(self, schedule_id: str) -> None:
|
|
762
|
+
'''Runs a schedule now.'''
|
|
763
|
+
return self.schedules.runSchedule(schedule_id)
|
|
764
|
+
|
|
765
|
+
def enableSchedule(self, schedule_id: str) -> None:
|
|
766
|
+
'''Enables a schedule.'''
|
|
767
|
+
return self.schedules.enableSchedule(schedule_id)
|
|
768
|
+
|
|
769
|
+
def disableSchedule(self, schedule_id: str) -> None:
|
|
770
|
+
'''Disables a schedule.'''
|
|
771
|
+
return self.schedules.disableSchedule(schedule_id)
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
# other shortcuts
|
|
775
|
+
def getSubusers(self):
|
|
776
|
+
'''Returns every subuser. See `server.users.getSubusers()`.'''
|
|
777
|
+
return self.users.getSubusers()
|
|
778
|
+
|
|
779
|
+
def getAllocations(self):
|
|
780
|
+
'''Returns every allocation. See `server.network.getAllocations()`.'''
|
|
781
|
+
return self.network.getAllocations()
|
|
782
|
+
|
|
783
|
+
|
|
784
|
+
def _uuid(value):
|
|
785
|
+
return value.get('uuid') if isinstance(value, dict) else idOf(value)
|