<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.alpinelinux.org/w/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Ttrask</id>
	<title>Alpine Linux - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.alpinelinux.org/w/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Ttrask"/>
	<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/wiki/Special:Contributions/Ttrask"/>
	<updated>2026-04-30T07:45:16Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.40.0</generator>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=Alpine_Package_Keeper&amp;diff=14065</id>
		<title>Alpine Package Keeper</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=Alpine_Package_Keeper&amp;diff=14065"/>
		<updated>2017-10-11T21:11:28Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Explain the repository and repositories-file commandline options&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!--For searching: apk, APK--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Because Alpine Linux is designed to run from RAM, package management involves two phases:&lt;br /&gt;
* Installing / Upgrading / Deleting packages on a running system.&lt;br /&gt;
* Restoring a system to a previously configured state (e.g. after reboot), including all previously installed packages and locally modified configuration files. &#039;&#039;&#039;(RAM-Based Installs Only)&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;apk&#039;&#039;&#039; is the tool used to install, upgrade, or delete software on a running sytem.&amp;lt;br /&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;lbu&#039;&#039;&#039; is the tool used to capture the data necessary to restore a system to a previously configured state.&lt;br /&gt;
&lt;br /&gt;
This page documents the [http://git.alpinelinux.org/cgit/apk-tools.git apk tool] - See the [[Alpine_local_backup|Alpine Local Backup page]] for the lbu tool.&lt;br /&gt;
&lt;br /&gt;
= Overview =&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;apk&#039;&#039;&#039; tool has the following applets:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
| [[#Add a Package|add]] &lt;br /&gt;
| Add new packages to the running system&lt;br /&gt;
|-&lt;br /&gt;
| [[#Remove a Package|del]]&lt;br /&gt;
| Delete packages from the running system&lt;br /&gt;
|-&lt;br /&gt;
| fix &lt;br /&gt;
| Attempt to repair or upgrade an installed package &lt;br /&gt;
|-&lt;br /&gt;
| [[#Update the Package list|update]] &lt;br /&gt;
| Update the index of available packages&lt;br /&gt;
|-&lt;br /&gt;
| [[#Info on Packages|info]]&lt;br /&gt;
| Prints information about installed or available packages&lt;br /&gt;
|-&lt;br /&gt;
| [[#Search for Packages|search]] &lt;br /&gt;
| Search for packages or descriptions with wildcard patterns&lt;br /&gt;
|-&lt;br /&gt;
| [[#Upgrade a Running System|upgrade]]&lt;br /&gt;
| Upgrade the currently installed packages&lt;br /&gt;
|-&lt;br /&gt;
| [[#Cache Maintenance|cache]]&lt;br /&gt;
| Maintenance operations for locally cached package repository&lt;br /&gt;
|-&lt;br /&gt;
| version &lt;br /&gt;
| Compare version differences between installed and available packages&lt;br /&gt;
|-&lt;br /&gt;
| index &lt;br /&gt;
| create a repository index from a list of packages&lt;br /&gt;
|-&lt;br /&gt;
| fetch &lt;br /&gt;
| download (but not install) packages&lt;br /&gt;
|-&lt;br /&gt;
| audit &lt;br /&gt;
| List changes to the file system from pristine package install state&lt;br /&gt;
|-&lt;br /&gt;
| verify &lt;br /&gt;
| Verify a package signature&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
= Packages and Repositories =&lt;br /&gt;
&lt;br /&gt;
Software packages for Alpine Linux are digitally signed tar.gz archives containing programs, configuration files, and dependency metadata. They have the extension &amp;lt;code&amp;gt;.apk&amp;lt;/code&amp;gt;, and are often called &amp;quot;a-packs&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
The packages are stored in one or more &#039;&#039;repositories&#039;&#039;. A repository is simply a directory with a collection of *.apk files.  The directory must include a special index file, named {{Path|APKINDEX.tar.gz}} to be considered a repository.&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;apk&#039;&#039;&#039; utility can install packages from multiple repositories.  The list of repositories to check is stored in {{Path|/etc/apk/repositories}}, one repository per line. If you booted from a USB stick ({{Path|/media/sda1}}) or CD-ROM ({{Path|/media/cdrom}}), your repository file probably looks something like this:&lt;br /&gt;
&lt;br /&gt;
{{Cat|/etc/apk/repositories|/media/sda1/apks/}}&lt;br /&gt;
&lt;br /&gt;
In addition to local repositories, the &#039;&#039;&#039;apk&#039;&#039;&#039; utility uses &#039;&#039;&#039;busybox wget&#039;&#039;&#039; to fetch packages using &#039;&#039;http:&#039;&#039;, &#039;&#039;https:&#039;&#039; or &#039;&#039;ftp:&#039;&#039; protocols. The following is a valid repository file:&lt;br /&gt;
&lt;br /&gt;
{{Cat|/etc/apk/repositories|&lt;br /&gt;
/media/sda1/apks&lt;br /&gt;
http://dl-3.alpinelinux.org/alpine/v2.6/main&lt;br /&gt;
https://dl-3.alpinelinux.org/alpine/v2.6/main&lt;br /&gt;
ftp://dl-3.alpinelinux.org/alpine/v2.6/main&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Note|Currently, there are no public https or ftp repositories. The protocols are available for local repositories.}}&lt;br /&gt;
&lt;br /&gt;
== Repository pinning ==&lt;br /&gt;
&lt;br /&gt;
You can specify additional &amp;quot;tagged&amp;quot; repositories in {{Path|/etc/apk/repositories}}:&lt;br /&gt;
&lt;br /&gt;
{{Cat|/etc/apk/repositories|&lt;br /&gt;
http://nl.alpinelinux.org/alpine/v2.6/main&lt;br /&gt;
@edge http://nl.alpinelinux.org/alpine/edge/main&lt;br /&gt;
@testing http://nl.alpinelinux.org/alpine/edge/testing&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
After which you can &amp;quot;pin&amp;quot; dependencies to these tags using:&lt;br /&gt;
&lt;br /&gt;
{{cmd|apk add stableapp newapp@edge bleedingapp@testing}}&lt;br /&gt;
&lt;br /&gt;
Apk will now by default only use the untagged repositories, but adding a tag to specific package:&lt;br /&gt;
&lt;br /&gt;
1. will prefer the repository with that tag for the named package, even if a later version of the package is available in another repository&lt;br /&gt;
&lt;br /&gt;
2. &#039;&#039;allows&#039;&#039; pulling in dependencies for the tagged package from the tagged repository (though it &#039;&#039;prefers&#039;&#039; to use untagged repositories to satisfy dependencies if possible)&lt;br /&gt;
&lt;br /&gt;
== Commandline repository options ==&lt;br /&gt;
&lt;br /&gt;
By default, the &#039;&#039;&#039;apk&#039;&#039;&#039; utility will use the system repositories for all operations. This behavior can be overridden by the following options:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
| --repositories-file REPOFILE&lt;br /&gt;
| Override the system repositories by specifying a repositories file.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;nowiki&amp;gt;-X|--repository REPO&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
| Specify a supplemental repository that will be used in addition to the system repositories. This option can be provided multiple times.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
= Update the Package list =&lt;br /&gt;
&lt;br /&gt;
Remote repositories change as packages are added and upgraded.   To get the latest list of available packages, use the &#039;&#039;update&#039;&#039; command.  The command downloads the {{Path|APKINDEX.tar.gz}} from each repository and stores it in the local cache, typically {{Path|/var/cache/apk/}}, {{Path|/var/lib/apk/}} or {{Path|/etc/apk/cache/}}.&lt;br /&gt;
&lt;br /&gt;
{{Cmd|apk update}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
fetch http://dl-3.alpinelinux.org/alpine/v2.1/main/APKINDEX.tar.gz&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Tip|If using remote repositories, it is a good idea to do an &#039;&#039;&#039;update&#039;&#039;&#039; just before doing an &#039;&#039;&#039;add&#039;&#039;&#039; or &#039;&#039;&#039;upgrade&#039;&#039;&#039; command.  That way you know you are using the latest software available.}}&lt;br /&gt;
&lt;br /&gt;
= Add a Package =&lt;br /&gt;
&lt;br /&gt;
Use &#039;&#039;&#039;add&#039;&#039;&#039; to install packages from a repository. Any necessary dependencies are also installed. If you have multiple repositories, the &#039;&#039;&#039;add&#039;&#039;&#039; command installs the newest package.&lt;br /&gt;
&lt;br /&gt;
{{Cmd|apk add openssh&lt;br /&gt;
apk add openssh openntp vim}}&lt;br /&gt;
&lt;br /&gt;
If you only have the main repository enabled in your configuration, apk will not include packages from the other repositories. To install a package from the edge/testing repository without changing your repository configuration file, use the command below. This will tell apk to use that particular repository.&lt;br /&gt;
&lt;br /&gt;
{{cmd|apk add cherokee --update-cache --repository http://dl-3.alpinelinux.org/alpine/edge/testing/ --allow-untrusted}}&lt;br /&gt;
&lt;br /&gt;
{{Note|Be careful when using third-party or the testing repository. Your system can go down.}}&lt;br /&gt;
&lt;br /&gt;
= Add a local Package =&lt;br /&gt;
&lt;br /&gt;
To install a locally available apk package, for example if this device has no internet access but you can upload apk packages directly to it, use the &#039;&#039;&#039;--allow-untrusted&#039;&#039;&#039; flag:&lt;br /&gt;
&lt;br /&gt;
{{cmd|apk add --allow-untrusted /path/to/file.apk}}&lt;br /&gt;
&lt;br /&gt;
Note that multiple packages can be given.  When installing a local package, all dependencies should also be specified.  For example:&lt;br /&gt;
&lt;br /&gt;
{{cmd|apk add --allow-untrusted /var/tig-2.2-r0.apk /var/git-2.11.1-20.apk}}&lt;br /&gt;
&lt;br /&gt;
= Remove a Package  =&lt;br /&gt;
Use &#039;&#039;&#039;del&#039;&#039;&#039; to remove a package (and dependencies that are no longer needed.)  &lt;br /&gt;
&lt;br /&gt;
{{cmd|apk del openssh&lt;br /&gt;
apk del openssh openntp vim}}&lt;br /&gt;
&lt;br /&gt;
= Upgrade a Running System =&lt;br /&gt;
&lt;br /&gt;
To upgrade &#039;&#039;all&#039;&#039; the packages of a running system, use &#039;&#039;&#039;upgrade&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
{{cmd|apk update&lt;br /&gt;
apk upgrade&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
To upgrade &#039;&#039;only a few&#039;&#039; packages, use the &#039;&#039;&#039;add&#039;&#039;&#039; command with the &#039;&#039;-u&#039;&#039; or &#039;&#039;--upgrade&#039;&#039; option:&lt;br /&gt;
&lt;br /&gt;
{{cmd|apk update&lt;br /&gt;
apk add --upgrade busybox &lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Note|Remember that when you reboot your machine, the remote repository will not be available until after networking is started. This means packages newer than your local boot media will likely not be installed after a reboot. To make an &amp;quot;upgrade&amp;quot; persist over a reboot, use a [[#Local Cache|local cache]].}}&lt;br /&gt;
&lt;br /&gt;
= Search for Packages =&lt;br /&gt;
The &#039;&#039;&#039;search&#039;&#039;&#039; command searches the repository Index files for installable packages. &lt;br /&gt;
&lt;br /&gt;
Examples:&lt;br /&gt;
* To list all packages available, along with their descriptions: {{cmd|apk search -v}}&lt;br /&gt;
* To list all packages are part of the ACF system: {{cmd|apk search -v &#039;acf*&#039; }}&lt;br /&gt;
* To list all packages that list NTP as part of their description, use the &#039;&#039;-d&#039;&#039; or &#039;&#039;--description&#039;&#039; option: {{cmd|apk search -v --description &#039;NTP&#039; }}&lt;br /&gt;
&lt;br /&gt;
= Info on Packages =&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;info&#039;&#039;&#039; command provides information on the contents of packages, their dependencies, and which files belong to a package.&lt;br /&gt;
&lt;br /&gt;
For a given package, each element can be chosen (for example, &#039;&#039;-w&#039;&#039; to show just the webpage information), or all information displayed with the &#039;&#039;-a&#039;&#039; command.&lt;br /&gt;
&lt;br /&gt;
Example: {{cmd|apk info -a zlib}}&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;zlib-1.2.5-r1 description:&#039;&#039;&#039;&lt;br /&gt;
 A compression/decompression Library&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;&#039;zlib-1.2.5-r1 webpage:&#039;&#039;&#039;&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;http://zlib.net&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;&#039;zlib-1.2.5-r1 installed size:&#039;&#039;&#039;&lt;br /&gt;
 94208&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;&#039;zlib-1.2.5-r1 depends on:&#039;&#039;&#039;&lt;br /&gt;
 libc0.9.32&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;&#039;zlib-1.2.5-r1 is required by:&#039;&#039;&#039;&lt;br /&gt;
 libcrypto1.0-1.0.0-r0&lt;br /&gt;
 apk-tools-2.0.2-r4&lt;br /&gt;
 openssh-client-5.4_p1-r2&lt;br /&gt;
 openssh-5.4_p1-r2&lt;br /&gt;
 libssl1.0-1.0.0-r0&lt;br /&gt;
 freeswitch-1.0.6-r6&lt;br /&gt;
 atop-1.25-r0 &lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;&#039;zlib-1.2.5-r1 contains:&#039;&#039;&#039;&lt;br /&gt;
 lib/libz.so.1.2.5&lt;br /&gt;
 lib/libz.so.1&lt;br /&gt;
 lib/libz.so &lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;&#039;zlib-1.2.5-r1 triggers:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
As shown in the example you can determine&lt;br /&gt;
* The &#039;&#039;&#039;description&#039;&#039;&#039; of the package (&#039;&#039;-d&#039;&#039; or &#039;&#039;--description&#039;&#039;)&lt;br /&gt;
* The &#039;&#039;&#039;webpage&#039;&#039;&#039; where the application is hosted (&#039;&#039;-w&#039;&#039; or &#039;&#039;--webpage&#039;&#039;)&lt;br /&gt;
* The &#039;&#039;&#039;size&#039;&#039;&#039; the package will require once installed (in bytes) (&#039;&#039;-s&#039;&#039; or &#039;&#039;--size&#039;&#039;)&lt;br /&gt;
* What packages are required to use this one  (&#039;&#039;&#039;depends&#039;&#039;&#039;) (&#039;&#039;-R&#039;&#039; or &#039;&#039;--depends&#039;&#039;)&lt;br /&gt;
* What packages require this one to be installed (&#039;&#039;&#039;required by&#039;&#039;&#039;) (&#039;&#039;-r&#039;&#039; or &#039;&#039;--rdepends&#039;&#039;)&lt;br /&gt;
* The &#039;&#039;&#039;contents&#039;&#039;&#039; of the package, that is, which files it installs (&#039;&#039;-L&#039;&#039; or &#039;&#039;--contents&#039;&#039;)&lt;br /&gt;
* Any &#039;&#039;&#039;triggers&#039;&#039;&#039; this package sets. (&#039;&#039;-t&#039;&#039; or &#039;&#039;--triggers&#039;&#039;) Listed here are directories that are watched; if a change happens to the directory, then the trigger script is run at the end of the apk add/delete. For example, doing a depmod once after installing all packages that add kernel modules.&lt;br /&gt;
&lt;br /&gt;
{{Tip|The &#039;&#039;&#039;info&#039;&#039;&#039; command is also useful to determine which package a file belongs to.  For example: {{cmd|apk info --who-owns /sbin/lbu}} will display&lt;br /&gt;
&lt;br /&gt;
 /sbin/lbu is owned by alpine-conf-x.x-rx&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Listing installed packages ==&lt;br /&gt;
&lt;br /&gt;
To list all installed packages, use:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;apk info&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To list all installed packages in alphabetical order, with a description of each, do:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;apk -vv info|sort&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Additional apk Commands =&lt;br /&gt;
In progress...&lt;br /&gt;
&lt;br /&gt;
= Local Cache =&lt;br /&gt;
&lt;br /&gt;
{{:Local_APK_cache}}&lt;br /&gt;
&lt;br /&gt;
= Advanced APK Usage =&lt;br /&gt;
&lt;br /&gt;
== Holding a specific package back ==&lt;br /&gt;
&lt;br /&gt;
In certain cases, you may want to upgrade a system, but keep a specific package at a back level. It is possible to add &amp;quot;sticky&amp;quot; or versioned dependencies. For instance, to hold the &#039;&#039;asterisk&#039;&#039; package to the 1.6.2 level or lower:&lt;br /&gt;
{{cmd|1=apk add asterisk=1.6.0.21-r0}}&lt;br /&gt;
or&lt;br /&gt;
{{cmd|apk add &#039;asterisk&amp;lt;1.6.1&#039;}}&lt;br /&gt;
&lt;br /&gt;
after which a {{cmd|apk upgrade}}&lt;br /&gt;
&lt;br /&gt;
will upgrade the entire system, keeping the asterisk package at the 1.6.0 or lower level&lt;br /&gt;
&lt;br /&gt;
To later upgrade to the current version,&lt;br /&gt;
&lt;br /&gt;
{{cmd|apk add &#039;asterisk&amp;gt;1.6.1&#039;}}&lt;br /&gt;
&lt;br /&gt;
[[Category:Package Manager]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=Alpine_Linux:FAQ&amp;diff=13726</id>
		<title>Alpine Linux:FAQ</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=Alpine_Linux:FAQ&amp;diff=13726"/>
		<updated>2017-08-11T02:34:47Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: s/apt/apk/g&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Image:filetypes.svg|64px|left|link=]]&lt;br /&gt;
This is a list of &#039;&#039;&#039;frequently asked questions&#039;&#039;&#039; about Alpine Linux.&amp;lt;br&amp;gt;&lt;br /&gt;
If your question is not answered on this page, use the search box above to find work in progress pages not linked here, or in case of no answer, edit this page and write down your question.&lt;br /&gt;
{{Tip| Prepare your question. Think it through. Make it simple and understandable.}} &lt;br /&gt;
&lt;br /&gt;
=General=&lt;br /&gt;
&lt;br /&gt;
To get oriented and learn what makes our distribution distinctive, see the [http://alpinelinux.org/about About page] or [[Alpine Linux:Overview|our more detailed overview]].&lt;br /&gt;
&lt;br /&gt;
== I have found a bug, where can I report it? ==&lt;br /&gt;
You can report it on the [http://bugs.alpinelinux.org/ bugtracker].&lt;br /&gt;
&lt;br /&gt;
== Are there any details about the releases available? ==&lt;br /&gt;
Yes, please check the [[Alpine Linux:Releases|Releases]] page.&lt;br /&gt;
&lt;br /&gt;
== Alpine freezes during boot from Compact Flash, how can I fix? ==&lt;br /&gt;
Most Compact Flash card readers do not support proper DMA.&amp;lt;br&amp;gt;&lt;br /&gt;
You should append &#039;&#039;&#039;nodma&#039;&#039;&#039; to the &#039;&#039;append&#039;&#039; line in {{path|syslinux.cfg}}.&lt;br /&gt;
&lt;br /&gt;
== How can I contribute? ==&lt;br /&gt;
You can contribute by:&lt;br /&gt;
* using the software and giving feedback&lt;br /&gt;
* by documenting your [http://www.alpinelinux.org Alpine Linux] experiences on this [[Main_Page|wiki]]&lt;br /&gt;
* in many other ways&lt;br /&gt;
Please visit [[Contribute|Contribute page]] to read more about this topic.&lt;br /&gt;
&lt;br /&gt;
Your contributions are highly appreciated.&lt;br /&gt;
&lt;br /&gt;
== How do I remove the CDROM? ==&lt;br /&gt;
Since the modloop loopback device is on CDROM you cannot just run &amp;lt;code&amp;gt;eject&amp;lt;/code&amp;gt;. You need to unmount the modloop first.&amp;lt;br&amp;gt;&lt;br /&gt;
Unmounting both the modloop and the CDROM in one step can be done by executing:&lt;br /&gt;
{{Cmd|/etc/init.d/modloop stop}}&lt;br /&gt;
&lt;br /&gt;
Then it&#039;s possible to eject the CDROM:&lt;br /&gt;
{{Cmd|eject}}&lt;br /&gt;
&lt;br /&gt;
== Why don&#039;t I have man pages or where is the &#039;man&#039; command? ==&lt;br /&gt;
The &amp;lt;code&amp;gt;man&amp;lt;/code&amp;gt; command and man pages are not installed by default.&lt;br /&gt;
&lt;br /&gt;
* First, install the {{pkg|man}} package:&lt;br /&gt;
: {{Cmd|apk add man}}&lt;br /&gt;
* Once that&#039;s done, install the documentation for the packages that you require man pages for.&amp;lt;br /&amp;gt;(Keep in mind, however, it&#039;s possible that not all packages will have a corresponding documentation package.)&lt;br /&gt;
: {{Cmd|apk add &#039;&#039;package&#039;&#039;-doc}}&lt;br /&gt;
: For example, say you installed {{Pkg|iptables}} and you now require its man pages:&lt;br /&gt;
: {{Cmd|apk add iptables-doc}}&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
In our example above, we installed the man pages (and other documentation) for &amp;lt;code&amp;gt;iptables&amp;lt;/code&amp;gt;. We can now read it:&lt;br /&gt;
{{Cmd|man iptables}}&lt;br /&gt;
&lt;br /&gt;
==Booting Alpine on an HP ML350 G6==&lt;br /&gt;
{{Note|This &#039;Booting Alpine on an HP ML350 G6&#039; section, only applies to [http://www.alpinelinux.org/ Alpine Linux] 1.9.3 and earlier.}}&lt;br /&gt;
[http://bugs.alpinelinux.org/issues/228 Ticket 228] on [http://bugs.alpinelinux.org/ bugs.alpinelinux.org] includes a patch that disables the kernel module hpwdt by default.&lt;br /&gt;
&lt;br /&gt;
Details: Kernel module for HP Watchdog Timer causes issues during boot.  Solution is to create an overlay (ie {{path|hpwdt.apkovl.tar.gz}}) containing {{path|/etc/modprobe.d/hpwdt}} (which contains &amp;quot;blacklist hpwdt&amp;quot;), place that on some removable media (ie USB key) and insert that during boot process.  This will insure that the offending module doesn&#039;t load and that the server will boot properly.&lt;br /&gt;
&lt;br /&gt;
==My cron jobs don&#039;t run?==&lt;br /&gt;
The cron daemon is started automatically on system boot and executes the scripts placed in the folders under {{path|/etc/periodic}} - there&#039;s a {{path|15min}} folder, plus ones for {{path|hourly}}, {{path|daily}}, {{path|weekly}} and {{path|monthly}} scripts.&lt;br /&gt;
&lt;br /&gt;
You can check whether your scripts are likely to run using the command:&lt;br /&gt;
&lt;br /&gt;
: {{cmd|run-parts --test /etc/periodic/[foldername]}} - for example: &#039;&#039;run-parts --test /etc/periodic/15min&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This command will tell you what should run but will not actually execute the scripts.&lt;br /&gt;
&lt;br /&gt;
If the results of the test are not as expected, check the following:&lt;br /&gt;
&lt;br /&gt;
* Make sure the script is executable - if unsure, issue the command : {{cmd|chmod a+x [scriptname]}}&lt;br /&gt;
* Make sure the first line of your script is :&amp;lt;pre&amp;gt;#!/bin/sh&amp;lt;/pre&amp;gt;&lt;br /&gt;
* Do not put file extensions on your script names - this stops them from working; for example: {{path|myscript}}  will run, but {{path|myscript.sh}} won&#039;t&lt;br /&gt;
&lt;br /&gt;
== What is the difference between edge and stable releases? ==&lt;br /&gt;
Stable releases are just what they sound like: initially a point-in-time snapshot of the package archives, but then maintained with bugfixes only in order to keep a stable environment.&lt;br /&gt;
&lt;br /&gt;
[[Edge]] is more of a rolling-release, with the latest and greatest packages available in the online repositories.&amp;lt;br&amp;gt;&lt;br /&gt;
Occasionally, snapshot ISO images of the then-current state of [[edge]] are made and are available for download.&amp;lt;br&amp;gt;&lt;br /&gt;
Typically these are made when there are major kernel upgrades or package upgrades that require initramfs rebuilds.&lt;br /&gt;
&lt;br /&gt;
== What kind of release of Alpine Linux are available? ==&lt;br /&gt;
Please check the [[Alpine_Linux:Releases|Releases]] page for more information.&lt;br /&gt;
&lt;br /&gt;
=Setup=&lt;br /&gt;
&lt;br /&gt;
== What is the difference between &#039;sys&#039;, &#039;data&#039;, and &#039;diskless&#039; installs when running setup-alpine (or setup-disk)? ==&lt;br /&gt;
&#039;&#039;&#039;sys:&#039;&#039;&#039; This mode is a traditional disk install. The following partitions will be created on the disk: /boot, / (filesystem root) and swap.&amp;lt;br&amp;gt;&lt;br /&gt;
This mode may be used for development boxes, desktops, virtual servers, etc.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;data:&#039;&#039;&#039; This mode uses your disk(s) for data storage, not for the operating system.  Only /var is created on disk.  The system itself will run from tmpfs (RAM).  &lt;br /&gt;
&lt;br /&gt;
Use this mode if you only want to use the disk(s) for a mailspool, databases, logs, etc.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;diskless:&#039;&#039;&#039; No disks are to be used.  [[Alpine local backup]] may still be used in this mode.&lt;br /&gt;
&lt;br /&gt;
These modes are explained further [[Installation#Basics|on the Installation page]].&lt;br /&gt;
&lt;br /&gt;
== How can I install a custom firmware in a diskless system? ==&lt;br /&gt;
&lt;br /&gt;
The modules and firmware are both special images which are mounted as read-only.&amp;lt;br&amp;gt;&lt;br /&gt;
To fix this issue you can copy the firmware directory to your writeable media (cf/usb) and copy your custom firmware to it.&amp;lt;br&amp;gt;&lt;br /&gt;
After reboot Alpine should automatically use the directory on your local storage instead of the loopback device.&lt;br /&gt;
&lt;br /&gt;
=Audio=&lt;br /&gt;
&lt;br /&gt;
== How do I play my .ogg/.mp3 files? ==&lt;br /&gt;
First, the sound card should be recognized (you must have {{path|/dev/snd/*****}} files)&lt;br /&gt;
&lt;br /&gt;
{{pkg|sox}}, {{pkg|mpg123}}, etc all use the oss sound driver, while Alpine uses ALSA drivers.&amp;lt;br&amp;gt;&lt;br /&gt;
So you need to load the snd-pcm-oss compatibility module.&amp;lt;br&amp;gt;&lt;br /&gt;
While you&#039;re at it, you might need {{pkg|aumix}} to turn up the sound volume&lt;br /&gt;
{{cmd|echo snd-pcm-oss &amp;gt;&amp;gt; /etc/modules&lt;br /&gt;
modprobe snd-pcm-oss &lt;br /&gt;
apk_add aumix sox&lt;br /&gt;
aumix (set volume settings)&lt;br /&gt;
play really_cool_song.mp3}}&lt;br /&gt;
&lt;br /&gt;
= Time and timezones =&lt;br /&gt;
&lt;br /&gt;
== How do I set the local timezone? ==&lt;br /&gt;
&lt;br /&gt;
Starting in Alpine 2.2, setting the timezone can be done through the [[Setup-alpine|setup-alpine]] script, and no manual settings should be necessary.&amp;lt;br&amp;gt;&lt;br /&gt;
If you wish to edit the timezone after installation, run the [[Alpine_setup_scripts|setup-timezone]] script.&lt;br /&gt;
&lt;br /&gt;
However, if you are using a previous version, please use the following steps:&lt;br /&gt;
&lt;br /&gt;
 /etc/timezone and the whole zoneinfo directory tree are not supported.&lt;br /&gt;
 To set the timezone, set the TZ environment variable as specified in&lt;br /&gt;
 http://www.opengroup.org/onlinepubs/007904975/basedefs/xbd_chap08.html&lt;br /&gt;
 or you may also create an /etc/TZ file of a single line, ending with a&lt;br /&gt;
 newline, containing the TZ setting.  For example&lt;br /&gt;
 echo CST6CDT &amp;gt; /etc/TZ&lt;br /&gt;
&#039;&#039;Source: http://www.uclibc.org/downloads/Glibc_vs_uClibc_Differences.txt&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For more information, see how other uClibc-based distributions do this:&lt;br /&gt;
* http://leaf.sourceforge.net/doc/buci-tz3.html&lt;br /&gt;
* http://www.sonoracomm.com/index.php?option=com_content&amp;amp;task=view&amp;amp;id=107&amp;amp;Itemid=32&lt;br /&gt;
&lt;br /&gt;
For a more complete list of timezones, please see: http://en.wikipedia.org/wiki/List_of_tz_database_time_zones&lt;br /&gt;
&lt;br /&gt;
== OpenNTPD reports an error with &amp;quot;adjtime&amp;quot; ==&lt;br /&gt;
Your log contains something like:&lt;br /&gt;
 reply from 85.214.86.126: offset 865033148.784255 delay 0.055466, next query 32s&lt;br /&gt;
 reply from 202.150.212.24: offset 865033148.779314 delay 0.400771, next query 3s&lt;br /&gt;
 adjusting local clock by 865033148.779835s                                      &lt;br /&gt;
 adjtime failed: Invalid argument    &lt;br /&gt;
&lt;br /&gt;
{{pkg|openntpd}} is supposed to make small adjustments in the time without causing time jumps.&amp;lt;br&amp;gt;&lt;br /&gt;
If the adjustment is too big then something is clearly wrong and ntpd gives up. (its actually adjtime(3) that has a limit on how big adjustments are allowed)&lt;br /&gt;
&lt;br /&gt;
You can make ntpd set the time at startup by adding &#039;&#039;-s&#039;&#039; option to ntpd. This is done by setting &#039;&#039;&#039;NTPD_OPTS=&amp;quot;-s&amp;quot;&#039;&#039;&#039; in {{path|/etc/conf.d/ntpd}}.&lt;br /&gt;
&lt;br /&gt;
== Using a cron job to keep the time in sync ==&lt;br /&gt;
Add the following to {{path|/etc/periodic/daily}} (or use another folder under the {{path|/etc/periodic}} heirarchy if you want to run the script more/less frequently)&lt;br /&gt;
&lt;br /&gt;
Example: file called {{path|do-ntp}}&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
ntpd -d -q -n -p uk.pool.ntp.org&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This queries the uk time server pool - you can modify this to suit your localisation, or just use &#039;&#039;pool.ntp.org&#039;&#039;. More info here: [http://www.pool.ntp.org/zone/@ http://www.pool.ntp.org/zone/@]&lt;br /&gt;
&lt;br /&gt;
== Windows clients reports an error when trying to sync ==&lt;br /&gt;
{{pkg|openntpd}} needs to run for a while before it is satisfied it is in sync.&lt;br /&gt;
Until then it will set a flag &amp;quot;clock not synchronized&amp;quot; and Windows will report an error while trying to sync with your {{pkg|openntpd}} server.&lt;br /&gt;
&lt;br /&gt;
Only thing to do is wait, do something else for 15-20mins and then check.&lt;br /&gt;
&lt;br /&gt;
= Packages =&lt;br /&gt;
== Can you build an apk package for ...? ==&lt;br /&gt;
Yes, we probably can.&amp;lt;br&amp;gt;&lt;br /&gt;
Please create an [https://bugs.alpinelinux.org/projects/alpine/issues/new issue] in the [https://bugs.alpinelinux.org bugtracker]. Mark it as &amp;quot;feature&amp;quot; and include a short description (one-line), an url for the home page, and an url for the source package.&lt;br /&gt;
&lt;br /&gt;
== How can I build my own package? ==&lt;br /&gt;
Please see the [[Creating an Alpine package]] page.&lt;br /&gt;
&lt;br /&gt;
== WARNING: Ignoring APKINDEX.xxxx.tar.gz ==&lt;br /&gt;
If you get &amp;lt;code&amp;gt;WARNING: Ignoring APKINDEX.xxxx.tar.gz: No such file or directory&amp;lt;/code&amp;gt; while running package related tools, check your {{path|/etc/apk/repositories}} file if an entry points to {{path|.../v2.4/testing/}}. This directory is gone.&lt;br /&gt;
&lt;br /&gt;
To check the content of the repositories file&lt;br /&gt;
{{Cmd|cat /etc/apk/repositories}}&lt;br /&gt;
&lt;br /&gt;
or &lt;br /&gt;
{{Cmd|setup-apkrepos}}&lt;br /&gt;
&lt;br /&gt;
== What does &amp;quot;required by: world[$pkgname]&amp;quot; mean? ==&lt;br /&gt;
&lt;br /&gt;
It means that the package you try to install does not exist in the repositories you have configured in &amp;lt;code&amp;gt;/etc/apk/repositories&amp;lt;/code&amp;gt;. Maybe you forgot to add community, testing or unmaintained to /etc/apk/repositories?&lt;br /&gt;
&lt;br /&gt;
== How can i find out if a certain package exists in alpine? ==&lt;br /&gt;
&lt;br /&gt;
If you want to only search repositories you have configured in /etc/apk/repositories, then &amp;lt;code&amp;gt;apk search $pkgname&amp;lt;/code&amp;gt; should get you sorted. If you want to search all repositories have a look at the [https://pkgs.alpinelinux.org/ online pkg oracle]&lt;br /&gt;
&lt;br /&gt;
= Dynamic DNS =&lt;br /&gt;
== How do I schedule a regular dynamic DNS update? ==&lt;br /&gt;
You&#039;ll want to install the {{pkg|ez-ipupdate}} package:&lt;br /&gt;
{{cmd|apk add ez-ipupdate}}&lt;br /&gt;
&lt;br /&gt;
After that, create a new file at {{path|/etc/ez-ipupdate.conf}} with the contents similar to:&lt;br /&gt;
 service-type=dyndns&lt;br /&gt;
 user=myusername:mypassword&lt;br /&gt;
 interface=eth1&lt;br /&gt;
 host=myhostname.dyndns.org&lt;br /&gt;
&lt;br /&gt;
Make the new ip cache directory:&lt;br /&gt;
{{cmd|mkdir /var/cache/ez-ipupdate&lt;br /&gt;
lbu add /var/cache/ez-ipupdate}}&lt;br /&gt;
&lt;br /&gt;
Then schedule a new cron job with this command:&lt;br /&gt;
{{cmd|echo &amp;gt;&amp;gt; /var/log/ez-ipupdate &amp;amp;&amp;amp; \&amp;lt;br&amp;gt;/bin/date &amp;gt;&amp;gt; /var/log/ez-ipupdate &amp;amp;&amp;amp; \&amp;lt;br&amp;gt;ez-ipupdate --config-file /etc/ez-ipupdate.conf -f -F /var/run/ez-ipupdate.pid \&amp;lt;br&amp;gt;  --cache-file /var/cache/ez-ipupdate/ipcache --quiet &amp;gt;&amp;gt; /var/log/ez-ipupdate 2&amp;gt;&amp;amp;1}}&lt;br /&gt;
&lt;br /&gt;
Don&#039;t forget to backup your settings!&lt;br /&gt;
{{cmd|lbu ci}}&lt;br /&gt;
&lt;br /&gt;
= Terminal =&lt;br /&gt;
&lt;br /&gt;
== How to enable/fix colors for git? ==&lt;br /&gt;
&lt;br /&gt;
The problem is not in git itself or terminal, but in the &amp;lt;tt&amp;gt;less&amp;lt;/tt&amp;gt; command.&lt;br /&gt;
Busybox’s &amp;lt;tt&amp;gt;less&amp;lt;/tt&amp;gt; doesn’t support &amp;lt;tt&amp;gt;-r&amp;lt;/tt&amp;gt; (&amp;lt;tt&amp;gt;--raw-control-chars&amp;lt;/tt&amp;gt;) and &amp;lt;tt&amp;gt;-R&amp;lt;/tt&amp;gt; (&amp;lt;tt&amp;gt;--RAW-CONTROL-CHARS&amp;lt;/tt&amp;gt;) options.&lt;br /&gt;
&lt;br /&gt;
The simplest (yet not ideal) solution is to install GNU less:&lt;br /&gt;
&lt;br /&gt;
{{cmd|apk add less}}&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=Ansible&amp;diff=13582</id>
		<title>Ansible</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=Ansible&amp;diff=13582"/>
		<updated>2017-06-07T20:24:23Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Replace command examples with use of actual ansible modules&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[http://ansible.com/ ansible] is a simple configuration management, deployment, task-execution, and multinode orchestration framework. It uses SSH for the communication between the involved systems, no server or client daemons are needed, and no additional software beside Python on client boxes is required.&lt;br /&gt;
&lt;br /&gt;
= Installation of ansible =&lt;br /&gt;
ansible is available in &#039;&#039;testing&#039;&#039;. The latest package is broken, sorry.&lt;br /&gt;
&lt;br /&gt;
{{Cmd|apk add ansible}}&lt;br /&gt;
&lt;br /&gt;
== Create a SSH key ==&lt;br /&gt;
Generate a SSH key for the managed node. It&#039;s recommended to use a key which is protected with a password.&lt;br /&gt;
&lt;br /&gt;
{{Cmd|ssh-keygen -t rsa}}&lt;br /&gt;
&lt;br /&gt;
= Managed nodes =&lt;br /&gt;
There are only minimal requirements for the clients. For every system you want to manage, you need to have the client&#039;s SSH key in the &amp;lt;code&amp;gt;authorized_keys&amp;lt;/code&amp;gt; file of the management system and Python.&lt;br /&gt;
&lt;br /&gt;
Install the Python package.&lt;br /&gt;
&lt;br /&gt;
{{Cmd|apk add python}}&lt;br /&gt;
&lt;br /&gt;
== Transfer the SSH key ==&lt;br /&gt;
There are two ways to do it. From a default Alpine installation you can use ssh and cat to do it.&lt;br /&gt;
&lt;br /&gt;
{{Cmd|&amp;lt;nowiki&amp;gt;ssh root@[IP of the management system] &#039;cat ~/.ssh/id_rsa.pub&#039; | cat - &amp;gt;&amp;gt; ~/.ssh/authorized_keys&amp;lt;/nowiki&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
If you are planning to use additional features of SSH. &amp;lt;code&amp;gt;ssh-copy-id&amp;lt;/code&amp;gt;, which is provided by the &amp;lt;code&amp;gt;openssh-client&amp;lt;/code&amp;gt; package, can help you with the key setup. &lt;br /&gt;
&lt;br /&gt;
{{Cmd|ssh-copy-id -i ~/.ssh/id_rsa.pub root@[IP of the management system]}}&lt;br /&gt;
&lt;br /&gt;
= Setup hosts =&lt;br /&gt;
Add all your remote systems to &amp;lt;code&amp;gt;/etc/ansible/hosts&amp;lt;/code&amp;gt;. For details, please refer to [http://ansible.cc/docs/patterns.html#hosts-and-groups Hosts and Groups] in the ansible documentation.&lt;br /&gt;
&lt;br /&gt;
{{Cat|/etc/ansible/hosts|&lt;br /&gt;
192.168.1.50&lt;br /&gt;
10.0.0.12&lt;br /&gt;
webserver.example.org&lt;br /&gt;
mail.example.org}}&lt;br /&gt;
&lt;br /&gt;
= First test =&lt;br /&gt;
&lt;br /&gt;
{{Cmd|$ ansible all -m ping -u you --sudo}}&lt;br /&gt;
&lt;br /&gt;
Another test is check all variables.&lt;br /&gt;
&lt;br /&gt;
{{Cmd|# ansible [IP of your Alpine Linux box] -m setup}}&lt;br /&gt;
&lt;br /&gt;
= Playbooks =&lt;br /&gt;
When writing playbooks for Alpine Linux there are modules to keep in mind:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ol&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;There is support for OpenRC, the [[Alpine_Linux_Init_System|Init System]], in the service module.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
- service:&lt;br /&gt;
   name: lighttpd&lt;br /&gt;
   enabled: yes&lt;br /&gt;
   state: started&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;There is support for [[Alpine_Linux_package_management|APK]] as of Ansible 2.0.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
- apk:&lt;br /&gt;
   name: lighttpd&lt;br /&gt;
   state: present&lt;br /&gt;
   update_cache: yes&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;There is support for the [[Alpine_Wall|Awall]] firewall as of Ansible 2.4.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
- awall:&lt;br /&gt;
   name: policyfile&lt;br /&gt;
   state: enabled&lt;br /&gt;
   activate: yes&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;If you are going to re-use playbooks from other Linux distribution, please keep in mind that Alpine Linux uses different paths for the binaries. &amp;lt;code&amp;gt;/bin/rm&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;/ol&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The [http://git.alpinelinux.org/cgit/fab/alpine-ansible/ alpine-ansible git repository] contain some example playbooks. &lt;br /&gt;
&lt;br /&gt;
[[Category:Installation]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=APKBUILD_Reference&amp;diff=13002</id>
		<title>APKBUILD Reference</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=APKBUILD_Reference&amp;diff=13002"/>
		<updated>2016-10-19T18:18:59Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Updates to install script descriptions&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;APKBUILDs are the scripts that are created in order to build Alpine packages using the [[abuild]] tool.&lt;br /&gt;
&lt;br /&gt;
This page is intended to serve as a reference for creating APKBUILDs; if this is your first time creating a package for Alpine Linux, please see [[Creating an Alpine package]].&lt;br /&gt;
&lt;br /&gt;
= Legend =&lt;br /&gt;
The following notes will assist you in understanding this document.&lt;br /&gt;
&lt;br /&gt;
In description text:&lt;br /&gt;
* If a variable is not prefixed with a &#039;&#039;$&#039;&#039;, it will be represented by italics (i.e., &#039;&#039;srcdir&#039;&#039; ).&lt;br /&gt;
* Functions will also be represented by italics, but will also end with a pair of parentheses (i.e., &#039;&#039;build()&#039;&#039; ).&lt;br /&gt;
* Shell commands will be represented &amp;lt;code&amp;gt;like this&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Variables =&lt;br /&gt;
{{Note|Variables that contain a path (e.g. &#039;&#039;$srcdir&#039;&#039; and &#039;&#039;$pkgdir&#039;&#039;) should always be quoted using double quotes (i.e., &#039;&#039;&amp;quot;$srcdir&amp;quot;&#039;&#039;).  This is done to prevent things from breaking, should the user have the APKBUILD in a directory path that contains spaces.}}&lt;br /&gt;
{{Note|All arbitrary variable and function names should be prefixed with an underscore character ( _ ) to avoid name clashes with the internals of abuild (for example, &#039;&#039;_luaversions&#039;&#039;).}}&lt;br /&gt;
&lt;br /&gt;
== abuild-defined variables ==&lt;br /&gt;
The following variables are defined by abuild:&lt;br /&gt;
&lt;br /&gt;
==== startdir ====&lt;br /&gt;
: The directory where the APKBUILD script is.&lt;br /&gt;
==== srcdir ====&lt;br /&gt;
: The directory where sources, from the &#039;&#039;source&#039;&#039; variable, are downloaded to and unpacked to.&lt;br /&gt;
==== pkgdir ====&lt;br /&gt;
: This directory should receive the files for the main package.  For example, a normal [http://en.wikipedia.org/wiki/GNU_build_system autotools] package would have &amp;lt;code&amp;gt;make DESTDIR=&amp;quot;$pkgdir&amp;quot; install&amp;lt;/code&amp;gt; in the &#039;&#039;package()&#039;&#039; function.&lt;br /&gt;
==== subpkgdir ====&lt;br /&gt;
: This directory should receive the files for a subpackage. This variable should only be used from subpackage functions.&lt;br /&gt;
==== builddir ====&lt;br /&gt;
: This variable should point to the directory inside the &#039;&#039;srcdir&#039;&#039; where the main package source is unpacked.  This is typically &#039;&#039;$srcdir/$pkgname-$pkgver&#039;&#039;.  It’s used by the default &#039;&#039;prepare()&#039;&#039; function as a working directory when applying patches.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== User-defined variables ==&lt;br /&gt;
The following variables should be defined by the user:&lt;br /&gt;
==== arch ====&lt;br /&gt;
: Package architecture(s) to build for.  Can be one of: &#039;&#039;&#039;x86, x86_64, armhf, aarch64, all&#039;&#039;&#039;, or &#039;&#039;&#039;noarch&#039;&#039;&#039;, where &#039;&#039;&#039;all&#039;&#039;&#039; means all architectures, and &#039;&#039;&#039;noarch&#039;&#039;&#039; means it&#039;s architecture-independent (e.g., a pure-python package).&lt;br /&gt;
: {{Tip|To determine if your APKBUILD can use &#039;&#039;&#039;noarch&#039;&#039;&#039;: First specify &#039;&#039;&#039;all&#039;&#039;&#039; and then build the package by executing &amp;lt;code&amp;gt;abuild -r&amp;lt;/code&amp;gt;.  Watch the output towards the end for warnings saying that &#039;&#039;&#039;noarch&#039;&#039;&#039; can be used.  If the main package and all subpackages, if you have any subpackages, give a warning saying that &#039;&#039;&#039;noarch&#039;&#039;&#039; can be used, then you can use &#039;&#039;&#039;noarch&#039;&#039;&#039;.}}&lt;br /&gt;
==== depends ====&lt;br /&gt;
: Run-time dependency package(s) that are not shared-object dependencies.  Shared objects dependencies are auto-detected and should not be specified here.&lt;br /&gt;
==== depends_dev ====&lt;br /&gt;
: Run-time dependency package(s) for the &#039;&#039;&#039;$pkgname-dev&#039;&#039;&#039; subpackage.&lt;br /&gt;
&lt;br /&gt;
: {{Note|From ncopa on IRC: To find out if you need to add a package to depends_dev have a look at *requires* in usr/lib/pkgconfig/*.pc. With libtool it gets more complicated, but we should delete the .la files. Also check if there are any  /usr/bin/*-configure #!/bin/bash #!/usr/bin/perl or Python. Sometimes scripts or similar are generated at build time (i.e autoconf automake) then you normally don&#039;t need add those to depends_dev. You can also just add all -dev makedepends to depends_dev but it will slow the build process a little bit (more build dependencies).}}&lt;br /&gt;
&lt;br /&gt;
==== install ====&lt;br /&gt;
: There are 6 different types of install scripts.  Install scripts are named &#039;&#039;&#039;$pkgname.action&#039;&#039;&#039;, where &#039;&#039;&#039;action&#039;&#039;&#039; can be:  &#039;&#039;&#039;pre-install, post-install, pre-upgrade, post-upgrade, pre-deinstall&#039;&#039;&#039;, or &#039;&#039;&#039;post-deinstall&#039;&#039;&#039;.  For example, if &#039;&#039;pkgname&#039;&#039; is set to &#039;&#039;&#039;mypackage&#039;&#039;&#039; and &#039;&#039;install&#039;&#039; is set to &#039;&#039;&#039;$pkgname.post-install&#039;&#039;&#039;, then a script named &#039;&#039;&#039;mypackage.post-install&#039;&#039;&#039; must exist along-side the APKBUILD.&lt;br /&gt;
&lt;br /&gt;
: First, a few notes regarding install scripts:&lt;br /&gt;
&amp;lt;blockquote&amp;gt;{{Note|When using install scripts, &#039;&#039;$install&#039;&#039; should be included in &#039;&#039;source&#039;&#039; so that checksums can be generated and used for the install scripts specified in &#039;&#039;install&#039;&#039;.  For example:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install=&amp;quot;$pkgname.pre-install $pkgname.post-install&amp;quot;&lt;br /&gt;
source=&amp;quot;http://....&lt;br /&gt;
       $install&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;}}&lt;br /&gt;
{{Note|Always use &amp;lt;code&amp;gt;/bin/sh&amp;lt;/code&amp;gt; for the command-line interpreter on the [http://en.wikipedia.org/wiki/Shebang_%28Unix%29 shebang line] of your install scripts.}}&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following are the different types of install scripts in detail:&lt;br /&gt;
&lt;br /&gt;
===== $pkgname.pre-install =====&lt;br /&gt;
: This script is executed &#039;&#039;before installing&#039;&#039; the package.  Typical use is when the package needs a group and a user to be created. For example:&lt;br /&gt;
&amp;lt;blockquote&amp;gt;&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
addgroup -S clamav 2&amp;gt;/dev/null&lt;br /&gt;
adduser -S -D -H -s /bin/false -G clamav -g clamav clamav 2&amp;gt;/dev/null&lt;br /&gt;
&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
{{Note|If the script exits with a failure (e.g., if the user already exists), the package will not be installed and &amp;lt;code&amp;gt;apk&amp;lt;/code&amp;gt; will exit with failure, hence the &amp;lt;code&amp;gt;exit 0&amp;lt;/code&amp;gt; at the end.}}&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== $pkgname.post-install =====&lt;br /&gt;
: This script is executed &#039;&#039;after installing&#039;&#039; the package.&lt;br /&gt;
&lt;br /&gt;
===== $pkgname.pre-upgrade =====&lt;br /&gt;
: This script is executed &#039;&#039;before upgrading/downgrading/reinstalling&#039;&#039; the package. Note that exiting with failure will not cause apk to exit with failure, but will mark the package as broken.&lt;br /&gt;
&lt;br /&gt;
===== $pkgname.post-upgrade =====&lt;br /&gt;
: This script is executed &#039;&#039;after upgrading/downgrading/reinstalling&#039;&#039; the package.&lt;br /&gt;
&lt;br /&gt;
===== $pkgname.pre-deinstall =====&lt;br /&gt;
: This script is executed &#039;&#039;before uninstalling&#039;&#039; the package.&lt;br /&gt;
: {{Note|If the script exits with failure, &amp;lt;code&amp;gt;apk&amp;lt;/code&amp;gt; will not uninstall the package.}}&lt;br /&gt;
&lt;br /&gt;
===== $pkgname.post-deinstall =====&lt;br /&gt;
: This script is executed &#039;&#039;after uninstalling&#039;&#039; the package.&lt;br /&gt;
&lt;br /&gt;
==== license ====&lt;br /&gt;
: License(s) for the package.&lt;br /&gt;
==== makedepends ====&lt;br /&gt;
: Build-time dependency package(s).&lt;br /&gt;
==== md5sums ====&lt;br /&gt;
: Checksums for the files/URLs listed in &#039;&#039;source&#039;&#039;.  The checksums are normally generated and updated by executing &amp;lt;code&amp;gt;abuild checksum&amp;lt;/code&amp;gt; and should be the last item in the APKBUILD.&lt;br /&gt;
==== options ====&lt;br /&gt;
: Build-time options for the package.  Can be: &#039;&#039;&#039;!strip&#039;&#039;&#039; - to avoid stripping symbols from binaries.&lt;br /&gt;
==== pkgdesc ====&lt;br /&gt;
: A brief, one-line description of what the package does.&lt;br /&gt;
&lt;br /&gt;
: Here&#039;s an example from the OpenSSH client package:&lt;br /&gt;
: &amp;lt;pre&amp;gt;pkgdesc=&amp;quot;Port of OpenBSD&#039;s free SSH release - client&amp;quot;&amp;lt;/pre&amp;gt;&lt;br /&gt;
==== pkggroups ====&lt;br /&gt;
: System group(s) to be created during build-time.  System group(s) should also be created in the &#039;&#039;&#039;[[APKBUILD Reference#.24pkgname.pre-install|$pkgname.pre-install]]&#039;&#039;&#039; script, so that the system group(s) are also created prior to package installation for run-time use.&lt;br /&gt;
==== pkgname ====&lt;br /&gt;
: The name of the package.  All letters should be lowercase.&lt;br /&gt;
: {{Note|When creating an APKBUILD of a module or library for another package, we use some common package prefixes, such as: &#039;&#039;lua-&#039;&#039;, &#039;&#039;perl-&#039;&#039;, &#039;&#039;php-&#039;&#039;, and &#039;&#039;py-&#039;&#039;.  Search aports for other common prefixes.}}&lt;br /&gt;
&lt;br /&gt;
==== pkgrel ====&lt;br /&gt;
: Alpine package release number.  Starts at 0 (zero).  Always increment &#039;&#039;pkgrel&#039;&#039; when making updates to an aport; reset &#039;&#039;pkgrel&#039;&#039; to 0 (zero) when incrementing &#039;&#039;pkgver&#039;&#039;.&lt;br /&gt;
==== pkgusers ====&lt;br /&gt;
: System user(s) to be created during build-time.  System user(s) should also be created in the &#039;&#039;&#039;[[APKBUILD Reference#.24pkgname.pre-install|$pkgname.pre-install]]&#039;&#039;&#039; script, so that the system user(s) are also created prior to package installation for run-time use.&lt;br /&gt;
==== pkgver ====&lt;br /&gt;
: The version of the software being packaged.&lt;br /&gt;
==== provides ====&lt;br /&gt;
: Add documentation.&lt;br /&gt;
==== replaces ====&lt;br /&gt;
: Package(s) that this package replaces.  This package will &amp;quot;take over&amp;quot; files owned by packages listed in the &#039;&#039;replaces&#039;&#039; variable.  This is useful when files move from one package to another, or when a package gets renamed.&lt;br /&gt;
==== replaces_priority ====&lt;br /&gt;
: The priority of the replaces. If multiple packages replace each other, then will the package with highest &#039;&#039;replaces_priority&#039;&#039; win.&lt;br /&gt;
==== source ====&lt;br /&gt;
: The source variable is not only used to list the remote source files to fetch, it is also used to list the local files that abuild will need in order to build the apk. Examples of such local files include: init.d files, conf.d files, install files (see [[APKBUILD Reference#install|install variable]]), patches, and all other necessary files.&lt;br /&gt;
&lt;br /&gt;
: Here are few things to note:&lt;br /&gt;
&lt;br /&gt;
:* When you are finished adding local and/or remote files to &#039;&#039;source&#039;&#039;, you can execute the following command to add their checksums to the APKBUILD file:&lt;br /&gt;
:: {{Cmd|abuild checksum}}&lt;br /&gt;
:: {{Note|When later updating the content of &#039;&#039;source&#039;&#039;, or updating a file that is listed in &#039;&#039;source&#039;&#039;, you must also update their checksums again with the same command.}}&lt;br /&gt;
&lt;br /&gt;
:* When the remote file is hosted at SourceForge, it&#039;s best to specify the special mirrors link used by SourceForge:&lt;br /&gt;
:: &amp;lt;pre&amp;gt;http://downloads.sourceforge.net/$pkgname/$pkgname-$pkgver.tar.gz&amp;lt;/pre&amp;gt;&lt;br /&gt;
:: (or similar depending on the package).&lt;br /&gt;
&lt;br /&gt;
:* You can set target filename (eg &#039;save as...&#039;) by prefixing the URI with &#039;&#039;filename::&#039;&#039;. This is useful when the remote filename is not specified in the URI (ie, does not end in &#039;/software-1.0.tar.gz&#039;), such as:&lt;br /&gt;
:: &amp;lt;pre&amp;gt;http://oss.example.org/?get=software&amp;amp;ver=1.0&amp;lt;/pre&amp;gt;&lt;br /&gt;
:: or when the filename is braindead, like githubs&#039; download tags:&lt;br /&gt;
:: &amp;lt;pre&amp;gt;https://github.com/software/software/archive/v$pkgver.tar.gz&amp;lt;/pre&amp;gt;&lt;br /&gt;
:: The above two examples needs a target filename prefix:&lt;br /&gt;
:: &amp;lt;pre&amp;gt;$pkgname-$pkgver.tar.gz::http://oss.example.org/?get=software&amp;amp;ver=$pkgver&amp;lt;/pre&amp;gt;&lt;br /&gt;
:: and:&lt;br /&gt;
:: &amp;lt;pre&amp;gt;$pkgname-$pkgver.tar.gz::https://github.com/software/software/archive/v$pkgver.tar.gz&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:* abuild currently supports the following protocols for remote file retrieval:&lt;br /&gt;
:** http&lt;br /&gt;
:** https&lt;br /&gt;
:** ftp&lt;br /&gt;
&lt;br /&gt;
:* abuild currently supports the following archive types/archive file extensions:&lt;br /&gt;
:** .tar.gz / .tgz&lt;br /&gt;
:** .tar.bz2&lt;br /&gt;
:** .tar.lzma&lt;br /&gt;
:** .tar.xz&lt;br /&gt;
:** .zip&lt;br /&gt;
&lt;br /&gt;
:: {{Note|Legacy APKBUILD scripts define &#039;&#039;source&#039;&#039; variable as &amp;quot;saveas-[brain-dead-url]/[target-filename]&amp;quot; format instead of the modern [target-filename]::[brain-dead-url].&amp;lt;br /&amp;gt;&#039;&#039;BAD&#039;&#039;:   source&amp;amp;#61;&amp;quot;saveas-http://releases.ddvtech.com/download.php?pack&amp;amp;#61;libmist_dist&amp;amp;ver&amp;amp;#61;RC/$pkgname-$pkgver.tar.gz&amp;quot;&amp;lt;br /&amp;gt;&#039;&#039;GOOD&#039;&#039;:   source&amp;amp;#61;$pkgname-$pkgver.tar.gz::http://releases.ddvtech.com/download.php?pack&amp;amp;#61;libmist_dist&amp;amp;ver&amp;amp;#61;RC&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
==== subpackages ====&lt;br /&gt;
: Subpackages built from this APKBUILD.  abuild will parse this variable and try to find a subpackage split function.  The split function must &#039;&#039;move&#039;&#039; files that do not belong in the main package, from &#039;&#039;$pkgdir&#039;&#039; to &#039;&#039;$subpkgdir&#039;&#039;.  Files and directories can also be &#039;&#039;copied&#039;&#039; from &#039;&#039;$startdir&#039;&#039; and &#039;&#039;$srcdir&#039;&#039; to &#039;&#039;$subpkgdir&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
: The split function can be specified in 1 of 3 different methods:&lt;br /&gt;
:# subpkgname:&#039;&#039;&#039;splitfunc&#039;&#039;&#039;&lt;br /&gt;
:# $pkgname-&#039;&#039;&#039;splitfunc&#039;&#039;&#039;&lt;br /&gt;
:# &#039;&#039;&#039;splitfunc&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
: {{Note|Split function names &#039;&#039;&#039;cannot&#039;&#039;&#039; use hyphens; use the first method above if the subpackage name contains a hyphen (-) character, like this: &#039;&#039;subpkg-name:subpkg_name&#039;&#039;, where &amp;lt;code&amp;gt;subpkg-name&amp;lt;/code&amp;gt; is the name of the &#039;&#039;&#039;subpackage&#039;&#039;&#039; and &amp;lt;code&amp;gt;subpkg_name&amp;lt;/code&amp;gt; is the name of the &#039;&#039;&#039;subpackage&#039;s split function&#039;&#039;&#039;.}}&lt;br /&gt;
&lt;br /&gt;
: {{Tip|For more information, see the [[APKBUILD_examples:Subpackages|Subpackages example]].}}&lt;br /&gt;
&lt;br /&gt;
==== triggers ====&lt;br /&gt;
: Apk-tools can &amp;quot;monitor&amp;quot; directories and execute a trigger if any package installed/uninstalled any file in the monitored dir. The triggers are always execute after the apk action (install, uninstall, upgrade).&lt;br /&gt;
&lt;br /&gt;
: The triggers are specified in the format: &#039;&#039;scriptname&#039;&#039;=&#039;&#039;pathlist&#039;&#039; where &#039;&#039;scriptname&#039;&#039; is the (sub)package name + .trigger suffix and pathlist is : separated list of the dirs to monitor.&lt;br /&gt;
&lt;br /&gt;
: The &#039;&#039;&#039;triggers&#039;&#039;&#039; variable must include the triggers for subpackages too if they have any.&lt;br /&gt;
&lt;br /&gt;
: It is possible to use wildcards (*) in the dir list.&lt;br /&gt;
&lt;br /&gt;
==== url ====&lt;br /&gt;
: The homepage for the package.  This is to help users find upstream documentation and other information regarding the package.&lt;br /&gt;
&lt;br /&gt;
==== install_if ====&lt;br /&gt;
:install_if can be used when a package needs to be installed when some packages are already installed or are in the dependency tree. As example we could take open-vm-tools. Currently it contains the userspace tools and separate packages for the kernel modules (grsec and vserver). When we install the userspace tools, apk should automatically install the correct kernel modules and will need to figure out for which kernel. This is where install_if jumps in. For any of the kernel modules package we would use:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;pre&amp;gt;install_if=&amp;quot;linux-${_flavor}=${_kernelver} open-vm-tools&amp;quot;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:This will automatically install the package when the specified packages are installed or are in dependency tree.&lt;br /&gt;
&lt;br /&gt;
= Functions =&lt;br /&gt;
{{Note|All functions should consider the current working directory as undefined, and should therefore use the [[APKBUILD Reference#abuild-defined_variables|abuild-defined directory variables]] to their advantage.}}&lt;br /&gt;
&lt;br /&gt;
== abuild-defined functions ==&lt;br /&gt;
The following functions are provided by abuild and can be overridden:&lt;br /&gt;
&lt;br /&gt;
==== fetch() ====&lt;br /&gt;
: Downloads remote sources listed in &#039;&#039;source&#039;&#039; to &#039;&#039;SRCDEST&#039;&#039; (&#039;&#039;SRCDEST&#039;&#039; is configured in &#039;&#039;/etc/abuild.conf&#039;&#039;) and creates symlinks in &#039;&#039;$srcdir&#039;&#039;.&lt;br /&gt;
==== unpack() ====&lt;br /&gt;
: Unpacks .tgz, .tar.gz, .tar.bz2, .tar.lzma, .tar.xz, and .zip archives in &#039;&#039;$srcdir&#039;&#039; to &#039;&#039;$srcdir&#039;&#039;.&lt;br /&gt;
==== dev() ====&lt;br /&gt;
: Subpackage function for the &#039;&#039;&#039;$pkgname-dev&#039;&#039;&#039; package.  Without specifying a custom &#039;&#039;dev()&#039;&#039; function, abuild will call it&#039;s internal &#039;&#039;dev()&#039;&#039; function, which in turn calls &#039;&#039;default_dev()&#039;&#039;, which will move &#039;&#039;&amp;quot;$pkgdir&amp;quot;/usr/include&#039;&#039;, &#039;&#039;*.a&#039;&#039;, &#039;&#039;*.la&#039;&#039; and similar files to &#039;&#039;$subpkgdir&#039;&#039;.&lt;br /&gt;
==== doc() ====&lt;br /&gt;
: Subpackage function for the &#039;&#039;&#039;$pkgname-doc&#039;&#039;&#039; package.  Without specifying a custom &#039;&#039;doc()&#039;&#039; function, abuild will call it&#039;s internal &#039;&#039;doc()&#039;&#039; function, which in turn calls &#039;&#039;default_doc()&#039;&#039;, which will move &#039;&#039;&amp;quot;$pkgdir&amp;quot;/usr/share/doc&#039;&#039;, &#039;&#039;&amp;quot;$pkgdir&amp;quot;/usr/share/man&#039;&#039; and similar to &#039;&#039;$subpkgdir&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
== User-defined functions ==&lt;br /&gt;
The following functions should be defined by the user: &lt;br /&gt;
&lt;br /&gt;
==== prepare() ====&lt;br /&gt;
: &#039;&#039;&#039;&#039;&#039;Optional&#039;&#039;.&#039;&#039;&#039;  Used for build preparation: patches, etc, should be applied here.  This function is available for your convenience.&lt;br /&gt;
==== build() ====&lt;br /&gt;
: &#039;&#039;&#039;Required.&#039;&#039;&#039;  This is the compilation stage.  This function will be called as the current user (unless the &#039;&#039;package()&#039;&#039; function is missing - for compatibility reasons).  If no compilation is needed, this function can contain a single line: &amp;lt;code&amp;gt;return 0&amp;lt;/code&amp;gt;&lt;br /&gt;
==== package() ====&lt;br /&gt;
: &#039;&#039;&#039;Required.&#039;&#039;&#039;  This is the packaging stage.  Here, the built application and support files should be installed into &#039;&#039;&#039;$pkgdir&#039;&#039;&#039;.  If this is a metapackage, this function can contain a single line: &amp;lt;code&amp;gt;mkdir -p &amp;quot;$pkgdir&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Note|Building in fakeroot will reduce performance for parallel builds dramatically.  It is for this reason that we split the build and package process into two separate functions.}}&lt;br /&gt;
&lt;br /&gt;
= Examples =&lt;br /&gt;
The [[APKBUILD examples]] page will assist you in understanding how to create an APKBUILD.&lt;br /&gt;
&lt;br /&gt;
[[Category:Development]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=Creating_an_Alpine_package&amp;diff=13001</id>
		<title>Creating an Alpine package</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=Creating_an_Alpine_package&amp;diff=13001"/>
		<updated>2016-10-19T18:16:10Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Updates to install script descriptions&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Requirements ==&lt;br /&gt;
&lt;br /&gt;
To build a package for Alpine Linux you need an Alpine Linux installation. Check the [[Installation]] page to see all available installation options.&lt;br /&gt;
&lt;br /&gt;
== Setup your system and account  ==&lt;br /&gt;
{{:Include:Setup_your_system_and_account_for_building_packages}}&lt;br /&gt;
&lt;br /&gt;
== Getting some help ==&lt;br /&gt;
&lt;br /&gt;
It might be wise to start by checking what the [[Abuild|abuild]] program can/cannot do.&lt;br /&gt;
&lt;br /&gt;
{{Cmd|abuild -h}}&lt;br /&gt;
&lt;br /&gt;
== Creating an APKBUILD file  ==&lt;br /&gt;
&lt;br /&gt;
=== Use a template APKBUILD ===&lt;br /&gt;
&lt;br /&gt;
To create the actual APKBUILD file {{Pkg|newapkbuild}} can serve you a template to start with. It will create a directory with the given package name, place an example/template APKBUILD file to the given directory, and fill some variables if those are provided. Please check the [[Package_policies| package policies]] page about naming details.&lt;br /&gt;
&lt;br /&gt;
If you doubt to which repository your package belongs to you can safely use &#039;&#039;&#039;testing&#039;&#039;&#039;. Building package in your aports/testing directory is not mandatory but this way the package is already at the right place.&lt;br /&gt;
&lt;br /&gt;
{{:Include:Newapkbuild}}&lt;br /&gt;
&lt;br /&gt;
{{Note|On older Alpine systems, abuild -c -n &#039;&#039;packagename&#039;&#039; was the way to create APKBUILD files. The &#039;packagename&#039; was a parameter to the -n option so order of -c and -n matters. }}&lt;br /&gt;
&lt;br /&gt;
[[Abuild_and_Helpers#apkbuild-cpan|apkbuild-cpan]] simplifies the creation of perl packages from CPAN and [[Abuild_and_Helpers#apkbuild-pypi|apkbuild-pypi]] ease the generation of APKBUILD files for python packages from PyPi.  &lt;br /&gt;
&lt;br /&gt;
If you are create a daemon package which needs initd scripts you can add the -c making it: &lt;br /&gt;
&lt;br /&gt;
{{Cmd|newapkbuild -c &#039;&#039;packagename&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
This will copy the sample initd and confd files to the build directory.&amp;lt;BR&amp;gt;&lt;br /&gt;
A third file sample.install file will be copied as well (we will discuss this later on).&lt;br /&gt;
&lt;br /&gt;
=== Modify your APKBUILD ===&lt;br /&gt;
Edit APKBUILD and fill in the needed info (especially pkgname, pkgver, pkgdesc, url, license, depends and source). &lt;br /&gt;
&lt;br /&gt;
If you are going to use any of the variables for directories like $pkgdir, always make sure they are double quoted like: &lt;br /&gt;
&lt;br /&gt;
 &amp;quot;$pkgdir&amp;quot;/somedir&lt;br /&gt;
&lt;br /&gt;
This will prevent issues with spaces/special characters in the future. &lt;br /&gt;
&lt;br /&gt;
{{Note|If you like syntax highlighting we suggest you to install vim. We have setup vim to recognize the APKBUILD file as a bash scripts so its easier to read them.}}&lt;br /&gt;
&lt;br /&gt;
=== APKBUILD variables/functions  ===&lt;br /&gt;
&lt;br /&gt;
==== source  ====&lt;br /&gt;
&lt;br /&gt;
The source variable is not only used to list the remote source files to fetch, it is also used to list the local files that abuild will need in order to build the apk. Examples of such local files include: init.d files, conf.d files, install files (see [[Creating an Alpine package#install|install variable]]), patches, and all other necessary files.&lt;br /&gt;
&lt;br /&gt;
Here are few things to note:&lt;br /&gt;
&lt;br /&gt;
* When you are finished adding local and/or remote files to &#039;&#039;source&#039;&#039;, you can execute the following command to add their checksums to the APKBUILD file:&lt;br /&gt;
: {{cmd|abuild checksum}}&lt;br /&gt;
: {{Note|When later updating the content of &#039;&#039;source&#039;&#039;, or updating a file that is listed in &#039;&#039;source&#039;&#039;, you must also update their checksums again with the same command.}}&lt;br /&gt;
&lt;br /&gt;
* When the remote file is hosted at SourceForge, it&#039;s best to specify the special mirrors link used by SourceForge:&lt;br /&gt;
: &amp;lt;pre&amp;gt;http://downloads.sourceforge.net/$pkgname/$pkgname-$pkgver.tar.gz&amp;lt;/pre&amp;gt;&lt;br /&gt;
: (or similar depending on the package).&lt;br /&gt;
&lt;br /&gt;
* When the remote filename is not specified in the URI (ie, does not end in &#039;/software-1.0.tar.gz&#039;), such as:&lt;br /&gt;
: &amp;lt;pre&amp;gt;http://oss.example.org/?get=software&amp;amp;ver=1.0&amp;lt;/pre&amp;gt;&lt;br /&gt;
: You must prepend &#039;${pkgname}-${pkgver}.tar.gz::&#039; to the protocol, like so:&lt;br /&gt;
: &amp;lt;pre&amp;gt;source=&amp;quot;${pkgname}-${pkgver}.tar.gz::http://oss.example.org/?get=software&amp;amp;ver=1.0&amp;quot;&amp;lt;/pre&amp;gt;&lt;br /&gt;
: This causes the file to be saved as &#039;&#039;software-1.0.tar.gz&#039;&#039; where abuild can use it, instead of &#039;&#039;?get=software&amp;amp;ver=1.0&#039;&#039;, where abuild cannot use it.&lt;br /&gt;
&lt;br /&gt;
* Some projects didn&#039;t provide a release tarball. If you point source to an tarball provided by a git web interface (gitweg, cgit, github), such as:&lt;br /&gt;
: &amp;lt;pre&amp;gt;http://repo.or.cz/w/gitstats.git/snapshot/ad7efbb9399e60cee6cb217c6b47e604174a8093.tar.gz&amp;lt;/pre&amp;gt;&lt;br /&gt;
: You will run into issues because the checksum changes when downloading on the build system.&lt;br /&gt;
&lt;br /&gt;
* abuild currently supports the following protocols for remote file retrieval:&lt;br /&gt;
** http&lt;br /&gt;
** https&lt;br /&gt;
** ftp&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--: {{Note|If the you want to download from https, you need GNU wget installed on your system.}}--&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
* abuild currently supports the following archive types/archive file extensions:&lt;br /&gt;
** .tar.gz / .tgz&lt;br /&gt;
** .tar.bz2&lt;br /&gt;
** .tar.lzma&lt;br /&gt;
** .tar.xz&lt;br /&gt;
** .zip&lt;br /&gt;
&lt;br /&gt;
==== depends &amp;amp;amp; makedepends  ====&lt;br /&gt;
&lt;br /&gt;
Depends are the actual running dependencies that a package would need when it is running. Makedepends are only needed when you are building a package. If you set a package, in depends you do not need to add it to makedepends as well. The best way to find out what the depends and makedepends of a package are is to [http://en.wikipedia.org/wiki/Rtfm RTFM]. &lt;br /&gt;
&lt;br /&gt;
No kidding, lots of important information can be found it the package INSTALL and README file (or the likes). Another good way is the run &amp;lt;code&amp;gt;./configure --help&amp;lt;/code&amp;gt; from the source directory to see which options are needed for configure to finish without errors. If you do not yet have a source directory you can create one with the command: &lt;br /&gt;
&lt;br /&gt;
{{Cmd|abuild unpack}}&lt;br /&gt;
&lt;br /&gt;
Running &amp;lt;code&amp;gt;configure&amp;lt;/code&amp;gt; will also show you how you can disable a specific option for this package. A good example is for instance &amp;quot;--disable-nls&amp;quot; which will disable native language support and thus does not depend on gettext (libiconv, glib, ...). &lt;br /&gt;
&lt;br /&gt;
Alpine likes to keep things small, so we try to disable as much as possible without losing too many features. The exact disable/enable options are decided the package builder but please try to follow Alpine&#039;s design concept as much as possible.&lt;br /&gt;
&lt;br /&gt;
An easy way of quickly finding out the build info for a package is to check Arch Linux (Alpine package management and build scripts are similar) or Gentoo Linux ebuilds (previous versions of Alpine were based on Gentoo).&lt;br /&gt;
&lt;br /&gt;
* [http://www.gentoo-portage.com Search ebuilds] &lt;br /&gt;
* [http://sources.gentoo.org/viewcvs.py/gentoo-x86/ Gentoo CVS] &lt;br /&gt;
* [http://www.archlinux.org/packages/search/ Arch Linux packages] [https://aur.archlinux.org/ Arch Linux User Repository]&lt;br /&gt;
&lt;br /&gt;
After the package is successfully compiled and created we should make sure it didn&#039;t link to any package that is not present in the &amp;lt;code&amp;gt;$depends&amp;lt;/code&amp;gt; variable. We do this by using &amp;lt;code&amp;gt;scanelf&amp;lt;/code&amp;gt;. If scanelf is not yet installed on your system you can do that by installing {{Pkg|pax-utils}}.&lt;br /&gt;
&lt;br /&gt;
{{Cmd|scanelf -nR pkg}}&lt;br /&gt;
&lt;br /&gt;
An example output of {{Pkg|libcurl}} would be: &lt;br /&gt;
&lt;br /&gt;
 ET_DYN libssl.so.0.9.8,libcrypto.so.0.9.8,libz.so.1,libc.so.0,ld-uClibc.so.0 pkg/usr/lib/libcurl.so.4.1.1&lt;br /&gt;
&lt;br /&gt;
You can see the needed files and should be able to find out which file belongs to which package.&lt;br /&gt;
&lt;br /&gt;
==== license  ====&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;license&#039;&#039;&#039; tag must reflect the license of the source code. Please check the source tarball for COPYING, LICENSE, or other files with names that indicates that it contains licensing information. Beside the license file most developer include headers in the source code files with licensing details. Please use the short name and the release number in the license tag, e.g &lt;br /&gt;
&lt;br /&gt;
{| cellpadding=&amp;quot;5&amp;quot; border=&amp;quot;1&amp;quot; class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|- &lt;br /&gt;
! Short name&lt;br /&gt;
! Full name&lt;br /&gt;
|-&lt;br /&gt;
| GPL2&lt;br /&gt;
| GNU General Public License Version 2.0&lt;br /&gt;
|-&lt;br /&gt;
| LGPL2+&lt;br /&gt;
| GNU Lesser General Public License Version 2.1&lt;br /&gt;
|-&lt;br /&gt;
| ASL 2.0&lt;br /&gt;
| Apache License Version 2.0&lt;br /&gt;
|-&lt;br /&gt;
| BSD&lt;br /&gt;
| BSD License&lt;br /&gt;
|-&lt;br /&gt;
| MIT&lt;br /&gt;
| MIT license&lt;br /&gt;
|-&lt;br /&gt;
| MPL 2.0&lt;br /&gt;
| Mozilla Public License v2.0&lt;br /&gt;
|-&lt;br /&gt;
| ZPL 2.0 &lt;br /&gt;
| Zope Public License v 2.0&lt;br /&gt;
|-&lt;br /&gt;
| PHP&lt;br /&gt;
| PHP License v3.0&lt;br /&gt;
|-&lt;br /&gt;
| zlib&lt;br /&gt;
| zlib/libpng License&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
For the GNU General Public License the &#039;+&#039; means &#039;&#039;or (at your option) any later version.&#039;&#039; which covers future releases of that license. We skip the &#039;&#039;v&#039;&#039; because it&#039;s obvious that the number shows the version. If you are unsure about the short name of a license, please check the resources below for additional information.&lt;br /&gt;
&lt;br /&gt;
* [https://wiki.archlinux.org/index.php/Licenses ArchLinux and Licensing] &lt;br /&gt;
* [https://fedoraproject.org/wiki/Licensing:Main?rd=Licensing#Good_Licenses Fedora and Licensing]&lt;br /&gt;
&lt;br /&gt;
If a package has a special/custom license we need to provide it with the release. Because we want to save space and don&#039;t like to have licenses all over our system we have decided to include the license in the doc subpackage. Please follow the following guidelines to add a proper license. Locate the license file inside the source package. Add the doc subpackage to the $subpackages variable as follows: &lt;br /&gt;
&lt;br /&gt;
 subpackages=&amp;quot;$pkgname-doc&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Add a similar line to the following to your package() function, depending on the license description file: &lt;br /&gt;
&lt;br /&gt;
 install -Dm644 COPYING &amp;quot;$pkgdir&amp;quot;/usr/share/licenses/$pkgname/COPYING&lt;br /&gt;
&lt;br /&gt;
If you follow these steps then abuild will automatically add the license to the package-doc apk for you.&lt;br /&gt;
&lt;br /&gt;
==== arch ====&lt;br /&gt;
&lt;br /&gt;
The package architecture(s) to build for.  This can be one of: &#039;&#039;x86, x86_64, all,&#039;&#039; or &#039;&#039;noarch&#039;&#039;, where &#039;&#039;all&#039;&#039; means all architectures, and &#039;&#039;noarch&#039;&#039; means it&#039;s architecture-independent (ie, a pure-python package).&lt;br /&gt;
{{Tip|To determine if your APKBUILD can use &#039;&#039;noarch&#039;&#039;, build the package for your architecture and then run &amp;quot;scanelf -R pkg&amp;quot; from the directory that the APKBUILD resides in, in order to scan for ELF files in the &#039;&#039;./pkg&#039;&#039; directory.  If you do NOT get output from this, then &#039;&#039;noarch&#039;&#039; can be used.}}&lt;br /&gt;
&lt;br /&gt;
==== url  ====&lt;br /&gt;
&lt;br /&gt;
Website address for the program. This is usefully later on when either finding documentation or other information about the package.&lt;br /&gt;
&lt;br /&gt;
==== pkgdesc  ====&lt;br /&gt;
&lt;br /&gt;
A brief, one line, description of what the package does. Useful for the package management system.&lt;br /&gt;
&lt;br /&gt;
Here is an example from apk_info for the OpenSSH client package&lt;br /&gt;
&lt;br /&gt;
 pkgdesc=&amp;quot;Port of OpenBSD&#039;s free SSH release - client&amp;quot;&lt;br /&gt;
&lt;br /&gt;
==== pkgver  ====&lt;br /&gt;
&lt;br /&gt;
Provide the release number of the package you are building.&lt;br /&gt;
&lt;br /&gt;
==== pkgrel  ====&lt;br /&gt;
&lt;br /&gt;
The $pkgrel versioning is made so if you change something to your APKBUILD file without changing the actual $pkgver you can higer pkgrel so apk tools will detect it as an update. For instance if you forget to add a dependency you can add it afterward and you can +1 pkgver so apk finds this update and add the missing dependency. When there&#039;s an upstream version changes, we reset the pkgrel to 0.&lt;br /&gt;
&lt;br /&gt;
==== pkgname  ====&lt;br /&gt;
&lt;br /&gt;
The base name of the package you are creating.  For Freeswitch 1.0.6, you would use &amp;quot;freeswitch&amp;quot;&lt;br /&gt;
&lt;br /&gt;
==== install  ====&lt;br /&gt;
&lt;br /&gt;
There are 6 different kinds of install scripts. Each script is called with the $pkgname.&#039;&#039;&amp;lt;action&amp;gt;&#039;&#039; where &#039;&#039;&amp;lt;action&amp;gt;&#039;&#039; is one of the following:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;dl&amp;gt;&lt;br /&gt;
&amp;lt;dt&amp;gt;$pkgname.pre-install&lt;br /&gt;
&amp;lt;dd&amp;gt;This script is executed before package is installed. Typical use is when package needs a group and a user to be created. For example:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
addgroup -S clamav 2&amp;gt;/dev/null&lt;br /&gt;
adduser -S -D -H -s /bin/false -G clamav -g clamav clamav 2&amp;gt;/dev/null&lt;br /&gt;
&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note the &#039;&#039;exit 0&#039;&#039; at the end. If the script exits with failure (if the user already exist), the package will not be installed and &amp;lt;code&amp;gt;apk add&amp;lt;/code&amp;gt; will exit with failure.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;dt&amp;gt;$pkgname.post-install&lt;br /&gt;
&amp;lt;dd&amp;gt;This script is executed after package is installed. Can be used to generate font cache and similar.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;dt&amp;gt;$pkgname.pre-upgrade&lt;br /&gt;
&amp;lt;dd&amp;gt;Same as pre-install but is executed before upgrading/downgrading/reinstalling an already installed package. Note that exiting with failure will not cause apk to exit with failure, but will mark the package as broken.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;dt&amp;gt;$pkgname.post-upgrade&lt;br /&gt;
&amp;lt;dd&amp;gt;Same as post-install but is executed after upgrading/downgrading/reinstalling an already installed package. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;dt&amp;gt;$pkgname.pre-deinstall&lt;br /&gt;
&amp;lt;dd&amp;gt;This script is executed before uninstalling a package. If script exits with failure apk will not uninstall the package.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;dt&amp;gt;$pkgname.post-deinstall&lt;br /&gt;
&amp;lt;dd&amp;gt;This script is executed after a package have been uninstalled. Can be used to update font caches and restore busybox links. For example:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
busybox --install -s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/dl&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the package have an pre-install and post-install script the APKBUILD should have the &#039;&#039;install&#039;&#039; variable defined and the scripts should also be added to the source variable:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
...&lt;br /&gt;
install=&amp;quot;$pkgname.pre-install $pkgname.post-install&amp;quot;&lt;br /&gt;
source=&amp;quot;http://....&lt;br /&gt;
       $install&amp;quot;&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== subpackages  ====&lt;br /&gt;
&lt;br /&gt;
$subpackages are made to split up the normal &amp;quot;make install&amp;quot; into separate packages. The most common subpackages we use are doc and dev. Because we like to keep our target system small we move documentation and development files (only needed when building packages) into separate packages. To use the specific program a user only need to install the base apk without package-doc or package-dev, but if he wants to read the manual he will need to install package-doc. &lt;br /&gt;
&lt;br /&gt;
The easiest way to find out if you need to use -dev and -doc is to first build the package without these options set and wait until the build finishes. When its finished you should have a pkg directory which is the fake root directory. Inside this directory you will see the structure as how it would be installed in / on the target system. &lt;br /&gt;
&lt;br /&gt;
To see if you need the -dev package you can run the following cmd: &lt;br /&gt;
&lt;br /&gt;
{{Cmd|find pkg/usr/ -name &#039;*.[acho]&#039; -o -name &#039;*.la&#039;}}&lt;br /&gt;
&lt;br /&gt;
If this returns any files you need to include the -dev package. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt; To see if you need the -doc package you can run the following cmd: &lt;br /&gt;
&lt;br /&gt;
{{Cmd|find pkg/usr/share -name doc -o -name man -o -name info -o -name html -o -name sgml -o -name licenses}}&lt;br /&gt;
&lt;br /&gt;
If this returns any directories you need to include the -doc package. &lt;br /&gt;
&lt;br /&gt;
===== Custom subpackages  =====&lt;br /&gt;
&lt;br /&gt;
Some software additionally has non-essential files that do not qualify as either documentation or development content. These files should be placed in their own, specialized subpackage(s). Some packages include large test suites which are only needed in specific circumstances or binaries which have depends which we prefer not to install. To handle those we create our own package/function. In the APKBUILD below the build() function we create another function: &lt;br /&gt;
&lt;br /&gt;
 test() {&lt;br /&gt;
        mkdir -p &amp;quot;$subpkgdir&amp;quot;/usr&lt;br /&gt;
        mv &amp;quot;$pkgdir&amp;quot;/usr/package-test &amp;quot;$subpkgdir&amp;quot;/usr/&lt;br /&gt;
 }&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
We also need to add the package info to $subpackages variable: &lt;br /&gt;
&lt;br /&gt;
 subpackages=&amp;quot;$pkgname-doc $pkgname-dev $pkgname-test&amp;quot;&lt;br /&gt;
&lt;br /&gt;
After we finish building the package you should see another apk called packagename-test.apk which includes the files which we moved to the $subpkgdir dir. &lt;br /&gt;
&lt;br /&gt;
The above mentioned variables can also be used in our custom function. If we want for instance to build the test() function with perl support we would add: &lt;br /&gt;
&lt;br /&gt;
 depends=&amp;quot;perl&amp;quot;&lt;br /&gt;
 makedepends=&amp;quot;perl-dev&amp;quot;&lt;br /&gt;
&lt;br /&gt;
If we would install the base package it would not install perl, but if we install the package-test package it would.&lt;br /&gt;
&lt;br /&gt;
==== Patches  ====&lt;br /&gt;
&lt;br /&gt;
Please make sure you always submit human readable patches. Way&#039;s to create them are: &lt;br /&gt;
&lt;br /&gt;
directory compare: &lt;br /&gt;
&lt;br /&gt;
{{Cmd|diff -urp original_directory new_directory &amp;amp;gt; filename.patch}}&lt;br /&gt;
&lt;br /&gt;
file compare: &lt;br /&gt;
&lt;br /&gt;
{{Cmd|diff -up original.file new.file &amp;amp;gt; filename.patch}}&lt;br /&gt;
&lt;br /&gt;
If you are going to use multiple patches for a single package, the preferred way to handle those is a loop and numbering the patches. &lt;br /&gt;
&lt;br /&gt;
 for i in &amp;quot;$srcdir&amp;quot;/*.patch; do&lt;br /&gt;
        msg &amp;quot;Applying ${i}&amp;quot;&lt;br /&gt;
        patch -p0 -i $i || return 1&lt;br /&gt;
 done&lt;br /&gt;
&lt;br /&gt;
Because multiple patches can patch the same file, we could create offset for the next patch. To make sure we always patch in a specified way we should number the patches as followed: &lt;br /&gt;
&lt;br /&gt;
 10-patch1.patch 20-patch2.patch 30-patch3.patch&lt;br /&gt;
&lt;br /&gt;
This way we are always sure patch 1 is first and if we want to add additional patches between them we can use 11,12,21,22... &lt;br /&gt;
&lt;br /&gt;
==== Configure options  ====&lt;br /&gt;
&lt;br /&gt;
Alpine has some default configure options we set by default. We use /usr for prefix to make sure everything is installed with /usr in front of it. If you notice that anything is installed in the wrong directory please run {{Cmd|./configure --help}} and see if you can set the correct location. &lt;br /&gt;
&lt;br /&gt;
We are not covering the depend switches here we have discussed this already in the depend section.&lt;br /&gt;
&lt;br /&gt;
{{Tip|Common steps for building package contain &#039;&#039;./configure&#039;&#039;, &#039;&#039;make&#039;&#039; and &#039;&#039;make install&#039;&#039;. Everyone of them should be enclosed by &#039;&#039;&amp;amp;#124;&amp;amp;#124; return 1&#039;&#039; to check exit status. Otherwise even if previous step fails e.g. &#039;&#039;./configure&#039;&#039;, next will be launched e.g. &#039;&#039;make&#039;&#039;. This complicates identifying the point where the build breaks. The same applies to installing additional files.}}&lt;br /&gt;
&lt;br /&gt;
==== Make options  ====&lt;br /&gt;
&lt;br /&gt;
If you notice weird problems when compiling or installing the package with make/make install you could try to disable [http://www.gnu.org/software/make/manual/make.html#Parallel parallel] building/installing. A normal make line would be: &lt;br /&gt;
&lt;br /&gt;
 make || return 1&lt;br /&gt;
&lt;br /&gt;
To disable parallel we use: &lt;br /&gt;
&lt;br /&gt;
 make -j1 || return 1&lt;br /&gt;
&lt;br /&gt;
We can use the same for make install. &lt;br /&gt;
&lt;br /&gt;
Because we do not want to install the package in our build environment but we want to install it in a fake root directory we need to tell &#039;make install&#039; to use another destination directory instead of &#039;/&#039;. We do this by setting a variable when we execute make install as followed: &lt;br /&gt;
&lt;br /&gt;
 make DESTDIR=&amp;quot;$pkgdir&amp;quot; install&lt;br /&gt;
&lt;br /&gt;
Please note that some Makefiles do not support this variable and will always install software in &#039;/&#039;. To make sure you do not mess up your build system NEVER run your build system as root but always use a custom user and sudo when needed. If by accident the Makefile does not support DESTDIR variable it will fail to install in our build system system directories.&lt;br /&gt;
&lt;br /&gt;
==== _builddir ====&lt;br /&gt;
If you used &amp;lt;tt&amp;gt;newapkbuild&amp;lt;/tt&amp;gt; to create your APKBUILD file, you must specify the path to your unpacked sources. Inside the sections during the prepare/build/install process &#039;&#039;_builddir&#039;&#039; is used. Most of the time a combination of &#039;&#039;$srcdir&#039;&#039; and &#039;&#039;$pkgname-$pkgver&#039;&#039; will work. When not, check the /src directory or the source tarball for the right string. Especially when you are working with automatically generated tarballs (like from github and gitorious), this needs to be adjusted.&lt;br /&gt;
&lt;br /&gt;
 _builddir=&amp;quot;$srcdir&amp;quot;/$pkgname-$pkgver&lt;br /&gt;
&lt;br /&gt;
==== Additional files  ====&lt;br /&gt;
&lt;br /&gt;
If you want/need to install additional files not mentioned above you can use the following cmd (this is an example of a conf file): &lt;br /&gt;
&lt;br /&gt;
 install -Dm644 doc/$pkgname.conf &amp;quot;$pkgdir&amp;quot;/etc/$pkgname.conf&lt;br /&gt;
&lt;br /&gt;
== Build the package  ==&lt;br /&gt;
&lt;br /&gt;
If you did not already create the checksums as mentioned above you can do so now: &lt;br /&gt;
&lt;br /&gt;
{{Cmd|cd $pkgname&lt;br /&gt;
abuild checksum}}&lt;br /&gt;
&lt;br /&gt;
It&#039;s about time we build our package. Because a build system should never have all the package installed to prevent linking to packages we don&#039;t want it to link we use a abuild recursively with the &#039;&#039;&#039;-r&#039;&#039;&#039; switch. It will install all dependency&#039;s from your repository and builds it, afterwards it will uninstall all those depending packages again. You could also use the &#039;&#039;&#039;-R&#039;&#039;&#039; switch which would build your package including the dependency packages. &lt;br /&gt;
&lt;br /&gt;
{{Cmd|abuild -r}}&lt;br /&gt;
&lt;br /&gt;
== Commit your work  ==&lt;br /&gt;
&lt;br /&gt;
After you successfully build your package you can submit your APKBUILD to alpine git repository. &lt;br /&gt;
&lt;br /&gt;
Update your git repo, before adding new files: &lt;br /&gt;
&lt;br /&gt;
{{Cmd|cd $aportsdir&lt;br /&gt;
git pull}}&lt;br /&gt;
&lt;br /&gt;
This should pull all the changes made by others into you local git repo.&lt;br /&gt;
&lt;br /&gt;
When you think you are ready you can add your files to git: &lt;br /&gt;
&lt;br /&gt;
NOTE: when using our github repo, you can create PR&#039;s for each package. Please squash all commits into a single one per PR.&lt;br /&gt;
&lt;br /&gt;
{{Cmd|cd $aportsdir&lt;br /&gt;
git add  testing/$pkgdir (include any other files needed for the build; $pkgname.install...)&lt;br /&gt;
git commit}}&lt;br /&gt;
&lt;br /&gt;
In the commit message, add the following (remove the comments in the last four lines):&lt;br /&gt;
&lt;br /&gt;
{{Cmd| # Please enter the commit message for your changes&lt;br /&gt;
 #[snip]&lt;br /&gt;
 #&lt;br /&gt;
 testing/$pkgname: new aport   # this will be the subject line&lt;br /&gt;
                               # a blank line&lt;br /&gt;
 $pkgurl                       # homepage of project&lt;br /&gt;
 $pkgdesc                      # a one line description}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Now your changes are only available locally in your repository.&lt;br /&gt;
&lt;br /&gt;
Because you do not have push rights to the Alpine aports repository you need to create diff (patch) of the changes you made and send this patch to the &lt;br /&gt;
[http://lists.alpinelinux.org/alpine-aports/  alpine-aports mailinglist].&lt;br /&gt;
&lt;br /&gt;
To create a diff patch&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Cmd|git format-patch HEAD^}}&lt;br /&gt;
&lt;br /&gt;
or if you have sprunge, you can create a link to your patch for convenience&lt;br /&gt;
&lt;br /&gt;
{{Cmd|git format-patch HEAD^ --stdout &amp;lt;nowiki&amp;gt;|&amp;lt;/nowiki&amp;gt; sprunge}}&lt;br /&gt;
&lt;br /&gt;
== Send a patch ==&lt;br /&gt;
&lt;br /&gt;
[[Creating_patches#Only_the_last_commit_with_.27git_send-email.27|git send-email]] will do that for you.&lt;br /&gt;
&lt;br /&gt;
== See Also ==&lt;br /&gt;
* [[APKBUILD Reference]]&lt;br /&gt;
* [[APKBUILD examples]]&lt;br /&gt;
* [[Development using git]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Development]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=Freeswitch_Voicemail_On_Alpine_Linux&amp;diff=12942</id>
		<title>Freeswitch Voicemail On Alpine Linux</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=Freeswitch_Voicemail_On_Alpine_Linux&amp;diff=12942"/>
		<updated>2016-09-08T20:15:58Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Fix typo&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Overview =&lt;br /&gt;
This page describes how to install a basic voicemail server based upon Freeswitch including a web interface based upon ACF. It is built and tested on Alpine Linux 2.7, 3.0, 3.1, and the future 3.2.&lt;br /&gt;
&lt;br /&gt;
= Setup Procedure=&lt;br /&gt;
== 1. Configure the machine ==&lt;br /&gt;
Install and configure a basic Alpine Linux server with the ACF web interface.&lt;br /&gt;
 setup-alpine&lt;br /&gt;
 setup-acf&lt;br /&gt;
&lt;br /&gt;
== 2. Install Freeswitch ==&lt;br /&gt;
 apk add freeswitch freeswitch-sounds-en-us-callie-8000 freeswitch-flite acf-freeswitch acf-freeswitch-vmail&lt;br /&gt;
&lt;br /&gt;
== 3. Configure Freeswitch ==&lt;br /&gt;
We will use the Freeswitch sample configuration for the purposes of this document. It is not recommended to use this config in production, but it will allow us to quickly get voicemail up and running. Rather than installing the package, we will use apk to fetch the files.&lt;br /&gt;
 apk fetch --quiet --stdout freeswitch-sample-config | tar -C / -zvx&lt;br /&gt;
Modify the default password to avoid warnings and delays (obviously, you can choose something other than 12345 if you want)&lt;br /&gt;
 sed -i s/&amp;quot;default_password=1234&amp;quot;/&amp;quot;default_password=12345&amp;quot;/ /etc/freeswitch/vars.xml&lt;br /&gt;
Use Freeswitch XML curl to call into ACF to determine voicemail accounts. To do so, edit &#039;&#039;/etc/freeswitch/autoload_configs/xml_curl.conf.xml&#039;&#039; and add the following bindings:&lt;br /&gt;
     &amp;lt;binding name=&amp;quot;voicemaildialplan&amp;quot;&amp;gt;&lt;br /&gt;
       &amp;lt;param name=&amp;quot;gateway-url&amp;quot; value=&amp;quot;https://127.0.0.1/cgi-bin/acf/freeswitch-vmail/vmail/processdialplanxml&amp;quot; bindings=&amp;quot;dialplan&amp;quot;/&amp;gt;&lt;br /&gt;
     &amp;lt;/binding&amp;gt;&lt;br /&gt;
     &amp;lt;binding name=&amp;quot;voicemaildirectory&amp;quot;&amp;gt;&lt;br /&gt;
       &amp;lt;param name=&amp;quot;gateway-url&amp;quot; value=&amp;quot;https://127.0.0.1/cgi-bin/acf/freeswitch-vmail/vmail/processdirectoryxml&amp;quot; bindings=&amp;quot;directory&amp;quot;/&amp;gt;&lt;br /&gt;
     &amp;lt;/binding&amp;gt;&lt;br /&gt;
And modify &#039;&#039;/etc/freeswitch/autoload_configs/modules.conf.xml&#039;&#039; to load the mod_xml_curl module&lt;br /&gt;
 &amp;lt;load module=&amp;quot;mod_xml_curl&amp;quot;/&amp;gt;&lt;br /&gt;
{{Note|These following items are not needed for Alpine Linux 3.2 or later. They are needed on earlier versions due to bad default paths in the Freeswitch package.}}&lt;br /&gt;
Fix the voicemail storage directory in &#039;&#039;/etc/freeswitch/autoload_configs/voicemail.conf.xml&#039;&#039; to point to &#039;&#039;/var/lib/freeswitch/voicemail&#039;&#039;&lt;br /&gt;
 &amp;lt;param name=&amp;quot;storage-dir&amp;quot; value=&amp;quot;/var/lib/freeswitch/voicemail&amp;quot;/&amp;gt;&lt;br /&gt;
Set the sounds directory in &#039;&#039;/etc/freeswitch/vars.xml&#039;&#039; by adding the following line:&lt;br /&gt;
 &amp;lt;X-PRE-PROCESS cmd=&amp;quot;set&amp;quot; data=&amp;quot;sounds_dir=/usr/share/freeswitch/sounds&amp;quot;/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4. Start Freeswitch ==&lt;br /&gt;
Start Freeswitch and add it to the default runlevel.&lt;br /&gt;
 rc-update add freeswitch&lt;br /&gt;
 /etc/init.d/freeswitch start&lt;br /&gt;
You should now be able to dial into Freeswitch voicemail by registering a SIP user agent to one of the Freeswitch default local extensions (user=1000-1019, password=12345 as defined above) and directing a SIP call to any of the other extensions between 1000 and 1019, or the extension 4000. For each voicemail account, the default password is the same as the extension. For more details on this, please refer to Freeswitch documentation. Please keep in mind that these extensions and voicemail are part of the sample config and are not managed by ACF. We will add ACF-managed voicemail accounts below.&lt;br /&gt;
&lt;br /&gt;
== 5. Configure acf-freeswitch-vmail ==&lt;br /&gt;
Add the voicemail authenticator to the ACF configuration. This will allow voicemail users to log into ACF to check their voicemail.&lt;br /&gt;
 echo &amp;quot;authenticator = authenticator-freeswitch-vmail,authenticator-plaintext&amp;quot; &amp;gt;&amp;gt; /etc/acf/acf.conf&lt;br /&gt;
Change the ACF voicemail domain to match the domain from the sample config. You should use the IP address of your voicemail server, as this is what the sample config uses.&lt;br /&gt;
 sed -i /^domain=/d /etc/freeswitchvmail.conf&lt;br /&gt;
 echo &amp;quot;domain=IPADDRESS&amp;quot; &amp;gt;&amp;gt; /etc/freeswitchvmail.conf&lt;br /&gt;
&lt;br /&gt;
= ACF Web interface =&lt;br /&gt;
You can now browse to https://IPADDRESS to access the web interface. To log in for administration, use the root user and the system root password (this is the default configuration set up by setup-acf).&lt;br /&gt;
== Users ==&lt;br /&gt;
The Voicemail | Users tab will display all of the voicemail users that are managed through the ACF web interface. At the start, this will be empty. When you create Users in the ACF, it will modify the Freeswitch dialplan and directory (using xml_curl) to allow these users to receive and retrieve voicemail. It will also allow these users log into the ACF interface to check their voicemail and modify voicemail settings. The password defined here will control both ACF and IVR access.&lt;br /&gt;
== Voicemail ==&lt;br /&gt;
The Voicemail | Voicemail tab will allow you to view, listen to, download, forward, and delete voicemail messages.&lt;br /&gt;
== Config ==&lt;br /&gt;
The Voicemail | Config tab allows administrators to modify the acf-freeswitch-vmail configuration.&lt;br /&gt;
== Settings ==&lt;br /&gt;
The Voicemail | Settings tab allows voicemail users to modify their settings.&lt;br /&gt;
&lt;br /&gt;
= Demonstration =&lt;br /&gt;
We are now ready to create voicemail accounts with ACF. The Freeswitch sample config creates a fairly full dialplan and we do not want to stomp on any of its features, so we will demonstrate accounts in the 2100 - 2199 range, which is unused. Once again, to dial into Freeswitch you can use one of the Freeswitch default local extensions as explained above.&lt;br /&gt;
# Dial extension 2100 to verify what happens when an unsupported extension is called&lt;br /&gt;
# Logged into ACF as root, create a voicemail user with extension 2100 and some password&lt;br /&gt;
# Dial extension 2100 again to verify that you can now leave a voicemail for extension 2100&lt;br /&gt;
# Dial extension 4000 and log in as user 2100 with the password you defined above to listen to the message&lt;br /&gt;
# Log out of ACF as root and log in again as user 2100 with the password you defined above to view your message&lt;br /&gt;
&lt;br /&gt;
= Other Features =&lt;br /&gt;
== Switching From wav to mp3 Recording (Recommended) ==&lt;br /&gt;
{{Note|The mp3 format is supported in acf-freeswitch-vmail-0.6.2 and later, available on Alpine Linux 3.2 or later.}}&lt;br /&gt;
By default, Freeswitch will store voicemail recordings in wav format. Freeswitch also has the option of storing the recordings in mp3 format if mod_shout is installed. As mp3 format requires less storage/bandwidth and enjoys better support by web browsers, I would recommend making this change:&lt;br /&gt;
=== 1. Add the mod_shout module ===&lt;br /&gt;
Modify &#039;&#039;/etc/freeswitch/autoload_configs/modules.conf.xml&#039;&#039; to load the mod_shout module&lt;br /&gt;
 &amp;lt;load module=&amp;quot;mod_shout&amp;quot;/&amp;gt;&lt;br /&gt;
=== 2. Change Record Format to mp3 ===&lt;br /&gt;
 sed -i s/&#039;name=&amp;quot;file-extension&amp;quot; value=&amp;quot;wav&amp;quot;&#039;/&#039;name=&amp;quot;file-extension&amp;quot; value=&amp;quot;mp3&amp;quot;&#039;/ /etc/freeswitch/autoload_configs/voicemail.conf.xml&lt;br /&gt;
=== 3. Restart Freeswitch ===&lt;br /&gt;
 /etc/init.d/freeswitch restart&lt;br /&gt;
== Using PostgreSQL ==&lt;br /&gt;
Freeswitch-1.2.5 and acf-freeswitch-vmail-0.5.0 fully support Postgresql as the voicemail database. These versions are available in Alpine Linux 3.2 and later. To configure it:&lt;br /&gt;
=== 1. Install Postgresql ===&lt;br /&gt;
 apk add postgresql acf-postgresql&lt;br /&gt;
=== 2. Setup and Start Postgresql ===&lt;br /&gt;
 rc-update add postgresql&lt;br /&gt;
 /etc/init.d/postgresql setup&lt;br /&gt;
 /etc/init.d/postgresql start&lt;br /&gt;
=== 3. Create the Database ===&lt;br /&gt;
 psql -U postgres -c &amp;quot;CREATE DATABASE voicemail&amp;quot;&lt;br /&gt;
=== 4. Stop Freeswitch ===&lt;br /&gt;
 /etc/init.d/freeswitch stop&lt;br /&gt;
{{Note|Freeswitch does not always stop properly. You might need to run &#039;ps ax&#039; to find the process and &#039;kill&#039; it. Running &#039;/etc/init.d/freeswitch zap&#039; might also be necessary.}}&lt;br /&gt;
=== 5. Configure Freeswitch and acf-freeswitch-vmail ===&lt;br /&gt;
Edit /etc/freeswitch/autoload_configs/voicemail.conf.xml to set the odbc-dsn param to &amp;quot;pgsql://hostaddr=127.0.0.1 dbname=voicemail user=postgres password= options=&#039;&#039;&amp;quot;&lt;br /&gt;
 &amp;lt;param name=&amp;quot;odbc-dsn&amp;quot; value=&amp;quot;pgsql://hostaddr=127.0.0.1 dbname=voicemail user=postgres password= options=&#039;&#039;&amp;quot; /&amp;gt;&lt;br /&gt;
Edit /etc/freeswitchvmail.conf and set the dsn param to &amp;quot;pgsql://hostaddr=127.0.0.1 dbname=voicemail user=postgres password= options=&#039;&#039;&amp;quot;&lt;br /&gt;
 dsn=pgsql://hostaddr=127.0.0.1 dbname=voicemail user=postgres password= options=&#039;&#039;&lt;br /&gt;
=== 6. Restart Freeswitch ===&lt;br /&gt;
 /etc/init.d/freeswitch start&lt;br /&gt;
&lt;br /&gt;
[[Category:ACF]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=SIP_Provisioning_On_Alpine_Linux&amp;diff=12902</id>
		<title>SIP Provisioning On Alpine Linux</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=SIP_Provisioning_On_Alpine_Linux&amp;diff=12902"/>
		<updated>2016-08-10T20:41:35Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Added more flesh&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Draft}}&lt;br /&gt;
= Overview =&lt;br /&gt;
This page describes how to install a basic provisioning server for SIP devices based upon Postgresql and the ACF web interface. It is built and tested on Alpine Linux 2.7, 3.0, 3.1, 3.2, 3.3, and 3.4.&lt;br /&gt;
&lt;br /&gt;
This solution is implemented by the acf-provisioning package. acf-provisioning is designed as a generic provisioning service which provides one web interface (ACF) to manage and configure devices and a second web interface to serve configuration files. The design is data-driven by database entries that define classes-of-service and parameters for each device. Configuration files are generated dynamically using the database parameter values and templates. Support for a variety of SIP clients is included by default.&lt;br /&gt;
&lt;br /&gt;
= Setup Procedure=&lt;br /&gt;
== 1. Configure the machine ==&lt;br /&gt;
Install and configure a basic Alpine Linux server with the ACF web interface.&lt;br /&gt;
 setup-alpine&lt;br /&gt;
 setup-acf&lt;br /&gt;
&lt;br /&gt;
== 2. Install packages ==&lt;br /&gt;
Here are the basic packages for the provisioning functionality:&lt;br /&gt;
 apk add acf-provisioning acf-postgresql acf-lighttpd&lt;br /&gt;
In addition, you can add firmware packages for the SIP devices you want to support (depending on the version of Alpine Linux, some packages may not be available or may be available from the community repository):&lt;br /&gt;
 apk add acf-provisioning-cisco acf-provisioning-linksys acf-provisioning-polycom acf-provisioning-snom acf-provisioning-algo acf-provisioning-cyberdata&lt;br /&gt;
{{Note|If you are running-from-RAM, it is recommended to mount /var/ to a hard disk to prevent data loss. If you do so, you should fetch the firmware packages, rather than install them. This way, the firmware will not take up RAM for storage. You can use the following command: apk fetch --quiet --stdout acf-provisioning-polycom &amp;amp;#124; tar -C / -zvx}}&lt;br /&gt;
&lt;br /&gt;
== 3. Configure Lighttpd ==&lt;br /&gt;
Lighttpd is used to provide the HTTP provisioning interface for the SIP devices. The provisioning package contains a sample configuration that can be used for lighttpd:&lt;br /&gt;
 mv /etc/lighttpd/lighttpd.conf /etc/lighttpd/lighttpd.conf.orig&lt;br /&gt;
 ln -s /etc/provisioning/lighttpd.sample.conf /etc/lighttpd/lighttpd.conf&lt;br /&gt;
Modify mod_cgi.conf to treat CGI scripts as shell scripts, rather than perl:&lt;br /&gt;
 sed -i &#039;s~/usr/bin/perl&amp;quot;$~&amp;quot;~&#039; /etc/lighttpd/mod_cgi.conf&lt;br /&gt;
Start the server:&lt;br /&gt;
 /etc/init.d/lighttpd start&lt;br /&gt;
&lt;br /&gt;
== 4. Start Postgresql ==&lt;br /&gt;
The device parameter details are stored in a Postgresql database. We can use the default configuration, so we just start the service:&lt;br /&gt;
 /etc/init.d/postgresql start&lt;br /&gt;
&lt;br /&gt;
== 5. Point your SIP device to the new server for provisioning ==&lt;br /&gt;
Instructions for various manufacturers are included on separate pages:&lt;br /&gt;
*[[SIP Provisioning With ACF - Algo]]&lt;br /&gt;
*[[SIP Provisioning With ACF - Cisco]]&lt;br /&gt;
*[[SIP Provisioning With ACF - CyberData]]&lt;br /&gt;
*[[SIP Provisioning With ACF - Linksys]]&lt;br /&gt;
*[[SIP Provisioning With ACF - Polycom]]&lt;br /&gt;
*[[SIP Provisioning With ACF - Snom]]&lt;br /&gt;
&lt;br /&gt;
= ACF Web interface =&lt;br /&gt;
You can now browse to https://IPADDRESS to access the web interface. To log in for administration, use the root user and the system root password (this is the default configuration set up by setup-acf).&lt;br /&gt;
&lt;br /&gt;
== Create a Device ==&lt;br /&gt;
acf-provisioning is organized by device, not by SIP URI or extension. Thus, each physical or logical device, such as a phone or soft phone, should have its own device in the provisioning system.&lt;br /&gt;
&lt;br /&gt;
If you have properly configured your SIP device to connect to the network and to the provisioning server, it will have already attempted to make contact with the provisioning server. If that is the case, you can create the device by finding the corresponding request and clicking on &#039;&#039;&#039;Create&#039;&#039;&#039;:&lt;br /&gt;
 &#039;&#039;Applications: &#039;&#039;&#039;Provisioning&#039;&#039;&#039; &amp;gt; &#039;&#039;&#039;Requests&#039;&#039;&#039; &amp;gt; Device Request: &#039;&#039;&#039;Create&#039;&#039;&#039;&lt;br /&gt;
This will create a device with the proper device class and MAC address and redirect you to modify the device classes-of-service.&lt;br /&gt;
&lt;br /&gt;
Otherwise, you can create a device from scratch using the &#039;&#039;Create&#039;&#039; tab:&lt;br /&gt;
 &#039;&#039;Applications: &#039;&#039;&#039;Provisioning&#039;&#039;&#039; &amp;gt; &#039;&#039;&#039;Create&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Finally, there is an option on newer systems to create devices in bulk using the &#039;&#039;Bulk Create&#039;&#039; tab:&lt;br /&gt;
 &#039;&#039;Applications: &#039;&#039;&#039;Provisioning&#039;&#039;&#039; &amp;gt; &#039;&#039;&#039;Bulk Create&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Classes of Service ==&lt;br /&gt;
Provisioning Classes are used to define groups of parameters and parameter defaults to account for different device types and classes of service. The structure of classes, groups, and parameters are defined in the database, making it flexible and extensible. Class Groups define the different classes of service that may be configured for each device. The Classes define the options for each Class Group and which Parameter Groups would apply to each option. The Parameter Groups then define a group of parameters and corresponding defaults. Finally, the Parameters define all of the details of each parameter, such as type, label, description, global default, and validation.&lt;br /&gt;
&lt;br /&gt;
By default, the system defines two Class Groups - Device Model and Services. The Device Model class group contains several Classes corresponding to a variety of supported SIP devices. Obviously, different device types would support different sets of parameters. The Services class group contains classes for Office/Residential/Public/Hotline Phones. These classes mostly are used to define different default values for features. The existing Class Groups, Classes, Parameter Groups, and Parameters can all be modified through the ACF web interface, or entirely new entries can be created, to customize the system.&lt;br /&gt;
&lt;br /&gt;
[[Category:ACF]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=ACF_mvc.lua_example&amp;diff=12894</id>
		<title>ACF mvc.lua example</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=ACF_mvc.lua_example&amp;diff=12894"/>
		<updated>2016-08-03T21:50:09Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Add explanation of how the web app and acf-cli use the dispatch method with application-specific controllers&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Set the hostname with mvc.lua =&lt;br /&gt;
&lt;br /&gt;
In this example we will create a simple hostname-setting command-line application using mvc.lua.  Once the controller/model are built, you can use &#039;&#039;the same code&#039;&#039; to set the hostname via the web with a web-based application controller.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
For this example, we will assume you have root access on the linux box you are running on (preferably an Alpine Linux box!)&lt;br /&gt;
&lt;br /&gt;
== Get the mvc.lua module == &lt;br /&gt;
&lt;br /&gt;
Get the mvc.lua module from the git repository.&lt;br /&gt;
&lt;br /&gt;
 wget http://git.alpinelinux.org/cgit/acf-core/plain/lua/mvc.lua&lt;br /&gt;
&lt;br /&gt;
== Create a model and controller ==&lt;br /&gt;
&lt;br /&gt;
Create a file &#039;&#039;&#039;hostname-controller.lua&#039;&#039;&#039;, defining the functions that an &amp;quot;end user&amp;quot; could run. We will only create one action, edithostname, which will be used to read and update the hostname. Since the action makes changes to the system, it naturally takes the form of a &#039;form&#039;:&lt;br /&gt;
&lt;br /&gt;
=== hostname-controller.lua ===&lt;br /&gt;
 -- Controller for editing hostname&lt;br /&gt;
 local mymodule = {} &lt;br /&gt;
 &lt;br /&gt;
 mymodule.edithostname = function (self)&lt;br /&gt;
         return self.handle_form(self, self.model.get_hostname, self.model.set_hostname, self.clientdata, &amp;quot;Update&amp;quot;, &amp;quot;Edit Hostname&amp;quot;, &amp;quot;Hostname Updated&amp;quot;)&lt;br /&gt;
 end                                   &lt;br /&gt;
 &lt;br /&gt;
 return mymodule&lt;br /&gt;
&lt;br /&gt;
Create a file &#039;&#039;&#039;hostname-model.lua&#039;&#039;&#039;, defining the model functions to get and set the hostname.  We return a cfe table for each function including the form with the one entry for hostname:&lt;br /&gt;
&lt;br /&gt;
=== hostname-model.lua ===&lt;br /&gt;
 -- Model functions for retrieving / setting the hostname&lt;br /&gt;
 local mymodule = {}&lt;br /&gt;
 &lt;br /&gt;
 -- Create a cfe defining the form for editing the hostname and containing the current value&lt;br /&gt;
 mymodule.get_hostname = function(self, clientdata)&lt;br /&gt;
         local retval = cfe({ type=&amp;quot;group&amp;quot;, value={}, label=&amp;quot;Hostname&amp;quot; })&lt;br /&gt;
 &lt;br /&gt;
         -- Warning - io.popen has security risks, never pass user data to io.popen&lt;br /&gt;
         local f = io.popen (&amp;quot;/bin/hostname&amp;quot;)&lt;br /&gt;
         local n = f:read(&amp;quot;*a&amp;quot;) or &amp;quot;none&amp;quot;&lt;br /&gt;
         f:close()&lt;br /&gt;
         n=string.gsub(n, &amp;quot;\n$&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
         retval.value.hostname = cfe({ value=n, label=&amp;quot;Hostname&amp;quot; })&lt;br /&gt;
 &lt;br /&gt;
         return retval&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 -- Set the hostname from the value contained in the cfe created by get_hostname&lt;br /&gt;
 mymodule.set_hostname = function(self, hostnameform, action)&lt;br /&gt;
         local success = true&lt;br /&gt;
 &lt;br /&gt;
         -- Check to make sure the name is valid&lt;br /&gt;
         if (hostnameform.value.hostname.value == &amp;quot;&amp;quot;) then&lt;br /&gt;
                 success = false&lt;br /&gt;
                 hostnameform.value.hostname.errtxt = &amp;quot;Hostname must not be blank&amp;quot;&lt;br /&gt;
         elseif (#hostnameform.value.hostname.value &amp;gt; 16) then&lt;br /&gt;
                 success = false&lt;br /&gt;
                 hostnameform.value.hostname.errtxt = &amp;quot;Hostname must be 16 characters or less&amp;quot;&lt;br /&gt;
         elseif (string.find(hostnameform.value.hostname.value, &amp;quot;[^%w%_%-]&amp;quot;)) then&lt;br /&gt;
                 success = false&lt;br /&gt;
                 hostnameform.value.hostname.errtxt = &amp;quot;Hostname can contain alphanumerics only&amp;quot;&lt;br /&gt;
         end&lt;br /&gt;
 &lt;br /&gt;
         -- If it is valid, set the hostname&lt;br /&gt;
         if ( success ) then&lt;br /&gt;
                 local f = io.open(&amp;quot;/etc/hostname&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
                 if f then&lt;br /&gt;
                         f:write(hostnameform.value.hostname.value .. &amp;quot;\n&amp;quot;)&lt;br /&gt;
                         f:close()&lt;br /&gt;
                 end&lt;br /&gt;
                 -- Warning - io.popen has security risks, never pass user data to io.popen&lt;br /&gt;
                 f = io.popen (&amp;quot;/bin/hostname -F /etc/hostname&amp;quot;)&lt;br /&gt;
                 f:close()&lt;br /&gt;
         else&lt;br /&gt;
                 hostnameform.errtxt = &amp;quot;Failed to set hostname&amp;quot;&lt;br /&gt;
         end&lt;br /&gt;
 &lt;br /&gt;
         return hostnameform&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 return mymodule&lt;br /&gt;
&lt;br /&gt;
== Optionally test the model code (without mvc.lua)  ==&lt;br /&gt;
&lt;br /&gt;
If you want, you can create a &#039;&#039;&#039;test.lua&#039;&#039;&#039; script to validate the model code works on its own:&lt;br /&gt;
&lt;br /&gt;
=== test.lua ===&lt;br /&gt;
 require(&amp;quot;mvc&amp;quot;) -- Needed for cfe function definition&lt;br /&gt;
 m=require(&amp;quot;hostname-model&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 local form = m.get_hostname()&lt;br /&gt;
 form.value.hostname.value = arg[1] or &amp;quot;&amp;quot;&lt;br /&gt;
 form = m.set_hostname(nil, form)&lt;br /&gt;
 &lt;br /&gt;
 if form.errtxt then&lt;br /&gt;
         print(&amp;quot;FAILED: &amp;quot;..form.value.hostname.errtxt or form.errtxt)&lt;br /&gt;
 end&lt;br /&gt;
 form = m.get_hostname()&lt;br /&gt;
 print(form.value.hostname.value)&lt;br /&gt;
&lt;br /&gt;
You can then test this with:&lt;br /&gt;
&lt;br /&gt;
 #lua test.lua &amp;quot;Alpine&amp;quot;&lt;br /&gt;
  Alpine&lt;br /&gt;
&lt;br /&gt;
 #lua test.lua &amp;quot;Invalid Name&amp;quot;&lt;br /&gt;
  FAILED: Hostname can contain alphanumerics only&lt;br /&gt;
  Alpine&lt;br /&gt;
&lt;br /&gt;
== Add the package to the ACF framework ==&lt;br /&gt;
&lt;br /&gt;
To make the model and controller work within the ACF mvc.lua framework, we must do several things.   &lt;br /&gt;
&lt;br /&gt;
1. Install the ACF core package:&lt;br /&gt;
&lt;br /&gt;
 apk add acf-core&lt;br /&gt;
&lt;br /&gt;
Optionally, you can add the entire web-based ACF framework:&lt;br /&gt;
&lt;br /&gt;
 setup-acf&lt;br /&gt;
&lt;br /&gt;
2. Modify the ACF configuration file to look in /etc/acf/app/ for additional packages. Edit the /etc/acf/acf.conf file to add the &#039;&#039;/etc/acf/app/&#039;&#039; directory to the &#039;&#039;appdir&#039;&#039; comma-separated list:&lt;br /&gt;
&lt;br /&gt;
 appdir=/etc/acf/app/,/usr/share/acf/app/&lt;br /&gt;
&lt;br /&gt;
3. Move the model and controller to the new package directory. We will call the package &amp;quot;test&amp;quot;:&lt;br /&gt;
&lt;br /&gt;
 mkdir -p /etc/acf/app/test&lt;br /&gt;
 mv hostname-*.lua /etc/acf/app/test&lt;br /&gt;
&lt;br /&gt;
4. Test the new package using the acf-cli application:&lt;br /&gt;
&lt;br /&gt;
 # acf-cli /test/hostname/edithostname&lt;br /&gt;
 result = {}&lt;br /&gt;
 result[&amp;quot;label&amp;quot;] = &amp;quot;Edit Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;option&amp;quot;] = &amp;quot;Edit&amp;quot;&lt;br /&gt;
 result[&amp;quot;type&amp;quot;] = &amp;quot;form&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;label&amp;quot;] = &amp;quot;Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;type&amp;quot;] = &amp;quot;text&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;value&amp;quot;] = &amp;quot;Alpine&amp;quot;&lt;br /&gt;
 # acf-cli /test/hostname/edithostname hostname=test submit=true&lt;br /&gt;
 result = {}&lt;br /&gt;
 result[&amp;quot;descr&amp;quot;] = &amp;quot;Hostname Updated&amp;quot;&lt;br /&gt;
 result[&amp;quot;label&amp;quot;] = &amp;quot;Edit Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;option&amp;quot;] = &amp;quot;Edit&amp;quot;&lt;br /&gt;
 result[&amp;quot;type&amp;quot;] = &amp;quot;form&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;label&amp;quot;] = &amp;quot;Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;type&amp;quot;] = &amp;quot;text&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;value&amp;quot;] = &amp;quot;test&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Note that the action string passed to acf-cli is of the form &amp;quot;/prefix/controller/action&amp;quot;. The prefix is the path within /etc/acf/app where the controller may be found, so &amp;quot;/test/&amp;quot; for our case. The controller is determined from the controller file name, so &amp;quot;hostname&amp;quot; for our case. And, the action corresponds to the function within the controller to call, so &amp;quot;edithostname&amp;quot; for our case. Also note that, in the two examples above, the first case reads the existing hostname and the second case updates it. The output of the acf-cli application is a serialized version of the cfe form, which is good for testing but not too useful in real life.&lt;br /&gt;
&lt;br /&gt;
== Enable the package in the ACF web interface ==&lt;br /&gt;
&lt;br /&gt;
1. Add the web-based ACF framework:&lt;br /&gt;
&lt;br /&gt;
 setup-acf&lt;br /&gt;
&lt;br /&gt;
2. Configure all users to have access to the new hostname action. Edit &amp;quot;/etc/acf/app/test/hostname.roles&amp;quot; file and add GUEST permission for the edithostname action:&lt;br /&gt;
&lt;br /&gt;
 echo &amp;quot;GUEST=hostname/edithostname&amp;quot; &amp;gt; /etc/acf/app/test/hostname.roles&lt;br /&gt;
&lt;br /&gt;
The new action should now be visible by browsing to &#039;&#039;https://IP-of-host/cgi-bin/acf/test/hostname/edithostname&#039;&#039;. Obviously you might want to reconsider providing GUEST access to this action, because this allows unauthenticated users to modify your hostname.&lt;br /&gt;
&lt;br /&gt;
3. Add the new hostname action to the ACF menu. Edit &amp;quot;/etc/acf/app/test/hostname.menu&amp;quot; file and add a menu item:&lt;br /&gt;
&lt;br /&gt;
 echo &amp;quot;Test Hostname Edit edithostname&amp;quot; &amp;gt; /etc/acf/app/test/hostname.menu&lt;br /&gt;
&lt;br /&gt;
You will need to log off from the ACF interface (or delete the session cookie) before the new menu item will be visible.&lt;br /&gt;
&lt;br /&gt;
== Make an MVC based application ==&lt;br /&gt;
You have two options for creating an MVC based application of your own.&lt;br /&gt;
&lt;br /&gt;
1. Use the &#039;&#039;dispatch&#039;&#039; function. This is the method used by the web interface and the acf-cli application. The action to be dispatched is passed in a string format &#039;&#039;/prefix/controller/action&#039;&#039; and the input values are passed in as a clientinfo table. Output is determined by the view.&lt;br /&gt;
&lt;br /&gt;
2. Use the &#039;&#039;new&#039;&#039; function to load the desired controller and then directly call the controller actions or model functions. Using the controller actions requires use of the clientdata structure to pass parameters. For calling the model functions, the application will call the get function to retrieve the form cfe, fill in the desired input values into the cfe, and then call the set function to submit the form and perform the desired action. The MVC application is responsible for any user interaction and display of the results.&lt;br /&gt;
&lt;br /&gt;
=== Use the Dispatch method ===&lt;br /&gt;
==== test_dispatch ====&lt;br /&gt;
 #!/usr/bin/lua&lt;br /&gt;
 -- Simple CLI based mvc application&lt;br /&gt;
 &lt;br /&gt;
 -- load the mvc module&lt;br /&gt;
 mvc = require(&amp;quot;acf.mvc&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 -- create an new &amp;quot;mvc object&amp;quot;&lt;br /&gt;
 MVC=mvc:new()&lt;br /&gt;
 &lt;br /&gt;
 -- load the config file so we can find the appdir&lt;br /&gt;
 MVC:read_config(&amp;quot;acf&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 -- dispatch the request&lt;br /&gt;
 local clientdata = {hostname=arg[1], viewtype=arg[2], submit=&amp;quot;Update&amp;quot;}&lt;br /&gt;
 MVC:dispatch(&amp;quot;/test/&amp;quot;, &amp;quot;hostname&amp;quot;, &amp;quot;edithostname&amp;quot;, clientdata)&lt;br /&gt;
 &lt;br /&gt;
 -- destroy the mvc object&lt;br /&gt;
 MVC:destroy()&lt;br /&gt;
&lt;br /&gt;
Test the application. As can be seen in the code above, the first argument is the new hostname and the second is a viewtype.&lt;br /&gt;
&lt;br /&gt;
 alpine:~# chmod 755 test_dispatch &lt;br /&gt;
 alpine:~# ./test_dispatch test&lt;br /&gt;
 test:~# ./test_dispatch alpine&lt;br /&gt;
 alpine:~#&lt;br /&gt;
&lt;br /&gt;
Note that the application functions, but there is no output. This is because there is no viewtype supplied. There is built-in support for these standard viewtypes (not all apply): html, json, stream, serialized&lt;br /&gt;
&lt;br /&gt;
 alpine:~# ./test_dispatch test serialized&lt;br /&gt;
 result = {}&lt;br /&gt;
 result[&amp;quot;descr&amp;quot;] = &amp;quot;Hostname Updated&amp;quot;&lt;br /&gt;
 result[&amp;quot;label&amp;quot;] = &amp;quot;Edit Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;option&amp;quot;] = &amp;quot;Update&amp;quot;&lt;br /&gt;
 result[&amp;quot;type&amp;quot;] = &amp;quot;form&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;label&amp;quot;] = &amp;quot;Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;type&amp;quot;] = &amp;quot;text&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;value&amp;quot;] = &amp;quot;test&amp;quot;&lt;br /&gt;
 test:~# ./test_dispatch alpine json&lt;br /&gt;
 {&amp;quot;type&amp;quot;:&amp;quot;form&amp;quot;,&amp;quot;label&amp;quot;:&amp;quot;Edit Hostname&amp;quot;,&amp;quot;value&amp;quot;:{&amp;quot;hostname&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;alpine&amp;quot;,&amp;quot;type&amp;quot;:&amp;quot;text&amp;quot;,&amp;quot;label&amp;quot;:&amp;quot;Hostname&amp;quot;}},&amp;quot;option&amp;quot;:&amp;quot;Update&amp;quot;,&amp;quot;descr&amp;quot;:&amp;quot;Hostname Updated&amp;quot;}&lt;br /&gt;
 alpine:~# &lt;br /&gt;
&lt;br /&gt;
You can also use a custom viewtype and/or view to customize the output. This relies on haserl to parse the view file, and haserl lua functions are only available when launched from haserl scripts (it would be nice if haserl were available as a standalone lua library). Haserl scripts expect to be launched by a web browser to handle CGI data, so passing input is trickier. I&#039;m sure there is a better way to do this, but this is just an example:&lt;br /&gt;
&lt;br /&gt;
==== test_haserl ====&lt;br /&gt;
 #!/usr/bin/haserl-lua5.2 --shell=lua&lt;br /&gt;
 &amp;lt;%&lt;br /&gt;
 -- Simple CLI based mvc application&lt;br /&gt;
 &lt;br /&gt;
 -- load the mvc module&lt;br /&gt;
 mvc = require(&amp;quot;acf.mvc&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 -- create an new &amp;quot;mvc object&amp;quot;&lt;br /&gt;
 MVC=mvc:new()&lt;br /&gt;
 &lt;br /&gt;
 -- load the config file so we can find the appdir&lt;br /&gt;
 MVC:read_config(&amp;quot;acf&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 -- dispatch the request&lt;br /&gt;
 local clientdata = {hostname=FORM.hostname, viewtype=FORM.viewtype, submit=&amp;quot;Update&amp;quot;}&lt;br /&gt;
 MVC:dispatch(&amp;quot;/test/&amp;quot;, &amp;quot;hostname&amp;quot;, &amp;quot;edithostname&amp;quot;, clientdata)&lt;br /&gt;
 &lt;br /&gt;
 -- destroy the mvc object&lt;br /&gt;
 MVC:destroy()&lt;br /&gt;
 %&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /etc/acf/app/test/hostname-edithostname-text.lsp ====&lt;br /&gt;
 &amp;lt;%&lt;br /&gt;
 local data, viewlibrary, page_info, session = ... &lt;br /&gt;
 &lt;br /&gt;
 if data.errtxt then&lt;br /&gt;
         print(data:print_errtxt())&lt;br /&gt;
 else&lt;br /&gt;
         print(data.descr)&lt;br /&gt;
 end&lt;br /&gt;
 %&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Test the application&lt;br /&gt;
&lt;br /&gt;
 test:~# chmod 755 test_haserl &lt;br /&gt;
 test:~# QUERY_STRING=&#039;hostname=alpine&amp;amp;viewtype=text&#039; REQUEST_METHOD=GET ./test_haserl&lt;br /&gt;
 Hostname Updated&lt;br /&gt;
 alpine:~# QUERY_STRING=&#039;hostname=asdfasdfasdfasdfasdf&amp;amp;viewtype=text&#039; REQUEST_METHOD=GET ./test_haserl&lt;br /&gt;
 Failed to set hostname&lt;br /&gt;
 hostname: Hostname must be 16 characters or less&lt;br /&gt;
 alpine:~# &lt;br /&gt;
&lt;br /&gt;
You can further customize the application by creating your own application-specific controller. This is how the web interface and the acf-cli application are written. In both cases, a simple application is written to load not just an mvc:new() reference, but an application-specific controller to wrap the mvc:new() object. The dispatch function is then called on the application-specific controller object, which can override any function in mvc.lua to add a customized implementation. For example, both applications override the handle_clientdata function to customize the way input data is provided in the clientdata structure, and the web interface-specific controller contains a lot of code to handle features such as menus, templates, skins, authentication, redirection, ... The best way to understand this is to just read the code.&lt;br /&gt;
&lt;br /&gt;
* Web application&lt;br /&gt;
** Application - [http://git.alpinelinux.org/cgit/acf/acf-core/tree/www/cgi-bin/acf acf]&lt;br /&gt;
** Controller - [http://git.alpinelinux.org/cgit/acf/acf-core/tree/app/acf_www-controller.lua acf_www-controller.lua]&lt;br /&gt;
* acf-cli&lt;br /&gt;
** Application - [http://git.alpinelinux.org/cgit/acf/acf-core/tree/bin/acf-cli acf-cli]&lt;br /&gt;
** Controller - [http://git.alpinelinux.org/cgit/acf/acf-core/tree/app/acf_cli-controller.lua acf_cli-controller.lua]&lt;br /&gt;
&lt;br /&gt;
=== Use the New method ===&lt;br /&gt;
==== test_new ====&lt;br /&gt;
 #!/usr/bin/lua&lt;br /&gt;
 -- Simple CLI based mvc application&lt;br /&gt;
 &lt;br /&gt;
 -- load the mvc module&lt;br /&gt;
 mvc = require(&amp;quot;acf.mvc&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 -- create an new &amp;quot;mvc object&amp;quot;&lt;br /&gt;
 MVC=mvc:new()&lt;br /&gt;
 &lt;br /&gt;
 -- load the config file so we can find the appdir&lt;br /&gt;
 MVC:read_config(&amp;quot;acf&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 -- load the hostname controller&lt;br /&gt;
 HOSTNAME=MVC:new(&amp;quot;/test/hostname&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 local hostname&lt;br /&gt;
 &lt;br /&gt;
 -- METHOD 1 - controller action&lt;br /&gt;
 HOSTNAME.clientdata = {hostname=arg[1], submit=&amp;quot;Update&amp;quot;}&lt;br /&gt;
 hostname = HOSTNAME:edithostname()&lt;br /&gt;
 &lt;br /&gt;
 -- METHOD 2 - model functions&lt;br /&gt;
 hostname = HOSTNAME.model:get_hostname()&lt;br /&gt;
 hostname.value.hostname.value = arg[1]&lt;br /&gt;
 hostname = HOSTNAME.model:set_hostname(hostname)&lt;br /&gt;
 &lt;br /&gt;
 if hostname.errtxt then&lt;br /&gt;
         print(hostname:print_errtxt())&lt;br /&gt;
 else&lt;br /&gt;
         print(hostname.descr or &amp;quot;Hostname Updated&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 HOSTNAME:destroy()&lt;br /&gt;
 MVC:destroy()&lt;br /&gt;
&lt;br /&gt;
Once you have a hostname controller object, you can access its actions, passing data through clientdata, or you can directly access the get/set functions of the model. Test the app:&lt;br /&gt;
&lt;br /&gt;
 test:~# chmod 755 test_new&lt;br /&gt;
 test:~# ./test_new alpine&lt;br /&gt;
 Hostname Updated&lt;br /&gt;
 alpine:~# ./test_new asdfasdfasdfasdfasdf&lt;br /&gt;
 Failed to set hostname&lt;br /&gt;
 hostname: Hostname must be 16 characters or less&lt;br /&gt;
 alpine:~# &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Obsolete}}&lt;br /&gt;
&lt;br /&gt;
= mvc load &amp;amp; exec special functions =&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;mvc.lua&#039;&#039;&#039; module has a provision for executing code on module load, prior to executing the controller&#039;s action, just after executing the controller&#039;s action, and on module unload.&lt;br /&gt;
&lt;br /&gt;
This is done with the mvc table in the controller.  To demonstrate, let&#039;s add a few functions to &#039;&#039;&#039;helloworld/app/app-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
 mvc = {}&lt;br /&gt;
 mvc.on_load = function (self, parent)&lt;br /&gt;
         print (&amp;quot;This is the app controller&#039;s on_load function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 mvc.pre_exec = function (self)&lt;br /&gt;
         print (&amp;quot;This is the app controller&#039;s pre_exec function&amp;quot;)&lt;br /&gt;
 end &lt;br /&gt;
 &lt;br /&gt;
 mvc.post_exec = function (self)&lt;br /&gt;
         print (&amp;quot;This is the app controller&#039;s post_exec function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
 mvc.on_unload = function (self)&lt;br /&gt;
         print (&amp;quot;This is the app controller&#039;s on_unload function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
Now running our script shows when the functions get called:&lt;br /&gt;
&lt;br /&gt;
 # lua helloworld.lua update &amp;quot;Alpine&amp;quot;&lt;br /&gt;
   This is the app controller&#039;s on_load function&lt;br /&gt;
   This is the app controller&#039;s pre_exec function&lt;br /&gt;
   This is the app controller&#039;s post_exec function&lt;br /&gt;
   Controller: hostname  Action: update&lt;br /&gt;
   Returned a table with the following values:&lt;br /&gt;
   value   Alpine&lt;br /&gt;
   type    string&lt;br /&gt;
   This is the app controller&#039;s on_unload function&lt;br /&gt;
&lt;br /&gt;
We can add mvc functions to a specific controller, as well.  Add this to &#039;&#039;&#039;helloworld/app/hostname-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
 mvc = {}&lt;br /&gt;
 mvc.on_load = function (self, parent)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s on_load function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 mvc.pre_exec = function (self)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s pre_exec function&amp;quot;)&lt;br /&gt;
 end &lt;br /&gt;
 &lt;br /&gt;
 mvc.post_exec = function (self)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s post_exec function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 mvc.on_unload = function (self)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s on_unload function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
And this happens:&lt;br /&gt;
&lt;br /&gt;
 # lua helloworld.lua update &amp;quot;Alpine&amp;quot;&lt;br /&gt;
   This is the app controller&#039;s on_load function&lt;br /&gt;
   This is the hostname controller&#039;s on_load function&lt;br /&gt;
   This is the hostname controller&#039;s pre_exec function&lt;br /&gt;
   This is the hostname controller&#039;s post_exec function&lt;br /&gt;
   This is the hostname controller&#039;s on_unload function&lt;br /&gt;
   Controller: hostname  Action: update&lt;br /&gt;
   Returned a table with the following values:&lt;br /&gt;
   value   Alpine&lt;br /&gt;
   type    string&lt;br /&gt;
   This is the app controller&#039;s on_unload function&lt;br /&gt;
&lt;br /&gt;
Note that both the &#039;&#039;app&#039;&#039; and &#039;&#039;hostname&#039;&#039; &#039;&#039;&#039;on_load&#039;&#039;&#039; and &#039;&#039;&#039;on_unload&#039;&#039;&#039; functions were run, but only the &#039;&#039;hostname&#039;&#039; &#039;&#039;&#039;pre_exec&#039;&#039;&#039; and &#039;&#039;&#039;post_exec&#039;&#039;&#039; functions ran.  This is because the pre and post exec functions are run as part of the &amp;quot;action&amp;quot;, and the &#039;&#039;&#039;dispatch&#039;&#039;&#039; function looks in the lowest-level controller for the pre/post_exec function.  Since &#039;&#039;hostname&#039;&#039; now defines those functions, it runs them.&lt;br /&gt;
&lt;br /&gt;
To run both the &#039;&#039;hostname&#039;&#039; and &#039;&#039;app&#039;&#039; pre_exec function, you must arrange for the &#039;&#039;hostname&#039;&#039; pre_exec function to call it&#039;s parent pre_exec:&lt;br /&gt;
&lt;br /&gt;
 mvc = {}&lt;br /&gt;
 mvc.on_load = function (self, parent)&lt;br /&gt;
        print (&amp;quot;This is the hostname controller&#039;s on_load function&amp;quot;)&lt;br /&gt;
        mvc.parent_pre_exec = parent.worker.mvc.pre_exec&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 mvc.pre_exec = function (self)&lt;br /&gt;
         mvc.parent_pre_exec (self)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s pre_exec function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
[[Category:ACF]] [[Category:Lua]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=ISP_Mail_Server_3.x_HowTo&amp;diff=12893</id>
		<title>ISP Mail Server 3.x HowTo</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=ISP_Mail_Server_3.x_HowTo&amp;diff=12893"/>
		<updated>2016-07-26T23:50:06Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Add ACF category&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Draft|This is a work in progress based on migrating alpinelinux mail servers to 3.x}}&lt;br /&gt;
&lt;br /&gt;
== A Full Service Mail Server ==&lt;br /&gt;
&lt;br /&gt;
This document describes installation process for the Alpine Linux 3.x platform. The goal of this document is to describe how to set up postfix, dovecot, clamav, dspam, roundecube, and postfixadmin for a full-featured &amp;quot;ISP&amp;quot; level mail server. This document superceeds the previous [[ISP Mail Server 2.x HowTo]] instructions for Alpine 2.x.  For this guide, we start with a server &#039;&#039;without&#039;&#039; ACF installed.&lt;br /&gt;
&lt;br /&gt;
The server provides:&lt;br /&gt;
&lt;br /&gt;
* multiple virtual domains&lt;br /&gt;
* admins for each domain (to add/remove virtual accounts)&lt;br /&gt;
* quota support per domain / account&lt;br /&gt;
* downloading email via IMAP / IMAPS / POP3 / POP3S&lt;br /&gt;
* relaying email for authenticated users with TLS or SSL (Submission / SMTPS protocol)&lt;br /&gt;
* standard filters (virus/spam/rbl/etc)&lt;br /&gt;
* web mail client&lt;br /&gt;
* value add services&lt;br /&gt;
&lt;br /&gt;
== Set up Lighttpd + PHP ==&lt;br /&gt;
&lt;br /&gt;
PostfixAdmin needs php pgpsql and imap modules, so we do it in this step.&lt;br /&gt;
&lt;br /&gt;
  apk add lighttpd php php-cgi php-pgsql php-imap&lt;br /&gt;
&lt;br /&gt;
We are setting this up to be a multi-domain virtual web server (replace host.example.com with the actual domain):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;mkdir -p /var/www/domains/host.example.com/www&lt;br /&gt;
cat &amp;lt;&amp;lt;-EOF &amp;gt;/var/www/domains/host.example.com/www/index.html&lt;br /&gt;
&amp;lt;!DOCTYPE HTML PUBLIC &amp;quot;-//W3C//DTD HTML 4.01//EN&amp;quot; &amp;quot;http://www.w3.org/TR/html4/strict.dtd&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;html lang=&amp;quot;en&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
&amp;lt;meta http-equiv=&amp;quot;Content-Type&amp;quot; content=&amp;quot;text/html; charset=ISO-8859-1&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;title&amp;gt;host.example.com Redirector&amp;lt;/title&amp;gt;&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&amp;lt;body&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;/postfixadmin&amp;quot;&amp;gt;PostfixAdmin&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;/roundcube&amp;quot;&amp;gt;Roundcube&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
EOF&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Get a web certificate, and install it.  There are several options:&lt;br /&gt;
# Use a self-signed certificate, such as [[Generating SSL certs with ACF]] &lt;br /&gt;
# Use the certificate created with &#039;&#039;&#039;setup-acf&#039;&#039;&#039;, if you installed ACF&lt;br /&gt;
# Get a certificate from a trusted root Certificate Authority, such as StartSSL.com, Thawte.com, Verisign.com, etc.&lt;br /&gt;
&lt;br /&gt;
These instructions will use a certificate issued from a self-signed ACF based CA&lt;br /&gt;
&lt;br /&gt;
  openssl pkcs12 -nokeys -cacerts -in certificate.pfx  -out /etc/lighttpd/ca-crt.pem&lt;br /&gt;
  openssl pkcs12 -nodes -in certificate.pfx -out /etc/lighttpd/server-bundle.pem&lt;br /&gt;
  chown root:root /etc/lighttpd/server-bundle.pem&lt;br /&gt;
  chmod 400 /etc/lighttpd/server-bundle.pem&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; The server certificate &#039;&#039;and&#039;&#039; key are in the server-bundle.pem file, so it is critical that the file be read-only by user &amp;quot;root&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Add these lines to /etc/lighttpd/lighttpd.conf to point to the new document root, and set it up to listen on port 443 (replace &#039;&#039;host.example.com&#039;&#039; with the actual domain and &#039;&#039;ip_address_of_server&#039;&#039; with the actual IP address):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
simple-vhost.server-root   = &amp;quot;/var/www/domains/&amp;quot;&lt;br /&gt;
simple-vhost.default-host  = &amp;quot;/host.example.com/&amp;quot;&lt;br /&gt;
simple-vhost.document-root = &amp;quot;www/&amp;quot;&lt;br /&gt;
&lt;br /&gt;
$SERVER[&amp;quot;socket&amp;quot;] == &amp;quot;ip_address_of_server:443&amp;quot; {&lt;br /&gt;
ssl.engine    = &amp;quot;enable&amp;quot;&lt;br /&gt;
ssl.pemfile   = &amp;quot;/etc/lighttpd/server-bundle.pem&amp;quot;&lt;br /&gt;
ssl.ca-file   = &amp;quot;/etc/lighttpd/ca-crt.pem&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensure that the simple_vhosts module is loaded, as well as the cgi config scripts by uncommenting the following lines in /etc/lighttpd/lighttpd.conf:&lt;br /&gt;
&lt;br /&gt;
 server.modules = (&lt;br /&gt;
     #  other modules may be listed&lt;br /&gt;
     &amp;quot;mod_simple_vhost&amp;quot;, &lt;br /&gt;
     #  other modules may be listed&lt;br /&gt;
  .&lt;br /&gt;
  .&lt;br /&gt;
  .&lt;br /&gt;
     include &amp;quot;mod_fastcgi.conf&amp;quot;&lt;br /&gt;
&lt;br /&gt;
start lighttpd, test:&lt;br /&gt;
&lt;br /&gt;
  rc-update add lighttpd&lt;br /&gt;
  rc&lt;br /&gt;
&lt;br /&gt;
At this point you should be able to see the redirect page:  https://host.example.com/&lt;br /&gt;
&lt;br /&gt;
== Install Postgresql ==&lt;br /&gt;
&lt;br /&gt;
Add and configure postgresql:&lt;br /&gt;
&lt;br /&gt;
  apk add postgresql postgresql-client&lt;br /&gt;
  /etc/init.d/postgresql setup&lt;br /&gt;
  /etc/init.d/postgresql start&lt;br /&gt;
  rc-update add postgresql&lt;br /&gt;
&lt;br /&gt;
At this point any user can connect to the sql server with &amp;quot;trust&amp;quot; mechanism.  If you want to enforce password authentication (you probably do) edit /var/lib/postgresql/9.0/data/pg_hba.conf. Since by default &amp;quot;trust&amp;quot; mechanism is for local connections only we assume using trust password-less access as safe.&lt;br /&gt;
&lt;br /&gt;
Create the postfix database:&lt;br /&gt;
&lt;br /&gt;
  psql -U postgres&lt;br /&gt;
   create user postfix with password &#039;******&#039;;&lt;br /&gt;
   create database postfix owner postfix;&lt;br /&gt;
   \q&lt;br /&gt;
&lt;br /&gt;
(Of course, use your selected password where ******* is shown above.)&lt;br /&gt;
&lt;br /&gt;
== Install PostfixAdmin ==&lt;br /&gt;
&lt;br /&gt;
We are going to install the postfix admin web front-end before we install the mail server.  This just creates an interface to populate the SQL tables that postfix and dovecot will use.&lt;br /&gt;
&lt;br /&gt;
Download PostfixAdmin from Sourceforge.  When these instructions were written, 2.92 was the current release, so (replace host.example.com with the actual domain):&lt;br /&gt;
&lt;br /&gt;
 wget http://downloads.sourceforge.net/project/postfixadmin/postfixadmin/postfixadmin-2.92/postfixadmin-2.92.tar.gz&lt;br /&gt;
 tar zxvf postfixadmin-2.92.tar.gz&lt;br /&gt;
 mkdir -p /var/www/domains/host.example.com/www/postfixadmin&lt;br /&gt;
 mv  postfixadmin-2.3.3/* /var/www/domains/host.example.com/www/postfixadmin&lt;br /&gt;
 rm -rf postfixadmin*&lt;br /&gt;
&lt;br /&gt;
Edit /var/www/domains/host.example.com/www/postfixadmin/config.inc.php and modify at least these lines (replace host.example.com with the actual domain):&lt;br /&gt;
&lt;br /&gt;
 $CONF[&#039;configured&#039;] = true;&lt;br /&gt;
 $CONF[&#039;setup_password&#039;] = &amp;quot;&amp;quot;;  &amp;lt;&amp;lt; Don&#039;t change this yet&lt;br /&gt;
 $CONF[&#039;database_type&#039;] = &#039;pgsql&#039;;&lt;br /&gt;
 $CONF[&#039;database_host&#039;] = &#039;localhost&#039;;&lt;br /&gt;
 $CONF[&#039;database_user&#039;] = &#039;postfix&#039;;&lt;br /&gt;
 $CONF[&#039;database_password&#039;] = &#039;*****&#039;;   &amp;lt;&amp;lt; The password you chose above&lt;br /&gt;
 $CONF[&#039;database_name&#039;] = &#039;postfix&#039;;&lt;br /&gt;
 $CONF[&#039;database_prefix&#039;] = &amp;quot;&amp;quot;;&lt;br /&gt;
 $CONF[&#039;admin_email&#039;] = &#039;you@some.email.com&#039;;  &amp;lt;&amp;lt; Your email address &lt;br /&gt;
 $CONF[&#039;encrypt&#039;] = &#039;md5crypt&#039;;&lt;br /&gt;
 $CONF[&#039;authlib_default_flavor&#039;] = &#039;md5raw&#039;;&lt;br /&gt;
 $CONF[&#039;dovecotpw&#039;] = &amp;quot;/usr/sbin/dovecotpw&amp;quot;;&lt;br /&gt;
 $CONF[&#039;domain_path&#039;] = &#039;YES&#039;;&lt;br /&gt;
 $CONF[&#039;domain_in_mailbox&#039;] = &#039;NO&#039;;&lt;br /&gt;
 $CONF[&#039;aliases&#039;] = &#039;10&#039;;                       &lt;br /&gt;
 $CONF[&#039;mailboxes&#039;] = &#039;10&#039;;&lt;br /&gt;
 $CONF[&#039;maxquota&#039;] = &#039;10&#039;;&lt;br /&gt;
 $CONF[&#039;quota&#039;] = &#039;YES&#039;;&lt;br /&gt;
 $CONF[&#039;quota_multiplier&#039;] = &#039;1024000&#039;;&lt;br /&gt;
 $CONF[&#039;vacation&#039;] = &#039;NO&#039;; &lt;br /&gt;
 $CONF[&#039;vacation_control&#039;] =&#039;NO&#039;;&lt;br /&gt;
 $CONF[&#039;vacation_control_admin&#039;] = &#039;NO&#039;;&lt;br /&gt;
 $CONF[&#039;alias_control&#039;] = &#039;YES&#039;;&lt;br /&gt;
 $CONF[&#039;alias_control_admin&#039;] = &#039;YES&#039;;&lt;br /&gt;
 $CONF[&#039;special_alias_control&#039;] = &#039;YES&#039;;&lt;br /&gt;
 $CONF[&#039;fetchmail&#039;] = &#039;NO&#039;;&lt;br /&gt;
 $CONF[&#039;user_footer_link&#039;] = &amp;quot;http://host.example.com/postfixadmin&amp;quot;;&lt;br /&gt;
 $CONF[&#039;footer_link&#039;] = &#039;http://host.example.com/postfixadmin/main.php&#039;;&lt;br /&gt;
 $CONF[&#039;create_mailbox_subdirs_prefix&#039;]=&amp;quot;&amp;quot;;  &lt;br /&gt;
 $CONF[&#039;used_quotas&#039;] = &#039;YES&#039;;   &lt;br /&gt;
 $CONF[&#039;new_quota_table&#039;] = &#039;YES&#039;;  &lt;br /&gt;
&lt;br /&gt;
You should further edit /var/www/domains/host.example.com/www/postfixadmin/config.inc.php and replace all instances of &amp;quot;change-this-to-your.domain.tld&amp;quot; with your actual mail domain. This can be done with busybox sed (replace example.com with your domain name):&lt;br /&gt;
&lt;br /&gt;
 sed -i -e &#039;s/change-this-to-your.domain.tld/example.com/g&#039; /var/www/domains/host.example.com/www/postfixadmin/config.inc.php&lt;br /&gt;
&lt;br /&gt;
Go to https://host.example.com/postfixadmin/setup.php&lt;br /&gt;
&lt;br /&gt;
Create the password hash, add it to the config.inc.php file&lt;br /&gt;
&lt;br /&gt;
Go back to https://host.example.com/postfixadmin/setup.php&lt;br /&gt;
&lt;br /&gt;
Create superadmin account.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;NOTE:&#039;&#039;&#039; Check http://sourceforge.net/tracker/index.php?func=detail&amp;amp;aid=2859165&amp;amp;group_id=191583&amp;amp;atid=937964 if you have bug on listing domains page.&lt;br /&gt;
&lt;br /&gt;
== Install Postfix ==&lt;br /&gt;
&lt;br /&gt;
Create a user for the virtual mail delivery, and get its uid/gid (you&#039;ll need the numeric uid/gid for postfix):&lt;br /&gt;
&lt;br /&gt;
 adduser vmail -H -D -s /bin/false&lt;br /&gt;
 grep vmail /etc/passwd&lt;br /&gt;
&lt;br /&gt;
(In examples below, we use 1006/1006 for the uid/gid)&lt;br /&gt;
&lt;br /&gt;
Create the mail directory, and assign vmail as the owner:&lt;br /&gt;
&lt;br /&gt;
 mkdir -p /var/mail/domains&lt;br /&gt;
 chown -R vmail:vmail /var/mail/domains&lt;br /&gt;
 &lt;br /&gt;
Install postfix:&lt;br /&gt;
&lt;br /&gt;
 apk add postfix postfix-pgsql postfix-pcre&lt;br /&gt;
&lt;br /&gt;
Edit the /etc/postfix/main.cf file. Here&#039;s an example (don&#039;t forget to replace the uid/gid):&lt;br /&gt;
&lt;br /&gt;
 myhostname=host.example.com&lt;br /&gt;
 mydomain=example.com&lt;br /&gt;
 &lt;br /&gt;
 mydestination = localhost.$mydomain, localhost&lt;br /&gt;
 mynetworks_style = subnet&lt;br /&gt;
 mynetworks = 127.0.0.0/8&lt;br /&gt;
 &lt;br /&gt;
 virtual_mailbox_domains = proxy:pgsql:/etc/postfix/sql/pgsql_virtual_domains_maps.cf&lt;br /&gt;
 virtual_alias_maps = proxy:pgsql:/etc/postfix/sql/pgsql_virtual_alias_maps.cf,&lt;br /&gt;
        proxy:pgsql:/etc/postfix/sql/pgsql_virtual_alias_domain_maps.cf,&lt;br /&gt;
        proxy:pgsql:/etc/postfix/sql/pgsql_virtual_alias_domain_catchall_maps.cf&lt;br /&gt;
 &lt;br /&gt;
 virtual_mailbox_maps = proxy:pgsql:/etc/postfix/sql/pgsql_virtual_mailbox_maps.cf,&lt;br /&gt;
        proxy:pgsql:/etc/postfix/sql/pgsql_virtual_alias_domain_mailbox_maps.cf&lt;br /&gt;
 &lt;br /&gt;
 virtual_mailbox_base = /var/mail/domains/&lt;br /&gt;
 virtual_gid_maps = static:1006&lt;br /&gt;
 virtual_uid_maps = static:1006&lt;br /&gt;
 virtual_minimum_uid = 100&lt;br /&gt;
 virtual_transport = virtual&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 # This next command means you must create a virtual&lt;br /&gt;
 # domain for the host itself - ALL mail goes through&lt;br /&gt;
 # The virtual transport&lt;br /&gt;
 &lt;br /&gt;
 mailbox_transport = virtual&lt;br /&gt;
 local_transport = virtual&lt;br /&gt;
 local_transport_maps = $virtual_mailbox_maps&lt;br /&gt;
 &lt;br /&gt;
 smtpd_helo_required = yes&lt;br /&gt;
 disable_vrfy_command = yes&lt;br /&gt;
 message_size_limit = 10240000&lt;br /&gt;
 queue_minfree = 51200000&lt;br /&gt;
 &lt;br /&gt;
 smtpd_sender_restrictions =&lt;br /&gt;
        permit_mynetworks,&lt;br /&gt;
        reject_non_fqdn_sender,&lt;br /&gt;
        reject_unknown_sender_domain&lt;br /&gt;
 &lt;br /&gt;
 smtpd_recipient_restrictions =&lt;br /&gt;
        reject_non_fqdn_recipient,&lt;br /&gt;
        reject_unknown_recipient_domain,&lt;br /&gt;
        permit_mynetworks,&lt;br /&gt;
        permit_sasl_authenticated,&lt;br /&gt;
        reject_unauth_destination,&lt;br /&gt;
        reject_rbl_client dnsbl.sorbs.net,&lt;br /&gt;
        reject_rbl_client zen.spamhaus.org,&lt;br /&gt;
        reject_rbl_client bl.spamcop.net&lt;br /&gt;
 &lt;br /&gt;
 smtpd_data_restrictions = reject_unauth_pipelining&lt;br /&gt;
 &lt;br /&gt;
 # we will use this later - This prevents cleartext authentication&lt;br /&gt;
 # for relaying&lt;br /&gt;
 smtpd_tls_auth_only = yes&lt;br /&gt;
&lt;br /&gt;
 # Silence the EAI warning on alpine linux&lt;br /&gt;
 smtputf8_enable = no&lt;br /&gt;
&lt;br /&gt;
Now we need to create a *bunch* of files so that postfix can get the delivery information out of sql. Here&#039;s a shell script to create the scripts.  Change PGPW to the password for the postfix user of the postfix SQL database:&lt;br /&gt;
&lt;br /&gt;
 cd /etc/postfix&lt;br /&gt;
 mkdir sql&lt;br /&gt;
 PGPW=&amp;quot;ChangeMe&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 cat - &amp;lt;&amp;lt;EOF &amp;gt;sql/pgsql_virtual_alias_domain_catchall_maps.cf&lt;br /&gt;
 user=postfix&lt;br /&gt;
 password = $PGPW&lt;br /&gt;
 hosts = localhost&lt;br /&gt;
 dbname = postfix&lt;br /&gt;
 query = Select goto From alias,alias_domain where alias_domain.alias_domain = &#039;%d&#039; and alias.address = &#039;@&#039; ||  alias_domain.target_domain and alias.active = true and alias_domain.active= true &lt;br /&gt;
 EOF&lt;br /&gt;
 &lt;br /&gt;
 cat - &amp;lt;&amp;lt;EOF &amp;gt;sql/pgsql_virtual_alias_domain_mailbox_maps.cf&lt;br /&gt;
 user=postfix&lt;br /&gt;
 password = $PGPW&lt;br /&gt;
 hosts = localhost&lt;br /&gt;
 dbname = postfix&lt;br /&gt;
 query = Select maildir from mailbox,alias_domain where alias_domain.alias_domain = &#039;%d&#039; and mailbox.username = &#039;%u&#039; || &#039;@&#039; || alias_domain.target_domain and mailbox.active = true and alias_domain.active&lt;br /&gt;
 EOF&lt;br /&gt;
 &lt;br /&gt;
 cat - &amp;lt;&amp;lt;EOF &amp;gt;sql/pgsql_virtual_alias_domain_maps.cf&lt;br /&gt;
 user=postfix&lt;br /&gt;
 password = $PGPW&lt;br /&gt;
 hosts = localhost&lt;br /&gt;
 dbname = postfix&lt;br /&gt;
 query = select goto from alias,alias_domain where alias_domain.alias_domain=&#039;%d&#039; and alias.address = &#039;%u&#039; || &#039;@&#039; || alias_domain.target_domain and alias.active= true and alias_domain.active= true&lt;br /&gt;
 EOF&lt;br /&gt;
 &lt;br /&gt;
 cat - &amp;lt;&amp;lt;EOF &amp;gt;sql/pgsql_virtual_alias_maps.cf&lt;br /&gt;
 user=postfix&lt;br /&gt;
 password = $PGPW&lt;br /&gt;
 hosts = localhost&lt;br /&gt;
 dbname = postfix&lt;br /&gt;
 query = Select goto From alias Where address=&#039;%s&#039; and active =&#039;1&#039;&lt;br /&gt;
 EOF&lt;br /&gt;
 &lt;br /&gt;
 cat - &amp;lt;&amp;lt;EOF &amp;gt;sql/pgsql_virtual_domains_maps.cf&lt;br /&gt;
 user=postfix&lt;br /&gt;
 password = $PGPW&lt;br /&gt;
 hosts = localhost&lt;br /&gt;
 dbname = postfix&lt;br /&gt;
 query = Select domain from domain where domain=&#039;%s&#039; and active=&#039;1&#039;&lt;br /&gt;
 EOF&lt;br /&gt;
 &lt;br /&gt;
 cat - &amp;lt;&amp;lt;EOF &amp;gt;sql/pgsql_virtual_mailbox_maps.cf&lt;br /&gt;
 user=postfix&lt;br /&gt;
 password = $PGPW&lt;br /&gt;
 hosts = localhost&lt;br /&gt;
 dbname = postfix&lt;br /&gt;
 query = Select maildir from mailbox where username=&#039;%s&#039; and active=true&lt;br /&gt;
 EOF&lt;br /&gt;
 &lt;br /&gt;
 chown -R postfix:postfix sql&lt;br /&gt;
 chmod 640 sql/*&lt;br /&gt;
&lt;br /&gt;
At this point you should be able to start up postfix:&lt;br /&gt;
 &lt;br /&gt;
 newaliases  # so postfix is happy...&lt;br /&gt;
 /etc/init.d/postfix start&lt;br /&gt;
 rc-update add postfix&lt;br /&gt;
&lt;br /&gt;
=== Create a domain in PostfixAdmin and test ===&lt;br /&gt;
&lt;br /&gt;
Go to http://host.example.com/postfixadmin/&lt;br /&gt;
&lt;br /&gt;
Log in using the superadmin account, create a domain for the local box (e.g. example.com), and create a user mailbox (e.g. root).&lt;br /&gt;
&lt;br /&gt;
From the machine, send a test message:&lt;br /&gt;
&lt;br /&gt;
 sendmail -t root@example.com&lt;br /&gt;
 subject: test&lt;br /&gt;
 .&lt;br /&gt;
 ^d&lt;br /&gt;
&lt;br /&gt;
In /var/log/mail.log (or /var/log/messages, if you still have busybox syslogd running) you should see the message queued.  The message should be in /var/mail/domains/example.com/root/new&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Install Dovecot ==&lt;br /&gt;
&lt;br /&gt;
Dovecot is the POP3/IMAP server to retrieve mail.&lt;br /&gt;
&lt;br /&gt;
Install dovecot: &lt;br /&gt;
&lt;br /&gt;
 apk add dovecot dovecot-pgsql&lt;br /&gt;
&lt;br /&gt;
Edit /etc/dovecot/dovecot.conf:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
auth_mechanisms = plain login&lt;br /&gt;
auth_username_format = %Lu&lt;br /&gt;
#auth_verbose = yes&lt;br /&gt;
#auth_debug = yes&lt;br /&gt;
#auth_debug_passwords = no&lt;br /&gt;
&lt;br /&gt;
disable_plaintext_auth = no&lt;br /&gt;
&lt;br /&gt;
mail_location = maildir:/var/mail/domains/%d/%n&lt;br /&gt;
&lt;br /&gt;
first_valid_gid = 1000&lt;br /&gt;
first_valid_uid = 1000&lt;br /&gt;
last_valid_gid = 65535&lt;br /&gt;
last_valid_uid = 65535&lt;br /&gt;
&lt;br /&gt;
log_timestamp = &amp;quot;%Y-%m-%d %H:%M:%S &amp;quot;&lt;br /&gt;
login_greeting = IMAP server ready&lt;br /&gt;
&lt;br /&gt;
protocols = imap&lt;br /&gt;
&lt;br /&gt;
service anvil {&lt;br /&gt;
  client_limit = 2100&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
ssl_cert = /etc/lighttpd/server-bundle.pem&lt;br /&gt;
ssl_key = /etc/lighttpd/server-bundle.pem&lt;br /&gt;
&lt;br /&gt;
userdb {&lt;br /&gt;
  args = uid=1006 gid=1006 home=/var/mail/domains/%d/%n&lt;br /&gt;
  driver = static&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
passdb {&lt;br /&gt;
  args = /etc/dovecot/dovecot-sql.conf&lt;br /&gt;
  driver = sql&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
namespace inbox {&lt;br /&gt;
        inbox = yes&lt;br /&gt;
&lt;br /&gt;
        mailbox Trash {&lt;br /&gt;
                auto = create&lt;br /&gt;
                special_use = \Trash&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        mailbox Spam {&lt;br /&gt;
                auto = no&lt;br /&gt;
                special_use = \Junk&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        mailbox Sent {&lt;br /&gt;
                auto = subscribe&lt;br /&gt;
                special_use = \Sent&lt;br /&gt;
        }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Be sure to replace the uid and gid with the appropriate values for the vmail user.&lt;br /&gt;
&lt;br /&gt;
We need a certificate for SSL/TLS authentication, so in the example above, we use the lighttpd cert. That way when the cert is renewed/replaced, Dovecot will have access to the new cert as well.  &lt;br /&gt;
&lt;br /&gt;
Create the /etc/dovecot/dovecot-sql.conf file:&lt;br /&gt;
&lt;br /&gt;
 driver = pgsql&lt;br /&gt;
 connect = host=localhost dbname=postfix user=postfix password=********&lt;br /&gt;
 password_query = select username,password from mailbox where local_part = &#039;%n&#039; and domain = &#039;%d&#039;&lt;br /&gt;
 default_pass_scheme =  MD5-CRYPT&lt;br /&gt;
&lt;br /&gt;
Again, change the password above to your postfix user password, and protect the file from prying eyes:&lt;br /&gt;
&lt;br /&gt;
 chown root:root /etc/dovecot/dovecot-sql.conf&lt;br /&gt;
 chmod 600 /etc/dovecot/dovecot-sql.conf&lt;br /&gt;
&lt;br /&gt;
Start dovecot&lt;br /&gt;
 /etc/init.d/dovecot start&lt;br /&gt;
 rc-update add dovecot&lt;br /&gt;
&lt;br /&gt;
== Testing ==&lt;br /&gt;
&lt;br /&gt;
Make sure your firewall allows in ports 25(SMTP) 110 (POP3), 995 (POP3S), 143(IMAP), 993(IMAPS), or whatever subset you support.  &lt;br /&gt;
 &lt;br /&gt;
At this point, you should be able to:&lt;br /&gt;
 * Create a new domain and add users with PostfixAdmin&lt;br /&gt;
 * Send mail to those users via SMTP to port 25&lt;br /&gt;
 * Retrieve mail using the user&#039;s full email and password (e.g. username: user@example.com  password: ChangeMe)&lt;br /&gt;
&lt;br /&gt;
== Value Add Features ==&lt;br /&gt;
&lt;br /&gt;
If you followed the guide above, you now have a functional mail server with many interconnected parts.  The features below assume that the server is already running as described above.  You should be able to add any or all of these features below to further enhance the mail service.&lt;br /&gt;
&lt;br /&gt;
=== Virus Scanning ===&lt;br /&gt;
&lt;br /&gt;
This procedure uses clamav and the postfix content_filter mechanism to scan inbound and outbound email for viruses.  Infected emails are dropped.  Clean emails are tagged with a &amp;quot;scanned by clamav&amp;quot; header.&lt;br /&gt;
&lt;br /&gt;
* Install clamav and clamsmtp:&lt;br /&gt;
 apk add acf-clamav clamsmtp&lt;br /&gt;
* Edit the /etc/clamav/clamd.conf file if desired (not necessary in most cases)&lt;br /&gt;
* Edit /etc/clamsmtpd.conf and verify the following lines&lt;br /&gt;
 OutAddress: 10026&lt;br /&gt;
 Listen: 127.0.0.1:10025                                               &lt;br /&gt;
 Header: X-Virus-Scanned: ClamAV using ClamSMTP&lt;br /&gt;
 Action: drop&lt;br /&gt;
 User: clamav                                                      &lt;br /&gt;
* Start the daemons&lt;br /&gt;
 rc-update add clamd&lt;br /&gt;
 rc-update add clamsmtpd&lt;br /&gt;
 /etc/init.d/clamd start&lt;br /&gt;
 /etc/init.d/clamsmtpd start&lt;br /&gt;
* Verify clamsmtp is listening on port 10025:&lt;br /&gt;
 netstat -anp | grep clamsmtp&lt;br /&gt;
* [http://memberwebs.com/stef/software/clamsmtp/postfix.html Following the clamsmtp instructions]&lt;br /&gt;
** edit /etc/postfix/main.cf and add:&lt;br /&gt;
 content_filter = scan:[127.0.0.1]:10025                                                      &lt;br /&gt;
** edit /etc/postfix/master.cf and add&lt;br /&gt;
 # AV scan filter (used by content_filter)&lt;br /&gt;
 scan      unix  -       -       n       -       16      smtp&lt;br /&gt;
         -o smtp_send_xforward_command=yes&lt;br /&gt;
         -o smtp_enforce_tls=no&lt;br /&gt;
 # For injecting mail back into postfix from the filter&lt;br /&gt;
 127.0.0.1:10026 inet  n -       n       -       16      smtpd&lt;br /&gt;
         -o content_filter=&lt;br /&gt;
         -o receive_override_options=no_unknown_recipient_checks,no_header_body_checks&lt;br /&gt;
         -o smtpd_helo_restrictions=&lt;br /&gt;
         -o smtpd_client_restrictions=&lt;br /&gt;
         -o smtpd_sender_restrictions=&lt;br /&gt;
         -o smtpd_recipient_restrictions=permit_mynetworks,reject&lt;br /&gt;
         -o mynetworks_style=host&lt;br /&gt;
         -o smtpd_authorized_xforward_hosts=127.0.0.0/8&lt;br /&gt;
* postfix reload&lt;br /&gt;
* Send and email into a local virtual domain - it should have the &#039;&#039;X-Virus-Scanned: ClamAV using ClamSMTP&#039;&#039; header.&lt;br /&gt;
&lt;br /&gt;
=== Relay for Authenticated Users ===&lt;br /&gt;
&lt;br /&gt;
As configured above, the mail server accepts email from the Internet, but it does not relay email.  If it is a perimeter exchanger for a protected network, then you can add the protected networks to the &#039;&#039;mynetworks&#039;&#039; configuration line in /etc/postfix/main.cf&lt;br /&gt;
&lt;br /&gt;
This configuration change allows &#039;&#039;remote&#039;&#039; users to authenticate against the mail server and relay through it.  The rules for relaying are:&lt;br /&gt;
* Only authenticated users can relay&lt;br /&gt;
* Authentication Credentials must be encrypted with TLS or SSL&lt;br /&gt;
* Allow Submission and SMTPS ports for relaying (many consumer networks block port 25 - SMTP by default)&lt;br /&gt;
The process uses the dovecot authentication mechanism (used with IMAPS) to authenticate users before they are allowed to relay through postfix.&lt;br /&gt;
&lt;br /&gt;
* Edit /etc/dovecot/dovecot.conf and add the following:&lt;br /&gt;
# this is for postfix SASL (authenticated users can relay through us)&lt;br /&gt;
&lt;br /&gt;
 service auth {&lt;br /&gt;
  unix_listener /var/spool/postfix/private/auth {&lt;br /&gt;
    group = postfix&lt;br /&gt;
    mode = 0660&lt;br /&gt;
    user = postfix&lt;br /&gt;
  }&lt;br /&gt;
  unix_listener /var/spool/postfix/auth-master {&lt;br /&gt;
    group = postfix&lt;br /&gt;
    mode = 0660&lt;br /&gt;
    user = vmail&lt;br /&gt;
  }&lt;br /&gt;
  user = root&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
* Restart dovecot&lt;br /&gt;
 /etc/init.d/dovecot restart&lt;br /&gt;
* Edit /etc/postfix/main.cf and add:&lt;br /&gt;
 # TLS Stuff -- since we allow SASL with tls *only*, we have to set up TLS first                    &lt;br /&gt;
 &lt;br /&gt;
 smtpd_tls_cert_file = /etc/lighttpd/server-bundle.pem&lt;br /&gt;
 smtpd_tls_key_file = /etc/lighttpd/server-bundle.pem&lt;br /&gt;
 smtpd_tls_CAfile = /etc/lighttpd/ca-crt.pem&lt;br /&gt;
 # If tls_security_level is set to &amp;quot;encrypt&amp;quot;, then SMTP rejects &lt;br /&gt;
 # unencrypted email (e.g. normal mail) which is bad.&lt;br /&gt;
 # By setting it to &amp;quot;may&amp;quot; you get TLS encrypted mail from google, slashdot, and other &lt;br /&gt;
 # interesting places.  Check your logs to see who&lt;br /&gt;
 smtpd_tls_security_level = may&lt;br /&gt;
 # Log info about the negotiated encryption levels&lt;br /&gt;
 smtpd_tls_received_header = yes&lt;br /&gt;
 smtpd_tls_loglevel = 1&lt;br /&gt;
 &lt;br /&gt;
 # SASL - this allows senders to authenticiate themselves&lt;br /&gt;
 # This along with &amp;quot;permit_sasl_authenticated&amp;quot; in smtpd_recipient_restrictions allows relaying&lt;br /&gt;
 smtpd_sasl_type = dovecot&lt;br /&gt;
 smtpd_sasl_path = private/auth&lt;br /&gt;
 smtpd_sasl_auth_enable = yes&lt;br /&gt;
 smtpd_sasl_authenticated_header = yes&lt;br /&gt;
 broken_sasl_auth_clients = yes&lt;br /&gt;
 smtpd_tls_auth_only = yes&lt;br /&gt;
* Edit /etc/postfix/master.cf and enable the submission and smtps transports.  They are probably already at the top of your  master.cf file, just commented out:&lt;br /&gt;
 submission inet n       -       n       -       -       smtpd&lt;br /&gt;
   -o smtpd_tls_security_level=encrypt&lt;br /&gt;
   -o smtpd_sasl_auth_enable=yes&lt;br /&gt;
   -o smtpd_client_restrictions=permit_sasl_authenticated,reject&lt;br /&gt;
   -o milter_macro_daemon_name=ORIGINATING&lt;br /&gt;
 smtps     inet  n       -       n       -       -       smtpd&lt;br /&gt;
   -o smtpd_tls_security_level=encrypt&lt;br /&gt;
   -o smtpd_tls_wrappermode=yes&lt;br /&gt;
   -o smtpd_sasl_auth_enable=yes&lt;br /&gt;
   -o smtpd_client_restrictions=permit_sasl_authenticated,reject&lt;br /&gt;
   -o milter_macro_daemon_name=ORIGINATING&lt;br /&gt;
*Verfiy submission and smtps are defined in /etc/services&lt;br /&gt;
 grep &amp;quot;submission\|ssmtp&amp;quot; /etc/services&lt;br /&gt;
 submission	587/tcp				# mail message submission&lt;br /&gt;
 submission	587/udp&lt;br /&gt;
 smtps		465/tcp		ssmtp		# smtp protocol over TLS/SSL&lt;br /&gt;
 smtps		465/udp		ssmtp&lt;br /&gt;
* Restart postfix&lt;br /&gt;
 postfix reload&lt;br /&gt;
&lt;br /&gt;
At this point, you should be able to set up a mail client to relay through the server with TLS (port 587) or SSL (port 465)  Note that &amp;quot;plain&amp;quot; authentication is used because the underlying link is encrypted.  For example, in Thunderbird leave &amp;quot;secure authentication&amp;quot; unchecked, and choose STARTTLS (or TLS) for the connection security.&lt;br /&gt;
&lt;br /&gt;
=== Mailbox Quotas ===&lt;br /&gt;
&lt;br /&gt;
In the default configuration, PostfixAdmin knows about quotas, but they are not enforced.   Documentation on the web mentions the [http://vda.sourceforge.net vda patch to postfix] to enforce quotas.  The only bad thing... its a &#039;&#039;patch&#039;&#039;.   Postfix and Dovecot are both conservative systems, so if the patch isn&#039;t in the upstream source, we&#039;ll assume there&#039;s a good reason.   There is a way of using quotas without patches - and it involves using dovecot&#039;s [http://wiki2.dovecot.org/LDA deliver] lda for local delivery.&lt;br /&gt;
&lt;br /&gt;
* Replace /etc/dovecot/dovecot.conf with the following:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
auth_mechanisms = plain login&lt;br /&gt;
auth_username_format = %Lu&lt;br /&gt;
#auth_verbose = yes&lt;br /&gt;
#auth_debug = yes&lt;br /&gt;
#auth_debug_passwords = no&lt;br /&gt;
&lt;br /&gt;
disable_plaintext_auth = no&lt;br /&gt;
&lt;br /&gt;
info_log_path = /var/log/dovecot-info.log&lt;br /&gt;
log_path = /var/log/dovecot.log&lt;br /&gt;
&lt;br /&gt;
mail_location = maildir:/var/mail/domains/%d/%n&lt;br /&gt;
&lt;br /&gt;
first_valid_gid = 1000&lt;br /&gt;
first_valid_uid = 1000&lt;br /&gt;
last_valid_gid = 65535&lt;br /&gt;
last_valid_uid = 65535&lt;br /&gt;
&lt;br /&gt;
log_timestamp = &amp;quot;%Y-%m-%d %H:%M:%S &amp;quot;&lt;br /&gt;
login_greeting = IMAP server ready&lt;br /&gt;
&lt;br /&gt;
protocols = imap&lt;br /&gt;
&lt;br /&gt;
service anvil {&lt;br /&gt;
  client_limit = 2100&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
service auth {&lt;br /&gt;
  unix_listener /var/spool/postfix/auth-master {&lt;br /&gt;
    group = postfix&lt;br /&gt;
    mode = 0660&lt;br /&gt;
    user = vmail&lt;br /&gt;
  }&lt;br /&gt;
  unix_listener /var/spool/postfix/private/auth {&lt;br /&gt;
    group = postfix&lt;br /&gt;
    mode = 0660&lt;br /&gt;
    user = postfix&lt;br /&gt;
  }&lt;br /&gt;
  user = root&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
service imap-login {&lt;br /&gt;
  inet_listener imap {&lt;br /&gt;
    address = 127.0.0.1&lt;br /&gt;
    port = 143&lt;br /&gt;
  }&lt;br /&gt;
  inet_listener imaps {&lt;br /&gt;
    address = *&lt;br /&gt;
    port = 993&lt;br /&gt;
  }&lt;br /&gt;
  process_limit = 1024&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
service pop3-login {&lt;br /&gt;
  process_limit = 1024&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
service dict {&lt;br /&gt;
  unix_listener dict {&lt;br /&gt;
    group =&lt;br /&gt;
    mode = 0600&lt;br /&gt;
    user = vmail&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
ssl_ca = &amp;lt;/etc/ssl/certs/&amp;lt;CA Certificate file&amp;gt;&lt;br /&gt;
ssl_cert = &amp;lt;/etc/ssl/private/&amp;lt;Public part of certificate file&amp;gt;&lt;br /&gt;
ssl_key = &amp;lt;/etc/ssl/private/&amp;lt;Private part of certificate file&amp;gt;&lt;br /&gt;
&lt;br /&gt;
passdb {&lt;br /&gt;
  args = /etc/dovecot/dovecot-pgsql.conf&lt;br /&gt;
  driver = sql&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
userdb {&lt;br /&gt;
  driver = prefetch&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
userdb {&lt;br /&gt;
  args = /etc/dovecot/dovecot-pgsql.conf&lt;br /&gt;
  driver = sql&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
plugin {&lt;br /&gt;
  quota = dict:user::proxy::quotadict&lt;br /&gt;
&lt;br /&gt;
  autocreate = Trash&lt;br /&gt;
  autocreate2 = Spam&lt;br /&gt;
  autocreate3 = Sent&lt;br /&gt;
  autosubscribe = Trash&lt;br /&gt;
  autosubscribe2 = Spam&lt;br /&gt;
  autosubscribe3 = Sent&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
protocol imap {&lt;br /&gt;
  mail_plugins = autocreate quota imap_quota&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
protocol pop3 {                                                               &lt;br /&gt;
         mail_plugins = quota                                                  &lt;br /&gt;
}   &lt;br /&gt;
&lt;br /&gt;
dict {&lt;br /&gt;
  quotadict = pgsql:/etc/dovecot/dovecot-dict-quota.conf&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
protocol lda {&lt;br /&gt;
  auth_socket_path = /var/spool/postfix/auth-master&lt;br /&gt;
  mail_plugins = quota&lt;br /&gt;
  postmaster_address = postmaster@host.example.com&lt;br /&gt;
  sendmail_path = /usr/sbin/sendmail&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
* edit &amp;lt;tt&amp;gt;/etc/dovecot/dovecot-sql.conf&amp;lt;/tt&amp;gt; and replace the user and password queries with the following (you may not have a user_query yet - add it):&lt;br /&gt;
&lt;br /&gt;
 password_query = select username as user, password, 1006 as userdb_uid, 1006 as userdb_gid, &#039;*:bytes=&#039; || quota as userdb_quota_rule from mailbox  where local_part = &#039;%n&#039; and domain = &#039;%d&#039;&lt;br /&gt;
 user_query = select &#039;/var/mail/domains/&#039; || maildir as home, 1006 as uid, 1006 as gid, &#039;*:bytes=&#039; || quota  as quota_rule from mailbox where local_part = &#039;%n&#039; and domain =&#039;%d&#039;&lt;br /&gt;
&lt;br /&gt;
* create &amp;lt;tt&amp;gt;/etc/dovecot/dovecot-dict-quota.conf&amp;lt;/tt&amp;gt;&lt;br /&gt;
 connect = host=localhost dbname=postfix user=postfix password=********&lt;br /&gt;
 &lt;br /&gt;
 map {&lt;br /&gt;
         pattern = priv/quota/storage&lt;br /&gt;
         table = quota2&lt;br /&gt;
         username_field =username&lt;br /&gt;
         value_field = bytes&lt;br /&gt;
 }&lt;br /&gt;
 &lt;br /&gt;
 map {&lt;br /&gt;
        pattern= priv/quota/messages&lt;br /&gt;
        table = quota2&lt;br /&gt;
        username_field = username&lt;br /&gt;
        value_field = messages&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
Again, change the password above to your postfix user password, and protect the file from prying eyes:&lt;br /&gt;
  chown dovecot:root /etc/dovecot/dovecot-sql.conf&lt;br /&gt;
  chmod 600 /etc/dovecot/dovecot-sql.conf&lt;br /&gt;
  chown dovecot:root /etc/dovecot/dovecot-dict-quota.conf&lt;br /&gt;
  chmod 600 /etc/dovecot/dovecot-dict-quota.conf&lt;br /&gt;
&lt;br /&gt;
Side note: [http://wiki2.dovecot.org/Quota/Dict The Dovecot Quota Documentation] mentions the need for a trigger with pgsql.  This was created in the PostfixAdmin install, which is why you instantiated the pgsql language when creating the database.  If not, you will need to create the trigger, to reference the quota2 table, not the quota table mentioned in the dovecot docs.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* create a new transport for the dovecot lda.   Add the following to  /etc/postfix/master.cf:&lt;br /&gt;
 # The dovecot deliver lda&lt;br /&gt;
 dovecot   unix  -       n       n       -       -       pipe&lt;br /&gt;
   flags=DRhu user=vmail:vmail argv=/usr/libexec/dovecot/deliver -f ${sender} -d ${user}@${nexthop}&lt;br /&gt;
&lt;br /&gt;
* Edit the /etc/postfix/main.cf.  Replace &lt;br /&gt;
 virtual_transport = virtual &lt;br /&gt;
with&lt;br /&gt;
 virtual_transport = dovecot&lt;br /&gt;
 dovecot_destination_recipient_limit = 1&lt;br /&gt;
&lt;br /&gt;
Change permissions on the /var/log/dovecot* log files, so that the vmail user can write to them:&lt;br /&gt;
&lt;br /&gt;
 chown vmail:vmail /var/log/dovecot*&lt;br /&gt;
&lt;br /&gt;
Restart Postfix and Dovecot:&lt;br /&gt;
&lt;br /&gt;
 /etc/init.d/postfix restart&lt;br /&gt;
 /etc/init.d/dovecot restart&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;TODO&#039;&#039;&#039;  This will cause over-quota emails to bounce.   Which could be a source of backscatter.   We need a way of checking quota limits after RBL checking but before the message is accepted in the queue.&lt;br /&gt;
&lt;br /&gt;
=== WebMail (RoundCube) ===&lt;br /&gt;
&lt;br /&gt;
[http://roundcube.net/ RoundCube] is an &amp;quot;ajax /Web2.0&amp;quot; web-mail client.  These instructions are for the Alpine Linux 2.2 repository &lt;br /&gt;
&lt;br /&gt;
* Verify that you have at least the following in /etc/postfix/main.cf. Unless you have followed the Relay for Authenticated Users section above, set &#039;&#039;&#039;smtpd_tls_auth_only = no&#039;&#039;&#039;, otherwise leave it set to &#039;&#039;&#039;yes&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# SASL - this allows senders to authenticiate themselves&lt;br /&gt;
# This along with &amp;quot;permit_sasl_authenticated&amp;quot; in smtpd_recipient_restrictions allows relaying&lt;br /&gt;
smtpd_sasl_type = dovecot&lt;br /&gt;
smtpd_sasl_path = private/auth&lt;br /&gt;
smtpd_sasl_auth_enable = yes&lt;br /&gt;
smtpd_sasl_authenticated_header = yes&lt;br /&gt;
# Set the next line to no if TLS auth is not configured &lt;br /&gt;
smtpd_tls_auth_only = no&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Ensure you have followed section &#039;&#039;Relay_for_Authenticated_Users&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
* Restart the relevant services:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/etc/init.d/postfix restart&lt;br /&gt;
/etc/init.d/dovecot restart&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Add the package and related php modules:&lt;br /&gt;
&lt;br /&gt;
 apk add roundcubemail php-xml php-openssl php-mcrypt php-gd php-iconv php-dom php-intl php-pdo php-ldap php-pdo_pgsql php-zlib&lt;br /&gt;
&lt;br /&gt;
* Link the roundcube application back into the docroot&lt;br /&gt;
&lt;br /&gt;
 ln -s /usr/share/webapps/roundcube /var/www/domains/host.example.com/www/roundcube&lt;br /&gt;
&lt;br /&gt;
* Install &#039;&#039;roundcubemail-install&#039;&#039; package&lt;br /&gt;
&lt;br /&gt;
 apk add roundcubemail-installer&lt;br /&gt;
&lt;br /&gt;
* Follow the instructions in /usr/share/webapps/roundcube/INSTALL:&lt;br /&gt;
 cd /usr/share/webapps/roundcube&lt;br /&gt;
 chown -R lighttpd:lighttpd /var/log/roundcube&lt;br /&gt;
 &lt;br /&gt;
 su postgres&lt;br /&gt;
 createuser roundcube&lt;br /&gt;
   Shall the new role be a superuser? (y/n) n&lt;br /&gt;
   Shall the new role be allowed to create databases? (y/n) n&lt;br /&gt;
   Shall the new role be allowed to create more new roles? (y/n) y&lt;br /&gt;
 createdb -O roundcube -E UNICODE -T template0 roundcubemail&lt;br /&gt;
 psql roundcubemail&lt;br /&gt;
   roundcubemail=# ALTER USER roundcube WITH PASSWORD &#039;the_new_password&#039;;&lt;br /&gt;
   roundcubemail=# \c - roundcube&lt;br /&gt;
   roundcubemail=&amp;gt; \i /usr/share/webapps/roundcube/SQL/postgres.initial.sql&lt;br /&gt;
   roundcubemail=&amp;gt; \q&lt;br /&gt;
 exit&lt;br /&gt;
&lt;br /&gt;
* Edit /etc/php/php.ini and set date.timezone to your local timezone, or to UTC&lt;br /&gt;
&lt;br /&gt;
* Restart lighttpd to verify the new php libraries are used&lt;br /&gt;
&lt;br /&gt;
 /etc/init.d/lighttpd restart&lt;br /&gt;
&lt;br /&gt;
* Get the additional php modules needed:&lt;br /&gt;
 apk add curl php-phar git&lt;br /&gt;
 cd /usr/share/webapps/roundcube&lt;br /&gt;
 curl -sS https://getcomposer.org/installer | php&lt;br /&gt;
 cp composer.json-dist composer.json&lt;br /&gt;
 # Note - As of 11 Aug 2015, with Roundcube 1.1.2, there is a regression that is &lt;br /&gt;
 # fixed here: https://github.com/roundcube/roundcubemail/commit/b3e9764c311a39012de1fa8ffd0fe874fbb322ef&lt;br /&gt;
 # Update your composer.json accordingly:&lt;br /&gt;
 # -        &amp;quot;pear/mail_mime&amp;quot;: &amp;quot;&amp;gt;=1.9.0&amp;quot;,&lt;br /&gt;
 # -        &amp;quot;pear/net_smtp&amp;quot;: &amp;quot;dev-master&amp;quot;&lt;br /&gt;
 # +        &amp;quot;pear-pear.php.net/mail_mime&amp;quot;: &amp;quot;&amp;gt;=1.9.0&amp;quot;,&lt;br /&gt;
 # +        &amp;quot;pear-pear.php.net/net_smtp&amp;quot;: &amp;quot;&amp;gt;=1.6.3&amp;quot;&lt;br /&gt;
 php composer.phar install --no-dev &lt;br /&gt;
&lt;br /&gt;
* Point your browser to https://host.example.com/roundcube/installer&lt;br /&gt;
* Start installation&lt;br /&gt;
&lt;br /&gt;
For the specific configuration parameters in the install step:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Property&lt;br /&gt;
!Setting&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;enable_spellcheck&#039;&#039; ||   disabled &lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;identities_level&#039;&#039; ||  one identity with possibility to edit all params but not email address &lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;log driver&#039;&#039; || syslog &lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;sylog_id&#039;&#039; || roundcube &lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;syslog_facility&#039;&#039; || mailsubsystem &lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;db_dnsw&#039;&#039; || pgsql properties, as described above &lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;imap_host&#039;&#039; || 127.0.0.1 &lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;default port&#039;&#039; || 143&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;auto_create_user&#039;&#039; || enabled &lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;smtp_server&#039;&#039; ||  127.0.0.1&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;smtp_port&#039;&#039; ||  25&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;smtp_user/smtp_pass&#039;&#039; ||  enable &#039;&#039;Use Current IMAP username and password for SMTP authentication&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;smtp_log&#039;&#039; ||  enable (optional, but gives additional log record)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The other items can be left at default settings, or adjusted if desired.&lt;br /&gt;
&lt;br /&gt;
* Follow the instructions in step 2 of the install to copy the files to the server&lt;br /&gt;
* You should now be able to get to roundcube at https://host.example.com/roundcube&lt;br /&gt;
&lt;br /&gt;
* After its working, the INSTALL file recommends removing the install directory.&lt;br /&gt;
&lt;br /&gt;
 apk del roundcubemail-installer&lt;br /&gt;
&lt;br /&gt;
* Disable installer mode in /etc/roundcube/main.inc.php file:&lt;br /&gt;
&lt;br /&gt;
 $rcmail_config[&#039;enable_installer&#039;] = false;&lt;br /&gt;
&lt;br /&gt;
* Change the ownership and permissions&lt;br /&gt;
&lt;br /&gt;
 cd /usr/share/webapps/roundcube&lt;br /&gt;
 chown -R root:root LICENSE UPGRADING INSTALL README CHANGELOG&lt;br /&gt;
 chmod -R 600 LICENSE UPGRADING INSTALL README CHANGELOG &lt;br /&gt;
&lt;br /&gt;
* If needed customize logos such as &#039;&#039;&#039;watermark.gif&#039;&#039;&#039;, &#039;&#039;&#039;roundcube_logo.gif&#039;&#039;&#039;, &#039;&#039;&#039;favicon.ico&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* If you would like to disable displaying of standard logos update template files accordingly&lt;br /&gt;
&lt;br /&gt;
* Comment all entries like &#039;&#039;&#039;&amp;lt;div ... img src=&amp;quot;/images/roundcube_logo.png&amp;quot;...&#039;&#039;&#039; in files:&lt;br /&gt;
&lt;br /&gt;
 includes/header.html &lt;br /&gt;
 templates/error.html&lt;br /&gt;
 templates/messageprint.html&lt;br /&gt;
 templates/login.html&lt;br /&gt;
 templates/printmessage.html&lt;br /&gt;
&lt;br /&gt;
* Comment all entries like &#039;&#039;&#039;&amp;lt;img src=&amp;quot;/images/watermark.gif&amp;quot;...&#039;&#039;&#039; in files:&lt;br /&gt;
&lt;br /&gt;
 templates/identities.html&lt;br /&gt;
 templates/messageerror.html&lt;br /&gt;
 watermark.html&lt;br /&gt;
&lt;br /&gt;
==== Enable Plug-ins ====&lt;br /&gt;
&lt;br /&gt;
RoundCube has various useful plug-ins, which could be found in &#039;&#039;/usr/share/webapps/roundcube/plugins&#039;&#039; directory. For example you may want to enable &#039;&#039;password&#039;&#039; plug-in to let users change their passwords directly from RoundCube using an extra Password Tab added to User Settings.&lt;br /&gt;
&lt;br /&gt;
* Grant limited permissions for &#039;&#039;roundcube&#039;&#039; database role &lt;br /&gt;
 psql -U postgres postfix&lt;br /&gt;
   postfix=# GRANT UPDATE (password,modified) ON mailbox TO roundcube;&lt;br /&gt;
   postfix=# GRANT SELECT (username) ON mailbox TO roundcube;&lt;br /&gt;
   postfix=# GRANT INSERT ON log TO roundcube;&lt;br /&gt;
   postfix=# \q&lt;br /&gt;
&lt;br /&gt;
* Setup &#039;&#039;password&#039;&#039; plug-in parameters in &#039;&#039;/usr/share/webapps/roundcube/plugins/password/config.inc.php&#039;&#039;&lt;br /&gt;
 mv /usr/share/webapps/roundcube/plugins/password/config.inc.php.dist /usr/share/webapps/roundcube/plugins/password/config.inc.php&lt;br /&gt;
 vi /usr/share/webapps/roundcube/plugins/password/config.inc.php&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$rcmail_config[&#039;password_minimum_length&#039;] = 7;&lt;br /&gt;
$rcmail_config[&#039;password_require_nonalpha&#039;] = true;&lt;br /&gt;
...&lt;br /&gt;
$rcmail_config[&#039;password_db_dsn&#039;] = &#039;pgsql://roundcube:&amp;lt;roundcube_password&amp;gt;@localhost/postfix&#039;;&lt;br /&gt;
...&lt;br /&gt;
$rcmail_config[&#039;password_query&#039;] = &amp;quot;UPDATE mailbox set password = %c, modified = NOW() where username = %u; INSERT INTO log (timestamp,username,domain,action,data) VALUES (NOW(),%u || &#039; (&#039; || %h || &#039;)&#039;,%d,&#039;edit_password&#039;,%u)&amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Enable &#039;&#039;password&#039;&#039; plug-in&lt;br /&gt;
 vi /usr/share/webapps/roundcube/config/main.inc.php&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
...&lt;br /&gt;
$rcmail_config[&#039;plugins&#039;] = array(&#039;password&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Enable &#039;&#039;create_default_folders&#039;&#039; for RoundCube&lt;br /&gt;
 vi /usr/share/webapps/roundcube/config/main.inc.php&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
...&lt;br /&gt;
$rcmail_config[&#039;create_default_folders&#039;] = TRUE;&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== OpenLDAP based Address Book ===&lt;br /&gt;
&lt;br /&gt;
This OpenLDAP configuration uses the SQL backend, which represents information stored in PostgreSQL as an LDAP subtree for Address Book functionality for email lookups, user authentication or even replication account information between sites. This procedure uses some metainformation to translate LDAP queries to SQL queries, leaving relational schema untouched, which allows SQL and LDAP applications to inter-operate without replication, and exchange data as needed. The SQL backend uses UnixODBC to connect to PostgresSQL. &lt;br /&gt;
&lt;br /&gt;
* Install OpenLDAP and ODBC&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
apk add openldap libldap openldap-back-sql php-ldap unixodbc psqlodbc ca-certificates&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Update &amp;quot;postfix&amp;quot; database (it will add &#039;id&#039; columns to mailbox and domain tables, also will create tables and views to represent LDAP metainformation)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: These instructions are for example domain example.com. So make sure you replaced all entries of &#039;example&#039; and &#039;com&#039; according to your domain name parts.&lt;br /&gt;
&lt;br /&gt;
Put the following into a new file called &#039;&#039;&#039;script&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE domain ADD COLUMN id SERIAL; &lt;br /&gt;
ALTER TABLE mailbox ADD COLUMN id SERIAL; &lt;br /&gt;
&lt;br /&gt;
CREATE TABLE ldap_entry_objclasses (&lt;br /&gt;
    entry_id integer NOT NULL,&lt;br /&gt;
    oc_name character varying(64)&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
CREATE TABLE ldap_oc_mappings (&lt;br /&gt;
    name character varying(64) NOT NULL,&lt;br /&gt;
    keytbl character varying(64) NOT NULL,&lt;br /&gt;
    keycol character varying(64) NOT NULL,&lt;br /&gt;
    create_proc character varying(255),&lt;br /&gt;
    delete_proc character varying(255),&lt;br /&gt;
    expect_return integer NOT NULL&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
ALTER TABLE ldap_oc_mappings ADD COLUMN id SERIAL;&lt;br /&gt;
ALTER TABLE ldap_oc_mappings ADD PRIMARY KEY (id);&lt;br /&gt;
&lt;br /&gt;
CREATE TABLE ldap_attr_mappings (&lt;br /&gt;
    oc_map_id integer NOT NULL REFERENCES ldap_oc_mappings(id),&lt;br /&gt;
    name character varying(255) NOT NULL,&lt;br /&gt;
    sel_expr character varying(255) NOT NULL,&lt;br /&gt;
    sel_expr_u character varying(255),&lt;br /&gt;
    from_tbls character varying(255) NOT NULL,&lt;br /&gt;
    join_where character varying(255),&lt;br /&gt;
    add_proc character varying(255),&lt;br /&gt;
    delete_proc character varying(255),&lt;br /&gt;
    param_order integer NOT NULL,&lt;br /&gt;
    expect_return integer NOT NULL&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
ALTER TABLE ldap_attr_mappings ADD COLUMN id SERIAL;&lt;br /&gt;
ALTER TABLE ldap_attr_mappings ADD PRIMARY KEY (id);&lt;br /&gt;
&lt;br /&gt;
CREATE VIEW ldap_dcs AS&lt;br /&gt;
    ((SELECT (domain.id + 100000) AS id,&lt;br /&gt;
            (&#039;dc=&#039;::text || replace((domain.domain)::text, &#039;.&#039;::text, &#039;,dc=&#039;::text)) AS dn,&lt;br /&gt;
            1 AS oc_map_id,&lt;br /&gt;
            100000 AS parent,&lt;br /&gt;
            0 AS keyval,&lt;br /&gt;
            domain.domain&lt;br /&gt;
     FROM domain&lt;br /&gt;
     WHERE domain.domain &amp;lt;&amp;gt; &#039;ALL&#039;)&lt;br /&gt;
      UNION&lt;br /&gt;
     (SELECT 100000 AS id,&lt;br /&gt;
           (&#039;dc=&#039; || regexp_replace((domain.domain)::text, &#039;.*\\.&#039;, &#039;&#039;::text)) AS dn,&lt;br /&gt;
           1 AS oc_map_id,&lt;br /&gt;
           0 AS parent,&lt;br /&gt;
           0 AS keyval,&lt;br /&gt;
           (regexp_replace((domain.domain)::text, &#039;.*\\.&#039;, &#039;&#039;::text)) AS domain&lt;br /&gt;
      FROM domain&lt;br /&gt;
      WHERE domain.domain &amp;lt;&amp;gt; &#039;ALL&#039;&lt;br /&gt;
      LIMIT 1));&lt;br /&gt;
&lt;br /&gt;
CREATE VIEW ldap_entries AS&lt;br /&gt;
    SELECT mailbox.id,&lt;br /&gt;
    (((&#039;cn=&#039;::text || initcap(replace(split_part((mailbox.username)::text, &#039;@&#039;::text, 1), &#039;.&#039;::text, &#039; &#039;::text))) || &#039;,dc=&#039;::text) ||&lt;br /&gt;
             replace(regexp_replace((mailbox.username)::text, &#039;.*@&#039;, &#039;&#039;::text), &#039;.&#039;::text, &#039;,dc=&#039;::text)) AS dn,&lt;br /&gt;
          1 AS oc_map_id,&lt;br /&gt;
          (SELECT ldap_dcs.id&lt;br /&gt;
           FROM ldap_dcs&lt;br /&gt;
           WHERE ((ldap_dcs.domain)::text = (mailbox.domain)::text)) AS parent,&lt;br /&gt;
           mailbox.id AS keyval&lt;br /&gt;
           FROM mailbox&lt;br /&gt;
           UNION&lt;br /&gt;
           SELECT ldap_dcs.id,&lt;br /&gt;
                  ldap_dcs.dn,&lt;br /&gt;
                  ldap_dcs.oc_map_id,&lt;br /&gt;
                  ldap_dcs.parent,&lt;br /&gt;
                  ldap_dcs.keyval&lt;br /&gt;
           FROM ldap_dcs;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Question to experts: Is this normal to have in this script &amp;quot;WARNING:  nonstandard use of \\ in a string literal&amp;quot;?&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Finally, execute the commands in the file with:&lt;br /&gt;
 cat script | psql -U postfix postfix&lt;br /&gt;
 rm script&lt;br /&gt;
&lt;br /&gt;
* Fill out LDAP tables according to following example (make sure to separate values with TABs):&lt;br /&gt;
&lt;br /&gt;
Put the following into a new file called &#039;&#039;&#039;script&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
COPY ldap_oc_mappings (id, name, keytbl, keycol, create_proc, delete_proc, expect_return) FROM stdin;&lt;br /&gt;
1	exampleBox	mailbox	id	\N	\N	1&lt;br /&gt;
\.&lt;br /&gt;
COPY ldap_attr_mappings (id, oc_map_id, name, sel_expr, sel_expr_u, from_tbls, join_where, add_proc, delete_proc, param_order, expect_return) FROM stdin;&lt;br /&gt;
1	1	displayName	mailbox.name	\N	mailbox	\N	\N	\N	3	0&lt;br /&gt;
2	1	mail	mailbox.username	\N	mailbox	\N	\N	\N	3	0&lt;br /&gt;
3	1	cn	mailbox.name	\N	mailbox	\N	\N	\N	3	0&lt;br /&gt;
4	1	userPassword	&#039;{CRYPT}&#039;||mailbox.password	\N	mailbox	\N	\N	\N	3	0&lt;br /&gt;
\.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally, execute the commands in the file with:&lt;br /&gt;
 cat script | psql -U postfix postfix&lt;br /&gt;
 rm script&lt;br /&gt;
&lt;br /&gt;
* Check that &amp;quot;ldap_dcs&amp;quot; view looks something like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
echo &#039;select * from ldap_dcs&#039; | psql -U postgres postfix&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
   id   |             dn              | oc_map_id | parent | keyval |       domain       &lt;br /&gt;
--------+-----------------------------+-----------+--------+--------+--------------------&lt;br /&gt;
 100000 | dc=com                      |         1 |      0 |      0 | com&lt;br /&gt;
 100001 | dc=example,dc=com           |         1 | 100000 |      0 | example.com&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Check that &amp;quot;ldap_entries&amp;quot; view looks something like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
echo &#039;select * from ldap_entries&#039; | psql -U postgres postfix&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
   id   |                          dn                           | oc_map_id | parent | keyval &lt;br /&gt;
--------+-------------------------------------------------------+-----------+--------+--------&lt;br /&gt;
    1   | cn=address1,dc=example,dc=com                         |         1 | 100001 |    1&lt;br /&gt;
...&lt;br /&gt;
   123  | cn=address123,dc=example,dc=com                       |         1 | 100001 |    1&lt;br /&gt;
 100000 | dc=com                                                |         1 |      0 |    0&lt;br /&gt;
 100001 | dc=example,dc=com                                     |         1 | 100000 |    0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Configure ODBC parameters&lt;br /&gt;
&lt;br /&gt;
Edit /etc/odbc.ini:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[PostgreSQL]&lt;br /&gt;
Description             = Connection to Postgres&lt;br /&gt;
Driver                  = PostgreSQL&lt;br /&gt;
Trace                   = Yes&lt;br /&gt;
TraceFile               = sql.log&lt;br /&gt;
Database                = postfix&lt;br /&gt;
Servername              = 127.0.0.1&lt;br /&gt;
UserName                =&lt;br /&gt;
Password                =&lt;br /&gt;
Port                    = 5432&lt;br /&gt;
Protocol                = 6.4&lt;br /&gt;
ReadOnly                = No&lt;br /&gt;
RowVersining            = No&lt;br /&gt;
ShowSystemTables        = No&lt;br /&gt;
ShowOidColumn           = No&lt;br /&gt;
FakeOidIndex            = No&lt;br /&gt;
ConnSettings            =&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Edit /etc/odbcinst.ini:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[PostgreSQL]&lt;br /&gt;
Description     = PostgreSQL driver for Linux&lt;br /&gt;
Driver          = /usr/lib/psqlodbcw.so&lt;br /&gt;
Setup           = /usr/lib/libodbcpsqlS.so&lt;br /&gt;
FileUsage       = 1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Test ODBC connection&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
echo &amp;quot;select * from domain;&amp;quot; | isql PostgreSQL postgres&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Provide permission to certificate for LDAP server&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown ldap /etc/lighttpd/server-bundle.pem&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Edit LDAP schema&lt;br /&gt;
&lt;br /&gt;
Edit /etc/openldap/schema/example.com.schema:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
attributetype ( 0.9.2342.19200300.100.1.3&lt;br /&gt;
	NAME ( &#039;mail&#039; &#039;rfc822Mailbox&#039; )&lt;br /&gt;
	DESC &#039;RFC1274: RFC822 Mailbox&#039;&lt;br /&gt;
        EQUALITY caseIgnoreIA5Match&lt;br /&gt;
        SUBSTR caseIgnoreIA5SubstringsMatch&lt;br /&gt;
        SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )&lt;br /&gt;
&lt;br /&gt;
attributetype ( 2.16.840.1.113730.3.1.241&lt;br /&gt;
	NAME &#039;displayName&#039;&lt;br /&gt;
	DESC &#039;RFC2798: preferred name to be used when displaying entries&#039;&lt;br /&gt;
	EQUALITY caseIgnoreMatch&lt;br /&gt;
	SUBSTR caseIgnoreSubstringsMatch&lt;br /&gt;
	SYNTAX 1.3.6.1.4.1.1466.115.121.1.15&lt;br /&gt;
	SINGLE-VALUE )&lt;br /&gt;
&lt;br /&gt;
objectclass   ( 2.16.840.1.113730.3.2.2&lt;br /&gt;
        NAME &#039;exampleBox&#039;&lt;br /&gt;
	DESC &#039;example.com mailbox&#039;&lt;br /&gt;
	MUST ( displayName $ mail $ userPassword )&lt;br /&gt;
	)&lt;br /&gt;
&lt;br /&gt;
# RFC 1274 + RFC 2247&lt;br /&gt;
attributetype ( 0.9.2342.19200300.100.1.25&lt;br /&gt;
        NAME ( &#039;dc&#039; &#039;domainComponent&#039; )&lt;br /&gt;
        DESC &#039;RFC1274/2247: domain component&#039;&lt;br /&gt;
        EQUALITY caseIgnoreIA5Match&lt;br /&gt;
        SUBSTR caseIgnoreIA5SubstringsMatch&lt;br /&gt;
        SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )&lt;br /&gt;
&lt;br /&gt;
attributetype ( 2.5.4.46 NAME &#039;dnQualifier&#039;&lt;br /&gt;
        DESC &#039;RFC2256: DN qualifier&#039;&lt;br /&gt;
        EQUALITY caseIgnoreMatch&lt;br /&gt;
        ORDERING caseIgnoreOrderingMatch&lt;br /&gt;
        SUBSTR caseIgnoreSubstringsMatch&lt;br /&gt;
        SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 )&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Configure LDAP server&lt;br /&gt;
&lt;br /&gt;
Edit /etc/openldap/slapd.conf:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
include         /etc/openldap/schema/example.com.schema&lt;br /&gt;
pidfile         /var/run/openldap/slapd.pid&lt;br /&gt;
argsfile        /var/run/openldap/slapd.args&lt;br /&gt;
&lt;br /&gt;
# Uncomment next five TLS... lines if you want to use LDAPs (secured). Probably you don&#039;t want it...&lt;br /&gt;
#TLSCipherSuite HIGH&lt;br /&gt;
#TLSCACertificateFile /etc/lighttpd/ca-crt.pem&lt;br /&gt;
#TLSCertificateFile /etc/lighttpd/server-bundle.pem&lt;br /&gt;
#TLSCertificateKeyFile /etc/lighttpd/server-bundle.pem&lt;br /&gt;
#TLSVerifyClient never &lt;br /&gt;
&lt;br /&gt;
# This is needed for proper representation of MD5-CRYPT format stored in database&lt;br /&gt;
#  see more details in http://strugglers.net/~andy/blog/2010/01/23/openldap-and-md5crypt/&lt;br /&gt;
password-hash  {CRYPT}&lt;br /&gt;
password-crypt-salt-format &amp;quot;$1$%.8s&amp;quot;&lt;br /&gt;
&lt;br /&gt;
loglevel        stats&lt;br /&gt;
moduleload	/usr/lib/openldap/back_sql.so&lt;br /&gt;
sizelimit 3000&lt;br /&gt;
&lt;br /&gt;
database        sql&lt;br /&gt;
&lt;br /&gt;
dbname		PostgreSQL&lt;br /&gt;
dbuser		postfix&lt;br /&gt;
dbpasswd	*****&lt;br /&gt;
&lt;br /&gt;
suffix          &amp;quot;dc=example,dc=com&amp;quot;&lt;br /&gt;
&lt;br /&gt;
upper_func      &amp;quot;upper&amp;quot;&lt;br /&gt;
strcast_func    &amp;quot;text&amp;quot;&lt;br /&gt;
concat_pattern  &amp;quot;?||?&amp;quot;&lt;br /&gt;
has_ldapinfo_dn_ru      no&lt;br /&gt;
lastmod         off&lt;br /&gt;
&lt;br /&gt;
access to attrs=userPassword by * auth&lt;br /&gt;
&lt;br /&gt;
access to * by peername.ip=127.0.0.1 read&lt;br /&gt;
#           by peername.ip=&amp;lt;IP&amp;gt;%&amp;lt;netmask&amp;gt; read&lt;br /&gt;
#           by peername.ip=&amp;lt;IP&amp;gt; read&lt;br /&gt;
	    by users read&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Set permissions for slapd.conf&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown ldap:ldap /etc/openldap/slapd.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Configure startup parameters to make sure that LDAP server start AFTER PostgreSQL and listens on localhost with clear text and public IP with SSL. In case you uncommented TLS lines in slapd.conf use this string: OPTS=&amp;quot;-h &#039;ldaps:// ldap://&#039;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Edit /etc/conf.d/slapd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
rc_need=&amp;quot;postgresql&amp;quot; &lt;br /&gt;
OPTS=&amp;quot;-h &#039;ldap://&#039;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Start LDAP server&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
rc-update add slapd default&lt;br /&gt;
/etc/init.d/slapd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Configure LDAP client utilities. In case you uncommented TLS lines in slapd.conf replace ldap with ldaps&lt;br /&gt;
&lt;br /&gt;
Edit /etc/openldap/ldap.conf&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
BASE	dc=example,dc=com&lt;br /&gt;
URI	ldap://host.example.com&lt;br /&gt;
&lt;br /&gt;
# Uncomment next three TLS... lines if you want to use LDAPs (secured). Probably you don&#039;t want it...&lt;br /&gt;
#TLS_CACERT /etc/lighttpd/ca-crt.pem&lt;br /&gt;
#TLS_CERT /etc/lighttpd/server-bundle.pem&lt;br /&gt;
#TLS_KEY /etc/lighttpd/server-bundle.pem&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Test LDAP server&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ldapsearch -z 3&lt;br /&gt;
ldapsearch -z 3 -x -W -D cn=admin,dc=example,dc=com&lt;br /&gt;
ldapsearch -z 3 -x -W -D cn=address1,dc=example,dc=com&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Configure RoundCube webmail for email lookups&lt;br /&gt;
&lt;br /&gt;
In order to enable php-ldap support you need to restart lighttpd server&lt;br /&gt;
&lt;br /&gt;
 /etc/init.d/lighttpd restart&lt;br /&gt;
&lt;br /&gt;
Edit /etc/roundcube/main.inc.php:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$rcmail_config[&#039;ldap_debug&#039;] = false;&lt;br /&gt;
...&lt;br /&gt;
$rcmail_config[&#039;address_book_type&#039;] = &#039;sql&#039;;&lt;br /&gt;
&lt;br /&gt;
$rcmail_config[&#039;ldap_public&#039;][&#039;example.com&#039;] = array(&lt;br /&gt;
  &#039;name&#039;          =&amp;gt; &#039;example.com&#039;,&lt;br /&gt;
  &#039;hosts&#039;         =&amp;gt; array(&#039;127.0.0.1&#039;),&lt;br /&gt;
  &#039;port&#039;          =&amp;gt; 389,&lt;br /&gt;
  &#039;use_tls&#039;         =&amp;gt; false,&lt;br /&gt;
  &#039;user_specific&#039; =&amp;gt; false,&lt;br /&gt;
  &#039;base_dn&#039;       =&amp;gt; &#039;dc=example,dc=com&#039;,&lt;br /&gt;
  &#039;bind_dn&#039;       =&amp;gt; &#039;&#039;,&lt;br /&gt;
  &#039;bind_pass&#039;     =&amp;gt; &#039;&#039;,&lt;br /&gt;
  &#039;writable&#039;      =&amp;gt; false,&lt;br /&gt;
  &#039;LDAP_Object_Classes&#039; =&amp;gt; array(&amp;quot;top&amp;quot;, &amp;quot;exampleBox&amp;quot;),&lt;br /&gt;
  &#039;required_fields&#039;     =&amp;gt; array(&amp;quot;cn&amp;quot;, &amp;quot;sn&amp;quot;, &amp;quot;mail&amp;quot;),&lt;br /&gt;
  &#039;LDAP_rdn&#039;      =&amp;gt; &#039;mail&#039;,&lt;br /&gt;
  &#039;ldap_version&#039;  =&amp;gt; 3,&lt;br /&gt;
  &#039;search_fields&#039; =&amp;gt; array(&#039;mail&#039;, &#039;cn&#039;, &#039;sn&#039;, &#039;givenName&#039;),&lt;br /&gt;
  &#039;name_field&#039;    =&amp;gt; &#039;cn&#039;,&lt;br /&gt;
  &#039;email_field&#039;   =&amp;gt; &#039;mail&#039;,&lt;br /&gt;
  &#039;surname_field&#039; =&amp;gt; &#039;sn&#039;,&lt;br /&gt;
  &#039;firstname_field&#039; =&amp;gt; &#039;gn&#039;,&lt;br /&gt;
  &#039;sort&#039;          =&amp;gt; &#039;cn&#039;,&lt;br /&gt;
  &#039;scope&#039;         =&amp;gt; &#039;sub&#039;,&lt;br /&gt;
  &#039;filter&#039;        =&amp;gt; &#039;(objectClass=*)&#039;, // Construct here any filter you need&lt;br /&gt;
  &#039;fuzzy_search&#039;  =&amp;gt; true);&lt;br /&gt;
&lt;br /&gt;
$rcmail_config[&#039;autocomplete_addressbooks&#039;] = array(&#039;sql&#039;,&#039;example.com&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Fix PostfixAdmin to work with the new table definition&lt;br /&gt;
&lt;br /&gt;
Edit /var/www/domains/example.com/www/postfixadmin/list-domain.php. Replace the line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
   SELECT domain.* , COUNT( DISTINCT mailbox.username ) AS mailbox_count&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
With the lines:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
   SELECT domain.domain, domain.description, domain.aliases, domain.mailboxes,&lt;br /&gt;
   domain.maxquota, domain.quota, domain.transport, domain.backupmx, domain.created,&lt;br /&gt;
   domain.modified, domain.active, COUNT( DISTINCT mailbox.username ) AS mailbox_count&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== log rotation ==&lt;br /&gt;
&lt;br /&gt;
Ensure the busybox cron service is started and is configured to auto-start:&lt;br /&gt;
&lt;br /&gt;
 /etc/init.d/cron start&lt;br /&gt;
 rc-update add cron default&lt;br /&gt;
&lt;br /&gt;
Add log rotate:&lt;br /&gt;
&lt;br /&gt;
 apk add logrotate&lt;br /&gt;
&lt;br /&gt;
Edit &#039;&#039;/etc/logrotate.conf&#039;&#039; as desired, but the defaults should be sufficient for most people.&lt;br /&gt;
&lt;br /&gt;
== Optional: Configure Web Server Virtual Domains ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; These steps can be done &#039;&#039;in addition to&#039;&#039; the default lighttpd configuration above, which allows you to access the ACF, PostfixAdmin and Roundcube interfaces as subfolders of one web service.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; If you provide SSL access for multiple domain site you may need to follow http://redmine.lighttpd.net/projects/lighttpd/wiki/Docs:SSL#SSL-on-multiple-domains in order to provide multi-domain certificates. If you would like to redirect hosts to their secure equivalents use the following instructions http://redmine.lighttpd.net/projects/lighttpd/wiki/HowToRedirectHttpToHttps.&lt;br /&gt;
&lt;br /&gt;
This server hosts three separate web applications, and these can be handled as three &#039;&#039;different&#039;&#039; virtual domains on the same web server. They will be distinguished by their DNS names, so you can choose domains for the three separate services (or at least the ones you want to publish):&lt;br /&gt;
&lt;br /&gt;
* ACF - Alpine Configuration Framework for managing the server&lt;br /&gt;
* PostfixAdmin - for managing the postfix installation&lt;br /&gt;
* RoundCube - for accessing individual mailboxes&lt;br /&gt;
&lt;br /&gt;
Choose three different domains (from here on known as ACF_DOMAIN, POSTFIXADMIN_DOMAIN, and ROUNDCUBE_DOMAIN) and configure DNS for all three to point to the IP address of your host. These should be DNS &#039;&#039;&#039;A&#039;&#039;&#039; records.&lt;br /&gt;
&lt;br /&gt;
Then, configure lighttpd to handle the three separate domains by editing /etc/lighttpd/lighttpd.conf:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
 $HTTP[&amp;quot;host&amp;quot;] == &amp;quot;ACF_DOMAIN&amp;quot; {&lt;br /&gt;
	simple-vhost.server-root   = &amp;quot;/var/www/domains/&amp;quot;&lt;br /&gt;
	simple-vhost.default-host  = &amp;quot;/ACF_DOMAIN/&amp;quot;&lt;br /&gt;
	simple-vhost.document-root = &amp;quot;www/&amp;quot;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
 $HTTP[&amp;quot;host&amp;quot;] == &amp;quot;POSTFIXADMIN_DOMAIN&amp;quot; {&lt;br /&gt;
	simple-vhost.server-root   = &amp;quot;/var/www/domains/&amp;quot;&lt;br /&gt;
	simple-vhost.default-host  = &amp;quot;/POSTFIXADMIN_DOMAIN/&amp;quot;&lt;br /&gt;
	simple-vhost.document-root = &amp;quot;www/&amp;quot;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
 $HTTP[&amp;quot;host&amp;quot;] == &amp;quot;ROUNDCUBE_DOMAIN&amp;quot; {&lt;br /&gt;
	simple-vhost.server-root   = &amp;quot;/var/www/domains/&amp;quot;&lt;br /&gt;
	simple-vhost.default-host  = &amp;quot;/ROUNDCUBE_DOMAIN/&amp;quot;&lt;br /&gt;
	simple-vhost.document-root = &amp;quot;www/&amp;quot;&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And, then link the appropriate www directories.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
 mkdir -p /var/www/domains/ACF_DOMAIN&lt;br /&gt;
 ln -s /usr/share/acf/www /var/www/domains/ACF_DOMAIN/www&lt;br /&gt;
&lt;br /&gt;
 mkdir -p /var/www/domains/POSTFIXADMIN_DOMAIN&lt;br /&gt;
 ln -s /var/www/domains/host.example.com/www/postfixadmin /var/www/domains/POSTFIXADMIN_DOMAIN/www&lt;br /&gt;
&lt;br /&gt;
 mkdir -p /var/www/domains/ROUNDCUBE_DOMAIN&lt;br /&gt;
 ln -s /usr/share/webapps/roundcube /var/www/domains/ROUNDCUBE_DOMAIN/www&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Optional: Enable compression in Lighttpd ==&lt;br /&gt;
&lt;br /&gt;
* Uncomment &#039;&#039;mod_compress&#039;&#039; and &#039;&#039;mod_setenv&#039;&#039; and modify website section as follows&lt;br /&gt;
&lt;br /&gt;
 mkdir -p /var/lib/lighttpd/cache&lt;br /&gt;
 chown lighttpd:lighttpd  /var/lib/lighttpd/cache&lt;br /&gt;
&lt;br /&gt;
 vi /etc/lighttpd/lighttpd.conf&lt;br /&gt;
&lt;br /&gt;
 ...&lt;br /&gt;
 &amp;quot;mod_setenv&amp;quot;,&lt;br /&gt;
 &amp;quot;mod_compress&amp;quot;,&lt;br /&gt;
 ...&lt;br /&gt;
 $HTTP[&amp;quot;host&amp;quot;] == &amp;quot;ROUNDCUBE_DOMAIN&amp;quot; {&lt;br /&gt;
  ...&lt;br /&gt;
  static-file.etags = &amp;quot;enable&amp;quot;&lt;br /&gt;
  etag.use-mtime = &amp;quot;enable&amp;quot;&lt;br /&gt;
  $HTTP[&amp;quot;url&amp;quot;] =~ &amp;quot;^/(plugins|skins|program)&amp;quot; { setenv.add-response-header  = ( &amp;quot;Cache-Control&amp;quot; =&amp;gt; &amp;quot;public, max-age=2592000&amp;quot;) }&lt;br /&gt;
  compress.cache-dir   = var.statedir + &amp;quot;/cache/compress&amp;quot;&lt;br /&gt;
  compress.filetype = (&amp;quot;text/plain&amp;quot;, &amp;quot;text/html&amp;quot;, &amp;quot;text/javascript&amp;quot;, &amp;quot;text/css&amp;quot;, &amp;quot;text/xml&amp;quot;, &amp;quot;image/gif&amp;quot;, &amp;quot;image/png&amp;quot;)&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
[[Category:Mail]]&lt;br /&gt;
[[Category:PHP]]&lt;br /&gt;
[[Category:SQL]]&lt;br /&gt;
[[Category:ACF]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=Small_Office_Services&amp;diff=12892</id>
		<title>Small Office Services</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=Small_Office_Services&amp;diff=12892"/>
		<updated>2016-07-26T23:48:45Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Add ACF category&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&#039;&#039;&#039;Abstract&#039;&#039;&#039;: This document will outline how to provide various network services for a small remote office, using Linux containerization (LXC).  It is designed to be a complement to the [[Dynamic Multipoint VPN (DMVPN)|DMVPN]] spoke node.&lt;br /&gt;
&lt;br /&gt;
The following services will be available in addition to the encrypted communications between offices provided by the DMVPN network:&lt;br /&gt;
* Internet browsing proxy server with domain filtering (wired clients on protected internal network)&lt;br /&gt;
* Separate proxy for wifi clients&lt;br /&gt;
* SIP phone system including web based provisioning and basic voicemail services&lt;br /&gt;
&lt;br /&gt;
The assumption is made that the following VLANs and subnets are used as the DMVPN document did:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&#039;&#039;&#039;Interface&#039;&#039;&#039;&lt;br /&gt;
!&#039;&#039;&#039;Description&#039;&#039;&#039;&lt;br /&gt;
!&#039;&#039;&#039;Subnet&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
|bond0.3&lt;br /&gt;
|Management&lt;br /&gt;
|10.1.0.129/26&lt;br /&gt;
|-&lt;br /&gt;
|bond0.101&lt;br /&gt;
|LAN&lt;br /&gt;
|10.1.0.0/25&lt;br /&gt;
|-&lt;br /&gt;
|bond0.256&lt;br /&gt;
|Internet from ISP1&lt;br /&gt;
|Allocated from ISP&lt;br /&gt;
|-&lt;br /&gt;
|bond0.257&lt;br /&gt;
|Internet from ISP2&lt;br /&gt;
|Allocated from ISP&lt;br /&gt;
|-&lt;br /&gt;
|bond0.620&lt;br /&gt;
|Transit between wifi proxy and dmvpn spoke node&lt;br /&gt;
|10.1.0.252/30&lt;br /&gt;
|-&lt;br /&gt;
|bond0.701&lt;br /&gt;
|WiFi clients (no access to DMVPN network)&lt;br /&gt;
|172.17.48.0/24&lt;br /&gt;
|-&lt;br /&gt;
|bond0.1101&lt;br /&gt;
|Voice&lt;br /&gt;
|10.2.0.0/24&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Tip|At the time of writing, the recommended Alpine version for building the Host box for the containers should be at minimum 2.7.9 64 bit.}}&lt;br /&gt;
&lt;br /&gt;
= Hardware =&lt;br /&gt;
For an office that will serve under 20 people, the following containers can easily run on low-power hardware such as a Via Nano 1.6Ghz Jetway board with 8GB RAM with dual 500GB SATA hard drives running in RAID 1 (software).&lt;br /&gt;
&lt;br /&gt;
= Setup LXC Host Box =&lt;br /&gt;
&lt;br /&gt;
== Boot Alpine USB == &lt;br /&gt;
Follow the instructions on  http://wiki.alpinelinux.org/wiki/Create_a_Bootable_USB about how to create a bootable USB.&lt;br /&gt;
&lt;br /&gt;
== Alpine Setup ==&lt;br /&gt;
{{Cmd|setup-alpine}}&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&#039;&#039;&#039;You will be prompted something like this...&#039;&#039;&#039;&lt;br /&gt;
!&#039;&#039;&#039;Suggestion on what you could enter...&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;Select keyboard layout [none]:&amp;lt;/code&amp;gt;&lt;br /&gt;
|&#039;&#039;Type an appropriate layout for you&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;Select variant:&amp;lt;/code&amp;gt;&lt;br /&gt;
|&#039;&#039;Type an appropriate layout for you (if prompted)&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;Enter system hostname (short form, e.g. &#039;foo&#039;) [localhost]:&amp;lt;/code&amp;gt;&lt;br /&gt;
|&#039;&#039;Enter the hostname, e.g.&#039;&#039; &#039;&#039;&#039;lxc-host&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;Available interfaces are: eth0&amp;lt;br&amp;gt;Enter &#039;?&#039; for help on bridges, bonding and vlans.&amp;lt;br&amp;gt;Which one do you want to initialize? (or &#039;?&#039; done&#039;)&amp;lt;/code&amp;gt;&lt;br /&gt;
|&#039;&#039;Enter&#039;&#039; &#039;&#039;&#039;bond0.3&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;Available bond slaves are: eth0 eth1&amp;lt;br&amp;gt;Which slave(s) do you want to add to bond0? (or &#039;done&#039;) [eth0]&amp;lt;/code&amp;gt;&lt;br /&gt;
|&#039;&#039;&#039;eth0 eth1&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;IP address for bond0? (or &#039;dhcp&#039;, &#039;none&#039;, &#039;?&#039;) [dhcp]:&amp;lt;/code&amp;gt;&lt;br /&gt;
|&#039;&#039;Press Enter confirming &#039;none&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;IP address for bond0.3? (or &#039;dhcp&#039;, &#039;none&#039;, &#039;?&#039;) [dhcp]:&amp;lt;/code&amp;gt;&lt;br /&gt;
|&#039;&#039;&#039;&amp;lt;%LXCHOST_MANAGEMENT_IP_ADDRESS%&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;Netmask? [255.255.255.0]:&amp;lt;/code&amp;gt;&lt;br /&gt;
|&#039;&#039;&#039;&amp;lt;%DMVPN_MANAGEMENT_NETMASK%&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;Gateway? (or &#039;none&#039;) [none]:&amp;lt;/code&amp;gt;&lt;br /&gt;
|&#039;&#039;&#039;&amp;lt;%DMVPN_MANAGEMENT_NET_IP%&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;Do you want to do any manual network configuration? [no]&amp;lt;/code&amp;gt;&lt;br /&gt;
|&#039;&#039;&#039;no&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;DNS domain name? (e.g. &#039;bar.com&#039;) []:&amp;lt;/code&amp;gt;&lt;br /&gt;
|&#039;&#039;Enter the domain name of your intranet, e.g.,&#039;&#039; &#039;&#039;&#039;office.example.net&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;DNS nameservers(s)? []:&amp;lt;/code&amp;gt;&lt;br /&gt;
|&#039;&#039;&#039;8.8.8.8 8.8.4.4&#039;&#039;&#039; (we will change them later)&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;Changing password for root&amp;lt;br&amp;gt;New password:&amp;lt;/code&amp;gt;&lt;br /&gt;
|&#039;&#039;Enter a secure password for the console&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;Retype password:&amp;lt;/code&amp;gt;&lt;br /&gt;
|&#039;&#039;Retype the above password&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;Which timezone are you in? (&#039;?&#039; for list) [UTC]:&amp;lt;/code&amp;gt;&lt;br /&gt;
|&#039;&#039;Press Enter confirming &#039;UTC&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;HTTP/FTP proxy URL? (e.g. &#039;http://proxy:8080&#039;, or &#039;none&#039;) [none]&amp;lt;/code&amp;gt;&lt;br /&gt;
|&#039;&#039;http://&#039;&#039;&#039;&amp;lt;%DMVPN_LAN_IP%&amp;gt;&#039;&#039;&#039;:8080&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;Enter mirror number (1-9) or URL to add (or r/f/e/done) [f]:&amp;lt;/code&amp;gt;&lt;br /&gt;
|&#039;&#039;Select a mirror close to you and press Enter&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;Which SSH server? (&#039;openssh&#039;, &#039;dropbear&#039; or &#039;none&#039;) [openssh]:&amp;lt;/code&amp;gt;&lt;br /&gt;
|&#039;&#039;Press Enter confirming &#039;openssh&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;Which NTP client to run? (&#039;openntpd&#039;, &#039;chrony&#039; or &#039;none&#039;) [chrony]:&amp;lt;/code&amp;gt;&lt;br /&gt;
|&#039;&#039;Press Enter confirming &#039;chrony&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;Which disk(s) would you like to use? (or &#039;?&#039; for help or &#039;none&#039;) [none]:&amp;lt;/code&amp;gt;&lt;br /&gt;
|&#039;&#039;&#039;sda sdb&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;How would you like to use them? (&#039;sys&#039;, &#039;data&#039; or &#039;?&#039; for help):&amp;lt;/code&amp;gt;&lt;br /&gt;
|&#039;&#039;&#039;data&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;Enter where to store configs (&#039;floppy&#039;, &#039;usb&#039; or &#039;none&#039;) [usb]:&amp;lt;/code&amp;gt;&lt;br /&gt;
|&#039;&#039;Press Enter confirming &#039;usb&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;Enter apk cache directory (or &#039;?&#039; or &#039;none&#039;) [/media/usb/cache]:&amp;lt;/code&amp;gt;&lt;br /&gt;
|&#039;&#039;Press Enter confirming &#039;/media/usb/cache&#039;&#039;&#039;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Upgrade packages&lt;br /&gt;
{{Cmd|apk update&lt;br /&gt;
apk upgrade}}&lt;br /&gt;
&lt;br /&gt;
Save Changes &lt;br /&gt;
{{Cmd|lbu commit}}&lt;br /&gt;
&lt;br /&gt;
Finish Setup with a reboot&lt;br /&gt;
{{Cmd|reboot}}&lt;br /&gt;
&lt;br /&gt;
== Setup Networking ==&lt;br /&gt;
With your favorite editor configure /etc/network/interfaces&lt;br /&gt;
{{cat|/etc/network/interfaces|&lt;br /&gt;
auto lo&lt;br /&gt;
iface lo inet loopback&lt;br /&gt;
&lt;br /&gt;
auto bond0&lt;br /&gt;
iface bond0 inet manual&lt;br /&gt;
        bond-slaves eth0 eth1&lt;br /&gt;
        bond-mode balance-tlb&lt;br /&gt;
        bond-miimon 100&lt;br /&gt;
        bond-updelay 500&lt;br /&gt;
        up ip link set $IFACE up&lt;br /&gt;
        down ip link set $IFACE down&lt;br /&gt;
&lt;br /&gt;
auto bond0.3&lt;br /&gt;
iface bond0.3 inet static&lt;br /&gt;
	address &amp;lt;%LXCHOST_MANAGEMENT_IP_ADDRESS%&amp;gt;&lt;br /&gt;
	netmask &amp;lt;%DMVPN_MANAGEMENT_NETMASK%&amp;gt;&lt;br /&gt;
	gateway &amp;lt;%DMVPN_MANAGEMENT_IP%&amp;gt;&lt;br /&gt;
&lt;br /&gt;
auto bond0.101&lt;br /&gt;
iface bond0.101 inet manual&lt;br /&gt;
	up ip link set $IFACE up&lt;br /&gt;
	down ip link set $IFACE down&lt;br /&gt;
&lt;br /&gt;
auto bond0.1101&lt;br /&gt;
iface bond0.1101 inet manual&lt;br /&gt;
	up ip link set $IFACE up&lt;br /&gt;
	down ip link set $IFACE down&lt;br /&gt;
&lt;br /&gt;
auto bond0.701&lt;br /&gt;
iface bond0.701 inet manual&lt;br /&gt;
	up ip link set $IFACE up&lt;br /&gt;
	down ip link set $IFACE down&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Apply changes by restarting networking&lt;br /&gt;
{{Cmd|/etc/init.d/networking restart}}&lt;br /&gt;
&lt;br /&gt;
== Enable IP Forwarding ==&lt;br /&gt;
{{Cmd|echo &amp;quot;1&amp;quot; &amp;gt; /proc/sys/net/ipv4/ip_forward}}&lt;br /&gt;
== Setup Firewall ==&lt;br /&gt;
{{Cmd|apk add acf-awall}}&lt;br /&gt;
&lt;br /&gt;
With your favorite editor, create the base policy for the firewall&lt;br /&gt;
{{cat|/etc/awall/optional/base.json|&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;description&amp;quot;: &amp;quot;Management&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
  &amp;quot;policy&amp;quot;: [&lt;br /&gt;
    { &amp;quot;in&amp;quot;: &amp;quot;_fw&amp;quot;, &amp;quot;action&amp;quot;: &amp;quot;accept&amp;quot; }&lt;br /&gt;
  ],&lt;br /&gt;
&lt;br /&gt;
  &amp;quot;filter&amp;quot;: [&lt;br /&gt;
    {&lt;br /&gt;
      &amp;quot;out&amp;quot;: &amp;quot;_fw&amp;quot;,&lt;br /&gt;
      &amp;quot;service&amp;quot;: [ &amp;quot;ssh&amp;quot;, &amp;quot;https&amp;quot;, &amp;quot;ping&amp;quot; ],&lt;br /&gt;
      &amp;quot;action&amp;quot;: &amp;quot;accept&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
  ]&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
Activate the firewall, and allow iptables to startup automatically at boot&lt;br /&gt;
{{Cmd|modprobe ip_tables&lt;br /&gt;
awall enable base&lt;br /&gt;
awall activate -f&lt;br /&gt;
rc-update add iptables}}&lt;br /&gt;
&lt;br /&gt;
== Install LXC ==&lt;br /&gt;
Install the LXC and Bridge packages&lt;br /&gt;
{{Cmd|apk add lxc bridge}}&lt;br /&gt;
With your favorite editor configure /etc/lxc/default.conf&lt;br /&gt;
{{cat|/etc/lxc/default.conf|&lt;br /&gt;
## Allow containers in the same VLAN to see each other&lt;br /&gt;
lxc.network.type {{=}} macvlan&lt;br /&gt;
lxc.network.macvlan.mode {{=}} bridge&lt;br /&gt;
lxc.network.link {{=}} bond0.3&lt;br /&gt;
lxc.network.name {{=}} eth0&lt;br /&gt;
&lt;br /&gt;
## Restrict capabilities of the containers&lt;br /&gt;
lxc.cap.drop {{=}} sys_admin audit_control audit_write fsetid ipc_lock&lt;br /&gt;
lxc.cap.drop {{=}} ipc_owner lease linux_immutable mac_admin mac_override&lt;br /&gt;
lxc.cap.drop {{=}} mknod setfcap setpcap sys_module sys_nice sys_pacct&lt;br /&gt;
lxc.cap.drop {{=}} sys_ptrace sys_rawio sys_tty_config sys_time&lt;br /&gt;
}}&lt;br /&gt;
Finish Installation&lt;br /&gt;
{{Cmd|lbu ci&lt;br /&gt;
reboot}}&lt;br /&gt;
= Install the Web Proxy Container =&lt;br /&gt;
== Create and Configure the container ==&lt;br /&gt;
{{Cmd|lxc-create -n webproxy -f /etc/lxc/default.conf -t alpine}}&lt;br /&gt;
Create the startup Script&lt;br /&gt;
{{Cmd|ln -s /etc/init.d/lxc /etc/init.d/lxc.webproxy}}&lt;br /&gt;
&lt;br /&gt;
Edit the container&#039;s config file found at /var/lib/lxc/webproxy/config, to reflect the network for the web proxy container&lt;br /&gt;
&lt;br /&gt;
{{cat|/var/lib/lxc/webproxy/config|&lt;br /&gt;
...&lt;br /&gt;
lxc.network.link {{=}} bond0.101&lt;br /&gt;
...&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Start the container&lt;br /&gt;
{{Cmd|/etc/iniit.d/lxc.webproxy}}&lt;br /&gt;
&lt;br /&gt;
Configure the container to automatically start&lt;br /&gt;
{{Cmd|rc-update add lxc.webproxy}}&lt;br /&gt;
&lt;br /&gt;
== Enter the webproxy container ==&lt;br /&gt;
{{Cmd|lxc-console -n webproxy}}&lt;br /&gt;
Login as root&lt;br /&gt;
{{Note|If the need arises to exit the container press {{Key| Ctrl}}+{{Key| a}} + {{Key| q}}}}&lt;br /&gt;
Remove obsolete /etc/network/interfaces&lt;br /&gt;
{{Cmd|rm /etc/network/interfaces}}&lt;br /&gt;
Create and configure the new /etc/network/interfaces as shown below:&lt;br /&gt;
{{cat|/etc/network/interfaces|&lt;br /&gt;
auto lo&lt;br /&gt;
iface lo inet loopback&lt;br /&gt;
&lt;br /&gt;
auto eth0&lt;br /&gt;
iface eth0 inet static&lt;br /&gt;
	address 10.1.0.2&lt;br /&gt;
	netmask 255.255.255.192&lt;br /&gt;
	gateway 10.1.0.1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Startup networking &lt;br /&gt;
{{Cmd| /etc/init.d/networking start}}&lt;br /&gt;
&lt;br /&gt;
Add rule to DMVPN awall policy to allow this proxy out to the internet&lt;br /&gt;
{{Note| this is to be configured on the DMVPN awall config}}&lt;br /&gt;
{{cat| /etc/awall/optional/internet-host.json|&lt;br /&gt;
{&lt;br /&gt;
     &amp;quot;in&amp;quot;: &amp;quot;B&amp;quot;,&lt;br /&gt;
      &amp;quot;src&amp;quot;: &amp;quot;$10.1.0.2&amp;quot;,&lt;br /&gt;
      &amp;quot;out&amp;quot;: &amp;quot;E&amp;quot;,&lt;br /&gt;
      &amp;quot;action&amp;quot;: &amp;quot;accept&amp;quot;,&lt;br /&gt;
},&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Configure remote administration&lt;br /&gt;
{{Cmd|apk update&lt;br /&gt;
setup-sshd -c openssh&lt;br /&gt;
sed -i &amp;quot;s/.PasswordAuthentication yes/PasswordAuthentication no/&amp;quot; /etc/ssh/sshd_config&lt;br /&gt;
sed -i &amp;quot;s/.UseDNS yes/UseDNS no/&amp;quot; /etc/ssh/sshd_config}}&lt;br /&gt;
&lt;br /&gt;
Start ssh&lt;br /&gt;
{{Cmd|/etc/init.d/sshd start}}&lt;br /&gt;
&lt;br /&gt;
Configure a passwd for the container&lt;br /&gt;
{{Cmd|passwd}}&lt;br /&gt;
&lt;br /&gt;
Setup acf for web administration&lt;br /&gt;
{{Cmd|setup-acf}}&lt;br /&gt;
&lt;br /&gt;
== Setup Firewall ==&lt;br /&gt;
{{Cmd|apk add acf-awall}}&lt;br /&gt;
&lt;br /&gt;
With your favorite editor, create the policies for the firewall&lt;br /&gt;
{{cat|/etc/awall/optional/base.json|&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;description&amp;quot;: &amp;quot;Management&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
  &amp;quot;policy&amp;quot;: [&lt;br /&gt;
    { &amp;quot;in&amp;quot;: &amp;quot;_fw&amp;quot;, &amp;quot;action&amp;quot;: &amp;quot;accept&amp;quot; }&lt;br /&gt;
  ],&lt;br /&gt;
&lt;br /&gt;
  &amp;quot;filter&amp;quot;: [&lt;br /&gt;
    {&lt;br /&gt;
      &amp;quot;out&amp;quot;: &amp;quot;_fw&amp;quot;,&lt;br /&gt;
      &amp;quot;service&amp;quot;: [ &amp;quot;ssh&amp;quot;, &amp;quot;https&amp;quot;, &amp;quot;ping&amp;quot; ],&lt;br /&gt;
      &amp;quot;action&amp;quot;: &amp;quot;accept&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
  ]&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
{{cat|/etc/awall/optional/webproxy.json|&lt;br /&gt;
{&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;Web Proxy&amp;quot;,&lt;br /&gt;
 &lt;br /&gt;
     &amp;quot;filter&amp;quot;: [&lt;br /&gt;
       {&lt;br /&gt;
         &amp;quot;out&amp;quot;: &amp;quot;_fw&amp;quot;,&lt;br /&gt;
         &amp;quot;service&amp;quot;: [ &amp;quot;http&amp;quot;, &amp;quot;http-alt&amp;quot; ],&lt;br /&gt;
         &amp;quot;action&amp;quot;: &amp;quot;accept&amp;quot;&lt;br /&gt;
       }&lt;br /&gt;
    ]&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
Activate the firewall, and allow iptables to startup automatically at boot&lt;br /&gt;
{{Cmd|awall enable base&lt;br /&gt;
awall enable webproxy&lt;br /&gt;
awall activate -f&lt;br /&gt;
rc-update add iptables&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Install and Configure the Squid Web Proxy Service ==&lt;br /&gt;
Install the required packages &lt;br /&gt;
{{Cmd|apk add acf-squid squark acf-lighttpd}}&lt;br /&gt;
&lt;br /&gt;
Configure /etc/squid/squid.conf, replace &amp;lt;%WEBPROXY_IP_ADDRESS%&amp;gt;, &amp;lt;%HOSTNAME%&amp;gt;, and &amp;lt;%DOMAIN%&amp;gt;&lt;br /&gt;
{{cat|/etc/init.d/squid/squid.conf|&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#Squid config for webproxy&lt;br /&gt;
&lt;br /&gt;
# This port listens for client requests&lt;br /&gt;
http_port 8080&lt;br /&gt;
&lt;br /&gt;
visible_hostname &amp;lt;%HOSTNAME%&amp;gt;.&amp;lt;%DOMAIN%&amp;gt;&lt;br /&gt;
cache_mem 8 MB&lt;br /&gt;
# If you don&#039;t have an HD installed comment the &amp;quot;cache_dir&amp;quot; line below&lt;br /&gt;
cache_dir aufs /var/cache/squid 900 16 256&lt;br /&gt;
&lt;br /&gt;
# Even though we only use one proxy, this line is recommended&lt;br /&gt;
# More info: http://www.squid-cache.org/Versions/v2/2.7/cfgman/hierarchy_stoplist.html&lt;br /&gt;
hierarchy_stoplist cgi-bin ?&lt;br /&gt;
&lt;br /&gt;
# Keep 7 days of access logs&lt;br /&gt;
logfile_rotate 7&lt;br /&gt;
&lt;br /&gt;
logformat squark %ts.%03tu %6tr %&amp;gt;a %Ss/%03&amp;gt;Hs %&amp;lt;st %rm %ru %un %Sh/%&amp;lt;A %mt %rG&lt;br /&gt;
access_log /var/log/squid/access.log squark&lt;br /&gt;
cache_store_log none&lt;br /&gt;
pid_filename /var/run/squid.pid&lt;br /&gt;
&lt;br /&gt;
# Make sure client IP is passed to Squark&lt;br /&gt;
log_uses_indirect_client on&lt;br /&gt;
acl_uses_indirect_client on&lt;br /&gt;
&lt;br /&gt;
# Fix for problems with branch file transfer application&lt;br /&gt;
# ignore_expect_100 on (deprecated)&lt;br /&gt;
&lt;br /&gt;
# Debugging Squid, see http://wiki.squid-cache.org/KnowledgeBase/DebugSections&lt;br /&gt;
# for more info&lt;br /&gt;
# Keep 7 days of cache log&lt;br /&gt;
debug_options rotate=7&lt;br /&gt;
&lt;br /&gt;
# Web auditors want to see the full uri, even with the query terms&lt;br /&gt;
strip_query_terms off&lt;br /&gt;
&lt;br /&gt;
refresh_pattern ^ftp:		1440	20%	10080&lt;br /&gt;
refresh_pattern ^gopher:	1440	0%	1440&lt;br /&gt;
refresh_pattern -i (/cgi-bin/|\?) 0	0%	0&lt;br /&gt;
refresh_pattern .		0	20%	4320&lt;br /&gt;
&lt;br /&gt;
coredump_dir /var/cache/squid&lt;br /&gt;
&lt;br /&gt;
# &lt;br /&gt;
# Authentication&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#&lt;br /&gt;
# Access Control Lists (ACL&#039;s)&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
# Standard ACL settings&lt;br /&gt;
acl QUERY urlpath_regex cgi-bin \? asp aspx jsp&lt;br /&gt;
acl to_localhost dst &amp;lt;%WEBPROXY_IP_ADDRESS%&amp;gt;&lt;br /&gt;
acl SSL_ports port 443 563 8004 9000&lt;br /&gt;
acl Safe_ports port 21 70 80 81 210 280 443 563 499 591 777 1024 1022 1025-65535&lt;br /&gt;
acl purge method PURGE&lt;br /&gt;
acl CONNECT method CONNECT&lt;br /&gt;
&lt;br /&gt;
# Squark filter&lt;br /&gt;
url_rewrite_program /usr/bin/squark-filter&lt;br /&gt;
url_rewrite_children 1 concurrency=128&lt;br /&gt;
&lt;br /&gt;
# Require authentication&lt;br /&gt;
acl userlist  src all&lt;br /&gt;
&lt;br /&gt;
# Definition of zones &lt;br /&gt;
acl Zone_B src &amp;lt;%LAN_SUBNET%&amp;gt;/&amp;lt;%LAN_SLASH_NOTATION%&amp;gt;&lt;br /&gt;
#acl Zone_D src &amp;lt;%WiFi_SUBNET%&amp;gt;/&amp;lt;%WiFi_SLASH_NOTATION%&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# Settings migrated from smn&lt;br /&gt;
acl Zone_B_AllowedUserDomains     dstdomain &amp;quot;/etc/squid/alloweduserdomains&amp;quot;&lt;br /&gt;
acl Zone_B_AllowedServicesHosts   src &amp;quot;/etc/squid/allowedserviceshosts&amp;quot;&lt;br /&gt;
acl Zone_B_AllowedServicesDomains dstdomain &amp;quot;/etc/squid/allowedservicesdomains&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Settings migrated from services&lt;br /&gt;
acl AnonBrowsers browser &amp;quot;/etc/squid/anonbrowserlist&amp;quot;&lt;br /&gt;
acl AnonIPAddrs src &amp;quot;/etc/squid/anoniplist&amp;quot;&lt;br /&gt;
acl AnonDomain url_regex &amp;quot;/etc/squid/anondomainlist&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&lt;br /&gt;
# Access restrictions&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
cache deny QUERY&lt;br /&gt;
&lt;br /&gt;
# Only allow cachemgr access from localhost&lt;br /&gt;
http_access allow manager localhost&lt;br /&gt;
http_access deny manager&lt;br /&gt;
&lt;br /&gt;
# Only allow purge requests from localhost&lt;br /&gt;
http_access allow purge localhost&lt;br /&gt;
http_access deny purge&lt;br /&gt;
&lt;br /&gt;
# Deny requests to unknown ports&lt;br /&gt;
http_access deny !Safe_ports&lt;br /&gt;
&lt;br /&gt;
# Deny CONNECT to other than SSL ports&lt;br /&gt;
http_access deny CONNECT !SSL_ports&lt;br /&gt;
&lt;br /&gt;
# Allow hosts in Zone_B and Zone_C to access hosts listed in&lt;br /&gt;
# /etc/squid/alloweduserdomains&lt;br /&gt;
http_access allow Zone_B Zone_B_AllowedUserDomains&lt;br /&gt;
&lt;br /&gt;
# Allow hosts listed in /etc/squid/allowedserviceshosts to&lt;br /&gt;
# access domains listed in /etc/squid/allowedservicesdomains&lt;br /&gt;
http_access allow Zone_B_AllowedServicesHosts Zone_B_AllowedServicesDomains&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Denying all access not explictly allowed&lt;br /&gt;
http_access deny all&lt;br /&gt;
&lt;br /&gt;
##Squark URL rewriter&lt;br /&gt;
#Prevent squark from filtering itself&lt;br /&gt;
url_rewrite_access deny manager&lt;br /&gt;
url_rewrite_access deny to_localhost&lt;br /&gt;
&lt;br /&gt;
#We do not want authentication for these sites:&lt;br /&gt;
url_rewrite_access deny Zone_B Zone_B_AllowedUserDomains&lt;br /&gt;
url_rewrite_access deny Zone_B Zone_B_AllowedServicesDomains&lt;br /&gt;
&lt;br /&gt;
http_reply_access allow all&lt;br /&gt;
icp_access allow all&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Configure /etc/lighttpd/lighttpd.conf, replace &amp;lt;%WEBPROXY_IP_ADDRESS%&amp;gt;&lt;br /&gt;
{{cat|/etc/lighttpd/lighttpd.conf|&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
##############################################################################&lt;br /&gt;
# Default lighttpd.conf for Gentoo.&lt;br /&gt;
# $Header: /var/cvsroot/gentoo-x86/www-servers/lighttpd/files/conf/lighttpd.conf,v 1.3 2005/09/01 14:22:35 ka0ttic Exp $&lt;br /&gt;
###############################################################################&lt;br /&gt;
var.basedir  = &amp;quot;/var/www/localhost&amp;quot;&lt;br /&gt;
var.logdir   = &amp;quot;/var/log/lighttpd&amp;quot;&lt;br /&gt;
var.statedir = &amp;quot;/var/lib/lighttpd&amp;quot;&lt;br /&gt;
&lt;br /&gt;
server.modules = (&lt;br /&gt;
    &amp;quot;mod_access&amp;quot;,&lt;br /&gt;
    &amp;quot;mod_accesslog&amp;quot;,&lt;br /&gt;
    &amp;quot;mod_extforward&amp;quot;&lt;br /&gt;
)&lt;br /&gt;
include &amp;quot;mime-types.conf&amp;quot; &lt;br /&gt;
&lt;br /&gt;
include &amp;quot;mod_cgi.conf&amp;quot;&lt;br /&gt;
&lt;br /&gt;
server.username      = &amp;quot;lighttpd&amp;quot;&lt;br /&gt;
&lt;br /&gt;
server.groupname     = &amp;quot;lighttpd&amp;quot;&lt;br /&gt;
&lt;br /&gt;
server.document-root = var.basedir + &amp;quot;/squark&amp;quot;&lt;br /&gt;
&lt;br /&gt;
server.pid-file      = &amp;quot;/var/run/lighttpd.pid&amp;quot;&lt;br /&gt;
    &lt;br /&gt;
server.errorlog      = var.logdir  + &amp;quot;/error.log&amp;quot;&lt;br /&gt;
&lt;br /&gt;
server.indexfiles    = (&amp;quot;index.php&amp;quot;, &amp;quot;index.html&amp;quot;,&lt;br /&gt;
                                                &amp;quot;index.htm&amp;quot;, &amp;quot;default.htm&amp;quot;)&lt;br /&gt;
server.follow-symlink = &amp;quot;enable&amp;quot;&lt;br /&gt;
&lt;br /&gt;
static-file.exclude-extensions = (&amp;quot;.php&amp;quot;, &amp;quot;.pl&amp;quot;, &amp;quot;.cgi&amp;quot;, &amp;quot;.fcgi&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
accesslog.filename   = var.logdir + &amp;quot;/access.log&amp;quot;&lt;br /&gt;
&lt;br /&gt;
url.access-deny = (&amp;quot;~&amp;quot;, &amp;quot;.inc&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
extforward.forwarder = (&amp;quot;&amp;lt;%WEBPROXY_IP_ADDRESS%&amp;gt;&amp;quot; =&amp;gt; &amp;quot;trust&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Configure mod_cgi.conf&lt;br /&gt;
{{cat|/etc/lighttpd/mod_cgi.conf|&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
###############################################################################&lt;br /&gt;
# mod_cgi.conf&lt;br /&gt;
# include&#039;d by lighttpd.conf.&lt;br /&gt;
# $Header: /var/cvsroot/gentoo-x86/www-servers/lighttpd/files/conf/mod_cgi.conf,v 1.1 2005/08/27 12:36:13 ka0ttic Exp $&lt;br /&gt;
###############################################################################&lt;br /&gt;
&lt;br /&gt;
#&lt;br /&gt;
# see cgi.txt for more information on using mod_cgi&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
server.modules += (&amp;quot;mod_cgi&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# NOTE: this requires mod_alias&lt;br /&gt;
alias.url = (&lt;br /&gt;
     &amp;quot;/cgi-bin/&amp;quot;	    =&amp;gt;	    var.basedir + &amp;quot;/cgi-bin/&amp;quot;&lt;br /&gt;
)&lt;br /&gt;
&lt;br /&gt;
#&lt;br /&gt;
# Note that you&#039;ll also want to enable the&lt;br /&gt;
# cgi-bin alias via mod_alias (above).&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
$HTTP[&amp;quot;url&amp;quot;] =~ &amp;quot;^/cgi-bin/&amp;quot; {&lt;br /&gt;
    # disable directory listings&lt;br /&gt;
    dir-listing.activate = &amp;quot;disable&amp;quot;&lt;br /&gt;
    # only allow cgi&#039;s in this directory&lt;br /&gt;
    cgi.assign = (&lt;br /&gt;
		&amp;quot;.pl&amp;quot;	=&amp;gt;	&amp;quot;/usr/bin/perl&amp;quot;,&lt;br /&gt;
		&amp;quot;.cgi&amp;quot;	=&amp;gt;	&amp;quot;/usr/bin/haserl&amp;quot;&lt;br /&gt;
	)&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Link the Squark web pages to the Web server home directory&lt;br /&gt;
{{Cmd|ln -s /usr/share/squark/www/ /var/www/localhost/squark}}&lt;br /&gt;
&lt;br /&gt;
Create a Squark group&lt;br /&gt;
{{Cmd|addgroup squark}}&lt;br /&gt;
&lt;br /&gt;
Make &#039;squid&#039; and &#039;lighttpd&#039; users member of the group squark&lt;br /&gt;
{{Cmd|addgroup squid squark&lt;br /&gt;
addgroup lighttpd squark}}&lt;br /&gt;
&lt;br /&gt;
Start lighttpd, and configure the service to start on when container is booted&lt;br /&gt;
{{Cmd|/etc/init.d/lighttpd start&lt;br /&gt;
rc-update add lighttpd}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Start Squid, and configure to start at boot&lt;br /&gt;
{{Cmd|/etc/init.d/squid start&lt;br /&gt;
rc-update add squid}}&lt;br /&gt;
&lt;br /&gt;
= Install the DHCP and DNS server  Container =&lt;br /&gt;
== Create and Configure the container ==&lt;br /&gt;
{{Cmd|lxc-create -n dhcpdns -f /etc/lxc/default.conf -t alpine}}&lt;br /&gt;
Create the startup Script&lt;br /&gt;
{{Cmd|ln -s /etc/init.d/lxc /etc/init.d/lxc.dhcpdns}}&lt;br /&gt;
&lt;br /&gt;
Edit the container&#039;s config file found at /var/lib/lxc/dhcpdns/config, to reflect the network for the web proxy container&lt;br /&gt;
&lt;br /&gt;
{{cat|/var/lib/lxc/dhcpdns/config|&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#Management Network Config&lt;br /&gt;
lxc.network.type = macvlan&lt;br /&gt;
lxc.network.macvlan.mode = bridge&lt;br /&gt;
lxc.network.link = bond0.3&lt;br /&gt;
lxc.network.name = eth0&lt;br /&gt;
&lt;br /&gt;
#WiFi Network Config&lt;br /&gt;
lxc.network.type = macvlan&lt;br /&gt;
lxc.network.macvlan.mode = bridge&lt;br /&gt;
lxc.network.link = bond0.701&lt;br /&gt;
lxc.network.name = eth1&lt;br /&gt;
&lt;br /&gt;
#Voice Network Config&lt;br /&gt;
lxc.network.type = macvlan&lt;br /&gt;
lxc.network.macvlan.mode = bridge&lt;br /&gt;
lxc.network.link = bond0.1101&lt;br /&gt;
lxc.network.name = eth2&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Start the container&lt;br /&gt;
{{Cmd|/etc/init.d/lxc.dhcpdns}}&lt;br /&gt;
&lt;br /&gt;
Configure the container to automatically start&lt;br /&gt;
{{Cmd|rc-update add lxc.dhcpdns}}&lt;br /&gt;
&lt;br /&gt;
== Enter the dhcpdns container ==&lt;br /&gt;
{{Cmd|lxc-console -n dhcpdns}}&lt;br /&gt;
Login as root&lt;br /&gt;
{{Note|If the need arises to exit the container press {{Key| Ctrl}}+{{Key| a}} + {{Key| q}}}}&lt;br /&gt;
Remove obsolete /etc/network/interfaces&lt;br /&gt;
{{Cmd|rm /etc/network/interfaces}}&lt;br /&gt;
Create and configure the new /etc/network/interfaces as shown below:&lt;br /&gt;
{{cat|/etc/network/interfaces|&lt;br /&gt;
auto lo&lt;br /&gt;
iface lo inet loopback&lt;br /&gt;
&lt;br /&gt;
#Management VLAN&lt;br /&gt;
auto eth0&lt;br /&gt;
iface eth0 inet static&lt;br /&gt;
      address 10.1.0.130&lt;br /&gt;
      netmask 255.255.255.192&lt;br /&gt;
&lt;br /&gt;
#WiFi VLAN&lt;br /&gt;
auto eth1&lt;br /&gt;
iface eth1 inet static&lt;br /&gt;
      address 172.16.48.2&lt;br /&gt;
      netmask 255.255.255.0&lt;br /&gt;
&lt;br /&gt;
#Voice VLAN&lt;br /&gt;
auto eth2&lt;br /&gt;
iface eth2 inet static&lt;br /&gt;
      address 10.2.0.2&lt;br /&gt;
      netmask 255.255.255.0&lt;br /&gt;
      gateway 10.2.0.1&lt;br /&gt;
      up ip address add 10.2.0.3/24 dev eth0&lt;br /&gt;
     &lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Startup networking &lt;br /&gt;
{{Cmd| /etc/init.d/networking start}}&lt;br /&gt;
&lt;br /&gt;
Configure and enable proxy settings&lt;br /&gt;
{{Cmd|setup-proxy http://10.1.0.2:8080&lt;br /&gt;
. /etc/profile.d/proxy.sh}}&lt;br /&gt;
&lt;br /&gt;
Configure remote administration&lt;br /&gt;
{{Cmd|apk update&lt;br /&gt;
setup-sshd -c openssh&lt;br /&gt;
sed -i &amp;quot;s/.PasswordAuthentication yes/PasswordAuthentication no/&amp;quot; /etc/ssh/sshd_config&lt;br /&gt;
sed -i &amp;quot;s/.UseDNS yes/UseDNS no/&amp;quot; /etc/ssh/sshd_config}}&lt;br /&gt;
&lt;br /&gt;
Start ssh&lt;br /&gt;
{{Cmd|/etc/init.d/sshd start}}&lt;br /&gt;
&lt;br /&gt;
Configure a passwd for the container&lt;br /&gt;
{{Cmd|passwd}}&lt;br /&gt;
&lt;br /&gt;
Setup acf for web administration&lt;br /&gt;
{{Cmd|setup-acf}}&lt;br /&gt;
&lt;br /&gt;
== Setup Firewall ==&lt;br /&gt;
{{Cmd|apk add acf-awall}}&lt;br /&gt;
&lt;br /&gt;
With your favorite editor, create the policies for the firewall&lt;br /&gt;
{{cat|/etc/awall/optional/base.json|&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;description&amp;quot;: &amp;quot;Management&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
  &amp;quot;policy&amp;quot;: [&lt;br /&gt;
    { &amp;quot;in&amp;quot;: &amp;quot;_fw&amp;quot;, &amp;quot;action&amp;quot;: &amp;quot;accept&amp;quot; }&lt;br /&gt;
  ],&lt;br /&gt;
&lt;br /&gt;
  &amp;quot;filter&amp;quot;: [&lt;br /&gt;
    {&lt;br /&gt;
      &amp;quot;out&amp;quot;: &amp;quot;_fw&amp;quot;,&lt;br /&gt;
      &amp;quot;service&amp;quot;: [ &amp;quot;ssh&amp;quot;, &amp;quot;https&amp;quot;, &amp;quot;ping&amp;quot; ],&lt;br /&gt;
      &amp;quot;action&amp;quot;: &amp;quot;accept&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
  ]&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
{{cat|/etc/awall/optional/dhcp.json|&lt;br /&gt;
{&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;DHCP&amp;quot;,&lt;br /&gt;
 &lt;br /&gt;
     &amp;quot;filter&amp;quot;: [&lt;br /&gt;
       {&lt;br /&gt;
         &amp;quot;out&amp;quot;: &amp;quot;_fw&amp;quot;,&lt;br /&gt;
         &amp;quot;service&amp;quot;: &amp;quot;dhcp&amp;quot;,&lt;br /&gt;
         &amp;quot;action&amp;quot;: &amp;quot;accept&amp;quot;&lt;br /&gt;
       }&lt;br /&gt;
    ]&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
{{cat|/etc/awall/optional/dns.json|&lt;br /&gt;
{&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;DNS&amp;quot;,&lt;br /&gt;
 &lt;br /&gt;
     &amp;quot;filter&amp;quot;: [&lt;br /&gt;
       {&lt;br /&gt;
         &amp;quot;out&amp;quot;: &amp;quot;_fw&amp;quot;,&lt;br /&gt;
         &amp;quot;service&amp;quot;: &amp;quot;dns&amp;quot;,&lt;br /&gt;
         &amp;quot;action&amp;quot;: &amp;quot;accept&amp;quot;&lt;br /&gt;
       }&lt;br /&gt;
    ]&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
Activate the firewall, and allow iptables to startup automatically at boot&lt;br /&gt;
{{Cmd|awall enable base&lt;br /&gt;
awall enable dhcp&lt;br /&gt;
awall enable dns&lt;br /&gt;
awall activate -f&lt;br /&gt;
rc-update add iptables&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Install and Configure DHCP and DNS services ==&lt;br /&gt;
install the dhcpd package&lt;br /&gt;
{{Cmd|apk add acf-dhcp}}&lt;br /&gt;
Create a new dhcpd.conf file&lt;br /&gt;
{{cat|/etc/dhcp/dhcpd.conf|&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
## Common settings&lt;br /&gt;
default-lease-time 302400;&lt;br /&gt;
max-lease-time 604800;&lt;br /&gt;
ddns-update-style none;&lt;br /&gt;
log-facility local7;&lt;br /&gt;
authoritative;&lt;br /&gt;
&lt;br /&gt;
## Common options&lt;br /&gt;
option time-servers 10.2.0.1;&lt;br /&gt;
option boot-server code 66 = string;&lt;br /&gt;
&lt;br /&gt;
## Voice&lt;br /&gt;
subnet 10.2.0.0 netmask 255.255.255.0&lt;br /&gt;
{&lt;br /&gt;
   range &amp;quot;10.2.0.20 10.2.0.250&amp;quot;;&lt;br /&gt;
   option domain-name-servers 10.2.0.2;&lt;br /&gt;
   option routers 10.2.0.1;&lt;br /&gt;
   option boot-server &amp;quot;http://10.2.0.4&amp;quot;;&lt;br /&gt;
   option domain-name &amp;quot;office.example.net&amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
## WiFi&lt;br /&gt;
subnet 172.17.48.0 netmask 255.255.255.0&lt;br /&gt;
{&lt;br /&gt;
  range &amp;quot;172.17.48.10 172.17.48.250&amp;quot;;&lt;br /&gt;
  option routers 172.17.48.1;&lt;br /&gt;
  option domain-name-servers 172.17.48.1;  &lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
Start DHCP service and add to runlevel default &lt;br /&gt;
{{Cmd|rc-service dhcpd start  &lt;br /&gt;
rc-update add dhcpd}}&lt;br /&gt;
&lt;br /&gt;
Install nsd and unbound packages&lt;br /&gt;
{{Cmd|apk add unbound }}&lt;br /&gt;
&lt;br /&gt;
Remove unbound.conf&lt;br /&gt;
{{Cmd|rm /etc/unbound/unbound.conf}}&lt;br /&gt;
&lt;br /&gt;
Create with your favorite editor a new configuration for unbound&lt;br /&gt;
{{cat|/etc/unbound/unbound.conf|&lt;br /&gt;
#Recursive DNS configuration&lt;br /&gt;
&lt;br /&gt;
server:&lt;br /&gt;
        interface: 10.2.0.2&lt;br /&gt;
        do-not-query-localhost: no&lt;br /&gt;
        verbosity: 1&lt;br /&gt;
        do-ip4: yes&lt;br /&gt;
        do-ip6: no&lt;br /&gt;
        do-udp: yes&lt;br /&gt;
        do-tcp: yes&lt;br /&gt;
        do-daemonize: yes&lt;br /&gt;
        access-control: 10.1.0.0/16 allow&lt;br /&gt;
        access-control: 127.0.0.0/8 allow&lt;br /&gt;
     &lt;br /&gt;
#use the root.hints file to determine where to send DNS queries outside of network&lt;br /&gt;
root-hints: &amp;quot;/etc/unbound/root.hints&amp;quot; &lt;br /&gt;
&lt;br /&gt;
stub-zone:&lt;br /&gt;
	name: &amp;quot;office.example.net&amp;quot;&lt;br /&gt;
	stub-addr: 10.2.0.3&lt;br /&gt;
&lt;br /&gt;
stub-zone:&lt;br /&gt;
        name: &amp;quot;example.net&amp;quot;&lt;br /&gt;
        stub-addr: 172.16.255.1&lt;br /&gt;
        stub-addr: 172.16.255.2&lt;br /&gt;
        stub-addr: 172.16.255.3&lt;br /&gt;
        stub-addr: 172.16.255.4&lt;br /&gt;
        stub-addr: 172.16.255.5&lt;br /&gt;
        stub-addr: 172.16.255.7&lt;br /&gt;
&lt;br /&gt;
stub-zone:&lt;br /&gt;
        name: &amp;quot;example2.net&amp;quot;&lt;br /&gt;
        stub-addr: 172.16.255.1&lt;br /&gt;
        stub-addr: 172.16.255.2&lt;br /&gt;
        stub-addr: 172.16.255.3&lt;br /&gt;
        stub-addr: 172.16.255.4&lt;br /&gt;
        stub-addr: 172.16.255.5&lt;br /&gt;
        stub-addr: 172.16.255.7&lt;br /&gt;
&lt;br /&gt;
}}&lt;br /&gt;
Start Unbound and allow the container to use it&lt;br /&gt;
{{Cmd|/etc/init.d/unbound start&lt;br /&gt;
rc-update add unbound&lt;br /&gt;
echo nameserver 10.2.0.2 &amp;gt; /etc/resolv.conf&lt;br /&gt;
&lt;br /&gt;
Install nsd&lt;br /&gt;
{{Cmd|apk add nsd}}&lt;br /&gt;
Configure nsd configuration&lt;br /&gt;
{{cat|/etc/nsd/nsd.conf|&lt;br /&gt;
server:&lt;br /&gt;
        ip-address: 10.2.0.3&lt;br /&gt;
        port: 53&lt;br /&gt;
        server-count: 1&lt;br /&gt;
        ip4-only: yes&lt;br /&gt;
        hide-version: yes&lt;br /&gt;
        identity: &amp;quot;&amp;quot;&lt;br /&gt;
        zonesdir: &amp;quot;/etc/nsd&amp;quot;&lt;br /&gt;
zone:&lt;br /&gt;
        name: office.example.net&lt;br /&gt;
        zonefile: office.example.net.zone&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Configure Zone file for nsd&lt;br /&gt;
{{cat|/etc/nsd/nsd.conf|&lt;br /&gt;
$ORIGIN office.example.net.&lt;br /&gt;
$TTL 86400&lt;br /&gt;
&lt;br /&gt;
@ IN SOA ns admin (&lt;br /&gt;
2013032200 ;  Serial number [yyyymmddnn]&lt;br /&gt;
28800 ; Refresh&lt;br /&gt;
7200 ; Retry&lt;br /&gt;
864000 ; Expire&lt;br /&gt;
86400 ; Min TTL&lt;br /&gt;
)&lt;br /&gt;
&lt;br /&gt;
@        NS      ns1&lt;br /&gt;
; NSA Servers&lt;br /&gt;
ns1      IN      A       10.2.0.3&lt;br /&gt;
&lt;br /&gt;
;A Records for SIP Devices&lt;br /&gt;
sip     IN      A       10.2.0.4&lt;br /&gt;
media     IN      A       10.2.0.5&lt;br /&gt;
&lt;br /&gt;
;NAPTR Records&lt;br /&gt;
sip        IN     NAPTR   10       1       &amp;quot;s&amp;quot;     &amp;quot;SIP+D2U&amp;quot;  &amp;quot;&amp;quot; _sip._udp.sip.office.example.net.&lt;br /&gt;
&lt;br /&gt;
;SIP SRV Record&lt;br /&gt;
_sip._udp.sip  IN   SRV  10   100    5060    sip&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Check nsd configuration and start service&lt;br /&gt;
{{Cmd|nsd-checkconf /etc/nsd/nsd.conf&lt;br /&gt;
/etc/init.d/nsd start&lt;br /&gt;
rc-update add nsd}}&lt;br /&gt;
&lt;br /&gt;
= Install the SIP Container =&lt;br /&gt;
&lt;br /&gt;
== Create and Configure the container ==&lt;br /&gt;
{{Cmd|lxc-create -n sip -f /etc/lxc/default.conf -t alpine}}&lt;br /&gt;
Create the startup Script&lt;br /&gt;
{{Cmd|ln -s /etc/init.d/lxc /etc/init.d/lxc.sip}}&lt;br /&gt;
&lt;br /&gt;
Edit the container&#039;s config file found at /var/lib/lxc/sip/config, to reflect the network for the sip container&lt;br /&gt;
&lt;br /&gt;
{{cat|/var/lib/lxc/sip/config|&lt;br /&gt;
...&lt;br /&gt;
lxc.network.link {{=}} bond0.1101&lt;br /&gt;
...&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Start the container&lt;br /&gt;
{{Cmd|/etc/iniit.d/lxc.sip}}&lt;br /&gt;
&lt;br /&gt;
Configure the container to automatically start&lt;br /&gt;
{{Cmd|rc-update add lxc.sip}}&lt;br /&gt;
&lt;br /&gt;
== Enter the sip container ==&lt;br /&gt;
{{Cmd|lxc-console -n sip}}&lt;br /&gt;
Login as root&lt;br /&gt;
{{Note|If the need arises to exit the container press {{Key| Ctrl}}+{{Key| a}} + {{Key| q}}}}&lt;br /&gt;
Remove obsolete /etc/network/interfaces&lt;br /&gt;
{{Cmd|rm /etc/network/interfaces}}&lt;br /&gt;
Create and configure the new /etc/network/interfaces as shown below:&lt;br /&gt;
{{cat|/etc/network/interfaces|&lt;br /&gt;
auto lo&lt;br /&gt;
iface lo inet loopback&lt;br /&gt;
&lt;br /&gt;
auto eth0&lt;br /&gt;
iface eth0 inet static&lt;br /&gt;
	address 10.2.0.4&lt;br /&gt;
	netmask 255.255.255.0&lt;br /&gt;
	gateway 10.2.0.1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Startup networking &lt;br /&gt;
{{Cmd| /etc/init.d/networking start}}&lt;br /&gt;
&lt;br /&gt;
Configure and enable proxy settings&lt;br /&gt;
{{Cmd|setup-proxy http://10.1.0.2:8080&lt;br /&gt;
. /etc/profile.d/proxy.sh}}&lt;br /&gt;
&lt;br /&gt;
Configure remote administration&lt;br /&gt;
{{Cmd|apk update&lt;br /&gt;
setup-sshd -c openssh&lt;br /&gt;
sed -i &amp;quot;s/.PasswordAuthentication yes/PasswordAuthentication no/&amp;quot; /etc/ssh/sshd_config&lt;br /&gt;
sed -i &amp;quot;s/.UseDNS yes/UseDNS no/&amp;quot; /etc/ssh/sshd_config}}&lt;br /&gt;
&lt;br /&gt;
Start ssh&lt;br /&gt;
{{Cmd|/etc/init.d/sshd start}}&lt;br /&gt;
&lt;br /&gt;
Configure a passwd for the container&lt;br /&gt;
{{Cmd|passwd}}&lt;br /&gt;
&lt;br /&gt;
Setup acf for web administration&lt;br /&gt;
{{Cmd|setup-acf}}&lt;br /&gt;
&lt;br /&gt;
== Setup Firewall ==&lt;br /&gt;
{{Cmd|apk add acf-awall}}&lt;br /&gt;
&lt;br /&gt;
With your favorite editor, create the policies for the firewall&lt;br /&gt;
{{cat|/etc/awall/optional/base.json|&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;description&amp;quot;: &amp;quot;Management&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
  &amp;quot;policy&amp;quot;: [&lt;br /&gt;
    { &amp;quot;in&amp;quot;: &amp;quot;_fw&amp;quot;, &amp;quot;action&amp;quot;: &amp;quot;accept&amp;quot; }&lt;br /&gt;
  ],&lt;br /&gt;
&lt;br /&gt;
  &amp;quot;filter&amp;quot;: [&lt;br /&gt;
    {&lt;br /&gt;
      &amp;quot;out&amp;quot;: &amp;quot;_fw&amp;quot;,&lt;br /&gt;
      &amp;quot;service&amp;quot;: [ &amp;quot;ssh&amp;quot;, &amp;quot;https&amp;quot;, &amp;quot;ping&amp;quot; ],&lt;br /&gt;
      &amp;quot;action&amp;quot;: &amp;quot;accept&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
  ]&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
{{cat|/etc/awall/optional/sip.json|&lt;br /&gt;
{&lt;br /&gt;
&lt;br /&gt;
	&amp;quot;description&amp;quot;: &amp;quot;Phone System&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
	&amp;quot;filter&amp;quot;: [&lt;br /&gt;
		{&lt;br /&gt;
      &amp;quot;out&amp;quot;: &amp;quot;_fw&amp;quot;,&lt;br /&gt;
      &amp;quot;service&amp;quot;: [ &amp;quot;sip&amp;quot;, &amp;quot;sip-tls&amp;quot; ],&lt;br /&gt;
      &amp;quot;action&amp;quot;: &amp;quot;accept&amp;quot;,&lt;br /&gt;
    }&lt;br /&gt;
	]&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
{{cat|/etc/awall/optional/syslog.json|&lt;br /&gt;
{&lt;br /&gt;
&lt;br /&gt;
  &amp;quot;description&amp;quot;: &amp;quot;Syslog server&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
  &amp;quot;filter&amp;quot;: [&lt;br /&gt;
    {&lt;br /&gt;
      &amp;quot;out&amp;quot;: &amp;quot;_fw&amp;quot;,&lt;br /&gt;
      &amp;quot;service&amp;quot;: &amp;quot;syslog&amp;quot;,&lt;br /&gt;
      &amp;quot;action&amp;quot;: &amp;quot;accept&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
  ]&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
Activate the firewall, and allow iptables to startup automatically at boot&lt;br /&gt;
{{Cmd|awall enable base&lt;br /&gt;
awall enable sip&lt;br /&gt;
awall enable syslog&lt;br /&gt;
awall activate -f&lt;br /&gt;
rc-update add iptables&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==Install and Configure Postgresql==&lt;br /&gt;
Install postgresql package&lt;br /&gt;
{{Cmd|apk update&lt;br /&gt;
apk add acf-postgresql}}&lt;br /&gt;
Prepare the database&lt;br /&gt;
{{Cmd|/etc/init.d/postgresql setup}}&lt;br /&gt;
Configure /var/lib/postgresql/9.3/data/postgresql.conf to set the &#039;log_destination&#039; variable to show:&lt;br /&gt;
{{cat|/var/lib/postgresql/9.3/data/postresql.conf|&lt;br /&gt;
..&lt;br /&gt;
log_destination {{=}}&#039;syslog&#039;&lt;br /&gt;
}}&lt;br /&gt;
Start up the database and configure postgresql to start at boot up&lt;br /&gt;
{{Cmd|/etc/init.d/postgresql start&lt;br /&gt;
rc-update add postgresql}}&lt;br /&gt;
&lt;br /&gt;
== Install acf-provisioning ==&lt;br /&gt;
* vi /etc/kamailio/kamctlrc&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SIP_DOMAIN=sip.office.example.net&lt;br /&gt;
DBENGINE=PGSQL&lt;br /&gt;
DBHOST=127.0.0.1&lt;br /&gt;
DBNAME=openser&lt;br /&gt;
DBRWUSER=openser&lt;br /&gt;
DBRWPW=&amp;quot;openser&amp;quot;&lt;br /&gt;
DBROUSER=openserro&lt;br /&gt;
DBROPW=openserro&lt;br /&gt;
DBROOTUSER=&amp;quot;postgres&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
* yes | kamdbctl create openser&lt;br /&gt;
* apk add acf-provisioning lua-socket lua-expat&lt;br /&gt;
* Create /etc/provisioning/update_device_params.lua:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-- This is the script run after editing device params - basically only worried about extension and password&lt;br /&gt;
local functions, params, oldparams = ...&lt;br /&gt;
&lt;br /&gt;
require(&amp;quot;posix&amp;quot;)&lt;br /&gt;
require(&amp;quot;luasql.postgres&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
local root = &amp;quot;/var/www/provisioning/htdocs/&amp;quot;&lt;br /&gt;
local b62 = &amp;quot;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789&amp;quot;&lt;br /&gt;
&lt;br /&gt;
APP.logevent(&amp;quot;got to update_device_params script&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
local function generatepw()&lt;br /&gt;
	-- generate a random 12-character alphanumeric string&lt;br /&gt;
	local file = io.open(&amp;quot;/dev/urandom&amp;quot;)&lt;br /&gt;
	local str = &amp;quot;&amp;quot;&lt;br /&gt;
	if file == nil then return nil end&lt;br /&gt;
	local size = 12&lt;br /&gt;
	while (size &amp;gt; 0 ) do&lt;br /&gt;
		local offset = (string.byte(file:read(1)) % 62) + 1&lt;br /&gt;
		str = str .. string.sub (b62, offset, offset)&lt;br /&gt;
		size = size - 1&lt;br /&gt;
	end&lt;br /&gt;
	return str&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function findip(mac)&lt;br /&gt;
	if not mac or mac == &amp;quot;&amp;quot; then&lt;br /&gt;
		return nil&lt;br /&gt;
	end&lt;br /&gt;
	local ipaddr = functions.getselectresponse(&amp;quot;SELECT ip FROM provisioning_requests WHERE mac~*&#039;&amp;quot;..mac..&amp;quot;&#039;&amp;quot;)&lt;br /&gt;
	if ipaddr and ipaddr[1] then&lt;br /&gt;
		return ipaddr[1].ip&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function addfuturenotify(ipaddr, extension)&lt;br /&gt;
	local res, err = pcall(function()&lt;br /&gt;
		functions.runsqlcommand(&amp;quot;DELETE FROM notify WHERE ipaddr=&#039;&amp;quot;..ipaddr..&amp;quot;&#039; AND extension=&#039;&amp;quot;..extension..&amp;quot;&#039;&amp;quot;)&lt;br /&gt;
	end)&lt;br /&gt;
	if not res and err then&lt;br /&gt;
		if string.match(err, &amp;quot;relation \&amp;quot;(%S+)\&amp;quot; does not exist&amp;quot;) then&lt;br /&gt;
			functions.runsqlcommand(&amp;quot;CREATE TABLE notify (ipaddr text, extension text, seasoned boolean DEFAULT false)&amp;quot;)&lt;br /&gt;
		else&lt;br /&gt;
			assert(res, err)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	-- if table missing, create it and delete again&lt;br /&gt;
	functions.runsqlcommand(&amp;quot;INSERT INTO notify VALUES(&#039;&amp;quot;..ipaddr..&amp;quot;&#039;, &#039;&amp;quot;..extension..&amp;quot;&#039;)&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local notify_device = function(mac, extension)&lt;br /&gt;
	local ipaddr = findip(mac)&lt;br /&gt;
	if ipaddr then&lt;br /&gt;
		APP.logevent(&amp;quot;Notifying &amp;quot;..ipaddr..&amp;quot; to update for &amp;quot;..(mac or &amp;quot;&amp;quot;))&lt;br /&gt;
		os.execute(&amp;quot;/etc/provisioning/notify_device &amp;quot;..ipaddr..&amp;quot; &amp;quot;..extension)&lt;br /&gt;
		addfuturenotify(ipaddr, extension)&lt;br /&gt;
	else&lt;br /&gt;
		APP.logevent(&amp;quot;Warning - could not find IP address for &amp;quot;..(mac or &amp;quot;&amp;quot;))&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local kam = APP:new(&amp;quot;kamailio/kamailio&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
-- A table of devices to notify to update&lt;br /&gt;
local devices = {}&lt;br /&gt;
&lt;br /&gt;
-- First, we check the registration numbers / passwords to 1) set a random password (if necessary) 2) make sure password matches any other registrations to same extension 3) push changes (add / delete / or update) to Kam database&lt;br /&gt;
-- Because this script also handles when device classes change, we have to consider that extensions are added / removed&lt;br /&gt;
local regs = {}&lt;br /&gt;
local forwarding = {&amp;quot;forwardnoanswerenable&amp;quot;, &amp;quot;forwardnoanswer&amp;quot;, &amp;quot;forwardbusyenable&amp;quot;, &amp;quot;forwardbusy&amp;quot;, &amp;quot;forwardallenable&amp;quot;, &amp;quot;forwardall&amp;quot;}&lt;br /&gt;
local forwardsettings = {} -- Collect forwarding settings&lt;br /&gt;
local passwords = {} -- Collect old extension/password pairs&lt;br /&gt;
for name,val in pairs(params.value) do&lt;br /&gt;
	if not regs[name] and string.match(name, &amp;quot;^reg&amp;quot;) then&lt;br /&gt;
		regs[name] = true&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
oldparams = oldparams or {value={}}&lt;br /&gt;
for name,val in pairs(oldparams.value) do&lt;br /&gt;
	if string.match(name, &amp;quot;^reg&amp;quot;) then&lt;br /&gt;
		if val.value and val.value.extension and val.value.password then&lt;br /&gt;
			passwords[val.value.extension.value] = val.value.password.value&lt;br /&gt;
		end&lt;br /&gt;
		if val.value and val.value.extension then&lt;br /&gt;
			local fwd = {}&lt;br /&gt;
			for i,f in ipairs(forwarding) do&lt;br /&gt;
				if val.value[f] then&lt;br /&gt;
					fwd[f] = val.value[f].value&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			forwardsettings[val.value.extension.value] = fwd&lt;br /&gt;
		end&lt;br /&gt;
		if not regs[name] then&lt;br /&gt;
			regs[name] = true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
end&lt;br /&gt;
local extension_id = params.value.reg1.value.extension.param_id&lt;br /&gt;
local password_id = params.value.reg1.value.password.param_id&lt;br /&gt;
functions.runsqlcommand(&amp;quot;BEGIN TRANSACTION&amp;quot;)&lt;br /&gt;
for name,val in pairs(regs) do&lt;br /&gt;
	local new = &amp;quot;&amp;quot;&lt;br /&gt;
	local old = &amp;quot;&amp;quot;&lt;br /&gt;
	if params.value[name] then new = params.value[name].value.extension.value end&lt;br /&gt;
	if oldparams.value[name] then old = oldparams.value[name].value.extension.value end&lt;br /&gt;
	if new ~= old then&lt;br /&gt;
		-- Extension changed&lt;br /&gt;
		-- First, let&#039;s remove the stuff for the old extension&lt;br /&gt;
		if old ~= &amp;quot;&amp;quot; then&lt;br /&gt;
			local others = functions.getselectresponse(&amp;quot;SELECT count(*) FROM provisioning_values WHERE param_id=&#039;&amp;quot;..extension_id..&amp;quot;&#039; AND value=&#039;&amp;quot;..old..&amp;quot;&#039;&amp;quot;)&lt;br /&gt;
			if others[1].count == &amp;quot;0&amp;quot; then&lt;br /&gt;
				APP.logevent(&amp;quot;Removing old registration &amp;quot;..old)&lt;br /&gt;
				-- remove the registration&lt;br /&gt;
				kam.model.delete_user(old)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- Now, add the new extension&lt;br /&gt;
		if new ~= &amp;quot;&amp;quot; then&lt;br /&gt;
			local pass&lt;br /&gt;
			if params.value[name].value.password.value ~= &amp;quot;&amp;quot; and (not oldparams.value[name] or (oldparams.value[name].value.password.value ~= params.value[name].value.password.value)) then&lt;br /&gt;
				-- The password parameter was changed and not blank, so use it&lt;br /&gt;
				pass = params.value[name].value.password.value&lt;br /&gt;
				APP.logevent(&amp;quot;Added a new registration &amp;quot;..new..&amp;quot; with specified password &amp;quot;..pass)&lt;br /&gt;
				-- There may be other devices with this extension&lt;br /&gt;
				local others = functions.getselectresponse(&amp;quot;SELECT value FROM provisioning_values WHERE param_id=(SELECT param_id FROM provisioning_params WHERE name=&#039;mac&#039;) AND device_id IN (SELECT device_id FROM provisioning_values WHERE param_id=&#039;&amp;quot;..extension_id..&amp;quot;&#039; AND value=&#039;&amp;quot;..new..&amp;quot;&#039;)&amp;quot;)&lt;br /&gt;
				for i,o in ipairs(others) do&lt;br /&gt;
					-- We&#039;ll notify whether it changed or not&lt;br /&gt;
					devices[o.value] = new&lt;br /&gt;
				end&lt;br /&gt;
			elseif passwords[new] then&lt;br /&gt;
				pass = passwords[new]&lt;br /&gt;
				APP.logevent(&amp;quot;Added a new registration &amp;quot;..new..&amp;quot; with reused password &amp;quot;..pass)&lt;br /&gt;
			else&lt;br /&gt;
				local others = functions.getselectresponse(&amp;quot;SELECT * FROM provisioning_values WHERE param_id=&#039;&amp;quot;..extension_id..&amp;quot;&#039; AND value=&#039;&amp;quot;..new..&amp;quot;&#039; AND device_id!=&#039;&amp;quot;..params.value.device_id.value..&amp;quot;&#039;&amp;quot;)&lt;br /&gt;
				-- If this is a new registration, use a new password&lt;br /&gt;
				if #others == 0 then&lt;br /&gt;
					pass = generatepw()&lt;br /&gt;
					APP.logevent(&amp;quot;Added a new registration &amp;quot;..new..&amp;quot; with random password &amp;quot;..pass)&lt;br /&gt;
				else&lt;br /&gt;
					-- Use the old password&lt;br /&gt;
					local p = functions.getselectresponse(&amp;quot;SELECT value FROM provisioning_values WHERE param_id=&#039;&amp;quot;..password_id..&amp;quot;&#039; AND device_id=&#039;&amp;quot;..others[1].device_id..&amp;quot;&#039; AND group_name=&#039;&amp;quot;..others[1].group_name..&amp;quot;&#039; LIMIT 1&amp;quot;)&lt;br /&gt;
					pass = p[1].value&lt;br /&gt;
					APP.logevent(&amp;quot;Added a new registration &amp;quot;..new..&amp;quot; with reused password &amp;quot;..pass)&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			passwords[new] = pass&lt;br /&gt;
			params.value[name].value.password.value = pass&lt;br /&gt;
			functions.runsqlcommand(&amp;quot;DELETE FROM provisioning_values WHERE param_id=&#039;&amp;quot;..password_id..&amp;quot;&#039; AND (device_id, group_name) IN (SELECT device_id, group_name FROM provisioning_values WHERE param_id=&#039;&amp;quot;..extension_id..&amp;quot;&#039; AND value=&#039;&amp;quot;..new..&amp;quot;&#039;)&amp;quot;)&lt;br /&gt;
			functions.runsqlcommand(&amp;quot;INSERT INTO provisioning_values (SELECT device_id, group_name, &#039;&amp;quot;..password_id..&amp;quot;&#039;, &#039;&amp;quot;..pass..&amp;quot;&#039; FROM provisioning_values WHERE param_id=&#039;&amp;quot;..extension_id..&amp;quot;&#039; AND value=&#039;&amp;quot;..new..&amp;quot;&#039;)&amp;quot;)&lt;br /&gt;
			local u = kam.model.get_user(new)&lt;br /&gt;
			if u.value.username.errtxt then&lt;br /&gt;
				-- Add this registration to Kam&lt;br /&gt;
				local u = kam.model.get_new_user()&lt;br /&gt;
				u.value.username.value = new&lt;br /&gt;
				u.value.password.value = pass&lt;br /&gt;
				u.value.password_confirm.value = pass&lt;br /&gt;
				kam.model.create_new_user(u)&lt;br /&gt;
			else&lt;br /&gt;
				u.value.password.value = pass&lt;br /&gt;
				u.value.password_confirm.value = pass&lt;br /&gt;
				kam.model.update_user(u)&lt;br /&gt;
			end&lt;br /&gt;
			-- Let&#039;s also look at the forwarding settings&lt;br /&gt;
			local change = false&lt;br /&gt;
			local supported = false&lt;br /&gt;
			local fwd = {}&lt;br /&gt;
			for i,f in ipairs(forwarding) do&lt;br /&gt;
				if params.value[name].value[f] then&lt;br /&gt;
					supported = true&lt;br /&gt;
					fwd[f] = params.value[name].value[f].value&lt;br /&gt;
					if ((not oldparams.value[name] or not oldparams.value[name].value[f]) and (params.value[name].value[f].value ~= params.value[name].value[f].default)) or&lt;br /&gt;
						(oldparams.value[name] and oldparams.value[name].value[f] and (params.value[name].value[f].value ~= oldparams.value[name].value[f].value)) then&lt;br /&gt;
						change = true&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
				if change then&lt;br /&gt;
					local others = functions.getselectresponse(&amp;quot;SELECT value FROM provisioning_values WHERE param_id=(SELECT param_id FROM provisioning_params WHERE name=&#039;mac&#039;) AND device_id IN (SELECT device_id FROM provisioning_values WHERE param_id=&#039;&amp;quot;..extension_id..&amp;quot;&#039; AND value=&#039;&amp;quot;..new..&amp;quot;&#039;)&amp;quot;)&lt;br /&gt;
					for i,o in ipairs(others) do&lt;br /&gt;
						-- We&#039;ll notify whether it changed or not&lt;br /&gt;
						devices[o.value] = new&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			if supported then&lt;br /&gt;
				if not change and forwardsettings[new] then&lt;br /&gt;
					fwd = forwardsettings[new]&lt;br /&gt;
				elseif not change then&lt;br /&gt;
					-- This is a new extension, and the forwarding has not been set. We should check to see if there are any other devices with this extension&lt;br /&gt;
					local others = functions.getselectresponse(&amp;quot;SELECT * FROM provisioning_values WHERE param_id=&#039;&amp;quot;..extension_id..&amp;quot;&#039; AND value=&#039;&amp;quot;..new..&amp;quot;&#039; AND device_id!=&#039;&amp;quot;..params.value.device_id.value..&amp;quot;&#039;&amp;quot;)&lt;br /&gt;
					if #others &amp;gt; 0 then&lt;br /&gt;
						-- Use the existing settings&lt;br /&gt;
						for i,f in ipairs(forwarding) do&lt;br /&gt;
							local v = functions.getselectresponse(&amp;quot;SELECT value FROM provisioning_values WHERE param_id=&#039;&amp;quot;..params.value[name].value[f].param_id..&amp;quot;&#039; AND device_id=&#039;&amp;quot;..others[1].device_id..&amp;quot;&#039; AND group_name=&#039;&amp;quot;..others[1].group_name..&amp;quot;&#039; LIMIT 1&amp;quot;)&lt;br /&gt;
							if #v &amp;gt; 0 then&lt;br /&gt;
								fwd[f] = v[1].value&lt;br /&gt;
							else&lt;br /&gt;
								fwd[f] = params.value[name].value[f].default&lt;br /&gt;
							end&lt;br /&gt;
						end&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
				if not change then&lt;br /&gt;
					for i,f in ipairs(forwarding) do&lt;br /&gt;
						params.value[name].value[f].value = fwd[f]&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
				forwardsettings[new] = fwd&lt;br /&gt;
				for i,f in ipairs(forwarding) do&lt;br /&gt;
					functions.runsqlcommand(&amp;quot;DELETE FROM provisioning_values WHERE param_id=&#039;&amp;quot;..params.value[name].value[f].param_id..&amp;quot;&#039; AND (device_id, group_name) IN (SELECT device_id, group_name FROM provisioning_values WHERE param_id=&#039;&amp;quot;..extension_id..&amp;quot;&#039; AND value=&#039;&amp;quot;..new..&amp;quot;&#039;)&amp;quot;)&lt;br /&gt;
					if fwd[f] ~= params.value[name].value[f].default then&lt;br /&gt;
						functions.runsqlcommand(&amp;quot;INSERT INTO provisioning_values (SELECT device_id, group_name, &#039;&amp;quot;..params.value[name].value[f].param_id..&amp;quot;&#039;, &#039;&amp;quot;..tostring(fwd[f])..&amp;quot;&#039; FROM provisioning_values WHERE param_id=&#039;&amp;quot;..extension_id..&amp;quot;&#039; AND value=&#039;&amp;quot;..new..&amp;quot;&#039;)&amp;quot;)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	elseif params.value[name] and oldparams.value[name] and params.value[name].value.extension.value ~= &amp;quot;&amp;quot; then&lt;br /&gt;
		if params.value[name].value.password.value ~= oldparams.value[name].value.password.value then&lt;br /&gt;
			-- Password changed - make any other registrations to this extension also change&lt;br /&gt;
			local pass = params.value[name].value.password.value&lt;br /&gt;
			if pass and pass ~= &amp;quot;&amp;quot; then&lt;br /&gt;
				APP.logevent(&amp;quot;Password changed for &amp;quot;..new..&amp;quot; from &amp;quot;..oldparams.value[name].value.password.value..&amp;quot; to &amp;quot;..pass)&lt;br /&gt;
			else&lt;br /&gt;
				pass = generatepw()&lt;br /&gt;
				APP.logevent(&amp;quot;Password cleared for &amp;quot;..new..&amp;quot;, so set new random password &amp;quot;..pass)&lt;br /&gt;
			end&lt;br /&gt;
			passwords[new] = pass&lt;br /&gt;
			functions.runsqlcommand(&amp;quot;DELETE FROM provisioning_values WHERE param_id=&#039;&amp;quot;..password_id..&amp;quot;&#039; AND (device_id, group_name) IN (SELECT device_id, group_name FROM provisioning_values WHERE param_id=&#039;&amp;quot;..extension_id..&amp;quot;&#039; AND value=&#039;&amp;quot;..new..&amp;quot;&#039;)&amp;quot;)&lt;br /&gt;
			functions.runsqlcommand(&amp;quot;INSERT INTO provisioning_values (SELECT device_id, group_name, &#039;&amp;quot;..password_id..&amp;quot;&#039;, &#039;&amp;quot;..pass..&amp;quot;&#039; FROM provisioning_values WHERE param_id=&#039;&amp;quot;..extension_id..&amp;quot;&#039; AND value=&#039;&amp;quot;..new..&amp;quot;&#039;)&amp;quot;)&lt;br /&gt;
			local u = kam.model.get_user(new)&lt;br /&gt;
			u.value.password.value = params.value[name].value.password.value&lt;br /&gt;
			u.value.password_confirm.value = params.value[name].value.password.value&lt;br /&gt;
			kam.model.update_user(u)&lt;br /&gt;
			-- Have to notify those other devices too&lt;br /&gt;
			local others = functions.getselectresponse(&amp;quot;SELECT value FROM provisioning_values WHERE param_id=(SELECT param_id FROM provisioning_params WHERE name=&#039;mac&#039;) AND device_id IN (SELECT device_id FROM provisioning_values WHERE param_id=&#039;&amp;quot;..extension_id..&amp;quot;&#039; AND value=&#039;&amp;quot;..new..&amp;quot;&#039;)&amp;quot;)&lt;br /&gt;
			for i,o in ipairs(others) do&lt;br /&gt;
				devices[o.value] = new&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		local change = false&lt;br /&gt;
		local fwd = {}&lt;br /&gt;
		for i,f in ipairs(forwarding) do&lt;br /&gt;
			if params.value[name].value[f] and (not oldparams.value[name].value[f] or params.value[name].value[f].value ~= oldparams.value[name].value[f].value) then&lt;br /&gt;
				change = true&lt;br /&gt;
				break&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		if change then&lt;br /&gt;
			-- Forwarding settings changed - make any other registrations to this extension also change&lt;br /&gt;
			for i,f in ipairs(forwarding) do&lt;br /&gt;
				functions.runsqlcommand(&amp;quot;DELETE FROM provisioning_values WHERE param_id=&#039;&amp;quot;..params.value[name].value[f].param_id..&amp;quot;&#039; AND (device_id, group_name) IN (SELECT device_id, group_name FROM provisioning_values WHERE param_id=&#039;&amp;quot;..extension_id..&amp;quot;&#039; AND value=&#039;&amp;quot;..new..&amp;quot;&#039;)&amp;quot;)&lt;br /&gt;
				if params.value[name].value[f].value ~= params.value[name].value[f].default then&lt;br /&gt;
					functions.runsqlcommand(&amp;quot;INSERT INTO provisioning_values (SELECT device_id, group_name, &#039;&amp;quot;..params.value[name].value[f].param_id..&amp;quot;&#039;, &#039;&amp;quot;..tostring(params.value[name].value[f].value)..&amp;quot;&#039; FROM provisioning_values WHERE param_id=&#039;&amp;quot;..extension_id..&amp;quot;&#039; AND value=&#039;&amp;quot;..new..&amp;quot;&#039;)&amp;quot;)&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			-- Have to notify those other devices too&lt;br /&gt;
			local others = functions.getselectresponse(&amp;quot;SELECT value FROM provisioning_values WHERE param_id=(SELECT param_id FROM provisioning_params WHERE name=&#039;mac&#039;) AND device_id IN (SELECT device_id FROM provisioning_values WHERE param_id=&#039;&amp;quot;..extension_id..&amp;quot;&#039; AND value=&#039;&amp;quot;..new..&amp;quot;&#039;)&amp;quot;)&lt;br /&gt;
			for i,o in ipairs(others) do&lt;br /&gt;
				devices[o.value] = new&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
functions.runsqlcommand(&amp;quot;COMMIT&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
-- If reg1 or freepstn changed, then need to change free-pstn in kam&lt;br /&gt;
-- Since there can be multiple devices with the same reg1, we can&#039;t rely on params and oldparams&lt;br /&gt;
-- Check both reg&#039;s&lt;br /&gt;
--APP.logevent(session.serialize(&amp;quot;params&amp;quot;, params))&lt;br /&gt;
local reg = {}&lt;br /&gt;
if oldparams.value.reg1 and oldparams.value.reg1.value.extension and oldparams.value.reg1.value.extension.value ~= &amp;quot;&amp;quot; then&lt;br /&gt;
	reg[oldparams.value.reg1.value.extension.value] = false&lt;br /&gt;
end&lt;br /&gt;
if params.value.reg1 and params.value.reg1.value.extension and params.value.reg1.value.extension.value ~= &amp;quot;&amp;quot; then&lt;br /&gt;
	local pstn = false&lt;br /&gt;
	if params.value.routing and params.value.routing.value.freepstn then&lt;br /&gt;
		pstn = params.value.routing.value.freepstn.value&lt;br /&gt;
	end&lt;br /&gt;
	reg[params.value.reg1.value.extension.value] = pstn&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
APP.logevent(&amp;quot;Looking at free-pstn&amp;quot;)&lt;br /&gt;
for r,f in pairs(reg) do&lt;br /&gt;
	-- if we&#039;re not sure, check provisioning database&lt;br /&gt;
	if not f then&lt;br /&gt;
		-- Most devices will have free-pstn due to class-of-service, but can be overridden, so this can get tricky&lt;br /&gt;
		-- Check all devices where reg1 extension = r, and see what the freepstn value is for each&lt;br /&gt;
		local others = functions.getselectresponse(&amp;quot;SELECT CASE WHEN v.value IS NOT NULL THEN v.value WHEN g2p.value IS NOT NULL THEN g2p.value ELSE p.value END AS value &amp;quot;..&lt;br /&gt;
			&amp;quot;FROM (devices_to_classes d2t JOIN provisioning_classes t USING(class_id) JOIN classes_to_param_groups t2g USING (class_id) JOIN provisioning_groups g USING(group_id) &amp;quot;..&lt;br /&gt;
			&amp;quot;JOIN param_groups_to_params g2p USING(group_id) JOIN provisioning_params p USING(param_id)) LEFT JOIN provisioning_values v ON (d2t.device_id=v.device_id AND p.param_id=v.param_id AND g.name=v.group_name ) &amp;quot;..&lt;br /&gt;
			&amp;quot;WHERE p.name=&#039;freepstn&#039; AND d2t.device_id IN (SELECT device_id FROM provisioning_values WHERE param_id=&#039;&amp;quot;..extension_id..&amp;quot;&#039; AND group_name=&#039;reg1&#039; AND value=&#039;&amp;quot;..r..&amp;quot;&#039;)&amp;quot;)&lt;br /&gt;
		for i,o in ipairs(others) do&lt;br /&gt;
			-- Now check the freepstn value for each one&lt;br /&gt;
			if o.value == &amp;quot;true&amp;quot; then&lt;br /&gt;
				f = true&lt;br /&gt;
				break&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	-- Now, check the Kamailio group table&lt;br /&gt;
	local alreadythere = false&lt;br /&gt;
	local entries = kam.model.list_table_entries(&amp;quot;grp&amp;quot;)&lt;br /&gt;
	for i,e in ipairs(entries.value.entries.value) do&lt;br /&gt;
		if e.username == r then&lt;br /&gt;
			alreadythere = true&lt;br /&gt;
			if not f then&lt;br /&gt;
				APP.logevent(&amp;quot;Removing free-pstn for &amp;quot;..r)&lt;br /&gt;
				-- Remove free-pstn from the old extension&lt;br /&gt;
				kam.model.delete_table_entry(&amp;quot;grp&amp;quot;, e.id)&lt;br /&gt;
			end&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if f and not alreadythere then&lt;br /&gt;
		APP.logevent(&amp;quot;Adding free-pstn for &amp;quot;..r)&lt;br /&gt;
		-- Add free-pstn to the new extension&lt;br /&gt;
		local e = kam.model.get_table_entry(&amp;quot;grp&amp;quot;)&lt;br /&gt;
		e.value.username.value = r&lt;br /&gt;
		e.value.domain.value = &amp;quot;&amp;quot;&lt;br /&gt;
		e.value.grp.value = &amp;quot;free-pstn&amp;quot;&lt;br /&gt;
		e.value.last_modified.value = os.date(&amp;quot;%c&amp;quot;)&lt;br /&gt;
		kam.model.create_table_entry(e)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
kam:destroy()&lt;br /&gt;
&lt;br /&gt;
-- If the mac address changed for Polycom with valid MAC (not blank or all 0&#039;s), we need to move the associated config files&lt;br /&gt;
if oldparams.value.device and oldparams.value.device.value.mac and oldparams.value.device.value.mac.value ~= &amp;quot;&amp;quot; and params.value.device and params.value.device.value.mac and params.value.device.value.mac.value ~= oldparams.value.device.value.mac.value then&lt;br /&gt;
	if string.match(oldparams.value.device.label, &amp;quot;Polycom&amp;quot;) and string.match(oldparams.value.device.value.mac.value, &amp;quot;[1-9A-F]&amp;quot;) then&lt;br /&gt;
		local deletefiles = true&lt;br /&gt;
		if string.match(params.value.device.value.mac.value, &amp;quot;[1-9A-F]&amp;quot;) then&lt;br /&gt;
			--APP.logevent(&amp;quot;Moving files for &amp;quot;..oldparams.value.device.value.mac.value)&lt;br /&gt;
			deletefiles = false&lt;br /&gt;
		else&lt;br /&gt;
			--APP.logevent(&amp;quot;Deleting files for &amp;quot;..oldparams.value.device.value.mac.value)&lt;br /&gt;
		end&lt;br /&gt;
		local path = root..&amp;quot;Polycom/&amp;quot;&lt;br /&gt;
		if posix.stat(path, &amp;quot;type&amp;quot;) == &amp;quot;directory&amp;quot; then&lt;br /&gt;
	                for d in posix.files(path) do&lt;br /&gt;
        	                if string.match(d, string.lower(oldparams.value.device.value.mac.value)) and posix.stat(path..d, &amp;quot;type&amp;quot;) == &amp;quot;regular&amp;quot; then&lt;br /&gt;
                	                local newfile = string.gsub(d, string.lower(oldparams.value.device.value.mac.value), string.lower(params.value.device.value.mac.value))&lt;br /&gt;
					if deletefiles then&lt;br /&gt;
	                        	        --APP.logevent(&amp;quot;deleting &amp;quot;..path..d)&lt;br /&gt;
        	                        	os.remove(path..d)&lt;br /&gt;
					else&lt;br /&gt;
	                        	        --APP.logevent(&amp;quot;moving &amp;quot;..path..d..&amp;quot; to &amp;quot;..path..newfile)&lt;br /&gt;
        	                        	os.rename(path..d, path..newfile)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
                        end&lt;br /&gt;
                end&lt;br /&gt;
        end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Then, notify the phone to pull it&#039;s config&lt;br /&gt;
-- Try to get a valid extension currently on the device&lt;br /&gt;
local oldexten = &amp;quot;&amp;quot;&lt;br /&gt;
for name,val in pairs(oldparams.value) do&lt;br /&gt;
	if string.match(name, &amp;quot;^reg&amp;quot;) and val.value.extension and val.value.extension.value ~= &amp;quot;&amp;quot; then&lt;br /&gt;
		oldexten = val.value.extension.value&lt;br /&gt;
		break&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
if params.value.device and params.value.device.value.mac and params.value.device.value.mac.value ~= &amp;quot;&amp;quot; then&lt;br /&gt;
	devices[params.value.device.value.mac.value] = oldexten&lt;br /&gt;
end&lt;br /&gt;
if oldparams.value.device and oldparams.value.device.value.mac and oldparams.value.device.value.mac.value ~= &amp;quot;&amp;quot; then&lt;br /&gt;
	devices[oldparams.value.device.value.mac.value] = oldexten&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
for name,value in pairs(devices) do&lt;br /&gt;
	notify_device(name,value)&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
* Create /etc/provisioning/provisioning_db_script&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/bin/lua&lt;br /&gt;
&lt;br /&gt;
local path = &amp;quot;PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin &amp;quot;&lt;br /&gt;
local creation_script = {&lt;br /&gt;
	-- Parameters&lt;br /&gt;
	&amp;quot;INSERT INTO provisioning_params VALUES(default, &#039;freepstn&#039;, &#039;boolean&#039;, &#039;Free PSTN Access&#039;, &#039;&#039;, &#039;false&#039;, &#039;200&#039;, &#039;&#039;)&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
	-- Parameter Groups&lt;br /&gt;
	&amp;quot;INSERT INTO provisioning_groups VALUES(default, &#039;routing&#039;, &#039;Free PSTN Access&#039;, &#039;31&#039;)&amp;quot;,&lt;br /&gt;
	&amp;quot;INSERT INTO provisioning_groups VALUES(default, &#039;routing&#039;, &#039;No Free PSTN Access&#039;, &#039;32&#039;)&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
	-- param_groups_to_params (group_id, param_id, value, editable)&lt;br /&gt;
	&amp;quot;INSERT INTO param_groups_to_params VALUES((SELECT group_id FROM provisioning_groups WHERE label=&#039;Free PSTN Access&#039;), (SELECT param_id FROM provisioning_params WHERE name=&#039;freepstn&#039;), &#039;true&#039;, false)&amp;quot;,&lt;br /&gt;
	&amp;quot;INSERT INTO param_groups_to_params VALUES((SELECT group_id FROM provisioning_groups WHERE label=&#039;No Free PSTN Access&#039;), (SELECT param_id FROM provisioning_params WHERE name=&#039;freepstn&#039;), &#039;false&#039;, false)&amp;quot;,&lt;br /&gt;
	&lt;br /&gt;
	-- Classes&lt;br /&gt;
	-- provisioning_class_groups (class_group_id, name, label, seq)&lt;br /&gt;
	&amp;quot;INSERT INTO provisioning_class_groups VALUES(default, &#039;routing&#039;, &#039;Routing&#039;, &#039;3&#039;)&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
	-- provisioning_classes (class_id, class_group_id, label, seq)&lt;br /&gt;
	&amp;quot;INSERT INTO provisioning_classes VALUES(default, (SELECT class_group_id FROM provisioning_class_groups WHERE name=&#039;routing&#039;), &#039;Free PSTN Access&#039;, &#039;1&#039;)&amp;quot;,&lt;br /&gt;
	&amp;quot;INSERT INTO provisioning_classes VALUES(default, (SELECT class_group_id FROM provisioning_class_groups WHERE name=&#039;routing&#039;), &#039;No Free PSTN Access&#039;, &#039;2&#039;)&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
	-- classes_to_param_groups (class_id, group_id)&lt;br /&gt;
	&amp;quot;INSERT INTO classes_to_param_groups VALUES((SELECT class_id FROM provisioning_classes WHERE label=&#039;Free PSTN Access&#039;), (SELECT group_id FROM provisioning_groups WHERE label=&#039;Free PSTN Access&#039;))&amp;quot;,&lt;br /&gt;
	&amp;quot;INSERT INTO classes_to_param_groups VALUES((SELECT class_id FROM provisioning_classes WHERE label=&#039;No Free PSTN Access&#039;), (SELECT group_id FROM provisioning_groups WHERE label=&#039;No Free PSTN Access&#039;))&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
	-- provisioning_options&lt;br /&gt;
	&amp;quot;INSERT INTO provisioning_options VALUES((SELECT param_id FROM provisioning_params WHERE name=&#039;polycomringtone&#039;), &#039;Warble&#039;, &#039;15&#039;, &#039;15&#039;)&amp;quot;,&lt;br /&gt;
	&amp;quot;INSERT INTO provisioning_options VALUES((SELECT param_id FROM provisioning_params WHERE name=&#039;polycomringtone&#039;), &#039;Analog Ring&#039;, &#039;16&#039;, &#039;16&#039;)&amp;quot;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local f = io.popen(&amp;quot;/usr/share/acf/www/cgi-bin/cli /provisioning/provisioning/getdevicevalues&amp;quot;)&lt;br /&gt;
print(f:read(&amp;quot;*a&amp;quot;))&lt;br /&gt;
f:close()&lt;br /&gt;
for i,c in ipairs(creation_script) do&lt;br /&gt;
	print(path..&#039;psql -U postgres -c &amp;quot;&#039;..c..&#039;&amp;quot; provisioning 2&amp;gt;&amp;amp;1&#039;)&lt;br /&gt;
	local f = io.popen(path..&#039;psql -U postgres -c &amp;quot;&#039;..c..&#039;&amp;quot; provisioning 2&amp;gt;&amp;amp;1&#039;)&lt;br /&gt;
	print(f:read(&amp;quot;*a&amp;quot;))&lt;br /&gt;
	f:close()&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
* chmod 755 /etc/provisioning/provisioning_db_script&lt;br /&gt;
* /etc/provisioning/provisioning_db_script&lt;br /&gt;
* echo &#039;*       *       *       *       *       run-parts /etc/periodic/1min&#039; &amp;gt;&amp;gt; /etc/crontabs/root&lt;br /&gt;
* mkdir /etc/periodic/1min/&lt;br /&gt;
* /etc/periodic/1min/notify_device&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/bin/lua&lt;br /&gt;
&lt;br /&gt;
-- Load libraries&lt;br /&gt;
require(&amp;quot;luasql.postgres&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
-- Set variables&lt;br /&gt;
local DatabaseName = &amp;quot;provisioning&amp;quot;&lt;br /&gt;
local DatabaseUser = &amp;quot;postgres&amp;quot;&lt;br /&gt;
local DatabasePassword&lt;br /&gt;
&lt;br /&gt;
local path = &amp;quot;PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin &amp;quot;&lt;br /&gt;
local env&lt;br /&gt;
local con&lt;br /&gt;
&lt;br /&gt;
-- ################################################################################&lt;br /&gt;
-- LOCAL FUNCTIONS&lt;br /&gt;
local function assert (v, m)&lt;br /&gt;
	if not v then&lt;br /&gt;
		m = m or &amp;quot;Assertion failed!&amp;quot;&lt;br /&gt;
		error(m, 0)&lt;br /&gt;
	end&lt;br /&gt;
	return v, m&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Escape special characters in sql statements&lt;br /&gt;
local escape = function(sql)&lt;br /&gt;
	sql = sql or &amp;quot;&amp;quot;&lt;br /&gt;
	sql = string.gsub(sql, &amp;quot;&#039;&amp;quot;, &amp;quot;&#039;&#039;&amp;quot;)&lt;br /&gt;
	return string.gsub(sql, &amp;quot;\\&amp;quot;, &amp;quot;\\\\&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local databaseconnect = function()&lt;br /&gt;
	if not con then&lt;br /&gt;
		-- create environment object&lt;br /&gt;
		env = assert (luasql.postgres())&lt;br /&gt;
		-- connect to data source&lt;br /&gt;
		local err&lt;br /&gt;
		con, err = assert(env:connect(DatabaseName, DatabaseUser, DatabasePassword))&lt;br /&gt;
		return true&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local databasedisconnect = function()&lt;br /&gt;
	if env then&lt;br /&gt;
		env:close()&lt;br /&gt;
		env = nil&lt;br /&gt;
	end&lt;br /&gt;
	if con then&lt;br /&gt;
		con:close()&lt;br /&gt;
		con = nil&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local getselectresponse = function(sql)&lt;br /&gt;
	local retval = {}&lt;br /&gt;
	local cur = assert (con:execute(sql))&lt;br /&gt;
	local row = cur:fetch ({}, &amp;quot;a&amp;quot;)&lt;br /&gt;
	while row do&lt;br /&gt;
		local tmp = {}&lt;br /&gt;
		for name,val in pairs(row) do&lt;br /&gt;
			tmp[name] = val&lt;br /&gt;
		end&lt;br /&gt;
		retval[#retval + 1] = tmp&lt;br /&gt;
		row = cur:fetch (row, &amp;quot;a&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	cur:close()&lt;br /&gt;
	return retval&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
databaseconnect()&lt;br /&gt;
&lt;br /&gt;
-- First, let&#039;s notify for the seasoned requests&lt;br /&gt;
local reqs = getselectresponse(&amp;quot;SELECT * FROM notify WHERE seasoned=&#039;true&#039;&amp;quot;)&lt;br /&gt;
for i,r in ipairs(reqs) do&lt;br /&gt;
	os.execute(&amp;quot;/etc/provisioning/notify_device &amp;quot;..r.ipaddr..&amp;quot; &amp;quot;..r.extension)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Then mark any others as seasoned&lt;br /&gt;
assert(con:execute(&amp;quot;DELETE FROM notify WHERE seasoned=&#039;true&#039;&amp;quot;))&lt;br /&gt;
assert(con:execute(&amp;quot;UPDATE notify SET seasoned=&#039;true&#039; WHERE seasoned=&#039;false&#039;&amp;quot;))&lt;br /&gt;
&lt;br /&gt;
databasedisconnect()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
* apk add lighttpd&lt;br /&gt;
* rm /etc/lighttpd/lighttpd.conf&lt;br /&gt;
* ln -s /etc/provisioning/lighttpd.sample.conf /etc/lighttpd/lighttpd.conf&lt;br /&gt;
* /etc/lighttpd/mod_cgi.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
###############################################################################&lt;br /&gt;
# mod_cgi.conf&lt;br /&gt;
# include&#039;d by lighttpd.conf.&lt;br /&gt;
# $Header: /var/cvsroot/gentoo-x86/www-servers/lighttpd/files/conf/mod_cgi.conf,v 1.1 2005/08/27 12:36:13 ka0ttic Exp $&lt;br /&gt;
###############################################################################&lt;br /&gt;
&lt;br /&gt;
#&lt;br /&gt;
# see cgi.txt for more information on using mod_cgi&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
server.modules += (&amp;quot;mod_cgi&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# NOTE: this requires mod_alias&lt;br /&gt;
alias.url = (&lt;br /&gt;
     &amp;quot;/cgi-bin/&amp;quot;	    =&amp;gt;	    var.basedir + &amp;quot;/cgi-bin/&amp;quot;&lt;br /&gt;
)&lt;br /&gt;
&lt;br /&gt;
#&lt;br /&gt;
# Note that you&#039;ll also want to enable the&lt;br /&gt;
# cgi-bin alias via mod_alias (above).&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
$HTTP[&amp;quot;url&amp;quot;] =~ &amp;quot;^/cgi-bin/&amp;quot; {&lt;br /&gt;
    # disable directory listings&lt;br /&gt;
    dir-listing.activate = &amp;quot;disable&amp;quot;&lt;br /&gt;
    # only allow cgi&#039;s in this directory&lt;br /&gt;
    cgi.assign = (&lt;br /&gt;
		&amp;quot;.pl&amp;quot;	=&amp;gt;	&amp;quot;/usr/bin/perl&amp;quot;,&lt;br /&gt;
		&amp;quot;.cgi&amp;quot;	=&amp;gt;	&amp;quot;&amp;quot;,&lt;br /&gt;
		&amp;quot;&amp;quot;	=&amp;gt;	&amp;quot;&amp;quot;&lt;br /&gt;
	)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# vim: set ft=conf foldmethod=marker et :&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
* /etc/init.d/lighttpd start&lt;br /&gt;
* rc-update add lighttpd&lt;br /&gt;
&lt;br /&gt;
* Customize provisioning:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Parameter	Default Value&lt;br /&gt;
registrar	 IP address or host name of SIP Router(s)&lt;br /&gt;
digitmap	 Digit map for your phone system (See Polycom Digit Map Reference)&lt;br /&gt;
digitmaptimeout	 Timeout in seconds corresponding to digitmap&lt;br /&gt;
sntpserver	 10.2.0.1 (DMVPN spoke node)&lt;br /&gt;
timezone	 Timezone information for this location - see below&lt;br /&gt;
musiconhold	 SIP uri of the music-on-hold service - moh@media.office.example.net&lt;br /&gt;
adminpassword	 Administration password for advanced settings on phone (on-screen and web interface)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
* apk fetch --stdout acf-provisioning-polycom | tar -C / -zx&lt;br /&gt;
* Create a test Polycom device and boot it in the voice network to verify that it works&lt;br /&gt;
&lt;br /&gt;
== Install Kamailio ==&lt;br /&gt;
* apk add kamailio kamailio-presence kamailio-pcre kamailio-postgres&lt;br /&gt;
* /etc/init.d/kamailio start&lt;br /&gt;
* rc-update add kamailio&lt;br /&gt;
&lt;br /&gt;
* FUTURE: POST CONFIG THAT USES SUBSCRIBER TABLE FOR AUTH&lt;br /&gt;
&lt;br /&gt;
= Install the SIP Media container =&lt;br /&gt;
&lt;br /&gt;
== Create and Configure the container ==&lt;br /&gt;
{{Cmd|lxc-create -n sipmedia -f /etc/lxc/default.conf -t alpine}}&lt;br /&gt;
Create the startup Script&lt;br /&gt;
{{Cmd|ln -s /etc/init.d/lxc /etc/init.d/lxc.sipmedia}}&lt;br /&gt;
&lt;br /&gt;
Edit the container&#039;s config file found at /var/lib/lxc/sipmedia/config, to reflect the network for the SIP Media container&lt;br /&gt;
&lt;br /&gt;
{{cat|/var/lib/lxc/sipmedia/config|&lt;br /&gt;
...&lt;br /&gt;
lxc.network.link {{=}} bond0.1101&lt;br /&gt;
...&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Start the container&lt;br /&gt;
{{Cmd|/etc/init.d/lxc.sipmedia}}&lt;br /&gt;
&lt;br /&gt;
Configure the container to automatically start&lt;br /&gt;
{{Cmd|rc-update add lxc.sipmedia}}&lt;br /&gt;
&lt;br /&gt;
== Enter the SIP Media container ==&lt;br /&gt;
{{Cmd|lxc-console -n sipmedia}}&lt;br /&gt;
Login as root&lt;br /&gt;
{{Note|If the need arises to exit the container press {{Key| Ctrl}}+{{Key| a}} + {{Key| q}}}}&lt;br /&gt;
Remove obsolete /etc/network/interfaces&lt;br /&gt;
{{Cmd|rm /etc/network/interfaces}}&lt;br /&gt;
Create and configure the new /etc/network/interfaces as shown below:&lt;br /&gt;
{{cat|/etc/network/interfaces|&lt;br /&gt;
auto lo&lt;br /&gt;
iface lo inet loopback&lt;br /&gt;
&lt;br /&gt;
auto eth0&lt;br /&gt;
iface eth0 inet static&lt;br /&gt;
	address 10.2.0.5&lt;br /&gt;
	netmask 255.255.255.0&lt;br /&gt;
	gateway 10.2.0.1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Startup networking &lt;br /&gt;
{{Cmd| /etc/init.d/networking start}}&lt;br /&gt;
&lt;br /&gt;
Configure and enable proxy settings&lt;br /&gt;
{{Cmd|setup-proxy http://10.1.0.2:8080&lt;br /&gt;
. /etc/profile.d/proxy.sh}}&lt;br /&gt;
&lt;br /&gt;
Configure remote administration&lt;br /&gt;
{{Cmd|apk update&lt;br /&gt;
setup-sshd -c openssh&lt;br /&gt;
sed -i &amp;quot;s/.PasswordAuthentication yes/PasswordAuthentication no/&amp;quot; /etc/ssh/sshd_config&lt;br /&gt;
sed -i &amp;quot;s/.UseDNS yes/UseDNS no/&amp;quot; /etc/ssh/sshd_config}}&lt;br /&gt;
&lt;br /&gt;
Start ssh&lt;br /&gt;
{{Cmd|/etc/init.d/sshd start}}&lt;br /&gt;
&lt;br /&gt;
Configure a passwd for the container&lt;br /&gt;
{{Cmd|passwd}}&lt;br /&gt;
&lt;br /&gt;
Setup acf for web administration&lt;br /&gt;
{{Cmd|setup-acf}}&lt;br /&gt;
&lt;br /&gt;
== Setup Firewall ==&lt;br /&gt;
{{Cmd|apk add acf-awall}}&lt;br /&gt;
&lt;br /&gt;
With your favorite editor, create the policies for the firewall&lt;br /&gt;
{{cat|/etc/awall/optional/base.json|&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;description&amp;quot;: &amp;quot;Management&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
  &amp;quot;policy&amp;quot;: [&lt;br /&gt;
    { &amp;quot;in&amp;quot;: &amp;quot;_fw&amp;quot;, &amp;quot;action&amp;quot;: &amp;quot;accept&amp;quot; }&lt;br /&gt;
  ],&lt;br /&gt;
&lt;br /&gt;
  &amp;quot;filter&amp;quot;: [&lt;br /&gt;
    {&lt;br /&gt;
      &amp;quot;out&amp;quot;: &amp;quot;_fw&amp;quot;,&lt;br /&gt;
      &amp;quot;service&amp;quot;: [ &amp;quot;ssh&amp;quot;, &amp;quot;https&amp;quot;, &amp;quot;ping&amp;quot; ],&lt;br /&gt;
      &amp;quot;action&amp;quot;: &amp;quot;accept&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
  ]&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
{{cat|/etc/awall/optional/sip-track.json|&lt;br /&gt;
{&lt;br /&gt;
&lt;br /&gt;
	&amp;quot;description&amp;quot;: &amp;quot;Phone system with SIP connection tracking&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
	&amp;quot;filter&amp;quot;: [&lt;br /&gt;
		{&lt;br /&gt;
      &amp;quot;out&amp;quot;: &amp;quot;_fw&amp;quot;,&lt;br /&gt;
      &amp;quot;service&amp;quot;: [ &amp;quot;sip&amp;quot;, &amp;quot;sip-tls&amp;quot; ],&lt;br /&gt;
      &amp;quot;action&amp;quot;: &amp;quot;accept&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
	]&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
Enable and activate firewall policies, and configure iptables to start at boot&lt;br /&gt;
{{Cmd|awall enable base&lt;br /&gt;
awall enable sip-track&lt;br /&gt;
awall activate -f&lt;br /&gt;
rc-update add iptables&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Install and Configure Freeswitch ==&lt;br /&gt;
Install package&lt;br /&gt;
{{Cmd|Install Freeswitch Package}}&lt;br /&gt;
&lt;br /&gt;
Configure /etc/freeswitch/freeswitch.xml&lt;br /&gt;
{{cat|/etc/freeswitch/freeswitch.xml|&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
...&lt;br /&gt;
    &amp;lt;extension name=&amp;quot;hold_music&amp;quot;&amp;gt;                                                                                                                                                                              &lt;br /&gt;
      &amp;lt;condition field=&amp;quot;destination_number&amp;quot; expression=&amp;quot;^moh$&amp;quot;&amp;gt;                                                                                                                                                &lt;br /&gt;
        &amp;lt;action application=&amp;quot;answer&amp;quot;/&amp;gt;                                                                                                                                                                         &lt;br /&gt;
        &amp;lt;action application=&amp;quot;playback&amp;quot; data=&amp;quot;$${hold_music}&amp;quot;/&amp;gt;                                                                                                                                                 &lt;br /&gt;
      &amp;lt;/condition&amp;gt;                                                                                                                                                                                             &lt;br /&gt;
    &amp;lt;/extension&amp;gt;&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;X-PRE-PROCESS cmd=&amp;quot;set&amp;quot; data=&amp;quot;hold_music=local_stream://default&amp;quot;/&amp;gt;&lt;br /&gt;
...&lt;br /&gt;
FUTURE: ADD VOICEMAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
Start Freeswitch and configure to start at boot&lt;br /&gt;
{{Cmd|/etc/init.d/freeswitch start&lt;br /&gt;
rc-update add freeswitch}}&lt;br /&gt;
&lt;br /&gt;
= Install the WiFi Web Proxy Container =&lt;br /&gt;
&lt;br /&gt;
== Create and Configure the container ==&lt;br /&gt;
{{Cmd|lxc-create -n wifi -f /etc/lxc/default.conf -t alpine}}&lt;br /&gt;
Create the startup Script&lt;br /&gt;
{{Cmd|ln -s /etc/init.d/lxc /etc/init.d/lxc.wifi}}&lt;br /&gt;
&lt;br /&gt;
Edit the container&#039;s config file found at /var/lib/lxc/wifi/config, to reflect the network for the wifi container&lt;br /&gt;
&lt;br /&gt;
{{cat|/var/lib/lxc/wifi/config|&lt;br /&gt;
...&lt;br /&gt;
lxc.network.link {{=}} bond0.701&lt;br /&gt;
...&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Start the container&lt;br /&gt;
{{Cmd|/etc/iniit.d/lxc.wifi}}&lt;br /&gt;
&lt;br /&gt;
Configure the container to automatically start&lt;br /&gt;
{{Cmd|rc-update add lxc.wifi}}&lt;br /&gt;
&lt;br /&gt;
== Enter the wifi container ==&lt;br /&gt;
{{Cmd|lxc-console -n wifi}}&lt;br /&gt;
Login as root&lt;br /&gt;
{{Note|If the need arises to exit the container press {{Key| Ctrl}}+{{Key| a}} + {{Key| q}}}}&lt;br /&gt;
Remove obsolete /etc/network/interfaces&lt;br /&gt;
{{Cmd|rm /etc/network/interfaces}}&lt;br /&gt;
Create and configure the new /etc/network/interfaces as shown below:&lt;br /&gt;
{{cat|/etc/network/interfaces|&lt;br /&gt;
auto lo&lt;br /&gt;
iface lo inet loopback&lt;br /&gt;
&lt;br /&gt;
auto eth0&lt;br /&gt;
iface eth0 inet static&lt;br /&gt;
	address 172.17.48.1&lt;br /&gt;
	netmask 255.255.255.0&lt;br /&gt;
&lt;br /&gt;
auto eth1&lt;br /&gt;
iface eth1 inet static&lt;br /&gt;
	address 10.1.0.254&lt;br /&gt;
	netmask 255.255.255.252&lt;br /&gt;
	gateway 10.1.0.253&lt;br /&gt;
&lt;br /&gt;
auto eth2&lt;br /&gt;
iface eth2 inet static&lt;br /&gt;
	address 10.1.0.131&lt;br /&gt;
	netmask 255.255.255.192&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Startup networking &lt;br /&gt;
{{Cmd| /etc/init.d/networking start}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Configure remote administration&lt;br /&gt;
{{Cmd|apk update&lt;br /&gt;
setup-sshd -c openssh&lt;br /&gt;
sed -i &amp;quot;s/.PasswordAuthentication yes/PasswordAuthentication no/&amp;quot; /etc/ssh/sshd_config&lt;br /&gt;
sed -i &amp;quot;s/.UseDNS yes/UseDNS no/&amp;quot; /etc/ssh/sshd_config}}&lt;br /&gt;
&lt;br /&gt;
Start ssh&lt;br /&gt;
{{Cmd|/etc/init.d/sshd start}}&lt;br /&gt;
&lt;br /&gt;
Configure a passwd for the container&lt;br /&gt;
{{Cmd|passwd}}&lt;br /&gt;
&lt;br /&gt;
Setup acf for web administration&lt;br /&gt;
{{Cmd|setup-acf}}&lt;br /&gt;
&lt;br /&gt;
== Setup Firewall ==&lt;br /&gt;
{{Cmd|apk add acf-awall}}&lt;br /&gt;
{{Todo|Need to lock down firewall rules}}&lt;br /&gt;
&lt;br /&gt;
==Install and Configure the Recursive DNS Service ==&lt;br /&gt;
Install unbound package&lt;br /&gt;
{{Cmd|apk add unbound}}&lt;br /&gt;
With your favorite editor configure /etc/unbound/unbound.conf&lt;br /&gt;
{{cat|/etc/unbound/unobund.conf|&lt;br /&gt;
server:&lt;br /&gt;
        verbosity: 1&lt;br /&gt;
        interface: 172.17.48.1&lt;br /&gt;
        do-ip4: yes&lt;br /&gt;
        do-ip6: no&lt;br /&gt;
        do-udp: yes&lt;br /&gt;
        do-tcp: yes&lt;br /&gt;
        do-daemonize: yes&lt;br /&gt;
        access-control: 172.17.0.0/16 allow&lt;br /&gt;
        access-control: 127.0.0.0/8 allow&lt;br /&gt;
&lt;br /&gt;
do-not-query-localhost: no&lt;br /&gt;
&lt;br /&gt;
root-hints: &amp;quot;/etc/unbound/root.hints&amp;quot;&lt;br /&gt;
&lt;br /&gt;
python:&lt;br /&gt;
remote-control:&lt;br /&gt;
        control-enable: no&lt;br /&gt;
}}&lt;br /&gt;
== Install and Configure the Proxy service ==&lt;br /&gt;
Install the necessary packages&lt;br /&gt;
{{Cmd|apk add squid squark lighttpd}}&lt;br /&gt;
With your preferred editor configure /etc/squid/squid.conf&lt;br /&gt;
{{cat|/etc/squid/squid.conf|&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#Squid config &lt;br /&gt;
&lt;br /&gt;
# This port listens for client requests&lt;br /&gt;
http_port 172.17.48.1:8080 transparent&lt;br /&gt;
http_port 127.0.0.1:8081&lt;br /&gt;
&lt;br /&gt;
visible_hostname wifi.local&lt;br /&gt;
cache_mem 8 MB&lt;br /&gt;
# If you don&#039;t have an HD installed comment the &amp;quot;cache_dir&amp;quot; line below&lt;br /&gt;
cache_dir aufs /var/cache/squid 900 16 256&lt;br /&gt;
&lt;br /&gt;
# Even though we only use one proxy, this line is recommended&lt;br /&gt;
# More info: http://www.squid-cache.org/Versions/v2/2.7/cfgman/hierarchy_stoplist.html&lt;br /&gt;
hierarchy_stoplist cgi-bin ?&lt;br /&gt;
&lt;br /&gt;
# Keep 7 days of access logs&lt;br /&gt;
logfile_rotate 7&lt;br /&gt;
&lt;br /&gt;
logformat squark %ts.%03tu %6tr %&amp;gt;a %Ss/%03&amp;gt;Hs %&amp;lt;st %rm %ru %un %Sh/%&amp;lt;A %mt %rG&lt;br /&gt;
access_log /var/log/squid/access.log squark&lt;br /&gt;
cache_store_log none&lt;br /&gt;
pid_filename /var/run/squid.pid&lt;br /&gt;
&lt;br /&gt;
# Make sure client IP is passed to Squark&lt;br /&gt;
log_uses_indirect_client on&lt;br /&gt;
acl_uses_indirect_client on&lt;br /&gt;
&lt;br /&gt;
# Debugging Squid, see http://wiki.squid-cache.org/KnowledgeBase/DebugSections&lt;br /&gt;
# for more info&lt;br /&gt;
# Keep 7 days of cache log&lt;br /&gt;
debug_options rotate=7&lt;br /&gt;
&lt;br /&gt;
# Web auditors want to see the full uri, even with the query terms&lt;br /&gt;
strip_query_terms off&lt;br /&gt;
&lt;br /&gt;
refresh_pattern ^ftp:		1440	20%	10080&lt;br /&gt;
refresh_pattern ^gopher:	1440	0%	1440&lt;br /&gt;
refresh_pattern -i (/cgi-bin/|\?) 0	0%	0&lt;br /&gt;
refresh_pattern .		0	20%	4320&lt;br /&gt;
&lt;br /&gt;
coredump_dir /var/cache/squid&lt;br /&gt;
&lt;br /&gt;
dns_nameservers 172.17.48.1&lt;br /&gt;
&lt;br /&gt;
# &lt;br /&gt;
# Authentication&lt;br /&gt;
#&lt;br /&gt;
# Squark external acl&lt;br /&gt;
#external_acl_type squark_snmp_auth_D children-max=1 ttl=4 grace=1 negative_ttl=0 concurrency=128 %SRC /usr/bin/squark-auth-snmp -c public -R &amp;lt;SWITCH_IP&amp;gt; -i &amp;lt;D_VLAN_IF&amp;gt; -v &amp;lt;D_VLAN_ID&amp;gt; -f &amp;quot;%N-%i=%I&amp;quot; -T /etc/squark/topology.conf&lt;br /&gt;
&lt;br /&gt;
#&lt;br /&gt;
# Access Control Lists (ACL&#039;s)&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
# Standard ACL settings&lt;br /&gt;
acl QUERY urlpath_regex cgi-bin \? asp aspx jsp&lt;br /&gt;
acl to_localhost dst 172.17.48.1&lt;br /&gt;
acl SSL_ports port 443 563 8004 9000&lt;br /&gt;
acl Safe_ports port 21 70 80 81 210 280 443 563 499 591 777 1024 1022 1025-65535&lt;br /&gt;
acl purge method PURGE&lt;br /&gt;
acl CONNECT method CONNECT&lt;br /&gt;
&lt;br /&gt;
#acl SquarkAuth external squark_auth&lt;br /&gt;
#acl SquarkSnmpAuthD external squark_snmp_auth_D&lt;br /&gt;
&lt;br /&gt;
# Squark filter&lt;br /&gt;
url_rewrite_program /usr/bin/squark-filter&lt;br /&gt;
url_rewrite_children 1 concurrency=128&lt;br /&gt;
&lt;br /&gt;
# Require authentication&lt;br /&gt;
acl userlist  src all&lt;br /&gt;
&lt;br /&gt;
# Definition of zones&lt;br /&gt;
acl Zone_D src 172.17.48.0/24&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#&lt;br /&gt;
# Access restrictions&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
cache deny QUERY&lt;br /&gt;
&lt;br /&gt;
# Only allow cachemgr access from localhost&lt;br /&gt;
http_access allow manager localhost&lt;br /&gt;
http_access deny manager&lt;br /&gt;
&lt;br /&gt;
# Only allow purge requests from localhost&lt;br /&gt;
http_access allow purge localhost&lt;br /&gt;
http_access deny purge&lt;br /&gt;
&lt;br /&gt;
# Deny requests to unknown ports&lt;br /&gt;
http_access deny !Safe_ports&lt;br /&gt;
&lt;br /&gt;
# Deny CONNECT to other than SSL ports&lt;br /&gt;
http_access deny CONNECT !SSL_ports&lt;br /&gt;
&lt;br /&gt;
# Allow hosts in Zone_D to access the entire Internet&lt;br /&gt;
http_access allow Zone_D&lt;br /&gt;
&lt;br /&gt;
# Denying all access not explictly allowed&lt;br /&gt;
http_access deny all&lt;br /&gt;
&lt;br /&gt;
##Squark URL rewriter&lt;br /&gt;
#Prevent squark from filtering itself&lt;br /&gt;
url_rewrite_access deny manager&lt;br /&gt;
url_rewrite_access deny to_localhost&lt;br /&gt;
&lt;br /&gt;
#Finally, permit access&lt;br /&gt;
url_rewrite_access allow Zone_D&lt;br /&gt;
&lt;br /&gt;
http_reply_access allow all&lt;br /&gt;
icp_access allow all&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
Configure lighttpd&lt;br /&gt;
{{cat|/etc/lighttpd/lighttpd.conf|&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
var.basedir  = &amp;quot;/var/www/localhost&amp;quot;&lt;br /&gt;
var.logdir   = &amp;quot;/var/log/lighttpd&amp;quot;&lt;br /&gt;
var.statedir = &amp;quot;/var/lib/lighttpd&amp;quot;&lt;br /&gt;
&lt;br /&gt;
server.modules = (&lt;br /&gt;
    &amp;quot;mod_access&amp;quot;,&lt;br /&gt;
    &amp;quot;mod_accesslog&amp;quot;,&lt;br /&gt;
    &amp;quot;mod_extforward&amp;quot;&lt;br /&gt;
)&lt;br /&gt;
&lt;br /&gt;
include &amp;quot;mime-types.conf&amp;quot;&lt;br /&gt;
include &amp;quot;mod_cgi.conf&amp;quot;&lt;br /&gt;
&lt;br /&gt;
server.username      = &amp;quot;lighttpd&amp;quot;&lt;br /&gt;
server.groupname     = &amp;quot;lighttpd&amp;quot;&lt;br /&gt;
&lt;br /&gt;
server.document-root = var.basedir + &amp;quot;/squark&amp;quot;&lt;br /&gt;
server.pid-file      = &amp;quot;/var/run/lighttpd.pid&amp;quot;&lt;br /&gt;
&lt;br /&gt;
server.errorlog      = var.logdir  + &amp;quot;/error.log&amp;quot;&lt;br /&gt;
&lt;br /&gt;
server.indexfiles    = (&amp;quot;index.php&amp;quot;, &amp;quot;index.html&amp;quot;,&lt;br /&gt;
						&amp;quot;index.htm&amp;quot;, &amp;quot;default.htm&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
server.follow-symlink = &amp;quot;enable&amp;quot;&lt;br /&gt;
&lt;br /&gt;
server.port          = 81&lt;br /&gt;
server.bind          = &amp;quot;172.17.48.1&amp;quot;&lt;br /&gt;
&lt;br /&gt;
static-file.exclude-extensions = (&amp;quot;.php&amp;quot;, &amp;quot;.pl&amp;quot;, &amp;quot;.cgi&amp;quot;, &amp;quot;.fcgi&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
accesslog.filename   = var.logdir + &amp;quot;/access.log&amp;quot;&lt;br /&gt;
&lt;br /&gt;
url.access-deny = (&amp;quot;~&amp;quot;, &amp;quot;.inc&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
extforward.forwarder = (&amp;quot;172.17.48.1&amp;quot; =&amp;gt; &amp;quot;trust&amp;quot;)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
{{cat|/etc/lighttpd/mod_cgi.conf|&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
###############################################################################&lt;br /&gt;
# mod_cgi.conf&lt;br /&gt;
# include&#039;d by lighttpd.conf.&lt;br /&gt;
# $Header: /var/cvsroot/gentoo-x86/www-servers/lighttpd/files/conf/mod_cgi.conf,v 1.1 2005/08/27 12:36:13 ka0ttic Exp $&lt;br /&gt;
###############################################################################&lt;br /&gt;
&lt;br /&gt;
#&lt;br /&gt;
# see cgi.txt for more information on using mod_cgi&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
server.modules += (&amp;quot;mod_cgi&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# NOTE: this requires mod_alias&lt;br /&gt;
alias.url = (&lt;br /&gt;
     &amp;quot;/cgi-bin/&amp;quot;	    =&amp;gt;	    var.basedir + &amp;quot;/cgi-bin/&amp;quot;&lt;br /&gt;
)&lt;br /&gt;
&lt;br /&gt;
#&lt;br /&gt;
# Note that you&#039;ll also want to enable the&lt;br /&gt;
# cgi-bin alias via mod_alias (above).&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
$HTTP[&amp;quot;url&amp;quot;] =~ &amp;quot;^/cgi-bin/&amp;quot; {&lt;br /&gt;
    # disable directory listings&lt;br /&gt;
    dir-listing.activate = &amp;quot;disable&amp;quot;&lt;br /&gt;
    # only allow cgi&#039;s in this directory&lt;br /&gt;
    cgi.assign = (&lt;br /&gt;
		&amp;quot;.pl&amp;quot;	=&amp;gt;	&amp;quot;/usr/bin/perl&amp;quot;,&lt;br /&gt;
		&amp;quot;.cgi&amp;quot;	=&amp;gt;	&amp;quot;/usr/bin/haserl&amp;quot;&lt;br /&gt;
	)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# vim: set ft=conf foldmethod=marker et :&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
Link Squark web pages to the Web server home directory&lt;br /&gt;
{{Cmd|ln -s /usr/share/squark/www/ /var/www/localhost/squark}}&lt;br /&gt;
Make &#039;squid&#039; and &#039;lighttpd&#039; users member of the group squark&lt;br /&gt;
{{Cmd|addgroup squid squark&lt;br /&gt;
addgroup lighttpd squark}}&lt;br /&gt;
Start lighttpd and configure the Web service to start at boot&lt;br /&gt;
{{Cmd|/etc/init.d/lighttpd start&lt;br /&gt;
rc-update add lighttpd}}&lt;br /&gt;
Start Squid and configure it to start at boot&lt;br /&gt;
{{Cmd|/etc/init.d/squid start&lt;br /&gt;
rc-update add squid}}&lt;br /&gt;
&lt;br /&gt;
[[Category:ACF]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=ISP_Mail_Server_2.x_HowTo&amp;diff=12891</id>
		<title>ISP Mail Server 2.x HowTo</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=ISP_Mail_Server_2.x_HowTo&amp;diff=12891"/>
		<updated>2016-07-26T23:41:25Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Add ACF category&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== A Full Service Mail Server ==&lt;br /&gt;
&lt;br /&gt;
This document describes installation process for latest Alpine Linux 2.x platform. The goal of this document is to describe how to set up postfix, dovecot, clamav, dspam, roundecube, and postfixadmin for a full-featured &amp;quot;ISP&amp;quot; level mail server. We recommend to run server at Alpine Linux version not earlier than 2.2, since release 2.2 has a lot of important fixes for bugs and security issues. (See also [[ISP_Mail_Server_HowTo|previous installation on 1.10 platform]]. There are also [[ISP Mail Server Upgrade 2.x|notes on upgrading from v1 to v2]].)&lt;br /&gt;
&lt;br /&gt;
The server must provide:&lt;br /&gt;
&lt;br /&gt;
* multiple virtual domains&lt;br /&gt;
* admins for each domain (to add/remove virtual accounts)&lt;br /&gt;
* quota support per domain / account&lt;br /&gt;
* downloading email via IMAP / IMAPS / POP3 / POP3S&lt;br /&gt;
* relaying email for authenticated users with TLS or SSL (Submission / SMTPS protocol)&lt;br /&gt;
* standard filters (virus/spam/rbl/etc)&lt;br /&gt;
* web mail client&lt;br /&gt;
* value add services&lt;br /&gt;
&lt;br /&gt;
== Set up Lighttpd + PHP ==&lt;br /&gt;
&lt;br /&gt;
PostfixAdmin needs php pgpsql and imap modules, so we do it in this step.&lt;br /&gt;
&lt;br /&gt;
  apk add lighttpd php php-pgsql php-imap&lt;br /&gt;
&lt;br /&gt;
Stop and remove mini_httpd, and move ACF to lighttpd;  We are setting this up to be a multi-domain virtual web server (replace host.example.com with the actual domain):&lt;br /&gt;
&lt;br /&gt;
  /etc/init.d/mini_httpd stop&lt;br /&gt;
  apk del mini_httpd&lt;br /&gt;
  mkdir -p /var/www/domains/host.example.com/www&lt;br /&gt;
  ln -s /usr/share/acf/www /var/www/domains/host.example.com/www/acf&lt;br /&gt;
&lt;br /&gt;
Edit /var/www/domains/host.example.com/www/index.html to put a simple redirection page:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;!DOCTYPE HTML PUBLIC &amp;quot;-//W3C//DTD HTML 4.01//EN&amp;quot; &amp;quot;http://www.w3.org/TR/html4/strict.dtd&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;html lang=&amp;quot;en&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
&amp;lt;meta http-equiv=&amp;quot;Content-Type&amp;quot; content=&amp;quot;text/html; charset=ISO-8859-1&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;title&amp;gt;host.example.com Redirector&amp;lt;/title&amp;gt;&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&amp;lt;body&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;/acf&amp;quot;&amp;gt;ACF&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;/postfixadmin&amp;quot;&amp;gt;PostfixAdmin&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;/roundcube&amp;quot;&amp;gt;Roundcube&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Edit /etc/lighttpd/mod_cgi.conf to serve haserl files by adding a &amp;quot;&amp;quot; =&amp;gt; &amp;quot;&amp;quot; cgi handler and to treat /acf/cgi-bin as a CGI directory (remove the &#039;^&#039;):&lt;br /&gt;
&lt;br /&gt;
 $HTTP[&amp;quot;url&amp;quot;] =~ &amp;quot;/cgi-bin/&amp;quot; {&lt;br /&gt;
     # disable directory listings&lt;br /&gt;
     dir-listing.activate = &amp;quot;disable&amp;quot;&lt;br /&gt;
     # only allow cgi&#039;s in this directory&lt;br /&gt;
     cgi.assign = (&lt;br /&gt;
 		&amp;quot;.pl&amp;quot;	=&amp;gt;	&amp;quot;/usr/bin/perl&amp;quot;,&lt;br /&gt;
 		&amp;quot;.cgi&amp;quot;	=&amp;gt;	&amp;quot;/usr/bin/perl&amp;quot;,&lt;br /&gt;
 		&amp;quot;&amp;quot; =&amp;gt; &amp;quot;&amp;quot;&lt;br /&gt;
 	)&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
Get a web certificate, and install it.  You have two options: 1. If you want to use a self-signed cert, you can use the instructions found at [[Generating SSL certs with ACF]] or [[Generating SSL certs with ACF 1.9]] to generate it. 2. Use the certificate created with the &#039;&#039;&#039;setup-acf&#039;&#039;&#039; command. 3. Get certificate from trusted root certification centers. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Option 1:&#039;&#039;&#039;&lt;br /&gt;
If you create your own self-signed certificate, you can create the &amp;quot;server-bundle.pem&amp;quot; and the &amp;quot;ca-crt.pem&amp;quot; file with these commands:&lt;br /&gt;
&lt;br /&gt;
  openssl pkcs12 -nokeys -cacerts -in certificate.pfx  -out /etc/lighttpd/ca-crt.pem&lt;br /&gt;
  openssl pkcs12 -nodes -in certificate.pfx -out /etc/lighttpd/server-bundle.pem&lt;br /&gt;
  chown root:root /etc/lighttpd/server-bundle.pem&lt;br /&gt;
  chmod 400 /etc/lighttpd/server-bundle.pem&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; The server certificate &#039;&#039;and&#039;&#039; key are in the server-bundle.pem file, so it is critical that the file be read-only by user &amp;quot;root&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Option 2:&#039;&#039;&#039;&lt;br /&gt;
If you prefer to just use the default certificate created with the &#039;&#039;&#039;setup-acf&#039;&#039;&#039; command, then you will need to do the following:&lt;br /&gt;
&lt;br /&gt;
  setup-acf&lt;br /&gt;
&lt;br /&gt;
During the above process, mini_httpd will be started, if it isn&#039;t already, and a certificate will be created. Once you have completed the setup-acf steps, do the following to move the certificate files to the correct location for lighttpd to use.&lt;br /&gt;
&lt;br /&gt;
  mv /etc/ssl/mini_httpd/server.pem /etc/lighttpd/server-bundle.pem&lt;br /&gt;
  chown root:root /etc/lighttpd/server-bundle.pem&lt;br /&gt;
  chmod 400 /etc/lighttpd/server-bundle.pem&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Option 3:&#039;&#039;&#039;&lt;br /&gt;
You may decide to receive a certificate from trusted root certification centers. One good choice is to request free certificate from StartSSL at page: https://www.startssl.com/?app=33.&lt;br /&gt;
&lt;br /&gt;
Add these lines to /etc/lighttpd/lighttpd.conf to point to the new document root, and set it up to listen on port 443 (replace &#039;&#039;host.example.com&#039;&#039; with the actual domain and &#039;&#039;ip_address_of_server&#039;&#039; with the actual IP address):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
simple-vhost.server-root   = &amp;quot;/var/www/domains/&amp;quot;&lt;br /&gt;
simple-vhost.default-host  = &amp;quot;/host.example.com/&amp;quot;&lt;br /&gt;
simple-vhost.document-root = &amp;quot;www/&amp;quot;&lt;br /&gt;
&lt;br /&gt;
$SERVER[&amp;quot;socket&amp;quot;] == &amp;quot;ip_address_of_server:443&amp;quot; {&lt;br /&gt;
ssl.engine    = &amp;quot;enable&amp;quot;&lt;br /&gt;
ssl.pemfile   = &amp;quot;/etc/lighttpd/server-bundle.pem&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you went with Option 1 above, then add an additional line underneath the ssl.pem file line, so that the section appears as follows:&lt;br /&gt;
&lt;br /&gt;
  $SERVER[&amp;quot;socket&amp;quot;] == &amp;quot;ip_address_of_server:443&amp;quot; {&lt;br /&gt;
  ssl.engine    = &amp;quot;enable&amp;quot;&lt;br /&gt;
  ssl.pemfile   = &amp;quot;/etc/lighttpd/server-bundle.pem&amp;quot;&lt;br /&gt;
  ssl.ca-file   = &amp;quot;/etc/lighttpd/ca-crt.pem&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
Ensure that the simple_vhosts module is loaded, as well as the cgi config scripts by uncommenting the following lines in /etc/lighttpd/lighttpd.conf:&lt;br /&gt;
&lt;br /&gt;
 server.modules = (&lt;br /&gt;
     #  other modules may be listed&lt;br /&gt;
     &amp;quot;mod_simple_vhost&amp;quot;, &lt;br /&gt;
     #  other modules may be listed&lt;br /&gt;
  .&lt;br /&gt;
  .&lt;br /&gt;
  .&lt;br /&gt;
     include &amp;quot;mod_cgi.conf&amp;quot;&lt;br /&gt;
     include &amp;quot;mod_fastcgi.conf&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Stop and remove mini_httpd; start lighttpd, test:&lt;br /&gt;
&lt;br /&gt;
  /etc/init.d/mini_httpd stop&lt;br /&gt;
  rc-update del mini_httpd&lt;br /&gt;
  apk del mini_httpd&lt;br /&gt;
  rc-update add lighttpd&lt;br /&gt;
  /etc/init.d/lighttpd start&lt;br /&gt;
&lt;br /&gt;
At this point you should be able to see ACF being served with lighttpd (Note: this will work well with alpine 1.10 and 2.x. With earlier versions there will be problems.)  https://host.example.com/acf/&lt;br /&gt;
&lt;br /&gt;
== Install Postgresql ==&lt;br /&gt;
&lt;br /&gt;
Add and configure postgresql:&lt;br /&gt;
&lt;br /&gt;
  apk add acf-postgresql postgresql-client&lt;br /&gt;
  /etc/init.d/postgresql setup&lt;br /&gt;
  /etc/init.d/postgresql start&lt;br /&gt;
  rc-update add postgresql&lt;br /&gt;
&lt;br /&gt;
At this point any user can connect to the sql server with &amp;quot;trust&amp;quot; mechanism.  If you want to enforce password authentication (you probably do) edit /var/lib/postgresql/9.0/data/pg_hba.conf. Since by default &amp;quot;trust&amp;quot; mechanism is for local connections only we assume using trust password-less access as safe.&lt;br /&gt;
&lt;br /&gt;
Create the postfix database:&lt;br /&gt;
&lt;br /&gt;
  psql -U postgres&lt;br /&gt;
   create user postfix with password &#039;******&#039;;&lt;br /&gt;
   create database postfix owner postfix;&lt;br /&gt;
   \q&lt;br /&gt;
&lt;br /&gt;
(Of course, use your selected password where ******* is shown above.)&lt;br /&gt;
&lt;br /&gt;
== Install PostfixAdmin ==&lt;br /&gt;
&lt;br /&gt;
We are going to install the postfix admin web front-end before we install the mail server.  This just creates an interface to populate the SQL tables that postfix and dovecot will use.&lt;br /&gt;
&lt;br /&gt;
Download PostfixAdmin from Sourceforge.  When these instructions were written, 2.3 was the current release, so (replace host.example.com with the actual domain):&lt;br /&gt;
&lt;br /&gt;
 wget http://downloads.sourceforge.net/project/postfixadmin/postfixadmin/postfixadmin-2.3.3/postfixadmin-2.3.3.tar.gz&lt;br /&gt;
 tar zxvf postfixadmin-2.3.3.tar.gz&lt;br /&gt;
 mkdir -p /var/www/domains/host.example.com/www/postfixadmin&lt;br /&gt;
 mv  postfixadmin-2.3.3/* /var/www/domains/host.example.com/www/postfixadmin&lt;br /&gt;
 rm -rf postfixadmin*&lt;br /&gt;
&lt;br /&gt;
Edit /var/www/domains/host.example.com/www/postfixadmin/config.inc.php and modify at least these lines (replace host.example.com with the actual domain):&lt;br /&gt;
&lt;br /&gt;
 $CONF[&#039;configured&#039;] = true;&lt;br /&gt;
 $CONF[&#039;setup_password&#039;] = &amp;quot;&amp;quot;;  &amp;lt;&amp;lt; Don&#039;t change this yet&lt;br /&gt;
 $CONF[&#039;database_type&#039;] = &#039;pgsql&#039;;&lt;br /&gt;
 $CONF[&#039;database_host&#039;] = &#039;localhost&#039;;&lt;br /&gt;
 $CONF[&#039;database_user&#039;] = &#039;postfix&#039;;&lt;br /&gt;
 $CONF[&#039;database_password&#039;] = &#039;*****&#039;;   &amp;lt;&amp;lt; The password you chose above&lt;br /&gt;
 $CONF[&#039;database_name&#039;] = &#039;postfix&#039;;&lt;br /&gt;
 $CONF[&#039;database_prefix&#039;] = &amp;quot;&amp;quot;;&lt;br /&gt;
 $CONF[&#039;admin_email&#039;] = &#039;you@some.email.com&#039;;  &amp;lt;&amp;lt; Your email address &lt;br /&gt;
 $CONF[&#039;encrypt&#039;] = &#039;md5crypt&#039;;&lt;br /&gt;
 $CONF[&#039;authlib_default_flavor&#039;] = &#039;md5raw&#039;;&lt;br /&gt;
 $CONF[&#039;dovecotpw&#039;] = &amp;quot;/usr/sbin/dovecotpw&amp;quot;;&lt;br /&gt;
 $CONF[&#039;domain_path&#039;] = &#039;YES&#039;;&lt;br /&gt;
 $CONF[&#039;domain_in_mailbox&#039;] = &#039;NO&#039;;&lt;br /&gt;
 $CONF[&#039;aliases&#039;] = &#039;10&#039;;                       &lt;br /&gt;
 $CONF[&#039;mailboxes&#039;] = &#039;10&#039;;&lt;br /&gt;
 $CONF[&#039;maxquota&#039;] = &#039;10&#039;;&lt;br /&gt;
 $CONF[&#039;quota&#039;] = &#039;YES&#039;;&lt;br /&gt;
 $CONF[&#039;quota_multiplier&#039;] = &#039;1024000&#039;;&lt;br /&gt;
 $CONF[&#039;vacation&#039;] = &#039;NO&#039;; &lt;br /&gt;
 $CONF[&#039;vacation_control&#039;] =&#039;NO&#039;;&lt;br /&gt;
 $CONF[&#039;vacation_control_admin&#039;] = &#039;NO&#039;;&lt;br /&gt;
 $CONF[&#039;alias_control&#039;] = &#039;YES&#039;;&lt;br /&gt;
 $CONF[&#039;alias_control_admin&#039;] = &#039;YES&#039;;&lt;br /&gt;
 $CONF[&#039;special_alias_control&#039;] = &#039;YES&#039;;&lt;br /&gt;
 $CONF[&#039;fetchmail&#039;] = &#039;NO&#039;;&lt;br /&gt;
 $CONF[&#039;user_footer_link&#039;] = &amp;quot;http://host.example.com/postfixadmin&amp;quot;;&lt;br /&gt;
 $CONF[&#039;footer_link&#039;] = &#039;http://host.example.com/postfixadmin/main.php&#039;;&lt;br /&gt;
 $CONF[&#039;create_mailbox_subdirs_prefix&#039;]=&amp;quot;&amp;quot;;  &lt;br /&gt;
 $CONF[&#039;used_quotas&#039;] = &#039;YES&#039;;   &lt;br /&gt;
 $CONF[&#039;new_quota_table&#039;] = &#039;YES&#039;;  &lt;br /&gt;
&lt;br /&gt;
You should further edit /var/www/domains/host.example.com/www/postfixadmin/config.inc.php and replace all instances of &amp;quot;change-this-to-your.domain.tld&amp;quot; with your actual mail domain. This can be done with busybox sed (replace example.com with your domain name):&lt;br /&gt;
&lt;br /&gt;
 sed -i -e &#039;s/change-this-to-your.domain.tld/example.com/g&#039; /var/www/domains/host.example.com/www/postfixadmin/config.inc.php&lt;br /&gt;
&lt;br /&gt;
Go to https://host.example.com/postfixadmin/setup.php&lt;br /&gt;
&lt;br /&gt;
Create the password hash, add it to the config.inc.php file&lt;br /&gt;
&lt;br /&gt;
Go back to https://host.example.com/postfixadmin/setup.php&lt;br /&gt;
&lt;br /&gt;
Create superadmin account.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;NOTE:&#039;&#039;&#039; Check http://sourceforge.net/tracker/index.php?func=detail&amp;amp;aid=2859165&amp;amp;group_id=191583&amp;amp;atid=937964 if you have bug on listing domains page.&lt;br /&gt;
&lt;br /&gt;
== Install Postfix ==&lt;br /&gt;
&lt;br /&gt;
Create a user for the virtual mail delivery, and get its uid/gid (you&#039;ll need the numeric uid/gid for postfix):&lt;br /&gt;
&lt;br /&gt;
 adduser vmail -H -D -s /bin/false&lt;br /&gt;
 grep vmail /etc/passwd&lt;br /&gt;
&lt;br /&gt;
(In examples below, we use 1006/1006 for the uid/gid)&lt;br /&gt;
&lt;br /&gt;
Create the mail directory, and assign vmail as the owner:&lt;br /&gt;
&lt;br /&gt;
 mkdir -p /var/mail/domains&lt;br /&gt;
 chown -R vmail:vmail /var/mail/domains&lt;br /&gt;
 &lt;br /&gt;
Install postfix:&lt;br /&gt;
&lt;br /&gt;
 apk add acf-postfix postfix-pgsql postfix-pcre&lt;br /&gt;
&lt;br /&gt;
Edit the /etc/postfix/main.cf file. Here&#039;s an example (don&#039;t forget to replace the uid/gid):&lt;br /&gt;
&lt;br /&gt;
 myhostname=host.example.com&lt;br /&gt;
 mydomain=example.com&lt;br /&gt;
 &lt;br /&gt;
 mydestination = localhost.$mydomain, localhost&lt;br /&gt;
 mynetworks_style = subnet&lt;br /&gt;
 mynetworks = 127.0.0.0/8&lt;br /&gt;
 &lt;br /&gt;
 virtual_mailbox_domains = proxy:pgsql:/etc/postfix/sql/pgsql_virtual_domains_maps.cf&lt;br /&gt;
 virtual_alias_maps = proxy:pgsql:/etc/postfix/sql/pgsql_virtual_alias_maps.cf,&lt;br /&gt;
        proxy:pgsql:/etc/postfix/sql/pgsql_virtual_alias_domain_maps.cf,&lt;br /&gt;
        proxy:pgsql:/etc/postfix/sql/pgsql_virtual_alias_domain_catchall_maps.cf&lt;br /&gt;
 &lt;br /&gt;
 virtual_mailbox_maps = proxy:pgsql:/etc/postfix/sql/pgsql_virtual_mailbox_maps.cf,&lt;br /&gt;
        proxy:pgsql:/etc/postfix/sql/pgsql_virtual_alias_domain_mailbox_maps.cf&lt;br /&gt;
 &lt;br /&gt;
 virtual_mailbox_base = /var/mail/domains/&lt;br /&gt;
 virtual_gid_maps = static:1006&lt;br /&gt;
 virtual_uid_maps = static:1006&lt;br /&gt;
 virtual_minimum_uid = 100&lt;br /&gt;
 virtual_transport = virtual&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 # This next command means you must create a virtual&lt;br /&gt;
 # domain for the host itself - ALL mail goes through&lt;br /&gt;
 # The virtual transport&lt;br /&gt;
 &lt;br /&gt;
 mailbox_transport = virtual&lt;br /&gt;
 local_transport = virtual&lt;br /&gt;
 local_transport_maps = $virtual_mailbox_maps&lt;br /&gt;
 &lt;br /&gt;
 smtpd_helo_required = yes&lt;br /&gt;
 disable_vrfy_command = yes&lt;br /&gt;
 message_size_limit = 10240000&lt;br /&gt;
 queue_minfree = 51200000&lt;br /&gt;
 &lt;br /&gt;
 smtpd_sender_restrictions =&lt;br /&gt;
        permit_mynetworks,&lt;br /&gt;
        reject_non_fqdn_sender,&lt;br /&gt;
        reject_unknown_sender_domain&lt;br /&gt;
 &lt;br /&gt;
 smtpd_recipient_restrictions =&lt;br /&gt;
        reject_non_fqdn_recipient,&lt;br /&gt;
        reject_unknown_recipient_domain,&lt;br /&gt;
        permit_mynetworks,&lt;br /&gt;
        permit_sasl_authenticated,&lt;br /&gt;
        reject_unauth_destination,&lt;br /&gt;
        reject_rbl_client dnsbl.sorbs.net,&lt;br /&gt;
        reject_rbl_client zen.spamhaus.org,&lt;br /&gt;
        reject_rbl_client bl.spamcop.net&lt;br /&gt;
 &lt;br /&gt;
 smtpd_data_restrictions = reject_unauth_pipelining&lt;br /&gt;
 &lt;br /&gt;
 # we will use this later - This prevents cleartext authentication&lt;br /&gt;
 # for relaying&lt;br /&gt;
 smtpd_tls_auth_only = yes&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Now we need to create a *bunch* of files so that postfix can get the delivery information out of sql. Here&#039;s a shell script to create the scripts.  Change PGPW to the password for the postfix user of the postfix SQL database:&lt;br /&gt;
&lt;br /&gt;
 cd /etc/postfix&lt;br /&gt;
 mkdir sql&lt;br /&gt;
 PGPW=&amp;quot;ChangeMe&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 cat - &amp;lt;&amp;lt;EOF &amp;gt;sql/pgsql_virtual_alias_domain_catchall_maps.cf&lt;br /&gt;
 user=postfix&lt;br /&gt;
 password = $PGPW&lt;br /&gt;
 hosts = localhost&lt;br /&gt;
 dbname = postfix&lt;br /&gt;
 query = Select goto From alias,alias_domain where alias_domain.alias_domain = &#039;%d&#039; and alias.address = &#039;@&#039; ||  alias_domain.target_domain and alias.active = true and alias_domain.active= true &lt;br /&gt;
 EOF&lt;br /&gt;
 &lt;br /&gt;
 cat - &amp;lt;&amp;lt;EOF &amp;gt;sql/pgsql_virtual_alias_domain_mailbox_maps.cf&lt;br /&gt;
 user=postfix&lt;br /&gt;
 password = $PGPW&lt;br /&gt;
 hosts = localhost&lt;br /&gt;
 dbname = postfix&lt;br /&gt;
 query = Select maildir from mailbox,alias_domain where alias_domain.alias_domain = &#039;%d&#039; and mailbox.username = &#039;%u&#039; || &#039;@&#039; || alias_domain.target_domain and mailbox.active = true and alias_domain.active&lt;br /&gt;
 EOF&lt;br /&gt;
 &lt;br /&gt;
 cat - &amp;lt;&amp;lt;EOF &amp;gt;sql/pgsql_virtual_alias_domain_maps.cf&lt;br /&gt;
 user=postfix&lt;br /&gt;
 password = $PGPW&lt;br /&gt;
 hosts = localhost&lt;br /&gt;
 dbname = postfix&lt;br /&gt;
 query = select goto from alias,alias_domain where alias_domain.alias_domain=&#039;%d&#039; and alias.address = &#039;%u&#039; || &#039;@&#039; || alias_domain.target_domain and alias.active= true and alias_domain.active= true&lt;br /&gt;
 EOF&lt;br /&gt;
 &lt;br /&gt;
 cat - &amp;lt;&amp;lt;EOF &amp;gt;sql/pgsql_virtual_alias_maps.cf&lt;br /&gt;
 user=postfix&lt;br /&gt;
 password = $PGPW&lt;br /&gt;
 hosts = localhost&lt;br /&gt;
 dbname = postfix&lt;br /&gt;
 query = Select goto From alias Where address=&#039;%s&#039; and active =&#039;1&#039;&lt;br /&gt;
 EOF&lt;br /&gt;
 &lt;br /&gt;
 cat - &amp;lt;&amp;lt;EOF &amp;gt;sql/pgsql_virtual_domains_maps.cf&lt;br /&gt;
 user=postfix&lt;br /&gt;
 password = $PGPW&lt;br /&gt;
 hosts = localhost&lt;br /&gt;
 dbname = postfix&lt;br /&gt;
 query = Select domain from domain where domain=&#039;%s&#039; and active=&#039;1&#039;&lt;br /&gt;
 EOF&lt;br /&gt;
 &lt;br /&gt;
 cat - &amp;lt;&amp;lt;EOF &amp;gt;sql/pgsql_virtual_mailbox_maps.cf&lt;br /&gt;
 user=postfix&lt;br /&gt;
 password = $PGPW&lt;br /&gt;
 hosts = localhost&lt;br /&gt;
 dbname = postfix&lt;br /&gt;
 query = Select maildir from mailbox where username=&#039;%s&#039; and active=true&lt;br /&gt;
 EOF&lt;br /&gt;
 &lt;br /&gt;
 chown -R postfix:postfix sql&lt;br /&gt;
 chmod 640 sql/*&lt;br /&gt;
&lt;br /&gt;
At this point you should be able to start up postfix:&lt;br /&gt;
 &lt;br /&gt;
 newaliases  # so postfix is happy...&lt;br /&gt;
 /etc/init.d/postfix start&lt;br /&gt;
 rc-update add postfix&lt;br /&gt;
&lt;br /&gt;
=== Create a domain in PostfixAdmin and test ===&lt;br /&gt;
&lt;br /&gt;
Go to http://host.example.com/postfixadmin/&lt;br /&gt;
&lt;br /&gt;
Log in using the superadmin account, create a domain for the local box (e.g. example.com), and create a user mailbox (e.g. root).&lt;br /&gt;
&lt;br /&gt;
From the machine, send a test message:&lt;br /&gt;
&lt;br /&gt;
 sendmail -t root@example.com&lt;br /&gt;
 subject: test&lt;br /&gt;
 .&lt;br /&gt;
 ^d&lt;br /&gt;
&lt;br /&gt;
In /var/log/mail.log (or /var/log/messages, if you still have busybox syslogd running) you should see the message queued.  The message should be in /var/mail/domains/example.com/root/new&lt;br /&gt;
&lt;br /&gt;
== Install Dovecot ==&lt;br /&gt;
&lt;br /&gt;
Dovecot is the POP3/IMAP server to retrieve mail.&lt;br /&gt;
&lt;br /&gt;
As before, we install dovecot: &lt;br /&gt;
&lt;br /&gt;
 apk add acf-dovecot dovecot-pgsql&lt;br /&gt;
&lt;br /&gt;
Edit /etc/dovecot/dovecot.conf:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
auth_mechanisms = plain login&lt;br /&gt;
auth_username_format = %Lu&lt;br /&gt;
#auth_verbose = yes&lt;br /&gt;
#auth_debug = yes&lt;br /&gt;
#auth_debug_passwords = no&lt;br /&gt;
&lt;br /&gt;
disable_plaintext_auth = no&lt;br /&gt;
&lt;br /&gt;
mail_location = maildir:/var/mail/domains/%d/%n&lt;br /&gt;
&lt;br /&gt;
first_valid_gid = 1000&lt;br /&gt;
first_valid_uid = 1000&lt;br /&gt;
last_valid_gid = 65535&lt;br /&gt;
last_valid_uid = 65535&lt;br /&gt;
&lt;br /&gt;
log_timestamp = &amp;quot;%Y-%m-%d %H:%M:%S &amp;quot;&lt;br /&gt;
login_greeting = IMAP server ready&lt;br /&gt;
&lt;br /&gt;
protocols = imap&lt;br /&gt;
&lt;br /&gt;
service anvil {&lt;br /&gt;
  client_limit = 2100&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
ssl_cert = &amp;lt;/etc/lighttpd/server-bundle.pem&lt;br /&gt;
ssl_key = &amp;lt;/etc/lighttpd/server-bundle.pem&lt;br /&gt;
&lt;br /&gt;
userdb {&lt;br /&gt;
  args = uid=1006 gid=1006 home=/var/mail/domains/%d/%n&lt;br /&gt;
  driver = static&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
passdb {&lt;br /&gt;
  args = /etc/dovecot/dovecot-sql.conf&lt;br /&gt;
  driver = sql&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
protocol imap {&lt;br /&gt;
  mail_plugins = autocreate&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
plugin {&lt;br /&gt;
  autocreate = Trash&lt;br /&gt;
  autocreate2 = Spam&lt;br /&gt;
  autocreate3 = Sent&lt;br /&gt;
  autosubscribe = Trash&lt;br /&gt;
  autosubscribe2 = Spam&lt;br /&gt;
  autosubscribe3 = Sent&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Be sure to replace the uid and gid with the appropriate values for the vmail user.&lt;br /&gt;
&lt;br /&gt;
We need a certificate for SSL/TLS authentication, so in the example above, we use the lighttpd cert. That way when the cert is renewed/replaced, Dovecot will have access to the new cert as well.  &lt;br /&gt;
&lt;br /&gt;
Create the /etc/dovecot/dovecot-sql.conf file:&lt;br /&gt;
&lt;br /&gt;
 driver = pgsql&lt;br /&gt;
 connect = host=localhost dbname=postfix user=postfix password=********&lt;br /&gt;
 password_query = select username,password from mailbox where local_part = &#039;%n&#039; and domain = &#039;%d&#039;&lt;br /&gt;
 default_pass_scheme =  MD5-CRYPT&lt;br /&gt;
&lt;br /&gt;
Again, change the password above to your postfix user password, and protect the file from prying eyes:&lt;br /&gt;
&lt;br /&gt;
 chown root:root /etc/dovecot/dovecot-sql.conf&lt;br /&gt;
 chmod 600 /etc/dovecot/dovecot-sql.conf&lt;br /&gt;
&lt;br /&gt;
Start dovecot&lt;br /&gt;
 /etc/init.d/dovecot start&lt;br /&gt;
 rc-update add dovecot&lt;br /&gt;
&lt;br /&gt;
== Testing ==&lt;br /&gt;
&lt;br /&gt;
Make sure your firewall allows in ports 25(SMTP) 110 (POP3), 995 (POP3S), 143(IMAP), 993(IMAPS), or whatever subset you support.  &lt;br /&gt;
 &lt;br /&gt;
At this point, you should be able to:&lt;br /&gt;
 * Create a new domain and add users with PostfixAdmin&lt;br /&gt;
 * Send mail to those users via SMTP to port 25&lt;br /&gt;
 * Retrieve mail using the user&#039;s full email and password (e.g. username: user@example.com  password: ChangeMe)&lt;br /&gt;
&lt;br /&gt;
== Value Add Features ==&lt;br /&gt;
&lt;br /&gt;
If you followed the guide above, you now have a functional mail server with many interconnected parts.  The features below assume that the server is already running as described above.  You should be able to add any or all of these features below to further enhance the mail service.&lt;br /&gt;
&lt;br /&gt;
=== Virus Scanning ===&lt;br /&gt;
&lt;br /&gt;
This procedure uses clamav and the postfix content_filter mechanism to scan inbound and outbound email for viruses.  Infected emails are dropped.  Clean emails are tagged with a &amp;quot;scanned by clamav&amp;quot; header.&lt;br /&gt;
&lt;br /&gt;
* Install clamav and clamsmtp:&lt;br /&gt;
 apk add acf-clamav clamsmtp&lt;br /&gt;
* Edit the /etc/clamav/clamd.conf file if desired (not necessary in most cases)&lt;br /&gt;
* Edit /etc/clamsmtpd.conf and verify the following lines&lt;br /&gt;
 OutAddress: 10026&lt;br /&gt;
 Listen: 127.0.0.1:10025                                               &lt;br /&gt;
 Header: X-Virus-Scanned: ClamAV using ClamSMTP&lt;br /&gt;
 Action: drop&lt;br /&gt;
 User: clamav                                                      &lt;br /&gt;
* Start the daemons&lt;br /&gt;
 rc-update add clamd&lt;br /&gt;
 rc-update add clamsmtpd&lt;br /&gt;
 /etc/init.d/clamd start&lt;br /&gt;
 /etc/init.d/clamsmtpd start&lt;br /&gt;
* Verify clamsmtp is listening on port 10025:&lt;br /&gt;
 netstat -anp | grep clamsmtp&lt;br /&gt;
* [http://memberwebs.com/stef/software/clamsmtp/postfix.html Following the clamsmtp instructions]&lt;br /&gt;
** edit /etc/postfix/main.cf and add:&lt;br /&gt;
 content_filter = scan:[127.0.0.1]:10025                                                      &lt;br /&gt;
** edit /etc/postfix/master.cf and add&lt;br /&gt;
 # AV scan filter (used by content_filter)&lt;br /&gt;
 scan      unix  -       -       n       -       16      smtp&lt;br /&gt;
         -o smtp_send_xforward_command=yes&lt;br /&gt;
         -o smtp_enforce_tls=no&lt;br /&gt;
 # For injecting mail back into postfix from the filter&lt;br /&gt;
 127.0.0.1:10026 inet  n -       n       -       16      smtpd&lt;br /&gt;
         -o content_filter=&lt;br /&gt;
         -o receive_override_options=no_unknown_recipient_checks,no_header_body_checks&lt;br /&gt;
         -o smtpd_helo_restrictions=&lt;br /&gt;
         -o smtpd_client_restrictions=&lt;br /&gt;
         -o smtpd_sender_restrictions=&lt;br /&gt;
         -o smtpd_recipient_restrictions=permit_mynetworks,reject&lt;br /&gt;
         -o mynetworks_style=host&lt;br /&gt;
         -o smtpd_authorized_xforward_hosts=127.0.0.0/8&lt;br /&gt;
* postfix reload&lt;br /&gt;
* Send and email into a local virtual domain - it should have the &#039;&#039;X-Virus-Scanned: ClamAV using ClamSMTP&#039;&#039; header.&lt;br /&gt;
&lt;br /&gt;
=== Relay for Authenticated Users ===&lt;br /&gt;
&lt;br /&gt;
As configured above, the mail server accepts email from the Internet, but it does not relay email.  If it is a perimeter exchanger for a protected network, then you can add the protected networks to the &#039;&#039;mynetworks&#039;&#039; configuration line in /etc/postfix/main.cf&lt;br /&gt;
&lt;br /&gt;
This configuration change allows &#039;&#039;remote&#039;&#039; users to authenticate against the mail server and relay through it.  The rules for relaying are:&lt;br /&gt;
* Only authenticated users can relay&lt;br /&gt;
* Authentication Credentials must be encrypted with TLS or SSL&lt;br /&gt;
* Allow Submission and SMTPS ports for relaying (many consumer networks block port 25 - SMTP by default)&lt;br /&gt;
The process uses the dovecot authentication mechanism (used with IMAPS) to authenticate users before they are allowed to relay through postfix.&lt;br /&gt;
&lt;br /&gt;
* Edit /etc/dovecot/dovecot.conf and add the following:&lt;br /&gt;
# this is for postfix SASL (authenticated users can relay through us)&lt;br /&gt;
&lt;br /&gt;
 service auth {&lt;br /&gt;
  unix_listener /var/spool/postfix/private/auth {&lt;br /&gt;
    group = postfix&lt;br /&gt;
    mode = 0660&lt;br /&gt;
    user = postfix&lt;br /&gt;
  }&lt;br /&gt;
  unix_listener /var/spool/postfix/auth-master {&lt;br /&gt;
    group = postfix&lt;br /&gt;
    mode = 0660&lt;br /&gt;
    user = vmail&lt;br /&gt;
  }&lt;br /&gt;
  user = root&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
* Restart dovecot&lt;br /&gt;
 /etc/init.d/dovecot restart&lt;br /&gt;
* Edit /etc/postfix/main.cf and add:&lt;br /&gt;
 # TLS Stuff -- since we allow SASL with tls *only*, we have to set up TLS first                    &lt;br /&gt;
 &lt;br /&gt;
 smtpd_tls_cert_file = /etc/lighttpd/server-bundle.pem&lt;br /&gt;
 smtpd_tls_key_file = /etc/lighttpd/server-bundle.pem&lt;br /&gt;
 smtpd_tls_CAfile = /etc/lighttpd/ca-crt.pem&lt;br /&gt;
 # If tls_security_level is set to &amp;quot;encrypt&amp;quot;, then SMTP rejects &lt;br /&gt;
 # unencrypted email (e.g. normal mail) which is bad.&lt;br /&gt;
 # By setting it to &amp;quot;may&amp;quot; you get TLS encrypted mail from google, slashdot, and other &lt;br /&gt;
 # interesting places.  Check your logs to see who&lt;br /&gt;
 smtpd_tls_security_level = may&lt;br /&gt;
 # Log info about the negotiated encryption levels&lt;br /&gt;
 smtpd_tls_received_header = yes&lt;br /&gt;
 smtpd_tls_loglevel = 1&lt;br /&gt;
 &lt;br /&gt;
 # SASL - this allows senders to authenticiate themselves&lt;br /&gt;
 # This along with &amp;quot;permit_sasl_authenticated&amp;quot; in smtpd_recipient_restrictions allows relaying&lt;br /&gt;
 smtpd_sasl_type = dovecot&lt;br /&gt;
 smtpd_sasl_path = private/auth&lt;br /&gt;
 smtpd_sasl_auth_enable = yes&lt;br /&gt;
 smtpd_sasl_authenticated_header = yes&lt;br /&gt;
 broken_sasl_auth_clients = yes&lt;br /&gt;
 smtpd_tls_auth_only = yes&lt;br /&gt;
* Edit /etc/postfix/master.cf and enable the submission and smtps transports.  They are probably already at the top of your  master.cf file, just commented out:&lt;br /&gt;
 submission inet n       -       n       -       -       smtpd&lt;br /&gt;
   -o smtpd_tls_security_level=encrypt&lt;br /&gt;
   -o smtpd_sasl_auth_enable=yes&lt;br /&gt;
   -o smtpd_client_restrictions=permit_sasl_authenticated,reject&lt;br /&gt;
   -o milter_macro_daemon_name=ORIGINATING&lt;br /&gt;
 smtps     inet  n       -       n       -       -       smtpd&lt;br /&gt;
   -o smtpd_tls_security_level=encrypt&lt;br /&gt;
   -o smtpd_tls_wrappermode=yes&lt;br /&gt;
   -o smtpd_sasl_auth_enable=yes&lt;br /&gt;
   -o smtpd_client_restrictions=permit_sasl_authenticated,reject&lt;br /&gt;
   -o milter_macro_daemon_name=ORIGINATING&lt;br /&gt;
*Verfiy submission and smtps are defined in /etc/services&lt;br /&gt;
 grep &amp;quot;submission\|ssmtp&amp;quot; /etc/services&lt;br /&gt;
 submission	587/tcp				# mail message submission&lt;br /&gt;
 submission	587/udp&lt;br /&gt;
 smtps		465/tcp		ssmtp		# smtp protocol over TLS/SSL&lt;br /&gt;
 smtps		465/udp		ssmtp&lt;br /&gt;
* Restart postfix&lt;br /&gt;
 postfix reload&lt;br /&gt;
&lt;br /&gt;
At this point, you should be able to set up a mail client to relay through the server with TLS (port 587) or SSL (port 465)  Note that &amp;quot;plain&amp;quot; authentication is used because the underlying link is encrypted.  For example, in Thunderbird leave &amp;quot;secure authentication&amp;quot; unchecked, and choose STARTTLS (or TLS) for the connection security.&lt;br /&gt;
&lt;br /&gt;
=== Mailbox Quotas ===&lt;br /&gt;
&lt;br /&gt;
In the default configuration, PostfixAdmin knows about quotas, but they are not enforced.   Documentation on the web mentions the [http://vda.sourceforge.net vda patch to postfix] to enforce quotas.  The only bad thing... its a &#039;&#039;patch&#039;&#039;.   Postfix and Dovecot are both conservative systems, so if the patch isn&#039;t in the upstream source, we&#039;ll assume there&#039;s a good reason.   There is a way of using quotas without patches - and it involves using dovecot&#039;s [http://wiki2.dovecot.org/LDA deliver] lda for local delivery.&lt;br /&gt;
&lt;br /&gt;
* Replace /etc/dovecot/dovecot.conf with the following:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
auth_mechanisms = plain login&lt;br /&gt;
auth_username_format = %Lu&lt;br /&gt;
#auth_verbose = yes&lt;br /&gt;
#auth_debug = yes&lt;br /&gt;
#auth_debug_passwords = no&lt;br /&gt;
&lt;br /&gt;
disable_plaintext_auth = no&lt;br /&gt;
&lt;br /&gt;
info_log_path = /var/log/dovecot-info.log&lt;br /&gt;
log_path = /var/log/dovecot.log&lt;br /&gt;
&lt;br /&gt;
mail_location = maildir:/var/mail/domains/%d/%n&lt;br /&gt;
&lt;br /&gt;
first_valid_gid = 1000&lt;br /&gt;
first_valid_uid = 1000&lt;br /&gt;
last_valid_gid = 65535&lt;br /&gt;
last_valid_uid = 65535&lt;br /&gt;
&lt;br /&gt;
log_timestamp = &amp;quot;%Y-%m-%d %H:%M:%S &amp;quot;&lt;br /&gt;
login_greeting = IMAP server ready&lt;br /&gt;
&lt;br /&gt;
protocols = imap&lt;br /&gt;
&lt;br /&gt;
service anvil {&lt;br /&gt;
  client_limit = 2100&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
service auth {&lt;br /&gt;
  unix_listener /var/spool/postfix/auth-master {&lt;br /&gt;
    group = postfix&lt;br /&gt;
    mode = 0660&lt;br /&gt;
    user = vmail&lt;br /&gt;
  }&lt;br /&gt;
  unix_listener /var/spool/postfix/private/auth {&lt;br /&gt;
    group = postfix&lt;br /&gt;
    mode = 0660&lt;br /&gt;
    user = postfix&lt;br /&gt;
  }&lt;br /&gt;
  user = root&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
service imap-login {&lt;br /&gt;
  inet_listener imap {&lt;br /&gt;
    address = 127.0.0.1&lt;br /&gt;
    port = 143&lt;br /&gt;
  }&lt;br /&gt;
  inet_listener imaps {&lt;br /&gt;
    address = *&lt;br /&gt;
    port = 993&lt;br /&gt;
  }&lt;br /&gt;
  process_limit = 1024&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
service pop3-login {&lt;br /&gt;
  process_limit = 1024&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
service dict {&lt;br /&gt;
  unix_listener dict {&lt;br /&gt;
    group =&lt;br /&gt;
    mode = 0600&lt;br /&gt;
    user = vmail&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
ssl_ca = &amp;lt;/etc/ssl/certs/&amp;lt;CA Certificate file&amp;gt;&lt;br /&gt;
ssl_cert = &amp;lt;/etc/ssl/private/&amp;lt;Public part of certificate file&amp;gt;&lt;br /&gt;
ssl_key = &amp;lt;/etc/ssl/private/&amp;lt;Private part of certificate file&amp;gt;&lt;br /&gt;
&lt;br /&gt;
passdb {&lt;br /&gt;
  args = /etc/dovecot/dovecot-pgsql.conf&lt;br /&gt;
  driver = sql&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
userdb {&lt;br /&gt;
  driver = prefetch&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
userdb {&lt;br /&gt;
  args = /etc/dovecot/dovecot-pgsql.conf&lt;br /&gt;
  driver = sql&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
plugin {&lt;br /&gt;
  quota = dict:user::proxy::quotadict&lt;br /&gt;
&lt;br /&gt;
  autocreate = Trash&lt;br /&gt;
  autocreate2 = Spam&lt;br /&gt;
  autocreate3 = Sent&lt;br /&gt;
  autosubscribe = Trash&lt;br /&gt;
  autosubscribe2 = Spam&lt;br /&gt;
  autosubscribe3 = Sent&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
protocol imap {&lt;br /&gt;
  mail_plugins = autocreate quota imap_quota&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
protocol pop3 {                                                               &lt;br /&gt;
         mail_plugins = quota                                                  &lt;br /&gt;
}   &lt;br /&gt;
&lt;br /&gt;
dict {&lt;br /&gt;
  quotadict = pgsql:/etc/dovecot/dovecot-dict-quota.conf&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
protocol lda {&lt;br /&gt;
  auth_socket_path = /var/spool/postfix/auth-master&lt;br /&gt;
  mail_plugins = quota&lt;br /&gt;
  postmaster_address = postmaster@host.example.com&lt;br /&gt;
  sendmail_path = /usr/sbin/sendmail&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
* edit &amp;lt;tt&amp;gt;/etc/dovecot/dovecot-sql.conf&amp;lt;/tt&amp;gt; and replace the user and password queries with the following (you may not have a user_query yet - add it):&lt;br /&gt;
&lt;br /&gt;
 password_query = select username as user, password, 1006 as userdb_uid, 1006 as userdb_gid, &#039;*:bytes=&#039; || quota as userdb_quota_rule from mailbox  where local_part = &#039;%n&#039; and domain = &#039;%d&#039;&lt;br /&gt;
 user_query = select &#039;/var/mail/domains/&#039; || maildir as home, 1006 as uid, 1006 as gid, &#039;*:bytes=&#039; || quota  as quota_rule from mailbox where local_part = &#039;%n&#039; and domain =&#039;%d&#039;&lt;br /&gt;
&lt;br /&gt;
* create &amp;lt;tt&amp;gt;/etc/dovecot/dovecot-dict-quota.conf&amp;lt;/tt&amp;gt;&lt;br /&gt;
 connect = host=localhost dbname=postfix user=postfix password=********&lt;br /&gt;
 &lt;br /&gt;
 map {&lt;br /&gt;
         pattern = priv/quota/storage&lt;br /&gt;
         table = quota2&lt;br /&gt;
         username_field =username&lt;br /&gt;
         value_field = bytes&lt;br /&gt;
 }&lt;br /&gt;
 &lt;br /&gt;
 map {&lt;br /&gt;
        pattern= priv/quota/messages&lt;br /&gt;
        table = quota2&lt;br /&gt;
        username_field = username&lt;br /&gt;
        value_field = messages&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
Again, change the password above to your postfix user password, and protect the file from prying eyes:&lt;br /&gt;
  chown dovecot:root /etc/dovecot/dovecot-sql.conf&lt;br /&gt;
  chmod 600 /etc/dovecot/dovecot-sql.conf&lt;br /&gt;
  chown dovecot:root /etc/dovecot/dovecot-dict-quota.conf&lt;br /&gt;
  chmod 600 /etc/dovecot/dovecot-dict-quota.conf&lt;br /&gt;
&lt;br /&gt;
Side note: [http://wiki2.dovecot.org/Quota/Dict The Dovecot Quota Documentation] mentions the need for a trigger with pgsql.  This was created in the PostfixAdmin install, which is why you instantiated the pgsql language when creating the database.  If not, you will need to create the trigger, to reference the quota2 table, not the quota table mentioned in the dovecot docs.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* create a new transport for the dovecot lda.   Add the following to  /etc/postfix/master.cf:&lt;br /&gt;
 # The dovecot deliver lda&lt;br /&gt;
 dovecot   unix  -       n       n       -       -       pipe&lt;br /&gt;
   flags=DRhu user=vmail:vmail argv=/usr/libexec/dovecot/deliver -f ${sender} -d ${user}@${nexthop}&lt;br /&gt;
&lt;br /&gt;
* Edit the /etc/postfix/main.cf.  Replace &lt;br /&gt;
 virtual_transport = virtual &lt;br /&gt;
with&lt;br /&gt;
 virtual_transport = dovecot&lt;br /&gt;
 dovecot_destination_recipient_limit = 1&lt;br /&gt;
&lt;br /&gt;
Change permissions on the /var/log/dovecot* log files, so that the vmail user can write to them:&lt;br /&gt;
&lt;br /&gt;
 chown vmail:vmail /var/log/dovecot*&lt;br /&gt;
&lt;br /&gt;
Restart Postfix and Dovecot:&lt;br /&gt;
&lt;br /&gt;
 /etc/init.d/postfix restart&lt;br /&gt;
 /etc/init.d/dovecot restart&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;TODO&#039;&#039;&#039;  This will cause over-quota emails to bounce.   Which could be a source of backscatter.   We need a way of checking quota limits after RBL checking but before the message is accepted in the queue.&lt;br /&gt;
&lt;br /&gt;
=== WebMail (RoundCube) ===&lt;br /&gt;
&lt;br /&gt;
[http://roundcube.net/ RoundCube] is an &amp;quot;ajax /Web2.0&amp;quot; web-mail client.  These instructions are for the Alpine Linux 2.2 repository &lt;br /&gt;
&lt;br /&gt;
* Verify that you have at least the following in /etc/postfix/main.cf. Unless you have followed the Relay for Authenticated Users section above, set &#039;&#039;&#039;smtpd_tls_auth_only = no&#039;&#039;&#039;, otherwise leave it set to &#039;&#039;&#039;yes&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# SASL - this allows senders to authenticiate themselves&lt;br /&gt;
# This along with &amp;quot;permit_sasl_authenticated&amp;quot; in smtpd_recipient_restrictions allows relaying&lt;br /&gt;
smtpd_sasl_type = dovecot&lt;br /&gt;
smtpd_sasl_path = private/dovecot-auth.sock&lt;br /&gt;
smtpd_sasl_auth_enable = yes&lt;br /&gt;
smtpd_sasl_authenticated_header = yes&lt;br /&gt;
# Set the next line to no if TLS auth is not configured &lt;br /&gt;
smtpd_tls_auth_only = no&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Ensure you have followed section &#039;&#039;Relay_for_Authenticated_Users&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
* Restart the relevant services:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/etc/init.d/postfix restart&lt;br /&gt;
/etc/init.d/dovecot restart&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Add the package and related php modules:&lt;br /&gt;
&lt;br /&gt;
 apk add roundcubemail php-xml php-openssl php-mcrypt php-gd php-iconv php-dom php-intl&lt;br /&gt;
&lt;br /&gt;
* Link the roundcube application back into the docroot&lt;br /&gt;
&lt;br /&gt;
 ln -s /usr/share/webapps/roundcube /var/www/domains/host.example.com/www/roundcube&lt;br /&gt;
&lt;br /&gt;
* Install &#039;&#039;roundcubemail-install&#039;&#039; package&lt;br /&gt;
&lt;br /&gt;
 apk add roundcubemail-installer&lt;br /&gt;
&lt;br /&gt;
* Follow the instructions in /usr/share/webapps/roundcube/INSTALL:&lt;br /&gt;
 cd /usr/share/webapps/roundcube&lt;br /&gt;
 chown -R lighttpd:lighttpd /var/log/roundcube&lt;br /&gt;
 &lt;br /&gt;
 su postgres&lt;br /&gt;
 createuser roundcube&lt;br /&gt;
   Shall the new role be a superuser? (y/n) n&lt;br /&gt;
   Shall the new role be allowed to create databases? (y/n) n&lt;br /&gt;
   Shall the new role be allowed to create more new roles? (y/n) y&lt;br /&gt;
 createdb -O roundcube -E UNICODE -T template0 roundcubemail&lt;br /&gt;
 psql roundcubemail&lt;br /&gt;
   roundcubemail=# ALTER USER roundcube WITH PASSWORD &#039;the_new_password&#039;;&lt;br /&gt;
   roundcubemail=# \c - roundcube&lt;br /&gt;
   roundcubemail=&amp;gt; \i /usr/share/webapps/roundcube/SQL/postgres.initial.sql&lt;br /&gt;
   roundcubemail=&amp;gt; \q&lt;br /&gt;
 exit&lt;br /&gt;
&lt;br /&gt;
* Edit /etc/php/php.ini and set date.timezone to your local timezone, or to UTC&lt;br /&gt;
&lt;br /&gt;
* Restart lighttpd to verify the new php libraries are used&lt;br /&gt;
&lt;br /&gt;
 /etc/init.d/lighttpd restart&lt;br /&gt;
&lt;br /&gt;
* Enable installer mode in /etc/roundcube/main.inc.php file:&lt;br /&gt;
&lt;br /&gt;
 $rcmail_config[&#039;enable_installer&#039;] = true;&lt;br /&gt;
&lt;br /&gt;
* Point your browser to https://host.example.com/roundcube/installer&lt;br /&gt;
* Start installation&lt;br /&gt;
&lt;br /&gt;
For the specific configuration parameters in the install step:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Property&lt;br /&gt;
!Setting&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;enable_spellcheck&#039;&#039; ||   disabled &lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;identities_level&#039;&#039; ||  one identity with possibility to edit all params but not email address &lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;log driver&#039;&#039; || syslog &lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;sylog_id&#039;&#039; || roundcube &lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;syslog_facility&#039;&#039; || mailsubsystem &lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;db_dnsw&#039;&#039; || pgsql properties, as described above &lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;imap_host&#039;&#039; || 127.0.0.1 &lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;auto_create_user&#039;&#039; || enabled &lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;smtp_server&#039;&#039; ||  127.0.0.1&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;smtp_port&#039;&#039; ||  25&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;smtp_user/smtp_pass&#039;&#039; ||  enable &#039;&#039;Use Current IMAP username and password for SMTP authentication&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;smtp_log&#039;&#039; ||  enable (optional, but gives additional log record)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The other items can be left at default settings, or adjusted if desired.&lt;br /&gt;
&lt;br /&gt;
* Follow the instructions in step 2 of the install to copy the files to the server&lt;br /&gt;
* You should now be able to get to roundcube at https://host.example.com/roundcube&lt;br /&gt;
&lt;br /&gt;
* After its working, the INSTALL file recommends removing the install directory.&lt;br /&gt;
&lt;br /&gt;
 apk del roundcubemail-installer&lt;br /&gt;
&lt;br /&gt;
* Disable installer mode in /etc/roundcube/main.inc.php file:&lt;br /&gt;
&lt;br /&gt;
 $rcmail_config[&#039;enable_installer&#039;] = false;&lt;br /&gt;
&lt;br /&gt;
* Change the ownership and permissions&lt;br /&gt;
&lt;br /&gt;
 cd /usr/share/webapps/roundcube&lt;br /&gt;
 chown -R root:root LICENSE UPGRADING INSTALL README CHANGELOG&lt;br /&gt;
 chmod -R 600 LICENSE UPGRADING INSTALL README CHANGELOG &lt;br /&gt;
&lt;br /&gt;
* If needed customize logos such as &#039;&#039;&#039;watermark.gif&#039;&#039;&#039;, &#039;&#039;&#039;roundcube_logo.gif&#039;&#039;&#039;, &#039;&#039;&#039;favicon.ico&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* If you would like to disable displaying of standard logos update template files accordingly&lt;br /&gt;
&lt;br /&gt;
* Comment all entries like &#039;&#039;&#039;&amp;lt;div ... img src=&amp;quot;/images/roundcube_logo.png&amp;quot;...&#039;&#039;&#039; in files:&lt;br /&gt;
&lt;br /&gt;
 includes/header.html &lt;br /&gt;
 templates/error.html&lt;br /&gt;
 templates/messageprint.html&lt;br /&gt;
 templates/login.html&lt;br /&gt;
 templates/printmessage.html&lt;br /&gt;
&lt;br /&gt;
* Comment all entries like &#039;&#039;&#039;&amp;lt;img src=&amp;quot;/images/watermark.gif&amp;quot;...&#039;&#039;&#039; in files:&lt;br /&gt;
&lt;br /&gt;
 templates/identities.html&lt;br /&gt;
 templates/messageerror.html&lt;br /&gt;
 watermark.html&lt;br /&gt;
&lt;br /&gt;
==== Enable Plug-ins ====&lt;br /&gt;
&lt;br /&gt;
RoundCube has various useful plug-ins, which could be found in &#039;&#039;/usr/share/webapps/roundcube/plugins&#039;&#039; directory. For example you may want to enable &#039;&#039;password&#039;&#039; plug-in to let users change their passwords directly from RoundCube using an extra Password Tab added to User Settings.&lt;br /&gt;
&lt;br /&gt;
* Grant limited permissions for &#039;&#039;roundcube&#039;&#039; database role &lt;br /&gt;
 psql -U postgres postfix&lt;br /&gt;
   postfix=# GRANT UPDATE (password,modified) ON mailbox TO roundcube;&lt;br /&gt;
   postfix=# GRANT SELECT (username) ON mailbox TO roundcube;&lt;br /&gt;
   postfix=# GRANT INSERT ON log TO roundcube;&lt;br /&gt;
   postfix=# \q&lt;br /&gt;
&lt;br /&gt;
* Setup &#039;&#039;password&#039;&#039; plug-in parameters in &#039;&#039;/usr/share/webapps/roundcube/plugins/password/config.inc.php&#039;&#039;&lt;br /&gt;
 mv /usr/share/webapps/roundcube/plugins/password/config.inc.php.dist /usr/share/webapps/roundcube/plugins/password/config.inc.php&lt;br /&gt;
 vi /usr/share/webapps/roundcube/plugins/password/config.inc.php&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$rcmail_config[&#039;password_minimum_length&#039;] = 7;&lt;br /&gt;
$rcmail_config[&#039;password_require_nonalpha&#039;] = true;&lt;br /&gt;
...&lt;br /&gt;
$rcmail_config[&#039;password_db_dsn&#039;] = &#039;pgsql://roundcube:&amp;lt;roundcube_password&amp;gt;@localhost/postfix&#039;;&lt;br /&gt;
...&lt;br /&gt;
$rcmail_config[&#039;password_query&#039;] = &amp;quot;UPDATE mailbox set password = %c, modified = NOW() where username = %u; INSERT INTO log (timestamp,username,domain,action,data) VALUES (NOW(),%u || &#039; (&#039; || %h || &#039;)&#039;,%d,&#039;edit_password&#039;,%u)&amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Enable &#039;&#039;password&#039;&#039; plug-in&lt;br /&gt;
 vi /usr/share/webapps/roundcube/config/main.inc.php&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
...&lt;br /&gt;
$rcmail_config[&#039;plugins&#039;] = array(&#039;password&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Enable &#039;&#039;create_default_folders&#039;&#039; for RoundCube&lt;br /&gt;
 vi /usr/share/webapps/roundcube/config/main.inc.php&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
...&lt;br /&gt;
$rcmail_config[&#039;create_default_folders&#039;] = TRUE;&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== OpenLDAP based Address Book ===&lt;br /&gt;
&lt;br /&gt;
This OpenLDAP configuration uses the SQL backend, which represents information stored in PostgreSQL as an LDAP subtree for Address Book functionality for email lookups, user authentication or even replication account information between sites. This procedure uses some metainformation to translate LDAP queries to SQL queries, leaving relational schema untouched, which allows SQL and LDAP applications to inter-operate without replication, and exchange data as needed. The SQL backend uses UnixODBC to connect to PostgresSQL. &lt;br /&gt;
&lt;br /&gt;
* Install OpenLDAP and ODBC&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
apk add openldap libldap openldap-back-sql php-ldap unixodbc psqlodbc ca-certificates&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Update &amp;quot;postfix&amp;quot; database (it will add &#039;id&#039; columns to mailbox and domain tables, also will create tables and views to represent LDAP metainformation)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: These instructions are for example domain example.com. So make sure you replaced all entries of &#039;example&#039; and &#039;com&#039; according to your domain name parts.&lt;br /&gt;
&lt;br /&gt;
Put the following into a new file called &#039;&#039;&#039;script&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE domain ADD COLUMN id SERIAL; &lt;br /&gt;
ALTER TABLE mailbox ADD COLUMN id SERIAL; &lt;br /&gt;
&lt;br /&gt;
CREATE TABLE ldap_entry_objclasses (&lt;br /&gt;
    entry_id integer NOT NULL,&lt;br /&gt;
    oc_name character varying(64)&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
CREATE TABLE ldap_oc_mappings (&lt;br /&gt;
    name character varying(64) NOT NULL,&lt;br /&gt;
    keytbl character varying(64) NOT NULL,&lt;br /&gt;
    keycol character varying(64) NOT NULL,&lt;br /&gt;
    create_proc character varying(255),&lt;br /&gt;
    delete_proc character varying(255),&lt;br /&gt;
    expect_return integer NOT NULL&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
ALTER TABLE ldap_oc_mappings ADD COLUMN id SERIAL;&lt;br /&gt;
ALTER TABLE ldap_oc_mappings ADD PRIMARY KEY (id);&lt;br /&gt;
&lt;br /&gt;
CREATE TABLE ldap_attr_mappings (&lt;br /&gt;
    oc_map_id integer NOT NULL REFERENCES ldap_oc_mappings(id),&lt;br /&gt;
    name character varying(255) NOT NULL,&lt;br /&gt;
    sel_expr character varying(255) NOT NULL,&lt;br /&gt;
    sel_expr_u character varying(255),&lt;br /&gt;
    from_tbls character varying(255) NOT NULL,&lt;br /&gt;
    join_where character varying(255),&lt;br /&gt;
    add_proc character varying(255),&lt;br /&gt;
    delete_proc character varying(255),&lt;br /&gt;
    param_order integer NOT NULL,&lt;br /&gt;
    expect_return integer NOT NULL&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
ALTER TABLE ldap_attr_mappings ADD COLUMN id SERIAL;&lt;br /&gt;
ALTER TABLE ldap_attr_mappings ADD PRIMARY KEY (id);&lt;br /&gt;
&lt;br /&gt;
CREATE VIEW ldap_dcs AS&lt;br /&gt;
    ((SELECT (domain.id + 100000) AS id,&lt;br /&gt;
            (&#039;dc=&#039;::text || replace((domain.domain)::text, &#039;.&#039;::text, &#039;,dc=&#039;::text)) AS dn,&lt;br /&gt;
            1 AS oc_map_id,&lt;br /&gt;
            100000 AS parent,&lt;br /&gt;
            0 AS keyval,&lt;br /&gt;
            domain.domain&lt;br /&gt;
     FROM domain&lt;br /&gt;
     WHERE domain.domain &amp;lt;&amp;gt; &#039;ALL&#039;)&lt;br /&gt;
      UNION&lt;br /&gt;
     (SELECT 100000 AS id,&lt;br /&gt;
           (&#039;dc=&#039; || regexp_replace((domain.domain)::text, &#039;.*\\.&#039;, &#039;&#039;::text)) AS dn,&lt;br /&gt;
           1 AS oc_map_id,&lt;br /&gt;
           0 AS parent,&lt;br /&gt;
           0 AS keyval,&lt;br /&gt;
           (regexp_replace((domain.domain)::text, &#039;.*\\.&#039;, &#039;&#039;::text)) AS domain&lt;br /&gt;
      FROM domain&lt;br /&gt;
      WHERE domain.domain &amp;lt;&amp;gt; &#039;ALL&#039;&lt;br /&gt;
      LIMIT 1));&lt;br /&gt;
&lt;br /&gt;
CREATE VIEW ldap_entries AS&lt;br /&gt;
    SELECT mailbox.id,&lt;br /&gt;
    (((&#039;cn=&#039;::text || initcap(replace(split_part((mailbox.username)::text, &#039;@&#039;::text, 1), &#039;.&#039;::text, &#039; &#039;::text))) || &#039;,dc=&#039;::text) ||&lt;br /&gt;
             replace(regexp_replace((mailbox.username)::text, &#039;.*@&#039;, &#039;&#039;::text), &#039;.&#039;::text, &#039;,dc=&#039;::text)) AS dn,&lt;br /&gt;
          1 AS oc_map_id,&lt;br /&gt;
          (SELECT ldap_dcs.id&lt;br /&gt;
           FROM ldap_dcs&lt;br /&gt;
           WHERE ((ldap_dcs.domain)::text = (mailbox.domain)::text)) AS parent,&lt;br /&gt;
           mailbox.id AS keyval&lt;br /&gt;
           FROM mailbox&lt;br /&gt;
           UNION&lt;br /&gt;
           SELECT ldap_dcs.id,&lt;br /&gt;
                  ldap_dcs.dn,&lt;br /&gt;
                  ldap_dcs.oc_map_id,&lt;br /&gt;
                  ldap_dcs.parent,&lt;br /&gt;
                  ldap_dcs.keyval&lt;br /&gt;
           FROM ldap_dcs;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Question to experts: Is this normal to have in this script &amp;quot;WARNING:  nonstandard use of \\ in a string literal&amp;quot;?&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Finally, execute the commands in the file with:&lt;br /&gt;
 cat script | psql -U postfix postfix&lt;br /&gt;
 rm script&lt;br /&gt;
&lt;br /&gt;
* Fill out LDAP tables according to following example (make sure to separate values with TABs):&lt;br /&gt;
&lt;br /&gt;
Put the following into a new file called &#039;&#039;&#039;script&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
COPY ldap_oc_mappings (id, name, keytbl, keycol, create_proc, delete_proc, expect_return) FROM stdin;&lt;br /&gt;
1	exampleBox	mailbox	id	\N	\N	1&lt;br /&gt;
\.&lt;br /&gt;
COPY ldap_attr_mappings (id, oc_map_id, name, sel_expr, sel_expr_u, from_tbls, join_where, add_proc, delete_proc, param_order, expect_return) FROM stdin;&lt;br /&gt;
1	1	displayName	mailbox.name	\N	mailbox	\N	\N	\N	3	0&lt;br /&gt;
2	1	mail	mailbox.username	\N	mailbox	\N	\N	\N	3	0&lt;br /&gt;
3	1	cn	mailbox.name	\N	mailbox	\N	\N	\N	3	0&lt;br /&gt;
4	1	userPassword	&#039;{CRYPT}&#039;||mailbox.password	\N	mailbox	\N	\N	\N	3	0&lt;br /&gt;
\.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally, execute the commands in the file with:&lt;br /&gt;
 cat script | psql -U postfix postfix&lt;br /&gt;
 rm script&lt;br /&gt;
&lt;br /&gt;
* Check that &amp;quot;ldap_dcs&amp;quot; view looks something like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
echo &#039;select * from ldap_dcs&#039; | psql -U postgres postfix&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
   id   |             dn              | oc_map_id | parent | keyval |       domain       &lt;br /&gt;
--------+-----------------------------+-----------+--------+--------+--------------------&lt;br /&gt;
 100000 | dc=com                      |         1 |      0 |      0 | com&lt;br /&gt;
 100001 | dc=example,dc=com           |         1 | 100000 |      0 | example.com&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Check that &amp;quot;ldap_entries&amp;quot; view looks something like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
echo &#039;select * from ldap_entries&#039; | psql -U postgres postfix&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
   id   |                          dn                           | oc_map_id | parent | keyval &lt;br /&gt;
--------+-------------------------------------------------------+-----------+--------+--------&lt;br /&gt;
    1   | cn=address1,dc=example,dc=com                         |         1 | 100001 |    1&lt;br /&gt;
...&lt;br /&gt;
   123  | cn=address123,dc=example,dc=com                       |         1 | 100001 |    1&lt;br /&gt;
 100000 | dc=com                                                |         1 |      0 |    0&lt;br /&gt;
 100001 | dc=example,dc=com                                     |         1 | 100000 |    0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Configure ODBC parameters&lt;br /&gt;
&lt;br /&gt;
Edit /etc/odbc.ini:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[PostgreSQL]&lt;br /&gt;
Description             = Connection to Postgres&lt;br /&gt;
Driver                  = PostgreSQL&lt;br /&gt;
Trace                   = Yes&lt;br /&gt;
TraceFile               = sql.log&lt;br /&gt;
Database                = postfix&lt;br /&gt;
Servername              = 127.0.0.1&lt;br /&gt;
UserName                =&lt;br /&gt;
Password                =&lt;br /&gt;
Port                    = 5432&lt;br /&gt;
Protocol                = 6.4&lt;br /&gt;
ReadOnly                = No&lt;br /&gt;
RowVersining            = No&lt;br /&gt;
ShowSystemTables        = No&lt;br /&gt;
ShowOidColumn           = No&lt;br /&gt;
FakeOidIndex            = No&lt;br /&gt;
ConnSettings            =&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Edit /etc/odbcinst.ini:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[PostgreSQL]&lt;br /&gt;
Description     = PostgreSQL driver for Linux&lt;br /&gt;
Driver          = /usr/lib/psqlodbcw.so&lt;br /&gt;
Setup           = /usr/lib/libodbcpsqlS.so&lt;br /&gt;
FileUsage       = 1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Test ODBC connection&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
echo &amp;quot;select * from domain;&amp;quot; | isql PostgreSQL postgres&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Provide permission to certificate for LDAP server&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown ldap /etc/lighttpd/server-bundle.pem&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Edit LDAP schema&lt;br /&gt;
&lt;br /&gt;
Edit /etc/openldap/schema/example.com.schema:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
attributetype ( 0.9.2342.19200300.100.1.3&lt;br /&gt;
	NAME ( &#039;mail&#039; &#039;rfc822Mailbox&#039; )&lt;br /&gt;
	DESC &#039;RFC1274: RFC822 Mailbox&#039;&lt;br /&gt;
        EQUALITY caseIgnoreIA5Match&lt;br /&gt;
        SUBSTR caseIgnoreIA5SubstringsMatch&lt;br /&gt;
        SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )&lt;br /&gt;
&lt;br /&gt;
attributetype ( 2.16.840.1.113730.3.1.241&lt;br /&gt;
	NAME &#039;displayName&#039;&lt;br /&gt;
	DESC &#039;RFC2798: preferred name to be used when displaying entries&#039;&lt;br /&gt;
	EQUALITY caseIgnoreMatch&lt;br /&gt;
	SUBSTR caseIgnoreSubstringsMatch&lt;br /&gt;
	SYNTAX 1.3.6.1.4.1.1466.115.121.1.15&lt;br /&gt;
	SINGLE-VALUE )&lt;br /&gt;
&lt;br /&gt;
objectclass   ( 2.16.840.1.113730.3.2.2&lt;br /&gt;
        NAME &#039;exampleBox&#039;&lt;br /&gt;
	DESC &#039;example.com mailbox&#039;&lt;br /&gt;
	MUST ( displayName $ mail $ userPassword )&lt;br /&gt;
	)&lt;br /&gt;
&lt;br /&gt;
# RFC 1274 + RFC 2247&lt;br /&gt;
attributetype ( 0.9.2342.19200300.100.1.25&lt;br /&gt;
        NAME ( &#039;dc&#039; &#039;domainComponent&#039; )&lt;br /&gt;
        DESC &#039;RFC1274/2247: domain component&#039;&lt;br /&gt;
        EQUALITY caseIgnoreIA5Match&lt;br /&gt;
        SUBSTR caseIgnoreIA5SubstringsMatch&lt;br /&gt;
        SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )&lt;br /&gt;
&lt;br /&gt;
attributetype ( 2.5.4.46 NAME &#039;dnQualifier&#039;&lt;br /&gt;
        DESC &#039;RFC2256: DN qualifier&#039;&lt;br /&gt;
        EQUALITY caseIgnoreMatch&lt;br /&gt;
        ORDERING caseIgnoreOrderingMatch&lt;br /&gt;
        SUBSTR caseIgnoreSubstringsMatch&lt;br /&gt;
        SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 )&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Configure LDAP server&lt;br /&gt;
&lt;br /&gt;
Edit /etc/openldap/slapd.conf:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
include         /etc/openldap/schema/example.com.schema&lt;br /&gt;
pidfile         /var/run/openldap/slapd.pid&lt;br /&gt;
argsfile        /var/run/openldap/slapd.args&lt;br /&gt;
&lt;br /&gt;
# Uncomment next five TLS... lines if you want to use LDAPs (secured). Probably you don&#039;t want it...&lt;br /&gt;
#TLSCipherSuite HIGH&lt;br /&gt;
#TLSCACertificateFile /etc/lighttpd/ca-crt.pem&lt;br /&gt;
#TLSCertificateFile /etc/lighttpd/server-bundle.pem&lt;br /&gt;
#TLSCertificateKeyFile /etc/lighttpd/server-bundle.pem&lt;br /&gt;
#TLSVerifyClient never &lt;br /&gt;
&lt;br /&gt;
# This is needed for proper representation of MD5-CRYPT format stored in database&lt;br /&gt;
#  see more details in http://strugglers.net/~andy/blog/2010/01/23/openldap-and-md5crypt/&lt;br /&gt;
password-hash  {CRYPT}&lt;br /&gt;
password-crypt-salt-format &amp;quot;$1$%.8s&amp;quot;&lt;br /&gt;
&lt;br /&gt;
loglevel        stats&lt;br /&gt;
moduleload	/usr/lib/openldap/back_sql.so&lt;br /&gt;
sizelimit 3000&lt;br /&gt;
&lt;br /&gt;
database        sql&lt;br /&gt;
&lt;br /&gt;
dbname		PostgreSQL&lt;br /&gt;
dbuser		postfix&lt;br /&gt;
dbpasswd	*****&lt;br /&gt;
&lt;br /&gt;
suffix          &amp;quot;dc=example,dc=com&amp;quot;&lt;br /&gt;
&lt;br /&gt;
upper_func      &amp;quot;upper&amp;quot;&lt;br /&gt;
strcast_func    &amp;quot;text&amp;quot;&lt;br /&gt;
concat_pattern  &amp;quot;?||?&amp;quot;&lt;br /&gt;
has_ldapinfo_dn_ru      no&lt;br /&gt;
lastmod         off&lt;br /&gt;
&lt;br /&gt;
access to attrs=userPassword by * auth&lt;br /&gt;
&lt;br /&gt;
access to * by peername.ip=127.0.0.1 read&lt;br /&gt;
#           by peername.ip=&amp;lt;IP&amp;gt;%&amp;lt;netmask&amp;gt; read&lt;br /&gt;
#           by peername.ip=&amp;lt;IP&amp;gt; read&lt;br /&gt;
	    by users read&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Set permissions for slapd.conf&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown ldap:ldap /etc/openldap/slapd.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Configure startup parameters to make sure that LDAP server start AFTER PostgreSQL and listens on localhost with clear text and public IP with SSL. In case you uncommented TLS lines in slapd.conf use this string: OPTS=&amp;quot;-h &#039;ldaps:// ldap://&#039;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Edit /etc/conf.d/slapd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
rc_need=&amp;quot;postgresql&amp;quot; &lt;br /&gt;
OPTS=&amp;quot;-h &#039;ldap://&#039;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Start LDAP server&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
rc-update add slapd default&lt;br /&gt;
/etc/init.d/slapd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Configure LDAP client utilities. In case you uncommented TLS lines in slapd.conf replace ldap with ldaps&lt;br /&gt;
&lt;br /&gt;
Edit /etc/openldap/ldap.conf&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
BASE	dc=example,dc=com&lt;br /&gt;
URI	ldap://host.example.com&lt;br /&gt;
&lt;br /&gt;
# Uncomment next three TLS... lines if you want to use LDAPs (secured). Probably you don&#039;t want it...&lt;br /&gt;
#TLS_CACERT /etc/lighttpd/ca-crt.pem&lt;br /&gt;
#TLS_CERT /etc/lighttpd/server-bundle.pem&lt;br /&gt;
#TLS_KEY /etc/lighttpd/server-bundle.pem&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Test LDAP server&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ldapsearch -z 3&lt;br /&gt;
ldapsearch -z 3 -x -W -D cn=admin,dc=example,dc=com&lt;br /&gt;
ldapsearch -z 3 -x -W -D cn=address1,dc=example,dc=com&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Configure RoundCube webmail for email lookups&lt;br /&gt;
&lt;br /&gt;
In order to enable php-ldap support you need to restart lighttpd server&lt;br /&gt;
&lt;br /&gt;
 /etc/init.d/lighttpd restart&lt;br /&gt;
&lt;br /&gt;
Edit /etc/roundcube/main.inc.php:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$rcmail_config[&#039;ldap_debug&#039;] = false;&lt;br /&gt;
...&lt;br /&gt;
$rcmail_config[&#039;address_book_type&#039;] = &#039;sql&#039;;&lt;br /&gt;
&lt;br /&gt;
$rcmail_config[&#039;ldap_public&#039;][&#039;example.com&#039;] = array(&lt;br /&gt;
  &#039;name&#039;          =&amp;gt; &#039;example.com&#039;,&lt;br /&gt;
  &#039;hosts&#039;         =&amp;gt; array(&#039;127.0.0.1&#039;),&lt;br /&gt;
  &#039;port&#039;          =&amp;gt; 389,&lt;br /&gt;
  &#039;use_tls&#039;         =&amp;gt; false,&lt;br /&gt;
  &#039;user_specific&#039; =&amp;gt; false,&lt;br /&gt;
  &#039;base_dn&#039;       =&amp;gt; &#039;dc=example,dc=com&#039;,&lt;br /&gt;
  &#039;bind_dn&#039;       =&amp;gt; &#039;&#039;,&lt;br /&gt;
  &#039;bind_pass&#039;     =&amp;gt; &#039;&#039;,&lt;br /&gt;
  &#039;writable&#039;      =&amp;gt; false,&lt;br /&gt;
  &#039;LDAP_Object_Classes&#039; =&amp;gt; array(&amp;quot;top&amp;quot;, &amp;quot;exampleBox&amp;quot;),&lt;br /&gt;
  &#039;required_fields&#039;     =&amp;gt; array(&amp;quot;cn&amp;quot;, &amp;quot;sn&amp;quot;, &amp;quot;mail&amp;quot;),&lt;br /&gt;
  &#039;LDAP_rdn&#039;      =&amp;gt; &#039;mail&#039;,&lt;br /&gt;
  &#039;ldap_version&#039;  =&amp;gt; 3,&lt;br /&gt;
  &#039;search_fields&#039; =&amp;gt; array(&#039;mail&#039;, &#039;cn&#039;, &#039;sn&#039;, &#039;givenName&#039;),&lt;br /&gt;
  &#039;name_field&#039;    =&amp;gt; &#039;cn&#039;,&lt;br /&gt;
  &#039;email_field&#039;   =&amp;gt; &#039;mail&#039;,&lt;br /&gt;
  &#039;surname_field&#039; =&amp;gt; &#039;sn&#039;,&lt;br /&gt;
  &#039;firstname_field&#039; =&amp;gt; &#039;gn&#039;,&lt;br /&gt;
  &#039;sort&#039;          =&amp;gt; &#039;cn&#039;,&lt;br /&gt;
  &#039;scope&#039;         =&amp;gt; &#039;sub&#039;,&lt;br /&gt;
  &#039;filter&#039;        =&amp;gt; &#039;(objectClass=*)&#039;, // Construct here any filter you need&lt;br /&gt;
  &#039;fuzzy_search&#039;  =&amp;gt; true);&lt;br /&gt;
&lt;br /&gt;
$rcmail_config[&#039;autocomplete_addressbooks&#039;] = array(&#039;sql&#039;,&#039;example.com&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Fix PostfixAdmin to work with the new table definition&lt;br /&gt;
&lt;br /&gt;
Edit /var/www/domains/example.com/www/postfixadmin/list-domain.php. Replace the line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
   SELECT domain.* , COUNT( DISTINCT mailbox.username ) AS mailbox_count&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
With the lines:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
   SELECT domain.domain, domain.description, domain.aliases, domain.mailboxes,&lt;br /&gt;
   domain.maxquota, domain.quota, domain.transport, domain.backupmx, domain.created,&lt;br /&gt;
   domain.modified, domain.active, COUNT( DISTINCT mailbox.username ) AS mailbox_count&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== log rotation ==&lt;br /&gt;
&lt;br /&gt;
Ensure the busybox cron service is started and is configured to auto-start:&lt;br /&gt;
&lt;br /&gt;
 /etc/init.d/cron start&lt;br /&gt;
 rc-update add cron default&lt;br /&gt;
&lt;br /&gt;
Add log rotate:&lt;br /&gt;
&lt;br /&gt;
 apk add logrotate&lt;br /&gt;
&lt;br /&gt;
Edit &#039;&#039;/etc/logrotate.conf&#039;&#039; as desired, but the defaults should be sufficient for most people.&lt;br /&gt;
&lt;br /&gt;
== Optional: Configure Web Server Virtual Domains ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; These steps can be done &#039;&#039;in addition to&#039;&#039; the default lighttpd configuration above, which allows you to access the ACF, PostfixAdmin and Roundcube interfaces as subfolders of one web service.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; If you provide SSL access for multiple domain site you may need to follow http://redmine.lighttpd.net/projects/lighttpd/wiki/Docs:SSL#SSL-on-multiple-domains in order to provide multi-domain certificates. If you would like to redirect hosts to their secure equivalents use the following instructions http://redmine.lighttpd.net/projects/lighttpd/wiki/HowToRedirectHttpToHttps.&lt;br /&gt;
&lt;br /&gt;
This server hosts three separate web applications, and these can be handled as three &#039;&#039;different&#039;&#039; virtual domains on the same web server. They will be distinguished by their DNS names, so you can choose domains for the three separate services (or at least the ones you want to publish):&lt;br /&gt;
&lt;br /&gt;
* ACF - Alpine Configuration Framework for managing the server&lt;br /&gt;
* PostfixAdmin - for managing the postfix installation&lt;br /&gt;
* RoundCube - for accessing individual mailboxes&lt;br /&gt;
&lt;br /&gt;
Choose three different domains (from here on known as ACF_DOMAIN, POSTFIXADMIN_DOMAIN, and ROUNDCUBE_DOMAIN) and configure DNS for all three to point to the IP address of your host. These should be DNS &#039;&#039;&#039;A&#039;&#039;&#039; records.&lt;br /&gt;
&lt;br /&gt;
Then, configure lighttpd to handle the three separate domains by editing /etc/lighttpd/lighttpd.conf:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
 $HTTP[&amp;quot;host&amp;quot;] == &amp;quot;ACF_DOMAIN&amp;quot; {&lt;br /&gt;
	simple-vhost.server-root   = &amp;quot;/var/www/domains/&amp;quot;&lt;br /&gt;
	simple-vhost.default-host  = &amp;quot;/ACF_DOMAIN/&amp;quot;&lt;br /&gt;
	simple-vhost.document-root = &amp;quot;www/&amp;quot;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
 $HTTP[&amp;quot;host&amp;quot;] == &amp;quot;POSTFIXADMIN_DOMAIN&amp;quot; {&lt;br /&gt;
	simple-vhost.server-root   = &amp;quot;/var/www/domains/&amp;quot;&lt;br /&gt;
	simple-vhost.default-host  = &amp;quot;/POSTFIXADMIN_DOMAIN/&amp;quot;&lt;br /&gt;
	simple-vhost.document-root = &amp;quot;www/&amp;quot;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
 $HTTP[&amp;quot;host&amp;quot;] == &amp;quot;ROUNDCUBE_DOMAIN&amp;quot; {&lt;br /&gt;
	simple-vhost.server-root   = &amp;quot;/var/www/domains/&amp;quot;&lt;br /&gt;
	simple-vhost.default-host  = &amp;quot;/ROUNDCUBE_DOMAIN/&amp;quot;&lt;br /&gt;
	simple-vhost.document-root = &amp;quot;www/&amp;quot;&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And, then link the appropriate www directories.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
 mkdir -p /var/www/domains/ACF_DOMAIN&lt;br /&gt;
 ln -s /usr/share/acf/www /var/www/domains/ACF_DOMAIN/www&lt;br /&gt;
&lt;br /&gt;
 mkdir -p /var/www/domains/POSTFIXADMIN_DOMAIN&lt;br /&gt;
 ln -s /var/www/domains/host.example.com/www/postfixadmin /var/www/domains/POSTFIXADMIN_DOMAIN/www&lt;br /&gt;
&lt;br /&gt;
 mkdir -p /var/www/domains/ROUNDCUBE_DOMAIN&lt;br /&gt;
 ln -s /usr/share/webapps/roundcube /var/www/domains/ROUNDCUBE_DOMAIN/www&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Optional: Enable compression in Lighttpd ==&lt;br /&gt;
&lt;br /&gt;
* Uncomment &#039;&#039;mod_compress&#039;&#039; and &#039;&#039;mod_setenv&#039;&#039; and modify website section as follows&lt;br /&gt;
&lt;br /&gt;
 mkdir -p /var/lib/lighttpd/cache&lt;br /&gt;
 chown lighttpd:lighttpd  /var/lib/lighttpd/cache&lt;br /&gt;
&lt;br /&gt;
 vi /etc/lighttpd/lighttpd.conf&lt;br /&gt;
&lt;br /&gt;
 ...&lt;br /&gt;
 &amp;quot;mod_setenv&amp;quot;,&lt;br /&gt;
 &amp;quot;mod_compress&amp;quot;,&lt;br /&gt;
 ...&lt;br /&gt;
 $HTTP[&amp;quot;host&amp;quot;] == &amp;quot;ROUNDCUBE_DOMAIN&amp;quot; {&lt;br /&gt;
  ...&lt;br /&gt;
  static-file.etags = &amp;quot;enable&amp;quot;&lt;br /&gt;
  etag.use-mtime = &amp;quot;enable&amp;quot;&lt;br /&gt;
  $HTTP[&amp;quot;url&amp;quot;] =~ &amp;quot;^/(plugins|skins|program)&amp;quot; { setenv.add-response-header  = ( &amp;quot;Cache-Control&amp;quot; =&amp;gt; &amp;quot;public, max-age=2592000&amp;quot;) }&lt;br /&gt;
  compress.cache-dir   = var.statedir + &amp;quot;/cache/compress&amp;quot;&lt;br /&gt;
  compress.filetype = (&amp;quot;text/plain&amp;quot;, &amp;quot;text/html&amp;quot;, &amp;quot;text/javascript&amp;quot;, &amp;quot;text/css&amp;quot;, &amp;quot;text/xml&amp;quot;, &amp;quot;image/gif&amp;quot;, &amp;quot;image/png&amp;quot;)&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
[[Category:Mail]]&lt;br /&gt;
[[Category:PHP]]&lt;br /&gt;
[[Category:SQL]]&lt;br /&gt;
[[Category:ACF]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=Multiple_Instances_of_Services&amp;diff=12890</id>
		<title>Multiple Instances of Services</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=Multiple_Instances_of_Services&amp;diff=12890"/>
		<updated>2016-07-26T23:38:59Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Add ACF category&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Alpine makes it very easy to install and run a service, but what if you need to have multiple instances of the same service running on the same machine. For instance, what if you want to run split-DNS, where you respond to DNS requests differently for internal and external networks. The simple response is to run two instances of tinyDNS listening on two different Ethernet interfaces. Now, how do you do it? How do you manage the two instances once you have them running?&lt;br /&gt;
&lt;br /&gt;
The following example is specifically for TinyDNS, but can be applied to other services as well. TinyDNS will work because the init.d script has been written in such a way as to allow multiple instances. The procedure is written for alpine 1.9, but should work for other versions as well.&lt;br /&gt;
&lt;br /&gt;
== Install The Service ==&lt;br /&gt;
First, we&#039;ll install TinyDNS and the ACF&lt;br /&gt;
&lt;br /&gt;
{{Cmd|apk add acf-tinydns}}&lt;br /&gt;
&lt;br /&gt;
This will install the TinyDNS service, as well as the ACF web interface used to manage the service. Having only one instance of TinyDNS, you can manage it from the console using /etc/init.d/tinydns, /etc/conf.d/tinydns, and /etc/tinydns/ or from ACF using the Networking &amp;gt; DNS pages.&lt;br /&gt;
&lt;br /&gt;
== Create a Second Instance of the Service ==&lt;br /&gt;
Now, we create a symlink to the TinyDNS init.d script for each new instance you want to run. (For alpine 1.8, you should not use the plain tinydns init.d script so that ACF can properly determine which instance is running, but create two new instances instead) We&#039;re going to create a second instance that will handle the internal network.&lt;br /&gt;
&lt;br /&gt;
{{Cmd|ln -s /etc/init.d/tinydns /etc/init.d/tinydns.internal}}&lt;br /&gt;
&lt;br /&gt;
Create conf.d and domain files for each new script. You can copy the tinydns files to start.&lt;br /&gt;
&lt;br /&gt;
{{Cmd|cp /etc/conf.d/tinydns /etc/conf.d/tinydns.internal&lt;br /&gt;
cp -r /etc/tinydns /etc/tinydns.internal}}&lt;br /&gt;
&lt;br /&gt;
You now have a second instance of the TinyDNS service that you can manage from the console using /etc/init.d/tinydns.internal, /etc/conf.d/tinydns.internal, and /etc/tinydns.internal/. But, how do you manage it with ACF?&lt;br /&gt;
&lt;br /&gt;
== Create a Second Instance of the ACF ==&lt;br /&gt;
Set up an ACF controller for the new script. Controller names can only contain letters, numbers, and &#039;_&#039;.&lt;br /&gt;
&lt;br /&gt;
{{Cmd|mkdir /usr/share/acf/app/tinydns_internal}}&lt;br /&gt;
&lt;br /&gt;
Set up the modified ACF code for each new script. Only three files must be modified, the others may be copied or symlink&#039;d. The name of the controller must be changed to allow the ACF permissions of the two TinyDNS instances to be managed separately.&lt;br /&gt;
&lt;br /&gt;
{{Cmd|cd /usr/share/acf/app/tinydns&lt;br /&gt;
for a in tinydns*; do cp $a /usr/share/acf/app/tinydns_internal/$(echo $a {{!}} sed &amp;quot;s/tinydns/tinydns_internal/&amp;quot;); done}}&lt;br /&gt;
&lt;br /&gt;
{{Cmd|vim /usr/share/acf/app/tinydns_internal/tinydns_internal-model.lua}}&lt;br /&gt;
Change &#039;local processname = &amp;quot;tinydns.internal&amp;quot;&#039;&lt;br /&gt;
&lt;br /&gt;
{{Cmd|vim /usr/share/acf/app/tinydns_internal/tinydns_internal.roles}}&lt;br /&gt;
Replace each instance of &#039;tinydns&#039; with &#039;tinydns_internal&#039;&lt;br /&gt;
&lt;br /&gt;
{{Cmd|vim /usr/share/acf/app/tinydns_internal/tinydns_internal.menu}}&lt;br /&gt;
&lt;br /&gt;
Modifiy the main menu entry, ie. replace each instance of &#039;30DNS&#039; with &#039;31DNS_Internal&#039;&lt;br /&gt;
&lt;br /&gt;
Once you log out and log back into ACF, you should see the new ACF pages and menu options available. User and role management can be handled separately for the new instance. (In alpine 1.8, users should not be given access to the plain TinyDNS ACF)&lt;br /&gt;
&lt;br /&gt;
== Save Your Changes ==&lt;br /&gt;
The /usr/share directory is not automatically included in lbu commits, so the new ACF instance must be added manually.&lt;br /&gt;
&lt;br /&gt;
{{Cmd|lbu add /usr/share/acf/app/tinydns_internal}}&lt;br /&gt;
&lt;br /&gt;
Then, commit the changes.&lt;br /&gt;
&lt;br /&gt;
{{Cmd|lbu commit}}&lt;br /&gt;
&lt;br /&gt;
[[Category:Booting]]&lt;br /&gt;
[[Category:ACF]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=Html.lua&amp;diff=12889</id>
		<title>Html.lua</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=Html.lua&amp;diff=12889"/>
		<updated>2016-07-26T23:34:17Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Add ACF category&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=html.lua =&lt;br /&gt;
This is used in the View section of the Model-Controller-View. &lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
== cookie.set ==&lt;br /&gt;
&#039;&#039;&#039;INPUT:&#039;&#039;&#039;&amp;lt;BR&amp;gt;&lt;br /&gt;
This library required the following inputs/parameters.&lt;br /&gt;
* name&lt;br /&gt;
* value&lt;br /&gt;
* path&lt;br /&gt;
&#039;&#039;&#039;OUTPUT:&#039;&#039;&#039;&amp;lt;BR&amp;gt;&lt;br /&gt;
This library deliverers the following output/parameters.&lt;br /&gt;
* String to set the cookie &lt;br /&gt;
&#039;&#039;&#039;CODING EXAMPLE:&#039;&#039;&#039;&lt;br /&gt;
 -- Set variable/Call for this library&lt;br /&gt;
&lt;br /&gt;
==html_escape ==&lt;br /&gt;
&#039;&#039;&#039;INPUT:&#039;&#039;&#039;&amp;lt;BR&amp;gt;&lt;br /&gt;
This library required the following inputs/parameters.&lt;br /&gt;
*text-string&lt;br /&gt;
&#039;&#039;&#039;OUTPUT:&#039;&#039;&#039;&amp;lt;BR&amp;gt;&lt;br /&gt;
This library deliverers the following output/parameters.&lt;br /&gt;
* String with &amp;amp;,&amp;gt;,&amp;lt; signs encoded&lt;br /&gt;
&#039;&#039;&#039;CODING EXAMPLE:&#039;&#039;&#039;&lt;br /&gt;
 -- Set variable/Call for this library&lt;br /&gt;
 bobo = require &amp;quot;html&amp;quot;&lt;br /&gt;
 format = bobo.html_escape(&amp;quot;This is &amp;gt; a test &amp;lt; string &amp;amp;&amp;quot;)&lt;br /&gt;
 print(format)&lt;br /&gt;
This is &amp;amp;gt; a test &amp;amp;lt; string &amp;amp;amp;&lt;br /&gt;
&lt;br /&gt;
==nv_pair==&lt;br /&gt;
&#039;&#039;&#039;INPUT:&#039;&#039;&#039;&amp;lt;BR&amp;gt;&lt;br /&gt;
This library required the following inputs/parameters.&lt;br /&gt;
*name&lt;br /&gt;
*value&lt;br /&gt;
&#039;&#039;&#039;OUTPUT:&#039;&#039;&#039;&amp;lt;BR&amp;gt;&lt;br /&gt;
This library deliverers the following output/parameters.&lt;br /&gt;
* name=&amp;quot;value&amp;quot;&lt;br /&gt;
&#039;&#039;&#039;CODING EXAMPLE:&#039;&#039;&#039;&lt;br /&gt;
 -- Set variable/Call for this library&lt;br /&gt;
 bobo = require &amp;quot;html&amp;quot;&lt;br /&gt;
 format = bobo.nv_pair(&amp;quot;foo&amp;quot;, &amp;quot;bar&amp;quot;)&lt;br /&gt;
 print(format)&lt;br /&gt;
 foo=&amp;quot;bar&amp;quot;&lt;br /&gt;
&lt;br /&gt;
==cfe_unpack ==&lt;br /&gt;
&#039;&#039;&#039;INPUT:&#039;&#039;&#039;&amp;lt;BR&amp;gt;&lt;br /&gt;
This library required the following inputs/parameters.&lt;br /&gt;
*Give a cfe table&lt;br /&gt;
&#039;&#039;&#039;OUTPUT:&#039;&#039;&#039;&amp;lt;BR&amp;gt;&lt;br /&gt;
This library deliverers the following output/parameters.&lt;br /&gt;
*String with the whole cfe table&lt;br /&gt;
&#039;&#039;&#039;CODING EXAMPLE:&#039;&#039;&#039;&lt;br /&gt;
 -- Set variable/Call for this library&lt;br /&gt;
 --haserl code below. Used in views in ACF&lt;br /&gt;
 &amp;lt;? require &amp;quot;html ?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;?= html.cfe(form) ?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==form.longtext ==&lt;br /&gt;
&#039;&#039;&#039;INPUT:&#039;&#039;&#039;&amp;lt;BR&amp;gt;&lt;br /&gt;
This library required the following inputs/parameters.&lt;br /&gt;
*Needs a cfe table&lt;br /&gt;
&#039;&#039;&#039;OUTPUT:&#039;&#039;&#039;&amp;lt;BR&amp;gt;&lt;br /&gt;
This library deliverers the following output/parameters.&lt;br /&gt;
*Html code for a &amp;lt;textarea&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;CODING EXAMPLE:&#039;&#039;&#039;&lt;br /&gt;
 -- Set variable/Call for this library&lt;br /&gt;
 --your model may return something like this&lt;br /&gt;
 return (cfe{value=status, name=&amp;quot;inputarea&amp;quot;, type=&amp;quot;longtext&amp;quot;, rows=&amp;quot;30&amp;quot;, cols=&amp;quot;20&amp;quot; })&lt;br /&gt;
 --your cfe will look something like this&lt;br /&gt;
 { value=status, type=&amp;quot;longtext&amp;quot;, options=&amp;quot;&amp;quot;, errtxt=&amp;quot;&amp;quot; , rows=&amp;quot;30, cols=&amp;quot;20&amp;quot;}&lt;br /&gt;
 --haserl code below. Used in views in ACF&lt;br /&gt;
 &lt;br /&gt;
 &amp;lt;? require &amp;quot;html&amp;quot; ?&amp;gt;&lt;br /&gt;
 &amp;lt;?= html.form[form.value.inputarea.type](form.value.inputarea) ?&amp;gt;&lt;br /&gt;
 --this way will only have to touch model/controller if change in code. Very rarely the view.&lt;br /&gt;
&lt;br /&gt;
==form.passwd ==&lt;br /&gt;
&#039;&#039;&#039;INPUT:&#039;&#039;&#039;&amp;lt;BR&amp;gt;&lt;br /&gt;
This library required the following inputs/parameters.&lt;br /&gt;
*Needs a cfe table&lt;br /&gt;
&#039;&#039;&#039;OUTPUT:&#039;&#039;&#039;&amp;lt;BR&amp;gt;&lt;br /&gt;
This library deliverers the following output/parameters.&lt;br /&gt;
*Html code for a &amp;lt;input type=password&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;CODING EXAMPLE:&#039;&#039;&#039;&lt;br /&gt;
 -- Set variable/Call for this library&lt;br /&gt;
 --your model may return something like this&lt;br /&gt;
 return (cfe{value=status, name=&amp;quot;inputarea&amp;quot;, type=&amp;quot;passwd&amp;quot; })&lt;br /&gt;
 --your cfe will look something like this&lt;br /&gt;
 { value=status, type=&amp;quot;passwd&amp;quot;, options=&amp;quot;&amp;quot;, errtxt=&amp;quot;&amp;quot; }&lt;br /&gt;
 --haserl code below. Used in views in ACF&lt;br /&gt;
 &lt;br /&gt;
 &amp;lt;? require &amp;quot;html&amp;quot; ?&amp;gt;&lt;br /&gt;
 &amp;lt;?= html.form[form.value.inputarea.type](form.value.inputarea) ?&amp;gt;&lt;br /&gt;
 --this way will only have to touch model/controller if change in code. Very rarely the view.&lt;br /&gt;
&lt;br /&gt;
==form.hidden==&lt;br /&gt;
&#039;&#039;&#039;INPUT:&#039;&#039;&#039;&amp;lt;BR&amp;gt;&lt;br /&gt;
This library required the following inputs/parameters.&lt;br /&gt;
*Needs a cfe table&lt;br /&gt;
&#039;&#039;&#039;OUTPUT:&#039;&#039;&#039;&amp;lt;BR&amp;gt;&lt;br /&gt;
This library deliverers the following output/parameters.&lt;br /&gt;
*Html code for a &amp;lt;input type=hidden&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;CODING EXAMPLE:&#039;&#039;&lt;br /&gt;
 -- Set variable/Call for this library&lt;br /&gt;
 --your model may return something like this&lt;br /&gt;
 return (cfe{value=status, name=&amp;quot;inputarea&amp;quot;, type=&amp;quot;hidden&amp;quot; })&lt;br /&gt;
 --your cfe will look something like this&lt;br /&gt;
 { value=status, type=&amp;quot;hidden&amp;quot;, options=&amp;quot;&amp;quot;, errtxt=&amp;quot;&amp;quot; }&lt;br /&gt;
 --haserl code below. Used in views in ACF&lt;br /&gt;
 &lt;br /&gt;
 &amp;lt;? require &amp;quot;html&amp;quot; ?&amp;gt;&lt;br /&gt;
 &amp;lt;?= html.form[form.value.inputarea.type](form.value.inputarea) ?&amp;gt;&lt;br /&gt;
 --this way will only have to touch model/controller if change in code. Very rarely the view.&lt;br /&gt;
&lt;br /&gt;
==form.submit==&lt;br /&gt;
&#039;&#039;&#039;INPUT:&#039;&#039;&#039;&amp;lt;BR&amp;gt;&lt;br /&gt;
This library required the following inputs/parameters.&lt;br /&gt;
*Needs a cfe table&lt;br /&gt;
&#039;&#039;&#039;OUTPUT:&#039;&#039;&#039;&amp;lt;BR&amp;gt;&lt;br /&gt;
This library deliverers the following output/parameters.&lt;br /&gt;
*Html code for a &amp;lt;input type=submit&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;CODING EXAMPLE:&#039;&#039;&#039;&lt;br /&gt;
 -- Set variable/Call for this library&lt;br /&gt;
 --your model may return something like this&lt;br /&gt;
 return (cfe{value=status, name=&amp;quot;inputarea&amp;quot;, type=&amp;quot;submit&amp;quot; })&lt;br /&gt;
 --your cfe will look something like this&lt;br /&gt;
 { value=status, type=&amp;quot;submit&amp;quot;, options=&amp;quot;&amp;quot;, errtxt=&amp;quot;&amp;quot; }&lt;br /&gt;
 --haserl code below. Used in views in ACF&lt;br /&gt;
 &lt;br /&gt;
 &amp;lt;? require &amp;quot;html&amp;quot; ?&amp;gt;&lt;br /&gt;
 &amp;lt;?= html.form[form.value.inputarea.type](form.value.inputarea) ?&amp;gt;&lt;br /&gt;
 --this way will only have to touch model/controller if change in code. Very rarely the view.&lt;br /&gt;
&lt;br /&gt;
==form.action ==&lt;br /&gt;
&#039;&#039;&#039;INPUT:&#039;&#039;&#039;&amp;lt;BR&amp;gt;&lt;br /&gt;
This library required the following inputs/parameters.&lt;br /&gt;
*Needs a cfe table&lt;br /&gt;
&#039;&#039;&#039;OUTPUT:&#039;&#039;&#039;&amp;lt;BR&amp;gt;&lt;br /&gt;
This library deliverers the following output/parameters.&lt;br /&gt;
*Html code for a &amp;lt;input type=submit&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;CODING EXAMPLE:&#039;&#039;&#039;&lt;br /&gt;
 -- Set variable/Call for this library&lt;br /&gt;
 --your model may return something like this&lt;br /&gt;
 return (cfe{value=status, name=&amp;quot;inputarea&amp;quot;, type=&amp;quot;submit&amp;quot; })&lt;br /&gt;
 --your cfe will look something like this&lt;br /&gt;
 { value=status, type=&amp;quot;submit&amp;quot;, options=&amp;quot;&amp;quot;, errtxt=&amp;quot;&amp;quot; }&lt;br /&gt;
 --haserl code below. Used in views in ACF&lt;br /&gt;
 &lt;br /&gt;
 &amp;lt;? require &amp;quot;html&amp;quot; ?&amp;gt;&lt;br /&gt;
 &amp;lt;?= html.form[form.value.inputarea.type](form.value.inputarea) ?&amp;gt;&lt;br /&gt;
 --this way will only have to touch model/controller if change in code. Very rarely the view.&lt;br /&gt;
&lt;br /&gt;
==form.file ==&lt;br /&gt;
&#039;&#039;&#039;INPUT:&#039;&#039;&#039;&amp;lt;BR&amp;gt;&lt;br /&gt;
This library required the following inputs/parameters.&lt;br /&gt;
*Needs a cfe table&lt;br /&gt;
&#039;&#039;&#039;OUTPUT:&#039;&#039;&#039;&amp;lt;BR&amp;gt;&lt;br /&gt;
This library deliverers the following output/parameters.&lt;br /&gt;
*Html code for a &amp;lt;input type=file&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;CODING EXAMPLE:&#039;&#039;&#039;&lt;br /&gt;
 -- Set variable/Call for this library&lt;br /&gt;
 --your model may return something like this&lt;br /&gt;
 return (cfe{value=status, name=&amp;quot;inputarea&amp;quot;, type=&amp;quot;file&amp;quot; })&lt;br /&gt;
 --your cfe will look something like this&lt;br /&gt;
 { value=status, type=&amp;quot;file&amp;quot;, options=&amp;quot;&amp;quot;, errtxt=&amp;quot;&amp;quot; }&lt;br /&gt;
 --haserl code below. Used in views in ACF&lt;br /&gt;
 &lt;br /&gt;
 &amp;lt;? require &amp;quot;html&amp;quot; ?&amp;gt;&lt;br /&gt;
 &amp;lt;?= html.form[form.value.inputarea.type](form.value.inputarea) ?&amp;gt;&lt;br /&gt;
 --this way will only have to touch model/controller if change in code. Very rarely the view.&lt;br /&gt;
&lt;br /&gt;
==form.image==&lt;br /&gt;
&#039;&#039;&#039;INPUT:&#039;&#039;&#039;&amp;lt;BR&amp;gt;&lt;br /&gt;
This library required the following inputs/parameters.&lt;br /&gt;
*Needs a cfe table&lt;br /&gt;
&#039;&#039;&#039;OUTPUT:&#039;&#039;&#039;&amp;lt;BR&amp;gt;&lt;br /&gt;
This library deliverers the following output/parameters.&lt;br /&gt;
*Html code for a &amp;lt;input type=image&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;CODING EXAMPLE:&#039;&#039;&#039;&lt;br /&gt;
 -- Set variable/Call for this library&lt;br /&gt;
 --your model may return something like this&lt;br /&gt;
 return (cfe{value=status, name=&amp;quot;inputarea&amp;quot;, type=&amp;quot;image&amp;quot; })&lt;br /&gt;
 --your cfe will look something like this&lt;br /&gt;
 { value=status, type=&amp;quot;image&amp;quot;, options=&amp;quot;&amp;quot;, errtxt=&amp;quot;&amp;quot; }&lt;br /&gt;
 --haserl code below. Used in views in ACF&lt;br /&gt;
 &lt;br /&gt;
 &amp;lt;? require &amp;quot;html&amp;quot; ?&amp;gt;&lt;br /&gt;
 &amp;lt;?= html.form[form.value.inputarea.type](form.value.inputarea) ?&amp;gt;&lt;br /&gt;
 --this way will only have to touch model/controller if change in code. Very rarely the view.&lt;br /&gt;
&lt;br /&gt;
==form.select==&lt;br /&gt;
&#039;&#039;&#039;INPUT:&#039;&#039;&#039;&amp;lt;BR&amp;gt;&lt;br /&gt;
This library required the following inputs/parameters.&lt;br /&gt;
*Needs a cfe table&lt;br /&gt;
&#039;&#039;&#039;OUTPUT:&#039;&#039;&#039;&amp;lt;BR&amp;gt;&lt;br /&gt;
This library deliverers the following output/parameters.&lt;br /&gt;
*Html code for a &amp;lt;select&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;CODING EXAMPLE:&#039;&#039;&#039;&lt;br /&gt;
 -- Set variable/Call for this library&lt;br /&gt;
 --your model may return something like this&lt;br /&gt;
 return (cfe{value=status, name=&amp;quot;inputarea&amp;quot;, type=&amp;quot;select&amp;quot; option= {&amp;quot;yes&amp;quot;, &amp;quot;no&amp;quot;, &amp;quot;maybe&amp;quot;, &amp;quot;so&amp;quot; } })&lt;br /&gt;
 --your cfe will look something like this&lt;br /&gt;
 { value=status, type=&amp;quot;select&amp;quot;, options=&amp;quot;&amp;quot;, errtxt=&amp;quot;&amp;quot; option= {&amp;quot;yes&amp;quot;, &amp;quot;no&amp;quot;, &amp;quot;maybe&amp;quot;, &amp;quot;so&amp;quot;} }&lt;br /&gt;
 --haserl code below. Used in views in ACF&lt;br /&gt;
 &lt;br /&gt;
 &amp;lt;? require &amp;quot;html&amp;quot; ?&amp;gt;&lt;br /&gt;
 &amp;lt;?= html.form[form.value.inputarea.type](form.value.inputarea) ?&amp;gt;&lt;br /&gt;
 --this way will only have to touch model/controller if change in code. Very rarely the view.&lt;br /&gt;
&lt;br /&gt;
==form.checkbox==&lt;br /&gt;
&#039;&#039;&#039;INPUT:&#039;&#039;&#039;&amp;lt;BR&amp;gt;&lt;br /&gt;
This library required the following inputs/parameters.&lt;br /&gt;
*Needs a cfe table&lt;br /&gt;
&#039;&#039;&#039;OUTPUT:&#039;&#039;&#039;&amp;lt;BR&amp;gt;&lt;br /&gt;
This library deliverers the following output/parameters.&lt;br /&gt;
*Html code for a &amp;lt;input type=checkbox&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;CODING EXAMPLE:&#039;&#039;&#039;&lt;br /&gt;
 -- Set variable/Call for this library&lt;br /&gt;
 --your model may return something like this&lt;br /&gt;
 return (cfe{value=status, name=&amp;quot;inputarea&amp;quot;, type=&amp;quot;checkbox&amp;quot; })&lt;br /&gt;
 --your cfe will look something like this&lt;br /&gt;
 { value=status, type=&amp;quot;checkbox&amp;quot;, options=&amp;quot;&amp;quot;, errtxt=&amp;quot;&amp;quot; }&lt;br /&gt;
 --haserl code below. Used in views in ACF&lt;br /&gt;
 &lt;br /&gt;
 &amp;lt;? require &amp;quot;html&amp;quot; ?&amp;gt;&lt;br /&gt;
 &amp;lt;?= html.form[form.value.inputarea.type](form.value.inputarea) ?&amp;gt;&lt;br /&gt;
 --this way will only have to touch model/controller if change in code. Very rarely the view.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==link==&lt;br /&gt;
&#039;&#039;&#039;INPUT:&#039;&#039;&#039;&amp;lt;BR&amp;gt;&lt;br /&gt;
This library required the following inputs/parameters.&lt;br /&gt;
*A table&lt;br /&gt;
&#039;&#039;&#039;OUTPUT:&#039;&#039;&#039;&amp;lt;BR&amp;gt;&lt;br /&gt;
This library deliverers the following output/parameters.&lt;br /&gt;
*Html code for a &amp;lt;input type=password&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;CODING EXAMPLE:&#039;&#039;&#039;&lt;br /&gt;
 -- Set variable/Call for this library&lt;br /&gt;
 --your model may return something like this&lt;br /&gt;
 url=&amp;quot;http://localhost/cgi-bin/acf/&amp;quot;&lt;br /&gt;
 &amp;lt;? require &amp;quot;html&amp;quot; ?&amp;gt;&lt;br /&gt;
 &amp;lt;?= html.link{value = url .. varible.url, label=&amp;quot;Download&amp;quot;}?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:Lua]]&lt;br /&gt;
[[Category:ACF]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=Algo_8180_Provisioning_With_ACF&amp;diff=12888</id>
		<title>Algo 8180 Provisioning With ACF</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=Algo_8180_Provisioning_With_ACF&amp;diff=12888"/>
		<updated>2016-07-26T23:27:00Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Add ACF category&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Draft}}&lt;br /&gt;
= Overview =&lt;br /&gt;
This page describes how to provision [http://algosolutions.com/products/Audible-and-Visual-Alerting/8180-sip-audio-alerter.html Algo 8180] devices using [[SIP Provisioning On Alpine Linux]] based upon the ACF web interface. It is supported starting with Alpine Linux 3.2.&lt;br /&gt;
&lt;br /&gt;
= Setup Procedure=&lt;br /&gt;
== 1. Install provisioning server ==&lt;br /&gt;
First, install the provisioning server according to the instructions at [[SIP Provisioning On Alpine Linux | this page]].&lt;br /&gt;
== 2. Install firmware package ==&lt;br /&gt;
The latest supported firmware is included in a package:&lt;br /&gt;
 apk add acf-provisioning-algo&lt;br /&gt;
{{Note|If you are running-from-RAM, it is recommended to mount /var/ to a hard disk to prevent data loss. If you do so, you should fetch the firmware packages, rather than install them. This way, the firmware will not take up RAM. You can use the following command: apk fetch --quiet --stdout acf-provisioning-algo &amp;amp;#124; tar -C / -zvx}}&lt;br /&gt;
== 3. Configure init.cfg ==&lt;br /&gt;
The Algo will override ALL parameters when it is provisioned. If parameters are not provided by the provisioning server, they will be reset to default. Since not all parameters are included in the provisioning database, they must be configured statically in &#039;&#039;/var/www/provisioning/htdocs/Algo/init.cfg&#039;&#039;. You can use &#039;&#039;/var/www/provisioning/htdocs/Algo/init.cfg.sample&#039;&#039; as a starting point. You will probably need to modify the network configuration and provisioning server IP.&lt;br /&gt;
 cp /var/www/provisioning/htdocs/Algo/init.cfg.sample /var/www/provisioning/htdocs/Algo/init.cfg&lt;br /&gt;
== 4. Configure the Algo 8180 to connect to the provisioning server ==&lt;br /&gt;
Unfortunately, the DHCP option 66 does not seem to work in our testing. So, you must manually configure the Algo to connect to the provisioning server. Once you get the Algo device connected to the network, follow these steps:&lt;br /&gt;
*MENU Advanced Settings &amp;gt; Provisioning&lt;br /&gt;
**Provisioning Mode: Enabled&lt;br /&gt;
**Server Method: Static&lt;br /&gt;
**Static Server: Enter the IP address or FQDN of the provisioning server&lt;br /&gt;
**Download Method: HTTP&lt;br /&gt;
**Config Download Path: Algo&lt;br /&gt;
**Firmware Download Path: Algo&lt;br /&gt;
&lt;br /&gt;
[[Category:ACF]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=Freeswitch_Voicemail_On_Alpine_Linux&amp;diff=12883</id>
		<title>Freeswitch Voicemail On Alpine Linux</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=Freeswitch_Voicemail_On_Alpine_Linux&amp;diff=12883"/>
		<updated>2016-07-13T17:45:23Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Add instructions to record in mp3 format&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Overview =&lt;br /&gt;
This page describes how to install a basic voicemail server based upon Freeswitch including a web interface based upon ACF. It is built and tested on Alpine Linux 2.7, 3.0, 3.1, and the future 3.2.&lt;br /&gt;
&lt;br /&gt;
= Setup Procedure=&lt;br /&gt;
== 1. Configure the machine ==&lt;br /&gt;
Install and configure a basic Alpine Linux server with the ACF web interface.&lt;br /&gt;
 setup-alpine&lt;br /&gt;
 setup-acf&lt;br /&gt;
&lt;br /&gt;
== 2. Install Freeswitch ==&lt;br /&gt;
 apk add freeswitch freeswitch-sounds-en-us-callie-8000 freeswitch-flite acf-freeswitch acf-freeswitch-vmail&lt;br /&gt;
&lt;br /&gt;
== 3. Configure Freeswitch ==&lt;br /&gt;
We will use the Freeswitch sample configuration for the purposes of this document. It is not recommended to use this config in production, but it will allow us to quickly get voicemail up and running. Rather than installing the package, we will use apk to fetch the files.&lt;br /&gt;
 apk fetch --quiet --stdout freeswitch-sample-config | tar -C / -zvx&lt;br /&gt;
Modify the default password to avoid warnings and delays (obviously, you can choose something other than 12345 if you want)&lt;br /&gt;
 sed -i s/&amp;quot;default_password=1234&amp;quot;/&amp;quot;default_password=12345&amp;quot;/ /etc/freeswitch/vars.xml&lt;br /&gt;
Use Freeswitch XML curl to call into ACF to determine voicemail accounts. To do so, edit &#039;&#039;/etc/freeswitch/autoload_configs/xml_curl.conf.xml&#039;&#039; and add the following bindings:&lt;br /&gt;
     &amp;lt;binding name=&amp;quot;voicemaildialplan&amp;quot;&amp;gt;&lt;br /&gt;
       &amp;lt;param name=&amp;quot;gateway-url&amp;quot; value=&amp;quot;https://127.0.0.1/cgi-bin/acf/freeswitch-vmail/vmail/processdialplanxml&amp;quot; bindings=&amp;quot;dialplan&amp;quot;/&amp;gt;&lt;br /&gt;
     &amp;lt;/binding&amp;gt;&lt;br /&gt;
     &amp;lt;binding name=&amp;quot;voicemaildirectory&amp;quot;&amp;gt;&lt;br /&gt;
       &amp;lt;param name=&amp;quot;gateway-url&amp;quot; value=&amp;quot;https://127.0.0.1/cgi-bin/acf/freeswitch-vmail/vmail/processdirectoryxml&amp;quot; bindings=&amp;quot;directory&amp;quot;/&amp;gt;&lt;br /&gt;
     &amp;lt;/binding&amp;gt;&lt;br /&gt;
And modify &#039;&#039;/etc/freeswitch/autoload_configs/modules.conf.xml&#039;&#039; to load the mod_xml_curl module&lt;br /&gt;
 &amp;lt;load module=&amp;quot;mod_xml_curl&amp;quot;/&amp;gt;&lt;br /&gt;
{{Note|These following items are not needed for Alpine Linux 3.2 or later. They are needed on earlier versions due to bad default paths in the Freeswitch package.}}&lt;br /&gt;
Fix the voicemail storage directory in &#039;&#039;/etc/freeswitch/autoload_configs/voicemail.conf.xml&#039;&#039; to point to &#039;&#039;/var/lib/freeswitch/voicemail&#039;&#039;&lt;br /&gt;
 &amp;lt;param name=&amp;quot;storage-dir&amp;quot; value=&amp;quot;/var/lib/freeswitch/voicemail&amp;quot;/&amp;gt;&lt;br /&gt;
Set the sounds directory in &#039;&#039;/etc/freeswitch/vars.xml&#039;&#039; by adding the following line:&lt;br /&gt;
 &amp;lt;X-PRE-PROCESS cmd=&amp;quot;set&amp;quot; data=&amp;quot;sounds_dir=/usr/share/freeswitch/sounds&amp;quot;/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4. Start Freeswitch ==&lt;br /&gt;
Start Freeswitch and add it to the default runlevel.&lt;br /&gt;
 rc-update add freeswitch&lt;br /&gt;
 /etc/init.d/freeswitch start&lt;br /&gt;
You should now be able to dial into Freeswitch voicemail by registering a SIP user agent to one of the Freeswitch default local extensions (user=1000-1019, password=12345 as defined above) and directing a SIP call to any of the other extensions between 1000 and 1019, or the extension 4000. For each voicemail account, the default password is the same as the extension. For more details on this, please refer to Freeswitch documentation. Please keep in mind that these extensions and voicemail are part of the sample config and are not managed by ACF. We will add ACF-managed voicemail accounts below.&lt;br /&gt;
&lt;br /&gt;
== 5. Configure acf-freeswitch-vmail ==&lt;br /&gt;
Add the voicemail authenticator to the ACF configuration. This will allow voicemail users to log into ACF to check their voicemail.&lt;br /&gt;
 echo &amp;quot;authenticator = authenticator-freeswitch-vmail.lua,authenticator-plaintext&amp;quot; &amp;gt;&amp;gt; /etc/acf/acf.conf&lt;br /&gt;
Change the ACF voicemail domain to match the domain from the sample config. You should use the IP address of your voicemail server, as this is what the sample config uses.&lt;br /&gt;
 sed -i /^domain=/d /etc/freeswitchvmail.conf&lt;br /&gt;
 echo &amp;quot;domain=IPADDRESS&amp;quot; &amp;gt;&amp;gt; /etc/freeswitchvmail.conf&lt;br /&gt;
&lt;br /&gt;
= ACF Web interface =&lt;br /&gt;
You can now browse to https://IPADDRESS to access the web interface. To log in for administration, use the root user and the system root password (this is the default configuration set up by setup-acf).&lt;br /&gt;
== Users ==&lt;br /&gt;
The Voicemail | Users tab will display all of the voicemail users that are managed through the ACF web interface. At the start, this will be empty. When you create Users in the ACF, it will modify the Freeswitch dialplan and directory (using xml_curl) to allow these users to receive and retrieve voicemail. It will also allow these users log into the ACF interface to check their voicemail and modify voicemail settings. The password defined here will control both ACF and IVR access.&lt;br /&gt;
== Voicemail ==&lt;br /&gt;
The Voicemail | Voicemail tab will allow you to view, listen to, download, forward, and delete voicemail messages.&lt;br /&gt;
== Config ==&lt;br /&gt;
The Voicemail | Config tab allows administrators to modify the acf-freeswitch-vmail configuration.&lt;br /&gt;
== Settings ==&lt;br /&gt;
The Voicemail | Settings tab allows voicemail users to modify their settings.&lt;br /&gt;
&lt;br /&gt;
= Demonstration =&lt;br /&gt;
We are now ready to create voicemail accounts with ACF. The Freeswitch sample config creates a fairly full dialplan and we do not want to stomp on any of its features, so we will demonstrate accounts in the 2100 - 2199 range, which is unused. Once again, to dial into Freeswitch you can use one of the Freeswitch default local extensions as explained above.&lt;br /&gt;
# Dial extension 2100 to verify what happens when an unsupported extension is called&lt;br /&gt;
# Logged into ACF as root, create a voicemail user with extension 2100 and some password&lt;br /&gt;
# Dial extension 2100 again to verify that you can now leave a voicemail for extension 2100&lt;br /&gt;
# Dial extension 4000 and log in as user 2100 with the password you defined above to listen to the message&lt;br /&gt;
# Log out of ACF as root and log in again as user 2100 with the password you defined above to view your message&lt;br /&gt;
&lt;br /&gt;
= Other Features =&lt;br /&gt;
== Switching From wav to mp3 Recording (Recommended) ==&lt;br /&gt;
{{Note|The mp3 format is supported in acf-freeswitch-vmail-0.6.2 and later, available on Alpine Linux 3.2 or later.}}&lt;br /&gt;
By default, Freeswitch will store voicemail recordings in wav format. Freeswitch also has the option of storing the recordings in mp3 format if mod_shout is installed. As mp3 format requires less storage/bandwidth and enjoys better support by web browsers, I would recommend making this change:&lt;br /&gt;
=== 1. Add the mod_shout module ===&lt;br /&gt;
Modify &#039;&#039;/etc/freeswitch/autoload_configs/modules.conf.xml&#039;&#039; to load the mod_shout module&lt;br /&gt;
 &amp;lt;load module=&amp;quot;mod_shout&amp;quot;/&amp;gt;&lt;br /&gt;
=== 2. Change Record Format to mp3 ===&lt;br /&gt;
 sed -i s/&#039;name=&amp;quot;file-extension&amp;quot; value=&amp;quot;wav&amp;quot;&#039;/&#039;name=&amp;quot;file-extension&amp;quot; value=&amp;quot;mp3&amp;quot;&#039;/ /etc/freeswitch/autoload_configs/voicemail.conf.xml&lt;br /&gt;
=== 3. Restart Freeswitch ===&lt;br /&gt;
 /etc/init.d/freeswitch restart&lt;br /&gt;
== Using PostgreSQL ==&lt;br /&gt;
Freeswitch-1.2.5 and acf-freeswitch-vmail-0.5.0 fully support Postgresql as the voicemail database. These versions are available in Alpine Linux 3.2 and later. To configure it:&lt;br /&gt;
=== 1. Install Postgresql ===&lt;br /&gt;
 apk add postgresql acf-postgresql&lt;br /&gt;
=== 2. Setup and Start Postgresql ===&lt;br /&gt;
 rc-update add postgresql&lt;br /&gt;
 /etc/init.d/postgresql setup&lt;br /&gt;
 /etc/init.d/postgresql start&lt;br /&gt;
=== 3. Create the Database ===&lt;br /&gt;
 psql -U postgres -c &amp;quot;CREATE DATABASE voicemail&amp;quot;&lt;br /&gt;
=== 4. Stop Freeswitch ===&lt;br /&gt;
 /etc/init.d/freeswitch stop&lt;br /&gt;
{{Note|Freeswitch does not always stop properly. You might need to run &#039;ps ax&#039; to find the process and &#039;kill&#039; it. Running &#039;/etc/init.d/freeswitch zap&#039; might also be necessary.}}&lt;br /&gt;
=== 5. Configure Freeswitch and acf-freeswitch-vmail ===&lt;br /&gt;
Edit /etc/freeswitch/autoload_configs/voicemail.conf.xml to set the odbc-dsn param to &amp;quot;pgsql://hostaddr=127.0.0.1 dbname=voicemail user=postgres password= options=&#039;&#039;&amp;quot;&lt;br /&gt;
 &amp;lt;param name=&amp;quot;odbc-dsn&amp;quot; value=&amp;quot;pgsql://hostaddr=127.0.0.1 dbname=voicemail user=postgres password= options=&#039;&#039;&amp;quot; /&amp;gt;&lt;br /&gt;
Edit /etc/freeswitchvmail.conf and set the dsn param to &amp;quot;pgsql://hostaddr=127.0.0.1 dbname=voicemail user=postgres password= options=&#039;&#039;&amp;quot;&lt;br /&gt;
 dsn=pgsql://hostaddr=127.0.0.1 dbname=voicemail user=postgres password= options=&#039;&#039;&lt;br /&gt;
=== 6. Restart Freeswitch ===&lt;br /&gt;
 /etc/init.d/freeswitch start&lt;br /&gt;
&lt;br /&gt;
[[Category:ACF]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=ACF_mvc.lua_example&amp;diff=12729</id>
		<title>ACF mvc.lua example</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=ACF_mvc.lua_example&amp;diff=12729"/>
		<updated>2016-04-30T01:50:36Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Remove obsolete example and changes to view_resolver&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Set the hostname with mvc.lua =&lt;br /&gt;
&lt;br /&gt;
In this example we will create a simple hostname-setting command-line application using mvc.lua.  Once the controller/model are built, you can use &#039;&#039;the same code&#039;&#039; to set the hostname via the web with a web-based application controller.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
For this example, we will assume you have root access on the linux box you are running on (preferably an Alpine Linux box!)&lt;br /&gt;
&lt;br /&gt;
== Get the mvc.lua module == &lt;br /&gt;
&lt;br /&gt;
Get the mvc.lua module from the git repository.&lt;br /&gt;
&lt;br /&gt;
 wget http://git.alpinelinux.org/cgit/acf-core/plain/lua/mvc.lua&lt;br /&gt;
&lt;br /&gt;
== Create a model and controller ==&lt;br /&gt;
&lt;br /&gt;
Create a file &#039;&#039;&#039;hostname-controller.lua&#039;&#039;&#039;, defining the functions that an &amp;quot;end user&amp;quot; could run. We will only create one action, edithostname, which will be used to read and update the hostname. Since the action makes changes to the system, it naturally takes the form of a &#039;form&#039;:&lt;br /&gt;
&lt;br /&gt;
=== hostname-controller.lua ===&lt;br /&gt;
 -- Controller for editing hostname&lt;br /&gt;
 local mymodule = {} &lt;br /&gt;
 &lt;br /&gt;
 mymodule.edithostname = function (self)&lt;br /&gt;
         return self.handle_form(self, self.model.get_hostname, self.model.set_hostname, self.clientdata, &amp;quot;Update&amp;quot;, &amp;quot;Edit Hostname&amp;quot;, &amp;quot;Hostname Updated&amp;quot;)&lt;br /&gt;
 end                                   &lt;br /&gt;
 &lt;br /&gt;
 return mymodule&lt;br /&gt;
&lt;br /&gt;
Create a file &#039;&#039;&#039;hostname-model.lua&#039;&#039;&#039;, defining the model functions to get and set the hostname.  We return a cfe table for each function including the form with the one entry for hostname:&lt;br /&gt;
&lt;br /&gt;
=== hostname-model.lua ===&lt;br /&gt;
 -- Model functions for retrieving / setting the hostname&lt;br /&gt;
 local mymodule = {}&lt;br /&gt;
 &lt;br /&gt;
 -- Create a cfe defining the form for editing the hostname and containing the current value&lt;br /&gt;
 mymodule.get_hostname = function(self, clientdata)&lt;br /&gt;
         local retval = cfe({ type=&amp;quot;group&amp;quot;, value={}, label=&amp;quot;Hostname&amp;quot; })&lt;br /&gt;
 &lt;br /&gt;
         -- Warning - io.popen has security risks, never pass user data to io.popen&lt;br /&gt;
         local f = io.popen (&amp;quot;/bin/hostname&amp;quot;)&lt;br /&gt;
         local n = f:read(&amp;quot;*a&amp;quot;) or &amp;quot;none&amp;quot;&lt;br /&gt;
         f:close()&lt;br /&gt;
         n=string.gsub(n, &amp;quot;\n$&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
         retval.value.hostname = cfe({ value=n, label=&amp;quot;Hostname&amp;quot; })&lt;br /&gt;
 &lt;br /&gt;
         return retval&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 -- Set the hostname from the value contained in the cfe created by get_hostname&lt;br /&gt;
 mymodule.set_hostname = function(self, hostnameform, action)&lt;br /&gt;
         local success = true&lt;br /&gt;
 &lt;br /&gt;
         -- Check to make sure the name is valid&lt;br /&gt;
         if (hostnameform.value.hostname.value == &amp;quot;&amp;quot;) then&lt;br /&gt;
                 success = false&lt;br /&gt;
                 hostnameform.value.hostname.errtxt = &amp;quot;Hostname must not be blank&amp;quot;&lt;br /&gt;
         elseif (#hostnameform.value.hostname.value &amp;gt; 16) then&lt;br /&gt;
                 success = false&lt;br /&gt;
                 hostnameform.value.hostname.errtxt = &amp;quot;Hostname must be 16 characters or less&amp;quot;&lt;br /&gt;
         elseif (string.find(hostnameform.value.hostname.value, &amp;quot;[^%w%_%-]&amp;quot;)) then&lt;br /&gt;
                 success = false&lt;br /&gt;
                 hostnameform.value.hostname.errtxt = &amp;quot;Hostname can contain alphanumerics only&amp;quot;&lt;br /&gt;
         end&lt;br /&gt;
 &lt;br /&gt;
         -- If it is valid, set the hostname&lt;br /&gt;
         if ( success ) then&lt;br /&gt;
                 local f = io.open(&amp;quot;/etc/hostname&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
                 if f then&lt;br /&gt;
                         f:write(hostnameform.value.hostname.value .. &amp;quot;\n&amp;quot;)&lt;br /&gt;
                         f:close()&lt;br /&gt;
                 end&lt;br /&gt;
                 -- Warning - io.popen has security risks, never pass user data to io.popen&lt;br /&gt;
                 f = io.popen (&amp;quot;/bin/hostname -F /etc/hostname&amp;quot;)&lt;br /&gt;
                 f:close()&lt;br /&gt;
         else&lt;br /&gt;
                 hostnameform.errtxt = &amp;quot;Failed to set hostname&amp;quot;&lt;br /&gt;
         end&lt;br /&gt;
 &lt;br /&gt;
         return hostnameform&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 return mymodule&lt;br /&gt;
&lt;br /&gt;
== Optionally test the model code (without mvc.lua)  ==&lt;br /&gt;
&lt;br /&gt;
If you want, you can create a &#039;&#039;&#039;test.lua&#039;&#039;&#039; script to validate the model code works on its own:&lt;br /&gt;
&lt;br /&gt;
=== test.lua ===&lt;br /&gt;
 require(&amp;quot;mvc&amp;quot;) -- Needed for cfe function definition&lt;br /&gt;
 m=require(&amp;quot;hostname-model&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 local form = m.get_hostname()&lt;br /&gt;
 form.value.hostname.value = arg[1] or &amp;quot;&amp;quot;&lt;br /&gt;
 form = m.set_hostname(nil, form)&lt;br /&gt;
 &lt;br /&gt;
 if form.errtxt then&lt;br /&gt;
         print(&amp;quot;FAILED: &amp;quot;..form.value.hostname.errtxt or form.errtxt)&lt;br /&gt;
 end&lt;br /&gt;
 form = m.get_hostname()&lt;br /&gt;
 print(form.value.hostname.value)&lt;br /&gt;
&lt;br /&gt;
You can then test this with:&lt;br /&gt;
&lt;br /&gt;
 #lua test.lua &amp;quot;Alpine&amp;quot;&lt;br /&gt;
  Alpine&lt;br /&gt;
&lt;br /&gt;
 #lua test.lua &amp;quot;Invalid Name&amp;quot;&lt;br /&gt;
  FAILED: Hostname can contain alphanumerics only&lt;br /&gt;
  Alpine&lt;br /&gt;
&lt;br /&gt;
== Add the package to the ACF framework ==&lt;br /&gt;
&lt;br /&gt;
To make the model and controller work within the ACF mvc.lua framework, we must do several things.   &lt;br /&gt;
&lt;br /&gt;
1. Install the ACF core package:&lt;br /&gt;
&lt;br /&gt;
 apk add acf-core&lt;br /&gt;
&lt;br /&gt;
Optionally, you can add the entire web-based ACF framework:&lt;br /&gt;
&lt;br /&gt;
 setup-acf&lt;br /&gt;
&lt;br /&gt;
2. Modify the ACF configuration file to look in /etc/acf/app/ for additional packages. Edit the /etc/acf/acf.conf file to add the &#039;&#039;/etc/acf/app/&#039;&#039; directory to the &#039;&#039;appdir&#039;&#039; comma-separated list:&lt;br /&gt;
&lt;br /&gt;
 appdir=/etc/acf/app/,/usr/share/acf/app/&lt;br /&gt;
&lt;br /&gt;
3. Move the model and controller to the new package directory. We will call the package &amp;quot;test&amp;quot;:&lt;br /&gt;
&lt;br /&gt;
 mkdir -p /etc/acf/app/test&lt;br /&gt;
 mv hostname-*.lua /etc/acf/app/test&lt;br /&gt;
&lt;br /&gt;
4. Test the new package using the acf-cli application:&lt;br /&gt;
&lt;br /&gt;
 # acf-cli /test/hostname/edithostname&lt;br /&gt;
 result = {}&lt;br /&gt;
 result[&amp;quot;label&amp;quot;] = &amp;quot;Edit Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;option&amp;quot;] = &amp;quot;Edit&amp;quot;&lt;br /&gt;
 result[&amp;quot;type&amp;quot;] = &amp;quot;form&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;label&amp;quot;] = &amp;quot;Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;type&amp;quot;] = &amp;quot;text&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;value&amp;quot;] = &amp;quot;Alpine&amp;quot;&lt;br /&gt;
 # acf-cli /test/hostname/edithostname hostname=test submit=true&lt;br /&gt;
 result = {}&lt;br /&gt;
 result[&amp;quot;descr&amp;quot;] = &amp;quot;Hostname Updated&amp;quot;&lt;br /&gt;
 result[&amp;quot;label&amp;quot;] = &amp;quot;Edit Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;option&amp;quot;] = &amp;quot;Edit&amp;quot;&lt;br /&gt;
 result[&amp;quot;type&amp;quot;] = &amp;quot;form&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;label&amp;quot;] = &amp;quot;Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;type&amp;quot;] = &amp;quot;text&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;value&amp;quot;] = &amp;quot;test&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Note that the action string passed to acf-cli is of the form &amp;quot;/prefix/controller/action&amp;quot;. The prefix is the path within /etc/acf/app where the controller may be found, so &amp;quot;/test/&amp;quot; for our case. The controller is determined from the controller file name, so &amp;quot;hostname&amp;quot; for our case. And, the action corresponds to the function within the controller to call, so &amp;quot;edithostname&amp;quot; for our case. Also note that, in the two examples above, the first case reads the existing hostname and the second case updates it. The output of the acf-cli application is a serialized version of the cfe form, which is good for testing but not too useful in real life.&lt;br /&gt;
&lt;br /&gt;
== Enable the package in the ACF web interface ==&lt;br /&gt;
&lt;br /&gt;
1. Add the web-based ACF framework:&lt;br /&gt;
&lt;br /&gt;
 setup-acf&lt;br /&gt;
&lt;br /&gt;
2. Configure all users to have access to the new hostname action. Edit &amp;quot;/etc/acf/app/test/hostname.roles&amp;quot; file and add GUEST permission for the edithostname action:&lt;br /&gt;
&lt;br /&gt;
 echo &amp;quot;GUEST=hostname/edithostname&amp;quot; &amp;gt; /etc/acf/app/test/hostname.roles&lt;br /&gt;
&lt;br /&gt;
The new action should now be visible by browsing to &#039;&#039;https://IP-of-host/cgi-bin/acf/test/hostname/edithostname&#039;&#039;. Obviously you might want to reconsider providing GUEST access to this action, because this allows unauthenticated users to modify your hostname.&lt;br /&gt;
&lt;br /&gt;
3. Add the new hostname action to the ACF menu. Edit &amp;quot;/etc/acf/app/test/hostname.menu&amp;quot; file and add a menu item:&lt;br /&gt;
&lt;br /&gt;
 echo &amp;quot;Test Hostname Edit edithostname&amp;quot; &amp;gt; /etc/acf/app/test/hostname.menu&lt;br /&gt;
&lt;br /&gt;
You will need to log off from the ACF interface (or delete the session cookie) before the new menu item will be visible.&lt;br /&gt;
&lt;br /&gt;
== Make an MVC based application ==&lt;br /&gt;
You have two options for creating an MVC based application of your own.&lt;br /&gt;
&lt;br /&gt;
1. Use the &#039;&#039;dispatch&#039;&#039; function. This is the method used by the web interface and the acf-cli application. The action to be dispatched is passed in a string format &#039;&#039;/prefix/controller/action&#039;&#039; and the input values are passed in as a clientinfo table. Output is determined by the view.&lt;br /&gt;
&lt;br /&gt;
2. Use the &#039;&#039;new&#039;&#039; function to load the desired controller and then directly call the controller actions or model functions. Using the controller actions requires use of the clientdata structure to pass parameters. For calling the model functions, the application will call the get function to retrieve the form cfe, fill in the desired input values into the cfe, and then call the set function to submit the form and perform the desired action. The MVC application is responsible for any user interaction and display of the results.&lt;br /&gt;
&lt;br /&gt;
=== Use the Dispatch method ===&lt;br /&gt;
==== test_dispatch ====&lt;br /&gt;
 #!/usr/bin/lua&lt;br /&gt;
 -- Simple CLI based mvc application&lt;br /&gt;
 &lt;br /&gt;
 -- load the mvc module&lt;br /&gt;
 mvc = require(&amp;quot;acf.mvc&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 -- create an new &amp;quot;mvc object&amp;quot;&lt;br /&gt;
 MVC=mvc:new()&lt;br /&gt;
 &lt;br /&gt;
 -- load the config file so we can find the appdir&lt;br /&gt;
 MVC:read_config(&amp;quot;acf&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 -- dispatch the request&lt;br /&gt;
 local clientdata = {hostname=arg[1], viewtype=arg[2], submit=&amp;quot;Update&amp;quot;}&lt;br /&gt;
 MVC:dispatch(&amp;quot;/test/&amp;quot;, &amp;quot;hostname&amp;quot;, &amp;quot;edithostname&amp;quot;, clientdata)&lt;br /&gt;
 &lt;br /&gt;
 -- destroy the mvc object&lt;br /&gt;
 MVC:destroy()&lt;br /&gt;
&lt;br /&gt;
Test the application. As can be seen in the code above, the first argument is the new hostname and the second is a viewtype.&lt;br /&gt;
&lt;br /&gt;
 alpine:~# chmod 755 test_dispatch &lt;br /&gt;
 alpine:~# ./test_dispatch test&lt;br /&gt;
 test:~# ./test_dispatch alpine&lt;br /&gt;
 alpine:~#&lt;br /&gt;
&lt;br /&gt;
Note that the application functions, but there is no output. This is because there is no viewtype supplied. There is built-in support for these standard viewtypes (not all apply): html, json, stream, serialized&lt;br /&gt;
&lt;br /&gt;
 alpine:~# ./test_dispatch test serialized&lt;br /&gt;
 result = {}&lt;br /&gt;
 result[&amp;quot;descr&amp;quot;] = &amp;quot;Hostname Updated&amp;quot;&lt;br /&gt;
 result[&amp;quot;label&amp;quot;] = &amp;quot;Edit Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;option&amp;quot;] = &amp;quot;Update&amp;quot;&lt;br /&gt;
 result[&amp;quot;type&amp;quot;] = &amp;quot;form&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;label&amp;quot;] = &amp;quot;Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;type&amp;quot;] = &amp;quot;text&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;value&amp;quot;] = &amp;quot;test&amp;quot;&lt;br /&gt;
 test:~# ./test_dispatch alpine json&lt;br /&gt;
 {&amp;quot;type&amp;quot;:&amp;quot;form&amp;quot;,&amp;quot;label&amp;quot;:&amp;quot;Edit Hostname&amp;quot;,&amp;quot;value&amp;quot;:{&amp;quot;hostname&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;alpine&amp;quot;,&amp;quot;type&amp;quot;:&amp;quot;text&amp;quot;,&amp;quot;label&amp;quot;:&amp;quot;Hostname&amp;quot;}},&amp;quot;option&amp;quot;:&amp;quot;Update&amp;quot;,&amp;quot;descr&amp;quot;:&amp;quot;Hostname Updated&amp;quot;}&lt;br /&gt;
 alpine:~# &lt;br /&gt;
&lt;br /&gt;
You can also use a custom viewtype and/or view to customize the output. This relies on haserl to parse the view file, and haserl lua functions are only available when launched from haserl scripts (it would be nice if haserl were available as a standalone lua library). Haserl scripts expect to be launched by a web browser to handle CGI data, so passing input is trickier. I&#039;m sure there is a better way to do this, but this is just an example:&lt;br /&gt;
&lt;br /&gt;
==== test_haserl ====&lt;br /&gt;
 #!/usr/bin/haserl-lua5.2 --shell=lua&lt;br /&gt;
 &amp;lt;%&lt;br /&gt;
 -- Simple CLI based mvc application&lt;br /&gt;
 &lt;br /&gt;
 -- load the mvc module&lt;br /&gt;
 mvc = require(&amp;quot;acf.mvc&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 -- create an new &amp;quot;mvc object&amp;quot;&lt;br /&gt;
 MVC=mvc:new()&lt;br /&gt;
 &lt;br /&gt;
 -- load the config file so we can find the appdir&lt;br /&gt;
 MVC:read_config(&amp;quot;acf&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 -- dispatch the request&lt;br /&gt;
 local clientdata = {hostname=FORM.hostname, viewtype=FORM.viewtype, submit=&amp;quot;Update&amp;quot;}&lt;br /&gt;
 MVC:dispatch(&amp;quot;/test/&amp;quot;, &amp;quot;hostname&amp;quot;, &amp;quot;edithostname&amp;quot;, clientdata)&lt;br /&gt;
 &lt;br /&gt;
 -- destroy the mvc object&lt;br /&gt;
 MVC:destroy()&lt;br /&gt;
 %&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /etc/acf/app/test/hostname-edithostname-text.lsp ====&lt;br /&gt;
 &amp;lt;%&lt;br /&gt;
 local data, viewlibrary, page_info, session = ... &lt;br /&gt;
 &lt;br /&gt;
 if data.errtxt then&lt;br /&gt;
         print(data:print_errtxt())&lt;br /&gt;
 else&lt;br /&gt;
         print(data.descr)&lt;br /&gt;
 end&lt;br /&gt;
 %&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Test the application&lt;br /&gt;
&lt;br /&gt;
 test:~# chmod 755 test_haserl &lt;br /&gt;
 test:~# QUERY_STRING=&#039;hostname=alpine&amp;amp;viewtype=text&#039; REQUEST_METHOD=GET ./test_haserl&lt;br /&gt;
 Hostname Updated&lt;br /&gt;
 alpine:~# QUERY_STRING=&#039;hostname=asdfasdfasdfasdfasdf&amp;amp;viewtype=text&#039; REQUEST_METHOD=GET ./test_haserl&lt;br /&gt;
 Failed to set hostname&lt;br /&gt;
 hostname: Hostname must be 16 characters or less&lt;br /&gt;
 alpine:~# &lt;br /&gt;
&lt;br /&gt;
=== Use the New method ===&lt;br /&gt;
==== test_new ====&lt;br /&gt;
 #!/usr/bin/lua&lt;br /&gt;
 -- Simple CLI based mvc application&lt;br /&gt;
 &lt;br /&gt;
 -- load the mvc module&lt;br /&gt;
 mvc = require(&amp;quot;acf.mvc&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 -- create an new &amp;quot;mvc object&amp;quot;&lt;br /&gt;
 MVC=mvc:new()&lt;br /&gt;
 &lt;br /&gt;
 -- load the config file so we can find the appdir&lt;br /&gt;
 MVC:read_config(&amp;quot;acf&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 -- load the hostname controller&lt;br /&gt;
 HOSTNAME=MVC:new(&amp;quot;/test/hostname&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 local hostname&lt;br /&gt;
 &lt;br /&gt;
 -- METHOD 1 - controller action&lt;br /&gt;
 HOSTNAME.clientdata = {hostname=arg[1], submit=&amp;quot;Update&amp;quot;}&lt;br /&gt;
 hostname = HOSTNAME:edithostname()&lt;br /&gt;
 &lt;br /&gt;
 -- METHOD 2 - model functions&lt;br /&gt;
 hostname = HOSTNAME.model:get_hostname()&lt;br /&gt;
 hostname.value.hostname.value = arg[1]&lt;br /&gt;
 hostname = HOSTNAME.model:set_hostname(hostname)&lt;br /&gt;
 &lt;br /&gt;
 if hostname.errtxt then&lt;br /&gt;
         print(hostname:print_errtxt())&lt;br /&gt;
 else&lt;br /&gt;
         print(hostname.descr or &amp;quot;Hostname Updated&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 HOSTNAME:destroy()&lt;br /&gt;
 MVC:destroy()&lt;br /&gt;
&lt;br /&gt;
Once you have a hostname controller object, you can access its actions, passing data through clientdata, or you can directly access the get/set functions of the model. Test the app:&lt;br /&gt;
&lt;br /&gt;
 test:~# chmod 755 test_new&lt;br /&gt;
 test:~# ./test_new alpine&lt;br /&gt;
 Hostname Updated&lt;br /&gt;
 alpine:~# ./test_new asdfasdfasdfasdfasdf&lt;br /&gt;
 Failed to set hostname&lt;br /&gt;
 hostname: Hostname must be 16 characters or less&lt;br /&gt;
 alpine:~# &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Obsolete}}&lt;br /&gt;
&lt;br /&gt;
= mvc load &amp;amp; exec special functions =&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;mvc.lua&#039;&#039;&#039; module has a provision for executing code on module load, prior to executing the controller&#039;s action, just after executing the controller&#039;s action, and on module unload.&lt;br /&gt;
&lt;br /&gt;
This is done with the mvc table in the controller.  To demonstrate, let&#039;s add a few functions to &#039;&#039;&#039;helloworld/app/app-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
 mvc = {}&lt;br /&gt;
 mvc.on_load = function (self, parent)&lt;br /&gt;
         print (&amp;quot;This is the app controller&#039;s on_load function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 mvc.pre_exec = function (self)&lt;br /&gt;
         print (&amp;quot;This is the app controller&#039;s pre_exec function&amp;quot;)&lt;br /&gt;
 end &lt;br /&gt;
 &lt;br /&gt;
 mvc.post_exec = function (self)&lt;br /&gt;
         print (&amp;quot;This is the app controller&#039;s post_exec function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
 mvc.on_unload = function (self)&lt;br /&gt;
         print (&amp;quot;This is the app controller&#039;s on_unload function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
Now running our script shows when the functions get called:&lt;br /&gt;
&lt;br /&gt;
 # lua helloworld.lua update &amp;quot;Alpine&amp;quot;&lt;br /&gt;
   This is the app controller&#039;s on_load function&lt;br /&gt;
   This is the app controller&#039;s pre_exec function&lt;br /&gt;
   This is the app controller&#039;s post_exec function&lt;br /&gt;
   Controller: hostname  Action: update&lt;br /&gt;
   Returned a table with the following values:&lt;br /&gt;
   value   Alpine&lt;br /&gt;
   type    string&lt;br /&gt;
   This is the app controller&#039;s on_unload function&lt;br /&gt;
&lt;br /&gt;
We can add mvc functions to a specific controller, as well.  Add this to &#039;&#039;&#039;helloworld/app/hostname-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
 mvc = {}&lt;br /&gt;
 mvc.on_load = function (self, parent)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s on_load function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 mvc.pre_exec = function (self)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s pre_exec function&amp;quot;)&lt;br /&gt;
 end &lt;br /&gt;
 &lt;br /&gt;
 mvc.post_exec = function (self)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s post_exec function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 mvc.on_unload = function (self)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s on_unload function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
And this happens:&lt;br /&gt;
&lt;br /&gt;
 # lua helloworld.lua update &amp;quot;Alpine&amp;quot;&lt;br /&gt;
   This is the app controller&#039;s on_load function&lt;br /&gt;
   This is the hostname controller&#039;s on_load function&lt;br /&gt;
   This is the hostname controller&#039;s pre_exec function&lt;br /&gt;
   This is the hostname controller&#039;s post_exec function&lt;br /&gt;
   This is the hostname controller&#039;s on_unload function&lt;br /&gt;
   Controller: hostname  Action: update&lt;br /&gt;
   Returned a table with the following values:&lt;br /&gt;
   value   Alpine&lt;br /&gt;
   type    string&lt;br /&gt;
   This is the app controller&#039;s on_unload function&lt;br /&gt;
&lt;br /&gt;
Note that both the &#039;&#039;app&#039;&#039; and &#039;&#039;hostname&#039;&#039; &#039;&#039;&#039;on_load&#039;&#039;&#039; and &#039;&#039;&#039;on_unload&#039;&#039;&#039; functions were run, but only the &#039;&#039;hostname&#039;&#039; &#039;&#039;&#039;pre_exec&#039;&#039;&#039; and &#039;&#039;&#039;post_exec&#039;&#039;&#039; functions ran.  This is because the pre and post exec functions are run as part of the &amp;quot;action&amp;quot;, and the &#039;&#039;&#039;dispatch&#039;&#039;&#039; function looks in the lowest-level controller for the pre/post_exec function.  Since &#039;&#039;hostname&#039;&#039; now defines those functions, it runs them.&lt;br /&gt;
&lt;br /&gt;
To run both the &#039;&#039;hostname&#039;&#039; and &#039;&#039;app&#039;&#039; pre_exec function, you must arrange for the &#039;&#039;hostname&#039;&#039; pre_exec function to call it&#039;s parent pre_exec:&lt;br /&gt;
&lt;br /&gt;
 mvc = {}&lt;br /&gt;
 mvc.on_load = function (self, parent)&lt;br /&gt;
        print (&amp;quot;This is the hostname controller&#039;s on_load function&amp;quot;)&lt;br /&gt;
        mvc.parent_pre_exec = parent.worker.mvc.pre_exec&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 mvc.pre_exec = function (self)&lt;br /&gt;
         mvc.parent_pre_exec (self)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s pre_exec function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
[[Category:ACF]] [[Category:Lua]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=ACF_mvc.lua_example&amp;diff=12728</id>
		<title>ACF mvc.lua example</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=ACF_mvc.lua_example&amp;diff=12728"/>
		<updated>2016-04-30T01:42:16Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Added example of MVC app using new method&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Set the hostname with mvc.lua =&lt;br /&gt;
&lt;br /&gt;
In this example we will create a simple hostname-setting command-line application using mvc.lua.  Once the controller/model are built, you can use &#039;&#039;the same code&#039;&#039; to set the hostname via the web with a web-based application controller.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
For this example, we will assume you have root access on the linux box you are running on (preferably an Alpine Linux box!)&lt;br /&gt;
&lt;br /&gt;
== Get the mvc.lua module == &lt;br /&gt;
&lt;br /&gt;
Get the mvc.lua module from the git repository.&lt;br /&gt;
&lt;br /&gt;
 wget http://git.alpinelinux.org/cgit/acf-core/plain/lua/mvc.lua&lt;br /&gt;
&lt;br /&gt;
== Create a model and controller ==&lt;br /&gt;
&lt;br /&gt;
Create a file &#039;&#039;&#039;hostname-controller.lua&#039;&#039;&#039;, defining the functions that an &amp;quot;end user&amp;quot; could run. We will only create one action, edithostname, which will be used to read and update the hostname. Since the action makes changes to the system, it naturally takes the form of a &#039;form&#039;:&lt;br /&gt;
&lt;br /&gt;
=== hostname-controller.lua ===&lt;br /&gt;
 -- Controller for editing hostname&lt;br /&gt;
 local mymodule = {} &lt;br /&gt;
 &lt;br /&gt;
 mymodule.edithostname = function (self)&lt;br /&gt;
         return self.handle_form(self, self.model.get_hostname, self.model.set_hostname, self.clientdata, &amp;quot;Update&amp;quot;, &amp;quot;Edit Hostname&amp;quot;, &amp;quot;Hostname Updated&amp;quot;)&lt;br /&gt;
 end                                   &lt;br /&gt;
 &lt;br /&gt;
 return mymodule&lt;br /&gt;
&lt;br /&gt;
Create a file &#039;&#039;&#039;hostname-model.lua&#039;&#039;&#039;, defining the model functions to get and set the hostname.  We return a cfe table for each function including the form with the one entry for hostname:&lt;br /&gt;
&lt;br /&gt;
=== hostname-model.lua ===&lt;br /&gt;
 -- Model functions for retrieving / setting the hostname&lt;br /&gt;
 local mymodule = {}&lt;br /&gt;
 &lt;br /&gt;
 -- Create a cfe defining the form for editing the hostname and containing the current value&lt;br /&gt;
 mymodule.get_hostname = function(self, clientdata)&lt;br /&gt;
         local retval = cfe({ type=&amp;quot;group&amp;quot;, value={}, label=&amp;quot;Hostname&amp;quot; })&lt;br /&gt;
 &lt;br /&gt;
         -- Warning - io.popen has security risks, never pass user data to io.popen&lt;br /&gt;
         local f = io.popen (&amp;quot;/bin/hostname&amp;quot;)&lt;br /&gt;
         local n = f:read(&amp;quot;*a&amp;quot;) or &amp;quot;none&amp;quot;&lt;br /&gt;
         f:close()&lt;br /&gt;
         n=string.gsub(n, &amp;quot;\n$&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
         retval.value.hostname = cfe({ value=n, label=&amp;quot;Hostname&amp;quot; })&lt;br /&gt;
 &lt;br /&gt;
         return retval&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 -- Set the hostname from the value contained in the cfe created by get_hostname&lt;br /&gt;
 mymodule.set_hostname = function(self, hostnameform, action)&lt;br /&gt;
         local success = true&lt;br /&gt;
 &lt;br /&gt;
         -- Check to make sure the name is valid&lt;br /&gt;
         if (hostnameform.value.hostname.value == &amp;quot;&amp;quot;) then&lt;br /&gt;
                 success = false&lt;br /&gt;
                 hostnameform.value.hostname.errtxt = &amp;quot;Hostname must not be blank&amp;quot;&lt;br /&gt;
         elseif (#hostnameform.value.hostname.value &amp;gt; 16) then&lt;br /&gt;
                 success = false&lt;br /&gt;
                 hostnameform.value.hostname.errtxt = &amp;quot;Hostname must be 16 characters or less&amp;quot;&lt;br /&gt;
         elseif (string.find(hostnameform.value.hostname.value, &amp;quot;[^%w%_%-]&amp;quot;)) then&lt;br /&gt;
                 success = false&lt;br /&gt;
                 hostnameform.value.hostname.errtxt = &amp;quot;Hostname can contain alphanumerics only&amp;quot;&lt;br /&gt;
         end&lt;br /&gt;
 &lt;br /&gt;
         -- If it is valid, set the hostname&lt;br /&gt;
         if ( success ) then&lt;br /&gt;
                 local f = io.open(&amp;quot;/etc/hostname&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
                 if f then&lt;br /&gt;
                         f:write(hostnameform.value.hostname.value .. &amp;quot;\n&amp;quot;)&lt;br /&gt;
                         f:close()&lt;br /&gt;
                 end&lt;br /&gt;
                 -- Warning - io.popen has security risks, never pass user data to io.popen&lt;br /&gt;
                 f = io.popen (&amp;quot;/bin/hostname -F /etc/hostname&amp;quot;)&lt;br /&gt;
                 f:close()&lt;br /&gt;
         else&lt;br /&gt;
                 hostnameform.errtxt = &amp;quot;Failed to set hostname&amp;quot;&lt;br /&gt;
         end&lt;br /&gt;
 &lt;br /&gt;
         return hostnameform&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 return mymodule&lt;br /&gt;
&lt;br /&gt;
== Optionally test the model code (without mvc.lua)  ==&lt;br /&gt;
&lt;br /&gt;
If you want, you can create a &#039;&#039;&#039;test.lua&#039;&#039;&#039; script to validate the model code works on its own:&lt;br /&gt;
&lt;br /&gt;
=== test.lua ===&lt;br /&gt;
 require(&amp;quot;mvc&amp;quot;) -- Needed for cfe function definition&lt;br /&gt;
 m=require(&amp;quot;hostname-model&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 local form = m.get_hostname()&lt;br /&gt;
 form.value.hostname.value = arg[1] or &amp;quot;&amp;quot;&lt;br /&gt;
 form = m.set_hostname(nil, form)&lt;br /&gt;
 &lt;br /&gt;
 if form.errtxt then&lt;br /&gt;
         print(&amp;quot;FAILED: &amp;quot;..form.value.hostname.errtxt or form.errtxt)&lt;br /&gt;
 end&lt;br /&gt;
 form = m.get_hostname()&lt;br /&gt;
 print(form.value.hostname.value)&lt;br /&gt;
&lt;br /&gt;
You can then test this with:&lt;br /&gt;
&lt;br /&gt;
 #lua test.lua &amp;quot;Alpine&amp;quot;&lt;br /&gt;
  Alpine&lt;br /&gt;
&lt;br /&gt;
 #lua test.lua &amp;quot;Invalid Name&amp;quot;&lt;br /&gt;
  FAILED: Hostname can contain alphanumerics only&lt;br /&gt;
  Alpine&lt;br /&gt;
&lt;br /&gt;
== Add the package to the ACF framework ==&lt;br /&gt;
&lt;br /&gt;
To make the model and controller work within the ACF mvc.lua framework, we must do several things.   &lt;br /&gt;
&lt;br /&gt;
1. Install the ACF core package:&lt;br /&gt;
&lt;br /&gt;
 apk add acf-core&lt;br /&gt;
&lt;br /&gt;
Optionally, you can add the entire web-based ACF framework:&lt;br /&gt;
&lt;br /&gt;
 setup-acf&lt;br /&gt;
&lt;br /&gt;
2. Modify the ACF configuration file to look in /etc/acf/app/ for additional packages. Edit the /etc/acf/acf.conf file to add the &#039;&#039;/etc/acf/app/&#039;&#039; directory to the &#039;&#039;appdir&#039;&#039; comma-separated list:&lt;br /&gt;
&lt;br /&gt;
 appdir=/etc/acf/app/,/usr/share/acf/app/&lt;br /&gt;
&lt;br /&gt;
3. Move the model and controller to the new package directory. We will call the package &amp;quot;test&amp;quot;:&lt;br /&gt;
&lt;br /&gt;
 mkdir -p /etc/acf/app/test&lt;br /&gt;
 mv hostname-*.lua /etc/acf/app/test&lt;br /&gt;
&lt;br /&gt;
4. Test the new package using the acf-cli application:&lt;br /&gt;
&lt;br /&gt;
 # acf-cli /test/hostname/edithostname&lt;br /&gt;
 result = {}&lt;br /&gt;
 result[&amp;quot;label&amp;quot;] = &amp;quot;Edit Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;option&amp;quot;] = &amp;quot;Edit&amp;quot;&lt;br /&gt;
 result[&amp;quot;type&amp;quot;] = &amp;quot;form&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;label&amp;quot;] = &amp;quot;Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;type&amp;quot;] = &amp;quot;text&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;value&amp;quot;] = &amp;quot;Alpine&amp;quot;&lt;br /&gt;
 # acf-cli /test/hostname/edithostname hostname=test submit=true&lt;br /&gt;
 result = {}&lt;br /&gt;
 result[&amp;quot;descr&amp;quot;] = &amp;quot;Hostname Updated&amp;quot;&lt;br /&gt;
 result[&amp;quot;label&amp;quot;] = &amp;quot;Edit Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;option&amp;quot;] = &amp;quot;Edit&amp;quot;&lt;br /&gt;
 result[&amp;quot;type&amp;quot;] = &amp;quot;form&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;label&amp;quot;] = &amp;quot;Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;type&amp;quot;] = &amp;quot;text&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;value&amp;quot;] = &amp;quot;test&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Note that the action string passed to acf-cli is of the form &amp;quot;/prefix/controller/action&amp;quot;. The prefix is the path within /etc/acf/app where the controller may be found, so &amp;quot;/test/&amp;quot; for our case. The controller is determined from the controller file name, so &amp;quot;hostname&amp;quot; for our case. And, the action corresponds to the function within the controller to call, so &amp;quot;edithostname&amp;quot; for our case. Also note that, in the two examples above, the first case reads the existing hostname and the second case updates it. The output of the acf-cli application is a serialized version of the cfe form, which is good for testing but not too useful in real life.&lt;br /&gt;
&lt;br /&gt;
== Enable the package in the ACF web interface ==&lt;br /&gt;
&lt;br /&gt;
1. Add the web-based ACF framework:&lt;br /&gt;
&lt;br /&gt;
 setup-acf&lt;br /&gt;
&lt;br /&gt;
2. Configure all users to have access to the new hostname action. Edit &amp;quot;/etc/acf/app/test/hostname.roles&amp;quot; file and add GUEST permission for the edithostname action:&lt;br /&gt;
&lt;br /&gt;
 echo &amp;quot;GUEST=hostname/edithostname&amp;quot; &amp;gt; /etc/acf/app/test/hostname.roles&lt;br /&gt;
&lt;br /&gt;
The new action should now be visible by browsing to &#039;&#039;https://IP-of-host/cgi-bin/acf/test/hostname/edithostname&#039;&#039;. Obviously you might want to reconsider providing GUEST access to this action, because this allows unauthenticated users to modify your hostname.&lt;br /&gt;
&lt;br /&gt;
3. Add the new hostname action to the ACF menu. Edit &amp;quot;/etc/acf/app/test/hostname.menu&amp;quot; file and add a menu item:&lt;br /&gt;
&lt;br /&gt;
 echo &amp;quot;Test Hostname Edit edithostname&amp;quot; &amp;gt; /etc/acf/app/test/hostname.menu&lt;br /&gt;
&lt;br /&gt;
You will need to log off from the ACF interface (or delete the session cookie) before the new menu item will be visible.&lt;br /&gt;
&lt;br /&gt;
== Make an MVC based application ==&lt;br /&gt;
You have two options for creating an MVC based application of your own.&lt;br /&gt;
&lt;br /&gt;
1. Use the &#039;&#039;dispatch&#039;&#039; function. This is the method used by the web interface and the acf-cli application. The action to be dispatched is passed in a string format &#039;&#039;/prefix/controller/action&#039;&#039; and the input values are passed in as a clientinfo table. Output is determined by the view.&lt;br /&gt;
&lt;br /&gt;
2. Use the &#039;&#039;new&#039;&#039; function to load the desired controller and then directly call the controller actions or model functions. Using the controller actions requires use of the clientdata structure to pass parameters. For calling the model functions, the application will call the get function to retrieve the form cfe, fill in the desired input values into the cfe, and then call the set function to submit the form and perform the desired action. The MVC application is responsible for any user interaction and display of the results.&lt;br /&gt;
&lt;br /&gt;
=== Use the Dispatch method ===&lt;br /&gt;
==== test_dispatch ====&lt;br /&gt;
 #!/usr/bin/lua&lt;br /&gt;
 -- Simple CLI based mvc application&lt;br /&gt;
 &lt;br /&gt;
 -- load the mvc module&lt;br /&gt;
 mvc = require(&amp;quot;acf.mvc&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 -- create an new &amp;quot;mvc object&amp;quot;&lt;br /&gt;
 MVC=mvc:new()&lt;br /&gt;
 &lt;br /&gt;
 -- load the config file so we can find the appdir&lt;br /&gt;
 MVC:read_config(&amp;quot;acf&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 -- dispatch the request&lt;br /&gt;
 local clientdata = {hostname=arg[1], viewtype=arg[2], submit=&amp;quot;Update&amp;quot;}&lt;br /&gt;
 MVC:dispatch(&amp;quot;/test/&amp;quot;, &amp;quot;hostname&amp;quot;, &amp;quot;edithostname&amp;quot;, clientdata)&lt;br /&gt;
 &lt;br /&gt;
 -- destroy the mvc object&lt;br /&gt;
 MVC:destroy()&lt;br /&gt;
&lt;br /&gt;
Test the application. As can be seen in the code above, the first argument is the new hostname and the second is a viewtype.&lt;br /&gt;
&lt;br /&gt;
 alpine:~# chmod 755 test_dispatch &lt;br /&gt;
 alpine:~# ./test_dispatch test&lt;br /&gt;
 test:~# ./test_dispatch alpine&lt;br /&gt;
 alpine:~#&lt;br /&gt;
&lt;br /&gt;
Note that the application functions, but there is no output. This is because there is no viewtype supplied. There is built-in support for these standard viewtypes (not all apply): html, json, stream, serialized&lt;br /&gt;
&lt;br /&gt;
 alpine:~# ./test_dispatch test serialized&lt;br /&gt;
 result = {}&lt;br /&gt;
 result[&amp;quot;descr&amp;quot;] = &amp;quot;Hostname Updated&amp;quot;&lt;br /&gt;
 result[&amp;quot;label&amp;quot;] = &amp;quot;Edit Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;option&amp;quot;] = &amp;quot;Update&amp;quot;&lt;br /&gt;
 result[&amp;quot;type&amp;quot;] = &amp;quot;form&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;label&amp;quot;] = &amp;quot;Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;type&amp;quot;] = &amp;quot;text&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;value&amp;quot;] = &amp;quot;test&amp;quot;&lt;br /&gt;
 test:~# ./test_dispatch alpine json&lt;br /&gt;
 {&amp;quot;type&amp;quot;:&amp;quot;form&amp;quot;,&amp;quot;label&amp;quot;:&amp;quot;Edit Hostname&amp;quot;,&amp;quot;value&amp;quot;:{&amp;quot;hostname&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;alpine&amp;quot;,&amp;quot;type&amp;quot;:&amp;quot;text&amp;quot;,&amp;quot;label&amp;quot;:&amp;quot;Hostname&amp;quot;}},&amp;quot;option&amp;quot;:&amp;quot;Update&amp;quot;,&amp;quot;descr&amp;quot;:&amp;quot;Hostname Updated&amp;quot;}&lt;br /&gt;
 alpine:~# &lt;br /&gt;
&lt;br /&gt;
You can also use a custom viewtype and/or view to customize the output. This relies on haserl to parse the view file, and haserl lua functions are only available when launched from haserl scripts (it would be nice if haserl were available as a standalone lua library). Haserl scripts expect to be launched by a web browser to handle CGI data, so passing input is trickier. I&#039;m sure there is a better way to do this, but this is just an example:&lt;br /&gt;
&lt;br /&gt;
==== test_haserl ====&lt;br /&gt;
 #!/usr/bin/haserl-lua5.2 --shell=lua&lt;br /&gt;
 &amp;lt;%&lt;br /&gt;
 -- Simple CLI based mvc application&lt;br /&gt;
 &lt;br /&gt;
 -- load the mvc module&lt;br /&gt;
 mvc = require(&amp;quot;acf.mvc&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 -- create an new &amp;quot;mvc object&amp;quot;&lt;br /&gt;
 MVC=mvc:new()&lt;br /&gt;
 &lt;br /&gt;
 -- load the config file so we can find the appdir&lt;br /&gt;
 MVC:read_config(&amp;quot;acf&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 -- dispatch the request&lt;br /&gt;
 local clientdata = {hostname=FORM.hostname, viewtype=FORM.viewtype, submit=&amp;quot;Update&amp;quot;}&lt;br /&gt;
 MVC:dispatch(&amp;quot;/test/&amp;quot;, &amp;quot;hostname&amp;quot;, &amp;quot;edithostname&amp;quot;, clientdata)&lt;br /&gt;
 &lt;br /&gt;
 -- destroy the mvc object&lt;br /&gt;
 MVC:destroy()&lt;br /&gt;
 %&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /etc/acf/app/test/hostname-edithostname-text.lsp ====&lt;br /&gt;
 &amp;lt;%&lt;br /&gt;
 local data, viewlibrary, page_info, session = ... &lt;br /&gt;
 &lt;br /&gt;
 if data.errtxt then&lt;br /&gt;
         print(data:print_errtxt())&lt;br /&gt;
 else&lt;br /&gt;
         print(data.descr)&lt;br /&gt;
 end&lt;br /&gt;
 %&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Test the application&lt;br /&gt;
&lt;br /&gt;
 test:~# chmod 755 test_haserl &lt;br /&gt;
 test:~# QUERY_STRING=&#039;hostname=alpine&amp;amp;viewtype=text&#039; REQUEST_METHOD=GET ./test_haserl&lt;br /&gt;
 Hostname Updated&lt;br /&gt;
 alpine:~# QUERY_STRING=&#039;hostname=asdfasdfasdfasdfasdf&amp;amp;viewtype=text&#039; REQUEST_METHOD=GET ./test_haserl&lt;br /&gt;
 Failed to set hostname&lt;br /&gt;
 hostname: Hostname must be 16 characters or less&lt;br /&gt;
 alpine:~# &lt;br /&gt;
&lt;br /&gt;
=== Use the New method ===&lt;br /&gt;
==== test_new ====&lt;br /&gt;
 #!/usr/bin/lua&lt;br /&gt;
 -- Simple CLI based mvc application&lt;br /&gt;
 &lt;br /&gt;
 -- load the mvc module&lt;br /&gt;
 mvc = require(&amp;quot;acf.mvc&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 -- create an new &amp;quot;mvc object&amp;quot;&lt;br /&gt;
 MVC=mvc:new()&lt;br /&gt;
 &lt;br /&gt;
 -- load the config file so we can find the appdir&lt;br /&gt;
 MVC:read_config(&amp;quot;acf&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 -- load the hostname controller&lt;br /&gt;
 HOSTNAME=MVC:new(&amp;quot;/test/hostname&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 local hostname&lt;br /&gt;
 &lt;br /&gt;
 -- METHOD 1 - controller action&lt;br /&gt;
 HOSTNAME.clientdata = {hostname=arg[1], submit=&amp;quot;Update&amp;quot;}&lt;br /&gt;
 hostname = HOSTNAME:edithostname()&lt;br /&gt;
 &lt;br /&gt;
 -- METHOD 2 - model functions&lt;br /&gt;
 hostname = HOSTNAME.model:get_hostname()&lt;br /&gt;
 hostname.value.hostname.value = arg[1]&lt;br /&gt;
 hostname = HOSTNAME.model:set_hostname(hostname)&lt;br /&gt;
 &lt;br /&gt;
 if hostname.errtxt then&lt;br /&gt;
         print(hostname:print_errtxt())&lt;br /&gt;
 else&lt;br /&gt;
         print(hostname.descr or &amp;quot;Hostname Updated&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 HOSTNAME:destroy()&lt;br /&gt;
 MVC:destroy()&lt;br /&gt;
&lt;br /&gt;
Once you have a hostname controller object, you can access its actions, passing data through clientdata, or you can directly access the get/set functions of the model. Test the app:&lt;br /&gt;
&lt;br /&gt;
 test:~# chmod 755 test_new&lt;br /&gt;
 test:~# ./test_new alpine&lt;br /&gt;
 Hostname Updated&lt;br /&gt;
 alpine:~# ./test_new asdfasdfasdfasdfasdf&lt;br /&gt;
 Failed to set hostname&lt;br /&gt;
 hostname: Hostname must be 16 characters or less&lt;br /&gt;
 alpine:~# &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Obsolete}}&lt;br /&gt;
4. Create a dispatch wrapper program, named &#039;&#039;&#039;helloworld.lua&#039;&#039;&#039; in the current directory:&lt;br /&gt;
&lt;br /&gt;
 -- Simple CLI based mvc application&lt;br /&gt;
 &lt;br /&gt;
 -- this is to get around having to store&lt;br /&gt;
 -- the config file in /etc/helloworld/helloworld.conf&lt;br /&gt;
 ENV={}&lt;br /&gt;
 ENV.HOME=&amp;quot;.&amp;quot; &lt;br /&gt;
 &lt;br /&gt;
 -- load the module&lt;br /&gt;
 require(&amp;quot;mvc&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 -- create an new &amp;quot;mvc object&amp;quot;&lt;br /&gt;
 MVC=mvc:new()&lt;br /&gt;
 &lt;br /&gt;
 -- load the config file so we can find the appdir&lt;br /&gt;
 MVC:read_config(&amp;quot;helloworld&amp;quot;) &lt;br /&gt;
 &lt;br /&gt;
 -- create an application container&lt;br /&gt;
 APP=MVC:new(&amp;quot;app&amp;quot;)&lt;br /&gt;
  &lt;br /&gt;
 -- dispatch the request&lt;br /&gt;
 APP.clientdata.hostname=arg[2]&lt;br /&gt;
 APP:dispatch( &amp;quot;&amp;quot;, &amp;quot;hostname&amp;quot;, (arg[1] or &amp;quot;&amp;quot;))&lt;br /&gt;
&lt;br /&gt;
 -- destroy the mvc objects&lt;br /&gt;
 APP:destroy()&lt;br /&gt;
 MVC:destroy()&lt;br /&gt;
&lt;br /&gt;
This application loads the &amp;quot;mvc.lua&amp;quot; framework, creates an mvc &amp;quot;object&amp;quot; named &amp;quot;MVC&amp;quot;, then reads the helloworld.conf file to find out where the app dir is (helloworld/app/).  It then loads the &#039;&#039;&#039;app-controller.lua&#039;&#039;&#039; into a new &amp;quot;application level&amp;quot; object named &#039;&#039;&#039;APP&#039;&#039;&#039;.  Finally, it sets the clientdata and dispatches the hostname-controller/model pair.&lt;br /&gt;
&lt;br /&gt;
5.  Test the application:&lt;br /&gt;
 # hostname&lt;br /&gt;
 Alpine&lt;br /&gt;
 # lua helloworld.lua no-such-function foo&lt;br /&gt;
 The following unhandled application error occured:&lt;br /&gt;
 &lt;br /&gt;
 controller: &amp;quot;hostname&amp;quot; does not have a &amp;quot;no-such-function&amp;quot; action.&lt;br /&gt;
 # hostname&lt;br /&gt;
 Alpine&lt;br /&gt;
&lt;br /&gt;
 # hostname&lt;br /&gt;
 Alpine&lt;br /&gt;
 # lua helloworld.lua update Alline&lt;br /&gt;
 Your controller and application did not specify a view resolver.&lt;br /&gt;
 The MVC framework has no view available. sorry.&lt;br /&gt;
 # hostname&lt;br /&gt;
 Alline&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Note in the second case the hostname &#039;&#039;was&#039;&#039; changed, although the application does not know how to report success.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Create a view resolver and view formatter ==&lt;br /&gt;
&lt;br /&gt;
The view resolver is a function that returns a function that processes the view. The returned function receives input from the controller and generates the output to be displayed.&lt;br /&gt;
&lt;br /&gt;
We will build a very simple view resolver and view processor for our application. Add this to the end of &#039;&#039;&#039;helloworld/app/hostname-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 local private = {} &lt;br /&gt;
 &lt;br /&gt;
 private.view = function (f)&lt;br /&gt;
        if (f.msg) then&lt;br /&gt;
                print( (f.value or &amp;quot;&amp;quot;) .. &amp;quot; is not a valid hostname &amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                print (&amp;quot;Hostname is currently &amp;quot; .. f.value )&lt;br /&gt;
        end&lt;br /&gt;
 end &lt;br /&gt;
 &lt;br /&gt;
 view_resolver = function (self)&lt;br /&gt;
         return private.view&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Now we can test:&lt;br /&gt;
&lt;br /&gt;
 # lua helloworld.lua update &amp;quot;1 2 3&amp;quot;&lt;br /&gt;
    1 2 3 is not a valid hostname &lt;br /&gt;
 # lua helloworld.lua update        &lt;br /&gt;
    is not a valid hostname &lt;br /&gt;
 # lua helloworld.lua update Alpine&lt;br /&gt;
   Hostname is currently Alpine&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
But we have two problems:&lt;br /&gt;
&lt;br /&gt;
1. We now have to make a view resolver and view function for every controller.  If we add a &#039;&#039;date&#039;&#039; setting controller, we&#039;ll have to make a view resolver and view function for it, and so on.&lt;br /&gt;
&lt;br /&gt;
2. Perhaps more importantly, view_resolver is now an &amp;quot;action&amp;quot; in our appliction.  Recall that invalid actions are captured, but try this:&lt;br /&gt;
&lt;br /&gt;
  # lua helloworld.lua no_such_action Alpine&lt;br /&gt;
     The following unhandled application error occured:&lt;br /&gt;
   &lt;br /&gt;
     controller: &amp;quot;hostname&amp;quot; does not have a &amp;quot;no_such_action&amp;quot; action.&lt;br /&gt;
  &lt;br /&gt;
  # lua helloworld.lua view_resolver Alpine &lt;br /&gt;
     The following unhandled application error occured:&lt;br /&gt;
   &lt;br /&gt;
     ./helloworld/app/hostname-controller.lua:27: attempt to index local &#039;f&#039; (a function value)&lt;br /&gt;
     stack traceback:&lt;br /&gt;
        ./helloworld/app/hostname-controller.lua:27: in function &#039;viewfunc&#039;&lt;br /&gt;
        ./mvc.lua:139: in function &amp;lt;./mvc.lua:90&amp;gt;&lt;br /&gt;
        [C]: in function &#039;xpcall&#039;&lt;br /&gt;
        ./mvc.lua:90: in function &#039;dispatch&#039;&lt;br /&gt;
        helloworld.lua:23: in main chunk&lt;br /&gt;
        [C]: ?&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Because view_resolver is an action in the worker table, the &#039;&#039;&#039;mvc.lua&#039;&#039;&#039; runs it; but it returns a function, not a table, and causes an unhandled exception in the view.&lt;br /&gt;
&lt;br /&gt;
== Move the view resolver to the application level ==&lt;br /&gt;
&lt;br /&gt;
The solution to both problems is to move the view resolver and the view function out of the controller&#039;s worker table, into the next higher level, in this case, the application&#039;s worker table:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
1. Delete the private.view and view_resolver functions from &#039;&#039;&#039;helloworld/app/hostname-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
2. Add the following to &#039;&#039;&#039;helloworld/app/app-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
 local private = {}&lt;br /&gt;
 &lt;br /&gt;
 private.view = function ( controller, action, viewtable )&lt;br /&gt;
         io.write(string.format(&amp;quot;Controller: %s  Action: %s\n&amp;quot;,&lt;br /&gt;
                 controller or &amp;quot;&amp;quot;, action or &amp;quot;&amp;quot;))&lt;br /&gt;
         io.write (&amp;quot;Returned a table with the following values:\n&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
         for k,v in pairs(viewtable) do&lt;br /&gt;
                 io.write(string.format(&amp;quot;%s\t%s\n&amp;quot;, k, v))&lt;br /&gt;
         end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 view_resolver = function (self)&lt;br /&gt;
         return function (viewtable)&lt;br /&gt;
                 return private.view (self.conf.controller, self.conf.action, viewtable)&lt;br /&gt;
         end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This creates a more &amp;quot;generic&amp;quot; view, but one that will work for any controller - not just &#039;&#039;&#039;hostname&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Now things work as they should:&lt;br /&gt;
&lt;br /&gt;
 # lua helloworld.lua update &amp;quot;one two&amp;quot;&lt;br /&gt;
   Controller: hostname  Action: update&lt;br /&gt;
   Returned a table with the following values:&lt;br /&gt;
   value   one two&lt;br /&gt;
   type    string&lt;br /&gt;
   msg     Hostname can contain alphanumerics only&lt;br /&gt;
 &lt;br /&gt;
 # lua helloworld.lua view_resolver &amp;quot;Alpine&amp;quot;&lt;br /&gt;
   The following unhandled application error occured:&lt;br /&gt;
   &lt;br /&gt;
   controller: &amp;quot;hostname&amp;quot; does not have a &amp;quot;view_resolver&amp;quot; action.&lt;br /&gt;
&lt;br /&gt;
= mvc load &amp;amp; exec special functions =&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;mvc.lua&#039;&#039;&#039; module has a provision for executing code on module load, prior to executing the controller&#039;s action, just after executing the controller&#039;s action, and on module unload.&lt;br /&gt;
&lt;br /&gt;
This is done with the mvc table in the controller.  To demonstrate, let&#039;s add a few functions to &#039;&#039;&#039;helloworld/app/app-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
 mvc = {}&lt;br /&gt;
 mvc.on_load = function (self, parent)&lt;br /&gt;
         print (&amp;quot;This is the app controller&#039;s on_load function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 mvc.pre_exec = function (self)&lt;br /&gt;
         print (&amp;quot;This is the app controller&#039;s pre_exec function&amp;quot;)&lt;br /&gt;
 end &lt;br /&gt;
 &lt;br /&gt;
 mvc.post_exec = function (self)&lt;br /&gt;
         print (&amp;quot;This is the app controller&#039;s post_exec function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
 mvc.on_unload = function (self)&lt;br /&gt;
         print (&amp;quot;This is the app controller&#039;s on_unload function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
Now running our script shows when the functions get called:&lt;br /&gt;
&lt;br /&gt;
 # lua helloworld.lua update &amp;quot;Alpine&amp;quot;&lt;br /&gt;
   This is the app controller&#039;s on_load function&lt;br /&gt;
   This is the app controller&#039;s pre_exec function&lt;br /&gt;
   This is the app controller&#039;s post_exec function&lt;br /&gt;
   Controller: hostname  Action: update&lt;br /&gt;
   Returned a table with the following values:&lt;br /&gt;
   value   Alpine&lt;br /&gt;
   type    string&lt;br /&gt;
   This is the app controller&#039;s on_unload function&lt;br /&gt;
&lt;br /&gt;
We can add mvc functions to a specific controller, as well.  Add this to &#039;&#039;&#039;helloworld/app/hostname-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
 mvc = {}&lt;br /&gt;
 mvc.on_load = function (self, parent)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s on_load function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 mvc.pre_exec = function (self)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s pre_exec function&amp;quot;)&lt;br /&gt;
 end &lt;br /&gt;
 &lt;br /&gt;
 mvc.post_exec = function (self)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s post_exec function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 mvc.on_unload = function (self)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s on_unload function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
And this happens:&lt;br /&gt;
&lt;br /&gt;
 # lua helloworld.lua update &amp;quot;Alpine&amp;quot;&lt;br /&gt;
   This is the app controller&#039;s on_load function&lt;br /&gt;
   This is the hostname controller&#039;s on_load function&lt;br /&gt;
   This is the hostname controller&#039;s pre_exec function&lt;br /&gt;
   This is the hostname controller&#039;s post_exec function&lt;br /&gt;
   This is the hostname controller&#039;s on_unload function&lt;br /&gt;
   Controller: hostname  Action: update&lt;br /&gt;
   Returned a table with the following values:&lt;br /&gt;
   value   Alpine&lt;br /&gt;
   type    string&lt;br /&gt;
   This is the app controller&#039;s on_unload function&lt;br /&gt;
&lt;br /&gt;
Note that both the &#039;&#039;app&#039;&#039; and &#039;&#039;hostname&#039;&#039; &#039;&#039;&#039;on_load&#039;&#039;&#039; and &#039;&#039;&#039;on_unload&#039;&#039;&#039; functions were run, but only the &#039;&#039;hostname&#039;&#039; &#039;&#039;&#039;pre_exec&#039;&#039;&#039; and &#039;&#039;&#039;post_exec&#039;&#039;&#039; functions ran.  This is because the pre and post exec functions are run as part of the &amp;quot;action&amp;quot;, and the &#039;&#039;&#039;dispatch&#039;&#039;&#039; function looks in the lowest-level controller for the pre/post_exec function.  Since &#039;&#039;hostname&#039;&#039; now defines those functions, it runs them.&lt;br /&gt;
&lt;br /&gt;
To run both the &#039;&#039;hostname&#039;&#039; and &#039;&#039;app&#039;&#039; pre_exec function, you must arrange for the &#039;&#039;hostname&#039;&#039; pre_exec function to call it&#039;s parent pre_exec:&lt;br /&gt;
&lt;br /&gt;
 mvc = {}&lt;br /&gt;
 mvc.on_load = function (self, parent)&lt;br /&gt;
        print (&amp;quot;This is the hostname controller&#039;s on_load function&amp;quot;)&lt;br /&gt;
        mvc.parent_pre_exec = parent.worker.mvc.pre_exec&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 mvc.pre_exec = function (self)&lt;br /&gt;
         mvc.parent_pre_exec (self)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s pre_exec function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
[[Category:ACF]] [[Category:Lua]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=ACF_mvc.lua_example&amp;diff=12727</id>
		<title>ACF mvc.lua example</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=ACF_mvc.lua_example&amp;diff=12727"/>
		<updated>2016-04-30T01:11:17Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Add example of MVC application using dispatch method&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Set the hostname with mvc.lua =&lt;br /&gt;
&lt;br /&gt;
In this example we will create a simple hostname-setting command-line application using mvc.lua.  Once the controller/model are built, you can use &#039;&#039;the same code&#039;&#039; to set the hostname via the web with a web-based application controller.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
For this example, we will assume you have root access on the linux box you are running on (preferably an Alpine Linux box!)&lt;br /&gt;
&lt;br /&gt;
== Get the mvc.lua module == &lt;br /&gt;
&lt;br /&gt;
Get the mvc.lua module from the git repository.&lt;br /&gt;
&lt;br /&gt;
 wget http://git.alpinelinux.org/cgit/acf-core/plain/lua/mvc.lua&lt;br /&gt;
&lt;br /&gt;
== Create a model and controller ==&lt;br /&gt;
&lt;br /&gt;
Create a file &#039;&#039;&#039;hostname-controller.lua&#039;&#039;&#039;, defining the functions that an &amp;quot;end user&amp;quot; could run. We will only create one action, edithostname, which will be used to read and update the hostname. Since the action makes changes to the system, it naturally takes the form of a &#039;form&#039;:&lt;br /&gt;
&lt;br /&gt;
=== hostname-controller.lua ===&lt;br /&gt;
 -- Controller for editing hostname&lt;br /&gt;
 local mymodule = {} &lt;br /&gt;
 &lt;br /&gt;
 mymodule.edithostname = function (self)&lt;br /&gt;
         return self.handle_form(self, self.model.get_hostname, self.model.set_hostname, self.clientdata, &amp;quot;Update&amp;quot;, &amp;quot;Edit Hostname&amp;quot;, &amp;quot;Hostname Updated&amp;quot;)&lt;br /&gt;
 end                                   &lt;br /&gt;
 &lt;br /&gt;
 return mymodule&lt;br /&gt;
&lt;br /&gt;
Create a file &#039;&#039;&#039;hostname-model.lua&#039;&#039;&#039;, defining the model functions to get and set the hostname.  We return a cfe table for each function including the form with the one entry for hostname:&lt;br /&gt;
&lt;br /&gt;
=== hostname-model.lua ===&lt;br /&gt;
 -- Model functions for retrieving / setting the hostname&lt;br /&gt;
 local mymodule = {}&lt;br /&gt;
 &lt;br /&gt;
 -- Create a cfe defining the form for editing the hostname and containing the current value&lt;br /&gt;
 mymodule.get_hostname = function(self, clientdata)&lt;br /&gt;
         local retval = cfe({ type=&amp;quot;group&amp;quot;, value={}, label=&amp;quot;Hostname&amp;quot; })&lt;br /&gt;
 &lt;br /&gt;
         -- Warning - io.popen has security risks, never pass user data to io.popen&lt;br /&gt;
         local f = io.popen (&amp;quot;/bin/hostname&amp;quot;)&lt;br /&gt;
         local n = f:read(&amp;quot;*a&amp;quot;) or &amp;quot;none&amp;quot;&lt;br /&gt;
         f:close()&lt;br /&gt;
         n=string.gsub(n, &amp;quot;\n$&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
         retval.value.hostname = cfe({ value=n, label=&amp;quot;Hostname&amp;quot; })&lt;br /&gt;
 &lt;br /&gt;
         return retval&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 -- Set the hostname from the value contained in the cfe created by get_hostname&lt;br /&gt;
 mymodule.set_hostname = function(self, hostnameform, action)&lt;br /&gt;
         local success = true&lt;br /&gt;
 &lt;br /&gt;
         -- Check to make sure the name is valid&lt;br /&gt;
         if (hostnameform.value.hostname.value == &amp;quot;&amp;quot;) then&lt;br /&gt;
                 success = false&lt;br /&gt;
                 hostnameform.value.hostname.errtxt = &amp;quot;Hostname must not be blank&amp;quot;&lt;br /&gt;
         elseif (#hostnameform.value.hostname.value &amp;gt; 16) then&lt;br /&gt;
                 success = false&lt;br /&gt;
                 hostnameform.value.hostname.errtxt = &amp;quot;Hostname must be 16 characters or less&amp;quot;&lt;br /&gt;
         elseif (string.find(hostnameform.value.hostname.value, &amp;quot;[^%w%_%-]&amp;quot;)) then&lt;br /&gt;
                 success = false&lt;br /&gt;
                 hostnameform.value.hostname.errtxt = &amp;quot;Hostname can contain alphanumerics only&amp;quot;&lt;br /&gt;
         end&lt;br /&gt;
 &lt;br /&gt;
         -- If it is valid, set the hostname&lt;br /&gt;
         if ( success ) then&lt;br /&gt;
                 local f = io.open(&amp;quot;/etc/hostname&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
                 if f then&lt;br /&gt;
                         f:write(hostnameform.value.hostname.value .. &amp;quot;\n&amp;quot;)&lt;br /&gt;
                         f:close()&lt;br /&gt;
                 end&lt;br /&gt;
                 -- Warning - io.popen has security risks, never pass user data to io.popen&lt;br /&gt;
                 f = io.popen (&amp;quot;/bin/hostname -F /etc/hostname&amp;quot;)&lt;br /&gt;
                 f:close()&lt;br /&gt;
         else&lt;br /&gt;
                 hostnameform.errtxt = &amp;quot;Failed to set hostname&amp;quot;&lt;br /&gt;
         end&lt;br /&gt;
 &lt;br /&gt;
         return hostnameform&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 return mymodule&lt;br /&gt;
&lt;br /&gt;
== Optionally test the model code (without mvc.lua)  ==&lt;br /&gt;
&lt;br /&gt;
If you want, you can create a &#039;&#039;&#039;test.lua&#039;&#039;&#039; script to validate the model code works on its own:&lt;br /&gt;
&lt;br /&gt;
=== test.lua ===&lt;br /&gt;
 require(&amp;quot;mvc&amp;quot;) -- Needed for cfe function definition&lt;br /&gt;
 m=require(&amp;quot;hostname-model&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 local form = m.get_hostname()&lt;br /&gt;
 form.value.hostname.value = arg[1] or &amp;quot;&amp;quot;&lt;br /&gt;
 form = m.set_hostname(nil, form)&lt;br /&gt;
 &lt;br /&gt;
 if form.errtxt then&lt;br /&gt;
         print(&amp;quot;FAILED: &amp;quot;..form.value.hostname.errtxt or form.errtxt)&lt;br /&gt;
 end&lt;br /&gt;
 form = m.get_hostname()&lt;br /&gt;
 print(form.value.hostname.value)&lt;br /&gt;
&lt;br /&gt;
You can then test this with:&lt;br /&gt;
&lt;br /&gt;
 #lua test.lua &amp;quot;Alpine&amp;quot;&lt;br /&gt;
  Alpine&lt;br /&gt;
&lt;br /&gt;
 #lua test.lua &amp;quot;Invalid Name&amp;quot;&lt;br /&gt;
  FAILED: Hostname can contain alphanumerics only&lt;br /&gt;
  Alpine&lt;br /&gt;
&lt;br /&gt;
== Add the package to the ACF framework ==&lt;br /&gt;
&lt;br /&gt;
To make the model and controller work within the ACF mvc.lua framework, we must do several things.   &lt;br /&gt;
&lt;br /&gt;
1. Install the ACF core package:&lt;br /&gt;
&lt;br /&gt;
 apk add acf-core&lt;br /&gt;
&lt;br /&gt;
Optionally, you can add the entire web-based ACF framework:&lt;br /&gt;
&lt;br /&gt;
 setup-acf&lt;br /&gt;
&lt;br /&gt;
2. Modify the ACF configuration file to look in /etc/acf/app/ for additional packages. Edit the /etc/acf/acf.conf file to add the &#039;&#039;/etc/acf/app/&#039;&#039; directory to the &#039;&#039;appdir&#039;&#039; comma-separated list:&lt;br /&gt;
&lt;br /&gt;
 appdir=/etc/acf/app/,/usr/share/acf/app/&lt;br /&gt;
&lt;br /&gt;
3. Move the model and controller to the new package directory. We will call the package &amp;quot;test&amp;quot;:&lt;br /&gt;
&lt;br /&gt;
 mkdir -p /etc/acf/app/test&lt;br /&gt;
 mv hostname-*.lua /etc/acf/app/test&lt;br /&gt;
&lt;br /&gt;
4. Test the new package using the acf-cli application:&lt;br /&gt;
&lt;br /&gt;
 # acf-cli /test/hostname/edithostname&lt;br /&gt;
 result = {}&lt;br /&gt;
 result[&amp;quot;label&amp;quot;] = &amp;quot;Edit Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;option&amp;quot;] = &amp;quot;Edit&amp;quot;&lt;br /&gt;
 result[&amp;quot;type&amp;quot;] = &amp;quot;form&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;label&amp;quot;] = &amp;quot;Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;type&amp;quot;] = &amp;quot;text&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;value&amp;quot;] = &amp;quot;Alpine&amp;quot;&lt;br /&gt;
 # acf-cli /test/hostname/edithostname hostname=test submit=true&lt;br /&gt;
 result = {}&lt;br /&gt;
 result[&amp;quot;descr&amp;quot;] = &amp;quot;Hostname Updated&amp;quot;&lt;br /&gt;
 result[&amp;quot;label&amp;quot;] = &amp;quot;Edit Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;option&amp;quot;] = &amp;quot;Edit&amp;quot;&lt;br /&gt;
 result[&amp;quot;type&amp;quot;] = &amp;quot;form&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;label&amp;quot;] = &amp;quot;Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;type&amp;quot;] = &amp;quot;text&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;value&amp;quot;] = &amp;quot;test&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Note that the action string passed to acf-cli is of the form &amp;quot;/prefix/controller/action&amp;quot;. The prefix is the path within /etc/acf/app where the controller may be found, so &amp;quot;/test/&amp;quot; for our case. The controller is determined from the controller file name, so &amp;quot;hostname&amp;quot; for our case. And, the action corresponds to the function within the controller to call, so &amp;quot;edithostname&amp;quot; for our case. Also note that, in the two examples above, the first case reads the existing hostname and the second case updates it. The output of the acf-cli application is a serialized version of the cfe form, which is good for testing but not too useful in real life.&lt;br /&gt;
&lt;br /&gt;
== Enable the package in the ACF web interface ==&lt;br /&gt;
&lt;br /&gt;
1. Add the web-based ACF framework:&lt;br /&gt;
&lt;br /&gt;
 setup-acf&lt;br /&gt;
&lt;br /&gt;
2. Configure all users to have access to the new hostname action. Edit &amp;quot;/etc/acf/app/test/hostname.roles&amp;quot; file and add GUEST permission for the edithostname action:&lt;br /&gt;
&lt;br /&gt;
 echo &amp;quot;GUEST=hostname/edithostname&amp;quot; &amp;gt; /etc/acf/app/test/hostname.roles&lt;br /&gt;
&lt;br /&gt;
The new action should now be visible by browsing to &#039;&#039;https://IP-of-host/cgi-bin/acf/test/hostname/edithostname&#039;&#039;. Obviously you might want to reconsider providing GUEST access to this action, because this allows unauthenticated users to modify your hostname.&lt;br /&gt;
&lt;br /&gt;
3. Add the new hostname action to the ACF menu. Edit &amp;quot;/etc/acf/app/test/hostname.menu&amp;quot; file and add a menu item:&lt;br /&gt;
&lt;br /&gt;
 echo &amp;quot;Test Hostname Edit edithostname&amp;quot; &amp;gt; /etc/acf/app/test/hostname.menu&lt;br /&gt;
&lt;br /&gt;
You will need to log off from the ACF interface (or delete the session cookie) before the new menu item will be visible.&lt;br /&gt;
&lt;br /&gt;
== Make an MVC based application ==&lt;br /&gt;
You have two options for creating an MVC based application of your own.&lt;br /&gt;
&lt;br /&gt;
1. Use the &#039;&#039;dispatch&#039;&#039; function. This is the method used by the web interface and the acf-cli application. The action to be dispatched is passed in a string format &#039;&#039;/prefix/controller/action&#039;&#039; and the input values are passed in as a clientinfo table. Output is determined by the view.&lt;br /&gt;
&lt;br /&gt;
2. Use the &#039;&#039;new&#039;&#039; function to load the desired controller and then directly call the controller actions. For handling forms and user input, the application will call the action once to retrieve the form cfe, then fill in the desired input values into the cfe and call the action again to submit the form and perform the desired action. The MVC application is responsible for any user interaction and display of the results.&lt;br /&gt;
&lt;br /&gt;
=== Use the Dispatch method ===&lt;br /&gt;
==== test_dispatch ====&lt;br /&gt;
 #!/usr/bin/lua&lt;br /&gt;
 -- Simple CLI based mvc application&lt;br /&gt;
 &lt;br /&gt;
 -- load the mvc module&lt;br /&gt;
 mvc = require(&amp;quot;acf.mvc&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 -- create an new &amp;quot;mvc object&amp;quot;&lt;br /&gt;
 MVC=mvc:new()&lt;br /&gt;
 &lt;br /&gt;
 -- load the config file so we can find the appdir&lt;br /&gt;
 MVC:read_config(&amp;quot;acf&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 -- dispatch the request&lt;br /&gt;
 local clientdata = {hostname=arg[1], viewtype=arg[2], submit=&amp;quot;Update&amp;quot;}&lt;br /&gt;
 MVC:dispatch(&amp;quot;/test/&amp;quot;, &amp;quot;hostname&amp;quot;, &amp;quot;edithostname&amp;quot;, clientdata)&lt;br /&gt;
 &lt;br /&gt;
 -- destroy the mvc object&lt;br /&gt;
 MVC:destroy()&lt;br /&gt;
&lt;br /&gt;
Test the application. As can be seen in the code above, the first argument is the new hostname and the second is a viewtype.&lt;br /&gt;
&lt;br /&gt;
 alpine:~# chmod 755 test_dispatch &lt;br /&gt;
 alpine:~# ./test_dispatch test&lt;br /&gt;
 test:~# ./test_dispatch alpine&lt;br /&gt;
 alpine:~#&lt;br /&gt;
&lt;br /&gt;
Note that the application functions, but there is no output. This is because there is no viewtype supplied. There is built-in support for these standard viewtypes (not all apply): html, json, stream, serialized&lt;br /&gt;
&lt;br /&gt;
 alpine:~# ./test_dispatch test serialized&lt;br /&gt;
 result = {}&lt;br /&gt;
 result[&amp;quot;descr&amp;quot;] = &amp;quot;Hostname Updated&amp;quot;&lt;br /&gt;
 result[&amp;quot;label&amp;quot;] = &amp;quot;Edit Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;option&amp;quot;] = &amp;quot;Update&amp;quot;&lt;br /&gt;
 result[&amp;quot;type&amp;quot;] = &amp;quot;form&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;label&amp;quot;] = &amp;quot;Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;type&amp;quot;] = &amp;quot;text&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;value&amp;quot;] = &amp;quot;test&amp;quot;&lt;br /&gt;
 test:~# ./test_dispatch alpine json&lt;br /&gt;
 {&amp;quot;type&amp;quot;:&amp;quot;form&amp;quot;,&amp;quot;label&amp;quot;:&amp;quot;Edit Hostname&amp;quot;,&amp;quot;value&amp;quot;:{&amp;quot;hostname&amp;quot;:{&amp;quot;value&amp;quot;:&amp;quot;alpine&amp;quot;,&amp;quot;type&amp;quot;:&amp;quot;text&amp;quot;,&amp;quot;label&amp;quot;:&amp;quot;Hostname&amp;quot;}},&amp;quot;option&amp;quot;:&amp;quot;Update&amp;quot;,&amp;quot;descr&amp;quot;:&amp;quot;Hostname Updated&amp;quot;}&lt;br /&gt;
 alpine:~# &lt;br /&gt;
&lt;br /&gt;
You can also use a custom viewtype and/or view to customize the output. This relies on haserl to parse the view file, and haserl lua functions are only available when launched from haserl scripts (it would be nice if haserl were available as a standalone lua library). Haserl scripts expect to be launched by a web browser to handle CGI data, so passing input is trickier. I&#039;m sure there is a better way to do this, but this is just an example:&lt;br /&gt;
&lt;br /&gt;
==== test_haserl ====&lt;br /&gt;
 #!/usr/bin/haserl-lua5.2 --shell=lua&lt;br /&gt;
 &amp;lt;%&lt;br /&gt;
 -- Simple CLI based mvc application&lt;br /&gt;
 &lt;br /&gt;
 -- load the mvc module&lt;br /&gt;
 mvc = require(&amp;quot;acf.mvc&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 -- create an new &amp;quot;mvc object&amp;quot;&lt;br /&gt;
 MVC=mvc:new()&lt;br /&gt;
 &lt;br /&gt;
 -- load the config file so we can find the appdir&lt;br /&gt;
 MVC:read_config(&amp;quot;acf&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 -- dispatch the request&lt;br /&gt;
 local clientdata = {hostname=FORM.hostname, viewtype=FORM.viewtype, submit=&amp;quot;Update&amp;quot;}&lt;br /&gt;
 MVC:dispatch(&amp;quot;/test/&amp;quot;, &amp;quot;hostname&amp;quot;, &amp;quot;edithostname&amp;quot;, clientdata)&lt;br /&gt;
 &lt;br /&gt;
 -- destroy the mvc object&lt;br /&gt;
 MVC:destroy()&lt;br /&gt;
 %&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /etc/acf/app/test/hostname-edithostname-text.lsp ====&lt;br /&gt;
 &amp;lt;%&lt;br /&gt;
 local data, viewlibrary, page_info, session = ... &lt;br /&gt;
 &lt;br /&gt;
 if data.errtxt then&lt;br /&gt;
         print(data:print_errtxt())&lt;br /&gt;
 else&lt;br /&gt;
         print(data.descr)&lt;br /&gt;
 end&lt;br /&gt;
 %&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Test the application&lt;br /&gt;
&lt;br /&gt;
 test:~# chmod 755 test_haserl &lt;br /&gt;
 test:~# QUERY_STRING=&#039;hostname=alpine&amp;amp;viewtype=text&#039; REQUEST_METHOD=GET ./test_haserl&lt;br /&gt;
 Hostname Updated&lt;br /&gt;
 alpine:~# QUERY_STRING=&#039;hostname=asdfasdfasdfasdfasdf&amp;amp;viewtype=text&#039; REQUEST_METHOD=GET ./test_haserl&lt;br /&gt;
 Failed to set hostname&lt;br /&gt;
 hostname: Hostname must be 16 characters or less&lt;br /&gt;
 alpine:~# &lt;br /&gt;
&lt;br /&gt;
=== Use the New method ===&lt;br /&gt;
Todo&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Obsolete}}&lt;br /&gt;
4. Create a dispatch wrapper program, named &#039;&#039;&#039;helloworld.lua&#039;&#039;&#039; in the current directory:&lt;br /&gt;
&lt;br /&gt;
 -- Simple CLI based mvc application&lt;br /&gt;
 &lt;br /&gt;
 -- this is to get around having to store&lt;br /&gt;
 -- the config file in /etc/helloworld/helloworld.conf&lt;br /&gt;
 ENV={}&lt;br /&gt;
 ENV.HOME=&amp;quot;.&amp;quot; &lt;br /&gt;
 &lt;br /&gt;
 -- load the module&lt;br /&gt;
 require(&amp;quot;mvc&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 -- create an new &amp;quot;mvc object&amp;quot;&lt;br /&gt;
 MVC=mvc:new()&lt;br /&gt;
 &lt;br /&gt;
 -- load the config file so we can find the appdir&lt;br /&gt;
 MVC:read_config(&amp;quot;helloworld&amp;quot;) &lt;br /&gt;
 &lt;br /&gt;
 -- create an application container&lt;br /&gt;
 APP=MVC:new(&amp;quot;app&amp;quot;)&lt;br /&gt;
  &lt;br /&gt;
 -- dispatch the request&lt;br /&gt;
 APP.clientdata.hostname=arg[2]&lt;br /&gt;
 APP:dispatch( &amp;quot;&amp;quot;, &amp;quot;hostname&amp;quot;, (arg[1] or &amp;quot;&amp;quot;))&lt;br /&gt;
&lt;br /&gt;
 -- destroy the mvc objects&lt;br /&gt;
 APP:destroy()&lt;br /&gt;
 MVC:destroy()&lt;br /&gt;
&lt;br /&gt;
This application loads the &amp;quot;mvc.lua&amp;quot; framework, creates an mvc &amp;quot;object&amp;quot; named &amp;quot;MVC&amp;quot;, then reads the helloworld.conf file to find out where the app dir is (helloworld/app/).  It then loads the &#039;&#039;&#039;app-controller.lua&#039;&#039;&#039; into a new &amp;quot;application level&amp;quot; object named &#039;&#039;&#039;APP&#039;&#039;&#039;.  Finally, it sets the clientdata and dispatches the hostname-controller/model pair.&lt;br /&gt;
&lt;br /&gt;
5.  Test the application:&lt;br /&gt;
 # hostname&lt;br /&gt;
 Alpine&lt;br /&gt;
 # lua helloworld.lua no-such-function foo&lt;br /&gt;
 The following unhandled application error occured:&lt;br /&gt;
 &lt;br /&gt;
 controller: &amp;quot;hostname&amp;quot; does not have a &amp;quot;no-such-function&amp;quot; action.&lt;br /&gt;
 # hostname&lt;br /&gt;
 Alpine&lt;br /&gt;
&lt;br /&gt;
 # hostname&lt;br /&gt;
 Alpine&lt;br /&gt;
 # lua helloworld.lua update Alline&lt;br /&gt;
 Your controller and application did not specify a view resolver.&lt;br /&gt;
 The MVC framework has no view available. sorry.&lt;br /&gt;
 # hostname&lt;br /&gt;
 Alline&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Note in the second case the hostname &#039;&#039;was&#039;&#039; changed, although the application does not know how to report success.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Create a view resolver and view formatter ==&lt;br /&gt;
&lt;br /&gt;
The view resolver is a function that returns a function that processes the view. The returned function receives input from the controller and generates the output to be displayed.&lt;br /&gt;
&lt;br /&gt;
We will build a very simple view resolver and view processor for our application. Add this to the end of &#039;&#039;&#039;helloworld/app/hostname-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 local private = {} &lt;br /&gt;
 &lt;br /&gt;
 private.view = function (f)&lt;br /&gt;
        if (f.msg) then&lt;br /&gt;
                print( (f.value or &amp;quot;&amp;quot;) .. &amp;quot; is not a valid hostname &amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                print (&amp;quot;Hostname is currently &amp;quot; .. f.value )&lt;br /&gt;
        end&lt;br /&gt;
 end &lt;br /&gt;
 &lt;br /&gt;
 view_resolver = function (self)&lt;br /&gt;
         return private.view&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Now we can test:&lt;br /&gt;
&lt;br /&gt;
 # lua helloworld.lua update &amp;quot;1 2 3&amp;quot;&lt;br /&gt;
    1 2 3 is not a valid hostname &lt;br /&gt;
 # lua helloworld.lua update        &lt;br /&gt;
    is not a valid hostname &lt;br /&gt;
 # lua helloworld.lua update Alpine&lt;br /&gt;
   Hostname is currently Alpine&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
But we have two problems:&lt;br /&gt;
&lt;br /&gt;
1. We now have to make a view resolver and view function for every controller.  If we add a &#039;&#039;date&#039;&#039; setting controller, we&#039;ll have to make a view resolver and view function for it, and so on.&lt;br /&gt;
&lt;br /&gt;
2. Perhaps more importantly, view_resolver is now an &amp;quot;action&amp;quot; in our appliction.  Recall that invalid actions are captured, but try this:&lt;br /&gt;
&lt;br /&gt;
  # lua helloworld.lua no_such_action Alpine&lt;br /&gt;
     The following unhandled application error occured:&lt;br /&gt;
   &lt;br /&gt;
     controller: &amp;quot;hostname&amp;quot; does not have a &amp;quot;no_such_action&amp;quot; action.&lt;br /&gt;
  &lt;br /&gt;
  # lua helloworld.lua view_resolver Alpine &lt;br /&gt;
     The following unhandled application error occured:&lt;br /&gt;
   &lt;br /&gt;
     ./helloworld/app/hostname-controller.lua:27: attempt to index local &#039;f&#039; (a function value)&lt;br /&gt;
     stack traceback:&lt;br /&gt;
        ./helloworld/app/hostname-controller.lua:27: in function &#039;viewfunc&#039;&lt;br /&gt;
        ./mvc.lua:139: in function &amp;lt;./mvc.lua:90&amp;gt;&lt;br /&gt;
        [C]: in function &#039;xpcall&#039;&lt;br /&gt;
        ./mvc.lua:90: in function &#039;dispatch&#039;&lt;br /&gt;
        helloworld.lua:23: in main chunk&lt;br /&gt;
        [C]: ?&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Because view_resolver is an action in the worker table, the &#039;&#039;&#039;mvc.lua&#039;&#039;&#039; runs it; but it returns a function, not a table, and causes an unhandled exception in the view.&lt;br /&gt;
&lt;br /&gt;
== Move the view resolver to the application level ==&lt;br /&gt;
&lt;br /&gt;
The solution to both problems is to move the view resolver and the view function out of the controller&#039;s worker table, into the next higher level, in this case, the application&#039;s worker table:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
1. Delete the private.view and view_resolver functions from &#039;&#039;&#039;helloworld/app/hostname-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
2. Add the following to &#039;&#039;&#039;helloworld/app/app-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
 local private = {}&lt;br /&gt;
 &lt;br /&gt;
 private.view = function ( controller, action, viewtable )&lt;br /&gt;
         io.write(string.format(&amp;quot;Controller: %s  Action: %s\n&amp;quot;,&lt;br /&gt;
                 controller or &amp;quot;&amp;quot;, action or &amp;quot;&amp;quot;))&lt;br /&gt;
         io.write (&amp;quot;Returned a table with the following values:\n&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
         for k,v in pairs(viewtable) do&lt;br /&gt;
                 io.write(string.format(&amp;quot;%s\t%s\n&amp;quot;, k, v))&lt;br /&gt;
         end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 view_resolver = function (self)&lt;br /&gt;
         return function (viewtable)&lt;br /&gt;
                 return private.view (self.conf.controller, self.conf.action, viewtable)&lt;br /&gt;
         end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This creates a more &amp;quot;generic&amp;quot; view, but one that will work for any controller - not just &#039;&#039;&#039;hostname&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Now things work as they should:&lt;br /&gt;
&lt;br /&gt;
 # lua helloworld.lua update &amp;quot;one two&amp;quot;&lt;br /&gt;
   Controller: hostname  Action: update&lt;br /&gt;
   Returned a table with the following values:&lt;br /&gt;
   value   one two&lt;br /&gt;
   type    string&lt;br /&gt;
   msg     Hostname can contain alphanumerics only&lt;br /&gt;
 &lt;br /&gt;
 # lua helloworld.lua view_resolver &amp;quot;Alpine&amp;quot;&lt;br /&gt;
   The following unhandled application error occured:&lt;br /&gt;
   &lt;br /&gt;
   controller: &amp;quot;hostname&amp;quot; does not have a &amp;quot;view_resolver&amp;quot; action.&lt;br /&gt;
&lt;br /&gt;
= mvc load &amp;amp; exec special functions =&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;mvc.lua&#039;&#039;&#039; module has a provision for executing code on module load, prior to executing the controller&#039;s action, just after executing the controller&#039;s action, and on module unload.&lt;br /&gt;
&lt;br /&gt;
This is done with the mvc table in the controller.  To demonstrate, let&#039;s add a few functions to &#039;&#039;&#039;helloworld/app/app-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
 mvc = {}&lt;br /&gt;
 mvc.on_load = function (self, parent)&lt;br /&gt;
         print (&amp;quot;This is the app controller&#039;s on_load function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 mvc.pre_exec = function (self)&lt;br /&gt;
         print (&amp;quot;This is the app controller&#039;s pre_exec function&amp;quot;)&lt;br /&gt;
 end &lt;br /&gt;
 &lt;br /&gt;
 mvc.post_exec = function (self)&lt;br /&gt;
         print (&amp;quot;This is the app controller&#039;s post_exec function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
 mvc.on_unload = function (self)&lt;br /&gt;
         print (&amp;quot;This is the app controller&#039;s on_unload function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
Now running our script shows when the functions get called:&lt;br /&gt;
&lt;br /&gt;
 # lua helloworld.lua update &amp;quot;Alpine&amp;quot;&lt;br /&gt;
   This is the app controller&#039;s on_load function&lt;br /&gt;
   This is the app controller&#039;s pre_exec function&lt;br /&gt;
   This is the app controller&#039;s post_exec function&lt;br /&gt;
   Controller: hostname  Action: update&lt;br /&gt;
   Returned a table with the following values:&lt;br /&gt;
   value   Alpine&lt;br /&gt;
   type    string&lt;br /&gt;
   This is the app controller&#039;s on_unload function&lt;br /&gt;
&lt;br /&gt;
We can add mvc functions to a specific controller, as well.  Add this to &#039;&#039;&#039;helloworld/app/hostname-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
 mvc = {}&lt;br /&gt;
 mvc.on_load = function (self, parent)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s on_load function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 mvc.pre_exec = function (self)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s pre_exec function&amp;quot;)&lt;br /&gt;
 end &lt;br /&gt;
 &lt;br /&gt;
 mvc.post_exec = function (self)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s post_exec function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 mvc.on_unload = function (self)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s on_unload function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
And this happens:&lt;br /&gt;
&lt;br /&gt;
 # lua helloworld.lua update &amp;quot;Alpine&amp;quot;&lt;br /&gt;
   This is the app controller&#039;s on_load function&lt;br /&gt;
   This is the hostname controller&#039;s on_load function&lt;br /&gt;
   This is the hostname controller&#039;s pre_exec function&lt;br /&gt;
   This is the hostname controller&#039;s post_exec function&lt;br /&gt;
   This is the hostname controller&#039;s on_unload function&lt;br /&gt;
   Controller: hostname  Action: update&lt;br /&gt;
   Returned a table with the following values:&lt;br /&gt;
   value   Alpine&lt;br /&gt;
   type    string&lt;br /&gt;
   This is the app controller&#039;s on_unload function&lt;br /&gt;
&lt;br /&gt;
Note that both the &#039;&#039;app&#039;&#039; and &#039;&#039;hostname&#039;&#039; &#039;&#039;&#039;on_load&#039;&#039;&#039; and &#039;&#039;&#039;on_unload&#039;&#039;&#039; functions were run, but only the &#039;&#039;hostname&#039;&#039; &#039;&#039;&#039;pre_exec&#039;&#039;&#039; and &#039;&#039;&#039;post_exec&#039;&#039;&#039; functions ran.  This is because the pre and post exec functions are run as part of the &amp;quot;action&amp;quot;, and the &#039;&#039;&#039;dispatch&#039;&#039;&#039; function looks in the lowest-level controller for the pre/post_exec function.  Since &#039;&#039;hostname&#039;&#039; now defines those functions, it runs them.&lt;br /&gt;
&lt;br /&gt;
To run both the &#039;&#039;hostname&#039;&#039; and &#039;&#039;app&#039;&#039; pre_exec function, you must arrange for the &#039;&#039;hostname&#039;&#039; pre_exec function to call it&#039;s parent pre_exec:&lt;br /&gt;
&lt;br /&gt;
 mvc = {}&lt;br /&gt;
 mvc.on_load = function (self, parent)&lt;br /&gt;
        print (&amp;quot;This is the hostname controller&#039;s on_load function&amp;quot;)&lt;br /&gt;
        mvc.parent_pre_exec = parent.worker.mvc.pre_exec&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 mvc.pre_exec = function (self)&lt;br /&gt;
         mvc.parent_pre_exec (self)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s pre_exec function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
[[Category:ACF]] [[Category:Lua]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=GNOME&amp;diff=12537</id>
		<title>GNOME</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=GNOME&amp;diff=12537"/>
		<updated>2016-03-25T17:42:47Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Add a link to community repo&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Draft|It doesn&#039;t produce the result expected YET}}&lt;br /&gt;
= Initial setup =&lt;br /&gt;
Start by booting up Alpine (see [[Installation|these]] instructions on how to do that)&amp;lt;BR&amp;gt;&lt;br /&gt;
When you Alpine is up and running, do the initial setup.&lt;br /&gt;
{{Cmd|setup-alpine}}&lt;br /&gt;
{{Cmd|setup-xorg-base}}&lt;br /&gt;
&lt;br /&gt;
= Install packages =&lt;br /&gt;
Install basic desktop system and gnome packages. For Alpine Linux 3.3 and later, these packages are in the [[Enable_Community_Repository|community]] repo.&amp;lt;BR&amp;gt;&lt;br /&gt;
This might take a few minutes depending on your network speed. &lt;br /&gt;
{{Cmd|apk add alpine-desktop gnome-base lxdm}}&lt;br /&gt;
&amp;lt;BR&amp;gt;&lt;br /&gt;
Lxdm is a display manager.  You can use a different one such as slim by replacing {{Cmd|lxdm}} with {{Cmd|slim}}&lt;br /&gt;
&lt;br /&gt;
== Optional packages ==&lt;br /&gt;
=== Video and Input packages ===&lt;br /&gt;
You &amp;lt;u&amp;gt;might&amp;lt;/u&amp;gt; also want to install a package suitable for your video chipset and input devices.&amp;lt;BR&amp;gt;&lt;br /&gt;
For example, if you have an Sis video chipset install &#039;xf86-video-sis&#039;, for Intel video chipset install &#039;xf86-video-intel&#039;.&amp;lt;BR&amp;gt;&lt;br /&gt;
{{Cmd|apk add xf86-video-sis}}&lt;br /&gt;
and / or &lt;br /&gt;
{{Cmd|apk add xf86-input-synaptics}}&lt;br /&gt;
&lt;br /&gt;
If you are running a virtual machine (i.e in VirtualBox or VMware) you probably also want these video drivers:&lt;br /&gt;
{{Cmd|apk add xf86-video-vmware}}&lt;br /&gt;
&lt;br /&gt;
Run &#039;apk search xf86-video*&#039; to see available xf86-video packages.&amp;lt;BR&amp;gt;&lt;br /&gt;
Run &#039;apk search xf86-input*&#039; to see available xf86-input packages.&amp;lt;BR&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== acpid ===&lt;br /&gt;
If you installed your Alpine Linux as a VirtualBox or VMWare guest you might find it handy be able send ACPI shutdown.&amp;lt;BR&amp;gt;&lt;br /&gt;
{{Cmd|rc-update add acpid}}&lt;br /&gt;
&lt;br /&gt;
= Configure xorg-server (optional) =&lt;br /&gt;
You can configure xorg-server and make your modifications&lt;br /&gt;
{{Cmd|Xorg -configure}}&lt;br /&gt;
This will result in `/root/xorg.conf.new`. You can modify this file to fit your needs.&amp;lt;BR&amp;gt;&lt;br /&gt;
(When finished modifying and testing the above configuration file, move it to `/etc/X11/xorg.conf` for normal usage.)&lt;br /&gt;
&lt;br /&gt;
== udev ==&lt;br /&gt;
Adding udev might help you with some finicky hardware like touchpads.&lt;br /&gt;
{{Cmd|apk add udev&lt;br /&gt;
/etc/init.d/udev start &amp;amp;&amp;amp; /etc/init.d/udev-postmount start&lt;br /&gt;
rc-update add udev sysinit&lt;br /&gt;
rc-update add udev-postmount default&lt;br /&gt;
}}&lt;br /&gt;
Adding evdev might also be necessary, for example if the keyboard doesn&#039;t work in X...&lt;br /&gt;
{{Cmd|apk add xf86-input-evdev}}&lt;br /&gt;
&lt;br /&gt;
= Create user accounts =&lt;br /&gt;
Create a normal user account.&lt;br /&gt;
{{Cmd|adduser ncopa}}&lt;br /&gt;
&lt;br /&gt;
Optionally, give that user sudo permissions in /etc/sudoers.&lt;br /&gt;
&lt;br /&gt;
= Start your desktop =&lt;br /&gt;
Start lxdm and log in with your new user.&lt;br /&gt;
{{Cmd|rc-service lxdm start}}&lt;br /&gt;
&lt;br /&gt;
Once you have verified that it actually works you can make lxdm start up at boot:&lt;br /&gt;
{{Cmd|rc-update add lxdm}}&lt;br /&gt;
&lt;br /&gt;
Or if using slim:&lt;br /&gt;
&lt;br /&gt;
{{Cmd|rc-service slim start}}&lt;br /&gt;
&lt;br /&gt;
and once confirmed as working enable it at boot:&lt;br /&gt;
{{Cmd|rc-update add slim}}&lt;br /&gt;
&lt;br /&gt;
= Troubleshooting =&lt;br /&gt;
If you are unable to login, check /var/log/lxdm.log, there may be output there from X to indicate failed modules, etc.&lt;br /&gt;
&amp;lt;BR&amp;gt;&lt;br /&gt;
If you are unable to login, or you see an error &amp;quot;Failed to execute login command&amp;quot;, you should check ~/.xinitrc with your preferred text editor (vi, nano, etc) and ensure that it is set to boot into gnome.  To do this, the &#039;exec&#039; line (usually the last line in the file) should read &amp;quot;exec gnome-session&amp;quot;.&lt;br /&gt;
If ~/.xinitrc does not exist, create it and add the exec line.  this command will do it:&lt;br /&gt;
&lt;br /&gt;
{{Cmd|touch ~/.xinitrc &amp;amp;&amp;amp; echo &amp;quot;exec gnome-session&amp;quot; &amp;gt;&amp;gt; ~/.xinitrc}} &lt;br /&gt;
&lt;br /&gt;
[[Category:Desktop]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=Enable_Community_Repository&amp;diff=12536</id>
		<title>Enable Community Repository</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=Enable_Community_Repository&amp;diff=12536"/>
		<updated>2016-03-25T17:41:34Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: New page&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Starting with Alpine Linux version 3.3, there is a new repository called &#039;&#039;&#039;community&#039;&#039;&#039;. Many packages have been moved from the main repository to community to indicate that they are not guaranteed to be supported beyond six months. If you are using any of these packages, be sure to add the community repository.&lt;br /&gt;
&lt;br /&gt;
Edit the {{Path|/etc/apk/repositories}} file using an editor ({{Pkg|nano}} for instance) and add (or uncomment) a line like:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;http://dl-6.alpinelinux.org/alpine/edge/community&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
After adding the community repository, obtain the latest index of available packages:&lt;br /&gt;
{{Cmd|apk update}}&lt;br /&gt;
&lt;br /&gt;
{{Tip|Adding the &amp;lt;code&amp;gt;-U&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;--update-cache&amp;lt;/code&amp;gt; to another apk command, as in &amp;lt;code&amp;gt;apk add -U ...&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;apk upgrade -U&amp;lt;/code&amp;gt;, has the same effect as running &amp;lt;code&amp;gt;apk update&amp;lt;/code&amp;gt; before the other apk command.}}&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=Aports_tree&amp;diff=12425</id>
		<title>Aports tree</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=Aports_tree&amp;diff=12425"/>
		<updated>2016-02-25T04:44:58Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Add community, state that testing is only for edge&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The [http://git.alpinelinux.org/cgit/aports/ aports] tree contains for every package/program a directory with the corresponding [[APKBUILD_Reference|APKBUILD]] file. &#039;&#039;[http://git.alpinelinux.org/cgit/abuild/tree/sample.APKBUILD APKBUILD]&#039;&#039; is a file containing &amp;quot;recipes&amp;quot; on how something should be built/compiled. These files are used when building alpine from source.&lt;br /&gt;
&lt;br /&gt;
When Alpine Linux is compiled, you will no longer see (or have use of) the APKBUILD. It is not included in the &#039;iso&#039; or &#039;tar.gz&#039;. The [[Abuild|abuild]] script reads the APKBUILD and executes the steps needed to create a package.&lt;br /&gt;
&lt;br /&gt;
== Directories ==&lt;br /&gt;
There are some directories placed in the [http://git.alpinelinux.org/cgit/aports/ aports] tree. A short description of every directory can be found in this section.&lt;br /&gt;
&lt;br /&gt;
=== main ===&lt;br /&gt;
&#039;&#039;main&#039;&#039; contains the basic set of packages for Alpine Linux.&lt;br /&gt;
&lt;br /&gt;
=== community ===&lt;br /&gt;
&#039;&#039;community&#039;&#039; contains the additional packages that, for various reasons, are not guaranteed to be supported beyond six months.&lt;br /&gt;
&lt;br /&gt;
=== testing ===&lt;br /&gt;
All new packages goes to &#039;&#039;testing&#039;&#039; first. Package will be moved to &#039;&#039;main&#039;&#039; if there positive feedback or other good reasons. Packages in &#039;&#039;testing&#039;&#039; are not included in stable builds, but are only built for edge.&lt;br /&gt;
&lt;br /&gt;
=== non-free ===&lt;br /&gt;
The packages in &#039;&#039;non-free&#039;&#039; are violating the standards of the [http://www.fsf.org/ Free Software Foundation] (FSF) about copying, redistributing, and modifying computer programs in one or another way.&lt;br /&gt;
&lt;br /&gt;
=== unmaintained ===&lt;br /&gt;
When a package is no longer maintained and no longer builds, it is moved to &#039;unmaintained&#039;. It is mostly to not break the build servers while still have to original APKBUILD available in case someone feels for fixing it.&lt;br /&gt;
&lt;br /&gt;
== Fetch latest APKBUILD files == &lt;br /&gt;
While inside your [[Setting up the build environment 1.9|build environment]] you need to install some needed packages and you need to fetch the APKBUILD&#039;s from the server (fetch the aports tree).&amp;lt;BR&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Note:&#039;&#039;&#039; You only need to do these 2 steps once! Next time you can skip this part.&#039;&#039;&lt;br /&gt;
{{Cmd|apk add alpine-sdk}}&lt;br /&gt;
{{Cmd|cd ~}}&lt;br /&gt;
{{Cmd|git clone git://dev.alpinelinux.org/aports}}&lt;br /&gt;
When the above is done you might be interested in fetching the latest updates.&lt;br /&gt;
{{Cmd|cd ~/aports}}&lt;br /&gt;
{{Cmd|git pull}}&lt;br /&gt;
&lt;br /&gt;
[[Category:Development]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=Include:Using_Internet_Repositories_for_apk-tools&amp;diff=11639</id>
		<title>Include:Using Internet Repositories for apk-tools</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=Include:Using_Internet_Repositories_for_apk-tools&amp;diff=11639"/>
		<updated>2016-02-03T13:54:05Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Add community repo&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Edit the {{Path|/etc/apk/repositories}} file using an editor ({{Pkg|nano}} for instance) and if necessary, add references to the Alpine package repositories. In the example below, the reference to the Alpine CD is maintained, so that if the requested package is available on the local media, it will be obtained from there instead of being downloaded from the remote repository:&lt;br /&gt;
&lt;br /&gt;
{{Cat|/etc/apk/repositories|/media/cdrom/apks&lt;br /&gt;
http://dl-3.alpinelinux.org/alpine/v2.6/main}}&lt;br /&gt;
&lt;br /&gt;
Another example:&lt;br /&gt;
upgrading from version 2.6 to 2.7 simply change:&lt;br /&gt;
 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;http://dl-3.alpinelinux.org/alpine/v2.6/main&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
to&lt;br /&gt;
 &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt; http://dl-3.alpinelinux.org/alpine/v2.7/main&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
Thus, the file will now look like this:&lt;br /&gt;
{{Cat|/etc/apk/repositories|/media/cdrom/apks&lt;br /&gt;
http://dl-3.alpinelinux.org/alpine/v2.7/main}}&lt;br /&gt;
&lt;br /&gt;
{{Note|Starting with version 3.3, there is a new repository called &#039;&#039;&#039;community&#039;&#039;&#039;. Many packages have been moved from the main repository to community to indicate that they are not guaranteed to be supported beyond six months. If you are using any of these packages, be sure to add the community repository. For example: &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;http://dl-3.alpinelinux.org/alpine/v3.3/community&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
Only one repository is shown above; however, you may also replace &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;http://dl-3.alpinelinux.org/alpine/&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; with any of the mirrors below:&lt;br /&gt;
{{Mirrors}}&lt;br /&gt;
&lt;br /&gt;
After updating the repositories file, obtain the latest index of available packages:&lt;br /&gt;
{{Cmd|apk update}}&lt;br /&gt;
&lt;br /&gt;
{{Tip|Adding the &amp;lt;code&amp;gt;-U&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;--update-cache&amp;lt;/code&amp;gt; to another apk command, as in &amp;lt;code&amp;gt;apk add -U ...&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;apk upgrade -U&amp;lt;/code&amp;gt;, has the same effect as running &amp;lt;code&amp;gt;apk update&amp;lt;/code&amp;gt; before the other apk command.}}&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=ACF_core_principles&amp;diff=11578</id>
		<title>ACF core principles</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=ACF_core_principles&amp;diff=11578"/>
		<updated>2016-01-13T19:54:45Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Remove CRUD section, remove function type, change table type to structure&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;While the mvc.lua code provides a generic framework for any lua &amp;quot;mvc&amp;quot;-based application, &amp;quot;acf&amp;quot; is the component that makes the mvc.lua framework into a web configuration Application. Some ideas and rationales for application-wide settings are discussed here.&lt;br /&gt;
&lt;br /&gt;
= Use of &#039;&#039;cfe&#039;&#039; =&lt;br /&gt;
&lt;br /&gt;
A &#039;&#039;cfe&#039;&#039; (&#039;&#039;&#039;C&#039;&#039;&#039;onfiguration &#039;&#039;&#039;F&#039;&#039;&#039;ramework &#039;&#039;&#039;E&#039;&#039;&#039;ntity) is a way to pass data between a model, controller and view in a common way. There are many ways to do this (e.g. closures, AKClass); use of cfe is an arbitrary decision just to keep development moving forward.&lt;br /&gt;
&lt;br /&gt;
A cfe is a table with some fields guaranteed to exist. It is also a way to abstract user-modifiable data from view-centric HTML input types. ACF isn&#039;t necessarily web-only. With a different controller and views, it could be cli (or gui?!). The acf-cli application is an example of this.&lt;br /&gt;
&lt;br /&gt;
== Fields in all cfes  ==&lt;br /&gt;
&lt;br /&gt;
cfe&#039;s are constructed from a function in &amp;lt;tt&amp;gt;mvc.lua&amp;lt;/tt&amp;gt; that returns an anonymous table with the following fields&lt;br /&gt;
&lt;br /&gt;
{| border=1&lt;br /&gt;
 ! Field !! Default !! Description&lt;br /&gt;
 |-&lt;br /&gt;
 |value || &amp;quot;&amp;quot; || The value of the cfe (e.g. the IP address, hostname, password, etc.)&lt;br /&gt;
 |-&lt;br /&gt;
 |type || &amp;quot;text&amp;quot; || The type of entity (see below)&lt;br /&gt;
 |-&lt;br /&gt;
 |label || &amp;quot;&amp;quot;  || User-readable label for the value&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The reason for having these fields pre-defined is to allow models, controllers and views to use the indexes without having to first check if they exist. For example the entity will always have a value and type.&lt;br /&gt;
&lt;br /&gt;
cfe&#039;s can have other fields.  Some common fields that may, or may not, be present:&lt;br /&gt;
&lt;br /&gt;
{| border=1&lt;br /&gt;
 ! Field !! Description&lt;br /&gt;
 |-&lt;br /&gt;
 |errtxt || Text explaining why validation failed&lt;br /&gt;
 |-&lt;br /&gt;
 |option || A list of options for this value&lt;br /&gt;
 |-&lt;br /&gt;
 |descr || User-readable description for the value&lt;br /&gt;
 |-&lt;br /&gt;
 |seq || Numeric sequence suggesting display order for cfe&#039;s in a group or form&lt;br /&gt;
 |-&lt;br /&gt;
 |default || Default value (can be displayed to user)&lt;br /&gt;
 |-&lt;br /&gt;
 |readonly || If present, indicates that the information is to be displayed, but not editable, typically used in forms&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
To set fields or overwrite field defaults, specify a table in the argument list to the cfe constructor:&lt;br /&gt;
&lt;br /&gt;
:mycfe = cfe({label=&amp;quot;User&amp;quot;, value=&amp;quot;asdf&amp;quot;, errtxt=&amp;quot;Invalid User&amp;quot;})&lt;br /&gt;
is equivalent to&lt;br /&gt;
:mycfe = {label=&amp;quot;User&amp;quot;, value=&amp;quot;asdf&amp;quot;, type=&amp;quot;text&amp;quot;, errtxt=&amp;quot;Invalid User&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
== cfe types ==&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;type&#039;&#039; field of a cfe can be one of the following:&lt;br /&gt;
&lt;br /&gt;
{| border=1&lt;br /&gt;
 ! type !! Description !! Modifiers &lt;br /&gt;
 |-&lt;br /&gt;
 | text || a text field, typically one line of text ||&lt;br /&gt;
 |-&lt;br /&gt;
 | longtext || a multi-line text field, like a textarea  ||&lt;br /&gt;
 |-&lt;br /&gt;
 | select || a select list || &#039;&#039;value&#039;&#039; is the currently selected item &amp;lt;br&amp;gt;&#039;&#039;option&#039;&#039; is an array of select options &lt;br /&gt;
 |-&lt;br /&gt;
 | multi || a multi-select list || &#039;&#039;value&#039;&#039; is an array of selected items &amp;lt;br&amp;gt;&#039;&#039;option&#039;&#039; is an array of select options &lt;br /&gt;
 |-&lt;br /&gt;
 | list || a list || &#039;&#039;value&#039;&#039; is an array of strings&lt;br /&gt;
 |-&lt;br /&gt;
 | boolean || true or false ||&lt;br /&gt;
 |-&lt;br /&gt;
 | raw || raw binary data || &lt;br /&gt;
 |-&lt;br /&gt;
 | form || a set of cfe&#039;s that make up a form || &#039;&#039;value&#039;&#039; is a table of cfe&#039;s that make up a form (the table should be name-indexed)&amp;lt;br&amp;gt;&#039;&#039;option&#039;&#039; is the command name to save changes (button name for HTML)&amp;lt;br&amp;gt;&#039;&#039;descr&#039;&#039; or &#039;&#039;errtxt&#039;&#039; may contain the result of a save attempt&lt;br /&gt;
 |-&lt;br /&gt;
 |  group || a set of cfe&#039;s that make up an anonymous group || &#039;&#039;value&#039;&#039; is a table of grouped cfe&#039;s (can be used to pass several items to a view) (the table should be name-indexed)&lt;br /&gt;
 |-&lt;br /&gt;
 |  structure || a Lua table with no further type info || &lt;br /&gt;
 |-&lt;br /&gt;
 |  password || a password which should not be readable by a user || &lt;br /&gt;
 |-&lt;br /&gt;
 |  hidden || a hidden field typically containing information used by ACF and not visible to a user || &lt;br /&gt;
 |}&lt;br /&gt;
&lt;br /&gt;
= The Model defines the object set =&lt;br /&gt;
&lt;br /&gt;
The model is responsible for writing and reading from the running system. The model typically does not need to know which part of a specific acf module it is running under. It is not mandatory that the model be lua &amp;quot;oop&amp;quot; as the rest of the system is. (the model may not have any need to know &amp;quot;self&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
Since the model can choose how much or how little of the system to expose, the basic data set should be defined in the model.&lt;br /&gt;
&lt;br /&gt;
[[Category:ACF]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=Algo_8180_Provisioning_With_ACF&amp;diff=11017</id>
		<title>Algo 8180 Provisioning With ACF</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=Algo_8180_Provisioning_With_ACF&amp;diff=11017"/>
		<updated>2015-06-09T19:13:25Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Manual firmware update is not necessary anymore&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Draft}}&lt;br /&gt;
= Overview =&lt;br /&gt;
This page describes how to provision [http://algosolutions.com/products/Audible-and-Visual-Alerting/8180-sip-audio-alerter.html Algo 8180] devices using [[SIP Provisioning On Alpine Linux]] based upon the ACF web interface. It is supported starting with Alpine Linux 3.2.&lt;br /&gt;
&lt;br /&gt;
= Setup Procedure=&lt;br /&gt;
== 1. Install provisioning server ==&lt;br /&gt;
First, install the provisioning server according to the instructions at [[SIP Provisioning On Alpine Linux | this page]].&lt;br /&gt;
== 2. Install firmware package ==&lt;br /&gt;
The latest supported firmware is included in a package:&lt;br /&gt;
 apk add acf-provisioning-algo&lt;br /&gt;
{{Note|If you are running-from-RAM, it is recommended to mount /var/ to a hard disk to prevent data loss. If you do so, you should fetch the firmware packages, rather than install them. This way, the firmware will not take up RAM. You can use the following command: apk fetch --quiet --stdout acf-provisioning-algo &amp;amp;#124; tar -C / -zvx}}&lt;br /&gt;
== 3. Configure init.cfg ==&lt;br /&gt;
The Algo will override ALL parameters when it is provisioned. If parameters are not provided by the provisioning server, they will be reset to default. Since not all parameters are included in the provisioning database, they must be configured statically in &#039;&#039;/var/www/provisioning/htdocs/Algo/init.cfg&#039;&#039;. You can use &#039;&#039;/var/www/provisioning/htdocs/Algo/init.cfg.sample&#039;&#039; as a starting point. You will probably need to modify the network configuration and provisioning server IP.&lt;br /&gt;
 cp /var/www/provisioning/htdocs/Algo/init.cfg.sample /var/www/provisioning/htdocs/Algo/init.cfg&lt;br /&gt;
== 4. Configure the Algo 8180 to connect to the provisioning server ==&lt;br /&gt;
Unfortunately, the DHCP option 66 does not seem to work in our testing. So, you must manually configure the Algo to connect to the provisioning server. Once you get the Algo device connected to the network, follow these steps:&lt;br /&gt;
*MENU Advanced Settings &amp;gt; Provisioning&lt;br /&gt;
**Provisioning Mode: Enabled&lt;br /&gt;
**Server Method: Static&lt;br /&gt;
**Static Server: Enter the IP address or FQDN of the provisioning server&lt;br /&gt;
**Download Method: HTTP&lt;br /&gt;
**Config Download Path: Algo&lt;br /&gt;
**Firmware Download Path: Algo&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=Algo_8180_Provisioning_With_ACF&amp;diff=11016</id>
		<title>Algo 8180 Provisioning With ACF</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=Algo_8180_Provisioning_With_ACF&amp;diff=11016"/>
		<updated>2015-06-09T16:04:46Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Add manual firmware upgrade&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Draft}}&lt;br /&gt;
= Overview =&lt;br /&gt;
This page describes how to provision [http://algosolutions.com/products/Audible-and-Visual-Alerting/8180-sip-audio-alerter.html Algo 8180] devices using [[SIP Provisioning On Alpine Linux]] based upon the ACF web interface. It is supported starting with Alpine Linux 3.2.&lt;br /&gt;
&lt;br /&gt;
= Setup Procedure=&lt;br /&gt;
== 1. Install provisioning server ==&lt;br /&gt;
First, install the provisioning server according to the instructions at [[SIP Provisioning On Alpine Linux | this page]].&lt;br /&gt;
== 2. Install firmware package ==&lt;br /&gt;
The latest supported firmware is included in a package:&lt;br /&gt;
 apk add acf-provisioning-algo&lt;br /&gt;
{{Note|If you are running-from-RAM, it is recommended to mount /var/ to a hard disk to prevent data loss. If you do so, you should fetch the firmware packages, rather than install them. This way, the firmware will not take up RAM. You can use the following command: apk fetch --quiet --stdout acf-provisioning-algo &amp;amp;#124; tar -C / -zvx}}&lt;br /&gt;
== 3. Configure init.cfg ==&lt;br /&gt;
The Algo will override ALL parameters when it is provisioned. If parameters are not provided by the provisioning server, they will be reset to default. Since not all parameters are included in the provisioning database, they must be configured statically in &#039;&#039;/var/www/provisioning/htdocs/Algo/init.cfg&#039;&#039;. You can use &#039;&#039;/var/www/provisioning/htdocs/Algo/init.cfg.sample&#039;&#039; as a starting point. You will probably need to modify the network configuration and provisioning server IP.&lt;br /&gt;
 cp /var/www/provisioning/htdocs/Algo/init.cfg.sample /var/www/provisioning/htdocs/Algo/init.cfg&lt;br /&gt;
== 4. Update the Algo firmware ==&lt;br /&gt;
Unfortunately, the Algo firmware update is a manual process:&lt;br /&gt;
*MENU System &amp;gt; Maintenance &amp;gt; Upgrade to New Firmware&lt;br /&gt;
**Method: From URL&lt;br /&gt;
**Upgrade URL: http://&amp;lt;IP or FQDN of provisioning&amp;gt;/Algo/algo-8180-2.4.fw&lt;br /&gt;
**Click Upgrade button&lt;br /&gt;
== 5. Configure the Algo 8180 to connect to the provisioning server ==&lt;br /&gt;
Unfortunately, the DHCP option 66 does not seem to work in our testing. So, you must manually configure the Algo to connect to the provisioning server. Once you get the Algo device connected to the network, follow these steps:&lt;br /&gt;
*MENU Advanced Settings &amp;gt; Provisioning&lt;br /&gt;
**Provisioning Mode: Enabled&lt;br /&gt;
**Server Method: Static&lt;br /&gt;
**Static Server: Enter the IP address or FQDN of the provisioning server&lt;br /&gt;
**Download Method: HTTP&lt;br /&gt;
**Config Download Path: Algo&lt;br /&gt;
**Firmware Download Path: Algo&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=SIP_Provisioning_On_Alpine_Linux&amp;diff=11015</id>
		<title>SIP Provisioning On Alpine Linux</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=SIP_Provisioning_On_Alpine_Linux&amp;diff=11015"/>
		<updated>2015-06-09T14:21:00Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Add link to Algo instructions&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Draft}}&lt;br /&gt;
= Overview =&lt;br /&gt;
This page describes how to install a basic provisioning server for SIP devices based upon Postgresql and the ACF web interface. It is built and tested on Alpine Linux 2.7, 3.0, 3.1, and 3.2.&lt;br /&gt;
&lt;br /&gt;
= Setup Procedure=&lt;br /&gt;
== 1. Configure the machine ==&lt;br /&gt;
Install and configure a basic Alpine Linux server with the ACF web interface.&lt;br /&gt;
 setup-alpine&lt;br /&gt;
 setup-acf&lt;br /&gt;
&lt;br /&gt;
== 2. Install packages ==&lt;br /&gt;
Here are the basic packages for the provisioning functionality:&lt;br /&gt;
 apk add acf-provisioning acf-postgresql acf-lighttpd&lt;br /&gt;
In addition, you can add the firmware packages for the SIP devices you want to support:&lt;br /&gt;
 apk add acf-provisioning-cisco acf-provisioning-linksys acf-provisioning-polycom acf-provisioning-snom&lt;br /&gt;
{{Note|If you are running-from-RAM, it is recommended to mount /var/ to a hard disk to prevent data loss. If you do so, you should fetch the firmware packages, rather than install them. This way, the firmware will not take up RAM. You can use the following command: apk fetch --quiet --stdout acf-provisioning-polycom &amp;amp;#124; tar -C / -zvx}}&lt;br /&gt;
&lt;br /&gt;
== 3. Configure Lighttpd ==&lt;br /&gt;
Lighttpd is used to provide the HTTP provisioning interface for the SIP devices. The provisioning package contains a sample configuration that can be used for lighttpd:&lt;br /&gt;
 mv /etc/lighttpd/lighttpd.conf /etc/lighttpd/lighttpd.conf.orig&lt;br /&gt;
 ln -s /etc/provisioning/lighttpd.sample.conf /etc/lighttpd/lighttpd.conf&lt;br /&gt;
Modify mod_cgi.conf to treat CGI scripts as shell scripts, rather than perl:&lt;br /&gt;
 sed -i &#039;s~/usr/bin/perl&amp;quot;$~&amp;quot;~&#039; /etc/lighttpd/mod_cgi.conf&lt;br /&gt;
Start the server:&lt;br /&gt;
 /etc/init.d/lighttpd start&lt;br /&gt;
&lt;br /&gt;
== 4. Start Postgresql ==&lt;br /&gt;
The device parameter details are stored in a Postgresql database. We can use the default configuration, so we just start the service:&lt;br /&gt;
 /etc/init.d/postgresql start&lt;br /&gt;
&lt;br /&gt;
== 5. Point your SIP device to the new server for provisioning ==&lt;br /&gt;
Instructions for various manufacturers are included on separate pages:&lt;br /&gt;
*[[Algo 8180 Provisioning With ACF]]&lt;br /&gt;
&lt;br /&gt;
= ACF Web interface =&lt;br /&gt;
You can now browse to https://IPADDRESS to access the web interface. To log in for administration, use the root user and the system root password (this is the default configuration set up by setup-acf).&lt;br /&gt;
&lt;br /&gt;
[[Category:ACF]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=Algo_8180_Provisioning_With_ACF&amp;diff=11014</id>
		<title>Algo 8180 Provisioning With ACF</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=Algo_8180_Provisioning_With_ACF&amp;diff=11014"/>
		<updated>2015-06-09T14:04:20Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: First cut&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Draft}}&lt;br /&gt;
= Overview =&lt;br /&gt;
This page describes how to provision [http://algosolutions.com/products/Audible-and-Visual-Alerting/8180-sip-audio-alerter.html Algo 8180] devices using [[SIP Provisioning On Alpine Linux]] based upon the ACF web interface. It is supported starting with Alpine Linux 3.2.&lt;br /&gt;
&lt;br /&gt;
= Setup Procedure=&lt;br /&gt;
== 1. Install provisioning server ==&lt;br /&gt;
First, install the provisioning server according to the instructions at [[SIP Provisioning On Alpine Linux | this page]].&lt;br /&gt;
== 2. Install firmware package ==&lt;br /&gt;
The latest supported firmware is included in a package:&lt;br /&gt;
 apk add acf-provisioning-algo&lt;br /&gt;
{{Note|If you are running-from-RAM, it is recommended to mount /var/ to a hard disk to prevent data loss. If you do so, you should fetch the firmware packages, rather than install them. This way, the firmware will not take up RAM. You can use the following command: apk fetch --quiet --stdout acf-provisioning-algo &amp;amp;#124; tar -C / -zvx}}&lt;br /&gt;
== 3. Configure init.cfg ==&lt;br /&gt;
The Algo will override ALL parameters when it is provisioned. If parameters are not provided by the provisioning server, they will be reset to default. Since not all parameters are included in the provisioning database, they must be configured statically in &#039;&#039;/var/www/provisioning/htdocs/Algo/init.cfg&#039;&#039;. You can use &#039;&#039;/var/www/provisioning/htdocs/Algo/init.cfg.sample&#039;&#039; as a starting point. You will probably need to modify the network configuration and provisioning server IP.&lt;br /&gt;
 cp /var/www/provisioning/htdocs/Algo/init.cfg.sample /var/www/provisioning/htdocs/Algo/init.cfg&lt;br /&gt;
== 4. Configure the Algo 8180 to connect to the provisioning server ==&lt;br /&gt;
Unfortunately, the DHCP option 66 does not seem to work in our testing. So, you must manually configure the Algo to connect to the provisioning server. Once you get the Algo device connected to the network, follow these steps:&lt;br /&gt;
*MENU Advanced Settings &amp;gt; Provisioning&lt;br /&gt;
**Provisioning Mode: Enabled&lt;br /&gt;
**Server Method: Static&lt;br /&gt;
**Static Server: Enter the IP address or FQDN of the provisioning server&lt;br /&gt;
**Download Method: HTTP&lt;br /&gt;
**Config Download Path: Algo&lt;br /&gt;
**Firmware Download Path: Algo&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=SIP_Provisioning_On_Alpine_Linux&amp;diff=10938</id>
		<title>SIP Provisioning On Alpine Linux</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=SIP_Provisioning_On_Alpine_Linux&amp;diff=10938"/>
		<updated>2015-06-01T19:36:38Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Minor fix&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Draft}}&lt;br /&gt;
= Overview =&lt;br /&gt;
This page describes how to install a basic provisioning server for SIP devices based upon Postgresql and the ACF web interface. It is built and tested on Alpine Linux 2.7, 3.0, 3.1, and 3.2.&lt;br /&gt;
&lt;br /&gt;
= Setup Procedure=&lt;br /&gt;
== 1. Configure the machine ==&lt;br /&gt;
Install and configure a basic Alpine Linux server with the ACF web interface.&lt;br /&gt;
 setup-alpine&lt;br /&gt;
 setup-acf&lt;br /&gt;
&lt;br /&gt;
== 2. Install packages ==&lt;br /&gt;
Here are the basic packages for the provisioning functionality:&lt;br /&gt;
 apk add acf-provisioning acf-postgresql acf-lighttpd&lt;br /&gt;
In addition, you can add the firmware packages for the SIP devices you want to support:&lt;br /&gt;
 apk add acf-provisioning-cisco acf-provisioning-linksys acf-provisioning-polycom acf-provisioning-snom&lt;br /&gt;
{{Note|If you are running-from-RAM, it is recommended to mount /var/ to a hard disk to prevent data loss. If you do so, you should fetch the firmware packages, rather than install them. This way, the firmware will not take up RAM. You can use the following command: apk fetch --quiet --stdout acf-provisioning-polycom &amp;amp;#124; tar -C / -zvx}}&lt;br /&gt;
&lt;br /&gt;
== 3. Configure Lighttpd ==&lt;br /&gt;
Lighttpd is used to provide the HTTP provisioning interface for the SIP devices. The provisioning package contains a sample configuration that can be used for lighttpd:&lt;br /&gt;
 mv /etc/lighttpd/lighttpd.conf /etc/lighttpd/lighttpd.conf.orig&lt;br /&gt;
 ln -s /etc/provisioning/lighttpd.sample.conf /etc/lighttpd/lighttpd.conf&lt;br /&gt;
Modify mod_cgi.conf to treat CGI scripts as shell scripts, rather than perl:&lt;br /&gt;
 sed -i &#039;s~/usr/bin/perl&amp;quot;$~&amp;quot;~&#039; /etc/lighttpd/mod_cgi.conf&lt;br /&gt;
Start the server:&lt;br /&gt;
 /etc/init.d/lighttpd start&lt;br /&gt;
&lt;br /&gt;
== 4. Start Postgresql ==&lt;br /&gt;
The device parameter details are stored in a Postgresql database. We can use the default configuration, so we just start the service:&lt;br /&gt;
 /etc/init.d/postgresql start&lt;br /&gt;
&lt;br /&gt;
== 5. Point your SIP device to the new server for provisioning ==&lt;br /&gt;
&lt;br /&gt;
= ACF Web interface =&lt;br /&gt;
You can now browse to https://IPADDRESS to access the web interface. To log in for administration, use the root user and the system root password (this is the default configuration set up by setup-acf).&lt;br /&gt;
&lt;br /&gt;
[[Category:ACF]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=SIP_Provisioning_On_Alpine_Linux&amp;diff=10937</id>
		<title>SIP Provisioning On Alpine Linux</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=SIP_Provisioning_On_Alpine_Linux&amp;diff=10937"/>
		<updated>2015-06-01T16:53:40Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Draft of new document for seting up provisioning server&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Draft}}&lt;br /&gt;
= Overview =&lt;br /&gt;
This page describes how to install a basic provisioning server for SIP devices based upon Postgresql and the ACF web interface. It is built and tested on Alpine Linux 2.7, 3.0, 3.1, and 3.2.&lt;br /&gt;
&lt;br /&gt;
= Setup Procedure=&lt;br /&gt;
== 1. Configure the machine ==&lt;br /&gt;
Install and configure a basic Alpine Linux server with the ACF web interface.&lt;br /&gt;
 setup-alpine&lt;br /&gt;
 setup-acf&lt;br /&gt;
&lt;br /&gt;
== 2. Install packages ==&lt;br /&gt;
Here are the basic packages for the provisioning functionality:&lt;br /&gt;
 apk add acf-provisioning acf-postgresql acf-lighttpd&lt;br /&gt;
In addition, you can add the firmware packages for the SIP devices you want to support:&lt;br /&gt;
 apk add acf-provisioning-cisco acf-provisioning-linksys acf-provisioning-polycom acf-provisioning-snom&lt;br /&gt;
{{Note|If you are running-from-RAM, it is recommended to mount /var/ to a hard disk to prevent data loss. If you do so, you should fetch the firmware packages, rather than install them. This way, the firmware will not take up RAM. You can use the following command: apk fetch --quiet --stdout acf-provisioning-polycom | tar -C / -zvx}}&lt;br /&gt;
&lt;br /&gt;
== 3. Configure Lighttpd ==&lt;br /&gt;
Lighttpd is used to provide the HTTP provisioning interface for the SIP devices. The provisioning package contains a sample configuration that can be used for lighttpd:&lt;br /&gt;
 mv /etc/lighttpd/lighttpd.conf /etc/lighttpd/lighttpd.conf.orig&lt;br /&gt;
 ln -s /etc/provisioning/lighttpd.sample.conf /etc/lighttpd/lighttpd.conf&lt;br /&gt;
Modify mod_cgi.conf to treat CGI scripts as shell scripts, rather than perl:&lt;br /&gt;
 sed -i &#039;s~/usr/bin/perl&amp;quot;$~&amp;quot;~&#039; /etc/lighttpd/mod_cgi.conf&lt;br /&gt;
Start the server:&lt;br /&gt;
 /etc/init.d/lighttpd start&lt;br /&gt;
&lt;br /&gt;
== 4. Start Postgresql ==&lt;br /&gt;
The device parameter details are stored in a Postgresql database. We can use the default configuration, so we just start the service:&lt;br /&gt;
 /etc/init.d/postgresql start&lt;br /&gt;
&lt;br /&gt;
== 5. Point your SIP device to the new server for provisioning ==&lt;br /&gt;
&lt;br /&gt;
= ACF Web interface =&lt;br /&gt;
You can now browse to https://IPADDRESS to access the web interface. To log in for administration, use the root user and the system root password (this is the default configuration set up by setup-acf).&lt;br /&gt;
&lt;br /&gt;
[[Category:ACF]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=Freeswitch_Voicemail_On_Alpine_Linux&amp;diff=10866</id>
		<title>Freeswitch Voicemail On Alpine Linux</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=Freeswitch_Voicemail_On_Alpine_Linux&amp;diff=10866"/>
		<updated>2015-05-21T18:00:07Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Bad paths fixed for Alpine Linux 3.2 and later&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Overview =&lt;br /&gt;
This page describes how to install a basic voicemail server based upon Freeswitch including a web interface based upon ACF. It is built and tested on Alpine Linux 2.7, 3.0, 3.1, and the future 3.2.&lt;br /&gt;
&lt;br /&gt;
= Setup Procedure=&lt;br /&gt;
== 1. Configure the machine ==&lt;br /&gt;
Install and configure a basic Alpine Linux server with the ACF web interface.&lt;br /&gt;
 setup-alpine&lt;br /&gt;
 setup-acf&lt;br /&gt;
&lt;br /&gt;
== 2. Install Freeswitch ==&lt;br /&gt;
 apk add freeswitch freeswitch-sounds-en-us-callie-8000 freeswitch-flite acf-freeswitch acf-freeswitch-vmail&lt;br /&gt;
&lt;br /&gt;
== 3. Configure Freeswitch ==&lt;br /&gt;
We will use the Freeswitch sample configuration for the purposes of this document. It is not recommended to use this config in production, but it will allow us to quickly get voicemail up and running. Rather than installing the package, we will use apk to fetch the files.&lt;br /&gt;
 apk fetch --quiet --stdout freeswitch-sample-config | tar -C / -zvx&lt;br /&gt;
Modify the default password to avoid warnings and delays (obviously, you can choose something other than 12345 if you want)&lt;br /&gt;
 sed -i s/&amp;quot;default_password=1234&amp;quot;/&amp;quot;default_password=12345&amp;quot;/ /etc/freeswitch/vars.xml&lt;br /&gt;
Use Freeswitch XML curl to call into ACF to determine voicemail accounts. To do so, edit &#039;&#039;/etc/freeswitch/autoload_configs/xml_curl.conf.xml&#039;&#039; and add the following bindings:&lt;br /&gt;
     &amp;lt;binding name=&amp;quot;voicemaildialplan&amp;quot;&amp;gt;&lt;br /&gt;
       &amp;lt;param name=&amp;quot;gateway-url&amp;quot; value=&amp;quot;https://127.0.0.1/cgi-bin/acf/freeswitch-vmail/vmail/processdialplanxml&amp;quot; bindings=&amp;quot;dialplan&amp;quot;/&amp;gt;&lt;br /&gt;
     &amp;lt;/binding&amp;gt;&lt;br /&gt;
     &amp;lt;binding name=&amp;quot;voicemaildirectory&amp;quot;&amp;gt;&lt;br /&gt;
       &amp;lt;param name=&amp;quot;gateway-url&amp;quot; value=&amp;quot;https://127.0.0.1/cgi-bin/acf/freeswitch-vmail/vmail/processdirectoryxml&amp;quot; bindings=&amp;quot;directory&amp;quot;/&amp;gt;&lt;br /&gt;
     &amp;lt;/binding&amp;gt;&lt;br /&gt;
And modify &#039;&#039;/etc/freeswitch/autoload_configs/modules.conf.xml&#039;&#039; to load the mod_xml_curl module&lt;br /&gt;
 &amp;lt;load module=&amp;quot;mod_xml_curl&amp;quot;/&amp;gt;&lt;br /&gt;
{{Note|These following items are not needed for Alpine Linux 3.2 or later. They are needed on earlier versions due to bad default paths in the freeswitch package.}}&lt;br /&gt;
Fix the voicemail storage directory in &#039;&#039;/etc/freeswitch/autoload_configs/voicemail.conf.xml&#039;&#039; to point to &#039;&#039;/var/lib/freeswitch/voicemail&#039;&#039;&lt;br /&gt;
 &amp;lt;param name=&amp;quot;storage-dir&amp;quot; value=&amp;quot;/var/lib/freeswitch/voicemail&amp;quot;/&amp;gt;&lt;br /&gt;
Set the sounds directory in &#039;&#039;/etc/freeswitch/vars.xml&#039;&#039; by adding the following line:&lt;br /&gt;
 &amp;lt;X-PRE-PROCESS cmd=&amp;quot;set&amp;quot; data=&amp;quot;sounds_dir=/usr/share/freeswitch/sounds&amp;quot;/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4. Start Freeswitch ==&lt;br /&gt;
Start Freeswitch and add it to the default runlevel.&lt;br /&gt;
 rc-update add freeswitch&lt;br /&gt;
 /etc/init.d/freeswitch start&lt;br /&gt;
You should now be able to dial into Freeswitch voicemail by registering a SIP user agent to freeswitch (user=1000-1019, password=12345 as defined above) and directing a SIP call to any of the other extensions between 1000 and 1019, or the extension 4000. For each voicemail account, the default password is the same as the extension. For more details on this, please refer to Freeswitch documentation. Please keep in mind that these extensions and voicemail are part of the sample config and are not managed by ACF. We will add ACF-managed voicemail accounts below.&lt;br /&gt;
&lt;br /&gt;
== 5. Configure acf-freeswitch-vmail ==&lt;br /&gt;
Add the voicemail authenticator to the ACF configuration. This will allow voicemail users to log into ACF to check their voicemail.&lt;br /&gt;
 echo &amp;quot;authenticator = authenticator-freeswitch-vmail.lua,authenticator-plaintext&amp;quot; &amp;gt;&amp;gt; /etc/acf/acf.conf&lt;br /&gt;
Change the ACF voicemail domain to match the domain from the sample config. You should use the IP address of your voicemail server, as this is what the sample config uses.&lt;br /&gt;
 sed -i /^domain=/d /etc/freeswitchvmail.conf&lt;br /&gt;
 echo &amp;quot;domain=IPADDRESS&amp;quot; &amp;gt;&amp;gt; /etc/freeswitchvmail.conf&lt;br /&gt;
&lt;br /&gt;
= ACF Web interface =&lt;br /&gt;
You can now browse to https://IPADDRESS to access the web interface. To log in for administration, use the root user and the system root password (this is the default configuration set up by setup-acf).&lt;br /&gt;
== Users ==&lt;br /&gt;
The Voicemail | Users tab will display all of the voicemail users that are managed through the ACF web interface. At the start, this will be empty. When you create Users in the ACF, it will modify the freeswitch dialplan and directory (using xml_curl) to allow these users to receive and retrieve voicemail. It will also allow these users log into the ACF interface to check their voicemail and modify voicemail settings. The password defined here will control both ACF and IVR access.&lt;br /&gt;
== Voicemail ==&lt;br /&gt;
The Voicemail | Voicemail tab will allow you to view, listen to, download, forward, and delete voicemail messages.&lt;br /&gt;
== Config ==&lt;br /&gt;
The Voicemail | Config tab allows administrators to modify the acf-freeswitch-vmail configuration.&lt;br /&gt;
== Settings ==&lt;br /&gt;
The Voicemail | Settings tab allows voicemail users to modify their settings.&lt;br /&gt;
&lt;br /&gt;
= Demonstration =&lt;br /&gt;
We are now ready to create voicemail accounts with ACF. The freeswitch sample config creates a fairly full dialplan and we do not want to stomp on any of its features, so we will demonstrate accounts in the 2100 - 2199 range, which is unused.&lt;br /&gt;
# Dial extension 2100 to verify what happens when an unsupported extension is called&lt;br /&gt;
# Logged into ACF as root, create a voicemail user with extension 2100 and some password&lt;br /&gt;
# Dial extension 2100 again to verify that you can now leave a voicemail for extension 2100&lt;br /&gt;
# Dial extension 4000 and log in as user 2100 with the password you defined above to listen to the message&lt;br /&gt;
# Log out of ACF as root and log in again as user 2100 with the password you defined above to view your message&lt;br /&gt;
&lt;br /&gt;
= Other Features =&lt;br /&gt;
== Using PostgreSQL ==&lt;br /&gt;
Freeswitch-1.2.5 and acf-freeswitch-vmail-0.5.0 fully support Postgresql as the voicemail database. These versions are available in Alpine Linux 3.2 and later. To configure it:&lt;br /&gt;
=== 1. Install Postgresql ===&lt;br /&gt;
 apk add postgresql acf-postgresql&lt;br /&gt;
=== 2. Setup and Start Postgresql ===&lt;br /&gt;
 rc-update add postgresql&lt;br /&gt;
 /etc/init.d/postgresql setup&lt;br /&gt;
 /etc/init.d/postgresql start&lt;br /&gt;
=== 3. Create the Database ===&lt;br /&gt;
 psql -U postgres -c &amp;quot;CREATE DATABASE voicemail&amp;quot;&lt;br /&gt;
=== 4. Stop Freeswitch ===&lt;br /&gt;
 /etc/init.d/freeswitch stop&lt;br /&gt;
{{Note|Freeswitch does not always stop properly. You might need to run &#039;ps ax&#039; to find the process and &#039;kill&#039; it. Running &#039;/etc/init.d/freeswitch zap&#039; might also be necessary.}}&lt;br /&gt;
=== 5. Configure Freeswitch and acf-freeswitch-vmail ===&lt;br /&gt;
Edit /etc/freeswitch/autoload_configs/voicemail.conf.xml to set the odbc-dsn param to &amp;quot;pgsql://hostaddr=127.0.0.1 dbname=voicemail user=postgres password= options=&#039;&#039;&amp;quot;&lt;br /&gt;
 &amp;lt;param name=&amp;quot;odbc-dsn&amp;quot; value=&amp;quot;pgsql://hostaddr=127.0.0.1 dbname=voicemail user=postgres password= options=&#039;&#039;&amp;quot; /&amp;gt;&lt;br /&gt;
Edit /etc/freeswitchvmail.conf and set the dsn param to &amp;quot;pgsql://hostaddr=127.0.0.1 dbname=voicemail user=postgres password= options=&#039;&#039;&amp;quot;&lt;br /&gt;
 dsn=pgsql://hostaddr=127.0.0.1 dbname=voicemail user=postgres password= options=&#039;&#039;&lt;br /&gt;
=== 6. Restart Freeswitch ===&lt;br /&gt;
 /etc/init.d/freeswitch start&lt;br /&gt;
&lt;br /&gt;
[[Category:ACF]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=Freeswitch_Voicemail_On_Alpine_Linux&amp;diff=10820</id>
		<title>Freeswitch Voicemail On Alpine Linux</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=Freeswitch_Voicemail_On_Alpine_Linux&amp;diff=10820"/>
		<updated>2015-04-27T20:22:13Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Overview =&lt;br /&gt;
This page describes how to install a basic voicemail server based upon Freeswitch including a web interface based upon ACF. It is built and tested on Alpine Linux 2.7, 3.0, 3.1, and the future 3.2.&lt;br /&gt;
&lt;br /&gt;
= Setup Procedure=&lt;br /&gt;
== 1. Configure the machine ==&lt;br /&gt;
Install and configure a basic Alpine Linux server with the ACF web interface.&lt;br /&gt;
 setup-alpine&lt;br /&gt;
 setup-acf&lt;br /&gt;
&lt;br /&gt;
== 2. Install Freeswitch ==&lt;br /&gt;
 apk add freeswitch freeswitch-sounds-en-us-callie-8000 freeswitch-flite acf-freeswitch acf-freeswitch-vmail&lt;br /&gt;
&lt;br /&gt;
== 3. Configure Freeswitch ==&lt;br /&gt;
We will use the Freeswitch sample configuration for the purposes of this document. It is not recommended to use this config in production, but it will allow us to quickly get voicemail up and running. Rather than installing the package, we will use apk to fetch the files.&lt;br /&gt;
 apk fetch --quiet --stdout freeswitch-sample-config | tar -C / -zvx&lt;br /&gt;
Modify the default password to avoid warnings and delays (obviously, you can choose something other than 12345 if you want)&lt;br /&gt;
 sed -i s/&amp;quot;default_password=1234&amp;quot;/&amp;quot;default_password=12345&amp;quot;/ /etc/freeswitch/vars.xml&lt;br /&gt;
Use Freeswitch XML curl to call into ACF to determine voicemail accounts. To do so, edit &#039;&#039;/etc/freeswitch/autoload_configs/xml_curl.conf.xml&#039;&#039; and add the following bindings:&lt;br /&gt;
     &amp;lt;binding name=&amp;quot;voicemaildialplan&amp;quot;&amp;gt;&lt;br /&gt;
       &amp;lt;param name=&amp;quot;gateway-url&amp;quot; value=&amp;quot;https://127.0.0.1/cgi-bin/acf/freeswitch-vmail/vmail/processdialplanxml&amp;quot; bindings=&amp;quot;dialplan&amp;quot;/&amp;gt;&lt;br /&gt;
     &amp;lt;/binding&amp;gt;&lt;br /&gt;
     &amp;lt;binding name=&amp;quot;voicemaildirectory&amp;quot;&amp;gt;&lt;br /&gt;
       &amp;lt;param name=&amp;quot;gateway-url&amp;quot; value=&amp;quot;https://127.0.0.1/cgi-bin/acf/freeswitch-vmail/vmail/processdirectoryxml&amp;quot; bindings=&amp;quot;directory&amp;quot;/&amp;gt;&lt;br /&gt;
     &amp;lt;/binding&amp;gt;&lt;br /&gt;
And modify &#039;&#039;/etc/freeswitch/autoload_configs/modules.conf.xml&#039;&#039; to load the mod_xml_curl module&lt;br /&gt;
 &amp;lt;load module=&amp;quot;mod_xml_curl&amp;quot;/&amp;gt;&lt;br /&gt;
{{Note|These following items should not be needed, but are caused by bad default paths in the freeswitch package}}&lt;br /&gt;
Fix the voicemail storage directory in &#039;&#039;/etc/freeswitch/autoload_configs/voicemail.conf.xml&#039;&#039; to point to &#039;&#039;/var/lib/freeswitch/voicemail&#039;&#039;&lt;br /&gt;
 &amp;lt;param name=&amp;quot;storage-dir&amp;quot; value=&amp;quot;/var/lib/freeswitch/voicemail&amp;quot;/&amp;gt;&lt;br /&gt;
Set the sounds directory in &#039;&#039;/etc/freeswitch/vars.xml&#039;&#039; by adding the following line:&lt;br /&gt;
 &amp;lt;X-PRE-PROCESS cmd=&amp;quot;set&amp;quot; data=&amp;quot;sounds_dir=/usr/share/freeswitch/sounds&amp;quot;/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4. Start Freeswitch ==&lt;br /&gt;
Start Freeswitch and add it to the default runlevel.&lt;br /&gt;
 rc-update add freeswitch&lt;br /&gt;
 /etc/init.d/freeswitch start&lt;br /&gt;
You should now be able to dial into Freeswitch voicemail by registering a SIP user agent to freeswitch (user=1000-1019, password=12345 as defined above) and directing a SIP call to any of the other extensions between 1000 and 1019, or the extension 4000. For each voicemail account, the default password is the same as the extension. For more details on this, please refer to Freeswitch documentation. Please keep in mind that these extensions and voicemail are part of the sample config and are not managed by ACF. We will add ACF-managed voicemail accounts below.&lt;br /&gt;
&lt;br /&gt;
== 5. Configure acf-freeswitch-vmail ==&lt;br /&gt;
Add the voicemail authenticator to the ACF configuration. This will allow voicemail users to log into ACF to check their voicemail.&lt;br /&gt;
 echo &amp;quot;authenticator = authenticator-freeswitch-vmail.lua,authenticator-plaintext&amp;quot; &amp;gt;&amp;gt; /etc/acf/acf.conf&lt;br /&gt;
Change the ACF voicemail domain to match the domain from the sample config. You should use the IP address of your voicemail server, as this is what the sample config uses.&lt;br /&gt;
 sed -i /^domain=/d /etc/freeswitchvmail.conf&lt;br /&gt;
 echo &amp;quot;domain=IPADDRESS&amp;quot; &amp;gt;&amp;gt; /etc/freeswitchvmail.conf&lt;br /&gt;
&lt;br /&gt;
= ACF Web interface =&lt;br /&gt;
You can now browse to https://IPADDRESS to access the web interface. To log in for administration, use the root user and the system root password (this is the default configuration set up by setup-acf).&lt;br /&gt;
== Users ==&lt;br /&gt;
The Voicemail | Users tab will display all of the voicemail users that are managed through the ACF web interface. At the start, this will be empty. When you create Users in the ACF, it will modify the freeswitch dialplan and directory (using xml_curl) to allow these users to receive and retrieve voicemail. It will also allow these users log into the ACF interface to check their voicemail and modify voicemail settings. The password defined here will control both ACF and IVR access.&lt;br /&gt;
== Voicemail ==&lt;br /&gt;
The Voicemail | Voicemail tab will allow you to view, listen to, download, forward, and delete voicemail messages.&lt;br /&gt;
== Config ==&lt;br /&gt;
The Voicemail | Config tab allows administrators to modify the acf-freeswitch-vmail configuration.&lt;br /&gt;
== Settings ==&lt;br /&gt;
The Voicemail | Settings tab allows voicemail users to modify their settings.&lt;br /&gt;
&lt;br /&gt;
= Demonstration =&lt;br /&gt;
We are now ready to create voicemail accounts with ACF. The freeswitch sample config creates a fairly full dialplan and we do not want to stomp on any of its features, so we will demonstrate accounts in the 2100 - 2199 range, which is unused.&lt;br /&gt;
# Dial extension 2100 to verify what happens when an unsupported extension is called&lt;br /&gt;
# Logged into ACF as root, create a voicemail user with extension 2100 and some password&lt;br /&gt;
# Dial extension 2100 again to verify that you can now leave a voicemail for extension 2100&lt;br /&gt;
# Dial extension 4000 and log in as user 2100 with the password you defined above to listen to the message&lt;br /&gt;
# Log out of ACF as root and log in again as user 2100 with the password you defined above to view your message&lt;br /&gt;
&lt;br /&gt;
= Other Features =&lt;br /&gt;
== Using PostgreSQL ==&lt;br /&gt;
Freeswitch-1.2.5 and acf-freeswitch-vmail-0.5.0 fully support Postgresql as the voicemail database. These versions are available in Alpine Linux 3.2 and later. To configure it:&lt;br /&gt;
=== 1. Install Postgresql ===&lt;br /&gt;
 apk add postgresql acf-postgresql&lt;br /&gt;
=== 2. Setup and Start Postgresql ===&lt;br /&gt;
 rc-update add postgresql&lt;br /&gt;
 /etc/init.d/postgresql setup&lt;br /&gt;
 /etc/init.d/postgresql start&lt;br /&gt;
=== 3. Create the Database ===&lt;br /&gt;
 psql -U postgres -c &amp;quot;CREATE DATABASE voicemail&amp;quot;&lt;br /&gt;
=== 4. Stop Freeswitch ===&lt;br /&gt;
 /etc/init.d/freeswitch stop&lt;br /&gt;
{{Note|Freeswitch does not always stop properly. You might need to run &#039;ps ax&#039; to find the process and &#039;kill&#039; it. Running &#039;/etc/init.d/freeswitch zap&#039; might also be necessary.}}&lt;br /&gt;
=== 5. Configure Freeswitch and acf-freeswitch-vmail ===&lt;br /&gt;
Edit /etc/freeswitch/autoload_configs/voicemail.conf.xml to set the odbc-dsn param to &amp;quot;pgsql://hostaddr=127.0.0.1 dbname=voicemail user=postgres password= options=&#039;&#039;&amp;quot;&lt;br /&gt;
 &amp;lt;param name=&amp;quot;odbc-dsn&amp;quot; value=&amp;quot;pgsql://hostaddr=127.0.0.1 dbname=voicemail user=postgres password= options=&#039;&#039;&amp;quot; /&amp;gt;&lt;br /&gt;
Edit /etc/freeswitchvmail.conf and set the dsn param to &amp;quot;pgsql://hostaddr=127.0.0.1 dbname=voicemail user=postgres password= options=&#039;&#039;&amp;quot;&lt;br /&gt;
 dsn=pgsql://hostaddr=127.0.0.1 dbname=voicemail user=postgres password= options=&#039;&#039;&lt;br /&gt;
=== 6. Restart Freeswitch ===&lt;br /&gt;
 /etc/init.d/freeswitch start&lt;br /&gt;
&lt;br /&gt;
[[Category:ACF]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=Freeswitch_Voicemail_On_Alpine_Linux&amp;diff=10818</id>
		<title>Freeswitch Voicemail On Alpine Linux</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=Freeswitch_Voicemail_On_Alpine_Linux&amp;diff=10818"/>
		<updated>2015-04-27T13:41:38Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: And commands to switch to postgresql instead of sqlite&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Overview =&lt;br /&gt;
This page describes how to install a basic voicemail server based upon Freeswitch including a web interface based upon ACF. It is built and tested on Alpine Linux 2.7, 3.0, 3.1, and the future 3.2.&lt;br /&gt;
&lt;br /&gt;
= Setup Procedure=&lt;br /&gt;
== 1. Configure the machine ==&lt;br /&gt;
Install and configure a basic Alpine Linux server with the ACF web interface.&lt;br /&gt;
 setup-alpine&lt;br /&gt;
 setup-acf&lt;br /&gt;
&lt;br /&gt;
== 2. Install Freeswitch ==&lt;br /&gt;
 apk add freeswitch freeswitch-sounds-en-us-callie-8000 freeswitch-flite acf-freeswitch acf-freeswitch-vmail&lt;br /&gt;
&lt;br /&gt;
== 3. Configure Freeswitch ==&lt;br /&gt;
We will use the Freeswitch sample configuration for the purposes of this document. It is not recommended to use this config in production, but it will allow us to quickly get voicemail up and running. Rather than installing the package, we will use apk to fetch the files.&lt;br /&gt;
 apk fetch --quiet --stdout freeswitch-sample-config | tar -C / -zvx&lt;br /&gt;
Modify the default password to avoid warnings and delays (obviously, you can choose something other than 12345 if you want)&lt;br /&gt;
 sed -i s/&amp;quot;default_password=1234&amp;quot;/&amp;quot;default_password=12345&amp;quot;/ /etc/freeswitch/vars.xml&lt;br /&gt;
Use Freeswitch XML curl to call into ACF to determine voicemail accounts. To do so, edit &#039;&#039;/etc/freeswitch/autoload_configs/xml_curl.conf.xml&#039;&#039; and add the following bindings:&lt;br /&gt;
     &amp;lt;binding name=&amp;quot;voicemaildialplan&amp;quot;&amp;gt;&lt;br /&gt;
       &amp;lt;param name=&amp;quot;gateway-url&amp;quot; value=&amp;quot;https://127.0.0.1/cgi-bin/acf/freeswitch-vmail/vmail/processdialplanxml&amp;quot; bindings=&amp;quot;dialplan&amp;quot;/&amp;gt;&lt;br /&gt;
     &amp;lt;/binding&amp;gt;&lt;br /&gt;
     &amp;lt;binding name=&amp;quot;voicemaildirectory&amp;quot;&amp;gt;&lt;br /&gt;
       &amp;lt;param name=&amp;quot;gateway-url&amp;quot; value=&amp;quot;https://127.0.0.1/cgi-bin/acf/freeswitch-vmail/vmail/processdirectoryxml&amp;quot; bindings=&amp;quot;directory&amp;quot;/&amp;gt;&lt;br /&gt;
     &amp;lt;/binding&amp;gt;&lt;br /&gt;
And modify &#039;&#039;/etc/freeswitch/autoload_configs/modules.conf.xml&#039;&#039; to load the mod_xml_curl module&lt;br /&gt;
 &amp;lt;load module=&amp;quot;mod_xml_curl&amp;quot;/&amp;gt;&lt;br /&gt;
{{Note|These following items should not be needed, but are caused by bad default paths in the freeswitch package}}&lt;br /&gt;
Fix the voicemail storage directory in &#039;&#039;/etc/freeswitch/autoload_configs/voicemail.conf.xml&#039;&#039; to point to &#039;&#039;/var/lib/freeswitch/voicemail&#039;&#039;&lt;br /&gt;
 &amp;lt;param name=&amp;quot;storage-dir&amp;quot; value=&amp;quot;/var/lib/freeswitch/voicemail&amp;quot;/&amp;gt;&lt;br /&gt;
Set the sounds directory in &#039;&#039;/etc/freeswitch/vars.xml&#039;&#039; by adding the following line:&lt;br /&gt;
 &amp;lt;X-PRE-PROCESS cmd=&amp;quot;set&amp;quot; data=&amp;quot;sounds_dir=/usr/share/freeswitch/sounds&amp;quot;/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4. Start Freeswitch ==&lt;br /&gt;
Start Freeswitch and add it to the default runlevel.&lt;br /&gt;
 rc-update add freeswitch&lt;br /&gt;
 /etc/init.d/freeswitch start&lt;br /&gt;
You should now be able to dial into Freeswitch voicemail by registering a SIP user agent to freeswitch (user=1000-1019, password=12345 as defined above) and directing a SIP call to any of the other extensions between 1000 and 1019, or the extension 4000. For each voicemail account, the default password is the same as the extension. For more details on this, please refer to Freeswitch documentation. Please keep in mind that these extensions and voicemail are part of the sample config and are not managed by ACF. We will add ACF-managed voicemail accounts below.&lt;br /&gt;
&lt;br /&gt;
== 5. Configure acf-freeswitch-vmail ==&lt;br /&gt;
Add the voicemail authenticator to the ACF configuration. This will allow voicemail users to log into ACF to check their voicemail.&lt;br /&gt;
 echo &amp;quot;authenticator = authenticator-freeswitch-vmail.lua,authenticator-plaintext&amp;quot; &amp;gt;&amp;gt; /etc/acf/acf.conf&lt;br /&gt;
Change the ACF voicemail domain to match the domain from the sample config. You should use the IP address of your voicemail server, as this is what the sample config uses.&lt;br /&gt;
 sed -i /^domain=/d /etc/freeswitchvmail.conf&lt;br /&gt;
 echo &amp;quot;domain=IPADDRESS&amp;quot; &amp;gt;&amp;gt; /etc/freeswitchvmail.conf&lt;br /&gt;
&lt;br /&gt;
= ACF Web interface =&lt;br /&gt;
You can now browse to https://IPADDRESS to access the web interface. To log in for administration, use the root user and the system root password (this is the default configuration set up by setup-acf).&lt;br /&gt;
== Users ==&lt;br /&gt;
The Voicemail | Users tab will display all of the voicemail users that are managed through the ACF web interface. At the start, this will be empty. When you create Users in the ACF, it will modify the freeswitch dialplan and directory (using xml_curl) to allow these users to receive and retrieve voicemail. It will also allow these users log into the ACF interface to check their voicemail and modify voicemail settings. The password defined here will control both ACF and IVR access.&lt;br /&gt;
== Voicemail ==&lt;br /&gt;
The Voicemail | Voicemail tab will allow you to view, listen to, download, forward, and delete voicemail messages.&lt;br /&gt;
== Config ==&lt;br /&gt;
The Voicemail | Config tab allows administrators to modify the acf-freeswitch-vmail configuration.&lt;br /&gt;
== Settings ==&lt;br /&gt;
The Voicemail | Settings tab allows voicemail users to modify their settings.&lt;br /&gt;
&lt;br /&gt;
= Demonstration =&lt;br /&gt;
We are now ready to create voicemail accounts with ACF. The freeswitch sample config creates a fairly full dialplan and we do not want to stomp on any of its features, so we will demonstrate accounts in the 2100 - 2199 range, which is unused.&lt;br /&gt;
# Dial extension 2100 to verify what happens when an unsupported extension is called&lt;br /&gt;
# Logged into ACF as root, create a voicemail user with extension 2100 and some password&lt;br /&gt;
# Dial extension 2100 again to verify that you can now leave a voicemail for extension 2100&lt;br /&gt;
# Dial extension 4000 and log in as user 2100 with the password you defined above to listen to the message&lt;br /&gt;
# Log out of ACF as root and log in again as user 2100 with the password you defined above to view your message&lt;br /&gt;
&lt;br /&gt;
= Other Features =&lt;br /&gt;
== Using PostgreSQL ==&lt;br /&gt;
Freeswitch-1.2.5 and acf-freeswitch-vmail-0.5.0 fully support Postgresql as the voicemail database. These versions are available in Alpine Linux 3.2 and later. To configure it:&lt;br /&gt;
=== 1. Install Postgresql ===&lt;br /&gt;
 apk add postgresql acf-postgresql&lt;br /&gt;
=== 2. Setup and Start Postgresql ===&lt;br /&gt;
 rc-update add postgresql&lt;br /&gt;
 /etc/init.d/postgresql setup&lt;br /&gt;
 /etc/init.d/postgresql start&lt;br /&gt;
=== 3. Create the Database ===&lt;br /&gt;
 psql -U postgres -c &amp;quot;CREATE DATABASE voicemail&amp;quot;&lt;br /&gt;
=== 4. Stop Freeswitch ===&lt;br /&gt;
 /etc/init.d/freeswitch stop&lt;br /&gt;
{{Note|Freeswitch does not always stop properly. You might need to run &#039;ps ax&#039; to find the process and &#039;kill&#039; it. Running &#039;/etc/init.d/freeswitch zap&#039; might also be necessary.}}&lt;br /&gt;
=== 5. Configure Freeswitch and acf-freeswitch-vmail ===&lt;br /&gt;
Edit /etc/freeswitch/autoload_configs/voicemail.conf.xml to set the odbc-dsn param to &amp;quot;pgsql://hostaddr=127.0.0.1 dbname=voicemail user=postgres password= options=&#039;&#039;&amp;quot;&lt;br /&gt;
 &amp;lt;param name=&amp;quot;odbc-dsn&amp;quot; value=&amp;quot;pgsql://hostaddr=127.0.0.1 dbname=voicemail user=postgres password= options=&#039;&#039;&amp;quot; /&amp;gt;&lt;br /&gt;
Edit /etc/freeswitchvmail.conf and set the dsn param to &amp;quot;pgsql://hostaddr=127.0.0.1 dbname=voicemail user=postgres password= options=&#039;&#039;&amp;quot;&lt;br /&gt;
 dsn=pgsql://hostaddr=127.0.0.1 dbname=voicemail user=postgres password= options=&#039;&#039;&lt;br /&gt;
=== 6. Restart Freeswitch ===&lt;br /&gt;
 /etc/init.d/freeswitch start&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=Freeswitch_Voicemail_On_Alpine_Linux&amp;diff=10813</id>
		<title>Freeswitch Voicemail On Alpine Linux</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=Freeswitch_Voicemail_On_Alpine_Linux&amp;diff=10813"/>
		<updated>2015-04-26T19:48:18Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Fix command for /etc/freeswitchvmail.conf because it does not exist at first&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Overview =&lt;br /&gt;
This page describes how to install a basic voicemail server based upon Freeswitch including a web interface based upon ACF. It is built and tested on Alpine Linux 2.7, 3.0, 3.1, and the future 3.2.&lt;br /&gt;
&lt;br /&gt;
= Setup Procedure=&lt;br /&gt;
== 1. Configure the machine ==&lt;br /&gt;
Install and configure a basic Alpine Linux server with the ACF web interface.&lt;br /&gt;
 setup-alpine&lt;br /&gt;
 setup-acf&lt;br /&gt;
&lt;br /&gt;
== 2. Install Freeswitch ==&lt;br /&gt;
 apk add freeswitch freeswitch-sounds-en-us-callie-8000 freeswitch-flite acf-freeswitch acf-freeswitch-vmail&lt;br /&gt;
&lt;br /&gt;
== 3. Configure Freeswitch ==&lt;br /&gt;
We will use the Freeswitch sample configuration for the purposes of this document. It is not recommended to use this config in production, but it will allow us to quickly get voicemail up and running. Rather than installing the package, we will use apk to fetch the files.&lt;br /&gt;
 apk fetch --quiet --stdout freeswitch-sample-config | tar -C / -zvx&lt;br /&gt;
Modify the default password to avoid warnings and delays (obviously, you can choose something other than 12345 if you want)&lt;br /&gt;
 sed -i s/&amp;quot;default_password=1234&amp;quot;/&amp;quot;default_password=12345&amp;quot;/ /etc/freeswitch/vars.xml&lt;br /&gt;
Use Freeswitch XML curl to call into ACF to determine voicemail accounts. To do so, edit &#039;&#039;/etc/freeswitch/autoload_configs/xml_curl.conf.xml&#039;&#039; and add the following bindings:&lt;br /&gt;
     &amp;lt;binding name=&amp;quot;voicemaildialplan&amp;quot;&amp;gt;&lt;br /&gt;
       &amp;lt;param name=&amp;quot;gateway-url&amp;quot; value=&amp;quot;https://127.0.0.1/cgi-bin/acf/freeswitch-vmail/vmail/processdialplanxml&amp;quot; bindings=&amp;quot;dialplan&amp;quot;/&amp;gt;&lt;br /&gt;
     &amp;lt;/binding&amp;gt;&lt;br /&gt;
     &amp;lt;binding name=&amp;quot;voicemaildirectory&amp;quot;&amp;gt;&lt;br /&gt;
       &amp;lt;param name=&amp;quot;gateway-url&amp;quot; value=&amp;quot;https://127.0.0.1/cgi-bin/acf/freeswitch-vmail/vmail/processdirectoryxml&amp;quot; bindings=&amp;quot;directory&amp;quot;/&amp;gt;&lt;br /&gt;
     &amp;lt;/binding&amp;gt;&lt;br /&gt;
And modify &#039;&#039;/etc/freeswitch/autoload_configs/modules.conf.xml&#039;&#039; to load the mod_xml_curl module&lt;br /&gt;
 &amp;lt;load module=&amp;quot;mod_xml_curl&amp;quot;/&amp;gt;&lt;br /&gt;
{{Note|These following items should not be needed, but are caused by bad default paths in the freeswitch package}}&lt;br /&gt;
Fix the voicemail storage directory in &#039;&#039;/etc/freeswitch/autoload_configs/voicemail.conf.xml&#039;&#039; to point to &#039;&#039;/var/lib/freeswitch/voicemail&#039;&#039;&lt;br /&gt;
 &amp;lt;param name=&amp;quot;storage-dir&amp;quot; value=&amp;quot;/var/lib/freeswitch/voicemail&amp;quot;/&amp;gt;&lt;br /&gt;
Set the sounds directory in &#039;&#039;/etc/freeswitch/vars.xml&#039;&#039; by adding the following line:&lt;br /&gt;
 &amp;lt;X-PRE-PROCESS cmd=&amp;quot;set&amp;quot; data=&amp;quot;sounds_dir=/usr/share/freeswitch/sounds&amp;quot;/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4. Start Freeswitch ==&lt;br /&gt;
Start Freeswitch and add it to the default runlevel.&lt;br /&gt;
 rc-update add freeswitch&lt;br /&gt;
 /etc/init.d/freeswitch start&lt;br /&gt;
You should now be able to dial into Freeswitch voicemail by registering a SIP user agent to freeswitch (user=1000-1019, password=12345 as defined above) and directing a SIP call to any of the other extensions between 1000 and 1019, or the extension 4000. For each voicemail account, the default password is the same as the extension. For more details on this, please refer to Freeswitch documentation. Please keep in mind that these extensions and voicemail are part of the sample config and are not managed by ACF. We will add ACF-managed voicemail accounts below.&lt;br /&gt;
&lt;br /&gt;
== 5. Configure acf-freeswitch-vmail ==&lt;br /&gt;
Add the voicemail authenticator to the ACF configuration. This will allow voicemail users to log into ACF to check their voicemail.&lt;br /&gt;
 echo &amp;quot;authenticator = authenticator-freeswitch-vmail.lua,authenticator-plaintext&amp;quot; &amp;gt;&amp;gt; /etc/acf/acf.conf&lt;br /&gt;
Change the ACF voicemail domain to match the domain from the sample config. You should use the IP address of your voicemail server, as this is what the sample config uses.&lt;br /&gt;
 sed -i /^domain=/d /etc/freeswitchvmail.conf&lt;br /&gt;
 echo &amp;quot;domain=IPADDRESS&amp;quot; &amp;gt;&amp;gt; /etc/freeswitchvmail.conf&lt;br /&gt;
&lt;br /&gt;
= ACF Web interface =&lt;br /&gt;
You can now browse to https://IPADDRESS to access the web interface. To log in for administration, use the root user and the system root password (this is the default configuration set up by setup-acf).&lt;br /&gt;
== Users ==&lt;br /&gt;
The Voicemail | Users tab will display all of the voicemail users that are managed through the ACF web interface. At the start, this will be empty. When you create Users in the ACF, it will modify the freeswitch dialplan and directory (using xml_curl) to allow these users to receive and retrieve voicemail. It will also allow these users log into the ACF interface to check their voicemail and modify voicemail settings. The password defined here will control both ACF and IVR access.&lt;br /&gt;
== Voicemail ==&lt;br /&gt;
The Voicemail | Voicemail tab will allow you to view, listen to, download, forward, and delete voicemail messages.&lt;br /&gt;
== Config ==&lt;br /&gt;
The Voicemail | Config tab allows administrators to modify the acf-freeswitch-vmail configuration.&lt;br /&gt;
== Settings ==&lt;br /&gt;
The Voicemail | Settings tab allows voicemail users to modify their settings.&lt;br /&gt;
&lt;br /&gt;
= Demonstration =&lt;br /&gt;
We are now ready to create voicemail accounts with ACF. The freeswitch sample config creates a fairly full dialplan and we do not want to stomp on any of its features, so we will demonstrate accounts in the 2100 - 2199 range, which is unused.&lt;br /&gt;
# Dial extension 2100 to verify what happens when an unsupported extension is called&lt;br /&gt;
# Logged into ACF as root, create a voicemail user with extension 2100 and some password&lt;br /&gt;
# Dial extension 2100 again to verify that you can now leave a voicemail for extension 2100&lt;br /&gt;
# Dial extension 4000 and log in as user 2100 with the password you defined above to listen to the message&lt;br /&gt;
# Log out of ACF as root and log in again as user 2100 with the password you defined above to view your message&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=Freeswitch_Voicemail_On_Alpine_Linux&amp;diff=10680</id>
		<title>Freeswitch Voicemail On Alpine Linux</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=Freeswitch_Voicemail_On_Alpine_Linux&amp;diff=10680"/>
		<updated>2015-04-13T13:43:57Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Fixed domain config issue&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Overview =&lt;br /&gt;
This page describes how to install a basic voicemail server based upon Freeswitch including a web interface based upon ACF. It is built and tested on Alpine Linux 2.7, 3.0, 3.1, and the future 3.2.&lt;br /&gt;
&lt;br /&gt;
= Setup Procedure=&lt;br /&gt;
== 1. Configure the machine ==&lt;br /&gt;
Install and configure a basic Alpine Linux server with the ACF web interface.&lt;br /&gt;
 setup-alpine&lt;br /&gt;
 setup-acf&lt;br /&gt;
&lt;br /&gt;
== 2. Install Freeswitch ==&lt;br /&gt;
 apk add freeswitch freeswitch-sounds-en-us-callie-8000 freeswitch-flite acf-freeswitch acf-freeswitch-vmail&lt;br /&gt;
&lt;br /&gt;
== 3. Configure Freeswitch ==&lt;br /&gt;
We will use the Freeswitch sample configuration for the purposes of this document. It is not recommended to use this config in production, but it will allow us to quickly get voicemail up and running. Rather than installing the package, we will use apk to fetch the files.&lt;br /&gt;
 apk fetch --quiet --stdout freeswitch-sample-config | tar -C / -zvx&lt;br /&gt;
Modify the default password to avoid warnings and delays (obviously, you can choose something other than 12345 if you want)&lt;br /&gt;
 sed -i s/&amp;quot;default_password=1234&amp;quot;/&amp;quot;default_password=12345&amp;quot;/ /etc/freeswitch/vars.xml&lt;br /&gt;
Use Freeswitch XML curl to call into ACF to determine voicemail accounts. To do so, edit &#039;&#039;/etc/freeswitch/autoload_configs/xml_curl.conf.xml&#039;&#039; and add the following bindings:&lt;br /&gt;
     &amp;lt;binding name=&amp;quot;voicemaildialplan&amp;quot;&amp;gt;&lt;br /&gt;
       &amp;lt;param name=&amp;quot;gateway-url&amp;quot; value=&amp;quot;https://127.0.0.1/cgi-bin/acf/freeswitch-vmail/vmail/processdialplanxml&amp;quot; bindings=&amp;quot;dialplan&amp;quot;/&amp;gt;&lt;br /&gt;
     &amp;lt;/binding&amp;gt;&lt;br /&gt;
     &amp;lt;binding name=&amp;quot;voicemaildirectory&amp;quot;&amp;gt;&lt;br /&gt;
       &amp;lt;param name=&amp;quot;gateway-url&amp;quot; value=&amp;quot;https://127.0.0.1/cgi-bin/acf/freeswitch-vmail/vmail/processdirectoryxml&amp;quot; bindings=&amp;quot;directory&amp;quot;/&amp;gt;&lt;br /&gt;
     &amp;lt;/binding&amp;gt;&lt;br /&gt;
And modify &#039;&#039;/etc/freeswitch/autoload_configs/modules.conf.xml&#039;&#039; to load the mod_xml_curl module&lt;br /&gt;
 &amp;lt;load module=&amp;quot;mod_xml_curl&amp;quot;/&amp;gt;&lt;br /&gt;
{{Note|These following items should not be needed, but are caused by bad default paths in the freeswitch package}}&lt;br /&gt;
Fix the voicemail storage directory in &#039;&#039;/etc/freeswitch/autoload_configs/voicemail.conf.xml&#039;&#039; to point to &#039;&#039;/var/lib/freeswitch/voicemail&#039;&#039;&lt;br /&gt;
 &amp;lt;param name=&amp;quot;storage-dir&amp;quot; value=&amp;quot;/var/lib/freeswitch/voicemail&amp;quot;/&amp;gt;&lt;br /&gt;
Set the sounds directory in &#039;&#039;/etc/freeswitch/vars.xml&#039;&#039; by adding the following line:&lt;br /&gt;
 &amp;lt;X-PRE-PROCESS cmd=&amp;quot;set&amp;quot; data=&amp;quot;sounds_dir=/usr/share/freeswitch/sounds&amp;quot;/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4. Start Freeswitch ==&lt;br /&gt;
Start Freeswitch and add it to the default runlevel.&lt;br /&gt;
 rc-update add freeswitch&lt;br /&gt;
 /etc/init.d/freeswitch start&lt;br /&gt;
You should now be able to dial into Freeswitch voicemail by registering a SIP user agent to freeswitch (user=1000-1019, password=12345 as defined above) and directing a SIP call to any of the other extensions between 1000 and 1019, or the extension 4000. For each voicemail account, the default password is the same as the extension. For more details on this, please refer to Freeswitch documentation. Please keep in mind that these extensions and voicemail are part of the sample config and are not managed by ACF. We will add ACF-managed voicemail accounts below.&lt;br /&gt;
&lt;br /&gt;
== 5. Configure acf-freeswitch-vmail ==&lt;br /&gt;
Add the voicemail authenticator to the ACF configuration. This will allow voicemail users to log into ACF to check their voicemail.&lt;br /&gt;
 echo &amp;quot;authenticator = authenticator-freeswitch-vmail.lua,authenticator-plaintext&amp;quot; &amp;gt;&amp;gt; /etc/acf/acf.conf&lt;br /&gt;
Change the ACF voicemail domain to match the domain from the sample config. You should use the IP address of your voicemail server, as this is what the sample config uses.&lt;br /&gt;
 sed -i s/domain=voicemail/domain=IPADDRESS/ /etc/freeswitchvmail.conf&lt;br /&gt;
&lt;br /&gt;
= ACF Web interface =&lt;br /&gt;
You can now browse to https://IPADDRESS to access the web interface. To log in for administration, use the root user and the system root password (this is the default configuration set up by setup-acf).&lt;br /&gt;
== Users ==&lt;br /&gt;
The Voicemail | Users tab will display all of the voicemail users that are managed through the ACF web interface. At the start, this will be empty. When you create Users in the ACF, it will modify the freeswitch dialplan and directory (using xml_curl) to allow these users to receive and retrieve voicemail. It will also allow these users log into the ACF interface to check their voicemail and modify voicemail settings. The password defined here will control both ACF and IVR access.&lt;br /&gt;
== Voicemail ==&lt;br /&gt;
The Voicemail | Voicemail tab will allow you to view, listen to, download, forward, and delete voicemail messages.&lt;br /&gt;
== Config ==&lt;br /&gt;
The Voicemail | Config tab allows administrators to modify the acf-freeswitch-vmail configuration.&lt;br /&gt;
== Settings ==&lt;br /&gt;
The Voicemail | Settings tab allows voicemail users to modify their settings.&lt;br /&gt;
&lt;br /&gt;
= Demonstration =&lt;br /&gt;
We are now ready to create voicemail accounts with ACF. The freeswitch sample config creates a fairly full dialplan and we do not want to stomp on any of its features, so we will demonstrate accounts in the 2100 - 2199 range, which is unused.&lt;br /&gt;
# Dial extension 2100 to verify what happens when an unsupported extension is called&lt;br /&gt;
# Logged into ACF as root, create a voicemail user with extension 2100 and some password&lt;br /&gt;
# Dial extension 2100 again to verify that you can now leave a voicemail for extension 2100&lt;br /&gt;
# Dial extension 4000 and log in as user 2100 with the password you defined above to listen to the message&lt;br /&gt;
# Log out of ACF as root and log in again as user 2100 with the password you defined above to view your message&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=Freeswitch_Voicemail_On_Alpine_Linux&amp;diff=10678</id>
		<title>Freeswitch Voicemail On Alpine Linux</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=Freeswitch_Voicemail_On_Alpine_Linux&amp;diff=10678"/>
		<updated>2015-04-12T20:49:57Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Fixed packages for alpine 3.0 - 3.2&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Draft}}&lt;br /&gt;
= Overview =&lt;br /&gt;
This page describes how to install a basic voicemail server based upon Freeswitch including a web interface based upon ACF. It is built and tested on Alpine Linux 2.7, 3.0, 3.1, and the future 3.2.&lt;br /&gt;
&lt;br /&gt;
{{Note|While dialing 4000 to get to the voicemail IVR works, and logging in as ACF-managed voicemail users works, new voicemail messages are not showing up. Not sure why yet.}}&lt;br /&gt;
&lt;br /&gt;
= Setup Procedure=&lt;br /&gt;
== 1. Configure the machine ==&lt;br /&gt;
Install and configure a basic Alpine Linux server with the ACF web interface.&lt;br /&gt;
 setup-alpine&lt;br /&gt;
 setup-acf&lt;br /&gt;
&lt;br /&gt;
== 2. Install Freeswitch ==&lt;br /&gt;
 apk add freeswitch freeswitch-sounds-en-us-callie-8000 freeswitch-flite acf-freeswitch acf-freeswitch-vmail&lt;br /&gt;
&lt;br /&gt;
== 3. Configure Freeswitch ==&lt;br /&gt;
We will use the Freeswitch sample configuration for the purposes of this document. It is not recommended to use this config in production, but it will allow us to quickly get voicemail up and running. Rather than installing the package, we will use apk to fetch the files.&lt;br /&gt;
 apk fetch --quiet --stdout freeswitch-sample-config | tar -C / -zvx&lt;br /&gt;
Modify the default password to avoid warnings and delays (obviously, you can choose something other than 12345 if you want)&lt;br /&gt;
 sed -i s/&amp;quot;default_password=1234&amp;quot;/&amp;quot;default_password=12345&amp;quot;/ /etc/freeswitch/vars.xml&lt;br /&gt;
Use Freeswitch XML curl to call into ACF to determine voicemail accounts. To do so, edit &#039;&#039;/etc/freeswitch/autoload_configs/xml_curl.conf.xml&#039;&#039; and add the following bindings:&lt;br /&gt;
     &amp;lt;binding name=&amp;quot;voicemaildialplan&amp;quot;&amp;gt;&lt;br /&gt;
       &amp;lt;param name=&amp;quot;gateway-url&amp;quot; value=&amp;quot;https://127.0.0.1/cgi-bin/acf/freeswitch-vmail/vmail/processdialplanxml&amp;quot; bindings=&amp;quot;dialplan&amp;quot;/&amp;gt;&lt;br /&gt;
     &amp;lt;/binding&amp;gt;&lt;br /&gt;
     &amp;lt;binding name=&amp;quot;voicemaildirectory&amp;quot;&amp;gt;&lt;br /&gt;
       &amp;lt;param name=&amp;quot;gateway-url&amp;quot; value=&amp;quot;https://127.0.0.1/cgi-bin/acf/freeswitch-vmail/vmail/processdirectoryxml&amp;quot; bindings=&amp;quot;directory&amp;quot;/&amp;gt;&lt;br /&gt;
     &amp;lt;/binding&amp;gt;&lt;br /&gt;
And modify &#039;&#039;/etc/freeswitch/autoload_configs/modules.conf.xml&#039;&#039; to load the mod_xml_curl module&lt;br /&gt;
 &amp;lt;load module=&amp;quot;mod_xml_curl&amp;quot;/&amp;gt;&lt;br /&gt;
{{Note|These following items should not be needed, but are caused by bad default paths in the freeswitch package}}&lt;br /&gt;
Fix the voicemail storage directory in &#039;&#039;/etc/freeswitch/autoload_configs/voicemail.conf.xml&#039;&#039; to point to &#039;&#039;/var/lib/freeswitch/voicemail&#039;&#039;&lt;br /&gt;
 &amp;lt;param name=&amp;quot;storage-dir&amp;quot; value=&amp;quot;/var/lib/freeswitch/voicemail&amp;quot;/&amp;gt;&lt;br /&gt;
Set the sounds directory in &#039;&#039;/etc/freeswitch/vars.xml&#039;&#039; by adding the following line:&lt;br /&gt;
 &amp;lt;X-PRE-PROCESS cmd=&amp;quot;set&amp;quot; data=&amp;quot;sounds_dir=/usr/share/freeswitch/sounds&amp;quot;/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4. Start Freeswitch ==&lt;br /&gt;
Start Freeswitch and add it to the default runlevel.&lt;br /&gt;
 rc-update add freeswitch&lt;br /&gt;
 /etc/init.d/freeswitch start&lt;br /&gt;
You should now be able to dial into Freeswitch voicemail by registering a SIP user agent to freeswitch (user=1000-1019, password=12345 as defined above) and directing a SIP call to any of the other extensions between 1000 and 1019, or the extension 4000. For each voicemail account, the default password is the same as the extension. For more details on this, please refer to Freeswitch documentation. Please keep in mind that these extensions and voicemail are part of the sample config and are not managed by ACF. We will add ACF-managed voicemail accounts below.&lt;br /&gt;
&lt;br /&gt;
== 5. Configure acf-freeswitch-vmail ==&lt;br /&gt;
Add the voicemail authenticator to the ACF configuration. This will allow voicemail users to log into ACF to check their voicemail.&lt;br /&gt;
 echo &amp;quot;authenticator = authenticator-freeswitch-vmail.lua,authenticator-plaintext&amp;quot; &amp;gt;&amp;gt; /etc/acf/acf.conf&lt;br /&gt;
&lt;br /&gt;
= ACF Web interface =&lt;br /&gt;
You can now browse to https://&amp;lt;ip address&amp;gt; to access the web interface. To log in for administration, use the root user and the system root password (this is the default configuration set up by setup-acf).&lt;br /&gt;
== Users ==&lt;br /&gt;
The Voicemail | Users tab will display all of the voicemail users that are managed through the ACF web interface. At the start, this will be empty. When you create Users in the ACF, it will modify the freeswitch dialplan and directory (using xml_curl) to allow these users to receive and retrieve voicemail. It will also allow these users log into the ACF interface to check their voicemail and modify voicemail settings. The password defined here will control both ACF and IVR access.&lt;br /&gt;
== Voicemail ==&lt;br /&gt;
The Voicemail | Voicemail tab will allow you to view, listen to, download, forward, and delete voicemail messages.&lt;br /&gt;
== Config ==&lt;br /&gt;
The Voicemail | Config tab allows administrators to modify the acf-freeswitch-vmail configuration.&lt;br /&gt;
== Settings ==&lt;br /&gt;
The Voicemail | Settings tab allows voicemail users to modify their settings.&lt;br /&gt;
&lt;br /&gt;
= Demonstration =&lt;br /&gt;
We are now ready to create voicemail accounts with ACF. The freeswitch sample config creates a fairly full dialplan and we do not want to stomp on any of its features, so we will demonstrate accounts in the 2100 - 2199 range, which is unused.&lt;br /&gt;
# Dial extension 2100 to verify what happens when an unsupported extension is called&lt;br /&gt;
# Logged into ACF as root, create a voicemail user with extension 2100 and some password&lt;br /&gt;
# Dial extension 2100 again to verify that you can now leave a voicemail for extension 2100&lt;br /&gt;
# Dial extension 4000 and log in as user 2100 with the password you defined above to listen to the message&lt;br /&gt;
# Log out of ACF as root and log in again as user 2100 with the password you defined above to view your message&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=Freeswitch_Voicemail_On_Alpine_Linux&amp;diff=10665</id>
		<title>Freeswitch Voicemail On Alpine Linux</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=Freeswitch_Voicemail_On_Alpine_Linux&amp;diff=10665"/>
		<updated>2015-04-11T01:20:41Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Current status&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Draft}}&lt;br /&gt;
= Overview =&lt;br /&gt;
This page describes how to install a basic voicemail server based upon Freeswitch including a web interface based upon ACF. It is built and tested on Alpine Linux 3.2.&lt;br /&gt;
&lt;br /&gt;
* Alpine 3.2 - dialing 4000 does not list voicemails, voicemail actions through ACF don&#039;t work (nc doesn&#039;t exit)&lt;br /&gt;
* Alpine 3.1 - &lt;br /&gt;
* Alpine 3.0 - dialing 4000 does not list voicemails, voicemail actions through ACF don&#039;t work (nc doesn&#039;t exit)&lt;br /&gt;
* Alpine 2.7 - dialing 4000 does not list voicemails, downloading works fine&lt;br /&gt;
&lt;br /&gt;
= Setup Procedure=&lt;br /&gt;
== 1. Configure the machine ==&lt;br /&gt;
Install and configure a basic Alpine Linux server with the ACF web interface.&lt;br /&gt;
 setup-alpine&lt;br /&gt;
 setup-acf&lt;br /&gt;
&lt;br /&gt;
== 2. Install Freeswitch ==&lt;br /&gt;
 apk add freeswitch freeswitch-sounds-en-us-callie-8000 freeswitch-flite acf-freeswitch acf-freeswitch-vmail&lt;br /&gt;
&lt;br /&gt;
== 3. Configure Freeswitch ==&lt;br /&gt;
We will use the Freeswitch sample configuration for the purposes of this document. It is not recommended to use this config in production, but it will allow us to quickly get voicemail up and running. Rather than installing the package, we will use apk to fetch the files.&lt;br /&gt;
 apk fetch --quiet --stdout freeswitch-sample-config | tar -C / -zvx&lt;br /&gt;
Modify the default password to avoid warnings and delays (obviously, you can choose something other than 12345 if you want)&lt;br /&gt;
 sed -i s/&amp;quot;default_password=1234&amp;quot;/&amp;quot;default_password=12345&amp;quot;/ /etc/freeswitch/vars.xml&lt;br /&gt;
Fix the voicemail storage directory in &#039;&#039;/etc/freeswitch/autoload_configs/voicemail.conf.xml&#039;&#039; to point to &#039;&#039;/var/lib/freeswitch/voicemail&#039;&#039;&lt;br /&gt;
 &amp;lt;param name=&amp;quot;storage-dir&amp;quot; value=&amp;quot;/var/lib/freeswitch/voicemail&amp;quot;/&amp;gt;&lt;br /&gt;
Set the sounds directory in &#039;&#039;/etc/freeswitch/vars.xml&#039;&#039; by adding the following line:&lt;br /&gt;
 &amp;lt;X-PRE-PROCESS cmd=&amp;quot;set&amp;quot; data=&amp;quot;sounds_dir=/usr/share/freeswitch/sounds&amp;quot;/&amp;gt;&lt;br /&gt;
Use Freeswitch XML curl to call into ACF to determine voicemail accounts. To do so, edit &#039;&#039;/etc/freeswitch/autoload_configs/xml_curl.conf.xml&#039;&#039; and add the following bindings:&lt;br /&gt;
     &amp;lt;binding name=&amp;quot;voicemaildialplan&amp;quot;&amp;gt;&lt;br /&gt;
       &amp;lt;param name=&amp;quot;gateway-url&amp;quot; value=&amp;quot;https://127.0.0.1/cgi-bin/acf/freeswitch-vmail/vmail/processdialplanxml&amp;quot; bindings=&amp;quot;dialplan&amp;quot;/&amp;gt;&lt;br /&gt;
     &amp;lt;/binding&amp;gt;&lt;br /&gt;
     &amp;lt;binding name=&amp;quot;voicemaildirectory&amp;quot;&amp;gt;&lt;br /&gt;
       &amp;lt;param name=&amp;quot;gateway-url&amp;quot; value=&amp;quot;https://127.0.0.1/cgi-bin/acf/freeswitch-vmail/vmail/processdirectoryxml&amp;quot; bindings=&amp;quot;directory&amp;quot;/&amp;gt;&lt;br /&gt;
     &amp;lt;/binding&amp;gt;&lt;br /&gt;
And modify &#039;&#039;/etc/freeswitch/autoload_configs/modules.conf.xml&#039;&#039; to load the mod_xml_curl module&lt;br /&gt;
 &amp;lt;load module=&amp;quot;mod_xml_curl&amp;quot;/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4. Start Freeswitch ==&lt;br /&gt;
Start Freeswitch and add it to the default runlevel.&lt;br /&gt;
 rc-update add freeswitch&lt;br /&gt;
 /etc/init.d/freeswitch start&lt;br /&gt;
You should now be able to dial into Freeswitch voicemail by registering a SIP user agent to freeswitch (user=1000-1019, password=12345 as defined above) and directing a SIP call to any of the other extensions between 1000 and 1019, or the extension 4000. For each voicemail account, the default password is the same as the extension. For more details on this, please refer to Freeswitch documentation. Please keep in mind that these extensions and voicemail are part of the sample config and are not managed by ACF. We will add ACF-managed voicemail accounts below.&lt;br /&gt;
&lt;br /&gt;
== 5. Configure acf-freeswitch-vmail ==&lt;br /&gt;
Add the voicemail authenticator to the ACF configuration. This will allow voicemail users to log into ACF to check their voicemail.&lt;br /&gt;
 echo &amp;quot;authenticator = authenticator-freeswitch-vmail.lua,authenticator-plaintext&amp;quot; &amp;gt;&amp;gt; /etc/acf/acf.conf&lt;br /&gt;
&lt;br /&gt;
= ACF Web interface =&lt;br /&gt;
You can now browse to https://&amp;lt;ip address&amp;gt; to access the web interface. To log in for administration, use the root user and the system root password (this is the default configuration set up by setup-acf).&lt;br /&gt;
== Users ==&lt;br /&gt;
The Voicemail | Users tab will display all of the voicemail users that are managed through the ACF web interface. At the start, this will be empty. When you create Users in the ACF, it will modify the freeswitch dialplan and directory (using xml_curl) to allow these users to receive and retrieve voicemail. It will also allow these users log into the ACF interface to check their voicemail and modify voicemail settings. The password defined here will control both ACF and IVR access.&lt;br /&gt;
== Voicemail ==&lt;br /&gt;
The Voicemail | Voicemail tab will allow you to view, listen to, download, forward, and delete voicemail messages.&lt;br /&gt;
== Config ==&lt;br /&gt;
The Voicemail | Config tab allows administrators to modify the acf-freeswitch-vmail configuration.&lt;br /&gt;
== Settings ==&lt;br /&gt;
The Voicemail | Settings tab allows voicemail users to modify their settings.&lt;br /&gt;
&lt;br /&gt;
= Demonstration =&lt;br /&gt;
We are now ready to create voicemail accounts with ACF. The freeswitch sample config creates a fairly full dialplan and we do not want to stomp on any of its features, so we will demonstrate accounts in the 2100 - 2199 range, which is unused.&lt;br /&gt;
# Dial extension 2100 to verify what happens when an unsupported extension is called&lt;br /&gt;
# Logged into ACF as root, create a voicemail user with extension 2100 and some password&lt;br /&gt;
# Dial extension 2100 again to verify that you can now leave a voicemail for extension 2100&lt;br /&gt;
# Dial extension 4000 and log in as user 2100 with the password you defined above to listen to the message&lt;br /&gt;
# Log out of ACF as root and log in again as user 2100 with the password you defined above to view your message&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=Freeswitch_Voicemail_On_Alpine_Linux&amp;diff=10664</id>
		<title>Freeswitch Voicemail On Alpine Linux</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=Freeswitch_Voicemail_On_Alpine_Linux&amp;diff=10664"/>
		<updated>2015-04-10T21:14:56Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: First cut&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Draft}}&lt;br /&gt;
= Overview =&lt;br /&gt;
This page describes how to install a basic voicemail server based upon Freeswitch including a web interface based upon ACF. It is built and tested on Alpine Linux 3.2.&lt;br /&gt;
&lt;br /&gt;
= Setup Procedure=&lt;br /&gt;
== 1. Configure the machine ==&lt;br /&gt;
Install and configure a basic Alpine Linux server with the ACF web interface.&lt;br /&gt;
 setup-alpine&lt;br /&gt;
 setup-acf&lt;br /&gt;
&lt;br /&gt;
== 2. Install Freeswitch ==&lt;br /&gt;
 apk add freeswitch freeswitch-sounds-en-us-callie-8000 freeswitch-flite acf-freeswitch acf-freeswitch-vmail&lt;br /&gt;
&lt;br /&gt;
== 3. Configure Freeswitch ==&lt;br /&gt;
We will use the Freeswitch sample configuration for the purposes of this document. It is not recommended to use this config in production, but it will allow us to quickly get voicemail up and running. Rather than installing the package, we will use apk to fetch the files.&lt;br /&gt;
 apk fetch --quiet --stdout freeswitch-sample-config | tar -C / -zvx&lt;br /&gt;
Modify the default password to avoid warnings and delays (obviously, you can choose something other than 12345 if you want)&lt;br /&gt;
 sed -i s/&amp;quot;default_password=1234&amp;quot;/&amp;quot;default_password=12345&amp;quot;/ /etc/freeswitch/vars.xml&lt;br /&gt;
Fix the voicemail storage directory in &#039;&#039;/etc/freeswitch/autoload_configs/voicemail.conf.xml&#039;&#039; to point to &#039;&#039;/var/lib/freeswitch/voicemail&#039;&#039;&lt;br /&gt;
 &amp;lt;param name=&amp;quot;storage-dir&amp;quot; value=&amp;quot;/var/lib/freeswitch/voicemail&amp;quot;/&amp;gt;&lt;br /&gt;
Set the sounds directory in &#039;&#039;/etc/freeswitch/vars.xml&#039;&#039; by adding the following line:&lt;br /&gt;
 &amp;lt;X-PRE-PROCESS cmd=&amp;quot;set&amp;quot; data=&amp;quot;sounds_dir=/usr/share/freeswitch/sounds&amp;quot;/&amp;gt;&lt;br /&gt;
Use Freeswitch XML curl to call into ACF to determine voicemail accounts. To do so, edit &#039;&#039;/etc/freeswitch/autoload_configs/xml_curl.conf.xml&#039;&#039; and add the following bindings:&lt;br /&gt;
     &amp;lt;binding name=&amp;quot;voicemaildialplan&amp;quot;&amp;gt;&lt;br /&gt;
       &amp;lt;param name=&amp;quot;gateway-url&amp;quot; value=&amp;quot;https://127.0.0.1/cgi-bin/acf/freeswitch-vmail/vmail/processdialplanxml&amp;quot; bindings=&amp;quot;dialplan&amp;quot;/&amp;gt;&lt;br /&gt;
     &amp;lt;/binding&amp;gt;&lt;br /&gt;
     &amp;lt;binding name=&amp;quot;voicemaildirectory&amp;quot;&amp;gt;&lt;br /&gt;
       &amp;lt;param name=&amp;quot;gateway-url&amp;quot; value=&amp;quot;https://127.0.0.1/cgi-bin/acf/freeswitch-vmail/vmail/processdirectoryxml&amp;quot; bindings=&amp;quot;directory&amp;quot;/&amp;gt;&lt;br /&gt;
     &amp;lt;/binding&amp;gt;&lt;br /&gt;
And modify &#039;&#039;/etc/freeswitch/autoload_configs/modules.conf.xml&#039;&#039; to load the mod_xml_curl module&lt;br /&gt;
 &amp;lt;load module=&amp;quot;mod_xml_curl&amp;quot;/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 4. Start Freeswitch ==&lt;br /&gt;
Start Freeswitch and add it to the default runlevel.&lt;br /&gt;
 rc-update add freeswitch&lt;br /&gt;
 /etc/init.d/freeswitch start&lt;br /&gt;
You should now be able to dial into Freeswitch voicemail by registering a SIP user agent to freeswitch (user=1000-1019, password=12345 as defined above) and directing a SIP call to any of the other extensions between 1000 and 1019, or the extension 4000. For each voicemail account, the default password is the same as the extension. For more details on this, please refer to Freeswitch documentation. Please keep in mind that these extensions and voicemail are part of the sample config and are not managed by ACF. We will add ACF-managed voicemail accounts below.&lt;br /&gt;
&lt;br /&gt;
== 5. Configure acf-freeswitch-vmail ==&lt;br /&gt;
Add the voicemail authenticator to the ACF configuration. This will allow voicemail users to log into ACF to check their voicemail.&lt;br /&gt;
 echo &amp;quot;authenticator = authenticator-freeswitch-vmail.lua,authenticator-plaintext&amp;quot; &amp;gt;&amp;gt; /etc/acf/acf.conf&lt;br /&gt;
&lt;br /&gt;
= ACF Web interface =&lt;br /&gt;
You can now browse to https://&amp;lt;ip address&amp;gt; to access the web interface. To log in for administration, use the root user and the system root password (this is the default configuration set up by setup-acf).&lt;br /&gt;
== Users ==&lt;br /&gt;
The Voicemail | Users tab will display all of the voicemail users that are managed through the ACF web interface. At the start, this will be empty. When you create Users in the ACF, it will modify the freeswitch dialplan and directory (using xml_curl) to allow these users to receive and retrieve voicemail. It will also allow these users log into the ACF interface to check their voicemail and modify voicemail settings. The password defined here will control both ACF and IVR access.&lt;br /&gt;
== Voicemail ==&lt;br /&gt;
The Voicemail | Voicemail tab will allow you to view, listen to, download, forward, and delete voicemail messages.&lt;br /&gt;
== Config ==&lt;br /&gt;
The Voicemail | Config tab allows administrators to modify the acf-freeswitch-vmail configuration.&lt;br /&gt;
== Settings ==&lt;br /&gt;
The Voicemail | Settings tab allows voicemail users to modify their settings.&lt;br /&gt;
&lt;br /&gt;
= Demonstration =&lt;br /&gt;
We are now ready to create voicemail accounts with ACF. The freeswitch sample config creates a fairly full dialplan and we do not want to stomp on any of its features, so we will demonstrate accounts in the 2100 - 2199 range, which is unused.&lt;br /&gt;
# Dial extension 2100 to verify what happens when an unsupported extension is called&lt;br /&gt;
# Logged into ACF as root, create a voicemail user with extension 2100 and some password&lt;br /&gt;
# Dial extension 2100 again to verify that you can now leave a voicemail for extension 2100&lt;br /&gt;
# Dial extension 4000 and log in as user 2100 with the password you defined above to listen to the message&lt;br /&gt;
# Log out of ACF as root and log in again as user 2100 with the password you defined above to view your message&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=ACF_how_to_write&amp;diff=10535</id>
		<title>ACF how to write</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=ACF_how_to_write&amp;diff=10535"/>
		<updated>2015-03-09T19:34:23Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Update to latest style&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=How to Write an ACF=&lt;br /&gt;
&lt;br /&gt;
For some examples please see the Web Configuration Framework projects in the Alpine Linux git repository&lt;br /&gt;
&lt;br /&gt;
http://git.alpinelinux.org/&lt;br /&gt;
&lt;br /&gt;
*acf-unbound - a simple ACF to control a service&lt;br /&gt;
*acf-awall - a slightly more complicated ACF for a firewall&lt;br /&gt;
*acf-provisioning - a complicated database application based on ACF&lt;br /&gt;
*...&lt;br /&gt;
&lt;br /&gt;
==From &amp;lt;nil&amp;gt; to a running ACF example application==&lt;br /&gt;
&lt;br /&gt;
===Step 1 - The Programming Language===&lt;br /&gt;
* ACF uses lua as its programming language. Have a look at [http://www.lua.org/ lua.org] before starting.&lt;br /&gt;
&lt;br /&gt;
===Step 2 - The Application Environment===&lt;br /&gt;
* Setup the ACF web application by running &#039;&#039;setup-acf&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
===Step 3 - Create A Development Directory===&lt;br /&gt;
* In your user home create a directory for your application (e.g. mkdir ~/myapp)&lt;br /&gt;
* And cd into it (e.g. cd ~/myapp)&lt;br /&gt;
&lt;br /&gt;
===Step 4 - MVC, How Does It Affect My Coding?===&lt;br /&gt;
ACF is an MVC based framework. What does this mean to you? Your application is separated into three layers: Model, View, Controller - each of which may have one or more files.&lt;br /&gt;
* Controller: The event dispatcher. Most of the controller functionality is handled by the ACF mvc.lua code and some standard controllers (such as acf_www-controller.lua or acf_cli-controller.lua). For the controller layer of your new ACF package, you must export one lua function per action in a lua module named &#039;myapp-controller.lua&#039;. The ACF controller code will interpret the user interaction to load your new controller and fire the appropriate action - the same-named function in your controller will be called.&lt;br /&gt;
* View: The view layer defines what your application will look like. For most actions, such as forms, your application can use the built-in automatic view generation. For others, you can link to standard views which are included in the acf-core package. For other actions, such as lists of data, you may create view files, each presenting a dynamic HTML page with only as much code as necessary to display the data you receive from the controller.&lt;br /&gt;
* Model:  The &#039;real work&#039; is done in the Model (e.g. modifying config files, starting/stopping services etc.). Each action exported by your controller will call into model functions to retrieve data and carry out actions.&lt;br /&gt;
&lt;br /&gt;
===Step 5 - The Example Files To Start With===&lt;br /&gt;
Now let us have a look at the files we need to place into our application directory:&lt;br /&gt;
&lt;br /&gt;
* config.mk&lt;br /&gt;
* Makefile&lt;br /&gt;
* myapp-controller.lua&lt;br /&gt;
* myapp-model.lua&lt;br /&gt;
* myapp.roles&lt;br /&gt;
* myapp.menu&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;config.mk:&#039;&#039;&#039;&lt;br /&gt;
For use with the Makefile. Just copy/paste it. We will look at it later.&lt;br /&gt;
 prefix=/usr&lt;br /&gt;
 datadir=${prefix}/share&lt;br /&gt;
 sysconfdir=${prefix}/etc&lt;br /&gt;
 localstatedir=${prefix}/var&lt;br /&gt;
 acfdir=${datadir}/acf&lt;br /&gt;
 wwwdir=${acfdir}/www&lt;br /&gt;
 cgibindir=${acfdir}/cgi-bin&lt;br /&gt;
 appdir=${acfdir}/app&lt;br /&gt;
 acflibdir=${acfdir}/lib&lt;br /&gt;
 sessionsdir=${localstatedir}/lib/acf/sessions&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Makefile:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Makefile is called to install our ACF application so that we can see it working.&lt;br /&gt;
 APP_NAME=myapp&lt;br /&gt;
 PACKAGE=acf-$(APP_NAME)&lt;br /&gt;
 VERSION=0.1&lt;br /&gt;
 &lt;br /&gt;
 APP_DIST=        \&lt;br /&gt;
         myapp*        \&lt;br /&gt;
 &lt;br /&gt;
 EXTRA_DIST=README Makefile config.mk&lt;br /&gt;
 &lt;br /&gt;
 DISTFILES=$(APP_DIST) $(EXTRA_DIST)&lt;br /&gt;
 &lt;br /&gt;
 TAR=tar&lt;br /&gt;
 &lt;br /&gt;
 P=$(PACKAGE)-$(VERSION)&lt;br /&gt;
 tarball=$(P).tar.bz2&lt;br /&gt;
 install_dir=$(DESTDIR)/$(appdir)/$(APP_NAME)&lt;br /&gt;
 &lt;br /&gt;
 all:&lt;br /&gt;
 clean:&lt;br /&gt;
 	rm -rf $(tarball) $(P)&lt;br /&gt;
 &lt;br /&gt;
 dist: $(tarball)&lt;br /&gt;
 &lt;br /&gt;
 install:&lt;br /&gt;
 	mkdir -p &amp;quot;$(install_dir)&amp;quot;&lt;br /&gt;
 	cp -a $(APP_DIST) &amp;quot;$(install_dir)&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 $(tarball):     $(DISTFILES)&lt;br /&gt;
 	rm -rf $(P)&lt;br /&gt;
 	mkdir -p $(P)&lt;br /&gt;
 	cp $(DISTFILES) $(P)&lt;br /&gt;
 	$(TAR) -jcf $@ $(P)&lt;br /&gt;
 	rm -rf $(P)&lt;br /&gt;
 &lt;br /&gt;
 # target that creates a tar package, unpacks is and install from package&lt;br /&gt;
 dist-install: $(tarball)&lt;br /&gt;
 	$(TAR) -jxf $(tarball)&lt;br /&gt;
 	$(MAKE) -C $(P) install DESTDIR=$(DESTDIR)&lt;br /&gt;
 	rm -rf $(P)&lt;br /&gt;
 &lt;br /&gt;
 include config.mk&lt;br /&gt;
 &lt;br /&gt;
 .PHONY: all clean dist install dist-install&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;myapp-controller.lua:&#039;&#039;&#039;&lt;br /&gt;
 -- the myapp controller&lt;br /&gt;
 local mymodule = {}&lt;br /&gt;
 &lt;br /&gt;
 mymodule.default_action = &amp;quot;myaction&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 mymodule.myaction = function(self)&lt;br /&gt;
    -- self.clientdata contains the user data&lt;br /&gt;
    -- self.model points to our model&lt;br /&gt;
    -- use the helper function to implement our form&lt;br /&gt;
    return self.handle_form(self, self.model.getdata, self.model.setdata, self.clientdata, &amp;quot;Submit&amp;quot;, &amp;quot;Edit data&amp;quot;, &amp;quot;Data Submitted&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 return mymodule&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;myapp-model.lua:&#039;&#039;&#039;&lt;br /&gt;
 -- acf model for myapp&lt;br /&gt;
 local mymodule = {}&lt;br /&gt;
 &lt;br /&gt;
 local cfgfile = &amp;quot;/tmp/myfile&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 -- This function returns a cfe (table of values) containing the file&#039;s&lt;br /&gt;
 -- value as a string. If the file does not exist, we&#039;ll&lt;br /&gt;
 -- simply return &amp;quot;&amp;quot; (an empty string, but NOT nil)&lt;br /&gt;
 mymodule.getdata = function(self, clientdata)&lt;br /&gt;
    local retval = cfe({ type=&amp;quot;group&amp;quot;, value={}, label=&amp;quot;Data&amp;quot; })&lt;br /&gt;
    retval.value.data = cfe({ type=&amp;quot;longtext&amp;quot;, label=&amp;quot;Data&amp;quot; })&lt;br /&gt;
 &lt;br /&gt;
    local fileptr = io.open(cfgfile, &amp;quot;r&amp;quot;)&lt;br /&gt;
    if fileptr ~= nil then&lt;br /&gt;
       retval.value.data.value = fileptr:read(&amp;quot;*a&amp;quot;) or &amp;quot;&amp;quot;&lt;br /&gt;
       fileptr:close()&lt;br /&gt;
    end&lt;br /&gt;
 &lt;br /&gt;
    return retval&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 -- This function will write new contents into our file&lt;br /&gt;
 -- The newdata parameter receives the same cfe as returned by getdata, now with the user data filled in&lt;br /&gt;
 mymodule.setdata = function(self, newdata, action)&lt;br /&gt;
    fileptr = io.open( cfgfile, &amp;quot;w+&amp;quot; )&lt;br /&gt;
    if fileptr ~= nil then&lt;br /&gt;
       fileptr:write(newdata.value.data.value)&lt;br /&gt;
       fileptr:close()&lt;br /&gt;
    else&lt;br /&gt;
       newdata.errtxt = &amp;quot;Failed to save data&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    return newdata&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 return mymodule&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;myapp.roles:&#039;&#039;&#039;&lt;br /&gt;
 GUEST=myapp:myaction&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;myapp.menu:&#039;&#039;&#039;&lt;br /&gt;
 # Cat   Group   Tab     Action&lt;br /&gt;
 Test    MyApp   MyAction  myaction&lt;br /&gt;
&lt;br /&gt;
===Step 6 - What Does It Do?===&lt;br /&gt;
This program just displays a &amp;amp;lt;textarea&amp;gt; box and a &amp;quot;Submit&amp;quot; button. The user can enter text that is saved into a file once he presses &amp;quot;Submit&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
====In Depth====&lt;br /&gt;
Now let us have a closer look at the different files&#039; contents:&lt;br /&gt;
&lt;br /&gt;
=====myapp-controller.lua=====&lt;br /&gt;
The controller is an event dispatcher. So, here you define all the actions that the user can call or that are defined in the menu. Each action is a separate function that will receive &#039;&#039;self&#039;&#039; as the only parameter.&lt;br /&gt;
&lt;br /&gt;
In our case the action is &#039;&#039;myaction&#039;&#039; - a simple form.&lt;br /&gt;
&lt;br /&gt;
This function can call the &#039;&#039;model&#039;s&#039;&#039; functions to update and/or retrieve data (e.g. self.model.getdata()).&lt;br /&gt;
&lt;br /&gt;
Anything that this function returns will be passed on to the &#039;&#039;view&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=====myapp-model.lua=====&lt;br /&gt;
The functions defined in here can be accessed by the controller to update/set/retrieve data, start/stop services, basically do any &#039;real work&#039;.&lt;br /&gt;
&lt;br /&gt;
In our case, we have implemented the getdata/setdata functions required for a form.&lt;br /&gt;
&lt;br /&gt;
The getdata function receives a copy of &#039;self&#039;, a clientdata table, and a string containing the submit action. It will generate a &#039;CFE&#039; table defining the form and including the current data.&lt;br /&gt;
&lt;br /&gt;
The setdata function is only called when the form is submitted, and it receives a copy of &#039;self&#039; and the updated form &#039;CFE&#039; now containing the submitted data. The setdata function will attempt to perform the action, returning the same form &#039;CFE&#039;. If there is an error, it will fill in the errtxt field of the &#039;CFE&#039;.&lt;br /&gt;
&lt;br /&gt;
=====myapp.roles=====&lt;br /&gt;
This file determines which users have access to which controllers and views. A separate &#039;&#039;roles&#039;&#039; file is generally defined for each ACF. The format of the file is as follows:&lt;br /&gt;
 group=controller:action[,controller:action]&lt;br /&gt;
Each line defines controller:action combinations that are permitted for a particular group. &#039;&#039;&#039;GUEST&#039;&#039;&#039; is a special group to which all users, including anonymous users, are members.&lt;br /&gt;
&lt;br /&gt;
=====myapp.menu=====&lt;br /&gt;
In this file you define:&lt;br /&gt;
* &#039;&#039;&#039;The Category&#039;&#039;&#039; in which a menu entry for your program will appear&lt;br /&gt;
* &#039;&#039;&#039;The Group&#039;&#039;&#039; menu name under Category for this controller&lt;br /&gt;
* &#039;&#039;&#039;The Tab&#039;&#039;&#039; name on the controller page&lt;br /&gt;
* &#039;&#039;&#039;The Action&#039;&#039;&#039; with-in your controller that will be called once the user clicks on the menu entry or tab defined by Category, Group, and Tab.&lt;br /&gt;
&lt;br /&gt;
===Step 7 - How To Get It Going?===&lt;br /&gt;
Once you have completed all the above mentioned steps, go on with:&lt;br /&gt;
* sudo make install (this will install your app)&lt;br /&gt;
* point your browser to https://ip-of-your-dev-host/&lt;br /&gt;
&lt;br /&gt;
===More Info===&lt;br /&gt;
====Where is the View?====&lt;br /&gt;
The above example does not contain any code for a view. So, how is the action getting displayed?&lt;br /&gt;
&lt;br /&gt;
For every action that you define in myapp-controller.lua, you can define a separate view file named: myapp-&#039;&#039;action&#039;&#039;-html.lsp&lt;br /&gt;
&lt;br /&gt;
If there is no view file for a specific action, the application will look for a generic view file for the controller named: myapp-html.lsp&lt;br /&gt;
&lt;br /&gt;
If that file does not exist, the ACF controller will attempt to display the &#039;CFE&#039; using the built-in library functions. This works well for forms, and is what allows us to display our view here.&lt;br /&gt;
&lt;br /&gt;
Here is a &#039;&#039;view&#039;&#039; file that displays our action using the built-in library functions. It looks exactly the same as when no view exists.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;myapp-myaction-html.lsp:&#039;&#039;&#039; &lt;br /&gt;
 &amp;lt;%&lt;br /&gt;
 local form, viewlibrary, page_info, session = ...&lt;br /&gt;
 htmlviewfunctions = require(&amp;quot;htmlviewfunctions&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 htmlviewfunctions.displayitem(form, page_info)&lt;br /&gt;
 %&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The view receives the data to be displayed from the &#039;&#039;controller&#039;&#039;. The view has access to the table returned by the controller action along with a helper library, a table of page information, and the session data &#039;&#039;(see the second line)&#039;&#039;.  The view can also load other libraries, but it should not directly access the &#039;&#039;controller&#039;&#039;, &#039;&#039;model&#039;&#039;, or any global variables.&lt;br /&gt;
&lt;br /&gt;
====How to exchange data between model-view-controller?====&lt;br /&gt;
To exchange data between model, view, and controller ACF uses &#039;&#039;Configuration Framework Entities (CFEs)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Please see [[ACF_core_principles]] for further details on CFEs.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:ACF]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=ACF_mvc.lua_example&amp;diff=10534</id>
		<title>ACF mvc.lua example</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=ACF_mvc.lua_example&amp;diff=10534"/>
		<updated>2015-03-09T14:59:12Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Minor changes&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Set the hostname with mvc.lua =&lt;br /&gt;
&lt;br /&gt;
In this example we will create a simple hostname-setting command-line application using mvc.lua.  Once the controller/model are built, you can use &#039;&#039;the same code&#039;&#039; to set the hostname via the web with a web-based application controller.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
For this example, we will assume you have root access on the linux box you are running on (preferably an Alpine Linux box!)&lt;br /&gt;
&lt;br /&gt;
== Get the mvc.lua module == &lt;br /&gt;
&lt;br /&gt;
Get the mvc.lua module from the git repository.&lt;br /&gt;
&lt;br /&gt;
 wget http://git.alpinelinux.org/cgit/acf-core/plain/lua/mvc.lua&lt;br /&gt;
&lt;br /&gt;
== Create a model and controller ==&lt;br /&gt;
&lt;br /&gt;
Create a file &#039;&#039;&#039;hostname-controller.lua&#039;&#039;&#039;, defining the functions that an &amp;quot;end user&amp;quot; could run. We will only create one action, edithostname, which will be used to read and update the hostname. Since the action makes changes to the system, it naturally takes the form of a &#039;form&#039;:&lt;br /&gt;
&lt;br /&gt;
=== hostname-controller.lua ===&lt;br /&gt;
 -- Controller for editing hostname&lt;br /&gt;
 local mymodule = {} &lt;br /&gt;
 &lt;br /&gt;
 mymodule.edithostname = function (self)&lt;br /&gt;
         return self.handle_form(self, self.model.get_hostname, self.model.set_hostname, self.clientdata, &amp;quot;Edit&amp;quot;, &amp;quot;Edit Hostname&amp;quot;, &amp;quot;Hostname Updated&amp;quot;)&lt;br /&gt;
 end                                   &lt;br /&gt;
 &lt;br /&gt;
 return mymodule&lt;br /&gt;
&lt;br /&gt;
Create a file &#039;&#039;&#039;hostname-model.lua&#039;&#039;&#039;, defining the model functions to get and set the hostname.  We return a cfe table for each function including the form with the one entry for hostname:&lt;br /&gt;
&lt;br /&gt;
=== hostname-model.lua ===&lt;br /&gt;
 -- Model functions for retrieving / setting the hostname&lt;br /&gt;
 local mymodule = {}&lt;br /&gt;
 &lt;br /&gt;
 -- Create a cfe defining the form for editing the hostname and containing the current value&lt;br /&gt;
 mymodule.get_hostname = function(self, clientdata)&lt;br /&gt;
         local retval = cfe({ type=&amp;quot;group&amp;quot;, value={}, label=&amp;quot;Hostname&amp;quot; })&lt;br /&gt;
 &lt;br /&gt;
         -- Warning - io.popen has security risks, never pass user data to io.popen&lt;br /&gt;
         local f = io.popen (&amp;quot;/bin/hostname&amp;quot;)&lt;br /&gt;
         local n = f:read(&amp;quot;*a&amp;quot;) or &amp;quot;none&amp;quot;&lt;br /&gt;
         f:close()&lt;br /&gt;
         n=string.gsub(n, &amp;quot;\n$&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
         retval.value.hostname = cfe({ value=n, label=&amp;quot;Hostname&amp;quot; })&lt;br /&gt;
 &lt;br /&gt;
         return retval&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 -- Set the hostname from the value contained in the cfe created by get_hostname&lt;br /&gt;
 mymodule.set_hostname = function(self, hostnameform, action)&lt;br /&gt;
         local success = true&lt;br /&gt;
 &lt;br /&gt;
         -- Check to make sure the name is valid&lt;br /&gt;
         if (hostnameform.value.hostname.value == &amp;quot;&amp;quot;) then&lt;br /&gt;
                 success = false&lt;br /&gt;
                 hostnameform.value.hostname.errtxt = &amp;quot;Hostname must not be blank&amp;quot;&lt;br /&gt;
         elseif (#hostnameform.value.hostname.value &amp;gt; 16) then&lt;br /&gt;
                 success = false&lt;br /&gt;
                 hostnameform.value.hostname.errtxt = &amp;quot;Hostname must be 16 characters or less&amp;quot;&lt;br /&gt;
         elseif (string.find(hostnameform.value.hostname.value, &amp;quot;[^%w%_%-]&amp;quot;)) then&lt;br /&gt;
                 success = false&lt;br /&gt;
                 hostnameform.value.hostname.errtxt = &amp;quot;Hostname can contain alphanumerics only&amp;quot;&lt;br /&gt;
         end&lt;br /&gt;
 &lt;br /&gt;
         -- If it is valid, set the hostname&lt;br /&gt;
         if ( success ) then&lt;br /&gt;
                 local f = io.open(&amp;quot;/etc/hostname&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
                 if f then&lt;br /&gt;
                         f:write(hostnameform.value.hostname.value .. &amp;quot;\n&amp;quot;)&lt;br /&gt;
                         f:close()&lt;br /&gt;
                 end&lt;br /&gt;
                 -- Warning - io.popen has security risks, never pass user data to io.popen&lt;br /&gt;
                 f = io.popen (&amp;quot;/bin/hostname -F /etc/hostname&amp;quot;)&lt;br /&gt;
                 f:close()&lt;br /&gt;
         else&lt;br /&gt;
                 hostnameform.errtxt = &amp;quot;Failed to set hostname&amp;quot;&lt;br /&gt;
         end&lt;br /&gt;
 &lt;br /&gt;
         return hostnameform&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 return mymodule&lt;br /&gt;
&lt;br /&gt;
== Optionally test the model code (without mvc.lua)  ==&lt;br /&gt;
&lt;br /&gt;
If you want, you can create a &#039;&#039;&#039;test.lua&#039;&#039;&#039; script to validate the model code works on its own:&lt;br /&gt;
&lt;br /&gt;
=== test.lua ===&lt;br /&gt;
 require(&amp;quot;mvc&amp;quot;) -- Needed for cfe function definition&lt;br /&gt;
 m=require(&amp;quot;hostname-model&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 local form = m.get_hostname()&lt;br /&gt;
 form.value.hostname.value = arg[1] or &amp;quot;&amp;quot;&lt;br /&gt;
 form = m.set_hostname(nil, form)&lt;br /&gt;
 &lt;br /&gt;
 if form.errtxt then&lt;br /&gt;
         print(&amp;quot;FAILED: &amp;quot;..form.value.hostname.errtxt or form.errtxt)&lt;br /&gt;
 end&lt;br /&gt;
 form = m.get_hostname()&lt;br /&gt;
 print(form.value.hostname.value)&lt;br /&gt;
&lt;br /&gt;
You can then test this with:&lt;br /&gt;
&lt;br /&gt;
 #lua test.lua &amp;quot;Alpine&amp;quot;&lt;br /&gt;
  Alpine&lt;br /&gt;
&lt;br /&gt;
 #lua test.lua &amp;quot;Invalid Name&amp;quot;&lt;br /&gt;
  FAILED: Hostname can contain alphanumerics only&lt;br /&gt;
  Alpine&lt;br /&gt;
&lt;br /&gt;
== Add the package to the ACF framework ==&lt;br /&gt;
&lt;br /&gt;
To make the model and controller work within the ACF mvc.lua framework, we must do several things.   &lt;br /&gt;
&lt;br /&gt;
1. Install the ACF core package:&lt;br /&gt;
&lt;br /&gt;
 apk add acf-core&lt;br /&gt;
&lt;br /&gt;
Optionally, you can add the entire web-based ACF framework:&lt;br /&gt;
&lt;br /&gt;
 setup-acf&lt;br /&gt;
&lt;br /&gt;
2. Modify the ACF configuration file to look in /etc/acf/app/ for additional packages. Edit the /etc/acf/acf.conf file to add the &#039;&#039;/etc/acf/app/&#039;&#039; directory to the &#039;&#039;appdir&#039;&#039; comma-separated list:&lt;br /&gt;
&lt;br /&gt;
 appdir=/etc/acf/app/,/usr/share/acf/app/&lt;br /&gt;
&lt;br /&gt;
3. Move the model and controller to the new package directory. We will call the package &amp;quot;test&amp;quot;:&lt;br /&gt;
&lt;br /&gt;
 mkdir -p /etc/acf/app/test&lt;br /&gt;
 mv hostname-*.lua /etc/acf/app/test&lt;br /&gt;
&lt;br /&gt;
4. Test the new package using the acf-cli application:&lt;br /&gt;
&lt;br /&gt;
 # acf-cli /test/hostname/edithostname&lt;br /&gt;
 result = {}&lt;br /&gt;
 result[&amp;quot;label&amp;quot;] = &amp;quot;Edit Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;option&amp;quot;] = &amp;quot;Edit&amp;quot;&lt;br /&gt;
 result[&amp;quot;type&amp;quot;] = &amp;quot;form&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;label&amp;quot;] = &amp;quot;Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;type&amp;quot;] = &amp;quot;text&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;value&amp;quot;] = &amp;quot;Alpine&amp;quot;&lt;br /&gt;
 # acf-cli /test/hostname/edithostname hostname=test submit=true&lt;br /&gt;
 result = {}&lt;br /&gt;
 result[&amp;quot;descr&amp;quot;] = &amp;quot;Hostname Updated&amp;quot;&lt;br /&gt;
 result[&amp;quot;label&amp;quot;] = &amp;quot;Edit Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;option&amp;quot;] = &amp;quot;Edit&amp;quot;&lt;br /&gt;
 result[&amp;quot;type&amp;quot;] = &amp;quot;form&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;label&amp;quot;] = &amp;quot;Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;type&amp;quot;] = &amp;quot;text&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;value&amp;quot;] = &amp;quot;test&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Note that the first case read the existing hostname and the second case updated it. The output of the acf-cli application is a serialized version of the cfe form, which is good for testing but not too useful in real life.&lt;br /&gt;
&lt;br /&gt;
== Enable the package in the ACF web interface ==&lt;br /&gt;
&lt;br /&gt;
1. Add the web-based ACF framework:&lt;br /&gt;
&lt;br /&gt;
 setup-acf&lt;br /&gt;
&lt;br /&gt;
2. Configure all users to have access to the new hostname action. Edit &amp;quot;/etc/acf/app/test/hostname.roles&amp;quot; file and add GUEST permission for the edithostname action:&lt;br /&gt;
&lt;br /&gt;
 echo &amp;quot;GUEST=hostname/edithostname&amp;quot; &amp;gt; /etc/acf/app/test/hostname.roles&lt;br /&gt;
&lt;br /&gt;
The new action should now be visible by browsing to &#039;&#039;https://IP-of-host/cgi-bin/acf/test/hostname/edithostname&#039;&#039;. Obviously you might want to reconsider providing GUEST access to this action, because this allows unauthenticated users to modify your hostname.&lt;br /&gt;
&lt;br /&gt;
3. Add the new hostname action to the ACF menu. Edit &amp;quot;/etc/acf/app/test/hostname.menu&amp;quot; file and add a menu item:&lt;br /&gt;
&lt;br /&gt;
 echo &amp;quot;Test Hostname Edit edithostname&amp;quot; &amp;gt; /etc/acf/app/test/hostname.menu&lt;br /&gt;
&lt;br /&gt;
You will need to log off from the ACF interface (or delete the session cookie) before the new menu item will be visible.&lt;br /&gt;
&lt;br /&gt;
== Make an MVC based application ==&lt;br /&gt;
You have two options for creating an MVC based application of your own.&lt;br /&gt;
&lt;br /&gt;
1. Use the &#039;&#039;dispatch&#039;&#039; function. This is the method used by the web interface and the acf-cli application. The action to be dispatched is passed in a string format &#039;&#039;/prefix/controller/action&#039;&#039; and the input values are passed in as a clientinfo table. Output is written to stdout.&lt;br /&gt;
&lt;br /&gt;
2. Use the &#039;&#039;new&#039;&#039; function to load the desired controller and then directly call the controller actions. For handling forms and user input, the application will call the action once to retrieve the form cfe, then fill in the desired input values into the cfe and call the action again to submit the form and perform the desired action. The MVC application is responsible for any user interaction and display of the results.&lt;br /&gt;
&lt;br /&gt;
=== Use the Dispatch method ===&lt;br /&gt;
Todo&lt;br /&gt;
&lt;br /&gt;
=== Use the New method ===&lt;br /&gt;
Todo&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Obsolete}}&lt;br /&gt;
4. Create a dispatch wrapper program, named &#039;&#039;&#039;helloworld.lua&#039;&#039;&#039; in the current directory:&lt;br /&gt;
&lt;br /&gt;
 -- Simple CLI based mvc application&lt;br /&gt;
 &lt;br /&gt;
 -- this is to get around having to store&lt;br /&gt;
 -- the config file in /etc/helloworld/helloworld.conf&lt;br /&gt;
 ENV={}&lt;br /&gt;
 ENV.HOME=&amp;quot;.&amp;quot; &lt;br /&gt;
 &lt;br /&gt;
 -- load the module&lt;br /&gt;
 require(&amp;quot;mvc&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 -- create an new &amp;quot;mvc object&amp;quot;&lt;br /&gt;
 MVC=mvc:new()&lt;br /&gt;
 &lt;br /&gt;
 -- load the config file so we can find the appdir&lt;br /&gt;
 MVC:read_config(&amp;quot;helloworld&amp;quot;) &lt;br /&gt;
 &lt;br /&gt;
 -- create an application container&lt;br /&gt;
 APP=MVC:new(&amp;quot;app&amp;quot;)&lt;br /&gt;
  &lt;br /&gt;
 -- dispatch the request&lt;br /&gt;
 APP.clientdata.hostname=arg[2]&lt;br /&gt;
 APP:dispatch( &amp;quot;&amp;quot;, &amp;quot;hostname&amp;quot;, (arg[1] or &amp;quot;&amp;quot;))&lt;br /&gt;
&lt;br /&gt;
 -- destroy the mvc objects&lt;br /&gt;
 APP:destroy()&lt;br /&gt;
 MVC:destroy()&lt;br /&gt;
&lt;br /&gt;
This application loads the &amp;quot;mvc.lua&amp;quot; framework, creates an mvc &amp;quot;object&amp;quot; named &amp;quot;MVC&amp;quot;, then reads the helloworld.conf file to find out where the app dir is (helloworld/app/).  It then loads the &#039;&#039;&#039;app-controller.lua&#039;&#039;&#039; into a new &amp;quot;application level&amp;quot; object named &#039;&#039;&#039;APP&#039;&#039;&#039;.  Finally, it sets the clientdata and dispatches the hostname-controller/model pair.&lt;br /&gt;
&lt;br /&gt;
5.  Test the application:&lt;br /&gt;
 # hostname&lt;br /&gt;
 Alpine&lt;br /&gt;
 # lua helloworld.lua no-such-function foo&lt;br /&gt;
 The following unhandled application error occured:&lt;br /&gt;
 &lt;br /&gt;
 controller: &amp;quot;hostname&amp;quot; does not have a &amp;quot;no-such-function&amp;quot; action.&lt;br /&gt;
 # hostname&lt;br /&gt;
 Alpine&lt;br /&gt;
&lt;br /&gt;
 # hostname&lt;br /&gt;
 Alpine&lt;br /&gt;
 # lua helloworld.lua update Alline&lt;br /&gt;
 Your controller and application did not specify a view resolver.&lt;br /&gt;
 The MVC framework has no view available. sorry.&lt;br /&gt;
 # hostname&lt;br /&gt;
 Alline&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Note in the second case the hostname &#039;&#039;was&#039;&#039; changed, although the application does not know how to report success.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Create a view resolver and view formatter ==&lt;br /&gt;
&lt;br /&gt;
The view resolver is a function that returns a function that processes the view. The returned function receives input from the controller and generates the output to be displayed.&lt;br /&gt;
&lt;br /&gt;
We will build a very simple view resolver and view processor for our application. Add this to the end of &#039;&#039;&#039;helloworld/app/hostname-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 local private = {} &lt;br /&gt;
 &lt;br /&gt;
 private.view = function (f)&lt;br /&gt;
        if (f.msg) then&lt;br /&gt;
                print( (f.value or &amp;quot;&amp;quot;) .. &amp;quot; is not a valid hostname &amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                print (&amp;quot;Hostname is currently &amp;quot; .. f.value )&lt;br /&gt;
        end&lt;br /&gt;
 end &lt;br /&gt;
 &lt;br /&gt;
 view_resolver = function (self)&lt;br /&gt;
         return private.view&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Now we can test:&lt;br /&gt;
&lt;br /&gt;
 # lua helloworld.lua update &amp;quot;1 2 3&amp;quot;&lt;br /&gt;
    1 2 3 is not a valid hostname &lt;br /&gt;
 # lua helloworld.lua update        &lt;br /&gt;
    is not a valid hostname &lt;br /&gt;
 # lua helloworld.lua update Alpine&lt;br /&gt;
   Hostname is currently Alpine&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
But we have two problems:&lt;br /&gt;
&lt;br /&gt;
1. We now have to make a view resolver and view function for every controller.  If we add a &#039;&#039;date&#039;&#039; setting controller, we&#039;ll have to make a view resolver and view function for it, and so on.&lt;br /&gt;
&lt;br /&gt;
2. Perhaps more importantly, view_resolver is now an &amp;quot;action&amp;quot; in our appliction.  Recall that invalid actions are captured, but try this:&lt;br /&gt;
&lt;br /&gt;
  # lua helloworld.lua no_such_action Alpine&lt;br /&gt;
     The following unhandled application error occured:&lt;br /&gt;
   &lt;br /&gt;
     controller: &amp;quot;hostname&amp;quot; does not have a &amp;quot;no_such_action&amp;quot; action.&lt;br /&gt;
  &lt;br /&gt;
  # lua helloworld.lua view_resolver Alpine &lt;br /&gt;
     The following unhandled application error occured:&lt;br /&gt;
   &lt;br /&gt;
     ./helloworld/app/hostname-controller.lua:27: attempt to index local &#039;f&#039; (a function value)&lt;br /&gt;
     stack traceback:&lt;br /&gt;
        ./helloworld/app/hostname-controller.lua:27: in function &#039;viewfunc&#039;&lt;br /&gt;
        ./mvc.lua:139: in function &amp;lt;./mvc.lua:90&amp;gt;&lt;br /&gt;
        [C]: in function &#039;xpcall&#039;&lt;br /&gt;
        ./mvc.lua:90: in function &#039;dispatch&#039;&lt;br /&gt;
        helloworld.lua:23: in main chunk&lt;br /&gt;
        [C]: ?&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Because view_resolver is an action in the worker table, the &#039;&#039;&#039;mvc.lua&#039;&#039;&#039; runs it; but it returns a function, not a table, and causes an unhandled exception in the view.&lt;br /&gt;
&lt;br /&gt;
== Move the view resolver to the application level ==&lt;br /&gt;
&lt;br /&gt;
The solution to both problems is to move the view resolver and the view function out of the controller&#039;s worker table, into the next higher level, in this case, the application&#039;s worker table:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
1. Delete the private.view and view_resolver functions from &#039;&#039;&#039;helloworld/app/hostname-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
2. Add the following to &#039;&#039;&#039;helloworld/app/app-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
 local private = {}&lt;br /&gt;
 &lt;br /&gt;
 private.view = function ( controller, action, viewtable )&lt;br /&gt;
         io.write(string.format(&amp;quot;Controller: %s  Action: %s\n&amp;quot;,&lt;br /&gt;
                 controller or &amp;quot;&amp;quot;, action or &amp;quot;&amp;quot;))&lt;br /&gt;
         io.write (&amp;quot;Returned a table with the following values:\n&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
         for k,v in pairs(viewtable) do&lt;br /&gt;
                 io.write(string.format(&amp;quot;%s\t%s\n&amp;quot;, k, v))&lt;br /&gt;
         end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 view_resolver = function (self)&lt;br /&gt;
         return function (viewtable)&lt;br /&gt;
                 return private.view (self.conf.controller, self.conf.action, viewtable)&lt;br /&gt;
         end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This creates a more &amp;quot;generic&amp;quot; view, but one that will work for any controller - not just &#039;&#039;&#039;hostname&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Now things work as they should:&lt;br /&gt;
&lt;br /&gt;
 # lua helloworld.lua update &amp;quot;one two&amp;quot;&lt;br /&gt;
   Controller: hostname  Action: update&lt;br /&gt;
   Returned a table with the following values:&lt;br /&gt;
   value   one two&lt;br /&gt;
   type    string&lt;br /&gt;
   msg     Hostname can contain alphanumerics only&lt;br /&gt;
 &lt;br /&gt;
 # lua helloworld.lua view_resolver &amp;quot;Alpine&amp;quot;&lt;br /&gt;
   The following unhandled application error occured:&lt;br /&gt;
   &lt;br /&gt;
   controller: &amp;quot;hostname&amp;quot; does not have a &amp;quot;view_resolver&amp;quot; action.&lt;br /&gt;
&lt;br /&gt;
= mvc load &amp;amp; exec special functions =&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;mvc.lua&#039;&#039;&#039; module has a provision for executing code on module load, prior to executing the controller&#039;s action, just after executing the controller&#039;s action, and on module unload.&lt;br /&gt;
&lt;br /&gt;
This is done with the mvc table in the controller.  To demonstrate, let&#039;s add a few functions to &#039;&#039;&#039;helloworld/app/app-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
 mvc = {}&lt;br /&gt;
 mvc.on_load = function (self, parent)&lt;br /&gt;
         print (&amp;quot;This is the app controller&#039;s on_load function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 mvc.pre_exec = function (self)&lt;br /&gt;
         print (&amp;quot;This is the app controller&#039;s pre_exec function&amp;quot;)&lt;br /&gt;
 end &lt;br /&gt;
 &lt;br /&gt;
 mvc.post_exec = function (self)&lt;br /&gt;
         print (&amp;quot;This is the app controller&#039;s post_exec function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
 mvc.on_unload = function (self)&lt;br /&gt;
         print (&amp;quot;This is the app controller&#039;s on_unload function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
Now running our script shows when the functions get called:&lt;br /&gt;
&lt;br /&gt;
 # lua helloworld.lua update &amp;quot;Alpine&amp;quot;&lt;br /&gt;
   This is the app controller&#039;s on_load function&lt;br /&gt;
   This is the app controller&#039;s pre_exec function&lt;br /&gt;
   This is the app controller&#039;s post_exec function&lt;br /&gt;
   Controller: hostname  Action: update&lt;br /&gt;
   Returned a table with the following values:&lt;br /&gt;
   value   Alpine&lt;br /&gt;
   type    string&lt;br /&gt;
   This is the app controller&#039;s on_unload function&lt;br /&gt;
&lt;br /&gt;
We can add mvc functions to a specific controller, as well.  Add this to &#039;&#039;&#039;helloworld/app/hostname-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
 mvc = {}&lt;br /&gt;
 mvc.on_load = function (self, parent)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s on_load function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 mvc.pre_exec = function (self)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s pre_exec function&amp;quot;)&lt;br /&gt;
 end &lt;br /&gt;
 &lt;br /&gt;
 mvc.post_exec = function (self)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s post_exec function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 mvc.on_unload = function (self)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s on_unload function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
And this happens:&lt;br /&gt;
&lt;br /&gt;
 # lua helloworld.lua update &amp;quot;Alpine&amp;quot;&lt;br /&gt;
   This is the app controller&#039;s on_load function&lt;br /&gt;
   This is the hostname controller&#039;s on_load function&lt;br /&gt;
   This is the hostname controller&#039;s pre_exec function&lt;br /&gt;
   This is the hostname controller&#039;s post_exec function&lt;br /&gt;
   This is the hostname controller&#039;s on_unload function&lt;br /&gt;
   Controller: hostname  Action: update&lt;br /&gt;
   Returned a table with the following values:&lt;br /&gt;
   value   Alpine&lt;br /&gt;
   type    string&lt;br /&gt;
   This is the app controller&#039;s on_unload function&lt;br /&gt;
&lt;br /&gt;
Note that both the &#039;&#039;app&#039;&#039; and &#039;&#039;hostname&#039;&#039; &#039;&#039;&#039;on_load&#039;&#039;&#039; and &#039;&#039;&#039;on_unload&#039;&#039;&#039; functions were run, but only the &#039;&#039;hostname&#039;&#039; &#039;&#039;&#039;pre_exec&#039;&#039;&#039; and &#039;&#039;&#039;post_exec&#039;&#039;&#039; functions ran.  This is because the pre and post exec functions are run as part of the &amp;quot;action&amp;quot;, and the &#039;&#039;&#039;dispatch&#039;&#039;&#039; function looks in the lowest-level controller for the pre/post_exec function.  Since &#039;&#039;hostname&#039;&#039; now defines those functions, it runs them.&lt;br /&gt;
&lt;br /&gt;
To run both the &#039;&#039;hostname&#039;&#039; and &#039;&#039;app&#039;&#039; pre_exec function, you must arrange for the &#039;&#039;hostname&#039;&#039; pre_exec function to call it&#039;s parent pre_exec:&lt;br /&gt;
&lt;br /&gt;
 mvc = {}&lt;br /&gt;
 mvc.on_load = function (self, parent)&lt;br /&gt;
        print (&amp;quot;This is the hostname controller&#039;s on_load function&amp;quot;)&lt;br /&gt;
        mvc.parent_pre_exec = parent.worker.mvc.pre_exec&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 mvc.pre_exec = function (self)&lt;br /&gt;
         mvc.parent_pre_exec (self)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s pre_exec function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
[[Category:ACF]] [[Category:Lua]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=ACF_acf_www_example&amp;diff=10482</id>
		<title>ACF acf www example</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=ACF_acf_www_example&amp;diff=10482"/>
		<updated>2015-02-15T14:29:19Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Mark obsolete&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{ Obsolete }}&lt;br /&gt;
= Set the hostname with a web interface =&lt;br /&gt;
&lt;br /&gt;
In this example we will use the hostname-model.lua and hostname-controller.lua from the previous example to set the hostname using the acf web interface.&lt;br /&gt;
&lt;br /&gt;
For this example, we will assume you have root access on the linux box you are running on (preferably an alpine box!)&lt;br /&gt;
&lt;br /&gt;
== Use the existing code  ==&lt;br /&gt;
&lt;br /&gt;
We should have &#039;&#039;&#039;mvc.lua&#039;&#039; in the current directory, with the controller and model in &#039;&#039;helloworld/app&#039;&#039;  The Controller and Model look the same as the end of the mvc.lua example, except we&#039;ve taken out the on_load, pre_exec, post_exec, and on_unload methods. &lt;br /&gt;
&lt;br /&gt;
=== helloworld/app/hostname-controller.lua ===&lt;br /&gt;
 -- hostname controller code &lt;br /&gt;
 &lt;br /&gt;
 module ( ... , package.seeall )&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 create = function (self )      &lt;br /&gt;
         return self.model.update(self.clientdata.hostname)&lt;br /&gt;
 end                                   &lt;br /&gt;
    &lt;br /&gt;
 read = function (self)&lt;br /&gt;
         return self.model.read()&lt;br /&gt;
 end                             &lt;br /&gt;
 &lt;br /&gt;
 update =  create&lt;br /&gt;
    &lt;br /&gt;
 delete = function (self )&lt;br /&gt;
         self.clientdata.hostname=&amp;quot;&amp;quot;                      &lt;br /&gt;
         return self.worker:create()       &lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== helloworld/app/hostname-model.lua ===&lt;br /&gt;
 -- Model functions for retrieving / setting the hostname&lt;br /&gt;
 module ( ..., package.seeall )&lt;br /&gt;
 &lt;br /&gt;
 -- All functions return a table with&lt;br /&gt;
 -- A value, the type of the value, and a message if there was an error&lt;br /&gt;
 &lt;br /&gt;
 local hosttype={ type=&amp;quot;string&amp;quot; }&lt;br /&gt;
 &lt;br /&gt;
 update= function ( name )&lt;br /&gt;
         -- Check to make sure the name is valid &lt;br /&gt;
 &lt;br /&gt;
         if (name == nil) then&lt;br /&gt;
                 hosttype.msg = &amp;quot;Hostname cannot be nil&amp;quot;&lt;br /&gt;
         elseif (#name &amp;gt; 16) then&lt;br /&gt;
                 hosttype.msg = &amp;quot;Hostname must be less than 16 chars&amp;quot;&lt;br /&gt;
         elseif (string.find(name, &amp;quot;[^%w%_%-]&amp;quot;)) then&lt;br /&gt;
                 hosttype.msg = &amp;quot;Hostname can contain alphanumerics only&amp;quot;&lt;br /&gt;
         end&lt;br /&gt;
 &lt;br /&gt;
         -- If it is, set the hostname&lt;br /&gt;
         if (hosttype.msg == nil ) then&lt;br /&gt;
                 local f = io.open(&amp;quot;/etc/hostname&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
                 if f then&lt;br /&gt;
                         f:write(name .. &amp;quot;\n&amp;quot;)&lt;br /&gt;
                         f:close()&lt;br /&gt;
                 end&lt;br /&gt;
                 f = io.popen (&amp;quot;/bin/hostname -F /etc/hostname&amp;quot;)&lt;br /&gt;
                 f:close()&lt;br /&gt;
                 return read()&lt;br /&gt;
         -- Otherwise, return the error message&lt;br /&gt;
         else&lt;br /&gt;
                 hosttype.value = name&lt;br /&gt;
                 return hosttype&lt;br /&gt;
         end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 read= function ()&lt;br /&gt;
         local f = io.popen (&amp;quot;/bin/hostname&amp;quot;)&lt;br /&gt;
         local n = f:read(&amp;quot;*a&amp;quot;) or &amp;quot;none&amp;quot;&lt;br /&gt;
         f:close()&lt;br /&gt;
         n=string.gsub(n, &amp;quot;\n$&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
         hosttype.value = n&lt;br /&gt;
         return (hosttype) &lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
== Get and configure ACF ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
1. Grab a copy of all the acf code and support libraries from svn and copy into an &amp;quot;/usr/share/acf&amp;quot; directory&lt;br /&gt;
&lt;br /&gt;
 svn export svn://svn.alpinelinux.org/acf/core/trunk /usr/share/acf&lt;br /&gt;
&lt;br /&gt;
2. Copy the acf.conf file to /etc/acf&lt;br /&gt;
&lt;br /&gt;
 cp /usr/share/acf/acf.conf /etc/acf/acf.conf&lt;br /&gt;
&lt;br /&gt;
3. Start the web server &lt;br /&gt;
&lt;br /&gt;
You&#039;ll need haserl, lua, and mini_httpd installed to run acf.  If not already done, please install them.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Start mini_httpd (by hand for now)&lt;br /&gt;
&lt;br /&gt;
  mini_httpd -d /usr/share/acf/www -c &#039;cgi-bin/**&#039; start&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
4. Move your hostname model and controller&lt;br /&gt;
&lt;br /&gt;
  mkdir /usr/share/acf/app/sample&lt;br /&gt;
  mv helloworld/app/hostname-* /usr/share/acf/app/sample/&lt;br /&gt;
&lt;br /&gt;
5. Create a new view template, which is a &amp;quot;lua server page&amp;quot;  Create the file as &#039;&#039;/usr/share/acf/app/sample/hostname-html.lsp&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;? local form = ... ?&amp;gt;                                                                          &lt;br /&gt;
 &amp;amp;lt;h1&amp;amp;gt;Hostname&amp;amp;lt;/h1&amp;amp;gt;&lt;br /&gt;
 &lt;br /&gt;
 &amp;amp;lt;form action=&amp;quot;update&amp;quot; method=post&amp;amp;gt;                                                          &lt;br /&gt;
 &amp;amp;lt;p&amp;amp;gt;The Hostname is now &amp;amp;lt;input name=hostname value=&amp;quot;&amp;amp;lt;?= form.value ?&amp;amp;gt;&amp;quot;&amp;amp;lt;/p&amp;amp;gt;&lt;br /&gt;
                                                                                             &lt;br /&gt;
 &amp;amp;lt;p&amp;amp;gt;input type=submit value=Submit&amp;amp;gt;&amp;amp;lt;/p&amp;amp;gt;&lt;br /&gt;
 &amp;amp;lt;/form&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
6. Create a new roles file, which gives all users permission to access your actions. Create the file as &#039;&#039;/usr/share/acf/app/sample/hostname.roles&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
 GUEST=hostname/read,hostname/update&lt;br /&gt;
&lt;br /&gt;
== Try the app ==&lt;br /&gt;
&lt;br /&gt;
point your browser to your host/cgi-bin/acf/sample/hostname/read&lt;br /&gt;
&lt;br /&gt;
You should now be able to update the hostname using the web interface, and your &#039;&#039;existing&#039;&#039; model and controller.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:ACF]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=Generating_SSL_certs_with_ACF&amp;diff=10407</id>
		<title>Generating SSL certs with ACF</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=Generating_SSL_certs_with_ACF&amp;diff=10407"/>
		<updated>2015-01-30T20:32:42Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Add examples of certificates for mini_httpd and openvpn&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;You need to create certificates for servers or remote persons. You might need an SSL cert for your web server running lighttpd or mini_httpd. You might use something like openvpn or racoon for your VPN services. Wouldn&#039;t it be nice to have some way to manage and view all the certs you have given to everyone? Revoke the certs? Review the certificate before you issue it?&lt;br /&gt;
Alpine, via ACF, has a nice web interface to use for this sort of job...&lt;br /&gt;
&lt;br /&gt;
==Installation Process==&lt;br /&gt;
This will somewhat guide you through the process of creating this type of server. It is suggested to not host this on your VPN gateway, but use another machine to generate your certificates. &lt;br /&gt;
&lt;br /&gt;
===Install Alpine ===&lt;br /&gt;
Link below to the standard document.&lt;br /&gt;
&lt;br /&gt;
[[Installing_Alpine]]&lt;br /&gt;
&lt;br /&gt;
=== Install and Configure ACF ===&lt;br /&gt;
Run the following command:&lt;br /&gt;
This will install the web front end to Alpine Linux, called ACF.&lt;br /&gt;
&lt;br /&gt;
{{Cmd|/sbin/setup-acf}}&lt;br /&gt;
&lt;br /&gt;
Install acf-openssl&lt;br /&gt;
&lt;br /&gt;
{{Cmd|apk add acf-openssl}}&lt;br /&gt;
&lt;br /&gt;
Browse to your computer https://ipaddr/&lt;br /&gt;
&lt;br /&gt;
Login as root.&lt;br /&gt;
&lt;br /&gt;
Click on the User Management tab and create yourself an account.&lt;br /&gt;
&lt;br /&gt;
=== Acf-openssl ===&lt;br /&gt;
&lt;br /&gt;
From the navigation bar on the left, under the Applications section, click the Certificate Authority link.&lt;br /&gt;
&lt;br /&gt;
If you already have a CA that you would like to have the web interface manage you can upload it from the Status page (as a pfx).&lt;br /&gt;
&lt;br /&gt;
From the Status tab, Click Configure(to remove most of the error messages).&lt;br /&gt;
&lt;br /&gt;
If you do not have a CA, To generate a new CA certificate:&lt;br /&gt;
Click the Edit Defaults tab. Input the Items that will be needed for the CA and any other certs generated from it then Click Save. &lt;br /&gt;
Click the Status tab. Input values for the input boxes to generate a CA and click Generate.&lt;br /&gt;
&lt;br /&gt;
== Generate a certificate with ACF ==&lt;br /&gt;
=== Request Form ===&lt;br /&gt;
Provided Fields:&lt;br /&gt;
* Country Name (2 letter abbreviation)&lt;br /&gt;
* Locality Name (e.g. city)&lt;br /&gt;
* Organization Name&lt;br /&gt;
* Common Name (eg, the certificate CN)&lt;br /&gt;
* Email Address&lt;br /&gt;
* Multiple Organizational Unit Name (eg, division)&lt;br /&gt;
* Certificate Type&lt;br /&gt;
&lt;br /&gt;
A box has been set aside for adding Additional x509 Extensions formatted the same as if you were to fill out a section directly in openssl.cnf. Section would be &lt;br /&gt;
&amp;lt;tt&amp;gt;[v3_req]&amp;lt;/tt&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You could put in here:&lt;br /&gt;
* subjectAltName =&amp;quot;IP:192.168.1.1&amp;quot;&lt;br /&gt;
* subjectAltName =&amp;quot;DNS:192.168.1.10&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Here is also where you would specify the CRL / OCSP distribution point, from where clients can query information:&lt;br /&gt;
* crlDistributionPoints=URI:http://whatever.com/whatever.crl&lt;br /&gt;
&lt;br /&gt;
Once this form has been filled out and the password entered click submit.&lt;br /&gt;
&lt;br /&gt;
=== View ===&lt;br /&gt;
Go to the View tab after you have the request form submitted. The view tab will show you pending requests for certificates. Also available from this tab are already approved requests (generated certs), revoked certs, and the CRL.&lt;br /&gt;
&lt;br /&gt;
For a Pending request, make sure to review the cert before approving it. Once you have verified that all the information is correct, with no mis-types or spelling mistakes, Approve the request. &lt;br /&gt;
&lt;br /&gt;
The file that will be generated can be downloaded from the ACF. Use the command lines below to extract the pkcs12 file into its part to begin using it.&lt;br /&gt;
&lt;br /&gt;
=== Extract PFX certificate ===&lt;br /&gt;
To get the CA CERT&lt;br /&gt;
&lt;br /&gt;
{{Cmd|openssl pkcs12 -in PFXFILE -cacerts -nokeys -out cacert.pem}}&lt;br /&gt;
 &lt;br /&gt;
To get the Private Key&lt;br /&gt;
&lt;br /&gt;
{{Cmd|openssl pkcs12 -in PFXFILE -nocerts -nodes -out mykey.pem}}&lt;br /&gt;
Since this file contains the key without passsword protection, make sure to set restrictive permissions on this file.&lt;br /&gt;
&lt;br /&gt;
To get the Certificate&lt;br /&gt;
&lt;br /&gt;
{{Cmd|openssl pkcs12 -in PFXFILE -nokeys -clcerts -out mycert.pem}}&lt;br /&gt;
&lt;br /&gt;
To get the Certificate and Private key in a single file (For lighttpd or mini_httpd for instance)&lt;br /&gt;
&lt;br /&gt;
{{Cmd|openssl pkcs12 -in PFXFILE -nodes -out server.pem}}&lt;br /&gt;
Since this file contains the key without passsword protection, make sure to set restrictive permissions on this file.&lt;br /&gt;
&lt;br /&gt;
To get the CA Chain (For lighttpd for instance)&lt;br /&gt;
&lt;br /&gt;
{{Cmd|openssl pkcs12 -in PFXFILE -nokeys -cacerts -chain -out ca-certs.pem}}&lt;br /&gt;
&lt;br /&gt;
Display the cert or key readable/text format&lt;br /&gt;
&lt;br /&gt;
{{Cmd|openssl x509 -in mycert.pem -noout -text}}&lt;br /&gt;
&lt;br /&gt;
==Examples==&lt;br /&gt;
===Replacing the ACF SSL cert===&lt;br /&gt;
By default, setup-acf uses mini_httpd with a self-signed certificate for serving ACF webpages. We can replace the self-signed certificate with one signed by our new CA.&lt;br /&gt;
&lt;br /&gt;
Create a certificate of type &#039;ssl_server_cert&#039; with appropriate settings (i.e. Common Name = server name)&lt;br /&gt;
&lt;br /&gt;
Download the certificate pfx and upload it to the ACF server (remember, this is generally separate from the standalone Certificate Authority server)&lt;br /&gt;
&lt;br /&gt;
Replace the mini_httpd server certificate&lt;br /&gt;
{{Cmd|openssl pkcs12 -in PFXFILE -nodes -out /etc/ssl/mini_httpd/server.pem}}&lt;br /&gt;
&lt;br /&gt;
Restart mini_httpd&lt;br /&gt;
{{Cmd|/etc/init.d/mini_httpd restart}}&lt;br /&gt;
&lt;br /&gt;
===Generating server and client certs for OpenVPN===&lt;br /&gt;
For OpenVPN use, we need a server certificate and one client certificate for each user. ACF can be used to generate all of them, including allowing users to request their own client certificates.&lt;br /&gt;
&lt;br /&gt;
Generate a certificate of type &#039;ssl_server_cert&#039; with appropriate settings for the OpenVPN server.&lt;br /&gt;
&lt;br /&gt;
Copy the server certificate pfx to the OpenVPN server and extract the certificate using the commands above. Configuration of the OpenVPN server is beyond the scope here.&lt;br /&gt;
&lt;br /&gt;
Create an ACF user account on the Certificate Authority server for each OpenVPN user. From the navigation bar, click on User Management under System. Click on Create. Create a user with CERT_REQUESTER role for each user. You could set the user Home to /openssl/openssl/read to default to showing that user&#039;s certificates.&lt;br /&gt;
&lt;br /&gt;
Each user can request his own client certificate. Log in as the new user. Create a certificate request for a certificate of type &#039;ssl_client_cert&#039; with appropriate settings.&lt;br /&gt;
&lt;br /&gt;
You can view and approve the requested certificates as described above.&lt;br /&gt;
&lt;br /&gt;
The user can then download and install the client certificate pfx on his OpenVPN client. Once again, this is beyond the scope of this document.&lt;br /&gt;
&lt;br /&gt;
==Extras==&lt;br /&gt;
===OpenSSL command line to create your CA ===&lt;br /&gt;
The following command will need a password. Make sure to remember this.&lt;br /&gt;
&lt;br /&gt;
{{Cmd|openssl genrsa -des3 -out server.key 2048}}&lt;br /&gt;
&lt;br /&gt;
{{Cmd|openssl req -new -key server.key -out server.csr}}&lt;br /&gt;
&lt;br /&gt;
{{Cmd|openssl rsa -in server.key. -out server.pem}}&lt;br /&gt;
&lt;br /&gt;
{{Cmd|openssl x509 -req -days 365 -in server.csr -signkey server.pem -out cacert.pem}}&lt;br /&gt;
&lt;br /&gt;
{{Cmd|mv server.pem /etc/ssl/private; mv cacert.pem /etc/ssl/}}&lt;br /&gt;
&lt;br /&gt;
===Edits to /etc/ssl/openssl-ca-acf.cnf ===&lt;br /&gt;
Via the expert tab on ACF edit the openssl-ca-acf.cnf file. Something like subjectAltName can be added to be used by the certificates that you generate.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tt&amp;gt;3.subjectAltName        = Assigned IP Address &amp;lt;/tt&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tt&amp;gt;3.subjectAltName_default = 192.168.1.1/32&amp;lt;/tt&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:Networking]]&lt;br /&gt;
[[Category:ACF]]&lt;br /&gt;
[[Category:Security]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=Generating_SSL_certs_with_ACF&amp;diff=10406</id>
		<title>Generating SSL certs with ACF</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=Generating_SSL_certs_with_ACF&amp;diff=10406"/>
		<updated>2015-01-30T19:59:33Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Cleanup / reorg&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;You need to create certificates for servers or remote persons. You might need an SSL cert for your web server running lighttpd or mini_httpd. You might use something like openvpn or racoon for your VPN services. Wouldn&#039;t it be nice to have some way to manage and view all the certs you have given to everyone? Revoke the certs? Review the certificate before you issue it?&lt;br /&gt;
Alpine, via ACF, has a nice web interface to use for this sort of job...&lt;br /&gt;
&lt;br /&gt;
==Installation Process==&lt;br /&gt;
This will somewhat guide you through the process of creating this type of server. It is suggested to not host this on your VPN gateway, but use another machine to generate your certificates. &lt;br /&gt;
&lt;br /&gt;
===Install Alpine ===&lt;br /&gt;
Link below to the standard document.&lt;br /&gt;
&lt;br /&gt;
[[Installing_Alpine]]&lt;br /&gt;
&lt;br /&gt;
=== Install and Configure ACF ===&lt;br /&gt;
Run the following command:&lt;br /&gt;
This will install the web front end to Alpine Linux, called ACF.&lt;br /&gt;
&lt;br /&gt;
{{Cmd|/sbin/setup-acf}}&lt;br /&gt;
&lt;br /&gt;
Install acf-openssl&lt;br /&gt;
&lt;br /&gt;
{{Cmd|apk add acf-openssl}}&lt;br /&gt;
&lt;br /&gt;
Browse to your computer https://ipaddr/&lt;br /&gt;
&lt;br /&gt;
Login as root.&lt;br /&gt;
&lt;br /&gt;
Click on the User Management tab and create yourself an account.&lt;br /&gt;
&lt;br /&gt;
=== Acf-openssl ===&lt;br /&gt;
&lt;br /&gt;
From the navigation bar on the left, under the Applications section, click the Certificate Authority link.&lt;br /&gt;
&lt;br /&gt;
If you already have a CA that you would like to have the web interface manage you can upload it from the Status page (as a pfx).&lt;br /&gt;
&lt;br /&gt;
From the Status tab, Click Configure(to remove most of the error messages).&lt;br /&gt;
&lt;br /&gt;
If you do not have a CA, To generate a new CA certificate:&lt;br /&gt;
Click the Edit Defaults tab. Input the Items that will be needed for the CA and any other certs generated from it then Click Save. &lt;br /&gt;
Click the Status tab. Input values for the input boxes to generate a CA and click Generate.&lt;br /&gt;
&lt;br /&gt;
== Generate a certificate with ACF ==&lt;br /&gt;
=== Request Form ===&lt;br /&gt;
Provided Fields:&lt;br /&gt;
* Country Name (2 letter abbreviation)&lt;br /&gt;
* Locality Name (e.g. city)&lt;br /&gt;
* Organization Name&lt;br /&gt;
* Common Name (eg, the certificate CN)&lt;br /&gt;
* Email Address&lt;br /&gt;
* Multiple Organizational Unit Name (eg, division)&lt;br /&gt;
* Certificate Type&lt;br /&gt;
&lt;br /&gt;
A box has been set aside for adding Additional x509 Extensions formatted the same as if you were to fill out a section directly in openssl.cnf. Section would be &lt;br /&gt;
&amp;lt;tt&amp;gt;[v3_req]&amp;lt;/tt&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You could put in here:&lt;br /&gt;
* subjectAltName =&amp;quot;IP:192.168.1.1&amp;quot;&lt;br /&gt;
* subjectAltName =&amp;quot;DNS:192.168.1.10&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Here is also where you would specify the CRL / OCSP distribution point, from where clients can query information:&lt;br /&gt;
* crlDistributionPoints=URI:http://whatever.com/whatever.crl&lt;br /&gt;
&lt;br /&gt;
Once this form has been filled out and the password entered click submit.&lt;br /&gt;
&lt;br /&gt;
=== View ===&lt;br /&gt;
Go to the View tab after you have the request form submitted. The view tab will show you pending requests for certificates. Also available from this tab are already approved requests (generated certs), revoked certs, and the CRL.&lt;br /&gt;
&lt;br /&gt;
For a Pending request, make sure to review the cert before approving it. Once you have verified that all the information is correct, with no mis-types or spelling mistakes, Approve the request. &lt;br /&gt;
&lt;br /&gt;
The file that will be generated can be downloaded from the ACF. Use the command lines below to extract the pkcs12 file into its part to begin using it.&lt;br /&gt;
&lt;br /&gt;
=== Extract PFX certificate ===&lt;br /&gt;
To get the CA CERT&lt;br /&gt;
&lt;br /&gt;
{{Cmd|openssl pkcs12 -in PFXFILE -cacerts -nokeys -out cacert.pem}}&lt;br /&gt;
 &lt;br /&gt;
To get the Private Key&lt;br /&gt;
&lt;br /&gt;
{{Cmd|openssl pkcs12 -in PFXFILE -nocerts -nodes -out mykey.pem}}&lt;br /&gt;
Since this file contains the key without passsword protection, make sure to set restrictive permissions on this file.&lt;br /&gt;
&lt;br /&gt;
To get the Certificate&lt;br /&gt;
&lt;br /&gt;
{{Cmd|openssl pkcs12 -in PFXFILE -nokeys -clcerts -out mycert.pem}}&lt;br /&gt;
&lt;br /&gt;
To get the Certificate and Private key in a single file (For lighttpd or mini_httpd for instance)&lt;br /&gt;
&lt;br /&gt;
{{Cmd|openssl pkcs12 -in PFXFILE -nodes -out server.pem}}&lt;br /&gt;
Since this file contains the key without passsword protection, make sure to set restrictive permissions on this file.&lt;br /&gt;
&lt;br /&gt;
To get the CA Chain (For lighttpd for instance)&lt;br /&gt;
&lt;br /&gt;
{{Cmd|openssl pkcs12 -in PFXFILE -nokeys -cacerts -chain -out ca-certs.pem}}&lt;br /&gt;
&lt;br /&gt;
Display the cert or key readable/text format&lt;br /&gt;
&lt;br /&gt;
{{Cmd|openssl x509 -in mycert.pem -noout -text}}&lt;br /&gt;
&lt;br /&gt;
==Extras==&lt;br /&gt;
===OpenSSL command line to create your CA ===&lt;br /&gt;
The following command will need a password. Make sure to remember this.&lt;br /&gt;
&lt;br /&gt;
{{Cmd|openssl genrsa -des3 -out server.key 2048}}&lt;br /&gt;
&lt;br /&gt;
{{Cmd|openssl req -new -key server.key -out server.csr}}&lt;br /&gt;
&lt;br /&gt;
{{Cmd|openssl rsa -in server.key. -out server.pem}}&lt;br /&gt;
&lt;br /&gt;
{{Cmd|openssl x509 -req -days 365 -in server.csr -signkey server.pem -out cacert.pem}}&lt;br /&gt;
&lt;br /&gt;
{{Cmd|mv server.pem /etc/ssl/private; mv cacert.pem /etc/ssl/}}&lt;br /&gt;
&lt;br /&gt;
===Edits to /etc/ssl/openssl-ca-acf.cnf ===&lt;br /&gt;
Via the expert tab on ACF edit the openssl-ca-acf.cnf file. Something like subjectAltName can be added to be used by the certificates that you generate.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tt&amp;gt;3.subjectAltName        = Assigned IP Address &amp;lt;/tt&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tt&amp;gt;3.subjectAltName_default = 192.168.1.1/32&amp;lt;/tt&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:Networking]]&lt;br /&gt;
[[Category:ACF]]&lt;br /&gt;
[[Category:Security]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=Local_APK_cache&amp;diff=10077</id>
		<title>Local APK cache</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=Local_APK_cache&amp;diff=10077"/>
		<updated>2014-07-02T13:19:14Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Removed reference to yum and apt-get&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Alpine Linux needs to be able to pull packages from local media on boot. (You can&#039;t download packages from the net before you have a network connection.)  Using remote repositories presents a problem.  If the config files have been modified for a newer version of a package, and the older package is on local media, all sorts of fun can result.&lt;br /&gt;
&lt;br /&gt;
The solution is a local cache of updated packages.   This cache can be stored on any r/w media, typically the same location as the apkovl.&lt;br /&gt;
&lt;br /&gt;
The cache is enabled by creating a symlink named &#039;&#039;/etc/apk/cache&#039;&#039; that points to the cache directory.&lt;br /&gt;
&lt;br /&gt;
To enable local cache run: {{Cmd|setup-apkcache}}&lt;br /&gt;
&lt;br /&gt;
= To enable Local Cache on releases prior v2.3 =&lt;br /&gt;
Alpine Linux version prior to v2.3 does not have the &#039;&#039;setup-apkcache&#039;&#039; tool so the symlink needs to be set up manually.&lt;br /&gt;
&lt;br /&gt;
== To manually enable Local Cache on HDD install ==&lt;br /&gt;
If you&#039;ve installed Alpine to your hard drive (as &#039;sys&#039;), then create a cache dir and then an &#039;&#039;/etc/apk/cache&#039;&#039; symlink pointing to that dir:&lt;br /&gt;
{{Cmd|mkdir -p /var/cache/apk&lt;br /&gt;
ln -s /var/cache/apk /etc/apk/cache}}&lt;br /&gt;
&lt;br /&gt;
You normally don&#039;t need apk cache on HDD &#039;sys&#039; installs but it might be handy if you re-install from net to have the packages cached.&lt;br /&gt;
&lt;br /&gt;
== To manually enable Local Cache on run-from-RAM installs ==&lt;br /&gt;
&lt;br /&gt;
# Create a &#039;&#039;&#039;cache&#039;&#039;&#039; directory on the device you store your lbu backups (typically, &amp;lt;code&amp;gt;/dev/sda1&amp;lt;/code&amp;gt;.)  {{Cmd| mkdir /media/sda1/cache }}&lt;br /&gt;
{{Tip|If you get an error that says &amp;quot;mkdir: can&#039;t create directory &#039;/media/usbdisk/cache&#039;: Read-only file system&amp;quot;, then you probably need to remount your disk read-write temporarily.  Try {{Cmd|mount -o remount,rw /media/sda1}} and then don&#039;t forget to run {{Cmd|mount -o remount,ro /media/sda1}} when you are done with the following commands}}&lt;br /&gt;
# Create a symlink to this directory from &amp;lt;code&amp;gt;/etc/apk/cache&amp;lt;/code&amp;gt;.  {{Cmd|ln -s /media/sda1/cache /etc/apk/cache}}&lt;br /&gt;
# Run an lbu commit to save the change (&amp;lt;code&amp;gt;/etc/apk/cache&amp;lt;/code&amp;gt; is in &amp;lt;code&amp;gt;/etc&amp;lt;/code&amp;gt; and is automatically backed up.) {{Cmd|lbu commit}}&lt;br /&gt;
# Done.  Now whenever you run an apk command that pulls a new package from a remote repository, the package is stored on your local media.  On startup, Alpine Linux will check the local cache for new packages, and will install them if available.&lt;br /&gt;
&lt;br /&gt;
= Cache maintenance =&lt;br /&gt;
Over time, newer packages will replace older ones; the cache directory will contain all older versions of packages.  &lt;br /&gt;
&lt;br /&gt;
== Delete old packages ==&lt;br /&gt;
To clean out older versions of packages, run the &#039;&#039;&#039;clean&#039;&#039;&#039; command.  {{cmd|apk cache clean}} or to see what is deleted {{cmd|apk -v cache clean}}&lt;br /&gt;
&lt;br /&gt;
== Download missing packages ==&lt;br /&gt;
If you accidentally delete packages from the cache directory, you can make sure they are there with the &#039;&#039;&#039;download&#039;&#039;&#039; command, {{cmd|apk cache download}}&lt;br /&gt;
&lt;br /&gt;
== Delete and download in one step ==&lt;br /&gt;
You can combine the two steps into one with the &#039;&#039;&#039;sync&#039;&#039;&#039; command - this cleans out old packages and downloads missing packages. {{cmd|apk cache -v sync}}&lt;br /&gt;
&lt;br /&gt;
== Automatically Cleaning Cache on Reboot ==&lt;br /&gt;
To automatically attempt to validate your cache on reboot, you can add the above command to a {{Path|/etc/local.d/*.stop}} file:&lt;br /&gt;
&lt;br /&gt;
{{Cat|/etc/local.d/cache.stop|#!/bin/sh&lt;br /&gt;
# verify the local cache on shutdown&lt;br /&gt;
apk cache -v sync&lt;br /&gt;
&lt;br /&gt;
# We should always return 0&lt;br /&gt;
return 0&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Tip|Usually the only time you need to reboot is when things have gone horribly wrong; so this is a &amp;quot;best effort&amp;quot; to cover forgetting to sync the cache; It is much better to run &#039;&#039;&#039;sync&#039;&#039;&#039; immediately after adding or upgrading packages.}}&lt;br /&gt;
&lt;br /&gt;
{{Note|Custom shutdown commands were formerly added to a {{Path|/etc/conf.d/local}}; but that method is now deprecated.}}&lt;br /&gt;
&lt;br /&gt;
[[Category:Package Manager]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
&lt;br /&gt;
== Enabling Package Caching (Older) ==&lt;br /&gt;
&lt;br /&gt;
{{Note|This document applies to Alpine 1.9 and later versions only}}&lt;br /&gt;
&lt;br /&gt;
Package caching is useful when the need arises to upgrade packages on read-only media. Package caching allows you to store newer packages in a location on writable media, which Alpine checks for when loading packages on start-up.&lt;br /&gt;
&lt;br /&gt;
This can be enabled either from the console or through the ACF.&lt;br /&gt;
&lt;br /&gt;
{{Tip|For package upgrades, it is useful to point Alpine Package Manager to repositories on the internet, to easily obtain package upgrades when they become available.}}&lt;br /&gt;
&lt;br /&gt;
{{:Include:Using_Internet_Repositories_for_apk-tools}}&lt;br /&gt;
&lt;br /&gt;
== Enable caching from either console or ACF ==&lt;br /&gt;
&lt;br /&gt;
Choose one of the two methods below to enable, either from the console or from the ACF web interface.&lt;br /&gt;
&lt;br /&gt;
=== Option 1: Enable caching from the console ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Substitute &#039;sda1&#039; in the example below for the actual device you will use to store the caching directory.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
First upgrade apk-tools:&lt;br /&gt;
{{Cmd|apk add –u apk-tools}}&lt;br /&gt;
&lt;br /&gt;
Now enable package caching:&lt;br /&gt;
{{Cmd|mkdir –p /media/sda1/cache&lt;br /&gt;
ln –s /media/sda1/cache /etc/apk/cache}}&lt;br /&gt;
&lt;br /&gt;
=== Option 2: From the ACF ===&lt;br /&gt;
&lt;br /&gt;
Browse to &#039;&#039;&#039;System &amp;gt; Packages &amp;gt; Cache&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Edit Cache Settings:&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Tick &#039;&#039;&#039;Enable Cache&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Specify the &#039;&#039;&#039;Cache Directory&#039;&#039;&#039;, for example:&lt;br /&gt;
 /media/sda1/cache&lt;br /&gt;
&lt;br /&gt;
Click &#039;&#039;&#039;Save&#039;&#039;&#039;&lt;br /&gt;
--&amp;gt;&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=Alpine_ACF_Skins&amp;diff=9839</id>
		<title>Alpine ACF Skins</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=Alpine_ACF_Skins&amp;diff=9839"/>
		<updated>2014-02-04T21:10:50Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Ttrask moved page Alpine Skins to Alpine ACF Skins without leaving a redirect: Should have ACF in the title&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= ACF Skins =&lt;br /&gt;
&lt;br /&gt;
ACF has support for multiple skins.&amp;lt;br&amp;gt;Only a few skins are available. Feel free to contribute by programming css-stylesheets for ACF. &lt;br /&gt;
&lt;br /&gt;
== Directory Structure ==&lt;br /&gt;
&lt;br /&gt;
Sample skins are located in the /usr/share/acf/www/skins/ directory. User-modifiable skins can be added to the /etc/acf/skins/ directory. Each skin is located in its own subdirectory and contains one or more of the following files:&lt;br /&gt;
* &#039;skin&#039;.css - the main CSS file for the skin&lt;br /&gt;
* &#039;skin&#039;-ie.css - Internet Explorer-specific CSS file (loaded in addition to &#039;skin&#039;.css)&lt;br /&gt;
* &#039;skin&#039;.js - javascript file for the skin&lt;br /&gt;
&lt;br /&gt;
=== Images ===&lt;br /&gt;
&lt;br /&gt;
It is common for skins to include image files. Skin-specific image files should be located in the skin subdirectory. Please see the sample skins for how to include images.&lt;br /&gt;
&lt;br /&gt;
The acf-skin package also includes a variety of [http://tango.freedesktop.org/Tango_Icon_Library tango] images located in the static/tango directory.&lt;br /&gt;
&lt;br /&gt;
=== Templates ===&lt;br /&gt;
&lt;br /&gt;
As of acf-core-0.15.1, skins can also include template files. The acf_www-controller will look in the following locations for skin-specific templates before searching /usr/share/acf:&lt;br /&gt;
* &#039;skindirectory&#039;/template-&#039;controller&#039;-&#039;action&#039;-&#039;viewtype&#039;.lsp&lt;br /&gt;
* &#039;skindirectory&#039;/template-&#039;controller&#039;-&#039;viewtype&#039;.lsp&lt;br /&gt;
* &#039;skindirectory&#039;/template-&#039;action&#039;-&#039;viewtype&#039;.lsp&lt;br /&gt;
* &#039;skindirectory&#039;/template-&#039;viewtype&#039;.lsp&lt;br /&gt;
&lt;br /&gt;
For example, the acf_www-controller will check the following locations for action /acf-util/skins/read with viewtype &#039;html&#039; while using skin &#039;wik&#039;:&lt;br /&gt;
* /usr/share/acf/www/skins/wik/template-skins-read-html.lsp&lt;br /&gt;
* /usr/share/acf/www/skins/wik/template-skins-html.lsp&lt;br /&gt;
* /usr/share/acf/www/skins/wik/template-read-html.lsp&lt;br /&gt;
* /usr/share/acf/www/skins/wik/template-html.lsp&lt;br /&gt;
&lt;br /&gt;
== Sample Skins ==&lt;br /&gt;
&lt;br /&gt;
Some example skins are available: &lt;br /&gt;
&lt;br /&gt;
*/usr/share/acf/www/skins/alps/ &lt;br /&gt;
*/usr/share/acf/www/skins/cloud/ &lt;br /&gt;
*/usr/share/acf/www/skins/ice/ &lt;br /&gt;
*/usr/share/acf/www/skins/snow/&lt;br /&gt;
*/usr/share/acf/www/skins/wik/ &lt;br /&gt;
&lt;br /&gt;
== How To Contribute  ==&lt;br /&gt;
&lt;br /&gt;
Make a new skin folder. &lt;br /&gt;
&lt;br /&gt;
 mkdir -p /etc/acf/skins/myskin&lt;br /&gt;
&lt;br /&gt;
Create a css file called as the folder. &lt;br /&gt;
&lt;br /&gt;
 touch /etc/acf/skins/myskin/myskin.css&lt;br /&gt;
&lt;br /&gt;
Now you can start editing your myskin.css.&amp;lt;br&amp;gt;If you have ACF running on the computer, you can browse to https://&amp;amp;lt;hostname&amp;amp;gt;/cgi-bin/acf/acf-util/skins/read and switch to your new skin (called myskin) and see the results of your changes. &lt;br /&gt;
&lt;br /&gt;
Pack your myskin folder, containing your css file (and images, if there are any).&amp;lt;br&amp;gt;Send this patch to acf@lists.alpinelinux.org &#039;&#039;(&#039;&#039;&#039;Note:&#039;&#039;&#039; Don&#039;t forget to [http://wiki.alpinelinux.org/w/index.php?title=Mailing_lists subscribe] before sending your patch)&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[Category:ACF]] [[Category:Lua]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=Alpine_ACF_Skins&amp;diff=9838</id>
		<title>Alpine ACF Skins</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=Alpine_ACF_Skins&amp;diff=9838"/>
		<updated>2014-02-04T21:10:03Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Initial cut with some content stolen from Alpine_Configuration_Framework_Design&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= ACF Skins =&lt;br /&gt;
&lt;br /&gt;
ACF has support for multiple skins.&amp;lt;br&amp;gt;Only a few skins are available. Feel free to contribute by programming css-stylesheets for ACF. &lt;br /&gt;
&lt;br /&gt;
== Directory Structure ==&lt;br /&gt;
&lt;br /&gt;
Sample skins are located in the /usr/share/acf/www/skins/ directory. User-modifiable skins can be added to the /etc/acf/skins/ directory. Each skin is located in its own subdirectory and contains one or more of the following files:&lt;br /&gt;
* &#039;skin&#039;.css - the main CSS file for the skin&lt;br /&gt;
* &#039;skin&#039;-ie.css - Internet Explorer-specific CSS file (loaded in addition to &#039;skin&#039;.css)&lt;br /&gt;
* &#039;skin&#039;.js - javascript file for the skin&lt;br /&gt;
&lt;br /&gt;
=== Images ===&lt;br /&gt;
&lt;br /&gt;
It is common for skins to include image files. Skin-specific image files should be located in the skin subdirectory. Please see the sample skins for how to include images.&lt;br /&gt;
&lt;br /&gt;
The acf-skin package also includes a variety of [http://tango.freedesktop.org/Tango_Icon_Library tango] images located in the static/tango directory.&lt;br /&gt;
&lt;br /&gt;
=== Templates ===&lt;br /&gt;
&lt;br /&gt;
As of acf-core-0.15.1, skins can also include template files. The acf_www-controller will look in the following locations for skin-specific templates before searching /usr/share/acf:&lt;br /&gt;
* &#039;skindirectory&#039;/template-&#039;controller&#039;-&#039;action&#039;-&#039;viewtype&#039;.lsp&lt;br /&gt;
* &#039;skindirectory&#039;/template-&#039;controller&#039;-&#039;viewtype&#039;.lsp&lt;br /&gt;
* &#039;skindirectory&#039;/template-&#039;action&#039;-&#039;viewtype&#039;.lsp&lt;br /&gt;
* &#039;skindirectory&#039;/template-&#039;viewtype&#039;.lsp&lt;br /&gt;
&lt;br /&gt;
For example, the acf_www-controller will check the following locations for action /acf-util/skins/read with viewtype &#039;html&#039; while using skin &#039;wik&#039;:&lt;br /&gt;
* /usr/share/acf/www/skins/wik/template-skins-read-html.lsp&lt;br /&gt;
* /usr/share/acf/www/skins/wik/template-skins-html.lsp&lt;br /&gt;
* /usr/share/acf/www/skins/wik/template-read-html.lsp&lt;br /&gt;
* /usr/share/acf/www/skins/wik/template-html.lsp&lt;br /&gt;
&lt;br /&gt;
== Sample Skins ==&lt;br /&gt;
&lt;br /&gt;
Some example skins are available: &lt;br /&gt;
&lt;br /&gt;
*/usr/share/acf/www/skins/alps/ &lt;br /&gt;
*/usr/share/acf/www/skins/cloud/ &lt;br /&gt;
*/usr/share/acf/www/skins/ice/ &lt;br /&gt;
*/usr/share/acf/www/skins/snow/&lt;br /&gt;
*/usr/share/acf/www/skins/wik/ &lt;br /&gt;
&lt;br /&gt;
== How To Contribute  ==&lt;br /&gt;
&lt;br /&gt;
Make a new skin folder. &lt;br /&gt;
&lt;br /&gt;
 mkdir -p /etc/acf/skins/myskin&lt;br /&gt;
&lt;br /&gt;
Create a css file called as the folder. &lt;br /&gt;
&lt;br /&gt;
 touch /etc/acf/skins/myskin/myskin.css&lt;br /&gt;
&lt;br /&gt;
Now you can start editing your myskin.css.&amp;lt;br&amp;gt;If you have ACF running on the computer, you can browse to https://&amp;amp;lt;hostname&amp;amp;gt;/cgi-bin/acf/acf-util/skins/read and switch to your new skin (called myskin) and see the results of your changes. &lt;br /&gt;
&lt;br /&gt;
Pack your myskin folder, containing your css file (and images, if there are any).&amp;lt;br&amp;gt;Send this patch to acf@lists.alpinelinux.org &#039;&#039;(&#039;&#039;&#039;Note:&#039;&#039;&#039; Don&#039;t forget to [http://wiki.alpinelinux.org/w/index.php?title=Mailing_lists subscribe] before sending your patch)&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[Category:ACF]] [[Category:Lua]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=ACF_mvc.lua_example&amp;diff=9387</id>
		<title>ACF mvc.lua example</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=ACF_mvc.lua_example&amp;diff=9387"/>
		<updated>2013-10-03T03:48:34Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Start on the MVC application section&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Set the hostname with mvc.lua =&lt;br /&gt;
&lt;br /&gt;
In this example we will create a simple hostname-setting command-line application using mvc.lua.  Once the controller/model are built, you can use &#039;&#039;the same code&#039;&#039; to set the hostname via the web with a web-based application controller.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
For this example, we will assume you have root access on the linux box you are running on (preferably an alpine box!)&lt;br /&gt;
&lt;br /&gt;
== Get the mvc.lua module == &lt;br /&gt;
&lt;br /&gt;
Get the mvc.lua module from the git repository.&lt;br /&gt;
&lt;br /&gt;
 wget http://git.alpinelinux.org/cgit/acf-core/plain/lua/mvc.lua&lt;br /&gt;
&lt;br /&gt;
== Create a model and controller ==&lt;br /&gt;
&lt;br /&gt;
Create a file &#039;&#039;&#039;hostname-controller.lua&#039;&#039;&#039;, defining the functions that an &amp;quot;end user&amp;quot; could run. We will only create one action, edithostname, which will be used to read and update the hostname. Since the action makes changes to the system, it naturally takes for form of a &#039;form&#039;:&lt;br /&gt;
&lt;br /&gt;
=== hostname-controller.lua ===&lt;br /&gt;
 -- Controller for editing hostname&lt;br /&gt;
 local mymodule = {} &lt;br /&gt;
 &lt;br /&gt;
 mymodule.edithostname = function (self)&lt;br /&gt;
         return self.handle_form(self, self.model.get_hostname, self.model.update_hostname, self.clientdata, &amp;quot;Edit&amp;quot;, &amp;quot;Edit Hostname&amp;quot;, &amp;quot;Hostname Updated&amp;quot;)&lt;br /&gt;
 end                                   &lt;br /&gt;
 &lt;br /&gt;
 return mymodule&lt;br /&gt;
&lt;br /&gt;
Create a file &#039;&#039;&#039;hostname-model.lua&#039;&#039;&#039;, defining the model functions to get and update the hostname.  We return a cfe table for each function including the form with the one entry for hostname:&lt;br /&gt;
&lt;br /&gt;
=== hostname-model.lua ===&lt;br /&gt;
 -- Model functions for retrieving / setting the hostname&lt;br /&gt;
 local mymodule = {}&lt;br /&gt;
 &lt;br /&gt;
 -- Create a cfe defining the form for editing the hostname and containing the current value&lt;br /&gt;
 mymodule.get_hostname = function(self, clientdata)&lt;br /&gt;
         local retval = {}&lt;br /&gt;
 &lt;br /&gt;
         local f = io.popen (&amp;quot;/bin/hostname&amp;quot;)&lt;br /&gt;
         local n = f:read(&amp;quot;*a&amp;quot;) or &amp;quot;none&amp;quot;&lt;br /&gt;
         f:close()&lt;br /&gt;
         n=string.gsub(n, &amp;quot;\n$&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
         retval.hostname = cfe({ value=n, label=&amp;quot;Hostname&amp;quot; })&lt;br /&gt;
 &lt;br /&gt;
         return cfe({ type=&amp;quot;group&amp;quot;, value=retval, label=&amp;quot;Hostname&amp;quot; })&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 -- Update the hostname from the value contained in the cfe created by get_hostname&lt;br /&gt;
 mymodule.update_hostname = function(self, hostnameform, action)&lt;br /&gt;
         local success = true&lt;br /&gt;
 &lt;br /&gt;
         -- Check to make sure the name is valid&lt;br /&gt;
         if (hostnameform.value.hostname.value == &amp;quot;&amp;quot;) then&lt;br /&gt;
                 success = false&lt;br /&gt;
                 hostnameform.value.hostname.errtxt = &amp;quot;Hostname must not be blank&amp;quot;&lt;br /&gt;
         elseif (#hostnameform.value.hostname.value &amp;gt; 16) then&lt;br /&gt;
                 success = false&lt;br /&gt;
                 hostnameform.value.hostname.errtxt = &amp;quot;Hostname must be less than 16 chars&amp;quot;&lt;br /&gt;
         elseif (string.find(hostnameform.value.hostname.value, &amp;quot;[^%w%_%-]&amp;quot;)) then&lt;br /&gt;
                 success = false&lt;br /&gt;
                 hostnameform.value.hostname.errtxt = &amp;quot;Hostname can contain alphanumerics only&amp;quot;&lt;br /&gt;
         end&lt;br /&gt;
 &lt;br /&gt;
         -- If it is, set the hostname&lt;br /&gt;
         if ( success ) then&lt;br /&gt;
                 local f = io.open(&amp;quot;/etc/hostname&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
                 if f then&lt;br /&gt;
                         f:write(hostnameform.value.hostname.value .. &amp;quot;\n&amp;quot;)&lt;br /&gt;
                         f:close()&lt;br /&gt;
                 end&lt;br /&gt;
                 f = io.popen (&amp;quot;/bin/hostname -F /etc/hostname&amp;quot;)&lt;br /&gt;
                 f:close()&lt;br /&gt;
         else&lt;br /&gt;
                 hostnameform.errtxt = &amp;quot;Failed to update hostname&amp;quot;&lt;br /&gt;
         end&lt;br /&gt;
 &lt;br /&gt;
         return hostnameform&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 return mymodule&lt;br /&gt;
&lt;br /&gt;
== Optionally test the model code (without mvc.lua)  ==&lt;br /&gt;
&lt;br /&gt;
If you want, you can create a &#039;&#039;&#039;test.lua&#039;&#039;&#039; script to validate the model code works on its own:&lt;br /&gt;
&lt;br /&gt;
=== test.lua ===&lt;br /&gt;
 require(&amp;quot;mvc&amp;quot;) -- Needed for cfe function definition&lt;br /&gt;
 m=require(&amp;quot;hostname-model&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 local form = m.get_hostname()&lt;br /&gt;
 form.value.hostname.value = arg[1] or &amp;quot;&amp;quot;&lt;br /&gt;
 form = m.update_hostname(nil, form)&lt;br /&gt;
 &lt;br /&gt;
 if form.errtxt then&lt;br /&gt;
         print(&amp;quot;FAILED: &amp;quot;..form.value.hostname.errtxt or form.errtxt)&lt;br /&gt;
 end&lt;br /&gt;
 form = m.get_hostname()&lt;br /&gt;
 print(form.value.hostname.value)&lt;br /&gt;
&lt;br /&gt;
You can then test this with:&lt;br /&gt;
&lt;br /&gt;
 #lua test.lua &amp;quot;Alpine&amp;quot;&lt;br /&gt;
  Alpine&lt;br /&gt;
&lt;br /&gt;
 #lua test.lua &amp;quot;Invalid Name&amp;quot;&lt;br /&gt;
  FAILED: Hostname can contain alphanumerics only&lt;br /&gt;
  Alpine&lt;br /&gt;
&lt;br /&gt;
== Add the package to the ACF framework ==&lt;br /&gt;
&lt;br /&gt;
To make the model and controller work within the ACF mvc.lua framework, we must do several things.   &lt;br /&gt;
&lt;br /&gt;
1. Install the ACF core package:&lt;br /&gt;
&lt;br /&gt;
 apk add acf-core&lt;br /&gt;
&lt;br /&gt;
Optionally, you can add the entire web-based ACF framework:&lt;br /&gt;
&lt;br /&gt;
 setup-acf&lt;br /&gt;
&lt;br /&gt;
2. Modify the ACF configuration file to look in /etc/acf/app/ for additional packages. Edit the /etc/acf/acf.conf file to add the &#039;&#039;/etc/acf/app/&#039;&#039; directory to the &#039;&#039;appdir&#039;&#039; comma-separated list:&lt;br /&gt;
&lt;br /&gt;
 appdir=/etc/acf/app/,/usr/share/acf/app/&lt;br /&gt;
&lt;br /&gt;
3. Move the model and controller to the new package directory. We will call the package &amp;quot;test&amp;quot;:&lt;br /&gt;
&lt;br /&gt;
 mkdir -p /etc/acf/app/test&lt;br /&gt;
 mv hostname-*.lua /etc/acf/app/test&lt;br /&gt;
&lt;br /&gt;
4. Test the new package using the acf-cli application:&lt;br /&gt;
&lt;br /&gt;
 # acf-cli /test/hostname/edithostname&lt;br /&gt;
 result = {}&lt;br /&gt;
 result[&amp;quot;label&amp;quot;] = &amp;quot;Edit Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;option&amp;quot;] = &amp;quot;Edit&amp;quot;&lt;br /&gt;
 result[&amp;quot;type&amp;quot;] = &amp;quot;form&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;label&amp;quot;] = &amp;quot;Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;type&amp;quot;] = &amp;quot;text&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;value&amp;quot;] = &amp;quot;Alpine&amp;quot;&lt;br /&gt;
 # acf-cli /test/hostname/edithostname hostname=test submit=true&lt;br /&gt;
 result = {}&lt;br /&gt;
 result[&amp;quot;descr&amp;quot;] = &amp;quot;Hostname Updated&amp;quot;&lt;br /&gt;
 result[&amp;quot;label&amp;quot;] = &amp;quot;Edit Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;option&amp;quot;] = &amp;quot;Edit&amp;quot;&lt;br /&gt;
 result[&amp;quot;type&amp;quot;] = &amp;quot;form&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;label&amp;quot;] = &amp;quot;Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;type&amp;quot;] = &amp;quot;text&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;value&amp;quot;] = &amp;quot;test&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Note that the first case read the existing hostname and the second case updated it. The output of the acf-cli application is a serialized version of the cfe form, which is good for testing but not too useful in real life.&lt;br /&gt;
&lt;br /&gt;
== Enable the package in the ACF web interface ==&lt;br /&gt;
&lt;br /&gt;
1. Add the web-based ACF framework:&lt;br /&gt;
&lt;br /&gt;
 setup-acf&lt;br /&gt;
&lt;br /&gt;
2. Configure all users to have access to the new hostname action. Edit &amp;quot;/etc/acf/app/test/hostname.roles&amp;quot; file and add GUEST permission for the edithostname action:&lt;br /&gt;
&lt;br /&gt;
 echo &amp;quot;GUEST=hostname/edithostname&amp;quot; &amp;gt; /etc/acf/app/test/hostname.roles&lt;br /&gt;
&lt;br /&gt;
The new action should now be visible by browsing to &#039;&#039;https://IP-of-host/cgi-bin/acf/test/hostname/edithostname&#039;&#039;. Obviously you might want to reconsider providing GUEST access to this action, because this allows unauthenticated users to modify your hostname.&lt;br /&gt;
&lt;br /&gt;
3. Add the new hostname action to the ACF menu. Edit &amp;quot;/etc/acf/app/test/hostname.menu&amp;quot; file and add a menu item:&lt;br /&gt;
&lt;br /&gt;
 echo &amp;quot;Test Hostname Edit edithostname&amp;quot; &amp;gt; /etc/acf/app/test/hostname.menu&lt;br /&gt;
&lt;br /&gt;
You will need to log off from the ACF interface (or delete the session cookie) before the new menu item will be visible.&lt;br /&gt;
&lt;br /&gt;
== Make an MVC based application ==&lt;br /&gt;
You have two options for creating an MVC based application of your own.&lt;br /&gt;
&lt;br /&gt;
1. Use the &#039;&#039;dispatch&#039;&#039; function. This is the method used by the web interface and the acf-cli application. The action to be dispatched is passed in a string format &#039;&#039;/prefix/controller/action&#039;&#039; and the input values are passed in as a clientinfo table. Output is written to stdout.&lt;br /&gt;
&lt;br /&gt;
2. Use the &#039;&#039;new&#039;&#039; function to load the desired controller and then directly call the controller actions. For handling forms and user input, the application will call the action once to retrieve the form cfe, then fill in the desired input values into the cfe and call the action again to submit the form and perform the desired action. The MVC application is responsible for any user interaction and display of the results.&lt;br /&gt;
&lt;br /&gt;
=== Use the Dispatch method ===&lt;br /&gt;
Todo&lt;br /&gt;
&lt;br /&gt;
=== Use the New method ===&lt;br /&gt;
Todo&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Obsolete}}&lt;br /&gt;
4. Create a dispatch wrapper program, named &#039;&#039;&#039;helloworld.lua&#039;&#039;&#039; in the current directory:&lt;br /&gt;
&lt;br /&gt;
 -- Simple CLI based mvc application&lt;br /&gt;
 &lt;br /&gt;
 -- this is to get around having to store&lt;br /&gt;
 -- the config file in /etc/helloworld/helloworld.conf&lt;br /&gt;
 ENV={}&lt;br /&gt;
 ENV.HOME=&amp;quot;.&amp;quot; &lt;br /&gt;
 &lt;br /&gt;
 -- load the module&lt;br /&gt;
 require(&amp;quot;mvc&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 -- create an new &amp;quot;mvc object&amp;quot;&lt;br /&gt;
 MVC=mvc:new()&lt;br /&gt;
 &lt;br /&gt;
 -- load the config file so we can find the appdir&lt;br /&gt;
 MVC:read_config(&amp;quot;helloworld&amp;quot;) &lt;br /&gt;
 &lt;br /&gt;
 -- create an application container&lt;br /&gt;
 APP=MVC:new(&amp;quot;app&amp;quot;)&lt;br /&gt;
  &lt;br /&gt;
 -- dispatch the request&lt;br /&gt;
 APP.clientdata.hostname=arg[2]&lt;br /&gt;
 APP:dispatch( &amp;quot;&amp;quot;, &amp;quot;hostname&amp;quot;, (arg[1] or &amp;quot;&amp;quot;))&lt;br /&gt;
&lt;br /&gt;
 -- destroy the mvc objects&lt;br /&gt;
 APP:destroy()&lt;br /&gt;
 MVC:destroy()&lt;br /&gt;
&lt;br /&gt;
This application loads the &amp;quot;mvc.lua&amp;quot; framework, creates an mvc &amp;quot;object&amp;quot; named &amp;quot;MVC&amp;quot;, then reads the helloworld.conf file to find out where the app dir is (helloworld/app/).  It then loads the &#039;&#039;&#039;app-controller.lua&#039;&#039;&#039; into a new &amp;quot;application level&amp;quot; object named &#039;&#039;&#039;APP&#039;&#039;&#039;.  Finally, it sets the clientdata and dispatches the hostname-controller/model pair.&lt;br /&gt;
&lt;br /&gt;
5.  Test the application:&lt;br /&gt;
 # hostname&lt;br /&gt;
 Alpine&lt;br /&gt;
 # lua helloworld.lua no-such-function foo&lt;br /&gt;
 The following unhandled application error occured:&lt;br /&gt;
 &lt;br /&gt;
 controller: &amp;quot;hostname&amp;quot; does not have a &amp;quot;no-such-function&amp;quot; action.&lt;br /&gt;
 # hostname&lt;br /&gt;
 Alpine&lt;br /&gt;
&lt;br /&gt;
 # hostname&lt;br /&gt;
 Alpine&lt;br /&gt;
 # lua helloworld.lua update Alline&lt;br /&gt;
 Your controller and application did not specify a view resolver.&lt;br /&gt;
 The MVC framework has no view available. sorry.&lt;br /&gt;
 # hostname&lt;br /&gt;
 Alline&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Note in the second case the hostname &#039;&#039;was&#039;&#039; changed, although the application does not know how to report success.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Create a view resolver and view formatter ==&lt;br /&gt;
&lt;br /&gt;
The view resolver is a function that returns a function that processes the view. The returned function receives input from the controller and generates the output to be displayed.&lt;br /&gt;
&lt;br /&gt;
We will build a very simple view resolver and view processor for our application. Add this to the end of &#039;&#039;&#039;helloworld/app/hostname-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 local private = {} &lt;br /&gt;
 &lt;br /&gt;
 private.view = function (f)&lt;br /&gt;
        if (f.msg) then&lt;br /&gt;
                print( (f.value or &amp;quot;&amp;quot;) .. &amp;quot; is not a valid hostname &amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                print (&amp;quot;Hostname is currently &amp;quot; .. f.value )&lt;br /&gt;
        end&lt;br /&gt;
 end &lt;br /&gt;
 &lt;br /&gt;
 view_resolver = function (self)&lt;br /&gt;
         return private.view&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Now we can test:&lt;br /&gt;
&lt;br /&gt;
 # lua helloworld.lua update &amp;quot;1 2 3&amp;quot;&lt;br /&gt;
    1 2 3 is not a valid hostname &lt;br /&gt;
 # lua helloworld.lua update        &lt;br /&gt;
    is not a valid hostname &lt;br /&gt;
 # lua helloworld.lua update Alpine&lt;br /&gt;
   Hostname is currently Alpine&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
But we have two problems:&lt;br /&gt;
&lt;br /&gt;
1. We now have to make a view resolver and view function for every controller.  If we add a &#039;&#039;date&#039;&#039; setting controller, we&#039;ll have to make a view resolver and view function for it, and so on.&lt;br /&gt;
&lt;br /&gt;
2. Perhaps more importantly, view_resolver is now an &amp;quot;action&amp;quot; in our appliction.  Recall that invalid actions are captured, but try this:&lt;br /&gt;
&lt;br /&gt;
  # lua helloworld.lua no_such_action Alpine&lt;br /&gt;
     The following unhandled application error occured:&lt;br /&gt;
   &lt;br /&gt;
     controller: &amp;quot;hostname&amp;quot; does not have a &amp;quot;no_such_action&amp;quot; action.&lt;br /&gt;
  &lt;br /&gt;
  # lua helloworld.lua view_resolver Alpine &lt;br /&gt;
     The following unhandled application error occured:&lt;br /&gt;
   &lt;br /&gt;
     ./helloworld/app/hostname-controller.lua:27: attempt to index local &#039;f&#039; (a function value)&lt;br /&gt;
     stack traceback:&lt;br /&gt;
        ./helloworld/app/hostname-controller.lua:27: in function &#039;viewfunc&#039;&lt;br /&gt;
        ./mvc.lua:139: in function &amp;lt;./mvc.lua:90&amp;gt;&lt;br /&gt;
        [C]: in function &#039;xpcall&#039;&lt;br /&gt;
        ./mvc.lua:90: in function &#039;dispatch&#039;&lt;br /&gt;
        helloworld.lua:23: in main chunk&lt;br /&gt;
        [C]: ?&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Because view_resolver is an action in the worker table, the &#039;&#039;&#039;mvc.lua&#039;&#039;&#039; runs it; but it returns a function, not a table, and causes an unhandled exception in the view.&lt;br /&gt;
&lt;br /&gt;
== Move the view resolver to the application level ==&lt;br /&gt;
&lt;br /&gt;
The solution to both problems is to move the view resolver and the view function out of the controller&#039;s worker table, into the next higher level, in this case, the application&#039;s worker table:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
1. Delete the private.view and view_resolver functions from &#039;&#039;&#039;helloworld/app/hostname-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
2. Add the following to &#039;&#039;&#039;helloworld/app/app-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
 local private = {}&lt;br /&gt;
 &lt;br /&gt;
 private.view = function ( controller, action, viewtable )&lt;br /&gt;
         io.write(string.format(&amp;quot;Controller: %s  Action: %s\n&amp;quot;,&lt;br /&gt;
                 controller or &amp;quot;&amp;quot;, action or &amp;quot;&amp;quot;))&lt;br /&gt;
         io.write (&amp;quot;Returned a table with the following values:\n&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
         for k,v in pairs(viewtable) do&lt;br /&gt;
                 io.write(string.format(&amp;quot;%s\t%s\n&amp;quot;, k, v))&lt;br /&gt;
         end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 view_resolver = function (self)&lt;br /&gt;
         return function (viewtable)&lt;br /&gt;
                 return private.view (self.conf.controller, self.conf.action, viewtable)&lt;br /&gt;
         end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This creates a more &amp;quot;generic&amp;quot; view, but one that will work for any controller - not just &#039;&#039;&#039;hostname&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Now things work as they should:&lt;br /&gt;
&lt;br /&gt;
 # lua helloworld.lua update &amp;quot;one two&amp;quot;&lt;br /&gt;
   Controller: hostname  Action: update&lt;br /&gt;
   Returned a table with the following values:&lt;br /&gt;
   value   one two&lt;br /&gt;
   type    string&lt;br /&gt;
   msg     Hostname can contain alphanumerics only&lt;br /&gt;
 &lt;br /&gt;
 # lua helloworld.lua view_resolver &amp;quot;Alpine&amp;quot;&lt;br /&gt;
   The following unhandled application error occured:&lt;br /&gt;
   &lt;br /&gt;
   controller: &amp;quot;hostname&amp;quot; does not have a &amp;quot;view_resolver&amp;quot; action.&lt;br /&gt;
&lt;br /&gt;
= mvc load &amp;amp; exec special functions =&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;mvc.lua&#039;&#039;&#039; module has a provision for executing code on module load, prior to executing the controller&#039;s action, just after executing the controller&#039;s action, and on module unload.&lt;br /&gt;
&lt;br /&gt;
This is done with the mvc table in the controller.  To demonstrate, let&#039;s add a few functions to &#039;&#039;&#039;helloworld/app/app-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
 mvc = {}&lt;br /&gt;
 mvc.on_load = function (self, parent)&lt;br /&gt;
         print (&amp;quot;This is the app controller&#039;s on_load function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 mvc.pre_exec = function (self)&lt;br /&gt;
         print (&amp;quot;This is the app controller&#039;s pre_exec function&amp;quot;)&lt;br /&gt;
 end &lt;br /&gt;
 &lt;br /&gt;
 mvc.post_exec = function (self)&lt;br /&gt;
         print (&amp;quot;This is the app controller&#039;s post_exec function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
 mvc.on_unload = function (self)&lt;br /&gt;
         print (&amp;quot;This is the app controller&#039;s on_unload function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
Now running our script shows when the functions get called:&lt;br /&gt;
&lt;br /&gt;
 # lua helloworld.lua update &amp;quot;Alpine&amp;quot;&lt;br /&gt;
   This is the app controller&#039;s on_load function&lt;br /&gt;
   This is the app controller&#039;s pre_exec function&lt;br /&gt;
   This is the app controller&#039;s post_exec function&lt;br /&gt;
   Controller: hostname  Action: update&lt;br /&gt;
   Returned a table with the following values:&lt;br /&gt;
   value   Alpine&lt;br /&gt;
   type    string&lt;br /&gt;
   This is the app controller&#039;s on_unload function&lt;br /&gt;
&lt;br /&gt;
We can add mvc functions to a specific controller, as well.  Add this to &#039;&#039;&#039;helloworld/app/hostname-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
 mvc = {}&lt;br /&gt;
 mvc.on_load = function (self, parent)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s on_load function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 mvc.pre_exec = function (self)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s pre_exec function&amp;quot;)&lt;br /&gt;
 end &lt;br /&gt;
 &lt;br /&gt;
 mvc.post_exec = function (self)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s post_exec function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 mvc.on_unload = function (self)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s on_unload function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
And this happens:&lt;br /&gt;
&lt;br /&gt;
 # lua helloworld.lua update &amp;quot;Alpine&amp;quot;&lt;br /&gt;
   This is the app controller&#039;s on_load function&lt;br /&gt;
   This is the hostname controller&#039;s on_load function&lt;br /&gt;
   This is the hostname controller&#039;s pre_exec function&lt;br /&gt;
   This is the hostname controller&#039;s post_exec function&lt;br /&gt;
   This is the hostname controller&#039;s on_unload function&lt;br /&gt;
   Controller: hostname  Action: update&lt;br /&gt;
   Returned a table with the following values:&lt;br /&gt;
   value   Alpine&lt;br /&gt;
   type    string&lt;br /&gt;
   This is the app controller&#039;s on_unload function&lt;br /&gt;
&lt;br /&gt;
Note that both the &#039;&#039;app&#039;&#039; and &#039;&#039;hostname&#039;&#039; &#039;&#039;&#039;on_load&#039;&#039;&#039; and &#039;&#039;&#039;on_unload&#039;&#039;&#039; functions were run, but only the &#039;&#039;hostname&#039;&#039; &#039;&#039;&#039;pre_exec&#039;&#039;&#039; and &#039;&#039;&#039;post_exec&#039;&#039;&#039; functions ran.  This is because the pre and post exec functions are run as part of the &amp;quot;action&amp;quot;, and the &#039;&#039;&#039;dispatch&#039;&#039;&#039; function looks in the lowest-level controller for the pre/post_exec function.  Since &#039;&#039;hostname&#039;&#039; now defines those functions, it runs them.&lt;br /&gt;
&lt;br /&gt;
To run both the &#039;&#039;hostname&#039;&#039; and &#039;&#039;app&#039;&#039; pre_exec function, you must arrange for the &#039;&#039;hostname&#039;&#039; pre_exec function to call it&#039;s parent pre_exec:&lt;br /&gt;
&lt;br /&gt;
 mvc = {}&lt;br /&gt;
 mvc.on_load = function (self, parent)&lt;br /&gt;
        print (&amp;quot;This is the hostname controller&#039;s on_load function&amp;quot;)&lt;br /&gt;
        mvc.parent_pre_exec = parent.worker.mvc.pre_exec&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 mvc.pre_exec = function (self)&lt;br /&gt;
         mvc.parent_pre_exec (self)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s pre_exec function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
[[Category:ACF]] [[Category:Lua]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=ACF_mvc.lua_example&amp;diff=9369</id>
		<title>ACF mvc.lua example</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=ACF_mvc.lua_example&amp;diff=9369"/>
		<updated>2013-09-30T16:19:16Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Export the new action to the web interface&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Set the hostname with mvc.lua =&lt;br /&gt;
&lt;br /&gt;
In this example we will create a simple hostname-setting command-line application using mvc.lua.  Once the controller/model are built, you can use &#039;&#039;the same code&#039;&#039; to set the hostname via the web with a web-based application controller.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
For this example, we will assume you have root access on the linux box you are running on (preferably an alpine box!)&lt;br /&gt;
&lt;br /&gt;
== Get the mvc.lua module == &lt;br /&gt;
&lt;br /&gt;
Get the mvc.lua module from the git repository.&lt;br /&gt;
&lt;br /&gt;
 wget http://git.alpinelinux.org/cgit/acf-core/plain/lua/mvc.lua&lt;br /&gt;
&lt;br /&gt;
== Create a model and controller ==&lt;br /&gt;
&lt;br /&gt;
Create a file &#039;&#039;&#039;hostname-controller.lua&#039;&#039;&#039;, defining the functions that an &amp;quot;end user&amp;quot; could run. We will only create one action, edithostname, which will be used to read and update the hostname. Since the action makes changes to the system, it naturally takes for form of a &#039;form&#039;:&lt;br /&gt;
&lt;br /&gt;
=== hostname-controller.lua ===&lt;br /&gt;
 -- Controller for editing hostname&lt;br /&gt;
 local mymodule = {} &lt;br /&gt;
 &lt;br /&gt;
 mymodule.edithostname = function (self)&lt;br /&gt;
         return self.handle_form(self, self.model.get_hostname, self.model.update_hostname, self.clientdata, &amp;quot;Edit&amp;quot;, &amp;quot;Edit Hostname&amp;quot;, &amp;quot;Hostname Updated&amp;quot;)&lt;br /&gt;
 end                                   &lt;br /&gt;
 &lt;br /&gt;
 return mymodule&lt;br /&gt;
&lt;br /&gt;
Create a file &#039;&#039;&#039;hostname-model.lua&#039;&#039;&#039;, defining the model functions to get and update the hostname.  We return a cfe table for each function including the form with the one entry for hostname:&lt;br /&gt;
&lt;br /&gt;
=== hostname-model.lua ===&lt;br /&gt;
 -- Model functions for retrieving / setting the hostname&lt;br /&gt;
 local mymodule = {}&lt;br /&gt;
 &lt;br /&gt;
 -- Create a cfe defining the form for editing the hostname and containing the current value&lt;br /&gt;
 mymodule.get_hostname = function(self, clientdata)&lt;br /&gt;
         local retval = {}&lt;br /&gt;
 &lt;br /&gt;
         local f = io.popen (&amp;quot;/bin/hostname&amp;quot;)&lt;br /&gt;
         local n = f:read(&amp;quot;*a&amp;quot;) or &amp;quot;none&amp;quot;&lt;br /&gt;
         f:close()&lt;br /&gt;
         n=string.gsub(n, &amp;quot;\n$&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
         retval.hostname = cfe({ value=n, label=&amp;quot;Hostname&amp;quot; })&lt;br /&gt;
 &lt;br /&gt;
         return cfe({ type=&amp;quot;group&amp;quot;, value=retval, label=&amp;quot;Hostname&amp;quot; })&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 -- Update the hostname from the value contained in the cfe created by get_hostname&lt;br /&gt;
 mymodule.update_hostname = function(self, hostnameform, action)&lt;br /&gt;
         local success = true&lt;br /&gt;
 &lt;br /&gt;
         -- Check to make sure the name is valid&lt;br /&gt;
         if (hostnameform.value.hostname.value == &amp;quot;&amp;quot;) then&lt;br /&gt;
                 success = false&lt;br /&gt;
                 hostnameform.value.hostname.errtxt = &amp;quot;Hostname must not be blank&amp;quot;&lt;br /&gt;
         elseif (#hostnameform.value.hostname.value &amp;gt; 16) then&lt;br /&gt;
                 success = false&lt;br /&gt;
                 hostnameform.value.hostname.errtxt = &amp;quot;Hostname must be less than 16 chars&amp;quot;&lt;br /&gt;
         elseif (string.find(hostnameform.value.hostname.value, &amp;quot;[^%w%_%-]&amp;quot;)) then&lt;br /&gt;
                 success = false&lt;br /&gt;
                 hostnameform.value.hostname.errtxt = &amp;quot;Hostname can contain alphanumerics only&amp;quot;&lt;br /&gt;
         end&lt;br /&gt;
 &lt;br /&gt;
         -- If it is, set the hostname&lt;br /&gt;
         if ( success ) then&lt;br /&gt;
                 local f = io.open(&amp;quot;/etc/hostname&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
                 if f then&lt;br /&gt;
                         f:write(hostnameform.value.hostname.value .. &amp;quot;\n&amp;quot;)&lt;br /&gt;
                         f:close()&lt;br /&gt;
                 end&lt;br /&gt;
                 f = io.popen (&amp;quot;/bin/hostname -F /etc/hostname&amp;quot;)&lt;br /&gt;
                 f:close()&lt;br /&gt;
         else&lt;br /&gt;
                 hostnameform.errtxt = &amp;quot;Failed to update hostname&amp;quot;&lt;br /&gt;
         end&lt;br /&gt;
 &lt;br /&gt;
         return hostnameform&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 return mymodule&lt;br /&gt;
&lt;br /&gt;
== Optionally test the model code (without mvc.lua)  ==&lt;br /&gt;
&lt;br /&gt;
If you want, you can create a &#039;&#039;&#039;test.lua&#039;&#039;&#039; script to validate the model code works on its own:&lt;br /&gt;
&lt;br /&gt;
=== test.lua ===&lt;br /&gt;
 require(&amp;quot;mvc&amp;quot;) -- Needed for cfe function definition&lt;br /&gt;
 m=require(&amp;quot;hostname-model&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 local form = m.get_hostname()&lt;br /&gt;
 form.value.hostname.value = arg[1] or &amp;quot;&amp;quot;&lt;br /&gt;
 form = m.update_hostname(nil, form)&lt;br /&gt;
 &lt;br /&gt;
 if form.errtxt then&lt;br /&gt;
         print(&amp;quot;FAILED: &amp;quot;..form.value.hostname.errtxt or form.errtxt)&lt;br /&gt;
 end&lt;br /&gt;
 form = m.get_hostname()&lt;br /&gt;
 print(form.value.hostname.value)&lt;br /&gt;
&lt;br /&gt;
You can then test this with:&lt;br /&gt;
&lt;br /&gt;
 #lua test.lua &amp;quot;Alpine&amp;quot;&lt;br /&gt;
  Alpine&lt;br /&gt;
&lt;br /&gt;
 #lua test.lua &amp;quot;Invalid Name&amp;quot;&lt;br /&gt;
  FAILED: Hostname can contain alphanumerics only&lt;br /&gt;
  Alpine&lt;br /&gt;
&lt;br /&gt;
== Add the package to the ACF framework ==&lt;br /&gt;
&lt;br /&gt;
To make the model and controller work within the ACF mvc.lua framework, we must do several things.   &lt;br /&gt;
&lt;br /&gt;
1. Install the ACF core package:&lt;br /&gt;
&lt;br /&gt;
 apk add acf-core&lt;br /&gt;
&lt;br /&gt;
Optionally, you can add the entire web-based ACF framework:&lt;br /&gt;
&lt;br /&gt;
 setup-acf&lt;br /&gt;
&lt;br /&gt;
2. Modify the ACF configuration file to look in /etc/acf/app/ for additional packages. Edit the /etc/acf/acf.conf file to add the &#039;&#039;/etc/acf/app/&#039;&#039; directory to the &#039;&#039;appdir&#039;&#039; comma-separated list:&lt;br /&gt;
&lt;br /&gt;
 appdir=/etc/acf/app/,/usr/share/acf/app/&lt;br /&gt;
&lt;br /&gt;
3. Move the model and controller to the new package directory. We will call the package &amp;quot;test&amp;quot;:&lt;br /&gt;
&lt;br /&gt;
 mkdir -p /etc/acf/app/test&lt;br /&gt;
 mv hostname-*.lua /etc/acf/app/test&lt;br /&gt;
&lt;br /&gt;
4. Test the new package using the acf-cli application:&lt;br /&gt;
&lt;br /&gt;
 # acf-cli /test/hostname/edithostname&lt;br /&gt;
 result = {}&lt;br /&gt;
 result[&amp;quot;label&amp;quot;] = &amp;quot;Edit Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;option&amp;quot;] = &amp;quot;Edit&amp;quot;&lt;br /&gt;
 result[&amp;quot;type&amp;quot;] = &amp;quot;form&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;label&amp;quot;] = &amp;quot;Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;type&amp;quot;] = &amp;quot;text&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;value&amp;quot;] = &amp;quot;Alpine&amp;quot;&lt;br /&gt;
 # acf-cli /test/hostname/edithostname hostname=test submit=true&lt;br /&gt;
 result = {}&lt;br /&gt;
 result[&amp;quot;descr&amp;quot;] = &amp;quot;Hostname Updated&amp;quot;&lt;br /&gt;
 result[&amp;quot;label&amp;quot;] = &amp;quot;Edit Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;option&amp;quot;] = &amp;quot;Edit&amp;quot;&lt;br /&gt;
 result[&amp;quot;type&amp;quot;] = &amp;quot;form&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;label&amp;quot;] = &amp;quot;Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;type&amp;quot;] = &amp;quot;text&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;value&amp;quot;] = &amp;quot;test&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Note that the first case read the existing hostname and the second case updated it. The output of the acf-cli application is a serialized version of the cfe form, which is good for testing but not too useful in real life.&lt;br /&gt;
&lt;br /&gt;
== Enable the package in the ACF web interface ==&lt;br /&gt;
&lt;br /&gt;
1. Add the web-based ACF framework:&lt;br /&gt;
&lt;br /&gt;
 setup-acf&lt;br /&gt;
&lt;br /&gt;
2. Configure all users to have access to the new hostname action. Edit &amp;quot;/etc/acf/app/test/hostname.roles&amp;quot; file and add GUEST permission for the edithostname action:&lt;br /&gt;
&lt;br /&gt;
 echo &amp;quot;GUEST=hostname/edithostname&amp;quot; &amp;gt; /etc/acf/app/test/hostname.roles&lt;br /&gt;
&lt;br /&gt;
The new action should now be visible by browsing to &#039;&#039;https://IP-of-host/cgi-bin/acf/test/hostname/edithostname&#039;&#039;. Obviously you might want to reconsider providing GUEST access to this action, because this allows unauthenticated users to modify your hostname.&lt;br /&gt;
&lt;br /&gt;
3. Add the new hostname action to the ACF menu. Edit &amp;quot;/etc/acf/app/test/hostname.menu&amp;quot; file and add a menu item:&lt;br /&gt;
&lt;br /&gt;
 echo &amp;quot;Test Hostname Edit edithostname&amp;quot; &amp;gt; /etc/acf/app/test/hostname.menu&lt;br /&gt;
&lt;br /&gt;
You will need to log off from the ACF interface (or delete the session cookie) before the new menu item will be visible.&lt;br /&gt;
&lt;br /&gt;
== Make an MVC based application ==&lt;br /&gt;
&lt;br /&gt;
4. Create a dispatch wrapper program, named &#039;&#039;&#039;helloworld.lua&#039;&#039;&#039; in the current directory:&lt;br /&gt;
&lt;br /&gt;
 -- Simple CLI based mvc application&lt;br /&gt;
 &lt;br /&gt;
 -- this is to get around having to store&lt;br /&gt;
 -- the config file in /etc/helloworld/helloworld.conf&lt;br /&gt;
 ENV={}&lt;br /&gt;
 ENV.HOME=&amp;quot;.&amp;quot; &lt;br /&gt;
 &lt;br /&gt;
 -- load the module&lt;br /&gt;
 require(&amp;quot;mvc&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 -- create an new &amp;quot;mvc object&amp;quot;&lt;br /&gt;
 MVC=mvc:new()&lt;br /&gt;
 &lt;br /&gt;
 -- load the config file so we can find the appdir&lt;br /&gt;
 MVC:read_config(&amp;quot;helloworld&amp;quot;) &lt;br /&gt;
 &lt;br /&gt;
 -- create an application container&lt;br /&gt;
 APP=MVC:new(&amp;quot;app&amp;quot;)&lt;br /&gt;
  &lt;br /&gt;
 -- dispatch the request&lt;br /&gt;
 APP.clientdata.hostname=arg[2]&lt;br /&gt;
 APP:dispatch( &amp;quot;&amp;quot;, &amp;quot;hostname&amp;quot;, (arg[1] or &amp;quot;&amp;quot;))&lt;br /&gt;
&lt;br /&gt;
 -- destroy the mvc objects&lt;br /&gt;
 APP:destroy()&lt;br /&gt;
 MVC:destroy()&lt;br /&gt;
&lt;br /&gt;
This application loads the &amp;quot;mvc.lua&amp;quot; framework, creates an mvc &amp;quot;object&amp;quot; named &amp;quot;MVC&amp;quot;, then reads the helloworld.conf file to find out where the app dir is (helloworld/app/).  It then loads the &#039;&#039;&#039;app-controller.lua&#039;&#039;&#039; into a new &amp;quot;application level&amp;quot; object named &#039;&#039;&#039;APP&#039;&#039;&#039;.  Finally, it sets the clientdata and dispatches the hostname-controller/model pair.&lt;br /&gt;
&lt;br /&gt;
5.  Test the application:&lt;br /&gt;
 # hostname&lt;br /&gt;
 Alpine&lt;br /&gt;
 # lua helloworld.lua no-such-function foo&lt;br /&gt;
 The following unhandled application error occured:&lt;br /&gt;
 &lt;br /&gt;
 controller: &amp;quot;hostname&amp;quot; does not have a &amp;quot;no-such-function&amp;quot; action.&lt;br /&gt;
 # hostname&lt;br /&gt;
 Alpine&lt;br /&gt;
&lt;br /&gt;
 # hostname&lt;br /&gt;
 Alpine&lt;br /&gt;
 # lua helloworld.lua update Alline&lt;br /&gt;
 Your controller and application did not specify a view resolver.&lt;br /&gt;
 The MVC framework has no view available. sorry.&lt;br /&gt;
 # hostname&lt;br /&gt;
 Alline&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Note in the second case the hostname &#039;&#039;was&#039;&#039; changed, although the application does not know how to report success.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Create a view resolver and view formatter ==&lt;br /&gt;
&lt;br /&gt;
The view resolver is a function that returns a function that processes the view. The returned function receives input from the controller and generates the output to be displayed.&lt;br /&gt;
&lt;br /&gt;
We will build a very simple view resolver and view processor for our application. Add this to the end of &#039;&#039;&#039;helloworld/app/hostname-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 local private = {} &lt;br /&gt;
 &lt;br /&gt;
 private.view = function (f)&lt;br /&gt;
        if (f.msg) then&lt;br /&gt;
                print( (f.value or &amp;quot;&amp;quot;) .. &amp;quot; is not a valid hostname &amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                print (&amp;quot;Hostname is currently &amp;quot; .. f.value )&lt;br /&gt;
        end&lt;br /&gt;
 end &lt;br /&gt;
 &lt;br /&gt;
 view_resolver = function (self)&lt;br /&gt;
         return private.view&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Now we can test:&lt;br /&gt;
&lt;br /&gt;
 # lua helloworld.lua update &amp;quot;1 2 3&amp;quot;&lt;br /&gt;
    1 2 3 is not a valid hostname &lt;br /&gt;
 # lua helloworld.lua update        &lt;br /&gt;
    is not a valid hostname &lt;br /&gt;
 # lua helloworld.lua update Alpine&lt;br /&gt;
   Hostname is currently Alpine&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
But we have two problems:&lt;br /&gt;
&lt;br /&gt;
1. We now have to make a view resolver and view function for every controller.  If we add a &#039;&#039;date&#039;&#039; setting controller, we&#039;ll have to make a view resolver and view function for it, and so on.&lt;br /&gt;
&lt;br /&gt;
2. Perhaps more importantly, view_resolver is now an &amp;quot;action&amp;quot; in our appliction.  Recall that invalid actions are captured, but try this:&lt;br /&gt;
&lt;br /&gt;
  # lua helloworld.lua no_such_action Alpine&lt;br /&gt;
     The following unhandled application error occured:&lt;br /&gt;
   &lt;br /&gt;
     controller: &amp;quot;hostname&amp;quot; does not have a &amp;quot;no_such_action&amp;quot; action.&lt;br /&gt;
  &lt;br /&gt;
  # lua helloworld.lua view_resolver Alpine &lt;br /&gt;
     The following unhandled application error occured:&lt;br /&gt;
   &lt;br /&gt;
     ./helloworld/app/hostname-controller.lua:27: attempt to index local &#039;f&#039; (a function value)&lt;br /&gt;
     stack traceback:&lt;br /&gt;
        ./helloworld/app/hostname-controller.lua:27: in function &#039;viewfunc&#039;&lt;br /&gt;
        ./mvc.lua:139: in function &amp;lt;./mvc.lua:90&amp;gt;&lt;br /&gt;
        [C]: in function &#039;xpcall&#039;&lt;br /&gt;
        ./mvc.lua:90: in function &#039;dispatch&#039;&lt;br /&gt;
        helloworld.lua:23: in main chunk&lt;br /&gt;
        [C]: ?&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Because view_resolver is an action in the worker table, the &#039;&#039;&#039;mvc.lua&#039;&#039;&#039; runs it; but it returns a function, not a table, and causes an unhandled exception in the view.&lt;br /&gt;
&lt;br /&gt;
== Move the view resolver to the application level ==&lt;br /&gt;
&lt;br /&gt;
The solution to both problems is to move the view resolver and the view function out of the controller&#039;s worker table, into the next higher level, in this case, the application&#039;s worker table:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
1. Delete the private.view and view_resolver functions from &#039;&#039;&#039;helloworld/app/hostname-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
2. Add the following to &#039;&#039;&#039;helloworld/app/app-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
 local private = {}&lt;br /&gt;
 &lt;br /&gt;
 private.view = function ( controller, action, viewtable )&lt;br /&gt;
         io.write(string.format(&amp;quot;Controller: %s  Action: %s\n&amp;quot;,&lt;br /&gt;
                 controller or &amp;quot;&amp;quot;, action or &amp;quot;&amp;quot;))&lt;br /&gt;
         io.write (&amp;quot;Returned a table with the following values:\n&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
         for k,v in pairs(viewtable) do&lt;br /&gt;
                 io.write(string.format(&amp;quot;%s\t%s\n&amp;quot;, k, v))&lt;br /&gt;
         end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 view_resolver = function (self)&lt;br /&gt;
         return function (viewtable)&lt;br /&gt;
                 return private.view (self.conf.controller, self.conf.action, viewtable)&lt;br /&gt;
         end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This creates a more &amp;quot;generic&amp;quot; view, but one that will work for any controller - not just &#039;&#039;&#039;hostname&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Now things work as they should:&lt;br /&gt;
&lt;br /&gt;
 # lua helloworld.lua update &amp;quot;one two&amp;quot;&lt;br /&gt;
   Controller: hostname  Action: update&lt;br /&gt;
   Returned a table with the following values:&lt;br /&gt;
   value   one two&lt;br /&gt;
   type    string&lt;br /&gt;
   msg     Hostname can contain alphanumerics only&lt;br /&gt;
 &lt;br /&gt;
 # lua helloworld.lua view_resolver &amp;quot;Alpine&amp;quot;&lt;br /&gt;
   The following unhandled application error occured:&lt;br /&gt;
   &lt;br /&gt;
   controller: &amp;quot;hostname&amp;quot; does not have a &amp;quot;view_resolver&amp;quot; action.&lt;br /&gt;
&lt;br /&gt;
= mvc load &amp;amp; exec special functions =&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;mvc.lua&#039;&#039;&#039; module has a provision for executing code on module load, prior to executing the controller&#039;s action, just after executing the controller&#039;s action, and on module unload.&lt;br /&gt;
&lt;br /&gt;
This is done with the mvc table in the controller.  To demonstrate, let&#039;s add a few functions to &#039;&#039;&#039;helloworld/app/app-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
 mvc = {}&lt;br /&gt;
 mvc.on_load = function (self, parent)&lt;br /&gt;
         print (&amp;quot;This is the app controller&#039;s on_load function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 mvc.pre_exec = function (self)&lt;br /&gt;
         print (&amp;quot;This is the app controller&#039;s pre_exec function&amp;quot;)&lt;br /&gt;
 end &lt;br /&gt;
 &lt;br /&gt;
 mvc.post_exec = function (self)&lt;br /&gt;
         print (&amp;quot;This is the app controller&#039;s post_exec function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
 mvc.on_unload = function (self)&lt;br /&gt;
         print (&amp;quot;This is the app controller&#039;s on_unload function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
Now running our script shows when the functions get called:&lt;br /&gt;
&lt;br /&gt;
 # lua helloworld.lua update &amp;quot;Alpine&amp;quot;&lt;br /&gt;
   This is the app controller&#039;s on_load function&lt;br /&gt;
   This is the app controller&#039;s pre_exec function&lt;br /&gt;
   This is the app controller&#039;s post_exec function&lt;br /&gt;
   Controller: hostname  Action: update&lt;br /&gt;
   Returned a table with the following values:&lt;br /&gt;
   value   Alpine&lt;br /&gt;
   type    string&lt;br /&gt;
   This is the app controller&#039;s on_unload function&lt;br /&gt;
&lt;br /&gt;
We can add mvc functions to a specific controller, as well.  Add this to &#039;&#039;&#039;helloworld/app/hostname-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
 mvc = {}&lt;br /&gt;
 mvc.on_load = function (self, parent)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s on_load function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 mvc.pre_exec = function (self)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s pre_exec function&amp;quot;)&lt;br /&gt;
 end &lt;br /&gt;
 &lt;br /&gt;
 mvc.post_exec = function (self)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s post_exec function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 mvc.on_unload = function (self)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s on_unload function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
And this happens:&lt;br /&gt;
&lt;br /&gt;
 # lua helloworld.lua update &amp;quot;Alpine&amp;quot;&lt;br /&gt;
   This is the app controller&#039;s on_load function&lt;br /&gt;
   This is the hostname controller&#039;s on_load function&lt;br /&gt;
   This is the hostname controller&#039;s pre_exec function&lt;br /&gt;
   This is the hostname controller&#039;s post_exec function&lt;br /&gt;
   This is the hostname controller&#039;s on_unload function&lt;br /&gt;
   Controller: hostname  Action: update&lt;br /&gt;
   Returned a table with the following values:&lt;br /&gt;
   value   Alpine&lt;br /&gt;
   type    string&lt;br /&gt;
   This is the app controller&#039;s on_unload function&lt;br /&gt;
&lt;br /&gt;
Note that both the &#039;&#039;app&#039;&#039; and &#039;&#039;hostname&#039;&#039; &#039;&#039;&#039;on_load&#039;&#039;&#039; and &#039;&#039;&#039;on_unload&#039;&#039;&#039; functions were run, but only the &#039;&#039;hostname&#039;&#039; &#039;&#039;&#039;pre_exec&#039;&#039;&#039; and &#039;&#039;&#039;post_exec&#039;&#039;&#039; functions ran.  This is because the pre and post exec functions are run as part of the &amp;quot;action&amp;quot;, and the &#039;&#039;&#039;dispatch&#039;&#039;&#039; function looks in the lowest-level controller for the pre/post_exec function.  Since &#039;&#039;hostname&#039;&#039; now defines those functions, it runs them.&lt;br /&gt;
&lt;br /&gt;
To run both the &#039;&#039;hostname&#039;&#039; and &#039;&#039;app&#039;&#039; pre_exec function, you must arrange for the &#039;&#039;hostname&#039;&#039; pre_exec function to call it&#039;s parent pre_exec:&lt;br /&gt;
&lt;br /&gt;
 mvc = {}&lt;br /&gt;
 mvc.on_load = function (self, parent)&lt;br /&gt;
        print (&amp;quot;This is the hostname controller&#039;s on_load function&amp;quot;)&lt;br /&gt;
        mvc.parent_pre_exec = parent.worker.mvc.pre_exec&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 mvc.pre_exec = function (self)&lt;br /&gt;
         mvc.parent_pre_exec (self)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s pre_exec function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
[[Category:ACF]] [[Category:Lua]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=ACF_mvc.lua_example&amp;diff=9368</id>
		<title>ACF mvc.lua example</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=ACF_mvc.lua_example&amp;diff=9368"/>
		<updated>2013-09-30T16:01:35Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Test the code with acf-cli&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Set the hostname with mvc.lua =&lt;br /&gt;
&lt;br /&gt;
In this example we will create a simple hostname-setting command-line application using mvc.lua.  Once the controller/model are built, you can use &#039;&#039;the same code&#039;&#039; to set the hostname via the web with a web-based application controller.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
For this example, we will assume you have root access on the linux box you are running on (preferably an alpine box!)&lt;br /&gt;
&lt;br /&gt;
== Get the mvc.lua module == &lt;br /&gt;
&lt;br /&gt;
Get the mvc.lua module from the git repository.&lt;br /&gt;
&lt;br /&gt;
 wget http://git.alpinelinux.org/cgit/acf-core/plain/lua/mvc.lua&lt;br /&gt;
&lt;br /&gt;
== Create a model and controller ==&lt;br /&gt;
&lt;br /&gt;
Create a file &#039;&#039;&#039;hostname-controller.lua&#039;&#039;&#039;, defining the functions that an &amp;quot;end user&amp;quot; could run. We will only create one action, edithostname, which will be used to read and update the hostname. Since the action makes changes to the system, it naturally takes for form of a &#039;form&#039;:&lt;br /&gt;
&lt;br /&gt;
=== hostname-controller.lua ===&lt;br /&gt;
 -- Controller for editing hostname&lt;br /&gt;
 local mymodule = {} &lt;br /&gt;
 &lt;br /&gt;
 mymodule.edithostname = function (self)&lt;br /&gt;
         return self.handle_form(self, self.model.get_hostname, self.model.update_hostname, self.clientdata, &amp;quot;Edit&amp;quot;, &amp;quot;Edit Hostname&amp;quot;, &amp;quot;Hostname Updated&amp;quot;)&lt;br /&gt;
 end                                   &lt;br /&gt;
 &lt;br /&gt;
 return mymodule&lt;br /&gt;
&lt;br /&gt;
Create a file &#039;&#039;&#039;hostname-model.lua&#039;&#039;&#039;, defining the model functions to get and update the hostname.  We return a cfe table for each function including the form with the one entry for hostname:&lt;br /&gt;
&lt;br /&gt;
=== hostname-model.lua ===&lt;br /&gt;
 -- Model functions for retrieving / setting the hostname&lt;br /&gt;
 local mymodule = {}&lt;br /&gt;
 &lt;br /&gt;
 -- Create a cfe defining the form for editing the hostname and containing the current value&lt;br /&gt;
 mymodule.get_hostname = function(self, clientdata)&lt;br /&gt;
         local retval = {}&lt;br /&gt;
 &lt;br /&gt;
         local f = io.popen (&amp;quot;/bin/hostname&amp;quot;)&lt;br /&gt;
         local n = f:read(&amp;quot;*a&amp;quot;) or &amp;quot;none&amp;quot;&lt;br /&gt;
         f:close()&lt;br /&gt;
         n=string.gsub(n, &amp;quot;\n$&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
         retval.hostname = cfe({ value=n, label=&amp;quot;Hostname&amp;quot; })&lt;br /&gt;
 &lt;br /&gt;
         return cfe({ type=&amp;quot;group&amp;quot;, value=retval, label=&amp;quot;Hostname&amp;quot; })&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 -- Update the hostname from the value contained in the cfe created by get_hostname&lt;br /&gt;
 mymodule.update_hostname = function(self, hostnameform, action)&lt;br /&gt;
         local success = true&lt;br /&gt;
 &lt;br /&gt;
         -- Check to make sure the name is valid&lt;br /&gt;
         if (hostnameform.value.hostname.value == &amp;quot;&amp;quot;) then&lt;br /&gt;
                 success = false&lt;br /&gt;
                 hostnameform.value.hostname.errtxt = &amp;quot;Hostname must not be blank&amp;quot;&lt;br /&gt;
         elseif (#hostnameform.value.hostname.value &amp;gt; 16) then&lt;br /&gt;
                 success = false&lt;br /&gt;
                 hostnameform.value.hostname.errtxt = &amp;quot;Hostname must be less than 16 chars&amp;quot;&lt;br /&gt;
         elseif (string.find(hostnameform.value.hostname.value, &amp;quot;[^%w%_%-]&amp;quot;)) then&lt;br /&gt;
                 success = false&lt;br /&gt;
                 hostnameform.value.hostname.errtxt = &amp;quot;Hostname can contain alphanumerics only&amp;quot;&lt;br /&gt;
         end&lt;br /&gt;
 &lt;br /&gt;
         -- If it is, set the hostname&lt;br /&gt;
         if ( success ) then&lt;br /&gt;
                 local f = io.open(&amp;quot;/etc/hostname&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
                 if f then&lt;br /&gt;
                         f:write(hostnameform.value.hostname.value .. &amp;quot;\n&amp;quot;)&lt;br /&gt;
                         f:close()&lt;br /&gt;
                 end&lt;br /&gt;
                 f = io.popen (&amp;quot;/bin/hostname -F /etc/hostname&amp;quot;)&lt;br /&gt;
                 f:close()&lt;br /&gt;
         else&lt;br /&gt;
                 hostnameform.errtxt = &amp;quot;Failed to update hostname&amp;quot;&lt;br /&gt;
         end&lt;br /&gt;
 &lt;br /&gt;
         return hostnameform&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 return mymodule&lt;br /&gt;
&lt;br /&gt;
== Optionally test the model code (without mvc.lua)  ==&lt;br /&gt;
&lt;br /&gt;
If you want, you can create a &#039;&#039;&#039;test.lua&#039;&#039;&#039; script to validate the model code works on its own:&lt;br /&gt;
&lt;br /&gt;
=== test.lua ===&lt;br /&gt;
 require(&amp;quot;mvc&amp;quot;) -- Needed for cfe function definition&lt;br /&gt;
 m=require(&amp;quot;hostname-model&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 local form = m.get_hostname()&lt;br /&gt;
 form.value.hostname.value = arg[1] or &amp;quot;&amp;quot;&lt;br /&gt;
 form = m.update_hostname(nil, form)&lt;br /&gt;
 &lt;br /&gt;
 if form.errtxt then&lt;br /&gt;
         print(&amp;quot;FAILED: &amp;quot;..form.value.hostname.errtxt or form.errtxt)&lt;br /&gt;
 end&lt;br /&gt;
 form = m.get_hostname()&lt;br /&gt;
 print(form.value.hostname.value)&lt;br /&gt;
&lt;br /&gt;
You can then test this with:&lt;br /&gt;
&lt;br /&gt;
 #lua test.lua &amp;quot;Alpine&amp;quot;&lt;br /&gt;
  Alpine&lt;br /&gt;
&lt;br /&gt;
 #lua test.lua &amp;quot;Invalid Name&amp;quot;&lt;br /&gt;
  FAILED: Hostname can contain alphanumerics only&lt;br /&gt;
  Alpine&lt;br /&gt;
&lt;br /&gt;
== Add the package to the ACF framework ==&lt;br /&gt;
&lt;br /&gt;
To make the model and controller work within the ACF mvc.lua framework, we must do several things.   &lt;br /&gt;
&lt;br /&gt;
1. Install the ACF core package:&lt;br /&gt;
&lt;br /&gt;
 apk add acf-core&lt;br /&gt;
&lt;br /&gt;
Optionally, you can add the entire web-based ACF framework:&lt;br /&gt;
&lt;br /&gt;
 setup-acf&lt;br /&gt;
&lt;br /&gt;
2. Modify the ACF configuration file to look in /etc/acf/app/ for additional packages. Edit the /etc/acf/acf.conf file to add the &#039;&#039;/etc/acf/app/&#039;&#039; directory to the &#039;&#039;appdir&#039;&#039; comma-separated list:&lt;br /&gt;
&lt;br /&gt;
 appdir=/etc/acf/app/,/usr/share/acf/app/&lt;br /&gt;
&lt;br /&gt;
3. Move the model and controller to the new package directory. We will call the package &amp;quot;test&amp;quot;:&lt;br /&gt;
&lt;br /&gt;
 mkdir -p /etc/acf/app/test&lt;br /&gt;
 mv hostname-*.lua /etc/acf/app/test&lt;br /&gt;
&lt;br /&gt;
4. Test the new package using the acf-cli application:&lt;br /&gt;
&lt;br /&gt;
 # acf-cli /test/hostname/edithostname&lt;br /&gt;
 result = {}&lt;br /&gt;
 result[&amp;quot;label&amp;quot;] = &amp;quot;Edit Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;option&amp;quot;] = &amp;quot;Edit&amp;quot;&lt;br /&gt;
 result[&amp;quot;type&amp;quot;] = &amp;quot;form&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;label&amp;quot;] = &amp;quot;Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;type&amp;quot;] = &amp;quot;text&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;value&amp;quot;] = &amp;quot;Alpine&amp;quot;&lt;br /&gt;
 # acf-cli /test/hostname/edithostname hostname=test submit=true&lt;br /&gt;
 result = {}&lt;br /&gt;
 result[&amp;quot;descr&amp;quot;] = &amp;quot;Hostname Updated&amp;quot;&lt;br /&gt;
 result[&amp;quot;label&amp;quot;] = &amp;quot;Edit Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;option&amp;quot;] = &amp;quot;Edit&amp;quot;&lt;br /&gt;
 result[&amp;quot;type&amp;quot;] = &amp;quot;form&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;] = {}&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;label&amp;quot;] = &amp;quot;Hostname&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;type&amp;quot;] = &amp;quot;text&amp;quot;&lt;br /&gt;
 result[&amp;quot;value&amp;quot;][&amp;quot;hostname&amp;quot;][&amp;quot;value&amp;quot;] = &amp;quot;test&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Note that the first case read the existing hostname and the second case updated it. The output of the acf-cli application is a serialized version of the cfe form, which is good for testing but not too useful in real life.&lt;br /&gt;
== Make an MVC based application ==&lt;br /&gt;
&lt;br /&gt;
4. Create a dispatch wrapper program, named &#039;&#039;&#039;helloworld.lua&#039;&#039;&#039; in the current directory:&lt;br /&gt;
&lt;br /&gt;
 -- Simple CLI based mvc application&lt;br /&gt;
 &lt;br /&gt;
 -- this is to get around having to store&lt;br /&gt;
 -- the config file in /etc/helloworld/helloworld.conf&lt;br /&gt;
 ENV={}&lt;br /&gt;
 ENV.HOME=&amp;quot;.&amp;quot; &lt;br /&gt;
 &lt;br /&gt;
 -- load the module&lt;br /&gt;
 require(&amp;quot;mvc&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 -- create an new &amp;quot;mvc object&amp;quot;&lt;br /&gt;
 MVC=mvc:new()&lt;br /&gt;
 &lt;br /&gt;
 -- load the config file so we can find the appdir&lt;br /&gt;
 MVC:read_config(&amp;quot;helloworld&amp;quot;) &lt;br /&gt;
 &lt;br /&gt;
 -- create an application container&lt;br /&gt;
 APP=MVC:new(&amp;quot;app&amp;quot;)&lt;br /&gt;
  &lt;br /&gt;
 -- dispatch the request&lt;br /&gt;
 APP.clientdata.hostname=arg[2]&lt;br /&gt;
 APP:dispatch( &amp;quot;&amp;quot;, &amp;quot;hostname&amp;quot;, (arg[1] or &amp;quot;&amp;quot;))&lt;br /&gt;
&lt;br /&gt;
 -- destroy the mvc objects&lt;br /&gt;
 APP:destroy()&lt;br /&gt;
 MVC:destroy()&lt;br /&gt;
&lt;br /&gt;
This application loads the &amp;quot;mvc.lua&amp;quot; framework, creates an mvc &amp;quot;object&amp;quot; named &amp;quot;MVC&amp;quot;, then reads the helloworld.conf file to find out where the app dir is (helloworld/app/).  It then loads the &#039;&#039;&#039;app-controller.lua&#039;&#039;&#039; into a new &amp;quot;application level&amp;quot; object named &#039;&#039;&#039;APP&#039;&#039;&#039;.  Finally, it sets the clientdata and dispatches the hostname-controller/model pair.&lt;br /&gt;
&lt;br /&gt;
5.  Test the application:&lt;br /&gt;
 # hostname&lt;br /&gt;
 Alpine&lt;br /&gt;
 # lua helloworld.lua no-such-function foo&lt;br /&gt;
 The following unhandled application error occured:&lt;br /&gt;
 &lt;br /&gt;
 controller: &amp;quot;hostname&amp;quot; does not have a &amp;quot;no-such-function&amp;quot; action.&lt;br /&gt;
 # hostname&lt;br /&gt;
 Alpine&lt;br /&gt;
&lt;br /&gt;
 # hostname&lt;br /&gt;
 Alpine&lt;br /&gt;
 # lua helloworld.lua update Alline&lt;br /&gt;
 Your controller and application did not specify a view resolver.&lt;br /&gt;
 The MVC framework has no view available. sorry.&lt;br /&gt;
 # hostname&lt;br /&gt;
 Alline&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Note in the second case the hostname &#039;&#039;was&#039;&#039; changed, although the application does not know how to report success.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Create a view resolver and view formatter ==&lt;br /&gt;
&lt;br /&gt;
The view resolver is a function that returns a function that processes the view. The returned function receives input from the controller and generates the output to be displayed.&lt;br /&gt;
&lt;br /&gt;
We will build a very simple view resolver and view processor for our application. Add this to the end of &#039;&#039;&#039;helloworld/app/hostname-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 local private = {} &lt;br /&gt;
 &lt;br /&gt;
 private.view = function (f)&lt;br /&gt;
        if (f.msg) then&lt;br /&gt;
                print( (f.value or &amp;quot;&amp;quot;) .. &amp;quot; is not a valid hostname &amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                print (&amp;quot;Hostname is currently &amp;quot; .. f.value )&lt;br /&gt;
        end&lt;br /&gt;
 end &lt;br /&gt;
 &lt;br /&gt;
 view_resolver = function (self)&lt;br /&gt;
         return private.view&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Now we can test:&lt;br /&gt;
&lt;br /&gt;
 # lua helloworld.lua update &amp;quot;1 2 3&amp;quot;&lt;br /&gt;
    1 2 3 is not a valid hostname &lt;br /&gt;
 # lua helloworld.lua update        &lt;br /&gt;
    is not a valid hostname &lt;br /&gt;
 # lua helloworld.lua update Alpine&lt;br /&gt;
   Hostname is currently Alpine&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
But we have two problems:&lt;br /&gt;
&lt;br /&gt;
1. We now have to make a view resolver and view function for every controller.  If we add a &#039;&#039;date&#039;&#039; setting controller, we&#039;ll have to make a view resolver and view function for it, and so on.&lt;br /&gt;
&lt;br /&gt;
2. Perhaps more importantly, view_resolver is now an &amp;quot;action&amp;quot; in our appliction.  Recall that invalid actions are captured, but try this:&lt;br /&gt;
&lt;br /&gt;
  # lua helloworld.lua no_such_action Alpine&lt;br /&gt;
     The following unhandled application error occured:&lt;br /&gt;
   &lt;br /&gt;
     controller: &amp;quot;hostname&amp;quot; does not have a &amp;quot;no_such_action&amp;quot; action.&lt;br /&gt;
  &lt;br /&gt;
  # lua helloworld.lua view_resolver Alpine &lt;br /&gt;
     The following unhandled application error occured:&lt;br /&gt;
   &lt;br /&gt;
     ./helloworld/app/hostname-controller.lua:27: attempt to index local &#039;f&#039; (a function value)&lt;br /&gt;
     stack traceback:&lt;br /&gt;
        ./helloworld/app/hostname-controller.lua:27: in function &#039;viewfunc&#039;&lt;br /&gt;
        ./mvc.lua:139: in function &amp;lt;./mvc.lua:90&amp;gt;&lt;br /&gt;
        [C]: in function &#039;xpcall&#039;&lt;br /&gt;
        ./mvc.lua:90: in function &#039;dispatch&#039;&lt;br /&gt;
        helloworld.lua:23: in main chunk&lt;br /&gt;
        [C]: ?&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Because view_resolver is an action in the worker table, the &#039;&#039;&#039;mvc.lua&#039;&#039;&#039; runs it; but it returns a function, not a table, and causes an unhandled exception in the view.&lt;br /&gt;
&lt;br /&gt;
== Move the view resolver to the application level ==&lt;br /&gt;
&lt;br /&gt;
The solution to both problems is to move the view resolver and the view function out of the controller&#039;s worker table, into the next higher level, in this case, the application&#039;s worker table:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
1. Delete the private.view and view_resolver functions from &#039;&#039;&#039;helloworld/app/hostname-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
2. Add the following to &#039;&#039;&#039;helloworld/app/app-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
 local private = {}&lt;br /&gt;
 &lt;br /&gt;
 private.view = function ( controller, action, viewtable )&lt;br /&gt;
         io.write(string.format(&amp;quot;Controller: %s  Action: %s\n&amp;quot;,&lt;br /&gt;
                 controller or &amp;quot;&amp;quot;, action or &amp;quot;&amp;quot;))&lt;br /&gt;
         io.write (&amp;quot;Returned a table with the following values:\n&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
         for k,v in pairs(viewtable) do&lt;br /&gt;
                 io.write(string.format(&amp;quot;%s\t%s\n&amp;quot;, k, v))&lt;br /&gt;
         end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 view_resolver = function (self)&lt;br /&gt;
         return function (viewtable)&lt;br /&gt;
                 return private.view (self.conf.controller, self.conf.action, viewtable)&lt;br /&gt;
         end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This creates a more &amp;quot;generic&amp;quot; view, but one that will work for any controller - not just &#039;&#039;&#039;hostname&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Now things work as they should:&lt;br /&gt;
&lt;br /&gt;
 # lua helloworld.lua update &amp;quot;one two&amp;quot;&lt;br /&gt;
   Controller: hostname  Action: update&lt;br /&gt;
   Returned a table with the following values:&lt;br /&gt;
   value   one two&lt;br /&gt;
   type    string&lt;br /&gt;
   msg     Hostname can contain alphanumerics only&lt;br /&gt;
 &lt;br /&gt;
 # lua helloworld.lua view_resolver &amp;quot;Alpine&amp;quot;&lt;br /&gt;
   The following unhandled application error occured:&lt;br /&gt;
   &lt;br /&gt;
   controller: &amp;quot;hostname&amp;quot; does not have a &amp;quot;view_resolver&amp;quot; action.&lt;br /&gt;
&lt;br /&gt;
= mvc load &amp;amp; exec special functions =&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;mvc.lua&#039;&#039;&#039; module has a provision for executing code on module load, prior to executing the controller&#039;s action, just after executing the controller&#039;s action, and on module unload.&lt;br /&gt;
&lt;br /&gt;
This is done with the mvc table in the controller.  To demonstrate, let&#039;s add a few functions to &#039;&#039;&#039;helloworld/app/app-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
 mvc = {}&lt;br /&gt;
 mvc.on_load = function (self, parent)&lt;br /&gt;
         print (&amp;quot;This is the app controller&#039;s on_load function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 mvc.pre_exec = function (self)&lt;br /&gt;
         print (&amp;quot;This is the app controller&#039;s pre_exec function&amp;quot;)&lt;br /&gt;
 end &lt;br /&gt;
 &lt;br /&gt;
 mvc.post_exec = function (self)&lt;br /&gt;
         print (&amp;quot;This is the app controller&#039;s post_exec function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
 mvc.on_unload = function (self)&lt;br /&gt;
         print (&amp;quot;This is the app controller&#039;s on_unload function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
Now running our script shows when the functions get called:&lt;br /&gt;
&lt;br /&gt;
 # lua helloworld.lua update &amp;quot;Alpine&amp;quot;&lt;br /&gt;
   This is the app controller&#039;s on_load function&lt;br /&gt;
   This is the app controller&#039;s pre_exec function&lt;br /&gt;
   This is the app controller&#039;s post_exec function&lt;br /&gt;
   Controller: hostname  Action: update&lt;br /&gt;
   Returned a table with the following values:&lt;br /&gt;
   value   Alpine&lt;br /&gt;
   type    string&lt;br /&gt;
   This is the app controller&#039;s on_unload function&lt;br /&gt;
&lt;br /&gt;
We can add mvc functions to a specific controller, as well.  Add this to &#039;&#039;&#039;helloworld/app/hostname-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
 mvc = {}&lt;br /&gt;
 mvc.on_load = function (self, parent)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s on_load function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 mvc.pre_exec = function (self)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s pre_exec function&amp;quot;)&lt;br /&gt;
 end &lt;br /&gt;
 &lt;br /&gt;
 mvc.post_exec = function (self)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s post_exec function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 mvc.on_unload = function (self)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s on_unload function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
And this happens:&lt;br /&gt;
&lt;br /&gt;
 # lua helloworld.lua update &amp;quot;Alpine&amp;quot;&lt;br /&gt;
   This is the app controller&#039;s on_load function&lt;br /&gt;
   This is the hostname controller&#039;s on_load function&lt;br /&gt;
   This is the hostname controller&#039;s pre_exec function&lt;br /&gt;
   This is the hostname controller&#039;s post_exec function&lt;br /&gt;
   This is the hostname controller&#039;s on_unload function&lt;br /&gt;
   Controller: hostname  Action: update&lt;br /&gt;
   Returned a table with the following values:&lt;br /&gt;
   value   Alpine&lt;br /&gt;
   type    string&lt;br /&gt;
   This is the app controller&#039;s on_unload function&lt;br /&gt;
&lt;br /&gt;
Note that both the &#039;&#039;app&#039;&#039; and &#039;&#039;hostname&#039;&#039; &#039;&#039;&#039;on_load&#039;&#039;&#039; and &#039;&#039;&#039;on_unload&#039;&#039;&#039; functions were run, but only the &#039;&#039;hostname&#039;&#039; &#039;&#039;&#039;pre_exec&#039;&#039;&#039; and &#039;&#039;&#039;post_exec&#039;&#039;&#039; functions ran.  This is because the pre and post exec functions are run as part of the &amp;quot;action&amp;quot;, and the &#039;&#039;&#039;dispatch&#039;&#039;&#039; function looks in the lowest-level controller for the pre/post_exec function.  Since &#039;&#039;hostname&#039;&#039; now defines those functions, it runs them.&lt;br /&gt;
&lt;br /&gt;
To run both the &#039;&#039;hostname&#039;&#039; and &#039;&#039;app&#039;&#039; pre_exec function, you must arrange for the &#039;&#039;hostname&#039;&#039; pre_exec function to call it&#039;s parent pre_exec:&lt;br /&gt;
&lt;br /&gt;
 mvc = {}&lt;br /&gt;
 mvc.on_load = function (self, parent)&lt;br /&gt;
        print (&amp;quot;This is the hostname controller&#039;s on_load function&amp;quot;)&lt;br /&gt;
        mvc.parent_pre_exec = parent.worker.mvc.pre_exec&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 mvc.pre_exec = function (self)&lt;br /&gt;
         mvc.parent_pre_exec (self)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s pre_exec function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
[[Category:ACF]] [[Category:Lua]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=ACF_mvc.lua_example&amp;diff=9367</id>
		<title>ACF mvc.lua example</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=ACF_mvc.lua_example&amp;diff=9367"/>
		<updated>2013-09-30T15:15:42Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Update the controller and model to current standards&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Set the hostname with mvc.lua =&lt;br /&gt;
&lt;br /&gt;
In this example we will create a simple hostname-setting command-line application using mvc.lua.  Once the controller/model are built, you can use &#039;&#039;the same code&#039;&#039; to set the hostname via the web with a web-based application controller.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
For this example, we will assume you have root access on the linux box you are running on (preferably an alpine box!)&lt;br /&gt;
&lt;br /&gt;
== Get the mvc.lua module == &lt;br /&gt;
&lt;br /&gt;
Get the mvc.lua module from the git repository.&lt;br /&gt;
&lt;br /&gt;
 wget http://git.alpinelinux.org/cgit/acf-core/plain/lua/mvc.lua&lt;br /&gt;
&lt;br /&gt;
== Create a model and controller ==&lt;br /&gt;
&lt;br /&gt;
Create a file &#039;&#039;&#039;hostname-controller.lua&#039;&#039;&#039;, defining the functions that an &amp;quot;end user&amp;quot; could run. We will only create one action, edithostname, which will be used to read and update the hostname. Since the action makes changes to the system, it naturally takes for form of a &#039;form&#039;:&lt;br /&gt;
&lt;br /&gt;
=== hostname-controller.lua ===&lt;br /&gt;
 -- Controller for editing hostname&lt;br /&gt;
 local mymodule = {} &lt;br /&gt;
 &lt;br /&gt;
 mymodule.edithostname = function (self)&lt;br /&gt;
         return self.handle_form(self, self.model.get_hostname, self.model.update_hostname, self.clientdata, &amp;quot;Edit&amp;quot;, &amp;quot;Edit Hostname&amp;quot;, &amp;quot;Hostname Updated&amp;quot;)&lt;br /&gt;
 end                                   &lt;br /&gt;
 &lt;br /&gt;
 return mymodule&lt;br /&gt;
&lt;br /&gt;
Create a file &#039;&#039;&#039;hostname-model.lua&#039;&#039;&#039;, defining the model functions to get and update the hostname.  We return a cfe table for each function including the form with the one entry for hostname:&lt;br /&gt;
&lt;br /&gt;
=== hostname-model.lua ===&lt;br /&gt;
 -- Model functions for retrieving / setting the hostname&lt;br /&gt;
 local mymodule = {}&lt;br /&gt;
 &lt;br /&gt;
 -- Create a cfe defining the form for editing the hostname and containing the current value&lt;br /&gt;
 mymodule.get_hostname = function(self, clientdata)&lt;br /&gt;
         local retval = {}&lt;br /&gt;
 &lt;br /&gt;
         local f = io.popen (&amp;quot;/bin/hostname&amp;quot;)&lt;br /&gt;
         local n = f:read(&amp;quot;*a&amp;quot;) or &amp;quot;none&amp;quot;&lt;br /&gt;
         f:close()&lt;br /&gt;
         n=string.gsub(n, &amp;quot;\n$&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
         retval.hostname = cfe({ value=n, label=&amp;quot;Hostname&amp;quot; })&lt;br /&gt;
 &lt;br /&gt;
         return cfe({ type=&amp;quot;group&amp;quot;, value=retval, label=&amp;quot;Hostname&amp;quot; })&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 -- Update the hostname from the value contained in the cfe created by get_hostname&lt;br /&gt;
 mymodule.update_hostname = function(self, hostnameform, action)&lt;br /&gt;
         local success = true&lt;br /&gt;
 &lt;br /&gt;
         -- Check to make sure the name is valid&lt;br /&gt;
         if (hostnameform.value.hostname.value == &amp;quot;&amp;quot;) then&lt;br /&gt;
                 success = false&lt;br /&gt;
                 hostnameform.value.hostname.errtxt = &amp;quot;Hostname must not be blank&amp;quot;&lt;br /&gt;
         elseif (#hostnameform.value.hostname.value &amp;gt; 16) then&lt;br /&gt;
                 success = false&lt;br /&gt;
                 hostnameform.value.hostname.errtxt = &amp;quot;Hostname must be less than 16 chars&amp;quot;&lt;br /&gt;
         elseif (string.find(hostnameform.value.hostname.value, &amp;quot;[^%w%_%-]&amp;quot;)) then&lt;br /&gt;
                 success = false&lt;br /&gt;
                 hostnameform.value.hostname.errtxt = &amp;quot;Hostname can contain alphanumerics only&amp;quot;&lt;br /&gt;
         end&lt;br /&gt;
 &lt;br /&gt;
         -- If it is, set the hostname&lt;br /&gt;
         if ( success ) then&lt;br /&gt;
                 local f = io.open(&amp;quot;/etc/hostname&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
                 if f then&lt;br /&gt;
                         f:write(hostnameform.value.hostname.value .. &amp;quot;\n&amp;quot;)&lt;br /&gt;
                         f:close()&lt;br /&gt;
                 end&lt;br /&gt;
                 f = io.popen (&amp;quot;/bin/hostname -F /etc/hostname&amp;quot;)&lt;br /&gt;
                 f:close()&lt;br /&gt;
         else&lt;br /&gt;
                 hostnameform.errtxt = &amp;quot;Failed to update hostname&amp;quot;&lt;br /&gt;
         end&lt;br /&gt;
 &lt;br /&gt;
         return hostnameform&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 return mymodule&lt;br /&gt;
&lt;br /&gt;
== Optionally test the model code (without mvc.lua)  ==&lt;br /&gt;
&lt;br /&gt;
If you want, you can create a &#039;&#039;&#039;test.lua&#039;&#039;&#039; script to validate the model code works on its own:&lt;br /&gt;
&lt;br /&gt;
=== test.lua ===&lt;br /&gt;
 require(&amp;quot;mvc&amp;quot;) -- Needed for cfe function definition&lt;br /&gt;
 m=require(&amp;quot;hostname-model&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 local form = m.get_hostname()&lt;br /&gt;
 form.value.hostname.value = arg[1] or &amp;quot;&amp;quot;&lt;br /&gt;
 form = m.update_hostname(nil, form)&lt;br /&gt;
 &lt;br /&gt;
 if form.errtxt then&lt;br /&gt;
         print(&amp;quot;FAILED: &amp;quot;..form.value.hostname.errtxt or form.errtxt)&lt;br /&gt;
 end&lt;br /&gt;
 form = m.get_hostname()&lt;br /&gt;
 print(form.value.hostname.value)&lt;br /&gt;
&lt;br /&gt;
You can then test this with:&lt;br /&gt;
&lt;br /&gt;
 #lua test.lua &amp;quot;Alpine&amp;quot;&lt;br /&gt;
  Alpine&lt;br /&gt;
&lt;br /&gt;
 #lua test.lua &amp;quot;Invalid Name&amp;quot;&lt;br /&gt;
  FAILED: Hostname can contain alphanumerics only&lt;br /&gt;
  Alpine&lt;br /&gt;
&lt;br /&gt;
== Make an MVC based application ==&lt;br /&gt;
&lt;br /&gt;
To make the model and and controller work within the mvc.lua framework, we must do serveral things.   &lt;br /&gt;
&lt;br /&gt;
1. Create a configuration file.   We&#039;ll call the application &#039;&#039;helloworld&#039;&#039;, so edit helloworld.conf and add:&lt;br /&gt;
&lt;br /&gt;
 appdir=helloworld/app/&lt;br /&gt;
&lt;br /&gt;
2. Move the model and controller to the helloworld app directory:&lt;br /&gt;
&lt;br /&gt;
 mkdir -p helloworld/app&lt;br /&gt;
 mv hostname-*.lua helloworld/app&lt;br /&gt;
&lt;br /&gt;
3. Create an application level controller in the helloworld/app directory, named &#039;&#039;&#039;helloworld/app/app-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
 module ( ..., package.seeall)&lt;br /&gt;
 -- application specific functions will go here&lt;br /&gt;
&lt;br /&gt;
Nothing else needs to go in this controller for now.&lt;br /&gt;
&lt;br /&gt;
4. Create a dispatch wrapper program, named &#039;&#039;&#039;helloworld.lua&#039;&#039;&#039; in the current directory:&lt;br /&gt;
&lt;br /&gt;
 -- Simple CLI based mvc application&lt;br /&gt;
 &lt;br /&gt;
 -- this is to get around having to store&lt;br /&gt;
 -- the config file in /etc/helloworld/helloworld.conf&lt;br /&gt;
 ENV={}&lt;br /&gt;
 ENV.HOME=&amp;quot;.&amp;quot; &lt;br /&gt;
 &lt;br /&gt;
 -- load the module&lt;br /&gt;
 require(&amp;quot;mvc&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 -- create an new &amp;quot;mvc object&amp;quot;&lt;br /&gt;
 MVC=mvc:new()&lt;br /&gt;
 &lt;br /&gt;
 -- load the config file so we can find the appdir&lt;br /&gt;
 MVC:read_config(&amp;quot;helloworld&amp;quot;) &lt;br /&gt;
 &lt;br /&gt;
 -- create an application container&lt;br /&gt;
 APP=MVC:new(&amp;quot;app&amp;quot;)&lt;br /&gt;
  &lt;br /&gt;
 -- dispatch the request&lt;br /&gt;
 APP.clientdata.hostname=arg[2]&lt;br /&gt;
 APP:dispatch( &amp;quot;&amp;quot;, &amp;quot;hostname&amp;quot;, (arg[1] or &amp;quot;&amp;quot;))&lt;br /&gt;
&lt;br /&gt;
 -- destroy the mvc objects&lt;br /&gt;
 APP:destroy()&lt;br /&gt;
 MVC:destroy()&lt;br /&gt;
&lt;br /&gt;
This application loads the &amp;quot;mvc.lua&amp;quot; framework, creates an mvc &amp;quot;object&amp;quot; named &amp;quot;MVC&amp;quot;, then reads the helloworld.conf file to find out where the app dir is (helloworld/app/).  It then loads the &#039;&#039;&#039;app-controller.lua&#039;&#039;&#039; into a new &amp;quot;application level&amp;quot; object named &#039;&#039;&#039;APP&#039;&#039;&#039;.  Finally, it sets the clientdata and dispatches the hostname-controller/model pair.&lt;br /&gt;
&lt;br /&gt;
5.  Test the application:&lt;br /&gt;
 # hostname&lt;br /&gt;
 Alpine&lt;br /&gt;
 # lua helloworld.lua no-such-function foo&lt;br /&gt;
 The following unhandled application error occured:&lt;br /&gt;
 &lt;br /&gt;
 controller: &amp;quot;hostname&amp;quot; does not have a &amp;quot;no-such-function&amp;quot; action.&lt;br /&gt;
 # hostname&lt;br /&gt;
 Alpine&lt;br /&gt;
&lt;br /&gt;
 # hostname&lt;br /&gt;
 Alpine&lt;br /&gt;
 # lua helloworld.lua update Alline&lt;br /&gt;
 Your controller and application did not specify a view resolver.&lt;br /&gt;
 The MVC framework has no view available. sorry.&lt;br /&gt;
 # hostname&lt;br /&gt;
 Alline&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Note in the second case the hostname &#039;&#039;was&#039;&#039; changed, although the application does not know how to report success.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Create a view resolver and view formatter ==&lt;br /&gt;
&lt;br /&gt;
The view resolver is a function that returns a function that processes the view. The returned function receives input from the controller and generates the output to be displayed.&lt;br /&gt;
&lt;br /&gt;
We will build a very simple view resolver and view processor for our application. Add this to the end of &#039;&#039;&#039;helloworld/app/hostname-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 local private = {} &lt;br /&gt;
 &lt;br /&gt;
 private.view = function (f)&lt;br /&gt;
        if (f.msg) then&lt;br /&gt;
                print( (f.value or &amp;quot;&amp;quot;) .. &amp;quot; is not a valid hostname &amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                print (&amp;quot;Hostname is currently &amp;quot; .. f.value )&lt;br /&gt;
        end&lt;br /&gt;
 end &lt;br /&gt;
 &lt;br /&gt;
 view_resolver = function (self)&lt;br /&gt;
         return private.view&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Now we can test:&lt;br /&gt;
&lt;br /&gt;
 # lua helloworld.lua update &amp;quot;1 2 3&amp;quot;&lt;br /&gt;
    1 2 3 is not a valid hostname &lt;br /&gt;
 # lua helloworld.lua update        &lt;br /&gt;
    is not a valid hostname &lt;br /&gt;
 # lua helloworld.lua update Alpine&lt;br /&gt;
   Hostname is currently Alpine&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
But we have two problems:&lt;br /&gt;
&lt;br /&gt;
1. We now have to make a view resolver and view function for every controller.  If we add a &#039;&#039;date&#039;&#039; setting controller, we&#039;ll have to make a view resolver and view function for it, and so on.&lt;br /&gt;
&lt;br /&gt;
2. Perhaps more importantly, view_resolver is now an &amp;quot;action&amp;quot; in our appliction.  Recall that invalid actions are captured, but try this:&lt;br /&gt;
&lt;br /&gt;
  # lua helloworld.lua no_such_action Alpine&lt;br /&gt;
     The following unhandled application error occured:&lt;br /&gt;
   &lt;br /&gt;
     controller: &amp;quot;hostname&amp;quot; does not have a &amp;quot;no_such_action&amp;quot; action.&lt;br /&gt;
  &lt;br /&gt;
  # lua helloworld.lua view_resolver Alpine &lt;br /&gt;
     The following unhandled application error occured:&lt;br /&gt;
   &lt;br /&gt;
     ./helloworld/app/hostname-controller.lua:27: attempt to index local &#039;f&#039; (a function value)&lt;br /&gt;
     stack traceback:&lt;br /&gt;
        ./helloworld/app/hostname-controller.lua:27: in function &#039;viewfunc&#039;&lt;br /&gt;
        ./mvc.lua:139: in function &amp;lt;./mvc.lua:90&amp;gt;&lt;br /&gt;
        [C]: in function &#039;xpcall&#039;&lt;br /&gt;
        ./mvc.lua:90: in function &#039;dispatch&#039;&lt;br /&gt;
        helloworld.lua:23: in main chunk&lt;br /&gt;
        [C]: ?&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Because view_resolver is an action in the worker table, the &#039;&#039;&#039;mvc.lua&#039;&#039;&#039; runs it; but it returns a function, not a table, and causes an unhandled exception in the view.&lt;br /&gt;
&lt;br /&gt;
== Move the view resolver to the application level ==&lt;br /&gt;
&lt;br /&gt;
The solution to both problems is to move the view resolver and the view function out of the controller&#039;s worker table, into the next higher level, in this case, the application&#039;s worker table:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
1. Delete the private.view and view_resolver functions from &#039;&#039;&#039;helloworld/app/hostname-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
2. Add the following to &#039;&#039;&#039;helloworld/app/app-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
 local private = {}&lt;br /&gt;
 &lt;br /&gt;
 private.view = function ( controller, action, viewtable )&lt;br /&gt;
         io.write(string.format(&amp;quot;Controller: %s  Action: %s\n&amp;quot;,&lt;br /&gt;
                 controller or &amp;quot;&amp;quot;, action or &amp;quot;&amp;quot;))&lt;br /&gt;
         io.write (&amp;quot;Returned a table with the following values:\n&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
         for k,v in pairs(viewtable) do&lt;br /&gt;
                 io.write(string.format(&amp;quot;%s\t%s\n&amp;quot;, k, v))&lt;br /&gt;
         end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 view_resolver = function (self)&lt;br /&gt;
         return function (viewtable)&lt;br /&gt;
                 return private.view (self.conf.controller, self.conf.action, viewtable)&lt;br /&gt;
         end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This creates a more &amp;quot;generic&amp;quot; view, but one that will work for any controller - not just &#039;&#039;&#039;hostname&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Now things work as they should:&lt;br /&gt;
&lt;br /&gt;
 # lua helloworld.lua update &amp;quot;one two&amp;quot;&lt;br /&gt;
   Controller: hostname  Action: update&lt;br /&gt;
   Returned a table with the following values:&lt;br /&gt;
   value   one two&lt;br /&gt;
   type    string&lt;br /&gt;
   msg     Hostname can contain alphanumerics only&lt;br /&gt;
 &lt;br /&gt;
 # lua helloworld.lua view_resolver &amp;quot;Alpine&amp;quot;&lt;br /&gt;
   The following unhandled application error occured:&lt;br /&gt;
   &lt;br /&gt;
   controller: &amp;quot;hostname&amp;quot; does not have a &amp;quot;view_resolver&amp;quot; action.&lt;br /&gt;
&lt;br /&gt;
= mvc load &amp;amp; exec special functions =&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;&#039;mvc.lua&#039;&#039;&#039; module has a provision for executing code on module load, prior to executing the controller&#039;s action, just after executing the controller&#039;s action, and on module unload.&lt;br /&gt;
&lt;br /&gt;
This is done with the mvc table in the controller.  To demonstrate, let&#039;s add a few functions to &#039;&#039;&#039;helloworld/app/app-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
 mvc = {}&lt;br /&gt;
 mvc.on_load = function (self, parent)&lt;br /&gt;
         print (&amp;quot;This is the app controller&#039;s on_load function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 mvc.pre_exec = function (self)&lt;br /&gt;
         print (&amp;quot;This is the app controller&#039;s pre_exec function&amp;quot;)&lt;br /&gt;
 end &lt;br /&gt;
 &lt;br /&gt;
 mvc.post_exec = function (self)&lt;br /&gt;
         print (&amp;quot;This is the app controller&#039;s post_exec function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
 mvc.on_unload = function (self)&lt;br /&gt;
         print (&amp;quot;This is the app controller&#039;s on_unload function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
Now running our script shows when the functions get called:&lt;br /&gt;
&lt;br /&gt;
 # lua helloworld.lua update &amp;quot;Alpine&amp;quot;&lt;br /&gt;
   This is the app controller&#039;s on_load function&lt;br /&gt;
   This is the app controller&#039;s pre_exec function&lt;br /&gt;
   This is the app controller&#039;s post_exec function&lt;br /&gt;
   Controller: hostname  Action: update&lt;br /&gt;
   Returned a table with the following values:&lt;br /&gt;
   value   Alpine&lt;br /&gt;
   type    string&lt;br /&gt;
   This is the app controller&#039;s on_unload function&lt;br /&gt;
&lt;br /&gt;
We can add mvc functions to a specific controller, as well.  Add this to &#039;&#039;&#039;helloworld/app/hostname-controller.lua&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
 mvc = {}&lt;br /&gt;
 mvc.on_load = function (self, parent)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s on_load function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 mvc.pre_exec = function (self)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s pre_exec function&amp;quot;)&lt;br /&gt;
 end &lt;br /&gt;
 &lt;br /&gt;
 mvc.post_exec = function (self)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s post_exec function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 mvc.on_unload = function (self)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s on_unload function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
And this happens:&lt;br /&gt;
&lt;br /&gt;
 # lua helloworld.lua update &amp;quot;Alpine&amp;quot;&lt;br /&gt;
   This is the app controller&#039;s on_load function&lt;br /&gt;
   This is the hostname controller&#039;s on_load function&lt;br /&gt;
   This is the hostname controller&#039;s pre_exec function&lt;br /&gt;
   This is the hostname controller&#039;s post_exec function&lt;br /&gt;
   This is the hostname controller&#039;s on_unload function&lt;br /&gt;
   Controller: hostname  Action: update&lt;br /&gt;
   Returned a table with the following values:&lt;br /&gt;
   value   Alpine&lt;br /&gt;
   type    string&lt;br /&gt;
   This is the app controller&#039;s on_unload function&lt;br /&gt;
&lt;br /&gt;
Note that both the &#039;&#039;app&#039;&#039; and &#039;&#039;hostname&#039;&#039; &#039;&#039;&#039;on_load&#039;&#039;&#039; and &#039;&#039;&#039;on_unload&#039;&#039;&#039; functions were run, but only the &#039;&#039;hostname&#039;&#039; &#039;&#039;&#039;pre_exec&#039;&#039;&#039; and &#039;&#039;&#039;post_exec&#039;&#039;&#039; functions ran.  This is because the pre and post exec functions are run as part of the &amp;quot;action&amp;quot;, and the &#039;&#039;&#039;dispatch&#039;&#039;&#039; function looks in the lowest-level controller for the pre/post_exec function.  Since &#039;&#039;hostname&#039;&#039; now defines those functions, it runs them.&lt;br /&gt;
&lt;br /&gt;
To run both the &#039;&#039;hostname&#039;&#039; and &#039;&#039;app&#039;&#039; pre_exec function, you must arrange for the &#039;&#039;hostname&#039;&#039; pre_exec function to call it&#039;s parent pre_exec:&lt;br /&gt;
&lt;br /&gt;
 mvc = {}&lt;br /&gt;
 mvc.on_load = function (self, parent)&lt;br /&gt;
        print (&amp;quot;This is the hostname controller&#039;s on_load function&amp;quot;)&lt;br /&gt;
        mvc.parent_pre_exec = parent.worker.mvc.pre_exec&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 mvc.pre_exec = function (self)&lt;br /&gt;
         mvc.parent_pre_exec (self)&lt;br /&gt;
         print (&amp;quot;This is the hostname controller&#039;s pre_exec function&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
[[Category:ACF]] [[Category:Lua]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
	<entry>
		<id>https://wiki.alpinelinux.org/w/index.php?title=ACF_mvc.lua_reference&amp;diff=9143</id>
		<title>ACF mvc.lua reference</title>
		<link rel="alternate" type="text/html" href="https://wiki.alpinelinux.org/w/index.php?title=ACF_mvc.lua_reference&amp;diff=9143"/>
		<updated>2013-06-29T20:50:12Z</updated>

		<summary type="html">&lt;p&gt;Ttrask: Remove links to deleted images and update to match current code&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== mvc.lua function reference ==&lt;br /&gt;
&lt;br /&gt;
This lua module provides the basics for creating an mvc application.  It is patterned&lt;br /&gt;
loosely after the Ruby on Rails pattern - but much more simplistic.&lt;br /&gt;
&lt;br /&gt;
The general pattern is to use the mvc &#039;&#039;&#039;new&#039;&#039;&#039; function to create a set of tables,&lt;br /&gt;
and then use &#039;&#039;&#039;new&#039;&#039; within those tables to create sub &amp;quot;objects&amp;quot;.   By using&lt;br /&gt;
metatable .__index methods, function references flow up through the parent tables.&lt;br /&gt;
&lt;br /&gt;
=== new( self, modname ) ===&lt;br /&gt;
&lt;br /&gt;
Returns 3 values: an &#039;&#039;mvc&#039;&#039; table and 2 booleans ( true or false if the &#039;&#039;modname&#039;&#039;-controller and &#039;&#039;modname&#039;&#039;-model.lua were loaded)  &lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;mvc&#039;&#039; table contains:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
! table !! used for  !! comments !! .__index points to&lt;br /&gt;
|-&lt;br /&gt;
|conf&lt;br /&gt;
|configuration items&lt;br /&gt;
|only created if a &#039;&#039;conf&#039;&#039; table does not exist in a parent&lt;br /&gt;
| n/a&lt;br /&gt;
|-&lt;br /&gt;
|clientdata&lt;br /&gt;
|data sent from the client&lt;br /&gt;
|only created if a &#039;&#039;clientdata&#039;&#039; table does not exist in a parent&lt;br /&gt;
| n/a&lt;br /&gt;
|-&lt;br /&gt;
|worker&lt;br /&gt;
|the &amp;quot;controller&amp;quot; methods&lt;br /&gt;
|if &#039;&#039;&#039;modname&#039;&#039;&#039; is given, then &#039;&#039;&#039;&#039;&#039;modname&#039;&#039;&#039;-controller.lua&#039;&#039; module is loaded into this table.  Otherwise, an empty table is returned.&lt;br /&gt;
|&#039;&#039;&#039;self&#039;&#039;&#039; (parent mvc object)&lt;br /&gt;
|-&lt;br /&gt;
|worker.mvc&lt;br /&gt;
|special methods run by the mvc dispatch function&lt;br /&gt;
|If the &#039;&#039;&#039;modname&#039;&#039;&#039;-controller.lua module does not initalize a .mvc table, an empty one is created&lt;br /&gt;
|&#039;&#039;&#039;self.mvc&#039;&#039;&#039; (parent mvc object&#039;s mvc table)&lt;br /&gt;
|-&lt;br /&gt;
|model&lt;br /&gt;
|the &amp;quot;model&amp;quot; methods&lt;br /&gt;
|if &#039;&#039;&#039;modname&#039;&#039;&#039; is given, then &#039;&#039;&#039;&#039;&#039;modname&#039;&#039;&#039;-model.lua&#039;&#039; module is loaded into this table.  Otherwise, an empty table is returned.&lt;br /&gt;
|&#039;&#039;&#039;worker&#039;&#039;&#039; (this mvc object&#039;s worker table)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The returned table  has a .__index method that points to &#039;&#039;&#039;worker&#039;&#039;&#039;, so this table can&lt;br /&gt;
inherit values from the parent table.&lt;br /&gt;
&lt;br /&gt;
If the &#039;&#039;&#039;&#039;&#039;modname&#039;&#039;&#039;-controller.lua&#039;&#039; contains a .mvc.on_load function, the function is run before &#039;&#039;new&#039;&#039; returns.&lt;br /&gt;
&lt;br /&gt;
The .__index metamethods mean that this code will set up inheritance as shown in the diagram:&lt;br /&gt;
&lt;br /&gt;
  require(&amp;quot;mvc&amp;quot;)&lt;br /&gt;
  MVC=mvc:new()&lt;br /&gt;
    APP=MVC:new()&lt;br /&gt;
     controller=APP:new()&lt;br /&gt;
       subcontroller=controller:new()&lt;br /&gt;
&lt;br /&gt;
If you try to run &#039;&#039;&#039;subcontroller.model.somefunction()&#039;&#039;&#039;, and it does not exist, the inheritance will look for somefunction() in ...&lt;br /&gt;
&lt;br /&gt;
# subcontroller.worker&lt;br /&gt;
# controller&lt;br /&gt;
# controller.worker&lt;br /&gt;
# APP&lt;br /&gt;
# APP.worker&lt;br /&gt;
# MVC&lt;br /&gt;
# MVC.worker&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This allows, for instance, the application to set a default method that is available to all child controllers.  The reason the model looks to its parent worker table first is that controller methods are usually in the worker table, and models do not normally inherit from each other.&lt;br /&gt;
&lt;br /&gt;
The calling code should be sure to call the &#039;&#039;destroy&#039;&#039; function when done with this object.&lt;br /&gt;
&lt;br /&gt;
=== destroy ( self ) ===&lt;br /&gt;
&lt;br /&gt;
Calls the &#039;&#039;mvc.on_unload&#039;&#039; function, if it exists, to close any resources opened by the object.&lt;br /&gt;
&lt;br /&gt;
=== dispatch ( self, prefix, controller, action, clientdata ) ===&lt;br /&gt;
&lt;br /&gt;
The gateway for executing controller actions in a protected xpcall.&lt;br /&gt;
&lt;br /&gt;
# If no controller specified, use default prefix/controller&lt;br /&gt;
# Creates a &#039;&#039;&#039;self:new ( prefix .. controller)&#039;&#039;&#039; mvc object&lt;br /&gt;
# If no action specified, use controller default_action&lt;br /&gt;
# runs any existing &#039;&#039;worker.mvc.pre_exec&#039;&#039; function&lt;br /&gt;
# runs the &#039;&#039;worker.&#039;&#039;&#039;action&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
# runs any existing &#039;&#039;worker.mvc.post_exec&#039;&#039; function&lt;br /&gt;
# gets the view function from &#039;&#039;view_resolver&#039;&#039;&lt;br /&gt;
# executes the view with the results of the &#039;&#039;worker.&#039;&#039;&#039;action&#039;&#039;&#039;&#039;&#039; &lt;br /&gt;
# and calls &#039;&#039;destroy&#039;&#039; to destroy the mvc object&lt;br /&gt;
&lt;br /&gt;
If an error occurs, an exception_handler function is run.  If possible, the exception handler of the new mvc object is run, otherwise &#039;&#039;self::exception_handler&#039;&#039; function handles the error.  If an exception occurs after creating the new mvc object, &#039;&#039;mvc.on_unload&#039;&#039; is guaranteed to run, but &#039;&#039;mvc.post_exec&#039;&#039; is not.&lt;br /&gt;
&lt;br /&gt;
=== soft_require ( self, modname ) ===&lt;br /&gt;
&lt;br /&gt;
Looks for &#039;&#039;&#039;&#039;&#039;modname&#039;&#039;&#039;.lua&#039;&#039; in &#039;&#039;self.conf.appdir&#039;&#039; and returns the results of a &#039;&#039;require()&#039;&#039;  If the &#039;&#039;&#039;modname&#039;&#039;&#039; does not exist, returns nil.&lt;br /&gt;
&lt;br /&gt;
This function allows modules to be loaded without generating an exception if they do not exist.&lt;br /&gt;
&lt;br /&gt;
=== read_config ( self, modname ) ===&lt;br /&gt;
&lt;br /&gt;
Looks in various places for a &#039;&#039;&#039;&#039;&#039;modname&#039;&#039;&#039;.conf&#039;&#039; file and parses its contents into the &#039;&#039;&#039;self.conf&#039;&#039;&#039; table.&lt;br /&gt;
&lt;br /&gt;
=== parse_path_info ( string ) ===&lt;br /&gt;
&lt;br /&gt;
Returns 3 strings: a prefix, controller, and action.  Given a string in the format of a URI or pathspec, returns the &#039;&#039;basename&#039;&#039; as the action, the last component of the &#039;&#039;dirname&#039;&#039; as the controller, and the&lt;br /&gt;
rest as the prefix.   Missing components are returned as empty strings.&lt;br /&gt;
&lt;br /&gt;
=== find_view ( appdir, prefix, controller, action, viewtype ) ===&lt;br /&gt;
&lt;br /&gt;
Returns a string with the view filename for this combination of prefix/controller/action/viewtype or nil if no view file exists.&lt;br /&gt;
&lt;br /&gt;
=== create_helper_library ( self ) ===&lt;br /&gt;
&lt;br /&gt;
Returns a table of function pointers to be passed to each view.&lt;br /&gt;
&lt;br /&gt;
=== auto_view (viewtable, viewlibrary, pageinfo, session) ===&lt;br /&gt;
&lt;br /&gt;
This functions is used as the view of last resort. If no view file is found, this function is called to display the CFE resulting from the invoked action. The following &#039;viewtype&#039;s are supported:&lt;br /&gt;
&lt;br /&gt;
* html&lt;br /&gt;
* json&lt;br /&gt;
* stream&lt;br /&gt;
* serialized&lt;br /&gt;
&lt;br /&gt;
=== view_resolver ( self ) ===&lt;br /&gt;
&lt;br /&gt;
Returns a function pointer to display the view, a table of functions made available to the view function, a table containing values of interest to the view, and a table containing the session data. The last three return values are designed to be the last three parameters to the view function pointer.&lt;br /&gt;
&lt;br /&gt;
=== soft_traceback ( self, message ) ===&lt;br /&gt;
&lt;br /&gt;
If called with no arguments, returns a &#039;&#039;debug.traceback&#039;&#039;, otherwise returns &amp;quot;message&amp;quot;.   &lt;br /&gt;
&lt;br /&gt;
=== exception_handler ( self, message ) ===&lt;br /&gt;
&lt;br /&gt;
Prints an error message and then re-asserts the exception.  Called if the xpcall in &#039;&#039;dispatch&#039;&#039; has an error.  &lt;br /&gt;
This is the exception_handler of last resort.  The application should provide&lt;br /&gt;
a more robust exception handler.&lt;br /&gt;
&lt;br /&gt;
=== cfe ( table ) ===&lt;br /&gt;
&lt;br /&gt;
Returns a table with &#039;&#039;value&#039;&#039;, &#039;&#039;type&#039;&#039;, and &#039;&#039;label&#039;&#039; values.  If an input table is given in the call, those key/value pairs are added as well.&lt;br /&gt;
Guarantees the view will not get a &amp;quot;nil&amp;quot; on value, type, or label.&lt;br /&gt;
This function is added to the global (_G) environment so it is available to the entire application.&lt;br /&gt;
&lt;br /&gt;
=== logevent ( message ) ===&lt;br /&gt;
&lt;br /&gt;
Logs the message to syslog.&lt;br /&gt;
&lt;br /&gt;
=== handle_clientdata ( form, clientdata ) ===&lt;br /&gt;
&lt;br /&gt;
This is a helper function for controllers to implement forms. It parses the form CFE and applies any changes submitted by the user in clientdata.&lt;br /&gt;
&lt;br /&gt;
=== handle_form ( self, getFunction, setFunction, clientdata, option, label, descr ) ===&lt;br /&gt;
This is a helper function for controllers to implement forms. It calls the getFunction to get the form CFE. If the form is submitted, it will call handle_clientdata and the setFunction to submit the form. The form CFE is returned.&lt;br /&gt;
&lt;br /&gt;
[[Category:ACF]] [[Category:Lua]]&lt;/div&gt;</summary>
		<author><name>Ttrask</name></author>
	</entry>
</feed>