Skip to content


Subversion checkout URL

You can clone with
Download ZIP
Interface for managing extensions on PGXN
Perl PLpgSQL Perl6 CSS Shell Ruby Other

PGXN/Manager version 0.16.1

This application provides a Web interface and REST API for extension owners to upload and manage extensions on PGXN, the PostgreSQL Extension Network. It also provides an administrative interface for PGXN administrators. For more information, visit the PGXN site. For a working deployment, hit PGXN Manager.


  • First, you need to satisfy the dependencies. These include:

    • Perl 5.10.0 or higher (5.12 or higher strongly recommended)
    • PostgreSQL 9.0.0 or higher with support for PL/Perl included.
  • Next, you'll need to install all CPAN dependencies. To determine what they are, simply run

      perl Build.PL

    To install them, run

      ./Build installdeps
  • Configure the PostgreSQL server to preload modules used by PL/Perl functions. Just add these lines to the end of your postgresql.conf file:

      custom_variable_classes = 'plperl'
      plperl.use_strict = on
      plperl.on_init='use 5.12.0; use JSON::XS; use Email::Valid; use Data::Validate::URI; use SemVer; use PGXN::Meta::Validator;'

    If you would also like those modules to load in the parent PostgreSQL process, rather than for each connection, add:

     shared_preload_libraries = '$libdir/plperl'
  • Install these PostgreSQL core extensions:

    If you installed from source, you can either install all the core extensions, like so:

      cd contrib/
      gmake install

    Or if you like, you can install individual extensions like so:

      cd contrib
      for ext in citext hstore pgcrypto
          cd citext
          gmake install
          cd ..
  • Install the PostreSQL semver extension v0.3.0 or higher. It's available from PGXN itself. Grab the latest release and follow its installation instructions.

  • Create a "pgxn" system user and the master mirror directory:

    useradd pgxn -d /nonexistent
    mkdir -p /var/www/
    chown -R pgxn:pgxn /var/www/

    The "pgxn" user should not have any system access. You should also configure your Web server to serve this directory. For proper networking, it should also be copy-able via anonymous rsync connections.

  • Create the configuration file. The easiest way is to copy one of the templates:

    cp conf/local.json conf/prod.json

    Change the DSN if you'd like to use a different database name or connect to another host. (Consult the DBI and DBD::Pg documentation for details on the attributes that can be included in the DSN). You can also change the templates for the files that will be managed on the master mirror.

  • If you're using PostgreSQL 9.0, you'll need to load the extensions into the template database so that they'll be included in the PGXN database when it's created. (This isn't necessary for PostgreSQL 9.1, as the installer will load the extensions for you). The simplest way to do so is to create a "contrib" schema and put them there. You'll also need to create the "pgxn" user and give it access to the schema.

      psql -U postgres -d template1 -c 'CREATE SCHEMA contrib;'
      psql -U postgres -d template1 -c 'CREATE USER pgxn;'
      psql -U postgres -d template1 -c 'GRANT USAGE ON SCHEMA contrib TO pgxn;'

    Then use the $PGOPTIONS environment variable to load the extensions into that schema:

      for ext in citext hstore pgcrypto semver
          PGOPTIONS=--search_path=contrib psql -d template1 \
            -f /path/to/pgsql/share/contrib/$ext.sql
  • Build PGXN::Manager:

    perl Build.PL --db_super_user postgres \
                  --db_client /path/to/pgsql/bin/psql \
                  --context local
    ./Build db

    If you're on PostgreSQL 9.0 and have installed the extensions into the "contrib" schema, you'll need to set $PGOPTIONS for ./Build db:

      PGOPTIONS=--search_path=public,contrib ./Build db
  • Once the database has been built, if you're running PostgreSQL 9.1, you can drop the "contrib" schema from the template database:

      psql -U postgres -d template1 -c 'DROP SCHEMA contrib CASCADE;'

    You'll also need to make sure that the "contrib" schema is in the search path of your new database (so you don't have to use the $PGOPTIONS environment variable anymore):

      psql -U postgres -c 'ALTER DATABASE pgxn_manager SET search_path = "$user",public,contrib;'
  • If you'd like to run the test suite, you'll need to install pgTAP from pgTAP. Download it and install it like so:

      gmake install

    Then repeat the steps above but use the "test" context, specified by the call to Build.PL like so:

      perl Build.PL --db_super_user postgres \
                    --db_client /path/to/pgsql/bin/psql \
                    --context test

    If you're on 9.0, you'll need to load pgTAP into the database; I recommend putting it into the "contrib" schema along with the other extensions:

      PGOPTIONS=--search_path=contrib psql -U postgres-d pgxn_manager_test \
        -f /path/to/pgsql/share/contrib/pgtap.sql

    Next, edit the DSN in conf/test.json so that it will connect to the test database. Then run the tests, which will need to be able to find psql in the system path:

      ./Build test

    You can then drop the test database if you like:

      dropdb -U postgres pgxn_manager_test
  • Fire up the app:

    sudo -u pgxn plackup -E prod bin/pgxn_manager.psgi
  • Connect to port 5000 on your host and you should see the UI!

  • Now you need to make yourself an administrator. Click the "Request Account" link and request an account.

  • Now connect to the database:

    /usr/local/pgsql/bin/psql -U postgres pgxn_manager

    And approve your account, making youself an admin while you're at it. Also, set your password to an empty string. Assuming you gave yourself the nickname "fred", the query is:

      UPDATE users
         SET status   = 'active',
             is_admin = true,
             set_by   = 'fred',
             password = ''
       WHERE nickname = 'fred';
  • Then give yourself a proper password by executing the change_password() function. Make sure the third argument is your great new password:

    SELECT change_password('fred', '', 'changme!');
  • Hit the "Log In" link and log yourself in.

  • Profit!

Running a Proxy Server

PGXN::Manager is actually two apps in one. The public site runs under /pub/ and the site for users authenticated via Basic Auth runs under /auth/. A nice way to separate these is to set up two reverse proxy servers: One to serve /pub/ on port 80 and one to serve /auth/ on port 443. Here's how to do that.

  • Get or create an SSL certificate and install it in your system.

  • Create the reverse proxy hosts. Here's what the mod_proxy configuration for looks like, both apps to a a PGXN::Manager instance running locally on port 7496:

      <VirtualHost *:80>
        ProxyPass / http://localhost:7496/pub/
        ProxyPassReverse / http://localhost:7496/pub/
        RequestHeader set X-Forwarded-HTTPS %{HTTPS}s
        RequestHeader set X-Forwaded-Proto http
        RequestHeader set X-Forwarded-Port 80
        RequestHeader set X-Forwarded-Script-Name ""
      <VirtualHost *:443>
        SSLEngine On
        SSLCertificateFile /path/to/certs/
        SSLCertificateKeyFile /path/to/private/
        ProxyPass / http://localhost:7496/auth/
        ProxyPassReverse / http://localhost:7496/auth/
        RequestHeader set X-Forwarded-HTTPS %{HTTPS}s
        RequestHeader set X-Forwaded-Proto https
        RequestHeader set X-Forwarded-Port 443
        RequestHeader set X-Forwarded-Script-Name ""

    Note that to do this, you need to have mod_proxy, mod_headers, and mod_ssl built and installed in your Apache server (most distributions do). The value of X-Forwarded-Script-Name should be the relative path to the app from the proxy server. Here ProxyPass is set to /, so the value should be the empty string. The other headers need to be set to ensure that URLs are properly rewritten by Plack::Middleware::ReverseProxy and clients can't spoof the values to fool the server into thinking it's running under HTTPS when it's not.

    Here's the equivalent configuration using NGINX ngx_http_proxy_module:

      server {
          listen 80;
          merge_slashes: off;
          location / {
              proxy_redirect    off;
              proxy_set_header  X-Forwarded-Host        $host;
              proxy_set_header  X-Forwarded-For         $proxy_add_x_forwarded_for;
              proxy_set_header  X-Forwarded-HTTPS       "";
              proxy_set_header  X-Forwaded-Proto        http;
              proxy_set_header  X-Forwarded-Port        80;
              proxy_set_header  X-Forwarded-Script-Name "";
      server {
          listen 443;
          merge_slashes: off;
          ssl on;
          ssl_certificate /path/to/certs/;
          ssl_certificate_key /path/to/certs/;
          location / {
              proxy_redirect    off;
              proxy_set_header  X-Forwarded-Host        $host;
              proxy_set_header  X-Forwarded-For         $proxy_add_x_forwarded_for;
              proxy_set_header  X-Forwarded-HTTPS       ON;
              proxy_set_header  X-Forwaded-Proto        https;
              proxy_set_header  X-Forwarded-Port        443;
              proxy_set_header  X-Forwarded-Script-Name "";

    Again, it's important to get the headers rewritten properly in order for the routing and writing of URLs is correct and so that clients can't spoof them. Also, be sure to disable merge_slashes or else the mirror management interface will not work.

  • Install Plack::Middleware::ReverseProxy from CPAN:

      cpan Plack::Middleware::ReverseProxy
  • Edit the production configuration file. The there are only a few additional keys to edit:

    1. Add the ReverseProxy middleware. The "middleware" key should end up looking something like this:

        "middleware": [
           ["ErrorDocument", 500, "/error", "subrequest", 1],
           ["StackTrace", "no_print_errors", 1],
    2. Tell PGXN::Manager to use the X-Forwarded-Script-Name header to create proper URLs (otherwise no images, CSS, or JavaScript will work):

        "uri_script_name_key": "HTTP_X_FORWARDED_SCRIPT_NAME",
    3. Tell the public site what link to use to the authenticated site:

        "auth_uri": "",
    4. Configure the Twitter OAuth token so that PGXN::Manager can tweet uploads. The simplest way to do so is to run bin/get_twitter_token -h for helpful intructions and easy configuration.

      You'll also find these settings in conf/proxied.json to help get you started.

  • Restart your Apache server and then your PGXN Manager server. You should now be able to hit the public site at the root of your domain on port 80, and at the authenticated site at the root of your domain on port 443.

Monitoring Mirrors

Once you have mirrors syncing from the master mirror directory (via rsync or however else), you might want to use the check_mirrors utility in a cron job. It simply iterates over teh list of mirrors maintained by PGXN::Manager and reports of any of them appear to be more than a specified number of days, hours, or minutes behind. This will allow you to determine when a mirror may no longer be available, so that you can contact the owner or remove the mirror from the system.

Copyright and License

Copyright (c) 2010-2011 David E. Wheeler.

This module is free software; you can redistribute it and/or modify it under the PostgreSQL License.

Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies.

In no event shall David E. Wheeler be liable to any party for direct, indirect, special, incidental, or consequential damages, including lost profits, arising out of the use of this software and its documentation, even if David E. Wheeler has been advised of the possibility of such damage.

David E. Wheeler specifically disclaims any warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose. The software provided hereunder is on an "as is" basis, and David E. Wheeler has no obligations to provide maintenance, support, updates, enhancements, or modifications.

Something went wrong with that request. Please try again.