1b38c6783c2c0f59a7922552f2627ec82aef3cfe
[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("/sbin/block") 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