-
Notifications
You must be signed in to change notification settings - Fork 1
/
server.lua
319 lines (261 loc) · 7.78 KB
/
server.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
local thisResource = GetCurrentResourceName()
-- Internal resources that should never be shown
local internalResources = {
["_cfx_internal"] = true
}
-- Default resources that should be hidden by default
local defaultResources = {
["fivem"] = true,
["hardcap"] = true,
["chat"] = true,
["example-loadscreen"] = true,
["baseevents"] = true,
["basic-gamemode"] = true,
["chat-theme-gtao"] = true,
["fivem-map-hipster"] = true,
["fivem-map-skater"] = true,
["harcap"] = true,
["mapmanager"] = true,
["money"] = true,
["money-fountain"] = true,
["money-fountain-example-map"] = true,
["monitor"] = true,
["ped-money-drops"] = true,
["player-data"] = true,
["playernames"] = true,
["rconlog"] = true,
["redm-map-one"] = true,
["runcode"] = true,
["sessionmanager"] = true,
["sessionmanager-rdr3"] = true,
["spawnmanager"] = true,
["webadmin"] = true,
["webpack"] = true,
["yarn"] = true
}
-- Metadata to fetch for resources
local metadataNames = {
"name",
"version",
"author",
"description",
"url",
"repository"
}
-- Get all dependencies for a resource, and all dependencies for those
-- dependencies,and so on.
local function getDependencies(resource, dependencies)
if not dependencies then
dependencies = {}
end
for i = 0, GetNumResourceMetadata(resource, "dependency") - 1 do
local dep = GetResourceMetadata(resource, "dependency", i)
local traverse = not dependencies[dep]
dependencies[dep] = true
if traverse then
getDependencies(dep, dependencies)
end
end
return dependencies
end
-- Get all resources that depend on a resource.
local function getDependants(resource)
local dependants = {}
for i = 0, GetNumResources() - 1 do
local otherResource = GetResourceByFindIndex(i)
if getDependencies(otherResource)[resource] then
dependants[otherResource] = true
end
end
return dependants
end
-- Get the full chain of dependencies for resourcemanager. Stopping a
-- dependency anywhere in the chain can cause the server to crash!
local dependencies = getDependencies(thisResource)
-- Determine if resource can be omitted from the list
local function ignoreResource(resource)
return resource == thisResource or internalResources[resource] or dependencies[resource]
end
-- Check if the number of arguments is what was expected
local function argCountMatches(args, wanted)
if #args == wanted then
return true
else
print(("Argument count mismatch (passed %d, wanted %d)"):format(#args, wanted))
return false
end
end
-- Detect if a resource is part of a circular dependency.
local function hasCircularDependency(resource)
return getDependencies(resource)[resource] == true
end
-- Stop a resource, ignoring those which are part of a circular dependency
local function safelyStopResource(resource)
if dependencies[resource] then
print(("%s is a dependency of %s and cannot be stopped by it."):format(resource, thisResource))
return false
elseif hasCircularDependency(resource) then
print(("Circular dependency detected. Stopping %s would cause the server to crash!"):format(resource))
return false
else
StopResource(resource)
return true
end
end
-- Restart a resource, ignoring those which are part of a circular dependency
local function safelyRestartResource(resource)
if dependencies[resource] then
print(("%s is a dependency of %s and cannot be restarted by it."):format(resource, thisResource))
return false
elseif hasCircularDependency(resource) then
print(("Circular dependency detected. Restarting %s would cause the server to crash!"):format(resource))
return false
else
StopResource(resource)
StartResource(resource)
return true
end
end
exports("safelyStopResource", safelyStopResource)
exports("safelyRestartResource", safelyRestartResource)
-- Restart a resource and all resources that depend on it automatically
RegisterCommand("restart_all", function(source, args, raw)
if not argCountMatches(args, 1) then
return
end
local targetResource = args[1]
local resources = {}
for i = 0, GetNumResources() - 1 do
local resource = GetResourceByFindIndex(i)
if GetResourceState(resource) == "started" then
table.insert(resources, resource)
end
end
if not safelyStopResource(targetResource) then
return
end
for _, resource in ipairs(resources) do
local deps = getDependencies(resource)
if deps[targetResource] then
StartResource(resource)
end
end
end, true)
-- Stop a resource, ignoring those in circular dependencies.
RegisterCommand("safe_stop", function(source, args, raw)
if not argCountMatches(args, 1) then
return
end
local resource = args[1]
if GetResourceState(resource) == "started" then
safelyStopResource(args[1])
end
end, true)
-- Restart a resource, ignoring those in circular dependencies.
RegisterCommand("safe_restart", function(source, args, raw)
if not argCountMatches(args, 1) then
return
end
local resource = args[1]
if GetResourceState(resource) == "stopped" then
safelyRestartResource(resource)
end
end, true)
-- Ensure a resource, ignoring those in circular dependencies.
RegisterCommand("safe_ensure", function(source, args, raw)
if not argCountMatches(args, 1) then
return
end
safelyRestartResource(args[1])
end, true)
-- List all dependencies (direct and indirect) of a resource.
RegisterCommand("list_dependencies", function(source, args, raw)
if not argCountMatches(args, 1) then
return
end
for dependency, _ in pairs(getDependencies(args[1])) do
print(dependency)
end
end, true)
-- List all resources that depend on a resource (directly or indirectly).
RegisterCommand("list_dependants", function(source, args, raw)
if not argCountMatches(args, 1) then
return
end
for dependant, _ in pairs(getDependants(args[1])) do
print(dependant)
end
end, true)
-- Start/stop managed resources
Citizen.CreateThread(function()
local onStartup = true
while true do
local time = os.date("*t")
for resource, condition in pairs(config.managedResources) do
local start = condition(onStartup, time)
if start == true then
if GetResourceState(resource) == "stopped" then
print("Starting resource " .. resource)
StartResource(resource)
end
elseif start == false then
if GetResourceState(resource) == "started" then
print("Stopping resource " .. resource)
safelyStopResource(resource)
end
end
end
onStartup = false
Citizen.Wait(60000)
end
end)
SetHttpHandler(exports.httpmanager:createHttpHandler{
authorization = config.authorization,
routes = {
["^/resources$"] = function(req, res, helpers)
local resources = {}
for i = 0, GetNumResources() - 1 do
local resourceName = GetResourceByFindIndex(i)
if not ignoreResource(resourceName) then
local metadata = {}
for _, metadataName in ipairs(metadataNames) do
local values = {}
for i = 0, GetNumResourceMetadata(resourceName, metadataName) - 1 do
table.insert(values, GetResourceMetadata(resourceName, metadataName, i))
end
if #values < 2 then
metadata[metadataName] = values[1]
else
metadata[metadataName] = values
end
end
table.insert(resources, {
name = resourceName,
path = GetResourcePath(resourceName),
state = GetResourceState(resourceName),
metadata = metadata,
isDefaultResource = defaultResources[resourceName],
isManagedResource = config.managedResources[resourceName]
})
end
end
res.sendJson(resources)
end,
["^/start/(.+)$"] = function(req, res, helpers, resource)
StartResource(resource)
res.sendJson{}
end,
["^/stop/(.+)$"] = function(req, res, helpers, resource)
safelyStopResource(resource)
res.sendJson{}
end,
["^/restart/(.+)$"] = function(req, res, helpers, resource)
safelyRestartResource(resource)
res.sendJson{}
end,
["^/refresh$"] = function(req, res, helpers)
ExecuteCommand("refresh")
res.sendJson{}
end
}
})