68f3d258ddff728225a6b0f1dd64215735b20404
[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-2009 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  = "tar -czT %s 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 filelist = "/tmp/luci-backup-list.%d" % os.time()
253                 sys.call(
254                         "( find $(sed -ne '/^[[:space:]]*$/d; /^#/d; p' /etc/sysupgrade.conf " ..
255                         "/lib/upgrade/keep.d/* 2>/dev/null) -type f 2>/dev/null; " ..
256                         "opkg list-changed-conffiles ) | sort -u > %s" % filelist
257                 )
258                 if fs.access(filelist) then
259                         local reader = ltn12_popen(backup_cmd:format(filelist))
260                         luci.http.header('Content-Disposition', 'attachment; filename="backup-%s-%s.tar.gz"' % {
261                                 luci.sys.hostname(), os.date("%Y-%m-%d")})
262                         luci.http.prepare_content("application/x-targz")
263                         luci.ltn12.pump.all(reader, luci.http.write)
264                         fs.unlink(filelist)
265                 end
266         elseif luci.http.formvalue("restore") then
267                 --
268                 -- Unpack received .tar.gz
269                 --
270                 local upload = luci.http.formvalue("archive")
271                 if upload and #upload > 0 then
272                         luci.template.render("admin_system/applyreboot")
273                         luci.sys.reboot()
274                 end
275         elseif luci.http.formvalue("image") or luci.http.formvalue("step") then
276                 --
277                 -- Initiate firmware flash
278                 --
279                 local step = tonumber(luci.http.formvalue("step") or 1)
280                 if step == 1 then
281                         if image_supported() then
282                                 luci.template.render("admin_system/upgrade", {
283                                         checksum = image_checksum(),
284                                         storage  = storage_size(),
285                                         size     = nixio.fs.stat(image_tmp).size,
286                                         keep     = (not not luci.http.formvalue("keep"))
287                                 })
288                         else
289                                 nixio.fs.unlink(image_tmp)
290                                 luci.template.render("admin_system/flashops", {
291                                         reset_avail   = reset_avail,
292                                         upgrade_avail = upgrade_avail,
293                                         image_invalid = true
294                                 })
295                         end
296                 --
297                 -- Start sysupgrade flash
298                 --
299                 elseif step == 2 then
300                         local keep = (luci.http.formvalue("keep") == "1") and "" or "-n"
301                         luci.template.render("admin_system/applyreboot", {
302                                 title = luci.i18n.translate("Flashing..."),
303                                 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."),
304                                 addr  = (#keep > 0) and "192.168.1.1" or nil
305                         })
306                         fork_exec("killall dropbear uhttpd; sleep 1; /sbin/sysupgrade %s %q" %{ keep, image_tmp })
307                 end
308         elseif reset_avail and luci.http.formvalue("reset") then
309                 --
310                 -- Reset system
311                 --
312                 luci.template.render("admin_system/applyreboot", {
313                         title = luci.i18n.translate("Erasing..."),
314                         msg   = luci.i18n.translate("The system is erasing the configuration partition now and will reboot itself when finished."),
315                         addr  = "192.168.1.1"
316                 })
317                 fork_exec("killall dropbear uhttpd; sleep 1; mtd -r erase rootfs_data")
318         else
319                 --
320                 -- Overview
321                 --
322                 luci.template.render("admin_system/flashops", {
323                         reset_avail   = reset_avail,
324                         upgrade_avail = upgrade_avail
325                 })
326         end
327 end
328
329 function action_passwd()
330         local p1 = luci.http.formvalue("pwd1")
331         local p2 = luci.http.formvalue("pwd2")
332         local stat = nil
333
334         if p1 or p2 then
335                 if p1 == p2 then
336                         stat = luci.sys.user.setpasswd("root", p1)
337                 else
338                         stat = 10
339                 end
340         end
341
342         luci.template.render("admin_system/passwd", {stat=stat})
343 end
344
345 function action_reboot()
346         local reboot = luci.http.formvalue("reboot")
347         luci.template.render("admin_system/reboot", {reboot=reboot})
348         if reboot then
349                 luci.sys.reboot()
350         end
351 end
352
353 function fork_exec(command)
354         local pid = nixio.fork()
355         if pid > 0 then
356                 return
357         elseif pid == 0 then
358                 -- change to root dir
359                 nixio.chdir("/")
360
361                 -- patch stdin, out, err to /dev/null
362                 local null = nixio.open("/dev/null", "w+")
363                 if null then
364                         nixio.dup(null, nixio.stderr)
365                         nixio.dup(null, nixio.stdout)
366                         nixio.dup(null, nixio.stdin)
367                         if null:fileno() > 2 then
368                                 null:close()
369                         end
370                 end
371
372                 -- replace with target command
373                 nixio.exec("/bin/sh", "-c", command)
374         end
375 end
376
377 function ltn12_popen(command)
378
379         local fdi, fdo = nixio.pipe()
380         local pid = nixio.fork()
381
382         if pid > 0 then
383                 fdo:close()
384                 local close
385                 return function()
386                         local buffer = fdi:read(2048)
387                         local wpid, stat = nixio.waitpid(pid, "nohang")
388                         if not close and wpid and stat == "exited" then
389                                 close = true
390                         end
391
392                         if buffer and #buffer > 0 then
393                                 return buffer
394                         elseif close then
395                                 fdi:close()
396                                 return nil
397                         end
398                 end
399         elseif pid == 0 then
400                 nixio.dup(fdo, nixio.stdout)
401                 fdi:close()
402                 fdo:close()
403                 nixio.exec("/bin/sh", "-c", command)
404         end
405 end