Обновить

Руководство по программе восстановления паролей «John the Ripper»

Как-то у меня появилась необходимость подобрать пароль к пользовательской записи на linux системе, имя пользователя я знал, а вот пароль надо было восстановить старый. Хоть я и имел доступ с правами root'а и мог просто изменить пароль на данного пользователя, но мне стало интересно как можно этот пароль восстановить. После поисков и Интернете и на различных форумах я наткнулся на программку John the Ripper и решил воспользоваться ей.


Итак, согласно википедии:

John The Ripper (JTR)— свободная программа, предназначенная для восстановления паролей по их хешам. Основное назначение программы — аудит слабых паролей в UNIX системах. Программа также может выполнять аудит NTLM хешей, Kerberos, и др. Существуют реализации под различные ОС. Весьма популярна из-за поддержки большого количества хешей, автораспознавания хеша и настраиваемого взломщика. Также поддерживает модули, предоставляющие поддержку MD4 хешей, LDAP и MySQL паролей.


Однако, прежде чем начать разбираться с самим JTR, давайте остановимся на самих паролях в *nix системах.



Немного о паролях.



В Linux пароли шифруются в алгоритме «DES», также есть такая фишка как «salt», это 2е любые буквы, которые являются ключом к паролю, другими словами, с помощью «salt» и шифруется пароль. Подробнее об этом написано в статье «пароли в UNIXе».
Пароли в большинстве случаев хранятся в следующих файлах:



/etc/passwd
/etc/shadow


Хотя могут и лежать в каком-нибудь /etc/shadow.old или /etc/passwd.tmp. Сделав листинг директории /etc/, сразу можно все понять. Просто иногда хитрые админы меняют названия файлов :). passwd файл, это как раз то что нам надо, но в большинстве случаев пароли хранятся в shadow (в тени), в этом случае в passwd видим следующую ситуацию:



root:*:0:0:System Administrator:/root:/bin/csh
rfindd:*:66:1:Rfind Daemon and Fsdump:/var/rfindd:/bin/sh


Внимание! Это на самом деле fake, и никаких паролей здесь нет! А вот если видим такую ситуацию:



root:9IDv/CqdFuqWo:0:0:System Administrator:/root:/bin/csh
john:653MWdpUGN3BM:66:1:John Nikolsen, 2-nd west:/home/john:/bin/sh


То можно заметить, что данный пример более похож на правду. Давайте разберемся на примере с пользователем «john»:



john:653MWdpUGN3BM:66:1:John Nikolsen, 2-nd west:/home/john:/bin/sh
  1. john — имя пользователя.
  2. 653MWdpUGN3BM — пароль зашифрованный в DES.
  3. 66:1 — номер пользователя в системе: номер рабочей группы (у root всегда 0:0).
  4. John Nikolsen, 2-nd west — информация о пользователе (обычно Имя, Фамилия…).
  5. /home/john — Домашний каталог.
  6. /bin/csh — shell.

Во Free BSD пароли хранятся в файле /etc/master.passwd, а в остальном технология такая же как и в LINUX, хотя используется более стойкий алгоритм шифрования MD5. В Open BSD, для шифрования используется алгоритм Blowfish.


Ну а теперь о самом JTR и как им пользоваться.



Режимы.



JTR может работать в следующих режимах:

  1. Brute force
  2. Перебор по словарю (wordlist mode)
  3. Incremental mode
  4. Single mode (одиночный режим)


Рассмотрим каждый из режимов подробно.



Brute force:

john passwd

(Я в дальнейшем буду использовать passwd как имя файла с паролями, указанной выше структуры)
Данный способ является самым простым, и не очень эффективным, так как JTR начинает перебирать все символы от фонаря, что конечно очень долго… хотя многое зависит от мощности вашего компьютера и от сложности пароля.

Перебор по словарю (wordlist mode):

john -w:wordlist passwd

где wordfile — имя файла со словарем. Также можно и так:

john -w:wordlist -rules passwd

-rules, включает правила синтаксиса для словаря. С вариантом по словарю, пароли подбираются на много быстрее чем в варианте с «Brute force».

Incremental mode:

john -i passwd

Авторы JTR говорят, что это самый сильный способ перебора, 95 символов. А вот таким способом:

john -i:alpha passwd

JTR перепробует 26 символов, от «a», до «zzzzzzzz». Если с wordlist не получилось, советую запускать данный способ.

Single mode (одиночный режим):

john -single passwd

Рекомендуется начать перебирать пароль с данной опцией. Намного быстрее чем wordlist mode, но рассчитан на хиленькие пароли. При переборе нескольких файлов с данной опцией, возможно подобрать пароли быстрее чем просто 1 passwd файл

Опции.



Теперь давайте рассмотрим несколько полезных опции для JTR. Опции можно запускать в любых режимах:-w, -i, -single.

Перебор для конкретного пользователя:

john -w:wordlist -rules -users:0 passwd

Перебирает пароль к конкретному пользователю (в нашем случае root). Что бы указать пользователя, надо после ":" указать UID (номер пользователя в системе).

Смотрим подобранные пароли.

john -show passwd

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

Разбиваем файл на куски.

john -w:wordlist -salts:5 passwd
john -w:wordlist -salts:-5 passwd


Иногда бывает полезно (если есть 2 компьютера). Число «5» это количество «salts» (паролей). JTR будет отчитывать с самого верху. В первом примере (на 1ом компе), берется первые 5 пользователей, во втором примере "-5" (на 2ом компе), перебираются все, кроме 5и первых.

Люблю shells.

john -w:wordlist -rules -shells:sh,csh,tcsh,bash passwd

В данном случае, будут перебираться только те пользователи, которые имеют указанные в вышеприведенном примере shell'ы(оболочки).

Несколько passwd файлов.
Если у вас есть несколько файлов с паролями в DES, то их можно всех вместе поставить на расшифровку данным способом:

john -single passwd passwd1 passwd2

Количество файлов по-моему не ограничено :). Способ с несколькими файлами, работает на всех режимах и опциях, просто укажите имена файлов в конце (как в примере).
Unshadow files.

unshadow passwd shadow

Если у вас пароли в shadow, то данной командой, их можно восстановить (требует shadow файл).

Ну вот вроде и все. В данной статье были описаны практически все самые полезные примеры, которым можно найти применение. За более подробной информацией можно обратиться к документации прилагаемой к JTR.

Ubuntu+Postfix как антиспам шлюз для Exchange2003

Ubuntu 8.10 (Postfix) в качестве почтового шлюза для Exchange+antispam
Как все начиналось или Зе хистори оф батл! ;)
В жизни каждого системного администратора наступает такой момент, когда количество спама, переходит все мыслимые и не мыслимые пределы. Когда начальство при вашем появлении в кабинете, начинает нудить — что устало начинать свой рабочий день с разгребания спама, вместо того чтобы совершать подвиги — типа разгона облаков или мативирования подчиненных на новые трудовые свершения — на ниве капиталистического труда.)
При этом денег на покупку лицензий антиспама выделять не хочет, мотивируя это тем, что: ТЫ и так умный, вот и придумай что-нибудь, да и кризис на дворе. А лицензирование большинства современных систем фильтрации нежелательной корреспонденции, основывается на на принципе:1 почтовый ящик =1 лицензия, а если количество ящиков больше 100, а если 200 и более как у меня, то сумма получается вполне приличной.

Схема работы системы будет следующей
Канал интернета ->шлюз ubuntu->сервер win2k3(exchange 2003, AD, DNS,WINS,ISA)ну и прочие прелести от мелко-мягких.
Установку Ubuntu server 8.10 оставлю за рамками статьи
Схема работы шлюза подробнее:
1) Ubuntu принимает, входящие письма от спамеров клиентов, проверяет IP отправителя, на наличие в списках негодяев (спам листах) – если IP присутствует, соединение разрывается, если отсутствует – считаем, отправителю повезло, переходим к пункту 2
2) Поверяем как представится сервер отправитель, если в helo содержит записи типа localhost или IP адрес отправитель идет лесом, а если сервер представился правильно проводим проверку по реверс ДНС запросу – если ответ не соответствует helo при начале соединения – отправитель посылается в пешее эротическое путешествие. Вот такая вот викторина получается, ах да, совсем забыл — если во время всей этой игры в кошки-мышки сервер отправитель допустил более 1й ошибки- то что? Правильно — соединение разрывается! Но если и тут все хорошо- то отправитель признается, самым хитрым спамером и я как Якубович предлагаю сыграть со мной в супер-игру, от которой нельзя отказаться с суперпризом будет велосипед, который мы только что изобрели, т.е. к переходим пункту 3.
3) .(СУПЕР игра и супер приз велосипед!) проверяем наличие почтовых ящиков получателей в Exhange, если получатель отсутствует в списке, то отправитель уходит несолоно хлебавши, со следами пинка на пятой точке и клеймом спамера-неудачника, соединение разрывается, не доходя то закачки тела сообщения ( экономия на трафике на лицо) — если присутствует то отправитель признается абсолютным чемпионом и письмо передается на exchange, который тихо сидит backend и его не видно, снаружи.

Переходим непосредственно к первой части нашего эротического боевика.
Предполагаю что система, уже установлена и обновления поставлены, настройки сделаны.
Заходим в качестве супер-пользователя
#Sudo su
Вводим пароль администратора
Ставим postfix

apt-get install postfix

Ну и далее запускаем текстовый редактор.
Вместо «сервер конторы.ru» подставляем реальные данные ВАШЕГО домена.
Начнем:
nano /etc/postfix/main.cf

нас интересуют следующие строки
myhostname = mail.сервер конторы.ru # полное имя хоста
mydomain = сервер конторы.ru # имя домена от имени которого приходит почта

mydestination = mail. сервер конторы.ru, localhost. сервер конторы.ru,, localhost
mynetworks = 127.0.0.0/8, 192.168.200.20 [::ffff:127.0.0.0]/104 [::1]/128 # список доверенных сетей из которых разрешена неавторизированная отправка, сервер win2k3 имеет адрес 192.168.200.20( это IP не я придумал, пришел на новую работу так все уже было настроено, ну а я систему ломать не стал), этой директивой мы разрешаем отправку почты наружу через postfix только непосредственно серверу с Exchange на борту, даже клиенты локальной сети должны отправлять почту только через Exhcange.

virtual_mailbox_domains = сервер конторы.ru

transport_maps = hash:/etc/postfix/transport
virtual_transport = hash:/etc/postfix/transport
Эти пункты отвечают за то, куда будет передаваться принятая почта.

В конце конфига добавляем

bounce_queue_lifetime = 1d
maximal_queue_lifetime = 1d
minimal_backoff_time = 180s
maximal_backoff_time = 12h

strict_rfc821_envelopes = yes
disable_vrfy_command = yes
smtpd_delay_reject = yes
smtpd_helo_required = yes
anvil_rate_time_unit = 60s
smtp_always_send_ehlo = yes
smtpd_hard_error_limit = 1
smtpd_recipient_limit = 1
smtpd_sasl_security_options = noanonymous

anvil_rate_time_unit = 60s
smtpd_client_connection_count_limit = 5
smtpd_client_connection_rate_limit = 6
smtpd_client_message_rate_limit = 6
smtpd_client_recipient_rate_limit = 1

smtpd_client_restrictions =
reject_unauth_pipelining,
permit_sasl_authenticated,
permit_mynetworks,
check_helo_access regexp:/etc/postfix/helo,
reject_unknown_client_hostname,
check_client_access regexp:/etc/postfix/dul_checks,
permit

smtpd_helo_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
reject_unknown_client,
# проверяем IP на присутствие в спам листах
reject_rbl_client cbl.abuseat.org,
reject_rbl_client sbl-xbl.spamhaus.org,
reject_rbl_client sbl.spamhaus.org,
reject_rbl_client dnsbl.njabl.org,
check_helo_access regexp:/etc/postfix/helo,
reject_invalid_helo_hostname,
reject_non_fqdn_helo_hostname,
reject_unknown_helo_hostname,
check_sender_access hash:/etc/postfix/access,
check_recipient_access hash:/etc/postfix/recipients,
permit

smtpd_sender_restrictions =
permit_sasl_authenticated,
permit_mynetworks,
reject_non_fqdn_sender,
reject_unknown_sender_domain,
permit

smtpd_recipient_restrictions =
reject_unauth_pipelining,
permit_mynetworks,
permit_sasl_authenticated,
reject_unauth_destination,
reject_invalid_hostname,
reject_non_fqdn_recipient,
reject_unknown_recipient_domain,
permit

сохраняем конфиг и выходим в консоль.
Подробно на всех пунктах останавливаться не буду их назначение легко найти в гугле,
Все содержимое можно просто за копипастить как есть. Остановлюсь на тех, которые нужно редактировать

Нам необходимо создать следующие файлы:
nano /etc/postfix/transport # отвечает за то, куда нужно передавать принятую почту, содержт:

серверконторы.ru smtp:[192.168.200.20]

где: smtp-серер получатель, IP в [ ] говорит о том, что не нужно проверять его ДНС зону, а передавать почту на этот адрес как есть, а там разберутся!

nano /etc/postfix/recipients
Это наш список получателей, который экспортируется из Exchange
Содержит записи вида:

user@серверконторы.ru OK

где: user-имя пользователя в AD c mailbox, серверконторы.ru-домен получателя, OK –директива, как поступить с письмом, в нашем случае – пропустить.
Сведения о получателях будут содержаться именно в этом файле, можно конечно сделать опрос exchange в реальном времени, но тут появляется ненужная нагрузка на контроллер домена, а оно вам надо?! Причем если одновременно долбиться несколько сот тыс. отправителей все эти запросы посыпятся и на AD. Это и потерянное время на ожидание ответа от винды и нагрузка на процессор уже 2х серверов. По моему глубокому мнению, проще запустить скрит через, который в заданное время, выдернет список получателей, и перебросит их в файл, как часто его запускать — решать вам, у меня он запускается 2 раза в день 12 и 15 часов — больше не требуется. А ночью, мы, слава богу, не работаем.

nano /etc/postfix/helo
содержит:
/([0-9]{1,3}(\.|-)){3}[0-9]{1,3}/i REJECT IP-able helo SPAM!
/localhost/i REJECT you are SPAM!
/ сервер конторы.ru/i REJECT you are not in my local networks-SPAM!

В этом файле используются регулярные выражения для фильтрации команды helo
Если отправитель представился IP адресом-то это нарушение и почту от него не принимают, тоже самое со вторым пунктом localhost –нормальный сервер не может представляться так, либо там админ криворукий (пускай настраивает нормально) либо на другом конце просто сильно запаршивевший компьютер, который является частью ботнета, рассылаешего спам. Ну и последнее если отправитель представляется — серверконторы.ru, ну не может на другом конце быть мой сервер — где же тогда я нахожусь, это на 100% спамер можно посылать смело. Команда REJECT указывает на то, что сделать при выполнении условий.
Да ксати после идет ответ сервера, с указанием причины отказа в принятии почты, туда можно вписать что-то обидное ;) и это сообщение увидят на том конце. Но это дело воспитания конкретного индивидуума.

nano /etc/postfix/access
содержание данного файла схоже с предыдущим, но тут уже используются явные указатели, этим файлом можно, например, блокировать отдельные ящики пользователей.
bad_user@сервер конторы.ru REJECT user is note available
сервер конторы.ru REJECT you are not in my local networks
IP вашего сервера REJECT you are not in my local networks
localhost REJECT you are SPAM!

nano /etc/postfix/dul_checks
проверка на наличии в различных сетях
содержит регулярные выражения различных сетей и как следствие признаков что это не почтовые сервера а зараженные клиентские компьютеры, а если там действительно работают нормальные сервера, то почту можно отправлять и через релей провайдера или на худой конец обратиться в тех поддержку чтобы прописали обратную DNS зону.

/([0-9]*-){3}[0-9]*(\..*){2,}/i REJECT 553 SPAM_ip-add-rr-ess_networks
/([0-9]*\.){4}(.*\.){3,}.*/i REJECT 553 SPAM_ip-add-rr-ess_networks
/.*\.broadband\.hu/i REJECT 553 SPAM_broadband-hu
/client.*\..*\..*/i REJECT 553 SPAM_CLIENT
/cable.*\..*\..*/i REJECT 553 SPAM_CABLE
/pool.*\..*\..*/i REJECT 553 SPAM_POOL
/dial.*\..*\..*/i REJECT 553 SPAM_DIAL
/ppp.*\..*\..*/i REJECT 553 SPAM_PPP
/dslam.*\..*\..*/i REJECT 553 SPAM_DSLAM
/dhcp.*\..*\..*/i REJECT 553 SPAM_DHCP
/[\.-]dsl.*\..*\..*/i REJECT 553 SPAM_DSL
/[ax]dsl.*\..*\..*/i REJECT 553 SPAM_XDSL
/.*([0-9]*\.){4}cableonline\.com\.mx/i REJECT 553 SPAM_IP-cableonline-com-mx
/.*\.([0-9]*\.){4}ip\.holtonks\.net/i REJECT 553 SPAM_ip-holtonks-net
/([0-9]*-){3}[0-9]*\.fibertel\.com\.ar/i REJECT 553 SPAM_IP-fibertel-com-ar
/.*[0-9]*-[0-9]*\.fibertel\.com\.ar/i REJECT 553 SPAM_IP-fibertel-com-ar
/[0-9]*\.user\.veloxzone\.com\.br/i REJECT 553 SPAM_user-veloxzone-com-br
/[0-9]*\.customer\.alfanett\.no/i REJECT 553 SPAM_customer-alfanett-no
/.*([0-9]*-){3}[0-9]*\.telecom\.net\.ar/i REJECT 553 SPAM_host-telecom-net-ar
/.*(-[0-9]*){2}\.telpol\.net\.pl/i REJECT 553 SPAM_host-telpol-net-pl
/(.*\.){2}maxonline\.com\.sg/i REJECT 553 SPAM_host-maxonline-com-sg
/(.*-){2}.*\.fairgamemail\.us/i REJECT 553 SPAM_host-fairgamemail-us
/[0-9]*[0-9]*-\.wispnet\.net/i REJECT 553 SPAM_host-wispnet-net
/.*-.*(\..*){2}\.ne\.jp/i REJECT 553 SPAM_host-ne-jp
/[0-9]*\..*\.ne\.jp/i REJECT 553 SPAM_h09t-ne-jp
/(.*\.){3}ad\.jp/i REJECT 553 SPAM_host-ad-jp
/(.*\.){4}revip\.asianet\.co\.th/i REJECT 553 SPAM_revip-asianet-co-th
/[0-9]*\..*\.virtua\.com\.br/i REJECT 553 SPAM_host-virtua-com-br
/([0-9]*-){3}[0-9]*\.exatt\.net/i REJECT 553 SPAM_host-exatt-net
/([0-9]*\.){4}ip\.alltel\.net/i REJECT 553 SPAM_host-ip-alltel-net
/[0-9]{6,}\.chello\.../i REJECT 553 SPAM_host-chello
/.*[0-9]*\..*\.chello\.../i REJECT 553 SPAM_host-chello-xx
/.*\..*\.t-dialin\.net/i REJECT 553 SPAM_t-dialin-net
/.*\..*\.t-ipconnect\.de/i REJECT 553 SPAM_t-ipconnect-de
/([0-9]*-){2,3}[0-9]*\..*\.cgocable\.net/i REJECT 553 SPAM_host-cgocable-net
/.*\..*\.shawcable\.net/i REJECT 553 SPAM_host-shawcable-net
/p[0-9]*\.mp[0-9]*\.aaanet\.ru/i REJECT 553 SPAM_aaa_modem_pool
/([0-9]*-){2}[0-9]*\.ip\.adsl\.hu/i REJECT 553 SPAM_ip-adsl-hu
/([0-9]{1,3}\.){2}broadband4\.iol\.cz/i REJECT 553 SPAM_broadband-iol-cz

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

postmap /etc/postfix/transport
postmap /etc/postfix/recipients
postmap /etc/postfix/helo
postmap /etc/postfix/access
postmap /etc/postfix/dul_checks

вот пожалуй и все с ubuntu

Чась вторая: Настройка Win2k3

По моему глубокому мнению, не имеет смысла пускать сервер с передовой, в тыл нашей обороны, по этой же причине мы не будем давать лазить в системе и опрашивать ее, мы разрешим ей видеть только то, что мы сами разрешили.
Как это можно реализовать на практике?! Postfix будет видеть только тот список получателей который мы ему сами дадим -уже готовый!
В то время когда я только собирался все это безобразие настраивать-у меня был скрипт, на перле, который опрашивает AD, но с ним что-то не заладилось, во-первых там была какае-то шляпа с ключами, да и пересобирать openLDAP не очень хотелось.
Я пошел иным путем, на самом сервере win 2003 через планировщик заданий мы будем запускать скрипт который будет сбрасывать нам список пользователей в файл recipients.

Схема работы системы Win:
В AD заводится пользователь с минимальными правами, без почтового ящика, с правом доступа, лишь в одну папку. Назовем его postfix с паролем 1234567.
Через планировщик заданий создается новое задание с частотой выполнения нужной вам.
На просторах сети был найден скрипт, который не полностью соответствовал нашим задачам, я его слегка допилил и теперь он делает на 100% то что нужно.
Сам скрипт написан на VBS, назовем его mail.vbs-такого содержания:

Option Explicit

Dim StartTime,EndTime: StartTime = Now ' For seeing how long the script takes to run
Dim objShell
Dim objFSO
Const ScriptVersion = «1.01»
Set objShell = WScript.CreateObject(«WScript.Shell»)
Set objFSO = CreateObject(«Scripting.FileSystemObject»)
Wscript.Echo «StartTime = » & StartTime
' ***************************************************************** '
Const ForReading = 1, ForWriting = 2, ForAppending = 8
Dim objRootDSE
Dim objDomain
Dim objContainer
Dim objOrganizationalUnit
Dim strOutputFileName, objOutputFileName, GarbageRC
Dim intUserObjectCountAll, intUserObjectCountSelected

strOutputFileName = «c:\путь\ к расшаренной папке\ для пользователя postfix\recipients»

Set objOutputFileName = objFSO.OpenTextFile(strOutputFileName, ForWriting, True)
intUserObjectCountAll = 0
intUserObjectCountSelected = 0

Set objRootDSE = GetObject(«LDAP://RootDSE»)
Set objDomain = GetObject(«LDAP://» & objRootDSE.Get(«DefaultNamingContext»))

Call Sub_EnumOUs(objDomain.ADsPath)

Sub Sub_EnumOUs(sADsPath)
Set objContainer = GetObject(sADsPath)
objContainer.Filter = Array(«OrganizationalUnit»)
For Each objOrganizationalUnit in objContainer
WScript.Echo «Checking OU: » & objOrganizationalUnit.ADsPath
Wscript.Echo " User Object Count: " & intUserObjectCountAll
Sub_EnumUsers(objOrganizationalUnit.ADsPath)
Sub_EnumOUs(objOrganizationalUnit.ADsPath)
Next
End Sub

Sub Sub_EnumUsers(sADsPath)
Dim objADobject
Set objContainer = GetObject(sADsPath)
objContainer.Filter = Array(«User»)
For Each objADobject in objContainer
If objADobject.Class = «user» Then
intUserObjectCountAll = intUserObjectCountAll + 1
If objADobject.Mail <> "" Then
objOutputFileName.Writeline( objADobject.Mail & " OK" ) ' *** "[TAB]OK"- разделение с помощью табуляции **** '
intUserObjectCountSelected = intUserObjectCountSelected + 1
End If
End If
Next
End Sub

objOutputFileName.Close

' ***************************************************************** '

Чтобы создать задание в планировщике, в строку инициализации нашего скрипта пишем:
c:\windows\system32\cscript.exe c:\путь к папке со скриптом\mail.vbs
ВСЕ это одной строкой!
Ну и выставляем время, когда он будет выполнятся, и как часто.
Далее создаем директорию и открываем в нее доступ по сети пользователю postfix.

Часть треться –заключительная.
Теперь задача, чтобы наш linux сервер мог увидеть содержимое папки с файлом recipirnts
Я предпочел монтировать нашаренную директорию при старте системы, по этому, запихал команду в файл монтирования /etc/rc.local.
Также необходимо создать в директории media директорию win. Почему именно win- чтобы через несколько месяцев, можно было понять, что это такое и откуда оно взялось.
Сначала ставим пакет smbfs -без него наша директория не примантируется.
Sudo apt-get install smbfs
Sudo nano /etc/rc.local
И перед строкой exit0 вписываем команду монтирования шары:
mount -t smbfs //192.168.200.20/antispam /media/win/ -o iocharset=utf8,user=postfix,pass=1234567

где: 192.168.200.20-ip сервера с exchange.
antispam-директория в которой лежит файл recipients.
/media/win/-куда должна примантироваться виндовая шара.
iocharset=utf8-кодировака, если случайно в ней появятся папки с русскими названиями то они будут читаемы, а не ???????????, да и хуже от этого не будет, в общем делаем сразу как надо и спим спокойно.
user=postfix,pass=1234567 –имя пользователя и пароль для доступа к шаре.

Перезагружаем Ubuntu лезем в /media/win/ видим ее содержимое виндовой шары- значит все отлично.

Далее создадим небольшой скрипт который будет переписывать файл recipients
В директорию /etc/postfix затем будет пере конвертировать его в базу данных postfix
И перезагружать почтовик.

#!/bin/bash
cp /media/win/recipients /etc/postfix/
postmap /etc/postfix/recipients
/etc/init.d/postfix restart

Создаем задание в CRON которое будет выполняться через 3 мин после выполнения задания на Win2k3-на всякий случай

Вот и все!

Что мы получили
Аниспам фильтр, с высокой степенью эффективности-до его установки я каждое утро разгребал по 240-260 писем сейчас наблюдаю только нужную почту.
Единственный ящик в который спам, все таки доходит, это ящик на который приходят заявки для нашей конторы, но их число не превышает 3-4.
Безопасность — в случае компрометации сервера Linux шлюз легко восстанавливается,
Да и злоумышленник не получит доступа к корреспонденции, т.к ее на нем просто нет, она сразу передается Exchange-если проходит все проверки.
Кросс платформенность-нет смысла собирать модули с поддержкой всяких экзотических функций.

Время на развертывание-1 час с условием что Интернет шустрый, да и если все копипастить как есть — система протестирована в течении 3х мес. -работает без сбоев.
А теперь, как говорят в Одессе -Подабьем бабки…
Стоимость шлюза 14000руб. –можно и дешевле.
Стоимость антиспама от “Кашпировского” по минимуму 34500, по хорошему 76000-это так, на вскидку.
Вычитаем затраты на железо = сальдо в нашу пользу! Можно у руководства просить премию…
А что еще можно прикрутить к нему- решать вам.

Да немного аналитики-за время использования данной схемы читал логи, так вот-в первые 2 недели работы, очень много писем отбрасывалось по IP заблокированных спам-листами.
Теперь в основном по helo -т.е используются в основном IP не попавшие в спам-листы, очень удивило что на той стороне силы, все анализируется и проверяется… Много думал!
Кстати нужно будет протестировать на Win2k8 и Exchange2k7 по всем вариантам должно пахать нормально.

BackUp DataBase для маленького ресурса

Скрипт будет выдавать ошибку времени исполнения при большом объеме базы. Во избежание этого нужно рекурсивно вызывать скрипт, но еще не придумал как.
Если укажите на ошибки буду только рад. Скрипт естественно несовершенен.

<?php

$tmp_backup_cfg = "backup_config.dat";
$backup_cfg = "backup_cfg.php";

$parse = "////\n";

chdir(dirname($_SERVER['PHP_SELF'])."/");

function structuredb(){
require_once("backup_cnt.php");

$struct_db = "structure_".$backup_dbname.".dat"; // файл структуры БД
if (file_exists($struct_db)) unlink($struct_db);
$fp = fopen($struct_db,"a+");

$tables = mysql_list_tables($backup_dbname); // список таблиц
while ($table = mysql_fetch_row($tables)) { // перебор таблиц

//if ($table[0] != "rusfusion_aw_ec_logins") {

$fields = mysql_list_fields($backup_dbname,$table[0]); // список полей таблицы
$count_fields = mysql_num_fields($fields); // количество полей

fwrite($fp,"\nTable: ".$table[0]."\n");
for ($i = 0; $i < $count_fields; $i++){ // перебор полей
$name_fields = mysql_field_name($fields, $i); // создание массива с именами полей для дальнейшей выборки из бд
$type_fields = mysql_field_type($fields, $i);
$len_fields = mysql_field_len($fields, $i);
$flag_fields = mysql_field_flags($fields, $i);
$name_field = mysql_field_name($fields, $i); // вывод шапки таблицы (имена полей)
fwrite($fp,"Field: $name_fields | $type_fields | $len_fields | $flag_fields\n");
} // END FOR #1
//}//ENDIF
} // END WHILE #1
fclose($fp);

$content = "Save structure db $backup_dbname - complite.";
$fo = fopen($struct_db,"r");
$content .= "

Структура БД: $backup_dbname



";

$data = fread($fo,filesize($struct_db));
$data = str_replace("\n","
",$data);
$data = str_replace("Table:","Table:",$data);
$data = str_replace("Field:","    Field:",$data);

fclose($fo);
$content .= $data;

$content .= "

<form method=\"POST\" action='".$_SERVER['PHP_SELF']."'>
";

return $content; //"Function Structure DB";
}

function copydb($parse,$start){
require_once("backup_cnt.php");
$content = "";
$dirdata = "database";
if (!file_exists($dirdata) || !is_dir($dirdata)) mkdir($dirdata);
$tables = mysql_list_tables($backup_dbname); // список таблиц
while ($table = mysql_fetch_row($tables)){ // перебор таблиц

$fields = mysql_list_fields($backup_dbname,$table[0]); // список полей таблицы
$count_fields = mysql_num_fields($fields); // количество полей

$name_fields = "";
for ($i = 0; $i < $count_fields; $i++){ // перебор полей
$name_fields[] .= mysql_field_name($fields, $i); // создание массива с именами полей для дальнейшей выборки из бд
} // END FOR #1

$sql_count = mysql_query("SELECT count(*) AS c FROM ".$table[0]);
$count_str = mysql_result($sql_count,'c');
/**Нужно рекурсивно вызывать скрипт с заданным началом отсчета. А вход в функцию по переменной в адресе!**/
if ($count_str > 500){
$count = 500;
if (empty($start)) $start = 0; else $start = intval($start);
$file = $table[0].".dat";
if (file_exists($dirdata."/".$file)) unlink($dirdata."/".$file);
$fo = fopen($dirdata."/".$file,"a");
if ($count_str > $start+$count){
$sql = mysql_query("SELECT * FROM ".$table[0]." LIMIT $start, $count"); // выборка
$i = 0;
while (mysql_fetch_array($sql)){
fputs($fo,$parse);
for ($y = 0; $y < $count_fields; $y++){
$name_field_db = $name_fields[$y];
$data_field = mysql_result($sql,$i,$name_field_db);
$dstr = $data_field."\n";
//$dstr = "[".$name_field_db."] => ".$data_field."\n";
fputs($fo,"$dstr");
} // END FOR #2
fputs($fo,"\n\n");
$i++;
} // END WHILE #2
$start =+ $count;
//header("Location: backup.php?s=$start");
//die();
} // END WHILE #1
if ($count_str > $start){ // Копируем остаток таблицы
$count = $count_str - $start;
$sql = mysql_query("SELECT * FROM ".$table[0]." LIMIT $start, $count"); // выборка
$i = 0;
while (mysql_fetch_array($sql)){
fputs($fo,$parse);
for ($y = 0; $y < $count_fields; $y++){
$name_field_db = $name_fields[$y];
$data_field = mysql_result($sql,$i,$name_field_db);
$dstr = $data_field."\n";
//$dstr = "[".$name_field_db."] => ".$data_field."\n";
fputs($fo,"$dstr");
} // END FOR #2
fputs($fo,"\n\n");
$i++;
} // END WHILE #2
}
fclose($fo);
$content .= "Table ".$table[0]." ($count_str) - complite.<br>";
} else {
$sql = mysql_query("SELECT * FROM ".$table[0]); // выборка
$file = $table[0].".dat";
if (file_exists($dirdata."/".$file)) unlink($dirdata."/".$file);
$fo = fopen($dirdata."/".$file,"a");
$i = 0;
while (mysql_fetch_array($sql)){
fputs($fo,$parse);
for ($y = 0; $y < $count_fields; $y++){
$name_field_db = $name_fields[$y];
$data_field = mysql_result($sql,$i,$name_field_db);
$dstr = $data_field."\n";
//$dstr = "[".$name_field_db."] => ".$data_field."\n";
fputs($fo,"$dstr");
} // END FOR #2
fputs($fo,"\n\n");
$i++;
} // END WHILE #2
fclose($fo);
$content .= "Table ".$table[0]." ($i) - complite.<br>";
} // if >5000
} // END WHILE #1

global $backup_cfg;
unlink($backup_cfg);
return $content;
}

function sendmail(){
$boundary = "--".md5(uniqid(time()));
$headers = "From: My site<noreply@example.com>\r\n";
$headers .= "Reply-To: mail@example.com\r\n";
$headers .= "Return-Path: mail@example.com\r\n";
$headers .= "X-Mailer: Example\n";
$headers .= "MIME-Version: 1.0\r\n";
$headers .= "Content-Type: multipart/mixed; boundary=\"$boundary\"\r\n";
$kod = 'windows-1251';
$multipart .= "--$boundary\n";
$multipart .= "Content-Type: text/html; charset=$kod\n";
$multipart .= "Content-Transfer-Encoding: Quot-Printed\n\n";
$multipart .= "Резервная копия\n\n";

global $backup_dbname;
$path = "structure_".$backup_dbname.".dat";
if (file_exists($path)){
$fp = fopen($path,"r");
if ($fp){
$file = fread($fp,filesize($path));
fclose($fp);
$message_part = "";
$message_part .= "Content-Type: application/octet-stream";
$message_part .= "; name = \"$path\"\n";
$message_part .= "Content-Transfer-Encoding: base64\n";
$message_part .= "Content-Disposition: attachment; filename = \"".$path."\"\n\n";
$message_part .= chunk_split(base64_encode($file))."\n\n";
$multipart .= "--$boundary\n".$message_part;
unlink($path);
}
}

$database = opendir("database/");
while ($filename = readdir($database)){
$ftype = filetype("database/".$filename);
if ($ftype != "dir" && preg_match("#.dat$#D",$filename)){
$path = "database/".$filename;
$fp = fopen($path,"r");
if ($fp){
if (filesize($path) == 0) $file = "";
else $file = fread($fp,filesize($path));
fclose($fp);
$message_part = "Content-Type: application/octet-stream";
$message_part .= "; name = \"$filename\"\n";
$message_part .= "Content-Transfer-Encoding: base64\n";
$message_part .= "Content-Disposition: attachment; filename = \"".$path."\"\n\n";
$message_part .= chunk_split(base64_encode($file))."\n\n";
$multipart .= "--$boundary\n".$message_part;
unlink($path);
} // END IF #2
}
} // END WHILE
$multipart .= "--$boundary--\n";

if (mail("mail@example.com","BackUp",$multipart,$headers))
return "Сообщение отправлено.";
else return "Ошибка отправки.";
}

$content = "

Резервное копирование

";

if (file_exists($backup_cfg)) require_once($backup_cfg);

/**** Не работает ****/
if (isset($_GET['s']) && !empty($_GET['s'])){
$content .= copydb($parse,$_GET['s']);
}

if (isset($_POST['nextstep'])){
$content .= copydb($parse,"");
$content .= sendmail();
//chmod(BASEDIR."backup",0644);
} elseif (isset($_POST['deleteconfig'])){
unlink($backup_cfg);
header("Refresh: 0;");
} elseif (isset($_POST['servername']) && isset($_POST['inputserver']) && !empty($_POST['servername'])){
$backup_fp = fopen($tmp_backup_cfg,"w");
$backup_server = $_POST['servername'];
fwrite($backup_fp,$backup_server);
fclose($backup_fp);
header("Refresh: 0;");
} elseif (file_exists($tmp_backup_cfg)){
if (isset($_POST['logindb']) && isset($_POST['passdb']) && isset($_POST['inputauth']) && !empty($_POST['logindb'])){
$backup_fp = fopen($tmp_backup_cfg,"a");
$backup_lp = $_POST['logindb']."\n".$_POST['passdb'];
fwrite($backup_fp,"\n".$backup_lp);
fclose($backup_fp);
header("Refresh: 0;");
} elseif (isset($_POST['namedb']) && isset($_POST['selectdb']) && !empty($_POST['namedb'])){
$backup_fp = fopen($tmp_backup_cfg,"a");
fwrite($backup_fp,"\n".$_POST['namedb']);
fclose($backup_fp);
$backup_fp = fopen($tmp_backup_cfg,"r");
$backup_data = fread($backup_fp,filesize($tmp_backup_cfg));
fclose($backup_fp);
$backup_str = explode("\n",$backup_data);
$backup_servername = $backup_str[0];
$backup_login = $backup_str[1];
$backup_password = $backup_str[2];
$backup_dbname = $backup_str[3];
$backup_fp = fopen($backup_cfg,"w");
$backup_write = '<?php'."\n".'$backup_servername = "'.$backup_servername.'";'."\n".'$backup_login = "'.$backup_login.'";'."\n".'$backup_password = "'.$backup_password.'";'."\n".'$backup_dbname = "'.$backup_dbname.'";'."\n".'?>';
fwrite($backup_fp,$backup_write);
fclose($backup_fp);
unlink($tmp_backup_cfg);
if (isset($_POST['struct'])) $content .= structuredb();
else {
$content .= copydb($parse,"");
$content .= sendmail();
//chmod(BASEDIR."backup",0644);
}

} else {
$backup_fp = fopen($tmp_backup_cfg,"r");
$backup_data = fread($backup_fp,filesize($tmp_backup_cfg));
fclose($backup_fp);
$backup_count_str = count(explode("\n",$backup_data));
if ($backup_count_str == 3){
$backup_data = explode("\n",$backup_data);
$backup_servername = $backup_data[0];
$backup_login = $backup_data[1];
$backup_password = $backup_data[2];
$backup_conn = @mysql_connect($backup_servername,$backup_login,$backup_password);
if (!$backup_conn){
unlink($tmp_backup_cfg);
header("Refresh: 5;");
die("Could not connect: Неверный логин или пароль. Страница перезагрузится автоматически через 5 секунд.");
}
$backup_dbs = @mysql_list_dbs($backup_conn);

$content .= "
<form method=\"POST\" action='".$_SERVER['PHP_SELF']."'>

База Данных для копирования:


--";

while ($backup_db_name = mysql_fetch_object($backup_dbs)){
$content .= "
<option value='".$backup_db_name->Database."'"; if(isset($backup_dbname) && $backup_db_name->Database == $backup_dbname) $content .= " selected"; $content .= ">".$backup_db_name->Database."";
}
$content .= "

Копировать структуру базы.

";

} elseif ($backup_count_str == 1){
$content .= "
<form method=\"POST\" action='".$_SERVER['PHP_SELF']."'>

Логин:

Пароль:


";
} elseif ($backup_count_str == 3){
$backup_fp = fopen($tmp_backup_cfg,"a");
fwrite($backup_fp,"\n".$_POST['namedb']);
fclose($backup_fp);
$backup_fp = fopen($tmp_backup_cfg,"r");
$backup_data = fread($backup_fp,filesize($tmp_backup_cfg));
fclose($backup_fp);
$backup_str = explode("\n",$backup_data);
$backup_servername = $backup_str[0];
$backup_login = $backup_str[1];
$backup_password = $backup_str[2];
$backup_dbname = $backup_str[3];
$backup_fp = fopen($backup_cfg,"w");
$backup_write = '<?php'."\n".'$backup_servername = "'.$backup_servername.'";'."\n".'$backup_login = "'.$backup_login.'";'."\n".'$backup_password = "'.$backup_password.'";'."\n".'$backup_dbname = "'.$backup_dbname.'";'."\n".'?>';
fwrite($backup_fp,$backup_write);
fclose($backup_fp);
unlink($tmp_backup_cfg);
if (isset($_POST['struct'])) $content .= structuredb();
else {
$content .= copydb($parse,"");
$content .= sendmail();
//chmod(BASEDIR."backup",0644);

}
} else {
unlink($tmp_backup_cfg);
if (file_exists($backup_cfg)) unlink($backup_cfg);
header("Refresh: 0;");
}

}
} else {
if (file_exists($backup_cfg)){
$content .= "

Найден файл конфигурации:

";
$content .= "Server Name: ".$backup_servername."<br>";
$content .= "Login: ".$backup_login."<br>";
$content .= "Password: ".$backup_password."<br>";
$content .= "DB Name: ".$backup_dbname."<br>";
$content .= "
<form method=\"POST\" action='".$_SERVER['PHP_SELF']."'>";
}
$content .= "
<form method=\"POST\" action='".$_SERVER['PHP_SELF']."'>

Имя сервера:


";
}
$content .= "";
print $content;
?>

Делаем свой Pager для ASP.NET Web Forms

Предыстория


Думаю, каждый кто хоть раз реализовал пэйджизацию на asp.net(не MVC) плевался и задавал простой вопрос. Неужели нельзя было сделать по-людски. К примеру, стандартный GridView генерирует ссылки вида
«javascript:__doPostBack('ctl00$ctl00$MainContent$LiveExample$GridView1','Page$2')»>. Думаю не нужно объяснять, почему это плохо. В версии 3.5 появился DataPager, в котором можно передать номер страницы через GET запрос. Но всеобщие счастье опять не настало Необходимо, чтобы контрол реализовал интерфейс IPageableItemContainer, а его реализует только ListView. Это как я понял новый сверхмощный контрол на замену DataList GridView. Соответственно если нужно использовать новый пэйджер со старыми контролами нужно реализовать интерфейс. Как это сделать можно прочитать к примеру тут. Основной минус данного подхода — при связывании данных будет извлекаться вся(если не использовать LINQ DataSource) коллекция и исходя из этого строится грид и пэйджер. Обычно при постраничном отображении мы имеем коллекцию объектов для отображения на текущей странице, плюс мы знаем общее число страниц и текущую страницу. Эти предпосылки плюс не желание в 3-раз (все же 2 раза я это сделал) писать пэйджер под каждый конкретный грид послужило поводом сделать что-то более общее.

Требования


  • Идентификатор страницы при пейджизации должен передаваться в строке GET запроса
  • Контролу должен знать количество страниц для отображения
  • визуальные элементы отображения (ссылки следующая, предыдущая, первая, последняя и тд) должны быть шаблонными, что бы при использовании можно было легко править стил пэйджера под текущую страницу
  • естественно свойства для имени параметра идентифицирующего номер странице в строке запроса

Да и вообще модифицировать контрол нужно практически декларативно(разметкой и заданием свойств). Максимум что можно стерпеть так это связывание данных кодом.

Реализация


При реализации возник вопрос, что делать чистый серверный контрол или User Control. Был выбран второй. Почему? ну так фишка легла. Всегда можно сделать по-другому.
Если кто не в курсе как писать шаблонные user контролы, то это хорошо описано тут, а еще лучше тут.

Для начала нужно создать класс для шаблона.
public class PageContainer : Control, INamingContainer
{
  private int pageIndex;

  private string destinationURL;

  public string DestinationURL
  {
    get
    {
      return destinationURL;
    }
  }

  public int PageIndex
  {
    get
    {
      return pageIndex;
    }
  }

  public PageContainer(int index,string url)
  {
    pageIndex = index;
    destinationURL = url;
  }
}


* This source code was highlighted with Source Code Highlighter
.



Далее создаем несколько шаблонов
private ITemplate currentPageTemplate; // текущая страница

  [TemplateContainer(typeof(PageContainer))]
  [PersistenceMode(PersistenceMode.InnerProperty)]
  public ITemplate CurrentPageTemplate
  {
    get
    {
      return currentPageTemplate;
    }
    set
    {
      currentPageTemplate = value;
    }
  }

  private ITemplate nextPageTemplate; // следующая страница

  [TemplateContainer(typeof(PageContainer))]
  [PersistenceMode(PersistenceMode.InnerProperty)]
  public ITemplate NextPageTemplate
  {
    get { return nextPageTemplate; }
    set { nextPageTemplate = value; }
  }

  private ITemplate prevPageTemplate; // предыдущая страница

  [TemplateContainer(typeof(PageContainer))]
  [PersistenceMode(PersistenceMode.InnerProperty)]
  public ITemplate PrevPageTemplate
  {
    get { return prevPageTemplate; }
    set { prevPageTemplate = value; }
  }

  private ITemplate numericalPageTemplate; // шаблон для отображения номера страницы

  [TemplateContainer(typeof(PageContainer))]
  [PersistenceMode(PersistenceMode.InnerProperty)]
  public ITemplate NumericalPageTemplate
  {
    get { return numericalPageTemplate; }
    set { numericalPageTemplate = value; }
  }

  private ITemplate firstPageTemplate; // первая страница

  [TemplateContainer(typeof(PageContainer))]
  [PersistenceMode(PersistenceMode.InnerProperty)]
  public ITemplate FirstPageTemplate
  {
    get { return firstPageTemplate; }
    set { firstPageTemplate = value; }
  }

  private ITemplate lastPageTemplate; // последняя страница

  [TemplateContainer(typeof(PageContainer))]
  [PersistenceMode(PersistenceMode.InnerProperty)]
  public ITemplate LastPageTemplate
  {
    get { return lastPageTemplate; }
    set { lastPageTemplate = value; }
  }


* This source code was highlighted with Source Code Highlighter
.



На странице отображатся будет в следующей последовательности
[PrevPageTemplate][FirstPageTemplate][NumericalPageTemplate][CurrentPageTemplate][NumericalPageTemplate][LastPageTemplate][NextPageTemplate]

Вся генерация происходить в методе
public override void DataBind()
  {

    if( CurrentPageIndex >0)
    {
      if(PrevPageTemplate!=null)
      {
        AddTemplateAsControl(PrevPageTemplate, new PageContainer(CurrentPageIndex-1,GetDestinationURL(CurrentPageIndex-1)));
      }
      if(FirstPageTemplate!=null)
      {
        AddTemplateAsControl(FirstPageTemplate, new PageContainer(0, GetDestinationURL(0)));
      }
    }
    for(int i=CurrentPageIndex-NumericRange;i<CurrentPageIndex; i++)
    {
      if(i>=0 && NumericalPageTemplate!=null)
      {
        AddTemplateAsControl(NumericalPageTemplate, new PageContainer(i,GetDestinationURL(i)));
      }
    }

    if(CurrentPageTemplate!=null)
    {
      AddTemplateAsControl(CurrentPageTemplate, new PageContainer(CurrentPageIndex, GetDestinationURL(CurrentPageIndex)));
    }
    for(int i=CurrentPageIndex+1;i<CurrentPageIndex+NumericRange; i++)
    {
      if(i<=TotalPageCount && NumericalPageTemplate!=null)
      {
        AddTemplateAsControl(NumericalPageTemplate, new PageContainer(i,GetDestinationURL(i)));
      }
    }
    if(CurrentPageIndex!=TotalPageCount )
    {

      if(LastPageTemplate!=null)
      {
        AddTemplateAsControl(LastPageTemplate, new PageContainer(TotalPageCount, GetDestinationURL(TotalPageCount)));
      }
      if(NextPageTemplate!=null)
      {
        AddTemplateAsControl(NextPageTemplate, new PageContainer(currentPageIndex+1, GetDestinationURL(currentPageIndex+1)));
      }

    }
    base.DataBind();
  }


* This source code was highlighted with Source Code Highlighter
.



А теперь просто добавляем контрол на любую страницу


 <uc:pager ID="super_pager" TotalPageCount="10" NumericRange="3" QueryStringParameter="p" runat="server" >
      <NumericalPageTemplate>
          <a href='<%# Container.DestinationURL %>'><%# Container.PageIndex %></a>      
      </NumericalPageTemplate>   
      <CurrentPageTemplate>
        <span style="background-color:Red; color:Black;">
         current page <b> <%# Container.PageIndex %></b>
        </span>      
      </CurrentPageTemplate>
      <FirstPageTemplate>
        <a href='<%# Container.DestinationURL %>'><b>First</b></a>
      </FirstPageTemplate>
      <LastPageTemplate>
       <a href='<%# Container.DestinationURL %>'><b>Last</b></a>
      </LastPageTemplate>
      <NextPageTemplate>
       <a href='<%# Container.DestinationURL %>'>--></a>      
      </NextPageTemplate>
      <PrevPageTemplate>
       <a href='<%# Container.DestinationURL %>'><--</a>
      </PrevPageTemplate>    
    </uc:pager>

* This source code was highlighted with Source Code Highlighter
.



Правя шаблон можно сделать пейджизацию digg or Habra style

Еще немного занудства
  • TotalPageCount количестьво страниц
  • NumericRange сколько страниц(слева и справа) будет отображатся от текущей в числомов шаблоне
  • QueryStringParameter параметр в котором хранится номер страницы


Исходный код можна скачать тут

Заключение


Если вы используете URL Rewriting, то придется вместо свойства контейнера DestinationURL формировать ссылку самому. Если кто-то как выполнить обратную операцию для URL Rewriting, то будет весьма полезно.
К слову сказать по аналогии можно реализовать систему табов(разделов) на сайте. Фактически вместо номера страницы будет использоваться идентификатор таба. Можно разместить на странице несколько таких контролов и организовать «cross paging», а скорее cross tabing. Например первый контрол отвечает за бренд продукции второй за категорию или один за период дат (месяц года или день) другой за тэг

Создание deb пакета без использования стандартных утилит Debian

В этой статье я рассужу как создать пакет для установки в Debian не используя стандартные утилиты из Debian, такие как devhelper, dh_make, cdbs, alien и т.д. Мы будем использовать только стандартные Linux утилиты, которые есть в любом современном дистрибутиве такие как sh, tar, cat, fakeroot, date. Нижеизложенную методику можно легко применить для создания deb пакета из любого программного продукта распространяемого производителем в бинарном виде.

Для примера давайте возьмём стандартную бинарную сборку Firefox с сайта Mozilla и создадим для него deb пакет. Для удобства все необходимые команды запишем в скрипт на стандартном shell, который можно легко будет изменить позднее под конкретные нужды.

Для начала зададим несколько переменных которые мы будем использовать в скрипте. В переменных зададим название пакета, версию, имя загружаемого архива, минорную версию, архитектуру собираемого пакета, имя создателя пакета и его почтовый адрес, ссылку для скачивания с сайта Mozilla и дату создания пакета в формате RFC-2822

#!/bin/sh

echo "Setting variables..."

PACKAGE=firefox
VERSION=3.5.5
FILE=${PACKAGE}-${VERSION}.tar.bz2
MINOR=1
ARCH=i386
USER='Your Name'
EMAIL='your@e-mail.here'
URL=ftp://ftp.mozilla.org/pub/firefox/releases/${VERSION}/linux-i686/ru/${PACKAGE}-${VERSION}.tar.bz2
DATE=`date -R`
...


Скачаем бинарный пакет Firefox с сайта производителя, если нет его в локальной директории:

...
echo "Downloading package..."
[ ! -f ${FILE} ] && wget $URL
...


Стандартный бинарный пакет Debian состоит из двух архивов data.tar.gz и control.tar.gz и файла debian-binary, которые запакованы в архив командой ar. С помощью ar собираются статические библиотеки из объектных файлов. Ее можно найти в любом дистрибутиве в пакете binutils.

debian-binary — содержит версию бинарной сборки. На момент написания статьи текущая версия была 2.0.
data.tar.gz — содержит данные которые будут установлены в систему пользователя
control.tar.gz — содержит файл control с метаданными о пакете и вспомогательные скрипты для установки удалении пакета.

Такое разделение позволяет быстро извлекать информацию о пакете прямо из пакета без разархивирования всего пакета.

Давайте создадим файлы которые необходимо будет добавить в control.tar.gz

...
echo "Creating control file..."

cat > control << EOF;
Package: ${PACKAGE}
Version: ${VERSION}-${MINOR}
Maintainer: ${USER} <${EMAIL}>
Section: non-free/web
Architecture: ${ARCH}
Homepage: http://www.firefox.com/
Priority: optional
Replaces: mozilla-firefox
Provides: www-browser
Depends: fontconfig, psmisc, procps, libc6, libgcc1, libstdc++6
Description: This is the original version of Firefox from Mozilla
Package built with dh-firefox utility
EOF

cat > debian-binary << EOF;
2.0
EOF
...


Создадим структуру каталогов будущего пакета и добавим README, changelog.Debian и файл с лицензией, который возьмем из репозитария с исходниками Firefox. Чтобы у пользователя они занимали меньше места на диске их мы сразу запакуем:

...
echo "Creating doc files..."
rm -Rf usr
mkdir -p usr/share/doc/firefox

cat << EOF | gzip -9 - > usr/share/doc/${PACKAGE}/changelog.Debian.gz
firefox (${VERSION}-1) unstable; urgency=low

* Initial release.

-- ${USER} <${EMAIL}> ${DATE}

EOF

cat << EOF | gzip -9 - > usr/share/doc/${PACKAGE}/README.Debian.gz
Firefox has been packged by dh-firefox utility.
EOF

echo "Downloading copyright file..."
#wget -O - http://hg.mozilla.org/mozilla-central/raw-file/f09961c1d5fa/LICENSE \
#> usr/share/doc/${PACKAGE}/copyright
[ ! -f LICENSE ] && wget http://hg.mozilla.org/mozilla-central/raw-file/f09961c1d5fa/LICENSE.
cp LICENSE usr/share/doc/${PACKAGE}/copyright
...


Создадим desktop файл для рабочих окружении которые поддерживают стандарты freedesktop.

...
echo "Creating desktop file..."
mkdir -p usr/share/applications
cat << EOF > usr/share/applications/firefox.desktop
[Desktop Entry]
Encoding=UTF-8
Name=Firefox Web Browser
Name[ru]=Веб браузер Firefox
Comment=Browse the World Wide Web
Comment[ru]=Просмотрщик для WWW
GenericName=Web Browser
GenericName=Веб браузер
Exec=firefox-original %u
Terminal=false
X-MultipleArgs=false
Type=Application
Icon=/usr/share/firefox/icons/mozicon128.png
Categories=Network;WebBrowser;
MimeType=text/html;text/xml;application/xhtml+xml;application/xml;application/vnd.mozilla.xul+xml;application/rss+xml;applicat
StartupWMClass=Firefox-bin
StartupNotify=true
EOF
...


Файловое имя /usr/bin/firefox используется в пакете iceweasel. Конечно можно использовать утилиту dpkg-divert для подстановки и замены файлов в системе, но мы будем придерживаться условий задачи и не будем использовать стандартные утилиты Debian. Поэтому мы просто создадим ссылку с другим именем на скрипт запуска firefox. Эту ссылку назовем firefox-original.



...
mkdir -p usr/bin
ln -s ../share/firefox/firefox usr/bin/firefox-original
...


Теперь создадим три скрипта postinst, prerm и postrm, которые будут запускаться после установки и перед удалением пакета и после удаления. Из этих скриптов запускаются утилиты, которые обновят базу desktop файлов, базу MIME типов и алтернатив.

...
echo "Creating install/uninstall scripts..."

cat << EOF > postinst
#!/bin/sh -e

if [ "\$1" = "configure" ] || [ "\$1" = "abort-upgrade" ] ; then
update-alternatives --install /usr/bin/x-www-browser \\
x-www-browser /usr/bin/firefox-original 70
update-alternatives --install /usr/bin/mozilla mozilla \\
/usr/bin/firefox-original 0
fi

if [ -x /usr/sbin/update-desktop-database ]; then
/usr/sbin/update-desktop-database
fi

if [ "\$1" = "configure" ] && [ -x "\`which update-mime 2>/dev/null\`" ]; then
update-mime
fi

if [ "\$1" = "configure" ] && which update-desktop-database >/dev/null 2>&1 ; then
update-desktop-database -q
fi

EOF

cat << EOF > postrm
#!/bin/sh -e

#if [ -x "\`which update-menus 2>/dev/null\`" ]; then update-menus ; fi
if which update-mime >/dev/null 2>&1; then update-mime; fi
if [ "\$1" = "remove" ] && which update-desktop-database >/dev/null 2>&1 ; then
update-desktop-database -q
fi

EOF

cat << EOF > prerm
#!/bin/sh -e

if [ "\$1" = "remove" ] || [ "\$1" = "deconfigure" ] ; then
update-alternatives --remove x-www-browser /usr/bin/firefox-original
update-alternatives --remove mozilla /usr/bin/firefox-original
fi

EOF

chmod +x postinst postrm prerm
...


Распакуем полученный бинарный архив с сайта Mozilla в директорию user/share. Также уберем флаг исполнения с динамически подгружаемых библиотек и подсчитаем контрольные суммы всех файлов:

...
echo "Unpacking ${PACKAGE} ${VERSION} archive..."

tar -C usr/share -xjf firefox-${VERSION}.tar.bz2

echo "Fixing permission for *.so files..."
find usr/share -type f -name *.so -exec chmod -x {} \;

echo "Calculating MD5 sums..."
find usr -type f -exec md5sum {} >> md5sums \;
...


Теперь пришло время упаковать все созданные файлы в архивы и сложить их в deb файл:

...
echo "Creating data.tar.gz ..."
fakeroot tar cvfz data.tar.gz usr

echo "Creating control.tar.gz ..."
fakeroot tar cvfz control.tar.gz control postinst postrm prerm

echo "Creating ${PACKAGE}_${VERSION}-${MINOR}_${ARCH}.deb ..."
rm -f ${PACKAGE}-${VERSION}-${MINOR}.deb
ar r ${PACKAGE}_${VERSION}-${MINOR}_${ARCH}.deb debian-binary control.tar.gz data.tar.gz
echo "The END..."


Чтобы убедится в том что deb файл создан корректно, запустим проверку на валидность:

man@work:~/tmp/firefox$ lintian -v firefox_3.5.5-1_i386.deb
N: Setting up lab in /tmp/UmQRWztbi7 ...
N: Processing 1 packages...
N: ----
N: Processing binary package firefox (version 3.5.5-1) ...
W: firefox: binary-without-manpage usr/bin/firefox-original
W: firefox: new-package-should-close-itp-bug
N: Removing /tmp/UmQRWztbi7 ...
man@work:~/tmp/firefox$


Как мы видим, пакет имеет только два несущественных предупреждения. Теперь можно его установить и насладится проделанной работой :)

dpkg -i firefox_3.5.5-1_i386.deb


Спасибо, за внимание!

P.S. Мне нужно то что было у всех зарегистрированных здесь пользователей ;)

Перенос FreeBSD7.1 из реального окружения в виртуальное. HYPER-V.

После выхода Windows Server 2008 покоя не давал HYPER-V. Уж очень хотелось установить его и завести виртуальных машин, да побольше. Но ни времени, ни ресурсов, под это дело, не хватало. Однажды, на работе на складе мы нашли сервер … пауза … нет правда! Настоящий IBM x3400. Как он туда попал до сих пор «наверху» понять не могут, по-моему. Примечание, фирма продает компьютеры. Сервер какое-то время стоял без дела, после чего было решено сделать его основным для разработчиков сайта. На него поставили FreeBSD, настроили все, апач, пхп, битрикс и т.п. Так прошло лето…

Однажды я про него вспомнил и решил немного подвинуть программистов и самому попользоваться сервером в целях самообразования, а также чтобы перенести туда часть виртуальных машин с ноутбука.

Часть1. Настойка и установка Windows Hyper-V Server 2008R2



Начнем с установки и настройки HYPER-V, как части Windows Hyper-V Server 2008 R2. Рекомендую выбрать английскую версию, т.к. большинство руководств по настройке содержит в себе английские названия служб, утилит и т.п. и сопоставить их с русскими, порой занимает лишнее время. Также в одном из топиков на social.technet.microsoft.com я встречал фразу «пункт 1 пропускаем, т.к. он все равно в русской версии не будет работать» и действительно когда настраиваешь в русской версии пункт 4-1 в «scconfig.cmd», то всегда будет появляться окошко «Не удалось перенастроить брандмауэр Windows». Данная операционная система построена на базе Windows Server Core и не предполагает графических утилит для настройки, однако ей очень удобно можно управлять через консоли MMC с другого компьютера, например на Windows 7. После установки, система выглядит так:

image

Как видно, тут присутствует пустое окно cmd.exe, а также запущенный командный файл scconfig.cmd, которые запускаются автоматически при входе в систему. Первое, что необходимо сделать, это ввести сервер в домен и присвоить ему имя. Можно конечно не вводить машинку в домен, но тогда будут проблемы с правами, которые, если честно, мне так и не удалось победить до конца. При наличии вас в группе, которая попадет в локальные администраторы, при применении доменных политик (по умолчанию, это «Администраторы домена»), вы получите все административные функции, кроме тех, что блокируются брандмауэром на вашем сервере. Компьютер, с которого вы будете управлять, тоже должен быть в составе домена.
Настройка доступа для сервера в рабочей группе описана в форуме Technet

Далее о брандмауэре. Для разрешения доступа по MMC и через «Удаленный рабочий стол» необходимо перейти в окно «scconfig.cmd» и выбрать пункт номер 7, а также номер 4, где включить mmc, а также разрешить использование Powershell. «Удаленное управление через диспетчер сервера» мне включить не удалось из-за проблем с брандмауэром на моей Windows 7.
Для управления сервером из Windows 7 вам потребуется набор административных консолей mmc – RSAT. Скачать его можно с сайта Майкрософт

На данном этапе возникают 2 проблемы, это не работающие «Диспетчер устройств» и «Управление дисками» через консоль «Управление компьютером».

  • Создаем новую консоль mmc и сохраняем ее куда-нибудь.
  • Открываем ее под пользователем домена, с административными правами. (если на исполняемом файле или ярлыке нажать «Shift+ПКМ» то в контекстном меню появится несколько дополнительных функций.
  • Добавляем туда «Редактор объектов групповой политики» и вместо «локальный компьютер», выбираем ваш Hyper-V Server.
  • Переходим в «Конфигурация компьютера\Административные шаблоны\Система\Установка устройств» и включаем политику «Разрешить удаленный доступ к устройствам Plug’n’Play” для того, чтобы удаленно заработал «Диспетчер устройств»

Стоит отметить, что диспетчер устройств будет в режиме read only.

Для удаленного управления дисками нужно в консоли сервера выполнить:

netsh advfirewall firewall set rule group=“Remote Volume Management” new enable=yes

Для удаленного запуска оснастки по управлению брандмауэром:

netsh advfirewall firewall set rule group=“Windows Firewall Remote Management” new enable=yes

Все. Теперь у вас есть доступ к 2м основным компонентам на сервере
— Управление компьютером (Computer management);
— Диспетчер Hyper-V (Hyper-V manager)

Часть 2. Миграция FreeBSD 7.1 на Hyper-V Server.



Во-первых, необходимо выполнить резервное копирование FreeBSD на другой диск, чтобы эксперимент с переносом не закончился плачевно. Т.к. просто клонировать образ диска на другой, с помощью стандартных утилит (Acronis, Partition Magic), не получится, то обойдемся средствами FreeBSD.
Берем новый диск, подключаем его рабочей freebsd и создаем на нем разделы и слайсы нужных нам размеров, а также делаем диск загрузочным и устанавливаем загрузчик FreeBSD. В общем случае, процедура резервного копирования заключается в следующих командах:

# создаем файловую систему UFS на новом слайсе.
# newfs /dev/ad6s1a
# монтируем новый слайс в нужную нам директорию
# mount /dev/ad6s1a /mnt
# cd /mnt
# делаем резервную копию раздела / в примонтированный слайс
# dump 0af - / | restore xf -


Дальше таким же образом для каждого вашего раздела, зависит от того какие вы создавали на этапе установки FreeBSD, например /usr, /tmp и т.п.
После бекапа всех разделов, нужно открыть /etc/fstab на новом диске и поправить таблицу монтирования разделов под ваш новый жесткий диск, после этого, перемещать диск, в другие разъемы SATA или т.п., не рекомендуется.
Все, теперь пытаемся загрузиться с нового диска, и если загрузка успешна, откладываем его в сторону.
Приступим к миграции в Hyper-V. К серверу с установленной Hyper-V, необходимо подключить диск с FreeBSD.
Запускаем Windows 2008 и подключаемся к ней через консоль Hyper-V manager (диспетчер Hyper-V), запущенной с правами доменной учетной записи. Нажимаем ПКМ на нужном сервере в консоли и выбираем «создать\жесткий диск» в открывшемся окне выбираем тип диска, размер, и главное в пункте «Настройка диска» выбираем «Копировать содержимое указанного жесткого диска» где отмечаем наш HDD с FreeBSD. После окончания процесса создаем новую виртуальную машину и подключаем к ней наш новый виртуальный диск.
Запускаем новую виртуальную машину! И видим что она не запускается, вернее, на этапе загрузки доходит до момента монтирования файловых систем и не может это сделать т.к. в файле «/etc/fstab» указан другой диск.
Поправить это просто, в окне с FreeBSD мы увидим приглашение смонтировать ФС вручную

mount:

что мы и сделаем, выполнив команду

mount /dev/ad0s1a /

вам нужно будет указать свой слайс, в котором находится корневой раздел. Загрузка продолжится, после этого логинимся в систему под root и правим /etc/fstab под наши названия дисков.
Перезагрузка и проверка, что ОС загрузилась успешно…

FreeBSD действительно загрузилась успешно, но как и пишет Андрей Бешков в своем блоге присутствует проблема с DHCP, которая решается либо отключение DHCP и установкой ручных параметров, либо добавлением дополнительных параметров для сетевой карты

ifconfig_de0=”DHCP media 100baseTX mediaopt full-duplex”

Вот и все! Удачной всем виртуализации =)

Blackberry Ant Tools — система сборки проектов для Blackberry.

Стандартный диалог с заказчиком при обсуждении очередного проекта для Blackberry выглядит примерно так:

PM (с надеждой): А вам версии приложения для каких платформ нужны?..
Заказчик : Для всех!!!

Вот тут-то, %username%, начинается тот самый кошмар, имя которому «разработка для Blackberry».

Особенности процесса компиляции.

Во-первых, у Blackberry достаточно много версий их ОС. Не буду перечислять их все, отмечу лишь, что как правило требуется поддержка как минимум диапазона 4.1-4.7, реже версий меньше 4.0. Соответственно, приходится как минимум создавать билд для каждой из платформ, так как одни и те же функции API работают на них по-разному.

Во-вторых, вне зависимости от установленной версии ОС, у телефонов Blackberry большой набор возможных разрешений. Значит, скорее всего придется делать отдельный билд приложения для каждого из этих разрешений.

В-третьих, для того, чтобы скомпилировать приложение для Blackberry надо:

  1. Нажать кнопку F7 в Blackberry IDE (не сложно ведь, правда?)
  2. Подписать получившийся .cod-файл, что включает в себя
    • Дважды щелкнуть по .cod-файлу мышкой
    • Ввести пароль для подписи
    • Нажать OK
  3. Разархивировать получившийся .cod-файл. Здесь имеет смысл пояснить — Blackberry JDE преобразует стандартный формат бинарника .JAR в формат Blackberry OS .cod. Более того, JDE автоматически разбивает слишком большой .cod файл на несколько более мелких кусочков и затем зипует их в архив с тем же расширением. Но установить такой архив на телефоне невозможно — надо сначала раззиповать его на кучу .cod-файлов
  4. И наконец, надо скопировать все .cod-файлы и .jad-файл на какой-нибудь сервер — ибо проще всего установить приложение на Blackberry можно лишь скачав его из браузера, установленного на телефоне


А теперь представим, что заказчику захотелось иметь версии приложения для 10 девайсов. Если делать все руками, потратим минимум полчаса — это если все сделать без ошибок.

Как же все сделать по-человечески?

Сначала, перелопатив документацию о RIMовском компиляторе, я сделал несколько батников, с помощью которых компилировал приложения. Но потом и их стало нехватать. В один прекрасный день я наткнулся на проект Blackberry Ant Tools — и я забыл о получасовой компиляции навсегда.

Основные возможности расширения:
  • С помощью тега <jdp> настроить все параметры проекта — название приложения, его иконку, импорт нужных библиотек и многое другое.Выглядеть это будет примерно вот так:

    <jdp title="Kick Ass App" vendor="Slashdev" version="1.0" type="cldc" />
  • Директива <sigtool> позволяет избежать ручной подписи файлов
  • Директива <define> дает возможность указать директивы препроцессора для компилятора RIM (да, у них даже есть препроцессинг!). В JDE версии < 4.7 это можно сделать только редактируя вручную файл проекта JDE (.jdp)

Есть еще множество плюшек, с которыми можно ознакомиться здесь

Примечание.

Единственной серьезной проблемой данного расширения было то, что при компиляции проекта иногда ресурсы (картинки, звуки итп) складывались в корень .cod-файла. Оказалось, что «иногда» — это когда для директивы <rapc> указан аттрибут destdir, отличающийся от корневой директории проекта. Таким образом, проблема оказалась в компиляторе от RIM. Решение очевидно — копировать бинарники с помощью стандартных антовских директив.

Вот, собственно и все %).

Создание интернет-магазина с нуля и с малым запасом денег. Личный опыт.

Добрый день, расскажу Вам про создание интернет-магазина с нуля со скромным стартовым капиталом. Придерживаюсь приблизительной хронологии, что бы иметь представление о сроках реализации «под ключ».

Вводная:


Магазин создавался как пилотный проект: прощупать почву, набить шишки. В общем получить опыт. Изначально не было мечтаний на тему золотых гор, напротив, ожидалось плохая отдача и затруднительная окупаемость. Таким образом было принято решение выбрать ходовой, но конкурентный товар высокой цены и только с курьерской доставкой (на первых порах быть и кассиром и курьером и всем остальным). Посему выбор пал на мобильники стоимость от 10 ка, дабы с заказа был навар минимум в тысячу рублей.

Средства реализации:
Друпал 5, без уберкарт. Фактически используется как фреймворк, что ли ) Познания в нем больше, чем в других CMS(F), поэтому над выбором не заморачивался. Да и модельный ряд — два десятка аппаратов, не тот объем, который требует специализированного решения.

Набор знаний:

Вышеуказанное знание Drupal, хороший опыт в SEO (Думал писать или нет, закидают же...), ну и, надеюсь, мышление. В принципе уже не плохо.

День первый:


Выбор формы регистрации магазина. Все просто, ИП, т.к. дешево, сердито и справится любой школьник. День потрачен на поиск информации, что бы не попасть в просак. Поскольку регистрация проходит в Питере, то ехать нужно было на Маршала Попова, в «Единый центр регистрации ФНС». С собой иметь заявление на регистрацию, заявление на упрощенку и прочие требуемые документы.

Совет 1: Дешевая и хорошая нотариальная контора находится на Чкаловском проспекте, если идти от метро дважды налево, как на Петровский, то после дома увидите кафешку, а за ней маленькое здание - это оно. Стоимость 100 рублей.


Приходить нужно не позже, чем за час (в ФНС).
Совет 2: на сайте ФНС имеется набор программ, есть и милая программа для ИП, которая поможет Вам сделать верные документы.


День второй:


Собственно поход в ЕЦРФНС, все прошло быстро, минут за 30. За документами через неделю. Отлично, вернулся домой, задача на вечер — поиск кассы. По сколько мы, фактически, готовимся к выездной торговле, то вполне сойдет и переносная касса. Для экономия мой выбор пал и вовсе на БУ аппарат. Итог — 11 000 рублей против 16 000 за новую, не плохая экономия. Созвонился, договорился. Кассу на учет можно ставить с документами на руках, а значит пора заняться реализацией.

День третий:


Выбор итоговой функциональности сайта, телефонов, просмотр всех конкурентов и выделение слабых и сильных сторон, ну и, собственно, рисунки дизайна «от руки». Хочется сказать, что я не дизайнер и с Фотошопом на Вы, но даже так можно добиться результата лучше, чем у 90% интернет-магазинов.

Дни четвертый — седьмой:


Собственно говоря каждый из дней поделился на пополам, рисунки, перенос характеристик, в общем, по-большой части рутинная работа, а так же встречи с поставщиками.
ВНИМАНИЕ: ИНТЕРНЕТ МАГАЗИН — ЭТО ПОЛНОЦЕННЫЙ БИЗНЕС, ОТЛИЧИЕМ ЯВЛЯЕТСЯ ЛИШЬ РЕКЛАМНОЕ ПРОСТРАНСТВО. ВИТРИНЫ МАГАЗИНА ЗАМЕНЕНЫ НА СТРАНИЧКУ САЙТА. ВСЕ!

Нужно торговаться, где-то врать, где-то честно говорить о предложение конкурента. В общем — игра. Иначе смысла браться просто нету. Цены высокие, бывает что и купить телефон в розницу дешевле, чем у оптовиков.

Благо поставщиков было 5, выбор был сделан, ожидалось завершение первого периода…

День восьмой:


Получение документов в Едином центре регистрации и сразу же поездка в районный отдель ФНС, встреча с технологом ЦТО, покупка и постановка кассы на учет. Документы обещают через 2 недели, кассой можно пользоваться сразу.

По приезду домой, несмотря на то, что работал неделю на износ понял одну вещь, забыта реклама. В SEO вкладываться пока что не решил, ибо качественное, постоянное продвижение все таки стоит денег, даже если техническую работу выполняешь сам. На помощь пришла карточка ЭдВордса. Думаю и у Вас такие были ))) Много ))) Пропарсив поисковую выдачу были найдены наиболее популярные из предлагаемых мною моделей телефоны, сверив мое предложение и предложения конкурентов были отобраны наиболее конкурентоспособные, созданы объявления и размещены в интернете. Но…

День девятый — одиннадцатый:


Тишина. На специально заведенный телефон было несколько звонков, скорее всего проверяли конкуренты, а может и нет… Разочарования, впрочем, так же не было. Да и наконец был взят отдых. Таки усталость берет свое. Так что двенадцатый и тринадцатый день ушел на развлечения, но и рабочий телефон я брал с собой. И не зря. На тринадцатый день поступил заказ, все прошло ровно, без проблем.

Дни четырнадцатый — двадцатый:


В общем это уже немного выходит за пределы материала, что я хотел дать, но, тем не менее, очень важно.

В это время было принято решение работать над интерфейсом, дизайном каждый раз, когда было время. Параллельно выполнять заказы. Было их штук 7, три из них — пустышки. Отказы. Сейчас я понимаю, что по % просто не повезло, но тогда злости было много. В общем, спустя неделю был принципиально новый сайт, не богатый, но красивый, удобный. И кол-во заказов, собственно, увеличилось.

На данный момент это порядка 15и заказов в неделю при работе без выходных.

Итог:



За месяц реально выйти на объем в 50 реальных продаж в месяц
В среднем наценка составляет 1 300 рублей


Так что о смерти отросли говорить рано, главное — подход. Ну и конверсия. В среднем продажа 1 телефона обходится в 500 рублей, в случае если делаешь все САМ. В эту сумму входит контекстная реклама (~300р даже с оптимально настроенными ключами), 200р доставка, это в лучшем случае. В скором времени буду увеличивать объемы, надеюсь смогу об этом написать уже в качестве Вашего коллеги ;)
Рад, если кому-нибудь написанное мной принесет пользу.

Zen Coding: Ускорение верстки

Оригинал

В статье описывается новый способ написания HTML кода с использованием CSS селекторов, применяя удобный комплект инструментов, разработанный Сергеем Чикунком.

Сколько времени вы тратите для написания HTML? Теги, атрибуты, кавычки, скобки? Конечно, задача упрощается при использовании редактора с использованием автодополнения слов, но все же приходится вручную набирать довольно таки много кода.

Та же проблема и с Javascript, когда мы хотим получить конкретный элемент на странице. Приходилось писать много громоздкого кода, который становился все сложнее и сложнее в поддержке. Задачу решили Javascript фрэймворки, введя методики использования CSS селекторов. И вот теперь, вы можете использовать простые CSS выражения для получения любого DOM элемента. Удобно не правда ли?

А если бы вы могли использовать селекторы не только для выбора и доступа к элементам, но и еще и для генерации кода? Например, если бы вы могли написать так…
div#content>h1+p
… и получить такой текст
<div id="content">
<h1></h1>
<p></p>
</div>

Итак, сегодня мы представим вам Zen Coding, набор инструментов для высокоскоростной верстки. Изначально способ был предложен Вадимом Макеевым в апреле 2009, реализовывался автором этой статьи на протяжении нескольких месяцев, и наконец готов к использованию. Zen Coding состоит из 2 компонентов: заменитель аббревиатур(аббревиатура — селекторы, схожие с CSS-селекторами) и контекстно-независимые пары HTML тэгов. Взгляните на демонстрационное видео и убедитесь как все просто.
Если у вас нет желания читать подробные инструкции и примеры использования, взгляните на демо и скачайте плагин
Замена аббревиатуры
Функция замены аббревиатуры на лету трансформирует похожие на CSS селекторы в готовый XHTML кода. Возможно, термин «аббревиатура» может быть немного непонятным. Почему нельзя просто использовать выражение «CSS селектор»? На это есть 2 причины: первая, семантическая, селектор означает выбор чего-либо, здесь же мы генерируем, с помощью написания короткой конструкции, отображающей большой код. Вторая причина, поддержка лишь небольшой части CSS синтаксиса, плюс введение новых операторов.

Ниже приведен список, поддерживаем свойств и операторов:
E
Название элемента (div, p);
E#id
Элемент с идентификатором (div#content, p#intro, span#error)
E.class
Элемент с классом (div.header, p.error.critial). Можно комбинировать с идентификатором, например: div#content.column.width;
E>N
Дочерний элемент (div>p, div#footer>p>span);
E+N
Вложенный элемент (h1+p, div#header+div#content+div#footer);
E*N
Определенное количество элементов (ul#nav>li*5>a);
E$*N
Нумерация элементов (ul#nav>li.item-$*5);

Итак, вы уже имеете представление как применять Zen Coding, просто напишите подобные CSS селекторы(«аббревиатуры)…
div#header>img.logo+ul#nav>li*4>a
и вызовите функцию Замены Аббревиатуры (Expand Abbreviation).

Как вы уже поняли, используется два оригинальных оператора: нумерация и генерация необходимого количества элементов. Например, если вам требуется получить 5 тэгов, необходимо написать li*5. Также подобная конструкция позволяет повторять и вложенные элементы. Если вам необходимо 4 элемента с вложенными тегами, пишем li*4>a и получаем код:
<li><a href=»"></a></li>
<li><a href=""></a></li>
<li><a href=""></a></li>
<li><a href=""></a></li>

Нумерация используется если необходимо выделить повторяющейся элемент с присвоением уникального индекса. Предположим требуется конструкция из трех тэгов c классами item1,item2,item3. Все что вам нужно это написать аббревиатуру div.item$*3
<div class=«item1»></div>
<div class=«item2»></div>
<div class=«item3»></div>
Просто добавьте знак $ для свойства класса или идентификатора, которое вы хотите сделать уникальным. Как здесь…
div#i$-test.class$$$*5
и получаем:
<div id=«i1-test» class=«class111»></div>
<div id=«i2-test» class=«class222»></div>
<div id=«i3-test» class=«class333»></div>
<div id=«i4-test» class=«class444»></div>
<div id=«i5-test» class=«class555»></div>
Как вы уже догадались, аббревиатура «a» генерирует <a href=""></a>. Или «img»=<img src="" alt="" />

Каким образом Zen Coding узнает о необходимости аттрибутов по умолчания для генерации тэга или о том, что закрывающий тэг не нужен? Все просто, в файле zen_settings.js описаны элементы, которые должны получиться в результате, файл представляем собой простой JSON файл, описывающий аббревиатуры для каждого конкретного языка (да, у вас есть возможность определять свои аббревиатуры для синтаксиса различных языков (HTML, XSL, CSS и т.д.). В общем случае определение собственной аббревиатуры выглядит так:
'html': {
'snippets': {
'cc:ie6': '<!--[if lte IE 6]>\n\t${child}|\n<![endif]-->',
...
},

'abbreviations': {
'a': '<href="">
',
'img': '<img src="" alt="" />',

}
}

Типы элементов


Zen Coding поддерживает два главных типа элементов: «сниппеты» и «аббревиатуры». Сниппеты-произвольные фрагменты кода, а аббревиатуры-определения тэгов. С помощью сниппетов можно написать абсолютно любой код, и он будет выведен как есть. Но потребуется форматирование текста вручную(например используя \n и \t для генерации переноса строки и табуляции) и использовать ${child} переменную, там где вы желаете получить дочерние элементы, например, cc:ie6>style. Если вы не включите ${child} переменную, дочерний элемент будет сгенерирован после сниппета.

Благодаря аббревиатурам, вы можете описать определения тэгов, при этом очень важен правильный синтаксис. Обычно, мы пишем простой тэг с необходимым аттрибутами, вроде этого:. При использовании Zen Coding, определение тэга помещается в специальный объект, описывающий тэг, аттрибуты и при необходимости пустой тэг. Если вы наберете , Zen Coding применит особые правила перед тем как вывести законченную конструкцию.

И для сниппетов, и для аббревиатур можно добавить символ "|", определяющий положение курсора, после обработки конструкции. По умолчанию, Zen Coding, ставит курсор между кавычек в пустых атрибутах и между открывающим и закрывающим тэгом.
Пример

Приведу пример, что происходит если набрать аббревиатуру и вызвать функцию обработки. Для начала, аббревиатура разбивается на отдельные элементы: так div>a будет разделено на «div» и «a» тэги с сохранением необходимой вложенности. Далее для каждого элемента парсер ищет определение в сниппетах и потом внутри аббревиатуры. Если же определение конструкции не найдено используется название элемента как название нового тэга, применяя ID и class аттрибуты. Например, если написать mytag#example, парсер не найдет определение mytag и сгенерирует Была проделана большая работа по написанию стандартных CSS и HTML аббревиатур и снипетов. Изучив их, вы повысите свою продуктивность при использовании Zen Coding.

HTML поиск пар


Еще одно часто используемое задание для HTML верстальщика — поиск пары тэгов для элемента. Например, вы хотите выделить содержимое переместить его в другое место или просто напросто удалить. Или вы ищете закрывающий тэг или хотите знать какой открывающий тэг соответствует закрытому.

К сожалению множество современных инструментов для разработчиков не включают поддержу этого свойства. И, я решил написать свой собственный поиск соответствий тэгов как часть Zen Coding. На данном этапе это все лишь бета версия и имеет некоторые недостатки, но работает довольно хорошо и быстро. Вместо просмотра полного документа ищется релевантный тэг исходя из позиции курсора. Таким образом достигается сокрость и контексто-независимость. И работает даже с сниппетом в виде JavaScript кода

var table = '<table>';
for (var i = 0; i < 3; i++) {
table += '

"Обертка" аббревиатурами


Довольно удобная возможность, комбинирующая мощь замены аббревиатуры(Abbreviation Expander) в сочетании с парными тэгами. Довольно часто приходится добавлять оборачивающий элемент для обхода ошибки браузера. Или, например, вам необходимо небольшое украшение, вроде фонового изображения или рамки, для содержимого блока. Вам приходилось писать открывающий тэг, временно нарушающий целостность кода, искать необходимо место и только потом закрывать тэг. И тут удобно воспользоваться функцией “Wrap with Abbreviation”

Пользоватся ей довольно просто, необходимо набрать аббреиватуру, к которой будет применяться обычная функция Expand Abbreviation и текст появится внутри последнего элемента выражения. Так же Zen Coding учитывает положение курсора: внутри содержимого тега или внутри открывающего и закрывающего тэга. Исходя из этого, оборачивается содержимое тэга или же сам тэг.

Обертка аббревиатур использует особый синтаксис для обертки одиночных строк. Просто опустите переменную нумерации после вызова оператора для вывода определенного количества элементов. Когда Zen Coding встречает элемент с неопределенным количеством повторений элемент выводится столько раз, сколько выбрано строк,вкладывая контент каждой строки внутрь последнего вложенного элемента.

Если вы обернете аббревиатурой div#header>ul#navigation>li.item$*>a>span текст, подобный этому...
About Us
Products
News
Blog
Contact Up

то на выходе получите
<div id="header">
<ul id="navigation">
<li class="item1">About Us

<li class="item2">Products

<li class=«item3»>News
<li class=«item4»>Blog
<li class=«item5»>Contact Up
</ul>
</div>
Как вы уже убедились, Zen Coding довольно мощный инструмент для обработки текста

Комбинации клавиш


Ctrl+,
Обработать аббревиатуру
Ctrl+M
Найти пару для тэга(открывающий или закрывающий тэг)
Ctrl+H
Обернуть с аббревиатурой
Shift+Ctrl+M
Объединить строки
Ctrl+Shift+?
Последнее место редактирования
Ctrl+Shift+?
Следующее место редактирования
Ctrl+Shift+?
Перейти к подходящей паре тэгов

Онлайн демо


Итак, вы узнали о принципах работы Zen Coding и как можно облегчить вашу работу. Почему бы не попробовать прямо сейчас? Ведь Zen Coding написан на чистом JavaScripte и портирован на Python, таким образом работает в браузере, и довольно просто интегрируется в CMS

Поддерживаемые редакторы


Zen Coding нельзя интегрировать в каждый редактор. Это компонент, работающий с текстом: берет текст, что то делает и возвращает новый текст. Написан на JavaScript и Python, и может быть запущен виртуально на любой платформе. В Windows вы можете запустить JavaScript версию Widnows Scripting Host. Все современные Mac'и и Линукс дистрибутивы включают в состав Python

Чтобы ваш редактор поддерживал Zen Coding, необходимо написать плагин, способный обмениваться данными между редактором и Zen Coding. Редактор может не полностью поддерживать Zen Coding ввиду особенностей собственной системы плагинов.

Полная поддержка


* Aptana (кроссплатформенный);
* Coda, через TEA for Coda (Mac);
* Espresso, через TEA for Espresso (Mac);

Частичная поддержка (Только ”Expand Abbreviation”)


* TextMate (Mac, возможно использование E-text editor для Windows);
* TopStyle;
* Sublime Text;
* GEdit;
* editArea online editor;

Заключение


Многие люди, попробовавшие Zen Coding говорят, что изменили свои методы создания веб-страниц. Впереди еще много работы, реализовать поддержку многих редакторов, и написание документации. На данный момент вы можете посмотреть уже написаную документацию и исходные коды, чтобы найти ответы на ваши вопросы. Надеюсь вам понравится работы с Zen Coding!

Удалённая компиляция TeX

Возникла в кои-то веки необходимость поработать с LaTeX.
Вариант оторваться от eeePc и дивана в пользу стационарного компа не рассматривается в силу непреодолимых обстоятельств. Лень, если яснее выражаться.

Первый позыв — ставим texlive из репозитория и работаем.

$ sudo aptitude install texlive texlive-lang-cyrillic
...
Необходимо получить 128MБ архивов. После распаковки 272MБ будет занято.


Да ну, нафиг. Жалко место, системный раздел ощутимо нерезиновый.

Значит, надо удалённо компилировать TeX в pdf, посещает меня гениальная мысль. Благо ssh между Старшим и Младшим братьями навострён довольно давно, с ключевой авторизацией и всеми делами.

Читать дальше →

haXe 2.04 c поддержкой C++

Благодаря напряженной работе Hugh Sanderson haXe теперь поддерживает С++ платформу.

Это значит, что можно компилировать любую haXe программу под С++ и, таким образом, получать производительность одного из самых быстрых языков. Это открывает массу возможностей, таких например, как запуск haXe программ на iPhone.

Чтобы использовать новую возможность, нужно проставить таргет -cpp, но перед этим следует установить библиотеку hxcpp:

haxelib install hxcpp

Пишем простую программку на haXe:

class HelloWorld {
static function main() {
trace("Hello World, haXe!");
}
}


И компилируем:

haxe -cpp out -main HelloWorld

В итоге получим файл out/HelloWorld.exe

Также доступно C++ API.

C++ стал шестой платформой, поддерживаемой haXe:
  • Flash (для Flash Player версии <=8)
  • Flash9 (AVM2, для Flash Player версий>= 9)
  • Javascript
  • Neko
  • PHP
  • теперь и C++
Интересно, что будет дальше?

Микроформаты, reset.css, экскременты.

Вы не догадываетесь какая связь между этими тремя словами? Узнаете, прочитав до конца. Не торопитесь воротить нос, тут совсем не много букаФ :). Просьба к модератору: не выкидывайте эту статью, опубликуйте, пожалуйста, в песочнице. Люди, которые считают, что микроформаты и reset.css — это необходимость, посмеются над очередным «ламером» или немного задумаются. В любом случае польза будет.

Начнем с микроформатов. Цитата из википедии:

С помощью содержания атрибута class, можно передать необходимый смысл, которым наполнен данный элемент. Таким образом, люди приходят к соглашению об использовании определённых имён атрибутов для того, чтобы облегчить задачу друг другу и себе остальным в обработке такой разметки машинными средствами.


Другими словами ленивые программисты придумали микроформаты, которые HTML-верстальщику говорят как он должен верстать. А им (программистам) потом не придется ломать голову как это всё распарсить для нужд программы. Проведем аналогию:
ГИБДД ввело новые правила, по которым любители быстрой езды обязаны ездить на автомобилях красного цвета, женщины — на машинах розового цвета, водители с замедленной реакцией — желтый цвет, пьяницы — синий и т.д. Ездить нужно строго группами. Впереди и сзади каждой группы должен ехать грузовик с плакатом, например «Мы выпили бутылку пива».
Вас не тошнит от таких правил?

Продолжить?
Читать дальше →

Еще один пример использования WPF или заменяем MessageBox

В данной статье люди знакомые c технологией WPF не найдут ничего нового. Это всего лишь краткий обзор того, чем еще можно порадовать себя, используя данную технологию.
Все мы знаем как иногда раздражают всякого рода MessageBox-ы, они всегда вылетают как раз не к стати, сбивают нас с мысли посторонним звуком, да и выглядят ужасно. Ведь уже никто не читает что на них написано.
В этой статье я покажу вам пример того, как можно создать менее раздражающую замену стандартному MessageBox-у, используя технологию Windows Presentation Foundation.
Читать дальше →

Методика безопасного обновления баз данных

Здравствуйте, уважаемые хабралюди!

Порывшись в сети и в частности на Хабре не обнаружил ни одной темы как безболезненно и главное с 99% успеха обновлять базы данных.

Всем кто работал с БД известно, что основная проблема при обновлении БД — это зависимости между объектами.
Начинаешь удалять(пересоздавать процедуру), а у нее куча ссылок и без устранения зависимостей ничего не получится.
К тому же необходимо еще правильно написать скрипт обновления БД, и проверить его раз семь прежде чем отправлять клиенту, иначе не трудно догадаться что может случиться с его базой :) Плюс ко всему вряд ли кто пишет скрипты обновления с любой версии на последнюю. Как правило это целый ряд скриптов которые необходимо запускать в определенной последовательности.

Для того чтобы решить эти проблемы необходимо применять следующую методику:

1. Удалить все объекты(кроме таблиц)
2. Выполнить действия по обновлению структуры стаблиц, данных
3. Пересоздать все объекты(уже новой версии)

Данная методика как раз позволяет обновить БД с любой версии до последней, но возникает вопрос: как безопасно удалить все объекты?
Можно конечно написать скрипт который будет учитывать связи между объектами и удалять их в нужной последовательности. Такой подход трудоемок и при модификации БД требует корректировки скрипта удаления объектов, к тому же существует проблема с удалением рекуррентных процедур.
Лучше и быстрее избавляться от связей в процедурах пересоздав их с теми же входными и выходными параметрами, но с пустым телом. При этом зависимости пропадут сами собой.
Вот пример скрипта который как раз этим и занимается(писался и тестировался для FireBird 2.0 и выше)

Блок 1 — Пересоздаем все процедуры с пустым телом
execute block
as
declare variable lPROC_NAME varchar(31);
declare variable lPARAM_NAME varchar(31);
declare variable lPARAM_NUMBER smallint;
declare variable lPARAM_COUNT_INP smallint;
declare variable lPARAM_COUNT_OUT smallint;
declare variable lPARAM_TYPE smallint;
declare variable lTEXT varchar(5000);
declare variable lTEXT_INP varchar(2500);
declare variable lTEXT_OUT varchar(2500);
declare variable lSUB_TEXT varchar(50) = ' as begin end' ;
declare variable lFIELD_SOURCE varchar(31);
declare variable lVARIABLE varchar(100);
begin
for select P.RDB$PROCEDURE_NAME, P.RDB$PROCEDURE_INPUTS, P.RDB$PROCEDURE_OUTPUTS from RDB$PROCEDURES P
into :lPROC_NAME, :lPARAM_COUNT_INP, :lPARAM_COUNT_OUT
do
begin
lTEXT = 'create or alter procedure '||trim(:lPROC_NAME);
lTEXT_INP = '';
lTEXT_OUT = '';
if (coalesce(lPARAM_COUNT_INP,0)>0) then
lTEXT = lTEXT||'(';
if (coalesce(lPARAM_COUNT_OUT,0)>0) then
lTEXT_OUT = 'returns( ';
for
select PP.RDB$PARAMETER_NAME, PP.RDB$PARAMETER_NUMBER,
PP.RDB$PARAMETER_TYPE, PP.RDB$FIELD_SOURCE
from RDB$PROCEDURE_PARAMETERS PP
where PP.RDB$PROCEDURE_NAME = trim(:lPROC_NAME)
order by PP.RDB$PARAMETER_NUMBER, PP.RDB$PARAMETER_TYPE
into :lPARAM_NAME, :lPARAM_NUMBER,
:lPARAM_TYPE, :lFIELD_SOURCE
do
begin
lPARAM_NAME = trim(lPARAM_NAME);
lFIELD_SOURCE = trim(lFIELD_SOURCE);
select
case
when F.RDB$FIELD_TYPE = 261 then :lPARAM_NAME||' blob sub_type '||F.RDB$FIELD_SUB_TYPE||' segment size '||F.RDB$SEGMENT_LENGTH
when F.RDB$FIELD_TYPE = 14 then :lPARAM_NAME||' char('||F.RDB$FIELD_LENGTH||')'
when F.RDB$FIELD_TYPE = 40 then :lPARAM_NAME||' cstring('||F.RDB$FIELD_LENGTH||')'
when F.RDB$FIELD_TYPE = 11 then :lPARAM_NAME||' dfloat'
when F.RDB$FIELD_TYPE = 27 then :lPARAM_NAME||' double'
when F.RDB$FIELD_TYPE = 10 then :lPARAM_NAME||' float'
when F.RDB$FIELD_TYPE = 16 and coalesce(F.RDB$FIELD_SCALE,0) = 0 then :lPARAM_NAME||' bigint'
when F.RDB$FIELD_TYPE = 8 and coalesce(F.RDB$FIELD_SCALE,0) = 0 then :lPARAM_NAME||' integer'
when F.RDB$FIELD_TYPE = 7 and coalesce(F.RDB$FIELD_SCALE,0) = 0 then :lPARAM_NAME||' smallint'
when F.RDB$FIELD_TYPE = 9 then :lPARAM_NAME||' quad'
when F.RDB$FIELD_TYPE = 12 then :lPARAM_NAME||' date'
when F.RDB$FIELD_TYPE = 13 then :lPARAM_NAME||' time'
when F.RDB$FIELD_TYPE = 35 then :lPARAM_NAME||' timestamp'
when F.RDB$FIELD_TYPE = 37 then :lPARAM_NAME||' varchar('||F.RDB$FIELD_LENGTH||')'
when coalesce(F.RDB$FIELD_SCALE,0) <> 0 then :lPARAM_NAME||' numeric('||F.RDB$FIELD_PRECISION||','||iif(F.RDB$FIELD_SCALE<0, -F.RDB$FIELD_SCALE, F.RDB$FIELD_SCALE)||')'
end
from RDB$FIELDS F
where F.RDB$FIELD_NAME = :lFIELD_SOURCE
into :lVARIABLE;
if (lPARAM_TYPE = 0) then --входные параметры
begin
lTEXT_INP = lTEXT_INP||' '||lVARIABLE||',';
if (lPARAM_NUMBER = lPARAM_COUNT_INP - 1) then
lTEXT_INP = substring(lTEXT_INP from 1 for char_length(lTEXT_INP) - 1)||')';
end
else --выходные переметры
begin
lTEXT_OUT = lTEXT_OUT||' '||lVARIABLE||',';
if (lPARAM_NUMBER = lPARAM_COUNT_OUT - 1) then
lTEXT_OUT = substring(lTEXT_OUT from 1 for char_length(lTEXT_OUT) - 1)||')';
end
end
lTEXT = lTEXT||coalesce(lTEXT_INP,'')||' '||coalesce(lTEXT_OUT,'')||lSUB_TEXT;
execute statement lTEXT;
end
end;


Блок 2 — Удаляем все процедуры

execute block
as
declare variable lPROC_NAME varchar(31);
begin
for select P.RDB$PROCEDURE_NAME from RDB$PROCEDURES P
into :lPROC_NAME
do
execute statement 'drop procedure '||:lPROC_NAME;
end;

Все остальные объекты удаляются аналогично как в блоке 2.
Дальше нам необходим набор из скриптов(например пересоздения ВСЕХ представлений, внешних функций и процедур) в которых будет храниться актуальная на данный момент информация.
Запускаем их и получаем БД самой последней версии.
P.S. Все скрипты тестились неоднократно и прекрасно работают на FireBird, немного поработать напильником и можно адаптировать под любую СУБД.

Спасибо за внимание.

Реализация Ajax в ASP.NET Web Form

Привет всем!

Хочу рассказать о том как передать и обработать информацию на сервере не перегружая страницу. В ASP.NET уже существуют такие инструменты в виде UpdatePanel и т.д, которые дают большие возможности, причем не имея абсолютно никаких знаний. Достаточно поиграться с этими инструментами полчаса и вуаля, готово! Но как показал мне горький опыт вместо того чтобы ускорить работу, все стало еще больше тормозить (использовался дешевый windows хостинг).

Итак начнем.

идем сюда и качаем библиотеку jquery (можно использовать и другие фреймворки, по вкусу)

создаем новый сайт и самую простую форму
<input id="some_text" type="text" /><input id="some_button" type="button" value="OK" />
<div id="some_div"></div>



не забываем подключить библиотеку jquery
<script src="jquery-1.3.2.js" type="text/javascript"></script>


теперь сам клиентский код который сначала отправит данные на сервер а потом их получит
<script language="javascript">
  
   $(document).ready(function() {
    $('#some_button').click(function() {
    send();
    
   });
});

function send()
{
$.get("some_handler.ashx", { text: $("#some_text").val()},
 function(data){
  $("#some_div").html(data);
 });
}
  </script>




с помощью метода GET мы отправляем данные с текстового поля в some_handler.ashx.
  1. using System;
  2. using System.Web;
  3.  
  4. public class some_handler : IHttpHandler {
  5.   
  6.   public void ProcessRequest (HttpContext context) {
  7.    
  8.     string somecomment = context.Request["text"];
  9.     context.Response.Write(some_class.some_method(somecomment));
  10.   }
  11.   public bool IsReusable {
  12.     get {
  13.       return false;
  14.     }
  15.   }
  16.  
  17. }



Дальше нужно создать класс some_class со статическим методом some_method который будет обрабатывать полученные данные
  1. public class some_class
  2. {
  3.   public static string some_method(string some_string)
  4.   {
  5.     some_string = "Привет <b>" + some_string + "</b> с сервера!!!";
  6.     
  7.     return some_string;
  8.   }
  9. }



Смотрим что получилось:
image

Pivot — как установить и заставить работать работать на не EN-US windows

Многие прочитали пост Pivot — инновационный путь работы с данными, некоторые запросили код, который присылают достаточно быстро (за несколько часов). Но при попытке установить программу на русскую версию windows начинаются проблемы.
image
Ошибка которая возникает на стадии установки
Решается все довольно просто
Заходим в панель управления -> язык и региональные стандарты
image
рис.1 выбираем формат Английский(США)
image
рис.2 далее в расположении ставим США
image
рис.3 и наконец меняем язык системы на Английский (США)

Перезагружаемся!

Это все нужно для того чтобы установщик сформировал правильный POST-запрос
Action 8:59:52: ValidateKeyCode.
Action start 8:59:52: ValidateKeyCode.
Custom Action Log: Validating keycode 8180 E827 9D04 C529
MSI (c) (98!B8) [08:59:52:951]: PROPERTY CHANGE: Modifying ValidationResult property. Its current value is '0'. Its new value: '-1'.
Custom Action Log: In ValidateKeyCodeHelper, decrementKey is False, KeyCode is 8180 E827 9D04 C529
Custom Action Log: CleanKeyCode result is 8180E8279D04C529
Custom Action Log: IsProperlyFormated result is True
Custom Action Log: In ValidateKeyCodeHelper, KeyCode property formatted
Custom Action Log: In ValidateKeyCodeWithServer, decrementKey is False, KeyCode is 8180E8279D04C529
Custom Action Log: In ValidateKeyCodeWithServer, httpRequest created
Custom Action Log: In ValidateKeyCodeWithServer, postData is {"keyCode":"8180E8279D04C529","decrementInstallCount":"false"}
Custom Action Log: In ValidateKeyCodeWithServer, validationUrl is https://www.getpivot.com/Activation/KeycodeService.svc/ValidatePivotKeyCode
Custom Action Log: In ValidateKeyCodeWithServer, request complete
Custom Action Log: In ValidateKeyCodeWithServer, request successful, response was 1
Custom Action Log: ValidateKeyCodeWithServer, result is 1
Custom Action Log: ValidateKeyCodeHelper result is 1
MSI (c) (98!B8) [08:59:54:141]: PROPERTY CHANGE: Modifying ValidationResult property. Its current value is '-1'. Its new value: '1'.
Custom Action Log: Validation complete, result is 1
Action ended 8:59:54: ValidateKeyCode. Return value 1.

Так выглядит кусок лога который можно получить командой msiexec /i ваш_пакет.msi /lv* log.txt KEYCODEDEBUG=1, на месте false не должно быть «ложь», «faux» и т.д.

После того как вы установите Pivot, вам захочется вернуть настройки назад, но когда вы это сделаете Pivot перестанет запускаться. Для того чтобы вернуть его к жизни выставьте формат «Английский(США)» как на рис.1

Официальная позиция (перевод)


… Я согласен что данное решение подходит далеко не всем пользователям. Мы выясняем сколько пользователей заинтересовано в многоязычной версии. В текущем релизе это просто ограничение в программе… proof

Так что качайте, устанавливайте и создавайте треды с просьбой локализовать pivot.

p.s. код из лога можно использовать еще раз 9

Admin Runner — решаем проблему «запуска от имени» в IE7, IE8

Приветствую! Я тружусь администратором на благо нашей родины в крупном государственном медицинском учреждении.
После появления у нас на работе компьютеров с IE7 и IE8, мы, как и многие столкнулись с проблемой запуска эксплорера и нужных оснасток от имени администратора. Классический runas перестал работать =( Т.е. при запуске из командной строки — не открывается вообще ничего, а при запуске Internet Explorer через контекстное меню «Запуск от имени...» открывается только сам IE. В результате, чтобы выполнить какие-либо административные действия, приходится завершать сеанс и залогиниваться под администратором. Что долго и неудобно.

На помощь мне пришла простая и очень удобная программка AdminRunner, написанная моим знакомым Крюковым Алексеем Анатольевичем.
С его позволения расскажу и покажу как она работает.
Читать дальше →

Postgresql replication.

Тихо и незаметно подкралась ко мне задача оптимизировать работу своих серверов. Долго я её откладывал и совсем не хотел ей заниматься, НО настало время когда нужно было что то делать. Итак первым делом мы в нашем отделе решили, что наверное как то неразумно использовать на каждом своём сервере Postgresql сервер, и решили вынести всё на отдельную мощную машинку.
Итак заказали мы машинку, долго её ждали, и жутко обрадовались когда эта самая машинка поселилась в нашей серверной. Поставили мы на неё «базу» и начали тестировать развернув по копии базы каждого из имеющихся у нас проектов. Вроде всё работало положительно, НО тут образовался вопрос: а что делать если вдруг машинка упадёт ?!
После долгих споров было решено для резервирования приобрести еще один сервер. И опять мы ждали, пока не пропишется у нас в серверной и вторая машинка. И вот это случилось!
Теперь нужно было решить каким способом мы будем эти самые данные резервировать, да еще и так чтобы данные на одном сервере были всегда актуальны, а именно чтобы в любой момент времени данные на обеих машинах были абсолютно идентичны. Порылись в интернете и нашли 3 живых проекта, реализующих репликацию postgres.
Итак первый из них был Pgpool. Поставить и сконфигурировать его особого труда не составило. Но сразу не понравилось что pgpool не поддерживает md5 шифрование в режиме репликации — это сразу очень сильно покоробило. Да и работал он всё таки не так как нам бы хотелось на тот момент. И было решено отказаться от данного решения.
Вторым способом был избран Slony-I из за большого количества доступной документации. Хотя сразу нам не понравилось что всё как то сложно и геморно. Но ничего, нам не привыкать. Поставили настроили, заработало! И думали что вот оно решение которое мы так долго искали, даже уже написали какие то скрипты для автоматического добавления баз данных и таблиц в реплецируемый кластер, ан нет и тут оказалось не всё так гладко. Во первых когда пришло время обновления одного из проектов, мы в ужасе узнали что slony-I не умеет DDL, а при обновлении проекта как раз требовалось создать новые колонки в одной из таблиц. Ну что ж, подумал я, значит проведем изменения в базе сначала на мастер сервере, а потом уже и на слэйве и всё у нас будет реплецироваться замечательно. Ан нет, не тут то было! Оказывается Slon-I при старте репликации успешно заблокировал таблицы на slave сервере и проведя успешные изменения таблицы на мастер сервере, на слэйве нам это сделать не удалось, после этого было найдено решение: slony.info/documentation/ddlchanges.html. Но нам показалось что использовать его постоянно( а обновления у нас процедура довольно частая ) это слишком неудобно и не комфортно. И тут было принято решение, которого я сильно опасался по причине отсутствия на тот момент хоть мало мальски внятной документации у проекта. А именно решено было попробовать skytools(londiste) от Skype.
Сейчас уже можно сказать, что зря я наверно тогда так сильно волновался по поводу Londiste. Всё оказалось очень просто, а что, наверное, самое главное работоспособно и надёжно. Итак установка:
Сразу скажу что мы используем CentOs на своих серверах. Сначала необходимо всё правильно поставить. SkyTools есть в репозиториях постгреса для CentOs, но проблема заключается что в этом пакете не включены модули дополнительные, а именно Pgq_LowLevel. Поэтому сделал следующее:
Скачал последнюю версию skytools с сайта, сконфигурил, скомпилировал, и взял оттуда лишь модули для питона Pgq_LowLevel, затем поставил пакет( благо версии пакета в репозитории и модулей совпали) и скопировал модули pgq_lowlevel.so, pgq_triggers.so, logtriga.so в /usr/lib/pgsql/.
Всё дальше можно переходить к репликации.
Теперь все нижеследующие действия будут проходить от пользователя postgres и только на master сервере, но нужно учесть, что на слэйв сервер был доступ для пользователя владеющего базой данных, то есть должна выполняться команда:
psql -h slave.server.r -U your_user -d your_database

Теперь нужно установить pgqadmin. Создаём любой файл следующего содержания:
[pgqadm]
job_name = your_cluster_name
db = dbname=your_database_name

# how often to run maintenance [seconds]
maint_delay = 600

# how often to check for activity [seconds]
loop_delay = 0.1

logfile = ~/log/%(job_name)s.log
pidfile = ~/pid/%(job_name)s.pid

в моём случае это файл /etc/pgq_database_name.
Дальше «проинсталлируем» pgq для каждой конкретной базы.
$ pgqadm.py /etc/pgq_database_name install

Pапускаем pgqadm в режиме демона
$ pgqadm.py -d /etc/pgq_database_name

Создаём очередь и регистрируем консьюмер для каждой базы.
$ pgqadm.py create your_queue
$ pgqadm.py /ect/pgq_databse_name register your_queue your_consumer

Теперь когда создана очередь, мы можем начать нашу репликацию. Создаём файл /etc/londiste_your_cluster_name.ini вида:
[londiste]
job_name = your_cluster_name

provider_db = dbname=your_database
subscriber_db = dbname=your_database port=5432 host=slave.server.r user=files_testing password=your_password

# it will be used as sql ident so no dots/spaces
pgq_queue_name = your_queue

logfile = /tmp/%(job_name)s.log
pidfile = /tmp/%(job_name)s.pid

Когда файл создан запускаем londiste:
londiste.py /etc/londiste_your_cluster_name.ini provider install
londiste.py /etc/londiste_your_cluster_name.ini subscriber install

Добавляем таблицы для репликации и запустим репликацию:
londiste.py /etc/londiste_your_cluster_name.ini provider add table1 table2 table3
londiste.py /etc/londiste_your_cluster_name.ini subscriber add table1 table2 table3
londiste.py -d /etc/londiste_your_cluster_name.ini replay

Так же нужно заметить что у каждой реплицируемой таблицы должен быть Primary Key.
Вот собственно и всё. Теперь Оставалось протестировать всё это. Собственно DDL londiste так же как и Slony-I тоже не умеет, зато при репликации он не блокирует таблицы на Slave сервере и мы можем спокойно производить изменения таблиц на как на слэйве так и на мастер сервере. Был написан сценарий для capistrano(с его помощью у нас выкатываются все последние изменения и апдейты ) который проводит изменения на 2 хостах сразу. Работает всё это очень быстро. Крайне удобно всем этим управлять благодаря средствам Londiste. Добавить например таблицу или же удалить её из репликации труда особого не составляет.
После всего написал плагины для нагиоса для мониторинга репликации. Вернее даже сказать что они просто дёргают сначала из одной, а потом из другой базы количество записей в одной из таблиц и сравнивают их. Так же монитором наличие процесса londiste.py replay но это всё уже просто на всякий случай, пербоев и прерываний репликации к счастью пока не было, надеюсь не будет и впредь.
После успешного тестирования данной системы, потизоньку перетащили все базы с локальных хостов на один выделенный сервер, что принесло весьма положительный эффект и сейчас очень этим довольны.

P.S. Не ругайте сильно, если что не так, это моя первая статья на хабре.

Admin Runner — Решаем проблему «запуска от имени» в IE7, IE8

Приветствую! Я тружусь администратором на благо нашей родины в крупном государственном медицинском учреждении.
После появления у нас на работе компьютеров с IE7 и IE8, мы, как и многие столкнулись с проблемой запуска эксплорера и нужных оснасток от имени администратора. Классический runas перестал работать =( Т.е. при запуске из командной строки — не открывается вообще ничего, а при запуске Internet Explorer через контекстное меню «Запуск от имени...» открывается только сам IE. В результате, чтобы выполнить какие-либо административные действия, приходится завершать сеанс и залогиниваться под администратором. Что долго и неудобно.

На помощь мне пришла простая и очень удобная программка AdminRunner, написанная моим знакомым Крюковым Алексеем Анатольевичем.
С его позволения расскажу и покажу как она работает.
Читать дальше →