             tables : undefined  # if not given, backs up all tables; can also be a list of tables
             path   : 'backup'
             limit  : 3          # number of tables to backup in parallel
+            bup    : true       # creates/updates a bup archive in backup/.bup,
+                                # so we have snapshots of all past backups!
             cb     : required
         if opts.tables?
             tables = opts.tables

            tables = (t for t,s of SCHEMA when not s.virtual)
        dbg = @_dbg("backup_tables()")
        dbg("backing up tables: #{misc.to_json(tables)}")
        backup = (table, cb) =>
            dbg("backup '#{table}'")
            @_backup_table
                table : table
                path  : opts.path
                cb    : cb
        async.mapLimit(tables, opts.limit, backup, (err)=>opts.cb(err))

    _backup_table: (opts) =>
        opts = defaults opts,

                PGPASSWORD : @_password
                PGUSER     : 'smc'
                PGHOST     : @_host
            err_on_exit : true
            cb      : opts.cb

    _backup_bup: (opts) =>
        opts = defaults opts,
            path  : 'backup'
            cb    : required
        dbg = @_dbg("_backup_bup(path='#{opts.path}')")
        # We use no compression because the backup files are already all highly compressed.
        cmd = "mkdir -p '#{opts.path}' && export  && bup init && bup index '#{opts.path}' && bup save --compress=0 '#{opts.path}' -n master"
        dbg(cmd)
        misc_node.execute_code
            command : cmd
            timeout : 0
            home    : '.'
            env     :
                BUP_DIR : "#{opts.path}/.bup"
            err_on_exit : true
            cb      : opts.cb

