Category Archives: Administrare

MySQL Backup peste SSH

Update (1 Martie 2011):

  • Suport pentru a specifica un MySQL host. E musai să fie definit minim cu “localhost” pentru o conexiune locală. Va folosi UNIX socket. Pentru TCP socket, se folosește 127.0.0.1 ca adresă.
  • Suport pentru compresie la nivel de protocol SSH. Mă gândeam la asta, dar am activat-o doar pentru MySQL – chestie destul de inutilă pentru o conexiune locală prin UNIX socket. Acum, cu suport pentru host extern, își are rostul.
  • Schimbat criptarea în blowfish-cbc ce este semnificativ mai rapidă pentru SSH.

Backup-ul – un rău necesar. Existența unui server cu skip-networking complică problema. Dar nu o face imposibilă. SSH știe să returneze chestii peste STDOUT. Iar un script pus în cron face ca această pacoste să intre în categoria “set it and forget it”.

Iar dacă tot am vorbit de un script, acesta este:

#!/bin/bash
 
# EDIT
MY_USER=""
MY_PASS=""
MY_HOST="localhost"
SSH_USER=""
SSH_HOST=""
SSH_PORT=""
# END EDIT
 
CDIR=$(dirname $0)
PKEY=$CDIR/private-key.pem
DUMP=$CDIR/dump
DATE=$(date +%F+%H-%M-%S)
 
if [ ! -f $PKEY ]
then
        echo "Error: The SSH private key file is missing!"
        exit 1
fi
 
if [ ! -d $DUMP ]
then
        mkdir $DUMP
fi
 
echo "Fetching the databases ..."
database=($(ssh -C -c blowfish-cbc -i $PKEY -p $SSH_PORT $SSH_USER@$SSH_HOST mysql -C -h$MY_HOST -u$MY_USER -p$MY_PASS -e '"SHOW DATABASES;"'))
count=${#database[@]}
for ((i=1; i<count ; i++))
do
        if [ "${database[$i]}" != "mysql" -a "${database[$i]}" != "information_schema"  ]
        then
                echo "Dumping ${database[$i]} ..."
                ssh -C -c blowfish-cbc -i $PKEY -p $SSH_PORT $SSH_USER@$SSH_HOST mysqldump -v -C -h$MY_HOST -u$MY_USER -p$MY_PASS -x -B ${database[$i]} | nice -n 19 pbzip2 -c > $DUMP/${database[$i]}-$DATE.sql.bz2
        fi
done
 
</count>

Are 5 opțiuni de configurare, destul de evidente. Caută private-key.pem în directorul în care se află – unde private-key.pem este cheia ce face posibilă comunicarea peste SSH fără a folosi autentificare cu parolă. Iar din moment ce se folosește rețeaua, atât mysql cât și mysqldump folosesc protocol compression (flag-ul -C). Dependințele sunt evidente la o primă citire. Nu este cel mai eficient mod de a face backup (rețea, spațiu), dar este simplu de implementat iar câteodată aceasta contează. Ignoră bazele de date mysql și information_schema. A se ignora ultimul tag a-la HTML din blocul de cod de mai sus. GeSHi face fițe.

mysqldump cu rename

O chestie ce lipsește de multicel în mysqldump este opțiunea de a putea da un “rename” la o bază de date în fișierul de dump. Din moment ce nu pot aștepta la infinit pentru ca acest lucru să apară în versiunea standard, rămân două alternative:

  1. aplicații de dump ce știu deja să facă aceasta
  2. un pic de lucru cu sed prin fișierul de dump

Din moment ce nu am găsit decât o chestie în Python ce nu știu exact ce face, am ales a doua variantă, ceva mai rumegabilă. Să zicem că am scriptul dbdump în directorul curent, cu privilegiu de execuție:

#!/bin/sh
mysqldump -u $1 -B $2 --skip-comments -xvnp > $3.sql
sed -i "s/USE \`$2\`;/USE \`$3\`;/g" $3.sql
pbzip2 -fv $3.sql

Invocarea este simplă: ./dbdump mysql_username database_to_dump new_name

Mai departe face totul de unul singur. Dă dump, modifică numele bazei de date și face o compresie cu pbzip2 (Parallel bzip2). Se poate modifica să funcționeze cu bzip2 standard, dar pe servere multi-core și / sau multi-procesor merită efortul de a instala pbzip2.

Opțional, pentru puturoși precum subsemnatul, se mai poate trânti o linie în script ce să mute arhiva pe noul server:

scp -i transfer.pem $3.sql.bz2 transfer@example.com:$3.sql.bz2

Am pus sintaxa pentru oameni plictisiți de parole de SSH, recte subsemnatul, folosind o cheie privată. Nu e musai, dar e mai comod, în special dacă sunt mai multe baze de date de transferat.

Parole aleatoare în Linux, “extended ASCII”

Deși în teorie problema din titlu este una rezolvată, în practică de multe ori mi-am dorit o parolă de o complexitate mai mare față de ce-mi oferă majoritatea uneltelor ce nu ies din standard ASCII. Prin metoda subsemnatului, se ia următoarea funcție și se trântește în .bashrc:

function urandompass()
{
	cat /dev/urandom | tr -dc '[\41-\176\241-\254\256-\376]' | fold -w $1 | head -n 1
}

Apelul este cretin de simplu: urandompass 12 va genera o parolă de 12 caractere. Sau cheie, am zis-o și pe asta. Funcția poate fi folosită pentru a genera chei pentru un “block cipher”.

Cu toate acestea, pentru cei cu terminalul setat pe UTF-8, caracterele dincolo de range-ul celui de-al 7-lea bit (aka cele ce intră în extended ASCII) nu vor fi printate corect. Teoria spune faptul că UTF-8 este compatibil doar cu setul ASCII, ăla standard. În prealabil este nevoie să se seteze terminalul pe un char encoding single-byte ce implementează vreo formă de extended ASCII gen ISO-8859-x, unde x = 1 este Western, x = 2 = Central European, etc. Personal prefer Western. Pentru cei cu Gnome Terminal, “Terminal » Set Character Encoding » Add or Remove …” dacă e pentru prima dată, dacă nu, atunci ar trebui să poată fi ales din meniu un encoding diferit de cel implicit. Buba este faptul că Gnome Terminal are memorie destul de proastă deci va da reset la UTF-8 dacă terminalul este închis sau se execută reset, deci operația trebuie repetată înainte de a genera orice parolă / cheie.

Acum urmează partea pentru obsedații de detalii aka cei după chipul și asemănarea subsemnatului. “Limitarea” la extended ASCII, ce oricum oferă o complexitate mai bună decât majoritatea generatoarelor, provine de fapt din tr. Un info coreutils ‘tr invocation’ în shell specifică destul de clar: “Currently `tr’ fully supports only single-byte characters.”, deci suportă multi-byte din jumătatea lui cinci. Pentru amatorii de detalii, acele range-uri din funcția mea au fost documentate, oarecum, în prealabil.

În DEC:

33-126
161-172
174-254

În OCT:

41-176
241-254
256-376

Evident, cu ochiul liber se poate vedea că tr primește octal, precum zice manualul. Pentru a le determina am încercat cu encoding ISO-8859-1 și ISO-8859-2 o aplicație ce printează tot range-ul single-byte (mă rog, fără ultimul caracter).

Pentru PHP-iști:

for ($i = 0; $i < 256; $i++)
{
	echo $i.': '.chr($i)."\n";
}

Iar pentru snobii cu C, avem alternativă 😀 :

#include 
 
int main()
{
        for (int i = 0; i < 256; i++)
        {
                printf("%d: %c\n", i, i);
        }
        return 0;
}

gcc -std=c99 -o chars chars.c

Inițial m-am inspirtat dintr-o versiune *sh de pe linuxquestions.org, dar arată cretin, nu dau paste la așa ceva. Mă cam strânge octalul de dinți. O versiune rescrisă pentru DEC junkies:

#!/bin/bash
 
chr()
{
	printf \\$(printf '%03o' $1)
}
 
i=0
while [ $i -lt 256 ]; do
	echo "$i: `chr $i`"
	let "i++"
done

Pentru cei ce preferă alte encoding-uri single-byte, dacă range-urile de mai sus nu sunt potrivite, atunci se poate rula oricând vreo aplicație de mai sus pentru a face un test de char printing.

Pentru cei ce nu s-au născut cu “DEC to OCT converter” în cap, așa ca subsemnatul, Calculator din Gnome (gcalctool) are și un mod Programming (Ctrl+P).

Căi UNIX corecte în PHP

Se dă următoarea problemă, aparent mai simplă decât zice titlul: să se determine dacă o cale de tip UNIX este corectă. Buba este faptul că această cale poate să fie către un fișier ce nu există, spre exemplu o cale de UNIX socket ce va fi creat într-un director ce va conține un pool de socket-uri. Deci funcțiile la nivel de filesystem nu vor ajuta, cel puțin nu în primă fază. În cazul  în care există vreo încercare de intuiție a scopului, da, scriu server scripting și în PHP. *sh este prea limitat pentru anumite chestii mai avansate, sau are nevoie de prea mult “boilerplate code”, pe când în Perl nu sunt încă fluent. Pentru cei ce au pierdut știrile de la ora 5, PHP nu mai este de mult un limbaj strict “web oriented”, funcționează bine merci și pentru “general purpose”.

Să revin la problema inițială. Practic se pot identifica două probleme: a) validarea unei căi inexistente – deci apelăm la regex; b) validarea directorului – deși fișierul poate să nu existe în timpul execuției de validare, directorul e musai să fie acolo. De regulă aplicațiile care creează câte un UNIX socket nu verifică aceasta și vor eșua să pornească.

Rezumat, codul arată cam așa:

$validate = preg_match('/^(\/[^\0]*?\/?)[^\0\/]+$/', $path, $matches);
if (empty($validate) === TRUE OR is_dir($matches[1]) !== TRUE OR is_dir($matches[0]) !== FALSE)
{
	// handle error here
}

Acum, discuție pe marginea textului. Acest regex satisface direct ambele probleme. Prima, este fix problema de validare. Se ține cont doar de căi absolute – acesta fiind contextul în care operez. Cu toate acestea, soluția se poate adapta ușor pentru căi relative.

Singurul capturing group din expresie preia întreaga cale, mai puțin numele fișierului. A fost făcută să funcționeze inclusiv pentru root (/). Calea capturată trebuie să fie către un director, pe când calea întreagă trebuie să nu fie un director.

Teoria căilor este simplă. O cale UNIX poate să conțină orice caracter, mai puțin nul, iar / este folosit pe post de separator de directoare, deci este rezervat acestui scop. Căile de tip //var///www//// sunt valide. Exemplul anterior va duce în //var/www unde // în general (Linux, BSD) este tot una cu /. Nu am lucrat pe sisteme unde // să fie distinct față de /, deci nu am considerat că este nevoie să tratez cazul prin expresia de mai sus.