บันทึกการติดตั้งเซิร์ฟเวอร์เดเบียน etch all-in-one แบบลูกทุ่ง

ควรสงสัยไว้ก่อนว่า ข้อเขียนนี้ต้องมีที่ผิดพลาดแน่นอน หากจะทำตาม ควรมีความรู้เรื่องลินุกซ์พอควรที่จะแก้ปัญหาที่เกิดจากการผิดพลาดในข้อเขียนได้

  • เป็นการบันทึกขั้นตอนการทำงานในระหว่างการเปลี่ยนฮาร์ดดิสก์ของเซิร์ฟเวอร์
  • บางรายการใช้แพกเกจที่มีความสามารถมากไป อาจเรียกว่าขี่ช้างจับตั๊กแตน (บางรายการก็น้อยเกินไป)
  • บางรายการมีแพกเกจอยู่แล้ว แต่ดันมาเขียนสคริปต์เอง (เพื่อเพิ่มความยืดหยุ่น)
  • เรื่องความปลอดภัยจัดอยู่ในระดับ "ตายดาบหน้า"
  • ทั้งหมดมาจากเพราะภาษาอังกฤษไม่ดีครับ อ่านเอกสารไม่ค่อยเข้าใจ เลยทำเท่าที่ทำได้ครับ

จะติดตั้งเซิร์ฟเวอร์แบบ all-in-one สำหรับใช้ในหน่วยงานเล็ก ๆ โดย

  • เพื่อให้ประหยัด จะใช้บริการ dynamic dns
  • พยายามให้แพกเกจน้อย ๆ และทันสมัยที่สุดเท่าที่เป็นไปได้
  • แยกไดเรกทอรี่ของข้อมูลออกมา เพื่อสะดวกในการสำรองข้อมูลแบบใช้ rsync โดยจะแยกไปรวมไว้ที่ /sys1 และจะย่อยออกเป็น
    • /sys1/sysb (b คือ backup) สำหรับต้องสำรองข้อมูลทุกวัน เช่น apache2, samba, database-backup, /home และไฟล์คอนฟิกทั้งหลาย
    • /sys1/syst (t คือ temporary) อาจไม่ต้องสำรองข้อมูลก็ได้ แต่แยกไว้ให้สะดวกในการย้ายข้อมูลเมื่อปรับปรุงระบบ เช่น apt-proxy

    (ดังนั้น เวลาติดตั้ง ควรให้มีเนื้อที่มากที่สุด)
    ตั้งชื่อใน /sys1 ให้เลียนแบบกับไดเรกทอรี่จริง เช่น /var/www ต้องสำรองข้อมูลทุกวัน ก็เป็น /sys1/sysb/var/www เป็นต้น

รายการที่ต้องทำคือ

  • เตรียมไดเรกทอรี่ /sys1
  • ติดตั้งแพกเกจเบื้องต้น ssh + screen + vim + less
  • ปรับไฟล์เน็ตเวิร์ก
  • ทำ iptables + portsentry
  • ทำ apt-proxy และ squid3 เพื่อประหยัดแบนด์วิธ
  • ทำเรื่อง dynamic dns client
  • ทำ dns server สำหรับเครือข่ายภายใน โดยใช้ bind9
  • ทำ web server และ database server ใช้ apache2 + mysql + postgresql
  • ทำ mail server ใช้ postfix + Courier
  • ทำ ftp ใช้ proftpd
  • ทำ samba สำหรับแชร์ไฟล์
  • ปรับแต่งขั้นสุดท้าย

สมมุติว่า

  • เครือข่ายภายในมี 2 ชุดคือ 192.168.1.0/24-eth0 และต่อภายนอกผ่านเราเตอร์ด้วย 192.168.5.0/24-eth1
  • เครื่องที่กำลังติดตั้งชื่อ server1.example.com ไอพี 192.168.1.1 และ 192.168.5.3
  • เครื่องนี้อยู่หลังเราเตอร์ซึ่งมีไอพี 192.168.5.1 โดยตั้งให้เราเตอร์ฟอร์เวิรด์ทุกแพ็กเก็ตมาที่ server1 (dmz-host)
  • มีการจดโดเมน 2 ชื่อว่า example.com และ example.org มีไอพีเดียวกัน โดยสมัครเป็นสมาชิก dynamic dns ไว้ที่ zonedit.com และ everydns.net ทั้งสองโดเมน

เตรียมไดเรกทอรี่ /sys1

หลังจากผ่านการติดตั้งแบบ Net Install หรือแบบ debootstrap มาแล้ว
เราจะเตรียมไดเรกทอรี่เก็บค่าต่าง ๆ ไว้ที่ /sys1 เพื่อให้สะดวกในการสำรองข้อมูล
สำหรับการนี้ ควรแยก /sys1 ออกมาเป็นอีกพาร์ติชั่นนึง โดยให้มีเนื้อที่มากที่สุด
# mkdir -p /sys1/{sysb,syst}
# mkdir -p /sys1/sysb/{etc,var}

ย้าย /home
# mv /home /sys1/sysb
# ln -sf /sys1/sysb/home /

ย้าย /usr/local/{bin,sbin}
# mkdir -p /sys1/sysb/usr/local
# mv /usr/local/{bin,sbin} /sys1/sysb/usr/local
# ln -sf /sys1/sysb/usr/local/{bin,sbin} /usr/local

ย้าย crontab
# mkdir -p /sys1/sysb/var/spool/cron
# mv /var/spool/cron/crontabs /sys1/sysb/var/spool/cron
# ln -sf /sys1/sysb/var/spool/cron/crontabs /var/spool/cron

แพกเกจเบื้องต้น

มี ssh screen และ vim
# aptitude install ssh screen vim less pciutils rsync lynx
แต่ง vim เล็กน้อย
# vi /etc/vim/vimrc.local

syntax on
set tabstop=4

ปรับให้ vim เป็นค่าปริยายของ editor และ vi
# update-alternatives --config editor

<--- เลือก vim.basic

# update-alternatives --config vi

<--- เลือก vim.basic
Topic: 

all-in-one 2 (portsentry+iptables+apt-proxy+squid3)

เรื่อง network

ปรับตั้ง interfaces
# vi /etc/network/interfaces

auto eth0
iface eth0 inet static
    address 192.168.1.3
    netmask 255.255.255.0
    network 192.168.1.0
    broadcast 192.168.1.255

auto eth1
iface eth1 inet static
    address 192.168.5.3
    netmask 255.255.255.0
    network 192.168.5.0
    broadcast 192.168.5.255
    gateway 192.168.5.1

เพื่อให้เครือข่ายภายในสามารถออกสู่ภายนอกได้ โดยใช้เซิร์ฟเวอร์ตัวนี้เป็นเกตเวย์ จะตั้งให้ฟอร์เวิร์ดไอพีได้
# vi /etc/sysctl.conf

...
net.ipv4.ip_forward=1

ทำให้มีผลทันที
# echo 1 > /proc/sys/net/ipv4/ip_forward

portsentry + iptables พื้นฐาน

ติดตั้ง
# aptitude install portsentry iptables

สำหรับ portsentry ใช้ค่าปริยายทั้งหมด

สำหรับ iptables จะทำเป็นแบบสคริปต์ ไว้รันเวลาเปิดเครื่อง (จริง ๆ คือ รันตอนอินเทอร์เฟส eth1 เปิดขึ้นมาใช้งาน)
มีการเพิ่มกฎในการบล๊อกไอพีนิดหน่อย ทำให้ลดภาระ web server ในการกรองไอพี

เตรียมไฟล์ข้อมูล
# mkdir -p /sys1/sysb/etc/iptables
# ln -sf /sys1/sysb/etc/iptables /etc
# echo "#BLOCKED IP LIST" >> /etc/iptables/block_ip

สร้างสคริปต์ไว้เรียกตอนเปิด eth1 คือ /usr/local/sbin/d.eth1-iptables
# vi /usr/local/sbin/d.eth1-iptables

#!/bin/bash
# SIMPLE IPTABLES
INTERFACE=eth1
INT_NET=192.168.0.0/16
BLOCK_FILE=/etc/iptables/block_ip

#DELETE OLD RULES
iptables -F > /dev/null
iptables -F -t nat > /dev/null

#NAT
iptables -t nat -A POSTROUTING -o $INTERFACE -j MASQUERADE

#BLOCK SPECIFIC IP
#SORT IP LIST IF DATA CHANGED
touch $BLOCK_FILE "$BLOCK_FILE.bak"
cmp -s "$BLOCK_FILE" "$BLOCK_FILE.bak"
if [ $? -ne 0 ]; then
    TEMP_FILE=/tmp/block_ip
    touch $TEMP_FILE
    mv $BLOCK_FILE "$BLOCK_FILE.bak"
    cat "$BLOCK_FILE.bak" | while read LINE; do
        if [ ${LINE:0:1} == "#" ]; then
            echo $LINE >> $BLOCK_FILE
        else
            echo $LINE >> $TEMP_FILE
        fi
    done
    cat $TEMP_FILE | sort >> $BLOCK_FILE
    rm $TEMP_FILE
fi
#DO BLOCK
cat $BLOCK_FILE | grep -v "#" | cut -d \  -f 1 | while read IP; do
    iptables -A INPUT -s $IP -j DROP
done

#BLOCK ssh INTRUDER BY RATE-LIMIT 4 FOR 600 SECONDS
#FROM http://www.debian-administration.org/articles/187
iptables -I INPUT -p tcp --dport 22 -i $INTERFACE -m state --state NEW -m recent --set
iptables -I INPUT -p tcp --dport 22 -i $INTERFACE -m state --state NEW -m recent --update --seconds 600 --hitcount 4 -j DROP

#FORWARD INTERNAL
iptables -A FORWARD -s $INT_NET -j ACCEPT
iptables -A FORWARD -d $INT_NET -j ACCEPT

#DROP REST
iptables -A FORWARD -s ! $INT_NET -j DROP

# chmod 755 /usr/local/sbin/d.eth1-iptables

สร้างไพล์ข้อมูลไอพีที่ต้องการบล๊อก ชื่อ block_ip เอาไว้ที่ /etc/iptables
# vi /etc/iptables/block_ip

#BLOCKED IP LIST
WWW.XXX.YYY.ZZZ    #COMMENT
...

เปลี่ยนตัวเลขเอาตามจริงนะครับ
เวลาเราจะเพิ่มไอพี ก็มาแก้ไขที่ไฟล์นี้

นำกฎมาใช้ตอนเปิดเครื่องใหม่ ผ่าน /etc/network/interfaces
# vi /etc/network/interfaces

...
auto eth1
iface eth1 inet static
    address 192.168.5.3
    netmask 255.255.255.0
    network 192.168.5.0
    broadcast 192.168.5.255
    gateway 192.168.5.1
    post-up /usr/local/sbin/d.eth1-iptables
    pre-down /sbin/iptables-save > /etc/iptables/rules-backup

(เวลาจะปรับปรุงสคริปต์ เอาเนื้อความจาก /etc/iptables/rules-backup มาใช้อ้างอิงดูในการปรับปรุงได้)

สำหรับครั้งแรก ต้องรัน 1 ครั้ง ครั้งต่อไปไม่ต้องแล้ว จะรันผ่านอินเทอร์เฟส eth1 เอง
# /usr/local/sbin/d.eth1-iptables

apt-proxy

เอาไว้เก็บแพกเกจเดเบียน สำหรับเครือข่ายภายใน อันนี้เนื้อหาไม่ค่อยจำเป็น จะเอาเก็บไว้ที่ /sys1/syst

เตรียมไดเรกทอรี่
# mkdir -p /sys1/syst/var/cache/apt-proxy
# ln -sf /sys1/syst/var/cache/apt-proxy /var/cache

ติดตั้งและปรับแต่ง
# aptitude install apt-proxy
# mv /etc/apt-proxy /sys1/sysb/etc
# ln -sf /sys1/sysb/etc/apt-proxy /etc
# vi /etc/apt-proxy/apt-proxy-v2.conf

...
[debian]
backends =
    http://ftp.debianclub.org/debian
    http://ftp.debian.org/debian
...
[security]
backends =
    ftp://security.debian.org/debian-security
    http://security.debian.org/
...

ลูกข่ายสามารถใช้งานผ่าน apt-proxy โดยเปลี่ยน sources.list ดังนี้

...
#deb    http://ftp.debian.org/debian stable main contrib non-free
deb    http://server1.example.com:9999/debian stable main contrib non-free
...
#deb    http://security.debian.org/ stable/updates main contrib non-free
deb    http://server1.example.com:9999/security stable/updates main contrib non-free
...

แก้ปัญหา apt-proxy ชอบตายบ่อย ๆ ด้วยการให้เริ่ม apt-proxy ใหม่ ตอนเที่ยงคืนทุกวัน
# crontab -e

...
5 0 * * * /etc/init.d/apt-proxy restart >&2
...

squid3

เพื่อให้ประหยัดแบนด์วิธสูงสุด ควรติดตั้งพร๊อกซี่สำหรับภายในด้วย
ติดตั้ง
# aptitude install squid3

ปรับเล็กน้อย
# vi /etc/squid3/squid.conf

...
#http_port 3128
#MOVE 3128 TO 8080 AND MAKE TRANSPARENT PROXY
http_port 8080 transparent
...
acl to_localhost dst 127.0.0.0/8
acl ournetwork src 192.168.0.0/16
acl SSL_ports port 443
...
http_access allow localhost
http_access allow ournetwork
...
Topic: 

all-in-one 3 (dynamic ip script)

ทำเรื่อง dynamic dns client

สมมุติว่าได้สมัครเป็นสมาชิก ddns client ไว้ที่ www.zoneedit.com และ www.everydns.net ไว้เรียบร้อยแล้ว
การนี้เราจะใช้ทั้งคู่ในการเป็น name server ให้เรา กันเหนียวไว้ เวลาอันไหนตาย อีกอันจะได้ทำหน้าที่แทน
จากประวัติพบว่า zoneedit เสถียรและอัปเดตเร็วกว่า เราจึงให้ขึ้นเป็น primary

งานที่ต้องทำคือ

  1. สคริปต์ดูเลขไอพีจากเราเตอร์ หรืออาจให้เริ่มการทำงานเราเตอร์ใหม่
  2. สคริปต์ cron ที่จะตรวจไอพีมาเก็บไว้เป็นระยะ ทุก 5 นาที ถ้าไม่เท่าเก่าจึงสั่งอัปเดต แบบอัปเดตทุกโซน
  3. สคริปต์ cron ที่จะตรวจไอพีซ้ำ จาก dns ภายนอก ทุกครึ่งชั่วโมง ถ้าไอพีไม่ตรงค่อยสั่งอัปเดต เฉพาะโซนที่ไม่ตรง
  4. สคริปต์อัปเดต แบบมีอาร์กิวเมนต์ต่อท้ายว่าจะให้อัปเดตทั้งหมด หรือเฉพาะราย
เตรียมไดเรคทอรี่ก่อน

# mkdir -p /sys1/sysb/etc/ddns
# ln -sf /sys1/sysb/etc/ddns /etc

สคริปต์ดูเลขไอพีจากเราเตอร์

ตั้งชื่อว่า d.router-getip เอาไว้ใน /usr/local/sbin
โดยถ้ามีอาร์กิวเมนต์ต่อท้ายว่า "RESET" จะสั่งให้เราเตอร์เริ่มการทำงานใหม่ สำหรับใช้ในกรณีที่เราเตอร์ฝั่งเราหรือฝั่งไอเอสพีเกิดอาการแฮงก์
(ต้องปรับสคริปต์ตามเราเตอร์ที่ใช้นะครับ)
# vi /usr/local/sbin/d.router-getip

#!/bin/bash
USER="ROUTER_ADMIN"
PASSWORD="ROUTER_PASSWORD"
IPAB="58.9"    #OUR FIRST 2 DIGIT OF IP NUMBER
ROUTERNAME="192.168.5.1"

if [ "$1" == "RESET" ]; then
    ADDRESS="http://$ROUTERNAME/rebootinfo.cgi"
    wget -o /dev/null -O - \
      --http-user="$USER" \
      --http-passwd="$PASSWORD" \
      "$ADDRESS"
    echo "RESET ROUTER"
else
    ADDRESS="http://$ROUTERNAME/wancfg.cmd?action=view"
    IP_ADDR=`wget -o /dev/null -O - \
      --http-user="$USER" \
      --http-passwd="$PASSWORD" \
      "$ADDRESS" \
      | grep $IPAB \
      | cut -d ">" -f 2 \
      | cut -d "<" -f 1`
    echo $IP_ADDR
fi

เปลี่ยนสถานะให้ root ดูได้เท่านั้น เพราะมีรหัสผ่านอยู่ในสคริปต์
# chmod 700 /usr/local/sbin/d.router-getip

สคริปต์ cron ที่จะตรวจไอพีมาเก็บไว้เป็นระยะ ทุก 5 นาที

ตั้งชื่อว่า d.router-cron-checkip เอาไว้ใน /usr/local/sbin
การทำงานคือ ถ้าอ่านค่าไอพีจากเราเตอร์ไม่ได้ (อาจเกิดจากเราเตอร์ของเราหรือของไอเอสพีแฮงก์) จะสั่งรีเซ็ตเราเตอร์ใหม่
# vi /usr/local/bin/d.router-cron-checkip

#!/bin/bash
DATA="/etc/ddns/router-ip"
OLD_IP=`cat $DATA`
CUR_IP=`/usr/local/sbin/d.router-getip`

#SOLVE ROUTER (OR ISP?) IS HUNG
if [ ! $CUR_IP ]; then
    /usr/local/sbin/d.router-checkip RESET
fi
#TEST BETWEEN SAVED-IP AND NEW-READ-IP
if [ "$OLD_IP" != "$CUR_IP" ]; then
    echo $CUR_IP > $DATA    #SAVE NEW IP
    /usr/local/sbin/d.router-reconnect
fi

เปลี่ยนสถานะให้รันได้
# chmod 700 /usr/local/bin/d.router-cron-checkip

ตั้ง cron ทุก 5 นาที (ถ้าคุณภาพสายไม่ดี อาจตั้งค่าให้บ่อยขึ้นก็ได้ครับ)
# crontab -e

...
#CHECK ROUTER IP ADDRESS EVERY 5 MIN
*/5 *   * * *   /usr/local/bin/d.router-cron-checkip
...

ในครั้งแรก ที่ยังไม่มีไฟล์ router-ip ต้องสร้างก่อน เพียงครั้งเดียว
# d.router-getip > /etc/ddns/router-ip

สคริปต์ cron ที่จะตรวจไอพีซ้ำ จาก dns ภายนอก ทุกครึ่งชั่วโมง

สร้างไฟล์ข้อมูลโดเมนเอาไว้ที่ /etc/ddns (เลียนแบบการวางไฟล์จาก apache2)
โดยเราจะเขียนไฟล์คอนฟิกเอาไว้ใน /etc/ddns/ddns-available และเมื่อเสร็จแล้วก็โยงลิงก์เข้าไปที่ /etc/ddns/ddns-enabled
(วิธีนี้ทำให้ปรับแต่งไฟล์ง่าย อันไหนที่ทดสอบแล้วว่ายังไม่เรียบร้อย ก็แค่ลบลิงก์ไป)
สคริปต์จะเข้าไปอ่านไฟล์คอนฟิกจากในไดเรกทอรี่ /etc/ddns/ddns-enabled
# mkdir -p /etc/ddns/{ddns-available,ddns-enabled}

สมมุติว่าเราจดทะเบียนไว้ที่ zoneedit.com และ everydns.net ไว้ 2 โดเมน
คือ example.com และ example.org
ตัวอย่างไฟล์คอนฟิก จะเป็นดังนี้
# vi /etc/ddns/ddns-available/zoneedit

#ZONEEDIT DATA
USER="ZONEEDIT-USER"
PASSWORD="ZONEEDIT-PASSWORD"
#UPDATE_DOMAIN="DOMAIN:NAME-SERVER, ... "       #NO SPACE IN FIELD
UPDATE_DOMAIN="
example.com:ns13.zoneedit.com
example.org:ns2.zoneedit.com"

# vi /etc/ddns/ddns-available/everydns

#EVERYDNS DATA
USER="EVERYDNS-USER"
PASSWORD="EVERYDNS-PASSWORD"
#UPDATE_DOMAIN="DOMAIN:NAME-SERVER, ... "       #NO SPACE IN FIELD
UPDATE_DOMAIN="
example.com:ns2.everydns.net
example.org:ns2.everydns.net"

ต้องระวัง พยายามอย่าพิมพ์ผิด เพราะข้อมูลในนี้คือตัวแปรในสคริปต์โดยตรง ไม่มีการตรวจสอบความถูกต้องจากสคริปต์อีกแล้ว

โยงลิงก์ เพื่อเปิดใช้งาน
# ln -sf /etc/ddns/ddns-available/* /etc/ddns/ddns-enabled

ต่อไปสร้างสคริปต์ชื่อ d.router-cron-checkddns เอาไว้ที่ /usr/local/sbin เพื่อตรวจสอบไอพีของเราจากผู้ให้บริการ
# vi /usr/local/sbin/d.router-cron-checkddns

#!/bin/bash
# CRON SCRIPT FOR TEST IP FROM PROVIDER.
# KNOWN PROVIDER:
#   zoneedit.com 
#   everydns.net
#
# PUT PROVIDERS DATA FILE IN /etc/ddns/ddns-available .
# MAKE LINK TO /etc/ddns/ddns-enabled TO ENABLE.

#DEFAULT DIRECTORY CONTAIN DDNS PROVIDERS DATA
DIR="/etc/ddns/ddns-enabled"

# GLOBAL VAR
CUR_IP=`/usr/local/sbin/d.router-getip`
UPDATE_SCRIPT="/usr/local/sbin/d.router-updatezone"

for PROVIDER in `ls -1 $DIR`; do
    #GET VARIABLE: $USER,$PASSWORD,$UPDATE_DOMAIN
    . $DIR/$PROVIDER
    for i in $UPDATE_DOMAIN; do
        DOMAIN=`echo $i | cut -d: -f1`
        NAME_SERVER=`echo $i | cut -d: -f2`
        TEST_IP=`nslookup $DOMAIN $NAME_SERVER | grep -v "#" | grep Address | cut -d\  -f2`
        if [ "$TEST_IP" != "$CUR_IP" ]; then
            $UPDATE_SCRIPT $PROVIDER
        fi
    done
done

ทำให้รันได้
# chmod 700 /usr/local/sbin/d.router-cron-checkddns

ตั้ง cron ทุก 30 นาที
# crontab -e

...
#CHECK DOMAIN IP ADDRESS EVERY 30 MIN
*/30 *   * * *   /usr/local/sbin/d.router-cron-checkddns
...
สคริปต์อัปเดต

เป็นการสั่งอัปเดต โดยนำค่าจากข้อมูลในไฟล์คอนฟิกข้างบน คือใน /etc/ddns/ddns-enabled มาอัปเดต ชื่อไฟล์คือค่าอาร์กิวเมนต์ของสคริปต์ ยกเว้นถ้าใส่ -a จะหมายถึง all คืออัปเดตทุก ๆ ผู้ให้บริการ

การทำงานของสคริปต์จะแบ่งเป็น 2 จังหวะ คือในครั้งแรกจะอัปเดตรวดเดียวกับทุกโดเมน เมื่อเสร็จเรียบร้อยแล้วจะย้อนกลับมาตรวจสอบอีกครั้งนึงว่า ในแต่ละโดเมนรายงานค่าไอพีถูกต้องหรือไม่ ถ้าไม่ถูกก็จะเว้นไป 5 วินาที แล้วเชื่อมต่อใหม่เป็นจำนวน 10 รอบการทำงาน

สร้างสคริปต์ชื่อ d.router-updatezone เอาไว้ที่ /usr/local/sbin ในการอัปเดตเมื่อไอพีเปลี่ยน
# vi /usr/local/sbin/d.router-updatezone

#!/bin/bash 
# SCRIPT FOR UPDATE DNS RECORD.
# KNOWN PROVIDER:
#   zoneedit.com 
#   everydns.net
#
# PUT PROVIDERS DATA FILE IN /etc/ddns/ddns-available .
# MAKE LINK TO /etc/ddns/ddns-enabled TO ENABLE.
# DATA FILE FORMAT:
#   USER="user"
#   PASSWORD="password"
#   UPDATE_DOMAIN="
#   example.com:ns1.zoneedit.com
#   example.com:ns2.zoneedit.com"

if [ ! $1 ]; then
    PROG=`basename $0`
    echo "Usage: $PROG [-a=all | ddns_provider_file]"
    exit
fi

#DEFAULT DIRECTORY CONTAIN DDNS PROVIDERS DATA
DIR="/etc/ddns/ddns-enabled"
if [ "$1" == "-a" ]; then
    DDNS_ENABLED=`ls -1 $DIR`
else
    DDNS_ENABLED=$1
fi

# GLOBAL VAR
IP_ADDR=`/usr/local/sbin/d.router-getip`

# FIRST FAST UPDATE
fastupdate() {
    ADDRESS=$1
    DOMAIN=$2
    USER=$3
    PASSWORD=$4
    echo "$DOMAIN fast update ..."
    wget -O - --http-user=$USER --http-passwd=$PASSWORD "$ADDRESS"
}

# SECOND RECHECK & UPDATE
recheck() {
    ADDRESS=$1
    DOMAIN=$2
    NAME_SERVER=$3
    USER=$4
    PASSWORD=$5
    echo "$DOMAIN recheck with $NAME_SERVER ..."
    TEST_IP=`nslookup $DOMAIN $NAME_SERVER | grep -v "#" | grep Address | cut -d \  -f 2`
    echo "REAL_IP=$IP_ADDR , TEST_IP=$TEST_IP" 
    I=0
    LOOP=10
    while [ $I -lt $LOOP ] && [ "$IP_ADDR" != "$TEST_IP" ] ; do
        wget -O - --http-user=$USER --http-passwd=$PASSWORD $ADDRESS
        sleep 5
        echo "ROUND=$I , NAME_SERVER=$NAME_SERVER , REAL_IP=$IP_ADDR , TEST_IP=$TEST_IP" 
        TEST_IP=`nslookup $DOMAIN $NAME_SERVER | grep -v "#" | grep Address | cut -d \  -f 2`
        I=$[$I+1]
    done
}

# BEGIN MAIN
#1.FAST UPDATE
for PROVIDER in $DDNS_ENABLED; do
    #GET VARIRABLE $USER,$PASSWORD,$UPDATE_DOMAIN
    . $DIR/$PROVIDER
    for i in $UPDATE_DOMAIN; do
        DOMAIN=`echo $i | cut -d: -f1`
        case $PROVIDER in
        zoneedit)
            ADDRESS="http://dynamic.zoneedit.com/auth/dynamic.html?host=$DOMAIN"
            ;;
        everydns)
            ADDRESS="http://dyn.everydns.net/index.php?ver=0.1&domain=$DOMAIN"
            ;;
        esac
        fastupdate $ADDRESS $DOMAIN $USER $PASSWORD
    done
done

#WAIT 30 SECONDS BEFORE RECHECK
sleep 30

#2.RECHECK
for PROVIDER in $DDNS_ENABLED; do
    #GET VARIABLE $USER,$PASSWORD,$UPDATE_DOMAIN
    . $DIR/$PROVIDER
    for i in $UPDATE_DOMAIN; do
        DOMAIN=`echo $i | cut -d: -f1`
        NAME_SERVER=`echo $i | cut -d: -f2`
        case $PROVIDER in
        zoneedit)
            # NEW wget , USED WHEN dynamic.zoneedit.com IS DOWN OR THIS MACHINE STAY BEHIND FIREWALL
            ADDRESS="http://www.zoneedit.com/auth/dynamic.html?host=$DOMAIN&type=A&dnsto=$IP_ADDR"
            ;;
        everydns)
            # WITH IP, SUITABLE FOR MACHINE BEHIND FIREWALL
            ADDRESS="http://dyn.everydns.net/index.php?ver=0.1&ip=$IP_ADDR&domain=$DOMAIN"
            ;;
        esac
        recheck $ADDRESS $DOMAIN $NAME_SERVER $USER $PASSWORD
    done
done

เปลี่ยนสถานะให้รันได้
# chmod 700 /usr/local/sbin/d.router-updatezone

สคริปต์รวมในการบันทึกไอพีใหม่และสั่งอัปเดต

สคริปต์นี้แทบไม่มีอะไร เพียงแต่ไปเรียกใช้สคริปต์อัปเดตข้างบน เพียงแต่ใส่พารามิเตอร์เป็น -a (อัปเดตทุกผู้ให้บริการ) เท่านั้น
แต่ว่าต้องสร้างสคริปต์นี้ไว้ เพราะเราจะต้องเชื่อมโยงกับการอัปเดต bind9 ที่จะติดตั้งต่อไป

ตั้งชื่อว่า d.router-reconnect เอาไว้ใน /usr/local/sbin
# vi /usr/local/sbin/d.router-reconnect

#!/bin/bash
# UPDATE ALL ENABLED ZONE

#KILL OLD PROCESS
killall d.router-updatezone
#UPDATE ALL ZONE
/usr/local/sbin/d.router-updatezone -a

#-------ADDITIONAL BIND9 SCRIPT:------------------

เสร็จแล้ว

Topic: 

all-in-one 4 (bind9 - internal/external+dynamic update)

ทำ dns server โดยใช้ bind9

จะทำให้สามารถใช้งานได้ทั้งภายใน และภายนอก (ภายนอกไม่ค่อยจำเป็น แต่ติดตั้งไว้เผื่อในอนาคตอาจเพิ่มการ lookup จากเซิร์ฟเวอร์ของเราที่อยู่ภายนอก)
โดยจะแยกไอพีภายในและภายนอกเป็น 2 กลุ่ม
และสร้างกรงขังด้วย (chroot jail)

สมมุติว่า

  • มีโซนภายใน โดเมนหลักเป็น example.com มีชื่อทุกเครื่องในเครือข่าย โดยจะทำ reverse ip ด้วย (คือค้นย้อนกลับจากเลขไอพีได้) และโดเมนรองเป็น example.org มีชื่อเครื่องนี้เครื่องเดียว ไม่ทำค้นย้อน (ทำไม่ได้ เพราะไอพีซ้ำกัน) ทั้งสองโดเมนมีไอพีเป็น 192.168.1.0/24
  • โซนภายนอก โดเมนเป็น example.com และ example.org โดยที่ทุกโดเมนมีไอพีเดียวกัน เพราะถือว่าต่อสาย adsl สายเดียว และแต่ละโดเมนอาจมีหลายชื่อ โดยใช้ CNAME เทียบเอา
    ทุกโดเมนของโซนภายนอก จะไม่ทำค้นย้อน (reverse zone) เพราะเรามีหลายโดเมนแต่มีไอพีเดียว และถึงแม้จะมีโดเมนเดียว ค่าที่โลกภายนอกค้นได้ก็คงไม่ใช่จากเครื่องเราอยู่ดี
  • วงในทำเป็นแบบเปลี่ยนค่าไม่ได้ วงนอกทำเป็นแบบเปลี่ยนค่าได้อย่างง่าย เพื่ออัปเดตตอนสายหลุด

ติดตั้งแพกเกจ แล้วสั่งหยุดการทำงาน
# aptitude install bind9 dnsutils
# /etc/init.d/bind9 stop

เรื่องกรงขัง (chroot jail) จะเก็บไว้ที่ /sys1/sysb/chroot/bind
สร้างไดเรกทอรี่ก่อน แล้วโยงลิงก์ chroot ไปที่ / ให้พิมพ์ง่าย
# mkdir -p /sys1/sysb/chroot/bind
# ln -sf /sys1/sysb/chroot /
# chown root:bind /chroot/bind

แก้ให้อยู่ในกรง
# vi /etc/default/bind9

...
# OPTIONS="-u bind"
OPTIONS="-u bind -t /chroot/bind"
...

แก้ไขผู้ใช้ชื่อ bind ให้ย้ายบ้านไปที่ /chroot/bind แทน
# usermod --home /chroot/bind bind

เตรียมไดเรคทอรี่ย่อยในกรง
# mkdir -p /chroot/bind/{etc,dev,var/{cache,log,run}}
# mkdir -p /chroot/bind/var/cache/bind
# mv /etc/bind /chroot/bind/etc
# ln -sf /chroot/bind/etc/bind /etc
# mknod /chroot/bind/dev/null c 1 3
# mknod /chroot/bind/dev/random c 1 8
# chmod 666 /chroot/bind/dev/{null,random}
# chown -R bind:bind /chroot/bind/var/*

ทำให้ระบบเก็บปูมของ bind (system logging - syslogd)
# vi /etc/default/syslogd

...
# SYSLOGD=""
SYSLOGD="-a /chroot/bind/dev/log"
...

ปรับตั้งระบบปูมของ bind
# vi /etc/bind/named.conf.local

...
logging {
    channel "querylog" { file "/var/log/bind9-query.log"; print-time yes; };
    category queries { querylog; };
};
...

ปรับตั้งประวัติปูมของ bind
# vi /etc/logrotate.d/bind9-query

/chroot/bind/var/log/bind9-query.log {
    weekly
    missingok
    rotate 10
    postrotate
        /etc/init.d/bind9 reload > /dev/null
    endscript
    compress
    notifempty
}

# ln -sf /chroot/bind/var/log/bind9-query.log /var/log/bind9-query.log

แก้ไข options ให้มาเก็บ pid ในกรงขัง
# vi /etc/bind/named.conf.options

...
options {
    directory "/var/cache/bind";
    pid-file    "/var/run/named.pid";
...

เสร็จขั้นต้น ทดสอบระบบครั้งแรก
# /etc/init.d/sysklogd restart
# /etc/init.d/bind9 restart

ต้องไม่มีรายงานข้อผิดพลาด

ถ้าเรียบร้อยก็สั่งหยุดบริการ เพื่อปรับตั้งต่อ
# /etc/init.d/bind9 stop

เครือข่ายภายในและภายนอก

ต่อไปเป็นเรื่องการทำให้รองรับเครือข่ายภายในและภายนอก

สร้างไดเรกทอรี่ขึ้นมารองรับ
# mkdir -p /etc/bind/{internal,external}
# cd /etc/bind

สร้างกฎ acl สำหรับเครือข่ายภายใน ตั้งชื่อว่า acl_internal (นอกจากนั้นจะถือว่าเป็น external ทั้งหมด)
และให้มาอ่านไฟล์คอนฟิกที่เราจะสร้างขึ้นภายหลัง ของโซน example.com และ example.org
# vi named.conf.local

...
acl acl_internal {
#    127.0.0.0/8;
    192.168.0.0/16;
};

include "/etc/bind/allzone.conf";
...

หมายเหตุ - ปกติวง 127.0.0.0/8 ควรจะถึอเป็นวงในด้วย แต่ในที่นี้เราจะตัดออกจาก acl_internal เนื่องจากเราไม่สามารถสั่งอัปเดตไอพีจากวงในได้ เพราะตั้งเป็นแบบเปลี่ยนค่าไม่ได้ ดังนั้นเราจะใช้ localhost ในการอัปเดตไอพีที่ถูกเรียกจากวงนอก จึงต้องเว้นไว้ แต่ใส่คอมเมนต์ไว้เพื่อจะได้ไม่หลงลืมข้อนี้

สร้างไฟล์คอนฟิกของ example.com และ example.org รวมกัน ตั้งชื่อว่า allzone.conf โดยตัดเอาเนื้อไฟล์ใน named.conf ในส่วนของการกำหนดโซนมาใส่ไว้
(ถ้ามีการใช้ view เราต้องใส่การกำหนดโซนไว้ภายใต้ view เท่านั้น ไม่สามารถกำหนดแบบซ้อนได้)

ตัดเนื้อไฟล์จาก named.conf ออก
# vi named.conf

...
//-----8<-------------------------------------------
// prime the server with knowledge of the root servers
zone "." {
    type hint;
    file "/etc/bind/db.root";
};

// be authoritative for the localhost forward and reverse zones, and for
// broadcast zones as per RFC 1912

zone "localhost" {
    type master;
    file "/etc/bind/db.local";
};

zone "127.in-addr.arpa" {
    type master;
    file "/etc/bind/db.127";
};

zone "0.in-addr.arpa" {
    type master;
    file "/etc/bind/db.0";
};

zone "255.in-addr.arpa" {
    type master;
    file "/etc/bind/db.255";
};
//------------------------------------------->8-----
...

สร้างไฟล์ allzone.conf ในการกำหนดค่าไฟล์ในการคุมทุกโซน โดยเอาเนื้อจาก named.conf ข้างบนมาใส่ในส่วนของ view internal
# vi allzone.conf

include "/etc/bind/key.conf";

view "internal" {
    match-clients { acl_internal; };

    // ----- PASTE FROM named.conf ----------------------------------------
    // prime the server with knowledge of the root servers
    zone "." {
        type hint;
        file "/etc/bind/db.root";
    };

    // be authoritative for the localhost forward and reverse zones, and for
    // broadcast zones as per RFC 1912

    zone "localhost" {
        type master;
        file "/etc/bind/db.local";
    };

    zone "127.in-addr.arpa" {
        type master;
        file "/etc/bind/db.127";
    };

    zone "0.in-addr.arpa" {
        type master;
        file "/etc/bind/db.0";
    };

    zone "255.in-addr.arpa" {
        type master;
        file "/etc/bind/db.255";
    };
    // ----- END PASTE FROM named.conf ------------------------------------
    zone "example.com" IN {
        type master;
        file "/etc/bind/internal/example.com.zone";
        allow-update { none; };
    };
    zone "1.168.192.in-addr.arpa" IN {
        type master;
        file "/etc/bind/internal/example.com.reverse";
        allow-update { none; };
    };
    zone "example.org" IN {
        type master;
        file "/etc/bind/internal/example.org.zone";
        allow-update { none; };
    };
};

view "external" {
    match-clients { any; };
    zone "example.com" IN {
        type master;
        file "/etc/bind/external/example.com.zone";
        allow-update {
            key allhost.;
        }; 
    };
    zone "example.org" IN {
        type master;
        file "/etc/bind/external/example.org.zone";
        allow-update {
            key allhost.;
        }; 
    };
};

สร้างกุญแจสำหรับเปลี่ยนค่าโซน ใช้กุญแจเดียวกันทุกโซน
# dnssec-keygen -r /dev/urandom -a HMAC-MD5 -b 512 -n HOST allhost

Kallhost.+157+38278

และได้ไฟล์ Kallhost.+157+38278.key และ Kallhost.+157+38278.private

ตัวอย่างเนื้อไฟล์ของ Kallhost.+157+38278.key มีดังนี้

allhost. IN KEY 512 3 157 hvql4JlIIajNJzxFLuQQQg4HUNSQExpTuO2kzLudphxHPo3+N976ztqI dPXn2rdvVM6mHSG+0wwhlky+MRwQ+Q==

นำเนื้อไฟล์ของ Kallhost.+157+38278.key ท่อนที่เป็นรหัสมาสร้างไฟล์กุญแจ ตั้งชื่อว่า key.conf
# vi key.conf

key allhost. {
    algorithm HMAC-MD5;
    secret "hvql4JlIIajNJzxFLuQQQg4HUNSQExpTuO2kzLudphxHPo3+N976ztqI dPXn2rdvVM6mHSG+0wwhlky+MRwQ+Q==";
};

ส่วนต่อไปนี้ เขียนไว้เพื่อให้เห็นโครงสร้าง แต่ตอนหน้าจะเขียนเป็นสคริปต์สำหรับผลิตไฟล์โซน และไฟล์รีเวิร์ส หากคร้านที่จะเขียนโซนไฟล์เอง อาจข้ามไปอ่านส่วนขยายตอนหน้าเลยก็ได้ครับ

ต่อไปเป็นการสร้างไฟล์โซนและไฟล์รีเวิร์สโซน

internal
เป็นแบบเปลี่ยนค่าไม่ได้ทั้งหมด example.com ทำทั้ง forward และ reverse
และ example.org ทำ forward อย่างเดียว
มีดังนี้

ไฟล์โซน internal ของ example.com
# vi internal/example.com.zone

$TTL    86400
@       IN      SOA server1.example.com. root.server1.example.com. (
                        41      ; serial (d. adams)
                        3H      ; refresh
                        15M     ; retry
                        1W      ; expiry
                        1D )    ; minimum
@               NS      ns1
server1 IN      A       192.168.1.1
www     IN      CNAME   server1
ftp     IN      CNAME   server1
mail    IN      CNAME   server1
ns1     IN      CNAME   server1
work1   IN      A       192.168.1.101
work2   IN      A       192.168.1.102
work3   IN      A       192.168.1.103

ไฟล์รีเวิร์ส internal ของ example.com
# vi internal/example.com.reverse

$TTL    86400
@       IN      SOA server1.example.com. root.server1.example.com. (
                        41      ; serial (d. adams)
                        3H      ; refresh
                        15M     ; retry
                        1W      ; expiry
                        1D )    ; minimum
@               NS      ns1
1       IN      PTR     server1.example.com.
101     IN      PTR     work1.example.com.
102     IN      PTR     work2.example.com.
103     IN      PTR     work3.example.com.

ไฟล์โซน internal ของ example.org
# vi internal/example.org.zone

$TTL    86400
@       IN      SOA server1.example.org. root.server1.example.org. (
                        42      ; serial (d. adams)
                        3H      ; refresh
                        15M     ; retry
                        1W      ; expiry
                        1D )    ; minimum
@               NS      ns1
server1 IN      A       192.168.1.1
www     IN      CNAME   server1
ns1     IN      CNAME   server1
mail    IN      CNAME   server1
ftp     IN      CNAME   server1
external
เป็นแบบเปลี่ยนค่าได้แบบง่าย ทำ forward อย่างเดียว ทั้ง example.com และ example.org

สร้างโซนไฟล์ external ของ example.com
# vi external/example.com.zone

$TTL    86400
@       IN      SOA server1.example.com. root.server1.example.com. (
                        51      ; serial (d. adams)
                        3H      ; refresh
                        15M     ; retry
                        1W      ; expiry
                        1D )    ; minimum
@               NS      ns1
server1 IN      A       101.102.103.104
www     IN      CNAME   server1
ftp     IN      CNAME   server1
mail    IN      CNAME   server1
ns1     IN      CNAME   server1

สร้างโซนไฟล์ external ของ example.org
# vi external/example.org.zone

$TTL    86400
@       IN      SOA server1.example.org. root.server1.example.org. (
                        52      ; serial (d. adams)
                        3H      ; refresh
                        15M     ; retry
                        1W      ; expiry
                        1D )    ; minimum
@               NS      ns1
server1 IN      A       101.102.103.104
www     IN      CNAME   server1
ns1     IN      CNAME   server1
mail    IN      CNAME   server1
ftp     IN      CNAME   server1

เปลี่ยนเจ้าของและกลุ่ม
# chown -R bind:bind *

ทดสอบขั้นต้น
# /etc/init.d/bind9 restart
ต้องไม่มีรายงานข้อผิดพลาด

เสร็จหมดแล้ว

ทดสอบการอัปเดต

เนื่องจากเราแบ่งเป็นวงในและวงนอก โดยที่สามารถอัปเดตได้เฉพาะวงนอกเท่านั้น ดังนั้นในการสั่งอัปเดต ต้องระบุเซิร์ฟเวอร์เป็น localhost เท่านั้น (ถ้าระบุเซิร์ฟเวอร์เป็น 192.168.1.1 เขาจะไม่ยอมอัปเดตให้)

ทดลองอัปเดต
# nsupdate -d -v -k /etc/bind/Kallhost.+157+38278.private

Creating key...
> server localhost
> update add testing.example.com. 86400 IN A 101.102.103.155
> send
Reply from SOA query:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id:  22523
;; flags: qr aa rd ra ; QUESTION: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1
;; QUESTION SECTION:
;testing.example.com.           IN      SOA

;; AUTHORITY SECTION:
example.com.            86400   IN      SOA     server1.example.com. root.server1.example.com. 57 10800 900 604800 86400

;; TSIG PSEUDOSECTION:
allhost.                0       ANY     TSIG    hmac-md5.sig-alg.reg.int. 1203992365 300 16 sMkuPmAy7VF1RCoGpHjrXA== 22523 NOERROR 0


Found zone name: example.com
The master is: server1.example.com
Sending update to 127.0.0.1#53
Outgoing update query:
;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id:  50100
;; flags: ; ZONE: 1, PREREQ: 0, UPDATE: 1, ADDITIONAL: 1
;; ZONE SECTION:
;example.com.                   IN      SOA

;; UPDATE SECTION:
testing.example.com.    86400   IN      A       101.102.103.155

;; TSIG PSEUDOSECTION:
allhost.                0       ANY     TSIG    hmac-md5.sig-alg.reg.int. 1203992365 300 16 pmaYIKqjlgGU+FJvT3uZxA== 50100 NOERROR 0


Reply from update query:
;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id:  50100
;; flags: qr ra ; ZONE: 0, PREREQ: 0, UPDATE: 0, ADDITIONAL: 1
;; TSIG PSEUDOSECTION:
allhost.                0       ANY     TSIG    hmac-md5.sig-alg.reg.int. 1203992365 300 16 9ca6E1RY9RfHacqi7uSuaQ== 50100 NOERROR 0

> quit

ทั้งหมดต้องไม่มีข้อผิดพลาด

ลองค้นค่าดู
# nslookup testing.example.com localhost

nslookup testing.example.com 
Server:         localhost
Address:        127.0.0.1#53

Name:   testing.example.com
Address: 101.102.103.155

เริ่ม bind ใหม่
# /etc/init.d/bind9 restart

จบส่วนของ name server ปกติ

การอัปเดตไอพีเมื่อสายหลุด

(ส่วนนี้เป็นตอนต่อจากตอนที่แล้ว คือการทำเรื่อง dynamic dns client)

กลับไปแก้ไขไฟล์ d.router-reconnect โดยเพิ่มส่วนของการอัปเดตไอพี
# vi /usr/local/sbin/d.router-reconnect

...
#-------ADDITIONAL BIND9 SCRIPT:------------------

#UPDATE LOCAL BIND9
IP=`/usr/local/sbin/d.router-getip`
KEY="/etc/bind/Kallhost.+157+38278.private"
HOSTNAME=`hostname`

#GET DOMAIN FROM FIRST PROVIDER ONLY
DIR="/etc/ddns/ddns-enabled"
PROVIDER=`ls $DIR | cut -d\  -f1`
. $DIR/$PROVIDER
for i in $UPDATE_DOMAIN; do
    DOMAIN=`echo $i | cut -d: -f1`
    nsupdate -k $KEY << EOF
server localhost
update delete $HOSTNAME.$DOMAIN
update add $HOSTNAME.$DOMAIN. 86400 IN A $IP
send
quit
EOF

done

จบจริง ๆ แล้ว

อ้างอิง

Topic: 

all-in-one 4.1 (bind9 - zone generator script)

ทำ dns server โดยใช้ bind9 - ต่อส่วนขยาย

ท่อนนี้ไม่มีอะไรมาก เพียงแต่ต้องการรวมศูนย์ข้อมูลไว้ในไฟล์เดียว เพื่อต้องการให้ดูง่ายตรวจสอบง่ายและลดโอกาสผิดพลาดในการแก้ไขไฟล์โซนและไฟล์รีเวิร์สโซน
จึงสร้างเป็นสคริปต์เล็ก ๆ ขึ้นมาเพื่อใช้อ่านค่าข้อมูลที่เราสร้างเอาไว้ แล้วผลิตไฟล์โซนต่าง ๆ ออกมาให้ครบตามที่เราตั้งไว้

สคริปต์ตั้งชื่อว่า d.bind-genzone เอาไว้ที่ /usr/local/sbin มีดังนี้
# vi /usr/local/sbin/d.bind-genzone

#!/bin/bash
# GENERATE ZONE FILE [AND REVERSE ZONE FILE IF ADD OPTION -b]
# READ DATA FROM DATAFILE IN FORMAT
#     zone:ZONENAME:HOSTNAME:SERIAL:IP_ABC
#     ns:NAMESERVER1 NAMESERVER2
#     mx:MAILSERVER1,RR1 MAILSERVER2,RR2
#     IP_D:NAME CNAME1 CNAME2 ...
#     ...
# EXAMPLE:
#     zone:example.com:server1:43:192.168.1
#     ns:ns1 ns2
#     mx:mail,10 mail2,20
#     1:server1 ns1 mail www ftp
#     2:ns2
#     101:work1
#     102:work2
#     ...

#GLOBAL VAR
TTL=86400
BINDUSER="bind"
BINDGROUP="bind"

#FUNTION
function usage {
    cat << EOF

Script to generate zone file and reverse zone file
USAGE: $0 [-b] DATAFILE
OPTIONS:
  -b = both, generate both forward and reverse zone file,
       only generate forward zone file, if omitted
ARGUMENT:
  DATAFILE : datafile in format:-
     zone:ZONENAME:HOSTNAME:SERIAL:IP_ABC
    [ns:NAMESERVER1 NAMESERVER2]
    [mx:MAILSERVER1,RR1 MAILSERVER2,RR2]
     IP_D:NAME CNAME1 CNAME2 ...
     ...
  EXAMPLE OF DATAFILE:
  example1:
     zone:example.com:server1:43:192.168.1
     ns:ns1 ns2
     mx:mail,10 mail2,20
     1:server1 ns1 mail www ftp
     2:ns2
     101:work1
     102:work2
     192.168.5.1:router
     ...
  example2:
     zone:example.org:ns2:44:192.168.1
     2:ns2
     ns:ns1 ns2

EOF
}

function gen_header {
    Z=$1
    H=$2
    S=$3
    cat << EOF
\$TTL   $TTL
@       IN      SOA $H.$Z. root.$H.$Z. (
                        $S      ; serial (d. adams)
                        3H      ; refresh
                        15M     ; retry
                        1W      ; expiry
                        1D )    ; minimum
EOF
}

function gen_ns {
    H=$1
    cat << EOF
@               NS      $H
EOF
}

function gen_mx {
    H=$1
    R=$2
    cat << EOF
@               MX      $R $H
EOF
}

function gen_name {
    N=$1
    I=$2
    cat << EOF
$N      IN      A       $I
EOF
}

function gen_ptr {
    I=$1
    N=$2
    cat << EOF
$I      IN      PTR     $N.$ZONENAME.
EOF
}

function gen_cname {
    C=$1
    N=$2
    cat << EOF
$C      IN      CNAME   $N
EOF
}

function get_ip {       
    I1="$1."      #input ip     
    I2="$2."      #$IP_ABC      
    A=$(echo "$I1" | cut -d. -f1) 
    B=$(echo "$I1" | cut -d. -f2)
    C=$(echo "$I1" | cut -d. -f3)
    D=$(echo "$I1" | cut -d. -f4)
    W=$(echo "$I2" | cut -d. -f1)
    X=$(echo "$I2" | cut -d. -f2)
    Y=$(echo "$I2" | cut -d. -f3)
    Z=$(echo "$I2" | cut -d. -f4)
    if [ ! "$B" ]; then
        echo "$W.$X.$Y.$A"; return
    elif [ ! "$C" ]; then
        echo "$W.$X.$A.$B"; return
    elif [ ! "$D" ]; then
        echo "$W.$A.$B.$C"; return
    else
        echo "$I1"      
    fi
}

#BEGIN MAIN
DATAFILE=""
WITHREVERSE=""
    
while getopts "b" ARGS; do
    case "$ARGS" in
        b) WITHREVERSE="true"; shift $(($OPTIND - 1)) ;;
    esac
done

DATAFILE=$1
shift

while getopts "b" ARGS; do
    case "$ARGS" in
        b) WITHREVERSE="true"; shift $(($OPTIND - 1)) ;;
    esac
done

if [ ! "$DATAFILE" ] || [ ! -f "$DATAFILE" ]; then
    usage
    exit 1
fi

ZONENAME=""
HOSTNAME=""
SERIAL=""
IP_ABC=""
ZONEFILE=""
REVERSEFILE=""

cat $DATAFILE | grep -v "#" | while read DATA; do
    #ZONE HEADER
    if [ "${DATA:0:5}" == "zone:" ]; then
        ZONENAME=$(echo $DATA | cut -d: -f2)
        HOSTNAME=$(echo $DATA | cut -d: -f3)
        SERIAL=$(echo $DATA | cut -d: -f4)
        IP_ABC=$(echo $DATA | cut -d: -f5)

        #FORWARD ZONE FILE
        ZONEFILE="$ZONENAME.zone"
        if [ -f "$ZONEFILE" ]; then
            mv "$ZONEFILE" "$ZONEFILE.bak"
        fi
        gen_header $ZONENAME $HOSTNAME $SERIAL > $ZONEFILE
        chown $BINDUSER:$BINDGROUP $ZONEFILE

        if [ "$WITHREVERSE" ]; then
            #REVERSE ZONE FILE
            REVERSEFILE="$ZONENAME.reverse"
            if [ -f "$REVERSEFILE" ]; then
                mv "$REVERSEFILE" "$REVERSEFILE.bak"
            fi
            gen_header $ZONENAME $HOSTNAME $SERIAL > $REVERSEFILE
            chown $BINDUSER:$BINDGROUP $REVERSEFILE
    
            echo "Generate $ZONEFILE and $REVERSEFILE ..."
        else
            echo "Generate $ZONEFILE ..."
        fi

    #NS 
    elif [ "${DATA:0:3}" == "ns:" ] && [ "$ZONENAME" ]; then
        for i in $(echo $DATA | cut -d: -f2); do
            gen_ns $i >> $ZONEFILE
            if [ "$WITHREVERSE" ]; then
                gen_ns $i >> $REVERSEFILE
            fi
        done
    
    #MX
    elif [ "${DATA:0:3}" == "mx:" ] && [ "$ZONENAME" ]; then
        for i in $(echo $DATA | cut -d: -f2); do
            $MAILNAME=$(echo $i | cut -d, -f1)
            $RRPRIORITY=$(echo $i | cut -d, -f2)
            gen_mx $MAILNAME $RRPRIORITY >> $ $ZONEFILE
        done
    
    #DATA
    elif [ "$DATA" ]; then
        RAW_IP=$(echo $DATA | cut -d: -f1)
        FULL_IP=`get_ip $RAW_IP $IP_ABC`
        NAMES=$(echo $DATA | cut -d: -f2)
        FIRSTNAME=$(echo $NAMES | cut -d\  -f1)
        gen_name $FIRSTNAME $FULL_IP >> $ZONEFILE
        if [ "$WITHREVERSE" ]; then
            gen_ptr $RAW_IP $FIRSTNAME >> $REVERSEFILE
        fi
        for i in $NAMES; do
            if [ "$i" != "$FIRSTNAME" ]; then
                gen_cname $i $FIRSTNAME >> $ZONEFILE
            fi
        done
    fi  

done

แก้ให้รันได้เฉพาะ root
# chmod 700 /usr/local/sbin/d.bind-genzone

หลังจากนั้นสร้างไฟล์ข้อมูลของ internal บรรจุข้อมูลของ example.com เอาไว้ใน /etc/bind/internal ตั้งชื่อว่า both-data เพราะจะทำค้นย้อนด้วย ดังนี้
# cd /etc/bind/internal
# vi both-data

# DATAFILE FORMAT
#     zone:ZONENAME:HOSTNAME:SERIAL:IP_ABC
#     ns:NAMESERVER1 NAMESERVER2
#     mx:MAILSERVER1,RR1 MAILSERVER2,RR2
#     IP_D:NAME CNAME1 CNAME2 ...
#     ...
# EXAMPLE:
#     zone:example.com:server1:43:192.168.1
#     ns:ns1 ns2
#     mx:mail,10 mail2,20
#     1:server1 ns1 mail www ftp
#     2:ns2
#     101:work1
#     102:work2
#     ...

#EXAMPLE.COM
zone:example.com:server1:41:192.168.1
ns:ns1
1:server1 www ftp mail ns1
101:work1
102:work2
103:work3

สั่งผลิตไฟล์
# d.bind-genzone -b both-data
จะได้ไฟล์โซนและไฟล์รีเวิร์สโซน ของ example.com ที่เป็นของเครือข่ายภายในออกมา
(ถ้ามีไฟล์เก่า จะสำเนาไว้ในชื่อเดิม ต่อท้ายนามสกุลด้วย .bak)

สร้างไฟล์ข้อมูลของ internal บรรจุข้อมูลของ example.org ตั้งชื่อว่า forward-data เพราะจะทำค้นชื่ออย่างเดียว ไม่ทำค้นย้อน ดังนี้
# vi forward-data

# DATAFILE FORMAT
#     zone:ZONENAME:HOSTNAME:SERIAL:IP_ABC
#     ns:NAMESERVER1 NAMESERVER2
#     mx:MAILSERVER1,RR1 MAILSERVER2,RR2
#     IP_D:NAME CNAME1 CNAME2 ...
#     ...
# EXAMPLE:
#     zone:example.com:server1:43:192.168.1
#     ns:ns1 ns2
#     mx:mail,10 mail2,20
#     1:server1 ns1 mail www ftp
#     2:ns2
#     101:work1
#     102:work2
#     ...

#EXAMPLE.ORG
zone:example.org:server1:42:192.168.1
ns:ns1
1:server1 www ns1 mail ftp

สั่งผลิตไฟล์ โดยไม่ต้องใส่ออปชั่น -b
# d.bind-genzone forward-data
จะได้ไฟล์โซน ของ example.org ที่เป็นของเครือข่ายภายใน

และสร้างไฟล์ข้อมูลของ external บรรจุข้อมูลของทุกโซน เอาไว้ใน /etc/bind/external ตั้งชื่อว่า forward-data (จะทำแค่ forward อย่างเดียว) ดังนี้
# cd /etc/bind/external
# vi forward-data

# DATAFILE FORMAT
#     zone:ZONENAME:HOSTNAME:SERIAL:IP_ABC
#     ns:NAMESERVER1 NAMESERVER2
#     mx:MAILSERVER1,RR1 MAILSERVER2,RR2
#     IP_D:NAME CNAME1 CNAME2 ...
#     ...
# EXAMPLE:
#     zone:example.com:server1:43:192.168.1
#     ns:ns1 ns2
#     mx:mail,10 mail2,20
#     1:server1 ns1 mail www ftp
#     2:ns2
#     101:work1
#     102:work2
#     ...

#EXAMPLE.COM
zone:example.com:server1:51:101.102.103
ns:ns1
104:server1 www ftp mail ns1

#EXAMPLE.ORG
zone:example.org:server1:52:101.102.103
ns:ns1
104:server1 www ns1 mail ftp

สั่งผลิตไฟล์ โดยไม่ต้องใส่ออปชั่น -b
# d.bind-genzone forward-data
จะได้เฉพาะไฟล์โซนของ example.com และ example.org ที่เป็นของเครือข่ายภายนอก

เสร็จแล้ว สั่งเริ่ม bind9 ใหม่ได้เลย
# /etc/init.d/bind9 restart

Topic: 

all-in-one 4.2 (ปรับปรุง zone generator script)

ปรับสคริปต์นิดหน่อย

# vi /usr/local/sbin/d.bind-genzone
#!/bin/bash
# GENERATE ZONE FILE [AND REVERSE ZONE FILE IF ADD OPTION -b]
# READ DATA FROM DATAFILE IN FORMAT
#     zone:ZONENAME:IP_D:CNAME1 CNAME2 ...:SERIAL:IP_ABC
#     ns:NAMESERVER1 NAMESERVER2
#     mx:MAILSERVER1,RR1 MAILSERVER2,RR2
#     IP_D:NAME CNAME1 CNAME2 ...
#     ...
# EXAMPLE:
#     zone:example.com:1:server1 ns1 mail www ftp:43:192.168.1
#     ns:ns1 ns2
#     mx:mail,10 mail2,20
#     2:ns2
#     101:work1
#     102:work2
#     ...

#GLOBAL VAR
TTL=86400
BINDUSER="bind"
BINDGROUP="bind"

#FUNTION
function usage {
    cat << EOF

Script to generate zone file and reverse zone file
USAGE: $0 [-b] DATAFILE
OPTIONS:
  -b = both, generate both forward and reverse zone file,
       only generate forward zone file, if omitted
ARGUMENT:
  DATAFILE : datafile in format:-
     zone:ZONENAME:IP_D:CNAME1 CNAME2 ...:SERIAL:IP_ABC
    [ns:NAMESERVER1 NAMESERVER2]
    [mx:MAILSERVER1,RR1 MAILSERVER2,RR2]
     IP_D:NAME CNAME1 CNAME2 ... 
     ...
  EXAMPLE OF DATAFILE:
  example1:
     zone:example.com:1:server1 ns1 mail www ftp:43:192.168.1
     ns:ns1 ns2
     mx:mail,10 mail2,20
     2:ns2
     101:work1
     102:work2
     192.168.5.1:router
     ...
  example2:
     zone:ns2.example.org:2::44:192.168.1
     ns:ns1 ns2

EOF
}

function gen_header {
    Z=$1
    S=$2
    cat << EOF
\$TTL   $TTL
@       IN      SOA $Z. root.$Z. (
                        $S      ; serial (d. adams)
                        3H      ; refresh
                        15M     ; retry
                        1W      ; expiry
                        1D )    ; minimum
EOF
}

function gen_ns {
    H=$1
    cat << EOF
@               NS      $H
EOF
}

function gen_mx {
    H=$1
    R=$2
    cat << EOF
@               MX      $R $H
EOF
}

function gen_name {
    N=$1
    I=$2
    cat << EOF
$N      IN      A       $I
EOF
}

function gen_ptr {
    I=$1
    N=$2
    cat << EOF
$I      IN      PTR     $N.$ZONENAME.
EOF
}

function gen_cname {
    C=$1
    N=$2
    cat << EOF
$C      IN      CNAME   $N.
EOF
}
function get_ip {
    I1="$1."      #input ip     
    I2="$2."      #$IP_ABC      
    A=$(echo "$I1" | cut -d. -f1)
    B=$(echo "$I1" | cut -d. -f2)
    C=$(echo "$I1" | cut -d. -f3)
    D=$(echo "$I1" | cut -d. -f4)
    W=$(echo "$I2" | cut -d. -f1)
    X=$(echo "$I2" | cut -d. -f2)
    Y=$(echo "$I2" | cut -d. -f3)
    Z=$(echo "$I2" | cut -d. -f4)
    if [ ! "$B" ]; then
        echo "$W.$X.$Y.$A"; return
    elif [ ! "$C" ]; then
        echo "$W.$X.$A.$B"; return
    elif [ ! "$D" ]; then
        echo "$W.$A.$B.$C"; return
    else
        echo "$I1"      
    fi
}

#BEGIN MAIN
DATAFILE=""
WITHREVERSE=""

while getopts "b" ARGS; do
    case "$ARGS" in
        b) WITHREVERSE="true"; shift $(($OPTIND - 1)) ;;
    esac
done

DATAFILE=$1
shift
while getopts "b" ARGS; do
    case "$ARGS" in
        b) WITHREVERSE="true"; shift $(($OPTIND - 1)) ;;
    esac
done

if [ ! "$DATAFILE" ] || [ ! -f "$DATAFILE" ]; then
    usage
    exit 1
fi

ZONENAME=""
SERIAL=""
IP_ABC=""
ZONEFILE=""
REVERSEFILE=""

cat $DATAFILE | grep -v "#" | while read DATA; do
    #ZONE HEADER
    if [ "${DATA:0:5}" == "zone:" ]; then
        ZONENAME=$(echo $DATA | cut -d: -f2)
        IP_D=$(echo $DATA | cut -d: -f3)
        CNAMES=$(echo $DATA | cut -d: -f4)
        SERIAL=$(echo $DATA | cut -d: -f5)
        IP_ABC=$(echo $DATA | cut -d: -f6)

        #FORWARD ZONE FILE
        ZONEFILE="$ZONENAME.zone"
        if [ -f "$ZONEFILE" ]; then
            mv "$ZONEFILE" "$ZONEFILE.bak"
        fi
        FULL_IP=`get_ip $IP_D $IP_ABC`
        gen_header $ZONENAME $SERIAL > $ZONEFILE
        chown $BINDUSER:$BINDGROUP $ZONEFILE
        gen_name "@" $FULL_IP >> $ZONEFILE
        for i in $CNAMES; do
            gen_cname $i $ZONENAME >> $ZONEFILE
        done

        if [ "$WITHREVERSE" ]; then
            #REVERSE ZONE FILE
            REVERSEFILE="$ZONENAME.reverse"
            if [ -f "$REVERSEFILE" ]; then
                mv "$REVERSEFILE" "$REVERSEFILE.bak"
            fi
            gen_header $ZONENAME $SERIAL > $REVERSEFILE
            chown $BINDUSER:$BINDGROUP $REVERSEFILE
            gen_ptr $IP_D "" >> $REVERSEFILE

            echo "Generate $ZONEFILE and $REVERSEFILE ..."
        else
            echo "Generate $ZONEFILE ..."
        fi

    #NS 
    elif [ "${DATA:0:3}" == "ns:" ] && [ "$ZONENAME" ]; then
        for i in $(echo $DATA | cut -d: -f2); do
            gen_ns $i >> $ZONEFILE
            if [ "$WITHREVERSE" ]; then
                gen_ns $i >> $REVERSEFILE
            fi
        done

    #MX
    elif [ "${DATA:0:3}" == "mx:" ] && [ "$ZONENAME" ]; then
        for i in $(echo $DATA | cut -d: -f2); do
            $MAILNAME=$(echo $i | cut -d, -f1)
            $RRPRIORITY=$(echo $i | cut -d, -f2)
            gen_mx $MAILNAME $RRPRIORITY >> $ $ZONEFILE
        done

    #DATA
    elif [ "$DATA" ]; then
        RAW_IP=$(echo $DATA | cut -d: -f1)
        FULL_IP=`get_ip $RAW_IP $IP_ABC`
        NAMES=$(echo $DATA | cut -d: -f2)
        FIRSTNAME=$(echo $NAMES | cut -d\  -f1)
        echo "gen_name $FIRSTNAME $FULL_IP  $ZONEFILE"
        gen_name $FIRSTNAME $FULL_IP >> $ZONEFILE
        if [ "$WITHREVERSE" ]; then
            gen_ptr $RAW_IP $FIRSTNAME >> $REVERSEFILE
        fi
        for i in $NAMES; do
            if [ "$i" != "$FIRSTNAME" ]; then
                gen_cname $i $FIRSTNAME >> $ZONEFILE
            fi
        done
    fi

done

all-in-one 5 (postfix ส่งโดย gmail+courier)

ทำ mail server ใช้ postfix + Courier

หัวข้อนี้สบายมาก เพราะไม่รู้เรื่องเลย ลอกมาอย่างเดียว จาก The Perfect Setup - Debian Etch (Debian 4.0) - Page 5
ของเราแปลงนิดเดียว ตรงที่จะให้กูเกิลเป็นตัวส่งเมลแทน (เรารับเหมือนเดิม แต่ให้กูเกิลส่งให้)
ทีแรกกะว่าจะใช้ exim4 แต่หาเอกสารติดตั้งที่สมบูรณ์ยาก และความนิยมของ postfix มีมากกว่า เลยตัดสินใจใช้อันนี้ครับ

ติดตั้ง
# aptitude install postfix libsasl2 sasl2-bin libsasl2-modules libdb3-util procmail
# dpkg-reconfigure postfix

General type of configuration?   <<<--- Internet Site
Where should mail for root go   <<<--- [ENTER]
Mail name?   <<<--- mail.example.com
Other destinations to accept mail for? (blank for none)   <<<--- mail.example.com, server1.example.com, localhost.example.com, localhost, mail.example.org
Force synchronous updates on mail queue?   <<<--- No
Local networks?   <<<--- 127.0.0.0/8, 192.168.0.0/16
Use procmail for local delivery?   <<<--- Yes
Mailbox size limit   <<<--- 0
Local address extension character?   <<<--- +
Internet protocols to use?   <<<--- all

ปรับ
# postconf -e 'relayhost = smtp.gmail.com'
# postconf -e 'smtp_sasl_auth_enable = yes'
# postconf -e 'smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd'
# postconf -e 'smtp_sasl_security_options ='
# postconf -e 'smtpd_sasl_security_options = noanonymous'
# postconf -e 'smtpd_sasl_local_domain ='
# postconf -e 'smtpd_sasl_auth_enable = yes'
# postconf -e 'broken_sasl_auth_clients = yes'
# postconf -e 'smtpd_recipient_restrictions = permit_sasl_authenticated,permit_mynetworks,reject_unauth_destination'
# postconf -e 'inet_interfaces = all'
# echo 'pwcheck_method: saslauthd' >> /etc/postfix/sasl/smtpd.conf
# echo 'mech_list: plain login' >> /etc/postfix/sasl/smtpd.conf

ทำเรื่อง ssl
# mkdir /etc/postfix/ssl
# cd /etc/postfix/ssl/
# openssl genrsa -des3 -rand /etc/hosts -out smtpd.key 1024

2597 semi-random bytes loaded
Generating RSA private key, 1024 bit long modulus
...................++++++
..........................................++++++
e is 65537 (0x10001)
Enter pass phrase for smtpd.key:   <<<--- PASSWORD
Verifying - Enter pass phrase for smtpd.key:   <<<--- SAME-PASSWORD

สร้างกุญแจ
# chmod 600 smtpd.key
# openssl req -new -key smtpd.key -out smtpd.csr

Enter pass phrase for smtpd.key:   <<<--- SAME-PASSWORD
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:   <<<--- TH
State or Province Name (full name) [Some-State]:   <<<--- Bangkok
Locality Name (eg, city) []:   <<<--- Bangrak
Organization Name (eg, company) [Internet Widgits Pty Ltd]:   <<<--- Example Company
Organizational Unit Name (eg, section) []:   <<<--- Computer Department
Common Name (eg, YOUR name) []:   <<<--- MYNAME-MYSURNAME
Email Address []:   <<<--- ***GMAIL-USER@gmail.com***

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:   <<<--- [ENTER]
An optional company name []:   <<<--- [ENTER]

ลงทะเบียน
# openssl x509 -req -days 3650 -in smtpd.csr -signkey smtpd.key -out smtpd.crt

Signature ok
subject=/C=TH/ST=Bangkok/L=Bangrak/O=Example Company/OU=Computer Department/CN=myname-mysurname/emailAddress=gmail.user@gmail.com
Getting Private key
Enter pass phrase for smtpd.key:   <<<--- SAME-PASSWORD

# openssl rsa -in smtpd.key -out smtpd.key.unencrypted

Enter pass phrase for smtpd.key:   <<<--- SAME-PASSWORD
writing RSA key

# mv -f smtpd.key.unencrypted smtpd.key
# openssl req -new -x509 -extensions v3_ca -keyout cakey.pem -out cacert.pem -days 3650

650
Generating a 1024 bit RSA private key
..................++++++
....................++++++
writing new private key to 'cakey.pem'
Enter PEM pass phrase:   <<<--- SAME-PASSWORD
Verifying - Enter PEM pass phrase:   <<<--- SAME-PASSWORD
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:   <<<--- TH
State or Province Name (full name) [Some-State]:   <<<--- Bangkok
Locality Name (eg, city) []:   <<<--- Bangrak
Organization Name (eg, company) [Internet Widgits Pty Ltd]:   <<<--- Example Company
Organizational Unit Name (eg, section) []:   <<<--- Computer Department
Common Name (eg, YOUR name) []:   <<<--- MYNAME-MYSURNAME
Email Address []:   <<<--- ***GMAIL-USER@gmail.com***

ปรับ postfix เรื่องกุญแจ
# postconf -e 'smtpd_tls_auth_only = no'
# postconf -e 'smtp_use_tls = yes'
# postconf -e 'smtpd_use_tls = yes'
# postconf -e 'smtp_tls_note_starttls_offer = yes'
# postconf -e 'smtpd_tls_key_file = /etc/postfix/ssl/smtpd.key'
# postconf -e 'smtpd_tls_cert_file = /etc/postfix/ssl/smtpd.crt'
# postconf -e 'smtpd_tls_CAfile = /etc/postfix/ssl/cacert.pem'
# postconf -e 'smtpd_tls_loglevel = 1'
# postconf -e 'smtpd_tls_received_header = yes'
# postconf -e 'smtpd_tls_session_cache_timeout = 3600s'
# postconf -e 'tls_random_source = dev:/dev/urandom'
# postconf -e 'myhostname = mail.example.com'

ไฟล์รหัสผ่าน gmail
# echo "smtp.gmail.com GMAIL-USER@gmail.com:GMAIL-PASSWORD" >> /etc/postfix/sasl_passwd
# chown root:root /etc/postfix/sasl_passwd
# chmod 600 /etc/postfix/sasl_passwd
# postmap /etc/postfix/sasl_passwd

เรื่อง sasl
# mkdir -p /var/spool/postfix/var/run/saslauthd

ปรับให้มาใช้ใน chroot ของ postfix
# vi /etc/default/saslauthd

...
# Should saslauthd run automatically on startup? (default: no)
#START=no
START=yes
...
# Other options (default: -c)
# See the saslauthd man page for information about these options.
#
# Example for postfix users: "-c -m /var/spool/postfix/var/run/saslauthd"
# Note: See /usr/share/doc/sasl2-bin/README.Debian
#OPTIONS="-c"
OPTIONS="-c -m /var/spool/postfix/var/run/saslauthd -r"
...

เริ่ม sasl ใหม่
# /etc/init.d/saslauthd start

ทดสอบ sasl
# telnet localhost 25

Trying 127.0.0.1...
Connected to localhost.localdomain.
Escape character is '^]'.
220 server1.example.com ESMTP Postfix (Debian/GNU)
ehlo localhost
250-server1.example.com
250-PIPELINING
250-SIZE 10240000
250-VRFY
250-ETRN
250-STARTTLS
250-AUTH PLAIN LOGIN
250-AUTH=PLAIN LOGIN
250-ENHANCEDSTATUSCODES
250-8BITMIME
250 DSN
quit
221 2.0.0 Bye
Connection closed by foreign host.

ติดตั้ง courier เพื่อใช้งาน pop3 และ imap
# aptitude install courier-authdaemon courier-base courier-imap courier-imap-ssl courier-pop courier-pop-ssl courier-ssl gamin libgamin0 libglib2.0-0

Create directories for web-based administration ?   <<<--- No
SSL certificate required   <<<--- Ok

ทำเรื่อง Maildir เป็นเรื่องสุดท้ายเสมอ
# postconf -e 'home_mailbox = Maildir/'
# postconf -e 'mailbox_command ='
# /etc/init.d/postfix restart

เสร็จเรื่องติดตั้ง

เรื่องปรับ gmail

สมัครเข้าใช้บริการ gmail ที่ mail.google.com

ปรับให้สามารถส่งอีเมลจากเครื่องเราได้

การตั้งค่า -> บัญชี -> ส่งอีเมลเป็นแบบ เพิ่มที่อยู่อีเมลอื่นอีก

ทำตามขั้นตอน แล้วนำตัวเลขยืนยันมากรอกในช่อง เป็นอันเสร็จ

อ้างอิง

Topic: 

all-in-one 6 (apache2+php5+mysql5+postgresql8)

apache2 + mysql5 + php5 + phpmyadmin

เตรียมสร้างไดเรกทอรี่ข้อมูลให้ apache2 และ mysql
อันนี้ลักไก่ ใช้คำสั่งเดียว แล้วสั่งหยุดบริการเลย
# aptitude install phpmyadmin mysql-server-5.0 php-apc
# /etc/init.d/apache2 stop
# /etc/init.d/mysql stop

ตัวคอนฟิกของ apache2 mysql และ php5 จะเอามาเก็บไว้ที่ /sys1/sysb
# mv /etc/apache2/ /sys1/sysb/etc/
# ln -sf /sys1/sysb/etc/apache2/ /etc
# mv /etc/mysql/ /sys1/sysb/etc/
# ln -sf /sys1/sysb/etc/mysql/ /etc
# mv /etc/php5 /sys1/sysb/etc/
# ln -sf /sys1/sysb/etc/php5 /etc

ตัวข้อมูล apache2 เอามาเก็บที่ /sys1/sysb
# mv /var/www /sys1/sysb/var/
# ln -sf /sys1/sysb/var/www /var

ตัวข้อมูลของ mysql ไม่สำรองแบบคัดลอกปกติ จึงเอามาเก็บไว้ที่ /sys1/syst แทน
# mkdir -p /sys1/syst/var/lib
# mv /var/lib/mysql /sys1/syst/var/lib
# mv /var/lib/mysql-cluster /sys1/syst/var/lib
# ln -sf /sys1/syst/var/lib/mysql /var/lib
# ln -sf /sys1/syst/var/lib/mysql-cluster /var/lib

สั่งให้บริการ
# /etc/init.d/mysql start
# /etc/init.d/apache2 start

เพิ่มผู้ใช้ admin ในระบบ
# adduser admin

ตั้งค่ารหัสผ่านของ root และผู้ใช้งาน admin
# mysql
mysql> SET PASSWORD FOR 'root'@'localhost' = PASSWORD('********');
mysql> GRANT ALL PRIVILEGES ON *.* TO 'admin'@'localhost' IDENTIFIED BY '*********';
mysql> \q

postgresql-8.1 + phppgadmin

ต้องติดตั้งไปทีละตัว เพราะแพกเกจ phppgadmin ผูกกับ postgresql-7.4 ซึ่งเราไม่ต้องการ
เอา postgresql-8.1 มาก่อน แล้วหยุดการทำงาน
# aptitude install postgresql-8.1 php5-pgsql
# /etc/init.d/postgresql-8.1 stop

ย้ายคอนฟิกไป /sys1/sysb และย้ายข้อมูลไป /sys1/syst
# mv /etc/postgresql* /sys1/sysb/etc
# ln -sf /sys1/sysb/etc/postgresql* /etc
# mv /var/lib/postgresql/ /sys1/syst/var/lib
# ln -sf /sys1/syst/var/lib/postgresql/ /var/lib

ปรับให้เรียงภาษาไทยโดยการ initdb ใหม่ และปรับการอนุญาตใช้งาน
# dpkg-reconfigure locales
<--- เลือกให้มี th_TH.UTF-8
# su postgres
$ /usr/lib/postgresql/8.1/bin/initdb /var/lib/postgresql/8.1/temp --locale=th_TH.UTF-8
$ cd /var/lib/postgresql/8.1
$ cp -xa temp/* main
$ rm -rf temp

ปรับตั้งสิทธิ์
$ vi /etc/postgresql/8.1/main/pg_hba.conf

...
#local   all         all                               ident sameuser
local   all     all                             md5
host    all     all     192.168.0.0/16          md5
...

สั่งเริ่ม postgresql ใหม่ และสร้างผู้ใช้งาน phppgadmin ชื่อ admin ให้เป็น superuser
$ exit
# /etc/init.d/postgresql-8.1 start
# su -l postgres -c "createuser -s -P admin"

ส่วนของ phppgadmin
ก่อนอื่นติดตั้งแพกเกจที่ขึ้นกับ phppgadmin ก่อน เหลือแค่ตัวเดียวคือ wwwconfig-common
# aptitude install wwwconfig-common

และไปดาวน์โหลด phppgadmin รุ่นของ testing มาติดตั้งเอง
# wget http://ftp.debianclub.org/debian/pool/main/p/phppgadmin/phppgadmin_4.1.3-0.1_all.deb
# dpkg -i phppgadmin_4.1.3-0.1_all.deb

ปรับตั้ง
# dpkg-reconfigure phppgadmin

Web server to reconfigure automatically: <<<--- Apache2

ปรับให้เรียกดูได้จากเฉพาะเครือข่ายภายใน
# vi /etc/phppgadmin/apache.conf

...
order deny,allow
deny from all
allow from 127.0.0.0/255.0.0.0
allow from 192.168.0.0/16
# allow from all
...

เสร็จแล้ว

ต่อไปเป็นการปรับ apache2

ตั้งให้รากของเว็บ www.example.com อยู่ที /var/www/example.com
และของ www.example.org อยู่ที่ /var/www/example.org ตามลำดับ
# mkdir -p /var/www/{example.com,example.org}

กำหนดให้ www.example.com เป็นเว็บปริยาย
ต้องไปแก้ไขไฟล์ default ของ sites-enable
# vi /etc/apache2/sites-enable/000-default

NameVirtualHost *
<VirtualHost *>
    #ServerAdmin webmaster@localhost
    ServerAdmin webmaster@example.com
    ServerName www.example.com
    ServerAlias example.com

    DocumentRoot /var/www/example.com/
    <Directory />
        Options FollowSymLinks
        #AllowOverride None
        AllowOverride All


    </Directory>
    <Directory /var/www/example.com/>
        Options Indexes FollowSymLinks MultiViews
        #AllowOverride None
        AllowOverride All
        Order allow,deny
        allow from all
        # This directive allows us to have apache2's default start page
        # in /apache2-default/, but still have / go to the right place
        #RedirectMatch ^/$ /apache2-default/
    </Directory>
    ...

และ www.example.org ต้องปรับตั้งใหม่ โดยเอาตัวอย่างจาก default
# cd /etc/apache2/sites-available
# cp default example.org
# vi example.org

NameVirtualHost *
<VirtualHost *>
    #ServerAdmin webmaster@localhost
    ServerAdmin webmaster@example.org
    ServerName www.example.org
    ServerAlias example.org

    DocumentRoot /var/www/example.org/
    <Directory />
        Options FollowSymLinks
        #AllowOverride None
        AllowOverride All
    </Directory>
    <Directory /var/www/example.org/>
        Options Indexes FollowSymLinks MultiViews
...

สั่งให้เปิดใช้งาน example.org
# a2ensite example.org

มอดูลที่น่าจะเปิดใช้งาน
# a2enmod rewrite
# a2enmod proxy
# a2enmod userdir

ให้รองรับการเข้ารหัสภาษาไทยได้หลายแบบ
# vi /etc/apache2/conf.d/charset

#AddDefaultCharset UTF-8
AddCharset    TIS-620    .tis-620 .th
AddCharset    CP874      .cp874

เพิ่มเติมพิเศษ
สำหรับไพธอน ถ้าจะใช้งาน django ต้องติดตั้งมอดูลเพิ่ม
# aptitude install libapache2-mod-python python-psycopg python-mysqldb
# a2enmod mod_python

อีกนิดนึง ถ้าจะทำให้การใช้งานแบบ (เขียนโค๊ดเอง) ไม่ขึ้นต่อฐานข้อมูล ควรติดตั้ง adodb เพิ่ม
# aptitude install python-adodb python-psyco libphp-adodb

เริ่ม apache2 ใหม่เพื่อให้มอดูลใหม่ทำงาน
# /etc/init.d/apache2 restart

จบแล้ว

ทำสคริปต์สำรองข้อมูล

ทำสคริปต์เล็ก ๆ สำหรับสำรองข้อมูลทุกวัน (ฮาร์ดดิสก์ควรมีเนื้อที่พอสำหรับการนี้ด้วยนะครับ) ใส่ไว้ใน cron
เมื่อสำรองเสร็จแล้ว จะเก็บในชื่อ all.sql อยู่ใน /sys1/sysb/backupdb โดยแยกไปตามชื่อระบบฐานข้อมูล และให้เจ้าของเป็น admin ดูได้คนเดียว
ไฟล์เก่าของเมื่อวานจะถูก gzip เก็บไว้ในชื่อ all.sql.tar.gz (หากต้องการเก็บเกิน 2 วัน ฝากเขียนต่อเอาเองครับ)

ตั้งชื่อว่า d.cron-daily ใส่ไว้ใน /usr/local/sbin
# vi /usr/local/sbin/d.cron-daily

#!/bin/bash
#BACKUP DB 
ROOT="/sys1/sysb/backupdb"
BACKUPFILE="all.sql"

function chkfile() {
    D=$1
    U=$2
    if [ ! -d "$ROOT/$D" ]; then
        mkdir -p "$ROOT/$D"
        chmod 770 "$ROOT/$D"
        chown $DB:$DB "$ROOT/$D"
    fi
    #GZIP OLD FILE (ONLY ONE LAST)
    if [ -f "$ROOT/$D/$BACKUPFILE" ]; then
        rm $ROOT/$D/$BACKUPFILE.tar.gz
        su -l $U -c "tar cfz $ROOT/$D/$BACKUPFILE.tar.gz $ROOT/$D/$BACKUPFILE"
        rm $ROOT/$D/$BACKUPFILE
        chmod 700 "$ROOT/$D/$BACKUPFILE.tar.gz"
    fi
}

# MYSQL
if [ `which mysqldump` ]; then
    USER="admin"
    PASSWORD="MYSQL-ADMIN-PASSWORD"
    DB="mysql"
    chkfile $DB $USER
    su -l $USER -c "mysqldump -A -u$USER -p$PASSWORD > $ROOT/$DB/$BACKUPFILE"
    chmod 700 "$ROOT/$DB/$BACKUPFILE"
fi

# POSTGRESQL
if [ `which pg_dumpall` ]; then
    USER="admin"
    DB="postgres"
    chkfile $DB $USER
    su -l postgres -c "pg_dumpall > $ROOT/$DB/$BACKUPFILE"
    chown $USER "$ROOT/$DB/$BACKUPFILE"
    chmod 700 "$ROOT/$DB/$BACKUPFILE"
fi

ตั้งให้รันได้
# chmod 700 /usr/local/sbin/d.cron-daily

ทำให้รันตอนเที่ยงคืนทุกวัน
# crontab -e

...
#DAILY CRON TASK: BACKUP DB
20 0 * * *  /usr/local/sbin/d.cron-daily
...

เสร็จ

Topic: 

all-in-one 7 (multisite proftpd)

ทำ ftp ใช้ proftpd

ที่ทำเป็น คือใช้ proftpd (ตัวอื่นทำ VirtualHost ไม่เป็น หรืออาจทำยาก)
เนื่องจากเรามี 2 โดเมน คือ example.com และ example.org เราต้องการทำ ftp ทั้งสองโดเมน จึงต้องทำเรื่อง VirtualHost

แต่เนื่องจากระบบ VirtualHost ใน proftpd ไม่เหมือนใน apache2 เสียทีเดียว เนื่องจากเขาไม่ถือชื่อโฮสต์เป็นสำคัญ แต่จะถือไอพีและพอร์ตเป็นตัวจำแนกแทน ดังนั้นถ้าเราต้องการแยกไดเรกทอรี่ระหว่าง example.com และ example.org เราจะต้องใช้พอร์ตเป็นตัวแยกแทน

สมมุติฐานมีดังนี้คือ

  • Anonymous user ให้ไปใช้ที่ /var/ftp/pub/ โดยให้ ftp เป็นเจ้าของ อยู่ในกลุ่ม nogroup ใช้พอร์ตมาตรฐาน คือ 21
  • example.com - anonymous ใช้ที่ /var/ftp/example.com/ ให้ user1:comgroup เป็นเจ้าของ ใช้พอร์ต 10021
  • example.org - anonymous ใช้ที่ /var/ftp/example.org/ ให้ user2:orggroup เป็นเจ้าของ ใช้พอร์ต 10022
  • ผู้ใช้ในระบบ จะสามารถเข้าถึงได้ทุกแชร์ มีสิทธิ์อ่านเขียนตามสิทธิ์ในระบบลินุกซ์
  • Anonymous จะถูกขังในกรงของแต่ละโดเมนเท่านั้น อ่านได้อย่างเดียว
  • ไฟล์ทั้งหมดจะถูกสำรอง นั่นคือไฟล์ตัวจริงอยู่ภายใต้ /sys1/sysb/var/ftp

เตรียมผู้ใช้และกลุ่ม
เพิ่มกลุ่ม ftpusers สำหรับพอร์ตกลาง และ comgroup กับ orggroup สำหรับพอร์ตพิเศษ
# groupadd ftpusers
# groupadd comgroup
# groupadd orggroup

แก้ไขผู้ใช้ ftpuser1 ให้เข้ามาเป็นสมาชิกของ comgroup และ ftpuser2 ให้เข้ามาเป็นสมาชิกของ orggroup
# useradd -g ftpusers -G comgroup -m -d /home/ftpuser1 ftpuser1
# useradd -g ftpusers -G orggroup -m -d /home/ftpuser2 ftpuser2
# passwd ftpuser1
# passwd ftpuser2

ป้อนรหัสผ่านตามปกติ

เตรียมไดเรกทอรี่
# mkdir -p /sys1/sysb/var/ftp/{pub,example.com,example.org}
# ln -sf /sys1/sysb/var/ftp /var
# chown ftpuser1:comgroup /var/ftp/example.com
# chown ftpuser2:orggroup /var/ftp/example.org
# chown ftp:ftpusers /var/ftp/pub
# chmod 775 /var/ftp/*

ติดตั้ง
# aptitude install proftpd

Run proftpd from inetd or standalone? <<<--- standalone

แก้ไขเรื่อง IPv6 ไม่ให้แสดงรายงานความผิดพลาด
และแก้ไขงานของ Anonymous โดยเอาจากตัวอย่างในไฟล์
และเพิ่มให้อ่านไฟล์คอนฟิกของ example.com และ example.org
# vi /etc/proftpd/proftpd.conf

...
#UseIPv6             on
UseIPv6             off
...

# A basic anonymous configuration, no upload directories.

#<Anonymous ~ftp>
<Anonymous /var/ftp/pub>
  User              ftp 
  Group             nogroup
  # We want clients to be able to login with "anonymous" as well as "ftp"
  UserAlias         anonymous ftp 
  # Cosmetic changes, all files belongs to ftp user
  DirFakeUser   on ftp
  DirFakeGroup on ftp

  RequireValidShell     off 

  # Limit the maximum number of anonymous logins
  MaxClients            10

  # We want 'welcome.msg' displayed at login, and '.message' displayed
  # in each newly chdired directory.
  DisplayLogin          welcome.msg
  DisplayFirstChdir     .message

  # Limit WRITE everywhere in the anonymous chroot
  <Directory *>
    <Limit WRITE>
      DenyAll
    </Limit>
  </Directory>

  # Uncomment this if you're brave.
  # <Directory incoming>
  #   # Umask 022 is a good standard umask to prevent new files and dirs
  #   # (second parm) from being group and world writable.
  #   Umask             022  022 
  #            <Limit READ WRITE>
  #            DenyAll
  #            </Limit>
  #            <Limit STOR>
  #            AllowAll
  #            </Limit>
  # </Directory>

</Anonymous>

Include /etc/proftpd/example.com.conf
Include /etc/proftpd/example.org.conf

สร้างไฟล์ example.com.conf ให้มาใช้พอร์ต 10021
# vi /etc/proftpd/example.com.conf

<VirtualHost ftp.example.com>
    ServerName              "Example.com's FTP site"
    DefaultRoot             ~
    AllowOverwrite          on
    Umask                   002
    User                    ftpuser1
    Group                   comgroup
    MaxClients              10
    Port                    10021

    <Anonymous /var/ftp/example.com>
      User                                ftp
      Group                               nogroup
      # We want clients to be able to login with "anonymous" as well as "ftp"
      UserAlias                   anonymous ftp
      # Cosmetic changes, all files belongs to ftp user
      DirFakeUser on ftp
      DirFakeGroup on ftp

      RequireValidShell           off

      # Limit the maximum number of anonymous logins
      MaxClients                  10

      # We want 'welcome.msg' displayed at login, and '.message' displayed
      # in each newly chdired directory.
      DisplayLogin                welcome.msg
      DisplayFirstChdir           .message

      # Limit WRITE everywhere in the anonymous chroot
      <Directory /var/ftp/example.com>
      </Directory>
      <Directory *>
        <Limit WRITE>
          DenyAll
        </Limit>
      </Directory>
    </Anonymous>

</VirtualHost>

สร้าง example.org.conf ให้มาใช้พอร์ต 10022
# vi /etc/proftpd/example.org.conf

<VirtualHost ftp.example.org>
    ServerName              "Example.org's FTP site"
    DefaultRoot             ~
    AllowOverwrite          on
    Umask                   002
    User                    ftpuser2
    Group                   orggroup
    MaxClients              10
    Port                    10022

    <Anonymous /var/ftp/example.org>
      User                                ftp
      Group                               nogroup
      # We want clients to be able to login with "anonymous" as well as "ftp"
      UserAlias                   anonymous ftp
      # Cosmetic changes, all files belongs to ftp user
      DirFakeUser on ftp
      DirFakeGroup on ftp

      RequireValidShell           off

      # Limit the maximum number of anonymous logins
      MaxClients                  10

      # We want 'welcome.msg' displayed at login, and '.message' displayed
      # in each newly chdired directory.
      DisplayLogin                welcome.msg
      DisplayFirstChdir           .message

      # Limit WRITE everywhere in the anonymous chroot
      <Directory /var/ftp/example.org>
      </Directory>
      <Directory *>
        <Limit WRITE>
          DenyAll
        </Limit>
      </Directory>
    </Anonymous>

</VirtualHost>

เริ่ม protftpd ใหม่
# /etc/init.d/proftpd restart

เสร็จแล้ว
ผู้ใช้งานสามารถใช้งานผ่าน

  • ftp://ftp.example.com หรือ ftp://ftp.example.org ซึ่งจะไปที่ /var/ftp/pub
  • ftp://ftp.example.com:10021/ จะไปที่ /var/ftp/example.com
  • ftp://ftp.example.org:10022/ จะไปที่ /var/ftp/example.org
  • ผู้ใช้ในระบบ ดูรายชื่อไฟล์ในระบบได้ทั่ว
หมายเหตู
Topic: 

all-in-one 8 (samba)

ทำ file server ใช้ samba

จะสร้าง 2 แชร์ บรรจุไว้ใน /sys1/sysb/samba แล้วโยงลิงก์ไปที่รูต เป็น /samba เฉย ๆ
สำหรับอ่านอย่างเดียวตั้งชื่อว่า app และ สำหรับอ่านเขียนตั้งชื่อว่า data
Workgroup ตั้งชื่อว่า smbdomain
ตั้งชื่อกลุ่มผู้มีสิทธิ์ใช้งานว่า smbgroup
ผู้คุมระบบชื่อ admin
ผู้ใช้มีชื่อ user1, user2, ... ตามลำดับ

มีพิเศษเพิ่มเติม คือแชร์ที่ทำ ftp ไว้แล้ว จะให้มาแชร์ใน samba ด้วย เพื่อให้ผู้ใช้งานเครือข่ายภายใน สามารถใช้งานได้ง่าย

สร้างโฟลเดอร์ขึ้นมารองรับก่อน
# mkdir -p /sys1/sysb/samba/{app,data}
# ln -sf /sys1/sysb/samba /
# mkdir -p /sys1/sysb/etc/samba
# ln -sf /sys1/sysb/etc/samba /etc

ติดตั้ง samba
# aptitude install samba

Workgroup/Domain Name: <<<--- smbdomain
Modify smb.conf to use WINS settings from DHCP? <<<--- No

สร้างกลุ่มของ samba
# groupadd -g 1100 smbgroup

เขียนสคริปต์สำหรับสร้างผู้ใช้และรหัสผ่าน ตั้งชื่อว่า d.samba-adduser เอาไว้ใน /usr/local/sbin
# vi /usr/local/sbin/d.samba-adduser

#!/bin/bash
# SAMBA - ADD USER FROM DATAFILE IN FORMAT:
#     USER:UID:PASSWORD
#     ...
# EXAMPLE:
#     #USER:UID:PASSWORD
#     user1:1101:user1-password
#     user2:1102:user2-password
#     ...

#GLOBAL VAR
GROUPNAME="smbgroup"
SYSTEMGROUP="lp,dialout,cdrom"

#FUNTION
function usage {
    PROG=`basename $0`
    cat << EOF

Script to add user in linux system and samba suit
USAGE: $PROG DATAFILE
ARGUMENT:
  DATAFILE : datafile in format:-
     USER:UID:PASSWORD
     ...
  EXAMPLE OF DATAFILE:
  example1:
     #USER:UID:PASSWORD
     user1:1101:user1-password
     user2:1102:user2-password
     ...
Please run as root.

EOF
}

#BEGIN MAIN
DATAFILE=$1
shift
if [ ! $DATAFILE ] || [ ! -f $DATAFILE ]; then
    usage
    exit 1
fi

cat $DATAFILE | grep -v "#" | while read DATA; do
    if [ $DATA ]; then
        USERNAME=$(echo $DATA | cut -d: -f1)
        USERID=$(echo $DATA | cut -d: -f2)
        PASSWORD=$(echo $DATA | cut -d: -f3)
        /usr/sbin/useradd -g $GROUPNAME -G $SYSTEMGROUP -u $USERID -m $USERNAME
        echo "$USERNAME:$PASSWORD" | /usr/sbin/chpasswd
        (echo "$PASSWORD"; echo "$PASSWORD") | /usr/bin/smbpasswd -a -s $USERNAME
        echo "User: $USERNAME , uid: $USERID added."
    fi
done

# chmod 700 /usr/local/sbin/d.samba-adduser

สร้างไฟล์ข้อมูลผู้ใช้ตั้งชื่อว่า userdata เอาไว้ใน /etc/samba/secure และตั้งให้คนอื่นอ่านไม่ได้
# mkdir -p /etc/samba/secure
# vi /etc/samba/secure/userdata

# SAMBA USER LIST
#USER:UID:PASSWORD
admin:1100:admin_password
user1:1101:user1_password
user2:1102:user2_password
user3:1103:user3_password

# chmod -R 700 /etc/samba/secure

สั่งผลิตรายชื่อผู้ใช้
# d.samba-adduser /etc/samba/secure/userdata

ปรับตั้ง samba
# vi /etc/samba/smb.conf

...
[global]
    workgroup = smbdomain
    security = user
    unix charset = utf8
    display charset = utf8
    unix extensions = yes

...

[app]
    comment = Application Dir
    path = /samba/app
    valid users = @smbgroup
    write list = admin
    public = no
    create mask = 0750
    directory mask = 0750
    fake oplocks = yes        ;; increase speed
    writable = no

[data]
    comment = Data Dir
    path = /samba/data
    valid users = @smbgroup
    write list = @smbgroup
    public = no
    create mask = 0770
    directory mask = 0770
    writable = yes

[ftp-pub]
    comment = Public ftp files
    path = /var/ftp/pub
    valid users = @smbgroup
    write list = @smbgroup
    public = no
    create mask = 0775
    directory mask = 0775
    writable = yes

[ftp-com]
    comment = Example.com ftp files
    path = /var/ftp/example.com
    valid users = @smbgroup, @comuser
    write list = @comuser
    public = no
    create mask = 0775
    directory mask = 0775
    writable = yes

[ftp-org]
    comment = Example.org ftp files
    path = /var/ftp/example.org
    valid users = @smbgroup, @orguser
    write list = @smbgroup
    public = no
    create mask = 0775
    directory mask = 0775
    writable = yes
...

เปลี่ยนสิทธ์ของไดเรกทอรี่
# chown -R admin:smbgroup /samba
# chmod -R 0750 /samba/app
# chmod -R 0770 /samba/data

เริ่ม samba ใหม่
# /etc/init.d/samba restart

เสร้จ

Topic: 

all-in-one 9 (backup cron)

สุดท้ายเป็นการเก็บกวาดเล็กน้อย
งานที่ทำคือ...

ย้าย cron ของ apt-proxy มารวมกับการสำรองฐานข้อมูล

จุดประสงค์คือการรวมศูนย์การใช้งาน cron แบบรายวัน ไว้ที่ไฟล์นี้ไฟล์เดียว

ยกเลิก cron ของ apt-proxy
# crontab -e

...
#5 0 * * * /etc/init.d/apt-proxy restart >&2
...

เปลี่ยนมาบรรจุคำสั่งใน d.cron-daily แทน
# vi /usr/local/sbin/d.cron-daily

#!/bin/bash
#-----RESTART APT-PROXY------------------------------------
/etc/init.d/apt-proxy restart

#-----BACKUP DB--------------------------------------------
ROOT="/sys1/sysb/backupdb"
...
สำรองข้อมูลทั้งหมด

แม้จะนอกรายการไปนิดนึง แต่ก็ขอบันทึกไว้เพื่อให้สมบูรณ์อย่างที่ตั้งใจครับ

จะสมมุติว่ามีเครื่องเซิร์ฟเวอร์สำรองอยู่ข้างหลังเซิร์ฟเวอร์เครื่องนี้อีกเครื่องนึง เพื่อที่จะทำ rsync มาคัดลอกข้อมูลในไดเรกทอรี่ /sys1/sysb ทั้งหมดไปเก็บไว้ ถ้าหากเกิดความเสียหายรุนแรงที่เครื่อง server1 นี้ จะได้มีข้อมูลมาสำรองกลับได้

สมมุติว่าชื่อ server2.example.com ไอพี 192.168.1.2
งานนี้จะทำที่เครื่อง server2 ทั้งหมด

ติดตั้ง rsync และ ssh
# aptitude install rsync ssh
สร้างกุญแจให้กับ ssh
# ssh-keygen -t dsa

Enter file in which to save the key (/root/.ssh/id_dsa): <<<--- [ENTER]
Enter passphrase (empty for no passphrase): <<<---  [ENTER]
Enter same passphrase again: <<<---  [ENTER]
Your identification has been saved in /root/.ssh/id_dsa.
Your public key has been saved in /home/user1/.ssh/id_dsa.pub.
The key fingerprint is:
cd:43:9b:3a:b1:60:01:ae:a2:0e:f8:00:21:8c:d8:f0 root@server2

คัดลอกกุญแจไปยัง server1
# ssh-copy-id -i /root/.ssh/id_dsa.pub root@server1

ต่อเข้า server1 เพื่อบันทึกรหัสผ่าน 1 ครั้ง
# ssh server1 -l root

The authenticity of host 'server1 (192.168.1.1)' can't be established.
RSA key fingerprint is 1a:d1:12:f4:bd:d4:4c:11:93:55:9c:75:a7:eb:7d:ae.
Are you sure you want to continue connecting (yes/no)? <<<--- yes
Warning: Permanently added 'server1,192.168.1.1' (RSA) to the list of known hosts.
root@server1's password: <<<--- ROOT@SERVER1-PASSWORD
Now try logging into the machine, with "ssh 'root@server1'", and check in:

  .ssh/authorized_keys

to make sure we haven't added extra keys that you weren't expecting.

#

ต่อเข้าจริงอีก 1 ครั้ง
# ssh root@server1

Last login: Mon Mar  3 21:02:28 2008 from work1.example.com
Linux server1 2.6.18 #1 Mon Mar 3 13:02:29 ICT 2008 i686

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
No mail.
# exit

(***อย่าลืม exit ออกจาก server1 ด้วยนะครับ***)

เสร็จเรื่อง ssh

ต่อเรื่องทำ rsync
สร้างไดเรคทอรี่มารองรับไฟล์จาก server1 สมมุติว่าให้ชื่อ /sys2/server1-sysb
# mkdir -p /sys2/server1-sysb

สร้างไฟล์ d.cron-rsync-server1 ใน /usr/local/sbin
# vi /usr/local/sbin/d.cron-rsync-server1

#!/bin/bash
# IF sys2 IS SEPARATE, MOUNT /sys2 FIRST
#mount /sys2

# RSYNC FROM server1:/sys1/sysb TO /sys2/server1-sysb
rsync -aq --delete -e "ssh -i /root/.ssh/id_dsa" root@server1.example.com:/sys1/sysb/ /sys2/server1-sysb/

# IF sys2 IS SEPARATE, MOUNT /sys2 FIRST
#umount /sys2

ทำให้รันได้
# chmod 700 /usr/local/sbin/d.cron-rsync-server1

ตั้ง cron ให้รันตอนตี 1
# crontab -e

...
#BACKUP server1:/sys1/sysb TO /sys2/server1-sysb DAILY
20 1  * * *   /usr/local/sbin/d.cron-rsync-server1
...

เสร็จหมดแล้ว

Topic: 

all-in-one tips1: แก้ปัญหา fsck ตอนบูต

จากเรื่อง all-in-one ผมแบ่งพาร์ติชั่น /sys1 โดยผูกติดกับ / (root) ไว้ ซึ่งจะทำให้ไม่สามารถยกเลิกการเมานต์ในระหว่างการใช้งานได้
สำหรับเครื่องที่เปิดไว้ตลอด 24 ช.ม.คงไม่มีปัญหาอะไร แต่ถ้าเป็นเครื่องที่ต้องมีการปิดเปิดตามเวลา จะพบปัญหาที่ระบบไฟล์จะทำการตรวจสอบตัวเองเมื่อทำการเมานต์ครบ 30 ครั้ง ซึ่งถ้าพาร์ติชั่นไม่ใหญ่ก็คงไม่เป็นไรนัก เพราะโปรแกรม fsck ทำงานแป๊ปเดียวก็เสร็จ แต่ถ้าเป็นพาร์ติชั่นที่ใหญ่เกิน 200G ขึ้นไปจะเกิดปัญหาการบูตที่นานเกินควร

วิธีแก้คือการบังคับให้บูตในตอนที่ไม่มีใครใช้งานเครื่อง โดยให้บังคับให้ตรวจสอบระบบไฟล์หลังการบูต ด้วยคำสั่ง shutdown ตามด้วยพารามิเตอร์ -F (force check)

เริ่มเลย
แก้ /etc/fstab ตั้งค่า fs_passno ให้พาร์ติชั่นของ /sys1 เป็น 1 เหมือน root file system เพื่อให้ระบบทำการตรวจสอบตอนเปิดเครื่อง
# vi /etc/fstab

...
# <file system> <mount point>   <type>  <options>       <dump>  <pass>
...
#/dev/hda8       /sys1           ext3    defaults        0       2
/dev/hda8       /sys1           ext3    defaults        0       1
...

สมมุติว่าปิดเครื่องสัปดาห์ละ 1 ครั้งคือเย็นวันศุกร์ เราจะตั้งค่า crontab ให้บูตเครื่องพร้อมตรวจสอบไฟล์ในวันพุธ ซึ่งเป็นช่วงกลางสัปดาห์ (ถ้ามีปัญหาอะไรก็จะไม่ยุ่งมากนัก) ตอนตี 1
# crontab -e

# m h  dom mon dow   command
...
#REBOOT FOR FSCK IN WEDNESDAY 1:00am
0 1 * * 3    /sbin/shutdown -F -r now
...

เสร็จแล้วครับ เราจะได้เครื่องที่พร้อมใช้งานเมื่อเปิดทุกวันจันทร์

อ้างอิง

update
เราสามารถตั้งให้การตรวจสอบดิสก์ตอนบูต ให้เป็นแบบซ่อมอัตโนมัติได้ (ยังไม่ทราบผลเสีย) โดยการเปลี่ยนค่าตัวแปรใน /etc/default/rcS

# vi /etc/default/rcS
...
#FSCKFIX=no
FSCKFIX=yes
...
Topic: