januari 2009 Archieven
Evolutie van scripts
Geplaatst door miekg op maandag 26 januari 2009 | Permanente link | Categorie: Systeembeheer | Reacties: 0
Voor het beheren van UNIX machines worden er bij AT Computing regelmatig scripts geschreven. Deze scripts beginnen altijd eenvoudig, maar dat blijft meestal niet zo... Dit blog artikel laat zien hoe een simpel script uitgebreid kan worden.
N.B. Dit is een manier om je script leesbaar te houden. Er zijn
andere manieren die ook door consultants van AT Computing gebruikt
worden.
Alvorens ik inhoudelijk op het script in ga, wil ik eerst een kleine
inleiding geven:
Het scriptje zorgt ervoor dat je een file met vi kunt editen en dat het
daarna in een git repository wordt gezet zodat er versie beheer is. Ook
wordt de speciale string $Hash$ geëxpandeerd a la de $Id$ string die
bekend is van subversion en CVS.
Het origineel
Het oer script is klein genoeg (net geen 50 regels) om hier in zijn volle
glorie te tonen, zoals je ziet is het ook nog in zsh geschreven
(aangezien dat mijn favoriete shell is).
#!/bin/zsh
# a wrapper around git and vi
# expands $Hash$ to $Hash: file short_hash date committer $
[[ ! -x =git ]] && exit 1
who=${SUDO_USER:-$LOGNAME}
full=$(getent passwd $who | awk -F: '{ gsub(/,*/, ""); print $5 }')
author="$full <$who@atoom.net>"
function search_git_dir {
gpath="$1"
[[ -d "$gpath/.git" ]] && echo "$gpath" && return
[[ -z "$gpath" ]] && echo "" && return
# strip that last path component and try again
search_git_dir "${gpath%/*}"
}
for file in "$@"; do
dir=$(dirname "$file")
cd "$dir"
base=$(basename "$file")
if [[ -z $(search_git_dir "$PWD") ]]; then
# make a new one in $PWD
git init || exit 1
else
#echo FOUND ONE
fi
chmod +w "$base" 2> /dev/null
if ${EDITOR:-/usr/bin/vi} "$base"; then
[[ ! -e $base ]] && exit 0
git add $base
# collapse $Hash: id $ line
sed -i -e 's/\$Hash:.*\$/$Hash$/' "$base"
git commit --author "$author" "$base"
fi
id=$(git-show -s --pretty=format:$base\ %h\ %ci\ $who%n -- "$base")
[[ -z $id ]] && exit 1
# re-add $Hash: sha1hash$ line
sed -i -e 's/\$Hash\$'/\$Hash:\ $id\ \$/ "$base"
chmod a-w $base 2> /dev/null
cd - >/dev/null
done
Zoals je ziet weinig commentaar, maar voor de gevorderde shell programmeur nog redelijk te behappen.
Het script is nu af, maar binnen grotere organisaties is het vaak
gewenst om een script van meer commentaar te voorzien. Als er
veel scripts in inloop zijn kunnen deze het beste centraal beheert
worden. Ook een versie beheer systeem mag niet ontbreken, bijvoorbeeld
(hoe kan het ook anders) git.
Een file header toevoegen
Aangezien we op onze interne systemen meestal de bash shell gebruiken is het
script als eerste hiernaartoe omgezet. Ook is een programma header toegevoegd.
Het commentaar in dit script is in het Engels. Dit is geen bedrijfs standaard,
maar veel van onze klanten prefereren scripts in het Engels, vandaar.
Dit ziet er als volgt uit:
#!/bin/bash
#------------------------------------------------------------------------------#
# vi: set sw=4 ts=4 ai: ("set modeline" in ~/.exrc) #
#------------------------------------------------------------------------------#
# Program : vigit #
# #
# Author : Ton Kersten Ton.Kersten@ATComputing.nl #
# AT Computing Toernooiveld 104 #
# 6525 EC Nijmegen The Netherlands #
# Fax: +31-24 3527292 Tel: +31-24 3527282 #
# #
# Date : 23-01-2009 Time : 12:18 #
# #
# Description : Program to edit files and commit them to git #
# #
# Parameters : The files #
# #
# Pre reqs : Git should be installed #
# #
# Remarks : Stolen from and based on an idea of Miek Gieben #
# #
# Exit codes : 0 -> OK #
# <> 0 -> !OK #
# #
# Updates : None (yet) #
#------------------------------------------------------------------------------#
# (c) Copyright 2009 by AT Computing, The Netherlands #
#------------------------------------------------------------------------------#
#------------------------------------------------------------------------------#
# V e r s i o n i n f o r m a t i o n #
#------------------------------------------------------------------------------#
# $Id:: vigit 3 2009-01-26 09:36:01Z tonk $: #
# $Revision:: 3 $: #
# $Author:: Ton Kersten <Ton.Kersten@ATComputing.nl> $: #
# $Date:: 2009-01-26 10:36:13 +0100 (Mon, 26 Jan 2009) $: #
#------------------------------------------------------------------------------#
# E n d o f v e r s i o n i n f o r m a t i o n #
#------------------------------------------------------------------------------#
#------------------------------------------------------------------------------#
# Determine the program name and the 'running directory' #
#------------------------------------------------------------------------------#
IAM="${0##*/}"
CRD="$( [[ "$(printf "${0}" | cut -c 1 )" = "." ]] &&
{ printf "${PWD}/${0}"
} || {
printf "${0}"
})"
CRD="${CRD%/*}"
CUR="${PWD}"
#------------------------------------------------------------------------------#
# Save the shell settings #
#------------------------------------------------------------------------------#
SETA=0; [[ ${-} = *a* ]] && SETA=1
SETE=0; [[ ${-} = *e* ]] && SETE=1
SETU=0; [[ ${-} = *u* ]] && SETU=1
SETX=0; [[ ${-} = *x* ]] && SETX=1
#------------------------------------------------------------------------------#
# Set and unset the needed shell settings #
#------------------------------------------------------------------------------#
set +o noclobber # Overwrite existing files, if needed #
set -o nounset # Don't allow uninitialized variables #
set +o errexit # No returncode checking #
Hierbij wordt een aantal zaken altijd op dezelfde manier opgezet, zoals bijvoorbeeld
het set -o nounset waardoor een script geen onbekende variabelen toestaat.
Deze complete header kan worden gegenereerd met een, speciaal voor dit doel geschreven,
header script.
Ook wordt het script voorzien van duidelijk commentaar, hier en daar doorspekt met humor. Het commentaar dient ervoor te zorgen dat het script voor iedereen beter leesbaar wordt.
Een paar voorbeelden uit de uiteindelijke code:
#------------------------------------------------------------------------------#
# No git, no glory #
#------------------------------------------------------------------------------#
[[ x"$(which git 2>/dev/null)" = x"" ]] &&
{ echo "No 'git' found. Please use plain vi(m)"
exit 1
}
of
#----------------------------------------------------------------------#
# Check if the file is already in git (get the current hash) #
#----------------------------------------------------------------------#
initial=0
hash=$(git-show -s --pretty=format:"${base} %h %ci ${who}" -- "${base}" 2>/dev/null)
[[ x"${hash}" = x"" ]] &&
{ git add "${base}"
git commit --author "${author}" -m "Automatic initial checkin by '${IAM}'" "${base}"
initial=1
}
of
#--------------------------------------------------------------------------#
# Re-add the $Hash$ line #
#--------------------------------------------------------------------------#
if [[ ${havehash} != 0 ]]
then
if [[ ${longhash} != 0 ]]
then
id="${id}${spc}"
sed -i.bck -e 's!\([[:space:]]*\$Hash::\).*\$:!\1 '"${id:0:66}"'\$:!' "${base}"
else
sed -i.bck "s/\\\$Hash\\\$/\$Hash: ${id} \$/" "${base}"
fi
rm -f "${base}.bck"
fi
Conclusie
Gedurende dit proces zijn ook talloze bugjes gefixt en is het hele script geaudit. Het eind resultaat is dus dat we hier een "Enterprise Ready" (TM) script hebben gekregen, terwijl het oer script ook heeft geprofiteerd van dit herschrijf proces.
Hierbij is echter wel opvallend dat het oer script slechts uit 50 regels bestaat en de uiteindelijke versie 254 regels groot is. Ook loopt het nu op FreeBSD (en waarschijnlijk andere Unixen) in plaats van alleen op Linux.
Het uiteindelijke script is hier te vinden.
iSCSI disk vergroten zonder te rebooten
Geplaatst door jacco op maandag 19 januari 2009 | Permanente link | Categorie: Systeembeheer | Reacties: 0
Als je een iSCSI-device gebruikt voor opslag is de kans groot dat de ruimte op dat device op een gegeven moment niet meer toereikend is. Je wilt die dan graag vergroten en deze vergrote ruimte door de client laten gebruiken.
Nu is dat vergroten vanaf de machine die de iSCSI-target aanbiedt normal gesproken niet zo'n probleem. OpenFiler biedt daarvoor mogelijkheden in zijn webinterface.
Maar hoe maak je de iSCSI-client (de machine waarop het
stukje storage gebruikt wordt) duidelijk dat zijn device - dat hij ziet als
een normale SCSI-disk als /dev/sd[a-z] - groter is dan hij een paar
minuten geleden dacht?
Rebooten zou je zeggen. Maar dat is natuurlijk niet de Linux-manier, een beetje Linux-server gaat alleen down wanneer de spanning eraf moet ...
Er bestaat een tooltje, genaamd /sbin/partprobe, dat tot doel heeft de
kernel kennis te laten nemen van een gewijzigde partitietabel.Maar dat
biedt geen soelaas, want de partitietabel is nog niet gewijzigd. Eerst moet je
de kernel duidelijk maken dat het device groter is geworden. Dat kan, en
gelukkig hoef je daarvoor niet te rebooten.
Het magische commando heet /sbin/blockdev. Als je dit tooltje aanroept met de parameters --rereadpt en de devicenaam (/dev/sd[a-z]), zal de kernel op de hoogte worden gesteld van de gewijzigde devicegrootte.
Daarna hoef je alleen nog de partitie te resizen (bijvoorbeeld met gparted) en het device opnieuw mounten -> highscore. Het resizen van het filesystem op de betreffende partitie gaat automatisch (met dank aan gparted).
Voorbeeld:
- Oude situatie
# df -h /iscsi Filesystem Size Used Avail Use% Mounted on /dev/sdb1 1008M 18M 940M 2% /iscsi # fdisk -l /dev/sdb Disk /dev/sdb: 1073 MB, 1073741824 bytes Device Boot Start End Blocks Id System /dev/sdb1 1 1011 1048376+ 83 Linux
- iSCSI op (bijvoorbeeld) OpenFiler oprekken tot 3 GB.
- Filesystem unmounten (anders vindt
/sbin/blockdevdat het device busy is) en/sbin/blockdevhet device opnieuw laten scannen.
# umount /iscsi # /sbin/blockdev --rereadpt /dev/sdb # fdisk -l /dev/sdb Disk /dev/sdb: 3154 MB, 3154116608 bytes Device Boot Start End Blocks Id System /dev/sdb1 1 1011 1048376+ 83 Linux
- So far, so good. Nu de partitie oprekken. Start gparted en resize de partitie naar het maximum. Daarna kun je met
fdiskopvragen of het gelukt is.
# fdisk -l /dev/sdb Disk /dev/sdb: 3154 MB, 3154116608 bytes Device Boot Start End Blocks Id System /dev/sdb1 1 2970 3079859+ 83 Linux
- Opnieuw mounten en je kunt gebruik maken van de extra ruimte.
# mount /dev/sdb1 /iscsi # df -h /iscsi Filesystem Size Used Avail Use% Mounted on /dev/sdb1 2.9G 18M 2.8G 1% /iscsi
Notabene:
- Deze procedure zal ook prima werken met (fibre channel) SAN-devices. Het principe is gebaseerd op het gegeven dat de kernel een block-based SCSI-device heeft dat onder zijn handen van grootte verandert.
- Het tooltje
/sbin/blockdevzit in het pakket util-linux (zowel bij Ubuntu als RedHat).
deeplink foto's van een website markeren
Geplaatst door hjt op woensdag 7 januari 2009 | Permanente link | Categorie: Tips and Tricks | Reacties: 0
Een bepaalde website die ik beheer bevat veel mooie foto's. Bezoekers worden enthousiast, en praten erover op discussiefora waar ze lid van zijn. Soms zetten ze in hun forumbericht een link naar een van de foto's op "mijn" site. Niet als clickable link, maar zodanig dat de foto rechtstreeks ingebed in hun bericht wordt getoond. Dan lijkt het alsof die foto van henzelf is, terwijl hij in werkelijkheid steeds van mijn site opgeroepen wordt zonder dat de lezer dat meteen ziet. Deze techniek heet "deep-linking". Voor zover ik weet is er Nederlandse jurisprudentie die dat zonder toestemming strafbaar acht als een copyright-violation. Als de dader er nog een bronvermelding bij zou zetten zou het wat mij betreft niet zo erg zijn; de betreffende site is niet commercieel, en de foto's zijn dat ook niet. En de verbruikte transmissie-bandbreedte is best wel te betalen. Maar het weglaten van bronvermelding ergerde me. Daarom besloot ik daar zelf maar voor te gaan zorgen.
Het probleem valt uiteen in een paar stappen:
- hoe kan een Apache webserver zien dat vanuit een vreemde webpagina (d.w.z. vanuit een pagina die niet van zijn eigen site is) een foto/image wordt opgevraagd.
- hoe trigger je in zo'n geval een actie die iets anders doet dan braaf de gevraagde foto serveren.
- hoe schilder je "on the fly" een stringetje tekst in een foto.
Stappen 1 en 2 pak je aan met de "mod_rewrite" van Apache, en stap 3 kan met PHP en z'n grafische GD-library.
In de httpd.conf van Apache controleer je dat 'mod_rewrite' wordt geladen (dat is i.h.a. default) en voeg je, in de "container" van de virtual host website, de volgende regels toe. De nummers in de kantlijn horen er niet bij; die zijn voor de uitleg die volgt:
01 RewriteEngine on
02 RewriteLog /xxxx/logs/rewrite_log
03 RewriteLogLevel 0
04 RewriteCond %{HTTP_REFERER} !^http://www.mijnsite.nl/.* [NC]
05 RewriteRule .*\.jpe?g$|.*\.gif$|.*\.png$
/rewriteimage.php?URI=%{REQUEST_URI} [NC,last]
In regel 01 schakelen we het rewrite-module aan. Dat moet expliciet want het is een setting die je nooit "erft" uit een hoger configuratie-niveau. Met dat module kun je binnenkomende URL's bekijken, en onder voorwaarden wijzigen/herschrijven. Dat luistert nogal nauwkeurig, dus het heeft zin om debug-logging aan te zetten (02, 03). Regel 02 vertelt hoe de logfile moet heten. De 'xxxx' in regel 02 moet in je UNIX-servermachine een absolute padnaam vormen, en gebruiker 'apache' moet in die logfile kunnen schrijven. Je kiest typisch dezelfde directory als waar ook de access-logfile en de error-logfile staan (zoek naar AccessLog en ErrorLog regels). Rewrite-logging is erg duur. Regel 03 zet deze logging effectief uit. Om het tijdens het testen (tijdelijk!!) aan te zetten moet je het level-getal opkrikken, tot maximaal 9.
Regel 04 bekijkt de 'HTTP_REFERER' string. Dat is de aanvragende website; we willen weten of dat 'onze eigen' website is. Zo'n regel bestaat uit 3 of 4 delen: (deel a) keyword 'RewriteCond' zegt dat een voorwaarde getest moet worden. (deel b) %{HTTP_REFERER} benoemt het slachtoffer: de REFERER-string uit het binnengekomen HTTP-request. Dit deel (b) van de regel kent zeer veel verschillende mogelijkheden; de Apache-mod_rewrite documentatie is hierbij onontbeerlijk. Deel (c) is een reguliere expressie, Perl-stijl, die we op deel (b) willen loslaten. Dat levert dan wel of geen match op. Maar Apache heeft als extra mogelijkheid een ! voor de expressie, die je gebruikt als je juist niet een match zoekt. Dat doen wij dus.
Deel (d), tussen [] haken, is een optioneel aanhangel waarin je een reeks "vlaggen" kunt zetten; ook hier weer veel mogelijkheden. In ons geval gebruiken we vlag [NC] (regexp-match NotCase sensitive).
Dan komen we op regel 05; die moet je als één lange regel schrijven. Deze RewriteRule is de work-horse, die het echte werk doet. En ook zo'n RewriteRule heeft weer 3 of 4 delen. Het keyword RewriteRule (deel a) spreekt voor zich. Maar je moet wel weten dat een RewriteRule alleen maar dienst doet als alle RewriteCond statements die er onmiddellijk aan vooraf gaan een 'true' hebben gescoord.
Delen (b) en (c) van een RewriteRule vormen een search+replace. (b) is een Perl-regexp die moet matchen op een deel van de gevraagde filenaam, (c) is de replace-string. Optioneel deel (d), tussen [], bevat weer vlaggetjes.
De reguliere-expressie in ons deel (b) ziet er wat ingewikkeld uit. Je moet 'm even in drie stukken knippen bij de |-tekens. Dan krijg je drie aparte expressies; de | vormt een OR van die drie. De eerste van de drie is .*\.jpe?g$ en betekent stukje voor stukje: .* pakt een willekeurig (longest match) beginstuk, daarachter \. een letterlijke punt, daarachter de letters jp, dan betekent e? optioneel een letter e, dan een letter g, en tenslotte betekent $ het einde van de string. We zoeken dus naar filenaam-extensies .jpg en .jpeg (en de vlag [NC] had je natuurlijk ook weer gezien). De andere twee van de drie expressies zoeken vergelijkbaar naar .gif en naar .png extensies.
Deel (c) in de regel is de replace: we vervangen de opgevraagde filenaam door de vast gekozen filenaam /rewriteimage.php (een PHP-programma dat we hierna bespreken; de naam hebben we zelf verzonnen) en met een '?' koppelen we daar de oorspronkelijke gevraagde REQUEST_URI (de naam van de foto-file) als z.g. HTTP-GET-parameter aan vast. De vlag '[last]' betekent: verder geen andere rewrite-regels meer toepassen; eindbestemming bereikt. Default gedrag is dat de hele rewrite-matching met de gewijzigde filenaam opnieuw begint, en dat zou met onze criteria een oneindige loop opleveren.
Nog even wat fijnproeverij over het verschil tussen een URI (I=identifier) en een URL (L=locator): http://www.mijnsite.nl/aap/noot/mies.jpg is een URL, en aap/noot/mies.jpg is een URI.
We zijn een heel eind gevorderd: als vanuit een vreemde website een .jpg of .jpeg of .gif of .png bestand van onze website wordt opgeroepen, start Apache ons PHP-programma /rewriteimage.php en geeft als GET-parameter daar de opgevraagde bestandsnaam aan door. Alle data die ons PHP-programma op zijn stdout-kanaal naar buiten pompt, gaan terechtkomen op het bordje van de browsende klant die op de gevraagde foto staat te wachten.
Nu bekijken we dat /rewriteimage.php -programma. Als je geen PHP spreekt kun je misschien nog de grote lijnen volgen. Als je wel PHP spreekt kun je details over de gebruikte functies nalezen op de PHP website (merk rechtsboven op die webpagina de 'search ... function list' op!).
<?php
01 $uri = $_GET['URI'];
02 $ext = substr($uri, 1+strrpos($uri, '.'));
03 switch(strtolower($ext)) {
04 case 'jpeg': case 'jpg':
05 $inputfunction = 'ImageCreateFromJPEG';
06 $outputfunction = 'ImageJPEG';
07 header('Content-Type: image/jpeg');
08 break;
09 case 'gif':
10 $inputfunction = 'ImageCreateFromGIF';
11 $outputfunction = 'ImageGIF';
12 header('Content-Type: image/gif');
13 break;
14 case 'png':
15 $inputfunction = 'ImageCreateFromPNG';
16 $outputfunction = 'ImagePNG';
17 header('Content-Type: image/png');
18 break;
19 default:
20 exit(1);
21 }
22 $handle = $inputfunction($_SERVER['DOCUMENT_ROOT'].'/'.$uri);
23 $xsize = ImageSx($handle);
24 $ysize = ImageSy($handle);
25 if (($xsize >= 190) && ($ysize >= 40)) {
26 $white = ImageColorAllocate($handle, 0xFE, 0xFE, 0xFE);
27 $black = ImageColorAllocate($handle, 0x01, 0x01, 0x01);
28 $font = 'LucidaSansDemiBold';
29 $text = 'from: www.mijnsite.nl';
30 ImageTTFText($handle, 10, 0, 2, $ysize-2, $black, $font, $text);
31 ImageTTFText($handle, 10, 0, 3, $ysize-2, $white, $font, $text);
32 }
33 $outputfunction($handle);
?>
Regel 01 haalt de GET-parameter met de URI-string op. Dat is dus de naam van het foto-bestand dat door de browsende klant is opgevraagd; via de Apache-rewrite hadden we die naam laten doorgeven. Regel 02 peutert de extensie van die filenaam apart. De extensie gebruiken we in regels 03 t.m. 21 om twee functienamen te vormen, afhankelijk van het type image waar het om gaat. Ook sturen we (07,12,17) alvast de verplichte http-header naar de browser toe.
Regel 22 haalt het gevraagde foto-bestand van disk. Merk op dat we de document-root van de website voor de URI-naam moeten plakken. Als uw website erg ingewikkelde filenamen gebruikt, dan is dit statement mogelijk iets te simpel. Regels 23 en 24 kijken hoe groot de ingelezen foto is. Regel 25 voorkomt dat we in al te kleine plaatjes nog tekst erbij proberen te frutten. De getallen corresponderen ongeveer met de afmeting van de tekst die we gaan schrijven, plus een randje eromheen. Regels 26 en 27 definiëren de kleuren wit en zwart. We kiezen 0xFE in plaats van 0xFF, en 0x01 in plaats van 0x00 om net naast de "echte" kleuren wit resp. zwart te gaan zitten. De reden bespreken we hieronder. Regels 28 en 29 spreken voor zich; het genoemde font moet natuurlijk wel in TTF-formaat op de server beschikbaar zijn voor PHP. In regel 30 schrijven we onze tekst in zwarte letters in de foto. Regel 31 doet dat nogmaals in witte letters, en ietsje verschoven. Regel 33 perst het resultaat naar buiten, naar de browsende klant. Klaar.....
Het doel is bereikt: als een foto in mijn eigen webpagina verschijnt is hij ongemarkeerd, maar verschijnt hij deep-linked in "andermans" pagina dan staat mijn bronvermelding in de foto geschreven.
In GIF en PNG plaatjes kan een bepaalde kleur tot "transparant" zijn verklaard, en vaak wordt "echt" zwart (0x000000) of "echt" wit (0xFFFFFF) gekozen omdat dat de oorspronkelijke achtergrondkleur was. Door met onze eigen kleurdefinities daar net naast te gaan zitten (regels 26/27) minimaliseren we de kans dat onze zwarte of witte tekstcomponent (regels 30/31) in het plaatje transparant wordt gemaakt.
Merk op dat onze aanpak je niet beschermt tegen iemand die een kopie van je foto op zijn eigen webserver plaatst, en 'm vanaf daar serveert. Dat kun je niet tegenhouden, zelfs niet met een simpel JavaScriptje dat een klik van de rechter muisknop afvangt en een "Foei, mag niet" pop-up displayt. Je kunt gestolen kopieën alleen maar achteraf proberen op te sporen. Als je zo iets nodig hebt moet je eens kijken bij een specialist in deze zaken.
