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