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