055142b53e67c2706947a7ccebf8a3f952feec25
[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 -- XXX: yay...
182 return ( 0 == os.execute(
183 ". /lib/functions.sh; " ..
184 "include /lib/upgrade; " ..
185 "platform_check_image %q >/dev/null"
186 % image_tmp
187 ) )
188 end
189
190 local function image_checksum()
191 return (luci.sys.exec("md5sum %q" % image_tmp):match("^([^%s]+)"))
192 end
193
194 local function storage_size()
195 local size = 0
196 if fs.access("/proc/mtd") then
197 for l in io.lines("/proc/mtd") do
198 local d, s, e, n = l:match('^([^%s]+)%s+([^%s]+)%s+([^%s]+)%s+"([^%s]+)"')
199 if n == "linux" or n == "firmware" then
200 size = tonumber(s, 16)
201 break
202 end
203 end
204 elseif fs.access("/proc/partitions") then
205 for l in io.lines("/proc/partitions") do
206 local x, y, b, n = l:match('^%s*(%d+)%s+(%d+)%s+([^%s]+)%s+([^%s]+)')
207 if b and n and not n:match('[0-9]') then
208 size = tonumber(b) * 1024
209 break
210 end
211 end
212 end
213 return size
214 end
215
216
217 local fp
218 luci.http.setfilehandler(
219 function(meta, chunk, eof)
220 if not fp then
221 if meta and meta.name == "image" then
222 fp = io.open(image_tmp, "w")
223 else
224 fp = io.popen(restore_cmd, "w")
225 end
226 end
227 if chunk then
228 fp:write(chunk)
229 end
230 if eof then
231 fp:close()
232 end
233 end
234 )
235
236 if luci.http.formvalue("backup") then
237 --
238 -- Assemble file list, generate backup
239 --
240 local reader = ltn12_popen(backup_cmd)
241 luci.http.header('Content-Disposition', 'attachment; filename="backup-%s-%s.tar.gz"' % {
242 luci.sys.hostname(), os.date("%Y-%m-%d")})
243 luci.http.prepare_content("application/x-targz")
244 luci.ltn12.pump.all(reader, luci.http.write)
245 elseif luci.http.formvalue("restore") then
246 --
247 -- Unpack received .tar.gz
248 --
249 local upload = luci.http.formvalue("archive")
250 if upload and #upload > 0 then
251 luci.template.render("admin_system/applyreboot")
252 luci.sys.reboot()
253 end
254 elseif luci.http.formvalue("image") or luci.http.formvalue("step") then
255 --
256 -- Initiate firmware flash
257 --
258 local step = tonumber(luci.http.formvalue("step") or 1)
259 if step == 1 then
260 if image_supported() then
261 luci.template.render("admin_system/upgrade", {
262 checksum = image_checksum(),
263 storage = storage_size(),
264 size = (fs.stat(image_tmp, "size") or 0),
265 keep = (not not luci.http.formvalue("keep"))
266 })
267 else
268 fs.unlink(image_tmp)
269 luci.template.render("admin_system/flashops", {
270 reset_avail = reset_avail,
271 upgrade_avail = upgrade_avail,
272 image_invalid = true
273 })
274 end
275 --
276 -- Start sysupgrade flash
277 --
278 elseif step == 2 then
279 local keep = (luci.http.formvalue("keep") == "1") and "" or "-n"
280 luci.template.render("admin_system/applyreboot", {
281 title = luci.i18n.translate("Flashing..."),
282 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."),
283 addr = (#keep > 0) and "192.168.1.1" or nil
284 })
285 fork_exec("killall dropbear uhttpd; sleep 1; /sbin/sysupgrade %s %q" %{ keep, image_tmp })
286 end
287 elseif reset_avail and luci.http.formvalue("reset") then
288 --
289 -- Reset system
290 --
291 luci.template.render("admin_system/applyreboot", {
292 title = luci.i18n.translate("Erasing..."),
293 msg = luci.i18n.translate("The system is erasing the configuration partition now and will reboot itself when finished."),
294 addr = "192.168.1.1"
295 })
296 fork_exec("killall dropbear uhttpd; sleep 1; mtd -r erase rootfs_data")
297 else
298 --
299 -- Overview
300 --
301 luci.template.render("admin_system/flashops", {
302 reset_avail = reset_avail,
303 upgrade_avail = upgrade_avail
304 })
305 end
306 end
307
308 function action_passwd()
309 local p1 = luci.http.formvalue("pwd1")
310 local p2 = luci.http.formvalue("pwd2")
311 local stat = nil
312
313 if p1 or p2 then
314 if p1 == p2 then
315 stat = luci.sys.user.setpasswd("root", p1)
316 else
317 stat = 10
318 end
319 end
320
321 luci.template.render("admin_system/passwd", {stat=stat})
322 end
323
324 function action_reboot()
325 local reboot = luci.http.formvalue("reboot")
326 luci.template.render("admin_system/reboot", {reboot=reboot})
327 if reboot then
328 luci.sys.reboot()
329 end
330 end
331
332 function fork_exec(command)
333 local pid = nixio.fork()
334 if pid > 0 then
335 return
336 elseif pid == 0 then
337 -- change to root dir
338 nixio.chdir("/")
339
340 -- patch stdin, out, err to /dev/null
341 local null = nixio.open("/dev/null", "w+")
342 if null then
343 nixio.dup(null, nixio.stderr)
344 nixio.dup(null, nixio.stdout)
345 nixio.dup(null, nixio.stdin)
346 if null:fileno() > 2 then
347 null:close()
348 end
349 end
350
351 -- replace with target command
352 nixio.exec("/bin/sh", "-c", command)
353 end
354 end
355
356 function ltn12_popen(command)
357
358 local fdi, fdo = nixio.pipe()
359 local pid = nixio.fork()
360
361 if pid > 0 then
362 fdo:close()
363 local close
364 return function()
365 local buffer = fdi:read(2048)
366 local wpid, stat = nixio.waitpid(pid, "nohang")
367 if not close and wpid and stat == "exited" then
368 close = true
369 end
370
371 if buffer and #buffer > 0 then
372 return buffer
373 elseif close then
374 fdi:close()
375 return nil
376 end
377 end
378 elseif pid == 0 then
379 nixio.dup(fdo, nixio.stdout)
380 fdi:close()
381 fdo:close()
382 nixio.exec("/bin/sh", "-c", command)
383 end
384 end