Готовясь с переезду со старого сервера (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;}
Всё работает.