SSH, ограничение домашним каталогом
Проще говоря, chroot.
Имеем сервер FreeBSD 7.2-RELEASE
Нужно дать ssh человеку, но не хочется, чтоб он видел лишнее. Поэтому копаем Гугль.
Из сотен похожих, находим один, который работает и отлично описан.
Из дополнительных действий – это лишь копирование bash, который у меня в качестве шела юзеру.
На всякий случай, продублирую сам скрипт
#!/bin/sh
# В скрипт нужно передавать имя пользователя для которого создаем chroot окружение
if [ "$1" = "" ] ; then
echo " Usage: $0 [ username ]"
exit
fi
USER=$1
GID=`cat /etc/master.passwd | grep "^$USER:" | cut -d ":" -f 4`
HOME=/home/$USER
# Задаем список бинарей, нужных для работы в chroot
BINS="
/bin/cat \
/bin/chmod \
/bin/cp \
/bin/csh \
/bin/date \
/bin/df \
/bin/echo \
/bin/expr \
/bin/ln \
/bin/ls \
/bin/mkdir \
/bin/mv \
/bin/ps \
/bin/pwd \
/bin/rm \
/bin/rmdir \
/bin/sh \
/usr/bin/awk \
/usr/bin/bzip2 \
/usr/bin/diff \
/usr/bin/du \
/usr/bin/ee \
/usr/bin/fetch \
/usr/bin/find \
/usr/bin/grep \
/usr/bin/gunzip \
/usr/bin/gzip \
/usr/bin/less \
/usr/bin/sed \
/usr/bin/sort \
/usr/bin/scp \
/usr/bin/ssh \
/usr/bin/tail \
/usr/bin/tar \
/usr/bin/touch \
/usr/bin/vi \
/usr/bin/uname \
/usr/bin/uptime \
/usr/local/bin/mc \
/usr/local/bin/mcedit \
/usr/local/bin/mcmfmt \
/usr/local/bin/unrar \
/usr/local/bin/unzip \
"
# Создаем структуру каталогов chroot окружения
mkdir $HOME/bin
mkdir $HOME/etc
mkdir $HOME/home
mkdir $HOME/home/$USER
mkdir $HOME/lib
mkdir $HOME/libexec
mkdir $HOME/tmp
mkdir $HOME/usr
mkdir $HOME/usr/bin
mkdir $HOME/usr/local
mkdir $HOME/usr/local/bin
mkdir $HOME/usr/local/etc
mkdir $HOME/usr/local/share
# Копируем бинарники в chroot окружение
for item in $BINS;
do
cp $item $HOME$item
done
# Определяем какие библиотеки необходимо скопировать chroot
for item in $BINS;
do
ldd $item |awk '{print $3}'|grep "." >> /tmp/libs
done
# Копируем библиотеки
for item in `cat /tmp/libs|sort|uniq`;
do
cp $item $HOME/lib/
done
# Копируем оставшиеся необходимые файлы и библиотеки
cp /etc/termcap $HOME/etc/termcap
cp /etc/resolv.conf $HOME/etc/resolv.conf
cp /etc/nsswitch.conf $HOME/etc/nsswitch.conf
cp -R /usr/local/share/mc $HOME/usr/local/share/mc
cp /libexec/ld-elf.so.1 $HOME/libexec/ld-elf.so.1
# Создадим /etc/motd для пользователя
echo "Welcome $USER" > $HOME/etc/motd
# Теперь /etc/profile для него же
echo 'export TERMCAP=/etc/termcap' > $HOME/etc/profile
echo 'export PS1="$ "' >> $HOME/etc/profile
# /etc/group тоже нужен свой
cat /etc/group | grep $GID > $HOME/etc/group
# Теперь внутри chroot создадим пользователя
cat /etc/master.passwd|grep "^$USER:" > $HOME/etc/master.passwd
pwd_mkdb -d $HOME/etc $HOME/etc/master.passwd
# Выставляем права
chown root:wheel $HOME
chmod 755 $HOME
chmod 755 $HOME
chown -R $USER:$GID $HOME/bin
chown -R $USER:$GID $HOME/etc
chown -R $USER:$GID $HOME/home
chown -R $USER:$GID $HOME/lib
chown -R $USER:$GID $HOME/libexec
chown -R $USER:$GID $HOME/tmp
chown -R $USER:$GID $HOME/usr
chmod 777 $HOME/tmp
# Убираем за собой
rm /tmp/libs
ДР
ДР моей страны. Всех поздравляю.
UPD: Обновился youtube-плагин для дешевых медиатанков. Учтено изменение отдачи контента и немного упрощен код. Доступен там же: Youtube Lite Reloaded.
Промо-код для GoDaddy, окончание 12.09.2010
gda835p -30% для .com доменов
Simple Machines Forum кросс-пост и авторизация.
Захотелось мне добавить на сайт http://maps.ck.ua систему комментариев. Да не простую, а золотую необычную. Т.е. чтобы можно было постить и просматривать комментарии и с сайта и с более удобного для просмотра большого количества тем и текста – с форума.
В качестве форума был выбран любимый SMF, 2 версии. Почему любимый – потому что симпатичный дизайн, очень, очень удобная админка и просто хорошая скорость работы.
Официального API для SMF 2.х еще нету, поэтому пришлось использовать чужие наработки в этом плане, плюс немного фантазии и чтения кода форума.
Приведенные ниже строчки фактически являются хардкодингом. Поэтому если вас воротит от этого слова, этот пост можно пропускать.
Итак, авторизация:
function login($_user,$_password ) {
require_once ("...../forum/SSI.php");
require_once("...../forum/Sources/Subs-Auth.php");
$db_prefix="ИМЯ_БД.ПРЕФИКС_ТАБЛИЦ;
$request = mysql_query("
SELECT ID_MEMBER
FROM {$db_prefix}members
WHERE member_name = '$_user'
LIMIT 1");
if ($request == false && mysql_num_rows($request) < 1)
{
mysql_free_result($request);
setLoginCookie(-3600, 0);
return "no user name";
}
$get_password_hash = mysql_query ("
SELECT passwd, ID_MEMBER, ID_GROUP, lngfile, is_activated, email_address, additional_groups, member_name, password_salt
FROM {$db_prefix}members
WHERE member_name = '$_user'
LIMIT 1");
$user_settings = mysql_fetch_assoc($get_password_hash);
$hash_input_password = sha1(strtolower($user_settings['member_name']) . un_htmlspecialchars(stripslashes($_password)));
if ($user_settings['passwd']==$hash_input_password)
{
$cookielength = 3153600;
setLoginCookie( $cookielength, $user_settings['ID_MEMBER'], sha1($user_settings['passwd'] . $user_settings['password_salt']));
} else{
setLoginCookie(-3600, 0);
return "no user";
}
return "ok";
}
Аутентификация, загрузка из куки.
function smf_authenticateUser($mode="all"){
require_once ("...../forum/SSI.php");
require_once("...../forum//Sources/Subs-Auth.php");
global $smf_connection, $smf_settings, $smf_user_info;
$ID_MEMBER = 0;
if (isset($_COOKIE[$cookiename]))
{
$_COOKIE[$cookiename] = stripslashes($_COOKIE[$cookiename]);
// Fix a security hole in PHP 4.3.9 and below...
if (preg_match('~^a:[34]:\{i:0;(i:\d{1,6}|s:[1-8]:"\d{1,8}");i:1;s:(0|40):"([a-fA-F0-9]{40})?";i:2;[id]:\d{1,14};(i:3;i:\d;)?\}$~',$_COOKIE[$cookiename]) == 1)
{
list ($ID_MEMBER, $password) = @unserialize($_COOKIE[$cookiename]);
$ID_MEMBER = !empty($ID_MEMBER) && strlen($password) > 0 ? (int) $ID_MEMBER : 0;
} else return "no cookie";
} else return "no cookie2";
//echo $ID_MEMBER;
// Don't even bother if they have no authentication data.
if ($ID_MEMBER != 0){
$request = mysql_query("
SELECT *
FROM {$db_prefix}members
WHERE ID_MEMBER = $ID_MEMBER
LIMIT 1");
// Did we find 'im? If not, junk it.
if (mysql_num_rows($request) != 0)
{
// The base settings array.
$smf_user_info = mysql_fetch_assoc($request);
if (strlen($password) == 40)
$check = sha1($smf_user_info['passwd'] . $smf_user_info['password_salt']) == $password;
else
$check = false;
// Wrong password or not activated - either way, you're going nowhere.
$ID_MEMBER = $check && ($smf_user_info['is_activated'] == 1 || $smf_user_info['is_activated'] == 11) ? $smf_user_info['id_member'] : 0;
} else {
$ID_MEMBER = 0;
mysql_free_result($request);
return "no user in mysql";
}
} else {
return "no cookie";
}
if(!empty($ID_MEMBER)){
$cookielength = 3153600;
setLoginCookie( $cookielength, $smf_user_info['id_member'], sha1($smf_user_info['passwd'] . $smf_user_info['password_salt']));
} else return "no member";
// A few things to make life easier...
$smf_user_info['id'] = &$smf_user_info['id_member'];
$smf_user_info['username'] = &$smf_user_info['member_name'];
$smf_user_info['name'] = &$smf_user_info['real_name'];
$smf_user_info['email'] = &$smf_user_info['email_address'];
$smf_user_info['messages'] = &$smf_user_info['instant_messages'];
$smf_user_info['unread_messages'] = &$smf_user_info['unread_messages'];
$smf_user_info['is_guest'] = $ID_MEMBER == 0;
$smf_user_info['is_admin'] = in_array(1, $smf_user_info['id_group']);
$smf_user_info['status']="ok";
if (empty($smf_user_info['time_format']))
$smf_user_info['time_format'] = $smf_settings['time_format'];
if($mode=="all") return $smf_user_info;
if($mode=="check") return "ok";
if($mode=="json") return json_encode($smf_user_info);
if($mode=="id") return $smf_user_info['id'];
}
Постинг в форум. Если topic пустой или равен 0, создается новая тема.
function post_to_smf($board,$topic,$subject,$txt,$oid,$otype,$maptype) {
$ID_MEMBER = smf_authenticateUser("id");
if($ID_MEMBER==0 or !is_numeric($ID_MEMBER)) return "wrong poster";
require_once("......../forum/SSI.php");
require_once("......../forum/Sources/Subs-Post.php");
global $smcFunc;
$TOPIC=$topic;
$ID_BOARD=$board;
$excerpt = stripcslashes(trim(strip_tags($txt)));
$words = preg_split("/(?<=(\.|!|\?)+)\s/", $excerpt, -1, PREG_SPLIT_NO_EMPTY);
foreach ( $words as $word )
{
$new_text = $text . substr($excerpt, 0, strpos($excerpt, $word) + strlen($word));
$excerpt = substr($excerpt, strpos($excerpt, $word) + strlen($word), strlen($excerpt));
if ( ( strlen($text) != 0 ) && ( strlen($new_text) > $max_length ) )
{
break;
}
$text = $new_text;
}
$post_text = $text;
$msgOptions = array(
'id' => 0,
'subject' => stripcslashes($subject),
'body' => $post_text,
'smileys_enabled' => true,
);
$topicOptions = array(
'id' => $TOPIC,
'board' => $ID_BOARD,
'mark_as_read' => true,
);
$posterOptions = array(
'id' => $ID_MEMBER,
);
$out=Array();
$top=createPost2($msgOptions, $topicOptions, $posterOptions);
if($top) {
$out["status"]="ok";
$out["topicid"]=$top;
return json_encode($out);
} else {
$out["status"]="error create post";
return json_encode($out);
}
}
function createPost2(&$msgOptions, &$topicOptions, &$posterOptions)
{
global $user_info, $txt, $modSettings, $smcFunc, $context;
// Set optional parameters to the default value.
$msgOptions['icon'] = empty($msgOptions['icon']) ? 'xx' : $msgOptions['icon'];
$msgOptions['smileys_enabled'] = !empty($msgOptions['smileys_enabled']);
$msgOptions['attachments'] = empty($msgOptions['attachments']) ? array() : $msgOptions['attachments'];
$msgOptions['approved'] = isset($msgOptions['approved']) ? (int) $msgOptions['approved'] : 1;
$topicOptions['id'] = empty($topicOptions['id']) ? 0 : (int) $topicOptions['id'];
$topicOptions['poll'] = isset($topicOptions['poll']) ? (int) $topicOptions['poll'] : null;
$topicOptions['lock_mode'] = isset($topicOptions['lock_mode']) ? $topicOptions['lock_mode'] : null;
$topicOptions['sticky_mode'] = isset($topicOptions['sticky_mode']) ? $topicOptions['sticky_mode'] : null;
$posterOptions['id'] = empty($posterOptions['id']) ? 0 : (int) $posterOptions['id'];
$posterOptions['ip'] = empty($posterOptions['ip']) ? $user_info['ip'] : $posterOptions['ip'];
$smcFunc['db_query']('', 'SET NAMES UTF8',null);
// We need to know if the topic is approved. If we're told that's great - if not find out.
if (!$modSettings['postmod_active'])
$topicOptions['is_approved'] = true;
elseif (!empty($topicOptions['id']) && !isset($topicOptions['is_approved']))
{
$request = $smcFunc['db_query']('', '
SELECT approved
FROM {db_prefix}topics
WHERE id_topic = {int:id_topic}
LIMIT 1',
array(
'id_topic' => $topicOptions['id'],
)
);
list ($topicOptions['is_approved']) = $smcFunc['db_fetch_row']($request);
$smcFunc['db_free_result']($request);
}
// If nothing was filled in as name/e-mail address, try the member table.
if (!isset($posterOptions['name']) || $posterOptions['name'] == '' || (empty($posterOptions['email']) && !empty($posterOptions['id'])))
{
if (empty($posterOptions['id']))
{
$posterOptions['id'] = 0;
$posterOptions['name'] = $txt['guest_title'];
$posterOptions['email'] = '';
}
elseif ($posterOptions['id'] != $user_info['id'])
{
$request = $smcFunc['db_query']('', '
SELECT member_name, email_address
FROM {db_prefix}members
WHERE id_member = {int:id_member}
LIMIT 1',
array(
'id_member' => $posterOptions['id'],
)
);
// Couldn't find the current poster?
if ($smcFunc['db_num_rows']($request) == 0)
{
trigger_error('createPost(): Invalid member id ' . $posterOptions['id'], E_USER_NOTICE);
$posterOptions['id'] = 0;
$posterOptions['name'] = $txt['guest_title'];
$posterOptions['email'] = '';
}
else
list ($posterOptions['name'], $posterOptions['email']) = $smcFunc['db_fetch_row']($request);
$smcFunc['db_free_result']($request);
}
else
{
$posterOptions['name'] = $user_info['name'];
$posterOptions['email'] = $user_info['email'];
}
}
// It's do or die time: forget any user aborts!
$previous_ignore_user_abort = ignore_user_abort(true);
$new_topic = empty($topicOptions['id']);
// Insert the post.
$smcFunc['db_insert']('',
'{db_prefix}messages',
array(
'id_board' => 'int', 'id_topic' => 'int', 'id_member' => 'int', 'subject' => 'string-255', 'body' => (!empty($modSettings['max_messageLength']) && $modSettings['max_messageLength'] > 65534 ? 'string-' . $modSettings['max_messageLength'] : 'string-65534'),
'poster_name' => 'string-255', 'poster_email' => 'string-255', 'poster_time' => 'int', 'poster_ip' => 'string-255',
'smileys_enabled' => 'int', 'modified_name' => 'string', 'icon' => 'string-16', 'approved' => 'int',
),
array(
$topicOptions['board'], $topicOptions['id'], $posterOptions['id'], $msgOptions['subject'], $msgOptions['body'],
$posterOptions['name'], $posterOptions['email'], time(), $posterOptions['ip'],
$msgOptions['smileys_enabled'] ? 1 : 0, '', $msgOptions['icon'], $msgOptions['approved'],
),
array('id_msg')
);
$msgOptions['id'] = $smcFunc['db_insert_id']('{db_prefix}messages', 'id_msg');
// Something went wrong creating the message...
if (empty($msgOptions['id']))
return false;
// Fix the attachments.
if (!empty($msgOptions['attachments']))
$smcFunc['db_query']('', '
UPDATE {db_prefix}attachments
SET id_msg = {int:id_msg}
WHERE id_attach IN ({array_int:attachment_list})',
array(
'attachment_list' => $msgOptions['attachments'],
'id_msg' => $msgOptions['id'],
)
);
// Insert a new topic (if the topicID was left empty.
if ($new_topic)
{
$smcFunc['db_insert']('',
'{db_prefix}topics',
array(
'id_board' => 'int', 'id_member_started' => 'int', 'id_member_updated' => 'int', 'id_first_msg' => 'int',
'id_last_msg' => 'int', 'locked' => 'int', 'is_sticky' => 'int', 'num_views' => 'int',
'id_poll' => 'int', 'unapproved_posts' => 'int', 'approved' => 'int',
),
array(
$topicOptions['board'], $posterOptions['id'], $posterOptions['id'], $msgOptions['id'],
$msgOptions['id'], $topicOptions['lock_mode'] === null ? 0 : $topicOptions['lock_mode'], $topicOptions['sticky_mode'] === null ? 0 : $topicOptions['sticky_mode'], 0,
$topicOptions['poll'] === null ? 0 : $topicOptions['poll'], $msgOptions['approved'] ? 0 : 1, $msgOptions['approved'],
),
array('id_topic')
);
$topicOptions['id'] = $smcFunc['db_insert_id']('{db_prefix}topics', 'id_topic');
// The topic couldn't be created for some reason.
if (empty($topicOptions['id']))
{
// We should delete the post that did work, though...
$smcFunc['db_query']('', '
DELETE FROM {db_prefix}messages
WHERE id_msg = {int:id_msg}',
array(
'id_msg' => $msgOptions['id'],
)
);
return false;
}
// Fix the message with the topic.
$smcFunc['db_query']('', '
UPDATE {db_prefix}messages
SET id_topic = {int:id_topic}
WHERE id_msg = {int:id_msg}',
array(
'id_topic' => $topicOptions['id'],
'id_msg' => $msgOptions['id'],
)
);
// There's been a new topic AND a new post today.
trackStats(array('topics' => '+', 'posts' => '+'));
updateStats('topic', true);
updateStats('subject', $topicOptions['id'], $msgOptions['subject']);
//What if we want to export new topics out to a CMS?
if (isset($modSettings['integrate_create_topic']) && is_callable($modSettings['integrate_create_topic']))
call_user_func(strpos($modSettings['integrate_create_topic'], '::') === false ? $modSettings['integrate_create_topic'] : explode('::', $modSettings['integrate_create_topic']), $msgOptions, $topicOptions, $posterOptions);
}
// The topic already exists, it only needs a little updating.
else
{
$countChange = $msgOptions['approved'] ? 'num_replies = num_replies + 1' : 'unapproved_posts = unapproved_posts + 1';
// Update the number of replies and the lock/sticky status.
$smcFunc['db_query']('', '
UPDATE {db_prefix}topics
SET
' . ($msgOptions['approved'] ? 'id_member_updated = {int:poster_id}, id_last_msg = {int:id_msg},' : '') . '
' . $countChange . ($topicOptions['lock_mode'] === null ? '' : ',
locked = {int:locked}') . ($topicOptions['sticky_mode'] === null ? '' : ',
is_sticky = {int:is_sticky}') . '
WHERE id_topic = {int:id_topic}',
array(
'poster_id' => $posterOptions['id'],
'id_msg' => $msgOptions['id'],
'locked' => $topicOptions['lock_mode'],
'is_sticky' => $topicOptions['sticky_mode'],
'id_topic' => $topicOptions['id'],
)
);
// One new post has been added today.
trackStats(array('posts' => '+'));
}
// Creating is modifying...in a way.
//!!! Why not set id_msg_modified on the insert?
$smcFunc['db_query']('', '
UPDATE {db_prefix}messages
SET id_msg_modified = {int:id_msg}
WHERE id_msg = {int:id_msg}',
array(
'id_msg' => $msgOptions['id'],
)
);
// Increase the number of posts and topics on the board.
if ($msgOptions['approved'])
$smcFunc['db_query']('', '
UPDATE {db_prefix}boards
SET num_posts = num_posts + 1' . ($new_topic ? ', num_topics = num_topics + 1' : '') . '
WHERE id_board = {int:id_board}',
array(
'id_board' => $topicOptions['board'],
)
);
else
{
$smcFunc['db_query']('', '
UPDATE {db_prefix}boards
SET unapproved_posts = unapproved_posts + 1' . ($new_topic ? ', unapproved_topics = unapproved_topics + 1' : '') . '
WHERE id_board = {int:id_board}',
array(
'id_board' => $topicOptions['board'],
)
);
// Add to the approval queue too.
$smcFunc['db_insert']('',
'{db_prefix}approval_queue',
array(
'id_msg' => 'int',
),
array(
$msgOptions['id'],
),
array()
);
}
// Mark inserted topic as read (only for the user calling this function).
if (!empty($topicOptions['mark_as_read']) && !$user_info['is_guest'])
{
// Since it's likely they *read* it before replying, let's try an UPDATE first.
if (!$new_topic)
{
$smcFunc['db_query']('', '
UPDATE {db_prefix}log_topics
SET id_msg = {int:id_msg}
WHERE id_member = {int:current_member}
AND id_topic = {int:id_topic}',
array(
'current_member' => $posterOptions['id'],
'id_msg' => $msgOptions['id'],
'id_topic' => $topicOptions['id'],
)
);
$flag = $smcFunc['db_affected_rows']() != 0;
}
if (empty($flag))
{
$smcFunc['db_insert']('ignore',
'{db_prefix}log_topics',
array('id_topic' => 'int', 'id_member' => 'int', 'id_msg' => 'int'),
array($topicOptions['id'], $posterOptions['id'], $msgOptions['id']),
array('id_topic', 'id_member')
);
}
}
// If there's a custom search index, it needs updating...
if (!empty($modSettings['search_custom_index_config']))
{
$customIndexSettings = unserialize($modSettings['search_custom_index_config']);
$inserts = array();
foreach (text2words($msgOptions['body'], $customIndexSettings['bytes_per_word'], true) as $word)
$inserts[] = array($word, $msgOptions['id']);
if (!empty($inserts))
$smcFunc['db_insert']('ignore',
'{db_prefix}log_search_words',
array('id_word' => 'int', 'id_msg' => 'int'),
$inserts,
array('id_word', 'id_msg')
);
}
// Increase the post counter for the user that created the post.
if (!empty($posterOptions['update_post_count']) && !empty($posterOptions['id']) && $msgOptions['approved'])
{
// Are you the one that happened to create this post?
if ($user_info['id'] == $posterOptions['id'])
$user_info['posts']++;
updateMemberData($posterOptions['id'], array('posts' => '+'));
}
// They've posted, so they can make the view count go up one if they really want. (this is to keep views >= replies...)
$_SESSION['last_read_topic'] = 0;
// Better safe than sorry.
if (isset($_SESSION['topicseen_cache'][$topicOptions['board']]))
$_SESSION['topicseen_cache'][$topicOptions['board']]--;
// Update all the stats so everyone knows about this new topic and message.
updateStats('message', true, $msgOptions['id']);
// Update the last message on the board assuming it's approved AND the topic is.
if ($msgOptions['approved'])
updateLastMessages($topicOptions['board'], $new_topic || !empty($topicOptions['is_approved']) ? $msgOptions['id'] : 0);
// Alright, done now... we can abort now, I guess... at least this much is done.
ignore_user_abort($previous_ignore_user_abort);
// Success.
return $topicOptions['id'];
}
тут в строчке
$ID_MEMBER = smf_authenticateUser("id");
ждет сюрпиз. Если постит не администратор, функция возвращает ошибку.
Для исправления придется лезть в файл forum/SSI.php и выше строчки
loadPermissions();
добавлять
loadBoard();
Гнусный хардкодинг :\
В работе использованы куски кода моста Mambo-SMF, куски кода самого SMF, документация к форуму.
Тук-тук
Сегодня позвонили из «Визикома«. Девушка с хорошо поставленным голосом поинтересовалась, каким образом я использую их сервер.
Врать я не стал, да и нет смысла. Тем более, что в ФАК сайта map.ck.ua указано, что я использую сервис от Визикома. Она сказала, что запросы на их сервер идут, а где виден их результат, неясно.
Пришлось провести маленькую презентацию сайта и объяснять, что Визиком (посредством XML-апи) используется для прокладки маршрутов и для поиска адресов, а результат выводится на Яндекс.Карты. Заодно упрекнул визикомовские карты в однообразии и плохой документированности АПИ.
В целом возражений не было, она только выразила пожелание, чтобы логотип Визикома более четко отображался в сервисах, в которых он участвует. После мне пожелали успехов и попрощались. Приятно!
Что характерно: Есть подобная возможность и в использовании серверов Гугля. Однако в пользовательском соглашении Google API четко указано, что вывод результатов должен производиться только на картах Гугла.
Путешествие в Буки.
Прошла почти неделя, как мы вернулись из небольшого отпуска: ездили в Буки (или, как говорят местные жители, Букы, ударение на ы).
Посетить это чудесное место я и жена планировали еще прошлым летом, но поехали на фестиваль «Свирж». В этом году звезды сошлись и мы запланировали поездку.
Мы рассчитывали выехать в пятницу утром на поезде, доехать до станции «Поташ» и оттуда, на велосипедах, доехать до Бук.
В пятницу, в 6:40 мы были на вокзале и пошли покупать билеты. Но…у нас часто бывает не так, как того хотим. Самое стабильное в этом плане – это практически каждый поход на пляж сопровождается дождем. Ну и других веселых вещей хватает.
В этот раз нам «повезло»: ремонтировали дорогу. Ближайший поезд был вечером, ждать нам не хотелось.
Поехали на автовокзал, на котором рассчитывали сесть в автобус Черкассы-Винница. У него большое багажное отделение и туда влезли бы наши велосипеды. Да, вполне ожидаемо, нас ждало известие о том, что автобус поломался :)
Решили поехать «автостопом». Проторчав час у дороги, поехали в Смелу, там более верно можно словить нужный транспорт. Через полтора часа уже махали рукой на Смелянском кольце.
Примерно час ожидания и нас подобрала Газелька, которая ехала до Шполы. Водитель, как это часто бывает, оказался умным и разговорчивым. Примерно 40км мы ехали не скучая, а велосипеды звонко побрякивали в кузове.
В Шполе мы немного поели и покрутили педали в сторону Звенигородки, периодически останавливаясь помахать рукой. Не везло, да и транспорта было мало.
Преодолев множество затяжных подъемов и основательно вспотев, остановились у Богачевки, перевести дух и искупаться в озере. После купания, только успев одеться, заметили грузовую Газель. После недолгих разговоров, водитель сжалился над нами и согласился довезти до Звенигородки.
Как оказалось, нам очень повезло. Эти недолгие 15 километров были насыщенны такими серьезными подъемами и спусками, что у нас этот путь занял бы часа 3, а то и больше. Высадили нас на окружной у поворота на Умань. Катимся на велосипедах дальше.
Едем через Водяники (те самые, в которых наш бывший губернатор забабахал горнолыжный курорт). Дорога совершенно пустынная, поэтому почти не останавливались. Делали только привалы попить воды. Отмахали километров 30, часам к восьми вечера решили остановиться и продолжить путь на следующий день.
Поставили палатку в заброшенном саду, как оказалось потом, примерно в паре километров от села Багва. Достаточно мирно поели, если не считать грома от лопнувшей покрышки проезжавшего мимо камаза с прицепом( все обошлось, покрышка лопнула на прицепе и скорость была очень маленькой, он как раз собирался поворачивать ), почитали книжки и легли спать.
Утром, часов в восемь выехали. По утренней прохладе дорога казалась ровнее и даже ветер был попутным. Спустя час-полтора увидели Буки.
Буки – знамениты своим каньоном. Конечно, это не Гранд-Каньон реки Колорадо, но для здешних мест очень прилично. Очень красивые берега, речка Горный Тикич, уточки и замечательные облака. Место можно было бы назвать живописным, если вычесть из него людей и их деятельность.
К сожалению, интернетом пользуются не только люди, у которых есть совесть. И эти самые люди, увидев в интернете красивые фотографии Букинского Каньона, едут сюда отдохнуть, оставляя после себя горы мусора среди живописной природы. Мы видим, что происходит в Крыму, где даже самые дикие на вид местности усеяны бутылками, пакетами прочей дрянью, привозимой туристами. Очень боюсь, что если не ввести жесткие меры, подобное будет и в Буках, где и так грязно и во всех других популярных местах.
Но не будем о грустном. Немного порыскав, нашли относительно не замусоренное место, и разбили там лагерь, куда вскоре подъехали наши друзья, которые добрались до Бук на утреннем автобусе.
Рассказывать купании речке и готовке пищи на костре нет смысла, это просто нужно делать :) Скажу лишь, что мы ходили с экскурсией к местым скалам, полежали под гидромассажными струями воды на порогах, попили воды из источника у переправы, исследовали недостроенную ГЭС, прошлись по обоим красивым берегам этой речки. Фото прилагаются ниже.
Друзья уехали на следующий день после обеда, мы же остались до вторника. Во вторник, без особых приключений, если не считать пару километровых подьемов, операции по спасению котенка, которого нашли в паре километров от ближайшего села (куда и доставили его), добрались до железнодорожной станции Поташ. Через 15 минут подошел поезд и спустя пять часов мы были в Черкассах.
Вот приблизительная схема нашего маршрута, дорога автомобилем не указана:
Подробнее: http://map.ck.ua/#umid=4SR5cb5
А вот немного фото (фотографировала Лена, моя жена):
В заключении хочу сказать, что это первая долгая поездка желто-черного велосипеда, которую он выдержал с честью :)
map.ck.ua
Наконец-то доделал самую нужную часть карты(??) – пользовательские карты.
У нее есть два предназначения:
1. Вы кому-то хотите показать часть карты с нанесенными обозначениями. К примеру, место проведения какого-либо мероприятия, обозначенное областями и маркерами.
2. Вы хотите создать собственную карту, например карту страйкбольных полей и разместить ее у себя на сайте.
Либо вам просто нечем заняться и вы хотите что-то нарисовать, используя небогатый арсенал map.ck.ua :). Один из таких примеров:
Ссылка: http://map.ck.ua/#umid=gkub545
Поделки
То, что велик жене нужен, мы поняли давно. Но сейчас денег на покупку нового и более-менее хорошего нету. Поэтому решили возродить старенький «Турист», который мирно гнил в подвале бывшего дома.
Фото показывать не буду, потому как нету. Скажу лишь, что смотреть на это подвальное чудо было больно. Но хитрая лесть жены, которая сказала что-то про мои золотые руки и просто интерес к процессу подвигли меня заняться очеловечиванием этого велосипеда.
Что было куплено:
- Покрышки
- Камеры
- Седло
- Крылья
- Механизм переключения передач (задний)
- Тросики и оболочки для тросиков
- Цепь
- Вынос руля
- Светоотражатели
- 3 баллона с краской (грунтовка, желтый, черный)
- Наждачная бумага
- Подшипники на каретку и рулевую колонку
Итого 500+ грн.
Почистил раму, погрунтовал. Покрасил в желтый.
Вечер мы посвятили обклеиванию рамы малярным скотчем. На следующий день покрасили черным цветом. Получился билайновский бренд :)
Ну и потом все это я собрал.
Теперь это полосатое ездит по дорогам Черкасс :) Если увидите, улыбнитесь.
Тест
Добавил на map.ck.ua возможность забрать к себе на сайт часть карты с информацией.
В принципе, основные новости касательно map.ck.ua описываются тут: word.map.ck.ua.
Но сегодняшнее обновление – это шаг, если учитывать то, что прокладка маршрутов в Яндекс-картах доступна только для московской области.
Собственно да, доступно прокладывание маршрутов проезда автомобилем. Используется Визиком.
Кусок весны и кусок лета
Прошел с момента последней записи.
Но я не с пустыми руками. За это время, вернее, в это время я работал над обновлением проекта MAP.CK.UA
Пожалуй, слово «работал» слишком громкое. Да и код, который я рождаю, по-настоящему работой назвать нельзя :) Но! Какое-то количество строк (говно)кода написано (привет Ден!), а значит нужно рассказать, что получилось.
Итак. Возможно в нарушение общепринятых правил, но сайт получил приставку Бета 2. Почему бета – понятно, почему аж 2 – потому что изменился основной движок карт.
Просто перечислю, что работает на сегодняшний день:
Движок карт: Яндекс.Карты, со всеми ихними недостатками, но все же более подробные и красивые, чем Гугльмапс. Да, GoogleMaps более функционален, более шустр. Но его унылость схематических карт и очень упрощенное изображение домов и адресов не впечатляют.
Поиск адреса: Визиком. Это одна из основных фич сайта. Карты от Яндекса красивее Визикомовских и Гуглевских, но поиск от Яндекса не годится. Можно было бы использовать АПИ и карты от Визикома, но карты Визикома почти такие же унылые как и у Гугля, плюс, и это решающий фактор, скудное АПИ и не менее скудная документация. Но! Визиком – это практически гарантия правильности адреса, поэтому взял поиск от него.
Тематические карты. Пожалуй, именно тут мапцкуа наиболее тесно подходит к концепции ВикиМапии. Но я успокоил себя тем, что: а) другой движок. б) более локализовано. На данный момент доступны для просмотра и наполнения такие карты: Провайдеры города, Хотспоты WiFi, Кинотеатры, Маршруты транспорта (Привет Серж!) Я наконец-то придумал алгоритм рисования маршрутов и остановок.
Примеры:
Маршрут на карте маршрутов: http://map.ck.ua/?maptype=bus&routename=%25E2%2584%25969
Одна из точек доступа wifi: http://map.ck.ua/?maptype=wifi&id=12
Свободное добавление объектов. Отказался от идеи регистрации, как было на старой версии. Добавлять может любой. Кто угодно и что угодно. Естественно, перед появлением на общедоступной карте, добавленные объекты будут проходить модерацию. Исключение будет для пользовательских карт (которые пока что в разработке). Возможно, в дальнейшем появится свободная регистрация, для присваивания авторства и т.п. Впрочем, ничто не мешает зарегистрироваться сейчас: word.map.ck.ua. Если я и буду использовать авторизацию, то от этого блога.
Полноэкранный, однооконный интерфейс. Минимум перегрузок сайта, идею web 2.0 поддерживаю целиком. Возможно из-за этого немного нелепо выглядят ссылки на конкретные разделы, но это поправимо. Возможно ссылки обретут другое пристанище, вместо адресбара будут отображаться на поле карты.
Ну, вроде все из того, что помню.
Критика не принимается, код-говно, я знаю. Реализация идей еще хуже. Возможно результат получился хуже, чем ничего, но есть люди, которые меня поддерживают. Спасибо им.
Если есть (совершенно случайно), замечания и предложения, прошу.













