Мой Telegram-канал
DGrid: обработка onDoubleClick на строке
Разбор
Наткнулся на StackOverflow на вопрос, который давно мучил и меня самого: как в dgrid обрабатывать двойной клик по строке? Встроенного обработчика события onDblClick в классах библиотеки не было, поэтому я решил посмотреть, можно ли что-то сделать с помощью подручных средств.
Посмотрев код библиотеки, я обнаружил, что у класса Grid есть методы row() и cell(), возвращающие информацию о строке и ячейке соответственно при передаче в качестве параметра события, которое произошло в таблице. Главная сложность была в определении того, на что ставить обработчик. Понятно, что для тысяч строк нельзя вешать onClick() на каждую строку. В этом случае на контейнер вешают один единственный обработчик, который смотрит на то, от кого пришло событие, и уже тогда что-то делает. Всё оказалось не так уж сложно.
define([
"dojo/_base/declare",
"dojo/_base/lang",
"dgrid/Grid"
], function(declare, lang, Grid){
return declare(Grid, {
onDblClick: function(){
/* Функция-пустышка, которая должна быть заменена на настоящую в классах,
унаследованных от этого. Тут должна быть какая-то работа с переданным
объектом, состоящим из трех полей:
- id - id записи, если есть
- data - полный объект из связанного с таблицей Store
- element - DOM-элемент, на котором случился двойной клик */
},
_onDblClick: function(event){
/* Вызываем onDblClick, передав "чистый" объект с параметрами. Обычно тут
разработчики Dojo Toolkit рекомендуют дополнительно вызывать on.emit с
необходимыми параметрами. На случай, если кто-то ещё должен слушать
события, происходящие в таблице, и как-то на них реагировать */
this.onDblClick(this.row(event));
}, // _onDblClick
postCreate: function(){
// Установка обработчиков на построенную DOM-модель таблицы
this.inherited(arguments);
this.on(".dgrid-content .dgrid-row:dblclick", lang.hitch(this, "_onDblClick"));
} // postCreate
}); // declare
}); //define
Можно пойти дальше и создать класс-расширение, раз уж в Dojo реализовано множественное наследование. Если что, это рекомендуемый способ расширения функционала классов и каноничный подход к написанию плагинов.
Комментарии в исходном коде
Устал искать, в каком месте удалил лишнюю скобку. Устал определять по отступу, где закончился цикл или if. Поэтому сейчас пишу код примерно так:
define([
/*
* Главное меню приложения.
* Обработчики событий должны устанавливаться в классе Application.
*/
"dojo/_base/array", // array.forEach в одном месте
"dojo/_base/declare",
"dijit/DropDownMenu",
"dijit/MenuBar",
"dijit/MenuBarItem",
"dijit/MenuItem",
"dijit/PopupMenuBarItem"
], function(
array,
declare,
DropDownMenu
MenuBar,
MenuBarItem,
MenuItem,
PopupMenuBarItem
){
// Создает пункт главного меню с DropDown'ом
function createMenuBarItem(label){
return new PopupMenuBarItem({
label: label,
popup: new DropDownMenu({})
});
} // createMenuBarItem
return declare(MenuBar, {
buildRendering: function(){
this.inherited(arguments);
// Какой-то код...
this.fileMenu = createMenuBarItem("Файл");
this.editMenu = createMenuBarItem("Правка");
this.dictsMenu = createMenuBarItem("Справочники");
array.forEach([
this.fileMenu,
this.editMenu,
this.dictsMenu
], this.addChild, this); // forEach
} // buildRendering
}); // declare
}); // define
Если вкратце, то я просто пишу после закрывающих скобок, что именно они закрывают - конец цикла, тела функции, массива и т. д. Подсмотрел такое в исходном коде одного сайта, там верстальщик в комментариях указывал границы логических блоков - хедер, футер, тело страницы, панель навигации и т. д.
Debian Buster, SecureBoot и Nvidia RTX 2070
Условия задачи
Дано:
- Ошибка Xorg в логах:
May 7 16:21:42 darktower kernel: [ 5.572986] Lockdown: ioperm is restricted; see https://wiki.debian.org/SecureBoot - Debian 10 Booster с подключенными репозиториями
non-freeиcontrib. - Права
root - Видеокарта NVidia RTX 2070
- Компьютер с включенной в настройках материнской платы опцией
SecureBoot (EFI)
Проблема в том, что в ряде случаев видеокарта может не запускаться, потому что драйверы не подписаны. Для подписания драйверов следует выполнить действия, описанные в Wiki Debian / SecureBoot.
В моём случае пришлось выполнить следующую последовательность действий:
Установка драйвера Nvidia, исходных кодов ядра и mokutil
apt-get update
apt-get install nvidia-driver nvidia-xconfig linux-headers-amd64 mokutil
| Пакет | Предназначение |
|---|---|
| nvidia-driver | Проприетарный драйвер Nvidia с поддержкой нужных моделей видеокарт. |
| nvidia-xconfig | Утилита конфигурирования параметров Xorg для поддержки им нужных драйверов. |
| linux-headers-amd64 | Исходные коды установленного ядра. |
| mokutil | Утилита для работы с ключами EFI |
Генерация ключа EFI, его установка и подписывание драйверов.
Теперь действуем ровно так, как написано в руководстве Debian'а:
# Генерация ключей и импорт в EFI
cd /root
openssl req -new -x509 -newkey rsa:2048 -keyout MOK.priv -outform DER -out MOK.der -nodes -days 36500 -subj "/CN=NVidia RTX 2070 key/"
mokutil --import MOK.der
# Подписывание драйверов Nvidia
cd /lib/modules/4.19.0-8-amd64/updates/dkms/
/usr/lib/linux-kbuild-4.19/scripts/sign-file sha256 /root/MOK.priv /root/MOK.der nvidia-current-drm.ko
/usr/lib/linux-kbuild-4.19/scripts/sign-file sha256 /root/MOK.priv /root/MOK.der nvidia-current.ko
/usr/lib/linux-kbuild-4.19/scripts/sign-file sha256 /root/MOK.priv /root/MOK.der nvidia-current-modeset.ko
/usr/lib/linux-kbuild-4.19/scripts/sign-file sha256 /root/MOK.priv /root/MOK.der nvidia-current-uvm.ko
# Перенастройка Xorg под nvidia
cd /etc/X11
rm xorg.conf
nvidia-xconfig
Следует обратить внимание на путь, в котором расположены исходные модули ядра nvidia и скрипт для подписывания драйверов - они могут быть другими.
Перезагрузка
При перезагруке EFI выдаст сообщение, что появился запрос на установку собственного ключа цифровой подписи. Принимаем этот ключ и устанавливаем. После этого компьютер должен перезагрузиться и запустить установленный Linux, используя подписанные данным ключом драйверы. После этого запустится Xorg и всё будет нормально работать.
Автоматизация подписывания ключей.
Можно автоматизировать процесс по максимуму, если ручной выбор исходников ядра переключить на работу с результатами команды uname -r и поместить всё в файл скрипта:
#!/bin/sh
SIGN=/usr/lib/linux-kbuild-4.19/scripts/sign-file
MOK=/root/MOK.priv
DER=/root/MOK.der
cd /lib/modules/$(uname -r)/updates/dkms/
$SIGN sha256 $MOK $DER nvidia-current-drm.ko
$SIGN sha256 $MOK $DER nvidia-current.ko
$SIGN sha256 $MOK $DER nvidia-current-modeset.ko
$SIGN sha256 $MOK $DER nvidia-current-uvm.ko
Его нужно запускать всякий раз при обновлении драйвера Nvidia или ядра.
Настройка Exim4 / Dovecot в Astra Linux SE 1.5
Настройка почты в Astra Linux 1.5 ALD
Исходные данные и задачи
Необходимо настроить сервер электронной почты, работающий в рамках домена Astra Linux 1.5 SE. Исходные данные:
| Параметр | Значение |
|---|---|
| Имя домена | local.net |
| FQDN сервера | server.local.net |
| IP-адрес сервера | 192.168.0.1 |
| Маска сети: | 255.255.255.0 |
Настройка сети
Отключаем графический менеджер управления сетевыми подключениями wicd.
chkconfig wicd off
Прописываем настройки сети в файле /etc/network/interfaces
auto lo
iface lo inet loopback
auto eth0
allow-hotplug eth0
iface eth0 inet static
address 192.168.0.1
netmask 255.255.255.0
gateway 192.168.0.1
network 192.168.0.0
broadcast 192.168.0.255
dns-nameservers 192.168.0.1
dns-search local.lan
После этого нужно выполнить перезапуск демона сети:
service networking stop && service networking start
В документации к демону networking написано, что команда restart является устаревшей и работает не так, как ожидается.
Необходимо в файле /etc/hosts прописать соответствие FQDN сервера и его IP-адреса, а также задать hostname
127.0.0.1 localhost
192.168.0.1 server.local.net server
192.168.0.101 arm01.local.net arm01
192.168.0.102 arm02.local.net arm02
192.168.0.103 arm03.local.net arm03
В файле /etc/hostname меняем краткое имя компьютера server на полное server.local.net, а также изменяем текущее значение системной переменной:
echo server.local.net > /etc/hostname
hostname server.local.net
Инициализация домена
Действуем согласно официальной инструкции от РусБиТех:
ald-init init
Появится уведомление о том, что команда init уничтожит всю базу данных LDAP и Kerberos, будут остановлены и перезапущены службы, и упадет с неба большая звезда, горящая подобно светильнику, и падет на третью часть рек и на источники вод... Отвечаем утвердительно
Попросят ввести пароли для доступа к базе данных Kerberos и пароль администратора Astra Linux Directory. Лучше записать куда-нибудь, потому что понадобится ещё не раз.
Спустя какое-то время на экране появится сообщение:
Astra Linux Directory сконфигурирована.
Сервер ALD активен.
Клиент ALD включен.
Astra Linux Directory сервер успешно инициализирован.
Установка и настройка почтовых сервисов.
Необходимо установить три пакета:
| Пакет | Описание |
|---|---|
| exim4-daemon-heavy | Передает сообщения и умеет работать с мандатными метками. |
| dovecot-imapd | Умеет отдавать почту клиентам. |
| dovecot-gssapi | Умеет принимать авторизацию на сервере через Kerberos (ALD). |
Отдельно отмечу, что сразу же после установки нужно будет запустить конфигурирование exim4.
aptitude install exim4-daemon-heavy dovecot-imapd dovecot-gssapi -y
dpkg-reconfigure exim4-config
Процесс настройки exim4 не очень сложный, но нужно правильно ответить на несколько вопросов.
| Вопрос | Ответ |
|---|---|
| Общий тип почтовой конфигурации | интернет-сайт; приём и отправка почты напрямую, используя SMTP |
| Почтовое имя системы: | local.net |
| IP-адреса, с которых следует ожидать входящие соединения SMTP: | 192.168.0.1 |
| Другие места назначения, для которых должна приниматься почта: | local.net |
| Домены, для которых доступна релейная передача почты: | Оставляем пустым |
| Машины, для которых доступна релейная передача почты: | Оставляем пустым |
| Сокращать количество DNS-запросов до минимума (дозвон по требованию)? | Нет |
| Метод доставки локальной почты: | Maildir формат в домашнем каталоге |
| Разделить конфигурацию на маленькие файлы? | Да |
Теперь нужно удалить из каталога /var/mail файл с именем пользователя, созданного при установке системы (у меня обычно administrator).
rm /var/mail/administrator
Создадим сервисы ALD для работы с почтой:
ald-admin service-add imap/server.local.lan
ald-admin sgroup-svc-add imap/server.local.lan --sgroup=mac
ald-admin sgroup-svc-add imap/server.local.lan --sgroup=mail
ald-admin service-add smtp/server.local.lan
ald-admin sgroup-svc-add smtp/server.local.lan --sgroup=mac
ald-admin sgroup-svc-add smtp/server.local.lan --sgroup=mail
ald-client update-svc-keytab imap/server.local.lan --ktfile="/var/lib/dovecot/dovecot.keytab"
ald-client update-svc-keytab smtp/server.local.lan --ktfile="/var/lib/dovecot/dovecot.keytab"
Указывая имя сервиса, важно помнить, что после слеша указывается то же самое имя сервера, что и в файле /etc/hostname. Если даже в настройках Bind вы потом укажете, что mail.local.net и smtp.local.net являются всего лишь псевдонимами для server.local.net, почта работать не будет, потому что Kerberos очень строго проверяет этот параметр.
После создания файла ключей нужно предоставить доступ к нему для dovecot:
setfacl -m u:dovecot:x /var/lib/dovecot
setfacl -m u:dovecot:r /var/lib/dovecot/dovecot.keytab
Dovecot
Выполняем настройку dovecot. Если убрать все комментарии из файлов, то получится примерно следующее (в тех файлах, где меняли):
!include_try /usr/share/dovecot/protocols.d/*.protocol
protocols = imap
listen = 192.168.0.1
dict {}
!include conf.d/*.conf
!include_try local.confdisable_plaintext_auth = yes
auth_mechanisms = gssapi
auth_gssapi_hostname = server.local.lan
auth_krb5_keytab = /var/lib/dovecot/dovecot.keytab
!include auth-system.conf.extЯ не могу в настройку SSL. Отключаем:
ssl = no
ssl_cert = </etc/dovecot/dovecot.pem
ssl_key = </dovecot/private/dovecot.pemМаленькая настройка для корректной работы с почтой - добавляем в файле /etc/dovecot/conf.d/10-master.conf в секцию service auth:
# ...
service auth {
unix_listener auth-client {
mode = 0600
user = Debian-exim
}
# ...
}Немного упрощаем себе и пользователям жизнь, автоматически создавая на сервере каталоги под входящие, отправленные и удаленные письма при первой авторизации (параметр auto = subscribe).
namespace inbox {
mailbox Drafts {
special_use = \Drafts
auto = subscribe
}
mailbox Junk {
special_use = \Junk
}
mailbox Trash {
special_use = \Trash
auto = subscribe
}
mailbox Sent {
special_use = \Sent
auto = subscribe
}
}Расположение каталогов внутри /var/mail/%username% можно изменить, сделав более удобным, если в /etc/dovecot/conf.d/10-mail.conf немного изменить параметр mail_location:
mail_location = maildir:/var/mail/%u:LAYOUT=fs
Exim4
Теперь нужно сделать пару дополнительных настроек Exim4.
dovecot_gssapi:
driver = dovecot
public_name = GSSAPI
server_socket = /var/run/dovecot/auth-client
server_set_id = $auth1 # В конце цифра один, а не маленькая эльФайл /etc/exim4/conf.d/acl/30_exim4-config_check_rcpt очень большой, поэтому не привожу его полностью. В самом начале нужно добавить в секцию acl_check_rcpt 4 строки, чтобы выглядело примерно так:
acl_check_rcpt:
deny
message = "Auth required"
hosts = *:+relay_from_hosts
!authenticated = *
accept:
hosts = :
control = dkim_disable_verify
# и так далееПроверка работоспособности
Заходим от имени пользователя ALD. Запускаем Thunderbird и создаем новую учетную запись. В параметрах IMAP- и SMTP-серверов пишем одно и то же - server.local.net. При нажатии кнопки "Проверка параметров" параметры авторизации должны измениться на Kerberos/GSSAPI. Сохраняем, перезапускаем почтовый клиент. После второго запуска должно загрузиться дерево папок с сервера. Пишем письмо сами себе, если пришло - все хорошо. Если нет - очень внимательно проверяем содержимое всех конфигурационных файлов. Я сам потерял три дня из-за того, что в одном месте пропустил всего одну строку.
Настройка GRUB2 в Debian
Зачем настраивать GRUB 2
Решил я настроить свой GRUB 2, потому что не все его параметры меня устраивают. Ну, например, уменьшить таймаут, увеличить разрешение, в конце концов, поменять фоновую картинку. К чему в итоге пришёл, написано ниже.
Настройка разрешения
Первое, что нужно сделать - зайти в консоль самого GRUB'а при запуске. Для этого нужно нажать клавишу c, и, если пароль на загрузчик не установлен, сразу же осуществляется переход к командной строке. Тут вводим одну команду из двух (результат на моём Debian 9 одинаковый):
videoinfo
vbeinfo
Обе команды дают один и тот же результат - список доступных GRUB'у режимов видеоадаптера. Однако, всё не так просто. Дело в том, что при запуске загрузчика загружается видеодрайвер, НО ЭТО НЕ ТОТ ВИДЕОДРАЙВЕР, который даёт полный доступ ко всем режимам видеоадаптера. Таким образом, у меня, например, максимально доступное разрешение не соответствует параметрам монитора, т. е. тут не всё так просто.
В общем, параметры монитора я выяснил, теперь надо было подкрутить /etc/default/grub. Настраивать нужно именно этот файл, поскольку при вызове скрипта update-grub настройки будут взяты оттуда. Ниже привожу только те настройки, которые менял.
GRUB_DEFAULT=0
GRUB_TIMEOUT=3
GRUB_GFXMODE=1280x1024x32 # Разрешение загрузочного меню GRUB
GRUB_GFXPAYLOAD_LINUX=1920x1080x32 # Передается в параметрах ядра
GRUB_BACKGROUND=/etc/alternatives/desktop-theme/grub/Hexagons-16x9.png
GRUB_DISABLE_OS_PROBER="true"
Краткое описание параметров:
| GRUB_DEFAULT | Пункт меню по умолчанию |
| GRUB_TIMEOUT | Время в секундах до загрузки пункта по умолчанию |
| GRUB_GFXMODE | Графический режим загрузчика. Можно указать так же значение auto. |
| GRUB_GFXPAYLOAD_LINUX | Разрешение графического режима, которое загрузчик передаст ядру. Если указать правильное значение, можно запускать ОС сразу с нужным разрешением экрана. |
| GRUB_BACKGROUND | Путь к фону загрузчика. Допускается использовать файлы в форматах jpg, png и tga. При необходимости изображение будет отмасштабировано под GRUB_GFXMODE. Если картинки не будет в указанном месте, загрузчик будет запущен в текстовом режиме, без какой-либо графики. |
| GRUB_DISABLE_OS_PROBER | Запретить запуск утилиты osprober, собирающей информацию о других установленных ОС. Поскольку у меня Linux единственная ОС на этом компьютере, могу себе позволить сэкономить немного времени. |
После настройки всех параметров обязательно нужно вызвать команду update-grub. Для тех систем, где используется GRUB2, данная команда является символической ссылкой на update-grub2.
Django, JSON и формы
Очень интересные результаты выдает Google при поиске по словам django json forms. Большая часть ссылок ведет на Stack Overflow, но там всё одно и то же. Как правило, всё сводится к тому, чтобы: 1) сменить стек технологий; 2) разобрать request.POST как словарь (это не работает); 3) передать данные каким-то другим способом.
А вот рабочее решение, проверенное в Django 1.4.22 (из-за некоторых особенностей мне сейчас приходится пользоваться именно таким старьем).
# -*- coding: utf-8 -*-
from __future__ import absolute_imports
from django.utils import simplejson as json
from django import http
from app.items import forms
from app.items import models
from api.items import serializers
from rest import response
def api_post_view(request):
if request.method == 'POST' and request.is_ajax():
# Ну да, всего одна строка. А вы что думали?
raw_data = json.loads(request.POST)
new_item_form = forms.CreateItemForm(raw_data)
if new_item_form.is_valid():
new_item = new_item_form.save()
serializer = serializers.ItemReadSerializer()
return reponse.JsonResponse(serializer.serialize(new_item))
else:
return response.JsonResponse(new_tem_form.errors)
else:
return http.HttpResponseBadRequest()
Насчет JsonResponse тоже надо пару слов сказать. В старых версиях такого класса нет, но он отлично портируется из новых. Вот код:
from django.core.serializers.json import DjangoJSONEncoder
from django.utils import simplejson as json
from django.http.response import HttpResponse
class JsonResponse(HttpResponse):
"""
An HTTP response class that consumes data to be serialized to JSON.
:param data: Data to be dumped into json. By default only ``dict`` objects
are allowed to be passed due to a security flaw before EcmaScript 5. See
the ``safe`` parameter for more information.
:param encoder: Should be a json encoder class. Defaults to
``django.core.serializers.json.DjangoJSONEncoder``.
:param safe: Controls if only ``dict`` objects may be serialized. Defaults
to ``True``.
:param json_dumps_params: A dictionary of kwargs passed to json.dumps().
"""
def __init__(self, data, encoder=DjangoJSONEncoder, safe=True,
json_dumps_params=None, **kwargs):
if safe and not isinstance(data, dict):
raise TypeError(
'In order to allow non-dict objects to be serialized set the '
'safe parameter to False.'
)
if json_dumps_params is None:
json_dumps_params = {}
kwargs.setdefault('content_type', 'application/json')
data = json.dumps(data, cls=encoder, **json_dumps_params)
super().__init__(content=data, **kwargs)
dojoConfig
В Dojo Toolkit все базовые настройки приложения хранятся в глобальном объекте dojoConfig, и его инициализация должна происходить раньше, чем будет подключена сама библиотека Dojo, потому что при загрузке выполняется код, который проверяет существование этой переменной. В итоге пришёл к вот такому конфигу:
(function(G) {
"use strict"; // Возможно, избыточно, поскольку весь Dojo написан без этой директивы
G.dojoConfig = {
async: true, // Для всех браузеров должно быть так
parseOnLoad: false, // Разбор страницы на виджеты при загрузке? Лучше дождаться domReady
debug: true,
// Самая сложная часть - определение текущей локали браузера. Влияет на всякие
// внутренние механизмы Dojo, например, какой язык будет использован для виджетов.
// Хотите календарь на русском языке? Первый день недели - понедельник? Это сюда.
// Можно и явно указать "ru-ru", но мне нравится вот такой подход.
locale: navigator.language || navigator.userLanguage
};
})(this); // this === window
Dgrid: 100% высоты родителя, проблемы рендеринга и totalLength
Я довольно часто пользуюсь Dgrid. Это довольно мощная таблица, однако, у нее есть ряд недостатков, которые решаются обходными путями, например, через редактирование стилей. Вот так можно задать автоматическое растягивание таблицы по размеру родительского блока:
.dgrid {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
height: auto;
}
Решение проблемы взято со StackOverflow.
Но есть другая проблема. Допустим, мы решили разместить DGrid внутри компонента типа ContentPane. При отображении заголовок будет сжат в 0 и наедет на данные. Можно изменить размер окна браузера, чтобы отображалось нормально, а можно:
var cpWithDgrid = new ContentPane({
buildRendering: function() {
this.inherited(arguments);
this.grid = new DGrid({}); // Подставьте свой класс для Grid'а
this.set("content", this.grid);
},
resize: function() {
this.inherited(arguments);
this.grid.resize();
}
});
А разгадка такого поведения очень проста: высота элементов таблицы DGrid вычисляется до помещения в DOM, а потому они получают 0 в качестве значения. DGrid, разумеется, под суд. По-хорошему, при запуске вашего виджета он должен всем дочерним виджетам дать команду пересчитать размеры, но конкретно с DGrid это почему-то не работает. Костыль? Костыль. Но работает.
Надо ещё про работу с Rest сказать. Мои таблицы получают данные с сервера. В поле collection в качестве значения я указываю экземпляра класса-наследника dstore/Rest. Самое интересное, что при получении данных с сервера в логах часто можно увидеть сообщение об ошибке: Store reported null or undefined totalLength. Make sure your store (and service, if applicable) are reporting total correctly! Разумеется, это не правда. На самом деле DGrid ожидает увидеть в ответе сервера поле total, а не totalLength.
Поскольку на сервере у меня обычно Django + Django REST Framework, нужно соблюдать его требования. Одним из них является завершающий слеш в конце любого запроса. В итоге создал миксин, с которым смешиваю все создаваемые мной классы-наследники dstore/Rest:
define([
"dojo/_base/declare"
], function(declare) {
return declare(null, {
ascendingPrefix: "",
_getTarget: function(id) {
// По требованиям DRF в конце URL должен стоять /
var target = this.target;
// А можно решить с помощью строковых литералов {$id}, но не буду
if (target.slice(-1) == '/') {
return target + id + '/';
} else {
return target + '/' + id + '/';
} // fi
} // _getTarget
}); // declare
}); // define
CSRF-Token в Dojo Toolkit 1.x и Django
Постоянно забываю, как в Dojo Toolkit 1.x автоматически цеплять CSRF-Token к XHR-запросам. Ниже просто код, который должен запускаться при старте приложения (FrontEnd).
require([
"dojo/cookie",
"dojo/request/notify",
"dojo/domReady!"
], function(cookie, notify) {
notify("send", function(response, cancel) {
response.xhr.setRequestHeader("X-SCRFToken", cookie("csrftoken"));
});
});
И
csrf перенесена в модуль django.template.context_processors# -*- coding: utf-8 -*-
u"""Набор видов для построения базового интерфейса приложения."""
from django.shortcuts import render_to_response
from django.views.generic import View
from django.core.context_processors import csrf
class Index(View):
u"""Главное окно приложения."""
template_name = "index.html"
def get(self, request):
# Установка CSRF-токена
c = {}
c.update(csrf(request))
return render_to_response(self.template_name, c)
Но этого мало. Нужно ещё в теле главной страницы разместить скрытое поле, куда будет записан токен. Кстати, только так в старых версиях Django можно указать cookie для его хранения.
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8"/>
<title>Установка CSRF-Token'а</title>
</head>
<body>
<!-- ТО САМОЕ СКРЫТОЕ ПОЛЕ -->
<input name="csrftoken" type="hidden" value="{% csrf_token %}" />
<!-- /ТО САМОЕ СКРЫТОЕ ПОЛЕ -->
</body>
</html>
Запуск Django-приложений через mod_python в Astra Linux SE
Запуск Python-Web-приложений в Astra Linux
В Astra Linux до версии 1.6 не было иного способа нормального запуска Web-приложений, написанных на Python, кроме mod_python. В настоящее время самый популярный способ - WSGI, однако, в сертифицированных версиях Astra Linux SE (1.4, 1.5) есть только указанный выше модуль для Apache. Безусловно, есть и третий способ - запуск приложения в отладочном режиме через вот эту команду:
python manage.py runserver
Надо ли объяснять, что делать так не нужно?
Итак, задачи, решение которых я предлагаю в этой статье:
- Настройка Apache для работы в рамках ALD (Astra Linux Domain)
- Настройка виртуального хоста для Django-приложения
- Авторизация в приложении Django через ALD/Kerberos
Настройка ALD и Apache
Установка необходимых модулей Apache.
Устанавливаем два модуля Apache (считаем, что Apache уже установлен на сервере).
apt-get install libapache2-auth-mod-kerb libapache2-mod-python
Включаем эти модули и перезапускаем WEB-сервер:
a2dismod auth_pam
a2enmod python
a2enmod auth_kerb
Настройка ALD.
Теперь нужно настроить сервер таким образом, чтобы клиенты, подключающиеся в рамках сессии ALD, прозрачно авторизовались на сервере. В инструкции всё описано, лишь немного постараюсь прояснить моменты, которые у меня вызвали вопросы.
Создадим принципала в ALD и добавим его в группу mac
ald-admin service-add HTTP/server.domain.lan
ald-admin sgroup-svc-add HTTP/server.domain.lan --sgroup=mac
Теперь создадим файл ключа Kerberos и дадим права на него пользователю www-data.
KEYTAB="/etc/apache2/keytab"
ald-client update-svc-keytab HTTP/server.domain.lan --ktfile=$KEYTAB
chown www:data $KEYTAB
chmod 644 $KEYTAB
Теперь можно перезапустить WEB-сервер:
service apache2 restart
Создание виртуального хоста Apache.
Подготовительные операции выполнены, на клиентах Firefox настроен на использование GSS API для авторизации (about:config, потом в параметр network.negotiate-auth.delegation-uris вписываем http://,https://). Самое время создать виртуальный хост для нашего Django-проекта, лежащего в каталоге /var/www/site/.
<VirtualHost *:80>
ServerName server.domain.lan
ServerAdmin useradmin@domain.lan
DocumentRoot /var/www/site
AddDefaultCharset utf-8
<Directory "/var/www/site/”>
Options -Indexes FollowSymLinks -MultiViews
AllowOverride None
AuthType Kerberos
KrbAuthRealms DOMAIN.LAN
KrbServiceName HTTP/server.domain.lan
Krb5Keytab /etc/apache2/keytab
KrbMethodNegotiate on
KrbMethodK5Passwd off
KrbSaveCredentials on
require valid-user
Order deny,allow
Allow from all
</Directory>
<Location "/">
SetHandler python-program
PythonHandler django.core.handlers.modpython
SetEnv DJANGO_SETTINGS_MODULE site.settings
PythonOption diango.root /var/www/site
PythonPath "['/var/www/site/',] + sys.path"
PythonAutoReload On
</Location>
<Location "/media/”>
SetHandler None
</Location>
<Location "/static/">
SetHandler None
</Location>
<LocationMatch "\.(jpg|gif|png)$">
SetHandler None
</Location>
ErrorLog /var/www/site/log/error.log
LogLevel warn
SetEnfIf Request_URI "\.jpg$|\.gif$|\.css$|\.js" is_static
CustomLog /var/www/site/log/access.log combined env=!is_static # Убрать лишнее из логов доступа, например, статику
</VirtualHost>
Не забываем включить наш сайт в список разрешенных:
a2ensite astra-django-project
service apache2 reload
Побочные эффекты
Возможно, у меня руки кривые, или я чего-то не знаю, но есть некоторые факты, которым я не нахожу другого объяснения, например, откусывание заголовков HTTP сервером Apache. Допустим, у нас есть вот такой код:
# -*- coding: utf-8 -*-
u"""Виды для обработки данных абонентов."""
from django.shortcuts import get_object_or_404
from django.views.generic import View
from abonent.models import Abonent
from rest.responses import JsonResponse
from .serializers import AbonentSimpleSerializer
class AbonentRootView(View):
def get(self, request):
u"""
Именно здесь происходит сериализация модели в JSON.
Возвращаемый объект - JsonResponse.
"""
root_abonent = Abonent.objects.filter(parent=None)[0]
serializer = AbonentSimpleSerializer()
response = JsonResponse({
"items": [
serializer.serialize(root_abonent),
],
"total": 1
})
# Тут задаем заголовки, чтобы библиотека dgrid могла с ними работать
# Не надо смотреть на цифры, они сейчас не имеют значения (просто пример)
response["Content-Range"] = "items: 1-1/1"
return response
Не суть важно, как происходит сериализация (сериализатор возвращает словарь), важно то, что происходит в строке, где устанавливается заголовок ответа Content-Range. Если запустить отладочный сервер, то в заголовках ответа мы его увидим. Если выполнять этот же код с помощью Apache, т. е. так, как я выше написал, заголовок будет просто выброшен. Как это лечить в Astra Linux 1.4, я не знаю. Но, например, при использовании библиотеки DStore использовать свойство useRangeHeaders будет нельзя. Т. е. и DGrid тоже работать не будет, нужно помимо свойства items передавать ещё и total. Не такая уж большая проблема, но под определение подводного камня подходит хорошо.
Elpy, python-mode и use-package
Emacs - отличный редактор для тех, кто пишет на Python, и есть куча пакетов для поддержки этого языка. Я уже раньше писал, что теперь храню свою конфигурацию и управляю пакетами с помощью use-package, однако, долго не мог найти способ для красивого скрещивания замечательного пакета elpy cо стандартным python-mode.
Решение оказалось не очень сложным, хотя на его поиск пришлось потратить некоторое время. Ниже выкладываю кусок своего init.el.
(use-package python-mode
:mode ("\\.py\\'" . python-mode)
:init(add-hook 'python-mode-hook #'elpy-enable)
:config
(use-package elpy
:bind
("M-," . elpy-goto-definition)
:init
(elpy-enable)
(defalias 'workon 'pyvenv-workon)
:config
(add-to-list 'company-backends 'elpy-company-backend)
(elpy-enable))
(use-package py-autopep8
:hook
(python-mode . py-autopep8-enable-on-save))
(use-package py-isort
:init
(add-hook 'before-save-hook #'py-isort-before-save)))
Этот код не делает никакой магии. В общем-то, тут вообще нет ничего интересного.
Самому себе оставляю памятку: в настройках settings.el уже включена автоматическая загрузка недостающих пакетов с помощью use-package. Для всех Python'ьих файлов как major-mode указан python-mode. При его инициализации дополнительно подключаются пакеты elpy, py-autopep8 и py-isort, почти все настройки которых уже вынесены сами знаете куда. Кроме того, каждый пакет при загрузке ставит хуки на определенное действие. Например, автоматическое форматирование или упорядочение импортированных модулей.
Не уверен в том, какую строку с elpy-enable можно удалить, чтобы ничего не сломалось. Сегодня выяснять лень, потом разберусь.
Красивый способ хранения настроек Emacs
Интересный подход к хранению конфигурации Emacs
Интересный способ хранения конфигурации подсмотрел у автора use-package. Если вкратце, то все настройки для конкретных пакетов вынесены в файл settings.el, а вот список используемых пакетов и нюансы взаимодействия между ними описаны целиком и полностью в init.el.
Чтобы далеко не ходить, вот пример кода:
;;; init.el --- Summary
;;; Commentary:
;;; Main EMACS settings file, load settings from parts.
;;; Code:
;; Added by Package.el. This must come before configurations of
;; installed packages. Don't delete this line. If you don't want it,
;; just comment it out by adding a semicolon to the start of the line.
;; You may delete these explanatory comments.
(package-initialize)
(eval-when-compile(require 'cl))
(require 'package)
(setq package-archives nil)
(add-to-list 'package-archives '("melpa-stable" . "http://stable.melpa.org/packages/") t)
(add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/") t)
(add-to-list 'package-archives '("org" . "http://orgmode.org/elpa/") t)
(add-to-list 'package-archives '("gnu" . "http://elpa.gnu.org/packages/") t)
(add-to-list 'package-archives '("elpy" . "https://jorgenschaefer.github.io/packages/") t)
(setq package-enable-at-startup nil)
(package-initialize nil)
;;; Загрузка и установка пакета use-package, если его нет в системе
(unless(package-installed-p 'use-package)
(message "EMACS install use-package.el")
(package-refresh-contents)
(package-install 'use-package))
(require 'use-package)
;;; Шрифты
(set-face-attribute 'default nil :height 110)
(when (member "DejaVu Sans Mono" (font-family-list))
(set-face-attribute 'default nil :font "DejaVu Sans Mono"))
;;; Заставляю хоткеи работать и в русской раскладке
(defun cfg:reverse-input-method (input-method)
"Build the reverse mapping of single letters from INPUT-METHOD."
(interactive
(list (read-input-method-name "Use input method (default current): ")))
(if (and input-method (symbolp input-method))
(setq input-method (symbol-name input-method)))
(let ((current current-input-method)
(modifiers '(nil (control) (meta) (control meta))))
(when input-method
(activate-input-method input-method))
(when (and current-input-method quail-keyboard-layout)
(dolist (map (cdr (quail-map)))
(let* ((to (car map))
(from (quail-get-translation
(cadr map) (char-to-string to) 1)))
(when (and (characterp from) (characterp to))
(dolist (mod modifiers)
(define-key local-function-key-map
(vector (append mod (list from)))
(vector (append mod (list to)))))))))
(when input-method
(activate-input-method current))))
(cfg:reverse-input-method 'russian-computer)
;; Resize windows
(global-set-key(kbd "S-C-") 'shrink-window-horizontally)
(global-set-key(kbd "S-C-") 'enlarge-window-horizontally)
(global-set-key(kbd "S-C-") 'shrink-window)
(global-set-key(kbd "S-C-") 'enlarge-window)
;; Isearch
(global-set-key (kbd "C-M-r") 'isearch-backward-other-window)
(global-set-key (kbd "C-M-s") 'isearch-forward-other-window)
(defun xah-new-empty-buffer()
"Open a new empty buffer.
URL `http: // ergoemacs.org / emacs / emacs_new_empty_buffer.html'
Version 2015 - 06 - 12"
(interactive)
(let((ξbuf(generate-new-buffer "untitled")))
(switch-to-buffer ξbuf)
(funcall(and initial-major-mode))
(setq buffer-offer-save t)))
;; Save/close/open
(global-set-key(kbd "C-w") 'kill-this-buffer)
(global-set-key(kbd "C-s") 'save-buffer)
(global-set-key(kbd "C-S-s") 'write-file)
(global-set-key(kbd "C-r") 'revert-buffer)
(global-set-key(kbd "C-a") 'mark-whole-buffer)
(global-set-key(kbd "M-'") 'comment-or-uncomment-region)
(global-set-key(kbd "C-o") 'dired)
(global-set-key(kbd "C-n") 'xah-new-empty-buffer)
(global-set-key(kbd "C-+") 'text-scale-increase)
(global-set-key(kbd "C--") 'text-scale-decrease)
;; Buffers and windows
(global-set-key(kbd "C-") 'next-buffer)
(global-set-key(kbd "C-") 'previous-buffer)
(global-set-key(kbd "C-") 'other-window)
(global-set-key(kbd "M-3") 'delete-other-windows)
(global-set-key(kbd "M-4") 'split-window-horizontally)
(global-set-key(kbd "M-5") 'split-window-vertically)
(global-set-key(kbd "M-6") 'balance-windows)
(global-set-key(kbd "C-f") 'isearch-forward)
(global-set-key(kbd "C-h") 'query-replace)
(global-set-key(kbd "C-S-h") 'query-replace-regexp)
(global-set-key(kbd "M-a") 'execute-extended-command)
(global-set-key(kbd "M-x") 'kill-whole-line)
(global-set-key(kbd "") 'keyboard-quit)
(when(get-buffer "*scratch*")
(kill-buffer "*scratch*"))
(fset 'yes-or-no-p 'y-or-n-p); ; ; Shortcuts for yea and no
;;; Format file before save
(defun format-current-buffer()
(indent-region (point-min) (point-max)))
(defun untabify-current-buffer()
(if (not indent-tabs-mode)
(untabify (point-min) (point-max)))
nil)
(add-to-list 'write-file-functions 'untabify-current-buffer)
(add-to-list 'write-file-functions 'delete-trailing-whitespace)
;;; САМЫЙ ВАЖНЫЙ МОМЕНТ - ВЫНЕСЕНИЕ НАСТРОЕК СРЕДЫ В settings.el и загрузка их оттуда
(setq custom-file "~/.emacs.d/settings.el")
(load-file "~/.emacs.d/settings.el")
;;; Теперь просто описание используемых пакетов
;;; Настройки в большинстве случаев подтягиваются из файла settings.el
(use-package airline-themes
:requires powerline
:init
(load-theme 'airline-molokai))
(use-package all-the-icons)
(use-package beacon
:diminish
:commands beacon-mode)
(use-package company
:diminish
:commands company-mode
:config
;; From https://github.com/company-mode/company-mode/issues/87
;; See also https://github.com/company-mode/company-mode/issues/123
(defadvice company-pseudo-tooltip-unless-just-one-frontend
(around only-show-tooltip-when-invoked activate)
(when (company-explicit-action-p)
ad-do-it))
(defun check-expansion ()
(save-excursion
(if (outline-on-heading-p t)
nil
(if (looking-at "\\_>") t
(backward-char 1)
(if (looking-at "\\.") t
(backward-char 1)
(if (looking-at "->") t nil))))))
(define-key company-mode-map [tab]
'(menu-item "maybe-company-expand" nil
:filter (lambda (&optional _)
(when (check-expansion)
#'company-complete-common))))
(eval-after-load "yasnippet"
'(progn
(defun company-mode/backend-with-yas (backend)
(if (and (listp backend) (member 'company-yasnippet backend))
backend
(append (if (consp backend) backend (list backend))
'(:with company-yasnippet))))
(setq company-backends
(mapcar #'company-mode/backend-with-yas company-backends))))
:init (global-company-mode t))
(use-package company-quickhelp
:bind
(:map company-active-map
("C-c h" . company-quickhelp-manual-begin)))
(use-package css-mode
:mode "\\.css\\'")
(use-package elpy
:mode "\\.py\\'"
:requires python-mode
:hook (add-hook 'before-save-hook #'elpy-format-code)
:init
(elpy-enable)
(defalias 'workon 'pyvenv-workon))
(use-package emmet-mode
:mode ("\\.html\\'" . emmet-mode)
:bind
("C-j" . emmet-expand-line))
(use-package flycheck
:commands flycheck-mode
:init(add-hook 'after-init-hook #'global-flycheck-mode))
(use-package highlight-numbers
:hook(prog-mode . highlight-numbers-mode))
(use-package ibuffer
:bind([f2] . ibuffer)
:init
(add-hook 'ibuffer-mode-hook #'(lambda ()(ibuffer-switch-to-saved-filter-groups "default"))))
(use-package js2-mode
:mode ("\\.js\\'" . js2-mode)
:requires flycheck
:bind(
:map js2-mode-map
("M-n" . flycheck-next-error)
("M-p" . flycheck-previous-error))
:config
(add-to-list 'flycheck-disabled-checkers #'javascript-jshint)
(flycheck-add-mode 'javascript-eslint 'js2-mode)
(flycheck-mode 1)
(js2-highlight-unused-variables-mode t))
(use-package json-mode
:mode (("\\.json\\'" . json-mode)
("\\.bowerrc\\'" . json-mode)
("\\.jshintrc\\'" . json-mode)))
(use-package json-reformat
:after json-mode)
(use-package magit
:bind([f5] . magit-status))
(use-package markdown-mode
:mode "\\.md\\'")
(use-package monokai-theme
:init(load-theme 'monokai))
(use-package neotree
:requires all-the-icons
:bind
([f8] . neotree-toggle))
(use-package powerline)
(use-package py-isort
:hook
(add-hook 'before-save-hook 'py-isort-before-save))
(use-package rainbow-delimiters
:hook
(prog-mode . rainbow-delimiters-mode))
(scroll-bar-mode -1)
(use-package tide
:requires js2-mode
:after js2-mode
:mode ("\\.js\\'" . tide-mode)
:hook((before-save-hook . tide-format-before-save)
(typescript-mode-hook . setup-tide-mode)))
(use-package web-beautify)
(use-package web-mode
:commands web-mode
:requires web-beautify
:mode("\\.phtml\\'" . web-mode)
("\\.html\\'" . web-mode)
:hook (add-hook 'before-save-hook 'web-beautify-html-buffer t t))
(use-package yasnippet
:after prog-mode
:defer 10
:diminish yas-minor-mode
:mode("/\\.emacs\\.d/snippets/" . snippet-mode)
:config
(yas-load-directory (expand-file-name "snippets" user-emacs-directory))
(yas-global-mode 1))
(use-package yasnippet-snippets
:after yasnippet)
;;; init.el ends here
Что интересного я взял у автора?
Во-первых, саму организацию хранения описания пакетов. Во-вторых, он отсортировал вызовы use-package по алфавиту. Быстрый поиск не нужен, если можно просто нажать пару раз [PgDn]. В-третьих, он и в файле настроек применил сортировку по алфавиту.
Для тех, кому интересен оригинальный конфиг, его можно найти здесь.
Настройка DNS-сервера bind9 в Debian/Astra Linux
Цели
- Настройка основного сервера DNS для маленькой локальной сети
- Сервер должен быть безопасным
- Сервер должен хранить настройки для конфигурации данной сети
- Сервер должен обслуживать доменную зону
home.netс адресами в подсети192.168.0/24
Как обычно, если не указано иное, команды выполняются от имени пользователя root.
Исходные данные
| Доменная зона | home.net |
| Подсеть | 192.168.0/24 |
| Имя основного сервера | dc.home.net |
| IP-адрес сервера имен | 192.168.0.1 |
| Имя первого хоста | arm01.home.net |
| IP-адрес первого хоста | 192.168.0.101 |
| Имя N-го хоста | armN.home.net |
| IP-адрес N-го хоста | 192.168.0.(100+N) |
Установка сервера
apt-get install bind9 -y
Сервер установлен, но установка сделана не безопасным способом. Нужно выполнить несколько дополнительных действий, описанных подробнее в Debian Wiki - Bind.
Будем запускать сервер в изолированном окружении:
mkdir -p /var/bind9/chroot/{etc,dev,var/cache/bind,var/run/named}
mknod /var/bind9/chroot/dev/null c 1 3
mknod /var/bind9/chroot/dev/random c 1 8
chmod 660 /var/bind9/chroot/dev/{null,random}
mv /etc/bind /var/bind9/chroot/etc
ln -s /var/bind9/chroot/etc/bind /etc/bind
chown bind:bind /var/bind9/chroot/etc/bind/rndc.key
chmod 775 /var/bind9/chroot/var/{cache/bind,run/named}
chgrp bind /var/bind9/chroot/var/{cache/bind,run/named}
Теперь нужно изменить расположение PIDFILE. Для этого в файле /etc/init.d/bind9 нужно переопределить переменную PIDFILE.
PIDFILE=/var/bind9/chroot/var/run/named/named.pid
Чтобы сервер понимал, что его запускают в изолированном окружении, нужно изменить параметры его запуска, внеся небольшие коррективы в файл /etc/default/bind9
OPTIONS="-u bind -t /var/bind9/chroot"
Перенастроим логирование rsyslog
echo "\$AddUnixListenSocket /var/bind9/chroot/dev/log" > /etc/rsyslog.d/bind-chroot.conf
Настройка опций сервера
Безопасность
Основные настройки сервера хранятся в файле /etc/bind/named.conf.options (на самом деле сейчас это всего лишь символическая ссылка, но на суть дела это не влияет). Добавим пару параметров для пущей безопасности в разделе options:
options {
...
version none; // Скроем версию bind
allow-query {
192.168.0/24; // Отвечать будем только на запросы, приходящие из внутренней сети
};
...
}
Включение в конфигурацию новой доменной зоны
В файле /etc/bind/named.conf.default-zones добавим указание на файлы прямой и обратной зоны, чтобы сервер мог обслуживать локальную сеть:
...
zone "home.net" in { // Локальная сеть
type master;
file "/etc/bind/db.home.net"; // В этом файле будут адреса прямой зоны
};
zone "0.168.192.in-addr.arpa" { // Так описывается обратная зона, т. е. первые три октета
// записываются в обратном порядке и добавляется .in-addr.arpa
type master;
file "/etc/bind/db.0.168.192"; // В этом файле будут адреса обратной зоны
};
Прямая доменная зона
Ниже просто приведу содержимое файла /etc/bind/db.home.net.
$TTL 3h
@ IN SOA dc.home.net. administrator.home.net. (
; Вместо @ будет подставлено автоматически имя зоны из файла /etc/bind/named.conf.default-zones
2017092701 ; Это число нужно менять каждый раз при изменении этого файла
; Очень удобно взять текущее число и добавить ещё пару цифр для указания ревизии
3h ;
1h ;
1w ;
1h
)
IN NS dc.home.net. ; Указываем имя основного DNS-сервера, обслуживающего зону
; Наличие пробела или табуляции в начале строки и точки
; после .net обязательно
dc IN A 192.168.0.1 ; IP-адрес сервера имен
imap IN CNAME dc ; Указываем, что наш DNS-сервер так же отзывается на imap.home.net
smtp IN CNAME dc ; и smtp.home.net
arm01 IN A 192.168.0.101
arm02 IN A 192.168.0.102
arm03 IN A 192.168.0.103
arm04 IN A 192.168.0.104
arm05 IN A 192.168.0.105
arm06 IN A 192.168.0.106
arm07 IN A 192.168.0.107
arm08 IN A 192.168.0.108
arm09 IN A 192.168.0.109
arm10 IN A 192.168.0.110
Обратная зона
Создадим файл, на который ссылается обратная зона, и заполним его по образцу:
$TTL 3h
@ IN SOA dc.home.net. administrator.home.net. (
2017092701 ; Это число тоже нужно менять
3h ;
1h ;
1w ;
1h
)
IN NS dc.home.net.
1 IN PTR dc.home.net. ; Наш сервер
101 IN PTR arm01.home.net.
102 IN PTR arm02.home.net.
103 IN PTR arm03.home.net.
104 IN PTR arm04.home.net.
105 IN PTR arm05.home.net.
106 IN PTR arm06.home.net.
107 IN PTR arm07.home.net.
108 IN PTR arm08.home.net.
109 IN PTR arm09.home.net.
110 IN PTR arm10.home.net.
Запуск сервера и проверка работоспособности
Если всё сделано правильно, точки расставлены и IP-адреса указаны верно, можно попробовать запустить наш сервер:
service bind9 start
Если вместо кучи ошибок сервер просто написал, что всё хорошо, можно проверить разрешение имен:
host arm01.home.net
host 192.168.0.9
В обоих случаях сервер должен вернуть информацию о том, на что указывает доменное имя или IP-адрес. Если вместо этого он выдает сообщение об ошибке, самое время проверить корректность конфигурационных файлов. Это делается вызовом следующих несложных команд:
named-checkconf /etc/bind/named.conf
named-checkzone home.net /etc/bind/db.home.net
named-checkzone 192.168.0 /etc/bind/db.0.168.192
Данные утилиты из состава пакета bind9utils укажут на ошибки в конфигурационных файлах.
Клиенты
Существует несколько способов настроить обращение к DNS-серверу на клиентских машинах. По скольку речь идет об операционной системе для социальной группы 'siloviki', выбор инструментальных средств невелик - wicd, /etc/network/interfaces и /etc/resolv.conf.
Первый способ отметаем сразу же, поскольку с wicd довольно много проблем. Не буду останавливаться на них подробно, но обычно первое, что приходится делать на свежеустановленной системе - убрать его из автозагрузки:
service wicd stop
chkconfig wicd off
Можно указать DNS-сервер в настройках сетевых интерфейсов, однако, в этом случае их нужно прописывать для каждого сетевого интерфейса, а я не люблю повторяться. В связи с этим просто откорректируем файл /etc/resolv.conf на клиентских машинах:
domain home.net
search home.net
nameserver 192.168.0.1
Работа в ненулевых мандатных режимах
В данном дистрибутиве реализована мандатная модель разграничения доступа, в том числе к сетевым сервисам. Чтобы обеспечить корректную работу демона named в режимах с мандатной меткой, отличной от нулевой, нужно добавить всего одну строчку в файл /etc/parsec/privsock.conf:
/usr/sbin/named
Управление пакетами в EMACS с помощью use-package
Что такое use-package и как с ним бороться.
Автор данного пакета преследовал цель удобного управления своими пакетами, поддержание чистоты конфигурационных файлов EMACS и по возможности оптимизацию запуска. Ему удалось их успешно решить, а я решил воспользоваться результатами чуждого труда и заодно поделиться информацией. Почитать на англиском про use-package можно здесь, а в статье я лишь оставлю заметку о том, что реально пригодилось.
~/.emacs
Мой файл .emacs содержит довольно мало строк: установщик некоторых базовых пакетов, загрузчик файлов конфигурации, немного комментариев. Выглядит это всё так:
;;; package --- Summary
;;; Commentary:
;;; Main EMACS settings file, load settings from parts.
;;; Code:
(require 'package)
;;; Источники для установки пакетов. Дело в том, что MELPA Stable
;;; содержит не очень свежие версии, поэтому иногда лучше ставить
;;; пакеты из основного репозитория.
(add-to-list 'package-archives '("melpa-stable" . "http://stable.melpa.org/packages/"))
(add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/"))
(setq package-enable-at-startup nil)
(package-initialize nil)
;;; Если пакет use-package не установлен, его нужно скачать и
;;; установить
(unless (package-installed-p 'use-package)
(message "EMACS install use-package.el")
(package-refresh-contents)
(package-install 'use-package))
;;; Установили, загрузили, указали, что недостающие пакеты нужно
;;; автоматически загружать и устанавливать.
(require 'use-package)
(setq use-package-always-ensure t)
;;; Указываем откуда брать части настроек.
(defconst user-init-dir
(cond ((boundp 'user-emacs-directory) user-emacs-directory)
((boundp 'user-init-directory) user-init-directory)
(t "~/.emacs.d/")))
;;; Функция для загрузки настроек из указанного файла.
(defun load-user-file (file)
(interactive "f")
"Load a file in current user's configuration directory"
(load-file (expand-file-name file user-init-dir)))
;;; Части конфигурации. Порядок не имеет принципиального значения,
;;; однако я рекомендую некоторые базовые вещи помещать в начало,
;;; чтобы не было необходимости вспоминать базовые команды EMACS
;;; если в результате улучшения сломается один из базовых конфигов.
(load-user-file "neotree.el")
(load-user-file "personal.el")
(load-user-file "themes.el")
(load-user-file "flycheck.el")
(load-user-file "company.el")
(load-user-file "ibuffer.el")
(load-user-file "python.el")
(load-user-file "web.el")
(load-user-file "javascript.el")
(load-user-file "markdown.el")
(load-user-file "json.el")
(load-user-file "yasnippet.el")
(load-user-file "helm.el")
(load-user-file "keyboard.el")
(load-user-file "fonts.el")
(load-user-file "powerline.el")
(load-user-file "elisp.el")
;;; А здесь EMACS хранит настройки, задаваемые через customize
(setq custom-file "~/.emacs.d/customize.el")
(load-user-file "customize.el")
;;; .emacs ends here
Половина дела сделана. Настройки размещены в разных файлах, каждый из которых не зависит от других. Вот теперь заживём!
neotree.el
;;; Package --- Summary
;;; Commentary:
;;; NeoTree - sidebar for file browsing
;;; Code:
;;; Как я и говорил, пакет будет скачан и установлен автоматически.
(use-package
neotree
:bind ([f8] . neotree-toggle) ;;; Так делается привязка клавиш.
:init (setq neo-window-width 35) ;;; Настройки ДО загрузки пакета.
:config (setq neo-smart-open nil)) ;;; Настройки ПОСЛЕ загрузки пакета.
;;; neotree.el ends here
Полагаю, данный пример наглядно показывает, как пользоваться use-package. Продолжим.
personal.el
;;; Package --- Summary
;;; Commentary:
;;; User settings for .emacs
;;; Code:
;;; Закрывать *scratch* при запуске.
(kill-buffer "*scratch*")
;;; Цветные скобочки
(use-package
rainbow-delimiters
:init (add-hook 'prog-mode-hook #'rainbow-delimiters-mode)
(setq rainbow-delimiters-max-face-count 9))
;;; Scrolling
(setq scroll-step 1) ;; one line
(setq scroll-margin 10) ;; scroll buffer to 10 lines at going to last line
(setq scroll-conservatively 10000)
(setq directory-free-space-args "-Pm")
;; Подсветка результатов поиска и всё такое
(setq search-highlight t)
(setq query-replace-highlight t)
(setq column-number-mode 1) ;; Номера строк слева
(setq use-dialog-box nil) ;; Не нужны нам диалоги, будем всё руками делать
(auto-fill-mode -1) ;; Не знаю, что за параметр, так и не разобрался
(setq-default tab-width 4) ;; Заменить TAB на 4 пробела
(setq-default standart-indent 4) ;; Стандартный отступ - 4 пробела
(setq backup-inhibited t) ;; Backup'ы тоже делать не будем
(setq auto-save-default nil) ;; Автосохранение не нужно
(setq scroll-preserve-screen-position 10) ;; Показывать не менее 10 строк на экране
(setq-default c-basic-offset 4 c-indent-level 4 indent-tabs-mode nil) ;; TAB'ы не нужны
(setq-default save-place t) ;; Помнить, где был курсор в прошлый раз
;;; Нажатие Insert больше не включает режим замены
(define-key global-map [(insert)] nil)
;;; Автоформатирование перед сохранением
(defun format-current-buffer()
(indent-region (point-min)
(point-max)))
(defun untabify-current-buffer()
(if (not indent-tabs-mode)
(untabify (point-min)
(point-max)))
nil)
(add-to-list 'write-file-functions 'untabify-current-buffer)
(add-to-list 'write-file-functions 'delete-trailing-whitespace)
(cua-mode 1) ;;; Ctrl+C, Ctrl+V! Прямо как в Windows!
(desktop-save-mode 1) ;;; Помнить, какие файлы были открыты в прошлый раз
(fset 'yes-or-no-p 'y-or-n-p) ;;; Вместо yes и no понимать y и n
(global-hl-line-mode 1) ;;; Подсветка текущей строки
(global-linum-mode 1) ;;; Показывать номера строк всегда
(menu-bar-mode -1) ;;; А меню - никогда
(scroll-bar-mode -1) ;;; Скроллбар не нужен
(tool-bar-mode -1) ;;; Тулбар не нужен
;;; Умные скобочки
(use-package
smartparens
:config (smartparens-global-mode 1))
;; Electric pair mode
(electric-pair-mode 1)
;; Иконки в статус-баре
(use-package
mode-icons
:config (mode-icons-mode 1))
;; Показывать отступы во всех режимах
(use-package
indent-guide
:config (indent-guide-global-mode 1))
;;; Дерево отмены
(use-package
undo-tree
:config (global-undo-tree-mode 1))
;;; personal.el ends here
python.el
Очень интересный конфиг получается для Python.
;;; Package --- Summary
;;; Commentary:
;;; Settings for Python
;;; Code:
(defun annotate-pdb()
(interactive)
(highlight-lines-matching-regexp "import ipdb")
(highlight-lines-matching-regexp "import pdb")
(highlight-lines-matching-regexp "set_trace()")
(highlight-regexp "^TODO ")
(highlight-phrase "FIXME"))
(use-package
python
:mode ("\\.py'" . python-mode)
:init (progn
(defalias 'python2-mode 'python-mode)
(defalias 'python3-mode 'python-mod))
:config (setq-default py-separator-char 47) ;; Use spaces instead tab
(setq-default python-indent-offset 4) ;; 4 spaces instead 2 for python-mode
)
(use-package
company-anaconda
:ensure t
:init (add-to-list 'company-backends 'company-anaconda))
(use-package
py-autopep8
:init (progn (add-hook 'python-mode-hook 'py-autopep8-enable-on-save)))
(use-package
anaconda-mode
:commands anaconda-mode
:diminish anaconda-mode
:init (progn (add-hook 'python-mode-hook 'anaconda-mode)
(add-hook 'python-mode-hook 'eldoc-mode)
(add-hook 'python-mode-hook 'annotate-pdb)))
(use-package
pyvenv
:config (defalias 'workon 'pyvenv-workon))
(use-package
py-isort
:config (setq py-isort-options '("-sl"))
:init (progn (add-hook 'python-mode-hook 'py-isort-before-save)))
(use-package
company-anaconda
:config (add-to-list 'company-backends 'company-anaconda))
;;; python.el ends here
Не уверен, что нужно что-то тут объяснять, но на всякий случай свой company.el тоже выставляю напоказ.
company.el
Этот модуль отвечает за подсказки и автозавершение. Он новее, чем autocomplete, и активно развивается. Большая часть плагинов дополнения сейчас пишется именно под него.
;;; Package --- Summary
;;; Commentary:
;;; Settings for company
;;; Code:
(use-package
company
:diminish company-mode
:config (setq company-backends (remove 'company-ropemacs company-backends) company-tooltip-limit
20 company-tooltip-align-annotations t)
(global-company-mode 1))
;;; company.el ends here
Gulp для сборки приожений Angular 1.x
Немного про Gulp
Было время, когда мне очень нравился Grunt, а причина была проста - другие средства сборки Web-проектов только начинали развиваться (2013 год, если что). Вскоре я открыл для себя Gulp. В сравнении с Grunt это был действительно удобный инструмент, и я надолго остановился именно на нём.
Всё никак не найду время на изучение Angular 2 и Webpack, поэтому один из мелких проектов пишу на Angular 1.x (актуальная версия на момент написания статьи - 1.6.x). Кто писал на нём, знает, что все контроллеры, директивы, сервисы и прочие компоненты лучше размещать в отдельных файлах, однако, потом на странице нужно подключать каждый файл отдельно.
Мириться с этим было нельзя, я поискал на StackOverflow похожие запросы и выяснил, что многие сталкивались с этой проблемой. Я предлагаю своё решение, являющееся компиляцией из разных источников.
Установка пакетов
В каталоге проекта инициализируем npm, отвечаем на несколько простых вопросов, после чего ставим необходимые плагины:
npm init && npm install gulp gulp-rename gulp-uglify gulp-include gulp-watch --save
| Название | Описание |
|---|---|
| gulp-rename | Позволяет указать имя целевого файла, отличное от исходного. Используется для создания отладочных и минимизированных версий файлов. |
| gulp-uglify | Сжимает JS. Используется для создания минимизированных продуктовых версий. |
| gulp-include | Позволяет вставлять код из одного файла в другой подобно тому, как это делает директива #include> в C-подобных языках. Так же позволяет делать вызов require, при этом код будет вставлен только в том случае, если не вставлялся ранее. |
| gulp-watch | Отслеживает изменения в нужных файлах, при необходимости автоматически вызывая нужные Gulp Task |
gulpfile.js
В каталоге приложения так же создадим файл по имени gulpfile.js (регистр важен) со следующим содержимым:
(function (r) {
"use strict";
var gulp = r("gulp"),
uglify = r("gulp-uglify"),
include = r("gulp-include"),
rename = r("gulp-rename"),
sources = {
login: "./src/login.js",
app: "./src/app.js"
},
DEST = "./todoist/static/app/"
gulp.task("js-dev", function () {
// Сборка без минификации, специально для отладки
gulp.src(sources.login)
.pipe(gulp.dest(DEST));
gulp.src(sources.app)
.pipe(include())
.pipe(gulp.dest(DEST));
});
gulp.task("js-prod", function () {
gulp.src(sources.login)
.pipe(uglify())
.pipe(rename("login.min.js"))
.pipe(gulp.dest("./todoist/static/app/login.min.js"));
gulp.src(sources.app)
.pipe(include())
.pipe(uglify())
.pipe(rename("app.min.js"))
.pipe(gulp.dest(DEST));
});
gulp.task("watch", function () {
gulp.watch("./src/**/*.js", ["js-dev"]);
});
gulp.task("default", ["js-dev", "watch"]);
}(require));
Надеюсь, комментарии к тому, что тут происходит, не нужны.
Файлы приложения
Все исходные файлы приложения у меня лежат в src/, для контроллеров создан каталог controllers, для сервисов - services (директивы и фильтры пока не успел написать, поэтому и каталогов нет).
Так выглядит главный файл приложения:
(function (A) {
"use strict";
var app = A.module("todoist", ["ui.bootstrap", "ngRoute", "ngCookies"]);
app.config(["$interpolateProvider", "$httpProvider", "$routeProvider", function ($interpolateProvider, $httpProvider, $routeProvider) {
$interpolateProvider.startSymbol("{$");
$interpolateProvider.endSymbol("$}");
$httpProvider.defaults.xsrfCookieName = "csrftoken";
$httpProvider.defaults.xsrfHeaderName = "X-CSRFToken";
$httpProvider.defaults.headers.common.Authorization = "Basic";
$routeProvider.when("/", {
controller: "MainController",
templateUrl: "/ui/index.html"
}).when("/users", {
controller: "UserListController",
templateUrl: "/ui/users/list.html"
}).when("/users/:id", {
controller: "UserDetailController",
templateUrl: "/ui/users/detail.html"
}).otherwise({
redirectTo: "/login"
})
}]);
//=include ./controllers/*.js
//=include ./services/*.js"
}(this.angular));
При сборке вместо комментариев //=include ./controllers/*.js будет вставлено содержимое лежащих там файлов - как раз то, чего я и хотел добиться.
Последний шаг - подключить файл приложения в шаблоне Django:
<script src="{% static 'app/app.js' %}"></script>
Настройка сети в Astra Linux
Astra Linux использует для конфигурирования сетей собственную утилиту wicd. В общем, она довольно удобна, но у неё есть фатальный недостаток - её писали не мы сеть не будет работать до авторизации пользователя в системе. Для обычных компьютеров в этом нет ничего страшного, однако, для сервера это большая проблема, так как иногда его приходится перезагружать по SSH.
Пусть компьютеры будут находиться в сети с адресами 192.168.0.XXX, где вместо XXX - число от 1 до 254.
Настройка осуществляется путем правки файла /etc/network/interfaces. Каждый сетевой интерфейс (сетевая карта, хотя это не совсем точное название) настраивается отдельно. Настройки для сервера выглядят так:
auto lo eth0
iface lo inet loopback
iface eth0 inet static
address 192.168.0.1
netmask 255.255.255.0
gateway 192.168.0.1 # В качестве шлюза - наш сервер с IP=1
network 192.168.0.0 # Указываем сеть, это обязательно для работы в составе ALD
broadcast 192.168.0.255 # Сервер ALD начиная с Astra 1.5 выводит ошибку, если не видит
# этого параметра в настройках сети.
dns-nameservers 192.168.0.1 # Тут через пробел можно перечислить ВСЕ DNS-серверы сети
dns-search DOMAIN.NET # Имя домена ALD
Первая строчка auto lo eth0 указывает, какие интерфейсы должны быть запущены при загрузке ОС. Отмечу, что локальная петля lo должна присутствовать там в любом случае.
Пропустим описание локальной петли и сразу перейдем к сетевому интерфейсу.
iface |
Ключевое слово, говорящее о том, что дальше будет описание сетевого интерфейса |
eth0 |
Указываем, что данный сетевой интерфейс должен быть привязан к сетевой карте eth0. Посмотреть список карт можно командой: lshw -class network |
inet |
Указываем, что это будет настройка сети. |
static |
При этом все настройки будут указаны вручную. |
address |
IPv4-адрес компьютера |
netmask |
Маска подсети. |
gateway |
Шлюз, т. е. IP-адрес, через который идёт подключение к интернету. Обычно на сервере указывают адрес, выданный провайдером, но в нашем случае (закрытый от мира сегмент) пусть будет 192.168.150.1, т. е. компьютер обращается сам к себе. |
dns-nameservers |
Список разделенных пробелами IP-адресов DNS-серверов. Полезно при разворачивании ЕПП под управлением Astra Linux и настройке приложения bind. |
На клиентских компьютерах настройки следует выполнить аналогичным образом, меняя только четвёртый октет в поле address.
На этом настройка не заканчивается. Теперь нужно отключить автозапуск встроенных утилит и остановить уже запущенный экземпляр службы wicd, после чего перезапустить службу поддержки сети.
service wicd stop
chkconfig wicd off
rm /etc/xdg/autostart/fly-admin-wicd.desktop
service networking restart
Если на одной сетевой карте по каким-то причинам нужно иметь 2 или более IP-адресов, настройки делаются следующим образом:
auto lo
iface lo inet loopback # Кстати, удалять интерфейс локальной петли не стоит,
# некоторые службы от этого сходят с ума и начинают
# выдавать весьма странные ошибки
auto eth0:0
allow-hotplug eth0:0
iface eth0:0 inet static
address 192.168.0.1
netmask 255.255.255.0
network 192.168.0.0
gateway 192.168.0.1
broadcast 192.168.0.255
dns-nameservers 192.168.0.1 192.168.0.2
dns-search DOMAIN.NET
auto eth0:1
allow-hotplug eth0:1
iface eth0:1 inet static
address 10.2.12.54 # Адрес, маска и все остальные параметры в этой
netmask 255.255.255.128 # секции были придуманы за 20 секунд, поэтому
network 10.2.12.0 # не ручаюсь за правильность заполнения. Гуглите
gateway 10.2.12.1 # маску подсети для этого случая самостоятельно.
broadcast 10.2.12.255
dns-nameservers 8.8.8.8
dns-search GOOGLE.RU
Посмотреть настройку сети в Debian более подробно можно на официальной Wiki-странице или её несколько устаревшей русской версии
Не-IT: польза записной книжки
Сегодня на Хабре наткнулся на статью Записывай всё. Признаюсь, читал не очень внимательно, тем более что в книге Фила Портера "Съесть или быть съеденным" рассматривается не только этот вопрос корпоративной политики, но и многие другие, причём более обстоятельно. Однако, данный пост сподвиг меня написать комментарий. Дабы его увидело больше людей, я решил перенести сюда свои мысли по поводу пользы записных книжек.
Записывайте всё, что вам говорят
Что бы ни говорил Большой Босс, лучше записать его слова дословно и как можно подробнее. Во-первых, он будет видеть, что вам не всё равно, что он говорит. Это значит, что он будет требовать с вас именно то, что вы записали. Во-вторых, даже если у вас хорошая память, плохая может быть у него, а запись - она и есть запись. Её можно потом зачитать. Другими словами, чем подробнее вы ведёте записи, тем лучше для вас - вы ничего не забудете и будете понимать задачу почти так же, как её понимает ваш босс.
Запись - вещественное доказательство
Есть такой тип руководителей, названия которому я не знаю, но могу дать им характеристику. Эти люди ставят задачу, а через некоторое время требуют с вас не то или не совсем то, что говорили. Допустим, Большой Босс хочет купить партию компьютеров на базе процессоров "Эльбрус", чтобы потом их продавать в комплекте с софтом вашей фирмы. Вы едете к поставщику и договариваетесь о поставке нужного количества комплектов. Их привозят, и тут Большой Босс спрашивает:
- Ты что, идиот?
Вы не понимаете, в чём дело.
- Я же сказал, взять самые мощные компьютеры! Надо было брать на базе Xeon'ов!
Тут вы достаете свою записную книжку, открываете нужную страницу и говорите:
- Нет, вы сказали "Эльбрус". Я записал.
Записная книжка выручит вас в подобных ситуациях
Записывайте, кто помимо вас слышал постановку задачи
Большой босс может сказать, что он такого не говорил либо сказал по-другому. Если у вас есть список свидетелей, вы можете обратиться к ним.
Используйте ручки разных цветов
Скорее всего, в большой фирме у вас будет больше одного руководителя, например, руководитель отдела и руководитель проекта. Используйте ручки разных цветов, чтобы не путать, кто из них что говорит. Ну или записывайте фамилию.
Уведомляйте коллег, что их слова тоже записываются
Не надо вести записи из-под полы. Ведите их открыто, т.е. пусть ваши жертвы коллеги и начальство знают, что вы, как компьютер, ведёте полный лог всех разговоров. Они будут строже формулировать задачи и не будут болтать лишнего. С другой стороны, выкладывать свои заметки в общий доступ тоже не стоит - начальство не любит, когда на него собирают компромат (а компроматом можно считать что угодно, доказывающее, что ошиблись не вы, а руководитель).
Отмечайте выполненные задачи
Я ставлю рядом с каждой задачей кружок, а иногда так же и дату, если задача срочная, т.е. должна быть выполнена к определенному сроку. Если задача выполнена успешно, я ставлю галочку и дату выполнения. Если отменена - зачеркиваю одной чертой, как на знаке "Остановка запрещена". Проваленная задача отмечается крестом.
Хоткеи в EMACS
Основы
Я долгое время пользовался пакетом ergoemacs и был доволен, однако, со временем он стал обрастать функциями, которые мне стали не нужны, а иногда откровенно глючили на моём самосборном EMACS'е. Пришлось изучить, как настраиваются хоткеи в редакторе.
Во-первых, вместо привычных Ctrl, Alt и Shift в EMACS для обозначения модификаторов используются другие значения:
| C | Ctrl |
| M | Alt |
| S | Shift |
Ни в одном руководстве по EMACS вы не найдете записи типа Ctrl+S - там будет написано C-S, хотя по сути эти две записи идентичны.
Во-вторых, клавиши, которые нажимаются вместе, записываются через дефис.
Третье, регистр имеет значение.
Последнее, что нужно знать о клавиатурных комбинациях EMACS - некоторые команды состоят из последовательного нажатия разных клавиатурных комбинаций, например, выход из редактора выглядит так: C-x C-c, что означает следующее:
- Сначала нажимаем
Ctrl+X - Теперь нажимаем
Ctrl+C
Так же команда выхода из редактора требует подтверждения, если имеются модифицированные буферы, но об этом я напишу в другой раз.
К делу!
Перед тем, как использовать какую-либо клавиатурную комбинацию, следует убедиться, что она уже не привязана к какому-либо действию, либо освободить её от этой привязки. Ниже показано, как удалить привязку команды к комбинации и тут же связать её с новой командой:
(global-unset-key (kbd "C-S-c C-S-c"))
(global-set-key (kbd "C-S-c C-S-c") 'mc/edit-lines)
С простыми клавишами разобрались, добавим функциональные:
(require 'neotree)
(require 'ibuffer)
(require 'magit)
(require 'dired)
(global-set-key [f2] 'ibuffer)
(global-set-key [f3] 'dired)
(global-set-key [f8] 'neotree-toggle)
(global-set-key [f10] 'magit-status)
Теперь посмотрим, как описываются стрелки:
;; Resize windows
(global-set-key (kbd "S-C-<left>") 'shrink-window-horizontally)
(global-set-key (kbd "S-C-<right>") 'enlarge-window-horizontally)
(global-set-key (kbd "S-C-<down>") 'shrink-window)
(global-set-key (kbd "S-C-<up>") 'enlarge-window)
Можно ли сделать с полученными знаниями что-то полезное? Да! Давайте удалим ergoemacs и начнем вместо него использовать свои собственные комбинации!
;;; Package --- Summary
;;; Commentary:
;;; Keybindings for non-latin layout
;;; Code:
(setq initial-major-mode (quote text-mode))
(defun cfg:reverse-input-method (input-method)
"Build the reverse mapping of single letters from INPUT-METHOD."
(interactive
(list (read-input-method-name "Use input method (default current): ")))
(if (and input-method (symbolp input-method))
(setq input-method (symbol-name input-method)))
(let ((current current-input-method)
(modifiers '(nil (control) (meta) (control meta))))
(when input-method
(activate-input-method input-method))
(when (and current-input-method quail-keyboard-layout)
(dolist (map (cdr (quail-map)))
(let* ((to (car map))
(from (quail-get-translation
(cadr map) (char-to-string to) 1)))
(when (and (characterp from) (characterp to))
(dolist (mod modifiers)
(define-key local-function-key-map
(vector (append mod (list from)))
(vector (append mod (list to)))))))))
(when input-method
(activate-input-method current))))
(cfg:reverse-input-method 'russian-computer)
(require 'neotree)
(require 'ibuffer)
(require 'magit)
(require 'dired)
(global-set-key [f2] 'ibuffer)
(global-set-key [f3] 'dired)
(global-set-key [f8] 'neotree-toggle)
(global-set-key [f10] 'magit-status)
;; Multiple cursors
(global-unset-key (kbd "C-S-c C-S-c"))
(global-set-key (kbd "C-S-c C-S-c") 'mc/edit-lines)
;; Resize windows
(global-set-key (kbd "S-C-<left>") 'shrink-window-horizontally)
(global-set-key (kbd "S-C-<right>") 'enlarge-window-horizontally)
(global-set-key (kbd "S-C-<down>") 'shrink-window)
(global-set-key (kbd "S-C-<up>") 'enlarge-window)
(defun xah-new-empty-buffer ()
"Open a new empty buffer.
URL `http://ergoemacs.org/emacs/emacs_new_empty_buffer.html'
Version 2015-06-12"
(interactive)
(let ((ξbuf (generate-new-buffer "untitled")))
(switch-to-buffer ξbuf)
(funcall (and initial-major-mode))
(setq buffer-offer-save t)))
;; Save/close/open
(global-set-key (kbd "C-w") 'kill-this-buffer)
(global-set-key (kbd "C-s") 'save-buffer)
(global-set-key (kbd "C-S-s") 'write-file)
(global-set-key (kbd "C-r") 'revert-buffer)
(global-set-key (kbd "C-a") 'mark-whole-buffer)
(global-set-key (kbd "M-'") 'comment-or-uncomment-region)
(global-set-key (kbd "C-o") 'dired)
(global-set-key (kbd "C-n") 'xah-new-empty-buffer)
(global-set-key (kbd "C-+") 'text-scale-increase)
(global-set-key (kbd "C--") 'text-scale-decrease)
;; Buffers and windows
(global-set-key (kbd "C-<next>") 'next-buffer)
(global-set-key (kbd "C-<prior>") 'previous-buffer)
(global-set-key (kbd "C-<tab>") 'other-window)
(global-set-key (kbd "M-3") 'delete-other-windows)
(global-set-key (kbd "M-4") 'split-window-horizontally)
(global-set-key (kbd "M-5") 'split-window-vertically)
(global-set-key (kbd "M-6") 'balance-windows)
(global-set-key (kbd "C-f") 'isearch-forward)
(global-set-key (kbd "C-h") 'query-replace)
(global-set-key (kbd "C-S-h") 'query-replace-regexp)
(global-set-key (kbd "M-a") 'execute-extended-command)
(global-set-key (kbd "M-x") 'kill-whole-line)
(global-set-key (kbd "<esc>") 'keyboard-quit)
(setq x-select-enable-clipboard t)
;; Highlight search resaults
(setq search-highlight t)
(setq query-replace-highlight t)
;;; keyboard.el ends here
У меня конфигурация разбита на несколько маленьких файлов, и я привёл целиком содержимое keyboard.el. Полагаю, не проблема выбросить ненужное и оставить нужное. Большая часть команд будет работать без установки дополнительных пакетов.
Об этом блоге
Меня зовут Дунаевский Максим, и я программист-самоучка. Чем я пользуюсь:
- EMACS
- Django
- Django Rest Framework
- bower
- Dojo
- Debian Linux
- Windows 10
Угораю по литературному программированию, книге "Шаблоны проектирования", по статическим анализаторам, autopep8 и isort, а так же интересным задачам. В этот блог пишу только когда есть время.
Как сказано в примечании к моему блогу, он выполняет в первую очередь роль дневника, и только во вторую - пособие для начинающих. С одной стороны, неспособность профессионала снизойти до уровня новичка и объяснить ему всё доступно есть снобизм, а с другой мне лень описывать вещи, которые можно найти в первой десятке выдачи Google. Кроме того, в связи со сменой работы, места жительства и рода деятельности программирование и администрирование с декабря 2015 года перешли на второй план, статьи будут выходить очень редко, если вообще будут. У меня всё, извините, если кого-то задел.