a5666ccbb22e0346fb8b64e31ea3dad12a61dbc5
[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