forked from notmike-uk/container
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontainer.lua
More file actions
executable file
·401 lines (358 loc) · 10.7 KB
/
container.lua
File metadata and controls
executable file
·401 lines (358 loc) · 10.7 KB
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
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
---Global API functions.
--@topic Global_Environment
math.randomseed(os.time() + (os.clock()*1000000))
---Execute command.
--@param command string.
--@param capture_output boolean return stdout instead of true/false.
--@return boolean success true/false.
function exec(command, capture_output)
debug_print('exec', command)
--capture output
if capture_output then
local f = assert(io.popen(command, 'r'))
local output = f:read('*a')
f:close()
debug_print('exec_ret', output)
return output
end
--execute normally
local ret = os.execute(command)
retval = ret
debug_print('exec_ret', retval)
return ret
end
---Execute command, terminate on failure.
--@param command string.
--@return boolean true.
function exec_or_die(command)
debug_print('exec_or_die', command)
local ret = exec(command)
if not ret then die("Error Executing " .. command) end
return ret
end
---Install package using apt.
--@param name string.
function install_package(name)
debug_print('install_package', 'install_package("' .. name .. '")')
exec_or_die("RUNLEVEL=1 DEBIAN_FRONTEND=noninteractive apt-get install -qy --force-yes " .. name)
return 0
end
---Terminate execution.
--@param reason string.
function die(reason)
print(reason .. "\r\n")
os.exit(1)
end
local filesystems = {}
---Mount point.
--@param type string Type of mount, "map" or "tmpfs".
--@param path string Target path in container.
--@param source string For type 'map', specifies the source directory on the host.
--@param size string For type 'tmpfs', specifies the size of the mount.
--@table mount
---Mount Filesystem.
--@see mount
--@param mount mount
function Mount(mount)
filesystems[mount.path] = mount
return 0
end
Mount{ path="/tmp", type="tmpfs", size="2G" }
Mount{ path="/run", type="tmpfs", size="128M" }
include = require
---Load external file
function require(modulename)
debug_print('require', modulename)
include(modulename)
FIX_ENVIRONMENT(modulename)
end
---Read file contents.
--@param filename string.
--@return string
function read_file(filename)
debug_print('read_file', filename)
local contents = ""
file = io.open(filename, "r")
if not file then return nil end
io.input(file)
while true do
local block = io.read(1024*1024)
if not block then break end
contents = contents .. block
end
io.close(file)
return contents
end
---Write to a file.
--@param filename string.
--@param contents string.
--@return 0
function write_file(filename, contents)
debug_print('write_file', filename .. "\n" .. contents)
file = io.open(filename, "w")
if not file then return 1 end
io.output(file)
io.write(contents)
io.close(file)
return 0
end
---Check if file exists.
--@param name string.
--@return boolean
function exists(name)
if type(name)~="string" then return false end
return os.rename(name,name) and true or false
end
---Check if file is a file.
--@param name string.
--@return boolean
function isFile(name)
if type(name)~="string" then return false end
if not exists(name) then return false end
local f = io.open(name)
if f then
f:close()
return true
end
return false
end
---Check if file is a directory.
--@param name string.
--@return boolean
function isDir(name)
return (exists(name) and not isFile(name))
end
---Get the path to a file.
--@param name string.
--@return boolean
function dirname(name)
index = name:match("^.*()/")
return name:sub(0,index)
end
local debug_table=nil
---Enable debugging.
--@param filter string Function to debug, nil for all.
function enable_debug(filter)
if not debug_table then debug_table = {} end
if not filter then filter = 'all' end
debug_table[filter] = true
end
function debug_print(filter, ...)
if debug_table and ( debug_table[filter] or debug_table['all'] ) then
print(filter, ...)
end
end
---Callbacks.
--Define these functions to define behaviour that should be executed at the appropriate time.
--If the same function is defined in multiple files then all will be executed.
--
--Callbacks should always return 0 on success.
--@section callbacks
---Callback executed to install container applications.
function install_container()
debug_print("install_container", 'EXEC')
return 0
end
---Callback executed to launch container applications.
function run()
debug_print("run", 'EXEC')
return 0
end
---Callback launched as daemon when container has launched.
function background()
return 0
end
local nameservers=nil
---Callback executed to write configuration files.
function apply_config()
debug_print('apply_config', 'configure nameservers')
if not nameservers then
local file = io.open("/etc/resolv.conf", "r")
if file then
nameservers={}
io.input(file)
local line = io.read("*line")
while line do
local server = line:match("nameserver%s*(%g*)")
if server == "::1" then server = "2001:4860:4860::8844" end
if server == "127.0.0.1" then server = "8.8.4.4" end
if server then table.insert(nameservers, server) end
line = io.read("*line")
end
io.close(file)
end
end
if nameservers then
local resolvconf = ""
for _,server in pairs(nameservers) do
resolvconf = resolvconf .. "nameserver " .. server .. "\n"
end
if resolvconf then write_file("etc/resolv.conf", resolvconf) end
end
return 0
end
---Callback executed to test if container neets an isolated network
--@return 1 to initialise networking, 0 otherwise
function init_network_needed()
return 0
end
---Callback executed to initialise host networking.
--@param pid int PID of container.
function init_network_host(pid)
debug_print("init_network_host", 'EXEC')
return 0
end
---Callback executed to initialise child networking.
function init_network_child()
debug_print("init_network_child", 'EXEC')
return 0
end
function shell()
print("Launching shell.")
exec("sh")
return 0
end
function need_build()
if not isFile(base_path .. ".built") then return 1 end
return 0
end
debian = {}
debian.mirror = "http://deb.debian.org/debian";
debian.arch = exec("uname -m", true)
debian.arch = string.gsub(debian.arch, "\n", "")
if debian.arch:find("x86_64") then debian.arch = "amd64" end
if debian.arch:find("i686") then debian.arch = "i386" end
debian.cache_file = "/usr/local/container/debian." .. debian.arch .. ".tar.gz";
function build()
debug_print("build", "EXEC")
if not isFile(debian.cache_file) then
print("Building debian cache...")
mkdir("../.debootstrap")
chdir("../.debootstrap")
exec_or_die("debootstrap --include=iproute2,net-tools,wget stable . " .. debian.mirror)
if isFile("etc/debian_version") then
print("Saving cache...")
exec_or_die("tar --exclude='dev' --exclude='sys' --exclude='proc' -zcf ../.debian.cache *")
exec_or_die("rm -f /var/cache/debian.cache && mv ../.debian.cache " .. debian.cache_file)
end
chdir("../.jail")
exec_or_die("rm -rf ../.debootstrap")
end
print("Installing debian from cache...")
exec("tar -zxf " .. debian.cache_file)
if not isFile("etc/debian_version") then die("Error extracting debian image.") end
print("Updating...")
exec_or_die("chroot . apt-get update; chroot . apt-get -y dist-upgrade")
print("Debian Installed.")
old_exec = exec
exec = function (cmd) return old_exec("chroot . sh -c '" .. cmd .. "'") end
local ret = install_container()
exec = old_exec
write_file("../.built", "")
return ret
end
function pairsByKeys (t, f)
local a = {}
for n in pairs(t) do table.insert(a, n) end
table.sort(a, f)
local i = 0 -- iterator variable
local iter = function () -- iterator function
i = i + 1
if a[i] == nil then return nil
else return a[i], t[a[i]]
end
end
return iter
end
function mount_container()
debug_print('mount_container', 'EXEC')
exec_or_die("mkdir -p .jail && mkdir -p .filesystem && mount -n --make-rprivate --make-private -o rw --bind .filesystem .jail")
exec_or_die("mkdir -p .jail/proc && mount -t proc proc .jail/proc")
exec_or_die("mkdir -p .jail/sys && mount -t sysfs /sys .jail/sys")
exec_or_die("mkdir -p .jail/dev && mount -t devtmpfs udev .jail/dev")
exec_or_die("mkdir -p .jail/dev/pts && mount -t devpts devpts .jail/dev/pts")
for target, mount in pairsByKeys(filesystems) do
debug_print('mount_container', mount['type'] .. ':' .. target)
if mount['type'] == "tmpfs" then
if not isDir(".jail/" .. target) then
exec("mkdir -p .jail/" .. target)
end
mount_opts = "-n "
if mount['size'] then
mount_opts = mount_opts .. "-o size=" .. mount['size'] .. " "
end
exec_or_die("mount " .. mount_opts .. "-t tmpfs tmp" .. string.sub(tostring(mount),10) .. " .jail/" .. target)
elseif mount['type'] == "map" then
if not mount['source'] then die("No source specified for " .. target) end
if not isDir(".jail/" .. target) then
exec("mkdir -p .jail/" .. target)
end
exec("mkdir -p " .. mount['source']);
exec_or_die("mount -n --make-rprivate --make-private --bind " .. mount['source'] .. " .jail/" .. target)
end
end
return 0
end
function unmount_container()
debug_print('unmount_container', "EXEC")
exec("umount -l -R " .. base_path .. "/.jail 2>&1", true)
return 0
end
function lock_container()
debug_print('lock_container', "EXEC")
exec_or_die("mount -n -o remount,ro --bind / /")
return 0
end
function table.clone(org)
local new = {}
for name, value in pairs(org) do
new[name] = value
end
return new
end
local daemons={}
function start_daemons()
debug_print('start_daemons', daemons)
for _, daemon in pairs(daemons) do
if fork() == 0 then
debug_print('start_daemons', daemon.mod)
local ret = daemon.func();
debug_print('start_daemons', daemon.mod .. ' returned ' .. ret)
os.exit(0)
end
end
debug_print('start_daemons', "Started.")
return 0
end
function prepend_functions(target, source, modulename)
debug_print('prepend_functions', modulename)
for name, sourcefunc in pairs(source) do
if (name == 'background' and type(target[name]) == "function") and target[name] ~= source[name] then
debug_print('prepend_functions', modulename .. ': Daemon.')
table.insert(daemons, {mod=modulename, func=target[name]})
target[name]=source[name]
elseif type(source[name]) == "function" and type(target[name]) == "function" and source[name] ~= target[name] then
local previousfunc = target[name]
target[name] = function (...)
local ret = sourcefunc(...)
if ret ~= 0 then return ret end
debug_print(name, "Calling from " .. modulename)
ret = previousfunc(...)
debug_print(name, "Module " .. modulename .. " returned " .. ret)
return ret
end
debug_print('prepend_functions', modulename .. ': ' .. name)
end
end
end
---Fix Global Functions.
--Internal function used to chain functions together.
--@param modulename string.
local DEFAULT_ENVIRONMENT = table.clone(_ENV)
function FIX_ENVIRONMENT(modulename)
debug_print('FIX_ENVIRONMENT', modulename)
if not modulename then modulename = 'container' end
prepend_functions(_ENV, DEFAULT_ENVIRONMENT, modulename)
DEFAULT_ENVIRONMENT = table.clone(_ENV)
return 0
end