Готовясь с переезду со старого сервера (FreeBSD) на новый (CentOS) — пришлось задуматься о том, сколько сайтов надо создавать вручную: добавить пользователя, создать директории, файлы логов, отредактировать файл конфигурации Apache HTTP.
С целью упростить себе жизнь, а заодно — попрактиковаться в Python, задумался следующий скрипт.
Предполагается, что Apache настроен с поддержкой suEXEC + mod_fcgid (CentOS: установка Apache HTTP + suEXEC + mod_fcgid) + в качестве frontend установлен NGINX (CentOS: установка Apache HTTP + NGINX).
Скрипт использует модуль argparcer для получения данных. Есть возможность отключить добавление базы и пользователя MySQL или наоборот — отключить создание файлов и директорий, и создать только базу и пользователя MySQL (хотя в данном случае используется MariaDB, но роли не играет).
В общем, это завершённая и уже более адекватная версия скрипта из поста Python: скрипт создания нового виртуалхоста для нового пользователя.
На сервере используются:
# cat /etc/redhat-release CentOS release 6.5 (Final)
# python -V Python 2.6.6
# httpd -V Server version: Apache/2.2.15 (Unix) Server built: Jul 23 2014 14:17:29 ... Architecture: 64-bit Server MPM: Prefork
# nginx -v nginx version: nginx/1.6.1
Скрипт разделён на две части — одна с классами, и вторая, «основная» — непосредственно тело скрипта.
Сначала — файл классов:
#!/usr/bin/env python
import os
import sys
import crypt
import subprocess
import errno
import errno
import shutil
import pwd
import grp
import re
import MySQLdb
import warnings
class MainClass(object):
def __init__(self, user, password, domain):
self.user = user
self.password = password
self.domain = domain
def isroot(self):
if not os.geteuid() == 0:
raise SystemExit
def answer(self, prompt, choice='Yes or no, please!'):
while True:
result = raw_input(prompt)
if result in ('y', 'Y', 'yes', 'Yes'):
break
elif result in ('n', 'N', 'no', 'No'):
raise SystemExit
else:
print(choice)
class SystemOperations(MainClass):
def __init__(self, *args):
super(SystemOperations, self).__init__(*args)
def useradd(self):
# create salt
salt = os.urandom(6).encode('base_64')
# encrypt password
encpass = crypt.crypt(self.password, salt)
# adding user
subprocess.call('useradd -p %s %s' % (encpass, self.user), shell=True)
def mkdir(self, dir):
self.dir = dir
os.makedirs(self.dir)
def cpcgi(self, src, dst):
self.src = src
self.dst = dst
shutil.copy(self.src, self.dst)
def chowdir(self, path):
self.path = path
uid = pwd.getpwnam(self.user).pw_uid
gid = grp.getgrnam(self.user).gr_gid
for root, dirs, files in os.walk(self.path):
for a in dirs:
os.chown(os.path.join(root, a), uid, gid)
for a in files:
os.chown(os.path.join(root, a), uid, gid)
os.chown(self.path, uid, gid)
subprocess.call('ls -l %s | grep -v total' % self.path, shell=True)
# create lof-files
def tchlog(self, logfile):
self.logfile = logfile
open(logfile, 'a')
def createconf(self, configin, configout, oflist):
self.configin = configin
self.configout = configout
self.oflist = oflist
tolist = (self.user, self.domain)
shutil.copy(self.configin, self.configout)
for of, to in zip(self.oflist, tolist):
with open(self.configout, 'r') as infile:
data = infile.readlines()
with open(self.configout, 'w') as infile:
for string in data:
infile.write(re.sub(of, to, string))
class mysqlUserDb(MainClass):
warnings.filterwarnings('error')
def __init__(self, dbroot, dbhost, dbrootpw, *args):
super(mysqlUserDb, self).__init__(*args)
self.dbroot = dbroot
self.dbhost = dbhost
self.dbrootpw = dbrootpw
self.db = MySQLdb.connect(self.dbhost, self.dbroot, self.dbrootpw)
self.cursor = self.db.cursor()
def createdb(self, dbuserdb):
self.dbuserdb = dbuserdb
self.cursor.execute('create database if not exists ' + self.dbuserdb)
self.cursor.execute("show databases like '%s' " % self.dbuserdb) #+ ''' + self.dbuserdb + ''')
self.dbs = self.cursor.fetchone()
return self.dbs
def grants(self, dbuserdb):
self.cursor.execute("grant all on %s.* to '%s'@'localhost' identified by '%s' " % (self.dbuserdb, self.user, self.password))
self.cursor.execute("select user, db from mysql.db where db='%s'" % self.dbuserdb)
self.grs = self.cursor.fetchall()
return self.grs
def __del__(self):
self.cursor.close()
self.db.close()
И файл самого скрипта:
#!/usr/bin/env python
import os
import argparse
import getpass
import sys
import pwd
import warnings
import create_vhost_Classes_new as fun
parser = argparse.ArgumentParser()
parser.add_argument('-u', '--user', help='Username, for OS user and MySQL user.')
parser.add_argument('-p', '--password', help='Password for OS and MySQL user.', dest='password', default=None)
parser.add_argument('-d', '--domain', help='Domainname, will be created virtualhost directories, files.')
parser.add_argument('-m', '--dbuserdb', help='MySQL database name for user.')
parser.add_argument('-r', '--dbrootpw', help='MySQL root password.', dest='dbrootpw', default=None)
parser.add_argument('-s', '--system', help='Add `-s` option to disable system operation (add user, create directories and files)', action='store_false')
parser.add_argument('-M', '--mysql', help='Add `-m` to disable MySQL operations (add user, add database).', action='store_false')
options = parser.parse_args()
# user's data
user = options.user
password = options.password
domain = options.domain
# MySQL data
dbhost = 'localhost'
dbroot = 'root'
dbrootpw = options.dbrootpw
dbuserdb = options.dbuserdb
# Apache and NGINX config directories
virt_hosts_dir = '/var/www/'
# Apache log dir
logs_dir = '/var/log/httpd/'
# used when creates new directories (mkdir)
dest = ('vhosts/', 'php-cgi/')
# used when creates log-files
logtypes = ('-access.log', '-error.log')
# used when creates Apache and NGINX configuration files
config_dirs = ('/etc/httpd/conf.d/', '/etc/nginx/conf.d/')
try:
# user can skip enter password with option;
# instead - will enter here, more secure
if not options.password:
password = getpass.getpass('Please, enter user password: ')
# check input data
main = fun.MainClass(user, password, domain)
print('nUser = %s;nDomain = %s;nPassword = %s;nMySQL database = %s.n' % (main.user, main.password, main.domain, dbuserdb))
main.answer('Is it correct? ')
print('OK, proceeding.')
except KeyboardInterrupt:
print('Exit.')
sys.exit(0)
# if answer() `NO` - it raise SystemExit
except SystemExit:
print('Exit. Please, restart with correct data.')
sys.exit(0)
try:
# check does scipt started under root
print('nRunning user check...')
main.isroot()
print('You are root, OK.n')
except SystemExit:
print('Must be started as root. Exit.n')
sys.exit(0)
# if `-s` option was not set and options.system = True
if options.system:
# creates object for main OS operations
sysacts = fun.SystemOperations(user, password, domain)
# user add
print('Adding user %s...' % user)
sysacts.useradd()
# check if new user exist
try:
pwd.getpwnam(user)
print('Done.')
except KeyError as e:
print('ERROR! %s.nExit.' %e)
sys.exit(1)
## DIRECTORIES
# first create site files directory
# then php-cgi directory
for i in dest:
dir = virt_hosts_dir + i + user + '/' + domain + '/'
print('nCreating new site files directory %s' % dir)
# if no directory - run mkdir
if not os.path.isdir(dir):
try:
sysacts.mkdir(dir)
print('Done.')
except OSError as e:
print('ERROR: %s' %e)
sys.exit(2)
# otherwise - skip
else:
print('Directory %s already present, skipping.' % dir)
# copy php.cgi file to new directory
# from file template
src = virt_hosts_dir + 'php-cgi/php.cgi'
# to newly created directory
dst = virt_hosts_dir + 'php-cgi/' + user + '/' + domain
print('nCopying %s to %s...' % (src, dst))
if not os.path.isfile(dst + '/php.cgi'):
try:
sysacts.cpcgi(src, dst)
print('Done.')
except IOError as e:
print('ERROR! %s.nExit.' %e)
sys.exit(3)
else:
print('File %s already present, skipping.' % (dst + '/php.cgi'))
# chmod files and directories for new user
for i in dest:
path = virt_hosts_dir + i + user + '/'
print('nChanging owner of %s directory and files to %s:' % (path, user))
try:
sysacts.chowdir(path)
except OSError as e:
print('ERROR: %s' %e)
sys.exit(4)
# create log-file
# two types of logs (access and error)
# thus - run in loop both
for i in logtypes:
logfile = logs_dir + domain + i
if not os.path.isfile(logfile):
print('nCreating log-file %s' % logfile)
try:
sysacts.tchlog(logfile)
except OSError as e:
print('ERROR: %s' %e)
sys.exit(5)
else:
print('nFile %s already present, skipping.' % logfile)
## create config file
# data which will looking for
# in template configs to change it
oflist = ('USER', 'DOMAIN')
# will change to this variables
tolist = (user, domain)
# for both Aoache and NGINX config dirs
for path in config_dirs:
# create var with name of template file
configin = path + 'TEMPLATE'
# creat var with name of new config file
configout = path + domain + '.conf'
print('nCreating new configuration file %s.' % configout)
if not os.path.isfile(configout):
try:
sysacts.createconf(configin, configout, oflist)
print('Done.')
except IOError as e:
print('ERROR: %s' %e)
sys.exit(6)
else:
print('File %s already present, skipping.' % configout)
else:
print('Skipping system actions.')
# if `-M` option was not set and options.mysql = True
if options.mysql:
print('nStarting MySQL actions.')
if not options.dbrootpw:
dbrootpw = getpass.getpass('nPlease, enter MySQL root password: ')
# we must have database name
if dbuserdb == None:
print('nERROR! please, set `-m` option with User database name.n')
sys.exit(7)
warnings.filterwarnings('error')
# Open and check MySQL connection
try:
dbconnect = fun.mysqlUserDb(dbroot, dbhost, dbrootpw, user, password, domain)
print('Connection opened successfully.')
except MySQLdb.Error as e:
print('MySQL error: %s nExit.n' % e)
sys.exit(8)
# create database
try:
print('nCreating database %s.' % dbuserdb)
dbconnect.createdb(dbuserdb)
print 'Database created: %s' % dbconnect.dbs
except Warning as error:
print('MySQL warning: %s' % error)
except MySQLdb.Error as e:
print('MySQL error: %s nExit.n' % e)
sys,exit(9)
# add user and grant prielegies
try:
print('nAdding privilegies to user %s with password %s to database %s.' % (user, password, dbuserdb))
dbconnect.grants(dbuserdb)
print('Access granted: %r' % dbconnect.grs)
except MySQLdb.Error as e:
print('MySQL error: %s nExit.n' % e)
sys,exit(10)
# close connection
print('nFinishing MySQL actions...')
del dbconnect
print 'Done.n'
else:
print('Skipping MySQL actions.')
Пример работы:
# ./create_vhost_new.py -h
usage: create_vhost_new.py [-h] [-u USER] [-p PASSWORD] [-d DOMAIN]
[-m DBUSERDB] [-r DBROOTPW] [-s] [-M]
optional arguments:
-h, --help show this help message and exit
-u USER, --user USER Username, for OS user and MySQL user.
-p PASSWORD, --password PASSWORD
Password for OS and MySQL user.
-d DOMAIN, --domain DOMAIN
Domainname, will be created virtualhost directories,
files.
-m DBUSERDB, --dbuserdb DBUSERDB
MySQL database name for user.
-r DBROOTPW, --dbrootpw DBROOTPW
MySQL root password.
-s, --system Add `-s` option to disable system operation (add user,
create directories and files)
-M, --mysql Add `-m` to disable MySQL operations (add user, add
database).
# ./create_vhost_new.py -u testsite8 -p Password -d testsite8.com -m testsite8_db1
User = testsite8;
Domain = Password;
Password = testsite8.com;
MySQL database = testsite8_db1.
Is it correct? y
OK, proceeding.
Running user check...
You are root, OK.
Adding user testsite8...
Done.
Creating new site files directory /var/www/vhosts/testsite8/testsite8.com/
Done.
Creating new site files directory /var/www/php-cgi/testsite8/testsite8.com/
Done.
Copying /var/www/php-cgi/php.cgi to /var/www/php-cgi/testsite8/testsite8.com...
Done.
Changing owner of /var/www/vhosts/testsite8/ directory and files to testsite8:
drwxr-xr-x 2 testsite8 testsite8 4096 Sep 14 17:19 testsite8.com
Changing owner of /var/www/php-cgi/testsite8/ directory and files to testsite8:
drwxr-xr-x 2 testsite8 testsite8 4096 Sep 14 17:19 testsite8.com
Creating log-file /var/log/httpd/testsite8.com-access.log
Creating log-file /var/log/httpd/testsite8.com-error.log
Creating new configuration file /etc/httpd/conf.d/testsite8.com.conf.
Done.
Creating new configuration file /etc/nginx/conf.d/testsite8.com.conf.
Done.
Starting MySQL actions.
Please, enter MySQL root password:
Connection opened successfully.
Creating database testsite8_db1.
Database created: testsite8_db1
Adding privilegies to user testsite8 with password Password to database testsite8_db1.
Access granted: ('testsite8', 'testsite8_db1')
Finishing MySQL actions...
Done.
Проверяем MySQL (хотя — это видно и в выводе самого скрипта):
# mysql -u testsite8 -pPassword Welcome to the MariaDB monitor. Commands end with ; or g. Your MariaDB connection id is 22311 Server version: 5.5.39-MariaDB MariaDB Server Copyright (c) 2000, 2014, Oracle, Monty Program Ab and others. Type 'help;' or 'h' for help. Type 'c' to clear the current input statement. MariaDB [(none)]> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | testsite8_db1 | +--------------------+ 2 rows in set (0.00 sec)
Проверяем работу PHP:
# printf "This is Testsite 8.n<?php echo phpinfo(); ?>" > /var/www/vhosts/testsite8/testsite8.com/index.php
Перезапускаем Apache HTTP и NGINX:
# service httpd restart Stopping httpd: [ OK ] Starting httpd: [ OK ] # service nginx restart Stopping nginx: [ OK ] Starting nginx: [ OK ]
Добавляем запись в hosts:
# cat /etc/hosts | tail -n 1 77.***.***.20 testsite8.com
Проверяем:
# curl http://testsite8.com | head
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 57122 0 57122 0 0 1558k 0 --:--:-- --:--:-- --:--:-- 1690k
This is Testsite 8.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "DTD/xhtml1-transitional.dtd">
<html><head>
<style type="text/css">
body {background-color: #ffffff; color: #000000;}
body, td, th, h1, h2 {font-family: sans-serif;}
pre {margin: 0px; font-family: monospace;}
a:link {color: #000099; text-decoration: none; background-color: #ffffff;}
a:hover {text-decoration: underline;}
table {border-collapse: collapse;}
Всё работает.




