Category: Operating System

  • Recovering Files From ecryptfs Encrypted Home

    ผมเจอปัญหาว่ามีเครื่องเสีย ผมต้องเข้าไปช่วยกู้ข้อมูลในดิสก์ที่เสีย แต่ก็พบว่า home directory ได้ทำ encrypt ไว้ในตอนติดตั้ง ubuntu 10.04 desktop ผมจึงค้นหาดูพบหลายบทความ แต่มาลงตัวที่บทความนี้ http://www.kaijanmaki.net/2009/10/26/recovering-files-from-ecryptfs-encrypted-home/ เป็นจุดเริ่มต้นให้ลองทำตามดู

    ผมทำ Virtual Machine เป็น guest ใน Oracle VM VirtualBox โดยติดตั้ง ubuntu 10.04 desktop (จะลองกับ 12.04 หรืออื่นๆก็คงได้) แล้วลองสร้างไฟล์ test.txt, test2.txt จากนั้น shutdown เครื่อง guest แล้วบูตอีกทีด้วยไฟล์ ubuntu-10.04.4-desktop-i386.iso แล้วเลือก Try ubuntu

    เมื่อเข้าถึงหน้ากราฟิกแล้ว คลิกเมนู places, เลือก disk ที่ต้องการ จากนั้นเปิด terminal เพื่อออกไปทำงานในโหมด command line

    ใช้คำสั่ง df ดูจะเห็นว่าดิสก์ถูก mount เป็นชื่อ ดังนี้

    /dev/sda1 /media/c460e698-ae7a-478e-a9fa-7ea4beb4f7c6

    ให้เข้าทำงานเป็น root

    ubuntu@ubuntu:~$ sudo su

    ลองตรวจสอบดูว่ามีอะไรอยู่ข้างในบ้างด้วยคำสั่งข้างล่างนี้

    root@ubuntu:~# ls -l /media/c460e698-ae7a-478e-a9fa-7ea4beb4f7c6/home/mama/
     total 0
     lrwxrwxrwx 1 1000 1000 56 2013-04-10 08:52 Access-Your-Private-Data.desktop -> /usr/share/ecryptfs-utils/ecryptfs-mount-private.desktop
     lrwxrwxrwx 1 1000 1000 52 2013-04-10 08:52 README.txt -> /usr/share/ecryptfs-utils/ecryptfs-mount-private.txt

    ตรวจสอบดูว่าไฟล์ต่างๆ น่าจะซ่อนอยู่ที่ไหนก็พบว่าอยู่ที่นี่

    root@ubuntu:~# ls -l /media/c460e698-ae7a-478e-a9fa-7ea4beb4f7c6/home/.ecryptfs/mama/.Private/
     total 276
     -rw-r--r-- 1 1000 1000 12288 2013-04-10 09:10 ECRYPTFS_FNEK_ENCRYPTED.FWaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmC1PAe3rM36mt8nRtyRWtxlE--
     drwxr-xr-x 4 1000 1000  4096 2013-04-10 09:06 ECRYPTFS_FNEK_ENCRYPTED.FWaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmC2Yj-dqfvpHuYFJe32oPmP---
     drwx------ 4 1000 1000  4096 2013-04-10 09:05 ECRYPTFS_FNEK_ENCRYPTED.FWaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmC3kM22hNF7litgpIAwtmwtU--
     drwx------ 2 1000 1000  4096 2013-04-10 09:11 ECRYPTFS_FNEK_ENCRYPTED.FWaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmC7W8YJQSLe20t0XJHf6xjB---
     -rw-r--r-- 1 1000 1000 12288 2013-04-10 08:52 ECRYPTFS_FNEK_ENCRYPTED.FWaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmC9cPIgv52y7AFWY.H1WCtTE--
     -rw------- 1 1000 1000 12288 2013-04-10 09:05 ECRYPTFS_FNEK_ENCRYPTED.FWaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmC9dcmZbWv.pjpzikXKTE2xU--
     lrwxrwxrwx 1 1000 1000   104 2013-04-10 08:52 ECRYPTFS_FNEK_ENCRYPTED.FWaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmC9u1V-uJOrNxm7ZdoXYvX5E-- -> ECRYPTFS_FNEK_ENCRYPTED.FXaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmCGOuByR46pE4zxC1IyPOC1XMuz8uG9d.Y91Nys0NXy1o-
     drwx------ 2 1000 1000  4096 2013-04-10 09:06 ECRYPTFS_FNEK_ENCRYPTED.FWaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmCBinhrU4ijNIOXaDLIUmz6---
     drwxr-xr-x 6 1000 1000  4096 2013-04-10 09:08 ECRYPTFS_FNEK_ENCRYPTED.FWaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmCbQymH7oV5njbGxrao6qK.E--
     drwx------ 2 1000 1000  4096 2013-04-10 09:11 ECRYPTFS_FNEK_ENCRYPTED.FWaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmCcib.jfkdAKRfPUX3SRC3PE--
     drwx------ 2 1000 1000  4096 2013-04-10 09:05 ECRYPTFS_FNEK_ENCRYPTED.FWaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmCd8fDbrGkyQn76SLx7OFbWk--
     drwxr-xr-x 2 1000 1000  4096 2013-04-10 09:05 ECRYPTFS_FNEK_ENCRYPTED.FWaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmCf9KXChgI4vRxtQINyM5f2U--
     -rw-r--r-- 1 1000 1000 12288 2013-04-10 09:08 ECRYPTFS_FNEK_ENCRYPTED.FWaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmC.FSa4ytf8l5gxGoCy5Z.hU--
     -rw-r--r-- 1 1000 1000 12288 2013-04-10 09:05 ECRYPTFS_FNEK_ENCRYPTED.FWaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmCHD.zza5bDIfbpJCBK20OEk--
     drwx------ 7 1000 1000  4096 2013-04-10 09:11 ECRYPTFS_FNEK_ENCRYPTED.FWaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmCHMmbRJdm8bOuKAIY9l2nNE--
     drwxr-xr-x 2 1000 1000  4096 2013-04-10 09:05 ECRYPTFS_FNEK_ENCRYPTED.FWaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmCI0txn1fN.CnTaplNnnfw-E--
     -rw-r--r-- 1 1000 1000 12288 2013-04-10 09:10 ECRYPTFS_FNEK_ENCRYPTED.FWaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmCinSVUy2qcWnG4Fkkvq3TbE--
     drwxr-xr-x 2 1000 1000  4096 2013-04-10 09:05 ECRYPTFS_FNEK_ENCRYPTED.FWaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmCJCe9LKWs69b5Gm7sz9l8bk--
     drwxr-xr-x 2 1000 1000  4096 2013-04-10 09:05 ECRYPTFS_FNEK_ENCRYPTED.FWaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmCkjHGX5Z.4t2crv-B-HErFE--
     drwx------ 3 1000 1000  4096 2013-04-10 09:05 ECRYPTFS_FNEK_ENCRYPTED.FWaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmCkL2EHaUs.epFv6xhN2RgnE--
     drwxr-xr-x 2 1000 1000  4096 2013-04-10 09:05 ECRYPTFS_FNEK_ENCRYPTED.FWaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmCNnJ2nY9cMM4vKFU6acxrIE--
     drwx------ 3 1000 1000  4096 2013-04-10 09:05 ECRYPTFS_FNEK_ENCRYPTED.FWaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmC-NphMDal80t55pvvVnvyiE--
     drwxr-xr-x 2 1000 1000  4096 2013-04-10 09:05 ECRYPTFS_FNEK_ENCRYPTED.FWaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmCNYJp37Rmr3eJHZWw-yuZiE--
     -rw------- 1 1000 1000 12288 2013-04-10 09:05 ECRYPTFS_FNEK_ENCRYPTED.FWaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmCRIV.WMwNJ.HPDn2Qm9Is4---
     -rw------- 1 1000 1000 12288 2013-04-10 09:11 ECRYPTFS_FNEK_ENCRYPTED.FWaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmCRqHIvRgijJ2kR4EkiHAV.U--
     drwxr-xr-x 2 1000 1000  4096 2013-04-10 09:05 ECRYPTFS_FNEK_ENCRYPTED.FWaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmCTnsCL.MmBrnuuZJB7rUqu---
     drwxr-xr-x 2 1000 1000  4096 2013-04-10 09:05 ECRYPTFS_FNEK_ENCRYPTED.FWaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmCt-qx19u1J7cqrJdDeAcGXU--
     drwxr-xr-x 2 1000 1000  4096 2013-04-10 09:05 ECRYPTFS_FNEK_ENCRYPTED.FWaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmCtrtmC.5pOzU71mXW9lsBTU--
     -rw-r--r-- 1 1000 1000 12288 2013-04-10 08:52 ECRYPTFS_FNEK_ENCRYPTED.FWaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmCtZB8bfkkrg5Vwn12I0a36k--
     -rw-r--r-- 1 1000 1000 12288 2013-04-10 08:52 ECRYPTFS_FNEK_ENCRYPTED.FWaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmCugEFlA5kzGzGdCrk98SRdk--
     -rw------- 1 1000 1000 12288 2013-04-10 09:05 ECRYPTFS_FNEK_ENCRYPTED.FWaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmCUvV.UqheVgE8ii35hI1CD---
     drwxr-xr-x 2 1000 1000  4096 2013-04-10 09:05 ECRYPTFS_FNEK_ENCRYPTED.FWaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmCwkbU8zR9wlpqaW.qpSOOG---
     -rw-r--r-- 1 1000 1000 12288 2013-04-10 09:05 ECRYPTFS_FNEK_ENCRYPTED.FWaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmCWmtlLdW3Fscyzdv92P7ya---
     drwx------ 2 1000 1000  4096 2013-04-10 09:05 ECRYPTFS_FNEK_ENCRYPTED.FWaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmCxb3ohG01RPLQKdGf5aGO0k--
     lrwxrwxrwx 1 1000 1000   104 2013-04-10 08:52 ECRYPTFS_FNEK_ENCRYPTED.FWaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmCY20y7DesFofU3WXsupOCaU-- -> ECRYPTFS_FNEK_ENCRYPTED.FXaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmCHlpTqADLNpd4HnYAdygClBA6cPCLon7WcZ6N6OFCv8g-
     -rw------- 1 1000 1000 12288 2013-04-10 09:11 ECRYPTFS_FNEK_ENCRYPTED.FXaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmCpbeOQivGSuvqOu51OftghDjzC0ltpVNARnFgkWRBi-2-
     -rw------- 1 1000 1000 12288 2013-04-10 09:11 ECRYPTFS_FNEK_ENCRYPTED.FXaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmCpmXGVsVsyK8V95y22w4vq88Zp17vcB8FLxmDpAeLfzM-
     -rw-r--r-- 1 1000 1000 12288 2013-04-10 08:52 ECRYPTFS_FNEK_ENCRYPTED.FXaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmCpmXGVsVsyK8V95y22w4vqAzU.HY0GIf1CP9WH.239TU-
     drwx------ 2 1000 1000  4096 2013-04-10 09:09 ECRYPTFS_FNEK_ENCRYPTED.FXaiRtz3Ag4mTURWaW1nT.6mYpw-PkyvsXmCpmXGVsVsyK8V95y22w4vq-Qwp5WNWvevyfXuTlIfkN2-

    เข้าสู่ขั้นตอนการกู้ข้อมูล โดยสร้างไดเรกทอรีใน /mnt ดังนี้

    root@ubuntu:/home/ubuntu# cd /mnt
    root@ubuntu:/mnt# mkdir oldhome
    root@ubuntu:/mnt# ln -s /media/c460e698-ae7a-478e-a9fa-7ea4beb4f7c6/home/.ecryptfs/mama/.Private oldprivate

    ตรวจสอบดูว่าได้ผลตามต้องการ

    root@ubuntu:/mnt# ls -l /mnt
    total 12
    drwx------ 24 mama mama 4096 2013-04-10 16:11 oldhome
    lrwxrwxrwx  1 root root   72 2013-04-10 20:51 oldprivate -> /media/c460e698-ae7a-478e-a9fa-7ea4beb4f7c6/home/.ecryptfs/mama/.Private

    เราจะต้องเข้าไปสร้าง passphase จากรหัสผ่านของบัญชีผู้ใช้งาน ในที่นี่คือ username ชื่อ mama มีรหัสผ่าน 123456 โดยการเข้าไปใช้คำสั่ง ecryptfs-unwrap-passphrase กับไฟล์ wrapped-passphrase ซึ่งตามความเป็นจริง เราจะต้องรู้ passphase ยาวๆนี้อยู่แล้ว แต่ไม่รู้ก็ไม่เป็นไร

    เข้าไปไดเรกทอรีเพื่อจะได้ไม่ต้องพิมพ์คำสั่งยาวๆ

    root@ubuntu:/mnt# cd /media/c460e698-ae7a-478e-a9fa-7ea4beb4f7c6/home/

    ใช้คำสั่งเพื่อสร้าง passphase

    root@ubuntu:/media/c460e698-ae7a-478e-a9fa-7ea4beb4f7c6/home# ecryptfs-unwrap-passphrase .ecryptfs/mama/.ecryptfs/wrapped-passphrase
    Passphrase: 123456

    ตรงนี้ใส่รหัสผ่านของ username ที่มี home directory encrypted คือ mama มีรหัสผ่าน 123456 ไว้

    ได้ผลคือได้ passphase ยาวๆ ออกมา ซึ่งจะถูกนำไปใช้ต่อไป

    44dcd2be32726c9925eb2403e295aa2e

    ออกจากไดเรกทอรี

    root@ubuntu:/media/c460e698-ae7a-478e-a9fa-7ea4beb4f7c6/home# cd

    ตอนนี้จะมีการถาม passphase อีก หากใช้วิธี copy และ paste ได้จะสะดวกกว่าคีย์ทั้งหมดทีละตัว

    root@ubuntu:~# ecryptfs-add-passphrase --fnek
    Passphrase: ใส่ 44dcd2be32726c9925eb2403e295aa2e ที่ได้มา

    ได้ผลลัพธ์

    Inserted auth tok with sig [f09d3c0aedc95c7b] into the user session keyring
    Inserted auth tok with sig [ae779fc532c1b27e] into the user session keyring

    เราจะใช้ค่า ae779fc532c1b27e ในขั้นตอนต่อไป

    root@ubuntu:~# mount -t ecryptfs /mnt/oldprivate /mnt/oldhome
    Passphrase: ใส่ 44dcd2be32726c9925eb2403e295aa2e ที่ได้มา
    Select cipher:
    1) aes: blocksize = 16; min keysize = 16; max keysize = 32 (not loaded)
    2) blowfish: blocksize = 16; min keysize = 16; max keysize = 56 (not loaded)
    3) des3_ede: blocksize = 8; min keysize = 24; max keysize = 24 (not loaded)
    4) twofish: blocksize = 16; min keysize = 16; max keysize = 32 (not loaded)
    5) cast6: blocksize = 16; min keysize = 16; max keysize = 32 (not loaded)
    6) cast5: blocksize = 8; min keysize = 5; max keysize = 16 (not loaded)
    Selection [aes]: กด Enter
    Select key bytes:
    1) 16
    2) 32
    3) 24
    Selection [16]:  กด Enter
    Enable plaintext passthrough (y/n) [n]: n
    Enable filename encryption (y/n) [n]: y
    Filename Encryption Key (FNEK) Signature [f09d3c0aedc95c7b]: ae779fc532c1b27e ใส่ค่านี้
    Attempting to mount with the following options:
    ecryptfs_unlink_sigs
    ecryptfs_fnek_sig=ae779fc532c1b27e
    ecryptfs_key_bytes=16
    ecryptfs_cipher=aes
    ecryptfs_sig=f09d3c0aedc95c7b
    WARNING: Based on the contents of [/root/.ecryptfs/sig-cache.txt],
    it looks like you have never mounted with this key
    before. This could mean that you have typed your
    passphrase wrong.
    
    Would you like to proceed with the mount (yes/no)? : yes
    Would you like to append sig [f09d3c0aedc95c7b] to
    [/root/.ecryptfs/sig-cache.txt]
    in order to avoid this warning in the future (yes/no)? : no
    Not adding sig to user sig cache file; continuing with mount.
    Mounted eCryptfs

    หากได้บรรทัด Mounted eCryptfs ก็ไชโยได้ แต่ถ้าไม่สำเร็จจะฟ้องเป็น error ก็เพราะระบุตำแหน่ง directory ที่มีไฟล์ encrypted ไม่ถูกต้องในขั้นตอนแรกๆ ที่เป็นคำสั่ง ln -s

    หากทำสำเร็จ เราลอง ใช้คำสั่งดูรายชื่อไฟล์

    root@ubuntu:~# ls -l /mnt/oldhome/
    total 80
    drwxr-xr-x 2 mama mama 4096 2013-04-10 16:05 Desktop
    drwxr-xr-x 2 mama mama 4096 2013-04-10 16:05 Documents
    drwxr-xr-x 2 mama mama 4096 2013-04-10 16:05 Downloads
    -rw-r--r-- 1 mama mama  179 2013-04-10 15:52 examples.desktop
    drwxr-xr-x 2 mama mama 4096 2013-04-10 16:05 Music
    -rw-r--r-- 1 mama mama   33 2013-04-10 16:08 mypassphase
    drwxr-xr-x 2 mama mama 4096 2013-04-10 16:05 Pictures
    drwxr-xr-x 2 mama mama 4096 2013-04-10 16:05 Public
    drwxr-xr-x 2 mama mama 4096 2013-04-10 16:05 Templates
    -rw-r--r-- 1 mama mama   16 2013-04-10 16:10 test2.txt
    -rw-r--r-- 1 mama mama   15 2013-04-10 16:10 test.txt
    drwxr-xr-x 2 mama mama 4096 2013-04-10 16:05 Videos

    จะเห็นไฟล์ตามปกติ ซึ่งมีไฟล์ที่ผมสร้างไว้คือ test.txt และ test2.txt

    ผมเคยเก็บ passphase ไว้เพื่อเปรียบเทียบ ซึ่งก็ตรงกับที่เราสร้าง passphase ในขั้นตอนด้านบนจากรหัสผ่านของ username mama

    root@ubuntu:~# cat /mnt/oldhome/mypassphase
    44dcd2be32726c9925eb2403e295aa2e

    ถึงตอนนี้ หากเป็นการกู้ข้อมูล ก็ทำการคัดลอกไฟล์ตามสะดวก

    เมื่อคัดลอกไฟล์ที่ต้องการแล้ว ก็ทำการ unmount เพื่อเลิกใช้ดิสก์นั้น

    root@ubuntu:~# umount /mnt/oldhome

    หวังว่าคงเป็นประโยชน์สำหรับ ใครที่บังเอิญติดตั้งแบบ encrypted home directory เอาไว้ แล้ววันหนึ่งต้องกู้ข้อมูล หรือใครจะลองทำดูก็ได้นะ

    หากโชคดีไฟล์ wrapped-passphrase ไม่เสียหาย ก็จะทำได้สำเร็จครับ

  • แนวทางการ Backup บน Ubuntu Server (กรณีระบบ PSU EMail)

    เนื่องจากจะจัดทำระบบ Backup and Recovery ระบบ Mail ซึ่งทำงานอยู่บน Ubuntu จึงทำการรบรวมข้อมูล และหาแนวทางที่เหมาะสม

     

    ก่อนจะตัดสินใจเลือก แผนการ Backup ที่เหมาะสม ก็ควรพิจารณาเรื่องต่อไปนี้ด้วย [1]

    1. WHY: จุดประสงค์การ Backup/Restore, ถ้าข้อมูลสูญหายจริงๆจะเกิดความเสียหายขนาดไหน ?

    2. WHAT: สิ่งที่จะทำการ Backup, ทั้ง Hard Drive? หรือเป็นข้อมูลบางส่วน?

    3. WHEN: เวลาที่ดีที่สุดที่จะ Backup, บ่อยขนาดไหน, จะทำการ Full/Incremental Backup เมื่อใดบ้าง ?

    4. WHERE: เก็บ Backup ไว้ที่ใด, ในเครื่องนั้นๆ, เก็บไว้ภายนอก หรือใช้บริการ Cloud Storage

    5. MEDIUM: สื่อที่ใช้จัดเก็บ, USB Stick, External HDD, Tape หรือ Backup Server

     

    ประเภทของการ Backup [1]

    1. Full: สำรองทุกสิ่งอย่าง

    2. Incremental: สำรองเฉพาะสิ่งที่เพิ่มขึ้นมา นับจากการสำรองครั้งล่าสุด

    3. Differential:  เหมือนกับ Incremental แต่เก็บเฉพาะไฟล์ที่ยังไม่ปรับค่า Archive bit (ในกรณี Windows Filesystem)

     

    วิธีการ Backup : จากการสำรวจ พบว่าบน Ubuntu มีเครื่องมือ และวิธีการให้ใช้มากมาย เช่น SimpleBackupSuite, grsync, pybackpack หรือที่ติดมาพร้อมกับ Ubuntu Desktop อย่าง Déjà Dup [1] แต่เครื่องมือเหล่านี้เหมาะสำหรับการใช้งานแบบ Desktop Computer มากกว่า แต่ในระบบ Mail Server ซึ่งมีปริมาณข้อมูลจำนวนมาก และเป็นของผู้ใช้จำนวนมาก (ในระบบมีผู้ใช้รวม 6000 คน) จึงควรต้องมีการทำงานที่ยืดหยุ่นกว่า และสามารถปรับแต่งตามต้องการได้

    จึงพิจารณาใช้ tar เพื่อทำการ Full [2] และ Incremental Backup [3] แล้วจึงใช้ rsync ส่งไฟล์ไปเก็บไว้ที่ Backup Server ต่อไป

     

    ในกรณี PSU Email มีลักษณะดังนี้

    1. เก็บ email แต่ละฉบับเป็นแบบไฟล์ ไฟล์ขนาดเล็กๆ

    2. ไม่มีการเขียนทับไฟล์เดิม (เว้นแต่ Index ของ Mailbox ซึ่งไม่จำเป็นนัก เพราะ ต้อง rebuild ใหม่เมื่อทำการ restore)

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

    4. เมื่อผู้ใช้สามารถ สร้าง และ rename ชื่อ directory ได้
    พิจารณาคำถามข้างต้น แล้วให้ตำตอบ

    WHY: เพื่อสำรอง email ไว้ให้ ในกรณีฉุกเฉินที่ผู้ใช้ลบ email ไปโดยไม่ตั้งใจ

    WHAT: email อยู่บน disk แยกออกไป, ทำการสำรองเฉพาะไฟล์ใน directory จัดเก็บ ซึ่งใช้เนื้อที่รวม 600 GB ซึ่งเป็นของผู้ใช้ในระบบ 6,000 คน, ข้อมูลมีลักษณะเป็นไฟล์ขนาดเล็กจำนวนมาก และมีการเพิ่มและลดจำนวน แต่ไม่มีการแก้ไขไฟล์, มีการเพิ่ม/ลบ/เปลี่ยนชื่อ directory

    WHEN: จากสถิติการใช้งาน พบว่า ผู้ใช้ใช้งานน้อยที่สุดหลังเวลา 03:00 ของทุกวัน และในวันอาทิตย์เป็นวันที่มีปริมาณการใช้งานน้อยที่สุด และจากการย้ายระบบครั้งล่าสุด พบว่าการทำ Full Backup ใช้เวลา 16 ชั่วโมง ส่วนการทำ Incremental Backup ประจำวัน ใช้เวลาประมาณ 1 ชั่วโมง จึงเลือกสำรองแบบ Full ทุกวันอาทิตย์ เริ่มเวลา 00:01 และทำ Incremental ในวันจันทร์ ถึงเสาร์ เริ่มเวลา 03:01

    WHERE: เนื่องจากในอนาคตวางแผนจะปรับเป็นระบบ Distributed Mailbox จึงวางระบบให้ Backup เก็บไว้ในเครื่องก่อน แล้วใช้ rsync ไปเก็บไว้ใน Backup Server แล้วมีระบบย้ายข้อมูลเก่า ลงไปเก็บใน External HDD เพื่อสำรองไว้ในระยะยาวได้

    MEDIUM: สำรองลงใน Local Disk ก่อน แล้ว rsync ไปลงใน Backup Server จากนั้นย้ายลง External HDD

     

    แนวทางการ Backup ที่เลือกใช้สำหรับ PSU Email (เป็นเพียงตัวอย่าง)

    1. การทำ Full Backup : ให้ระบบเข้าไป Backup ข้อมูลใน Mailbox ของผู้ใช้ เป็นรายคน (เช่น username.s) จาก directory /var/spool/mail/ ทุกวันอาทิตย์ เวลา 00:01 โดยเก็บไฟล์ไว้ที่ /path/to/backupfile ด้วยคำสั่ง

    tar -zcf  /path/to/backupfile/username.s-f-20130414.tar.gz -g  /path/to/backupfile/username.s-20130414.snar   /var/spool/mail/username^s/

    2. การทำ Incremental Backup: ทำคล้ายกับ Full Backup แต่ใช้คำสั่งต่อไปนี้ ทุกวันจันทร์ ถึงเสาร์ เวลา 03:01

    tar -zcf  /path/to/backupfile/username.s-i-20130415.tar.gz -g  /path/to/backupfile/username.s-20130414.snar   /var/spool/mail/username^s/

    3. เมื่อทำครบทุก mailbox จึงทำการ Rsync ไปเก็บไว้ใน Backup Server ต่อไป

     

    Reference

    [1] https://help.ubuntu.com/community/BackupYourSystem

    [2] http://www.gnu.org/software/tar/manual/html_chapter/Backups.html

    [3] http://www.tuxradar.com/content/quick-guide-backups-using-tar

  • InnoDB Storage Engine Data Recovery สำหรับ MySQL database

    ปัญหาของการลืม password ของ root ของ MySQL ไม่ได้เป็นปัญหาเดียวที่ผมเจอ ในเรื่องของการใช้งาน MySQL Database Server ถึงแม้จะไม่บ่อยนัก แต่ก็บ่อยมากพอที่จะทำให้ ใน 2-3 ครั้งให้หลัง ผม “จำ” วิธีการแก้ปัญหาได้ โดยไม่ต้องพึ่งพา google อีกต่อไป

    ถ้าจะว่าไป ถ้าปัญหานี้ เกิดขึ้น “บ่อย” กว่าเดิมอีกซักหน่อย ผมก็คง “จำ” root password ได้ (เพราะต้องใช้บ่อยขึ้น) และส่งผลในทางกลับ ทำให้ ไม่ลืม root password และทำให้ปัญหานี้ไม่ใช่ปัญหาอีกต่อไป — ฟังดูย้อนแย้ง ชวนขยักขย่อน หรือเปล่าครับ 🙂

    แต่ไม่ว่าอย่างไร … การลืม password ในระดับสร้างความหงุดหงิดรำคาญใจแค่นั้นเอง ความรู้สึกอย่างมากก็คือ “ลืมอีกแล้ว ต้องรีเซ็ทพาสเวิร์ดอีกรอบ งี่เง่าจริงๆ!”  แต่มีปัญหาอีกอย่างที่ … ไม่ได้เกิดขึ้นบ่อย … นานๆเกิดที แต่พอเกิดขึ้นมาแล้ว ทำให้ระดับความเครียดของ sysadmin พุ่งขึ้นสูงอย่างฉับพลัน

    ปัญหาแบบที่ว่า เกิดขึ้น ในลักษณะนี้ ครับ นี่คือข้อความทีี่มีอยู่ในไฟล์ “/var/log/mysql/error.log”

    InnoDB: Database was not shut down normally!
    InnoDB: Starting crash recovery.
    InnoDB: Reading tablespace information from the .ibd files...
    InnoDB: Restoring possible half-written data pages from the doublewrite
    InnoDB: buffer...
    InnoDB: Doing recovery: scanned up to log sequence number 7 3581528990
    InnoDB: Error: page 4 log sequence number 7 3581552218
    InnoDB: is in the future! Current system log sequence number 7 3581528990.
    InnoDB: Your database may be corrupt or you may have copied the InnoDB
    InnoDB: tablespace but not the InnoDB log files. See
    InnoDB: http://dev.mysql.com/doc/refman/5.1/en/forcing-recovery.html
    InnoDB: for more information.

    ก่อนที่จะมาถึงที่จุดนี้ อาการของตัว mysql server ก็คือว่า load ของ mysqld ตัว daemon ทำงานอยู่แกว่งอยู่ในช่วง 90-100% อยู่ตลอดเวลา หลังจาก boot เครื่องขึ้นมา และถึงแม้จะไม่มีการใช้งาน mysql server จาก process อื่นๆเลย (ผมทดสอบโดยการ shutdown ตัว web server ซึ่งเป็น client เพียงตัวเดียวที่ใช้งาน mysql server อยู่) ก็ไม่ได้ทำให้ load ของตัว mysqld ลดลง

    หลังจากลองตรวจสอบจาก system log ของระบบ จนไปเจอข้อความข้างบนใน error log ของ mysql เองก็เลยรู้ว่าปัญหาอยู่ที่ ไฟล์ที่บันทึกข้อมูล/transaction ของ MySQL มีปัญหา

    ปัญหานั้นมาจากใหน? ปัญหาเกิดขึ้นจากข้อมูลของ MySQL database ที่ผมได้มานั้น ได้มาจากการ copy (อันที่จริง rsync — แต่นั่นไม่ใช่ประเด็น)  ไฟล์ข้อมูลของ MySQL database ในขณะที่ตัว database server ยังเปิดใช้งานอยู่

    เมื่อมาคิดอีกทีในตอนหลัง วิธีการที่ดีกว่าสำหรับการ copy database มาครั้งนั้น สิ่งที่ควรจะทำก็คือการ dump database โดยใช้คำสั่ง mysqldump แล้วเอาไฟล์ sql ที่ได้จากการ dump อันนั้นมา import กลับเข้า  database อีกทีในตอนหลัง

    แต่จะว่าไปชีวิตของ admin ก็ไม่ง่ายอย่างนั้น ข้อแรกการที่ผมจะ dump ข้อมูลจาก database server ออกมาทั้งหมดได้ ผมจะต้องรู้ root password ของ database server อันนั้น … ถึงแม้ผมจะสามารถใช้สิทธิของ root บนระบบนั้นๆได้ แต่ … ผมไม่รู้ทั้ง root password ของ system และ ไม่รู้ root password ของ mysql ด้วยเหมือนกัน … อ้าว งั้นก็ reset root password ของ mysql เสียก่อนสิ โดยใช้วิธีการที่ผมได้อธิบายไปแล้ว ในบันทึกที่แล้ว … จะมีปัญหาอะไรล่ะ? มีครับ ระบบที่ว่ามีการใช้งานอยู่ตลอดเวลา database server เป็นองค์ประกอบสำคัญ ในขณะนั้นผมไมแน่ใจว่า การที่จะ shutdown และ restart mysql server จะส่งผลกระทบต่อการทำงานของส่วนอื่นๆของระบบอย่างไรบ้าง ถ้า restart ตัว database server แล้ว การทำงานส่วนอื่นๆที่เหลือ จะกลับมาทำงานได้ตามปกติหรือเปล่า? หรือผมจะต้อง restart สซอฟต์วแร์ส่วนอื่นๆที่เกี่ยวข้องด้วย? … ผมไม่มีคำตอบในขณะนั้น และไม่อยากจะเสี่ยงหาคำตอบบนระบบที่มีคนใช้งานอยู่จริง (แบบว่ากล้วน่ะครับ ทำงานเป็น admin มาหลายปี ก็ยังทำใจไม่ได้ครับ)

    ผมมี database อยู่ในมือแล้ว มันมีปัญหา ก็ซ่อมมันสิ … สมมติว่า database ตัวที่ใช้งานจริงมีปัญหาแบบเดียวกัน จะทำยังไง? ก็ต้องหาวิธีการแก้ไขแบบเดียวกันใช่ใหม?

    เอ่อ … อันที่จริง วิธีการที่ดีกว่า สำหรับ ระบบที่ใช้งานอยู่จริงๆ ก็คือ “ป้องกัน” และ “สำรอง” ครับ แต่ในขณะที่ผมยังไม่มี ระบบที่ว่าอยู่ในมือ ก็สมมติเอาก่อนแล้วกันว่า ผมเจอปัญหากับ database พัง แล้วต้องหาวิธีการกู้ database ให้กลับมาใช้งานให้ได้ …

    โอเค, งั้นก็ได้ เริ่มกันเลย ใน error message ของ mysql เองก็มี URL ให้อยู่แล้ว ดีกว่าระบบอื่นๆอีก 2-3 ระบบที่ผมเคยเจอมา นอกเหนือจากจะไม่มีตัวช่วย ที่จะชี้ว่าจะไปหาข้อมูลเพิ่ม เพื่อแก้ปัญหาได้ที่ใหน ข้อมูลเกีี่ยวกับ error ที่แสดงออกมายัง มากเกินความจำเป็น ข้อมูลที่สำคัญที่เกี่ยวกับข้อผิดพลาด ถูกซ่อนเอาไว้ในกองของข้อมูลอื่นที่ไม่เกี่ยวข้อง ที่นอกจากจะไม่ช่วยในการแก้ปัญหาแล้ว ยังช่วยให้หลงทางอีกต่างหาก

    นี่, มี URL ให้แล้ว http://dev.mysql.com/doc/refman/5.1/en/forcing-recovery.html ก็เปิด web browser ขึ้นมาอ่านเลยสิ

    ถ้าคุณอ่านมาจนถึงบรรทัดนี้แล้ว และจะลอง ตัด/แปะ URL อันที่ว่านี่ใน web browser ดูนะครับ คุณก็จะเจอว่า

    แป่ว … Page Not Found …

    อันนี้ ไม่ใช่ว่า MySQL version 5.1 มันจะเก่าจนเกินไปนะครับ เพราะถ้าลองหาอย่างอื่นดู ในส่วนของ reference/manual ก็จะหาเจอ แต่ว่า ส่วนของ forcing-recovery สำหรับ InnoDB Engine อันที่จริงแล้ว อยู่ที่นี่ครับ

    http://dev.mysql.com/doc/refman/5.1/en/forcing-innodb-recovery.html

    มี bug อยู่ใน error message ของ MySQL ครับ

    แต่ถึงกระนั้น หลังจากลองทำตามดูตามคำแนะนำ ใน page ที่ว่านี่แล้ว (ปรับค่า innodb_force_recovery ขึ้นไปเรื่อยๆ แล้ว restart mysql ดู) ก็ยังมีปัญหาอย่างเดิม ยังไม่สามารถแก้ปัญหาได้

    อาจจะเป็นกรณีที่โชคยังดีอยู่บ้าง ตรงที่ว่า ถึงแม้จะมี error เกิดขึ้นแต่ตัว server ก็ยังทำงานได้ และรับการติดต่อจากตัว mysql client แสดงว่าส่วนของฐานข้อมูลที่มีปัญหา “น่าจะ” (ผมเดาเอา) ยังไม่ร้ายแรงมาก เมื่อลอง query ข้อมูลดูบางส่วนก็ปรากฏว่ายังใช้งานได้ หลังจากลองทำตามคำแนะนำใน mysql document เองแล้วงยังไม่ work หาอ่านคำแนะนำจากที่อื่น มีคำแนะนำให้ dump database ออกมา แล้วค่อยใส่กลับเข้าไปใหม่ ผมก็เลยลองดู …

    ปรากฏว่า วิธีการนี้สามารถใช้งานได้ มีขั้นตอนที่ผมลองผิดลองถูกเพิ่มขึ้นอีกหน่อยนึง ถึงจะทำให้ database กลับมาใช้งานได้ ก็เลยคิดว่าจะเอามาบันทึกไว้ที่นี่

    โดยสรุป: ผมยังไม่รู้วิธีการที่จะแก้ปัญหาทั้งหมดนะครับ สมมติว่า ถ้า database เกิดพังมากกว่านี้ วิธีการนี้ก็อาจจะใช้งานไม่ได้ (ผมเคยลองพัง database ที่ใช้ InnoDB engine แล้ว ปรากฏว่า mysqld ไม่สามารถ start ขึ้นมาทำงานได้ … ซึ่งถ้าเป้นกรณีนั้น วิธีการที่ผมจะพูดถึงก็ไม่มีประโยชน์อะไรเหมือนกัน) แต่ถ้ามันอาการแย่กว่า แบบที่ document ของ MySQL แนะนำเอาไว้แล้ว ทำตามแล้วยังมีปัญหาอยู่ วิธีการนี้ก็อาจจะมีประโยชน์อยู่บ้างในบางระดับ … ผมหวังเอาไว้อย่างนั้นนะครับ

    โดยหลักแล้วก็คือ การ dump database ออกมาเป็น sql file แล้วก็ import กลับเข้าไปแค่นั้นเองครับ … ในกรณีนี้ ผมยังไม่รู้ root password ของ mysql และ ผมจะเปลี่ยนมันทหลัง แต่ในตอนนี้ผมต้องการให้ database ทั้งหมดถูกต้อง และ mysql server กลับมาทำงานได้ โดย load ไม่ได้อยู่ที่ 90-100% ตลอดเวลาก่อน

    ขั้นตอน

    $ sudo service mysql stop
    $ sudo /usr/sbin/mysqld --skip-grant-tables &
    $ MYPID=$!
    $ mysqldump --all-databases > all.sql
    $ sudo kill -QUIT $MYPID

    เพื่อที่จะให้สามารถ dump database ได้ โดยที่ผมไม่รู้ root password ผมก็ต้องให้ mysql ทำงานใน mode ที่ไม่ตรวจสอบ privileges โดยการใช้ option “–skip-grant-tables” ซึ่งหลังจากนั้น ก็จะทำให้ผมใช้คำสั่ง mysqldump สำหรับ dump database ได้ เมื่อใช้ option –all-databases ก็จะเป็นการ dump ทุกๆ database ที่มีอยู่ และเก็บลงในไฟล์ชื่อ all.sql

    หลังจาก dump เสร็จก็ terminate mysqld process โดยการส่ง SIGQUIT signal ให้กับ process ของ mysqld

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

    $ sudo mv /var/lib/mysql /var/lib/mysql.0
    $ sudo mkdir /var/lib/mysql
    $ sudo chown mysql:mysql /var/lib/mysql
    $ sudo chmod 700 /var/lib/mysql

    หลังจากนั้นก็จะเป็นการ import ข้อมูลของ mysql กลับเข้าไป ซึ่งก็ต้อง start mysql server กลับขึ้นมาอีกรอบ และให้มันสร้างไฟล์อื่นๆที่จำเป็นใน mysql directory

    $ sudo /usr/sbin/mysqld --skip-grant-tables &
    $ MYPID=$!
    $ mysql < all.sql
    $ sudo kill -QUIT $MYPID
    
    

    ถึงขั้นตอนนี้ database ที่เรา dump ออกมาในตอนแรกก็ถูก import กลับเขาไปใน database อีกครั้ง รวมทั้ง privileges และ password ทั้งหมดด้วย ซึ่งเราสามารถใช้คำสั่ง

    $ sudo service mysql start

    เพื่อให้ database กลับมาทำงานตามปกติ

    ซึ่งในกรณีของผม ตัว database สามารถกลับมาทำงานได้ load ของ mysqld กลับเข้าสู่สภาวะปกติ (เข้าใกล้ 0 เมื่อไม่มีการใช้งาน) ไม่มีข้อความแสดง error ในไฟล์ /var/log/mysql/error.log จะมีแต่ข้อความแสดงการทำงานตามปกติ) และตัว application ที่ใช้งาน database ใช้งานได้อย่างถูกต้อง

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

  • Solved Firefox browser with adobe flash player outdated

    ผมรัน Firefox browser แล้วพบว่ามันจะแจ้งว่า Adobe Flash Player Plugin ไม่ปลอดภัยเนื่องจาก Outdated จึงคลิกเข้าไปในหน้า Addons แล้ว ดาวน์โหลดจากเว็บ http://get.adobe.com/flashplayer/ ได้ไฟล์ install_flash_player_11_linux.x86_64.tar.gz จากนั้นแตกไฟล์ออกมาอย่างง่ายๆ เพราะใช้ GUI อยู่ ได้ไฟล์มาดังนี้

    libflashplayer.so และไดเรกทอรี usr

    flashplayer-outdated

    แล้วเปิดอ่าน readme.txt สั่งว่าให้อัปเดตแบบ manual ก็ต้องทำตาม ทั้งๆที่อยากทำแบบคลิกๆๆๆ จะสะดวกสำหรับผู้ใช้มากกว่า

    ตอนนี้ผมอยู่ที่หน้าต่าง terminal ของ linux mint ทำตามดังนี้

    sudo cp libflashplayer.so /opt/mint-flashplugin-11/
    sudo cp -r usr/* /usr

    รัน firefox ใหม่ ตอนนี้ใช้งานได้แล้ว

    หมายเหตุ

    เครื่องที่ใช้งานคือ

    ผมใช้คำสั่ง cat /etc/lsb-release ได้ดังนี้

    DISTRIB_ID=LinuxMint
    DISTRIB_RELEASE=13
    DISTRIB_CODENAME=maya
    DISTRIB_DESCRIPTION="Linux Mint 13 Maya"

    ผมใช้คำสั่ง uname -a ได้ดังนี้

    Linux HP-ProBook-6450b 3.2.0-39-generic #62-Ubuntu SMP Thu Feb 28 00:28:53 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux

    จบครับ

  • Script สำหรับการ reset mysql root’s password

    สำหรับ admin ของ Linux Server โดยทั่วไปแล้ว มักจะหลีกไม่พ้นที่จะต้อง ติดตั้ง database server สักครั้งนึง เพราะว่า สำหรับ application โดยส่วนใหญ่แล้ว โดยเฉพาะ web application ในปัจจุบัน มักจะต้องการใช้ database server เป็น backend สำหรับการเก็บข้อมูล

    และบน Linux ส่วนใหญ่ก็จะหนีไม่พ้นการใช้งาน MySQL เป็น Server … เพราะเป็นตัวที่มีคนใช้กันมากที่สุด และรองรับโดยหลายๆ framework ของ Web App ทั้งหลาย

    แน่นอน ตอนที่ติดตั้งครั้งแรก ตัวซอฟต์แวร์ที่ใช้ในการติดตั้ง ก็ถาม root password ของ MySQL สำหรับการ กำหนดสิทธิในการใช้งาน

    สำหรับคนที่ใช้งาน database server มานานระดับหนึ่ง ก็จะรู้ว่า password ที่จะใช้สำหรับ root ของ MySQL นั้น ไม่จำเป็นจะต้องเป็นตัวเดียวกับ root password ของระบบ … และ ไม่ควรที่จะเป็นตัวเดียวกัน … สำหรับคนที่ไม่รู้ในเรื่องนี้ ก็มักจะต้อง “เรียนรู้” ด้วยความยากลำบากในทีหลัง … ผมรู้สิน่า ผม “จ่าย” ค่าเล่าเรียนเรื่องนี้ด้วยราคาที่ “แพง” พอสมควรเชียวล่ะ

    หลังจากที่รู้แล้วว่า มัน “จะต้อง” ไม่ใช่ password เดียวกันกับ password ที่สำคัญของระบบ ปัญหาที่จะตามมาก็คือว่า เราก็อาจจะตั้ง password ของ root ของ MySQL แบบง่ายๆ เช่น “1234”, “abcd” อะไรทำนองนี้ เพราะว่า ในแง่ของการให้บริการ MySQL server ก็มักจะให้บริการกับ “client” ท่อยู่บนเครื่องเดียวกัน ไม่ได้ให้บริการผ่าน network ไปให้เครื่องอื่นๆ หรือ ถ้าจำเป้นจะต้องให้บริการ ก้ให้บริการกับ server  ที่อยู่ใน cluster เดียวกัน จะมีเฉพาะ admin ด้วยกันที่จะรู้ว่ามี mysql ให้บริการอยู่บนเครื่องนี้

    แน่นอน ความคิดเช่นนี้ ก็มักจะนำบทเรียน “ราคาแพง” บทที่สอง เกี่ยวกับ root password ของ mysql มาให้ อีกเหมือนกัน และมันมักจะพ่วงมากับ “phpmyadmin” ที่จะทำให้ ตัว mysql server ซึ่งเคยเข้าใจว่า “ไม่สามารถเข้าถึงจาก server อื่นๆได้” กลับเข้าถึงได้ง่ายๆ ผ่าน “web” และเมื่อเจอกับ bot ที่โจมตีแบบอัตโนมัติ หายนะ … ก็มาเยือนได้ง่ายๆ เหมือนกัน

    นั่นก็จะเป็นบทเรียนราคาแพงอีกบทนึงเช่นกัน

    ข้อสรุปของผม หลังจากได้บทเรียนอย่างนั้นมาก็คือ password ของ root ไม่ว่าจะเป็นของระบบเอง หรือ จะเป็นของ database “จะต้อง” เป็นคนละตัวกัน  _และ_ จะต้องมีระดับของความปลอดภัยสูงใกล้ๆกัน

    ได้ password ที่ secure มา … ก็ดี … แต่ปัญหาของ password ที่ secure ก็คือ จำยาก … สำหรับ root’s password ของระบบ ที่ยังมีการใช้งานค่อนข้างบ่อย ก็จะมีโอกาสที่จะลืมได้น้อยกว่า … วิธีการที่จะทำให้ ไม่ลืม และยังมี password ที่ secure ก็มีแน่ๆ แต่นั่นไม่ใช่สิ่งที่ผมอยากจะพูดถึงในบันทึกนี้ (อันที่จริงก็เขียนเอาไว้ที่อื่นแล้วด้วย)

    ที่จะมาแก้ปัญหาในบันทึกนี้ ก็คือ password ของ root ของ MySQL … โอเค … ก็รู้แล้วล่ะว่าจะต้อง set ให้มัน secure … ก็ set ไปแล้ว แต่ก็ลืมไปแล้วด้วย (ฮา) … งั้นทำไงดีล่ะ?

    ครับ ถ้าลืม root password ไม่ว่าจะเป็นของระบบเอง หรือ จะเป็นของ MySQL server ถ้าจะให้สามารถ “ยึดอำนาจ” ของ root กลับมาได้ ก็คงต้องใช้วิธีการ reset password กัน

    วิธีการ reset password ของ root ระบบที่ใช้ Linux ที่ไม่ได้การ encrypt ระบบไฟล์เอาไว้ และ ไม่ได้ lock password ที่ระดับของ bios ก็ไม่ได้ยุ่งยากอะไรมาก … แต่ก็ไม่ได้เป็นเรื่องที่ตั้งใจจะเขียนในบันทึกฉบับนี้อีกเหมือนกัน และคิดว่า admin แทบทุกท่านก็น่าจะรู้วิธีการกันอยู่ดีพอสมควรแล้ว

    ที่ตั้งใจจะพูดถึงก็คือ การ reset password ของ root ของ MySQL Server

    ซึ่งอันที่จริง ก็ไม่ได้เป็นความลับอะไร ถ้าลอง search หาด้วย google ก็จะเจอในระยะไม่เกิน 2-3 click ของ mouse

    แต่โดยตัวผมเอง ที่จำเป็นจะต้อง เกี่ยวข้องกับ MySQL Server บนหลายเครื่อง และเกิดเหตุการณ์จำเป็นให้ต้อง reset password ของ MySQL อยู่ 2-3 รอบ ในช่วงไม่กี่เดือนที่ผ่านมา ก็ชักจะรู้สึกหงุดหงิอยู่นิดหน่อย ตรงที่ว่า วิธีการที่มี มันจะต้องทำแบบ manual และ จะต้องเปิด terminal ขึ้นมามากกว่า 2 terminal หรือ 2 tab เพื่อที่จะจัดการให้เสร็จเรียบร้อย เพราะจะต้อง run โปรแกรม server บน terminal นึง และ run โปรแกรม client อีก terminal นึง ขนานกันไป

    ทำอยู่ หลายรอบ … รอบสุดท้ายที่ทำ ก็ชักเบื่อ แล้วก็ถามตัวเองว่า จะเขียนมันเป็น script แล้ว run ครั้งเดียวเสร็จ ไม่ได้เชียวหรือ?

    ลองกลับไป ล้วง,แคะ,แกะ,เกา shell script และตัว mysqld กับ mysql client ใหม่อีกรอบ ก็ทำให้ได้ script ตัวนี้มาครับ

    #!/bin/sh
    
    PASSWD="$1"
    [ -z "$PASSWD" ] && echo "Usage: $0 newpassword" && exit
    
    [ `id -u` != "0" ] && echo "Must be root!" && exit
    
    # Is mysql installed?
    [ ! -x /usr/bin/mysql ]    && \
        echo "[?] Is mysql-server & mysql client installed?" && \
        exit
    
    RUNNING=0
    # is mysql server running?
    if [ "`ps auxw | grep [/]usr/sbin/mysqld`" ]; then
        echo "[*] mysqld is running, try to stop it..."
        RUNNING=1
        # yes, then shutdown it
        service mysql stop
        for i in `seq 1 10`; do
            [ "$i" = "10" ] && {
                echo "[?] Hmm? can't stop mysqld server";
                exit;
            }
            [ -z "`ps auxw | grep [/]usr/sbin/mysqld`" ] && {
                echo "[*] mysqld stopped!";
                break;
            }
            echo -n "."
            sleep 1
        done
        echo ""
    fi
    
    # Start mysqld not using grant tables
    echo "[*] Start mysqld without using grant tables"
    /usr/sbin/mysqld --skip-grant-tables 2>/dev/null &
    MPID=$!
    
    echo "[*] Wait for mysqld to be ready ..."
    sleep 5
    
    echo "[*] Now, set new password..."
    cat <<EOT | /usr/bin/mysql
    UPDATE mysql.user SET Password=PASSWORD("$PASSWD") WHERE User='root';
    FLUSH PRIVILEGES;
    EOT
    
    echo "[*] Done! now stop the mysqld again ..."
    kill -QUIT $MPID
    sleep 3
    
    [ "$RUNNING" = "1" ] && {
        echo "[*] Restart mysqld using normal running method..";
        service mysql start;
    }

    วิธีการ ใช้งาน  ก็ save โปรแกรมลงในไฟล์ สมมติว่าชื่อ reset-mysql-root-password

    $ chmod +x bin/reset-mysql-root-password
    $ bin/reset-mysql-root-password NewSecretPasswd2013

    ครับ เรียกใช้โปรแกรมตามด้วย password ที่ต้องการจะให้ set ใหม่แค่นั้นเองครับ

    ตัว script เขียนขึ้นมาเพื่อให้ใช้งานได้กับ Debian และ Ubuntu

    ผมใช้ delay เพื่อรอให้การ start/stop ของ mysqld เสร็จเรียบร้อยก่อนที่จะทำงานต่อไป ซึ่งก็ขึ้นอยู่กับว่า load ของเครื่อง และความเร็วของเครื่องที่ใช้อยู่เป้นอย่างไร ถ้ามีปัญหา ก็อาจจะต้องปรับเพิ่มค่า delay ที่มีอยู่ใน script ให้มากขึ้นกว่าเดิมอีกหน่อยครับ

    จริงๆแล้วผมควรที่จะอธิบายเพิ่มว่า เทคนิคที่ใช้ใน script มีอะไรบ้าง แต่ก็คาดว่า หลายๆคนที่เตยเจอปัญหาแบบเดียวกัน ก็น่าจะเคยอ่านจาก document ของ MySQL เองแล้ว ก็น่าจะไม่จำเป็นมาก ที่ผมต้องการจะเสนอในที่นี ก็คือ tool เป้น shell script ที่จะช่วยให้ทำงานให้ง่ายขึ้น แทนที่จะต้องใช้วิธีการแบบ manual น่ะครับ

    แต่ยังไงถ้ามีข้อสงสัย ก็ comment / ถามมาก็แล้วกันนะครับ ผมจะกลับมาตอบ ถ้ามีคำถามครับ 🙂

  • วิธีลดขนาดไฟล์ Microsoft Word ให้เล็กลง

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

    บ่อยครั้ง เป็นภาพ “หน้าจอ” โดยการ “Print Screen” หรือ การ เอาภาพจากกล้องดิจิตอล ซึ่งปัจจุบันมีความละเอียดสูง เช่นกล้อง 5MP เป็นอย่างต่ำ หรือกล้องใหม่ๆ 12 MP กันเลยทีเดียว ซึ่งภาพที่ได้จะมีขนาดใหญ่มาก เมื่อนำมาใส่ในไฟล์เอกสาร แล้วทำการ Resize ภาพ หรือ Crop เอาเฉพาะบางส่วนของภาพ ก็มักจะคิดว่า ขนาดของไฟล์ จะลดลงไปด้วย

    ซึ่งความจริงไม่ใช่อย่างนั้น เพราะโปรแกรม Microsoft Word จะยังเก็บภาพขนาดเดิมเอาไว้ เพียงแต่เลือกแสดงบางส่วน หรือ ลดขนาดการแสดงผลเท่านั้น จึงทำให้ไฟล์มีขนาดใหญ่ … และอาจจะเป็นปัญหาได้ เมื่อไฟล์มีขนาดใหญ่มากๆ, การส่งไฟล์ผ่าน email ก็จะมีขนาดใหญ่ จนบางครั้ง ผู้รับไม่สามารถรับได้ เช่นส่งไฟล์ขนาดเกิน 25 MB ไปยัง Gmail/Hotmail เป็นต้น

    วิธีการลดขนาดไฟล์

    1. คลิกที่ภาพในไฟล์ >Pictures Tools > Format > Compress Pictures
    แล้วเลือกความละเอียดขนาดแค่ email ก็พอ
    คลิก OK แล้ว Save

    MicrosoftWord-PictureCompress

    2. ผลการลดขนาด จะทำให้ไฟล์เดิมขนาด 6 MB ลดเหลือ 2 MB ในทันที

    fileresize-result

  • เปลี่ยน uptime timestamp ในคำสั่ง dmesg ให้เป็น date/time timestamp

    เคยใหมครับ เมื่อใช้คำสั่ง dmesg เพื่อที่จะดู kernel message แล้วสงสัยว่า เวลาที่แสดงในเครื่องหมาย square bracket ที่อยู่ด้านหน้าของ message น่ะมันเป็นเวลาเท่าไหร่กันแน่?

    อาจจะไม่บ่อยนัก แต่ผมก็เคยมีปัญหานั้น

    ถ้าใช้คำสั่ง dmesg บน command line เราอาจจะเห็น message ประมาณนี้

     [   10.738140] udev[228]: starting version 164
     [   12.840798] ACPI: PCI Interrupt Link [LNKD] enabled at IRQ 9
     [   12.842168] PCI: setting IRQ 9 as level-triggered
     [   12.843002] pci 0000:00:04.0: PCI INT A -> Link[LNKD] -> GSI 9 (level, low) -> IRQ 9
     [   12.856454] input: PC Speaker as /devices/platform/pcspkr/input/input2
     [   12.911121] vboxguest: major 0, IRQ 9, I/O port d020, MMIO at 00000000f0400000 (size 0x400000)
     [   12.912531] vboxguest: Successfully loaded version 3.2.10_OSE (interface 0x00010004)
     [   13.165633] piix4_smbus 0000:00:07.0: SMBus base address uninitialized - upgrade BIOS or use force_addr=0xaddr
     [   13.233473] parport_pc 00:05: reported by Plug and Play ACPI
     [   13.314410] ACPI: AC Adapter [AC] (on-line)
     [   13.318154] input: Power Button as /devices/LNXSYSTM:00/LNXPWRBN:00/input/input3
     [   13.345820] ACPI: Power Button [PWRF]
     [   13.376871] input: Sleep Button as /devices/LNXSYSTM:00/LNXSLPBN:00/input/input4
     [   13.420217] ACPI: Sleep Button [SLPF]
     [   13.455000] input: ImExPS/2 Generic Explorer Mouse as /devices/platform/i8042/serio1/input/input5
     [   15.017345] Intel ICH 0000:00:05.0: PCI INT A -> Link[LNKA] -> GSI 5 (level, low) -> IRQ 5
     [   15.019308] Intel ICH 0000:00:05.0: setting latency timer to 64
     [   15.344130] intel8x0_measure_ac97_clock: measured 54439 usecs (10177 samples)
     [   15.346177] intel8x0: measured clock 186943 rejected
     [   15.704139] intel8x0_measure_ac97_clock: measured 55930 usecs (10470 samples)
     [   15.705569] intel8x0: measured clock 187198 rejected
     [   16.064125] intel8x0_measure_ac97_clock: measured 55919 usecs (10451 samples)
     [   16.065415] intel8x0: measured clock 186895 rejected
     [   16.066703] intel8x0: clocking to 48000
     [   17.234971] EXT3 FS on dm-0, internal journal
     [   17.967548] loop: module loaded
     [   23.754984] Adding 8388600k swap on /dev/mapper/lv-nilanont--swap.  Priority:-1 extents:1 across:8388600k
     [   30.580221] kjournald starting.  Commit interval 5 seconds
     [   30.581590] EXT3 FS on dm-2, internal journal
     [   30.582599] EXT3-fs: mounted filesystem with ordered data mode.
     [   31.094231] FAT: utf8 is not a recommended IO charset for FAT filesystems, filesystem will be case sensitive!
     [   35.777014] e1000: eth0 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: RX
     [   39.640499] fuse init (API version 7.13)
     [   40.373844] binfmt_misc: Unknown symbol bprm_change_interp
     [   40.469771] binfmt_misc: Unknown symbol bprm_change_interp
     [   41.587335] vboxsf: Successfully loaded version 3.2.10_OSE (interface 0x00010004)
     [   52.668229] lp: driver loaded but no devices found
     [   52.742090] ppdev: user-space parallel port driver
     [   64.384194] device eth0 entered promiscuous mode
     [   66.379973] device eth0 left promiscuous mode
     [   73.139965] tun: Universal TUN/TAP device driver, 1.6
     [   73.141787] tun: (C) 1999-2004 Max Krasnyansky <maxk@qualcomm.com>

    ตัวเลขที่อยู่ภายใน [ ] เช่น “[   73.141787]” ในบรรทัดสุดท้าย 73.141787 หมายความว่า ข้อความของ kernel ในบรรทัดนั้น

    tun: (C) 1999-2004 ...

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

    ซึ่งการแสดงผลแบบนี้ จะไม่มีปัญหาซักเท่าไหร่ สำหรับเครื่องที่เพิ่งเปิดขึ้นมาใหม่ๆ เช่นเครื่อง desktop หรือ notebook ทั่วไป แต่สำหรับเครื่องที่ทำงานเป็น Server ซึ่งเปิดทิ้งเอาไว้เป็นชั่วนาตาปี, โขง, ชี, มูล, ปิง, วัง, ยม, น่าน นั้น บางทีเวลาเราใช้คำสั่ง dmesg ออกมา อาจจะเห็นเป็น message แบบนี้

     ...
     [201089.204812] br0: topology change detected, propagating
     [201089.204815] br0: port 1(eth0) entering forwarding state
     [201089.204838] br0: port 1(eth0) entering forwarding state
     [237531.522878] br0: port 1(eth0) entering forwarding state
     [237531.671857] r8169 0000:02:00.0: eth0: link down
     [237531.671888] r8169 0000:02:00.0: eth0: link down
     ...

    จะเห็นว่าบรรทัด “br0: port 1(eth0) enter forwarding state” ในสองบรรทัดแรก กับบรรทัดที่สาม จะเกิดห่างกันพอสมควร ระยะเวลาห่างกันเท่าไหร่ เป็นเวลากี่วินาที ก็เอาตัวเลขทั้งสองตัวมาหักลบกันได้ ยุ่งยากนิดหน่อย แต่ก็ไม่มากจนเกินไป

    แต่ปัญหาที่เกิดขึ้นก็คือ หลายครั้ง ผมอยากจะรู้ว่า “มันเกิดขึ้นเมื่อไหร่?” มากกว่า ที่อยากจะรู้ว่า “มันเกิดขึ้นหลังจากครั้งที่แล้วนานเท่าไหร่?”

    อืม แล้วถ้าเป็นอย่างนั้นผมจะหาคำตอบได้ยังไง?
    เนื่องจากเวลาที่คำสั่ง dmesg แสดงออกมา เป็นเวลานับเป็นจำนวนวินาที นับตั้งแต่เครื่องเริ่มต้นทำงาน เพราะฉะนั้น ถ้าเรา “รู้” ว่าเราเริ่มเปิดเครื่องเมื่อไหร่ เราก็สามารถ ใช้เวลานั้นเป็นเวลาตั้งต้น แล้วบวกเวลาด้วยจำนวนวินาทีที่ผ่านไป แล้วค่อยแปลงเวลานั่นกลับมาเป็นวันเดือนปีอีกครั้ง เราก็จะรู้เวลาที่เกิดเหตุการณ์นั้นขึ้นใช่ใหมครับ

    ฟังดูไม่ยาก … รึเปล่า?

    เวลาที่เครื่องเริ่มต้นทำงานเราจะเอามันมาจากใหนได้บ้างล่ะนี่?

    ถ้าเครื่องของเราเพิ่ง reboot มาเมื่อไม่นานเท่าไหร่ เราอาจจะใช้คำสั่ง last ในการหาเวลา boot ของเครื่องได้โดยประมาณแบบนี้

     $ last | grep reboot
     reboot   system boot  3.2.0-4-amd64    Tue Mar 19 18:14 - 02:25 (6+08:10)

    อืม … เครื่องของผมเพิ่ง reboot ไปเมื่อวันที่ 19 มีค. เวลา 18:14 และ เวลาขณะที่เขียนบันทึกฉบับนี้ เวลาผ่านไปแล้ว 6 วัน 8 ชม. กับอีก 10 นาที … แต่ว่า วิธีการนี้จะมีปัญหากับเครื่อง boot มาแล้วตั้งแต่เดือนที่แล้ว หรือ นานกว่านั้น เพราะ /var/log/wtmp จะเก็บข้อมูลเฉพาะเดือนนี้ และ โดยทั่วไปแล้วจะมีข้อมูลเก็บย้อนหลังอีกแค่ 1 เดือนเท่านั้น (สำหรับ server ที่ผมดูแลอยู่ ข้อมูลของ wtmp จะระบุใน logrotate config ให้เก็บย้อนหลังไปอย่างน้อย 12 เดือน) … ไม่ว่าอย่างไร วิธีการนี้จะมีปัญหาถ้าต้องการที่จะได้ข้อมูลที่ถูกต้องแน่นอนทุกครั้ง

    งั้นมีวิธีใหนอีกบ้าง? คำสั่ง uptime ล่ะ

     $ uptime
     02:31:56 up 6 days, 8:17, 23 users, load average: 1.00, 0.98, 0.95

    แต่ … เอ วิธีการนี้เราจะได้มาแค่ ระยะเวลาที่เครื่องทำงานอยู่เท่านั้นนี่นา ไม่ได้บอกว่าเครื่อง ‘เริ่ม’ ทำงานเมื่อไหร่ … ใช่ใหม?

    ใช่แล้วครับ! แต่เรามีคำสั่งที่จะช่วยอีก 1 คำสั่งคือคำสั่ง date

     $ date;uptime
     Tue Mar 26 02:33:05 ICT 2013
     02:33:05 up 6 days, 8:18, 23 users, load average: 0.85, 0.95, 0.94

    เอาล่ะ คราวนี้ เราได้เวลา ‘ขณะนี้’ ของเครื่องออกมาแล้ว จากคำสั่ง date และเราได้ ‘ระยะเวลาที่เครื่องทำงาน’ มาจากคำสั่ง uptime เพราะฉะนั้น ถ้าผมนับย้อนหลังจากเวลา ‘ขณะนี้’ ไปเท่ากับเวลา ‘uptime’ ผมก็จะได้เวลาที่เครืองเริ่มต้นทำงานขึ้นมา … ใช่ใหมครับ

    start_time = "Tue Mar 26 02:33:05 ICT 2013" - "6 days, 8:18"

    เอ่อ … จะง่ายไปใหมเนี่ย เอามันมาลบได้อย่างนั้นน่ะ? ถ้าทำอย่างได้จริง ชีวิตของ sysadmin ก็คงง่ายขึ้นครับ แต่ชีวิตจริงของ admin มันไม่ง่ายดายขนาดนั้น

    แต่ถ้าจะให้ผมแปลง string ของวันที่กลับมาเป็นตัวเลข และแปลง string ของระยะเวลา กลับมาเป็นตัวเลข โดยใช้ shell script เองก็คงไม่ไหวเหมือนกันแหละครับ 🙂

    บน unix เรามี tools ซึ่งช่วยในการจัดการเรื่อง วัน/เวลาอยู่แล้ว คำสั่งนั้นก็คือ คำสั่ง date เพื่อนเก่าของเราน่ะเอง

    ถ้าเราใช้คำสั่งนี้แบบธรรมดา เราก็จะได้ string ของ ข้อความที่เป็นแบบที่เรียกว่า human readable date string ออกมาตามตัวอย่าง  เราสามารถระบุให้คำสั่ง date แสดงข้อความออกมาเป็นรูปแบบอื่นๆได้อีกด้วย เช่น แสดงเวลานับเป็นวินาทีเริ่มจาก เวลาอ้างอิงของระบบปฏิบัติการแบบ unix ที่เรียกกันโดยทั่วไปว่า epoch ซึ่งเวลาที่ว่านั่นก็คือ 1970-01-01 00:00:00 … ถ้าจะถาม ว่าทำไมเวลาอ้างอิง หรือ epoch ถึงต้องเป็นวัน/เวลานี้ด้วย  คำตอบคงจะต้องเท้าความถึงประวัติศาสตร์ของ Unix ค่อนข้างไกล … ซึ่งคงจะออกนอกเรื่องไปอีกพอสมควร เพราะฉะนั้นขอละไว้เพียงแค่นี้ก่อนก็แล้วกันครับ ผู้ที่สนใจลองหาอ่านเองโดยใช้ google ช่วยได้ครับ

    คำสั่ง date สามารถแสดงผลออกมาได้หลากหลายรูปแบบมาก รายละเอียดสามารถอ่านได้จาก man page ของคำสั่ง date นะครับ แต่สำหรับกรณีของการแสดงเวลาเป็นวินาทีนับจากเวลาอ้างอิง ก็สามารถใช้คำสั่ง

    date +%s

    รูปแบบ +%… จะเป็นการกำหนดรูปแบบการแสดงผล นะครับ ตัวอย่างเช่น บนเครื่องของผมขณะที่เขียนบันทึกอยู่ขณะนี้

     $ date;date +%s
     Tue Mar 26 02:50:49 ICT 2013
     1364241049

    เวลา 1364241049 วินาทีนับจาก epoch ก็เป็นเวลาเดียวกันกับ Tue Mar 26 02:50:49 ICT 2013 เพียงแต่แสดงในรูปแบบที่ต่างกัน

    เอาล่ะเราได้เวลาที่เป็น เวลาปัจจุบัน แสดงออกมาเป็นวินาทีแล้ว … แล้วทีนี้ ค่าเวลา uptime ล่ะ เราจะแปลง “6 days, 8:18” หรือ 6 วัน 8 ชม. 18 นาที มาเป็นตัวเลขวินาทียังไง … ดูแล้วก็น่าจะไม่ยากมากเท่าไหร่ … แต่ก็ไม่ง่าย …(ฮา)

    จริงๆแล้ว ก็มีวิธีง่ายๆอยู่ครับ ค่าของ uptime เราสามารถอ่านมาจากไฟล์ /proc/uptime ได้ โดยไม่ต้องไปแปลงจาก string ของ “N days, HH:MM” โดยตรง

     $ cat /proc/uptime
     549627.40 1106209.61

    ตัวเลขตัวแรกเป็น uptime ส่วนตัวที่สองเป็น cpu idle time

    เอ๊ะ.. ทำไม idle time ถึงมากกว่า uptime ล่ะ? … คือว่าเครื่องที่ผมใช้งานอยู่มันมี core ของ processor อยู่จำนวน 4 core ใน 1 cpu น่ะครับ ค่า idle time นับของ cpu ทุกตัวรวมกัน ตัวเลขก็เลยออกมาสูงกว่า cpu uptime

    เอาล่ะ เราสามารถ หาวัน/เวลา ที่เครื่องเริ่มต้นทำงานได้ โดยการใช้คำสั่งของ shell ได้คร่าวๆประมาณนี้ครับ

     NOW=`date +%s`
     UPTIME=`cat /proc/uptime | cut -f1 -d.`
     STARTTIME=`expr $NOW - $UPTIME`
     echo $STARTTIME

    เวลาของ UPTIME เราใช้เฉพาะหน่วยวินาทีโดยการใช้ จุดเป็นตัวคั่น แล้วตัดส่วนที่เหลือทิ้งไปทั้งหมด (รวมทั้ง idle time ด้วย) ส่วนการคำนวณ ก็ใช้คำสั่ง expr เป็นตัวช่วย

    แต่เวลาที่ได้เป็นตัวเลข จำนวนเป็นวินาทีนี่!! ดูไปก็ยังไม่รู้ว่ามันเป็นวัน/เวลาใหนกันแน่ … จะต้องแปลงตัวเลขกลับยังไงนี่

    ตรงนี้ไม่ยากครับ ใช้คำสั่ง date อีกนั่นแหละ เราสามารถใช้ option -d หรือ –date= สำหรับการระบุเวลาที่ต้องการเป็นเวลาอื่น แทนที่จะเป็นเวลา “ปัจจุบัน” ได้ โดยที่ ถ้าเราจะระบุเวลาเป็น จำนวนวินาทีนับจากเวลาอ้างอิง — epoch ก็ให้ใส่เครื่องหมาย @ หน้าตัวเลขนั้นครับ ซึ่ง สามารถลองได้ประมาณนี้

     $ date -d @0
     Thu Jan  1 07:00:00 ICT 1970
     $ date -d @1363691682
     Tue Mar 19 18:14:42 ICT 2013

    ถ้าใช้ @0 นั่นก็คือระบุเวลาอ้างอิงเลย ก็จะได้เป็นวันที่ 1 มค. 1970 เวลา 00:00:00 UTC — เวลาที่แสดงเป็น 07:00:00 ICT ซึ่งเป็นเวลาของประเทศไทย (=GMT+7)  และ เวลาที่เครื่องคอมพิวเตอร์ที่ผมใช้ reboot ครั้งสุดท้ายเมื่อ วันที่ 19 มีค. เวลา 18:14 น.

    ส่วนที่เหลือก็คือ รับข้อมูลแต่ละบรรทัดจาก คำสั่ง dmesg เข้ามาแล้วก็แปลงข้อมูลของ time stamp ที่อยู่ในรูปแบบ uptime ซึ่งอยู่ระหว่างเครื่องหมาย [] ข้างหน้าออกมา ส่วนนี้ สามารถใช้คำสั่ง read ช่วย เช่น dmesg บรรทัดสุดท้ายได้ข้อมูลออกมาเป็น

     $ dmesg | tail -1
     [288163.997319] br0: port 1(eth0) entering forwarding state

    คำสั่ง read จะอ่านค่ามาเก็บไว้ในตัวแปร โดยจะแบ่งโดยใช้ space

     $ dmesg | tail -1 | (read STAMP REST; echo "STAMP=$STAMP REST=$REST")
     STAMP=[288163.997319] REST=br0: port 1(eth0) entering forwarding state

    ในกรณีนี้ เราจะได้ค่าเวลา ซึ่งมี เครื่องหมาย [] คร่อม อยู่ในตัวแปร shell ชื่อ STAMP และ ส่วนที่เหลือทั้งหมด จะอยู่ในตัวแปร shell ที่ชื่อว่า REST

    ในส่วนของ เวลา อาจจะอยู่ในรูปแบบ  [ 0.00000] ก็ได้ ซึ่งจะมีช่องว่างอยู่หลัง ‘[‘ และก่อนตัวเลข ซึ่งสามารถกำจัดทิ้งได้ ด้วยคำสั่ง sed และ เครื่องหมาย ‘]’ ด้านหลังตัวเลขก็จะสามารถตัดทิ้งได้ด้วย sed เช่นเดียวกัน หรือ อาจจะใช้คำสั่ง tr  ด้วย option -d ก็ได้

    ตัวเลขที่ได้ ตัดเอามาเฉพาะส่วนที่เป็นวินาที แล้วเอามาบวกกับค่า STARTTIME โดยใช้คำสั่ง expr เป็นตัวช่วยเหมือนเดิม

    เอาล่ะ ใช้สิ่งที่ได้เรียนรู้จากข้างต้น เราก็จะสามารถเขียนเป็น shell script ออกมาได้ประมาณนี้

    #!/bin/sh
    DSTR1="+%b %d %H:%M:%S"                    # Syslog format Month Date Hour:Min:Sec
    DSTR2="+%Y-%m-%d %H:%M:%S"                 # ISO format
    NOW=`date +%s`                             # current timestamp in second
    UPTIME=`cat /proc/uptime | cut -f1 -d.`    # current uptime
    START=`expr $NOW - $UPTIME                 # start time of this machine
    cat - | sed 's/^\[ *//' |\
    while read STAMP REST; do
        SEC=`echo $STAMP | tr -d '[]' | cut -f1 -d.`
        TIME=`expr $START + $SEC`
        DATE=`date -d @$TIME "$DSTR1"`
        echo "$DATE $REST"
    done

    ส่วนที่เพิ่มขึ้นมาก็คือ การเลือกแสดงผล ออกมาเป้นรูปแบบเดียวกับ date/time stamp ที่ใช้ใน log file (DSTR1) หรือ จะใช้แบบ ISO format (DSTR2)

    วิธีการใช้งาน script ตัวนี้ ซึ่งสมมติให้มีชื่อว่า dmesg-datestamp ก็ใช้แบบนี้ครับ

    $ dmesg | ./dmesg-datestamp

    ซึ่งในกรณีที่มี ข้อความใน dmesg จำนวนมาก ก็อาจจะใช้เวลาค่อนข้างนานเพราะในแต่ละบรรทัด ของ dmesg จะต้องมีการคำนวณ เวลาและแปลงเป็นวันที่ใหม่ โดยการใช้คำสั่ง expr และ date ตามลำดับ ซึ่งสามารถลดเวลาที่ใช้ในการแสดงผล โดยเลือกเอาเฉพาะ dmesg ในบรรทัดที่เราสนใจได้ เช่น

    $ dmesg | grep eth0 | ./dmesg-datestamp

    หรืออาจจะ redirection ลงไปเก็บไว้ในไฟล์ แล้วอ่านจากไฟล์ แทนที่จะอ่านจากคำสั่ง dmesg ทุกครั้ง

    สำหรับคนที่ ทนอ่านมาจนถึงบรรทัดนี้ แล้วกำลังจะบอกผมว่า … จริงๆแล้ว ไม่ต้องทำถึงขนาดนั้นก็ได้ครับ คำสั่ง /bin/dmesg มี option -T ซึ่ง สามารถแปลง time stamp ให้อยู่ในรูปแบบ human readable ให้อยู่แล้วโดยไม่จำเป็นจะต้องพึ่งพา shell script ใดๆเลย

    แหะๆ ครับ Hit with Brick

    ผมก็ทราบแล้วครับ … แต่ว่านั่นมันหลังจากผมเขียน script เสร็จแล้ว (ฮา)

    บน Debian Linux ถ้าใช้ wheezy ก็จะสามารถใช้

    $ dmesg -T

    ได้เลย แต่บน Debian squeeze ซึ่งเป็น stable release ของ Debian ในตอนนี้ คำสั่ง dmesg ยังไม่สามารถใช้งาน option -T ได้ แต่ปัญหานี้ก็คงมีอีกไม่นานเท่าไหร่ เพราะ wheezy กำลังจะกลายเป็น stable release ในของ Debian ภายในไม่ช้า …

    แต่ … จะทำยังไงได้ล่ะ ก็เขียนไปแล้วนี่ …

    ส่วนใครที่อ่านมาจนถึงบรรทัดนี้แล้ว … ก็อ่านไปแล้วนี่ ช่วยอะไรไม่ได้เนอะ …

    ขอจบเอาดื้อๆแค่นี้แหละคร้าบ …

  • shell script for download virtualbox packages Part 2

    หลังจาก เกริ่นนำ เสียยืดยาว แต่ ยังไม่เข้าสู่เนื้อหาใน ภาคแรก
    ก็ขอเอามาต่อภาคสองที่นี่ …
    ความเดิมตอนที่แล้ว โจทย์มีอยู่ว่า

    เราต้องการ download package ของ virtualbox ตัวล่าสุด และต้องการไฟล์ package สำหรับ
    1. Windows
    2. Ubuntu โดยแยกเป็น
    2.1 Ubuntu 12.04 — precise pangolin
    2.2 Ubuntu 12.10 — quantal quetzal
    ทั้งสอง revision ต้องการ package สำหรับทั้ง i386 และ amd64 architecture
    3. Extension pack
    4. User manual
    5. ไฟล์ MD5SUMS สำหรับตรวจสอบไฟล์ ที่ download มา

    โดยไฟล์ทั้งหมดจะเก็บอยู่ใน http://download.virtualbox.org/virtualbox/$version
    ส่วนของตัวเลข $version นั้นเรามีวิธีการที่จะใช้ script ในการอ่านค่านั้นมาโดยอัตโนมัติเอาไว้แล้ว ใน ตอนแรกของบทความ ชุดนี้ (เรียกซะเป็นชุด แต่จริงๆแล้วก็มีแค่ 2 เท่านั้นแหละครับ)

    ไฟล์ที่ต้องการจริงๆนั้นมีอะไรบ้าง?
    1. Windows
    http://download.virtualbox.org/virtualbox/4.2.8/VirtualBox-4.2.8-83876-Win.exe
    2. Ubuntu
    2.1 Ubuntu 12.04 มี 2 file สำหรับ 2 architecture
    http://download.virtualbox.org/virtualbox/4.2.8/virtualbox-4.2_4.2.8-83876~Ubuntu~precise_amd64.deb
    http://download.virtualbox.org/virtualbox/4.2.8/virtualbox-4.2_4.2.8-83876~Ubuntu~precise_i386.deb

    เอาเท่านี้ก่อน … จะเห็นว่า URL ที่จะใช้ในการเข้าถึงไฟล์นั้น จะซับซ้อนพอดู
    ส่วนแรกที่เหมือนกัน ก็คือ URL ที่ระบุไปถึง directory และ version คือ

    http://download.virtualbox.org/virtualbox/4.2.8/

    จะเหมือนกัน แต่หลังจากนั้น ส่วนของไฟล์จะได้เป็น

    VirtualBox-4.2.8-83876-Win.exe
    virtualbox-4.2_4.2.8-83876~Ubuntu~precise_amd64.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~precise_i386.deb

    และไฟล์ของ Ubuntu 12.10

    virtualbox-4.2_4.2.8-83876~Ubuntu~quantal_amd64.deb
    virtualbox-4.2_4.2.8-83877~Ubuntu~quantal_i386.deb

    นั่นแน่ะ ตัวเลข release เฉพาะของ Ubuntu 12.10 เองก็ยังต่างกัน ระหว่าง amd64 (83876) กับ i386 (83877) อีกต่างหาก ถ้าจะระบุชื่อเองโดยหาข้อมูล version/sub version/release บวกกับ architecture บวกกับการตั้งชื่อที่แตกต่างกัน (ตัวเล็ก/ใหญ่ ของชื่อ VirtualBox ระหว่าง Windows กับ Ubuntu) ก็คงจะได้ script ที่ซับซ้อนน่าดู

    Crying

    แต่จริงๆแล้วมีวิธีการที่ง่ายกว่านั้น

    ก่อนอื่นเรามา list ไฟล์ที่มีอยู่ออกมาดูกันก่อน เราสามารถใช้คำสั่ง

    wget -q -O- http://download.virtualbox.org/virtualbox/4.2.8/

    ก็จะเห็น ข้อมูลของหน้า download อยู่ใน format ของ html มีขยะอะไรที่เราไม่ต้องการเยอะแยะไปหมด เราต้องการเฉพาะบรรทัดที่อ้างถึงไฟล์ที่เราสามารถเอามาใช้ download ได้ ลองสังเกตดู เราก็จะเห็นว่า บรรทัดเหล่านั้นจะเริ่มบรรทัดด้วย ‘<A HREF=’ … งั้นเอาละ เราก็กรองมาเฉพาะบรรทัดเหล่านั้น

    wget -q -O- http://download.virtualbox.org/virtualbox/4.2.8/ | grep '^<A HREF'

    สวยงามขึ้น … แต่เราต้องการเฉพาะชื่อไฟล์ ไม่ได้ต้องการ html format ทั้งบรรทัด ในกรณีนี้ ส่วนที่เราต้องการอยู่ระหว่างเครื่องหมาย double qoute (“) คู่แรก ซึ่งเราใช้คำสั่ง cut ช่วยแยกสิ่งที่เราต้องการออกมาได้ โดยเพิ่มคำสั่งเป็น

    wget -q -O- http://download.virtualbox.org/virtualbox/4.2.8/ | grep '^<A HREF' | cut -f2 -d\"

    ผลลัพธ์คือ

    /virtualbox/
    MD5SUMS
    Oracle_VM_VirtualBox_Extension_Pack-4.2.8-83876.vbox-extpack
    Oracle_VM_VirtualBox_Extension_Pack-4.2.8.vbox-extpack
    SDKRef.pdf
    SHA256SUMS
    UserManual.pdf
    VBoxGuestAdditions_4.2.8.iso
    VirtualBox-4.2-4.2.8_83876_el4-1.i386.rpm
    VirtualBox-4.2-4.2.8_83876_el5-1.i386.rpm
    VirtualBox-4.2-4.2.8_83876_el5-1.x86_64.rpm
    VirtualBox-4.2-4.2.8_83876_el6-1.i686.rpm
    VirtualBox-4.2-4.2.8_83876_el6-1.x86_64.rpm
    VirtualBox-4.2-4.2.8_83876_fedora16-1.i686.rpm
    VirtualBox-4.2-4.2.8_83876_fedora16-1.x86_64.rpm
    VirtualBox-4.2-4.2.8_83876_fedora17-1.i686.rpm
    VirtualBox-4.2-4.2.8_83876_fedora17-1.x86_64.rpm
    VirtualBox-4.2-4.2.8_83876_fedora18-1.i686.rpm
    VirtualBox-4.2-4.2.8_83876_fedora18-1.x86_64.rpm
    VirtualBox-4.2-4.2.8_83876_mdv2010.0-1.i586.rpm
    VirtualBox-4.2-4.2.8_83876_mdv2010.0-1.x86_64.rpm
    VirtualBox-4.2-4.2.8_83876_mdv2011.0-1.i586.rpm
    VirtualBox-4.2-4.2.8_83876_mdv2011.0-1.x86_64.rpm
    VirtualBox-4.2-4.2.8_83876_openSUSE114-1.i586.rpm
    VirtualBox-4.2-4.2.8_83876_openSUSE114-1.x86_64.rpm
    VirtualBox-4.2-4.2.8_83876_sles10.1-1.i586.rpm
    VirtualBox-4.2-4.2.8_83876_sles10.1-1.x86_64.rpm
    VirtualBox-4.2-4.2.8_83876_sles11.0-1.i586.rpm
    VirtualBox-4.2-4.2.8_83876_sles11.0-1.x86_64.rpm
    VirtualBox-4.2.8-83876-Linux_amd64.run
    VirtualBox-4.2.8-83876-Linux_x86.run
    VirtualBox-4.2.8-83876-OSX.dmg
    VirtualBox-4.2.8-83876-SunOS.tar.gz
    VirtualBox-4.2.8-83876-Win.exe
    VirtualBox-4.2.8.tar.bz2
    VirtualBoxSDK-4.2.8-83876.zip
    virtualbox-4.2_4.2.8-83876~Debian~squeeze_amd64.deb
    virtualbox-4.2_4.2.8-83876~Debian~squeeze_i386.deb
    virtualbox-4.2_4.2.8-83876~Debian~wheezy_amd64.deb
    virtualbox-4.2_4.2.8-83876~Debian~wheezy_i386.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~hardy_amd64.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~hardy_i386.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~lucid_amd64.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~lucid_i386.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~natty_amd64.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~natty_i386.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~oneiric_amd64.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~oneiric_i386.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~precise_amd64.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~precise_i386.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~quantal_amd64.deb
    virtualbox-4.2_4.2.8-83877~Ubuntu~quantal_i386.deb

    สวยงามยิ่งกว่าเดิม … มี directory ที่เราไม่ต้องการโผล่มา 1 คือ “/virtualbox/” ซึ่งสามารถใช้ grep -v เพื่อทิ้งไปได้ แต่สำหรับกรณี ไม่จำเป็น

    จากไฟล์ที่ list ออกมา ถ้าพิจารณาดูหน่อยนึง เราก็จะเห็นว่า งานที่เราจำเป็นจะต้องทำ ไม่ต้องไปสร้าง ชื่อของไฟล์ให้ยุ่งยากซับซ้อนเกินไปก็ได้ เพราะใน list ที่เรามีอยู่ เราสามารถเลือก “grep” ส่วนที่เราต้องการได้เลย เช่น ไฟล์ package สำหรับ ubuntu 12.04 ก็สามารถเลือก grep โดยใช้ string “precise” ได้เลยเช่น

    wget -q -O- http://download.virtualbox.org/virtualbox/4.2.8/ | grep '^<A HREF' | cut -f2 -d\" | grep "precise"

    ก็จะได้ผลลัพธ์เป็น

    virtualbox-4.2_4.2.8-83876~Ubuntu~precise_amd64.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~precise_i386.deb

    ถ้าต้องการ ubuntu 12.10 ด้วยล่ะ ก็เพิ่ม option -E สำหรับคำสั่ง grep และเปลี่ยน string ที่จะใช้ grep นิดหน่อยเป็น “precise|quantal” ก็จะได้คำสั่งเป็น

    wget -q -O- http://download.virtualbox.org/virtualbox/4.2.8/ | grep '^<A HREF' | cut -f2 -d\" | grep -E "precise|quantal"

    และได้ผลลัพธ์เป็น

    virtualbox-4.2_4.2.8-83876~Ubuntu~precise_amd64.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~precise_i386.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~quantal_amd64.deb
    virtualbox-4.2_4.2.8-83877~Ubuntu~quantal_i386.deb

    อะฮ่า … งดงามเป็นอย่างยิ่ง โดยใช้ trick เดียวกันเราก็จะได้คำสั่ง สำหรับ “เลือก”ไฟล์ที่เราต้องการทั้งหมด ได้เป็น

    wget -q -O- http://download.virtualbox.org/virtualbox/4.2.8/ | grep '^<A HREF=' | cut -f2 -d\" | grep -E "Win|precise|quantal|MD5SUM|UserManual|Extension_Pack"

    โอเค คราวนี้เราก็พร้อมที่จะดัดแปลงมันเป็น shell script สำหรับการ download VirtualBox package ที่เราต้องการแล้วละครับ โดยส่ง list รายการของไฟล์ทั้งหมดไปเก็บไว้ในตัวแปร แล้วก็ใช้การวน loop download มาทีละไฟล์ประมาณนี้ครับ

    #!/bin/sh
    
    VERS="4.2.8"
    VBOXDL="http://download.virtualbox.org/virtualbox/$VERS"
    LIST=`    wget -q -O- $VBOXDL                        |\
        grep '^<A HREF='                        |\
        cut -f2 -d\"                            |\
        grep -E "Win|precise|quantal|MD5SUM|UserManual|Extension_Pack"    |\
        grep -v "Extension_Pack-${VERS}-"                `
    
    for file in $LIST; do
        wget ${VBOXDL}/${file}
    done

    บรรทัด grep -v “Extension_Pack-${VERS}-” ที่เพิ่มขึ้นมาก็เพื่อตัดให้เหลือไฟล์ extension pack ที่ต้อง download เพียงไฟล์เดียว เพราะทั้งสองไฟล์ถึงจะมีชื่อไฟล์ต่างกัน แต่จริงๆแล้วก็เป็นไฟล์เดียวกันครับ

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

    อันนี้ ทิ้งไว้เป็นโจทย์ ให้ลองหัดทำกันดูเก็แล้วกันนะครับ

    ถ้ามีปัญหาแก้ไม่ได้ ก็ขอให้ comment มาครับ เผื่อว่า ผมจะได้ idea สำหรับเขียน blog ใหม่เพิ่ม เพราะว่า ถ้าปล่อยให้คิดเอง ก็คิดไม่ออกเหมือนกัน (ฮา)

    สุดท้ายนี้ ขอขอบคุณเจ้าของ email ต้นเรื่องนะครับ ที่ทำให้ผมเขียน blog เพิ่มขึ้นได้อีก 2 บทความ ถ้าไม่ได้คำถามดีๆ ผมก็ไม่มีอะไรให้เขียนจริงๆครับ ขอบคุณอีกครั้งครับ

  • shell script for download virtualbox packages

    หลังจากบรรยายเรื่อง Shell script ไปในสัปดาห์ที่แล้ว ด้วยหัวข้อเรื่องที่มีอยู่ค่อนข้างเยอะกว่าที่คิดไว้ และใช้เวลาบรรยายไปอย่างไม่ค่อยมีประสิทธิภาพสักเท่าไหร่ ทำให้เนื้อเรื่องบางส่วน ต้องทิ้งเอาไว้ ไม่ได้พูดต่อให้จบ ปล่อยคนที่เอา slide มาอ่านงงไปก็แล้วกันว่า มันคือเรื่องอะไรกัน

    เรื่อง slide ก็ … เหอะ … เดี๋ยวมีโอกาส ค่อยกลับไปปรับปรุงมันอีกที ผมยังพอมี idea อยู่บ้างว่าจะแก้ไขมันยังไง แต่เรื่องหนึ่งซึ่งจริงๆแล้ว มีปัญหาตั้งแต่ก่อนเริ่มบรรยายแล้วว่า จะยกตัวอย่างโจทย์ปัญหาอย่างไรดี ที่จะทำให้เห็น วิธีการที่ผมใช้ในการ “เขียน” shell script ตัวนึงออกมาได้ ตัวอย่างที่ยกให้ดูในตอนแรกของ การบรรยาย เป็นแค่ตัวอย่างหลอกๆ ที่เขียนไปเพื่อให้เห็น รูปแบบ ของการใช้ shell script แต่จะว่าไป ที่ผมใช้งานอยู่ปัจจุบัน ก็ไม่ได้ตัวอย่างที่ว่านั่น จะใช้ตัวอย่างอื่นที่มี ตัวอย่างเช่น psuautosigned ก็คงต้องอธิบายในเรื่องอื่นกันยาว ก่อนที่จะวกกลับมาที่เรื่องของ shell script ได้

    จนกระทั่งวันนี้ ได้รับ email ฉบับนี้มา

    > อาจารย์ครับ
    >    มีเรืองปรึกษครับ
    >
    > สิ่งที่ต้องการคือ wget file หลายๆ  file ตามข้างล่างนี้
    > wget
    > http://download.virtualbox.org/virtualbox/${version1}/VirtualBox-${version1}-83876-Win.exe
    > wget
    > http://download.virtualbox.org/virtualbox/${version1}/virtualbox-4.2_${version1}-83877~Ubuntu~quantal_i386.deb
    > wget
    > http://download.virtualbox.org/virtualbox/${version1}/virtualbox-4.2_${version1}-83876~Ubuntu~quantal_amd64.deb
    > wget
    > http://download.virtualbox.org/virtualbox/${version1}/virtualbox-4.2_${version1}-83876~Ubuntu~precise_i386.deb
    > wget
    > http://download.virtualbox.org/virtualbox/${version1}/virtualbox-4.2_${version1}-83876~Ubuntu~precise_amd64.deb
    > wget http://download.virtualbox.org/virtualbox/${version1}/MD5SUMS
    > wget
    > http://download.virtualbox.org/virtualbox/${version1}/Oracle_VM_VirtualBox_Extension_Pack-${version1}-83876.vbox-extpack
    > wget http://download.virtualbox.org/virtualbox/${version1}/UserManual.pdf
    >
    > ผมเขียน  script แบบนี้
    > version1=4.2.8
    > url1=http://download.virtualbox.org/virtualbox/${version1}
    > for i in Win  quantal  precise Oracle MD5 User
    >     do
    >        wget  ${url1}/*{i}*
    >    done
    > ไม่ work ครับ
    > ขอคำแนะนำด้วยครับ

    ผมตอบกลับไปว่า

    |        ใช้ *{i}* หรือ *${i}* ไม่ได้ครับ เพราะ * ที่ใช้บน command line
    |        ปกติ จะเป็นตัวอักษรที่ตีความโดย shell ที่เราใช้งานอยู่ ซึ่งโดยทั่วไป
    |        มันก็จะพยายาม match กับ ชื่อไฟล์ ใน directory บนเครื่องของเราครับ
    |
    |        ทีนี้ ตอนท่ีส่งให้กับ wget ถ้า shell ไม่สามารถ expand ตัว * ออกมาเป็น
    |        ไฟล์ได้ มันก็จะส่ง * ไปยังเครื่องปลายทาง ซึ่งก็จะเปรียบเทียบแบบ literal
    |        คือเป็นตัวอักษร * โดยตรงโดยไม่ได้พยายาม match กับไฟล์ที่มีอยู่ใน
    |        directory นั้นๆ
    |
    |        วิธีการหนึ่งที่อาจจะใช้สำหรับการ download ทั้งหมดมาได้ ก็คือใช้ option -r
    |        แต่ว่านั่นก็จะได้ไฟล์ ที่เราไม่ต้องการมาอีกหลายๆไฟล์
    |
    |       สำหรับกรณีนี้ วิธีการที่ สามารถใช้ในการแก้ปัญหาได้ก็คือ อ่าน list ของไฟล์
    |       ที่มีอยู๋ทั้งหมดมาก่อน แล้วค่อยมาเลือกชื่อไฟล์ที่เราต้องการ เอามาเก็บไว้ใน
    |       list ที่เราต้องการ download อีกรอบนึง ครับ
    |
    |       อันที่จริงแล้ว นี่เป็นคำถามที่ดีมาที่จะใช้ยกตัวอย่างในเรื่องของการใช้งาน
    |       shell script เลยทีเดียวครับ
    |
    |       ผมกำลังหา หัวข้อเรื่องที่เหมาะสมสำหรับเขียนเป้น blog ลงใน psu sysadmin
    |       พอดีเลยครับ ผมขอเอาคำตอบไปตอบในโน้นนะครับ

    จริงๆแล้วแอบร้องยูเรก้าอยู่ในใจ เพราะนี่แหละสิ่งที่ผมต้องการ ไม่ต้องรอคำอนุญาตจากเจ้าของ email ผมก็รีบเอามา post ที่นี่ทันทีถ้าไม่มีคำถามดีๆ ผมก็ไม่มีไอเดีย อะไรที่จะเอามาเขียนเป็น blog เหมือนกันครับ ของคุณเจ้าของ email ที่ส่งมาถามเป็นอย่างมากเลยครับขอเริ่มจากอธิบายโจทย์ใหม่อีกสักรอบก่อนนะครับโจทย์ คือต้องการที่จะ download package ของ virtualbox ใหม่ล่าสุดที่มีอยู่บน site ของ virtualbox มาเก็บไว้ที่ local เพื่อให้การติดตั้ง package บนเครื่องคอมพิวเตอร์ในเครือข่าย ซึ่งมีอยู่หลายเครื่อง จะได้ไม่ต้องไป download มาสำหรับแต่ละเครื่องให้เปลือง bandwidthถ้าหากว่า เครื่องคอมพิวเตอร์ที่ใช้ เป็น Linux และติดตั้ง Ubuntu Linux หรือ Debian Linux และต้องการ
    VirtualBox ตัวที่ทันสมัย แต่ไม่ถึงกับต้องการตัว “ล่าสุด” จริงๆ ปัญหานี้ก็สามารถแก้ได้ง่ายๆ โดยติดตั้ง
    Virtualbox บนเครื่อง โดยการใช้คำสั่ง

    sudo apt-get install virtualbox

    ซึ่งบนเครื่อง Debian Wheezy ที่ผมใช้งานอยู่ปัจจุบัน ก็จะมี Virtualbox 4.1.8 ให้ใช้ติดตั้งได้เลย โดยไม่ต้อง download มาจาก website ของ virtualbox

    แต่ถ้าต้องการ virtualbox version ล่าสุด ซึ่ง version stable ล่าสุด ซึ่งประกาศ release อยู่บน website ของ virtualbox (ณ วันนี้ 6 มีค. 2556) คือ 4.2.8 ซึ่งก็ … อะ แฮ่ม … ต่างกันไม่มากเท่าไหร่

    แต่ถ้าต้องการ version ล่าสุด หรือ package สำหรับ Windows หรือ Mac OSX ด้ว ก็คงต้องใช้วิธีการ download มาจาก website ของ virtualbox โดยตรงจาก https://www.virtualbox.org/wiki/Downloads ครับ

    กลับมาที่ โจทย์ ต่อ

    เราต้องการ download package ของ virtualbox ตัวล่าสุด และต้องการไฟล์ package สำหรับ
    1. Windows
    2. Ubuntu โดยแยกเป็น
    2.1 Ubuntu 12.04 — precise pangolin
    2.2 Ubuntu 12.10 — quantal quetzal
    ทั้งสอง revision ต้องการ package สำหรับทั้ง i386 และ amd64 architecture
    3. Extension pack
    4. User manual
    5. ไฟล์ MD5SUMS สำหรับตรวจสอบไฟล์ ที่ download มา

    ไฟล์ของ virtualbox ซึ่งสร้างเป็น link ไว้ ในหน้า download จะเก็บอยู่บน http://download.virtualbox.org/virtualbox/4.2.8/ สำหรับ version 4.2.8

    ซึ่งถ้าเรา list มาดูจะได้ ประมาณนี้

    [DIR]  Parent Directory
    [   ]  MD5SUMS                                                          01-Mar-2013 02:11     3K    
    [   ]  Oracle_VM_VirtualBox_Extension_Pack-4.2.8-83876.vbox-extpack     27-Feb-2013 12:56    10M    
    [   ]  Oracle_VM_VirtualBox_Extension_Pack-4.2.8.vbox-extpack           27-Feb-2013 12:56    10M    
    [   ]  SDKRef.pdf                                                       27-Feb-2013 12:55     2M    
    [   ]  SHA256SUMS                                                       01-Mar-2013 02:12     5K    
    [   ]  UserManual.pdf                                                   27-Feb-2013 12:55     5M    
    [BIN]  VBoxGuestAdditions_4.2.8.iso                                     27-Feb-2013 13:00    54M    
    [   ]  VirtualBox-4.2-4.2.8_83876_el4-1.i386.rpm                        27-Feb-2013 12:45    82M    
    [   ]  VirtualBox-4.2-4.2.8_83876_el5-1.i386.rpm                        27-Feb-2013 12:45    81M    
    [   ]  VirtualBox-4.2-4.2.8_83876_el5-1.x86_64.rpm                      27-Feb-2013 13:01    81M    
    [   ]  VirtualBox-4.2-4.2.8_83876_el6-1.i686.rpm                        27-Feb-2013 12:45    67M    
    [   ]  VirtualBox-4.2-4.2.8_83876_el6-1.x86_64.rpm                      27-Feb-2013 13:01    67M    
    [   ]  VirtualBox-4.2-4.2.8_83876_fedora16-1.i686.rpm                   27-Feb-2013 12:45    66M    
    [   ]  VirtualBox-4.2-4.2.8_83876_fedora16-1.x86_64.rpm                 27-Feb-2013 12:45    66M    
    [   ]  VirtualBox-4.2-4.2.8_83876_fedora17-1.i686.rpm                   27-Feb-2013 12:45    66M    
    [   ]  VirtualBox-4.2-4.2.8_83876_fedora17-1.x86_64.rpm                 27-Feb-2013 12:45    66M    
    [   ]  VirtualBox-4.2-4.2.8_83876_fedora18-1.i686.rpm                   27-Feb-2013 12:46    68M    
    [   ]  VirtualBox-4.2-4.2.8_83876_fedora18-1.x86_64.rpm                 27-Feb-2013 12:46    68M    
    [   ]  VirtualBox-4.2-4.2.8_83876_mdv2010.0-1.i586.rpm                  27-Feb-2013 12:46    69M    
    [   ]  VirtualBox-4.2-4.2.8_83876_mdv2010.0-1.x86_64.rpm                27-Feb-2013 12:46    69M    
    [   ]  VirtualBox-4.2-4.2.8_83876_mdv2011.0-1.i586.rpm                  27-Feb-2013 12:46    61M    
    [   ]  VirtualBox-4.2-4.2.8_83876_mdv2011.0-1.x86_64.rpm                27-Feb-2013 12:46    61M    
    [   ]  VirtualBox-4.2-4.2.8_83876_openSUSE114-1.i586.rpm                27-Feb-2013 12:46    61M    
    [   ]  VirtualBox-4.2-4.2.8_83876_openSUSE114-1.x86_64.rpm              27-Feb-2013 12:46    61M    
    [   ]  VirtualBox-4.2-4.2.8_83876_sles10.1-1.i586.rpm                   27-Feb-2013 12:46    80M    
    [   ]  VirtualBox-4.2-4.2.8_83876_sles10.1-1.x86_64.rpm                 27-Feb-2013 12:46    80M    
    [   ]  VirtualBox-4.2-4.2.8_83876_sles11.0-1.i586.rpm                   27-Feb-2013 12:46    73M    
    [   ]  VirtualBox-4.2-4.2.8_83876_sles11.0-1.x86_64.rpm                 27-Feb-2013 12:47    73M    
    [BIN]  VirtualBox-4.2.8-83876-Linux_amd64.run                           27-Feb-2013 12:55    79M    
    [BIN]  VirtualBox-4.2.8-83876-Linux_x86.run                             27-Feb-2013 12:55    79M    
    [   ]  VirtualBox-4.2.8-83876-OSX.dmg                                   27-Feb-2013 12:55   103M    
    [   ]  VirtualBox-4.2.8-83876-SunOS.tar.gz                              27-Feb-2013 12:55   113M    
    [BIN]  VirtualBox-4.2.8-83876-Win.exe                                   27-Feb-2013 12:55    93M    
    [   ]  VirtualBox-4.2.8.tar.bz2                                         28-Feb-2013 02:22    72M    
    [   ]  VirtualBoxSDK-4.2.8-83876.zip                                    27-Feb-2013 13:00     9M    
    [   ]  virtualbox-4.2_4.2.8-83876~Debian~squeeze_amd64.deb              27-Feb-2013 09:00    60M    
    [   ]  virtualbox-4.2_4.2.8-83876~Debian~squeeze_i386.deb               27-Feb-2013 08:49    60M    
    [   ]  virtualbox-4.2_4.2.8-83876~Debian~wheezy_amd64.deb               27-Feb-2013 10:20    60M    
    [   ]  virtualbox-4.2_4.2.8-83876~Debian~wheezy_i386.deb                27-Feb-2013 10:05    61M    
    [   ]  virtualbox-4.2_4.2.8-83876~Ubuntu~hardy_amd64.deb                27-Feb-2013 12:12    82M    
    [   ]  virtualbox-4.2_4.2.8-83876~Ubuntu~hardy_i386.deb                 27-Feb-2013 11:59    79M    
    [   ]  virtualbox-4.2_4.2.8-83876~Ubuntu~lucid_amd64.deb                27-Feb-2013 09:42    71M    
    [   ]  virtualbox-4.2_4.2.8-83876~Ubuntu~lucid_i386.deb                 27-Feb-2013 09:33    71M    
    [   ]  virtualbox-4.2_4.2.8-83876~Ubuntu~natty_amd64.deb                27-Feb-2013 08:38    59M    
    [   ]  virtualbox-4.2_4.2.8-83876~Ubuntu~natty_i386.deb                 27-Feb-2013 08:26    59M    
    [   ]  virtualbox-4.2_4.2.8-83876~Ubuntu~oneiric_amd64.deb              27-Feb-2013 11:46    59M    
    [   ]  virtualbox-4.2_4.2.8-83876~Ubuntu~oneiric_i386.deb               27-Feb-2013 11:30    60M    
    [   ]  virtualbox-4.2_4.2.8-83876~Ubuntu~precise_amd64.deb              27-Feb-2013 09:24    60M    
    [   ]  virtualbox-4.2_4.2.8-83876~Ubuntu~precise_i386.deb               27-Feb-2013 09:10    60M    
    [   ]  virtualbox-4.2_4.2.8-83876~Ubuntu~quantal_amd64.deb              27-Feb-2013 08:56    59M    
    [   ]  virtualbox-4.2_4.2.8-83877~Ubuntu~quantal_i386.deb               01-Mar-2013 01:45    60M

    ถ้าต้องการ download มาทั้งหมด ก็ทำได้โดยการใช้คำสั่ง

     wget -r -np -nH --cut-dirs=2 http://download.virtualbox.org/virtualbox/4.2.8/

    แต่ก็จะเห็นว่าเราจะได้ไฟล์จำนวนมากที่เราไม่ต้องการมาด้วย เราต้องการไฟล์จริงๆเพียงไม่กี่ไฟล์ เพราะฉะนั้น ก็ต้องทำงานเพ่ิมขึ้นอีกหน่อยนึง

    แต่ก่อนที่จะเริ่ม จากการ download ไฟล์จาก directory ที่เราต้องการ ซึ่งเรารู้ตำแหน่งที่แน่นอนอยู่แล้ว จากการสำรวจแบบ manual มาตั้งข้อสมมติฐานก่อนว่า ถ้าเมื่อไหร่ virtualbox มีการ release version ใหม่ออกมา เราก็ควรที่จะรู้ได้โดยอัตโนมัติ หรืออย่างน้อย เราก็ไม่น่าจะจำเป็นที่จะต้องมาเขียน script ใหม่ … คำถามคือ เราจะรู้ได้ใหม? และ ถ้าได้ เราจะรู้ได้อย่างไร

    คำตอบแรกคือ ได้ คำตอบถัดมามี อยู่หลายทางเลือกครับ แต่ถ้าเอาแบบไม่ซับซ้อนมากนัก วิธีการที่ผมใช้แบบ manual อยู่ก็คือ เข้าไปดูที่ https://www.virtualbox.org/wiki/Downloads ซึ่งในหน้าที่ก็จะ list ตัว package ของ virtualbox ที่จะให้ download สำหรับ OS ต่างๆ และจะมี ระบุ version เอาไว้ด้วย

    ในบรรทัดที่ใช้สำหรับระบุ URL ให้ download ของ Linux ก็จะเขียนไว้ว่า

    VirtualBox 4.2.8 for Linux hosts

    ตัวเลข 4.2.8 คือ version ซึ่งจะเปลี่ยนไปเรื่อยๆ เมื่อ Virtualbox มีการ update เป็น version ใหม่
    เราสามารถ เอาตัวเลขนี้ มาได้ โดยการใช้ความร่วมมือระหว่าง โปรแกรม 2 ตัว คือ wget กับ grep โดยที่คำสั่ง

    wget -q -O- http://www.virtualbox.org/wiki/Downloads

    เราจะได้ ข้อมูลของ page ดังกล่าวอยู่ในรูป html ปรากฏขึ้นบนหน้าจอ … เราต้องการเฉพาะบรรทัดที่ระบุ URL สำหรับ Linux ก็สามารถใช้ grep สำหรับ ตัดมา เฉพาะบรรทัดนั้นได้ โดยการใช้คำสั่ง

    wget -q -O- http://www.virtualbox.org/wiki/Downloads | grep 'for Linux hosts'

    ซึ่งจะได้ผลลัพธ์ออกมาเป็น

    </li><li><strong><a href="/wiki/Linux_Downloads">VirtualBox 4.2.8 for Linux hosts</a></strong>

    และ โดยการใช้คำสั่ง sed เพื่อที่จะตัดข้อความข้างหน้า ตั้งแต่ <li><s… ไปจนถึง VirtualBox ทิ้งไป ก็สามารถเพิ่มคำสั่งเข้าไปเป็น

    wget -q -O- http://www.virtualbox.org/wiki/Downloads  |\
    grep 'for Linux hosts' |\
    sed -e 's/^.*VirtualBox //'

    ก็จะได้ผลลัพธ์ออกมาเป็น

    4.2.8 for Linux hosts</a></strong>

    คราวนี้ เพิ่มส่วนของการกำจัดข้อความด้านหลังตัวเลข version โดยใช้ expression ที่สองของ sed ก็จะได้คำสั่งเป็น

    wget -q -O- http://www.virtualbox.org/wiki/Downloads  |\
     grep 'for Linux hosts' |\
     sed -e 's/^.*VirtualBox //' -e 's/ for Linux hosts.*$//'

    และได้ผลลัพธ์ออกมาเป็น

    4.2.8

    สวยงามไม่มีที่ติ Smile
    เอาน่า … เชื่อผมหน่อยน่า บอกว่าสวยก็สวยสิ!

    หลังจากพยายามมาสักพักเราก็ได้ตัวเลข version มา … ซึ่งจะเสียเวลากับมันมากไปหรือเปล่า? …
    เอาน่า โดยตัวเลขที่มีอยู่นี้ เราสามารถเขียน script สำหรับการตรวจสอบว่า Virtualbox มีการ update version ใหม่หรือเปล่า โดยการเอา code ข้างต้น มาปรับให้เป็น script ได้ประมาณนี้

    #!/bin/sh
    
    VBOXWIKIDL="http://www.virtualbox.org/wiki/Downloads"
    
    getVBoxVersion() {
        VERS=`wget -q -O- $VBOXWIKIDL          |\
              grep 'for Linux hosts'           |\
              sed  -e 's/^.*VirtualBox //'      \
                   -e 's/ for Linux hosts.*$//' `
    }
    
    getVBoxVersion
    echo "Last VirtaulBox Version = $VERS"

    เขียนส่วนของ code ให้ execute ใน backtick เพื่อส่งค่ามาให้กับตัวแปร shell VERS ซึ่งเราจะเอาไปใช้ต่อไป ใช้ตัวแปร VBOXWIKIDL สำหรับการระบุ URL ของ download page เพื่อให้ code อ่านง่ายขึ้น และเขียนในรูปแบบของ shell function เพื่อที่เราจะเอาฟังก์ชันนี้ไปใช้ในงานอื่นต่อไปในอนาคต

    กำหนด ชื่อให้ script เป็น vboxvers แล้วกำหนดให้มัน execute ได้โดยการใช้คำสั่ง

    chmod +x vboxvers

    และทดสอบ

    ./vboxvers

    เราก็จะได้ script สำหรับแสดง version ล่าสุดของ VirtualBox ที่มีให้ download จาก website ของ VirtualBox … แต่ถ้าให้ดีขึ้นไปกว่านี้ ตัว script นี้ควรจะทำงานโดยอัตโนมัติ โดยการตรวจสอบวันละครั้ง และถ้ามี version ที่ใหม่กว่า ก็ค่อยส่ง email มาแจ้งเรา

    ซึ่งเราสามารถทำได้โดย การเพิ่ม code สำหรับการเก็บข้อมูล version ล่าสุดที่เคยตรวจสอบเอาไว้ในไฟล์ และเปรียบเทียบ ตัวเลขนั้นกับ version ใหม่ที่มี สมมติให้ไฟล์นั้นชื่อ .vboxvers.txt เก็บไว้ใน HOME ของเราเอง

    FILE="$HOME/.vboxvers.txt"

    ถ้ามีไฟล์นั้นอยู่ เราก็อ่านค่า version ล่าสุดที่เคยตรวจสอบมาจากไฟล์นั้น แต่ถ้าไม่มีไฟล์ ก็กำหนดค่า version เริ่มต้นให้เป็น “0.0.0” ซะ

    if [ ! -f "$FILE" ]; then
            LAST="0.0.0"
    else
            LAST=`cat $FILE`
    fi

    หลังจากนั้น เราก็จะอ่านค่า version ล่าสุดจาก web ของ VirtualBox มาโดยใช้ ฟังก์ชัน getVBoxVersion ที่เขียนเอาไว้แล้ว

    เอาค่าที่ได้มาเปรียบเทียบกัน ถ้าไม่เท่ากัน ก็ส่ง email ให้กับ address ที่กำหนดเอาไว้

    if [ "$LAST" != "$VERS" ]; then
            echo "New VirtualBox $VERS available" |\
            mail -s "VirtualBox $VERS" $EMAIL
            # Save new version
            echo "$VERS" > $FILE
    fi

    ซึ่งก็จะได้ script สุดท้ายมีหน้าตาประมาณนี้ครับ

    #!/bin/sh
    
    VBOXWIKIDL="http://www.virtualbox.org/wiki/Downloads"
    FILE="$HOME/.vboxvers.txt"
    EMAIL="me@example.com" # Change to your email address!
    
    getVBoxVersion() {
        VERS=`wget -q -O- $VBOXWIKIDL          |\
              grep 'for Linux hosts'           |\
              sed  -e 's/^.*VirtualBox //'      \
                   -e 's/ for Linux hosts.*$//' `
    }
    
    # Check whether we have version file
    # if not use initial 0.0.0 version
    # if there is, then get the last version from that file
    if [ ! -f "$FILE" ]; then
            LAST="0.0.0"
    else
            LAST=`cat $FILE`
    fi
    
    # Then get lastest version from VirtualBox web
    getVBoxVersion
    
    # Then check and report
    if [ "$LAST" != "$VERS" ]; then
            echo "New VirtualBox $VERS available" |\
            mail -s "VirtualBox $VERS" $EMAIL
            # Save new version
            echo "$VERS" > $FILE
    fi

    ว้า … ยังไม่ไปถึงใหนเลย … แต่ชักจะยาวเกินไปแล้ว ขอตัดจบแค่นี้ก่อนครับ เดี๋ยวจะกลับมาต่อเรื่องจะ download package ไฟล์ที่เราต้องการจาก download.virtualbox.org ได้ยังไง ใน blog ถัดไปครับ Exciting (กรรมวิธีในการเพิ่มจำนวน blog ทั้งที่มีเนื้อหาที่จะเล่าเท่าเดิม … (ฮา) …)