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