summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile52
-rw-r--r--README22
-rw-r--r--config.mk10
-rw-r--r--hostname-controller.lua43
-rw-r--r--hostname-html.lsp2
-rw-r--r--hostname-model.lua24
-rw-r--r--hostname.menu3
-rw-r--r--interfaces-controller.lua171
-rw-r--r--interfaces-create-html.lsp37
-rw-r--r--interfaces-delete-html.lsp15
-rw-r--r--interfaces-model.lua284
-rw-r--r--interfaces-read-html.lsp34
-rw-r--r--interfaces-update-html.lsp36
-rw-r--r--interfaces.menu4
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
diff --git a/README b/README
new file mode 100644
index 0000000..7050ef2
--- /dev/null
+++ b/README
@@ -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&nbsp;" )
+ 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&nbsp;" )
+ ?></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