Как мы поднимали сервер или создание веб-сервера для большой нагрузки (осторожно, с юмором)

Разное

Автор: Eugen

1 нояб. 2011 г., 10:33:10  2521


Доброго времени суток :-)
Начну немного с истории… Предлогом для написания этой статьи послужили две вещи. Во-первых, умеренная DDoS атака на gibs0n.name/, из-за которой сервер был недоступен аж несколько дней, а одна из попыток побороть атаку завершилась нашей ошибкой – был заблокирован доступ к серверу со всех адресов. Пришлось ехать в датацентр и разбираться с этой адской машиной под названием сервер… Во-вторых, недавний сбой по питанию в датацентре, после которого мы однозначно решили: будем менять железо. С этого всё и началось… На этом этапе, хочется сказать огромное спасибо моему лучшему другу – gibson'у за предоставленное железо для сервера. Среди этого железа – материнская плата MSI P965 Neo. Было решено установить процессор Intel Core2Duo и 8ГБ памяти DDRII-800. Как потом оказалось, на этой материнке жутко нестандартные контроллеры дисков. Обнаружилось это, когда я подключил к ней 2 винчестера, чтобы перенести полностью настроенную и готовую к работе ОС с испытательного винчестера на действующий… Тут-то и начались танцы с бубном. Master-винчестер, подключенный к единственному IDE каналу, оказался /dev/ad6, вместо ожидаемого /dev/ad0, а Slave-винчестер — /dev/ad7. Находящийся рядом с ним «отделённый» SATA порт оказался /dev/ad4 (как, в общем, первый SATA порт практически на любых материнках). А сгруппированные сбоку 4 SATA порта имели названия /dev/ad8, /dev/ad10, /dev/ad12, /dev/ad14 и были расположены «змейкой».

Все винчестеры, порты на материнке и шлейфы, а так же, слоты на материнке и сетевые карты (их у меня две) были помечены наклейками.
Вот так выглядит сервер изнутри:

x

А так – снаружи:

x

А теперь – в датацентре:

(по понятным причинам, телефон, написанный на верхнем сервере, стёрт)

Наша широкая душа занимает аж три юнита в стойке… :)
Ну а теперь – хватит. Насмотрелись мы уже подобных картин:

x

Будем поднимать сервер самостоятельно и экономить на тушОнке :)

В качестве ОС была выбрана FreeBSD 6.4 amd64. Такой выбор связан с тем, что, во-первых, я большой любитель FreeBSD, во-вторых, нужна 64-битная система для нормальной работы с 8ГБ памяти, ну а в-третьих, на момент всех моих действий, более новые версии системы имели известные уязвимости.
Итак, начнём с установки системы. Процесс установки хорошо описан в одной из моих предыдущих статей. Но на том, что там описано, мы не остановимся.
Устанавливаем систему, доходим до выбора компонентов (а конкретнее – src) и выбираем там все исходники, кроме games. Почему так – а потому, что мы будем перестраивать абсолютно всё, чтобы добиться максимальной производительности. После установки – обновляем порты и устанавливаем deco и le, это наши рабочие инструменты. mc пока можно не ставить (хотя, кому он удобнее, может и поставить).
С chflags прийдётся повременить, т.к. он нам будет мешать перестроить «мир». Точно так же и с удалением ненужных файлов и папок, потому как перестройка «мира» восстановит всю файловую структуру. Так же, не вижу особого смысла настраивать rc.conf на этом этапе.
Теперь у нас две задачи – сначала перестроить ядро системы, потом перестроить «мир». Начнём с конфигурации /etc/make.conf перед всем этим… Приводим вышеуказанный файл к такому виду:
CPUTYPE?=core2
NO_PROFILE=yes
NO_GAMES=yes
DOC_LANG=en_US.ISO8859-1 ru_RU.KOI8-R
WITHOUT_X11=yes
NO_X11=yes
WITH_IDEA=yes
MAKE_IDEA=yes
WITHOUT_GAMES=yes
WITHOUT_INET6=yes
WITHOUT_INET6_SUPPORT=yes
WITHOUT_PROFILE=yes
WITHOUT_IPV6=yes
NO_BLUETOOTH=yes
NO_AUDIT=yes
NO_USB=yes
NO_NIS=yes
SENDMAIL_CFLAGS=-I/usr/local/include/sasl -DSASL
SENDMAIL_LDFLAGS=-L/usr/local/lib
SENDMAIL_LDADD=-lsasl2
PORTSDIR?=/ports
DEFAULT_MYSQL_VER=51
.if ${.CURDIR} == ${PORTSDIR}/databases/mysql${DEFAULT_MYSQL_VER}-client
 WITH_CHARSET=utf8
 WITH_COLLATION=utf8_general_ci
 BUILD_OPTIMIZED=yes
 BUILD_STATIC=yes
.endif
.if ${.CURDIR} == ${PORTSDIR}/databases/mysql${DEFAULT_MYSQL_VER}-server
 WITH_CHARSET=utf8
 WITH_XCHARSET=all
 WITH_COLLATION=utf8_general_ci
 WITHOUT_OPENSSL=yes
 WITHOUT_LINUXTHREADS=yes
 BUILD_STATIC=yes
 WITH_INNODB=yes
 WITHOUT_NDB=yes
.endif
CPUTYPE – тип процессора. Указываем свой из:
AMD: opteron, athlon64, athlon-mp, athlon-xp, athlon-4, athlon-tbird, athlon, k8, k6-3, k6-2, k6, k5
Intel: nocona, pentium4[m], prescott, pentium3[m], pentium-m, pentium2, pentiumpro, pentium-mmx, pentium, i486, i386, core2
Via: c3, c3-2
Alpha/AXP: ev67, ev6, pca56, ev56, ev5, ev45, ev4
Intel ia64: itanium2, itanium

Заменяем 51 на свою версию MySQL (если она вообще будет на этом сервере). Если MySQL устанавливаться не будет – можно удалить эту строку и всё, что идёт после неё.
PORTSDIR – РЕАЛЬНЫЙ путь к папке с портами. Обычно /usr/ports, но у меня это /ports, а /usr/ports – символьная ссылка на /ports.
Опции для Sendmail – в данной конфигурации включают поддержку SASL (для SMTP авторизации на сервере). Если сервер не будет использоваться как SMTP сервер (или если не нужна авторизация) – эти строки следует убрать. Если же будет – тогда ещё и SASL поставить прийдётся. Как – расскажу чуть позже.
Ах, да… По поводу NO_USB погорячился. Если на сервере используются USB устройства, следует удалить эту строку :) У меня USB устройств нет, поэтому usbd мне не нужен :)

Далее – приводим /etc/src.conf к такому виду:

WITHOUT_INET6=yes
WITHOUT_INET6_SUPPORT=yes
WITHOUT_PROFILE=yes
Тут, как мне кажется, всё должно быть понятно...

Далее – идём в /usr/src/sys/amd64/conf (у кого i386 – будут всего два отличия в конфигурации) и приводим файл нашего ядра к такому виду (не забываем про hints):
ident		TEM
# у кого i386 – пишем здесь i386 вместо amd64
machine		amd64
# И тип процессора – соответственно I686_CPU
cpu		HAMMER
maxusers	0
options 	SCHED_4BSD
# Если у вас только одно ядро – SMP делать не нужно – убираем эту строку.
options 	SMP
options 	ADAPTIVE_GIANT
options 	PREEMPTION
options 	COMPAT_43
options 	COMPAT_FREEBSD4
options 	COMPAT_FREEBSD5
options 	SYSVSHM
options 	SYSVSEM
options		DEVICE_POLLING
options 	SYSVMSG
options 	INET
device		acpi
device		ether
device		loop
device		bpf
options 	IPFIREWALL
options 	IPFIREWALL_VERBOSE
options 	DUMMYNET
options 	ZERO_COPY_SOCKETS
options 	FFS
options 	SOFTUPDATES
options 	UFS_DIRHASH
device		random
device		mem
options 	_KPOSIX_PRIORITY_SCHEDULING
options 	HZ=5000
options 	PPS_SYNC
device		pty
options 	LIBICONV
device		atkbdc
device		atkbd
options 	ATKBD_DFLT_KEYMAP
makeoptions	ATKBD_DFLT_KEYMAP=ru.koi8-r
options 	KBD_DISABLE_KEYMAP_LOAD
options 	KBD_INSTALL_CDEV
device		vga
device		sc
options 	MAXCONS=8
options 	SC_DFLT_FONT
makeoptions	SC_DFLT_FONT=cp866
options 	SC_DISABLE_KDBKEY
options 	SC_DISABLE_REBOOT
options 	SC_HISTORY_SIZE=100
options 	SC_MOUSE_CHAR=0x3
options 	SC_PIXEL_MODE
options 	SC_NO_CUTPASTE
options 	SC_NO_SYSMOUSE
device		ata
device		atadisk
options 	ATA_STATIC_ID
device		miibus
device		pci
device		crypto
device		cryptodev
options 	INIT_PATH=/sbin/init
options 	PANIC_REBOOT_WAIT_TIME=10
options 	DIRECTIO
device		pf
# У меня обе сетевые карты в сервере Intel PRO/100
# У кого другая карта – соответственно пишем здесь её название
# Небольшой выигрыш в производительности можно получить, если вшить драйвер в ядро
device		fxp
options		ACCEPT_FILTER_HTTP
options		ACCEPT_FILTER_DATA
options		ALTQ
options		ALTQ_CBQ
options		ALTQ_RED
options		ALTQ_RIO
options		ALTQ_HFSC
options		ALTQ_CDNR
options		ALTQ_PRIQ
options		ALTQ_NOPCC
options		ALTQ_DEBUG
device		pflog
device		pfsync
Компилируем и устанавливаем ядро, как это было описано в моей предыдущей статье :) Это моя рабочая конфигурация ядра...
Перезагружаем сервер. Если всё нормально загрузилось – папку /boot/kernel.old можно удалить (если вам старое ядро, конечно, не нужно сохранять на память)...

Теперь будем пересобирать «мир». Для этого идём в /usr/src и делаем там make buildworld
Далее создаём файл, к примеру, /update.sh такого содержания:
#!/bin/sh
fsck -p
mount -u /
mount -a
cd /usr/src
mergemaster -p
make installworld && make delete-old && mergemaster -i
Выставляем файлу права 0755 и запускаем команду shutdown now, чтобы перейти в однопользовательский режим (подразумевается, что сервер находится перед вами, а не в километрах от вас и по SSH доступу). Соглашаемся с тем, что наш шелл будет /bin/sh, запускаем /update.sh и ждём. Ждать придется некоторое, весьма продолжительное время, которое зависит от вашего железа… После всех этих действий, перезагружаем сервер (нас reboot, а мы крепчаем...) и радуемся, если всё получилось правильно. Не забываем удалить этот скрипт после перезагрузки :)
Дальше, пока не забыли, выполняем такую команду (только если наш сервер будет отдавать почту через POP3 протокол!):
chmod o-x /usr/bin/mail /usr/bin/Mail /usr/bin/mailx /usr/bin/biff
Это нужно, чтобы запретить пользователям пользоваться этими командами. В противном случае, использование этих команд вместе с POP3 сервером, в некоторых случаях, может привести к повреждению почтовых ящиков.
Теперь можно и продолжить с удалением ненужных папок, переносом папок на другие винчестеры и установкой schg флагов. Очень советую поставить schg ещё и на всё в папке /boot, кроме loader.conf
Сделать это можно, например, так (подразумевается, что loader.conf уже есть в папке /boot):
cd /boot
chflags -R schg *
chflags noschg loader.conf
cd /куда/нам/дальше/надо :)

Дальше создаём (или перезаписываем) файл /etc/sysctl.conf таким вот образом:
net.inet.tcp.blackhole=2
net.inet.udp.blackhole=1
net.inet.tcp.always_keepalive=0
net.inet.tcp.keepidle=60000
net.inet.tcp.recvspace=8192
net.inet.tcp.sendspace=16384
kern.ipc.nmbclusters=65536
kern.ipc.somaxconn=32768
kern.ipc.maxsockets=204800
kern.maxfiles=256000
kern.maxfilesperproc=230400
net.inet.ip.portrange.first=1024
net.inet.ip.portrange.last=65535
net.inet.ip.portrange.randomized=0
net.inet.tcp.maxtcptw=40960
net.inet.tcp.msl=15000
net.inet.tcp.syncookies=1
net.inet.tcp.sack.enable=1
net.inet.tcp.nolocaltimewait=1
net.inet.icmp.drop_redirect=1
net.inet.icmp.log_redirect=1
net.inet.ip.redirect=0
kern.polling.enable=1
kern.polling.burst_max=1000
kern.polling.each_burst=50
net.inet.icmp.icmplim=5000
security.bsd.see_other_gids=0
security.bsd.see_other_uids=0
Это увеличивает производительность всего, что работает с сетью, увеличивает максимальное количество одновременно открытых файлов и запрещает пользователям видеть чужие процессы. Зачем нужно последнее – расскажу позже (если конечно нужно)...

Так же, в /boot/loader.conf можно добавить такие строки:
net.inet.tcp.syncache.hashsize=1024
net.inet.tcp.syncache.bucketlimit=100
net.inet.tcp.tcbhashsize=4096
Дальше можно настроить ttys и rc.conf :) Не забываем включить ipfw и pf, они нам будут нужны. Для ftpd задаём единственный флаг – -R. Он нужен, чтобы разрешить режим fxp. А вот стандартный sshd не будем включать. Поставим альтернативный из портов.
Теперь расскажу про firewall'ы:
Файл /etc/pf.conf у меня выглядит таким образом:
ext_if="fxp0"

table  persist
block in log quick from 

pass in on $ext_if proto tcp to $ext_if port www flags S/SA keep state ( max-src-conn-rate 100/10, overload  flush)
Это простой Anti-DDoS, который блокирует адрес, если с него идут более 100 подключений на 80-й TCP порт за 10 секунд.

Теперь ipfw (в принципе, можно обойтись и без ipfw, просто, как по мне, он гораздо легче воспринимается). /etc/rc.firewall (у меня он выглядит так):
#!/bin/sh -

# Some config...
fwcmd="ipfw"			# ipfw command
ext_if="fxp0"			# external interface
int_if="fxp1"			# internal interface
my_net_ext="193.34.96.112/28"	# my external network
my_net_int="192.168.0.0/24"	# my internal network

# Delete all rules
${fwcmd} -f flush

# Block all traffic while rules are loading
${fwcmd} add 00001 deny all from any to any

# Allow all local connections
${fwcmd} add 00002 allow all from any to any via lo0

# Reset all IDENT connections to make SSH faster
${fwcmd} add 00003 reset tcp from any to any ident

# Allow all from my (trusted) networks
${fwcmd} add 00004 allow all from ${my_net_ext} to me # incoming, external
${fwcmd} add 00004 allow all from me to ${my_net_ext} # outgoing, external
${fwcmd} add 00005 allow all from ${my_net_int} to me # incoming, internal
${fwcmd} add 00005 allow all from me to ${my_net_int} # outgoing, internal

# Deny smartd and mbmon traffic from all except trusted networks described above
${fwcmd} add 00006 deny tcp from any to me smartd
${fwcmd} add 00007 deny tcp from any to me mbmon

# Allow all other traffic
${fwcmd} add 65534 allow all from any to any

# Delete the first all-blocking rule
${fwcmd} delete 00001
Теперь поехали разбираться… Есть два интерфейса, один внешний и один внутренний (аварийный). Первым делом, мы блокируем весь трафик, до тех пор, пока все правила не загрузятся. Далее, мы разрешаем весь локальный трафик. Потом сбрасываем IDENT соединения (чтобы ssh работал быстрее). Потом я разрешаю весь трафик из своих сетей. Дальше – запрещаю доступ к smartd (программа для контроля состояния винчестеров) и mbmon (мониторинг состояния материнки). Кстати, последняя с новой материнкой работать не захотела… Дальше – разрешаем всё, что не попало под другие правила. Так же, если 6 и 7 правила вам нужны (и на сервере таки будут эти программы), то в файл /etc/services нужно добавить mbmon и smartd. Для mbmon – стандартный порт 12999. Для smartd я сделал порт 13000. Обе программы будут запускаться через inetd.

Теперь несколько слов про /etc/crontab и /etc/newsyslog.conf
Кронтаб у меня выглядит таким образом:
SHELL=/bin/sh
PATH=/etc:/bin:/sbin:/usr/bin:/usr/sbin
HOME=/var/log

#minute	hour	mday	month	wday	who	command

#*/5	*	*	*	*	root	/usr/libexec/atrun
*/11	*	*	*	*	operator /usr/libexec/save-entropy
0	*	*	*	*	root	newsyslog
#1	3	*	*	*	root	periodic daily
#15	4	*	*	6	root	periodic weekly
#30	5	1	*	*	root	periodic monthly
0	*	*	*	*	root	pfctl -t ddos -T flush > /dev/null 2>&1
0	*	*	*	*	root	/root/bashorg > /dev/null 2>&1
0	0	*	*	*	root	/root/backup > /dev/null 2>&1
0	0	*	*	*	root	portsnap fetch update > /dev/null 2>&1
0	0	*	*	1	root	fetch –q –p –o /etc/namedb/named.root ftp://ftp.internic.net/domain/named.root > /dev/null 2>&1
0	0	*	*	2 	root	rndc reload > /dev/null 2>&1


 

1) at я использовать не собираюсь.
2) Периодические отчёты я всё равно не читаю. Зачем их генерировать?
3) Очищаем таблицу заблокированных адресов каждый час.
4) Обновляем приветствие в системе. (этот скрипт я опишу чуть позже)
5) Создаём автоматические бэкапы. (этот скрипт тоже не останется за кадром)
6) Каждую неделю обновляем named.root и перезапускаем named

newsyslog.conf у меня выглядит так:
# logfilename          [owner:group]    mode count size when  flags [/pid_file] [sig_num]
/var/log/all.log			600  7	   *	@T00  J
/var/log/amd.log			644  7	   100	*     J
/var/log/auth.log			600  7     100  *     JC
/var/log/console.log			600  5	   100	*     J
/var/log/cron				600  3	   100	*     JC
#/var/log/daily.log			640  7	   *	@T00  JN
/var/log/debug.log			600  7     100  *     JC
/var/log/kerberos.log			600  7	   100	*     J
/var/log/lpd-errs			644  7	   100	*     JC
/var/log/maillog			640  7	   *	@T00  JC
/var/log/messages			644  5	   100	*     JC
#/var/log/monthly.log			640  12	   *	$M1D0 JN
/var/log/pflog				600  3	   100	*     JB    /var/run/pflogd.pid
#/var/log/ppp.log	root:network	640  3	   100	*     JC
/var/log/security			600  10	   100	*     JC
/var/log/sendmail.st			640  10	   *	168   B
/var/log/slip.log	root:network	640  3	   100	*     JC
#/var/log/weekly.log			640  5	   1	$W6D0 JN
/var/log/wtmp				644  3	   *	@01T05 B
/var/log/xferlog			600  7	   100	*     JC
/var/log/httpd.access.log	www:www	644  7	   *	$D0   B	    /var/run/lighttpd.pid
/var/log/httpd.error.log	www:www	644  7	   *	$D0   B	    /var/run/lighttpd.pid


 

Заметим последние две строки – они нужны для ротации логов lighttpd (именно такой сервер мы используем...). Остальное – вроде стандартное :)
/etc/ftpusers в моём случае выглядит так (/etc/ftpchroot описан в прошлой статье):
root
toor
daemon
operator
bin
tty
kmem
games
news
man
sshd
bind
proxy
_pflogd
_dhcp
uucp
pop
www
nobody
mailnull
smmsp
mysql
_sphinx
cyrus
@wheel
@mailuser
Создаём группу mailuser:
pw groupadd mailuser
Дальше командой adduser создаём пользователя с UID=1000 и группой www (шелл – csh, домашний каталог, например, /home/web). Этот пользователь будет иметь доступ к ftp и от его имени должен будет работать lighttpd. Очень советую пользователю www сделать такой же UID (в нашей конфигурации 3 пользователя с одинаковым UID'ом, в их числе и www).
Точно так же, можно добавлять почтовых пользователей с шеллом nologin и домашним каталогом /nonexistent
Настройка ntp была описана в прошлой статье, поэтому на этом месте я не останавливаюсь. А вот с named (DNS демон) и sendmail'ом прийдётся немножко поковыряться… И начнём с named.
Первым делом, приведу листинг его рабочей папки (где видны владелец, группа и права):
/usr/var/named/etc/namedb$ ls -al
total 22
drwxr-xr-x  5 root  wheel   512 Feb  9 20:16 .
drwxr-xr-x  3 root  wheel   512 Feb  9 00:44 ..
drwxr-xr-x  2 bind  wheel   512 Nov 26  2008 dynamic
drwxr-xr-x  2 root  wheel   512 Feb  9 20:11 master
-rw-r-----  1 bind  wheel  4035 Feb  9 20:19 named.conf
-rw-r--r--  1 bind  wheel  2969 Nov 26  2008 named.root
-rw-r-----  1 bind  wheel   163 Feb  9 20:14 rndc.conf
-rw-r-----  1 bind  wheel    97 Feb  9 00:44 rndc.key
drwxr-xr-x  2 bind  wheel   512 Nov 26  2008 slave


 

Как генерировать ключ я уже рассказывал. Приведу пример named.conf (прямо с нашего сервера):
options {
	directory	"/etc/namedb";
	pid-file	"/var/run/named/pid";
	listen-on	{
				any;
	};
	version		"Forget it! :)";
	forwarders	{ // DNS серверы провайдера (так будет быстрее, всё-таки)
				193.34.96.113;
				193.34.96.1;
	};
};

key "rndc-key" {
	algorithm	hmac-md5;
	secret		"здесь ключ из rndc.key";
};

controls {
	inet 127.0.0.1 port 953
		allow {
			127.0.0.1;
		} keys {
			"rndc-key";
		};
};

//zone "." {
//	type hint;
//	file "named.root";
//};

// Вместо named.root просто делаем slave для всех зон, это будет быстрее
// ...при условии, что в /etc/resolv.conf записан 127.0.0.1 на первом месте
zone "." {
	type		slave;
	file		"slave/root.slave";
	masters		{
				192.5.5.241;	// F.ROOT-SERVERS.NET.
	};
	notify		no;
};

zone "arpa" {
	type		slave;
	file		"slave/arpa.slave";
	masters		{
				192.5.5.241;	// F.ROOT-SERVERS.NET.
	};
	notify		no;
};

zone "in-addr.arpa" {
	type		slave;
	file		"slave/in-addr.arpa.slave";
	masters		{
				192.5.5.241;	// F.ROOT-SERVERS.NET.
	};
	notify		no;
};

zone "localhost" {
	type		master;
	file		"master/localhost-forward.db";
};

zone "127.in-addr.arpa" {
	type		master;
	file		"master/localhost-reverse.db";
};

zone "255.in-addr.arpa"	{
	type		master;
	file		"master/empty.db";
};

zone "0.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "10.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "16.172.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "17.172.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "18.172.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "19.172.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "20.172.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "21.172.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "22.172.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "23.172.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "24.172.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "25.172.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "26.172.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "27.172.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "28.172.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "29.172.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "30.172.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "31.172.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "168.192.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "254.169.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "2.0.192.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "18.198.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "19.198.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "240.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "241.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "242.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "243.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "244.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "245.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "246.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "247.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "248.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "249.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "250.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "251.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "252.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "253.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

zone "254.in-addr.arpa" {
	type		master;
	file		"master/empty.db";
};

// Our zones
// Наши зоны (как пример)
zone "eugen.su" {
	type		master;
	file		"master/eugen.su";
};

zone "gibs0n.name" {
	type		master;
	file		"master/gibs0n.name";
};

zone "symfony.info" {
	type		master;
	file		"master/symfony.info";
};
Приведу пример одного файла зоны (в нашем случае, они все одинаковые):
$TTL	3600
@	IN	SOA	ns1.tem.dp.ua. tem.tem.dp.ua. (
			2010011001	; Serial
			3600		; Refresh
			900		; Retry
			1209600		; Expire
			3600 )		; Minimum
@	IN	NS	ns1.tem.dp.ua.
@	IN	NS	ns2.tem.dp.ua.
@	IN	NS	ns.iti.dp.ua.
@	IN	MX 10	smtp.tem.dp.ua.
@	IN	MX 20	smtp.iti.dp.ua.
@	IN	A	193.34.96.114
@	IN	A	193.34.96.115
*	IN	A	193.34.96.114
*	IN	A	193.34.96.115


 

Ничего военного. Все эти данные можно получить из DNS :)
Теперь будем настраивать sendmail. Как бы все его не ругали за сложность настройки и т.п. – я его предпочитаю другим MTA...
Первым делом, идём в /etc/mail, где находятся все конфигурационные файлы от sendmail'а.
Настраиваем access. У меня этот файл выглядит вот так:
# local
127.0.0.1	RELAY

# our external IPs
193.34.96.114	RELAY
193.34.96.115	RELAY

# trusted external network
193.34.96.116	RELAY
193.34.96.117	RELAY
193.34.96.118	RELAY
193.34.96.119	RELAY
193.34.96.120	RELAY
193.34.96.121	RELAY
193.34.96.122	RELAY
193.34.96.123	RELAY
193.34.96.124	RELAY
193.34.96.125	RELAY
193.34.96.126	RELAY

# trusted internal (emergency) network
192.168.0	RELAY
Для чего служит этот файл я уже рассказывал :)
Далее редактируем aliases:
root		:	мой_логин
abuse		:	мой_логин

MAILER-DAEMON	:	/dev/null
postmaster	:	/dev/null
_dhcp		:	/dev/null
_pflogd		:	/dev/null
bin		:	/dev/null
bind		:	/dev/null
daemon		:	/dev/null
games		:	/dev/null
kmem		:	/dev/null
mailnull	:	/dev/null
man		:	/dev/null
news		:	/dev/null
nobody		:	/dev/null
operator	:	/dev/null
pop		:	/dev/null
proxy		:	/dev/null
smmsp		:	/dev/null
sshd		:	/dev/null
system		:	/dev/null
toor		:	/dev/null
tty		:	/dev/null
usenet		:	/dev/null
uucp		:	/dev/null
security	:	/dev/null
ftp		:	/dev/null
ftp-bugs	:	/dev/null
hostmaster	:	/dev/null
webmaster	:	/dev/null
www		:	/dev/null
_sphinx		:	/dev/null
mysql		:	/dev/null
cyrus		:	/dev/null
В нашей системе этот файл выглядит таким образом… Лишнюю почту нам получать не нужно :) Список пользователей можно брать из /etc/ftpusers, например.
Далее создаём пустой файл default-auth-info и local-host-names. В local-host-names записываем все домены нашего сервера, по одному на строку. Например, так:
tem.dp.ua
eugen.su
gibs0n.name
symfony.info
Далее можно создать файл virtusertable и записать в него строки вида:
Если добавить сюда все домены, то это даёт возможность рассылать почту, приходящую на адреса admin@<любой_домен_сервера> реальным пользователям – администраторам этих сайтов, например :)
Далее можно выполнить команду rm freebsd* submit* sendmail* (находясь в /etc/mail), чтобы удалить ненужные файлы. Следующим этапом будет редактирование файла, который называется <имя_домена>.mc (в моём случае это eugen.su.mc). В него мы будем добавлять dnsbl серверы, чтобы блокировать спамеров по адресам. Да и ещё некоторые полезные настройки...
Приведу в качестве примера файл eugen.su.mc с нашей системы:
divert(-1)
divert(0)
VERSIONID(`$FreeBSD: src/etc/sendmail/freebsd.mc,v 1.30.2.6.2.1 2008/10/02 02:57:24 kensmith Exp $')
OSTYPE(freebsd6)
DOMAIN(generic)

FEATURE(access_db, `hash -o -T /etc/mail/access')
FEATURE(blacklist_recipients)
FEATURE(local_lmtp)
FEATURE(mailertable, `hash -o /etc/mail/mailertable')
FEATURE(virtusertable, `hash -o /etc/mail/virtusertable')

FEATURE(delay_checks)

FEATURE(dnsbl, `abuse.rfc-ignorant.org', `550 Spam - abuse.rfc-ignorant.org.')
FEATURE(dnsbl, `aspews.ext.sorbs.net', `550 Spam - aspews.ext.sorbs.net.')
FEATURE(dnsbl, `b.barracudacentral.org', `550 Spam - b.barracudacentral.org .')
FEATURE(dnsbl, `bl.csma.biz', `550 Spam - bl.csma.biz.')
FEATURE(dnsbl, `bl.deadbeef.com', `550 Spam - bl.deadbeef.com .')
FEATURE(dnsbl, `bl.spamcannibal.org', `550 Spam - bl.spamcannibal.org.')
FEATURE(dnsbl, `bl.spamcop.net', `550 Spam - bl.spamcop.net .')
FEATURE(dnsbl, `bl.spamcop.net', `550 Spam - bl.spamcop.net.')
FEATURE(dnsbl, `blackholes.brainerd.net', `550 Spam - blackholes.brainerd.net.')
FEATURE(dnsbl, `blackholes.five-ten-sg.com', `550 Spam - blackholes.five-ten-sg.com.')
FEATURE(dnsbl, `blackholes.mail-abuse.org', `550 Spam - blackholes.mail-abuse.org.')
FEATURE(dnsbl, `blackholes.uceb.org', `550 Spam - blackholes.uceb.org.')
FEATURE(dnsbl, `blackholes.wirehub.net', `550 Spam - blackholes.wirehub.net .')
FEATURE(dnsbl, `blacklist.junkemailfilter.com', `550 Spam - blacklist.junkemailfilter.com.')
FEATURE(dnsbl, `blacklist.sci.kun.nl', `550 Spam - blacklist.sci.kun.nl.')
FEATURE(dnsbl, `blacklist.woody.ch', `550 Spam - blacklist.woody.ch.')
FEATURE(dnsbl, `block.dnsbl.sorbs.net', `550 Spam - block.dnsbl.sorbs.net.')
FEATURE(dnsbl, `cbl.abuseat.org', `550 Spam - cbl.abuseat.org.')
FEATURE(dnsbl, `cbl.anti-spam.org.cn', `550 Spam - cbl.anti-spam.org.cn.')
FEATURE(dnsbl, `cblless.anti-spam.org.cn', `550 Spam - cblless.anti-spam.org.cn .')
FEATURE(dnsbl, `cblplus.anti-spam.org.cn', `550 Spam - cblplus.anti-spam.org.cn.')
FEATURE(dnsbl, `combined.njabl.org', `550 Spam - combined.njabl.org.')
FEATURE(dnsbl, `db.wpbl.info', `550 Spam - db.wpbl.info .')
FEATURE(dnsbl, `dialups.mail-abuse.org', `550 Spam - dialups.mail-abuse.org.')
FEATURE(dnsbl, `dialups.visi.com', `550 Spam - dialups.visi.com.')
FEATURE(dnsbl, `dnsbl-0.uceprotect.net', `550 Spam - dnsbl-0.uceprotect.net .')
FEATURE(dnsbl, `dnsbl-1.uceprotect.net', `550 Spam - dnsbl-1.uceprotect.net.')
FEATURE(dnsbl, `dnsbl-2.uceprotect.net', `550 Spam - dnsbl-2.uceprotect.net.')
FEATURE(dnsbl, `dnsbl-3.uceprotect.net', `550 Spam - dnsbl-3.uceprotect.net .')
FEATURE(dnsbl, `dnsbl.ahbl.org', `550 Spam - dnsbl.ahbl.org.')
FEATURE(dnsbl, `dnsbl.njabl.org', `550 Spam - dnsbl.njabl.org.')
FEATURE(dnsbl, `dnsbl.sorbs.net', `550 Spam - dnsbl.sorbs.net .')
FEATURE(dnsbl, `dnsbl.sorbs.net', `550 Spam - dnsbl.sorbs.net.')
FEATURE(dnsbl, `duinv.aupads.org', `550 Spam - duinv.aupads.org.')
FEATURE(dnsbl, `dul.dnsbl.sorbs.net', `550 Spam - dul.dnsbl.sorbs.net.')
FEATURE(dnsbl, `dul.ru', `550 Spam - dul.ru.')
FEATURE(dnsbl, `dun.dnsrbl.net', `550 Spam - dun.dnsrbl.net.')
FEATURE(dnsbl, `ex.dnsbl.org', `550 Spam - ex.dnsbl.org.')
FEATURE(dnsbl, `http.dnsbl.sorbs.net', `550 Spam - http.dnsbl.sorbs.net .')
FEATURE(dnsbl, `ips.backscatterer.org', `550 Spam - ips.backscatterer.org.')
FEATURE(dnsbl, `ircbl.ahbl.org', `550 Spam - ircbl.ahbl.org.')
FEATURE(dnsbl, `ix.dnsbl.manitu.net', `550 Spam - ix.dnsbl.manitu.net .')
FEATURE(dnsbl, `korea.services.net', `550 Spam - korea.services.net.')
FEATURE(dnsbl, `l2.bbfh.ext.sorbs.net', `550 Spam - l2.bbfh.ext.sorbs.net.')
FEATURE(dnsbl, `list.dsbl.org', `550 Spam - list.dsbl.org.')
FEATURE(dnsbl, `mail-abuse.blacklist.jippg.org', `550 Spam - mail-abuse.blacklist.jippg.org .')
FEATURE(dnsbl, `misc.dnsbl.sorbs.net', `550 Spam - misc.dnsbl.sorbs.net.')
FEATURE(dnsbl, `msgid.bl.gweep.ca', `550 Spam - msgid.bl.gweep.ca.')
FEATURE(dnsbl, `multi.surbl.org', `550 Spam - multi.surbl.org .')
FEATURE(dnsbl, `multi.uribl.com', `550 Spam - multi.uribl.com.')
FEATURE(dnsbl, `no-more-funn.moensted.dk', `550 Spam - no-more-funn.moensted.dk.')
FEATURE(dnsbl, `overdb.aupads.org', `550 Spam - overdb.aupads.org.')
FEATURE(dnsbl, `pbl.spamhaus.org', `550 Spam - pbl.spamhaus.org .')
FEATURE(dnsbl, `phishing.rbl.msrbl.net', `550 Spam - phishing.rbl.msrbl.net.')
FEATURE(dnsbl, `probes.dnsbl.net.au', `550 Spam - probes.dnsbl.net.au.')
FEATURE(dnsbl, `proxy.bl.gweep.ca', `550 Spam - proxy.bl.gweep.ca .')
FEATURE(dnsbl, `psbl.surriel.com', `550 Spam - psbl.surriel.com.')
FEATURE(dnsbl, `rbl-plus.mail-abuse.org', `550 Spam - rbl-plus.mail-abuse.org.')
FEATURE(dnsbl, `rbl.snark.net', `550 Spam - rbl.snark.net .')
FEATURE(dnsbl, `rdts.dnsbl.net.au', `550 Spam - rdts.dnsbl.net.au.')
FEATURE(dnsbl, `relays.bl.gweep.ca', `550 Spam - relays.bl.gweep.ca.')
FEATURE(dnsbl, `relays.bl.kundenserver.de', `550 Spam - relays.bl.kundenserver.de .')
FEATURE(dnsbl, `relays.mail-abuse.org', `550 Spam - relays.mail-abuse.org.')
FEATURE(dnsbl, `relays.nether.net', `550 Spam - relays.nether.net.')
FEATURE(dnsbl, `relays.ordb.org', `550 Spam - relays.ordb.org.')
FEATURE(dnsbl, `ricn.dnsbl.net.au', `550 Spam - ricn.dnsbl.net.au .')
FEATURE(dnsbl, `rsbl.aupads.org', `550 Spam - rsbl.aupads.org.')
FEATURE(dnsbl, `sbl-xbl.spamhaus.org', `550 Spam - sbl-xbl.spamhaus.org.')
FEATURE(dnsbl, `sbl.spamhaus.org', `550 Spam - sbl.spamhaus.org.')
FEATURE(dnsbl, `smtp.dnsbl.sorbs.net', `550 Spam - smtp.dnsbl.sorbs.net .')
FEATURE(dnsbl, `socks.dnsbl.sorbs.net', `550 Spam - socks.dnsbl.sorbs.net.')
FEATURE(dnsbl, `socks.opm.blitzed.org', `550 Spam - socks.opm.blitzed.org.')
FEATURE(dnsbl, `sorbs.dnsbl.net.au', `550 Spam - sorbs.dnsbl.net.au .')
FEATURE(dnsbl, `spam.dnsbl.sorbs.net', `550 Spam - spam.dnsbl.sorbs.net.')
FEATURE(dnsbl, `spam.olsentech.net', `550 Spam - spam.olsentech.net .')
FEATURE(dnsbl, `spamguard.leadmon.net', `550 Spam - spamguard.leadmon.net.')
FEATURE(dnsbl, `spamrbl.imp.ch', `550 Spam - spamrbl.imp.ch.')
FEATURE(dnsbl, `spamsites.dnsbl.net.au', `550 Spam - spamsites.dnsbl.net.au.')
FEATURE(dnsbl, `spamsources.dnsbl.info', `550 Spam - spamsources.dnsbl.info .')
FEATURE(dnsbl, `spamsources.fabel.dk', `550 Spam - spamsources.fabel.dk.')
FEATURE(dnsbl, `t1.dnsbl.net.au', `550 Spam - t1.dnsbl.net.au.')
FEATURE(dnsbl, `ubl.unsubscore.com', `550 Spam - ubl.unsubscore.com.')
FEATURE(dnsbl, `ucepn.dnsbl.net.au', `550 Spam - ucepn.dnsbl.net.au .')
FEATURE(dnsbl, `virbl.bit.nl', `550 Spam - virbl.bit.nl.')
FEATURE(dnsbl, `virbl.dnsbl.bit.nl', `550 Spam - virbl.dnsbl.bit.nl.')
FEATURE(dnsbl, `virus.rbl.jp', `550 Spam - virus.rbl.jp .')
FEATURE(dnsbl, `virus.rbl.msrbl.net', `550 Spam - virus.rbl.msrbl.net.')
FEATURE(dnsbl, `web.dnsbl.sorbs.net', `550 Spam - web.dnsbl.sorbs.net.')
FEATURE(dnsbl, `whois.rfc-ignorant.org', `550 Spam - whois.rfc-ignorant.org .')
FEATURE(dnsbl, `xbl.spamhaus.org', `550 Spam - xbl.spamhaus.org.')
FEATURE(dnsbl, `zen.spamhaus.org', `550 Spam - zen.spamhaus.org.')
FEATURE(dnsbl, `zombie.dnsbl.sorbs.net', `550 Spam - zombie.dnsbl.sorbs.net.')

define(`confCW_FILE', `-o /etc/mail/local-host-names')

dnl set SASL options
TRUST_AUTH_MECH(`GSSAPI DIGEST-MD5 CRAM-MD5 LOGIN')
define(`confAUTH_MECHANISMS', `GSSAPI DIGEST-MD5 CRAM-MD5 LOGIN PLAIN')

DAEMON_OPTIONS(`Name=IPv4, Family=inet')

define(`confBIND_OPTS', `WorkAroundBrokenAAAA')
define(`confNO_RCPT_ACTION', `add-to-undisclosed')
define(`confPRIVACY_FLAGS', `authwarnings,noexpn,novrfy')
MAILER(local)
MAILER(smtp)

LOCAL_RULESETS
HX-Mailer: $>CheckMailer
HX-Server: $>CheckMailer

SCheckMailer
RAdvanced Direct Remailer $*	$#error $@ 5.7.1 $: "554 Spam."
RAdvanced Mass Sender $*	$#error $@ 5.7.1 $: "554 Spam."
RSpammer $*			$#error $@ 5.7.1 $: "554 Spam."
R$* Bomber $*			$#error $@ 5.7.1 $: "554 Spam."
RMega-Mailer $*			$#error $@ 5.7.1 $: "554 Spam."
RMMailer $*			$#error $@ 5.7.1 $: "554 Spam."
RMailer $*			$#error $@ 5.7.1 $: "554 Spam."
RLigra Mailer $*		$#error $@ 5.7.1 $: "554 Spam."
RDynamic Opt-In Emailer $*	$#error $@ 5.7.1 $: "554 Spam."
R$* Group Spamer		$#error $@ 5.7.1 $: "554 Spam."
RMail Sender $*			$#error $@ 5.7.1 $: "554 Spam."
RMail Service $*		$#error $@ 5.7.1 $: "554 Spam."
RMailloop $*			$#error $@ 5.7.1 $: "554 Spam."
RPersMail $*			$#error $@ 5.7.1 $: "554 Spam."
RLK SendIt $*			$#error $@ 5.7.1 $: "554 Spam."
RWC Mail $*			$#error $@ 5.7.1 $: "554 Spam."
RZUBA ZUB $*			$#error $@ 5.7.1 $: "554 Spam."
RMailList Express $*		$#error $@ 5.7.1 $: "554 Spam."
RCaretop $*			$#error $@ 5.7.1 $: "554 Spam."
RMailer Signature		$#error $@ 5.7.1 $: "554 Spam."
Rnone				$#error $@ 5.7.1 $: "554 Spam."
RPG-MAILINGLIST			$#error $@ 5.7.1 $: "554 Spam."
R$* advcomtest $*		$#error $@ 5.7.1 $: "554 Spam."
Ryo yo mail			$#error $@ 5.7.1 $: "554 Spam."
RZanziMailer $*			$#error $@ 5.7.1 $: "554 Spam."
RMicrosoft Outlook Express 5.0	$#error $@ 5.7.1 $: "554 Spam."
RVersion 5.0			$#error $@ 5.7.1 $: "554 Spam."
Rnethack			$@ OK
RZ-Mail-SGI			$@ OK
RDipost				$@ OK
R$*				$: < $1 >
R< >				$#error $@ 5.7.1 $: "554 Spam."
R$*				$@ OK

HTo: $>CheckTo
HCC: $>CheckTo
HMessage-ID: $>CheckMessageID

SCheckTo
R$*Recipient$*			$#error $@ 5.7.1 $: "554 Spam."
R$*Undisclosed$*		$#error $@ 5.7.1 $: "554 Spam."

SCheckMessageID
R<$+@$+>			$@ < $1 @ $2 >
R$*				$#error $@ 5.5.2 $: "553 Spam."

Kdnsname dns -R PTR
Kfrmail regex -aFRRRMAIL ^(.*-.*-.*-.*|.*adsl.*|.*dhcp.*)$

Сохраняем этот файл и делаем команду make (находясь в /etc/mail). После этого прийдётся подредактировать файл <имя_домена>.cf:
Находим первую строку
# DNS based IP address spam list
И добавляем перед ней:
# DNS based (DNS-name - PTR-record)
R$*			$: $&{client_addr}
R$-.$-.$-.$-		$:  $(dnsname $4.$3.$2.$1.in-addr.arpa. $: OK $)
ROK		$#error $@ 5.7.1 $: 550 Spam.
R$+			$: OKSOFAR

# DNS based (DNS-name - *-*-*-*)
R$*			$: $&{client_addr}
R$-.$-.$-.$-		$:  $(dnsname $4.$3.$2.$1.in-addr.arpa. $: OK $)
R$*			$: $(frmail $1 $)
RFRRRMAIL	$#error $@ 5.7.1 $: 550 Spam.
R$+			$: OKSOFAR

Следующим шагом ищем и раскомментируем строки (по одной):
O DefaultAuthInfo=/etc/mail/default-auth-info
Сразу после неё идёт строка (закомментированная):
O AuthOptions
Её нужно раскомментировать и привести к такому виду:
O AuthOptions=A
После этого выполняем две команды – make и make install. Sendmail только может выругаться на отсутствие SASL, но это не проблема :) Сейчас мы и его установим :)

Редактируем файл /etc/rc.conf, добавляем в конец строку:
saslauthd_enable=«YES»
Я люблю делать это заранее...
Теперь идём в /usr/ports/security/cyrus-sasl2 и набираем уже привычные нам 3 слова – make install clean. При этом, у нас запросят некоторые настройки. Мой выбор был такой:
             Options for cyrus-sasl 2.1.23
---------------------------------------------------------
[ ] BDB           Use Berkeley DB
[ ] MYSQL         Use MySQL
[ ] PGSQL         Use PostgreSQL
[ ] SQLITE        Use SQLite
[ ] DEV_URANDOM   Use /dev/urandom
[ ] ALWAYSTRUE    Enable the alwaystrue password verifier
[ ] KEEP_DB_OPEN  Keep handle to Berkeley DB open
[X] AUTHDAEMOND   Enable use of authdaemon
[X] LOGIN         Enable LOGIN authentication
[X] PLAIN         Enable PLAIN authentication
[X] CRAM          Enable CRAM-MD5 authentication
[X] DIGEST        Enable DIGEST-MD5 authentication
[ ] OTP           Enable OTP authentication
[ ] NTLM          Enable NTLM authentication


 

Дальше установка должна пойти «как обычно» :)
После установки этого порта, идём в /usr/ports/cyrus-sasl2-saslauthd и делаем то же самое. Здесь только настройки будут вот такими (всё пусто):
  Options for cyrus-sasl-saslauthd 2.1.23
---------------------------------------------
[ ] BDB       Use Berkeley DB
[ ] OPENLDAP  Use OpenLDAP
[ ] HTTPFORM  Enable HTTP form authentication
Дальше, в качестве перерыва, можно поставить свои любимые порты – mc, wget, lynx, архиваторы вроде rar/unrar, zip/unzip – это то, что в хозяйстве всегда пригодится :) Обязательно проверяем права на папку /root – очень часто она читаемая для всех. Права на неё должны быть 0700 или drwx------. Иначе быть не должно :)

Теперь расскажу про два скрипта, которые были упомянуты выше – /root/backup и /root/bashorg
И начну я со скрипта для автоматического резервного копирования всех данных (файлов и баз MySQL) пользователей из группы www.
Сам скрипт написан мной на шелле (первая версия была написана на php, но в данном случае это просто неразумное использование ресурсов – можно ведь и на шелле сделать) :)
Алгоритм очень простой :) Скрипт берёт всех пользователей из группы www (задано в переменной), создаёт у них в домашнем каталоге папку backups (задано в переменной) и создаёт в ней архивы (название архива соответствует названию архивируемой папки). Сохраняются 3 последние архива. Точно так же и с базой данных – скрипт архивирует и помещает в этот каталог дампы баз, названия которых соответствуют имени системного пользователя или попадают под маску <имя_пользователя>_*. Так же, самые последние архивы и дампы сохраняются в папке /base/backups (задано в переменной). Естественно, что права на эту папку должны быть соответствующими… Так же, необходимо задать логин/пароль суперпользователя MySQL (который имеет право читать все базы данных) и путь к базам MySQL. Существует возможность задать базы данных (через пробел), которые ни при каких условиях не будут архивироваться. В нашем случае это полезно для одной очень большой базы (несколько гигабайт), которая никогда не изменяется – просто она была скопирована один раз, и нет необходимости архивировать её каждый раз заново. Поскольку скрипт передаёт пароль в аргументах к mysqldump, то здесь очень уместна опция в sysctl.conf, которая запрещает пользователям просматривать чужие процессы.
Собственно, сам скрипт /root/backup:
#!/bin/sh

www_group="www"			# Name of group to backup
mysql_user="root"		# MySQL superuser
mysql_pass="password"	# MySQL password
mysql_path="/var/db/mysql"	# Path to MySQL's databases
mysql_ignore_dbs="abc"	# Databases excluded from backup
backup_dirname="backups"	# Name of backups directory
backup_store="/base/backups"	# Path to backup storage

umask 0277
www_gid=`pw groupshow ${www_group} | awk -F : '{print $3}'`
userlst=`cat /etc/passwd | grep -v '^#' | awk -F : '{print $1,$4}' | grep " ${www_gid}\$" | awk '{print $1}'`
mysql_dumped='0'
for user in ${userlst}
do
  home=`pw usershow ${user} | awk -F : '{print $9}'`
  if [ -d ${home} ]
  then
    backups="${home}/${backup_dirname}"
    if [ ! -d ${backups} ]
    then
      mkdir ${backups}
    fi
    chown "${user}:${www_gid}" ${backups}
    chmod 0500 ${backups}
    for dir in `ls -Af ${home}`
    do
      if [ ${dir} != ${backup_dirname} ]
      then
        if [ -f "${backups}/backup.${dir}.2.tgz" ]
        then
          mv "${backups}/backup.${dir}.2.tgz" "${backups}/backup.${dir}.3.tgz"
        fi
        if [ -f "${backups}/backup.${dir}.1.tgz" ]
        then
          mv "${backups}/backup.${dir}.1.tgz" "${backups}/backup.${dir}.2.tgz"
        fi
        if [ -d "${home}/${dir}" ]
        then
          tar -cz -C ${home} -f "${backups}/backup.${dir}.1.tgz" ${dir}
          cp "${backups}/backup.${dir}.1.tgz" "${backup_store}/${user}_backup.${dir}.tgz"
          chown "${user}:${www_gid}" "${backups}/backup.${dir}.1.tgz"
        fi
      fi
    done
    for djustifyb in `ls -Af ${mysql_path}`
    do
      if [ -d "${mysql_path}/${db}" ]
      then
        cf=`echo ${user} | wc | awk '{print $3}'`
        cmp=`echo $db | cut -c "-${cf}"`
        disabled='0'
        for ddb in ${mysql_ignore_dbs}
        do
          if [ ${db} = ${ddb} ]
          then
            disabled='1'
            break
          fi
        done
        if [ ${cmp} = ${user} -o ${cmp} = "${user}_" -o ${db} = 'mysql' ]
        then
          if [ ${disabled} != '1' ]
	  then
            if [ ${db} = 'mysql' ]
            then
              destination="${backup_store}/dump.mysql.gz"
              if [ ${mysql_dumped} = '1' ]
              then
                continue
              fi
              mysql_dumped='1'
            else
              destination="${backups}/dump.${db}.1.gz"
              if [ -f "${backups}/dump.${db}.2.gz" ]
              then
                mv "${backups}/dump.${db}.2.gz" "${backups}/dump.${db}.3.gz"
              fi
              if [ -f "${backups}/dump.${db}.1.gz" ]
              then
                mv "${backups}/dump.${db}.1.gz" "${backups}/dump.${db}.2.gz"
              fi
            fi
            mysqlcheck -o -r "--user=${mysql_user}" "--password=${mysql_pass}" ${db} > /dev/null 2>&1
            mysqldump "--user=${mysql_user}" "--password=${mysql_pass}" --add-drop-table --add-drop-database --add-locks --extended-insert --quick --skip-comments --quote-names --complete-insert ${db} | gzip -9 > ${destination}
          fi
        fi
      fi
    done
  fi
done


 

/root/bashorg – прикольный, но абсолютно бесполезный скрипт :) Обновляет приветствия в системе с bash.org.ru. Первая версия тоже была написана на php, однако буквально несколько дней назад переписал на шелл. Думаю, что обновление цитаты на приветствии в системе один раз в час не будет сильно нагружать сеть… Единственное что, для работы скрипта нужен iconv, который мы дальше будем ставить как модуль для php (но у которого есть и CLI) :) Хотя, вполне вероятно, что iconv у вас уже будет стоять, если вы устанавливали mc...
Скрипт:
#!/bin/sh
fetch -q -o - http://bash.org.ru/random | grep -e '
.*
' -m 1 | awk '{gsub(" ","\n",$0);print}' | sed -e 's/<.*>//g' -e 's/"/"/g' -e 's/ / /g' -e 's/<//g' -e 's/©/(c)/g' -e 's/&/&/g' | sed -e 's/^[ \t]*//;s/[ \t]*$//' | awk '{gsub("^[ \t]*","",$0);print}' > /etc/ftpwelcome iconv -c -f CP1251 -t KOI8-R /etc/ftpwelcome > /etc/motd
Естественно, что права обоих скриптов должны позволять запускать их. Т.е., например, 0700 (владелец root, и запуск из crontab'а от root'а).
Теперь можно поставить утилиты типа mbmon, smartd, а так же, SSH сервер...
Начнём с mbmon, т.к. это проще всего :)
Идём в /usr/ports/sysutils/mbmon и пишем свои любимые три слова – make install clean :) Конфигурация вот такая:
  Options for mbmon 205_5
------------------------------
[ ] SMB  enable smb(4) support
После установки проверяем – выполняем команду mbmon –A –c1 -u, и, если она не ругается, а пишет температуры – значит, всё работает :) Можно добавить в inetd (подразумевается, что все дополнительные TCP порты были добавлены в /etc/services):

mbmon stream tcp nowait/20/0/10 root /usr/local/bin/mbmon mbmon –A –c1 -u

Сразу оговорюсь. Мой inetd.conf выглядит таким образом:
#mbmon		stream	tcp	nowait/20/0/10	root	/usr/local/bin/mbmon	mbmon -A -c1 -u
smartd		stream	tcp	nowait/20/0/10	root	/usr/local/bin/smart	smart
pop3pw		stream	tcp	nowait/50/0/20	root	/usr/local/libexec/poppwd	poppwd
pop3		stream	tcp	nowait/100/0/50	root	/usr/local/libexec/qpopper	qpopper -c -C -F -s -S -T 600

 

Здесь мы видим закомментированный mbmon (он не работает с моей материнкой), smartd, poppwd (сервер для смены паролей) и pop3 сервер. Естественно, inetd должен быть включен в rc.conf.
Установка всех этих сервисов описана далее, однако про inetd я больше не буду делать оговорки :)

Теперь мы будем ставить smartd. Идём в /usr/ports/sysutils/smartmontools и опять запускаем нашу любимую команду из трёх слов :) (в смысле, «make install clean», а не «иди на х..» :) ) Конфигурировать здесь нечего, поэтому просто дожидаемся окончания установки.
После всего этого нужно добавить в rc.conf (а можно и не добавлять, но правильнее будет добавить):
mbmon_enable=«NO»
smartd_enable=«NO»

Нам ведь не нужно, чтобы они запускались самостоятельно...
Теперь нужно создать скрипт для запуска smartctl из inetd. Создаём файл /usr/local/bin/smart, делаем ему права, которые позволят выполнять его, и пишем в него:
#!/bin/sh
read disk
echo $disk | awk '{print $1}' | xargs /usr/local/sbin/smartctl -iA

Дальше с ним можно будет общаться, например, таким php скриптом:

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

Последнее, что нам осталось в этой части – это установить SSH и почтовый сервер (вместе с сервером для смены паролей).

Начнём с конца, т.к. это проще :)
cd /usr/ports/mail/poppwd && make install clean – без дополнительных опций, всё просто и красиво :) Это будет сервер для смены паролей (должен работать на 106-м порту).
Теперь pop3:
cd /usr/ports/mail/qpopper && make install clean – а вот POP3 сервер уже запросит некоторые настройки:
                Options for qpopper 4.0.9_2
--------------------------------------------------------------
[ ] APOP_ONLY        build with APOP authentication only
[ ] APOP             build with APOP
[ ] DOCUMENTATION    install pdf documentation
[ ] DRAC             build with Dynamic Relay Authorization
[ ] FULL_POPD_DEBUG  build with more verbose debugging
[ ] PAM              build with PAM authentication
[ ] POPPASSD         build the poppassd daemon
[X] QPOPAUTH_SETUID  install qpopauth setuid to pop user
[X] SAMPLE_POPUSERS  build a default reject file
[X] SHY_ENABLED      hide qpopper version in POP3 banner
[ ] SSL              build with SSL/TLS support
[ ] STANDALONE_MODE  build qpopper to be run without inetd
[ ] U_OPTION         include support for user .qpopper-options


 

После всего этого, у нас появится файл /usr/local/etc/qpopper/popusers – список пользователей, которым запрещён вход через POP3. Файл копируется из /etc/ftpusers, поэтому нам прийдётся немного поработать над ним – убрать @mailuser и @www из этого файла, чтобы разрешать доступ к почте пользователям из этих групп. Пример приводить не буду, т.к. уже приводил пример /etc/ftpusers :)

Теперь SSH сервер. Нам нужен хороший, быстрый и безопасный SSH сервер, у которого нет проблем при работе с разными клиентами (например, у стандартного sshd есть проблемы при работе с ShellGuard'ом, и как их решить, я пока не разобрался, хотя ShellGuard – мой любимый SSH клиент).
Первым делом, в rc.conf добавляем строку:
sshd2_enable=«YES»
Теперь идём в /usr/ports/security/ssh2-nox11 и пишем любимые три слова. Опций запрашивать, вроде, не должно. Конфигурационный файл находится по адресу /usr/local/etc/ssh2/sshd2_config
В общем, рассказывать про отдельные опции я не буду, т.к. они абсолютно аналогичны опциям стандартного sshd. Единственное что, всё-таки, приведу пример рабочего конфига :)
## SSH CONFIGURATION FILE FORMAT VERSION 1.1
## REGEX-SYNTAX egrep
## end of metaconfig
## (leave above lines intact!)
## sshd2_config
## SSH 3.2 Server Configuration File
##

## General

#	HostKeyFile			hostkey
#	PublicHostKeyFile		hostkey.pub
#	RandomSeedFile			random_seed
#	BannerMessageFile		/usr/local/etc/ssh2/ssh_banner_message
#	BannerMessageFile		/etc/issue.net
#
#	VerboseMode			no
#	QuietMode			no
#	SyslogFacility			AUTH
#	SyslogFacility			LOCAL7
#	SftpSyslogFacility		LOCAL7

## Network

# Port is commented out as it is specified by the startup script.
	Port 				22
	ListenAddress			any
	ResolveClientHostName		no
#	RequireReverseMapping		no
#	MaxBroadcastsPerSecond		0
#	MaxBroadcastsPerSecond		1  
#	NoDelay				no
#	KeepAlive			yes
	MaxConnections			50
#	MaxConnections			0 
# 0 == number of connections not limited 

## Crypto

#	Ciphers				AnyCipher
#	Ciphers				AnyStdCipher
#	Ciphers				3des
# Following includes "none" 'cipher': 
#	Ciphers				AnyStd
#
#	MACs				AnyMAC
#	MACs				AnyStdMAC
# Following includes "none" 'mac':
#	MACs				AnyStd
#
#	RekeyIntervalSeconds		3600

## User

	PrintMotd			yes
	CheckMail			yes
#	StrictModes			yes
# Specifies 1 hour (you can also use 'w' for week, 'd' for day, 'm' for
#                   minute, 's' for seconds)
	IdleTimeOut			6h
# without specifier, the default number is in seconds
#	IdleTimeOut			3600
#
#	UserConfigDirectory		"%D/.ssh2"
#	UserConfigDirectory		"/usr/local/etc/ssh2/auth/%U"
#	AuthorizationFile		authorization
# This variable is set here, because by default it's empty, and so no
# variables can be set. Because of that, we set a few common ones here.
	SettableEnvironmentVars		LANG,LC_(ALL|COLLATE|CTYPE|MONETARY|NUMERIC|TIME),PATH,TERM,TZ
	
## Tunneling

	AllowX11Forwarding		no
#	AllowTcpForwarding		yes
#	AllowTcpForwardingForUsers	sjl, cowboyneal@slashdot\.org
#	DenyTcpForwardingForUsers	2[[:digit:]]*4,peelo
#	AllowTcpForwardingForGroups	privileged_tcp_forwarders
#	DenyTcpForwardingForGroups	coming_from_outside
#
# Local port forwardings to host 10.1.0.25 ports 143 and 25 are 
# allowed for all users in group users.
# Note that forwardings using the name of this host will be allowed (if
# it can be resolved from the DNS). 
#
#      ForwardACL allow local .*%users \i10\.1\.0\.25%(143|25)
#
# Local port forwardings requested exactly to host proxy.company.com
# port 8080 are allowed for users that have 's' as first character
# and belong to the group with group id 10:
#
#      ForwardACL allow local s.*%10 proxy\.company\.com%8080
#
# Remote port forwarding is denied for all users to all hosts:
#      ForwardACL deny remote .* .*


## Authentication
## publickey and password allowed by default

#	AllowedAuthentications		publickey,password
#	AllowedAuthentications		hostbased,publickey,password
#	AllowedAuthentications		hostbased,publickey,keyboard-interactive
#	RequiredAuthentications		publickey,password
	LoginGraceTime			60
#	AuthInteractiveFailureTimeout	2
#
#	HostbasedAuthForceClientHostnameDNSMatch no
#	UserKnownHosts			yes
#
#	AuthPublicKey.MaxSize		0
#	AuthPublicKey.MinSize		0
#	AllowAgentForwarding		yes
#
#	AuthKbdInt.NumOptional		0
#	AuthKbdInt.Optional		pam,password
#	AuthKbdInt.Required		password 
#	AuthKbdInt.Retries		3
#
	PermitEmptyPasswords		no
	PasswordGuesses			3

## Host restrictions

#	AllowHosts			localhost, foobar.com, friendly.org
#
## Next one matches with, for example, taulu.foobar.com, tuoli.com, but
## not tuoli1.com. Note that you have to input string "\." when you want it
## to match only a literal dot. You also have to escape "," when you
## want to use it in the pattern, because otherwise it is considered a list
## separator.
## 
##     AllowHosts		t..l.\..*
##
## The following matches any numerical IP-address (yes, it is cumbersome)
##
##     AllowHosts		([[:digit:]]{1\,3}\.){3}[[:digit:]]{1\,3}
##
## Same thing is achieved with using the special prefix "\i" in a
## pattern. This means that the pattern is only used to match
## IP-addresses.
##
## Using the above example:
##
##     AllowHosts		\i.*
##
## You can probably see the difference between the two.
##
## Also, you can use subnet masks, by using prefix "\m"
##
##     AllowHosts		\m127.0/8
## and
##     AllowHosts		\m127.0.0.0/24
##
## would match localhost ("127.0.0.1").
##
#	DenyHosts			evil\.org, aol\.com
#	AllowSHosts			trusted\.host\.org
#	DenySHosts			not\.quite\.trusted\.org
#	IgnoreRhosts			no
#	IgnoreRootRHosts		no
# (the above, if not set, is defaulted to the value of IgnoreRHosts)

## User restrictions

#	AllowUsers			sj.*,s[[:digit:]]*,s(jl|amza)
#	DenyUsers			skuuppa,warezdude,31373
#	DenyUsers			don@untrusted\.org
#	AllowGroups			staff,users
	DenyGroups			mailuser
	PermitRootLogin			yes
#	PermitRootLogin			nopwd

## Chrooted environment

#	ChRootUsers			anonymous,ftp,guest
#	ChRootGroups			sftp,guest

## SSH1 compatibility

#	Ssh1Compatibility		no
#	Sshd1Path			
#
# This is given as argument to sshd1 with "-f" if sshd2 is invoked
# with "-f", otherwise the default configuration for sshd1 is used.
#	Sshd1ConfigFile			/etc/sshd_config_alternate

## subsystem definitions

# Subsystems don't have defaults, so this is needed here (uncommented).
#	subsystem-sftp                  sftp-server
# Also internal sftp-server subsystem can be used.
#	subsystem-sftp			internal://sftp-server

## Subconfiguration
# There are no default subconfiguration files. When specified the last
# obtained keyword value will prevail. Note that the host specific files
# are read before the user specific files.

# Following matches (from) any host
#
#      HostSpecificConfig .* /usr/local/etc/ssh2/subconfig/host_ext.example
#
# Following matches to subnet mask:
#
#      HostSpecificConfig \m192.168.0.0/16 /usr/local/etc/ssh2/subconfig/host_int.example
#
# Following matches to users from ssh.com that have two character long
# username or is sjl and belong to group wheel or wheel[0-9]
#
#      UserSpecificConfig (..|sjl)%wheel[[:digit:]]?@ssh\.com /usr/local/etc/ssh2/subconfig/user.example
#
# Following matches to the user anonymous from any host
#
#      UserSpecificConfig anonymous@.* /usr/local/etc/ssh2/subconfig/anonymous.example

После настройки нужно запустить команду:
/usr/local/etc/rc.d/sshd2 keygen
Чтобы сервер сгенерировал публичный и приватный ключи. По идее, он это должен сделать сам при первом запуске, но я, как правило, делаю это сам, до его первого запуска.

Вот мы и закончили с первой частью – настройкой системы и её основных компонентов. Теперь приступим к установке MySQL, php (со всеми потрохами) и веб-сервера lighttpd.
Итак, начинаем с MySQL. Сначала редактируем /etc/rc.conf и добавляем в него строку:
mysql_enable=«YES»
Теперь идём в /usr/ports/databases и делаем make install clean. Все опции у нас уже заданы в /etc/make.conf (если не заданы – можно устанавливать MySQL и так, как это было описано мной ранее).

После установки запускаем /usr/local/etc/rc.d/mysql-server start – при первом запуске MySQL создаст все нужные папки и файлы (кроме конфига).
Идём в /var/db/mysql и создаём файл my.cnf (пример):
[client]
# Настройки для клиента
port		= 3306 # Порт по умолчанию
socket		= /tmp/mysql.sock # Путь к сокету

[mysqld]
# Настройки для сервера
port		= 3306 # Порт
socket		= /tmp/mysql.sock # Путь к сокету
back_log	= 200
# С этой опцией MySQL вообще не будет использовать TCP порт.
# Все соединения будут осуществляться через файл-сокет, что гораздо быстрее
skip-networking
# Максимальное количество одновременных подключений
max_connections = 100
# Максимальное количество ошибок, после которого MySQL будет считать хост «умершим».
# Указано максимально допустимое значение, т.к. ошибки авторизации тоже учитываются.
# Однако, при успешном подключении, счётчик ошибок будет сбрасываться
max_connect_errors = 4294967295
# Этот ключ определяет память, выделяемую для хранения открытых таблиц
table_open_cache = 1024
# Максимальный размер пакета
max_allowed_packet = 128M
# Размер кэша, который будет хранить SQL-операторы для бинарного журнала регистраций
# во время транзакции
binlog_cache_size = 16M
# Максимальный размер таблицы типа HEAP
max_heap_table_size = 128M
# Размер КЭШа, который будет хранить SQL-операторы для бинарного журнала регистраций.
sort_buffer_size = 32
# Размер буфера, используемого при операциях полного соединения таблиц (без индексов)
join_buffer_size = 8M
# Количество потоков, которое сервер должен поместить в кэш для повторного использования
thread_cache_size = 8		
# Количество одновременно запускаемых потоков
thread_concurrency = 4
# Память, выделяемая для кэширования результатов запросов
query_cache_size = 64M
# Не помещать в кэш результаты, размер которых больше этого числа
query_cache_limit = 2M
# Минимальная длина слова для полнотекстового поиска
ft_min_word_len = 3
# Тип таблицы по умолчанию
default-storage-engine = InnoDB
# Размер стека для каждого потока
thread_stack = 192K
# Изолированность транзакций
transaction_isolation = SERIALIZABLE
# Максимальный размер временных таблиц
tmp_table_size = 128M
# Файл бинарного журнала
log-bin=mysql-bin
# Формат файла бинарного журнала
binlog_format=mixed
# Время (в секундах), по истечению которого запрос будет считаться «долгим»
long_query_time = 5
# Путь ко временной папке
tmpdir = /tmp
# Номер сервера, если используется сеть MySQL серверов.
# Если сервер один – оставляем здесь 1.
server-id = 1
# Размер буфера, используемого для хранения блоков индексов
key_buffer_size = 256M
# Размер буфера для операций чтения
read_buffer_size = 16M
# Размер буфера для упорядоченного чтения
read_rnd_buffer_size = 16M
# Размер кэша, используемого при оптимизации групповой вставки
bulk_insert_buffer_size = 128M
# Буфер, который выделяется для сортировки индексов
myisam_sort_buffer_size = 128M
# Максимальный размер временного файла
myisam_max_sort_file_size = 10G
# Количество потоков при восстановлении таблиц
myisam_repair_threads = 1
# Память, отведенная для хранения внутренних структур данных InnoDB
innodb_additional_mem_pool_size = 16M
# Размер кэша InnoDB для буферизации табличных данных и индексов
innodb_buffer_pool_size = 256M
# Файлы для хранения InnoDB таблиц.
# В моём случае – это 4 файла по 4 гигабайта.
innodb_data_file_path = ibdata1:4G;ibdata2:4G;ibdata3:4G;ibdata4:4G
# Путь к рабочей папке для InnoDB
innodb_data_home_dir = /var/db/mysql/
# Количество потоков ввода-вывода для InnoDB
innodb_file_io_threads = 4
# Максимальное количество потоков для InnoDB
innodb_thread_concurrency = 4
# Записывать ли журнал при завершении транзакции?
innodb_flush_log_at_trx_commit = 1
# Размер буфера для записи информации файлов журналов InnoDB на диск.
innodb_log_buffer_size = 8M
# Размер каждого файла журнала в группе журналов
innodb_log_file_size = 256M
# Количество файлов журналов в группе журналов
innodb_log_files_in_group = 3
# Время простоя (в секундах), на протяжении которого транзакция InnoDB может ожидать
# блокировки прежде, чем будет произведен откат
innodb_lock_wait_timeout = 120

[mysqldump]
# Опции для утилиты mysqldump – быстрый метод и максимальный размер пакета в дампе.
quick
max_allowed_packet = 128M

[mysql]
# Автоматическое повторное хеширование не используется
no-auto-rehash

[myisamchk]
# Опции для mysqlcheck – размеры буферов (описаны выше для mysqld)
key_buffer_size = 256M
sort_buffer_size = 128M
read_buffer = 8M
write_buffer = 8M

[mysqlhotcopy]
interactive-timeout

[mysqld_safe]
# Ограничение на количество одновременно открытых файлов
open-files-limit = 8192
С первого взгляда, всё сложно. Но если сесть и разобраться один раз – ничего сложного не будет :) Если что-то не понятно – советую почитать документацию MySQL.
Правильно сконфигурированный MySQL – это весьма важно, если сервер будет обрабатывать большое количество запросов. Настройки, скорее всего, прийдётся изменить, т.к. этот конфиг я взял с одного из своих серверов, где MySQL настроен под мои задачи. В общем, стоит почитать и другие статьи про настройку MySQL, где, возможно, настройка будет описана более подробно...
После конфигурации нужно остановить MySQL сервер командой /usr/local/etc/rc.d/mysql-server stop, удалить из /var/db/mysql все файлы, кроме конфига, удалить папку test (это тестовая база данных) и заново запустить MySQL. Запуск будет долгим, т.к. при этом создаются все файлы для InnoDB (а они могут быть большими).
Удалить лишних пользователей и сменить пароль на root'а можно сейчас, из консоли. А можно и позже, через phpmyadmin. Лично я выбрал phpmyadmin :)

C MySQL разобрались, теперь будем устанавливать php :)
Идём в /usr/ports/lang/php5 (для отсталых от жизни можно и php4 :) ) и набираем любимые три слова.
Опции я выбрал такие:
                   Options for php5 5.2.12
---------------------------------------------------------------
[X] CLI        Build CLI version
[X] CGI        Build CGI version
[ ] APACHE     Build Apache module
[ ] DEBUG      Enable debug
[ ] SUHOSIN    Enable Suhosin protection system (not for jails)
[ ] MULTIBYTE  Enable zend multibyte support
[ ] IPV6       Enable ipv6 support
[X] MAILHEAD   Enable mail header patch
[ ] REDIRECT   Enable force-cgi-redirect support (CGI only)
[ ] DISCARD    Enable discard-path support (CGI only)
[X] FASTCGI    Enable fastcgi support (CGI only)
[X] PATHINFO   Enable path-info-check support (CGI only)
Далее устанавливаем ZendOptimizer. С ним будет один прикол у тех, кто использует amd64 систему.
Обязательно создаём /usr/local/etc/php.ini, можно даже пустой. Идём в /usr/ports/devel/ZendOptimizer и набираем любимые три слова.
Обязательно запоминаем (записываем) строки, которые он выдаст после установки. Можно делать make install clean > A – при этом весь вывод будет перенаправлен в файл A.
Теперь идём в /usr/local/etc и редактируем php.ini.
В моей системе он выглядит так:
[PHP]
engine=On
zend.ze1_compatibility_mode=Off
short_open_tag=On
asp_tags=Off
precision=14
y2k_compliance=On
output_buffering=4096
zlib.output_compression=Off
implicit_flush=Off
ignore_user_abort=On
unserialize_callback_func=
serialize_precision=100
allow_call_time_pass_reference=Off
safe_mode=Off
disable_functions=
disable_classes=
highlight.string=#DD0000
highlight.comment=#FF9900
highlight.keyword=#007700
highlight.bg=#FFFFFF
highlight.default=#0000BB
highlight.html=#555555
expose_php=Off
max_execution_time=300
max_input_time=300
memory_limit=32M
error_reporting=E_ALL & ~E_NOTICE
display_errors=On
display_startup_errors=Off
log_errors=On
log_errors_max_len=1024
ignore_repeated_errors=Off
ignore_repeated_source=Off
report_memleaks=On
track_errors=On
error_log=syslog
variables_order="EGPCS"
register_globals=On
register_long_arrays=On
register_argc_argv=On
auto_globals_jit=On
post_max_size=16M
magic_quotes_gpc=Off
magic_quotes_runtime=Off
magic_quotes_sybase=Off
auto_prepend_file=
auto_append_file=
default_mimetype="text/html"
doc_root=
user_dir=
enable_dl=On
cgi.fix_pathinfo=1
file_uploads=On
upload_max_filesize=12M
allow_url_fopen=On
allow_url_include=Off
from="[email protected]"
user_agent="Eugen.SU"
default_socket_timeout=20
[Pcre]
pcre.backtrack_limit=1000000000
pcre.recursion_limit=100000000
[Syslog]
define_syslog_variables=Off
[SQL]
sql.safe_mode=Off
[MySQL]
mysql.allow_persistent=On
mysql.max_persistent=-1
mysql.max_links=-1
mysql.default_socket=
mysql.default_host=
mysql.default_user=
mysql.default_password=
mysql.connect_timeout=10
mysql.trace_mode=Off
[MySQLi]
mysqli.max_links=-1
mysqli.default_port=3306
mysqli.default_socket=/tmp/mysql.sock
mysqli.default_host=
mysqli.default_user=
mysqli.default_pw=
mysqli.reconnect=Off
[Session]
session.save_handler=files
session.save_path="/tmp"
session.use_cookies=1
session.name=sessid
session.auto_start=0
session.cookie_lifetime=0
session.cookie_path=/
session.cookie_domain=
session.cookie_httponly=
session.serialize_handler=php
session.gc_probability=1
session.gc_divisor=10
session.gc_maxlifetime=86400
session.bug_compat_42=0
session.bug_compat_warn=1
session.referer_check=
session.entropy_length=0
session.entropy_file=
session.cache_limiter=nocache
session.cache_expire=180
session.use_trans_sid=0
session.hash_function=1
session.hash_bits_per_character=6
url_rewriter.tags="a=href,area=href,frame=src,input=src,form=fakeentry"
[Zend]
zend_optimizer.optimization_level=2
zend_extension_manager.optimizer="/usr/local/lib/php/20060613/Optimizer"
zend_extension_manager.optimizer_ts="/usr/local/lib/php/20060613/Optimizer_TS"
zend_extension="/usr/local/lib/php/20060613/ZendExtensionManager.so"
zend_extension_ts="/usr/local/lib/php/20060613/ZendExtensionManager_TS.so"
Прошу обратить внимание, что убраны все комментарии. Это ускоряет парсинг конфига при запуске CGI процессов.
Про параметры php в этой статье я рассказывать не буду, но оговорю две вещи:
1. Последние строки в файле взяты из данных, полученных при установке ZendOptimizer :) Для этого мы и сохраняли все данные
2. zend_optimizer.optimization_level равен двум, поскольку иначе в системе amd64 php будет «вылетать в кору».
Теперь устанавливаем модули для php. По инструкции – идём в /usr/ports/lang/php5-extensions (справедливо для php4 :) ) и набираем три слова. Выбираем все модули, кроме явно не нужных.
После установки редактируем /usr/local/etc/php/extensions.ini и удаляем лишние модули. С комментариями – та же история, что и с php.ini :) И ещё оговорюсь – никаких лишних модулей оставлять не нужно. Загружайте только то, что действительно используется. Это экономит память и время, что очень важно для нагруженного сервера.

Напоследок, установим lighttpd в качестве веб-сервера. Сервер хороший, лёгкий, а некоторые его возможности меня просто очень порадовали. К сожалению (а может и к счастью) не поддерживает динамическую конфигурацию (.htaccess), хотя это даже к лучшему – на парсинг этих файлов уходит лишнее время.
Для начала, пойдём в /tmp и создадим там папку wwwcache. Это очень пригодится для модуля mod_compress. Теперь делаем:
chflags sunlnk /tmp/wwwcache – это нужно, чтоб система не удалила эту папку при очистке /tmp
chown www:www /tmp/wwwcache – чтобы сам сервер мог записывать в эту папку.
chmod 0700 /tmp/wwwcache – без комментариев.
Теперь создаём 2 файла с указанными правами:
/var/log/httpd.access.log www:www 644
/var/log/httpd.error.log www:www 644
В /etc/rc.conf добавляем следующие строки:
lighttpd_enable="YES"				# Запускать lighttpd?
spawn_fcgi_enable="YES"				# FastCGI сервер
spawn_fcgi_app="/usr/local/bin/php-cgi"		# Путь к php
spawn_fcgi_app_args=""				# php аргументы
spawn_fcgi_pidfile="/var/run/spawn-fcgi.pid"	# PID файл
spawn_fcgi_username="www"			# php user
spawn_fcgi_groupname="www"			# php group
spawn_fcgi_chroot_dir=""			# php chroot
spawn_fcgi_bindaddr=""				# TCP использовать не будем
spawn_fcgi_bindport=""				# ...
spawn_fcgi_bindsocket="/tmp/php.sock"		# UNIX socket лучше
spawn_fcgi_bindsocket_mode="0777"		# Socket permissions
spawn_fcgi_children="200"			# Максимум одновременно обрабатываемых запросов
spawn_fcgi_max_requests="1000"	# Макс. количество запросов до перезапуска процесса
spawn_fcgi_path_env="/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin:/libexec:/usr/libexec:/usr/local/libexec" # PATH
Теперь идём в /usr/ports/www/lighttpd и устанавливаем его.
Мой конфиг:
          Options for lighttpd 1.4.26
-------------------------------------------------
[X] BZIP2      Enable Bzip2 support
[ ] CML        Enable Cache Meta Language support
[ ] FAM        Enable fam/gamin support
[ ] GDBM       Enable gdbm storage support
[ ] IPV6       Enable IPV6 support
[ ] MAGNET     Enable magnet support
[X] MEMCACHE   Enable memory caching support
[ ] MYSQL      Enable MYSQL support
[ ] OPENLDAP   Enable LDAP support
[ ] OPENSSL    Enable SSL support
[X] SPAWNFCGI  Enable spawn-fcgi utility
[ ] VALGRIND   Enable valgrind support
[ ] WEBDAV     Enable WebDAV support


 

Установили? Теперь перейдём к настройке. Но перед настройкой идём в /usr/local/www и создаём там файл ddos-ban.c – это моя программка для блокировки DDoS-ботов по User-Agent. Программу обязательно сохраняем в кодировке windows-1251 (для перекодировки из KOI8 можно использовать iconv). Как это работает я расскажу чуть позже, когда буду приводить пример конфига.
Текст программы:
#include <stdio.h>
#include <stdlib.h>

int main()
{
  if (!getenv("REMOTE_ADDR")) {
    printf("This is CGI-only program.\n");
    exit(1);
  }
  printf("Content-Type: text/html; charset=windows-1251\r\n\r\n\
<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n\
<html xmlns=\"http://www.w3.org/1999/xhtml\">\n\
<head>\n\
<meta http-equiv=\"content-type\" content=\"text/html; charset=windows-1251\"/>\n\
<link rel=\"stylesheet\" href=\"/errors_style.css\"/>\n\
<link rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/favicon.ico\"/>\n\
<title>Eugen Server - ошибка 403 - вы забанены</title>\n\
<style type=\"text/css\">\n\
body {\n\
    background-color: #0D0D0D;\n\
    font-family: Tahoma;\n\
    font-size: 14px;\n\
    color: #FFFF9B;\n\
}\n\
a:hover {\n\
    color: #FFCC00;\n\
}\n\
a {\n\
    color: #FFFF9B;\n\
}\n\
input, textarea, select {\n\
    color: #FFFF9B;\n\
    background: #343434;\n\
    border: none;\n\
}\n\
</style>\n\
</head>\n\
<body>\n\
<center>\n\
<br />\n\
<br />\n\
<h1>Ошибка 403</h1>\n\
<h2>Вы забанены Anti-DDoS'ом</h2>\n\
<br />\n\
<div align=\"justify\" style=\"width:500px;overflow:auto\">\n\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\n\
Ваш IP %s заблокирован Anti-DDoS'ом, т.к. в строке User-Agent, посланной вашим браузером найдены соответствия с базой Anti-DDoS.\n\
По вопросам обращайтесь в ICQ 671915. Доступ будет открыт через 1 час после блокировки.\n\
Дальнейшие попытки подключений на 80-й порт будут безуспешными.\n\
</div>\n\
<br />\n\
<strong>&copy; Eugen &amp; gibson</strong>\n\
</center>\n\
</body>\n\
</html>\n", getenv("REMOTE_ADDR"));
  execl("/usr/sbin/daemon","daemon","-f","/sbin/pfctl","-t","ddos","-T","add",getenv("REMOTE_ADDR"),0);
}

Компилируем и делаем программе SUID – она должна запускаться от root'а:
cc ddos-ban.c
strip a.out
mv a.out ddos-ban
chmod a-rw ddos-ban
chmod a+s ddos-ban
Для желающих – rm ddos-ban.c – исходник можно не держать здесь :)
Теперь приступаем к конфигурированию веб-сервера. Редактируем файл /usr/local/etc/lighttpd.conf:
## Загружаемые модули
# Для работы необходимы mod_access и mod_accesslog. Остальное по желанию
server.modules              = (
                                "mod_rewrite",
                                "mod_redirect",
                                "mod_alias",
                                "mod_access",
#                               "mod_trigger_b4_dl",
#                               "mod_auth",
#                               "mod_status",
#                               "mod_setenv",
                                "mod_fastcgi",
#                               "mod_proxy",
#                               "mod_simple_vhost",
#                               "mod_evhost",
#                               "mod_userdir",
                                "mod_cgi",
                                "mod_evasive",
                                "mod_compress",
#                               "mod_ssi",
#                               "mod_usertrack",
#                               "mod_expire",
#                               "mod_secdownload",
#                               "mod_rrdtool",
                                "mod_accesslog" )

## document-root по умолчанию
server.document-root        = "/usr/local/www/data/"

## Лог ошибок
server.errorlog             = "/var/log/httpd.error.log"

# Индексовые файлы
index-file.names            = ( "index.php", "index.html", "index.htm" )

## Обработчик событий
server.event-handler = "freebsd-kqueue"

# Имя сервера (по умолчанию)
server.name = "Eugen.SU"

# Некоторые системные настройки
server.max-keep-alive-requests = 15
server.max-keep-alive-idle = 5
server.max-read-idle = 60
server.max-write-idle = 300
server.max-fds = 2048 # Max connections

# Ограничение количества одновременных подключений с одного IP
evasive.max-conns-per-ip = 10

# Типы файлов
mimetype.assign             = (
  ".pdf"          =>      "application/pdf",
  ".sig"          =>      "application/pgp-signature",
  ".spl"          =>      "application/futuresplash",
  ".class"        =>      "application/octet-stream",
  ".ps"           =>      "application/postscript",
  ".torrent"      =>      "application/x-bittorrent",
  ".dvi"          =>      "application/x-dvi",
  ".gz"           =>      "application/x-gzip",
  ".pac"          =>      "application/x-ns-proxy-autoconfig",
  ".rar"          =>      "application/rar",
  ".swf"          =>      "application/x-shockwave-flash",
  ".tar.gz"       =>      "application/x-tgz",
  ".tgz"          =>      "application/x-tgz",
  ".tar"          =>      "application/x-tar",
  ".zip"          =>      "application/zip",
  ".mp3"          =>      "audio/mpeg",
  ".m3u"          =>      "audio/x-mpegurl",
  ".wma"          =>      "audio/x-ms-wma",
  ".wax"          =>      "audio/x-ms-wax",
  ".ogg"          =>      "application/ogg",
  ".wav"          =>      "audio/x-wav",
  ".gif"          =>      "image/gif",
  ".jar"          =>      "application/x-java-archive",
  ".jpg"          =>      "image/jpeg",
  ".jpeg"         =>      "image/jpeg",
  ".png"          =>      "image/png",
  ".xbm"          =>      "image/x-xbitmap",
  ".xpm"          =>      "image/x-xpixmap",
  ".xwd"          =>      "image/x-xwindowdump",
  ".css"          =>      "text/css",
  ".html"         =>      "text/html",
  ".htm"          =>      "text/html",
  ".js"           =>      "text/javascript",
  ".asc"          =>      "text/plain",
  ".c"            =>      "text/plain",
  ".cpp"          =>      "text/plain",
  ".log"          =>      "text/plain",
  ".conf"         =>      "text/plain",
  ".text"         =>      "text/plain",
  ".txt"          =>      "text/plain",
  ".dtd"          =>      "text/xml",
  ".xml"          =>      "text/xml",
  ".mpeg"         =>      "video/mpeg",
  ".mpg"          =>      "video/mpeg",
  ".mov"          =>      "video/quicktime",
  ".qt"           =>      "video/quicktime",
  ".avi"          =>      "video/x-msvideo",
  ".asf"          =>      "video/x-ms-asf",
  ".asx"          =>      "video/x-ms-asf",
  ".wmv"          =>      "video/x-ms-wmv",
  ".bz2"          =>      "application/x-bzip",
  ".tbz"          =>      "application/x-bzip-compressed-tar",
  ".tar.bz2"      =>      "application/x-bzip-compressed-tar",
  # default mime type
  ""              =>      "application/octet-stream",
 )

# Заголовок Server:
server.tag                 = "Eugen.SU"

#### Лог доступа
accesslog.filename          = "/var/log/httpd.access.log"

# Расширения НЕ статических файлов
static-file.exclude-extensions = ( ".php", ".phtml", ".phps" )

## Порт
server.port                = 80

## Адрес
server.bind                = "0.0.0.0"

## PID-файл
server.pid-file            = "/var/run/lighttpd.pid"

# chroot()
#server.chroot              = "/"

## Пользователь (все хосты должны иметь этого владельца или UID'ы должны совпадать)
server.username            = "www"

## Группа
server.groupname           = "www"

## Настройки mod_compress для сжатия статических файлов
compress.allowed-encodings = ( "bzip2", "gzip", "deflate" )
compress.filetype          = ( "text/plain", "text/html" )
compress.cache-dir         = "/tmp/wwwcache/"

#### fastcgi
## for PHP don't forget to set cgi.fix_pathinfo = 1 in the php.ini
## слушаем совет и не забываем это исправить...
## отдельные два сервера для php и phps
fastcgi.map-extensions = ( ".phtml" => ".php" )
fastcgi.server             = ( ".phps" =>
                               ( "phps" =>
                                 (
                                   "socket" => "/tmp/phps.sock",
                                   "bin-path" => "/usr/local/bin/php-cgi -s",
				   "max-procs" => 10,
				   "bin-environment" => ( 
                                     "PHP_FCGI_CHILDREN" => "10",
                                     "PHP_FCGI_MAX_REQUESTS" => "1000" 
                                   )
                                 )
                               ),
			       ".php" =>
			       ( "php" =>
			         (
				   "socket" => "/tmp/php.sock"
				 )
			       )
                            )

#### ssi
#ssi.extension              = ( ".shtml" )

# Простой Anti-DDoS
# Блокирует IP адрес файрволлом при совпадении User-Agent'а
# Вот для этого и была нужна моя программа на C...
$HTTP["useragent"] =~ "(vaginamook|2003100|ODI3|Hotbar|4.75|DigExt|20030718|Q312461|H010818|inktomi|googlebawt|FAST-WebCrawler|Microsoft-WebDAV-MiniRedir|FunWebProducts|looksmart|avastye)" {
  cgi.assign = ( "" => "/usr/local/www/ddos-ban" )
}

# Настройки виртуального хостинга
# Можно использовать поставляемый модуль, но мы люди продвинутые и пользуемся RegExp :)

# Default
$HTTP["url"] =~ "^/cgi-bin/" {
  cgi.assign = ( "" => "" )
}
$HTTP["host"] != "admin.eugen.su" { # Задайте это! Путь к phpMyAdmin.
  url.redirect = ( "^/myadmin/(.*)$" => "http://admin.eugen.su/myadmin/$1",
                   "^/myadmin$" => "http://admin.eugen.su/myadmin/" )
}
dir-listing.activate       = "disable" # Отключаем листинг папок без индексового файла
server.errorfile-prefix    = "/usr/local/www/errors/" # Путь к папке с файлами ошибок
# 401.html 403.html ...

# Убираем www. из Host.
# Можно наоборот – добавлять. Или вообще не трогать. Но тогда это нужно предусмотреть.
$HTTP["host"] =~ "^www\.(.*)$" {
  url.redirect = ( "^/(.*)" => "http://%1/$1" )
}

# Приведу несколько записей для виртуальных хостов, как пример.

# eugen.su
$HTTP["host"] == "tem.dp.ua" {
  server.name = "tem.dp.ua"
  # Перенаправление
  url.redirect = ( "^/ub/(.*)" => "http://eugen.su/ru/userbar/$1",
                   "^/ub$" => "http://eugen.su/ru/userbar/",
		   "^/forum/(.*)" => "http://forum.eugen.su/$1",
		   "^/forum$" => "http://forum.eugen.su/",
		   "^/releases/(.*)" => "http://releases.eugen.su/$1",
		   "^/releases$" => "http://releases.eugen.su/",
		   "^/$" => "http://live.eugen.su/",
		   "^/index.php$" => "http://live.eugen.su/" )
}
$HTTP["host"] =~ "^(tools\.tem\.dp\.ua|tools\.gibs0n\.name|webtools\.xakepok\.org)$" {
  url.redirect = ( "^/(.*)" => "http://eugen.su/$1" )
}
$HTTP["host"] =~ "eugen\.su$" { # Можно использовать регулярные выражения (Perl)
  alias.url += ( "/cgi-bin/" => "/home/eugen/cgi-bin/" )
  server.errorfile-prefix = "/home/eugen/errors/"
}
$HTTP["host"] == "eugen.su" {
  server.name = "eugen.su"
  server.document-root = "/home/eugen/eugen.su" # document-root
  $HTTP["url"] =~ "^(/base/|/fonts/|/temp/|/userbar/data/|/userbar/fonts/|/scripts/geoipcity\.dat)" {
    url.access-deny = ( "" ) # Запрет доступа
  }
  # Реврайт ссылок
  url.rewrite-once = ( "^/([a-zA-Z]{2})/tool/([a-zA-Z0-9]+)" => "/index.php?lang=$1&tool=$2",
                       "^/tool/([a-zA-Z0-9]+)" => "/index.php?tool=$1",
		       "^/([a-zA-Z]{2})[/]?$" => "/index.php?lang=$1",
		       "^/actions\.js$" => "/actions.php",
		       "^/style\.css$" => "/style.php",
		       "^/download/([^/]+)/([0-9a-fA-F]{32})$" => "/scripts/download.php?uiq=$2&type=$1",
		       "^/userbar/im(.*)_(.*)_(.*)_(.*)_(.*)_(.*)\.png$" => "/userbar/img.php?x=$1&sz=$2&bg=$3&fn=$4&cl=$5&text=$6",
		       "^/([a-zA-Z]{2})/userbar[/]?$" => "/userbar/index.php?lang=$1" )
}

# Final. По умолчанию все /cgi-bin/ метим в папку "по умолчанию"
alias.url += ( "/cgi-bin/" => "/usr/local/www/cgi-bin/" )
Естественно, здесь не все наши virtual-host'ы. :)
Теперь можно перезагрузить сервер. Если всё сделано правильно и вы не запутались с правами/владельцами/UID'ами – то всё должно заработать.
Лично у меня получилось настроить такую конфигурацию с первого раза, когда я делал домашний сервер :) Но с первого раза получается не у всех… Так что, огорчаться не стоит, даже если ничего не получится. Внимательно проверьте права на файлы, логи ошибок и попытайтесь исправить ситуацию. В самом крайнем случае – мой ICQ 671915 :)
С вами был Eugen. Удачи вам в ваших разработках :)