воскресенье, 25 августа 2013 г.

Сборка deb-пакета с Zabbix 2 для Debian Wheezy

Ответ на вопрос, почему Zabbix'а нет в Debian Wheezy довольно прост - способ разработки Zabbix не соответствует правилам Debian Stable, т.к. разработчики Zabbix устраняют уязвимости программного обеспечения только в следующих версиях, не бэкпортируя заплатки в старые версии. Более подробно об этом написано тут: Why is there no Zabbix in Debian Wheezy?

Включаем в /etc/apt/sources строчку с репоизториями Jessie:
deb-src http://mirror.ufanet.ru/debian testing main contrib non-free
Обновляем репозитории, скачиваем исходники пакета, ставим необходимое для сборки и собираем пакет:
# apt-get update
# apt-get sources zabbix
# apt-get install build-essential:native automake1.9 dh-autoreconf libcurl4-gnutls-dev libgcrypt11-dev libiksemel-dev libldap2-dev libmysqlclient-dev libopenipmi-dev libpq-dev libsnmp-dev libsqlite3-dev libssh2-1-dev
# cd zabbix-2.0.6+dfsg
# dpkg-buildpackage -us -uc -rfakeroot
В каталоге выше - собранные deb-пакеты с Zabbix.

Установка Zabbix-агента:
# apt-get install libcurl3-gnutls libldap-2.4-2 ucf
Установка веб-интерфейса Zabbix вместе с веб-сервером Lighttpd и SpawnFCGI:
# apt-get install spawn-fcgi lighttpd gamin
# apt-get install ucf php5 php5-mysql php5-gd ttf-dejavu-core
После чего нужно включить модули веб-сервера lighttpd:
# lighty-enable-mod fastcgi
# lighty-enable-mod fastcgi-php
Прописать псевдонимы для каталога с PHP-файлами веб-интерфейса в новом файле /etc/lighttpd/conf-available/20-zabbix.conf:
alias.url += (
  "/zabbix/" => "/usr/share/zabbix/"
)
И включить только что созданный "модуль" Lighttpd с веб-интерфейсом Zabbix:
# lighty-enable-mod zabbix
Осталось лишь перезапустить веб-сервер:
# /etc/init.d/lighttpd restart
Установка Zabbix-сервера, использующего MySQL:
# apt-get install libcurl3-gnutls libiksemel3 libldap-2.4-2 libopenipmi0 libsnmp15 libssh2-1 ucf

О процедуре дальнейшей настройки сервера Zabbix и веб-интерфейса Zabbix можно почитать в одной из моих прошлых заметок: Установка системы мониторинга Zabbix.

P.S. Как я узнал некоторое время спустя, этот пакет уже бэкпортирован и доступен в репозитории wheezy-backports.

воскресенье, 18 августа 2013 г.

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

Перевод статьи: Writing serious Perl. The absolute minimum you need to know
Автор: Хеннинг Кох

Чрезвычайно гибкий синтаксис Perl'а позволяет легко писать код, который сложнее читать и поддерживать, чем этого хотелось бы. Эта статья описывает некоторые основополагающие практики, которые я считаю необходимыми для написания понятных и лаконичных программ на Perl.

Оглавление
Пространства имён

Один пакет никогда не должен пересекаться с пространством имён другого пакета, до тех пор, пока тот явно не попросит об этом. Поэтому, никогда не определяйте методы в другом скрипте для использования с require. Всегда оборачивайте вашу библиотеку в пакет и используйте его. Таким образом пространство имён останется чистым и отделённым:
package Sophie;
sub say_hello {
  print "Привет, Мир!";
}

package Clara;
use Sophie;          # Загрузить пакет, но НЕ импортировать какие-либо методы
say_hello();         # Не сработает
Sophie->say_hello(); # Правильное использование
Sophie::say_hello(); # Работает, кроме наследуемых методов

Корень пространства имён

Если вы используете выгруженный пакет Some::Package, Perl ищет файл Some/Package.pm в текущем каталоге. Если этот файл не существует, поиск продолжается в другом корне пространства имён (например, в c:/perl/lib) из глобального массива @INC.

Хорошая мысль сохранить пакеты вашего приложения в отдельный каталог, например в lib, и добавлять этот каталог к списку корней пространства имён с помощью use lib 'my/root/path':
use lib 'lib';     # Добавить подкаталог 'lib' к корневому пространству имён @INC
use Some::Package; # Пройтись по @INC в поисках файла пакета
Экспорт идентификаторов

Бывают случаи, когда нужно экспортировать методы или имен переменных в вызывающий пакет. Я делаю так лишь в редких случаях, когда очень-очень часто требуются статические вспомогательные методы. Для экспорта идентификаторов наследуйте от класса Exporter и заполните массив @EXPORT идентификаторов, которые хотите экспортировать:
package Util;
use base 'Exporter';
our @EXPORT = ('foo', 'bar');

sub foo {
  print "foo!";
}
sub bar {
  print "bar!";
}

package Amy;
use Util; # Импортировать символы в @EXPORT
foo();    # Работает
bar();    # Работает
Постарайтесь не засорять пространство имён другого пакета, если у вас нет для этого достаточно уважительной причины! Однако, как бы то ни было, большинство пакетов на CPAN экспортируют идентификаторы при их явном подключении.

Будет неплохо, если оставить программам возможность самим решать, какие идентификаторы нужно экспортировать в их пространство имён. Для этого можно воспользоваться массивом @EXPORT_OK или @EXPORT.
package Util;
use base 'Exporter';
our @EXPORT_OK = ('foo', 'bar');

sub foo {
  print "foo!";
}
sub bar {
  print "bar!";
}

package Amy;
use Util 'foo'; # Импортировать только foo()
foo();          # Работает
bar();          # Не сработает
Быстрое приготовление структур данных

Используйте { } для создания ссылок на анонимные хэши. Используйте [ ] для создания ссылок на анонимные массивы. Сочетайте эти конструкции для создания более сложных структур данных вроде списков из хэшей:
my @students = ( { name         => 'Clara',
                   registration => 10405,
                   grades       => [ 2, 3, 2 ] },
                 { name         => 'Amy',
                   registration => 47200,
                   grades       => [ 1, 3, 1 ] },
                 { name         => 'Deborah',
                   registration => 12022,
                   grades       => [ 4, 4, 4 ] } );
Используйте -> для получения доступа к значениям структуры данных:
# Напечатать имена всех студентов
foreach my $student (@students) {
  print $student->{name} . "\n";
}

# Напечатать вторую оценку Клары
print $students[0]->{grades}->[1];

# Удалить код регистрации Клары
delete $students[0]->{registration};
Классы и объекты

Пакеты - это классы. Объекты - это обычно ссылки на хэши, "благословлённые"* именем класса. Атрибуты - это пары ключ/значение в хэше.

Конструкторы

Конструкторы - это статические методы, которым довелась участь возвращать объекты:
package Student;

sub new {
  my($class, $name) = @_;       # Первый параметр - название класса
  my $self = { name => $name }; # Ссылка на анонимный хэш, содержащий атрибуты экземпляра
  bless($self, $class);         # Говорит: $self - это $class
  return $self;
}

package main;
use Student;
my $amy = Student->new('Amy');
print $amy->{name};             # Доступ к атрибуту
Вместо Student->new('Amy') можно также написать Student('Amy'). Однако отметим, что в этом случае Perl полагается на непредсказуемую эвристику, чтобы понять ваши истинные намерения и иногда может ошибаться.

Множественные конструкторы

Поскольку ключевое слово new не имеет особого значения в Perl, можно создавать столько методов-конструкторов, сколько захочется и называть их как захочется. Например, вам может потребоваться два разных конструктора, в зависимости от того, каким образом нужно сформировать объект - загрузив существующую запись из базы данных или создав новый экземпляр с нуля:
my $amy = Student->existing('Amy');
my $clara = Student->create();
Поскольку конструктор явным образом возвращает конструируемый объект, $self не является чем-то необычным. Например, вы можете взять $self из статического кэша уже сконструированных объектов:
package Coke;
my %CACHE;

sub new {
  my($class, $type) = @_;
  return $CACHE{$type} if $CACHE{$type}; # По возможности использовать копию из кэша
  my $self = $class->from_db($type);     # Получить его из базы данных
  $CACHE{$type} = $self;                 # Помещаем в кэш полученный объект
  return $self;
}

sub from_db {
  my($class, $type) = @_;
  my $self = ...        # Получить данные из базы данных
  bless($self, $class); # Сделать $self экземпляром $class
  return $self;
}

package main;
use Coke;

my $foo = Coke->new('Lemon');   # Получение из базы данных
my $bar = Coke->new('Vanilla'); # Получение из базы данных
my $baz = Coke->new('Lemon');   # Используется копия из кэша
Ради полноты картины я должен напомнить, что ссылки в %CACHE будут удерживать кэшированные объекты в памяти, даже если остальные экземпляры объектов перестали существовать. Поэтому, если ваши кэшированные объекты обладают методами-деструкторами, они не будут вызваны до момента завершения программы.

Методы экземпляров

Методы экземпляров получают ссылку на вызываемый объект в первом параметре:
package Student;

sub work {
  my($self) = @_;
  print "$self работает\n";
}
sub sleep {
  my($self) = @_;
  print "$self спит\n";
}

package main;
use Student;

my $amy = Student->new('Amy');
$amy->work();
$amy->sleep();
Ссылка на себя (this в Java) никогда не подразумевается в Perl:
sub work {
  my($self) = @_;
  sleep();        # Не делайте этого!
  $self->sleep(); # Правильное использование
}
Статические методы

Статические методы получают имя вызывающего класса в первом параметре. Конструкторы - это просто статические методы:
package Student;

sub new {
  my($class, $name) = @_;
  # ...
}
sub list_all {
  my($class) = @_;
  # ...
}

package main;
use Student;
Student->list_all();
Методы экземпляров могут вызывать статические методы с помощью$self->static_method():
sub work {
  my($self) = @_;
  $self->list_all();
}
Наследование

Наследование осуществляется при помощи use base 'Base::Class':
package Student::Busy;
use base 'Student';

sub night_shift {
  my($self) = @_;
  $self->work();
}

sub sleep { # Перекрывает метод родительского класса
  my($self) = @_;
  $self->night_shift();
}
Все классы автоматически наследуют некую основополагающую функциональность, такую как isa или can, от класса UNIVERSAL. Также, если вы чувствуете навязчивое желание выстрелить себе в ногу с помощью множественного наследования, Perl не станет вас останавливать.

Строгие атрибуты экземпляров

Поскольку традиционный объект является просто ссылкой на хэш, вы можете воспользоваться любым именем атрибута и Perl не будет жаловаться:
use Student;
my $amy = Student->new('Amy');
$amy->{gobbledegook} = 'какое-то значение'; # Работает
Часто хочется задать список допустимых атрибутов так, чтобы Perl завершался с ошибкой, если кто-то попытался воспользоваться неизвестным атрибутом. Это можно сделать при помощи прагмы fields:
package Student;
use fields 'name',
           'registration',
           'grades';

sub new {
  my($class, $name) = @_;
  $self = fields::new($class); # Возвращает пустой "строгий" объект
  $self->{name} = $name; # Получаем доступ к атрибуту как обычно
  return $self; # $self уже благословлён
}

package main;
use Student;

my $clara = Student->new('Clara');
$clara->{name} = 'WonderClara'; # Работает
$clara->{gobbledegook} = 'foo'; # Не работает
Памятка о принципе обобщённого доступа

Кто-то может воротить нос от того, каким образом я получаю доступ к атрибутам экземпляров в моих примерах. Писать $clara->{name} легко до тех пор, пока нужно только лишь получить сохранённое значение. Однако, каким образом нужно изменить пакет Student, если при доступе к атрибуту {name}, потребуется произвести какие-то вычисления (вроде сцепления полей {first_name} и {last_name}), что нужно сделать? Замена публичного интерфейс пакета и замена всех упоминаний $clara->{name} на $clara->get_name() не является приемлемым решением.

По существу, остаётся лишь одна из двух возможностей:
  • Можно воспользоваться tie** для того, чтобы при обращении к скаляру $clara->{name} класса выполнить необходимые вычисления атрибута. Я считаю этот процесс утомительным в чистом Perl, однако вам стоит заглянуть на страницу perltie документации Perl и составить собственное впечатление.
  • Использовать только методы доступа (также известные как геттеры и сеттеры) и сделать незаконным прямое обращение к атрибуту в вашем программном проекте. Лично я предпочитаю этот способ, потому что он позволяет сделать код красивее и предоставляет больше контроля над видимостью атрибутов для других классов. CPAN содержит различные модули, автоматизирующие создание методов доступа. Я хочу показать, как развернуть собственный генератор методов доступа в разделе Расширение языка.
Импорт

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

Параметры импорта

Вы можете передать параметры любому пакету, который вы используете:
package Student;
use Some::Package 'param1', 'param2';
Каждый раз при использовании пакета все параметры, с которыми вы его используете, передаются статическому методу import из этого пакета:
package Some::Package;
sub import {
  my($class, @params) = @_;
}
Кто вызвал?

Функция caller() позволяет вам (среди всего прочего) находить, какой класс вызвал текущий метод:
package Some::Package;

sub import {
  my($class, @params) = @_;
  print "Гляди, " . caller() . " пытается меня импортировать!";
}
Расширение языка

Давайте соединим наши знания и напишем простые члены пакета, которые настраивают поля для вызывающего пакета и создают удобные методы доступа к этим полям:
package members;

sub import {
  my($class, @fields) = @_;
  return unless @fields;
  my $caller = caller();
  
  # Построим код, который вычислим для вызывающего
  # Выполним вызов fields для вызывающего пакета
  my $eval = "package $caller;\n" .
             "use fields qw( " . join(' ', @fields) . ");\n";

  # Сгенерируем удобные методы доступа
  foreach my $field (@fields) {
    $eval .= "sub $field : lvalue { \$_[0]->{$field} }\n";
  }

  # Вычислим подготовленный код
  eval $eval;

  # $@ содержит возможные ошибки вычисления
  $@ and die "Ошибка настройки членов для $caller: $@";
}

# И ещё немного кода ниже...
package Student;
use members 'name',
            'registration',
            'grades';

sub new {
  my($class, $name) = @_;
  $self = fields::new($class);
  $self->{name} = $name;
  return $self;
}

package main;
my $eliza = Student->new('Eliza');
print $eliza->name;           # Гляди, мам, фигурных скобок нет! То же, что и $eliza->name()
$eliza->name = 'WonderEliza'; # Работает, потому что наш метод доступа является lvalue
print $eliza->name;           # Напечатает "WonderEliza"
Ценные ресурсы
Заключительные слова

Я рад, если это небольшое руководство оказалось для вас полезным. Если у вас есть вопросы или комментарии, задавайте их (только не отправляйте мне ваши домашние задания).

По теме заметки: я написал прагму под названием Reformed Perl - исправленный Perl, которая облегчает многие задачи ООП в Perl 5 и предоставляет намного более приятный синтаксис. Посмотрите сами!

Об авторе

Хеннинг Кох - студент Аугсбургского университета информатики и мультимедиа, Германия. Он ведёт блог о проектировании и технологиях программного обеспечения по адресу http://www.netalive.org/swsu/.

Примечания переводчика

* В Perl объект создаётся на основе ссылки операцией bless, что переводится как "благословлять".

** Операция tie - это, в некотором роде, родственник операции bless. Переводится как "связывать", превращает объект в структуру данных, с которой можно обращаться точно так же, как с хэшем, массивом или файловым дескриптором. Подробнее об этом можно почитать, например, тут: Изменение поведения хэша с использованием функции tie.

Решил перевести эту статью, потому что редко встретишь такую статью, в которой настолько компактно излагалось бы столько важной информации о программировании на Perl, дающей хотя бы поверхностное представление о действительной мощи этого языка.

воскресенье, 11 августа 2013 г.

Загрузка файлов во Flask

Перевод статьи: Uploading Files

Ну да, старая добрая проблема загрузки файлов. Основная мысль загрузки файлов на самом деле очень проста. В общих чертах это работает так:
  1. Тег <form> помечается атрибутом enctype=multipart/form-data, а в форму помещается тег <input type=file>.
  2. Приложение получает доступ к файлу через словарь в объекте запроса.
  3. Воспользуйтесь методом save() для того чтобы сохранить временный файл в файловой системе для последующего использования.

Введение

Начнём с простейшего приложения, которое загружает файл в определённый каталог и отображает его пользователю. Вот начало нашего приложения:
import os
from flask import Flask, request, redirect, url_for
from werkzeug import secure_filename

UPLOAD_FOLDER = '/path/to/the/uploads'
ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'])

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
Сначала нужно выполнить серию импортов. Большая часть понятна, werkzeug.secure_filename() рассматривается чуть позже. UPLOAD_FOLDER - это путь, куда будут загружаться файлы, а ALLOWED_EXTENSIONS - это набор допустимых расширений. Теперь вручную добавим в приложение правило для URL. Обычно мы так не делаем, но почему мы делаем это сейчас? Причина в том, что мы хотим заставить веб-сервер (или наш сервер приложения) обслуживать эти файлы и поэтому нам нужно генерировать правила для связывания URL с этими файлами.

Почему мы ограничили список допустимых расширений? Наверное вам совсем не хочется, чтобы пользователи могли загружать что угодно, если сервер напрямую отправляет данные клиенту. В таком случае вам нужно быть уверенными в том, что пользователи не загрузят файлы HTML, которые могут вызвать проблему XSS (см. Cross-Site Scripting (XSS) - межсайтовый скриптинг). Также убедитесь в том, что запрещены файлы с расширением .php, если сервер их выполняет. Правда, кому нужен PHP на сервере? :)

Следующая функция проверяет, что расширение файла допустимо, загружает файл и перенаправляет пользователя на URL с загруженным файлом:
def allowed_file(filename):
    return '.' in filename and \
           filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS

@app.route('/', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        file = request.files['file']
        if file and allowed_file(file.filename):
            filename = secure_filename(file.filename)
            file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
            return redirect(url_for('uploaded_file', filename=filename))
    return '''
<!doctype html>
<title>Загрузить новый файл</title>
<h1>Загрузить новый файл</h1>
<form action="" method=post enctype=multipart/form-data>
  <p><input type=file name=file>
    <input type=submit value="Загрузить">
</form>

'''
Что делает функция secure_filename()? Мы исходим из принципа "никогда не доверяй тому, что ввёл пользователь". Это справедливо и для имени загружаемого файла. Все отправленные из формы данные могут быть поддельными и имя файла может представлять опасность. Сейчас главное запомнить: всегда используйте эту функцию для получения безопасного имени файла, если собираетесь поместить файл прямо в файловую систему.

Информация для профи

Может быть вам интересно, что делает функция secure_filename() и почему нельзя обойтись без её использования? Просто представьте, что кто-то хочет отправить следующую информацию в ваше приложение в качестве имени файла:
filename = "../../../../home/username/.bashrc"
Если считать, что ../ - это нормально, то при соединении этого имени с UPLOAD_FOLDER, пользователь может получить возможность изменять на файловой системе сервера те файлы, который он не должен изменять. Нужно немного разбираться в устройстве вашего приложения, но поверьте мне, хакеры настойчивы :)

Посмотрим, как отработает функция:
>>> secure_filename('../../../../home/username/.bashrc')
'home_username_.bashrc'
Осталась последняя вещь: обслуживание загруженных файлов. Начиная с Flask 0.5 для этого можно использовать соответствующую функцию:
from flask import send_from_directory

@app.route('/uploads/<имя_файла>')
def uploaded_file(filename):
    return send_from_directory(app.config['UPLOAD_FOLDER'], filename)
Другая возможность - зарегистрировать uploaded_file с помощью правила build_only и воспользоваться SharedDataMiddleware. Такой вариант будет работать и в более старых версиях Flask:
from werkzeug import SharedDataMiddleware

app.add_url_rule('/uploads/<имя_файла>', 'uploaded_file', build_only=True)
app.wsgi_app = SharedDataMiddleware(app.wsgi_app, {
    '/uploads': app.config['UPLOAD_FOLDER']
})
Теперь, если запустить приложение, всё должно работать как положено.

Улучшение загрузки

Новинка версии 0.6

Как на самом деле Flask обрабатывает загрузку? Если файл достаточно мал, он сохраняется в памяти веб-сервера. В противном случае он помещается во временное место (туда, куда укажет tempfile.gettempdir()). Но как указать максимальный размер файла, после которого загрузка файла должна быть прервана? По умолчанию Flask не ограничивает размер файла, но вы можете задать лимит настройкой ключа конфигурации MAX_CONTENT_LENGTH:
from flask import Flask, Request

app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
Код выше ограничит максимальный размер файла 16 мегабайтами. Если передаваемый файл окажется больше, Flask сгенерирует исключение RequestEntityTooLarge.

Эта функциональность была добавлена во Flask 0.6, но может быть реализована и в более ранних версиях при помощи наследовании от класса request. За более подробной информацией обратитесь к документации Werkzeug об обработке файлов.

Индикаторы процесса загрузки

Многие разработчики придумывают считывать файл мелкими частями, сохранять процент загрузки в базу данных и давать возможность JavaScript считывать эти данные из клиента. Короче говоря, клиент спрашивает у сервера каждые 5 секунд, сколько уже было передано. Почувствовали иронию ситуации? Клиент спрашивает у сервера о том, что уже и так знает.

Сейчас существуют способы получше, которые работают быстрее и более надёжны. В последнее время в вебе многое изменилось и теперь можно использовать HTML5, Java, Silverlight или Flash, чтобы сделать загрузку удобнее со стороны клиента. Посмотрите на следующие библиотеки, предназначенные именно для этого:

Простейшее решение

Поскольку общая процедура загрузки файлов остаётся неизменной для всех приложений, занимающихся загрузкой файлов, для Flask есть расширение под названием Flask-Uploads, которое реализует полностью самостоятельный механизм загрузки с белым и чёрным списком расширений и т.п.

Примечания переводчика

Этот и другие переводы можно найти на сайте проекта перевода документации по Flask. Автор проекта - Виталий Кузьмин aka ferm32.

воскресенье, 4 августа 2013 г.

Настройка smbnetfs

Каталоги SMB в Thunar можно открывать точно так же, как это было описано для SFTP. Если пользоваться этим не часто, то никаких затруднений обычно не возникает. В противном случае может оказаться удобнее воспользоваться пакетом smbnetfs, основанном на подсистеме FUSE. Дополнительный бонус - с сетевыми папками смогут работать обычные программы, не знающие о существовании GVFS.

Установим пакет smbnetfs:
# apt-get install smbnetfs
Добавим пользователя, который будет пользоваться пакетом, в группу fuse:
# usermod -aG fuse stupin
В домашнем каталоге пользователя создаём каталог с настройками:
$ mkdir .smb
А в нём - конфиг для подключения к серверам ~/.smb/smbnetfs.conf, содержащий учётные данные для подключения к сетевым каталогам.

Можно указать через косую черту сервер и каталог, для которого должны использоваться конкретные учётные данные следующим образом:
auth "SERVER/SHARE" "username" "password"
Вместо сервера с каталогом можно указать только имя сервера или только имя рабочей группы, вот так:
auth "SERVER" "username" "password"
auth "WORKGROUP" "username" "password"
Для всех остальных подключений можно задать только имя и пароль:
auth "username" "password"
Я записал в этот файл нечто подобное:
auth "TRASH" "guest" ""
auth "CORE/stupin" "password"
Не забудьте сделать файл с настройками доступным только самому владельцу:
$ chmod go= ~/.smb/smbnetfs.conf
Теперь осталось создать каталог, в который будет отображаться содержимое сети, и смонтировать представление SMB-сети в него.
$ mkdir ~/smb
$ smbnetfs -o direct_io,readdir_ino ~/smb
Для автоматического монтирования сети при входе в XFCE, можно прописать следующую команду в автозапуск:
/usr/bin/smbnetfs -o direct_io,readdir_ino /home/stupin/smb

Открываем "Сеансы и запуск" в настройках XFCE:

Переходим на вкладку "Автозапуск приложений", добавляем новый элемент (на страничке ):

Вводим команду автозапуска и пояснительный текст:

Отмечаем этот элемент как активный:

Теперь сразу после входа под своей учётной записью можно зайти в каталог smb и увидеть содержимое сети. На первом уровне иерархии будут рабочие группы, потом серверы, потом общие каталоги, а далее - каталоги и файлы внутри общей папки. Для примера, снимок окна Thunar: