114acba6698911efbe04e8f71dc58f3bd2e2c894
[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 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
32 if nixio.fs.access("/etc/config/fstab") then
33 entry({"admin", "system", "fstab"}, cbi("admin_system/fstab"), _("Mount Points"), 50)
34 entry({"admin", "system", "fstab", "mount"}, cbi("admin_system/fstab/mount"), nil).leaf = true
35 entry({"admin", "system", "fstab", "swap"}, cbi("admin_system/fstab/swap"), nil).leaf = true
36 end
37
38 if nixio.fs.access("/sys/class/leds") then
39 entry({"admin", "system", "leds"}, cbi("admin_system/leds"), _("<abbr title=\"Light Emitting Diode\">LED</abbr> Configuration"), 60)
40 end
41
42 entry({"admin", "system", "flashops"}, call("action_flashops"), _("Backup / Flash Firmware"), 70)
43 entry({"admin", "system", "flashops", "backupfiles"}, form("admin_system/backupfiles"))
44
45 entry({"admin", "system", "reboot"}, call("action_reboot"), _("Reboot"), 90)
46 end
47
48 function action_clock_status()
49 local set = tonumber(luci.http.formvalue("set"))
50 if set ~= nil and set > 0 then
51 local date = os.date("*t", set)
52 if date then
53 -- prevent session timeoutby updating mtime
54 nixio.fs.utimes(luci.sauth.sessionpath .. "/" .. luci.dispatcher.context.authsession, set, set)
55
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 ipkg = require("luci.model.ipkg")
68 local submit = luci.http.formvalue("submit")
69 local changes = false
70 local install = { }
71 local remove = { }
72 local stdout = { "" }
73 local stderr = { "" }
74 local out, err
75
76 -- Display
77 local display = luci.http.formvalue("display") or "installed"
78
79 -- Letter
80 local letter = string.byte(luci.http.formvalue("letter") or "A", 1)
81 letter = (letter == 35 or (letter >= 65 and letter <= 90)) and letter or 65
82
83 -- Search query
84 local query = luci.http.formvalue("query")
85 query = (query ~= '') and query or nil
86
87
88 -- Packets to be installed
89 local ninst = submit and luci.http.formvalue("install")
90 local uinst = nil
91
92 -- Install from URL
93 local url = luci.http.formvalue("url")
94 if url and url ~= '' and submit then
95 uinst = url
96 end
97
98 -- Do install
99 if ninst then
100 install[ninst], out, err = ipkg.install(ninst)
101 stdout[#stdout+1] = out
102 stderr[#stderr+1] = err
103 changes = true
104 end
105
106 if uinst then
107 local pkg
108 for pkg in luci.util.imatch(uinst) do
109 install[uinst], out, err = ipkg.install(pkg)
110 stdout[#stdout+1] = out
111 stderr[#stderr+1] = err
112 changes = true
113 end
114 end
115
116 -- Remove packets
117 local rem = submit and luci.http.formvalue("remove")
118 if rem then
119 remove[rem], out, err = ipkg.remove(rem)
120 stdout[#stdout+1] = out
121 stderr[#stderr+1] = err
122 changes = true
123 end
124
125
126 -- Update all packets
127 local update = luci.http.formvalue("update")
128 if update then
129 update, out, err = ipkg.update()
130 stdout[#stdout+1] = out
131 stderr[#stderr+1] = err
132 end
133
134
135 -- Upgrade all packets
136 local upgrade = luci.http.formvalue("upgrade")
137 if upgrade then
138 upgrade, out, err = ipkg.upgrade()
139 stdout[#stdout+1] = out
140 stderr[#stderr+1] = err
141 end
142
143
144 -- List state
145 local no_lists = true
146 local old_lists = false
147 local tmp = nixio.fs.dir("/var/opkg-lists/")
148 if tmp then
149 for tmp in tmp do
150 no_lists = false
151 tmp = nixio.fs.stat("/var/opkg-lists/"..tmp)
152 if tmp and tmp.mtime < (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 nixio.fs.unlink("/tmp/luci-indexcache")
177 end
178 end
179
180 function action_flashops()
181 local sys = require "luci.sys"
182 local fs = require "luci.fs"
183
184 local upgrade_avail = nixio.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 = "tar -czT %s 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 ". /etc/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 nixio.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 nixio.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 filelist = "/tmp/luci-backup-list.%d" % os.time()
252 sys.call(
253 "( find $(sed -ne '/^[[:space:]]*$/d; /^#/d; p' /etc/sysupgrade.conf " ..
254 "/lib/upgrade/keep.d/* 2>/dev/null) -type f 2>/dev/null; " ..
255 "opkg list-changed-conffiles ) | sort -u > %s" % filelist
256 )
257 if fs.access(filelist) then
258 local reader = ltn12_popen(backup_cmd:format(filelist))
259 luci.http.header('Content-Disposition', 'attachment; filename="backup-%s-%s.tar.gz"' % {
260 luci.sys.hostname(), os.date("%Y-%m-%d")})
261 luci.http.prepare_content("application/x-targz")
262 luci.ltn12.pump.all(reader, luci.http.write)
263 fs.unlink(filelist)
264 end
265 elseif luci.http.formvalue("restore") then
266 --
267 -- Unpack received .tar.gz
268 --
269 local upload = luci.http.formvalue("archive")
270 if upload and #upload > 0 then
271 luci.template.render("admin_system/applyreboot")
272 luci.sys.reboot()
273 end
274 elseif luci.http.formvalue("image") or luci.http.formvalue("step") then
275 --
276 -- Initiate firmware flash
277 --
278 local step = tonumber(luci.http.formvalue("step") or 1)
279 if step == 1 then
280 if image_supported() then
281 luci.template.render("admin_system/upgrade", {
282 checksum = image_checksum(),
283 storage = storage_size(),
284 size = nixio.fs.stat(image_tmp).size,
285 keep = (not not luci.http.formvalue("keep"))
286 })
287 else
288 nixio.fs.unlink(image_tmp)
289 luci.template.render("admin_system/flashops", {
290 reset_avail = reset_avail,
291 upgrade_avail = upgrade_avail,
292 image_invalid = true
293 })
294 end
295 --
296 -- Start sysupgrade flash
297 --
298 elseif step == 2 then
299 local keep = (luci.http.formvalue("keep") == "1") and "" or "-n"
300 luci.template.render("admin_system/applyreboot", {
301 title = luci.i18n.translate("Flashing..."),
302 msg = luci.i18n.translate("The system is flashing now.<br /> DO NOT POWER OFF THE DEVICE!<br /> Wait a few minutes until you try to reconnect. It might be necessary to renew the address of your computer to reach the device again, depending on your settings."),
303 addr = (#keep > 0) and "192.168.1.1" or nil
304 })
305 fork_exec("killall dropbear uhttpd; sleep 1; /sbin/sysupgrade %s %q" %{ keep, image_tmp })
306 end
307 elseif reset_avail and luci.http.formvalue("reset") then
308 --
309 -- Reset system
310 --
311 luci.template.render("admin_system/applyreboot", {
312 title = luci.i18n.translate("Erasing..."),
313 msg = luci.i18n.translate("The system is erasing the configuration partition now and will reboot itself when finished."),
314 addr = "192.168.1.1"
315 })
316 fork_exec("killall dropbear uhttpd; sleep 1; mtd -r erase rootfs_data")
317 else
318 --
319 -- Overview
320 --
321 luci.template.render("admin_system/flashops", {
322 reset_avail = reset_avail,
323 upgrade_avail = upgrade_avail
324 })
325 end
326 end
327
328 function action_passwd()
329 local p1 = luci.http.formvalue("pwd1")
330 local p2 = luci.http.formvalue("pwd2")
331 local stat = nil
332
333 if p1 or p2 then
334 if p1 == p2 then
335 stat = luci.sys.user.setpasswd("root", p1)
336 else
337 stat = 10
338 end
339 end
340
341 luci.template.render("admin_system/passwd", {stat=stat})
342 end
343
344 function action_reboot()
345 local reboot = luci.http.formvalue("reboot")
346 luci.template.render("admin_system/reboot", {reboot=reboot})
347 if reboot then
348 luci.sys.reboot()
349 end
350 end
351
352 function fork_exec(command)
353 local pid = nixio.fork()
354 if pid > 0 then
355 return
356 elseif pid == 0 then
357 -- change to root dir
358 nixio.chdir("/")
359
360 -- patch stdin, out, err to /dev/null
361 local null = nixio.open("/dev/null", "w+")
362 if null then
363 nixio.dup(null, nixio.stderr)
364 nixio.dup(null, nixio.stdout)
365 nixio.dup(null, nixio.stdin)
366 if null:fileno() > 2 then
367 null:close()
368 end
369 end
370
371 -- replace with target command
372 nixio.exec("/bin/sh", "-c", command)
373 end
374 end
375
376 function ltn12_popen(command)
377
378 local fdi, fdo = nixio.pipe()
379 local pid = nixio.fork()
380
381 if pid > 0 then
382 fdo:close()
383 local close
384 return function()
385 local buffer = fdi:read(2048)
386 local wpid, stat = nixio.waitpid(pid, "nohang")
387 if not close and wpid and stat == "exited" then
388 close = true
389 end
390
391 if buffer and #buffer > 0 then
392 return buffer
393 elseif close then
394 fdi:close()
395 return nil
396 end
397 end
398 elseif pid == 0 then
399 nixio.dup(fdo, nixio.stdout)
400 fdi:close()
401 fdo:close()
402 nixio.exec("/bin/sh", "-c", command)
403 end
404 end