Создание виртуальных хостов в apache под Linux на Python

Занимаюсь разработкой сайтов и всякие эксперименты и основную разработку делаю на локальном компьютере под Debian. В следствии того, что приходилось постоянно ручками создавать виртуальные хосты пришлось поставить себе цель автоматизировать процесс.
Первый делом двинулся я в просторы интернета в поисках необходимого решения, которое должно было обладать простотой и выполнять всего 2 задачи: добавлять виртуальный хост и удалять его. Мне удобно пользоваться консолью, поэтому и приложение должно было быть консольным. Но все варианты которые нашел имели большое количество ненужного функционала, кроме того почти все они предоставляли web интерфейс, которым я просто не хотел пользоваться.
В результате были поставлены цели:
— написать свой простенький скрипт, который создавал все то, что мне нужно;
— в качестве языка разработке я выбрал python, т.к. давно искал повод на нем учится писать.

Update (08.09.11 20:25): учитывая ошибки в комментариях немного исправил скрипт. Начал использовать optparse, сократил использование .write.

В результате я получил полностью удовлетворяющий меня скрипт под катом.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
###
# Скрипт предназначен для легкого управления локальными доменами
###

import os, sys, re, shutil, string, pwd, grp, stat
from optparse import OptionParser

# Проверяем права администратора, если нет, то выводим предупреждение
# т.к. правка системных файлов не возможно без соответствующих прав доступа 
# и приведет к ошибкам
if os.getuid()!=0:
	sys.exit ("\033[31mСкрипт должен работать с правами администратора!\033[0m");
	
# Название скрипта
sname=os.path.basename(__file__);
	
def main():
    parser = OptionParser(usage="usage: %prog [options] [add|drop] domain", 
		version="%prog 1.0")
    parser.add_option("-d", "--dir_site", default="/home/alen/domains/", 
		metavar="/home/alen/domains/", help=u"Директория для домена.");
    parser.add_option("-a", "--apache_bin", default="/etc/init.d/apache2", 
		metavar="/etc/init.d/apache2", help=u"Бинарник apache для рестарта.");
    parser.add_option("-c", "--apache_config_site", 
		default="/etc/apache2/sites-enabled/", metavar="/etc/apache2/sites-enabled/", 
		help=u"Директория sites-enabled от apache для настроек virtualhost.");
    parser.add_option("-t", "--host", default="/etc/hosts", metavar="/etc/hosts", 
		help=u"Системный файл hosts для внесения информации о домене.");
    parser.add_option("-i", "--ip", default="127.0.0.1", metavar="127.0.0.1", 
		help=u"IP для сайта");
    parser.add_option("-e", "--a2ensite", default="a2ensite", metavar="a2ensite", 
		help=u"enable an apache2 site / virtual host");

    (options, args) = parser.parse_args();
    if len(args)!=2 or not args[0] in {"add":1,"drop":2}:
		parser.error(sname+" -h");
    return {"options":options,"args":args};
    
conf=main();
options=conf['options'];

# Получаем группу и пользователя права которого будут для папки
# Возьмем их из папки в которую все сложим: dir_site
stat_info = os.stat(options.dir_site);
options.uid = stat_info.st_uid;
options.gid = stat_info.st_gid;
options.user = pwd.getpwuid(options.uid)[0];
options.group = grp.getgrgid(options.gid)[0];

# Функция удаления строки из файла
def remove_string(filename, string):
    rst = [];
    with open(filename) as fd:
        t = fd.read();
        for line in t.splitlines():
            if line != string:
                rst.append(line);
    with open(filename, 'w') as fd:
        fd.write('\n'.join(rst))
        fd.write('\n')
        
        
def apache_site_config(name):
	file_name=options.apache_config_site+name;
	dir_site=options.dir_site+name;
	f = open(file_name,"w+");
	print >> f, '<VirtualHost *:80>\n\n'+\
	'DocumentRoot '+ dir_site +'/www\n'+\
	'ServerAlias www.'+name+'\n'+\
	'ServerName '+name+'\n'+\
	'ScriptAlias /cgi-bin/ '+dir_site+'/www/cgi-bin/\n\n'+\
	'<Directory "'+dir_site+'/www">\n'+\
	'\tAllowOverride All\n'+\
	'\tOrder Deny,Allow\n'+\
	'\tAllow from all\n'+\
	'\tOptions All\n'+\
	'</Directory>\n\n'+\
	'<Directory "'+dir_site+'/www/cgi-bin/">\n'+\
	'\tAllowOverride None\n'+\
	'\tOptions +ExecCGI -MultiViews +SymLinksIfOwnerMatch\n'+\
	'\tOrder allow,deny\n'+\
	'\tAllow from all\n'+\
	'</Directory>\n\n'+\
	'<IfModule dir_module>\n'+\
	'\tDirectoryIndex index.php index.html index.cgi\n'+\
	'</IfModule>\n\n'+\
	'#SuexecUserGroup '+options.user+' '+options.group+'\n'+\
	'ErrorLog \"'+ dir_site +'/log/error.log\"\n'+\
	'CustomLog \"'+ dir_site +'/log/access.log\" combined\n'+\
	'LogLevel warn\n\n'+\
	'</VirtualHost>';
	f.close();
	
# Функция добавления домена
def add_domain(name):
	dir_site=options.dir_site+name;
	if os.path.exists(dir_site):
		sys.exit("Сайт "+name+" не может быть записан в "+dir_site);
	elif os.path.exists(options.apache_config_site+name):
		sys.exit(options.apache_config_site+name+" - Занят конфигурационный файл!");
	else:
		os.makedirs(dir_site+"/");
		os.makedirs(dir_site+"/www/");
		os.makedirs(dir_site+"/www/cgi-bin/");
		os.makedirs(dir_site+"/log/");
		f = open(dir_site+"/www/index.php","a+");
		f.write('<?php\nphpinfo();');
		f.close();
		f = open(dir_site+"/www/cgi-bin/index.cgi","a+");
		print >> f,'#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n'+\
		'import cgitb\ncgitb.enable()\n\n'+\
		'print "Content-Type: text/plain;charset=utf-8"\n'+\
		'print\n\nprint "Hello World!"';
		f.close();
		os.system("chown -R "+options.user+":"+options.group+" "+dir_site);
		os.chmod(dir_site+"/www/cgi-bin/index.cgi", 0755);
		apache_site_config(name);
		f = open(options.host,"a+");
		f.write("\n"+options.ip+"\t"+name+"\twww."+name);
		f.close();	
		f = open(dir_site+"/www/.htaccess","a+");
		f.write("AddDefaultCharset UTF-8");
		f.close();	
		os.system(options.a2ensite+" "+name);
		os.system(options.apache_bin+" restart");	
		sys.exit("\033[31mДомен http://"+name+" успешно создан\033[0m");
	pass;
	
# Функция удаления домена
def drop_domain(name):
	dir_site=options.dir_site+name;
	if os.path.exists(dir_site):
		shutil.rmtree(dir_site);
	if os.path.exists(options.apache_config_site+name):
		os.unlink(options.apache_config_site+name);
	remove_string(options.host, options.ip+"\t"+name+"\twww."+name);
	os.system(options.apache_bin+" restart");
	sys.exit("\033[31mУпоминания о домене "+name+" удалены!\033[0m");
	pass;

if conf["args"][0] in {"add":1,"drop":2} and \
		re.compile('^[-\w.]{3,}$').match(conf["args"][1]):
	if conf["args"][0]=='add':
		add_domain(conf["args"][1]);
	else:
		drop_domain(conf["args"][1]);
else:
	sys.exit("\033[31mКоманды \"" + conf["args"][0] + "\" не существует!\033[0m");


Примечания к скрипту

В связи с предназначением скрипта, он запускается под root`om или через sudo.

Мини инструкция, для тех кто не дойдет до -help:
Usage: script [options] [add|drop] domain
В опциях изменяются настройки для работы скрита.

Во время добавления либо удаления домена:
— вносится изменение в hosts файл, для внесении информации о домене;
— в директориях переданных через dir_site создаются необходимые файлы и папки для нашего домена;
— в директории указанной в apache_config_site создается файл с конфигурацией virtualhost для apache.

Пользуясь моментом

В связи с тем, что это мой первый опыт написания Python скрипта, очень хочу, чтоб максимально критически отнеслись к моим ошибкам или к неправильному подходу (особенно благодарен буду за поджопник пинок в нужное направление).
Метки:
Поделиться публикацией
Похожие публикации
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама
Комментарии 12
  • +2
    def exp_str(sstring,delim):
    arr={}
    arr=sstring.split(delim);
    for v in arr:
    if v=='':
    del arr[arr.index('')];
    return arr;

    Вот это удачно заменяется одной строкой вида
    lst = [x for x in str.split(delimeter) if x.strip()]
    • 0
      Спасибо — исправляю. Сейчас как раз читаю про операторы и их ассоциативность, чтоб писать некоторые выражения в строчку.
      • +1
        Эта конструкция называется list comprehension, крайне удобная штука.
        docs.python.org/tutorial/datastructures.html#list-comprehensions
        • 0
          Я понял это таким же планом, как и в PHP:
          true?false:true?true:false;
          Правильно я разобрался?
          • 0
            Нет, почитайте по ссылке, это просто свернутая форма цикла for, например:

            lst = []
            for x in str.split(delimeter):
             if x.strip():
              lst.append(x)


            тоже самое, что:
            lst = [x for x in str.split(delimeter) if x.strip()]

    • –1
      Поставь webmin
      • +2
        WebAdmin хороший вариант, но не подошел он мне, как я уже написал в статье — слишком много лишнего.
      • 0
        Я бы сделал чуток по другому:

        в конфиг:
        "apache_config_site":"/etc/apache2/sites-available/",

        в конец add_domain или apache_site_config (как удобней, главное до перегрузки apache):
        os.system("a2ensite "+name);

        ну и в добавок можно в СУБД создавать базу данных(тут уже зависит от потребностей). Благо это дело 4-х строк кода.
        • 0
          a2ensite — согласен, добавлю. А вот база поскольку не всегда нужна, сделаю ее c добавлением по запросу.
        • +3
          Товарищ, да вам туториал читать!
          От количества .write глаза рябят.
          Также, откройе для себя optparse, не делайте велосипеда такого квадратного.
          Для такой простой задачи, как добавление вирт. хостов, проще использовать темплейты в файлах и делать file.format() и сохранять куда надо
          • 0
            optparse — то, что нужно было мне.
            По поводу шаблонов, которые хранить в отдельном файле — идея мне нравится, но всетаки хотелось бы такой простой скрипт уместить в одном файле. По поводу большого количества write я с вами солидарен, сейчас ищу оптимальный вариант-решение. Пробовал перенос строк через +\ , но результат — немного больше отступы чем нужно. Пока пришел к тому, что можно было сдеать строки в виде списка, но я уверен, что есть более гуманное решение, которого я еще просто не изучил.
            • 0
              > По поводу большого количества write я с вами солидарен, сейчас ищу оптимальный вариант-решение.
              У оператора print есть возможность перенаправления вывода на файловый дескриптор, емнип print >>fd, «message»

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.