834879db4041918a15149a5415960d9e3b6386a5
[project/luci.git] / modules / 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-2009 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         luci.i18n.loadc("base")
20         local i18n = luci.i18n.translate
21
22         entry({"admin", "system"}, alias("admin", "system", "system"), i18n("System"), 30).index = true
23         entry({"admin", "system", "system"}, cbi("admin_system/system"), i18n("System"), 1)
24         entry({"admin", "system", "packages"}, call("action_packages"), i18n("Software"), 10)
25         entry({"admin", "system", "packages", "ipkg"}, form("admin_system/ipkg"))
26         entry({"admin", "system", "passwd"}, form("admin_system/passwd"), i18n("Admin Password"), 20)
27         entry({"admin", "system", "sshkeys"}, form("admin_system/sshkeys"), i18n("<abbr title=\"Secure Shell\">SSH</abbr>-Keys"), 30)
28         entry({"admin", "system", "processes"}, form("admin_system/processes"), i18n("Processes"), 45)
29
30         if nixio.fs.access("/etc/config/fstab") then
31                 entry({"admin", "system", "fstab"}, cbi("admin_system/fstab"), i18n("Mount Points"), 50)
32         end
33
34         if nixio.fs.access("/sys/class/leds") then
35                 entry({"admin", "system", "leds"}, cbi("admin_system/leds"), i18n("<abbr title=\"Light Emitting Diode\">LED</abbr> Configuration"), 60)
36         end
37
38         entry({"admin", "system", "backup"}, call("action_backup"), i18n("Backup / Restore"), 70)
39         entry({"admin", "system", "upgrade"}, call("action_upgrade"), i18n("Flash Firmware"), 80)
40         entry({"admin", "system", "reboot"}, call("action_reboot"), i18n("Reboot"), 90)
41 end
42
43 function action_packages()
44         local ipkg = require("luci.model.ipkg")
45         local submit = luci.http.formvalue("submit")
46         local changes = false
47         local install = { }
48         local remove  = { }
49
50         -- Search query
51         local query = luci.http.formvalue("query")
52         query = (query ~= '') and query or nil
53
54
55         -- Packets to be installed
56         local ninst = submit and luci.http.formvalue("install")
57         local uinst = nil
58
59         -- Install from URL
60         local url = luci.http.formvalue("url")
61         if url and url ~= '' and submit then
62                 uinst = url
63         end
64
65         -- Do install
66         if ninst then
67                 _, install[ninst] = ipkg.install(ninst)
68                 changes = true
69         end
70
71         if uinst then
72                 _, install[uinst] = ipkg.install(uinst)
73                 changes = true
74         end
75
76         -- Remove packets
77         local rem = submit and luci.http.formvalue("remove")
78         if rem then
79                 _, remove[rem] = ipkg.remove(rem)
80                 changes = true
81         end
82
83
84         -- Update all packets
85         local update = luci.http.formvalue("update")
86         if update then
87                 _, update = ipkg.update()
88         end
89
90
91         -- Upgrade all packets
92         local upgrade = luci.http.formvalue("upgrade")
93         if upgrade then
94                 _, upgrade = ipkg.upgrade()
95         end
96
97
98         luci.template.render("admin_system/packages", {
99                 query=query, install=install, remove=remove, update=update, upgrade=upgrade
100         })
101
102         -- Remove index cache
103         if changes then
104                 nixio.fs.unlink("/tmp/luci-indexcache")
105         end
106 end
107
108 function action_backup()
109         local sys = require "luci.sys"
110         local fs  = require "luci.fs"
111
112         local reset_avail = os.execute([[grep '"rootfs_data"' /proc/mtd >/dev/null 2>&1]]) == 0
113         local restore_cmd = "tar -xzC/ >/dev/null 2>&1"
114         local backup_cmd  = "tar -czT %s 2>/dev/null"
115
116         local restore_fpi
117         luci.http.setfilehandler(
118                 function(meta, chunk, eof)
119                         if not restore_fpi then
120                                 restore_fpi = io.popen(restore_cmd, "w")
121                         end
122                         if chunk then
123                                 restore_fpi:write(chunk)
124                         end
125                         if eof then
126                                 restore_fpi:close()
127                         end
128                 end
129         )
130
131         local upload = luci.http.formvalue("archive")
132         local backup = luci.http.formvalue("backup")
133         local reset  = reset_avail and luci.http.formvalue("reset")
134
135         if upload and #upload > 0 then
136                 luci.template.render("admin_system/applyreboot")
137                 luci.sys.reboot()
138         elseif backup then
139                 local filelist = "/tmp/luci-backup-list.%d" % os.time()
140
141                 sys.call(
142                         "( find $(sed -ne '/^[[:space:]]*$/d; /^#/d; p' /etc/sysupgrade.conf " ..
143                         "/lib/upgrade/keep.d/* 2>/dev/null) -type f 2>/dev/null; " ..
144                         "opkg list-changed-conffiles ) | sort -u > %s" % filelist
145                 )
146
147                 if fs.access(filelist) then
148                         local reader = ltn12_popen(backup_cmd:format(filelist))
149                         luci.http.header('Content-Disposition', 'attachment; filename="backup-%s-%s.tar.gz"' % {
150                                 luci.sys.hostname(), os.date("%Y-%m-%d")})
151                         luci.http.prepare_content("application/x-targz")
152                         luci.ltn12.pump.all(reader, luci.http.write)
153                         fs.unlink(filelist)
154                 end
155         elseif reset then
156                 luci.template.render("admin_system/applyreboot")
157                 luci.util.exec("mtd -r erase rootfs_data")
158         else
159                 luci.template.render("admin_system/backup", {reset_avail = reset_avail})
160         end
161 end
162
163 function action_passwd()
164         local p1 = luci.http.formvalue("pwd1")
165         local p2 = luci.http.formvalue("pwd2")
166         local stat = nil
167
168         if p1 or p2 then
169                 if p1 == p2 then
170                         stat = luci.sys.user.setpasswd("root", p1)
171                 else
172                         stat = 10
173                 end
174         end
175
176         luci.template.render("admin_system/passwd", {stat=stat})
177 end
178
179 function action_reboot()
180         local reboot = luci.http.formvalue("reboot")
181         luci.template.render("admin_system/reboot", {reboot=reboot})
182         if reboot then
183                 luci.sys.reboot()
184         end
185 end
186
187 function action_upgrade()
188         require("luci.model.uci")
189
190         local tmpfile = "/tmp/firmware.img"
191
192         local function image_supported()
193                 -- XXX: yay...
194                 return ( 0 == os.execute(
195                         ". /etc/functions.sh; " ..
196                         "include /lib/upgrade; " ..
197                         "platform_check_image %q >/dev/null"
198                                 % tmpfile
199                 ) )
200         end
201
202         local function image_checksum()
203                 return (luci.sys.exec("md5sum %q" % tmpfile):match("^([^%s]+)"))
204         end
205
206         local function storage_size()
207                 local size = 0
208                 if nixio.fs.access("/proc/mtd") then
209                         for l in io.lines("/proc/mtd") do
210                                 local d, s, e, n = l:match('^([^%s]+)%s+([^%s]+)%s+([^%s]+)%s+"([^%s]+)"')
211                                 if n == "linux" then
212                                         size = tonumber(s, 16)
213                                         break
214                                 end
215                         end
216                 elseif nixio.fs.access("/proc/partitions") then
217                         for l in io.lines("/proc/partitions") do
218                                 local x, y, b, n = l:match('^%s*(%d+)%s+(%d+)%s+([^%s]+)%s+([^%s]+)')
219                                 if b and n and not n:match('[0-9]') then
220                                         size = tonumber(b) * 1024
221                                         break
222                                 end
223                         end
224                 end
225                 return size
226         end
227
228
229         -- Install upload handler
230         local file
231         luci.http.setfilehandler(
232                 function(meta, chunk, eof)
233                         if not nixio.fs.access(tmpfile) and not file and chunk and #chunk > 0 then
234                                 file = io.open(tmpfile, "w")
235                         end
236                         if file and chunk then
237                                 file:write(chunk)
238                         end
239                         if file and eof then
240                                 file:close()
241                         end
242                 end
243         )
244
245
246         -- Determine state
247         local keep_avail   = true
248         local step         = tonumber(luci.http.formvalue("step") or 1)
249         local has_image    = nixio.fs.access(tmpfile)
250         local has_support  = image_supported()
251         local has_platform = nixio.fs.access("/lib/upgrade/platform.sh")
252         local has_upload   = luci.http.formvalue("image")
253
254         -- This does the actual flashing which is invoked inside an iframe
255         -- so don't produce meaningful errors here because the the
256         -- previous pages should arrange the stuff as required.
257         if step == 4 then
258                 if has_platform and has_image and has_support then
259                         -- Mimetype text/plain
260                         luci.http.prepare_content("text/plain")
261                         luci.http.write("Starting sysupgrade...\n")
262
263                         io.flush()
264
265                         -- Now invoke sysupgrade
266                         local keepcfg = keep_avail and luci.http.formvalue("keepcfg") == "1"
267                         --local flash = ltn12_popen("/sbin/sysupgrade %q" % tmpfile)
268                         local flash = ltn12_popen("hexdump %q" % tmpfile)
269
270                         luci.ltn12.pump.all(flash, luci.http.write)
271                 end
272
273
274         --
275         -- This is step 1-3, which does the user interaction and
276         -- image upload.
277         --
278
279         -- Step 1: file upload, error on unsupported image format
280         elseif not has_image or not has_support or step == 1 then
281                 -- If there is an image but user has requested step 1
282                 -- or type is not supported, then remove it.
283                 if has_image then
284                         nixio.fs.unlink(tmpfile)
285                 end
286
287                 luci.template.render("admin_system/upgrade", {
288                         step=1,
289                         bad_image=(has_image and not has_support or false),
290                         keepavail=keep_avail,
291                         supported=has_platform
292                 } )
293
294         -- Step 2: present uploaded file, show checksum, confirmation
295         elseif step == 2 then
296                 luci.template.render("admin_system/upgrade", {
297                         step=2,
298                         checksum=image_checksum(),
299                         filesize=nixio.fs.stat(tmpfile).size,
300                         flashsize=storage_size(),
301                         keepconfig=(keep_avail and luci.http.formvalue("keepcfg") == "1")
302                 } )
303
304         -- Step 3: load iframe which calls the actual flash procedure
305         elseif step == 3 then
306                 luci.template.render("admin_system/upgrade", {
307                         step=3,
308                         keepconfig=(keep_avail and luci.http.formvalue("keepcfg") == "1")
309                 } )
310         end
311 end
312
313 function ltn12_popen(command)
314
315         local fdi, fdo = nixio.pipe()
316         local pid = nixio.fork()
317
318         if pid > 0 then
319                 fdo:close()
320                 local close
321                 return function()
322                         local buffer = fdi:read(2048)
323                         local wpid, stat = nixio.waitpid(pid, "nohang")
324                         if not close and wpid and stat == "exited" then
325                                 close = true
326                         end
327
328                         if buffer and #buffer > 0 then
329                                 return buffer
330                         elseif close then
331                                 fdi:close()
332                                 return nil
333                         end
334                 end
335         elseif pid == 0 then
336                 nixio.dup(fdo, nixio.stdout)
337                 fdi:close()
338                 fdo:close()
339                 nixio.exec("/bin/sh", "-c", command)
340         end
341 end