jiyuu-מחשבות פתוחות

SQLite ו-Full Text Search

15/05/2008 22:15 ע"י כתריאל טראום

כאשר הוספתי יכולות חיפוש ל-Jiyuu, החלטתי קודם לבדוק האם יש ל-SQLite יכולות של Full Text Searching. לשמחתי ולדאבוני, גיליתי כי יכולת כזו קיימת, אך רק כהרחבה, אשר אינה זמינה ברוב אם לא בכל ההפצות כיום. מסיבה זו החלטתי להשתמש בשאילתות סטנדרטיות עם LIKE.

כאשר בחנתי את הרחבת ה-Full Text Search גיליתי כי היא מעניינת ומלאת אפשרויות, ולכן החלטתי לחקור אותה קצת ולשתף אתכם עם המסקנות.

הקדמה קצרה

Full Text Search היא טכניקת חיפוש שמאפשרת חיפוש מהיר (בד"כ) בכמות גדולה של מסמכים או כל מקור טקסט אחר. כאשר כמות המסמכים שיש לחפש בהם היא קטנה, ביצע חיפוש רגיל (חיפוש סריאלי או סדרתי) בד"כ יספק תוצאות במהירות סבירה יחסית. אבל מה עושים כאשר יש מאות או אלפי מסמכים לחפש בהם?

Full Text Searching (או FTS) היא טכניקה אשר מחולקת ל-2 חלקים: יצירת Index וחיפוש:

  • יצירת Index הוא תהליך של מיפוי הטקסט או טקסטים ויצירת Index בר-חיפוש של הטקסט.
  • תהליך החיפוש הוא תהליך שליפת המידע. החיפוש עצמו לא ניגש לטקסטים אלא ל-Index שנוצר מבעוד מועד ומאפשר שליפה מהירה של הטקסט, בלי סריקה מלאה שלו.

דוגמה קלאסית מעולם הקוד הפתוח לאפליקציית Full Text Searching היא Beagle אשר משתמש במנוע Indexing בשם Lucene, ליצירת Index של מידע אישי ואחזור מהיר שלו (מסמכים\אימיילים ועוד).

FTS ו-SQLite

בעולם מסדי הנתונים, שימוש ב-FTS אינו קיים או מוגדר כתקן (ANSI SQL), ובד"כ אם יש אפשרות כזו, זו היא הרחבה של מסד הנתונים.

יש הטוענים, ובצדק, כי מסד נתונים הוא אינו המקום לאחסן כמויות או את סוג המידע שיצריך שימוש ב-FTS. לראיה, מספר הפרויקטים\מוצרים (פתוחים או לא) אשר מיישים FTS לאחזור מידע וטקסט (ואינם מסדי נתונים) הוא די גדול.

אבל אני טוען, כי בימינו, כמות המידע אשר נדחס לתוך מסדי נתונים הולך וגדל. עם העליה של כמות הבלוגים והתוכן בהם, כמות אתרי החדשות והתוכן בהם וכו', השימוש ב-FTS כבר לא כל כך מופרך במסדי נתונים.

SQLite, כמו מסדי נתונים אחרים, מיישם גם הוא FTS, כהרחבה, אשר לצערי עדיין אינה נכללת ברוב ההפצות. קימפול של sqlite עם FTS הוא יחסית פשוט ומוסבר בקישור הבא. שימו לב כי ההוראות הן לגבי fts1 אבל נכונות גם לגרסאות החדשות יותר fts2\fts3 אשר נכללות בגרסאות האחרונות של sqlite.

בניית טבלאת FTS

לאחר בנייה של sqlite עם fts3, ניתן להתחיל לצור טבלאות אשר יבנו Index של המידע שנוסף אליהן. כדי לבנות טבלה אשר תשתמש ב-FTS משתמשים בתחביר הבא:

create virtual table email using fts3(subject, content)

פקודה זו תצור טבלה אשר יבנה לה Index מסוג FTS, ותאפשר אחזור מידע בעזרת פקודות SQL מתאימות.

הכנסת מידע

הכנסת מידע לטבלה נעשית בצורה סטנדרטית, ללא צורך בשינוי התחביר הסטנדרטי של SQLite

INSERT INTO email (subject, content) VALUES \
('meeting summary','summary for tuesday 23rd meeting \
 about bugs in the soup');
INSERT INTO email (subject, content) VALUES \
('trip plans','trip plans for upcoming flight to europe');

אחזור מידע

באחזור המידע מתגלה הכח והגמישות של הרחבת ה-FTS של SQLite. ההרחבה מאפשרת שליפה לא רק מהירה יותר כאשר מדובר בכמות מסמכים גדולה, אלא גם גמישה. שליפה סטנדרטית היא בעזרת שימוש במילת הברירה MATCH:

sqlite> SELECT subject, content FROM email \
WHERE content MATCH 'soup';
meeting summary|summary for tuesday 23rd meeting about bugs in the soup

FTS מאפשר שליפות שונות נוספות, למשל שליפה עם שלילה של מילים:

sqlite> SELECT * FROM email WHERE content MATCH 'for -meeting';
trip plans|trip plans for upcoming flight to europe

מכיוון ששאילתות FTS יכולות להכיל רק MATCH אחד, ניתן גם להשתמש בשם העמודה בשאילתה:

sqlite> SELECT subject FROM email where email MATCH \
'subject:plans content:europe';

trip plans

מכיוון שכל המידע שנשמר בטבלאות FTS הוא מידע שעובר אינדוקס מלא, לפעמים נוטים לחלק את הטבלאות ל-2: טבלאות metadata וטבלאות FTS. ניתן לאחזר מידע ע"י שימוש ב-JOIN וכך לקבל את כל המידע בשאילתה אחת. למשל הרצף הבא יחזיר את כל כותרות האימיילים והשולח שלהם שמכילים את המילה "some":

sqlite> create virtual table email using fts3(subject, content);
sqlite> create table email_meta (sender text, date integer);
sqlite> insert into email_meta values ('abc@def.com', 1210878295);
sqlite> insert into email values ('email from me','with some content');
sqlite> select sender, subject from email_meta join email_body \
 on email_meta.rowid = email_body.rowid where content match 'some';

abc@def.com|email from me

אלו הן, על קצה המזלג, היכולות של הרחבת ה-FTS של SQLite. ניתן למצוא מידע נוסף בויקי של SQLite

שימוש ב-HTML_QuickForm בשילוב עם Smarty

08/05/2008 22:24 ע"י כתריאל טראום

מכיוון ששתי אלו "טכנולוגיות" שאני משתמש בהם ב-Jiyuu בכל מקרה, חשבתי אולי אכתוב איזה מדריכון על איך לשלב בין השתיים.

הקדמה

לדעתי, 2 מהדברים הכי מייגעים בבנית כל אפליקציית Web הוא כתיבת טפסים ועיצוב הפלט.

  • השילוש הקדוש של יצירה\עריכה\מחיקה הוא אחד מהדברים האחרונים שאני תמיד כותב.
  • עיצוב העמודים, אם יש משהו שאני לא אוהב זה המגושמות של שילוב של HTML בתוך PHP או להפך.

2 הפתרונות שמצאתי, אשר עוזרים ומקלים עלי הם HTML_QuickForm ו-Smarty. שניהם פתרונות שבנויים כ-Class אשר מאפשרים שימוש נוח ואפילו הרחבה בעת הצורך.

HTML_QuickForm

HTML_QuickForm היא הרחבת PEAR שמאפשרת בניית טפסים בצורה מונחת עצמים ונוחה. דוגמה קטנה:

require_once('HTML/QuickForm.php');
$form = new HTML_QuickForm('formName','post');

$form->addElement('text','name','Name');
$form->addElement('text','address','Address');
$form->addElement('text','email','E-Mail');
$form->addElement('submit','send','Send');

$form->addRule('name','Please enter name','required');

if ($form->validate()) {
  // save data
} else {
  $form->display();
}

קל לראות איך זה מקצר את העבודה המייגעת של בניית טפסים. יותר מזה, השימוש בפונקציה setDefaults מאפשרת הכנסת ערכים לשדות, לבניית טפסי עריכה בקלות:

$form->addElemnt('text','name','Name');
$form->addElement('text','address','Address');

//Fetch data from DB

foreach ($resultSet as $key => $val) {
 if ($form->elementExists($key)) {
  $form->setDefaults(array($key=>$val));
 }
}

הפלט שמייצר HTML_QuickForm נראה סביר, אבל משתמש בשיטות בניית HTML שקצת פסו מן העולם: טבלאות.

Smarty

ה"שנאה" השניה שלי היא בניית דפי HTML דרך שילוב PHP בתוך הדף או שליחת פלט HTML-י דרך קוד PHP. לאחר בחינת כמה חלופות, בחרתי ב-Smarty. המנוע מאפשר בניית דפי HTML ו"שתילת" משתנים ופונקציות שמוחלפות בזמן עיבוד ה-Template. שימוש ב-Smarty אולי לא נראה נחוץ ואפילו קצת Over-Kill באפליקציות קטנות כמו Jiyuu. אבל גם כאן, אני חושב שכל אפליקציה יכול להרוויח מההפרדה בין HTML ל-PHP. דוגמה לקוד שמשתמש ב-Smarty:

$smarty = new Smarty();

$smarty->assign('itemList',fetchItemList());
$smarty->register_function('formatTimeString','_formatTimeString');

$smarty->display('template.tpl');

והשבלונה אשר אותה מציג הקוד:

<ol>
{foreach from=$itemList item=$item}
 <li>{formatString param=$item}</li>
{/foreach}
</ol>

ההפרדה בין קוד ל-HTML אומנם תשרת טוב יותר מפתחי אפליקציות גדולות יותר בהן יש אנשים אשר אחראים על עיצוב התוכן ואחרים על פיתוח הלוגיקה. בכל זאת, אפילו באפליקציות קטנות כמו Jiyuu, היתרון הוא די ברור.

שילוב בין השתיים

כמו שכבר ציינתי, פלט ברירת המחדל של HTML_QuickForm הוא טבלאי ולא ממש גמיש. לשמחתי, ל-QuickForm יש תמיכה ב-Renderers שונים שמאפשרים לשנות את הפלט של הטופס, אחד מהם הוא SmartyArray. שימוש ב-SmartyArray מאפשר שיוך של Array ל-Template ושימוש באותו ה-Array, אשר מכיל את כל המידע של הטופס בתוך ה-Template. את הדוגמא הראשונה של בניית הטופס נצטרך לשנות מעט:

require_once('HTML/QuickForm.php');
require_once('HTML/QuickForm/Renderer/ArraySmarty.php');
$form = new HTML_QuickForm('formName','post');
$tpl = new Smarty();
$renderer =& new HTML_QuickForm_Renderer_ArraySmarty($tpl);

$form->addElement('text','name','Name');
$form->addElement('text','address','Address');
$form->addElement('text','email','E-Mail');
$form->addElement('submit','send','Send');

$form->addRule('name','Please enter name','required');

if ($form->validate()) {
 // save data
} else {
 $form->accept($renderer);
 $tpl->assign('form', $renderer->toArray());
 $tpl->display('form.tpl');
}

וה-Template יראה כך:

<form {$form.attributes}>
  {$form.hidden}
  {$form.name.label} {$form.name.html}<br />
  {$form.address.label} {$form.address.html}<br />
  {$form.email.label} {$form.email.html}<br />
  {$form.send.html}     
</form>

פלט לדוגמא של הטופס ניתן לראות בכתובת הבאה

חושבים שאתם מגבים כמו שצריך?

26/01/2008 10:40 ע"י כתריאל טראום

אתם לא יכולים להיות בטוחים עד שלא תעברו את ההכשרה על "שבעת ראשי הגיבוי" לפי תורת הטאו של הגיבויים: כיסוי, תדירות, הפרדה, היסטוריה, בדיקה, אבטחה ושלמות המידע.

Foreign Keys ב-SQLite

23/01/2008 20:29 ע"י כתריאל טראום

עם העבודה על jiyuu, יוצא לי לא מעט לעבוד עם sqlite. למי שלא מכיר, זה מסד נתונים אשר מיושם בספריה, ללא שרת, ללא צורך בהתקנה ועם תמיכה ב-Transactions.

סכמת מסד הנתונים של jiyuu אינה מסובכת כל כך. היא כוללת כמה טבלאות פשוטות שמחזיקות פוסטים, תגובות, תגיות וקונפיגורציה. אבל, למרות הפשטות, היה חסר לי מאפיין אחד חשוב, שיעזור לי לשמור על הנקיון ועל תקינות המידע: Foreign Keys.

הסבר מהיר: Foreign Keys הוא מנגנון במסדי נתונים שמאפשר יצירת קשר בין טבלאות שונות. בעזרת Foreign Key ניתן לקשר מידע בטבלה א' למידע בטבלה ב' כך שלא יכול להיות מידע בעמודה מקושרת בטבלה א' שאינו קיים בטבלה ב'. קישור זה הוא בד"כ דו-כיווני: לא ניתן יהיה למחוק מידע מטבלה ב', כל עוד ישנם רשומות בטבלה א' שעדיין מפנות אליה.

דוגמא:

CREATE TABLE tbl1 (
	id INTEGER PRIMARY KEY NOT NULL,
	col1 VARCHAR(20),
	col2 INTEGER NOT NULL CONSTRAINT fk_tbl2_id REFERENCES tbl2(id))

זהו התחביר ליצירת Foreign Keys ב-SQLite (אפילו שהוא לא תומך ב-Foreign Keys. עוד על זה מיד). לפי הדוגמא, לא ניתן יהיה להכניס שורה חדשה לטבלה tbl1, במידה והערך של col2 לא יהיה קיים ב-tbl2. בכיוון השני, ברגע שיש מידע שבטבלה tbl1 אשר מקושר ל-tbl2, לא ניתן יהיה למחוק את הרשומה שאליה המידע קשור ב-tbl2.

רק רגע, אבל הרגע אמרתי ש-Foreign Keys לא נתמך ב-SQLite. המצב כרגע הוא שהפקודות קיימות, אך SQLite לא מתייחס אליהם או עושה להם Parsing\Enforcing. אז איך כן ניתן ליישם Foreign Keys שישמרו על אחידות המידע שלי? התשובה פשוטה: Triggers

הפוסט הזה הופך לשעור ב-SQL… אז מה זה Triggers? אלו פקודות SQL אשר מורות למסד הנתונים לבצע פקודה מסויימת כאשר מתרחש מקרה מסויים. ניתן לנצל יכולת זו כדי לתפוס כל מחיקה, יצירה או שינוי של רשומה כדי לוודא שאין התנגשות עם Foreign Key. נצטרך לצור 3 Trigger-ים: אחד עבור יצירה, מחיקה ועדכון רשומה, בעבור כל טבלה שבא הגדרנו Foreign Key Constraint

CREATE TRIGGER fk_insert_tbl1
BEFORE INSERT ON tbl1
FOR EACH ROW BEGIN
  SELECT RAISE(ROLLBACK, 'insert on table "tbl1" violates foreign key constraint "fk_insert_tbl1"')
  WHERE (SELECT id FROM tbl2 WHERE id = NEW.col2) IS NULL;
END;

CREATE TRIGGER fk_update_tbl1
BEFORE UPDATE ON tbl1
FOR EACH ROW BEGIN
    SELECT RAISE(ROLLBACK, 'update on table "tbl1" violates foreign key constraint "fk_update_tbl1"')
      WHERE (SELECT id FROM tbl2 WHERE id = NEW.col2) IS NULL;
END;

CREATE TRIGGER fk_delete_tbl2
BEFORE DELETE ON tbl2
FOR EACH ROW BEGIN
  SELECT RAISE(ROLLBACK, 'delete on table "tbl2" violates foreign key constraint "fk_delete_tbl2"')
  WHERE (SELECT col2 FROM tbl1 WHERE col2 = OLD.id) IS NOT NULL;
END;

הרבה מידע, אני יודע. אבל אם מתעמקים טיפה, לא מסובך מדי. 2 הדוגמאות הראשונות בעצם דואגות כי לפני יצירה\שינוי ב-tbl1, ייבדק כי קיים ערך מתאים ב-tbl2. הדוגמא השלישית פועלת בכלל על tbl2 בזמן מחיקת רשומה, ומוודאת כי אין רשומות מקושרות ב-tbl1. שימו לב כי אם השדה col2 ב-tbl1 לא היה מוגדר כ-NOT NULL, הפלט היה קצת אחר, אם לדוגמא ניקח את ה Insert Trigger הבא:

CREATE TRIGGER fk_insert_tbl1
BEFORE INSERT ON tbl1
FOR EACH ROW BEGIN
  SELECT RAISE(ROLLBACK, 'insert on table "tbl1" violates foreign key constraint "fk_insert_tbl1"')
  WHERE NEW.col2 IS NOT NULL AND (SELECT id FROM tbl2 WHERE id = NEW.col2) IS NULL;
END;

התוספת היא השורה WHERE NEW.col2 IS NOT NULL, אשר מוודא כי השדה החדש אינו ריק לפני שה-Foreign Key נאכף.

למחפשים עוד מידע, את הדוגמאות שלי שאבתי מהקישור הבא, והשתמשתי בכלי הבא כדי לייצר Triggers מה-Foreign Keys שלי.

אז עכשיו יש ל-jiyuu מסד נתונים מסודר, נקי ונכון. וכדי להפוך את הסכמה לקריאה יותר, השארתי את פקודות ב-Foreign Key המקוריות.

טיפ: ssh-copy-id

20/01/2008 20:11 ע"י כתריאל טראום

אז יש לכם כמה שרתים לנהל? אולי כמה עשרות? אתם בטח משתמשים ב- SSH Private/Public key בשביל להזדהות בהתחברות אליהם, זה מה שאני הייתי עושה.

אם זה המצב, סדר הפעולות הבא בודאי לא זר לכם. העתקת המפתח, התחברות למכונה, העברה ל-authorized_keys, מחיקת המפתח. מיגע.

$ scp .ssh/id_dsa.pub 192.168.1.10:~/
Password:
id_dsa.pub            100%  604 0.6KB/s   00:00
$ ssh 192.168.1.10
Password:
.
katriel@server1:~$ cat id_dsa.pub >> .ssh/authorized_keys
katriel@server1:~$ chmod 700 .ssh
katriel@server1:~$ chmod 600 .ssh/authorized_keys
katriel@server1:~$ rm id_dsa.pub
katriel@web-vm:~$ logout
Connection to 192.168.22.107 closed.
$ ssh 192.168.1.10
katriel@server1:~$

אז אל דאגה. לעזרתנו מגיע הכלי ssh-copy-id, שעושה את הנ"ל, בפקודה אחת:

$ ssh-copy-id -i ~/.ssh/id_dsa.pub 192.168.1.10
34
Password:
Now try logging into the machine, with "ssh '192.168.1.10'", and check in:

  .ssh/authorized_keys

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

$ ssh 192.168.1.10

katriel@server1:~$

קצר ולעניין, אני לא חשוב שיש צורך בהסברים מיותרים. תהנו!

הרפתקאות עם lguest

31/08/2007 18:17 ע"י כתריאל טראום

כבר זמן מה אני מדבר על lguest ועל הרצון שלי לנסות אותו קצת. הפוסט הבא הוא קיצור נסיוני עם lguest.

למי שלא מכיר, למי שלא שמע, lguest הוא פתרון וירטואליזצה קליל ("רק" 5000 שורות קוד) שכתב ראסטי ראסל (Rusty Russell) ומוזג עם קרנל 2.6.23 לראשונה.

lguest מגיע כמודול (אם רוצים) וכלי userland פשוט לניהול ה-VM-ים אשר יושב בספריית lguest תחת Documentation בעץ הקרנל. קימפול הכלי הוא די פשוט ומוסבר בדוקומנטציה. לפני שתתחילו לעבוד עם lguest, קמפלו אותו.

בהמשך להשוואה שערכתי בפוסט קודם, אציין רק שהחוזק העיקרי של lguest הוא הפשטות וה"קלות" שלו לעומת פתרונות אחרים. הוא משתמש באותו הקרנל בעבור מחשב אורח ומארח, ומדובר רק במודול ולא ארכיטקטורה שלמה.
החסרון העיקרי שלו לעומת המתחרים הוא חוסר תמיכה במערכות אחרות מלינוקס. אישית הנושא של חוסר תמיכה במערכות שאינן לינוקס אינו מפריע. גם כך אינני עובד איתם בבית ביום יום. (למרות שיש לי XP (חוקי, כן, כן, חוקי למהדרין) ב-Dual Boot בשביל הבנק ופתרון VPN שלא נתמך בלינוקס, לכו תבינו).

לפני שמתחילים לעבוד עם lguest, להלן רשימת נושאים שכדאי לדעת לפני. למזלכם, כבר השקעתי את הזמן כדי לפתור את הבעיות עד שעלתה לי מערכת:

  • lguest עדיין לא מגיע עם הפצות נפוצות כרגע, מכיוון שהוא הוכנס כ-Patch רק בגירסא 2.6.23-rc1. מה שאומר בעצם שכרגע, צריך לקמפל קרנל בשביל תמיכה ב-lguest. לא להיט, אבל לא נורא אם יש קצת סבלנות. הערכים שיש לדאוג לאפשר הם:
  CONFIG_HIGHMEM64G=n ("High Memory Support" "64GB")[1]
  CONFIG_TUN=y/m ("Universal TUN/TAP device driver support")
  CONFIG_EXPERIMENTAL=y ("Prompt for development and/or incomplete code/drivers")
  CONFIG_PARAVIRT=y ("Paravirtualization support (EXPERIMENTAL)")
  CONFIG_LGUEST=y/m ("Linux hypervisor example code") 

הערה: CONFIG_LGUEST נמצא תחת Device Drivers

  • ה-console device של מערכת שרצה תחת lguest הוא /dev/hvc0, צריך להוסיף שורה בסגנון של:
1:2345:respawn:/sbin/getty hvc0

לקובץ /etc/inittab של ה-image שבו תשתמשו. ולא לשכוח, כדי ש-root יכול לעשות דרכו לוגין, צריך להוסיף אותו ל-/etc/securetty

  • lguest משתמש ב-tunnel device כדי לתקשר עם מערכת ההפעלה המארחת. דרך אחת היא להשתמש ברשת פרטית ו-NAT כדי לאפשר ל-VM לצאת לעולם, אך הדרך שמצאתי נוחה יותר היא בעזרת Bridge. בפדורה, כדי לקנפג Bridge Interface יש לצור קובץ ifcfg-lg0 שהוא בעצם Bridge ולקשר אליו את eth0.

אצלי זה נראה כך:

  • הקובץ /etc/sysconfig/network-scripts/ifcfg-lg0:
DEVICE=lg0
BOOTPROTO=static
BROADCAST=192.168.1.255
IPADDR=192.168.1.10
NETMASK=255.255.255.0
NETWORK=192.168.1.0
ONBOOT=yes
  • והקובץ /etc/sysconfig/network-scripts/ifcfg-eth0:
DEVICE=eth0
BOOTPROTO=static
ONBOOT=yes
BRIDGE=lg0

יותר מאוחר כאשר נצור את ה-VM, נקשר אותו ל-Bridge הנ"ל.

  • הדיסקים של המערכת האורחת נקראים lgba, lgbb וכו'. הייתי צריך לעדכן את ה-fstab של המערכת האורחת כדי שתתאים לשמות החדשים. בנוסף צריך לעשות אחד משני הדברים הבאים כדי שהמערכת תכיר את ה-block devices:
    • לדאוג שלמערכת האורחת יש udev
    • לצור אותם עם mknod. ה-major של lgba הוא 253 ושל lgbb הוא 252. השיטה העדיפה היא כמובן udev.

טוב, אני חושב שזה כל ה-gotchas שצריך לשים לב אליהם. אפשר להתחיל לעבוד.
אני השתמשי ב-image של דביאן בגירסא 3.1 שהורדתי מ-jailtime.org, ושידרגתי בעזרת apt לגירסה 4.
שימו לב : כדי לחסוך בזמן הורדה/מקום, jailtime מספקים rootfs בגודל של 1G בלבד. ניתן לעקוב אחרי ההוראות הבאות כדי להגדיל אותו.

אחרי שהורדתי, פרסתי, מחקתי את המיותר ושיניתי שמות למשהו הגיוני יותר נותרתי עם הקבצים הבאים:

  • root-lgba.img
  • swap-lgbb.img

שלב הבא היה סידור ה-rootfs כדי שיעבוד עם lguest. עיגנתי את root-lgba.img וביצעתי chroot לתוכו. ערכתי את fstab, inittab, securetty והתקנתי udev בשביל יצירה אוטומאטית של devices. זהו, מוכנים להפעיל את lguest כבר?
רק עוד נקודה אחרונה. אין ל-lguest עדיין ניהול קונסול מבריק או יכולות daemon. הכלי lguest (כבר קימפלתם אותו נכון?) מורץ משורת הפקודה, ואם תסגרו את הטרמינל או תריצו pkill lguest, ה-VM שלכם ימות באכזריות. לצורך כך השתמשתי ב-screen כדי להריץ את ה-vm ולשמור לי את היכולת להתנתק ממנו בעת הצורך כדי שלא יתפוס לי טרמינל.

יאללה, לעבודה, הכל מוכן. הפקודה להרצת ה-vm נראית כך:

# screen
# lguest 128m /boot/vmlinux-2.6.23-rc5-lguest --tunnet=bridge:lg0 \
--block=/home/data/vm/web-vm/root-lgba.img \
--block=/home/data/vm/web-vm/swap-lgbb.img root="/dev/lgba ro"

שימו לב שאני משתמש ב vmlinux (שעשיתי לו strip) ולא vmlinuz. לפי הדוקומנטציה ניתן להשתמש ב vmlinuz אך בנסיונות שלי זה לא עבד. אם הכל עבר כשורה, יש לכם כעת VM רץ לשימושכם.

אישית אני משתמש ב-lguest כ-vm לפיתוח של jiyuu. שימושי.

מי מפחד מהחושך? (ומ-Sendmail ו-LDAP?)

18/07/2007 19:51 ע"י כתריאל טראום

שנים רבות שפחדתי מ-Sendmail, באמת. הסתכלתם פעם בקובץ קונפיגורציה שלו? אם אתם אמיצים, פתחו את הקובץ sendmail.cf (משאיר לכם כתרגיל למצוא את המיקום שלו), וראו אם אתם מצליחים להבין איזה כמה שורות קונפיגורציה שם. לא, הא? טוב, אתם לא לבד, 99.999% מאוכלוסיית העולם אינה מסוגלת לקרוא את הקובץ. אבל אל דאגה, לעזרתנו באה שפת המאקרואים m4, למי שלא מכיר, תגידו שלום ל-sendmail.mc, מכיל רק כמה שורות, קצת יותר נהירות, שבעזרתם בונה המפענח (m4) את sendmail.cf.

יופי, עכשיו שהבהרנו את הנושא הזה, בוא ונסתכל על אחד מהמאפיינים של Sendmail שעושה אותו כלי מצויין (למרות שכיום, חביב עלי יותר postfix), מפות. לא, לא מפות שולחן, אלא שימוש בקבצי מיפוי כדי להכניס גמישות ושליטה של מנהל הרשת על Sendmail מחוץ ל-sendmail.cf. לדוגמא, Sendmail מאפשר לנתב דואר נכנס לשרת אחר, או דואר מאימייל אחד למשתמש אחר.

הכל טוב ויפה כאשר יש שרת אחד לנהל. מה קורה כאשר יש 10? 20? (קיימות מערכות כאלו ואף גדולות יותר) אנחנו צריכים מקום מרכזי שיחזיק את כל המידע הזה בצורה מרוכזת, כדי שדואר אשר נכנס בכל אחד מהשרתים יקבל את אותו "הטיפול". להצלתנו מגיע כמובן LDAP הפרוטוקול ו-OpenLDAP השרת.

למי שחי מתחת לאדמה בשנים האחרונות ולא שם לב, LDAP הפך להיות ה"סכין צבאית שוויצרית" (Swiss Army Knife, נשמע יותר טוב באנגלית, תודו) של אנשי ה-IT. אם פעם היה מחזיק רק משתמשים או כתובות, היום הוא כבר "יודע" להחזיק מידע כמו מפות של Sendmail/Postfix, מידע DNS-י, משתמשים, קבוצות, automounts ומה לא… במקרה שלנו, נרתם שרת ה-LDAP כדי להחזיק מפות של Sendmail. לא רע בכלל בשביל פרוטוקול צעיר (יחסית, בערך מ-1993).
LDAP (או Light Weight Directory Access) תוכנן כפרוטוקול שנועד להחזיק מידע היררכי בצורה יעילה ומהירה לגישה, וזה בדיוק מה שהוא עושה פה בעבור Sendmail.

על הנושא של התקנת, קינפוג ותחזוקת שרת OpenLDAP לא אדבר, כי הוא חורג מגבולות הבלוג הזה שמטרתו: שירת הלל ל-LDAP. בכל מקרה, אז איך אנו גורמים ל-Sendmail לדבר עם שרת LDAP? ואיך אנו גורמים לו לשאול את השאלות הנכונות? בואו ואספר לכם.

בתור התחלה, נצטרך להגיד לשרת ה-LDAP כיצד לבנות רשומות שיבין Sendmail, איך עושים את זה? במילה אחת - sendmail.schema. סכמה היא קובץ המגדיר סוגי אוביקטים, סוג המידע שבכל אוביקט ואת היחס ביניהם. ניתן למצוא את הסכמה בקבצי המקור של Sendmail או ברוב ההפצות כיום. את הסכמה יש לשים בספריית הסכמות של OpenLDAP (בד"כ /etc/openldap/schema) ולהוסיפה לקובץ הקונפיגורציה של OpenLDAP:

include         /etc/openldap/schema/core.schema
include         /etc/openldap/schema/cosine.schema
include         /etc/openldap/schema/inetorgperson.schema
include         /etc/openldap/schema/nis.schema
include         /etc/openldap/schema/sendmail.schema

שלב הבא במסענו לארץ LDAP, יהיה יצירת המפות כדי ש-Sendmail יוכל לגשת אליהם, ניצור את ה-DN הבסיסי ומתחתיו את המיפויים. בדוגמא הבאה ניצור מפה מסוג virtusertable:

# cat > ldap-virtuser 
dn: sendmailMTAMapName=virtuser, dc=example, dc=com
objectClass: sendmailMTA
objectClass: sendmailMTAMap
sendmailMTACluster: example.com
sendmailMTAMapName: virtuser

dn: sendmailMTAKey=user@example.com, sendmailMTAMapName=virtuser, dc=example, dc=com
objectClass: sendmailMTA
objectClass: sendmailMTAMap
objectClass: sendmailMTAMapObject
sendmailMTAMapName: virtuser
sendmailMTACluster: example.com
sendmailMTAKey: user@example.com
sendmailMTAMapValue: user-example

כדי להוסיף את המפה נצטרך להשתמש בכלי ldapadd של OpenLDAP:

# ldapadd -D cn=manager,dc=example,dc=com -b dc=example,dc=com -W -x < ldap-virtuser

מצויין! יש לנו רשומה בשרת ה-LDAP שממפה את user@example.com למשתמש user-example.

שלב אחרון במזימה שלנו הוא קינפוג Sendmail שידע לחפש בשרת ה-LDAP, על מנת לעשות זאת, נצטרך להכניס כמה שינויים ותוספות ב-sendmail.mc חפשו את השורה

FEATURE(`virtusertable',`hash -o /etc/mail/virtusertable.db')dnl

והחליפו אותה בשורות הבאות:

define(`confLDAP_DEFAULT_SPEC', ` -h ldserver -b dc=example,dc=com')
define(`confLDAP_CLUSTER', `examplecom')
FEATURE(`virtusertable', `LDAP')

הערה: עוד מידע על האפשרויות של confLDAP_DEFAULT_SPEC בכתובת הבאה

כדי לצור sendmail.cf חדש, נשתמש במפענח m4:

m4 sendmail.mc > sendmai..cf

זהו, פשוט לא? אתחול זריז ל-Sendmail ואנחנו מוכנים לבדוק אם הכל עובד כמו שאנו מקווים. נבצע בדיקת מיפוי, ונראה שאכן המשתמש ממופה כמו שצריך:

# sendmail -bt
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
> /map virtuser user@example.com
map_lookup: virtuser (user@example.com) returns user-example (0)

מושלם! ביקשנו מ-Sendmail לפענח בעבורנו, מתוך המפה virtuser לאיזה משתמש מופנה הדואר בעבור הכתובת user@example.com, והתשובה הנכונה לא אחרה להגיע לאחר שאילתה קטנה לשרת ה-LDAP

אז נכון, Sendmail הוא יצור מאיים, LDAP הוא פרוטוקול שנראה מסובך, אבל השילוב בין השתיים יכול להביא לניהול נוח וגמיש בהרבה של שרתי Sendmail. תודו שאתם מופתעים מכמה שזה היה פשוט.

לאמן את הכלב

18/07/2007 19:45 ע"י כתריאל טראום

את מנוע החיפוש beagle לא צריך להציג, הוא מן הסתם החלופה הראויה ביותר ל-Google Desktop בעבור סביבת לינוקס. חלק מתכונותיו השימושיות כוללות עדכון בזמן אמת של תוצאות החיפוש, אינדוקס מיידי של דואר נכנס, שיחות ב-Instant Messenger וקבצים חדשים, יכולת אינדוקס של מגוון רחב של פורמטים ומקורות מידע ועוד.

Beagle נשען על מנוע האינדוקס המצויין Lucene מבית פרויקט Apache שהומר לשפת .Net

לאחרונה הוספתי דיסק קשיח נוסף למחשב אשר החזיק מסמכים, מוזיקה וסרטים ממערכת ישנה שלי. כיוון שהתנהגות ברירת המחדל של Beagle היא לאנדקס רק תוכן מתוך ספריית הבית, פתחתי את ממשק הקונפיגורציה שלו, והוספתי ספריה נוספת לאינדוקס:

לספריה /home/data/mm עיגנתי את הדיסק הנוסף. ברגע שנוסף ל-Beagle ספריה נוספת, הוא מתחיל ישר לסרוק אותה ולאנדקס את כל החומר החדש. הבעיה התחילה כאשר לקח לו יותר מדי זמן, ורציתי להתחיל למצוא מסמכים ושירים בצורה די מיידית (חיברתי את הדיסק כדי למצוא מסמך מסויים). הפתרון נמצא בדמות משתנה סביבה (Environment Variable), שכאשר הוא מועבר ל-Beagle, מורה לו להתחיל לסרוק בצורה נמרצת. התהליך הוא:

$ beagle-shutdown
$ export BEAGLE_EXERCISE_THE_DOG=1
$ beagled

אם בד"כ Beagle מנסה לגזול כמה שפחות משאבי מעבד וזכרון כדי לא להפריע לריצה נורמאלית, אז משתנה זה אומר לו לעשות הכל ולהשתמש בכל משאב שהוא רוצה כדי להתחיל ולסרוק שינויים ואוביקטים חדשים. אכן, תוך 15 דקות כבר היה הדיסק הקשיח החדש סרוק ומאונדקס כולו.

ומה איתי? בזמן שהכלבלב סרק, אני יצאתי בנתיים להפסקת קפה.

מחיקת תור ההודעות של Postfix

18/07/2007 19:43 ע"י כתריאל טראום

לאחרונה יצא לי לשדרג את מערכת ההפעלה של הפינגווין מפדורה בגירסא 5 ל-CentOS 5. אני מקווה שהצעד הזה ישאיר את המערכת מעודכנת ומאובטחת יותר לאורך זמן (פדורה 5 כבר אוטוטו עוברת ל-Legacy). בתהליך השדרוג, שכחתי לכוון את שרת הדואר של הפינגווין שיצא דרך ה-Mail Relay שלנו. כתוצאה מכך נתקעו לי לא מעט מיילים יוצאים בשרת הדואר הפנימי.
חשבתי כבר הנה שוב אצטרך לכתוב איזה שורת פקודה ארוכה ומסובכת כדי לרוקן את התור היוצא (כמו שאני בד"כ עושה עם סנדמייל שמחזיק מיילים לא רצויים). להפתעתי ושימחתי, גיליתי כלי ל-Postfix יש כלי לניהול התור שמסוגל לעשות זאת בשבילי. פקודה אחת קצרה ולעניין פתרה את כל הבעיה:

sudo postsuper -d ALL

פקודה זו תרוקן את כל התורים של Postfix: נכנס, יוצא ו-Deffered. ניתן למחוק בצורה סלקטיבית יותר, כמו תמיד man 1 postsuper יספק יותר מידע מבלוג זה.