#!/usr/bin/env bash if [ -v $OSHIPKA_PATH ]; then echo "You need to specify OSHIPKA_PATH env variable" exit 1 fi echo "oshipka is at: $OSHIPKA_PATH" #!/usr/bin/env bash HELP=" Usage $0 [ bootstrap | model | db_migrate | db_upgrade | db_populate | db_recreate | db_purge_recreate | init | worker | web | venv | install | link | cert ] bootstrap [PROJECT_PATH] Create a new project in PROJECT_PATH init Install dev env download_sensitive Download sensitive model Create or update a model from files in webapp/view_models/*.yaml db_migrate DB migration db_upgrade DB upgrade to last migration db_populate Populate db with data from data_static/ and populate.py db_recreate Delete the database, recreate to latest migration db_recreate_populate Same as db_recreate and populate db_purge_recreate_populate Same as db_recreate_populate but also purge the migrations translate Translations subcommand ca Create oshipka certificate authority (Oshipka CA) cert_dev [DOMAIN] Generate dev certificate signed by Oshipka CA worker Start worker web Start webapp venv Init venv install Install requirements link Link dev oshipka prod Run in prod prod_install Install in prod cert [DOMAIN] Install certificate " HELP_TRANSLATION=" USAGE ./manage.sh translate [extract|gen {lang}|compile|update] extract Extract strings in files as defined in translations/babel.cfg gen {lang} Init translations for {lang} compile Compile all translations update Use after a new extract - it may mark strings as fuzzy. " command_translate() { shift TRANSLATE_COMMAND=$1 shift source venv/bin/activate case "$TRANSLATE_COMMAND" in extract) pybabel extract -F translations/babel.cfg -o translations/messages.pot . ;; gen) pybabel init -i translations/messages.pot -d translations -l "$@" ;; compile) pybabel compile -d translations ;; update) pybabel update -i translations/messages.pot -d translations ;; *) >&2 echo -e "${HELP_TRANSLATION}" ;; esac return $? } ca () { if [ -f ${OSHIPKA_PATH}/ssl/oshipka_ca.pem ]; then echo "Oshipka CA already exists" exit 1; fi echo "Creating Oshipka CA..." mkdir -p ${OSHIPKA_PATH}/ssl cd ${OSHIPKA_PATH}/ssl || exit openssl genrsa -out oshipka_ca.key 2048 openssl req -x509 -new -nodes -key oshipka_ca.key -sha256 -days 1825 -out oshipka_ca.pem -subj "/C=/ST=/L=/O=Oshipka Web Development CA/OU=/CN=oshipka_web_development_ca" } cert_dev () { shift DOMAIN=$1 if [ -f webapp/ssl/cert.crt ]; then echo "Certificate already exists" exit 1; elif [ ! -f ${OSHIPKA_PATH}/ssl/oshipka_ca.key ]; then echo "Oshipka CA not found, generating..." ca fi if [ -z "${DOMAIN}" ]; then DOMAIN=$(basename `pwd`) fi mkdir -p webapp/ssl echo "Create CSR for ${DOMAIN}" openssl genrsa -out "webapp/ssl/cert.key" 2048 openssl req -new -key "webapp/ssl/cert.key" -out "webapp/ssl/cert.csr" -subj "/C=/ST=/L=/O=Oshipka Web Development/OU=/CN=${DOMAIN}.localhost" cat > "webapp/ssl/cert.ext" << EOF authorityKeyIdentifier=keyid,issuer basicConstraints=CA:FALSE keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment subjectAltName = @alt_names [alt_names] DNS.1 = ${DOMAIN}.localhost EOF echo "Create self-signed certificate" openssl x509 -req -in "webapp/ssl/cert.csr" -CA ${OSHIPKA_PATH}/ssl/oshipka_ca.pem -CAkey ${OSHIPKA_PATH}/ssl/oshipka_ca.key -CAcreateserial -out "webapp/ssl/cert.crt" -days 825 -sha256 -extfile "webapp/ssl/cert.ext" rm "webapp/ssl/cert.ext" "webapp/ssl/cert.csr" } worker () { source venv/bin/activate python worker.py } web () { source venv/bin/activate python run.py } init_apt() { sudo apt-get update sudo apt-get install -y build-essential libssl-dev libffi-dev python3-dev } init_venv() { virtualenv -p python3 venv } install_reqs() { source venv/bin/activate pip3 install --upgrade pip --trusted-host pypi.org --trusted-host files.pythonhosted.org pip install -r requirements.txt } link_dev_oshipka() { source venv/bin/activate pip install -e ${OSHIPKA_PATH} pip install -e ${TWW_PATH} } download_sensitive() { if [ ! -f sensitive.py ]; then echo "File sensitive.py NOT FOUND" if [ -f sensitive_dev.py ]; then echo "Copying sensitive_dev for dev env" cp sensitive_dev.py sensitive.py else exit 1; fi fi } init() { init_apt init_venv install_reqs link_dev_oshipka mkdir -p data db_upgrade download_sensitive db_populate } install_cert() { PROJECT_DOMAIN=$1 sudo apt install certbot sudo certbot certonly --authenticator standalone --installer nginx --pre-hook "service nginx stop" --post-hook "service nginx start" --redirect --agree-tos --no-eff-email --email danieltcv@gmail.com -d ${PROJECT_DOMAIN} --no-bootstrap } bootstrap() { shift PROJECT_PATH=$1 if [[ -z "$PROJECT_PATH" ]]; then read -p "Enter project path: " PROJECT_PATH fi if [[ -z "$PROJECT_PATH" ]]; then echo "ERROR: Specify project path" exit 1 else echo "INFO: Project path: $PROJECT_PATH" PROJECT_PATH=`realpath $PROJECT_PATH` echo "INFO: Absolute project path: $PROJECT_PATH" if [[ "$PROJECT_PATH" == $OSHIPKA_PATH* ]]; then echo "ERROR: Project path can't be inside this directory. Exiting..." exit 1 fi if [ -d $PROJECT_PATH ]; then echo "ERROR: Project path exists. Please remove or specify another. Exiting..." exit 1 else echo "INFO: Project path doesn't exist, creating..." mkdir -p $PROJECT_PATH fi fi PROJECT_NAME=$(basename $PROJECT_PATH) echo "INFO: Bootstrapping project $PROJECT_NAME..." mkdir -p ${PROJECT_PATH} cp -r ${OSHIPKA_PATH}/bootstrap/* ${PROJECT_PATH}/ cp ${OSHIPKA_PATH}/bootstrap/.gitignore ${PROJECT_PATH}/.gitignore mkdir ${PROJECT_PATH}/data cd ${PROJECT_PATH} sed -i "s/PROJECT_NAME.localhost:5000/${PROJECT_NAME}.localhost:5000/" config.py cert_dev init_venv link_dev_oshipka source venv/bin/activate model python manager.py db init python manager.py db migrate -m "001" _post_migrate python manager.py db upgrade command_translate "" extract command_translate "" gen en # TODO: sed acts on a line-by-line # sed -i 's/msgid "Project"/msgstr ${PROJECT_NAME}/' translations/en/LC_MESSAGES/messages.po command_translate "" compile git init . git add . git commit -m "Initial commit" } run_in_prod() { shift PORT=$1 source venv/bin/activate gunicorn -w 4 -b 0.0.0.0:${PORT} run:app } prod_install() { shift if [ ! -f ${OSHIPKA_PATH}/provision/auto_dns/sensitive.py ]; then echo "File ${OSHIPKA_PATH}/provision/auto_dns/sensitive.py NOT FOUND" exit 1; fi sudo apt install -y nginx dnsutils source venv/bin/activate PROJECT_NAME=$(basename `pwd`) echo "1/6 Generating service and config files..." python "${OSHIPKA_PATH}/provision/prod_mgmt.py" if [ -f "/etc/systemd/system/${PROJECT_NAME}.service" ]; then echo "Service gunicorn for ${PROJECT_NAME} service exists." systemctl status ${PROJECT_NAME} else echo "Installing '$PROJECT_NAME' gunicorn service" sudo cp "${OSHIPKA_PATH}/provision/tmp/${PROJECT_NAME}.service" /etc/systemd/system/ sudo systemctl enable "${PROJECT_NAME}" sudo systemctl start "${PROJECT_NAME}" fi echo "2/6 Installing '$PROJECT_NAME' worker service" if [ -f "/etc/systemd/system/${PROJECT_NAME}_worker.service" ]; then echo "Service worker for ${PROJECT_NAME} service exists." systemctl status "${PROJECT_NAME}_worker" else sudo cp "${OSHIPKA_PATH}/provision/tmp/${PROJECT_NAME}_worker.service" /etc/systemd/system/ sudo systemctl enable "${PROJECT_NAME}_worker" sudo systemctl start "${PROJECT_NAME}_worker" fi NGINX_CONFIG_FILE=$(basename `find $OSHIPKA_PATH/provision/tmp -name *.conf`) DOMAIN=$(basename -s .conf $NGINX_CONFIG_FILE) echo "3/6 Installing '$DOMAIN' domain..." python "${OSHIPKA_PATH}/provision/auto_dns/set_domain_ipv4.py" "$DOMAIN" sudo systemctl start nginx echo "Enabling firewall rule -> 80/tcp..." sudo ufw allow proto tcp to any port 80 echo "4/6 Installing '$PROJECT_NAME' insecure nginx config..." if [ -f "/etc/nginx/sites-available/${DOMAIN}.insecure" ]; then echo "Insecure Nginx config for ${PROJECT_NAME} available." if [ -f "/etc/nginx/sites-enabled/${DOMAIN}_insecure" ]; then echo "Nginx config for ${PROJECT_NAME} enabled." else echo "Nginx config for ${PROJECT_NAME} NOT enabled." fi else echo "Installing insecure nginx config for ${PROJECT_NAME} -> enabling + available." sudo cp "${OSHIPKA_PATH}/provision/tmp/${DOMAIN}.insecure" /etc/nginx/sites-available/ sudo ln -s "/etc/nginx/sites-available/${DOMAIN}.insecure" "/etc/nginx/sites-enabled/${DOMAIN}.insecure" sudo systemctl reload nginx fi echo "5/6 Installing '$PROJECT_NAME' certificate..." install_cert $DOMAIN echo "6/6 Installing '$PROJECT_NAME' secure nginx config..." echo "Enabling firewall rule for 192.168.1.1 -> 443/tcp..." sudo ufw allow proto tcp to any port 443 echo "Removing '$PROJECT_NAME' insecure nginx config..." sudo rm "/etc/nginx/sites-available/${DOMAIN}.insecure" "/etc/nginx/sites-enabled/${DOMAIN}.insecure" # PROBLEM: BIO_new_file("/etc/nginx/dhparam.pem") failed # SOLUTION: sudo openssl dhparam -out /etc/nginx/dhparam.pem 2048 if [ ! -f "/etc/nginx/dhparam.pem" ]; then sudo openssl dhparam -out /etc/nginx/dhparam.pem 2048 fi if [ -f "/etc/nginx/sites-available/${NGINX_CONFIG_FILE}" ]; then echo "Nginx config for ${PROJECT_NAME} available." if [ -f "/etc/nginx/sites-enabled/${NGINX_CONFIG_FILE}" ]; then echo "Nginx config for ${PROJECT_NAME} enabled." else echo "Nginx config for ${PROJECT_NAME} NOT enabled." fi else echo "Installing nginx config for ${PROJECT_NAME} -> enabling + available." sudo cp "${OSHIPKA_PATH}/provision/tmp/${NGINX_CONFIG_FILE}" /etc/nginx/sites-available/ sudo ln -s "/etc/nginx/sites-available/${NGINX_CONFIG_FILE}" "/etc/nginx/sites-enabled/${NGINX_CONFIG_FILE}" sudo systemctl reload nginx fi # PROBLEM : Certificates missing # SOLUTION: rm /etc/ssl/certs/ca-certificates.crt # sudo update-ca-certificates in virtual environment. } model() { shift source venv/bin/activate python "${OSHIPKA_PATH}/vm_gen/vm_gen.py" "`pwd`" } db_migrate() { shift source venv/bin/activate next_id=$(printf "%03d" $(($(ls -la migrations/versions/*.py | wc -l)+1))) python manager.py db migrate -m "${next_id}" _post_migrate } db_upgrade() { shift source venv/bin/activate mkdir -p data python manager.py db upgrade } db_purge_recreate_populate() { shift source venv/bin/activate rm -rf data/db.sqlite data/search_index migrations/ data/media python manager.py db init model db_migrate db_upgrade db_populate } db_populate() { shift source venv/bin/activate python init_populate.py } db_recreate() { shift source venv/bin/activate rm -rf data/db.sqlite data/search_index db_upgrade } db_recreate_populate() { shift db_recreate db_populate } _post_migrate() { for i in migrations/versions/*.py; do sed -i "s/sqlalchemy_utils.types.choice.ChoiceType(length=255), /sa.String(), / " "$i"; sed -i "s/oshipka.persistance.LiberalBoolean(), /sa.Boolean(), / " "$i"; done } command_main() { INITIAL_COMMAND=$1 case "$INITIAL_COMMAND" in bootstrap) bootstrap "$@" ;; model) model "$@" ;; db_migrate) db_migrate "$@" ;; db_upgrade) db_upgrade "$@" ;; db_populate) db_populate "$@" ;; db_recreate) db_recreate "$@" ;; db_recreate_populate) db_recreate_populate "$@" ;; db_purge_recreate_populate) db_purge_recreate_populate "$@" ;; translate) command_translate "$@" ;; init) init "$@" ;; download_sensitive) download_sensitive "$@" ;; worker) worker "$@" ;; web) web "$@" ;; venv) init_venv "$@" ;; ca) ca "$@" ;; cert_dev) cert_dev "$@" ;; install) install_reqs "$@" ;; link) link_dev_oshipka "$@" ;; prod) run_in_prod "$@" ;; prod_install) prod_install "$@" ;; cert) shift && install_cert "$@" ;; *) >&2 echo -e "${HELP}" return 1 ;; esac return $? } command_main "$@"