ห้องนี้เป็นห้องทดลอง เมื่อทดลองเสร็จแล้ว ถึงจะนำเสนออีกทีนึงครับ
ต้องการเชื่อม Thin Client เข้าหา Thick Server ลินุกส์
โดยจะใช้งานในระบบดอส
งานที่ทำคือ
โฮมเพจ: 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 เป็นตัวบูต
จะให้จ่ายไอพีตั้งแต่ 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
เอาจาก 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
ดู 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 ของเครื่องเซิร์ฟเวอร์
เอาบราวเซอร์ไปที่ 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
จะเอาไปทำแผ่นบูต
ทำที่เครื่องเซิร์ฟเวอร์
ถ้าในระบบเรามีการ์ดแลนเพียงชนิดเดียว เราก็แค่สร้างไฟล์ 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
ได้เปลื้องหนี้คุณสมเจตน์แล้ว ค่อยสบายหน่อย
เดี๋ยวถ้ามีเวลา จะกลับมาศึกษาต่ออีกที
update 50-07-07
# aptitude install vim-full less screen
# usermod -s /bin/bash user1
# vi /etc/profile
... EDITOR="/usr/bin/vi" export EDITOR ...
ปรับปรุง: จาก debianclub.com: system-wide default editor
# update-alternatives --config editor
<<<--- เลือกหมายเลขหน้า vim
# aptitude install vim-full
# vi /etc/vim/vimrc.local
... syntax on
%sudo ALL=NOPASSWD: ALL
ถ้าเราชื่อ user1 ใช้คำสั่ง usermod แก้ให้เราอยู่ในกลุ่ม sudo ด้วย
# usermod -g sudo user1
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
ต้องการปรับหน้าตาเดเบียนให้ดูทันสมัยทัดเทียมเพื่อนพ้องบ้าง
ก่อนอื่นต้องตรวจดูว่าการ์ดแสดงผลเราสามารถใช้งาน 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/
เสร็จแล้วครับ
Customize Sid Desktop
สมมุติว่าติดตั้งเดเบียนจาก debootstrap พร้อมทั้งทำ apt-proxy ไว้เรียบร้อยแล้ว
# 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
# 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
# useradd -m -g mygroup -G lp,dialout,cdrom,floppy,audio,video,plugdev,lpadmin -s /bin/bash user1
# passwd user1
ของ samba คือ
# smbpasswd -a -s user1
# 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
# vi /etc/wgetrc
... http_proxy = http://server1.example.com:8080/ ftp_proxy = http://server1.example.com:8080/ ...
# aptitude install gdebi
# 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
# aptitude install pidgin
เมื่อเรียบร้อยแล้วก็ให้เริ่ม gdm ใหม่ได้เลย
# /etc/init.d/gdm restart
ผู้ใช้ที่ได้ล๊อกอินเข้าไป คงเหลือเพียงเพิ่มสถานะของภาษาลงบนพาเนล
คลิกขวาบนพาเนล -> Add to Panel -> Keyboard Indicator -> Add
แล้วก็จะสามารถทำงานได้ทันทีครับ
ฝากเสริมด้านเดสก์ทอปให้ด้วยนะครับ
หลังจากผ่านการติดตั้งแบบ Net Install หรือแบบ debootstrap มาแล้ว
ต้องติดตั้ง Xwindows ก่อน
# aptitude install x-window-system-core
ตามด้วย gnome แบบต่าง ๆ ดังนี้
# aptitude install gnome
# aptitude install gnome-core gdm
# aptitude install gnome-fifth-toe
# aptitude install gnome-desktop-environment
เลือกใช้แบบน้อยที่สุด และส่วนเสริมที่ใช้
# 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
สมมุติว่าถูกติดตั้งจาก debootstrap หรือแผ่น Net Install
หลังจากทำ Customize บน Terminal เรียบร้อยแล้ว ทำต่อส่วนของ Desktop ด้วย
$ sudo aptitude install xorg gnome
$ sudo aptitude install less gdebi
$ 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
Desktop -> Preference -> Screensaver -> ปิด Lock screen when screensaver is active
$ sudo aptitude install iceweasel
$ 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
$ sudo vi /etc/dosemu/dosemu.conf
... $_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 ปรับเปลี่ยนตามต้องการ
$ sudo aptitude install qemu uml-utilities module-assistant
$ 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
$ 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
$ sudo aptitude install pidgin
$ sudo aptitude install xchm
ช่วงนี้ 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 ทีหลังก็ได้ครับ
ขอเขียนลงห้องทดลองด้วยคนนะครับ :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 ไม่ได้
บันทึกการติดตั้งแพกเกจต่าง ๆ
มีงานที่ต้องแปลงไฟล์ภาพ 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
$ gs -dNOPAUSE -sDEVICE=pdfwrite -sOutputFile=filename.pdf -- filename.prn
เพิ่มเติม -sDEVICE
png = png16m
แต่คุณภาพไม่ค่อยดี สู้เป็น pdfwrite ไม่ได้ (เอามาแก้ใน gimp ได้)
ดูรายละเอียดของ DEVICE ได้จากคำสั่ง gs --help
เพิ่มเติมสำหรับวินโดวส์
หากต้องการทำต้นฉบับหนังสือเพื่อส่งโรงพิมพ์ ให้เลือกเครื่องพิมพ์เป็นพวก Image Setter เช่น Scitex เป็นต้น
จะไม่มีปัญหาเรื่องพิมพ์ตกขอบทำให้ขอบขาด
$ 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
จะใช้งานง่ายกว่า
$ gs -dBATCH -dNOPAUSE -q -sDEVICE=pdfwrite -sOutputFile=combined.pdf file1.pdf
file2.pdf
เอามาจาก Blog of Amorn Jiraseree-amornkun : วิธีรวมไฟล์ pdf หลายไฟล์เข้าด้วยกันในลินุกซ์
บันทึกการทำงานกับเทอร์มินัล
เทอร์มินัลของ 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
เอามาจาก 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 ดี ๆ แบ่งบ้างนะครับ
screen tip: screenrc และการสั่งงานด้วยบรรทัดคำสั่ง
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_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
จบแล้วครับ
เอามาจาก 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
สร้างไฟล์ที่จะดักการทำงานตอนรับแฟกซ์ ชื่อ /etc/hylafax/FaxDispatch
แล้วเริ่มการทำงานใหม่ เพื่อให้ไฟล์ใหม่นี้ถูกคัดลอกไปไว้ในไดเรคทอรี่ของ hylafax
# vi /etc/hylafax/FaxDispatch
/usr/bin/tiff2ps -a $FILE | lpr -P $PRINTER_NAME
# /etc/init.d/hylafax restart
ใช้ความสามารถของ gs
--รอทดลอง--
เอามาจาก 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
# vi /etc/hylafax/config.ttyS0
... RecvFileMode: 0666 ...
ปรับปรุง 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 ให้ส่งแฟกซ์เอง
เพิ่มเติม
/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): ...
debootstrap เป็นแพกเกจที่ใช้ในการติดตั้งเดเบียนผ่านเครือข่าย
สามารถนำมาใช้ประโยชน์ได้เช่น การติดตั้งลินุกซ์ซ้อนเข้าในระบบเพื่อการทดลอง หรือการติดตั้งระบบเพิ่มเติมสำหรับงาน Virtualization
การใช้งาน ควรมี mirror ซึ่งอาจเป็น apt-proxy apt-cacher หรือ mirror แท้ ๆ อยู่ในเครือข่ายเราก่อน
รูปแบบใช้งานคือ
debootstrap [OPTION...] SUITE TARGET [MIRROR [SCRIPT]]
SUITE คือรุ่นของลินุกซ์ที่เราจะติดตั้ง สำหรับเดเบียนได้แก่ sarge หรือ etch เป็นต้น
TARGET คือพาร์ติชั่นที่เราเมานต์ไว้แล้วสำหรับการติดตั้ง
MIRROR คือคลังแพกเกจ เช่น apt-proxy หรือ mirror อื่น
รายละเอียดสามารถศึกษาได้เพิ่มเติมจาก man 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 ใหม่แบบสะอาด ๆ
เมื่อจะทำการทดลองระบบใหม่ ถ้าจำเป็นต้องลงใหม่ก็ทำเหมือนเดิมครับ
สมมุติว่า 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
บนเดเบียน สามารถเปิดใช้งานมอดูลด้วยคำสั่ง a2enmod MODULENAME
และปิดการใช้งานด้วยคำสั่ง a2dismod MODULENAME
# 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
... LoadModule wsgi_module modules/mod_wsgi.so
ที่มา: http://serverfault.com/questions/435926/cant-locate-api-module-structure-mod-wsgi
# 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
!!!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: 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
เอามาจาก ThaiLinuxCafe: ทำ mirror ด้วย rsync อย่างง่าย
ต้องการ syncronize ไดเรคทอรี่ /var/www ของเครื่อง server1.example.com
มายังไดเรคทอรี่ /var/www ของเครื่อง mirror.example.com
ซึ่งมีข้อดีกว่าการคัดลอกธรรมดาด้วย cp หรือ scp ตรงที่
ถ้าเราลบไฟล์ใน server1 ไฟล์ใน mirror จะถูกลบตามไปด้วย
ทำให้ทั้งสองเครื่องมีความสดใหม่เหมือนกัน
# aptitude install rsync
# vi /etc/default/rsync
RSYNC_ENABLE=true
# dpkg-reconfigure rsync
server1:# useradd -m -s /bin/bash someuser
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 ไม่ต้องถามรหัสผ่าน โดยการใช้ 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/
จบแล้วครับ
ต่อไปเป็นการให้ทำงานอัตโนมัติผ่าน cron
# 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 ตอนเที่ยงคืน ทุกวัน )
จบแล้วครับ
# mkdir temp
# for i in *jpg; do convert $i -interlace line temp/$i ; done
# convert -adjoin `ls *.tif` newfile.pdf
$ convert x1.tif x2.tif x3.tif -adjoin newfile.tif
$ convert x.tif x%d.tif
$ convert x.tif /tmp/x.gif
$ convert /tmp/x.gif -depth 4 x.tif
$ convert x.tif /tmp/x.jpg
$ convert /tmp/x.jpg x.tif
$ convert x.tif -quality 100 /tmp/x%d.jpg
$ convert /tmp/*jpg -geometry 1654 -density 200 -compress lzw -depth 4 -adjoin x.tif
$ convert infile.jpg -level 40%,85%,0.5 -type bilevel -monochrome -compress group4 outfile.tif
$ convert infile.jpg -compress lzw -colors 8 outfile.tif
tiff2pdf
และรวมด้วย pdfjam
หมายเหตุ
ต้องลงแพกเกจ imagemagick ก่อน
# aptitude install imagemagick
ยังหาโปรแกรมที่ใช้แก้ไขภาพ 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 ครับ
ความจำเป็นบีบบังคับให้ต้องรีบหาโปรแกรมที่สามารถทำ 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 จริง ๆ แฮะ
ลูกสาวให้ทำ msn ในลินุกซ์ให้ใช้ เคยอ่านผ่านตาที่ ubuntuclub.com
เลยทดลองดาวน์โหลด pidgin มาคอมไพล์เอง
คอมไพล์ครั้งแรกไม่ผ่าน ค้นเจอที่ ubuntuforum รวบรวมแพกเกจที่จำเป็นดังนี้
$ sudo aptitude install libgtk2.0-dev libxml-perl libssl-dev libnspr4-dev libnss3-dev
หลังจากนั้นก็คอมไพล์ตามปกติ ก็สามารถเล่นได้แล้ว
เอามาจาก
ThaiLinuxCafe - debian : ติดตั้ง dns อย่างง่าย
เรื่องของ DNS (Domain Name Server) เป็นเรื่องหลักของการใช้งานอินเตอร์เน็ต เนื้อหาซับซ้อนและทำความเข้าใจยาก
สำหรับในที่นี้ เราเอาแค่ติดตั้งพอใช้งานได้
โดยเราจะติดตั้งเครื่องเซิร์ฟเวอร์เพื่อทำหน้าที่เป็น DNS สำหรับใช้งานเครือข่ายภายใน
โครงร่างคือ
แพกเกจที่ทำหน้าที่ DNS ในเดเบียนชื่อ bind9 และแพกเกจที่เป็นโปรแกรมช่วยชื่อ dnsutils
งานปรับตั้งคือการสร้างไฟล์สำหรับให้ bind9 เรียกใช้ ดังนี้
เริ่มด้วย
ติดตั้ง 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.
หมายเหตุ
เนื้อหาไม่ค่อยถูกตามหลักการนะครับ เพราะมันยาก ถ้าจะให้รู้เรื่องจริง ๆ ต้องเข้าใจระบบอินเตอร์เน็ตพอสมควร
ในที่นี้เอาแค่พอใช้งานได้ครับ
อ้างอิงเพิ่มเติม
*** ยังเขียนไม่เสร็จ + ยังไม่ได้ตรวจทานซ้ำ ***
เป้า
สมมุติว่า
ติดตั้งแพกเกจ
# 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
# 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
การตั้งให้ name server ให้บริการเครือข่ายภายในและภายนอก โดยใช้โดเมนเดียวกัน มีข้อดีตรงที่เราไม่จำเป็นต้องแยกโดเมนให้จำยาก และเมื่อเวลาลูกข่ายภายในสั่งค้น การค้นก็ไม่ต้องเปลืองแพ็กเก็ตออกสู่ภายนอก เพียงแต่การปรับตั้งยุ่งนิดนึง
# aptitude install bind9
สมมุติว่า
จะต้องมีไฟล์คอนฟิกคือ 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
จะทดลองทำ 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
เรียบร้อยแล้วครับ
หมายเหตุ
หมายเหตุ 2
ข้อเขียนนี้เป็นเพียงการทดลองเท่านั้น แต่ใจสู้เขียน ด้วยค้นหาวิธีการแบบง่าย ๆ ไม่พบ (step by step) เลยต้องทดลองเอง และต้องการบันทึกผลการทดลองที่สามารถใช้งานได้แล้ว ดังนั้นจึงยังไม่ทราบผลข้างเคียงเรื่องความปลอดภัย และความถูกต้องตามระเบียบแบบแผนที่ควรเป็น
จึงต้องการขอคอมเมนต์และคำแนะนำเพื่อให้ได้รู้เรื่องกันในวงกว้างครับ
(ก่อนหน้านี้ เวลาต้องการอัปเดตเลขไอพี ผมใช้สคริปต์ในการสร้างโซนไฟล์ และรีเวิร์สไฟล์ขึ้นมาใหม่ แลัวจึงสั่งเริ่ม bind9 ใหม่ จึงไม่มีปัญหาเรื่องความปลอดภัยมาเกี่ยว)
อ้างอิง
Postfix เป็นแพกเกจ Mail Server ที่ได้รับความนิยมมากกว่าแพกเกจของเดเบียนเอง (Exim)
ดูจาก debian-administration: Mail Server Poll
ผมแทบไม่รู้อะไรเกี่ยวกับ Postfix SASL และ TLS เลย
แต่ขออนุญาตบันทึกไว้รอศึกษาครับ
เอามาจาก tribulaciones: Postfix/SASL/TLS HowTo for Debian Sid and Sarge
# 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
# 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
## 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
ต้องไม่มีรายงานข้อผิดพลาด
# 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 ฉบับที่ให้ผลดีกว่า คือ
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
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 ได้เลย
หมายเหตุ
# vi /etc/drupal/5/apache.conf
Alias /content /usr/share/drupal5
# 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
# /etc/init.d/apache2 restart
(แต่หากต้องการใช้ใน root คือ http://www.example.com เฉย ๆ ควรดาวน์โหลดและติดตั้ง drupal เองที่ /var/www
หรือแก้ไข /etc/apache2/site-enabled/default ให้ชี้มาที่ /usr/share/drupal5 อย่างใดอย่างหนึ่ง)
# a2enmod rewrite
# vi /etc/drupal/5/htaccess
... RewriteBase /content ...
# /etc/init.d/apache2 restart
# 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 สะดวกกว่า
เที่ยวนี้บันทึกเอาไว้ดู ว่าติดตั้งจากเดเบียนทำยังไง
ทดลองเกี่ยวกับ Virtualization
เดี๋ยวนี้ 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-img create -f qcow winxp.qcow 5G
$ qemu-img create -b winxp.qcow -f qcow winxp.ovl
$qemu-img commit winxp.ovl
สรุปว่าสำหรับ qemu ตั้งแต่รุ่น 0.8.2 เป็นต้นไป ต้องทดลองดูหลาย ๆ แบบ ดูว่าแบบไหนดีที่สุด ไม่จำเป็นต้องเป็นตามคู่มือเสมอไป
หมายเหตุ
อ้างอิง
ทดลองใช้ 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
รุ่นนี้เป็นรุ่นปรับแก้บั๊กเรื่อง 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
หากคร้านที่จะคอมไพล์เอง ดาวน์โหลดได้ที่นี่ครับ ฝากทดสอบบั๊กให้ด้วย
เมื่อได้ลองใช้งาน 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 ทั้งคู่
จะเอามาลองทดสอบ 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
... ติดตั้งตามปกติ ...
หมายเหตุ
$ xtightvncviewer localhost
ใหม่boot = 'c'
ด้วยผลการทดลอง
Control Panel -> Mouse -> TAB-Pointer options -> ปิด Enhance pointer position
สรุป
ถึงแม้จะยังไม่สมบูรณ์แบบ แต่ก็น่าใช้มาก โดยเฉพาะถ้าต้องการความเร็วในการใช้งาน การติดตั้งก็ไม่ยากเหมือนแต่ก่อน
เสียดายที่ต้องใช้กับซีพียูที่มีโหมด Vitualize เท่านั้น (Intel-VT, AMD-Pacifica) และโอเอสก็ต้องเป็น 32bit ขึ้นไปแบบ Windows XP
ถ้าใช้กับ Win98 ได้นี่จะประหยัดไลเซ่นซ์วินโดวส์เก่า ๆ ได้เยอะเลย
อ้างอิง
หลังจากทดลอง Xen แล้ว พบว่ายังมีปัญหาเกี่ยวกับคีย์บอร์ดที่ค่อนข้างเอาแน่เอานอนไม่ได้ และปัญหาความไม่คุ้นเคยในการใช้เมาส์ผ่าน VNC
จึงได้มาทดลองใช้ VirtualBox
ซีพียูอินเทล E6300 หน่วยความจำ 1G
พบว่า
จึงบันทึกการปรับตั้งค่าไว้ดังนี้
มีปัญหาว่าบางครั้ง แม้จะแก้ไขให้เราอยู่ในกลุ่ม 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 -> Host Interface
Interface name : tap0
Setup application : ~/ifup-tap0
สำหรับ Windows XP เขาให้หน่วยความจำปริยายมาเป็น 128M แต่เรากำหนดเป็น 384M
ใน Windows XP ตั้งค่าให้ไอพีเป็น 192.168.1.4 และเกตเวย์เป็น 192.168.4.254
$ 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
$ sudo mount -o loop,offset=0x$(hd -h 100000 IMAGE.vdi | grep "eb 3C 90" | cut -c 1-8) IMAGE.vdi /MOUNT/POINT
มีงานที่ต้องการทดสอบการแสดงผล ของ 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
iBoot-Ivy-Bridge.iso
ไว้ (ถ้าบูตไม่ผ่าน ลองดาวน์โหลด iBoot ตัวอื่นดู)เอา 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" นี้ไว้ได้ แต่หากไม่ต้องการใช้แล้วก็ลบทิ้งได้เลย
เอา 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)
จบแล้ว
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
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
เสร็จแล้วครับ
ที่มา
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
เสร็จแล้ว
ที่มา
บันทึกการทำ 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
เป็นต้น
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
เสร็จแล้วครับ
ที่มา
cpu: Intel E6300
mb: Asrock Conroe945G-DVI
ram: 1G
เที่ยวนี้ลองคอมไพล์ซอร์สเอง ไม่ผ่านซักอย่าง เลยใช้แบบไบนารีดีกว่า
ทำตาม wiki.ubuntu.com/XenOnEdgy เกือบทั้งหมด
เอาแพกเกจที่เกี่ยวข้องมาก่อน
$ 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 ...
เน็ตเวิร์กยังไม่ได้ลอง เดี๋ยวจะกลับมาลอง
ในตัวอย่างของ 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 มีข้อจำกัดมาก คือ
ข้อเสียอื่น ๆ ก็พอยอมรับได้ครับ เมื่อแลกกับความเร็วที่ได้มา
(ลองกับ WinMe ยังไม่ผ่านครับ)
เนื่องจากอูบุนตูเปลี่ยนการเมานต์ดิสก์ จากเดิมที่กำหนดเป็นค่าดีไวซ์ มาเป็น 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 แต่ลืมบันทึกครับ
iptables บนเดเบียน มี 2 วิธี (จริง ๆ มีหลายวิธีมาก)
# 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 ...
# 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
ทำแผ่นบูตฉุกเฉิน
ปัญหาคือมีเครื่องลูกข่ายที่เป็นวินโดวส์ติดไวรัส และไม่ได้ลงซอฟต์แวร์ป้องกันไวรัสไว้ (เนื่องจากทอนกำลังเครื่อง)
ทางแก้คือ ใช้แผ่นบูตเดเบียนที่ลงแพกเกจ 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
/usr/share/live-helper/functions/defaults.sh
โดยตรงอ้างอิง
ต้องการหาโปรแกรมที่ทำงานกับไฟล์ pdf ที่สามารถใช้งานได้ทั้งลินุกซ์และวินโดวส์
$ sudo aptitude install pdfsam
$ 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
$ 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/
c:\xournal\etc\fonts\fonts.conf
จุดประสงค์คือ จะสร้างเซอร์เวอร์สำหรับการทดลอง ไว้แทนตัวเก่าที่ปลดประจำการไปทำ เครื่องเขียนซีดี
เพื่อเวลาจะทดสอบอะไร ก็สามารถลบและลงใหม่แบบคล่องตัว
จะติดตั้ง sarge เพื่อเป็นฐานในการทดลองรันเซิร์ฟเวอร์แบบต่าง ๆ
เพื่อให้ประหยัดแบนวิธ จะเขียนเป็น text ทั้งหมด
/dev/hda1 primary /boot 100MB /dev/hda2 primary / 10GB /dev/hda3 extend /dev/hda5 logical swap 512MB
ที่เหลือปล่อยว่างไว้เพื่อใช้ในการทดสอบและติดตั้งระบบต่อไป
เริ่มเลย
- บูตเครื่องด้วยแผ่นติดตั้งเดเบียน 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
เสร็จแล้ว
ทีแรกจะทำฐานเป็น 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
เสร็จแล้ว
งานที่ทำในครั้งนี้คือ
คราวที่แล้วเรามี
/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 ให้เต็มพื้นที่
ในคราวนี้เราจะมาแก้ไขเรื่องนี้ด้วย
ขั้นตอนขยายพาร์ติชั่นและเตรียมการติดตั้งคือ
หยุดการ 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
# apt-get install 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
ใช้งานเดสก์ทอป sid มานานจนย่ามใจ ลองอัปเกรดเซิร์ฟเวอร์ที่ใช้เป็น testing แบบ dist-upgrade ผลปรากฎว่าเครื่องวินโดวส์ตระกูล 9x ติดต่อแม่ข่ายไม่ได้ ทำให้วุ่นวายหลายวัน
บันทึกข้อควรจำ
/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
เด็ดขาด$ sudo apt-get install samba/stable
apt-get upgrade
อีกทีนึงพอดีไปค้นเจอ ขอบันทึกไว้ก่อนครับ เอาไว้ขยายทีหลัง
โมเด็มเสีย เลยถือโอกาสปรับปรุงระบบหลายอย่าง รวมทั้งการคอมไพล์เคอร์เนลด้วย
ปัญหาคือการคอมไพล์เคอร์เนล มันจะเร่งการคอมไพล์ให้เร็วด้วยการสร้างการคอมไพล์แบบขนานไปหลาย ๆ งาน (concurrent) จนเต็มค่าปริยายคือ 40
ทำให้บริการอื่นตายหมด
วิธีแก้ไขคือ ปรับค่านี้ให้น้อยลง ตอนที่กำลังคอมไพล์เคอร์เนล ผมใช้ค่า 2
# export CONCURRENCY_LEVEL=2
# make-kpkg --initrd kernel-image
ถ้าเป็นการคอมไพล์ปกติ ใช้ make -j2 ...
ช่วงนี้ติดตั้งเครื่องหลายเครื่องด้วย 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
เสร็จแล้ว รีบูตเข้าเคอร์เนลใหม่ได้เลย
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
ติดตั้ง slony1 บน etch และ lenny เบื้องต้น
slony เป็นแพ็กเกจส่วนขยายของ PostgreSQL ที่ใช้ทำหน้าที่สำเนาฐานข้อมูลปัจจุบันขณะ แบบใช้ตัวแม่และตัวลูก (master to multiple slaves replication system)
เหมาะสำหรับงานที่ต้องกระจายการทำงาน คืออ่านจากตัวลูกหลายตัว เขียนที่ตัวแม่ตัวเดียว
หรืองานที่ต้องการสำรองข้อมูลแบบปัจจุบันขณะ
ข้อจำกัดของ slony คือ รุ่นของโปรแกรมในการรัน ต้องเป็นรุ่นเดียวกันทั้งตัวแม่และตัวลูก ดังนั้นจึงไม่สามารถใช้แพกเกจของเดเบียนเองได้ เราจึงต้องมาคอมไพล์เอง เพราะเราติดตั้งบนเครื่องที่ต่างรุ่นกัน
และเนื่องจากหาเอกสารที่สมบูรณ์ยาก ในข้อเขียนนี้จึงเป็นเพียงแค่การเริ่มต้นให้ slony ทำงานได้เท่านั้น ยังต้องการปรับตั้งอีกมากที่จะทำให้สามารถใช้งานได้จริง
ข้อควรรู้
รายละเอียดในการทดสอบ คือ
SAMPLE
)เริ่มงาน
1 ติดตั้งแพกเกจ และคอมไพล์
# vi /etc/apt/sources.list
... deb http://www.backports.org/debian etch-backports main contrib non-free ...
# 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 ตอนเริ่มระบบ
# 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 แล้ว
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)
$ 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 จาก 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
บันทึกงานศึกษาแอนดรอยด์
/data/data/com.android.providers.telephony/databases/mmssms.db
คัดลอกมา ลบไฟล์นามสกุลอื่น แล้วรีบูต
กำลังบ้าแอนดรอยด์ อย่าว่ากันนะ
เครื่องทดสอบเป็น WellcoM รุ่น A88 และทำงานบนลินุกซ์เดเบียน
เครื่องคงต้องเข้าถึง root ได้ก่อน วิธีการดูได้ที่ MrChoke.Org: ROOT WellcoM A88
เป็นพื่นฐานของรุ่น 1.6 และ droidsans.com: วิธี Root Wellcom A88 2.1 ใน 1 นาที อันนี้เป็นของรุ่น 2.1
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.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
โทรศัพท์มือถือของน้อง คือ HTC Legend มีปัญหาหน้าจอทัชได้บ้าง ไม่ได้บ้าง ถ้าเปลี่ยนกับ HTC งบประมาณ 5 พันกว่าบ่าท ไม่คุ้มค่าโทรศัพท์แล้ว
ค้นเว็บดูวิธีแก้ มี 2 วิธี คือ
วิธีแรกต้องรอนานเกินไป วิธีที่สองลองแล้ว ได้ผลเพียงชั่วครู่ เนื่องจากไม่กล้าให้ความร้อนนาน กลัวว่าจอจะเสีย
จึงทดลองถอดโทรศัพท์ออกมา แล้วให้ความร้อนเฉพาะตัวกระจก Digitizer วิธีการสรุปดังนี้
เสร็จแล้ว ใช้งานได้เหมือนเดิม
หมายเหตุ
ลองบนอูบุนตู
$ sudo vi /etc/password
... user1:x:1101:1001::/home/user1:/bin/bash ...
หรือผ่านคำสั่ง usermod
$ sudo usermod -s /bin/bash user1
$ export GREP_OPTIONS='--color=auto'
$ wget -O country.txt "http://api.hostip.info/get_html.php?ip=$IP"
$ echo $(head -1 /dev/urandom | od -N 2 | awk '{ print $2 }')
ลูกน้องเอาธัมบ์ไดรฟ์มาให้หาไวรัส
ผลปรากฎว่าพบไฟล์ที่เป็นนามสกุล 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
เสร็จแล้วครับ
หมายเหตุ
$ sudo aptitude install clamav $ sudo freshclam
vfat
เขาจะเมานต์ด้วยค่าปริยายด้วยรหัสอักขระภาษาอังกฤษ ทำให้อ่านชื่อไฟล์ภาษาไทยไม่รู้เรื่อง ซึ่งจริง ๆ แล้วต้องตั้งค่ารหัสอักขระเป็น utf-8runauto..
เราจะลบไฟล์นี้ตรง ๆ ไม่ได้ ต้องใช้คำสั่งว่า$ rm runaut~1 -rf
ที่มา
เครื่องลูกข่ายวินโดวส์ติดไวรัส 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 ใช้ช่องว่างสองช่องคั่นชื่อไฟล์และไวรัส ดังนั้นถ้าชื่อไฟล์มีช่องว่างสองช่อง คำสั่งนี้จะใช้งานไม่ได้)
เสร็จแล้วครับ
$ 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
เสร็จแล้วicedax
แล้วใช้คำสั่ง$ CDDA_DEVICE=/dev/sr0 cdda2mp3
bash + inkscape
เพราะง่ายดีXXXX
เพื่อให้สะดวกในการใช้คำสั่ง grep
โค๊ดที่สร้างขึ้น ทำแบบง่าย ๆ คือรันตัวเลขตั้งแต่ 1-500 แต่รุ่นนี้เป็นรุ่นทดสอบ ทำแค่ 1-10 พอ ขั้นตอนคือ
awk
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 ก็สามารถนำไฟล์เหล่านี้ไปพิมพ์งานได้ตามต้องการ
ต้องการโอนข้อมูลผู้ใช้ไปเครื่องใหม่
ถ้าเราคัดลอกไฟล์ /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
ให้เป็นชื่อเครื่องใหม่ที่เราจะโอนไป แล้วก็สั่งรันได้เลย
โปรแกรมจะทำงานดังนี้
/etc/{passwd,shadow,group,gshadow}
และบีบอัดไดเรกทอรี่ /home
และ /var/spool/mail
มาไว้ในไดเรกทอรี่ $MIGRATEDIR
d.rollback-groupuser
เอาไว้สั่งทำย้อนกลับที่เครื่องใหม่เช่นกัน$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
เสร็จแล้ว เครื่องใหม่จะมีชื่อผู้ใช้งานและข้อมูลของผู้ใช้ครบตามเครื่องเก่าทุกประการ
หากสำเร็จเป็นที่พอใจแล้ว ควรลบไฟล์ต่าง ๆ ตามคำแนะนำนะครับ
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
สคริปต์คัดลอกผู้ใช้จากระบบปัจจุบันไปยังไดเรคทอรี่ที่ติดตั้งลินุกซ์อีกตัวหนึ่ง
มีประโยชน์สำหรับติดตั้งลินุกซ์หลายตัว และต้องการให้ผู้ใช้เหมือนกับระบบปัจจุบัน ตัวอย่างการใช้งานเช่น
ข้อกำหนดคือ
/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/
ตามต้องการ
ทำสคริปต์บล๊อกผู้ใช้ 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
ให้เข้ากับงานเรา
ปรับปรุงสคริปต์ให้สามารถบล๊อกโดยอัตโนมัติ
$ 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
ใส่ค่า spam keyword
$ vi spamdata.txt
did not match any documents Spam ไม่ตรงกับเอกสารใด
ใส่ค่า uid ของผู้ใช้คนสุดท้ายที่ไม่ต้องการตรวจ (สมมุติว่าเป็นผู้ใช้คนที่ 1000)
$ vi site_last_uid.txt
1000
$ ./drupal_auto_blockuser.sh
ครั้งต่อไปก็แค่สั่งรัน โดยไม่ต้องปรับแต่งอะไรอีก เว้นแต่มี spam keyword เพิ่มเติมก็ไปแก้ไฟล์ spamdata.txt
ลองใช้สคริปต์กับพาร์ติชั่นที่ขนาดไม่เท่ากันแล้วปรากฎว่าใช้ไม่ได้ เพราะ 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
(ศึกษาเพราะต้องการเอาไฟล์ในคลังของ 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 {} \;
*** ใช้ด้วยความระมัดระวังนะครับ ***
วันนี้แค่นี้ก่อนครับ
อ้างอิง
ขออนุญาตเขียนแบบกองโจรนะครับ โดยมือใหม่ เพื่อมือใหม่ครับ
เอามาจาก tldp: BASH Programming - Introduction HOW-TO
ศึกษาเพิ่มเติมได้จาก tldp: Bash Guide for Beginners
และในเชิงลึก จาก tldp: Advanced Bash-Scripting Guide
เชลล์สคริปต์ พูดง่าย ๆ ก็คือการนำคำสั่งในเชลล์ของลินุกซ์มาเรียงต่อกันให้ทำงานตามที่เราต้องการ โดยเพิ่มโครงสร้างการวนรอบ และฟังก์ชั่นต่าง ๆ เติมเข้ามา เพื่อให้การทำงานได้ตามที่เราต้องการ ซึ่งจะเหมาะมากกับงานแบบ 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
สมมุติตั้งชื่อสคริปต์ว่า 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
ออกทางจอภาพ
จากตัวอย่างข้างบน ผมเขียนอธิบายโดยละเอียดโดยใช้เอดิเตอร์ vi แต่เพื่อให้กระชับเข้า จะขอละเลยการใช้เอดิเตอร์ โดยจะเขียนเฉพาะโค๊ดอย่างเดียวครับ
#!/bin/bash tar -cvzf /tmp/my-backup.tgz /home/USER/
บรรทัดที่สองให้เปลี่ยนคำว่า USER เป็นชื่อเรา
เป็นการสั่งให้ใช้คำสั่ง tar ทำการสำรองข้อมูลพร้อมบีบอัดข้อมูลในไดเรคทอรี่ของบ้านเราไปสู่ไฟล์ชื่อ /tmp/my-backup.tgz
ใช้สัญญลักษณ์ >
ใสการเปลี่ยนทิศ
ข้อมูลมาตรฐานในเชลล์จะมีอยู่ 4 ชนิด คือข้อมูลเข้า(stdin), ข้อมูลแสดงผล(stdout), ข้อมูลข้อผิดพลาด(stderr), และแฟ้มข้อมูล(file)
ในทางปฏิบัติ เราสามารถเปลี่ยนทิศทางของข้อมูลเหล่านี้ไปมาได้ โดยมีมาตรฐานคือ 1 จะหมายถึงข้อมูลแสดงผล(stdout) และ 2 จะหมายถึงข้อมูลความผิดพลาด(stderr)
เช่น
$ ls -l > ls-l.txt
จะเปลี่ยนการแสดงผลของคำสั่ง ls -l
ไปเก็บไว้ที่ไฟล์ชื่อ ls-l.txt ดังนั้นคำสั่งตามตัวอย่างนี้จะไม่แสดงอะไรออกมาทางจอภาพ แต่จะเก็บไว้ที่ไฟล์แทน หากเราต้องการดูผล สามารถใช้คำสั่งแสดงผลของไฟล์ได้คือ
$ cat ls-l.txt
$ grep da * 2> grep-errors.txt
ตัวอย่างนี้เป็นการค้นหาข้อความ da
ในทุกไฟล์ (*) และหากเกิดข้อผิดพลาดขึ้น จะนำข้อความผิดพลาดไปเก็บไว้ที่ไฟล์ชื่อ grep-errors.txt
$ grep da * 1>&2
เป็นการค้นหาข้อความ da
ในทุกไฟล์ (*) โดยนำการแสดงผลไปใส่ไว้ใน stderr แทนการแสดงผลปกติ แต่ในกรณีนี้เราป้อนคำสั่งทางแป้นพิมพ์ stdout และ stderr คือจอภาพเหมือนกัน จึงไม่เห็นความแตกต่าง แต่หากคำสั่งนี้ไปอยู่ในสคริปต์ที่เรากำหนดให้ stderr เป็นไฟล์ error-log การแสดงผลก็จะถูกเปลี่ยนทิศไปตามนั้น
$ grep da * 2>&1
เป็นการค้นหาข้อความ da
ในทุกไฟล์ (*) โดยหากเกิดข้อผิดพลาดขึ้น จะแสดงผลข้อผิดพลาดออกมาทาง stdout ซึ่งในที่นี้คือจอภาพเหมือนกัน
$ rm -f $(find /home/USER -name core) &> /dev/null
คำสั่งนี้เป็นการค้นหาไฟล์ในไดเรคทอรี่ /home/USER
ที่มีชื่อว่า core (find /home/USER -name core
)
เมื่อพบแล้วก็จัดการลบทิ้งโดยไม่เตือน (rm -f
)
โดยโยกการแสดงผลทั้งหมด (ทั้ง stderr และ stdout - ใช้สัญญลักษณ์ &>
) ไปยังไฟล์ชื่อ /dev/null
ซึ่งเป็นไฟล์พิเศษ หมายความว่ายกเลิกการแสดงผลทั้งหมด
(คำสั่งนี้ค่อนข้างอันตราย เพราะลบโดยไม่เตือน โปรดทดลองด้วยความระมัดระวังครับ)
ไปป์เป็นการส่งต่อผลลัพธ์จากคำสั่งหนึ่งไปเป็นค่านำเข้าของอีกคำสั่งหนึ่ง
$ 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
จะเห็นว่าการทำไปป์ ลดขั้นตอนไปมาก คงเหลือเพียงบรรทัดเดียว
$ ls -l | grep "\.txt$"
ตัวอย่างนี้จะส่งผลลัพธ์จากคำสั่ง ls -l
ต่อไปให้คำสั่ง grep "\.txt$"
คือให้แสดงเฉพาะไฟล์ที่มีนามสกุลเป็น .txt
เท่านั้น
มีค่าเท่ากับคำสั่ง ls แบบใส่พารามิเตอร์กรอง
$ ls -l *.txt
หมายเหตุ
รูปแบบ "\.txt$"
เป็นรูปแบบของ Regular Expression ซึ่งใช้มากในเชลล์สคริปต์ มีความหมายว่า "ที่ต้องลงท้ายด้วย .txt"
ตัวแปรในเชลล์สคริปต์ ไม่มีชนิดข้อมูล คือเราสามารถใช้ตัวแปรแทนตัวเลขหรืออักขระใด ๆ ก็ได้
โดยในขั้นตอนกำหนดค่า ไม่ต้องใช้เครื่องหมายใด ๆ นำหน้า แต่ตอนอ้างถึง ต้องใช้เครื่องหมาย $
นำหน้าตัวแปร
#!/bin/bash STR="Hello World!" echo $STR
ให้ผลลัพธ์เหมือนตัวอย่างที่ 2.1
ข้อควรระวังคือ
=
$
จะหมายถึงการแสดงผลข้อความว่า STR
เฉย ๆ#!/bin/bash OF=/tmp/my-backup-$(date +%Y%m%d).tgz tar -cvzf $OF /home/USER/
ให้ผลลัพธ์คล้ายตัวอย่าง 2.2 แต่เพิ่มการใช้ตัวแปรลอยในคำสั่ง $(date +%Y%m%d)
ซึ่งมีผลทำให้ชื่อไฟล์ข้อมูลสำรองมีวันที่ต่อท้ายชื่อด้วย
ตัวแปรในเชลล์สคริปต์ทุกตัว จะเป็นตัวแปรรวม (Global) คือทุก ๆ ส่วนของโปรแกรมจะเห็นเหมือนกันหมด
แต่ในกรณีที่เราต้องการให้เห็นเฉพาะในฟังก์ชั่นที่เราต้องการ เราสามารถกำหนดให้ตัวแปรเป็นตัวแปรท้องถิ่นได้ด้วยคำสั่ง local
เช่น
#!/bin/bash HELLO=Hello function hello { local HELLO=World echo $HELLO } echo $HELLO hello echo $HELLO
สคริปต์นี้ตัวแปร HELLO
ในโปรแกรมหลัก กับในฟังก์ชั่นจะเป็นตัวแปรคนละตัวกัน
มีรูปแบบคือ
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
if ... then
#!/bin/bash if [ "foo" = "foo" ]; then echo expression evaluated as true fi
โค๊ดนี้จะเป็นจริงเสมอ ดังนั้นข้อความ "expression evaluated as true
" จะถูกพิมพ์ออกมาเสมอ
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
" จะถูกพิมพ์ออกมาเสมอ
#!/bin/bash T1="foo" T2="bar" if [ "$T1" = "$T2" ]; then echo expression evaluated as true else echo expression evaluated as false fi
ตัวอย่างนี้จะเป็นเท็จเสมอ
สังเกตุการใช้ตัวแปรในการเปรียบเทียบ ควรให้ตัวแปรอยู่ในเครื่องหมายคำพูดเสมอ เพื่อป้องการการผิดพลาดจากการแทนค่าที่ซับซ้อน หรือการที่มีช่องว่างในค่าตัวแปร
คำสั่ง 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 จะเป็นจริง
for
#!/bin/bash for i in $( ls ); do echo item: $i done
เป็นการนำคำสั่ง ls
ไปเป็นตัวแปรชั่วคราวในการกำหนดขอบเขตให้กับตัวแปร i
ในคำสั่ง for
ในที่นี้จะทำการแสดงผลว่า item: FILENAME ...
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
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
until
#!/bin/bash COUNTER=20 until [ $COUNTER -lt 10 ]; do echo COUNTER $COUNTER let COUNTER-=1 done
จะแสดงตัวเลขตั้งแต่ 20 ลดลงทีละ 1 จนถึง 10
ในการใช้งานเชลล์สคริปต์แบบจริงจัง เราจำเป็นต้องเขียนฟังก์ชั่นเพื่อประโยชน์ในการเรียกใช้งานแบบซ้ำ ๆ เพื่อให้ประหยัดการเขียนโค๊ด และให้โค๊ดดูง่าย
มีรูปแบบเป็น
function FUNCTION_NAME { COMMAND }
หรือ
FUNCTION_NAME () { COMMAND }
โปรแกรมจะเว้นไม่ถูกเรียกทำงานในช่วงตั้งแต่ชื่อฟังก์ชั่นจนกระทั่งจบบล๊อก { COMMAND }
เรานิยมวางฟังก์ชั่นไว้ที่ต้นโปรแกรม เพื่อให้สามารถถูกเรียกจากโค๊ดหลักได้
#!/bin/bash function quit { exit } function hello { echo Hello! } hello quit echo foo
ตัวอย่างนี้ บรรทัดที่ 10 คือคำสั่ง echo foo
จะไม่ถูกเรียกใช้ เนื่องจากโปรแกรมจะหลุดสู่เชลล์ในบรรทัดที่ 9 คือคำสั่ง quit
#!/bin/bash function quit { exit } function ex { echo $1 } ex Hello ex World quit echo foo
จากตัวอย่าง จะเห็นการส่งผ่านข้อความเข้าไปในฟังก์ชั่น ex
ด้วยตัวแปร $1
ในทำนองเดียวกัน ถ้ามีการส่งผ่านตัวแปรหลายตัว ก็จะใช้รูปแบบเป็น $2, $3, ...
โดยเรียกใช้งานด้วยรูปแบบ ex VAR1 VAR2 VAR3 ...
ตามลำดับ
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
ให้ออกจากการวนรอบ
#!/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
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.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
เราใช้ $((ARITHMATIC))
หรือ $[ARITHMATIC]
ในการแทนค่าตัวแปร
ดังนี้
$ echo $(1+1) bash: 1+1: command not found $ echo 1+1 1+1 $ echo $((1+1)) 2 $ echo $[1+1] 2
บรรทัดเริ่มต้นของสคริปต์ หลังเครื่องหมาย #!
(hash-bang) เราต้องใส่พาธของโปรแกรม bash ให้เต็ม
สำหรับเดเบียน อยู่ที่ /bin/bash
อยู่แล้ว แต่หากเป็นดิสโตรอื่น อาจค้นหาว่าโปรแกรม bash อยู่ที่ไหน โดยใช้คำสั่งเหล่านี้
$ which bash $ whereis bash $ find / -name bash
หลายโปรแกรมของเชลล์มีการส่งค่าออกมา (Return value) อาจเพื่อแจ้งสถานะการรันว่ารันสำเร็จหรือไม่อย่างไร หรืออาจส่งออกเป็นค่าที่จะนำไปประมวลผลต่อก็ตาม เราสามารถใช้ตัวแปรพิเศษ $?
ในการดูผลลัพธ์ของโปรแกรมได้
เช่น
#!/bin/bash cd /dada &> /dev/null echo rv: $? cd $(pwd) &> /dev/null echo rv: $?
กรณีนี้ ไดเรคทอรี่ /dada
เป็นไดเรคทอรี่ที่เราแกล้งพิมพ์ผิดไว้ เพื่อดูว่าสคริปต์จะส่งออกค่าออกมาเป็นอย่างไร ซึ่งจะได้ผลออกมาเป็น 1 และ 0 ตามลำดับ คือ 1 หมายถึงมีข้อผิดพลาดในโปรแกรม และ 0 หมายถึงรันสำเร็จ ไม่มีข้อผิดพลาดใด ๆ
เราสามารถนำผลลัพธ์ของโปรแกรมมาใส่ในตัวแปร ด้วยการสั่งภายใต้เครื่องหมาย `
(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
[ "$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 ไม่มีค่า#!/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
+
การบวก-
การลบ*
การคูณ/
การหาร%
การหาเศษจากตัวหาร (remainder)-lt
น้อยกว่า (<)-gt
มากกว่า (>)-le
น้อยกว่าหรือเท่ากับ (<=)-ge
มากกว่าหรือเท่ากับ (>=)-eq
เท่ากับ (==)-ne
ไม่เท่ากับ (!=) 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
เป็นทั้งโปรแกรมและภาษาในการค้นหาข้อความในไฟล์จากรูปแบบที่เรากำหนดให้/tmp/dummy
มีเนื้อไฟล์คือ
test123 test tteesstt
ตัวอย่างการใช้งานคือ
$ awk '/test/ {print}' /tmp/dummy test123 test
ดูรายละเอียดเพิ่มเติมได้ที่ gentoo: Awk by example
grep
เป็นโปรแกรมที่ใช้บ่อยในการค้นข้อความในไฟล์ และยังมีความสามารถในการสรุปผลการนับข้อความด้วย$ man grep | grep "standard" -c 8
เป็นการค้นคำว่า standard ในการแสดงผลของคำสั่ง man grep
ว่ามีอยู่กี่คำ คำตอบคือ 8
ดูตัวอย่างเพิ่มเติมที่ tdlp: Examples using grep
wc
ใช้ในการนับคำ, นับบรรทัด และนับจำนวนหน่วยความจำที่ถูกใช้ในไฟล์ เป็นไบต์$ wc --words --lines --bytes /tmp/dummy 3 3 22 /tmp/dummy
sort
ใช้จัดเรียงข้อมูลb c a
ตัวอย่างคำสั่งคือ
$ sort /tmp/dummy a b c
คือการนำเอาเนื้อไฟล์ /tmp/dummy
มาจัดเรียง และแสดงผลออกทางจอภาพ
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
ใช้ในการตั้งค่าหรือแสดงค่าต่าง ๆ ของเทอร์มินัล$ tput cup 10 4
เลื่อนเคอร์เซอร์ไปยังบรรทัดที่ 10 สดมภ์ที่ 4
$ tput reset
ล้างจอภาพ มีค่าเท่ากับคำสั่ง clear
$ tput cols
แสดงจำนวนสดมภ์ (ความกว้าง) ของจอเทอร์มินัล
#!/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
#!/bin/bash SRCD="/home/" TGTD="/var/backups/" OF=home-$(date +%Y%m%d).tgz tar -cZf $TGTD$OF $SRCD
#!/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!
#!/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
เราใช้พารามิเตอร์ -x
ต่อท้ายคำสั่งในบรรทัดแรก
#!/bin/bash -x
จะมีผลว่าเชลล์จะแสดงทุกคำสั่งที่ถูกรันออกมาทางจอภาพ
จบแล้วจ้า
ต้องการแปลงชื่อไฟล์เป็นตัวเล็ก ค้นไปค้นมาปรากฎว่ามีอยู่ในแพกเกจ 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
ลองศึกษาการขุดเหมืองเงินคริปโตด้วยการ์ดจอ Nvidia
ปัญหาคือทดลองในเซิร์ฟเวอร์ที่ไม่ได้ต่อจอภาพไว้ และไม่ได้ลง Xserver ไว้ การใช้งานผ่าน ssh จึงปรับแต่งอะไรไม่ได้เลย การใช้ค่าที่ติดตั้งมาจากโรงงาน ทำให้การ์ดร้อนจัดจนใช้งานจริงไม่ได้
ปัญหาต่อมาคือไดรเวอร์บนลินุกซ์ล้าสมัยเล็กน้อย ใช้กับการ์ดจอใหม่ ๆ ไม่ค่อยได้ หรือใช้ได้แต่ไม่ดีพอ
(ระบบปฏิบัติการที่ใช้ คือ debian stretch)
ทางแก้คือ
ทำที่เครื่องเซิร์ฟเวอร์ที่ติดตั้งการ์ด 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
ที่มา
ติดตั้งแพ็คเกจที่ใช้งาน
$ 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
บูตเครื่องใหม่ ก็จะสามารถใช้งานไดรเวอร์รุ่นใหม่ได้แล้ว
ที่มา
จบ
ภาคต่อของการขุดเหมืองโดยใช้การ์ดจอ Nvidia
จากครั้งก่อน การตั้งค่าของการ์ด ต้องตั้งค่าใหม่ทุกครั้งเมื่อบูตใหม่ (จริง ๆ สามารถบันทึกค่าเดิมไว้ แล้วนำมาใช้ใหม่ได้ แต่ไม่สะดวกสำหรับงาน console)
คราวนี้จะทำให้มีความเป็นอัตโนมัติมากขึ้น โดยไม่ต้องมาตั้งผ่าน Display Manager (lightdm) ดังนั้นจึงไม่ต้องลง lightdm แบบครั้งก่อนแล้ว
เราจะใช้โปรแกรมและไฟล์ EDID ของคุณ ฺBoris Dimitrov ในการจำลองจอเสมือน และปรับความเร็วพัดลมให้แปรผันตามอุณหภูมิ แต่เนื่องจากดูเหมือนโปรแกรมเดิมใช้งานได้แค่การ์ดใบเดียว และต้องลง tcsh ซึ่งไม่ใช่เชลล์ปริยายของเดเบียน เราจึงเอาของเดิมมาปรับปรุงเล็กน้อยดังนี้
เนื่องจากเป้าหมายคือความเป็นอัตโนมัติมากขึ้น จึงขอใช้งานด้วยสิทธิ์ 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
เพื่อให้เริ่มงานตอนเปิดเครื่องได้เลย
บันทึกเรื่อง Web Server
aptitude install phpmyadmin mysql-server-5.0
aptitude install phpmyadmin apache2-mpm-worker libapache2-mod-fcgid php-cgi mysql-server-5.0
aptitude install phpmyadmin nginx php-cgi lighttpd
#!/bin/bash SERVER='192.168.1.1' time $(for i in `seq 101 200`; do wget -q -O- http://$SERVER/?q=node/$i; done)
#!/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 |
ต้องการเปลี่ยนเครื่องเซิร์ฟเวอร์ที่ใช้รัน Drupal จึงทดลองทดสอบเปรียบเทียบ Web Server และโปรแกรมที่ใช้รัน php รุ่นต่าง ๆ ไว้ดังนี้
ทำสคริปต์เรียก node ของ Drupal จากเซิร์ฟเวอร์ จำนวน 200 node โดยแบ่งเป็นแบบลำดับและแบบขนาน
#!/bin/bash SERVER='www.example.com' time $(for i in `seq 101 300`;do wget -q -O- http://$SERVER/?q=node/$i; done)
#!/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 ให้)
ติดตั้ง
# 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)
ติดตั้ง
# 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)
ติดตั้ง
# 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)
ติดตั้ง
# 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)
บันทึกการติดตั้งฮาร์ดแวร์
# 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/
สำหรับเครื่องพิมพ์ในลินุกซ์ เราเรียกใช้จาก 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
มีปัญหาเรื่อง 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
เสร็จเรียบร้อยแล้ว สามารถพิมพ์ผ่านเครือข่ายได้แล้ว
ปกติเวลาจะติดตั้งเครื่องพิมพ์เครือข่าย จะใช้การติดตั้งผ่านเว็บ ทางพอร์ต 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
ก็จะสามารถใช้งานเครื่องพิมพ์เครือข่ายวินโดวส์ได้แล้ว
บางครั้งเวลาอัปเกรดแพกเกจ อาจเกิดปัญหาพิมพ์ไปยังเครื่องพิมพ์วินโดวส์ไม่ได้
แก้ปัญหาด้วยการ ถอดและลงไดรฟเวอร์ปรินเตอร์ใหม่
บางครั้งเวลาเลือกเปลี่ยนไดรฟเวอร์เครื่องพิมพ์ให้เหมาะสมกับเครื่องพิมพ์นั้น ๆ เมื่อเปลี่ยนแล้วพิมพ์ผ่านเครือข่ายไม่ออก
ดู /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 ตรง ๆ )
$ sudo vi /etc/cups/mime.convs
... application/octet-stream application/vnd.cups-raw 0 - ...
(ลองบน sid เครื่องนึงต้องแก้ อีกเครื่องนึงไม่ต้องแก้ ยังงงอยู่ครับ)
เอามาจาก: Debian and Windows Shared Printing mini-HOWTO
Upgrade Required
/etc/cups/cupsd.conf
โดยเติม
DefaultEncryption Never
แล้วสั่งเริ่มใหม่
$ /etc/init.d/cupsd.conf restart
เอามาจาก CUPS: 426 - Upgrade Required
ถ้าติดตั้งใหม่แล้วโดนรายงานว่า หาไฟล์ /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
~/.cups/lpoptions
/etc/cups/lpoptions
สมมุติว่าจะให้พิมพ์ไปเครื่องพิมพ์ brother โดยตั้งความละเอียดที่ 600dpi และขนาดกระดาษ A4
Dest brother Resolution=600dpi PageSize=A4
จากเรื่อง 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
เมื่อคืนไฟฟ้าดับ ทำให้เครื่องต้องปิดตัวเอง ก่อนหน้านั้นติดตั้งเคอร์เนลใหม่ไว้ แต่ยังไม่ได้รีบูตเครื่อง พอไฟฟ้ามา เปิดเครื่องใหม่เลยกลายเป็นเคอร์เนลใหม่ ทำให้ต้องมาติดตั้งไดรฟ์เวอร์อุปกรณ์ใหม่
ลองตรวจดูปรากฎว่าไม่เคยบันทึกเกี่ยวกับโมเด็ม 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
ติดตั้งโมเด็ม 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 ขึ้น เราสามารถเรียกใช้โมเด็มได้จากลิงก์นี้ครับ
เสร็จแล้วครับ :)
ข้อมูลเพิ่มเติม
EIP is at down+0x19/0x39
จากครั้งก่อน หลังจากติดตั้งเสร็จแล้ว ผมได้ลองอัปเกรดโดยใช้คำสั่ง 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
ใช้งานได้แล้ว
ข้อมูลปรับปรุง
ถึงแม้ 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
ก็ใช้ได้เช่นกัน
อ้างอิง
ใช้แพกเกจชุด 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: 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
เสริมช่วย อาจได้ผลในการเขียนกลับได้ดีขึ้นpci=nomsi
และใช้คำสั่ง# hdparm -Z /dev/sda # smartctl -a /dev/sda
ได้ผลเป็นปกติแล้ว
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 ด้วย ดังนี้
System -> Preferences -> Mouse
Disable touchpad while typing
Enable mouse clicks with touchpad
Enable horizontal scrolling
# 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
~/.xsessionrc
เนื้อไฟล์มีดังนี้
synclient VertEdgeScroll=1 synclient HorizEdgeScroll=1 synclient TapButton1=1
/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
รวมเรื่องเกี่ยวกับ cache และ proxy
จะทดลองเพิ่มความเร็วฮาร์ดดิสก์ ลองซื้อ SSD ขนาด 60G มาตัวนึง เพื่อมาทำ cache
ได้ทดสอบ bcache แต่ไปไม่รอด มันต้องการเคอร์เนลใหม่เกินไป ทำให้กระทบส่วนอื่น ๆ ของเครื่อง จึงได้หันมาหา flashcache
ติดตั้งแพคเก็จที่จำเป็น
# 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}
รีบูตไปที่ /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 ...
รีบูต
เสร็จแล้ว
รีบูตไปที่ /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 ...
รีบูต
-p thru
ยังใช้ไม่ได้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
หมายเหตุ
ทำแคชให้ 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
หมายเหตุ
# vi /etc/approx/approx.conf
debian http://localhost:9998/debian security http://localhost:9998/security volatile http://localhost:9998/volatile
เป็นต้น ใช้งานสักสองสามวัน จนคลังของ approx เต็ม แล้วจึงค่อยเปลี่ยนกลับ
บันทึกการทำงานเกี่ยวกับไฟล์ภาพ เสียง และวิดีโอ
ลองทำสคริปต์แปลงไฟล์ภาพเป็นวิดีโอ
วิธีใช้คือเข้าไปในไดเรคทอรี่ของไฟล์ภาพ แล้วสั่งรันโปรแกรมตรง ๆ
$ 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 ได้แล้ว
มีงานที่จะต้องเก็บแผ่นซีดี วีซีดี และดีวีดี ลงบนฮาร์ดดิสก์ เลยขอบันทึกคำสั่งที่เกี่ยวข้องเอาไว้ดูอ้างอิงในภายหลังครับ
พยายามทำเป็นแบบบรรทัดคำสั่งให้มากที่สุด เพื่อจะได้ใช้งานกับเครื่องกำลังต่ำ ๆ ได้ เผื่อจะขยายไปเป็นเครื่องเขียนซีดีอัตโนมัติ
เครื่องมือที่ใช้ พยายามใช้ cdrecord (wodim) ให้มากที่สุด เพราะเข้าใจว่าเขียนได้เรียบร้อยกว่า cdrdao
ติดตั้งด้วยคำสั่ง
$ sudo aptitude install cdrecord cdrdao
$ dd if=/dev/cdrom of=XXX.iso
$ cdrecord speed=8 dev=/dev/cdrom -data XXX.iso
$ sudo mount -t iso9660 XXX.iso /mnt/disk
$ 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
$ readcd -clone dev=/dev/cdrom f=XXX.bin
$ cdrecord -clone gracetime=2 -raw96r dev=/dev/cdrom speed=8 driveropts=burnfree -overburn -multi -xa1 -eject XXX.bin
$ mplayer XXX.bin
$ sudo aptitude install module-assistant
$ sudo m-a a-i cdfs
$ sudo modprobe cdfs
$ sudo mount -t cdfs -r /dev/cdrom /mnt/disk
$ sudo aptitude install vcdtools
$ mkvcdfs /home/user1/mpg/*
$ cdrdao write --device /dev/cdrom vcd.toc
ตอนนี้วิธีที่ง่ายและดีที่สุดคือ ซื้อโปรแกรม nerolinux มาใช้
แต่ถ้าหากจะต้องการใช้โอเพนซอร์สล้วน ๆ และต้องการเป็นแบบบรรทัดคำสั่ง มีรายละเอียดดังนี้
$ dd bs=1k if=XXX.nrg of=XXX.iso skip=300
$ sudo aptitude install nrg2iso
$ nrg2iso XXX.nrg xxx.iso
$ sudo mount -t iso9660 -o loop,offset=307200 XXX.nrg /mnt/disk
$ sudo aptitude install fuseiso
$ sudo fuseiso XXX.nrg /mnt/disk
$ 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 XXX.nrg
หมายเหตุ
โพสต์นี้ยังไม่เสร็จ ยังขาดรายละเอียดของออปชั่นในบรรทัดคำสั่งอีกมาก และจะพยายามเติมงานของดีวีดีลงไปด้วย
ดังนั้น จะต้องมีการเปลี่ยนแปลงเรื่อย ๆ ครับ
อ้างอิง
ต้องการเขียนแผ่นข้อมูล ซึ่งเป็นไฟล์ mp3 ธรรมดา
ที่แรกเขียนด้วย k3b ปรากฎว่าอ่านด้วยคอมพิวเตอร์ออกสบาย ๆ แต่กลับอ่านด้วยเครื่องเล่นดีวีดีทั่วไปตามท้องตลาดไม่ได้ ไม่ว่าลองเปลี่ยนค่าเป็น DAO TAO Mode1 Mode2 ยังไงก็อ่านไม่ออก
ไม่แน่ใจเครื่องเขียนว่าเก่าไปหรือเปล่า
เลยลองดาวน์โหลด Nero Linux 3 มาลองเขียนดู ปรากฎว่าอ่านได้สบาย ๆ
ลองเขียนด้วยโปรแกรมปริยายของ Gnome คือ nautilus-cd-burner ก็อ่านออกเหมือนกัน
เลยไม่ทราบว่าตั้งค่า k3b ผิดตรงไหน คงต้องกลับไปศึกษาต่อ เที่ยวนี้บันทึกไว้แค่นี้ก่อน
ลองบนเดเบียน sid ครับ
มีงานต้องตัดต่อวีดีโอจากกล้องวีดีโอแบบเขียนลงแผ่น ดังนั้นไฟล์ที่ได้จะมีโครงสร้างเป็นดีวีดีแล้ว แต่เราต้องการนำมาตัดต่อและเชื่อมกัน เพื่อนำไปเขียนลงดีวีดีแผ่นใหม่
เริ่มด้วยติดตั้งแพกเกจ
$ 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
อ้างอิง
เอามาจาก 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
ถ้าจะสร้างคีย์ลัดไว้ใช้เอง ก็แก้เองในไฟล์นี้แหละครับ โครงสร้างไฟล์เป็นแบบง่าย ๆ
การกำจัด 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 แบบโปร่งใสได้จำกัด คือไม่สามารถไล่ระดับความโปร่งใสแบบเดียวกับ png ทำได้
วิธีแก้คือให้สร้างภาพเป็น png เมื่อทำจนเสร็จเรียบร้อย ก็แค่ใช้เชลล์เปลี่ยนนามสกุลจาก png เป็น gif ก็จะได้ภาพโปร่งใสตามต้องการ
หมายเหตุ
update
จะรีไซเคิลเครื่องเก่า มาทำเครื่องคัดลอกซีดีเพื่อถวายวัด
เครื่องใหม่ราคาประมาณหมื่นเศษ
สเปคเครื่องเก่าคือ Celeron 850MHz, RAM 128MB, HD 15G (เป็น /dev/hda)
ลงทุนซื้อใหม่คือ CD-Writer 3 ตัว ตัวละ 750 บาท
เนื่องจากจะไม่มีจอภาพ จึงจะใช้เสียงเป็นตัวแจ้งสถานะ
จะทำให้มีการทำงานคือ
หมายเหตุ
วิธีการสร้าง
เริ่มด้วย ติดตั้งอูบุนตู 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
มีปัญหากับไดร์ฟ
ไม่สามารถอ่านดิสก์แบบ multisession ที่ถูก overburn ได้ (เช่นแผ่น VCD)
ยังแก้ไม่ตก
เห็นคำถามเกี่ยวกับการแบ่งไฟล์สื่อออกเป็นหลายส่วนที่ ubuntuclub.com
ด้วยต้องการคลายเครียด จึงวางแผนลงมือเขียนสคริปต์ โดยคิดให้สคริปต์มีการทำงานดังต่อไปนี้
ffmpeg -i "$INFILE" 2>&1 | grep "Duration" | cut -d ' ' -f 4 | sed s/,//
เสร็จแล้วเขียนออกมาหยาบ ๆ และทดสอบไปเล็กน้อยว่าใช้งานได้จริงแล้วเลยเอามาบันทึกไว้กันลืมเสียหน่อย
โดยผลลัพธ์ที่ได้ออกมาเป็นสคริปต์ 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
เป็นอันพร้อมใช้งานครับ ^_^
โจทย์คือแปลงไฟล์วีดีโอจากแผ่นดีวีดี 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 มาสร้างเป็นแผ่นต่อไป
เสร็จแล้วครับ
มีงานต้องแปลงไฟล์เสียงสกุล amr ไปเป็น mp3 เพื่อแจกจ่าย พบปัญหาว่า
ทางแก้ไขคือ
-q9
โปรแกรมที่ต้องการใช้งานคือ 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 ตามต้องการ
ต้องการอัดเสียงด้วยบรรทัดคำสั่งบนเครื่องเซิร์ฟเวอร์ที่ไม่ได้ลง 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 นาที
เสร็จแล้ว
เอามาจาก
แผ่นดีวีดีข้อมูลเป็นรอยมาก (อาจไม่ค่อยคอมแพตด้วย เป็นแผ่น DVD+R)
คัดลอกด้วย Nautilus ทีละหลาย ๆ ไฟล์ไม่ได้
ใช้แบบบรรทัดคำสั่งก็ไม่ด้
ผ่านด้วยการคัดลอกทีละไฟล์
จะรีไซเคิลเครื่องเก่า มาทำเครื่องคัดลอกซีดีเพื่อถวายวัด
ถ้าซื้อเครื่องคัดลอกซีดีตัวจริงใหม่ราคาประมาณหมื่นเศษ
ของเราใช้รีไซเคิลเอา ราคาไม่เกินสองพันบาท (คิดเฉพาะอุปกรณ์ที่ซื้อใหม่)
สเปคเครื่องเก่าคือ Celeron 850MHz (เครื่องตรวจสอบได้ 600MHz), RAM 128MB, HD 8G (เป็น /dev/hda)
ลงทุนซื้อใหม่คือ CD-Writer 3 ตัว ตัวละ 630 บาท
เนื่องจากจะไม่มีจอภาพ จึงจะใช้เสียงเป็นตัวแจ้งสถานะ
จะทำให้มีการทำงานคือ
หมายเหตุ
วิธีการสร้าง
เริ่มด้วยติดตั้งเดเบียน 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
เสร็จแล้วก็รีบูตทดสอบได้เลยครับ
อ้างอิง
ท่านใดเก่งเชลล์สคริปต์ฝากช่วยปรับปรุง และเผยแพร่ ด้วยนะครับ
จะรีไซเคิลเครื่องเก่า มาทำเครื่องคัดลอกซีดีเพื่อถวายวัด
สเปคเครื่องเก่าคือ AMD Sempron(tm) 2000+, RAM 512MB, HD 40G (เป็น /dev/sda)
ลงทุนซื้อใหม่คือ CD-Writer 4 ตัว ตัวละ 545 บาท (IDE 3 และ SATA 1)
เนื่องจากจะไม่มีจอภาพ จึงจะใช้เสียงเป็นตัวแจ้งสถานะ
จะทำให้มีการทำงานคือ
เริ่มด้วยติดตั้งเดเบียน squeeze แบบไม่ติดตั้งอะไรเพิ่มเติมเลย
ดามด้วยเอาแพกเกจที่จำเป็นที่สคริปต์เราต้องเรียกใช้ คือ
# 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 โดยจะปรับให้สามารถเขียนแผ่น 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 และเขียนด้วยความเร็วต่ำสุดที่เป็นไปได้ ทั้งนี้เพื่อให้แผ่นมีความทนทานใช้งานได้ยาวนาน
การทำงานของเครื่อง
เริ่มสร้างด้วย...
การติดตั้งเดเบียนแบบเปล่า ๆ ดูตัวอย่างจาก 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
# 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
งานต้นฉบับสำหรับส่งโรงพิมพ์ สำหรับหน้าที่มีภาพชิดขอบ เราจำเป็นต้องเผื่อขอบให้มากกว่าขนาดกระดาษจริงด้านละ 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
ดังนี้
$ 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>
$ 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
เสร็จแล้ว
/usr/share/inkscape/extensions
รวมทั้งตัว inkex.py ด้วยเอามาจาก
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
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
for i in *.mp3; do mpg321 -w "`basename "$i" .mp3`.wav" "$i"; done
for i in *.mp3; do madplay -o "`basename "$i" .mp3`.wav" "$i"; done
for i in *.mp3; do lame --decode "$i" "`basename "$i" .mp3`.wav"; done
for i in *.ogg ; do ogg123 -d wav -f "`basename "$i" .ogg`.wav" "$i"; done
for i in *.wma; do mplayer -vo null -vc dummy -af resample=44100 \ -ao pcm:waveheader:file="${i%.wma}.wav" "$i" ; done
aptitude install faac faac -b 16 -c 44100 -w --title "TITLE" --artist "ARTIST" INFILE.wav
ได้ไฟล์ออกมาเป็น INFILE.m4a (คุณภาพดีกว่าแปลงจาก ffmpeg)
คำสั่งคือ
normalize-audio -m *.wav
หรือ
mp3gain -c -r *mp3
เกลี่ยเสียง ลดเสียงทุ้มลง 18db เพิ่มเสียงแหลม 3db
sox --norm INFILE.wav OUTFILE.wav bass -24 treble +3
cdda2wav -D /dev/cdrom -t TRACK 01
update
$ 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
$ ffmpeg -i sound.rm sound.wav
รูปแบบที่เลือกใช้
เลือกใช้ mp3 เนื่องจากแพร่หลาย และไม่ผูกติดกับวินโดวส์
เลือกใช้ตัวแปลงคือ lame เนื่องจากเสียงนุ่มนวลกว่า
$ lame -h -b 16 INFILE.XXX OUTFILE.mp3
$ lame -h -b 128 INFILE.XXX OUTFILE.mp3
$ 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
$ lame --mp3input -m m --resample 24 -h INFILE.mp3 OUTFILE.mp3
$ ffmpeg -i INFILE.mpg -f 3gp -vcodec mpeg4 -b 150000 -s 320x240 -r 18 -acodec libfaac -ab 64000 -ar 24000 -ac 2 OUTFILE.3gp
หมายเหตุ
การติดตั้งแพกเกจที่เกี่ยวข้อง
$ sudo aptitude install ffmpeg
$ sudo aptitude install mpg321
$ sudo aptitude install madplay
$ sudo aptitude install lame
$ sudo aptitude install vorbis-tools
$ sudo aptitude install mplayer
$ sudo aptitude install normalize-audio
$ sudo aptitude install cdda2wav
บันทึกการซ่อม และการแก้ไขแพกเกจเสียหาย
คราวก่อนบันทึก การติดตั้งหลายระบบปฏิบัติการ ปรากฎว่าเครื่องมีปัญหานิดหน่อย เลยต้องติดตั้ง 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
Error Opening terminal: Bterm
ให้ใช้คำสั่งว่า
# export TERM=linux
แล้วก็จะสามารถทำงานต่อจนจบได้ครับ
ทดลองซ่อม 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
ขั้นตอนมีดังนี้
# fdisk -l
# 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
# chroot /mnt/root
# mount /dev/hda2 /boot
ถ้าเป็น sata เปลี่ยนจาก hda2 เป็น sda2
# grub-install /dev/hda
# reboot
สมมุติว่าใช้เดเบียนรุ่นตั้งแต่ 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
เป็นต้น)
เมื่อเสร็จแล้ว ก็สามารถบูตเพื่อใช้งานไบออสใหม่ได้เลย
เอามาจาก
ล่าสุดใช้แพ็คเกจ flashrom แทนได้แล้ว
ติดตั้ง
# aptitude install flashrom
เก็บรอมเก่า
# flashrom -r OLD.bin
แฟลชรอมใหม่
# flashrom -w NEW.bin
เอามาจาก
เนื่องจากปัญหา 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
ควรสงสัยไว้ก่อนว่า ข้อเขียนนี้ต้องมีที่ผิดพลาดแน่นอน หากจะทำตาม ควรมีความรู้เรื่องลินุกซ์พอควรที่จะแก้ปัญหาที่เกิดจากการผิดพลาดในข้อเขียนได้
จะติดตั้งเซิร์ฟเวอร์แบบ all-in-one สำหรับใช้ในหน่วยงานเล็ก ๆ โดย
(ดังนั้น เวลาติดตั้ง ควรให้มีเนื้อที่มากที่สุด)
ตั้งชื่อใน /sys1 ให้เลียนแบบกับไดเรกทอรี่จริง เช่น /var/www ต้องสำรองข้อมูลทุกวัน ก็เป็น /sys1/sysb/var/www เป็นต้น
รายการที่ต้องทำคือ
สมมุติว่า
หลังจากผ่านการติดตั้งแบบ 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
ปรับตั้ง 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
ติดตั้ง
# 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
เอาไว้เก็บแพกเกจเดเบียน สำหรับเครือข่ายภายใน อันนี้เนื้อหาไม่ค่อยจำเป็น จะเอาเก็บไว้ที่ /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 ...
เพื่อให้ประหยัดแบนด์วิธสูงสุด ควรติดตั้งพร๊อกซี่สำหรับภายในด้วย
ติดตั้ง
# 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 ...
สมมุติว่าได้สมัครเป็นสมาชิก ddns client ไว้ที่ www.zoneedit.com และ www.everydns.net ไว้เรียบร้อยแล้ว
การนี้เราจะใช้ทั้งคู่ในการเป็น name server ให้เรา กันเหนียวไว้ เวลาอันไหนตาย อีกอันจะได้ทำหน้าที่แทน
จากประวัติพบว่า zoneedit เสถียรและอัปเดตเร็วกว่า เราจึงให้ขึ้นเป็น primary
งานที่ต้องทำคือ
# 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
ตั้งชื่อว่า 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
สร้างไฟล์ข้อมูลโดเมนเอาไว้ที่ /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:------------------
เสร็จแล้ว
จะทำให้สามารถใช้งานได้ทั้งภายใน และภายนอก (ภายนอกไม่ค่อยจำเป็น แต่ติดตั้งไว้เผื่อในอนาคตอาจเพิ่มการ lookup จากเซิร์ฟเวอร์ของเราที่อยู่ภายนอก)
โดยจะแยกไอพีภายในและภายนอกเป็น 2 กลุ่ม
และสร้างกรงขังด้วย (chroot jail)
สมมุติว่า
ติดตั้งแพกเกจ แล้วสั่งหยุดการทำงาน
# 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
# 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 ของ 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
จบจริง ๆ แล้ว
อ้างอิง
ท่อนนี้ไม่มีอะไรมาก เพียงแต่ต้องการรวมศูนย์ข้อมูลไว้ในไฟล์เดียว เพื่อต้องการให้ดูง่ายตรวจสอบง่ายและลดโอกาสผิดพลาดในการแก้ไขไฟล์โซนและไฟล์รีเวิร์สโซน
จึงสร้างเป็นสคริปต์เล็ก ๆ ขึ้นมาเพื่อใช้อ่านค่าข้อมูลที่เราสร้างเอาไว้ แล้วผลิตไฟล์โซนต่าง ๆ ออกมาให้ครบตามที่เราตั้งไว้
สคริปต์ตั้งชื่อว่า 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
ปรับสคริปต์นิดหน่อย
# 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
หัวข้อนี้สบายมาก เพราะไม่รู้เรื่องเลย ลอกมาอย่างเดียว จาก 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 ที่ mail.google.com
ปรับให้สามารถส่งอีเมลจากเครื่องเราได้
การตั้งค่า -> บัญชี -> ส่งอีเมลเป็นแบบ เพิ่มที่อยู่อีเมลอื่นอีก
ทำตามขั้นตอน แล้วนำตัวเลขยืนยันมากรอกในช่อง เป็นอันเสร็จ
อ้างอิง
เตรียมสร้างไดเรกทอรี่ข้อมูลให้ 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
ต้องติดตั้งไปทีละตัว เพราะแพกเกจ 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 ...
เสร็จแล้ว
ตั้งให้รากของเว็บ 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 ...
เสร็จ
ที่ทำเป็น คือใช้ proftpd (ตัวอื่นทำ VirtualHost ไม่เป็น หรืออาจทำยาก)
เนื่องจากเรามี 2 โดเมน คือ example.com และ example.org เราต้องการทำ ftp ทั้งสองโดเมน จึงต้องทำเรื่อง VirtualHost
แต่เนื่องจากระบบ VirtualHost ใน proftpd ไม่เหมือนใน apache2 เสียทีเดียว เนื่องจากเขาไม่ถือชื่อโฮสต์เป็นสำคัญ แต่จะถือไอพีและพอร์ตเป็นตัวจำแนกแทน ดังนั้นถ้าเราต้องการแยกไดเรกทอรี่ระหว่าง example.com และ example.org เราจะต้องใช้พอร์ตเป็นตัวแยกแทน
สมมุติฐานมีดังนี้คือ
เตรียมผู้ใช้และกลุ่ม
เพิ่มกลุ่ม 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
เสร็จแล้ว
ผู้ใช้งานสามารถใช้งานผ่าน
จะสร้าง 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
เสร้จ
สุดท้ายเป็นการเก็บกวาดเล็กน้อย
งานที่ทำคือ...
จุดประสงค์คือการรวมศูนย์การใช้งาน 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 ...
เสร็จหมดแล้ว
จากเรื่อง 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 ...
งานเกี่ยวกับ Gnome
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 อยู่อย่างคือ หาเอกสารยากชะมัด ยังดีที่ตั้งชื่อในการปรับตั้งไม่ยากเท่าไหร่ พอเดา ๆ ออก)
เนื่องจากในการเปิดไฟล์ tif นั้น Gnome เลือกใช้ Image Viewer (eog) เป็นค่าปริยาย
แต่สำหรับไฟล์ tif แบบหลายหน้า พบว่า Image Viewer ไม่สามารถดูหน้าถัดไปได้ คงดูได้เพียงหน้าแรกเท่านั้น
ดังนั้นเราจะเปลี่ยนค่าปริยายสำหรับการเปิดไฟล์ tif ไปเป็น Document Viewer (evince) แทน
ทำได้โดย
คลิกขวาที่ไฟล์ภาพ -> Properties -> [TAB]Open With ->
เปลี่ยนค่าปริยายมาเป็น Document Viewer -> Close
เรียบร้อยแล้ว
สำหรับเดสก์ทอปที่ใช้ 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 เพิ่มเติม
sudo gconftool-2 --direct \
--config-source xml:readwrite:/etc/gconf/gconf.xml.defaults \
--type bool \
--set /app/gnome-screensaver/lock_enabled false
ลองบน เดเบียน 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
อ้างอิง
วิธีแก้ 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 ได้แล้ว
เอามาจาก
$ 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]
$ 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?
บันทึกผลของการเลือกใช้ SELECT
แบบต่าง ๆ
เดเบียน Etch, Postgresql 8.1
มีตารางสองตาราง
การค้นหาเพียงศัพท์เดียว ลักษณะจะเหมือนกัน คำสั่งคือ
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
หมายเหตุ
ตารางเพิ่มเติมจากตัวอย่างคือ
ความรู้ 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
ขออนุญาตบันทึกการทดลองเพื่อเอาไว้ดูครับ
สมมุติถ้ามีตาราง 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
วิธีหลังดูจะช้ากว่า แต่ก็น่าจะเขียนโค๊ดง่ายกว่า
รวมหมวดฟอนต์
เอาไฟล์มาสรุปให้ดาวน์โหลดได้ง่ายขึ้นครับ
$ sudo ln -sf 65-z-ttfonts.conf /etc/fonts/conf.d $ fc-cache -fv
$ 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
***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 อีกทีนึงAngsima; Angsana New
ทั้งในหมวด Western text font และ CTL font
จะให้ผลที่แน่นอนกว่าการใช้การทดแทนฟอนต์ใน openoffice เอง นอกจากนี้ยังสามารถเปิดใช้งานทั้งใน Microsoft Word และ OpenOffice Writer ได้โดยไม่เกิดปัญหาครับ
Attachment | Size |
---|---|
rtv.zip | 45.32 KB |
จำเป็นต้องใช้ฟอนต์เก่าจากวินโดวส์มาทำอาร์ตเวิร์ก จึงต้องเตรียมฟอนต์เก่าให้สามารถแสดงผลในลินุกซ์ได้
ขั้นตอนคร่าว ๆ มีดังนี้
$ sudo aptitude install fontforge
$ cp ./thaifonts-scalable-0.4.5/nectec template.sfd
$ fontforge template.sfd
Element -> Merge Font...
จะได้ชุดอักษรจากฟอนต์เก่ามาทับบนเทมเพลตElement -> Font Info...
Encoding -> Reencode -> ISO 16041-1 (UNICODE, BMP)
คลิกขวา -> Add anchor
File -> Generate Fonts...
ฟอนต์ที่ได้จะสามารถใช้ได้กับลินุกซ์ที่ส่วนใหญ่จะใช้รหัสฟอนต์เป็นยูนิโค๊ด และมีความสามารถในการแก้สระลอยได้ในตัวเอง
หมายเหตุ
ช่วงนี้กำลังทดลองเอา 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
ฟอนต์ตัวที่สองตั้งชื่อว่า บรมลีลา - Bromlila
จุดประสงค์จะใช้แทน Browallia (โดยไม่เสียรูปแบบ)
ใช้ฐานจากฟอนต์ Garuda ยกเว้นตัวหนาที่เป็นภาษาอังกฤษ ตัว Garuda-Bold ยังไม่หนาเท่า Browallia ผมจึงเอามาจากตัวหนา sans ของ Freefont แทน
มีข้อสังเกตุคือ ตัว Browallia กับ Garuda เมื่อปรับสัดส่วนแล้ว มีลายเส้นที่แทบจะนาบกันสนิท
เที่ยวนี้ขี้เกียจทำ Screenshot เพราะตัวเหมือนกันเป๊ะ ๆ ดาวน์โหลดเลยดีกว่า
ดาวน์โหลดได้ดังนี้
ฟอนต์ตัวที่สามตั้งชื่อว่า ครดา - Corada
จุดประสงค์จะใช้แทน Cordia (โดยไม่เสียรูปแบบ)
ชื่อฟอนต์ไม่มีคำแปล เพราะพยายามให้อักขระสามตัวแรกเหมือนกัน เพื่อให้เลือกฟอนต์ได้ง่าย จำง่าย
ใช้ฐานจากฟอนต์ Loma แต่ยากหน่อยตรงที่ฐานของฟอนต์โลมาคือ FreeSerif ไม่มีขนาดความหนาแบบ Light เลยต้องเสียเวลาสร้างตัวภาษาอังกฤษแบบ Light ด้วย และใช้ขนาดปกติ (Book) ของ FreeSerif เป็น Bold ของ Corada แทน
ดังนั้นสำหรับเที่ยวนี้ ในส่วนของอักขระภาษาอังกฤษจึงไม่ค่อยเรียบร้อยเท่าที่ควร เพราะสร้างขึ้นเอง
พยายามให้หน้าตาฟอนต์ออกมาอยู่ระหว่างฟอนต์คอร์เดีย และสุดยอดฟอนต์แห่งยุค คือ ดีบีฟองน้ำ
ดาวน์โหลดได้ดังนี้
สรุปงานทำฟอนต์ทดแทนฟอนต์ของวินโดวส์ทั้งสามตัว คือ
สร้างด้วยการปรับความกว้างของฟอนต์แต่ละอักขระ ให้เท่ากับฟอนต์วินโดวส์ และลบ kerning ของฟอนต์ต้นแบบออกให้หมด ส่วนความสูงของฟอนต์ ไม่ได้ปรับความสูงของสระให้เท่าของวินโดวส์ แต่ใช้วิธีสร้างอักขระพิเศษเพิ่มมาตัวนึง ให้จุดสูงสุดของอักขระเท่ากับไม้โทสูงของวินโดวส์ (ซึ่งสูงที่สุด) และให้จุดต่ำสุดของอักขระนี้ ให้เท่ากับสระอูที่ต่ำที่สุดของวินโดวส์
รวมทั้งสร้างไฟล์คอนฟิกให้ฟอนต์ ให้แทนชื่อฟอนต์จากวินโดวส์ตามรายละเอียดข้างบน ไฟล์นี้ตั้งชื่อว่า 65-2-thaifont-abc.conf ซึ่งจะต้องนำไปใส่ไว้ที่ไดเรกทอรี่ /etc/fonts/conf.d
เมื่อทำครบทุกอย่างแล้ว เราก็จะสามารถเปิดไฟล์ตระกูลออฟฟิศจากวินโดวส์ โดยไม่เสียรูปแบบ
สามารถดาวน์โหลดไฟล์รวมได้ดังนี้
ทดลองทำฟอนต์ใช้เองโดยใช้ไฟล์ฟอนต์ Norasi.sfd ของ NECTEC เป็นต้นแบบ
จุดประสงค์เพื่อทำฟอนต์แบบ Serif ที่ใช้รหัสอักขระยูนิโค๊ด จะนำมาแทนฟอนต์ Angsana ของวินโดวส์
ตั้งชื่อว่า นรบุตร (Noraputta)
ตั้งใจให้ออกมาเป็นลูกผสมของ Norasi กับ Angsana
ยังไม่ค่อยสวยเหมือนที่มืออาชีพเขาทำกัน ฮิ้นต้งฮิ้นติ้งไม่รู้เรื่องเลย เอาแค่พอใช้งานได้ตามสไตล์
ฝากลองทดสอบและใช้ฟรีโดยไม่มีเงื่อนไขครับ
update
ลองทำ Serif อีกฟอนต์นึง เพื่อให้สมดุลย์กับ Sans serif โดยเอาต้นแบบมาจากฟอนต์ Gentium ซึ่งเป็น Serif ที่ดูสบายตา กึ่งทางการกึ่งลำลอง จึงตั้งชื่อให้คล้าย ๆ กัน ว่า เจนทินี - Gentini
ทำไปก็งงไป ทำให้ทราบว่างานออกแบบฟอนต์นี่ยากจริง ๆ ผมเองยังมือไม่ถึง จึงอ่านบุคลิกของฟอนต์ Gentium ไม่ออก เลยทำแค่ฟอนต์ขนาด book แค่ตัวเดียว
ดาวน์โหลด
ไฟล์รวม Gentini.tar.gz
ไม่ใช่งานใหม่ แต่เป็นการเอา โลมาบุตร มาชำระล้าง ปรับปรุงเล็กน้อย แต่พอทำไป ๆ มันชักเพี้ยนมาทาง อัมพุช
เลยเปลี่ยนชื่อเสียเลยดีกว่า เพื่อไม่ให้สับสน และไม่ให้ทับซ้อนกับโลมา
ตั้งชื่อว่า ปัณถกะ - Pantaka แปลว่าชำนาญทาง
ทำแล้วรู้สึกว่าอ่านง่ายกว่า วีรชาติ เลยบรรจุเข้าประจำการเป็นฟอนต์หน้าจอแทนวีรชาติ
ดาวน์โหลดไฟล์รวมดังนี้
update
หลังจากทำฟอนต์ 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
ถ้าไม่ประสบเหตุอื่นอีก ฟอนต์นี้คงเป็นฟอนต์สุดท้ายแล้วครับ
ทำฟอนต์หน้าจอเพิ่ม ตั้งชื่อว่า วีรชาติ เพราะเอามาจากฟอนต์ Vera ของ BitStream
ตัวบางไปหน่อย line spacing แคบไปนิด Hinting ยังแย่อยู่
ช่วยทดสอบและใช้ได้ฟรีเหมือนเดิมครับ
ดาวน์โหลดซอร์ส
(ไฟล์ ttf สามารถดาวน์โหลดที่ หน้ารวม Fonts)
การทดลองที่ไม่สำเร็จ
ทดลองทำ Truetype Instruction ด้วยเมนู AutoInstruction
แก้ไขการที่โปรแกรม Fontforge ทำ Truetype Hint ไม่สมบูรณ์ ด้วยการปรับละเอียดที่ส่วนโค้งของตัวอักษรแทน
ไม่สำเร็จเนื่องจาก คุณภาพพอดูได้แค่ 11 ปอยนต์ ที่เหลือนอกจากนี้ ดูไม่ได้เลย
ไฟล์ชุดนี้ยังไม่สมบูรณ์ แต่ถ้าหากต้องการทดลองใช้งานดู ไม่จำเป็นต้องแก้ไขไฟล์ 65-ttfonts.conf เนื่องจากมีการ Hint อยู่ในฟอนต์เรียบร้อยแล้ว
update
<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 ก็จะดูคมแข็งขึ้นครับ
(ดูรายละเอียดการปรับตั้งที่ ลองทำฟอนต์ใช้เอง - แถม)
update
โพสต์นี้ได้ความรู้มาจากคุณสมเจตน์ ท่านช่วยทดสอบให้ ได้ความว่า
ฟอนต์ชุดนี้ หากจะนำไปใช้ให้ได้ผลดีมีความคมชัด ต้องกำหนด 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
$ sudo ln -sf /etc/fonts/conf.avail/10-autohint.conf /etc/fonts/conf.d
คราวก่อนทำฟอนต์โลมาบุตร ดูแล้วยังรู้สึกว่าอ่านยาก (ไม่รู้ว่าเป็นที่ hinting หรือเปล่า)
คราวนี้ลองเอาอีกฟอนต์นึงคือฟอนต์ครุฑ (Garuda) มาปรับให้เหมาะกับการแสดงผลบนจอคอมพิวเตอร์
โดยการลดขนาดสระและวรรณยุกต์ และปรับความกว้างของบรรทัดให้แคบเข้า
เอาไว้เป็นตัวเผื่อเลือกสำหรับการแสดงผลของฟอนต์ sans-serif
ตั้งชื่อว่าฟอนต์ ครุฑทัศน์ (Garudatas)
ดาวน์โหลด
update
ทำเพิ่มเติมสำหรับ serif ด้วย คือฟอนต์นรสีห์ เป็น นรสีห์ทัศน์
ดาวน์โหลด
หมายเหตุ
ไม่เหมาะสำหรับงานเอกสารที่ต้องพิมพ์ออกมา สำหรับงานพิมพ์ใช้ฟอนต์ Garuda และ Norasi เดิมสวยอยู่แล้ว
โปรแกรม FontForge เป็นโปรแกรมที่ใช้ออกแบบฟอนต์ของลินุกซ์ที่ได้รับความนิยม มีความสามารถสูง แต่มีข้อเสียคือ ทำ AutoHint ได้แย่มาก เพราะโปรแกรมจะกันเหนียวด้วยการกำหนดแถบการ Hint ไว้กว้างเกินไป
การกำหนดแถบการ Hint ไว้กว้าง ทำให้เวลาแสดงฟอนต์ทางจอภาพ อักษร ก ไก่ จะดูเตี้ยกว่าอักษร ข ไข่ ซึ่งไม่ถูก AutoHint ไว้กว้างแบบนั้น
ถ้าจะทำให้ได้ผลการ Hint ที่ดี แบบที่เรา ๆ ท่าน ๆ สามารถทำได้เอง โดยไม่ต้องใช้ฝีมือระดับเทพ ก็คือการ Hint เองด้วยมือ
ซึ่งทำได้โดยการกำหนดสองจุดคู่ที่จะทำการ Hint แล้วสั่ง Add HHint หรือ Add VHint ตามลักษณะของส่วนโค้งตัวอักษรนั้น ๆ
วิธีนี้ทำให้ได้ตัวอักษรที่ดูสูงเท่ากัน แต่ปัญหาคือโปรแกรม FontForge ไม่ทำปุ่มลัดสำหรับคำสั่ง Add HHint และ Add VHint ไว้ด้วย
งานที่จะต้องมานั่งเลือกจุดคู่แล้วไล่คลิกเมนูสั่งงาน นับว่าเปลืองเวลาเกินไป
แต่ยังโชคดีที่โปรแกรมถูกออกแบบให้สามารถกำหนดปุ่มลัดได้เอง เพียงแต่ต้องออกแรงหน่อยนึงด้วยการคอมไพล์แพกเกจเอง
เราจะกำหนดปุ่มลัดเอง ทำให้การทำงานเร็วขึ้นมาก
เริ่มด้วยการติดตั้งแพกเกจที่จำเป็นในการคอมไพล์ก่อน
$ 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 ได้คือ
เพิ่มเติม
Element -> Font Info ... -> เลือก Quadratic Splines
หลังจากนั้นจึงทำ AutoHint และ AutoInstruction แล้วจึงสั่งผลิตฟอนต์ทรูไทป์
เลือกช่วงฟอนต์ที่ต้องการ -> Hints -> AutoHint
Hints -> AutoInstr
Files -> Generates Fonts
ทดลองแล้วได้ผลดีพอควร
ถ้าผ่านข้อกำหนดดังกล่าว การ Hint จะได้ผลดีที่สุด
ค้นไปค้นมา ไปพบฟอนต์ที่เป็นฟรีแวร์และ gpl สรุปได้ดังนี้
มีฟอนต์สวย ๆ มากจริง ๆ
ทำฟอนต์เอง มาตายตรง hinting มือไม่ถึง :P เวลาไม่เอื้อให้ศึกษา
แต่ก็ยังชอบใจ โลมาบุตรอยู่ดี ;D
ฟอนต์ 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/
ศึกษาการทำ TrueType Hinting Instruction
การทำ Truetype Hinting Instructions
$ ttx FONT.ttf
ฟอนต์รุ่นนี้เป็นรุ่นลัดคิว
'ccmp' Glyph Composition/Decomposition in Thai lookup 2"
เล็กน้อย เพื่อให้แสดงระดับวรรณยุกต์ได้ถูกต้อง เราจะให้อยู่ในชื่อ DroidSansThai... <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> ...
ดาวน์โหลดไฟล์ฟอนต์ (ล่าสุดคือ 20100812)
ดาวน์โหลดไฟล์ซอร์ส
ทดสอบภาษาลาว
ภาพตัวอย่าง
ภาพจากบราวเซอร์ epiphany
ตัวธรรมดา
ตัวหนา
ลัดคิว #3
ทำฟอนต์ DroidSansMono เพิ่ม โดยทำทั้งภาษาไทยและลาว เสียดายที่ต้นฉบับไม่มีตัวหนา เลยทำแค่ตัวธรรมดาอย่างเดียว
ทำเป็นฟอนต์แบบ Monospace แท้ ๆ โดยใช้ต้นแบบตาราง Lookup จากฟอนต์ TlwgTypo
ดาวน์โหลดไฟล์ฟอนต์ (ล่าสุดคือ 20100917)
ดาวน์โหลดไฟล์ซอร์ส
ทดสอบภาษาลาว
ภาพตัวอย่าง
ภาพจากบราวเซอร์ epiphany
ตัวธรรมดา
ลัดคิว #2
ทำฟอนต์ DroidSerif เพิ่ม ถึงจะมีที่ใช้น้อย แต่ก็ทำไว้เพื่อให้แสดงผลได้สมบูรณ์ ทำทั้งภาษาไทยและลาว
Droid Serif ThaiLao
และ font family preferred เป็น Droid Serif
ดังนั้น ถ้าเราติดตั้งฟอนต์ fonts-droid จะสามารถใช้ภาษาไทยและลาวจากฟอนต์ Droid Serif ThaiLao ได้ภายใต้ชื่อ Droid Serif (ยกเว้น Inkscape ต้องใช้ Droid Serif Web แทน)24!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_` Aabcdefghijklmnopqrstuvwxyz{|}~ mrnrtodocode ṃāñūṭḍṇṅ
aกัขิฃีคึฅืฆุงูจฺฉ็ช่ซ้ฌ๊ญ๋ญุฎ์ฏํฐ๎ฐูฑั่ฒิ้ณี๊ดื๋ติ์ถะทาธำน่ำเบี่แปิํโผใฝ่ไพฟ้ภมยรฤๅลฦวศษสหฬอฮฯๆ ฿๏๐๑๒๓๔๕ ๖๗๘๙๚๛ ก่ป่ฝ้ฟ๊ฬ๋ คฅศ ดตฒ ญฐญานํ ญฐญานํ โสตฺถิํ
ກະຂັຄາງ່ຳຈິ່ຊີ້ຍຶ໊ດື໋ຕຸ່ຖູ້ທົ໊ນຼ໋ເບແປໂຜໃຝໄພຟ່ມ້ຢ໊ຣ໋ລ໌ວໍສຫອຮຯຽໆ ໐໑໒໓໔໕໖໗໘໙ ໜໝໞໟ
24!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_` Aabcdefghijklmnopqrstuvwxyz{|}~ mrnrtodocode ṃāñūṭḍṇṅ
aกัขิฃีคึฅืฆุงูจฺฉ็ช่ซ้ฌ๊ญ๋ญุฎ์ฏํฐ๎ฐูฑั่ฒิ้ณี๊ดื๋ติ์ถะทาธำน่ำเบี่แปิํโผใฝ่ไพฟ้ภมยรฤๅลฦวศษสหฬอฮฯๆ ฿๏๐๑๒๓๔๕ ๖๗๘๙๚๛ ก่ป่ฝ้ฟ๊ฬ๋ คฅศ ดตฒ ญฐญานํ ญฐญานํ โสตฺถิํ
ກະຂັຄາງ່ຳຈິ່ຊີ້ຍຶ໊ດື໋ຕຸ່ຖູ້ທົ໊ນຼ໋ເບແປໂຜໃຝໄພຟ່ມ້ຢ໊ຣ໋ລ໌ວໍສຫອຮຯຽໆ ໐໑໒໓໔໕໖໗໘໙ ໜໝໞໟ
24!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_` Aabcdefghijklmnopqrstuvwxyz{|}~ mrnrtodocode ṃāñūṭḍṇṅ
aกัขิฃีคึฅืฆุงูจฺฉ็ช่ซ้ฌ๊ญ๋ญุฎ์ฏํฐ๎ฐูฑั่ฒิ้ณี๊ดื๋ติ์ถะทาธำน่ำเบี่แปิํโผใฝ่ไพฟ้ภมยรฤๅลฦวศษสหฬอฮฯๆ ฿๏๐๑๒๓๔๕ ๖๗๘๙๚๛ ก่ป่ฝ้ฟ๊ฬ๋ คฅศ ดตฒ ญฐญานํ ญฐญานํ โสตฺถิํ
ກະຂັຄາງ່ຳຈິ່ຊີ້ຍຶ໊ດື໋ຕຸ່ຖູ້ທົ໊ນຼ໋ເບແປໂຜໃຝໄພຟ່ມ້ຢ໊ຣ໋ລ໌ວໍສຫອຮຯຽໆ ໐໑໒໓໔໕໖໗໘໙ ໜໝໞໟ
24!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_` Aabcdefghijklmnopqrstuvwxyz{|}~ mrnrtodocode ṃāñūṭḍṇṅ
aกัขิฃีคึฅืฆุงูจฺฉ็ช่ซ้ฌ๊ญ๋ญุฎ์ฏํฐ๎ฐูฑั่ฒิ้ณี๊ดื๋ติ์ถะทาธำน่ำเบี่แปิํโผใฝ่ไพฟ้ภมยรฤๅลฦวศษสหฬอฮฯๆ ฿๏๐๑๒๓๔๕ ๖๗๘๙๚๛ ก่ป่ฝ้ฟ๊ฬ๋ คฅศ ดตฒ ญฐญานํ ญฐญานํ โสตฺถิํ
ກະຂັຄາງ່ຳຈິ່ຊີ້ຍຶ໊ດື໋ຕຸ່ຖູ້ທົ໊ນຼ໋ເບແປໂຜໃຝໄພຟ່ມ້ຢ໊ຣ໋ລ໌ວໍສຫອຮຯຽໆ ໐໑໒໓໔໕໖໗໘໙ ໜໝໞໟ
24!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_` Aabcdefghijklmnopqrstuvwxyz{|}~ mrnrtodocode ṃāñūṭḍṇṅ
aกัขิฃีคึฅืฆุงูจฺฉ็ช่ซ้ฌ๊ญ๋ญุฎ์ฏํฐ๎ฐูฑั่ฒิ้ณี๊ดื๋ติ์ถะทาธำน่ำเบี่แปิํโผใฝ่ไพฟ้ภมยรฤๅลฦวศษสหฬอฮฯๆ ฿๏๐๑๒๓๔๕ ๖๗๘๙๚๛ ก่ป่ฝ้ฟ๊ฬ๋ คฅศ ดตฒ ญฐญานํ ญฐญานํ โสตฺถิํ
ກະຂັຄາງ່ຳຈິ່ຊີ້ຍຶ໊ດື໋ຕຸ່ຖູ້ທົ໊ນຼ໋ເບແປໂຜໃຝໄພຟ່ມ້ຢ໊ຣ໋ລ໌ວໍສຫອຮຯຽໆ ໐໑໒໓໔໕໖໗໘໙ ໜໝໞໟ
24!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_` Aabcdefghijklmnopqrstuvwxyz{|}~ mrnrtodocode ṃāñūṭḍṇṅ
aกัขิฃีคึฅืฆุงูจฺฉ็ช่ซ้ฌ๊ญ๋ญุฎ์ฏํฐ๎ฐูฑั่ฒิ้ณี๊ดื๋ติ์ถะทาธำน่ำเบี่แปิํโผใฝ่ไพฟ้ภมยรฤๅลฦวศษสหฬอฮฯๆ ฿๏๐๑๒๓๔๕ ๖๗๘๙๚๛ ก่ป่ฝ้ฟ๊ฬ๋ คฅศ ดตฒ ญฐญานํ ญฐญานํ โสตฺถิํ
ກະຂັຄາງ່ຳຈິ່ຊີ້ຍຶ໊ດື໋ຕຸ່ຖູ້ທົ໊ນຼ໋ເບແປໂຜໃຝໄພຟ່ມ້ຢ໊ຣ໋ລ໌ວໍສຫອຮຯຽໆ ໐໑໒໓໔໕໖໗໘໙ ໜໝໞໟ
เอา Lomaputta - โลมาบุตร ซึ่งเอามาจากฟอนต์โลมาของ ltn: ThaiFonts-Scalable มาทำเรื่อง hinting
ตอนทำ hinting จริง ๆ ต้องปรับลายเส้นจากเดิมเยอะเหมือนกัน
ดาวน์โหลดไฟล์ฟอนต์(20100602)
ดาวน์โหลดไฟล์ซอร์ส
SROUND
Lomaputta-src-20090506.tar.gzภาพตัวอย่าง
จากบราวเซอร์ epiphany
ตัวธรรมดา
ตัวหนา
เอาฟอนต์นรสีห์จาก ltn: ThaiFonts-Scalable มาทำเรื่อง hinting
แทบไม่เปลี่ยนลายเส้นเลย คงรูปเดิมให้มากที่สุด แปลงแต่จุด เพื่อให้เหมาะกับการทำ hinting
ดาวน์โหลดไฟล์ฟอนต์ (20100625)
ดาวน์โหลดไฟล์ซอร์ส
ภาพตัวอย่าง
จาก Epiphany Browser
ตัวธรรมดา
ตัวหนา
เอาฟอนต์ Roboto มาใส่ภาษาไทย โดยเอาโครงฟอนต์ Roboto (20%) + Droid Sans (30%) + Garuda (50%)
จุดประสงค์คือจะนำไปใช้ทดสอบ Android ICS
Roboto
ใช้งานภายใต้ชื่อ Roboto แต่ต้องมีการลงแพ็กเกจหรือปรับตั้งอื่น ดังนี้fonts-roboto
แล้วสามารถใช้งานผ่านชื่อฟอนต์ Roboto ได้เลย (ยกเว้น Inkscape ให้ใช้ Roboto Web แทน)/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> ...
24!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_` Aabcdefghijklmnopqrstuvwxyz{|}~ mrnrtodocode ṃāñūṭḍṇṅ
aกัขิฃีคึฅืฆุงูจฺฉ็ช่ซ้ฌ๊ญ๋ญุฎ์ฏํฐ๎ฐูฑั่ฒิ้ณี๊ดื๋ติ์ถะทาธำน่ำเบี่แปิํโผใฝ่ไพฟ้ภมยรฤๅลฦวศษสหฬอฮฯๆ ฿๏๐๑๒๓๔๕ ๖๗๘๙๚๛ ก่ป่ฝ้ฟ๊ฬ๋ คฅศ ดตฒ ญฐญานํ ญฐญานํ โสตฺถิํ
ກະຂັຄາງ່ຳຈິ່ຊີ້ຍຶ໊ດື໋ຕຸ່ຖູ້ທົ໊ນຼ໋ເບແປໂຜໃຝໄພຟ່ມ້ຢ໊ຣ໋ລ໌ວໍສຫອຮຯຽໆ ໐໑໒໓໔໕໖໗໘໙ ໜໝໞໟ
24!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_` Aabcdefghijklmnopqrstuvwxyz{|}~ mrnrtodocode ṃāñūṭḍṇṅ
aกัขิฃีคึฅืฆุงูจฺฉ็ช่ซ้ฌ๊ญ๋ญุฎ์ฏํฐ๎ฐูฑั่ฒิ้ณี๊ดื๋ติ์ถะทาธำน่ำเบี่แปิํโผใฝ่ไพฟ้ภมยรฤๅลฦวศษสหฬอฮฯๆ ฿๏๐๑๒๓๔๕ ๖๗๘๙๚๛ ก่ป่ฝ้ฟ๊ฬ๋ คฅศ ดตฒ ญฐญานํ ญฐญานํ โสตฺถิํ
ກະຂັຄາງ່ຳຈິ່ຊີ້ຍຶ໊ດື໋ຕຸ່ຖູ້ທົ໊ນຼ໋ເບແປໂຜໃຝໄພຟ່ມ້ຢ໊ຣ໋ລ໌ວໍສຫອຮຯຽໆ ໐໑໒໓໔໕໖໗໘໙ ໜໝໞໟ
24!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_` Aabcdefghijklmnopqrstuvwxyz{|}~ mrnrtodocode ṃāñūṭḍṇṅ
aกัขิฃีคึฅืฆุงูจฺฉ็ช่ซ้ฌ๊ญ๋ญุฎ์ฏํฐ๎ฐูฑั่ฒิ้ณี๊ดื๋ติ์ถะทาธำน่ำเบี่แปิํโผใฝ่ไพฟ้ภมยรฤๅลฦวศษสหฬอฮฯๆ ฿๏๐๑๒๓๔๕ ๖๗๘๙๚๛ ก่ป่ฝ้ฟ๊ฬ๋ คฅศ ดตฒ ญฐญานํ ญฐญานํ โสตฺถิํ
ກະຂັຄາງ່ຳຈິ່ຊີ້ຍຶ໊ດື໋ຕຸ່ຖູ້ທົ໊ນຼ໋ເບແປໂຜໃຝໄພຟ່ມ້ຢ໊ຣ໋ລ໌ວໍສຫອຮຯຽໆ ໐໑໒໓໔໕໖໗໘໙ ໜໝໞໟ
24!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_` Aabcdefghijklmnopqrstuvwxyz{|}~ mrnrtodocode ṃāñūṭḍṇṅ
aกัขิฃีคึฅืฆุงูจฺฉ็ช่ซ้ฌ๊ญ๋ญุฎ์ฏํฐ๎ฐูฑั่ฒิ้ณี๊ดื๋ติ์ถะทาธำน่ำเบี่แปิํโผใฝ่ไพฟ้ภมยรฤๅลฦวศษสหฬอฮฯๆ ฿๏๐๑๒๓๔๕ ๖๗๘๙๚๛ ก่ป่ฝ้ฟ๊ฬ๋ คฅศ ดตฒ ญฐญานํ ญฐญานํ โสตฺถิํ
ກະຂັຄາງ່ຳຈິ່ຊີ້ຍຶ໊ດື໋ຕຸ່ຖູ້ທົ໊ນຼ໋ເບແປໂຜໃຝໄພຟ່ມ້ຢ໊ຣ໋ລ໌ວໍສຫອຮຯຽໆ ໐໑໒໓໔໕໖໗໘໙ ໜໝໞໟ
24!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_` Aabcdefghijklmnopqrstuvwxyz{|}~ mrnrtodocode ṃāñūṭḍṇṅ
aกัขิฃีคึฅืฆุงูจฺฉ็ช่ซ้ฌ๊ญ๋ญุฎ์ฏํฐ๎ฐูฑั่ฒิ้ณี๊ดื๋ติ์ถะทาธำน่ำเบี่แปิํโผใฝ่ไพฟ้ภมยรฤๅลฦวศษสหฬอฮฯๆ ฿๏๐๑๒๓๔๕ ๖๗๘๙๚๛ ก่ป่ฝ้ฟ๊ฬ๋ คฅศ ดตฒ ญฐญานํ ญฐญานํ โสตฺถิํ
ກະຂັຄາງ່ຳຈິ່ຊີ້ຍຶ໊ດື໋ຕຸ່ຖູ້ທົ໊ນຼ໋ເບແປໂຜໃຝໄພຟ່ມ້ຢ໊ຣ໋ລ໌ວໍສຫອຮຯຽໆ ໐໑໒໓໔໕໖໗໘໙ ໜໝໞໟ
24!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_` Aabcdefghijklmnopqrstuvwxyz{|}~ mrnrtodocode ṃāñūṭḍṇṅ
aกัขิฃีคึฅืฆุงูจฺฉ็ช่ซ้ฌ๊ญ๋ญุฎ์ฏํฐ๎ฐูฑั่ฒิ้ณี๊ดื๋ติ์ถะทาธำน่ำเบี่แปิํโผใฝ่ไพฟ้ภมยรฤๅลฦวศษสหฬอฮฯๆ ฿๏๐๑๒๓๔๕ ๖๗๘๙๚๛ ก่ป่ฝ้ฟ๊ฬ๋ คฅศ ดตฒ ญฐญานํ ญฐญานํ โสตฺถิํ
ກະຂັຄາງ່ຳຈິ່ຊີ້ຍຶ໊ດື໋ຕຸ່ຖູ້ທົ໊ນຼ໋ເບແປໂຜໃຝໄພຟ່ມ້ຢ໊ຣ໋ລ໌ວໍສຫອຮຯຽໆ ໐໑໒໓໔໕໖໗໘໙ ໜໝໞໟ
24!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_` Aabcdefghijklmnopqrstuvwxyz{|}~ mrnrtodocode ṃāñūṭḍṇṅ
aกัขิฃีคึฅืฆุงูจฺฉ็ช่ซ้ฌ๊ญ๋ญุฎ์ฏํฐ๎ฐูฑั่ฒิ้ณี๊ดื๋ติ์ถะทาธำน่ำเบี่แปิํโผใฝ่ไพฟ้ภมยรฤๅลฦวศษสหฬอฮฯๆ ฿๏๐๑๒๓๔๕ ๖๗๘๙๚๛ ก่ป่ฝ้ฟ๊ฬ๋ คฅศ ดตฒ ญฐญานํ ญฐญานํ โสตฺถิํ
ກະຂັຄາງ່ຳຈິ່ຊີ້ຍຶ໊ດື໋ຕຸ່ຖູ້ທົ໊ນຼ໋ເບແປໂຜໃຝໄພຟ່ມ້ຢ໊ຣ໋ລ໌ວໍສຫອຮຯຽໆ ໐໑໒໓໔໕໖໗໘໙ ໜໝໞໟ
24!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_` Aabcdefghijklmnopqrstuvwxyz{|}~ mrnrtodocode ṃāñūṭḍṇṅ
aกัขิฃีคึฅืฆุงูจฺฉ็ช่ซ้ฌ๊ญ๋ญุฎ์ฏํฐ๎ฐูฑั่ฒิ้ณี๊ดื๋ติ์ถะทาธำน่ำเบี่แปิํโผใฝ่ไพฟ้ภมยรฤๅลฦวศษสหฬอฮฯๆ ฿๏๐๑๒๓๔๕ ๖๗๘๙๚๛ ก่ป่ฝ้ฟ๊ฬ๋ คฅศ ดตฒ ญฐญานํ ญฐญานํ โสตฺถิํ
ກະຂັຄາງ່ຳຈິ່ຊີ້ຍຶ໊ດື໋ຕຸ່ຖູ້ທົ໊ນຼ໋ເບແປໂຜໃຝໄພຟ່ມ້ຢ໊ຣ໋ລ໌ວໍສຫອຮຯຽໆ ໐໑໒໓໔໕໖໗໘໙ ໜໝໞໟ
ทำฟอนต์เพื่อทดแทน Tahoma ตั้งชื่อว่า "เตยหอม" (Taeyhom)
พยายามทำให้คอมแพตกับ Tahoma 100% ยกเว้นตัวเลขไทย ของเดิมความกว้างไม่เท่ากัน แต่ของเราทำให้เท่ากันตามมาตรฐาน
ดาวน์โหลดไฟล์ฟอนต์ (ล่าสุดคือ 20100531)
ดาวน์โหลดไฟล์ซอร์ส
MD, MPPEM, SDPVTL, MUL, DIV, IF, EIF
) ยังงง ๆ อยู่DELTAC
)SROUND
ไปตามขนาด ppem ต่าง ๆ ทำให้ไม่จำเป็นต้องกำหนดค่า cvt ของกั้นหน้ากั้นหลังและความกว้างตัวอักษร (ถ้าใช้ได้ผลจะได้เอาไปใช้ในการสร้างฟอนต์ตัวต่อ ๆ ไป งานจะลดลงมาก) Taeyhom-src-20100503.tar.gzภาพตัวอย่าง
ภาพจากบราวเซอร์ epiphany และ oo.o
ตัวธรรมดาที่ hint แล้ว
ตัวธรรมดา
ตัวหนาที่ hint แล้ว
ตัวหนา
เอา TlwgTypo จาก ltn: ThaiFonts-Scalable มาทำเรื่อง hinting
พอจะเริ่ม hint จริง ๆ พบว่าลายเส้นของไทยไม่เข้ากับของฝรั่ง ซึ่งจะทำให้การ hint ยุ่งยาก จึงตัดสินใจสร้างลายเส้นไทยขึ้นมาใหม่ และตั้งชื่อใหม่ว่า TlwgTypott
ดาวน์โหลดไฟล์ฟอนต์(520421)
ดาวน์โหลดไฟล์ซอร์ส
ภาพตัวอย่าง
จาก abiword (openoffice แสดงผลเพี้ยน)
ตัวธรรมดา
ตัวหนา
ความรู้ที่ได้
เอาฟอนต์ Waree - วารี จาก ltn: ThaiFonts-Scalable มาทำเรื่อง hinting
ต้องขออภัยที่ต้องปรับความกว้างฟอนต์จากเดิม เพื่อให้เหมาะกับการทำ hinting ดังนั้นจึงไม่สามารถใช้แทนกันได้อย่างสนิท
ดาวน์โหลดไฟล์ฟอนต์(รุ่น 20090603)
ดาวน์โหลดไฟล์ซอร์ส
ภาพตัวอย่าง
จาก openoffice.org รุ่น 3.0.1
ตัวธรรมดา
ตัวหนา
เอาลายเส้นจาก Waree Sans มาทำเป็น Monospace แต่ทำเป็นแบบไม่ใช่ความกว้างคงที่แท้ ๆ คือสระและวรรณยุกต์ทำเป็นแบบความกว้างเป็นศูนย์ เพื่อให้ใช้งานกับ Openoffice.org ได้
ทำแล้วดูแล้วยังไม่ค่อยสวย เพราะความกว้างฟอนต์อังกฤษเดิมแคบไป จึงทำให้สัดส่วนฟอนต์ไทยดูสูงชลูดไปหน่อย
ดาวน์โหลดไฟล์ฟอนต์(รุ่น 20090613)
ดาวน์โหลดไฟล์ซอร์ส
ภาพตัวอย่าง
จาก openoffice.org รุ่น 3.0.1
ตัวธรรมดา
ตัวหนา
เอาภาษาไทยจากฟอนต์ 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
ตัวธรรมดา
ตัวหนา
เพิ่งเริ่มทำนะครับ
ขั้นตอน
งาน
งานของฟอนต์ตัวนี้คือ เริ่มศึกษาการทำ ตาราง Lookups - GPOS โดยใช้ภาษาลาวเป็นต้นแบบ คือ
download ttf
download src
screenshot
OpenOffice.org-2.4 แสดงผลที่ ppem ต่าง ๆ
รุ่นล่าสุด 520207
รุ่นแรก
บันทึกการแปลงฟอนต์ Waree เป็น DejaVuSansThai รุ่นแรก
dfont.py
เอาไว้คลี่และเรียงสแต็ก ใช้ได้ผลดีพอควร แต่สคริปต์ยังขาดการตรวจสอบความถูกต้องของ instructing code ซึ่งถ้าใช้ด้วยพารามิเตอร์ --all
อาจทำให้ fontforge หยุดการทำงานได้ - รอปรับปรุงต่อไปความรู้ที่ได้
MIRP
(ซึ่งต้องอ้างค่าจาก cvt - Control Value Table) มีความแม่นยำแน่นอนกว่าการเคลื่อนค่าแบบ direct (เช่นคำสั่ง MDRP
)ip
(Interpolate) จะทำให้ฟอนต์ดูฟุ้ง แต่ระยะที่ได้ แน่นอนกว่าการเคลื่อนจุดแบบ direct (เว้นเสียแต่ถ้าไม่มีการทดลงจุด คืออาร์กิวเมนต์ rnd
ควรใช้ MDRP
เสมอ ไม่งั้นบางทีอาจเพี้ยน)rnd
ห้ามใช้คำสั่ง SHPIX
ที่จุดนั้นเด็ดขาด จะมีปัญหาเลอะตอนพิมพ์ โดยเฉพาะในแนวแกน Yผลการทดลอง - บนลินุกซ์ ความละเอียดจอภาพ 85 dpi
ดาวน์โหลดไฟล์ฟอนต์ ttf ล่าสุด (รุ่น 520401)
ดาวน์โหลดไฟล์ซอร์ส
งานที่เหลือ และที่ตั้งใจจะทำ
ภาพตัวอย่างของรุ่นล่าสุด
ตัวหนา
ภาพจาก OpenOffice.org รุ่นล่าสุด
oo ตัวหนา
ภาพตัวอย่าง รุ่นแรก (จากจอภาพขนาด 85 dpi)
dfont.py
การทำงานแค่คลี่และเรียงแสต็กใหม่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))
เที่ยวนี้ไม่มีอะไร บันทึกตัวอย่างการ 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
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 เป็นต้นไปsrp0 26จากจุดเริ่มต้น จะเคลื่อนจุดอ้างอิง rp0 ไปยังจุดที่ใกล้ขอบซ้ายที่สุด คือจุด 18 เพื่อกั้นเป็นระยะช่องไฟด้านหน้า ด้วยค่า cvt คือ front_kai
mirp[rp0,min,rnd,grey] front_kai 18จุดที่จะไปต่อคือ จุด 8 แต่เนื่องจากเมื่อเคลื่อนไปแล้ว ไม่มีจุดที่จะไปต่อ เราจึงไม่ต้องเคลื่อนจุด rp0 ไปด้วย จึงไม่ต้องใส่อาร์กิวเมนต์ rp0
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 เสมอ
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
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]จบแล้วครับ
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
...
}
ภาพลายเส้น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
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]เสร็จแล้ว
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]
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
...
}
ภาพลายเส้น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
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
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]เสร็จแล้ว
View -> Grid Fit -> Show Grid Fit -> 17pt 72dpi
ได้ภาพดังนี้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 |
SVTCA[x-axis]ใส่ delta hint โดยใส่จำนวนคู่ไว้เป็นค่าแรก
deltap1 8 2 143 39 143 0 143 1 143 3 141 38 141 37 139 4 139ภาพหลังการขยับแล้วจะป็นดังนี้
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
บันทึกการสร้างฟอนต์ DejaVu Serif Thai
ผลการทดลอง
ปรับปรุง
(เนื่องจากเป็นการทำงานไปด้วย ศึกษาไปด้วย การทำ hinting จึงไม่เป็นอันหนึ่งอันเดียวกันในทุก ๆ ฟอนต์ที่ทำ ต้องขออภัยด้วย)
ดาวน์โหลดไฟล์ฟอนต์ ttf
ดาวน์โหลดซอร์ส
ภาพตัวอย่าง (จากขนาดจอภาพ 72 dpi จึงกำหนดระยะเป็น ppem แทนเพื่อป้องกันสับสน)
ต้องการทำฟอนต์ที่ใช้ในงานโฆษณา แต่ไม่แปลงรูปฟอนต์มากนัก ให้มีหัวบาง ๆ อยู่ทุกตัว ตั้งชื่อว่า อุโฆษ (Ukhosa) สร้างโดยเอาโครงฟอนต์โลมาบุตรมาแปลง
ดาวน์โหลดไฟล์ฟอนต์ (20100707)
ดาวน์โหลดไฟล์ซอร์ส
ภาพตัวอย่าง
จาก Epiphany Browser
ตัวธรรมดา
ตัวหนา
$ 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
$ 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ไม่เหมือนเป๊ะ แต่น่าจะทำงานได้เหมือนกัน
ได้ลองทดสอบโปรแกรมสำนักงาน เฉพาะเรื่องกระดานคำนวณ
งานเก่าเป็น Excel ทั้งหมด มีรายการคำนวณไม่ซับซ้อน แต่มีปริมาณแผ่นงานมาก
ขนาดไฟล์ (XLS) 1.3 MB โดยมีเป้าหมายคือ ในระยะแรก ต้องทำงานร่วมกับวินโดวส์ได้ด้วย
ทดสอบกับอูบุนตู dapper และเดเบียน etch
ได้เรื่องว่า
อย่างไรก็ตาม ก็ยังมีปัญหาบางประการ เช่น
งานนี้จึงเลือกใช้ gnumeric ในการทำงานร่วมกับวินโดวส์ครับ
ต้องการค้นหาและแทนที่ เอกสาร 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
-invisible
ช่วยได้เล็กน้อย)มีโจทย์คือ จะสร้างเอกสาร pdf จากหนังสือภาพสี
ขั้นตอนคือ
จบ