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