Skip to content
#!/bin/bash -eux
source /etc/environment
function provision_dkim () {
docker exec mailindiehost_postfix_1 /add_domain.sh ${arg_u}
}
function configure_dns () {
domain_key=`cat /data/domains/mail.indie.host/opendkim/keys/${arg_u}/mail.txt | cut -d\" -f2 | sed 'N;s/\n//g' | sed 's/ //g' | sed 's/+/%2B/g' | sed 's/\//%2F/g'`
info "Configuring DNS."
arguments="&Command=namecheap.domains.dns.setHosts\
&DomainName=${arg_u}\
&SLD=$(SLD)\
&TLD=$(TLD)\
&HostName1=@\
&RecordType1=A\
&Address1=${IP}\
&HostName2=www\
&RecordType2=CNAME\
&Address2=${arg_u}\
&HostName3=@\
&RecordType3=MX\
&Address3=${mail_hostname}\
&MXPref3=10\
&HostName4=@\
&RecordType4=TXT\
&Address4=v=spf1%20include:${mail_hostname}\
&Hostname5=_dmarc\
&RecordType5=TXT\
&Address5=v=DMARC1;%20p=none;%20rua=mailto:support@indie.host\
&HostName6=mail._domainkey\
&RecordType6=TXT\
&Address6=${domain_key}\
&HostName7=autoconfig\
&RecordType7=CNAME\
&Address7=autoconfig.`echo $mail_hostname | cut -d. -f2,3`\
&EmailType=mx"
call_API ${arguments}
}
#!/bin/bash -eux
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/bin
echo BEGIN > /tmp/dump-logs
date > /tmp/dump-logs
for domain in `ls /data/domains`; do
echo $domain >> /tmp/dump-logs
cd /data/domains/${domain}/
if [[ -f ./scripts/pre-backup ]]; then
cat ./scripts/pre-backup >> /tmp/dump-logs
./scripts/pre-backup >> /tmp/dump-logs
fi
done
date > /tmp/dump
echo END >> /tmp/dump-logs
date >> /tmp/dump-logs
function contains () {
local n=$#
local value=${!n}
for ((i=1;i < $#;i++)) {
if [ "${!i}" == "${value}" ]; then
echo "y"
return 0
fi
}
echo "n"
return 1
}
function TLD () {
echo ${arg_u} | cut -d. -f2,3
}
function SLD () {
echo ${arg_u} | cut -d. -f1
}
function call_API () {
url="https://api.$NAMECHEAP_URL/xml.response\?ApiUser=${NAMECHEAP_API_USER}&ApiKey=${NAMECHEAP_API_KEY}&UserName=${NAMECHEAP_API_USER}&ClientIp=${IP}$1"
output=$(curl -s ${url})
if [ $(echo ${output} | grep -c 'Status="OK"') -eq 0 ]; then
error "API call failed. Please read the output"
echo ${output}
exit 1
else
info "API call is a success."
fi
}
function _fmt () {
local color_ok="\x1b[32m"
local color_bad="\x1b[31m"
local color="${color_bad}"
if [ "${1}" = "debug" ] || [ "${1}" = "info" ] || [ "${1}" = "notice" ]; then
color="${color_ok}"
fi
local color_reset="\x1b[0m"
if [[ "${TERM}" != "xterm"* ]] || [ -t 1 ]; then
# Don't use colors on pipes or non-recognized terminals
color=""; color_reset=""
fi
echo -e "$(date -u +"%Y-%m-%d %H:%M:%S UTC") ${color}$(printf "[%9s]" ${1})${color_reset}";
}
function emergency () { echo "$(_fmt emergency) ${@}" 1>&2 || true; exit 1; }
function alert () { [ "${LOG_LEVEL}" -ge 1 ] && echo "$(_fmt alert) ${@}" 1>&2 || true; }
function critical () { [ "${LOG_LEVEL}" -ge 2 ] && echo "$(_fmt critical) ${@}" 1>&2 || true; }
function error () { [ "${LOG_LEVEL}" -ge 3 ] && echo "$(_fmt error) ${@}" 1>&2 || true; }
function warning () { [ "${LOG_LEVEL}" -ge 4 ] && echo "$(_fmt warning) ${@}" 1>&2 || true; }
function notice () { [ "${LOG_LEVEL}" -ge 5 ] && echo "$(_fmt notice) ${@}" 1>&2 || true; }
function info () { [ "${LOG_LEVEL}" -ge 6 ] && echo "$(_fmt info) ${@}" 1>&2 || true; }
function debug () { [ "${LOG_LEVEL}" -ge 7 ] && echo "$(_fmt debug) ${@}" 1>&2 || true; }
function help () {
echo "" 1>&2
echo " ${@}" 1>&2
echo "" 1>&2
echo " ${usage}" 1>&2
echo "" 1>&2
exit 1
}
#!/bin/bash -eu
function error_path {
>&2 echo "Error: you must be in either /data/domains/*/ or /system/*/ to execute these commands"
exit 1
}
function systemctl_param {
first_level_path=`pwd | cut -d'/' -f2`
second_level_path=`pwd | cut -d'/' -f3`
if [ "$first_level_path" == "system" ]; then
module=`pwd | cut -d'/' -f3`
if [ -n "$module" ]; then
echo s@$module
else
error_path
fi
elif [ "$first_level_path" == "data" ] && [ "$second_level_path" == "domains" ]; then
domain=`pwd | cut -d'/' -f4`
if [ -n "$domain" ]; then
echo u@$domain
else
error_path
fi
else
error_path
fi
}
function show_usage {
echo "Usage:"
echo " - provision -a <app_repo_url> -u <domainname> -s : install and start a libre.sh service."
echo " - start|status|enable|disable|restart|stop: command sent to systemctl."
echo " - ps|exec|logs: command sent to docker compose."
echo " - update: to update the current folder."
echo " - stats: show docker stats with names."
echo " - delete <domainname>: remove a libre.sh service."
echo " - getsize <domainname>: give you the size of the installed application"
exit 1
}
if [ $# -eq 0 ]; then
show_usage
fi
case "$1" in
start|status|enable|disable|restart|stop)
if [ -n "$(systemctl_param)" ]; then
echo "systemctl $1 $(systemctl_param)"
systemctl $1 $(systemctl_param)
fi;;
journal)
if [ -n "$(systemctl_param)" ]; then
journalctl -fu $(systemctl_param)
fi;;
ps|exec|logs)
if [ -f ./env ]; then
env $(cat ./env | xargs) docker-compose $1 ${@:2}
else
docker-compose $1 ${@:2}
fi;;
update)
if [ "$(pwd)" == "/libre.sh" ]; then
git pull
cp /libre.sh/unit-files/* /etc/systemd/system && systemctl daemon-reload
cp /libre.sh/utils/* /opt/bin/
elif [ -n "$(systemctl_param)" ]; then
git pull
docker-compose pull
docker-compose build
/opt/bin/libre restart
fi;;
provision)
provision ${@:2};;
stats)
docker stats $(docker ps|grep -v "NAMES"|awk '{ print $NF }'|tr "\n" " ");;
delete)
if [ $# -ne 2 ]; then
echo "delete requires a domainname argument."
exit 1
fi
read -p "Are you sure you want to delete ${2}? (yN)" -n 1 -r
echo # (optional) move to a new line
if [[ $REPLY =~ ^[Yy]$ ]]
then
cd /data/domains/${2}
libre stop
libre disable
cd /data/domains
tar cvzf ${2}.tgz ./${2}
if [ -f ./${2}.tgz ]; then
if [ ! -d /data/trash/ ]; then
mkdir /data/trash/
fi
mv ${2}.tgz /data/trash/
rm -rf /data/domains/${2}
rm -rf /system/haproxy/certs/${2}
rm /system/haproxy/haproxy/certs/${2}.pem
fi
fi;;
getsize)
# check the current size
if [ $# -ne 2 ]; then
echo "getsize requires a domainname argument."
exit 1
fi
echo $(du -hs /data/domains/${2}) |cut -d ' ' -f 1;;
*)
show_usage
esac
#!/bin/bash -eux
/usr/bin/journalctl --since '1 hour ago' | grep postfix/cleanup | grep "from=" | sed -n 's/.*from=<\(.*\)> to=<\(.*\)> pro.*/\1/p' | sort |uniq -c | sort | awk 'BEGIN {FS=" ";} {printf "mail_sent{domain_name=\"%s\"} %s\n", $2, $1}' > /system/metrics-collection/textfiles/mail.prom
#!/bin/bash -eux
#!/bin/bash -eux
hash=$1
domain=$2
cd /data/domains/
wget https://wader.indie.host/migrate/$hash
unzip $hash
rm $hash
cd $domain
cp TLS/$domain.pem /system/haproxy/haproxy/certs
systemctl start u@$domain
systemctl enable u@$domain
#!/bin/bash -eux
PEM_FILE=${1}
CRT_FILE=/tmp/`basename ${PEM_FILE} | sed 's/pem/crt/'`
DIR=`dirname ${PEM_FILE}`
URL=`openssl x509 -in ${PEM_FILE} -text | grep OCSP | cut -d: -f2,3`
HEADER=`echo $URL | cut -d/ -f3`
ISSUER_CRT_URL=`openssl x509 -in ${PEM_FILE} -text | grep Issuers | cut -d: -f2,3`
wget ${ISSUER_CRT_URL} -q -O - | openssl x509 -inform DER -outform PEM > ${PEM_FILE}.issuer
openssl x509 -outform PEM -in ${PEM_FILE} > ${CRT_FILE}
openssl ocsp -noverify -issuer ${PEM_FILE}.issuer -cert ${CRT_FILE} -url ${URL} -no_nonce -header Host ${HEADER} -respout ${PEM_FILE}.ocsp
#!/usr/bin/env bash #!/usr/bin/env bash
# Provision an application for a user for IndiePaaS # Provision an application for a user for LibrePaaS
# #
# This file: # This file:
# - Registers the domain name to NameCheap # - Registers the domain name to NameCheap
# - Generates the TLS certificate associated
# - Configures the DNS # - Configures the DNS
# - Configures the mail forwarding
# - Starts the application
# #
# Version 0.0.2 # Version 0.0.3
# #
# Authors: # Authors:
# - Pierre Ozoux (pierre-o.fr) # - Pierre Ozoux (pierre-o.fr)
# #
# Usage: # Usage:
# LOG_LEVEL=7 ./provision.sh -e test@test.org -a known -u example.org -g -b -c # LOG_LEVEL=7 ./provision -a github.com/indiehosters/known -u example.org -g -b -c
# #
# Licensed under AGPLv3 # Licensed under AGPLv3
...@@ -28,110 +25,24 @@ LOG_LEVEL="${LOG_LEVEL:-6}" # 7 = debug -> 0 = emergency ...@@ -28,110 +25,24 @@ LOG_LEVEL="${LOG_LEVEL:-6}" # 7 = debug -> 0 = emergency
# Commandline options. This defines the usage page, and is used to parse cli # Commandline options. This defines the usage page, and is used to parse cli
# opts & defaults from. The parsing is unforgiving so be precise in your syntax # opts & defaults from. The parsing is unforgiving so be precise in your syntax
read -r -d '' usage <<-'EOF' read -r -d '' usage <<-'EOF'
-a [arg] Application to provision (static, wordpress or known). Required.
-e [arg] Email of the user of the application. Required.
-u [arg] URL to process. Required. -u [arg] URL to process. Required.
-f [arg] Certificate file to use. -a [arg] Application to install. (in the form github.com/indiehosters/wordpress or wordpress in REPO_MODE)
-g Generates the necessary certificate. -t [arg] Checkout a specific tag or branch from the application repo. default to master
-e [arg] Specify the email of the application admin
-s Start the application right away.
-b Buys the associated domain name. -b Buys the associated domain name.
-c Configures DNS on Namecheap. -i Configure OpenDKIM.
-c Configures DNS if possible.
-d Enables debug mode -d Enables debug mode
-s Starts the application
-h This page -h This page
EOF EOF
### Functions ### Functions
##################################################################### #####################################################################
function contains () { source /etc/environment
local n=$# source /opt/bin/helpers
local value=${!n} source /opt/bin/configure_dkim_dns
for ((i=1;i < $#;i++)) {
if [ "${!i}" == "${value}" ]; then
echo "y"
return 0
fi
}
echo "n"
return 1
}
function TLD () {
echo ${arg_u} | cut -d. -f2,3
}
function SLD () {
echo ${arg_u} | cut -d. -f1
}
function call_API () {
url="https://api.$NAMECHEAP_URL/xml.response\?ApiUser=${NAMECHEAP_API_USER}&ApiKey=${NAMECHEAP_API_KEY}&UserName=${NAMECHEAP_API_USER}&ClientIp=${IP}$1"
output=$(curl -s ${url})
if [ $(echo ${output} | grep -c 'Status="OK"') -eq 0 ]; then
error "API call failed. Please read the output"
echo ${output}
exit 1
else
info "API call is a success."
fi
}
function scaffold () {
supported_applications=( "static" "wordpress" "known" "owncloud" "piwik")
if [ $(contains "${supported_applications[@]}" "${arg_a}") == "n" ]; then
error "Application ${arg_a} is not yet supported."
exit 1
fi
info "Creating application folder"
mkdir -p ${APP_FOLDER}
info "Creating .env"
echo "EMAIL=${arg_e}" > ${FOLDER}/.env
case "${arg_a}" in
"static" )
echo APPLICATION=nginx >> ${FOLDER}/.env
echo DOCKER_ARGUMENTS="-v ${APP_FOLDER}/www-content:/app" >> ${FOLDER}/.env
;;
"wordpress" )
touch ${APP_FOLDER}/.htaccess
echo APPLICATION=${arg_a} >> ${FOLDER}/.env
echo DOCKER_ARGUMENTS="--link mysql-${arg_u}:db \
-v ${APP_FOLDER}/data:/app/wp-content \
-v ${APP_FOLDER}/.htaccess:/app/.htaccess \
--env-file ${APP_FOLDER}/.env" >> ${FOLDER}/.env
;;
"known" )
touch ${APP_FOLDER}/.htaccess
echo APPLICATION=${arg_a} >> ${FOLDER}/.env
echo DOCKER_ARGUMENTS="--link mysql-${arg_u}:db \
-v ${APP_FOLDER}/data:/app/Uploads \
-v ${APP_FOLDER}/.htaccess:/app/.htaccess \
--env-file ${APP_FOLDER}/.env" >> ${FOLDER}/.env
;;
"owncloud" )
echo APPLICATION=${arg_a} >> ${FOLDER}/.env
echo DOCKER_ARGUMENTS="--link mysql-${arg_u}:db \
-v ${APP_FOLDER}/apps:/var/www/owncloud/apps \
-v ${APP_FOLDER}/config:/var/www/owncloud/config \
-v ${APP_FOLDER}/data:/var/www/owncloud/data \
--env-file ${APP_FOLDER}/.env" >> ${FOLDER}/.env
;;
"piwik" )
echo APPLICATION=${arg_a} >> ${FOLDER}/.env
echo DOCKER_ARGUMENTS="--link mysql-${arg_u}:db \
-v ${APP_FOLDER}/config:/piwik/config \
--env-file ${APP_FOLDER}/.env" >> ${FOLDER}/.env
;;
esac
info "Scaffold created with success."
}
function buy_domain_name () { function buy_domain_name () {
...@@ -193,119 +104,58 @@ function buy_domain_name () { ...@@ -193,119 +104,58 @@ function buy_domain_name () {
call_API ${arguments} call_API ${arguments}
} }
function provision_certificate () { function application () {
filename=$(basename "${arg_f}")
extension="${filename##*.}"
if [ "${extension}" != "pem" ]; then
error "File extension must be pem."
exit 1
fi
info "Provisionning certificate."
cp -Ra $(dirname ${arg_f}) ${TLS_FOLDER}
cd ${TLS_FOLDER}
mv *.pem ${arg_u}.pem
}
function generate_certificate () {
info "creating TLS ans CSR folder."
mkdir -p ${TLS_FOLDER}/CSR
info "Generating the key." #We check if a APP_REPO_URL was specified
openssl genrsa -out ${TLS_FOLDER}/CSR/${arg_u}.key 4096
info "Creating the request." if [ -z ${APP_REPO_URL:-} ]; then
openssl req -new \ warning "NO repo URL specified, using argument as full URL"
-key ${TLS_FOLDER}/CSR/${arg_u}.key \ git_url=https://${arg_a}.git
-out ${TLS_FOLDER}/CSR/${arg_u}.csr \ else
-subj "/C=${CountryCode}/ST=${City}/L=${City}/O=${arg_u}/OU=/CN=${arg_u}/emailAddress=${EmailAddress}" warning "REPO specified, using argument as app name"
git_url=https://${APP_REPO_URL}/${arg_a}.git
info "Here is your CSR, paste it in your Certificate authority interface."
echo ""
cat ${TLS_FOLDER}/CSR/${arg_u}.csr
echo ""
info "You should have received a certificate."
info "Please paste your certificate now: (finish with ctrl-d)"
cat > ${TLS_FOLDER}/CSR/${arg_u}.crt
info "Concat certificate, CA and key into pem file."
cat ${TLS_FOLDER}/CSR/${arg_u}.crt /data/indiehosters/certs/sub.class2.server.sha2.ca.pem /data/indiehosters/certs/ca-sha2.pem ${TLS_FOLDER}/CSR/${arg_u}.key > ${TLS_FOLDER}/${arg_u}.pem
}
function configure_dns () {
info "Configuring DNS."
arguments="&Command=namecheap.domains.dns.setHosts\
&DomainName=${arg_u}\
&SLD=$(SLD)\
&TLD=$(TLD)\
&HostName1=@\
&RecordType1=A\
&Address1=${IP}\
&HostName2=www\
&RecordType2=CNAME\
&Address2=${arg_u}\
&HostName3=mail\
&RecordType3=A\
&Address3=${IP}\
&HostName4=@\
&RecordType4=MX\
&Address4=mail.${arg_u}\
&MXPref4=10\
&EmailType=mx"
call_API ${arguments}
}
function start_application () {
case "${arg_a}" in
"static" )
service_file="static"
;;
* )
service_file="lamp"
;;
esac
# Enable and start application (Sorry Systemd)
systemctl enable ${service_file}@${arg_u}
systemctl start ${service_file}@${arg_u}
}
function _fmt () {
local color_ok="\x1b[32m"
local color_bad="\x1b[31m"
local color="${color_bad}"
if [ "${1}" = "debug" ] || [ "${1}" = "info" ] || [ "${1}" = "notice" ]; then
color="${color_ok}"
fi fi
#Define the tag/branch
git clone ${git_url} -b ${arg_t} /data/domains/${arg_u}
cd /data/domains/${arg_u}
if [ -f ./scripts/install ]; then
#domain
export URL=${arg_u}
#admin email
if [ -z "${arg_e}" ]; then
warning "No admin_email specified with -e , using default hoster email"
else
export ADMIN_EMAIL=${arg_e}
debug " admin email is ${ADMIN_EMAIL} "
fi
local color_reset="\x1b[0m" if [ -z ${MAIL_DOMAIN:-} ]; then
if [[ "${TERM}" != "xterm"* ]] || [ -t 1 ]; then warning "you have no email server setup, we'll print a random configuration in your application. Make sure to check the parameters for your app to send proper emails."
# Don't use colors on pipes or non-recognized terminals warning "To stop having this warning, please configure your libre.sh to be abble to create email accounts."
color=""; color_reset="" warning "You can also contact support@indie.host to setup an email account for you"
export MAIL_PASS="randompass"
export MAIL_USER="example@indie.host"
export MAIL_DOMAIN="indie.host"
export MAIL_HOST="mail.indie.host"
export MAIL_PORT="587"
else
echo "using MAIL_DOMAIN from server env"
#export MAIL_PASS=`tr -dc A-Za-z0-9_ < /dev/urandom | head -c 20 | xargs`
#export MAIL_USER="noreply.${arg_u}@${MAIL_DOMAIN}"
#/opt/bin/add_mailbox ${MAIL_USER} ${MAIL_PASS}
fi
./scripts/install
fi fi
echo -e "$(date -u +"%Y-%m-%d %H:%M:%S UTC") ${color}$(printf "[%9s]" ${1})${color_reset}";
} }
function emergency () { echo "$(_fmt emergency) ${@}" 1>&2 || true; exit 1; }
function alert () { [ "${LOG_LEVEL}" -ge 1 ] && echo "$(_fmt alert) ${@}" 1>&2 || true; }
function critical () { [ "${LOG_LEVEL}" -ge 2 ] && echo "$(_fmt critical) ${@}" 1>&2 || true; } function start () {
function error () { [ "${LOG_LEVEL}" -ge 3 ] && echo "$(_fmt error) ${@}" 1>&2 || true; } systemctl start u@${arg_u}
function warning () { [ "${LOG_LEVEL}" -ge 4 ] && echo "$(_fmt warning) ${@}" 1>&2 || true; } systemctl enable u@${arg_u}
function notice () { [ "${LOG_LEVEL}" -ge 5 ] && echo "$(_fmt notice) ${@}" 1>&2 || true; }
function info () { [ "${LOG_LEVEL}" -ge 6 ] && echo "$(_fmt info) ${@}" 1>&2 || true; }
function debug () { [ "${LOG_LEVEL}" -ge 7 ] && echo "$(_fmt debug) ${@}" 1>&2 || true; }
function help () {
echo "" 1>&2
echo " ${@}" 1>&2
echo "" 1>&2
echo " ${usage}" 1>&2
echo "" 1>&2
exit 1
} }
### Parse commandline options ### Parse commandline options
...@@ -376,10 +226,10 @@ fi ...@@ -376,10 +226,10 @@ fi
### Validation (decide what's required for running your script and error out) ### Validation (decide what's required for running your script and error out)
##################################################################### #####################################################################
[ -z "${arg_a}" ] && help "Application is required."
[ -z "${arg_e}" ] && help "Email is required."
[ -z "${arg_u}" ] && help "URL is required." [ -z "${arg_u}" ] && help "URL is required."
[ -z "${LOG_LEVEL}" ] && emergency "Cannot continue without LOG_LEVEL." [ -z "${LOG_LEVEL}" ] && emergency "Cannot continue without LOG_LEVEL."
# tags/branch for modules
[ -z "${arg_t}" ] && arg_t=master
### Runtime ### Runtime
...@@ -387,7 +237,7 @@ fi ...@@ -387,7 +237,7 @@ fi
# Exit on error. Append ||true if you expect an error. # Exit on error. Append ||true if you expect an error.
# set -e is safer than #!/bin/bash -e because that is neutralised if # set -e is safer than #!/bin/bash -e because that is neutralised if
# someone runs your script like `bash yourscript.sh` # someone runs your script like `bash yourscript`
set -o errexit set -o errexit
set -o nounset set -o nounset
...@@ -395,20 +245,13 @@ set -o nounset ...@@ -395,20 +245,13 @@ set -o nounset
# This way you can catch the error in case mysqldump fails in `mysqldump |gzip` # This way you can catch the error in case mysqldump fails in `mysqldump |gzip`
set -o pipefail set -o pipefail
if [[ "${OSTYPE}" == "darwin"* ]]; then
info "You are on OSX"
fi
FOLDER=/data/domains/${arg_u} FOLDER=/data/domains/${arg_u}
APP_FOLDER=${FOLDER}/${arg_a}
TLS_FOLDER=${FOLDER}/TLS TLS_FOLDER=${FOLDER}/TLS
[ ${arg_b} -eq 1 ] && buy_domain_name [ ${arg_b} -eq 1 ] && buy_domain_name
scaffold [ ! -z "${arg_a}" ] && application
[ ${arg_g} -eq 1 ] && generate_certificate [ ${arg_i} -eq 1 ] && provision_dkim
[ ! -z "${arg_f}" ] && provision_certificate
[ ${arg_c} -eq 1 ] && configure_dns [ ${arg_c} -eq 1 ] && configure_dns
[ ${arg_s} -eq 1 ] && start_application [ ${arg_s} -eq 1 ] && start
exit 0 exit 0
#!/bin/bash -eux
domain=$1
ip=$2
cd /data/domains/$domain
./scripts/backup
systemctl stop u@$domain
systemctl disable u@$domain
tar cvzf /home/core/${domain}.tgz .
#!/bin/bash
cd /data/domains
my_ip=`curl http://ipv4.icanhazip.com/`
echo "My IP is $my_ip"
for domain in `ls .`; do
domain_ip=`host ${domain} | awk '/has address/ { print $4 }'`
if [[ "${domain_ip}" != "${my_ip}" ]]; then
echo "$domain has this IP: $domain_ip"
fi
done