original in en Erdal Mutlu
en to pl Radosław
Wesołowski
Erdal jest jednym z tureckich redaktorów LF. Obecnie pracuje jako administrator
systemu w Linotype Library. Od czasów
akademickich jest wielkim fanem Linuksa i aktywnie rozwija ten system.
scp /path/to/the/file/file1 user@remote_host:/remotedir/newfile
W tym przykładzie plik o nazwie file1 zostanie skopiowany z lokalnego komputera na komputer o adresie 'remote_host' ( zamiast nazwy możesz użyć adresu IP ) do katalogu '/remoredir'. Plik będzie dostępny pod nazwą 'newfile'. Zostaniesz poproszony o podanie hasła dla użytkownika 'user'. Jeżeli autentykacja powiedzie się i zgadzają się prawa dostępu, plik zostanie skopiowany. Można pominąć nazwę pliku na docelowej maszynie i wtedy nie zostanie ona zmieniona, mechanizm ten daje ci to możliwość zmiany nazwy pliku podczas kopiowania. Proces może odbywać się także w drugą stronę:scp user@remote_host:/remotedir/file /path/to/local/folder/newfile
Kolejną cechą programu scp jest możliwość kopiowania całych katalogów. Parametr '-r' określający rekursywne kopiowanie służy właśnie do tego celu.scp -r user@remote_host:/remotedir .
Wydanie powyższego polecenia spowoduje skopiowanie zawartości katalogu 'remotedir' z hosta 'remote_host' do twojego katalogu domowego, nazwy pozostaną niezmienione.ssh erdal@helvetica.fonts.de
W zależności od twojej konfiguracji możesz zostać poproszony o podanie hasła lub hasła-frazy. W powyższym przykładzie łączymy się z komputerem 'helvetica.fonts.de' na konto użytkownika o nazwie 'erdal'. ssh posiada mnóstwo dodatkowych opcji, które mogą być wykorzystane w zależności od potrzeb. Aby dowiedzieć się więcej zapoznaj się z dokumentacja dostępną w twoim systemie (man ssh).ssh erdal@helvetica.fonts.de df -H
Składnia polecenia jest podobna do logowania, z tym, że po nazwie hosta występuje polecenie, które ma zostać wykonane ( w naszym przypadku 'df -H' ). Dane wyjściowe pojawią się na twoim terminalu.ssh-keygen -b 1024 -t dsa
Na początku zostaniesz zapytany o nazwę klucza prywatnego, natomiast nazwa klucza publicznego zwykle jest taka sama jak prywatnego, ale z końcówką '.pub'. Parametr '-b 1024' określa liczbą bitów użytych do wygenerowania klucza. Jeżeli zostawisz to miejsce puste, program użyje standartowej wartości. Typ klucza został określony przy pomocy parametru '-t dsa', jednak istnieją inne wartości, które mogą zostać użyte np. 'rsa1' dla protokołu w wersji pierwszej, lub 'rsa' i 'dsa' dla protokołu w wersji drugiej. Zalecam stosowanie drugiej wersji protokołu SSH, jednak, jeśli trafisz na stary serwer wspierający jedynie pierwszą wersję musisz stworzyć klucze używając '-t rsa1'. W sytuacji, kiedy zaistnieje taka konieczności możesz wymusić używanie wersji 1 lub 2 protokołu odpowiednio przez parametr '-1', lub '-2'.W tym miejscu możemy już zestawić bezpieczne połączenie pomiędzy komputerami,
kopiować pliki i wykonywać polecenia, ale, żeby wykonywać niektóre prace
automatycznie należy wyeliminować wpisywanie hasła, lub hasła-frazy. Rozwiązaniem
może być pisanie w każdym skrypcie wymaganego hasła, co zresztą nie jest dobrym
pomysłem. Lepszym rozwiązaniem, jest użycie agenta kluczy, który zajmie się
tym zadaniem. 'ssh-agent' jest programem, przechowującym prywatne klucze używane
do weryfikacji. Aby rozpocząć korzystanie z niego wykonaj polecenie:
ssh-agent $BASH
a następnie dodaj swój prywatny kluczssh-add .ssh/id_dsa
lubssh-add .ssh/identity
'id_dsa' odnosi się do klucza prywatnego typu DSA, natomiast 'identity' ma zastosowanie w przypadku, gdy klucz jest typu RSA1. Są to standardowe nazwy plików utworzone podczas generowania przy użyciu 'ssh-keygen'. Oczywiście przed dodaniem twojego klucza zostaniesz poproszony o hasło-frazę. Możesz także zobaczyć wcześniej dodane klucze:ssh-add -l
W tym momencie, kiedy spróbujesz połączyć się z serwerem, który ma twój
klucz publiczny, połączenie zostanie zestawione bez twojego udziału - 'ssh-agent'
zajmie się procesem autoryzacji.
Kiedy używasz programu 'ssh-agent', tak jak jest to opisane powyżej, możesz
z niego korzystać jedynie na termintalu, z którego wystartowałeś ssh-agenta.
Aby temu zaradzić czeka Cię jeszcze trochę pracy, żeby ułatwić Ci to zadanie
stworzyłem prosty skrypt startujący agenta:
#!/bin/sh
#
# Erdal mutlu
#
# Starting an ssh-agent for batch jobs usage.
agent_info_file=~/.ssh/agent_info
if [ -f $agent_info_file ]; then
echo "Agent info file : $agent_info_file exists."
echo "make sure that no ssh-agent is running and then delete this file."
exit 1
fi
ssh-agent | head -2 > $agent_info_file
chmod 600 $agent_info_file
exit 0
source ~/.ssh/agent_info or . ~/.ssh/agent_info
W celu usprawnienia możesz dopisać kilka poniższych linijek do swojego pliku .bashrc:
if [ -f .ssh/agent_info ]; then
. .ssh/agent_info
fi
Ostrzeżenie: Warto dobrze zabezpieczyć twój komputer, gdyż w przeciwnym wypadku włamywacz uzyskując dostęp do twojego konta bez problemu będzie mógł logować się na inne serwery. Wszystko ma swoją cenę!
Nadszedł wreszcie czas na wyjaśnienia, jak zautomatyzować niektóre prace administracyjne. Idea jest prosta - wykonanie określonych poleceń na podanej liście hostów, a następnie skopiowanie kilku plików, czyli rutynowe czynności. Oto skrypt:
#!/bin/sh
# Installing anything using Secure SHELL and SSH agent
# Erdal MUTLU
# 11.03.2001
##################################################################
# Functions #
##################################################################
### Copy files between hosts
copy_files()
{
if [ $files_file != "files_empty.txt" ];then
cat $files_file | grep -v "#" | while read -r line
do
direction=`echo ${line} | cut -d " " -f 1`
file1=`echo ${line} | cut -d " " -f 2`
file2=`echo ${line} | cut -d " " -f 3`
case ${direction} in
"l2r") : ### From localhost to remote host
echo "$file1 --> ${host}:${file2}"
scp $file1 root@${host}:${file2}
;;
"r2l") : ### From remote host to localhost
echo "${host}:${file2} --> localhost:${file2}"
scp root@${host}:${file1} ${file2}
;;
*)
echo "Unknown direction of copy : ${direction}"
echo "Must be either local or remote."
;;
esac
done
fi
}
### Execute commands on remote hosts
execute_commands()
{
if [ $commands_file != "commands_empty.txt" ];then
cat $commands_file | grep -v "#" | while read -r line
do
command_str="${line}"
echo "Executing $command_str ..."
ssh -x -a root@${host} ${command_str} &
wait $!
echo "Execute $command_str OK."
done
fi
}
### Wrapper function to execute_commands and copy_files functions
doit()
{
cat $host_file | grep -v "#" | while read -r host
do
echo "host=$host processing..."
case "${mode}" in
"1")
copy_files
execute_commands
;;
"2")
execute_commands
copy_files
;;
*)
echo "$0 : Unknown mode : ${mode}"
;;
esac
echo "host=$host ok."
echo "------------------------------------------------------------------"
done
}
##################################################################
### Program starts here
##################################################################
if [ $# -ne 4 ]; then
echo "Usage : $0 mode host_file files_file commands_file"
echo ""
echo "mode is 1 or 2 "
echo " 1 : first copy files and then execute commands."
echo " 2 : first execute commands and then copy files."
echo "If the name of files.txt is files_empty.txt then it is not processed."
echo "If the name of commands.txt is commands_empty.txt then it is
echo "not processed."
exit
fi
mode=$1
host_file=$2
files_file=$3
commands_file=$4
agent_info_file=~/.ssh/agent_info
if [ -f $agent_info_file ]; then
. $agent_info_file
fi
if [ ! -f $host_file ]; then
echo "Hosts file : $host_file does not exist!"
exit 1
fi
if [ $files_file != "files_empty.txt" -a ! -f $files_file ]; then
echo "Files file : $files_file does not exist!"
exit 1
fi
if [ $commands_file != "commands_empty.txt" -a ! -f $commands_file ]; then
echo "Commands file : $commands_file does not exist!"
exit 1
fi
#### Do everything there
doit
Zapiszmy plik jako ainstal.sh (automatyczna instalacja). Uruchamiając program bez żadnych parametrów otrzymamy wiadomość:
./ainstall.sh
Usage : ./ainstall.sh mode host_file files_file commands_file |
Zgodnie z wiadomością, jeżeli nie chcesz wykonać żadnej komendy, wtedy
jako argumentu dla 'commands_file' użyj 'commands_empty.txt', natomiast,
jeżeli nie masz zamiaru kopiować plików użyj wartości 'files_empty.txt' dla
opcji 'files_file'. Nie zawsze przecież jednocześnie kopiujesz lub wykonujesz
jakieś polecenia.
Zanim zacznę wyjaśniać skrypt linijka po linijce, zaprezentuję przykład użycia: przypuśćmy, że uruchomiłeś zapasowy serwer DNS i chcesz go dodać do pliku '/etc/resolv.conf'. Dla ułatwienia przypuśćmy również, że wszystkie maszyny mają taki sam plik 'resolv.conf', jedyną rzeczą do zrobienia, jest skopiowanie nowego pliku na wszystkie inne komputery.
Na początek potrzebna Ci będzie lista wszystkich hostów, zapiszemy je w pliku 'hosts.txt'. Format pliku jest następujący - każda linijka zawiera tylko jeden wpis (nazwę lub adres IP). Pryzkład:
########################################################################## |
Jak widać można podać pełną nazwę, lub tylko jej część odpowiadająca nazwie hosta. Następnie konieczne jest stworzenie pliku, w którym znajdą się nazwy plików przeznaczone do kopiowania. Istnieją dwie możliwości kopiowania:
Stworzymy listę plików przeznaczonych do kopiowania i zapiszmy ją jako np. 'files_file.txt'. Każda linia w tym pliku zawiera informacje, które zostaną użyte do kopiowania pliku. Istnieją dwa kierunki kopiowania: l2r (local to remote) i r2l (remote to local). l2r zostanie zastosowany, kiedy plik ma zostać umieszczony na zdalnej maszynie, natomiast r2l w przeciwnym przypadku. Kiedy już określimy kierunek należy podać dwie nazwy plików, oddzielone spacją lub znakiem tabulacji. Pierwszy plik jest kopiowany do lokalizacji znajdującej się pod drugą nazwą, z uwzględnieniem kierunku. Nazwę pliku dla zdalnego systemu należy podać w formie bezwzględnej, w przeciwnym wypadku plik trafi do katalogu domowego roota. Oto przykład:
############################################################################ |
Tak jak widzisz dołączyłem do pliku wyjaśnienie struktury. Jest to prosty, lecz skuteczny sposób na wyjaśniający zawartość. W naszym przykładzie mamy zamiar skopiować plik resolv.conf na zdalną maszynę pod nazwą '/etc/resolv.conf'. Dla demonstracji zasady działania skryptu po skopiowaniu zostaną zmienione prawa dostępu do pliku, a następnie zostanie wyświetlona jego zawartość. Polecenia do wykonania umieszczone są w oddzielnym pliku, nazwijmy go 'commands_file.txt'. Zawartość:
########################################################################### |
Komendy zawarte w pliku 'command_file.txt' zostaną wykonane na każdym
hoście, którego adres znajduje się w pliku 'hosts.txt'. Wykonywane są jedna
po drugiej w kolejności wystąpienia w spisie.
Dobrze, a więc mamy wszystkie potrzebne pliki. Pozostało nam tylko jeszcze
określić tryb, w jakim uruchomimy skrypt, to znaczy, który z dwóch plików
'commands_file.txt' czy 'files_file.txt' zostanie wykonany pierwszy. Jeżeli
najpierw chcesz skopiować pliki, a następnie wykonać polecenia wybierz tryb
1, w przeciwnym wypadku skorzystaj z drugiego. Możemy teraz wykonać nasz
skrypt podając odpowiednie opcje:
./ainstall.sh 1 hosts.txt files_file.txt commands_file.txt
Wskazówka: Aby ułatwić sobie pracę warto zmienić nazwę pliku na taką, która
jednoznacznie określi zawartość, w naszym przypadku dobrym pomysłem może
być 'files_resolvconf.txt', taką samą metodę stosuję do plików hosts.txt,
oraz commands.txt.
Nadszedł wreszcie czas, na wyjaśnienie skryptu. Program rozpoczyna działanie, od sprawdzenia liczby argumentów, jeżeli liczba ta jest różna od czterech zostanie wyświetlona pomoc, kiedy wszystko jest w porządku poszczególne argumenty przypisywane są odpowiednim zmiennym. Po sprawdzeniu istnienie pliku '~/.ssh/agent_info' jest on odczytywany. Plik ten zawiera informację na temat działania programu 'ssh-agent'. Jeżeli nie korzystasz z agenta, będziesz zmuszony wprowadzić wszystkie hasła ręcznie, co oznacz, że nie uzyskasz automatyzacji :). Następnie skrypt sprawdza istnienie plików zawierających: adresy hostów, komendy i pliki. Istnieje również specjalny test, sprawdzający wystąpienie jednej z nazw: 'files_empty.txt' lub 'commands_empty.txt'. Kiedy jako argument podasz jedną z tych nazw nie ma potrzeby sprawdzania istnienia takiego pliku. Dokonałem drobnej zmiany w kodzie skryptu podczas pisania tego artykułu, przedtem wyglądało to tak:
if [ -f $host_file -a -f $files_file -a -f $commands_file ]; then
echo "$host_file $files_file $commands_file"
doit
else
echo "$host_file or $files_file or $commands_file does not exist"
exit
fi
Używając powyższej wersji konieczne było stworzenie plików o nazwach: 'files_empty.txt' oraz 'commands_empty.txt', chociaż nie stanowiło to problemu, ponieważ pracowałem cały czas w tym samym katalogu, postanowiłem to zmienić. Na końcu wywołuję funkcję 'doit', która rozpoczyna właściwe działanie skryptu. Funkcja ta przy pomocy pętli dla każdego hosta znajdującego się w '$hosts_file' wywołuje funkcje 'copy_files' oraz 'execute_commands' w kolejności zależnej od trybu. Zmienna '$host' zawiera aktualny adres IP lub nazwę komputera. Przyjrzyjmy się teraz bliżej funkcji 'copy_files'. W pierwszej kolejności sprawdzana jest wartość zmiennej 'files_file'. Jeżeli zawiera ona 'files_empty.txt', żadna akcja nie zostanie podjęta, w przeciwnym wypadku każda linia zmiennej '$files_file', jest sprawdzana, pobierane są następujące dane 'direction', 'file1' i 'file2' zawierające kierunek kopiowania, pierwszą i drugą nazwę pliku. Zgodnie z przyjętym kierunkiem dane są kopiowane przy użyciu programu 'scp'. Sprawdźmy jeszcze, co dzieje się w funkcji 'execute_commands'. Tak samo, jak w poprzedniej funkcji sprawdzane jest istnienie odpowiedniego pliku. Jeżeli zmienna ma wartość 'commands_empty.txt' żadne polecenie nie zostanie wykonane. Kiedy plik istnieje i nie jest pusty wszystkie zawarte w nim polecenia zostaną wykonane jako zadania działające w tle. Następnie wywołana jest komenda 'wait' z parametrem '$!', która daje nam pewność, że kolejne polecenie zostanie wykonane dopiero wtedy, gdy zakończy się poprzednie. '$!' reprezentuje ID ostatniego procesu uruchomionego w tle. Proste prawda?
Przedstawię teraz trochę bardziej zaawansowany przykład użycia skryptu.
Zostanie on wykorzystany do stworzenia kopii zapasowej plików konfiguracyjnych
poszczególnych serwerów. Do tego celu napisałem mały skrypt wykorzystujący
ainstall.sh:
#!/bin/sh
server_dir=${HOME}/erdal/sh/ServerBackups
if [ ! -d $server_dir ]; then
echo "Directory : $server_dir does not exists."
exit 1
fi
cd $server_dir
servers=ll_servers.txt
prog=${HOME}/erdal/sh/einstall_sa.sh
cat $servers | grep -v "#" | while read -r host
do
echo $host > host.txt
$prog 1 host.txt files_empty.txt
servers/${host}/commands_make_backup.txt
$prog 1 host.txt files_getbackup.txt commands_empty.txt
mv -f backup.tgz servers/${host}/backup/`date +%Y%m%d`.tgz
rm -f host.txt
done
exit 0
Należy założyć specjalny katalog nazwany 'servers', a w tym katalogu muszą znaleźć się dwa pliki: 'files_getbackup.txt' i 'll_servers.txt'. Oto zawartość pliku 'files_getbackup.txt':
r2l /root/backup.tgz backup.tgz
'll_servers.txt' zawiera adresy hostów, które mają być poddane archiwizacji. Każdy host musi posiadać katalog o takiej samej nazwie, a w tym katalogu musi znajdować się plik 'commands_make_backups.txt' zawierający polecenie tworzące archiwum /root/backup.tgz. Oraz katalog nazwany 'backup'. Wszystkie kopie zapasowe będą przechowywane w tym katalogu. Jeżeli zawartość pliku 'll_servers.txt' jest następująca:
fileserver |
wtedy struktura twojego katalogu '$servers' wygląda jak poniżej:
servers |
A oto zawartości przykładowych plików 'commands_make_backups.txt' :
tar cfz /root/backup.tgz /etc/samba /etc/atalk /etc/named.conf /var/named/zones
Powyższa polecenie tworzy kopię plików /etc/samba, /etc/atalk, /etc/named.conf
i /var/named/zones.
tar cfz /root/backup.tgz /etc/httpd /usr/local/apache
Kopia zapasowa plików serwera apache.
tar cfz /root/backup.tgz /etc/squid /etc/named.conf
Przy pomocy tego polecenia zarchiwizujesz konfigurację proxy serwera squid,
oraz konfigurację serwera dns.
Stosując odpowiednie wpisy proces archiwizacji możesz odpowiednio dostosować do własnych potrzeb.