Populate docker-cartodb with required files.

Here I commit all the required files for a development cartodb container
to be successfully build and run with Docker.
This commit is contained in:
fleu 2014-11-15 16:32:19 +01:00
parent b5854192fe
commit 21cc7911c1
10 changed files with 719 additions and 1 deletions

109
Dockerfile Normal file
View File

@ -0,0 +1,109 @@
#
# Cartodb container
#
FROM ubuntu:14.04
MAINTAINER Adrien Fleury <fleu42@gmail.com>
# Configuring locales
RUN dpkg-reconfigure locales && \
locale-gen en_US.UTF-8 && \
update-locale LANG=en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8
# Preparing apt
RUN apt-get update && \
useradd -m -d /home/cartodb -s /bin/bash cartodb && \
apt-get install -y -q software-properties-common && \
add-apt-repository -y ppa:chris-lea/node.js && \
apt-get update
# Installing stuff
RUN apt-get install -y -q build-essential checkinstall unp zip libgeos-c1 \
libgeos-dev libjson0 python-simplejson libjson0-dev proj-bin \
proj-data libproj-dev postgresql-9.3 postgresql-client-9.3 \
postgresql-contrib-9.3 postgresql-server-dev-9.3 \
postgresql-plpython-9.3 gdal-bin libgdal1-dev nodejs \
redis-server python2.7-dev build-essential python-setuptools \
varnish imagemagick git postgresql-9.3-postgis-2.1 libmapnik-dev \
python-mapnik mapnik-utils postgresql-9.3-postgis-2.1-scripts postgis \
python-argparse python-gdal python-chardet openssl libreadline6 curl \
git-core zlib1g zlib1g-dev libssl-dev libyaml-dev \
libsqlite3-dev sqlite3 libxml2-dev libxslt-dev autoconf \
libc6-dev ncurses-dev automake libtool bison subversion \
pkg-config libpq5 libpq-dev libcurl4-gnutls-dev libffi-dev \
libgdbm-dev gnupg libreadline6-dev
# Setting PostgreSQL
RUN sed -i 's/\(peer\|md5\)/trust/' /etc/postgresql/9.3/main/pg_hba.conf
# Install schema_triggers
RUN git clone https://github.com/CartoDB/pg_schema_triggers.git && \
cd pg_schema_triggers && \
make all install && \
sed -i \
"/#shared_preload/a shared_preload_libraries = 'schema_triggers.so'" \
/etc/postgresql/9.3/main/postgresql.conf
ADD ./template_postgis.sh /tmp/template_postgis.sh
RUN service postgresql start && /bin/su postgres -c \
/tmp/template_postgis.sh && service postgresql stop
# Install cartodb extension
RUN git clone https://github.com/CartoDB/cartodb-postgresql && \
cd cartodb-postgresql && \
PGUSER=postgres make install
ADD ./cartodb_pgsql.sh /tmp/cartodb_pgsql.sh
RUN service postgresql start && /bin/su postgres -c \
/tmp/cartodb_pgsql.sh && service postgresql stop
# Install CartoDB API
RUN git clone git://github.com/CartoDB/CartoDB-SQL-API.git && \
cd CartoDB-SQL-API && ./configure && npm install
ADD ./config/CartoDB-dev.js \
/CartoDB-SQL-API/config/environments/development.js
# Install Windshaft
RUN git clone git://github.com/CartoDB/Windshaft-cartodb.git && \
cd Windshaft-cartodb && ./configure && npm install && mkdir logs
ADD ./config/WS-dev.js \
/Windshaft-cartodb/config/environments/development.js
# Install rvm
RUN gpg --keyserver hkp://keys.gnupg.net --recv-keys D39DC0E3
RUN curl -L https://get.rvm.io | bash -s stable --ruby
RUN echo 'source /usr/local/rvm/scripts/rvm' >> /etc/bash.bashrc
RUN /bin/bash -l -c rvm requirements
ENV PATH /usr/local/rvm/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
RUN /bin/bash -l -c 'rvm install 1.9.3'
RUN /bin/bash -l -c 'rvm use 1.9.3 --default'
RUN /bin/bash -l -c 'gem install bundle archive-tar-minitar'
# Install bundler
RUN /bin/bash -l -c 'gem install bundler --no-doc --no-ri'
# Install CartoDB (with the bug correction on bundle install)
RUN git clone git://github.com/CartoDB/cartodb.git && \
cd cartodb && /bin/bash -l -c 'bundle install' || \
/bin/bash -l -c "cd $(/bin/bash -l -c 'gem contents \
debugger-ruby_core_source' | grep CHANGELOG | sed -e \
's,CHANGELOG.md,,') && /bin/bash -l -c 'rake add_source \
VERSION=$(/bin/bash -l -c 'ruby --version' | awk \
'{print $2}' | sed -e 's,p55,-p55,' )' && cd /cartodb && \
/bin/bash -l -c 'bundle install'"
# Copy confs
ADD ./config/app_config.yml /cartodb/config/app_config.yml
ADD ./config/database.yml /cartodb/config/database.yml
ADD ./create_dev_user /cartodb/script/create_dev_user
ENV PATH /usr/local/rvm/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
RUN service postgresql start && service redis-server start && \
bash -l -c "cd /cartodb && bash script/create_dev_user" && \
service postgresql stop && service redis-server stop
EXPOSE 3000
ADD ./startup.sh /opt/startup.sh
CMD ["/bin/bash", "/opt/startup.sh"]

View File

@ -1,4 +1,28 @@
docker-cartodb
==============
A try to dockerize cartodb
That container provides a fully working cartodb development solution
without the installation hassle.
Just run and connect to locahost:3000 into you browser.
The default login is dev/pass. You may want to change it when you'll run
it for the outside.
How to build the container:
--------------
```
git clone https://github.com/fleu42/docker-cartodb.git
docker build -t="fleu42/docker-cartodb" docker-cartodb/
```
How to run the container:
--------------
```
docker run -t -i fleu42/docker-cartodb -p 3000:3000 /bin/bash
```
You might need to add dev.localhost to your hosts file.

7
cartodb_pgsql.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
#
# Init script to success tests.
#
psql -c "CREATE EXTENSION postgis;"
psql -c "CREATE EXTENSION cartodb;"

61
config/CartoDB-dev.js Normal file
View File

@ -0,0 +1,61 @@
module.exports.base_url = '/api/:version';
// If useProfiler is true every response will be served with an
// X-SQLAPI-Profile header containing elapsed timing for various
// steps taken for producing the response.
module.exports.useProfiler = true;
module.exports.log_format = '[:date] :req[X-Real-IP] :method :req[Host]:url :status :response-time ms -> :res[Content-Type] (:res[X-SQLAPI-Profiler])';
// If log_filename is given logs will be written there, in append mode. Otherwise stdout is used (default).
// Log file will be re-opened on receiving the HUP signal
module.exports.log_filename = 'logs/cartodb-sql-api.log';
// Regular expression pattern to extract username
// from hostname. Must have a single grabbing block.
module.exports.user_from_host = '^([^\\.]+)\\.';
module.exports.node_port = 8080;
module.exports.node_host = '127.0.0.1';
// idle socket timeout, in miliseconds
module.exports.node_socket_timeout = 600000;
module.exports.environment = 'development';
module.exports.db_base_name = 'cartodb_dev_user_<%= user_id %>_db';
// Supported labels: 'user_id' (read from redis)
module.exports.db_user = 'development_cartodb_user_<%= user_id %>';
// Supported labels: 'user_id', 'user_password' (both read from redis)
module.exports.db_user_pass = '<%= user_password %>'
// Name of the anonymous PostgreSQL user
module.exports.db_pubuser = 'publicuser';
// Password for the anonymous PostgreSQL user
module.exports.db_pubuser_pass = 'public';
module.exports.db_host = 'localhost';
module.exports.db_port = '5432';
// Max database connections in the pool
// Subsequent connections will wait for a free slot.
// NOTE: not used by OGR-mediated accesses
module.exports.db_pool_size = 500;
// Milliseconds before a connection is removed from pool
module.exports.db_pool_idleTimeout = 30000;
// Milliseconds between idle client checking
module.exports.db_pool_reapInterval = 1000;
module.exports.redis_host = '127.0.0.1';
module.exports.redis_port = 6379;
module.exports.redisPool = 50;
module.exports.redisIdleTimeoutMillis = 100;
module.exports.redisReapIntervalMillis = 10;
module.exports.redisLog = false;
// Max number of entries in the query tables cache
module.exports.tableCacheMax = 8192;
// Max age of query table cache items, in milliseconds
module.exports.tableCacheMaxAge = 1000*60*10;
// Temporary directory, make sure it is writable by server user
module.exports.tmpDir = '/tmp';
// Optional statsd support
module.exports.statsd = {
host: 'localhost',
port: 8125,
prefix: 'dev.:host.',
cacheDns: true
// support all allowed node-statsd options
};
module.exports.health = {
enabled: true,
username: 'development',
query: 'select 1'
};

156
config/WS-dev.js Normal file
View File

@ -0,0 +1,156 @@
var config = {
environment: 'development'
,port: 8181
,host: '127.0.0.1'
,uv_threadpool_size: undefined
// Regular expression pattern to extract username
// from hostname. Must have a single grabbing block.
,user_from_host: '^([^\\.]+)\\.'
// Base URLs for the APIs
//
// See http://github.com/CartoDB/Windshaft-cartodb/wiki/Unified-Map-API
//
// Base url for the Templated Maps API
// "/api/v1/map/named" is the new API,
// "/tiles/template" is for compatibility with versions up to 1.6.x
,base_url_templated: '(?:/api/v1/map/named|/tiles/template)'
// Base url for the Detached Maps API
// "maps" is the the new API,
// "tiles/layergroup" is for compatibility with versions up to 1.6.x
,base_url_detached: '(?:/api/v1/map|/tiles/layergroup)'
// Base url for the Inline Maps and Table Maps API
,base_url_legacy: '/tiles/:table'
// Maximum number of connections for one process
// 128 is a good value with a limit of 1024 open file descriptors
,maxConnections:128
// Maximum number of templates per user. Unlimited by default.
,maxUserTemplates:1024
// Seconds since "last creation" before a detached
// or template instance map expires. Or: how long do you want
// to be able to navigate the map without a reload ?
// Defaults to 7200 (2 hours)
,mapConfigTTL: 7200
// idle socket timeout, in milliseconds
,socket_timeout: 600000
,enable_cors: true
,cache_enabled: false
,log_format: ':req[X-Real-IP] :method :req[Host]:url :status :response-time ms -> :res[Content-Type] (:res[X-Tiler-Profiler])'
// If log_filename is given logs will be written
// there, in append mode. Otherwise stdout is used (default).
// Log file will be re-opened on receiving the HUP signal
,log_filename: 'logs/node-windshaft.log'
// Templated database username for authorized user
// Supported labels: 'user_id' (read from redis)
,postgres_auth_user: 'development_cartodb_user_<%= user_id %>'
// Templated database password for authorized user
// Supported labels: 'user_id', 'user_password' (both read from redis)
,postgres_auth_pass: '<%= user_password %>'
,postgres: {
// Parameters to pass to datasource plugin of mapnik
// See http://github.com/mapnik/mapnik/wiki/PostGIS
type: "postgis",
user: "publicuser",
password: "public",
host: '127.0.0.1',
port: 5432,
extent: "-20037508.3,-20037508.3,20037508.3,20037508.3",
/* experimental
geometry_field: "the_geom",
extent: "-180,-90,180,90",
srid: 4326,
*/
row_limit: 65535,
simplify_geometries: true,
use_overviews: true, // use overviews to retrieve raster
/*
* Set persist_connection to false if you want
* database connections to be closed on renderer
* expiration (1 minute after last use).
* Setting to true (the default) would never
* close any connection for the server's lifetime
*/
persist_connection: false,
max_size: 500
}
,mapnik_version: undefined
,mapnik_tile_format: 'png8:m=h'
,statsd: {
host: 'localhost',
port: 8125,
prefix: 'dev.',
cacheDns: true
// support all allowed node-statsd options
}
,renderer: {
// Milliseconds since last access before renderer cache item expires
cache_ttl: 60000,
metatile: 4,
bufferSize: 64,
statsInterval: 5000 // milliseconds between each report to statsd about number of renderers and mapnik pool status
}
,millstone: {
// Needs to be writable by server user
cache_basedir: '/tmp/cdb-tiler-dev/millstone-dev'
}
,redis: {
host: '127.0.0.1',
port: 6379,
// Max number of connections in each pool.
// Users will be put on a queue when the limit is hit.
// Set to maxConnection to have no possible queues.
// There are currently 2 pools involved in serving
// windshaft-cartodb requests so multiply this number
// by 2 to know how many possible connections will be
// kept open by the server. The default is 50.
max: 50,
returnToHead: true, // defines the behaviour of the pool: false => queue, true => stack
idleTimeoutMillis: 1, // idle time before dropping connection
reapIntervalMillis: 1, // time between cleanups
slowQueries: {
log: true,
elapsedThreshold: 200
},
slowPool: {
log: true, // whether a slow acquire must be logged or not
elapsedThreshold: 25 // the threshold to determine an slow acquire must be reported or not
},
emitter: {
statusInterval: 5000 // time, in ms, between each status report is emitted from the pool, status is sent to statsd
}
}
,sqlapi: {
protocol: 'http',
// If "host" is given, it will be used
// to connect to the SQL-API without a
// DNS lookup
host: '127.0.0.1',
port: 8080,
// The "domain" part will be appended to
// the cartodb username and passed to
// SQL-API requests in the Host HTTP header
domain: 'localhost',
version: 'v1',
// Maximum lenght of SQL query for GET
// requests. Longer queries will be sent
// using POST. Defaults to 2048
max_get_sql_length: 2048,
// Maximum time to wait for a response,
// in milliseconds. Defaults to 100.
timeout: 100
}
,varnish: {
host: 'localhost',
port: 6082,
secret: 'xxx',
ttl: 86400,
layergroupTtl: 86400 // the max-age for cache-control header in layergroup responses
}
// If useProfiler is true every response will be served with an
// X-Tiler-Profile header containing elapsed timing for various
// steps taken for producing the response.
,useProfiler:true
};
module.exports = config;

255
config/app_config.yml Normal file
View File

@ -0,0 +1,255 @@
defaults: &defaults
ogr2ogr:
binary: 'which ogr2ogr'
csv_guessing: false
debug_assets: true
mandatory_keys: [layer_opts, sql_api, varnish_management, redis, session_domain]
session_domain: '.localhost'
secret_token: '71c2b25921b84a1cb21c71503ab8fb23'
account_host: 'localhost:3000'
account_path: '/account'
watcher:
ttl: 60
tiler:
internal:
protocol: 'http'
domain: 'localhost'
port: '8181'
host: '127.0.0.1'
verifycert: false
private:
protocol: 'http'
domain: 'localhost'
port: '8181'
verifycert: false
public:
protocol: 'http'
domain: 'localhost'
port: '8181'
verifycert: false
sql_api:
private:
protocol: 'http'
domain: 'localhost'
endpoint: '/api/v1/sql'
port: 8080
public:
protocol: 'http'
domain: 'localhost'
endpoint: '/api/v1/sql'
port: 8080
api_requests_service_url: ''
developers_host: 'http://developers.localhost:3000'
google_analytics:
primary: ''
embeds: ''
domain: ''
rollbar:
token: ''
tumblr:
api_key: ''
trackjs:
customer: ''
common_data:
protocol: 'https'
username: ''
host: ''
api_key: ''
format: 'shp'
generate_every: 86400
s3_bucket_name: ''
common_data:
username: ''
api_key: ''
varnish_management:
critical: false
host: '127.0.0.1'
port: 6082
purge_command: 'purge'
retries: 5
timeout: 5
redis:
host: '127.0.0.1'
port: 6379
timeout: 20
databases:
tables_metadata: 0
api_credentials: 3
users_metadata: 5
redis_migrator_logs: 6
# mixpanel:
# api_key: xxx
# api_secret: xxx
# token: xxx
# github:
# repo: xxx
# org: xxx
# auth: xxx:xxx
superadmin:
username: "superadmin"
password: "monkey"
geocoder:
app_id: ''
token: ''
mailto: ''
base_url: ''
non_batch_base_url: ''
internal:
username: ''
api_key: ''
cache:
base_url: ''
api_key: ''
table_name: ''
importer:
s3:
access_key_id:
secret_access_key:
bucket_name:
url_ttl:
error_track:
url: 'https://viz2.cartodb.com/api/v1/sql'
percent_users: 10
# graphite endpoint used to post frontend stats
graphite_public:
host: ""
port:
layer_opts:
public_opts: ["type", "active", "query", "opacity", "auto_bound",
"interactivity", "debug", "visible", "tiler_domain",
"tiler_port", "tiler_protocol", "sql_domain", "sql_port",
"sql_protocol", "extra_params", "cdn_url", "table_name",
"user_name", "style_version", "tile_style", "query_wrapper"]
default_tile_styles:
point: "{\n marker-fill: #FF6600;\n marker-opacity: 0.9;\n marker-width: 12;\n marker-line-color: white;\n marker-line-width: 3;\n marker-line-opacity: 0.9;\n marker-placement: point;\n marker-type: ellipse;\n marker-allow-overlap: true;\n}"
geometry: "{\n // points\n [mapnik-geometry-type=point] {\n    marker-fill: #FF6600;\n    marker-opacity: 1;\n    marker-width: 12;\n    marker-line-color: white;\n    marker-line-width: 3;\n    marker-line-opacity: 0.9;\n    marker-placement: point;\n    marker-type: ellipse;marker-allow-overlap: true;\n  }\n\n //lines\n [mapnik-geometry-type=linestring] {\n    line-color: #FF6600; \n    line-width: 2; \n    line-opacity: 0.7;\n  }\n\n //polygons\n [mapnik-geometry-type=polygon] {\n    polygon-fill:#FF6600;\n    polygon-opacity: 0.7;\n    line-opacity:1;\n    line-color: #FFFFFF;\n   }\n }"
polygon: "{\n polygon-fill:#FF6600;\n polygon-opacity: 0.7;\n line-opacity:1;\n line-color: #FFFFFF;\n}"
multipolygon: "{\n polygon-fill:#FF6600;\n polygon-opacity: 0.7;\n line-opacity:1;\n line-color: #FFFFFF;\n}"
multilinestring: "{\n line-color:#FF6600;\n line-width:1;\n line-opacity: 0.7;\n}"
data:
kind: "carto"
options:
query: ""
opacity: 0.99
auto_bound: false
interactivity: "cartodb_id"
debug: false
visible: true
tiler_domain: "localhost"
tiler_port: "8181"
tiler_protocol: "http"
sql_domain: "localhost"
sql_port: "8080"
sql_protocol: "http"
extra_params: { cache_policy: 'persist' }
cdn_url: ""
tile_style_history: []
style_version: "2.1.1"
infowindow:
template_name: "table/views/infowindow_light"
background:
kind: "background"
options: { color: '#ffffff' }
base:
kind: "tiled"
options:
visible: true
type: "Tiled"
urlTemplate: "https://maps.nlp.nokia.com/maptiler/v2/maptile/newest/normal.day/{z}/{x}/{y}/256/png8?lg=eng&token=A7tBPacePg9Mj_zghvKt9Q&app_id=KuYppsdXZznpffJsKT24"
name: 'Nokia Day'
className: "nokia_day"
attribution: "©2012 Nokia <a href='http://here.net/services/terms' target='_blank'>Terms of use</a>"
gmaps:
kind: "gmapsbase"
options:
base_type: "roadmap"
style: ""
cartodb_com_hosted: true
cartodb_central_domain_name: 'cartodb.com'
aws:
s3:
access_key_id: "test"
secret_access_key: "test"
assets:
s3_bucket_name: "tests"
max_file_size: 5242880 # 5.megabytes
app_assets:
asset_host: "//cartodb-libs.global.ssl.fastly.net/cartodbui"
avatars:
base_url: 'example.com/avatars'
kinds: ['stars', 'mountains']
colors: ['red', 'blue']
dropbox_api_key: ""
gdrive:
api_key: ""
app_id: ""
enforce_non_empty_layer_css: true
oauth:
# If the client_id/app_key is not set won't appear at the UI. @see application_helper.rb -> frontend_config
# Must be the same as CartoDB::Datasources::xxxx DATASOURCE_NAME constants
gdrive:
application_name: ''
client_id: ''
client_secret: ''
callback_url: 'http://cartodb.com'
dropbox:
app_key: ''
app_secret: ''
callback_url: ''
instagram:
app_key: ''
app_secret: ''
callback_url: ''
datasource_search:
# Must be the same as CartoDB::Datasources::xxxx DATASOURCE_NAME constants
twitter_search:
standard:
auth_required: false
username: ''
password: ''
search_url: 'http://fake.url.nil'
ratelimit_active: true
ratelimit_concurrency: 8
ratelimit_ttl: 4
ratelimit_wait_secs: 0.5
customized_user_list: []
customized_orgs_list: []
entity_to_config_map: [] # { user_or_org_name: 'custom_config_name'}
customized:
custom1:
auth_required: false
username: ''
password: ''
search_url: 'http://fake.url.nil'
ratelimit_active: false
ratelimit_concurrency: 3
ratelimit_ttl: 4
ratelimit_wait_secs: 0.1
development:
<<: *defaults
varnish_management:
critical: false
host: '127.0.0.1'
port: 6082
purge_command: 'purge'
url_purge_command: 'url.purge'
retries: 5
timeout: 5
enforce_non_empty_layer_css: false
test:
<<: *defaults
redis:
host: '127.0.0.1'
port: 6335
enforce_non_empty_layer_css: false
api_requests_es_service:
url: "http://api-calls-service.localhost/search"
body: ""
staging:
<<: *defaults
production:
<<: *defaults

40
config/database.yml Normal file
View File

@ -0,0 +1,40 @@
production:
adapter: postgres
encoding: unicode
host: localhost
port: 5432
database: carto_db_production
username: postgres
password:
conn_validator_timeout: 900
staging:
adapter: postgres
encoding: unicode
host: localhost
port: 5432
database: carto_db_staging
username: postgres
password:
conn_validator_timeout: 900
development:
adapter: postgres
encoding: unicode
host: localhost
port: 5432
database: carto_db_development
username: postgres
password:
conn_validator_timeout: 900
test:
adapter: postgres
encoding: unicode
database: carto_db_test
host: localhost
port: 5432
username: postgres
password:
conn_validator_timeout: -1

32
create_dev_user Executable file
View File

@ -0,0 +1,32 @@
#!/bin/sh
SUBDOMAIN="dev"
PASSWORD="pass"
ADMIN_PASSWORD="pass"
EMAIL="dev@example.com"
echo "--- Creating databases"
bundle exec rake cartodb:db:setup --trace SUBDOMAIN="${SUBDOMAIN}" \
PASSWORD="${PASSWORD}" ADMIN_PASSWORD="${ADMIN_PASSWORD}" \
EMAIL="${EMAIL}"
if test $? -ne 0; then exit 1; fi
# # Update your quota to 100GB
echo "--- Updating quota to 100GB"
bundle exec rake cartodb:db:set_user_quota["${SUBDOMAIN}",102400]
if test $? -ne 0; then exit 1; fi
# # Allow unlimited tables to be created
echo "--- Allowing unlimited tables creation"
bundle exec rake cartodb:db:set_unlimited_table_quota["${SUBDOMAIN}"]
if test $? -ne 0; then exit 1; fi
# # Allow user to create private tables in addition to public
echo "--- Allowing private tables creation"
bundle exec rake cartodb:db:set_user_private_tables_enabled["${SUBDOMAIN}",'true']
if test $? -ne 0; then exit 1; fi
# # Set the account type
echo "--- Setting cartodb account type"
bundle exec rake cartodb:db:set_user_account_type["${SUBDOMAIN}",'[DEDICATED]']
if test $? -ne 0; then exit 1; fi

18
startup.sh Executable file
View File

@ -0,0 +1,18 @@
#!/bin/bash
PORT=3000
service postgresql start
service redis-server start
service varnish start
cd /Windshaft-cartodb
node app.js development &
cd /CartoDB-SQL-API
node app.js development &
cd /cartodb
bundle exec script/resque &
bundle exec rails s -p $PORT

16
template_postgis.sh Executable file
View File

@ -0,0 +1,16 @@
#!/bin/bash
#
# Init script for template postgis
#
POSTGIS_SQL_PATH=`pg_config --sharedir`/contrib/postgis-2.1.2;
createdb -E UTF8 template_postgis;
createlang -d template_postgis plpgsql;
psql -d postgres -c "UPDATE pg_database SET datistemplate='true' \
WHERE datname='template_postgis'"
psql -d template_postgis -c "CREATE EXTENSION postgis;"
psql -d template_postgis -c "CREATE EXTENSION postgis_topology;"
psql -d template_postgis -c "GRANT ALL ON geometry_columns TO PUBLIC;"
psql -d template_postgis -c "GRANT ALL ON spatial_ref_sys TO PUBLIC;"
psql -c "CREATE EXTENSION plpythonu;"
psql -c "CREATE EXTENSION schema_triggers;"