Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
*.py[cod]

test.db
test.txt
sina.html

# C extensions
*.so

# Packages
*.egg
*.egg-info
dist
build
eggs
parts
bin
var
sdist
develop-eggs
.installed.cfg
lib
lib64
__pycache__

# Installer logs
pip-log.txt

# Unit test / coverage reports
.coverage
.tox
nosetests.xml

######################
# OS generated files #
######################
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
Icon?
ehthumbs.db
Thumbs.db
dist
MANIFEST

# Translations
*.mo

# Mr Developer
.mr.developer.cfg
.project
.pydevproject
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
awesome-python3-webapp
======================

A python webapp tutorial.
1 change: 1 addition & 0 deletions backup/README
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
stores database backup files.
1 change: 1 addition & 0 deletions conf/nginx/README
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sample nginx configuration file
33 changes: 33 additions & 0 deletions conf/nginx/awesome
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
server {
listen 80;

root /srv/awesome/www;
access_log /srv/awesome/log/access_log;
error_log /srv/awesome/log/error_log;

# server_name awesome.liaoxuefeng.com;

client_max_body_size 1m;

gzip on;
gzip_min_length 1024;
gzip_buffers 4 8k;
gzip_types text/css application/x-javascript application/json;

sendfile on;

location /favicon.ico {
root /srv/awesome/www;
}

location ~ ^\/static\/.*$ {
root /srv/awesome/www;
}

location / {
proxy_pass http://127.0.0.1:9000;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
1 change: 1 addition & 0 deletions conf/supervisor/README
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sample supervisor configuration file
11 changes: 11 additions & 0 deletions conf/supervisor/awesome.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[program:awesome]

command = /srv/awesome/www/app.py
directory = /srv/awesome/www
user = www-data
startsecs = 3

redirect_stderr = true
stdout_logfile_maxbytes = 50MB
stdout_logfile_backups = 10
stdout_logfile = /srv/awesome/log/app.log
1 change: 1 addition & 0 deletions dist/README
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
store dist package.
169 changes: 169 additions & 0 deletions fabfile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

__author__ = 'Michael Liao'

'''
Deployment toolkit.
'''

import os, re

from datetime import datetime
from fabric.api import *

env.user = 'michael'
env.sudo_user = 'root'
env.hosts = ['192.168.0.3']

db_user = 'www-data'
db_password = 'www-data'

_TAR_FILE = 'dist-awesome.tar.gz'

_REMOTE_TMP_TAR = '/tmp/%s' % _TAR_FILE

_REMOTE_BASE_DIR = '/srv/awesome'

def _current_path():
return os.path.abspath('.')

def _now():
return datetime.now().strftime('%y-%m-%d_%H.%M.%S')

def backup():
'''
Dump entire database on server and backup to local.
'''
dt = _now()
f = 'backup-awesome-%s.sql' % dt
with cd('/tmp'):
run('mysqldump --user=%s --password=%s --skip-opt --add-drop-table --default-character-set=utf8 --quick awesome > %s' % (db_user, db_password, f))
run('tar -czvf %s.tar.gz %s' % (f, f))
get('%s.tar.gz' % f, '%s/backup/' % _current_path())
run('rm -f %s' % f)
run('rm -f %s.tar.gz' % f)

def build():
'''
Build dist package.
'''
includes = ['static', 'templates', 'transwarp', 'favicon.ico', '*.py']
excludes = ['test', '.*', '*.pyc', '*.pyo']
local('rm -f dist/%s' % _TAR_FILE)
with lcd(os.path.join(_current_path(), 'www')):
cmd = ['tar', '--dereference', '-czvf', '../dist/%s' % _TAR_FILE]
cmd.extend(['--exclude=\'%s\'' % ex for ex in excludes])
cmd.extend(includes)
local(' '.join(cmd))

def deploy():
newdir = 'www-%s' % _now()
run('rm -f %s' % _REMOTE_TMP_TAR)
put('dist/%s' % _TAR_FILE, _REMOTE_TMP_TAR)
with cd(_REMOTE_BASE_DIR):
sudo('mkdir %s' % newdir)
with cd('%s/%s' % (_REMOTE_BASE_DIR, newdir)):
sudo('tar -xzvf %s' % _REMOTE_TMP_TAR)
with cd(_REMOTE_BASE_DIR):
sudo('rm -f www')
sudo('ln -s %s www' % newdir)
sudo('chown www-data:www-data www')
sudo('chown -R www-data:www-data %s' % newdir)
with settings(warn_only=True):
sudo('supervisorctl stop awesome')
sudo('supervisorctl start awesome')
sudo('/etc/init.d/nginx reload')

RE_FILES = re.compile('\r?\n')

def rollback():
'''
rollback to previous version
'''
with cd(_REMOTE_BASE_DIR):
r = run('ls -p -1')
files = [s[:-1] for s in RE_FILES.split(r) if s.startswith('www-') and s.endswith('/')]
files.sort(cmp=lambda s1, s2: 1 if s1 < s2 else -1)
r = run('ls -l www')
ss = r.split(' -> ')
if len(ss) != 2:
print ('ERROR: \'www\' is not a symbol link.')
return
current = ss[1]
print ('Found current symbol link points to: %s\n' % current)
try:
index = files.index(current)
except ValueError, e:
print ('ERROR: symbol link is invalid.')
return
if len(files) == index + 1:
print ('ERROR: already the oldest version.')
old = files[index + 1]
print ('==================================================')
for f in files:
if f == current:
print (' Current ---> %s' % current)
elif f == old:
print (' Rollback to ---> %s' % old)
else:
print (' %s' % f)
print ('==================================================')
print ('')
yn = raw_input ('continue? y/N ')
if yn != 'y' and yn != 'Y':
print ('Rollback cancelled.')
return
print ('Start rollback...')
sudo('rm -f www')
sudo('ln -s %s www' % old)
sudo('chown www-data:www-data www')
with settings(warn_only=True):
sudo('supervisorctl stop awesome')
sudo('supervisorctl start awesome')
sudo('/etc/init.d/nginx reload')
print ('ROLLBACKED OK.')

def restore2local():
'''
Restore db to local
'''
backup_dir = os.path.join(_current_path(), 'backup')
fs = os.listdir(backup_dir)
files = [f for f in fs if f.startswith('backup-') and f.endswith('.sql.tar.gz')]
files.sort(cmp=lambda s1, s2: 1 if s1 < s2 else -1)
if len(files)==0:
print 'No backup files found.'
return
print ('Found %s backup files:' % len(files))
print ('==================================================')
n = 0
for f in files:
print ('%s: %s' % (n, f))
n = n + 1
print ('==================================================')
print ('')
try:
num = int(raw_input ('Restore file: '))
except ValueError:
print ('Invalid file number.')
return
restore_file = files[num]
yn = raw_input('Restore file %s: %s? y/N ' % (num, restore_file))
if yn != 'y' and yn != 'Y':
print ('Restore cancelled.')
return
print ('Start restore to local database...')
p = raw_input('Input mysql root password: ')
sqls = [
'drop database if exists awesome;',
'create database awesome;',
'grant select, insert, update, delete on awesome.* to \'%s\'@\'localhost\' identified by \'%s\';' % (db_user, db_password)
]
for sql in sqls:
local(r'mysql -uroot -p%s -e "%s"' % (p, sql))
with lcd(backup_dir):
local('tar zxvf %s' % restore_file)
local(r'mysql -uroot -p%s awesome < backup/%s' % (p, restore_file[:-7]))
with lcd(backup_dir):
local('rm -f %s' % restore_file[:-7])
Loading