modules/admin-full: reuse sysupgrade facilities to create backup archive
[project/luci.git] / modules / admin-full / luasrc / controller / admin / system.lua
1 --[[
2 LuCI - Lua Configuration Interface
3
4 Copyright 2008 Steven Barth <steven@midlink.org>
5 Copyright 2008-2011 Jo-Philipp Wich <xm@subsignal.org>
6
7 Licensed under the Apache License, Version 2.0 (the "License");
8 you may not use this file except in compliance with the License.
9 You may obtain a copy of the License at
10
11 http://www.apache.org/licenses/LICENSE-2.0
12
13 $Id$
14 ]]--
15
16 module("luci.controller.admin.system", package.seeall)
17
18 function index()
19 entry({"admin", "system"}, alias("admin", "system", "system"), _("System"), 30).index = true
20 entry({"admin", "system", "system"}, cbi("admin_system/system"), _("System"), 1)
21 entry({"admin", "system", "clock_status"}, call("action_clock_status"))
22
23 entry({"admin", "system", "admin"}, cbi("admin_system/admin"), _("Administration"), 2)
24
25 if nixio.fs.access("/bin/opkg") then
26 entry({"admin", "system", "packages"}, call("action_packages"), _("Software"), 10)
27 entry({"admin", "system", "packages", "ipkg"}, form("admin_system/ipkg"))
28 end
29
30 entry({"admin", "system", "startup"}, form("admin_system/startup"), _("Startup"), 45)
31 entry({"admin", "system", "crontab"}, form("admin_system/crontab"), _("Scheduled Tasks"), 46)
32
33 if nixio.fs.access("/etc/config/fstab") then
34 entry({"admin", "system", "fstab"}, cbi("admin_system/fstab"), _("Mount Points"), 50)
35 entry({"admin", "system", "fstab", "mount"}, cbi("admin_system/fstab/mount"), nil).leaf = true
36 entry({"admin", "system", "fstab", "swap"}, cbi("admin_system/fstab/swap"), nil).leaf = true
37 end
38
39 if nixio.fs.access("/sys/class/leds") then
40 entry({"admin", "system", "leds"}, cbi("admin_system/leds"), _("<abbr title=\"Light Emitting Diode\">LED</abbr> Configuration"), 60)
41 end
42
43 entry({"admin", "system", "flashops"}, call("action_flashops"), _("Backup / Flash Firmware"), 70)
44 entry({"admin", "system", "flashops", "backupfiles"}, form("admin_system/backupfiles"))
45
46 entry({"admin", "system", "reboot"}, call("action_reboot"), _("Reboot"), 90)
47 end
48
49 function action_clock_status()
50 local set = tonumber(luci.http.formvalue("set"))
51 if set ~= nil and set > 0 then
52 local date = os.date("*t", set)
53 if date then
54 -- prevent session timeoutby updating mtime
55 nixio.fs.utimes(luci.sauth.sessionpath .. "/" .. luci.dispatcher.context.authsession, set, set)
56
57 luci.sys.call("date -s '%04d-%02d-%02d %02d:%02d:%02d'" %{
58 date.year, date.month, date.day, date.hour, date.min, date.sec
59 })
60 end
61 end
62
63 luci.http.prepare_content("application/json")
64 luci.http.write_json({ timestring = os.date("%c") })
65 end
66
67 function action_packages()
68 local ipkg = require("luci.model.ipkg")
69 local submit = luci.http.formvalue("submit")
70 local changes = false
71 local install = { }
72 local remove = { }
73 local stdout = { "" }
74 local stderr = { "" }
75 local out, err
76
77 -- Display
78 local display = luci.http.formvalue("display") or "installed"
79
80 -- Letter
81 local letter = string.byte(luci.http.formvalue("letter") or "A", 1)
82 letter = (letter == 35 or (letter >= 65 and letter <= 90)) and letter or 65
83
84 -- Search query
85 local query = luci.http.formvalue("query")
86 query = (query ~= '') and query or nil
87
88
89 -- Packets to be installed
90 local ninst = submit and luci.http.formvalue("install")
91 local uinst = nil
92
93 -- Install from URL
94 local url = luci.http.formvalue("url")
95 if url and url ~= '' and submit then
96 uinst = url
97 end
98
99 -- Do install
100 if ninst then
101 install[ninst], out, err = ipkg.install(ninst)
102 stdout[#stdout+1] = out
103 stderr[#stderr+1] = err
104 changes = true
105 end
106
107 if uinst then
108 local pkg
109 for pkg in luci.util.imatch(uinst) do
110 install[uinst], out, err = ipkg.install(pkg)
111 stdout[#stdout+1] = out
112 stderr[#stderr+1] = err
113 changes = true
114 end
115 end
116
117 -- Remove packets
118 local rem = submit and luci.http.formvalue("remove")
119 if rem then
120 remove[rem], out, err = ipkg.remove(rem)
121 stdout[#stdout+1] = out
122 stderr[#stderr+1] = err
123 changes = true
124 end
125
126
127 -- Update all packets
128 local update = luci.http.formvalue("update")
129 if update then
130 update, out, err = ipkg.update()
131 stdout[#stdout+1] = out
132 stderr[#stderr+1] = err
133 end
134
135
136 -- Upgrade all packets
137 local upgrade = luci.http.formvalue("upgrade")
138 if upgrade then
139 upgrade, out, err = ipkg.upgrade()
140 stdout[#stdout+1] = out
141 stderr[#stderr+1] = err
142 end
143
144
145 -- List state
146 local no_lists = true
147 local old_lists = false
148 local tmp = nixio.fs.dir("/var/opkg-lists/")
149 if tmp then
150 for tmp in tmp do
151 no_lists = false
152 tmp = nixio.fs.stat("/var/opkg-lists/"..tmp)
153 if tmp and tmp.mtime < (os.time() - (24 * 60 * 60)) then
154 old_lists = true
155 break
156 end
157 end
158 end
159
160
161 luci.template.render("admin_system/packages", {
162 display = display,
163 letter = letter,
164 query = query,
165 install = install,
166 remove = remove,
167 update = update,
168 upgrade = upgrade,
169 no_lists = no_lists,
170 old_lists = old_lists,
171 stdout = table.concat(stdout, ""),
172 stderr = table.concat(stderr, "")
173 })
174
175 -- Remove index cache
176 if changes then
177 nixio.fs.unlink("/tmp/luci-indexcache")
178 end
179 end
180
181 function action_flashops()
182 local sys = require "luci.sys"
183 local fs = require "luci.fs"
184
185 local upgrade_avail = nixio.fs.access("/lib/upgrade/platform.sh")
186 local reset_avail = os.execute([[grep '"rootfs_data"' /proc/mtd >/dev/null 2>&1]]) == 0
187
188 local restore_cmd = "tar -xzC/ >/dev/null 2>&1"
189 local backup_cmd = "sysupgrade --create-backup - 2>/dev/null"
190 local image_tmp = "/tmp/firmware.img"
191
192 local function image_supported()
193 -- XXX: yay...
194 return ( 0 == os.execute(
195 ". /etc/functions.sh; " ..
196 "include /lib/upgrade; " ..
197 "platform_check_image %q >/dev/null"
198 % image_tmp
199 ) )
200 end
201
202 local function image_checksum()
203 return (luci.sys.exec("md5sum %q" % image_tmp):match("^([^%s]+)"))
204 end
205
206 local function storage_size()
207 local size = 0
208 if nixio.fs.access("/proc/mtd") then
209 for l in io.lines("/proc/mtd") do
210 local d, s, e, n = l:match('^([^%s]+)%s+([^%s]+)%s+([^%s]+)%s+"([^%s]+)"')
211 if n == "linux" or n == "firmware" then
212 size = tonumber(s, 16)
213 break
214 end
215 end
216 elseif nixio.fs.access("/proc/partitions") then
217 for l in io.lines("/proc/partitions") do
218 local x, y, b, n = l:match('^%s*(%d+)%s+(%d+)%s+([^%s]+)%s+([^%s]+)')
219 if b and n and not n:match('[0-9]') then
220 size = tonumber(b) * 1024
221 break
222 end
223 end
224 end
225 return size
226 end
227
228
229 local fp
230 luci.http.setfilehandler(
231 function(meta, chunk, eof)
232 if not fp then
233 if meta and meta.name == "image" then
234 fp = io.open(image_tmp, "w")
235 else
236 fp = io.popen(restore_cmd, "w")
237 end
238 end
239 if chunk then
240 fp:write(chunk)
241 end
242 if eof then
243 fp:close()
244 end
245 end
246 )
247
248 if luci.http.formvalue("backup") then
249 --
250 -- Assemble file list, generate backup
251 --
252 local reader = ltn12_popen(backup_cmd)
253 luci.http.header('Content-Disposition', 'attachment; filename="backup-%s-%s.tar.gz"' % {
254 luci.sys.hostname(), os.date("%Y-%m-%d")})
255 luci.http.prepare_content("application/x-targz")
256 luci.ltn12.pump.all(reader, luci.http.write)
257 elseif luci.http.formvalue("restore") then
258 --
259 -- Unpack received .tar.gz
260 --
261 local upload = luci.http.formvalue("archive")
262 if upload and #upload > 0 then
263 luci.template.render("admin_system/applyreboot")
264 luci.sys.reboot()
265 end
266 elseif luci.http.formvalue("image") or luci.http.formvalue("step") then
267 --
268 -- Initiate firmware flash
269 --
270 local step = tonumber(luci.http.formvalue("step") or 1)
271 if step == 1 then
272 if image_supported() then
273 luci.template.render("admin_system/upgrade", {
274 checksum = image_checksum(),
275 storage = storage_size(),
276 size = nixio.fs.stat(image_tmp).size,
277 keep = (not not luci.http.formvalue("keep"))
278 })
279 else
280 nixio.fs.unlink(image_tmp)
281 luci.template.render("admin_system/flashops", {
282 reset_avail = reset_avail,
283 upgrade_avail = upgrade_avail,
284 image_invalid = true
285 })
286 end
287 --
288 -- Start sysupgrade flash
289 --
290 elseif step == 2 then
291 local keep = (luci.http.formvalue("keep") == "1") and "" or "-n"
292 luci.template.render("admin_system/applyreboot", {
293 title = luci.i18n.translate("Flashing..."),
294 msg = luci.i18n.translate("The system is flashing now.<br /> DO NOT POWER OFF THE DEVICE!<br /> Wait a few minutes until you try to reconnect. It might be necessary to renew the address of your computer to reach the device again, depending on your settings."),
295 addr = (#keep > 0) and "192.168.1.1" or nil
296 })
297 fork_exec("killall dropbear uhttpd; sleep 1; /sbin/sysupgrade %s %q" %{ keep, image_tmp })
298 end
299 elseif reset_avail and luci.http.formvalue("reset") then
300 --
301 -- Reset system
302 --
303 luci.template.render("admin_system/applyreboot", {
304 title = luci.i18n.translate("Erasing..."),
305 msg = luci.i18n.translate("The system is erasing the configuration partition now and will reboot itself when finished."),
306 addr = "192.168.1.1"
307 })
308 fork_exec("killall dropbear uhttpd; sleep 1; mtd -r erase rootfs_data")
309 else
310 --
311 -- Overview
312 --
313 luci.template.render("admin_system/flashops", {
314 reset_avail = reset_avail,
315 upgrade_avail = upgrade_avail
316 })
317 end
318 end
319
320 function action_passwd()
321 local p1 = luci.http.formvalue("pwd1")
322 local p2 = luci.http.formvalue("pwd2")
323 local stat = nil
324
325 if p1 or p2 then
326 if p1 == p2 then
327 stat = luci.sys.user.setpasswd("root", p1)
328 else
329 stat = 10
330 end
331 end
332
333 luci.template.render("admin_system/passwd", {stat=stat})
334 end
335
336 function action_reboot()
337 local reboot = luci.http.formvalue("reboot")
338 luci.template.render("admin_system/reboot", {reboot=reboot})
339 if reboot then
340 luci.sys.reboot()
341 end
342 end
343
344 function fork_exec(command)
345 local pid = nixio.fork()
346 if pid > 0 then
347 return
348 elseif pid == 0 then
349 -- change to root dir
350 nixio.chdir("/")
351
352 -- patch stdin, out, err to /dev/null
353 local null = nixio.open("/dev/null", "w+")
354 if null then
355 nixio.dup(null, nixio.stderr)
356 nixio.dup(null, nixio.stdout)
357 nixio.dup(null, nixio.stdin)
358 if null:fileno() > 2 then
359 null:close()
360 end
361 end
362
363 -- replace with target command
364 nixio.exec("/bin/sh", "-c", command)
365 end
366 end
367
368 function ltn12_popen(command)
369
370 local fdi, fdo = nixio.pipe()
371 local pid = nixio.fork()
372
373 if pid > 0 then
374 fdo:close()
375 local close
376 return function()
377 local buffer = fdi:read(2048)
378 local wpid, stat = nixio.waitpid(pid, "nohang")
379 if not close and wpid and stat == "exited" then
380 close = true
381 end
382
383 if buffer and #buffer > 0 then
384 return buffer
385 elseif close then
386 fdi:close()
387 return nil
388 end
389 end
390 elseif pid == 0 then
391 nixio.dup(fdo, nixio.stdout)
392 fdi:close()
393 fdo:close()
394 nixio.exec("/bin/sh", "-c", command)
395 end
396 end