Skip to content
# Migration procedure version 0.3
## TLS certificate
### Without a passphrase
The old hoster sends the certificate to the new hoster over a secure channel, as one or more .pem files.
### With a passphrase
The old hoster sends the certificate to the new hoster as one or more .pem files, where the .pem file containing the private key is
encrypted with a passphrase.
The old hoster sends the passphrase over a different and secure medium, in an unrelated message. For instance, if the .pem files were sent via
scp, the passphrase may be sent via PGP-encrypted email.
# Migration procedure version 0.3
## Web application (simplistic procedure)
* The old hoster puts the site in read-only mode by changing the permissions of the database user to read-only
* The old hoster creates the migration archive as per the IndieHosters migration format (see below)
* The old hoster sends the migration archive to the new hoster
* The new hoster imports the migration archive
* Once DNR, DNS, and TLS have also been migrated, the old hoster terminates the service.
## Web application (advanced procedure)
* The TLS certificate is sent ahead first
* The old hoster programmatically creates the migration archive, and immediately *posts* it to the new hoster via a webhook
* The webhook programmatically imports the migration archive, and returns the IP address
* The old hoster programmatically configures the new hoster's public IP address into their load balancer
* The old hoster's load balancer now forwards (at the TCP level) all traffic to this new IP address
* Once DNR and DNS transfer are complete, the old hoster terminates the TCP forwarding service.
## IndieHosters migration format, version 0.3
### General
An IndieHosters migration archive is a directory structure (probably packaged up as a tar file or zip file).
There should be an 'indiehosters.json' file in the root of the archive. It should contain at least the following fields:
* format: the URL of this spec (probably https://indiehosters.net/spec/0.3)
* application: a string, which determines what the rest of the folder contents should be imported into.
### Known
When migrating a Known application, the 'indiehosters.json' file should furthermore contain the following fields:
* application: 'known'
* version: the version of Known as a string, for instance '0.6.5'
* database:
* engine: the database engine used, either 'mysql' or 'mongodb'
* name: the database name inside the dump file, for instance 'known'
* file: the database dump file inside the archive, for instance 'dump.sql'
* uploads: the uploads folder name inside the archive, for instance 'uploads/'
* plugins: the folder with any non-standard plugins for instance 'plugins/'
### WordPress
(to be determined)
[Unit]
Description=Back up data from %i
[Service]
Type=oneshot
TimeoutStartSec=0
EnvironmentFile=/etc/environment
ExecStartPre=/usr/bin/docker run --rm -v /opt/bin:/opt/bin ibuildthecloud/systemd-docker
ExecStartPre=-/usr/bin/docker kill mysqldump-%i
ExecStartPre=-/usr/bin/docker rm mysqldump-%i
ExecStartPre=/bin/bash -euxc ' \
if [ -d /data/domains/%i/mysql ]; then \
echo "Backing up mysql databases for %i"; \
mysql_passwd=`cat /data/domains/%i/mysql/.env | cut -d= -f2`; \
/usr/bin/docker run \
--rm \
--name mysqldump-%i \
--link mysql-%i:db \
--env-file /data/domains/%i/mysql/.env \
pierreozoux/mysql \
mysqldump \
--all-databases \
--events \
-uadmin \
-p$mysql_passwd \
-h db > /data/domains/%i/mysql/dump.sql; \
fi'
ExecStart=/bin/bash -euxc ' \
/bin/docker run \
--rm \
--name duplicity \
-h backup.container \
-v /root:/root \
-v /data/domains/%i:/backup pierreozoux/duplicity \
--encrypt-key ${ENCRYPT_KEY} \
/backup \
sftp://${BACKUP_DESTINATION}/%i'
[Unit]
Description=Hourly backup of www and mysql content to a git repo
# Dependency binding
BindsTo=web@%i.service
[Timer]
OnActiveSec=20
OnUnitActiveSec=60min
[Unit]
Description=%p
# Requirements
Requires=docker.service
Requires=etcd.service
# Dependency ordering
After=docker.service
After=etcd.service
Before=haproxy.service
[Service]
Restart=always
RestartSec=20
TimeoutStartSec=0
ExecStartPre=-/usr/bin/docker kill %p
ExecStartPre=-/usr/bin/docker rm %p
ExecStart=/usr/bin/docker run \
--rm \
--name %p \
-v /data/runtime/haproxy/:/etc/haproxy/ \
-v /var/run/docker.sock:/var/run/docker.sock \
pierreozoux/confd
ExecReload=/usr/bin/docker restart %p
ExecStop=/usr/bin/docker stop %p
[Install]
WantedBy=multi-user.target
[Unit]
Description=%p for %i etcd registration
# Requirements
Requires=etcd.service
# Dependency binding
BindsTo=web@%i.service
[Service]
ExecStart=/bin/bash -c ' \
sleep 30; \
while true; do \
ip=`docker inspect --format \'{{.NetworkSettings.IPAddress}}\' %i`; \
curl -f $ip; \
if [ $? -eq 0 ]; then \
etcdctl set /services/web/%i \'{"ip":"\'$ip\'", "port":"80"}\' --ttl 60; \
else \
etcdctl rm /services/web/%i; \
fi; \
sleep 50; \
done'
ExecStop=-/usr/bin/etcdctl rm /services/web/%i
[Service]
Type=oneshot
Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/bin
ExecStart=/opt/bin/dump_all.sh
[Unit]
Description=Run dump all dayly
[Timer]
OnCalendar=*-*-* 00:15:30
[Install]
WantedBy=timers.target
[Path]
PathExists=/data/runtime/haproxy/haproxy.cfg
[Install]
WantedBy=multi-user.target
[Unit]
Description=%p
# Requirements
Requires=docker.service
# Dependency ordering
After=docker.service
[Service]
Restart=always
RestartSec=20
TimeoutStartSec=0
ExecStartPre=-/usr/bin/docker kill %p
ExecStartPre=-/usr/bin/docker rm %p
ExecStart=/usr/bin/docker run \
--rm \
--name %p \
-v /data/runtime/haproxy:/etc/haproxy \
-p 80:80 \
-p 443:443 \
pierreozoux/haproxy
ExecReload=/usr/bin/docker restart %p
ExecStop=/usr/bin/docker stop %p
[Unit]
Description=%p-%i
# Requirements
Requires=mysql@%i.service
Requires=web@%i.service
# Dependency ordering
After=mysql@%i.service
Before=web@%i.service
# Dependency binding
BindsTo=web@%i.service
[Service]
Type=oneshot
RemainAfterExit=yes
EnvironmentFile=/data/domains/%i/.env
ExecStart=/bin/bash -euxc ' \
application_folder=/data/domains/%i/${APPLICATION}; \
if [ ! -d $application_folder ]; then \
mkdir -p $application_folder; \
touch $application_folder/.htaccess; \
cat /data/domains/%i/mysql/.env | sed s/MYSQL_PASS/DB_PASS/ > $application_folder/.env; \
fi;'
[Install]
WantedBy=multi-user.target
[Service]
Type=oneshot
ExecStart=/libre.sh/utils/mail-mon.sh
[Unit]
Description=Run mail mon hourly and on boot
[Timer]
OnBootSec=15min
OnUnitActiveSec=1h
[Install]
WantedBy=timers.target
[Unit]
Description=%p-%i
# Requirements
Requires=docker.service
# Dependency ordering
After=docker.service
Before=backup@%i.timer
# Dependency binding
BindsTo=lamp@%i.service
[Service]
Restart=always
RestartSec=20
TimeoutStartSec=0
Type=notify
NotifyAccess=all
ExecStartPre=/usr/bin/docker run --rm -v /opt/bin:/opt/bin ibuildthecloud/systemd-docker
ExecStartPre=-/usr/bin/docker kill %p-%i
ExecStartPre=-/usr/bin/docker rm %p-%i
ExecStartPre=/bin/bash -euxc ' \
mysql_folder=/data/domains/%i/mysql; \
if [ ! -d /data/runtime/domains/%i/mysql ]; then \
mkdir -p /data/runtime/domains/%i/mysql/db_files; \
if [ ! -d $mysql_folder ]; then \
mkdir -p $mysql_folder; \
pass=`echo $RANDOM ${date} | md5sum | base64 | cut -c-10`; \
echo MYSQL_PASS=$pass > $mysql_folder/.env; \
fi; \
fi'
ExecStart=/opt/bin/systemd-docker run \
--rm \
--name %p-%i \
-v /data/runtime/domains/%i/%p/db_files:/var/lib/mysql \
--env-file /data/domains/%i/%p/.env \
pierreozoux/mysql
ExecReload=/usr/bin/docker restart %p-%i
ExecStop=/usr/bin/docker stop %p-%i
[Unit]
Description=%p
Description=%p-%i
# Requirements
Requires=docker.service
......@@ -9,19 +9,15 @@ After=docker.service
[Service]
Restart=always
RestartSec=20
TimeoutStartSec=0
ExecStartPre=-/usr/bin/docker kill %p
ExecStartPre=-/usr/bin/docker rm %p
ExecStart=/usr/bin/docker run \
--rm \
--name %p \
-v /data/runtime/postfix/:/data \
-p 25:25 \
pierreozoux/email-forwarder
ExecReload=/usr/bin/docker restart %p
ExecStop=/usr/bin/docker stop %p
RestartSec=10
TimeoutStartSec=60
TimeoutStopSec=15
EnvironmentFile=-/system/%i/env
Environment=HOSTNAME=%H
WorkingDirectory=/system/%i/
ExecStartPre=-docker-compose rm -f
ExecStart=/bin/bash -euxc "docker-compose up"
ExecStop=docker-compose stop
[Install]
WantedBy=multi-user.target
[Unit]
Description=%p-%i
# Requirements
Requires=web@%i.service
# Dependency ordering
Before=web@%i.service
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/bash -euxc ' \
application_folder=/data/domains/%i/%p/www-content; \
if [ ! -d $application_folder ]; then \
mkdir -p $application_folder; \
echo Hello %i > $application_folder/index.html; \
fi'
[Install]
WantedBy=multi-user.target
[Unit]
Description=Turn on swap
[Service]
Type=oneshot
RemainAfterExit=true
ExecStartPre=-/bin/bash -euxc ' \
fallocate -l 8192m /swap &&\
chmod 600 /swap &&\
mkswap /swap'
ExecStart=/sbin/swapon /swap
ExecStop=/sbin/swapoff /swap
[Install]
WantedBy=local.target
[Unit]
Description=%p-%i
# Requirements
Requires=docker.service
# Dependency ordering
After=docker.service
[Service]
Restart=always
RestartSec=10
TimeoutStartSec=60
TimeoutStopSec=15
EnvironmentFile=-/data/domains/%i/env
Environment=HOSTNAME=%H
WorkingDirectory=/data/domains/%i/
ExecStartPre=-docker-compose rm -f
ExecStart=/bin/bash -euxc "LETSENCRYPT_HOST=%i VIRTUAL_HOST=%i,www.%i docker-compose up"
ExecStop=docker-compose stop
[Install]
WantedBy=multi-user.target
[Unit]
Description=Create lb_web network
Requires=docker.service
After=docker.service
[Service]
Type=oneshot
RemainAfterExit=true
ExecStart=/usr/bin/docker network create lb_web
ExecStop=/usr/bin/docker network rm lb_web
[Install]
WantedBy=local.target
[Unit]
Description=%p-%i
# Requirements
Requires=docker.service
Requires=discovery@%i.service
Requires=backup@%i.timer
# Dependency ordering
After=docker.service
Before=discovery@%i.service
Before=backup@%i.timer
[Service]
Restart=always
RestartSec=20
TimeoutStartSec=0
Type=notify
NotifyAccess=all
EnvironmentFile=/data/domains/%i/.env
Environment=URL=%i
ExecStartPre=/usr/bin/docker run --rm -v /opt/bin:/opt/bin ibuildthecloud/systemd-docker
ExecStartPre=-/usr/bin/docker kill %i
ExecStartPre=-/usr/bin/docker rm %i
ExecStartPre=/bin/cp /data/domains/%i/TLS/%i.pem /data/runtime/haproxy/approved-certs/%i.pem
ExecStart=/bin/bash -euxc ' \
/opt/bin/systemd-docker --env run \
--rm \
--name %i \
${DOCKER_ARGUMENTS} \
pierreozoux/${APPLICATION}'
ExecReload=/usr/bin/docker restart %i
ExecStop=/usr/bin/docker stop %i