ห้องทดลองลินุกซ์

ห้องนี้เป็นห้องทดลอง เมื่อทดลองเสร็จแล้ว ถึงจะนำเสนออีกทีนึงครับ

Topic: 

debian: ทดลอง ltsp

ต้องการเชื่อม Thin Client เข้าหา Thick Server ลินุกส์
โดยจะใช้งานในระบบดอส

งานที่ทำคือ

  • ติดตั้ง ltsp
  • สร้างอิมเมจ FreeDos
  • สร้างแผ่นบูตเข้า ltsp

โฮมเพจ: Linux Terminal Server Project และ wiki.ltsp.org

ltsp ภาษาไทย: การติดตั้ง Linux Server เพื่อใช้งานแทน Netware Server
และ itdestination: มาสร้างระบบ Thin Client ด้วยลีนุกซ์กันเถอะ
และ linuxsiam: การติดตั้ง LTSP Server บน Debain

tfpt ภาษาไทย: itwizard: ตัวอย่างการใช้ TFTP กับการคอนฟิก CISCO Router

ต้องการแพคเกจ : ltsp, dhcp, tftp, nfs, xdmcp, samba
ltsp เป็นตัวหลัก
dhcp เป็นตัวจ่ายไอพี และตัวคัดเลือกการบูต
tftp เป็นตัวทำ file transfer
nfs เป็นตัว file server ของลินุกส์
samba เป็น file server อีกตัวที่ใช้ได้ทั้งลินุกส์และดอส
xdmcp เป็นตัวใช้งาน Xserver ของลินุกส์
rpld เป็นตัวบูต

ติดตั้ง dhcp

จะให้จ่ายไอพีตั้งแต่ 192.168.1.128-192.168.1.254
# apt-get install dhcp3-server

ปรับตั้ง
# vi /etc/default/dhcp3-server

...
INTERFACES="eth0"

# vi /etc/dhcp3/dhcpd.conf

...
option domain-name "example.com";
option domain-name-servers dns.example.com;
...
subnet 192.168.1.0 netmask 255.255.255.0 {
  range 192.168.1.128 192.168.1.254;
  option routers server1.example.com;
}

สั่งเริ่มใหม่
# /etc/init.d/dhcp3-server restart

ติดตั้งแพคเกจอื่น

# apt-get install atftpd portmap nfs-kernel-server


ติดตั้ง ltsp

เอาจาก http://wiki.ltsp.org/twiki/pub/Ltsp/Documentation/ltspguide.pdf
# apt-get install ltsp-utils

ติดตั้ง
# ltspadmin
<<<--- Install/Update LTSP Packages

Where to retrieve packages from?
<<<--- [http://ltsp.mirrors.tds.net/pub/ltsp/ltsp-4.2/]

In which directory would you like to place the LTSP client tree?
<<<--- [/opt/ltsp]

If you want to use an HTTP proxy, enter it here
<<<--- ใส่ค่าพร๊อกซี่เซิร์ฟเวอร์ของเรา

If you want to use an FTP proxy, enter it here
<<<--- ใส่ค่าพร๊อกซี่เซิร์ฟเวอร์ของเรา

Correct? (y/n/c)
<<<--- y

Component Size (kb) Status
[*] ltsp_core 80916 Not installed
[*] ltsp_debug_tools 4284 Not installed
[*] ltsp_kernel 27708 Not installed
[*] ltsp_libusb 896 Not installed
[*] ltsp_localdev 4 Not installed
[*] ltsp_pciutils 428 Not installed
[*] ltsp_perl 28072 Not installed
[*] ltsp_rdesktop 1872 Not installed
[*] ltsp_scanners 29308 Not installed
[*] ltsp_vnc_module 452 Not installed
[ ] ltsp_x_addtl_fonts 17368 Not installed
[ ] ltsp_x_core 97716 Not installed
<<<--- q

Ready to install/update the selected LTSP packages? (y/n)
<<<--- y

...โปรแกรมจะดาวน์โหลดแพคเกจที่เกี่ยวข้องเข้ามาติดตั้ง...

Configure LTSP
Make a selection:
<<<--- c (Configure the services manually)

ตั้งค่าอินเทอร์เฟส
Make a selection:
<<<--- 2 (Interface selection)

Interface IP Address Netmask Network Broadcast Used
eth0 192.168.1.5 255.255.255.0 192.168.1.0 192.168.1.255 <-----
Only 1 Ethernet interface found, using eth0
Press <enter> to continue..

ปรับตั้ง dhcp
Make a selection:
<<<--- 3 (DHCP configuration)

Do you want to build a dhcpd.conf file (y/n) ?
<<<--- y
The dhcpd config file has been created as: /etc/dhcp3/dhcpd.conf.sample
Press to continue..

ปรับตั้ง tftpd
Make a selection:
<<<--- 4 (TFTP configuration)

Do you want to enable tftpd (y/n) ?
<<<--- y

กดไล่ค่าปริยายไปจนหมดแล้วจึงกด r เพื่อออกมา
กด s ดูความเรียบร้อย
และ q เพื่อออกมาสู่เมนูหลัก
และ q เพื่อออกจากโปรแกรม

สั่งเริ่มเซอร์วิสใหม่
# /etc/init.d/nfs-kernel-server restart
# /etc/init.d/portmap restart


สร้างอิมเมจ FreeDos

ดู samba: เรียกใช้งานจากดอสโดยใช้ดิสเก็ต 1 แผ่น
โดยเมื่อปรับแต่งจนสามารถบูตเข้าระบบได้แล้ว พร้อมทั้งปรับแต่ง autoexec.bat ให้เรียกใช้เน็ตเวิร์กไดรฟ์เรียบร้อยแล้ว
(วิธีการไม่ได้เขียนครับ เพราะเป็นเรื่องของการปรับแต่งดอสตามสภาพใช้งานของแต่ละท่าน)
เราก็เอาแผ่นดิสเก็ตแผ่นนี้มาทำเป็นดิสก์อิมเมจ
$ dd if=/dev/fd0 of=freedos.img
$ mcopy a:/KERNEL.SYS .
$ sudo apt-get install mknbi
$ mknbi-fdos --output=freedos.nb KERNEL.SYS freedos.img

หลังจากนั้นก็เอาไฟล์ freedos.nb ที่ได้ ไปใส่ในไดเรคทอรี่ /tftpboot ของเครื่องเซิร์ฟเวอร์


สร้างแผ่นบูตเข้า ltsp

เอาบราวเซอร์ไปที่ http://www.rom-o-matic.net/5.4.2/
(อาจตรวจสอบรุ่นใหม่ ๆ ได้จากหน้าหลัก)

ไปที่
1.Choose NIC/ROM type:
<<<--- เลือกชนิดการ์ดแลนให้ตรงกับเครื่องลูก

2.Choose ROM output format:
<<<--- Floppy bootable ROM Image (.zdsk)

4.To generate and download a ROM image press:
<<<--- Get ROM

เซฟไฟล์ไว้ แล้วเขียนลงแผ่นฟลอปปีดิสก์
(ตัวอย่าง ของผมเป็น via_rhine ได้ไฟล์ชื่อ eb-5.4.2-via-rhine.zdsk)
# cat eb-5.4.2-via-rhine.zdsk > /dev/fd0
จะเอาไปทำแผ่นบูต


ปรับแต่ง dhcp ขั้นสุดท้าย

ทำที่เครื่องเซิร์ฟเวอร์
ถ้าในระบบเรามีการ์ดแลนเพียงชนิดเดียว เราก็แค่สร้างไฟล์ freedos.nb เพียงไฟล์เดียว
ไฟล์ /etc/dhcp3/dhcpd.conf จะเป็นดังนี้

# dhcpd.conf

ddns-update-style             ad-hoc;

option subnet-mask            255.255.255.0;
option broadcast-address      192.168.1.255;
option routers                192.168.1.5;
option domain-name-servers    192.168.1.5;
#option domain-name            "your_domain.org";   # You really should fix this
option domain-name            "example.com";   # You really should fix this
option option-128 code 128 = string;
option option-129 code 129 = text;

get-lease-hostnames           true;

next-server                   192.168.1.5;
option root-path              "192.168.1.5:/opt/ltsp/i386";

subnet 192.168.1.0 netmask 255.255.255.0 {
    range   192.168.1.128   192.168.1.253;
    filename "/tftpboot/freedos.nb";
}

แต่ในทางปฏิบัติคงเป็นไปไม่ได้ที่จะมีการ์ดแลนเพียงชนิดเดียว
ดังนั้นเราจึงต้องสร้างอิมเมจหลายไฟล์ และแบ่งการใช้ด้วย mac address
สมมุติว่าเราสร้างอิมเมจสำหรับ
การ์ด via_rhine ในชื่อ fdvr.nb ค่า mac address ของเครื่องลูกเป็น 00:11:22:33:44:55
และการ์ด ne2000 ในชื่อ fdne2000.nb ค่า mac address ของเครื่องลูกเป็น 01:02:03:04:05:06
ไฟล์ /etc/dhcp3/dhcpd.conf จะเป็นดังนี้

# dhcpd.conf

ddns-update-style             ad-hoc;

option subnet-mask            255.255.255.0;
option broadcast-address      192.168.1.255;
option routers                192.168.1.5;
option domain-name-servers    192.168.1.5;
#option domain-name            "your_domain.org";   # You really should fix this
option domain-name            "example.com";   # You really should fix this
option option-128 code 128 = string;
option option-129 code 129 = text;

get-lease-hostnames           true;

next-server                   192.168.1.5;
option root-path              "192.168.1.5:/opt/ltsp/i386";

host ws128 {
    hardware ethernet  00:11:22:33:44:55;
    fixed-address  192.168.1.128;
    filename  "/tftpboot/fdvr.nb";
}
host ws129 {
    hardware ethernet  01:02:03:04:05:06;
    fixed-address  192.168.1.129;
    filename  "/tftpboot/fdne2000.nb";
}

subnet 192.168.1.0 netmask 255.255.255.0 {
    range   192.168.1.130   192.168.1.253;
    filename "/tftpboot/freedos.nb";
}

เริ่ม dhcp ใหม่
# /etc/init.d/dhcp3-server restart

วิธีดูค่า mac address อาจดูตอนบูตด้วยแผ่นบูตก็ได้
หรือดูจากลินุกส์ด้วยคำสั่ง ifconfig
หรือดูจากดอสด้วยคำสั่ง ipconfig /all

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

เขียนหยาบและสเปะสปะหน่อยนะครับ เป็นเรื่องใหม่ที่ไม่เคยศึกษามาก่อน :P
ได้เปลื้องหนี้คุณสมเจตน์แล้ว ค่อยสบายหน่อย

เดี๋ยวถ้ามีเวลา จะกลับมาศึกษาต่ออีกที

บันทึก customize debian

update 50-07-07

ติดตั้งแพกเกจที่ใช้บ่อย
มี vim, less, screen
# aptitude install vim-full less screen
แก้ default shell
เดเบียนใช้ /bin/sh เป็น default shell
แต่ /bin/bash ใช้งานสะดวกกว่า
# usermod -s /bin/bash user1
แก้ default editor
เดเบียนใช้ nano เป็น default editor แต่เรานิยมใช้ vi
แก้ /etc/profile ให้มีตัวแปร EDITOR
# vi /etc/profile
...
EDITOR="/usr/bin/vi"
export EDITOR
...

ปรับปรุง: จาก debianclub.com: system-wide default editor
# update-alternatives --config editor
<<<--- เลือกหมายเลขหน้า vim

แก้ default vi
เดเบียนใช้ nvi เป็น default vi
แต่เราต้องการ syntax color เลยเปลี่ยนมาใช้ vim
# aptitude install vim-full
vim syntax color
ปกติ vim จะไม่ทำ syntax color
แก้ /etc/vim/vimrc.local
# vi /etc/vim/vimrc.local
...
syntax on
ถ้าต้องการใช้ sudo
แก้ไขไฟล์ /etc/sudoers ลบคอมเมนต์หน้าบรรทัดนี้
%sudo ALL=NOPASSWD: ALL

ถ้าเราชื่อ user1 ใช้คำสั่ง usermod แก้ให้เราอยู่ในกลุ่ม sudo ด้วย
# usermod -g sudo user1

ปรับตั้ง Proxy (ถ้าใช้พร๊อกซี่)
System -> Preference -> Network Proxy

แก้ไข wgetrc ให้ใช้งาน wget ผ่านพร๊อกซี่ได้
$ sudo vi /etc/wgetrc

...
http_proxy = http://proxy.example.com:8080/
ftp_proxy = http://proxy.example.com:8080/
...
ปรับตั้งภาษาไทย
System -> Preferenct -> Keyboard
TAB Layouts -> Add -> Thailand
TAB Layout Options -> Group Shift/Lock behavior -> Alt+Shift changes group.

ให้แสดงสถานะของภาษาบนพาเนล
คลิกขวาที่พาเนล Add to Panel -> Keyboard Indicator -> Add

ติดตั้ง libthai
$ sudo aptitude install libthai0

ติดตั้งฟอนต์ไทย
$ sudo aptitude install ttf-thai-tlwg otf-thai-tlwg

debian: Compiz

ต้องการปรับหน้าตาเดเบียนให้ดูทันสมัยทัดเทียมเพื่อนพ้องบ้าง
ก่อนอื่นต้องตรวจดูว่าการ์ดแสดงผลเราสามารถใช้งาน compiz ได้หรือไม่ โดยการดาวน์โหลดสคริปต์ Compiz-Check มารัน

$ wget http://blogage.de/files/9124/download -O compiz-check
$ chmod +x compiz-check
$ ./compiz-check

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

 Driver in use:         YYY

1.ติดตั้งแพกเกจ

$ sudo aptitude install compiz compizconfig-settings-manager\
 compiz-fusion-plugins-main compiz-fusion-plugins-extra compiz-gnome compiz-gtk

2.ปรับแก้ไฟล์ /etc/X11/xorg.conf

เดี๋ยวนี้เดเบียนรุ่นใหม่ ๆ ตั้งแต่ squeeze ขึ้นไป ไม่มีไฟล์ xorg.conf เสียแล้ว แต่เราสามารถสร้างขึ้นใหม่ได้เอง ถ้าไฟล์นี้มีอยู่แล้วก็แก้ไขจากไฟล์เดิมได้เลย

$ sudo vi /etc/X11/xorg.conf
Section "ServerLayout"
        Identifier "XXX"
        Option "AIGLX" "true"
EndSection
Section "Extensions"
        Option "Composite" "enable"
EndSection
Section "Device"
        Identifier "XXX"
        Driver     "YYY"
        Option "AllowGLXWithComposite" "true"
        Option "RenderAccel" "true"
EndSection
Section "Screen"
        Identifier "XXX"
        Option "AddARGBGLXVisuals" "true"
        Option "XAANoOffscreenPixmaps" "true"
EndSection

ค่า Identifier XXX และ Driver YYY ให้ใส่ตามจริง

3.ปรับตั้งให้ทำงานตอนเริ่มระบบ X
ให้มาใช้ compiz แทน metacity

$ echo "export WINDOW_MANAGER=/usr/bin/compiz" >> ~/.gnomerc

4.ล๊อกเอาต์และล๊อกอินใหม่

5.ปรับตั้งแบบให้ทำงานน้อยที่สุด
เมนู System -> Preference -> CompizConfig Settings Manager
เลือก Effects - Windows Decoration
หากต้องการปรับแต่งให้มีลูกเล่นแบบพิสดาร ศึกษาวิธีการต่อได้ที่ http://wiki.compiz.org/

เสร็จแล้วครับ

เรียบเรียงจาก

ปรับปรุง

Topic: 

debian: Customize Sid

Customize Sid Desktop

สมมุติว่าติดตั้งเดเบียนจาก debootstrap พร้อมทั้งทำ apt-proxy ไว้เรียบร้อยแล้ว

แก้ไข source.list และปรับปรุงแพกเกจ
# vi /etc/apt/source.list
deb	http://server1.example.com:9999/debian main contrib non-free
deb-src	http://server1.example.com:9999/debian main contrib non-free
deb	http://server1.example.com:9999/debian-multimedia main

# aptitude update
# aptitude dist-upgrade

เก็บตกแพกเกจที่จำเป็นเบื้องต้น
# aptitude install ssh vim-full less screen

ปรับแต่ง vimrc ปริยาย (ดู vi โดยย่อ ประกอบ)
# vi /etc/vim/vimrc.local

set tabstop=4
set expandtab
set autoindent
set smartindent
set nu!
syntax on

ปรับ vim ให้เป็น editor ปริยาย
# update-alternatives --config editor

เลือกหมายเลขหน้า vim-full
สร้างผู้ใช้ชั่วคราว
# useradd temp1
# passwd temp1
ติดตั้ง Gnome
# aptitude install gnome xorg

ถ้ามีผู้ใช้หลายคน ควรปรับค่าปริยายของ Gnome
ปรับภาษาไทย ตาม gnome: เกร็ดการปรับตั้งค่าคีย์บอร์ดภาษาไทย
# gconftool-2 --direct \
--config-source xml:readwrite:/etc/gconf/gconf.xml.defaults \
--type list --list-type string \
--set /desktop/gnome/peripherals/keyboard/kbd/layouts [us,th]
# gconftool-2 --direct \
--config-source xml:readwrite:/etc/gconf/gconf.xml.defaults \
--type list --list-type string \
--set /desktop/gnome/peripherals/keyboard/kbd/options ["grp grp:alt_shift_toggle"]

ยกเลิกการล๊อกจอภาพ
# gconftool-2 --direct \
--config-source xml:readwrite:/etc/gconf/gconf.xml.defaults \
--type bool \
--set /apps/gnome-screensaver/lock_enabled false

ปรับเรื่องรหัสอักขระตอนเมานต์ usb กับ cdrom ทำให้เป็น utf8 อัตโนมัติ
# gconftool-2 --direct \
--config-source xml:readwrite:/etc/gconf/gconf.xml.defaults \
--type list --list-type string \
--set /system/storage/default_options/iso9660/mount_options [uid=,utf8,iocharset=utf8]
# gconftool-2 --direct \
--config-source xml:readwrite:/etc/gconf/gconf.xml.defaults \
--type list --list-type string \
--set /system/storage/default_options/udf/mount_options [uid=,utf8,iocharset=utf8]
# gconftool-2 --direct \
--config-source xml:readwrite:/etc/gconf/gconf.xml.defaults \
--type list --list-type string \
--set /system/storage/default_options/vfat/mount_options [shortname=lower,uid=,utf8,iocharset=utf8]

ติดตั้งแพกเกจกันเหนียวอื่น ๆ
# aptitude install smbfs
สร้างผู้ใช้
สมมุติว่าผู้ใช้ชื่อ user1 จะให้อยู่ในกลุ่ม mygroup
ของระบบคือ
# useradd -m -g mygroup -G lp,dialout,cdrom,floppy,audio,video,plugdev,lpadmin -s /bin/bash user1
# passwd user1

-m คือให้ยึดโครงสร้างไดเรกทอรี่ /home/user1 ตามแบบโครงสร้างใน /etc/skel
-g คือกลุ่มหลัก
-G คือกลุ่มที่ผู้ใช้คนนี้จะเข้าไปเป็นสมาชิก
-s คือเปลี่ยนเชลล์ จาก /bin/sh ไปเป็น /bin/bash ซึ่งปรับแต่งได้มากกว่า

ของ samba คือ
# smbpasswd -a -s user1

ปรับแก้ sudo
# vi /etc/sudoers
...
%mygroup    ALL=(ALL) ALL
..
ภาษาไทย
# aptitude install libthai0 ttf-thai-tlwg otf-thai-tlwg

หรือพม่า
# aptitude install ttf-sil-paduak

หรือลาว
# aptitude install ttf-lao

หรือเขมร
# aptitude install ttf-khmeros

ถ้าเราอยู่หลัง proxy ควรปรับตั้งให้ wget ใช้สำหรับดาวน์โหลดได้
# vi /etc/wgetrc
...
http_proxy = http://server1.example.com:8080/
ftp_proxy = http://server1.example.com:8080/
...
ตัวติดตั้งแพกเกจของ Gnome
# aptitude install gdebi
บราวเซอร์ ปกติ epiphany ใช้งานได้ดีและเร็วอยู่แล้ว แต่หากต้องการความสามารถเพิ่ม อาจติดตั้ง iceweasel เพิ่มได้
# aptitude install iceweasel flashplugin-nonfree
ออฟฟิศ
# aptitude install openoffice.org
มัลติมีเดีย
# aptitude install mplayer xmms gxine vlc audacity toolame k3b w32codecs
การทำงานเกี่ยวกับภาพ
# aptitude install imagemagick jhead libjpeg-progs
MSN
# aptitude install pidgin

เมื่อเรียบร้อยแล้วก็ให้เริ่ม gdm ใหม่ได้เลย
# /etc/init.d/gdm restart

ผู้ใช้ที่ได้ล๊อกอินเข้าไป คงเหลือเพียงเพิ่มสถานะของภาษาลงบนพาเนล
คลิกขวาบนพาเนล -> Add to Panel -> Keyboard Indicator -> Add
แล้วก็จะสามารถทำงานได้ทันทีครับ

ฝากเสริมด้านเดสก์ทอปให้ด้วยนะครับ

debian: ติดตั้ง gnome แบบต่าง ๆ

หลังจากผ่านการติดตั้งแบบ Net Install หรือแบบ debootstrap มาแล้ว
ต้องติดตั้ง Xwindows ก่อน
# aptitude install x-window-system-core

ตามด้วย gnome แบบต่าง ๆ ดังนี้

gnome แบบเต็ม
# aptitude install gnome
แบบน้อยที่สุด
# aptitude install gnome-core gdm
กินเนื้อที่ประมาณ 1.4G
แบบมากขึ้น
# aptitude install gnome-fifth-toe
กินเนื้อที่ประมาณ 2.1G (clean แล้วเหลือ 1.6G)
มากขึ้นเกือบสุด
# aptitude install gnome-desktop-environment
กินเนื้อที่ประมาณ 2.1G

เลือกใช้แบบน้อยที่สุด และส่วนเสริมที่ใช้
# aptitude install gnome-core gdm gnome-themes gimp inkscape evolution vim-full dosemu libthai0 ttf-thai-tlwg otf-thai-tlwg ttf-thai-arundina openoffice.org
กินเนื้อที่ 2.3G
หลังจากสั่ง aptitude clean เหลือสุทธิ 1.9G

อ้างอิง - wiki.debian.org: Installing GNOME

Topic: 

debian: บันทึก customize lenny desktop

สมมุติว่าถูกติดตั้งจาก debootstrap หรือแผ่น Net Install
หลังจากทำ Customize บน Terminal เรียบร้อยแล้ว ทำต่อส่วนของ Desktop ด้วย

ติดตั้งเดสก์ทอป
$ sudo aptitude install xorg gnome
แพกเกจระบบ
$ sudo aptitude install less gdebi
การเพิ่มผู้ใช้งาน
ถ้าต้องการเพิ่มผู้ใช้งานเดสก์ทอป สมมุติว่าชื่อ user1 ควรสั่งให้เป็นสมาชิกในกลุ่มของระบบด้วย
$ sudo useradd -g dialout,cdrom,floppy,audio,video,plugdev user1 ...
ภาษาไทย
$ sudo aptitude install libthai0 pango-libthai scim-thai
เพิ่มคีย์บอร์ดไทย
Desktop -> Preference -> Keyboard
- TAB Layouts -> Add -> Thailand
- TAB Layout Options -> Group Shift/Lock behavior -> Alt+Shift changes group.
เพิ่มการแสดงสถานะของภาษา
คลิกที่แถบพาเนล บนหรือล่างตามต้องการ คลิกขวา -> Add to panel -> Keyboard Indicator
ปิดการล๊อกจอภาพของ Screen saver
Desktop -> Preference -> Screensaver -> ปิด Lock screen when screensaver is active
บราวเซอร์ Iceweasel
$ sudo aptitude install iceweasel
ติดตั้ง flash เพิ่ม
$ sudo aptitude install flashplugin-nonfree
ออฟฟิศ
$ sudo aptitude install openoffice.org
มัลติมีเดีย
$ sudo aptitude install mplayer xmms gxine vlc audacity toolame k3b

หากต้องการทำงานเกี่ยวกับ audacity มากเป็นพิเศษ ให้ติดตั้งปลั๊กอินชื่อ swh-plugins
$ sudo aptitude install swh-plugins

ภาพ
$ sudo aptitude install imagemagick jhead jpegtran
ดอส
$ sudo aptitude install dosemu freedos
แก้ไขไฟล์ /etc/dosemu/dosemu.conf ให้ใช้เครื่องพิมพ์เครือข่ายได้
$ sudo vi /etc/dosemu/dosemu.conf
สมมุติว่ามีเครื่องพิมพ์สองตัว ชื่อ epson และ epson2
...
$_printer_command = "lpr -l -P epson, lpr -l -P epson2"
...

หมายเหตุ
ถ้าเป็น dosemu รุ่น 1.40 ขึ้นไป เปลี่ยนรูปแบบเป็น

...
$_lpt1 = "lpr -l -P epson"
$_lpt2 = "lpr -l -P epson2"
...

แก้ไขไฟล์ autoexec.bat และ config.sys ที่ /etc/freedos ปรับเปลี่ยนตามต้องการ

qemu
$ sudo aptitude install qemu uml-utilities module-assistant
ส่วนของ kqemu
$ sudo m-a a-i kqemu
ตั้งให้ฟอร์เวิร์ดแพกเก็ต เตรียมการให้กับส่วนของเน็ตเวิร์ก
$ sudo bash -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
$ sudo vi /etc/sysctl.conf
...
net.ipv4.ip_forward=1
...

ดูเพิ่มเติมที่ ubuntu: feisty - qemu

เครื่องพิมพ์
ขณะที่บันทึก gnome-cups-manager ยังใช้งานได้ไม่ดี ได้บ้าง ไม่ได้บ้าง จำเป็นต้องใช้แบบบรรทัดคำสั่ง
ถ้าเป็นเครื่องพิมพ์เครือข่ายที่ต่อกับเซิร์ฟเวอร์ลินุกซ์ ใช้คำสั่ง
$ sudo lpadmin -p $PRINTER -u allow:all -E -v smb://$USER:$PASSWORD@$SERVER/$SHARE -P /$PATH/$TO/$PPD
ถ้าเป็นเครื่องพิมพ์ที่ต่อกับเซิร์ฟเวอร์วินโดวส์ เอาชื่อผู้ใช้ออก เหลือแค่
$ sudo lpadmin -p $PRINTER -u allow:all -E -v smb://$SERVER/$SHARE -P /$PATH/$TO/$PPD
อย่าลืมแทนค่าตัวแปรต่าง ๆ ให้เป็นค่าที่เราต้องการ
Chat - MSN
$ sudo aptitude install pidgin
CHM - Compiled HTML Help
$ sudo aptitude install xchm

debian: บันทึกแก้ปัญหา Xorg ใน sid

ช่วงนี้ Xorg ของ sid มีปัญหาว่าไม่สามารถปรับตั้งไฟล์ /etc/X11/xorg.conf ได้
เนื่องจาก Bug #443004 ทำให้ในการติดตั้งใหม่ไม่สามารถใช้งาน X-window ได้

แก้ปัญหาโดยเปลี่ยน repository ไปเป็น lenny ชั่วคราว
$ sudo sed -i 's/sid/lenny/g' /etc/apt/sources.list

แลัวจึงติดตั้ง xorg ใหม่
$ sudo aptitude purge xorg
$ sudo aptitude update
$ sudo aptitude install xorg

เมื่อปรับตั้งเรียบร้อยแล้ว จึงเปลี่ยน repo กลับมาเป็น sid ก็จะสามารถใช้งานได้แล้ว
$ sudo sed -i 's/lenny/sid/g' /etc/apt/sources.list
$ sudo aptitude update
$ sudo aptitude dist-upgrade

เรียบร้อยแล้ว

หมายเหตุ
ช่วงนี้ gnome มีการเปลี่ยนรุ่น ก็สามารถใช้วิธีเดียวกันแก้ปัญหาได้
หรืออาจติดตั้งเป็นรุ่น lenny เลย แล้วจึงอัปเกรดมาเป็น sid ทีหลังก็ได้ครับ

Topic: 

Resize รูปด้วย Shell script แบบมี GUI

ขอเขียนลงห้องทดลองด้วยคนนะครับ :D

Shell script อันนี้เขียนไว้นานแล้วครับ (ประมาณ 2 ปีแล้ว :P) ให้ทำการ resize รูป JPEG ด้วยคำสั่ง convert จาก ImageMagick แล้วช่วงนั้นก็อยากเขียน shell script ให้มี GUI ก็เลยใช้ Xdialog ในการสร้าง GUI ขึ้นมา แต่ตอนนั้นสคริปต์มีข้อจำกัดนิดหน่อย คือทำงานกับชื่อแฟ้มหรือไดเร็คทอรีที่มี space ไม่ได้ โค้ดก็ไม่สวย :P (ตอนนั้นยังรู้อะไรไม่ค่อยมากนัก จริงๆ มันไม่ได้มีอะไรใหม่หรอก)

วันนี้มีเหตุให้ต้องมานั่ง resize ภาพประมาณ 200 กว่าภาพได้ เลยได้ฤกษ์ขุดขึ้นมาลองใช้ดูหน่อย แล้วก็นั่งแก้ไปแก้มา ก็แก้ให้ข้อจำกัดเหล่านั้นหมดไปแล้วเลยมาเสนอที่นี่ดีกว่า สคริปต์นี้ต้องการ ImageMagick กับ Xdialog ถึงจะทำงานนะครับ ใครใช้ *buntu ก็คง apt-get ได้เลย

ถ้าดูแล้วมีตรงไหนน่าแก้ไขก็แก้ได้เลยนะครับ

คำอธิบายทั้งหลายก็พยายามเขียนเอาไว้ใน comment ของ code ไว้แล้ว

#!/bin/bash


#jpgresize shell script with Xdialog.
#This program depends on ImageMagick (convert), Xdialog, textutils, expr and bc

#variables
tempfile=`mktemp`

#======================================================================

#functions
# numfiles -> it gets number of files to calculate the percentage.
# setcount -> This function sets the variables, named COUNT2 and COUNT3, to calculate the percentage. COUNT4 is for use with --gauge option.
# Xresize -> This function resizes the jpg and JPG files and displays progress bar using COUNT4 value.
# prog_term -> terminate the program.
# help_me -> display help.
# version -> display version.
# wizard_start -> start the wizard.
# choose_dir_wiz -> This function chooses a directory and keep the value into $tempfile.
# get_dir -> This function gets the value of a directory that will be used to keep the resized files. 
# get_size -> it gets the size (using slide bar) to work with convert. 

numfiles() {
	
	
	for f in *.[jJ][pP][gG]
		do
			COUNT=`expr $COUNT + 1`
		done
	
}

setcount() {
	COUNT2=`echo "scale=2; 100/$COUNT" |bc -l`
	COUNT3=`echo "scale=2; 100/$COUNT" |bc -l`
	COUNT4=`echo "$COUNT2" |cut -f1 -d"."`
}



Xresize() {
	for files in *.[jJ][pP][gG]
	do
		convert -resize "$SIZE"x"$SIZE" "$files" "$RESIZEDIR"/"r_$files" 2>/dev/null
		if [ $COUNT4 -gt 100 ]; then
			COUNT4=100
		fi
		echo $COUNT4
		echo XXX
		echo "Resizing... ($COUNT2 percent)"
		echo XXX
		COUNT2=`echo "scale=2; $COUNT2 + $COUNT3" | bc -l`
		COUNT4=`echo "$COUNT2" |cut -f1 -d"."`
	done| Xdialog --title "JPG Resizer" --gauge "Resizing..." 10 70 0
	
}

prog_term() {
	Xdialog --title "JPG Resizer" --msgbox "Program terminated" 10 30
	rm -f $tempfile
	exit 1
}


helpme() {
cat << EOF >$tempfile
	Script for JPEG resizing with wizard (from Xdialog). 
	Copyright (c) 2005 by Sahachart Anukulkitch. Distributed under GPL.
	
	The script will resize all JPEG files with specified size 
	(in percent) using wizard. Hope this will make picture
	resizing job easier ;). 
	
	Actually, if you want, just replace "jpg" in the script to other 
	image extensions such as png, bmp, xpm and so on. This script will 
	work for you to resize other image file types.
	
	Usage:
		Xjpgresize [option]
			-h display this help dialog and exit
			-v display version dialog and exit
	
		Without option, the script will start the wizard to resize 
		JPEG files. Choose the directory containg files you want, 
		enter the size and directory name that will be used to 
		keep the resized files. 
EOF
Xdialog --title "JPG Resizer Help"  --textbox $tempfile 30 100
rm -f $tempfile
exit 0
}

version() {
Xdialog --title "JPG Resizer" --msgbox "The JPEG resizing script version 0.3.1x, with Xdialog" 10 70
exit 0
}

wizard_start() {
	Xdialog --title "JPG Resizer" --left --wizard \
	--yesno "Welcome to JPG Resizer Wizard version 0.3.1x. \\nClick Next to proceed \\nor Cancle to terminate program. \\n \\n \\n \\nCopyright (c) 2005 by Sahachart Anukulkitch. \\n \\nYou can modify or redistributed this under GPL" 30 70
	case $? in
		0) choose_dir_wiz
		;;
		1) prog_term
		;;
		3) prog_term
	esac

}
	
choose_dir_wiz() {
	Xdialog --title "JPG Resizer - Please choose a directory" --wizard\
	--help "Please choose the directory contaning files \\n you wish to resize"\
	--dselect ~ 30 70 2>$tempfile

	case $? in
		0)  
		D_SELECTED=`cat $tempfile`
		cd "$D_SELECTED"		
		#test if there are JPEG files in the directory

		if [ -z `ls |grep -i "\.jpg"` ] 2>/dev/null
			then
			Xdialog --title "JPG Resizer" \
			--msgbox "There're no JPEG files in this directory." 10 70
			rm -rf $tempfile
			exit 1
		fi
		get_dir
		;;
		1) prog_term
		;;
		3) wizard_start
		;;
	esac
}

get_dir() {
	Xdialog --title "JPG Resizer" --left --wizard \
	--inputbox "Please enter directory name you want to keep files" 30 70 \
	"resized"  2>$tempfile

	case $? in
		0) 
		RESIZEDIR="`cat $tempfile`"
		get_size
		;;
		1) prog_term
		;;
		3) choose_dir_wiz
		;;
	esac
}

get_size() {
	Xdialog --title "JPG Resizer" --wizard \
	--rangebox "Use slide bar to choose size (%)" 30 70 1 99 50 2>$tempfile

	case $? in
		0)
		SIZE="`cat $tempfile`%"
		;;
		1) prog_term
		;;
		3) get_dir
		;;
	esac
}

#==============================================================================

#main program
#test the arguments using case

case $1 in
	-h)
		helpme
		;;
	-v)
		version
		;;
	*)
		;;
esac

#start the wizard

wizard_start



#check if directory is existed or create directory
if [ -d $RESIZEDIR ]; then
	Xdialog --title "JPG Resizer" --msgbox "The `pwd`/$RESIZEDIR exists." 7 70
else
	
	Xdialog --title "JPG Resizer" --yesno "Create `pwd`/$RESIZEDIR?" 10 70
	case $? in
		0) mkdir -p $RESIZEDIR
		;;
		1) prog_term
		;;
	esac
fi
#get number of files
numfiles
#set the COUNT- variables
setcount
#resize and display progress bar
Xresize

#get exit status from progress bar
case $? in 
	0)
	Xdialog --title "JPG Resizer" --msgbox "Resizing completed! \\n Click OK to finish the Wizard" 30 70 
	;;

	255)
	Xdialog --title "JPG Resizer" --msgbox "Program aborted!" 7 30
	;;
esac

rm -f $tempfile
#End

ปล. จริงๆ ใช้ gwenview หรือ digikam ทำเอาก็ได้ อาจจะดีกว่าด้วย เพราะมีปลั๊กอิน batch resize แถม Xdialog ก็หยุดพัฒนาแล้วด้วย เลยมีแต่หน้าตาน่าเกลียดๆ แบบ gtk+ (1.x) เท่านั้น แต่อันนี้เขียนเอาไว้ลองวิชาครับ อยากลอง port ไปใช้ kdialog ของ KDE ดูเหมือนกัน แต่ความยืดหยุ่นสู้ Xdialog ไม่ได้

debian: ติดตั้งแพกเกจ

บันทึกการติดตั้งแพกเกจต่าง ๆ

Topic: 

debian: พิมพ์ลงไฟล์ pdf

มีงานที่ต้องแปลงไฟล์ภาพ tif เป็น pdf ถ้าหากเป็นภาพหน้าเดียว ก็สามารถแปะลงใน OpenOffice Writer แล้วส่งออกเป็น pdf ได้
แต่ถ้าหากเป็นเอกสารหลายหน้า จะไม่สามารถทำได้
จึงคิดว่าน่าจะใช้วิธีสร้างเครื่องพิมพ์เทียมให้ส่งออกเป็น pdf ดีกว่า ซึ่งจะทำให้สามารถใช้งานได้หลากหลายกว่า ไม่ใช่กับเฉพาะไฟล์ภาพ tif อย่างเดียว

ค้นไปค้นมา ปรากฎว่าแพกเกจ cups เขาทำมาให้อยู่แล้ว ชื่อ cups-pdf ลงเสร็จแล้วก็สามารถใช้งานได้เลย
$ sudo aptitude install cups-pdf

งานต่อมาคือเพิ่มเครื่องพิมพ์
System -> Administration -> Printing
โดยเพิ่มเครื่องพิมพ์เป็น Virtual PDF Printer
และให้ไดรเวอร์เป็น Generic -> Postcript color printer
เสร็จเรียบร้อยแล้ว

สำหรับ tif แบบหลายหน้า สามารถใช้ Document Viewer คือ Evince เปิดเอกสาร แล้วพิมพ์ผ่านเครื่องพิมพ์ postscript-color-printer อันใหม่นี้ได้เลย โดยไฟล์ pdf ที่ได้ จะไปอยู่ในไดเรกทอรี่บ้านของเรา ~/PDF

update-เพิ่มเติมคำสั่ง gs

งานพิมพ์จากวินโดวส์
ให้เลือกเครื่องพิมพ์เป็น Postscript แล้วพิมพ์ลงไฟล์
ต่อจากนี้อาจใช้ gs for windows ทำต่อ
หรือใช้คำสั่งบนลินุกซ์คือ gs รูปแบบคำสั่งคือ
$ gs -dNOPAUSE -sDEVICE=pdfwrite -sOutputFile=filename.pdf -- filename.prn
-dNOPAUSE คือให้ทำรวดเดียวไม่ต้องหยุด
-sDEVICE=pdfwrite คือทำเป็น pdf
-- คือทำเสร็จแล้วให้ออกจากโปรแกรมเลย

เพิ่มเติม -sDEVICE
png = png16m
แต่คุณภาพไม่ค่อยดี สู้เป็น pdfwrite ไม่ได้ (เอามาแก้ใน gimp ได้)
ดูรายละเอียดของ DEVICE ได้จากคำสั่ง gs --help

เพิ่มเติมสำหรับวินโดวส์
หากต้องการทำต้นฉบับหนังสือเพื่อส่งโรงพิมพ์ ให้เลือกเครื่องพิมพ์เป็นพวก Image Setter เช่น Scitex เป็นต้น
จะไม่มีปัญหาเรื่องพิมพ์ตกขอบทำให้ขอบขาด

เอามาจาก Hui's BLOG: gs แปลง PDF ภาษาญี่ปุ่นเป็น PNG

กำหนดขนาดกระดาษ
$ gs -dBATCH -dNOPAUSE -sDEVICE=pdfwrite -sOutputFile=output.pdf -c "<< /PageSize [843 596] >> setpagedevice" -- source.ps

ตัวอย่างข้างต้นเป็นขนาด A4 แบบ landscape
(เอาขนาดเป็น นิ้ว คูณด้วย 72
หรือเป็น มิลลิเมตร คูณด้วย 2.8387)

จาก:
Ghostscript:
Taking PDF Creation to the Next Level

หมายเหตุ
หากต้องการ crop ไฟล์ pdf ที่ผลิตออกมาแล้ว สามารถใช้คำสั่ง pdfcrop จะใช้งานง่ายกว่า

รวมไฟล์ pdf
$ gs -dBATCH -dNOPAUSE -q -sDEVICE=pdfwrite -sOutputFile=combined.pdf file1.pdf
file2.pdf

เอามาจาก Blog of Amorn Jiraseree-amornkun : วิธีรวมไฟล์ pdf หลายไฟล์เข้าด้วยกันในลินุกซ์

tip

debain: Terminal

บันทึกการทำงานกับเทอร์มินัล

debian: XTerm

เทอร์มินัลของ GNOME ดือ gnome-terminal ยังใช้งานภาษาไทยกับ vim ได้ไม่ดีนัก
คือการเลื่อนเคอร์เซอร์ไม่ถูกตำแหน่ง และการคัดลอกข้อความด้วยเมาส์ก็ยังคัดลอกแบบผิด ๆ

การนี้ XTerm ใช้งานได้ดีกว่า แต่หน้าตาโบราณไปหน่อย และสีอักษรบนพื้นดำดูยากสักนิด
เลยทดลองปรับแต่งดูครับ

สำหรับเดเบียนติดตั้งด้วย
$ sudo aptitude install xterm xfonts-thai xfonts-thai-nectec xfonts-thai-poonlap xfonts-thai-vor

สำหรับผม ปรับแต่งให้เป็นพื้นขาว เลียนแบบ gnome-terminal และปรับแต่งตัวหนังสือให้ใหญ่ขึ้น และสีเข้มขึ้นเล็กน้อย
$ vi ~/.Xdefaults

!FONT
*VT100.utf8Fonts.font:  -misc-fixed-medium-r-normal--18-120-100-100-c-90-iso10646-1

!COLOR
*VT100*foreground: grey10
*VT100*background: grey98

*VT100*color0: black
*VT100*color1: red2
*VT100*color2: green4
*VT100*color3: yellow3
*VT100*color4: blue4
*VT100*color5: magenta4
*VT100*color6: cyan4
*VT100*color7: gray90
*VT100*color8: gray50
*VT100*color9: red1
*VT100*color10: green3
*VT100*color11: yellow2
*VT100*color12: rgb:5c/5c/ff
*VT100*color13: magenta
*VT100*color14: cyan3
*VT100*color15: white

ให้ระบบรับรู้ค่าใหม่ของเรา
$ xrdb .Xdefaults

เสร็จแล้ว เรียกใช้ด้วยคำสั่ง
$ xterm
เรียบร้อย ใช้งานภาษาไทยได้ดีแล้ว

หมายเหตุ

  • ดูชื่อสี ได้จากไฟล์ /etc/X11/rgb.txt
  • ดูตัวอย่างพารามิเตอร์การปรับตั้งได้ที่ /etc/X11/apt-defaults/ ในไฟล์ XTerm และ XTerm-color

เอามาจาก - Xterm Terminal Emulator Options

debian: screen

เอามาจาก ThaiLinuxCafe - bash tips
---

จิ๊กมาจากบล๊อกคุณพูนลาภเรื่อง คุณเปิด terminal กี่บาน?
ท่านเขียนไว้ดีมาก ๆ

screen เป็น virtual terminal มีประโยชน์มากในการใช้งานผ่าน ssh
ข้อดีคือเวลาเรากลับเข้าไปในงานที่ทำค้างอยู่ มันจะเห็นเหมือนกับเราทำที่จุดนั้นจริง ๆ
สมมุติเราคอมไพล์งานค้างไว้ ถ้ามีข้อผิดพลาด มันจะแสดงให้เห็นข้อผิดพลาดนั้นด้วย
ที่สำคัญคือมันเป็น text mode จึงทำงานเร็วและไม่เปลืองแบนด์วิธ

สั่งเริ่ม screen
$ screen

คีย์ลัดที่ใช้บ่อย
C-a c = สร้างเชลล์ใหม่
C-a n = ไปเทอร์มินัลถัดไป ( ที่ยัง attach อยู่ )
C-a p = ไปเทอร์มินัลก่อนหน้า ( ที่ยัง attach อยู่ )
C-a d = detach เชลล์ปัจจุบัน ( กลับมาด้วยคำสั่ง screen -r )
C-a " = ดูเทอร์มินัลทั้งหมด ( คล้ายคำสั่ง screen -ls )
C-a 0 = ไปเทอร์มินัล 0
C-a 1 = ไปเทอร์มินัล 1
C-a S = แบ่งเทอร์มินัลแบบแนวตั้ง
(*หมายเหตุ C-a คือกด Ctrl+a )

ดูเชลล์ที่มี
$ screen -ls

ไป attach เชลล์ที่ถูก detach
ถ้ามีแค่เชลล์เดียว
$ screen -r
ถ้ามีหลายเชลล์
$ screen -r [pid.]tty[.host]

ปรับแต่งลักษณะของ screen ด้วยไฟล์ ~/.screenrc
$ vi ~/.screenrc

# ไม่เอาข้อความต้อนรับ
startup_message off

# ตั้งค่า scroll มากกว่าค่าปริยาย (100)
defscrollback 1024

# ทำให้มีแถบ status ด้านล่าง
hardstatus on
hardstatus alwayslastline
hardstatus string "%{.bW}%-w%{.rW}%n %t%{-}%+w %=%{..G} %H %{..Y} %m/%d %C%a "

# ใช้ Ctrl+t แทน Ctrl+a
escape ^Ta

ดูเพิ่มเติมได้ที่ gentoo-wiki: TIP Using screen

ถ้าใช้แล้วติดใจ อยากเวียนหัวเพิ่ม ให้ดู man screen


ตัวอย่างการใช้งาน

สมมุติว่าต้องการแก้ธีมของ drupal ที่เครื่อง server1.example.com
เราติดตั้ง drupal ไว้ที่ /var/www/drupal
สมมุติว่าเป็นธีม newsportal เหมือนของ debianclub
ไฟล์ที่ต้องการแก้ไขหลัก ๆ จะมีสองไฟล์ คือ style.css และ page.tpl.php
อยู่ใน /var/www/drupal/themes/newsportal/

เราจะเข้าถึง server1 ด้วยการใช้ ssh ปกติ
$ ssh server1.example.com

สั่งรัน screen
$ screen
กด Space bar ตามปกติ

ตอนนี้เราจะอยู่ในเทอร์มินัล 0 แต่ผมนิยมเว้นเทอร์มินัล 0 ไว้สำหรับงานจิปาถะชั่วคราว
และใช้เทอร์มินัล 1 ในการแก้ไขไฟล์ style.css
และใช้เทอร์มินัล 2 ในการแก้ไฟล์ page.tpl.php

กด C-a c เพื่อสร้างเทอร์มินัล 1
$ cd /var/www/drupal/themes/newsportal
$ vi style.css

กด C-a c เพื่อสร้างเทอร์มินัล 2
$ cd /var/www/drupal/themes/newsportal
$ vi page.tpl.php

หลังจากนี้เราสามารถสลับงานไปมาด้วยการกด C-a สองครั้ง
มันจะสลับไปมาระหว่างเทอร์มินัล 1 และ 2

ถ้าเราปิด ssh โดยไม่ออกจาก screen
เมื่อเข้ามาใหม่ เราสามารถเข้ามาที่งานที่ทำค้างไว้ได้เลย ด้วยคำสั่ง screen -r

หรือถ้าเราทำงานค้างไว้ที่ทำงาน และยังเปิด ssh ค้างอยู่
และเรากลับไปต่องานที่บ้าน เราสามารถช่วงชิง screen ที่ยังค้างอยู่ที่ทำงาน
มาด้วยคำสั่ง screen -d -r

จบแล้วครับ ใครมีทิป screen ดี ๆ แบ่งบ้างนะครับ

Topic: 

screen tip: screenrc และการสั่งงานด้วยบรรทัดคำสั่ง

screen tip: screenrc และการสั่งงานด้วยบรรทัดคำสั่ง

ต้องการให้เมื่อเริ่ม screen ทุกครั้ง จะสั่งเปิด 3 หน้าต่างคือแสดง top, syslog, และ dmesg ตามลำดับ โดยตั้งชื่อ session ว่า norm

ใช้การแก้ไขไฟล์ ~/.screenrc ดังนี้

# vi ~/.screenrc
sessionname norm
screen top
screen 1 tail -f /var/log/syslog
screen 2 tail -f /var/log/dmesg

รัน screen

# screen

จะได้ผลตามต้องการ

ต้องการให้ screen เริ่มงานแบบข้างต้น เฉพาะเมื่อเราต้องการ

จะใช้การทำงานผ่านสคริปต์ ดังนี้
สมมุติตั้งชื่อสคริปต์ว่า screen_monitor.sh

# vi screen_monitor.sh
#!/bin/bash
screen -S norm -md bash
screen -S norm -p0 -X stuff "screen bash
"
screen -S norm -p0 -X stuff "top
"
screen -S norm -p1 -X stuff "screen bash
"
screen -S norm -p1 -X stuff "tail -f /var/log/syslog
"
screen -S norm -p2 -X stuff "screen bash
"
screen -S norm -p2 -X stuff "tail -f /var/log/dmesg
"

เปลี่ยนสิทธิ์และสั่งรัน

# chmod 755 screen_monitor.sh
# ./screen_monitor.sh

จะไม่เห็นการเปลี่ยนแปลง เพราะเราสั่งให้ทำงานแบบเบื้องหลัง (-md)

จะเข้าไปสู่ (attach) การทำงานของ screen ได้ด้วยคำสั่ง

# screen -r norm

จบแล้วครับ

Topic: 

debian: ลองติดตั้ง Hylafax

เอามาจาก debian : ทดลองติดตั้ง hylafax

HylaFAX เป็นแพกเกจที่ใช้ทำ Fax Server
ใช้งานได้เสถียรดี และสามารถรองรับงานใหญ่ได้

ติดตั้ง hylafax-server
# aptitude install hylafax-server
Major possible upgrade issues
<<<--- [Enter]
Which paper size should be the system default?
<<<--- a4

ติดตั้งโมเด็มเป็นแฟกซ์
# faxsetup
ตอบตามจริง ยกเว้นข้อมูลของเรา ตัวอย่างของผมคือ
...
Country code [1]? <<<--- 66
Area code [415]? <<<--- 2
Phone number of fax modem [+1.999.555.1212]? <<<--- ใส่เบอร์แฟกซ์เรา
Local identification string (for TSI/CIG) ["NothingSetup"]? <<<--- ชื่อร้านหรือหน่วยงาน
...
Protection mode for received facsimile [0600]? <<<--- 0755
Protection mode for session logs [0600]? <<<--- 0755
...

อันนี้ผมขี้เกียจเก็บข้อมูลผู้ใช้ เลยตั้งเป็นว่าสามารถใช้งานได้จากทุกเครื่อง โดยไม่ต้องมีชื่อผู้ใช้งาน
# vi /etc/hylafax/hosts.hfaxd

...
^.*@.*\.example\.com$

# /etc/init.d/hylafax restart

client ฝั่งวินโดวส์ ผมใช้ Whfc
ส่วน client ฝั่งลินุกส์ ยังหาตัวที่ใช้งานสะดวกยังไม่ได้ครับ

ปรับปรุง: 49-11-14
วันนี้ได้มีโอกาสทดสอบการติดตั้งจริงบน etch อีกครั้งนึง
ปรากฎว่าเกิดปัญหาตอนติดตั้ง คือ
หน้าจอขึ้นมาถาม Area code ไม่หยุด ต้องกด Ctrl+C เพื่อเบรก
ทำให้การติดตั้งไฟล์ไม่สมบูรณ์
(ยังไม่แน่ใจว่าเกิดจากแพกเกจ หรือฮาร์ดแวร์ครับ)

ผมแก้ไขดังนี้ครับ

ดาวน์โหลดแพกเกจจากต้นฉบับ (ไม่แน่ใจความถูกต้องของแพกเกจในเครื่องเรา)
# wget http://ftp.us.debian.org/debian/pool/main/h/hylafax/hylafax-server_4.3.0-9_i386.deb

แตกไฟล์โดยไม่ติดตั้ง ไปยัง / (root directory)
# dpkg-deb -x hylafax-server_4.3.0-9_i386.deb /

ปรับตั้งแพกเกจใหม่
# dpkg-reconfigure hylafax-server

หลังจากนั้นก็เริ่มขั้นตอนตามปกติคือ
# faxsetup
...
# /etc/init.d/hylafax restart

debian: customize hylafax

ตั้งให้พิมพ์แฟกซ์รับ อัตโนมัติ

สร้างไฟล์ที่จะดักการทำงานตอนรับแฟกซ์ ชื่อ /etc/hylafax/FaxDispatch
แล้วเริ่มการทำงานใหม่ เพื่อให้ไฟล์ใหม่นี้ถูกคัดลอกไปไว้ในไดเรคทอรี่ของ hylafax
# vi /etc/hylafax/FaxDispatch

/usr/bin/tiff2ps -a $FILE | lpr -P $PRINTER_NAME

# /etc/init.d/hylafax restart

ตั้งให้เก็บสำเนาแฟกซ์รับและแฟกซ์ส่ง

ใช้ความสามารถของ gs

แฟกซ์รับ
ใส่ที่ /etc/hylafax/FaxDispatch
แฟกซ์ส่ง
ใส่ที่ /var/spool/hylafax/bin/notify ปรับแก้สคริปต์นิดหน่อย
จริง ๆ แล้วสามารถปรับได้ที่ faxsend โดยเติมออปชั่น -A แต่ถ้าเราปรับสคริปต์ notify ก็ไม่จำเป็นต้องใส่ออปชั่นแล้ว เพราะหาที่ใส่ออปชั่นใน WHFC ไม่พบ เลยมาแก้ที่ตัวเซิร์ฟเวอร์ดีกว่า

--รอทดลอง--

เกร็ด

  • โมเด็ม Class 2 หรือ 2.0 มีปัญหาในการรับแฟกซ์ จากเครื่องแฟกซ์ที่เป็น Class 1 แต่ไม่มีปัญหาในการส่ง
Topic: 

debian: ทดลองทำ HylaFAX Client

เอามาจาก debian : ทดลองติดตั้ง hylafax

ต้องมีเซิร์ฟเวอร์ที่เป็น HylaFAX แล้ว ดูวิธีติดตั้งได้จากคราวก่อน

ติดตั้ง Pyla
$ sudo aptitude install python python-tk
$ wget http://www.teamsw.it/pyla/download/pyla1.1.7.1.tgz
$ tar xfz pyla1.1.7.1.tgz
$ sudo mv pyla /usr/local
$ /usr/local/pyla/pyla.py
-> ปรับตั้งโปรแกรม

ติดตั้งเครื่องพิมพ์เทียมให้ pyla
เอามาจาก Fun with CUPS and Pyla
หลักการคือเขาใช้ perl เขียนโปรแกรมทำหน้าที่เป็น socket คอยดูว่ามีข้อมูลเข้ามาทางพอร์ต 5691 หรือเปล่า ถ้ามี ก็จะเรียกใช้โปรแกรมนี้ซึ่งจะไปเรียกใช้ pyla อีกทีนึง

สร้างโปรแกรมเรียก pyla ชื่อ pyla-print เอาใส่ไว้ใน /usr/local/bin
$ sudo vi /usr/local/bin/pyla-print

#!/usr/bin/perl

# Daniel E. Markle 
# 20031114 Revision 0.1

# This program starts up a daemon which will watch for a postscript file
#  coming in from CUPS, then launch pyla to send it as a fax.
# Use it with the socket://localhost:5691 URL in cups.

# Issues:
#    -security, make sure only localhost can see this port, it allows anyone
#      who can stream data to this port to popup pyla on your desktop
#    -must be ran as the user or the window may not pop up at the right
#      place, if at all

# ***CONFIGURE ME HERE

# Port to listen on, use socket://localhost: in cups
my $MY_PORT = 5691;

# Temporary file storage, this is a file not a folder
my $TMP_FILE_NAME = "/tmp/cupsfaxtemp";

# Path to pyla.py
my $PYLA_PATH = "/usr/local/pyla/pyla.py";

# Path to python binary
#  use this one for Linux
my $PYTHON_PATH = "/usr/bin/python";
#  use this one for OS X
#my $PYTHON_PATH = "/usr/bin/pythonw";

# ***END CONFIGURATION

# ----- You shouldn't need to touch anything beyond this point -----

use IO::Socket::INET;

# check to make sure we can use the temp file
open ( TMPFILE, ">", $TMP_FILE_NAME )
   or die "Can not open temp file $TMP_FILE_NAME, " .
   "check to make sure it is not owned by another user";
close ( TMPFILE );

if ( !(-e $PYLA_PATH) ) {
   die "I do not see pyla at $PYLA_PATH";
   }

my $data;
my $server = IO::Socket::INET->new (
   LocalPort => $MY_PORT,
   Type => SOCK_STREAM,
   Reuse => 1,
   Listen => 1
   ) or die "Could not start server process";
   
while ( my $job = $server->accept() ) {
   open ( TMPFILE, ">", $TMP_FILE_NAME );
   while ( <$job> ) {
      print TMPFILE $_;
      }
   close ( TMPFILE );
   close $job;
   `cat "$TMP_FILE_NAME" | "$PYTHON_PATH" "$PYLA_PATH" -i`
   }

ทำให้รันได้
$ sudo chmod 755 /usr/local/bin/pyla-print

สร้างโปรแกรมที่รันเป็น daemon คอยดูซอกเก็ต ชื่อ pyla-printd เอาใส่ไว้ใน /etc/init.d
$ sudo vi /etc/init.d/pyla-printd

#!/bin/sh

PATH=/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/usr/local/bin/pyla-print
NAME=pyla-print
DESC="Pseudo printer for Pyla"

test -x $DAEMON || exit 0

case "$1" in
        start)
                echo -n "Starting $DESC: "

                start-stop-daemon --start --quiet --pidfile /var/run/$NAME.pid \
                        --background --make-pidfile --exec $DAEMON -- $DAEMON_OPTS

                echo "$NAME."
                ;;

        stop)
                echo -n "Stopping $DESC: "

                start-stop-daemon --stop --quiet --pidfile /var/run/$NAME.pid \
                        --oknodo

                echo "$NAME."
                ;;

        restart|force-reload)
                echo -n "Restarting $DESC: "

                start-stop-daemon --stop --quiet --pidfile /var/run/$NAME.pid \
                        --oknodo

                sleep 1

                start-stop-daemon --start --quiet --pidfile /var/run/$NAME.pid \
                        --background --make-pidfile --exec $DAEMON -- $DAEMON_OPTS

                echo "$NAME."
                ;;

        *)
                N=/etc/init.d/$NAME
                echo "Usage: $N {start|stop|restart|force-reload}" >&2
                exit 1
                ;;
esac

exit 0

ทำให้รันได้ - สั่งรันเป็น daemon และให้เริ่มรันทุกครั้งที่เปิดเครื่อง
$ sudo chmod 755 /etc/init.d/pyla-printd
$ sudo /etc/init.d/pyla-printd restart
$ sudo update-rc.d pyla-printd defaults

ติดตั้งตัวจัดการเครื่องพิมพ์
$ sudo aptitude install cupsys-client

เพิ่มเครื่องพิมพ์เทียมให้ระบบ
$ sudo lpadmin -p pseudo-pyla -E -v socket://localhost:5691
หรือตั้งชื่อให้เรียกง่ายขึ้นว่า hylafax เฉย ๆ ก็ได้
$ sudo lpadmin -p hylafax -E -v socket://localhost:5691

เมื่อทุกอย่างเรียบร้อย จะมีเครื่องพิมพ์ฃื่อ psuedo-pyla ขึ้นมา ถ้าเราพิมพ์มาที่เครื่องพิมพ์ตัวนี้ เขาจะมาเรียกใช้ pyla ให้ส่งแฟกซ์เอง

update

แก้ปัญหาดูแฟกซ์ที่รับไม้ได้
ตั้งการอนุญาตใช้ไฟล์ ที่ไฟล์โมเด็ม (ส่วนใหญ่จะชื่อ config.ttyS0)
# vi /etc/hylafax/config.ttyS0
...
RecvFileMode:       0666
...
Topic: 

debian: ปรับปรุง HylaFax Client

ปรับปรุง HylaFax Client

ปรับปรุงจากdebian: ทดลองทำ HylaFAX Client
ครั้งก่อนมีปัญหาว่า daemon ถูกรันด้วย root ใน runlevel ที่อยู่ในสถานะอักขระ (text mode) ซึ่งยังไม่มีการกำหนดค่าตัวแปรแวดล้อม $DISPLAY
แต่เวลาซอกเก็ตถูกเรียกใช้ จะถูกเรียกใช้ด้วยผู้ใช้ที่ runlevel ที่อยู่ในสถานะกราฟฟิก โดย gtk จะมาดูว่าต้องแสดงผลที่จอไหนจากตัวแปรแวดล้อม $DISPLAY นี้ ทำให้โปรแกรมตาย
ซึ่งต้องแก้ปัญหาด้วยการใช้คำสั่ง $ sudo /etc/init.d/pyla-printd restart จากเทอร์มินอล จึงจะหาย

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

แต่เพื่อป้องกันการสับสน จะขอเริ่มใหม่เลย

ต้องมีเซิร์ฟเวอร์ที่เป็น HylaFAX แล้ว ดูวิธีติดตั้งได้จากdebian: ลองติดตั้ง Hylafax

ติดตั้ง Pyla
$ sudo aptitude install python python-tk
$ wget http://www.teamsw.it/pyla/download/pyla1.1.7.1.tgz
$ tar xfz pyla1.1.7.1.tgz
$ sudo mv pyla /usr/local
$ /usr/local/pyla/pyla.py
-> ปรับตั้งโปรแกรม

ติดตั้งเครื่องพิมพ์เทียมให้ pyla
เอามาจาก Fun with CUPS and Pyla
หลักการคือเขาใช้ perl เขียนโปรแกรมทำหน้าที่เป็น socket คอยดูว่ามีข้อมูลเข้ามาทางพอร์ต 5691 หรือเปล่า ถ้ามี ก็จะเรียกใช้โปรแกรมนี้ซึ่งจะไปเรียกใช้ pyla อีกทีนึง

สร้างโปรแกรมเรียก pyla ชื่อ pyla-print เอาใส่ไว้ใน /usr/local/bin
$ sudo vi /usr/local/bin/pyla-print

#!/usr/bin/perl

# Daniel E. Markle 
# 20031114 Revision 0.1

# This program starts up a daemon which will watch for a postscript file
#  coming in from CUPS, then launch pyla to send it as a fax.
# Use it with the socket://localhost:5691 URL in cups.

# Issues:
#    -security, make sure only localhost can see this port, it allows anyone
#      who can stream data to this port to popup pyla on your desktop
#    -must be ran as the user or the window may not pop up at the right
#      place, if at all

# ***CONFIGURE ME HERE

# Port to listen on, use socket://localhost: in cups
my $MY_PORT = 5691;

# Temporary file storage, this is a file not a folder
my $TMP_FILE_NAME = "/tmp/cupsfaxtemp";
`touch "$TMP_FILE_NAME"; chmod 777 "$TMP_FILE_NAME"`;

# Log file storage
my $LOG_FILE_NAME = "/tmp/cupsfaxlog";
`touch "$LOG_FILE_NAME"; chmod 777 "$LOG_FILE_NAME"`;

# Path to pyla.py
my $PYLA_PATH = "/usr/local/pyla/pyla.py";

# Path to python binary
#  use this one for Linux
my $PYTHON_PATH = "/usr/bin/python";
#  use this one for OS X
#my $PYTHON_PATH = "/usr/bin/pythonw";

# ***END CONFIGURATION

# ----- You shouldn't need to touch anything beyond this point -----

use IO::Socket::INET;

# check to make sure we can use the temp file
open ( TMPFILE, ">", $TMP_FILE_NAME )
   or die "Can not open temp file $TMP_FILE_NAME, " .
   "check to make sure it is not owned by another user";
close ( TMPFILE );

if ( !(-e $PYLA_PATH) ) {
   die "I do not see pyla at $PYLA_PATH";
   }

my $data;
my $server = IO::Socket::INET->new (
   LocalPort => $MY_PORT,
   Type => SOCK_STREAM,
   Reuse => 1,
   Listen => 1
   ) or die "Could not start server process";
   
while ( my $job = $server->accept() ) {
   open ( TMPFILE, ">", $TMP_FILE_NAME );
   while ( <$job> ) {
      print TMPFILE $_;
      }
   close ( TMPFILE );
   close $job;
   `cat "$TMP_FILE_NAME" | "$PYTHON_PATH" "$PYLA_PATH" -i &> "$LOG_FILE_NAME"`;
   }

ทำให้รันได้
$ sudo chmod 755 /usr/local/bin/pyla-print

ตั้งให้ gnome-sessions รัน pyla-print
ทำผ่านเมนู System -> Preferences -> Sessions
แท็บ Startup Programs กดปุ่ม Add

Name: Pyla Print Daemon
Command: /usr/local/bin/pyla-print
Comment: Pyla Print Daemon (start by gnome-sessions)

ล๊อกเอาต์ และ ล๊อกอิน 1 ครั้ง

จัดการให้โปรแกรมตายตอนล๊อกเอาต์
เมนู System -> Preferences -> Sessions
แท็บ Current Sessions เลือกบรรทัดที่เป็น pyla-print
เปลี่ยนปุ่ม Style เป็น Restart
เสร็จแล้ว

ติดตั้งตัวจัดการเครื่องพิมพ์
$ sudo aptitude install cupsys-client

เพิ่มเครื่องพิมพ์เทียมให้ระบบ ตั้งชื่อว่า hylafax
$ sudo lpadmin -p hylafax -E -v socket://localhost:5691

เมื่อทุกอย่างเรียบร้อย จะมีเครื่องพิมพ์ฃื่อ hylafax ขึ้นมา ถ้าเราพิมพ์มาที่เครื่องพิมพ์ตัวนี้ เขาจะมาเรียกใช้ pyla ให้ส่งแฟกซ์เอง

เพิ่มเติม

แก้ปัญหา pyla ไม่เรียงข้อมูลคิว
แก้ไขไฟล์ /usr/local/pyla/hylaproto.py
$ sudo vi /usr/local/pyla/hylaproto.py
...
class Hylafax:
    ...
    def getqueue(self,queue,jfmt=JFMT,view='priv'):
        ...
                        else:
                            retlist[-1] = retlist[-1] + l + '\n'

                    #wd's
                    retlist.sort(reverse=True)
                    return (1,retlist)

    def callback(self,str):
    ...

debian: debootstrap

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

การใช้งาน ควรมี mirror ซึ่งอาจเป็น apt-proxy apt-cacher หรือ mirror แท้ ๆ อยู่ในเครือข่ายเราก่อน

รูปแบบใช้งานคือ

debootstrap [OPTION...] SUITE TARGET [MIRROR [SCRIPT]]

SUITE คือรุ่นของลินุกซ์ที่เราจะติดตั้ง สำหรับเดเบียนได้แก่ sarge หรือ etch เป็นต้น
TARGET คือพาร์ติชั่นที่เราเมานต์ไว้แล้วสำหรับการติดตั้ง
MIRROR คือคลังแพกเกจ เช่น apt-proxy หรือ mirror อื่น

รายละเอียดสามารถศึกษาได้เพิ่มเติมจาก man debootstrap

ตัวอย่างการใช้งาน debootstrap สำหรับงานทดลอง

ก่อนหน้าที่จะรู้จักคำสั่ง aptitude การติดตั้งแพกเกจด้วยคำสั่ง apt-get นั้น เมื่อเราลบแพกเกจที่เราไม่ต้องการออกแล้ว จะเหลือแพกเกจลูกที่ไม่ถูกใช้งานอยู่เป็นจำนวนมาก เวลาเราต้องการทำการทดลองใหม่ เราจะไม่ทราบแน่ชัดถึงผลกระทบของแพกเกจใหม่กับแพคเกจลูกของใหม่กับของเก่า
ดังนั้นเพื่อให้ระบบสะอาดเหมือนกับการติดตั้งใหม่ เราจึงควรแบ่งพาร์ติชั่นไว้สำหรับการทดลองนี้โดยเฉพาะ ทุกครั้งที่ต้องการการทดลองใหม่ ๆ เราจะติดตั้งเดเบียนใหม่ลงในพาร์ติชั่นนี้เลย ด้วย debootstrap

สมมุติว่า mirror เราชื่อ www.example.com
เครื่องเราชื่อ server1

และสมมุติว่าแบ่งพาร์ติชั่นไว้ดังนี้

/dev/hda1   = /boot ขนาด 100M
/dev/hda2   = /     ขนาด 10G เป็นระบบปัจจุบัน
/dev/hda3   = Extended
/dev/hda5   = swap  ขนาด 512M

เราจะติดตั้งระบบใหม่บน /dev/hda6 ให้มีขนาด 20G เพื่อใช้เป็น / (root) ของระบบใหม่

สร้างพาร์ติชั่นใหม่ คือ /dev/hda6 ขนาด 20G
# fdisk /dev/hda

Command (m for help): <<<--- n
Command action
   l   logical (5 or over)
   p   primary partition (1-4)
<<<--- l
First cylinder (XXXX-XXXXX, default XXXXX):<<<--- {Enter}
Last cylinder or +size or +sizeM or +sizeK (XXXX-XXXXX, default XXXXX):<<<--- +20G
.
Command (m for help): <<<--- w

สร้างระบบไฟล์เป็น ext3
# mkfs.ext3 /dev/hda6

สร้างจุดเมานต์ให้ /dev/hda6 สมมุติว่าชื่อ /mnt/disk
# mkdir -p /mnt/disk

ก่อนจะติดตั้ง ต้องเมานต์ /dev/hda6 ลงบน /mnt/disk ก่อน
# mount /dev/hda6 /mnt/disk

ติดตั้ง etch บน /mnt/disk ผ่าน debootstrap
# debootstrap etch /mnt/disk http://www.example.com:9999/debian

เมื่อติดตั้งเสร็จแล้ว จำเป็นต้องคัดลอกไฟล์ที่จำเป็นในการเริ่มต้นระบบไปยัง /mnt/disk และทำการแก้ไขตามจำเป็น
# cp /etc/hosts /mnt/disk/etc ; vi /mnt/disk/etc/hosts
# cp /etc/network/interfaces /mnt/disk/etc/network ; vi /mnt/disk/etc/network/interfaces
# cp /etc/hostname /mnt/disk/etc ; vi /mnt/disk/etc/hostname
# cp /etc/apt/sources.list /mnt/disk/etc/apt ; vi /mnt/disk/etc/apt/sources.list
# cp /etc/fstab /mnt/disk/etc ; vi /mnt/disk/etc/fstab

ตรงไฟล์ /mnt/disk/etc/fstab อย่าลืมเปลี่ยน /dev/hda2 เป็น /dev/hda6

สร้างสภาพแวดล้อม เพื่อใช้ในการ chroot ไปยัง /mnt/disk
# mount -t proc none /mnt/disk/proc
# mount -o bind /dev /mnt/disk/dev
# mount -t sysfs /sys /mnt/disk/sys

chroot ไปยัง /mnt/disk
# chroot /mnt/disk /bin/bash

เริ่มการติดตั้งแพกเกจที่จำเป็นและปรับตั้งระบบ
server1:/# aptitude update
server1:/# aptitude install locales console-data
server1:/# dpkg-reconfigure locales
server1:/# aptitude install initrd-tools usbutils pciutils bzip2 ssh grub udev yaird ssh

( ถ้าต้องการระบบ X-Window ด้วย
server1:/# aptitude install xorg gdm gnome )

ติดตั้งเคอร์เนล
server1:/# aptitude install linux-image-686

อย่าลืมตั้งรหัสผ่านให้ root
server1:/# passwd

กันเหนียวด้วยการปรับตั้งระบบใหม่ทั้งหมด
server1:/# dpkg-reconfigure -a
ตอบค่าปริยายทั้งหมด ยกเว้นส่วนของ locales, tzconfig, dash ให้ตอบตามต้องการ

ออกจากสภาพ chroot
server1:/# exit

ยกเลิกการเมานต์ที่ผ่านมา
# umount /mnt/disk/proc
# umount /mnt/disk/dev
# umount /mnt/disk/sys
# ldconfig
# sync

(ถ้ายกเลิกเมานต์ไม่ผ่าน ไม่เป็นไร ให้ผ่านไปก่อน)

ปรับตั้งการบูต
# vi /boot/grub/menu.lst

แก้ไขลำดับการบูต
แก้จาก default 0 เป็น default 2

...
default        2
...

เติมลำดับการบูตให้กับเดเบียนใหม่ที่เราเพิ่งติดตั้งเสร็จ โดยต่อท้ายจากของเก่า

...
title       Etch-Experiment, kernel 2.6.18-3-386
root        (hd0,5)
kernel      /vmlinuz root=/dev/hda5 ro
initrd      /initrd.img
savedefault
...

เรียบร้อยแล้ว รีบูตใหม่ได้เลย
# reboot

ระบบใหม่จะเป็นเดเบียน Etch ใหม่แบบสะอาด ๆ
เมื่อจะทำการทดลองระบบใหม่ ถ้าจำเป็นต้องลงใหม่ก็ทำเหมือนเดิมครับ

ติดตั้งอูบุนตู Feisty ด้วย debootstrap จากเดเบียน Etch

สมมุติว่า mirror เราชื่อ www.example.com ได้ติดตั้ง apt-proxy สำหรับอูบุนตูไว้เรียบร้อยแล้ว
(เวลาใช้งานจาก apt-proxy ต้องระบุพอร์ตเป็น 9999)

และสมมุติว่าแบ่งพาร์ติชั่นไว้ดังนี้

/dev/hda1   = /    เป็นเดเบียน Etch
/dev/hda2   = ว่าง     ขนาด 20G เตรียมสำหรับ Feisty
/dev/hda3   = Extended
/dev/hda5   = swap  ขนาด 512M

เราจะติดตั้งระบบใหม่บน /dev/hda2 ให้มีขนาด 20G เพื่อใช้เป็น / (root) ของระบบใหม่

สร้างระบบไฟล์เป็น ext3
# mkfs.ext3 /dev/hda2

สร้างจุดเมานต์ให้ /dev/hda2 สมมุติว่าชื่อ /mnt/disk
# mkdir -p /mnt/disk

ก่อนจะติดตั้ง ต้องเมานต์ /dev/hda2 ลงบน /mnt/disk ก่อน
# mount /dev/hda2 /mnt/disk

ติดตั้ง debootstrap บน Etch
# aptitude install debootstrap

เนื่องจากแพกเกจ debootstrap ของ Etch ไม่มีสคริปต์สำหรับอูบุนตู Feisty
เราจึงต้องไปดาว์นโหลดแพกเกจ debootstrap ของ Feisty เอง เพื่อมาแตกเอาไฟล์สคริปต์นี้
# wget http://fr.archive.ubuntu.com/ubuntu/pool/main/d/debootstrap/debootstrap_0.3.3.2ubuntu3_all.deb
ถึงวันนี้ มิเรอร์ของไทย mirror.in.th ยังไม่อัปเดตครับ

แตกด้วย Archive Manager เฉพาะไฟล์ที่ชื่อ feisty แล้วนำไปใส่ไว้ใน /usr/lib/debootstrap/scripts
# cp feisty /usr/lib/debootstrap/scripts

เริ่มติดตั้ง Feisty บน /mnt/disk ผ่าน debootstrap
# debootstrap feisty /mnt/disk http://www.example.com:9999/debian

เมื่อติดตั้งเสร็จแล้ว จำเป็นต้องคัดลอกไฟล์ที่จำเป็นในการเริ่มต้นระบบไปยัง /mnt/disk และทำการแก้ไขตามจำเป็น
# cp /etc/hosts /mnt/disk/etc ; vi /mnt/disk/etc/hosts
# cp /etc/networks/interface /mnt/disk/etc/networks ; vi /mnt/disk/etc/networks/interface
# cp /etc/hostname /mnt/disk/etc ; vi /mnt/disk/etc/hostname
# cp /etc/apt/sources.list /mnt/disk/etc/apt ; vi /mnt/disk/etc/apt/sources.list
# cp /etc/fstab /mnt/disk/etc ; vi /mnt/disk/etc/fstab

ตรงไฟล์ /mnt/disk/etc/fstab อย่าลืมเปลี่ยน /dev/hda1 เป็น /dev/hda2

แก้ไขไฟล์ /etc/apt/sources.list เป็นดังนี้
# vi /mnt/disk/etc/apt/sources.list

deb     http://www.example.com:9999/ubuntu feisty main restricted universe multiverse
deb-src http://www.example.com:9999/ubuntu feisty main restricted universe multiverse

สร้างสภาพแวดล้อม เพื่อใช้ในการ chroot ไปยัง /mnt/disk
# mount -t proc none /mnt/disk/proc
# mount -o bind /dev /mnt/disk/dev
# mount -t sysfs /sys /mnt/disk/sys

chroot ไปยัง /mnt/disk
# chroot /mnt/disk /bin/bash

เริ่มการติดตั้งแพกเกจที่จำเป็นและปรับตั้งระบบ
(CHROOT)# mount -a
(CHROOT)# aptitude update
(CHROOT)# aptitude install locales console-data localeconf

ปรับแต่ง locale ให้ด้วย
(CHROOT)# locale-def en_US.UTF-8
(CHROOT)# dpkg-reconfigure locales localeconf

ติดตั้งแพกเกจจำเป็นที่เหลือ
(CHROOT)# aptitude install initrd-tools usbutils pciutils bzip2 ssh grub udev yaird ubuntu-minimal ubuntu-standard

( ถ้าต้องการระบบ X-Window ด้วย
(CHROOT)# aptitude install ubuntu-desktop xorg gdm gnome )

ติดตั้งเคอร์เนล
(CHROOT)# aptitude install linux-image-686

อย่าลืมตั้งรหัสผ่านให้ root
(CHROOT)# passwd

ปรับตั้งระบบใหม่ทั้งหมด
(CHROOT)# dpkg-reconfigure -a
ตอบค่าปริยายทั้งหมด ยกเว้นส่วนของ locales, tzconfig, dash ให้ตอบตามต้องการ

ออกจากสภาพ chroot
(CHROOT)# exit

ยกเลิกการเมานต์ที่ผ่านมา
# umount /mnt/disk/proc
# umount /mnt/disk/dev
# umount /mnt/disk/sys
# ldconfig
# sync

(ถ้ายกเลิกเมานต์ไม่ผ่าน ไม่เป็นไร ให้ผ่านไปก่อน)

ปรับตั้งการบูต
# vi /boot/grub/menu.lst

แก้ไขลำดับการบูต
แก้จาก default 0 เป็น default 2

...
default        2
...

เติมลำดับการบูตให้กับอูบุนตูใหม่ที่เราเพิ่งติดตั้งเสร็จ โดยต่อท้ายจากของเก่า

...
title       Ubuntu Feisty - Experiment, kernel 2.6.20-15-generic
root        (hd0,1)
kernel      /vmlinuz root=/dev/hda2 ro
initrd      /initrd.img
savedefault
...

เรียบร้อยแล้ว รีบูตใหม่ได้เลย
# reboot

จะได้อูบุนตู Feisty ที่ติดตั้งผ่าน debootstrap

หมายเหตุ

  • เนื่องจากติดตั้งข้ามดิสโตร เมื่อบูตด้วยอูบุนตูแล้ว อย่าลืมใช้คำสั่งชุดนี้ด้วย
    $ sudo dpkg-reconfigure -a
    $ sudo aptitude update && sudo aptitude dist-upgrade
  • การทดลองจริง อาจไม่ราบรื่นแบบที่เขียน ถ้าผ่านขั้นตอนการติดตั้งเคอร์เนลแล้ว สามารถรีบูตเป็น Feisty แล้วมาทำต่อ อาจให้ผลที่ดีกว่า

debian: บันทึก apache2

บนเดเบียน สามารถเปิดใช้งานมอดูลด้วยคำสั่ง a2enmod MODULENAME และปิดการใช้งานด้วยคำสั่ง a2dismod MODULENAME

มอดูล

autoindex
ดูไฟล์ในไดเรกทอรี่
userdir
public_html ของผู้ใช้
rewrite
การทำ rewrite คือแปลงรูปของ url แบบต่าง ๆ
proxy
การทำ Redirect ไปยังเครื่องอื่น (mod_proxy) - ตัวอย่างการติดตั้ง
cband
จำกัดแบนด์วิดธ์ ทำได้หลายอย่าง
ยกตัวอย่างแค่อย่างเดียว คือจำกัดการใช้งานต่อไอพีให้ความเร็วไม่เกิน 1000kbps คำร้องไม่เกิน 10 ครั้งต่อวินาที และเปิดการใช้งานได้ไม่เกิน 10 ครั้งต่อวินาที
# vi /etc/apache2/site-enables/example.com
<VirtualHost *:80>
    ServerAdmin webmaster@example.com
    ServerName www.example.com
    ServerAlias example.com
    <IfModule mod_cband.c>
        CBandExceededSpeed 1000 10 10
    </IfModule>
    ...
</VirtualHost>

# /etc/init.d/apache2 force-reload

ภาษา

# vi /etc/apache2/conf.d/charset

ไทย
AddCharset    TIS-620    .tis-620 .th
AddCharset    CP874      .cp874

Windows 7 (นอกเรื่อง)

ติดตั้ง
How install Apache 2.4 PHP 5.4 and MySQL 5.5.21 on Windows 7
ยกเลิกพอร์ต 80 ของ Windows
Control Panel -> Program and Features -> Turn Windows features on or off
ปิด Internet Information Service
mod_wsgi
  • ไซต์ http://code.google.com/p/modwsgi/
  • คอมไพล์เองด้วยการใช้ Visual C++ รุ่นเดียวกับ Python เมื่อได้ไฟล์ mod_wsgi.so เอาไปใส่ใน c:\Apache2\modules
  • ปรับแต่ง httpd.conf ดังนี้
    ...
    LoadModule wsgi_module modules/mod_wsgi.so
    

    ที่มา: http://serverfault.com/questions/435926/cant-locate-api-module-structure-mod-wsgi

  • แก้ Firewall สำหรับพอร์ต 80
    http://superuser.com/questions/92488/apache-server-on-windows-7-opening-up-ports

    บันทึกการอัปเกรด apache2.2 และ php5 บน etch

    # aptitude update
    # aptitude install apache2 apache2-doc apache2-mpm-prefork apache2-utils apache2.2-common
    # aptitude install php5 php5-cgi php5-commonphp5-curlphp5-gd php5-mcrypt php5-mysql php5-odbc php5-pgsql php5-xmlrpc
    # aptitude install libapache2-mod-php5

    ช่วงนี้ aptitude ของ etch เป็นอะไรไม่รู้ ชอบติดตั้งไฟล์ไม่ครบ
    (อาจเป็นที่ apt-proxy)
    แก้ไขแพกเกจติดตั้งไฟล์ไม่ครบ โดยการแตกไฟล์โดยไม่ติดตั้ง ไปยัง /
    # dpkg-deb -x /var/cache/apt/archives/apache2.2-common_2.2.3-3.1_i386.deb /

    # aptitude install mysql-client-5.0 mysql-common mysql-server-5.0 phpmyadmin

    บันทึกการเปิดโมดูลแบบน้อยสุด module-minimal
    # a2enmod actions
    # a2enmod alias
    # a2enmod authz_host
    # a2enmod cgi
    # a2enmod dir
    # a2enmod mime
    # a2enmod php5
    # a2enmod rewrite
    # a2enmod status
    # /etc/init.d/apache2 restart

    บันทึกการเปิดใช้โมดูลแบบปกติ module-normal
    # a2enmod alias
    # a2enmod auth_basic
    # a2enmod authn_file
    # a2enmod authz_default
    # a2enmod authz_groupfile
    # a2enmod authz_host
    # a2enmod authz_user
    # a2enmod autoindex
    # a2enmod cgi
    # a2enmod dir
    # a2enmod env
    # a2enmod mime
    # a2enmod mod_python
    # a2enmod negotiation
    # a2enmod php5
    # a2enmod setenv
    # a2enmod status
    # /etc/init.d/apache2 restart

    เอา php.ini จากตัวอย่างแบบ recommended มาใช้
    # cp /usr/share/doc/php5/examples/php.ini-recommended /etc/php5/apache2/php.ini

    แก้จะให้ใช้ได้กับ drupal
    # vi /etc/php5/apache2/php.ini

    ...
    ; หน่วยความจำที่ยอมให้สคริปต์รันได้
    ; เก่าเป็น 8M drupal แนะนำ 12M เราใช้ 16M กันเหนียว
    memory_limit = 16M ; Maximum amount of memory a script may consume (8MB)
    ...
    ; ตัวแปรเป็น global อัตโนมัติ
    ; อันนี้ก็กันเหนียว ตอนเขียนโค๊ดเรี่ยราดไว้หลายที่ เก่าเป็น Off เราแก้เป็น On
    register_globals = On
    ...
    ; ขนาดไฟล์ใหญ่สุด ที่ post ได้
    ; อันนี้จำได้แม่น หน้าแตกเพราะเอา Bon Echo เข้าไม่ได้ เก่าเป็น 8M แก้เป็น 20M
    post_max_size = 20M
    ...
    ; adodb ไม่ต้องก็ได้
    include_path = ".:/usr/share/php5:/usr/share/php4:/usr/share/php/adodb"
    ...
    ; ค่าแนะนำของ drupal
    session.save_handler = user
    ...
    ; ค่าแนะนำของ drupal
    session.cache_limiter = none
    ...

    ส่วนของไซต์ ถ้ามีหลายไซท์ ควรแยกไฟล์ออกมาวางไว้ที่ /etc/apache2/site-available
    เวลาจะเปิดใช้งานก็ใช้คำสั่ง a2ensite
    เช่นไซต์ example.com
    # vi /etc/apache2/sites-available/example.com

    <VirtualHost *:80>
      ServerAdmin webmaster@example.com
      ServerName www.example.com
    
      DocumentRoot /var/www/www.example.com/
      <Directory />
        Options FollowSymLinks
        AllowOverride All
      </Directory>
      <Directory /swww2/var/www/www.thaitux.info/>
        Options Indexes FollowSymLinks MultiViews
        DirectoryIndex index.html index.php
        AllowOverride All
        Order allow,deny
        allow from all
      </Directory>
    
      ErrorLog /var/log/apache2/error.log
    
      LogLevel warn
    
      CustomLog /var/log/apache2/access.log combined
      ServerSignature On
    </VirtualHost>

    เปิดใช้งานด้วยคำสั่ง
    # a2ensite example.com
    # /etc/init.d/apache2 restart

    apache2-ssl

    !!!BETA!!!
    เอามาจาก

    Debian Wiki - SubversionApache2SSLHowto
    Bug#395823: apache2-ssl-certificate disappeared

    ติดตั้ง apache2 กับ ssl
    # aptitude install apache2 openssl

    เปิดใช้โมดูล ssl
    # a2enmod ssl

    แก้ให้ apache2 ใช้พอร์ต https คือ 443
    # vi /etc/apache2/ports.conf

    ...
    Listen 443

    etch รุ่นล่าสุด ชุดคำสั่งสร้างกุญแจของ apache2 ชื่อ apache2-ssl-certificate หายไปแล้ว
    ต้องสั่งเองทีละขั้น
    # export RANDFILE=/dev/random
    # mkdir -p /etc/apache2/ssl
    # openssl req $@ -new -x509 -days 365 -nodes -out \
    /etc/apache2/ssl/apache.pem -keyout /etc/apache2/ssl/apache.pem

    กรอกข้อมูลตามปกติ
    ...
    Country Name (2 letter code) [AU]: <<<--- TH
    State or Province Name (full name) [Some-State]: <<<--- Bangkok
    Locality Name (eg, city) []: <<<--- Bangkok
    Organization Name (eg, company) [Internet Widgits Pty Ltd]: <<<--- Example Co.,Ltd.
    Organizational Unit Name (eg, section) []: <<<--- computer
    Common Name (eg, YOUR name) []: <<<--- webmaster
    Email Address []: <<<--- webmaster@example.com

    ป้องกันกุญแจ
    # chmod 600 /etc/apache2/ssl/apache.pem

    สร้างไฟล์ไซต์ ssl สมมุติชื่อไฟล์ว่า example-ssl โดยเอาโครงมาจากไฟล์ default
    # cp /etc/apache2/sites-available/default /etc/apache2/sites-available/example-ssl

    ปรับแก้
    # vi /etc/apache2/sites-available/example-ssl

    NameVirtualHost *:443
    <VirtualHost *:443>
    
    ...
        SSLEngine on
        SSLCertificateFile /etc/apache2/ssl/apache.pem
        SSLProtocol all
        SSLCipherSuite HIGH:MEDIUM
    
    </VirtualHost>

    (ตรงนี้ สามารถเปลี่ยนไดเรกทอรี่ของ ssl ไปยังจุดใดก็ได้ ตามที่เราต้องการ
    เช่น ถ้าใช้ svn ไดเรกทอรี่จะเป็น /var/lib/svn/)

    สั่งเปิดใช้ไซต์ที่สร้างขึ้น
    # a2ensite example-ssl

    สั่งเริ่ม apache2 ใหม่
    # /etc/init.d/apache2 restart

    debian: การติดตั้งให้เรียกใช้งานเว็บเซิร์ฟเวอร์ภายในจากภายนอก

    debian: lenny/etch
    package: apache2.2 (2.2.9-10+lenny1/2.2.3-4+etch6)

    สมมุติว่าเซิร์ฟเวอร์ภายนอก ชื่อ www.example.com มีการติดตั้ง apache2 ไว้แล้ว
    และเซิร์ฟเวอร์ภายใน ชื่อ internal.example.com มีการติดตั้ง apache2 ไว้แล้ว

    ทำที่เครื่องเซิร์ฟเวอร์ภายนอกอย่างเดียว โดยใช้มอดูล proxy_http
    # a2enmod proxy
    # a2enmod proxy_http
    # /etc/init.d/apache2 restart

    ติดตั้งชื่อไฟล์ไซต์ใน sites-available ว่า internal
    # vi /etc/apache2/sites-available/internal

    <VirtualHost *:80>
        ServerAdmin webmaster@internal.example.com
        ServerName internal.example.com
        ProxyRequests Off
    
        <Proxy *>
            Order Deny,Allow
            Allow from all
        </Proxy>
    
        ProxyPreserveHost On
        ProxyPass / http://internal.example.com/
        ProxyPassReverse / http://internal.example.com/
    </VirtualHost>

    เปิดใช้งาน
    # a2ensite internal
    # /etc/init.d/apache2 reload

    เสร็จแล้ว
    ถ้าเราตั้งค่า dns ของภายนอกไว้แล้ว สามารถเรียกจากภายนอกผ่าน internal.example.com

    debian: ทำ Mirror ด้วย rsync แบบง่าย

    เอามาจาก ThaiLinuxCafe: ทำ mirror ด้วย rsync อย่างง่าย

    ต้องการ syncronize ไดเรคทอรี่ /var/www ของเครื่อง server1.example.com
    มายังไดเรคทอรี่ /var/www ของเครื่อง mirror.example.com
    ซึ่งมีข้อดีกว่าการคัดลอกธรรมดาด้วย cp หรือ scp ตรงที่
    ถ้าเราลบไฟล์ใน server1 ไฟล์ใน mirror จะถูกลบตามไปด้วย
    ทำให้ทั้งสองเครื่องมีความสดใหม่เหมือนกัน

    ทำที่ทั้งสองเครื่อง server1.example.com และ mirror.example.com
    ติดตั้งและปรับแต่ง rsync
    # aptitude install rsync
    # vi /etc/default/rsync
    RSYNC_ENABLE=true

    # dpkg-reconfigure rsync

    ทำที่เครื่อง server1.example.com
    เพิ่มผู้ใช้ชื่อ someuser
    server1:# useradd -m -s /bin/bash someuser
    ( -m คือให้คัดลอกรูปแบบจากไดเรคทอรี่ /etc/skel/
    -s คือให้ใช้เชลล์คือ /bin/bash )
    ทำที่เครื่อง mirror.example.com
    ทำการคัดลอก
    mirror:# rsync -avz -e ssh someuser@server1.example.com:/var/www/ /var/www/
    <<<--- ใส่ค่า password ของ someuser

    rsync จะทำการคัดลอกไฟล์ทั้งหมดใน /var/www ที่ someuser มีสิทธิในการอ่านทั้งหมด
    มาที่ /var/www ของเครื่อง mirror

    หมายเหตุ
    ตรวจสอบเนื้อที่การใช้ไฟล์ด้วยคำสั่ง
    # cd /var/www
    # du -c | grep total

    ทำให้ rsync ไม่ต้องถามรหัสผ่าน

    ต่อไปเป็นการทำให้ rsync ไม่ต้องถามรหัสผ่าน โดยการใช้ public key

    ทำที่ mirror.example.com
    สร้าง public key เพื่อให้ไม่ต้องใส่รหัสผ่าน
    mirror:# ssh-keygen -t dsa -b 1024
    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]

    คัดลอก key ไปยัง server1
    mirror:# scp ~/.ssh/id_dsa.pub someuser@server1.example.com:/home/someuser/
    Password: <<<--- ใส่ค่ารหัสผ่านของ someuser ( บน server1 )

    ssh ไปยัง server1
    mirror:# ssh server1.example.com -l someuser
    Are you sure you want to continue connecting (yes/no)? <<<--- yes
    Password: <<<--- ใส่ค่ารหัสผ่านของ someuser

    ทำให้ key ที่สร้างไว้ ถูกใช้จาก rsync
    someuser@server1:~$ mkdir ~/.ssh
    someuser@server1:~$ chmod 700 ~/.ssh
    someuser@server1:~$ mv id_dsa.pub ~/.ssh
    someuser@server1:~$ cd ~/.ssh
    someuser@server1:~/.ssh$ touch authorized_keys
    someuser@server1:~/.ssh$ chmod 600 authorized_keys
    someuser@server1:~/.ssh$ cat id_dsa.pub >> authorized_keys
    someuser@server1:~/.ssh$ exit

    ลบ known_hosts เก่าทิ้งก่อน
    mirror:# rm .ssh/known_hosts

    ทดสอบครั้งแรก
    mirror:# rsync -avz --delete -e "ssh -i /root/.ssh/id_dsa" \
    someuser@server1.example.com:/var/www/ /var/www/
    Are you sure you want to continue connecting (yes/no)? <<<--- yes

    เรียบร้อย ครั้งต่อไปเขาจะไม่ถามอีกแล้ว
    เวลาจะใช้งาน ก็ใช้คำสั่งเดิม
    mirror:# rsync -avz --delete -e "ssh -i /root/.ssh/id_dsa" \
    someuser@server1.example.com:/var/www/ /var/www/

    หรือ ถ้าใช้ชื่อเหมือนกัน ในที่นี้ someuser เป็น root เหมือนกันทั้งสองฝั่ง ย่อได้เป็น
    mirror:# rsync -avz --delete --rsh=ssh root@server1.example.com:/var/www/ /var/www/

    จบแล้วครับ

    rsync อัตโนมัติผ่าน cron

    ต่อไปเป็นการให้ทำงานอัตโนมัติผ่าน cron

    ทำที่เครื่อง mirror.example.com
    แก้ไข crontab
    # crontab -e
    ...
    0 0 * * * /usr/bin/rsync -azq --delete -e "ssh -i /root/.ssh/id_dsa" someuser@server1.example.com:/var/www/ /var/www/
    ...
    

    ( พิมพ์ต่อกันเป็นบรรทัดเดียวกันนะครับ - ให้ทำการ sync ตอนเที่ยงคืน ทุกวัน )

    จบแล้วครับ

    debian: บันทึก imagemagick - convert

    แปลงรูปจาก jpg ธรรมดา ไปเป็น jpg แบบ interlace
    ใช้คำสั่งคือ
    # mkdir temp
    # for i in *jpg; do convert $i -interlace line temp/$i ; done

    จะได้ไฟล์ jpg ชุดใหม่เข้าไปอยู่ในไดเรกทอรี่ temp
    แปลงหลายรูป จาก tif ไปเป็น pdf
    # convert -adjoin `ls *.tif` newfile.pdf
    เอามาจาก jmetrix : Viewing multiple page tif on Linux
    แปลง tif หลายไฟล์ไปเป็น tif ไฟล์เดียวหลายภาพ
    $ convert x1.tif x2.tif x3.tif -adjoin newfile.tif
    แตก tif แบบหลายภาพ ออกมาเป็นหลายไฟล์ ไฟล์ละหนึ่งหน้า
    $ convert x.tif x%d.tif
    จะได้ออกมาเป็น x1.tif x2.tif x3.tif
    เอามาจาก [magick-users] how to split multi-page tiff into single pages
    แปลง tif แบบ 8-bit ไปเป็น 4-bit 256 สี เพื่อลดขนาด และสามารถเปิดใน Windows ได้คล่องตัวขึ้น
    เทคนิคคือแปลงเป็น gif ก่อน ไม่งั้นแปลงแล้วเปิดไม่ได้
    $ convert x.tif /tmp/x.gif
    $ convert /tmp/x.gif -depth 4 x.tif
    ไฟล์ tif จาก gimp พิมพ์บน evince ไม่ออก
    แก้ด้วยการแปลงเป็น jpg ก่อน แล้วจึงแปลงกลับเป็น tif อีกที
    $ convert x.tif /tmp/x.jpg
    $ convert /tmp/x.jpg x.tif
    อีกกรณีหนึ่ง ที่ใช้งานจริง คืองานที่เป็น tif แบบหลายหน้า เราจะกระจายออกมาก่อน แล้วแก้ไขด้วย gimp แล้วจึงรวมกลับเข้าไป โดยต้องการให้ไฟล์เล็กที่สุด คือเป็น gray 256 สี ขนาดภาพ A4 ที่ความละเอียด 200dpi
    $ convert x.tif -quality 100 /tmp/x%d.jpg
    หลังจากแก้ไขด้วย gimp เสร็จแล้ว ก็ใช้คำสั่ง
    $ convert /tmp/*jpg -geometry 1654 -density 200 -compress lzw -depth 4 -adjoin x.tif
    ที่มา : My MCP: ImageMagick convert -geometry weirdness
    สแกนหนังสือ ลดขนาดให้มากที่สุด ทำเป็น tiff g4
    $ convert infile.jpg -level 40%,85%,0.5 -type bilevel -monochrome -compress group4 outfile.tif
    ตัวเลข level black,white,gamma ปรับเอาเองตามสภาพหนังสือที่สแกน
    ถ้าเป็นหน้าสี ใช้
    $ convert infile.jpg -compress lzw -colors 8 outfile.tif
    แล้วแปลงเป็น pdf ด้วย tiff2pdf และรวมด้วย pdfjam
    ที่มา : http://www.imagemagick.org/discourse-server/viewtopic.php?f=3&t=6448

    หมายเหตุ
    ต้องลงแพกเกจ imagemagick ก่อน
    # aptitude install imagemagick

    imagemagick: การแก้ไข multiple pages tif

    ยังหาโปรแกรมที่ใช้แก้ไขภาพ tif แบบหลายหน้า แบบ Imaging for Windows บนลินุกซ์ไม่ได้
    ก็คงต้องใช้แบบบรรทัดคำสั่งไปพลาง ๆ ก่อน

    ขั้นตอนคือ
    ต้องลง ImageMagick ก่อน
    $ sudo aptitude install imagemagick

    แตกไฟล์ tif แบบหลายหน้าออกมาเป็น แบบหน้าเดียวหลายไฟล์
    $ convert image.tif x%d.tif

    แก้ไขหน้าที่ต้องการจากไฟล์ที่แตกออกมาแล้ว ด้วย gimp สมมุติว่าเป็นหน้า 2
    $ gimp x2.tif

    เมื่อบันทึกเรียบร้อยแล้ว ก็รวมกลับเป็นไฟล์เดียวตามเดิม สมมุติว่ามีทั้งหมด 3 ไฟล์
    $ convert x1.tif x2.tif x3.tif -adjoin newimage.tif

    ถ้าจะให้บีบอัดด้วย

    • ถ้าเป็นภาพสีหรือสีเทา
      $ convert newimage.tif -compress lzw newimage.tif
    • ถ้าเป็นภาพแบบขาวดำ
      $ convert newimage.tif -compress fax newimage.tif
    • สำหรับการบีบอัดมีพารามิเตอร์คือ None BZip Fax Group4 JPEG JPEG2000 Lossless LZW RLE และ Zip
      ต้องทดลองเลือกใช้ดูให้เหมาะกับประเภทของภาพ

    ลยไฟล์ย่อยทิ้ง
    $ rm x?.tif

    ดูผลได้ด้วย evince
    $ evince newimage.tif

    อ้างอิง : debian: บันทึก imagemagick - convert

    *** โปรดระวัง - ไม่สามารถใช้กับไฟล์ tif ที่มีข้อมูล annotation ได้ ***

    update
    จากขั้นตอนข้างบน สามารถนำมาเขียนสคริปต์ทำให้ใช้คำสั่งเดียวได้ ดังนี้
    สมมุติว่าจะต้องการแก้ไข image.tif ในหน้า 0 และหน้า 1 สั่งจากสคริปต์ว่า
    $ d.edittif image.tif 0 1
    gimp จะเปิดไฟล์ออกมา 2 ไฟล์ คือหน้าแรก และหน้าที่สอง
    หลังจากบันทึกและปิด gimp แล้ว จะได้ไฟล์ image-new.tif ออกมาเป็นภาพที่แก้ไขแล้วพร้อมบีบอัดเรียบร้อย

    เนื้อไฟล์ d.edittif มีดังนี้
    $ sudo touch /usr/local/bin/d.edittif
    $ sudo chmod 755 /usr/local/bin/d.edittif
    $ sudo vi /usr/local/bin/d.edittif

    #!/bin/bash
    # EDIT MULTIPLE PAGES TIF FILE
    # PREREQUIST: gimp imagemagick evince
    
    #NAME=${0##*/}
    NAME=`basename $0`
    
    USAGE=" Usage: $NAME FILE PAGE0 PAGE1 ...
        ex1: $NAME image.tif 0 1 = Edit file image.tif on PAGE0 and PAGE1"
    
    phelp()
    {
        echo "$NAME: edit multiple pages tif.
    $USAGE"
    }
    
    while getopts "h" opt; do
        case "$opt" in
        h)  phelp; exit 0;;
        *)  echo "$Usage" 1>&2; exit 2;;
        esac
    done
    
    shift $((OPTIND - 1))
    
    if [ ! $2 ]; then
        phelp
        exit 1;
    fi
    
    TIF=$1
    shift
    
    FILE=${TIF%.*}      #STRIP FILENAME
    
    EDITFILE=""
    while [ $1 ]; do
        EDITFILE="$EDITFILE ~${FILE}$1.tif"
        shift
    done
    
    ALLFILE=`ls ~${FILE}*.tif`
    
    if [ -f $TIF ]; then
        echo "Process $TIF"
        /usr/bin/convert $TIF "~$FILE%d.tif"
        if [ -f "~${FILE}0.tif" ]; then
            #EDIT WITH gimp
            echo "Edit $EDITFILE"
            /usr/bin/gimp $EDITFILE
    
            #COMPRESS EACH TIF
            for i in $ALLFILE; do
                echo -n "IDENTIFY $i "
                if /usr/bin/identify -verbose $i | grep "Gray: 1-bits"; then
                    /usr/bin/convert $i -compress fax $i
                else
                    /usr/bin/convert $i -compress lzw $i
                fi
            done
    
            /usr/bin/convert $ALLFILE -adjoin -compress lzw "$FILE-new.tif" #COMPRESS & JOIN
            /usr/bin/evince "$FILE-new.tif"     #VIEW NEW FILE
            rm $ALLFILE     #DELETE ALL SPLIT FILE
        fi
    fi
    

    ลองบนเดเบียน sid ครับ

    imagemagick: ทำ annotate ไฟล์ tif

    ความจำเป็นบีบบังคับให้ต้องรีบหาโปรแกรมที่สามารถทำ Annotation ไฟล์ tif ให้ได้
    จริง ๆ ก็มีโปรแกรมชื่อ Xournal ที่สามารถทำ annotate บน pdf ได้
    ซึ่งเราอาจแปลง tif ไปเป็น pdf ก่อน แล้วจึงค่อยลงมือแก้ไข
    แต่พบว่าในรุ่นปัจจุบันบน sid คือ 0.3.3 ยังทำ Text annotation ไม่ได้
    และรุ่นล่าสุด 0.4 ความสามารถและการใช้งานยังค่อนข้างด้อยกว่าที่ Kodakimg ทำได้อยู่โข

    แต่จากครั้งก่อนที่ทำ การแก้ไข multiple pages tif
    คราวนี้เลยได้ความคิดว่าถ้าลองเปลี่ยนจาก Gimp มาเป็น Inkscape เราก็จะได้ความสามารถในการทำ annotation จาก inkscape ในแบบที่ก้าวหน้าสุด ๆ

    มีปัญหาที่ต้องแก้คือ inkscape ไม่สามารถแก้ไขไฟล์แบบหลายหน้าได้ (แต่ในอนาคตคงมาแน่ ๆ เห็นอยู่ใน wishlist ลำดับต้น ๆ )
    ทางแก้แบบชั่วคราวคือ กระจายไฟล์ลงในไดเรคทอรี่ชั่วคราวที่สร้างไว้ แล้วแก้ไขตามไฟล์ที่เราระบุ

    อีกปัญหาคือเวลาแปลงกลับจาก svg มาเป็น tif แบบหลายหน้าแล้ว ข้อมูล annotation ที่เราทำไว้ จะถูกแปลงเป็นข้อมูล bitmap ฝังรวมกับไฟล์ tif ไปหมด
    ทางแก้ชั่วคราวอีกเหมือนกันคือ ให้คงไดเรกทอรี่ชั่วคราวนี้ไว้ โดยไม่ลบไฟล์ทิ้งเลย เมื่อเวลาเราใช้คำสั่ง annotate ครั้งใหม่ เขาก็จะมาแก้ไขต่อจากที่เราเคยแก้เอาไว้ โดยต้องยอมรกรุงรังบ้าง

    เนื้อแบตช์ไฟล์มีดังนี้
    $ sudo vi /usr/local/bin/d.tifannotate

    #!/bin/bash
    # ANNOTATE MULTIPLE PAGES TIF FILE
    # PREREQUIST: inkscape imagemagick evince
    
    #NAME=${0##*/}
    NAME=`basename $0`
    
    USAGE=" Usage: $NAME FILE PAGE0 PAGE1 ...
        ex1: $NAME image.tif 0 1 = Edit file image.tif on PAGE0 and PAGE1"
    
    phelp()
    {
        echo "$NAME: annotate multiple pages tif.
    $USAGE"
    }
    
    while getopts "h" opt; do
        case "$opt" in
        h)  phelp; exit 0;;
        *)  echo "$Usage" 1>&2; exit 2;;
        esac
    done
    
    shift $((OPTIND - 1))
    
    if [ ! $2 ]; then
        phelp
        exit 1;
    fi
    
    TIF=$1
    shift
    
    FILE=${TIF%.*}      #STRIP FILENAME
    TEMPDIR="${FILE}~"
    
    EDITFILES=""
    while [ $1 ]; do
        EDITFILES="$EDITFILES ${FILE}$1.svg"
        shift
    done
    
    INKSCAPE=`which inkscape`
    IDENTIFY=`which identify`
    CONVERT=`which convert`
    VIEW=`which evince`
    if [ -f $TIF ]; then
        echo "Process $TIF"
        #TEST TEMP DIR EXIST
        if ! [ -d $TEMPDIR ]; then
            #CREATE TEMP DIR AND SPLIT tif INTO 
            mkdir $TEMPDIR
            $CONVERT $TIF "${TEMPDIR}/$FILE%d.tif"
    
            #ENTER WORKING TEMP DIR
            pushd $TEMPDIR
    
            ALLFILE=`ls ${FILE}*.tif`
            #CONVERT TO svg
            for i in $ALLFILE; do
                $INKSCAPE -l "${i%.*}.svg" $i
            done
        else
            #ELSE; EDIT OLD svg
            pushd $TEMPDIR
        fi
    
        #ANNOTATE   
        if [ -f "${FILE}0.svg" ]; then
            #ANNOTATE WITH inkscape
            echo "Edit $EDITFILES"
            $INKSCAPE $EDITFILES
    
            ALLSVG=`ls ${FILE}*.svg`
            #CONVERT ADJOIN
            $CONVERT $ALLSVG -adjoin -compress lzw "$FILE-new.tif"  #COMPRESS & JOIN
            $VIEW "$FILE-new.tif"       #VIEW NEW FILE
            mv "$FILE-new.tif" ..
        fi
    
        #EXIT WORK DIR
        popd
    
        #REMOVE DIR
        #rm -rf $TEMPDIR
    fi
    

    $ sudo chmod 755 /usr/local/bin/d.tifannotate

    เรียกใช้งานด้วยคำสั่ง... โปรแกรม ไฟล์tif หน้าที่ต้องการแก้ไข
    $ d.tifannotate FILE.tif 0 1 ...
    จะได้ไฟล์ที่ทำ annotate แล้วชื่อ FILE-new.tif และไดเรกทอรี่ชั่วคราวชื่อ FILE~ เหลืออยู่ เพื่อใช้ในการแก้ไขครั้งต่อไป (แต่ถ้าไม่ได้ใช้แน่ ๆ ก็อาจเติมคำสั่งลบต่อท้ายแบตช์ไฟล์ หรือลบด้วยมือเอาทีหลังก็ได้ครับ)

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

    อย่าลืม เปลี่ยนขนาดกระดาษไน inkscape ด้วย

    แต่ติดใจการใช้งาน annotation บน inkscape จริง ๆ แฮะ

    debian: บันทึกการคอมไพล์ pidgin

    ลูกสาวให้ทำ msn ในลินุกซ์ให้ใช้ เคยอ่านผ่านตาที่ ubuntuclub.com
    เลยทดลองดาวน์โหลด pidgin มาคอมไพล์เอง

    คอมไพล์ครั้งแรกไม่ผ่าน ค้นเจอที่ ubuntuforum รวบรวมแพกเกจที่จำเป็นดังนี้
    $ sudo aptitude install libgtk2.0-dev libxml-perl libssl-dev libnspr4-dev libnss3-dev

    หลังจากนั้นก็คอมไพล์ตามปกติ ก็สามารถเล่นได้แล้ว

    debian: ลองติดตั้ง dns

    เอามาจาก
    ThaiLinuxCafe - debian : ติดตั้ง dns อย่างง่าย

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

    • เน็ตเวิร์กเราเป็น 192.168.1.0/24 มีโดเมนเป็น example.com
    • เครื่องที่ทำหน้าที่ name server มีชื่อว่า server1.example.com ไอพีเป็น 192.168.1.1 โดยมีชื่อเสมือนคือ ns1.example.com
    • มีเครื่องในวงเครื่องอื่น ๆ ดังนี้
      • ns2 = 192.168.1.2
      • client1 = 192.168.1.101
      • client2 = 192.168.1.102
      • client3 = 192.168.1.103

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

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

    เริ่มด้วย
    ติดตั้ง DNS และโปรแกรมช่วย
    # aptitude install bind9 dnsutils

    ไปที่ไดเรกทอรี่ของการปรับตั้ง
    # cd /etc/bind

    เริ่มสร้างโซนไฟล์ ให้ชื่อว่า example.com.zone มีเนื้อไฟล์ดังนี้
    # vi example.com.zone

    $TTL    86400
    @          IN      SOA    server1.example.com.    root.server1.example.com. (
                              51              ; serial (d. adams)
                              3H              ; refresh after 3 hours
                              15M             ; retry after 15 minutes
                              1W              ; expire after 7 days
                              1D )            ; minimum TTL (Time To Live) of 1 days
    @          IN      NS      ns1.example.com.    ; primary NS
    @          IN      NS      ns2.example.com.    ; secondary NS
    
    ns1        IN      CNAME  server1
    
    ; append or edit host ip here
    server1  IN      A      192.168.1.1
    ns2      IN      A      192.168.1.2
    client1  IN      A      192.168.1.101
    client2  IN      A      192.168.1.102
    client3  IN      A      192.168.1.103

    สร้างรีเวิร์สไฟล์ ให้ชื่อว่า example.com.reverse มีเนื้อไฟล์ดังนี้
    # vi example.com.reverse

    $TTL    86400
    @          IN      SOA    server1.example.com.    root.server1.example.com. (
                              51              ; serial (d. adams)
                              3H              ; refresh after 3 hours
                              15M             ; retry after 15 minutes
                              1W              ; expire after 7 days
                              1D )            ; minimum TTL (Time To Live) of 1 days
    @          IN      NS      ns1.example.com.    ; primary NS
    @          IN      NS      ns2.example.com.    ; secondary NS
    
    ; append or edit host name here
    1        IN      PTR    server1.example.com.
    2        IN      PTR    ns2.example.com.
    101      IN      PTR    client1.example.com.
    102      IN      PTR    client2.example.com.
    103      IN      PTR    client3.example.com.

    สร้างคอนฟิกไฟล์สำหรับโซน example.com ให้ชื่อว่า example.com.conf มีเนื้อไฟล์ดังนี้
    # vi example.com.conf

    zone "example.com" IN {
            type master;
            file "/etc/bind/example.com.zone";
            allow-update { none; };
    };
    zone "1.168.192.in-addr.arpa" IN {
            type master;
            file "/etc/bind/example.com.reverse";
            allow-update { none; };
    };

    เปลี่ยนสิทธ์ให้ bind เป็นเจ้าของไฟล์
    # chown bind:bind example.com.*

    บอกให้ bind9 เอาไฟล์ของเราไปใช้งาน โดยการเพิ่มลงในไฟล์ named.conf.local ดังนี้
    # vi named.conf.local

    ...
    include "/etc/bind/example.com.conf";

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

    ถ้ามีข้อผิดพลาด ให้ดูที่ /etc/log/syslog

    ทดสอบโดย
    # nslookup server1.example.com
    จะแสดงผลเป็น 192.168.1.1
    Server: 192.168.1.1
    Address: 192.168.1.1#53

    Name: server1.example.com
    Address: 192.168.1.1

    ทดสอบย้อนกลับ
    # nslookup 192.168.1.1
    จะแสดงผลเป็น server1.example.com
    Server: 192.168.1.1
    Address: 192.168.1.1#53

    1.1.168.192.in-addr.arpa name = server1.example.com.

    หมายเหตุ

    • เลขซีเรียลตามตัวอย่างเป็น 51 แต่ส่วนใหญ่นิยมใช้วันที่ เช่น 2006030401 เป็นต้น
    • ในเนื้อไฟล์ของโซนไฟล์และริเวิร์สไฟล์ เวลาพิมพ์ระวังอยาลืมเครื่องหมายจุด ท้ายชื่อโฮสต์
    • สำหรับเครื่อง DNS ที่ทำหน้าที่เกตเวย์ออกอินเตอร์เน็ต พบว่าเมื่อแก้ไขไฟล์ /etc/resolv.conf ให้ชี้ไปที่ DNS ของไอเอสพี เครื่องเราจะทำหน้าทีเป็น slave โดยอัตโนมัติ
    • ถ้าจะเพิ่มโดเมน และเป็นเน็ตเวิร์กคนละวง ก็ใช้วิธีเดียวกันได้ โดยสร้างโซนไฟล์ รีเวิร์สไฟล์ และคอนฟิกไฟล์สำหรับโดเมนที่เพิ่ม
    • ถ้าเพิ่มโดเมน และเป็นเน็ตเวิร์กวงเดียวกัน เช่น หนึ่งไอพี มีหลายโดเมน ให้สร้างเฉพาะโซนไฟล์ ไม่ต้องสร้างรีเวิร์สไฟล์ ซึ่งก็จะทำให้เรียกดูชื่อย้อนจากไอพีไม่ได้
    • ถ้าจะเพิ่มเน็ตเวิร์กเป็นสองวง โดยเป็นโดเมนเดียวกัน ให้ดูตัวอย่างที่ Debian Administration: Two-in-one DNS server with BIND9

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

    อ้างอิงเพิ่มเติม

    debian: bind9 + chroot + internal/external

    *** ยังเขียนไม่เสร็จ + ยังไม่ได้ตรวจทานซ้ำ ***

    เป้า

    • ติดคุกให้ bind (chroot jail)
    • จะทำให้สามารถใช้งานได้ทั้งภายใน และภายนอก (ภายนอกไม่ค่อยจำเป็น แต่ติดตั้งไว้เผื่อจะทำอะไรเพิ่ม) คือโดเมนเดียวกัน แต่มีไอพี 2 กลุ่ม ภายในกลุ่มนึง และภายนอกอีกกลุ่มนึง
    • ติดตั้งเป็นแบบสามารถเปลี่ยนค่าไอพีได้ (allow-update)

    สมมุติว่า

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

    ติดตั้งแพกเกจ
    # aptitude install bind9 dnsutils

    ทำเรื่องติดคุก (chroot jail) จะย้ายคุกไปไว้ที่ /sys1/chroot/bind
    # vi /etc/default/bind9

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

    สร้างผู้ใช้ชื่อ bindinjail ให้ใช้เชลล์ไม่ได้ อยู่ในกลุ่ม nogroup
    # useradd -g nogroup -d /nonexistent -s /bin/false -u 65533 bindinjail
    # passwd -l bindinjail

    สร้างคุก
    # mkdir -p /sys1/chroot/bind/{etc,dev,var/cache/bind,var/log,var/run/bind/run}
    # mv /etc/bind /sys1/chroot/bind/etc
    # ln -sf /sys1/chroot/bind/etc/bind /etc
    # mknod /sys1/chroot/bind/dev/null c 1 3
    # mknod /sys1/chroot/bind/dev/random c 1 8
    # chmod 666 /sys1/chroot/bind/dev/null /sys1/chroot/bind/dev/random
    # chown -R bindinjail:nogroup /sys1/chroot/bind/var/*
    # chown -R bindinjail:nogroup /sys1/chroot/bind/etc/bind/

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

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

    ปรับตั้งระบบปูมของ bind
    # vi /sys1/chroot/bind/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

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

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

    แก้ไข options ให้มาใช้ pid ของคุก
    # vi /sys1/chroot/bind/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

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

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

    บอก bind ว่าเราจะใช้งาน dnssec
    # vi /etc/bind/named.conf.options

    ...
    options {
        ...
        recursion yes;
        dnssec-enable yes;
    };
    ...

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

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

    ...
    acl internal {
        127.0.0.0/8;
        192.168.0.0/16;
    };
    
    include "/etc/bind/example.com.conf";
    include "/etc/bind/example.org.conf";
    ...

    สร้างไฟล์เปล่า ๆ กันเหนียวเผื่อทดสอบ จะได้มีไฟล์รองรับ
    # touch /etc/bind/{example.com.conf,example.org.conf}

    สร้างกุญแจ สำหรับอัปเดตไอพี example.com และ example.org

    ของ example.com
    สั่งสร้างกุญแจสำหรับลงทะเบียนโซน
    # dnssec-keygen -r /dev/urandom -a rsasha1 -b 1024 -n ZONE example.com
    ตัวอย่างได้ผลเป็น
    Kexample.com.+005+11187

    และได้ไฟล์ Kexample.com.+005+11187.key และ Kexample.com.+005+11187.private

    และกุญแจสำหรับอัปเดตโซน
    # dnssec-keygen -r /dev/urandom -k -a rsasha1 -b 1024 -n ZONE example.com
    ตัวอย่างได้ผลเป็น

    Kexample.com.+005+09453

    และได้ไฟล์ Kexample.com.+005+09453.key และ Kexample.com.+005+09453.private

    สร้างไฟล์คอนฟิกของโซน example.com
    # vi example.com.conf

    view "internal" {
        match-clients { internal; };
        zone "example.com" IN {
            type master;
            file "/etc/bind/internal/example.com.zone.signed";
            key-directory "/etc/bind";
            update-policy {
                grant example.com. subdomain example.com any;
            };
        };
        zone "0.168.192.in-addr.arpa" IN {
            type master;
            file "/etc/bind/internal/example.com.reverse.signed";
            key-directory "/etc/bind";
            update-policy {
                grant example.com. subdomain example.com any;
            };
        };
    };
    
    view "external" {
        match-clients { any; };
        zone "example.com" IN {
            type master;
            file "/etc/bind/external/example.com.zone.signed";
            key-directory "/etc/bind";
            update-policy {
                grant example.com. subdomain example.com any;
            };
    
        };
    };
    

    สร้างโซนไฟล์ของ internal
    # vi internal/example.com.zone
    อันนี้ลองเปลี่ยนตามจริงนะครับ สมมุติว่าเครื่องนี้เอาชื่อ server1 เป็นหลัก แล้วใช้ชื่ออื่นคือ www, ns1, ftp, mail เป็นชื่อรอง

    $TTL    86400
    @       IN  SOA server1.example.com. root.server1.example.com. (
                        41      ; serial (d. adams)
                        3H      ; refresh
                        15M     ; retry
                        1W      ; expiry
                        1D )    ; minimum
    @           IN  NS  ns1.example.com.    ; primary NS
    @           IN  NS  ns2.example.com.    ; secondary NS
    
    www         IN  CNAME   server1
    ns1         IN  CNAME   server1
    ftp         IN  CNAME   server1
    mail        IN  CNAME   server1
    www         IN  A   192.168.1.1
    ns2         IN  A   192.168.1.2
    work1       IN  A   192.168.1.101
    work2       IN  A   192.168.1.102
    work3       IN  A   192.168.1.103
    
    $INCLUDE /etc/bind/Kexample.com.+005+11187.key
    $INCLUDE /etc/bind/Kexample.com.+005+09453.key
    

    ลงทะเบียนโซน example.com ของ internal
    # cd internal
    # dnssec-signzone -r /dev/urandom -t -g -o example.com example.com.zone /etc/bind/Kexample.com.+005+11187.private

    example.com.zone.signed
    Signatures generated:                       21
    Signatures retained:                         0
    Signatures dropped:                          0
    Signatures successfully verified:            0
    Signatures unsuccessfully verified:          0
    Runtime in seconds:                      0.063
    Signatures per second:                 331.151
    

    # cd ..

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

    เสร็จโซนไฟล์ของ internal

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

    ทดสอบอัปเดต
    # nsupdate -d -v -k Kexample.com.+005+09453.private

    Creating key...
    > server 192.168.1.1
    > zone example.com
    > update add testing.example.com. 3600 A 192.168.1.5
    > show
    Outgoing update query:
    ;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id:      0
    ;; flags: ; ZONE: 0, PREREQ: 0, UPDATE: 0, ADDITIONAL: 0
    ;; UPDATE SECTION:
    testing.example.com.    3600    IN      A       192.168.1.5
    
    > send
    Sending update to 192.168.1.1#53
    Outgoing update query:
    ;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id:  43656
    ;; flags: ; ZONE: 1, PREREQ: 0, UPDATE: 1, ADDITIONAL: 1
    ;; ZONE SECTION:
    ;example.com.                   IN      SOA
    
    ;; UPDATE SECTION:
    testing.example.com.    3600    IN      A       192.168.1.5
    
    ;; SIG0 PSEUDOSECTION:
    .                       0       ANY     SIG     0 5 0 0 20080223084852 20080223083852 9453 example.com. zpGfPIECwJ8V31lm7mlq7zMiKYSuh2TlIyOZgEUJjZUgYTRHYZrwi75Z mDaKAJGb7uG+r4SLmuFDRdjQQ1mvtZcuo8SK2yjHK59QzUoFnGJXz427 vRNj/do4DbMxMkJpJwqDiKU87lGiy+CXvpbQAm+4AeJutpCTsGnfhixx k4c=
    
    
    Reply from update query:
    ;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id:  43656
    ;; flags: qr ra ; ZONE: 0, PREREQ: 0, UPDATE: 0, ADDITIONAL: 0
    
    
    > quit
    

    ลองค้นดูอีกที
    # nslookup testing.example.com localhost

    Server:         localhost
    Address:        127.0.0.1#53
    
    Name:   testing.example.com
    Address: 192.168.1.5
    

    เสร็จ forward internal

    ยังเขียนไม่เสร็จ หมดแรงก่อน ข้อเขียนข้างบนยังไม่ได้ตรวจทาน
    - reverse internal example.com ทำเหมือนเดิม ยกเว้นตอนอัปเดตใช้คำสั่งเป็น
    > update add 5.1.168.192.in-addr.arpa. 3600 IN PTR testing.example.com.
    โดยอย่าให้มีคำสั่ง zone XXXX มาก่อนหน้า อัปเดตได้เลย

    - forward external ทำเหมือนเดิม ---รอเขียนต่อ---

    อ้างอิง
    http://myrddin.org/howto/debian-bind9-chroot/
    http://www.ops.ietf.org/dns/dynupd/secure-ddns-howto.html
    http://garnser.blogspot.com/2008/02/how-to-enable-bind-with-dnssec-and.html
    http://www.unixwiz.net/techtips/bind9-chroot.html

    Topic: 

    debian: bind9 แบบให้บริการเครือข่ายภายในและภายนอก

    bind9 แบบให้บริการเครือข่ายภายในและภายนอก

    การตั้งให้ name server ให้บริการเครือข่ายภายในและภายนอก โดยใช้โดเมนเดียวกัน มีข้อดีตรงที่เราไม่จำเป็นต้องแยกโดเมนให้จำยาก และเมื่อเวลาลูกข่ายภายในสั่งค้น การค้นก็ไม่ต้องเปลืองแพ็กเก็ตออกสู่ภายนอก เพียงแต่การปรับตั้งยุ่งนิดนึง
    # aptitude install bind9

    สมมุติว่า

    • เราให้บริการอยู่ 2 โดเมนคือ example.com กับ example.org
    • example.com ภายในเป็น 192.168.1.0/24 ส่วนภายนอกเป็น 111.112.113.114 (ไอพีสมมุติ)
    • example.org ภายในเป็น 10.0.0.0/24 ภายนอกเป็น 211.212.213.214 (ไอพีสมมุติ)
    • เครื่องที่เรากำลังตั้งอยู่ ให้ชื่อว่า ns1 (ซึ่งเราทำเป็น all-in-one ก็อาจมีชื่อเป็น www และ mail ด้วย)
      โดยมีไอพีของ ns1.example.com เป็น 192.168.1.1 และ ไอพีของ ns1.example.org เป็น 10.0.0.1

    จะต้องมีไฟล์คอนฟิกคือ example.com.conf และ example.org.conf
    มีโซนไฟล์คือ internal/example.com.zone กับ internal/example.org.zone และ external/example.com.zone กับ external/example.org.zone
    ตามด้วยรีเวิร์สไฟล์คือ internal/example.com.reverse กับ internal/example.org.reverse และ external/example.com.reverse กับ external/example.org.reverse

    เริ่มด้วย
    # cd /etc/bind

    เพิ่มกฎแยก internal และ external และให้อ่านไฟล์คอนฟิกของแต่ละโดเมนเข้ามาในระบบด้วย ผ่านไฟล์ named.conf.local
    # vi named.conf.local

    ...
    acl internals {
        127.0.0.0/8;
        10.0.0.0/24;
        192.168.1.0/24;
    };
    
    include "/etc/bind/example.com.conf";
    include "/etc/bind/example.org.conf";
    

    สร้างไฟล์คอนฟิกของ example.com
    # vi example.com.conf

    view "internal" {
        match-clients { internals; };
        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; };
        };
    };
    
    view "external" {
        match-clients { any; };
        zone "example.com" IN 
            type master;
            file "/etc/bind/external/example.com.zone";
            allow-update { none; };
        };
        zone "113.112.111.in-addr.arpa" IN {
            type master;
            file "/etc/bind/external/example.com.reverse";
            allow-update { none; };
        };
    };
    

    สร้างไฟล์คอนฟิกของ example.org
    # vi example.org.conf

    view "internal" {
        match-clients { internals; };
        zone "example.org" IN {
            type master;
            file "/etc/bind/internal/example.org.zone";
            allow-update { none; };
        };
        zone "0.0.10.in-addr.arpa" IN {
            type master;
            file "/etc/bind/internal/example.org.reverse";
            allow-update { none; };
        };
    };
    
    view "external" {
        match-clients { any; };
        zone "example.org" IN 
            type master;
            file "/etc/bind/external/example.org.zone";
            allow-update { none; };
        };
        zone "213.212.211.in-addr.arpa" IN {
            type master;
            file "/etc/bind/external/example.org.reverse";
            allow-update { none; };
        };
    };
    

    อย่าลืมสร้างไดเรกทอรี่ก่อน
    # mkdir internal external

    ตัวอย่างสร้างโซนไฟล์ของ internal/example.com
    # vi internal/example.com.zone

    $TTL    86400
    @       IN  SOA ns1.example.com. root.ns1.example.com. (
                    42      ; serial (d. adams)
                    3H      ; refresh
                    15M     ; retry
                    1W      ; expiry
                    1D )    ; minimum
    @       IN  NS  ns1.example.com.    ; primary NS
    @       IN  NS  ns2.example.com.    ; secondary NS
    
    www     IN  CNAME   ns1
    ftp     IN  CNAME   ns1
    mail    IN  CNAME   ns1
    ; append or edit host file here
    ns1     IN  A   192.168.1.1
    ns2     IN  A   192.168.1.2
    work1   IN  A   192.168.1.11
    work2   IN  A   192.168.1.12
    ...

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

    $TTL    86400
    @       IN  SOA ns1.example.com. root.ns1.example.com. (
                    42      ; serial (d. adams)
                    3H      ; refresh
                    15M     ; retry
                    1W      ; expiry
                    1D )    ; minimum
    @       IN  NS  ns1.example.com.    ; primary NS
    @       IN  NS  ns2.example.com.    ; secondary NS
    
    www     IN  CNAME   ns1
    ftp     IN  CNAME   ns1
    mail    IN  CNAME   ns1
    ; append or edit host file here
    ns1     IN  A   111.112.113.114
    

    ตัวอย่างสร้างโซนไฟล์ของ internal/example.org
    # vi internal/example.org.zone

    $TTL    86400
    @       IN  SOA ns1.example.org. root.ns1.example.org. (
                    42      ; serial (d. adams)
                    3H      ; refresh
                    15M     ; retry
                    1W      ; expiry
                    1D )    ; minimum
    @       IN  NS  ns1.example.org.    ; primary NS
    @       IN  NS  ns2.example.org.    ; secondary NS
    
    www     IN  CNAME   ns1
    ftp     IN  CNAME   ns1
    mail    IN  CNAME   ns1
    ; append or edit host file here
    ns1     IN  A   10.0.0.1
    ns2     IN  A   10.0.0.2
    shop1   IN  A   10.0.0.11
    shop2   IN  A   10.0.0.12
    ...

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

    $TTL    86400
    @       IN  SOA ns1.example.org. root.ns1.example.org. (
                    42      ; serial (d. adams)
                    3H      ; refresh
                    15M     ; retry
                    1W      ; expiry
                    1D )    ; minimum
    @       IN  NS  ns1.example.org.    ; primary NS
    @       IN  NS  ns2.example.org.    ; secondary NS
    
    www     IN  CNAME   ns1
    ftp     IN  CNAME   ns1
    mail    IN  CNAME   ns1
    ; append or edit host file here
    ns1     IN  A   211.212.213.214
    ...

    ตัวอย่างสร้างรีเวิร์สไฟล์ของ internal/example.com
    # vi internal/example.com.reverse

    $TTL    86400
    @       IN  SOA ns1.example.com. root.ns1.example.com. (
                    42      ; serial (d. adams)
                    3H      ; refresh
                    15M     ; retry
                    1W      ; expiry
                    1D )    ; minimum
    @       IN  NS  ns1.example.com.    ; primary NS
    @       IN  NS  ns2.example.com.    ; secondary NS
    ;
    ; host in network 192.168.1.0
    ; append or edit host file here
    1   IN  PTR ns1.example.com.
    2   IN  PTR ns2.example.com.
    11  IN  PTR work1.example.com.
    12  IN  PTR work2.example.com.
    ...
    

    ตัวอย่างสร้างรีเวิร์สไฟล์ของ external/example.com
    # vi external/example.com.reverse

    $TTL    86400
    @       IN  SOA ns1.example.com. root.ns1.example.com. (
                    42      ; serial (d. adams)
                    3H      ; refresh
                    15M     ; retry
                    1W      ; expiry
                    1D )    ; minimum
    @       IN  NS  ns1.example.com.    ; primary NS
    @       IN  NS  ns2.example.com.    ; secondary NS
    ;
    ; host in network 111.112.113.0
    ; append or edit host file here
    114   IN  PTR ns1.example.com.
    

    ตัวอย่างสร้างรีเวิร์สไฟล์ของ internal/example.org
    # vi internal/example.org.reverse

    $TTL    86400
    @       IN  SOA ns1.example.org. root.ns1.example.org. (
                    42      ; serial (d. adams)
                    3H      ; refresh
                    15M     ; retry
                    1W      ; expiry
                    1D )    ; minimum
    @       IN  NS  ns1.example.org.    ; primary NS
    @       IN  NS  ns2.example.org.    ; secondary NS
    ;
    ; host in network 10.0.0.0
    ; append or edit host file here
    1   IN  PTR ns1.example.org.
    2   IN  PTR ns2.example.org.
    11  IN  PTR shop1.example.org.
    12  IN  PTR shop2.example.org.
    ...
    

    ตัวอย่างสร้างรีเวิร์สไฟล์ของ external/example.org
    # vi external/example.org.reverse

    $TTL    86400
    @       IN  SOA ns1.example.org. root.ns1.example.org. (
                    42      ; serial (d. adams)
                    3H      ; refresh
                    15M     ; retry
                    1W      ; expiry
                    1D )    ; minimum
    @       IN  NS  ns1.example.org.    ; primary NS
    @       IN  NS  ns2.example.org.    ; secondary NS
    ;
    ; host in network 211.212.213.0
    ; append or edit host file here
    214   IN  PTR ns1.example.org.
    

    เปลี่ยนสิทธิ์ทั้งหมด
    # chown bind:bind * -R

    เสร็จแล้ว
    # /etc/init.d/bind9 restart

    หมายเหตุ
    ตัวเลข serial ไม่ค่อยจำเป็นเท่าไหร่ เป็นตัวเลขอะไรก็ได้ แต่ถ้าเราทำเป็น dynamic dns จะต้องเป็นตัวเลขที่เพิ่มค่าขึ้นไปเรื่อย ๆ ให้มากกว่าค่าเดิม ไม่งั้นเขาไม่ยอมอัปเดตค่าไอพีให้

    อ้างอิง
    Two-in-one DNS server with BIND9

    Topic: 

    ลองติดตั้ง dns แบบเปลี่ยนค่าได้

    จะทดลองทำ DNS แบบยอมให้เปลี่ยนค่าไอพีได้

    ขออนุญาตเริ่มใหม่ เพื่อให้บทความจบในตัว

    เริ่มต้นด้วยการติดตั้ง bind9 ใหม่
    # aptitude remove --purge bind9 dnsutils
    # rm -rf /etc/bind
    # aptitude install bind9 dnsutils

    เนื่องจากต้องมีการเปลี่ยนค่าไอพีโดยใช้สิทธิ์ของผู้ใช้ชื่อ bind เราจึงควรสร้างไดเรกทอรีขึ้นมาเพื่อให้ผู้ใช้ bind มีสิทธิ์ในการเขียนไฟล์
    # cd /etc/bind
    # mkdir example.com
    # chown bind:bind example.com
    # cd example.com

    สร้างกุญแจสำหรับใช้ในการอัปเดตค่าไอพี (โดยใช้อัลกอริธึม HMAC-MD5 ขนาด 512บิต ตั้งชื่อกุญแจว่า server1.example.com.)
    # dnssec-keygen -a HMAC-MD5 -b 512 -n HOST server1.example.com.
    ผลลัพธ์ของผมคือ

    Kserver1.example.com.+157+49285

    เราจะได้ไฟล์มาสองไฟล์ซึ่งมีลักษณะคือ K(ชื่อกุญแจ)+NNN+NNNNN โดยมีนามสกุลเป็น .key และ .private

    ตามตัวอย่างได้เนื้อไฟล์ Kserver1.example.com.+157+49285.key เป็น

    server1.example.com. IN KEY 512 3 157 wLuCYKvKDqM2XJsqcspdycoJgLNJKUKga4bHANjE7FY0HCujucYhDUKt FD5wgGXGPCNVsZi7NOYDgVZSIJ8LbA==

    และเนื้อไฟล์ Kserver1.example.com.+157+49285.private เป็น

    Private-key-format: v1.2
    Algorithm: 157 (HMAC_MD5)
    Key: wLuCYKvKDqM2XJsqcspdycoJgLNJKUKga4bHANjE7FY0HCujucYhDUKtFD5wgGXGPCNVsZi7NOYDgVZSIJ8LbA==

    เราจะเอาค่า private key มาใช้ โดยสร้างไฟล์คอนฟิกขึ้นมา ตั้งชื่อว่า dnskeys.conf และนำเนื้อหาของกุญแจ private key มาใช้เป็นค่า secret
    # vi dnskeys.conf

    key server1.example.com. {
        algorithm hmac-md5;
        secret "wLuCYKvKDqM2XJsqcspdycoJgLNJKUKga4bHANjE7FY0HCujucYhDUKtFD5wgGXGPCNVsZi7NOYDgVZSIJ8LbA==";
    };
    

    เพิ่มไฟล์คอนฟิกของคีย์ลงใน named.conf.local
    # vi ../named.conf.local

    ...
    include "/etc/bind/example.com/dnskeys.conf";
    ...

    งานต่อไปเป็นงานซ้ำของเก่าคือสร้างโซนไฟล์ และรีเวิร์สไฟล์ และคอนฟิกไฟล์ โดยปรับเปลี่ยนค่าคอนฟิกไฟล์ตรงคำสั่ง allow-update เท่านั้น

    สร้างโซนไฟล์ชื่อ example.com.zone เหมือนเดิม
    # vi example.com.zone

    $TTL    86400
    @          IN      SOA    server1.example.com.    root.server1.example.com. (
                              51              ; serial (d. adams)
                              3H              ; refresh after 3 hours
                              15M             ; retry after 15 minutes
                              1W              ; expire after 7 days
                              1D )            ; minimum TTL (Time To Live) of 1 days
    @          IN      NS      ns1.example.com.    ; primary NS
    @          IN      NS      ns2.example.com.    ; secondary NS
    
    ns1        IN      CNAME  server1
    
    ; append or edit host ip here
    server1  IN      A      192.168.1.1
    ns2      IN      A      192.168.1.2
    client1  IN      A      192.168.1.101
    client2  IN      A      192.168.1.102
    client3  IN      A      192.168.1.103

    สร้างรีเวิร์สไฟล์ชื่อ example.com.reverse เหมือนเดิม
    # vi example.com.reverse

    $TTL    86400
    @          IN      SOA    server1.example.com.    root.server1.example.com. (
                              51              ; serial (d. adams)
                              3H              ; refresh after 3 hours
                              15M             ; retry after 15 minutes
                              1W              ; expire after 7 days
                              1D )            ; minimum TTL (Time To Live) of 1 days
    @          IN      NS      ns1.example.com.    ; primary NS
    @          IN      NS      ns2.example.com.    ; secondary NS
    
    ; append or edit host name here
    1        IN      PTR    server1.example.com.
    2        IN      PTR    ns2.example.com.
    101      IN      PTR    client1.example.com.
    102      IN      PTR    client2.example.com.
    103      IN      PTR    client3.example.com.

    สร้างคอนฟิกไฟล์ชื่อ example.com.conf โดยกำหนดให้สามารถเปลี่ยนแปลงค่าไอพีได้ (allow-update)
    # vi example.com.conf

    zone "example.com" IN {
        type master;
        file "/etc/bind/example.com/example.com.zone";
        allow-update { key "server1.example.com."; };
    };
    zone "1.168.192.in-addr.arpa" IN {
        type master;
        file "/etc/bind/example.com/example.com.reverse";
        allow-update { key "server1.example.com."; };
    };
    

    เพิ่มค่าคอนฟิกไฟล์ให้ระบบรับรู้ โดยเติมลงใน named.conf.local
    # vi ../named.conf.local

    ...
    include "/etc/bind/example.com/example.com.conf";

    แก้ไขให้กรุ๊ป bind เขียนลงโซนไฟล์ รีเวิร์สไฟล์ และในไดเรกทอรี่นี้ได้ ทั้งนี้เพื่อประโยชน์ตอนอัปเดตค่าไอพี
    # chmod g+w example.com.zone example.com.reverse
    # chmod g+w ../example.com

    เมื่อเสร็จเรียบร้อยแล้ว ก็สั่งเริ่ม bind9 ใหม่
    # /etc/init.d/bind9 restart

    ติดตั้ง ntpdate ซึ่งต้องใช้ตอนอัปเดตไอพี
    # aptitude install ntpdate

    จบการติดตั้งแค่นี้ครับ
    ต่อไปเป็นการทดสอบการอัปเดตค่าไอพี
    ก่อนอื่นต้องซิงก์เวลาก่อน ผมลองใช้เซิร์ฟเวอร์ที่ thaicert
    # ntpdate -s -b clock.thaicert.nectec.or.th

    อัปเดตโดยนำไฟล์ private key มาใช้
    # nsupdate -k Kserver1.example.com.+157+49285.private -d
    ทดลองเปลี่ยนค่า server1.example.com เป็น 192.168.1.4

    server localhost
    zone example.com.
    update delete server1.example.com.
    update add server1.example.com. 60 IN A 192.168.1.4
    send
    quit

    ตัวอย่างหน้าจอภาพเครื่องทดสอบ

    Sending update to 127.0.0.1#53
    Outgoing update query:
    ;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id:  11566
    ;; flags: ; ZONE: 1, PREREQ: 0, UPDATE: 2, ADDITIONAL: 1
    ;; ZONE SECTION:
    ;example.com.                   IN      SOA
    
    ;; UPDATE SECTION:
    server1.example.com.    0       ANY     ANY
    server1.example.com.    60      IN      A       192.168.1.4
    
    ;; TSIG PSEUDOSECTION:
    server1.example.com.    0       ANY     TSIG    hmac-md5.sig-alg.reg.int. 1165632027 300 16 yjgvsLCdFobUj/p7lex21g== 11566 NOERROR 0
    
    
    Reply from update query:
    ;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id:  11566
    ;; flags: qr ra ; ZONE: 0, PREREQ: 0, UPDATE: 0, ADDITIONAL: 1
    ;; TSIG PSEUDOSECTION:
    server1.example.com.    0       ANY     TSIG    hmac-md5.sig-alg.reg.int. 1165632027 300 16 j9mG82rwthDTBJyCopHyUw== 11566 NOERROR 0

    ตรงฟิลด์ status ต้องเป็น NOERROR การทำงานจึงจะสมบูรณ์ครับ

    ทดลองค้นค่า server1.example.com
    # nslookup server1.example.com localhost

    Server:         localhost
    Address:        127.0.0.1#53
    
    Name:   server1.example.com
    Address: 192.168.1.4

    เรียบร้อยแล้วครับ

    หมายเหตุ

    • ในระหว่างการทำงานอัปเตด โปรแกรมจะสร้างโซนไฟล์สำรองขึ้นมาในชื่อว่า example.com.zone.jnl
    • เมื่อสั่งเริ่ม bind9 ใหม่ จะพบว่าเนื้อหาของโซนไฟล์ถูกเปลี่ยนแปลงไปตามการอัปเดต
    • ส่วนการอัปเดตรีเวิร์สไฟล์ด้วย ยังทำไม่เป็นครับ :)
    • ใจจริง อยากปรับเปลี่ยนเนื้อโซนไฟล์กับรีเวิร์สไฟล์ ให้เอาเรื่อง ลูกเล่นของ BIND9 DNS Server! มารวมเสียทีเดียว แต่กลัวตัวเองงง เลยปล่อยไว้อย่างเดิม :)

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

    อ้างอิง

    บันทึกติดตั้ง postfix/SASL/TLS

    Postfix เป็นแพกเกจ Mail Server ที่ได้รับความนิยมมากกว่าแพกเกจของเดเบียนเอง (Exim)
    ดูจาก debian-administration: Mail Server Poll

    ผมแทบไม่รู้อะไรเกี่ยวกับ Postfix SASL และ TLS เลย
    แต่ขออนุญาตบันทึกไว้รอศึกษาครับ

    เอามาจาก tribulaciones: Postfix/SASL/TLS HowTo for Debian Sid and Sarge

    งาน Server
    ติดตั้งแพกเกจ
    # aptitude install postfix postfix-tls libsasl2-modules sasl2-bin openssl

    ปรับตั้ง Postfix ให้ใช้ SASL
    # vi /etc/postfix/main.cf

    ...
    # SASL SUPPORT FOR CLIENTS
    #
    # The following options set parameters needed by Postfix to enable
    # Cyrus-SASL support for authentication of mail clients.
    #
    smtpd_sasl_auth_enable = yes
    smtpd_sasl_security_options = noanonymous
    smtpd_sasl_local_domain = $myhostname
    broken_sasl_auth_clients = yes
    ... 
    smtpd_recipient_restrictions = permit_sasl_authenticated,
     permit_mynetworks, 
     reject_unauth_destination
    ...

    ปรับตั้ง SASL

      แก้คอนฟิกของ SASL
      # vi /etc/postfix/sasl/smtpd.conf
    ...
    pwcheck_method: auxprop
    mech_list: digest-md5 cram-md5
    ...

    สมมุติว่าเซิร์ฟเวอร์เราชื่อ mail.example.com เราจะสร้างผู้ใช้ชื่อ test
    # saslpasswd2 -c -u mail.example.com -a smtpauth test
    Password: <<<--- ใส่รหัสผ่านของ test
    Again (for verification): <<<--- ใส่รหัสผ่านของ test

    คำสั่งนี้จะสร้างไฟล์รหัสผ่านชือ /etc/sasldb2
    เราต้องเปลี่ยนเจ้าของให้มีกรุ๊ปเป็น sasl
    # chgrp sasl /etc/sasldb2
    (เดเบียนเปลี่ยนให้เอง)

    และเปลี่ยนผู้ใช้ postfix ให้มาอยู่ในกรุ๊ป sasl
    # usermod -G sasl postfix

    และเริ่ม postfix ใหม่
    # /etc/init.d/postfix reload

    ตรวจสอบได้โดยการใช้ telnet ตัวอย่างคือ

    [root@mail root]# telnet localhost 25
    Trying 127.0.0.1...
    Connected to localhost.
    Escape character is '^]'.
    220 mail.example.com ESMTP Postfix (Debian/GNU)
    EHLO client.example.com
    250-mail.example.com
    250-PIPELINING
    250-SIZE 20480000
    250-ETRN
    250-AUTH DIGEST-MD5 CRAM-MD5
    250-AUTH=DIGEST-MD5 CRAM-MD5
    250-XVERP
    250 8BITMIME
    QUIT
    221 Bye
    Connection closed by foreign host.

    ปรับตั้ง TLS

      แก้ไขไฟล์ /etc/postfix/main.cf
    ## TLS
    #
    smtpd_use_tls = yes
    smtpd_tls_auth_only = yes
    smtpd_tls_key_file = /etc/postfix/postfix-key.pem
    smtpd_tls_cert_file = /etc/postfix/postfix-cert.pem
    smtpd_tls_CAfile = /etc/postfix/cacert.pem
    smtpd_tls_received_header = yes
    smtpd_tls_session_cache_timeout = 3600s
    tls_random_source = dev:/dev/urandom

    ตั้งค่าเริ่มต้นให้ openssl
    # vi /etc/ssl/openssl.cnf

    ...
    countryName_default          = TH
    stateOrProvinceName_default  = Bangkok
    localityName_default         = Bangkok
    0.organizationName_default   = example.com
    organizationalUnitName_default  = admin
    ...

    เอาสคริปต์ตัวอย่างมาใช้สร้างคีย์
    # /usr/lib/ssl/misc/CA.sh -newca
    CA certificate filename (or enter to create)
    <<<--- [ENTER]
    Making CA certificate ...
    Generating a 1024 bit RSA private key
    .......................................++++++
    ...++++++
    writing new private key to './demoCA/private/./cakey.pem'
    Enter PEM pass phrase: <<<--- ใส่รหัสผ่าน
    Verifying - Enter PEM pass phrase: <<<--- ใส่รหัสผ่านอีกครั้ง
    -----
    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) [TH]: <<<--- [ENTER]
    State or Province Name (full name) [Bangkok]: <<<--- [ENTER]
    Locality Name (eg, city) [Bangkok]: <<<--- [ENTER]
    Organization Name (eg, company) [example.com]: <<<--- ใส่ชื่อร้านหรือหน่วยงาน
    Organizational Unit Name (eg, section) [admin]: <<<--- ชื่อย่อหน่วยงานย่อย
    Common Name (eg, YOUR name) []: <<<--- ชื่อหน่วยงานปกติ
    Email Address []: <<<--- อีเมลเรา
    Please enter the following 'extra' attributes
    to be sent with your certificate request
    A challenge password []: <<<--- [ENTER]
    An optional company name []: <<<--- [ENTER]
    Using configuration from /usr/lib/ssl/openssl.cnf
    Enter pass phrase for ./demoCA/private/./cakey.pem: <<<--- รหัสผ่านให้เหมือน PEM
    ...
    Write out database with 1 new entries
    Data Base Updated

    ไฟล์กุญแจ CA certificate จะอยู่ที่ /etc/ssl/demoCA/private/cakey.pem

    เอาไฟล์ตัวอย่าง CA.sh มาปรับแก้
    # cd /etc/ssl
    # cp /usr/lib/ssl/misc/CA.sh CA-nodes.sh

    เราจะแพตช์ให้ใช้กับของเรา ด้วยการสร้างไฟล์ชื่อ CA.patch
    # vi CA.patch

    --- /usr/lib/ssl/misc/CA.sh     2006-11-16 17:57:32.000000000 +0700
    +++ /etc/ssl/CA-nodes.sh        2006-11-14:16:28:39.000000000 +0700
    @@ -53,13 +53,13 @@ case $i in
         ;;
     -newcert)
         # create a certificate
    -    $REQ -new -x509 -keyout newkey.pem -out newcert.pem $DAYS
    +    $REQ -new -nodes -x509 -keyout newkey.pem -out newcert.pem $DAYS
         RET=$?
         echo "Certificate is in newcert.pem, private key is in newkey.pem"
         ;;
     -newreq)
         # create a certificate request
    -    $REQ -new -keyout newkey.pem -out newreq.pem $DAYS
    +    $REQ -new -nodes -keyout newkey.pem -out newreq.pem $DAYS
         RET=$?
         echo "Request is in newreq.pem, private key is in newkey.pem"
         ;;

    สั่งแพตช์
    # patch -p3 < CA.patch

    สร้างกุญแจจากไฟล์ที่แพตช์แล้ว
    # ./CA-nodes.sh -newreq
    Generating a 1024 bit RSA private key
    ........................++++++
    ...........++++++
    writing new private key to 'newkey.pem'
    -----
    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) [TH]: <<<--- [ENTER]
    State or Province Name (full name) [Bangkok]: <<<--- [ENTER]
    Locality Name (eg, city) [Bangkok]: <<<--- [ENTER]
    Organization Name (eg, company) [example.com]: <<<--- ใส่ชื่อร้านหรือหน่วยงาน
    Organizational Unit Name (eg, section) [admin]: <<<--- ชื่อย่อหน่วยงานย่อย
    Common Name (eg, YOUR name) []: <<<--- ชื่อหน่วยงานปกติ
    Email Address []: <<<--- อีเมลเรา
    Please enter the following 'extra' attributes
    to be sent with your certificate request
    A challenge password []: <<<--- [ENTER]
    An optional company name []: <<<--- [ENTER]
    Request is in newreq.pem, private key is in newkey.pem

    สั่งใช้กุญแจที่สร้าง
    # ./CA-nodes.sh -sign

    คัดลอกไฟล์กุญแจที่สร้างไว้ไปที่ /etc/postfix
    # cp demoCA/cacert.pem /etc/postfix
    # cp newreq.pem /etc/postfix/postfix-key.pem
    # cp newcert.pem /etc/postfix/postfix-cert.pem
    # cd /etc/postfix
    # chown root:root postfix-key.pem ; chmod 400 postfix-key.pem

    เริ่ม postfix ใหม่อีกครั้ง
    # /etc/init.d/postfix reload
    ต้องไม่มีรายงานข้อผิดพลาด

    งาน Client
    ติดตั้งแพกเกจ
    # aptitude install postfix postfix-tls libsasl2-modules openssl

    ปรับตั้ง Postfix ให้ใช้ SASL
    # vi /etc/postfix/main.cf

    ...
    # SASL for client side
    #
    smtp_sasl_auth_enable = yes
    smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
    smtp_sasl_security_options =
    ...

    ปรับแก้ให้ไฟล์รหัสผ่าน อ่านได้จาก root อย่างเดียว
    # chown root:root /etc/postfix/sasl_passwd && chmod 600 /etc/postfix/sasl_passwd

    สร้างไฟล์ข้อมูล sasl_passwd DB ด้วยคำสั่ง postmap
    # postmap hash:/etc/postfix/sasl_passwd

    ปรับตั้ง TLS
    แก้ /etc/postfix/main.cf
    # vi /etc/postfix/main.cf

    ...
    # Use TLS client side
    smtp_use_tls = yes
    ...
    # If we want also the client to issue a certificate, these lines are needed:
    smtp_tls_key_file = /etc/postfix/postfix-key.pem
    smtp_tls_cert_file = /etc/postfix/postfix-cert.pem
    smtp_tls_CAfile = /etc/postfix/cacert.pem
    ...

    เสร็จแล้วด้วยความมึนงง :)

    บันทึก postfix

    การติดตั้ง postfix ฉบับที่ให้ผลดีกว่า คือ
    The Perfect Setup - Debian Sarge (3.1) - Page 4

    บันทึก error

    warning: database /etc/postfix/virtusertable.db is older than source file /etc/postfix/virtusertable
    แก้ด้วยการรัน
    # postmap /etc/postfix/virtusertable
    Topic: 

    debian: ลองติดตั้ง drupal5 แบบเดเบียน

    debian: lenny

    ถ้าต้องการให้ url เป็น http://www.example.com/drupal5 และใช้กับ mysql ใช้คำสั่งเดียว
    # aptitude install drupal5 phppgadmin
    แล้วไปติดตั้งที่ http://www.example.com/drupal5/install.php ได้เลย

    กรณีเรา จะติดตั้งบน postgresql-8.3 (เขาว่ารุ่นนี้มี Full Text Index ให้ใช้แล้ว)
    ติดตั้ง postgresql ก่อน
    # aptitude install postgresql
    # su postgres
    $ createuser -s -P admin

    เอาไว้ใช้งาน phppgadmin

    แก้ไขให้เรียกใช้งานจากลูกข่ายใด ๆ ก็ได้
    # vi /etc/postgresql/8.3/main/postgresql.conf

    listen_addresses = '*'

    แก้ไขสิทธ์
    # vi /etc/postgresql/8.3/main/pg_hba.conf

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

    ติดตั้ง apache2
    # aptitude install apache2 php5 phppgadmin
    (ต้องระบุ apache2 และ php5 เพราะ phppgadmin ดีเพน apache และ php4 ด้วย ถ้าระบุแบบนี้ เขาจะไม่ติดตั้ง apache และ php4 ซึ่งเราไม่ต้องการ โดยอัตโนมัติ )
    # dpkg-reconfigure phppgadmin
    # vi /etc/phppgadmin/apache.conf

    # deny from all
    allow from 127.0.0.0/255.0.0.0
    allow from all

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

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

     Web server(s) that should be configured automatically: apache2
     Configure database for drupal5 with dbconfig-common? Yes
     Database type to be used by drupal5: pgsql
     PostgreSQL application password for drupal5: DRUPAL5-PASSWORD
    

    *** ยังไม่แน่ใจว่าเป็นบั๊กหรือไม่ เขาไม่ยอมโหลดส่วนขยาย pdo ทำให้ pdo_pgsql โหลดต่อไม่ได้ ***
    แก้ไขเรื่อง pdo
    # vi /etc/php5/apache2/php.ini

    ...
    extension=pdo.so
    ...

    ไปที่ http://www.example.com/drupal5/install.php
    ติดตั้งเสร็จแล้ว ไปต่อที่ http://www.example.com/drupal5 ได้เลย

    หมายเหตุ

    หากต้องการเปลี่ยนเฉพาะซับไดเรคทอรี่เป็นชื่ออื่น เช่นเปลี่ยนเป็น http://www.example.com/content
    1. ให้เปลี่ยนที่ไฟล์ /etc/drupal/5/apache.conf
      # vi /etc/drupal/5/apache.conf
      Alias /content /usr/share/drupal5
    2. สร้างไฟล์ทดแทนไฟล์ settings.php ของ drupal ชื่อไฟล์ baseurl.php
      # cd /etc/drupal/5/sites/default
      # vi baseurl.php
      <?php
       $base_url = 'http://www.example.com/content';  // NO trailing slash!
       ?>

      # chgrp www-data baseurl.php

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

    (แต่หากต้องการใช้ใน root คือ http://www.example.com เฉย ๆ ควรดาวน์โหลดและติดตั้ง drupal เองที่ /var/www
    หรือแก้ไข /etc/apache2/site-enabled/default ให้ชี้มาที่ /usr/share/drupal5 อย่างใดอย่างหนึ่ง)

    หากต้องการ clean url
    1. เปิดใช้งานมอดูล rewrite
      # a2enmod rewrite
    2. แก้ htaccess
      # vi /etc/drupal/5/htaccess
      ...
      RewriteBase /content
      ...
    3. เริ่ม apache2 ใหม่
      # /etc/init.d/apache2 restart
    ถ้าทดลองหลายครั้ง คือมีการ install และ remove หลายครั้ง อาจทำให้หามอดูลใน apache2 ไม่พบ
    แก้ไขดังนี้
    # aptitude purge drupal5 apache2 libapache2-mod-php5 apache2.2-common php5-pgsql php5-gd
    # aptitude purge postgresql-8.3
    # rm -rf /etc/php5 /etc/apache2 /etc/postgresql /etc/phppgadmin
    # aptitude install postgresql-8.3
    # aptitude install drupal5 php5-pgsql

    สรุป
    ยังทำไม่ค่อยเป็นเท่าไหร่ ตอนเอาจริง ติดตั้งแบบดิบ ๆ จาก drupal สะดวกกว่า
    เที่ยวนี้บันทึกเอาไว้ดู ว่าติดตั้งจากเดเบียนทำยังไง

    Topic: 

    debian: Virtualization

    ทดลองเกี่ยวกับ Virtualization

    Topic: 

    ubuntu: feisty - qemu

    เดี๋ยวนี้ qemu พร้อม kqemu มีเป็นแบบแพกเกจเรียบร้อย การติดตั้งจึงต่างออกไปจากเดิม
    สำหรับใน Feisty การติดตั้งเป็นดังนี้

    ส่วนของ qemu
    ติดตั้ง qemu
    $ sudo aptitude install qemu

    ส่วนของ kqemu
    ติดตั้ง module-assistant
    $ sudo aptitude install module-assistant

    ติดตั้ง kqemu ผ่าน module-assistant
    $ sudo m-a a-i kqemu

    ใน Feisty ไม่ยอมสร้าง /dev/kqemu ให้ ต้องทำเอง
    $ sudo mknod /dev/kqemu c 250 0
    $ sudo chmod 666 /dev/kqemu
    $ sudo modprobe kqemu

    ส่วนของเน็ตเวิร์ก
    สร้างตาราง nat ไว้รอก่อน
    $ sudo iptables -t nat -D POSTROUTING -j MASQUERADE -o eth0
    $ sudo iptables -t nat -A POSTROUTING -j MASQUERADE -o eth0

    ติดตั้งแพกเก็จชื่อ uml-utilities
    $ sudo aptitude install uml-utilities

    เอาอินเทอร์เฟส tun ขึ้นมา
    $ sudo modprobe tun
    $ sudo chmod 666 /dev/net/tun

    qemu จะใช้งานเน็ตเวิร์กผ่านอินเทอร์เฟส tap0 เราจึงต้องสร้าง tap0 ขึ้นมา สมมุติว่าเราชื่อ user1
    $ sudo tunctl -u user1

    เสร็จแล้ว

    สมมุติว่าเรามีอิมเมจ winxp.img อยู่แล้ว สามารถเรียกใช้งาน qemu ด้วย
    $ qemu -kernel-kqemu -hda winxp.img -net nic -net tap,ifname=tap0

    บันทึกเกร็ดเพิ่มเติม

    • ทดสอบไปทดสอบมา ดูแล้ว qemu น่าใช้ที่สุด เนื่องจากการทำงานราบเรียบกว่า Xen, VirtualBox, kvm, VMWare แพ้แต่ Parallel เท่านั้น ถึงแม้จะรันช้ากว่า แต่มีข้อดีตรงที่เราสามารถปรับเปลี่ยนได้ตามที่เราต้องการ
    • ในขั้นตอนติดตั้งโปรแกรม ควรปิดการทำงานของ kqemu โดยถ้าเป็น Windows Xp ให้เอาออปชั่น -kernel-kqemu ออก และถ้าเป็น Windows 98-ME ให้เติมออปชั่น -no-kqemu
    • ถ้าเป็นซีพียูตระกูลอินเทล - VT จะได้ความเร็วที่ราบรื่น แม้ไม่ได้ใส่ออปชั่น -kernel-kqemu ก็ตาม
    • เวลาติดตั้งโปรแกรม ควรใช้ประโยชน์จากการที่ qemu สามารถทำ overlay ได้มีเทคนิกดังนี้
      1. สร้างอิมเมจหลักก่อน สมมุติว่าชื่อ winxp.qcow
        $ qemu-img create -f qcow winxp.qcow 5G
      2. ติดตั้งวินโดวส์ โดยใช้ hda เป็น winxp.qcow จนเสร็จเรียบร้อย
      3. สร้าง Overlay image เพื่อเก็บอิมเมจเก่าไว้
        $ qemu-img create -b winxp.qcow -f qcow winxp.ovl
      4. ติดตั้งโปรแกรม หรือทำการทดลองใด ๆ บน Overlay image ที่สร้างขึ้น
      5. ถ้าติดตั้งไม่ผ่าน หรือทดลองจนงานเละ ก็ให้ลบอิมเมจ winxp.ovl ทิ้งไป แล้วเริ่มขั้นตอนก่อนหน้านี้ใหม่
      6. ถ้าติดตั้งหรือทดลองจนเสร็จเรียบร้อยแล้ว และต้องการบันทึกงานเข้าไปรวมกับอิมเมจเก่า ให้ใช้คำสั่ง
        $qemu-img commit winxp.ovl
        ผลคือข้อมูลใน winxp.ovl จะถูกบันทึกลงใน winxp.qcow และ winxp.ovl จะถูกลดขนาดลงมา พร้อมที่จะรับงานใหม่ต่อไป
    • ทดสอบล่าสุดบนเดเบียน qemu รุ่น 0.8.2 ซีพียู Pentium E6300 พบว่า
      • บน WindowsXP สั่งด้วย qemu เฉย ๆ เร็วกว่าใช้พารามิเตอร์ -kernel-kqemu
      • บน WindowsMe สั่งด้วย qemu -no-kqemu เร็วและเสถียรกว่า qemu เฉย ๆ (การสั่งด้วย qemu เฉย ๆ จะเปิดใช้งาน kqemu ในโหมด user โดยอัตโนมัติ)
      • บน WindowsMe สั่งด้วย qemu-system-x86_64 เฉย ๆ เสถียรเท่ากับสั่งด้วย qemu -no-kqemu แต่ช้ากว่า

      สรุปว่าสำหรับ qemu ตั้งแต่รุ่น 0.8.2 เป็นต้นไป ต้องทดลองดูหลาย ๆ แบบ ดูว่าแบบไหนดีที่สุด ไม่จำเป็นต้องเป็นตามคู่มือเสมอไป

    หมายเหตุ

    • ผมอัปเกรดจาก dapper ด้วยคำสั่ง aptitude dist-upgrade จึงอาจมีสคริปต์เก่าหลงเหลืออยู่ ไม่แน่ใจว่าถ้าติดตั้งอูบุนตูจากแผ่นติดตั้งของ Feisty ล้วน ๆ ขั้นตอนการติดตั้ง qemu จะต่างออกไปจากนี้หรือไม่

    อ้างอิง

    debian: qemu เก็บตก

    ทดลองใช้ qemu กับ usb
    ในการสั่งรัน ต้องเติมพารามิเตอร์ -usbdevice host:XXXX:XXXX ต่อท้ายคำสั่ง
    ซึ่งค่านี้คือค่า Vender ID และ Product ID ตามลำดับ โดยหาได้จากคำสั่ง lsusb

    ตัวอย่างของเครื่องผมคือ
    ต้องการใช้งาน usb ใน qemu โดยมี Printer Epson Stylus Photo 830 และสแกนเนอร์ UMAX Astra 5650

    ดูค่าไอดี
    $ lsusb

    Bus 005 Device 001: ID 0000:0000  
    Bus 004 Device 001: ID 0000:0000  
    Bus 003 Device 002: ID 04b8:0005 Seiko Epson Corp. Stylus Printer
    Bus 003 Device 001: ID 0000:0000  
    Bus 002 Device 001: ID 0000:0000  
    Bus 001 Device 002: ID 0461:0392 Primax Electronics, Ltd 
    Bus 001 Device 001: ID 0000:0000  

    สั่งรัน qemu
    $ qemu YOUR-PARAMETER -usbdevice host:04b8:0005 -usbdevice host:0461:0392

    เสียง
    ใช้คำสั่ง
    $ qemu YOUR-PARAMETER -soundhw all

    Topic: 

    debian: บันทึกการปรุง qemu รุ่นปรับแก้ NumLock/CapsLock เพื่อแจกจ่าย

    รุ่นนี้เป็นรุ่นปรับแก้บั๊กเรื่อง NumLock/CapsLock ไม่ยอมเปลี่ยน ตามที่เคยบันทึกไว้

    แต่ผมลืมแพกเกจที่ต้องการก่อนติดตั้งหมดแล้ว ฝากดูเอาเองตอน debuild หรือ configure

    วิธีสร้างแพกเกจเดเบียน
    $ sudo aptitude install devscripts
    $ apt-get source qemu
    $ cd qemu-0.9.0+20070816
    $ vi sdl.c

    . . .
        if (ev->keysym.sym == SDLK_PAUSE) {
            /* specific case */
            v = 0;
            if (ev->type == SDL_KEYUP)
                v |= 0x80;
            kbd_put_keycode(0xe1);
            kbd_put_keycode(0x1d | v);
        //    kbd_put_keycode(0x45 | v);
            return;
        }
    
        if (kbd_layout) {
            keycode = sdl_keyevent_to_keycode_generic(ev);
        } else {
            keycode = sdl_keyevent_to_keycode(ev);
        }
    
        switch(keycode) {
        case 0x00:
            /* sent when leaving window: reset the modifiers state */
            reset_keys();
            return;
        case 0x2a:                          /* Left Shift */
        case 0x36:                          /* Right Shift */
        case 0x1d:                          /* Left CTRL */
        case 0x9d:                          /* Right CTRL */
        case 0x38:                          /* Left ALT */
        case 0xb8:                         /* Right ALT */
            if (ev->type == SDL_KEYUP)
                modifiers_state[keycode] = 0;
            else
                modifiers_state[keycode] = 1;
            break;
        //case 0x45: /* num lock */
        //case 0x3a: /* caps lock */
            /* SDL does not send the key up event, so we generate it */
        //    kbd_put_keycode(keycode);
        //    kbd_put_keycode(keycode | 0x80);
        //    return;
        }
    . . .

    $ debuild -us -uc
    จะได้แพกเกจเดเบียน ตามต้องการ

    ติดตั้งด้วยคำสั่ง
    $ sudo dpkg -i ../qemu_0.9.0+20070816-1_i386.deb

    วิธีสร้างแพกเกจแบบทั่วไป
    $ wget http://fabrice.bellard.free.fr/qemu/qemu-0.9.0.tar.gz
    $ tar xfz qemu-0.9.0.tar.gz
    $ cd qemu-0.9.0
    $ configure --cc=gcc-3.4
    $ make
    $ checkinstall -D make install
    $ make qemu-doc.html
    $ make qemu-tech.html
    $ make qemu.1
    $ make qemu-img.1
    $ sudo make install-doc
    $ make tarbin

    จะได้แพกเกจ qemu-0.9.0-i386.tar.gz ตามต้องการ โดยไฟล์จะมาอยู่ที่ไดเรกทอรี่ Home

    Download
    หากคร้านที่จะคอมไพล์เอง ดาวน์โหลดได้ที่นี่ครับ ฝากทดสอบบั๊กให้ด้วย

    อ้างอิง
    Murray's Blog: Building modified debian packages

    บันทึกการแก้ปัญหา Num Lock - Caps Lock ของ qemu

    เมื่อได้ลองใช้งาน qemu แบบจริงจัง เจอปัญหาว่า Num Lock กับ Caps Lock มีอาการแปลก ๆ กดแล้วได้ผลบ้าง ไม่ได้ผลบ้าง
    เลยลองถอด qemu ของดิสโตร แล้วดาวน์โหลด qemu รุ่นล่าสุดมาลองดูซอร์สโค๊ด
    $ sudo aptitude remove qemu
    $ wget http://fabrice.bellard.free.fr/qemu/qemu-0.9.0.tar.gz
    $ tar xfz qemu-0.9.0.tar.gz
    $ cd qemu-0.9.0

    ค้นข้อมูลในกูเกิลดู พบว่าต้นตออยู่ที่ไฟล์ sdl.c
    พบว่า qemu แก้ปัญหา sdl ไม่ยอมส่งรหัส Num Lock - Caps Lock ไปให้ระบบ ด้วยการเขียนโค๊ดส่งรหัสเอง
    แต่ปัจจุบันนี้ sdl น่าจะแก้ปัญหานี้แล้ว แต่โค๊ดส่วนนี้ยังคงอยู่ เลยทดลองเติมคอมเมนต์ในโค๊ดส่วนนี้ดู
    $ vi sdl.c

    ...
        //case 0x45: /* num lock */
        //case 0x3a: /* caps lock */
            /* SDL does not send the key up event, so we generate it */
        //    kbd_put_keycode(keycode);
        //    kbd_put_keycode(keycode | 0x80);
        //    return;
        ...
    

    แล้วจึงคอมไพล์ใหม่ ด้วย gcc-3.4 ซึ่งต้องติดตั้งใหม่
    $ sudo aptitude install gcc-3.4
    $ ./configure --cc=gcc-3.4
    $ make
    $ sudo make install

    ผลปรากฎว่า สามารถใช้คีย์ Num Lock และ Caps Lock ได้อย่างที่ควรจะเป็นครับ

    ทดลองกับเดเบียน Lenny และอูบุนตู Feisty ไลบรารี libsdl1.2-dev ทั้งคู่

    debian: ทดลอง XenOnLenny

    จะเอามาลองทดสอบ Windows XP
    ใช้ซีพียูอินเทล E6300 (เป็น VT อันดับเบื้องต้นที่สามารถวิ่ง Unmodified OS ได้) แรม 1G

    ติดตั้ง xen
    $ sudo aptitude install xen-hypervisor-3.0.3-1-i386 xen-utils-common

    ติดตั้ง Dom0
    $ sudo aptitude install xen-linux-system-2.6.18-4-xen-686
    บูตใหม่ด้วย Dom0

    เตรียมติดตั้ง DomU ที่เป็น Windows XP
    $ sudo aptitude install xen-ioemu-3.0.3-1

    สร้างไฟล์คอนฟิกสำหรับ Windows XP
    $ sudo vi /etc/xen/winxp.cfg

    ติดตั้ง DomU ไว้บนพาร์ติชั่นของฮาร์ดดิสก์จริง ๆ คือ /dev/hda2
    ติดตั้งเน็ตเวิร์กตามค่าปริยาย คือเป็นแบบบริดจ์
    เนื้อไฟล์เป็นดังนี้

    kernel = "/usr/lib/xen-3.0.3-1/boot/hvmloader"
    device_model ='/usr/lib/xen-3.0.3-1/bin/qemu-dm'
    builder='hvm'
    memory = 384
    name = "winxp"
    vcpus=1
    disk = ['phy:/dev/hda2,ioemu:hda,w','phy:/dev/hdb,hdb:cdrom,r']
    boot='d'
    vif = [ 'type=ioemu, bridge=xenbr0' ]
    vnc=1
    vncviewer=1
    serial='pty'
    ne2000=0

    ติดตั้ง VNC สำหรับดูหน้าจอ Windows XP
    $ sudo aptitude install xtightvncviewer

    เริ่มบูตเพื่อติดตั้ง DomU
    $ sudo xm create winxp.cfg

    เมื่อเคอร์เซอร์หลุดออกมาสู่ภาวะพร้อม ก็สั่งดูหน้าจอติดตั้ง Windows XP ด้วย xtightvncviewer
    $ xtightvncviewer localhost

    ... ติดตั้งตามปกติ ...
    หมายเหตุ

    • ถ้ามีการเปลี่ยนความละเอียดของหน้าจอบน Windows XP อาจต้องปิดหน้าต่างของ xtightvncviewer แล้วก็ใช้คำสั่ง $ xtightvncviewer localhost ใหม่
    • ถ้าติดตั้งเรียบร้อยแล้ว อย่าลืมเปลี่ยนพารามิเตอร์ boot ในไฟล์ winxp.cfg เป็น boot = 'c' ด้วย

    ผลการทดลอง

    • หลังจากใช้ qemu มานาน เลยมีความรู้สึกว่าเร็วจี๋ทีเดียว โดยเฉพาะเรื่องเน็ตเวิร์ก เร็วมาก ๆ
    • แป้นพิมพ์ตัวเลข ยังมีอาการพิมพ์ไม่ติดเป็นบางครั้ง
    • ตำแหน่งเมาส์ไม่ตรงกัน ระหว่าง Dom0 กับ DomU แก้โดยต้องลองปรับตำแหน่งเมาส์ด้วยการเคลื่อนเมาส์ให้พ้นกรอบของหน้าต่าง Viewer แล้วย้ายมาเริ่มใหม่ในด้านตรงข้าม (ต้องลองไปลองมา แล้วจะเริ่มขยับตรงเอง)
    • ความเร่งของเมาส์ไม่เท่ากัน ระหว่าง Dom0 กับ DomU แก้โดยปรับที่ Windows XP ให้ปิดการทำงานของการเร่งเมาส์
      Control Panel -> Mouse -> TAB-Pointer options -> ปิด Enhance pointer position
    • ยังมีปัญหา(ติดตั้งยังไม่เป็น)เรื่องเน็ตเวิร์กคือ ping จาก DomU ไป Dom0 ได้ แต่ในทางกลับกัน ping ไม่ได้ ยังไม่ทราบว่าต้องตั้งที่ไหน
      จะติดตั้งแบบ NAT เหมือน qemu ก็กลัวเสียความเร็ว เดี๋ยวมีโอกาสค่อยลองอีกทีนึง (แก้ที่ไฟล์ winxp.cfg ตำแหน่ง vif และไฟล์ /etc/xen/scripts/qemu-ifup)

    สรุป
    ถึงแม้จะยังไม่สมบูรณ์แบบ แต่ก็น่าใช้มาก โดยเฉพาะถ้าต้องการความเร็วในการใช้งาน การติดตั้งก็ไม่ยากเหมือนแต่ก่อน
    เสียดายที่ต้องใช้กับซีพียูที่มีโหมด Vitualize เท่านั้น (Intel-VT, AMD-Pacifica) และโอเอสก็ต้องเป็น 32bit ขึ้นไปแบบ Windows XP
    ถ้าใช้กับ Win98 ได้นี่จะประหยัดไลเซ่นซ์วินโดวส์เก่า ๆ ได้เยอะเลย

    อ้างอิง

    debian: บันทึก VirtualBox

    หลังจากทดลอง Xen แล้ว พบว่ายังมีปัญหาเกี่ยวกับคีย์บอร์ดที่ค่อนข้างเอาแน่เอานอนไม่ได้ และปัญหาความไม่คุ้นเคยในการใช้เมาส์ผ่าน VNC
    จึงได้มาทดลองใช้ VirtualBox
    ซีพียูอินเทล E6300 หน่วยความจำ 1G
    พบว่า

    • ถ้าใช้กับวินโดวส์ 9x ติดตั้งยาก มีปัญหากับ VGA และถ้าสามารถติดตั้งผ่านแล้ว ความเร็วสู้ qemu -no-kqemu ไม่ได้
    • ถ้าใช้กับวินโดวส์ Xp ความเร็วดีมาก สงสัยว่าจะเร็วกว่า Xen เสียอีก และเสถียรดีมาก ดีกว่า qemu สรุปว่าถ้าเป็น Windows Xp น่าใช้ VirtualBox มาก ๆ

    จึงบันทึกการปรับตั้งค่าไว้ดังนี้

    มีปัญหาว่าบางครั้ง แม้จะแก้ไขให้เราอยู่ในกลุ่ม vboxusers แล้วก็ตาม แต่บางครั้งก็ยังไม่สามารถรันได้ จึงแก้ไขให้ /dev/vboxdrv เป็นโหมด 666 ผ่าน udev
    $ sudo vi /etc/udev/rules.d/60-vboxdrv.rules
    แก้จาก KERNEL=="vboxdrv", NAME="vboxdrv", OWNER="root", GROUP="vboxusers", MODE="0660"
    แก้เป็น

    KERNEL=="vboxdrv", NAME="vboxdrv", OWNER="root", GROUP="vboxusers", MODE="0666"

    แก้ไขค่าเน็ตเวิร์ก tun ให้เป็น 666 เหมือนกัน
    $ sudo vi /etc/udev/rules.d/020_permissions.rules
    แก้เป็น

    ...
    KERNEL=="tun",          MODE="0666"
    ...

    สร้างสคริปต์สำหรับเน็ตเวิร์กแบบกำหนดเอง
    $ vi ~/ifup-tap0

    sudo iptables -t nat -D POSTROUTING -j MASQUERADE -o eth0
    sudo iptables -t nat -A POSTROUTING -j MASQUERADE -o eth0
    UNAME=$USER
    sudo tunctl -u $UNAME -t tap0
    sudo ifconfig tap0 192.168.4.254 up

    $ sudo chmod 755 ~/ifup-tap0

    ตั้งค่าให้ VirtualBox ใช้เน็ตเวิร์กแบบ Host Interface
    Setting -> Network -&gt Host Interface
    Interface name : tap0
    Setup application : ~/ifup-tap0

    สำหรับ Windows XP เขาให้หน่วยความจำปริยายมาเป็น 128M แต่เรากำหนดเป็น 384M

    ใน Windows XP ตั้งค่าให้ไอพีเป็น 192.168.1.4 และเกตเวย์เป็น 192.168.4.254

    การเมานต์ไฟล์ vdi

    ntfs
    $ sudo mount -o loop,offset=0x$(hd -h 100000 IMAGE.vdi | grep "eb 52 90 4e 54 46 53" | cut -c 1-8) IMAGE.vdi /MOUNT/POINT
    vfat
    $ sudo mount -o loop,offset=0x$(hd -h 100000 IMAGE.vdi | grep "eb 3C 90" | cut -c 1-8) IMAGE.vdi /MOUNT/POINT

    ที่มา: forums.virtualbox.org: Mounting .vdi file on host

    debian: ติดตั้ง OS X Guest ใน VirtualBox

    มีงานที่ต้องการทดสอบการแสดงผล ของ Safari/iBooks ใน iPhone/iPad (iOS) และ Mac (OS X) จึงต้องลง OS X เพื่อทดสอบ แอปที่จะใช้ คือ iBooks, iBooks Author และ Xcode ต้องการทรัพยากรเป็น Mavericks (10.9) แต่ Mavericks ไม่มีแผ่นขาย มีขายแต่ Snow Leopard (10.6.3) จึงต้องมีขั้นตอนเยอะหน่อย

    ระบบที่ใช้ทดสอบ

    host: Debian 3.12-1-amd64
    cpu: Intel(R) Core(TM) i7-3770 CPU @ 3.40GHz
    ram: 8G
    VirtualBox: Official 4.3

    ขั้นตอน

    • 1.ติดตั้ง Snow Leopard (10.6) เพื่อจะสามารถดาวน์โหลด Mavericks (10.9) ได้ และนำมาสร้างเป็นไฟล์ iso เพื่อติดตั้งต่อไป
    • 2.ติดตั้ง Mavericks

    ต้องเตรียม

    • ซื้อแผ่น Snow Leopard (10.6) ราคา 620 บาท บวกค่าส่งอีก 300 บาท (ต้องสมัคร/ใช้บัญชี Apple ID)
    • iBoot Ivy Bridge 1.1.0 สำหรับบูตเข้า Snow Leopard (ต้องลงทะเบียน) แตกไฟล์แล้วเก็บไฟล์ iBoot-Ivy-Bridge.iso ไว้ (ถ้าบูตไม่ผ่าน ลองดาวน์โหลด iBoot ตัวอื่นดู)
    • เนื้อที่ว่างของฮาร์ดดิสก์ ประมาณ 30-60 GB

    1.ติดตั้ง Snow Leopard

    เอา VirtualBox สร้าง Virtual Machine ใหม่ดังนี้

    Name: osx
    Type: Max OS X
    Version: Mac OS X (64 bit)
    Memory size: 2048
    Hard drive: virtual hard drive 30.00GB VDI (VirtualBox Disk Image) (ค่าปริยาย 20G ไม่พอ)
    Storage on physical hard drive: Dynamically allocated
    

    ตั้งค่าอื่น

    System: Motherboard: ยกเลิก Enable EFI (special OSes only)
    
    Display: Video: Enable 3D Acceleration
    
    Storage: Controller: SATA -> Empty เปลี่ยน Attributes: Choose a virtual CD/DVD disk file:
        เปลี่ยนเป็น iBoot-Ivy-Bridge.iso ที่เตรียมไว้
    

    ส่วนที่เหลือ ใช้ค่าปริยายทั้งหมด

    เสร็จแล้วก็บูตเข้า iBoot ต่อด้วย Snow Leopard เพื่อติดตั้ง

    ตอนบูตเข้า iBoot ให้ใส่แผ่น Snow Leopard ในซีดีรอม แล้วเปลี่ยน Devices เป็น Host Drive ATAPI แล้วกด F5 เพื่อรีเฟรช แล้วจึงบูตติดตึ้ง Snow Leopard

    Devices -> CD/DVD devices -> Host Drive ATAPI (sr0)
    กด F5
    บูตด้วย Mac OS X Install DVD
    

    ติดตั้งตามปกติ ระหว่างติดตั้งต้องมีการสร้างพาร์ติชั่นด้วย

    Utilities -> Disk Utility
    เลือก 32.21 GB VBOX HARDISK -> Partition -> + ->
        Name: osx
        Apply
    

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

    บูตเข้าระบบโดยต้องกลับไปใช้ iBoot-Ivy-Bridge อีกครั้ง แล้วเลือกเข้า osx ที่ติดตั้งเสร็จแล้ว
    ถ้าบูตไม่ขึ้น ให้ลองปิดและบูตใหม่

    หลังติดตั้งเสร็จเรียบร้อย ให้อัปเดตระบบ เพื่อให้มี App Store

    Apple -> Software Update
    

    อัปเดตเสร็จ รีบูตใหม่ ระบบจะกลายเป็น 10.6.8 และมี App Store เรียบร้อย

    ดาวน์โหลด Mavericks ผ่าน AppStore ขนาดประมาณ 5.3GB (5GiB)
    เมื่อเสร็จ ไฟล์ที่ได้จะอยู่ที่ /Applications/Install OS X Mavericks.app

    แปลงเป็น iso เพื่อจะนำไปติดตั้งในขั้นตอนต่อไป
    เข้า Terminal

    Applications -> Utilities -> Terminal

    แล้วพิมพ์คำสั่งดังนี้

    # Mount the installer image
    hdiutil attach /Applications/Install\ OS\ X\ Mavericks.app/Contents/SharedSupport/InstallESD.dmg -noverify -nobrowse -mountpoint /Volumes/install_app
    
    # Convert the boot image to a sparse bundle
    hdiutil convert /Volumes/install_app/BaseSystem.dmg -format UDSP -o /tmp/Mavericks
    
    # Increase the sparse bundle capacity to accommodate the packages
    hdiutil resize -size 8g /tmp/Mavericks.sparseimage
    
    # Mount the sparse bundle for package addition
    hdiutil attach /tmp/Mavericks.sparseimage -noverify -nobrowse -mountpoint /Volumes/install_build
    
    # Remove Package link and replace with actual files
    rm /Volumes/install_build/System/Installation/Packages
    cp -rp /Volumes/install_app/Packages /Volumes/install_build/System/Installation/
    
    # Unmount the installer image
    hdiutil detach /Volumes/install_app
    
    # Unmount the sparse bundle
    hdiutil detach /Volumes/install_build
    
    # Resize the partition in the sparse bundle to remove any free space
    hdiutil resize -size `hdiutil resize -limits /tmp/Mavericks.sparseimage | tail -n 1 | awk '{ print $1 }'`b /tmp/Mavericks.sparseimage
    
    # Convert the sparse bundle to ISO/CD master
    hdiutil convert /tmp/Mavericks.sparseimage -format UDTO -o /tmp/Mavericks
    
    # Remove the sparse bundle
    rm /tmp/Mavericks.sparseimage
    
    # Rename the ISO and move it to the desktop
    mv /tmp/Mavericks.cdr ~/Desktop/Mavericks.iso
    

    คัดลอก Mavericks.iso บน Desktop มาสู่เครื่องจริง (อาจผ่าน usb หรือเน็ตเวิร์ค - ผมใช้ผ่านเน็ตเวิร์ค) เพื่อจะนำไปติดตั้งในขั้นตอนต่อไป

    ปิดเครื่อง

    จบขั้นแรก

    หากต้องการทดสอบ Snow Leopard ต่อไปก็สามารถเก็บ Virtual Machine "osx" นี้ไว้ได้ แต่หากไม่ต้องการใช้แล้วก็ลบทิ้งได้เลย

    ที่มา

    2.ติดตั้ง Mavericks

    เอา VirtualBox สร้าง Virtual Machine ใหม่ดังนี้

    Name: osx64
    Type: Max OS X
    Version: Mac OS X (64 bit)
    Memory size: 2048
    Hard drive: virtual hard drive 30.00GB VDI (VirtualBox Disk Image) (ถ้าต้องใช้งานจริงจัง ควรเป็น 40G ขึ้นไป)
    Storage on physical hard drive: Dynamically allocated
    

    คราวนี้ไม่ต้องใช้ iBoot แล้ว โดยจะใช้ Mavericks.iso ที่ได้จากขั้นตอนก่อนหน้ามาทำเป็น virtual CD/DVD disk file
    สำหรับบูตเพื่อติดตั้ง และไม่ต้องยกเลิก EFI แล้ว

    ตั้งค่าอื่น

    System: Processor: 2 CPU
    
    Display: Video:
        Video Memory: 32 MB (ใช้ค่าอื่นแล้วเปิด System Information ไม่ได้)
        Enable 3D Acceleration
    
    Storage: Controller: SATA -> Empty เปลี่ยน Attributes: Choose a virtual CD/DVD disk file:
        เปลี่ยนเป็น Mavericks.iso ที่เตรียมไว้
    

    ก่อนจะบูต ให้บอก VirtualBox ว่าจะติดตั้งเป็น 64 บิต และเลือกหน้าจอเป็นขนาด 1440x900
    เข้า Terminal ของลินุกซ์ แล้วพิมพ์ดังนี้

    VM=osx64
    N=4    #N can be one of 0,1,2,3,4,5 referring to the 640x480, 800x600, 1024x768, 1280x1024, 1440x900, 1920x1200 screen resolution respectively (5 may not worked).
    
    VBoxManage modifyvm "$VM" --firmware efi64
    VBoxManage setextradata "$VM" VBoxInternal2/EfiGopMode $N
    VBoxManage setextradata "$VM" VBoxInternal2/SmcDeviceKey "ourhardworkbythesewordsguardedpleasedontsteal(c)AppleComputerInc"
    

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

    วิธีติดตั้งเหมือนเดิม คือต้องพาร์ติชั่นก่อน แล้วติดตั้งต่อจนจบ

    ตอนบูตครั้งแรก อย่าลืมเปลี่ยน virtual CD/DVD จาก Mavericks กลับเป็น Host Drive ATAPI (sr0)

    จบแล้ว

    ที่มา

    รายงานผล

    • Snow Leopard: เปิด About this Mac ไม่ได้
    • Mavericks: การรันไม่ค่อยลื่นเท่าไหร่ แต่ก็พอใช้งาน test/debug Safari/iBooks ตามจุดประสงค์หลักได้
    • ใช้ปุ่ม Alt ขวามือ แทนปุ่ม Command ใน Mac
    • Mouse wheel หมุนกลับทางกับของลินุกซ์/วินโดวส์

    debian: macOS guest on VirtualBox - create installation media short note.

    SYSTEM
    OS: Debian Linux 4.18.0-3-amd64
    CPU: AMD Ryzen 7 1700 Eight-Core Processor
    VirtualBox: virtualbox-5.2 : 5.2.20-125813~Ubuntu~bionic

    STEP
    1. CREATE VM
    2. MODIFY VM
    3. BOOT INSTALLER
    4. BOOT NEW OS
    5. CREATE NEXT VERSION CDR INSTALLER
    6. FIX VirtualBox NOT SUPPORT apfs IN High Sierra/Mojave

    1. CREATE VM
    General
    Machine -> New
    Name="NAME OF VM"
    Type=Mac OS X
    Version=Mac OS X (64-bit)

    Storage
    HDD=NEW VDI DISK IMAGE (20-60G)
    CD=CDR INSTALLER

    2. MODIFY VM
    RUN COMMANDS:

    VM_RES="1280x960"	#EXAMPLE
    NAME="NAME OF VM"	#EXAMPLE
    
    VBoxManage modifyvm "$NAME" --usbxhci on --firmware efi --chipset ich9 --mouse usbtablet --keyboard usb --vram 128 --cpus 2 --memory 4096 --boot1 dvd --boot2 disk --boot3 none --boot4 none --cpu-profile "Intel Core i7-6700K" --cpuidset 00000001 000106e5 00100800 0098e3fd bfebfbff
    VBoxManage setextradata "$NAME" "CustomVideoMode1" "${VM_RES}x32"
    VBoxManage setextradata "$NAME" VBoxInternal2/EfiGraphicsResolution "$VM_RES"
    VBoxManage setextradata "$NAME" "VBoxInternal/Devices/efi/0/Config/DmiSystemProduct" "iMac11,3"
    VBoxManage setextradata "$NAME" "VBoxInternal/Devices/efi/0/Config/DmiSystemVersion" "1.0"
    VBoxManage setextradata "$NAME" "VBoxInternal/Devices/efi/0/Config/DmiBoardProduct" "Iloveapple"
    VBoxManage setextradata "$NAME" "VBoxInternal/Devices/smc/0/Config/DeviceKey" "ourhardworkbythesewordsguardedpleasedontsteal(c)AppleComputerInc"
    VBoxManage setextradata "$NAME" "VBoxInternal/Devices/smc/0/Config/GetKeyFromRealSMC" 1
    
    #FOR Snow Leopard: NUMBER OF CPU=1
    VBoxManage modifyvm "$NAME" --usbxhci on --firmware efi --chipset ich9 --mouse usbtablet --keyboard usb --vram 128 --cpus 1 --memory 4096 --boot1 dvd --boot2 disk --boot3 none --boot4 none --cpu-profile "Intel Core i7-6700K" --cpuidset 00000001 000106e5 00100800 0098e3fd bfebfbff
    
    #FOR Sierra: SOLVE PARAVIRTUALIZATION BUG
    #https://gist.github.com/arobb/447a962af4f07ef81e79987d686275e5
    #https://www.insanelymac.com/forum/topic/292170-how-to-spoof-real-mac-in-vmware/
    VBoxManage setextradata "$NAME" "VBoxInternal/Devices/efi/0/Config/DmiBoardProduct" "Mac-F22589C8"
    VBoxManage setextradata "$NAME" "VBoxInternal/Devices/efi/0/Config/DmiSystemSerial" "CK1156I6DB6"
    VBoxManage modifyvm "$NAME" --paravirtprovider none
    #REVERT BACK WHEN FINISH INSTALLATION
    VBoxManage modifyvm "$NAME" --paravirtprovider default
    
    ##SOME USEFUL SETTINGS, DO NOT RUN
    ##BOOT SINGLE USER
    #VBoxManage setextradata "$NAME" VBoxInternal2/EfiBootArgs "-s"
    ##REVERT
    #VBoxManage setextradata "$NAME" VBoxInternal2/EfiBootArgs ""
    ##TURN OFF
    #VBoxManage setextradata "$NAME" VBoxInternal2/EfiBootArgs " "
    ##VERBOSE
    #VBoxManage setextradata "$NAME" VBoxInternal2/EfiBootArgs "-v"
    ##NO AMD, NO NVIDIA
    #VBoxManage setextradata "$NAME" VBoxInternal2/EfiBootArgs "-s agc=0 -amd_no_dgpu_accel nv_disable=1"
    ##DEBUG
    #VBoxManage setextradata "$NAME" VBoxInternal2/EfiBootArgs "-v debug=0x144"
    
    #IF MOUSE OR KEYBOARD DO NOT WORK, TRY TO ADD NEW USB FILTERS
    

    3. BOOT INSTALLER
    #STEP
    #- ERASE DISK
    #- INSTALL
    #FOR Mavericks NETWORK MAY NOT WORK, JUST INSTALL LOCALLY, WILL WORK AFTER REBOOT.

    4. BOOT NEW OS

    5. CREATE NEXT VERSION CDR INSTALLER
    #GET PACKAGES FROM SAFARI, DO NOT INSTALL:

    #LION
    https://itunes.apple.com/app/id444303913?mt=12

    #MOUNTAIN LION
    https://itunes.apple.com/app/id537386512?mt=12

    #MAVERICKS
    https://itunes.apple.com/app/id675248567?mt=12

    #YOSEMITE
    https://itunes.apple.com/app/id444303913?mt=12

    #EL CAPITAN
    https://itunes.apple.com/app/id1147835434?mt=12

    #SIERRA
    https://itunes.apple.com/app/id1127487414?mt=12

    #HIGH SIERRA
    https://itunes.apple.com/app/id1246284741?mt=12

    #MOJAVE
    https://itunes.apple.com/app/id1398502828?mt=12
    #IF FAIL, DOWNLOAD FROM HERE
    #http://osxdaily.com/2018/09/29/download-full-macos-mojave-installer/
    #http://dosdude1.com/mojave/
    #AFTER FINISH INSTALLATION, RE-DOWNLOAD FROM APPLE AGAIN, THEN RE-CREATE INSTALLER FROM MOJAVE

    #SOME LINK ARE DEAD, PLEASE DOWNLOAD FROM "iTunes -> Purchased"

    #START BUILD
    FILE="ElCapitan"	#EXAMPLE
    APPDIR="/Applications/Install OS X El Capitan.app"	#EXAMPLE
    
    SIZE=8000
    
    #CREATE IMAGE AND MOUNT
    hdiutil create -o /tmp/$FILE -size ${SIZE}m -layout SPUD -fs HFS+J  # -> /tmp/$FILE.dmg
    hdiutil attach /tmp/$FILE.dmg -noverify -nobrowse 	# -> /Volumes/untitled
    
    #***** asr METHOD ***** (<=Yosemite, El Capitan(CDR), Sierra)
    #MOUNT InstallESD
    hdiutil attach "$APPDIR/Contents/SharedSupport/InstallESD.dmg"  # -> /Volumes/OS X Install ESD
    ESD="/Volumes/OS X Install ESD"
    
    #FOR <= Yosemite, Sierra:
    #RESTORE BaseSystem TO IMAGE
    asr restore -source "$ESD/BaseSystem.dmg" -target /Volumes/untitled -noprompt -noverify -erase    #/Volumes/untitled -> /Volumes/OS X Base System
    
    #COPY PACKAGES
    rm "/Volumes/OS X Base System/System/Installation/Packages"
    cp -av "$ESD/Packages" "/Volumes/OS X Base System/System/Installation/"
    
    
    #FOR >= Yosemite:
    cp -av "$ESD/BaseSystem.dmg" "/Volumes/OS X Base System/"
    cp -av "$ESD/BaseSystem.chunklist" "/Volumes/OS X Base System/"
    
    #FOR >= El Capitan(CDR):
    cp -av "$ESD/AppleDiagnostics.dmg" "/Volumes/OS X Base System/"
    cp -av "$ESD/AppleDiagnostics.chunklist" "/Volumes/OS X Base System/"
    
    #UNMOUNT ALL
    hdiutil detach "/Volumes/OS X Base System"
    hdiutil detach "$ESD"
    
    #***** createinstallmedia METHOD ***** (El Capitan(VDI), High Sierra, Mojave)
    #CREATE INSTALLER TO IMAGE
    #FOR El Capitan(VDI) (USE VDI INSTEAD OF CDR - NEED WRITABLE MEDIA WHEN INSTALL), >= High Sierra:
    sudo "$APPDIR/Contents/Resources/createinstallmedia" --volume /Volumes/untitled/ --applicationpath "$APPDIR" --nointeraction    #/Volumes/untitles -> /Volumes/Install OS X XXX
    
    #UNMOUNT BASE
    hdiutil detach "/Volumes/Install XXX"
    
    #***** END 2 METHOD *****
    
    #RESIZE IMAGE FOR BEST FIT
    hdiutil resize -size `hdiutil resize -limits /tmp/$FILE.dmg | tail -n 1 | awk '{ print $1 }'`b /tmp/$FILE.dmg
    
    #CONVERT IMAGE TO CDR
    hdiutil convert /tmp/$FILE.dmg -format UDTO -o /tmp/$FILE  # -> /tmp/$FILE.cdr
    
    #COPY TO HOST
    scp /tmp/$FILE.cdr USER1@HOST1:/WHERE/TO/STORE/IMAGE

    6. FIX VirtualBox NOT SUPPORT apfs IN High Sierra/Mojave

    #FOR Mojave FIRST BOOT 
    fs1:
    cd macOS\ Install\ Data
    cd Locked\ Files
    cd Boot\ Files
    boot.efi
    
    #PREPARE apfs DRIVER FOR NEXT BOOT
    #https://github.com/acidanthera/AppleSupportPkg
    #AT Debian HOST
    mkdir /tmp/tmp
    cd /tmp/tmp
    wget https://github.com/acidanthera/AppleSupportPkg/releases/download/2.0.5/AppleSupport-v2.0.5-RELEASE.zip
    unzip AppleSupport-v2.0.5-RELEASE.zip
    cd Drivers/
    genisoimage -o ../driver.iso .
    
    #ATTACH /tmp/tmp/driver.iso TO Mojave GUEST OPTICAL DRIVE STORAGE
    #BOOT Mojave GUEST
    fs1:
    load APFSDRIV.EFI
    load USBKBDXE.EFI
    load APPLEUIS.EFI
    map -r
    fs1:
    cd System\Library\CoreServices
    boot.efi
    #IF FAILED, TRY TO LOAD JUST APFSDRIV.EFI
    
    #AFTER FINISH INSTALLATION, CREATE startup.nsh FOR NEXT BOOT
    #https://forums.virtualbox.org/viewtopic.php?f=22&t=88258&sid=20c97a3fb77e1578f58f3b1fd74460eb&start=15
    #https://github.com/img2tab/okiomov/blob/master/macos_okiomov.sh
    #AT Mojave GUEST
    diskutil list
    sudo mkdir /Volumes/efi
    sudo mount -t msdos /dev/disk1s1 /Volumes/efi
    mkdir -p /Volumes/efi/EFI/DRIVERS
    cp -av /Volumes/CDROM/* /Volumes/efi/EFI/DRIVERS/
    ls /Volumes/efi/EFI/DRIVERS/ >> /Volumes/efi/startup.nsh
    vi /Volumes/efi/startup.nsh
    
    load fs0:\EFI\DRIVERS\APFSDRIV.EFI
    load fs0:\EFI\DRIVERS\USBKBDXE.EFI
    load fs0:\EFI\DRIVERS\APPLEUIS.EFI
    map -r
    for %a run (1 5)
      fs%a:
      cd "macOS Install Data\Locked Files\Boot Files"
      boot.efi
      cd "System\Library\CoreServices"
      boot.efi
    endfor
    
    #SHUTDOWN GUEST AND DETACH driver.iso
    #IF FAILED, TRY TO LOAD JUST APFSDRIV.EFI
    

    ##### SOME USEFUL LINK/COMMANDS #####

    #IF BOOT FAILED, TRY TO BOOT...
    #FROM: fs1:>macOS Install Data\Locked Files\Boot Files\boot.efi
    #OR: fs1:>System\Library\CoreServices\boot.efi

    #CREATE BOOTABLE INSTALLER
    #https://support.apple.com/th-th/HT201372

    #SOLVE "Install macOS High Sierra.app does not appear to be a valid OS installer application."
    #https://www.reddit.com/r/MacOS/comments/7470rb/install_macos_high_sierraapp_does_not_appear_to/

    sudo cp -av /macOS Install Data /Applications/Install macOS High Sierra.app/Contents/SharedSupport
    

    #CREATE High Sierra ISO INSTALLER
    #https://www.howtogeek.com/289594/how-to-install-macos-sierra-in-virtualbox-on-windows-10/

    #REF
    #https://github.com/rtrouton/create_macos_vm_install_dmg

    #Mojave
    #https://astr0baby.wordpress.com/2018/09/25/running-macos-mojave-10-14-on-virtualbox-5-2-18-on-linux-x86_64/
    MOJAVE

    debian: ทดลองติดตั้ง OpenVZ บน lenny

    OpenVZ เป็นซอฟต์แวร์ที่ใช้ทำ virtualization ที่ใช้กับลินุกซ์เท่านั้น
    มีข้อแตกต่างจาก xen ตรงที่ใช้เพียงเคอร์เนลเดียว

    ข้อดีคือ เร็วและพร้อมใช้
    ข้อเสียคือ ไม่มีการ swap หน่วยความจำ ถ้าหน่วยความจำเต็ม โปรเซสที่มาทีหลังจะตายหมด
    (แต่ก็ยังมีข้อดีในข้อเสีย คือ openvz จะบริหารการสลับหน่วยความจำระหว่าง guest os อย่างมีประสิทธิภาพ และโปรเซสที่รันอยู่ก่อนที่หน่วยความจำจะเต็ม จะยังคงได้ความเร็วที่เกือบคงที่)

    กำหนด
    cpu: Intel E2140
    mb: ECS P4M890T-M V2.0
    ram: 4GB
    debian: Lenny
    host: server1.example.com, 192.168.1.31, 2.6.26-2-openvz-686
    guest-101: test.example.com, 192.168.1.131
    vz-partition: /dev/sda9

    ติดตั้ง
    เริ่มบน lenny

    # aptitude install linux-image-2.6.26-2-openvz-686 vzctl vzquota

    แก้ไขระบบตามคู่มือ

    # vi /etc/sysctl.conf
    ...
    #openvz
    net.ipv4.conf.all.rp_filter=1
    net.ipv4.icmp_echo_ignore_broadcasts=1
    net.ipv4.conf.default.forwarding=1
    net.ipv4.conf.default.proxy_arp = 0
    net.ipv4.ip_forward=1
    kernel.sysrq = 1
    net.ipv4.conf.default.send_redirects = 1
    net.ipv4.conf.all.send_redirects = 0
    net.ipv4.conf.eth0.proxy_arp=1
    ...
    

    สั่งปรับให้มีผลทันที

    # sysctl -p

    แก้ระบบเน็ตเวิร์คสำหรับ guest ตามคำแนะนำ

    # vi /etc/vz/vz.conf
    ...
    # Controls which interfaces to send ARP requests and modify APR tables on.
    #NEIGHBOUR_DEVS=detect
    NEIGHBOUR_DEVS=all
    ...

    บูตใหม่

    # shutdown -r now

    ตรวจผลหลังการบูต

    # uname -r
    2.6.26-2-openvz-686

    สมมุติว่าจะติดตั้งระบบ openvz container ไว้ที่ /dev/sda9 ที่ฟอร์แมตเป็น ext3 ไว้เรียบร้อยแล้ว
    สั่งหยุดเพิ่อปรับระบบ

    # /etc/init.d/vz stop

    แก้ไข fstab เพื่อให้เมานต์ที่ /var/lib/vz อัตโนมัติ

    # vi /etc/fstab
    ...
    /dev/sda9       /var/lib/vz     ext3    defaults        0       2
    ...

    ย้ายไฟล์ที่ติดตั้งแล้วมาที่ /dev/sda9 แล้วเริ่ม openvz ใหม่

    # mkdir /mnt/tmp
    # mount /dev/sda9 /mnt/tmp
    # mv /var/lib/vz/* /mnt/tmp
    # umount /mnt/tmp
    # mount -a
    # /etc/init.d/vz start

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

    # cd /etc/vz/conf

    สร้างตัวอย่างไฟล์คอนฟิก โดยสมมุติว่าเราจะแบ่งให้เซิร์ฟเวอร์เครื่องนี้มี 4 guest os โดยให้ชื่อไอดี (ของอันแรก) ว่า 101

    # vzsplit -n 4 > 101.conf

    ปรับแต่งค่าอื่น ๆ ของ guest ตามการกำหนดข้างต้น

    # vzctl set 101 --onboot yes --save
    # vzctl set 101 --hostname test.example.com --save
    # vzctl set 101 --ipadd 192.168.1.131 --save
    # vzctl set 101 --nameserver ns1.example.com --save

    ติดตั้ง guest os โดยไปดาวน์โหลด template มาจาก debian.systs.org

    # cd /var/lib/vz/template/cache/
    # wget http://debian.systs.org/ostemplates/debian-5.0-i386-minimal.tar.gz

    ติดตั้ง

    # vzctl create 101 --ostemplate debian-5.0-i386-minimal

    หากต้องการคัดลอก apt source-list ก็สามารถคัดลอกจาก host ไปใช้ได้

    # cp /etc/apt/sources.list /var/lib/vz/private/101/etc/apt

    เริ่มระบบ

    # vzctl start 101 

    ตั้งรหัสผ่านของ guest ให้ root

    # vzctl exec 101 passwd

    ใช้งานผ่าน vzctl

    # vzctl enter 102

    (ออกด้วย exit)

    หรือใช้งานผ่าน ssh

    # ssh root@test.example.com

    ติดตั้งส่วนของ guest ที่ยังเหลืออยู่

    # aptitude update
    # aptitude install console-data locales
    # dpkg-reconfigure locales
    # aptitude upgrade

    เสร็จแล้วครับ

    ที่มา

    debian: บันทึกติดตั้ง WebVZ บน lenny

    WebVZ เป็นโปรแกรมจัดการ OpenVZ แบบ web based
    ตอนนี้ยังทำอะไรไม่ได้มาก แต่ติดตั้งไว้ก็ดี เผื่อทำงานอะไรเล็ก ๆ น้อย ๆ

    สมมุติว่าเครื่องหลักมีไอพี 192.168.1.31

    # aptitude install ruby rubygems libsqlite3-ruby sqlite3 rails
    # gem install -v=2.1.0 rails    #รอ 1 เพลิน
    # ln -sf /var/lib/vz /
    # cd /usr/local
    # wget http://downloads.sourceforge.net/project/webvz/webvz/webvz%202.0/webvz.2.0.tar.gz
    # tar xfz webvz.2.0.tar.gz 
    # ln -sf webvz.2.0 webvz
    # cd webvz
    # ruby script/server -b 192.168.1.31

    เครื่องอื่น ๆ ในวง สามารถใช้งานผ่าน url: http://192.168.1.31:3000
    user: admin
    pass: admin123

    เสร็จแล้ว

    ที่มา

    webvz: ทำ container ใช้เอง

    บันทึกการทำ container ใช้เอง ด้วย debootstrap

    # aptitude install debootstrap
    # cd /var/lib/vz/private
    # mkdir 1001
    # debootstrap --arch=amd64 lenny 1001 http://server1.example.com:9999/debian
    # cp /etc/apt/sources.list 1001/etc/apt/
    # chroot 1001
    # aptitude install console-data locales ssh screen less vim
    # dpkg-reconfigure locales
    # passwd
    # aptitude update
    # aptitude upgrade
    # vi /etc/inittab
    ...
    ตัดทิ้ง--------8<---------------------
    # Note that on most Debian systems tty7 is used by the X Window System,
    # so if you want to add more getty's go ahead but skip tty7 if you run X.
    #
    1:2345:respawn:/sbin/getty 38400 tty1
    2:23:respawn:/sbin/getty 38400 tty2
    3:23:respawn:/sbin/getty 38400 tty3
    4:23:respawn:/sbin/getty 38400 tty4
    5:23:respawn:/sbin/getty 38400 tty5
    6:23:respawn:/sbin/getty 38400 tty6
    จบตัดทิ้ง--------->8--------------------
    ...
    

    (อาจติดตั้งแพกเกจ หรือปรับแต่งค่าอื่น ๆ ที่จำเป็นเพิ่มได้)

    # exit

    เก็บไว้ใช้ในโอกาสต่อไป

    # cd 1001
    # tar cfz ../debian.tar.gz *

    เที่ยวหลังก็สามารถแตกไฟล์ออกมาใช้งานได้ เช่น

    # mkdir 1002
    # cd 1002
    # tar xfz ../debian.tar.gz

    เป็นต้น

    debian: ทดลองติดตั้ง Xen บน lenny

    Xen เป็นซอฟต์แวร์ที่ใช้ทำ virtualization ในทางทฤษฎีสามารถรัน guest os ใด ๆ ก็ได้ โดยมีข้อแม้คือ ซีพียู ต้องรองรับการทำ virtualization ด้วย แต่ถ้าซีพียูไม่รองรับ ต้องใช้กับ os ที่ดัดแปลงให้ใช้กับ xen เท่านั้น

    (คราวก่อนติดตั้งเพื่อทดลอง windows แต่เที่ยวนี้จะทำ host)

    ข้อดีคือ เร็วพอควร และสามารถ swap หน่วยความจำได้ ทำให้ใช้งานได้เหมือนในระบบลินุกส์ปกติ
    ข้อเสียคือ เนื่องจากแยกเคอร์เนลและฮาร์ดแวร์กันอย่างเด็ดขาด จึงทำให้ต้องการฮาร์ดแวร์มากกว่าปกติ และหากหน่วยความจำที่แบ่งให้แต่ละ guest os ไม่พอ xen จะทำการสลับหน่วยความจำลงดิสก์ ทำให้ระบบทำงานช้ามาก (ซึ่งจะมีผลกับทุก ๆ โปรเซสที่ยังค้างอยู่ในหน่วยความจำ คือช้าทั้งระบบ)

    กำหนด
    cpu: Intel E2140
    mb: ECS P4M890T-M V2.0
    ram: 4GB
    host: server1.example.com, 192.168.1.31
    guest-101: test.example.com, 192.168.1.131, bridged
    xen-physical-partition: /dev/sda10,xvda
    apt-proxy: 192.168.1.3:9999

    ติดตั้ง Xen - dom0
    เริ่มติดตั้งบน lenny

    # aptitude install xen-hypervisor-3.2-1-i386 xen-linux-system-2.6.26-2-xen-686 xen-utils-3.2-1 xenstore-utils xen-shell

    จะใช้เน็ตเวิร์กแบบ bridge

    # vi /etc/xen/xend-config.sxp
    ...
    # (network-script network-dummy)
    (network-script network-bridge)
    ...
    

    บูตใหม่

    # shutdown -r now

    ตรวจผลหลังการบูต

    # uname -r
    2.6.26-2-xen-686

    ติดตั้ง domU
    สมมุติว่าจะติดตั้ง xen-domU ไว้ที่ฮาร์ดดิสก์จริง /dev/sda10 ที่แบ่งพาร์ติชั่นเป็น ext3 ไว้เรียบร้อยแล้ว โดยจะเมานต์ /dev/sda10 เป็น /dev/xvda ใน xen และแบ่งพาร์ติชั่นต่าง ๆ ของ domU ใน /dev/xvda

    # xm block-attach 0 phy:/dev/sda10 xvda w

    ใช้ fdisk แบ่งพาร์ติชั่นตามใจชอบ

    # fdisk /dev/xvda

    ในที่นี้สมมุติว่า /dev/xvda5 เมานต์เป็น /
    และ /dev/xvda6 ใช้เป็น swap

    ฟอร์แมต ext3 และ swap ตามลำดับ

    # mkfs.ext3 /dev/xvda5
    # mkswap /dev/xvda6

    ใช้วิธีติดตั้งแบบ debootstrap เนื่องจากเร็วกว่าวิธีอื่น
    ติดตั้ง debootstrap

    # aptitude install debootstrap

    ติดตั้ง lenny บน /dev/xvda5 ผ่าน apt-proxy 192.168.1.3:9999
    และเนื่องจากซีพียูไม่รองรับการทำ virtualize จึงต้องติดตั้งเคอร์เนลที่แปลงมาใช้กับ xen แล้ว

    # mkdir /mnt/tmp
    # mount /dev/xvda5 /mnt/tmp
    # debootstrap lenny /mnt/tmp http://192.168.1.3:9999/debian
    # mount -t proc none /mnt/tmp/proc
    # mount -o bind /dev /mnt/tmp/dev
    # mount -t sysfs none /mnt/tmp/sys
    # cp /etc/apt/sources.list /mnt/tmp/etc/
    # ehco 'test.example.com' > /mnt/tmp/etc/hostname
    # chroot /mnt/tmp
    test# vi /etc/network/interface
    # The loopback network interface
    auto lo
    iface lo inet loopback
    
    # The primary network interface
    allow-hotplug eth0
    iface eth0 inet static
            address 192.168.1.131
            netmask 255.255.255.0
            network 192.168.1.0
            broadcast 192.168.1.255
            gateway 192.168.1.3
            # dns-* options are implemented by the resolvconf package, if installed
            dns-nameservers 192.168.1.3
            dns-search example.com
    
    test# vi /etc/fstab
    ...
    #                
    proc            /proc           proc    defaults        0       0
    /dev/xvda5      /               ext3    errors=remount-ro 0       1
    /dev/xvda6      none            swap    sw              0       0
    
    test# aptitude update
    test# aptitude install console-data locales ssh
    test# dpkg-reconfigure locales
    test# aptitude upgrade
    test# aptitude install linux-image-2.6.26-2-xen-686
    test# passwd
    test# exit
    # umount /mnt/tmp/sys
    # umount /mnt/tmp/dev
    # umount /mnt/tmp/proc
    # umount /mnt/tmp
    # xm block-detach 0 xvda

    สร้างไฟล์คอนฟิก

    # vi /etc/xen/101.cfg
    #
    #  Kernel + memory size
    #
    kernel      = '/boot/vmlinuz-2.6.26-2-xen-686'
    ramdisk     = '/boot/initrd.img-2.6.26-2-xen-686'
    memory      = '1024'
    #
    #  Disk device(s).
    #
    root        = '/dev/xvda5 ro'
    disk        = [ 'phy:sda10,xvda,w' ]
    #
    #  Hostname
    #
    name        = 'test.example.com'
    #
    #  Networking
    #
    vif         = [ 'bridge=eth0' ]
    #
    #  Behaviour
    #
    on_poweroff = 'destroy'
    on_reboot   = 'restart'
    on_crash    = 'restart'
    

    เริ่ม domU - 101.cfg

    # xm create 101.cfg

    ตรวจสถานะ

    # xm list

    เข้าใช้งาน domU ด้วย ssh

    # ssh root@test.example.com

    เสร็จแล้วครับ

    ที่มา

    ubuntu: ลอง XenOnEdgy

    cpu: Intel E6300
    mb: Asrock Conroe945G-DVI
    ram: 1G

    เที่ยวนี้ลองคอมไพล์ซอร์สเอง ไม่ผ่านซักอย่าง เลยใช้แบบไบนารีดีกว่า
    ทำตาม wiki.ubuntu.com/XenOnEdgy เกือบทั้งหมด

    ติดตั้ง Dom0

    เอาแพกเกจที่เกี่ยวข้องมาก่อน
    $ sudo apt-get install xen-hypervisor-3.0-i386 xen-image-xen0-2.6.17-6-generic-xen0 xen-utils-3.0
    +++ xen-hypervisor-3.0-i386* xen-image-xen0-2.6.17-6-generic-xen0*
    xen-ioemu-3.0* xen-utils-3.0*

    สร้างแรมดิสก์สำหรับบูตไว้ที่ /boot
    $ sudo mkinitramfs -o /boot/xen0-linux-2.6.17-6-generic-xen0.initrd.img 2.6.17-6-generic-xen0

    แก้ไขเมนูบูตให้ Xen อยู่อันแรก
    $ sudo vi /boot/grub/menu.lst
    ของเดิม /boot อยู่ที่ /dev/hda5 และ / อยู่ที่ /dev/hda7

    ...
    title XEN/2.6.17
    root (hd0,4)
    kernel /boot/xen-3.0-i386.gz
    module /boot/xen0-linux-2.6.17-6-generic-xen0 root=/dev/hda7 ro
    module /boot/xen0-linux-2.6.17-6-generic-xen0.initrd.img
    ...

    เน็ตเวิร์กยังไม่ได้ลอง เดี๋ยวจะกลับมาลอง

    ติดตั้ง WinXP เป็น DomU

    ในตัวอย่างของ wiki เขาใช้ลินุกส์เป็น DomU แล้วใช้ disk image เป็นไดร์ฟ
    แต่ในที่นี้จะติดตั้ง WinXP โดยแบ่งพาร์ติชั่นจริง ๆ เป็นไดร์ฟ

    สร้างไฟล์คอนฟิก ตั้งชื่อว่า winxp.cfg
    $ sudo vi /etc/xen/winxp.cfg
    พาร์ติชั่นที่ใช้ ผมแยกไปใช้ SATA คือ /dev/sda1

    kernel = "/usr/lib/xen-ioemu-3.0/boot/hvmloader"
    builder='hvm'
    memory = 384
    name = "winxp"
    vcpus=1
    vif = [ 'type=ioemu, bridge=xenbr0' ]
    disk = ['phy:/dev/sda1,ioemu:hda,w','phy:/dev/hdb,hdb:cdrom,r']
    device_model ='/usr/lib/xen-ioemu-3.0/bin/qemu-dm'
    boot='d'
    vnc=1
    vncviewer=1
    serial='pty'
    ne2000=0

    ก่อนบูตเพื่อติดตั้ง ต้องติด xvncviewer ก่อน
    $ sudo apt-get install xtightvncviewer

    แล้วก็ลองบูตเพื่อติดตั้งได้เลย ด้วยคำสั่ง
    $ sudo xm create winxp.cfg

    รีบตามไปดูจอของ WinXP ด้วยการเรียกใช้ xvncviewer
    $ xtightvncviewer localhost

    ผลการทดลอง
    ข้อดีคือ ความเร็วที่เกี่ยวกับฮาร์ดแวร์ดีถึงดีมาก รู้สึกหน่วงนิดเดียว สามารถใช้งานได้อย่างจริงจัง
    ข้อเสีย เหมือนเดิม คือการแสดงผลผ่าน VNC มีข้อจำกัดมาก คือ

    • พอยเตอร์ของเมาส์ไม่ตรง ทำให้ขัดความรู้สึก ต้องไปยกเลิกฟังก์ชั่น Enhance pointer precesion ของเมาส์ ถึงจะพอใช้งานได้
    • เวลาเปลี่ยนความละเอียดของ Desktop อาจต้องเริ่ม xtightvncviewer ใหม่

    ข้อเสียอื่น ๆ ก็พอยอมรับได้ครับ เมื่อแลกกับความเร็วที่ได้มา

    (ลองกับ WinMe ยังไม่ผ่านครับ)

    ubuntu: แก้ปัญหาเรื่อง UUID

    เนื่องจากอูบุนตูเปลี่ยนการเมานต์ดิสก์ จากเดิมที่กำหนดเป็นค่าดีไวซ์ มาเป็น UUID แทน (เข้าใจว่าเปลี่ยนมาหลายรุ่นแล้ว)
    และเนื่องจากผมแบ่งพาร์ติชั่นแยกไว้หลายอัน เพื่อให้ลงได้หลายดิสโตรและสามารถใช้ข้อมูลร่วมกันได้ ทำไปทำมากลัวงง ผมเลยกำหนดค่าให้ไดรฟ์ใหม่ จาก UUID กลับมาใช้เป็น /dev/hda? แบบเก่า
    ลองตรวจดูว่าจะใช้ค่าอะไรบ้างด้วยคำสั่ง
    $ mount
    ปรากฎว่าอูบุนตูแปลงค่าเดิม /dev/hda? กลายเป็น /dev/sda? เสียหมด
    ก็เลยต้องปรับแก้ค่าในไฟล์ /etc/fstab จาก UUID=... มาเป็น /dev/sda? ให้หมด

    วันนี้จะกลับมาทดสอบ Xen อีกครั้ง งานที่เคยเปลี่ยนค่าพาร์ติชั่นไว้เลยกลายมาเป็นปัญหา เพราะเคอร์เนลของ Xen หาไดรฟ์ sda? ไม่พบ
    ทางแก้คือต้องแก้กลับมาเป็น UUID เหมือนเดิม
    คำสั่งที่ใช้คือ
    $ ls /dev/disk/by-uuid/ -alh

    total 0
    drwxr-xr-x 2 root root 180 May  9 21:27 .
    drwxr-xr-x 5 root root 100 May  9 21:27 ..
    lrwxrwxrwx 1 root root  10 May  9 21:27 0e6d39ab-0446-46d6-a6fa-e0d37dc1d73c -> ../../sdb1
    lrwxrwxrwx 1 root root  10 May  9 21:27 17905509-63dd-40e5-99fd-6522baa71c13 -> ../../sda9
    lrwxrwxrwx 1 root root  10 May  9 21:27 257ff35d-8add-492c-9b76-e3adc11ff7f8 -> ../../sda7
    lrwxrwxrwx 1 root root  10 May  9 21:27 2A13-19F4 -> ../../sda1
    lrwxrwxrwx 1 root root  10 May  9 21:27 50f0a6d8-10d6-4a6c-b1ff-3508d66f618e -> ../../sda2
    lrwxrwxrwx 1 root root  10 May  9 21:27 694d81d4-da76-430c-acb4-78491037055e -> ../../sda8
    lrwxrwxrwx 1 root root  10 May  9 21:27 f64defd2-5ebd-4749-947c-b02b55ee4137 -> ../../sda6
    

    แล้วจึงตามแก้ค่าในไฟล์ /etc/fstab และไฟล์ /boot/grub/menu.lst ให้หมด
    เมื่อเปลี่ยนกลับมาเป็นรูป UUID=... เรียบร้อยแล้ว ก็จะสามารถบูต Xen ได้อย่างที่ควรจะเป็น

    อีกคำสั่งนึงคือ
    $ sudo vol_id -u /dev/sda8

    694d81d4-da76-430c-acb4-78491037055e

    เอามาจาก ubuntuforum แต่ลืมบันทึกครับ

    Topic: 

    debian: iptables

    iptables บนเดเบียน มี 2 วิธี (จริง ๆ มีหลายวิธีมาก)

    1. แบบใช้ iptables-save + iptables-restore
      รันคำสั่งเพื่อสร้างกฎต่าง ๆ
      # iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE
      # iptables -A FORWARD -s 192.168.0.0/16 -j ACCEPT
      # iptables -A FORWARD -d 192.168.0.0/16 -j ACCEPT
      # iptables -A FORWARD -s ! 192.168.0.0/16 -j DROP

      เก็บกฎไว้ที่ไฟล์ /etc/iptables/rules
      # mkdir -p /etc/iptables
      # iptables-save > /etc/iptables

      นำกฎมาใช้ตอนเปิดเครื่องใหม่ ผ่าน /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 /sbin/iptables-restore < /etc/iptables/rules
          pre-down /sbin/iptables-save > /etc/iptables/rules
      ...
      
    2. แบบใช้สคริปต์
      สร้างสคริปต์เก็บไว้คือ /usr/local/bin/d.iptables
      # vi /usr/local/bin/d.iptables
      #!/bin/sh
      INTERFACE=eth1
      INT_NET=192.168.0.0/16
      /sbin/iptables -F > /dev/null
      /sbin/iptables -t nat -A POSTROUTING -o $PPP_INT -j MASQUERADE
      /sbin/iptables -A FORWARD -s $INT_NET -j ACCEPT
      /sbin/iptables -A FORWARD -d $INT_NET -j ACCEPT
      /sbin/iptables -A FORWARD -s ! $INT_NET -j DROP
      

      # chmod 755 /usr/local/bin/d.iptables

      นำกฎมาใช้ตอนเปิดเครื่องใหม่ ผ่าน /etc/network/interfaces
      # mkdir -p /etc/iptables/
      # 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/bin/d.iptables
          pre-down /sbin/iptables-save > /etc/iptables/rules
      

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

    อ้างอิง - http://debianclub.org/node/156

    debian: ทำแผ่นบูตฉุกเฉิน

    ทำแผ่นบูตฉุกเฉิน

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

    ติดตั้งแพกเกจในการสร้างแผ่นบูตคือ live-helper

    $ sudo aptitude install live-helper

    สร้างไดเรกทอรี่สำหรับงานนี้

    $ mkdir ~/debian-live
    $ cd ~/debian-live

    เราทำแผ่นบูตฉุกเฉิน ต้องใช้แพกเกจ rescue

    $ lh_config -p rescue

    หากต้องการเปลี่ยนแปลงค่าปริยาย สามารถเปลี่ยนได้จากไฟล์ config/bootstrap

    $ vi config/bootstrap

    เช่น

    ...
    # $LH_DISTRIBUTION: select distribution to use
    # (Default: lenny)
    LH_DISTRIBUTION="sid"
    ...
    # $LH_SECTIONS: select section(s) to use
    # (Default: main)
    LH_SECTIONS="main contrib non-free"
    ...

    ปรุงไฟล์ iso

    $ sudo lh_build

    จะได้ไฟล์ binary.iso ซึ่งสามารถนำไปเขียนแผ่นบูตได้ตามต้องการ
    ซึ่งแผ่น rescue ที่ได้นี้ จะมีแพกเกจ clamav สำหรับค้นหาไวรัสให้เรียบร้อยแล้ว

    สามารถทดสอบไฟล์ iso ที่ได้ โดยใช้แพกเกจ qemu

    $ sudo aptitude install qemu
    $ qemu -cdrom binary.iso

    หมายเหตุ

    • ชื่อแพกเกจย่อย สามารถดูได้จาก /usr/share/live-helper/lists/ เช่น ถ้าจะติดตั้ง gnome ด้วย ก็ใช้พารามิเตอร์ -p gnome เป็นต้น
    • หากเกิดข้อผิดพลาด และต้องการเริ่มใหม่ ให้ลบไดเรกทอรี่ซ่อนที่ชื่อ .stage
    • หากต้องการปรับค่าปริยายของระบบ เช่นต้องเปลี่ยน mirror โดยทดสอบหลายครั้ง ให้ปรับแก้สคริปต์ /usr/share/live-helper/functions/defaults.sh โดยตรง

    อ้างอิง

    Topic: 

    debian: โปรแกรมจัดการ pdf

    ต้องการหาโปรแกรมที่ทำงานกับไฟล์ pdf ที่สามารถใช้งานได้ทั้งลินุกซ์และวินโดวส์

    รวมหรือแตกไฟล์ แบบ GUI เลือกใช้ Pdfsam
    เป็น java แต่ใช้งานไม่ยาก ถ้าบริจาคตั้งแต่ 1 เหรียญขึ้นไปจะมีสิทธิ์ใช้งานรุ่นพิเศษที่มีความสามารถมากขึ้น
    $ sudo aptitude install pdfsam
    รวมหรือแตกไฟล์แบบบรรทัดคำสั่ง เลือกใช้ pdftk (the pdf toolkit)
    $ sudo aptitude install pdftk

    แตกไฟล์ pdf

    $ pdftk FILE.pdf burst

    จะได้ไฟล์ pg_0001.pdf pg_0002.pdf ...

    รวมไฟล์ pdf

    $ pdftk pg_0001.pdf pg_0002.pdf ... cat output OUTFILE.pdf

    ทั้งสองคำสั่ง จะส่งรายงานผลไปออกที่ไฟล์ doc_data.txt

    เอามาจาก: zolved.com: How to combine and separate pdf files on Ubuntu

    ทำ annotation เลือกใช้ Xournal
    $ sudo aptitude install xournal

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

    • สำหรับลินุกซ์ต้องแก้ไขที่ /etc/fonts/conf.d/ คือไฟล์ 90-synthetic.conf หรือ 89-ttf-thai-tlwg-synthetic.conf
    • สำหรับวินโดวส์ ปกติต้องแก้ไขรีจิสตรีที่ HKLM/software/Microsoft/WindowsNT/CurrentVersion/FontSubstitutes/
      แต่กับ xournal เราต้องแก้ที่ c:\xournal\etc\fonts\fonts.conf
    Topic: 

    debian: ทดลองสารพัด

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

    เอามาจาก ThaiLinuxCafe - debian : ทดลอง debootstrap

    ติดตั้ง sarge แบบว่าง ๆ

    จะติดตั้ง sarge เพื่อเป็นฐานในการทดลองรันเซิร์ฟเวอร์แบบต่าง ๆ

    เพื่อให้ประหยัดแบนวิธ จะเขียนเป็น text ทั้งหมด

    • ตั้งชื่อว่า server1
    • ให้มีไอพีเป็น 192.168.1.5 (สมมุติว่า gateway, name-server และ apt-proxy ในระบบคือ 192.168.1.1)
    • ฮาร์ดดิสก์ 120G จะแบ่งพาร์ติชั่นเป็น
      /dev/hda1    primary    /boot    100MB
      /dev/hda2    primary    /        10GB
      /dev/hda3    extend
      /dev/hda5    logical    swap     512MB

      ที่เหลือปล่อยว่างไว้เพื่อใช้ในการทดสอบและติดตั้งระบบต่อไป

    • ผู้ใช้ในระบบชื่อ user1

    เริ่มเลย

    - บูตเครื่องด้วยแผ่นติดตั้งเดเบียน sarge
    Press F1 for help, or ENTER to boot:
    <<<--- linux26

    Choose Language
    <<<--- English

    Choose country of region
    <<<--- other <<<--- Asia <<<--- Thailand

    Select a keyboard layout
    <<<--- American English

    ...

    ถ้ามีเราเตอร์หรือ dhcp server ก็ข้ามขั้นตอนนี้ไปได้


    Configure the network
    <<<--- Configure network manually

    IP address:
    <<<--- 192.168.1.5

    Netmask:
    <<<--- 255.255.255.0

    Gateway:
    <<<--- (ใส่ค่าไอพีของ router) 192.168.1.1

    Name server addresses:
    <<<--- (เหมือนเดิม) 192.168.1.1


    Hostname:
    <<<--- server1

    Domain name:
    <<<--- (ใส่ชื่อโดเมนของเรา) example.com

    Partition disks
    <<<--- ใส่ค่าตามที่ตั้งใจไว้

    /dev/hda1    primary    /boot    100MB
    /dev/hda2    primary    /        10GB
    /dev/hda5    logical    swap     512MB

    Installing the Debian base system
    ...

    Install the GRUB boot loader to the master boot record?
    <<<--- Yes

    Installation complete
    <<<--- Continue

    รีบูตเข้าระบบใหม่

    Welcome to your new Debian system!
    <<<--- Ok

    Is the hardware clock set to GMT?
    <<<--- No

    Are you in the Asia/Bangkok time zone?
    <<<--- Yes

    Root password:
    <<<--- ใส่ค่ารหัสผ่านของ root
    Re-enter password to verify:
    <<<--- เหมือนเดิม

    Enter a full name for the new user:
    <<<--- user1

    Enter a username for your account:
    <<<--- user1

    Enter a password for the new user:
    <<<--- ใส่รหัสผ่านของ user1
    Re-enter password to verify:
    <<<--- เหมือนเดิม

    Archive access method for apt:
    <<<--- cdrom

    CD-ROM device file:
    <<<--- /dev/cdrom

    Scan another CD?
    <<<--- No

    Testing apt sources
    ...

    Choose software to install
    เราไม่เลือกอะไรเลย เพราะจะทำเป็นฐานเพื่อไปงานอื่นเฉย ๆ
    <<<--- Ok

    ...

    General type of mail configuration:
    <<<--- local delivery only; not on a network

    Root and postmaster mail recipient:
    <<<--- user1

    Thank you for choosing Debian!
    <<<--- Ok

    เสร็จแล้ว

    อัปเกรดเป็น etch

    ทีแรกจะทำฐานเป็น sarge และตัวทดลองจริงเป็น etch
    แต่การเปลี่ยน base-package จาก sarge เป็น etch มันเยอะเกินไป
    แก้สคริปต์ debootstrap ไม่ไหว อัปเกรดเลยดีกว่า

    แก้ apt-sources เปลี่ยน sarge เป็น etch ให้หมด
    # vi /etc/apt/sources.list

    deb     http://MY-APT-PROXY:9999/debian etch main contrib non-free
    deb-src http://MY-APT-PROXY:9999/debian etch main contrib non-free
    deb     http://security.debian.org/debian etch main contrib non-free
    deb-src http://security.debian.org/debian etch main contrib non-free

    อัปเดทและอัปเกรด
    # apt-get upgdate; apt-get dist-upgrade
    ตอบค่าปริยายทั้งหมด

    ติดตั้งเคอร์เนลใหม่
    # apt-get install linux-image-686
    ตอบ Y ทั้งหมด

    รีบูตใช้เคอร์เนลใหม่
    ...

    ถอดเคอร์เนลเก่า
    # apt-get remove --purge linux-image-2.6.8-2-386

    เสร็จแล้ว

    ติดตั้ง etch รุ่นทดลอง แบบ debootstrap

    งานที่ทำในครั้งนี้คือ

    • แบ่งพาร์ติชั่น
    • ติดตั้ง debootstrap
    • ติดตั้ง etch รุ่นทดลอง (ย่อว่า etch-exp ในพาร์ติชั่นใหม่ โดยใช้ debootstrap

    แบ่งพาร์ติชั่น

    คราวที่แล้วเรามี

    /dev/hda1    primary    /boot    100MB
    /dev/hda2    primary    /        10GB
    /dev/hda3    extend
    /dev/hda5    logical    swap     512MB

    คราวนี้เราจะเพิ่ม /dev/hda6 ขนาด 20G เพื่อทำเป็น / ให้กับ etch-exp

    /dev/hda6    logical    /        20G

    ในครั้งก่อน การติดตั้งของ sarge ไม่ยอมขยาย extended partition ให้เต็มพื้นที่
    ในคราวนี้เราจะมาแก้ไขเรื่องนี้ด้วย
    ขั้นตอนขยายพาร์ติชั่นและเตรียมการติดตั้งคือ

    1. หยุดการ swap ก่อน
    2. ลบ /dev/hda3 ทิ้ง และสร้างใหม่ให้เต็มพื้นที่
    3. สร้าง /dev/hda5 เป็น swap ใหม่
    4. สร้าง /dev/hda6 เป็น / ของ etch-exp ขนาด 20G
    5. รีบูตใหม่
    6. ฟอร์แมต /dev/hda6 เป็น ext3

    หยุดการ swap
    # swapoff -a

    ลบ /dev/hda3 ทิ้ง
    # fdisk /dev/hda
    <<<--- d
    <<<--- 3

    สร้างใหม่ให้เต็มพื้นที่
    <<<--- n
    <<<--- e (extended)
    Partition number (1-4):
    <<<--- 3
    First cylinder:
    <<<--- DEFAULT
    Last cylinder:
    <<<--- DEFAULT

    สร้าง /dev/hda5 เป็น swap ใหม่
    <<<--- n
    <<<--- l (logical)
    First cylinder:
    <<<--- DEFAULT
    Last cylinder:
    <<<--- +512M
    <<<--- t
    <<<--- 5
    <<<--- 82

    สร้าง /dev/hda6 เป็น / ของ etch-exp ขนาด 20G
    <<<--- n
    <<<--- l (logical)
    First cylinder:
    <<<--- DEFAULT
    Last cylinder:
    <<<--- +20G

    เขียนลงดิสก์
    <<<--- w

    รีบูต เพื่อให้ระบบรู้จักพาร์ติชั่นใหม่
    ...

    ฟอร์แมต /dev/hda6 เป็น ext3
    # mkfs.ext3 /dev/hda6

    ติดตั้ง debootstrap

    # apt-get install debootstrap

    ติดตั้ง etch ในพาร์ติชั่นใหม่ โดยใช้ debootstrap

    สร้างจุดเมานต์ให้ /dev/hda6
    # mkdir -p /mnt/disk

    เมานต์
    # mount /dev/hda6 /mnt/disk

    เริ่ม debootstrap
    # debootstrap --arch i386 etch /mnt/disk http://MY-APT-PROXY:9999/debian

    คัดลอกข้อมูลระบบไปยัง etch-exp
    # cp /etc/hosts /mnt/disk/etc
    # cp /etc/network/interfaces /mnt/disk/etc
    # cp /etc/apt/sources.list /mnt/disk/etc
    # cp /etc/fstab /mnt/disk/etc

    แก้ไข fstab โดยเปลี่ยนจาก /dev/hda2 เป็น /dev/hda6 อย่างอื่นเหมือนเดิม
    # vi /mnt/disk/etc/fstab

    ...
    /dev/hda6    /        ext3    defaults,errors=remount-ro 0       1
    ...

    chroot ไป etch-exp
    # mount -t proc proc /mnt/disk/proc
    # mount -o bind /dev /mnt/disk/dev
    # mount -t sysfs /sys /mnt/disk/sys
    # chroot /mnt/disk /bin/bash

    อัปเดตหนึ่งครั้ง
    # apt-get update

    ติดตั้งแพคเกจจำเป็น
    # apt-get install locales console-data
    # dpkg-reconfigure locales console-data
    <<<--- Ok
    <<<--- Don't touch keymap

    แพคเกจอื่น
    # apt-get install initrd-tools usbutils pciutils bzip2 ssh grub udev yaird ssh
    # dpkg-reconfigure -a

    ตอบค่าปริยายทั้งหมด
    ยกเว้นรหัสผ่าน root, การเพิ่มผู้ใช้และรหัสผ่าน, โซนเวลา

    ติดตั้งเคอร์เนล
    # apt-get install linux-image-686
    Create a symbolic link to the current kernel image?
    <<<--- Y
    Do you want to abort now?
    <<<--- N

    ออกจาก etch-exp
    exit

    กลับมาใช้ทรัพยากรของ etch เดิม
    # umount /mnt/disk/proc
    # umount /mnt/disk/dev
    # umount /mnt/disk/sys
    # sync
    # ldconfig

    ปรับแก้ grub เพิ่ม etch-exp
    # vi /boot/grub/menu.lst
    ปรับแก้ default ให้บูตเป็น etch-exp

    ...
    default         2
    ...

    ไปที่ท้ายไฟล์ (กด G)

    ...
    title           Etch-Experiment, kernel 2.6.17-2-686
    root            (hd0,0)
    kernel          /vmlinuz-2.6.17-2-686 root=/dev/hda6 ro
    initrd          /initrd.img-2.6.17-2-686
    savedefault

    เสร็จแล้ว สามารถรีบูตเข้า etch-exp เพื่อทดสอบได้เลย

    สร้างสคริปต์ติดตั้ง

    เนื่องจากขั้นตอนเยอะ น่าจะทำเป็นสคริปต์ติดตั้งเอาไว้เลย กันพลาดภายหลัง
    เนื้องจากต้องมีการ chroot จึงต้องทำสคริปต์เป็นสองไฟล์

    สคริปต์แรก ชื่อ d.debootstrap.stage1
    # vi d.debootstrap.stage1

    #!/bin/bash
    # EDIT THESE VARIABLE
    DISK=/dev/hda6          # etch-exp PARTITION
    MNT=/mnt/disk           # MOUNT POINT
    
    # VARIABLE
    RELEASE=etch
    PROXY="http://archive.debian.org/debian"
    
    ###### COMMANDS NEED TO RUN BEFORE CALLING THIS SCRIPT
    echo "----- Prerequist command: -----"
    echo "# mkfs.ext3 $DISK"
    echo "# mount $DISK $MNT"
    echo "# debootstrap $RELEASE $MNT $PROXY"
    echo "-------------------------------"
    read -p "*** THIS SCRIPT WILL DESTROY $DISK, PRESS Y TO CONTINUE: " CONFIRM
    if [ $CONFIRM != "Y" ]; then
      exit 1;
    fi
    
    # BEGIN STAGE 1
    cp /etc/hosts $MNT/etc
    cp /etc/network/interfaces $MNT/etc/network
    cp /etc/hostname $MNT/etc
    cp /etc/fstab $MNT/etc
    cp /etc/apt/sources.list $MNT/etc/apt
    mount -t proc proc $MNT/proc
    mount -o bind /dev $MNT/dev
    mount -t sysfs /sys $MNT/sys
    cp d.debootstrap.stage2 $MNT
    echo "edit /etc/host /etc/network/interfaces /etc/hostname /etc/fstab /etc/apt/s
    ources.list"
    echo "then run d.debootstrap.stage2"
    chroot $MNT /bin/bash
    #
    umount $MNT/proc
    umount $MNT/dev
    umount $MNT/sys
    ldconfig
    sync; sync; sync
    echo "Edit /boot/grub/menu.lst to boot into new system."
    echo "AT:TOP"
    echo "--- default     0"
    echo "+++ default     2"
    echo "AT:BOTTOM"
    echo "+++ title       Etch-Experiment, kernel 2.6.17-2.686"
    echo "+++ root        (hd0,0)"
    echo "+++ kernel      /vmlinuz-2.6.17-2-686 root=$DISK ro"
    echo "+++ initrd      /initrd.img-2.6.17-2-686"
    echo "+++ savedefault"

    และสคริปต์สอง ตั้งชื่อว่า d.debootstrap.stage2
    # vi d.debootstrap.stage2

    #/bin/bash
    apt-get update
    apt-get install locales console-data
    dpkg-reconfigure locales
    apt-get install initrd-tools usbutils pciutils bzip2 ssh grub udev yaird ssh
    #
    # FOR X-WINDOWS-SYSTEM
    # apt-get install xorg gdm gnome
    #
    # FOR PRINT SERVER
    # apt-get install cupsys linuxprinting.org-ppds
    #
    # CHANGE THIS LINE FOR THE PROPER KERNEL IMAGE
    apt-get install linux-image-2.6.17-2-686
    #
    passwd
    dpkg-reconfigure -a
    exit

    อย่าลืมเปลี่ยนโหมดรัน
    # chmod 0755 d.*

    เวลาสั่งรันก็ใช้
    # ./d.debootstrap.stage1
    (แก้ /etc/fstab และไฟล์อื่นที่เกี่ยวข้อง)
    # ./d.debootstrap.stage2
    (ปรับตั้ง locales และแพคเกจอื่น ๆ)
    # exit
    (แก้ /boot/grub/menu.lst)
    # reboot

    debian: บันทึกการใช้งาน stable

    ใช้งานเดสก์ทอป sid มานานจนย่ามใจ ลองอัปเกรดเซิร์ฟเวอร์ที่ใช้เป็น testing แบบ dist-upgrade ผลปรากฎว่าเครื่องวินโดวส์ตระกูล 9x ติดต่อแม่ข่ายไม่ได้ ทำให้วุ่นวายหลายวัน

    บันทึกข้อควรจำ

    1. ถ้าเป็นเครื่อง production ควรใช้ stable เท่านั้น
    2. หากต้องการซอฟต์แวร์ที่ทันสมัย แต่ยังเสถียรที่สุดเท่าที่เป็นไปได้ ให้พ่วง testing เข้ามาโดยยังไม่ต้องทิ้ง stable ตัวอย่างไฟล์ /etc/apt/sources.list คือ
      deb     http://ftp.debian.org/debian stable main contrib non-free
      deb-src http://ftp.debian.org/debian stable main contrib non-free
      deb     http://security.debian.org/ stable/updates main contrib non-free
      deb-src http://security.debian.org/ stable/updates main contrib non-free
      deb     http://ftp.debian.org/debian testing main contrib non-free
      deb-src http://ftp.debian.org/debian testing main contrib non-free
      deb     http://security.debian.org/ testing/updates main contrib non-free
      deb-src http://security.debian.org/ testing/updates main contrib non-free

      โดยมีข้อปฏิบัติคือ

      • ห้ามใช้ apt-get dist-upgrade เด็ดขาด
      • เวลาต้องการติดตั้งแพกเกจใหม่ ให้ติดตั้งเป็น stable เช่นจะติดตั้ง samba ใช้คำสั่งว่า
        $ sudo apt-get install samba/stable
        เสร็จแล้วจึง apt-get upgrade อีกทีนึง
    Topic: 

    debian: ลิงก์ชั้นเยี่ยม

    พอดีไปค้นเจอ ขอบันทึกไว้ก่อนครับ เอาไว้ขยายทีหลัง

    Topic: 

    debian: เกร็ดการคอมไพล์เคอร์เนล

    โมเด็มเสีย เลยถือโอกาสปรับปรุงระบบหลายอย่าง รวมทั้งการคอมไพล์เคอร์เนลด้วย
    ปัญหาคือการคอมไพล์เคอร์เนล มันจะเร่งการคอมไพล์ให้เร็วด้วยการสร้างการคอมไพล์แบบขนานไปหลาย ๆ งาน (concurrent) จนเต็มค่าปริยายคือ 40
    ทำให้บริการอื่นตายหมด
    วิธีแก้ไขคือ ปรับค่านี้ให้น้อยลง ตอนที่กำลังคอมไพล์เคอร์เนล ผมใช้ค่า 2
    # export CONCURRENCY_LEVEL=2
    # make-kpkg --initrd kernel-image

    ถ้าเป็นการคอมไพล์ปกติ ใช้ make -j2 ...

    debian: lenny แก้ปัญหา waiting for root file system

    ช่วงนี้ติดตั้งเครื่องหลายเครื่องด้วย lenny พบปัญหาบูตไม่ขึ้นด้วยข้อความว่า "waiting for root file system"
    ค้นกูเกิลดูพบว่าเป็นปัญหาของแพกเกจ initramfs (เป็นทั้งรุ่น 0.92a และ 0.92b) ที่ตีความฮาร์ดดิสก์เป็น sda
    ทางแก้มีหลายทาง เช่น กำหนดชื่อ (label) ให้พาร์ติชั่น หรือใช้ uuid
    แต่รู้สึกว่ายุ่งยากไปหน่อย เพราะเคยมึนกับปัญหา uuid มาทีนึงแล้ว เลยคิดว่าลองกลับไปคบกับ yaird แทน ซึ่งดูจะมีปัญหานี้น้อยกว่า

    ตอนนี้ yaird บน lenny ไม่รู้หายไปไหน ก็เลยต้องไปดาวน์โหลดของ sid มาแทน เวลาติดตั้งด้วยคำสั่ง dpkg -i เขาจะแจ้งว่าติดดีเพนเดนซี่ที่แพกเกจไหนบ้าง ก็ลงไปตามนั้นจนจบ

    มีต้องแก้ไฟล์นิดนึงคือไฟล์ kernel-img.conf
    $ sudo vi /etc/kernel-img.conf

    ...
    #ramdisk = /usr/sbin/mkinitrd /usr/sbin/mkinitramfs
    ramdisk = /usr/sbin/mkinitrd.yaird
    

    เสร็จแล้วก็กลับมาคอนฟิกเคอร์เนลใหม่
    $ sudo dpkg-reconfigure linux-image-2.6.24-1-686

    เสร็จแล้ว รีบูตเข้าเคอร์เนลใหม่ได้เลย

    อ้างอิง
    Kernel compile gone wrong - SOLVED (yaird issue)

    Topic: 

    sid: kernel-2.6.24 / smbfs

    sid อัปเดตเคอร์เนลเป็นรุ่น 2.6.24 แล้ว
    ปัญหาคือ เขาไม่ยอมตั้งให้ smbfs เป็นแบบมอดูลเสียแล้ว (ท่าจะเลิกสนับสนุน smbfs จริง ๆ)
    ทำให้การใช้งานโปรแกรมผ่าน dosemu มีปัญหา เพราะใช้งานผ่าน cifs แล้วเพี้ยน ๆ

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

    วิธีการไม่ถูกหลักนะครับ ที่ถูกต้องควรดูที่ คอมไพล์เคอร์เนลสไตล์เดเบียน | debianclub
    $ cd /usr/src
    $ sudo aptitude install linux-source-2.6.24
    $ tar xfj linux-source-2.6.24.tar.bz2
    $ cd linux-source-2.6.24
    $ sudo cp /boot/config-2.6.24-1-686 .config
    $ sudo vi .config

    ...
    CONFIG_RPCSEC_GSS_SPKM3=m
    CONFIG_SMB_FS=m
    # CONFIG_SMB_NLS_DEFAULT is not set
    CONFIG_CIFS=m
    ...

    $ sudo make-kpkg clean
    $ sudo make-kpkg --initrd linux-image

    -- รอครึ่งชั่วโมง --
    $ cd ..
    $ sudo dpkg -i linux-image-2.6.24_2.6.24-10.00.Custom_i386.deb

    บูตเครื่อง เลือกเคอร์เนลใหม่

    เสร็จแล้ว โปรแกรมที่ใช้ดอส ผ่าน dosemu กลับมาใช้ได้เหมือนเดิมแล้ว
    $ sudo mount -t smbfs //server/dosapp /mnt/dosapp -o username=user1,password=user1password

    PostgreSQL Main

    หน้าหลัก PostgreSQL

    Topic: 

    debian: ติดตั้ง slony1 บน etch และ lenny เบื้องต้น

    ติดตั้ง slony1 บน etch และ lenny เบื้องต้น

    slony เป็นแพ็กเกจส่วนขยายของ PostgreSQL ที่ใช้ทำหน้าที่สำเนาฐานข้อมูลปัจจุบันขณะ แบบใช้ตัวแม่และตัวลูก (master to multiple slaves replication system)
    เหมาะสำหรับงานที่ต้องกระจายการทำงาน คืออ่านจากตัวลูกหลายตัว เขียนที่ตัวแม่ตัวเดียว
    หรืองานที่ต้องการสำรองข้อมูลแบบปัจจุบันขณะ

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

    ข้อควรรู้

    • slony ทำงานแบบ 1 ตัวแม่ หลายตัวลูก เราสามารถอ่านได้ทั้งจากตัวแม่และตัวลูก แต่จะเขียนได้ที่ตัวแม่อย่างเดียว (แต่เขาบอกว่าสามารถทำให้ตัวลูกแทนตัวแม่ได้ด้วย ในกรณีที่ตัวแม่ขัดข้อง และสามารถกลับสู่สภาพเดิมได้ ถ้าตัวแม่กลับขึ้นมาอีกครั้ง แต่ต้องศึกษาเพิ่ม, ถ้ามีเครื่อง 3 เครื่องขึ้นไป มีคนทำให้เป็นตัวแม่ตัวลูกแบบวนรอบได้ ทำให้ได้ระบบที่เหมือนมีตัวแม่ได้หลายตัว แต่เปลืองพลังการทำงาน)
    • โปรแกรมที่ใช้ตั้งค่าเริ่มต้นระบบ ชื่อ slonik ใช้รันที่ตัวแม่อย่างเดียว และโปรแกรมที่ทำงานสำเนาเป็นเบื้องหลังชื่อ slon ตัองสั่งรันทั้งตัวแม่และตัวลูก
    • cluster คือการตั้งชื่อกลุ่มคอมพิวเตอร์ที่เกี่ยวข้องในงานสำเนา 1 งาน ซึ่งจะประกอบด้วย node คือคอมพิวเตอร์และฐานข้อมูลแต่ละชุด และ node จะประกอบด้วย table คือตารางที่เราต้องการคัดลอก ทั้งหมดนี้เราจะต้องนำไปเขียนเป็นข้อกำหนดให้โปรแกรม slonik ทำงาน
    • slony ใช้หลักการทำงาน โดยสร้าง trigger ที่ฐานข้อมูลที่เราจะสำเนา โดยทำในระดับตาราง และมีข้อแม้ว่า ตารางที่ต้องการสำเนา ต้องมี Primary Key ด้วย แต่ถ้าหากไม่มี Primary Key เราตัองแจ้งไว้ในส่วนตั้งข้อกำหนด แล้ว slony ก็จะสร้าง Primary Key ขึ้นมาในตารางของเรา
      ดังนั้นฐานข้อมูลเราจะถูกปรับเปลี่ยนโดย slony ใน 2 จุด คือ 1. เพิ่ม trigger ในแต่ละตารางที่ต้องการสำเนา และ 2. สร้าง Primary Key ในตารางที่ยังไม่มี

    รายละเอียดในการทดสอบ คือ

    • ติดตั้งบน etch ใช้เป็นตัวแม่ และบน lenny ใช้เป็นตัวลูก
    • ใช้กับ PostgreSQL รุ่น 8.3 และ slony รุ่น slony-1 1.2.15
    • ตัวอย่างที่ใช้ ปรับปรุงจากตัวอย่างในไฟล์ซอร์สของ slony (ไฟล์ชื่อ SAMPLE)

    เริ่มงาน

    1 ติดตั้งแพกเกจ และคอมไพล์

    ทำเฉพาะที่ตัวแม่ etch
    จะปรับรุ่น postgresql ให้เป็น 8.3 เพราะมันเร็วกว่า 8.1 เยอะ จึงต้องใช้บริการของ Debian Backports
    ขั้นตอนคือ แก้ /etc/apt/sources.list เพิ่ม backports
    # vi /etc/apt/sources.list
    ...
    deb http://www.backports.org/debian etch-backports main contrib non-free
    ...
    ทำทั้งตัวแม่และตัวลูก
    ขั้นตอนคือสั่งอัปเดต ติดตั้งแพกเกจ ดาวน์โหลด slony และคอมไพล์
    # aptitude update
    # aptitude install postgresql-8.3 postgresql-8.3-slony1 postgresql-contrib-8.3 postgresql-server-dev-8.3 build-essential byacc flex
    # cd
    # mkdir slony
    # cd slony
    # wget http://slony.info/downloads/1.2/source/slony1-1.2.15.tar.bz2
    # tar xfj slony1-1.2.15.tar.bz2
    # cd slony1-1.2.15
    # ./configure --prefix=/usr/local/slony1-1.2.9
    # make
    # make install    # (uninstall with make uninstall)

    2. ปรับแต่ง postgresql

    ทำทั้งตัวแม่และตัวลูก
    ตั้งให้สามารถติดต่อได้จากเครื่องอื่น
    # vi /etc/postgresql/8.3/main/postgresql.conf
    ...
    listen_addresses = '*'
    ...

    ปรับตั้งผู้ใช้ (แต่งกันเองนะครับ อันนี้เป็นเพียงตัวอย่าง)

    # vi /etc/postgresql/8.3/main/pg_hba.conf
    ...
    # "local" is for Unix domain socket connections only
    local   all         all                               md5
    # IPv4 local connections:
    host    all         all         127.0.0.1/32          md5
    host    all         all         192.168.0.0/16        md5
    # IPv6 local connections:
    host    all         all         ::1/128               md5
    ...

    เริ่มการทำงานใหม่

    # /etc/init.d/postgresql-8.3 restart

    3. ปรับตั้ง slony ตอนเริ่มระบบ

    ทำที่ตัวแม่
    จะทำงานด้วยผู้ใช้ชื่อ postgres โดยใช้วิธีสร้างสคริปต์ เพื่อให้สะดวกในการปรับแต่ง
    3.1 เตรียมการสร้างไดเรกทอรี่
    # su postgres
    $ cd
    $ mkdir slony-script
    $ cd slony-script

    หมายเหตุ หากต้องการเปลี่ยนผู้ใช้เป็นคนอื่น ควรสร้างไฟล์รหัสผ่านของ postgres ไว้ในไดเรกทอรี่บ้านของตัวเอง ทั้งนี้เพื่อไม่ให้โปแกรมขึ้นมาถามรหัสผ่านตอนรัน ไฟล์รหัสผ่านนี้ ต้องมีชื่อว่า ~/.pgpass มีรูปแบบเป็น HOSTNAME:PORT:DATABASE:USERNAME:PASSWORD และต้องมีข้ออนุญาตเป็น 600 เท่านั้น

    3.2 โปรแกรมเก็บตัวแปร ใช้เพื่อกันข้อผิดพลาด จึงทำแยกไว้ เพื่อให้โปรแกรมอื่นมาเรียกใช้ ตั้งชื่อว่า variable.sh ข้อกำหนดเบื้องต้นต่าง ๆ สามารถอ่านจากตัวแปรในโค๊ดได้เลย

    $ vi variable.sh
    #!/bin/bash
    #*** WARNING ***
    #   All dabase name DBNAME1 and DBNAME2 will be deleted and replaced by this script. ***
    
    CLUSTER=test_cluster             #CLUSTER NAME
    DBNAME1=test                        #DB OF MASTER
    DBNAME2=test                        #SLAVE DB
    HOST1=master.example.com   #MASTER MACHINE NAME
    HOST2=slave.example.com      #SLAVE MACHINE NAME
    SLONY_USER=postgres           #SUPERUSER
    NORMAL_USER=user1             #NORMAL USER
    $ chmod 700 variable.sh

    ข้อควรระวัง ห้ามตั้งชื่อเครื่องเป็น localhost เพราะข้อมูลต้องถูกส่งผ่านไปมาระหว่างเครื่องแม่กับเครื่องลูก จะเกิดความสับสนได้ ต้องใช้ชื่อจริงของเครื่อง

    3.3 โปรแกรมเตรียมฐานข้อมูล และคัดลอกโครงสร้างไปยังเครื่องลูก ตั้งชื่อว่า slony-prepare.sh
    โปรแกรมนี้ดัดแปลงตามใจชอบ สำคัญอยู่แค่ บรรทัดเอาตัวแปรจาก variable.sh กับ 2 บรรทัดสุดท้าย

    $ vi slony-prepare.sh
    #!/bin/bash
    
    . ./variable.sh
    
    #*** WARNING ***
    #   All dabase name DBNAME1 and DBNAME2 will be deleted and replaced by this script. ***
    
    echo "$HOST1 task"
    #create db to replicate at master
    dropdb -U $SLONY_USER -h $HOST1 $DBNAME1
    createdb -U $SLONY_USER -O $NORMAL_USER -h $HOST1 $DBNAME1
    createlang -U $SLONY_USER -h $HOST1 plpgsql $DBNAME1
    
    psql -U $SLONY_USER $DBNAME1 <<EOF
        CREATE TABLE accounts (
            aid     SERIAL,
            name    VARCHAR(50),
            address VARCHAR(255)
        );
        INSERT INTO accounts (name, address) VALUES ('account1','address1');
    
        CREATE TABLE branches (
            bid     SERIAL,
            name    VARCHAR(50),
            address VARCHAR(255)
        );
        INSERT INTO branches (name, address) VALUES ('branch1','address2');
    
        CREATE TABLE tellers (
            tid     SERIAL,
            name    VARCHAR(50),
            address VARCHAR(255)
        );
        INSERT INTO tellers (name, address) VALUES ('tellers1','address3');
    
        CREATE TABLE history (
            session VARCHAR(50),
            detail  VARCHAR(255)
        );
        INSERT INTO history (session, detail) VALUES ('xxxx','detail1');
    EOF
    
    #...
    
    echo "$HOST2 task"
    
    #drop old db at slave
    dropdb -U $SLONY_USER -h $HOST2 $DBNAME2
    
    #create db at slave
    createdb -U $SLONY_USER -O $NORMAL_USER -h $HOST2 $DBNAME2
    
    #copy structure from master to slave
    pg_dump -s -U $SLONY_USER -h $HOST1 $DBNAME1 | psql -U $SLONY_USER -h $HOST2 $DBNAME2
    
    $ chmod 700 slony-prepare.sh
    $ ./slony-prepare.sh

    3.4 โปรแกรมสร้างเซ็ตให้ slony บรรจุข้อกำหนดของคลัสเตอร์ โหนด และตารางที่จะทำการคัดลอก
    ตั้งชื่อว่า slony-setup.sh

    $ vi slony-setup.sh
    #!/bin/bash
    
    . ./variable.sh
    
    slonik <<_EOF_ 
        # ----
        # This defines which namespace the replication system uses
        # ----
        cluster name = $CLUSTER;
    
        # ----
        # Admin conninfo's are used by the slonik program to connect
        # to the node databases.  So these are the PQconnectdb arguments
        # that connect from the administrators workstation (where
        # slonik is executed).
        # ----
        node 1 admin conninfo = 'dbname=$DBNAME1 host=$HOST1 user=$SLONY_USER';
        node 2 admin conninfo = 'dbname=$DBNAME2 host=$HOST2 user=$SLONY_USER';
        
        # ----
        # Initialize the first node.  The id must be 1.
        # This creates the schema "_test1" containing all replication
        # system specific database objects.
        # ----
        init cluster ( id = 1, comment = 'Node 1' );
    
        # ----
        # The pgbench table history does not have a primary key or
        # any other unique constraint that could be used to identify
        # a row.  The following command adds a bigint column named
        # "_Slony-I_test1_rowID" to the table.  It will have a default
        # value of nextval('"_test1".sl_rowid_seq'), be unique and not
        # null.  All existing rows will be initialized with a number.
        # ----
        table add key ( node id = 1, fully qualified name = 'public.history' );   
    
        # ----
        # The Slony replication system organizes tables in sets.  The
        # smallest unit another node can subscribe is a set.  Usually the
        # tables contained in one set would be all tables that have
        # relationships to each other.  The following commands create
        # one set containing all 4 pgbench tables.  The "master" or origin
        # of the set is node 1.
        # ----
        create set ( id = 1, origin = 1, comment = 'All pgbench tables' );
        set add table ( set id = 1, origin = 1,
            id = 1, fully qualified name = 'public.accounts',
            comment = 'Table accounts' );
        set add table ( set id = 1, origin = 1,
            id = 2, fully qualified name = 'public.branches',
            comment = 'Table branches' );
        set add table ( set id = 1, origin = 1,
            id = 3, fully qualified name = 'public.tellers',
            comment = 'Table tellers' );
        set add table ( set id = 1, origin = 1,
            id = 4, fully qualified name = 'public.history',
            key = serial,
            comment = 'Table history' );
    
        # ----
        # Create the second node, tell the two nodes how to connect to
        # each other and that they should listen for events on each
        # other.  Note that these conninfo arguments are used by the
        # slon daemon on node 1 to connect to the database of node 2
        # and vice versa.  So if the replication system is supposed to
        # use a separate backbone network between the database servers,
        # this is the place to tell it.
        # ----
        store node ( id = 2, comment = 'Node 2' );
        store path ( server = 1, client = 2,
            conninfo = 'dbname=$DBNAME1 host=$HOST1 user=$SLONY_USER');
        store path ( server = 2, client = 1,
            conninfo = 'dbname=$DBNAME2 host=$HOST2 user=$SLONY_USER');
        store listen ( origin = 1, provider = 1, receiver = 2 );
        store listen ( origin = 2, provider = 2, receiver = 1 );
    _EOF_
    
    $ chmod 700 slony-setup.sh
    $ ./slony-setup.sh

    หมายเหตุ บันทึกกันลืม ใน slony1 รุ่น 2.X ขึ้นไป ไม่ต้องใช้คำสั่ง table add key สำหรับตารางที่ไม่มี Primary Key แล้ว

    ทำที่ตัวลูก
    จะทำงานด้วยผู้ใช้ชื่อ postgres เหมือนกัน

    3.5 เตรียมการสร้างไดเรกทอรี่

    # su postgres
    $ cd
    $ mkdir slony-script
    $ cd slony-script

    3.6 โปรแกรมเก็บตัวแปร (กันข้อผิดพลาด)

    $ vi variable.sh
    #!/bin/bash
    #*** WARNING ***
    #   All dabase name DBNAME1 and DBNAME2 will be deleted and replaced by this script. ***
    
    CLUSTER=test_cluster             #CLUSTER NAME
    DBNAME1=test                        #DB OF MASTER
    DBNAME2=test                        #SLAVE DB
    HOST1=master.example.com   #MASTER MACHINE NAME
    HOST2=slave.example.com      #SLAVE MACHINE NAME
    SLONY_USER=postgres           #SUPERUSER
    NORMAL_USER=user1             #NORMAL USER
    $ chmod 700 variable.sh

    4. สั่งเตรียมคัดลอก

    ทำที่ตัวแม่
    สั่งรันโปรแกรมในการส่งข้อมูลคัดลอก โดยต้องรันค้างไว้ (ในการใช้งานจริง ควรทำสคริปต์ไว้เป็นการทำงานเบื้องหลัง
    $ . ./variable.sh
    $ slon $CLUSTER "dbname=$DBNAME1 user=$SLONY_USER"

    จะมีข้อความรายงานออกมามากมายเป็นปกติ

    ทำที่ตัวลูก
    สั่งรันโปรแกรมในการรับข้อมูลคัดลอก ต้องรันค้างไว้เหมือนกัน
    $ . ./variable.sh
    $ slon $CLUSTER "dbname=$DBNAME2 user=$SLONY_USER"

    ถึงตอนนี้ ฐานข้อมูลของทั้งสองโหนด พร้อมที่คัดลอกไปสู่กันแล้ว แต่ยังไม่มีการคัดลอกจริง ๆ เราต้องสั่ง subscribe เซ็ตที่เราสร้างขึ้นในหัวข้อ 3 ในหัวข้อถัดไป

    5. สั่งให้ทำการคัดลอกตามเซ็ตที่กำหนด (subscribe)

    ทำที่ตัวแม่อย่างเดียว
    สร้างสคริปต์สำหรับ subscribe ตั้งชื่อว่า slony-subscribe.sh
    $ vi slony-subscribe.sh
    #!/bin/bash
    
    . ./variable.sh
    
    slonik <<_EOF_
        # ----
        # This defines which namespace the replication system uses
        # ----
        cluster name = $CLUSTER;
    
        # ----
        # Admin conninfo's are used by the slonik program to connect
        # to the node databases.  So these are the PQconnectdb arguments
        # that connect from the administrators workstation (where
        # slonik is executed).
        # ----
        node 1 admin conninfo = 'dbname=$DBNAME1 host=$HOST1 user=$SLONY_USER';
        node 2 admin conninfo = 'dbname=$DBNAME2 host=$HOST2 user=$SLONY_USER';
    
        # ----
        # Node 2 subscribes set 1
        # ----
        subscribe set ( id = 1, provider = 1, receiver = 2, forward = no);
    _EOF_
    
    $ chmod 700 slony-subscribe.sh
    $ ./slony-subscribe.sh

    ถึงตอนนี้ slony จะทำการคัดลอกฐานข้อมูล ใช้เวลาแป๊ปเดียวก็เสร็จ เพราะข้อมูลเรามีนิดเดียว (แหล่งที่มาบอกว่า ฐานข้อมูลขนาด 30G ใช้เวลา 4 ชั่วโมง)

    จบแล้วครับ

    การศึกษาต่อไปคือ ให้ทดลองติดตั้งแพกเกจ slony1-bin และ slony1-doc ของเดเบียน และศึกษาการทำงานของสคริปต์ในแพกเกจนั้น ในการทำให้โปรแกรมทำงานเป็นเบื้องหลัง และสั่ง subscribe หรือ unsubscribe ให้สะดวกมากขึ้น

    เอามาจาก

    postgresql: เชื่อมต่อจาก VFP

    บันทึกการเชื่อมต่อฐานข้อมูล PostgreSQL จาก Visual Foxpro
    เนื่องจากต้องการเอาข้อมูลเก่าใน VFP เข้ามาเก็บใน PostgreSQL

    ขั้นตอนมีดังนี้

    ดาวน์โหลดไดรฟ์เวอร์จาก GBorg: spsqlODBC Downloads
    และติดตั้งให้เรียบร้อย

    ติดตั้ง Data Source ในวินโดวส์ผ่าน Control panel -> ODBC Data Sources โดยป้อนชื่อฐานข้อมูลและชื่อผู้ใช้ให้เรียบร้อย

    เชื่อมต่อจาก vfp ด้วยคำสั่ง
    conn = SQLSTRINGCONNECT([Driver={PostgreSQL Unicode};Server=127.0.0.1;Port=5432;Database=persoane;Uid=postgres;Pwd=123456])

    ตัวอย่าง
    conn = SQLSTRINGCONNECT([Driver={PostgreSQL Unicode};Server=192.168.1.1;Port=5432;Database=mydata;Uid=wd;Pwd=wdpassword])
    SQLEXEC(conn, "SELECT * FROM mytable WHERE myfield='condition'")
    sele sqlresult
    brow

    ยังมีปัญหาเรื่องฟิลด์ TEXT ไปเป็น Memo ทำความยาวได้ไม่เกิน 256 อักขระ
    อีกอันนึงคือเรื่อง LF ในลินุกซ์ และ CR+LF ในวินโดวส์

    อ้างอิง
    PostgreSQL: i can't connect to postgresql with VFP 8

    android: บันทึกแอนดรอยด์

    บันทึกงานศึกษาแอนดรอยด์

    บันทึกเรื่องย่อย

    • ไฟล์ config ของ bluetooth อยู่ที่ /data/misc/bluetoothd/ ถ้า bluetooth ใช้ไม่ได้ ให้ลบไฟล์อื่นทิ้ง เหลือแค่ config ไฟล์เดียว
    • Kyle Brandt: cross compile "bash" for android
    • Backup SMS message: ไฟล์อยู่ที่ /data/data/com.android.providers.telephony/databases/mmssms.db
      คัดลอกมา ลบไฟล์นามสกุลอื่น แล้วรีบูต
    Topic: 

    android: เปลี่ยน boot screen ให้ A88

    กำลังบ้าแอนดรอยด์ อย่าว่ากันนะ

    เครื่องทดสอบเป็น WellcoM รุ่น A88 และทำงานบนลินุกซ์เดเบียน

    เครื่องคงต้องเข้าถึง root ได้ก่อน วิธีการดูได้ที่ MrChoke.Org: ROOT WellcoM A88
    เป็นพื่นฐานของรุ่น 1.6 และ droidsans.com: วิธี Root Wellcom A88 2.1 ใน 1 นาที อันนี้เป็นของรุ่น 2.1

    1. เปลี่ยน boot screen อันแรก

    1.1 หาไฟล์ภาพมา แปลงขนาดให้เป็น 320x480 พิกเซล บันทึกเป็น png สมมุติว่าชื่อ splash.png
    1.2 แปลงเป็น rgb ด้วยโปรแกรม convert ของ imagemagick

    $ convert -depth 8 splash.png rgb:splash.raw
    

    1.3 แปลงเป็น raw565 ด้วยโปรแกรม rgb2565 (จาก Android SDK Platform - ชองผมเป็น 2.1) สมมุติว่าชื่อ mysplash.rgb565

    $ rgb2565 < splash.raw > mysplash.rgb565
    

    1.4 แฟลชกลับเข้าเครื่องด้วย adb และ fastboot (จาก Android SDK Component)
    เสียบสาย usb โทรศัพท์แล้วใช้คำสั่ง

    $ adb reboot bootloader
    

    รอบูตเสร็จ ตามด้วย

    $ fastboot flash splash mysplash.rgb565
    

    ที่มา : Android Wiki: Creating Splashboot Images

    2. เปลี่ยน animated boot screen อันที่สอง

    2.1 เอาไฟล์ bootanimation.zip จาก /data/local/ มาแตก
    เสียบสาย usb ก่อน

    $ adb pull /data/local/bootanimation.zip .
    $ unzip bootanimation.zip
    

    2.2 ในนั้นจะมีไฟล์ภาพ png จำนวน 13 ไฟล์ อยู่ใน ./part0 ที่เราสามารถแก้ไขให้เป็นเหมือนภาพเคลื่อนที่ได้
    2.3 เมื่อแก้ไขเสร็จแล้ว ให้บีบอัดกลับด้วยการที่ไม่ต้องบีบอัดข้อมูล (ใช้ออปชั่น --compression-method store)

    $ zip --compression-method store -r ../bootanimation.zip .
    

    2.4 นำกลับเข้าเครื่องใหม่ แล้วรีบูตเพื่อทดสอบผล

    $ adb push ../bootanimation.zip /data/local
    $ adb reboot

    ที่มา : Goomba141 Tutorials: How to Change Android Boot Screen – without root!
    เสร็จแล้วครับ

    จริง ๆ ไม่ต้อง root ก็ได้นะ แต่ลงที่มาของแหล่งข้อมูลไว้ให้ครบ ๆ เพราะขี้เกียจเขียนละเอียด :P

    Topic: 

    ซ่อมหน้าจอ htc legend

    โทรศัพท์มือถือของน้อง คือ HTC Legend มีปัญหาหน้าจอทัชได้บ้าง ไม่ได้บ้าง ถ้าเปลี่ยนกับ HTC งบประมาณ 5 พันกว่าบ่าท ไม่คุ้มค่าโทรศัพท์แล้ว

    ค้นเว็บดูวิธีแก้ มี 2 วิธี คือ

    • เปลี่ยนกระจก Digitizer งบ 35 เหรียญ
    • เอาที่เป่าผมเป่าหน้าจอ

    วิธีแรกต้องรอนานเกินไป วิธีที่สองลองแล้ว ได้ผลเพียงชั่วครู่ เนื่องจากไม่กล้าให้ความร้อนนาน กลัวว่าจอจะเสีย
    จึงทดลองถอดโทรศัพท์ออกมา แล้วให้ความร้อนเฉพาะตัวกระจก Digitizer วิธีการสรุปดังนี้

    • ถอดโทรศัพท์จนเหลือแต่กระจกหน้า คือ Digitizer ดูวิธีการจาก
      http://www.formymobile.co.uk/htclegenddisassembly.php
    • เอากระจก Digitizer ไปนาบกับเตารีด ความร้อนจะไล่ชั้นอากาศที่ทำให้การทัชเพี้ยนออกไปหมด
    • ประกอบกลับ

    เสร็จแล้ว ใช้งานได้เหมือนเดิม

    หมายเหตุ

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

    bash: tip

    ลองบนอูบุนตู

    ls ให้มีสี
    แก้ /etc/passwd ให้ชื่อเรามีเชลล์เป็น /bin/bash
    $ sudo vi /etc/password
    ...
    user1:x:1101:1001::/home/user1:/bin/bash
    ...

    หรือผ่านคำสั่ง usermod
    $ sudo usermod -s /bin/bash user1

    grep ให้มีสี
    แก้ตัวแปร GREP_OPTION
    $ export GREP_OPTIONS='--color=auto'
    เอามาจาก debian-administration - grep: highlighting matches in color
    ดูที่มาของไอพี
    $ wget -O country.txt "http://api.hostip.info/get_html.php?ip=$IP"
    ผลิตเลขสุ่มแบบง่าย
    $ echo $(head -1 /dev/urandom | od -N 2 | awk '{ print $2 }')
    Topic: 

    bash tips: ลบไฟล์ไวรัส

    ลูกน้องเอาธัมบ์ไดรฟ์มาให้หาไวรัส
    ผลปรากฎว่าพบไฟล์ที่เป็นนามสกุล exe เป็นจำนวนมากภายใต้ไดเรกทอรี่ย่อยเป็นร้อย
    ทางแก้คือสแกนแล้วเก็บชื่อไฟล์ไว้ ตัวอย่างของเนื้อไฟล์ที่ถูกสแกนเก็บไว้ เช่น

    PhotoShop 7.0/• วิธีทำตัวเยลลลี่ •_files/• วิธีทำตัวเยลลลี่ •_files.exe: W32.Autoit.Obfus FOUND
    PhotoShop 7.0/• วิธีทำตัวหนังสือชอล์ค •_files/truehitsstat_files/truehitsstat_files.exe: W32.Autoit.Obfus FOUND
    ...

    จะเห็นว่ามีรูปแบบที่เราจะตัดโดยใช้คำสั่ง cut ได้คือตั้งแต่เครื่องหมาย : เป็นต้นไป
    คำสั่งที่ใช้คือ

    cut -d: -f1

    -d: คือใช้ : เป็นตัวแบ่ง
    -f1 คื่อเมื่อแบ่งแล้ว เราจะเอาสดมถ์ที่ 1 มาใช้งาน

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

    แต่มีปัญหาเพิ่มคือ ชื่อไฟล์ประกอบด้วยช่องว่างจำนวนมาก ไม่สามารถใช้คำสั่ง for i in `cat file` ได้
    ค้นกูเกิลดู พบว่าเขาใช้คำสั่ง while read VARIABLE

    สรุปคำสั่งทั้งหมดมาเป็นขั้นตอนดังนี้
    สมมุติว่าค้นหาไวรัสในธัมบ์ไดรฟ์ในไดเรกทอรี่ /media/disk

    ไปที่ที่ทำงาน

    $ cd /media/disk

    สแกนไวรัส แล้วเก็บผลไว้ที่ ~/virus.txt

    $ clamscan -i -r * > ~/virus.txt

    -i คือให้แสดงเฉพาะไฟล์ที่ติดไวรัส
    -r คือให้ขุดลึกลงไปในไดเรกทอรี่ย่อยด้วย

    แก้ไขไฟล์เล็กน้อย โดยตัดรายงานส่วนท้ายออก
    ยกตัวอย่างส่วนที่ตัดออกคือ

    $ vi ~/virus.txt
    ----------- SCAN SUMMARY -----------
    Known viruses: 203664
    Engine version: 0.92
    Scanned directories: 97
    Scanned files: 1234
    Infected files: 252
    Data scanned: 378.68 MB
    Time: 130.603 sec (2 m 10 s)

    ทีนี้ก็ถึงเวลาลบจริง ๆ แล้ว ***ใช้ด้วยความระมัดระวังนะครับ***
    ถ้ายังไม่แน่ใจ อาจแทนที่คำสั่ง rm ด้วยคำสั่ง ls ลองดูก่อน

    $ cat ~/virus.txt | cut -d: -f1 | while read FILE; do rm "$FILE"; done

    เสร็จแล้วครับ

    หมายเหตุ

    • ต้องติดตั้ง clamscan ไว้ก่อน ถ้ายัง ติดตั้งด้วยคำสั่ง
      $ sudo aptitude install clamav
      $ sudo freshclam
    • ปกติการเมานต์ธัมบ์ไดรฟ์ที่เป็นระบบไฟล์ vfat เขาจะเมานต์ด้วยค่าปริยายด้วยรหัสอักขระภาษาอังกฤษ ทำให้อ่านชื่อไฟล์ภาษาไทยไม่รู้เรื่อง ซึ่งจริง ๆ แล้วต้องตั้งค่ารหัสอักขระเป็น utf-8
      เราสามารถบังคับด้วยการเมานต์ใหม่ หรือตั้งค่ารหัสอักขระถาวร ดูวิธีการได้ที่ th.gnome.org: เกร็ดการแก้รหัสอักขระ usb drive
    • ถ้ามีไฟล์ชื่อ runauto.. เราจะลบไฟล์นี้ตรง ๆ ไม่ได้ ต้องใช้คำสั่งว่า
      $ rm runaut~1 -rf
      ถึงจะลบออก

    ที่มา

    Topic: 

    ลบไวรัสด้วย AVG Anti-Virus

    เครื่องลูกข่ายวินโดวส์ติดไวรัส Win32/Heur ซึ่งใช้ clamav สแกนไม่พบ
    ลองค้นกูเกิลดู พบ ubuntuclub แนะนำ AVG

    ก่อนอื่นก็ไปดาวน์โหลดที่นี่ http://free.avg.com/us-en/download?prd=afl

    แสดงตัวอย่างด้วยบรรทัดคำสั่งคือ
    ดาวน์โหลดรุ่นฟรีมาใช้

    $ wget http://download.avgfree.com/filedir/inst/avg85flx-r290-a2950.i386.deb
    $ sudo dpkg -i avg85flx-r290-a2950.i386.deb

    สั่งให้รัน daemon

    $ sudo /etc/init.d/avgd start

    สั่งอัปเดตข้อมูลไวรัส

    $ sudo avgupdate

    เนื่องจากรุ่นที่เรานำมาใช้งานเป็นรุ่นฟรี จึงไม่สามารถลบไฟล์ไวรัสได้ จึงใช้วิธีสั่งสแกนและรายงานไว้ในไฟล์ หลังจากนั้นจึงอ่านชื่อไฟล์จากรายงานขึ้นมาลบ

    สมมุติว่าให้ค้นที่ /media/disk และให้รายงานไว้ที่ไฟล์ ~/virus.txt

    $ avgscan -r ~/virus.txt /media/disk

    เตรียมการสำหรับการลบ โดยการแก้ไขไฟล์ ~/virus.txt โดยตัดส่วนหัวและส่วนท้ายให้เรียบร้อย ให้เหลือแต่ชื่อไฟล์ที่ติดไวรัส

    $ vi ~/virus.txt
    /media/disk/filename1  Virus XXX
    /media/disk/filename2  Virus YYY
    

    สั่งลบ (*** ใช้ด้วยความระมัดระวังนะครับ ***)

    $ cat ~/virus.txt | awk -F'  ' '{print $1}' | while read FILE; do rm "$FILE"; done

    หลัง -F เคาะสองวรรค (AVG ใช้ช่องว่างสองช่องคั่นชื่อไฟล์และไวรัส ดังนั้นถ้าชื่อไฟล์มีช่องว่างสองช่อง คำสั่งนี้จะใช้งานไม่ได้)

    เสร็จแล้วครับ

    Topic: 

    bash: Rip audio disc to mp3

    มีงานต้องเก็บแผ่นซีดีธรรมะลงไว้ในฮาร์ดดิสก์เป็นจำนวนมาก
    เพื่อให้สะดวก จึงทำเป็นสคริปต์ให้ใช้งานได้สะดวก
    ลักษณะของโปรแกรมคือ เมื่อเราใส่แผ่นแล้วสั่งรันโปรแกรม เขาจะถอดไฟล์เสียงทั้งหมดมาเป็นไฟล์ mp3 ที่ไดเรกทอรี่ปัจจุบัน โดยเอาข้อมูลชื่อเพลงจากแผ่นมาเป็นชื่อไฟล์ ก่อนอื่นติดตั้งแพกเกจที่จำเป็นก่อน
    $ sudo aptitude install lame cdda2wav สคริปต์มีดังนี้
    $ sudo vi /usr/local/bin/d.audio2mp3
    #!/bin/bash
    # Rip audio disc to mp3
    # 
    # USAGE: $0 prefix
    # exam1: $0 T2
    #    -> T2-01-Title1.mp3
    #       T2-02-Title2.mp3
    #       ...
    # in current dir
    # 
    # Requist: aptitude install lame cdda2wav
    
    if [ $1 ]; then PREFIX="$1-"; fi
    
    DEV='/dev/cdrom'
    TMP="/tmp/$USER/`basename $0`_$RANDOM"
    
    mkdir -p $TMP
    pushd $TMP
    
    #to wave
    cdda2wav -L 1 -D $DEV -B
    
    #to mp3
    for i in *wav; do
        NUM=`echo ${i%.*} | cut -d_ -f 2`-
        TITLE=`grep 'Tracktitle=' ${i%.*}.inf | cut -d\'  -f2`
        lame -h -V 2 $i $PREFIX$NUM$TITLE.mp3
    done
    
    popd
    mv $TMP/*mp3 .
    
    rm -rf $TMP
    
    ทำให้รันได้
    $ sudo chmod 777 /usr/local/bin/d.audio2mp3 เสร็จแล้ว
    การใช้งานคือ ใส่แผ่น audio แล้วสั่งรัน d.audio2mp3 จะได้ไฟล์ mp3 มาอยู่ในไดเรคทอรี่ปัจจุบัน การทำงานของสคริปต์คือ
    • แปลงข้อมูลเสียงมาเป็นไฟล์ wav ด้วยโปรแกรม cdda2wav โดยนำไปใส่ในไดเรคทอรี่ชั่วคราวใน /tmp (เพื่อป้องกันผู้ใช้หลายคน จึงใส่ตัวแปร $USER ไว้ด้วย เพราะโค๊ดต้นฉบับ เขียนบนเซิร์ฟเวอร์ที่มีผู้ใช้หลายคน)
    • แปลงไฟล์ wav ที่ได้มาเป็น mp3 โดยใช้โปรแกรม lame (ซึ่งอาจมีปัญหาว่าตอนนี้หา lame บน lenny กับ sid ไม่ได้แล้ว ให้ลองดูวิธีปรุงขึ้นมาเองได้ที่ วิธีปรุง lame โดยคุณโดม http://thisk.org/dr/node/32)
      โดยเอาข้อมูล Tracktitle จากไฟล์นามสกุล inf มาเป็นชื่อไฟล์ mp3 ที่ได้ รวมถึงใส่คำนำหน้า (ตัวแปร $PREFIX) ให้ด้วย

    update

    • 25570325: แพ็กเกจเปลี่ยน ไม่ต้องใช้สคริปต์แล้ว เพียงลงแพ็กเกจชื่อ icedax แล้วใช้คำสั่ง
      $ CDDA_DEVICE=/dev/sr0 cdda2mp3
    Topic: 

    bash: ทำบัตรเลขที่

    มีโจทย์คือ ให้สร้างบัตรแบบเรียงเลขที่ ตั้งแต่ ๑ จนถึง ๕๐๐
    ตัดสินใจใช้ bash + inkscape เพราะง่ายดี
    โดยสมมุติว่าเราได้สร้างเอกสารเป็นไฟล์ inkscape ไว้แล้ว โดยให้ตัวเลขที่จะเรียงให้มีค่าเป็น XXXX เพื่อให้สะดวกในการใช้คำสั่ง grep โค๊ดที่สร้างขึ้น ทำแบบง่าย ๆ คือรันตัวเลขตั้งแต่ 1-500 แต่รุ่นนี้เป็นรุ่นทดสอบ ทำแค่ 1-10 พอ ขั้นตอนคือ
    • แปลงตัวเลขเป็นเลขไทย ด้วยคำสั่ง awk
    • นำไปแทนที่ในไฟล์ inkscape แล้วทำออกมาเป็นไฟล์ใหม่ ด้วยคำสั่ง sed
    • แล้วสั่งส่งออกมาเป็นไฟล์นามสกุล png ด้วยคำสั่งแบบบรรทัดคำสั่งของ inkscape เอง ด้วยพารามิเตอร์ -e
    โค๊ดมีดังนี้
    $ vi runcard
    #!/bin/bash
    
    FILE='card.svg'
    QUAN=10
    MARKER='XXXX'
    
    for i in $(seq 1 $QUAN); do
        TNUM=`echo $i | awk '{ gsub ("0","๐"); gsub ("1","๑"); gsub ("2","๒"); gsub ("3","๓"); gsub ("4","๔"); gsub ("5","๕"); gsub ("6","๖"); gsub ("7","๗"); gsub ("8","๘"); gsub ("9","๙"); print }'`
        sed -e "s/$MARKER/$TNUM/g" $FILE > tmp.svg
        inkscape -d 300 -e tmp$i.png tmp.svg
    done
    
    $ chmod 755 runcard
    เวลาใช้งานก็สั่งรันชื่อไฟล์ runcard เฉย ๆ จะได้ไฟล์ tmp1.png จนถึง tmp10.png ก็สามารถนำไฟล์เหล่านี้ไปพิมพ์งานได้ตามต้องการ
    Topic: 

    bash: ย้ายข้อมูลผู้ใช้ไปเครื่องใหม่

    ต้องการโอนข้อมูลผู้ใช้ไปเครื่องใหม่

    ถ้าเราคัดลอกไฟล์ /etc/passwd /etc/shadow /etc/group /etc/gshadow ไปทับเครื่องใหม่แบบตรง ๆ จะเกิดปัญหาเรื่องผู้ใช้ของระบบจะติดไปด้วย ซึ่งอาจมีค่า UID และ GID ที่ไม่ตรงกัน

    ค้นกูเกิลได้วิธีการจากที่นี่ครับ Move or migrate user accounts from old Linux server to a new Linux server

    เขาใช้หลักการที่ว่า UID ของผู้ใช้ทั่วไป จะมีค่ามากกว่า 1000 (ของ RedHat คือ 500)
    และใช้ awk เป็นตัวกรอง

    --- ข้อเขียนต่อจากนี้ไป ควรทดสอบกับเครื่องทดสอบ ก่อนใช้งานจริง---

    ขั้นตอนตามต้นฉบับก็ไม่มากเท่าไหร่ แต่กลัวว่าเวลาย้ายจริงจะพิมพ์พลาด เลยเอามาเขียนเป็นสคริปต์เพื่อช่วยลดความผิดพลาดตอนพิมพ์บนบรรทัดคำสั่ง รวมทั้งเป็นการศึกษาการเขียนสคริปต์ของ bash ร่วมกันแล้วกันนะครับ

    ตั้งชื่อว่า d.migrate-groupuser ผมใส่ไว้ใน /root
    (ห้ามใส่ในพาธการค้นหาของระบบเด็ดขาด เพราะต้องมีการแก้ไขค่าก่อนใช้งานจริง)

    ตอนใช้งานก็เปลี่ยนค่าตัวแปร TARGETMACHINE ให้เป็นชื่อเครื่องใหม่ที่เราจะโอนไป แล้วก็สั่งรันได้เลย

    โปรแกรมจะทำงานดังนี้

    1. คัดลอกไฟล์ /etc/{passwd,shadow,group,gshadow} และบีบอัดไดเรกทอรี่ /home และ /var/spool/mail มาไว้ในไดเรกทอรี่ $MIGRATEDIR
    2. ผลิตสคริปต์ชื่อ d.import-groupuser เอาไว้สั่งรันที่เครื่องใหม่ และ d.rollback-groupuser เอาไว้สั่งทำย้อนกลับที่เครื่องใหม่เช่นกัน
    3. โอนไฟล์ทั้งหมดใน $MIGRATEDIR ไปยังเครื่องใหม่

    หลังจากนั้น เราก็สั่งรันสคริปต์ d.import-groupuser ที่เครื่องใหม่ได้เลย

    *** ใช้ด้วยความระมัดระวัง ***

    เริ่มเลย
    # vi /root/d.migrate-groupuser

    #!/bin/bash
    #PREREQUISITE:
    #  1. INSTALL PACKAGE: openssh-client
    #  2. EDIT THIS FILE, CHANGE VARIABLE "TARGETMACHINE" TO REAL TARGET 
    #THEN RUN AS root
    
    MIGRATEDIR="/root/migrategroupuser"    #MIGRATE DIR
    UGIDLIMIT=1000    #UID&GID OF USER DATA: DEBIAN=1000, REDHAT=500
    
    # EDIT TARGETMACHINE 
    TARGETMACHINE="newserver"              #COPY TO THIS MACHINE
    TARGETDIR="/root/importgroupuser"      #COPY DATA TO THIS DIR
    
    if [ "$1" != "OK" ]; then
        PROG=`basename $0`
        cat << EOF
    
    *** DON'T PLACE THIS SCRIPT IN SYSTEM SEARCH PATH
    *** USE WITH CARE
    *** EDIT TARGETMACHINE VARIABLE THEN RUN AS ROOT
    
    Move or migrate user accounts from old Linux server to a new Linux server
    FROM: http://www.cyberciti.biz/faq/howto-move-migrate-user-accounts-old-to-new-server/
    
    USE WITH CARE, PLEASE BACKUP OLD DATA, RUN AS ROOT
    - COPY FILTERED /etc/{passwd,group,shadow,gshadow} TO $MIGRATEDIR WITH EXT .mig 
    - BACKUP /home, /var/spool/mail TO $MIGRATEDIR .tar.gz
    - TRANSFER ALL FILES IN $MIGRATEDIR TO root@$TARGETMACHINE:$TARGETDIR WITH scp
     
    USAGE:
        $PROG OK
    
    ('OK' is safety argument)
    DON'T FORGET TO EDIT TARGETMACHINE VARIABLES
    
    EOF
        exit 1
    fi
    
    if [ ! `which scp` ]; then
        echo "Please install 'openssh-client' first."
        exit 1
    fi
    
    mkdir -p $MIGRATEDIR
    
    echo "
    Copy /etc/{password,group,shadow,gshadow} to $MIGRATEDIR ..."
    for i in /etc/{passwd,group,shadow,gshadow}; do
        j=`basename $i`
        awk -v LIMIT=$UGIDLIMIT -F: '($3>=LIMIT) && ($3!=65534)' $i > $MIGRATEDIR/$j.mig
    done
    
    echo "
    gzip /home ..."
    tar -zcpf $MIGRATEDIR/home.tar.gz /home/*
    
    echo "
    gzip /var/spool/mail ..."
    tar -zcpf $MIGRATEDIR/mail.tar.gz /var/spool/mail/*
    
    #------------------------------------------------
    
    echo "
    Generate import script ..."
    IMPORTPROG="d.import-groupuser"
    cat > $MIGRATEDIR/$IMPORTPROG << VIRTUAL_EOF
    #!/bin/bash
    
    IMPORTDIR="/root/importgroupuser"     #COPY DATA TO THIS DIR
    BACKUPDIR="/root/backupgroupuser"     #BACKUP OLD DATA
    
    if [ "\$1" != "OK" ]; then
        PROG=\`basename \$0\`
        cat << EOF
    
    Move or migrate user accounts from old Linux server to a new Linux server
    FROM: http://www.cyberciti.biz/faq/howto-move-migrate-user-accounts-old-to-new-server/
    
    USE WITH CARE, PLEASE BACKUP OLD DATA, RUN AS ROOT
    - BACKUP /etc/{passwd,group,shadow,gshadow} TO \$BACKUPDIR/etc
    - BACKUP /home /var/spool/mail TO \$BACKUPDIR/tar 
    - ADD NEW GROUP-USER DATA FROM \$IMPORTDIR TO /etc
    - ADD NEW /home AND /var/spool/mail TO /
     
    USAGE:
        \$PROG OK
    
    ('OK' is safety argument.)
        
    EOF
        exit 1
    fi
    
    
    if [ ! -d \$IMPORTDIR ]; then
        echo "\$IMPORTDIR not exist, program aborted"
        exit 1
    fi
    
    
    echo "
    Backup old data and add migrate data to /etc/{passwd,group,shadow,gshadow} ..."
    mkdir -p \$BACKUPDIR
    pushd \$BACKUPDIR
    ls | while read FILE; do
        mv "\$FILE" "\$FILE.bak"
    done
    mkdir -p {etc,tar}
    for i in {passwd,group,shadow,gshadow}; do
        cp /etc/\$i etc
        cat \$IMPORTDIR/\$i.mig >> /etc/\$i
    done
    
    echo "
    Backup /home and /var/spool/mail in \$BACKUPDIR/tar ..."
    tar -zcpf tar/home.tar.gz /home/*
    tar -zcpf tar/mail.tar.gz /var/spool/mail/*
    popd
    
    echo "
    Extract imported data in \$IMPORTDIR to /home and /var/spool/mail ..."
    pushd /
    echo "
    gunzip \$IMPORTDIR/home.tar.gz to / ..." 
    tar -zxf \$IMPORTDIR/home.tar.gz
    
    echo "
    gunzip \$IMPORTDIR/mail.tar.gz to / ..." 
    tar -zxf \$IMPORTDIR/mail.tar.gz
    popd
    
    echo "
    Import finished."
    echo
    echo "Please delete these files to finish the work:"
    echo "  \$BACKUPDIR"
    echo "  \$IMPORTDIR/d.import-groupuser"
    echo "  \$IMPORTDIR/d.rollback-groupuser"
    echo
    
    VIRTUAL_EOF
    chmod 700 $MIGRATEDIR/$IMPORTPROG
    #------------------------------------------------
    
    echo "Generate rollback script ..."
    ROLLBACKPROG="d.rollback-groupuser"
    cat > $MIGRATEDIR/$ROLLBACKPROG << VIRTUAL_EOF
    #!/bin/bash
    
    BACKUPDIR="/root/backupgroupuser"     #BACKUP OLD DATA
    
    if [ "\$1" != "OK" ]; then
        PROG=\`basename $0\`
        cat << EOF
    
    Move or migrate user accounts from old Linux server to a new Linux server
    FROM: http://www.cyberciti.biz/faq/howto-move-migrate-user-accounts-old-to-new-server/
    
    USE WITH CARE, PLEASE BACKUP OLD DATA, RUN AS ROOT, NO WARNING
    - COPY BACKUP DATA IN \$BACKUPDIR/etc TO /etc
    - *** REMOVE OLD /home AND /var/spool/mail ***
    - COPY BACKUP DATA IN \$BACKUPDIR/tar TO /home and /var/spool/mail
    - ROLLBACK \$BACKUPDIR/*.bak
     
    USAGE:
        \$PROG OK
    
    ('OK' is safety argument.)
    
    EOF
        exit 1
    fi
    
    if [ ! -d \$BACKUPDIR/etc ]; then
        echo "\$BACKUPDIR/etc not exist, program aborted"
        exit 1
    fi
    if [ ! -d \$BACKUPDIR/tar ]; then
        echo "\$BACKUPDIR/tar not exist, program aborted"
        exit 1
    fi
    
    
    echo "
    Copy \$BACKUPDIR/etc to /etc ..."
    pushd \$BACKUPDIR
    cp etc/* /etc
    popd
    
    
    echo "
    *** Remove /home and /var/spool/mail *** ..."
    rm -rf /home /var/spool/mail
    
    echo "
    Copy \$BACKUPDIR/tar to /home and /var/spool/mail ..."
    pushd /
    tar -zxf \$BACKUPDIR/tar/home.tar.gz
    tar -zxf \$BACKUPDIR/tar/mail.tar.gz
    popd
    
    echo "
    Remove last rollback data ..."
    pushd \$BACKUPDIR
    rm -rf etc tar
    for i in *.bak; do
        mv \$i \${i%.bak}
    done
    
    echo "
    Rollback finished."
    
    VIRTUAL_EOF
    chmod 700 $MIGRATEDIR/$ROLLBACKPROG
    #------------------------------------------------
    
    echo "
    Transfer data to root@$TARGETMACHINE:$TARGETDIR, enter $TARGETMACHINE root password:"
    echo "COMMAND RUN: scp -r $MIGRATEDIR root@$TARGETMACHINE:$TARGETDIR"
    scp -r $MIGRATEDIR/* root@$TARGETMACHINE:$TARGETDIR
    
    echo "
    Finished.
    
    Next, run $MIGRATEDIR/$IMPORTPROG at $TARGETMACHINE
    (*** Use $ROLLBACKPROG to undo the job, BUT DO USE WITH CARE ***)
    "
    

    เปลี่ยนสถานะให้รันได้
    # chmod 700 /root/d.migrate-groupuser

    ทดลองรัน
    เริ่มที่เครื่องเก่า
    # /root/d.migrate-groupuser OK

    Copy /etc/{password,group,shadow,gshadow} to /root/migrategroupuser ...
    
    gzip /home ...
    tar: Removing leading `/' from member names
    
    gzip /var/spool/mail ...
    tar: Removing leading `/' from member names
    
    Generate import script ...
    Generate rollback script ...
    
    Transfer data to root@newserver:/root/importgroupuser, enter newserver root password:
    COMMAND RUN: scp -r /root/migrategroupuser root@newserver:/root/importgroupuser
    root@newserver's password: <<<---NEWSERVER_ROOT_PASSWORD
    d.import-groupuser                                            100% 1668     1.6KB/s   00:00    
    d.rollback-groupuser                                          100% 1291     1.3KB/s   00:00    
    group.mig                                                     100%   90     0.1KB/s   00:00    
    gshadow.mig                                                   100%    0     0.0KB/s   00:00    
    home.tar.gz                                                   100%   61MB  10.2MB/s   00:06    
    mail.tar.gz                                                   100%   10KB  10.4KB/s   00:00    
    passwd.mig                                                    100% 1287     1.3KB/s   00:00    
    shadow.mig                                                    100% 2937     2.9KB/s   00:00    
    
    Finished.
    
    Next, run /root/migrategroupuser/d.import-groupuser at newserver
    (*** Use d.rollback-groupuser to undo the job, BUT DO USE WITH CARE ***)
    
    

    ย้ายไปทำที่เครื่อง newserver
    # /root/importgroupuser/d.import-groupuser OK

    Backup old data and add migrate data to /etc/{passwd,group,shadow,gshadow} ...
    ~/backupgroupuser ~
    
    Backup /home and /var/spool/mail in /root/backupgroupuser/tar ...
    tar: Removing leading `/' from member names
    tar: Removing leading `/' from member names
    ~
    
    Extract imported data in /root/importgroupuser to /home and /var/spool/mail ...
    / ~
    
    gunzip /root/importgroupuser/home.tar.gz to / ...
    
    gunzip /root/importgroupuser/mail.tar.gz to / ...
    ~
    
    Import finished.
    
    Please delete these files to finish the work:
      /root/backupgroupuser
      /root/importgroupuser/d.import-groupuser
      /root/importgroupuser/d.rollback-groupuser
    
    

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

    Topic: 

    bash: ลองทำ cron ตรวจ apt-proxy

    แพกเกจ apt-proxy เป็นแพกเกจที่ใช้เป็นคลังเก็บแพกเกจที่เครือข่ายเราใช้ประจำ ใช้ง่ายและสะดวก แต่ชอบตายบ่อย จะเข้าไปรื้อดูการทำงาน ก็ยากเกินความสามารถ เลยลองเขียนเป็น cron แก้ปัญหาเฉพาะหน้าไปก่อน การทำงานของสคริปต์ก็ไม่มีอะไรมาก แค่ตรวจว่าการใช้คำสั่ง aptitude update นานเกิน 60 วินาทีหรือไม่ ถ้านานก็ให้เริ่ม apt-proxy ใหม่ แค่นี้เอง
    (สามารถปรับช่วงเวลารอที่ตัวแปร TIME_WAIT ตามความเร็วของเน็ตที่มีอยู่จริง) # vi /usr/local/sbin/d.cron-check-apt-proxy
    #!/bin/bash
    TIME_WAIT='60'
    
    function sub_wait() {
            sleep $TIME_WAIT 
            echo `date +%F-%R-%s`
    }
    
    function update_apt_proxy() {
            aptitude update
            echo `date +%F-%R-%s`
    }
    
    T1=`sub_wait` &
    T2=`update_apt_proxy` &
    wait
    if [ "$T1" \< "$T2" ]; then
            echo "apt-proxy update longer than $TIME_WAIT seconds, restart apt-proxy." 
            /etc/init.d/apt-proxy restart
    fi
    
    # chmod 755 /usr/local/bin/d.cron-check-apt-proxy ตั้ง crontab ให้รันทุกชั่วโมง
    # crontab -e
    ...
    #CHECK apt-proxy EVERY 60 MIN
    0 * * * *   /usr/local/sbin/d.cron-check-apt-proxy
    ...
    เสร็จแล้ว ลองใช้ดูก่อน แล้วจะรายงานผลต่อไปตรับ update
    • เจอสาเหตุแล้ว มาจากตั้ง squid3 เป็นแบบ transparent ไว้ ทางแก้คือ ยกเลิก transparent หรือไม่ก็ปรับ apt-proxy ให้ไปใช้ http_proxy ที่พอร์ต 8080 (หรือพอร์ตอื่นที่ตั้งไว้ใน squid) เรียบร้อยแล้ว - แต่สคริปต์นี้ก็ยังน่าใช้อยู่ดี อาจปรับเป็นทุก 3 ชั่วโมงก็ได้

    bash: สคริปต์คัดลอกผู้ใช้

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

    • ติดตั้งลินุกซ์ผ่าน debootstrap
    • ติดตั้งลินุกซ์โดยการเมานต์ live cd แล้วคัดลอก squashfs มาติดตั้งโดยตรง

    ข้อกำหนดคือ

    • ต้องใช้สิทธิ์ root ในการรัน
    • ต้องเป็นลินุกซ์ที่มีการเก็บไฟล์ผู้ใช้แบบมาตรฐาน คือเก็บที่ไฟล์ /etc/passwd, /etc/group และ /etc/shadow

    สคริปต์มีดังนี้

    $ sudo vi /usr/local/sbin/transfer_users.sh
    #!/bin/bash
    
    function usage() {
        cat <<EOF
    Usage: $0 DESTINATION
    Transfer users from current linux system to DESTINATION directory that have another linux system.
    Run as root.
    EOF
        exit 1
    }
        
    DEST=$1
    
    if [ ! "$UID" == "0" ]; then
        echo "Please run as root."
        usage
    fi
    if [ ! -d "$DEST" ]; then
        echo "DESTINATION directory not found."
        usage
    fi
    if [ ! -d "$DEST/etc" ]; then
        echo "DESTINATION/etc directory not found."
        usage
    fi
    
    US=`ls /home`
    TMP=/tmp/${RANDOM}.txt
    
    #SORT ON UID
    for i in $US; do
        UUID=`grep ":/home/${i}:" /etc/passwd | cut -d: -f3`
        echo "${UUID}:${i}" >> $TMP
    done
    
    #PROCESS EACH USER
    for i in `cat $TMP | sort`; do
        UUID=`echo $i | cut -d: -f1`
        U=`echo $i | cut -d: -f2`
    
        PASSWDLINE=`grep ":/home/${U}:" /etc/passwd`
    
        #/etc/group
        GNUM=`echo $PASSWDLINE | cut -d: -f4`
        GROUPLINE=`grep $GNUM /etc/group`
        GNAME=`echo $GROUPLINE | cut -d: -f1`
        OLDGROUP=`grep $GNAME $DEST/etc/group`
        if [ "$OLDGROUP" == "" ]; then
            echo $GROUPLINE >> $DEST/etc/group
        elif [ "$OLDGROUP" != "$GROUPLINE" ]; then
            sed -i "s/$OLDGROUP/$GROUPLINE/g" $DEST/etc/group
        fi
    
        #/etc/passwd
        OLDPASS=`grep ":/home/${U}:" $DEST/etc/passwd`
        if [ "$OLDPASS" == "" ]; then
            echo $PASSWDLINE >> $DEST/etc/passwd
        elif [ "$OLDPASS" != "$PASSWDLINE" ]; then
            sed -i "s/$OLDPASS/$PASSWDLINE/g" $DEST/etc/passwd
        fi
    
        #/etc/shadow
        SHADOWLINE=`grep "${U}:" /etc/shadow | grep -v '*'`
        OLDSHADOW=`grep "${U}:" $DEST/etc/shadow | grep -v '*'`
        if [ "$OLDSHADOW" == "" ]; then
            echo $SHADOWLINE >> $DEST/etc/shadow
        elif [ "$OLDSHADOW" != "$SHADOWLINE" ]; then
            sed -i "s#$OLDSHADOW#$SHADOWLINE#g" $DEST/etc/shadow
        fi
    
        #GROUP MEMBERS
        for j in `groups $U | cut -d: -f2`; do
            for k in `echo $j`; do
                if [ "$k" == "$GNAME" ]; then
                    continue
                fi
    
                OLDLINE=`grep "${k}:" $DEST/etc/group`
                if ! echo $OLDLINE | grep $U ; then
                    if [ "${OLDLINE: -1}" == ":" ]; then
                        sed -i "s/$OLDLINE/${OLDLINE}${U}/g" $DEST/etc/group
                    else
                        sed -i "s/$OLDLINE/${OLDLINE},${U}/g" $DEST/etc/group
                    fi
                fi
            done
        done
        
        #/HOME
        if [ ! -d "$DEST/home/$U" ]; then
            mkdir -p "$DEST/home/$U"
        fi
        chown -R ${U}:${GNAME} $DEST/home/$U
    done
    
    #for i in passwd group shadow; do
    #    cp $DEST/etc/$i $DEST/etc/${i}-
    #done
    rm $TMP
    

    ตัวอย่างเช่น เราติดตั้งลินุกซ์อีกอันไว้ที่ /dev/sdaXX

    $ sudo mount /dev/sdaXX /mnt/tmp
    $ sudo /usr/local/sbin/transfer_users.sh /mnt/tmp

    ผู้ใช้ทั้งหมดใน /home/* จะถูกคัดลอกไปยัง /mnt/tmp/home/ ตามต้องการ

    Topic: 

    bash: สคริปต์บล๊อกผู้ใช้ Drupal

    ทำสคริปต์บล๊อกผู้ใช้ Drupal จากบรรทัดคำสั่ง (bash)

    $ vi drupal_blockuser.sh
    #!/bin/bash
    
    #FROM: https://drupal.org/node/118759
    
    if [ "$#" == "0" ]; then
        echo "Script to block drupal users by uid"
        echo "Usage: $0 UID1 UID2 UID3 ..."
        exit 1
    fi
    
    SITE=http://www.example.com    #NO END SLASH
    NAME=drupal_admin_user
    PASS=drupal_admin_password
    
    STATUS=0    #0:block,1:unblock
    OPLOGIN="Log%20in"
    OPSUBMIT="Save"
    TMP=/tmp/$RANDOM.txt
    COOKIES=/tmp/$RANDOM.txt
    
    #LOGIN
    wget -q -o /dev/null -O /dev/null \
        --keep-session-cookies --save-cookies $COOKIES --load-cookies $COOKIES \
        --post-data="name=${NAME}&pass=${PASS}&op=${OPLOGIN}&form_id=user_login" \
        "${SITE}/?q=user/login"
    
    #DO BLOCK
    while (( "$#" )); do
        ID=$1
        wget -q -o /dev/null -O $TMP \
            --keep-session-cookies --save-cookies $COOKIES --load-cookies $COOKIES \
            "${SITE}/?q=user/${ID}/edit"
        let "LINE=`grep -n 'value="user_profile_form"' $TMP | cut -d: -f1`-1"
        TOKEN=`sed -n -e "${LINE}p" $TMP | awk -F'value="' '{ print $2 }' | cut -d\" -f1`
        USER1=`grep 'id="edit-name"' $TMP | awk -F'value="' '{ print $2 }' | cut -d\" -f1`
        EMAIL=`grep 'id="edit-mail"' $TMP | awk -F'value="' '{ print $2 }' | cut -d\" -f1`
        wget -q -o /dev/null -O /dev/null \
            --keep-session-cookies --save-cookies $COOKIES --load-cookies $COOKIES \
            --post-data="status=${STATUS}&op=${OPSUBMIT}&name=${USER1}&mail=${EMAIL}&form_token=${TOKEN}&form_id=user_profile_form" \
            "${SITE}/?q=user/${ID}/edit"
        echo "USER:\"$USER1\" --- EMAIL:\"$EMAIL\" --- Blocked."
    
        shift
    done
    
    rm $TMP
    rm $COOKIES
    
    $ chmod 700 ./drupal_blockuser.sh

    วิธีใช้งานก็สั่ง

    $ ./drupal_blockuser.sh UID1 UID2 UID3 ...

    อย่าลืมแก้ตัวแปร SITE, NAME, PASS ให้เข้ากับงานเรา

    Topic: 

    bash: สคริปต์บล๊อกผู้ใช้ Drupal แบบอัตโนมัติ

    ปรับปรุงสคริปต์ให้สามารถบล๊อกโดยอัตโนมัติ

    $ vi drupal_auto_blockuser.sh
    #!/bin/bash
    # AUTO BLOCK DRUPAL USER SCRIPT, CHECK SPAM FROM google.co.th
    # FROM: https://drupal.org/node/118759
    
    SITE=http://www.example.com     # NO TRAILING SLASH
    NAME=drupal_admin_user
    PASS=drupal_admin_password
    UIDFILE="site_last_uid.txt"
    
    SPAMDATA="spamdata.txt"
    SLEEP=10    #REDUCE SERVER LOAD
    STATUS=0    #0:block,1:unblock
    OPLOGIN="Log%20in"
    OPSUBMIT="Save"
    TMP=/tmp/$RANDOM.txt
    COOKIES=/tmp/$RANDOM.txt
    GGCOOKIES=/tmp/$RANDOM.txt
    AGENT="Mozilla/5.0 (X11; Linux x86_64; rv:25.0) Gecko/20130712 Firefox/25.0"
    QURL="https://www.google.co.th/search?q="
    
    # LOGIN
    wget -q -o /dev/null -O /dev/null \
        --keep-session-cookies --save-cookies $COOKIES --load-cookies $COOKIES \
        --post-data="name=${NAME}&pass=${PASS}&op=${OPLOGIN}&form_id=user_login" \
        "${SITE}/?q=user/login"
    
    # PREVIOUS USER ID
    PREVUID=`cat $UIDFILE`
    let "STARTUID=${PREVUID}+1"
    
    # GET LAST USER ID
    wget -q -o /dev/null -O $TMP \
        --keep-session-cookies --save-cookies $COOKIES --load-cookies $COOKIES \
        "${SITE}/?q=admin/user/user"
    LASTUID=`grep -m1 'class="form-item" id="edit-accounts-' $TMP | awk -F'class="form-item" id="edit-accounts-' '{ print $2 }' | cut -d\- -f1`
    
    #DO CHECK
    for ID in `eval echo {$STARTUID..$LASTUID}`; do
        wget -q -o /dev/null -O $TMP \
            --keep-session-cookies --save-cookies $COOKIES --load-cookies $COOKIES \
            "${SITE}/?q=user/${ID}/edit"
    
        LINE0=`grep -n 'value="user_profile_form"' $TMP | cut -d: -f1`
        # PREVENT BLOCKED USER ERROR
        if ! [ "$LINE0" ]; then
            continue
        fi
        let "LINE=${LINE0}-1"
        TOKEN=`sed -n -e "${LINE}p" $TMP | awk -F'value="' '{ print $2 }' | cut -d\" -f1`
        USER1=`grep 'id="edit-name"' $TMP | awk -F'value="' '{ print $2 }' | cut -d\" -f1`
        EMAIL=`grep 'id="edit-mail"' $TMP | awk -F'value="' '{ print $2 }' | cut -d\" -f1`
    
        # QUERY FOR SPAM
        wget -q -o /dev/null -O $TMP \
            --keep-session-cookies --save-cookies $GGCOOKIES --load-cookies $GGCOOKIES \
            -U "$AGENT" "${QURL}${EMAIL}"
    
        ISSPAM=0
        KEYWORD=""
    #***********************
        ### BEWARE THIS WHILE LOOP, VARIABLE $ISSPAM IS LOCAL, USE REDIRECT INSTEAD OF PIPE ###
        #cat $SPAMDATA | while read TXT; do
        #    if [ "$TXT" ]; then
        #        if grep -m1 "$TXT" $TMP > /dev/null; then
        #            ISSPAM=1
        #            break
        #        fi
        #    fi
        #done
    #***********************
        while read TXT; do
            if [ "$TXT" ]; then
                if grep -m1 "$TXT" $TMP > /dev/null; then
                    ISSPAM=1
                    KEYWORD=$TXT
                    break
                fi
            fi
        done < <(cat $SPAMDATA )
    
        if [ "$ISSPAM" != "0" ]; then
            #DO BLOCK
            wget -q -o /dev/null -O /dev/null \
                --keep-session-cookies --save-cookies $COOKIES --load-cookies $COOKIES \
                --post-data="status=${STATUS}&op=${OPSUBMIT}&name=${USER1}&mail=${EMAIL}&form_token=${TOKEN}&form_id=user_profile_form" \
                "${SITE}/?q=user/${ID}/edit"
            echo "UID:\"$ID\", USER:\"$USER1\", EMAIL:\"$EMAIL\", KEYWORD:\"$KEYWORD\" --- Blocked."
            sleep $SLEEP
        else
            echo "--- UID:\"$ID\", USER:\"$USER1\", EMAIL:\"$EMAIL\", not found as spam."
        fi
        sleep $SLEEP
    done
    
    # SAVE LAST UID
    echo $LASTUID > $UIDFILE
    
    rm $TMP
    rm $COOKIES
    rm $GGCOOKIES
    
    $ chmod 700 drupal_auto_blockuser.sh

    สร้างไฟล์ spamdata.txt

    ใส่ค่า spam keyword

    $ vi spamdata.txt
    did not match any documents
    Spam
    ไม่ตรงกับเอกสารใด
    

    สร้างไฟล์ site_last_uid.txt

    ใส่ค่า uid ของผู้ใช้คนสุดท้ายที่ไม่ต้องการตรวจ (สมมุติว่าเป็นผู้ใช้คนที่ 1000)

    $ vi site_last_uid.txt
    1000

    รัน

    $ ./drupal_auto_blockuser.sh

    ครั้งต่อไปก็แค่สั่งรัน โดยไม่ต้องปรับแต่งอะไรอีก เว้นแต่มี spam keyword เพิ่มเติมก็ไปแก้ไฟล์ spamdata.txt

    ปรับปรุง

    • เพิ่ม cookies ให้กับ google เพื่อไม่ให้ google คิดว่าเป็น bot
    Topic: 

    bash: สคริปต์แก้ Boot record ของ NTFS

    update 2556-12-03

    ลองใช้สคริปต์กับพาร์ติชั่นที่ขนาดไม่เท่ากันแล้วปรากฎว่าใช้ไม่ได้ เพราะ NTFS เก็บข้อมูลหลายอย่างมากกว่าแค่จุดเริ่มต้นและขนาด (ดูที่ NTFS Partition Boot Sector)

    วิธีที่ได้ผลกว่าคือ ฟอร์แมตไดร์ฟไว้ก่อน -> เก็บ boot sector ไว้ 72 ไบต์ -> ทำ ntfsclone -> เอา boot recort ที่เก็บไว้มาเขียนทับ

    ตัวอย่างเช่น จะคัดลอก ไดร์ฟ /dev/sda1 ไปยัง /dev/sdb1 ขั้นตอนจะเป็นดังนี้

    $ sudo mkfs.ntfs -f /dev/sdb1
    $ sudo dd if=/dev/sdb1 of=sdb1.img bs=72 count=1
    $ sudo ntfsclone -O /dev/sdb1 /dev/sda1
    $ sudo dd if=sdb1.img of=/dev/sdb1

    *** สคริปต์ด้านล่างนี้ ล้าสมัยแล้ว ***

    แก้ปัญหาเวลาใช้ ntfsclone ในการ restore พาร์ติชั่น NTFS มาลงในฮาร์ดดิสก์ลูกใหม่ ซึ่งจุดเริ่มต้นและขนาดอาจไม่เท่าของเดิม

    วิธีการคือใช้ข้อมูลจากตาราง Master Boot Record ปัจจุบัน มาเขียนทับ boot record ของพาร์ติชั่น NTFS ที่ต้องการ โดยใช้เชลล์สคริปต์

    $ vi ntfs_fix_boot_sector.sh
    #!/bin/bash
    
    function usage() {
        cat <<EOF
    Fix NTFS boot record:
    Usage: $0 DEVICE
    Example: $0 /dev/sda1
    EOF
        exit 1
    }
    
    if [ ! "$1" ]; then
        usage
    fi
    
    PART=$1
    
    if [ ! -b "$PART" ]; then
        echo -e "$PART not found. Exit.\n"
        usage
    fi
    
    BSF="`echo $PART | tr '/' '_'`.img"
    BSFB=${BSF}.bak
    BSFD="${BSF}_`date +%F`.bak"
    
    function reverse_byte () {  #reverse_byte HEXSTR
        local S=$1  #HEXSTR
        local B
        local C
        while [ "$S" ]; do
            B=${S:(-2)}
            S=${S:0:-2}
            C="${C}\\x${B}"
        done
        echo $C
    }
    
    function replace_byte () {  #replace_byte OFFSET LENGTH NUMBER
        local O=$1  #OFFSET
        local L=$2  #LENGTH
        local N=$3  #NUMBER
        let NL=${L}*2
        XN=`printf "%0${NL}x" $N`
        RXN=`reverse_byte $XN`
        #echo "printf $RXN | dd of=$BSF bs=1 seek=$O count=$L conv=notrunc"
        printf $RXN | sudo dd of=$BSF bs=1 seek=$O count=$L conv=notrunc > /dev/null 2>&1
    }
    
    sudo dd if=$PART of=$BSF bs=512 count=1 > /dev/null 2>&1
    
    TMP="/tmp/$0_${RANDOM}.txt"
    
    HDD=$PART
    while [ "`echo ${HDD:(-1)} | tr '0123456789' ' '`" == " " ]; do
        HDD=${HDD:0:-1}
    done
    
    sudo fdisk -l $HDD > $TMP
    
    if ! cat $TMP | grep $PART | grep NTFS > /dev/null 2>&1; then
        echo -e "$PART is not NTFS partition. Exit.\n"
        sudo rm $TMP
        sudo rm $BSF
        usage
    fi
    
    if [ ! -f "$BSFB" ]; then
        cp $BSF $BSFB
    else
        cp $BSF $BSFD
    fi
    
    echo "Fixing $PART ..."
    
    HEADS=`cat $TMP | grep 'sectors/track' | cut -d, -f1 | cut -d\  -f1`
    SECTORS=`cat $TMP | grep 'sectors/track' | cut -d, -f2 | cut -d\  -f2`
    START=`cat $TMP | grep "${PART} " | awk -F' ' '{ print $2 }'`
    END=`cat $TMP | grep "${PART} " | awk -F' ' '{ print $4 }'`
    if [ "$START" == "*" ]; then
        START=`cat $TMP | grep "${PART} " | awk -F' ' '{ print $3 }'`
        END=`cat $TMP | grep "${PART} " | awk -F' ' '{ print $4 }'`
    fi
    let LENGTH=$END-$START
    
    OFFSET_HEADS=26
    OFFSET_SECTORS=24
    OFFSET_START=28
    OFFSET_LENGTH=40
    LEN_HEADS=1
    LEN_SECTORS=1
    LEN_START=4
    LEN_LENGTH=4
    
    for i in HEADS SECTORS START LENGTH; do
        A="OFFSET_${i}"
        B="LEN_${i}"
        replace_byte ${!A} ${!B} ${!i}
    done
    
    echo "Fix with heads=$HEADS, sectors/track=$SECTORS, start=$START, length=$LENGTH"
    
    sudo dd if=$BSF of=$PART > /dev/null 2>&1
    
    sudo rm $TMP
    
    cat <<EOF
    
    Command used:
    sudo dd if=$BSF of=$PART
    
    Revert with command:
    sudo dd if=$BSFB of=$PART
    EOF
    

    ตัวอย่าง สมมุติว่าพาร์ติชั่นที่ต้องการเป็น /dev/sda1 คำสั่งคือ

    $ ntfs_fix_boot_record.sh /dev/sda1

    ได้ผลลัพธ์คือ

    Fixing /dev/sda1 ...
    Fix with heads=255, sectors/track=63, start=2048, length=62914559
    
    Command used:
    sudo dd if=_dev_sda1.img of=/dev/sda1
    
    Revert with command:
    sudo dd if=_dev_sda1.img.bak of=/dev/sda1
    

    ที่มา:

    bash: เกร็ดคำสั่ง find

    bash: เกร็ดคำสั่ง find

    (ศึกษาเพราะต้องการเอาไฟล์ในคลังของ apt-proxy เฉพาะไฟล์ใหม่ ๆ เลยต้องการลบไฟล์เก่า ๆ ทิ้ง เพื่อให้ขนาดคลังแพ็กเกจเล็กลง)

    เริ่มเลยครับ
    ต้องการค้นหาไฟล์ชื่อ *Doc*
    $ find /PATH/TO/FILE -name '*Doc*'

    ค้นหาและลบไฟล์
    $ find /PATH/TO/FILE -name '*Doc*' -exec rm {} \;

    ค้นหาไฟล์ที่เก่ากว่า 5 วันลงไป
    $ find /PATH/TO/FILE -mtime +5

    ค้นหาไฟล์เก่าตั้งแต่ 5 วันขึ้นมา
    $ find /PATH/TO/FILE -mtime -5

    ค้นหาไฟล์เก่ากว่า 1 ปีลงไป และลบไฟล์เหล่านั้นทิ้ง
    $ find /PATH/TO/FILE -mtime +365 -exec rm {} \;

    ค้นหาไฟล์ที่มีขนาด 0 byte และลบไฟล์
    $ find /PATH/TO/FILE -type f -size 0 -exec rm {} \;

    *** ใช้ด้วยความระมัดระวังนะครับ ***

    วันนี้แค่นี้ก่อนครับ

    อ้างอิง

    bash: แตะ Shell Script

    ขออนุญาตเขียนแบบกองโจรนะครับ โดยมือใหม่ เพื่อมือใหม่ครับ

    เอามาจาก tldp: BASH Programming - Introduction HOW-TO

    ศึกษาเพิ่มเติมได้จาก tldp: Bash Guide for Beginners
    และในเชิงลึก จาก tldp: Advanced Bash-Scripting Guide

    1.เกริ่น

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

    ฉะนั้นการที่จะเขียนโค๊ดให้ได้ดี จึงต้องศึกษาจดจำคำสั่งต่าง ๆ ของเชลล์ให้ได้เท่าที่เราต้องการใช้งาน (จำหมดคงไม่ไหว)
    คำสั่งต่าง ๆ สามารถดูได้ที่ gnu.org: Bash Reference Manual

    สำหรับเดเบียน หากต้องการใช้งาน bash แบบเต็มรูป (ไม่อั้นความสามารถ) อาจต้องปรับแต่งเล็กน้อย
    เปลี่ยนให้เชลล์ของเราเป็น bash แทน sh ใช้คำสั่ง

    $ chsh -s /bin/bash

    สำหรับเอดิเตอร์ ถ้าใช้ vi ควรติดตั้ง vim-full และอย่าลืมแก้ไขไฟล์ vimrc ให้แสดงสีด้วย เพื่อให้ดูโค๊ดได้ง่ายขึ้น

    $ sudo aptitude install vim-full
    $ vi ~/.vimrc
    syntax on
    :wq

    2.เริ่มเขียน

    2.1 สคริปต์ Hello World

    สมมุติตั้งชื่อสคริปต์ว่า hello.sh

    $ vi hello.sh
    #!/bin/bash          
    echo Hello World
    :wq

    อย่าลืมเปลี่ยนสถานะเพื่อให้สคริปต์สามารถรันได้

    $ chmod 755 hello.sh

    เริ่มรัน

    $ ./hello.sh
    Hello World

    เรียบร้อยแล้ว

    บรรทัดแรก เรียกว่า hash-bang เป็นการบอกให้เชลล์รู้ว่า โค๊ดที่เราเขียนนี้จะถูกประมวลผลด้วยโปรแกรมอะไร ในที่นี้คือ /bin/bash
    บรรทัดที่สอง เป็นการสั่งให้พิมพ์ Hello World ออกทางจอภาพ

    2.2 สคริปต์สำหรับสำรองข้อมูล

    จากตัวอย่างข้างบน ผมเขียนอธิบายโดยละเอียดโดยใช้เอดิเตอร์ vi แต่เพื่อให้กระชับเข้า จะขอละเลยการใช้เอดิเตอร์ โดยจะเขียนเฉพาะโค๊ดอย่างเดียวครับ

    #!/bin/bash          
    tar -cvzf /tmp/my-backup.tgz /home/USER/

    บรรทัดที่สองให้เปลี่ยนคำว่า USER เป็นชื่อเรา
    เป็นการสั่งให้ใช้คำสั่ง tar ทำการสำรองข้อมูลพร้อมบีบอัดข้อมูลในไดเรคทอรี่ของบ้านเราไปสู่ไฟล์ชื่อ /tmp/my-backup.tgz

    3. การเปลี่ยนทิศข้อมูล (Redirection)

    ใช้สัญญลักษณ์ > ใสการเปลี่ยนทิศ

    3.1 ข้อมูลมาตรฐาน

    ข้อมูลมาตรฐานในเชลล์จะมีอยู่ 4 ชนิด คือข้อมูลเข้า(stdin), ข้อมูลแสดงผล(stdout), ข้อมูลข้อผิดพลาด(stderr), และแฟ้มข้อมูล(file)
    ในทางปฏิบัติ เราสามารถเปลี่ยนทิศทางของข้อมูลเหล่านี้ไปมาได้ โดยมีมาตรฐานคือ 1 จะหมายถึงข้อมูลแสดงผล(stdout) และ 2 จะหมายถึงข้อมูลความผิดพลาด(stderr)

    เช่น

    3.2 ตัวอย่างเปลี่ยน stdout ไปเป็น file

    $ ls -l > ls-l.txt

    จะเปลี่ยนการแสดงผลของคำสั่ง ls -l ไปเก็บไว้ที่ไฟล์ชื่อ ls-l.txt ดังนั้นคำสั่งตามตัวอย่างนี้จะไม่แสดงอะไรออกมาทางจอภาพ แต่จะเก็บไว้ที่ไฟล์แทน หากเราต้องการดูผล สามารถใช้คำสั่งแสดงผลของไฟล์ได้คือ

    $ cat ls-l.txt

    3.3 ตัวอย่างเปลี่ยน stderr ไปเป็น file

    $ grep da * 2> grep-errors.txt

    ตัวอย่างนี้เป็นการค้นหาข้อความ da ในทุกไฟล์ (*) และหากเกิดข้อผิดพลาดขึ้น จะนำข้อความผิดพลาดไปเก็บไว้ที่ไฟล์ชื่อ grep-errors.txt

    3.4 ตัวอย่างเปลี่ยน stdout ไปเป็น stderr

    $ grep da * 1>&2

    เป็นการค้นหาข้อความ da ในทุกไฟล์ (*) โดยนำการแสดงผลไปใส่ไว้ใน stderr แทนการแสดงผลปกติ แต่ในกรณีนี้เราป้อนคำสั่งทางแป้นพิมพ์ stdout และ stderr คือจอภาพเหมือนกัน จึงไม่เห็นความแตกต่าง แต่หากคำสั่งนี้ไปอยู่ในสคริปต์ที่เรากำหนดให้ stderr เป็นไฟล์ error-log การแสดงผลก็จะถูกเปลี่ยนทิศไปตามนั้น

    3.5 ตัวอย่างเปลี่ยน stderr ไปเป็น stdout

    $ grep da * 2>&1

    เป็นการค้นหาข้อความ da ในทุกไฟล์ (*) โดยหากเกิดข้อผิดพลาดขึ้น จะแสดงผลข้อผิดพลาดออกมาทาง stdout ซึ่งในที่นี้คือจอภาพเหมือนกัน

    3.6 ตัวอย่างเปลี่ยน stderr และ stdout ไปยัง file

    $ rm -f $(find /home/USER -name core) &> /dev/null

    คำสั่งนี้เป็นการค้นหาไฟล์ในไดเรคทอรี่ /home/USER ที่มีชื่อว่า core (find /home/USER -name core)
    เมื่อพบแล้วก็จัดการลบทิ้งโดยไม่เตือน (rm -f)
    โดยโยกการแสดงผลทั้งหมด (ทั้ง stderr และ stdout - ใช้สัญญลักษณ์ &>) ไปยังไฟล์ชื่อ /dev/null ซึ่งเป็นไฟล์พิเศษ หมายความว่ายกเลิกการแสดงผลทั้งหมด
    (คำสั่งนี้ค่อนข้างอันตราย เพราะลบโดยไม่เตือน โปรดทดลองด้วยความระมัดระวังครับ)

    4. การส่งต่อผลลัพธ์ หรือ ไปป์ (Pipes)

    4.1 ความหมาย

    ไปป์เป็นการส่งต่อผลลัพธ์จากคำสั่งหนึ่งไปเป็นค่านำเข้าของอีกคำสั่งหนึ่ง

    4.2 ตัวอย่างไปป์

    $ ls -l | sed -e "s/[aeio]/u/g"

    ตัวอย่างนี้จะนำเอาผลลัพธ์ที่ได้จากคำสั่ง ls -l ส่งต่อไปให้คำสั่ง sed -e "s/[aeio]/u/g"
    ซึ่งจะแปลงการแสดงผลจากอักขระ a หรือ e หรือ i หรือ o ไปเป็นอักขระ u ทั้งหมด

    เราอาจเขียนคำสั่งเทียบเท่าได้ดังนี้

    $ ls -l > temp.txt
    $ sed -e "s/[aeio]/u/g" temp.txt
    $ rm temp.txt

    จะเห็นว่าการทำไปป์ ลดขั้นตอนไปมาก คงเหลือเพียงบรรทัดเดียว

    4.3 ตัวอย่างไปป์ที่สอง

    $ ls -l | grep "\.txt$"

    ตัวอย่างนี้จะส่งผลลัพธ์จากคำสั่ง ls -l ต่อไปให้คำสั่ง grep "\.txt$" คือให้แสดงเฉพาะไฟล์ที่มีนามสกุลเป็น .txt เท่านั้น
    มีค่าเท่ากับคำสั่ง ls แบบใส่พารามิเตอร์กรอง

    $ ls -l *.txt

    หมายเหตุ
    รูปแบบ "\.txt$" เป็นรูปแบบของ Regular Expression ซึ่งใช้มากในเชลล์สคริปต์ มีความหมายว่า "ที่ต้องลงท้ายด้วย .txt"

    5. ตัวแปร (Variables)

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

    5.1 ตัวอย่างสคริปต์ Hello World แบบใช้ตัวแปร

    #!/bin/bash          
    STR="Hello World!"
    echo $STR

    ให้ผลลัพธ์เหมือนตัวอย่างที่ 2.1
    ข้อควรระวังคือ

    • การกำหนดค่าให้ตัวแปร อย่าเว้นวรรคระหว่างตัวแปรกับเครื่องหมาย =
    • หากลืมใส่เครื่องหมาย $ จะหมายถึงการแสดงผลข้อความว่า STR เฉย ๆ

    5.2 ตัวอย่างสคริปต์สำรองข้อมูลแบบใช้ตัวแปร

    #!/bin/bash          
    OF=/tmp/my-backup-$(date +%Y%m%d).tgz
    tar -cvzf $OF /home/USER/

    ให้ผลลัพธ์คล้ายตัวอย่าง 2.2 แต่เพิ่มการใช้ตัวแปรลอยในคำสั่ง $(date +%Y%m%d) ซึ่งมีผลทำให้ชื่อไฟล์ข้อมูลสำรองมีวันที่ต่อท้ายชื่อด้วย

    5.3 ตัวแปรท้องถิ่น

    ตัวแปรในเชลล์สคริปต์ทุกตัว จะเป็นตัวแปรรวม (Global) คือทุก ๆ ส่วนของโปรแกรมจะเห็นเหมือนกันหมด
    แต่ในกรณีที่เราต้องการให้เห็นเฉพาะในฟังก์ชั่นที่เราต้องการ เราสามารถกำหนดให้ตัวแปรเป็นตัวแปรท้องถิ่นได้ด้วยคำสั่ง local
    เช่น

    #!/bin/bash
    HELLO=Hello 
    function hello {
            local HELLO=World
            echo $HELLO
    }
    
    echo $HELLO
    hello
    echo $HELLO

    สคริปต์นี้ตัวแปร HELLO ในโปรแกรมหลัก กับในฟังก์ชั่นจะเป็นตัวแปรคนละตัวกัน

    6. ประโยคเงื่อนไข

    6.1 รูปแบบ

    มีรูปแบบคือ

    if [EXPRESSION]; then
        CODE IF 'EXPRESSION' IS TRUE.
    [elif [EXPRESSION-ELIF]; then
        CODE IF 'EXPRESSION-ELIF' IS TRUE.]
    [else
        CODE IF NOTHING IS TRUE.]
    fi

    6.2 ตัวอย่าง if ... then

    #!/bin/bash
    if [ "foo" = "foo" ]; then
        echo expression evaluated as true
    fi

    โค๊ดนี้จะเป็นจริงเสมอ ดังนั้นข้อความ "expression evaluated as true" จะถูกพิมพ์ออกมาเสมอ

    6.3 ตัวอย่าง if ... then ... else

    #!/bin/bash
    if [ "foo" = "foo" ]; then
       echo expression evaluated as true
    else
       echo expression evaluated as false
    fi

    โค๊ดนี้จะเป็นจริงเสมอ ดังนั้นข้อความ "expression evaluated as true" จะถูกพิมพ์ออกมาเสมอ

    6.4 ตัวอย่างแบบใช้ตัวแปร

    #!/bin/bash
    T1="foo"
    T2="bar"
    if [ "$T1" = "$T2" ]; then
        echo expression evaluated as true
    else
        echo expression evaluated as false
    fi

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

    7.การวนรอบ โดยใช้คำสั่ง for, while และ until

    คำสั่ง for มีลักษณะคล้าย for ในภาษาไพธอน มีรูปแบบเป็น

    for VAR in SCOPE; do
        COMMAND
    done

    คำสั่ง while มีรูปแบบเป็น

    while [CONDITION]; do
        COMMAND
    done

    ถ้าเงื่อนไข CONDITION เป็นจริง ก็จะทำคำสั่ง COMMAND คำสั่ง until รูปแบบตรงกันข้ามกับ while โดยมีรูปแบบเป็น

    until [CONDITION]; do
        COMMAND
    done

    คือจะทำคำสั่ง COMMAND จนกว่าเงื่อนไข CONDITION จะเป็นจริง

    7.1 ตัวอย่าง for

    #!/bin/bash
    for i in $( ls ); do
        echo item: $i
    done

    เป็นการนำคำสั่ง ls ไปเป็นตัวแปรชั่วคราวในการกำหนดขอบเขตให้กับตัวแปร i ในคำสั่ง for ในที่นี้จะทำการแสดงผลว่า item: FILENAME ...

    7.2 ตัวอย่าง for อีกแบบ

    #!/bin/bash
    for i in `seq 1 10`; do
        echo $i
    done

    เป็นการนำผลจากคำสั่ง seq 1 10 ไปกำหนดขอบเขตให้กับตัวแปร i ในคำสั่ง for อาจเขียนเลียนแบบตัวอย่าง 7.1 ได้เหมือนกันดังนี้

    #!/bin/bash
    for i in $( seq 1 10 ); do
        echo $i
    done

    7.3 ตัวอย่าง while

    #!/bin/bash 
    COUNTER=0
    while [  $COUNTER -lt 10 ]; do
        echo The counter is $COUNTER
        let COUNTER=COUNTER+1 
    done

    เป็นการแสดงค่าตัวแปร COUNTER ที่เพิ่มขึ้นทีละ 1 จาก 0 ถึง 9 โปรดสังเกตุการใช้ตัวแปรเก็บค่าตัวเลข, การเปรียบเทียบตัวเลขโดยใช้ตัวเปรียบเทียบ -lt (less than) และการกำหนดเพิ่มค่าให้กับตัวแปรแบบตัวเลขโดยใช้คำสั่ง let

    7.4 ตัวอย่าง until

    #!/bin/bash 
    COUNTER=20
    until [  $COUNTER -lt 10 ]; do
        echo COUNTER $COUNTER
        let COUNTER-=1
    done

    จะแสดงตัวเลขตั้งแต่ 20 ลดลงทีละ 1 จนถึง 10

    8.ฟังก์ชั่น (functions)

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

    function FUNCTION_NAME {
        COMMAND
    }

    หรือ

     FUNCTION_NAME () {
        COMMAND
    }

    โปรแกรมจะเว้นไม่ถูกเรียกทำงานในช่วงตั้งแต่ชื่อฟังก์ชั่นจนกระทั่งจบบล๊อก { COMMAND }
    เรานิยมวางฟังก์ชั่นไว้ที่ต้นโปรแกรม เพื่อให้สามารถถูกเรียกจากโค๊ดหลักได้

    8.1 ตัวอย่างฟังก์ชั่น

    #!/bin/bash 
    function quit {
        exit
    }
    function hello {
        echo Hello!
    }
    hello
    quit
    echo foo

    ตัวอย่างนี้ บรรทัดที่ 10 คือคำสั่ง echo foo จะไม่ถูกเรียกใช้ เนื่องจากโปรแกรมจะหลุดสู่เชลล์ในบรรทัดที่ 9 คือคำสั่ง quit

    8.2 ตัวอย่างฟังก์ชั่นที่มีการส่งผ่านค่าตัวแปร

    #!/bin/bash 
    function quit {
        exit
    }  
    function ex {
        echo $1 
    }  
    ex Hello
    ex World
    quit
    echo foo

    จากตัวอย่าง จะเห็นการส่งผ่านข้อความเข้าไปในฟังก์ชั่น ex ด้วยตัวแปร $1
    ในทำนองเดียวกัน ถ้ามีการส่งผ่านตัวแปรหลายตัว ก็จะใช้รูปแบบเป็น $2, $3, ...
    โดยเรียกใช้งานด้วยรูปแบบ ex VAR1 VAR2 VAR3 ... ตามลำดับ

    9.การติดต่อผู้ใช้ (User Interfaces)

    9.1 ใช้คำสั่ง select ในการสร้างหัวข้อให้เลือก

    #!/bin/bash
    OPTIONS="Hello Quit"
    select opt in $OPTIONS; do
        if [ "$opt" = "Quit" ]; then
            echo done
            exit
        elif [ "$opt" = "Hello" ]; then
            echo Hello World
        else
            clear
            echo bad option
        fi
    done

    ตัวอย่างนี้จะสร้างหัวข้อ 1) และ 2) จากตัวแปร OPTIONS เพื่อมาให้เลือก โดยจะวนรอบถามไปเรื่อย ๆ จนกว่าจะพบคำสั่ง exit ให้ออกจากการวนรอบ

    9.2 ใช้การตรวจสอบว่ามีการใส่ค่าพารามิเตอร์หรือไม่

    #!/bin/bash        
    if [ -z "$1" ]; then 
       echo usage: $0 directory
       exit
    fi
    SRCD=$1
    TGTD="/var/backups/"
    OF=home-$(date +%Y%m%d).tgz
    tar -cZf $TGTD$OF $SRCD

    บรรทัดที่ 2 จะตรวจว่ามีการใส่พารามิเตอร์ให้กับโปรแกรมหรือไม่ (if [ -z "$1" ] -z หมายถึงการตรวจสอบว่ามีค่าหรือไม่)
    ถ้าไม่มีการใส่ค่าพารามิเตอร์ โปรแกรมจะทำคำสั่งในบรรทัดที่ 3 คือแสดงการใช้งาน ($0 คือชื่อโปรแกรมนี้) และบรรทัดที่ 4 คือออกจากโปรแกรม
    แต่ถ้ามีการใส่ค่าพารามิเตอร์ถูกต้อง ก็จะทำบรรทัดที่ 6 ต่อไปจนจบ ซึ่งในที่นี้คือการบีบอัดทำสำเนาให้กับไดเรกทอรี่ที่เราให้เป็นพารามิเตอร์ ($1) ในชื่อไฟล์ว่า /var/backups/home-YYYYMMDD

    9.3 หยุดถามผู้ใช้ด้วยคำสัง read

    #!/bin/bash
    echo Please, enter your name
    read NAME
    echo "Hi $NAME!"

    สังเกตุการใช้คำสั่ง read กำหนดค่าให้ตัวแปร NAME ไม่ต้องใช้เครื่องหมาย $ นำหน้าตัวแปร

    อาจรอรับค่าทีละหลายตัวแปรได้ด้วย โดยคั่นแต่ละตัวแปรด้วยช่องว่าง

    #!/bin/bash
    echo Please, enter your firstname and lastname
    read FN LN 
    echo "Hi! $LN, $FN !"

    10.เกร็ดอื่น ๆ

    10.1 การสั่งรันสคริปต์และคำสั่ง source

    การสั่งรันสคริปต์ในเชลล์ มีเกร็ดคือ

    • ถ้าเราใส่ชื่อสคริปต์พร้อมพาธ เชลล์จะค้นหาสคริปต์จากชื่อเต็มที่เราใส่ เช่น
      $ /bin/ls
    • ถ้าเราใส่ชื่อสคริปต์โดด ๆ เชลล์จะค้นหาสคริปต์จากตัวแปร $PATH โดยไม่สนใจไดเรคทอรี่ปัจจุบัน เช่น
      $ mycode

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

      $ ./mycode

    เมื่อสคริปต์ถูกรันจนจบแล้ว ค่าของตัวแปรต่าง ๆ ในสคริปต์จะถูกลบไปด้วย ยกเว้นถ้าเราใช้คำสั่ง source หรือคำสั่ง .
    เชลล์จะรันคำสั่งนั้นโดยถือเสมือนเป็นสภาพแวดล้อมเดียวกัน ดังนั้นค่าตัวแปรต่าง ๆ ในสคริปต์จะยังคงค้างอยู่ในเชลล์
    โดยเมื่อใช้คำสั่งนี้แล้ว การค้นหาสคริปต์ เชลล์จะค้นหาจากตัวแปร $PATH ก่อน ตามด้วยไดเรคทอรี่ปัจจุบันด้วย
    เช่น ถ้าสคริปต์ mycode มีเนื้อไฟล์เป็น

    #!/bin/bash
    ABC="This is new ABC"

    ทดลองรันได้ดังนี้

    $ ABC="Old ABC"
    $ echo $ABC
    Old ABC
    $ ./mycode
    $ echo $ABC
    Old ABC
    $ . mycode
    $ echo $ABC
    This is new ABC

    10.2 การแทนค่าตัวเลข

    เราใช้ $((ARITHMATIC)) หรือ $[ARITHMATIC] ในการแทนค่าตัวแปร
    ดังนี้

    $ echo $(1+1)
    bash: 1+1: command not found
    
    $ echo 1+1
    1+1
    $ echo $((1+1))
    2
    $ echo $[1+1]
    2

    10.3 bash อยู่ที่ไหน

    บรรทัดเริ่มต้นของสคริปต์ หลังเครื่องหมาย #! (hash-bang) เราต้องใส่พาธของโปรแกรม bash ให้เต็ม
    สำหรับเดเบียน อยู่ที่ /bin/bash อยู่แล้ว แต่หากเป็นดิสโตรอื่น อาจค้นหาว่าโปรแกรม bash อยู่ที่ไหน โดยใช้คำสั่งเหล่านี้

    $ which bash
    $ whereis bash
    $ find / -name bash

    10.4 ดูค่าที่โปรแกรมส่งออกมา

    หลายโปรแกรมของเชลล์มีการส่งค่าออกมา (Return value) อาจเพื่อแจ้งสถานะการรันว่ารันสำเร็จหรือไม่อย่างไร หรืออาจส่งออกเป็นค่าที่จะนำไปประมวลผลต่อก็ตาม เราสามารถใช้ตัวแปรพิเศษ $? ในการดูผลลัพธ์ของโปรแกรมได้
    เช่น

    #!/bin/bash
    cd /dada &> /dev/null
    echo rv: $?
    cd $(pwd) &> /dev/null
    echo rv: $?

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

    10.5 จับการแสดงผลใส่ตัวแปร

    เราสามารถนำผลลัพธ์ของโปรแกรมมาใส่ในตัวแปร ด้วยการสั่งภายใต้เครื่องหมาย ` (grave accent)
    เช่น

    #!/bin/bash
    DBS=`mysql -u root  -e "show databases"`
    for b in $DBS ;
    do
        mysql -u root -e "show tables from $b"
    done

    เป็นการนำผลลัพธ์ของคำสั่งแรกคือ mysql -u root -e "show databases" มาใส่ในตัวแปร DBS เพื่อทำเป็นขอบเขตให้กับตัวแปร b ในคำสั่ง for อีกครั้งหนึ่ง
    ตามตัวอย่างจะแสดงผลทุกตารางในทุกฐานข้อมูลของ mysql

    11. ตัวดำเนินการ (operators) และคำสั่งน่าสนใจ

    11.1 ตัวดำเนินการเปรียบเทียบตัวอักษร (String comparison operators)

    • [ "$s1" = "$s2" ] หรือ [ "$s1" == "$s2" ] เป็นจริง ถ้า s1 เท่ากับ s2
    • [ "$s1" != "$s2" ] เป็นจริง ถ้า s1 ไม่เท่ากับ s2
    • [[ "$s1" < "$s2" ]] หรือ [ "$s1" \< "$s2" ] เป็นจริง ถ้า s1 น้อยกว่า s2
    • [[ "$s1" > "$s2" ]] หรือ [ "$s1" \> "$s2" ] เป็นจริง ถ้า s1 มากกว่า s2
    • [ -n "$s1" ] เป็นจริง ถ้า s1 มีค่าใด ๆ
    • [ -z "$s1" ] เป็นจริง ถ้า s1 ไม่มีค่า

    11.2 ตัวอย่างการเปรียบเทียบอักษร

    #!/bin/bash
    S1='string'
    S2='String'
    if [ "$S1"="$S2" ]; then
        echo "S1('$S1') is not equal to S2('$S2')"
    fi
    if [ "$S1"="$S1" ]; then
        echo "S1('$S1') is equal to S1('$S1')"
    fi

    11.3 ตัวดำเนินการทางคณิตศาลตร์ (Arithmetic operators)

    • + การบวก
    • - การลบ
    • * การคูณ
    • / การหาร
    • % การหาเศษจากตัวหาร (remainder)

    11.4 ตัวเปรียบเทียบทางคณิตศาตร์ (Arithmetic relational operators

    • -lt น้อยกว่า (<)
    • -gt มากกว่า (>)
    • -le น้อยกว่าหรือเท่ากับ (<=)
    • -ge มากกว่าหรือเท่ากับ (>=)
    • -eq เท่ากับ (==)
    • -ne ไม่เท่ากับ (!=)

    11.5 คำสั่งควรรู้

    sed (stream editor)
    sed เป็นเอดิเตอร์แบบบรรทัดคำสั่ง มีการใช้งานที่พลิกแพลงหลากหลายมาก ตัวอย่าง
    $ sed 's/old/new/g' /tmp/dummy

    นำเอาเนื้อไฟล์ /tmp/dummy มาแทนที่ old ด้วย new และแสดงออกทางจอภาพ

    $ sed 12,18d /tmp/dummy

    นำเอาเนื้อไฟล์ /tmp/dummy มาแสดงทางจอภาพ โดยเว้นไม่แสดงบรรทัดที่ 12 ถึงบรรทัดที่ 18

    ดูรายละเอียดเพิ่มเติมได้ที่ gentoo: Sed by example

    awk (manipulation of datafiles, text retrieval and processing)
    awk เป็นทั้งโปรแกรมและภาษาในการค้นหาข้อความในไฟล์จากรูปแบบที่เรากำหนดให้
    สมมุติว่าไฟล์ /tmp/dummy มีเนื้อไฟล์คือ
    test123
    test
    tteesstt

    ตัวอย่างการใช้งานคือ

    $ awk '/test/ {print}' /tmp/dummy
    test123
    test 

    ดูรายละเอียดเพิ่มเติมได้ที่ gentoo: Awk by example

    grep (print lines matching a search pattern)
    grep เป็นโปรแกรมที่ใช้บ่อยในการค้นข้อความในไฟล์ และยังมีความสามารถในการสรุปผลการนับข้อความด้วย
    ตัวอย่าง
    $ man grep | grep "standard" -c
    8

    เป็นการค้นคำว่า standard ในการแสดงผลของคำสั่ง man grep ว่ามีอยู่กี่คำ คำตอบคือ 8

    ดูตัวอย่างเพิ่มเติมที่ tdlp: Examples using grep

    wc (counts lines, words and bytes)
    wc ใช้ในการนับคำ, นับบรรทัด และนับจำนวนหน่วยความจำที่ถูกใช้ในไฟล์ เป็นไบต์
    ตัวอย่าง
    $ wc --words --lines --bytes /tmp/dummy
     3  3 22 /tmp/dummy
    sort (sort lines of text files)
    sort ใช้จัดเรียงข้อมูล
    สมมุติว่าไฟล์ /tmp/dummy มีเนื้อว่า
    b
    c
    a

    ตัวอย่างคำสั่งคือ

    $ sort /tmp/dummy
    a
    b
    c

    คือการนำเอาเนื้อไฟล์ /tmp/dummy มาจัดเรียง และแสดงผลออกทางจอภาพ

    bc (a calculator programming language)
    bc เป็นเครื่องคิดเลขแบบใช้บรรทัดคำสั่ง
    ตัวอย่างเช่น
    $ echo 1+1
    1+1
    $ echo 1+1 | bc
    2

    หรือใช้แบบโต้ตอบ

    $ bc -q
    1 == 5
    0
    
    0.05 == 0.05
    1
    
    5 != 5
    0
    
    2 ^ 8
    256
    
    sqrt(9)
    3
    
    while (i != 9) {
    i = i + 1;
    print i
    }
    123456789
    
    quit
    
    tput (initialize a terminal or query terminfo database)
    tput ใช้ในการตั้งค่าหรือแสดงค่าต่าง ๆ ของเทอร์มินัล
    เช่น
    $ tput cup 10 4

    เลื่อนเคอร์เซอร์ไปยังบรรทัดที่ 10 สดมภ์ที่ 4

    $ tput reset

    ล้างจอภาพ มีค่าเท่ากับคำสั่ง clear

    $ tput cols

    แสดงจำนวนสดมภ์ (ความกว้าง) ของจอเทอร์มินัล

    12.ตัวอย่างสคริปต์

    12.1 ตัวอย่างสคริปต์ดูรายชื่อไฟล์ในไดเรคทอรี่ย่อย

    #!/bin/bash 
    function listdir {
        local PAT="$1"
        local ROOT="$2"
        for i in *; do
            if [ -d "$i" ]; then
                local CUR="$ROOT/$i"
                pushd "$i" &>/dev/null
                listdir "$PAT" "$CUR"
                popd &>/dev/null
            fi
        done
        if [ ! -z "$( ls -d $PAT 2>/dev/null )" ]; then
            echo "Directory: $ROOT"
            ls -d $PAT 2>/dev/null
            echo 
        fi
    }
    
    if [ -z "$1" ]; then
       echo List file in PATTERN recursive into directories.
       echo Usage: $0 "PATTERN"
       exit
    fi
    PATTERN="$1"
    echo "List $PATTERN"
    listdir "$PATTERN" "."

    ให้ผลคล้ายคำสั่ง

    $ find * -name PATTERN

    12.2 ตัวอย่างสคริปต์บีบอัดสำรองข้อมูล

    #!/bin/bash          
    SRCD="/home/"
    TGTD="/var/backups/"
    OF=home-$(date +%Y%m%d).tgz
    tar -cZf $TGTD$OF $SRCD

    12.3 เปลี่ยนชื่อไฟล์ทีละหลายไฟล์

    #!/bin/sh
    # renna: rename multiple files according to several rules
    # written by felix hudson Jan - 2000
    
    #first check for the various 'modes' that this program has
    #if the first ($1) condition matches then we execute that portion of the
    #program and then exit
    
    # check for the prefix condition
    if [ $1 = p ]; then
    
    #we now get rid of the mode ($1) variable and prefix ($2)
      prefix=$2 ; shift ; shift
    
    # a quick check to see if any files were given
    # if none then its better not to do anything than rename some non-existent
    # files!!
    
      if [$1 = ]; then
         echo "no files given"
         exit 0
      fi
    
    # this for loop iterates through all of the files that we gave the program
    # it does one rename per file given
      for file in $*
        do
        mv ${file} $prefix$file
      done
    
    #we now exit the program
      exit 0
    fi
    
    # check for a suffix rename
    # the rest of this part is virtually identical to the previous section
    # please see those notes
    if [ $1 = s ]; then
      suffix=$2 ; shift ; shift
    
       if [$1 = ]; then
        echo "no files given"
       exit 0
       fi
    
     for file in $*
      do
       mv ${file} $file$suffix
     done
    
     exit 0
    fi
    
    # check for the replacement rename
    if [ $1 = r ]; then
    
      shift
    
    # i included this bit as to not damage any files if the user does not specify
    # anything to be done
    # just a safety measure
    
      if [ $# -lt 3 ] ; then
        echo "usage: renna r [expression] [replacement] files... "
        exit 0
      fi
    
    # remove other information
      OLD=$1 ; NEW=$2 ; shift ; shift
    
    # this for loop iterates through all of the files that we give the program
    # it does one rename per file given using the program 'sed'
    # this is a sinple command line program that parses standard input and
    # replaces a set expression with a give string
    # here we pass it the file name ( as standard input) and replace the nessesary
    # text
    
      for file in $*
      do
        new=`echo ${file} | sed s/${OLD}/${NEW}/g`
        mv ${file} $new
      done
    exit 0
    fi
    
    # if we have reached here then nothing proper was passed to the program
    # so we tell the user how to use it
    echo "usage;"
    echo " renna p [prefix] files.."
    echo " renna s [suffix] files.."
    echo " renna r [expression] [replacement] files.."
    exit 0
    
    # done!

    12.4 เปลี่ยนชื่อไฟล์แบบง่าย

    #!/bin/bash
    # renames.sh
    # basic file renamer
    
    criteria=$1
    re_match=$2
    replace=$3
    
    for i in $( ls *$criteria* ); 
    do
        src=$i
        tgt=$(echo $i | sed -e "s/$re_match/$replace/")
        mv $src $tgt
    done

    13. การค้นหาที่ผิดในสคริปต์

    เราใช้พารามิเตอร์ -x ต่อท้ายคำสั่งในบรรทัดแรก

    #!/bin/bash -x

    จะมีผลว่าเชลล์จะแสดงทุกคำสั่งที่ถูกรันออกมาทางจอภาพ

    จบแล้วจ้า

    bash: แปลงชื่อไฟล์เป็นตัวเล็ก

    ต้องการแปลงชื่อไฟล์เป็นตัวเล็ก ค้นไปค้นมาปรากฎว่ามีอยู่ในแพกเกจ bash-doc เป็นตัวอย่างอยู่แล้ว

    ต้องติดตั้งก่อน
    $ sudo aptitude install bash-doc

    ขมายมาใช้
    $ sudo cp /usr/share/doc/bash-doc/examples/scripts.v2/lowercase /usr/local/bin
    $ sudo chmod 755 /usr/local/bin/lowercase

    เรียกใช้ด้วยคำสั่ง lowercase $FILENAME

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

    ถ้าจะแปลงเป็นตัวใหญ่ก็แค่กลับ จาก tr A-Z a-z ไปเป็น tr a-z A-Z ก็เสร็จแล้ว
    ทำเป็นตัวอย่างคือ
    $ sudo vi /usr/local/bin/uppercase

    #! /bin/bash
    #
    # original from
    # @(#) lowercase.ksh 1.0 92/10/08
    # 92/10/08 john h. dubois iii (john@armory.com)
    #
    # conversion to bash v2 syntax done by Chet Ramey
    
    Usage="Usage: $name file ..."
    phelp()
    {
    echo "$name: change filenames to upper case.
    $Usage
    Each file is moved to a name with the same directory component, if any,
    and with a filename component that is the same as the original but with
    any lower case letters changed to upper case."
    }
    
    name=${0##*/}
    
    while getopts "h" opt; do
        case "$opt" in
        h)  phelp; exit 0;;
        *)  echo "$Usage" 1>&2; exit 2;;
        esac
    done
    
    shift $((OPTIND - 1))
    
    for file; do
        filename=${file##*/}
        case "$file" in
        */*)    dirname=${file%/*} ;;
        *)    dirname=. ;;
        esac
        nf=$(echo $filename | tr a-z A-Z)
        newname="${dirname}/${nf}"
        if [ "$nf" != "$filename" ]; then
        mv "$file" "$newname"
        echo "$0: $file -> $newname"
        else
        echo "$0: $file not changed."
        fi
    done
    

    $ sudo chmod 755 /usr/local/bin/uppercase

    เสร็จแล้ว เรียกใช้ด้วยคำสัง uppercase $FILENAME

    debian: Headless Nvidia for Cryptomining

    ลองศึกษาการขุดเหมืองเงินคริปโตด้วยการ์ดจอ Nvidia

    ปัญหาคือทดลองในเซิร์ฟเวอร์ที่ไม่ได้ต่อจอภาพไว้ และไม่ได้ลง Xserver ไว้ การใช้งานผ่าน ssh จึงปรับแต่งอะไรไม่ได้เลย การใช้ค่าที่ติดตั้งมาจากโรงงาน ทำให้การ์ดร้อนจัดจนใช้งานจริงไม่ได้
    ปัญหาต่อมาคือไดรเวอร์บนลินุกซ์ล้าสมัยเล็กน้อย ใช้กับการ์ดจอใหม่ ๆ ไม่ค่อยได้ หรือใช้ได้แต่ไม่ดีพอ
    (ระบบปฏิบัติการที่ใช้ คือ debian stretch)

    ทางแก้คือ

    แก้ปัญหาใช้งาน nvidia-settings ผ่าน ssh

    ทำที่เครื่องเซิร์ฟเวอร์ที่ติดตั้งการ์ด Nvidia ไว้

    ก่อนอื่น ถือว่าเราได้ลงไดรเวอร์ของ Nvidia มาแล้ว ด้วยคำสั่งดังนี้

    $ sudo apt install nvidia-driver nvidia-smi nvidia-settings nvidia-xconfig
    

    ต้องลง Xorg และเพื่อให้สะดวกในการเริ่มต้นและออกจาก X จึงติดตั้ง desktop manager ตัวเล็กไว้ด้วย คือ lightdm

    $ sudo apt install lightdm
    

    แก้ไข xorg.conf ให้สามารถใช้งานการปรับแต่งการ์ดจอได้ แล้วก็เริ่ม Xserver ใหม่ด้วยคำสั่ง

    $ sudo nvidia-xconfig -a --cool-bits=31 --allow-empty-initial-configuration
    $ sudo systemctl restart lightdm
    

    ทำที่เครื่องลูก ที่ใช้งาน ssh
    ssh เข้าไปที่เซิร์ฟเวอร์ ทำการ merge ค่า $DISPLAY หลักของ X ซึ่งก็คือของการ์ดจอ เข้ากับค่า $DISPLAY ของเราที่มาจาก ssh แล้วก็เรียกใช้ nvidia-settings ผ่านค่าดิสเพลย์หลัก

    $ ssh -X root@SERVER
    
    # xauth merge /var/run/lightdm/root/\:0
    # nvidia-settings -c :0
    

    จะสามารถใช้งานการปรับแต่งได้ตามต้องการ โดยตรวจดูค่าที่ปรับแต่งแล้ว ด้วยคำสั่ง nvidia-smi

    ที่มา

    แก้ปัญหาไดรเวอร์ลินุกซ์ล้าสมัย ด้วยการคอมไพล์รุ่นใหม่บน svn เอง

    ติดตั้งแพ็คเกจที่ใช้งาน

    $ sudo apt install subversion svn-buildpackage
    $ sudo apt build-dep nvidia-driver
    

    ดาวน์โหลดรุ่นใหม่จาก svn และคอมไพล์
    *** อย่าลืมแก้ค่าในตัวแปร $TARGET ให้เป็นไดเรคทอรี่ที่เราต้องการ ***

    $ TARGET=nvidia-graphics-drivers; mkdir $TARGET; cd $TARGET
    $ svn co svn://anonscm.debian.org/pkg-nvidia/packages/nvidia-graphics-drivers/branches/384
    $ cd 384
    $ debian/rules get-orig-source
    $ mkdir ../tarballs
    $ mv nvidia-graphics-drivers* ../tarballs
    $ svn-buildpackage --svn-ignore -us -uc -rfakeroot
    

    จะได้ไฟล์แพ็คเกจนามสกุล .deb มาอยู่ในไดเรคทอรี่ ../build-area
    ต่อไปก็สร้าง local repository จากไดเรคทอรี่ดังกล่าว ทำการแก้ไข apt/source ลบไดรเวอร์เก่า (ถ้ามี) แล้วก็ติดตั้งของใหม่

    $ cd ../build-area
    $ dpkg-scanpackages . /dev/null > Packages
    $ sudo vi /etc/apt/sources.list.d/nvidia-384.list
    
    deb [ trusted=yes ] file:$TARGET/nvidia-graphics-drivers/build-area ./
    
    $ sudo systemctl stop lightdm
    $ sudo apt-get remove ^nvidia ^libnvidia
    $ sudo apt update
    $ sudo apt install nvidia-driver nvidia-smi nvidia-settings nvidia-xconfig
    

    บูตเครื่องใหม่ ก็จะสามารถใช้งานไดรเวอร์รุ่นใหม่ได้แล้ว

    ที่มา

    จบ

    Topic: 

    debian: Headless Nvidia for Cryptomining II

    ภาคต่อของการขุดเหมืองโดยใช้การ์ดจอ Nvidia

    จากครั้งก่อน การตั้งค่าของการ์ด ต้องตั้งค่าใหม่ทุกครั้งเมื่อบูตใหม่ (จริง ๆ สามารถบันทึกค่าเดิมไว้ แล้วนำมาใช้ใหม่ได้ แต่ไม่สะดวกสำหรับงาน console)
    คราวนี้จะทำให้มีความเป็นอัตโนมัติมากขึ้น โดยไม่ต้องมาตั้งผ่าน Display Manager (lightdm) ดังนั้นจึงไม่ต้องลง lightdm แบบครั้งก่อนแล้ว

    เราจะใช้โปรแกรมและไฟล์ EDID ของคุณ ฺBoris Dimitrov ในการจำลองจอเสมือน และปรับความเร็วพัดลมให้แปรผันตามอุณหภูมิ แต่เนื่องจากดูเหมือนโปรแกรมเดิมใช้งานได้แค่การ์ดใบเดียว และต้องลง tcsh ซึ่งไม่ใช่เชลล์ปริยายของเดเบียน เราจึงเอาของเดิมมาปรับปรุงเล็กน้อยดังนี้

    • ให้ใช้กับ bash ได้
    • ใช้กับการ์ดหลายใบได้
    • ปรับให้พัดลมทำงานรอบจัดมากขึ้น เพื่อให้ GPU เย็นลง
    • เอื้อให้ง่ายในการเร่งการทำงานของ GPU และหน่วยความจำ
    • ปรับลดการกินพลังงานลงมาเหลือ 80%
    • งานอื่น ๆ ให้ใช้งานได้คล่องตัวขึ้น

    เนื่องจากเป้าหมายคือความเป็นอัตโนมัติมากขึ้น จึงขอใช้งานด้วยสิทธิ์ root ตั้งแต่ต้น เพื่อให้สะดวกในการปรับแต่ง ดังนั้นจึงต้องระวังในการทำงานหน่อย

    เริ่มด้วย ถ้าเดิมลง lightdm ไว้แล้ว ให้ถอดออกก่อน

    # apt purge lightdm
    

    ถ้าเดิมยังไม่ได้ลงไดรเวอร์ ก็ลงก่อน

    # apt install nvidia-driver nvidia-smi nvidia-settings
    

    ลงของจำเป็นก่อน และดาวน์โหลดโปรแกรมมาใช้

    # apt install awk git
    # mkdir -p /PATH/TO/SOURCE/
    # cd /PATH/TO/SOURCE
    # git clone https://github.com/widhaya3/set_gpu_fans_public.git
    # cd set_gpu_fans_public
    

    เราอาจสั่งรันตรง ๆ ได้จากในนี้เลย หรืออาจนำไปติดตั้งในไดเรคทอรี่ /opt/set_gpu_fans ตามตัวอย่าง README.md ก็ได้
    แต่เพื่อให้คล้ายกับต้นฉบับ เราจะสร้างลิงก์ไปที่ /opt แล้วสั่งรันจากที่นั่น และเผื่อต้นฉบับมีการปรับปรุง เราก็สามารถสั่ง git pull เพื่ออัปเดตโปรแกรมล่าสุดได้เลย

    # mkdir -p /opt
    # cd /opt
    # ln -sf /PATH/TO/SOURCE/set_gpu_fans_public set_gpu_fans
    # /opt/set_gpu_fans/cool_gpu
    

    เสร็จแล้ว
    หลังรันครั้งแรก โปรแกรมจะสร้างไฟล์คอนฟิกไว้ที่ /etc/set_gpu_fans_nv/ และไฟล์ปูม (log) ไว้ที่ /var/log/set_gpu_fans_nv-X มีรายละเอียดคือ

    • ไฟล์คอนฟิก /etc/set_gpu_fans_nv/nvscmd_exmod_once-0.cfg ไฟล์นี้จะถูกสั่งรันแค่ครั้งเดียวตอนเริ่มรัน
    • ไฟล์คอนฟิก /etc/set_gpu_fans_nv/nvscmd_exmod_loop-0.cfg ถูกรันทุกครั้งที่มีการเปลี่ยนความเร็วพัดลม
    • ไฟล์ปูม /var/log/set_gpu_fans_nv-0 บันทึกปูมของการ์ดใบแรก

    ถ้ามีการ์ดหลายใบ เลขลงท้ายจะเพิ่มไปเป็น -1,-2,-3 ไปเรื่อย
    เราจะดูผลงานและปรับแต่งภายหลังได้จากคำสั่ง nvidia-smi
    ถ้าปรับแต่งจนใช้งานได้ดีแล้ว ก็เอาคำสั่งไปใส่ไว้ในไฟล์ /etc/rc.local เพื่อให้เริ่มงานตอนเปิดเครื่องได้เลย

    debian: web server

    บันทึกเรื่อง Web Server

    Topic: 

    debian: Web Server PHP Benchmark

    ได้ยินว่า nginx เป็น web server ที่จ่าย static content ได้เร็วจัดและกินหน่วยความจำน้อย แต่ด้อยเรื่อง dynamic content
    จึงสงสัยต่อว่า แล้วถ้าเทียบกับ apache2-mpm-worker ล่ะ ความเร็วจะเป็นอย่างไร
    เลยลองทำ benchmark ของ dynamic content แบบง่าย ๆ เทียบระหว่าง 3 ตัว (ได้เอา cache ของ php คือ php-apc มาร่วมทดสอบด้วย) โดยเลียนแบบสถานการณ์จริง คืออ่าน node ของ drupal จำนวน 100 nodes

    กำหนด
    • hardware: E2140 RAM 4G
    • debian: lenny
    • kernel: 2.6.26-2-xen-amd64
    • apache2: 2.2.9-10+lenny4
    • nginx: 0.6.32-3
    • mysql-server: 5.0.51a-24+lenny1

    คำสั่งติดตั้งโดยย่อคือ
    1. apache2-mpm-prefork:
      aptitude install phpmyadmin mysql-server-5.0
    2. apache2-mpm-worker:
      aptitude install phpmyadmin apache2-mpm-worker libapache2-mod-fcgid php-cgi mysql-server-5.0
    3. nginx:
      aptitude install phpmyadmin nginx php-cgi lighttpd
    การปรับแต่งดูจากอ้างอิงท้ายเรื่อง

    การทดสอบคือ
    BENCHMARK 1: อ่าน node ของ drupal แบบต่อเนื่อง จำนวน 100 nodes
    #!/bin/bash
    SERVER='192.168.1.1'
    time $(for i in `seq 101 200`; do wget -q -O- http://$SERVER/?q=node/$i; done)
    

    และ
    BENCHMARK 2: อ่าน node ของ drupal จำนวน 100 nodes แบบพร้อมกัน
    #!/bin/bash
    SERVER='192.168.1.1'
    time $(
        for i in `seq 101 200`; do 
            wget -q -O- http://$SERVER/?q=node/$i & 
        done
    wait)
    


    ได้ผลดังนี้
    BENCH1 – sequential get 100 nodes (secs) 1 2 3 4 5 avg real
    First run – apache2-mpm-prefork / mod_php5 35.74 33.8 31.9 34.79 34.81 34.21
    Second run – apache2-mpm-prefork / mod_php5 29.46 29.49 29.52 29.52 29.51 29.5 31.85
    First run – apache2-mpm-prefork / mod_php5 / php-apc 18.37 15.78 19.24 19.49 19.77 18.53
    Second run – apache2-mpm-prefork / mod_php5 / php-apc 13.04 13.13 13.06 13.61 13.12 13.19 15.86
    First run – apache2-mpm-worker / mod_fcgi 35.48 35.88 35.29 34.54 34.8 35.2
    Second run – apache2-mpm-worker / mod_fcgi 29.08 29.13 29.05 29.06 29.15 29.09 32.14
    First run – apache2-mpm-worker / mod_fcgi / php-apc 15.06 18.22 18.86 18.85 14.23 17.04
    Second run – apache2-mpm-worker / mod_fcgi / php-apc 12.8 12.97 12.9 12.89 12.8 12.87 14.96
    First run – nginx / spawn_fcgi 33.96 33.71 34.7 33.53 33.93 33.97
    Second run – nginx / spawn_fcgi 28.94 28.78 28.85 28.81 28.93 28.86 31.41
    First run – nginx / spawn_fcgi / php-apc 19.08 17.96 19.07 18.34 18.82 18.65
    Second run – nginx / spawn_fcgi / php-apc 13.08 13.03 13 13.1 13.08 13.06 15.86
















    BENCH2 – concurrent get 100 nodes (secs) 1 2 3 4 5 avg real
    First run – apache2-mpm-prefork / mod_php5 21.98 21.34 21.6 21.49 21.48 21.58
    Second run – apache2-mpm-prefork / mod_php5 19.28 15.14 12.53 19.14 18.9 17 19.29
    First run – apache2-mpm-prefork / mod_php5 / php-apc 11.61 11.99 11.77 11.75 11.93 11.81
    Second run – apache2-mpm-prefork / mod_php5 / php-apc 9.71 9.73 9.52 9.06 9.9 9.58 10.7
    First run – apache2-mpm-worker / mod_fcgi 20.89 20.93 21.33 21.23 21.43 21.16
    Second run – apache2-mpm-worker / mod_fcgi 19.47 19.36 19.39 19.33 19.35 19.38 20.27
    First run – apache2-mpm-worker / mod_fcgi / php-apc 14.33 14.02 14.21 14.32 14.35 14.25
    Second run – apache2-mpm-worker / mod_fcgi / php-apc 11.5 11.37 11.57 11.77 11.12 11.47 12.86
    First run – nginx / spawn_fcgi 18.04 20.16 21.28 21.22 21.36 20.41
    Second run – nginx / spawn_fcgi 17.38 17.47 20.88 20.36 18.37 18.89 19.65
    First run – nginx / spawn_fcgi / php-apc 11.26 11.15 11.18 11.25 11.18 11.2
    Second run – nginx / spawn_fcgi / php-apc 10.56 10.59 10.56 10.57 10.58 10.57 10.89



    สรุปว่า ได้ผลใกล้เคียงกันมาก

    หมายเหตุ ได้ทดสอบ php5-xcache ไว้ด้วย แต่ผลเหมือนกับ php-apc จึงไม่ได้แสดงผลไว้

    อ้างอิง
    Topic: 

    debian: Web Server PHP Benchmark 2

    ต้องการเปลี่ยนเครื่องเซิร์ฟเวอร์ที่ใช้รัน Drupal จึงทดลองทดสอบเปรียบเทียบ Web Server และโปรแกรมที่ใช้รัน php รุ่นต่าง ๆ ไว้ดังนี้

    1. apache2-mpm-prefork libapache2-mod-php5
    2. apache2-mpm-worker libapache2-mod-fcgid php5-cgi
    3. lighttpd php5-cgi
    4. nginx php5-cgi spawn-fcgi

    สมบัติ

    • เครื่อง: cpu E5200 ram 2G
    • os: Debian Squeeze 2.6.32-5-xen-amd64
    • mysql-server: 5.1.49-3
    • apache2: 2.2.16-4
    • lighttpd: 1.4.28-1
    • nginx: 0.7.67-3
    • php-apc: 3.1.3p1-2
    • สมมุติว่าลง Drupal ไว้ที่ /var/www/example.com/drupal

    วิธี

    ทำสคริปต์เรียก node ของ Drupal จากเซิร์ฟเวอร์ จำนวน 200 node โดยแบ่งเป็นแบบลำดับและแบบขนาน

    1. แบบลำดับ
      #!/bin/bash
      SERVER='www.example.com'
      time $(for i in `seq 101 300`;do wget -q -O- http://$SERVER/?q=node/$i; done)
      
    2. แบบขนาน
      #!/bin/bash
      SERVER='www.example.com'
      time $(
          for i in `seq 101 300`; do 
              wget -q -O- http://$SERVER/?q=node/$i & 
          done
      wait)
      

    รัน 5 ครั้งแล้วดูค่าเฉลี่ย และได้เปรียบเทียบระหว่างไม่มีแคช กับมีแคชด้วย โดยใช้แพ็กเกจ php-apc

    ผลการทดลอง

    ได้ผลดังนี้

    BENCH1 - sequential get 200 nodes (secs) 1 2 3 4 5 avg
    apache2-mpm-prefork mod_php5 7.277 6.701 6.523 6.549 6.528 6.7156
    apache2-mpm-prefork mod_php5 php-apc 4.779 4.524 4.514 4.339 4.104 4.452
    apache2-mpm-worker mod_fcgi 7.376 6.618 6.595 6.596 6.58 6.753
    apache2-mpm-worker mod_fcgi php-apc 4.718 4.507 4.547 4.094 4.1 4.3932
    lighttpd php5-cgi 7.178 7.111 6.484 6.439 6.481 6.7386
    lighttpd php5-cgi php-apc 4.575 4.423 4.046 4.097 4.093 4.2468
    nginx php5-cgi 7.139 6.394 6.411 6.395 6.4 6.5478
    nginx php5-cgi php-apc 4.667 4.066 4.049 4.06 4.05 4.1784
    BENCH2 - concurrent get 200 nodes (secs) 1 2 3 4 5 avg
    apache2-mpm-prefork mod_php5 5.465 5.491 5.912 5.357 5.354 5.5158
    apache2-mpm-prefork mod_php5 php-apc 5.142 5.204 5.195 5.067 5.078 5.1372
    apache2-mpm-worker mod_fcgi 7.031 4.272 4.259 4.247 4.271 4.816
    apache2-mpm-worker mod_fcgi php-apc 5.686 2.982 2.956 2.911 2.918 3.4906
    lighttpd php5-cgi 4.771 4.759 4.657 4.18 4.15 4.5034
    lighttpd php5-cgi php-apc 3.211 3.102 3.116 3.09 2.906 3.085
    nginx php5-cgi 4.207 3.871 4.117 4.131 3.97 4.0592
    nginx php5-cgi php-apc 2.801 2.824 2.975 2.857 2.399 2.7712

    การติดตั้ง

    ลงวิธีติดตั้งแบบหยาบ ๆ ไว้เพื่ออ้างอิง
    (แพ็กเกจ phpmyadmin ลงไว้เพื่อให้เดเบียนหา dependencies ให้)

    1. apache2-mpm-prefork libapache2-mod-php5

    ติดตั้ง

    # aptitude install phpmyadmin

    ปรับ apache2

    # vi /etc/apache2/site-enabled/000-default
    <VirtualHost *:80>
        ...
        #DocumentRoot /var/www
        DocumentRoot /var/www/example.com/drupal
        ...
        #<Directory /var/www/>
        <Directory /var/www/example.com/drupal/>
            Options Indexes FollowSymLinks MultiViews
            DirectoryIndex index.html index.php
            #AllowOverride None
            AllowOverride All
            Order allow,deny
            allow from all
        </Directory>
        ...
    </VirtualHost>
    

    เริ่มใหม่

    # /etc/init.d/mysql restart; /etc/init.d/apapche2 restart

    (ถอดถอนด้วยคำสั่ง aptitude purge phpmyadmin apache2-mpm-prefork libapache2-mod-php5 apache2.2-common)

    2. apache2-mpm-worker libapache2-mod-fcgid php5-cgi

    ติดตั้ง

    # aptitude install phpmyadmin apache2-mpm-worker libapache2-mod-fcgid php5-cgi

    ปรับ apache2

    # vi /etc/apache2/site-enabled/000-default
    <VirtualHost *:80>
        ...
        #DocumentRoot /var/www
        DocumentRoot /var/www/example.com/drupal
        ...
        #<Directory /var/www/>
        <Directory /var/www/example.com/drupal/>
            Options Indexes FollowSymLinks MultiViews
            DirectoryIndex index.html index.php
            AddHandler fcgid-script .php
            FCGIWrapper /usr/lib/cgi-bin/php5 .php
            Options +ExecCGI
            #AllowOverride None
            AllowOverride All
            Order allow,deny
            allow from all
        </Directory>
        ...
    </VirtualHost>
    

    เริ่มใหม่

    # /etc/init.d/mysql restart; /etc/init.d/apapche2 restart

    (ถอดถอนด้วยคำสั่ง aptitude purge phpmyadmin apache2-mpm-worker libapache2-mod-fcgid php5-cgi)

    3. lighttpd php5-cgi

    ติดตั้ง

    # aptitude install phpmyadmin lighttpd php5-cgi

    ปรับ lighttpd

    # vi /etc/lighttpd/lighttpd.conf
    ...
    #server.document-root        = "/var/www"
    server.document-root        = "/var/www/example.com/drupal"
    ...
    fastcgi.server = ( ".php" => ((
                         "bin-path" => "/usr/bin/php5-cgi",
                         "socket" => "/tmp/php.socket"
                     )))
    ...
    

    เริ่มใหม่

    # /etc/init.d/mysql restart; /etc/init.d/lighttpd restart

    (ถอดถอนด้วยคำสั่ง aptitude purge phpmyadmin lighttpd php5-cgi)

    4. nginx php5-cgi spawn-fcgi

    ติดตั้ง

    # aptitude install phpmyadmin nginx php5-cgi spawn-fcgi

    ปรับ spawn-fcgi

    # cp /usr/share/doc/spawn-fcgi/run-php /usr/local/bin/spawn-fcgi-run-php
    # vi /usr/local/bin/spawn-fcgi-run-php
    ...
    #exec /usr/bin/spawn-fcgi -n -s /var/run/lighttpd/php-xxx.sock -n -u xxx -U www-data -- /usr/bin/php5-cgi
    exec /usr/bin/spawn-fcgi -n -s /var/run/php-www-data.sock -n -u www-data -U www-data -- /usr/bin/php5-cgi
    ...
    
    # sh /usr/local/bin/spawn-fcgi-run-php &

    (ถ้าทำ server จริง ให้เอาคำสั่งนี้ไปใส่ใน /etc/rc.local ตรงก่อน exit 0 แล้วเปิดเครื่องใหม่)

    ปรับ nginx

    # vi /etc/nginx/sites-enabled/default
    ...
    server {
    ...
        #server_name  localhost;
        server_name  _;
    ...
        location / {
            #root   /var/www;
            root   /var/www/example.com/drupal;
            #index  index.html index.htm;
            index  index.html index.htm index.php;
        }
    ...
        #location ~ \.php$ {
            #fastcgi_pass   127.0.0.1:9000;
            #fastcgi_index  index.php;
            #fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
            #includefastcgi_params;
        #}
        location ~ \.php$ {
            #fastcgi_pass   127.0.0.1:9000;
            fastcgi_pass   unix:/var/run/php-www-data.sock;
            fastcgi_index  index.php;
            #fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
            fastcgi_param  SCRIPT_FILENAME  /var/www/example.com/drupal/$fastcgi_script_name;
            #includefastcgi_params;
            include fastcgi_params;
        }
    ..
    

    เริ่มใหม่

    # killall php5-cgi; sh /usr/local/bin/spawn-fcgi-run-php &; /etc/init.d/mysql restart; /etc/init.d/nginx restart

    (ถอดถอนด้วยคำสั่ง aptitude purge phpmyadmin nginx php5-cgi spawn-fcgi)

    อ้างอิง

    debian: ติดตั้งฮาร์ดแวร์

    บันทึกการติดตั้งฮาร์ดแวร์

    ติดตั้งเครื่องพิมพ์ให้เครือข่าย

    # aptitude install cupsys cupsys-bsd \
    cupsys-client cupsys-common cupsys-dbg \
    cupsys-driver-gimpprint \
    cupsys-driver-gutenprint

    Do you want to set up the BSD lpd compatibility server?
    <<<--- No

    ปรับตั้ง cupsys
    # dpkg-reconfigure cupsys
    Do you want CUPS to print unknown jobs as raw jobs?
    <<<--- Yes
    Printer communication backends:
    <<<--- [DEFAULT]

    ปรับตั้งให้สามารถใช้งานผ่านเว็บ ใช้พอร์ต 631 แบบไม่ต้องล๊อกอิน
    # vi /etc/cups/cupsd.conf

    ...
    #--- Listen localhost:631
    Listen *:631
    ...
    #BrowseAllow @LOCAL
    BrowseAllow all
    ...
    
    <Location />
      Order allow,deny
    # Allow localhost
      Allow all
    </Location>
    
    <Location /admin>
    #  Encryption Required
      AuthType None
      Order allow,deny
    # Allow localhost
      Allow all
    </Location>
    
    <Location /admin/conf>
      AuthType None
      Require user @SYSTEM
      Order allow,deny
    # Allow localhost
      Allow all
    </Location>
    ...

    # /etc/init.d/cupsys restart

    สามารถใช้งานผ่านบราวเซอร์ในเครือข่ายได้แล้ว
    เช่น http://192.168.1.5:631/

    debian: บันทึกเครื่องพิมพ์อิงก์เจ็ต epson

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

    สำหรับเครื่องพิมพ์ Epson เดเบียนจะมีแพกเกจที่ทำหน้าที่ดังกล่าว ชื่อ mtink
    ติดตั้งดังนี้

    ติดตั้งแพกเกจ
    $ sudo aptitude install mtink

    แก้ไขให้เราสามารถใช้งานเครื่องพิมพ์ได้ โดยปรับตั้งให้เราไปอยู่ในกรุ๊ปของเครื่องพิมพ์ คือ lp
    สมมุติว่าเราชื่อ user1
    $ sudo usermod -G lp user1
    หรือถ้ามีชื่อผู้ใช้หลายคน อาจแก้ไขที่ไฟล์ /etc/group โดยตรง
    $ sudo vi /etc/group

    ...
    lp:x:7:cupsys,user1
    ...

    เรียกใช้งานด้วยคำสั่ง mtink

    • อาจต้องล๊อกเอาต์ออกจากระบบหนึ่งครั้ง การปรับตั้งจึงจะสมบูรณ์
    • การตอบสนองของโปรแกรม ค่อนข้างช้า แต่ก็ใช้งานได้ราบรื่นดี
    Topic: 

    debian: สร้างเครื่องพิมพ์ socket ให้ cups

    มีปัญหาเรื่อง cups พิมพ์ผ่านเครือข่ายไม่ได้ เป็นอยู่เครื่องเดียว ไม่ทราบว่าเป็นเพราะอะไร

    แก้ชั่วคราวด้วยการสร้างซอคเก็ตสำหรับรับงานพิมพ์ผ่านเครือข่าย (เผื่อเอาไว้เวลาอัปเกรดรุ่นบนเดเบียนแล้ว cups ชอบตายด้วย)

    เอาความรู้จาก debian: ปรับปรุง HylaFax Client โดยใช้ perl สร้างเครื่องพิมพ์เทียมขึ้นมา โดย

    ที่เครื่องปรินต์เซิร์ฟเวอร์
    สร้างไฟล์ perl รับงานพิมพ์ผ่าน socket
    สมมุติว่าเครื่องพิมพ์ชื่อ brother ติดตั้งไดร์ฟเวอร์ไว้เรียบร้อยแล้ว
    เราจะสร้างสคริปต์สำหรับรับงานพิมพ์ผ่านทาง socket หมายเลข 5692
    # vi /usr/local/bin/brother-print

    #!/usr/bin/perl
    
    # Daniel E. Markle 
    # 20031114 Revision 0.1
    
    # This program starts up a daemon which will watch for a postscript file
    #  coming in from CUPS, then launch pyla to send it as a fax.
    # Use it with the socket://localhost:5691 URL in cups.
    
    # Issues:
    #    -security, make sure only localhost can see this port, it allows anyone
    #      who can stream data to this port to popup pyla on your desktop
    #    -must be ran as the user or the window may not pop up at the right
    #      place, if at all
    
    # ***CONFIGURE ME HERE
    
    # Port to listen on, use socket://localhost: in cups
    my $MY_PORT = 5692;
    
    # Printer name
    my $PRINTER = "brother";
    
    # Temporary file storage, this is a file not a folder
    my $TMP_FILE_NAME = "/tmp/brotherprint";
    
    # ***END CONFIGURATION
    
    # ----- You shouldn't need to touch anything beyond this point -----
    
    use IO::Socket::INET;
    
    # check to make sure we can use the temp file
    open ( TMPFILE, ">", $TMP_FILE_NAME )
       or die "Can not open temp file $TMP_FILE_NAME, " .
       "check to make sure it is not owned by another user";
    close ( TMPFILE );
    
    my $data;
    my $server = IO::Socket::INET->new (
       LocalPort => $MY_PORT,
       Type => SOCK_STREAM,
       Reuse => 1,
       Listen => 1
       ) or die "Could not start server process";
    
    while ( my $job = $server->accept() ) {
       open ( TMPFILE, ">", $TMP_FILE_NAME );
       while ( <$job> ) {
          print TMPFILE $_;
          }
       close ( TMPFILE );
       close $job;
       `lpr -P "$PRINTER" "$TMP_FILE_NAME"`;
       unlink "$TMP_FILE_NAME";
       }
    

    สร้างสคริปต์สำหรับเปิดปิดการใช้งาน
    เอาไว้ที่ /etc/init.d/ (ไม่ค่อยดีเท่าไหร่ ควรเอาไว้ที่อื่นแล้วสร้างลิงก์มาจะดีกว่า)
    # vi /etc/init.d/brother-printd

    #!/bin/sh
    
    PATH=/sbin:/bin:/usr/sbin:/usr/bin
    DAEMON=/usr/local/bin/brother-print
    NAME=brother-print
    DESC="Pseudo printer for Brother"
    
    test -x $DAEMON || exit 0
    
    case "$1" in
            start)
                    echo -n "Starting $DESC: "
    
                    start-stop-daemon --start --quiet --pidfile /var/run/$NAME.pid \
                            --chuid $USER --background --make-pidfile \
                            --exec $DAEMON -- $DAEMON_OPTS
    
                    echo "$NAME."
                    ;;
    
            stop)
                    echo -n "Stopping $DESC: "
    
                    start-stop-daemon --stop --quiet --pidfile /var/run/$NAME.pid \
                            --oknodo
    
                    echo "$NAME."
                    ;;
    
            restart|force-reload)
                    echo -n "Restarting $DESC: "
    
                    start-stop-daemon --stop --quiet --pidfile /var/run/$NAME.pid \
                            --oknodo
    
                    sleep 1
    
                                                                                        
                    start-stop-daemon --start --quiet --pidfile /var/run/$NAME.pid \
                            --chuid $USER --background --make-pidfile \
                            --exec $DAEMON -- $DAEMON_OPTS
    
                    echo "$NAME."
                    ;;
    
            *)
                    N=/etc/init.d/$NAME
                    echo "Usage: $N {start|stop|restart|force-reload}" >&2
                    exit 1
                    ;;
    esac
    
    exit 0
    

    สั่งเปิดใช้งานเมื่อเริ่มเปิดเครื่อง และสั่งเปิดใช้งานเลย
    # update-rc.d brother-printd defaults
    # /etc/init.d/brother-printd start

    ที่เครื่องลูกข่าย
    เปิดใช้งานด้วยบราวเซอร์ http://localhost:631
    ให้ตั้งเครื่องพิมพ์ไปที่ Device URI: socket://server:5692
    โดยใช้ไดร์ฟเวอร์เป็น Generic Postscript Printer

    หรือสั่งด้วยบรรทัดคำสั่งว่า
    $ sudo lpadmin -p brother -E -v socket://server:5692

    เสร็จเรียบร้อยแล้ว สามารถพิมพ์ผ่านเครือข่ายได้แล้ว

    debian: แก้ปัญหา cups

    ปกติเวลาจะติดตั้งเครื่องพิมพ์เครือข่าย จะใช้การติดตั้งผ่านเว็บ ทางพอร์ต 631
    แต่ปัญหาคือ สำหรับเครื่องพิมพ์ที่ต่ออยู่กับเครื่องวินโดวส์ จะไม่สามารถติดตั้งผ่านเว็บแบบนั้นได้
    ต้องติดตั้งผ่าน gnome-cups-manager คือ System -> Administration -> Printing

    ผมลองติดตั้งเครื่องพิมพ์ Brother MFC-7420
    พบว่าต้องติดตั้งไดร์เวอร์สองตัวคือ brmfc7420lpr-2.0.1-1.i386.deb และ cupswrapperMFC7420-2.0.1-1.i386.deb
    เมื่อติดตั้งเสร็จจึงเรียก gnome-cups-manager อีกทีนึง แต่พอติดตั้งเสร็จแล้ว เขาไม่ยอมบันทึกค่าให้ ลองย้อนมาดูล๊อกไฟล์ที่ /var/log/cups/error_log จึงทราบว่าเขาไม่ยอมคัดลอกไฟล์ MFC7420.ppd ไปยังไดเรคทอรี่ /usr/share/ppd
    ก็เลยคัดลอกไฟล์ด้วยมือ
    $ sudo cp /usr/share/cups/model/MFC7420.ppd /usr/share/ppd

    ก็จะสามารถใช้งานเครื่องพิมพ์เครือข่ายวินโดวส์ได้แล้ว

    update: แก้ปัญหาพิมพ์ไปวินโดวส์ไม่ได้

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

    update2: 50-08-20 - แก้ปัญหาพิมพ์ผ่านเครือข่ายไม่ออก

    บางครั้งเวลาเลือกเปลี่ยนไดรฟเวอร์เครื่องพิมพ์ให้เหมาะสมกับเครื่องพิมพ์นั้น ๆ เมื่อเปลี่ยนแล้วพิมพ์ผ่านเครือข่ายไม่ออก
    ดู /var/log/cups/error_log แล้วแก้ปัญหาโดยต้องเปลี่ยนการอนุญาตใช้ไฟล์ของ cache ของ cups
    $ sudo chmod 660 /var/cache/cups/*
    $ sudo chown root:lp /var/cache/cups/*

    โดยให้เราเข้าไปเป็นสมาชิกของกลุ่ม lp
    $ sudo usermod -G lp $USER
    (แต่ผมใช้วิธีเข้าไปแก้ไฟล์ /etc/group ตรง ๆ )

    update3: 50-09-06 - การพิมพ์ไปยังเครื่องวินโดวส์

    การพิมพ์ไปเครื่องวินโดวส์
    การพิมพ์ไปเครื่องวินโดวส์ อาจต้องปรับแก้ /etc/cups/mime.convs ลบคอมเมนต์บรรทัด raw filter ออก
    $ sudo vi /etc/cups/mime.convs
    ...
    application/octet-stream        application/vnd.cups-raw        0       -
    ...

    (ลองบน sid เครื่องนึงต้องแก้ อีกเครื่องนึงไม่ต้องแก้ ยังงงอยู่ครับ)
    เอามาจาก: Debian and Windows Shared Printing mini-HOWTO

    update4: 51-01-11 - ปัญหา Upgrade Required

    แก้ปัญหา Upgrade Required
    เกิดเวลาเราปรับตั้งเครื่องพิมพ์ผ่านบราวเซอร์
    ปรับแก้ที่ /etc/cups/cupsd.conf โดยเติม
    DefaultEncryption Never

    แล้วสั่งเริ่มใหม่
    $ /etc/init.d/cupsd.conf restart
    เอามาจาก CUPS: 426 - Upgrade Required

    update5: 51-04-22 - ปัญหาหาไฟล์ ppd ไม่พบ

    ถ้าติดตั้งใหม่แล้วโดนรายงานว่า หาไฟล์ /usr/share/cups/model/MFC7420.ppd ไม่พบ
    ให้สร้างไฟล์เปล่าขึ้นมา แล้วติดตั้งใหม่ เขาจะสร้างไฟล์ใหม่ขึ้นมาแทน
    # mkdir -p /usr/share/cups/model
    # touch /usr/share/cups.model/MFC7420.ppd
    # aptitude reinstall cupwrappermfc7420
    # /etc/init.d/cupsys restart

    update6: 55-07-08 - ตั้งค่าความละเอียดและขนาดกระดาษ

    • ส่วนบุคคล แก้ที่ไฟล์ ~/.cups/lpoptions
    • ระบบส่วนรวม แก้ที่ไฟล์ /etc/cups/lpoptions

    สมมุติว่าจะให้พิมพ์ไปเครื่องพิมพ์ brother โดยตั้งความละเอียดที่ 600dpi และขนาดกระดาษ A4

    Dest brother Resolution=600dpi PageSize=A4
    

    debian: แก้ปัญหางานพิมพ์ "socket failed"

    จากเรื่อง debian: สร้างเครื่องพิมพ์ socket ให้ cups
    พบว่าหลังปรับรุ่น cups เป็นรุ่น 1.4.4-1 แล้วมีปัญหาว่าเครื่องพิมพ์แบบ socket พิมพ์ไม่ออก

    ค้นพบว่า มีบั๊กอยู่ตรงไดเรคทอรี่ backend โดยให้เพิ่มข้ออนุญาตในการอ่านและรัน

    $ sudo chmod go+rx /usr/lib/cups/backend

    ที่มา: Bug#586324: Printing fails with "/usr/lib/cups/backend/socket failed" fixed for me

    Topic: 

    debian: บันทึกติดตั้งโมเด็ม HSF

    เมื่อคืนไฟฟ้าดับ ทำให้เครื่องต้องปิดตัวเอง ก่อนหน้านั้นติดตั้งเคอร์เนลใหม่ไว้ แต่ยังไม่ได้รีบูตเครื่อง พอไฟฟ้ามา เปิดเครื่องใหม่เลยกลายเป็นเคอร์เนลใหม่ ทำให้ต้องมาติดตั้งไดรฟ์เวอร์อุปกรณ์ใหม่

    ลองตรวจดูปรากฎว่าไม่เคยบันทึกเกี่ยวกับโมเด็ม HSF เลย จึงขอบันทึกไว้แบบหยาบ ๆ สักนิดนึง

    โมเด็มที่เป็นแบบติดตั้งภายใน (Internal) ส่วนใหญ่จะใช้กับลินุกซ์ไม่ค่อยได้ เพราะไม่ใช่โมเด็มแท้ ๆ คือต้องใช้ซอฟต์แวร์คือไดรฟ์เวอร์ช่วย
    แต่ถ้าเป็นโมเด็มที่ใช้ชิปเซตของ Conexant จะสามารถติดตั้งได้โดยใช้ไดรฟ์เวอร์ที่เขาขายผ่าน Linuxant ซึ่งราคาไดรฟ์เวอร์แพงกว่าค่าโมเด็ม ก็จะสามารถติดตั้งใช้งานได้
    โมเด็มแบบนี้มีข้อดีคือทนทานต่อฟ้าร้องฟ้าผ่าได้ดีกว่าโมเด็มแท้ ๆ
    สามารถตรวจดูฮาร์ดแวร์ได้จากคำสั่ง
    # lspci | grep Conexant
    ถ้ามีปรากฎขึ้นมา ก็แสดงว่าโมเด็มเราใช้ชิปเซตของ Conexant

    ใช้เดเบียน Etch เคอร์เนลรุ่น 2.6.18-4
    เริ่มด้วยการติดตั้งเคอร์เนลเฮดเดอร์ ผมใช้ซีพียูตระกูล AMD-k7
    # aptitude install linux-headers-2.6.18-4-k7

    สร้างซอฟต์ลิงก์ไว้รอ
    # cd /usr/src
    # ln -sf linux-headers-2.6.18-4-k7 linux

    ไปดาวน์โหลดไฟล์ติดตั้งจากบริษัท
    # wget http://www.linuxant.com/drivers/hsf/full/archive/cnxtinstall.run

    สั่งรัน
    # ./cnxinstall.run
    # hsfconfig

    ตอบคำถามตามค่าปริยาย ยกเว้นข้อมูลของเรา และตัวเลข License

    เสร็จแล้ว สามารถใช้งานผ่านดีไวซ์ /dev/ttySHSF0-9

    debian: ติดตั้งโมเด็ม Aztech UM-9800

    ติดตั้งโมเด็ม Aztech UM-9800
    โมเด็มตัวนี้ใช้ชิปเซ็ตของ SmartLink (sl-modem) โมเด็มตัวอื่นที่ใช้ชิปเซ็ตเดียวกันก็สามารถใช้วิธีเดียวกันได้ครับ
    สมัยก่อนไดรฟเวอร์ตัวนี้มีปัญหามาก ว่าติดตั้งแล้วมักจะมีปัญหากับเคอร์เนล ไม่ว่าจะเลือกใช้ไดรฟเวอร์ของโมเด็มเองหรือของที่เขาเขียนสำหรับลินุกซ์โดยเฉพาะก็ตาม
    แต่ปัจจุบันมีแพกเกจ module-assistant ทำให้การติดตั้งง่ายขึ้นมาก

    กับ Etch ผมทดสอบกับเคอร์เนล 2.6.17 ไม่ผ่าน แต่กับเคอร์เนลปัจจุบันคือ 2.6.18 ผ่านเรียบร้อยครับ

    เริ่มด้วยติดตั้งแพกเกจ module-assistant
    # aptitude install module-assistant

    เรียกใช้ module-assistant ให้ติดตั้งโมดูล sl-modem โดยอัตโนมัติ
    # m-a a-i sl-modem

    เนื่องจากโมเด็มตัวนี้ไม่ใช่โมเด็มจริง ๆ (เป็นคล้าย ๆ WinModem) จึงต้องมีโปรแกรมทำงานเบื้องหลังเป็น daemon อีกทีนึง เราจึงต้องติดตั้งแพกเกจตัวนี้ด้วย
    # aptitude install sl-modem-daemon

    ปรับแต่ง - อาจไม่จำเป็นก็ได้
    เมื่อเริ่มต้นรัน sl-modem-daemon โปรแกรมจะทำการตรวจสอบโดยอัตโนมัติว่า ฮาร์ดแวร์ของเราเป็น pci หรือ usb
    ถ้าเป็น pci โปรแกรมจะเลือกใช้เคอร์เนลโมดูล slamr
    ถ้าเป็น usb โปรแกรมจะเลือกใช้เคอร์เนลโมดูล slusb
    ฮาร์ดแวร์ของเราเป็น usb เวลาโปรแกรมตรวจสอบอัตโนมัติ เขาจะขึ้นมารายงานผลว่าไม่สามารถแทรกโมดูล slamr ได้ ทำให้รำคาญพอควร
    ผมเลยเลือกที่จะไม่ให้เขาตรวจสอบอัตโนมัติ แต่จะระบุไปเลยว่าของเราต้องใช้เคอร์เนลโมดูล slusb
    # vi /etc/default/sl-modem-daemon

    ...
    #SLMODEMD_DEVICE=auto
    SLMODEMD_DEVICE=slusb0
    SLMODEMD_COUNTRY=THAILAND
    ...

    สั่งเริ่มรัน daemon ใหม่
    # /etc/init.d/sl-modem-daemon restart

    ถ้าไม่มีอะไรผิดพลาด จะเกิดดีไวซ์ /dev/ttySL0 ขึ้น เราสามารถเรียกใช้โมเด็มได้จากลิงก์นี้ครับ

    เสร็จแล้วครับ :)

    ข้อมูลเพิ่มเติม

    • 51-12-09 บนเดเบียน lenny เคอร์เนลรุ่น 2.6.26-1-486 แพกเกจรุ่น 2.9.9d+e-pre2-12+2 : ติดตั้งผ่าน แต่ใช้งานแฟกซ์ไม่ได้ dmesg ขึ้น error ว่า EIP is at down+0x19/0x39

    debian: ติดตั้ง slmodem จากซอร์ส

    จากครั้งก่อน หลังจากติดตั้งเสร็จแล้ว ผมได้ลองอัปเกรดโดยใช้คำสั่ง aptitude dist-upgrade
    ปรากฎว่าโมดูล sl-modem ไม่ทำงาน (ไม่รายงานข้อผิดพลาด แต่ไม่ทำงาน)
    เลยทดลองถอดออกและติดตั้งจากซอร์สใหม่
    ขั้นตอนมีดังนี้

    ถอดโมดูลเก่าก่อน
    # dpkg -r sl-modem-modules-2.6.18-3-k7

    ถอดแพกเกจ sl-modem-daemon ด้วย
    # aptitude purge sl-modem-daemon

    ดาวน์โหลดซอร์สจาก linmodem - Smartlink ผมเลือกตัวใหม่สุด รวมทั้งต้องดาวน์โหลดซอร์สของ ungrab-winmodem ด้วย
    # cd /usr/src
    # wget http://linmodems.technion.ac.il/packages/smartlink/ungrab-winmodem.tar.gz
    # tar xfz ungrab-winmodem.tar.gz
    # cd ungrab-winmodem
    # make && make install
    # cd ..
    # wget http://linmodems.technion.ac.il/packages/smartlink/slmodem-2.9.11-20061021.tar.gz
    # tar xfz slmodem-2.9.11-20061021.tar.gz
    # cd slmodem-2.9.11-20061021
    # make && make install
    # mknod -m 600 /dev/slamr0 c 242 0
    # mknod -m 600 /dev/slusb0 c 243 0
    # modprobe ungrab-winmodem
    # modprobe slusb

    ปรับ inittab
    # vi /etc/iniitab

    ...
    S3:2345:once:/bin/mknod -m 600 /dev/slusb0  c 243 0 && /sbin/modprobe/ungrab-winmodem && /sbin/modprobe slusb
    S4:2345:respawn:/usr/sbin/slmodemd -d1 --country=THAILAND /dev/slusb0
    ...

    สั่ง init ใหม่
    # init q

    ใช้งานได้แล้ว

    ข้อมูลปรับปรุง

    • 51-12-09 บนเดเบียน lenny เคอร์เนล 2.6.26 ไม่ยอมให้คอมไพล์มอดูล slusb ซึ่งไม่ได้เป็น GPL ดังนั้น ตั้งแต่เคอร์เนล 2.6.26 เป็นต้นไป จะไม่สามารถใช้งาน slusb ได้แล้ว

    debian: ตัวอย่างการติดตั้งสแกนเนอร์ Epson รุ่น CX5500

    ถึงแม้ Epson จะมีไดรฟเวอร์มาให้ แต่ก็เป็นแพกเกจแบบ rpm ของ RedHat ทำให้การติดตั้งยังไม่สะดวกเท่าที่ควร
    หน้าเว็บอยู่ที่ Avasys official Epson drivers
    ซึ่งถ้านำมาแปลงด้วยโปรแกรม alien ก็อาจจะได้ แต่ที่ผมยกมาเป็นตัวอย่าง จะเป็นการปรุงด้วยเดเบียนโดยตรง

    ตามตัวอย่าง ผมใช้เดเบียนรุ่น sid ซึ่งใช้ gcc-4.3 นะครับ

    ติดตั้งแพกเกจสำหรับปรุง deb และ xsane

    $ sudo aptitude install devscripts libsane-dev xsane

    เอาไฟล์ซอร์สและแพทช์มาคอมไพล์

    $ wget http://lx1.avasys.jp/iscan/2.11.0/iscan_2.11.0-1.tar.gz
    $ wget http://aur.archlinux.org/packages/iscan/iscan/iscan-gcc43-fix.patch
    $ tar zvxf iscan_2.11.0-1.tar.gz
    $ cd iscan-2.11.0/
    $ patch -Np0 -i ../iscan-gcc43-fix.patch
    $ debuild -us -uc

    จะได้ไฟล์ .deb ต้องติดตั้งด้วยพารามิเตอร์เพิ่มเติม เพราะแพกเกจนี้ ขัดกับแพกเกจ libsane-extras

    $ sudo dpkg --force-overwrite --force-conflicts -i ../iscan_2.11.0-1_i386.deb

    เสร็จแล้ว ต่อด้วยการปรับตั้ง
    ก่อนอื่นดูค่าพวก Vendor ID กับ Product ID ก่อน ด้วยคำสั่ง

    $ sane-find-scanner
    ...
    found USB scanner (vendor= [Language Error], product=0x083f [Language Error]) at libusb:001:003
    ...

    เราจะเอานำค่าเหล่านี้ไปปรับตั้ง

    $ sudo vi /etc/sane.d/epkowa.conf

    คอมเมนต์หน้าบรรทัด scsi และเพิ่มบรรทัด usb ดังตัวอย่าง

    ...
    #scsi EPSON
    ...
    usb
    ...
    #usb 0x04b8 0x0110
    usb 0x04b8 0x083f
    ...

    ตรวจดูว่าในไฟล์กำกับอุปกรณ์ udev มี Vendor ID และ Product ID ด้วยหรือไม่ ถ้ายังไม่มี ต้องเติมเข้าไป (แต่ถ้าเป็นรุ่นตัวอย่างนี้ เขามีอยู่แล้ว)

    $ sudo vi /etc/udev/rules.d/z60_libsane.rules
    ...
    # Epson Stylus DX4450 (CX5500)
    SYSFS{idVendor}=="04b8", SYSFS{idProduct}=="083f", MODE="0664", GROUP="scanner", ENV{libsane_matched}="yes"
    ...

    สั่งให้มีผลเลย

    $ sudo /etc/init.d/hal restart
    $ sudo /etc/init.d/udev restart

    (ถ้าไม่ได้ผล อาจต้องบูตเครื่องหรือล๊อกเอาต์แล้วล๊อกอินใหม่)

    ต่อไปก็ทำให้เราสามารถใช้งาน scanner ได้ ด้วยการให้ชื่อเราเข้าไปอยู่ใน group ชื่อ scanner

    $ sudo usermod -a -G scanner USER

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

    สามารถใช้งานแสกนเนอร์ผ่านเมนู Application -> Graphics -> Image Scan
    หรือสั่งผ่านบรรทัดคำสั่งว่า iscan ก็ใช้ได้เช่นกัน

    อ้างอิง

    debian: ติดตั้งสแกนเนอร์เครือข่าย

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

    ทำที่เครื่องเซิร์ฟเวอร์

    สมมุติว่าชื่อ server.example.com
    ไอพี 192.168.1.1
    ใช้งานเครือข่ายภายใน 192.168.1.0/24
    และติดตั้งสแกนเนอร์ไว้แล้ว

    ติดตั้งแพกเกจ sane และ inetd
    # aptitude install sane sane-utils openbsd-inetd

    ปรับให้ saned ทำงาน
    # vi /etc/default/saned

    ...
    #RUN=no
    RUN=yes
    ...

    ตั้งค่า inetd และเริ่ม inetd ใหม่
    # vi /etc/inetd.conf

    ...
    sane-port   stream  tcp nowait  saned:saned /usr/sbin/saned saned
    ...

    # /etc/init.d/openbsd-inetd restart

    ปรับตั้งค่าเครื่องลูกข่าย เราจะกำหนดให้ทุกเครื่องใช้งานได้
    # vi /etc/sane.d/saned.conf

    ...
    192.168.1.0/24
    example.com
    ...

    เสร็จแล้ว
    ทดสอบว่าสแกนเนอร์ที่ติดตั้งไว้แล้ว สามารถใช้งานได้หรือไม่ ด้วยคำสั่ง
    # scanimage -L

    ทำที่เครื่องลูกข่าย

    สมมุติว่าผู้ใช้งานชื่อ user1

    ติดตั้งแพกเกจ xsane และ sane-utils
    (xsane สามารถใช้งานกับลูกข่ายที่เป็นวินโดวส์ Xp ขึ้นไปได้ด้วย)
    $ sudo aptitude install xsane sane-utils

    ตั้งค่าให้รู้จักเซิร์ฟเวอร์
    $ sudo vi /etc/sane.d/net.conf

    ...
    192.168.1.1 #หรือ server.example.com ก็ได้ ถ้ามี dns server ภายใน
    ...

    ปรับให้ชื่อเราไปอยู่ในกลุ่ม scanner
    $ sudo usermod -a -G scanner user1

    เสร็จแล้ว
    ทดสอบว่าเครื่องลูกสามารถมองเห็นสแกนเนอร์ของเครื่องแม่ได้หรือไม่ จากคำสั่ง
    $ scanimage -L

    สามารถเรียกใช้งานผ่านโปรแกรม xsane

    อ้างอิง

    debian: แก้ปัญหามองไม่เห็น SATA

    debian: lenny
    kernel: 2.6.26-1-686

    เครื่องมองไม่เห็นฮาร์ดดิสก์ SATA โดยขึ้นข้อความใน dmseg ว่า

    ...
    [    3.228017] ata1: SATA link up 3.0 Gbps (SStatus 123 SControl 300)
    [    3.249140] APIC error on CPU0: 00(08)
    [   33.228013] ata1.00: qc timeout (cmd 0xec)
    [   33.228019] ata1.00: failed to IDENTIFY (I/O error, err_mask=0x4)
    ...
    

    แก้ด้วยการใส่ออปชั่นในการบูตว่า pci=nomsi
    $ sudo vi /boot/grub/menu.lst

    ...
    kernel      /boot/vmlinuz-2.6.26-1-686 root=/dev/hda1 ro quiet pci=nomsi
    ...
    

    แล้วบูตใหม่ก็ใช้ได้

    เอามาจาก : lkml.org : APIC error on 32-bit kernel

    update

    • อาจต้องเพิ่มพารามิเตอร์ pci=nomsi,noapic,nosmp,roteirq,noacpi เสริมช่วย อาจได้ผลในการเขียนกลับได้ดีขึ้น
    • ทางแก้ที่ดีที่สุดคือ กลับไปใช้เคอร์เนลของ etch รุ่น 2.6.18 โดยไม่ต้องใส่พารามิเตอร์ จะได้ความเร็วในการเขียนกลับดีกว่าเยอะเลย
    • ล่าสุด กับเคอร์เนล 2.6.26 เติมพารามิเตอร์ pci=nomsi และใช้คำสั่ง
      # hdparm -Z /dev/sda
      # smartctl -a /dev/sda

      ได้ผลเป็นปกติแล้ว

    Topic: 

    debian: แก้ปัญหาเรื่อง Tapping บน Touchpad

    debian: แก้ปัญหาเรื่อง Tapping บน Touchpad
    Tapping คือการใช้นิ้วแตะบน Touchpad แล้วให้ผลเหมือนการคลิกเมาส์ปุ่มซ้าย ซึี่งบน Windows และ Ubuntu เขาปรับตั้งมาให้อยู่แล้ว

    ทดสอบบน
    Netbook: Acer Aspire One D255
    Linux: Debian Squeeze 6.0

    วิธีที่ทดลอง แต่ไม่ได้ผล คือ การตั้งไฟล์คอนฟิกใน /etc/X11/xorg.conf.d/ และ /etc/udev/rules.d/
    Xorg เขารายงานว่าหาฮาร์ดแวร์ไม่พบบ้าง ไม่พบมอดูล fbcon บ้าง สุดท้ายคือต้องแก้ที่ session
    แต่ไหน ๆ ทำแล้ว ก็เลยทำเรื่อง mouse scroll ด้วย ดังนี้

    • สำหรับ gnome-desktop มีผลกับเราคนเดียว
      ใช้เมนู System -> Preferences -> Mouse
      เลือก
      • Disable touchpad while typing
      • Enable mouse clicks with touchpad
      • Enable horizontal scrolling
    • สำหรับ gnome-desktop มีผลกับผู้ใช้ทุกคน
      ใช้บรรทัดคำสั่งดังนี้
      # gconftool-2 --direct --config-source xml:readwrite:/etc/gconf/gconf.xml.defaults --type bool --set /desktop/gnome/peripherals/touchpad/horiz_scroll_enabled true
      # gconftool-2 --direct --config-source xml:readwrite:/etc/gconf/gconf.xml.defaults --type bool --set /desktop/gnome/peripherals/touchpad/tap_to_click true
      # gconftool-2 --direct --config-source xml:readwrite:/etc/gconf/gconf.xml.defaults --type bool --set /desktop/gnome/peripherals/touchpad/disable_while_typing true
    • สำหรับ desktop อื่น มีผลกับเราคนเดียว (ลองกับ xfce-desktop) อย่างเดียว
      อันนี้เราทำกับ xserver โดยทำที่ xsession
      สร้างไฟล์ ~/.xsessionrc เนื้อไฟล์มีดังนี้
      synclient VertEdgeScroll=1
      synclient HorizEdgeScroll=1
      synclient TapButton1=1
    • สำหรับ desktop อื่น มีผลกับผู้ใช้ทุกคน (ลองกับ xfce-desktop) อย่างเดียว
      สร้างไฟล์ /etc/X11/Xsession.d/99x11-synaptic
      เนื้อไฟล์เหมือนเดิม
      synclient VertEdgeScroll=1
      synclient HorizEdgeScroll=1
      synclient TapButton1=1

    เสร็จแล้ว

    อ้างอิง: Solution, Tips And More: Enable Touchpad Tapping & Scrolling in Debian

    debian: เรื่องของ cache และ proxy

    รวมเรื่องเกี่ยวกับ cache และ proxy

    Topic: 

    debian: ติดตั้ง flashcache

    จะทดลองเพิ่มความเร็วฮาร์ดดิสก์ ลองซื้อ SSD ขนาด 60G มาตัวนึง เพื่อมาทำ cache

    ได้ทดสอบ bcache แต่ไปไม่รอด มันต้องการเคอร์เนลใหม่เกินไป ทำให้กระทบส่วนอื่น ๆ ของเครื่อง จึงได้หันมาหา flashcache

    สมบัติเครื่อง

    • debian wheezy/sid linux-image-3.2.0-3-amd64
    • ฮาร์ดดิสก์เก่า เป็น /dev/sda โดยเมานต์ /dev/sda6 เป็น /share และ /dev/sda8 เป็น /
    • SSD ใหม่ เป็น /dev/sdb และเพื่อป้องกันปัญหาของระบบ จึงเลือกแบ่งพาร์ติชั่นดังนี้
      • /dev/sdb5 ขนาด 16G คัดลอกมาจาก /dev/sda8
      • /dev/sdb6 ขนาด 4G ทำ swap
      • /dev/sdb7 ขนาด 40G ทำ cache ให้กับ /dev/sda6 (sda6 มี UUID="fcc7ad22-4bee-408b-998b-25351c89e7d4")

    วิธีการ

    ติดตั้งแพคเก็จที่จำเป็น

    # aptitude install dkms build-essential linux-headers-`uname -r`
    

    ดาวน์โหลด flashcache และคอมไพล์

    # cd /usr/src
    # mkdir flashcache
    # cd flashcache
    # git clone https://github.com/facebook/flashcache.git
    # cd flashcache
    # make -f Makefile.dkms
    

    เตรียม /dev/sdb

    # fdisk -l /dev/sdb
    Disk /dev/sdb: 60.0 GB, 60022480896 bytes
    81 heads, 63 sectors/track, 22973 cylinders, total 117231408 sectors
    Units = sectors of 1 * 512 = 512 bytes
    Sector size (logical/physical): 512 bytes / 512 bytes
    I/O size (minimum/optimal): 512 bytes / 512 bytes
    Disk identifier: 0x58997006
    
       Device Boot      Start         End      Blocks   Id  System
    /dev/sdb1            2048   117231407    58614680    5  Extended
    /dev/sdb5            4096    33558527    16777216   83  Linux
    /dev/sdb6        33560576    41949183     4194304   82  Linux swap / Solaris
    /dev/sdb7        41951232   117231407    37640088   83  Linux
    
    # mkfs.ext4 /dev/sdb5	#(UUID="c1ba303d-bd4e-4473-9430-34d6b907bbdd")
    # mkfs.ext4 /dev/sdb7
    # mkswap /dev/sdb6
    

    ตรวจ uuid

    # blkid
    /dev/sda5: UUID="ab04b1bc-d1d2-4bdd-8fda-7e2755c35271" TYPE="ext4" 
    /dev/sda6: UUID="fcc7ad22-4bee-408b-998b-25351c89e7d4" TYPE="ext4" 
    /dev/sda7: UUID="eb268873-fcb5-4d5c-b826-59b5fa339079" TYPE="swap" 
    /dev/sda8: UUID="15c9a8a3-0449-458c-9564-0daff370a0d2" TYPE="ext4" 
    /dev/sdb5: UUID="c1ba303d-bd4e-4473-9430-34d6b907bbdd" TYPE="ext4" 
    /dev/sdb6: UUID="6cada279-d335-429f-ad72-69442de1ee08" TYPE="swap" 
    /dev/sdb7: UUID="f06896fe-b3be-43a1-9230-2433c683ee2e" TYPE="ext4" 
    
    # mkdir /mnt/tmp{1,2}
    # mount /dev/sda8 /mnt/tmp1
    # mount /dev/sdb5 /mnt/tmp2
    # rsync -av --delete /mnt/tmp1/ /mnt/tmp2
    # vi /mnt/tmp2/etc/fstab
    
    ...
    #old /dev/sda8#UUID=15c9a8a3-0449-458c-9564-0daff370a0d2       /               ext4    errors=remount-ro 0    
    UUID=c1ba303d-bd4e-4473-9430-34d6b907bbdd       /               ext4    errors=remount-ro 0
    ...
    
    # umount /mnt/tmp{1,2}
    # rm -rf /mnt/tmp{1,2}
    

    วิธีสร้าง cache device (/dev/sdb7 เป็น cache ให้ /dev/sda6)

    รีบูตไปที่ /dev/sdb5 โดยบูตเป็นแบบ recovery เข้า grub แล้วกด c

    set root=(hd1,5)
    linux /boot/vmlinuz-3.2.0-3-amd64 root=UUID=c1ba303d-bd4e-4473-9430-34d6b907bbdd ro single
    initrd /boot/initrd.img-3.2.0-3-amd64
    boot

    ยกเลิกเมานต์ /dev/sda6

    # umount /dev/sda6
    

    ทำ /dev/sdb7 เป็น cache ให้ /dev/sda6 โดยทำให้เป็นแคชแบบ writeback

    # flashcache_create -p back cache_dev /dev/sdb7 /dev/disk/by-uuid/fcc7ad22-4bee-408b-998b-25351c89e7d4
    

    จะเกิด device ขึ้นมาอยู่ที่ /dev/mapper/cache_dev

    แก้ไข /etc/fstab ให้มาใช้ cache device

    # vi /etc/fstab
    
    ...
    #UUID=fcc7ad22-4bee-408b-998b-25351c89e7d4       /share          ext4    defaults        0       2
    /dev/mapper/cache_dev       /share          ext4    defaults        0       0
    ...
    

    รีบูต
    เสร็จแล้ว

    วิธีลบ cache drive (กลับมาใช้ /dev/sda6 ดังเดิม)

    รีบูตไปที่ /dev/sdb5 โดยบูตเป็นแบบ recovery
    ยกเลิกเมานต์ /dev/mapper/cache_dev

    # umount /dev/mapper/cache_dev
    

    ยกเลิก cache drive

    # flashcache_destroy /dev/sdb7
    

    ลบ cache drive

    # dmsetup remove cache_dev
    

    แก้ /etc/fstab ให้กลับไปใช้ /dev/sda6

    # vi /etc/fstab
    
    ...
    UUID=fcc7ad22-4bee-408b-998b-25351c89e7d4       /share          ext4    defaults        0       2
    #/dev/mapper/cache_dev       /share          ext4    defaults        0       0
    ...
    

    รีบูต

    ผลทดลอง

    • เร็วขึ้นแบบรู้สึกได้
    • เจอโหลด write back แบบหนักหน่วงแล้วอาจทำให้เครื่องหยุดได้
    • พารามิเตอร์ -p thru ยังใช้ไม่ได้

    ที่มา

    Topic: 

    debian: ทำแคชให้ apt ด้วย apt-proxy

    apt-proxy เป็นซอฟต์แวร์ที่ใช้เก็บแพคเกจ apt
    ใครมีเครื่องลินุกส์ที่ใช้แพคเกจ apt ในหน่วยงานตั้งแต่สองเครื่องขึ้นไป
    ควรติดตั้งไว้ที่เครื่องเซิร์ฟเวอร์เป็นอย่างยิ่งครับ

    เอามาจาก : ThaiLinuxCafe - ทำแคชให้เดเบียนแพคเก็จด้วย apt-proxy
    โฮมเพจอยู่ที่ : http://apt-proxy.sourceforge.net/
    ขอไว้อาลัยและอุทิศกุศลแด่ : คูณ Manuel Estrada Sainz (ranty) และคุณ Andrés García (ErConde) ผู้พัฒนาด้วยครับ

    ที่เครื่องเซิร์ฟเวอร์
    สมมุติว่าเซิร์ฟเวอร์ชื่อ server1.example.com ไอพี 192.168.1.5

    เริ่มติดตั้งด้วย
    # aptitude install apt-proxy

    ปรับตั้งแพคเกจนิดหน่อย
    # vi /etc/apt-proxy/apt-proxy-v2.conf

    ...
    max_age = 120d        ;; อายุแพคเกจในแคช ผมใช้ 360d
    ...
    [debian]
    backends =
            http://ftp.us.debian.org/debian
            ;;http://mirror.in.th/debian
            ...
    ...
    [security]
    backends =
            http://security.debian.org/debian-security
            ...
    ...
    [ubuntu]
    backends =
            http://th.archive.ubuntu.com/ubuntu        
            http://mirror.in.th/ubuntu/archive
            ...
    ...
    [ubuntu-security]
    backends = 
            http://security.ubuntu.com/ubuntu
    ...
    

    ใช้งานได้แล้ว

    ที่เครื่องลูกข่าย
    เวลาเรียกใช้งานจากเครื่องลูก เราแก้ไขไฟล์ /etc/apt/sources.list ให้มาใช้ของเรา
    $ sudo vi /etc/apt/sources.list

    ถ้าเป็นเดเบียน

    deb     http://server1.example.com:9999/debian/ etch main contrib non-free
    deb-src http://server1.example.com:9999/debian/ etch main contrib non-free
    deb     http://server1.example.com:9999/security/ etch/updates main contrib non-free
    deb-src http://server1.example.com:9999/security/ etch/updates main contrib non-free

    ถ้าเป็นอูบุนตู

    deb     http://server1.example.com:9999/ubuntu/ edgy main restricted universe multiverse
    deb-src http://server1.example.com:9999/ubuntu/ edgy main restricted universe multiverse
    deb     http://server1.example.com:9999/ubuntu/ edgy-updates main restricted universe multiverse
    deb-src http://server1.example.com:9999/ubuntu/ edgy-updates main restricted universe multiverse
    deb     http://server1.example.com:9999/ubuntu-security/ edgy-security main restricted universe multiverse
    deb-src http://server1.example.com:9999/ubuntu-security/ edgy-security main restricted universe multiverse

    หมายเหตุ

    • ตรง server1.example.com อาจใช้เป็นเลขไอพี 192.168.1.5 ก็ได้
    • สำหรับเครื่องแม่ข่ายเอง อาจใช้ของตัวเองโดยเปลี่ยน server1.example.com เป็น localhost ก็ได้
    • หากมีเครื่องที่เป็น apt-proxy ต่อกัน ให้ตั้งค่า Backend ใน /etc/apt-proxy/apt-proxy-v2.conf ให้ชี้ไปที่ apt-proxy ตัวแรก ไม่ควรออกสู่ภายนอกด้วยตัวเอง จะมีปัญหาตายบ่อย
    • แพกเกจนี้ยังมีข้อผิดพลาดอยู่ เนื่องจากผู้พัฒนาเสียชีวิตไปเสียก่อน เมื่อเครื่องลูกข่ายรอนานผิดปกติ หรือเกิดข้อผิดพลาดขึ้น อาจต้องเริ่มเซอร์วิสที่เซิร์ฟเวอร์ใหม่บ้างเป็นบางครั้ง แต่ผมว่าคุ้มค่ากับการประหยัดแบนวิธครับ

    ทำแคชให้ apt ด้วย approx

    ทำแคชให้ apt ด้วย approx

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

    จากการทดลองใช้งานพบว่า ช้ากว่า apt-proxy (ซึ่งช่วงหลังมีปัญหาตายบ่อยมาก) แต่เสถียรกว่าเยอะ
    ใช้พอร์ต 9999 เป็นพอร์ตเดียวกับ apt-proxy ทำให้ปรับใช้แทน apt-proxy ได้อย่างง่ายดาย

    เริ่มติดตั้ง

    ที่เครื่องเซิร์ฟเวอร์
    สมมุติว่าเซิร์ฟเวอร์ชื่อ server1.example.com ไอพี 192.168.1.5

    เริ่มติดตั้งด้วย
    # aptitude install approx

    ปรับตั้งแพคเกจนิดหน่อย
    # vi /etc/approx/approx.conf

    ...
    #debian     http://ftp.debian.org/debian
    debian     ftp://debianclub.org/debian
    security   http://security.debian.org/debian-security
    volatile   http://volatile.debian.org/debian-volatile
    ...
    

    เริ่มใหม่
    # /etc/init.d/approx restart
    ใช้งานได้แล้ว

    ที่เครื่องลูกข่าย
    เวลาเรียกใช้งานจากเครื่องลูก เราแก้ไขไฟล์ /etc/apt/sources.list ให้มาใช้ของเรา
    $ sudo vi /etc/apt/sources.list

    ถ้าเป็นเดเบียน

    deb     http://server1.example.com:9999/debian/ lenny main contrib non-free
    deb-src http://server1.example.com:9999/debian/ lenny main contrib non-free
    deb     http://server1.example.com:9999/security/ lenny/updates main contrib non-free
    deb-src http://server1.example.com:9999/security/ lenny/updates main contrib non-free

    เอามาจาก
    debianadmin.com: Upgrade multiple debian systems with Approx

    หมายเหตุ

    • ตรง server1.example.com อาจใช้เป็นเลขไอพี 192.168.1.5 ก็ได้
    • สำหรับเครื่องแม่ข่ายเอง อาจใช้ของตัวเองโดยเปลี่ยน server1.example.com เป็น localhost ก็ได้
    • หากย้ายจาก apt-proxy มาใช้ approx แทน มีวิธีประหยัดแบนด์วิธเล็กน้อย คือเปลี่ยนพอร์ต apt-proxy เป็น 9998 และตั้งค่าคลังแพกเกจให้ approx เรียกใช้ apt-proxy จากพอร์ต 9998 นี้แทน เช่น
      # vi /etc/approx/approx.conf
      debian      http://localhost:9998/debian
      security    http://localhost:9998/security
      volatile    http://localhost:9998/volatile

      เป็นต้น ใช้งานสักสองสามวัน จนคลังของ approx เต็ม แล้วจึงค่อยเปลี่ยนกลับ

    Topic: 

    multimedia

    บันทึกการทำงานเกี่ยวกับไฟล์ภาพ เสียง และวิดีโอ

    Topic: 

    ubuntu: ลองแปลงไฟล์ภาพเป็นวิดีโอ

    ลองทำสคริปต์แปลงไฟล์ภาพเป็นวิดีโอ
    วิธีใช้คือเข้าไปในไดเรคทอรี่ของไฟล์ภาพ แล้วสั่งรันโปรแกรมตรง ๆ
    $ sudo aptitude install imagemagick mjpegtools
    $ vi d.mkvid

    #!/bin/bash
    #REQUIRED PACKAGE: imagemagick mjpegtools
    #VIDEO VARIABLE
    SCREEN="720x576"	#DVD-PAL=720x576 DVD-NTSC=720x480
    BACKGROUND="black"
    FRAMERATE="25"	#PAL=25 NTSC=29.97
    LOOP="75"	#STILL IMAGE FOR 3 SEC x FRAMERATE
    FADEFRAME="50"	#FADE FOR 2 SEC x FRAMERATE
    
    #PROGRAM VARIABLE
    MAINFILE="main.m2v"
    WORKDIR="temp"
    IMGSKEL="[Jj][Pp][Gg]"
    
    #-------------FUNCTION---------------- 
    #MAKING AVI FROM JPG
    fMakeAvi() {
      echo -n "Make AVI $1 ... "
      jpeg2yuv -v 0 -f $FRAMERATE -j $1 -I p -n 1 -l $LOOP | yuv2lav -o $1.avi
    }
    
    #MAKE CROSS FADE BETWEEN AVI FRAME 
    fMakeFade() {
      echo -n "Make fade $1 $2 ... "
      ypipe -v 0 "lav2yuv -v 0 -o -$FADEFRAME $1.avi" "lav2yuv -v 0 -f $FADEFRAME $2.avi" | transist.flt -o 0 -O 255 -d $FADEFRAME | yuv2lav -v 0 -o $1$2.avi
    }
    
    #MAKING M2V FROM AVI
    fMakeM2V() {
      echo -n "Make M2V $1 ... "
      lav2yuv -v 0 $1.avi | mpeg2enc -v 0 -o $1.m2v
    }
    
    #-------------BEGIN MAIN--------------
    #DETERMINE FIRST FILE
    FIRSTJPG=`echo *$IMGSKEL | cut -d \  -f 1`
    
    #CREATE WORKING DIRECTORY
    if [ -d $WORKDIR ]; then
      echo "Directory $WORKDIR existed, exit 1"
      exit 1;
    fi 
    mkdir $WORKDIR 
    
    #MAKE JPG FILE SUITABLE FOR VIDEO BY USING imagemagick:montage
    for i in *$IMGSKEL; do 
      montage -geometry $SCREEN -background $BACKGROUND -quality 100 $i $WORKDIR/$i
    done
    
    #CHANGE TO WORKING DIRECTORY
    pushd $WORKDIR
    
    #MAKE AVI FOR FIRST JPG FILE
    fMakeAvi $FIRSTJPG
    #MAKE M2V FROM FIRST AVI FILE
    fMakeM2V $FIRSTJPG
    #ADD FIRST M2V TO MAIN
    cat $FIRSTJPG.m2v > $MAINFILE
    #DELETE FIRST M2V
    rm $FIRSTJPG.m2v
    echo ""
    
    LASTFILE=$FIRSTJPG
    
    for i in *$IMGSKEL ; do 
      if [ $i != $FIRSTJPG ] ; then
        #MAKE AVI FOR THIS FILE
        fMakeAvi $i
        #MAKE FADE BETWEEN LAST FILE AND THIS FILE
        fMakeFade $LASTFILE $i
        #MAKE M2V FOR THIS FILE
        fMakeM2V $i
        #MAKE M2V FOR FADE FILE
        fMakeM2V $LASTFILE$i
        #KEEP AVI OF THIS FILE
        #DELETE AVI OF LAST FILE
        rm $LASTFILE.avi
        #DELETE AVI OF FADE FILE
        rm $LASTFILE$i.avi
        #ADD M2V TO MAIN MPEG
        cat $LASTFILE$i.m2v >> $MAINFILE	#ADD FADE
        cat $i.m2v >> $MAINFILE		#ADD THIS FILE
        #DELETE M2V OF THIS FILE
        rm $i.m2v
        #DELETE M2V OF FADE FILE
        rm $LASTFILE$i.m2v
        #REASSIGN LAST FILE TO THIS FILE
        LASTFILE=$i
        echo ""
      fi ;
    done
    #DELETE AVI OF LAST FILE
    rm $LASTFILE.avi
    
    mv $MAINFILE ..
    popd
    #rm -rf $WORKDIR

    $ chmod 755 d.mkvid

    ใช้งานได้แล้ว แต่ยังมีข้อเสียอยู่คือ แปลงเป็น avi โดยไม่จำเป็น
    ขอศึกษาต่ออีกหน่อยครับ
    ท่านใดพอมีความรู้ ช่วยแนะนำด้วยนะครับ :)

    อ้างอิง

    ปรับปรุงการแปลงไฟล์ภาพเป็นวิดีโอ

    ปรับปรุงโปรแกรมจากครั้งก่อน ลดขั้นตอนการแปลงเป็น AVI

    โจทย์คือ จะแปลงภาพนิ่งจากกล้องถ่ายภาพ ไปเป็นไฟล์วิดีโอ เพื่อนำไปเขียนเป็น VCD/SVCD/DVD

    $ vi d.mkvid2

    #!/bin/bash
    #REQUIRED PACKAGE: imagemagick mjpegtools
    #VIDEO VARIABLE
    SCREEN="720x576"	#DVD-PAL=720x576 DVD-NTSC=720x480
    BACKGROUND="black"
    FRAMERATE="25"	#PAL=25 NTSC=29.97
    LOOP="75"	#STILL IMAGE FOR 3 SEC x FRAMERATE
    FADEFRAME="50"	#FADE FOR 2 SEC x FRAMERATE
    
    #PROGRAM VARIABLE
    MAINFILE="main.m2v"
    WORKDIR="temp"
    IMGSKEL="[Jj][Pp][Gg]"
    
    #-------------FUNCTION---------------- 
    #MAKING AVI FROM JPG
    fMakeM2V() {
      echo -n "Make AVI $1 ... "
      jpeg2yuv -v 0 -f $FRAMERATE -j $1 -I p -n 1 -l $LOOP | mpeg2enc -v 0 -o $1.m2v
    }
    
    #MAKE CROSS FADE BETWEEN AVI FRAME 
    fMakeFadeM2V() {
      echo -n "Make fade $1 $2 ... "
      ypipe -v 0 "jpeg2yuv -v 0 -f $FRAMERATE -j $1 -I p -n 1 -l $FADEFRAME" \
    "jpeg2yuv -v 0 -f $FRAMERATE -j $2 -I p -n 1 -l $FADEFRAME" \
    | transist.flt -o 0 -O 255 -d $FADEFRAME | mpeg2enc -v 0 -o $1$2.m2v
    }
    
    #-------------BEGIN MAIN--------------
    #DETERMINE FIRST FILE
    FIRSTJPG=`echo *$IMGSKEL | cut -d \  -f 1`
    
    #CREATE WORKING DIRECTORY
    if [ -d $WORKDIR ]; then
      echo "Directory $WORKDIR existed, exit 1"
      exit 1;
    fi 
    mkdir $WORKDIR 
    
    #MAKE JPG FILE SUITABLE FOR VIDEO BY USING imagemagick:montage
    for i in *$IMGSKEL; do 
      montage -geometry $SCREEN -background $BACKGROUND -quality 100 $i $WORKDIR/$i
    done
    
    #CHANGE TO WORKING DIRECTORY
    pushd $WORKDIR
    
    #MAKE M2V FROM FIRST AVI FILE
    fMakeM2V $FIRSTJPG
    #ADD FIRST M2V TO MAIN
    cat $FIRSTJPG.m2v > $MAINFILE
    #DELETE FIRST M2V
    rm $FIRSTJPG.m2v
    echo ""
    
    LASTFILE=$FIRSTJPG
    
    for i in *$IMGSKEL ; do 
      if [ $i != $FIRSTJPG ] ; then
        #MAKE M2V FOR THIS FILE
        fMakeM2V $i
        #MAKE M2V FOR FADE FILE
        fMakeFadeM2V $LASTFILE $i
        #ADD M2V TO MAIN MPEG
        cat $LASTFILE$i.m2v >> $MAINFILE	#ADD FADE
        cat $i.m2v >> $MAINFILE		#ADD THIS FILE
        #DELETE M2V OF THIS FILE
        rm $i.m2v
        #DELETE M2V OF FADE FILE
        rm $LASTFILE$i.m2v
        #REASSIGN LAST FILE TO THIS FILE
        LASTFILE=$i
        echo ""
      fi ;
    done
    
    mv $MAINFILE ..
    popd
    #rm -rf $WORKDIR

    $ chmod 755 d.mkvid2
    ใช้งานด้วยการสร้างไดเรกทอรี่ที่บรรจุไฟล์ jpg อยู่ แล้ว cd ไปที่ไดเรกทอรี่นั้น
    หลังจากนั้นก็สั่งรันโปรแกรมตรง ๆ ในไดเรกทอรี่
    โปรแกรมจะสร้างไฟล์ main.m2v ออกมา เป็นไฟล์ภาพอย่างเดียว

    ถ้าต้องการใส่เสียงเข้าไปด้วย
    สมมุติถ้าต้นฉบับเป็น mp3
    แปลง mp3 เป็น wav ด้วย mpg321
    $ mpg321 sound.mp3 -w sound.wav

    แปลง wav เป็น mp2 ด้วย mp2enc
    $ cat sound.wav | mp2enc -v 0 -V -o main.mp2
    (ถ้ามีหลายไฟล์ ใช้ cat sound1.wav sound2.wav | ... )

    นำมาเชื่อมกันเป็นไฟล์วิดีโอแบบมีเสียงด้วย mplex
    $ mplex -f 8 -M main.m2v main.mp2 -o main.mpg

    ตอนนี้เราสามารถเอาไฟล์ main.mpg ไปเขียนเป็น DVD ได้แล้ว

    debian: บันทึกคำสั่งสำเนาข้อมูลลงซีดี

    มีงานที่จะต้องเก็บแผ่นซีดี วีซีดี และดีวีดี ลงบนฮาร์ดดิสก์ เลยขอบันทึกคำสั่งที่เกี่ยวข้องเอาไว้ดูอ้างอิงในภายหลังครับ

    พยายามทำเป็นแบบบรรทัดคำสั่งให้มากที่สุด เพื่อจะได้ใช้งานกับเครื่องกำลังต่ำ ๆ ได้ เผื่อจะขยายไปเป็นเครื่องเขียนซีดีอัตโนมัติ
    เครื่องมือที่ใช้ พยายามใช้ cdrecord (wodim) ให้มากที่สุด เพราะเข้าใจว่าเขียนได้เรียบร้อยกว่า cdrdao
    ติดตั้งด้วยคำสั่ง
    $ sudo aptitude install cdrecord cdrdao

    แผ่นซีดีข้อมูล

    คัดลอกแผ่นซีดีแบบข้อมูล ให้เป็นไฟล์ iso
    $ dd if=/dev/cdrom of=XXX.iso
    เขียนข้อมูล iso ลงแผ่น
    เลือกใช้ความเร็ว 8 เพราะทำให้แผ่นมีความทนทาน
    $ cdrecord speed=8 dev=/dev/cdrom -data XXX.iso
    เมานต์ไฟล์ iso ขึ้นมาดู
    $ sudo mount -t iso9660 XXX.iso /mnt/disk
    สำเนาข้อมูลในไดเรกทอรี่ลงแผ่น
    สร้างไฟล์ iso
    $ mkisofs -r -o XXX.iso /PATH/TO/BACKUP
    หรือถ้าเป็นแบบสามารถใช้ชื่อไฟล์แบบยาว
    $ mkisofs -r -J -l -d -allow-multidot -allow-leading-dots -no-bak -o XXX.iso /PATH/TO/BACKUP/
    เขียนลงแผ่น
    $ cdrecord speed=8 dev=/dev/cdrom -data XXX.iso

    แผ่น VCD

    สำเนาแผ่นวีซีดี ให้เป็นไฟล์ bin
    $ readcd -clone dev=/dev/cdrom f=XXX.bin
    จะได้ไฟล์ XXX.bin และ XXX.bin.toc
    เขียนไฟล์ bin ลงซีดี
    $ cdrecord -clone gracetime=2 -raw96r dev=/dev/cdrom speed=8 driveropts=burnfree -overburn -multi -xa1 -eject XXX.bin
    ดูไฟล์วิดีโอสกุล .bin
    ใช้ mplayer ได้เลย
    $ mplayer XXX.bin
    ดูไฟล์ในแผ่นวีซีดี
    เนื่องจากแผ่นวีซีดีเป็นแผ่นแบบ multi-session ไม่ใช่รูปแบบมาตรฐานแบบ iso9660 จึงต้องเมานต์ด้วยวิธีพิเศษ
    แพกเกจที่ใช้คือ cdfs ซึ่งต้องคอมไพล์เป็นเคอร์เนลโมดูล จึงต้องติดตั้งเครื่องมือช่วยอีกทีนึงคือ module-assistant
    $ sudo aptitude install module-assistant
    $ sudo m-a a-i cdfs
    $ sudo modprobe cdfs

    เสร็จแล้ว สามารถเมานต์แผ่นมัลติเซสซั่นด้วยคำสั่ง
    $ sudo mount -t cdfs -r /dev/cdrom /mnt/disk
    update
    สำหรับเคอร์เนล 2.6.25 ถ้าคอมไพล์ไม่ผ่าน ดูวิธีแก้ที่ cdfs-src: Patch for building in 2.6.25 kernel version
    สร้างแผ่นวีซีดีจากไฟล์ mpg
    แพกเกจที่ใช้คือ vcdtools
    $ sudo aptitude install vcdtools
    สมมุติว่าไฟล์ mpg ที่จะทำเป็นวีซีดี อยู่ที่ไดเรกทอรี่ /home/user1/mpg คำสั่งคือ
    $ mkvcdfs /home/user1/mpg/*
    จะได้ไฟล์ชื่อ vcd_image.bin และ vcd.toc ออกมา เราต้องเขียนด้วย cdrdao เป็นภาคบังคับ ใช้คำสั่งว่า
    $ cdrdao write --device /dev/cdrom vcd.toc

    เกี่ยวกับไฟล์สกุล nrg ของ nero

    ตอนนี้วิธีที่ง่ายและดีที่สุดคือ ซื้อโปรแกรม nerolinux มาใช้
    แต่ถ้าหากจะต้องการใช้โอเพนซอร์สล้วน ๆ และต้องการเป็นแบบบรรทัดคำสั่ง มีรายละเอียดดังนี้

    ถ้าเป็นไฟล์ข้อมูล
    แปลงเป็นไฟล์ iso
    ทำได้หลายอย่าง
    1. ใช้คำสั่ง dd
      $ dd bs=1k if=XXX.nrg of=XXX.iso skip=300
    2. ใช้แพกเกจ nrg2iso
      $ sudo aptitude install nrg2iso
      $ nrg2iso XXX.nrg xxx.iso
    เมานต์ขึ้นมาดูข้อมูล
    $ sudo mount -t iso9660 -o loop,offset=307200 XXX.nrg /mnt/disk
    เมานต์ขึ้นมาดูข้อมูล ด้วย fuseiso
    $ sudo aptitude install fuseiso
    $ sudo fuseiso XXX.nrg /mnt/disk
    ถ้าเป็นไฟล์วิดีโอ
    เขียนกลับเป็นแผ่นวีซีดี
    ยังหาวิธีที่ง่าย ๆ ไม่ได้ครับ วิธีที่ทำได้คือ ต้องถอดเอาไฟล์ mpeg ในไฟล์ nrg ออกมาด้วย vcdgear แล้วจึงทำเป็นวิดีโอตามวิธีข้างบน
    ขั้นตอนติดตั้งคือ
    $ cd
    $ wget http://www.vcdgear.com/files/vcdgear176-040415_linux.tar.gz
    $ tar xfz vcdgear176-040415_linux.tar.gz

    เรียกใช้ด้วยคำสั่ง
    $ ~/vcdgear/vcdgear -nrg2mpg XXX.nrg
    ดูวีซีดีในไฟล์
    ใช้ mplayer ได้เหมือนเดิม
    $ mplayer XXX.nrg

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

    อ้างอิง

    บันทึก K3b, Nero, Nautilus

    ต้องการเขียนแผ่นข้อมูล ซึ่งเป็นไฟล์ mp3 ธรรมดา
    ที่แรกเขียนด้วย k3b ปรากฎว่าอ่านด้วยคอมพิวเตอร์ออกสบาย ๆ แต่กลับอ่านด้วยเครื่องเล่นดีวีดีทั่วไปตามท้องตลาดไม่ได้ ไม่ว่าลองเปลี่ยนค่าเป็น DAO TAO Mode1 Mode2 ยังไงก็อ่านไม่ออก

    ไม่แน่ใจเครื่องเขียนว่าเก่าไปหรือเปล่า
    เลยลองดาวน์โหลด Nero Linux 3 มาลองเขียนดู ปรากฎว่าอ่านได้สบาย ๆ

    ลองเขียนด้วยโปรแกรมปริยายของ Gnome คือ nautilus-cd-burner ก็อ่านออกเหมือนกัน

    เลยไม่ทราบว่าตั้งค่า k3b ผิดตรงไหน คงต้องกลับไปศึกษาต่อ เที่ยวนี้บันทึกไว้แค่นี้ก่อน

    ลองบนเดเบียน sid ครับ

    debian: สร้างแผ่น dvd จากกล้องวีดีโออย่างง่าย

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

    เริ่มด้วยติดตั้งแพกเกจ
    $ sudo aptitude install kino ffmpeg mplayer dvd+rw-tools mjpegtools mkisofs dvdauthor normalize-audio audacity k3b

    ตัดต่อด้วย kino
    $ kino /media/cdrom0/DVD_RTAV/VR_MOVIE.VRO
    เมื่อตัดต่อเรียบร้อยสามารถส่งออกเป็นไฟล์ mpeg
    Export -> MPEG -> vid01.mpeg
    เลือก Deinterlace ตามชอบ

    ทำโครงสร้าง DVD
    $ mkdir ./dvd
    $ dvdauthor -t -o ./dvd vid*.mpeg
    $ dvdauthor -o ./dvd -T

    ทำเป็น iso
    $ mkisofs -dvd-video -o dvd.iso ./dvd

    เขียนลงแผ่น
    $ growisofs -dvd-compat -speed=1 -Z /dev/hdb=dvd.iso

    เสร็จแล้ว

    การบันทึกเสียงจากวีดีโอเป็น mp3
    เมื่อผ่านขั้นตอนตัดต่อจาก kino แล้ว อาจส่งออกไฟล์เสียงออกมาเป็น wav ได้เลย
    Export -> Audio -> audio.wav
    หรือถ้าส่งออกมาเป็นไฟล์ mpeg แล้วก็สามารถใช้ ffmpeg ได้ด้วยคำสั่ง
    $ ffmpeg -i video.mpeg audio.wav

    เกลี่ยเสียงให้ดังพอดี
    $ normalize-audio audio.wav

    ปรับระดับ Equalize ด้วย audacity
    $ audacity audio.wav
    ผมเลือกใช้
    Effect -> Equalization... -> EMI 78
    และส่งออกมาเป็น wav หรือ mp3 ตามต้องการ

    หากส่งออกเป็น wav สามารถแปลงเป็น mp3 โดย lame
    $ lame -h audio.wav audio.mp3

    อ้างอิง

    Topic: 

    gimp: สร้างคีย์ลัดแบบ PhotoShop

    เอามาจาก Photoshop-ish Keyboard Shortcuts for The Gimp 2.2

    เพิ่งรู้ว่า gimp สามารถสร้างคีย์ลัดเองได้ โดยผ่านไฟล์ menurc
    ซึ่งอยู่ภายในไดเรคทอรี่ ~/gimp-2.2 ( ของวินโดวส์จะเป็น C:\Documents and Settings\<user name>\.gimp-2.2 )

    วิธีการก็คือไปเอาไฟล์ ps-menurc ที่เขาสร้างไว้เลียนแบบคีย์ลัดของ Photoshop (ตามลิงค์)
    เปลี่ยนชื่อเป็น menurc แล้วใส่เข้าในไดเรคทอรี่ของ gimp
    เพียงเท่านี้ ชีวิตภายใต้ร่มเงา gimp ก็สบายขึ้นเยอะ :)

    $ cd ~/.gimp-2.2
    $ mv menurc menurc.old
    $ wget http://epierce.freeshell.org/gimp/ps-menurc
    $ mv ps-menurc menurc
    $ cd

    ถ้าจะสร้างคีย์ลัดไว้ใช้เอง ก็แก้เองในไฟล์นี้แหละครับ โครงสร้างไฟล์เป็นแบบง่าย ๆ

    gimp: การแก้ไข Noise

    การกำจัด Noise ของไฟล์ภาพ จำได้ว่าฝั่งวินโดวส์ มีโปรแกรมที่มีชื่อเสียงอยู่สองตัวคือ
    Neat Image ที่มาก่อน และ Noise Ninja ที่ดูเหมือนจะมาทีหลังแต่ดังกว่า

    ฝั่งลินุกส์ยังไม่ทราบว่ามีตัวไหน ผมลองค้นกูเกิลดู ไปพบที่เว็บนี้
    Nikon D70 under Linux
    ซึ่งท่านผู้เขียน พยายามเลือกใช้ลินุกส์กับงานด้านภาพถ่ายทั้งหมด ท่านจึงเขียนสคริปต์ไพธอนขึ้นมาใช้เองสำหรับการกำจัด Noise โดยทำเป็นปลั๊กอินใน Gimp

    ผมลองทดสอบดูพบว่า ภาพที่ผ่านการโปรเซสแล้ว ยังคงรายละเอียดไว้ได้ดี ไม่ดูเป็นพลาสติก หรือขาดความเป็นธรรมชาติ มีเพี้ยนสีเล็กน้อย ซึ่งสามารถแก้โดยดึง curve ขึ้นมาได้ง่าย ๆ
    จึงขออนุญาตบันทึกการติดตั้งไว้เล็กน้อยครับ

    ทดลองบนเดเบียน sid
    เริ่มด้วยติดตั้งแพกเกจที่จำเป็น
    $ sudo aptitude update
    $ sudo aptitude install gimp-python python-numarray

    ดาวน์โหลดแพกเกจ และนำไปใส่ไว้ในไดเรกทอรี่ปลั๊กอินของ Gimp
    $ wget http://pages.quicksilver.net.nz/pepe/d70/noise_remover.py
    $ sudo mv noise_remover.py /usr/lib/gimp/2.0/plug-ins/

    เสร็จแล้ว เรียกใช้งานด้วยเมนู Plugins -> Noise -> Remove

    gimp: ทำ gif แบบโปร่งใส

    ปรกติ gimp ทำภาพ gif แบบโปร่งใสได้จำกัด คือไม่สามารถไล่ระดับความโปร่งใสแบบเดียวกับ png ทำได้

    วิธีแก้คือให้สร้างภาพเป็น png เมื่อทำจนเสร็จเรียบร้อย ก็แค่ใช้เชลล์เปลี่ยนนามสกุลจาก png เป็น gif ก็จะได้ภาพโปร่งใสตามต้องการ

    หมายเหตุ

    • IE ดูได้
    • gimp รุ่นเก่าเปิดไม่ออก เพราะรูปแบบไฟล์ไม่ใช่ gif จริง ๆ แต่เป็น png เวลาเราจะแก้ไขต้องเปลี่ยนนามสกุลกลับ
    • gimp รุ่นปัจจุบัน (2.4.3) สามารถเปิดมาแก้ไขได้แล้ว

    update

    • บันทึก tif เป็น greyscale 256 สี
      image -> mode -> indexed -> Use custom palette -> Grays
      save แบบบีบอัด lzw
    Topic: 

    ubuntu: เครื่องคัดลอกแผ่นซีดี

    จะรีไซเคิลเครื่องเก่า มาทำเครื่องคัดลอกซีดีเพื่อถวายวัด
    เครื่องใหม่ราคาประมาณหมื่นเศษ

    สเปคเครื่องเก่าคือ Celeron 850MHz, RAM 128MB, HD 15G (เป็น /dev/hda)
    ลงทุนซื้อใหม่คือ CD-Writer 3 ตัว ตัวละ 750 บาท
    เนื่องจากจะไม่มีจอภาพ จึงจะใช้เสียงเป็นตัวแจ้งสถานะ

    จะทำให้มีการทำงานคือ

    • เมื่อเปิดเครื่อง ถาด /dev/hdb จะดีดออกเพื่อรอการอ่านข้อมูล ใช้เสียงดัง 1 ปี๊บ ทุก 3 วินาที
    • เมื่อใส่แผ่นต้นฉบับ เครื่องจะตรวจสอบแผ่น ถ้าแผ่นเสียจะแจ้งเป็นเสียง 6 ปี๊บ ทุก 3 วินาที เพื่อรออ่านแผ่นใหม่
    • ถ้าแผ่นดีจะอ่านจนเสร็จ แล้วทั้งสามถาดจะถูกดีดออกเพื่อรอการเขียนข้อมูล โดยใช้เสียงดัง 2 ปี๊บ ทุก 3 วินาที
    • จะเขียนวนไปเรื่อยจนกระทั่งปิดเครื่องเขียนด้วยถาดเปล่า ไดร์ฟนั้นก็จะหยุดทำงาน
    • เมื่อหยุดทำงานครบสามไดร์ฟแล้ว เครื่องจะปิดตัวเอง
    • ในระหว่างที่ถาดถูกดีดออก ถ้าไม่มีการใส่แผ่นใหม่เข้าไป หลังจากผ่านไป 100 ปี๊ป (หรืออาจเป็นเรากดถาดเปล่าดีดกลับเข้าไป) ถาดจะปิดตัวเอง และปิดการทำงานของถาดนั้นตลอดไป

    หมายเหตุ

    • เนื่องจากเป็นการทำงานแบบขนาน ทุกไดร์ฟจึงทำงานเป็นอิสระ
    • กำหนดให้ความเร็วในการเขียนเป็นแค่ 8x เพื่อให้ฮาร์ดดิสก์อ่านข้อมูลได้ทัน และเพิ่มความคงทนของข้อมูล
    • แผ่นต้นฉบับควรเป็นแผ่นที่ดี ถึงดีมาก ผลจึงจะออกมาดี

    วิธีการสร้าง
    เริ่มด้วย ติดตั้งอูบุนตู dapper แบบ server โดยเลือกไม่ติดตั้งอะไรเพิ่มเติมเลย
    ดามด้วยเอาแพคเก็จที่จำเป็นคือ setcd สำหรับเขียนสคริปต์ และ cdrecord
    $ su
    # aptitude install eject setcd cdrecord

    สร้างสคริปต์สำหรับให้ init เรียกใช้ตอนเปิดเครื่อง สำหรับให้อ่านแผ่นต้นฉบับ
    ตั้งชื่อว่า d.inittab-cpcd
    # vi d.inittab-cpcd

    #!/bin/bash
    # THIS IS THE COPY CD PROGRAM
    # PUT THE LINE
    #     D1:2345:once:/root/d.inittab-cpcd
    # IN /etc/inittab THEN REBOOT 
    # 
    
    beep() {
        echo -e "\7" 
    }
    beeperror() {
        for i in `seq 1 6`; do beep; sleep .15; done
    }
    wait_read_close() {
        # MAX RETRY LOOP = 100
        for (( i=0; i<=100; i++ )) 	
        do
            if setcd -i $DEV | grep open >> /dev/null; then
                # SLEEP 1 SECONDS 
                beep; sleep 3
            else
                readcd -clone dev=$DEV f=$TMP
                return
            fi
        done
        eject -t $1 
        /sbin/shutdown -h now
        return 
    }
    
    # TEMPOLARY IMAGE FILE
    DEV=/dev/hdb
    #KERNEL-2.4 #ATA="ATAPI:0,1,0"
    #KERNEL-2.6 #ATA=/dev/hdb
    ATA=/dev/hdb
    TMP=/tmp/x.bin
    
    rm -rf $TMP
    
    #CHECK 1ST CDR IS ORIGINAL CD THEN DUP. TO /tmp
    while ! [ -s $TMP ]
    do
        eject $DEV
        wait_read_close $DEV
        beep
    done
    
    # CALL MULTIPLE INSTANCE
    #d.func.cpcd DEVICE SPEED ISOFILE
    #KERNEL-2.4
    #/root/sh-func-cpcd /dev/hdb ATAPI:0,1,0 8 $TMP &
    #/root/sh-func-cpcd /dev/hdc ATAPI:1,0,0 8 $TMP &
    #/root/sh-func-cpcd /dev/hdd ATAPI:1,1,0 8 $TMP &
    #KERNEL-2.6
    /root/d.func-cpcd /dev/hdb /dev/hdb 8 $TMP &
    /root/d.func-cpcd /dev/hdc /dev/hdc 8 $TMP &
    /root/d.func-cpcd /dev/hdd /dev/hdd 8 $TMP &
    wait
    /sbin/shutdown -h now
    

    อีกไฟล์นึงเป็นฟังก์ชั่นสำหรับเขียนซีดี ตั้งชื่อว่า d.func-cpcd
    # vi d.func-cpcd

    #!/bin/bash
    beep() {
        echo -e "\7"
    }
    beepwrite() {
        beep; sleep .15; beep
    }
    beeperror() {
        for i in `seq 1 6`; do beep; sleep .15; done
    }
    wait_write_close() {
        # MAX RETRY LOOP = 100
        for (( i=0; i<=100; i++ ))
        do
            # WAIT FOR INSERT DISC
            if setcd -i $DEV | grep 'is open' >> /dev/null; then
                beepwrite; sleep 3
            # IF NO DISC INSERTED THEN BREAK
            elif setcd -i $DEV | grep 'No disc' >> /dev/null; then
                exit 1
            else
                cdrecord -clone -raw96r dev=$ATA speed=$SPEED driveropts=burnfree gracetime=2 -overburn -eject $TMP
                # LAST CHECK WRITING
                if setcd -i $DEV | grep 'error' >> /dev/null; then
                    beeperror; sleep 3
                else
                    eject $DEV
                    return
                fi
            fi
        done
        #END PROG
        eject -t $1
        exit 1 
    }
    if [ ! $1 ] && [ ! $2 ] && [ ! $3 ] && [ ! $4 ] ; then
        echo "   USAGE : $0 DRIVE ATAPI SPEED TEMP-IMG" 
        echo "   EXAM1 : $0 /dev/hdb ATAPI:0,1,0 4 /tmp/x.bin"
        exit 1
    fi
    
    DEV=$1
    ATA=$2
    SPEED=$3
    TMP=$4
    
    #CHECK IMAGE FILE
    if ! [ -f $TMP ] ; then
    	echo "  Image file $TMP does not existed"
    	exit 1
    fi
    
    #COPY TO NEW BLANK CD
    while [ -f $TMP ]
    do
    	eject $DEV
    	wait_write_close $DEV
    	beepwrite
    done
    

    ต่อไปแก้ไขไฟล์ /etc/inittab ให้มาเรียกใช้โปรแกรม d.inittab-cpcd ตอนเปิดเครื่อง
    # vi /etc/inittab
    (ไปบรรทัดล่างสุด กด G)

    ...
    D1:2345:once:/root/d.inittab-cpcd
    

    เสร็จแล้วก็รีบูตทดสอบได้เลยครับ

    update: 49-11-25
    cdrecord เติมพารามิเตอร์ gracetime=2

    update: ubuntu edgy
    edgy ใช้แพกเกจ upstart แทน sysvinit ทำให้บูตเร็วขึ้น
    upstart ไม่ใช้ไฟล์ /etc/inittab แต่ใช้สคริปต์ใน /etc/event.d แทน
    เราต้องสร้างสคริปต์ สมมุติว่าชื่อ cpcd เอาไว้ใน /etc/event.d
    (สคริปต์นี้ ห้ามรันได้ คือแอตทริบิวต์ x ต้องไม่ถูกเซ็ต)
    $ sudo vi /etc/event.d/cpcd

    #
    start on runlevel-2
    start on runlevel-3
    start on runlevel-4
    start on runlevel-5
    #
    stop on shutdown
    #
    exec /root/d.inittab-cpcd
    console output

    รีบูตเครื่องก็ใช้ได้

    update: 49-11-28
    มีปัญหากับไดร์ฟ

    • Liteon SOHR-5239V 2$0D
    • Liteon SOHR-5239S 2$0B

    ไม่สามารถอ่านดิสก์แบบ multisession ที่ถูก overburn ได้ (เช่นแผ่น VCD)
    ยังแก้ไม่ตก

    audacity: บันทึกการลดเสียงรบกวน

    กรณีเสียงรบกวนอยู่เป็นพื้นหลัง
  • ให้เลือกช่วงที่เป็นเสียงรบกวนพื้นหลัง -> Effect -> Noise Removal -> Get Noise Profile
  • เลือกทั้งหมด -> Effect -> Noise Removal -> OK
  • กรณีเสียงรบกวนไม่ใช่เสียงที่คงที่ แต่ขี่อยู่บนเสียงพูด
  • เลือกทั้งหมด -> Effect -> Plug ins -> Vocal Remover (for center-panned vocals)...
  • Effect -> Noise Removal -> Get Noise Profile
  • ปิดแทรกนี้ทิ้ง แล้วเปิดใหม่
  • เลือกทั้งหมด -> Effect -> Noise Removal -> OK
  • bash script: แบ่งไฟล์สื่อออกเป็นหลาย ๆ ส่วน

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

    1. หาความยาวสื่อ ด้วยโปรแกรม ffmpeg ด้วยคำสั่ง
      ffmpeg -i "$INFILE" 2>&1 | grep "Duration" | cut -d ' ' -f 4 | sed s/,//
    2. หาความยาวแต่ละส่วน (ความยาวสื่อ ÷ จำนวนส่วนที่จะแยก)
    3. ใช้ mencoder มาตัดส่วน -ss คือ seek ไปยังตำแหน่ง, -endpos คือให้ประมวลผลเท่าใด (เป็นขนาดก็ได้ เป็นจำนวนเฟรมก็ได้ เป็นค่าเวลาก็ได้)

    เสร็จแล้วเขียนออกมาหยาบ ๆ และทดสอบไปเล็กน้อยว่าใช้งานได้จริงแล้วเลยเอามาบันทึกไว้กันลืมเสียหน่อย

    โดยผลลัพธ์ที่ได้ออกมาเป็นสคริปต์ sep_media.sh เป็นดังนี้:

    sep_media.sh:

    #!/bin/bash
    
    #
    # Script for separate media file by MENCoder
    # by Thanomsub Noppaburana <donga.nb@gmail.com>
    # Usage:    sep_media.sh   
    # Example: 
    #           For separate Doraemon.avi into 20 parts with prefix "Doraemon_Part"
    #           sep_media.sh Doraemon.avi Doraemon_Part 20
    #
    
    INFILE="$1"
    OUTFILEPREFIX="$2"
    TOTALPART="$3"
    EXTENSION=""
    [ ! -z "$INFILE" ] && EXTENSION="`echo "$INFILE" | awk -F . '{print $NF}'`"
    
    ## ----------------------------------------------------------------- ##
    ## functions declaration
    ## ----------------------------------------------------------------- ##
    
    usage() {
       echo
       echo -e "Usage:"
       echo -e "\t`basename $0`   "
       echo -e "Example:"
       echo -e "\tFor separate Doraemon.avi into 20 parts with prefix \"Doraemon_Part\""
       echo -e "\tsep_media.sh Doraemon.avi Doraemon_Part 20"
       echo
    }
    
    getTotalLength() {
       echo `ffmpeg -i "$INFILE" 2>&1 | grep "Duration" | cut -d ' ' -f 4 | sed s/,// | awk -F: '{ print ($1*3600)+($2*60)+$3 }'`
    }
    
    getEachPartLength() {
       echo $1:$2 | awk -F: '{ print $1/$2 }'
    }
    
    separateMedia() {
       TOTALPART=$1
       TOTALLENGTH=$2
       EACHPARTLENGTH=$3
       
       for ((i=0;i<$TOTALPART;i++))
       do
          STARTPOS=`echo $i $EACHPARTLENGTH | awk '{print ($1*$2)+0.01}'`
          echo "====================================================================================================="
          echo "   Separating media part $i: $OUTFILEPREFIX$i.$EXTENSION : "
          echo "-----------------------------------------------------------------------------------------------------"
          mencoder -of lavf -oac copy -ovc copy -o $OUTFILEPREFIX$i.$EXTENSION -ss $STARTPOS -endpos $EACHPARTLENGTH $INFILE
          echo "+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"
       done
    }
    
    ## ----------------------------------------------------------------- ##
    ## main program
    ## ----------------------------------------------------------------- ##
    
    if [ -z "$INFILE" -o -z "$OUTFILEPREFIX" -o -z "$TOTALPART" ];
    then
       usage
       exit 0
    fi
    
    TOTALLENGTH=`getTotalLength`
    EACHPARTLENGTH=`getEachPartLength $TOTALLENGTH $TOTALPART`
    
    separateMedia $TOTALPART $TOTALLENGTH $EACHPARTLENGTH
    

    วิธีใช้งาน:

    ตัวอย่างเช่น ต้องการแยก Doraemon.avi เป็น 20 ส่วน โดยให้ไฟล์ที่โดนแยกนำหน้าด้วยชื่อ "Doraemon_Part"

    sep_media.sh Doraemon.avi Doraemon_Part 20

    ซึ่งก็จะได้ผลลัพธ์เป็นแฟ้ม Doraemon_Part0.avi ถึง Doraemon_Part19.avi ออกมา เป็นต้น

    ดาวน์โหลด:

    ผู้สนใจสามารถดาวน์โหลดไปใช้ได้เลยครับ ดาวน์โหลดสคริปต์ sep_media.sh
    จากนั้นเซฟไว้ใน search path และอย่าลืมตั้ง permission ให้ประมวลผลได้ก่อน ด้วยคำสั่ง

    sudo chmod ugo+x /path/to/sep_media.sh

    เป็นอันพร้อมใช้งานครับ ^_^

    debian: ตัดต่อ dvd ด้วยบรรทัดคำสั่ง

    โจทย์คือแปลงไฟล์วีดีโอจากแผ่นดีวีดี Double Layer 8.5G มาเป็นแผ่นดีวีดีธรรมดา 4.7G โดยให้คุณภาพภาพและเสียงคงเดิม

    วิธีการคือจะคัดลอกไฟล์ vob มาตัดต่อแล้วเขียนกลับลงไปบนแผ่น 4.7G

    ต้องการเครื่องมือคือ ffmpeg dvdauthor mkisofs (genisoimage) และ vlc เพื่อตรวจดูภาพ และ wodim เพื่อเขียนแผ่น
    โดยเฉพาะ ffmpeg ต้องการตัวที่มาจาก debian-multimedia.org เพราะมีไลบรารีมากกว่าของ debian เอง

    $ sudo vi /etc/apt/sourcelist
    ...
    deb    http://www.debian-multimedia.org squeeze main
    ...
    $ sudo aptitude update
    $ sudo aptitude install ffmpeg dvdauthor mkisofs vlc wodim

    เตรียมไดเรคทอรี่ temp ไว้เก็บไฟล์ vob ชั่วคราว และ dvd ไว้เก็บโครงสร้างไฟล์สำหรับเขียนลงแผ่น

    $  mkdir temp dvd

    คัดลอกไฟล์ vob จากแผ่นด้วยคำสั่ง dvdunauthor

    $ cd temp
    $ dvdunauthor /media/cdrom0

    จะได้ไฟล์ vob ออกมาหลายไฟล์ แต่เราจะเก็บเฉพาะไฟล์ vob_01t_xxx.vob เท่านั้น

    $ rm vob_01m*

    ตรวจดูขนาดไฟล์ว่ารวมกันมากกว่า 4.3G หรือไม่ หากมากกว่าให้ลองดูภาพด้วย vlc แล้วสังเกตุช่วงเวลาที่จะทำการตัดต่อไว้
    สมมุติเราได้ไฟล์ vob_01t_001.vob ออกมาไฟล์เดียว และจะตัดที่เวลา 1 ชั่วโมง 2 นาที 3 วินาที ใช้คำสั่ง ffmpeg ในการตัด

    $  ffmpeg -i vob_01t_001.vob -sameq -target dvd -ss 0:0:0 -t 01:02:03 file1.vob

    จะได้ file1.vob เป็นไฟล์ส่วนแรก
    และ

    $  ffmpeg -i vob_01t_001.vob -sameq -target dvd -ss 01:02:03 file2.vob

    จะได้ file2.vob เป็นไฟล์ส่วนหลัง

    เตรียมนำไฟล์ที่ได้ไปสร้างเป็นโครงสร้าง dvd คือ
    สร้างไฟล์โครงสร้าง xml สมมุติชื่อ auth.xml บรรจุไฟล์ file1.vob ลงไป

    $ vi auth.xml
    <dvdauthor allgprm="yes">
      <vmgm />
      <titleset>
        <titles>
          <pgc>
            <vob file="file1.vob" />
          </pgc>
        </titles>
      </titleset>
    </dvdauthor>
    

    สร้าง dvd

    $  dvdauthor -o ../dvd -x auth.xml

    สร้างไฟล์ iso

    $  mkisofs -dvd-video -o ../dvd.iso ../dvd

    นำไปเขียนแผ่น

    $  wodim -v gracetime=2 dev=/dev/cdrom speed=1 -eject ../dvd.iso

    ต่อจากนี้ก็เอาไฟล์ที่เหลือ คือ file2.vob มาสร้างเป็นแผ่นต่อไป

    เสร็จแล้วครับ

    Topic: 

    debian: บันทึกการแปลงไฟล์ amr เป็น mp3

    มีงานต้องแปลงไฟล์เสียงสกุล amr ไปเป็น mp3 เพื่อแจกจ่าย พบปัญหาว่า

    • เมื่อแปลงแล้วจะเกิดเสียง hiss มาก
    • เสียงต้นฉบับเบามาก
    • lame ไม่รู้จัก amr

    ทางแก้ไขคือ

    • ปัญหาเสียง hiss เกิดจากการแปลงด้วยออปชั่นปกติหรือเพิ่มออปชั่นที่ไปเพิ่มคุณภาพเสียง แก้ด้วยการใช้ lame โดยใส่ออปชั่นว่าไม่ต้องพยายามเพิ่มลดอะไรจากต้นฉบับ -q9
    • ปัญหาเสียงเบา แก้ด้วยการ normalize
    • ปัญหา lame ไม่รู้จัก amr แก้ด้วยการแปลงจาก amr เป็น wav ก่อน ด้วย ffmpeg แล้วจึงแปลงเป็น mp3 ด้วย lame

    โปรแกรมที่ต้องการใช้งานคือ ffmpeg, normalize-audio, lame จาก debian-multimedia.org
    (ผมใช้ squeeze ให้เปลี่ยนตามที่ใช้จริง)

    $ sudo vi /etc/apt/sources.list
    ...
    deb http://www.debian-multimedia.org squeeze main non-free
    ...
    $ sudo apttiude update
    $ sudo aptitude install ffmpeg normalize-audio lame

    คำสั่งที่ใช้คือ

    $ for i in *amr; do 
    ffmpeg -i "$i" /tmp/x.wav
    normalize-audio /tmp/x.wav
    lame -q9 /tmp/x.wav /PATH/TO/NEW/FILE/${i%amr}mp3"
    rm /tmp/x.wav
    done
    

    จะได้ไฟล์ mp3 ที่คุณภาพและขนาดไฟล์ใกล้เคียงกับต้นฉบับ amr และไม่มีเสียง hiss ตามต้องการ

    debian: อัดเสียงด้วยบรรทัดคำสั่ง

    ต้องการอัดเสียงด้วยบรรทัดคำสั่งบนเครื่องเซิร์ฟเวอร์ที่ไม่ได้ลง X ไว้

    หากต้องการปรับความดังของไมค์ หรือปรับตั้งลำโพง ต้องติดตั้ง alsamixer ก่อน
    $ sudo aptitude install alsa-utils
    $ alsamixer

    ปรับตั้งให้เรียบร้อย

    ติดตั้งโปรแกรมอัดเสียง
    $ sudo aptitude install sound-recorder

    สมมุติจะอัดเทป 1 หน้า 30 นาที
    $ sound-recorder -c 2 -b 16 -P -S 30:00 recording.wav

    -c 2 คือสเตริโอ 2 ช่อง
    -b 16 คือสุ่มด้วยอัตรา 16 บิต
    -P คือให้โปรแกรมมี priority เยอะกว่าโปรแกรมอื่น
    -S 30:00 คืออัดด้วยเวลา 30 นาที

    เสร็จแล้ว

    เอามาจาก

    Topic: 

    debian: เกร็ดการกู้ดีวีดี

    แผ่นดีวีดีข้อมูลเป็นรอยมาก (อาจไม่ค่อยคอมแพตด้วย เป็นแผ่น DVD+R)
    คัดลอกด้วย Nautilus ทีละหลาย ๆ ไฟล์ไม่ได้
    ใช้แบบบรรทัดคำสั่งก็ไม่ด้

    ผ่านด้วยการคัดลอกทีละไฟล์

    Topic: 

    debian: เครื่องคัดลอกแผ่นซีดี

    จะรีไซเคิลเครื่องเก่า มาทำเครื่องคัดลอกซีดีเพื่อถวายวัด
    ถ้าซื้อเครื่องคัดลอกซีดีตัวจริงใหม่ราคาประมาณหมื่นเศษ
    ของเราใช้รีไซเคิลเอา ราคาไม่เกินสองพันบาท (คิดเฉพาะอุปกรณ์ที่ซื้อใหม่)

    สเปคเครื่องเก่าคือ Celeron 850MHz (เครื่องตรวจสอบได้ 600MHz), RAM 128MB, HD 8G (เป็น /dev/hda)
    ลงทุนซื้อใหม่คือ CD-Writer 3 ตัว ตัวละ 630 บาท
    เนื่องจากจะไม่มีจอภาพ จึงจะใช้เสียงเป็นตัวแจ้งสถานะ

    จะทำให้มีการทำงานคือ

    • เมื่อเปิดเครื่อง ถาด /dev/hdb จะดีดออกเพื่อรอการอ่านข้อมูล ใช้เสียงดัง 1 ปี๊บ ทุก 3 วินาที
    • เมื่อใส่แผ่นต้นฉบับ เครื่องจะตรวจสอบแผ่น ถ้าแผ่นเสียจะแจ้งเป็นเสียง 6 ปี๊บ ทุก 3 วินาที เพื่อรออ่านแผ่นใหม่
    • ถ้าแผ่นดีจะอ่านจนเสร็จ แล้วทั้งสามถาดจะถูกดีดออกเพื่อรอการเขียนข้อมูล โดยใช้เสียงดัง 2 ปี๊บ ทุก 3 วินาที
    • จะเขียนวนไปเรื่อยจนกระทั่งปิดเครื่องเขียนด้วยถาดเปล่า ไดร์ฟนั้นก็จะหยุดทำงาน
    • เมื่อหยุดทำงานครบสามไดร์ฟแล้ว เครื่องจะปิดตัวเอง
    • ในระหว่างที่ถาดถูกดีดออก ถ้าไม่มีการใส่แผ่นใหม่เข้าไป หลังจากผ่านไป 100 ปี๊ป (หรืออาจเป็นเรากดถาดเปล่าดีดกลับเข้าไป) ถาดจะปิดตัวเอง และปิดการทำงานของถาดนั้นตลอดไป

    หมายเหตุ

    • เนื่องจากเป็นการทำงานแบบขนาน ทุกไดร์ฟจึงทำงานเป็นอิสระ
    • กำหนดให้ความเร็วในการเขียนเป็นแค่ 8x เพื่อให้ฮาร์ดดิสก์อ่านข้อมูลได้ทัน และเพิ่มความคงทนของข้อมูล
    • แผ่นต้นฉบับควรเป็นแผ่นที่ดี ถึงดีมาก ผลจึงจะออกมาดี

    วิธีการสร้าง
    เริ่มด้วยติดตั้งเดเบียน etch แบบไม่ติดตั้งอะไรเพิ่มเติมเลย
    (ผมใช้แผ่น sarge แล้วอัปเกรดเป็น etch เลือกใช้เคอร์เนล 2.6.18-3-686)

    ดามด้วยเอาแพกเกจที่จำเป็นที่สคริปต์เราต้องเรียกใช้ คือ
    eject สำหรับดีดถาดเข้า/ออก setcd สำหรับตรวจสถานะถาด และ cdrecord สำหรับการคัดลอกและเขียนข้อมูล
    $ su
    # aptitude install eject setcd cdrecord

    หมายเหตุ สำหรับแพกเกจ cdrecord นั้น เป็นแพกเกจเสมือน ซึ่งจะไปติดตั้งแพกเกจจริงชื่อ wodim อีกทีนึง
    ช่วงก่อนหน้านี้ แพกเกจ wodim มีความสามารถน้อยกว่า cdrecord ตัวจริงมาก bug ก็เยอะมาก
    แต่ตอน etch ใกล้จะออกนี้ แพกเกจนี้มีความสามารถทัดเทียมแพกเกจจริงแล้ว สามารถนำมาใช้งานได้จริงเลยครับ

    สร้างสคริปต์สำหรับให้ init เรียกใช้ตอนเปิดเครื่อง สำหรับให้อ่านแผ่นต้นฉบับ
    ตั้งชื่อว่า d.inittab-cpcd
    # vi d.inittab-cpcd

    #!/bin/bash
    # THIS IS THE COPY CD PROGRAM
    # PUT THE LINE
    #     D1:2345:once:/root/d.inittab-cpcd
    # IN /etc/inittab THEN REBOOT 
    # 
    
    beep() {
        echo -e "\a" 
    }
    beeperror() {
        for i in `seq 1 6`; do beep; sleep .15; done
    }
    wait_read_close() {
        # MAX RETRY LOOP = 100
        for (( i=0; i<=100; i++ )) 	
        do
            if setcd -i $DEV | grep open >> /dev/null; then
                # SLEEP 1 SECONDS 
                beep; sleep 3
            else
                readcd -clone dev=$DEV f=$TMP
                return
            fi
        done
        eject -t $1 
        /sbin/shutdown -h now
        return 
    }
    
    # TEMPOLARY IMAGE FILE
    DEV=/dev/hdb
    #KERNEL-2.4 #ATA="ATAPI:0,1,0"
    #KERNEL-2.6 #ATA=/dev/hdb
    ATA=/dev/hdb
    TMP=/tmp/x.bin
    
    rm -rf $TMP
    
    #CHECK 1ST CDR IS ORIGINAL CD THEN DUP. TO /tmp
    while ! [ -s $TMP ]
    do
        eject $DEV
        wait_read_close $DEV
        beep
    done
    
    # CALL MULTIPLE INSTANCE
    #d.func.cpcd DEVICE SPEED ISOFILE
    #KERNEL-2.4
    #/root/sh-func-cpcd /dev/hdb ATAPI:0,1,0 8 $TMP &
    #/root/sh-func-cpcd /dev/hdc ATAPI:1,0,0 8 $TMP &
    #/root/sh-func-cpcd /dev/hdd ATAPI:1,1,0 8 $TMP &
    #KERNEL-2.6
    /root/d.func-cpcd /dev/hdb /dev/hdb 8 $TMP &
    /root/d.func-cpcd /dev/hdc /dev/hdc 8 $TMP &
    /root/d.func-cpcd /dev/hdd /dev/hdd 8 $TMP &
    wait
    /sbin/shutdown -h now
    

    อีกไฟล์นึงเป็นฟังก์ชั่นสำหรับเขียนซีดี ตั้งชื่อว่า d.func-cpcd
    # vi d.func-cpcd

    #!/bin/bash
    beep() {
        echo -e "\a"
    }
    beepwrite() {
        beep; sleep .15; beep
    }
    beeperror() {
        for i in `seq 1 6`; do beep; sleep .15; done
    }
    wait_write_close() {
        # MAX RETRY LOOP = 100
        for (( i=0; i<=100; i++ ))
        do
            # WAIT FOR INSERT DISC
            if setcd -i $DEV | grep 'is open' >> /dev/null; then
                beepwrite; sleep 3
            # IF NO DISC INSERTED THEN BREAK
            elif setcd -i $DEV | grep 'No disc' >> /dev/null; then
                exit 1
            else
                cdrecord -clone -raw96r dev=$ATA speed=$SPEED driveropts=burnfree gracetime=2 -overburn -eject $TMP
                # LAST CHECK WRITING
                if setcd -i $DEV | grep 'error' >> /dev/null; then
                    beeperror; sleep 3
                else
                    eject $DEV
                    return
                fi
            fi
        done
        #END PROG
        eject -t $1
        exit 1 
    }
    if [ ! $1 ] && [ ! $2 ] && [ ! $3 ] && [ ! $4 ] ; then
        echo "   USAGE : $0 DRIVE ATAPI SPEED TEMP-IMG" 
        echo "   EXAM1 : $0 /dev/hdb ATAPI:0,1,0 4 /tmp/x.bin"
        exit 1
    fi
    
    DEV=$1
    ATA=$2
    SPEED=$3
    TMP=$4
    
    #CHECK IMAGE FILE
    if ! [ -f $TMP ] ; then
    	echo "  Image file $TMP does not existed"
    	exit 1
    fi
    
    #COPY TO NEW BLANK CD
    while [ -f $TMP ]
    do
    	eject $DEV
    	wait_write_close $DEV
    	beepwrite
    done
    

    ต่อไปแก้ไขไฟล์ /etc/inittab ให้มาเรียกใช้โปรแกรม d.inittab-cpcd ตอนเปิดเครื่อง
    # vi /etc/inittab
    (ไปบรรทัดล่างสุด กด G)

    ...
    D1:2345:once:/root/d.inittab-cpcd
    

    เสร็จแล้วก็รีบูตทดสอบได้เลยครับ

    อ้างอิง

    ท่านใดเก่งเชลล์สคริปต์ฝากช่วยปรับปรุง และเผยแพร่ ด้วยนะครับ

    debian: ปรับปรุงเครื่องคัดลอกซีดี บน Squeeze

    จะรีไซเคิลเครื่องเก่า มาทำเครื่องคัดลอกซีดีเพื่อถวายวัด

    สเปคเครื่องเก่าคือ AMD Sempron(tm) 2000+, RAM 512MB, HD 40G (เป็น /dev/sda)
    ลงทุนซื้อใหม่คือ CD-Writer 4 ตัว ตัวละ 545 บาท (IDE 3 และ SATA 1)
    เนื่องจากจะไม่มีจอภาพ จึงจะใช้เสียงเป็นตัวแจ้งสถานะ

    จะทำให้มีการทำงานคือ

    • เมื่อเปิดเครื่อง ถาด /dev/sr0 จะดีดออกเพื่อรอการอ่านข้อมูล ใช้เสียงดัง 1 ปี๊บ ทุก 3 วินาที
    • เมื่อใส่แผ่นต้นฉบับ เครื่องจะตรวจสอบแผ่น ถ้าแผ่นเสียจะแจ้งเป็นเสียง 6 ปี๊บ ทุก 3 วินาที เพื่อรออ่านแผ่นใหม่
    • ถ้าแผ่นดีจะอ่านจนเสร็จ แล้วทั้ง 4 ถาดจะถูกดีดออกเพื่อรอการเขียนข้อมูล โดยใช้เสียงดัง 2 ปี๊บ ทุก 3 วินาที
    • จะเขียนวนไปเรื่อยจนกระทั่งปิดเครื่องเขียนด้วยถาดเปล่า ไดร์ฟนั้นก็จะหยุดทำงาน
    • เมื่อหยุดทำงานครบสามไดร์ฟแล้ว เครื่องจะปิดตัวเอง
    • ในระหว่างที่ถาดถูกดีดออก ถ้าไม่มีการใส่แผ่นใหม่เข้าไป หลังจากผ่านไป 100 ปี๊ป หรือเรากดถาดเปล่าดีดกลับเข้าไป ถาดจะปิดตัวเอง และปิดการทำงานของถาดนั้นตลอดไป

    หมายเหตุ

    • เนื่องจากเป็นการทำงานแบบขนาน ทุกไดร์ฟจึงทำงานเป็นอิสระ
    • กำหนดให้ความเร็วในการเขียนเป็นแค่ 1x เพื่อให้ฮาร์ดดิสก์อ่านข้อมูลได้ทัน และเพิ่มความคงทนของข้อมูล
    • แผ่นที่เป็นเสียง Audio อ่านช้ามาก

    ธีการสร้าง

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

    • eject สำหรับดีดถาดเข้า/ออก
    • setcd สำหรับตรวจสถานะถาด และ
    • cdrdao สำหรับการคัดลอกและเขียนข้อมูล
      • # aptitude install eject setcd cdrdao

        สร้างสคริปต์สำหรับให้ rc.local เรียกใช้ตอนเปิดเครื่อง สำหรับให้อ่านแผ่นต้นฉบับ
        ตั้งชื่อว่า d.rc-cpcd

        # vi /usr/local/sbin/d.rc-cpcd
        #!/bin/bash
        
        # CD COPIER PROGRAM
        
        # PREREQUISITE:
        #       # aptitude install eject cdrdao setcd dvd+rw-tools
        #       file: d.rc-cpcd d.func-cpcd
        
        # PUT d.rc-cpcd AND d.func-cpcd IN /usr/local/sbin
        # THEN CALL d.rc-cpcd FROM /etc/rc.local
        
        #VARIABLE
        SRC=/dev/sr0        #SOURCE DRIVE
        TOC=/tmp/toc.bin    #TEMP.IMAGE FILE
        DSC="
        /dev/sr0
        /dev/sr1
        /dev/sr2
        /dev/sr3
        "               #DESTINATION DRIVE
        
        . /usr/local/sbin/d.common-func
        
        wait_read_close() {
            # MAX RETRY LOOP = 100
            for (( i=0; i<=100; i++ )); do
                if setcd -i $SRC | grep 'CD tray is open' >> /dev/null; then
                    # SLEEP 1 SECONDS
                    beep_close
                elif setcd -i $SRC | grep 'Drive is not ready' >> /dev/null; then
                    sleep 1
                else   
                    if setcd -i $SRC | grep 'No disc is inserted' >> /dev/null; then
                        break
                    else   
                        #DO READ
                        cdrdao  read-cd --device $SRC --read-raw --datafile /tmp/data.bin $TOC
                        return
                    fi
                fi
            done
            eject -t $SRC
            /sbin/shutdown -h now
            return
        }
        
        
        rm -rf $TOC
        
        
        #CHECK 1ST CDR IS ORIGINAL CD THEN DUP. TO /tmp
        while ! [ -s $TOC ]; do
            eject $SRC
            echo "eject $SRC"
            wait_read_close $SRC
            beep
        done
        
        
        # CALL MULTIPLE INSTANCE IN $DSC DRIVES
        for i in $DSC; do
            if [ "$i" ]; then
                /usr/local/sbin/d.func-cpcd $i $TOC &
            fi
        done
        wait
        /sbin/shutdown -h now
        

        อีกไฟล์นึงเป็นฟังก์ชั่นสำหรับเขียนซีดี ตั้งชื่อว่า d.func-cpcd

        # vi /usr/local/sbin/d.func-cpcd
        #!/bin/bash
        
        DEV=$1
        TOC=$2
        SPEED=1
        
        . /usr/local/sbin/d.common-func
        
        wait_write_close() {
            # MAX RETRY LOOP = 100
            for (( i=0; i<=100; i++ )); do
                # WAIT FOR INSERT DISC
                if setcd -i $DEV | grep 'is open' >> /dev/null; then
                    beep_write; sleep 3
                elif setcd -i $DEV | grep 'Drive is not ready' >> /dev/null; then
                    sleep 1
                elif setcd -i $DEV | grep 'No disc' >> /dev/null; then
                    # IF NO DISC INSERTED THEN BREAK
                    exit 1
                else   
                    # DO WRITE
                    cdrdao write --device $DEV --speed $SPEED --overburn -n $TOC
                    # LAST CHECK WRITING
                    if setcd -i $DEV | grep 'error' >> /dev/null; then
                        beep_error; sleep 3
                        eject $DEV
                    else   
                        eject $DEV
                        return
                    fi
                fi
            done
            #END PROG
            eject -t $DEV
            exit 1
        }
        
        if [ ! $1 ] && [ ! $2 ] ; then
            echo " USAGE : $0 DRIVE SPEED TEMP-IMG"
            echo " EXAM1 : $0 /dev/sr0 /tmp/toc.bin"
            exit 1
        fi
        
        
        #CHECK IMAGE FILE
        if ! [ -f $TOC ] ; then
            echo " Image file $TOC does not existed"
            exit 1
        fi
        
        
        #COPY TO NEW BLANK CD
        while [ -f $TOC ]; do
            eject $DEV
            wait_write_close
            beep_write
        done
        

        และสุดท้ายคือไฟล์ฟังก์ชั่นที่ใช้ร่วมกัน

        # vi /usr/local/sbin/d.common-func
        #!/bin/bash
        
        beep() {
            echo -e "\a"
        }
        
        beep_close() {
            beep; sleep 1;
        }
        beep_write() {
            beep; sleep .15; beep
        }
        
        beep_error() {
            for i in `seq 1 6`; do beep; sleep .15; done
        }
        

        ต่อไปแก้ไขไฟล์ /etc/rc.local ให้มาเรียกใช้โปรแกรม d.rc-cpcd ตอนเปิดเครื่อง

        # vi /etc/rc.local
        ...
        /usr/local/sbin/d.rc-cpcd
        exit 0
        

        เสร็จแล้วรีบูตทดสอบได้เลย

    debian: ปรับปรุงเครื่องคัดลอกแผ่นซีดีบน Lenny

    ปรับปรุงจาก debian: เครื่องคัดลอกแผ่นซีดี

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

    สมบัติของเครื่องที่นำมาใช้ทดสอบและรุ่นของเดเบียน
    cpu: AMD Athlon (tm) XP 2400+
    motherboard: ECS L7VMM2
    ram: DDR 512M
    harddisk: Seagate 10G เป็น /dev/hda
    cdrom: DVD-Writer Lite-on จำนวน 3 เครื่อง เป็น /dev/hdb /dev/hdc และ /dev/hdd ตามลำดับ
    os: Debian GNU/Linux 5.0 (2.6.26-2-686) - Lenny
    packages: wodim setcd dvd+rw-tools

    ความสามารถของโปรแกรม
    สามารถคัดลอก cd, vcd และ dvd ได้ เป็นแบบเข้า 1 ออก 3 และเขียนด้วยความเร็วต่ำสุดที่เป็นไปได้ ทั้งนี้เพื่อให้แผ่นมีความทนทานใช้งานได้ยาวนาน

    การทำงานของเครื่อง

    1. เมื่อเปิดเครื่อง ถาด /dev/hdb จะดีดออกมารับแผ่นต้นฉบับ เครื่องจะส่งเสียง 1 ปี๊ปเว้น 3 วินาที ไปเรื่อย ๆ จนกว่าจะใส่แผ่นต้นฉบับ หรือดังครบ 100 ครั้งเครื่องจะดับตัวเอง
    2. เมื่อใส่แผ่นต้นฉบับแล้ว เครื่องจะทำการอ่านข้อมูลไปเก็บในฮาร์ดดิสก์จนเสร็จ เครื่องเขียนซีดีทั้ง 3 ถาดจะดีดออกมาเพื่อรับแผ่นว่าง โดยจะส่งเส่ียง 2 ปี๊ปเว้น 3 วินาที ไปเรื่อย ๆ จนกว่าจะใส่แผ่นว่าง เว้นแต่ถ้าเขียนผิดพลาดจะส่งเสียง 6 ปี๊ปครั้งเดียว
    3. หากเสร็จแล้วให้กดถาดเปล่ากลับเข้าไป ซีดีเครื่องนั้นจะหยุดการทำงาน
    4. หลังจากหยุดการทำงานครบทั้ง 3 ถาดแล้ว เครื่องจะดับตัวเอง

    เริ่มสร้างด้วย...
    การติดตั้งเดเบียนแบบเปล่า ๆ ดูตัวอย่างจาก debianclub: การติดตั้ง Debian เป็น Desktop แต่ของเราไม่ต้องเลือก Desktop Environment

    ติดตั้งแพกเกจที่ต้องการ
    # aptitude install vim less wodim dvd+rw-tools setcd

    สร้างไฟล์เริ่มการทำงานอ่านซีดี ชื่อ d.inittab-cpcd
    # vi /root/d.inittab-cpcd

    #!/bin/bash
    # CD COPIER PROGRAM
    # PREREQUISITE:
    #       # aptitude install wodim setcd dvd+rw-tools
    #       file: d.inittab-cpcd d.func-cpcd
    # PUT THE LINE
    #       D1:2345:once:/root/d.inittab-cpcd
    # IN /etc/inittab THEN REBOOT
    #
    
    #VARIABLE
    DEV=/dev/hdb            #SOURCE DRIVE
    TMP=/tmp/x.bin          #TEMP.IMAGE FILE
    
    beep() {
        echo -e "\a"
    }
    beeperror() {
        for i in `seq 1 6`; do beep; sleep .15; done
    }
    wait_read_close() {
        # MAX RETRY LOOP = 100
        for (( i=0; i<=100; i++ ))
        do
            if setcd -i $DEV | grep open >> /dev/null; then
                # SLEEP 1 SECONDS
                beep; sleep 3
            else
                #TRY CLONE MODE
                readom -v dev=$DEV f=$TMP -clone retries=128 ts=128k
                ISCLONE=$?
                if [ $ISCLONE -ne 0 ]; then
                    readom -v dev=$DEV f=$TMP retries=128 ts=128k
                fi
                return
            fi
        done
        eject -t $1
        /sbin/shutdown -h now
        return
    }
    
    rm -rf $TMP
    
    #CHECK 1ST CDR IS ORIGINAL CD THEN DUP. TO /tmp
    while ! [ -s $TMP ]
    do
        eject $DEV
        wait_read_close $DEV
        beep
    done
    
    # CALL MULTIPLE INSTANCE IN 3 DRIVES /dev/hdb /dev/hdc AND /dev/hdd
    /root/d.func-cpcd /dev/hdb $ISCLONE 1 $TMP &
    /root/d.func-cpcd /dev/hdc $ISCLONE 1 $TMP &
    /root/d.func-cpcd /dev/hdd $ISCLONE 1 $TMP &
    wait
    /sbin/shutdown -h now
    

    # chmod 755 /root/d.inittab-cpcd

    สร้างไฟล์ฟังก์ชั่นเขียนซีดีแบบขนาน
    # vi /root/d.func-cpcd

    #!/bin/bash
    
    DEV=$1
    ISCLONE=$2
    SPEED=$3
    TMP=$4
    
    beep() {
        echo -e "\a"
    }
    beepwrite() {
        beep; sleep .15; beep
    }
    beeperror() {
        for i in `seq 1 6`; do beep; sleep .15; done
    }
    wait_write_close() {
        # MAX RETRY LOOP = 100
        for (( i=0; i<=100; i++ ))
        do
            # WAIT FOR INSERT DISC
            if setcd -i $DEV | grep 'is open' >> /dev/null; then
                beepwrite; sleep 3
            # IF NO DISC INSERTED THEN BREAK
            elif setcd -i $DEV | grep 'No disc' >> /dev/null; then
                exit 1
            else
                if [ $ISCLONE -ne 0 ]; then
                    wodim -v gracetime=2 dev=$DEV speed=$SPEED driveropts=burnfree -overburn -eject $TMP
                else
                    wodim -v gracetime=2 dev=$DEV speed=$SPEED driveropts=burnfree -raw96r -clone -overburn -eject $TMP
                fi
                # LAST CHECK WRITING
                if setcd -i $DEV | grep 'error' >> /dev/null; then
                    beeperror; sleep 3
                else
                    eject $DEV
                    return
                fi
            fi
        done
        #END PROG
        eject -t $1
        exit 1
    }
    if [ ! $1 ] && [ ! $2 ] && [ ! $3 ] ; then
        echo "   USAGE : $0 DRIVE SPEED TEMP-IMG"
        echo "   EXAM1 : $0 /dev/hdb 8 /tmp/x.bin"
        exit 1
    fi
    
    #CHECK IMAGE FILE
    if ! [ -f $TMP ] ; then
            echo "  Image file $TMP does not existed"
            exit 1
    fi
    
    #COPY TO NEW BLANK CD
    while [ -f $TMP ]
    do
            eject $DEV
            wait_write_close $DEV
            beepwrite
    done
    

    # chmod 755 /root/d.func-cpcd

    แก้ไข inittab ให้เรียกการทำงานตอนเริ่มต้นเปิดเครื่อง
    # vi /etc/inittab

    ...
    #COPY CD
    D1:2345:once:/root/d.inittab-cpcd
    

    เสร็จแล้ว บูตเครื่องแล้วใช้ได้เลย

    เที่ยวนี้มีเวลาทดสอบน้อย ฝากทดสอบกันเองนะครับ

    ปรับปรุงเครื่องคัดลอกแผ่นซีดี

    ปรับปรุงครั้งที่ 1

    • แก้ปัญหาแผ่นธรรมดาอ่านผิดพลาด ด้วยการพยายามแยกแผ่น
      • ถ้าเป็นตระกูล VCD ซึ่งเป็น multitrack ใช้ readcd+cdrecord -clone
      • แต่ถ้าเป็นพวก mp3 ธรรมดา ใช้ dd+cdrecord แบบ normal
    • เนื่องจากทดสอบด้วย debian-desktop (etch) เลยต้องเพิ่มฟังก์ชั่นการยกเลิกการเมาต์อัตโนมัติด้วย
    • ฝากทดสอบและช่วยปรับปรุงให้ด้วยครับ

    # vi d.inittab-cpcd

    #!/bin/bash
    # THIS IS THE CD COPIER PROGRAM
    # - REQUIRE PACKAGE: setcd eject cdrecord(wodim)
    # - PUT THE LINE
    #     D1:2345:once:/root/d.inittab-cpcd
    #   IN /etc/inittab THEN REBOOT
    # - PUT THESE 2 FILES IN /root
    # - RUN AS ROOT
    
    #if [ $USER != "ROOT" ]; then
    #    su
    #fi
    
    DEV=/dev/hdb        # SOURCE DRIVE
    TMP=/tmp/x.bin      # TEMP IMAGE FILE
    SPEED=8             # WRITE SPEED
    
    MNT="/mnt/tmp"      # TEMP MOUNT DIR
    
    beep() {
        echo -e "\a"
    }
    beeperror() {
        for i in `seq 1 6`; do beep; sleep .15; done
    }
    check_disk_type() {
        # RETURN 1=CLONE 2=NORMAL
        while mount | grep "$DEV "; do umount $DEV >> /dev/null; sleep 1; done
        if setcd -i $DEV | grep 'Disc found in drive: audio disc' >> /dev/null; then return 1; fi
        if ! [ -d $MNT ]; then mkdir $MNT; fi
        if mount | grep "$MNT "; then umount $MNT >> /dev/null; fi
        mount $DEV $MNT
        VAR=`ls $MNT | tr '[A-Z]' '[a-z]'`
        VAR=`echo $VAR | grep vcd | grep mpeg | grep segment | grep ext`
        while mount | grep "$DEV "; do umount $DEV >> /dev/null; sleep 1; done
        if [ "$VAR" == "" ]; then return 2; fi
        return 1
    }
    wait_read_close() {
        # MAX RETRY LOOP = 100
        for (( i=0; i<=100; i++ ))
        do
            umount $DEV
            if setcd -i $DEV | grep open >> /dev/null; then
                # SLEEP 3 SECONDS
                beep; sleep 3
            else
                check_disk_type
                ISCLONE=$?
                if [ $ISCLONE -eq 1 ]; then 
                  readcd -clone dev=$DEV f=$TMP
                else 
                  dd if=$DEV of=$TMP
                fi
                return $ISCLONE
            fi
        done
        eject -t $1 
        /sbin/shutdown -h now
        return
    }
    
    # BEGIN MAIN
    rm -rf $TMP
    
    #CHECK 1ST CDR IS ORIGINAL CD THEN DUP. TO /tmp
    while ! [ -s $TMP ]
    do  
        eject $DEV
        beep
        wait_read_close $DEV
    done
    
    ISCLONE=$?
    
    # CALL MULTIPLE INSTANCE TO DESTINATION DRIVES
    #d.func.cpcd DEVICE SPEED ISOFILE
    #KERNEL-2.6
    /root/d.func-cpcd /dev/hdb $ISCLONE $SPEED $TMP &
    /root/d.func-cpcd /dev/hdc $ISCLONE $SPEED $TMP &
    /root/d.func-cpcd /dev/hdd $ISCLONE $SPEED $TMP &
    wait
    /sbin/shutdown -h now

    # vi d.func-cpcd

    #!/bin/bash
    # THIS IS THE CD COPIER PROGRAM
    # - REQUIRE PACKAGE: setcd eject cdrecord(wodim)
    # - PUT THE LINE
    #     D1:2345:once:/root/d.inittab-cpcd
    #   IN /etc/inittab THEN REBOOT
    # - PUT THESE 2 FILES IN /root
    # - RUN AS ROOT
    
    DEV=$1
    ISCLONE=$2
    SPEED=$3
    TMP=$4
    
    beep() {
        echo -e "\a"
    }
    beepwrite() {
        beep; sleep .15; beep
    }
    beeperror() {
        for i in `seq 1 6`; do beep; sleep .15; done
    }
    wait_write_close() {
        # MAX RETRY LOOP = 100
        for (( i=0; i<=100; i++ ))
        do
            # WAIT FOR INSERT DISC
            if setcd -i $DEV | grep 'is open' >> /dev/null; then
                beepwrite; sleep 3
            # IF NO DISC INSERTED THEN BREAK
            elif setcd -i $DEV | grep 'No disc' >> /dev/null; then
                exit 1
            else
                while mount | grep "$DEV "; do umount $DEV >> /dev/null; sleep 1; done
                if [ $ISCLONE -eq 1 ]; then
                    cdrecord -clone gracetime=2 -raw96r dev=$DEV speed=$SPEED driveropts=burnfree -overburn -multi -xa1 -eject $TMP
                else
                    cdrecord gracetime=2 dev=$DEV speed=$SPEED driveropts=burnfree -overburn -eject $TMP
                fi
    
                # LAST CHECK WRITING
                if setcd -i $DEV | grep 'error' >> /dev/null; then
                    beeperror; sleep 3
                    exit 1
                else
                    eject $DEV
                    return
                fi
            fi
        done
        #END PROG
        eject -t $1
        exit 1
    }
    if [ ! $1 ] && [ ! $2 ] && [ ! $3 ] && [ ! $4 ] ; then
        echo "   USAGE : $0 DRIVE ISCLONE(1=CLONE,2=NORMAL) SPEED TEMP-IMG"
        echo "   EXAM1 : $0 /dev/hdb 1 4 /tmp/x.bin"
        exit 1
    fi
    
    #CHECK IMAGE FILE
    if ! [ -f $TMP ] ; then
            echo "  Image file $TMP does not existed"
            exit 1
    fi
    
    #COPY TO NEW BLANK CD
    while [ -f $TMP ]
    do
            eject $DEV
            wait_write_close
            beepwrite
    done

    inkscape: ตัวอย่างสคริปต์ส่วนขยาย สำหรับขยายหน้ากระดาษ

    งานต้นฉบับสำหรับส่งโรงพิมพ์ สำหรับหน้าที่มีภาพชิดขอบ เราจำเป็นต้องเผื่อขอบให้มากกว่าขนาดกระดาษจริงด้านละ 3 มม. เพื่อกันตัดขอบพลาด ดังนั้นเวลาเตรียมต้นฉบับ ต้องเผื่อขนาดหน้าไว้ +6 มม.เสมอ

    เราจะทำ Inkscape Script Extensions ด้วยไพธอน เพื่อใช้ในการนี้ในการรันแปลงไฟล์ภาพทีละหลาย ๆ ไฟล์ได้

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

    จะตั้งชื่อส่วนขยายนี้ว่า expand_3mm จะต้องสร้างไฟล์ 2 ไฟล์ คือ Extension description file (.inx) และ Python script file (.py) แล้วใส่ลงในไดเรคทอรี่ ~/.config/inkscape/extensions/ หรือหากต้องการให้ใช้ได้กับทุกคนก็ใส่ลงใน /usr/share/inkscape/extensions

    ดังนี้

    Extension description file: expand_3mm.inx

    $ vi ~/.config/inkscape/extensions/expand_3mm.inx
    <inkscape-extension>
      <_name>Expand 3mm</_name>
      <id>org.ekips.filter.expand_3mm</id>
      <dependency type="executable" location="extensions">expand_3mm.py</dependency>
      <dependency type="executable" location="extensions">inkex.py</dependency>
      <dependency type="executable" location="extensions">simpletransform.py</dependency>
      <effect>
        <object-type>all</object-type>
        <effects-menu>
           <submenu _name="Example"/>
        </effects-menu>
      </effect>
      <script>
        <command reldir="extensions" interpreter="python">expand_3mm.py</command>
      </script>
    </inkscape-extension>
    

    Python script file: expand_3mm.py

    $ vi ~/.config/inkscape/extensions/expand_3mm.py
    #!/usr/bin/env python
    
    # These two lines are only needed if you don't put the script directly into the installation directory
    import sys
    sys.path.append('/usr/share/inkscape/extensions')
    
    # We will use the inkex module with the predefined Effect base class.
    import inkex, simpletransform
    
    class Expand3mmEffect(inkex.Effect):
        """
        Example Inkscape effect extension.
        """
        def __init__(self):
            """
            Constructor.
            """
            # Call the base class constructor.
            inkex.Effect.__init__(self)
    
        def effect(self):
            """
            Effect behaviour.
            """
            #MOVE 3mm,3mm
            add = 10.629878067  #3mm
            transformation = 'translate(%s,%s)' % (add,add,) 
            transform = simpletransform.parseTransform(transformation)
            if self.selected:
                for id, node in self.selected.iteritems():
                    simpletransform.applyTransformToNode(transform, node)
            #EXPAND 6mm
            svg = self.document.getroot()
            width = float(svg.get('width')) + (add * 2)     #6mm
            height = float(svg.get('height')) + (add * 2)   #6mm
            svg.set('width','%s' % (width,))
            svg.set('height','%s' % (height,))
    
    # Create effect instance and apply it.
    effect = Expand3mmEffect()
    effect.affect()
    
    # vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 encoding=utf-8 textwidth=99
    

    เสร็จแล้ว
    ทดสอบว่าสคริปต์ใช้ได้หรือไม่ ด้วยการลองเปิด inkscape ดู จะเห็นอยู่ในเมนู Extensions -> Example -> Expand 3mm
    หรือผ่านบรรทัดคำสั่งว่า

    $ inkscape --verb-list | grep expand_3mm

    จะขึ้นมาว่า

    org.ekips.filter.expand_3mm: Expand 3mm
    org.ekips.filter.expand_3mm.noprefs: Expand 3mm (No preferences)
    

    ตัวอย่างการรันด้วยบรรทัดคำสั่ง

    เปิดไฟล์ demo.svg -> Select All in All Layers -> ขยายขนาด -> บันทึกกลับ -> ปิด inkscape

    $ inkscape demo.svg --verb=EditSelectAllInAllLayers --verb=org.ekips.filter.expand_3mm --verb=FileSave --verb=FileClose

    เสร็จแล้ว

    ที่มา

    บันทึกเทคนิกการแปลงไฟล์เสียง

    เอามาจาก

    การเปลี่ยนชื่อไฟล์

    แปลงวรรคในชื่อไฟล์เป็น underscore
    ใช้เทคนิกของโปรแกรม tr เช่นแปลงทุกไฟล์ที่มีนามสกุล mp3
    for i in *.mp3; do mv "$i" `echo $i | tr ' ' '_'`; done
    แปลงชื่อไฟล์ตัวใหญ๋เป็นตัวเล็ก
    for i in *.[Mm][Pp]3; do mv "$i" `echo $i | tr '[A-Z]' '[a-z]'`; done
    แปลงเฉพาะนามสกุล
    for i in *.MP3; do mv "$i" "`basename "$i" .MP3`.mp3"; done

    การแปลงเป็น WAV

    เลือกใช้ ffmpeg
    คำสั่งคือ
    ffmpeg -i infile.xxx -acodec pcm_s16le -ar 44100 -ac 2 -f wav outfile.wav

    (มีความรู้สึกว่าแปลงด้วย ffmpeg เสียงครบกว่าแปลงด้วย mplayer ซึ่งเสียงจะนุ่มลงเล็กน้อย)

    ถ้าแปลงทีละไดเรกทอรี่ คำสั่งคือ

    for i in *; do ffmpeg -i "$i" -acodec pcm_s16le -ar 44100 -ac 2 \
    -f wav "$i.wav"; done

    แทรกนิด แปลงจาก mp3 เป็น mp3 ผ่านไลบรารี libmp3lame แบบเล็ก แต่ฟังได้ดี

    for i in *; do ffmpeg -i "$i" -acodec libmp3lame -ab 24k -ar 11025 -ac 2 \
    "mp3/$i"; done
    แปลงจาก mp3 โดยใช้ mpg321
    คำสั่งคือ
    for i in *.mp3; do mpg321 -w "`basename "$i" .mp3`.wav" "$i"; done
    แปลงจาก mp3 โดยใช้ MAD
    คำสั่งคือ
    for i in *.mp3; do madplay -o "`basename "$i" .mp3`.wav" "$i"; done
    แปลงจาก mp3 โดยใช้ Lame
    คำสั่งคือ
    for i in *.mp3; do lame --decode "$i" "`basename "$i" .mp3`.wav"; done
    แปลงจาก OGG โดยใช้ ogg123
    คำสั่งคือ
    for i in *.ogg ; do ogg123 -d wav -f "`basename "$i" .ogg`.wav" "$i"; done
    แปลงจากรูปแบบอื่น ๆ โดยใช้ mplayer
    คำสั่งคือ
    for i in *.wma; do mplayer -vo null -vc dummy -af resample=44100 \
    -ao pcm:waveheader:file="${i%.wma}.wav" "$i" ; done

    รูปแบบอื่น

    จาก wav เป็น m4r
    aptitude install faac
    faac -b 16 -c 44100 -w --title "TITLE" --artist "ARTIST" INFILE.wav

    ได้ไฟล์ออกมาเป็น INFILE.m4a (คุณภาพดีกว่าแปลงจาก ffmpeg)

    Normalize (เกลี่ย) เสียง

    คำสั่งคือ

    normalize-audio -m *.wav

    หรือ

    mp3gain -c -r *mp3

    แต่งเสียง

    เกลี่ยเสียง ลดเสียงทุ้มลง 18db เพิ่มเสียงแหลม 3db

    sox --norm INFILE.wav OUTFILE.wav bass -24 treble +3

    Rip Audio

    cdda2wav -D /dev/cdrom -t TRACK 01

    update

    แปลงจาก mp3 เป็น wma โดยใช้ ffmpeg
    จากการทดลอง ได้ต่ำสุดถึง sampling=16000Hz bitrate=8kbits
    $ ffmpeg -i sound.mp3 -ar 16000 -ab 8 -ac 1 sound.wma
    อีกรุ่นนึง
    $ ffmpeg -i sound.mp3 -acodec wmav2 -ab 24k -ac 1 -ar 11025 sound.wma
    แปลงจาก rm เป็น wav โดยใช้ ffmpeg

    $ ffmpeg -i sound.rm sound.wav

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

    บีบอัดเล็กสุดแบบมีคุณภาพ
    $ lame -h -b 16 INFILE.XXX OUTFILE.mp3
    บีบเพื่อเก็บเป็นต้นฉบับ
    $ lame -h -b 128 INFILE.XXX OUTFILE.mp3
    update 520730 แปลงจาก mp3 ใหญ่เป็น mp3 เล็ก 32kbps 11.025kHz
    $ for i in *mp3; do mplayer -vo null -vc dummy -af resample=44100 -ao pcm:waveheader:file=/tmp/x.wav "$i"; ffmpeg -i /tmp/x.wav -acodec libmp3lame -ab 32k -ar 11025 -ac 2 "24k/$i"; done; rm /tmp/x.wav
    update 520815 แปลงจาก mp3 ใหญ่เป็น mp3 เล็ก 32kbps 24kHz
    $ lame --mp3input -m m --resample 24 -h INFILE.mp3 OUTFILE.mp3
    แปลงวีดีโอเป็น 3gp
    $ ffmpeg -i INFILE.mpg -f 3gp -vcodec mpeg4 -b 150000 -s 320x240 -r 18 -acodec libfaac -ab 64000 -ar 24000 -ac 2 OUTFILE.3gp

    หมายเหตุ
    การติดตั้งแพกเกจที่เกี่ยวข้อง

    ffmpeg
    $ sudo aptitude install ffmpeg
    mpg321
    $ sudo aptitude install mpg321
    MAD
    $ sudo aptitude install madplay
    lame
    $ sudo aptitude install lame
    ogg123
    $ sudo aptitude install vorbis-tools
    mplayer
    $ sudo aptitude install mplayer
    normalize
    $ sudo aptitude install normalize-audio
    cdda2wav
    $ sudo aptitude install cdda2wav

    งานซ่อม

    บันทึกการซ่อม และการแก้ไขแพกเกจเสียหาย

    Topic: 

    ubuntu: บันทึกการซ่อม grub

    คราวก่อนบันทึก การติดตั้งหลายระบบปฏิบัติการ ปรากฎว่าเครื่องมีปัญหานิดหน่อย เลยต้องติดตั้ง WinMe กลับเข้าไป ทำให้ grub หายหมด จึงต้องทดลองซ่อมให้ grub คืนมา

    คราวก่อน
    /dev/hda1 เป็น WinMe
    /dev/hda2 เป็น WinXP
    /dev/hda3 เป็น extend
    /dev/hda5 เป็น /boot
    /dev/hda6 เป็น swap
    /dev/hda7 เป็น /

    เริ่มซ่อมดังนี้
    บูตด้วยแผ่น Ubuntu Live CD 6.06.1 LTS

    เปิด Terminal
    Applications -> Accessories -> Terminal

    แปลงตัวเป็น root
    $ sudo su

    สร้างจุดเมานต์ /mnt/root
    # mkdir /mnt/root

    เมานต์ให้ /dev/hda7 มาอยู่ที่ /mnt/root
    # mount /dev/hda7 /mnt/root

    เตรียมการย้ายรูตไปอยู่ที่ /mnt/root โดยต้องเมานต์ process และลิงก์ device ไปที่ใหม่
    # mount -t proc none /mnt/root/proc
    # mount -o bind /dev /mnt/root/dev
    # mount -t sysfs none /mnt/root/sys

    ย้ายรูต
    # chroot /mnt/root /bin/bash

    เนื่องจากแยก /boot เป็น /dev/hda5 เลยต้องเมานต์ลงมาทับ /boot
    # mount /dev/hda5 /boot

    คราวนี้ก็ทำตามวิธีของ คุณหมอ DrRider จาก ThaiLinuxCafe ได้แล้วครับ
    # grub
    grub> root (hd0,4)

    Filesystem type is ext2fs, partition type 0x83

    grub> setup (hd0)

    Checking if "/boot/grub/stage1" exists... no
    Checking if "/grub/stage1" exists... yes
    Checking if "/grub/stage2" exists... yes
    Checking if "/grub/e2fs_stage1_5" exists... yes
    Running "embed /grub/e2fs_stage1_5 (hd0)"...  15 sectors are embedded.
    succeeded
    Running "install /grub/stage1 (hd0) (hd0)1+15 p (hd0,4)/grub/stage2 /grub/menu
    .lst"... succeeded
    Done.

    เรียบร้อยแล้วครับ ออกจาก grub ด้วยคำสั่ง quit แล้วก็บูตใหม่ได้เลย

    update

    • หากใช้แผ่นเดเบียนแบบดิบ ๆ เมื่อบูตและทำงานผ่านขั้นตอน chroot แล้ว ถ้าขึ้นข้อผิดพลาดว่า
      Error Opening terminal: Bterm

      ให้ใช้คำสั่งว่า
      # export TERM=linux
      แล้วก็จะสามารถทำงานต่อจนจบได้ครับ

    debian: บันทึกการซ่อม grub

    ทดลองซ่อม grub บนเดเบียน ข้อมูลคือ

       Device Boot      Start         End      Blocks   Id  System
    /dev/hda1               1        1217     9775521    c  W95 FAT32 (LBA)
    /dev/hda2   *        1218        1230      104422+  83  Linux
    /dev/hda3            1231        6769    44492017+   5  Extended
    /dev/hda5            1231        1293      506016   82  Linux swap / Solaris
    /dev/hda6            1294        3118    14659281   83  Linux
    /dev/hda7            3119        4335     9775521   83  Linux
    /dev/hda8            4336        5552     9775521   83  Linux
    /dev/hda9            5553        6769     9775521   83  Linux
    

    /boot คือ /dev/hda2 และ / คือ /dev/hda6

    ขั้นตอนมีดังนี้

    1. ใส่แผ่นติดตั้งเดเบียน
    2. เมื่อบูตจนถึงขั้นตอนติดตั้งเครือข่าย ให้กด ALT+F2 เพื่อออกมาสู่เชลล์
    3. ตรวจดูพาร์ติชั่นเก่าด้วยคำสั่ง
      # fdisk -l
    4. เตรียมการ chroot ไปยังพาร์ติชั่นของเดเบียนที่เราจะซ่อม
      # mkdir /mnt/root
      # mount /dev/ide/host0/bus0/target0/lun0/part6 /mnt/root

      ถ้าเป็นดิสก์แบบ sata เปลี่ยนจาก /dev/ide/... เป็น /dev/scsi/...
      lun0 คือ hda
      part6 คือ /dev/hda6 หรือ (hd0,5) ของ grub

    5. chroot ไปยังพาร์ติชั่นของเดเบียน
      # chroot /mnt/root
    6. ถ้าแยกบูตพาร์ติชั่นตามตัวอย่าง ต้องเมานต์ /boot ในขั้นตอนนี้ด้วย
    7. # mount /dev/hda2 /boot
      ถ้าเป็น sata เปลี่ยนจาก hda2 เป็น sda2

    8. ซ่อม grub
      # grub-install /dev/hda
      ถ้าเป็น sata เปลี่ยนจาก hda เป็น sda
    9. เรียบร้อยแล้ว รีบูตได้เลย
      # reboot

    เอามาจาก nixCraft - Restore Debian Linux Grub boot loader

    debian: อัปเดตไบออสด้วย Thumb Drive โดย freedos อาศัย grub2

    สมมุติว่าใช้เดเบียนรุ่นตั้งแต่ squeeze ขึ้นไป ซึ่งติดตั้ง grub2 เป็นตัวจัดการการบูตอยู่แล้ว

    เตรียมการ

    1. ที่ต้องใฃ้คือ Thumb Drive ที่ฟอร์แมตแล้ว (มีข้อมูลอยู่ก็ไม่เป็นไร ใช้เนื้อที่แค่ 1.44M ดิสเก็ตต์แผ่นเดียว โดยไม่ต้องฟอร์แมตใหม่) สมมุติว่าเมื่อเสียบช่อง usb แล้ว เขาจะเมานต์อัตโนมัติไว้ที่ /media/disk

    2. ติดตั้งแพคเกจ

    $ sudo aptitude install syslinux dosfstools

    3. เตรียมไฟล์อัปเดตไบออสจากผู้ผลิตเมนบอร์ดเอาไว้ให้เรียบร้อย ถ้าไฟล์ถูกบีบอัดเป็นไฟล์เดียว ให้แตกออกมาด้วย Archive Manager (File Roller) จะมี 2 ไฟล์ที่ต้องใช้ คือไฟล์ EXE สำหรับการรันไฟล์นึง และไฟล์ข้อมูลไบออสอีกไฟล์นึง

    สมมุติถ้าเป็นเมนบอร์ดกิกาไบต์ G31M-ES2L เมื่อแตกไฟล์ที่ดาวน์โหลดออกมาแล้วจะได้ไฟล์ FLASHSPI.EXE และ G31MES2L.F10 ตามลำดับ

    4. เอาไฟล์ freedos มา

    $ wget http://www.ibiblio.org/pub/micro/pc-stuff/freedos/files/distributions/1.0/fdboot.img

    5. เมานต์ขึ้นมาเอาไว้ที่ /mnt/tmp เพื่อลบไฟล์ที่ไม่ต้องการออก

    $ sudo mkdir -p /mnt/tmp
    $ sudo mount -o loop fdboot.img /mnt/tmp
    $ sudo rm -rf /mnt/tmp/{driver,fdconfig.sys,freedos}

    และคัดลอกไฟล์ไบออสเข้าไป

    จากตัวอย่างจะเป็นสองไฟล์ข้างต้น ให้ปรับเปลี่ยนเอาตามจริง

    $ sudo cp ~/Desktop/FLASHSPI.EXE /mnt/tmp
    $ sudo cp ~/Desktop/G31MES2L.F10 /mnt/tmp

    6. ถอดการเมานต์ คัดลอกไฟล์ไปยัง Thumb Drive

    $ sudo umount /mnt/tmp
    $ cp fdboot.img /media/disk

    7. คัดลอกไฟล์เคอร์เนลที่ต้องใช้ในการบูตคือ memdisk จากแพคเกจ syslinux และรีบูต

    $ cp /usr/lib/syslinux/memdisk /media/disk
    $ umount /media/disk
    $ sudo shutdown -r now

    ขั้นตอนบูต

    เมื่อเริ่มเข้า grub2 ให้กด "c" เพื่อเลือกใช้บรรทัดคำสั่ง และพิมพ์คำสั่งดังนี้

    set root=(hd1,1)
    linux16 /memdisk
    initrd16 /fdboot.img
    boot

    เครื่องจะบูตเข้า freedos ซึ่งเราสามารถแฟลชไบออสด้วยการรันโปรแกรมไบออสที่เราเตรียมการไว้แล้วข้างต้น
    (จากตัวอย่าง จะต้องสั่งรันด้วย FLASHSPI.EXE G31MES2L.F10 เป็นต้น)

    เมื่อเสร็จแล้ว ก็สามารถบูตเพื่อใช้งานไบออสใหม่ได้เลย

    เอามาจาก

    update

    ล่าสุดใช้แพ็คเกจ flashrom แทนได้แล้ว

    ติดตั้ง

    # aptitude install flashrom

    เก็บรอมเก่า

    # flashrom -r OLD.bin

    แฟลชรอมใหม่

    # flashrom -w NEW.bin

    เอามาจาก

    บันทึกแก้ไข sid บูตไม่ขึ้น

    เนื่องจากปัญหา initramfs ไม่ยอมสร้างไฟล์ initrd.img ทำให้ dpkg ค้างบูตไม่ได้

    บูตด้วยแผ่นติดตั้ง netinst ของ lenny
    ทำตามขั้นตอนไปเรื่อย ๆ จนถึงขั้นแบ่งพาร์ติชั่นดิสก์ แล้วจึงออกสู่เชลล์ busybox

    เนื่องจากเชลล์ busybox ไม่ฉลาดเหมือน bash จึงต้องใส่พารามิเตอร์ตามลำดับ

    # mount /dev/hda5 /mnt -t ext3
    # mount sysfs /mnt/sys -t sysfs
    # mount proc /mnt/proc -t proc
    # mount tmpfs /mnt/tmp -t tmpfs
    # chroot /mnt /bin/bash

    ตอนนี้เราย้าย root มาที่เครื่องที่ต้องการซ่อมแล้ว (ขอแทนเครื่องหมายพร้อมด้วย $#)
    หากต้องมีการแก้ไขเนื้อไฟล์

    $# TERM=linux
    $# vi XXXX.XXX

    แก้ไขงานตามความจำเป็น

    $# dpkg --reconfigure -a
    $# aptitude -f install
    $# exit

    เสร็จแล้ว บูตเครื่องใหม่ได้เลย

    # reboot
    Topic: 

    บันทึกการติดตั้งเซิร์ฟเวอร์เดเบียน 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: 

    ห้อง Gnome

    งานเกี่ยวกับ Gnome

    Topic: 

    gnome: บันทึกแก้รหัสอักขระ usb drive

    gnome ไม่ได้ตั้งค่าปริยาย สำหรับ usb disk ในการเข้ารหัสอักขระมาให้
    เวลาเราเสียบอุปกรณ์พวก Thumb drive เขาเลยเมานต์เป็นค่าปริยายของระบบ
    (น่าจะอยู่ที่เคอร์เนล CONFIG_NLS_DEFAULT="iso8859-1" ไม่แน่ใจครับ)
    ทุกครั้งที่เมานต์ เลยเมานต์เป็น iso8859-1 ทำให้อ่านชื่อไฟล์ภาษาไทยไม่ออก
    ถ้าจะให้อ่านออก เราต้องเมานต์ใหม่เป็น utf-8

    วิธีแก้ผ่านบรรทัดคำสั่งที่เคยใช้คือ
    $ sudo umount /media/DEVICENAME
    $ sudo mount -t vfat -o iocharset=utf-8 /dev/sdXX /media/DEVICENAME

    แต่ถ้าต้องทำทุกครั้งคงไม่สะดวกอย่างยิ่ง

    การนี้ gnome ได้จัดเตรียมเครื่องมือไว้ให้แล้ว คือ gconf-editor
    สำหรับ เดเบียน ใช้งานผ่านเมนู Application -> System Tools -> Configuration Editor
    โดยไปที่ / -> system -> storage -> defaults_options -> vfat
    คลิกขวาที่ mount_options -> Edit Key...
    กดปุ่ม Add เพื่อเพิ่ม Value

    New List Value: utf-8

    (ใส่ค่า utf-8 เฉย ๆ โดยไม่ต้องมีเครื่องหมาย =)

    เสร็จแล้ว ครั้งหน้าเวลาเราเสียบพวกอุปกรณ์ทาง usb เขาจะเมานต์โดยใช้ utf-8 เป็นค่าปริยาย

    อ้างอิง

    (ติ gnome อยู่อย่างคือ หาเอกสารยากชะมัด ยังดีที่ตั้งชื่อในการปรับตั้งไม่ยากเท่าไหร่ พอเดา ๆ ออก)

    gnome: เปลี่ยนค่า Open with

    เนื่องจากในการเปิดไฟล์ tif นั้น Gnome เลือกใช้ Image Viewer (eog) เป็นค่าปริยาย
    แต่สำหรับไฟล์ tif แบบหลายหน้า พบว่า Image Viewer ไม่สามารถดูหน้าถัดไปได้ คงดูได้เพียงหน้าแรกเท่านั้น
    ดังนั้นเราจะเปลี่ยนค่าปริยายสำหรับการเปิดไฟล์ tif ไปเป็น Document Viewer (evince) แทน

    ทำได้โดย
    คลิกขวาที่ไฟล์ภาพ -> Properties -> [TAB]Open With ->
    เปลี่ยนค่าปริยายมาเป็น Document Viewer -> Close

    เรียบร้อยแล้ว

    อ้างอิง
    How to edit Gnome's "Open With" list?

    Topic: 

    gnome: บันทึกการปรับตั้งคีย์บอร์ดภาษาไทย

    สำหรับเดสก์ทอปที่ใช้ Gnome เราสามารถปรับตั้งให้ใช้คีย์บอร์ดภาษาไทยได้ผ่านทางเมนู

    ตัวอย่างจากเดเบียน - sid ใช้เมนู System -> Preference -> Keyboard

    • เพิ่มภาษาไทย
      TAB:Layouts -> Add -> Thailand
    • เปลี่ยนปุ่มการสลับภาษา
      TAB:Layout Options -> Group Shift/Lock behavior -> ALT+Shift changes group.

    แต่ถ้าหากมีผู้ใช้งานเครื่องคอมพิวเตอร์เครื่องนั้นอยู่หลายคน เช่น ตามสถาบันการศึกษา
    การที่ต้องมาเปลี่ยนทุกคนจึงเป็นเรื่องลำบากอย่างยิ่ง

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

    • เพิ่มภาษาไทย
      $ sudo gconftool-2 --direct \
      --config-source xml:readwrite:/etc/gconf/gconf.xml.defaults \
      --type list --list-type string \
      --set /desktop/gnome/peripherals/keyboard/kbd/layouts [us,th]
    • เปลี่ยนปุ่มการสลับภาษา
      $ sudo gconftool-2 --direct \
      --config-source xml:readwrite:/etc/gconf/gconf.xml.defaults \
      --type list --list-type string \
      --set /desktop/gnome/peripherals/keyboard/kbd/options ["grp grp:alt_shift_toggle"]

    เท่านี้เราก็จะได้ค่าปริยายสำหรับผู้ใช้ทุกคนในเครื่องนี้แล้วครับ

    หมายเหตุ
    ตามตัวอย่างบรรทัดสุดท้าย ค่าที่ใช้ต้องเป็น "grp[TAB]grp:alt_shift_toggle" แต่ผมยังพิมพ์ TAB ใส่ลงในบรรทัดคำสั่งไม่เป็น เลยใช้เป็นเคาะวรรคแทน เมื่อป้อนบรรทัดคำสั่งเสร็จแล้ว จึงไปแก้ในไฟล์ /etc/gconf/gconf.xml.defaults/.%gconf-tree.xml โดยเปลี่ยนจากเคาะวรรคเป็น TAB แทนครับ :P

    อ้างอิง
    Gnome - Setting General Preferences

    บันทึก Customize เพิ่มเติม

    • ยกเลิกการล๊อกจอภาพเมื่อใช้ Screen Saver
      sudo gconftool-2 --direct \
      --config-source xml:readwrite:/etc/gconf/gconf.xml.defaults \
      --type bool \
      --set /app/gnome-screensaver/lock_enabled false

    gnome: เปิด numlock เป็นค่าปริยาย

    ลองบน เดเบียน Sid

    ต้องติดตั้งแพกเกจ numlockx
    $ sudo aptitude install numlockx

    ตั้งให้รันตอนล๊อกอินเข้า Gnome
    Sytem -> Preferences -> Sessions
    [TAB]Startup Programs -> New

    Name: Numlock
    Command: /usr/bin/numlockx on

    เสร็จแล้ว ล๊อกอินครั้งต่อไปจะเปิด Numlock เป็นค่าปริยาย

    แถมนิดนึง
    ถ้าต้องการบันทึก Sessions ไว้ทุกครั้ง ให้ไปที่
    [TAB]Session Options -> Automatic save changes to session
    ทุกครั้งที่ล๊อกอินเข้า gdm ใหม่ จะเปิดเดสก์ทอปที่เคยเปิดจากครั้งที่แล้วมาทั้งหมด

    อีกนิดนึงว่า สำหรับ terminal ในโหมดเท็กซ์ สามารถทำให้เปิด numlock เป็นค่าปริยายโดยเติมคำสั่งใน ~/.bashrc
    $ vi ~/.bashrc

    ...
    if [[ `tty` == /dev/tty* ]];  then
            setleds +num
    fi

    อ้างอิง

    Topic: 

    gedit กับ TIS-620

    วิธีแก้ gedit ให้สามารถใช้ภาษาไทยที่เข้ารหัสแบบ TIS-620

    $ gconf-editor

    ตั้งค่าที่

    /apps/gedit-2/preferences/encoding/auto_detected

    เพิ่มให้มีค่า TIS-620 โดยเลื่อนให้ขึ้นมาอยู่บน ๆ ตามความถี่ในการใช้งานของเรา
    เช่น ของผมตั้งเป็น

    UTF-8
    CURRENT
    TIS-620
    ISO-8859-1
    UTF-16

    เป็นต้น

    ต่อไปก็สามารถใช้แก้ไขไฟล์ที่เข้ารหัสเป็น TIS-620 ได้แล้ว

    เอามาจาก

    update

    • Gnome2
      $ gconftool-2 -t list --list-type string -s /apps/gedit-2/preferences/encodings/auto_detected [UTF-8,TIS-620,CURRENT,ISO-8859-15,UTF-16]
    • 20130117 - สำหรับ Gnome3 ตั้งให้ tis-620 มีความสำคัญเหนือ Default
      $ gsettings set org.gnome.gedit.preferences.encodings auto-detected "['TIS-620', 'UTF-8', 'CURRENT']"

      ที่มา - askubuntu.com: How to set default encoding for non UTF encoded documents system wide?

    Topic: 

    sql: บันทึก Benchmark

    บันทึกผลของการเลือกใช้ SELECT แบบต่าง ๆ
    เดเบียน Etch, Postgresql 8.1

    มีตารางสองตาราง

    1. ตาราง ft_word เก็บคำศัพท์ประมาณ 20,000 เรคคอร์ด
      • wordid = ไอดีคำศัพท์
      • word = คำศัพท์
    2. ตาราง ft เก็บการกระจายศัพท์ มีประมาณ 6 แสนเรคคอร์ด
      • wordid = ไอดีคำศัพท์
      • mftid = ไอดีที่จะชี้ไปที่ตารางเก็บ
      • occur = จำนวนคำศัพท์ที่พบในตารางเก็บ

    การค้นหาเพียงศัพท์เดียว ลักษณะจะเหมือนกัน คำสั่งคือ

    SELECT mftid AS mftid FROM ft WHERE wordid IN (SELECT wordid FROM ft_word WHERE word LIKE '%various%')

    จับเวลาได้ 35ms

    แบบแรก ใช้คำสั่ง INTERSECT
    สองคำศัพท์

    (SELECT mftid AS mftid FROM ft WHERE wordid IN (SELECT wordid FROM ft_word WHERE word LIKE '%various%'))
    INTERSECT
    (SELECT mftid AS mftid FROM ft WHERE wordid IN (SELECT wordid FROM ft_word WHERE word LIKE '%types%'))

    จับเวลาได้ 400ms
    สามคำศัพท์ ได้ 440ms
    สี่คำศัพท์ ได้ 450ms
    ห้าคำศัพท์ ได้ 500ms
    หกคำศัพท์ ได้ 520ms ด้วยคำสั่ง

    (SELECT mftid FROM ft WHERE wordid IN (SELECT wordid FROM ft_word WHERE word LIKE '%various%'))
    INTERSECT
    (SELECT mftid FROM ft WHERE wordid IN (SELECT wordid FROM ft_word WHERE word LIKE '%types%'))
    INTERSECT
    (SELECT mftid FROM ft WHERE wordid IN (SELECT wordid FROM ft_word WHERE word LIKE '%debugging%'))
    INTERSECT
    (SELECT mftid FROM ft WHERE wordid IN (SELECT wordid FROM ft_word WHERE word LIKE '%builds%'))
    INTERSECT
    (SELECT mftid FROM ft WHERE wordid IN (SELECT wordid FROM ft_word WHERE word LIKE '%enable%'))
    INTERSECT
    (SELECT mftid FROM ft WHERE wordid IN (SELECT wordid FROM ft_word WHERE word LIKE '%available%'))
    

    เวลาที่ใช้ในการค้นหาคำศัพท์ เพิ่มขึ้นมากในครั้งแรก และเพิ่มขึ้นเล็กน้อยในครั้งต่อ ๆ มา

    แบบที่สอง ใช้คำสั่ง SELECT ต่อ ๆ กัน
    สองคำศัพท์

    SELECT DISTINCT a1.mftid FROM 
      (SELECT mftid FROM ft WHERE wordid IN (SELECT wordid FROM ft_word WHERE word LIKE '%various%')) AS a1,
      (SELECT mftid FROM ft WHERE wordid IN (SELECT wordid FROM ft_word WHERE word LIKE '%types%')) AS a2
    WHERE a1.mftid=a2.mftid

    จับเวลาได้ 100ms
    สามคำศัพท์ ได้ 80ms
    สี่คำศัพท์ ได้ 150ms
    ห้าคำศัพท์ ได้ 300ms
    หกคำศัพท์ ได้ 450ms ด้วยคำสั่ง

    SELECT DISTINCT a1.mftid FROM 
      (SELECT mftid FROM ft WHERE wordid IN (SELECT wordid FROM ft_word WHERE word LIKE '%various%')) AS a1,
      (SELECT mftid FROM ft WHERE wordid IN (SELECT wordid FROM ft_word WHERE word LIKE '%types%')) AS a2,
      (SELECT mftid FROM ft WHERE wordid IN (SELECT wordid FROM ft_word WHERE word LIKE '%debugging%')) AS a3,
      (SELECT mftid FROM ft WHERE wordid IN (SELECT wordid FROM ft_word WHERE word LIKE '%builds%')) AS a4,
      (SELECT mftid FROM ft WHERE wordid IN (SELECT wordid FROM ft_word WHERE word LIKE '%enable%')) AS a5,
      (SELECT mftid FROM ft WHERE wordid IN (SELECT wordid FROM ft_word WHERE word LIKE '%available%')) AS a6
    WHERE a1.mftid=a2.mftid AND a2.mftid=a3.mftid AND a3.mftid=a4.mftid AND a4.mftid=a5.mftid AND a5.mftid=a6.mftid

    เวลาในการค้นหา เพิ่มขึ้นเกือบเท่าตัวในทุก ๆ คำศัพท์ที่เพิ่มขึ้นไป

    UPDATE
    ทดลองไป ๆ มา ๆ พบว่าความช้าไปตกอยู่ที่การค้นหาศัพท์
    ไม่ได้อยู่ที่คำสั่ง INTERSECT
    ถ้ามี wildcard "%" อยู่หน้าศัพท์จะทำให้ผลการค้นหาช้าลงมาก
    ซึ่งในการใช้งานจริง การค้นหามักจะเอา wildcard อยู่ข้างหลังอยู่แล้ว
    จึงปรับคำสั่งในวิธีแรกเป็น

    (SELECT mftid FROM ft WHERE wordid IN (SELECT wordid FROM ft_word WHERE word LIKE 'various%'))
    INTERSECT
    (SELECT mftid FROM ft WHERE wordid IN (SELECT wordid FROM ft_word WHERE word LIKE 'types%'))
    INTERSECT
    (SELECT mftid FROM ft WHERE wordid IN (SELECT wordid FROM ft_word WHERE word LIKE 'debugging%'))
    INTERSECT
    (SELECT mftid FROM ft WHERE wordid IN (SELECT wordid FROM ft_word WHERE word LIKE 'builds%'))
    INTERSECT
    (SELECT mftid FROM ft WHERE wordid IN (SELECT wordid FROM ft_word WHERE word LIKE 'enable%'))
    INTERSECT
    (SELECT mftid FROM ft WHERE wordid IN (SELECT wordid FROM ft_word WHERE word LIKE 'available%'))
    

    ได้ผลออกมาที่ 145 ms

    แต่การใช้งานจริงจะแก้คำสั่ง INTERSECT เป็น UNION แทน
    แล้วเติมฟิลด์น้ำหนักให้กับคำสั่ง
    คำสั่งสุทธิจะกลายเป็น

    SELECT a.weight, c.parent, c.field, b.parentid, a.occur FROM (
      SELECT DISTINCT SUM(weight) AS weight, mftid, SUM(occur) AS occur FROM 
        (
        SELECT 30 AS weight, mftid, occur FROM ft WHERE wordid IN (SELECT wordid FROM ft_word WHERE word='various')
        UNION
        SELECT 30 AS weight, mftid, occur FROM ft WHERE wordid IN (SELECT wordid FROM ft_word WHERE word='types')
        UNION
        SELECT 30 AS weight, mftid, occur FROM ft WHERE wordid IN (SELECT wordid FROM ft_word WHERE word='debugging')
        UNION
        SELECT 30 AS weight, mftid, occur FROM ft WHERE wordid IN (SELECT wordid FROM ft_word WHERE word='builds')
        UNION
        SELECT 30 AS weight, mftid, occur FROM ft WHERE wordid IN (SELECT wordid FROM ft_word WHERE word='enable')
        UNION
        SELECT 30 AS weight, mftid, occur FROM ft WHERE wordid IN (SELECT wordid FROM ft_word WHERE word='available')
        UNION
        SELECT 10 AS weight, mftid, occur FROM ft WHERE wordid IN (SELECT wordid FROM ft_word WHERE word LIKE 'various%')
        UNION
        SELECT 10 AS weight, mftid, occur FROM ft WHERE wordid IN (SELECT wordid FROM ft_word WHERE word LIKE 'types%')
        UNION
        SELECT 10 AS weight, mftid, occur FROM ft WHERE wordid IN (SELECT wordid FROM ft_word WHERE word LIKE 'debugging%')
        UNION
        SELECT 10 AS weight, mftid, occur FROM ft WHERE wordid IN (SELECT wordid FROM ft_word WHERE word LIKE 'builds%')
        UNION
        SELECT 10 AS weight, mftid, occur FROM ft WHERE wordid IN (SELECT wordid FROM ft_word WHERE word LIKE 'enable%')
        UNION
        SELECT 10 AS weight, mftid, occur FROM ft WHERE wordid IN (SELECT wordid FROM ft_word WHERE word LIKE 'available%')
        ) 
      AS a GROUP BY a.mftid ) 
    AS a, ft_main AS b, ft_parent AS c
    WHERE a.mftid=b.mftid AND b.pftid=c.pftid
    ORDER BY weight DESC, occur DESC
    

    ได้ผลอยู่ที่ 450 ms

    ถ้าเติม LIMIT 10 OFFSET 0 ต่อท้ายคำสั่ง
    เวลาค้นจะเหลือเพียง 200ms

    หมายเหตุ
    ตารางเพิ่มเติมจากตัวอย่างคือ

    1. ตาราง ft_main ใช้เก็บ
      • mftid คือ ไอดีของตารางนี้
      • pftid คือ ไอดีของตารางที่เก็บข้อมูลของ parent
    2. ตาราง ft_parent ใช้เก็บ
      • pftid ไอดีของตารางนี้
      • parent ชื่อตาราง parent
      • field ชื่อฟิลด์ของ parent

    sql: บันทึกการแปลงตาราง

    ความรู้ sql น้อยมาก ขออนุญาตบันทึกเอาไว้ดูครับ

    ต้องการแปลงโครงสร้างตารางคือ
    สมมุติว่ามีตาราง phone มีโครงสร้างดังนี้

    CREATE TABLE phone 
    (   phoneid	SERIAL,
        custid      VARCHAR(10),
        name        VARCHAR(128),
        phone       VARCHAR(64),
        cat         VARCHAR(64),
        rem         TEXT
    );

    เมื่อใส่ข้อมูลแล้ว ต้องการเปลี่ยนสดมถ์ custid เป็นข้อมูลชนิด integer
    พบว่าไม่สามารถเปลี่ยนตรง ๆ โดยใช้คำสั่ง ALTER ได้

    แปลงโดยใช้ตาราง temp เป็นตัวทด
    ใช้เงื่อนไข CASE บวกฟังก์ชั่น CAST ในการแปลง

    CREATE TABLE temp AS 
    SELECT phoneid, 
      CASE WHEN custid='' THEN 0 ELSE CAST(custid AS INT) END AS custid,
      name,
      phone,
      cat
    FROM phone;

    แล้วจึงเอาตารางใหม่ไปทับตารางเก่า

    DROP TABLE phone;
    SELECT * INTO phone FROM temp;
    DROP TABLE temp;

    หรือ

    DROP TABLE phone;
    ALTER TABLE temp RENAME TO phone;

    ทดลองกับเดเบียน etch, postgresql-8.1, phppgadmin-4.0.1

    Topic: 

    sql: บันทึกค้นข้อมูลแบบให้น้ำหนัก

    ขออนุญาตบันทึกการทดลองเพื่อเอาไว้ดูครับ

    สมมุติถ้ามีตาราง t_phone เอาไว้เก็บข้อมูลลูกค้า มีโครงสร้างคือ

    CREATE TABLE t_phone 
    (   phoneid	SERIAL,
        custid      VARCHAR(10),
        name        VARCHAR(128),
        phone       VARCHAR(64),
        cat         VARCHAR(64),
        rem         TEXT
    );
    

    ถ้าเราต้องการค้นข้อมูลจากฟิลด์ rem เป็นข้อมูลสองชุด เช่น
    ค้นว่าในฟิลด์ rem ต้องมีข้อมูลว่า 'S1' และ/หรือ 'S2' ประกอบอยู่
    เราอาจใช้เงื่อนไขคือ

    SELECT name, phone, cat, rem FROM t_phone
    WHERE rem LIKE "%S1%S2"
        OR rem LIKE "%S1%"
        OR rem LIKE "%S2%"
    

    ข้อมูลที่ได้จะเป็นข้อมูลผสม โดยไม่สามารถเรียงลำดับความสำคัญของข้อมูลได้

    ในที่นี้ความต้องการคือ ต้องการให้เรียงลำดับความสำคัญว่า ถ้าพบข้อมูล "S1" และ "S2" อยู่ด้วยกัน จะมีความสำคัญมากกว่า แต่ถ้าพบเพียงข้อมูลตัวเดียว จะให้มีความสำคัญน้อยกว่า

    ในที่นี้จึงต้องใช้คำสั่ง CASE เข้ามาช่วย โดยเพิ่มฟิลด์ score เขามาในการจัดเรียง
    คำสั่งจะกลายเป็น

    SELECT
    CASE WHEN rem LIKE '%S1%S2%' THEN 4
      WHEN rem LIKE '%S1%' THEN 2
      WHEN rem LIKE '%S2%' THEN 2
      ELSE 0
    END AS score,
    name, phone, cat, rem FROM t_phone
    WHERE rem LIKE "%S1%"
        OR rem LIKE "%S2%"
    ORDER BY score DESC
    

    เราจะได้ผลลัพธ์ที่ถูกเรียงลำดับตามความสำคัญเรียบร้อย

    อีกวิธีนึงคือใช้ UNION ALL

    SELECT SUM(score) AS score, name, phone, rem FROM
    (
      SELECT 1 AS score, name, phone, rem FROM t_phone 
      WHERE rem LIKE '%S1%' 
    UNION ALL
      SELECT 1 AS score, name, phone, rem FROM t_phone
      WHERE rem LIKE '%S2%' 
    ) AS a
    GROUP BY a.name, a.phone, a.rem
    ORDER BY score DESC
    

    วิธีหลังดูจะช้ากว่า แต่ก็น่าจะเขียนโค๊ดง่ายกว่า

    Topic: 

    ฟอนต์

    รวมหมวดฟอนต์

    เอาไฟล์มาสรุปให้ดาวน์โหลดได้ง่ายขึ้นครับ

    ฟอนต์ที่ทดลองทำเอง
    เช่น Verachart เป็นต้น ดาวน์โหลดเฉพาะไฟล์ ttf ttfonts.tar.gz
    เมื่อแตกไฟล์แล้ว เอาไฟล์ ttf ทั้งหมดไปใส่ไว้ภายใต้ ~/.fonts หรือ /usr/share/fonts/truetype และ
    ทำลิงก์ไฟล์ 65-z-ttfonts.conf ไปยัง /etc/fonts/conf.d

    $ sudo ln -sf 65-z-ttfonts.conf /etc/fonts/conf.d
    $ fc-cache -fv
    ฟอนต์ทดแทนฟอนต์วินโดวส์
    คือ Angsima, Bromlila และ Corada ดาวน์โหลดเฉพาะไฟล์ ttf thaifont-abc.tar.gz
    เมื่อแตกไฟล์แล้ว เอาไฟล์ ttf ทั้งหมดไปใส่ไว้ภายใต้ ~/.fonts หรือ /usr/share/fonts/truetype และ
    ทำลิงก์ไฟล์ 65-z-thaifont-abc.conf และไฟล์ 90-1-thaifont-abc-synthetic.conf ไปยัง /etc/fonts/conf.d

    $ sudo ln -sf 65-z-thaifont-abc.conf /etc/fonts/conf.d
    $ sudo ln -sf 90-1-thaifont-abc-synthetic.conf /etc/fonts/conf.d
    $ fc-cache -fv
    
    ฟอนต์ที่เติมภาษาไทยเข้าไป
  • DejaVu Sans Thai - ประวัติ
  • DejaVu Serif Thai - ประวัติ
  • DejaVu Sans Mono Thai - ประวัติ
  • Droid Sans - ประวัติ
  • Droid Serif , Droid Serif ThaiLao และ Droid Serif Web - ประวัติ
  • Droid Sans Mono - ประวัติ
  • Roboto , Roboto ThaiLao และ Roboto Web - ประวัติ
  • ฟอนต์อื่น ๆ ที่ทำ Truetype Hinting เรียบร้อยแล้ว
  • ฟอนต์ TlwgTypott (Monospace) - ประวัติ
  • ฟอนต์ Lomaputta - โลมาบุตร (Sans Serif) - ประวัติ
  • ฟอนต์ Norasiputta - นรสีห์บุตร (Serif) - ประวัติ
  • ฟอนต์ Waree Sans - ประวัติ
  • ฟอนต์ Waree Serif - ประวัติ
  • ฟอนต์ Waree Sans Mono - ประวัติ
  • ฟอนต์เตยหอม (Taeyhom) - ประวัติ
  • ***update*** ฟอนต์รุ่นใหม่ของ ltn มีการทดแทนฟอนต์ Browallia ด้วย Garuda อยู่แล้ว (กำหนดเลขไฟล์เป็น 90-XXX) หากไม่ต้องการใช้ชุดทดแทนของฟอนต์นี้ ให้เปลี่ยนชื่อไฟล์ 90-1-thaifont-abc-synthetic.conf เป็นตัวเลขที่มากกว่า 90 เช่น 95-1-thaifont-abc-synthetic.conf เป็นต้น

    ดาวน์โหลดแบบรวมซอร์ส sfd ด้วย

    บันทึกเกร็ดฟอนต์ของเดเบียน
  • ไฟล์คอนฟิกของฟอนต์ในเดเบียนอยู่ที่ /etc/fonts/conf.d ซึ่งเดเบียนบรรจุไฟล์จริงไว้ที่ /etc/fonts/conf.avail แล้วใช้โยงลิงก์มาที่ conf.d อีกทีนึง
  • ถ้ามีชื่อฟอนต์ซ้ำกันในไฟล์คอนฟิก ชื่อไฟล์ที่มาทีหลังจะมีความสำคัญน้อยกว่าชื่อไฟล์ที่มาถึงก่อน เช่น 65-1-ttfonts.conf จะสำคัญมากกว่า 65-2-ttfonts.conf
    ดังนั้นถ้าจะ override ฟอนต์ของระบบ จะต้องตั้งให้ชื่อให้เรียงอยู่ต้น ๆ
  • บันทึกเกร็ดฟอนต์ทดแทน
  • ถ้าใช้ฟอนต์ทดแทนฟอนต์วินโดวส์ (Angsima, Bromlila, Corada) บน openoffice เราสามารถกำหนดเป็น style โดยกำหนดฟอนต์ดังนี้ เช่น สไตล์ Normal กำหนดเป็น
    Angsima; Angsana New

    ทั้งในหมวด Western text font และ CTL font
    จะให้ผลที่แน่นอนกว่าการใช้การทดแทนฟอนต์ใน openoffice เอง นอกจากนี้ยังสามารถเปิดใช้งานทั้งใน Microsoft Word และ OpenOffice Writer ได้โดยไม่เกิดปัญหาครับ

  • บันทึกเรื่องฟอนต์ของกูเกิล
  • เดิมกูเกิลใช้ฟอนต์ชื่อ Droid Sans และ Droid Serif โดยมีภาษาไทยคือ Droid Sans Thai ตัวเดียว ไม่มีตัวหนา
  • ตั้งแต่ Android รุ่น 4 กูเกิลเพิ่มฟอนต์หลักคือ Roboto
  • ตั้งแต่ Android รุ่น 4.3 เปลี่ยนชื่อฟอนต์รองที่เป็นภาษาอื่น ๆ เป็น Noto รองรับหลายภาษามาก เริ่มสมบูรณ์มากขึ้นในรุ่น 4.4.2 ดาวน์โหลดฟอนต์ Noto ได้ที่ Noto Fonts
  • AttachmentSize
    Package icon rtv.zip45.32 KB
    Topic: 

    ubuntu: แปลงฟอนต์วินโดวส์

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

    • ติดตั้ง FontForge
      $ sudo aptitude install fontforge
    • ดาวน์โหลดซอร์สโค๊ดของฟอนต์ไทย เพื่อเอาไฟล์ sfd มาทำเป็นต้นแบบ - ไฟล์นามสกุล sfd เป็นไฟล์ข้อมูลของโปรแกรม FontForge
    • แตกไฟล์ และนำเอาไฟล์ Loma.sfd จากไดเรคทอรี่ ./thaifonts-scalable-0.4.5/nectec มาทำเป็น template
      $ cp ./thaifonts-scalable-0.4.5/nectec template.sfd
    • เปิดไฟล์ template
      $ fontforge template.sfd
    • Merge เทมเพลตเข้ากับไฟล์ฟอนต์ที่จะแก้ไข ใช้เมนู Element -> Merge Font... จะได้ชุดอักษรจากฟอนต์เก่ามาทับบนเทมเพลต
    • ตั้งชื่อฟอนต์โดยใช้เมนู Element -> Font Info...
    • ตั้ง Encode เป็น Unicode BMP โดยใช้เมนู Encoding -> Reencode -> ISO 16041-1 (UNICODE, BMP)
    • ย้ายชุดอักษรเก่าให้มาทับบนตำแหน่งของยูนิโค๊ด โดยเริ่มที่ชุดของภาษาอังกฤษคือ SPACE (0x0020) จนถึง TILDE(~ = 0x007E) และชุดของภาษาไทยคือ ก.ไก่ (0x0E01) จนถึง ฟองมัน (0x0E5B) และชุดเศษที่เหลือคือ เริ่มตั้งแต่ตำแหน่ง 0xF700 เป็นต้นไป โดยเทียบจากชุดของเทมเพลต (ใช้วิธี cut&paste ธรรมดา ใช้คีย์ ctrl+alt+[ และ ] ในการเลื่อนหาชุดฟอนต์)
    • แก้ไขข้อมูลในชุดสระ คือ อั อิ อี อึ อื อุ อู อฺ อ็ โดยแก้เพียงชุดแรกในช่วง 0x0E01 ถึง 0x0E5B เท่านั้น โดยเติม anchor ตามตัวอย่างจากเทมเพลต ด้วยเมนู คลิกขวา -> Add anchor
    • ตรวจดูความเรียบร้อย แล้วสั่งผลิตฟอนต์จากเมนู File -> Generate Fonts...

    ฟอนต์ที่ได้จะสามารถใช้ได้กับลินุกซ์ที่ส่วนใหญ่จะใช้รหัสฟอนต์เป็นยูนิโค๊ด และมีความสามารถในการแก้สระลอยได้ในตัวเอง

    หมายเหตุ

    • อาจเลือก AutoHint ได้ตามความเหมาะสม

    ลองทำฟอนต์ทดแทน ๑ - Angsima

    ช่วงนี้กำลังทดลองเอา OpenOffice บนลินุกซ์ มาใช้แทน Microsoft Office บนวินโดวส์ ได้ผลดีพอควร แต่มาติดปัญหาสำคัญคือฟอนต์ในเอกสารเก่าจำนวนมาก เป็นฟอนต์บนวินโดวส์ คือ Angsana New และ Browallia New
    ถ้าจะแก้ ก็ต้องแก้กันเป็นจำนวนมาก จึงคิดว่าถ้าเราทำฟอนต์ใหม่ทดแทน Angsana ให้สามารถเปิดเอกสารเก่าที่เป็นฟอนต์ Angsana โดยไม่เสียรูปแบบ น่าจะเป็นทางออกที่ดีกว่า
    จึงทดลองนำฟอนต์นรบุตรมาแปลงขนาดความกว้างตัวอักษรให้เท่ากับ Angsana ตั้งชื่อว่า Angsima (อังสิมา) แปลว่าอะไรก็ไม่รู้ แต่ตั้งชื่อให้คล้าย เพื่อให้เวลาเราเลือกฟอนต์ รายชื่อจะได้อยู่ใกล้ ๆ กัน
    ทดลองรุ่นแรก แบบหยาบ ๆ ได้ผลคือ

    ภาษาไทยได้แล้ว แต่ภาษาอังกฤษยังไม่ได้
    ( ทำส่วนโค้งเป็นแบบ quadratic เพราะเริ่มถนัดแล้ว และสามารถแปลงเป็น cubic ได้โดยไม่สูญเสียตำแหน่ง )

    ดาวน์โหลดซอร์ส sfd

    (ไฟล์ดาวน์โหลดที่ทันสมัย นำไปไว้ใน หน้ารวม Fonts)

    ถ้าต้องการทำเป็นฟอนต์ทดแทนฟอนต์อังศณา ในไฟล์คอนฟิกของฟอนต์ ต้องเติมท่อนนี้ลงไปด้วย
    $ sudo vi /etc/fonts/65-thai-XXX.conf

    ...
             <alias>
                <family>Angsana New</family>
                <accept>
                    <family>Angsima</family>
                </accept>
            </alias>
    ...

    ท่อนนี้เอาตัวอย่างมาจาก http://lists.freedesktop.org/archives/fontconfig/2006-June/002326.html

    ถึงตอนนี้เรียกว่ายังไม่เสร็จ แต่ก็พอใช้งานได้ แต่จะพยายามทำต่อให้ใช้งานได้จริง ๆ ครับ

    update

    • 50-06-06 ทำ Angsima แบบปกติเสร็จแล้ว แก้(ลบ) Kerning ของต้นฉบับอักษรภาษาอังกฤษ ปรับปรุงเลขไทยให้เหมือนต้นแบบ ปรับระยะห่างระหว่างบรรทัดเรียบร้อย แต่ยังไม่ได้ทดสอบแบบละเอียด
    • 50-06-07 ทำ Angsima-Bold เสร็จแบบหยาบ ๆ
    • 50-06-08 Angsima-Normal ปรับละเอียดเสร็จแล้ว
    • 50-06-09 สร้างเส้นใหม่-ปรับละเอียด Angsima-Bold เสร็จ
    • 50-06-09 ทำ Oblique และ BoldOblique ด้วยเมนู Element -> Transformation -> Transform -> skew แบบอัตโนมัติทั้งตาราง แต่ยังไม่ได้ทดสอบ
    • 520801 แก้ไขตาราง GPOS GSUB ปรับการวางสระวรรณยุกต์ให้ถูกต้อง
    Topic: 

    ลองทำฟอนต์ทดแทน ๒ - Bromlila

    ฟอนต์ตัวที่สองตั้งชื่อว่า บรมลีลา - Bromlila
    จุดประสงค์จะใช้แทน Browallia (โดยไม่เสียรูปแบบ)

    ใช้ฐานจากฟอนต์ Garuda ยกเว้นตัวหนาที่เป็นภาษาอังกฤษ ตัว Garuda-Bold ยังไม่หนาเท่า Browallia ผมจึงเอามาจากตัวหนา sans ของ Freefont แทน

    มีข้อสังเกตุคือ ตัว Browallia กับ Garuda เมื่อปรับสัดส่วนแล้ว มีลายเส้นที่แทบจะนาบกันสนิท

    เที่ยวนี้ขี้เกียจทำ Screenshot เพราะตัวเหมือนกันเป๊ะ ๆ ดาวน์โหลดเลยดีกว่า

    ดาวน์โหลดได้ดังนี้

    • รวมทั้งหมดในไฟล์เดียว Bromlila.tar.gz
    Topic: 

    ลองทำฟอนต์ทดแทน ๓ - Corada

    ฟอนต์ตัวที่สามตั้งชื่อว่า ครดา - Corada
    จุดประสงค์จะใช้แทน Cordia (โดยไม่เสียรูปแบบ)

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

    ใช้ฐานจากฟอนต์ Loma แต่ยากหน่อยตรงที่ฐานของฟอนต์โลมาคือ FreeSerif ไม่มีขนาดความหนาแบบ Light เลยต้องเสียเวลาสร้างตัวภาษาอังกฤษแบบ Light ด้วย และใช้ขนาดปกติ (Book) ของ FreeSerif เป็น Bold ของ Corada แทน
    ดังนั้นสำหรับเที่ยวนี้ ในส่วนของอักขระภาษาอังกฤษจึงไม่ค่อยเรียบร้อยเท่าที่ควร เพราะสร้างขึ้นเอง

    พยายามให้หน้าตาฟอนต์ออกมาอยู่ระหว่างฟอนต์คอร์เดีย และสุดยอดฟอนต์แห่งยุค คือ ดีบีฟองน้ำ

    ดาวน์โหลดได้ดังนี้

    • รวมทั้งหมดในไฟล์เดียว Corada.tar.gz
    Topic: 

    ลองทำฟอนต์ทดแทน - สรุป

    สรุปงานทำฟอนต์ทดแทนฟอนต์ของวินโดวส์ทั้งสามตัว คือ

    • Angsana แทนด้วย Angsima
    • Browallia แทนด้วย Bromlila
    • Cordia แทนด้วย Corada

    สร้างด้วยการปรับความกว้างของฟอนต์แต่ละอักขระ ให้เท่ากับฟอนต์วินโดวส์ และลบ kerning ของฟอนต์ต้นแบบออกให้หมด ส่วนความสูงของฟอนต์ ไม่ได้ปรับความสูงของสระให้เท่าของวินโดวส์ แต่ใช้วิธีสร้างอักขระพิเศษเพิ่มมาตัวนึง ให้จุดสูงสุดของอักขระเท่ากับไม้โทสูงของวินโดวส์ (ซึ่งสูงที่สุด) และให้จุดต่ำสุดของอักขระนี้ ให้เท่ากับสระอูที่ต่ำที่สุดของวินโดวส์
    รวมทั้งสร้างไฟล์คอนฟิกให้ฟอนต์ ให้แทนชื่อฟอนต์จากวินโดวส์ตามรายละเอียดข้างบน ไฟล์นี้ตั้งชื่อว่า 65-2-thaifont-abc.conf ซึ่งจะต้องนำไปใส่ไว้ที่ไดเรกทอรี่ /etc/fonts/conf.d

    เมื่อทำครบทุกอย่างแล้ว เราก็จะสามารถเปิดไฟล์ตระกูลออฟฟิศจากวินโดวส์ โดยไม่เสียรูปแบบ

    สามารถดาวน์โหลดไฟล์รวมได้ดังนี้

    Topic: 

    ลองทำฟอนต์ใช้เอง

    ทดลองทำฟอนต์ใช้เองโดยใช้ไฟล์ฟอนต์ Norasi.sfd ของ NECTEC เป็นต้นแบบ
    จุดประสงค์เพื่อทำฟอนต์แบบ Serif ที่ใช้รหัสอักขระยูนิโค๊ด จะนำมาแทนฟอนต์ Angsana ของวินโดวส์


    ตั้งชื่อว่า นรบุตร (Noraputta)
    ตั้งใจให้ออกมาเป็นลูกผสมของ Norasi กับ Angsana
    ยังไม่ค่อยสวยเหมือนที่มืออาชีพเขาทำกัน ฮิ้นต้งฮิ้นติ้งไม่รู้เรื่องเลย เอาแค่พอใช้งานได้ตามสไตล์
    ฝากลองทดสอบและใช้ฟรีโดยไม่มีเงื่อนไขครับ

    update

    • 50-04-13 ปรับมาใช้เทมเพลตของ thaifonts รุ่น 0.4.6 ทำให้ไม่มีปัญหาสระอำและวรรณยุกต์ กับ OO.o
    • 50-04-19 ทำเรื่องเลขไทยใหม่ เก็บกวาดเรื่อง Hinting แบบหยาบ ๆ
    • 50-04-20 ทำ Noraputta-Bold เพิ่ม
    • 50-04-28 แก้ปัญหาเส้นบางเกินไป ทำให้เวลาถูก Hint แล้วเส้นขาด ด้วยการเพิ่มความหนาของเส้น

    ลองทำฟอนต์ใช้เอง (ต่อ) - Gentini

    ลองทำ Serif อีกฟอนต์นึง เพื่อให้สมดุลย์กับ Sans serif โดยเอาต้นแบบมาจากฟอนต์ Gentium ซึ่งเป็น Serif ที่ดูสบายตา กึ่งทางการกึ่งลำลอง จึงตั้งชื่อให้คล้าย ๆ กัน ว่า เจนทินี - Gentini
    ทำไปก็งงไป ทำให้ทราบว่างานออกแบบฟอนต์นี่ยากจริง ๆ ผมเองยังมือไม่ถึง จึงอ่านบุคลิกของฟอนต์ Gentium ไม่ออก เลยทำแค่ฟอนต์ขนาด book แค่ตัวเดียว

    ดาวน์โหลด
    ไฟล์รวม Gentini.tar.gz

    Topic: 

    ลองทำฟอนต์ใช้เอง (ต่อ) - Pantaka

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

    ดาวน์โหลดไฟล์รวมดังนี้

    update

    • 50-07-02 ได้ปรับปรุงวีรชาติ จนฟอนต์วีรชาติอ่านง่ายกว่าแล้ว จึงเอาฟอนต์วีรชาติกลับเข้ามาประจำการเป็นฟอนต์แบบ San-Serif แทนปัณถกะ
    Topic: 

    ลองทำฟอนต์ใช้เอง (ต่อ) - Umpush

    หลังจากทำฟอนต์ Corada เสร็จแล้ว มีความรู้สึกว่าฟอนต์ Corada ตัวหนา น่าจะมาทำฟอนต์บนจอได้ เลยเอา Corada เดิม มาแปลงเป็น Light เอา Bold ทำเป็น Book แล้วสร้าง Bold ใหม่ ตั้งชื่อว่า อัมพุช - Umpush

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

    ดาวน์โหลดได้ดังนี้

    อย่าลืมว่า ฟอนต์ทุกตัวที่สร้างไว้ ถูกออกแบบไว้ด้วยการ Hint ของระบบ ดังนั้น จะต้องคอนฟิกฟอนต์ทุกครั้ง ด้วย
    $ sudo vi /etc/fonts/conf.d/65-1-ttfonts.conf

    <?xml version="1.0"?>
    <!DOCTYPE fontconfig SYSTEM "fonts.dtd">
    <fontconfig>
        <alias>
            <family>serif</family>
            <accept>
                <family>Noraputta</family>
                <family>Angsima</family>
            </accept>
        </alias>
        <alias>
            <family>sans-serif</family>
            <accept>
                <family>Umpush</family>
                <family>Verachart</family>
                <family>Lomaputta</family>
                <family>Bromlila</family>
                <family>Corada</family>
            </accept>
        </alias>
        ...
        <match target="font">
            <test name="family"><string>Umpush</string></test>
            <edit name="autohint" mode="assign"><bool>true</bool></edit>
            <edit name="hinting" mode="assign"><bool>true</bool></edit>
        </match>
    </fontconfig>
    

    $ fc-cache -r

    ถ้าไม่ประสบเหตุอื่นอีก ฟอนต์นี้คงเป็นฟอนต์สุดท้ายแล้วครับ

    Topic: 

    ลองทำฟอนต์ใช้เอง - Verachart

    ทำฟอนต์หน้าจอเพิ่ม ตั้งชื่อว่า วีรชาติ เพราะเอามาจากฟอนต์ Vera ของ BitStream


    ตัวบางไปหน่อย line spacing แคบไปนิด Hinting ยังแย่อยู่
    ช่วยทดสอบและใช้ได้ฟรีเหมือนเดิมครับ

    ดาวน์โหลดซอร์ส

    (ไฟล์ ttf สามารถดาวน์โหลดที่ หน้ารวม Fonts)

    การทดลองที่ไม่สำเร็จ
    ทดลองทำ Truetype Instruction ด้วยเมนู AutoInstruction
    แก้ไขการที่โปรแกรม Fontforge ทำ Truetype Hint ไม่สมบูรณ์ ด้วยการปรับละเอียดที่ส่วนโค้งของตัวอักษรแทน
    ไม่สำเร็จเนื่องจาก คุณภาพพอดูได้แค่ 11 ปอยนต์ ที่เหลือนอกจากนี้ ดูไม่ได้เลย

    • ไฟล์ฟอนต์ Verachartti.ttf
    • ไฟล์ FontForge Verachartti.sfd
    • ไฟล์ชุดนี้ยังไม่สมบูรณ์ แต่ถ้าหากต้องการทดลองใช้งานดู ไม่จำเป็นต้องแก้ไขไฟล์ 65-ttfonts.conf เนื่องจากมีการ Hint อยู่ในฟอนต์เรียบร้อยแล้ว

    update

    • 50-04-22 ปรับลดความสูงของฟอนต์ให้สมดุลย์กับภาษาอังกฤษ ใช้วิธี Hint แบบ AutoInstruction
    • 50-04-24 กลับมาใช้ Hint แบบเดิม เปลี่ยนมาให้ระบบ Hint ได้ผลดีกว่า โดยเฉพาะฟอนต์ชุดนี้ ให้ปรับไฟล์ 65-1-ttfont.conf เป็นดังนี้
      <fontconfig>
              <alias>
                      ...
                      <family>Verachart</family>
                      <default><family>san-serif</family></default>
              </alias>
              ...
              <match target="font">
                      <test name="family"><string>Verachart</string></test>
                      <edit name="autohint" mode="assign"><bool>true</bool></edit>
                      <edit name="hinting" mode="assign"><bool>true</bool></edit>
                      <edit name="hintstyle" mode="assign"><int>3</int></edit>
              </match>
      </fontconfig>
      

      จะให้ผลที่ดูนุ่มสบายตากว่า แม้ในขนาดฟอนต์ที่เล็กมาก ๆ
      หรือถ้าหากเห็นว่าเบลอเกินไป ก็ตั้งค่า hintstyle เป็น 4 ก็จะดูคมแข็งขึ้นครับ
      (ดูรายละเอียดการปรับตั้งที่ ลองทำฟอนต์ใช้เอง - แถม)

    • 50-04-24 สร้างฟอนต์ชุด Verachart-Bold เพิ่ม
    • 50-04-24 ปรับละเอียด
    • 50-06-06 ปรับปรุง Verachart-Bold เสร็จ ทำ Verachart-Oblique เสร็จ
    • 50-07-02 ปรับละเอียด Verachart 90% และ Verachart-Bold 50%
    • 50-07-04 ปรับละเอียด Verachart และ Verachart-Bold ครั้งที่สอง ทำเรื่อง Hinting โดยปรับให้ความหนาของตัวอักษร ในแนวนอน มีความสม่ำเสมอ จะทำให้การ Hint ดูดีขึ้นมาก
    • 50-07-12 ปรับปรุงตัวหนา ช ฃ ช ซ โ พ ฟ เล็กน้อย

    ลองทำฟอนต์ใช้เอง - แถม

    update

    • การตั้งค่าตามเนื้อความในโพสต์นี้ จะมีผลทำให้ฟอนต์ที่ถูกระบุในการปรับตั้งครั้งนี้ ไม่อยู่ภายใต้การควบคุมของระบบ (กับ Gnome คือ System -> Preference -> Fonts) นั่นคือการแสดงผลของฟอนต์ชุดนี้จะไม่เปลี่ยนไปตามการตั้งค่า Smoothing และ Hinting ของระบบ

    โพสต์นี้ได้ความรู้มาจากคุณสมเจตน์ ท่านช่วยทดสอบให้ ได้ความว่า

    ฟอนต์ชุดนี้ หากจะนำไปใช้ให้ได้ผลดีมีความคมชัด ต้องกำหนด Hint ให้ฟอนต์ด้วย ดังนี้

    สร้างไฟล์ชื่อ 65-1-ttfonts.conf เอาไปใส่ไว้ในไดเรกทอรี่ /etc/fonts/conf.avail แล้วสร้างลิงก์ไปยังไดเรกทอรี่ /etc/fonts/conf.d
    $ sudo vi /etc/fonts/conf.avail/65-1-ttfonts.conf

    <?xml version="1.0"?>
    <!DOCTYPE fontconfig SYSTEM "fonts.dtd">
    <fontconfig>
            <alias>
                    <family>Lomaputta</family>
                    <family>Lomaputta2</family>
                    <family>Garudatas</family>
                    <default><family>san-serif</family></default>
            </alias>
            <alias>
                    <family>Noraputta</family>
                    <family>Norasitas</family>
                    <default><family>serif</family></default>
            </alias>
            <match target="font">
                    <test name="family"><string>Lomaputta</string></test>
                    <edit name="autohint" mode="assign"><bool>true</bool></edit>
                    <edit name="hinting" mode="assign"><bool>true</bool></edit>
                    <edit name="embeddedbitmap" mode="assign"><bool>false</bool></edit>
            </match>
            <match target="font">
                    <test name="family"><string>Lomaputta2</string></test>
                    <edit name="autohint" mode="assign"><bool>true</bool></edit>
                    <edit name="hinting" mode="assign"><bool>true</bool></edit>
                    <edit name="embeddedbitmap" mode="assign"><bool>false</bool></edit>
            </match>
            <match target="font">
                    <test name="family"><string>Garudatas</string></test>
                    <edit name="autohint" mode="assign"><bool>true</bool></edit>
                    <edit name="hinting" mode="assign"><bool>true</bool></edit>
                    <edit name="embeddedbitmap" mode="assign"><bool>false</bool></edit>
            </match>
            <match target="font">
                    <test name="family"><string>Noraputta</string></test>
                    <edit name="autohint" mode="assign"><bool>true</bool></edit>
                    <edit name="hinting" mode="assign"><bool>true</bool></edit>
                    <edit name="embeddedbitmap" mode="assign"><bool>false</bool></edit>
            </match>
            <match target="font">
                    <test name="family"><string>Norasitas</string></test>
                    <edit name="autohint" mode="assign"><bool>true</bool></edit>
                    <edit name="hinting" mode="assign"><bool>true</bool></edit>
                    <edit name="embeddedbitmap" mode="assign"><bool>false</bool></edit>
            </match>
    </fontconfig>

    $ sudo ln -sf /etc/fonts/conf.avail/65-1-ttfonts.conf /etc/fonts/conf.d

    แล้วก็รีบูตเครื่องใหม่ หลังจากนั้นก็จะเห็นฟอนต์ชุดนี้มีความคมชัดเพิ่มขึ้นครับ

    ลิงก์รอศึกษา

    ลองทำฟอนต์ใช้เอง ๒

    ลองทำอีกชุดนึงสำหรับ sans-serif โดยเอาฟอนต์โลมามาเป็นต้นแบบ
    จุดประสงค์เพื่อจะนำมาทำเป็นฟอนต์สำหรับดูที่หน้าจอมอนิเตอร์ โดยต้องการให้อ่านง่าย จึงปรับให้หัวตัวอักษรกลมขึ้น แยกเส้นตัวอักษรให้เด็ดขาดขึ้น

    ตั้งชื่อว่าโลมาบุตร (Lomaputta)
    ช่วยทดสอบหรือเอาไปใช้ได้ฟรี (ฟอนต์โลมาต้นฉบับเป็น GPL)

    เพิ่มความหนานิดนึง ดูคล้าย ๆ Tahoma เสียดายอันเก่า เลยตั้งชื่อใหม่เป็นโลมาบุตร2

    update

    • 50-04-14 ปรับช่องไฟละเอียด, ปรับส่วนโค้งอักษรแบบละเอียด, ปรับมาใช้เทมเพลตของ thaifonts รุ่น 0.4.6 ทำให้สามารถใช้งาน OO.o ได้
    • 50-04-16 ปรับแต่ง Hinting ด้วยมือ ดูดีขึ้นมาก
    • 50-04-17 ปรับมาใช้เทคนิกการ Hint แบบเบลอภายในฟอนต์ด้วย AutoHint และคมภายนอกด้วย ManualHint
    • 50-04-17 สร้าง Lomaputta-Bold เพิ่ม
    • 50-04-17 เปลี่ยนกลับมาใช้ Hint ด้วยมือล้วน เนื่องจากแสดงผลในเดเบียนฟุ้ง(Blur)เกินไป (คงไม่เกี่ยวกับเดเบียน แต่อาจเป็นเพราะเครื่องเดเบียนของผมใช้จอภาพเป็น LCD ของเอเซอร์ ซึ่งการแสดงผลดูแล้วจะสู้ของแอลจีไม่ได้) แก้อาการหลอกตาด้วยการลดขนาดหัวตัวอักษรแทน
      รู้ปัญหาอาการฟุ้งบนเดเบียนแล้ว คือ เดเบียนไม่ทำ AutoHint เป็นค่าปริยาย จึงต้องทำเองด้วยมือ
      $ sudo ln -sf /etc/fonts/conf.avail/10-autohint.conf /etc/fonts/conf.d
      แล้วเข้าระบบใหม่ ผลที่ได้คือ ฟอนต์บนเดเบียนดูคมกว่าบนอูบุนตูอีก
    • 50-04-18 สร้าง Lomaputta-Oblique เพิ่ม
    • 50-04-19 แก้ไขตาราง Contextual ซ่อมเรื่องวรรณยุกต์กับสระอุ-สระอู ตามต้นฉบับ
    • 50-04-19 สร้าง Lomaputta-BoldOblique เพิ่ม
    • 50-04-27 ล้างลายเส้นเก่า สร้างลายเส้นใหม่ เลียนแบบของคุณเทพ

    ลองทำฟอนต์ใช้เอง ๓

    คราวก่อนทำฟอนต์โลมาบุตร ดูแล้วยังรู้สึกว่าอ่านยาก (ไม่รู้ว่าเป็นที่ hinting หรือเปล่า)
    คราวนี้ลองเอาอีกฟอนต์นึงคือฟอนต์ครุฑ (Garuda) มาปรับให้เหมาะกับการแสดงผลบนจอคอมพิวเตอร์
    โดยการลดขนาดสระและวรรณยุกต์ และปรับความกว้างของบรรทัดให้แคบเข้า
    เอาไว้เป็นตัวเผื่อเลือกสำหรับการแสดงผลของฟอนต์ sans-serif
    ตั้งชื่อว่าฟอนต์ ครุฑทัศน์ (Garudatas)

    ดาวน์โหลด

    update
    ทำเพิ่มเติมสำหรับ serif ด้วย คือฟอนต์นรสีห์ เป็น นรสีห์ทัศน์
    ดาวน์โหลด

    หมายเหตุ
    ไม่เหมาะสำหรับงานเอกสารที่ต้องพิมพ์ออกมา สำหรับงานพิมพ์ใช้ฟอนต์ Garuda และ Norasi เดิมสวยอยู่แล้ว

    เกร็ดเกี่ยวกับโปรแกรม FontForge

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

    การกำหนดแถบการ Hint ไว้กว้าง ทำให้เวลาแสดงฟอนต์ทางจอภาพ อักษร ก ไก่ จะดูเตี้ยกว่าอักษร ข ไข่ ซึ่งไม่ถูก AutoHint ไว้กว้างแบบนั้น

    ถ้าจะทำให้ได้ผลการ Hint ที่ดี แบบที่เรา ๆ ท่าน ๆ สามารถทำได้เอง โดยไม่ต้องใช้ฝีมือระดับเทพ ก็คือการ Hint เองด้วยมือ
    ซึ่งทำได้โดยการกำหนดสองจุดคู่ที่จะทำการ Hint แล้วสั่ง Add HHint หรือ Add VHint ตามลักษณะของส่วนโค้งตัวอักษรนั้น ๆ

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

    การทำปุ่มลัดสำหรับ FontForge

    เริ่มด้วยการติดตั้งแพกเกจที่จำเป็นในการคอมไพล์ก่อน
    $ sudo aptitude install libfreetype6-dev
    (แพกเกจอื่น ๆ ไม่แน่ใจครับ อาจตรวจดูจากข้อผิดพลาดตอนสั่ง ./configure ก็ได้)

    ไปเอาซอร์สมาก่อนคือไฟล์ fontforge_full-20070312.tar.bz2

    เมื่อได้ไฟล์มาแล้วก็แตกไฟล์ออกก่อน
    $ tar xfj fontforge_full-20070312.tar.bz2

    คอมไพล์และติดตั้ง
    $ cd fontforge-20070312
    $ ./configure
    $ make
    $ sudo make install

    ต่อไปเป็นการสร้างไฟล์สำหรับกำหนดปุ่มลัด
    ทำตามคู่มือ ในการนำเอาไฟล์ FontForge-MenuShortCuts.pot ออกมา
    $ cd fontforge-20070312/fontforge
    $ make -f Makefile.in FontForge-MenuShortCuts.pot

    เราจะได้ไฟล์ FontForge-MenuShortCuts.pot
    เราจะแก้ไขในไฟล์นี้โดยกำหนดให้ Add HHint ใช้ปุ่มลัดเป็น Alt+Ctl+1
    และ Add VHint ใช้ปุ่มลัดเป็น Alt+Ctl+2
    $ vi FontForge-MenuShortCuts.pot

    ...
    #: charview.c:8105
    msgid "Add HHint|No Shortcut"
    msgstr "Alt+Ctl+1" 
    
    #: charview.c:8106
    msgid "Add VHint|No Shortcut"
    msgstr "Alt+Ctl+2"
    ...
    

    คอมไพล์ไฟล์ pot
    $ msgfmt -o FontForge-MenuShortCuts.mo FontForge-MenuShortCuts.pot

    ย้ายไฟล์นี้ไปไว้ที่ไดเรกทอรี่ที่เก็บ locale ของเครื่อง
    ถ้าตั้ง locale เป็น en_US.UTF8 คำสั่งจะเป็น
    $ sudo mkdir -p /usr/local/share/locale/en_US/LC_MESSAGES
    $ sudo mv FontForge-MenuShortCuts.mo /usr/local/share/locale/en_US/LC_MESSAGES

    เสร็จเรียบร้อยแล้ว สั่งรันโปรแกรมด้วยคำสั่ง
    $ /usr/local/bin/fontforge -dontopenxdevices [ชื่อไฟล์ฟอนต์]

    ผลที่ได้จากการทำ Hint ด้วยมือ ดูดีขึ้นกว่าทำ AutoHint มากครับ

    จากความรู้ที่ได้นี้ สามารถสรุปการ Hint ได้คือ

    • ส่วนในของฟอนต์เลือกใช้ AutoHint เนื่องจากต้องการให้เกิดการเบลอภายในให้มากที่สุด
    • ส่วนนอกของฟอนต์ คือ จุดสูงสุดและต่ำสุด ต้อง Hint เองด้วยมือ เพราะต้องการให้ส่วนสูงมีความคงที่
    • เพิ่มเติม

    • อ่านมาจาก http://osdir.com/ml/fonts.fontforge.user/2006-08/msg00030.html
      บอกว่า ปกติ FontForge ไม่ทำ Hint ของทรูไทป์โดยตรง แต่เป็นของ Type1
      ถ้าจะพยายามทำ Hint ให้ทรูไทป์ วิธีทำคือให้เปลี่ยนส่วนโค้งเป็นแบบ Quadratic
      Element -> Font Info ... -> เลือก Quadratic Splines

      หลังจากนั้นจึงทำ AutoHint และ AutoInstruction แล้วจึงสั่งผลิตฟอนต์ทรูไทป์
      เลือกช่วงฟอนต์ที่ต้องการ -> Hints -> AutoHint
      Hints -> AutoInstr
      Files -> Generates Fonts

      ทดลองแล้วได้ผลดีพอควร

    • 50-05-05 ลองทดสอบเรื่อง Truetype Hint ได้ความว่า
      ถึงแม้ว่า Fontforge จะทำ Truetype Hint ได้แย่มากก็ตาม แต่เราก็อาจบังคับทำได้ โดยได้ผลดีพอควร โดยมีข้อแม้ว่า :-
      1. จุดต่าง ๆ ที่กำกับส่วนโค้ง ต้องเลือกใช้จุดให้ตรงจริง ๆ ทั้งสามชนิดคือ จุดส่วนโค้ง จุดหัวมุม และจุดแทนเจนต์ ห้ามใช้จุดมั่วเด็ดขาด
      2. ขนาดความหนาของส่วนตัวอักษร และจุดกำกับส่วนโค้งต่าง ๆ ควรอยู่ในตำแหน่งทางเรขาคณิตแบบง่าย ๆ
      3. ส่วนโค้งที่ยาวมาก ต้องมีจุดแบ่งส่วนกำกับเสมอ
      4. ถ่าหากจุดเริ่มต้นส่วนโค้ง ไม่อยู่ในตำแหน่งทางเรขาคณิตอย่างง่าย ควรใช้จุดแทนเจนต์กำกับอยู่ใกล้ ๆ (เช่น ปลายหางของตัว จ.จาน เป็นต้น)
      5. เมื่อทดสอบแล้วพบว่ามีบางอักขระที่มีอาการฟุ้ง ให้ลองปรับลดหรือเพิ่มความกว้างของอักขระนั้นที่ละ 1 พิกเซล (ส่วนใหญ่ลองที่ขนาดฟอนต์ประมาณ 11 ปอยนต์)

      ถ้าผ่านข้อกำหนดดังกล่าว การ Hint จะได้ผลดีที่สุด

    • การทำงานกับส่วนโค้งที่เป็น Quadratic ไม่ยากจนเกินไปนัก หัดสักพักก็จะชิน เมื่อชินแล้วจะพบว่าการบังคับส่วนโค้ง ง่ายกว่าแบบ Cubic เสียอีก เพราะโดนบังคับด้วยจุดขนาบ
    • อนาคตของ Truetype Hint น่าจะเสื่อมความนิยมลงไปเรื่อย ๆ ผกผันกับขนาดความละเอียดของจอภาพที่จะละเอียดขึ้นไปเรื่อย ๆ
    • การทำให้การ Hint แบบ Adobe ได้ผลดี ควรปรับความหนาของตัวอักษรในแนวนอนให้มีความสม่ำเสมอ จึงจะได้ความสูงของตัวอักษรที่เท่า ๆ กัน ที่ขนาดตัวอักษรต่าง ๆ กัน

    ลองทำฟอนต์ใช้เอง - จบ

    ค้นไปค้นมา ไปพบฟอนต์ที่เป็นฟรีแวร์และ gpl สรุปได้ดังนี้

    • ฟอนต์แห่งชาติ นอกเหนือจากฟอนต์โลมา ครุฑ และนรสีห์แล้ว ยังมีฟอนต์กินรีที่สวยอยู่แล้ว ใช้แทนฟอนต์ Angsana ในวินโดวส์ได้เลย - ดาวน์โหลด national-fonts.zip
    • ฟอนต์ของบริษัท Tep Club เป็นฟรีแวร์ มีฟอนต์สวยมาก ๆ คือ CmPrasanmit - ดาวน์โหลด tep-clubfonts.zip
    • ฟอนต์ที่ SIPA พัฒนา ไม่ค่อยสวยเท่าไหร่ - ดาวน์โหลด dip-sipa-font.zip
    • ฟอนต์ตระกูล JS ของคุณหมอภาณุฑัต ที่แนบมากับลินุกซ์ทะเล 7 สามารถนำออกจากแผ่น TLE-7 ได้เลย หรือสามารถดาวน์โหลดไฟล์ rpm แล้วนำมาแตกเองด้วยโปรแกรม Archeive Manager - ดาวน์โหลด thai-ttf-js.0.0.2-1.src.rpm
    • มีฟอนต์สวย ๆ มากจริง ๆ

    ทำฟอนต์เอง มาตายตรง hinting มือไม่ถึง :P เวลาไม่เอื้อให้ศึกษา
    แต่ก็ยังชอบใจ โลมาบุตรอยู่ดี ;D

    Topic: 

    debian: ติดตั้งฟอนต์ Tahoma

    ฟอนต์ Tahoma ถูกใช้เป็นฟอนต์ปริยายในการแสดงผลของเว็บไซต์เป็นจำนวนมาก ลองค้นหาดูพบว่ามันอยู่ในแพคเกจของ cinelerra-data ในคลังของ debian-multimedia

    วิธีการคือเพิ่มคลังของ debian-multimedia และติดตั้งแพคเกจ cinelerra-data

    $ sudo vi /etc/apt/sources.list
    ...
    deb http://www.debian-multimedia.org sid main non-free
    ...
    $ sudo aptitude update
    $ sudo aptitude install cinelerra-data

    อาจเปลี่ยนรุ่นจาก sid เป็น lenny หรือรุ่นที่ใช้งานจริงได้

    เสร็จแล้วครับ
    จะได้ฟอนต์ Tahoma ที่มีภาษาไทยครบถ้วน อีกทั้งฟอนต์ของไมโครซอฟท์อีกหลายตัว ซึ่งฟอนต์ทั้งหมดจะถูกติดตั้งไว้ที่ /usr/lib/cinelerra/fonts/

    Topic: 

    fonts: TrueType Hinting Instruction

    ศึกษาการทำ TrueType Hinting Instruction

    ลิงก์

    การทำ Truetype Hinting Instructions

    ตัวอย่างไฟล์

    ไฟล์ DejaVuSansThai.tar.gz

    Topic: 

    fonts: บันทึก DroidSans+Thai+Lao

    ฟอนต์รุ่นนี้เป็นรุ่นลัดคิว

    1. งานคือเอาฟอนต์ DroidSansThai ของกูเกิล รุ่นที่เป็นแอนดรอยด์ 2.2 ลงไป (อาจจะเป็นชื่อ DroidSansThai.ttf หรือ DroidThai-Regular.ttf) มาทำตัวหนาเพิ่มแบบหยาบ ๆ โดยมีการดัดแปลงสคริปต์ 'ccmp' Glyph Composition/Decomposition in Thai lookup 2" เล็กน้อย เพื่อให้แสดงระดับวรรณยุกต์ได้ถูกต้อง เราจะให้อยู่ในชื่อ DroidSansThai
      รุ่นนี้ เนื่องจากมีเแต่ภาษาไทย ดังนั้นถ้านำมาใช้ในลินุกซ์จะต้องสร้างไฟล์ fontconfig ประมาณนี้ (ยังไม่ได้ทดสอบ)

      ...
        <match target="scan">
          <test name="family">
            <string>Droid Sans Thai</string>
          </test>
          <edit name="family">
            <string>Droid Sans</string>
          </edit>
          <edit name="fullname">
            <string>Droid Sans</string>
          </edit>
        </match>
      ...
         <alias binding="same">
          <family>Droid Sans Thai</family>
          <accept>
            <family>Droid Sans</family>
          </accept>
        </alias>
      ...
    2. อีกงานนึงคือเอาอักขระไทยจาก DroidSansThai สอดเข้าไปในฟอนต์ DroidSans เพื่อให้โทรศัพท์แอนดรอยด์ใช้งานฟอนต์ตัวนี้ได้ อันนี้จะอยู่ในชื่อ DroidSans (เสียดายที่แอนดรอยด์ไม่รองรับตาราง OpenType การแสดงวรรณยุกต์จึงยังลอยอยู่)

    ดาวน์โหลดไฟล์ฟอนต์ (ล่าสุดคือ 20100812)

    1. DroidSansThai-ttf.tar.gz
    2. DroidSans-ttf.tar.gz (สำหรับใช้กับโทรศัพท์แอนดรอยด์)
    ข้อความด้านล่างเป็นการแสดงผลจากหน้าเว็บด้วยฟอนต์นี้ (จากความสามารถของ css3)

    ดาวน์โหลดไฟล์ซอร์ส

    ทดสอบภาษาลาว

    • ກະຂັຄາງ່ຳຈິ່ຊີ້ຍຶ໊ດື໋ຕຸ່ຖູ້ທົ໊ນຼ໋ເບແປໂຜໃຝໄພຟ່ມ້ຢ໊ຣ໋ລ໌ວໍສຫອຮຯຽໆ ໐໑໒໓໔໕໖໗໘໙ ໜໝ
    • ກະຂັຄາງ່ຳຈິ່ຊີ້ຍຶ໊ດື໋ຕຸ່ຖູ້ທົ໊ນຼ໋ເບແປໂຜໃຝໄພຟ່ມ້ຢ໊ຣ໋ລ໌ວໍສຫອຮຯຽໆ ໐໑໒໓໔໕໖໗໘໙ ໜໝ

    ภาพตัวอย่าง
    ภาพจากบราวเซอร์ epiphany
    ตัวธรรมดา

    ตัวหนา

    Topic: 

    fonts: บันทึก DroidSansMono+Thai+Lao

    ลัดคิว #3
    ทำฟอนต์ DroidSansMono เพิ่ม โดยทำทั้งภาษาไทยและลาว เสียดายที่ต้นฉบับไม่มีตัวหนา เลยทำแค่ตัวธรรมดาอย่างเดียว
    ทำเป็นฟอนต์แบบ Monospace แท้ ๆ โดยใช้ต้นแบบตาราง Lookup จากฟอนต์ TlwgTypo

    ดาวน์โหลดไฟล์ฟอนต์ (ล่าสุดคือ 20100917)

    ข้อความด้านล่างเป็นการแสดงผลจากหน้าเว็บด้วยฟอนต์นี้ (จากความสามารถของ css3)

    ดาวน์โหลดไฟล์ซอร์ส

    ทดสอบภาษาลาว

    • ກະຂັຄາງ່ຳຈິ່ຊີ້ຍຶ໊ດື໋ຕຸ່ຖູ້ທົ໊ນຼ໋ເບແປໂຜໃຝໄພຟ່ມ້ຢ໊ຣ໋ລ໌ວໍສຫອຮຯຽໆ ໐໑໒໓໔໕໖໗໘໙ ໜໝ

    ภาพตัวอย่าง
    ภาพจากบราวเซอร์ epiphany
    ตัวธรรมดา

    fonts: บันทึก DroidSerif+Thai+Lao

    ลัดคิว #2
    ทำฟอนต์ DroidSerif เพิ่ม ถึงจะมีที่ใช้น้อย แต่ก็ทำไว้เพื่อให้แสดงผลได้สมบูรณ์ ทำทั้งภาษาไทยและลาว

    update

    • 561227: Google เปลี่ยนชื่อฟอนต์ Droid Serif เป็น Noto Serif ไฟล์ต้นฉบับเป็นภาษาอังกฤษ+ละติน และแยกไฟล์ออกมาอีกหลายภาษา (รวมทั้งไทยและลาว) ดาวน์โหลดได้ที่ code.google.com:noto
    • 561228: เปลี่ยน Droid Serif เป็น Droid Serif Web ป้องกันการสับสน มี อังกฤษ ละติน ไทย ลาว และปรับ Glyph ใหม่หมด
    • 570219:
      • เอาฐานฟอนต์ Droid Serif ของกูเกิลรุ่นล่าสุด คือ 1.0.3 มาทำ Droid Serif Web
      • เอาฐานฟอนต์ Noto Sans Thai มาทำ Droid Serif ThaiLao โดยให้ชื่อ font family เป็น Droid Serif ThaiLao และ font family preferred เป็น Droid Serif ดังนั้น ถ้าเราติดตั้งฟอนต์ fonts-droid จะสามารถใช้ภาษาไทยและลาวจากฟอนต์ Droid Serif ThaiLao ได้ภายใต้ชื่อ Droid Serif (ยกเว้น Inkscape ต้องใช้ Droid Serif Web แทน)

    Droid Serif (ต้นฉบับ+ไทย+ลาว) - เลิกทำแล้ว (จริง ๆ คือเปลี่ยนชื่อเป็น Droid Serif Web แทน ป้องกันสับสน)

    ดาวน์โหลดไฟล์ฟอนต์ (ล่าสุดคือ 20100914)
    DroidSerif-ttf.tar.gz
    ดาวน์โหลดไฟล์ซอร์ส
  • รุ่นร่าง ยังไม่ได้ทำ hint : DroidSerif-src-20100823.tar.gz
  • เสร็จแบบยังไม่มี hint ลดความกว้างลงเล็กน้อย : DroidSerif-src-20100914.tar.gz
  • ตัวอย่าง

    24!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_` Aabcdefghijklmnopqrstuvwxyz{|}~ mrnrtodocode ṃāñūṭḍṇṅ
    aกัขิฃีคึฅืฆุงูจฺฉ็ช่ซ้ฌ๊ญ๋ญุฎ์ฏํฐ๎ฐูฑั่ฒิ้ณี๊ดื๋ติ์ถะทาธำน่ำเบี่แปิํโผใฝ่ไพฟ้ภมยรฤๅลฦวศษสหฬอฮฯๆ ฿๏๐๑๒๓๔๕ ๖๗๘๙๚๛ ก่ป่ฝ้ฟ๊ฬ๋ คฅศ ดตฒ ญฐญานํ ญฐญานํ โสตฺถิํ
    ກະຂັຄາງ່ຳຈິ່ຊີ້ຍຶ໊ດື໋ຕຸ່ຖູ້ທົ໊ນຼ໋ເບແປໂຜໃຝໄພຟ່ມ້ຢ໊ຣ໋ລ໌ວໍສຫອຮຯຽໆ ໐໑໒໓໔໕໖໗໘໙ ໜໝໞໟ

    24!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_` Aabcdefghijklmnopqrstuvwxyz{|}~ mrnrtodocode ṃāñūṭḍṇṅ
    aกัขิฃีคึฅืฆุงูจฺฉ็ช่ซ้ฌ๊ญ๋ญุฎ์ฏํฐ๎ฐูฑั่ฒิ้ณี๊ดื๋ติ์ถะทาธำน่ำเบี่แปิํโผใฝ่ไพฟ้ภมยรฤๅลฦวศษสหฬอฮฯๆ ฿๏๐๑๒๓๔๕ ๖๗๘๙๚๛ ก่ป่ฝ้ฟ๊ฬ๋ คฅศ ดตฒ ญฐญานํ ญฐญานํ โสตฺถิํ
    ກະຂັຄາງ່ຳຈິ່ຊີ້ຍຶ໊ດື໋ຕຸ່ຖູ້ທົ໊ນຼ໋ເບແປໂຜໃຝໄພຟ່ມ້ຢ໊ຣ໋ລ໌ວໍສຫອຮຯຽໆ ໐໑໒໓໔໕໖໗໘໙ ໜໝໞໟ


    Droid Serif ThaiLao (มีเฉพาะภาษาไทย+ลาว)

    ดาวน์โหลดไฟล์ฟอนต์ (ล่าสุดคือ 20140219)
    DroidSerifThaiLao-ttf.tar.gz
    ดาวน์โหลดไฟล์ซอร์ส
  • ปรับ glyph ใหม่หมดให้ดูสง่าขึ้น ลดช่องไฟตัวหนาให้ดูกระชับ เพิ่ม glyph ภาษาลาว (ขมุ-ก และ ขมุ-ญ) รุ่นนี้มีการเปลี่ยนความกว้างฟอนต์หลายตัว จึงไม่ compat กับของเก่าเลย : DroidSerifThaiLao-src-20131227.tar.gz
  • แต่งตัวเลขไทย : DroidSerifThaiLao-src-20140104.tar.gz
  • ปรับฐานใหม่ ให้ใช้งานภายใต้ชื่อ Droid Serif ได้ : DroidSerifThaiLao-src-20140219.tar.gz
  • ตัวอย่าง

    24!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_` Aabcdefghijklmnopqrstuvwxyz{|}~ mrnrtodocode ṃāñūṭḍṇṅ
    aกัขิฃีคึฅืฆุงูจฺฉ็ช่ซ้ฌ๊ญ๋ญุฎ์ฏํฐ๎ฐูฑั่ฒิ้ณี๊ดื๋ติ์ถะทาธำน่ำเบี่แปิํโผใฝ่ไพฟ้ภมยรฤๅลฦวศษสหฬอฮฯๆ ฿๏๐๑๒๓๔๕ ๖๗๘๙๚๛ ก่ป่ฝ้ฟ๊ฬ๋ คฅศ ดตฒ ญฐญานํ ญฐญานํ โสตฺถิํ
    ກະຂັຄາງ່ຳຈິ່ຊີ້ຍຶ໊ດື໋ຕຸ່ຖູ້ທົ໊ນຼ໋ເບແປໂຜໃຝໄພຟ່ມ້ຢ໊ຣ໋ລ໌ວໍສຫອຮຯຽໆ ໐໑໒໓໔໕໖໗໘໙ ໜໝໞໟ

    24!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_` Aabcdefghijklmnopqrstuvwxyz{|}~ mrnrtodocode ṃāñūṭḍṇṅ
    aกัขิฃีคึฅืฆุงูจฺฉ็ช่ซ้ฌ๊ญ๋ญุฎ์ฏํฐ๎ฐูฑั่ฒิ้ณี๊ดื๋ติ์ถะทาธำน่ำเบี่แปิํโผใฝ่ไพฟ้ภมยรฤๅลฦวศษสหฬอฮฯๆ ฿๏๐๑๒๓๔๕ ๖๗๘๙๚๛ ก่ป่ฝ้ฟ๊ฬ๋ คฅศ ดตฒ ญฐญานํ ญฐญานํ โสตฺถิํ
    ກະຂັຄາງ່ຳຈິ່ຊີ້ຍຶ໊ດື໋ຕຸ່ຖູ້ທົ໊ນຼ໋ເບແປໂຜໃຝໄພຟ່ມ້ຢ໊ຣ໋ລ໌ວໍສຫອຮຯຽໆ ໐໑໒໓໔໕໖໗໘໙ ໜໝໞໟ


    Droid Serif Web (อังกฤษ+ละติน+ไทย+ลาว)

    ดาวน์โหลดไฟล์ฟอนต์ (ล่าสุดคือ 20140219)
    DroidSerifWeb-ttf.tar.gz
    ดาวน์โหลดไฟล์ซอร์ส
  • DroidSerifWeb-src-20131227.tar.gz
  • แต่งตัวเลขไทย : DroidSerifWeb-src-20140104.tar.gz
  • ปรับฐานเป็นรุ่นล่าสุด : DroidSerifWeb-src-20140219.tar.gz
  • ตัวอย่าง

    24!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_` Aabcdefghijklmnopqrstuvwxyz{|}~ mrnrtodocode ṃāñūṭḍṇṅ
    aกัขิฃีคึฅืฆุงูจฺฉ็ช่ซ้ฌ๊ญ๋ญุฎ์ฏํฐ๎ฐูฑั่ฒิ้ณี๊ดื๋ติ์ถะทาธำน่ำเบี่แปิํโผใฝ่ไพฟ้ภมยรฤๅลฦวศษสหฬอฮฯๆ ฿๏๐๑๒๓๔๕ ๖๗๘๙๚๛ ก่ป่ฝ้ฟ๊ฬ๋ คฅศ ดตฒ ญฐญานํ ญฐญานํ โสตฺถิํ
    ກະຂັຄາງ່ຳຈິ່ຊີ້ຍຶ໊ດື໋ຕຸ່ຖູ້ທົ໊ນຼ໋ເບແປໂຜໃຝໄພຟ່ມ້ຢ໊ຣ໋ລ໌ວໍສຫອຮຯຽໆ ໐໑໒໓໔໕໖໗໘໙ ໜໝໞໟ

    24!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_` Aabcdefghijklmnopqrstuvwxyz{|}~ mrnrtodocode ṃāñūṭḍṇṅ
    aกัขิฃีคึฅืฆุงูจฺฉ็ช่ซ้ฌ๊ญ๋ญุฎ์ฏํฐ๎ฐูฑั่ฒิ้ณี๊ดื๋ติ์ถะทาธำน่ำเบี่แปิํโผใฝ่ไพฟ้ภมยรฤๅลฦวศษสหฬอฮฯๆ ฿๏๐๑๒๓๔๕ ๖๗๘๙๚๛ ก่ป่ฝ้ฟ๊ฬ๋ คฅศ ดตฒ ญฐญานํ ญฐญานํ โสตฺถิํ
    ກະຂັຄາງ່ຳຈິ່ຊີ້ຍຶ໊ດື໋ຕຸ່ຖູ້ທົ໊ນຼ໋ເບແປໂຜໃຝໄພຟ່ມ້ຢ໊ຣ໋ລ໌ວໍສຫອຮຯຽໆ ໐໑໒໓໔໕໖໗໘໙ ໜໝໞໟ

    Topic: 

    fonts: บันทึก Lomaputta

    เอา Lomaputta - โลมาบุตร ซึ่งเอามาจากฟอนต์โลมาของ ltn: ThaiFonts-Scalable มาทำเรื่อง hinting

    ตอนทำ hinting จริง ๆ ต้องปรับลายเส้นจากเดิมเยอะเหมือนกัน

    ดาวน์โหลดไฟล์ฟอนต์(20100602)

    ดาวน์โหลดไฟล์ซอร์ส

    • รุ่นเริ่มต้น ยังไม่มีตัวหนา ฟอนต์ไทย hint ด้วยมือ ส่วนฟอนต์อังกฤษ ใช้ Auto-Instruction แบบช่วยปรับแต่งเล็กน้อย (การปรับแต่งยังไม่ดีนัก เพราะโค๊ด dfont.py ยังไม่เข้าที่ รอรุ่นหน้าต่อไป :) Lomaputta-src-520501.tar.gz
    • ตัวหนาเกือบเสร็จ เปลี่ยนการเดินจุดให้อัตโนมัติมากขึ้น ด้วยคำสั่ง SROUND Lomaputta-src-20090506.tar.gz
    • ตัวหนาเสร็จแล้ว Lomaputta-src-20090511.tar.gz
    • แก้บั๊กสระ 'แ' ใน openoffice.org-3.0.1 พิมพ์ไม่ออก Lomaputta-src-20090518.tar.gz - แก้โดยการเติมจุดเปล่าที่ช่อง uni0E00 (ข้างหน้า 'ก') แล้ว copy reference มาผสมลงในช่องสระแอ (วิธีพิสดารหน่อย อนาคตอาจเอาออก) เป็นบั๊กทางโปรแกรมมิ่งของผมเอง แก้ไขเรียบร้อยแล้วครับ
    • ปรับปรุงสระและวรรณยุกต์เหนืออักษรมีหางบน (ป ฝ ฟ ฬ) ให้แยกจากตัวอักษรมากขึ้น Lomaputta-src-20100119.tar.gz
    • ทำ hinting ภาษาอังกฤษเพิ่ม Lomaputta-src-20100531.tar.gz
    • ปรับ hint ให้เส้นนอนนุ่มนวลขึ้น แก้ปาก ก.ไก่ ให้หนาขึ้น แก้เส้นหลัง ร.เรือ ให้คมขึ้น Lomaputta-src-20100602.tar.gz

    ภาพตัวอย่าง
    จากบราวเซอร์ epiphany
    ตัวธรรมดา

    ตัวหนา

    Topic: 

    fonts: บันทึก Norasiputta

    เอาฟอนต์นรสีห์จาก ltn: ThaiFonts-Scalable มาทำเรื่อง hinting

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

    ดาวน์โหลดไฟล์ฟอนต์ (20100625)

    ข้อความด้านล่างเป็นการแสดงผลจากหน้าเว็บด้วยฟอนต์นี้ (จากความสามารถของ css3)

    ดาวน์โหลดไฟล์ซอร์ส

    • รุ่นเริ่มต้น ยังไม่มีตัวหนา ฟอนต์ไทย hint ด้วยมือ ส่วนฟอนต์อังกฤษ ใช้ Auto-Instruction แบบช่วยปรับแต่งเล็กน้อย Norasiputta-src-520702.tar.gz
    • ตัวหนาเสร็จแบบยังไม่ได้เกลา Norasiputta-src-520707.tar.gz
    • รื้อมาทำใหม่ จุดประสงค์คือต้องการทำเป็นฟอนต์สำหรับดูหน้าจอภาพ ด้วยการเพิ่มความหนา stem ไทย เปลี่ยน glyph ภาษาอังกฤษ โดยเอาฐานมาจากฟอนต์ Dustismo Roman และ Droid Serif เพื่อให้ดูกลมกลืนกับ glyph ไทย รุ่นนี้จึงถือเป็นรุ่นร่าง ยังไม่มี hint - Norasiputta-src-20100610.tar.gz
    • ตัวธรรมดา hint หยาบเสร็จ - Norasiputta-src-20100618.tar.gz
    • ตัวหนาเสร็จ ตรวจสอบการแสดงผลไปเรื่อย ๆ - Norasiputta-src-20100625.tar.gz

    ภาพตัวอย่าง
    จาก Epiphany Browser
    ตัวธรรมดา

    ตัวหนา

    Topic: 

    fonts: บันทึก Roboto

    เอาฟอนต์ Roboto มาใส่ภาษาไทย โดยเอาโครงฟอนต์ Roboto (20%) + Droid Sans (30%) + Garuda (50%)

    จุดประสงค์คือจะนำไปใช้ทดสอบ Android ICS

    Update

    • 20140220
      • เลิกทำภายใต้ชื่อ Roboto เฉย ๆ แต่เปลี่ยนเป็น Roboto Web แทน ซึ่งจะมีภาษาอังกฤษ, ละติน, ไทย และลาว ใช้งานภายใต้ชื่อ Roboto Web
      • ทำรุ่นที่มีเฉพาะไทยกับลาวภายใต้ชื่อ Roboto ThaiLao โดยตึ้งค่า perferred family เป็น Roboto ใช้งานภายใต้ชื่อ Roboto แต่ต้องมีการลงแพ็กเกจหรือปรับตั้งอื่น ดังนี้
        • สำหรับเดสก์ทอป ลงแพ็กเกจชื่อ fonts-roboto แล้วสามารถใช้งานผ่านชื่อฟอนต์ Roboto ได้เลย (ยกเว้น Inkscape ให้ใช้ Roboto Web แทน)
        • สำหรับโทรศัพท์แอนดรอยด์ ให้นำ RobotoThaiLao-*.ttf ใส่ใน /system/fonts แล้วแก้ไขไฟล์ /system/etc/fallback_fonts.xml เพิ่มบรรทัดดังนี้
          ...
              <family>
                  <fileset>
                      <file>RobotoThaiLao-Regular.ttf</file>
                      <file>RobotoThaiLao-Bold.ttf</file>
                  </fileset>
              </family>
          ...
          
          ยกเว้น RobotoThaiLao-Light และ RobotoThaiLao-Thin ต้องแก้ /system/etc/system_fonts.xml โดยตรง ดังนี้
          ...
              <family>
                  <nameset>
                      <name>sans-serif-light</name>
                  </nameset>
                  <fileset>
                      <file>Roboto-Light.ttf</file>
                      <file>Roboto-LightItalic.ttf</file>
                      <file>RobotoThaiLao-Light.ttf</file>
                  </fileset>
              </family>
          
              <family>
                  <nameset>
                      <name>sans-serif-thin</name>
                  </nameset>
                  <fileset>
                      <file>Roboto-Thin.ttf</file>
                      <file>Roboto-ThinItalic.ttf</file>
                      <file>RobotoThaiLao-Thin.ttf</file>
                  </fileset>
              </family>
          ...
          

    Roboto (ต้นฉบับ+ไทย+ลาว) - เลิกทำแล้ว (จริง ๆ คือเปลี่ยนชื่อเป็น Roboto Web แทน ป้องกันสับสน)

    ดาวน์โหลดไฟล์ฟอนต์ (ล่าสุดคือ 20121003)
    Roboto-ttf.tar.gz
    ดาวน์โหลดไฟล์ซอร์ส
  • รุ่นร่างเริ่มต้น ยังไม่ได้ทำ hint ยังไม่ได้ทำภาษาลาว (ใช้ของเดิมที่เป็นของ Droid Sans ที่ทำไว้ก่อนแล้ว) เพิ่มการรองรับภาษาบาลี (lang="pi"): Roboto-src-20121003.tar.gz
  • ตัวอย่าง

    24!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_` Aabcdefghijklmnopqrstuvwxyz{|}~ mrnrtodocode ṃāñūṭḍṇṅ
    aกัขิฃีคึฅืฆุงูจฺฉ็ช่ซ้ฌ๊ญ๋ญุฎ์ฏํฐ๎ฐูฑั่ฒิ้ณี๊ดื๋ติ์ถะทาธำน่ำเบี่แปิํโผใฝ่ไพฟ้ภมยรฤๅลฦวศษสหฬอฮฯๆ ฿๏๐๑๒๓๔๕ ๖๗๘๙๚๛ ก่ป่ฝ้ฟ๊ฬ๋ คฅศ ดตฒ ญฐญานํ ญฐญานํ โสตฺถิํ
    ກະຂັຄາງ່ຳຈິ່ຊີ້ຍຶ໊ດື໋ຕຸ່ຖູ້ທົ໊ນຼ໋ເບແປໂຜໃຝໄພຟ່ມ້ຢ໊ຣ໋ລ໌ວໍສຫອຮຯຽໆ ໐໑໒໓໔໕໖໗໘໙ ໜໝໞໟ

    24!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_` Aabcdefghijklmnopqrstuvwxyz{|}~ mrnrtodocode ṃāñūṭḍṇṅ
    aกัขิฃีคึฅืฆุงูจฺฉ็ช่ซ้ฌ๊ญ๋ญุฎ์ฏํฐ๎ฐูฑั่ฒิ้ณี๊ดื๋ติ์ถะทาธำน่ำเบี่แปิํโผใฝ่ไพฟ้ภมยรฤๅลฦวศษสหฬอฮฯๆ ฿๏๐๑๒๓๔๕ ๖๗๘๙๚๛ ก่ป่ฝ้ฟ๊ฬ๋ คฅศ ดตฒ ญฐญานํ ญฐญานํ โสตฺถิํ
    ກະຂັຄາງ່ຳຈິ່ຊີ້ຍຶ໊ດື໋ຕຸ່ຖູ້ທົ໊ນຼ໋ເບແປໂຜໃຝໄພຟ່ມ້ຢ໊ຣ໋ລ໌ວໍສຫອຮຯຽໆ ໐໑໒໓໔໕໖໗໘໙ ໜໝໞໟ


    Roboto ThaiLao (มีเฉพาะภาษาไทย+ลาว)

    ดาวน์โหลดไฟล์ฟอนต์ (ล่าสุดคือ 20140225)
    RobotoThaiLao-ttf.tar.gz
    ดาวน์โหลดไฟล์ซอร์ส
  • ใช้ฐานฟอนต์ของ Noto Sans Thai และ Roboto ต้นฉบับ เกลาภาษาไทยเล็กน้อย ทำ Glyph ภาษาลาวใหม่ให้สอดคล้องกับรูปแบบของ Roboto (ภาษาลาวมีการเปลี่ยนความกว้าง ไม่คอมแพตกับของเดิม) : RobotoThaiLao-src-20140221.tar.gz
  • เพิ่ม Light : RobotoThaiLao-src-20140225.tar.gz
  • ตัวอย่าง

    24!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_` Aabcdefghijklmnopqrstuvwxyz{|}~ mrnrtodocode ṃāñūṭḍṇṅ
    aกัขิฃีคึฅืฆุงูจฺฉ็ช่ซ้ฌ๊ญ๋ญุฎ์ฏํฐ๎ฐูฑั่ฒิ้ณี๊ดื๋ติ์ถะทาธำน่ำเบี่แปิํโผใฝ่ไพฟ้ภมยรฤๅลฦวศษสหฬอฮฯๆ ฿๏๐๑๒๓๔๕ ๖๗๘๙๚๛ ก่ป่ฝ้ฟ๊ฬ๋ คฅศ ดตฒ ญฐญานํ ญฐญานํ โสตฺถิํ
    ກະຂັຄາງ່ຳຈິ່ຊີ້ຍຶ໊ດື໋ຕຸ່ຖູ້ທົ໊ນຼ໋ເບແປໂຜໃຝໄພຟ່ມ້ຢ໊ຣ໋ລ໌ວໍສຫອຮຯຽໆ ໐໑໒໓໔໕໖໗໘໙ ໜໝໞໟ

    24!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_` Aabcdefghijklmnopqrstuvwxyz{|}~ mrnrtodocode ṃāñūṭḍṇṅ
    aกัขิฃีคึฅืฆุงูจฺฉ็ช่ซ้ฌ๊ญ๋ญุฎ์ฏํฐ๎ฐูฑั่ฒิ้ณี๊ดื๋ติ์ถะทาธำน่ำเบี่แปิํโผใฝ่ไพฟ้ภมยรฤๅลฦวศษสหฬอฮฯๆ ฿๏๐๑๒๓๔๕ ๖๗๘๙๚๛ ก่ป่ฝ้ฟ๊ฬ๋ คฅศ ดตฒ ญฐญานํ ญฐญานํ โสตฺถิํ
    ກະຂັຄາງ່ຳຈິ່ຊີ້ຍຶ໊ດື໋ຕຸ່ຖູ້ທົ໊ນຼ໋ເບແປໂຜໃຝໄພຟ່ມ້ຢ໊ຣ໋ລ໌ວໍສຫອຮຯຽໆ ໐໑໒໓໔໕໖໗໘໙ ໜໝໞໟ

    24!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_` Aabcdefghijklmnopqrstuvwxyz{|}~ mrnrtodocode ṃāñūṭḍṇṅ
    aกัขิฃีคึฅืฆุงูจฺฉ็ช่ซ้ฌ๊ญ๋ญุฎ์ฏํฐ๎ฐูฑั่ฒิ้ณี๊ดื๋ติ์ถะทาธำน่ำเบี่แปิํโผใฝ่ไพฟ้ภมยรฤๅลฦวศษสหฬอฮฯๆ ฿๏๐๑๒๓๔๕ ๖๗๘๙๚๛ ก่ป่ฝ้ฟ๊ฬ๋ คฅศ ดตฒ ญฐญานํ ญฐญานํ โสตฺถิํ
    ກະຂັຄາງ່ຳຈິ່ຊີ້ຍຶ໊ດື໋ຕຸ່ຖູ້ທົ໊ນຼ໋ເບແປໂຜໃຝໄພຟ່ມ້ຢ໊ຣ໋ລ໌ວໍສຫອຮຯຽໆ ໐໑໒໓໔໕໖໗໘໙ ໜໝໞໟ


    Roboto Web (อังกฤษ+ละติน+ไทย+ลาว)

    ดาวน์โหลดไฟล์ฟอนต์ (ล่าสุดคือ 20140225)
    RobotoWeb-ttf.tar.gz
    ดาวน์โหลดไฟล์ซอร์ส
  • รุ่นแรก ใส่ตัวละตินยังไม่ครบ : RobotoWeb-src-20140221.tar.gz
  • เพิ่ม Light : RobotoWeb-src-20140225.tar.gz
  • ตัวอย่าง

    24!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_` Aabcdefghijklmnopqrstuvwxyz{|}~ mrnrtodocode ṃāñūṭḍṇṅ
    aกัขิฃีคึฅืฆุงูจฺฉ็ช่ซ้ฌ๊ญ๋ญุฎ์ฏํฐ๎ฐูฑั่ฒิ้ณี๊ดื๋ติ์ถะทาธำน่ำเบี่แปิํโผใฝ่ไพฟ้ภมยรฤๅลฦวศษสหฬอฮฯๆ ฿๏๐๑๒๓๔๕ ๖๗๘๙๚๛ ก่ป่ฝ้ฟ๊ฬ๋ คฅศ ดตฒ ญฐญานํ ญฐญานํ โสตฺถิํ
    ກະຂັຄາງ່ຳຈິ່ຊີ້ຍຶ໊ດື໋ຕຸ່ຖູ້ທົ໊ນຼ໋ເບແປໂຜໃຝໄພຟ່ມ້ຢ໊ຣ໋ລ໌ວໍສຫອຮຯຽໆ ໐໑໒໓໔໕໖໗໘໙ ໜໝໞໟ

    24!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_` Aabcdefghijklmnopqrstuvwxyz{|}~ mrnrtodocode ṃāñūṭḍṇṅ
    aกัขิฃีคึฅืฆุงูจฺฉ็ช่ซ้ฌ๊ญ๋ญุฎ์ฏํฐ๎ฐูฑั่ฒิ้ณี๊ดื๋ติ์ถะทาธำน่ำเบี่แปิํโผใฝ่ไพฟ้ภมยรฤๅลฦวศษสหฬอฮฯๆ ฿๏๐๑๒๓๔๕ ๖๗๘๙๚๛ ก่ป่ฝ้ฟ๊ฬ๋ คฅศ ดตฒ ญฐญานํ ญฐญานํ โสตฺถิํ
    ກະຂັຄາງ່ຳຈິ່ຊີ້ຍຶ໊ດື໋ຕຸ່ຖູ້ທົ໊ນຼ໋ເບແປໂຜໃຝໄພຟ່ມ້ຢ໊ຣ໋ລ໌ວໍສຫອຮຯຽໆ ໐໑໒໓໔໕໖໗໘໙ ໜໝໞໟ

    24!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_` Aabcdefghijklmnopqrstuvwxyz{|}~ mrnrtodocode ṃāñūṭḍṇṅ
    aกัขิฃีคึฅืฆุงูจฺฉ็ช่ซ้ฌ๊ญ๋ญุฎ์ฏํฐ๎ฐูฑั่ฒิ้ณี๊ดื๋ติ์ถะทาธำน่ำเบี่แปิํโผใฝ่ไพฟ้ภมยรฤๅลฦวศษสหฬอฮฯๆ ฿๏๐๑๒๓๔๕ ๖๗๘๙๚๛ ก่ป่ฝ้ฟ๊ฬ๋ คฅศ ดตฒ ญฐญานํ ญฐญานํ โสตฺถิํ
    ກະຂັຄາງ່ຳຈິ່ຊີ້ຍຶ໊ດື໋ຕຸ່ຖູ້ທົ໊ນຼ໋ເບແປໂຜໃຝໄພຟ່ມ້ຢ໊ຣ໋ລ໌ວໍສຫອຮຯຽໆ ໐໑໒໓໔໕໖໗໘໙ ໜໝໞໟ

    Topic: 

    fonts: บันทึก Taeyhom

    ทำฟอนต์เพื่อทดแทน Tahoma ตั้งชื่อว่า "เตยหอม" (Taeyhom)

    พยายามทำให้คอมแพตกับ Tahoma 100% ยกเว้นตัวเลขไทย ของเดิมความกว้างไม่เท่ากัน แต่ของเราทำให้เท่ากันตามมาตรฐาน

    ดาวน์โหลดไฟล์ฟอนต์ (ล่าสุดคือ 20100531)

    ดาวน์โหลดไฟล์ซอร์ส

    • รุ่นร่างเริ่มต้น ยังไม่ได้ทำ hint ยังไม่ได้เกลาละเอียด Taeyhom-src-20100305.tar.gz
    • ทำ hint ตัวธรรมดาเสร็จ Taeyhom-src-20100327.tar.gz บันทึก - ลองเพิ่มจุดอ้างอิงเพื่อมัดติดกับจุดสมอ แต่พบว่าจุดสมอไม่เลื่อนไปตามจุดอ้างอิงที่ถูก hint เราต้องเผื่อเอาเอง
    • รุ่นพักเหนื่อย - ทำ hint ตัวหนาเสร็จ และกลับมาเกลาตัวธรรมดาจนเกือบเสร็จ Taeyhom-src-20100410.tar.gz
      บันทึก - ทดลองใช้ฟังก์ชั่นใหม่ ๆ หลายตัว (MD, MPPEM, SDPVTL, MUL, DIV, IF, EIF) ยังงง ๆ อยู่
    • รุ่นพักยก - เปลี่ยนการวางอักขระยึดตาม tlwg เพื่อให้เข้ากันได้กับเอกสารทั่วไป ตัวธรรมดาเกลาจนดีแล้ว แต่พบปัญหาว่าตัวบางเกินไป จึงเก็บโค๊ดไว้ก่อน รุ่นหน้าจะทำตามมาตรฐานที่ควรเป็นเสียที Taeyhom-src-20100413.tar.gz
      บันทึก
      • ปรับโค๊ด dfont.py หลายจุด ตอนนี้สามารถถอดโค๊ดฟอนต์ทั่วไปได้แล้ว
      • การ hint ที่ผ่านมา เดินเส้น stem แบบไม่ปัดให้เต็ม จุดประสงค์คือให้ดูนุ่มนวล แต่พบปัญหาคือ เราไม่สามารถเติมเส้นให้เต็มในขนาด ppem วิกฤต (14,15,16ppem) คือไม่ขาดนิดนึงก็เกินนิดนึง ถ้าขาด ฟอนต์จะดูบาง ถ้าเกิน ฟอนต์จะดูฟุ้ง รุ่นต่อไปจะปัดให้เส้นเต็มจนถึง 32 ppem แล้วจึงปล่อย น่าจะทำให้ดูดีขึ้น
    • รุ่นพักยก2 - ตัวธรรมดาเปลี่ยนเยอะมาก แต่ตัวหนายังไม่ได้เปลี่ยน รอดูผลสักพัก Taeyhom-src-20100417.tar.gz
      บันทึก
      • เริ่มใช้ function call
      • เปลี่ยนการ hint มาเป็นแบบเต็มเส้นจนถึง 42ppem แล้วจึงปล่อย
      • เริ่มใช้ตาราง prep โดยส่วนใหญ่จะใช้ในการแทนค่า cvt เพื่อให้ขนาดเส้นดูสม่ำเสมอ จนถึง 42ppem (DELTAC)
    • รุ่นอีกนิดเดึยว - ออกเพื่อพักเหนื่อย เหลือเกลาตัวหนา และปรับให้กลไกมาเหมือนกับตัวธรรมดา Taeyhom-src-20100421.tar.gz
      บันทึก
      • สงสัยว่าทำไม่ใช้กับวินโดวส์ไม่สวยเลย
    • เสร็จแล้วครับ - แต่คงยังมีบั๊กอีกเยอะ Taeyhom-src-20100423.tar.gz
    • แก้บั๊กหลายจุด และปรับปรุงความฟุ้ง Taeyhom-src-20100428.tar.gz
    • รุ่นทดลอง - สร้างฟังก์ชั่นหาจุดลงตัวของ stem หน้าหลัง และฟังก์ชั่นปรับเปลี่ยนค่า SROUND ไปตามขนาด ppem ต่าง ๆ ทำให้ไม่จำเป็นต้องกำหนดค่า cvt ของกั้นหน้ากั้นหลังและความกว้างตัวอักษร (ถ้าใช้ได้ผลจะได้เอาไปใช้ในการสร้างฟอนต์ตัวต่อ ๆ ไป งานจะลดลงมาก) Taeyhom-src-20100503.tar.gz
    • รุ่นทดลองที่เริ่มลงตัว - ทำกับตัวหนาอย่างเดียว Taeyhom-src-20100511.tar.gz
    • เสร็จ - รอตรวจบั๊กไปเรื่อย ๆ Taeyhom-src-20100512.tar.gz
    • เปลี่ยนเทคนิกลดความฟุ้งที่หัวอักษร ย้ายการลดความฟุ้งจากเส้นตั้งเป็นเส้นนอน ทำให้ได้ความกลมและแยกแยะอักขระได้ดีขึ้น ความเปรียบต่างสูงขึ้น Taeyhom-src-20100516.tar.gz
    • เก็บรายละเอียด 9-16 ppem และเก็บรายละเอียดอื่น ๆ Taeyhom-src-20100518.tar.gz
    • แก้ไขตัว พ ฟ ให้ชัดขึ้นที่ 11,12,13 ppem Taeyhom-src-20100524.tar.gz
    • แก้ไขปากตัว ก.ไก่ และตัวอื่น ๆ ให้โค้งเล็กน้อยเพื่อให้รับกับตัว ร.เรือ (ทำให้เริ่มไม่เหมือน Tahoma แล้ว) Taeyhom-src-20100531.tar.gz

    ภาพตัวอย่าง
    ภาพจากบราวเซอร์ epiphany และ oo.o
    ตัวธรรมดาที่ hint แล้ว

    ตัวธรรมดา

    ตัวหนาที่ hint แล้ว

    ตัวหนา

    Topic: 

    fonts: บันทึก TlwgTypott

    เอา TlwgTypo จาก ltn: ThaiFonts-Scalable มาทำเรื่อง hinting
    พอจะเริ่ม hint จริง ๆ พบว่าลายเส้นของไทยไม่เข้ากับของฝรั่ง ซึ่งจะทำให้การ hint ยุ่งยาก จึงตัดสินใจสร้างลายเส้นไทยขึ้นมาใหม่ และตั้งชื่อใหม่ว่า TlwgTypott

    ดาวน์โหลดไฟล์ฟอนต์(520421)

    ดาวน์โหลดไฟล์ซอร์ส

    • รุ่นเริ่มต้น ยังไม่มีตัวหนา ยังไม่ได้ปรับละเอียด ฟอนต์ไทย hint ด้วยมือ ส่วนฟอนต์อังกฤษ ใช้ Auto-Instruction TlwgTypott-src-520408.tar.gz (พบว่า Auto-Instruction ของ Fontforge มีประสิทธิภาพดีขึ้นมาก ฟอนต์อังกฤษสวยงามมาก แต่กับฟอนต์ไทย ผลยังแย่อยู่จึง hint เองด้วยมือ)
    • ทำตัวหนา ตัวเอียง และตัวหนาเอียง แต่ใช้ Auto-Instruction ทั้งหมด TlwgTypott-src-520415.tar.gz (รุ่นนี้เอาไฟล์ฟอนต์ต้นฉบับของ Freefont มาใช้ ทำให้ได้รหัสอักขระเพิ่มขึ้น แต่พบบั๊กใน Abiword แสดงผลเพี้ยน ๆ แต่กับ gedit และ Epiphany แสดงปกติ)
    • hint ตัวหนาเสร็จ (เฉพาะตัวอักษรหลัก สระและวรรณวุกต์ไม่ได้ทำ) ใช้วิธี hint แบบไม่ลงจุด เพื่อให้เข้ากันได้กับอักขระโรมันที่ hint ด้วย Auto-Instruction TlwgTypott-src-520421.tar.gz (รุ่นนี้แก้บั๊กของรุ่นก่อนเรื่องการแสดงผลเพี้ยน โดยเอาซอร์สของ TlwgTypo กลับมาทำแทน บั๊กรุ่นก่อนเกิดจากต้นฉบับของ GNU ทำเรื่อง OpenType ไว้ยังไม่สมบูรณ์)
    • แก้บั๊ก konsole แสดงวรรณยุกต์เพี้ยน และบั๊กการ hint อื่นเล็กน้อย TlwgTypott-src-520423.tar.gz

    ภาพตัวอย่าง
    จาก abiword (openoffice แสดงผลเพี้ยน)
    ตัวธรรมดา

    ตัวหนา

    ความรู้ที่ได้

    • Auto-Instruction ใช้ได้ดีกับฟอนต์ที่มีเส้นบาง ๆ แต่ก็ต้องเพิ่ม/ลด Hhint Vhint ช่วยด้วย จึงจะได้ผลดี ลำพังใช้ AutoHint เฉย ๆ ไม่ไหว ประกอบกับการสร้างจุด ต้องแม่นจริง ๆ จึงจะทำให้งานออกมาดูกลมกลืน ซึ่งหลาย ๆ อย่างรวมกันแล้วอาจทำให้เสียเวลามากกว่าการเขียนโปรแกรม hint ด้วยซ้ำไป
    Topic: 

    fonts: บันทึก Waree Sans

    เอาฟอนต์ Waree - วารี จาก ltn: ThaiFonts-Scalable มาทำเรื่อง hinting

    ต้องขออภัยที่ต้องปรับความกว้างฟอนต์จากเดิม เพื่อให้เหมาะกับการทำ hinting ดังนั้นจึงไม่สามารถใช้แทนกันได้อย่างสนิท

    ดาวน์โหลดไฟล์ฟอนต์(รุ่น 20090603)

    ดาวน์โหลดไฟล์ซอร์ส

    • รุ่นเริ่มต้น ยังไม่มีตัวหนา ฟอนต์ไทย hint ด้วยมือ ส่วนฟอนต์อังกฤษ ใช้ของเดิมคือฟอนต์ Bitstream Vera Sans ซึ่ง hint stem ด้วยการทดลงจุด (ของเขาดูนุ่ม แต่ของเราดูแข็งหน่อย) : WareeSans-src-20090517.tar.gz
    • เกลาตัวธรรมดาเสร็จ พยัญชนะตัวหนาเสร็จ freeze เพื่อดูผล : WareeSans-src-20090520.tar.gz
    • ปรับลายเส้นให้ดูนุ่มนวลขึ้น เกลาตัวหนาเสร็จ : WareeSans-src-20090523.tar.gz
    • เกลาส่วนฟุ้ง ปรับช่องไฟให้หลวมขึ้น : WareeSans-src-20090527.tar.gz
    • เกลาละเอียด WareeSans-src-20090603.tar.gz

    ภาพตัวอย่าง
    จาก openoffice.org รุ่น 3.0.1
    ตัวธรรมดา

    ตัวหนา

    Topic: 

    fonts: บันทึก Waree Sans Mono

    เอาลายเส้นจาก Waree Sans มาทำเป็น Monospace แต่ทำเป็นแบบไม่ใช่ความกว้างคงที่แท้ ๆ คือสระและวรรณยุกต์ทำเป็นแบบความกว้างเป็นศูนย์ เพื่อให้ใช้งานกับ Openoffice.org ได้

    ทำแล้วดูแล้วยังไม่ค่อยสวย เพราะความกว้างฟอนต์อังกฤษเดิมแคบไป จึงทำให้สัดส่วนฟอนต์ไทยดูสูงชลูดไปหน่อย

    ดาวน์โหลดไฟล์ฟอนต์(รุ่น 20090613)

    ดาวน์โหลดไฟล์ซอร์ส

    • รุ่นเริ่มต้น ยังไม่มีตัวหนา เอา hint เดิมจาก Waree Sans มาใช้ทั้งหมด ดูหยาบสักหน่อย เพราะเริ่มเหนื่อยแล้ว ;D : WareeSansMono-src-20090609.tar.gz
    • ตัวหนาเสร็จแล้ว แต่เสร็จแบบไม่ดีเลย เนื่องจากความกว้างของภาษาอังกฤษแคบมาก ทำให้ hint ยากมาก WareeSansMono-src-20090613.tar.gz

    ภาพตัวอย่าง
    จาก openoffice.org รุ่น 3.0.1
    ตัวธรรมดา

    ตัวหนา

    Topic: 

    fonts: บันทึก Waree Serif

    เอาภาษาไทยจากฟอนต์ DejaVu Serif Thai และภาษาอังกฤษจาก Bitstream Vera Serif มาทำใหม่ ให้ความสูงฟอนต์ไทยเท่ากับ Waree Sans

    ภาษาไทยดั้งเดิม เอาแบบมาจาก JS-Saksit (คล้าย Angsana New) จึงพยายามปรับให้หน้าตาออกมาระหว่าง JS-Saksit กับ Bitstream Vera Serif

    เนื่องจากเห็นเลขไทยของเก่าสวยมากอยู่แล้ว จึงต้องการคงลักษณะเดิมไว้ แต่มีปัญหาว่าลายเส้นมีความซับซ้อนและมีหลายความหนา hint ออกมาแล้วไม่สวย จึงแก้ปัญหาด้วยการ hint แบบไม่ลงจุด ดังนั้นฟอนต์ชุดนี้จะมี hinting แปลกแยกตรงเลขไทยจะดูนุ่มหน่อยครับ

    ดาวน์โหลดไฟล์ฟอนต์(รุ่น 20091124)

    ดาวน์โหลดไฟล์ซอร์ส

    ภาพตัวอย่าง
    จาก openoffice.org รุ่น 3.0.1
    ตัวธรรมดา

    ตัวหนา

    Topic: 

    fonts: บันทึกการทดลองทำ DejaVuSansMonoThai

    เพิ่งเริ่มทำนะครับ

    ขั้นตอน

    • เอาซอร์สของ DejaVu รุ่น 2.28
    • เอาลายเส้นจาก DejaVuSansThai มาปรับ
    • ทำด้วย fontforge รุ่น 0.0.20080429-1 20081224

    งาน
    งานของฟอนต์ตัวนี้คือ เริ่มศึกษาการทำ ตาราง Lookups - GPOS โดยใช้ภาษาลาวเป็นต้นแบบ คือ

    • การวางสระบนอักษร ป ฟ ฬ จะไม่ได้ใช้ตัวหลบแล้ว
    • ความกว้างสระ ใช้แบบความกว้างคงที่ แต่ใช้ตาราง Lookups - GSUB ดันกลับให้มีความกว้างเป็นศูนย์

    download ttf

    download src

    • DejaVuSansMonoThai-src-520127.tar.gz
      • รุ่นแรก ยังไม่ได้ทำ Instruction ยังไม่มีตัวหนา
      • ลบสระเยื้องซ้ายออก
    • DejaVuSansMonoThai-src-520207.tar.gz
      • ลบสระหลบออกทั้งหมด กลับมาใช้ตาราง GPOS อย่างเดียว (คงเหลือแค่ "ฐ" กับ "ญ" ไม่มีเชิง ที่ยังอยู่ในช่วง private area #F700)
      • ทำ Instruction แล้ว รวมทั้งอักขระลาวตัวที่ยังไม่ได้ทำในไฟล์ต้นฉบับ (แต่สำหรับอักขระลาว เนื่องจากไม่ต้องการดัดแปลง spline ต้นฉบับที่ยังวางจุดไม่สมบูรณ์ จึงยังมีข้อจำกัดในการทำ Instruction เล็กน้อย) การทำ Instruction ในรุ่นนี้ ใช้วิธีคล้ายของ DejaVu แต่จะเขียนโค๊ดลงจุดให้น้อยที่สุดเท่าที่จำเป็นเท่านั้น เพื่อให้พัฒนาได้เร็ว และรูปร่างอักขระเพี้ยนน้อย จึงอาจเห็นการฟุ้งอยู่บ้าง
      • ดัดแปลงตัวอักขระ "บ" "ป" "อ" และ "ฮ" ให้มีเชิงเล็กน้อย เพื่อให้ดูเต็มช่องกับขนาดความกว้างอักขระคงที่
      • ช่วงบ่าย ปรับช่องไฟละเอียดแล้ว (ฝากดาวน์โหลดใหม่ด้วย)

    screenshot
    OpenOffice.org-2.4 แสดงผลที่ ppem ต่าง ๆ
    DejaVuSansMonoThai in OpenOffice.org

    รุ่นล่าสุด 520207
    DejaVuSansMonoThai latest screenshot

    รุ่นแรก
    DejaVuSansMonoThai before instruct

    Topic: 

    fonts: บันทึกการทดลองทำ DejaVuSansThai รุ่นแรก

    บันทึกการแปลงฟอนต์ Waree เป็น DejaVuSansThai รุ่นแรก

    • ใช้หลักการ instruct โดยพยายามยึดตามแนวของ DejaVu เท่าที่พอแกะออก
    • ยังขาดการทำ delta hint เพราะยังทำไม่เป็น ดังนั้นการออกแบบฟอนต์และการ instruct จึงพยายามปรับ เพื่อเลี่ยงการทำ delta hint ซึ่งต้องรอศึกษาต่อไป
      ทำ delta hint ได้แล้ว แต่เสียเวลาและสายตามากเกินไป จึงพยายามออกแบบโค๊ดให้ลดการทำ delta hint ให้มากที่สุด
      ล่าสุดเปลี่ยนแนวคิดการออกแบบเป็น กำหนดจุดให้น้อยที่สุดเพื่อให้ฟอนต์เพี้ยนน้อยที่สุด จึงจำเป็นต้องพึ่งการใช้ delta hint อย่างมาก
    • ลดความสูงของฟอนต์ไทย ให้เท่ากับอังกฤษตัวเล็ก แล้วพยายามย่นความสูงของสระและพยัญชนะลงมา แต่กระนั้นก็ยังล้นระยะห่างระหว่างบรรทัดของ DejaVu อยู่ดี
    • ค่า cvt ของ DejaVu Sans เต็ม 256 ค่าแล้ว แต่มีความจำเป็นต้องเพิ่มค่าของเราเข้าไป ทำให้ต้องอ้างค่าเป็น word แทน byte โค๊ดจึงอาจดูยากขึ้นเล็กน้อย
    • เขียนสคริปต์ไพธอน เล็ก ๆ ชื่อ dfont.py เอาไว้คลี่และเรียงสแต็ก ใช้ได้ผลดีพอควร แต่สคริปต์ยังขาดการตรวจสอบความถูกต้องของ instructing code ซึ่งถ้าใช้ด้วยพารามิเตอร์ --all อาจทำให้ fontforge หยุดการทำงานได้ - รอปรับปรุงต่อไป
    • ยังเหลือบั๊กอีกหลายจุด ตัว ค.ควาย จ.จาน ย.ยักษ์ ยังออกแบบไม่ดี ตัว ร.เรือ ธ.ธง ยัง instruct ได้ไม่ดี และยังไม่ได้ทำตัวหนา ตัวเอียง และตัวหนาเอียง แต่มีความจำเป็นต้องเว้นวรรคนาน จึงบันทึกไว้เพื่อให้จำได้

    ความรู้ที่ได้

    • การทำ TrueType Hinting Instruction ตัวอักษรจะเพี้ยนเสมอ ทั้งนี้เนื่องมาจากการบังคับให้ลงจุดในจอมอนิเตอร์ ดังนั้น การ instruct ก็คือการบอกว่าจะให้เพี้ยนยังไง จึงจะทำให้ดูคมชัด
    • การเคลื่อนจุดด้วยวิธี indirect เช่นคำสั่ง MIRP (ซึ่งต้องอ้างค่าจาก cvt - Control Value Table) มีความแม่นยำแน่นอนกว่าการเคลื่อนค่าแบบ direct (เช่นคำสั่ง MDRP)
    • คำสั่ง ip (Interpolate) จะทำให้ฟอนต์ดูฟุ้ง แต่ระยะที่ได้ แน่นอนกว่าการเคลื่อนจุดแบบ direct (เว้นเสียแต่ถ้าไม่มีการทดลงจุด คืออาร์กิวเมนต์ rnd ควรใช้ MDRP เสมอ ไม่งั้นบางทีอาจเพี้ยน)
    • ถ้ามีความจำเป็นต้องใช้การ Interpolate อาจทำแค่แกนเดียว ส่วนอีกแกนนึงให้ใช้การเคลื่อนแบบ direct แทน
    • พยายามยึดแนวแกนซ้ายขวาและความสูงเฉลี่ยของตัวอักษรไว้ ดังนั้นจึงควรบรรจุค่าความสูงและความกว้างของตัวอักษรในตาราง cvt เสมอ
    • ควรออกแบบความกว้างของตัวอักษรให้ดูสม่ำเสมอ แล้วเขียนโค๊ดโดยดูจากตาราง cvt เป็นสำคัญ แต่จะมีข้อยกเว้นเป็นบางตัวอักษรที่มีหัวอักษรอยู่ตรงกลางฟอนต์ เช่น ค.ควาย หรือ ด.เด็ก อาจต้องยอมทิ้งค่าความกว้างจาก cvt โดยต้องไล่จุดไปตามแนวที่ผ่านหัวอักษรไปเรื่อย ๆ ไม่งั้นเวลาแสดงผลที่ขนาดปอยต์ต่าง ๆ หัวอักษรอาจเยื้องไปทางซ้ายบ้างขวาบ้าง บางครั้งอาจดูลีบติดไปทางแกนอักษรข้างใดข้างหนึ่งบ้าง - อาจแก้ได้ด้วย delta hint
    • การทำ delta hint หรือ grid fitting โดยกำหนดให้เป็น version 1 จะได้ฟอนต์ที่ดูนุ่มนวลดีกว่า version 0
    • ถ้าไม่ทดลงจุดด้วยอาร์กิวเมนต์ rnd ห้ามใช้คำสั่ง SHPIX ที่จุดนั้นเด็ดขาด จะมีปัญหาเลอะตอนพิมพ์ โดยเฉพาะในแนวแกน Y

    ผลการทดลอง - บนลินุกซ์ ความละเอียดจอภาพ 85 dpi

    • ดูดีที่ 12 และ 13 ปอยต์
    • ตั้งแต่ 7 ปอยต์ลงไป อ่านไม่ได้
    • ตั้งแต่ 18 ปอยต์ขึ้นไป ไม่มีปัญหาเรื่องการ hint
    • แย่สุดที่ 14 ปอยต์ ที่เหลือนอกจากนี้ พอดูได้
    • รุ่นล่าสุด (510519) ปรับด้วย delta shift hint พยายามทำให้ดูดีในทุก ๆ ppem แต่ปรับละเอียดที่ 14,15 และ 16 ppem
    • ความกระชับของโค๊ด ขึ้นกับแนวคิดในการไล่เส้นทางเดินของฟอนต์
    • ความสวยของฟอนต์ขึ้นกับความเพียรในการทำ delta hint

    ดาวน์โหลดไฟล์ฟอนต์ ttf ล่าสุด (รุ่น 520401)

    ดาวน์โหลดไฟล์ซอร์ส

    • DejaVuSansThai-src-510331.tar.gz
    • ปรับปรุงช่องไฟ (ยังไม่ดี) ปรับปรุงเรื่องหัวเล็กน้อย (ดีขึ้น) และปรับหลัก instruction บางส่วน (ดีขึ้น)
      DejaVuSansThai-src-510408.tar.gz
    • รุ่นสุดท้ายก่อนทำ delta hint DejaVuSansThai-src-510415.tar.gz
    • เริ่มทำ delta hint (gridfit) ของตัว ค.ควาย ด.เด็ก และปรับปรุงตัว ร.เรือ DejaVuSansThai-src-510416.tar.gz
    • ทำตัวหนาเพิ่ม DejaVuSansThai-src-510420.tar.gz
    • ปรับวิธี hint ใหม่หมด เลียนแบบจาก Serif ซึ่งพยายามเลียนจาก Tahoma อีกที โดยปรับละเอียดที่ 16 ppem และปรับหัวให้ดูนุ่มขึ้น ที่ 14,15 และ 16 ppem
      DejaVuSansThai-src-510519.tar.gz
    • ทำตัวหนาเสร็จ ปรับวิธี hint อีกเล็กน้อย โดยใช้หลัก hint เท่าที่จำเป็น
      รุ่นนี้คิดว่าสมบูรณ์พอจะเอาไปใช้งานได้แล้ว DejaVuSansThai-src-510523.tar.gz
    • ทำรุ่นใหม่ โดยใช้ฟังก์ชั่น SHPIX บีบหัวให้ฟุ้งน้อยลง จึงทำให้ดูเหมือนกับคมขึ้น ตามไปช่วย hint ให้ภาษาลาวเล็กน้อย แต่ยังไม่เสร็จ DejaVuSansThai-src-510612.tar.gz
    • hint ภาษาลาวเสร็จ ปรับแก้เรื่องเส้นขีดฆ่า ตามคำแนะนำของคุณเทพ (แต่ตัวหนายังไม่ได้แก้ คิดว่าจะแก้ใหญ่ในภายหลังครับ) DejaVuSansThai-src-510618.tar.gz
    • เพิ่มตาราง open type (แก้ปัญหา OpenOffice-2.4 "สระอำ" เพี้ยน) และตรวจแก้ข้อมูลตาราง lookup gsub ขาดไป : DejaVuSansThai-src-510823.tar.gz
    • เริ่มการใช้ตาราง GPOS ลดสระหลบทั้งหมด ย้าย ฐ และ ญ ไม่มีฐานขึ้นข้างบน ดัดแปลงลายเส้นเล็กน้อย hint แบบให้กลมกลึง (คมชัดน้อยลง) : DejaVuSansThai-src-520218.tar.gz
    • hint ตัวหนา(เกือบ)เสร็จ (พักไปงานอื่นก่อน) รุ่นนี้พยายามทดลองเขียนโค๊ดไพธอนแบบนำมาใช้ใหม่ แต่เวลากระชั้นมาก โค๊ดข้างในจึงเลอะเทอะมาก (ตัวหนา) : DejaVuSansThai-src-520303.tar.gz
    • เกลา hint ตัวธรรมดาใหม่ โค๊ดเป็นฟังก์ชั่นเพื่อให้การ hint ไปในแนวเดียวกันทุก ๆ อักขระ แก้ปัญหาหัวบางที่ 9-12 ppem ทำให้อ่านง่ายขึ้น ลบการฟุ้งมากไปหน่อยจนทำให้ฟอนต์ดูแห้ง พยายามทำให้ขนาดเส้นตั้งกับเส้นนอนไม่ต่างกันมากนัก ที่ 17-19 ppem ยังไม่ได้แยกวรรณยุกต์เป็น 2 ระดับ (โค๊ดที่เป็นฟังก์ชั่นยังเลอะเทอะอยู่) : DejaVuSansThai-src-520322.tar.gz
    • ทำวรรณยุกต์ระดับสอง ปรับช่องไฟและความกว้างฟอนต์ขนาดเล็ก : DejaVuSansThai-src-520401.tar.gz

    งานที่เหลือ และที่ตั้งใจจะทำ

    • เกลาช่องไฟละเอียด
    • ดูเส้นขาดฟอนต์ขาวพื้นหลังดำ
    • ทำตัวหนาให้มาแนวเดียวกัน

    ภาพตัวอย่างของรุ่นล่าสุด

    ตัวหนา

    ภาพจาก OpenOffice.org รุ่นล่าสุด

    oo ตัวหนา

    ภาพตัวอย่าง รุ่นแรก (จากจอภาพขนาด 85 dpi)








    Topic: 

    fonts: โค๊ดไพธอนที่ใช้ช่วยทำงาน

    สรุปรวมจาก python: เขียนโค๊ดคลี่แสต็กฟอนต์ และ python: โค๊ดฟอนต์ย้อนกลับ
    รวมเป็นไฟล์เดียว ตั้งชื่อว่า dfont.py การทำงานแค่คลี่และเรียงแสต็กใหม่
    เวลาใช้งาน จะต้องเขียนสคริปต์ไพธอนเพิ่มสำหรับฟอนต์แต่ละตัว เพื่อมาเรียกใช้ dfont เป็นมอดูล ปรับปรุง
    • 51-05-06 เพิ่มฟังก์ชั่นการทำ grid fitting ทำให้การเขียนโค๊ดง่ายขึ้น
      เช่นต้องการขยับจุด 20 ไป +8 ที่ฟอนต์ขนาด 9, 10 และ 11 ppem
      เดิมเขียนโค๊ดเป็น
      deltap1 3 20 15 20 31 20 47 เปลี่ยนเป็น
      f_deltap 20 9+8 20 10+8 20 11+8
    โค๊ดมีดังนี้
    $ vi dfont.py
    #!/usr/bin/env python
    """
    This file is only stack render engine.
    Usage:
    1. Decode from instucted code
        ./dfont.py -d INST_CODE.txt > PSEUDO_CODE.txt
    2. Encode from pseudo_code
        ./dfont.py -e PSEUDO_CODE.txt > INST_CODE.txt
    
    
    Implement:
    1 Create FONTNAME.py contain code as followed:
        1.1 import dfont, sys, os
        1.2 cvt_dict = {"NAME": VALUE, ... }
        1.3 pseudo_code_dict = {"CHARACTER_NAME": "PSEUDO CODE TRUETYPE INSTRUCTION", ...}
        1.4 if __name__ == "__main__":
                if sys.argv[1] == "--all":
                    #$0 --all = ENCODE ALL CHARS
                    sfd_file = os.path.basename(sys.argv[0]).split('.',1)[0] + '.sfd'
                    print "Encoding all character in %s" % (sfd_file)
                    dfont.inst_encode_all( sfd_file, pseudo_code_dict, cvt_dict )
                else:
                    #$0 uni0E01 > x.txt = PRINT INSTRUCTION CODE OF uni0E01 TO x.txt
                    print '\n'.join(dfont.inst_encode( pseudo_code_dict[sys.argv[1]], cvt_dict ))
    
    2 Encode:
        2.1 Process all character, this script will modify FONTNAME.sfd
            Usage: ./FONTNAME.py --all
        2.2 Encode each character, for example KO_KAI="uni0E01"
            Usage: ./FONTNAME.py uni0E01 > INSTRUCTED.TXT
    """
    
    import sys
    import os
    
    # put cvt_dict and pseudo_code_dict varirable in FONTNAME.py
    # cvt value example:
    #cvt_dict = {
    #    "base":             10,     #0
    #    "hstem":            8,      #184
    #    "vstem":            96,     #154 
    #    "vstem_small":      27,     #135 
    #    "headstem_small":   11,     #113    =kho
    #    "headdia_small":    180,    #377    =kho
    #    "headhole_small":   254,    #150    =kho
    #    "headneck_kho":     39,     #180    =kho
    #    "hwidth_ko":        164,    #987    =ko,tho 
    #    "hwidth_kho":       14,     #690    =ko,tho 
    #    "hwidth_sho":       108,    #772    =sho
    #    "vheight":          49,     #1208 
    #    "vheight_hi":       54,     #1208   =ko 
    #    "vheight_hi_kho":   54,     #1229   =kho 
    #    "vheight_lo":       88,     #20 
    #    "vheight_lo_sho":   256,    #6  *** NEWVALUE
    #    "head_diff":        131,    #254    =head of character: bo,po
    #    "beak_diff":        9,      #102    =beak of ko,tho
    #    "front_space_ko":   65,     #201    =front spacing of ko,tho
    #    "front_space_kho":  38,     #340    =front spacing of kho
    #}
    
    # pseudo_code example:
    #pseudo_code_dict = {
    #    #ko_kai
    #    "uni0E01": """
    #srp0    26
    #mirp[rp0,min,rnd,grey] front_kai 14
    #mirp[min,rnd,grey]  hstem  12
    #mirp[rp0,rnd,grey]  hwidth_14 25
    #mirp[min,rnd,grey]  hstem 1
    #mdrp[min,rnd,grey]  27
    #srp0   14
    #mdrp[rp0,rnd,grey]  18
    #mdrp[min,rnd,grey]  8
    #mdrp[rp0,rnd,grey]  17
    #mdrp[min,rnd,grey]  10
    #iup[x]
    #svtca[y-axis]
    #mdap[rnd]  1
    #ALIGNRP    13
    #mirp[rp0,rnd,grey]  vheight_shoot    22
    #mirp[min,rnd,grey]  vstem_curve  5
    #srp1    5
    #srp2    13
    #sloop   10
    #ip  19 8 18 9 17 10 2 25 15 12
    #iup[y]
    #""",
    #}
    
    
    #BEGIN
    # inst_dict = { "COMMAND" : ("Description",diff,pops,push), ... }
    # diff: 0=NOOP, 1=1BYTE, 2=2BYTE, ...
    #       -1=FIRST BYTE IS ONE BYTE COUNTER,
    #       -2=FIRST BYTE IS TWO BYTE COUNTER, 
    #       -3=CLEAR STACK
    #       -4=REQUIRE SOME PROCESSING
    inst_dict = {
        "AA" :       ("Adjust Angle", 1, 1, 0),
        "ABS" :      ("ABSolute value", 0, 1, 1),
        "ADD" :      ("ADD", 1, 2, 1),
        "ALIGNPTS" : ("ALIGN Points", 2, 2, 0),
        "ALIGNRP" :  ("ALIGN to Reference Point", 1, 1, 0),
        "AND" :      ("logical AND", 1, 2, 1),
        "CALL" :     ("CALL function", 1, 1, 0),
        "CEILING" :  ("CEILING", 0, 1, 1),
        "CINDEX" :   ("Copy the INDEXed element to the top of the stack", 1, 1, -4),
        "CLEAR" :    ("CLEAR the stack", -3, 0, 0),
        "DEBUG" :    ("DEBUG call", 1, 1, 0),
        "DELTAC1" :  ("DELTA exception C1", -2, 0, 0),
        "DELTAC2" :  ("DELTA exception C2", -2, 0, 0),
        "DELTAC3" :  ("DELTA exception C3", -2, 0, 0),
        "DELTAP1" :  ("DELTA exception P1", -2, 0, 0),
        "DELTAP2" :  ("DELTA exception P2", -2, 0, 0),
        "DELTAP3" :  ("DELTA exception P3", -2, 0, 0),
        "DEPTH" :    ("DEPTH of the stack", 0, 0, 1),
        "DIV" :      ("DIVide", 1, 2, 1),
        "DUP" :      ("DUPlicate top stack element", 0, 1, 1),
        "EIF" :      ("End IF", 0, 0, 0),
        "ELSE" :     ("ELSE clause", 0, 0, 0),
        "ENDF" :     ("END Function definition", 0, 0, 0),
        "EQ" :       ("EQual", 1, 2, 1),
        "EVEN" :     ("EVEN", 0, 1, 1),
        "FDEF" :     ("Function DEFinition", 1, 1, 0),
        "FLIPOFF" :  ("set the auto FLIP Boolean to OFF", 0, 0, 0),
        "FLIPON" :   ("set the auto FLIP Boolean to ON", 0, 0, 0),
        "FLIPPT" :   ("FLIP PoinT", 1, 1, 0),
        "FLIPRGOFF" :    ("FLIP RanGe OFF", 2, 2, 0),
        "FLIPRGON" : ("FLIP RanGe ON", 2, 2, 0),
        "FLOOR" :    ("FLOOR", 2, 2, 0),
        "GC" :       ("Get Coordinate projected onto the projection vector", 0, 1, 1),
        "GETINFO" :  ("GET INFOrmation", 0, 1, 1),
        "GFV" :      ("Get Freedom Vector", 0, 0, 2),
        "GPV" :      ("Get Projection Vector", 0, 0, 2),
        "GT" :       ("Greater Than", 1, 2, 1),
        "GTEQ" :     ("Greater Than or EQual", 1, 2, 1),
        "IDEF" :     ("Instruction DEFinition", 1, 1, 0),
        "IF" :       ("IF test", 1, 1, 0),
        "INSTCTRL" : ("INSTRuction execution ConTRoL", 2, 2, 0),
        "IP" :       ("Interpolate Point", 1, 1, 0),
        "ISECT" :    ("moves point p to the InterSECTion of two lines", 5, 5, 0),
        "IUP" :      ("Interpolate Untouched Points through the outline", 0, 0, 0),
        "JMPR" :     ("JuMP Relative", 1, 1, 0),
        "JROF" :     ("Jump Relative On False", 1, 1, 0),
        "JROT" :     ("Jump Relative On True", 3, 3, 0),
        "LOOPCALL" : ("LOOP and CALL function", 2, 2, 0),
        "LT" :       ("Less Than", 1, 2, 1),
        "LTEQ" :     ("Less Than or Equal", 1, 2, 1),
        "MAX" :      ("MAXimum of top two stack elements", 1, 2, 1),
        "MD" :       ("Measure Distance", 1, 2, 1),
        "MDAP" :     ("Move Direct Absolute Point", 1, 1, 0),
        "MDRP" :     ("Move Direct Relative Point", 1, 1, 0),
        "MIAP" :     ("Move Indirect Absolute Point", 2, 2, 0),
        "MIN" :      ("MINimum of top two stack elements", 1, 2, 1),
        "MINDEX" :   ("Move the INDEXed element to the top of the stack", 1, 1, 3),
        "MIRP" :     ("Move Indirect Relative Point", 2, 2, 0),
        "MPPEM" :    ("Measure Pixels Per EM", 0, 0, 1),
        "MPS" :      ("Measure Point Size", 0, 0, 1),
        "MSIRP" :    ("Move Stack Indirect Relative Point", 1, 1, 0),
        "MUL" :      ("MULtiply", 1, 2, 1),
        "NEG" :      ("NEGate", 0, 1, 1),
        "NEQ" :      ("Not EQual", 1, 2, 1),
        "NOT" :      ("logical NOT", 0, 1, 1),
        "NPUSHB" :   ("PUSH N Bytes", -1, 0, 0),
        "NPUSHW" :   ("PUSH N Words", -1, 0, 0),
        "NROUND" :   ("No ROUNDing of value", 0, 1, 1),
        "ODD" :      ("ODD", 0, 1, 1),
        "OR" :       ("logical OR", 1, 2, 1),
        "POP" :      ("POP top stack element", 1, 1, 0),
        "PUSHB" :    ("PUSH Bytes", -4, 0, 0),
        "PUSHW" :    ("PUSH Words", -4, 0, 0),
        "RCVT" :     ("Read Control Value Table entry", 0, 1, 1),
        "RDTG" :     ("Round Down To Grid", 0, 0, 0),
        "ROFF" :     ("Round OFF", 0, 0, 0),
        "ROLL" :     ("ROLL the top three stack elements", 0, 3, 3),
        "ROUND" :    ("ROUND value", 0, 1, 1),
        "RS" :       ("Read Store", 0, 1, 1),
        "RTDG" :     ("Round To Double Grid", 0, 0, 0),
        "RTG" :      ("Round To Grid", 0, 0, 0),
        "RTHG" :     ("Round To Half Grid", 0, 0, 0),
        "RUTG" :     ("Round Up To Grid", 0, 0, 0),
        "S45ROUND" : ("Super ROUND 45 degrees", 1, 1, 0),
        "SANGW" :    ("Set Angle Weight", 1, 1, 0),
        "SCANCTRL" : ("SCAN conversion ConTRoL", 1, 1, 0),
        "SCANTYPE" : ("SCANTYPE", 1, 1, 0),
        "SCFS" : ("Sets Coordinate From the Stack using projection vector and freedom vector", 2, 2, 0),
        "SCVTCI" :   ("Set Control Value Table Cut-In", 1, 1, 0),
        "SDB" :      ("Set Delta Base in the graphics state", 1, 1, 0),
        "SDPVTL" :   ("Set Dual Projection Vector To Line", 2, 2, 0),
        "SDS" :      ("Set Delta Shift in the graphics state", 1, 1, 0),
        "SFVFS" :    ("Set Freedom Vector From Stack", 2, 2, 0),
        "SFVTCA" :   ("Set Freedom Vector To Coordinate Axis", 0, 0, 0),
        "SFVTL" :    ("Set Freedom Vector To Line", 2, 2, 0),
        "SFVTP" :    ("Set Freedom Vector To Projection Vector", 0, 0, 0),
        "SHC" :      ("SHift Contour using reference point", 1, 1, 0),
        "SHP" :      ("SHift Point using reference point", 1, 1, 0),
        "SHPIX" :    ("SHift point by a PIXel amount", 2, 2, 0),
        "SHZ" :      ("SHift Zone using reference point", 1, 1, 0),
        "SLOOP" :    ("Set LOOP variable", 1, 1, 0),
        "SMD" :      ("Set Minimum Distance", 1, 1, 0),
        "SPVFS" :    ("Set Projection Vector From Stack", 2, 2, 0),
        "SPVTCA" :   ("Set Projection Vector To Coordinate Axis", 0, 0, 0),
        "SPVTL" :    ("Set Projection Vector To Line", 2, 2, 0),
        "SROUND" :   ("Super ROUND", 1, 1, 0),
        "SRP0" :     ("Set Reference Point 0", 1, 1, 0),
        "SRP1" :     ("Set Reference Point 1", 1, 1, 0),
        "SRP2" :     ("Set Reference Point 2", 1, 1, 0),
        "SSW" :      ("Set Single Width", 1, 1, 0),
        "SSWCI" :    ("Set Single Width Cut-In", 1, 1, 0),
        "SUB" :      ("SUBtract", 1, 2, 1),
        "SVTCA" :    ("Set freedom and projection Vectors To Coordinate Axis", 0, 0, 0),
        "SWAP" :     ("SWAP the top two elements on the stack", 0, 2, 2),
        "SZP0" :     ("Set Zone Pointer 0", 1, 1, 0),
        "SZP1" :     ("Set Zone Pointer 1", 1, 1, 0),
        "SZP2" :     ("Set Zone Pointer 2", 1, 1, 0),
        "SZPS" :     ("Set Zone PointerS", 1, 1, 0),
        "UTP" :      ("UnTouch Point", 1, 1, 0),
        "WCVTF" :    ("Write Control Value Table in Funits", 1, 1, 0),
        "WCVTP" :    ("Write Control Value Table in Pixel units", 2, 2, 0),
        "WS" :       ("Write Store", 2, 2, 0),
    }
    
    stack1_command = ("NPUSHB", "NPUSHW")
    stack2_command = ("PUSHB", "PUSHW")
    
    arg_1_command = ("MDAP", "MDRP")
    arg_2_command = ("MIAP", "MIRP")
    
    delta_command = ("F_DELTA",)
    #step_list USE IN SPECIAL PSUEDO FUNCTION f_delta
    step_list = [-8, -7, -6, -5, -4, -3, -2, -1, 1, 2, 3, 4, 5, 6, 7, 8]
    
    normal_command = []
    for i in range(6):
        normal_command.append( [ j for j in inst_dict.keys() if inst_dict[j][1]==i ] )
    
    #           -1=FIRST BYTE IS ONE BYTE COUNTER,
    #           -2=FIRST BYTE IS TWO BYTE COUNTER, 
    #           -3=CLEAR STACK
    #           -4=REQUIRE PROCESS
    pop_command = []
    pop_command.append([])
    for i in range(-1, -4, -1):
        pop_command.append( [ j for j in inst_dict.keys() if inst_dict[j][1]==i ] )
    
    
    def usage(prog_name):
        print """\
    Usage: %s [-d CODE | [-e] PSUEDO_CODE]
    """ % (prog_name)
        return
    
    def line_format(cmd,stack,desc):
        #STRING, LIST, STRING
        return "%-24s %-10s ;%10s" % (cmd, "  ".join(stack), desc,)
    
    def f_delta(first_cmd, l):
        #PSUEDO COMMAND TO PROCESS DELTA FUNCTION
        #
        #first_cmd = PSUEDO COMMAND, ex: f_deltap1 = deltap1
        #l = LIST OF ARGUMENT (INCLUDE SELF UN-UPPERCASE COMMAND IN FIRST ARGUMENT)
        #
        #usage: 
        #   f_deltap 0 16+8     #deltap1 MOVE POINT 0 AT 16 PPEM +8
        #real code will be:
        #   DELTAP1  1 0 127
        #
        d1_list = []
        d2_list = []
        d3_list = []
        first_cmd = first_cmd[2:]   #TRIM F_ OUT
        l.pop(0)
        while len(l) > 0:
            point = l.pop(0)
            pstep = l.pop(0)
            if '+' in pstep or '-' in pstep:
                if '+' in pstep:
                    ppem, no_of_step = pstep.split('+')
                    ppem = int(ppem)
                    no_of_step = int(no_of_step)
                else:
                    ppem, no_of_step = pstep.split('-')
                    ppem = int(ppem)
                    no_of_step = -int(no_of_step)
                if ppem < 9:
                    raise ValueError('ppem must greater than 9, %s: %s %s' % (first_cmd, point, pstep,))
                if abs(no_of_step) > 8:
                    raise ValueError('no_of_step must be between +8 and -8, %s: %s %s' % (first_cmd, point, pstep,))
                if no_of_step == 0:
                    raise ValueError('no_of_step must not be 0, %s: %s %s' % (first_cmd, point, pstep,))
                num = (ppem-9)*16 + step_list.index(no_of_step)
                if num < 256:
                    d1_list.extend([point,str(num)])
                elif num < 512:
                    d2_list.extend([point,str(num)])
                elif num < 1024:
                    d3_list.extend([point,str(num)])
                else:
                    raise ValueError('Number of delta point exceed 1024, %s' % (num,))
            else:
                raise SyntaxError('Please use + or - in f_delta second argument, %s %s' % (point, pstep,))
        new_l = []
        if d1_list:
            new_l.append(first_cmd+'1')
            new_l.append(str(len(d1_list)/2))
            new_l.extend(d1_list)
        if d2_list:
            new_l.append(first_cmd+'2')
            new_l.append(str(len(d2_list)/2))
            new_l.extend(d2_list)
        if d3_list:
            new_l.append(first_cmd+'3')
            new_l.append(str(len(d3_list)/2))
            new_l.extend(d3_list)
        return  new_l[0], new_l
    
    def inst_decode(txt):
        """Decode TrueType Instruction code into simpler code"""
        txt_list = txt.split("\n")
        new_list = []
        commentlist = []
        stack_list = []
        sloop = 0
        i = 0
        while i < len(txt_list):
            if "[" in txt_list[i]:
                c1,c2 = txt_list[i].strip().split("[",1)
            else:
                c1,c2 = txt_list[i].strip(), ""
            #SKIP EMPTY LINE
            if c1 == "":
                i += 1
                continue
            #STACK: NPUSHB, NPUSHW
            if c1 in stack1_command:
                i += 1
                n = int(txt_list[i].strip())
                i += 1
                while n > 0:
                    stack_list.append(txt_list[i].strip())
                    i += 1
                    n -= 1
                continue
            #STACK2: PUSHB, PUSHW
            if c1[:5] in stack2_command:
                n = int(txt_list[i].strip()[6:])
                i += 1
                while n > 0:
                    stack_list.append(txt_list[i].strip())
                    i += 1
                    n -= 1
                continue
            temp_list = []
            #POP ONE
            if c1 in pop_command[1]:
                idx = stack_list.pop()
                temp_list = [idx]
                for j in range(int(idx)):
                    temp_list.append(stack_list.pop()) 
                new_list.append(line_format(txt_list[i],temp_list,inst_dict[c1][0]))
                i += 1
                continue
            #POP PAIR
            elif c1 in pop_command[2]:
                idx = stack_list.pop()
                temp_list = [idx]
                for j in range(int(idx)):
                    temp_list.append(stack_list.pop()) 
                    temp_list.append(stack_list.pop()) 
                new_list.append(line_format(txt_list[i],temp_list,inst_dict[c1][0]))
                i += 1
                continue
            #POP ALL (CLEAR STACK)
            elif c1 in pop_command[3]:
                stack_list = []
                new_list.append(line_format(txt_list[i],temp_list,inst_dict[c1][0]))
                i += 1
                continue
    
            #NORMAL COMMAND
            if c1 in normal_command[0]:
                count = 0
            elif c1 in normal_command[1]:
                count = 1 
            elif c1 in normal_command[2]:
                count = 2 
            elif c1 in normal_command[3]:
                count = 3 
            elif c1 in normal_command[4]:
                count = 4 
            elif c1 in normal_command[5]:
                count = 5 
            else:
                count = -1
                #print 'count-1:',c1
                new_list.append('count-1:%s' % (c1,))
            if count > 0:
                if sloop > 0:
                    count += sloop-1
                    sloop = 0
            while count > 0:
                if stack_list == []:
                    #print temp_list
                    new_list.extend(temp_list)
                    break
                cnum = stack_list.pop()
                temp_list.append(cnum)
                if c1 == 'SLOOP':
                    sloop = int(cnum)
                count -= 1
            new_list.append(line_format(txt_list[i],temp_list,inst_dict[c1][0]))
            i += 1
        return new_list
        
    def stack_format(stack_list):
    
        def flush(new_list, cmd_list, ind, cur_ind):
            #if len(cmd_list)==0: return [], [], cur_ind
            if ind == 0:
                temp = ['PUSHB_','NPUSHB']
            else:
                temp = ['PUSHW_','NPUSHW']
            if len(cmd_list) < 8:
                new_list.append('%s%s' % (temp[0],len(cmd_list),))
            else:
                new_list.append(temp[1])
                new_list.append(' %s' % (len(cmd_list),))
            new_list.extend(cmd_list)
            return new_list, [], cur_ind
    
        if len(stack_list)==0: return []
        n = 0
        new_list = []
        cmd_list = []
        stack_list.reverse()
        if int(stack_list[n]) < 256:
            ind = 0     #BYTE
        else:
            ind = 1     #WORD
        while n < len(stack_list):
            if int(stack_list[n]) < 256:
                cur_ind = 0     #BYTE
            else:
                cur_ind = 1     #WORD
            if ind == cur_ind:
                cmd_list.append(' %s' % (stack_list[n],))
            else:
                new_list, cmd_list, ind = flush(new_list, cmd_list, ind, cur_ind)    #FLUSH
                cmd_list.append(' %s' % (stack_list[n],))
            n += 1
        new_list, cmd_list, ind = flush(new_list, cmd_list, ind, cur_ind)    #FLUSH
        return new_list 
    
    def inst_encode(txt,cvt_dict={}):
        """Encode simple code into TrueType Instruction code"""
        if cvt_dict == {}:
            print "WARNING: cvt_dict is empty, please supply cvt_dict argument"
        comment_list = ['#',';']    #COMMENT CHARACTER
        flush_list = ['SVTCA']      #FLUSH STACK
        stack_list = []
        new_list = []
        cmd_list = []
        txt_list = txt.split('\n')
        for line in txt_list:
            line = line.replace("\t"," ")
            if " " in line:
                l = line.split(" ")
            else:
                l = [line] 
            l = [ j for j in l if j != "" ]
            if l == []:
                continue
            if l[0][0] in comment_list: #BYPASS COMMENT
                continue
            for f in flush_list:    #?FLUSH STACK
                if f == l[0][:len(f)]:
                    new_list.extend(stack_format(stack_list))
                    stack_list = []
                    new_list.extend(cmd_list)
                    cmd_list = []
                    break
            if '[' in l[0]:         #TO UPPERCASE
                ltemp = l[0].split('[',1)
                first_cmd = ltemp[0].upper()
                first_word = first_cmd+'['+ltemp[1]
            else:
                first_word = l[0].upper()
                first_cmd = first_word
            #CHECK SPECIAL COMMAND ---------------------------------------
            # f_delta -> f_deltap1 = deltap1, ...
            for temp in delta_command:
                if temp in first_word:
                    first_word, l = f_delta(first_word, l)
        
            #END CHECK SPECIAL COMMAND -----------------------------------
            cmd_list.append(first_word)     #KEEP COMMAND
            l.pop(0)                        #PROCESS REST STACK NUM
    #
            temp_arg_list = []
            for element in l:
                if element[0] in comment_list:
                    break 
                else:
                    temp_list = element.split('[',1)
                    if element in cvt_dict.keys():                  #CHECK CVT VALUE
                        stack_list.append(cvt_dict[element])
                        temp_arg_list.append(element)
                    elif temp_list[0].upper() in inst_dict.keys():  #TRAP DELTA COMMAND
                        temp_list[0] = temp_list[0].upper()
                        element = '['.join(temp_list)
                        cmd_list.append(element)
                    else:
                        stack_list.append(element)
                        temp_arg_list.append(element)
            #TEST NUMBER OF PARAMETER
            if first_cmd in arg_1_command:
                if len(temp_arg_list) != 1:
                    raise ValueError('%s must take 1 argument, %s' % (first_word, temp_arg_list,))
            elif first_cmd in arg_2_command:
                if len(temp_arg_list) != 2:
                    raise ValueError('%s must take 2 arguments, %s' % (first_word, temp_arg_list,))
            #
        if stack_list != []:
            new_list.extend(stack_format(stack_list))
        new_list.extend(cmd_list)
        return new_list
    
    
    def inst_encode_all(sfd_file, pseudo_code_dict={}, cvt_dict={}):
        """Encode all characters in pseudo_code_dict, modify FONTNAME.sfd."""
        if os.path.isfile(sfd_file):
            sfd = open(sfd_file).read().split('\n')
            sfd_bak = sfd[:]
        else:
            print "File %s.sfd is not existed, script aborted." % (sfd_file,)
            sys.exit[1]
        for chr,txt in pseudo_code_dict.iteritems():
            search_line = 'StartChar: %s' % (chr,)
            if search_line in sfd:
                line = sfd.index(search_line)
                while line < len(sfd):
                    if sfd[line] == "TtInstrs:":
                        temp_sfd = sfd[:line+1]
                        #print temp_sfd[-2:]
                        temp_sfd += inst_encode(txt,cvt_dict)
                        line += 1
                        while sfd[line] != "EndTTInstrs":
                            line += 1
                        temp_sfd += sfd[line:]
                        sfd = temp_sfd[:]
                        break
                    elif sfd[line] in ["Back","Fore"]:
                        temp_sfd = sfd[:line] \
                                   + ["TtInstrs:"] \
                                   + inst_encode(txt,cvt_dict) \
                                   + ["EndTTInstrs"]+sfd[line:]
                        sfd = temp_sfd[:]
                        break
                    elif sfd[line] == "EndChar":
                        print "sfd file format error at %s" % (chr,)
                        break
                    else:
                        line += 1
            else:
                print "Character %s not found, script aborted." % (chr,)
                sys.exit(1)
        if sfd != sfd_bak:
            os.rename(sfd_file, "%s.bak" % (sfd_file,))
            f = open(sfd_file,"w")
            f.write("\n".join(sfd))
            f.close()
            print "%s modified." % (sfd_file,)
        else:
            print "No modified."
    
    
    if __name__ == "__main__":
        if sys.argv[1] == "-d":
            #DECODE
            #$0 -d x.txt > d.txt = PRINT PSEUDO_CODE FROM x.txt TO d.txt
            f = open(sys.argv[2])
            txt = f.read()
            f.close()
            print '\n'.join(inst_decode(txt))
        elif sys.argv[1] == "-e":
            #ENCODE
            #$0 -e d.txt > x.txt = PRINT INSTRUCTED CODE FROM PSEUDO_CODE d.txt TO x.txt
            f = open(sys.argv[2])
            txt = f.read()
            f.close()
            print '\n'.join(inst_encode(txt))
        else:
            #DEFAULT IS ENCODING
            #$0 -e d.txt > x.txt = PRINT INSTRUCTED CODE FROM PSEUDO_CODE d.txt TO x.txt
            f = open(sys.argv[1])
            txt = f.read()
            f.close()
            print '\n'.join(inst_encode(txt))
    
    Topic: 

    บันทึกการ instruct ฟอนต์

    เที่ยวนี้ไม่มีอะไร บันทึกตัวอย่างการ instruct เอาไว้ดูเพื่ออ้างอิงเฉย ๆ

    ตัวอย่าง DejaVuSans อักขระ n

    โค๊ด

    NPUSHB
     25
     3
     9
     0
     3
     14
     1
     6
     135
     14
     17
     184
     12
     188
     10
     1
     2
     8
     0
     78
     13
     9
     8
     11
     70
     20
    SRP0
    MIRP[rp0,min,rnd,grey]
    MIRP[min,rnd,grey]
    SHP[rp2]
    MIRP[rp0,rnd,grey]
    MIRP[min,rnd,grey]
    IUP[x]
    SVTCA[y-axis]
    MDAP[rnd]
    ALIGNRP
    MIRP[rnd,grey]
    MIRP[rp0,rnd,grey]
    MDRP[rnd,grey]
    MIRP[min,rnd,grey]
    SRP1
    SRP2
    SLOOP
    IP
    IUP[y]
    PUSHB_5
     96
     21
     207
     21
     2
    SVTCA[x-axis]
    DELTAP1
    

    outline

    x-direction instruction

    y-direction instruction

    คลี่สแต็กให้พออ่านง่าย

    CODE                   RP0     STACK      DESCRIPTION
                                               (x-axis is default direction)
    SRP0                           20         Set Reference Point 0
    MIRP[rp0,min,rnd,grey] (20)    70 11      Move Indirect Relative Point
    MIRP[min,rnd,grey]     (11)    8 9
    SHP[rp2]               (11)    13         SHift Point using reference point
    MIRP[rp0,rnd,grey]     (11)    78 0
    MIRP[min,rnd,grey]     (0)     8 2
    IUP[x]                                    Interpolate Untouched Points through the outline
    
    SVTCA[y-axis]                             Set freedom and projection Vectors To Coordinate Axis
    MDAP[rnd]                      1          Move Direct Absolute Point
    ALIGNRP                (1)     10         ALIGN to Reference Point
    MIRP[rnd,grey]         (1)     188 12
    MIRP[rp0,rnd,grey]     (1)     184 17
    MDRP[rnd,grey]         (17)    14
    MIRP[min,rnd,grey]     (17)    135 6
    SRP1                           1          Set Reference Point 1
    SRP2                           14         Set Reference Point 2
    SLOOP                          3          Set LOOP variable
    IP                             0 9 3      Interpolate Point
    IUP[y]                                    Interpolate Untouched Points through the outline
    
    SVTCA[x-axis]
    DELTAP1                       2 21 207 21 96    DELTA exception P1
    


    อธิบาย deltap

    The 8 bit arg component of the DELTA instructions decomposes into two parts. 
    The first 4 bits represent the relative number of pixels per em at which the exception is applied. 
    The second 4 bits represent the magnitude of the change to be made. The structure of the arg is shown in FIGURE 26.
    ppem = pointSize * dpi / 72 
    
    207 = 1100 1111
    1100=12 (9+12=21ppem)
    1111=15 = 8step = 8/64 of pixel    (0=-8, 1=-7, ... 14=+7, 15=+8)
    
    96 = 0110 0000
    0110=6 (9+6=15ppem)
    0000=0 = -8step = -8/64 of pixel
    

    แปลว่า
    1.เลื่อนจุด 21 ไป 8step ที่ขนาดฟอนต์ 21 ppem (จอ Mac75dpi=21pt Windows96dpi~16pt Linux(จอผม85dpi)~18pt)
    2. เลื่อนจุด 21 ไป -8step ที่ขนาดฟอนต์ 15 ppem (จอ Mac75dpi=15pt Windows96dpi~11pt Linux(จอผม85dpi)~13pt)

    ตาราง cvt

    ITEM            VALUE           DESCRIPTION
    8               184             stem width
    70              186             left spacing
    78              938             outer width
    135             156             top stem thick
    184             1147            straight vertical height
    188             1120            outer height
    
    Topic: 

    fonts: ตัวอย่างการ hint ก.ไก่

    มีค่า cvt ของฟอนต์ DejaVu Sans ที่เกี่ยวข้อง ทำเป็นตัวแปรแบบดิกชันนารีในไพธอน คือ
    cvt_dict = {
        ...
        "front_kai":      11,     #113    =front spacing of ko_kai 
        "hstem":          8,      #184    =horizontal stem width
        "w_881":          163,    #881    =width of ko kai
        "vheight_shoot":  184,    #1147   =overshoot height
        "vstem_curve":    185,    #156    =curve vertical stem width
        ...
    }
    
    ค่า cvt ของฟอนต์ ดูได้จากเมนูของ FontForge คือ Hints -> Edit 'cvt' ... โดยจะเรียงตั้งแต่ลำดับที่ 0 เป็นต้นไป
    ค่า cvt ของ ก.ไก่ข้างต้น คือ กำหนดให้ลำดับที่ 11 (ซึ่งมีค่า 113) เป็นระยะช่องไฟด้านหน้า เป็นต้น โครง ก.ไก่ ตัวอย่างจะเริ่มด้วยการ hint ในแนวแกน X ก่อน (เป็นค่าปริยาย) จะแสดงเป็นโค๊ด instruction แบบจำลอง เพื่อให้ดูง่าย เวลาใช้งานจริง ก็นำไปเขียนเป็นโค๊ดในไพธอน โปรแกรม dfont.py จะคลี่แสต็กและเรียงออกมาเป็นโค๊ดจริง ๆ อีกที แนวแกน X
    เส้นทาง hint ในแนวแกน X จุดเริ่มต้นคือจุดที่ 26 (จุดสุดท้ายคือ 25 + 1)
    srp0                26
    
    จากจุดเริ่มต้น จะเคลื่อนจุดอ้างอิง rp0 ไปยังจุดที่ใกล้ขอบซ้ายที่สุด คือจุด 18 เพื่อกั้นเป็นระยะช่องไฟด้านหน้า ด้วยค่า cvt คือ front_kai
    การเคลื่อนด้วยการอ้างอิงค่า คำสั่งคือ MIRP
    การเคลื่อนจุดอ้างอิงไปด้วย ใช้อาร์กิวเมนต์ rp0
    การปัดเศษให้ลงจุด (round) ใช้อาร์กิวเมนต์ rnd
    การแรเงาพื้นที่ ใช้อาร์กิวเมนต์ grey (ปกติควรเป็น white แต่ DejaVu ใฃ้ grey เสมอ จึงใช้ตาม เพื่อกันสับสน)
    ค่า cvt ที่ใช้ คือ front_kai
    จุดที่จะเคลื่อนไป คือ 18
    mirp[rp0,min,rnd,grey]  front_kai 18
    
    จุดที่จะไปต่อคือ จุด 8 แต่เนื่องจากเมื่อเคลื่อนไปแล้ว ไม่มีจุดที่จะไปต่อ เราจึงไม่ต้องเคลื่อนจุด rp0 ไปด้วย จึงไม่ต้องใส่อาร์กิวเมนต์ rp0
    ระยะของปาก ก.ไก่ ไม่จำเป็นต้องเป็นค่าที่แน่นอนนัก เราจึงไม่ต้องอ้างค่า cvt จึงใช้คำสั่ง MDRP
    ปัดเศษด้วย ใช้ rnd
    แรเงา ใช้ grey
    จุดที่เคลื่อนไปคือ 8
    mdrp[min,rnd,grey]  8
    
    ตอนนี้ rp0 ยังอยู่ที่เดิม คือ จุด 18 เราต้องเคลื่อนไปต่อที่จุด 17 และ 10 ตามลำดับ แต่หากเราเคลื่อนไปจุด 17 ก่อน แล้วต่อไปที่ 10 จะทำให้ระยะที่ถูกทด (round) แล้ว มีค่ามากเกินไป ทำให้ปาก ก.ไก่ ไม่สวยงาม เราจึงใช้การเคลื่อนไปที่จุด 10 ก่อน แล้วจึงย้อนมาที่ 17 อีกครั้งหนึ่ง ทำให้ได้ภาพที่สวยงามกว่า
    mdrp[rp0,rnd,grey]  10
    mdrp[min,rnd,grey]  17
    
    จุดที่จะไปต่อคือจุด 14 แต่ตอนนี้จุดอ้างอิง rp0 มาอยู่ที่จุด 10 แล้ว หากเราเคลื่อนจากจุดนี้ไปเลย จะทำให้ระยะทดไม่แน่นอน จึงควรย้อนกลับไปที่จุดแรกคือ 18 จะดีกว่า
    srp0                18
    mdrp[rp0,rnd,grey]  14
    
    ต่อไปเป็นการกำหนดความหนาของเส้นตั้ง (hstem) ต้องใช้ค่า cvt เสมอ
    ต้องอ้างอิงจาก cvt คำสั่งคือ MIRP
    ไม่ต้องย้าย rp0 ไปด้วย เพราะไม่มีจุดที่จะไปต่อ จึงไม่ต้องใช้อาร์กิวเมนต์ rp0
    ถึงแม้ขนาดเส้นจะบางอย่างไร การแสดงผลต้องกำหนดค่าให้อย่างน้อย 1 จุดเสมอ ใช้อาร์กิวเมนต์ min
    แรเงาใช้ grey
    mirp[min,rnd,grey]  hstem  12
    
    ต่อไปเป็นการกำหนดความกว้างของตัวอักษร ต้องใช้ cvt เสมอ โดยต้องย้ายจุด rp0 ไปด้วย เพื่อไปทำงานต่อ โดยจะกำหนดขนาดให้คลุมความกว้างรวมก่อน แล้วจึงย้อนมากำหนดความหนาของเส้นตั้งตัวหลังอีกครั้งหนึ่ง
    mirp[rp0,rnd,grey]  w_881 25
    mirp[min,rnd,grey]  hstem  1
    
    กั้นเป็นฃ่องไฟด้านหลัง โดยไม่ต้องกำหนดระยะ min และเผื่อการแรเงาเป็น white เพื่อให้มีช่องว่างอย่างน้อย 1 จุดคั่นกับอักษรตัวถัดไป มีประโยชน์สำหรับฟอนต์ที่มีการทดลงจุดในแนวแกน X มาก ๆ ซึ่งจะทำให้ล้นความกว้างที่มีอยู่ (คำสั่งนี้คิดเอง เพื่อให้ไม่ต้องทำ delta hint ตามแบบของ DejaVu)
    mdrp[rnd,white]     27
    
    เกลี่ยจุดที่เรายังไม่ได้อ้างอิงในแนวแกน X ซึ่งจะใช้คำสั่งสี้ปิดท้ายเสมอ (IUP คือ Interpolate Untouched Point)
    IUP[x]
    
    แนวแกน Y
    เส้นทาง hint ในแนวแกน Y เริ่มย้ายมาแกน Y
    SVTCA[y-axis]
    
    จะเริ่มต้นที่จุด 1 ในแนวแกน Y นี้ เรายังไม่ได้ทำอะไรเลย จึงควรปัดให้ลงจุดเสียก่อน
    mdap[rnd]           1
    
    บอกว่าจุด 13 อยู่ในระดับเดียวกับจุด 1
    alignrp             13
    
    กำหนดความสูงของฟอนต์
    mirp[rp0,rnd,grey]  vheight_shoot    22
    
    กำหนดความหนาของเส้นในแนวนอน
    mirp[min,rnd,grey]  vstem_curve  5
    
    กำหนดให้ rp1 เป็นจุด 5 และ rp2 เป็นจุด 13 เพื่อจะเกลี่ยจุดที่สำคัญอื่น ๆ โดยใช้ rp1 และ rp2 เป็นจุดอ้างอิงในการเฉลี่ยค่า
    srp1                5
    srp2                13
    sloop               10
    ip      19 8 18 9 17 10 2 25 15 12
    
    เกลี่ยจุดที่เหลือ ในแนวแกน Y
    IUP[y]
    
    เมื่อนำโค๊ดที่เขียนมาถอดด้วยสคริปต์ dfont.py จะได้โค๊ด instruction ดังนี้
    NPUSHB
     15
     27
     1
     8
     25
     163
     12
     8
     14
     18
     17
     10
     8
     18
     11
     26
    SRP0
    MIRP[rp0,min,rnd,grey]
    MDRP[min,rnd,grey]
    MDRP[rp0,rnd,grey]
    MDRP[min,rnd,grey]
    SRP0
    MDRP[rp0,rnd,grey]
    MIRP[min,rnd,grey]
    MIRP[rp0,rnd,grey]
    MIRP[min,rnd,grey]
    MDRP[rnd,white]
    IUP[x]
    NPUSHB
     19
     12
     15
     25
     2
     10
     17
     9
     18
     8
     19
     10
     13
     5
     5
     185
     22
     184
     13
     1
    SVTCA[y-axis]
    MDAP[rnd]
    ALIGNRP
    MIRP[rp0,rnd,grey]
    MIRP[min,rnd,grey]
    SRP1
    SRP2
    SLOOP
    IP
    IUP[y]
    จบแล้วครับ
    Topic: 

    fonts: ตัวอย่างการ hint ข.ไข่

    ค่า cvt ที่เกี่ยวข้องคือ
    cvt_dict = {
        ...
        "w_690":            14,     #690    =width of kho_khai
        "hstem":            8,      #184    =horizontal stem
        "vstem":            96,     #154    =vertical stem
        "headstem":         258,    #116    =head stem
        "headstem_plus_hole":   259, #268   =head stem + hole width
        "headdia":          257,    #384    =head diameter
        "vheight":          188,    #1120   =normal height 
        "vheight_shoot":    184,    #1147   =overshoot height
        "front_khai":       32,     #51     =front spacing of kho_khai
        ...
    }
    ภาพลายเส้น
    โครง ข.ไข่ แนวแกน X
    เส้นทาง hint ในแนวแกน X ตัวอย่างนี้เริ่มซับซ้อน เนื่องจากช่วงหัวของ ข.ไข่ มีทางเดินที่ต้องการทดลงจุดมาก ทำให้ระยะที่ได้มีค่าไม่แน่นอนตามขนาดฟอนต์ที่เปลี่ยนไป ผลที่ได้คือ
    ถ้าเราทำให้ฟอนต์มีขนาดคงที่ จะทำให้บางครั้งหัวดูลีบหรือใหญ่เกินไป
    ถ้าเราทำให้หัวดูสวยงาม บางครั้งฟอนต์จะดูกว้างหรือลีบเกินไป
    ทางแก้คือเราจะ hint ให้ช่วงหัวให้ดูสวยงามก่อน แล้วโยงไปเส้น stem ด้านหลัง แล้วจึงโยงกลับมาที่ stem ด้านหน้าอีกครั้ง โดยใช้ตรงโค้งช่วงคอของอักษรเป็นช่วงที่รับระยะการเปลี่ยนแปลง วิธีนี้ทำให้เกิดผลข้างเคียงน้อยที่สุด (แต่ก็ยังมีอยู่ดี ต้องแก้ต่อไปด้วย delta hint สำหรับขนาดฟอนต์ที่ดูไม่งาม) เริ่มต้นที่จุด 53
    srp0                53
    
    เคลื่อนไปจุดใกล้ทีสุดด้วยระยะ front_khai
    mirp[rp0,min,rnd,grey]  front_khai 21
    
    จัดการหัวก่อน ด้วยการกำหนดขนาดเส้นหัว เคลือนไปอีกด้านหนึ่ง แล้วกำหนดขนาดย้อนกลับอีกครั้ง
    mirp[min,rnd,grey]  headstem 50
    mirp[rp0,rnd,grey]  headdia 15
    mirp[min,rnd,grey]  headstem 44
    
    กำหนดจุดคอดที่จุด 12
    mdrp[rnd,grey]      12
    
    เคลื่อนไปที่จุด 7 ด้วยการแรเงาแบบ white เพื่อแยกหัวออกจากคออักษรให้ชัดเจน
    mdrp[rp0,rnd,white] 7
    
    เคลื่อนไปต่อ เพื่อกำหนดความกว้างของคอ ให้เท่ากับระยะ vstem (ยืมค่า vstem ซึ่งน้อยกว่า hstem มาใช้สำหรับคออักษร)
    mirp[rp0,rnd,grey]  vstem 27
    
    เคลื่อนไปต้อ ด้วยการแรเงาแบบ white เพื่อแยกเส้นหน้าและเส้นหลังจากกัน
    mdrp[rp0,rnd,white] 36
    
    เคลื่อนต่อ กำหนดขนาดของเส้นหลัง
    mirp[rp0,min,rnd,grey] hstem 38
    
    กำหนดระยะกั้นหลัง
    mdrp[rnd,white]     54
    
    เคลื่อนย้อนกลับมาที่จุด 1 เพื่อกำหนดความกว้างหลักของอักษร ให้เท่ากับระยะ w_690
    mirp[rp0,rnd,grey]  w_690 1
    
    กำหนดความกว้างของเส้นหน้า
    mirp[min,rnd,grey]  hstem 32
    
    เกลี่ยจุดที่เหลือ จบการทำงานในแกน X
    IUP[x]
    
    แนวแกน Y
    เส้นทาง hint ในแนวแกน Y ทำงานในแกน Y
    SVTCA[y-axis]
    
    ปัดเศษลงจุด 1
    mdap[rnd]           1
    
    กำหนดความสูงปกติของเส้นหลัง
    mirp[rnd,grey]      vheight 38
    
    กำหนดความหนาของเส้นแนวนอนให้เท่ากับระยะ vstem
    mirp[min,rnd,grey]  vstem 33
    
    เคลื่อนไปเพื่อกำหนดความสูงหลอกตา (overshoot) ของหัว
    mirp[rp0,rnd,grey]  vheight_shoot 24
    
    เคลื่อนจุด กำหนดความหนาของคอแนวนอน โดยกำหนดให้เท่ากับความหนาของหัว คือ headstem
    mirp[rp0,rnd,grey]  headstem 10
    
    ย้ายมากำหนดส่วนคอด
    mdrp[rp0,rnd,white] 12
    
    ตรงนี้ เราจะเคลื่อนลงไปใต้สุดของหัวเลย เพื่ออาศัยพื้นที่ส่วนบนของหัว เป็นระยะรองรับการทดจุด (มีพื้นที่ดำมากกว่าส่วนอื่น) แล้วจึงกำหนดจุดของความหนาหัวและรูของหัวอักษรจากด้านล่าง
    mdrp[rp0,rnd,grey]  18
    mirp[min,rnd,grey]  headstem 41
    mirp[rnd,grey]      headstem_plus_hole 47
    
    เกลี่ยจุดเป็นคำสั่งสุดท้าย
    IUP[y]
    
    เสร็จแล้ว
    ถอดด้วย dfont.py จะได้โค๊ด instruction ดังนี้
    NPUSHB
     12
     32
     8
     1
     14
     54
     38
     8
     36
     27
     96
     7
     44
    PUSHW_1
     258
    PUSHB_2
     12
     15
    PUSHW_1
     257
    PUSHB_1
     50
    PUSHW_1
     258
    PUSHB_3
     21
     32
     53
    SRP0
    MIRP[rp0,min,rnd,grey]
    MIRP[min,rnd,grey]
    MIRP[rp0,rnd,grey]
    MDRP[rnd,grey]
    MIRP[min,rnd,grey]
    MDRP[rp0,rnd,white]
    MIRP[rp0,rnd,grey]
    MDRP[rp0,rnd,white]
    MIRP[rp0,min,rnd,grey]
    MDRP[rnd,white]
    MIRP[rp0,rnd,grey]
    MIRP[min,rnd,grey]
    IUP[x]
    PUSHB_1
     47
    PUSHW_1
     259
    PUSHB_1
     41
    PUSHW_1
     258
    PUSHB_3
     18
     12
     10
    PUSHW_1
     258
    PUSHB_7
     24
     184
     33
     96
     38
     188
     1
    SVTCA[y-axis]
    MDAP[rnd]
    MIRP[rnd,grey]
    MIRP[min,rnd,grey]
    MIRP[rp0,rnd,grey]
    MIRP[rp0,rnd,grey]
    MDRP[rp0,rnd,white]
    MDRP[rp0,rnd,grey]
    MIRP[min,rnd,grey]
    MIRP[rnd,grey]
    IUP[y]
    
    Topic: 

    fonts: ตัวอย่างการ hint ค.ควาย

    ค่า cvt ที่เกี่ยวข้องคือ
    cvt_dict = {
        ...
        "w_1001":           71,     #1001   =width of kho_kwai
        "w_690":            14,     #690    =
        "front_kai":        11,     #113    =front spacing of ko_kai
        "hstem":            8,      #184    =horizontal stem thick
        "headstem":         258,    #116    =head stem thick
        "headstem_plus_hole":   259, #268   =head stem thick + head hole width
        "vheight_shoot":    184,    #1147   =overshoot height
        "vstem_curve":      185,    #156    =curve range vertical stem
        ...
    }
    ภาพลายเส้น
    โครง ค.ควาย แนวแกน X
    เส้นทาง hint ในแนวแกน X เริ่มที่จุด 52
    srp0                52
    
    เคลื่อนไปที่จุดใกล้สุด 33 ด้วยระยะ front_kai
    mirp[rp0,min,rnd,grey] front_kai 33
    
    กำหนดความหนาของเส้นหน้า ที่จุด 8
    mirp[min,rnd,grey]  hstem 8
    
    เคลื่อนไปกำหนดความกว้างของตัวอักษรที่จุดขวาสุด 39 ด้วยความกว้าง w_1001
    mirp[rp0,rnd,grey]  w_1001  39
    
    กำหนดช่องไฟหลัง
    mdrp[rnd,white]     53
    
    กำหนดความหนาของเส้นหลัง ที่จุด 1
    mirp[min,rnd,grey]  hstem 1
    
    มาเริ่มกันใหม่ที่จุด 8
    srp0                8
    
    เคลื่อนลงข้างล่าง สู่จุด 11 (เพื่อจะนำไปสู่ฐานของเส้นหน้า) ให้การแรเงาเป็น white เพื่อแยกเส้นหน้ากับคอของหัวอักษร
    mdrp[rp0,rnd,white] 11
    
    เนื่องจากเส้นตรงนี้บางมาก จึงกำหนดจุดเพื่อคงระยะอย่างน้อย ที่จุด 27 กำหนดเป็น min
    mdrp[min,rnd,grey]  27
    
    จากจุด 11 เคลื่อนไปลงจุด 30
    mdrp[rp0,rnd,grey]  30
    
    กำหนดความหนาของฐาน ที่จุด 29
    mirp[min,rnd,grey]  hstem 29
    
    กลับไปที่จุด 11
    srp0                11
    
    เคลื่อนไปที่จุด 14 ด้วยการแรเงา white อีกครั้ง เพื่อแยกเส้นให้เด็ดขาด
    mdrp[rp0,rnd,white] 14
    
    คงความหนาของส่วนคอดไว้
    mdrp[min,rnd,grey]  25
    
    ย้อนกลับไปตั้งต้นที่จุด 33 อีกครั้งหนึ่ง เพื่อจะสร้างหัวอักษร โดยให้ระยะ จาก 14-49 เป็นตัวรองรับการทดจุด
    srp0                33
    
    เพื่อความแน่นอนแม่นยำ เราจะเคลื่อนจุดไปหาจุดขวาสุดของหัวอักษร โดยกำหนดค่าเป็น w_690
    (จำเป็นต้องให้แม่นยำ เนื่องจากช่องว่างตรงหัวอักษรมีจำกัดมาก) แล้วจึงกำหนดขนาดความหนาหัวอักษรและรูย้อนกลับอีกที
    mirp[rp0,rnd,grey]  w_690   20
    mirp[min,rnd,grey]  headstem 43
    mirp[rnd,grey]      headstem_plus_hole 49
    
    เกลี่ยจุดเป็นคำสั่งสุดท้าย
    IUP[x]
    
    แนวแกน Y
    เส้นทาง hint ในแนวแกน Y ทำงานในแกน Y
    SVTCA[y-axis]
    
    เริ่มที่จุด 0
    MDAP[rnd]           0
    
    บอกว่าจุด 29 อยู่ในระดับเดียวกัน
    ALIGNRP             29
    
    เคลื่อนไปกำหนดจุดสูงสุดด้วยค่า vheight_shoot
    mirp[rp0,rnd,grey]  vheight_shoot  36
    
    กำหนดความหนาตรงส่วนนี้ด้วยค่า vstem_curve
    mirp[min,rnd,grey]  vstem_curve  5
    
    จากจุด 36 เดิม เคลื่อนไปส่วนบนของหัวอักษรที่จุด 17 เนื่องจากไม่กังวลระยะมากนัก จึงใช้การเคลื่อนแบบไม่อ้างค่า cvt คือคำสั่ง MDRP
    (เหตุที่ใช้จุด 36 แทนที่จะเป็นจุด 1 หรือ จุด 0 ในการอ้างอิงสำหรับหัวอักษร ค.ควาย เนื่องจากความสวยงามในการวาดหัวอักษร จะขึ้นกับโค้งด้านบนมากว่าฐานด้านล่าง)
    mdrp[rp0,rnd,grey]  17
    
    กำหนดขนาดหัวโดยใช้เทคนิกเหมือนเดิม คือกำหนดความหนาด้านบน เคลื่อนไปกำหนดขนาดรวม และกำหนดความหนาด้านล่าง
    mirp[min,rnd,grey]  headstem  46
    mirp[rp0,rnd,grey]  headdia  23
    mirp[min,rnd,grey]  headstem  40
    
    ย้ายมาที่จุด 29 เพื่อมากำหนดความหนาตรงจุดนั้น
    srp0                29
    mdrp[min,rnd,grey]  11
    
    เกลี่ยจุดสำคัญ โดยให้ท้องตัวอักษรเป็นจุดเริ่มต้น คือจุด 5 และความหนาของฐานเป็นจุดสิ้นสุด คือจุด 11
    srp1                5
    srp2                11
    sloop               4
    ip                  33 8 2 39
    
    เกลี่ยจุดที่เหลือ
    IUP[y]
    
    เสร็จแล้ว
    แต่ถึงแม้จะป้องกันอย่างไร รูปร่างฟอนต์แบบตัว ค.ควาย จะยังมีปัญหาในตอนทดจุดอยู่ดี
    คือที่ขนาด 17 ppem หัวอักษรจะชิดติดกับแกนขวาของอักษร
    (สรุปเป็นปอยต์ตามความละเอียดของจอภาพคือ 72dpi=17pt, 85dpi=14.5pt, 96dpi=13pt)
    ดูจากเมนู View -> Grid Fit -> Show Grid Fit -> 17pt 72dpi ได้ภาพดังนี้
    17ppem gridfit แก้ด้วยการขยับจุดจำนวน 8 จุด ในแนวแกน X คือ
    จุด 2 ขยับไปทางขวา 8px คือ +8px ที่ 17ppem สูตรคือ
    +8px ค่าในตาราง delta hint คือ 15
    No.of step -8 -7 -6 -5 -4 -3 -2 -1 +1 +2 +3 +4 +5 +6 +7 +8
    Selector 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
    17ppem นำมาลบด้วย 9 (ลบด้วย 9 เสมอ) คือ 6
    ค่า 6 จะเป็น 4 บิตบน และค่า 15 คือ 4 บิตล่าง รวมกันคือ 6*16+15=143
    จะได้คู่ของการทำ delta hint คือ จุด 2 และค่าผลรวม 143 เขียนสรุปย่อดังนี้
    ที่ 17 ppem
    จุด 2 ขยับ +8 = 2 143
    จุด 39 ขยับ +8 = 39 143
    จุด 0 ขยับ +8 = 0 143
    จุด 1 ขยับ +8 = 1 143
    จุด 3 ขยับ +6 = 1 141
    จุด 38 ขยับ +6 = 38 141
    จุด 37 ขยับ +4 = 37 139
    จุด 4 ขยับ +4 = 4 139
    นับรวมได้ 8 คู่ จะได้โค๊ดจำลองดังนี้ กำหนดการทำงานในแกน X
    SVTCA[x-axis]
    
    ใส่ delta hint โดยใส่จำนวนคู่ไว้เป็นค่าแรก
    deltap1             8 2 143 39 143 0 143 1 143 3 141 38 141 37 139 4 139
    
    ภาพหลังการขยับแล้วจะป็นดังนี้
    17ppem after gridfit เสร็จแล้ว
    เมื่อนำมาถอดด้วย dfont.py จะได้โค๊ด instruction ดังนี้
    PUSHB_1
     49
    PUSHW_1
     259
    PUSHB_1
     43
    PUSHW_1
     258
    NPUSHB
     22
     20
     14
     33
     25
     14
     11
     29
     8
     30
     27
     11
     8
     1
     8
     53
     39
     71
     8
     8
     33
     11
     52
    SRP0
    MIRP[rp0,min,rnd,grey]
    MIRP[min,rnd,grey]
    MIRP[rp0,rnd,grey]
    MDRP[rnd,white]
    MIRP[min,rnd,grey]
    SRP0
    MDRP[rp0,rnd,white]
    MDRP[min,rnd,grey]
    MDRP[rp0,rnd,grey]
    MIRP[min,rnd,grey]
    SRP0
    MDRP[rp0,rnd,white]
    MDRP[min,rnd,grey]
    SRP0
    MIRP[rp0,rnd,grey]
    MIRP[min,rnd,grey]
    MIRP[rnd,grey]
    IUP[x]
    NPUSHB
     10
     39
     2
     8
     33
     4
     11
     5
     11
     29
     40
    PUSHW_1
     258
    PUSHB_1
     23
    PUSHW_1
     257
    PUSHB_1
     46
    PUSHW_1
     258
    PUSHB_7
     17
     5
     185
     36
     184
     29
     0
    SVTCA[y-axis]
    MDAP[rnd]
    ALIGNRP
    MIRP[rp0,rnd,grey]
    MIRP[min,rnd,grey]
    MDRP[rp0,rnd,grey]
    MIRP[min,rnd,grey]
    MIRP[rp0,rnd,grey]
    MIRP[min,rnd,grey]
    SRP0
    MDRP[min,rnd,grey]
    SRP1
    SRP2
    SLOOP
    IP
    IUP[y]
    NPUSHB
     17
     139
     4
     139
     37
     141
     38
     141
     3
     143
     1
     143
     0
     143
     39
     143
     2
     8
    SVTCA[x-axis]
    DELTAP1
    
    Topic: 

    fonts: บันทึกการทดลองทำ DejaVuSerifThai อีกรุ่น

    บันทึกการสร้างฟอนต์ DejaVu Serif Thai

    • ตัวนี้ทดลองทำ hinting เลียนแบบวิธีของ Tahoma คือ
      คงระยะคงที่ช่องไฟหน้าและหลังไว้ และให้ความกว้างของตัวอักษรเป็นตัวรองรับระยะการทดจุดที่เปลี่ยนไปมา ซึ่งบังคับว่าต้องทำ grid fitting (delta hint) เพื่อกำหนดความกว้างของฟอนต์ก่อน แล้วจึงเริ่มเดินจุด
      การทำ delta hint ก่อน จำเป็นมากสำหรับฟอนต์ขนาดเล็ก ซึ่งมีอัตราการเพี้ยนของความกว้างของอักษรมาก ไหน ๆ ก็ต้องทำ delta hint อยู่แล้ว เที่ยวนี้จึงพยายามลงลึกไปทำถึงขนาดหัวอักษรด้วย ผลที่ได้คืออ่านง่ายขึ้น โดยเฉพาะกับฟอนต์ขนาดเล็ก ๆ แต่ก็ต้องแลกมาด้วยการเสียเวลาและสายตาเป็นอย่างมาก
      (การทำงานต้องปรับขนาดจอภาพให้มีความละเอียด 72 dpi เพื่อให้รองรับขนาดฟอนต์ได้ทุก ๆ ppem บวกกับการเพ่งกำหนดจุดที่เปลี่ยน จึงเปลืองสายตามาก)
    • รูปแบบฟอนต์ของ DejaVu Serif ดูไม่เป็น serif จัดนัก จึงนำเอาฟอนต์ Angsima มาแปลงใส่เข้าไป
      เนื่องจากเห็นว่า ภาษาไทยทำ serif แท้ ๆ ไม่สวย จึงพยายามทำเท่าที่ทำได้ คือ hint เฉพาะพยัญชนะ ส่วนสระบนและล่างไม่ hint และไม่แปลงรูปแบบเลย

    ผลการทดลอง

    • เนื่องจากมีการทำ grid fitting (เกือบ)เต็มที่ จึงดูดีที่ฟอนต์ขนาดเล็ก ๆ ด้วย (หรือเปล่า?)
    • สระบนล่าง ทำ hint แล้วไม่สวย จึงตัดสินใจไม่ hint เพียงแค่กำหนดระยะในแกน Y เล็กน้อย ผลที่ได้คือแนวเส้นบรรทัดที่เป็นพญัญชนะจะดูคมชัดกว่าแนวสระบนล่าง - ไม่รู้ว่าดีหรือเปล่า รอดูผลการใช้งานต่อไป
    • แบบอักษรดูคล้าย sans ไปหน่อย โดยเฉพาะกับฟอนต์ขนาดเล็ก ยกเว้นสระบนล่างที่เป็นแบบของ Angima ชัดเจน ซึ่งอาจทำให้ดูไม่กลมกลืน

    ปรับปรุง
    (เนื่องจากเป็นการทำงานไปด้วย ศึกษาไปด้วย การทำ hinting จึงไม่เป็นอันหนึ่งอันเดียวกันในทุก ๆ ฟอนต์ที่ทำ ต้องขออภัยด้วย)

    • 51-05-06 ทำตัวหนาเพิ่มเสร็จแล้ว ตัวหนาที่ทำ ใช้เทคนิก hint เท่าที่จำเป็น จึงอาจดูไม่คมชัดนัก แต่ฟอนต์น่าจะอ่านง่ายขึ้นและผิดรูปน้อยลง
    • เขียนฟังก์ชั่นให้ทำ grid fitting ง่ายขึ้น งานของตัวหนาจึงเพิ่มเรื่อง grid fitting มากขึ้นมาก
    • 51-05-31 ปรับละเอียด เริ่มใช้ฟังก์ชั่น SHPIX ในการปรับหัวไม่ให้คมแข็ง พยายามปรับฟอนต์ให้ดูนิ่มนวลขึ้น ทำให้ดูดีขึ้นสำหรับการแสดงผลพื้นขาวตัวดำ แต่ยังมีปัญหากับพื้นหลังที่เข้มไม่มาก แต่ตัวหนังสือสีขาว (เช่นเว็บ pantip.com) ยังดูยากหน่อย พยายามปรับระดับความสูงวรรณยุกต์ระดับ 2 ไม่ให้เหลื่อม (ยังไม่ได้ทำตัวหนา เพราะยังไม่แน่ใจผล)

    ดาวน์โหลดไฟล์ฟอนต์ ttf

    ดาวน์โหลดซอร์ส

    • รุ่นแรกยังไม่มีตัวหนา DejaVuSerifThai-src-510502.tar.gz
    • รุ่นสองเพิ่มตัวหนาแล้ว DejaVuSerifThai-src-510506.tar.gz
    • ทำเลขไทยเสร็จแล้ว hint สระแล้ว DejaVuSerifThai-src-510508.tar.gz
    • รุ่นใหม่ ปรับให้ดูนุ่มนวล DejaVuSerifThai-src-510531.tar.gz
    • ทำตัวหนาเสร็จ แต่ตัวหนาไม่ได้ปรับหัว เพราะความจำเป็นน้อย สิ่งที่ปรับปรุงคือปรับการ hint โดยใช้แนวคิดว่าไม่จำเป็นต้องปัดเศษให้ลงจุดในทุกจุด ซึ่งจะทำให้ดูนุ่มนวลขึ้น เว้นเสียแต่ว่าระบบไม่มีการทำ Anti-Alias เช่นวินโดวส์รุ่นเก่า อาจทำให้ฟอนต์ดูเพี้ยน DejaVuSerifThai-src-510606.tar.gz
    • แก้บั๊กตาราง Lookups/GSUB ให้เรียบร้อย ทำตาราง OpenType เพื่อแก้ปัญหา "สระอำ" ใน OpenOffice : DejaVuSerifThai-src-510824.tar.gz

    ภาพตัวอย่าง (จากขนาดจอภาพ 72 dpi จึงกำหนดระยะเป็น ppem แทนเพื่อป้องกันสับสน)
    DejaVu Serif Thai

    Topic: 

    fonts: บันทึกฟอนต์อุโฆษ (Ukhosa)

    ต้องการทำฟอนต์ที่ใช้ในงานโฆษณา แต่ไม่แปลงรูปฟอนต์มากนัก ให้มีหัวบาง ๆ อยู่ทุกตัว ตั้งชื่อว่า อุโฆษ (Ukhosa) สร้างโดยเอาโครงฟอนต์โลมาบุตรมาแปลง

    ดาวน์โหลดไฟล์ฟอนต์ (20100707)

    ข้อความด้านล่างเป็นการแสดงผลจากหน้าเว็บด้วยฟอนต์นี้ (จากความสามารถของ css3)

    ดาวน์โหลดไฟล์ซอร์ส

    • รุ่นร่าง ยังไม่มีตัวหนา ยังไม่มี hint (รุ่นนี้อาจไม่ทำ hinting) Ukhosa-src-20100626.tar.gz
    • ยังไม่เสร็จ แต่จะเปลี่ยนลายเส้นภาษาอังกฤษเป็นแบบโลโกอูบุนตู - Ukhosa-src-20100629.tar.gz
    • อาจเว้นยาวหลายวัน โพสต์แปะไว้ก่อน - Ukhosa-src-20100630.tar.gz
    • ร่างตัวหนาเสร็จ - Ukhosa-src-20100707.tar.gz

    ภาพตัวอย่าง
    จาก Epiphany Browser
    ตัวธรรมดา

    ตัวหนา

    Topic: 

    python: เขียนโค๊ดคลี่แสต็กฟอนต์

    เหตุมาจากต้องการดูการ instruct ของฟอนต์ DejaVu Sans
    แต่โค๊ดอ่านยากเหลือเกิน เพราะเขียนข้อมูลเป็นสแต็กไว้ก่อน แล้วจึงเขียนโค๊ดตามหลัง ทำให้ดูยาก จึงเขียนสคริปต์แบบหยาบ ๆ มาคลี่ข้อมูลเรียงต่อท้ายคำสั่ง เพื่อให้ดูง่ายขึ้น ตั้งชื่อชั่วคราวว่า dfont.py
    $ touch dfont.py; chmod 755 dfont.py; vi dfont.py
    #!/usr/bin/env python
    
    import sys
    
    # ins_dict = { "COMMAND" : ("Description",pops,push), ... }
    # pops,push: 0=NOOP, 1=1BYTE, 2=2BYTE, ...
    #           -1=FIRST BYTE IS ONE BYTE COUNTER,
    #           -2=FIRST BYTE IS TWO BYTE COUNTER, 
    #           -3=CLEAR STACK
    #           -4=REQUIRE SOME PROCESSING
    
    ins_dict = {
        "AA" :       ("Adjust Angle", 1, 0),
        "ABS" :      ("ABSolute value", 1, 1),
        "ADD" :      ("ADD", 2, 1),
        "ALIGNPTS" : ("ALIGN Points", 2, 0),
        "ALIGNRP" :  ("ALIGN to Reference Point", 1, 0),
        "AND" :      ("logical AND", 2, 1),
        "CALL" :     ("CALL function", 1, 0),
        "CEILING" :  ("CEILING", 1, 1),
        "CINDEX" :   ("Copy the INDEXed element to the top of the stack", 1, -1),
        "CLEAR" :    ("CLEAR the stack", -3, 0),
        "DEBUG" :    ("DEBUG call", 1, 0),
        "DELTAC1" :  ("DELTA exception C1", -2, 0),
        "DELTAC2" :  ("DELTA exception C2", -2, 0),
        "DELTAC3" :  ("DELTA exception C3", -2, 0),
        "DELTAP1" :  ("DELTA exception P1", -2, 0),
        "DELTAP2" :  ("DELTA exception P2", -2, 0),
        "DELTAP3" :  ("DELTA exception P3", -2, 0),
        "DEPTH" :    ("DEPTH of the stack", 0, 1),
        "DIV" :      ("DIVide", 2, 1),
        "DUP" :      ("DUPlicate top stack element", 1, 1),
        "EIF" :      ("End IF", 0, 0),
        "ELSE" :     ("ELSE clause", 0, 0),
        "ENDF" :     ("END Function definition", 0, 0),
        "EQ" :       ("EQual", 2, 1),
        "EVEN" :     ("EVEN", 1, 1),
        "FDEF" :     ("Function DEFinition", 1, 0),
        "FLIPOFF" :  ("set the auto FLIP Boolean to OFF", 0, 0),
        "FLIPON" :   ("set the auto FLIP Boolean to ON", 0, 0),
        "FLIPPT" :   ("FLIP PoinT", 1, 0),
        "FLIPRGOFF" :    ("FLIP RanGe OFF", 2, 0),
        "FLIPRGON" : ("FLIP RanGe ON", 2, 0),
        "FLOOR" :    ("FLOOR", 2, 0),
        "GC" :       ("Get Coordinate projected onto the projection vector", 1, 1),
        "GETINFO" :  ("GET INFOrmation", 1, 1),
        "GFV" :      ("Get Freedom Vector", 0, 2),
        "GPV" :      ("Get Projection Vector", 0, 2),
        "GT" :       ("Greater Than", 2, 1),
        "GTEQ" :     ("Greater Than or EQual", 2, 1),
        "IDEF" :     ("Instruction DEFinition", 1, 0),
        "IF" :       ("IF test", 1, 0),
        "INSTCTRL" : ("INSTRuction execution ConTRoL", 2, 0),
        "IP" :       ("Interpolate Point", 1, 0),
        "ISECT" :    ("moves point p to the InterSECTion of two lines", 5, 0),
        "IUP" :      ("Interpolate Untouched Points through the outline", 0, 0),
        "JMPR" :     ("JuMP Relative", 1, 0),
        "JROF" :     ("Jump Relative On False", 1, 0),
        "JROT" :     ("Jump Relative On True", 3, 0),
        "LOOPCALL" : ("LOOP and CALL function", 2, 0),
        "LT" :       ("Less Than", 2, 1),
        "LTEQ" :     ("Less Than or Equal", 2, 1),
        "MAX" :      ("MAXimum of top two stack elements", 2, 1),
        "MD" :       ("Measure Distance", 2, 1),
        "MDAP" :     ("Move Direct Absolute Point", 1, 0),
        "MDRP" :     ("Move Direct Relative Point", 1, 0),
        "MIAP" :     ("Move Indirect Absolute Point", 2, 0),
        "MIN" :      ("MINimum of top two stack elements", 2, 1),
        "MINDEX" :   ("Move the INDEXed element to the top of the stack", 1, 3),
        "MIRP" :     ("Move Indirect Relative Point", 2, 0),
        "MPPEM" :    ("Measure Pixels Per EM", 0, 1),
        "MPS" :      ("Measure Point Size", 0, 1),
        "MSIRP" :    ("Move Stack Indirect Relative Point", 1, 0),
        "MUL" :      ("MULtiply", 2, 1),
        "NEG" :      ("NEGate", 1, 1),
        "NEQ" :      ("Not EQual", 2, 1),
        "NOT" :      ("logical NOT", 1, 1),
        "NPUSHB" :   ("PUSH N Bytes", -1, 0),
        "NPUSHW" :   ("PUSH N Words", -1, 0),
        "NROUND" :   ("No ROUNDing of value", 1, 1),
        "ODD" :      ("ODD", 1, 1),
        "OR" :       ("logical OR", 2, 1),
        "POP" :      ("POP top stack element", 1, 0),
        "PUSHB" :    ("PUSH Bytes", -4, 0),
        "PUSHW" :    ("PUSH Words", -4, 0),
        "RCVT" :     ("Read Control Value Table entry", 1, 1),
        "RDTG" :     ("Round Down To Grid", 0, 0),
        "ROFF" :     ("Round OFF", 0, 0),
        "ROLL" :     ("ROLL the top three stack elements", 3, 3),
        "ROUND" :    ("ROUND value", 1, 1),
        "RS" :       ("Read Store", 1, 1),
        "RTDG" :     ("Round To Double Grid", 0, 0),
        "RTG" :      ("Round To Grid", 0, 0),
        "RTHG" :     ("Round To Half Grid", 0, 0),
        "RUTG" :     ("Round Up To Grid", 0, 0),
        "S45ROUND" : ("Super ROUND 45 degrees", 1, 0),
        "SANGW" :    ("Set Angle Weight", 1, 0),
        "SCANCTRL" : ("SCAN conversion ConTRoL", 1, 0),
        "SCANTYPE" : ("SCANTYPE", 1, 0),
        "SCFS" : ("Sets Coordinate From the Stack using projection vector and freedom vector", 2, 0),
        "SCVTCI" :   ("Set Control Value Table Cut-In", 1, 0),
        "SDB" :      ("Set Delta Base in the graphics state", 1, 0),
        "SDPVTL" :   ("Set Dual Projection Vector To Line", 2, 0),
        "SDS" :      ("Set Delta Shift in the graphics state", 1, 0),
        "SFVFS" :    ("Set Freedom Vector From Stack", 2, 0),
        "SFVTCA" :   ("Set Freedom Vector To Coordinate Axis", 0, 0),
        "SFVTL" :    ("Set Freedom Vector To Line", 2, 0),
        "SFVTP" :    ("Set Freedom Vector To Projection Vector", 0, 0),
        "SHC" :      ("SHift Contour using reference point", 1, 0),
        "SHP" :      ("SHift Point using reference point", 1, 0),
        "SHPIX" :    ("SHift point by a PIXel amount", 2, 0),
        "SHZ" :      ("SHift Zone using reference point", 1, 0),
        "SLOOP" :    ("Set LOOP variable", 1, 0),
        "SMD" :      ("Set Minimum Distance", 1, 0),
        "SPVFS" :    ("Set Projection Vector From Stack", 2, 0),
        "SPVTCA" :   ("Set Projection Vector To Coordinate Axis", 0, 0),
        "SPVTL" :    ("Set Projection Vector To Line", 2, 0),
        "SROUND" :   ("Super ROUND", 1, 0),
        "SRP0" :     ("Set Reference Point 0", 1, 0),
        "SRP1" :     ("Set Reference Point 1", 1, 0),
        "SRP2" :     ("Set Reference Point 2", 1, 0),
        "SSW" :      ("Set Single Width", 1, 0),
        "SSWCI" :    ("Set Single Width Cut-In", 1, 0),
        "SUB" :      ("SUBtract", 2, 1),
        "SVTCA" :    ("Set freedom and projection Vectors To Coordinate Axis", 0, 0),
        "SWAP" :     ("SWAP the top two elements on the stack", 2, 2),
        "SZP0" :     ("Set Zone Pointer 0", 1, 0),
        "SZP1" :     ("Set Zone Pointer 1", 1, 0),
        "SZP2" :     ("Set Zone Pointer 2", 1, 0),
        "SZPS" :     ("Set Zone PointerS", 1, 0),
        "UTP" :      ("UnTouch Point", 1, 0),
        "WCVTF" :    ("Write Control Value Table in Funits", 1, 0),
        "WCVTP" :    ("Write Control Value Table in Pixel units", 2, 0),
        "WS" :       ("Write Store", 2, 0),
    }
    
    stack1_command = ("NPUSHB", "NPUSHW")
    stack2_command = ("PUSHB", "PUSHW")
    
    normal_command = []
    for i in range(6):
        normal_command.append( [ j for j in ins_dict.keys() if ins_dict[j][1]==i ] )
    
    #           -1=FIRST BYTE IS ONE BYTE COUNTER,
    #           -2=FIRST BYTE IS TWO BYTE COUNTER, 
    #           -3=CLEAR STACK
    #           -4=REQUIRE PROCESS
    pop_command = []
    pop_command.append([])
    for i in range(-1, -4, -1):
        pop_command.append( [ j for j in ins_dict.keys() if ins_dict[j][1]==i ] )
    
    
    def line_format(cmd,stack,desc):
        #STRING, LIST, STRING
        return "%-24s %-10s ;%10s" % (cmd, "  ".join(stack), desc,)
    
    def ins_decode(txt):
        txtlist = txt.split("\n")
        newlist = []
        commentlist = []
        stacklist = []
        sloop = 0
        i = 0
        while i < len(txtlist):
            if "[" in txtlist[i]:
                c1,c2 = txtlist[i].strip().split("[",1)
            else:
                c1,c2 = txtlist[i].strip(), ""
            #SKIP EMPTY LINE
            if c1 == "":
                i += 1
                continue
            #STACK: NPUSHB, NPUSHW
            if c1 in stack1_command:
                i += 1
                n = int(txtlist[i].strip())
                i += 1
                while n > 0:
                    stacklist.append(txtlist[i].strip())
                    i += 1
                    n -= 1
                continue
            #STACK2: PUSHB, PUSHW
            if c1[:5] in stack2_command:
    
                n = int(txtlist[i].strip()[6:])
                i += 1
                while n > 0:
                    stacklist.append(txtlist[i].strip())
                    i += 1
                    n -= 1
                continue
            temp_list = []
            #POP ONE
            if c1 in pop_command[1]:
                idx = stacklist.pop()
                temp_list = [idx]
                for j in range(int(idx)):
                    temp_list.append(stacklist.pop())
                newlist.append(line_format(txtlist[i],temp_list,ins_dict[c1][0]))
                i += 1
                continue
            #POP PAIR
            elif c1 in pop_command[2]:
                idx = stacklist.pop()
                temp_list = [idx]
                for j in range(int(idx)):
                    temp_list.append(stacklist.pop())
                    temp_list.append(stacklist.pop())
                newlist.append(line_format(txtlist[i],temp_list,ins_dict[c1][0]))
                i += 1
                continue
            #POP ALL (CLEAR STACK)
            elif c1 in pop_command[3]:
                stacklist = []
                newlist.append(line_format(txtlist[i],temp_list,ins_dict[c1][0]))
                i += 1
                continue
    
            #NORMAL COMMAND
            if c1 in normal_command[0]:
                count = 0
            elif c1 in normal_command[1]:
                count = 1
            elif c1 in normal_command[2]:
                count = 2
            elif c1 in normal_command[3]:
                count = 3
            elif c1 in normal_command[4]:
                count = 4
            elif c1 in normal_command[5]:
                count = 5
            else:
                count = -1
                print 'count-1:',c1
            if count > 0:
                if sloop > 0:
                    count += sloop-1
                    sloop = 0
            while count > 0:
                cnum = stacklist.pop()
                temp_list.append(cnum)
                if c1 == 'SLOOP':
                    sloop = int(cnum)
                count -= 1
            newlist.append(line_format(txtlist[i],temp_list,ins_dict[c1][0]))
            i += 1
        print '--------------------------------------'
        print '\n'.join(newlist)
    
    if __name__ == "__main__":
        txt = sys.argv[1]
        ins_decode(txt)
    
    เนื่องจากทำแบบหยาบ ๆ ใช้ลำบากหน่อย
    เรียกใช้ด้วยคำสั่ง
    $ ./dfont.py "
    แปะด้วยโค๊ดที่คัดลอกมาจากฟอนต์ อักขระ n
    "
    ได้ผลเป็น
    SRP0                     20         ;Set Reference Point 0
    MIRP[rp0,min,rnd,grey]   70  11     ;Move Indirect Relative Point
    MIRP[min,rnd,grey]       8  9       ;Move Indirect Relative Point
    SHP[rp2]                 13         ;SHift Point using reference point
    MIRP[rp0,rnd,grey]       78  0      ;Move Indirect Relative Point
    MIRP[min,rnd,grey]       8  2       ;Move Indirect Relative Point
    IUP[x]                              ;Interpolate Untouched Points through the outline
    SVTCA[y-axis]                       ;Set freedom and projection Vectors To Coordinate Axis
    MDAP[rnd]                1          ;Move Direct Absolute Point
    ALIGNRP                  10         ;ALIGN to Reference Point
    MIRP[rnd,grey]           188  12    ;Move Indirect Relative Point
    MIRP[rp0,rnd,grey]       184  17    ;Move Indirect Relative Point
    MDRP[rnd,grey]           14         ;Move Direct Relative Point
    MIRP[min,rnd,grey]       135  6     ;Move Indirect Relative Point
    SRP1                     1          ;Set Reference Point 1
    SRP2                     14         ;Set Reference Point 2
    SLOOP                    3          ;Set LOOP variable
    IP                       0  9  3    ;Interpolate Point
    IUP[y]                              ;Interpolate Untouched Points through the outline
    SVTCA[x-axis]                       ;Set freedom and projection Vectors To Coordinate Axis
    DELTAP1                  2  21  207  21  96 ;DELTA exception P1
    
    ทดสอบอีกอันนึง เป็นอักขระ a
    SRP0                     38         ;Set Reference Point 0
    MIRP[rp0,min,rnd,grey]   69  20     ;Move Indirect Relative Point
    MIRP[min,rnd,grey]       8  3       ;Move Indirect Relative Point
    MDRP[min,rnd,grey]       31         ;Move Direct Relative Point
    MDRP[rp0,rnd,grey]       11         ;Move Direct Relative Point
    MIRP[min,rnd,grey]       8  9       ;Move Indirect Relative Point
    SHP[rp2]                 13         ;SHift Point using reference point
    SHP[rp2]                 24         ;SHift Point using reference point
    SRP1                     3          ;Set Reference Point 1
    IP                       23         ;Interpolate Point
    IP                       0          ;Interpolate Point
    IUP[x]                              ;Interpolate Untouched Points through the outline
    SVTCA[y-axis]                       ;Set freedom and projection Vectors To Coordinate Axis
    MDAP[rnd]                12         ;Move Direct Absolute Point
    MDRP[rnd,grey]           23         ;Move Direct Relative Point
    MIRP[rnd,grey]           140  17    ;Move Indirect Relative Point
    MIRP[rp0,rnd,grey]       184  35    ;Move Indirect Relative Point
    MIRP[rp0,min,rnd,grey]   185  28    ;Move Indirect Relative Point
    MIRP[rp0,rnd,grey]       186  31    ;Move Indirect Relative Point
    MIRP[min,rnd,grey]       134  32    ;Move Indirect Relative Point
    SRP0                     17         ;Set Reference Point 0
    MDRP[rnd,white]          14         ;Move Direct Relative Point
    MIRP[min,rnd,white]      185  6     ;Move Indirect Relative Point
    SRP0                     23         ;Set Reference Point 0
    MIRP[min,rnd,white]      169  0     ;Move Indirect Relative Point
    SRP1                     14         ;Set Reference Point 1
    IP                       9          ;Interpolate Point
    SRP1                     23         ;Set Reference Point 1
    IP                       11         ;Interpolate Point
    SRP2                     31         ;Set Reference Point 2
    IP                       25         ;Interpolate Point
    IUP[y]                              ;Interpolate Untouched Points through the outline
    DELTAP1                  24  33  128  32  128  31  128  30  128  33  112  32  112  31  112  30  112  33  96  32  96  31  96  30  96  33  80  32  80  31  80  30  80  33  64  32  64  31  64  30  64  33  48  32  48  31  48  30  48 ;DELTA exception P1
    SVTCA[x-axis]                       ;Set freedom and projection Vectors To Coordinate Axis
    DELTAP1                  30  39  240  39  160  39  144  34  133  33  135  32  135  31  135  30  135  29  133  39  112  39  80  34  80  33  80  32  80  31  80  30  80  29  80  34  64  33  64  32  64  31  64  30  64  29  64  39  63  34  48  33  48  32  48  31  48  30  48  29  48 ;DELTA exception P1
    
    Topic: 

    python: โค๊ดฟอนต์ย้อนกลับ

    เพื่อให้ครบกระบวน ต่อด้วยโค๊ดย้อนกลับอีกที
    ต่อจาก python: เขียนโค๊ดคลี่แสต็กฟอนต์ ที่ผ่านมานะครับ $ vi dfont.py
    ...
    def stack_format(stacklist):
    
        def flush(newlist, templist, ind, cur_ind):
            if ind == 0:
                newlist.append('NPUSHB')
            else:
                newlist.append('NPUSHW')
            newlist.append(' %s' % len(templist))
            newlist.extend(templist)
            return newlist, [], cur_ind
    
        if len(stacklist)==0: return []
        n = 0
        newlist = []
        templist = []
        if int(stacklist[0]) < 256:
            ind = 0
        else:
            ind = 1
        while n < len(stacklist):
            if int(stacklist[n]) < 256:
                cur_ind = 0
            else:
                cur_ind = 1
            if ind == cur_ind:
                templist.insert(0,' '+stacklist[n])
            else:
                newlist, templist, ind = flush(newlist, templist, ind, cur_ind) #FLUSH
            n += 1
        newlist, templist, ind = flush(newlist, templist, ind, cur_ind) #FLUSH
        return newlist
    
    def ins_encode(txt):
        """Encode simple code into TrueType Instruction code"""
        comment_list = ['#',';']    #COMMENT CHARACTER
        flush_list = ['SVTCA']      #FLUSH STACK
        stacklist = []
        newlist = []
        templist = []
        txtlist = txt.split('\n')
        for line in txtlist:
            line = line.replace("\t"," ")
            if " " in line:
                l = line.split(" ")
            else:
                l = [line]
            l = [ j for j in l if j != "" ]
            if l == []:
                continue
            if l[0][0] in comment_list: #BYPASS COMMENT
                continue
            for f in flush_list:    #?FLUSH STACK
                if f == l[0][:len(f)]:
                    newlist.extend(stack_format(stacklist))
                    stacklist = []
                    newlist.extend(templist)
                    templist = []
                    break
            if '[' in l[0]:         #TO UPPERCASE
                ltemp = l[0].split('[',1)
                temp = ltemp[0].upper()+'['+ltemp[1]
            else:
                temp = l[0].upper()
            templist.append(temp)   #KEEP COMMAND
            l.pop(0)                #PROCESS REST STACK NUM
            for element in l:
                if element[0] in comment_list:
                    break
                else:
                    stacklist.append(element)
        if stacklist != []:
            newlist.extend(stack_format(stacklist))
        newlist.extend(templist)
        print '===Encode==============================='
        print '\n'.join(newlist)
    
    
    if __name__ == "__main__":
        txt = sys.argv[1]
        #ins_decode(txt)
        ins_encode(txt)
    
    
    ทดสอบด้วยตัวอย่าง n เมื่อกี้นี้
    $ dfont.py "
    แปะด้วยโค๊ดเทียมที่ถูกคลี่แล้ว จากตัวอย่าง n
    "
    ===Encode===============================
    NPUSHB
     10
     2
     8
     0
     78
     13
     9
     8
     11
     70
     20
    SRP0
    MIRP[rp0,min,rnd,grey]
    MIRP[min,rnd,grey]
    SHP[rp2]
    MIRP[rp0,rnd,grey]
    MIRP[min,rnd,grey]
    IUP[x]
    NPUSHB
     15
     3
     9
     0
     3
     14
     1
     6
     135
     14
     17
     184
     12
     188
     10
     1
    SVTCA[y-axis]
    MDAP[rnd]
    ALIGNRP
    MIRP[rnd,grey]
    MIRP[rp0,rnd,grey]
    MDRP[rnd,grey]
    MIRP[min,rnd,grey]
    SRP1
    SRP2
    SLOOP
    IP
    IUP[y]
    NPUSHB
     5
     96
     21
     207
     21
     2
    SVTCA[x-axis]
    DELTAP1
    
    ไม่เหมือนเป๊ะ แต่น่าจะทำงานได้เหมือนกัน
    Topic: 

    เกร็ด office บนลินุกส์ (OpenOffice.org)

    ได้ลองทดสอบโปรแกรมสำนักงาน เฉพาะเรื่องกระดานคำนวณ

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

    ทดสอบกับอูบุนตู dapper และเดเบียน etch
    ได้เรื่องว่า

    openoffice2
    ทำงานช้ามาก และมีปัญหาเข้ากับ Excel ไม่ได้
    kspread
    เปิดไฟล์ไม่ได้ crash เสียก่อน
    gnumeric
    เร็วที่สุด การทำงานใกล้เคียง Excel ที่สุด เมื่อบันทึกไฟล์กลับ สามารถเปิดด้วย Excel ได้

    อย่างไรก็ตาม ก็ยังมีปัญหาบางประการ เช่น

    • ฟอนต์ Norasi มีปัญหาตอนพิมพ์ ตัวหนังสือเพี้ยนแหลก แก้ด้วยใช้ฟอนต์ FreeSerif แทน
    • หน้าชีตเละ แก้ด้วยการแทรกหน้าสำหรับการพิมพ์จากลินุกส์โดยเฉพาะ โดยมีเนื้อหาสูตรเหมือนกับหน้าเก่า แต่มาจัดรูปแบบใหม่

    งานนี้จึงเลือกใช้ gnumeric ในการทำงานร่วมกับวินโดวส์ครับ

    openoffice: ค้นหาและแทนที่

    ต้องการค้นหาและแทนที่ เอกสาร OpenOffice-Writer ที่บันทึกไว้เป็นนามสกุล .doc
    (เพื่อให้ใช้งานข้ามระบบได้ เลยบันทึกเป็น .doc ตระกูล Microsoft-Word)
    โดยต้องการทำงานเป็นแบตช์ คือค้นหาและแทนที่คำว่า OLD ด้วยคำว่า NEW กับทุก ๆ ไฟล์

    แก้ปัญหาโดยต้องเข้าไปสร้างคำสั่งมาโครภาษาเบสิกไว้ก่อน แล้วจึงสั่งงานด้วยบรรทัดคำสั่งในเชลล์อีกทีนึง

    เริ่มด้วย ไปที่เมนู
    Tools -> Macros -> Organize Macros -> OpenOffice.org Basic
    -> My Macros -> Standard

    เลือก New

    ตั้งชื่อรูทีนว่า BatchFixMissSpell

    ...
    Sub BatchFixMissSpell(cFile)
      rem Load file
      cURL = ConvertToURL( cFile ) 
      rem Set document
      oDoc = StarDesktop.loadComponentFromURL( cURL, "_blank", SELF, Array() )
      rem Create Search Descriptor
      FandR = oDoc.createSearchDescriptor()
      rem Set search/replace string
      FandR.setSearchString("OLD") : FandR.setReplaceString("NEW")
      rem Do replace
      oDoc.ReplaceAll(FandR) 
      rem Save file
      oDoc.storeToURL( cURL, Array() )
      rem Write & exit
      oDoc.dispose()
    End Sub
    ...

    บันทึกแล้วออกจากทั้งโปรแกรมมาโคร และ OpenOffice-Writer เลย

    สมมุติถ้าจะค้นและแทนที่กับทุกไฟล์ที่เป็นนามสกุล .doc ใช้คำสั่งในเชลล์ว่า
    $ for i in *.doc; do soffice -invisible "macro:///Standard.Module1.BatchFixMissSpell(`pwd`/$i)"; done

    เวลาใช้งานจริงอาจเก็บเป็นคำศัพท์ที่ผิดบ่อยแล้วเขียนลงไว้หลาย ๆ ศัพท์ น่าจะดี

    อ้างอิง

    เพิ่มเติม

    • เวลาใช้งานกับชื่อไฟล์ภาษาไทย อาจมีปัญหาตอนส่งผ่านอาร์กิวเมนต์ อาจแปลงชื่อไฟล์เป็นอังกฤษก่อน แล้วจึงแปลงกลับ บรรทัดคำสั่งเป็นดังนี้
      $ for i in *.doc; do \
          echo $i;\
          cp "$i" xxx.doc;\
          soffice "macro:///Standard.Module1.BatchFixMissSpell(`pwd`/xxx.doc)";\
          mv xxx.doc "$i";\
      done
    • ถ้าไฟล์ใหญ่ ดูเหมือนหลังจากทำไปสัก 10 ไฟล์ จะช้าลงจนค้างไปเลย ยังไม่ทราบวิธีแก้ (การยกเลิกตัวเลือก -invisible ช่วยได้เล็กน้อย)

    openoffice: สร้าง pdf

    มีโจทย์คือ จะสร้างเอกสาร pdf จากหนังสือภาพสี
    ขั้นตอนคือ

    • สแกนหน้าหนังสือทั้งหมดไว้ (หน้าสีเก็บเป็น jpg หน้าขาวดำเก็บเป็น tif)
    • ตบแต่งภาพที่สแกนไว้ด้วย gimp
    • นำภาพที่ตบแต่งเสร็จแล้ว insert เข้าใน openoffice
    • ใช้ openoffice ส่งงานออกเป็น pdf

    จบ