9b09e27667814666ccb421327c47bb270eb6f724
[project/luci.git] / modules / luci-mod-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         local fs = require "nixio.fs"
20
21         entry({"admin", "system"}, alias("admin", "system", "system"), _("System"), 30).index = true
22         entry({"admin", "system", "system"}, cbi("admin_system/system"), _("System"), 1)
23         entry({"admin", "system", "clock_status"}, call("action_clock_status"))
24
25         entry({"admin", "system", "admin"}, cbi("admin_system/admin"), _("Administration"), 2)
26
27         if fs.access("/bin/opkg") then
28                 entry({"admin", "system", "packages"}, call("action_packages"), _("Software"), 10)
29                 entry({"admin", "system", "packages", "ipkg"}, form("admin_system/ipkg"))
30         end
31
32         entry({"admin", "system", "startup"}, form("admin_system/startup"), _("Startup"), 45)
33         entry({"admin", "system", "crontab"}, form("admin_system/crontab"), _("Scheduled Tasks"), 46)
34
35         if fs.access("/etc/config/fstab") then
36                 entry({"admin", "system", "fstab"}, cbi("admin_system/fstab"), _("Mount Points"), 50)
37                 entry({"admin", "system", "fstab", "mount"}, cbi("admin_system/fstab/mount"), nil).leaf = true
38                 entry({"admin", "system", "fstab", "swap"},  cbi("admin_system/fstab/swap"),  nil).leaf = true
39         end
40
41         if fs.access("/sys/class/leds") then
42                 entry({"admin", "system", "leds"}, cbi("admin_system/leds"), _("<abbr title=\"Light Emitting Diode\">LED</abbr> Configuration"), 60)
43         end
44
45         entry({"admin", "system", "flashops"}, call("action_flashops"), _("Backup / Flash Firmware"), 70)
46         entry({"admin", "system", "flashops", "backupfiles"}, form("admin_system/backupfiles"))
47
48         entry({"admin", "system", "reboot"}, call("action_reboot"), _("Reboot"), 90)
49 end
50
51 function action_clock_status()
52         local set = tonumber(luci.http.formvalue("set"))
53         if set ~= nil and set > 0 then
54                 local date = os.date("*t", set)
55                 if date then
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         if fs.access("/var/opkg-lists/") then
148                 local list
149                 for list in fs.dir("/var/opkg-lists/") do
150                         no_lists = false
151                         if (fs.stat("/var/opkg-lists/"..list, "mtime") or 0) < (os.time() - (24 * 60 * 60)) then
152                                 old_lists = true
153                                 break
154                         end
155                 end
156         end
157
158
159         luci.template.render("admin_system/packages", {
160                 display   = display,
161                 letter    = letter,
162                 query     = query,
163                 install   = install,
164                 remove    = remove,
165                 update    = update,
166                 upgrade   = upgrade,
167                 no_lists  = no_lists,
168                 old_lists = old_lists,
169                 stdout    = table.concat(stdout, ""),
170                 stderr    = table.concat(stderr, "")
171         })
172
173         -- Remove index cache
174         if changes then
175                 fs.unlink("/tmp/luci-indexcache")
176         end
177 end
178
179 function action_flashops()
180         local sys = require "luci.sys"
181         local fs  = require "nixio.fs"
182
183         local upgrade_avail = fs.access("/lib/upgrade/platform.sh")
184         local reset_avail   = os.execute([[grep '"rootfs_data"' /proc/mtd >/dev/null 2>&1]]) == 0
185
186         local restore_cmd = "tar -xzC/ >/dev/null 2>&1"
187         local backup_cmd  = "sysupgrade --create-backup - 2>/dev/null"
188         local image_tmp   = "/tmp/firmware.img"
189
190         local function image_supported()
191                 -- XXX: yay...
192                 return ( 0 == os.execute(
193                         ". /lib/functions.sh; " ..
194                         "include /lib/upgrade; " ..
195                         "platform_check_image %q >/dev/null"
196                                 % image_tmp
197                 ) )
198         end
199
200         local function image_checksum()
201                 return (luci.sys.exec("md5sum %q" % image_tmp):match("^([^%s]+)"))
202         end
203
204         local function storage_size()
205                 local size = 0
206                 if fs.access("/proc/mtd") then
207                         for l in io.lines("/proc/mtd") do
208                                 local d, s, e, n = l:match('^([^%s]+)%s+([^%s]+)%s+([^%s]+)%s+"([^%s]+)"')
209                                 if n == "linux" or n == "firmware" then
210                                         size = tonumber(s, 16)
211                                         break
212                                 end
213                         end
214                 elseif fs.access("/proc/partitions") then
215                         for l in io.lines("/proc/partitions") do
216                                 local x, y, b, n = l:match('^%s*(%d+)%s+(%d+)%s+([^%s]+)%s+([^%s]+)')
217                                 if b and n and not n:match('[0-9]') then
218                                         size = tonumber(b) * 1024
219                                         break
220                                 end
221                         end
222                 end
223                 return size
224         end
225
226
227         local fp
228         luci.http.setfilehandler(
229                 function(meta, chunk, eof)
230                         if not fp then
231                                 if meta and meta.name == "image" then
232                                         fp = io.open(image_tmp, "w")
233                                 else
234                                         fp = io.popen(restore_cmd, "w")
235                                 end
236                         end
237                         if chunk then
238                                 fp:write(chunk)
239                         end
240                         if eof then
241                                 fp:close()
242                         end
243                 end
244         )
245
246         if luci.http.formvalue("backup") then
247                 --
248                 -- Assemble file list, generate backup
249                 --
250                 local reader = ltn12_popen(backup_cmd)
251                 luci.http.header('Content-Disposition', 'attachment; filename="backup-%s-%s.tar.gz"' % {
252                         luci.sys.hostname(), os.date("%Y-%m-%d")})
253                 luci.http.prepare_content("application/x-targz")
254                 luci.ltn12.pump.all(reader, luci.http.write)
255         elseif luci.http.formvalue("restore") then
256                 --
257                 -- Unpack received .tar.gz
258                 --
259                 local upload = luci.http.formvalue("archive")
260                 if upload and #upload > 0 then
261                         luci.template.render("admin_system/applyreboot")
262                         luci.sys.reboot()
263                 end
264         elseif luci.http.formvalue("image") or luci.http.formvalue("step") then
265                 --
266                 -- Initiate firmware flash
267                 --
268                 local step = tonumber(luci.http.formvalue("step") or 1)
269                 if step == 1 then
270                         if image_supported() then
271                                 luci.template.render("admin_system/upgrade", {
272                                         checksum = image_checksum(),
273                                         storage  = storage_size(),
274                                         size     = (fs.stat(image_tmp, "size") or 0),
275                                         keep     = (not not luci.http.formvalue("keep"))
276                                 })
277                         else
278                                 fs.unlink(image_tmp)
279                                 luci.template.render("admin_system/flashops", {
280                                         reset_avail   = reset_avail,
281                                         upgrade_avail = upgrade_avail,
282                                         image_invalid = true
283                                 })
284                         end
285                 --
286                 -- Start sysupgrade flash
287                 --
288                 elseif step == 2 then
289                         local keep = (luci.http.formvalue("keep") == "1") and "" or "-n"
290                         luci.template.render("admin_system/applyreboot", {
291                                 title = luci.i18n.translate("Flashing..."),
292                                 msg   = luci.i18n.translate("The system is flashing now.<br /> DO NOT POWER OFF THE DEVICE!<br /> Wait a few minutes before you try to reconnect. It might be necessary to renew the address of your computer to reach the device again, depending on your settings."),
293                                 addr  = (#keep > 0) and "192.168.1.1" or nil
294                         })
295                         fork_exec("killall dropbear uhttpd; sleep 1; /sbin/sysupgrade %s %q" %{ keep, image_tmp })
296                 end
297         elseif reset_avail and luci.http.formvalue("reset") then
298                 --
299                 -- Reset system
300                 --
301                 luci.template.render("admin_system/applyreboot", {
302                         title = luci.i18n.translate("Erasing..."),
303                         msg   = luci.i18n.translate("The system is erasing the configuration partition now and will reboot itself when finished."),
304                         addr  = "192.168.1.1"
305                 })
306                 fork_exec("killall dropbear uhttpd; sleep 1; mtd -r erase rootfs_data")
307         else
308                 --
309                 -- Overview
310                 --
311                 luci.template.render("admin_system/flashops", {
312                         reset_avail   = reset_avail,
313                         upgrade_avail = upgrade_avail
314                 })
315         end
316 end
317
318 function action_passwd()
319         local p1 = luci.http.formvalue("pwd1")
320         local p2 = luci.http.formvalue("pwd2")
321         local stat = nil
322
323         if p1 or p2 then
324                 if p1 == p2 then
325                         stat = luci.sys.user.setpasswd("root", p1)
326                 else
327                         stat = 10
328                 end
329         end
330
331         luci.template.render("admin_system/passwd", {stat=stat})
332 end
333
334 function action_reboot()
335         local reboot = luci.http.formvalue("reboot")
336         luci.template.render("admin_system/reboot", {reboot=reboot})
337         if reboot then
338                 luci.sys.reboot()
339         end
340 end
341
342 function fork_exec(command)
343         local pid = nixio.fork()
344         if pid > 0 then
345                 return
346         elseif pid == 0 then
347                 -- change to root dir
348                 nixio.chdir("/")
349
350                 -- patch stdin, out, err to /dev/null
351                 local null = nixio.open("/dev/null", "w+")
352                 if null then
353                         nixio.dup(null, nixio.stderr)
354                         nixio.dup(null, nixio.stdout)
355                         nixio.dup(null, nixio.stdin)
356                         if null:fileno() > 2 then
357                                 null:close()
358                         end
359                 end
360
361                 -- replace with target command
362                 nixio.exec("/bin/sh", "-c", command)
363         end
364 end
365
366 function ltn12_popen(command)
367
368         local fdi, fdo = nixio.pipe()
369         local pid = nixio.fork()
370
371         if pid > 0 then
372                 fdo:close()
373                 local close
374                 return function()
375                         local buffer = fdi:read(2048)
376                         local wpid, stat = nixio.waitpid(pid, "nohang")
377                         if not close and wpid and stat == "exited" then
378                                 close = true
379                         end
380
381                         if buffer and #buffer > 0 then
382                                 return buffer
383                         elseif close then
384                                 fdi:close()
385                                 return nil
386                         end
387                 end
388         elseif pid == 0 then
389                 nixio.dup(fdo, nixio.stdout)
390                 fdi:close()
391                 fdo:close()
392                 nixio.exec("/bin/sh", "-c", command)
393         end
394 end