小型办公室服务

出自Alpine Linux

摘要:本文档将概述如何使用 Linux 容器化 (LXC) 为小型远程办公室提供各种网络服务。它旨在作为 DMVPN 辐条节点的补充。

除了 DMVPN 网络提供的办公室之间加密通信外,还将提供以下服务

  • 带有域过滤的 Internet 浏览代理服务器(受保护内部网络上的有线客户端)
  • 用于 wifi 客户端的独立代理
  • SIP 电话系统,包括基于 Web 的配置和基本语音邮件服务

假设使用以下 VLAN 和子网,与 DMVPN 文档相同

接口 描述 子网
bond0.3 管理 10.1.0.129/26
bond0.101 局域网 10.1.0.0/25
bond0.256 来自 ISP1 的互联网 从 ISP 分配
bond0.257 来自 ISP2 的互联网 从 ISP 分配
bond0.620 wifi 代理和 dmvpn 辐条节点之间的传输 10.1.0.252/30
bond0.701 WiFi 客户端(无法访问 DMVPN 网络) 172.17.48.0/24
bond0.1101 语音 10.2.0.0/24


提示: 在编写本文时,构建容器主机框的推荐 Alpine 版本应至少为 2.7.9 64 位。

硬件

对于服务少于 20 人的办公室,以下容器可以轻松运行在低功耗硬件上,例如 Via Nano 1.6Ghz Jetway 板,配备 8GB RAM 和双 500GB SATA 硬盘,以 RAID 1(软件)运行。

设置 LXC 主机

启动 Alpine USB

按照 创建可引导设备 中的说明操作。

Alpine 设置

setup-alpine

您将看到类似这样的提示... 关于您可以输入内容的建议...
选择键盘布局 [none] 输入适合您的布局
选择变体 输入适合您的布局(如果提示)
输入系统主机名(短格式,例如“foo”)[localhost] 输入主机名,例如 lxc-host
可用接口为:eth0
输入“?”以获得有关桥接、绑定和 vlan 的帮助。
您要初始化哪一个?(或“?”完成)
输入 bond0.3
可用的绑定从属设备为:eth0 eth1
您要将哪些从属设备添加到 bond0?(或“done”)[eth0]
eth0 eth1
bond0 的 IP 地址?(或“dhcp”、“none”、“?”)[dhcp] 按 Enter 键确认“none”
bond0.3 的 IP 地址?(或“dhcp”、“none”、“?”)[dhcp] <%LXCHOST_MANAGEMENT_IP_ADDRESS%>
子网掩码?[255.255.255.0] <%DMVPN_MANAGEMENT_NETMASK%>
网关?(或“none”)[none] <%DMVPN_MANAGEMENT_NET_IP%>
您是否要执行任何手动网络配置?[no] no
DNS 域名?(例如“bar.com”)[] 输入您内网的域名,例如, office.example.net
DNS 名称服务器?[] 8.8.8.8 8.8.4.4 (我们稍后会更改它们)
正在更改 root 密码
新密码
为控制台输入安全密码
重新输入密码 重新输入上述密码
您所在的时区是?(“?”查看列表)[UTC] 按 Enter 键确认“UTC”
HTTP/FTP 代理 URL?(例如“http://proxy:8080”,或“none”)[none] http://<%DMVPN_LAN_IP%>:8080
输入镜像编号 (1-9) 或要添加的 URL(或 r/f/e/done)[f] 选择离您较近的镜像并按 Enter 键
哪个 SSH 服务器?(“openssh”、“dropbear”或“none”)[openssh] 按 Enter 键确认“openssh”
要运行哪个 NTP 客户端?(“openntpd”、“chrony”或“none”)[chrony] 按 Enter 键确认“chrony”
您想使用哪些磁盘?(或“?”查看帮助或“none”)[none] sda sdb
您想如何使用它们?(“sys”、“data”或“?”查看帮助) data
输入存储配置的位置(“floppy”、“usb”或“none”)[usb] 按 Enter 键确认“usb”
输入 apk 缓存目录(或“?”或“none”)[/media/usb/cache] 按 Enter 键确认“/media/usb/cache”

升级软件包

apk update apk upgrade

保存更改

lbu commit

完成设置并重启

reboot

设置网络

使用您喜欢的编辑器配置 /etc/network/interfaces

内容为/etc/network/interfaces

auto lo iface lo inet loopback auto bond0 iface bond0 inet manual bond-slaves eth0 eth1 bond-mode balance-tlb bond-miimon 100 bond-updelay 500 up ip link set $IFACE up down ip link set $IFACE down auto bond0.3 iface bond0.3 inet static address <%LXCHOST_MANAGEMENT_IP_ADDRESS%> netmask <%DMVPN_MANAGEMENT_NETMASK%> gateway <%DMVPN_MANAGEMENT_IP%> auto bond0.101 iface bond0.101 inet manual up ip link set $IFACE up down ip link set $IFACE down auto bond0.1101 iface bond0.1101 inet manual up ip link set $IFACE up down ip link set $IFACE down auto bond0.701 iface bond0.701 inet manual up ip link set $IFACE up down ip link set $IFACE down

通过重启网络应用更改

rc-service networking restart

启用 IP 转发

echo "1" > /proc/sys/net/ipv4/ip_forward

设置防火墙

apk add acf-awall

使用您喜欢的编辑器,为防火墙创建基本策略

内容为/etc/awall/optional/base.json

{ "description": "管理", "policy": [ { "in": "_fw", "action": "accept" } ], "filter": [ { "out": "_fw", "service": [ "ssh", "https", "ping" ], "action": "accept" } ] }

激活防火墙,并允许 iptables 在启动时自动启动

modprobe ip_tables awall enable base awall activate -f rc-update add iptables

安装 LXC

安装 LXC 和 Bridge 软件包

apk add lxc bridge

使用您喜欢的编辑器配置 /etc/lxc/default.conf

内容为/etc/lxc/default.conf

## 允许同一 VLAN 中的容器相互查看 lxc.network.type = macvlan lxc.network.macvlan.mode = bridge lxc.network.link = bond0.3 lxc.network.name = eth0 ## 限制容器的功能 lxc.cap.drop = sys_admin audit_control audit_write fsetid ipc_lock lxc.cap.drop = ipc_owner lease linux_immutable mac_admin mac_override lxc.cap.drop = mknod setfcap setpcap sys_module sys_nice sys_pacct lxc.cap.drop = sys_ptrace sys_rawio sys_tty_config sys_time

完成安装

lbu ci reboot

安装 Web 代理容器

创建和配置容器

lxc-create -n webproxy -f /etc/lxc/default.conf -t alpine

创建启动脚本

ln -s lxc /etc/init.d/lxc.webproxy

编辑容器的配置文件,位于 /var/lib/lxc/webproxy/config,以反映 Web 代理容器的网络

内容为/var/lib/lxc/webproxy/config

... lxc.network.link = bond0.101 ...

启动容器

rc-service lxc.webproxy start

配置容器自动启动

rc-update add lxc.webproxy

进入 webproxy 容器

lxc-console -n webproxy

以 root 身份登录

注意: 如果需要退出容器,请按 Ctrl+ a + q

删除过时的 /etc/network/interfaces

rm /etc/network/interfaces

创建并配置新的 /etc/network/interfaces,如下所示

内容为/etc/network/interfaces

auto lo iface lo inet loopback auto eth0 iface eth0 inet static address 10.1.0.2 netmask 255.255.255.192 gateway 10.1.0.1

启动网络

rc-service networking start

将规则添加到 DMVPN awall 策略,以允许此代理访问互联网

注意: 这将在 DMVPN awall 配置上进行配置

内容为 /etc/awall/optional/internet-host.json

{ "in": "B", "src": "$10.1.0.2", "out": "E", "action": "accept", },

配置远程管理

apk update setup-sshd -c openssh sed -i "s/.PasswordAuthentication yes/PasswordAuthentication no/" /etc/ssh/sshd_config sed -i "s/.UseDNS yes/UseDNS no/" /etc/ssh/sshd_config

启动 ssh

rc-service sshd start

为容器配置密码

passwd

设置 acf 以进行 Web 管理

setup-acf

设置防火墙

apk add acf-awall

使用您喜欢的编辑器,为防火墙创建策略

内容为/etc/awall/optional/base.json

{ "description": "管理", "policy": [ { "in": "_fw", "action": "accept" } ], "filter": [ { "out": "_fw", "service": [ "ssh", "https", "ping" ], "action": "accept" } ] }

内容为/etc/awall/optional/webproxy.json

{ "description": "Web 代理", "filter": [ { "out": "_fw", "service": [ "http", "http-alt" ], "action": "accept" } ] }

激活防火墙,并允许 iptables 在启动时自动启动

awall enable base awall enable webproxy awall activate -f rc-update add iptables

安装和配置 Squid Web 代理服务

安装所需的软件包

apk add acf-squid squark acf-lighttpd

配置 /etc/squid/squid.conf,替换 <%WEBPROXY_IP_ADDRESS%>、<%HOSTNAME%> 和 <%DOMAIN%>

内容为/etc/squid/squid.conf

#Squid config for webproxy

# This port listens for client requests
http_port 8080

visible_hostname <%HOSTNAME%>.<%DOMAIN%>
cache_mem 8 MB
# If you don't have an HD installed comment the "cache_dir" line below
cache_dir aufs /var/cache/squid 900 16 256

# Even though we only use one proxy, this line is recommended
# More info: http://www.squid-cache.org/Versions/v2/2.7/cfgman/hierarchy_stoplist.html
hierarchy_stoplist cgi-bin ?

# Keep 7 days of access logs
logfile_rotate 7

logformat squark %ts.%03tu %6tr %>a %Ss/%03>Hs %<st %rm %ru %un %Sh/%<A %mt %rG
access_log /var/log/squid/access.log squark
cache_store_log none
pid_filename /var/run/squid.pid

# Make sure client IP is passed to Squark
log_uses_indirect_client on
acl_uses_indirect_client on

# Fix for problems with branch file transfer application
# ignore_expect_100 on (deprecated)

# Debugging Squid, see http://wiki.squid-cache.org/KnowledgeBase/DebugSections
# for more info
# Keep 7 days of cache log
debug_options rotate=7

# Web auditors want to see the full uri, even with the query terms
strip_query_terms off

refresh_pattern ^ftp:		1440	20%	10080
refresh_pattern ^gopher:	1440	0%	1440
refresh_pattern -i (/cgi-bin/|\?) 0	0%	0
refresh_pattern .		0	20%	4320

coredump_dir /var/cache/squid

# 
# Authentication
#


#
# Access Control Lists (ACL's)
#

# Standard ACL settings
acl QUERY urlpath_regex cgi-bin \? asp aspx jsp
acl to_localhost dst <%WEBPROXY_IP_ADDRESS%>
acl SSL_ports port 443 563 8004 9000
acl Safe_ports port 21 70 80 81 210 280 443 563 499 591 777 1024 1022 1025-65535
acl purge method PURGE
acl CONNECT method CONNECT

# Squark filter
url_rewrite_program /usr/bin/squark-filter
url_rewrite_children 1 concurrency=128

# Require authentication
acl userlist  src all

# Definition of zones 
acl Zone_B src <%LAN_SUBNET%>/<%LAN_SLASH_NOTATION%>
#acl Zone_D src <%WiFi_SUBNET%>/<%WiFi_SLASH_NOTATION%>

# Settings migrated from smn
acl Zone_B_AllowedUserDomains     dstdomain "/etc/squid/alloweduserdomains"
acl Zone_B_AllowedServicesHosts   src "/etc/squid/allowedserviceshosts"
acl Zone_B_AllowedServicesDomains dstdomain "/etc/squid/allowedservicesdomains"

# Settings migrated from services
acl AnonBrowsers browser "/etc/squid/anonbrowserlist"
acl AnonIPAddrs src "/etc/squid/anoniplist"
acl AnonDomain url_regex "/etc/squid/anondomainlist"

#
# Access restrictions
#

cache deny QUERY

# Only allow cachemgr access from localhost
http_access allow manager localhost
http_access deny manager

# Only allow purge requests from localhost
http_access allow purge localhost
http_access deny purge

# Deny requests to unknown ports
http_access deny !Safe_ports

# Deny CONNECT to other than SSL ports
http_access deny CONNECT !SSL_ports

# Allow hosts in Zone_B and Zone_C to access hosts listed in
# /etc/squid/alloweduserdomains
http_access allow Zone_B Zone_B_AllowedUserDomains

# Allow hosts listed in /etc/squid/allowedserviceshosts to
# access domains listed in /etc/squid/allowedservicesdomains
http_access allow Zone_B_AllowedServicesHosts Zone_B_AllowedServicesDomains


# Denying all access not explictly allowed
http_access deny all

##Squark URL rewriter
#Prevent squark from filtering itself
url_rewrite_access deny manager
url_rewrite_access deny to_localhost

#We do not want authentication for these sites:
url_rewrite_access deny Zone_B Zone_B_AllowedUserDomains
url_rewrite_access deny Zone_B Zone_B_AllowedServicesDomains

http_reply_access allow all
icp_access allow all

配置 /etc/lighttpd/lighttpd.conf,替换 <%WEBPROXY_IP_ADDRESS%>

内容为/etc/lighttpd/lighttpd.conf

##############################################################################
# Default lighttpd.conf for Gentoo.
# $Header: /var/cvsroot/gentoo-x86/www-servers/lighttpd/files/conf/lighttpd.conf,v 1.3 2005/09/01 14:22:35 ka0ttic Exp $
###############################################################################
var.basedir  = "/var/www/localhost"
var.logdir   = "/var/log/lighttpd"
var.statedir = "/var/lib/lighttpd"

server.modules = (
    "mod_access",
    "mod_accesslog",
    "mod_extforward"
)
include "mime-types.conf" 

include "mod_cgi.conf"

server.username      = "lighttpd"

server.groupname     = "lighttpd"

server.document-root = var.basedir + "/squark"

server.pid-file      = "/var/run/lighttpd.pid"
    
server.errorlog      = var.logdir  + "/error.log"

server.indexfiles    = ("index.php", "index.html",
                                                "index.htm", "default.htm")
server.follow-symlink = "enable"

static-file.exclude-extensions = (".php", ".pl", ".cgi", ".fcgi")

accesslog.filename   = var.logdir + "/access.log"

url.access-deny = ("~", ".inc")

extforward.forwarder = ("<%WEBPROXY_IP_ADDRESS%>" => "trust")

配置 mod_cgi.conf

内容为/etc/lighttpd/mod_cgi.conf

###############################################################################
# mod_cgi.conf
# include'd by lighttpd.conf.
# $Header: /var/cvsroot/gentoo-x86/www-servers/lighttpd/files/conf/mod_cgi.conf,v 1.1 2005/08/27 12:36:13 ka0ttic Exp $
###############################################################################

#
# see cgi.txt for more information on using mod_cgi
#

server.modules += ("mod_cgi")

# NOTE: this requires mod_alias
alias.url = (
     "/cgi-bin/"	    =>	    var.basedir + "/cgi-bin/"
)

#
# Note that you'll also want to enable the
# cgi-bin alias via mod_alias (above).
#

$HTTP["url"] =~ "^/cgi-bin/" {
    # disable directory listings
    dir-listing.activate = "disable"
    # only allow cgi's in this directory
    cgi.assign = (
		".pl"	=>	"/usr/bin/perl",
		".cgi"	=>	"/usr/bin/haserl"
	)
}

将 Squark 网页链接到 Web 服务器主目录

ln -s /usr/share/squark/www/ /var/www/localhost/squark

创建 Squark 组

addgroup squark

将“squid”和“lighttpd”用户设为 squark 组的成员

addgroup squid squark addgroup lighttpd squark

启动 lighttpd,并配置该服务在容器启动时启动

rc-service lighttpd start rc-update add lighttpd


启动 Squid,并配置为在启动时启动

rc-service squid start rc-update add squid

安装 DHCP 和 DNS 服务器容器

创建和配置容器

lxc-create -n dhcpdns -f /etc/lxc/default.conf -t alpine

创建启动脚本

ln -s lxc /etc/init.d/lxc.dhcpdns

编辑容器的配置文件,位于 /var/lib/lxc/dhcpdns/config,以反映 Web 代理容器的网络

内容为/var/lib/lxc/dhcpdns/config

#Management Network Config
lxc.network.type = macvlan
lxc.network.macvlan.mode = bridge
lxc.network.link = bond0.3
lxc.network.name = eth0

#WiFi Network Config
lxc.network.type = macvlan
lxc.network.macvlan.mode = bridge
lxc.network.link = bond0.701
lxc.network.name = eth1

#Voice Network Config
lxc.network.type = macvlan
lxc.network.macvlan.mode = bridge
lxc.network.link = bond0.1101
lxc.network.name = eth2

启动容器

rc-service lxc.dhcpdns start

配置容器自动启动

rc-update add lxc.dhcpdns

进入 dhcpdns 容器

lxc-console -n dhcpdns

以 root 身份登录

注意: 如果需要退出容器,请按 Ctrl+ a + q

删除过时的 /etc/network/interfaces

rm /etc/network/interfaces

创建并配置新的 /etc/network/interfaces,如下所示

内容为/etc/network/interfaces

auto lo iface lo inet loopback #管理 VLAN auto eth0 iface eth0 inet static address 10.1.0.130 netmask 255.255.255.192 #WiFi VLAN auto eth1 iface eth1 inet static address 172.16.48.2 netmask 255.255.255.0 #语音 VLAN auto eth2 iface eth2 inet static address 10.2.0.2 netmask 255.255.255.0 gateway 10.2.0.1 up ip address add 10.2.0.3/24 dev eth0

启动网络

rc-service networking start

配置和启用代理设置

setup-proxy http://10.1.0.2:8080 . /etc/profile.d/proxy.sh

配置远程管理

apk update setup-sshd -c openssh sed -i "s/.PasswordAuthentication yes/PasswordAuthentication no/" /etc/ssh/sshd_config sed -i "s/.UseDNS yes/UseDNS no/" /etc/ssh/sshd_config

启动 ssh

rc-service sshd start

为容器配置密码

passwd

设置 acf 以进行 Web 管理

setup-acf

设置防火墙

apk add acf-awall

使用您喜欢的编辑器,为防火墙创建策略

内容为/etc/awall/optional/base.json

{ "description": "管理", "policy": [ { "in": "_fw", "action": "accept" } ], "filter": [ { "out": "_fw", "service": [ "ssh", "https", "ping" ], "action": "accept" } ] }

内容为/etc/awall/optional/dhcp.json

{ "description": "DHCP", "filter": [ { "out": "_fw", "service": "dhcp", "action": "accept" } ] }

内容为/etc/awall/optional/dns.json

{ "description": "DNS", "filter": [ { "out": "_fw", "service": "dns", "action": "accept" } ] }

激活防火墙,并允许 iptables 在启动时自动启动

awall enable base awall enable dhcp awall enable dns awall activate -f rc-update add iptables

安装和配置 DHCP 和 DNS 服务

安装 dhcpd 软件包

apk add acf-dhcp

创建一个新的 dhcpd.conf 文件

内容为/etc/dhcp/dhcpd.conf

## Common settings
default-lease-time 302400;
max-lease-time 604800;
ddns-update-style none;
log-facility local7;
authoritative;

## Common options
option time-servers 10.2.0.1;
option boot-server code 66 = string;

## Voice
subnet 10.2.0.0 netmask 255.255.255.0
{
   range "10.2.0.20 10.2.0.250";
   option domain-name-servers 10.2.0.2;
   option routers 10.2.0.1;
   option boot-server "http://10.2.0.4";
   option domain-name "office.example.net";
}

## WiFi
subnet 172.17.48.0 netmask 255.255.255.0
{
  range "172.17.48.10 172.17.48.250";
  option routers 172.17.48.1;
  option domain-name-servers 172.17.48.1;  
}

启动 DHCP 服务并添加到默认运行级别

rc-service dhcpd start rc-update add dhcpd

安装 nsd 和 unbound 软件包

apk add unbound

删除 unbound.conf

rm /etc/unbound/unbound.conf

使用您喜欢的编辑器为 unbound 创建新配置

内容为/etc/unbound/unbound.conf

#递归 DNS 配置 server: interface: 10.2.0.2 do-not-query-localhost: no verbosity: 1 do-ip4: yes do-ip6: no do-udp: yes do-tcp: yes do-daemonize: yes access-control: 10.1.0.0/16 allow access-control: 127.0.0.0/8 allow #使用 root.hints 文件确定将 DNS 查询发送到网络外部的位置 root-hints: "/etc/unbound/root.hints" stub-zone: name: "office.example.net" stub-addr: 10.2.0.3 stub-zone: name: "example.net" stub-addr: 172.16.255.1 stub-addr: 172.16.255.2 stub-addr: 172.16.255.3 stub-addr: 172.16.255.4 stub-addr: 172.16.255.5 stub-addr: 172.16.255.7 stub-zone: name: "example2.net" stub-addr: 172.16.255.1 stub-addr: 172.16.255.2 stub-addr: 172.16.255.3 stub-addr: 172.16.255.4 stub-addr: 172.16.255.5 stub-addr: 172.16.255.7

启动 Unbound 并允许容器使用它 {{Cmd|rc-service unbound start rc-update add unbound echo nameserver 10.2.0.2 > /etc/resolv.conf

安装 nsd

apk add nsd

配置 nsd 配置

内容为/etc/nsd/nsd.conf

server: ip-address: 10.2.0.3 port: 53 server-count: 1 ip4-only: yes hide-version: yes identity: "" zonesdir: "/etc/nsd" zone: name: office.example.net zonefile: office.example.net.zone

为 nsd 配置区域文件

内容为/etc/nsd/nsd.conf

$ORIGIN office.example.net. $TTL 86400 @ IN SOA ns admin ( 2013032200 ; 序列号 [yyyymmddnn] 28800 ; 刷新 7200 ; 重试 864000 ; 过期 86400 ; 最小 TTL ) @ NS ns1 ; NSA 服务器 ns1 IN A 10.2.0.3 ;SIP 设备 A 记录 sip IN A 10.2.0.4 media IN A 10.2.0.5 ;NAPTR 记录 sip IN NAPTR 10 1 "s" "SIP+D2U" "" _sip._udp.sip.office.example.net. ;SIP SRV 记录 _sip._udp.sip IN SRV 10 100 5060 sip

检查 nsd 配置并启动服务

nsd-checkconf /etc/nsd/nsd.conf rc-service nsd start rc-update add nsd

安装 SIP 容器

创建和配置容器

lxc-create -n sip -f /etc/lxc/default.conf -t alpine

创建启动脚本

ln -s lxc /etc/init.d/lxc.sip

编辑容器的配置文件,位于 /var/lib/lxc/sip/config,以反映 sip 容器的网络

内容为/var/lib/lxc/sip/config

... lxc.network.link = bond0.1101 ...

启动容器

/etc/iniit.d/lxc.sip

配置容器自动启动

rc-update add lxc.sip

进入 sip 容器

lxc-console -n sip

以 root 身份登录

注意: 如果需要退出容器,请按 Ctrl+ a + q

删除过时的 /etc/network/interfaces

rm /etc/network/interfaces

创建并配置新的 /etc/network/interfaces,如下所示

内容为/etc/network/interfaces

auto lo iface lo inet loopback auto eth0 iface eth0 inet static address 10.2.0.4 netmask 255.255.255.0 gateway 10.2.0.1

启动网络

rc-service networking start

配置和启用代理设置

setup-proxy http://10.1.0.2:8080 . /etc/profile.d/proxy.sh

配置远程管理

apk update setup-sshd -c openssh sed -i "s/.PasswordAuthentication yes/PasswordAuthentication no/" /etc/ssh/sshd_config sed -i "s/.UseDNS yes/UseDNS no/" /etc/ssh/sshd_config

启动 ssh

rc-service sshd start

为容器配置密码

passwd

设置 acf 以进行 Web 管理

setup-acf

设置防火墙

apk add acf-awall

使用您喜欢的编辑器,为防火墙创建策略

内容为/etc/awall/optional/base.json

{ "description": "管理", "policy": [ { "in": "_fw", "action": "accept" } ], "filter": [ { "out": "_fw", "service": [ "ssh", "https", "ping" ], "action": "accept" } ] }

内容为/etc/awall/optional/sip.json

{ "description": "电话系统", "filter": [ { "out": "_fw", "service": [ "sip", "sip-tls" ], "action": "accept", } ] }

内容为/etc/awall/optional/syslog.json

{ "description": "Syslog 服务器", "filter": [ { "out": "_fw", "service": "syslog", "action": "accept" } ] }

激活防火墙,并允许 iptables 在启动时自动启动

awall enable base awall enable sip awall enable syslog awall activate -f rc-update add iptables

安装和配置 Postgresql

安装 postgresql 软件包

apk update apk add acf-postgresql

准备数据库

rc-service postgresql setup

配置 /var/lib/postgresql/9.3/data/postgresql.conf 以将“log_destination”变量设置为显示

内容为/var/lib/postgresql/9.3/data/postresql.conf

.. log_destination ='syslog'

启动数据库并配置 postgresql 以在启动时启动

rc-service postgresql start rc-update add postgresql

安装 acf-provisioning

  • vi /etc/kamailio/kamctlrc
SIP_DOMAIN=sip.office.example.net
DBENGINE=PGSQL
DBHOST=127.0.0.1
DBNAME=openser
DBRWUSER=openser
DBRWPW="openser"
DBROUSER=openserro
DBROPW=openserro
DBROOTUSER="postgres"
  • yes | kamdbctl create openser
  • apk add acf-provisioning lua-socket lua-expat
  • 创建 /etc/provisioning/update_device_params.lua
-- This is the script run after editing device params - basically only worried about extension and password
local functions, params, oldparams = ...

require("posix")
require("luasql.postgres")

local root = "/var/www/provisioning/htdocs/"
local b62 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"

APP.logevent("got to update_device_params script")

local function generatepw()
	-- generate a random 12-character alphanumeric string
	local file = io.open("/dev/urandom")
	local str = ""
	if file == nil then return nil end
	local size = 12
	while (size > 0 ) do
		local offset = (string.byte(file:read(1)) % 62) + 1
		str = str .. string.sub (b62, offset, offset)
		size = size - 1
	end
	return str
end

local function findip(mac)
	if not mac or mac == "" then
		return nil
	end
	local ipaddr = functions.getselectresponse("SELECT ip FROM provisioning_requests WHERE mac~*'"..mac.."'")
	if ipaddr and ipaddr[1] then
		return ipaddr[1].ip
	end
end

local function addfuturenotify(ipaddr, extension)
	local res, err = pcall(function()
		functions.runsqlcommand("DELETE FROM notify WHERE ipaddr='"..ipaddr.."' AND extension='"..extension.."'")
	end)
	if not res and err then
		if string.match(err, "relation \"(%S+)\" does not exist") then
			functions.runsqlcommand("CREATE TABLE notify (ipaddr text, extension text, seasoned boolean DEFAULT false)")
		else
			assert(res, err)
		end
	end
	-- if table missing, create it and delete again
	functions.runsqlcommand("INSERT INTO notify VALUES('"..ipaddr.."', '"..extension.."')")
end

local notify_device = function(mac, extension)
	local ipaddr = findip(mac)
	if ipaddr then
		APP.logevent("Notifying "..ipaddr.." to update for "..(mac or ""))
		os.execute("/etc/provisioning/notify_device "..ipaddr.." "..extension)
		addfuturenotify(ipaddr, extension)
	else
		APP.logevent("Warning - could not find IP address for "..(mac or ""))
	end
end

local kam = APP:new("kamailio/kamailio")

-- A table of devices to notify to update
local devices = {}

-- 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
-- Because this script also handles when device classes change, we have to consider that extensions are added / removed
local regs = {}
local forwarding = {"forwardnoanswerenable", "forwardnoanswer", "forwardbusyenable", "forwardbusy", "forwardallenable", "forwardall"}
local forwardsettings = {} -- Collect forwarding settings
local passwords = {} -- Collect old extension/password pairs
for name,val in pairs(params.value) do
	if not regs[name] and string.match(name, "^reg") then
		regs[name] = true
	end
end
oldparams = oldparams or {value={}}
for name,val in pairs(oldparams.value) do
	if string.match(name, "^reg") then
		if val.value and val.value.extension and val.value.password then
			passwords[val.value.extension.value] = val.value.password.value
		end
		if val.value and val.value.extension then
			local fwd = {}
			for i,f in ipairs(forwarding) do
				if val.value[f] then
					fwd[f] = val.value[f].value
				end
			end
			forwardsettings[val.value.extension.value] = fwd
		end
		if not regs[name] then
			regs[name] = true
		end
	end

end
local extension_id = params.value.reg1.value.extension.param_id
local password_id = params.value.reg1.value.password.param_id
functions.runsqlcommand("BEGIN TRANSACTION")
for name,val in pairs(regs) do
	local new = ""
	local old = ""
	if params.value[name] then new = params.value[name].value.extension.value end
	if oldparams.value[name] then old = oldparams.value[name].value.extension.value end
	if new ~= old then
		-- Extension changed
		-- First, let's remove the stuff for the old extension
		if old ~= "" then
			local others = functions.getselectresponse("SELECT count(*) FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..old.."'")
			if others[1].count == "0" then
				APP.logevent("Removing old registration "..old)
				-- remove the registration
				kam.model.delete_user(old)
			end
		end
		-- Now, add the new extension
		if new ~= "" then
			local pass
			if params.value[name].value.password.value ~= "" and (not oldparams.value[name] or (oldparams.value[name].value.password.value ~= params.value[name].value.password.value)) then
				-- The password parameter was changed and not blank, so use it
				pass = params.value[name].value.password.value
				APP.logevent("Added a new registration "..new.." with specified password "..pass)
				-- There may be other devices with this extension
				local others = functions.getselectresponse("SELECT value FROM provisioning_values WHERE param_id=(SELECT param_id FROM provisioning_params WHERE name='mac') AND device_id IN (SELECT device_id FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."')")
				for i,o in ipairs(others) do
					-- We'll notify whether it changed or not
					devices[o.value] = new
				end
			elseif passwords[new] then
				pass = passwords[new]
				APP.logevent("Added a new registration "..new.." with reused password "..pass)
			else
				local others = functions.getselectresponse("SELECT * FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."' AND device_id!='"..params.value.device_id.value.."'")
				-- If this is a new registration, use a new password
				if #others == 0 then
					pass = generatepw()
					APP.logevent("Added a new registration "..new.." with random password "..pass)
				else
					-- Use the old password
					local p = functions.getselectresponse("SELECT value FROM provisioning_values WHERE param_id='"..password_id.."' AND device_id='"..others[1].device_id.."' AND group_name='"..others[1].group_name.."' LIMIT 1")
					pass = p[1].value
					APP.logevent("Added a new registration "..new.." with reused password "..pass)
				end
			end
			passwords[new] = pass
			params.value[name].value.password.value = pass
			functions.runsqlcommand("DELETE FROM provisioning_values WHERE param_id='"..password_id.."' AND (device_id, group_name) IN (SELECT device_id, group_name FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."')")
			functions.runsqlcommand("INSERT INTO provisioning_values (SELECT device_id, group_name, '"..password_id.."', '"..pass.."' FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."')")
			local u = kam.model.get_user(new)
			if u.value.username.errtxt then
				-- Add this registration to Kam
				local u = kam.model.get_new_user()
				u.value.username.value = new
				u.value.password.value = pass
				u.value.password_confirm.value = pass
				kam.model.create_new_user(u)
			else
				u.value.password.value = pass
				u.value.password_confirm.value = pass
				kam.model.update_user(u)
			end
			-- Let's also look at the forwarding settings
			local change = false
			local supported = false
			local fwd = {}
			for i,f in ipairs(forwarding) do
				if params.value[name].value[f] then
					supported = true
					fwd[f] = params.value[name].value[f].value
					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
						(oldparams.value[name] and oldparams.value[name].value[f] and (params.value[name].value[f].value ~= oldparams.value[name].value[f].value)) then
						change = true
					end
				end
				if change then
					local others = functions.getselectresponse("SELECT value FROM provisioning_values WHERE param_id=(SELECT param_id FROM provisioning_params WHERE name='mac') AND device_id IN (SELECT device_id FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."')")
					for i,o in ipairs(others) do
						-- We'll notify whether it changed or not
						devices[o.value] = new
					end
				end
			end
			if supported then
				if not change and forwardsettings[new] then
					fwd = forwardsettings[new]
				elseif not change then
					-- 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
					local others = functions.getselectresponse("SELECT * FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."' AND device_id!='"..params.value.device_id.value.."'")
					if #others > 0 then
						-- Use the existing settings
						for i,f in ipairs(forwarding) do
							local v = functions.getselectresponse("SELECT value FROM provisioning_values WHERE param_id='"..params.value[name].value[f].param_id.."' AND device_id='"..others[1].device_id.."' AND group_name='"..others[1].group_name.."' LIMIT 1")
							if #v > 0 then
								fwd[f] = v[1].value
							else
								fwd[f] = params.value[name].value[f].default
							end
						end
					end
				end
				if not change then
					for i,f in ipairs(forwarding) do
						params.value[name].value[f].value = fwd[f]
					end
				end
				forwardsettings[new] = fwd
				for i,f in ipairs(forwarding) do
					functions.runsqlcommand("DELETE FROM provisioning_values WHERE param_id='"..params.value[name].value[f].param_id.."' AND (device_id, group_name) IN (SELECT device_id, group_name FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."')")
					if fwd[f] ~= params.value[name].value[f].default then
						functions.runsqlcommand("INSERT INTO provisioning_values (SELECT device_id, group_name, '"..params.value[name].value[f].param_id.."', '"..tostring(fwd[f]).."' FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."')")
					end
				end
			end
		end
	elseif params.value[name] and oldparams.value[name] and params.value[name].value.extension.value ~= "" then
		if params.value[name].value.password.value ~= oldparams.value[name].value.password.value then
			-- Password changed - make any other registrations to this extension also change
			local pass = params.value[name].value.password.value
			if pass and pass ~= "" then
				APP.logevent("Password changed for "..new.." from "..oldparams.value[name].value.password.value.." to "..pass)
			else
				pass = generatepw()
				APP.logevent("Password cleared for "..new..", so set new random password "..pass)
			end
			passwords[new] = pass
			functions.runsqlcommand("DELETE FROM provisioning_values WHERE param_id='"..password_id.."' AND (device_id, group_name) IN (SELECT device_id, group_name FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."')")
			functions.runsqlcommand("INSERT INTO provisioning_values (SELECT device_id, group_name, '"..password_id.."', '"..pass.."' FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."')")
			local u = kam.model.get_user(new)
			u.value.password.value = params.value[name].value.password.value
			u.value.password_confirm.value = params.value[name].value.password.value
			kam.model.update_user(u)
			-- Have to notify those other devices too
			local others = functions.getselectresponse("SELECT value FROM provisioning_values WHERE param_id=(SELECT param_id FROM provisioning_params WHERE name='mac') AND device_id IN (SELECT device_id FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."')")
			for i,o in ipairs(others) do
				devices[o.value] = new
			end
		end
		local change = false
		local fwd = {}
		for i,f in ipairs(forwarding) do
			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
				change = true
				break
			end
		end
		if change then
			-- Forwarding settings changed - make any other registrations to this extension also change
			for i,f in ipairs(forwarding) do
				functions.runsqlcommand("DELETE FROM provisioning_values WHERE param_id='"..params.value[name].value[f].param_id.."' AND (device_id, group_name) IN (SELECT device_id, group_name FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."')")
				if params.value[name].value[f].value ~= params.value[name].value[f].default then
					functions.runsqlcommand("INSERT INTO provisioning_values (SELECT device_id, group_name, '"..params.value[name].value[f].param_id.."', '"..tostring(params.value[name].value[f].value).."' FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."')")
				end
			end
			-- Have to notify those other devices too
			local others = functions.getselectresponse("SELECT value FROM provisioning_values WHERE param_id=(SELECT param_id FROM provisioning_params WHERE name='mac') AND device_id IN (SELECT device_id FROM provisioning_values WHERE param_id='"..extension_id.."' AND value='"..new.."')")
			for i,o in ipairs(others) do
				devices[o.value] = new
			end
		end
	end
end
functions.runsqlcommand("COMMIT")

-- If reg1 or freepstn changed, then need to change free-pstn in kam
-- Since there can be multiple devices with the same reg1, we can't rely on params and oldparams
-- Check both reg's
--APP.logevent(session.serialize("params", params))
local reg = {}
if oldparams.value.reg1 and oldparams.value.reg1.value.extension and oldparams.value.reg1.value.extension.value ~= "" then
	reg[oldparams.value.reg1.value.extension.value] = false
end
if params.value.reg1 and params.value.reg1.value.extension and params.value.reg1.value.extension.value ~= "" then
	local pstn = false
	if params.value.routing and params.value.routing.value.freepstn then
		pstn = params.value.routing.value.freepstn.value
	end
	reg[params.value.reg1.value.extension.value] = pstn
end

APP.logevent("Looking at free-pstn")
for r,f in pairs(reg) do
	-- if we're not sure, check provisioning database
	if not f then
		-- Most devices will have free-pstn due to class-of-service, but can be overridden, so this can get tricky
		-- Check all devices where reg1 extension = r, and see what the freepstn value is for each
		local others = functions.getselectresponse("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 "..
			"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) "..
			"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 ) "..
			"WHERE p.name='freepstn' AND d2t.device_id IN (SELECT device_id FROM provisioning_values WHERE param_id='"..extension_id.."' AND group_name='reg1' AND value='"..r.."')")
		for i,o in ipairs(others) do
			-- Now check the freepstn value for each one
			if o.value == "true" then
				f = true
				break
			end
		end
	end
	-- Now, check the Kamailio group table
	local alreadythere = false
	local entries = kam.model.list_table_entries("grp")
	for i,e in ipairs(entries.value.entries.value) do
		if e.username == r then
			alreadythere = true
			if not f then
				APP.logevent("Removing free-pstn for "..r)
				-- Remove free-pstn from the old extension
				kam.model.delete_table_entry("grp", e.id)
			end
			break
		end
	end
	if f and not alreadythere then
		APP.logevent("Adding free-pstn for "..r)
		-- Add free-pstn to the new extension
		local e = kam.model.get_table_entry("grp")
		e.value.username.value = r
		e.value.domain.value = ""
		e.value.grp.value = "free-pstn"
		e.value.last_modified.value = os.date("%c")
		kam.model.create_table_entry(e)
	end
end

kam:destroy()

-- If the mac address changed for Polycom with valid MAC (not blank or all 0's), we need to move the associated config files
if oldparams.value.device and oldparams.value.device.value.mac and oldparams.value.device.value.mac.value ~= "" and params.value.device and params.value.device.value.mac and params.value.device.value.mac.value ~= oldparams.value.device.value.mac.value then
	if string.match(oldparams.value.device.label, "Polycom") and string.match(oldparams.value.device.value.mac.value, "[1-9A-F]") then
		local deletefiles = true
		if string.match(params.value.device.value.mac.value, "[1-9A-F]") then
			--APP.logevent("Moving files for "..oldparams.value.device.value.mac.value)
			deletefiles = false
		else
			--APP.logevent("Deleting files for "..oldparams.value.device.value.mac.value)
		end
		local path = root.."Polycom/"
		if posix.stat(path, "type") == "directory" then
	                for d in posix.files(path) do
        	                if string.match(d, string.lower(oldparams.value.device.value.mac.value)) and posix.stat(path..d, "type") == "regular" then
                	                local newfile = string.gsub(d, string.lower(oldparams.value.device.value.mac.value), string.lower(params.value.device.value.mac.value))
					if deletefiles then
	                        	        --APP.logevent("deleting "..path..d)
        	                        	os.remove(path..d)
					else
	                        	        --APP.logevent("moving "..path..d.." to "..path..newfile)
        	                        	os.rename(path..d, path..newfile)
					end
				end
                        end
                end
        end
end

-- Then, notify the phone to pull it's config
-- Try to get a valid extension currently on the device
local oldexten = ""
for name,val in pairs(oldparams.value) do
	if string.match(name, "^reg") and val.value.extension and val.value.extension.value ~= "" then
		oldexten = val.value.extension.value
		break
	end
end
if params.value.device and params.value.device.value.mac and params.value.device.value.mac.value ~= "" then
	devices[params.value.device.value.mac.value] = oldexten
end
if oldparams.value.device and oldparams.value.device.value.mac and oldparams.value.device.value.mac.value ~= "" then
	devices[oldparams.value.device.value.mac.value] = oldexten
end

for name,value in pairs(devices) do
	notify_device(name,value)
end
  • 创建 /etc/provisioning/provisioning_db_script
#!/usr/bin/lua

local path = "PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin "
local creation_script = {
	-- Parameters
	"INSERT INTO provisioning_params VALUES(default, 'freepstn', 'boolean', 'Free PSTN Access', '', 'false', '200', '')",

	-- Parameter Groups
	"INSERT INTO provisioning_groups VALUES(default, 'routing', 'Free PSTN Access', '31')",
	"INSERT INTO provisioning_groups VALUES(default, 'routing', 'No Free PSTN Access', '32')",

	-- param_groups_to_params (group_id, param_id, value, editable)
	"INSERT INTO param_groups_to_params VALUES((SELECT group_id FROM provisioning_groups WHERE label='Free PSTN Access'), (SELECT param_id FROM provisioning_params WHERE name='freepstn'), 'true', false)",
	"INSERT INTO param_groups_to_params VALUES((SELECT group_id FROM provisioning_groups WHERE label='No Free PSTN Access'), (SELECT param_id FROM provisioning_params WHERE name='freepstn'), 'false', false)",
	
	-- Classes
	-- provisioning_class_groups (class_group_id, name, label, seq)
	"INSERT INTO provisioning_class_groups VALUES(default, 'routing', 'Routing', '3')",

	-- provisioning_classes (class_id, class_group_id, label, seq)
	"INSERT INTO provisioning_classes VALUES(default, (SELECT class_group_id FROM provisioning_class_groups WHERE name='routing'), 'Free PSTN Access', '1')",
	"INSERT INTO provisioning_classes VALUES(default, (SELECT class_group_id FROM provisioning_class_groups WHERE name='routing'), 'No Free PSTN Access', '2')",

	-- classes_to_param_groups (class_id, group_id)
	"INSERT INTO classes_to_param_groups VALUES((SELECT class_id FROM provisioning_classes WHERE label='Free PSTN Access'), (SELECT group_id FROM provisioning_groups WHERE label='Free PSTN Access'))",
	"INSERT INTO classes_to_param_groups VALUES((SELECT class_id FROM provisioning_classes WHERE label='No Free PSTN Access'), (SELECT group_id FROM provisioning_groups WHERE label='No Free PSTN Access'))",

	-- provisioning_options
	"INSERT INTO provisioning_options VALUES((SELECT param_id FROM provisioning_params WHERE name='polycomringtone'), 'Warble', '15', '15')",
	"INSERT INTO provisioning_options VALUES((SELECT param_id FROM provisioning_params WHERE name='polycomringtone'), 'Analog Ring', '16', '16')",
}


local f = io.popen("/usr/share/acf/www/cgi-bin/cli /provisioning/provisioning/getdevicevalues")
print(f:read("*a"))
f:close()
for i,c in ipairs(creation_script) do
	print(path..'psql -U postgres -c "'..c..'" provisioning 2>&1')
	local f = io.popen(path..'psql -U postgres -c "'..c..'" provisioning 2>&1')
	print(f:read("*a"))
	f:close()
end
  • chmod 755 /etc/provisioning/provisioning_db_script
  • /etc/provisioning/provisioning_db_script
  • echo '* * * * * run-parts /etc/periodic/1min' >> /etc/crontabs/root
  • mkdir /etc/periodic/1min/
  • /etc/periodic/1min/notify_device
#!/usr/bin/lua

-- Load libraries
require("luasql.postgres")

-- Set variables
local DatabaseName = "provisioning"
local DatabaseUser = "postgres"
local DatabasePassword

local path = "PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin "
local env
local con

-- ################################################################################
-- LOCAL FUNCTIONS
local function assert (v, m)
	if not v then
		m = m or "Assertion failed!"
		error(m, 0)
	end
	return v, m
end

-- Escape special characters in sql statements
local escape = function(sql)
	sql = sql or ""
	sql = string.gsub(sql, "'", "''")
	return string.gsub(sql, "\\", "\\\\")
end

local databaseconnect = function()
	if not con then
		-- create environment object
		env = assert (luasql.postgres())
		-- connect to data source
		local err
		con, err = assert(env:connect(DatabaseName, DatabaseUser, DatabasePassword))
		return true
	end
	return false
end

local databasedisconnect = function()
	if env then
		env:close()
		env = nil
	end
	if con then
		con:close()
		con = nil
	end
end

local getselectresponse = function(sql)
	local retval = {}
	local cur = assert (con:execute(sql))
	local row = cur:fetch ({}, "a")
	while row do
		local tmp = {}
		for name,val in pairs(row) do
			tmp[name] = val
		end
		retval[#retval + 1] = tmp
		row = cur:fetch (row, "a")
	end
	cur:close()
	return retval
end

databaseconnect()

-- First, let's notify for the seasoned requests
local reqs = getselectresponse("SELECT * FROM notify WHERE seasoned='true'")
for i,r in ipairs(reqs) do
	os.execute("/etc/provisioning/notify_device "..r.ipaddr.." "..r.extension)
end

-- Then mark any others as seasoned
assert(con:execute("DELETE FROM notify WHERE seasoned='true'"))
assert(con:execute("UPDATE notify SET seasoned='true' WHERE seasoned='false'"))

databasedisconnect()
  • apk add lighttpd
  • rm /etc/lighttpd/lighttpd.conf
  • ln -s /etc/provisioning/lighttpd.sample.conf /etc/lighttpd/lighttpd.conf
  • /etc/lighttpd/mod_cgi.conf
###############################################################################
# mod_cgi.conf
# include'd by lighttpd.conf.
# $Header: /var/cvsroot/gentoo-x86/www-servers/lighttpd/files/conf/mod_cgi.conf,v 1.1 2005/08/27 12:36:13 ka0ttic Exp $
###############################################################################

#
# see cgi.txt for more information on using mod_cgi
#

server.modules += ("mod_cgi")

# NOTE: this requires mod_alias
alias.url = (
     "/cgi-bin/"	    =>	    var.basedir + "/cgi-bin/"
)

#
# Note that you'll also want to enable the
# cgi-bin alias via mod_alias (above).
#

$HTTP["url"] =~ "^/cgi-bin/" {
    # disable directory listings
    dir-listing.activate = "disable"
    # only allow cgi's in this directory
    cgi.assign = (
		".pl"	=>	"/usr/bin/perl",
		".cgi"	=>	"",
		""	=>	""
	)
}

# vim: set ft=conf foldmethod=marker et :
  • rc-service lighttpd start
  • rc-update add lighttpd
  • 自定义配置
Parameter	Default Value
registrar	 IP address or host name of SIP Router(s)
digitmap	 Digit map for your phone system (See Polycom Digit Map Reference)
digitmaptimeout	 Timeout in seconds corresponding to digitmap
sntpserver	 10.2.0.1 (DMVPN spoke node)
timezone	 Timezone information for this location - see below
musiconhold	 SIP uri of the music-on-hold service - moh@media.office.example.net
adminpassword	 Administration password for advanced settings on phone (on-screen and web interface)
  • apk fetch --stdout acf-provisioning-polycom | tar -C / -zx
  • 创建一个测试 Polycom 设备并在语音网络中启动它,以验证它是否工作

安装 Kamailio

  • apk add kamailio kamailio-presence kamailio-pcre kamailio-postgres
  • rc-service kamailio start
  • rc-update add kamailio
  • FUTURE: POST CONFIG THAT USES SUBSCRIBER TABLE FOR AUTH

安装 SIP 媒体容器

创建和配置容器

lxc-create -n sipmedia -f /etc/lxc/default.conf -t alpine

创建启动脚本

ln -s lxc /etc/init.d/lxc.sipmedia

编辑容器的配置文件,位于 /var/lib/lxc/sipmedia/config,以反映 SIP 媒体容器的网络

内容为/var/lib/lxc/sipmedia/config

... lxc.network.link = bond0.1101 ...

启动容器

rc-service lxc.sipmedia

配置容器自动启动

rc-update add lxc.sipmedia

进入 SIP 媒体容器

lxc-console -n sipmedia

以 root 身份登录

注意: 如果需要退出容器,请按 Ctrl+ a + q

删除过时的 /etc/network/interfaces

rm /etc/network/interfaces

创建并配置新的 /etc/network/interfaces,如下所示

内容为/etc/network/interfaces

auto lo iface lo inet loopback auto eth0 iface eth0 inet static address 10.2.0.5 netmask 255.255.255.0 gateway 10.2.0.1

启动网络

rc-service networking start

配置和启用代理设置

setup-proxy http://10.1.0.2:8080 . /etc/profile.d/proxy.sh

配置远程管理

apk update setup-sshd -c openssh sed -i "s/.PasswordAuthentication yes/PasswordAuthentication no/" /etc/ssh/sshd_config sed -i "s/.UseDNS yes/UseDNS no/" /etc/ssh/sshd_config

启动 ssh

rc-service sshd start

为容器配置密码

passwd

设置 acf 以进行 Web 管理

setup-acf

设置防火墙

apk add acf-awall

使用您喜欢的编辑器,为防火墙创建策略

内容为/etc/awall/optional/base.json

{ "description": "管理", "policy": [ { "in": "_fw", "action": "accept" } ], "filter": [ { "out": "_fw", "service": [ "ssh", "https", "ping" ], "action": "accept" } ] }

内容为/etc/awall/optional/sip-track.json

{ "description": "具有 SIP 连接跟踪的电话系统", "filter": [ { "out": "_fw", "service": [ "sip", "sip-tls" ], "action": "accept" } ] }

启用和激活防火墙策略,并配置 iptables 在启动时启动

awall enable base awall enable sip-track awall activate -f rc-update add iptables

安装和配置 Freeswitch

安装软件包

安装 Freeswitch 软件包

配置 /etc/freeswitch/freeswitch.xml

内容为/etc/freeswitch/freeswitch.xml

...
    <extension name="hold_music">                                                                                                                                                                              
      <condition field="destination_number" expression="^moh$">                                                                                                                                                
        <action application="answer"/>                                                                                                                                                                         
        <action application="playback" data="$${hold_music}"/>                                                                                                                                                 
      </condition>                                                                                                                                                                                             
    </extension>
...
<X-PRE-PROCESS cmd="set" data="hold_music=local_stream://default"/>
...
FUTURE: ADD VOICEMAIL

启动 Freeswitch 并配置为在启动时启动

rc-service freeswitch start rc-update add freeswitch

安装 WiFi Web 代理容器

创建和配置容器

lxc-create -n wifi -f /etc/lxc/default.conf -t alpine

创建启动脚本

ln -s lxc /etc/init.d/lxc.wifi

编辑容器的配置文件,位于 /var/lib/lxc/wifi/config,以反映 wifi 容器的网络

内容为/var/lib/lxc/wifi/config

... lxc.network.link = bond0.701 ...

启动容器

/etc/iniit.d/lxc.wifi

配置容器自动启动

rc-update add lxc.wifi

进入 wifi 容器

lxc-console -n wifi

以 root 身份登录

注意: 如果需要退出容器,请按 Ctrl+ a + q

删除过时的 /etc/network/interfaces

rm /etc/network/interfaces

创建并配置新的 /etc/network/interfaces,如下所示

内容为/etc/network/interfaces

auto lo iface lo inet loopback auto eth0 iface eth0 inet static address 172.17.48.1 netmask 255.255.255.0 auto eth1 iface eth1 inet static address 10.1.0.254 netmask 255.255.255.252 gateway 10.1.0.253 auto eth2 iface eth2 inet static address 10.1.0.131 netmask 255.255.255.192

启动网络

rc-service networking start


配置远程管理

apk update setup-sshd -c openssh sed -i "s/.PasswordAuthentication yes/PasswordAuthentication no/" /etc/ssh/sshd_config sed -i "s/.UseDNS yes/UseDNS no/" /etc/ssh/sshd_config

启动 ssh

rc-service sshd start

为容器配置密码

passwd

设置 acf 以进行 Web 管理

setup-acf

设置防火墙

apk add acf-awall

Todo: 需要锁定防火墙规则


安装和配置递归 DNS 服务

安装 unbound 软件包

apk add unbound

使用您喜欢的编辑器配置 /etc/unbound/unbound.conf

内容为/etc/unbound/unobund.conf

server: verbosity: 1 interface: 172.17.48.1 do-ip4: yes do-ip6: no do-udp: yes do-tcp: yes do-daemonize: yes access-control: 172.17.0.0/16 allow access-control: 127.0.0.0/8 allow do-not-query-localhost: no root-hints: "/etc/unbound/root.hints" python: remote-control: control-enable: no

安装和配置代理服务

安装必要的软件包

apk add squid squark lighttpd

使用您首选的编辑器配置 /etc/squid/squid.conf

内容为/etc/squid/squid.conf

#Squid config 

# This port listens for client requests
http_port 172.17.48.1:8080 transparent
http_port 127.0.0.1:8081

visible_hostname wifi.local
cache_mem 8 MB
# If you don't have an HD installed comment the "cache_dir" line below
cache_dir aufs /var/cache/squid 900 16 256

# Even though we only use one proxy, this line is recommended
# More info: http://www.squid-cache.org/Versions/v2/2.7/cfgman/hierarchy_stoplist.html
hierarchy_stoplist cgi-bin ?

# Keep 7 days of access logs
logfile_rotate 7

logformat squark %ts.%03tu %6tr %>a %Ss/%03>Hs %<st %rm %ru %un %Sh/%<A %mt %rG
access_log /var/log/squid/access.log squark
cache_store_log none
pid_filename /var/run/squid.pid

# Make sure client IP is passed to Squark
log_uses_indirect_client on
acl_uses_indirect_client on

# Debugging Squid, see http://wiki.squid-cache.org/KnowledgeBase/DebugSections
# for more info
# Keep 7 days of cache log
debug_options rotate=7

# Web auditors want to see the full uri, even with the query terms
strip_query_terms off

refresh_pattern ^ftp:		1440	20%	10080
refresh_pattern ^gopher:	1440	0%	1440
refresh_pattern -i (/cgi-bin/|\?) 0	0%	0
refresh_pattern .		0	20%	4320

coredump_dir /var/cache/squid

dns_nameservers 172.17.48.1

# 
# Authentication
#
# Squark external acl
#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 <SWITCH_IP> -i <D_VLAN_IF> -v <D_VLAN_ID> -f "%N-%i=%I" -T /etc/squark/topology.conf

#
# Access Control Lists (ACL's)
#

# Standard ACL settings
acl QUERY urlpath_regex cgi-bin \? asp aspx jsp
acl to_localhost dst 172.17.48.1
acl SSL_ports port 443 563 8004 9000
acl Safe_ports port 21 70 80 81 210 280 443 563 499 591 777 1024 1022 1025-65535
acl purge method PURGE
acl CONNECT method CONNECT

#acl SquarkAuth external squark_auth
#acl SquarkSnmpAuthD external squark_snmp_auth_D

# Squark filter
url_rewrite_program /usr/bin/squark-filter
url_rewrite_children 1 concurrency=128

# Require authentication
acl userlist  src all

# Definition of zones
acl Zone_D src 172.17.48.0/24


#
# Access restrictions
#

cache deny QUERY

# Only allow cachemgr access from localhost
http_access allow manager localhost
http_access deny manager

# Only allow purge requests from localhost
http_access allow purge localhost
http_access deny purge

# Deny requests to unknown ports
http_access deny !Safe_ports

# Deny CONNECT to other than SSL ports
http_access deny CONNECT !SSL_ports

# Allow hosts in Zone_D to access the entire Internet
http_access allow Zone_D

# Denying all access not explictly allowed
http_access deny all

##Squark URL rewriter
#Prevent squark from filtering itself
url_rewrite_access deny manager
url_rewrite_access deny to_localhost

#Finally, permit access
url_rewrite_access allow Zone_D

http_reply_access allow all
icp_access allow all

配置 lighttpd

内容为/etc/lighttpd/lighttpd.conf

var.basedir  = "/var/www/localhost"
var.logdir   = "/var/log/lighttpd"
var.statedir = "/var/lib/lighttpd"

server.modules = (
    "mod_access",
    "mod_accesslog",
    "mod_extforward"
)

include "mime-types.conf"
include "mod_cgi.conf"

server.username      = "lighttpd"
server.groupname     = "lighttpd"

server.document-root = var.basedir + "/squark"
server.pid-file      = "/var/run/lighttpd.pid"

server.errorlog      = var.logdir  + "/error.log"

server.indexfiles    = ("index.php", "index.html",
						"index.htm", "default.htm")


server.follow-symlink = "enable"

server.port          = 81
server.bind          = "172.17.48.1"

static-file.exclude-extensions = (".php", ".pl", ".cgi", ".fcgi")

accesslog.filename   = var.logdir + "/access.log"

url.access-deny = ("~", ".inc")

extforward.forwarder = ("172.17.48.1" => "trust")

内容为/etc/lighttpd/mod_cgi.conf

###############################################################################
# mod_cgi.conf
# include'd by lighttpd.conf.
# $Header: /var/cvsroot/gentoo-x86/www-servers/lighttpd/files/conf/mod_cgi.conf,v 1.1 2005/08/27 12:36:13 ka0ttic Exp $
###############################################################################

#
# see cgi.txt for more information on using mod_cgi
#

server.modules += ("mod_cgi")

# NOTE: this requires mod_alias
alias.url = (
     "/cgi-bin/"	    =>	    var.basedir + "/cgi-bin/"
)

#
# Note that you'll also want to enable the
# cgi-bin alias via mod_alias (above).
#

$HTTP["url"] =~ "^/cgi-bin/" {
    # disable directory listings
    dir-listing.activate = "disable"
    # only allow cgi's in this directory
    cgi.assign = (
		".pl"	=>	"/usr/bin/perl",
		".cgi"	=>	"/usr/bin/haserl"
	)
}

# vim: set ft=conf foldmethod=marker et :

将 Squark 网页链接到 Web 服务器主目录

ln -s /usr/share/squark/www/ /var/www/localhost/squark

将“squid”和“lighttpd”用户设为 squark 组的成员

addgroup squid squark addgroup lighttpd squark

启动 lighttpd 并配置 Web 服务在启动时启动

rc-service lighttpd start rc-update add lighttpd

启动 Squid 并配置它在启动时启动

rc-service squid start rc-update add squid

参见