суббота, 20 сентября 2008 г.

Запуск X приложений из крона

У начинающих линуксоидов, пытающихся запустить X приложения из крона, часто возникают сложности. Этих сложностей я насчитал 3 штуки, и сейчас мы их все разберём.

Но в начале хочу сказать, что все эти сложности могут проявляться и в других случаях - при попытках запустить графические приложения от root, поэтому для облегчения тестирования я буду использовать крышку ноутбука. Я буду ей махать, и при этом будет выполнятся скрипт /etc/acpi/default.sh. Точнее я буду просто давить маленькую кнопочку на ноутбуке, которая нажимается крышкой при закрытии. Эта кнопочка генерирует ACPI событие с группой "button" и событием "lid", по которому например можно гасить экран, или повергнуть систему в suspend. Причём скрипт запускается примерно в том-же окружении, что и cron-овские задачи, поэтому тестировать я буду здесь, а потом уже начисто проверять в cron. Намного проще нажать кнопку, чем ждать 1-2 минуты, не так ли?
Хотя, в системах , отличных от Gentoo, этот скриптик может оказаться в другом месте. Так что проверяйте всё сами - эксперимент, эксперимент и ещё раз эксперимент!

Итак, попробуем вывести на экран диалог с текстом. Для этого есть 2 программы:
1. xmessage - универсальная, не зависит от оконного менеджера, но не очень красивая.
Попробуйте запустить xmessage -center "Hello Sergey"
2. kdialog - в составе KDE
Пробуем так: kdialog --msgbox "Hello Sergey"

Я предпочитаю kdialog, как более симпатичный.
Пробуем использовать - вписываем вызов по событию lid:

lid)
kdialog --msgbox "Hello Sergey" > /err 2>&1
;;

не забываем перенаправить все сообщения в /err, чтоб было над чем подумать.

Давим кнопочку - ничего не происходит (и не удивительно, зачем бы я тогда тут распинался?). Читаем /err, и видим что-то подобное этому:

/etc/acpi/default.sh: line 31: kdialog: команда не найдена

Язык может быть и другим - зависит от настроек локали. Но, допустим вам не нравится русский, или вместо сообщения вы получили какие-то кракозябы? Это легко исправить:

lid)
export LC_ALL=C
export LANG=C
kdialog --msgbox "Hello Sergey" > /err 2>&1
;;

Теперь уже лучше:

/etc/acpi/default.sh: line 33: kdialog: command not found

Но почему происходит эта ошибка? Да потому что kdialog действительно not found - пути к ней не прописаны. Это можно исправить 2 способами - использовать полный путь, или модифицировать PATH
1.

lid)
export PATH=$PATH:/usr/kde/3.5/bin/
kdialog --msgbox "Hello Sergey" > /err 2>&1
;;

2. Прописать полный путь

lid)
/usr/kde/3.5/bin/kdialog --msgbox "Hello Sergey" > /err 2>&1
;;

Во этом случае нас могут подстерегать некоторые затруднения - если программа вызывает какую-то другую программу, то эта другая программа в свою очередь может не найтись! Везде прописывайте полные пути!

Получаем вот что:

kdialog: cannot connect to X server

Бедняжка не может найти X сервер... Нужно ему помочь! Опять таки 2 способа указать сервер:
1. Указать из командной строки
kdialog --msgbox "Hello Sergey" -display :0
2. Задать переменную DISPLAY:
export DISPLAY=":0"
Второй способ мне кажется более удобным, так как не все программы поддерживают опцию -display

Пробуем:

lid)
export DISPLAY=":0"
/usr/kde/3.5/bin/kdialog --msgbox "Hello Sergey" > /err 2>&1
;;

И снова получаем получаем ошибку:

Xlib: connection to ":0.0" refused by server
Xlib: No protocol specified

kdialog: cannot connect to X server :0

Сервер найден, но он нас не пускает - секьюрити, панимаиш...
И снова есть несколько путей(ну это же Линукс!):
1. Использовать программу xhost, которая просто разрешает коннектиться к серверу всем. Не очень безопасно, не так ли? Тем не менее его можно применять, например, если вы сидите дома и никого не боитесь (aka "я в домике").
'xhost +' разрешает всем коннектиться к серверу. Но, запускать эту программу надо не из скрипта! При запуске из скрипта у программы нет прав, чтоб изменять права, поэтому эту команду нужно впихнуть либо в автозагрузку X сервера, либо выполнить самому. Где находится эта автозагрузка, я без понятия, поэтому дальше этот способ рассматривать не будем.
2. Для авторизации клиентов используется файл указанный в переменной окружения XAUTHORITY. Если переменная не задана - используется файл $HOME/.Xauthority
Поэтому попробуем руту подсунуть файл авторизации пользователя:

lid)
export XAUTHORITY="/home/sazarkevich/.Xauthority"
export DISPLAY=":0"
/usr/kde/3.5/bin/kdialog --msgbox "Hello Sergey" > /err 2>&1
;;

И... Это работает!
3. Вариант похож на предыдущий, но просто в /root делаем ссылку на .Xauthority пользователя:
ln -s /home/sazarkevich/.Xauthority /root/.Xauthority
Клиент пытается авторизоваться на сервере, берёт файл авторизации $HOME/.Xauthority, а это оказывается файл уже авторизованного пользователя. Вуаля.
4. Последний вариант - запуск из под пользователя

lid)
export DISPLAY=":0"
su sazarkevich -c '/usr/kde/3.5/bin/kdialog --msgbox "Hello Sergey" > /tmp/err 2>&1'
;;

Заметьте, ошибки скидываем в /tmp/err, т.к. пользователь sazarkevich не имеет доступа к корневой директории!
При этом автоматически используется .Xauthority пользователя.

Конечно, это не вполне хорошее решение. Тут жёстко задан пользователь. Более того, вы всегда должны работать за сервером :0 - если вы запустите ещё один сервер, на него ничего выводится не будет.
Однако, если вы за компьютером работаете всегда под одним пользователем (не root, боже упаси), и не запускаете лишних X серверов, а это часто так и есть, то вариант вполне подходящий.

Теперь с учётом всех проблем пробуем запустить оповещение из cron-а:
1. Полный путь
2. задать DISPLAY
3. разрешить авторизацию

Если вы запускаете задачи крона пользователя (crontab -e -u sazarkevich), то 3-ий пункт не нужен - он автоматически выполняется по 4-ому методу.
Если почему-либо вы используете крон через root-а(crontab -e -u root), то 3-ий пункт необходим.

Вот примерно так:

SHELL=/bin/bash
DISPLAY=":0"
MAIL=sazarkevich
# Morning shotdown
0 9 * * * /usr/kde/3.5/bin/konsole -e /home/sazarkevich/bin/morning-poweroff.sh

Это у меня по утрам, в 9 часов, запускается консоль, в которой 60 секунд идёт обратный отсчёт. Если никто не позаботится нажать Ctrl+C во время обратного отсчёта - компьютер выключится. Если кто-то за ним в это время работает, то он может отменить выключение.

Вот так. Всё просто!

8 комментариев:

Анонимный комментирует...

>Поэтому попробуем руту подсунуть файл авторизации пользователя

а если другого пользователя нет?
или другой пользователь тоже не может авторизовываться..
В общем толку от трех последних способов нет, а первый способ тоже не раскыт!

Сергей Азаркевич комментирует...

Если другого пользователя нет, то это не наш пользователь. Наши люди в X-ы на root-е не ездят. Они имеют для этого "другого пользователя".

А по поводу "толку от трех последних способов нет". Нет, так и не пользуйтесь. Мне толк есть, я пользуюсь. Может ещё кому толк будет.

_fox комментирует...

Полный путь можно не прописывать, если он есть в PATH. Т.е. если программа запускается просто из терминала, то и cron её тоже запустит.

Вот, например, у меня сработало так:

crontab -u ildar -e
и там прописываю

DISPLAY=:0.0
#*/1 * * * * gedit


Затем перезапуск cron-а (у меня vixie-cron)
/etc/init.d/vixie-cron restart
и через минуту запускается gedit.

Только, хотя мы и запускаем программу gedit от имени пользователя ildar, но команда crontab -u ildar -e у меня выполняется только из-под root-а.

_fox комментирует...

Ой, прошу прощения!

Не

DISPLAY=:0.0
#*/1 * * * * gedit


а

DISPLAY=:0.0
*/1 * * * * gedit


# - это комментарий :)

Сергей Азаркевич комментирует...

Гм. А вот у меня PATH урезанный.
Может это зависит от дистрибутива? У меня Gentoo

Анонимный комментирует...
Этот комментарий был удален администратором блога.
Анонимный комментирует...
Этот комментарий был удален администратором блога.
Анонимный комментирует...
Этот комментарий был удален администратором блога.