diff options
-rw-r--r-- | Makefile | 52 | ||||
-rw-r--r-- | README | 22 | ||||
-rw-r--r-- | config.mk | 10 | ||||
-rw-r--r-- | hostname-controller.lua | 43 | ||||
-rw-r--r-- | hostname-html.lsp | 2 | ||||
-rw-r--r-- | hostname-model.lua | 24 | ||||
-rw-r--r-- | hostname.menu | 3 | ||||
-rw-r--r-- | interfaces-controller.lua | 171 | ||||
-rw-r--r-- | interfaces-create-html.lsp | 37 | ||||
-rw-r--r-- | interfaces-delete-html.lsp | 15 | ||||
-rw-r--r-- | interfaces-model.lua | 284 | ||||
-rw-r--r-- | interfaces-read-html.lsp | 34 | ||||
-rw-r--r-- | interfaces-update-html.lsp | 36 | ||||
-rw-r--r-- | interfaces.menu | 4 |
14 files changed, 737 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b3a4f40 --- /dev/null +++ b/Makefile @@ -0,0 +1,52 @@ +APP_NAME=alpine-baselayout +PACKAGE=acf-$(APP_NAME) +VERSION=2.0 + +APP_DIST=hostname-controller.lua \ + hostname-html.lsp \ + hostname-model.lua \ + hostname.menu \ + interfaces-controller.lua \ + interfaces-create-html.lsp \ + interfaces-delete-html.lsp \ + interfaces-model.lua \ + interfaces-read-html.lsp \ + interfaces-update-html.lsp \ + interfaces.menu + +EXTRA_DIST=README Makefile config.mk + +DISTFILES=$(APP_DIST) $(EXTRA_DIST) + +TAR=tar + +P=$(PACKAGE)-$(VERSION) +tarball=$(P).tar.bz2 +install_dir=$(DESTDIR)/$(appdir)/$(APP_NAME) + +all: +clean: + rm -rf $(tarball) $(P) + +dist: $(tarball) + +install: + mkdir -p "$(install_dir)" + cp -a $(APP_DIST) "$(install_dir)" + +$(tarball): $(DISTFILES) + rm -rf $(P) + mkdir -p $(P) + cp $(DISTFILES) $(P) + $(TAR) -jcf $@ $(P) + rm -rf $(P) + +# target that creates a tar package, unpacks is and install from package +dist-install: $(tarball) + $(TAR) -jxf $(tarball) + $(MAKE) -C $(P) install DESTDIR=$(DESTDIR) + rm -rf $(P) + +include config.mk + +.PHONY: all clean dist install dist-install @@ -0,0 +1,22 @@ +acf-alpine-baselayout is an application for the ACF framework. + + +HOWTO INSTALL +------------ +Edit paths in config.mk + +As root, run: + + make install [DESTDIR=<target dir>] + + +HOWTO MODIFY +------------ +If you modify source and want to create a new source package, you will need +to verify that all files that should be included are listed in Makefile and +that no files that should not be included are not listed there. + +To make a new ditribution package run: + + make dist + diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..45f4d21 --- /dev/null +++ b/config.mk @@ -0,0 +1,10 @@ +prefix=/usr +datadir=${prefix}/share +sysconfdir=${prefix}/etc +localstatedir=${prefix}/var +acfdir=${datadir}/acf +wwwdir=${acfdir}/www +cgibindir=${acfdir}/cgi-bin +appdir=${acfdir}/app +acflibdir=${acfdir}/lib +sessionsdir=${localstatedir}/lib/acf/sessions diff --git a/hostname-controller.lua b/hostname-controller.lua new file mode 100644 index 0000000..800432b --- /dev/null +++ b/hostname-controller.lua @@ -0,0 +1,43 @@ +-- the hostname controller + +module (..., package.seeall) + +-- Cause an http redirect to our "read" action +-- We use the self.conf table because it already has prefix,controller,etc +-- The redir code is defined in the application error handler (acf-controller) +local list_redir = function (self) + self.conf.action = "read" + self.conf.type = "redir" + error (self.conf) +end + +mvc={} +mvc.on_load = function(self, parent) + if (self.worker[self.conf.action] == nil ) or ( self.conf.action == "init" ) then + self.worker[self.conf.action] = list_redir(self) + end + + logit ("hostname.mvc.on_load activated") + +end + + +-- Public methods +-- <prefix>/hostname/get + + +read = function (self ) + return ({hostname = self.model:get()} ) +end + + +update = function (self) + return ( {hostname = self.model:set(cfe({value=self.clientdata.hostname}))}) +end + +delete = function (self) + return ({hostname = self.model:set(cfe({value=""}))}) +end + + +create = update diff --git a/hostname-html.lsp b/hostname-html.lsp new file mode 100644 index 0000000..699f813 --- /dev/null +++ b/hostname-html.lsp @@ -0,0 +1,2 @@ +<? local view = ... ?> +<p>The Hostname is <?= view.hostname.value ?></p> diff --git a/hostname-model.lua b/hostname-model.lua new file mode 100644 index 0000000..9638ad7 --- /dev/null +++ b/hostname-model.lua @@ -0,0 +1,24 @@ +-- hostname model methods +module (..., package.seeall) + +-- no initializer in model - use controller.init for that + + +get = function (self) + local f = io.popen("/bin/hostname") + local n = f:read("*a") or "unknown" + f:close() + return (cfe{value=n, name="hostname"}) +end + + +set = function (self, name) + local f = io.open ("/etc/hostname", "w") + if f then + f:write(name.value) + f:close() + end + f = io.popen("/bin/hostname -F /etc/hostname") + f:close() + return get(self) +end diff --git a/hostname.menu b/hostname.menu new file mode 100644 index 0000000..fb02485 --- /dev/null +++ b/hostname.menu @@ -0,0 +1,3 @@ +# Prefix and controller are already known at this point +# Cat Group Tab Action +Setup Hostname Define read diff --git a/interfaces-controller.lua b/interfaces-controller.lua new file mode 100644 index 0000000..be07e02 --- /dev/null +++ b/interfaces-controller.lua @@ -0,0 +1,171 @@ +-- the interfaces controller + +module (..., package.seeall) + +-- Cause an http redirect to our "read" action +-- We use the self.conf table because it already has prefix,controller,etc +-- The redir code is defined in the application error handler (acf-controller) +local list_redir = function (self) + self.conf.action = "read" + self.conf.type = "redir" + error (self.conf) +end + +local pvt = {} +mvc= {} +mvc.on_load = function(self, parent) + -- If they try to run a bogus action, send them to read + if ( rawget(self.worker, self.conf.action) == nil ) then + list_redir(self) + end + + pvt.parent_on_exec = parent.worker.mvc.post_exec + + logit ("interfaces controller on_load has finished") + +end + + +mvc.pre_exec = function (self) + logit ("interfaces-controller pre_exec activated") + -- pvt.parent_on_exec () + +end + +mvc.post_exec = function ( self ) + logit ("interfaces-controller post_exec activated") + return pvt.parent_on_exec() +end + +read = function (self ) + local iface = self.model.get_all_interfaces() + -- these actions are available for each interface + -- **FIXME** This is technically wrong. The controller should pass + -- the "script" "prefix" "controller" "action" as separate fields, + -- since the view could be a cli, not a web interface. the VIEW + -- should make a uri from these these elements. + local actions = { link = ENV["SCRIPT_NAME"] .. self.conf.prefix .. + self.conf.controller .. "/", + action = { "create", "read", "update", "delete" } } + return ( { actions = actions, iface = iface } ) +end + + +-- Accepts form info +-- Returns a cfe object (the form) +update = function(self) + local iface = self.clientdata.iface or "" + local result + local data + + -- If interface is not found, return to list + result, data = self.model.get_iface_by_name ( iface ) + if result == false then + list_redir(self) + end + + -- If the "cmd" button was used, then attempt to do the update + if self.clientdata.cmd then + -- update the iface info the form gave us + for k,v in pairs (data) do + if self.clientdata[k] then + data[k].value = self.clientdata[k] + end + end + + result, data = self.model.update_iface_by_name ( data, data.name.value ) + + -- If validation worked then return to list + if result == true then + list_redir(self) + end + end + + -- If we reach this point in the function, then we are providing a form + -- for the user to edit (either first time in, or validation failed) + + -- Add a command button + data.cmd = cfe({type="action", value="save", label="action"}) + + return ( cfe ({type="form", + option={ script=ENV["SCRIPT_NAME"], + prefix=self.conf.prefix, + controller = self.conf.controller, + action = "update", + extra = "?iface=" .. data.name.value }, + value = data} ) ) +end + + + +delete = function(self) + local iface = self.clientdata.iface or "" + local result + local data + + -- If the interface doesn't exist, return to the list now + result, data = self.model.get_iface_by_name ( iface ) + if result == false then + list_redir(self) + end + + + -- If the cmd button was pressed, then commit the change + if self.clientdata.cmd then + if self.clientdata.cmd == "delete" then + self.model.delete_iface_by_name (iface) + end + list_redir(self) + end + + -- Otherwise, return a form + local me = {} + me.iface = cfe({value = self.clientdata.iface }) + me.action1 = cfe({name="cmd", type="action", value="delete", label="action"}) + me.action2 = cfe({name="cmd", type="action", value="cancel", label="action"}) + + return ( cfe ({type="form", + option={ script=ENV["SCRIPT_NAME"], + prefix=self.conf.prefix, + controller = self.conf.controller, + action = "delete", + extra = "?iface=" .. (self.clientdata.iface or "") }, + value = me} ) ) +end + + + +create = function(self) + local iface = self.clientdata.iface or "" + local index = 0 + local result, data = self.model.get_iface_by_name () + + -- If the "cmd" button was used, then attempt to do the insert + if self.clientdata.cmd then + for k,v in pairs (data) do + if self.clientdata[k] then + data[k].value = self.clientdata[k] + end + end + + result, data = self.model.create_iface_by_name ( iface, data ) + if result then + list_redir(self) + end + end + + -- If we reach this point in the function, we are providing a form + + -- Add a command button + data.cmd = cfe({type="action", value="save", label="action"}) + + return ( cfe ({type="form", + option={ script=ENV["SCRIPT_NAME"], + prefix=self.conf.prefix, + controller = self.conf.controller, + action = "create", + extra = "?iface=" .. iface or "" }, + value = data} ) ) +end + + diff --git a/interfaces-create-html.lsp b/interfaces-create-html.lsp new file mode 100644 index 0000000..56e9790 --- /dev/null +++ b/interfaces-create-html.lsp @@ -0,0 +1,37 @@ +<? local form = ... + require ("html") ?> +<h1>Create Interface</h1> + +<form action="<?= form.option.script .. "/" .. form.option.prefix .. + form.option.controller .. "/" .. form.option.action .. + form.option.extra ?>" method="POST"> +<table> + +<? local myform = form.value + +-- We redefine the list of tags from the model (iface_tags) because +-- the VIEW can choose the order to display the data. The Controller +-- does not care what order the data is presented. + +-- In this case, we don do show "name" +local tags = { "name", "comment", "method", "address", "netmask", "gateway", + "hostname", "provider", "pre-up", "up", "down", "post-down", "cmd" } + +for i,v in pairs(tags) do + local val = myform[v] ?> +<tr><td><? if (val.label) then io.write(val.label) else io.write (v) end ?></td><td> + <? if val.name == "" then + val.name = v + end + + if val.type == "longtext" then + val.cols = 60 + val.rows = 3 + end + + ?> + <?= html.form[val.type](val) ?> +</td></tr> +<? end ?> +</table> +</form> diff --git a/interfaces-delete-html.lsp b/interfaces-delete-html.lsp new file mode 100644 index 0000000..bb2634d --- /dev/null +++ b/interfaces-delete-html.lsp @@ -0,0 +1,15 @@ +<? local form = ... ?> +<h1>Delete Interface <?= form.value.iface.value ?></h1> + +<form action="<?= form.option.script .. "/" .. form.option.prefix .. + form.option.controller .. "/" .. form.option.action .. + form.option.extra ?>" method="POST"> +<table> + +<? local myform = form.value ?> +<p>Are you you sure you want to delete <?= myform.iface.value ?></p> +<center> +<input type=submit name=cmd value="<?= myform.action1.value ?>"> +<input type=submit name=cmd value="<?= myform.action2.value ?>"> +</center> +</form> diff --git a/interfaces-model.lua b/interfaces-model.lua new file mode 100644 index 0000000..b0d3b05 --- /dev/null +++ b/interfaces-model.lua @@ -0,0 +1,284 @@ +-- acf model for /etc/network/interfaces +-- Copyright(c) 2007 N. Angelacos - Licensed under terms of GPL2 +module (..., package.seeall) + +-- iface is a local (private) table with private methods for managing +-- the interfaces file. All low-level stuff is done here. It exposes +-- the iface.tags, file(raw), and array (parsed), as well as a number +-- of functions for managing the interface file. These are in a local +-- table because nobody outside the model should know anything about +-- the interfaces file (not even where it is - it could be in an LDAP +-- directory for all we know) The public module functions are defined +-- further below + +local iface = { tags = { "name", "comment", "method", "address", + "netmask", "gateway", "hostname", "provider", + "pre-up", "up", "down", "post-down" }, + methods = { "dhcp", "static", "ppp", "loopback", "manual" }, + -- lowlevel functions will insert file and array here + } + + +-- Lowlevel functions - they do things directly to iface. +iface.read_file = function () + local filename = "/etc/network/interfaces" + iface.file = cfe { name=filename, filename=filename } + local file = io.open(filename) + local rc = false + if file then + iface.file.value = file:read("*a") + -- make sure it has a terminating \n + iface.file.value = string.gsub (iface.file.value, "([^\n])$", "%1\n") + file:close() + rc = true + end + return rc +end + +iface.write_file = function () + local rc = false + local file = io.open ( iface.file.name, "w") + if file then + file:write ( iface.file.value ) + file:close() + rc = true + end + return rc + end + +function iface.iface_type ( ) + local f = {} + for k,v in pairs(iface.tags) do + f[v] = cfe { name = v } + end + + + for k,v in pairs ({"comment", "pre-up", "up", "down", "post-down"}) do + f[v].type ="longtext" + end + + f.method.type = "select" + f.method.option = iface.methods + return (f) +end + +-- return true or false + the index of iface.array matching "name" +function iface.index (name) + if name== nil or #name == 0 then + return true, 0 + end + + if iface.array == nil then + iface.unpack_interfaces () + end + + for k,v in ipairs(iface.array) do + if name == v.name.value then + return true, k + end + end + + return false, 0 +end + + +function iface.append ( self, value, prefix ) + self = self or "" + -- if we already have some values, then append a newline + if #self > 0 then self = self .. "\n" end + -- strip the prefix + local str = string.gsub(value, "^" .. ( prefix or "" ), "") + -- and append + return self .. str +end + +function iface.expand ( self, prefix ) + if #self == 0 then + return "" + end + -- force the string to end in a single linefeed + self = string.gsub (self, "\r", "") + self = string.gsub (self, "[\n]*$", "\n") + local str = "" + for line in string.gmatch ( self, ".-\n") do + if #line > 0 then + str = str .. prefix .. " " .. line + end + end + return str +end + +function iface.unpack_interfaces () + if iface.array == nil then + iface.read_file() + iface.array = {} + end + + -- call it array so we don't have to call it iface.array everywhere + local array = iface.array + local count = 0 + + array[count] = iface.iface_type() + + for line in string.gmatch ( iface.file.value, ".-\n") do + -- strip leading spaces, tabs + line = string.gsub (line, "^[%s]*", "") + line = string.gsub (line, "\n*$", "") + -- it can be #, auto, iface, or a parameter + if string.match(line, "^#") then + array[count].comment.value = + iface.append(array[count].comment.value, line , "#%s*" ) + elseif string.match(line, "^auto") then + -- do nothing + elseif string.match(line, "^iface") then + count = count + 1 + array[count] = iface.iface_type() + -- iface <name> [inet | ipx] <method> -- we assume inet + array[count].name.value, + array[count].method.value = + string.match(line, "%w+%s+(%w+)%s+%w+%s+(%w+)") + else -- it must be some kind of parameter + local param, val = + string.match(line, "(%S+)%s*(.*)$") + if (param) then + array[count][param].value = + iface.append (array[count][param].value, val) + end + end + end + + -- now move the comments to go with the interface + for n = count,1,-1 do + array[n].comment.value = array[n-1].comment.value + end + + return array +end + +function iface.pack_interfaces() + local str = "" + for n = 1,#iface.array,1 do + local me = iface.array[n] + for k,v in pairs (iface.tags) do + if v == "comment" then + str = str .. "\n" .. iface.expand ( me[v].value, "#") + elseif v == "method" then + str = str .. string.format ("\nauto %s\niface %s inet %s\n", + me.name.value, me.name.value, + me.method.value) + elseif v == "name" then + -- nothing + else + str = str .. iface.expand( me[v].value, "\t" .. v) + end + end + end + return str +end + +function iface.commit() + iface.file.value = iface.pack_interfaces() + return iface.write_file() +end + + +function iface.add_after ( name, def ) + -- if the new if.name is already in the table, then fail + local rc, idx = iface.index(def.name.value) + if idx > 0 then + def.name.errtxt = "This interface is already defined" + return false, def + end + + -- if the name to insert after doesn't exist, just fail + rc, idx = iface.index(name) + if rc == false then + return false, def + end + + rc, def = iface.validate (def) + if rc == false then + return rc, def + end + + table.insert( iface.array, idx+1, def ) + return iface.commit() , def +end + +function iface.read ( name ) + -- if the name is blank, then return an empty def + local rc, idx = iface.index(name or "") + if name == nil or #name == 0 or rc == false then + return rc, iface.iface_type() + end + return iface.commit(), iface.array[idx] +end + + +function iface.update ( def) + -- if the def by that name doesn't exist, fail + local rc, idx = iface.index(def.name.value or "" ) + if idx == 0 then + def.name.errtxt = "This is an invalid interface name" + return false, def + end + + rc, def = iface.validate ( def ) + if rc == false then + return rc, def + end + + iface.array[idx] = def + return iface.commit(), def +end + +function iface.delete (name ) + local rc, idx = iface.index(name or "" ) + if idx == 0 then + rc = false + else + table.remove (iface.array, idx ) + end + return iface.commit() +end + + +iface.validate = function ( def ) + if #def.name.value == 0 then + iface.name.errtxt = "The interface must have a name" + end + def.method.errtxt = "Method specified is invalid" + for k,v in pairs (iface.methods) do + if def.method.value == v then + def.method.errtxt = "" + end + end + -- More validation tests go here --- + -- + local rc = true + for k,v in pairs(def) do + if #def[k].errtxt > 0 then result = false end + end + + return result, def +end + +------------------------------------------------------------------------------- +-- Public Methods +------------------------------------------------------------------------------- + +get_all_interfaces = iface.unpack_interfaces + +get_iface_by_name = iface.read + +create_iface_by_name = iface.add_after + +update_iface_by_name = function (def, name ) + -- make sure name we think we are updating is name we are updating + def.name.value = name + return iface.update (def) +end + +delete_iface_by_name = iface.delete + + diff --git a/interfaces-read-html.lsp b/interfaces-read-html.lsp new file mode 100644 index 0000000..0b377bb --- /dev/null +++ b/interfaces-read-html.lsp @@ -0,0 +1,34 @@ +<? local view = ... ?> +<h1>Interfaces file</h1> + +<table border=1> +<? for i=0,table.maxn(view.iface) do + local iface="" + if i > 0 then do + j = view.iface[i] + iface = j.name.value ?> + <tr><td><?= j.name.value ?></td><td><?= j.method.value ?></td> <td><? + if ( j.method.value == "dhcp" ) then + io.write (j.hostname.value ) + else + io.write ( j.address.value .. "/" .. j.netmask.value ) + end ?></td><td><? + for x=3,4 do + a=view.actions.action[x] + io.write ("<a href=\"" .. view.actions.link .. a .. + "?iface=" .. j.name.value .. "\"" .. + ">" .. a .. "</a>\n " ) + end + ?></td></tr><? + end +end ?> +<tr><td></td><td></td><td></td><td><? + a = view.actions.action[1] + io.write ("<a href=\"" .. view.actions.link .. a .. + "?iface=" .. iface .. "\"" .. + ">" .. a .. "</a>\n " ) + ?></td></tr> +<? +end +?> +</table> diff --git a/interfaces-update-html.lsp b/interfaces-update-html.lsp new file mode 100644 index 0000000..ef53642 --- /dev/null +++ b/interfaces-update-html.lsp @@ -0,0 +1,36 @@ +<? local form = ... ?> +<h1>Update Interface <?= form.value.name.value ?></h1> + +<form action="<?= form.option.script .. "/" .. form.option.prefix .. + form.option.controller .. "/" .. form.option.action .. + form.option.extra ?>" method="POST"> +<table> + +<? local myform = form.value + +-- We redefine the list of tags from the model (iface_tags) because +-- the VIEW can choose the order to display the data. The Controller +-- does not care what order the data is presented. + +-- In this case, we don't show "name" - since its already known +local tags = { "comment", "method", "address", "netmask", "gateway", + "hostname", "provider", "pre-up", "up", "down", "post-down", "cmd" } + +for i,v in pairs(tags) do + local val = myform[v] ?> +<tr><td><? if (val.label) then io.write(val.label) else io.write (v) end ?></td><td> + <? if val.name == "" then + val.name = v + end + + if val.type == "longtext" then + val.cols = 60 + val.rows = 3 + end + + ?> + <?= html.form[val.type](val) ?> +</td></tr> +<? end ?> +</table> +</form> diff --git a/interfaces.menu b/interfaces.menu new file mode 100644 index 0000000..76eaed1 --- /dev/null +++ b/interfaces.menu @@ -0,0 +1,4 @@ +# Prefix and controller are already known at this point +# Cat Group Tab Action +Setup Interfaces Define read +Setup Interfaces Update update |