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