Блог Загирова Рустама

Около-интернетные заметки

Яндекс.танк — инструмент нагрузочного тестирования

| Комментарии

28 июля на я.субботнике был представлен новый инструмент для нагрузочного тестирования Яндекс.танк. Это внутреняя разработка яндекса, которая наконец-то вышла в свет. Видел я этот танк ещё на YaC 2011, когда были соревнования по конфигурированию nginx.

Это консольный инструмент, пока не имеющий графического интерфейса, но дающий довольно полную картину в этой самой консоли.

Yii: рецепты №2

| Комментарии

Продолжаю делится интересным о Yii

Шифрование данных

Иногда требуется зашировать данные с возможностью последующей обратной дешифровкой.

В yii есть отличная обёртка для такого рода операций: CSecurityManager::encrypt() и CSecurityManager::decrypt()

Настраиваем алгоритм, режим и ключ шифрования.

Yii: рецепты №1

| Комментарии

Пакетирование js и css-файлов и использование зависимостей между этими пакетами.

Есть замечательный инструмент для рисования графиков на js — highcharts, но он использует фреймворк jQuery и сам jQuery не подключает. Соответственно, мы создаём наш пакет, где указываем js и css файлы из highcharts и прописываем зависимость от jQuery.

Редирект при вставки сайта через Iframe

| Комментарии

Довольно долго бился c запрещением вставки сайта в iframe с указанием белого списка сайтов, которые могу это сделать

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (top != self) {
    var white_list = ['yandex.ru', 'google.com'];
    var isFriend = false;
    var hostname = document.referrer.split("/")[2].split(":")[0];
    for (var i=0; i < white_list.length; i++) {
        if (hostname == white_list[i] || hostname == "www." + white_list[i]) {
            isFriend = true;
            break;
        }
    }
    if ( ! isFriend ) {
        window.top.location = window.location.href;
    }
}

Эмуляция хоткея с участием Alt в Midnight Commander

| Комментарии

После перехода на Mac OS X, не как не мог отвыкнуть от использования хоткеев с участием клавиши Alt.

Нашёл наконец-то способ, как съэмулировать хоткеи в сочении с Alt. Например, чтобы сделать действие по Alt+c нужно последовательно нажать Esc и c. «А ларчик просто открывался» ©.

Тех, кто не знаком с хоткеями в mc, предлагаю ознакомиться с листингом горячих сочетаний mc.

Bacula - резервное копирование: быстро, бесплатно, без смс

| Комментарии

Не секрет, что админы делятся на 2 типа: кто ещё не делает бэкапы и кто их уже делает. Я совсем недавно перешёл на сторону светлых админов, хочу поделиться реально работающими конфигами. Теперь седина пропала и мои волосы вновь мягкие и шелковистые =)

Использовать буду систему под названием bacula. Соответственно всё проверялось и работает под ОС GNU/Debian 6.

В интернете видел много довольно полных мануалов, где описывается конфигурация. Я описывать почти ничего не буду, просто приведу рабочие конфиги и скажу что копировать, чтобы начать бэкапить с ещё одного сервера. Можно считать статью предназначенной для тех, кто не осилил man, ну или хочет съэкономить своё время и получить работающую систему резервирования “побыстрее”.

Имеется 2 машины

  • home.zagirov.name (1.1.1.1) – сам сервер bacula (bacula director и storage director), mysql + www
  • www.zagirov.name (2.2.2.2) – mysql + www

Для mysql будем использовать собственный скрипт, который запускает xtrabackup.

Устанавливаем на обоих серверах sudo (чтобы запускать бэкапилку базы от рута через sudo) и file director

1
aptitude install sudo bacula-fd openssh-server

А на сервере, который будет хранить все бэкапы (home.zagirov.name – 1.1.1.1) установим ещё bacula director и storage director:

1
aptitude install bacula-director-mysql bacula-sd-mysql bacula-console

Выполняем на обоих серверах:

1
usermod -s /bin/bash bacula

Выполняем на home.zagirov.name (1.1.1.1)

1
2
3
4
5
6
su - bacula
mkdir -p ~/.ssh
chmod 700 ~/.ssh
cd ~/.ssh
ssh-keygen -b 4096 -t rsa -f bacula -N ""
chmod 400 ~/.ssh/*

Добавляем правило для пользователя bacula запускать xtrabackup под sudo без запрашивания пароля.

1
echo "bacula ALL=NOPASSWD: /usr/bin/xtrabackup" > /etc/sudoers.d/bacula

Создаём файлы для создания бэкапов. Храниться они будут централизовано на сервере бэкапов, а запускаться на удалённых машинах будут через sudo bash -s

/etc/bacula/scripts/xtrabackup.sh

1
2
3
4
5
6
7
8
9
10
#!/bin/sh

DIR="/bacula/create/xtrabackup"
if [ -d "$DIR" ]; then
  rm -r $DIR
fi
mkdir -p $DIR

sudo xtrabackup --defaults-file=/etc/mysql/my.cnf --datadir=/var/lib/mysql \
 --target-dir=$DIR --backup

/etc/bacula/scripts/xtrabackup_rm.sh

1
2
3
4
#!/bin/sh

DIR="/bacula/create/xtrabackup"
rm -r $DIR

home.zagirov.name: /etc/bacula/bacula-dir.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
Director {
  Name = home.zagirov.name-dir
  DIRport = 9101
  QueryFile = "/etc/bacula/scripts/query.sql"
  WorkingDirectory = "/var/lib/bacula"
  PidDirectory = "/var/run/bacula"
  Maximum Concurrent Jobs = 1
  Password = "password-director"
  Messages = Daemon
  DirAddress = 1.1.1.1
}

Catalog {
  Name = MyCatalog
  dbname = "bacula"; DB Address = ""; dbuser = "bacula"; dbpassword = "password_for_db"
}

Console {
  Name = home.zagirov.name-mon
  Password = "password-console"
  CommandACL = status, .status
}


Messages {
  Name = Daemon
  mailcommand = "/usr/lib/bacula/bsmtp -h localhost -f \"\(Bacula\) \<%r\>\" -s \"Bacula daemon message\" %r"
  mail = root@localhost = all, !skipped            
  console = all, !skipped, !saved
  append = "/var/lib/bacula/log" = all, !skipped
}

Messages {
  Name = Standard
  mailcommand = "/usr/lib/bacula/bsmtp -h localhost -f \"\(Bacula\) \<%r\>\" -s \"Bacula: %t %e of %c %l\" %r"
  operatorcommand = "/usr/lib/bacula/bsmtp -h localhost -f \"\(Bacula\) \<%r\>\" -s \"Bacula: Intervention needed for %j\" %r"
  mail = rustam@zagirov.name = all, !skipped            
  operator = rustam@zagirov.name = mount
  console = all, !skipped, !saved
  append = "/var/lib/bacula/log" = all, !skipped
  catalog = all
}

Storage {
  Name = File
  Address = 1.1.1.1
  SDPort = 9103
  Password = "password-storage"
  Device = FileStorage
  Media Type = File
}

#Расписания
Schedule {
  Name = "WeeklyCycle"
  Run = Full 1st sun at 02:05
  Run = Differential 2nd-5th sun at 02:05
  Run = Incremental mon-sat at 02:05
}

Schedule {
  Name = "WeeklyCycleAfterBackup"
  Run = Full sun-sat at 02:10
}


Pool {
  Name = Default
  Pool Type = Backup
  Recycle = yes                       # Bacula can automatically recycle Volumes
  AutoPrune = yes                     # Prune expired volumes
  Volume Retention = 365 days         # one year
}

Pool {
  Name = File
  Pool Type = Backup
  Recycle = yes                       # Bacula can automatically recycle Volumes
  AutoPrune = yes                     # Prune expired volumes
  Volume Retention = 365 days         # one year
  Maximum Volume Jobs = 1
  Maximum Volume Bytes = 10G          # Limit Volume size to something reasonable
  Maximum Volumes = 100               # Limit number of Volumes in Pool
}


# Scratch pool definition
Pool {
  Name = Scratch
  Pool Type = Backup
}



@/etc/bacula/client_home.zagirov.name.conf
@/etc/bacula/client_www.zagirov.name.conf

home.zagirov.name: /etc/bacula/client_home.zagirov.name.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
### backup for home.zagirov.name


Client {
  Name = home.zagirov.name-fd
  Address = 1.1.1.1
  FDPort = 9102
  Catalog = MyCatalog
  Password = "password-fd-home.zagirov.name"
  File Retention = 30 days            # 30 days
  Job Retention = 6 months            # six months
  AutoPrune = yes                     # Prune expired Jobs/Files
}


JobDefs {
  Name = "DefaultJob"
  Type = Backup
  Level = Incremental
  Client = home.zagirov.name-fd
  Schedule = "WeeklyCycle"
  Storage = File
  Messages = Standard
  Pool = File
  Priority = 10
  Write Bootstrap = "/var/lib/bacula/%c.bsr"
}

Job {
  Name = "RestoreFiles"
  Type = Restore
  Client=home.zagirov.name-fd
  FileSet="home.zagirov.name-www"
  Storage = File
  Pool = Default
  Messages = Standard
  Where = /bacula/restore/
}


Job {
  Name = "BackupCatalog"
  JobDefs = "DefaultJob"
  Level = Full
  FileSet="Catalog"
  Schedule = "WeeklyCycleAfterBackup"
  RunBeforeJob = "/etc/bacula/scripts/make_catalog_backup.pl MyCatalog"
  RunAfterJob  = "/etc/bacula/scripts/delete_catalog_backup"
  Write Bootstrap = "/var/lib/bacula/%n.bsr"
  Priority = 11
}

FileSet {
  Name = "Catalog"
  Include {
    Options {
      signature = MD5
    }
    File = "/var/lib/bacula/bacula.sql"
  }
}


Job {
  Name = "home.zagirov.name-www"
  JobDefs = "DefaultJob"
  FileSet = "home.zagirov.name-www"
}


FileSet {
  Name = "home.zagirov.name-www"
  Include {
    Options {
      signature = MD5
    }
    File = /var/www
  }
}



Job {
  Name = "home.zagirov.name-MySQL"
  JobDefs = "DefaultJob"
  Level = Full
  FileSet="home.zagirov.name-MySQL"
  Schedule = "WeeklyCycle"
  RunBeforeJob = "/etc/bacula/scripts/xtrabackup.sh"
  RunAfterJob  = "/etc/bacula/scripts/xtrabackup_rm.sh"
  Write Bootstrap = "/var/lib/bacula/%n.bsr"
  Priority = 11                   # run after main backup
}

FileSet {
  Name = "home.zagirov.name-MySQL"
  Include {
    Options {
      signature = MD5
    }
    File = "/bacula/create/xtrabackup"
  }
}

home.zagirov.name: /etc/bacula/client_www.zagirov.name.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
### backup from www.zagirov.name

Client {
  Name = www.zagirov.name-fd
  Address = 2.2.2.2
  FDPort = 9102
  Catalog = MyCatalog
  Password = "password-fd-www.zagirov.name"
  File Retention = 30 days            # 30 days
  Job Retention = 6 months            # six months
  AutoPrune = yes                     # Prune expired Jobs/Files
}

JobDefs {
  Name = "DefaultJob-www.zagirov.name"
  Type = Backup
  Level = Incremental
  Client = www.zagirov.name-fd
  Schedule = "WeeklyCycle"
  Storage = File
  Messages = Standard
  Pool = File
  Priority = 10
  Write Bootstrap = "/var/lib/bacula/%c.bsr"
}

Job {
  Name = "Restore-www.zagirov.name"
  Type = Restore
  Client=www.zagirov.name-fd
  FileSet="www.zagirov.name"
  Storage = File
  Pool = Default
  Messages = Standard
  Where = /bacula/restore/
}



Job {
  Name = "www.zagirov.name-www"
  JobDefs = "DefaultJob-www.zagirov.name"
  FileSet = "www.zagirov.name-www"
}


FileSet {
  Name = "www.zagirov.name-www"
  Include {
    Options {
      signature = MD5
    }
    File = /var/www/www.zagirov.name/www
  }
}



Job {
  Name = "www.zagirov.name-MySQL"
  JobDefs = "DefaultJob-www.zagirov.name"
  Level = Full
  FileSet="www.zagirov.name-MySQL"
  Schedule = "WeeklyCycle"
  RunBeforeJob = "ssh -i /var/lib/bacula/.ssh/bacula bacula-www.zagirov.name 'sudo bash -s' </etc/bacula/scripts/xtrabackup.sh"
  RunAfterJob  = "ssh -i /var/lib/bacula/.ssh/bacula bacula-www.zagirov.name 'sudo bash -s' < /etc/bacula/scripts/xtrabackup_rm.sh"
  Write Bootstrap = "/var/lib/bacula/%n.bsr"
  Priority = 11
}

FileSet {
  Name = "www.zagirov.name-MySQL"
  Include {
    Options {
      signature = MD5
      compression = GZIP
    }
    File = "/bacula/create/xtrabackup"
  }
}

home.zagirov.name: bacula-sd.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Storage {
  Name = home.zagirov.name-sd
  SDPort = 9103
  WorkingDirectory = "/var/lib/bacula"
  Pid Directory = "/var/run/bacula"
  Maximum Concurrent Jobs = 20
  SDAddress = 1.1.1.1
}

Director {
  Name = home.zagirov.name-dir
  Password = "password-storage"
}

Director {
  Name = home.zagirov.name-mon
  Password = "password-mon"
  Monitor = yes
}

Device {
  Name = FileStorage
  Media Type = File
  Archive Device = /bacula/
  LabelMedia = yes;                   # lets Bacula label unlabeled media
  Random Access = Yes;
  AutomaticMount = yes;               # when device opened, read it
  RemovableMedia = no;
  AlwaysOpen = no;
}

Messages {
  Name = Standard
  director = home.zagirov.name-dir = all
}

home.zagirov.name: /etc/bacula/bacula-fd.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Director {
  Name = home.zagirov.name-dir
  Password = "password-fd-home.zagirov.name"
}

Director {
  Name = home.zagirov.name-mon
  Password = "password-mon-fd-home.zagirov.name"
  Monitor = yes
}


FileDaemon {
  Name = home.zagirov.name-fd
  FDport = 9102
  WorkingDirectory = /var/lib/bacula
  Pid Directory = /var/run/bacula
  Maximum Concurrent Jobs = 20
  FDAddress = 1.1.1.1
}

Messages {
  Name = Standard
  director = home.zagirov.name-dir = all, !skipped, !restored
}

home.zagirov.name: /etc/bacula/bconsole.conf

1
2
3
4
5
6
Director {
  Name = localhost-dir
  DIRport = 9101
  address = 1.1.1.1
  Password = "password-director"
}

А на удалённом настраиваем только fd

www.zagirov.name: /etc/bacula/bacula-fd.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Director {
  Name = home.zagirov.name-dir
  Password = "password-fd-www.zagirov.name"
}

Director {
  Name = stamm-server-mon
  Password = "password-mon-fd-www.zagirov.name"
  Monitor = yes
}

FileDaemon {
  Name = www.zagirov-name-fd
  FDport = 9102
  WorkingDirectory = /var/lib/bacula
  Pid Directory = /var/run/bacula
  Maximum Concurrent Jobs = 20
  FDAddress = 109.234.152.187
}

Messages {
  Name = Standard
  director = home.zagirov.name-dir = all, !skipped, !restored
}

Внимание!

Не забываем поменять пароли на свои!

Запускаем в консоле bconsole

1
2
3
4
5
6
7
8
9
10
11
12
13
14
*label
Automatically selected Storage: File
Enter new Volume name: backup
Defined Pools:
     1: Default
     2: File
     3: Scratch
Select the Pool (1-3): 2
Connecting to Storage daemon File at 95.31.29.182:9103 ...
Sending label command for Volume "backup" Slot 0 ...
3000 OK label. VolBytes=210 DVD=0 Volume="backup" Device="FileStorage" (/bacula/)
Catalog record for Volume "backup", Slot 0  successfully created.
Requesting to mount FileStorage ...
3906 File device "FileStorage" (/bacula/) is always mounted.

Восстановление происходит тоже через команду bconsole. Например, мы захотели восстановить файлы веб-сайта с www.zagirov.name на home.zagirov.name:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
*restore
Automatically selected Catalog: MyCatalog
Using Catalog "MyCatalog"

First you select one or more JobIds that contain files
to be restored. You will be presented several methods
of specifying the JobIds. Then you will be allowed to
select which files from those JobIds are to be restored.

To select the JobIds, you have the following choices:
     1: List last 20 Jobs run
     2: List Jobs where a given File is saved
     3: Enter list of comma separated JobIds to select
     4: Enter SQL list command
     5: Select the most recent backup for a client
     6: Select backup for a client before a specified time
     7: Enter a list of files to restore
     8: Enter a list of files to restore before a specified time
     9: Find the JobIds of the most recent backup for a client
    10: Find the JobIds for a backup for a client before a specified time
    11: Enter a list of directories to restore for found JobIds
    12: Select full restore to a specified Job date
    13: Cancel
Select item:  (1-13): 5
Defined Clients:
     1: home.zagirov.name-fd
     2: www.zagirov.name-fd
Select the Client (1-2): 2
The defined FileSet resources are:
     1: www.zagirov.name
     2: www.zagirov.name-MySQL
Select FileSet resource (1-2): 1
+-------+-------+----------+------------+---------------------+------------+
| JobId | Level | JobFiles | JobBytes   | StartTime           | VolumeName |
+-------+-------+----------+------------+---------------------+------------+
|    66 | F     |    3,327 | 32,960,325 | 2011-11-19 03:47:28 | Backup     |
|   138 | D     |        7 |  5,973,688 | 2011-11-27 02:08:41 | Backup     |
+-------+-------+----------+------------+---------------------+------------+
You have selected the following JobIds: 66,138

Building directory tree for JobId(s) 66,138 ...  ++++++++++++++++++++++++++++++++++++++++++++
2,959 files inserted into the tree.

You are now entering file selection mode where you add (mark) and
remove (unmark) files to be restored. No files are initially added, unless
you used the "all" keyword on the command line.
Enter "done" to leave this mode.

cwd is: /
$ add *
3,327 files marked.
$ done
Bootstrap records written to /var/lib/bacula/home.zagirov.name-dir.restore.1.bsr

The job will require the following
   Volume(s)                 Storage(s)                SD Device(s)
===========================================================================
   
    Backup                    File                      FileStorage              

Volumes marked with "*" are online.


3,327 files selected to be restored.

The defined Restore Job resources are:
     1: home.zagirov.name-restore
     2: www.zagirov.name-restore
Select Restore Job (1-2): 1
Run Restore job
JobName:         home.zagirov.name-restore
Bootstrap:       /var/lib/bacula/home.zagirov.name-dir.restore.1.bsr
Where:           /bacula/restore/
Replace:         always
FileSet:         home.zagirov.name-105
Backup Client:   www.zagirov.name-fd
Restore Client:  www.zagirov.name-fd
Storage:         File
When:            2011-11-27 02:56:27
Catalog:         MyCatalog
Priority:        10
Plugin Options:  *None*
OK to run? (yes/mod/no): y
Job queued. JobId=143
You have messages.

Для добавления нового сервера необходимо добавить в конфиг bacula director инклуд нового файла, который будет содержать следующие директивы:

  • Client, в который прописываем имя, ip и пароль
  • JobDefs – исходный джоб, от которого все будут наследоваться
  • Job восстановления впринципе не обязательно прописывать, но это удобно, когда нужно восстанавливать прямо на нужный сервер
  • Job и FileSet – собственно сам джоб и список файлов для этого джоба Думаю, теперь bacula кажется не такой уж и страшной как в начале?

Ссылки:

Nginx: общие принципы конфигурации

| Комментарии

На днях посмотрел видео с участием Игоря Сысоева – отца русского nginx =)

В выступлении Игорь говорит не о мастабировании в привычном для всех понимании (высокая нагрузка), а в плане рекомендаций к написанию конфигурации для nginx, чтобы при росте конфигурации не было проблем с его редактированием.

Виды location:

  1. Описанные простыми строками (статические)
1
2
3
location /dir/ {} - обычный префиксный location
location = /dir/ {} - точное совпадение по запросу
location ^~ /dir/ {} - префиксный location, но после него не идёт проверка по location на регулярных выражениях
  1. Описанные регулярными выражениями
1
2
location ~ /dir/ {} - с учётом регистра
location ~* /dir/ {} - без учёта регистра
  1. Именнованные location
1
location @php {}

Расположение статических location не играет роли.

Location’ы, написанные с помощью регулярных выражений, выполянются в порядке написания.

По возможности используйте статические location’ы. Например, вместо

1
location ~ .(css|js|jpe?g|png)$ {}

выносить все статические файлы в определённый каталог

1
location /static/ {}

Заворачивание location на регулярном выражении в статический location

1
2
3
location ~* ^/i/(.)(.-\.gif)(
  alias /images/$1$1$2
)
1
2
3
4
5
6
location /i/ {
  location ~* ^/i/(.)(.-\.gif)$ {
    alias /images/$1$1$2
  }
  return 404
}

Понимание директив root и alias:

В общем смысле, root – добавляем в запрос путь в качестве префикса, а alias – заменяет location на указанный в директиве alias

1
2
3
location /dir/ {
    root /path/to/data;
}
1
2
/dir/a/image.gif
/path/to/data/dir/a/image.gif
1
2
3
location /dir/ {
     alias /path/to/data/;
}
1
2
/dir/a/image.gif
/path/to/data/a/image.gif
1
2
3
location ~ ^/dir/ {
  root /path/to/$subdomain;
}
1
2
/dir/a/image.gif
/path/to/shop/dir/a/image.gif
1
2
3
location ~ ^/dir/.+/(.)(.*)$ {
    alias /path/to/data/$1/$1$2;
}
1
2
/dir/a/image.gif
/path/to/data/i/image.gif
1
2
3
location ~ ^/dir/ {
    alias /path/to/data/0.gif
}
1
2
/dir/a/image.gif
/path/to/data/0.gif

Обратите внимание на конечных слэш в proxy_pass

1
2
3
location /dir/ {
  proxy_pass http://back
}
1
2
/dir/a/image.gif
/dir/a/image.gif
1
2
3
location /dir/ {
    proxy_pass http://back/
}
1
2
/dir/a/image.gif
    /a/image.gif

Большинство директив носит декларативный характер, а это значит нет разницы, где их определять.

1
2
3
4
5
6
server {
    location / {}
    location /images/ {}
    
    root /path/;
}

Не рекомендуется использовать if. В примере сработает только последний if. Игорь рекомендует использовать if в связке с return.

1
2
3
4
5
6
7
8
9
10
location / {

  if (1) {
    gzip off;
  }

  if (1) {
    keepalive off;
  }
}

Распространённые ошибки:

В этом примере break вообще не нужен, а expires можно вынести в location

1
2
3
4
5
6
7
location ~* \.(gif|jpe?g|png)$ {
  root /data;
  if (-e $request_filename) {
     expires 1y;
     break;
  }
}

Break тут не нужен.

1
2
3
4
location ~* \.(gif|jpe?g|png)$ {
   root /data;
   break;
}

Такое возникает у переходящих с apache. Тут нужно заменить всё на статические location.

1
2
3
4
5
location / {
  if ($uri ~ ^/login.php$) { }

  if ($uri ~ ^/image.php$) { }
}

Yii: автодополнение в консоли

| Комментарии

Очень не хватало автодополнения комманд при вызове консольных комманд yii, чувствовал какую-то неполноценность yii в bash.

На просторах интернета была найдена статья, позволяющая реализовать автодополнение с помощью родной unix-утилиты bash_completion.

Если у вас проект находиться под управлением git, то просто добавляем сабмодуль:

1
git submodule add git://github.com/Stamm/yii-console-completion protected/extensions/complete/

Или создайте файл LCompleteCommand.php в protected/extensions/complete/

Теперь подключаем класс в конфигурационном файле для консольного приложения (обычно это console.php):

1
2
3
4
5
6
'commandMap' => array(
    'complete' => array(
        'class' => 'ext.complete.LCompleteCommand',
        //'bashFile' => '/etc/bash_completion.d/yii_applications' //Defaults to </etc/bash_completion.d/yii_applications>. May be changed if needed
    ),
),

Пути до директории bash-completion могут различаться в зависимости от системы. Для Debian и Ubuntu можно оставить стандартный путь. В Mac OS X bash-completion был установлен с помощью homebrew, так что путь нужно сменить на /usr/local/etc/bash_completion.d/yii_applications

Теперь выполняем комманду для создания bash-completion файла от root:

1
sudo ./yiic complete install

Теперь при создании новой сессии в bash будет работать автодополнение для yiic:

  1. Для приложения — набор возможных команд
  2. Для команды — набор возможных действий (actions) и именованых параметров действия по умолчанию
  3. Для действия — подсказки по ее именованым параметрам

Источник: http://habr-sandbox.livejournal.com/230319.html

Организация рабочего потока: Phpstorm, Redmine, Changelists

| Комментарии

Сегодня я покажу как у меня получается сделать работу удобной с использованием Phpstorm, Redmine, Teamcity.

  • Есть задача в redmine.
  • Я начинаю её выполнять.
  • Всё проверяю локально и коммичу в репозиторий.
  • Выливаю изменения на тестовый сервер.

Есть несколько ньюансов. Бывают задачи очень объёмные и/или не очень срочные, которые я делаю в перерывах между задачами с более высокими приоритетами. Или спокойно делаю задачу, но прибегает менеджер с огромными глазами и криками, что сайт выдаёт ошибку, и нужно сделать быстрый hotfix.

В этом случае очень помогают changelist в PhpStorm.

Смысл changelist’а заключается в логическом разделении группы файлов для коммита. Т.е. файлы “прикрепляются” к определённому changelist’у. И при коммите мы выбираем нужный changelist и коммитим только файлы из этого changelist’а. Changelist есть всегда, по-умолчанию название default.

Настраиваем Redmine:

Нужно включить в параметрах redmine пункт Enable REST web service

phpstorm-changelist

Добавляем новый Task server в PhpStorm. Api token можно найти в личном разделе в redmine.

phpstorm-changelist

И добавляем парсинг номера задачи из описания коммита для отображения её как ссылки на задачу в redmine.

Project Settings → Version Control → Issue Navigation

Вводим паттерн регулярного выражения #(\d+) и генерируемую ссылку: **http://redmine.company.com/issues/$1**

phpstorm-changelist

Теперь открываем задачу Tools → Task → Open. Тут работает автодополнение для имени задачи.

Если были открытые файлы, то они все скроются. Сейчас мы работаем в своём наборе открытых файлов. Если откроем changelist под названием default, то все открытые файлы вернуться в редактор. Нужно понимать, что изменения в скрытых файлах остаются (это вам не git stash), а просто скрываются из вида.

Мы можем создать множество changelist’ов. Они не обязательно создаются из задачи, можно создать свой changelist. Я, например, создаю changelist NO для временных файлов, файлов своих настроек, декларативные скрипты для работы автодополнения, которые мне не нужно включать в коммит.

Теперь при коммите будут выбраны только файлы, которые находяться в выбранном changelist’е.

Откройте в разделе changes (alt+9 или ⌘+9) вкладку Local. Тут показаны изменённые файлы, сгруппированные по changelist’ам. Здесь файлы можно перемещать между changelist’ами.

phpstorm-changelist

Кликнув правой кнопкой по changelist, вы можете изменить его комментарий. Этот комментарий потом автоматически вставиться в описание коммита.

Для запуска сборки проекта в TeamCity из PhpStorm нужно поставить соответсвующий плагин.

Получилась вот такая схема работы. Ребята из JetBrains очень крутые: развивают продукт очень стремительно, вводять очень удобные фишки. Кто ещё не пробывал PhpStorm – обязательно попробуйте. А на него перешёл с Netbeans и нисколько не жалею.

Yii: используем кэш в CActiveDataProvider

| Комментарии

Репост моей заметки из wiki: http://www.yiiframework.com/wiki/233/using-cache-in-cactivedataprovider/.

Первый параметр в конструкторе CActiveDataProvider может быть строковым значением с названием модели или экземпляр класса модели.

Поэтому можно использовать CActiveRecord::cache() для кэширования, но нужно установить значение 3 у третьего параметра, потому что мы должны закэшировать 2 запроса: получение количества записей и само получение записей.

Не забудьте использовать зависимости для принудительного протухания кэша.

1
2
3
4
5
6
7
8
9
10
11
$dependecy = new CDbCacheDependency('SELECT MAX(update_time) FROM {{post}}')

CActiveDataProvider(Post::model()->cache($duration, $dependecy, 2), array (
    'criteria' => array (
        'condition' => 'status = 1',
        'order' => 'DESC create_time',
    )
    'pagination' => array (
        'pageSize' => 20,
    )
));