So, these things are great if you want one file to appear in more than one
place. At least once I thought that a hardlink can allow two processes running
under different users to access and modify one file even if each file has a
pretty strict access mode. I could not be more wrong.
A directory contains filename to inode mapping for a file. And a hard link is
just another filename for the same inode. The directory entry does not have
the ownership information and access mode:
$ ls -l file.txt another-name-for-a-file.txt
-rw-rw-r-- 2 rtg rtg 6 Feb 2316:34 another-name-for-a-file.txt
-rw-rw-r-- 2 rtg rtg 6 Feb 2316:34 file.txt
$ chmod 0600 another-name-for-a-file.txt
$ ls -l file.txt another-name-for-a-file.txt
-rw------- 2 rtg rtg 6 Feb 2316:34 another-name-for-a-file.txt
-rw------- 2 rtg rtg 6 Feb 2316:34 file.txt
Yes, that information is stored in the inode itself and it is simply shared
between all the names of the file.
Hard links cannot cross the filesystem boundaries because they are simply
referencing an existing inode number and not the path.
You can’t create a hard link to a directory because it can introduce an
infinite loop while traversing the directories. You can still do this with
symbolic links but the system utilities will handle this for you, because it
can be clearly seen what file name is the canonical one:
$ mkdir /tmp/a
$ ln -s /tmp/a /tmp/a/b
$ ls -lHR /tmp/a
/tmp/a:
total 0lrwxrwxrwx 1 rtg rtg 6 Feb 2316:47 b -> /tmp/a
$ find -L /tmp/a
/tmp/a
find: File system loop detected;`/tmp/a/b' is part of the same file system loop as `/tmp/a'.
At some point I understood that I have no idea why hard links would be useful,
however, as a nice blog post by Paul Cobbaut and the comments suggest, hard
links are used during file renaming across a single file system. At first a
new link to the same inode is created, then an old one is removed.
They’re useful in the case of mostly-duplicated data, too. Backups are a good example of this. If you’ve got a folder things/ with thing1, thing2, thing3 in it, and you create a backup of it on your backup server, you’ll have:
then you delete thing2 from your disk, and back up again. In theory, then,
you’d get a new folder on the backup server containing thing1 and thing3 — so
the backup server would have two copies of thing1 and thing3, which is wasteful
of space because they havn’t changed. However, in practice, you get this:
backupserver:
/backup-2013-02-23
/things
thing1
thing2
thing3
/backup-2013-02-24
/things
thing1 (hardlink to thing1 in previous folder)
thing3 (hardlink to thing3 in previous folder)
so there’s only actually one copy of thing1, even though it’s in both backup
folders. This means that every backup folder looks like a full backup, but
only actually takes the space of an incremental backup.
Even though 8Mb is available to the program, there are various other things
that need to be put on the stack, such as the arguments and return values. When
a recursive function breaks and calls itself indefinitely it eventually uses up
all the stack space and crashes in exactly the same way.
I have a second network at home for my virtual machines and DHCP is set up to
give the classless routing information. I usually use 3G connection but today
I enabled WiFi on my android phone running 2.2 and it brought the WiFi up but
no hosts could be accessed. It turned out that it did not want to set the
default route.
It turns out that Android actually implements it the right way:
If the DHCP server returns both a Classless Static Routes option and a
Router option, the DHCP client MUST ignore the Router option.
Oh, so my network is broken! I added the default route to the classless static
route (which immediately triggered a bug in network manager, which is not that
critical – the gateway is still picked up from the router option) but now my
phone failed to get the DNS.
After forcing dhcp option 6 with the ip address of my DNS server the phone
finally connected to the outside world via WiFi.
So now my dnsmasq uci config started to look like this and it works for me:
config 'dhcp' 'lan'
...
# option 121
list 'dhcp_option' 'option:classless-static-route,192.168.100.0/24,192.168.1.10,0.0.0.0/0,192.168.1.1'
# option 6
list 'dhcp_option' 'option:dns-server,192.168.1.1'
I know the file is there and I know it’s a file an application can handle, but
bash autocompletion tries to be smart by refusing to provide the file name.
This gets really annoying if you happen to work with files that don’t have an
extension the autocompletion scripts expect or you are running a command using
sudo and want to pass the filename.
There is a dedicated shortcut for filename completion – M-/ by default, but
I’ve never used tab completion for options and that means that I can get rid
of it.
There is no need to uninstall anything, adding complete -r to .bashrc will
remove all the completion functions.
To completely disable programmatic completion, add shopt -u progcomp to
.bashrc.
Another thing that was annoying me for a while is command-not-found package.
It is extremely helpful at discovering what package the application I want is
in. However, there were quite a few times when I made a typo, pressed Enter,
noticed it right away but had to wait for a second or so before I got the
“command not found” and the prompt back. When disk is really busy, making a
typo costs me another 30 seconds of disk trashing before command-not-found
comes up with a friendly suggestion that killal is better spelled as Command
'killall' from package 'psmisc'.
Having command-not-found installed and available but not kicking in on every
occasion is preferred. The function bash runs in case it can’t find the
command is command_not_found_handle so we simply need to unset this function
in .bashrc and add an alias (in my case packages-providing after LP:486716,
but it can be anything) which will execute the real command-not-found script:
unset command_not_found_handle
alias packages-providing='/usr/lib/command-not-found --no-failure-msg'
So now I will be given the package name only when I want it:
$ sl
bash: sl: command not found
$ packages-providing sl
The program 'sl' is currently not installed. You can install it by typing:
sudo apt-get install sl
When testing network issues with different cards it is a good idea to make sure
they actually have different chipsets.
Update: I found no difference between the NIC operating with built-in r8169
module and the version that is available on the RealTek’s web site. You will
want to use the vendor-provided module only if your card is not supported by
the driver shipped with the linux kernel.
Update: See the end of the post, patch is required for DGE-528T to be
picked by this module.
I had a complex VM network setup with separate vlan on the router for virtual
machines and second network card in the server to prevent connection hanging
when network topology was changing on the VM startup. Then I decided to
simplify the network and use one NIC only, in my case that would be DLink
DGE-528T.
This card is driven by realtek 8169 chip and for some reason these like to drop
network connection. I’ve found quite a few topics on poor performance of 8169
with in-kernel driver, but the built-in r8168 chip running under r8169 driver
included in the kernel performs way worse than r8169.
There is a driver update on the Realtek web site so I decided to give it a try.
I started with dkms.conf from Fixing RTL8111/8168B kernel module on
Debian/Ubuntu post by Randy, but for some reason r8169 module kept overwriting
the system wide one. It turns out that Makefile installs module automatically
when called with no specific target.
At the moment due to the bug in dkms script it is not possible to create the
dkms tarball directly from realtek sources but you can create one after you
perform the steps above.
$ sudo dkms mktarball r8169/6.017.00
Marking modules for 3.2.0-35-generic (x86_64) for archiving...
Marking /var/lib/dkms/r8169/6.017.00/source for archiving...
Tarball location: /var/lib/dkms/r8169/6.017.00/tarball//r8169-6.017.00-kernel3.2.0-35-generic-x86_64.dkms.tar.gz
Afterwards you can load this tarbal on a different machine:
So I left this module running for about a week and yesterday I could not make
the card work after a reboot. It turns out that while the updated r8169 module
is installed, it is not being used for D-Link DGE-528T card.
So the module does not match PCI_VENDOR_ID_DLINK:0x4c00 subsystem (lspci -vnn):
02:06.0 Ethernet controller [0200]: D-Link System Inc DGE-528T Gigabit Ethernet Adapter [1186:4300] (rev 10)
Subsystem: D-Link System Inc DGE-528T Gigabit Ethernet Adapter [1186:4300]
Flags: bus master, 66MHz, medium devsel, latency 64, IRQ 21
I/O ports at e800 [size=256]
Memory at febffc00 (32-bit, non-prefetchable) [size=256]
Expansion ROM at febc0000 [disabled] [size=128K]
Capabilities: [dc] Power Management version 2
Kernel driver in use: r8169
Kernel modules: r8169
1186:4c00 is actually a DGE-T530 card, but it looks like there are some
DGE-T528 cards with 1186:4300 subsystem and some have 1186:4c00 one. The
original driver from D-Link has PCI_ANY_ID for subsystem fields so it looks
like these are compatible.
I was so sure the updated module would fix the issues I was having with the
network card that it actually stopped misbehaving.
However, I decided to patch the module to make it work with my card too:
And now the module prints the following when loaded:
[18014.031288] r8169 Gigabit Ethernet driver 6.017.00-NAPI loaded
[18014.031344] r8169 0000:02:06.0: PCI INT A -> GSI 21 (level, low) -> IRQ 21
[18014.033537] r8169: This product is covered by one or more of the following patents: US5,307,459, US5,434,872, US5,732,094, US6,570,884, US6,115,776, and US6,327,625.
So now I am really testing the module provided by Realtek.
26-го листопада я отримав повідомлення від nic.ua, в якому йшла мова про
можливість замовлення будь-яких послуг за половину вартості. nic.ua є
реєстратором доменних імен, а після об’єднання з hosted.ua також надає послуги
хостингу.
Я вже давно шукав місце для свого локального django проекту, тому вирішив
взяти в оренду хостинг з тарифом NIC•1. Про те, що Python можна
використовувати в режимі WSGI, я спочатку прочитав у їхній базі знань, а потім
власноручно перевірив модуль mod_wsgi на сервері, що обслуговує цей блог.
Хостинг працює на базі cPanel/WHM. Донедавна я користувався тільки
VPS-рішеннями і все звик налаштовувати самостійно, тому з cPanel я зустрівся
вперше саме при переїзді блогу.
В ідеалі встановлення модулів Python на сервер здійснюється через віддалене
з’єднання SSH. nic.ua надає доступ по SSH, про це заявлено у “додаткових
послугах”. Я поцікавився умовами в службі підтримки:
SSH доступ надається в ручному режимі. Необхідні:
Скан-копія вашого паспорту (перші 2 розвороти)
Ваша статична IP адреса, з якої ви працюєте з хостингом.
Логін хостингу.
Поповнення особового рахунку на 40₴. та повідомлення номера сплаченого рахунку. Ці гроші буде списано бухгалтерією.
Послуга надається 1 раз на весь час дії хостингу.
Я вирішив для початку відтворити віддалену систему вдома на віртуальній машині,
зібрати всі необхідні модулі та замовляти SSH доступ тільки якщо зібрані
бінарні модулі відмовляться працювати. Все ПЗ в автоматичному режимі встановлює
cPanel, проте я не бажав поки що мати з нею справу на своєму сервері. До того
ж, cPanel потребує ліцензії.
Отже, на призначеному мені сервері було встановлено:
CentOS 6.3 x86_64
Apache 2.2.23 (стандартно в CentOS 6.3, але перезібраний в /usr/local)
Python 2.7.3, встановлений до /opt (в /usr живе python 2.6.6)
setuptools і virtualenv вже встановлені
mod_wsgi 3.3, зібраний з python 2.7.3
nginx 1.2.4, що проксює всі запити до Apache та віддає статичні файли
PostgreSQL 8.4.13 (в CentOS 6.3)
Після інсталяції CentOS 6.3 у мінімальній конфігурації я додав необхідні пакети:
Для початку я зібрав Python (список пакетів для встановлення взяв з How to
install Python 2.7.3 on CentOS 6.2, тому що з системами RPM не працював з 2006
року). При конфігуруванні треба вказати:
Потім зібрав mod_wsgi, вказавши шлях до python в /opt/python2.7/bin/python. При
цьому в /etc/httpd/conf.d/mod_wsgi.conf додав шлях, куди треба складати сокети:
Далі створив віртуальний хост для test.staging.lappyfamily.net, в
інсталяцію нового Python додав setuptools, через easy_install встановив
virtualenv.
На сервері з повним доступом модулі можна встановлювати в системні директорії,
однак, якщо це не є можливим, то можна використати virtualenv.
Подальші дії вже проводив від імені звичайного користувача.
Створив новий virtualenv у своєму каталозі та, активувавши нове оточення, додав
pysocpg2 та django через easy_install. Після цього створив новий простий django
проект.
Для того, щоб проект зміг знайти модулі, що були встановлені у virtualenv, в
WSGI скрипті вказав наступне:
Після того, як мій додаток був готовий та коректно працював локально, я
заархівував створені директорії в tar.gz (тому що у віртуальному середовищі є
символічні посилання, які не можна створити через FTP) і розпакував його через
файловий менеджер у веб-інтерфейсі cPanel.
Проект запрацював.
Активація
У стандартній конфігурації WSGI працює під тим самим обліковим записом, що і
apache. У статті про те, як використовувати django, сказано, що для активації
проекту потрібно подати заявку до служби підтримки.
Активацією в nic.ua називається додавання наступних рядків до конфігурації
віртуального хосту:
Після цього процес WSGI буде працювати через обліковий запис користувача. Хочу
наголосити, що додавати Alias для статичних файлів не потрібно, запит спочатку
обробляє nginx, який самостійно перевіряє DOCUMENT_ROOT на наявність потрібних
файлів.
Якщо використовується WSGIScriptAlias в кореневій директорії, то .htaccess буде
ігноруватися. Служба підтримки пішла назустріч та прибрала рядок
WSGIScriptAlias з конфігурації. В такому разі wsgi скрипт має знаходитися в
DOCUMENT_ROOT, а в .htaccess буде щось на кшталт:
Options+ExecCGI
RewriteEngineOnAddHandlerwsgi-script.wsgi
# Prefer django.wsgi not to be visibleRewriteRule^django.wsgi$/[R,L]
RewriteCond%{REQUEST_FILENAME}!-f
RewriteRule^(.*)$/django.wsgi/$1
Basic/Digest Auth
Якщо використовувати wsgi через RewriteRule без WSGIScriptAlias, то django не
буде коректно працювати з Basic або Digest Auth, тому що RemoteUserMiddleware
не буде отримувати змінну REMOTE_USER (якщо авторизація була, наприклад, у
папці /api, а сам WSGI скрипт розміщено у корені). При цьому буде передаватися
змінна REDIRECT_REMOTE_USER, яку django не використовує.
Для того, щоб django використовував нову змінну, потрібно створити нове Middleware:
Для того, щоб мати змогу робити резервну копію бд локально та проводити
міграції структури напряму через django-admin, я надіслав до служби підтримки
запит з обґрунтуванням потреби доступу до бази даних, і через 4 години доступ
було надано. Ця послуга є безкоштовною, але клієнтові необхідно мати статичну
адресу. Тут є стаття про MySQL, але для PostgreSQL умови не відрізняються.
Якщо PostgreSQL відмовляється надавати доступ через localhost, потрібно
використовувати адресу 127.0.0.1, тому що localhost веде до IPv6 адреси ::1. Я
повідомив службу підтримки про цю ситуацію, і протягом декількох хвилин
необхідні зміни конфігурації вже було внесено.
Пошта
Локальний сервер відмовляється надсилати пошту, якщо не існує локального
користувача, що вказується як адресант. Для того, щоб мій додаток зміг
надіслати листа, потрібно створити локальну поштову скриньку або
переадресовувати пошту на іншу.
Якщо цього не зробити, то віддалений сервер (наприклад GMail або Яндекс) не
зможе підтвердити наявність облікового запису та відповість:
{'recipient@example.net':
(550, 'Verification failed for \nNo Such User Here"\nSender verify failed')}
DKIM
В cPanel можна увімкнути підтримку DKIM. Незважаючи на те, що текст у довідці
натякає тільки на підтримку перевірки вхідної кореспонденції, це налаштування
керує підписуванням вихідних листів. Для того, щоб необхідні заголовки DKIM
були додані до листів, останні потрібно надсилати через локальний SMTP сервер.
Використання sendmail напряму не підходить (це стосується тільки сторонніх
скриптів, django не використовує sendmail).
Приватний та публічний ключі створюються автоматично і їх не можна змінити або
побачити через cPanel. Оскільки мій домен зареєстрований і підтримується у
GoDaddy, то мені потрібно було лише видобути публічний ключ:
cPanel може не подобатися те, що сервер не є головним DNS для цієї зони, але це
повідомлення можна спокійно проігнорувати:
Після того, як я додав дані DKIM до свого DNS, cPanel скаржитися припинила.
На даний момент окрему IP адресу можна замовити тільки через службу підтримки і
коштує вона 25₴ на весь період дії хостингу. При цьому окрема IPv6 адреса не
надається через відсутність підтримки у cPanel. В майбутньому остання має
почати коректно працювати з новими адресами.
nic.ua працює над тим, щоб додати можливість замовлення цієї послуги через
“персональний кабінет”, але мене попередили, що після цього вартість може
змінитися.
Через обмеження cPanel для одного облікового запису можливо призначити тільки
одну IP адресу і SSL сертифікат можна встановити тільки для одного домену.
cPanel не підтримує SNI, тому використовувати SSL для більш ніж одного домену
(або субдомену) не є можливим.
На серверах встановлено модуль mod_security. Для того, щоб до сайтів не
зверталися погано написані боти, mod_security фільтрує запити за заголовком
User-Agent. Стандартні User-Agent для curl та lwp-request на будь-який запит
будуть отримувати у відповідь 404 Not found, тому, якщо програмне забезпечення
має працювати з сервісом на цьому хостингу, потрібно використовувати власні
унікальні заголовки User-Agent.
Завантаження файлів
Стандартно надається доступ через FTP, підтримується SSL, але сертифікат
самопідписаний. Для того, щоб змусити lftp прийняти цей SSL сертифікат, треба
встановити set ssl:verify-certificate false.
Також cPanel має свій власний WebDAV сервер, але він не дозволяє змінювати
режим доступу до файлів (chmod).
Результат
Мій проект успішно переїхав до nic.ua, і тепер можна спокійно вимикати домашній сервер.
В той час, коли прогресивне людство використовує VPS та створює віртуальні
машини на Amazon, такий тип хостингу продовжує існувати. Для більшості простих
проектів він підходить, не потрібно займатися апаратною частиною власноруч,
перейматися базою даних та тим, що на дешевій VPS можна вийти за межі доступної
оперативної пам’яті.
Однак на хостингу такого типу можна розміщувати тільки проекти, що не
використовують програми для фонової обробки даних (наприклад, celery).
Також хостинг не підійде для проектів, що використовують більше одного домену з SSL.
Дякую співробітнику служби технічної підтримки nic.ua Дмитру Хветкевичу за
допомогу в налаштуванні та вирішенні питань.
However, during my last trip to my VPS to fix my mail system after
opendkim update in Ubuntu 10.04
I found something interesting in netstat:
$ sudo netstat -lntp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address ... PID/Program name
...
tcp 0 0 173.212.238.58:8891 ... 15410/opendkim
Basically, when I specified localhost in opendkim configuration, it was
listening on a public interface instead. Pinging localhost revealed it is
actually a non-loopback address:
It looks like it’s been this way since the very begninning, as my
/etc/hosts had the following:
# Auto-generated hostname. Please do not remove this comment.
173.212.238.58 yankee.lappyfamily.net yankee localhost 204538 localhost.localdomain
And this was clearly a misconfiguration (I am sure 204538 is a good hostname).
I looked at my local Ubuntu installation and updated the VPS so that the hosts
file became:
127.0.0.1 localhost
::1 ip6-localhost ip6-loopback
# Auto-generated hostname. Please do not remove this comment.
173.212.238.58 yankee.lappyfamily.net yankee
After this I restarted all the applications that were supposed to listen on
the loopback interface and verified the fix with netstat again.
First of all, you need to have a firewall configured on your servers and allow
only trusted incoming connections to trusted applications. This is what
prevented my opendkim installation from accepting the incoming requests from
the internet.
Second, you need to verify that localhost actually refers to the loopback
interface and does not resolve to your public one, as you have a fully
qualified name for that purpose.
I found that now the control panel for the VPS I am using correctly generates
the hostname line, but it may not have been the case a year ago when I got the
VPS first configured.
Recently I needed to test the behavior of the function that fetched some
remote resource. I wanted to control how it works and supply my own cached
version stored as a file.
While I originally thought unittest should support this using some sort of a
method to get testdata directory, it is actually quite easy to implement. You
only need to create some folder (I called it “testdata”) in the “tests”
directory and then you can refer to it using plain old reference to __file__:
Disqus is a popular service that provides commenting functionality. All you
need to do for your HTML page to have embedded discussion is to add a bit of
javascript.
They have awesome importers from various blog engines. This blog was originally
hosted on Blogspot, migration to static blog generated by Octopress required me
to search for alternative commenting facilities and I decided to use disqus (is
there anything else, really?) Now I switched to WordPress and I wanted my
comments back.
Disqus does provide the ability to export all the comments in a XML file. Quick
Google search told me that it was possible to get the comments quite easily
into WordPress by converting the disqus dump into WXR, but it is not really
that easy. WordPress will ignore the post if it is already there and will not
import comments. There’s a plugin that imports disqus comments, but I wanted
full support of nested comments and little to no PHP coding.
I dumped all the wordpress database locally and hacked up a script that reads
the comments.xml file and puts the necessary data into the database. The script
needs access to some sort of a database in order to figure out the post_id for
each post and generate the comment identifiers for correct nesting.
...ANONYMOUS_EMAIL='nobody@example.net'# Database configurationDATABASE={'host':'lab.lappyfamily.net','user':'rtg','name':'rtginua6_wp1',}# Admin informationADMIN_INFO={'comment_author':'Roman Yepishev','comment_author_email':'roman.yepishev@yandex.ua','user_id':1}# Names used in disqus that represent administratorADMIN_ALIASES=set(['rtg','Roman','rye'])...
And the script itself runs on an uncompressed disqus XML. The script will skip all pages that it could not find the URLs for:
$ python wp-import-disqus.py comments.xml
Skipping comment for http://rtg.in.ua/app/acer-exif-fixup/index.html
After script finished I dumped the comments table and uploaded it via the phpmyadmin interface.