pax_global_header00006660000000000000000000000064146417075460014530gustar00rootroot0000000000000052 comment=ce0154c7c4054cec225265cf2350a5422bcdd8cc check_pgbackrest-REL2_4/000077500000000000000000000000001464170754600153035ustar00rootroot00000000000000check_pgbackrest-REL2_4/.github/000077500000000000000000000000001464170754600166435ustar00rootroot00000000000000check_pgbackrest-REL2_4/.github/workflows/000077500000000000000000000000001464170754600207005ustar00rootroot00000000000000check_pgbackrest-REL2_4/.github/workflows/main.yml000066400000000000000000000041021464170754600223440ustar00rootroot00000000000000--- name: main on: push: branches: - main workflow_dispatch: jobs: shared-storage: runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: include: - DOCKERI: rockylinux:9 DBTYPE: PG DBVERSION: 16 CLNAME: ro9pg EXTRA_VARS: "pgbackrest_excpected_release=2.52.1 check_pgbackrest_build=true" steps: - uses: actions/checkout@v4 with: path: check_pgbackrest - uses: shogo82148/actions-setup-perl@v1 - name: Initial step run: cd ${HOME?} && sh ${GITHUB_WORKSPACE?}/check_pgbackrest/tests/run.sh -i - name: Run CI script env: ARCH: shared-storage CLPATH: /home/runner/clusters CLNAME: ${{ matrix.CLNAME }} DBTYPE: ${{ matrix.DBTYPE }} DBVERSION: ${{ matrix.DBVERSION }} DOCKERI: ${{ matrix.DOCKERI }} EXTRA_VARS: ${{ matrix.EXTRA_VARS }} RUN_ARGS: ACTIVITY: true run: cd ${HOME?} && sh ${GITHUB_WORKSPACE?}/check_pgbackrest/tests/ci.sh with-repo-host: runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: include: - DOCKERI: ubuntu:22.04 DBTYPE: PG DBVERSION: 16 CLNAME: u22pg EXTRA_VARS: "pgbackrest_excpected_release=2.52.1 check_pgbackrest_build=true" steps: - uses: actions/checkout@v4 with: path: check_pgbackrest - uses: shogo82148/actions-setup-perl@v1 - name: Initial step run: cd ${HOME?} && sh ${GITHUB_WORKSPACE?}/check_pgbackrest/tests/run.sh -i - name: Run CI script env: ARCH: with-repo-host CLPATH: /home/runner/clusters CLNAME: ${{ matrix.CLNAME }} DBTYPE: ${{ matrix.DBTYPE }} DBVERSION: ${{ matrix.DBVERSION }} DOCKERI: ${{ matrix.DOCKERI }} EXTRA_VARS: ${{ matrix.EXTRA_VARS }} RUN_ARGS: ACTIVITY: true run: cd ${HOME?} && sh ${GITHUB_WORKSPACE?}/check_pgbackrest/tests/ci.sh check_pgbackrest-REL2_4/.gitignore000066400000000000000000000001571464170754600172760ustar00rootroot00000000000000.vagrant/ vagrant.yml tests/validation.sh tests/validation.log tests/test.sh tests/plugins/lookup/__pycache__/ check_pgbackrest-REL2_4/CHANGELOG.md000066400000000000000000000133241464170754600171170ustar00rootroot00000000000000Changelog ========= 2024-07-05 v2.4: - Only support pgBackRest **2.52** and above to fix issue with new pgBackRest versioning policy since `2.52.1`. (Reported by netphantm) - Add new `pgbackrest_version` service, in order to check for a specific pgBackRest target version. 2022-05-31 v2.3: - In case of pgBackRest db history change (after a successful `stanza-upgrade`), only the latest database system/version will be checked. (Reported by Hendrik Schöffmann) - Check if boundary WALs could be defined from history files in case of timeline switches. - Fix timeline switch detection from history files. (Reported by martindomeij) - Add `list-boundaries` option to debug print the boundary WALs fetched from the history files. - Eval pgBackRest JSON output in case of invalid config option would trigger warnings. (Reported by Manuel Alonso) 2021-12-06 v2.2: - The retention service will check if any error was detected during the backup (reported since pgBackRest 2.36). - Add nagios_strict output format to filter out unsupported types of values from performance data. (Reported by netphantm and Adrien Nayrat) - Support uncompressed files in the archives service. (Suggested by Jean-Philippe Guérard) - Add `retention-diff` and `retention-incr` options in the retention service. (Contributed by devopstales). - Add `retention-age-to-oldest` option in the retention service. (Suggested by Hendrik Schöffmann) 2021-09-21 v2.1: - Only support pgBackRest **2.33** and above in order to add support for the multi-repository feature. Introduce the `--repo` option to set the repository index to operate on. When multiple repositories will be found, if the `--repo` argument is not provided, the services will operate on all repositories defined, checking for inconsistencies across multiple repositories. It is however recommended to also define checks using the `--repo` argument to verify the sanity of each repository separately. (Reviewed by Adrien Nayrat) - Add a new `max-archives-check-number` option for the archives service. This is intended to use in case of timeline switch and when boundary WAL can't be detected properly, in order to prevent infinite WAL archives check. - Add `prtg` output format (Hans-Peter Zahno). 2021-02-10 v2.0: - Only support pgBackRest **2.32** and above in order to only use its internal commands. This remove Perl dependencies no-longer needed to reach repository hosts or S3 compatible object stores. This also brings Azure compatible object stores support. The `repo-*` arguments have then been deprecated. - Support non-gz compressed files in the archives check (Magnus Hagander). - Fix the `ignore-archived-*` features when using pgBackRest internal commands (Magnus Hagander). - Improve `ignore-archived-*` features to skip WAL consistency check for backups involving ignored archives. - Skip unneeded boundary WAL check on TL switch (reported by sebastienruiz). - The retention service will now check that at least the backup directory exists, not only trusting the pgBackRest info command output (suggested by Michael Banck). 2020-07-28 v1.9: - The archives service will now only look at the archives listed between the oldest backup start archive and the max WAL returned by the pgBackRest info command. This should avoid unnecessary alerts. To extend the check to all the archives found, the new --extended-check argument has been implemented (suggested by blogh). - Remove refresh of pgBackRest info return after getting the archives list. That avoids CRITICAL alert if an archive is generated between those two steps. Instead, a WARNING message "max WAL is not the latest archive" will be displayed (suggested by blogh). - Fix S3 archives detection (reported by khadijahvf). - New enable-internal-pgbr-cmds argument, for pgBackRest >= 2.28. Internal pgBackRest commands will then be used to list and get the content of files in the repository instead of Perl specific drivers. This is, for instance, needed to access encrypted repositories. This should become the default and only access method in the next release, removing some Perl dependencies. 2020-03-16 v1.8: - Change output of missing archives. The complete list is now only shown in --debug mode (suggested by Guillaume Lelarge). - Add --list-archives argument to print the list of all the archived WAL segments. 2020-01-14 v1.7: - Rename --format argument to --output. - Add json output format. - Add timing debug information. - Improve performance of the needed wal list check. 2019-11-14 v1.6: - Check for each backup its needed archived WALs based on wal start/stop information given by the pgBackRest "info" command. - Return WARNING instead of CRITICAL in case of missing archived WAL prior to latest backup, regardless its type. - Add ignore-archived-before argument to ignore the archived WALs before the provided interval. - Rename ignore-archived-since argument to ignore-archived-after. - Add --retention-age-to-full argument to check the latest full backup age. - Fix bad behavior on CIFS mount (reported by `renesepp`). - Add Amazon s3 support for archives service (Andrew E. Bruno). - Avoid chdir when scanning a directory to avoid some problems with `sudo -u` (Christophe Courtois). - New check_pgb_version service (suggested by Christophe Courtois). 2019-03-18 v1.5: - Order archived WALs list by filename to validate if none is missing. - Add --debug option to print some debug messages. - Add ignore-archived-since argument to ignore the archived WALs since the provided interval. - Add --latest-archive-age-alert to define the max age of the latest archived WAL before raising a critical alert. check_pgbackrest-REL2_4/INSTALL.md000066400000000000000000000014121464170754600167310ustar00rootroot00000000000000## Manual installation To handle the json output format of the pgBackRest info command, you need to install the following module: - on RedHat-like: `perl-JSON` - on Debian-like: `libjson-perl` The Data::Dump perl module is also needed: - On RedHat-like: `perl-Data-Dumper` - On Debian-like: `libdata-dump-perl` ----- ## PGDG packages ### RPM To install check_pgbackrest using the PGDG repositories: ``` yum install -y epel-release yum install -y nagios-plugins-pgbackrest ``` The rpm will also require nagios-plugins to be installed and put the `check_pgbackrest` script there. That's why the epel-release package is needed too. ### DEB To install check_pgbackrest using the PGDG repositories (located in `/usr/bin`): ``` apt-get -y install check-pgbackrest ``` check_pgbackrest-REL2_4/LICENSE000066400000000000000000000017361464170754600163170ustar00rootroot00000000000000PostgreSQL License Copyright (c) 2018-2020, DALIBO Copyright (c) 2020-2024, Stefan Fercot 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 Stefan Fercot 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 Stefan Fercot HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Stefan Fercot 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 Stefan Fercot HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. check_pgbackrest-REL2_4/README000066400000000000000000000131721464170754600161670ustar00rootroot00000000000000NAME check_pgbackrest - pgBackRest backup check plugin for Nagios SYNOPSIS check_pgbackrest [-s|--service SERVICE] [-S|--stanza NAME] check_pgbackrest [-l|--list] check_pgbackrest [--help] DESCRIPTION check_pgbackrest is designed to monitor pgBackRest (2.52 and above) backups from Nagios. -s, --service SERVICE The Nagios service to run. See section SERVICES for a description of available services or use "--list" for a short service and description list. -S, --stanza NAME Name of the stanza to check. --repo REPOSITORY Repository index to operate on. If no "--repo" argument is provided, the service will operate on all repositories defined, checking for inconsistencies across multiple repositories. When using multiple repositories, it is recommended to also define checks using the "--repo" argument to verify the sanity of each repository separately. -O, --output OUTPUT_FORMAT The output format. Supported outputs are: "human", "json", "nagios" (default), "nagios_strict" and "prtg". The "nagios_strict" output format will filter out unsupported types of values from the performance data. -C, --command FILE pgBackRest executable file (default: "pgbackrest"). -c, --config CONFIGURATION_FILE pgBackRest configuration file. -P, --prefix COMMAND Some prefix command to execute the pgBackRest info command (eg: "sudo -iu postgres"). -l, --list List available services. --debug Print some debug messages. -V, --version Print version and exit. -?, --help Show this help page. SERVICES Descriptions and parameters of available services. retention Fail when the number of full backups is less than the "--retention-full" argument. Fail when the number of differential backups is less than the "--retention-diff" argument. Fail when the number of incremental backups is less than the "--retention-incr" argument. Fail when the newest backup is older than the "--retention-age" argument. Fail when the newest full backup is older than the "--retention-age-to-full" argument. Fail when the oldest backup is newer than the "--retention-age-to-oldest" argument. The following units are accepted (not case sensitive): s (second), m (minute), h (hour), d (day). You can use more than one unit per given value. Arguments are not mandatory to only show some information. archives Check if all archived WALs exist between the oldest and the latest WAL needed for the recovery. Use the "--wal-segsize" argument to set the WAL segment size. The following units are accepted (not case sensitive): b (Byte), k (KB), m (MB), g (GB), t (TB), p (PB), e (EB) or Z (ZB). Only integers are accepted. Eg. "1.5MB" will be refused, use "1500kB". The factor between units is 1024 bytes. Eg. "1g = 1G = 1024*1024*1024." Use the "--ignore-archived-before" argument to ignore the archived WALs generated before the provided interval. Used to only check the latest archives. Use the "--ignore-archived-after" argument to ignore the archived WALs generated after the provided interval. The "--latest-archive-age-alert" argument defines the max age of the latest archived WAL as an interval before raising a critical alert. The following units are accepted as interval (not case sensitive): s (second), m (minute), h (hour), d (day). You can use more than one unit per given value. If not set, the last unit is in seconds. Eg. "1h 55m 6" = "1h55m6s". All the missing archives are only shown in the "--debug" mode. Use "--list-archives" in addition with "--debug" to print the list of all the archived WAL segments. Use "--list-boundaries" in addition with "--debug" to print the list of all the boundary WAL segments fetched from the history files. By default, all the archives older than the oldest backup start archive or newer than the max_wal returned by the pgBackRest info command are ignored. Use the "--extended-check" argument to force a full check of the found archives and raise warnings in case of inconsistencies. When WAL archives on different timelines are found, .history files are parsed to find the switch point and define the boundary WAL. Use the "--max-archives-check-number" to prevent infinite WAL archives check when boundary WAL can't be defined properly. In case of pgBackRest db history change (after a successful stanza-upgrade), only the latest db system/version will be checked. check_pgb_version Check if this script is running a given version. You must provide the expected version using "--target-version". pgbackrest_version Check if pgBackRest is running a given version. You must provide the expected version using "--target-version". CONTRIBUTING check_pgbackrest is an open project. Any contribution to improve it is welcome. VERSION check_pgbackrest version 2.4, released on Fri Jul 05 2024. LICENSING This program is open source, licensed under the PostgreSQL license. For license terms, see the LICENSE file. AUTHORS Author: Stefan Fercot. Logo: Damien Cazeils (www.damiencazeils.com). Copyright: (c) 2018-2020, Dalibo / 2020-2024, Stefan Fercot. check_pgbackrest-REL2_4/README.pod000066400000000000000000000123261464170754600167500ustar00rootroot00000000000000=head1 NAME check_pgbackrest - pgBackRest backup check plugin for Nagios =head1 SYNOPSIS check_pgbackrest [-s|--service SERVICE] [-S|--stanza NAME] check_pgbackrest [-l|--list] check_pgbackrest [--help] =head1 DESCRIPTION check_pgbackrest is designed to monitor pgBackRest (2.52 and above) backups from Nagios. =over =item B<-s>, B<--service> SERVICE The Nagios service to run. See section SERVICES for a description of available services or use C<--list> for a short service and description list. =item B<-S>, B<--stanza> NAME Name of the stanza to check. =item B<--repo> REPOSITORY Repository index to operate on. If no C<--repo> argument is provided, the service will operate on all repositories defined, checking for inconsistencies across multiple repositories. When using multiple repositories, it is recommended to also define checks using the C<--repo> argument to verify the sanity of each repository separately. =item B<-O>, B<--output> OUTPUT_FORMAT The output format. Supported outputs are: C, C, C (default), C and C. The C output format will filter out unsupported types of values from the performance data. =item B<-C>, B<--command> FILE pgBackRest executable file (default: "pgbackrest"). =item B<-c>, B<--config> CONFIGURATION_FILE pgBackRest configuration file. =item B<-P>, B<--prefix> COMMAND Some prefix command to execute the pgBackRest info command (eg: "sudo -iu postgres"). =item B<-l>, B<--list> List available services. =item B<--debug> Print some debug messages. =item B<-V>, B<--version> Print version and exit. =item B<-?>, B<--help> Show this help page. =back =head2 SERVICES Descriptions and parameters of available services. =over =item B Fail when the number of full backups is less than the C<--retention-full> argument. Fail when the number of differential backups is less than the C<--retention-diff> argument. Fail when the number of incremental backups is less than the C<--retention-incr> argument. Fail when the newest backup is older than the C<--retention-age> argument. Fail when the newest full backup is older than the C<--retention-age-to-full> argument. Fail when the oldest backup is newer than the C<--retention-age-to-oldest> argument. The following units are accepted (not case sensitive): s (second), m (minute), h (hour), d (day). You can use more than one unit per given value. Arguments are not mandatory to only show some information. =item B Check if all archived WALs exist between the oldest and the latest WAL needed for the recovery. Use the C<--wal-segsize> argument to set the WAL segment size. The following units are accepted (not case sensitive): b (Byte), k (KB), m (MB), g (GB), t (TB), p (PB), e (EB) or Z (ZB). Only integers are accepted. Eg. C<1.5MB> will be refused, use C<1500kB>. The factor between units is 1024 bytes. Eg. C<1g = 1G = 1024*1024*1024.> Use the C<--ignore-archived-before> argument to ignore the archived WALs generated before the provided interval. Used to only check the latest archives. Use the C<--ignore-archived-after> argument to ignore the archived WALs generated after the provided interval. The C<--latest-archive-age-alert> argument defines the max age of the latest archived WAL as an interval before raising a critical alert. The following units are accepted as interval (not case sensitive): s (second), m (minute), h (hour), d (day). You can use more than one unit per given value. If not set, the last unit is in seconds. Eg. "1h 55m 6" = "1h55m6s". All the missing archives are only shown in the C<--debug> mode. Use C<--list-archives> in addition with C<--debug> to print the list of all the archived WAL segments. Use C<--list-boundaries> in addition with C<--debug> to print the list of all the boundary WAL segments fetched from the history files. By default, all the archives older than the oldest backup start archive or newer than the max_wal returned by the pgBackRest info command are ignored. Use the C<--extended-check> argument to force a full check of the found archives and raise warnings in case of inconsistencies. When WAL archives on different timelines are found, .history files are parsed to find the switch point and define the boundary WAL. Use the C<--max-archives-check-number> to prevent infinite WAL archives check when boundary WAL can't be defined properly. In case of pgBackRest db history change (after a successful stanza-upgrade), only the latest db system/version will be checked. =item B Check if this script is running a given version. You must provide the expected version using C<--target-version>. =item B Check if pgBackRest is running a given version. You must provide the expected version using C<--target-version>. =back =head1 CONTRIBUTING check_pgbackrest is an open project. Any contribution to improve it is welcome. =head1 VERSION check_pgbackrest version 2.4, released on Fri Jul 05 2024. =head1 LICENSING This program is open source, licensed under the PostgreSQL license. For license terms, see the LICENSE file. =head1 AUTHORS Author: Stefan Fercot. Logo: Damien Cazeils (www.damiencazeils.com). Copyright: (c) 2018-2020, Dalibo / 2020-2024, Stefan Fercot. check_pgbackrest-REL2_4/RELEASING.md000066400000000000000000000037441464170754600171460ustar00rootroot00000000000000# Releasing ## Source code Edit variable `$VERSION` in `check_pgbackrest`, and update the version field at the end of the in-line documentation in this script. Use date format `LC_TIME=C date +"%a %b %d %Y"`. Update the `CHANGELOG.md` file too. ## Documentation Generate updated documentation: ```bash pod2text check_pgbackrest > README podselect check_pgbackrest > README.pod ``` ## Tagging and building tar file ```bash TAG=REL2_4 git tag -a $TAG -m "Release $TAG" git tag git push --tags git archive --prefix=check_pgbackrest-$TAG/ -o /tmp/check_pgbackrest-2.4.tar.gz $TAG ``` ## Release on github - Go to https://github.com/pgstef/check_pgbackrest/releases - Edit the release notes for the new tag - Set "check_pgbackrest $VERSION" as title, eg. "check_pgbackrest 2.4" - Here is the format of the release node itself: YYYY-MM-DD - Version X.Y Changelog: * item 1 * item 2 * ... - Upload the tar file - Save - Check or update https://github.com/pgstef/check_pgbackrest/releases ## Community ### Submit a news on postgresql.org * Title: "check_pgbackrest 2.4 has been released" * Content: ``` _Town, Country, Month xx, 2022_ `check_pgbackrest` is designed to monitor [pgBackRest](https://pgbackrest.org) backups from Nagios, relying on the status information given by the [info](https://pgbackrest.org/command.html#command-info) command. It allows to monitor the backups retention and the consistency of the archived WAL segments. Changes in check_pgbackrest 2.4 -------------------------------------------------------------------------------- * ... * ... Links & Credits -------------------------------------------------------------------------------- This is an open project, licensed under the PostgreSQL license. Any contribution to improve it is welcome. Links: * Download: https://github.com/pgstef/check_pgbackrest/releases * Support: https://github.com/pgstef/check_pgbackrest/issues ``` * check "Related Open Source" check_pgbackrest-REL2_4/check_pgbackrest000077500000000000000000001411441464170754600205200ustar00rootroot00000000000000#!/usr/bin/env perl #----------------------------------------------------------------------------- # This program is open source, licensed under the PostgreSQL license. # For license terms, see the LICENSE file. # # Author: Stefan Fercot # Copyright: (c) 2018-2020, Dalibo. # Copyright: (c) 2020-2024, Stefan Fercot. #----------------------------------------------------------------------------- =head1 NAME check_pgbackrest - pgBackRest backup check plugin for Nagios =head1 SYNOPSIS check_pgbackrest [-s|--service SERVICE] [-S|--stanza NAME] check_pgbackrest [-l|--list] check_pgbackrest [--help] =head1 DESCRIPTION check_pgbackrest is designed to monitor pgBackRest (2.52 and above) backups from Nagios. =cut use vars qw($VERSION $PROGRAM $PGBR_SUPPORT $INIT_TIME); use strict; use warnings; use POSIX; use Data::Dumper; use File::Basename; use File::Spec; use File::Find; use Getopt::Long qw(:config bundling no_ignore_case_always); use Pod::Usage; use Config; use FindBin; # Display error message if some specific modules are not loaded BEGIN { my(@DBs, @missingDBs, $mod); @DBs = qw(JSON); for $mod (@DBs) { if (eval "require $mod") { $mod->import(); } else { push @missingDBs, $mod; } } die "@missingDBs module(s) not loaded.\n" if @missingDBs; } # Messing with PATH so pod2usage always finds this script my @path = split /$Config{'path_sep'}/ => $ENV{'PATH'}; push @path => $FindBin::Bin; $ENV{'PATH'} = join $Config{'path_sep'} => @path; undef @path; # Reference to the output sub my $output_fmt; $VERSION = '2.4'; $PROGRAM = 'check_pgbackrest'; $PGBR_SUPPORT = '2.52'; $INIT_TIME = time(); # Available services and descriptions. #----------------------------------------------------------------------------- my %services = ( 'retention' => { 'sub' => \&check_retention, 'desc' => 'Check the retention policy.', 'stanza-arg' => 1 }, 'archives' => { 'sub' => \&check_wal_archives, 'desc' => 'Check WAL archives.', 'stanza-arg' => 1 }, 'check_pgb_version' => { 'sub' => \&check_check_pgbackrest_version, 'desc' => 'Check the version of this check_pgbackrest script.', 'stanza-arg' => 0 }, 'pgbackrest_version' => { 'sub' => \&check_pgbackrest_version, 'desc' => 'Check the version of pgBackRest.', 'stanza-arg' => 0 } ); =over =item B<-s>, B<--service> SERVICE The Nagios service to run. See section SERVICES for a description of available services or use C<--list> for a short service and description list. =item B<-S>, B<--stanza> NAME Name of the stanza to check. =item B<--repo> REPOSITORY Repository index to operate on. If no C<--repo> argument is provided, the service will operate on all repositories defined, checking for inconsistencies across multiple repositories. When using multiple repositories, it is recommended to also define checks using the C<--repo> argument to verify the sanity of each repository separately. =item B<-O>, B<--output> OUTPUT_FORMAT The output format. Supported outputs are: C, C, C (default), C and C. The C output format will filter out unsupported types of values from the performance data. =item B<-C>, B<--command> FILE pgBackRest executable file (default: "pgbackrest"). =item B<-c>, B<--config> CONFIGURATION_FILE pgBackRest configuration file. =item B<-P>, B<--prefix> COMMAND Some prefix command to execute the pgBackRest info command (eg: "sudo -iu postgres"). =item B<-l>, B<--list> List available services. =item B<--debug> Print some debug messages. =item B<-V>, B<--version> Print version and exit. =item B<-?>, B<--help> Show this help page. =back =cut my %args = ( 'command' => 'pgbackrest', 'output' => 'nagios', 'wal-segsize' => '16MB', 'default-pgbackrest-config-file' => '/etc/pgbackrest.conf', ); # Set name of the program without path* my $orig_name = $0; $0 = $PROGRAM; # Die on kill -1, -2, -3 or -15 $SIG{'HUP'} = $SIG{'INT'} = $SIG{'QUIT'} = $SIG{'TERM'} = \&terminate; # Handle SIG sub terminate { my ($signal) = @_; die ("SIG $signal caught."); } # Print the version and exit sub version { printf "%s version %s, Perl %vd\n", $PROGRAM, $VERSION, $^V; exit 0; } # List services that can be performed sub list_services { print "List of available services:\n\n"; foreach my $service ( sort keys %services ) { printf "\t%-17s\t%s\n", $service, $services{$service}{'desc'}; } exit 0; } # Handle output formats #----------------------------------------------------------------------------- # Define which @longmsg keys will use TimeSeconds or Count units. my @TimeKeys = ("latest_bck_age", "latest_full_age", "latest_archive_age", "oldest_bck_age"); my @CountKeys = ("full", "diff", "incr", "num_unique_archives", "num_missing_archives"); sub dprint { return unless $args{'debug'}; foreach (@_) { print "DEBUG: $_"; } } sub unknown($;$$$) { return $output_fmt->( 3, $_[0], $_[1], $_[2], $_[3] ); } sub critical($;$$$) { return $output_fmt->( 2, $_[0], $_[1], $_[2], $_[3] ); } sub warning($;$$$) { return $output_fmt->( 1, $_[0], $_[1], $_[2], $_[3] ); } sub ok($;$$$) { return $output_fmt->( 0, $_[0], $_[1], $_[2], $_[3] ); } sub human_output ($$;$$$) { my $rc = shift; my $service = shift; my $ret; my @msg; my @longmsg; my @human_only_longmsg; @msg = @{ $_[0] } if defined $_[0]; @longmsg = @{ $_[1] } if defined $_[1]; @human_only_longmsg = @{ $_[2] } if defined $_[2]; $ret = sprintf "%-15s: %s\n", 'Service', $service; $ret .= sprintf "%-15s: 0 (%s)\n", "Returns", "OK" if $rc == 0; $ret .= sprintf "%-15s: 1 (%s)\n", "Returns", "WARNING" if $rc == 1; $ret .= sprintf "%-15s: 2 (%s)\n", "Returns", "CRITICAL" if $rc == 2; $ret .= sprintf "%-15s: 3 (%s)\n", "Returns", "UNKNOWN" if $rc == 3; $ret .= sprintf "%-15s: %s\n", "Message", $_ foreach @msg; $ret .= sprintf "%-15s: %s\n", "Long message", $_ foreach @longmsg; $ret .= sprintf "%-15s: %s\n", "Long message", $_ foreach @human_only_longmsg; print $ret; return $rc; } sub json_output ($$;$$$) { my $rc = shift; my $service = shift; my @msg; my @longmsg; my @human_only_longmsg; @msg = @{ $_[0] } if defined $_[0]; @longmsg = @{ $_[1] } if defined $_[1]; @human_only_longmsg = @{ $_[2] } if defined $_[2]; my %json_hash = ('service' => $service); my @rc_long = ("OK", "WARNING", "CRITICAL", "UNKNOWN"); $json_hash{'status'}{'code'} = $rc; $json_hash{'status'}{'message'} = $rc_long[$rc]; $json_hash{'message'} = join( ', ', @msg ) if @msg; foreach my $msg_to_split (@longmsg, @human_only_longmsg) { my ($key, $value) = split(/=/, $msg_to_split); $json_hash{'long_message'}{$key} = $value; } my $json_text = encode_json \%json_hash; print "[$json_text]"; return $rc; } sub nagios_output ($$;$$) { my $rc = shift; my $ret = shift; my @msg; my @longmsg; $ret .= " OK" if $rc == 0; $ret .= " WARNING" if $rc == 1; $ret .= " CRITICAL" if $rc == 2; $ret .= " UNKNOWN" if $rc == 3; @msg = @{ $_[0] } if defined $_[0]; @longmsg = @{ $_[1] } if defined $_[1]; $ret .= " - ". join( ', ', @msg ) if @msg; $ret .= " | ". join( ' ', @longmsg ) if @longmsg; print $ret; return $rc; } sub nagios_strict_output ($$;$$) { my $rc = shift; my $service = shift; my @msg; my @longmsg; @msg = @{ $_[0] } if defined $_[0]; @longmsg = @{ $_[1] } if defined $_[1]; # Generate TEXT message my $text; $text .= $service . " OK" if $rc == 0; $text .= $service . " WARNING" if $rc == 1; $text .= $service . " CRITICAL" if $rc == 2; $text .= $service . " UNKNOWN" if $rc == 3; $text .= " - ". join( ', ', @msg ) if @msg; # Enforce Nagios strict specs, filter out some keys my @longmsg_strict; foreach my $msg_to_split (@longmsg) { my ($key, $value) = split(/=/, $msg_to_split); if ( grep /^$key$/, @TimeKeys or grep /^$key$/, @CountKeys ) { push @longmsg_strict, $msg_to_split; } } $text .= " | ". join( ' ', @longmsg_strict ) if @longmsg_strict; print $text; return $rc; } sub prtg_output ($$;$$) { my $rc = shift; my $service = shift; my @msg; my @longmsg; my @textmsg; @msg = @{ $_[0] } if defined $_[0]; @longmsg = @{ $_[1] } if defined $_[1]; # Generate TEXT message my $text = ""; $text .= $service . " OK" if $rc == 0; $text .= $service . " WARNING" if $rc == 1; $text .= $service . " CRITICAL" if $rc == 2; $text .= $service . " UNKNOWN" if $rc == 3; $text .= " - ". join( ', ', @msg ) if @msg; # Generate service status result my $results = "status$rc"; $results .= "0"; $results .= "1"; $results .= "1"; foreach my $msg_to_split (@longmsg) { my ($key, $value) = split(/=/, $msg_to_split); if ( grep /^$key$/, @TimeKeys ) { chop($value); $results .= "$key$valueTimeSeconds"; } elsif ( grep /^$key$/, @CountKeys ) { $results .= "$key$valueCount"; } else { # Add extra keys to the text message push @textmsg, $msg_to_split; } } $text .= " - ". join( ', ', @textmsg ) if @textmsg; $text .= ""; print "" . $results . $text. ""; return $rc; } # Handle time intervals #----------------------------------------------------------------------------- sub is_time($){ my $str_time = lc( shift() ); return 1 if ( $str_time =~ /^(\s*([0-9]\s*[smhd]?\s*))+$/ ); return 0; } # Return formatted time string with units. # Parameter: duration in seconds sub to_interval($) { my $val = shift; my $interval = ''; return $val if $val =~ /^-?inf/i; $val = int($val); if ( $val > 604800 ) { $interval = int( $val / 604800 ) . "w "; $val %= 604800; } if ( $val > 86400 ) { $interval .= int( $val / 86400 ) . "d "; $val %= 86400; } if ( $val > 3600 ) { $interval .= int( $val / 3600 ) . "h"; $val %= 3600; } if ( $val > 60 ) { $interval .= int( $val / 60 ) . "m"; $val %= 60; } $interval .= "${val}s" if $val > 0; return "${val}s" unless $interval; # Return a value if $val <= 0 return $interval; } sub to_interval_output_dependent($) { my $val = shift; my $interval = ''; return $val if $val =~ /^-?inf/i; $val = int($val); return to_interval($val) unless $args{'output'} =~ /^(nagios|nagios_strict|prtg)$/; return "${val}s"; } # Return a duration in seconds from an interval (with units). sub get_time($) { my $str_time = lc( shift() ); my $ts = 0; my @date; die( "Malformed interval: «$str_time»!\n" . "Authorized unit are: dD, hH, mM, sS.\n" ) unless is_time($str_time); # No bad units should exist after this line! @date = split( /([smhd])/, $str_time ); LOOP_TS: while ( my $val = shift @date ) { $val = int($val); die("Wrong value for an interval: «$val»!") unless defined $val; my $unit = shift(@date) || ''; if ( $unit eq 'm' ) { $ts += $val * 60; next LOOP_TS; } if ( $unit eq 'h' ) { $ts += $val * 3600; next LOOP_TS; } if ( $unit eq 'd' ) { $ts += $val * 86400; next LOOP_TS; } $ts += $val; } return $ts; } # Handle size units #----------------------------------------------------------------------------- # Return a size in bytes from a size with unit. # If unit is '%', use the second parameter to compute the size in bytes. sub get_size($;$) { my $str_size = shift; my $size = 0; my $unit = ''; die "Only integers are accepted as size. Adjust the unit to your need.\n" if $str_size =~ /[.,]/; $str_size =~ /^([0-9]+)(.*)$/; $size = int($1); $unit = lc($2); return $size unless $unit ne ''; if ( $unit eq '%' ) { my $ratio = shift; die("Can't compute a ratio without the factor!\n") unless defined $unit; return int( $size * $ratio / 100 ); } return $size if $unit eq 'b'; return $size * 1024 if $unit =~ '^k[bo]?$'; return $size * 1024**2 if $unit =~ '^m[bo]?$'; return $size * 1024**3 if $unit =~ '^g[bo]?$'; return $size * 1024**4 if $unit =~ '^t[bo]?$'; return $size * 1024**5 if $unit =~ '^p[bo]?$'; return $size * 1024**6 if $unit =~ '^e[bo]?$'; return $size * 1024**7 if $unit =~ '^z[bo]?$'; die("Unknown size unit: $unit\n"); } # Interact with pgBackRest #----------------------------------------------------------------------------- sub pgbackrest_info { my $infocmd = $args{'command'}." info"; $infocmd .= " --stanza=".$args{'stanza'}; $infocmd .= " --output=json --log-level-console=error"; if(defined $args{'config'}) { $infocmd .= " --config=".$args{'config'}; } if(defined $args{'repo'}) { $infocmd .= " --repo=".$args{'repo'}; } if(defined $args{'prefix'}) { $infocmd = $args{'prefix'}." $infocmd"; } dprint("pgBackRest info command was : '$infocmd'\n"); my $json_output = `$infocmd 2>&1 |grep -v "^ERROR:"`; die("Can't get pgBackRest info.\nCommand was '$infocmd'.\n") unless ($? eq 0); my $decoded_json = eval { decode_json($json_output) }; die("pgBackRest output was not valid JSON.\nHINT: does your configuration file contain invalid option?\n") if ($@); foreach my $line (@{$decoded_json}) { return $line if($line->{'name'} eq $args{'stanza'}); } return; } sub pgbackrest_get { my $directory = shift; my $filename = shift; my $repo_key = shift; pod2usage( -message => 'FATAL: Unsupported pgBackRest version.', -exitval => 127 ) if ( pgbackrest_version() lt $PGBR_SUPPORT ); my $getcmd = $args{'command'}." repo-get"; $getcmd .= " --stanza=".$args{'stanza'}; $getcmd .= " ".$directory."/".$filename; $getcmd .= " --log-level-console=error"; $getcmd .= " --repo=".$repo_key; if(defined $args{'config'}) { $getcmd .= " --config=".$args{'config'}; } if(defined $args{'prefix'}) { $getcmd = $args{'prefix'}." $getcmd"; } dprint("pgBackRest get command was : '$getcmd'\n"); my $file_content = `$getcmd 2>&1 |grep -v "^ERROR:" |grep -v "^WARN:"`; die("Can't get pgBackRest file content.\nCommand was '$getcmd'.\n") unless ($? eq 0); return $file_content; } sub pgbackrest_ls { my $directory = shift; my $repo_key = shift; my $recurse = shift; pod2usage( -message => 'FATAL: Unsupported pgBackRest version.', -exitval => 127 ) if ( pgbackrest_version() lt $PGBR_SUPPORT ); my $lscmd = $args{'command'}." repo-ls"; $lscmd .= " --stanza=".$args{'stanza'}; $lscmd .= " ".$directory; $lscmd .= " --output=json --log-level-console=error"; $lscmd .= " --repo=".$repo_key; if($recurse) { $lscmd .= " --recurse"; } if(defined $args{'config'}) { $lscmd .= " --config=".$args{'config'}; } if(defined $args{'prefix'}) { $lscmd = $args{'prefix'}." $lscmd"; } dprint("pgBackRest ls command was : '$lscmd'\n"); my $json_output = `$lscmd 2>&1 |grep -v "^ERROR:"`; die("Can't get pgBackRest list.\nCommand was '$lscmd'.\n") unless ($? eq 0); my $decoded_json = eval { decode_json($json_output) }; die("pgBackRest output was not valid JSON.\nHINT: does your configuration file contain invalid option?\n") if ($@); return $decoded_json; } sub pgbackrest_version { my $version_cmd = $args{'command'}." version"; if(defined $args{'config'}) { $version_cmd .= " --config=".$args{'config'}; } if(defined $args{'prefix'}) { $version_cmd = $args{'prefix'}." $version_cmd"; } dprint("pgBackRest version command was : '$version_cmd'\n"); my $pgbackrest_version = `$version_cmd | sed -e s/pgBackRest\\ //`; die("Can't get pgBackRest version.\nCommand was '$version_cmd'.\n") unless ($? eq 0); chomp($pgbackrest_version); return $pgbackrest_version; } # Services #----------------------------------------------------------------------------- =head2 SERVICES Descriptions and parameters of available services. =over =item B Fail when the number of full backups is less than the C<--retention-full> argument. Fail when the number of differential backups is less than the C<--retention-diff> argument. Fail when the number of incremental backups is less than the C<--retention-incr> argument. Fail when the newest backup is older than the C<--retention-age> argument. Fail when the newest full backup is older than the C<--retention-age-to-full> argument. Fail when the oldest backup is newer than the C<--retention-age-to-oldest> argument. The following units are accepted (not case sensitive): s (second), m (minute), h (hour), d (day). You can use more than one unit per given value. Arguments are not mandatory to only show some information. =cut sub check_retention { my $me = 'BACKUPS_RETENTION'; my %args = %{ $_[0] }; my @msg; my @warn_msg; my @crit_msg; my @longmsg; # When using the --repo option, pgBackRest info will apply the repository filter my $backups_info = pgbackrest_info(); die("Can't get pgBackRest info.\n") unless (defined $backups_info); if($backups_info->{'status'}->{'code'} == 0) { # List each repository content my @backups_dir_content; foreach my $repo (@{$backups_info->{'repo'}}) { my $backups_dir = "backup/".$args{'stanza'}; # Relative path inside repository dprint("repo".$repo->{'key'}.", backups_dir: $backups_dir\n"); $backups_dir_content[$repo->{'key'}] = pgbackrest_ls($backups_dir, $repo->{'key'}, 0); } # List backups per type and check consistency between backup info and real repository content my @full_bck; my @diff_bck; my @incr_bck; foreach my $line (@{$backups_info->{'backup'}}) { push @full_bck, $line if($line->{'type'} eq "full"); push @diff_bck, $line if($line->{'type'} eq "diff"); push @incr_bck, $line if($line->{'type'} eq "incr"); my $backup_label = $line->{'label'}; my $repo_key = $line->{'database'}->{'repo-key'}; unless(defined $backups_dir_content[$repo_key]->{$backup_label} and $backups_dir_content[$repo_key]->{$backup_label}->{'type'} eq 'path') { push @crit_msg, "$backup_label directory missing in repo$repo_key"; } # Check if any error was detected during the backup (reported in the json output since pgBackRest 2.36) if(defined $line->{'error'} && $line->{'error'}){ push @crit_msg, "error(s) detected during backup $backup_label (repo$repo_key)"; } } push @longmsg, "full=".scalar(@full_bck); push @longmsg, "diff=".scalar(@diff_bck); push @longmsg, "incr=".scalar(@incr_bck); # Check retention-full if(defined $args{'retention-full'} and scalar(@full_bck) < $args{'retention-full'}) { push @crit_msg, "not enough full backups: ".$args{'retention-full'}." required"; } # Check retention-diff if(defined $args{'retention-diff'} and scalar(@diff_bck) < $args{'retention-diff'}) { push @crit_msg, "not enough differential backups: ".$args{'retention-diff'}." required"; } # Check retention-incr if(defined $args{'retention-incr'} and scalar(@incr_bck) < $args{'retention-incr'}) { push @crit_msg, "not enough incremental backups: ".$args{'retention-incr'}." required"; } # Check latest age # Backup age considered at pg_stop_backup my $latest_bck = @{$backups_info->{'backup'}}[-1]; my $latest_bck_age = time() - $latest_bck->{'timestamp'}->{'stop'}; push @longmsg, "latest_bck=".$latest_bck->{'label'}; push @longmsg, "latest_bck_type=".$latest_bck->{'type'}; push @longmsg, "latest_bck_age=".to_interval_output_dependent($latest_bck_age); if(defined $args{'retention-age'}){ my $bck_age_limit = get_time($args{'retention-age'} ); push @crit_msg, "backups are too old" if $latest_bck_age >= $bck_age_limit; } # Check latest full backup age if(defined $args{'retention-age-to-full'}){ my $latest_full_bck = $full_bck[-1]; my $latest_full_bck_age = time() - $latest_full_bck->{'timestamp'}->{'stop'}; push @longmsg, "latest_full=".$latest_full_bck->{'label'}; push @longmsg, "latest_full_age=".to_interval_output_dependent($latest_full_bck_age); my $bck_age_limit = get_time($args{'retention-age-to-full'} ); push @crit_msg, "full backups are too old" if $latest_full_bck_age >= $bck_age_limit; } # Check oldest age my $oldest_bck = @{$backups_info->{'backup'}}[0]; my $oldest_bck_age = time() - $oldest_bck->{'timestamp'}->{'stop'}; push @longmsg, "oldest_bck=".$oldest_bck->{'label'}; push @longmsg, "oldest_bck_age=".to_interval_output_dependent($oldest_bck_age); if(defined $args{'retention-age-to-oldest'}){ my $bck_age_limit = get_time($args{'retention-age-to-oldest'} ); push @crit_msg, "backups are too young" if $oldest_bck_age < $bck_age_limit; } }else{ # Get the exact status code per repository foreach my $repo (@{$backups_info->{'repo'}}) { push @crit_msg, "repo".$repo->{'key'}.": ".$repo->{'status'}->{'message'} if $repo->{'status'}->{'code'} gt 0; } } return critical($me, \@crit_msg, \@longmsg) if @crit_msg; return warning($me, \@warn_msg, \@longmsg) if @warn_msg; push @msg, "backups policy checks ok"; return ok( $me, \@msg, \@longmsg ); } =item B Check if all archived WALs exist between the oldest and the latest WAL needed for the recovery. Use the C<--wal-segsize> argument to set the WAL segment size. The following units are accepted (not case sensitive): b (Byte), k (KB), m (MB), g (GB), t (TB), p (PB), e (EB) or Z (ZB). Only integers are accepted. Eg. C<1.5MB> will be refused, use C<1500kB>. The factor between units is 1024 bytes. Eg. C<1g = 1G = 1024*1024*1024.> Use the C<--ignore-archived-before> argument to ignore the archived WALs generated before the provided interval. Used to only check the latest archives. Use the C<--ignore-archived-after> argument to ignore the archived WALs generated after the provided interval. The C<--latest-archive-age-alert> argument defines the max age of the latest archived WAL as an interval before raising a critical alert. The following units are accepted as interval (not case sensitive): s (second), m (minute), h (hour), d (day). You can use more than one unit per given value. If not set, the last unit is in seconds. Eg. "1h 55m 6" = "1h55m6s". All the missing archives are only shown in the C<--debug> mode. Use C<--list-archives> in addition with C<--debug> to print the list of all the archived WAL segments. Use C<--list-boundaries> in addition with C<--debug> to print the list of all the boundary WAL segments fetched from the history files. By default, all the archives older than the oldest backup start archive or newer than the max_wal returned by the pgBackRest info command are ignored. Use the C<--extended-check> argument to force a full check of the found archives and raise warnings in case of inconsistencies. When WAL archives on different timelines are found, .history files are parsed to find the switch point and define the boundary WAL. Use the C<--max-archives-check-number> to prevent infinite WAL archives check when boundary WAL can't be defined properly. In case of pgBackRest db history change (after a successful stanza-upgrade), only the latest db system/version will be checked. =cut sub get_archived_wal_list { my $min_wal = shift; my $max_wal = shift; my $args_ref = shift; my %args = %{ $args_ref }; my $archives_dir = shift; my $suffix = "(\.(gz|lz4|zst|xz|bz2))?"; my %filelist; my @branch_wals; my $filename_re_full = qr/[0-9A-F]{24}.*$suffix$/; my $start_tl = substr($min_wal, 0, 8); my $end_tl = substr($max_wal, 0, 8); my $history_re_full = qr/$end_tl.history$/; foreach my $repo_key (keys %{$archives_dir}) { dprint("repo$repo_key, archives_dir: ".$archives_dir->{$repo_key}."\n"); my $list = pgbackrest_ls($archives_dir->{$repo_key}, $repo_key, 1); foreach my $key (keys %{$list}) { next unless $list->{$key}->{'type'} eq 'file'; my @split_tab = split('/', $key); my $filename = $split_tab[-1]; if($filename =~ /$filename_re_full/){ # Get stats of the archived WALs if ( $args{'ignore-archived-after'} or $args{'ignore-archived-before'} ) { my $diff_epoch = $INIT_TIME - $list->{$key}->{'time'}; if ( $args{'ignore-archived-after'} && $diff_epoch <= get_time($args{'ignore-archived-after'}) ){ dprint ("ignored file ".$filename." as interval since epoch : ".to_interval($diff_epoch)."\n"); next; } if ( $args{'ignore-archived-before'} && $diff_epoch >= get_time($args{'ignore-archived-before'}) ){ dprint ("ignored file ".$filename." as interval since epoch : ".to_interval($diff_epoch)."\n"); next; } } my $segname = substr($filename, 0, 24); if ( ! $args{'extended-check'} && $segname lt $min_wal ){ dprint ("ignored file ".$segname." older than ".$min_wal."\n"); next; } if ( ! $args{'extended-check'} && $segname gt $max_wal ){ dprint ("ignored file ".$segname." newer than ".$max_wal."\n"); next; } # Only add the file in the list if not already found in a previous loop/repository unless(defined $filelist{$segname}){ $filelist{$segname} = [$segname, $filename, $list->{$key}->{'time'}, $list->{$key}->{'size'}, $archives_dir->{$repo_key}."/$key"]; } }elsif($filename =~ /$history_re_full/ && $start_tl ne $end_tl){ # Look for the last history file if needed dprint("history file to open : ".$archives_dir->{$repo_key}."/$key\n"); my $history_content = pgbackrest_get($archives_dir->{$repo_key}, $filename, $repo_key); my @history_lines = split /\n/, $history_content; foreach my $line ( @history_lines ){ my $line_re = qr/^\s*(\d+)\t([0-9A-F]+)\/([0-9A-F]+)\t.*$/; $line =~ /$line_re/ || next; push @branch_wals => sprintf("%08X%08s%08X", $1, $2, hex($3)>>24); # DEBUG print all boundaries if($args{'list-boundaries'}) { dprint("pushed '".$branch_wals[-1]."' to boundary list\n"); } } } } } my @unique_branch_wals = do { my %seen; grep { !$seen{$_}++ } @branch_wals }; return(\%filelist, \@unique_branch_wals); } sub generate_needed_wal_archives_list { my $min_wal = shift; my $max_wal = shift; my $branch_wals_ref = shift; my @branch_wals = @{ $branch_wals_ref }; my $seg_per_wal = shift; my $start_tl = substr($min_wal, 0, 8); my $end_tl = substr($max_wal, 0, 8); my $timeline = hex($start_tl); my $wal = hex(substr($min_wal, 8, 8)); my $seg = hex(substr($min_wal, 16, 8)); my $args_ref = shift; my %args = %{ $args_ref }; # Generate list my $curr = $min_wal; my @needed_wal_archives_list; push @needed_wal_archives_list, $min_wal; for ( my $i=0, my $j=1; $curr lt $max_wal ; $i++, $j++ ) { $curr = sprintf('%08X%08X%08X', $timeline, $wal + int(($seg + $j)/$seg_per_wal), ($seg + $j)%$seg_per_wal ); if ( grep /$curr/, @branch_wals ) { dprint("boundary '$curr' reached, jumping to next timeline...\n"); $timeline++; $j--; next; }else{ push @needed_wal_archives_list, $curr; } # Break the loop in case max-archives-check-number is defined # Infinite loop might happen when there's a timeline switch but boundary WAL isn't detected correctly die("max-archives-check-number limit exceeded.\n") if ( defined $args{'max-archives-check-number'} and scalar(@needed_wal_archives_list) > $args{'max-archives-check-number'} ); } my @unique_needed_wal_archives_list = do { my %seen; grep { !$seen{$_}++ } @needed_wal_archives_list }; return sort @unique_needed_wal_archives_list; } sub check_wal_archives { my $me = 'WAL_ARCHIVES'; my %args = %{ $_[0] }; my @msg; my @warn_msg; my @crit_msg; my @longmsg; my @human_only_longmsg; # When using the --repo option, pgBackRest info will apply the repository filter my $start_time = time(); my $backups_info = pgbackrest_info(); die("Can't get pgBackRest info.\n") unless (defined $backups_info); dprint("!> pgBackRest info took ".(time() - $start_time)."s\n"); if ( $backups_info->{'status'}->{'code'} == 0 ) { my %archives_dir; my $min_wal; my $max_wal; # The latest PG system and version must match across repos for the stanza my %latest_db; foreach my $db (@{$backups_info->{'db'}}) { my $repo_key = $db->{'repo-key'}; if ( not $latest_db{$repo_key}{'id'} or $latest_db{$repo_key}{'id'} lt $db->{'id'} ) { $latest_db{$repo_key}{'id'} = $db->{'id'}; $latest_db{$repo_key}{'system-id'} = $db->{'system-id'}; $latest_db{$repo_key}{'version'} = $db->{'version'}; } } my @repo_keys = sort(keys %latest_db); for(my $i = $repo_keys[0]; $i < $repo_keys[-1]; $i++){ if ( $latest_db{$i}{'system-id'} ne $latest_db{$i+1}{'system-id'} || $latest_db{$i}{'version'} ne $latest_db{$i+1}{'version'} ) { return critical $me, ['database mismatch across repos']; } } # Only look at the latest PG system/version archives foreach my $line (@{$backups_info->{'archive'}}) { my $repo_key = $line->{'database'}->{'repo-key'}; if ( $latest_db{$repo_key}{'id'} == $line->{'database'}->{'id'} ) { $archives_dir{$repo_key} = "archive/".$args{'stanza'}."/".$line->{'id'}; # Relative path inside repository $min_wal = $line->{'min'} if(not $min_wal or $line->{'min'} lt $min_wal); $max_wal = $line->{'max'} if(not $max_wal or $line->{'max'} gt $max_wal); }else{ dprint ("ignoring archives for db id ".$line->{'database'}->{'id'}." in repo ".$repo_key."\n"); } } # Get the oldest and latest backup info (ordered by backup-timestamp-stop) for the latest PG system/version my @current_db_backups; foreach my $line (@{$backups_info->{'backup'}}) { my $repo_key = $line->{'database'}->{'repo-key'}; if ( $latest_db{$repo_key}{'id'} == $line->{'database'}->{'id'} ) { push(@current_db_backups, $line); } } my $oldest_bck = $current_db_backups[0]; my $oldest_bck_archive_start = $oldest_bck->{'archive'}->{'start'}; my $latest_bck = $current_db_backups[-1]; my $latest_bck_archive_start = $latest_bck->{'archive'}->{'start'}; # Change min_wal to oldest_bck_archive_start if ( $min_wal lt $oldest_bck_archive_start ) { $min_wal = $oldest_bck_archive_start; dprint ("min_wal changed to ".$min_wal."\n"); } # Get all the WAL archives and history files $start_time = time(); dprint("Get all the WAL archives and history files...\n"); my ($filelist_ref, $branch_wals_ref) = &get_archived_wal_list($min_wal, $max_wal, \%args, \%archives_dir); my %filelist; %filelist = %{ $filelist_ref } if $filelist_ref; my @filelist_simplified = sort(keys %filelist); my $first_wal_in_list = $filelist_simplified[0]; # identify first elem of hash array my $last_wal_in_list = $filelist_simplified[-1]; # identify last elem of hash array my @branch_wals; @branch_wals = @{ $branch_wals_ref } if $branch_wals_ref; return unknown $me, ['no archived WAL found'] unless %filelist; dprint("!> Get all the WAL archives and history files took ".(time() - $start_time)."s\n"); # Change min_wal if some archives are ignored if ( $args{'ignore-archived-before'} && $min_wal ) { $min_wal = $first_wal_in_list; dprint ("min_wal changed to ".$min_wal."\n"); } # Change max_wal if some archives are ignored if ( $args{'ignore-archived-after'} && $max_wal ) { $max_wal = $last_wal_in_list; dprint ("max_wal changed to ".$max_wal."\n"); } # Check min/max exists, start = min, last = max ? return critical $me, ['min WAL not found: '.$min_wal] if($min_wal && ! grep( /^$min_wal$/, @filelist_simplified )); return critical $me, ['max WAL not found: '.$max_wal] if($max_wal && ! grep( /^$max_wal$/, @filelist_simplified )); push @warn_msg, "min WAL is not the oldest archive" if($min_wal && $filelist{$first_wal_in_list}[0] lt $min_wal); push @warn_msg, "max WAL is not the latest archive" if($max_wal && $filelist{$last_wal_in_list}[0] gt $max_wal); my $latest_archive_age = time() - $filelist{$last_wal_in_list}[2]; my $num_archives = scalar(@filelist_simplified); push @longmsg, "latest_archive_age=".to_interval_output_dependent($latest_archive_age); push @longmsg, "num_unique_archives=$num_archives"; # Is the latest archive too old ? if ( $args{'latest-archive-age-alert'} && $latest_archive_age > get_time($args{'latest-archive-age-alert'})){ push @crit_msg => "latest_archive_age (".to_interval($latest_archive_age).") exceeded"; } push @msg, "$num_archives unique WAL archived"; push @msg, "latest archived since ". to_interval($latest_archive_age); # Check if boundary WALs could be defined from history files dprint("List timeline switches and check boundary WALs...\n"); $start_time = time(); my $check_tl = hex(substr($min_wal, 0, 8)); my $end_tl = hex(substr($max_wal, 0, 8)); my $nb_tl_switch = $end_tl - $check_tl; dprint("$nb_tl_switch timeline switch(es) happened between $min_wal and $max_wal\n") if($nb_tl_switch gt 0); while($check_tl < $end_tl){ my $check_tl_string = sprintf("%08X", $check_tl); return critical $me, [$check_tl_string.' boundary WAL not determined'] if(! grep(/^$check_tl_string.{16}$/, @branch_wals)); $check_tl++; } dprint("!> List timeline switches and check boundary WALs took ".(time() - $start_time)."s\n"); # Get all the needed WAL archives based on min/max pgBackRest info my $wal_segsize = $args{'wal-segsize'}; my $walsize = '4GB'; # 4 TB -> bytes my $seg_per_wal = get_size($walsize) / get_size($wal_segsize); #Only for PG >= 9.3 my $dbver=($backups_info->{'db'}[0]->{'version'}+0)*10; $seg_per_wal-- if $dbver <= 92; dprint("Get all the needed WAL archives...\n"); $start_time = time(); my @needed_wal_archives_list=&generate_needed_wal_archives_list($min_wal, $max_wal, \@branch_wals, $seg_per_wal, \%args); dprint("!> Get all the needed WAL archives took ".(time() - $start_time)."s\n"); # Print human_only_longmsg push @human_only_longmsg, "min_wal=$min_wal" if $min_wal; push @human_only_longmsg, "max_wal=$max_wal" if $max_wal; push @human_only_longmsg, "latest_archive=".$filelist{$last_wal_in_list}[0]; push @human_only_longmsg, "latest_bck_archive_start=".$latest_bck_archive_start; push @human_only_longmsg, "latest_bck=".$latest_bck->{'label'}; push @human_only_longmsg, "latest_bck_type=".$latest_bck->{'type'}; push @human_only_longmsg, "oldest_archive=".$filelist{$first_wal_in_list}[0]; push @human_only_longmsg, "oldest_bck_archive_start=".$oldest_bck_archive_start; push @human_only_longmsg, "oldest_bck=".$oldest_bck->{'label'}; push @human_only_longmsg, "oldest_bck_type=".$oldest_bck->{'type'}; my @warn_missing_files; my @crit_missing_files; # Go through needed WAL list and check if it exists in the file list $start_time = time(); foreach my $needed_wal (@needed_wal_archives_list) { unless ( $filelist{ $needed_wal } ) { if($needed_wal lt $latest_bck_archive_start) { push @warn_missing_files => $needed_wal; }else{ push @crit_missing_files => $needed_wal; } } } dprint("!> Go through needed WAL list and check took ".(time() - $start_time)."s\n"); # Go through each backup to check their needed WAL archives $start_time = time(); foreach my $line (@current_db_backups){ dprint("Get all the needed WAL archives for ".$line->{'label'}."...\n"); # Ignore backups if archives are ignored my $diff_epoch_stop = $INIT_TIME - $line->{'timestamp'}->{'stop'}; if ( $args{'ignore-archived-after'} && $diff_epoch_stop <= get_time($args{'ignore-archived-after'}) ){ dprint ("ignored backup ".$line->{'label'}." as interval since epoch : ".to_interval($diff_epoch_stop)."\n"); next; } my $diff_epoch_start = $INIT_TIME - $line->{'timestamp'}->{'start'}; if ( $args{'ignore-archived-before'} && $diff_epoch_start >= get_time($args{'ignore-archived-before'}) ){ dprint ("ignored backup ".$line->{'label'}." as interval since epoch : ".to_interval($diff_epoch_start)."\n"); next; } foreach my $needed_wal (&generate_needed_wal_archives_list($line->{'archive'}->{'start'}, $line->{'archive'}->{'stop'}, \@branch_wals, $seg_per_wal, \%args)) { unless ( $filelist{ $needed_wal } ) { push @crit_missing_files => $needed_wal; } } } dprint("!> Go through each backup, get the needed WAL and check took ".(time() - $start_time)."s\n"); # Generate @warn_msg and @crit_msg with missing files (sorted and unique) my @unique_warn_missing_files = do { my %seen; grep { !$seen{$_}++ } @warn_missing_files }; my @unique_warn_missing_files_sorted = sort @unique_warn_missing_files; my $num_missing_archives = scalar(@unique_warn_missing_files_sorted); my $oldest_missing_archive = $unique_warn_missing_files_sorted[0] || '000000000000000000000000'; my $latest_missing_archive = $unique_warn_missing_files_sorted[-1] || '000000000000000000000000'; push @warn_msg, "wrong sequence, $num_missing_archives missing file(s) ($oldest_missing_archive / $latest_missing_archive)" if @warn_missing_files; push @crit_missing_files, @warn_missing_files if @warn_missing_files and @crit_missing_files; my @unique_crit_missing_files = do { my %seen; grep { !$seen{$_}++ } @crit_missing_files }; my @unique_crit_missing_files_sorted = sort @unique_crit_missing_files; $num_missing_archives = scalar(@unique_crit_missing_files_sorted); $oldest_missing_archive = $unique_crit_missing_files_sorted[0] || $oldest_missing_archive || '000000000000000000000000'; $latest_missing_archive = $unique_crit_missing_files_sorted[-1] || $latest_missing_archive || '000000000000000000000000'; push @crit_msg, "wrong sequence, $num_missing_archives missing file(s) ($oldest_missing_archive / $latest_missing_archive)" if @crit_missing_files; push @longmsg, "num_missing_archives=$num_missing_archives" if $num_missing_archives; push @longmsg, "oldest_missing_archive=$oldest_missing_archive" if $num_missing_archives; push @longmsg, "latest_missing_archive=$latest_missing_archive" if $num_missing_archives; # DEBUG print all missing archives if(@warn_missing_files and not @crit_missing_files) { foreach (@unique_warn_missing_files_sorted) { dprint("missing $_\n"); } }elsif(@crit_missing_files) { foreach (@unique_crit_missing_files_sorted) { dprint("missing $_\n"); } } # DEBUG print all archives if($args{'list-archives'}) { foreach (@filelist_simplified) { dprint("found $_\n"); } } }else{ # Get the exact status code per repository foreach my $repo (@{$backups_info->{'repo'}}) { push @crit_msg, "repo".$repo->{'key'}.": ".$repo->{'status'}->{'message'} if $repo->{'status'}->{'code'} gt 0; } } return critical($me, \@crit_msg, \@longmsg, \@human_only_longmsg) if @crit_msg; return warning($me, \@warn_msg, \@longmsg, \@human_only_longmsg) if @warn_msg; return ok( $me, \@msg, \@longmsg, \@human_only_longmsg); } =item B Check if this script is running a given version. You must provide the expected version using C<--target-version>. =cut sub check_check_pgbackrest_version { my $me = 'CHECK_PGBACKREST_VERSION'; my %args = %{ $_[0] }; my @msg; my @warn_msg; my @crit_msg; my @longmsg; pod2usage( -message => 'FATAL: you must provide --target-version.', -exitval => 127 ) if not defined $args{'target-version'}; pod2usage( -message => "FATAL: given version does not look like a $PROGRAM version!", -exitval => 127 ) if ( defined $args{'target-version'} and $args{'target-version'} !~ m/^\d\.\d+(dev)?$/ ); if (defined $args{'target-version'} and $VERSION ne $args{'target-version'}){ push @crit_msg, sprintf("%s version should be %s", $PROGRAM, $args{'target-version'}); push @longmsg, sprintf("%s version %s, Perl %vd", $PROGRAM, $VERSION, $^V); } return critical($me, \@crit_msg, \@longmsg) if @crit_msg; return warning($me, \@warn_msg, \@longmsg) if @warn_msg; push @msg, sprintf("%s version %s, Perl %vd", $PROGRAM, $VERSION, $^V); return ok( $me, \@msg, \@longmsg ); } =item B Check if pgBackRest is running a given version. You must provide the expected version using C<--target-version>. =cut sub check_pgbackrest_version { my $me = 'PGBACKREST_VERSION'; my %args = %{ $_[0] }; my @msg; my @warn_msg; my @crit_msg; my @longmsg; pod2usage( -message => 'FATAL: you must provide --target-version.', -exitval => 127 ) if not defined $args{'target-version'}; pod2usage( -message => "FATAL: given version does not look like a pgBackRest version!", -exitval => 127 ) if ( defined $args{'target-version'} and $args{'target-version'} !~ m/^\d\.\d+(\.\d+)?(dev)?$/ ); my $pgbr_version = pgbackrest_version(); if (defined $args{'target-version'} and $pgbr_version ne $args{'target-version'}){ push @crit_msg, sprintf("pgBackRest version should be %s", $args{'target-version'}); push @longmsg, sprintf("pgBackRest version %s, Perl %vd", $pgbr_version, $^V); } return critical($me, \@crit_msg, \@longmsg) if @crit_msg; return warning($me, \@warn_msg, \@longmsg) if @warn_msg; push @msg, sprintf("pgBackRest version %s, Perl %vd", $pgbr_version, $^V); return ok( $me, \@msg, \@longmsg ); } # End of SERVICE section in pod doc =pod =back =cut Getopt::Long::Configure('bundling'); GetOptions( \%args, 'command|C=s', 'config|c=s', 'debug!', 'extended-check!', 'help|?!', 'ignore-archived-after=s', 'ignore-archived-before=s', 'latest-archive-age-alert=s', 'list|l!', 'list-archives|L!', 'list-boundaries!', 'max-archives-check-number=s', 'output|O=s', 'prefix|P=s', 'repo=s', 'retention-age=s', 'retention-age-to-full=s', 'retention-age-to-oldest=s', 'retention-diff=i', 'retention-full=i', 'retention-incr=i', 'service|s=s', 'stanza|S=s', 'target-version=s', 'version|V!', 'wal-segsize=s' ) or pod2usage( -exitval => 127 ); list_services() if $args{'list'}; version() if $args{'version'}; pod2usage( -verbose => 2 ) if $args{'help'}; pod2usage( -verbose => 1 ) unless defined $args{'service'}; # Check that the given service exists. pod2usage( -message => "FATAL: service $args{'service'} does not exist.\n" . " Use --list to show the available services.", -exitval => 127 ) unless exists $services{ $args{'service'} }; # The stanza name must be given if a service is specified and 'stanza-arg' is required pod2usage( -message => "FATAL: you must specify a stanza name.\n" . " See -S or --stanza command line option.", -exitval => 127 ) if defined $args{'service'} and $services{$args{'service'}}{'stanza-arg'} and not defined $args{'stanza'}; # Check "retention" specific args my @specific_args = ('retention-age', 'retention-age-to-full', 'retention-age-to-oldest', 'retention-diff', 'retention-full', 'retention-incr'); foreach( @specific_args ){ pod2usage( -message => "FATAL: \"$_\" is only allowed with \"retention\" service.", -exitval => 127 ) if ( $args{$_} and $args{'service'} ne 'retention' ); } # Check "archives" specific args @specific_args = ('extended-check', 'ignore-archived-after', 'ignore-archived-before', 'latest-archive-age-alert', 'max-archives-check-number'); foreach( @specific_args ){ pod2usage( -message => "FATAL: \"$_\" is only allowed with \"archives\" service.", -exitval => 127 ) if ( $args{$_} and $args{'service'} ne 'archives' ); } # Check "archives" debug specific args @specific_args = ('list-archives', 'list-boundaries'); foreach( @specific_args ){ pod2usage( -message => "FATAL: \"$_\" is only allowed with \"archives\" service and \"debug\" option.", -exitval => 127 ) if ( $args{$_} and ( $args{'service'} ne 'archives' or ! $args{'debug'} )); } # Check "check_pgb_version" and "pgbackrest_version" specific arg --target-version pod2usage( -message => 'FATAL: "target-version" is only allowed with "check_pgb_version" and "pgbackrest_version" service.', -exitval => 127 ) if ( $args{'target-version'} and ( $args{'service'} ne 'check_pgb_version' and $args{'service'} ne 'pgbackrest_version')); # Output format for ( $args{'output'} ) { if ( /^human$/ ) { $output_fmt = \&human_output } elsif ( /^json$/ ) { $output_fmt = \&json_output } elsif ( /^nagios$/ ) { $output_fmt = \&nagios_output } elsif ( /^nagios_strict$/ ) { $output_fmt = \&nagios_strict_output } elsif ( /^prtg$/ ) { $output_fmt = \&prtg_output } else { pod2usage( -message => "FATAL: unrecognized output format \"$_\" (see \"--output\")", -exitval => 127 ); } } exit $services{ $args{'service'} }{'sub'}->( \%args ); __END__ =head1 CONTRIBUTING check_pgbackrest is an open project. Any contribution to improve it is welcome. =head1 VERSION check_pgbackrest version 2.4, released on Fri Jul 05 2024. =head1 LICENSING This program is open source, licensed under the PostgreSQL license. For license terms, see the LICENSE file. =head1 AUTHORS Author: Stefan Fercot. Logo: Damien Cazeils (www.damiencazeils.com). Copyright: (c) 2018-2020, Dalibo / 2020-2024, Stefan Fercot. =cut check_pgbackrest-REL2_4/docs/000077500000000000000000000000001464170754600162335ustar00rootroot00000000000000check_pgbackrest-REL2_4/docs/img/000077500000000000000000000000001464170754600170075ustar00rootroot00000000000000check_pgbackrest-REL2_4/docs/img/logo-horizontal-white.png000066400000000000000000005227461464170754600240020ustar00rootroot00000000000000PNG  IHDR b栈sBIT|d IDATxwew}wn@I^c1=fya63>&cX %2![%$![1 w$+TUUn8[Vodf"I$I$I$I$I, H$I$I$I$I$I$I$I$I$i?$I$I$I$I$i?$I$I$I$I$i?$I$I$I$I$i?$I$I$I$I$i?$I$I$I$I$i?$I$I$I$I$i?$I$I$I$I$i?$I$I$I$I$i?$I$I$I$I$i?$I$I$I$I$i?$I$I$I$I$i?$I$I$I$I$i?$I$I$I$I$i?V~lv8TG~u$I$I$I$I$1p' A(ZqׇC0q'I$I$I$I$-+w {>v9\f,}[tNim؎BmU"ɍŠg{}"+]B-g g<D)mVuV:jjE>$I$I$I$I*eNߝ yAюL ,{~\m ]\s,(ܴu ;^+O}“(YÏIGϓ9c<Ԡe&zl߻>dw7p掛JRZ 22Z_|^Wq!;x? z(٘Y픙BϠJ?lk'x3_ uϮk.䗐Q w$I$I$I$I?;}W&ѹ2a.~#g_In߹JJia)e!\])Tn3*Uݡ~cscVH٥[翿huǸ}:ڶDDʕnZ:w''s"ǟ:B+P$I$I$I$IZM 3hc]mlQ mj=IGGQkv-rr7/PK}O׵,=O! t1jἚ. :>="7 #]irZ2*rgS\sfb ֨fUhMvVa+C;` M{<<ڤzѲI$I$I$I$I;}Wظu3g;vm,19; -vK;NtQڢ;+Q&t왲wn̦/q˵e.ޮ,69\C6be]縌xq ~)O;a/H$I$I$I$IhW@QՄ$5w~}lz\*IdBĢ@$׹t} 8؆P*Pra;; >aTm!6ᆙl{-]]w͗~Uvv"{|[ߒb-]-KTJN.3תYdMjyOz}yޓaCxo.95$dW(^MI$I$I$I$e eͷ7xƻmk yJwBceGooiq^p͖\yF޲o nqĒEMjεRJU;wuDgdSxqǝ2ҁwuIKRd=$I$I$I$I\VKuI-%7 cxʬ񭋘:pzЁu~' nKv ls7l\MoiuC^PnmfJkëgL}" JJOcNcN#;5M1 Mvn?'J$I$I$I$6Z#k6>Ʀe;n*A!Z1@Y-Ƌ&=A;wpm$An=^$k3G?nqyVv޽m Jٶ{9͟ƼJ>7Bk}%6^v벍1 GFd-ǵ9s~“lm8|!<`aIǞkN$I$I$I$I4w6] /:ZGY) tC.K!VAYD|) D&}&$8.Pj74eB+Dmvmk\ԔpD]v$E!jFP;X'}iSaF& d5%u^zatIJDI6vM-*z쓡tJ$I$I$I$IA&AL> ξܾsE$cJinlҘ6#̅VBR A}(IS24AW YQu ;ZA ڵ1I+ \z=\[WIZF=KmMwzRs eD$hmII_P I$I$I$I$g 4i͜}'MIEeeq1QZxkUk1ZC_DGC[Ѱ&IaD}2bDCoZII$I$I$I$i1pdiinfBso IzJ|D`$I$I$I$I[eMwdi wCe7^.9; #EPIb0g$I$I$I$I{1pĤΥr'Dpx%a624` Y5%2}7H$I$I$I$IV wL 5E&Y`%W~6| d+I"q&ʸL$I$I$I$I$Ifn5JȨv D!r}էٳalڂv5%=AF]$I$I$I$I*],ƻl:޵LnO>)K@d:*} h2դ҂{$I$I$I$I{1p . |u&F Ab : qxAP~6I$I$I$I$IV<w+Z%md =a=g^IJۂxm|l欲N-<"#l{U 'C PODfLN9Jµ۾E #I$I$I$I$IsV+RZ ddv͗Kg1g^IFt#t-p\!Ltq1I7J~/~?s!PFI$I$I$I$ b HJZ [7󗗜;n cMھdVQ[bD ?s~2%I$I$I$I&"H "l)ܾ;6,dI+x=n@+Y #`3. D]BFi#d+dƏ>ek3l'Q,-$&@$+?/"-'h7)Nido+r'I$I$I$IC,>\Hlhb;K7nབྷ|mg8VJd*T,#-"W% Zh1&jp!^:z)Ӷn˜GPHJ͖e܍JJ w$I$I$I$I:{eLHM : صp7c\pC]ӃSH2IBottY3f#9~:}.(dJe8DZ Q$I$I$I$I:{Em% Mwq g}}^H #\҃WjO "ڵqƣZʈ'}7rĚ.Zc AќN - I@wFJ$I$I$I$I w$C@vo[&MQѲ %3D)3vǎRkOD/PE=?kBƬYq>HG@`IGQ-T9 İ$I$I$I$IxjYa;#\qF*@TJQٓ]@m ;=XQ[O5Ke%ke-?罊>ƭD %>Yb>>ܲ6jJOv$I$I$I$I:{w ^ϙW}D&FKeDIFْv==nV]0ɸv?ˋO{dk#bf7 Mv%i]w];OvĘdM)Z!:2:p$I$I$I$IRH9\4ozhI#XvrH  T`ha !W*P.j@)P+AGOE< E`%Fs&d2*߳~eiǝJ%is [юBxdj7罛]`DO(ѵC$$I$I$I$It 3pDDd@"v1A y۶A_- Gi&/C\O1ZH*]@dKft5D?Y+]y 䣏[Hn:CD :F!k//9{(Yԓ$I$I$I$IFJҺL(IJJPuv>t'/cT $jҕBaXƴ&0.cQ]j %GM ? KB֍ʌN9dkllٿfV8BeHmzĈnN$I$I$I$I:X[*ZWl%[Bw/cW_y_<{`!nj(Z)g2ӪV(PkjO2?yֳ;R+t1 ya;ha;ʈҺu_J8傡-*0jmsDJBJ$I$I$I$I{)¯p1NU7n_<BT(kvP+1ZZ =~W?o={jQڈO ݌O$])D&B5iUS p0zC[\$7ȫ9c8Ss݃ DϱokxQJ*=ezMwTޗ] %gϠPm$I$I$I$INʼ!6626"`n[ugq^1髻R dad&?.|>R8/ER,g+ܳ_1G>wx2"]mmqukJ-D.P䣟ȟWx!G r+{˹}8cc~e1m'I$I$I$IInj.BxeOpƋ:'c+dD~ AkLJ)r!<17q ki O:D䕿[/:RKG$B-IԠϤtPǕ[ø$Aek ^Ÿp,I @!.=_{9~8w!#('h$I$I$I$Itt4[vDfn1mx _ٚ`QȎlA]4Fʞ{x_1IAFׂoI$I$I$I$u51fy$*I+l:wGnMtzb\Zcپ!ꖙ{Y𮏞w^ܰc+y+Pg.:Yv,YtӇ IDATa7.!"9Tl` #8raW'>nqRX:-0zO;vΈB&D X[I$I$I$I$I MRi Bm;/2ݐ^L@W+ nDI ;a2*vI$`7vxTοg'WqQ s-9mYȄ׿8/P*LFY9btoyůr1Ƿ{s$m$m7݈Jt [7+^IPL2ƌI)#‡$I$I$I$I ܵ15Q= yιS e8*YI%@?MlmbZ&\z'3)Q[h ˹~VkrI}!(+{أy?L铮 ]Ȭt̉ak6&I|oLJF!qA0 q͘$I$I$I$IB\.u_狼A@YCè˜dA-guāInڵќ햚5g} w_sұO .+D2^TK[f/y y'_g'% cRyN5=]_&[;d*Z{/;.x1kHP/X(} fO)$%I$I$I$I 2oZh`M;>{ELFb4 2X7$2shm>N>slApھSj lZ;S SE Irݔ's"_攣H1Oa~e +^vhAgq馷dӶƒ-?a)or;Ln=|?n=I$I$I$I$iu[} w͚Fh. %lm>t公t]$JZ&p8aknX{>/Bn! #e-t?&ݰ]2g7o:/yK~Oy.A&+Lw5Zt +_]?y=Ӧ2bQNk #(Zś}}&Y~[WD5>>J$I$I$I$I Buޕ!8$-t?_]Ǟ^5(R1ӊ]-R ·SOxҾ0N9dJ)dwSLG 9fM瘷;zHzJtCE3d٘52R8#y$iS#rf bfo_\t&7~{+.@ v:kGpжBI$I$I$I$w0 Dt@p _ὗXJe-*AGJF"q${mJP{@3 *KR:.`G7|]wkr(hmݣL[ډLƶL#kl찜7ܹO(FT:Jqv,LRʓ9q$I$I$I$I`/p7):nv|m˵d&kbD_h#dDk5+@(IWnfSi-8uv2,CdnOeT ]e!k/;򖗿9̲|vv8j l$Z>>MeҚ2!`GζO EO aŝ$I$I$I$I`j8*{g~njgQ븍d P ƭl6WֳC-"Skǝ:]֡@Q>. d7[pMPdm:4E[oC!'眬Ôٜ.#3. ]$FBmmG f񤣎P$I$I$I$IRZ (y./t(@ "3d1t \~ѷlZ3N8.!|quk1_nEsvB!}6wrm}j&,C/v5%KNO"{~9Skώ2GBd"*H>JGGv3oNl$I$I$I$IҁnՍݰu3g;;vSJ"[𧋄~/֡}sZ&?`<\ ۷$ICjW͊KPfrx=5V뻋ͯ$I{- UBGdɛ﹛\--IYD <)xɩgCuG+>;ם @%ɨDf?s%;Jo~|sMѵrI-X+t Wg319gUÑ> +A'd#IM:[;a>$q0m"V&Gd $Kzi̞DGٺG$ޖQ\+*Cm$0<u.gs7^T_a셧/ Okt24S^@]ۆaef3 7v^' ZIњ/Ɲa0^|~ _%IJk1|yM߫ka$IlU\L|@kK-U)crRKXwk^I/n:3gQ_Xw&"0.̀J%39sGNZ7>m4* tm6w!Of!JG}7rǵ -|9#11 a+kG&;;''ܽ>4wDfr=~R{z?1v)ՄTm;E/[2]~7Ԟ/>n.Z)Z2/|+<2 ELc2N')Z׼uϺ/=D+XѦ#wv /katYtjP"XHf=eP(1}su^_'1e` _y=W:a6 }6vB];8O M˧P'9+wӎ;$A.5uŧCAeGO3;O{6>$3i*a2VxO.=g>)w$^ӷo҅pg]rE%kE̽ PlN$iGYx2*;.>9tyAFWVeYU> Z@D+jAGD)gÿFM,Vy*F׎k 4=p;/羛knƬn7.goB##Z?Y]\z$% CzXL2޲Jμ'Y[.>1sL {WIolDZo"m҃QheϺ탬>1 NR3(}\=_IG=}jcvYVٻ7~JԤejys-fl>I$I$I$I$-,d(>*xxw=w3 0 0 0 0 0 0 0)H), 1Ǥ\jn?o.<D<"a8<#Wŋɝ /Q-?# .$_ēϿX 睪C<;R 9 U5sށBl5 0 0 0 0 0 0 0 0WV9}7FTUwRָLrqxԜ#˕gxjϷP'T|zy{y/jSx3\M<>< KEpYp<}= /N啫gѳmC(;aaaaaaaa3IU]Ĕyy+}/#V#_cۦsBFEy.A:5$8 *J AGB?^:G;B;"okoaaaaaaaa|&Ka*:Q!٤b;K(k\.*ȦO=yBt^ 5 ƭ<gu7=̶[Z2 %"h|'2 u;! A)y@.N zAE#>p! j\0 0 0 0 0 0 0 0 c G|G\_S#!}zK&OLswZzhI). uUqZ֩TZ*_乃/nUSJiH?pV>/!̼^13ru;}ü{=hGeطpqG{xG$sk`nOHJ;p4w]3+⛔faaaaaaa1S?UɥNsR2Sˤ\t (hf)A4:zԡ*PQG.EtT.ȈT8.ygv{[ƒkZL!;t!Sdԧ'LajQPyٶåVH [%]xŋ'S {p'wA(Ys oo?ҝX`raaaaaaaadQ]8R!]'WjyN>7}FmvW^{ꒇt"azq@.i"5r5$x'gZ^'PDTɑHyUǩ'e͢5M4ۍjKkpj>rU2q/ócw? hyaaaaaaaa\H%cM@2`Vyy>kiLP*"E"T|;AbX6.ZG!n^xcf@wT;;y* xd aa 9v7 NwW^txK<̶[xe-{%shH+b՜|kH,k=BRs1PU9$dNEcضq+^tVaaaaaaaa3 #MbMQyR O t{!]gz[ RtV6ڀwWT#|bcvMXc$7ǯoPx NtvT~b,êS100@E'< 0ԙ:Dco! s8!9tҸuZ:;Ď'S/d1 sxF*Ȅ$xUUPW%hw'{Q5کmaaaaaaaq%ij( ڌџll-Fcw//:rgʅv|s=@%r|> HOg[?Ǚ={6ι3:2!8o?'tvL Rq#4~t8Ie|g>)rµ. /5Q'}O=' 䴪ȥLNy}8jԵú 0Sy@ K)[0 0 0 0 0 0 0 0 c#>$UW4 !נ:ě'L.X(Z!HW@JY!Ϻ%(VHI3 *8+NaP7MϞHԡf1t=-/hg{Z-^T\YMĖQgJK^ᮆpK0^0{6s]kQo}Z<p"BK~?Yp%ӲA!M@'Ug}W|=E7 0 0 0 0 0 0 0 InzNeBObbT2z2I3qR,>ԡ^~ ~AD;pYu5vB,1=9^f%xltK̝;\KHŚV=,Nb4ɸUL0hͼcamd~degnS7.Vص nP>D֤aaaaaaaaS͔TW;k7hUw@)5EO&ɧL8En3(#BoUǵL?ǧ?(S2kきw{k Sc\#)d *k&9|M:: ST‘S|kO3KM=9ytN1Z1Z;BlD1umJ>#̟5Lp!u^u)#XnNB8k>1njZQ%ů h,x;OO=A@lJJ;Q'rPmYaaaaaaaaSͤ+Dw3}]AH$n3GH/ZZ&3S,\5*tK5"\Ŵ_q|Pڅ$yԥ|0< a|9hpHb u4CvVh=ӆhz±0 0 0 0 0 0 0 0 ØjDpw AHD8 %vd"}źūSwxє6E>Ƽ9GTJ5М#]'9?OuWsd[b G'`'(ir?0 0 0 0 0 0 0 0 øکMAOv:(8:BCWaޜk[~;WnHum5r7XZ̷'8:yd.:z}M/yl1Gp ǟK^n~84Z!\t}C YԤ ^RB+uLz.)Ǒm|k~yj^82 EQ$sTdʫop!'ZmV0 0 0 0 0 0 0 0 Ør&.KΕUx഼D/ G};C~wǟ6jū+/CX0{!?.Z֘JWwEP=NCԋ4xEI2n8͞ÿ~Cg! '!+"jUZ?<8ѓ ,*81C}?0 0 0 0 0 0 0 0 x;3%*}[WLy5y#JB$Jr{maujR3띪?'w<ѮvTmV='{6?ZhNA.sB7CrO͹P7\k҅KCζM['ӯXQ92yz{''u, 0 0 0 0 0 0 0 0) EsU%%<2<4 KY,iii3] W`4]}|b㴞=j:MEA "f06_a~g>9אǻ ΋G|">Ow7)7g}7:elaaaaaaaaW3S"ӊ,9Ub偺|PE\r uW8<E6 0 0 0 0 0 0 0 0dPazTD7pw[q-N ëL;[/^i;w#KfTPeds_{B;OQ@2={>W]ei9Ν+n]ׇ3 $Q-Ǔy%>4pRBJ4Q aaaaaaaaەI nhIB [ށ FERBxyܻf Z!NQՆkv5]bl]"HtIc"NXA` )%we  cLU6򌃰;;Cr nv (Ȋ 0 0 0 0 0 0 0 0 [M-y22^nGE d|}œG}iRztwhNkL)lxũD-N~LBlɍPw|b7Z:]0 0 0 0 0 0 0 0 0JLp7ѽ(hؘ/qDP Nw9)fȸ;D(fL5܁Wxo=.Fe"\dv :Y;/(?8ǟz#P+c*B.cf‚/Ewe/?Ǯ{Q. ?Ydb;gJ. (S1ʸ`)Tm/4t06ֈb0oc~nQ|QTifysq~xp8>f׵u 7Xf2帰^[R M3lP'çJFj~b(1g9>_]<[.\Qq9Lhc|s>a}Xk~xc=-9y4Oy{\6Nc_ULs'K<::D&Pj:(ڕI3^^@2Usur RURtn{ǻP -G}s_ѿEP#RUT"d=}O>gxl|'Hg !H/bcOҋqP^:4vW8JPJh"r4b2†(4]aa]xAW@B ju)5ԥԷFSAeX1~B:GVB;gu# T< E; e07K[-)-7 /ZuwTϣ%3ƥzdcoe#B*m*B]yx{BQ C2^ڠ%BOL/vxEJGgic] \yZRVxװ~,:.Hd@׉5sW+FIm )Bl cvNn>^(75!eE]a0nP=9mUi l$+"tRTcÞ }X2rܮWհaܟ%uy0d1S)9b3 xN4f@9Ɨ8~j?QƸ'/ Ɨ_EU%U:VeH1xN*q7J?0^$G îj2JQv!RZ֑7å׎+qC]JՅJ8<ɸ,aW6֩6\sWy}W(<hp#*MTI|ucT)}4G.X SbhTb[ m%\Ë0tŚ1>"R`|pU:8- I'*Gc7bʸ:k3]H9m+#ײҀAcym4/+bWOҟ!ZOE4qpeٴJzd#ǂynq9|BOa <"́{yLRKƺSCND*ouꛏ͸XV?NvB\z/M>sI8llTFA`4a<* yw_wzUi:غf 5N0blh4ˑaUǠo*Ku<5dkqJv8F|w'naj5fb [&~Eyx`xC#X0~LJLuv"v6R iwC!Jڻ !,!ha:AQ4@4.3U\ҐFRY (-jil3{3ot'Җyx_Gq҅׳l".Y|n]W36Hq];]Fұ06:r$:wqAYゐt9ͩnZϴSxg;}@ۙ6ʅ%)EQʼsYt5skpkYp K,*J+As Dr7-Lu~;2}ŐE t妜=ɞNwS]tcuvp~4l9 3վdE,[:XrXt5̾wX  #ʝ.QSPl1iTpET]h젣3'@j+_u0 cقE,_ĺUiZ PóS(ǿ\B-$K_tH J64-.պm\tٖi8>.ԩ]mTi;Bw~gC;B5:Y9MX: WruaǮBKsuX,NMŢfTF)DtiQ/mųd x.}ON.y]cv\z2hvK.bV.ܸt fekдsaMPV tYe}p2O~3b8TO]=gi=NO~3otq,st;ɉɹ(#âz[UX^vm\\q\q*s˪oI9}Eԍ? uGpģn] $ױ$ihrtQF']b|.DJό!SÇWcG9z9f:zp,{rn4sx_'r[tj\Iᘙ׳Ajl^LxϠY3fDO)BiщKZȾsL_xg{pTW%U*5犵;5.} 7-YǏ-6p M,q=$C1G|ab4. C|;LMnl3&iG &xvؠ9պS,T= )h(ONv|Ohk|=;[?̒B..PQ,"<ܗh>|V?%j۱4U {;ils,g8Kuш$ܪhw=ޅ zi۩*G Q3 +7r\k25_5k}E`fƏDp \0Jkzn9{fC;q(NĢɇO!,p!*-BQDbsf_aD0Iݰxe[gq$xд\o:g _$ Pr|LRڄ~VZzSfB H?^7y?Ƀ1u47~*6p#Z74.PbIHҌw"pPr(FfۛLWYNvh 9C9 \Ο{XWn`قEkZڦT@Ŷ-5r9 1TȢa,\% /pű!KS1vT5|4QD_ʢ* sL֢Psi1|cy@t_{;{ ۏp2qXuKVpuܹr3/s s@MB^)n"cLL0F`fID╊ irvy$YJ;n岦r[va;!1@\JGc-*9AƺE=8sbvpYx%W5?Ɲ+qp7!J w46Os]"j܇ni={;yq#@UqEReyK*,n2;sS^9y0UTBLFsϚ+X0kWE7f ]=\N.|s@!P ͚ˢibzz([8FkW'4΃SK]RA 7AX__M2[|3xJ(`<.Y͚EXdnXɆ+ B/RɅ3mh/uOWa|(Pwvcf6Gt׎ma?Ś1O> NN\OWn}qZaNtNF CV_ }Q-O>m;r7lVr~o(4BlU֣vVwtHWqCDТ98o},e"wG`p˪ _-7iK.*CraTuf]RGsXJ7dZΞ=Ʃg9u 6%E;/؀e"^ $LC]J{wv^x".lbU\fJ_ Vq怆!i ;S; /8ԕj\'EF9˜ #T(6"^o_/GI59G[) y0=0Ϯ<1Y$){f?gbOxD3ćyo0,ܠ/ ?}/ RVpCԥFӟ۸z@&B(B?r!v{/㼢53..ZtAH:\&sCbz8Irsp hhwq%7,b}z6.[W/whլKƛ'%MDmoex5x\.8@7l "LMi-!S8%Xw wD 69r4w(>7{o{ma;h]T&!QL%1!:̞üьj m,:i|t*fI>y^ ;ԇ+v'! N;W5wr ,=aQ?gwWYlR53Aotbx;p _ϣG"QA$'i3X&Ge@0zh?s)L==ttÎPT-r_|#Wl`y0d[I%oHz 9qօA; rDH%^ټjĚ+XP:"_mS y4\.Zݎ'ؾtE w>Tx]^jwᕖxsB=>b߅ys e u,#Ja&@)Sl穽,jxNJ[v]W q &O:A$il cX UyQ'8 ®\e.b8uz| c|Ԕ{tdǞgy5ި񨺸 _]ҜiAZ|ms{H4k]G9} {f}ܾ䯂BUB0 }Q-gNpxD ݜ sbc_Yֆrw/`h_HPN?+TE0c컥4x99Xx[gܾjC(S505.*(s'oȸbFRL.w6i]8ct N/N a;Pt8ޡ^tbwX6iw.[WgShشQƌpaA`?Ǻ:PD}NÜUX;.wA;? W:e~դARC~^<*/ q|yf)$WZsJ{RL38rB*qafęTQb,48®?v^ko )9$HQD5m2:26.l0RVQQDK8JG~<5hpқgĥŮH.Pȴ|(8NtXC=q=;T b8/`ifA!z/R᪥YxJ=eBaᅲ{faw!KյMܱ|=b&4 7uRӸ5k&ㅣgOsإ0@Kch'4 z.rML#W1ʘNzw3]X>oq1/vT|\v{n-Tu`:G<҄zilP#Ez/_߻\'fi\ gNr/(EyEvGNvʹzBU)Өp7wDg8C)/y^> /x`=,Y(~v2I *7Ϯ=ز'&.N"zE(O;@7 ߍC݉~$):Xޓip?9sg_{o8nY&-&Eo$xNvꄖ6v{-ijw2vLuZXH.C*]qTR?KDB`+w!ͬrjd>կ\We#Z-Vr ܹr#t;)<&)vxM:<=|/yy,H!;)1f.dbO BJܹrycPT)< ñ!v/.H%!1Q%!VҖ?xWϧש+<(Tr{vٶi+ۼ WAD]:3`>N9w GŁ8fHw 5G.m|)&0v>''mqPXv3eK)%}G_7^A|E\$Z~y\wOb4֘rRP 7V>i@Ep⁾<g"7 zyu=8/ s8J=9 ./7%ASJ naӆľx}1> 86*RIv}Nf'}~k7(!ٓy!\TSy|+tq'j.~q:DŽc)$fb4 Fb>\!cH3rYalNtO7]~:Bv:mbլa%X|pIp)[ /꣨pc dPڶyyw$ƬrGvɧH}:Oo\~i e+h*9~TLp7eKرIMq8':۹ijl-'{6ynz1TɝR\ %tO'WfJZ:O4C`\+Ȩ`idױꕿ/m8C,ѝQvQ.}sjُwbQZ&5M]ʬ,kh4! Qe(?/Թ8֋c82TiPdyy/G+]̺bӍ늀BZkx ID<}o/7泥@RܖŶD],1 uw̦i1!<{ /^9)}X }Gp8|ؾ[kocˊ۸{-,_ DEPcw`? v+DF3ax}xצJ~gA <*uo\wطFWgƯca!/k'|{dLB~UYk0ZA% *2@ժjm﫯j[E:^*jeHԶ DCdJrd@2p &眜aXk' CrE|%!jIW?G|[LHQ} OUj1.|?oץ;wxlQo_ǽeŎ5(+)I֮'DLq*R:ŚF mR,ޯ0  Orqj&ԝR UU7Wq<}),Uu)xxu1I~ٷHM1>lM,qDg"b\MuUHɨZrT_xKhS-3nEӹG{=5j԰%ݽv&Vl_[ :%4$b43(Ȭ굨}& W+^rwؠ^,#K1j$Y~)/`ܘ7r[.b9s̨ёeu^R*+dJHæk\hILa%)T`rk^ŕ$}n$ȇurSϦl,ZtܱgrocРR F:vTIb w@_^> sbPFmlÓHĮ3-<-~;֒v;sQ %Liia 8Qi9D&{bw IDAT75v<Jd5"9ٷڥU S* ;:_bu:IWǸoKbx^jV"BOK}CHdBsLTj}~1/'ɋ~+ƿbFR`"Vߨ)7)B±8Wn[+J^7 >,IUx? \Zޓܫ4ߝd|(Xa96,p qotIuA{Q] tbC} R%J"87Ck&| / 'W3dkJZy(Z})BJ(FiDT*8W'd󝒮KTQRo[m|n9ؓ/^ʘ >v'ߩEVl[=<³[W"GeFUvgVJ26x~ceѿm"Ʊ)PȈ&$%N[֕O!|Os71\}3_Z_k! en9O>13nje[.cOjN;3PL}%]/ϸ;7!2gf'6fD( [9Ms2Hvh9FXJ556'Wv=O=U+G({@sk^+ mOs^y~>:> w| ]mܷQk3$: l{Ǥ ^W[kuvn(>?{:z>W-U]Εo +pbdG3VVl_3>aw거PpME z+|}F{ل 8Zضmۊ:Hٌ;ٜ͜{Y*,%C X.2}GA%2͢OL5^\†i81 mH֨QCc?@rBKh]9bJhF6\P<~9OlٜL|;s!1ԑIH֌jT&) 㫟O̠+i I{ٱ'%?展K !hP:=8DyQ2Z+I4vHwaYyzQHý(vq?#^·.QG.iUΝP,I2)x.bc6/ke8GP1x6wl;Ow r1:!ac3TcRAؐLɷY8| !G O}zĤl'eMCnCqQIXL+HsBDر%,ظ)،fHj81do[_N:Wn>y{2w"a|HqƲa{K|,,O~}7K_曏V-3(geIPr}ERPNd it{4S;]}]ϼc\<$H( \$Dx>qqx}o\e߼7=_A*G#}}kPD<a{g;-y9/FrȅHsֵmfm|g,Ɵ&ƅ\}> 0QSEt.&MM[ cMBq$)^=&W$ ?~e96 !Q,y~/zGS:X(Ro[=>³[W!j_EkMر"䞨[z7<0Jxe%H}!n*I渹TjHbF4 +sV?=OȭҠ8]q^Q8P6xF*L2&(<0n_Ut3JC@ư8ɌATYU_>؇WB1aE]biHV>)],#Z癇كl#D /h3dRG{T]BfЎuIJkNV1L_6{<Ğxi.( ͫ~2/,߶7 ?Ql Z%Ϳ~k2q%f_ESQ j^΍?緬CЎzQvt䫏|KϸkGsđqriC\A6pN&.Kު5^|E4Cw䖹w1w\s. #~XMk P\mSz GDJhPC $uYp%JjrLv+vn,(ɉ?qYK*N H7Fvp}:QK^U dD=}ֵmdE!^ IX8XiwΟƎjH{G9yU|x^ly1GË~/-sBꆘQjdXz &5W%NQXƚ-̢SK=Vƛ'o[k>F&f6-7<cA1bJ )Ǎgϒ˃P R-K5.$%̗: U hՁAy~D⨆D.ENmefMѱ&R1,3V㮧gOV<^m ѢH:j[4l4kPs;Hr昱NE_Y9UjHTTNXO}eWcQK( vD]PdrTL|q|K~y:Lr1y6 ;񻊆JQ=Tk{!GU,߶_)8ፔ@M}5,*H4҉n9SMeWo/FVQ3D+(G^쀹>SSĄ:_NcLɗtBePO5j&HK1o[ōM/_F%E 9Y#)gNjPh\S WG8b<]}|;Y*S0j C SA &JBQ`0&c%NQF#tžx=|{7t}~!"*"գ!Aaİ?u.^~+y>~՜2BŠ k}2AQ ^=0;(!%f/͜º-XxHVB%7vc8vԑ|k7fl8^G$ƄWd$0k * ?DAŖ+<˶AW_?jL w}K(f" –@ X6[}G@1Rk~]a)[HW_w4+P Z ȰƁsu>qKRVZCxmm e[Ŝ @,UcyPuͤ 'x>lFsDaz j*>(u`$Tg<̔;Rnŧg<0Vmm"1]L#B,BnbAC`^9aE]*H14btx$A+;{46vl#dz@qJni ;U ȎB} Gy4_곌;!Nǩ|wr"Tv 4f,hȣc0>v㰠բ;?fgQ ȽZ S>vjxzS(z$< k^ʤnr)r̨H@R E`QO#c mOHK!ulkKmA{5JcQOosL3US@"_|CE:Ry-BR4K +7/H` (Z9 lPU O[V?v铽FN(=O?esM&-8ruVyX#4E`HؖԸi7XM#Lj5%8vhrյs*a#~ j43 HK Ywt_QL7Fڻ82ȩ2 ETF-^@\Զk:_|ߧ`9e#ܷQ~GJT&)N|(z/\\T7ҋ7-s-O ءjP!3}KP!6@2ZBNP1x[0۶ lt "\o@tR {!eM+l[wXӂ7'P 7?Ws.n6Cޓp<ʴs߅C.I8&CEɔx/li*t"?ڨl7v6oA0X58 c&Sfx|!4I GWo>~,82)tk̇a<'f FBq=o?\jO2 9bo,ֹX Lxv6}Xi5dS& Z?xN:>Xd,`\;S\ryqJ(J1l=O+x(A\*ʨSN!3vM# 5ƫ& r1Ws 74\jdܹ^8|rkԨQWAP 3Z1KC1ހS+s&(R=C^A5}G< PyxQ~ɳ79dU D"0}苟W|OTaozc Xih*Hj @SL wr\3SrTÈa /xa57ŋmatXϱXGԢa=xiB ބH!×fMaA<"a 1xMEs@ʸ1cfŸ0n̩ `)nn~֫Fm4f]|x_!$腼IV'A٬)ԬԆ&)hžIn?\6ͿԱXx?sV.y4"&4$q=l .ozwҁl7i歬ol!Gkl 7OퟺqcXhTؤ3ٛ|R^mulI[bz?~x !lfMHK *N%ƶ_%2/E$RQL$u&2o\76&H ͲHDt\?Nn\ ZGux8\8s¨&M +sRp%7CEN{mZ^yjCt0.K*"xor5UBqP}N| wMhP|#]>z>u۱,8P IЍ[w:6}Jt$^B{WC9`&)6LnO=(|Fk+v}%ؘL'<-͗fBOߞ\} b@(BW_̻f(eRU$6js6~Hk>{S%ҷwWW>MV~HM_6w?Wf-}+ Je W4*#WP)FV|cOowa,5Wsi}\] /w0iul/NפBU1jʇ*H-"24| '&4 gSx^殲wwi}K~ Z;b+\M|ٚ{ \TS}~)QV~u]ڎMQP46U[qC>ge3?8ʗ1hn Y#ɼz!D®ny :ԸSb*&zOOCX_cMWmzW_'_{1]}$/j]AB !YjA6:=3IqX bH72i]0٩PzMCE,قn`2 c/XlCSHk^ oFpjRKd 11ٟxDIzV%`l+qR!tfU'ש\M3bnd`|w#šޫcyp':<}BD`T^(ԧT1U)x&ϝʜ N zaS >x\tXUYhS03obEQ˖(JFPHLx& 7ϻ+'+>t5A hu!R3S!=%*I3idּeFaVT@!E'^7~mul%7aDT" )NmH^=}?@jCH : {'}s1935w1w3T2Ȋ5صqZ==|ql{aN-4F| \mlM:GqFPu]Cj慤df]VMtq5 DSôIogέAKT֭ij5jtx~3:RxZkzxSϚ|pEo~?|a̳R.Ťim 5M(bxrqX3O8_p=4*Yżp; !#q:[h"Q3ne:%~jqՒtkc3ϸ,oma 24ܻ_ϣ WDƫ^}yw kԨѴhdbicm60~1~2Fyp^7rt,)VCAGm(' =RUɰ"HYb`n,QCW_7>!l|hx"|UϜ sO'޴`8\Pdcd5*@V^ ܩⳘ3º-\7}2]}=4Awцr]  k+sWđ 4*;5jP@4){8s :dCo J3>Þ>-7>Jl(5)rQx\$p*˷̝ʼU H`Ml3hA2೜oV(&YqTccJes&E #@aYU1# ٹzy DZ`\IѼB瞮nx6z{B!Zf*$Fu} 7!r&j%)1/Qcè߄dn1ξHP9k Oдk m^LȊM鷱cSXc6BS L;I\XMv:7HJ7,%*Aۤ6@k/H욳z>̻+mu9\JiV_?hRTC0obniAlhn@MuCh:wrǂB~܇ }PpF60`c%LP47λX~q.4s ÿETn)b)UZc1'N(rpZ˷e,^,y$}Cea}6q}Ž$U01BMx\WZ< ֩ZӐ%4f=tZڰKƝ?G`c>8za Nfq4#3 < >ǘ,tq.o6^[ndz9fԑMx APO.(urìY&2cq( 1 8f6=ecь{OC"=ok.Tׅ,Ԕ*M`zj60>z1^ ͪq_Q+,4 ]4t=`0X%Һfb70RAI! orTL,sVsq턏>6FyB ihnX3idzًz_jF*BP!!r0jYt>BeX3-CKڎm1 c+}(t0i6oHڏ8CP;о`M ۉ3ǟFzA }˻{>^w)a\CèarcV,;ycBA z%7&t %kS+YsbՑK,`NBuhN (+O$HM2&BuV35j49jݠF.(8^8EKϸ Ll(+*AE"AT^TNx#G:v&nj:N<'ZHQ)]CsO;N;>%1c<Hk a cLlY}#DbB$' 484_CT@Y8ȱ˒ãqꡤxI==v]5y!'7F!pҨWOh>|-uY=[~T3\wTfX  o<9x+]?[GMTX}vDՒjԨff8 I/U1`IJhݳtpGUE8"o|4Kj,st&gpq9y|t*ig:#W)9'q̨~KƝ7w:;Q5aYCw_MLQP,jBIVFԩ@/]ZgOѺf@FˆuWSj"0Vt%M#Qlxo2#LZ4C&J6d#d11!,Z(]X[Xu57=&OQJy77̸0X N2_ۇ^ ]}]0Vul OcZ%X[!6D){%eT42bERL.4lk8DPDUcw;wT?È yAY H5LW + v2yQGTjS-bKR W$OEP%N|d6AKPI?PǴe$fcf&eqEnk $cʜqGXԧ]c"DAN`,wlz&'>qq(}}}\76o^n@c!J#WW'fƆ-$E@2 ['59 yŧKRQa'QGl*1钪xyzk7bFh&ovRSUvN95_欙9SQL'&.9GOC645B bN ]'M/`s_r̈́O!b?Xp96;i{ݪmږ eʿ/Ć5~ȹ`Ry~[⌓:>q'c97kw@%i@PZIx~z =5eGAGVa@ ո/t2{z{i/ kx04v,WVzn|t~S5]=jܡYӀyM  Seڲ| rT+Da??RtA ۘÐ !*2G+ \5!b1#MRjH b=׮d4ڴ* D%h]4k|«z'!11uXzw&\=}ui}dUOu裺^T7<Fg? |7f|[B"]GIrx16QMvŸ0w4.GxTvRQơiӶIi|.\YAYow\;,;آ %C+]X18>b4Lz8`yh}(ErON Ұoh^);Z&Xo ?^b_}{C8P;kĝDE\kl/"<SsKrEb >޻'W{u''>A'^,v},U 9YL<-8Ew:ʁaL kf'ر= UX}10S=XGT)jAd>dKYQcP aĎ1>_,>x)*D Nf-.qtDdxb;Fu1 r@^H+W1YNxu981qi9,F'qW|q@O?XS>sz菰FWERQzA+*qYK15>ظyi\&/΅ٟ(P/EK37r& M l$_DTv{ cX(TyB1@r1K=Oe܉o/1Xz)+pbZ,_Q*1R&Y;5*ِh@ɷYV.64G"dNFP-XTk?x;"r{kԨqAA53e?;udCB.1< E IDATu`^")wHSz[ **ź5j! m۸o/J2p|60J!{{[p1>*-U {(z"qϱqy^}žoE1Wļ|0}IeOMMg)ǞpjJ%$2;uگk|H7lEKش!B}%'*DӝbE""?h" {Z7cM FÍp7Hh0\t,E[xS91x%'ҁiQcMfWe[y)Eᴥdc35jU5F*6q#KC_C;HdUAġ*|ymtta,78 *yLJ(/EɗWcIZN1X&ϙW>r"o>~lD<<ǘ}Vesytb0Sm@ A&&qO4L`A)XfOW_f|u/oŪG5iƄ"lP9FpG\0v|5Ԓ_jJP-8v1K$$҃$i?ػ`gwl%<DZ)pԨQƾ!xgɃ\2\ƝpZUGhH!(mjWj(KoMDUJWSR5^]z_I|<! 1^:{*X8ÇsUf^LE興!Iz`$2cST:4V257^ Fŧ1&A kFuŠmkERj8Ã/gȢ]=L3.2cD0TCӈkkkl@fϱ"X)-He{ QxBgE\d>t'rtHUob Be-@ˀmĺ-,ݶ⏙Svm:Q/DJud5BfG1H0 G*< M?Ѩ wl :<ή}NP@*I42$l@A66$ |2`wwlc_<;~?LCu'`lj ch64OTks- $B"ݺ3}^oX$umϨ,wnƍރ-&.! D)dcb#>n]3崚gdž/GEV#`-ˉkl ʩH5<Ʀ6[m#+T5_@2}_Zy.DEPB@"9c0Dl;ӡ.ְ1ub+e_v^L Z'x"sUE)IB냒G5t ap!ٺUrhk㗷z6_3mؐ[ϞXDBKTxxRǡjr5`E"BP!d-1RfFFk\]I 5:82 u݇ҫ6ř}̅RFqHNBzX;U<|~i%y'!Ļ{5Q>6Z#Murx>*f!LRvqE7el9?18)c'3:ŬSȭ\]r^c^\P:V>~BW|R bEJ6(M?ޕ4hDǒAQ3-vP]~Sz ToӒZ%_5 !oEJ&Tj}dYU^`˩@.y,\h$I݂mYVk$yVSW3U} C|FC rj3kIP#0APՖm!|/ptZooؿhɁRF VřkxK5 bm#ʒ2e̤+!VJnZx;Zj _IĿFRU?EhjO9z%ijP5%ȁ^hs$ZhJ%vkr0ЍD/"h"O'T(}EqxI/JљB|JZŘ[q>qu"UUٛ uCP_#psxּU=iinD$CBQOX<zQ B%B}G}`-F 7Wj͡pA nW|1݅bm)9P*9H5@dݺӷ%gǽ-+;|f$jISB$C|؋RsO܃xֱZW6pX'dYER|n?+[{T%ڝsmw%~r6,"9WlhP锒F"`JtI^PC$Ps?O]]>DB}Rj?\//"Aϋ 3b ;-"5Cԍ5A}p?/m_%fĭSَT#W3 yuIT,Z&H˘=ptf2v<ήr05U"h!B,%,kRGIUqq'eӢxwcgg}SG[~-Hcw .4K[ډ>EL -]P%%eVph(*AQ7 B썃0Ps&/$UvXU^YRR6?Xawt`~>w! l F*vyFysH#8y`,Eb[$ 7G9iL @ZP+`im@9:x߿ꎢښYR22PR6_L`J.re\B oOHL:KC}~ [J52 \R ͙Zh $* S)s8kDOoXb@Q|:*vɒ\sjG}12ٺg'5.ї$0,"Ijn&uD:A]{ydR_4ępy,'\׿3_!b46fR&5xnD9lw7ٴo+#S@pF,GBhPWn<=cchFS?*sTÙq# j0~HEfw^x kO좡PmcE2%łjbq\l9r2&7q|ZAWNpIL9(odQo~ WNeڹV0T4HN,"!Uƞ{,<ؽBX8\#+TBN- vxRF ~e#ku]wNR;f(Ur2U[-[hwp6)eI?zt<6MEN3\>V~y6ơG A8QLwcbBX8Gw1mdF#7"ɺJɸû`k3ϾJc^.D[Ғr3F [ھ<rů`?{o< /zU$VN*$H *Hwjg6-YR@lGZÓ%Ok?[Y[,5&:!2"(Ә5c&26pl<]Պky~ֽWJ;Ȩ5@d1ak2K7G11<4'U?fhB-3XE)[l*LZQ™!p GsզJihc}qcZ %rZhBA]yK4|_n:NK2,ƔEŤ|9x N"ţ؞-߮ZxcڔgcrŇ<|$^U =9ftMcH&Q\9#˜s,/j 4F_9}7Z8ƽ[ٲoxg3%2'b-eΚ*,ꏅ=+ڋÔq H`Ub d^=yӻ&r1L7 )jz *BN`>vc< xy$)!hY M4Ef F#)Oo^o9L爑=L%dQnYS^t "U7I71 l/NverJg[x_JgA,UI'Ş1VQC=BV,={/$XbݕUkqTcŎ5 empI!!?]K&T݂'8E|BC@0J/G4j :JF1mD.0Qcwv;uι*hU;6l޿Gxi&6BSK}Ո r&- N4FļꝈTTQD] jPHr.wvUh/o%^o;!hٍ?OYs1Tk~+Ǻ2=kwE8/%8;Y % ]0[3i_6I/'_1[yAkJ>+UXZ$>Rf'ZcSP|RQƵd !Pdo= >`[<GƠZխq.bPt2c'qAgOF.x7 {]o?yx Q XH*zsuv; {ʨ ?T;K6Z(b9Yy 7*P[xЌc'=os\U߱EkptSSi )?31vtFaCFw1\#:{vr`qVֿCukǓSi0[92OdTu(FѼ=+9gDO(Z%%pO4V:Ί8X֓ɸh8d@TE *p=gᒉI'2myظg}?{^Wxqۆ.8]ƻhg HpyiPM_-PU4s22qрPL~œg2adc"׌'NTv`w6݉w?0[9Gx%%F'-Y ]vSNU'_6\6a\!fOouMuMfՓyp#6RPjNCru% ;?ϣ}D=ɅW %Hvlg=6FM%C$Þ=18T݀7x -ȀӸ{Zalٷ⎍lڳ _-ٯ{uOkW^ćTtu.lL^ڵm9/n[Ϟx Y +J;B"!Hk ';]BNpSoSHx=}wL9YOHf )?y;૖$p,(Z:qs+"/?;`@G^ڹ+/=o~$I^x=_y@pxd; j2P'1ks&_GQp7}/y7w=0.$CP#9`QSnuA VSIIwRdHkxnK 'S,V 4Ak)58IH t˘={.%'+ DH6qiˬܵҶu͟*& Z#d"ޒjM}HvWYD$3+^Fp sȶ' J c'>M{{;+j3y Y& 9*0BLy7 u9(s_ bjy6l2NrjjtE1bC⚘ P  nu\:bdҩTY|]2MqXrs\v)s&NkXBLگ|IxL< 1¶B.GWpSߧo(Jn$Q~'90‰>v*,ϕ:wy/|.~2- Om$#A%P4 G o-8w"BUeE^qe,73gi!<I|Ʋ{S&3f\7p]kyn*vP gȇg('Q<8IO) jhJ8A +bE!8)x#PCq*TofXF^T\6 GBl2:pʳVg>ƜvD6}c*.%ghuUa`TWJWX INJbKmUQ̦W:B/5$*V2%eR bs9P{>w}wu3rHv=}*Zx=a40"&^yG>F}IkxUbu*9m 7ѣY2{1WG[ٗ"xl]!!ޥwMA(fTI ga\͸{x|OظJ"i^TL]S Gk-(ٝGtDz`e,r+Kkw'WX{wx{_&s j^LwE$ ~} u8eCB9קg Udw]ǯrs~J2ywHtuɫ0wtɡ &30BTp{vxa:NQTĪ:14MBLGsky׾r?A fkAT\ !!Q!6t3m$ .eɻ1܉qqYVNČ6G Nę͗C!n\[V܆%2L 2PJK-OF%^f|8cuI$/W4Zx`d;/8εR>`#ŒqŜ6R/KT /l5)c&}'[FJԙ",Pg*z >82ܷqn6+T=*wx M2?%׋6D(Q G䲅0t5z( RDδRK< '`v n&Vn[w+&$3hSY!-xrr+@NFe/w&)!=P![ih =mX.MyeX\bd Y"!XjV5+>d+'ŃS?v"4Z;'3=!!c,.uOc.AU)V(vɿ4P `A\z)Oyo=u}}1b-k rf٦U̿7.JU7P̅HvPT^|'?@GU Dc sLYՔ% Alm%"Ӂμg^ s5v~HM'2[ ca,$"1E]N)Qvzg--/ֵ4>c9|~-teҾbӵ摫 Bf1&^(UEoȢ⒑"^OغQkmL0 m1*n@ͺ8_xj *_gG EMHqxi#XqX_.86"n\|?~t7Hv$72~sL '<n:*'@a2& Y+@=L>[\y.wvU滴3=|rsp?v噍+Xm}H!E,c^C>FHޑk!JL$/\)Hߦm(~dXLͯûdMe>L>Tpd{K-+q΢#٬#MdaH\tx.1)gb4=KUBj@ <][9qVvg;}]_lnlgg|*UAUrdTn=s5.? ) C{}Ar%H)`wcs>Mײ] }`C`LX BIY:7Ala3#K䠆<]ͼ3/bI邤FLc⳻vV\w;6=3c*/ s\ +}S$#JBkՂQ٫LEw?\2~flZiTlȵ@i1fWc{A+U==-Qj#N.X'DKJYξ=FKmD4΋ G`)JAC ɢrM Aҙ`ɜŌj0]eq`Л<) g;^g(_t/CXi~;ס!Zj]1*Q9u"~#Upټ+j R(pC_ T2>8*Xx3J3%e[hᭁJf*^<25 B$㬶vn% hU&s152ٛ U:(RDy[t7_y=߳tʢh\NŢBKVkІϩ=W>4p~f_;l$cO^3H$d4GB*LmcbOB3/uʻ?M}ldbDBK8N;q:ݟe3waV`dP`q-Z;NMP,4eD>uǹB5+RDJVMS IM̪E=x5]ɣ?)wvj !5>@3ᘢ T3G5K3**kAz{S01\@%CBZ=\32θc'P%s(>T@7ͮ+UmD,=/W(gF"`$L/oZ)sb,q@1>iIG=kh 0bu[5.ll|* UUoqU5iࣗ]3/R*#EƲP</-Z*iFY,>E3s{>βM+gxqX#NuZlI,f:ҐINݲo@\!@ |5/xUۆ QREH@S +(8?ʜP&B-Ķ.މ|qVo_"~uE*P##s&఼)E"'RΠ)KV T'gIq>)wuBl1u431mL7_a ܚ.})$R xag!#3u%èQ(r=Pqr~gfڏ(K7+x~JǑs,ͨDVb-T7+ZrvR(/uVm_O3<!]s \vxN9 ۤL qPsEH *rz"18Ć[yh+=*:vraF,O\uvޥœصz)bHo9jF[Tj!&z@XU;ֳ|zI2A=W"H]>j= `9]bEWՓ +otEO.k=>+UKފQ@8t7_~ybB>VtMV,&&G%Nro M3?mYzͪJp^b\4qFyp舭Zw,&YW-P vbuP4W}K,22F1UByX)i2P};6~}g]Wmܳ _%'r%^ p3bA& <56j>DBnVk$*AܱtY-] g<W#F1k"QbjIA "eڪ\|S,ۼ?{NYC Qoh*u8SEzX0m65˨#6AEJ kCųxv=[Vwpcq!%y-)}&bd;w/ mvp^e4]_thFp5< QqړiDdt(.8YML٩*=IzuE~*leΗزg'!3U95@ZLW cM>#yF!Eg|3tbIr"8:Nub؜IIO__͟ 5$W?/0;,+'WQi̊\ #FVT` S89 J.˂)sX0uʲMx~Ӌ8ڏJ 'x=zNnm N6I[RqAOA\bN5CB<>NR9Iplæ}3cqBRNT ;Ua6ƁZ?/Wup?;WmXUTm;l:R#iMގ'!w NW?cH,V-UZxgQ.'pJ=\tNM D@"5NabnwyG}?,%%R :Y]<˶=j̀uvBa۟_4cՎܵ!\ψ%Zfg>Ϲ9C5Ȫƪɂ՛W6__Ɇ}N%MZ,+z7ƥfˊ<#|^lW`qz*VC)c&%aT{ 2L%uc yu}vnLlYgvL"8K}ciVT3&XEq( _mJ.YDBPͼI{ld9Sϛ:gs:WakO]AA$V9';z}{j N]EknUO/<Α#@ "׋XDSj緷$~?g;@lhɘ<MAXFAq^t+P!~pdj;$B/.-^!"jY$I$c$(8bЭLTß|lڷjUscAA(4hC\T`G~Vm[KaC~'o[3E6| ĖɆQ. YWk>FGh=X̉|[YIHS+}. }׎R%s /RpfB㳜yCYIg [#&'T犺QJpcmΟ=wj!Jze>5V|d? 2 KPN#vǯtvQ趧"R$Bz W.$ @?-ok~b+ầqhs&'>zUrę'P?!U(mȚlS7^qټ w4*=ZRT>S&r[v,JN.sZJyHy+}IM8͋(pc,]ܻ́I]$G2`-U᝚~ o#lw7ٴo+#S##Y1"YPQkqݫXxgZ1M7\G PNϞ#Q̟z ..Yagdo2Di=W0^^Ya9CW%xumvm =XiFFɔG?ȳFR!P{x̤P5lOQ`?<`F|-+{ o-HR'M5x+wlhVl0c܅̻x%?ѳs{ uhkEf"Vd?g{zmZawVxM2g>N*d 2P$%_e#D;PAi0\"`Q IDAT5!JOxgHU|Zċkʕqף*¥G.O%$[Z`N} tUrgBТ&>h~%~ubBPL[81GWws>:Ն vYpqx(}GoGZ욚f|=7wmVkSaT|zHşYjIKq_|᾿`A2g!K\KgcvV<qd i(HS!R9N%%=7b:vb~.p{*tg`Ůu\2qj[x#:#vbTi}h)Hnc]5x)eZ#C>*Sш:_DJ@)UzRrGxM 欲_o;5B1e?߷K2" >?ƍD-C:2kWE5X2cysGXIVc bh6߉3|* ;̟:rUgޗypSsFrhSĮJTnq&|QgVa,)bb)=-yP7t´s&o?|oॆ!Pc΍CXk^og ygFHydk+ЦPf"YWmM#iYT2U#Iq2/ۘ%3=bW-/KϲzF)wH8KL0hO:BAu56>@&%8f;wMە,G|"~8LIS@ANK[7Py'3?<&$8I  ˟7H%Xg֓tPl*v淚vjx2qZx\6+4*0w9|f\5en@/yT)Z*źqbT/v,:^eR,IsNɯ/QF2~rhV^d%㍯Δ"9wz&7?pH5ȃCњlhWTv)v|XiKh5:rK;i^Ӹ]1|̼ E1;NWKmR[TvÕSLQHnxbn}"b8+YhROA;SyƢ;NI4^"N>[½@)MDՔ\ %g5r/|"X}[ox~s iACLVv6ۯf-(b)Ɣd^)+J|P$#Vopd-V#ώYWw=#kAoS)}oDUHq5Fi("Eqݬe˜AZjRȂF+?Ē9Ij@Hb ƛ6vO7tF ߷gd~g-|z-kqf$ K[]`ycsgp?y P<Wh$kzgRvl$&̈7+J"cGo#rՍ_DOW`Alnd@ ;!ЛCplFR\[ 'q*VClٻ} no[o%q\,V30CZu+ \Y&4V gvZc[H\ ќ,`U-BS<)8sTpu%JZg%&xbU@ӻ&>KGۈx}ŅM}X_ Ya4})0w,?W>{Wk͐0T3흇p[2XaM;/x;YgA*HCߡcx8HFg˩*$>αo} 0T eKRNᷯT`v"ѐAFxBΒ+Y}U ;\;LI7.H-2\y{57}IJ\:J+B/L_'9p:ƖjWAF&C9:Q>˴&k(uw81ym9;f,=lܷ "\Qg'^P _/ټ+)Fjr~NM( WxF1rSnڙ>;ȯPNs]<%sUxЀu~1/֓,4q *hTOq<\;=) jL%r ̦ m8xη.X =ܢ_0}xEb}UtHEWo ׽ dѴV7<@H)Τ%%l"%OEَ[VJ%v:)K]J%UIJr\vgU3UՕؒ]dNA8JLݳ}νhx֢w߹cU:?O5۞*5qG;|Ҹ gKR/=QGFT@F2ӱC!+^{ϣ|4ܧoV?L2%KUA/uٸZ^0Z{2V/aL7tꌬ/>e.2ZB7>ՐC$ZB?NdV Ð-f ?ۗ?>UvRYʒ77.ekh`W8淿ev؇`v\k50n~=T`cN<[;<^lXvgc߰9QN!4[3Z] V?1Mm".=j+2W]wulXz//^ E]¹<{hw !c?A<q)-@lN~mYӟ$kK Y>r{yfsb/ԩSmncԿ!& A$54[]dsQOh%k7Eۉujtɘ9)v~zgZw:۰8r ͿBԍ+!{d^*46]4yf@$ՑIMn 0P<vKOM~_䗿?οS_xnl/bf!? y^؝|]/$&Q-PjԔ "<~ϰaٝ%*U1D8I3 Ӧ79 /ɡ.լ;bTYf_r'Jh %Yў{FwyVA[L{쇯Ñ7²Yu#O0xbZh)ȘZ=ǽ٘UhG< dkY˭,ȉݒ$*T@Nl~ fL9(|!3[XXڳ=IL~: -N~]]9rDSV\ԓ:G-W[;w[:)vr Ã~m.N7ºh-GR! 8IkChkvԏ̞Bw'g^}!QQU~QV)ϘK۟ӴTYl J]xu}YXeGдN=ʂsӥ"[GC+s•|ɿik˒_5\ʢ#+TiS[9}i:Z:%՜OOy)\'b8?1˕}j#L 8cvjZm~yMTr%[@Jwћx`=x#f@8~-䕃;9rΛbf MTM^~֫~ VFl9Bԡ2xcfv\òk7aS=[gN!e8FdoI?x+8N|֤(Wgݢ|ɿjʁ>*cf1'|+_bkI"+A "w)uutNʿxϳoilАklE^<>꠳In뚛_h [E-w۞˕˚k̿jSZOmiADӠU\óhsz^͟*ƣ`?n+ڕp9#*쬛b$")'7$9 \RHʉPHU`QٻyD zR~?+k?o^; lS'.IΤ2x%LEDMDT'US*CBa[ȥGhom9$bo~2B|${p#[%i dOYs[;܃ TrIƣwv*69C*4o &myfwj#!u}dͻ"B] ۉD!D;*UeYgBSCgyj EfhkY:% |dJ;/Yo8]8w<)!d uZŧ}kq>cqbYX|g0 7p ОqY=_L vlgry~_bωx;OJP#mRp{u*OQu+THo~;f!>n5%Y8;COg8)*=9Syqf%ryqwGJt3w]?kCID)]fV9粳XDÑ9eZHP\v27| 4iP"Mjf9@{ϋ{_G*}(p1xQo#(j^!h &Om~ШˮՆ  ] uLsQ7jfvǚ;2@]׭ 6S<]?oZ(:Rۣgzh9Ŕ^~;;C+Ϳ=2\G<|wi23'/* {6["}OhvǭPQeHkH !'ߴ/G}BBdu2 kxᛛن q$3$M/uJꙵD$% I(`QgLXov' [<ξL ϕ1VI MϘO %IG%,K)&V={luEŤIł} DU8  ':ЪsGRT m~B]L38@=(/luf+`]P^4[>n4)=XB |m4Kkh<UsrǼeu ŝH%ymd#?zdgoj۱fCU;>r{:5 ?6[xBܶ;Xݷ yd=5M5}[}?ub<AI[Q\ @pX+,ϜݗD=/c7@$. 4شj`-fw*Q.lb\x=zR][Bdsj(Lsݶ;7̞:ǻ:ΧHRC%;:[4 \]\"=ǁe"S;׌?>ϸsGΔKpÉX|&kls=*5=:K[RJ#NH#3g44aw"NbSMP118\n%+e8T)^m_A %֒ OeG;< Y8$jR4r>Q| ÕDMGnUo[L&vJLrw+Th6Hetx#VuKgߍ ڪQ:i&<\4 5z$ VoHGk[ipd*>\?+VVZ8Kk{iP=v Nl: HZ__T%wybMb'h,eٳ|a׵m=+}^υ W_"{_?9m;x*lg Y}:KfZR:;O*T(3zgf ˑdc5/I3#o|7e"Dew~='ۓ15w6Bvg`eJ^=w.YeSSk"]b ׆{wʟxG ,8`cMoZ_eOwQfsx_ jwza57 ռ%\ԓo}oXsjY*ەu#%4.BEj-41hHj/87./yw;Syeṃp}~p`7gHB(MQNqvvqn)ɄS6=}7BHkv! [GKЮ^g}$!JilWDM5ZDHu;W96L8()HSGi_v/ ;⑬x7NP# ҬRJT߸!Zev͆)xc2PP,. -e A.evgw)Y  DS^=gx'fhC D{a;yCl P⭖%'RDHJjҸ(|_bYѻ:^9g+~ĶfbQw/O1fuvά3+'x|o+ٽ:aŖLn5sɚKUircv4a(>js&$Yˬl~yu \W‡ B츅ӻ: j|CRϞ:|8ɭKJӰA~dgsǓ>w e+D]Xģu;}ME[dž%k Vd+38v9b7f{})&Sa).̛սK$HU!{̾8ɕ%.AeDaS_s@< eÕ@He-?7>xCBj#wW1>%\~j7EYѿ8I_[UskvH( IM^AҠ'J5@]<)WkxlF,[I4iJȢ_2-凲xY˓Da6fqc<< 5aO-Kf3˳S|_fF6a Y3{I3Hx'59--.dV >6_̕heAFT6MDwd,! ApŨ1?KKC49i,(O}'ej+8cOHӋ {bZG~~y>x@]BGK'2n1H+{?G^JҰ"Z /<")~| dDnDR"pYy=V!VoKsffB͹η=gE=9]L׋h7Ѱel>D1Yj帋+ J3+Gl] #% ٵ )}cE"TS4R Pء6ev Ύ %A'׾cbb*),nņeo %ALB"}Jd{f4HvHʑֿM %u5tteH j͏0b[vH##4 򡝅BP'/DVmS&pC0%HL xN:q g [ IX/q]$Sj0 ʋzȼ:뙥p=kBXT`͒;45JW6w1s|cAX, U1Y5M;nnрxK"z-:ߡ 3RHR *4 mAI({}\;ĮSVX`m=ÔB3 K|+t:pr]8V*PM3+נE3)]MxPsI{O:dU6nŎ@Beʦ]i9"PSޱnN T N4R#)/᷾'|zD#д3kO 5޸8Wyeòu<1:ZڀpWKoz> ?K4{x# Y\ ړiY9^$Sq6=4ƪ~GmEZaW`Pr*Ȋ#Q*ڸk* O-jT(/lw9rsς7R[ }i,+^L`AyS#s(?*{u_ԑ GT@F@\ }HFr?jV!^j4obQ&n0E1[=qs RPO/i`*cwɥ ,);GϝJ֩Yh,2 owzRuĕi¥8w+c+hM܊((¬.c׉7ڑ;W~)"x7xDNBM;O;"Ջ$c=<~GQ¼IeEE {P't|yJrk(xs`}Ll 6Qj%(x.mnb?x1sWj1 eZuvΧ `Q<Ǫ,KGԐ$kA$[z- gj qi>gLr VKPp70]8$Cld i<.'dtऎxy`+Fq ZCBJd{PžCQj>-eSRٷtbR{w* j%?,ҺwI"A;7='H4͔b مŕsP,+O%%\OqN1;`UR!Si&KvԳAV/ r gJ^L§ZV݇Y.y$ ^qNHźb4Q8 An(*q"$YTSg ;IZ{^Pᦃ1U!UeŜEȠAUeUeU[R_jbm9 dU>.BwG8s<~8hceRkJ|/½ V"lg  +zV- @gdpe$&jN;qyÿTTLzafwJ/ePC$)).EU1rO4EC$8gxM͋zƂsƼse&] Q(G Hl]q.sy CU**-x)"RXFB!)d[n[ͷv||9とB*wӡm$i+"1I cкLx_B%,Q'ܳ`u/&+Ln8`ò5 PC^)5$8 9Sk" dDضg׫magp=L,6ٮWE=sU6g\P]rd5`Ån7 Mӂ5}m-9k`록TB!40E3f? ! Ey+fx:jެQ c ^Ԣr:!фGz'!˪P[\cu0lQafG>u իDBӯDhTTU?` vE2B$@2y9qʠXp$;H`yѻoW#fhǯhp^q { 9%,*F\50ʑS!v䕃;9|rc^Swþ }Z:CSԧ,K7ښ&#T,]F\v1c$! oIT Yݬo6e Ĝs :Dx3$5{7dx:޽>c@TkR~^b#yAgyv8x@)NzHw- XϤ(4 vbY5лv6zQK \ R d9urz,ݛ/p3[[q{bn1LjT҉u\ZaD]-p7:ϓ۞Uoϒ- ;j8*p~fǂdE.Yl`T  k.-x[] vO4%*qbv0Jj~B~ݡq|pGb;̈́BG=g AR:3ҷ!sI#Lђ`̾Ȱ'} IDATCv!ng&O|xT(0U+af5,atQp~StvZB"b\L.Sǣ>ޒ=?őTd P3Hj,D7.nFh"4X1gGOH ީ~LxșC?cz<(":3h %PP.W8)3V[_ySa$1wiy9&&}aT;L$JCg8J laϑ'xy`7~+{9|j0{c $*Ʊf/P,\8Ih='$^pR3Yx^'!\ ݔQiTgCA3+C"~{,$40 ㅨ>Y."8uIDHTL H K6rJ̛ X{8.za凒"$/Noi<.O$Xar; ʷG=:ٜXys]fXѻDSK iVTPNIbѐ訚 Ȅ zRO`T[nǣxgu6E,1nw"Ixxi*T *v37,֒u.[(^VYe꠺^xhN!J[Խ6 l˕Mwt߷)Y5{1B"iCX.b>` fKRpQpuη gYSR1u$M&}:l$_a`22!W ׃|E5 'ivDˌ jIT Ʃ$MOvBJ~De&No ~Ҩ`l!*gX 0"CgҖ=n |6 o`ClXƪ9K vɔҺ˜é4L2Z%jFh Eԛ,&!'M<}"U#fb޶!}.YQ3"3SVhJPsvL(Me[ ;suZ:=ѡLiuo6~Aޱ6pPS7 ?:TFe(_ ym6f 5x+ku'>utjI~K]cOM?uTH$؋1&Q p5kxJ,^>o=fD,s1º)i~` Y#ekn6,+`G*K JT[\b 1d&hJS?Kׄ_:8L )ڐL!yϢuԑ (*6?N@Tus,zhG2<\1|хDalǸ.`Ru:d{L 3Lyy,.y/7"X- ?yd~a_nTYlY> Wmg.e6 lL]cj; jRϊyKiMEȖKnQP ̞ŚH AEPm{O̵O ](sf{.O@IF Lv^jĩ$ u$N|hkGcUj,Aqx$q1'ZDҔZ8^PJPFbV-;plV-~¤FqE:;N!qb)?>i@GQHUIAV-͒^EɛcNN>Ǭ'ZTa!LC CڪNh3=šJgQB٠$xSUY9oI1 fꮐGPSzp'+6KGxA bFyeW+5'Y(fˊCUH 6`&Vr ĉ@ ~m~%*2ه۟Wa=-1dj|Zh{$q(/ T#dT5a](][$9w)vkg SߌDi<^ڦKo_~Ws_!Ru,˝YM99z3K⁉3 ™S2f*ϼF_nja$bƈEk濁'=R9^p)~p [ o-nc%[K:-( a>g{ѹ صo V׿5P&}/|qH ODx`ZwMiu|I,)G W/YdhglfcdU`Ydo^w/38|aam,ì[QV}{3xPB=!ZL^JpH׾Bsp~΂)ġ* o!T}Kn #*%"^qWY:KsdQ r ݟg& !A1vW'½@* @@˜b0aԆJv"^io blnlpPnhoj߄$bL+!5|}l]ά bs~(]\C)y_}olh V5UD :7 <ͮ7{Pz`gZj3@,hUeF.QlPaA$ݦ*tN|E]s'BSbURU*NK2 s}$4ދNUaTzx\\ˆXom6ĎBD~p)W71:S|6\4Q;Ȯc(޿uȪ9YӟO"oqNq44=dήp-T  cM]zD;g O%QEPpa)AյPL)jD9R4O8M!kHFC-ԐZK) zNzSr84q=~M#@M,sV/'.:aZLbUb;p59R(0@P-N^T1ͭߏCBDpgo͚8#T{iFȳ)w 7ȣ2hBO4%+5)??+9jUd̜O\$ a?T.ppA#^xv閭Z6-\iը;%ˎ˩y芐 vRwrB*6:ž9l@($Ib`aw/Ogx%EI]77"i#\:aE͆+Mg&hBǔTJf]-8]ؘ$Uu>I8 e'eD 5o m스>E,7Վ*L<$!A6X>[;C1k&)OB1HԖoM`WUfMc1^4itX,bfmt Xkǹr"%ԜC3B$:@K٣%aVkxO.fiH#I8jL-&ݔ"3Q,fgQaZ6l>_O.#w+VJ̚7_'9fͤ@f]snTix8e?nogOCB1G{8r"0mJ{3,g*i:X=)\]W}Dʿ=u"SÚH0}b kr0lŞ>v{LDH= {EZ30su^#%5wB_##g_Ww^ŋA\zOs*\ rU1͜p:[IQwJ[1X4cibDLT_E4|d,kY\:pvib3LWAC̎:_q*GO =ai,4q m] V[MO v..%/.R(o6 LXU&~q=Ė"ZkAUI1k_8Au"x_-]7Uw&6-T$*T.9CL5$љ{L366 Eٜ32E+U/s8[z'Y,/P"RZ FpfA\ß7% 2'ivʍl+T2|=x($nl nlfvx{&&7b΅@SRae )|a3G3~8;t~m*4t{W8 ŻnFg1s[h B9))5aq<~GliMCEHlWPn5Eio`;wđS-EAG5gP2;8]@])CnZT8x`Z˴fotV>WQr"L[d‹dq!3;oMQ15oW W;2soVZT<P`k88I̥oAu$}iSkjYMYc{TYrKd=&:"5"QE;ϿLjmx}rCbn{9PDM-8ԙWՃ+ܼZ-Y>q\\"*|x{·菐p-%@ C<38 ރKu:ppY~5<|;13sr!㇨ǰ p7Q @}_zoWMX΅W_H>̍:$JG4̚9>=},TVX S69;m|pPt̻5/M.^KO@B9:{@ց;=ͣ@|",1)y rlnlJ1_CH I %5d3Up^%_TNh$ewFZ~?xC.*=v7d*0ֈJ%)ձ{BGԆR@+TЀ;|$Kg]e/o{I\ǫ+OT5$飝AQ ?(cv+@TO:5;/]ZW2{(uGO 664,Y.2F& Ϸ?oHII$pg$4޻uUwߵϭت[AL0؆'X&aӀd2Lw$@3|Lt0tf! `KvO`IUzdUdIgݳ>*UF%ScTunվg~뷨dzkG$HpBIPX(\+ZaiR]1%.cy|]iYYfqNnF#O5Y$s\sʢ7;qܘ "lܻNJw_9EdzʛG1 #Ù-DNYhENn!O.xpC!Afn3|o3ܱM0oFwv c& &f4d7x3g{mQN"|#lD #~_T7"pJ%JQLO {͒͜9 YѵW.b܅̙}̰<"i}nq ,b=/3to-[ǷrtjF,Mw/q9SE&IA5;`Yע:K\V44%4!FVk*ChMt| 2P-_?DcRw<~d '\37#kE8UA <~>fv*4EAEqJR9[=>9 3j׷B`!+mp0AA";&0Bu-+}tJLԛenMwP$C ÜV[ퟅyLcؒj~%}R3]a7ypz΅Xbp(drna]_lZn xggOeŖJ m~2< 8)Ry(]" _<x۹C mu- .m.{n!bj.=sgz ͜Mar8|i KO̊yhGFEQCzh-+B<5ciΞϊkX6{.bzc,ZvmQ8I)bjR޹6Z.˘R-'ΞlӠQ]}N吝h/anߢK$]brpբ> IDATKÿi8q(Z1 GNO[,Z4lV5J$pbBpc::4v8iȌjQHe)10DRHa.e+_Y5i:s䪡ḾD0犫F<KI>={>e>f셡9Hjxws'Db8~M[:gw@Fc2g<>7_Xni0qTog3;)Z~^y;>?Ip70DġD$UQvv#wq篻whoS+"aqu~ d̢Q(# acN3V-Z,^̂Wjъ.`Ȓj2A 3+$ T"q1,]W;sO|닜<{63ࢮm%H$Kqn 1+\~7q)8 z\ ;B"S1kb|ޮ BF\D13:%RՎ9''5Y;6|gwž{֫9Db[c9n^⼗Z-f8iD} $`rp)/gV<ds ]ODDm!'G,ǹcfr1b=wf7Kfw/HUB"E{YBfV#e;!8s( t-d.Vt]êY}5:D]ŵΕqRZuJt$e;GܺgX)442qRd L$M#͌F1"w\J$&9 IfՐj܂fNr0b{?ϧ}/3;;]|t"HLzD:s\ŐUi%O#peǴ̽x-/T<:^J].]@og {E!1$ Gӝ;zy=E>{2s )O`[]:H1ftLwދfFhb^32|/@tLJºAyuo7ĤpN\$#ă:Z`(x`;o|3_|yXf[k DEUruX8Js堮ZŜ[ p.3v[ĪUJ `JF4j[F2^,1.sc"qqTJԇ+Kh#{"1YtM\JpPrqТY3+v'<eZi&0 b;Uvk\ƴXArK\BZZt܋-`h cXbQ.3!xֵ".Ďq"QwLB7# E*f dd9 lΕy@B45Χ[o9[_ NAÀsd5:0łvOZ̒@զ6 Ip7(pq|hw=KVd!1(>/xj, B/sۥyg9݋Yhgt{f/ _ `RF AZaZ5d d5߳SgO~6"Q\nܺF^x ; Cqlڷ4 4'չuI= հ؉[R"1qH ;.{Dbleht 3mp/{s|{1=oD"@9ӂ&VzSՖ^ՏIu3r)w0/ۻ !>86tfj,Xa$\%:$.:WEw).s:rv ~O>f4Z!EyprڬN$. L".|=_>M#гh9u0Qq40/sʉ3' KeZc 8D&-q$Hج8Ԛdoy-diޙ(b3x-51{\u/dEk,d٭?8SډkhwIͲ̵jۊDpGSWo]zyi?;C'Y:ip0#w9?]>ƼsF"PtS10)ŧeejL$^RwN SzQܟHbM{ql0sDh/[WXALp({gpݵ$\|їZ^k/$. j- #.+Sc if~J8JfDC\]9pyr KGfnRrqJ rSa=7?>wLCM0c-IQ̒H\l$~1`eČYܷ,1hXyvM yț94qk`@)G~\\U% &(Ve@xbΜS;e2[q*7-v9,Z9 Xֵ1,ΡЕĘJ7)oZTu(oB=K2vi^|s vǎ^v]kcgN V ń #[¨/<#׳5W=Zt\I\ָ26&•p0IF^j\\Tb*˂(wP(/,SvdVbYqLɡ3]{ǭ smk!-J5eA5kǜ(_?̜2d !tH@PU_L'!"ΘP {g}/-Of\!vDQk 6:i FE T8p!ͽKz ;(7SgNrqyYsNfl_ǿ3gOphf Kr:ڇe$5vӡ6@YeZKm5[$+lm? KHZ`Xp%N9] m啘Y]!&Rb״y7\ Oonza꿮 (yqip^Asf]D ofZt1q_J$~Zfqq`}YQ8zʼWtNykQ+{@xB;Yߖ8t(ȕ1y"p_7=XU71B3a .(s{m}q9j/bwr ^ bxe`o81̱|BftNtB^M9>'d8#?]Ϯ9-Xޭ4UhtlfƉ)Dͷ -ǣR"q!pO g7iS:1]"qa@8t|r+gF[E_;s*76-?d>Zt F8h8{̛Բr' &+ir"Y &FLDge}sEobK]kp!v~{p83̋|k? 蚨Т.*x Й~7L0{f&lj$3 gIfQ*r^sT n!MDVx+rbv1F`c}OԂC&HD1g`Gn 32R6qS4Eі瓁a-Wu:W)|KbJjL;X2o1{"h(2pll 96t .i9Q_˗Bx"ټ˝X_ILUphKRfM>S}PA%[ϭ'SuJ)IqaH\B d(d3\jJ^`%f]I١X5Ƨ#F;'oxdr*h)~~6BDK\Qhyu}G^[_'S&hb|):9Oi:\(,sڔ|o~ ~?mgwnJW>_'ދb?Nf4~h7|{nJԄ$?wSù8!(m3qhvw+n+~k'Ξfw^ve϶4BKYz1)͌yyd4Bo[GVm[Y>/PerqK,%k=3}~ "4LD,~x6_*BJ9d03c]i͑D.}V-Zƃ5\փ3xΜfڔ5`HS}N*-(Xp ɣ1qycf>1MɱI:dd@}Zz1%P_aVcJ[z.b)m'h'=vlqa :UkrJuU9ML6."]b1^kůn!ǚ8q!vr+jBB1-nA#z+[ qMA_xp*D7/.faF / sQv5>=qa.o;{,W ;/;>s_}uyH$.o\ Dek<=?d 8 1Ew5wL|J {9u u'KM2$l(V/Xt;>9#aa.j"+8/lDOvd瑃lG@`h$1DTs AgC|ou`'QCd9 `׽WuK|mC͡!U?$6A,[Ih*em9EP|D"qi)&ܜ-ȹ~H> kUm8zzy>C{h"N۰nPTh8K(ju۞@qmhI'+Yp5زw_}iyԬx-fV`ց,N4+OPz0ktM݁a1MulㅙH/tPr\ E{2phYh nO4A#:}*ͪxؒ%!V:#˖lN0qt0c,޹gyL^5Pq~oY#58vaI2{,ҕ.,>m}[e W8w1OK©{a\HU47Y*X @ MX _} 6 8WDb\^vOzq?刷ꮙ3yύocKŵHo f~-Ah:/^7poB4bDS* BDįՙ̎(9.z,BlVez^I :cǑ=@6zCMrS\?']:݇w`&f9&%s-|sZyV(]_xtS 1/erFEq@]"Ʀ[Y`9Ę-nx@/YҤwL\20w9V51g_U4<ce]Jw(F9Z[S1bws]NSR<Z׊${[l Y3"cOhZǔkpG+ܺtU=y~  }yÐrܓ3*WvAk-v-ӿ'wQ{]RL.M'ew5fǶ<#羵p(~5hUϹb#.ݶy$_&~׃9>' w1ǬiiӈLB^f[L1)7 dJzB.}o n<^_N`rAn'bNA1M?[Ζ'hqOnFU=&`άو3/-$8/_p3EMb]2ұ/H\RWΩ#4"t)8o9:cWLWȅv1q8)`|2hBuZt`gc AvN1_scHBpu "dw~gg@,kO03'3]Kׁv),o<⼈9&6z oպόw; ﷅl|v־j$H VBcAB Lu #|pYDsszcn!UvYLg0K{h +j^&.]W\*h 5 AxG)&tLq1e> ;rr*M_p9ZPݵwo#Q{θC]l}t nX?i_\ Ndř1qh**\1v>vGo!K\ol33M]u=/?#*X^BY43pf־6-n:P@r=? _56W,1enQ8 9:9eJukyt~r;=&n|\2Ǔ=9t0sgBJ; 嬷U ?g~Xu%O˽rYeVGi ׁ*M3#S)|w3| o{Vwh/HV*ȅ3f_w3o}Gt Ơ5]5 g^J8!._)˩iuL I$Ie6\k!;ָq90 S}hv;TO=ݳn\Np.of>HCЦȌS;~O>EN=Z8ыdӿAbDq|އ9APv'$eOhgM&h+cȦ>@B%9rz$5k~ax˜2`)8s}Elz[PP^V{~Ǩ{8)(uͥw,gbs|O U [͸"X<Ϟqhbs̻; wʫK7';OM#cN0C{0ED$r6’^\R铸496=cZt~_? rOc@s׾ WƘM$^ftL\0U2\ ܷEp7GfFVE3h`娸q xCǐ |u?r_ xc74WZ}o}&^DR)/MU$JӘ1gO'm]S?9^BH$H4ܾ|AY*"zG\}̛9gdIq?e0 %f_p9*-p ^c6b-XNaid5 ^c9. Vt/w~?:S##c($*IS ) -Lfp/IEtHLbp1FMQ~r`;k毬wXo,DU~Վs۵X=D4i8vq8 F䰴j߿C~E<Af6fPU3b/s }~bڪI+eO^8k^ ruW^rLʂ#|pǴjJZ!!X:P&?,=Vks=#mVoI\:Mcw|?ωksݎ?2'QWG,+5\[V Bᎃ89tSP?{Zw?'8`u<^xWϟo+R#W>߅܌zJn]f94xyq "Cr#!2N!r48թ_Uiږı>LwI\$$ܰ`Dgxkߌ/`Wv9@>v0ZT . uf%  wB4l*Ԫ ܴHꚙ <w뻘7J9Tr _27gYլ^ͽۑ̿SKZ.!,}5ni.a0^ͬp|"NZ]G(xfŠٰkSVn]2@10oEבCxE5^6jD`ь9NSc5*V *Tg{60W̮E#qQk{ S01#%S:ci|>u}|?36ۛn#4Sm Ci"d|w̙*9dqxwmBޭY O}OD0'{ &Ueyx-\B_ eՂBp lص׾1?)儙yҮ|ݿ?yN=UhCuC٢KpSQ .͂cKq&P_Bh.DXhe8'jqbHzptzزQɪz4ΉM?C?"BEB+3b5ktG|P y ,,x);i?LαO$.*1G ӻ8{ n婝ӔZ)mp+"²f%W-w ;X9{I{ D^y'̼سEqߕi(jIMf!9aQQΚêE&Zf?0%cy‰0uw^ęiM,6&h/skZftN.famO Nv։dEѱTDq=m@|MdBwOo@,,_~VAJwS Wy8V/o>?|_WȜ鳋BDۋsQ) L^ʹW0MػS ڃm QMhXF)diS WJ;S$7 :|Ey|̼m;zD9ďh:˹qJ]L7{ňDY8G1MAņ(ق=nɜmUIKю߉[x:&ǨTk:+Ձ):h0۩  1Ȳ,Ծk"7]?)*,̊qpf^-KjH*ײkф8:slع/Ɇ3s'(>m=Bwn.%tFi鳧xg3*jp!eF68-;s޾4qMF)]MoEQ ̝p]w.dYW2cT :X!o7e"myfbk&. /cib{jq~-(yt>U Tb"`ƒEa`ESp@멝14P ˹{E.9G. ~͓ g{9x. E^R跘'T80oK{?>q~_ey]Gb9.L wB#m]ݤ$$`32rA=ף9j\.,?.8غb1"duoVձF}%'kdȹ. 8ds?0g(CŦ>TY)AP)58:%&97,XӆB  Ѡs2?2^DQÒIБlfnU:{^}E+GtII[ٱP4/~~QHmC}բW'^ȽfV>irp%myG=÷lKIODk+o᾵$ƈ0|p)g9#'+[Bc0)n򥇿ʺO5h/c&#Ü Cș=opWo۱oznUp5[.K5:lề9d4"jD-ĤJVĹmfl:]fRв01:OĉAY-k%Q]Gr5I׆eM:T Gv%3jcͼ0(NT ǓC:_QOẕj-͂0sY>xFs;BH0"f]}FmRxV nzIfhCߙ!ag}rfOh't犎!9FM27}x//?9) =[@˵/. c(-G|s=4~MF~o|F\xOo\z#."NRu_U DQq8: *ps>1[#=:p`cAK˒jeqg NsD2Ζ|ck.g͢3r++_g W<:ê搩 r_u^`P JAYnE-+r?n4PA[vCbpP,:m8/h5Db4nXiZcڎ\!txy8քE+|;'3\nTr(+C;Cr:XeR, dzU _^ϽMc+*09(P~ а F{k?1~Emy.T_3%>k||Dq]3ph{62V-\9L'ܪ7Uc8|AaV`#%z!&Rf룗 ?3AXߥR"ןǶ-1>(fS;7יi=DSB!J'=8Q&S ů3bJ.ًrw>f°5s$D'f0ᩞE{pB/E4~VY-mXG,:E!*f *V0G6I,,ۯ{o%2Fwΐ<svS;r'|q' Mo.6|5qR8 lɣ^̄gvo.x%7'd/fxL O):|AhM x{o\cH[wC%p33UF})eו*CIp#1Q/*YZ]xb" cCxu`S:E}{[ytI& _fBU #+*4T.j1wkꊹ( ߞ l˥iUT'&7&Y8ס!Y܌6>q=/7E!!_0B!-xb"pP3*,89uݩ=?€ -J oJKZn-d˵hUmC|Ae~<nۆ"1C%/StK x\}{ IDAT//hp'R~Ax`o|Cpkb4l0DW.0#4º+sDҟ=1I1g-uʠz\b\ 'Ğrk[-񟘨nn ᚯ +v&t^ġ u7xƿza!^;K6B`my-~ʿgP㕸&(!?%=W*1k qN> 3WtDd=!"LCwJ>Io7G/`w9tCu%L<0hزIL_ɦJw<<{PDrY X7 뷾-_ >_efUuܷ μb;s36{SǾYphuekI@,7*MԝUȊu%&>pEgٵ ^rzEZ!6ҮȦ{aVTkokW?xa@|uxbp2 $q !0"ܵ~ݭu(y/ )%/u54򺯱nd9IL =KMmORR)Y`qay额tQ5 W p\i 7-]bFs^C *ƃ@)lx8_Z 1/OfǦޭ/~E-uD~36愼&U e^f6=]Vϒ#B1z,/ǽG8$Nf099E$:J,4~N O(T\(![\&kܲ("ޟ޲VXy+\tppzI0p*>9Ůmx ,Ϥ5h) >? k[ŭ->2J܃!ֆ;6p!{gtN7+Ry(0/WTCsg*$ :K"sR"Cck+_饌iP2;G[^'o~?y]u羒@@0HF nt6IC&v~plc'tFxB6&ѬBRi*jTݳs$P٪JZPOo<3ỿ;(ѥi vfb|?l<"vX4}>_\8b,NJ$ i%TOQ˕Ϙcąyx{ũI+޾v'$ڇ NJ[ 5@c5T#k$-i^P);zlבjp%n zW|ǀ+A_5"L5&CҜy򆆈Zlˣ[>ƭsFԃʝ˒aegC |} Q S6 57rX=9ws/iUM?LiqYF,H\uFhYGL"JwRrH>U1ogM3j}r_5 JpΌy:zUŠgm~ [$t9|'okT,CPM{wlWϦLsC%d-ZÇ:BN+{*[?ƻܘ%F5zJT2NU%S\vj1w~x]\7yBRhQ;[$/`kRj2Jbn롮,\ݳmWG%dy I3rXrMq4aլkiV͌hUAL C%7ES|wfDPZ#_D@S\EЗ[@t*J6 DP%m<:xbz{.;e%9"V n|ޙJTye*~u-ͬޱ.kIܺE>N 8iPhnCZACj(J(˷c2TS$bu[vUU%}H8Xhi;Dhptj?4|LթP7_>/Jf"l}X _7ϴ49{ T1m8/g ^Ѱc72$N&ZD-]o,@K:Wp#%84IڪY}a704(w̬SL= =;-V"ʭVSsDO1SuΣ,ݺ&Ē}UC/ZD#¤18v֫bJ|0mXux E $i_hP<#^ri^Z寏[C厸hԋ Z~ {[; c"I4ݩ /1Ō>& 4*^1Px U `MϷ}t։Qrֻ^%!UIZ{kCzhlq0oJa_?N(hb sO 2l 'WFj\;C@MSOfś~=TO*毅kfXPNȔT`Go9&[~ALʹ;xݯ f,~]mDZezp 5bkqgW̎&]Аf%\hJLe61Yś圫ۓوP?~)a'; Lf炦1ٚx% FʷC8{W?DK\ՌxUG5IgujJ8۽au9:^YП~GyU *YBo!\Z NWW/fՎu()`6Pc8g\W7dq~:FARRʢ,rRgA}eVeݠR Quu5KsїXy7/I hS# U\^IED e|8aCS {Q!S`Wt}ݯj:l}A<*A"C7-x/Xlt=k^ śdxpS*̿J؎׬Nq̝2=#h$to1QM#=_%!VH q$8Q:}<  gBA_Vc<ڍqd&\<?$Vl~*T~>p=6O,6NR;N᭳'.t3hZX䉼Uqd<XlBHٵxh㚺 Y{MYCa1^ ?qS%H>Mռ />A\J3;OD5p ~R EH[׉BxԲ@uY;uc0_[w ??τk':c¿%'p,,Α ^"B %u*pqlVoS>nU"`9H$V:β_ԕf\-d<~r=NSpFVTUu3 -Xc͙WLJ6R'#]bQh /|7=GiPl~@x_h0RM>1A 7 S8V>>HWofTF#M~]]Q bP&- |cڸT\6.B^ izd>EQ>;ן3 8Q:}.<&y=d윒(^׿ =.8WEɄ1cF|ZK3Q0v|ЬWCDAEH3YJR_56T2A@IYѼUcKbPۊs#&C'燠D[^2^3nwn4^|UZ‹%ձٕy@B!;Qqwm}WxAOoK]}śd gM8_*Γ:AdSnO)mbǫ-ܽ|!=]a3BPwsײg6Ɛ,xdi9Ą˘ʇ+.l24D. -lYY}Qt{^ϯS@ U-eI(8Crۃ[ž{ٗ >OSkd>E@W 8 uL;SEݫH} ;xT*F6PYUxq]Xql;yxy:|LJ']˜ j~ $fVu޳G(> =oj-%eXY*X 4 9bZ#U@q\;P:$SZmrHχ4t}lL4]LEʀ]% NͿֆ:Yfd>IKO<%{_/OWHwVTRS*$P $vwʷ\=`q69o'йP4B4_DB<=l/V^ c9&g~xmg y8v9yV?ğ_+Vs D Nu#]G OU*6T`dGfVTwDU!+ԅuW)؏N-,J,nw(:_Ӟu(9e]n~ُY}Ρ8lmjoYba83؈r93S+6?_rxL64pxʯkw! 8$A_8TUgH6*yXSʱ{V.-KE½?{U;FgRܐ`,4C> `%Wp~T ';Ǿ߬kA@u(HpCGB2d׃x F:Žѓ@8-qkiy )rd&HfhTIp(|f),c^-wP! 3FŪ-6olƏh2 wz [#8|}N![ƠPnY]fz[g?Tp`Y8^}!(LI[|(ܩ=bx7FNTNrWZP5_H,He5Uh`p /ݼU;6 .VŀH@-il I!Z᾵q[R/H4W(,tJM+0}.cM-jH J:GÇ抨OtOߩsI gϮWZ}= {cIB*08=NvsU>R& B b{XU:V'ʾ|H?M=g\C\[3rba<4NMB4K;7aK^4]P}י's؉ %L˕\#Ǟ_]B4fcR8+2ƇV 1ܿjzG3"^ "1Qsb+yHMCw#Ԑv=ܵ[bwYhMwRFrH)bѧ5N8˾atZPp=zG#&*s_.}h-^9rk_]O]F$ ;[[Cm4m rQ™BA)x@6$F#j . ,#a`GXj~n1jƌ]-ً(Qeu .[JR,r'(%"Z> k`S+EbCR5zg+ P =VXPJ9\ee -kT}錣,biu=q~7iv qS6a*  8A4kM?W>S"fUlmS8d;fh=kiI]n|c'rY"% BW 2QSN%Q%V~gD0qXsʞW5p#?Nwڃ8 %)s?86h gBA<;ɮZQgād C>7hr)%y$w+矟O1\@?sQ|̨3R U;Gg _,- O4,r 4pH~KSʶn><`fw~sr~YYv:^#8S..6N!ư4Vq;#w=7C`w!/RY2.!U: uYk|~?q(L1/цy.!8\ /fxyJ^DA, ~^Z x+,''(k[ {$qt<&%d}| q>QOVtb!`%ޕa}󺥈ZiŶl7ب,7_40$i+-meDW S6L!<܋qSbZ.sB(>C‘6Z~]M856bx@p`Ei VH>Hw}y]Kcwa{B)%p+5pNX8ɣqYS|(HuR(5Rjµ7t0Bb+2 ^+MںkK۟ A,i)Tl#K'?-~lk݉ qlaaX*6XH%-% кTg{^t6ȴ]AQBwٌ9<3my锼=d_~!:+ǃn[P|kuc7P?* |T_kR;|V;Hj6 crJWo\/knd4},,9^+6WcT|?fTcoG+4O20Z};ZdD;?zk/;@b7&ryx)qX`㊈+QC6KΧgK zFFC7i)8DLHg7=-" |[w[uxWhr!+*#d;xߛoAKl]p%R:t' ϓS2BDI@'6WƒUaɳ+E_͏hIHLl5pX6 5ι O%SEo8ix6%*ur q)A~ڛo4 m 'T-v!&蜩O@Ah<y"6 IWA)~.@4gMJԑU|YM+ǃQɝg4I$ؐKcw!l%})^9*ǎ ~\M2ōradJwW4°H5P)Ppݒ7f'y>NR$0pFN ['= q\ =D3'UlƱBWoV}72_[]>K,\]Z[-N fiPme;L~<i|l+^Xa"qdU}bz3”zA_GCqؓ{^+?6ݽ]%|HlũY$Ȩo6{h6PG l@JoV"J$'F& *NM+1sgj>1A msp_S2(2lMH|O8]ٿ(-*Gs6gZPo}߰}TG-]Gٰ *=ǃ1*dew"rҺ<~Ƿ=Bz(HB"iCaaH_{ @]DXL4bʵU۟3?{]dy,v: _$L}'s8^tk[⏖|6Hg)ZnA^9M*\)gS'(YrY#~TA˝2-!_!w]AxVmL*JƯ{śK64I1O_1"f" zX1OKD `+N$^, D;~ $q͟/?#Eԁi r)fۜ `=$$}P|QxV^g:˚}$TLMvS޻xPO?ٲ?u$>h%JPz40lhOL%%@O4 iy@KV<4$KU8eˁ|C4ii؟я;>r{ɔIUPI8C~'tjhĶ:ڻ`O'w @ش~)>`#IPk4 z{dEDՔD('fS +H2w.>y k꣩QU%=.ܷc?˷u{cJZ‹by.8l8,DÔ^ATUK{IcyY`CAىB)Iyv66z~-ͥL@%ErQP(L$ ͭYs?{ii J=)CCxJ\E9#OmI VjNK4:ʢG8@IdOb0PTI#"ro>#ֵla.;3WE,nۆY7q:C")HH@%WB'A^yq^9025]un'5EExދJwfٻ_g!"Jcڄ̸rƖM_Ɉ2T\})jzmwsXGz *+& iQAS€KB+"J ݰ S\"֦1QG+_~omݳ|2u,ڰ[V!QIQp^V ~ƌsH<^}km4cs+v8mnp5u}RzB˫9QtB.AڞzNC:BHn]l9n2[Mq=Q)ݕ,co 3Of9?I.MSgU9ϡ~soda ǜ MAk U lh ?\љ 8uVy+$CK۴ HHE=mW)& Mh$zu/7c:d9ȯ/Β\o~ͯzuMpo׹# VEEC]7W-*Ey!ȣxS>~ҭOrw 0v PBՄlq/YyBwxA'2IJbs 3*@)B'@]2d ZEuZxE(Qv';kv .4B#Ȫa z}{xj۳CmFQAIH$4ǖݾqjbb"TBf9?@1&Q~m{?c0Yˮ )sUeVo97۳o)3,@mww~Kv[{WɖS!)}g0$l^ԯ$SDH EK@޲ƫ,i1z *5Na}$C{S[#޶^>n>R*y ˷y]%W{&\3uF~C)WRd*W[]XO!ޛ,|}">̼)3g@惫cծu|r$8ohJBB%$9N}Vjwp? :PS YZkwnbgr7eի<9yi{H)+,Z].ZB;LqZ5T:>}Fޙ1~2c,\ΔZ")͗DjSRcܽ>?a温5{SZcF nkGzTHq~oooo QC 1j9Mi=BBgyjO3ܻ *IbҚC.Z)cb9_f 6;% hzW!%@?amlON׌T8V24CM#'E# ͹_qxn1ߝ{] ]5*xA>TlU$x83ᒱ㲒6K眆7ŕ߈#AJ]LhRk СXIu3?~؟W=H.#G@h0\ '{xٟqWb\2fUG9% "JrTիn mzh7vsj{-%(i?+^b f/=ǖC/ƣPIC\,:،;#ƶgeնgX{ nF7@vUտ>ҟ@9Y:{ 7æ=[y66ʱrO^4N#~^c-Rr0 ]$$c$i}/K6-%%RWA)e9$ECų*^2yeuڱƌguۘ92kx&N &&z{z`'͇w~tȩW2&q© Y@[ǭo< |/#E£))37&qN{6d6H#b$rw-wcԈN]JH7@- K~asĎuٶΘ{: u+ Q;{~U,k_zmqaNd\M?D %qyL3a@o#)iu<9]c. ZT[} &^g(O$-!)cR~aqnM$#9Aa3Q0eTrEzxmSn;rѯ87$1G:wwyp\?c.~MLhJN>({YjUWT,wq׊ԭ9 2wh scچ^޼ϭ܃+M1 }xq-:e9!BfؚN<3.癟agdHŜ|/+W] Ʈ*6t{z`ǀ@J 1;P1q; B)$50^n;G N^Ӭ" sU7!~*d$GC&X $I5#Zodu;wg\TR&DhGT PUJI Dq/Yc)9\qd?i&Q ᾄ 6kdzcliNKAv[6 IDAT吝c Ijh`PQEЬo}w Zƫ`&e'A >©*8W˝,cf^潝+_~*ѭp33 8{bLk ݫy *^!1({=/q/yv'*Đup_r)Yr+k4AFl0Y"LLT{k$#c\{,S4ʦH!q:2x 4gk=i?Ȯ~8pfVp `gs#* , TB!ܽ_‡>Ψ\;UIΰ؈R6laݞ8WKf2+1a2%6a2G"uDZW8Αv`k.E^Ș ``j>+?]wBI>^b$qxE$,2J }:m >vSSC@g], m/Փs+bT.1+&LuT|x߃wB_ [[y8 6QF{ы'GE=09̹|,!tw%2jyL?]mHMXCxבJX[usX?|q؋ڙ8S@~M&sr;O,$]a\#?1 wE1 5:k 'xMW_c2i4\Kd)̸d*3CJ( /'hh{Zپn?L HB}N Qwy+_]$+@RͨoDn+U00W#qwCA@>_<0|_rtjn;}'B@\Ub=OD9*mB,Vyuw|p;-mAKa)Yw M\u9&U$Ŗ5;N5Mq3\,(i:NSpO1RT+>͊ͬ]qm-[Y'5hg S#A4.ݧ_rY'gj>ܦa_p8gʪwKVR롁~ :EV&egŋ:5PVC6`~&i6R3L[a])RZ ܨ" N' k1V%~osٮoZy(;v6lu=Oz$9{AdRP)Ȓ(jsx7C✣" ^qL` S^mkXF4R$@|l9l*]6[gOw> $PeWtTk0AE"OIjJwêmuLRw,Of,y sP)㫂 g h/Kʖ8}xh7{>'x a:hḦ8?~GᅔP "1q; ?ePN<ƽlسԑ:&&O1 c/b˜ ! [Kxh>tfOARBoRL I҄V|YU-Pup#NHC, VQV;1rs+k7#ψJ:b六j/N4(zU{}Exs]!XI:L"dʛwllN"7+<&,zQGR~B%D8 4?EFP\å~uXcٰf5*&ˌ8bIRs2YPr ֲ@IUIU&SU:T̽JZ:z #hR2!uzyz^;濓{;N9& HNsB[g;m[NJvΊ^+w[GaP1?Ʌb\_ͺYg&_v%&\p.;G'oۿkݾ~pJdjjsY28_:H(H-op׫,(m‹zY{Y(DH5_./)Ff:^Sk\W:NMW:k ZN-kYּ7-`71}e 'E*cP3SƋa)ڪ/do~ŮW[f$,4WeEӦ.X[hVE{l=ͭ;U?bf+Mꩳ4fbxuA,KI:TPCȳf&dxԶc<˷]m'SmOס@c;!W ?! EѥGFroAzDil޿ PJ>T60,7,%TiEkT؂ׂʋop|oOvlgx[yx<^ݘ($ΑA|+?Cy% }!ГmX@b hUPzr &sT|*4Z8qIʖH,PRHZx[gV),ZɈ4#Gwd罯jWD I1y&V a PlPWFPP?|YLwJּx'D#6pOaÀgT̽|3OډsxL{9:Z wJD:z{h9zNj+:;hikT{ZB$T*8!uof}s!x"RJwqU9u; R j; MXҠ,`I%ox^x^KT PxuUs HSJcZR^aGD,Tb;N#wvgHc,DSRb* )8ݞܵ^?rHxH w;RBRׅ}T-o=2" \̬S;eMaɌiEk0Ѭ#ݽ]`W;GB`O/ޏ o8]Abܰ>~@ln^$ŜҒCӔ6tsd9k'f1\2b&Ϥ '0~EwsC- P$F nIʶ~vrwrUCr‰H]bT߰F0'J{J\;PdWOݛY/Y{pCp'-6v CLm8 iMcz᬴dl9̶3y_N_IIbT]%TpӔ$IBpTw>[qVX2NQ ejgFd(Y9tNÝBUk˜{MPx #TQC "M8q=ޙ2 ܮ* y@r!VO`o Ŋ%sxPp8VdH!n:OOhPD&Ndp4TʼnUߥ)ptϳ֞${Of}^/̞={k^>G5ul5FIT;MDB |搆ޮ87p# Bџ=nJ:&\ślߺ5jQN(LߧRHSânΚꋤVô8l5(| 暅+7J]/@ WLC[M,#ɜ s q^ݡj 5\q 1=>lk-MJYp 8Sj{)ҀYT)!>N^ShxBr[rMsgQ8+ x_~!x9%AS#?c|밺`#x2/4 3@JM{o[7x) 8;)injvZ$ua ^kbvkS 6ms\PBqCRC-_7/q՚-}1?qyGy5¨r[>u믢#k?$C8(,>}mh{ T电ܕ.\^Ai;+!| mM9YPG ep,R7x1ĔG$. qZ1 ظFWYFEPB㊡7Sn8Ly2i‚UTL6$Ongb>i_ƁTuB4W'yw~{T]ݜ42oAJ"rXW @),$b 쁟<3x4+d2 _v#lZk˫at dm5vloɝ VT2T@Zђ,S4 Jey P^%oR@s {A(N$__yVO4ptlb\[3N6-'&|9:JcCE*׉=s9[ k"j5jʋ^b5RZ&Dah=L[S =S61g`&`I.y4hX 5TcaJFR*\l-- ~)z @疊=Ԭm|C铧dZ Ճ[E]9ƈJAUK+籹%6[ZX%@yO% ΓAzTc@'KxWbzؾv3Gt0ޚmr24P#1H{<*7ĿFx$6WUA W~7N0 Mg;4׏kA{${B*j24DKðBڲlN6 ci&m&%V]BW~B# cIät3.H6]ˡ~k7]܁)DBo둩!VU r gk/|gO[>s01>WT0V+rI#Q["1E/OFsc=i OE-P 94=ݼ7 fb!"L2ՇQcdѩ#rlU vҠI$dž2vԕcsѳe:z;z/c]Yt|.Ny\3Okbٹμ]RٛYNMc*f?FW\uJg8MC(\lVZsp7,CLZg2i4v#kˉs\bk73!a>Sk" jd^,w̜4ᶨ!ZM$ofEy$1OYe!ظW \I RHpܯA!v冼;iZ`"掕{JkPa*:F:.t Gd IDATKu|I(@#-1XX%D:x*yW8*!F?XH25MA˥45ꤹaS% ^xO۷MI)_2E9ǩ}|'TU%@)c~D U[K 'NE6cp;)]zO9>u2) gEqF"v |YaA3EYSyߙoGɤ$2֏X8mru +Zr Ǒ|HB,TBCFTiU:{t.=e# h]YGAtj]Ze1vDRӣ$|rWi~Ď8fd# G"&Ѽ}g]JVywiaٖݞa^7P4vTi1ku߃rޙ #xz^>J !.^XX͂,#Z#d ʯi2(`8졩9g̮]Z^M?/P!WZ~];7[aK#w#ьJ.#?3Egqـq;q|~WXBƨ%_<寷Lt5 U҈}m;*IzVD\sdM\5]4u! ]KR/lrx&7svoNE$D]'YOůcӱZ; 0Ŀ]ѡY,?<1B >=D^Le ucuTu|^YRɑe_*C5q8SLIYT17^ɬ'Z!Ց9RD@'so N?yρV\q_ʷ54,]BO5o[_~v98emWuёkH K}}*; 45}??0RUu8čG֩w'|*S6Y/Fk#f-" K=]@U:p 1ʽ"_~Fub$~yHϘҎ%ܗq@j'QRGUƞQ%$s8I]p|̛?_l9^1u^tiȎ>V"ؾq~0dS[!\ ߼Ҵ@R{ۥMܖ6Ijt¥gTLtTweV*ҵGA #D}TIxb.!/B}\b-Dl]XZ{#cLeVIQbQ׏Ô]C#C&ԥut?t}CO~7qzgGc~!0!׾L-<0|p {,Aċ=#͟W;^ܻDžksZk,e킕xd9G}fuW[^רJDXhL'(9мUɨ tvn4@ uzs06if&\G H^]1't~!/~x VK)pƂUMhQx=}Q cU? -m4Y$TFSuIc: _EiwDd{5&IDž'Y Vl;␀SW!fd\c|CdSAvǜ\Y@(hETXF̱(xk/²TRE]O?Ѡ,C†!՜||bg╆hPh8M xAr䳭eY)4jX)ۚo>u̘4ݔ`Bx?NK④<=l[ ]4o]x5GG Q8p):hļAа\3^IhhIT}0dPb6/= YL>k=4X5[㼓2HCb{#/gbc0Z6hP n>->5F U,nN;P'spoCtP/dN {y- U8HV(I$Rm[9eȼ 5Z(+Dfژl?W _j9BeǨxR\9gqA8#ԖH txэ3zԼ`l_V 5ktfv 6Q4Jz=7dͰjZ#2UL>a5]nx50>| /{P` $+5B={`uuDG7'7׳e|<{E1f>cu _Hf_.k)xQƞ-fJ!d\yf,X(mp43wm\c|A"šs&䚍~Xju؆MH Ywq݆+k A 2H9JxlU4L;67YbaPpQ`j OE'AT#dIΆjˍ`ԘdwDWWbbq;z8(::IiBsg oT_4 3yʡ-L>j\T YnڏG3틶VY͂$a!6(0/-\:z룹JfO㲕l|zG#nċw=LkyK8:Lym)stwL䚍碌c">J:Zpov4Ҳ_E`Kޏ{pţ۫W9GgBךѾV&/;M yFT0E&VpΤYmD<|}?o77)ԗw>ȑڠsC@-Y TOdbW7Ya׽'OV5jmULǹ`㏞Aτ>2-DTE Q:TϚ];@9QpdL<N]hר1}gD]əV&vu}p+;&C[Krr׍6Ou5jm9:6xAh`ugڤ #.ǥgѴyx$t2ІwrDWϸ?CHD)T6tJ ð6WP f}za_ Z < %>~78 de@"QWMyO3kOvnyq T*v3ʤL!h?ߴ:𮱻Qc@MI3wy)j1yPqC'. h56v9hU,$}0ox'ou5j#?Ą&\uqqcŜhx88][nZIT5F; D.xGN,P2`uEb1pŧϕfNj{@Fz {lJ,*hJ64|3u({'q]0iϾYC9{WHl0{*.4mtɩ W0(3qeQs>O %My ly 4`alcE j3QISPҢG9}r ^LoW*G=YFv% l[ڈ.0bݫD5_Ksf`tM+qb!D v /ElZ[H~- Ŧ6&^!Q'hQ\QRxkh<42Pu5ǔ$`0{`:}8.6SDaEJ-ѕmf2u8? a "`MIC'z`@y!(kmʡZFCXnLӎ•Civbsbg7;-S_1J8k 6y!:Ơ *|TQq<˟ Tn'2n\r=]ݨQ;U&qm6 XZ~d} Sa;iPڑi:($) 7/M4fUx?BtvC(j_}RFRh ic&Ԩ1NךH߸xuGuI`FĨ^?#'F*'8[zN ALjب) 6y/Y.Nwž·qHwQ*yF^FVcd!|!%X`Wn!SW尉7*VHE 5ǻw<|Iifɠ5!(NeN\N,!}0/*Mұ#Î#9ˬ1r&RU:>x uG^x5sqd4uqhƂgoQ)n8sd5뷳nnm@ej*ķLIhFAo 'Yxu1pm#ڬ׿:D R2y9#ׯJ{t0פc;OކC9 BVeΗCxƒ;a͂lܜ|l?( *9tu1[ Û`hު)/>9)j֨2H%ޣWձvJ_URMPs&nDi30w~|οj)aTCAPA5Q͗.X!+:tZfz:+R]czؠM}e)0(Rb\Jᓦe[B<ϳh>(A kzqs1Y\ȶx)isx4As)Ɖմ81RkFH&-Xɕk/6҄(X_S9-G ȫi@O-n$ڑxË~{{Z`)pS9P%#pFhJ Sps kզUo8SHype|0*utKx }~زL"at1zϱ:q\0řާfX;$ĭUQ/F$Qߝ.s„nm IDAT܁V8(es)<8%ⅡdQ/XДL ELmRy=q}+O [+/6ȧ[][G :=}7s +ޙ:(('rFȼkRdQ8FflN ~ٸd;Yl{gl/g$^6#}Q&[ҢY ى|h4E4 ᤵŔ(đITe"/U%4BYRZ?Yıa k !EVH%o==|laR`[DN{4߰ ͎ VirPtCUà#DLsq\tK7Ju1xK \ ^Ph:9sykix'GηGFj)9Yw2< &VM6Dj?<[xۚYp9~z_bHf#"rBfd<2i Ah/6 V<. yDp⃒Sjsdfᑜ{G@(r*@k׿9SQ䚥_1x smՊlkƓN jF)F[N:C7ϘZfHv)q!1:{oj25 ċ9Q!OCJV(LtcU9to~jutޗo*vZ  QUsOMD] PW(#ZqZ-DUoZ*1a^4.]}MbGWF(J2IٴxgSVlp8S 4M:x0s"8΅6-$ 2scb޹_)546̮;גQ"Ù=g?^ziG ZfY/kHd]A뗜]nf=\P RA0 -ekNPZ^.YVz ֢̻HX8@Gtud9MŘvR=m6zҰJ3<ۯԖdRzi,T5r0W#XLŃoEADrRTTL=|`V~UѤFŜc~o1kPG%A^Fݠٺ DluY'ptwLD\vX-ù18[Vq)Gvv^Pg)G5R5!!OV 'т#7_"AJP]Tk5bUIP--$>FS|sF"6*<; ?P(̉ɝEU6C>V"dvjDBR}%jLܾ-U@Tyg+x'@-<PP$Xcvxa a07u~.ĤؽwBveBY`5nF_ p,+Q5oVQϰdBn2~(c#h/H l:i +mQEVd.Llj*oX h2!j#È;O1GϺa 22ƬהnfM.UgdKs.6 d z_?g?Slb{I>x'y@" 5ȹ[Ny](*7)5V&a*ڣs/x7o[q>=f(93y(kS7ځQFBZʂ ʙK7|sF13܆M%5vF|R/!/*&D¬I[u%RQ?\Cؔ`oG7o]nh@+,:y#[7bj9+/f킕h|aD0M̚E(bu}=VDF.ɘ=p,n*.x|Ԉ 첱pV!ZZXx݂UNQg"B~A`Ŝql䁈jSq]a5,; e .3TDa۶&ss*,Y${Cp9}JZT%rz]}DL=}|0tPg -ѶyH ^EI^0Jyf"^v#"w qk63ㄜ:N+= h`SBog7k7^GN%3/'ڳ}1\e%oFX(G0ͬL$ :S54)['eÐr_sjq)Pc,d H+H3?!yu@hn/W_!Xo3QL;, D m&x$hBE?͞pV; (ŵT|`3Ӷ}82%BD{^A4Fv^J¥g3PfUZDo- 4 MFiwz~S`(+ƌ_JWneS0]+rh4q){̒{OYxD)0rH=_#ǔ Ԯ |04l\P/!քnGyPޣYUkrSɀ XL,o;}AK<{QGwG8m|1فTP6p44e?ȌIӭE!]?q ΅Zl y6ܧܺj;YT\ <:дG&`;`iq VX#i {B"%4^j w> Pf!]F5oD})C\1KP7b ]ܬrTHwFiL (ey>sW FB*nFY7*{^'O+׮}ax-8rCT4Mu-mo }Y +F.Iʆ 0|L0Ʀz-J@\<! =/Ђ\]?Bﲤ=rhwu`%$fbdcۚ / D]%|8"١Z Υ=wa}"dV 0Ö2>#<] oɽ%a`Q%"\%wvfy yuEE=H)(N+{xOo/!萂4ƽl{!{.gr\L+8IUE\RX~L_?̿~>4 \֨1j\qMIbT57 O v*tV!rkYKX5s1+,)''%$#x>3rega 7~rt|ѐw]m 3aq!>W;y# 5j8hJ"ncڭ,>n_n$(\j>K[n/Lx+9fU%4-$h%f/y$:WZ¢)w/~./&&_4Gijj.=|& vɭO\s?y*bX?jR-=v5(Z@Xȷ:2 w5wA>q2DAg\vFz^#J]a dR_?^zTP!W=" L| 3&M/k Y qt_YtE]kzS=gN[UqJ>wGQdmqը(P!@Č8>f%lgg4EBȈ{ U7{s## 5X4?>ŬdW-: ]f2?~?yqNjxrr>qN38 870kqާNG ;U҆=[GO?~v@+d뤯F V)a:Ncfr,\g < qF,{"|5KhʃqEh;Mz"QAc!T z;'pʍl\u w>m3p!\GW5a*QDR1)$ң<,2O\{_ ITVawB`݂\zknw"ʯmj {A4upfS+BɶKahrnVn{4A 5Fy#k7]·)ÜE!ed4U ,w>ǻ^-'34oNhwS@%*2%6p&s՜2$?Wj' g[WͪKX=c1.iʭAD7qQGF!aGrgql1H)MGJP A¨ԗATJkXdၻwcǎFwO+ܥT[/[y>>== .7[U_Yfx`&[eb5q/h;{;XATW^9sļn)hCge#y)@;p7IeasϛH 7mU[,g" _"4VٺQQ0SSeװa f*J0Kϯ~+;od?|N76PqlYzKAk y 2?+=Wn\O?-3w? yT㿴ÐKĪ^hQ@?DН&4{ 6v|Y&L;jo(W? MA-DH^s{TνyG [r0Q@%CV.H _ܷ!&1>!+#8vv|lwMU}>F:uG&_zy/&P^+k,~%yQsR~`jo+Gw:C3 jMˆΘsLꗉk`xO*;okP*&~d/RH9TjMJ\r>xӟO5DTy d@95 NY;)'zSg{B3pl1\ nz.>KχVX+(G :5m {_|v@bWYN拷cPGe+6sWIEC_;y DG7[ņ%ƭ|H莛WULnԋi};ʜdw24jUL; I[H*ƏifM{ %,OߥP!qx(GxT%ųvJ.KUɂs',_cNSͱ5N5㪮"BjIl޼7,_ [rMk-#tws{_H{B<܁\zT+TTYvbJMUмXuS J-cNbf.|44 nƵ}d4~eܗ~V1Z{QY 5J[|IKEnd9~5BBoˇ7\57q"U&bW5xm)&@cy2ux'x-p o]([Vx1k+mӿvoԯ[Dv9!k5=L,uwpsooSJ|`Y$֮GFيZ;␚Ӫ*k1S/.Ə1z;|}^;chGp^tg.e s7-aKa3t˯_xk8`XQXS&vb>:g̓쾮Ϲ׍;I,Di$Q )zJT#KUۢS$)Ǚ#I2dLbɒ"ck# *H$ ) @wq~ucdΧֻ|%~KXb7M~N7>JNBJa*Sf5w;Ph5'e$x_^R&DGVLO__h h8qE^wճWld[zV^+8>Ǔ[;p@!7,*dTQ J0GAc۫J; qXI)w(4X,L?`~6ӕH"67}7 in%R4y5GAʻY׳xOivܓ8 t:)\=qazHX'Us5;>[%.j3MGe%+*8|=. y*ePI]{>r$JX΃+ =k S;7ea<.9j0dF4$ׇ#S(xqBb攕>#@uBǸI<>^Ʒ]iI8eU&u>ģwl}2%ާID9!k"-1a~k|) K׺,\d,QIr\bmJ%9:n Vfk9cOs4dTў_%AM]0y$>?_<۷oHz2ǣm"Hˑx sVfh_{*pW'~Q/8r䡯12Xu_gˁ# Aw- 1h#͝X3= deuCE5IHVn `]KְrR-^ìe>Ϡp-3ŽףClͶyN Ee̶ͦ)h)Ѳ'!T^qk(^t W4AZOڧ\4Ud S`j:G^B2C^C<'{1yZ4_)+s ܿtϝdq< DdY>9ƒ2e+fF:uɮNX oIm7܉)ჿ=8gσbKo|S hY\0])KTX1GXA6| zG,_]jdT}HF+㼢q) :z26,\ ˡ.s*qg:]'{yj<ڏ9qd RLB+@ xxjtghּu{$e5 D*^IP]lMMj.uq}ܳbko;~vHpɓXDv 9Akecf8 -̳cE1nDw9 ޵FK v^p_?`#B4 mƤ|Gyd{y鯲<2xDž=Rs98'@Qm/lF]w x_Zw0kJ'1m}آW$(IFc8ºլYMWzt[mS /9犊  /6Yu2ChXę~Tns3,厞B5g,MtW&qsx-J'}m -YCX5rl*3@<#iPڱlyW^|c .˲fA Y)s Z,ĆkeQm˨uЮ,i bq+tν+Cq2ya02%9GڦUq U@hn@X, #/r~M]F J._#?SfƅjlS=IjY#k\٤Ü6VX=<{/}ށp_H\[ACKqC8i#TBJZ5Cu_ɇS"\E0x m ZaO͝=kг]qcdcw\5i,Zɩ1~\}GW.(*P+^{oőǟ2*pҵ]>]LG!9.sk'kCI`496p{qPJ9 !LYEv/W1S]!EyIA?*$&TɊ)f@_3jJ$8 үs9H"!/P*V~{{i}#U ;籰s¾^^=/ 55i6 _|8Ox'UO(j7E̍ݦKQy/rcqVCRGɃÆw0srqi"#ءGu: oh)1Q^=?9[gy#dYO )2I]G~ N\ѧ@WWn*}_#{x e].F:k\pCO }@!Zq=pHl @'Q-'Ր{29D1tgMRn^cK9!3[AU2R_6r$ OӜ6Qom"ޅмB:!:0 EVdso_?KIhC5$ O|8WJIJa1/--}I~$L'7w>ľS-pK8ccR& _25 zC9w.D!dN5MAnaPfa\z&xm%RuDW­g .ffGW`;`0iNp/)Жׂx7ʢ|:~!:+&RGZ Lg?[獋EMhg5{d6smeS\b<5;`\K-Crg>i /|g~Q\N;p(,aM+-aJ6ŝfQ;pd/۱o@x xI>?)tEPйm߯V,i.x헬X#,4A ڬ$dQS&rۜ]K1=+W@?1o` \ɴ_6EMYWCŒC>8o.i<7Z(mx8Nrb[-f/3_e傺dm^G]6SyQzQ0ʇ׼"}gN|of:|5h44/ʺ,zyA)`٫i ɋ+&Q!3o%I !s!9҇qCpy C>H'aͱ˺z2nǪ:9$aSюQq)9tvKGvJfsP=  r SV~PU4~U1gWy|Rv=})C]"Rg}|.`9nԤ ̚ ºYhb>- Ҽ8¹͘9wq 18_ywΟٓv ?ȅ (t0ڇnY,0N2m2Qɑ; ^(w728`\P/̞q sKQ=E֥".vdWj჏{U{q^>;^EG4Q@(ʀidˠuX9Usߊ L="Ť)fK seC[MB DwtC\٪-ⷋL( Wz%EfRFLiMO*Bbjinj(H*DһbWw@0h,T2DWJ^Tnא4Z^R]J+Jn<>Igy^ҩX9o9]7U&Y4+04Ñ- LEiv-`J[nߕДJ@fQt2?ard2-/Qf`<[̶;xw7'Ο.m5K$hX +<IdL}z٦j5SD!jSn+weqzee,>ʧXCJaj7 Wc'ٴo+[槽{8w,CC4@V&{) IHԕ4,"ŶL?UKmV] ׁFg`;DèeЇr , I &L1q{(!CISuW"n8 mrLi!#cV'd20Z_hFvx`UR>cW5e: \<3?}vmb_}B[.ey.,^nR-aL\(ڱwnę1]mQ]vDr%#xjޅ)!ۧjȪV޹f6,\ׁ֢69!|4sC0c.d#qx7jބk't56>6+w^οy!΃}16 :fh,cUfk4٥vl C`uNW0)n5zb^zVd&{D D~yц^K5Zrs{ZQQOBgeLj@)ƛ0MkU-=Tzx=cvhoDJ"R%4Fh4h(yhV5weyUS.^ʚwaqz:$fgEP0}jD#:GmӨV㲐xΜ@?9~$;f!9Pڥ&AQuiގgժ0g:cjVۚszWu9˹uV]NGydz_\GT|R;]޾F" q0맢@$oc36 2ff_kPD7SgzW5`NEbSj97odm|zRr%~vdwY2:GnqeRn}3w,ZQ|NsؐHմ4N_aרiJi=Dڥz ./&=] *Q]ּp;6,⵲kL:˽{~l/{_㵾#;`IZL EHs&!599:Y9/aL7X7I};sзT1Xm8hoFq,44RcH%hW 3?u}'r~ƛC.|ePU讞jƼcOX {&Uk>S&tBfMɲܧGL5+0mWo<-<& >$VND/ۏfo/N,&Z/1Oi5N?8?CI,[]4R\IIY<ә=[,g9^981W(jV OcTtk 䯼>>+,yzF%ÝzU{7?7χ+Zغ9޵ᨓkF&Yع>Lhterc3ib)Jw;&=Ɠ܇~ * IDATx%0:s6+iݬl3dbPL iiS& C;k[Š5qdJƾ#0IfQ.:&&LIZRJQT @07-踆[%?͉co YnBXVZ,zeDQTVa,JhXr*eMBfFݰ cr8mM֔>QN Ot;Ż&t['m*\f+Je.DZ!d1e3Leb8z:1n aʸX7])_%28dMʉ 嗚@B xGΝ8J=sO P"pyhT-\”M,>]shH2E7ƅLpTe9 TV'iWC|K8`3& wM_BڬSp6m7]W4fKY5!iܲwێa!?EؔPʬ|`7,6]ݬ^mi;ÆׇqW~FJs~:>hylV7XfrsJbp5r|dOUrp;P#uF:޿Yӽ *tpc4K! N A4Lfds2#Sǭ3mK泲{3'M+.iiZ-dT3gTꪬ|YswNC{9|.zҤ]5]jͻFF̼i`nMδ0Bsi1)_+~,M:pj[8>oI)KV:_)!wxiƉ'9~u9IHF\2_ % ]| +V ćܳUe>2F7'6^[ tZ>P6u;X1Bi:l{[:k Ru*O5h^CLiyĐu8Msfjm`1;~4'Nwg9Mċ'K-~_0zFB潉k^,wQȼLSYٺz_"cy|%K KkbNxhκ<>7hƞ:>p¶8ً9s߹u.p n3#qc(-;N uһԼ)c~%Y_d jaaa-ug3Z1F֡ "d  ?9Dk-qtOECaaaajC|_x?ԡHck(uSӶ"{ލޓO~Of"PāRy  ݑpCǕhk@!HjtW5uqZ&Uǹ{;6R:/1c}d(J$s}(G>!x7Ȇ⓿3:B9S 0 0 0 hF 4>[d!t h[@By4j_2!GϊťJaaa1h9ÝC$D&ɯ}ʳ8@. />Y`Yƚ<˿zq>xd탤\4)Bؔ~Hc"6MKQ)kҘj2%!uDNkJZqV-9UqPFz^ } 0 0 0 JtM{>C8Rq],%F%/pܳJ)Z mDWf>kCY+e/4:;~&(/ų?ڏ|MsԽljPr |Pw1z~(h#ӌٓ;q߲;@]y- tDo4U90 0 0 0nl$ r'iHUSox@bbyN k/~b$)xw?pwOj 0 0 0 HtU$ %(3%J#^>G!޻o[hpxr]P~W<=_ܵ6&MɰWJU^HȺVF`(3*N-}AE!aaa1xTCfUuR{vmww=)QjIMEX/0%K\'k?6(9*Y_](kyf2fuLG'qgAaaaa˘3ܥ':qo޻b_vt'J%URZr|ED EDpQuB@Q_Zɘ$=ނE3Y4m=,-݋CA$-KA}s WFV6f3 UMdógo=>ω~^>5.!YcfČ+^!i:7MQ x>]j|h{0}bYnX5laaaaBʦPJ`{s|X4u6@Y5_eg"8<9bE%a,q<{3)YQO]iJ#!hZxhTV5kϪ 0 0 0 #9]!iY4,z:=ƦspZ/x'PBTőZ4]n/XUAǪJ6LP6q)>/p(aP'SIhaaacpR_DxA]\`5k/-Olg/^<5Эj|uNBf? T>$ry^˪шA*.y|.ek0ӥsR>213x$3U_wi_ݨ PsBN)3x=f24k Ox 6X1U0 0 0 0B*Se >-ԡѝt̚4WV}b4 Rߵ.4*Ao?gpJ>ShmD5'׈&`~s(*U2pqբCL|Mb&4ղ|kP!zO9 i9|GmN 4' s5TRyBbR>G9%L%Zri,aaaa>J2 !H,Tu9Ξ /=u-\+6p{ϪiyI4Rzd'={< GU!WʷQ׷G@.]r aaaaOKCP9qRd 8hx\2ݩ0߹ܽl-rN9":]d C9ް.Voy)98 σ֊lkH~2ɡYX6$3,c4m(k[=!(9B>9J.7u![+顱8ot_O#tugsdVtp3@6 0 0 0 c.>hzȥtDA1}"zVg zV1q,{Gڅ}˓BPMn P$ԃ#k߇HF Ɏdȃaaaa\`?}as$ix .h[xgvn♝0n"~jV]̎YcH!CQy/™ }5 0 0 0 F3I!LeE̎|]ew}?ƋP ^ A,jKYԜ+& Rǹ9^o/G־"nMł 0 0 0 øC!1TТL./~oP@5hAyF=ӢĬ;=5'E9O5QSG.u$sw`C̮\0 0 0 0n ͥEd+}9wM#ҨıtbW2B7X28sc[Q p)pWyb$}QKkK;W D@ 3;ѵlj+J}o 0 0 0 0n8ÝxsBQBq ZceR_ۊGYΛZѰx_dy+4Vƨk9:||PACz4h<8UjoXqci.)910S~랏3qBobʆaaaa\p;)lj0RD VVlΞwׯ'A^u`+bd+aaaaƍC2 'ć]R1\xŧ !@'2$m2mr }Q\N&3hGlve<${]< Laaaacprѹߢ `:JePό.C|\m!QQrW>qQj@ymWJʊGq_laaa A.edx%݊5d5<}Qn r{Ui,*532Z񧅲Rheo+*fSO?q⑖gaaaq܀˘+QWϾ˓6ًqx婜ԅOfEDa\ oޢmɖtؒ,[|\ےpl#_ RC` IS@C_2ڇ}!4I└4 ډX8_c;e0^هstd}?>g_笵ǚs2[hF@;rt6ٺ4P .h%I$饩icXs/Ld5ְqwZB)IcʨA.AOZڤb-X}C.}#2qe7ݯБ9tnVWk%I$Qvd2˰ֲC.c8ae.t@:E13)Y(QKB WAxƱ~$I$IG"t:>t=[_4~U+DM2By{:uƚ@ƅDe-탏1ޱz{M9dfGƶ/Er%ؒ$ILcY9Z3ٮwZGfO.t!|ZT*B_#>x{9oc%I$I^^H!tGy_{/p5Oߥe2Fg2*]+RIї htʪR0$\B~卷yf@i"r:#M&$I$Iec>Uae/sĨ vg=J'̤Z[QAIFkϹx2-׿ $I$i3pRE%ZL:<{7{_C>u<RCN:RG6z83:Ƶ.6̇i.z!e7fx3g;)uXT I$IGR8zF$|I\/g1Joq-hNl.FELxYj-_ljq‚$I$IZ ܽTYxX`SFK ;wI}&QrڽL: CLJutqx7re71tt#Λ 0E $I$a}֟|z:n-OkgdofpU\!tw_cQkAAZja5|}֡5 $I$I+(Q-vWANC׾kw\'|oR{P01 'I&i^Yz`2ҸB3cck]˂ $I$xgSJS ܵ=5.>< ]):&U=i2zEY\)I$IV>w/QP9tVfp; 7߽3=&[LJ>FꘌJǥ_-wY @AHo.iI$I:R]Nw/Tr7?g,dqt+iYnP /sN+TWdJ$IlfwiգR,_b5/|qABTgN^)D)E,*13[- K~:B1f3ng:{wFrL ,6.D$Ir3tv|tAj9h ']T =% dGB$Û~R6=n-xm\2}Dt$I$I'wBzj{Or{/w]K2Ⱦ .FG$ղUjOdP 5ޞbq$4Yn޺cϵ톱Q ~!I$INmb t%P @IC]$X*-|[KLr].׽-sI&u*I$IWQ!`%xh?|C1q] ̞ArWJvS[\.i6&+JtobӆLfal$I$I+_N Aem3K_հrL*]n V6㘾tI$I1 D3w7|?_$?xG¸(+=] -&LJ(5h,e!#Gyny;GNޒQɌVwQsH$IuZXBt Qv$,Ԩtنfb>F邫׿?0`YI$IW[BIFvν=wg}wP"eJ􅞞U-_(DM2J:ί®̭^˿p텗Gd*3j;h+$I$IV 5feL2ڂȡEO:_aӆSa^S?~3O2UR6fTWJP&(p+6MHp<^_Z;G K$I@C A.k̘(}pE헼s7 0LkV Ѿw$I$Icc2aѺei.޺xwOyv|zI[AV"  \[}EmltCn2:6 Uٷy$I$I+XT$;w>pw?g~ǵ-7r{4i:i-=PEø T$I$ݱUxƇ oK_?ʨB|."̶KBr :MބPhqU=i]q#oum T4P7gG1N$I$IE#(Ɉc'.ש0T&F\u:g?7_/&CMfR/TjZMtbD_GzYD3Nj@GYYᮛ\?O/Zg{R[j{E Sꆟ`Uv\ ޾םkW柭ձ|cI$I$egLvJ%E6ts{o|S{JV:ƭV IuT$KJ ٷۘdd-eO?NVOky?Xӊ1[$I$iJZ&g))CI"+P)PJ ϿWs7n㼫}?x~3O5 [ &jQ$Qۑ$+oKY|ka.9g(ܰ`.,A jQɨ4@cFcd8on/fI_o]$I$ILWbG&Jt>r%ʧyO2¸ƕ.bZVWFt$;̠#kY o/.̝6-b}(I$I5OP-$c__"tdVj)d!N<nkma9mT;rƣrCySTvԠ't$]&} /I$I:^DL%›FkcfIJtC $I$IfRNڈҊۭJ6ɣ5ҷ3|?F v h]VtiA軶9K@v5W=B؝HF$4%I$IǍi:dNU:%زT`(뗦nKY+Q 9-p[sOf϶ٶcw?̷z{~[|MfR ln~t"ٮ=]׵1(FĖ[ngeF■+IQ-vVM[9휳e+ϝ5Zj0:%֦$I$I^\bnchP#3ɠi|o_,?TVfd+pǽWknemwtu:&Q#$I~~yt=c}7jѭe{̨cfZ#)t@Zv?ViAsU$zdnpԶ<!~Qݘ8Ș=}SC:P[gQi\_=M6nv\YasO;u1Cp}B5.A$I$鈲23?z4J]m%h||~]|K3vuP(QٵmxcA]Xߔ$I$tZEciȨ %Ii+}$І4|6"x޸~4 `g2| z} iS\5Iؼ6o8e~$. nqjG$P ߫юj2]ۦ2$I$Iz -7J:J%fB"cnq%Wnޟ1Ceu$l1޳-\i!MYbUl4&]$I/($OdqvzݓcZJ5l1.oSarurpaHI9n`vt,A2?Nsg^xš6%ř4384w1?#e$I$I:[_TkCCu ;)߃f#_/nvd,F|V % Dd2{Rm^;/իWO -54%I$,S{h G났!vTnzLlˢsX=\gXc.$w>G0iwm@'z RJ$I$ V2|ѳef~;n'P,䃑2*BA,:VBd>їؐd>շy)$Ievb:6-el~I^N+13:V$I$It(w+0"L2goe~ߗ!==l$}BNz0fYJD=3$d2l;d e%$I$8y-A<LoiZ0f336li{ԝ$I$I$--2(F $<3wgԾ;:::9/QiEUQaBtk*>֏Np+|II$Iv5"smR csu$I$I$y[q*1RaOc?w71Q)Y}J0-7FFV( )eDJݗͻcn;I$IҬ4~hiwB jB:K$I$Ifnʡ_HaO]|߼SLp$I$I$I$IdN $m,l aJdi]&Ln>'{tY<Z[nRLjTvo.]gh0!XغU*e%I$I$I$I$;-49b:Jěρ}^w=] f o;/{gr"Qϸu$I$I$I$Iw:ԐBNSi7c˫|;}_':/,ݾ[0@M pg^|P[$Ε$I$I$I$I%uS}7w=G$5t52t65f#nm\lD@mAI7ܿع5qe\yƂi9I$I$I$I$IG;Q\oG'OQTK? Mf&%F$=PYO;^Õ\Uk&kʱ%I$I$I$I$ C[2?>`wŸ:t+5sr3goFƤ3N^I$I$I$I$I: Țu^t mh|2 ;I$I$I$I$IG;CWnrޥ&`v`I$I$I$I$Iz~k&w#%F#/|5Z-z p*px@"t'3!)6HKjqG.X|2mh.욌KۂֳT`avF 9?%=Qۂִ4372mv;ӐDS˹@"6-hMW\+Jy0\15n ZWklv[kR_tnmAwHT ,̍n.L7X;dzߔK8IkB)㽑<30[t)g*jW\wFONElIfn w3 ep.7ёW-ȯ?VmQǍw_r\(y7z?7xW(-;sN4FPʅR'jom`פ^U6^rMLVH2s\h%- $TrN{fֲ2437z˗wjTf u;#Y{dVvVa8sD8mv[`=&֞phu 񁏲qrn_FdZfF,L4X~xp\.ӷo,uMiu|%/uM5؃nR+jkjFR%o$(5xI1Zh2-x4z[|aJfF޷]'AmrD8}G>CùD//&'FO^l$/*{@M+m|Ǥ$e!A84{tRǵg[^m&97E9[p|*3H(rCGG{ tcm9V O5Ker~i6{m…lh*3xH bf%}{F_[]m7mG|սcvNծ;KZ7ʅR4{S+y&>TTr#[ՑQ, IΑS5-ZeoCk%/u?_0*(VKTXTo$(?w#p ў=ɬQd6}ƒY+gf.(˅\ۗ ҷo,큹Ԇqy#A)}ƒǖu'Q- [T./|'5-킚/:5`x^@\*3y*Jy A$TrPq7.pV-ȵކגKtҷo,-̍n.Lk|zږfjfPp5<{kx >ƶ>Ss~&ȝuг\#{m 3^%}Z?IJ;Ų|߫hlS@"h#!2o$(b:F__axedv7~i׆=w6߯wjq9-$7\ۧ1 ϫy6WJV`Xe8߮<_{/1/Cz8{~ir> ~?QmE'}c_MFHH2sV{yuɵmymVc1Uj?yxMR\{O2kpNT.?~xQcxHߔW\S[Yω~OԊz+j³;smxZeocsqevct-mv;ކ14yGHT4뽳ՆV jӛD/}h@%}n^yHF4V|uWc"Ɍpz7o 8v vMݎ)nbB)D>'і|?1471K%}@ bCj+ 2);D'UplPʫy7T3~0Fqܤ qG?yxZܑy#eA\ƿ= zo [XP?EۡpN 2]CY gH@gmi:Ktz?'w \ Nw]@+ bgǴnyiBTB)o$(.Ic=ɬ;l Jn^yaݾd_}DCR|HƘᠶ{ahnntSu(8o$(5+K3sZ(Zz?wnz%QZGu.$AX|a⠶1^\~0WиV -&@ pdFᠶLxh|/g)a:试qW\[Nᴀ-̡* '=fEVS-|4Qܑ"8i@rﶼ6sgl`Ua@G[Zvؚ39-qV <*AjPA8 f>AU7 z5 w T; *g N )p6Z27D'= Vhʑކp D0ީp.28f圖Ϸ4ը8!W-?K9)'~OAPYY N2j YVfinA<+a81𫨎0U{O2{kx>x⋕-"QjlkN {bv`o9zD8{kx5<{kx+ m:4"wP}ËO95* t0\+ $@"I[=ɬw7WiU 'z,L,̍6v:uݿZφXY*3S[_hH@ hzݥѵf ZI *c;,/$ņ6[mhH@^l8SAZeo'/6[m'%V1>f[^գ-ОVrË~jd6@ wJU*Xѫ=О IqUUwF*>~yWrJ0 dFW;r`,yQ{O2wfF_rp[f^SuS[}@p lks͛ĭ=?,U.;A8wnD/$Ň>ftHߔ>`NRĭ>~y7/y#Rz2]mhV-{]S)}&& wPݖfCR|HvA@]~0s*{Z~kB)P*s9UyA鴿_-]={F7?kZeo&`i~' $.43 kP;VjC|IJrҺ7S3X\ >_*pMzl}5at{O2_`_}_] 'Q ?vA46werW*>Ve`37{~F:cpZ,~g0g]$y.~rP߭l3AVkϯ pPjCB\SbY>ﲨ֦|W:DB)SŲ\--%e/|w{*&au׽?v' IDATS޷bYOkE~ew^o$iՏqeEPb pwuA~vM1޺kAz|Z2kDVCUsSᜭckҟh4i o$(2sZ#NNm|;~q + N{k:'.az9¤ ^/VRz-jz8=% ϊ_E|%}&pZ,{#z $I2#%Ewq|U [XPs)7߄/:ZeqJQ^WPz-2'.x#>OՏc34èB׿ Ҝ7m(5r s8g18RB chٔzva7Xo>|aj=YhO^n]ϋV(Ni $ņ֞NAmc0-ӷo,>~yMׇKtx`q*o$(mÅddCJGKL)sLZK NG2R^Jl3^R N׷MYS&`7iA\(뿛>Y{Wgw'd x=l*#ʅRbe`\s\bC&)7~mXwЌ%iAmw[^-Xp<峫T0cCޔ>bW\`1J؂1tkC?mN͌{R}-6Eg+Zeoc[^vg7͑ O7;f|.=1jeoiP-?KyLHaZeoc˻Ķ]EzËLX[H Jʅҟw3ouHӽz3,H1+^cɟn.LӜ@"vwᶼ6/X6R⽳>q(+U|| ^4jDy^kO{&n׊@"O}zfN;WkT3WX}chkzBN\\^}u#=юI3_>uf N *}ŕib'HPR Ij߲\(k?m(b7PN85+3ZDS\*#ٮut8s}݅U̞8q:]15152]&liIf4`C!)>ŕi!o6-r7O/̍2a ;iIfc1{d\(W|uětsVV&Ih]Ϣ\SCqGRY⹕(GA8 /_̴rxROC;&Ԝ2 П7p@my}Mao&=XV.?I NgJQ0F"ɌU#jM:+7RE@'=e8=lH NԨjv ڻYύRYԎ+_釉)eшI}$>A8I Nw-r_jhl욤Ful*]Ɯ4~FR і'.(n)ΑmymvO 7୆130vB<#DOOwdte!)>?;fYr+_W-[a8ؐa/Vfq<=$twTޓv^`Nԏt'3K_ҙ)AfeѳzT+TCЃfrxFʤ>I|UOٽ/Hf|o˻ܗp{#O0iE\9evo쎊!`v1un~qӏּ&I6\4acPU+g+ӄ[a8~O4$Ň Α)='S^{O2eB=sNЉ~OiϨw:?W_ a˻LH=eR<P]|aU'"a/~ickI ,~i<$ņZu@"N߾DoWmkK3_rMchCۃЂ@{XQZ.pZ+i轳Մjmym;Z!8i XGu vMm^r'x=Kt:GbX.dNfIO^d#%}n^yeS ʉaz~ih2nI=p:ݾTf G)ppiIf>~㔿_H`@?4Nf>f[^7]q'/{Ki(7K9:D8=o||2`jqGfv :GnJNZ W:&n1}֣\gBH~Zf4NHeZr` ' ܏Ifq׎raQ@_4Hj/VftjG`f9sNW5PN) tknd_#:GT $>A8؁sTdRiiz߸P'mRIcN1Gq&Sv*T0Z.ކNТ,\('Zu絑D'N.v<jfxBS?g@z :?D@ o$( |Ʃ׌2O<}A?*230bҘpT*-XYo$(u^]ʂ1{Кr}8'ć37yOl"`\\=j9i h]v 3)zPyTҟծ;f$|FcӁD֟ʟ%}N*{Ym8=ɬqgv9w#tvߖfć3f|4*M½RINi'~iZ?B)G@Ύpڮ׵u vMZy>`437jv;TzߔIO Jn^ydv;hΟ1V9BR|Mj5V{D'y;`wnSD/$Ň>f[^MeܜV8Wjyu+qUp:ҝ̄ؐ9(cQR@"V?0N;Ojjzgv.j#~bt*3m_v)$Ň:G,L؃7:M~i1w?%=QB뇘Oeik*{9G0a8Ed,vwِW]UɪJaAmZ,o MPʗ x4W8 Xur5)@m9'5|/?XX{*3I#Z@Ƶ$?0-P*/W [Ϭ<(ŕir;)|=)N9|5Y!HӁD8m0BR|%}jv_|=ӊar?ʯkZOJ7_렶`a;y :j3p0S;!u/_1-GwC5;Ojjz_ nᴕTh^*3c!~bgZqWK\#1_ g7U^[P;c;s7F",@ĥ}3Y{w2f6{PuZ堶[-ȵ6޷bYV~fqB) l$=Y*o$(0i}=* Gy呓&€7a0Dnt vMWB)ov[ؔΑ)u5g{pn_W\3-oBH %Uzv~Bs{0Zl{O2˜`.p:t'3j|2m#;Je/~2s3ӪG07TCD8m@Q@ͦh Շ}G*3ad ҝZGNgHPR+3V )_ֳnX:(kO{`' ϳILTrp56q& :P}u5+.0D;&:^Ӊ#ckr[^X֗^onGd14Z˟/LXeJ{x]2ş˹Wv7FlF| L6V;raFqF 33Am\(RtP:p սؓ2b#p <Y0ƿ׿0#KZ9?nA{=ɬUB-HPX=pTrn5~$Uk gRv^G3˲h#$ŇZ5@c"g2fḯ؛7:G̪"?N"Q#秌q?X[ N[)Ju~hT~i<$ŇnHgv˗w|,0D'Hjfl3a8'g'Չ~OKt̨T3uV-}E'|3+NH1Iמx=q<*=t^ږgzI3_g>VR.sڼpch8D[almy}VϺ77S{2pt'3j)kHw2 x^He NbCfL:)TmW{קHbWQO8x6$ņHe/a7F.;! :_-ȯ?f(|2mQ)9XKtҸޕ=@YVNzQS`}E@G[w^o$ &awKҘab ziQpx'O?M@OMB-m^Z\(Ų}ej,+?S<}Ko$ DݿZ *%0ABRlhsŕiw*ΑG h%/?[y'+罳wlTt[^md\(兂 Š`aB{~i;ۀL{Fw?򴔉Γ?yxMRĨRGڔ/VۨwR^z3tG<]HDۅ@-m~QHPFaVZJEqga 's:]#_4;ex#A)ҝq>]ϊDSƶ>3o[*p H=F?[.pqnY_f@,Qn3eݭgYiO]`Bv`Q.s\(W|uWcAk &x`פF/uo*/\-myu+/~O&[s)cJa:xɕq:S 'e^Gb'g z(JyA*}o$ 815437jv;Q.ֳj, pmymv[^MH 1V]̸^ZRch0}Nx4$NVu=m?ܴ$}Ȍ/,$ŇR˹ɟFR)!N(Qv [fwe*(:N( ʢQDۅ2NHҸ~⎼F ֞6_Lw vMq_O+#^rM $iW|eW\O>o8(:\e>08϶6/ʫ[5H#Ɍ owu8'Vk: $!)6d8̧zѾR뽳סS{/ڡ:\XÊᷓ*{kO{kO{!)>?;fLH -pi߉7F4chkb :Q_T\e%} [>xP]jԨu(pP" gV|y *{JZckR X81DgF'J=ڰ\ieRJ1vIsO]0b"=%iLw<T&*g&~yW\(D89r~n=0ձBq݇ V 5(/Vf;>{Gvw6Q=ɬ].a1 F=MƟh>v[U6^rtX{?ZD`פV@1r7rSs.n{g3W8Y[ 3u^WpZVj^Lerv<*I=+Nch*Кr^vF7D8-~gt3h Ni󓕎UڏJ IDATb@ƩU0+^3Dd][pZdBgvG9|U'IL8ʘwfsBx[^-Jy;N xAI hv]TK߾dUגSY)hyö΍.0c1_v$> f}&jqG>w#GpJ }4sM0ct?/_ٖgX}Up>7ZCY*33b&tH'/=lx鹉LxJ݌uZQC{VB)/AVAO]I!5Hw2E`t;⴪W_ 6iÛ3A;8JM * #^_m V^F݃XV8ڰQӆ:v t6>OF+Zeo'/oX2-oC3g@YaCKݯ|yg|//;ὰ N/̍Z.` fVRv/_yl}+C"Ɍpv6Pm,?XX~0ޓ?܇_݀Vz>rb_?sNKu vMP NeMmu^SS٥}'=v7v*d+\Ju^_-&bRѫ\(孴}l_A6\-=Q;.l 3' |RW_Ov?$>POov;R{nZZS W.êPÈP.Yc0.ЍZYOYsW.aPmymN׸ӵulE?{ֵ |KD6Td6GIL(q3Mh#ˣC'/鬧R;o(@N7KX$〶z%v@o5Z3:鱭ekkZ2u^@g8N:=7׎yuz _[y|¾XJD(|m^\d_/I!NhW<]꿛V=ޯ+ٵd\nG ~jEBDMdO7J{hcR;xwҵ ,6=Ôs10NT杍DNhOO/W *ﱀ}D_ܺ:R~/@kkv̱)3N;C7j ËXj>)+5ZO9y$"J'Ag8 s8|~x$H%&jgY-Qh3y0 hD]@[=Q=b+YۍRw8wQ9z9S|V~mENADaXCyB"k1;?7) wt){BZ81b%Cec _^jpT#yK)q4~ 9̊?=7_']`/ |zͬ(2$h\VF{;P Q;;?7m)pkኩ]oE1ޏNz螚Y-XT3%*GXpTUv--HlEYrޤkbv9Wзb@1o+cQ* V@IL ]H"$Ew&tAsd2Ԋ{)ddgxMί(4roE Q)_Ɇ{Ax(,dgEZ k*8Vp2qe\A`lVH(v) yıuwuF ̩E`^L6wKKD QS]\AO|(1b%Ce#10:Di`+jo(]G7 /]Ya0 e[w~6݇:uwu{R=tzqѩo/(㬝Zj q 2J  `?9D~=V,8}cFߦfVK'x-xKzEV<(yMĦԎ$z^TͫՒavV6,RXv -+jCEw맞P)o241ȿiCSPb8T'ÕD*<hz% JTWnȪV]`2R cxv"PzT3A9B8Q-QEv Y-Qb#JFԊ2)ݛ@ԮY-xT`CCdP8S9ƶvZ00cj't ph; ]TH+8d Hp ( #K^6yMA[L__Cv~o" B3o $,La(_slwÞH^K:7?ұ\M ޤ.2P.P0ZF*>Op4&yd#شORzs~~ЄvUX+Ǟ8:8|vDF*8nF;+dvCwPI%{AY'Sz9/~Q?7wJ;(wy븎Lo,,Ob%C>m͐O{rU sd NȨ ` ׌7ثEuWiCkOEqY?b%CeMpӆ&]ԗ;_ BVQH8|(0À@ZVlS(- !U^@ ݂yg_adAL(Hzʇ>,b8EH[IjSOP 0fd֡IDP(ؓM+,j "I|R(t:2 GLyr Q '=JyBڕ>mpR.G)!3Z|$DaN(52h0RN;;zZ_ ~ A1`'"SÕsN<[)t{ Yv˦N,@Lқ  jfhZ$TS!@yN82a<]2]_V50ջaZTN{o(EjfuwupbS@~jgޞ80Y?"T Wt#(v8:b8i[wVgZܳ䂓h+__tXɰzAOs7Ыz%f 1T 'SȩfVKOwF12uSCY+yU-p$"x,nTwHlpBϩ3P*C C MLk֙9-=K.}4Ev:'^;nh<>mhgSsvKd`X[/*Ji^ml= J:ɶiWL(WMux4ב I|kc"&nHIȴX(t#x ݘHlSsb*p 2дܹ{ vkwNg lig+=}gqgEukx=WG ~jG|e˶ ^[ qHAju JHl0!i/6* ÁbNB8CcDޫ#y=S'Y+uЮ.K7#1̿0(P;ǜ\o+r֝ՙ7?"p+PY`@C[\1(,@D\AO|èLamxWU~='=8;\g򩹓_^zL%֣Dd+b+rr1|kɸLyԹ羭oJ|1r>mp27=ٵd -؍T>ag*f9g輊1ɿ]zv *f$}.kv'9$@D\A3?(;q-XO@fGՎɏg! 14M4g{i}vtm㐝Ku GbXv-^Z} p LR (,GbjfTbjYd9cMm>mJ `v @^TxA@ AzuRE) S3oy3*pvİ 2Ϋ6tɶ6:5^8/Ϯ%g+h=(F(?>OhYrb1\حW&+zrc[/cc^T'$ +ɶ sΈɨ@SnS܌(bgxbhp?h^9WзgϏ]=q˧j>"rЮ=g;V$<ƽ3- }Htgy2@ldb8~X2āc (^Vc14:sтּx~}OO/SÁocY< '0 yZ+;"kx+kfTbj cNR3Z28vqw,+ Dy>0vy_{zC5`+l,mhZ @v5Zz3ɏKuN(S]b8i Jp(;?7F*03|a3t#Ot3e @ =৓{F>8oVN3?CAF>;5N;=R=}d+V{txi>. jhw[;|)!MoTw7@ -+/`q#És|xn@1NR W3Z2~ ZIgA yaIcזिX |7{ 1ihBM݇dW3J9r8Ku޸wX>(#OtV&}/7w{~3tvi9_Н4q|]{k@NHŃ6 ;h5Zu 2A~D sR=Ǯ9@CWy#6at?MhW+<ﱀ=P GD68]Kƛ==J79\gD$ Sz`=6i+p98br#p'| @VǮ\ v '(  Gb#Ϗ]wߐӿ4gvD7|~x$fe7ث5&x@p .%qtlEy5xӡ< *qbhkl/Ϣ{Zn G7ث>Oj),#Q1vF{5(Af89L= yaw;@dCQchObÆ5zAS<(xN\A9CWղ焜y1Bѵ&`7X{=5Zz`zzEЮODpmo`<^ZE|P Gve qDU!Okƽ3p~FѴ'}Ƿ3d;^VAa0`6٩icTf3_WtoWcT!%{AA|~5KZT>QL>1wfb8*(<8pV>mh2xé>mhd6|~A1!}$b8z5oЯ·Â08=0fɮ%vS}//=. ͝R/'N|BHKu TgmQE&ȖAA=8ږr7 ٵd}t"! ➞^T} IDATP68<Bmpr{k rA1!j@' y_8~͸ﺽ>kg+ 8l p8JqoW278 ގRm4(x*K$Ѷ扎Q3}}Y&t%o(C(0LzEO06)]kTwO68]Kyb8b'"S"&Yћ|@.:8m58;L=S}Ѐ)t4~CA?<$nM ǵT#[ASt * ߧ&lZ2uw],8K 148]caKuÁ/bTprA11"hQv̩7dgq;3J~{ B(Hή%gpf1rܙ('+M;b|ɶhK&Tw&*c͢RY7Dj6HfVK q*T"WTbh: q';JUQ,V2{zzŃo=?ѧ7^Z^Z5RӋ.)mCg8I ݐ'{,, $@f&ٌ7Xo3ȋ2 jԧ NWyq׻)bw;ɴ`Kx?vՉOMÁ(-( PQtN ~* \pi~\A3(,G R)juVt 8 ieaW9g4U T>A)8oW%oliC$ '"S#X '3R12@KͬXUyAu(;k8&TU'ԧ M~xa/>w_s}KG.(x#V˹.CG;J`xrSQ*Td]y & #/]=2oyqQsbj]cj.Cy>qtsOn6F9,1Bf4[x)oWC !Ƒ. q* f1Rċ7ѫU]hnP G7ث\>5wKs}/ǿcd]>?v gp @6wtk0,{>?v ݮؔC[>4o|-i2R9 V*].i |;G^){x[-"ƀPMeЎJ`ro~֝JEQT!ck *;ݟQ{ڄg%l;T>A-ICi@Q,BʹN)98]=W9W)2:u6_t2t0T1 ^g1ߑ{A irfgchjZe+J1cGdchl*vPX>穙RzE_*[/R)3b8TpDG`svf+ 9)1N7ɮ%(z7%JZQ{6x?Q̝Ч G^L$fۻ ch󄜶dCP,&1p===={,os=@1NÁjfD0ե{&oF!Qbx4|~1P@yndO(=/3о==HZ0Yd($']G@36g`<RP{.fVK0Tw叄ᅱԮyho]RG;jq^NйVFacb#B5bhp*E7'3;Zd*C1Zx#RK9ٺ:"roݥ{LXbn4m >YKyBS,Kdܹoꖡ *:B3RqpiI8tCkACUJj'0 \gVQI #1XIyB4ΘJBQ=pV΋-0mw[ZJw8X4dגqAQh&KE= ܳ14BWѹTwχƮ.=KEQF>.*ЌiJC2RjjLQ5٩9T'tO~=pNQ:/ BDjVC< dGeαM羋rTxȉޠ_9!ာvu՝v&Kɑ˧5v v{5jEp~k¶Kuᅱ@[/R8V0r؉BD-uU,V2 P\.=>yÁ(8hGsGGyB @5r ̩H"CA y?b=.3rԜ՛Q6pic ာv[w;ܫ* /EtʤOЉE̝ܷ_ސBaa@T{ ဴ[XRdȿ}JSw!JyB#Ou1RչCןoP92PTԓ('yC㺭3;$:) <` ˝E8F>0TbVޡahBJ8!Cw14ȌrvCĺ;]ѩs`? h6Dp4 ;\'?fPrS ^'c{y nhE?4- j,hЮv{^F% tYl'SKSHJ|p";:\>5G1nY_3%(. VGb'v1K>z""i焔nc+8.УS,@Tﻈ0f1Rq4yCDp X}색w_$jF>cp"Gp{K?[ Xrw8E&eI*Á//=hI20!m`<;ѯ?&,Z (ruZq˧}EԄe(Ek~`<QH);P.PzA/s]aa>mhR8[)gܹQؘR="vDeS!RoW;mQXp/]G mY-="]xđ{\=f!g//=aԧ M6QD$9qt pV`õZ1 ` p/lR "Y_^z$ Hx DԹ"܇jfDX[yBX#r v~^\>5Gub3RgÁh7욋5q Gb7>O<ΔW4}l &C :R=1# #jtȧ{B#O_{tm?m=/ipc83*]'^;>@ "uk&R=o"`;uxM$,+r .}WOԊ։! NLd>-.sKy *u)oإwhB}UyBCןo iCN\ 8{Bp[+mhRy (=+s~u֫Xuku6,`zk]KE,hЄvuD$~S$x$<y)v,[/R1 oW<]'Ezb%^.7b }ٵd|{i}Vĝp <B9?\AXx4}O~=vd#O?݈?ƽ~!8vf #Ocy[R9_9CWղ8{KuU'Di|yZ7g_5_nܧ~;}rSʕxt# {F @ǮZ ^jW|X?A.K볲5fڕ"i ,%o]!ѣZC)(khD$&}s`<c*rϒ 7oW''"SԮ YōijN|p"8=];k@tk/s8ey|iص]]ghBjun#Otr7w>ˣS߮e`<)ٱh1 bh17!yP̧>O䗗WYNXӆ&{zz},^K a3H5j(؈m޸wA14#n/1I;ޝ[~(=6 ɮ%g+2ngݎR}?zp *d ٵdjāHOa\O%pz.F,o+=}g1,Ѝ'[LOS,t _PK~2,b=={b:l5x"2%KNF*Ӌ}Ф] ; N]ϣX3OԝYwTkئi; %<ޚY-m/ώ\>5{,VB o"K 8P 6F|z;XaSsW8_gN MhW_nw%_V8C1'{jWxvYټ׫>OH$:c3'8&sR1ڪsnEbuzc`F1X]&ŝ"&\ՎjQٞ/_s&CUi}Rz9_;Ik@1o.*L|JO+4uk;Rz/k2pYbhl*EQitd|gE)bbgYvN\CUEs^HߣǯQyOdw9x p&@UNIiH|"GbbڪJM9CWA{=ݐ^yp_q +9v zr):$P;_P0lgg^VO+{pM8Av-z5(ā5mtV4,V2K볲u~:'(CB]#8og1KzEr‚"8k F Dٵd\yBTT YqЄvU|/(vnYdʹ^$.17R _o~MSO0 /fh-kEw;?ϴ=v_c7`-+ŴlۑR=71'wx! Ԯ?Ia>θ ry(f#h[^e* |YY[L>AQGOu]hHpQ|"Sڅ^ĵnCg8fw>Nr|hFͬ6My }qrcWګjfVK :Nnc4'XXVEA2ʮ%)^3%'tkgsa Y=֍ Q>ۧ N"A 2`G.=+5R5w6@>.8}S!:[3QgEi]yBͬb7^}b8plE<]i{dƻeSw="Rո:X'C2UˊG8`Z2n^{"aX[/F|X 14 _n78u {>cnlŵ"fض|\e7uk6⏦[>O.({Y|l[wWgp_N|M2~e|vpm/Z^ZEV7,`hTtT VmbhPI(S7cvDq^ϩ.cmAԎp#Okx+_>Xe}sرhUp_+x~r2]5Zz3H8K9W+ug رv|tnXx4M9hbhA9Wѿ 1@YkMuM)J=дi]KƟzxf_?x~E;Nwfnv$+ܿ*v%F*D]KƑN 8@(>jg~9pY-m-ͮ%ظ vcRC!dגq4rhYjfZ*+ Qj) {zzfVKfӋOo;ÁZ: T@]-C XŃxC'ʹ{g' F>;5{(Dng{;m]ʃ 'fG(TC<,V2XAq<|:}<;T{T R߹o@mk0) IDAT8 :N'lneGX\ڧfVKCPN&?0N{L9_QЙntQjG'AU%Z2Zub7G]aSm,,kŒT>ѭG}Z>&D4Q]Ky(lĬi/]{vWfmPV}:d>8 ? @4/_癘E-q"};S-!:v2G ش@(wاb8EQ=} HluV'"NuZ j ^ndPV}:%C2]2\Hh6 [wVgD߀&=n˃GA x||GBlQyN&\AGaPbV>\ٵdʉ`9$r'ʹv.sO~l{eW3%ZccayG*! .pReגq*T9|k87}r'k (hd\AXߝfVKOo;; ٵd\,k3\zQ I'I{ әmL ѩoZ{"na UM* [whs_\A_{, ɰ؍d>q3R  %‹ȶ%jf&bhp==HmbhXX) b=oX7lMpbNjTzEGݾvjƮiC~PiZ2s,'8(N8k`ʹN5ig~Y3Dd148 аugu+Jq#Ol/L*CE5jf@g׮HG' [@8,~ ЮzGtgʮ%ԯ}$A$(; X<iD)hsvxE4d 4ɋEkfVKx:78]i{+Q$i+<| f7aќ64R:9hE_"o"|2x6dגqsAЀ⠔35b8EQ3.>OD[>+*kAd`<{ɿ]z,KT>]݁ XfVK[wVgpl*t%'5Z%C"c`<uP׉LX+I٧7Q]F,V2o=Vf'OY]j(k֝ѭX15 +& Ь  "C"c574:N~dגqVؼSn fI|ybEE~ߦfVKo~0OXXF,\߀BA N%C Me(5ԙc{lYgMS.~f-vvDg q ?pu/G=x1 +Y-|FyY8Yu ԓSn DAjN&MAEÁh64~V8E!\s60'޸wEq`Z2΂x_'C)u\*W@v5ZaW Z2QcVFѮY-"zjEq,rPrc]Cd^`#E`#O,==cDAFٵdb\CAtnXW;x&g4+~֝? jV<'ќ'ڹpYdF>m{׎ 5ZI^签1 GbSn[3e>5F*0np :8qJN,kxv-ĆϏ]S}1Qg+=}gs Rt{i}v`<<]os'%Ocʱ40 MhWϏ]b+;?R)V^&)5Zwg!;^Ƿ@x70[3%; ?c1ՙ *b!14Ȉ5/_G>:,{\q_*ѣ?Q<]KƭE՘ 'jW(?fI1([/F|>OhSs{#\>574]}׷l>DfYof%v$W[CJ*lY|HЮ p?W{zz=}gQ-]XQ?OD5F*x7Nb+vog+ܳBv-wBK6  "VD2|~ydиxԧ M=6i68jgR=f`^b Vۧ M\hYd6l,zT >+YoYE @f,ާ MO|8ӋQfɮUs jp?j),wu<̮%''"Sv,V2lo{#z]V[wVgrϒ #?{ӎrΐv˞^={`n59'?ʴ#N\A~Ew{lYpaŶ[wWgNDb+Fj7oh ^-x"2է NR^T9Wsϒ Fj75&)H惈RVӋSt64 ֜,V2ncG ];[];Jf{i} s=K. _8~s>VtE M_ͬ6M#Yy \Hc{7A!^rWyEy] Gn'rϒ X},V2}>ϳN{~ˊ!޺:c#7:mXt2!/ ,;ź]ǿGnbEx&.ӧ N:/+H VR< 9C7RaI;3NJjH/7wۻ ,Jɧ+(묤AS:rA ZV  Nm3ۥ{^?\ joV):ߑPQ-ݐXN)׿C#1ĵ,V2o~jQDD{zޙV'Xl`[}xakv,X 촒?set{I%_%9&"&|AX#ڳ 2r`N˺z%_D'/{,mY⾘}"5#=Zo. Q>8ɧHOo;cu:bw3`:yH6steגn]17ث ծp<{OMQ>3"P}x#ڵNDC8/(E_M(xuo|'X>m@=&=?T'$JNMKH~ExkgnK1 SlWn Nԓ#GԊnVzǷp/yc`&ěدMc{7A @f,P\A'N y"fVK|Ag]ͩpbQ oOO/v;UE^Z}`:q8Mf1#am^#Ni|Lc ^ zp=J,9C<`kޠ_;}n,V2f):`ۊAޢ' _=o~|}EA7vt?<[iy[rY1ݯ `;AR=piENIvabj3t숤$A7wP1];(D/F1~}urlchG64yO~=iD ;uac}V'Q4@ @WcGwޠ_sqt{mߟOgsn)ؚ?(hyufk1oǿG1L"F*`+"tkǷEp%n45&M\|0wo?mYrxoAs4Wv~ HP ôh0:ux${o_3bbsVl!bV[^݀.;鸗50aPsQE b8f򩹡 j^O&\AXx41r\c·N,e5w1DQ^ E+^Q}IcfVK q(å{}E8b%Uz[/ugu楾sݶZǎ}~E^"xnqNQ]+==uTأONQ|"K"u{N~yƮL\ c}p1[/>" 3|~14cgZg8fVK/_Ϯ%Ǯ5Ǯ=6)cSS}veD$FaWHjf~Ń ;8r:o]/cb%<{\ќ7ث 5`>.{0ۓ]KƷǷyfb8:0|vj]7|~ZDdj{i}6\O<]Ae{T>'x)&T;ɮ%{Єvu5ByBSn?c]IY,V2OyBgc78F.4ק M 5xiOYtQ` Qͬv~o^y}yMT>ƽ3uc{7.~Sb%U3\#XNq#UϒܳB9Wy..G ~pӋ[w~1 |Ǯ|b#hy?:wJ!TcGЩ=fYo݄ qp @xdQ;?`G633tK:ٵd|{i}EqyGUEY*;KgkCU㡦~9Eۻ;T>!` 5O7PfY-m]ɮ%40 GbN/Xg _ p v%F.4>Oh` 3JY:``:Gy_{l>>+zQ$8CcQOMN9hԹ,gGMc`1\Azޙ>mhrGs=&z5EjfTt=ʯ5Z* ]QZj% OXb+\1ƮծTw1¥{@T  X,V2GӼ:D@,V2;+d\Ccר6gyb8Ń}~Ss膚Y-m/ϦWychF.|jnOO/枽XܑDGcߦ|bYdk8q4"_ (Bܳ@D]KƷȴkOӆ&kf,ܹO0n`<<]x$~ "1Rq#ĆϏ]SF*غL9Wyj.30 Gb5Z2R͝n,V2xYJ68I8ʹp "Z2,MhWjWdI24,V2[w~#S.:)J=?bl&*f9gF*X^kNTh3p)~t׳kX" @V5ZIucIyB㞘Do\A7L%olJ4~^UKtwo~ԩ?k@&c[>BVH[ ǘJ@tGs`=I Z2>+ "wl޴iME^R}c,V2K볼p#O$at`<Ԝ;4T>,vtDyAdG5Z=8ɮ%{Єvu5hGͬdגqcm/cC&qpR/$z`zv-}|;>>] F*x`:q#X,x${4m+co~4ڻ85 !*2ڂЕȸN0x ^Tki] 459ut5ŧAXCTUSVAYg(겇guG^||oT΅nxWp?sd@&>;Xjj-\OF.;cfkgR(fͮM/jƝ雭-#C际ōJ \pU` fpw㋥ō^IMgB7qyU ޳ݸ H$=,}er8E_{N\vp?CT7>Gͮ$B7qy4JSV)_,8 pR(fAnnԽٕ=\lq}\ʹ-\oݸxYpýniYٙXqu*zZ~=q5JnRln8Mp$n_,̯ C7^P?Y`кqrP̆n8 pQѹř{8գhos{QC@P={*3;#B1[rSfW&|8] EJT:1;Cݸ||2zzkRÙM;kkQYsspò#hIDAT>?NMgn-~Yro,ts{NZPW-5JϮI?In狛%kW~ăp7<'{?q}XQ)lי1\ovV/'3oM/tNJ-U=*Q9XJfɛtˢR(f_Q9"0 wZ_,,̯&3iѓ;kݸ pUWk=}c_ Iλ0qUrͿվG9Ϩ1\`գZu.F;XrيJD8z5JQC\dpD=*Z~nd@&uos{Uaq\Owx(N}?2*ni׎f/Q ] ݸzZ)SfWFC7]Wݸ:x=} ?!ݸjj%d ?J${'o~ށtu2tkWq(w~~:LJjr8:ٽgp?ӱ|g!/5d@H*H$}éOnf8i}^lNnD"q\mD'o~ lx3(Okѹř]zZ~os{znt|Vʹ}w1%P) 7q7xJ螫&nU l3r.niYٙ.ni_FO\M1%7ۇ鯿KMgfGҡ.zTmn/; W1%(?թ=ށMq(^nj-Mx=ށ{+wӏC\Tݸ{zR(fC IZp&tE|g2zҍ;-0n',̯'B(K d@7ۇKR-8pW\Ow`ѹ-gwZ'WnΗ15?2Y_OMgBninΟ153:78uov%9744Jq( c w ${&I?I9fpos{s[᮱`Խٕѹ-owZovVC1pffa~d(T 󝵸> \,pѹř/թR-ZTˇn.&c8EOw`nУ8#8mF;xS#8mG#Í`Y|q(~]ܨG><.c8IP:5y338yWF|vm=:uNSk$Djf懾wZFGu\=Ϋ 8k fyrIENDB`check_pgbackrest-REL2_4/docs/img/logo-white.png000066400000000000000000001203701464170754600215760ustar00rootroot00000000000000PNG  IHDRGHsBIT|d IDATxy_ey @@H ! -ʮUTm+تO]h<> JZhY PT!(V"f ($$!L̒9swMIf&9^dfYZ>UU*TP8TPDe|*TfuRSGe|*TIYC*_TƧB^ 8 O[g;s,߷;OQ zxO_ASoXk;GTd0kBf?8}맨O GsW `R\%TJkG \2 U!ܽUgO um=m{wVش;뗨Iˉvo9ߔF-'/YinM_l|eiMq@LXz1,}%_|@{B@ZxQԿ $mtAٟpBR{d@B0jcUE$3:$jwU9Gfvu|?] |W ~q|z9YP2$} b*:MYߴiVZ|'d ΙSxei#8cH&y#3rv3z\B _(TieݮMx΃$fpDAŌM\. :e_~+ ?Q >,y>g,+6bm!A@32HO!qxHT/LGiSp\0f2WM|@pNSB~/m_Wl_:]{=z Ϝsc922ĹΦp|]ϱ_ǥ7QEx{#-/13,zy%{⤆n"{o %b"HobJ}ݘNGW0m m</M~!h'?JvTqdQ2<0; \ ᳋*<5F2>' ,+1^iiBœphFFsrrcFXvfxã$*TS>9iS..FkKهye ]Dr78c#"m(|ݗ2qxs)m'KNQh%|kjQ%&J=JBb͒[E QwfIb $R3C(BO>tq9G{9)T<+eƯ~Mk`xr㽇h!72QhdoߙEcF2Z9Imk>;Z@ϰ"0ϯ{ QJ]PZQxYyo³ >?6ށ =ݮ\2<*P~;]ljMc[UKJ}V!צNѰJ:8 Y#sn 9G&/L 'I@{^vd%,Xpe/"[;nCҾx+y[+fQp+8{(V̫V, 5s?.3PP7֨ˡPkl灥Њ4xx'h!lX_z1Q[mCD (?&b<9@47,cenJ<+ u FX8eB +']\ĕ]̐dh0d9SzO)8u7P,^`n=QUOfҚA-q^ `0b|G쿒 ܺg7мd"1 ]v¹x{&F&KƝo5Ϛc&7!]6Duv&C.} 4t2eF,[WEnf#P('8yt,q'#3xadCfVlyM[̫ ^Džp9`\dB^ BP]loiB|"lرQT2kzRIܴdXd %x-91?(t(dHv2'x?K.ő(>x73B2($"Bt.AR y)9d>7Hǂ j,msJ Y} 3}緯 Pa rF PK$o-41cyn" Y/>|u<y 4SjB0KXKAr,JfHrB1L*dNXvON-(H?T縡O{y*{9 G[W+OY. /Uj"$ݽU;$|^1OoF~S~ϱ;|Hx=֖蔯EE2> 11rsr6m|UX҇|-]mj$^ˍQ±@e|rcG;QgOLo\ɷ=;B^SW*T@qBD%Iqd](Լ,z֘[J]j֘ eeZ.SP-EC3kf,{=!2Y% M<%C@L*s X4ijbZ]p['JGő&!=3L nK 9 (߰u'VFv3s[XЃ)kh. +Yyvi4 Q/TxXnե451y8KM/]KkWa޻BN! M,/,-{ h-ovP@k  \g橗wo OӀŭPy>J9@q(2mk梇Xk#`Z1OQf7TDUӌV' ` uoO.C`R!6 %[x7|kяxf kT:!|g}biH;BC<0˃&ʭozםЮ{TƧ~G[^fкqe6^ywwIV8(rIXP >x)O7|#*MŕE8Uaޚ%X0;ښ@I$s}3jҀhfy Zu\o(N=b|?gw-6|\׼<˷6 {)::a\zpR+ 5U =W on/.6QL Duҹ2>G 5lnA@0:GB#T\1!k>z͟篵#/CQ(xђr^yGI:!$vKqj|>^@Tex* o=^q{7atIur 畳>Be|ʐ-;դI3 ٸo, Mmd:dH+gLǃu5 X)Mk'_ A8>yeqGy@NEQy>e1/rAlM;wrF\ާ^‘KZt 2%unIxa|-₱s/:)f8|mRx=Ӳޓa2>^sؗJfk/?X0W>AFFn*)(c'j YHޘAJMj1qz5 _]#_Fx D&A\B5/w‰h)z KWLyTbknE& B hl [v5%I}d{:2ܷq[0y C\ 狮b7Y"|H2[zy%aO<W%u_|[lص!$%5Noq^Q1|p/]- B@M]v Tg$!Kn:/\eKP00xAlvYZLB<[Ԇa|³_L"x \mhħf~ִŪ[!$AU|z*XO=nEp37]Ga5|㩇X״ )dQ".%d&G! *)5P<Ԉ}'ʕK@jhgl#e|F`qt!4usBөCKOf'mC]$0GBFȐ$!|[GyԜá%yx*>,QD D)8|{p!$y}4ETbsC9KBW?ýO P c|b́ͫ.E0;/dsOA"BHHQ8<:< ͧ^>Cϛ|mډLg3<7G2ϥ7?^v ϪZc96bax\p?Oy<ئ_Yh' @+3F1lr,vI K<8j#b9\k' Xgpx44u]2hvW}Z{vl' [k"BSLLxDžoW36 ũ둅YmLOo\I&LԊ$,_N=%g OI|뛷[v)JJ$ .q)("h^Ǒ ǹ}i92>t8|H8q0?1e!55۶Or`k~|={[Y=N[G|H5% oNx~1~|!{+THxdz:{2\II%n33}}up:WCg0?g9ߺquPbpQog{+!Q)>o8Pl2)?3Fr e}KiSk[w-IUppҷ2ri3[HHyю~joQ;17h^1/W 4,@B>$-ad3} /iyG'[:Nf\qp嘆 !$V '$rI_~^C`iq+8wY>kuF}8kTqr%dHC 5i)l'ȴ)׿CJ{F&$ ,nc1>1Y\P |*(@ԽhÏyY+++dZ!Gӝs 4F \褮]!.vtx>ocë[A=%H*P5Xә*.z)_P^PƏu&y0lmm|<Ғ@S눗=7BbR5R[3W<_Yf\OP4ls:UDt={0opT28ęvTUIpxxL1IZ  $i0L_vh*SQ.s/.&ŦD򡪒<S'dH{Vl_˷Mq`joRwBvdRI #I= φ]7:o[mZ5 wo/xjkM'J܂bCeM\1bQR| ͛cag+rO Ռ4V B[W>N<;ZS3yj/I򄱨%5$S22oɻJA>)~Yϥ] &NfOX诺w3SGoc„kΡV7~T33X-GYm j9\y suNtX{'y`O FVrxi8p SG4&Iy?1w(&OCU^m|Ə0qUo9JF򿖃y~3]O>˔ąG Uđ3qX,ŰoAu ! µ,e`07GCs(jܵ'gt7|>A%#EaC0 Kccczu2$(lk}>;(U1D2Z2񔃾?&.ȉ}=Ac0Kf}[{7&_a d܁2(pL1ϼC|ryEo/-rHaͅ.zm}EH^T/G)$"$Ɍc4̣r0&"Ҡ3~O{ Zq`p/&|<\sfa^ytiJi]gi3wW^*}I6h8wlO+"QWUWym8"B(!&Z=Ckq,4hCAXW|x7vaƞ+T> b}`;D4b`o9g CMRHHUB:'˟u>e (;4Z2 vqf>=cwW+Id TLO{G'aUYLg?(S! 6 [_BUiOM9A|\f,΄A&kټc3| pl>2[}=YGhV`U*g/-fݮmq- tJ݄3!k+y}Jҍ8h`"^F U__^!axD OqK83+P|[e IDATl$U-R'}K%S~|#o7gLR>dZm5o DcCfG_bMOCdGv㣕qyZmhɣL_P1/t hdr@zI;}:N|W/ N'd*y~@$1Bd-Yxr&6KC,J[&%!Wq8WZ{X64RYn&4xZR0<^4vь>|fkࢅyªdJ!IHĆ^^2ּ;jk5L;[wӹO D)u-W恎w%B(뛷e/g<5ܽhQ86wأҝg\Ow-|oy7L>E",K<@ {xҩ]p@!1rx,sdΓ͂HN{G!8K'_ACwV-tp1{T_ m'}. }\VШRYaOG;;Z8sب\)Js=adb|!_Vmr;`G˯KߚgM݋U{ަ`lk _z9l·@j,Pմ^!%CK}-HYѰ*P=@xbUZW0@[^ϳb#ڹ '^غ(BНHwݔx۳-}{GH! y|cݮMyxgSq9,&\uť{:jEܸ_|(g8W^8l;scuk 6_;"9½p +faTf[+L5 Ƈ|LqJ",H[ډxcWHS`P',x>=^Zvi[Y"7;>r9raJB}N)BFvy)91UR^8}Tw|1Dϰuͼ>m{g1vVY NJ-/PY-;.f"|]U@Yk?t7wmP_vyyv/tx8 Ab ܱCmv{wbK;ܐi~9+~^]hB#ۧ8Y;◗ +2 ,*a\hlcʛw6q M3"}T'ԼO;vճOUO?jdIve#9O `.w^_oXlu,~vwE?sJ[W;8ǗE=Ib Ԍo>c>ӯ˼(a$r4 'T_+ro:phr> !!gz. rds젉$Z:?,`e%&0Y|Vޏæ:^raGݗzӢ摖ܘ)yEBU^ܼO۵r/GJ8sqP<ܽf=J !|p|hjU ,]֮v 0!Ky(cqk\ʘg7 P`4RO0 njg' +*^kaHB`>|1)p8y-3/>]]~Mk~ [H| qwybЭpdGBƧf} M/+jXMEBA%go< %JFp*|=<%p86Jl4'unp5¡ [HXyroM5I% \xn}Q|lavsVwYR9N4;nN?ۨv ERQY+p\28xIgeuQ4Έ?ypŇ-Y-7rYCtSuiT8: o{Z;{z M}BWyN\4 ;#6k,~#T|m{s&nh֢_"q"sWښL DccX:YњbEߵ=iUEQL/{CחhAs5Ҿ1w`YH|lڟ35cT5|֮v,E} %FX e[;Z#eDq &PqV<@w镖/>\ыkpljΑd֝\MMz^{й~3! kk^ne|{xwD-(ً G0 Bbz(U,e.g~WbՃSQ*1j>$qhbr|!LIBdz5߇f}Mδڃ@e|)iޚ]uga8aq1&_CJ GشAZ/Y4WR(PAŦQ({`~Y!\N\-׽Xz@*|dML; b]KQBk\g>o tҳiR 3]1G}TJ w.NSTO'\%KV0{Aغ ^t{Q" vz&)k k̗gz>q9̊]\׾/Hy0/g w$EC})SWvOG9w-abU ϭQtduV6mbgg+ ZNgu Q9*t9?5$ lUgTU9lsXV˫i^Z#YBjd%kobߩkL@6ڂˉ),y{7(u\Jes1hM>w^p>O>pXdp(x Nj9 9G JFDR֛ƌLѴ+ G"ʗ**H!ۓS -n 5IrL,)/!Ί?Ofe 'zYۥY33o^Rփ20HS4 ٝ0'?Y&_< 򭫹mF Ŭh"@Hy /3'sidlܵu;ܽ#h*}].Cep/Toe҈9y}]4w/?^ua#t71mea! BېWfWl]s݆9~L,ء,bFgXé6̼cB7>e|g`Pj(]9[C#er9|a𳋓/+~/`gt{{"c!B"ʠA 2q49cmb+ɲ^:z<8 ^?v6b?>oS-Flڽ%[I'z&[59.6ڼ^ ۈS⯧'33+^ &,FW4'HrilصOͺ\#5x}'Vp8 `Ǡ /6<%6D41q aw >Q5Qgo>1.66 *u3^Sk,3".1tKطA1K;ǰFå_[L<Ğ(FY߼y/-i, ]AWqVC861o"Ӫο~RzKo8dYϛQP#fB-ۑCaҨ>А9pIqT7dU}7뚷E7uO4MN{/5i .(VX|T53ڈx >{nfnM~޴gǦ7ڛ v]ܶUs_ ?lRnh M8q'㼑gIC Jǰg`T-ˑ/ve|mpA>uR iJ~o@Dh[B`eqZ]];{hl&)$u=ش6Xdh'rŖ,|ɫBZ]wNXV lfG{sN BL} ڍ׎l~'C*[0ᯎν|z]lhT{Z c&7ІAyQ&Xes$q0A4 QQyVuZgmatZ:r9 Eyԃ5WR#DKvEZ;֜jb`skR]DAgԕ%gT|1Rn&&hZ{hG/hiKXU\B(0b_o0/@ &b[S& #s;Ga6DŽrڦmٝg+h5T36 }x>/7o˭u;80!T}.-mkg}_-'a8c(^7мЫPشg(]6<QP)!?r _~Qc!XB`FNœJI#nah?hD O{gYV$Cq$aAڌNCcQMJ(tb>Wy~뚜.>>(^rA1O@5pP=u*Z#[Io޺we+/>/o8w86>uOϽ t-bɃ]^F6Iƺ{N&ּRCIݨ*-C37bƲGx`O'5$/gDQE|>$ `Dj :? \2v?ԎMGD:6X]I< F!%eHPpL97 ?o7 ~ky+'kR/H`!"j7܇Ixjݮ%݃Ó:O[8W*9։#>os>f R+cCZ2J~c\`x8ẘ>圏д E{c@yJ.sE hHR-.ާ۽Bc:ݼzynJagîM}9YW~Ml<2o|ne#B\.?bƜ6 O#?qHPG;x@gzZuS37~a ÌիY$Γzr%pHKR1|4wLze<~p]O> zW Rb#Q9Y쭡n(✋zhVq Ywf7=۶ KL3=Nb yY?;l$T꧹gCVxu&(x-EHh:ǎWٰkUBQH! R)VVʏY"ڵf3/qn3 1s¤?SǙxug~ :rx-fNklMp$jy ;s$;Q:" =PzR [3$j?~D#kآEN4r,QIpѝ[%vwK~/2$@ kikko~:Ll|bC Θ#nݗ(`6O)r? $Kϑ`ޮ9a՛mJB~;m aɼna+ bSW43]Y =rjqw$$xup[w- Vgq̰5瞅@$[ϑ WQRo3 -,ް&^\9!#^ O`IĴoS$eµu7؅aɍC?ݗ+~V.9xo_ʘ=1Gz|o9Y}i o7:2K-c?2&*S(J(K6@#ݩqZPNDŽnWt.^ #rvqyJ) k{Q 3`O>g/.+ #n|7-m}`qf 1)6H?(Դ["p#Iie#4QB%X~}O?F6O-5oرkMLpd_6cs?5nv6n4BM%W@r;zB}(aU$S5KB!1qHjc2Z=6NٸuQB^k.z>N; }*0zǨԲ7- rp.x *YJ[Gʗ:W/Ky77߷k`^.-YJ8vN2>6d0޻Gk):0Qcܑ.SЀ2-˥$e|ux$vԅBQ'>VsV,Kſa%UxYtV'@$Θv*Vix$+z\ IDAThxhE MxRVa@C}ՋG_5m0aFG_>aԳMa{k UOS_2>ӂTkov,&LD͗<) Sd80] $/۵q+#1(33dYlρg^^'f͆]wN&T'HH\&&tgᒇaqQ+v藜ߜ(^MM5͛ҴsFi{!H4 UyxxBzMUҙ:WCx;|&툚{> [S )y|Ǖ]jf3YqyD:GJa9#1qXKfkXf eMJش 7[ 'mxr~#Ē/Q AZX鮙zEx?(UPVDe|zUe[[ֽ`F0L!Wz%,?E^%+Oa Œ'_~>prFb癠󙧎w&_lME U^P '*CHybt I4S~KpYK.4VƧɇf?.o)\!*t=k:ML9?+G1Fs!W9 ܧ~HjM8oy)<Gͽ)jX8T)4*?Tg0,Xyw~ڠ&z(~ϵQyP&f*$g$$jOҰ!\4vcN= ZLP2[]U0C$J.NђdJ.?=U.^ug͒ўlu<A- nb.?➽]lKxz/7<*d$2V8 Mbh5!1aĘ8ܘ|9Ŧphy^f,}Y+V]!2qTHąg=G[^:7neNd%g)p'ɳ?2>=CX%=L[1bĩ1nƏІx*u5u"z$ˏ8Ukoڍ*?Vn/gdKP FHdX{$YTÂ;2bMng^L.nne˘\iaɬRMl1=S*¹?[!oH V|a|ư-y̑{#޵+f. *0@N% 9* [מvuybfGҜj&T| iƲ΅HW^6l2s`ELkk/: BQExN4<>d%h$< \챣?$ ϐ9KŅϞSj`0>uáj<M/rEJ+N| wyx/'NJxZ=u:S.jgf^ʴ,z*Լ Ps bћ"bd/WJKM0%f0/C>OrtNs +_G XF3Y|ԞW⩓MD!e ܲ䆚iL t,OFJr!\#w^c }RP%z b oW>ϡB@40)>Lj8XmIG۬RD"aΟhz=5ܕ[?G/׼6>)r/Xb,D'`WZM/^Npyhu$1ma|żGd˜u0L8>#;~߲9XtHjETi̪Wf0! 7H`dL!Si\5%=ha}:kx|sZi߉e^,LL7%t0a.'}`/oWyNN^Eʟ_RU)֎Rxi3緽ʪ9W,Qb;15IiZǼ\Ʌ&XZDF*#T0BJK%BE*&/a$P{&_ݷb'KQ IrfTfm^h f}hxJf\?oy/ڭrXhW8ʵ0>{]C=ozzO~zN[KaS@E UuC?8DsT`aM|fJնhY,û*2}Ylm`a| %u{@g \?we-a Ф>᭬-O *)ӽzO^ٴk;ol?'+ :/E2aW<# 絮L_bƧWϢ.̫𚴍gta| Ιr`[wXJeZS}}l=Sx{7d#9Bwϛ*gdT,ks S$lD)D-ܲd]:3# D<Fчk a| y/ūI6ݹ/Z55(z>&>!3 lh e!Sob6C>Ե_꾭[%!œ Rxk`ԡ$$Tu~$ qeW"yze] RŻjjT3iw!zb4G@Rr8p0סYkG[ gTa| /xn;TUBܹrD|,S1Bu0Q7iޭ@hExM׸G#0_ ' <š_dJA" uҼq5C@^;H*ёakQ]pEB5K;b^+6600> g$eMxrGBيP[/̗TȂGg%{  )qjgs)&Uظ<=UZX:{A q﫣³k` gHwG'9he(ܲ: :hؕU\Ib^?W1+JBÜ}<>UX9c16HQwŨnVRj9]P܍;'O8c7 w@L,\jf\ 64m DWJFMKߺdո ě&>Kպ,\deUd50*0>#^pbi4n5gޤ70j0>#gw-v8ۀJFk$>T2絮D@c+>c]hҶ= R# $Åwq!_~!_]Ea%IFc45PAH5 3ґ?F,r«X  ϦLk[6]h|3#ß޴.=f;r)ķD4L0Wɞ'NS_ݽw4@.G+f/@øF{D 3B ICa&znmZr_{tKepQ "Q&D9%8]LD#s᲎̘|){ aᔙL| `ؕN`[yp7=cry\UfD, ΅tGgae:v+2@k/oɪ6+J^y"ciJ G w9*{=*mT>|"@m_DDh.$4NN ն.~I 'O#*5[;p.G5grp[s /c`BX,<fSoIy!JPbvoP4Uqd# SfrYGgxmB(~\F0>#H/=EfHuarˉ6 cާll5+ޘ0jiL eI]Z,}7|?ԶV>=a"x;ͳ;sQYP%gϱ@FNte"UGQQa:PqJ{*d\1k:13Y6k!mDEH4(&4#-y=%SIy?gW>2%E٢a|F na96̭o@ Lp\= ޴ï/_@-cL[$M32u t2/IR%"x%!T0FӧL}|,0^p< FA[<|s7Looo@m0>#%'Q>ġ7^*E(y?Q\]ËMpTZ !t9ŋ9~ܩ,͂Kf1oZ'Sۦ pI <>kB}nH!Dh`ՊLJg N)aqZy'Vğ=Wb&$wUA"VNYBKllfY %5TSQW0͛VQ.[].L)t] vd"F1TW]#Ռq88 ;^e+!M[tTPr*5aƊI"{['hlS;e&;grWB4pVhF92y9\1Q˾߅a}Ne^\MNښZL5p^A/.z!-g'J5Xpf$ #{xpŎǍ̩I'ȲiǩMOlZ~N^P "&Q.FGH 3Jp *Tq̫9WX䧔ûakll3qJ8ު6E^\X)dI:-Nϲ{]'ٸw;rr3 3]h'H!C>]q3B"ZQ8uJV$CEMʶ.wQU#q|bM4L]Fg9GEqbG{s?ma\ cD ;77NVOg.Esjՠ(w_7n'FIѬȵDV^9Zc*S>6Ë%ș`,Kdȓ?ʯ_}OmZw9,3]~\KgvͶC{Sr@t,Ԋ12 g Wꕧ6cŤn$-ɄH5a8}=x}6u$.{wS6vmo3!#X)Sڦf{mC ?=ytsTu~rU2'aܲo(R4 (AZY ~~)CE8Z$):OBλBWMhlt QO_֋Im ,OHX׮ w3шF5J`qƼ͜cݶ|5dod8oal;ȼ9Wq2xR? "iGzӂ_iVV> k"8'^8`P I1+sO坋$.*6\ч%/xcgnώ.͉ӁoJu 5$$s4SRWAXU2)qCzɳF[tbbk%=bчy_XU.n( 3FfVk<#bE߀w9"? zJ X1kM£2aՌqr|wT!ظU,^Ar Mmva|~1k亜AC8R>w^y$8lu+'][;W%sOmNv/FF߬wMl\~3~GUkix*owqX(! A.l l*Q2B;G-L3>r m-5 \U3rEey'aw `DNUХt\?oybW;6'}GA72#\30Ǫ|Ɏ IQ71ֳcp);ֻ@쪏0:ϑaX{&)V)}odxoBo^~ ͓qR!SȫquxxcfœZzIwvoOU!G$ Osd]9Ťg\Č^| ~|!Qr;5BB|8Hh~O)Q2 ]"Qucz2&wa9Ѿc ޖQ~~. y89-9kS'{'f2\2*pҫҁ.|'.p`!dtxD!8 S#,L7Eg#8(dŹZ/Z?A̰3 ;z \{M*! D= qsbH+s˪iqG!D#iY15bo`BCrO{X(1-B#S\2:MbLgx7\4E3nGŬ0*끆3('RjuCU&ƨ1=#rXSAܛrc`Y9S9%/)R`<{Xb E, X2{_ȍgݣg90>cqgм-TZD<2IZ;#=\K?=QJV|m?حKV1V1\1sMHD)P|&,(Ыa|4,ξP!5~D`db໏M/!q+rlmukcrr qUge4겹zaN+˧a| $nML,khK6Fz7vgO0|兙IcXsTY&SE!k]I=e$j1v끲1MZ$ t RŜTH|՞ jaw._#3|<}cybd$!:RX>Vr-Y(wcH>pLn%|=Cߥ3{Қ,q\lbxq(\rQH1 \Xȴ`^< :| 'Mb>Ghx&Ĺ_:~ĺ6}gQ :GGE5ah^wm1OOٞD)ypA֠z+;rE@\Q*QY/`;}`?p}k*d}Xis uAݐn]&6R |<PUҏz(Ɗoe$V7EWKRb0Ո|2].r>(Om~1)ҹ^#jY+ŹC<{$LK5D8 $?lܷ{fZ'LΕ7%`!aZx9-y58~_:+if%}B$#]ѽNQ;,$|q`/t3֜Yb㹄X֣ϵ=E-o ZFs4!{!hYZ'L49UO(K28&j@;ry'4S\] m^j0r q^yL:Mڥ;O%#4'.`#Mu?M*]O(VQ/PQ%ziɶZ-·_x"4s,үZ_j Kkg[+ZLl`Hx{ IQ>"K4_•~ֲ _ЕdB/fzhG,J!WPW r;/F5[Y8?[) "mN8w`5zªМ) B%C4/rX=U8})s˒%8QZ&?L ·_~w#DUd?~ą:!=ʛjm}7P*;Y*G= WrB'%}6pi<oP1hX8<^z8Y^q [| 1%{|Eh";׫TnGpRA|NSU@yD5@rj`hdF_ίJF$\:&7ON\/*j3T S z= jS_rEE|Ps(ڦū1|ϿbnGgl먅PExîSޙ~%Y W[^<bPR{xF Vf+QUАJY쌺t뿆;$#'/ɉ#h6+W#˃L|kݣ50„~K?ɿ2#h:Gqu$}osaK!@ \RAf\sJ<RqǢl5)'TpGy^a?o 8|sȕ _)ZqZPmr3X#DIA0VO.} \{ =ΟlZˁiQ-2_iboknX /\bN]d%CiFtae`Sɋ̇ښs6ZС]XT,4>-醺 D[ױXwS˞jHv7HX#g9*wghiϤQj{c,Wx7~v[ЅK]db$p6B"/4c^{rH9&UJI3&G;r/f}ڐAuFvԩ޺cr:NȰŌ\ٹ茯IW̺q6O7CHHBl7xO7&alw= !\uHqSٷ_[i[O%bR9ZsgtY ¬Kr5TLjIN>fSkJbbBR0cTZysc:M (}_ Uxe6ߊהo4q. d8ZRŇ!3!\6>уJev |&dxR۽َu9Z~ ;pTА&p!B_*-h Qa+8(dΑM!@dܜR5E ķ[o5V"dޑp3W$Hbi7ԛ Mh/.I-Q?Z*ͤsg}|9{ QGDMŤ0e*vHKNB卖5K4gy>/ ZfYi|׭fzd ċ;_㺹+lh+ZhKiE&pX =r?K0.W Fה .G Zv'/wXߵƉx*TT 96Y}ޚ_g҄ɉp %S!6[{{=Hu{$ԟXѡ7( [͒wBe( yﳬs>-m%֭yA5~\sa]d['m&/9hCfka ̚U2(e"H &&8 _| )xZ۸ssښ[PK!)T%@uyrKHj>|į*GUT\r3\gb;P@<<)ŒʽFcf: IDATԃL{=8?J>?/=OL4-F4yKM gH ®(5\|- Y{DTMCJ%ˤ$jjԋ:ᅴ}Vi ąqK15: 8V0qAS^(̿s\9s)\TLHӋ´:|9wZ μ*wTO6ee&j{ghZ:pgXscþU >S[w'\sxnXPU|0P[CTV*H'ȼ+} \7gRk"e ]En^Np珿Ȓ0xG}s>yWЬKw&DО2F(T>n ?W(UD+O9FW^'wμK;A*C$sb'wv#8<2)ZfZ<d bMX{8\YÞ܊iT f\@&$+@az[XA>WK^bXE('2{2`2^߷9V=3|((9YVm=K0^Lkq?ybM wJQ,5}2|EA\"Ur'O~kZdB3AXڳzJV{뻶K?fþ-X"RO6#SEus^|.$"ejߜJVf҇ fܺzMߋ P,Q42ɊL9ĮHxK?NbAIh U.qm-hH=ӾncKa֭KC?}Qs387<` Y ;u¡#|%ښ&!7)Y;GƧxar]_s@][Xs$b"q9/cz[{V$fV8sRfJB2< QfCOzmGvQ!CJEacI_/VDr9-7e._?arskh3ήDϢqG=ʉ'(ϣ.ۺF)95ᨒkFfݥWm&5\GRa[_Q3B/vxIy)r1sCNkz%,YNZ\Z507= O<'jϬ͗D E&dJ75de<$>xw\}췙4(ki/ K=¦7w[xHk{wPu6ߪ9,nnnf9deu.@$cᥝLeS;o<@J^ppԛ|geWq1rySd%\5R"t˽t0Ǿ][or Dk2=!!%JUTi¿$vw>P$z@»9jC8l1~E~%=VC ea:?,y4k(}lz!ytִ;|yQeLY:r932~;a9F*(OמxoO<$Ӛ*pwpϵ *ˆs_]HN"dק&T<[;G yx_mxGN LG O>lyь`jUeP6Pu֧Noiv~Q"JBP-zͷѥ|> a^1/yNMM΢_j_S!YhDDM6Ч5A)r{-aZzGxxOyrZBeI}T5 7&y'7p[5/|-hw/ZVwֽUOUBR_JϾ6ZyKѯT"o pX{yT5T q)7][yl< x'Ab~Xq6()L8nUŻ/^?hL$*,:jpFg|JnĞ$<_{{<݃K ey'x #CmcZsܼZ1yC Ԕΐ-hHg66|={GY>sA 9VC\B<_Q|O?4Lt~<#.@(S[rue͒Uk\~HMyYfa۰mdpf^ٷ2?2}O1*Y Ug病F||?`ڙ = 2a4+B<ڸy*>\r5K)a<5x-eeՖwGɑ L 7:u_{ ;^h D4 Ӡq\% .PqT|;kSy[+[onfLyoJu`1 TXVzC~7_1{{˕,3 Ԅ 8Kc?d7J#JՇJuUW-fΔ6HIJ)eX$TJl4>dzkma-w h/aV*^Чi"ΥvimSj"VϽe4!%CD vK$J^qiR¿'^xƛ{e6znF׈bviyؔTJ9Ӥĉ-wܺx%3a{1C32#6gˆd_{.-Rj4OħZqhǪ'y usWa#{J4~\RS+WP )s[JN&>{4Bijqnd.B$+ś:^wn//x Q_m$L }PRO|^K$LIl`„I̝6 ̝2W$nĢ|[oW`Ӯ7<=orn{=TWPa]~_MޕJI/v[EL]1h^GxhqԂoEOB>1$'/MXg盔&÷Rކ.`X)FaA$ Zɿ2 g.ʗ>9VN_feRfIw|X' |Qh IS{~[^hbv!84 M]ЃparBy>"L9YQ#}\(H.nQűذ~91j2ܹr͠lu41-]1XSB.땮|@JVÏ((&2ovyORLuΕTʟ3kH`F*e#]R3NԊA rlū.DͯǼs(zO]1>/%dzNƧy蕧4 5c?7ħBG:+GF71sftx]\?oy8]0'b@X<k=?GERfjORrd%/ 1ЎBmb3«EO|1>I576@O|F . sP<oxozOp&0HH7P/T(/=V(#bw7oM'w82>q Us\qѸY1VhXڡg-Vr+Wmb+^}X~21Lk7'Ҕ[4hƔ5/̘C 7 xDuܽ>L0/=&ʘƍ)ڳ౷p P+WMR 4p&B#dYѹvӽLi5 4F~ aPE "I/-N48x5Dj T\)k~+f.6*0$#w5H|SSRQ+[IJNgy~<˵ 2R| 2⋎|o'L7qw #YaJK8zz ڞc|`Y*U}][?}[9c^sCJJNX|4Q%xi헦o1h}ƍn`it<{kO GUTO/7>I\: VWI?R.t#Qz{{u?!>PG٦fhZAQ)VCkD͐@DDZPE!Z?I"P8JcAԁE}x~/m X^.fhnOhTô]x3x[9&Oks_~^y3-YK7J9 -|$s5Phx40zQVKK"rl qSɝ=Ek>LEӇb ӻG B0j<w8{ܙ`YAEN gLcqèk$ OqAoܳ2.w.@Va(W0vc ꏆjJrNgl ~6<&FV&F=﹪⟽!H%!CMiӱj1@T2 #(}9>*8L:IM["شp"&#M&qM"7{nrw}ZOU(TD X* aުF$!wf>ɯNrlh*ТBG(M&1詭PbIvKDo^vJ"OCw9ۤU~~zҵKHdӳ[WR 5mD`9ȤKYi=[9wiIAQE$J7& 8|MnNJ3'SBFTJ{J(2nnC$lh'IqrDf)Fm6g|ڇGynE*h % CWr;]~y̑7|*WL?"| ?~U_kҵ#6b7W B``GFnKcC6=]8S)yÖ2Ńr}>9lʄ"x ,~pK?+1}!g{Z U:Q8Say?=#Y}'@"wS>ƭP"Ns>maÁUp^PV M"4k[:8yڟPS-p~:8Diʾ PH2݄ ӳX"$6IUOch\LHd¼ӥx^vt}I%9VQmS5鿶M㿡4vX>7Q)L#CV 22L1s_Nvwo6׹O"O 9H B^:ǡ3ݼ:%O5RHvA?s-@jk=P?ūѶ|.~L^/B$*){fooX*ο˝;dTI ,KרUHH$3jjYeN<+G*W"OߙOn)#"<9=͡ӿeImF#(ekW#Jq}ywC$zO}Cاhr/&qÍ{6u#,_%&8Gen{+P۝RS'V9Ñ?169HhCJ†TFe@$))R{ ʸD&)k:yeK?/:F" ;PT~o;wHtBE rZ1=)fZ'43B%3ښ[9ǞK.1ɧNcy319km\[gF-nmA D! %qi#/wog ~GHu˵H>um>ݬ5/ gHa܄Ft10UN*R2T xk{_i, A˜Xyj|]$AeVMD$/mF>$;Mc8v71F$rm=߷F1U UKˆlIENDB`check_pgbackrest-REL2_4/docs/img/logo.png000066400000000000000000000251161464170754600204620ustar00rootroot00000000000000PNG  IHDRFZ=;sBIT|d IDATx_lSY'Kp{vi)vpwZ\jI')'RmTQ3`GQ7Аa4 UMa^ czV347n6 vW-׻B(DžY# x9B# BB{^P@c{$!hj@&kw_gCz7:g8\S!Now΀۴ٔK-&5n6E]z1kw?}QI}l{9gXnxK;Xa&$?r_?ty[ kwuw6$ԥ}\Pm4C0 㚺ᆷ<ᆘ+)\ȥ2|j?7l]\D1'nY^aca?h}\[ʇD1cC cӃFvvWMr" 3ΎjQ l,]>.|4~qE+ Ӄ3I(ULgEac!D˻jbu'5uZ͓ܹT&9sY+տlP'Eɬְil$m":TA&Zcaݎ2z/ύunk=A9JMfMaݎ u"q2-˓sWa3dhYa c۹;J-kEFYLu1 Okoa cՠipH.I9N /@cݎ`K~ԭw[H vkR͵w:rP.3 ❗.{ZͶhbkw5N4l=w[ uѻ4vѢ/Ѻ71 56 ac Zl2P Jp3†r; [qaC)ҳ2`8^gw?el606`^ a#ZK?aK?a!1}i9шJ?-NN 8ym!lTf`l3>6~FvQK?qM ]ԻA(LRE(}ty!lܴQ^,{Pa#J?'Q/D(zM]ۧw;H l l#P.?mY f(4o}i=uH+NAج ӃSI 6z(65ph"g]Bә< @9+N-ha@+N΀xmϖ ~]qraú惭qh"wũ=ƻn,6(+\?KAS45`/OK @P.L ]gh|^i+N:lPVZYdJtwց#g]zYS J?j,B4_\H!p+N1]ؠ&$qxyO>/=Rs'04a#"Sy5VvÇ J?z.ϑ`1Xei=lXME(霤 Qnbf8?hƠaӴ;A!jzlqmT%gbf6 O::ClO45/gŕ*%jqIҶ3bNii{#Aiw¶:oBR%8Ou}R6(Jjl$'uE*8aTc{$A![PyOF_֞%~vXNдt5h°GnG]ܸꐺvwU&F74hk@S6ƻ:NJeJ^7SX+v+Nz@Ŝ X=]Ix׀)eyZ\΀Ĺ^46($|i^ܕ*)!}Sw X'9{;M+N%%w㗫 7۶lqM+{v 6'8^nng%aOXDyuk;hF˓yvΟc h@  ^B MN}܉zWݳƻhs`=cC?'KO>/=JNXͿzB5ԝO-ީg򹮞M>P.d_z\*K>GϠ>%*qhA#f =\FWʅ.vyr `uVTrϡOGn=|YGu =qj|WA0 hyxbz4vbxCz~{igۦ?p11C'YF$n4mf\#9.d)8 |iLA0 70_;@NmL ޢv|oufb:'߿5S0Lu,|6Ny&ky|iz)8r?R_Wn4 M0گFED#:X*F.hGQb(Cb6k0Lw0_WqL6IbϤ" s#Tl>Vm%&YmWп4އlf4[vY#v4GRU~i=Ifƿg_.U=un0B0LuD 0̶D]4H}(u^%7UrabfN%hD陰%~vvЇR0:=xC+U|sN \ao4>+9[A[JN.Iʙʥ2I%V2F A0 #\VE 5idY%zFFǃZ큱zY^ xi7obɒyM1v0 67 z7w (Qq7̎_*A0_׳il$x\fۖLnˏ3}ͿU]ĈU~)VxdyW_Ǧ&o..*Ohk@rzKMmѻ4 툓?;L{&X]45{w*q_ȗը T/FqMݤ3sclMO4 y 鞛A<͇X+v脙a6/ Ş+jl$bNwE=A4Bج oUjhH΀::aƠaͪHtsp\ZŠ30f{nDe6*X#HZrC:R7O䴚6k G?}P뙕Pguo CبLJ? f;GV M?lE:Q%pyAaҒ\ y,sWUj (aM3xyG7FHRVsCߦƽZ4ChDʞxi6Bhr{G*E>o6BhwSLgyU  %&4Bݤ%CcCV.@o/O>/=}X a! ]l}Rxa;g˅r}\xaƤѫ1-HmHt{oX/- l4&{n'Mz\x/շ(Uo_ +0:=ngٻT&)侘/etk[ #<_4m8q%~n,Tr!_/sԭUrGt}xh F?Fz?RzlzȈaS ,_&P1" 8qmbP7dd.I~,ȥ2\>m1:NB+惭j\*E}a_3"lCDdbatzP방_10H)b#ciQh/w|bfo?Z!Mff)ʇё uO 1u Tƻ6-FaJ@診?73u$ޛ?7LKeRʕBFgqYoJ{vw+i7~ + a3d(UgÙm7)vc{$+ l(@z|a" ߻J|Z C6N?.ȁ5l=I(9g xziwAP`zjLm3hLidJw cDKOg#5?Vm::V5uww6z5aΆRJF\f~av ?FGȗ˺GnY^eBq*aC ~D̼CN>$o>~V]Rn!l(hi 1TX#7Dp)e6݀6qK>=x8 Rry5[O c{7 E UR#܎I\?ލ5`52/%gh aC$kvػQE (TOzyc\#ef7J †BH6l.3NM1QX6r\ud%L]sacT>' %t={ RnrL}I:"9k2eFh/w'ލq l6r?޽b:ώfϡ0A T ^GrdCh=Tʅ[+&N]㚺kX z6@n*Bp{*Ϻ+ՅƤH4W:weݾ&,?u51bX+Q$n4:3Er\batzs8UލVbRǛ$^BsZn|^z aԒRz!lajOB6ĺAIUݐ٩RS acRQ΀7Rkݻr_=7AI2I۠{7#pmoGS4hl݅M'wR-ѫ AJU=U'S a Tջ һo#|&Y lL,QB{(ٻ$ .vm*s=gˑUw#H u}]3ya&ZwC|h.s7S?%>h弦٠g cZnfGsW`U aeFȗl2dfB4 <0mq6iٻKRR!_G;5Z_J~g eOCWjRz7$rL3Beޤs6{G= Ӄ8o}Ϳ|-M=/taImB%j\OSk3+2*B KKpc{$w F#1{7k>*Bp >6 IDATR&=a {lHVJ ӃC7{lHIջo/̬" b/aQ̛,N=2r Hu;$?jP;1$j/!= k#YP(Aۻ4΀\'1_<$]A%{F3J 9b:'߿46u)?kv\ِe(Y ,.3G0  :XkD_:Fu3+"}QJn*B |ǡ3> 'װ i"-/P5+-M# Ӭ-$WM_ P.n놗I`Mb%y5ލ΀ky똹v/UO}O]ۇ.<IRꆲZfW/79^#jXتatV^ȗn()B{j>k[̓kg2% Sfbilմ;"hTų&o / (TrWVF \*k/외2?;#/O WOjUY*m(!_x/xpmϖ~M}-4'<)O xmV٭ g˕2?/?~ 9>Y{zI>#VSO_}˿}Vyock X5b:VaaTN]ۇ.? Kr6YއF 9|i~nxS':v g#K>^ea0ߔM@H#N!*ۿ/Þ?|hiUY,|.LMizǡ38~P?) †a8rnG#I֓Ke3IqǍzX-Zv3Ԝ|7b:Kyn{ _bNhab:Qrr޷Æaһoih w~RbGd1ohuhHh*+lD-=jlb:O 󞧖z*BP[PX#ҳ5hTrabV?+l"a0ClӻYX~\Bnishu/ͧǦ}{9ǩfۖĦM̦\ +]7 P/O Ꝺ>~Vbݎ`&!GðĕƶH dRވ*a#@&7s},@[$/9iF,j؈#w;-CF<2kv ur&M.I\AJ4 5CLP7Ĥ}Y؈(XyU8^N zJfYh6",KWʅA ]?}g`R6)F$FF<K-tOy!7eåz*O˥{0L`iBGȗs3ɹR+jr΀s;pQ^E(.a9^gw?t(|1K^{m< m9^g=JS ̎fKY03KȊP.9aIbyb 0b{F.IN *&*FP5/0!_K;Æap`#56_D}؈Z@(0a#jl$\XY.IN]H 6 TOn!+=2lD8Vf:lD(ef0:=8s},mK٤L6"3ѪVL6 R`|41PFĺhbH\Ѵa#B) 0=K?ha#B)  ba}z刁,6"=VA+ JYh-K,@M~ kf#JBجR#~ f (eRV6K?haSu!l2K l$@) k%!l$-cZAȄR`ZA(, Q>(a0.+~ FE(ea,V-PʂnV/Fp'}p@[PA>PA?6PA z|i~Y1†"(e~†B(e! aC1I膰JYl ac(e:~0CƠZ ac`V*eƇ1@s@ؘnsAؘJY9!lLʈ,P6&g]6Ak)\*v,ac!4 dacQDh/w\?;<721Bz6 x@[$Bj-Y>=6= cNƺ63<ᆘ;3夆O1sd)\熱aw%{bk=" t<)t/9HA5*IENDB`check_pgbackrest-REL2_4/docs/img/shared-storage.excalidraw000066400000000000000000002201551464170754600237710ustar00rootroot00000000000000{ "type": "excalidraw", "version": 2, "source": "https://dai-shi.github.io", "elements": [ { "type": "line", "version": 2690, "versionNonce": 1990677530, "isDeleted": false, "id": "8PadqC2teQJrQKYrFsa2b", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "angle": 0, "x": 504.1192851874751, "y": 475.6408954055245, "strokeColor": "#364fc7", "backgroundColor": "#228be6", "width": 149.14020047953585, "height": 172.93718990719944, "seed": 566146992, "groupIds": [ "qUqE8TVENR5zP7f9qYcoe", "mSMSi6l5FmZ1AF6D_TOWp" ], "roundness": { "type": 2 }, "boundElements": [], "updated": 1720098904359, "link": null, "locked": false, "startBinding": null, "endBinding": null, "lastCommittedPoint": null, "startArrowhead": null, "endArrowhead": null, "points": [ [ 0, 0 ], [ 0, 119.45772383626583 ], [ 2.612884253575292, 141.06003234219625 ], [ 20.5487829754577, 148.99705608664033 ], [ 47.41246384241946, 155.27315546248917 ], [ 76.05912170928181, 156.893920106708 ], [ 100.51660891811896, 155.27790977939927 ], [ 123.45583656895063, 151.2829751494915 ], [ 146.3359920988982, 140.20865297402008 ], [ 147.90003903537678, 119.45772383626583 ], [ 149.14020047953585, 16.271505205385562 ], [ 147.25709610157793, -0.5201874078106299 ], [ 128.7191832837304, -9.477845552685583 ], [ 107.9139670795626, -14.552192465665541 ], [ 80.86628858863293, -16.043269800491437 ], [ 63.699444007302795, -15.999876756741044 ], [ 27.143748462563003, -12.772397221326981 ], [ 0, 0 ] ] }, { "type": "ellipse", "version": 941, "versionNonce": 691737222, "isDeleted": false, "id": "pVBqvLitN1VyXp6hceKqR", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "angle": 0, "x": 503.57016920005657, "y": 457.5191872679495, "strokeColor": "#364fc7", "backgroundColor": "#ffffff", "width": 148.87614152402688, "height": 40.47866047817269, "seed": 1752030032, "groupIds": [ "qUqE8TVENR5zP7f9qYcoe", "mSMSi6l5FmZ1AF6D_TOWp" ], "roundness": null, "boundElements": [], "updated": 1720098904359, "link": null, "locked": false }, { "type": "text", "version": 174, "versionNonce": 324585286, "isDeleted": false, "id": "tuR_b3vjQISqc75LWn9zC", "fillStyle": "hachure", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "angle": 0, "x": 527.5272520637614, "y": 633.9598146077444, "strokeColor": "#000000", "backgroundColor": "#228be6", "width": 103.51667022705078, "height": 36.75846402912392, "seed": 40649136, "groupIds": [ "VzT2HQNFjOWocYzN8yQfm", "mSMSi6l5FmZ1AF6D_TOWp" ], "roundness": null, "boundElements": [], "updated": 1720099102971, "link": null, "locked": false, "fontSize": 29.40677122329914, "fontFamily": 1, "text": "Primary", "textAlign": "center", "verticalAlign": "top", "containerId": null, "originalText": "Primary", "lineHeight": 1.25, "baseline": 26 }, { "type": "line", "version": 2844, "versionNonce": 1719849414, "isDeleted": false, "id": "3vCAzUeARZVsbpRTDFgpD", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "angle": 0, "x": 798.9068201645463, "y": 579.5656206300159, "strokeColor": "#2b8a3e", "backgroundColor": "#40c057", "width": 149.14020047953585, "height": 172.93718990719944, "seed": 455438160, "groupIds": [ "XVTRE9J7xgCauKrBFQvj7", "Jc3Mazm8TbZ0dKKLRmylq" ], "roundness": { "type": 2 }, "boundElements": [], "updated": 1720098904359, "link": null, "locked": false, "startBinding": null, "endBinding": null, "lastCommittedPoint": null, "startArrowhead": null, "endArrowhead": null, "points": [ [ 0, 0 ], [ 0, 119.45772383626583 ], [ 2.612884253575292, 141.06003234219625 ], [ 20.5487829754577, 148.99705608664033 ], [ 47.41246384241946, 155.27315546248917 ], [ 76.05912170928181, 156.893920106708 ], [ 100.51660891811896, 155.27790977939927 ], [ 123.45583656895063, 151.2829751494915 ], [ 146.3359920988982, 140.20865297402008 ], [ 147.90003903537678, 119.45772383626583 ], [ 149.14020047953585, 16.271505205385562 ], [ 147.25709610157793, -0.5201874078106299 ], [ 128.7191832837304, -9.477845552685583 ], [ 107.9139670795626, -14.552192465665541 ], [ 80.86628858863293, -16.043269800491437 ], [ 63.699444007302795, -15.999876756741044 ], [ 27.143748462563003, -12.772397221326981 ], [ 0, 0 ] ] }, { "type": "ellipse", "version": 1094, "versionNonce": 1428198810, "isDeleted": false, "id": "pKHWlw5SHQqPXyloWJAkc", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "angle": 0, "x": 798.3577041771277, "y": 561.4439124924411, "strokeColor": "#2b8a3e", "backgroundColor": "#ffffff", "width": 148.87614152402688, "height": 40.47866047817269, "seed": 665316784, "groupIds": [ "XVTRE9J7xgCauKrBFQvj7", "Jc3Mazm8TbZ0dKKLRmylq" ], "roundness": null, "boundElements": [], "updated": 1720098904359, "link": null, "locked": false }, { "type": "text", "version": 318, "versionNonce": 15644698, "isDeleted": false, "id": "dudwG5omBq8EHJPBOdC8T", "fillStyle": "hachure", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "angle": 0, "x": 815.434725770657, "y": 737.3528265075118, "strokeColor": "#000000", "backgroundColor": "#228be6", "width": 113.66666412353516, "height": 36.75846402912392, "seed": 2036008272, "groupIds": [ "NtAO7hOs1RVf0QKgElvUy", "Jc3Mazm8TbZ0dKKLRmylq" ], "roundness": null, "boundElements": [], "updated": 1720099102972, "link": null, "locked": false, "fontSize": 29.40677122329914, "fontFamily": 1, "text": "Standby", "textAlign": "center", "verticalAlign": "top", "containerId": null, "originalText": "Standby", "lineHeight": 1.25, "baseline": 26 }, { "type": "line", "version": 2906, "versionNonce": 2054083162, "isDeleted": false, "id": "4br_XB723i67GbujcQ6XA", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "angle": 0, "x": 1005.4448709587027, "y": 408.2584448737113, "strokeColor": "#2b8a3e", "backgroundColor": "#40c057", "width": 149.14020047953585, "height": 172.93718990719944, "seed": 455438160, "groupIds": [ "rvqY0MgG-uDv3cfWVdGKL", "6L2J8gKnx4-aOtiC8uPs5" ], "roundness": { "type": 2 }, "boundElements": [], "updated": 1720098904359, "link": null, "locked": false, "startBinding": null, "endBinding": null, "lastCommittedPoint": null, "startArrowhead": null, "endArrowhead": null, "points": [ [ 0, 0 ], [ 0, 119.45772383626583 ], [ 2.612884253575292, 141.06003234219625 ], [ 20.5487829754577, 148.99705608664033 ], [ 47.41246384241946, 155.27315546248917 ], [ 76.05912170928181, 156.893920106708 ], [ 100.51660891811896, 155.27790977939927 ], [ 123.45583656895063, 151.2829751494915 ], [ 146.3359920988982, 140.20865297402008 ], [ 147.90003903537678, 119.45772383626583 ], [ 149.14020047953585, 16.271505205385562 ], [ 147.25709610157793, -0.5201874078106299 ], [ 128.7191832837304, -9.477845552685583 ], [ 107.9139670795626, -14.552192465665541 ], [ 80.86628858863293, -16.043269800491437 ], [ 63.699444007302795, -15.999876756741044 ], [ 27.143748462563003, -12.772397221326981 ], [ 0, 0 ] ] }, { "type": "ellipse", "version": 1156, "versionNonce": 1180801094, "isDeleted": false, "id": "1fYZwb2zISmgpSJqNXDpl", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "angle": 0, "x": 1004.8957549712841, "y": 390.13673673613636, "strokeColor": "#2b8a3e", "backgroundColor": "#ffffff", "width": 148.87614152402688, "height": 40.47866047817269, "seed": 665316784, "groupIds": [ "rvqY0MgG-uDv3cfWVdGKL", "6L2J8gKnx4-aOtiC8uPs5" ], "roundness": null, "boundElements": [], "updated": 1720098904359, "link": null, "locked": false }, { "type": "text", "version": 380, "versionNonce": 1418456710, "isDeleted": false, "id": "GKgWvfY0hLRlc31Shrhf1", "fillStyle": "hachure", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "angle": 0, "x": 1021.9727765648133, "y": 566.0456507512072, "strokeColor": "#000000", "backgroundColor": "#228be6", "width": 113.66666412353516, "height": 36.75846402912392, "seed": 2036008272, "groupIds": [ "OgXrzLr5IgT4mnmHcWj9h", "6L2J8gKnx4-aOtiC8uPs5" ], "roundness": null, "boundElements": [], "updated": 1720099102972, "link": null, "locked": false, "fontSize": 29.40677122329914, "fontFamily": 1, "text": "Standby", "textAlign": "center", "verticalAlign": "top", "containerId": null, "originalText": "Standby", "lineHeight": 1.25, "baseline": 26 }, { "type": "rectangle", "version": 702, "versionNonce": 1931366362, "isDeleted": false, "id": "Av4wwxqsXOBSdUclyt8vH", "fillStyle": "solid", "strokeWidth": 4, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "x": 784.3763748307442, "y": 233.70302289742764, "strokeColor": "#228be6", "backgroundColor": "#228be6", "width": 77.6898161377464, "height": 85.72410944990688, "seed": 1158098841, "groupIds": [ "8rB96K2QrN6jgwpKPVnNM", "kBN1D-JEWRjyaoVTgExXV", "ImMiElv1zOpxHCCZV_3ML" ], "roundness": { "type": 3 }, "boundElements": [ { "id": "-HyL0YvulYuk-1P6UdgMT", "type": "arrow" }, { "id": "x8NQOCbggxBOgiGfPAGrz", "type": "arrow" }, { "id": "WpY5Wxo4z6t65SWdYiMcZ", "type": "arrow" } ], "updated": 1720099061167, "link": null, "locked": false }, { "type": "line", "version": 816, "versionNonce": 128279322, "isDeleted": false, "id": "32bEJhHMNqjiVIlA-IRjS", "fillStyle": "solid", "strokeWidth": 4, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "x": 786.2362280122007, "y": 310.2183756585947, "strokeColor": "#228be6", "backgroundColor": "#228be6", "width": 52.665245784628624, "height": 76.51525170136297, "seed": 1442312823, "groupIds": [ "8rB96K2QrN6jgwpKPVnNM", "kBN1D-JEWRjyaoVTgExXV", "ImMiElv1zOpxHCCZV_3ML" ], "roundness": { "type": 2 }, "boundElements": [], "updated": 1720098969537, "link": null, "locked": false, "startBinding": null, "endBinding": null, "lastCommittedPoint": null, "startArrowhead": null, "endArrowhead": null, "points": [ [ 0, 0 ], [ -14.707883432553087, -67.52054655227478 ], [ 37.95736235207554, -76.51525170136297 ] ] }, { "type": "line", "version": 984, "versionNonce": 975795078, "isDeleted": false, "id": "fvtnbid8VHXOmFAX8rfh4", "fillStyle": "solid", "strokeWidth": 4, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "x": 860.2955911213742, "y": 309.92479937776494, "strokeColor": "#228be6", "backgroundColor": "#228be6", "width": 55.184052133126876, "height": 76.22167542053306, "seed": 462410873, "groupIds": [ "8rB96K2QrN6jgwpKPVnNM", "kBN1D-JEWRjyaoVTgExXV", "ImMiElv1zOpxHCCZV_3ML" ], "roundness": { "type": 2 }, "boundElements": [], "updated": 1720098969537, "link": null, "locked": false, "startBinding": null, "endBinding": null, "lastCommittedPoint": null, "startArrowhead": null, "endArrowhead": null, "points": [ [ 0, 0 ], [ 13.564505877748402, -67.35417528571563 ], [ -41.619546255378474, -76.22167542053306 ] ] }, { "type": "rectangle", "version": 686, "versionNonce": 1845358554, "isDeleted": false, "id": "S_Myx1W6tXAazw2jZ74m-", "fillStyle": "solid", "strokeWidth": 4, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 6.164133487909485, "x": 775.1353258055896, "y": 240.2919616223947, "strokeColor": "#228be6", "backgroundColor": "#228be6", "width": 16.557555181026135, "height": 46.01169726210666, "seed": 90213271, "groupIds": [ "8rB96K2QrN6jgwpKPVnNM", "kBN1D-JEWRjyaoVTgExXV", "ImMiElv1zOpxHCCZV_3ML" ], "roundness": { "type": 3 }, "boundElements": [], "updated": 1720098969537, "link": null, "locked": false }, { "type": "rectangle", "version": 783, "versionNonce": 1828524742, "isDeleted": false, "id": "f89Vfu6KKkYIQ7Q9k5tkZ", "fillStyle": "solid", "strokeWidth": 4, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0.17835949278002072, "x": 852.4940513371448, "y": 239.52233118103288, "strokeColor": "#228be6", "backgroundColor": "#228be6", "width": 16.557555181026135, "height": 46.01169726210666, "seed": 1735449945, "groupIds": [ "8rB96K2QrN6jgwpKPVnNM", "kBN1D-JEWRjyaoVTgExXV", "ImMiElv1zOpxHCCZV_3ML" ], "roundness": { "type": 3 }, "boundElements": [], "updated": 1720098969537, "link": null, "locked": false }, { "type": "rectangle", "version": 581, "versionNonce": 992404634, "isDeleted": false, "id": "p-fgOAHeX58hXmQBmPh9K", "fillStyle": "solid", "strokeWidth": 4, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "x": 786.4192577492922, "y": 248.5802335178995, "strokeColor": "#ffffff", "backgroundColor": "transparent", "width": 30.339333975998805, "height": 30.515910009065944, "seed": 714670263, "groupIds": [ "kBN1D-JEWRjyaoVTgExXV", "ImMiElv1zOpxHCCZV_3ML" ], "roundness": { "type": 3 }, "boundElements": [], "updated": 1720098969537, "link": null, "locked": false }, { "type": "ellipse", "version": 601, "versionNonce": 446351878, "isDeleted": false, "id": "mCXwSHPD0XIh2V2TQEQKa", "fillStyle": "solid", "strokeWidth": 4, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "x": 808.1667924585138, "y": 285.7642780047902, "strokeColor": "#ffffff", "backgroundColor": "transparent", "width": 30.16573862615822, "height": 29.464936769740294, "seed": 670943801, "groupIds": [ "kBN1D-JEWRjyaoVTgExXV", "ImMiElv1zOpxHCCZV_3ML" ], "roundness": { "type": 2 }, "boundElements": [], "updated": 1720098969537, "link": null, "locked": false }, { "type": "diamond", "version": 744, "versionNonce": 148062554, "isDeleted": false, "id": "H8HuJd5RUSNI3JX9i7rYd", "fillStyle": "solid", "strokeWidth": 4, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "x": 826.6916729058455, "y": 246.4616444405258, "strokeColor": "#ffffff", "backgroundColor": "transparent", "width": 37.763427280069465, "height": 37.914269516661236, "seed": 927322583, "groupIds": [ "kBN1D-JEWRjyaoVTgExXV", "ImMiElv1zOpxHCCZV_3ML" ], "roundness": null, "boundElements": [], "updated": 1720098969537, "link": null, "locked": false }, { "type": "text", "version": 495, "versionNonce": 2102699226, "isDeleted": false, "id": "-jhvTmvevZ4bvB9gGOH3E", "fillStyle": "solid", "strokeWidth": 4, "strokeStyle": "solid", "roughness": 0, "opacity": 100, "angle": 0, "x": 765.7069526653224, "y": 152.0986581555827, "strokeColor": "#000000", "backgroundColor": "#fa5252", "width": 114.23332977294922, "height": 73.51692805824784, "seed": 1133842295, "groupIds": [ "ImMiElv1zOpxHCCZV_3ML" ], "roundness": null, "boundElements": [], "updated": 1720099102972, "link": null, "locked": false, "fontSize": 29.40677122329914, "fontFamily": 1, "text": "Backup\nStorage", "textAlign": "center", "verticalAlign": "top", "containerId": null, "originalText": "Backup\nStorage", "lineHeight": 1.25, "baseline": 63 }, { "id": "33n0KZdNGe0UNtvueN02w", "type": "image", "x": 609.75166085861, "y": 501.7162061893055, "width": 38.228802590288865, "height": 38.228802590288865, "angle": 0, "strokeColor": "transparent", "backgroundColor": "transparent", "fillStyle": "hachure", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "groupIds": [], "roundness": null, "seed": 198853402, "version": 242, "versionNonce": 834862598, "isDeleted": false, "boundElements": [ { "id": "-HyL0YvulYuk-1P6UdgMT", "type": "arrow" } ], "updated": 1720098982924, "link": null, "locked": false, "status": "pending", "fileId": "2eb8257a06841d380d8ba6c51d37f72fd3a34bfa", "scale": [ 1, 1 ] }, { "type": "image", "version": 303, "versionNonce": 1114741510, "isDeleted": false, "id": "8hfFXr989zqacigJBRPtu", "fillStyle": "hachure", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "angle": 0, "x": 906.2303887750962, "y": 606.7585512261926, "strokeColor": "transparent", "backgroundColor": "transparent", "width": 38.228802590288865, "height": 38.228802590288865, "seed": 198853402, "groupIds": [], "roundness": null, "boundElements": [ { "id": "x8NQOCbggxBOgiGfPAGrz", "type": "arrow" } ], "updated": 1720099036821, "link": null, "locked": false, "status": "pending", "fileId": "2eb8257a06841d380d8ba6c51d37f72fd3a34bfa", "scale": [ 1, 1 ] }, { "type": "image", "version": 324, "versionNonce": 2101036186, "isDeleted": false, "id": "fWl2gLooxQ-An0MuBTirV", "fillStyle": "hachure", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "angle": 0, "x": 1114.2387781304174, "y": 439.1272218728004, "strokeColor": "transparent", "backgroundColor": "transparent", "width": 38.228802590288865, "height": 38.228802590288865, "seed": 198853402, "groupIds": [], "roundness": null, "boundElements": [ { "id": "WpY5Wxo4z6t65SWdYiMcZ", "type": "arrow" } ], "updated": 1720099061168, "link": null, "locked": false, "status": "pending", "fileId": "2eb8257a06841d380d8ba6c51d37f72fd3a34bfa", "scale": [ 1, 1 ] }, { "type": "line", "version": 4019, "versionNonce": 1427331162, "isDeleted": false, "id": "8rsknAluX9SbzHOBuuK4Q", "fillStyle": "hachure", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "angle": 0, "x": 585.4333939607773, "y": 564.5060898501853, "strokeColor": "#000000", "backgroundColor": "transparent", "width": 49.4582267818055, "height": 49.63085194114822, "seed": 1726952975, "groupIds": [ "HUV3GluJ-oXqMFfDRINJG" ], "roundness": { "type": 2 }, "boundElements": [], "updated": 1720098904359, "link": null, "locked": false, "startBinding": null, "endBinding": null, "lastCommittedPoint": null, "startArrowhead": null, "endArrowhead": null, "points": [ [ 0, 0 ], [ -5.032931101083877, -1.4840694272427097 ], [ -8.83989180574997, -0.6452475770620443 ], [ -12.421015858444239, 0.25809903082481583 ], [ -23.50314299448491, -1.6615125109347577 ], [ -31.197720350949737, 3.3552874007226268 ], [ -30.988014888404535, 15.356892334076656 ], [ -26.437867242300946, 27.81132279919027 ], [ -20.43591254780789, 34.37786912361271 ], [ -14.421283347336667, 27.40919529134261 ], [ -11.125911793055536, 27.457588859622334 ], [ -9.992119621932183, 28.492289438553964 ], [ -11.38861973514494, 30.068537091091237 ], [ -13.275968898051493, 31.11706440381708 ], [ -16.340894889096276, 32.843101672458005 ], [ -11.829538912804091, 33.445332744382505 ], [ -8.603301027493963, 33.2087419661265 ], [ -6.624541791170299, 33.76795653291356 ], [ -5.332462830317487, 42.38788138446358 ], [ -2.371284845702893, 47.6951092099621 ], [ 4.5859427131632735, 46.022083921460315 ], [ 6.726772495035079, 41.2127135185 ], [ 6.936411453416965, 33.988416121743036 ], [ 7.855889250730349, 31.310638676935618 ], [ 13.163050572065663, 31.229982729802863 ], [ 16.93774889787865, 28.342499822450232 ], [ 12.727508457548831, 27.923088897359893 ], [ 10.08199339159442, 26.01960854502688 ], [ 13.679248633715295, 19.42195206956744 ], [ 17.421684580675134, 11.017602378334363 ], [ 18.260506430855763, 2.0002674888923293 ], [ 12.195179206472568, -1.4195446695364975 ], [ 5.1619806164963355, -1.9357427311861257 ], [ 0, 0 ] ] }, { "type": "line", "version": 1695, "versionNonce": 984642118, "isDeleted": false, "id": "WRijKnSEvdAE2AmLFpTmN", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "angle": 0, "x": 574.2109082509885, "y": 564.8446335929631, "strokeColor": "#000000", "backgroundColor": "transparent", "width": 10.592814390101838, "height": 32.369920115945696, "seed": 726808161, "groupIds": [ "HUV3GluJ-oXqMFfDRINJG" ], "roundness": { "type": 2 }, "boundElements": [], "updated": 1720098904359, "link": null, "locked": false, "startBinding": null, "endBinding": null, "lastCommittedPoint": null, "startArrowhead": null, "endArrowhead": null, "points": [ [ 0, 0 ], [ -3.360664463864801, 3.4682057267084665 ], [ -5.027554037941746, 9.759369603063378 ], [ -4.301650513746954, 16.991519529300426 ], [ -5.269521879339978, 21.21251409591456 ], [ -4.220994566614168, 24.546293244068494 ], [ -1.6937748897878335, 26.26695344956722 ], [ 1.5862336269442, 27.664989866535066 ], [ 4.40919177659063, 28.57909060070622 ], [ 5.32329251076186, 30.89122775184531 ], [ 3.8983707780831756, 32.369920115945696 ] ] }, { "type": "line", "version": 1132, "versionNonce": 1551580442, "isDeleted": false, "id": "j8jdjWi3wvj1trJ12TGVB", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "angle": 0, "x": 585.2444807922515, "y": 564.5102682489804, "strokeColor": "#000000", "backgroundColor": "transparent", "width": 9.84002555019615, "height": 25.89055902961443, "seed": 316382255, "groupIds": [ "HUV3GluJ-oXqMFfDRINJG" ], "roundness": { "type": 2 }, "boundElements": [], "updated": 1720098904359, "link": null, "locked": false, "startBinding": null, "endBinding": null, "lastCommittedPoint": null, "startArrowhead": null, "endArrowhead": null, "points": [ [ 0, 0 ], [ 2.7423022025136805, 0.5645916299292854 ], [ 6.936411453416969, 3.871485462372254 ], [ 8.872154184603094, 10.727240968656446 ], [ 8.388218501806552, 17.260372686409646 ], [ 9.84002555019615, 22.261041408640445 ], [ 8.710842290337574, 25.89055902961443 ] ] }, { "type": "line", "version": 1832, "versionNonce": 699808134, "isDeleted": false, "id": "S8zIVW6NcbAD7wrGdGQog", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "angle": 0, "x": 592.7592240455814, "y": 596.3836912249983, "strokeColor": "#000000", "backgroundColor": "transparent", "width": 7.355822378507269, "height": 22.228779029787354, "seed": 1331610177, "groupIds": [ "HUV3GluJ-oXqMFfDRINJG" ], "roundness": { "type": 2 }, "boundElements": [], "updated": 1720098904359, "link": null, "locked": false, "startBinding": null, "endBinding": null, "lastCommittedPoint": null, "startArrowhead": null, "endArrowhead": null, "points": [ [ 0, 0 ], [ 0.16668895740769388, -2.952007665058909 ], [ 1.3684625696857604, -4.210240440329895 ], [ 2.8928599704948215, -4.855488017391895 ], [ 1.0700355652945566, -6.1298519820895185 ], [ -0.43016505137471023, -8.791498237470364 ], [ -2.0271528046032645, -12.453278237297498 ], [ -4.462962408012448, -18.389555946268214 ], [ -3.4144350952866436, -22.01907356724235 ], [ 1.1345603230007582, -22.228779029787354 ] ] }, { "type": "line", "version": 1292, "versionNonce": 520653274, "isDeleted": false, "id": "jYQrLDIF5FbJ6khVpS2LO", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "angle": 0, "x": 569.3456662213416, "y": 577.2516047792574, "strokeColor": "#000000", "backgroundColor": "transparent", "width": 7.823626871877253, "height": 16.61512510934757, "seed": 1821785679, "groupIds": [ "HUV3GluJ-oXqMFfDRINJG" ], "roundness": { "type": 2 }, "boundElements": [], "updated": 1720098904359, "link": null, "locked": false, "startBinding": null, "endBinding": null, "lastCommittedPoint": null, "startArrowhead": null, "endArrowhead": null, "points": [ [ 0, 0 ], [ 2.634760939670006, -1.344265785545911 ], [ 5.968540087823887, -1.6937748897878564 ], [ 7.823626871877253, 1.0485273127258228 ], [ 7.823626871877253, 6.452475770620425 ], [ 6.694443612018691, 10.485273127258173 ], [ 6.049196034956644, 14.921350219559713 ] ] }, { "type": "line", "version": 1371, "versionNonce": 453056710, "isDeleted": false, "id": "rc6GNF05FHi-jj4IFheyo", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "angle": 6.251548691252406, "x": 590.9148637082301, "y": 575.202285094926, "strokeColor": "#000000", "backgroundColor": "transparent", "width": 2.5511342048730024, "height": 1.5915782724481118, "seed": 2108609057, "groupIds": [ "HUV3GluJ-oXqMFfDRINJG" ], "roundness": { "type": 2 }, "boundElements": [], "updated": 1720098904359, "link": null, "locked": false, "startBinding": null, "endBinding": null, "lastCommittedPoint": null, "startArrowhead": null, "endArrowhead": null, "points": [ [ 0, 0 ], [ -1.293532836273635, 0.6764207657904466 ], [ 0.07186293534853483, 1.5915782724481118 ], [ 1.257601368599367, 0.278526197678419 ], [ 0, 0 ] ] }, { "type": "line", "version": 1497, "versionNonce": 1674460826, "isDeleted": false, "id": "hcRz2pIRK-XUwmwO4D4ww", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "angle": 0.25834385002361504, "x": 574.2003413013061, "y": 576.5940765954768, "strokeColor": "#000000", "backgroundColor": "transparent", "width": 2.710159067921797, "height": 1.4966154553657987, "seed": 385326191, "groupIds": [ "HUV3GluJ-oXqMFfDRINJG" ], "roundness": { "type": 2 }, "boundElements": [], "updated": 1720098904359, "link": null, "locked": false, "startBinding": null, "endBinding": null, "lastCommittedPoint": null, "startArrowhead": null, "endArrowhead": null, "points": [ [ 0, 0 ], [ -1.325572160124439, 0.6424986103690914 ], [ -0.15876841439743236, 1.4966154553657987 ], [ 0.9711221605739294, 1.2498082249610247 ], [ 1.3845869077973583, 0.04878639282165335 ], [ 0, 0 ] ] }, { "type": "line", "version": 4110, "versionNonce": 908988422, "isDeleted": false, "id": "5iDfRTKG-x2hTqpRudvng", "fillStyle": "hachure", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "angle": 0, "x": 878.5431201232153, "y": 672.7248626006126, "strokeColor": "#000000", "backgroundColor": "transparent", "width": 49.4582267818055, "height": 49.63085194114822, "seed": 1726952975, "groupIds": [ "iqsNg4LZwWxJVGcvfw7mW" ], "roundness": { "type": 2 }, "boundElements": [], "updated": 1720098904359, "link": null, "locked": false, "startBinding": null, "endBinding": null, "lastCommittedPoint": null, "startArrowhead": null, "endArrowhead": null, "points": [ [ 0, 0 ], [ -5.032931101083877, -1.4840694272427097 ], [ -8.83989180574997, -0.6452475770620443 ], [ -12.421015858444239, 0.25809903082481583 ], [ -23.50314299448491, -1.6615125109347577 ], [ -31.197720350949737, 3.3552874007226268 ], [ -30.988014888404535, 15.356892334076656 ], [ -26.437867242300946, 27.81132279919027 ], [ -20.43591254780789, 34.37786912361271 ], [ -14.421283347336667, 27.40919529134261 ], [ -11.125911793055536, 27.457588859622334 ], [ -9.992119621932183, 28.492289438553964 ], [ -11.38861973514494, 30.068537091091237 ], [ -13.275968898051493, 31.11706440381708 ], [ -16.340894889096276, 32.843101672458005 ], [ -11.829538912804091, 33.445332744382505 ], [ -8.603301027493963, 33.2087419661265 ], [ -6.624541791170299, 33.76795653291356 ], [ -5.332462830317487, 42.38788138446358 ], [ -2.371284845702893, 47.6951092099621 ], [ 4.5859427131632735, 46.022083921460315 ], [ 6.726772495035079, 41.2127135185 ], [ 6.936411453416965, 33.988416121743036 ], [ 7.855889250730349, 31.310638676935618 ], [ 13.163050572065663, 31.229982729802863 ], [ 16.93774889787865, 28.342499822450232 ], [ 12.727508457548831, 27.923088897359893 ], [ 10.08199339159442, 26.01960854502688 ], [ 13.679248633715295, 19.42195206956744 ], [ 17.421684580675134, 11.017602378334363 ], [ 18.260506430855763, 2.0002674888923293 ], [ 12.195179206472568, -1.4195446695364975 ], [ 5.1619806164963355, -1.9357427311861257 ], [ 0, 0 ] ] }, { "type": "line", "version": 1786, "versionNonce": 394222426, "isDeleted": false, "id": "V0BZh05oYL_5ZBwOgyA2Z", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "angle": 0, "x": 867.3206344134265, "y": 673.0634063433904, "strokeColor": "#000000", "backgroundColor": "transparent", "width": 10.592814390101838, "height": 32.369920115945696, "seed": 726808161, "groupIds": [ "iqsNg4LZwWxJVGcvfw7mW" ], "roundness": { "type": 2 }, "boundElements": [], "updated": 1720098904359, "link": null, "locked": false, "startBinding": null, "endBinding": null, "lastCommittedPoint": null, "startArrowhead": null, "endArrowhead": null, "points": [ [ 0, 0 ], [ -3.360664463864801, 3.4682057267084665 ], [ -5.027554037941746, 9.759369603063378 ], [ -4.301650513746954, 16.991519529300426 ], [ -5.269521879339978, 21.21251409591456 ], [ -4.220994566614168, 24.546293244068494 ], [ -1.6937748897878335, 26.26695344956722 ], [ 1.5862336269442, 27.664989866535066 ], [ 4.40919177659063, 28.57909060070622 ], [ 5.32329251076186, 30.89122775184531 ], [ 3.8983707780831756, 32.369920115945696 ] ] }, { "type": "line", "version": 1223, "versionNonce": 658120518, "isDeleted": false, "id": "K-9oUObZLllX3ANiaRI0Y", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "angle": 0, "x": 878.3542069546894, "y": 672.7290409994075, "strokeColor": "#000000", "backgroundColor": "transparent", "width": 9.84002555019615, "height": 25.89055902961443, "seed": 316382255, "groupIds": [ "iqsNg4LZwWxJVGcvfw7mW" ], "roundness": { "type": 2 }, "boundElements": [], "updated": 1720098904359, "link": null, "locked": false, "startBinding": null, "endBinding": null, "lastCommittedPoint": null, "startArrowhead": null, "endArrowhead": null, "points": [ [ 0, 0 ], [ 2.7423022025136805, 0.5645916299292854 ], [ 6.936411453416969, 3.871485462372254 ], [ 8.872154184603094, 10.727240968656446 ], [ 8.388218501806552, 17.260372686409646 ], [ 9.84002555019615, 22.261041408640445 ], [ 8.710842290337574, 25.89055902961443 ] ] }, { "type": "line", "version": 1923, "versionNonce": 409919514, "isDeleted": false, "id": "rOE4kUotfN9Ir1s9GydHt", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "angle": 0, "x": 885.8689502080193, "y": 704.6024639754255, "strokeColor": "#000000", "backgroundColor": "transparent", "width": 7.355822378507269, "height": 22.228779029787354, "seed": 1331610177, "groupIds": [ "iqsNg4LZwWxJVGcvfw7mW" ], "roundness": { "type": 2 }, "boundElements": [], "updated": 1720098904359, "link": null, "locked": false, "startBinding": null, "endBinding": null, "lastCommittedPoint": null, "startArrowhead": null, "endArrowhead": null, "points": [ [ 0, 0 ], [ 0.16668895740769388, -2.952007665058909 ], [ 1.3684625696857604, -4.210240440329895 ], [ 2.8928599704948215, -4.855488017391895 ], [ 1.0700355652945566, -6.1298519820895185 ], [ -0.43016505137471023, -8.791498237470364 ], [ -2.0271528046032645, -12.453278237297498 ], [ -4.462962408012448, -18.389555946268214 ], [ -3.4144350952866436, -22.01907356724235 ], [ 1.1345603230007582, -22.228779029787354 ] ] }, { "type": "line", "version": 1383, "versionNonce": 310527622, "isDeleted": false, "id": "VHYX9HTuW02Ie2gixvTI1", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "angle": 0, "x": 862.4553923837797, "y": 685.4703775296846, "strokeColor": "#000000", "backgroundColor": "transparent", "width": 7.823626871877253, "height": 16.61512510934757, "seed": 1821785679, "groupIds": [ "iqsNg4LZwWxJVGcvfw7mW" ], "roundness": { "type": 2 }, "boundElements": [], "updated": 1720098904359, "link": null, "locked": false, "startBinding": null, "endBinding": null, "lastCommittedPoint": null, "startArrowhead": null, "endArrowhead": null, "points": [ [ 0, 0 ], [ 2.634760939670006, -1.344265785545911 ], [ 5.968540087823887, -1.6937748897878564 ], [ 7.823626871877253, 1.0485273127258228 ], [ 7.823626871877253, 6.452475770620425 ], [ 6.694443612018691, 10.485273127258173 ], [ 6.049196034956644, 14.921350219559713 ] ] }, { "type": "line", "version": 1462, "versionNonce": 42978522, "isDeleted": false, "id": "gd1_L7yg4zmmOYvyJUv_8", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "angle": 6.251548691252406, "x": 884.0245898706681, "y": 683.4210578453531, "strokeColor": "#000000", "backgroundColor": "transparent", "width": 2.5511342048730024, "height": 1.5915782724481118, "seed": 2108609057, "groupIds": [ "iqsNg4LZwWxJVGcvfw7mW" ], "roundness": { "type": 2 }, "boundElements": [], "updated": 1720098904359, "link": null, "locked": false, "startBinding": null, "endBinding": null, "lastCommittedPoint": null, "startArrowhead": null, "endArrowhead": null, "points": [ [ 0, 0 ], [ -1.293532836273635, 0.6764207657904466 ], [ 0.07186293534853483, 1.5915782724481118 ], [ 1.257601368599367, 0.278526197678419 ], [ 0, 0 ] ] }, { "type": "line", "version": 1588, "versionNonce": 147031494, "isDeleted": false, "id": "SMzs1bdXwk9iOr5_k7nOu", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "angle": 0.25834385002361504, "x": 867.3100674637441, "y": 684.8128493459039, "strokeColor": "#000000", "backgroundColor": "transparent", "width": 2.710159067921797, "height": 1.4966154553657987, "seed": 385326191, "groupIds": [ "iqsNg4LZwWxJVGcvfw7mW" ], "roundness": { "type": 2 }, "boundElements": [], "updated": 1720098904359, "link": null, "locked": false, "startBinding": null, "endBinding": null, "lastCommittedPoint": null, "startArrowhead": null, "endArrowhead": null, "points": [ [ 0, 0 ], [ -1.325572160124439, 0.6424986103690914 ], [ -0.15876841439743236, 1.4966154553657987 ], [ 0.9711221605739294, 1.2498082249610247 ], [ 1.3845869077973583, 0.04878639282165335 ], [ 0, 0 ] ] }, { "type": "line", "version": 4097, "versionNonce": 2071273882, "isDeleted": false, "id": "P6m4Ov8k72cZxevN30g6m", "fillStyle": "hachure", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "angle": 0, "x": 1081.5431201232154, "y": 500.7248626006127, "strokeColor": "#000000", "backgroundColor": "transparent", "width": 49.4582267818055, "height": 49.63085194114822, "seed": 1726952975, "groupIds": [ "vYban4X0aguFVaH8w8Miz" ], "roundness": { "type": 2 }, "boundElements": [], "updated": 1720098904359, "link": null, "locked": false, "startBinding": null, "endBinding": null, "lastCommittedPoint": null, "startArrowhead": null, "endArrowhead": null, "points": [ [ 0, 0 ], [ -5.032931101083877, -1.4840694272427097 ], [ -8.83989180574997, -0.6452475770620443 ], [ -12.421015858444239, 0.25809903082481583 ], [ -23.50314299448491, -1.6615125109347577 ], [ -31.197720350949737, 3.3552874007226268 ], [ -30.988014888404535, 15.356892334076656 ], [ -26.437867242300946, 27.81132279919027 ], [ -20.43591254780789, 34.37786912361271 ], [ -14.421283347336667, 27.40919529134261 ], [ -11.125911793055536, 27.457588859622334 ], [ -9.992119621932183, 28.492289438553964 ], [ -11.38861973514494, 30.068537091091237 ], [ -13.275968898051493, 31.11706440381708 ], [ -16.340894889096276, 32.843101672458005 ], [ -11.829538912804091, 33.445332744382505 ], [ -8.603301027493963, 33.2087419661265 ], [ -6.624541791170299, 33.76795653291356 ], [ -5.332462830317487, 42.38788138446358 ], [ -2.371284845702893, 47.6951092099621 ], [ 4.5859427131632735, 46.022083921460315 ], [ 6.726772495035079, 41.2127135185 ], [ 6.936411453416965, 33.988416121743036 ], [ 7.855889250730349, 31.310638676935618 ], [ 13.163050572065663, 31.229982729802863 ], [ 16.93774889787865, 28.342499822450232 ], [ 12.727508457548831, 27.923088897359893 ], [ 10.08199339159442, 26.01960854502688 ], [ 13.679248633715295, 19.42195206956744 ], [ 17.421684580675134, 11.017602378334363 ], [ 18.260506430855763, 2.0002674888923293 ], [ 12.195179206472568, -1.4195446695364975 ], [ 5.1619806164963355, -1.9357427311861257 ], [ 0, 0 ] ] }, { "type": "line", "version": 1773, "versionNonce": 2065358086, "isDeleted": false, "id": "C3Omn9xJWTrLbK8n6N_jK", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "angle": 0, "x": 1070.320634413427, "y": 501.0634063433905, "strokeColor": "#000000", "backgroundColor": "transparent", "width": 10.592814390101838, "height": 32.369920115945696, "seed": 726808161, "groupIds": [ "vYban4X0aguFVaH8w8Miz" ], "roundness": { "type": 2 }, "boundElements": [], "updated": 1720098904359, "link": null, "locked": false, "startBinding": null, "endBinding": null, "lastCommittedPoint": null, "startArrowhead": null, "endArrowhead": null, "points": [ [ 0, 0 ], [ -3.360664463864801, 3.4682057267084665 ], [ -5.027554037941746, 9.759369603063378 ], [ -4.301650513746954, 16.991519529300426 ], [ -5.269521879339978, 21.21251409591456 ], [ -4.220994566614168, 24.546293244068494 ], [ -1.6937748897878335, 26.26695344956722 ], [ 1.5862336269442, 27.664989866535066 ], [ 4.40919177659063, 28.57909060070622 ], [ 5.32329251076186, 30.89122775184531 ], [ 3.8983707780831756, 32.369920115945696 ] ] }, { "type": "line", "version": 1210, "versionNonce": 2119294554, "isDeleted": false, "id": "z6MAGVIyJ7vOZ5ClZHYti", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "angle": 0, "x": 1081.3542069546897, "y": 500.72904099940763, "strokeColor": "#000000", "backgroundColor": "transparent", "width": 9.84002555019615, "height": 25.89055902961443, "seed": 316382255, "groupIds": [ "vYban4X0aguFVaH8w8Miz" ], "roundness": { "type": 2 }, "boundElements": [], "updated": 1720098904359, "link": null, "locked": false, "startBinding": null, "endBinding": null, "lastCommittedPoint": null, "startArrowhead": null, "endArrowhead": null, "points": [ [ 0, 0 ], [ 2.7423022025136805, 0.5645916299292854 ], [ 6.936411453416969, 3.871485462372254 ], [ 8.872154184603094, 10.727240968656446 ], [ 8.388218501806552, 17.260372686409646 ], [ 9.84002555019615, 22.261041408640445 ], [ 8.710842290337574, 25.89055902961443 ] ] }, { "type": "line", "version": 1910, "versionNonce": 788876358, "isDeleted": false, "id": "MLB5vblkstzKyIhuqAmSQ", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "angle": 0, "x": 1088.8689502080197, "y": 532.6024639754256, "strokeColor": "#000000", "backgroundColor": "transparent", "width": 7.355822378507269, "height": 22.228779029787354, "seed": 1331610177, "groupIds": [ "vYban4X0aguFVaH8w8Miz" ], "roundness": { "type": 2 }, "boundElements": [], "updated": 1720098904359, "link": null, "locked": false, "startBinding": null, "endBinding": null, "lastCommittedPoint": null, "startArrowhead": null, "endArrowhead": null, "points": [ [ 0, 0 ], [ 0.16668895740769388, -2.952007665058909 ], [ 1.3684625696857604, -4.210240440329895 ], [ 2.8928599704948215, -4.855488017391895 ], [ 1.0700355652945566, -6.1298519820895185 ], [ -0.43016505137471023, -8.791498237470364 ], [ -2.0271528046032645, -12.453278237297498 ], [ -4.462962408012448, -18.389555946268214 ], [ -3.4144350952866436, -22.01907356724235 ], [ 1.1345603230007582, -22.228779029787354 ] ] }, { "type": "line", "version": 1370, "versionNonce": 670630682, "isDeleted": false, "id": "7EZ_XWkfLNl3dU3SNds0Z", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "angle": 0, "x": 1065.4553923837798, "y": 513.4703775296847, "strokeColor": "#000000", "backgroundColor": "transparent", "width": 7.823626871877253, "height": 16.61512510934757, "seed": 1821785679, "groupIds": [ "vYban4X0aguFVaH8w8Miz" ], "roundness": { "type": 2 }, "boundElements": [], "updated": 1720098904359, "link": null, "locked": false, "startBinding": null, "endBinding": null, "lastCommittedPoint": null, "startArrowhead": null, "endArrowhead": null, "points": [ [ 0, 0 ], [ 2.634760939670006, -1.344265785545911 ], [ 5.968540087823887, -1.6937748897878564 ], [ 7.823626871877253, 1.0485273127258228 ], [ 7.823626871877253, 6.452475770620425 ], [ 6.694443612018691, 10.485273127258173 ], [ 6.049196034956644, 14.921350219559713 ] ] }, { "type": "line", "version": 1449, "versionNonce": 845140870, "isDeleted": false, "id": "G-joXf1qcZjd36l8hJblU", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "angle": 6.251548691252406, "x": 1087.0245898706685, "y": 511.4210578453532, "strokeColor": "#000000", "backgroundColor": "transparent", "width": 2.5511342048730024, "height": 1.5915782724481118, "seed": 2108609057, "groupIds": [ "vYban4X0aguFVaH8w8Miz" ], "roundness": { "type": 2 }, "boundElements": [], "updated": 1720098904359, "link": null, "locked": false, "startBinding": null, "endBinding": null, "lastCommittedPoint": null, "startArrowhead": null, "endArrowhead": null, "points": [ [ 0, 0 ], [ -1.293532836273635, 0.6764207657904466 ], [ 0.07186293534853483, 1.5915782724481118 ], [ 1.257601368599367, 0.278526197678419 ], [ 0, 0 ] ] }, { "type": "line", "version": 1575, "versionNonce": 48074714, "isDeleted": false, "id": "zWm8po_-2qNK3l1ppiMfv", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", "roughness": 1, "opacity": 100, "angle": 0.25834385002361504, "x": 1070.3100674637444, "y": 512.812849345904, "strokeColor": "#000000", "backgroundColor": "transparent", "width": 2.710159067921797, "height": 1.4966154553657987, "seed": 385326191, "groupIds": [ "vYban4X0aguFVaH8w8Miz" ], "roundness": { "type": 2 }, "boundElements": [], "updated": 1720098904360, "link": null, "locked": false, "startBinding": null, "endBinding": null, "lastCommittedPoint": null, "startArrowhead": null, "endArrowhead": null, "points": [ [ 0, 0 ], [ -1.325572160124439, 0.6424986103690914 ], [ -0.15876841439743236, 1.4966154553657987 ], [ 0.9711221605739294, 1.2498082249610247 ], [ 1.3845869077973583, 0.04878639282165335 ], [ 0, 0 ] ] }, { "id": "0CyCxpti8c09OHlYJPJw3", "type": "arrow", "x": 619, "y": 596, "width": 211, "height": 83, "angle": 0, "strokeColor": "#d9480f", "backgroundColor": "transparent", "fillStyle": "hachure", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 2, "opacity": 100, "groupIds": [], "roundness": { "type": 2 }, "seed": 2110355718, "version": 137, "versionNonce": 1949229062, "isDeleted": false, "boundElements": null, "updated": 1720098962955, "link": null, "locked": false, "points": [ [ 0, 0 ], [ 211, 83 ] ], "lastCommittedPoint": null, "startBinding": null, "endBinding": null, "startArrowhead": "dot", "endArrowhead": "dot" }, { "id": "KQuLAF_F8FTW9234c2Cq7", "type": "arrow", "x": 621, "y": 572, "width": 416, "height": 53, "angle": 0, "strokeColor": "#d9480f", "backgroundColor": "transparent", "fillStyle": "hachure", "strokeWidth": 2, "strokeStyle": "solid", "roughness": 2, "opacity": 100, "groupIds": [], "roundness": { "type": 2 }, "seed": 406711002, "version": 70, "versionNonce": 705342470, "isDeleted": false, "boundElements": null, "updated": 1720098946225, "link": null, "locked": false, "points": [ [ 0, 0 ], [ 416, -53 ] ], "lastCommittedPoint": null, "startBinding": null, "endBinding": null, "startArrowhead": "dot", "endArrowhead": "dot" }, { "id": "-HyL0YvulYuk-1P6UdgMT", "type": "arrow", "x": 649, "y": 502, "width": 133, "height": 165, "angle": 0, "strokeColor": "#000000", "backgroundColor": "transparent", "fillStyle": "hachure", "strokeWidth": 1, "strokeStyle": "dashed", "roughness": 1, "opacity": 100, "groupIds": [], "roundness": { "type": 2 }, "seed": 856940442, "version": 87, "versionNonce": 905488922, "isDeleted": false, "boundElements": null, "updated": 1720099023482, "link": null, "locked": false, "points": [ [ 0, 0 ], [ 133, -165 ] ], "lastCommittedPoint": null, "startBinding": { "elementId": "33n0KZdNGe0UNtvueN02w", "focus": 0.1435421002034098, "gap": 1.0195365511011119 }, "endBinding": { "elementId": "Av4wwxqsXOBSdUclyt8vH", "focus": -0.10209137529495528, "gap": 17.572867652665494 }, "startArrowhead": "triangle", "endArrowhead": "triangle" }, { "id": "x8NQOCbggxBOgiGfPAGrz", "type": "arrow", "x": 913, "y": 603, "width": 84, "height": 265, "angle": 0, "strokeColor": "#495057", "backgroundColor": "transparent", "fillStyle": "hachure", "strokeWidth": 1, "strokeStyle": "dashed", "roughness": 1, "opacity": 100, "groupIds": [], "roundness": { "type": 2 }, "seed": 542136026, "version": 120, "versionNonce": 1087492250, "isDeleted": false, "boundElements": null, "updated": 1720099066749, "link": null, "locked": false, "points": [ [ 0, 0 ], [ -84, -265 ] ], "lastCommittedPoint": null, "startBinding": { "elementId": "8hfFXr989zqacigJBRPtu", "focus": -0.20237690334096833, "gap": 3.758551226192651 }, "endBinding": { "elementId": "Av4wwxqsXOBSdUclyt8vH", "focus": 0.26119847643409566, "gap": 18.572867652665494 }, "startArrowhead": "triangle", "endArrowhead": "triangle" }, { "id": "WpY5Wxo4z6t65SWdYiMcZ", "type": "arrow", "x": 871, "y": 328, "width": 238, "height": 128, "angle": 0, "strokeColor": "#495057", "backgroundColor": "transparent", "fillStyle": "hachure", "strokeWidth": 1, "strokeStyle": "dashed", "roughness": 1, "opacity": 100, "groupIds": [], "roundness": { "type": 2 }, "seed": 1410665562, "version": 67, "versionNonce": 721532934, "isDeleted": false, "boundElements": null, "updated": 1720099077115, "link": null, "locked": false, "points": [ [ 0, 0 ], [ 238, 128 ] ], "lastCommittedPoint": null, "startBinding": { "elementId": "Av4wwxqsXOBSdUclyt8vH", "focus": 0.40372422411816844, "gap": 8.933809031509327 }, "endBinding": { "elementId": "fWl2gLooxQ-An0MuBTirV", "focus": -0.36931794468861123, "gap": 5.238778130417359 }, "startArrowhead": "triangle", "endArrowhead": "triangle" } ], "appState": { "gridSize": null, "viewBackgroundColor": "#ffffff" }, "files": { "2eb8257a06841d380d8ba6c51d37f72fd3a34bfa": { "mimeType": "image/png", "id": "2eb8257a06841d380d8ba6c51d37f72fd3a34bfa", "dataURL": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARgAAAEYCAYAAACHjumMAAAABHNCSVQICAgIfAhkiAAAFhNJREFUeF7tnQvQblVZx48FpImQiFJhCYx4gSAkNbQEUUnGsouClaZkZWM5mtplnC4yjTYyjlaT06TZhSLCpjFFhxC7SN4qSyJEaFQ4iFmaclGjUMj6/+d8TB/nnPf9nme/a++99lq/NfPM+33vXtffWvv/rr32uuzahYMABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgEBVBO5RVW7IzJQEDlViR2/ZN+rza7fsCH3eV3bIlh2szwO37AB92t2xzb6gvz+/ZTfp89OyT23ZDfrcLfPnf8lwnRFAYNqv8HupiCfJTpYdJzt+6/P+Exb9f5XWJ2XXyD68ZR/U59WyOyfMB0lNTACBmRj4BMkdqTRO3bJT9GlBcQ+kRvffytSVsvfL3i17j+yWGjNKnoYRQGCGcasp1FcrM6fLzpQ9WXZsTZlL5uXL8v/Psstk75BZePw4hoMABCYkcD+l9VzZ22XuBfgRpEW7VeW6UPZ0mYUUBwEIjETg3or3WbJLZf5Vb1FQ1pXpNpX5Itl3yu4abB4JNdFCoB8CHkf5PZnf1qy7AXu65jdVr5U9tJ9mQEkhUI6AHweeL/N4RE/CkS2rx2z+Wna27CvL4ScmCLRJ4OtUrFfKPivL3my9+98tZi+R3afNpkGpIDCcgCe6/aas5QHbqQTwZnE8V+bJgjgIdE3ggSr9G2RflE11A/aSzufE9BUyz1jGQaArAoeptK+Wefp8Lzf8XOX04+ZPy+7ZVQujsF0S8OvVF8ncjZ/rhus13d1iflaXrY5Cd0HgiSql1+H0eoPXUu7LVQcndNHiKGQXBLyo8AKEpSph/ZLq4zyZF4LiILBYAuco57xyrrfXdr3qxz1LHAQWRcB7q7xNVstjAflYXReerOcpAl6KgYNA9QS8MI9ey+obulax+5jq7Vurb11ksFsCfp5/vazWG4h87Vw3Hpt5mewrum3FFLxKAl509yHEpRlxfafq8vAqWxqZ6o7A01Rizxqlh9AWg4+rTh/VXWumwNUQ8G6AvyLzICHi0iaD21W33tgLB4FJCXg7hTfLEJY+GHhZB+Myk95i/SbmLRW8Iz7i0heDt6rO2baz3/t+kpJ7MHc34tKtuHoTcu+HjINAcQKPVoyfkdFz6ZvBtWoD3r8HB4FiBE5VTD69EHGBgdvADbIHF2tdRNQ1gTNUeu9oj7jAYHsb8EmVD+v6zqDwGxOwuLCNJcKy6sfFZ3AjMmtuM0523D8cbwz1WJnPIOLNwf4Z8e0eAu7JnCa7DiAQiBDwfAcf8MWYCz2XVT2Xvb+/Qe3l5ZHGhR8IvE4I9m5A/A+TSBvwWVY4CKwk8POIC+K6QRu4U2G9Ng0HgX0IPHODhhX5dcNPH70gnxTBvjJbtxeDvHtAPEYfPoKUoy320V2+GEDAb5csMjcOCNtUEARmz6zMD6hWj2iqZinM3ASuUgb8JtJzqLp1vR8Y/lWq+ctkD+m2BVDwsQj4B+sYmVfdd+t6Fxgf2/pd3dY+BR+bwDcpAU93+LuxEyL++gh4IyEGXmEwdhu4Q+3Mj0pdul7HYLztgvd04aiKLpv95IX2YO9JslsmT3nmBHvcpcvjLm9CXGZueX0l7+0d3thXkfeUtscxmFep6D67CAeBKQkcp8S8kfiVUyY6d1q9PSJ9m4D/jaxHYZ27rZH+ntMnThSIbubH9PSI5FXR5yMu3OczEjhUaf/ujOlPnnRPv+Q+YuSpkxMmQQjcnYDnxrgH08WjUi+PSB7B/weZ93nBQWBuAjcpAw+XeY/npl0PPRg/Bl4s+4ama5LCLYmAH9d9/M1blpTpIXntYQzGE+o4AnRI6yDMmASepcj90qFp1/ojkgfVPiJ7QNO1SOGWSuAKZdw/fj5+uEnXeg/ml1RriEuTTbeJQp2sUriH3axruQfzINXav8jY46XZ5ttEwbxp+LEyn17RnGu5B/M41Rbi0lyTba5AX68SedV1k67VHsxBqi33Xo5ustYoVGsEvJviE1srlMvTag/mRxGXFptrs2V6gkp2eoula7EH497LR2UcUN5ii223TJe3KDIt9mCeg7i0exc2XLLHq2yntla+FnswFk3PknQP5sEyT8k+Xub5Bv4eB4G5CVyvDHjDs2u2zP/fKPPSAe+w14xrUWDWVY6XC3j2pAfUniQ7ap1nrkGgEIEPK56/lHkw9/2yzxaKt/poehOYvSvkBH3xvbKzZf4bB4ESBNwLeZ/MJwp4HdzuEpEuMY7eBWZ7nX2z/jlnyw5bYmWS59kJfEI5+B3ZBbJuRWV7LSAw+7bJe+mrH5SdIvOAsffwxUFgFYFbdeH1sr+XvV32P6s88j0E9ibgWZavkfm84bGPtyD+ZTH2gOzLZIfs3Wj4HwJZAhaa35L5jBuEoG8GX1Ab+GUZwpK9i/C/IwGvGXkXItOtyF6ouj9yx1aCBwhsSOCHFN6vGunN9MHgY6rr0zdsMwSHQIqA95n5M0SmaZH9sur312Xe4hIHgVkI/LhSvU1Gb6YtBp9SnZ45S4siUQjsRcCT9LzAEpFpg8F7VZcsK+E2r4rA1yg3lyIyixdZvy08sKqWRWYgsEXA5y550hU9meUx8AS5l9KSIbAEAt5sHJFZDoMvqr6+fwkNizxC4C4CL9IffguB0NTNwLO0n0KzhcASCbwQgalaYG9X/fCmaMQ7a+lHx3rMw72EWt0HlDEfR3FGrRnsOF93quzPkF1SOYPa2/hafLWvpj5CuX+k7DiZd6fzhlGe4OYTG33tPjILjOeh+EDxf5ftlvlEgatkXuHq+Qxzu19TBl48dyZI/24Enqf/vLXC3O4YZcC7LZ4oe4jsKJnb9v1kXtnve/RmmRdXfk7m9uzd73xiqTey8s54/h4XIHBv+Xm67I2y62Qlxi9cEa+TPVnmX4M5nLfx9OZDJcpDHJtzfMUcjWArTbdxb3B2vsyHrm1an3775R9T/4i5p8wrdkHY7qzQvvnfJBt7WwSvH/oN2Ry717lhuSFs2qAIvxnDt6oO5ui5n6Z0/0j2nyO3AffkPZfHvaKunTdy+gmZexhz3DR/pXS/Y+IacHf4lpnKOwfj2tL0o7Mfq6dyHuP0wtgrZ6pzDxH4iaDF00NW1qGh+/m3RPewRAN+j/Li3eumcmcpoRL5Jo4cRw+2e1vUqdz3KaFrK6nrq5WPp05V8DnT+XYlXuNjggeK/0B2+ERwfruShteTSL1woro9Vun4BIEa2b5T+XroRBwmTcbL3T3Q6gGpGsHflaf/UP6eNgEZd9N3V86i5nrK5s3HhYw97uL4XyIbexwxW/a9/bsn97OyZh6bvAucX6ftXdCa//d6Ip+fNKbzuUw1M2glbx5UPXrMilTc3iT+HQurT49BLn7FuMcbxh41H+tG+JLy/vyRG+YfL6xRjsV6zHi9MfdYzr2A1y64Dj0O+uix4Iwdr7thLazFefWIoLyZ+OcX3EDHFIYScfut0UEj1d89Fe9bGqg7P9L58MFFufMaAL+9gXtQdqxneFZej/eo+D0j3TWe0+RHjBIiWEMcXjZxzkisikf7qobAb698j8uM4dxY/61RZnPePN6Vbgznnkutb4k24e0XMD5osGrnx6JNCll72FeORP+nGuc2R70+YYS68phLC49Fq+rD535VO1/GMwZrfw29Cmzm+x8eoeH6Nf6nEZliP07vG6GOHKVP+cy0lSX69cFyJ43Eb3C0D1dIZ2yJQLN59jyCkweTWh3wFzrhl+U9xP93r8Y8+MoPdFQ/N6ishw0mVTign0k9FXlIQ1hqGJ8gcHBhjvdXfBavpTKpJd/XiWHpSWReQ9bb276LC7fvwdF5mXgtjWvKfLxhMLHVAc/vlGXJevuZ1XgHXbFYecC4ZB6XEtePDSJWMJAXCfYw7rK/BuE5PqcVZOmoHN/+0uK7GBdPjvSGZCXdCzquE6/8n222r5X9io7h+6b/kKzktqOea+MuPoIyjEHpbr0fW3vfXsN72Ax2mzyrnqNUHzE45TYCep1VyW6kheVP2kAzSykuKpzquYrPB+r17J6pwk++eZW35dst45d2165PiIM3zyrlHgnXQe3qdnEruZnUgxSfz0uije/addnQxj20B/NsJXjU0EQbC/dAlce9uVLuHxWRRQuXI+AtGTxVopTzYPFY65hK5XGqeLzz46BFkUMFhh3y71613gek5Fqlv5iq5TSUjjdTKuX8WPTcUpE1Es9Lh5RjiMA8TgmdMCSxhsM8TGU7rWD5vNYFlyNQUpS9JsdrxHD/T8AbsaXf0A0RmB+B+n4JlPzFe/d+U+DLVQRu0gVvalbKlazLUnmaOx6Pu3poZFTnZ9JbZQx87cvAh1+VHOz1OAycYwwuKdjqvWct3PfPwKcUpFy2B3O6Yj80lUI/ng9RUc2nlEtXZqmEFxiPj+gt5cbaQ6ZU/uaMxwO9R2YykBUYH5CGW03gzNWX0lc8iQ8XI+C1cKVcyToslaea4klpQFZgHl9TSSvMS8mB3pJjChWiKpqlUgLjIYDHFM1Ze5GlNCAjMB5V5+3R+gZjPqVWWX9kfVJc3SLgtXBeXlHCeWa6dwfArSbw2NWX9r2SERjfPAfsGwXfbCPgdUmlRPjjkA0R8Haj3lO2hKtuo6UShSocxzGKz+ONIZcRmONDMeLJ65NKOL+V8h4kuPUEblx/OXWVNr4zLk8oPW5nb3t8ZATGyoXbmcDRO3sJ+/hM2Ge/Hn0aZylXsu5K5anGeMJakBEYr7nB7UygJCdPIMOtJ3Dz+supqyXrLpXwwjyHOWUE5vCFQZgruyU5+TEJt56AJ36WciXrrlSeaownzCkjMEywi1V1eAAsEJ23C8CtJ+Bd7Eq5knVXKk81xhPWgozAsHQ9VtUllwsgMDszLykwJetu55wv10dYCzIC49Fj3M4ESr7K9xwP3HoC3hu5lCu5/WmpPNUYT1gLMgJT8peiRmjkCQIQiBEI96wzAsOcjBh8fEGgdQLhnQMzAsOcjNabDeWDQIxAWAsyAvPJWNr4ggAEGicQ1oKMwFzfODSKBwEIxAiEF5dmBOaaWNr4ggAEGicQ1oKMwFwpaLw2bbzlUDwI7EDAvZfwDPOMwNymiNllbQf6XIZA4wT+NlO+jMA43sszkeMXAhBojkBKA7IC847mcFEgCEAgSsCnLaSOkc0KzLuUQPj5K5pr/EEAAosg4GON/zWT06zAeLnAmzMJ4BcCEGiGwEXZkmQFxvH/fjYR/EMAAosn4M7FhdlSDBGY9yqRq7IJ4R8CEFg0AT+5pLcnHSIwpvSri0ZF5iEAgSyBQff8UIFxV4mlA9kqwj8ElkngUmXbA7xpN1RgfA7NuenUCAABCCyNgDf0+sWhmR4qME7PvZiSh44PLQPhIACB8QhcoKivGBr9JgLjSTc/KWN90lD6hINA3QRuUfZ+bpMsbiIwTveDstdskgHCQgAC1RJ4sXKWfnO0vTSbCozjernsn6pFRMYgAIEhBP5Ugf5wSMDtYUoIjCfgPEPGEoJNa4PwEKiDwEeVjeeVyEr4+IFAYk+Rn7fJej/6wfvmPCLAK+LlAfJ0cMRjx358dGyp0x3vUFwlj51ZYrV4c/9TZNfWmHkP+nrwt2fjcbHGlhnLkwWm57br40ieFEM1ny+/M++5khCY+drepin3LDAu+1mbApwqfM8ig8BM1crKp9OrwLjncnZ5nOPG6Mclz/jtrTeDwIzbrsaMvUeB8ZjLmWNCHTNuD/x6sk5PIoPAjNmixo27N4HxesITx0Ra4jX1uvz9uS4+SsZNt44S1yAwPYFLtu7NJrZeOUiFOU/Wwy8EYjr9zVIqxR7ap8+VfkEpYLXF8y3KkBdItvzIhMDU1uri+WldYC4WiqPiOJbp05P7ni3z81+LQoPALLNdOtetCozXDJ6x3GoZlvMDFczLDFoTGQRmWHuoIVSLAnO1wJacsZ+qp7EHeddl5q7KXOeHaxCAwGYEZr3P5hSYzbARGgIQqJ4AAlN9FZFBCCyXAAKz3Loj5xCongACU30VkUEILJcAArPcuiPnEKieAAJTfRWRQQgsl0Dvu3eNUXP3VaTPGSNi4hydwGzzRUYv2UwJzA3U+1B4nRIOAhAYh0DJLVzTOeQRKY2MABCAQJQAAhMlhT8IQCBNAIFJIyMABCAQJYDAREnhDwIQSBNAYNLICAABCEQJIDBRUviDAATSBBCYNDICQAACUQIITJQU/iAAgTQBBCaNjAAQgECUAAITJYU/CEAgTQCBSSMjAAQgECWAwERJ4Q8CEEgTQGDSyAgAAQhECSAwUVL4gwAE0gQQmDQyAkAAAlECCEyUFP4gAIE0AQQmjYwAEIBAlAACEyWFPwhAIE0AgUkjIwAEIBAlgMBESeEPAhBIE0Bg0sgIAAEIRAkgMFFS+IMABNIEEJg0MgJAAAJRAghMlBT+IACBNAEEJo2MABCAQJQAAhMlhT8IQCBNAIFJIyMABCAQJYDAREnhDwIQSBNAYNLICAABCEQJIDBRUviDAATSBBCYNDICQAACUQIITJQU/iAAgTQBBCaNjAAQgECUAAITJYU/CEAgTQCBSSMjAAQgECWAwERJ4Q8CEEgTQGDSyAgAAQhECSAwUVL4gwAE0gQQmDQyAkAAAlECCEyUFP4gAIE0AQQmjYwAEIBAlAACEyWFPwhAIE0AgUkjIwAEIBAlgMBESeEPAhBIE0Bg0sgIAAEIRAkgMFFS+IMABNIEEJg0MgJAAAJRAghMlBT+IACBNAEEJo2MABCAQJQAAhMlhT8IQCBNAIFJIyMABCAQJYDAREnhDwIQSBNAYNLICAABCEQJIDBRUviDAATSBBCYNDICQAACUQIITJQU/iAAgTQBBCaNjAAQgECUAAITJYU/CEAgTQCBSSMjAAQgECWAwERJ4Q8CEEgTQGDSyAgAAQhECSAwUVL4gwAE0gQQmDQyAkAAAlECCEyUFP4gAIE0AQQmjYwAEIBAlAACEyWFPwhAIE0AgUkjIwAEIBAlgMBESeEPAhBIE0Bg0sgIAAEIRAkgMFFS+IMABNIEEJg0MgJAAAJRAghMlBT+IACBNAEEJo2MABCAQJQAAhMlhT8IQCBNAIFJIyMABCAQJYDAREnhDwIQSBNAYNLICAABCEQJIDBRUviDAATSBBCYNDICQAACUQIITJQU/iAAgTQBBCaNjAAQgECUAAITJYU/CEAgTQCBSSMjAAQgECWAwERJ4Q8CEEgTQGDSyAgAAQhECSAwUVL4gwAE0gQQmDQyAkAAAlECCEyUFP4gAIE0AQQmjYwAEIBAlAACEyWFPwhAIE3ggHSIsgFOVHT3KBslsUEAAtsI3A4NCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgMD/AR+SS6MQSXqlAAAAAElFTkSuQmCC", "created": 1720098657557, "lastRetrieved": 1720098657557 } } }check_pgbackrest-REL2_4/docs/img/shared-storage.png000066400000000000000000002643031464170754600224350ustar00rootroot00000000000000PNG  IHDR IDATx^|UƟE%ʞ*( "T s EDDAETeU~6I&i49߯$s9+Ch$@$@$@$@$@^͡HHHH4 8EÑs@    P~HHHHN"9 E(?$@$@$@$@'@pHHHH"    u8rH$@$@$@$@ P:9$   gHHHH(B6l+V -[DQzuTV K\) @6P ؍@ǎfhbT4h:஻ÎIHEgBnG`ɒ%;v,N<4ddd%J`9r$"""܎D$@$E" y&J,|gpN>;v`HՙW_}Gv:PB k|H<En=N%… k7 ̙NԊDJlh???+dS  P~HB@Z:u9ł۷gϞ8~ /?!sv___̓?k14   PZΊ-IA`ƍh׮փ͆ ѴiS:tHKT믿pwctDHHHrbK |Xr%:wϱ3gj15ņȔ)QWviO+ 4 ;u]H]63?PlY\vM+sw^MEX4R^<7ozJДykHS* GyNW z!-kܹx'3|ZVn6j(L< ?Q\ uIEHd Z1.K^ܨ$JwRڵ!+gbŊmL7?>|X{ݔ՛2/joW\~!ڴiÇ_ޓZ5Wj>xwP|lX"DԊkRRRd.I[ocW^^{SNň#b$@$`o&I4ՔxE2ehl$RhܺuΪV 6B 曙uCˑRSS5O5~Z;&7e>,f̘%cI%}﫾xx q~wq;Gv"jubK.J9~[^z<&BSؾ}ТE u"%{nݺ4 uDH YfMx$SnٲEkls|J;GnS{}[~=̗zMI|I%K\ʑP&qVDO*&IQ'N@HHH*Gr.6n8HXAN&R_L*^cj S:|Im:PȊ : 8 Pg ΃Ҽ:e|9vs$@$`/"~I Y['NMM@ϥ*9{%D)l/@Ż'BN<ě;v,}o~?d]˪3)bk"VLԩy$ Ajb?۷owK$ ]?R©]v|ٕ}1IEl@H@b-%yH<"Ć FibJDxJI$9:pѱ~J5ӕg2f'є2M:&c>%1H ݋HZQ}xn@]~XPAٵkWDt AӧOꛌ/Qůx:{LiJ)b[&[1)z/a})$-a䎟9HP~q$27o?#_sI2L6gMO3InݺS/v/""E܊qL Bi#bXD=ܣ%ٳG">uQ"z )IrgxfELH;WyI{пڡR Ta+%3#x s#J(Yo8>L$@ XO@-[ڃ$+QE<_ICSEI^Jbq!x]9 >>oI'|< z1GĶmۆ{"bxw/ON&ZCD޽[2GRP|K)SD*&Bd W_$ٺu+""",F$@$`k&H4ѳh"L<;v0 "IJo$=7[d۰aڵkgxLȽd]wݥ]*^Z@8ՙ#GbAvըKr㓈_U +O'%UKI}JS9ڷ֬YYbJXB~PX\t N kӄtր h$@$P(B :$"e3*LnVΙ3'{ׯ_ŁI'x,5￙;h: |ɼxOʽk׮EVp%M0wVD@'\:c mI&i]tULĦa!-_& *'Zg 60Izi&?ժU3#--M"^Ť?ziOaI @A- = Pړ.&0I`ٲe՝b"u7#7Ϣ.GuPF8,/B-IfxHДp}嗵!D7Ҝu ˗/jJRSpp#r-G111Kȑ# ^f2epuY\]*!&SM0A{O>Z;ͭŊ5Av ؋EȲ_3x`->x̙/bKLil"%^L<Էg}V__)nROKĎ?~\+ok|:t@JJ>}ZWW4$LWϒ.sO2O]]U(edn&$Krkɓ'5+NzfH_$_BـLِ@P "oN2L"u8_i5*uYr$.&GϺ?6h"W|[(7b)Gb^3ei߿?VZ-CW=zD%ꫯ74Ѻy\%T'WxGz!,^X;_p{ҥ?U1hxYꛈL|xďꄽĤ 1rd/}1MyV. HH(Bm ݑЏԭ]wՠ!$R^?F 넠)nRI.qHF>u1ҿ[3>n8-k]jbR_i }xx-! \* #.&-Ze]+*SD(H\E+N$I &ݻW{M%9F$@L"ԕws'p[Ø1cےmڭQ4 pe{; [|2]#VZiA}||z\ u= I\@FF**T9sPD ^N$@P@$@$@$@$pGIHHH(B    p8P#$@8)8p9.nf fb $#5]Ta A~|kR /oA3yCg迮=tGNowcd.鿩[qz[)=57P %C}P/"QJz\/ʄF4 p#n\ "d%q2*(=!G4q7{\7 DךkM7lK$@NC"i!!PWSWS纴MmӳsRKڪRo@zLs8+ cL 8x{ u6 )p m N`%Bq_@Ll{-&$@H"shxvuu&ٺf4/okJf6xtu/3T1T .jSƑuӗn2+㥩/i%ZGVm.~̛o7@w$@"@(<1~4!9RUuKaɁD#1ʣAw5SYV,%ZW/$*#$*VS/]aq]$@.J"E7&g'{Ul{ IDAT;,t4.e3T\WBtg_w+cO;T/ XM"jd|HcJTfYn!F\}rn371|?oTA(˼ɢ.I&p?\ (ѫ`9U@lps DqE;'d鐀حI6H3q.BKK`WhԜ@5^)ja ؎EX'xmSuߦcN򷅦dmB]\>!$ʨ2N[p8 XE.~(HlIUTzE(R &xx )7'XZIʼ TꭞK,U$ @A's\p3lç[oXUŧ1/幅/e 4K |PPIE E.Fܫ8xYWEI|9/wÎYph,?Kmֳ4D|o5G)H Ps$~qWmcH@ P2*%BԊ̅9pdI+:cS -Pdo$|r F"TR1qPB"% 2b|[ ៿t_.3~HW)?|P=ubE EhA }&_lM7Gq|`ʆK}m1 {{Zڷ< iuGE/yo/̸w="qoy{.} @(B  JXk =5Y(5)d9YډˢXX.'<ǁ]Ϫd%{EC]9iS:QBZ  Pڊ$!'PË9s.=Lܷd[ m%k3۾J_JAobUYϠT%D-fU?r 2yg %=kt`z% P(#O%PC W{Pۍ Enbֿ޺kE#u߻^0*lP2me9y@uT7OCæ(3KyfJ F(&%TH EhAP$MbBsx,釥}v3;D%K\grT]]rJB2/FBTM)Lr;V?y#%BR-; мqS$@Fj}|j0obT]51FyT7ĺJC!*L-gEUC*Ѭh[G$?ǧInheVww|fzbpPu3mƢOHka:$RKijˮ۝*RgqkEǵ (a(DSWYb72%Gs Ђ"qIYozhTI1zkS,GɊbVZMCyX >*\MfgbP|%Kya1ɪfc .  cجtVE4Z;G̀,[n(iWVW ̜Ak."sˠ(fn(BԘuzդ|*/71ݲJ˺Ay[i$@$PP#B j5H$+EU\ٍ4i6 d)PKZ:hiBoP7PWT5QJ).' <3:>H$`LΔHP^:ͬ<= 5>OKv("QTGPCh_8 Ωjx"Mh[mHH.(B킕ga'4[L}EUGBB4Noe\/e-T<^ƁTy&ÚVύ @>P%0$ W^՗y1-kD:DhN]}*NKƂ%J++%}ī3 6QDkVIzUq:AX'FX4 (H$}MnF U9jd&'Yo=0kD6V,%7I>:z)K% bI%_gw +bh6%  FvB$#Pw%ܔTy+ͤݮw^MZ٩-jvJBPK+kZ0%&9ץbR3@}U tYU*釓S{u6Bi$@$P(B >&7$+8zvlМ }]1رtƪ"KTm̈O.Qߺ/O8ʇez61rc@L @ P#0M`JNZd,2ݍ):xCGu'mPїFcmQ h 'THh!8Lm")Pdg$@ە9mƪP QkD,jEQHD?^.K' Ǔ P[֦?6H|YJkBV%!@Ur *TϻvOD$j(B]m8_p]^5ƶdTM_*V_T }skѬiLh9Z76*YMY9u?4MEp_5ƃ`6 ;P#$ylԭ8A ёQWqhk9n]mz8+t%]ڧ(T&Q{rZY.^m 0r5WSIbYUУJw:|HlB"& >t2!{~z"7z=7o no!:u4.@WJ+=\dŗW4+ su},H4T"4:I /󩝶eo1T̺|$<>_ ܖ(7YxՏxtdc:UMOc[shQBMJY齎y" [5QG$xzq6H2Aôg"ХVPf b8d+Sǩ䢅yINZk/{ П'bOv' aZEpoyƃ$@!@[k RJae/;nnک-RB !J3Ϫd.OĀ%P7Όj uUcK$7yƧH,$(?8qOfqS%\ vp-Y%쾐ml}?g,'!*G)hl[g/ASH4,cx4ojLB 0UC H 8Pg·ܐ@/*>*!(!=9JK/ 'HHP:ێp>$j~t)'4NḙJ``o4.珚P4ł}RħiaR~d;٭1'!L2z/faW:!*`$f6wp6# +^vN$ /†c*.MGi}QKOKL쿩n"&Ѻ5h|5U<^gGnjҼpt-O!p<P3$煂<!(ڤ*oZ}I5tw#pnؼM׻*пiޒ!?P3$@__R7 mdUqФd!K1IF7 t ؒE-i/  |$WDk'٥~L^M>sC50SDsNMT-4]M2O#_'j<7"q;av*nutLPEu&*_Y zI ԙr -KGAجi‡ Ф -sM*~_2qXǎBHS^RꞋY2>2#r, EUؘH ?,?d "6z3~"ٺXy(.ťYս6axJQc6 ޴?ъcOYxm]! 8Es<`kՑPu$o`v:r7C9!Էb=r\oY4*G~ǍDhX7b ?CK*7E# g%@;yC'f;n#Q[پJ ft7woYH+]ĕ$?Q$لH P |MHe6 bXanXMcm /#ަfT8۪H=HE3FnH`*Z?pzpRD:K]S/څo^Ҷ&, )*Ra<& aI UG7t$녽ϗ$lh8`-_?.j#].Uf-⃿(f P -;&ȉ@Š4V YA^cxAuCcTq:5T*4 pvξC !?N&6>΁_B[U t/1v*&3<͏uӄv8*κޮ e$@$GINΩl n8ٳ*Qh0H_ K聄 r8/J2mWh$@$ (B]a8GpC^c67i33GN, @^V3ۚR+?ģIY7 kG8|ՆI&&SH./ 39am 0Ec8s !QJ ¿KsF$@.C"e%#0x*~ԾGuhTIՅS%WImb^Jfu@|&NHHN(Bݒ ':GƉIC]gg[>ɪ)SD,] P§zl6;" {7aO$+/8\d5WۄÐumo&Iniٟ֤>5I `T PW-Εܐ@jtle*Vj|tCdkې( w]2/x!COg}UIb CP:3!ȉS nO󇊗pʐ+4fu4ϵ JcQHv*]_fK-0q6 pK# 1N%gK' CY"1z}pq ͺSm{ڱ1<'_M$`^I PEg{[٦U#}qW_c>kMga \'YӍUm{HƧ u|I Wc\@x zonŜmBs |ߕ"zAUBڧ2mvB$@ @H w"EB ukҊ'r+HHE(?$@$^{`V220 $@$` PK( 0Nw?ZńG=w[  (B-%v$@$vݏw?Xh IDATg5&<ƅW̩ 8;Pg!ΏܘU6hɒ%{ШQ#7^s,?)Ӑ넂ݜ PYiZi' ؚE? \ \vM/ڵk Œ3\ܱ'QrE1yH+d'zڍ3>(z)>|={7|IO>ݬjhNrkmbR3pDݻ龽Hbq)a@ިSgbUv$$@Y[={6yaxQreL>C Ѯ>|&R]>/9ڷ{E]*T(S`Ƨ\kͿoŲqeTTIyF;hb4((1., 8P ΄܆#F`Μ9x;~/_y?WF\j;ƌSsRB:71.w7ߝ'/^ժWrΈRjqPͥ%pα M6i^cǎ᭷qCy$~ի3ZJرC pZMԬ"J ʋ/E{]j-r$ ;Ad.h.YUXB;j㹡]j͜, su},H H{dd$>sxVZڑ{jհtRkÆ cilq iG(ObtL,wgYҼu?VYEzD# k PZKIּ#h))</]FGk5FY? \3 L" 1a;w.~؎8)䘷fڧ/z{=*ES1Ahh!KfP#B$`s ۷o"ƸqRȵlK,HH/X5ʭ=kns=$`Px5 )ϟ?5kh5D]v݇W^{+)dq0K8q+^=YcS?\7 ߕT;F$@6# gDNJ/_^J 7;UV|&2kT"5i$ :}O֢r 15F=a߹F0E"  سgvލ=z _|~RJt"6Б5vʫO9kл3@Łޥ>p_[4nXWZx p5u<> SNe˖ XjJF ̈́3h ,\ڵnݺե A?,Y0V.#EDD {5YByZ3N5FqW8'-PN 8#htƍQNNr={,FӷhNgϝᣐq[JA}}hӢ9~']yFZS>>8#M0Uoh^;aK  VvJC`ڵj*!:©9B~š ;uo"YNq[D"A9 lشw,!|ѽC܉E;&B&wc٣¤^Fw99 ~ZX\ALc#Q( ֗^HBCP$8eKpͣLJCaٲe u)r> $''wgѺe3 <\HH %-IIHT_=5= HS S~9=#M5y_Y{ HQad$d> Rq5. >^ިZ2||g3ԗ_GBJݼqEFR(UX\Ǟ# ̟?F>}0ydyl1ءm/ N/٦Sg)C%O1+qWq%.9f4q#b8T"OD~$zTG%0o"e(1^/?򳒤Dj|WVQ,o%4Vx8MO~!D ""UY _x@KW_EX~^~Z7o&P˨Uj}U\}2+E+NzF-[yX` *D>NL@a۬SQgq.\Ծ.R%OS]‘n jSʑ,Sum$H Q~}A/[Ƅ揤cNLL“_ѩMG_'3JU%*%QJeza(x/G!%DlwNb/jD>JXjELQɩ)Kׄ"!5/|EP{b/Cw 8L"E|\:3J=v⡑ vZ{Qٻи|}%JReN= ' K.ؿڴiS_/((H%Fy! +7vzvۃ+o9u\~XV\47*I:. CB5%6El*3p24N)y9U4QE{ѯcھ9(BIc XwW#]8Y Uwi%<uKֲ*[E1ʹ%i`ˉ?q@Ӥ$-]nqBHHHzR2W㣰xJSSJQIQ,j&dֹ [j1)Z=Ո0 |٥uWG$@$@9HHLѣǴZ ؿbv&FʄDk]]pYyG^1)cjV.IN;*hHb)NvF$@S/+/ O _AJеS/__\'Q+~ZU{P[Dyef"BKMҵ6aXxI?AJvD% BE_gGUnF.V,] ϝE\`*4-Q{Խ=1- jZT (U饶-M5 "4 ؟\+GZԽǮ}_0yԮUcGpp.K@jI\<>g}{$ii߰.zq)]\vgm7qM_`CڵrOc@^"4_0 ؟F`P=/itUr5_ 8b"Zxϵ7CxvRtLVct}~6L-Yj¾9_;CJQ7mWy",pb"LEaݺuh֬td:ƿ<ϡe+ uY!K]8|+hb>?7<qG?x:V)G8S8z8&@R fѾzKǥ ƍO>'OƀFIII3Cy$_@=n>!9%QxUac@bj% #J\qWgPYE5ؖH`ڕ]v̙39TA_n$+!&IJUTT,.ݕ$"u Ɩ-[KVL: ƿ" fTǧߗV!ը8z8d:Vw\qu7< i{D*hMab#pj }(WGJLL“]n>N= G -mV/T:kQZM,l>%{/F^UYMzK.","6 8}4:w?M4a"chaڽw?ʕ)H/p $7O]sԆdZ ve3gviD'8Q9fFOYDl@# ^F# GhY<n2kێqڭLsv]/3fm۶Z*3I탙 1k~Ӻyo7Lh1!Qױ{~4_OeR:mw m8U~PxaxxGZD'"B<*^_","6 {Wr?,A"888{> Ō Eԭ\t oz'kLGиQuG3v"|WxAwT kCh ОINBQ >M.'ߴZh PBk6C4/:׎Hf$1I2OZHNDË5-z1/JWIQ؉ ~˗QR%tR^<>>>y؉쿾/õ@I_g7P+jm :u -[DDDV\e2.Զݦ71e+ZoLxmŅl ~ j(yLJ@X%J3P<(ƴ/4,YVQUE{AȃGNS^EWK Ep!2#}S|8fϞ~;vm:g/nI@>3Řq1vshܾ^)E$Por;l>{ᵊhPcKgnhpmm75FCm,vZZ<n@bUmvboX8 =FW|l3EYDl@!__1rHhً[HII3CGZ5kG[cMhiEn6IWo"L*nuиt=M#م Xu`V\uQX@(NǜEB\h"}-!FDhh]fu)B"b?T\:t̙3NكGgӿĦ-cWy$nEwMm<ƳcK U8T%TCCXc4^ĦPs=\=Aw ?=4zKOAѤ|#L(B"b }VZ NE.Lz}X4j@ln kM4|ZRu7Uѓ4I^e "KTPQ@Epw8ATAAdPMGf'Bm^:^~8=t$!QrvbGYeZth\~(/#3 'O4Fᨸ?Aa7.l 7 L*CH=+KҐYf mxZwp8^ /.:`̨>#`4ONF$1" Q&ɣQ@&2!KP0iCGa/sFNXiggc.j?ps`3a@=4'ip5*Rupɨ6"j_[x_pj"ށ#  kؼy3СC.$:ދ#@24Y0o{} :fDB$Z= ~r/x:x@nf"*!jҠÆ^Hbs,lGF# 2",`Bed.F>`+fQhN $9ͥ+#ʞf-,dr&!4n '0K}t7?$NB-B;p,#~ӧȑ#hDރ#T0t Z0^~X*Kh/u UNvGI}NWw@.'+#+BtupȪYVYVjRx y(Q t2gCi@^IVWCcc#d?Ѡ'qj"ށ#`ŋcԨQ:t(M!=J³eN<ھ-TB i2dX|tI"埘dS8&JRDFsu_yY6XA`Rw-NIEx@>}CDD6l@2@!_#_|?^n.٩x7s,y_ N] ƈ(%ˏלnYF9-ede;dq$"DGhX"R˖-ݻw#44g\`ڈc@=I:j"2/y zRbBE_1FFtƖPIY$};;2wk:{ZwKDڿ?VZD$~; ҕkdJ̛3|}}N>AB jX g?K$4)2'2YF"|Kכ/MϪfeGgbh'^1$"DGh,YgYܧO(W*8PGOn3) p33-9C%ޫIdTvq΢\x^iw@B0Sucpj"ށ#F5h԰>&Nx۶Wcȸ+}s$Xw8EGnt6^j9o?8 pl 撟9wؽYuyl~5ה18d2'\1#ufK4zTl=ײJ0KI6#OeV9GoSvvFj g$<9e"d 忈,] PsT)-RBέ.TDBg㵇v/Z6'!8!`0ѸQC"Pmo|EhL!}-bw{=G𾲮kA[ǫmZ\'!80_w7 m;wC&ˣl`E| Ŕx7|xۂ,jeZy$x߂4FЙZ1*M_z"ژ\}e9rj"ށ#H>Μ9ooo G&Skr78']¤i;: NBv#;IcTJ /%{1̥/;B!zb.K-'!s-}j k8 PسgPo߾;w.\]Y0oG>8t_FFp'(g>[aCF}}~Jkȣz_fihbkegh`B\A$"DCuF$'~_!!!"Ru!9US 1e g4^& $ӞxֽyKvuoQ;h*>-SK ģ#7#KڤTʵSekfuZx1xySsj"vT7vVs8ص-s8Ge+b7g*cѱԱ&) \(ȈgҤb "j "J:4rcj = + =b׮'&.~C\p\Lnqu0D/a0W6/5 ݳflB}!Z.. kgZμSha h¿Ѹ<힬*AVRFB%J3f -ZĔPG0xsDJNLV`٩նdn -+@T!UG*DTӛ=hRO|/H7QL|W~|9b0-$R0 hn,Ī.%l1"Zw07GWK\mfD`݆MXogY@ϟiӦH-YIh{Y6_g#Nl-+X=V%(tĬө8vuH_WFvBYB׏ Aj- =y^748u! 1$#)#k\挌8"8 ΈqF5eM[F,٣@=ȒORJifhtd&'x;!3pTgc>Es!8zVTK(7틹첰6FCj8 Gh[ŋ1j( :T ..Y{hu:(IՕQx܏Fnއ&d MvXK+^τ9Ä-"vCBOes.%M@#-f #٨uEYW=R8/Z5Q)hR M^z ! K,>PA\,R j^-zao{pit ;v?+"kpJ [27΂_+n -x<{2VY^$ч GedG 2bniy[1i l?ًHJoT[a.xj*Q?!` g/g\CVc5wfea dE$W5 DH!mPMxްNհD-[ׯGHHό?<@C`-{j7kpjw."zbWomţƒY:$Cw͟cr'ڌ%4Ceĺ-)ض_IjӑPRuȪ!OgV:X9"RONڴ;ZEztW`QeԋѹHPyFx(MX[Uٝ;w֬Yl򁙏8,3`1 -6~Ŋ_^qdHhۥxYu%L=Hʪ*9MpDb!D{_<7ǨXxe9ۓ糅GTg ". z}~l`9}'ڶ,[ll`>}:  5p5g7S9XR6/|DٌZ QI6(ܢHh%% ~潝pzƝiu8]Tgښ-#1lPZD!mGbIQJzpkEHLL;gT;W8GXuzN&o~R>-山HhEvtrL| !gbtDU DjM:}jWf'2q8)P#iW`VU~#fukp8 lyaᦿOL;æHy (Q J 0~[@NDӲ|G zvPcRq݇3hF!'ec4!+!!xfW 4C?jܝ \^96@LlF{cFDlz|qW"6#z?V-%/l*O&#[G-$ҎG 7x ofQWNr IDATTO@ t&mLѣglr#P|憩_O*UEV .+>:|8 jK$4x B=Ţ0- eZL$H*1 W/yĬzMpefJrؽ{7<==|0ҕkdJ̛]Gcˑ -v~c //$~|R1 2y4ae_ZXs20`Ο?G"<<DŽ)|0{DЋ}ǿ6G!%;iظg 'eoEgS$XòasE` e~vAlԅt>}Ix'@IĒuB&/`ځb͈BxI2AR0xehܨ!> "G`$7J&ݷ;FH$4EVgǝE|FF'0tc>9/ԥ6&Y*O?aڴi9r$K5&?#P`?#3gbՒUik|/%D`%d!% mJ8͖HhD oߟA6HJYɕ:`FqJ=oq*Zf T %/^F\śkITYRӔO@M#0b4ٸᜀ§Gz eÉT~y[Q@"k$cXJao-E ;qCsXG?lޙݒiɶlْY>z"tZF#PQ_^\#6պ4s9G f_c qxswE!`RTspd?L(Dًnػ@K!Bw*aƵX $2a+~.p8,"f"'pGNOSZAWiLz#2ϦO%jH(ՙ0_Lf! ըӜ%7m|pDk/#OP,?u2@FKb&t~]H• {M<=dXK QN{X> Jјaq/s8GwpOwS OS0"JfǑ*<sPF"TI#` K̪) s }xcX(?iY+UDbGXXUq`r `O^jYU8{~Gp9"o?WvWj5 r&F} &jH(KLJHa)3xT-.V/ ^މ!0gq6IAo4 ZhAAINF>_.o+@ `$t3p*~ #0}\ȝ]{]w$岛rȹ¸ेnQ$$kf#Gm]`YX o"n׿F#Gm {:KKh;ܹs1d( 8G#PӆMQ3O#%Oi9"J\xJL:^xhE" ed#i ZdÑz95T}{4r aθx5ÇcA<e ?>if͚{O }|r@@āW`̨ӳ[9u~5^Ξؐї݄[BmYDBod.T~,g,2TF:BVd~GjĜJwky#^FUz/-Q{*Gz>Sk|~$Z7| "{A~зo_ KrVo0LT STaliDB}ގËP۹8v}ػФ"3"3ǀUrjp{z=\#Wu]:eTUڨA2UNIGg{$fu; ~zoޗabbbЩS'++"UR`.+`ɲ7gݫIw/֨ZW!uJ |}"z5|LHOc1%H02!Q^=7u&է [FL )zH~Aho"&x"ip/|lI,[ O?~Hϟ3-wWaKqX7/x[0v/':'xޣv# htf8 U;3H &8'd5An,5)NmxyQWĎ:z )i:0`nw=Zd嚏)`60o<1B8gʔ)?w$DNq8ehě&RbR(&NxS '}=՘DŽǕUjS50$jS/ѱVQEBι]~pW)% GtTkDewx[ArOul•X_2zp+φQ3/,8ŧБ5\),EGG]whsbJƍCll, (F=x& ^$Gg6;vyw;f{ )"%:bB}ş{r@@CϘïCpEB}&sP $rj8k)CP'rM`[FD 7}ƛm#igVoI9vThC3, ,9[]%&U"|w\I,x8`g+HhLMfyH^ݾ3¼0eR'Û[B/ͪsaJ'EpiDPrͣ:ˏ;a*+(24P(p,e5A OF(YK[6@d74k`E .IͻT]q@ڮ?ZŖ+:,:{䒘~NH7rNGjj*}]L:UpG#PBf3:\Y Ӿ6Hh(,Cu%+ʾV+Hfr`$אh@6DЯf^_K]BZx5Rrғ2ILG"%  W}.hEzv }N R/+˺N_FYet5@_"~uܱQWN;HLdLn y9'wj5'o;4p8"HhйKy3^*\:feì4砮_"_θ q,& HVLĴA3TTA*>їupTH@ GA1i+3ެC9$™x-2˷PhHr=p^p/DQfk?!, 1&OZZ59@uG૭?o8f' Ul'(d$Tb(h"z,ēEH0Q\&] "DTIEIKYcLNO:KAZ 9#< NR4$W[= )H̥l\Eѱj$Vn_gXFj;rN$p>pR-Ӑ5M I۝[D-?p8%C˭?O~IXu Z2 Yw!#4r I}ALEB' 9Y$w|#EmQpoʼ!P=R",=TdE4 *<}(UJr (DZB'wr{x!=s_oeyu>`} Wv )K=6AZ$S0ӦiDk8H[Mwd4#24r4r7|iʀb]X|(#pJPܥ܄\x5 J*A 6j|\ĕsEB?vDʖ Kh}ۏa$ߕtxe  YNtDPsdQCCD5+R)2WmDZ$p *N:lʃ&/%KH;ut:NywrweXFٸ$2r՚H-B !y\z/aj=5=7XRƬ\Fٜrxs;U-EX u4uӚIM hKEâNG#`/DBu˻=*k0QlpUZ(DBI|PQ;/E.!N$;G֤&WA &wT-B,#KWDudSʈ!#.,yE7G\Uhy rEvg0̎zrK]GuA0Pk"CL+%㓄iSEgBw,7Q$S.S<KO ^-o5r?_ߓ"m.OPsȐ!ׯm#P%079q0* B5W{C5sg` PMA:\ZU6(y $Mc ^Ʊd}%+FP~8F2˒Yȭ@F# /HR_"d՗T]΀>K3%NPrǟQ#.7ۋFw@&K+ B 7Ɉ';x G#P[a 0cڷT3gSUo^΍_$ m`1f )b:rd@pܓs05y*,yo#ݙRHD1dtp qoy2u耬3ilB @=\eM{=/\WS"^nEܪ+x, …UӨG)j^q+d, S|jv"sThi PYE;' `kкTN> y>0G2Je:^|m ^4 ]#Hh/ PO;#ETD\=ANW)sWqք9*{_B☕Y+!etɽ%x< 2"<2O$ÝD*#^DċM5nelFD\},ʃ ԈQ8=g hDM }KDBX~Pw!0Ih`LpP×\-G`*`E>uE 0iTwFB,&+{- Ak[ nwk!SG{A4ĭ3ux4AW ʢ1׀Co@q(F+]BQlƬi{⑺']Bo9en>+oƹT( r~{E_^$" 6FwMJHwpk5!x~ 'VT&%lZ,^f*bTܓSە%gy1xF,?%yk m:\"wz DN"?ud.{Wj^&$)l,6* w C@PQK3)pndQ$/.WDBs8  .x7pՆ~X]%G350| OGqąӳ[MGt ?V eKVE$Zvc9Clڍ&> C1MI89W3^bOk-LEYR#ҪJ*ZkDs R!sǞ.jݢH۟_U Z-|lmɋ^Guw\=j`˃k⥧8 -S`` `41vDJBuԯ'`~ HhM@,LXM ńַ}g$\>q?h-}ߙ2%8zZC)-McqGoĆu~(8 )pp匒iTReV^(  KKzFS&He 7Z _^շN! xgL>G - 9pq)ZS+FBWjj(1ɻWdyؿ/ iuDhZ"7.EMdq+ڭSvR?%ݝXdyq}K/Z˳ءLCt_5SyiSM#>Q$tDBIh`)W Vɨ$-pl6/y&mxG@/?,J\k7?B`eH6&tq!Ƿv"X!Y7Y~qܧ7 JK#MZ?nB[EBGrANDBd oLw_;]R )>'Ih'q3UUՅ6oUFB|8_!WG&T:Z=3q;)e3 Aэ-.)IN*$Vz$ aEoI } p+zù;^46D ̾G˕HP{Jէ3L@1P'p8"H7 եiQKWg ѣgE;;کwwBEn*Sw [[. -AST&_E"j9cUr8 v ?$R"qVF$tpfy3G#FB=<4yt)DB> u,XWz#Z!%s 1e;#23X%=k .;^Lc:;o $oݭ݆gEG)1VK1'EB_99ae4YB9  v^r֞`ՖNaPRUG#PrX$,OnH(B%DC8]:[Eڢps!PEO&fS:Ycc<'& uY$~֩?=,z^[ȪW)'C6=p"|p5k:cuHhY_g+kS, RN"~*>,G#܋Q(+S6ٍ;>]w>mPgt .*@]ky!R:w9jbep̂@=%ܯ[&-i _vbHKEpMG5CRW;)1[oyS<͇1'>GNDxu-;p8%FW݀)]k%X|IR'O]:%&խ$o~>wr IDATv5 ޮ0|Id-#y0RBO OZב7&=A.elZJ**<鵹qKhj' '8PFBe [>@Jͨ1"d菏#zkHg5P@" =J4Ͷ`YJ#$#ޘ8 ŗ H?0cڷ^i|'FBe#7J2Y\cēYݥ>6gN2@,Giم_x3wSu(5,uԙ'SѳO 21{1ř|_fi#דLK7s+$.LaW} D泯f`h^Ѣ.{4PW#-ѭ䎖DZ_]VA[Z/ }7~3=("Zl<썏p* xމ#P(?12x ^| OU|m:r6ﮐĤA;/kVo+ _pyFbH c)& 7(H#X0-,\l}|G}mDg_:yG0;~"0I>c 0mlӰGutt"rIL"`2@M/z @T/rCIL^]٨ Ǵr0KL:K.3r,MLGcc{z@O;;1)[Vex ĬҔdf [oB*tWs)>OCVjQ$R*'|O/ƽ^$Ĥ:DBi8 UyO@!\KWe+K`/LFÐt8tl$3ҩ(&[ix2RYpMgt,Ga˒}ōsi0y%Ϫ1b1*'1Kg׃\;#>We4=<|Tڳx?NV՛K/AJDI8{!KP5Ԕ5dtA^D!cN u4 A/JuZW* eEY"9j}qOÍջTZ).?|Zae:60dH$0Hp=,aNؔ&$[*$ʡőPǐ~{c2ں~В`T,K>3KOO*bJ>G%iLnJ+7yP5Q GlTĒ]دs]tƻI&%!1Y@:ZŹE\mñ[=1\['j׀@ *ռ4Qĥ4ٸDn5q+5WQ&Ȑ_yYMm 5+"80#Kh TQ,Oj'#5tŷW=^{Gz9G hȷ\rp2@:y YV#MGOtES&Yj9"D0 [5鹋Ni! ABHVT%sZKӦo/>om_nĶc+z4pW6SdI:㞝Ub5ZR%BX9 Ѣx]=ˍˁ?; MEBuؔ).P{! ђ˼cֽD7lyp*SN&v'ߟYxM^,l2) z͚\h\cJȎ”7Uܴ$ш,&T D.o4"s-'TOF*Spga{&e{_*ߙMU桎oՈ"(m[%.&imLmWpcj9MznR)A^5–LF>.ӘH1miL-v>=e+V{'N`mF SX¸ZPW$]b;KbqO%&{Ď)f&Q2r?iao~JHT:y%X$+~b-v>[CTzF,BgMKEBzÝY%#K('ѵ6KB/%I}Fp838LYO8Uf .[Sdފ,6g+d&1dkia 8N>]IuJzLUe&>]ݣCD#SL D ޱ\eڈH菜s8FIݱdIbn՘%hϔ呻\OLf5$Y5u? W^,H-M`nC0w`s\%#P"ѢH7s_Ÿo0,h̅Ϊy 61ʁ޸V8:r3>NSAY)䓣b)d=K jqIE2dp pU牢Hh?FB);~~_A״̦YJTϸ;.6#PN8MQ۱u.Lrl`H;i7zդdޖ9" }ҝğ[qb2%ddZBqݹ $8Z,ii,1KI EP+hbal|_&|qsh8zh[w]\ (aȄ 5Y=3NP#>^iY'qs% 6AILc3$Ҹ*jR~N7px4݉073]Ql>S#6I&`"}_>ZnئsޗP3hJB ∟?for nٶ63}غD6ɤ[ύ!jɒt4)k@8)dZ,dg>]^nO,B:&QGձ4Mh}! %()N88M!=Q&-~,O"yMZss ͸Iu IeŃ2ztMm0{3I51RJr;tEKH? tCy=Eio.ÐPH>D>/%bBwW,~UZ;IWZP*112x ^|يl,BU[5 * P#Τ-B)~O<ͪ⳺N[[I@養l?4,":W -j5SsЃE5 %G5FMq+Y2MS~4 f"nBq,ddFb':";JEoQ 1Ai:NBEk+mֿ:Ar@4)HS*1keUvHB%)B`θM24>/I),D,e2oLzD2};.90 UrMx }.U5į"8H=dHx2'f&*KNr#R(\LsKZwĩWħZ6jwpbVȤpXzx,ؕGPon }n$dn {# ؟ 0gV )qw@& FyP]F|YJYOya9WUdIDE4GarɒPn7]C4,H4&=Ȩ@"Zt%Am]ḱu({]|ɝ]w) Cu9C}'  MBC)d/9 ûVIhrLJ9a'vxW%WWnŘQ#ѧNs;.3_PU zg,2,foihGf| 2٣YS<$w!Y2ۜe|fAguGNa/I^K@MK*53U-b&PKR)^3*1kۑ[[:EC/lZ)3UӦZ#%&x?+EÎ MjPl;>__U-4A9"0e@}*&R3>h(U ',L. 2͙%ҬN,e.)&2܋kRW["R ,嫁):v:\5j0[ <YR(CK|mK&L1e^I?zrf>^:C2/, <8^,@3£/U`O( K>Kbl*)͖~D Uő ,H( ^ e I$t.'=8V H"54$cdtp!+Y9a4˜\Zr_LHGheE %ɬ~,\]Ȋ-@ AArWRWiԃϮC-/zW~>.C[&уIǜِgK1XѫEBBP''E$R%*k` \U ΋>2w;6H*I 'fLnHr,&&yf*IP2[?2Ml tu&>R7"^pר 0q$p/)Qb'5UrʖͶ#T~(>F$4,闗߬-y:`RR@uALhN=fZt+ HO1DX%W!9aݎHf3}$`ΤGzQ<-sŻU:AQl'Aoq[QC]Oy[_Zd9w$5 }>_=(9w!gw"k^X-OI5H;s).SArIUly%kY],0 dn hr;Un=<$l'IW7" * s"\DEADP޻t4;{}c}}{V΂oiJ[xfyqq jz[]Da5/QI6i#('tKo/ZF(14Q`/<6ky.qhn b AuSoLIbq52)TzB)[,F$o$xѿ^m+yi: ?kO]Qݧ!G#m܄NwE 9 f}Hvx>B^L6HWLKaH)3[zBqW?N6JLE@PE W?~.ԐFBLJs s/Դt,wg$Gw6b9> # *#QW.m$QF, R*S!#&N&@B@Gֶ1% O~ۭIMbkhP &{rBAj|)PxS>8%B53k$2EGg(y kdS~R]\ϖl B7lڂWߜ7_y I6sVkEP pZNwX%P ы^V"SF!ou'=K9e(ړ ,:_ӤЉG t{8PM'lhӅlUm)̄P8k(9P_'G=b{D(PYm/;fBT2<`7O0w ۍuÕ d#)Sݩ0g"jų/ti7oY$' IDATbc6!OBb;b}6TZ 23>e@(?:5>i- u#}$!Wo@ nXޡ.t9 `N_I,E%1wt:=F?G3YS.1*UkT.[oVOc|.-^,CQ]|M=ZS˲f:s`) ji5Y/tfϏG܁`w$&wu py:$ å634go)@fU}O06]jAA!n}y+.uIL,;;}u{\;`.~>S`6q[**a-Zl!= C"jB$иh-vƄ/myjH> To9hwm3d59.UdRQUNջ*(ZO͖lO{ptxgQ<#uzal)H#Vo-U.%@:%26nQz{VZ`4C^eY "$>7tIcQ&Bp|v -Ӳfօ@NwuVOEWAڹo9ͅJ )6#s282Ot9y8$pb,n>y" 81Ē=˱xH/ĥ.f}"J :`cW~c8zf[p ` u (?D&DaL%#K Bc +3<3Aݻ2Z &r1OLX &wnAd6*˽|!q7 ~&0x24PPQ}Pb(Ў✤ZGf ֲ<_&JM]4(HF˜s |yផBǭGtk;=eyMdjHkc&E@NFF ΆP `3U 8O2 *xb;bC苮ɝ[J8_tŁؚ [w"4ݢGQe1vgǐg 4.*y(bIͥm{^|B7`9'k$ء 6>>\Q1C|Ϛ$z.(-%FJM'H(:lFkwk!0PPsʅl"FH-D(Eu}.X߮J5`3Hpx4E(N! Mx4TwpWtuƈ t%( Kagћ]E 쒓arGct ֲÞ1rF Ed-B"7ܳ!ɯh;`P)m6m\Pn c@C!eGێJ9TL%F !IB 鋘(N9I?f- szDEj?)6 m/>vA־Ff:~8?Әqðe6XA^ׁաQ$~\22 sw= {z 4k?|;EqS^Y>k?HF5k7!2:VEFb,&*.vvb>h}A,n~3-z@owM6y_6fmd3Mia8kn:U'9$d>mMĪp|9 jH?bQ\b:@s>R8\BS(xllP[; ܿB-48$S06@< ; ~1^ 4"\C)g nG/XQfrYenLK!`-wux,4q:ѳL 7ϖ<\Kب9ENSW eo[st8 L}TsI2c9*jZoDbHcyBp tȒZ-=y>u7c-X l~ouLF^`PN,i!zz;Qf9-:+g1*%j-+C@@Rocl{5BKk P B2Y ޢϵ7TjE;w>CڟML!@zکPcnSPA %> 5#5}y%}PjCkP'o]_PSQ T*DN|zn VJ;!&TB4N?$(r!!{C߄$L״\uB7S mh|EH-N'y\̥65Ε E_0(OZKLJhs% ϙT;?J? wҽ–c1?%觡CF``sٛu+p :'«E%bMii& t]6;Ȁp1.3NӉ:Rp ]#; RL萭y@O홻~]τF[EЅkD'$br{&FM<#]?/Bj5ZNf9P>jd24L!YNn)*N/Znd(4Gz?(Y *G P-n%]!M.›:3a#fBM½ P~̇LT0r # %}8*HRx@bLN>7| m:\+H_pt&M0WCO7fU/@KrbLϼ ΝxL3o'ƏVIЍAZJn+%~?3Z +Y+}|!s{J`a0E@w޼Rshޣ$5K.Cڐ"˿LJbÎ2QybSƴALGv 4 NL!sWQHUhkӡU2Fr>f3v3`/Hfdg@84n[EOeFJI2`mF~}h2CؚSn_S&׺4fvgf[!@F CŠ"v0=DѓYx9w7/yfqRGNDDTo‡j{LtJe [+Qb)E\"zO*EtZڭ ,mPZHZ PZ-"l2`2g72EVXtjL`(b!tf]AL؈ GFx&NW<_@ 1 c B9^qwO&UP[D'1~`V6-j5oBr.ߊ=P/R^iYJnBCi8G#NZm~GaSp,IE)wg¤_? 6xh1H43(\^Du+6f F@:8$du#r>fc>.-vtl*J;VN9zA$ t$]tvT.܉@-8Ͼ* ۯ5mT1V|RIHߕz]] _&{NL;]{D8JyVPA6HSqMYY#Qf=  (%"sfbJC6?LGE Z1DP=BslzaJ'`&՝%r>53 ~~Fzgˬj݋|')V/Y3lk ; ,cK;Uikݴ\Kd.wIaOg@aC갋 _?v<+n#>'Avf2K`'p`'A{PO$ :$vڮ\Ai:;0Aҏ.Ndd0a1vju':pl>t>1 I^rRFG'¯(J4eD:&{@" &B|O32IYTkJ5Cj=KuR^6@(NGn- Rۗ·>lg_LEF2^q./iXf A1-\+&E ֟U{(Z*tgMBկyl_Wd|"ڐm 5 4K>ig&SzZ3?i:DNfCIq ArE dv/LmtѺž :DޠA,hwۗ`9+\ἛκF9<&4j;Q8ZLw{;׳5@z<FIĸޗaRDhS6@ T#PRoV7Bݍ!7k36~?J XC-_=NJ$Kl|oZ+[oD1jn0X}շrR#  w.ً$$F>&6mRTG9A$G1Z}J,IdK z^SxRY|?=8xc7X{ܙK-;prJbi{E$[#&q<xAʸ%tĄ`ZlQAä9mmeO"nĶup{vHB~ =gv68f{Sҳk὏㒬 8?`PKOW6"ʐHnk_q*H͐C.tܭCϐIHD TCUz@۹k2Ѡ# j1,@f=t|$[(tz3ʅcws晁e!n>[O1]D/CID6ݻw/\?\h_̱@k5 40ОB[nu|@!sw~rbnBhzʊx$SQpԐerXbjmyU?pE|cm^t,jw]m7In 4)H8trHjQ.( *Bu

4hr4[nhXG:vJ鸀Ӹ]QΥbBֱi$^z%ڵk1d/⮾j믿ЫWjKjM TrA( vc${4hJ(剰X>W IM48 Ҏʽ_%(TF 3RM_<d&x`u 瞲%nM;%~|`plĻk?X@TAEgc ]wj ~3X|'I?\P/`Un0 :0LE>.y 5j42(=sgDF6:+΂ 0yCl۶m0Qn){t@mg@ {pb tԣĄ^#SgKwě#D<,IQ| 򾽟 vGN[M[yT^tt|o%YBZ*Ԁ# b [3H"C!P)Βgyg'ؖ V`B1m$[zB45Œ9bʵϰx j\NC;y_|9"/@E]Tm{ pg[us+W_,3-/ P^9W'F1̄.}Yuu()n;Z>v\?/ڬ&S!|!&/ xw3uR~hI:|ޡJVOO wPs6NDrYRi6I8d3n۔Fxj,\Dy۴m|MZ!mXIJ׶vWυhs|bǩ*f`EQEm)Mrłe˖ 믿״oV?ט2;~0Xkmƾ`n6;d}u]?(l6mYgկCR}#ޛB$RֹȟrB/ye@>uu |Y+ɤuI;[>Z}b8?y3v.v#P̄9y%}!&5D;ǝ~WN|MB~*XwˈLj #ci7:遽{Ê+PYYY5C=$ϚDu1:u*>, ao>Em)煙g}j y=lYf!<<-eInSL TK{}00H7;f&+^%}GB}.|JO'.B]Y(T^=:! l7rW,ku4 R^j IDATGHmvQL3í5:7.IC4 S4Lo ~fKrKjl&y2m PAw@ŪԂG `g^ =z) դKMӼ 6pM3z0+}#2eV3(߾Q4ڼB,s74;t`BMZ{mī[4ʘ+ZϺہ;^]G )qo:E ԶaH3#]9}atMW_](BUxaT.z8>3vUSB$R 3wy'SԺ\twgϞ +_g>o޼ر}YA^U#ͼruLJ= @:@hQAcn8ݫ6;|?N-)ۼ!qZC$Qڬ8x'd ԧz eAO1.[EMf"j.K*?6mRjd+#GhoI^6> #11H:Ƒ/y+obO!Y|m1%"+ۿ2)|[ދ.b}^oT0Ν~A!.;vr[wqMt4gPp17V`%.\kpaMF&@ CL`Wl5Pc]׆'|X̆7T-sBSq9_NWOo!Xd?;D!wkj|-zڽ\# O :_B sBO۝mv9~{=PTT)ލk7]?(R i?!WߟHmӷ3'mf&IH*}vԩ(N??S۰a3hr1!ƍ\ƥͦV999HOOP,vZ/u͙%XnJN".UeG3f+nt:8hf~muOl, ?a#4&pj;;*r 7r|}!q4|K1S[Sν]8r>'sm:Է>_8F|!0,FS)'TVza^Zw(5J}Ͼ* ۯ{tGQifi6vP̀ν]Ftz/^,Pn޼{APPu zfz'B'g tXkK,SWc n߾]0 QQZZ*eWZr#t2ˠ9:5O!wޞvZV ׻+y\ dk۷o/o϶h"]ĤЉĄkL(;;m`ѡ(ᑿ%X5jt&; !{?7 S_>[rS`10QQ j5(uNUxn=~nt7Vޘ. 䶝Ͷ>푿 :~[}:sߘ!?@yv|5=X(uS1w5kA'#0O3Y1hdzȳ2!%%:nnR l][:5E9X xNG>inB^,U/:)$3eE)l>QמfFaCg2(ݱos&j3Vr&g|jp@կ¯Pw熎Z-<-Ki㋎÷4Ն$${k;ky|/SaTmyn=i:ZL^:TS;+hmL݆=U}Kxw|~[FUSϳϲJJofֿOHJJuI\d-?WbW3dy(ItEV\\,;vL֯$%k>6^훪seԗB"+E/ t,%Yb m\/بg(-$N_rJ/^9ZzRWG_Qs mvOT|yz]&ݞIm;XƮWޯz`!VX,!WzeCA?xc{PMLLN<:^<1a:8'aml:ts`ƕA8 ޶m[5zf0>uP.4BBBD8A-իWsyNSNзoSƬ.1 NY=$:Zdi}[XR_D`q{O?*=cS4F /ĈUu_] !zO||۫Cm}3ݙ@ PY'kϓOlu2[Ű.+ͬ%̏Ycws~ ~CԒ:Jڝ@܆٢gޢp|1GFW]uw5C&fcPE4 )f>EeR.zb-4db%wxr5 @h>7M^Ye ZII8W;ޫ  $y\,XG  J1c7K};RvLA;cwOl)JF]3x|V}!.A`yosDc,ފCA=Hu+V$! !ɯ/|̒,֩m,lxy =|z~&JJ0g+ 1IyӰ͹;amM.ޛUyݣO`[*H` 1i 2@f|d9T*2ŤNco2${ :â -~XzqMr̥rwC.@Cz| ף.w p+¢GREv rEr$g~cͽdplbYm܇DPb2xcaοv dcpƽ؝\QU8ryg͵zWt9YK{^^zWpܞRݜʬ$Qq/,ɯs;8=3z;sU?3|/\ei n)g- ^z,vݧȠpͼ rjw,A.wpΡwW I-nhZ_ xvQBxUs~QUQ"Xuxy-(D ~_NT:gp3eJ [,'3g<~FBJY)WеhAɁ"X#˞ApYbuzF&R2NY=NKݻVMGklB00~L* *PJGM+9BN)/_=Pf霠AQZZʪ6cƒA&&fLqٙG\}hלRe,j\tȑ*(v,1b@EJ,@ơl1;y92ͬ*Hwyl Yl8UYCNLZO٫ZM+2۲\m,Hr]S][.b&L$:歨qxW<Յ~CVe}|-*C9$FI"Hzj+~wP>wƶQh2,$NZynTYky>MC_s,tm^ִ̊=!d_WKYǷQWZ_&{d7(,C  '?{r3q#sB'<}CիGw9'ط R),CHwU+t=6Mž6 7MY Mw=}> PGVVVAEIuƞZ3tESnB:t#G2"%5 E'ΩsۦN9G%K,B-;! ʠh4PIEa531:kpr6\A@8L̹ ~P6d\]I;4d :GTsU~m飏>*PgOwgf8 "G%a00iv3ΚƬ?/}mϜ9*Yc;w|x hRD߿_DP3Y䜰$z۠BVBU<\(᡿Ld3*Dj6f)x`L=p%PS&z }ƪ^ϞwĮF}ijHR}D!&2\)t\sOT7OV{w]F$faGe&t->?~(3(<0YˎY.};5'Yٽs/ yz?c/[ntCݱDZt_uiu=2$ =}PS)=kd!1v4s =Uҝ6 < YXzg%Asdf.PxԱ2fVA-WsAP] X|GE2 :TH.8b+.|S\,š)STu5v@;`DZ.i-k6&e[;: ~O3EN|CH)2WhE_z񍒗P .IݖCgjeQZڎzfn  Fgmaʼ2R󩀄ZM|^ m56spfXs:Asf FV3u\Bt,&(ݗWkU#G|2uti0{3nzõUC}pA B}I+ _+Pry҃SfԒ?GBCD\s HGE;3lz%G~ގJ+%G A(/u湰wϏж^(Zٟ@ԵlB,~ !ç <Ώ(1,ٔ˹K:;9_ cvQEyapj"ft<;8K2zIǫo#'kTISd@cewNpڋhxgBP)+i:Tj!ƢJ)(߽s|4T3/O[KG.8r,)ٲEGE* W] B++؏8-&2鐣-ʴvcɡBŽ_Op>pWϚ5KWTmOga@{w‚Ý`m}L(;?|*x I?HaXkY. . ;(Y3P'+/@9b.ZbK1̗t^=֞\Y(漣jJʀPѕqыW{¦9MѥB@~vOLY z`y$c*JYunÐ_ʼ JG]#}mnqTLgD`8B` `{^O*@蒛 ÿRίwz;19+Ṉ$) pR8,~;grOTR@QI-r>',=͔dvc*|Y?rZAk9+sNk8}*pHR&w07ǎWâܹWϮVLʼmdf!9Sq#ZGzCR RCa{*@Tٕt jE<] "ŽaIhILPb5h{m ?+DRLܢӵ:>0*(P_%"IWLwi߃ vC0H%@(*lW˅3LA>x;44G @Ba#`<>XL\OU뢩G`r5N5ČF~f,GS{W[N`ʱw=,vX}9Ff0cW FƱ.},mIZ'>{Mnhq=r <-FY&ء+pɣ!26?= IDATK`WTZ) Xb(mXr|2zN|HxT9Ô?!$ +Rk@ C3& YergGCH˳[bcYqE!Rй74tS^4g1cpqq>x\;斣YO юl;]b #d7K߄@f FݚL7Y!RIWIʸR!BMBTGN--;鹦k|Ǽm ]JY|fXlj]x>i%.#k,䟘ÔޕwO Fd -כD̄6A\m:7$:(,5@<c޴ 6zB.Q%=B> [[&B34n2f,V^E1jKqPtZT鯐A[7恙tїH9c}3B:% R28a@%Q*Rw(h <= e ;bE pDH `Jf#*)7ݗ @Z=bKN=[Fʍ@Xc9n $mF(*3EfEy8.O UjKZ Nԝ|ACmם [h۷h:p@l޼ r88bBسuԋ~¯xVi'3U)y Yݏ Ԕר;q;O_+O?2 =nm!0#~Pe&NYFD[3y%u|D2:e}6*50>rS)l/'WF9AY-X~#n(PT (&*3uRERƿ6 .ߌn @Zj ez-.$aMڄ [ MuAS6Q'Cg:uڱMllZtr(wzn @ә `lETjJF xJ5A喝 -uSc:}όF4i"1E{;:s~VE!@J(q22˪rP׋(w4(XfUܧrNhy[?D;dno a! P ,J8睻*vhG$X,Ѥ %qp|rnd <ՖSg.EYĎBwUMn?3IUcف )Q ׸{yPu r p)LHw>مT#㥞fKa*Rg)P/!ߠ^{ŮPsm6{!&Rs+4.`.~E>jBxE8.Qi.BȨ8t\+$Oz`NCџp(eIzXFj0dWTOMMT{ ~0&e&gd(AD/|АL醯a8 A h ݒڂ $)'H)d:l` SI'*AT#M<Hǡc2u-i/\(y=IumX/r vviOB@@+$:{^ Vm(!ouByOR?eCS.Cy~ B7q۶:[CFHcohb`8}U]8ĎmKs=0|-N#;X}wJ}@/\x0Z'mʦX<I tR>w!*Pv(ZJ7~C9CHPўġ[0yt֮*e+(ʻΣ-vTYW7mČNn[/~WT9'awϺXVxw>FP8VUy9@(Ow;mDx"KLZJ6[Y>0A-a'[╭w(_[ղUaĎv)(E (ZbOek0XJ2 Lhky'x:} Z+>6?T^d\68L!(*3B& t[>8֙[ PJ`R=UhL?``Guîn!^)ܳ0xkBFYe~*a0;_ =py[ ǽv^9G%Q}P7%o=3*<)/[(H+r:4v“̊2;,lw|YCvy.P/dB[deM:':l5PHL'+ܑ 2k]$= {@ba*V^dXQdX+-7SPU' }gWwOfJ?D"Ԝ"YMX ̅UvS%y.økҖȭG@ B[[X}w[&@rk<!`j[= n3FC_= {hvꜧQQYɯ @ZF0OCHùھl<g|7.r;j/TCI,6O`` \3<g'Q+4SQr1됲9sH:j؃|֠VXJ|o4"T=y ZZOx;𨪭 !{EzQ`v{W **x EEA;$2o $dR̬uLgw}ګ By4F SbP NCVh2Zz+{>ׯ`:\|Jة`VY*dkRuڥ.JVꝿ+cTCReTtw:>M@es}''” U w1SWK NV-PaAru#5G+!wԻ:>R[Qy1{p;{PB@ ψ>9 SV/D¢g] p:;_c%B',n*&f&BUUuT*3vBDY,4z;_J24 Qߙ.E<>nY=QWV0?Ǽ{l:QSG޻eLz~Ӓ 5ס\y+|T 1#`8fF7 -Tf,섀P;(`K4PnLal ̾-%BOh/MU䌣MurY.UBP:&B4|IK|ĿOwULv PP_Ep"4Yc)c*n$zm8#`4 "T<ٙh"4+!xϽfD#W !`36 @Ȏ=菟@;PWԿh-H=jԯ)?97(nxZ$&| EYUPyzRʪwJl 'B7n܈sG4i.Q$&@ҒTY+sz2a:x5KsLLZxT}]p Dhfl# bX3^a)}05Gbb׾}{u]0`43YC%yfT8o.~GLOhCB'TD겓3=W={/X08CЌ ɨ^:zꅫ#GT-+Of(A Mv7DzG|٣^UGxm[Wi&1g򻘼^x{;LwssC߾}1m4;Vu*t|ᇪ{+2a=J%o#~zi,Vʟ:GА:ޑۻ1?ƅULro*|=Gg͈JC^L6"Xs g!`8J'O_1M6ٳg|ɾDDDǔ)SECcp3f\xr9HV/t{ IDATU9tG"p?>Yw[f⦟ wu=dD"7:{j)3S0{ xo ;;֭/˗c۶mg;cF[t\} ٳgns/,1#uïDOJNԂԽJ5XpC0p˂UP9wEU"46O5WlBq RR}gФIKv ..' ,]T K,AJcI}NMM'S-C&!PQǑ<$9q<1(;ӾkR~z;m*[% f9#Ϥ 7.}M9ǢeBRk׮EjU, ~;233͛7YfWIIIXb(JoR#7 #HY9I穞/*({{)OYs x)%o)l|lk9ʤ#0  b9[n ,ײ|^|Eo:kưa}5k,pʕ+1sL޽)))Zķn]wfy1EΖ3mѤ_2OAY- -0lZΣ{k :S>$rfLçT"4ތ3j8eBB NŋN^KQرc0E,$$D'omٲ? /Pm_FΝ;(+w4eON=gԫ~K-"[ W:jM OҞЕ~Bw@\^t 'B_yXUVhϺի[okuAVV~w-qO2dNr裏СC-,F᭷ފ[{i^x]tZgαJ* j<=4M}wlSw4̓j=:e7;JA`z/>2*<^ 5ľ$R$";ӗCac)7.ÉPk56zCپ G5غ(clY)Pϟ_}jԨ&ylh?cSݺu9E&4(e&-'b|ލ7ި&=K:tPԯ__{~T4L-J;wNϛ٦MKI1IAuV-7nܨY0cbE#GUbO^QzG%OuQ7RO~W\%-oចk/WwG8%h¶,LV" Fp"^={w~iAH`I[nذa.DUVb_Z,"?3kLvEZ~u)ưRP)E',omlOl2QƍX,3ԯJ_G{W):f̘do;'ť7W̹0^Y*~q4=ӂ4p5ӴB(Qa`|1}Sr_aUi3v>c/TV'*D(Q[b9/Ǐq~\,,ϣxl.]"Gۏ>(rr{h2mGi]U"WZp}94ّih߾eEc=^{t9&Šu/uVPx:3T?%XF_Vak [ d\s^8FsF=9f;!e2z^w[_C?? U#ԳJOLk~2H ,Wl.Bf.쀀D5kлwoL%Io{Nʹ10!wl Ĭ߿ u{Ȏ;}yl粴,xo$^ x`eYJ ll~>iUL!`(O?ע|viGqE%mI%yl~A=E(39}"b!3Ņ j kd{_]gS3i,BΚ{衇9]ܒ5GY4e ݻW_Żロ)=іRN ]iV=Bk#WĜ"h]ȮWjj^J<)<%-KULhǗ\\(?nN !P %ByMhK 啬ꬍI/$zyyM#bcǎ:6K*157/[I//ZׯdBӦM+r_~%xvVIIIz^a2/z{y2X8U(x_L\ giaY#cȎ_A*i!KZ0h5x7TL.X/&@sP˦|}CB@\D(>zWbU,VϒK={ s/cCYDI&Y0BY5QKk K*rIMTFU5GM'm:)oQϋTW[۵b)2Z<2^m UA.  (@B NZeb;VǟRR(ikA|[w3TSyk=>N$g`ixeٻ"BctТzc̿|1! ʇjwXLb-D&l7Q'._E%-w3SD ^}owe3E"Ѯv|u]茿0.ݛ 'ch7˺Q*&*@ʟkAG=HoSW uqu} G~5sK fc$2$efBuM,%=)jYϐ1@q2ЉL̮Lח>9x!mJdۘ?̩@?`p~ 3PbBc,<<Ī~ 1.! ʑrk/C޽}ÇeJ 7+SQUsTŎfږʀ^ݪ:??#7Nh#bSܼReU%BUvЦ}ֈĄ("Bˇ]ʞ3fN>-m:j;VZg}Q1srE- |T5ի֒ s͹cR F=QhB@ "ɿfjmJt̙NNDoT9W<̓9_=4͋Ew8K8Y-Z5 GF[#+ǔ]1f&sG@Dmi`7Nߴxb1dB[eüyEӏT ºq:<G˞He}{p%Hkbs0cyF8Ln?^-vI@D]n[Mq4/e$!Pbq6@T/i^u-Ӧ6ҕ=$ز:p}qHlTϪ>E ..帓:;݌i5X# "6 -?}||nLbB^Ĥaܩ7d>W1C@$J_J!/ LؠG7$O;IN1#;դgoxwu_xWS.co<8.>0/",ato{Hߝ~*E ԸugeeO lq7wW-j@lQ!ۤSH9ނ;h/sy $ ".&ݾ}{D4iӐ1hc>)呿/%z:J։J|nǎ:OuA tWT2Gu͈\vT < o@ݕ8B4+&]U~~"T{) +ձrڱ$-N @pW"Fu˽HTUբ6^ʈB- ?ԯ6aZV^C{d֟U)6^ ZeΥ4%e!Uy;ێST-C"B oV~?%B=DJѮjP;J福:rh׽gQJbGkrOYxkJ]zhKj_0}|=(nKko iGPh<>v/ШӔy  "!Q!E?*rl vq}Cv]:^CyRѵ*~SՉ7C34 u0ۏ\wbU A{%BKcAӨ68fw-P)z8Q ڃ:ƈZ`,zMLjNVek?H7df ]1|g1qo#MM"PRYpگ_r%J]ō>BUQb&x$3ԯʣKŜFU on=nlQ}Ehf_w1Wb3z "鳡ǐ7խ{>.Ȍ@4;EiЭ*I@DsZ=ݷ 'Ĥ,1!P?ԌM+p 4B0;>Dz .1vVt㡿T{J.*XTv?B{©_,zlƣO̓q. LPZ|r6j$Dc_u L W::)bGO;53Ko]ԧ1~AJj,M:+MT!~?3dl3 v]qmTdEga΄hWӾC  r" "ʰB@/ֻ+~YLEeEbW чD2K;QJku,QzoSŪ?ڠ_M\2 pQjgR`1>@jMu2o!`D6$Ev ^QТȲc_Gդ2Ĵ *XpU^c#vڢ-9^<ȎB*LˋP&5x/M^[.-A(B'ɳ(~,C !p1B. XDh*& =_н,6T2̱&\rﴡ#' "$Bpzß]"82CZ[~B}li?7+kFjn̉>mF_Oo/U)]gk{5Ē^/Z$G|C{B`fZu&l9FJws {[v2F t{#|/QƳ樆N}[y3u({U /G/ M@DG&'@QDh-n݆2&<2o#ǥ^FtKN5cEG%\=G S]]HSG9xUFmϘڽ&=EC6ӾPƺXkܿbe8@xh%\pdJ vI !=܌[`úMe*B JJ&n^ [վZt/AJt\_ᦒ:|0&-}]襸k蕠*.Ggĩo7WP?0TibLc|Ot~wKVMI&RĄ(_"B˗.@9HH¨nDnͰy5F]v*!WhF8A%bfay27!D:6"HJŐEMcr +z1O/iaХ=`YFZMjlM/̂ChLĭ=ә夐բ~9HPQ:}uR- dS)'^(= (Kujح !p9Yi5hح1lܭJ4be|_a9%FϠD$xkA(PgKyR%Kt=Qˏ‚"*Y.qsM?UoSz^v, [~鳡6mƫ>n^X2{Q5v}2y!2̙h47PZ^w5%zUY `ֻ@=IPN^Nsb.@ V/ 5E${yR麠ՇSYǪx`Os?Y'PsYpYYH4kĤH{ФM#Mw^oow]=xODmlrQxyjp,R&\sz-)yF&R mF_}A%<])PoYp3{wo[UHLhyc!zm[$z*_lת;龗ףðyrFVak7j̎gzʫzjzNPH*eXP=(O_)ϗ g" "ԙv[*@n^.ڽ5 TmʼNhEZL@]mc:=74gbRwcU?.F-hVt::O e&pb%ᩤ֒Ρ2zuyxyp*"BjeB~"7'^Hx]][5{{MЉJIb: {R5Qmp=[7u,3#cθLJ8!dznZb%O1l@8{M6w<#5#%-NE?g&f~fכ2劬Ln%h]?ϻk+k_,|rT~vLѰ3>zmiŶK*NɲD!FHVY Uu-Xbiȷ]΁76chT7YW_KD#"у5'eA6ayv\ P AC(X/"ՆDyfU:UG7oFA7}t{WlK  ".I&)@a~r-2#rx0Am[,FoDi]{"B~ eBy ;f8^%8|{3x^?^cصg8  z"W0U^ZS>(U(Zd[ T*SNlJT=7G!W%7 e2Ű#YbӀ[ ˃E:žM@DGf'e\;vC*L(r4E%mb2gͫ$3 8Gd2'fwtI mUCб5_r&N]tX'>՟F')9[/j(^,K! K@DaF&&@q&s7D**D(dӑѸu`dv~ <.?8b3`9!"Ӫ8|y!n=~%&t$P{Nj$SBp>>EhUu$XHej$!&g1*^3Ru<7Գ*ߩ[)\7ܰT2#l.P%aXx*ILr=5PcNC~D"_ jW66 䦿}<-M pQN:Č/VVRZ hr pG-{?RO&!?:V[;~[ų=lU%T+nu;ozdBD}d~B@I`OacNMA]۽lQUu`]P -\ssr=577|d-rW@@D[\,{7L$V8-C?fDfn塺g=6n c6FFPu~|Rf KPfZÝm]OZd*2#еAG-jeF@Dh !PV]ͩM x2||U -{ѻ¿a0چ#SR=Yə,] S>B#gZUT^,@Y8w?CŁhӡ 6%nGv^NY=aa76`N1!e='KDfL:жUk|5 !PD?cy@-cѾV+l?{^;f=L }=5@)H]Uy`4IekmV|RO Ϝ : 792e-BDz{drB@k+pgѫY78uq[3i_Ik@$U \%>]T}YU?uHV/:Wi+% jܽ !P K~ z %n[ڏ6W!%bb}6fbt ryҞZܼU&svM@D]oL^8n n,, l> !Z]DaWڏsY!+!xJ廲3S"jaDAJAmrY"Bgiĵ !,މ"`T FLj,6܆Z~yH g4%f"K ϬLUucxbp٩Jk\,J[2W! JE %+ v-[ :5 gfJӽ»DG`HJ”rS`*وYy.]C W7<*Z/]A #z1?qq q2\gtquѱZtWo=5.%n7!F^M8N`! .&'6KNmS;%Ǡ^HulMj5@Iǐ2423Z@V0SrL Y t<ٹZpz{i&h~uE=+x8! l% "VRr]ؒm*׎{:'΢Z@(TGW/j-j6o/>y?Kiɾx6^fJ*2\yԮeQ`hlп~Qx !`?D^LP 8C_3I:Vtͱ3Z[ÉR=mUC랄ci =xS^&CfvYwqүT3T1=Q^ B甞R_OU>g?3cIo8MNC)jDe5U KzOH=_- kzZUU$sĄWJ@DH A 8JHGi* *#;Sw{L8 ştRU50 ɣpV"BueB@! D"B+`8! B ! H;_w}Æ +0Eƛq2'! *J/B@8LA_ڵk1tPL<fr(#"B #HOOѣGΝ;; +޺u+:vkڴ)ߟbQV->}i…PV!P$aM믿O?Ųeː//r ?~G,Z:tpo7|I&*BիWkyf\.fU1!PJ"BK NnB>UV͟Vnݰn:;hիp5m2! *J/BX譛>}z5jj,޼D$,];Z}l'뮻철3gPv%;?}ƍڵk !PDV*~yF"pA]FGc> 6TY'DÇGϞ=E:uIOasa+X쭷ƒ>eh{EKH0WL# "TV$`<i1j?m:#t43/_u(l5ugyrr2(kT^/J<=\9j4~gzLk֬i(@ijjΪ7n\/}{m~\(@ZB@s+hcT|ѫW38=< BN8zRH_J'\[jkiӦi1LQ8`=J'ZH>FxL4zУPLb?x#va۶mՉ^h-mW_}Usf `,/uعs넀d"B+yB@@bbd޽oK.]cLy?=LPb,vq 2Dg[`232z:)o6-D)@d2 #G,ogy1>a- 4z-s>ׯcƂLLX˫sN@K!^XRXvv.hwy[uB@T2x! Ol6kEAe˖ zE>&iYF g-xEɲOl.D4b(ī_]FcdO[oxbt<@~~~gwѣp7/X'XK[nўdKNܢ0ccvi,uDPp !`D>,0vG.bOc:Cz-G~ӝ Pe\SYߦM.i%yI=]Ez=Ѥ1"kixkЀ dE(g [wy'f͚86ibה1}dBu- !Pz5θIF[Zh^<>-*c)n1_|Vr$"&YeW;%f nk+dG}L?>@>C0ژ?l0,.E4^CMXnP#f8yog}= oB uo_Mȟ)x,Ϥ+KzzZer-!YUq]Phʐdz=ʢ|E1=4>೙U~bkrE%dUH)DJB 01e(,܋[.a荤֒Y%?Џb<رcM. #vz驤Y,HOa;v 3z\-,o]x{WwH }Y !PNx,LFADŖq8D)xޣG,,#0VEfpppme}Gv:+ZdҖXJD?{9c:/Z2,YΓSx>bd-ڃ̿sMf-p;sљ_NebI@D}Z;#m>dlX=e0dB@P#A! %,zNQ..kB@PlLD! .G“q _`qBO" "ԾKf+pZqτZy:-YS"BtdB@g"{n]stR >o.D*PDYB H3tf>M@D}^!۞%X{eĄ"BeB@&`7NqźĕO@D@!(:JcOp^,N8 βӲN!  DJJ |||tU1! PGYBa o!;w::eaBu ! ytS4i^+@QDwC! B p@! B@B@! p"B+c(P-Z@fPB/_E!2  p8wܐ=С~TMbTԩ'x5kDl,ngMIH'l~q… CB3v1j(Νv@HHH4bot(T"""|r\~.]޽{qQ-R MI0f̘L˜dȑ#9p, uVݻw'OdM_>=5xLGŋ,*"fa$@$xA" @@ZjSNXڞ={ܹs#GĢELXz]ܴ~1l$@$@$@ M6M6*uM@4i'NЉJ;wDmlG@޼y!HF$@$@ oaǎU7ZjH nݪ:I6mLbc;?tNZzGHHO"̹" +|ӣڭ[P`A^Ò%Klpƍc}/"HH(B #ߣ[n*I>2?z)z SN*bF$@$@ ѹsgm_|{.M {=c)Y...:64DܦA֪U OՔ3g!!!0`˗&sm  L&@ɀ9= J?H _bN4Dbp]Z?~Ųi۶mf  6k~Ru`РA8}tϟÆ Ä XԘ PZ$!KfΜ9uG]_Ϛ5K ДM]TT ./d\K͛ݻw^z} "T֋gW){:US>C*Iϟ7x&4o\5֬Y3Z."xMF'T[E'˔)PYO؃4~~~ _=zТS"gϞ'a*H5Ā&wODJb$,۶mKfBR$HlHq5ėY ų*v dc˙I([f~HjdJԬ\rT@P< .G"$ȑ#$Hּx)?Kٳg#ծ][LcƌI)s /KMwE0L+YH:'~e+z/6w)~/Ф\$AIi龍 H}zG)<$Hbf%^{/b+Z)!%dQN$@$@E(?$ "`$?)MDx^z4MmO Ԛ}8R)dh⭔SihRI RvIy&mRS<"h%]J>IQC+;IWv$ )Bo(dZvx}KM)s.K$@$6P~:H կԼ|2Mu]bE,dԊJ;*[̦ )Ӻ+wepDā~7޾}['}ɕ$<$@`yU~*Rgl+mA4|I%y\jkH)GϪ"F%FS`JVzZֲ B%Sl$@$@A"6΁V dMVIqZ  # P H쓀$VQC| & "@Xݐ п1? $@C`ԩ|P~YIHL%@j*1'+o4yI* d=Ь?Z@$)yqIڠAt]R6  '@g@ H2rQR^^rR"& 0E1؇H ̞='N{شiZnm$@$(B GISyT( @Z\HHHES;7mq.l m#   DNA$@$@$@$`Px7 PZ"    0Ei؛ld;w_MJHHEs?wy_ETTO<~v- 8"PG="ωq>$Dʕ+}&"^Ŷ.]@`# PJ$)u$Iwʕ+CS){NfėSZ+y*M kL1,9r@<qt?ID/Y<'"Q\ǯXBnh2NK/B-E,߿?յ dc˙Ijē)&Nr$IHZROA*7)bM[n}] ldP/3IvĔ&miB>|κ74+%TժUq"0֯:1)uӧ :^HHPޙ"0y=iɒ%z!RJ)u.$M"j"dGg7xU׮]W_}h)ׯ12Ў=ƍ2O.I %{ZL^Jj@׮]uMRC۹s'5jK/O>1^v$ PJ$`˗/f͚1k~II!?+n:]HwP,1=zH̤IJ\2ߍmSqJmQ;*S U&N)Jŕ0" M2/5xmVwv1e#<5,gt\{ܙ&z„ Z\&F!u<%HRsǎJ[J]$6qd y}I~'H}Jld'&A2 ~F⭔~R!-TR^ĵ|"Rų+!M+* a~$@$@!@j\L& WRZ(i!Y|D!)1Lrz6o޼ڗr%-PlRNIĠ)M 5$#I !Y9Rs*PiDo˗/;HHH"Ԋ B@ |N2s3gi(4z xc>ƍZiǹ}vc~qRKJ'J):eK/S5M->-#~7]_0l1iL$@$`qG IYM>Jx)"/:r5/]:>嵺dWTIQ&^Q7I$"J&Sx#YOKjv.Z(Ma)]ُAJy&)elk{/1*"HHPޙ"8ӧDJ6ʳm. C_*oKx(%^E}zJp$6R_~zJX'OZiźZ2'$ 0EIؙ쓀xM^^B24RS2K*TRҌy+ NDăg7::Z/-VkIIr=/όL)MILe# m}>,J@DdKF|zM x)sUM^$)^EdSzw  !@5ܹ* dr_}$4(Q溔/uФ/e$_%QKh֭Ѯ];m; $@$@$@$0(B(   sVHH +\l@FE"Զ΃֐ Suc&IHHHPy   p Nq$ P:ZC6M@jvK1N~^~p梊Ļ藍iǎ/IR__'EHHEs3wI@PP0nquUwDFW~3FgL4 KxZxB[qqu=[_̩+ŬnnJ̪/Ν \RhP.jըٳDIHE '.\.8y4^ %0Z qqp7|՗7r(odΜ93+WNɝ[\[)-2" CpH0BBBzϖWUĦxG=<<잞pSS[~/SqqWcosGFF/y7^┽ݺ{!6&ҥJxb(^ɋy _ȗ' , CI$` 7qI%6Sx M xWP=YEŋGG 9sj/|'z#]S\3n^ٔ2""LEL8P^KO%2Tʄ8&5bw5$Tj8#Dl!g.%x\9r7GGcYhѢǏώ޽{W'TbCBBʚŋ_EH-^WB~Ơf  L @ P9% dزu;Ojʨ!^]4jXO ع{/ʖ)F ekF$@$`Px7 M@?_~UWtBMPzulJef6d3'0pF Q-J<{ ԩh}RSHwهvcætGꝲ OJ"THn}4UP˰H=nnT!YyrAШIcxRIlK ,< GѣGpD}ϟ?׭ʹ IDATfZ>q}eF$@$6+P 8')틐P-<իRJ9'ص\9rD ӈp4oڷA ǟu~}zʼg# xE(?$`ar~?WU-̂ФiS4l«q&pI=gu=[gշl!-)}Ν:+Y) P;d/+w;yeTZ-[DU =v {v={Pv-<8q$~Td1hc;$ # P H -7oG>ǯ*+bxu+h "8'$!ݼe3lެlԕ~SaOw>{9!nH&@O A~ePfuR˔.N:2Xj޲σ8+_S|á͐ @FPfǐ"0gBUIUHȆNO>ʪ$ F~圤0G 85P>~n>Y ƥWѱc'k6Sqد^tݧ`]6F DVIH 9P~"HDKߛ!** /~I+^ݙ bst7cFóOwwf; uM' _Su  JѣXr%WO=ɬyS? Cuc&A`̷{>GӐ75J`)pqY5WuНr[$@$:P~2HHCƮ}1`@SO69~glظ誊GkV3ǒ #YEs*Xزu+*U,p|ZB$@M{&.c~9M! 5Z瀺WBޞsr 'tZ5K <֮3 -_=KO`@Fk.t$cHH&bW&驞TeyI0o7oYcݺu>xvhM$@vF"f~C^C1|pT^= vK $4˗-WѰaC_,̷&iv/N$@!@j|_D:{lxxx8%n:cŊ孽o}.ǢŨ\b&( Svzp4 wO{ °aÐ;wn *cȓ7/Ν;J~CSl$@$L(B鴹W 2y僫6looo+YYrcزy >sPٲeA7},xg!~Ε˱Apw$@$E(?$`$@EQZx(AڲeKt KFI |S>un(X+hvNy {G$@NE"ԩ5@CWr(UǎqY|:",4T 6mSO1 :テ)wthڴC;;s4͝HH4bo'&л )RDϭg$o{nϟ7nc=f͚xQ^:۪8}4WT=9REq),Xmr6T/ "0}k*$ڷolԁq*h sQFM4jXas|+~G}^lYl*ݳ?uNLڹ/ @ H^x//l׮]: mDpP0,[0 A#2Nƍ|Fn@@/u6jkP(S 7QXp vlhۥ$@$`Q/AR/Am#z1i*TD6W (\*t_5j^U6$p5}tTvtQOo,$/> >}ZyB,Ϗ&0Ebxsi=g^xKBA*MCpH0ۧ.]e(+W"U\9c#pelݲB`]P񿏣uV6)0D$[v{t4z<e˔_":pGѣGJ\)T`Pp0#Q\S"'BWW>e2 ʣy1H*(,,9r@jеkWG_˵Hbͧ>7oµ+WpyuDybŋr Sf7!ueN:c? "@Pd&Nzt/4o7Cs+qnE$FG[r ߸).3!!!*Q H()0O >޺z\7'r)ϪYTڗٽۙ)t9) }6C}a,W"$QD\6%"MEFp:?BаPo).QbʊUؘDtHeoJٔ@ĭ^<: V|Cy7㣿T\ߙ}ns ѯSZ*"vsT44 UVQW H"B- 5kJR#)1?jhز%dn$@C"qΒ;dm:tAԕ|4l0W6G֢M<#GĶa l$@$L(B鴹W<־Jf@͚I@DFcĈQزWrF$@D"ԙN{5@vOz*Y6֭g\LB *:Ç?ql$@$L(B鴹W|zjժDh]`1*kذUi,_B! "@T͚CcO-YA}%Dk3ǒ&R :~r+~,HEs7wkک*̘CI8UjȐ闛HHP:isfhܲ5{Ի5͚I@a!ȯXe# g"@LͽEa6DhQz5`"T@5h`W(e# g"@LͽEAǴ'EZ", 3 |…HH +d6KyI֪Q-ZB*Uxܚ$`A~:-Rzr% 6p4 CbBU۴EʕlhZh\+LZŊڼ4H,I"Ԓ49E6PR%m*R:a[qcٔ ߯?,Q܊+s) zYDGǠuN(_<:t *؁4 (OyB.]ͥ}$@$`Q@z٦z;l2ܥ ʕ+[徬H}×J-Sڊ+s) zYDFFm(U$v톲eځ4 DgA6~^H(B-˓9(tŋC=PFyDH\ZOW3\O$`_(Bhm CnϢpٳ"K# [jU+;ָ HEh؁0t, W%& " ]zj.yr "@j_Ek@pH= zD$Vv+nErh6pEp+BP$=!~BF puU?ߩ>g{ryE|P@4P5fm5+V^ @R\^+l/͓_~Ŋ3H||B\t"-EpZewM@ (+ϕT2r zBԭ]HHP:qs%p7(<2r΅W^郢E3ޛn.(i^ˬW['OE1iJ._< %) Tu]o*E 9 /X$|{$;6\V3ASub7ݻv7%0dP9o>q^[@'TйfUs #@[3p1b.dlvRM_ەˎ-sD.tCCOz;, ]ƚ QIGz0^R]$fUyluX@ʩe0SeŎ^L̛! $!@ʏ AM8\GY9Z|F?|}&/ :M.zH^BKQ~G}J:d|p:O[9HH1P:9rL  :qk}7%Ʉ/ )`VѾ4 %@ ԿOC2.Բ{I1Sж䜍HlEͳ ׯ#wFSд#hQRx58-޻ Pà[Crgҫ*޿ 5 6xBM'ڶԴHE]jW]c & W\F>:> ǃq h&n6Zɏ9vc܂MM=BZN.|eEܟ'uW˕bй43>t:NqKMyHH.P1Ȭ&pU3m~^/)uR>˙ !烚GY/wj5SR0y|@x<h{ԳLx@6 #`N7]@,$@$TV:)rNKdܾ" <ٙ?YVɋxJ IL{^KY?T~Ko$햀ORZvŤhtɒSs. y6D4p&N*Z?g|xGRj90\;0NMj;S{ʯvC^QjZx_L ht%\$@$`(Bmh-071o\dϞ$` Ln9z]i*bEx4X变"B׿uA Gr-/U x>Άƿ>ݟbɩ9 <P?"h Ν7'Oõ1xz&l8r7|2'F냫1G6tD]3/xVio&֋@)mz;EPiLHES77Qgϝĩ3 -ZwwD'^Mg4H3kv7]/xbu, Rx6ӟ)y^_KꕣmëPFyFS!qɞ4}z?~NT} nrۣl$`1lz=ӂ3s* }F:}fͅիW+|FneRx=4OV;r%KPJ{ks>O}+jN?~uJĦgú{G}M B^xHHP:qs%p)̚4x7ߤ~нT]zFC Ah L[*W\Y; "/ߍS"==e釓 S/Rt|^9|kÔ?bA{=KpN Y6{44̖Ws'OwͰiDmy?Adrm3%T'?=:9:)$*AyZs[ՇcO)%zhVgЍE޽2c I$@6K"f'Nb:~ժU6m7v9Cx&072%3~^X]B`,ɉI^UZ; ooN*A'7*h&vԉI}a~&cw '@jgX';=+3.Bg~QŔ^ް=䙼&6e/GGC6w`K|dFpqɆ+/9HH1P:9rL㘷pX¬Fo¥YOhJBPN JѧCWn`B~9\ѵ^)ށ#I>J~S9J=yoN$  Ā_$C$@NE"ԩ(#Gc%D,[,q?s''NDhw9_u&m~7GRR|g}|lU.A5:+G(+*T( 8Pg:m5=Aސ_ӽ IDAT^L2T JQ(st=_Ai!e/~g#Ԑ9ⱨ=GO/+y6 p&tk >r Y%B.Yy lŐmкvU)G*w.w%I)p<У̣0OA뤤A_uz@$\(B뼹 8t(.]ŋgp~>N 'oigji ݟ'S%B$İ&W<ⱶ -rЏ$^С^b' "@jSAclJ.]/c1Cߔ:ת';վ_=?ae1P%"$iСջOCd??3a{$(B30wƒepU;Ӵ^8|A&H[/.%^2ꓶTyIPxE88$q6|8 ^J̟E3:l2aيwq32"X&Ȟ2hWc#CrKv`^4rS#>bIXAMd`@Çg`! %@jgG˭H@Dʵk7oV~TSq`|˜-YN/lSrR GpN*bu['[PZ1Q#^{ tQ ƒ\HlE eҕ;oL=zSw{'2SϞj PMho~;F$i{pU}u#ѽ[1̼8HE(Oո~m9*;;v3zc7ϡk1iC6I@3LmR񘦷!119jwG D$@J"^Ov[|{&"t_{v J/SAzڽwmo+Fajd̈7DGcxK'<$@$`(BhdVlջ gs6]QoʟpGhL##V\~9\k*>rPjI&Jq_!W,7X#bDEGb옱x7fQc؉HE$?͛5+sDa_đ;. Q.ߘOQƶ+x#*$ HmǠ^A^g"#ƸԾƏeHuS2+ۘ9sf@DDƏͱc% '@ʏ A}n`F`H@xx&Nqc$@$@(B0#g~\Gڹf ÄI֘8uzww1UyHbhޤNo)9 P9*DT" L:5+MDn`fϙ&b7hg O"4}FAww)By}m4nԀ"bT9 YJ@Dr%BC0eʔ,;wnchPO8NHHF@b"TJL&۷oaWfLh99 P{9)ڙ(Bɋj9VXq>غcs[r?.Yuj*:ɒSs. y6D4ȳKFhx&MX3I͆Ka{L6oߑS&Xt)jתose H~PY,$`a8b! "ͥT`Y'J*evV^ժa4yʏIE}D(eƼ&zcvz ּUBj.L';vwd48+DxD$=YqXR԰Ě֡nV˗ڵRfOg@"$@6C"f2a刌Ƅ b5Z1.^J+Pʇv XEPs!{&A%BW*Ej#e5.ǺO?E )B3$91 Փ]6E`A ]|:P[9O>9^^^wAΙdΝŗ_|re` >k\HfPQ[&papbxod|Zu/3g_LRV%HlE-myK yeTF+^:u$;)Y :.H$(B?W !&:"4 ,5q&Zemݦ-,Y a#,'N@ 8]NH$`(BmhM8|(f_84&lr&#QF44[ܮ~]\|<ݚMWKv =p P{:-ښe]GؽiӦeξpz"4 p_V7Fr>6AIT_o|6 ppܣnTL f̘i| cl]ʮ؏~ 11iW^|={<10E$@vH"&[w+Y3)BV*v:xGO$@$@(B0#g_7Ww̞3;̏$`/"T6qI_**Q +CBBuLNJ; 1P;>aa31Ϋ1Zn<֪zQ== e# G!@('}d*0<ٽ Ykq أݜ9{>^RVhT\AcObرkbbbQT 4iT6WDŽHE!7` .O:.Xh5W*[9~8}y͋TX%KðXC?[q-̙M5@f(7H Pr '^LʆŋXcI`"T}vozgE2%QHDhK=`]&_ yMe6PkzvI B%Kh{/_QQ˙'wN-…0|@Qg{$$@L"4sz M.[]8ݵ{6n؄ ?@NoxTl*Jܞ8uJמRylxYI?m B_libA&JE|y1b`8IB`*-j[l<Ν J;j^&S:йl)EpHDh\93W1l#bbbT"ȴEyP/|UVh2Of$@&@ji! $8 ]{䦬O%Bɍ^7f HSl9rZl-ND$@%@QrT(B긭Y CB|&9,$6WCZHL%@j*1wJNy왾iTv<9}ʄL]OϜScPZL] 1(B>NO@vꪲ%1N B71ot ͚i_u'B36G (BnM >>[Uٍ"4? {v_@\l&L+=-[qY+W͚5TV^իɓX`n!ݾc7vۯ*%'yJN$`PsqShںZ߇MPO\vU?GٮC'̙В=תUtaa)ԭS豦v\}hڅ˲9Xyi䳯2O& J"TbzgΞ:-Kn\ᅨ6k۶ncðQtK.g\lA2ep9<\OTVt:u*}?+[ʕ|HH~"t*teZJhӪ9J*9j!/ @(B # |XZ,SPw77#G[JgNGkï?|OikVNObܬ񂊭[m 7)oߺ/+Z0oݺYfY:IoZt~s'?] X+[~AQ6 0E9ީxT,ηSS`xė>}/9$$];ExxZi9jxxx9^/<ʕ+[}jk׮aJؕܖ(ivOwcv%s.pbN|ܺx[%o@;ueܶ]['p7oc h۾#2eM*t?|t|K:p ֯&q}o/o%Fq7(<j2۶*t0E񞺸^6 HEhzwHB*=h(򿉧{>fMO:$S0nZ|FGG'˔27mߛũ&6|}];A>cXh>*U,li`\\<_B <7zelxh $P<e.^#2:=-۲Ybۡ<ΜƒUC5Jtŧ!)qڏ? رcg6x8hUʨEfh^SZHHz-xkǎEf ^C)?r8ue*U(Ļ7<==5¾$$(B䠹Mسw?F>^%QYܥT)nȓ'eF-l;hظ 6k ~^xe>~b?ٳgҵt*a%}UoL۵s>]I3gT,SܘvGbG] 6D`Ε۷Et34i$ XEu8s$oA-DK,s/z-W\r[ӷJ8rпM ?ycgJΝ斤ZjFj(RHc.^o-N<ߛ@ȕG?r5j{$#I'7TZmZP+Sہ^$:7z͇ح,5왳࣏LK«}^A*5󿝺hhh(Bg/tb&HE.[( P~H ¦[wA=~746.*kp"Q~\/_>4Q˔)'Nb8;s< /zŊ="TpwSAH▫c*yNPkPNK 2*Zyp5lټ ŋٳ'[jԪ<.Jٴ 翍jJϺdxisnYY+3g+)7V˝+Pxu/aǕHѢQNѽkz+gS3<<PskԠ-2Od_Cvxh4  G!W񯺲=x萺.\-[Dڵ3GNۤ_p%JP2aΪNOg{bÎ;O? )5Tp!q,+66V4i[6GZA]R# %-[(A̓*4u>m@Dda qr?*d%K,$>Wvݒ\VY];{uJ ܟ~%O9tSNIZ~e/a@IhEc2 … "%J$kc(R!KN#MZz zd$Q}{ɺ*,TVс$v bEE՟DATD=[GS$zvp RpPL"+ "J0gwr{{f:,~.WA<* u. UJPJ 4 ,Fyq3Y=GkrcGIhπ 1"\5,U@$LĥΚ9~ߊ/zJ5$+k12Ν"ZO܍KQkh޴ծ*rNR|JNICd0P#`?-”)S0f{揼*;A7}a(dOp,i=lٶ[LBjxe!HrQ;vݯX$<,k<.ov`7 `Hdxa߼u;QbSfDh[yV7V2fD@$-_ BI"U,**s~ xEqmz(Y|j*dN#^,d;΋ZN<F(Q`JۺNX㏟AW/ds#**dԏ76sE&k^e $2g0#`!/!{y ;'Ÿ !pc $<8(#06y:x(\Cq 1/`ZuF`l1쮷 52Npntnp=]<ࢨ:LB0-# өdSޖkcJ .^2OBDgU4TlT:5)(Q@GHޡksm2 ЇX7oĐ!CЩS'|쒴T9J%>.]AQx}y]PE(Ȕ9\ P.΂ §֩QDg Kg::љJQ')U"V+y%hheiɧT N^%Ň!#.d uTTi-r_7Ѐ~Qp߂Ihcw`<9ʕ+ѽ{w#ǎ8udk/#2$$Xtݫ ` Itg 0K"t p7=b$.i‚#cY5DBUtKdJ"z*]ܜ\WG\v\IS7G׿~v…D~{j$LZD<3Y oo ڭnGH3)'T| BFp@aVM}p#+E .&t4 FGaZ`kC@Rᣏ>"M:U$H#0zfcCr/wAVN%%9IzRt" a 0(O"~d)vه"ϘK鵈$Fq,!WU |0H$\.ݎC|V | FJ_v wh mZ 7&CJa+ 6…5їdw'H.kA4}]}OO_|=Bϒ,ᄓzOK N>^?%6mj)nu5&#<FH>|pDGGcYl+o"*2o")#L)i6`ۏqp&4r;[I` &! P? GLj9Ԫ8tV, B\T 7Z ygjΧblׯ_~]v!<<݈2v5XW̝1 aa.+IO9wv‚B)Y*-?Z= .,"Q$MalT">M,2U<"j ;ӯ"K+_6 mǛtTeMgYUf'|dž ЦMXU'ʼnЧWO>\ҳvzT(ŀG 'jl\(ۉb2(vS\AM2"[# (bŝvEJJ+H`8CUDmV:|CF>}:OHڠ# [btaUϞEEj:*?eR"S+)[uXE\sWl& ~Gxè)qlDF֋p)R*H# {s&3߄`Ϟԏ?ø#ѣkgrN絖;eRSou(eՇR\v$M8O׎[-xK3va-fJz6|\|Db!&n͖IhA3@uE@${뜠T]2N'N6Z`KEMsR:8{"~ٸEU߼i2<17MKU>}||h.2o`٣sL$$D܁ O.W"כm,ʊVQROݺp=y+;[(vo ׮IJ?"!Vel #^|0lXeCb陕za7 LBMBF(?%䚟;p(DQʊ@/BXD4d:m-YGz;Ά8p>1 v:z7 gMII#0A`XtL~Mr{*ypQJ@/F6f5<n2OxWGxsu_2dy}&$$D܁` +-18A<(e@Ӽ~Wbh1P"t|pKXt{l<Z>ı+'ÿ3b&&!#.ov`i cBkX|}FQ\D*1ujKL.-&! }سg"""f=3e* vsƮqkix5AAB>YE D/U;c'YY_BR"/lǘÑG'CZ&W[hPZhrUHB9pF;LX?7m$UE߿?2O) 䮌@A͜ x`DB}a䀢L `g7{Omyء> ! K! cS1q⫣?# ;8sϦ ?>Mc||+Bjrn܁`d"V1{lA_'N$JWs7FnW}"k9!uP8Wn^;ej f}/:t*VoEnpR8aG>yAdµ~g !ݓsM.II#`냰~1Br/^cA-{xv@#q~%MKnUM 1܃J5wAHsXީTUMgр-w`3S$2 @w(=5Pܝ|X[@$z.Z57g\PqFrOƀPN\-1@әN]Ih$jDFjr)f4϶!( M)tٙC̀ii8~+Nlxp PJ5 BRTyhfmDK,}|ɩ0 5 w`,$hFF6l؀cuי)*Yč 芳A/],wA_2U uRtrw@:0Pt oS)i3ڠahy*3/v *]("_]Pk%YKJHhVX 3 5 w`,]va7ou]IْoǙ1|} *u>6g6, xѢ4%tJ-R;QgMdL_ݵTk6h!< uM:/=(T+AgpwvChnA/*ݠwDޕl

ɱ;0@~~><1f}ؽtzַuV;cAֳOC 8aU!USܨ( W!ѫ^Wsl':{Uٲ9R}.a•T<: P QpuC"@WXK ^u`֣SMΉII#P(Ig͚5ҥ V}gΑTt==v&kh/HgP|Hh`cBͽP#%: Rj |'#܌.p5ŽbMH]U j< 8y;AA eO{֫""[)΂ #݃q(E!wÕhT#Rȁ>SjT46Tw]RHG?} 3oij9EyRnW "#`Bfmcf˲El 3yLBMBE@bɒ%2e }3Fr-DBƸ߀>p mmEFG>~H*0x߉zIa  =v@T'J, kpF $K pur43du$2K@ )xOMQ:*~%ȩATk'M4RT)RfKP zz4C42 [Qм˙S'>ə3 5 w`kϞ=8pd7Ddܸ>q*A|v~nZ D@nq^g$EG"G"ҙ* vd s;l'8@OXAEAOɺ)H, UY"FCҒU4Tɢj'w+I5N|`c!.x.1 = lrH&&!@!pU_fUYXCQ#PQdP='u--xbB+j}0Pq+&ڀt&VB`옥ɱ:ŖCX}o~LBMBA ;;}(?upp8 +#`;.cȥp )&Ԯ:𔓑Q0nlQX0Ddj"T>\x IDAT0a6mڄElm;26Ⱦ~vxnz-ߦD.jפ, b:"RjU\]!YB`HoϗL$$D܁\;vcĉTSܥr'wcE dF~}=f'1j׈|:eC\FRkPP!'7K$*&=t !;0ߦMm .{jUn#PI˜gfd %$TĎR!!1":R)%2R=- fZM^$$D܁|._,Dʇ4ىv ]SIKd4,j\ w"ԩUoOy-РZFx /wmrP&&!#T ؍m[ӃT]od%_Ɯ>cK?ń jyIDF YFkT{tdנFF;4y PYz'FP*U5e >ljwv1뿾%`UǘmCYFEk=jO4fs3ga$:p{9cj"0@# D[bt'_[`wK77OKGHhLL* ("RF <[8Uaz; ^f>Ƶy?crvLBMBC@dS98n8}<~דì\} ||5-eE*j*ΓBL<9'z~ 56(zU(/mDII#P1zuxyy!0b27Fp=y~* iW0xgkx癄dQ&ԉ;JFQy!Bvw'矄TO)ɓbMP2޼ eOĹ)yePeK49jo)RQx51mI)sFtذaS,YRL+ Ӧuˊo\%|s6&?2=-)&ԞikD"T>F ǖ!B< Lvp^)(Y@ ho۰Lw}Q'Gn{=5DBX k1;0G ''Fll,6n(rINl #c+ou?f$X*p&>%^{ds0ܿnY1NXЕ4 ]t obS&&!#0 xx{%PvWBE,q9 !D/<IA5Ce6;8=y+$ʤ|2bhjPF~ٸg_^B)V4WbѦ+8 m8&ԊOjx\}uYDUk?<}jz| ۀe@`*e1 5QI{yyN)صg?/oOM޾ y^6X7tn>iZ;ڶ9,YO SGdȩVCg^#*^ id">4RATډcpZHg$ɠѵRH5Jst9Ck'4끺5?ǿ;̎~^&Lرc$KZmہF(# vaG~4%!efQ],rm*z̪H}_EY;dH`:5\4 k;d+7,d=W/IX}Kt"bzF!n(ń<2E"ndd;p"Y* $rCiV6D%~IvBxx8ǃ JJ&;OCzF& \\ʠh-^Ͷ=A|Z\v739Ί:P#zk:zEDz,G1X3 ̍9+K"2IJ8vYP*b;N|P"Lf: nj:kJ S!$IC's%Bz?)VWSmݓ.Ҩ0W|*(i#0e!3ՍvnXIB"2f#w%p*p孲kvADs4|@j`$G{ngIJlg}vDJXяHiDxK_@1#XcJ 8;c?;2̳(z6pơ7ZHhM%lI%uݑO ͶNDtLD'#ڷ{ƪ)3,>fFV3a rODŽZuEBOƭiIЬnmD@jX|,%=As`$ꅀ߼+k">>jgf-h3 ’HhP'_z~,TEBߢ=ޒdV>ET"# CKj; \VV\@<}4>#̙3̏"H}ISݥʓkn_sbܬ"iu[% 2PQ|򧗩:J7$B3܉;Q,tn㋤",ل[͛71ddffbÆ =o4#`<3y<Ѓxj ד I-?3sӐ=[j{R)X 8ZQe-$TErK|p N,qF,ɂSQzVL^^F}aʕ޽; U̝'Y=xl$ܺ3||L?vA:2e6%4hâH45~- RPQ/!ӻkK~9HiȄ3$4qT 9UYJ%-ZSb̘1pvP*,>saѵ3}ibEYUNˎFFa&<폝kxk~K"R,V[ RN)*ص;p'FtF Kgvx4%6Qf՘0aT~ƌpsD;K+ӿ?Փ?|{tTHrKCZ3bY$4msd*>Ix;nj7;1wC$)({6sèpeIxD$~>|8~zxzr3?փ/`䒟0alAzgGk vZf-MA:da*: '@1`ˆō(݆#-@=ߴ,X855zZƮ]YǫPrE,ؗйc3z?ܺXzxZ:%`ߑ?xt!6B2Ogt"7$zs,d}<(Z65*rgIr* x :|jՊuAY:F#['cŬO_n+n%$c<$Wgd# eJ1=1)..%4,d=8u!/O0OM`Ϗm-F03qWcҔ1vг[NP23Zh2/c*@Q7FiY_'p9&㱯Uj (mu?Y$ՉHLHZZiZ|p26)khY$t77Q2b<6"Ox3&xTeʳpZ-w0%Iє60n {SĄ.+Z-O9NJ+н{wNF '+ou\O"v&1s"X;˖Ъ :Su =r,:xD!=vĉn]Y8Rܨ w󇷧p+B@MJ ~N)АSHw== ǕXpyj}Wx0_})k^ǻヒEI1Tш'?wb;-mo*{>wCg0VgmB$4dw|e-ޯ(] m %>>:n/kH̥7!|"/嘭[r>Z!g[{?tj틎m|ńTXh\->]tH;a\Qaƾ2$)J&''K^za霈dOb('Õ*n]v5bB+_bቕm%taO IDATj? k5,:} FLIHh]*2 ᜩр:5H{HS_"kܩj|,% "(>N$CrV"1AK ]IC4xK˗d$)a( Z'7O3?syOw}P,io{hSVBgeDYSEB?YpF/I"1rPDF3,,NLd&z &DNCN7UX{ARj\]Ѵ?#tut$gz.!Z4*gN܇u܃KŔɯyƜg~} "Xv{ 23Ӏ-10)_QH}Y"_jt1*j֢0CG((~47_744H6DnʏSRVU$~qEu]Y=6?1Fh՘IhwG`!#c'~t=p撵򀳡^vEMH|#)&I mo-E]?]zeC kEl$V_^?2 l52. vDx ]u*VneGV gS|~$~58*w˚P/"dK5uEdM#IX<`sFW [L P\\V[)'N=z9 4x 2 Eldv{yq$TTLHl"Ύ݌Dz)rrszz ~b||Y3Ԧvb>1ukbA &;-Q(HȓH+%,PLB7}NQQ!8h!Dij*gR|*,2@] 4 (}pD集PlDjݤ^:VKLQV5d%pR"D>s#K`?v4ḿIL{#GBEɓ'Zj15F6kPZ'GG&6Q'$-'ϖPӐq̅*i3&9P"eO Ix: !dg/{Rf6W|5 Hj ɪTeUck_IXF (ʅ77{̺($JDMwzߝB݃Ȭ+d?_;9k;%#! ::4'#GJ:{Y9*C6A) ۢ)Huh_XX%#JkD8,i ӊxh^~={D>}0{lb F*plPH>l }I_υ<_'33nDn~"mf{aQ %LPDV݉8# Swa5F$HM~:25@cX ]/d$IoJM:3 .ߔ0_qv%(^T~689@egBRZE !:奺eӘvK #"}%!_D,$›y'/=\p jG?Olhrp"?/\}Hh]&E H"^z ``j5 #̙3{a̘1pv`g!ʗ$W0> =B43)Ƞ8KIml:SXk4Jv3C8ֵ&WSEB9'o .d樑5 7$c7|y+pAO0ʖk'YCu y2w>.nmоa9h(TkesEB2|RL(Pr#v^Yj)BX?GzJ V=u0@YxL ХI?BE$Hhd\ a}VH'? @y)6M"Z N.| anJa)u*ʷ8u@Pk,؟'§o /b`=@hOHO@$4GѦ,f媜zu"O,%tN:I6"ܱ; Fb#!e}OK8W\P-3|auEĩӒaL4+JT`3P_%Z 287]O@D k6~(qW@EXJ*T2&KSO'  "".jLڋ>"ܳ14;[4KDƁ$](DHk b6 ꠶W^,GGHϪQXG^ڽpj@Dʌúu@ˌ$_X\v?Wa*YBuOd U%:PFl2V>R6_G$-M>$Y=MAQAp&{NmEx &gґIV;!n ԟܖ\egjUy6Y|] N84a"~xJk:᧳l ,w4@81OECMW]v!(( 4L2VwڋKzA$4/&6-i$? %]޷L;%.vCqKלt\ɒy3 䡚KXF%Tę ݬu>s OAdЗ߿@s| k[7&pǻ"?_Sã`7cvO$aP\O^>VS0`,K^G1**/T@qʴ GoCõD=}7ԝB;^7?;ޔ@cޯ 8>wJP@ʴK(,~.>=v)"^DП/ $T6ԑ2) 4E_(d"F<ך2ֆד+| K.DrOix?t88)ewsS/D3 q݃hAxﰢ_,t9no1cdJ# `>GsEB_|"Od*\kxťԞ\%FL3C0zHDYHzVN)xq$#e]~  ĊV㎿q)-$Wvi[\v~$$b9[|Srɛw)YM?lRIXtERTmO3FTԠJ\[.{NH)5Z-ѵF7&?R7yL+$:`Y"F0yqT,.CIM" Hh?Vl*drNߖ,K=' sSPr;|҅d+y8N[` Ю;{lV);Ӧ'$MS[i uÿ$Y$tDBC73˾3wRyiYy?nNJ:Ab3pvv6(HB7X#gcǟaѵ3 7x⇔_%Lzo\'RQfe*OXu 7=v?<"i[ye}G!J2M]24t)h,=Y$t[5qj&eukU!V-Gu* OP o U*xpelݺ^^^wfyz{ RCԷ߄5ƽ,AB}|:m%& |*ʳ)=rp}IMD}m{K?_&'UNWN:{7 eDޟX^[t)hYKJOt\պfS,{ ٷEBǼuaANX{]ee"`nZƈF&^Aܽ-"Ғ%KJ8b-sxV@#p+QidD8~|+B? ⦟E ~0 FB~5ť3);A*yDSdA?<`cJ/jr-,#W"C^5WbW`GHLP5<6SdѓuxZ`Ǥy * ڿGTnMXe݋Aŋ^QSqFٙ'iA<DNOOOIjw% aEP1MK6WC7S-xt i Q{G4xd&;z^JjdReA=c%s{=wdf{ k$B]uXrg#L|cSEBGM:PW*Ilt-ѿt.x?&_A@*O>VZXj牧0փf ӶAAb,_>|&.~t nEJE$\'%9QyG]ek^_zH>D  7:+Յhe8_$% ]Q[i"5JY}^,:s s䎏dwltcyhnK^P'oNH ύ[o0x`dffbŊhݺ5WD牧0?36N$b+11|`m_wL@F!u&DV7'kci^?ËQba<$yzV<5H4{T޽7֓2֮/9QSGOl-^I%ц1eO[ gz"8)D6Ѡ+a,+- HY</oPE?v]&L-[$ڽ{wҵoX|Fz!!P6t`½p&ٝMKj $"߱.|1rS (yz3]84W8[^U$WjK.jgLCIs7$'}|=tdmVh^E$R&6}xM"N<n%^$T61wQT_dKzz(QʇEņ;6P b(RN( gn{f&B)snv߼ysg9s.B/XLgAS@Z%H}8C+_d-yQ/v d%\c.\_|Q<ť;aydh\xc8)*k#Tߺ݉)w4WHBxVqӸ7[c_fl~o GAX(9TT9xD{!n> \2VXIBM]j9,Ġ6`~w6W/' SRvѨ((T/BeMlѯA{#)L20J˗/Lj#{n]O>\ /e= o go, Cwû:bc0>p8;t|u |::2NLysrwkET\坫w<;߇5^Tیǯhƀ_oʫI (>{܁'/ՅB!D"2+=P ihJ.'e}d,~>?ƲKZK,5^i=\x*'.h@3J 40Jr{X%(b(y Զ-wo0fBܢsŸ뮻j]<@O|{%g~o| vJ=s ddExD_p%PG"UЌQx,$nqȅO=/onIrмGfnRO,!eסմH+BV̲MY[Xd?wwth4XO Bgn2V% &tu儆9{|2}9", T{f' /;_l@ƤIj=y|Jϐm]$`0 \ZYL+)~]P:l_7d#]H.#^w?O^ҡwWs/E*0rFiIvr٠ѿ?i}?LпT4KI^LPp@=Ak~QT7UѱjRƈpciRW I tң冕FtIa6/yn(32P޵q5"K=д<`'w^IVhħT!-K}sN"JN! nYUM-@e9ȴM7yWzLCK\~o4?YmB3TЍ ?f읺mp,'vP;n[ wUO!F,.iHJNTLՔM&s᭥JH̄M 4TڝGSvvc9Bmr^B7ju#K3?y;u<@uo֫3ѮmkgNf5g#= 5OcZL9i5dlf1!g٣@hM1 C'b|y&(t={})qc ӬA*kљ?S}5\79;V{5ԁb<<$X@"oSKCC^K*w1 Z$zr'D Z"[@m@Ԟ #m~ķ5'h/v~  4^${@@{ ziKj5_<4K6i)I:,)7=GKDq Nzu3 0i{赦z*K5enLWu>g?h)NhMy Gf'ڀPusHYPIݓ8Gh'0xո&[dRtY,zFT1*J> *#'ָp*48tìU-Ī?-I "L 2YMw5lbQsIG.ߟ]IR\m<ߚ20ɶ JRcvS%{M[ 1 IDATPW+"&slE GO\Up}̔(~^6bi,-~:Z4:W@,3RېVZAz/׿nV3~„ ,y^.)i{xihۦ B/‡)l.*m`frk]5L5M4uTzTE[cF"=ߝQU"Ym3 \ 3Zq΋ 9V#v#kS`ZB<;1XlƺɿdlR糍B APR\ _H92|>s7%E0hXCJ$ ~bB=I劰[gC["gKفx_Egdhx`6TӋP4I  F%P9/ٻ d`CK_0F œg MPʱ^ Ľ2dhhE ` V6ie3+7 2Xy5(8dqe {jfvs08]_}rUo/<|u7AVo as6JRb"EL$bfk␼szUrQw}T $澟-]J} ][L 4Huq. ~>;鰭2Fe v3rCޗ${C6&prݔG5*(X@TzWlYi@ELM۳*-_ObuɔZ6[UJv5{9іAX|] rA'ّDuhPO[yZ~EP1&tjKҴ=X".ln6K)]?Pb9lIBh8$$΃]i86k;V p<, _/Zr{O)*4z/ dRVYܝgh`(b`ڔLP=JI t}KI2mMC7ְۗIhsŨld/:vrvj2.L9̲[N&|Ls!RMƀ2y 6?.?7?RT1IKj>w'yn_y΅ 5Hzd,yq+A*]hZ0f&W 2 | sjۛǓ<wbo]!Ztz'ifmN-@' }GI%/CE&)0@'B#, ^O 4rB7 !o`Ah\:چY:yW9XR5LzYEI;Өv NB;>T.2=M',. }7fшgfuB-v\| Sp9lLI.e`3jPw=^I&Et+6;'3"k 1ABߔAhc6oRDNc۩ T-(kgwȤ 'wj@SKz./IKd * sIȬes<S_@fxhZ bi!88Y綝u9 I&Rh†M:.)?= ?d4n &Es6KF^O3YY~*39[sMp9 De'TT $^!d!t{aȪi sE-T+<\qMc\eayP}W.:\ZYjZhJj-=uE 4 ۔2mX}wLD(soclE@ʼ4eopyefF5ޞLTd*/0\R >Lb.?L~ٺN$`e6J7AɠŸ<=+P#|N3i:ƴt"œ>A֒. ^{D3J HZ ?p?%zGuOy<@df9Tjf*I@MP3Sn֐*E; ŒAk%tQd&Ӊ d<H)Høٳjw\e<㶕B^<ow۟?<'TE EBi =hj sB%{WXQNh f˽KExRB·˵3ޫiN_1B,?KrLQqE6՘`lSqvC_z{i6l %6z:T.ݼ NJ']bֹǐ ^5ٺ$J<=hv%@;vp1+쁺@SQwe&.)4= 'fyIzN@rNf= ( zd*+!F3*]"L"D^ic67FFC啭z@kaPX:qpW}yVz{N&i8\~Mq6E.q "%kn'J.2Yy``¢dZM\]t֔KDEf#'C" Lh2kw ?eV&Zc3DBj m$֙3*xxs4h$^2Ex_x~j́7 dn\pQǠ]ჱ$! mI4c'@6u^J|~^u1<쁆s.U۪YCNGӔ^RXHtLʺ4Q C~ r2,LN朣,l>#=ezkS;Z)RMG&!#j->i~nB'BE $PΕh VPZi`gi NIbN$Pybj%+L3 dBK6֒~`Xv:E!"R ynypxZKKy'fB.]//(0>FQ@B#)'t̄uvw Μ*OTori(Mb#k2 &!8](tmĝHnikޞlE>\~`ZJq7MLY@Py_SiG:k0 si &I:$*[>7I%r|E \JG%,gKWMd}>>0z֞!yf+xoBC9x*'WWkO*O?%Hy)7vLo ֿDe9kdM@]ztN4Y@=㋈ uIs* F?$!R8vK ]y`ඝ ;S.=p)IqyM}͹wXILˤ6Me y)r/@eeV'=rx]?>e֝r${{ߴusR:ejР$fRs)rR#n|w'XdS|Nw>?d&ԡ?ryp`&U>A|ԝ.E҈3$!3"]- /ɴ*slFkmWˋ|)zk|结Uo0Am;'U:p]5Z`*23mYJpb'28H6zO ;A tuX{YޠJ=bB+PZ]^dvӔ~76v sb|lj2`B@5i+N4lVdeFae~an:#q7:"o-ioNΗ"~!賴"#AC[ 4&!;cwz7Gt@ B]q0&a/T B:ޏA~X%R 6i"5ع׉j(yTÏ ]lGX/t欷*pI1jq0 qx%uQQX˿ y f%)E`prSïs \1h%Ё BC]q(ؗ@̄:eyj@h|]rH]" 9'>7Tnc9$t$bi!d} `_ _@թ۾4O= Jԩci3cxd=Ĉbn7;rcG~ifIj2Jr<ѱm{ îC2 qPe&<-otڜ$ B%9D^N6P9RMt"Qv@opN&z3,|Q0')Adƴ=@p&fZ)3 bZ`ubY(=n"N=` i(w2폱mF Ҥ$v < CdZyO9^.Nu BP@-ds7˭ ]@&'irRCWM֫d"Дy_`?)$sS'Yᝥ+2[xs\,=F8z yɸ} Nx~X}f @½puz pp֪K;c00lmOp B›3l@Si-^࢟?:(ڷWߜO~}F~)hޤCG41?-ʹLj 1=Q9sXfH̙Pu>O}tO"b,%E' ɭHݔׇ^a-B Be֢iM9RyK:~ΫfpR+u0kػӧ,Tie1%WY~I'ol&Ul ||e&X~S~N#43F XSڄrRKas?35EdL&. 8 B6hQhU|9. z<*W99NLsG`?dZl&U 3L9 c*yf2.S]bH2WRl. D,wN/6m3$݁g*J9A}?7EUQ,)/l*2,qz[n Ӭ5R߸׽SM5>a ݃;A+~'t.dt ̔+ 쁺@k*Lr'&t2Z%z30f'Tqގ@,IR=`R~OccA bktg^ @tv䱱GSJw+ma(`8}s'(|s•_}^BΑQ8n㙁Y}l=u9s5VpIבc3dƂ#EqN膔%f,/Kbo^OAfmlF)櫝y.?J'+aFu G0h&u/<3Zݎ))rӋ:/>]f0wM ʄzzбq3ړ*~3d?LCQ._ B*NsrJѭ\)|0 r|}OG}c'k]pUKD;Y!1TLfa^({ SHq.(|H+3.2:};İBQABNpNf4Q`7*2q~SÇ^k_I覜J% m=npj)Һ=wlPNc&/jO 7|"?R/T9{mV۰*?2 X&ft DA6 Lx"AMG 9d"u1måuO^59p}.J;)إ v i'՟Ow;y%θ}KԿȠ\A̒>#7Ud449r_= ocuE%{Q*/FoHV&4@hw9*T'XPFrԸb0)LWWQ 9`ZkrvRߏ[!iN!b)"H  4#5 *9CI0d‹$/jz'w+ّC dƴ'ClWǡUrźM9r ʺpsY_,3} ݻv.Wp`n߁Ϸ-ơhG'\I=nG &{x`̙x7rn݊Aל2~xX~=:wP\BI*SfBH:$?Yw#p"|@';}MhOoESh9~W܉ou&b:*DjZߡ9jpܷ{w'y@f21Y2Icp9sIzv m np*T]7y 3P zr#Vs,~."L3ճ;y>8w}KgAxg)mP,ސpw ^ɤ>"YHIIA֭|Ic?駟pmGϞִ $Pu&&Txڸ~#r0jtxJĿn"k+>:#U]o/p^h}4($Ll#@f[$ʚ#R. Oi} 7MrvnR1 jH ~ xɄz>.׺SSSpB|8sح;e06lXe=z&L@߾}jڵC\\ tÆ >|x3f̘&ZA(uwAhR0X3]tTY=` zNr3O!yH\G>-5r|P?;f&/&P@@r)B7oj鏒k"7RJy5XJR2.hUupforN(֗N2@/7y7ڊ')1"Hn~iJ@&}w0$S;E 2A=C233Czzz EؙҘ3gڷآ._njo>u߬Y/q}IIIlj/G2u'&43}56ݨZ2W:<TMh1qIzĺ0@ݗZһCUd1#n DC16$~[ty w|Q>ԨG]!")2}]Kįq$A?>o[,ѓmJ̄^/7ў K؍ǿK@{sv|=/0UڛrjܰqEJev1Ukbٳ'$Н)';&5c(9'bNosI׮$zTϻ=/#<$y^s$~C1r]{d̟>q?+=J Uz'E ciy63}:S@}s?9r@$ .f4ӟf/U7x=`'߻gL{z'_֟ڂO}#QDC?ڽ4gKCXVC\ z聥K"::erM4 I#XuWL\]Πw"Fo܅rLUB{UuPymۊExRSшQ'OСCyN~DZIɓ#Cl7oW[ ,ٓ&Ux֭[8^ B* mxvF ӖR6nK[y&{&lDh9~q+)r@mU\ۦp+Nx$J6-5 n! pm"'A<:בILO;c+zL$___NgP`EE^tvafuy.QR|\c$С&ZJMZquȫD%ɟ%DΟ -G,@sTFN_# @/8~ۀRwXmZx˛9-? OWV/9kk7H1`:ircC\&HMKǣӟݷOF_0M_ <}r/ E83{NQߌa/jny =oƹsć?XqȜl 6c0'[EO!YLEm9rXL}0O@h6mh>7M>.+1г?٧~Z BH:Vay֧OQefgZ*i3Sm%aw솥 )ݨw# EuV]ǐv^~B#H6g՝P49g*I-ErN{3`z\sCɌ!b۷WsƹJf Xq/hb(.b{m9rJ S]wl A.Ol\ߗCK.6UNG`YUU]wg$k3$w o2 Tz)sF Ш򑖌YK)OӤq3jC֯O!H?֡I&}ܙJDtŨw;Qu!tp5iK z3"OCʯeQcX{ f ~ٽ*J)x۔ư|yUx`MExiЯw9z263S\-O.\0g ALw,JoG ՚'ۅ`08Aqqq"̬gUrJܲrtO|G SGsneDvg0bĈ 0hv TsE#<"14Tec;a_Ng=o7 `14o9}mN޵"M'tco&I#~9 s*jOm3qhڑ]49\yɖXP28|K61W6mahfX"s(s11P0{<3l θM+ʹ*GX v6d!}2.Rbz6esH~]̴rHwyl Yl8UVC6L5kZOٻ[A+Y1e&Dzx$Su!\1zMS\Mk=`$)XO>}oG"TWB!zڗZ('NAs|-b:R} tK+:,w Ϟ ˶/8Om HWʔb઴`W2zCc?P_bzVlbǬ'Kvq }+D In"O-e.8I-$7"<ٮhGy fYbW_TȪq@uRbP@YM\AՁ`YNꪫ1#.B.75:Rʿ뮻*Tq8I DkA|&DŽS~}RWggy>Y>JSt/m]2edu*ŒOeZ_~n~rKH5gb6? (I)3\_> ȵ꼴+}| ~^mZĩ*lmXx)pb/cv0yZʠC5KPb7v=O> 8vds?ftX+kulz- NvE%&mKLK dz_M)H|?n>|o"+A_ڗHng/4瞳eH9Te5Ur t-(ל$Ug8|E{CEKO%yK"\H>nKh)jVG; ߯eZUz`_CAyITsvs 6t9:2 ]EXHɝnEgބɧ>+v=ZyXb5^<$(^BU+Tξ.8HBZY|VG59$$]dԷwU,US}%ID~ av=/aq%\_,wކ \{KuTCrQί{,xt^He|r-R/lGWEԃX==h>99BϐQrxT=& ew2hu/SiѾDP֩y끏guXU2?b4QZ텉nD9d9ޛv""< )Ux@qR rDAEߗ^8f<,.@h B•W,,cPLq+=K NGD]#aw/v6ڹsEԢ;dbOTHIj"$y%~p|/)d~L0ì}ekmxw?ОB8IO;PT;h()dQ_ٮX8.[Rݺi?,zq!|?W]U{#{tW.]lz=."evvB:bHҞ;ɧ<mwQ^K$ƝA;Z-PJݽթ *Rngn Yۗߕs8Y*YT%R?$­XVgvQaΚ%yB%[-S#XX]):M-awh؂[o/ V4?Tin,:"jee~OCXUn'\r5;)t0i#I7©˿PG0U޹#zHaHsF~!x0BjcHףY/W_xV,y>"]r8FMDH!*jI )9:'-5hJ)v#g[ %٦v`'Ddt<ǍuLNQ! P}pWHl,܉'4NP=191eȈ6 AMH.00M(4D)4D(rF`};V> J1|.]&*L?R(z_SI_L%@E09U  YI-^ڈ(u4z9]Ջ޷iN"ZʅW=.Ӧ+ER!F9Hf(9Dѹ 5ZfRu#\'Hs quL.L:9;FnMbmgtRnDBOχtv1_w^l qWS>L%T]뚽o}Jvk{DB!ܒa)1,uZl+f" ^0$7"?!l%`"TR쯢)0H!{ܳ(ets,ZG7x$ u5T?P_4ê/ QQ*|2&{Xp29e2z[=T_*J)h(%Ri~/l("R3}+.YM9:. -Q9b!ꫯ{jSX#Tɜ"O+v'ԩc 64ylSyPPL?Չ Ir9[p Bm^W5'1BUuPEӱ>{_L(h0쓱 {=ѿT!ӉN:+F |MtBOy<@:.ºBBQ]=`kN^;q{BKλGDRվ iF7vGp't{

WɫܕvNM;3IVKLjHm:~Sd{ o Ī oL k^*=\9iwX{,ōAq?']JZ~=Hw֑9}B YH HhoErY3"'!$ B$<OhKJӸLH%#ru@9C)m6 N-$ A9$2,W*dJWG0k Xe8S^Uuw=x_\lMEP!w޵rl؋lXvТ/1A@u"\$xװ?.ay~(j< @%q-sm7RP~6$G};A T݇#+:hJn<۳м7?0;26 w-~<DB XE$%&S$"=B u $X*:JcnY_cc@ IDATf=O}vci>;;njPUP [ }T i$eG_%(5Uj#hب$x9eưV;DBJ8~^V^cjb X9yDB-flg4$&S$'`D"NBشOTc߻@,X5'>FXs^w7x6*Ox!/9Q}10ۢ^ zu|w*y ˽?aW~4pܝus`$Io<#BweSszw Í3^Od/ 5 :ᣧR!upOw`|1xg8%-7\ԁEuh hXWVrrApT7 AIcQWtaژrz i?jmt)5uqTh8yHT VyE[y;_7|i,xo&.ْ%:g7`+g IhZ W޻y&51>ĝ!{BwfBGFd*wmM?BQ@sGFILf)/t7uH@0}8V"P/ֽmO^#驊O9=] XA&b P78MBy[ s;`4PD8"Q7❳͂$UIk^MK)yHN~(Mԩ\_/"i*;Ս?ݏ_6t7r3x •| hV׋<{ٷ ppcm]AqhԹ޳m@@6UQ޺0A,P@yLF7.RhpVR{\I1*gb*}{Suo"(ȊIaL+!ez>kCbјwOz^ iw-_&{R^ &B"ZU#2|%+A:1I0 B*bbiJQ yCKVjPUR&.b #'uglNxy{}y9Zq=<(gG$1?KlM.΁F  0&!-Ч#0zŗVbq 59gт?>^w40_";ooǣ+^D)WHp9$guD+.2NCl94<sQ- @IhcJy0Ry/6CiDa{kȉlQ]U^YJl#_q{F-8E{K0}2O)Oon=oă˞C%AIWH[ܮ򐚬%BZ$K#R7QipV[|̟YhBiYA0t QHKw-VjB&=~AEZ~Ё#O7/xRPzʥJWbVyjFBkS1yo7*ה P=ꅄVCWz3dQ1*t!B#b ",o !-`=V+4&+i*M/@5SpAP=RZ;-ZoLdt[Bϼ¥_\g7R vgʩFYR1,i ޸YO&AYꝄ:6oc|2wʠ\=" Ȁ? +=RSlKs .*Ґv'PX$o ): EfHG^@OZyeڣDJYoit]l'C81\}0"$/!κ~,QJ:P< O%AY܆V\ĤBf=!H#ErHL2ɛ1[I(F6nWzϿL4 %Ig",={Gk4נig ~K UG[$뉈1!lޓq%M+>i ƆXŇ j vWEr[ !BdɞMbZ"EY .JD4bZlj¾Cň !I&[0tiJKyv~q-/ ;:3:%&*BAE7=b /bo7Z{Оә܍ּm9e5Cb! WS/9=8ẗ:BL-/r'48Ǭ@ J2a,#P/~]&.{jy=Vd;BJ>B **!"LP-}թctЂgR _"_8qZS1S^~C!aGx>.}#u (^9Hhr0DMmaAn Z ȥWN6154SjGUH?+!Rr)=/B!y7(Ej4#Hc\Q:4$of˄@ćz?XSp+3҆< 9 u'm^W#zVG}8 \6H(y)a8ʹ&s˳w#VFC! ùBV| !ut9DP̥C$M1F[|5/D1V`j}lٷAG~0Mbz#4 5'(#FB-f+= }?@AK+r_J6/E֗ϣd+I=mՒw4|Unv6KW1Ih6A!j0ؙ|tt\> {H9I,o”n=Yٺ  u9(T ϋy<33(ٴV:gGSG+3A* s>߹]&zW㥎^rHh#Ex`m"]&7BBd}A~ yT]oERh‡]Kx7Eh<)3)O$ $#?nGB+S{$iAp+jX\]׮]q-`ذahѢ'){^A. %V]9L5E5օ^BB=ݍ3aM1Q`DAl]po܊ (pZ||< 1c /Dtt{#+m+K(Z;f/G } 9e7ioa[%WԮ܍Öj+-'$pt AB$x5p}޸]θ POp#EYgзYOT,xnGB+_urB۴icǎ!??4P5k ]wBBBjދW_}Usk5O+@Pߓc?ih6V8N{Wϼ= sY(N)Bi{=I a9 $oo0/jb_a^ʌsF;Y}]{$G?ܕC8+cPehaY^5QMHG\!!MwA&qta O\n=X!J3m0w"K ލ[P&~|B~Νhݺi ?o~1g1Z9O?"@NNT<$ } 8T lUC\ md9M-9k~%J@>4sB ˊ/&v Z娂E-Ihl24h'Uϗ_~nFXf ڶm[m$0o<0)2eJFE y|y̓C"-$c.ύ7Zt(lfL #7[ =t6mGߓwVa۷+"5Iױc<;f]t"YXX*F¦4F=xll,bbb2VfL9Ǖcc{#>|=™t'L~d"Pv,*G}C/嗡]/Q>lDS.;[&nTcƒDBs{h[S P%nGB9׎L+{1qN+n'NobҤIO]\z5vء8ĉ1h %t&͛7GٳOڣVUERrClܸQub,**Jo]?cg+BgcMQx+"_šy줍3edsG+CuoCa>:){0 kD!avp;+Ƣ:tPu&};+-^X@M>]v7n Ʉ[Dg;Tᅬnݺ)b0^{[C_U=z ##CӎY.:LX9eKfÇXٻwoUXg㏊ggg+ .@^yF|~uS_ugEy RETsm\#"ܧZ˃uo⦙ P {'=/\ : !' [ٕ  }+{C}'lٲa3熾{LPJ2аaCE0vҥKUǦ&M94&dcRdr0&L+_zSNUD=K:j(4o\y~x%Mƚg"iiijߜSrvtzNI&֭S$yժU KWf\P`Rw^p*1Foswn,iTd-?C_ G 5 }`ʕJITbbbST$_9H(UNsЊR(59LYI'{1Ytq{ҹs窷Zjbej{ez*ȤB/bSً\1Al"& 'N \)GdjԎ.Ӈk^3%/(yB#0&ʳé9߄-HAp+^AΫd/#{D<6L:9ϖKZqP<^z+Pm?l史 (*b)ue%J& KQu|rG2eX7N5.6by*6&_}p}`9bm핗\VnTUnJ-)hoP(AL0z4>]׷.gS4QxkkJI?Vbo2g##B+6[$&u[P9r:p+kbAi2!3Ƥ1H*P<^G9ij/ZL9O;2mذ]v=+i|衇/6'O /\Yb 2Ƴe*(RKꁲ?(uTFH)O_wl|u3'a  $Գ?dnY+)źq6DH%K0p@teL\{SGUк3qA{,RUFBȡyiAue:}YGI%V4:U$0NIIQCb +.<ت̑^NN`*=xꩧ/yo\_YFGX IDAT@UkϽ%Th_P%{AJNOduRօxaO' sfAk`.rrA V$_~)עUx!mb%d%Ws|׮]j P<;$z+={gΜ<^z=K/ؙrsWPgN{8Mn"Jk1Yfٷo_ x7ʇ'!T{Q~!$RR]T+F"ɏ TeF {sa P eNhСCes~g&qcRsq1dH~r> {0y9mӦMy=N&P&,ŅUlLF:rPr){(;Jp Ӫ+ r6ՊWU+?aH7n_u|6j [Ql9iuz<Ri ZňA\/~`L PS7#\E7ϬT "lLWcO(_:kr3pg"obc#dbcMR#8^Ѷmۦ|ظ+\T WsU|vNails:=8c_|񅚯=|V\EmE9!u><1%"Qw9Mz6Y3ļ7GH(q8ڑCYR1 ^=gef#ݻ'ڬ(9y*u읽eoCksŊžs.xB7|~D.]T+Ҽ|=PI가&m,\/k[0{" 3sdOGBh~ݷ= AZ pw&zܡkbLX%_Y e֭[߳`>녲=kV*vhs@?O hOKژuXpeM%3YH w♹ӅǬZfM1/jmNHNGH\R!0n8ʄ F#R+I># %5r<儥ROܥT+0&^!Wsq08ڲo"?{/ٸCpl]Q>{MbF.h [0 ꛟ=uTtIlxv'A$B!ēsh9ouIYTL8w'ѯ!\Q6]l"{P!V KMڣͺK/ Z@@Hh- Sp{R?S+Oo8(5#/!5TЁ ̇w`/`4x5z=fw &[Ż1 POZ-[бcG5+^pL'@B??ЬHΉ*Qt;NHv [c䄊Lkhm^m㵱eAnZ7zԬ^SQG6u{޽YkVJSyz ϛ{71$;'zolU H *v ٛ21<>.A7X,HLLTmJoǔ3yԯ#=ᰏsv- "=Ƞn$ >v3͙q y9YS|!z=c ?^j;v#"ww՗2 rv X>,-Cc'!]E? zwܟhE,\ezG}nE!^g? Neqqq>S,k^ ̈{X,딈pzmMAm)+2kWa]6SZk,ڗɊvѵ6Md+&# w ێ' !nu~3aaa`wc<,Lzy1>Ffa۩,HҎDHYJzo;;ʏWBzK V"ږ 4 "pޛ[|(fE?ճ4ڟ ƴbџw@>uk1@mh KW4AQqi" veW㦾\zV k u /ݵkWpyߴ,Tuf/oE#y"Q(oQtg^MDGBg=MSO6*2) vb{k)[ 򫔾[`/ 4$ bm}` َ]9mK {r AcJ~~]ܭ*0!a*9ц> 2S:k!G<^[Rh!qwpuIr,9  uk9wꫯ*ɓ'e%A8H$DtEӹ|U.>T%G QrK)ihHh8":|Vf"ďnN\m@өjZ4d,:%G ;}wY=ڧ˃\# ރƸ˶dW" $+UUs4y =o|? !B\9rfv؛Q~.&*w^%f$2MRa0$Ra@YH(T=(4}̠ٴhzUj]|)Cң(9V;@y=DB B(m>3۔; $kݯEzQ>%&JA(9TgF<#z9A7(| >d(~Z8iYB?BēDUD 8w\zJQ4l2?ގ؞FxsbO՗ZX6WUu*ィ/B+O]^ҧk RAgQ= MCfg= V)ui=#p8.LfWeSI7wvujBh|+w3-;_\TE͸|uRR:2Zpo{y ˶dW" $+U%x?:h\[I)W^:M[ǡ֣RA+=voݚ;ztnU@\qq:Uao&W&cKI:{)=}69J0̜j;M>daA ,g ?f{I (\ߔ/Ԙ~1#7;] ~׫kTneۺPV7;[(P\ήoW!0zWJ*eأEQ69Iʒ^'n'+ۦdaAK+ 37~_ҶU#6=Vc()ۇ&2Iӕ'4ƖǗPqP0:>?$*';dZ9u.:x*s%V_0g4T+&s_M|x1d߂G $#I6)" A(/{mK2;U+S>r`ũ.hTs}>ۂߌQmkoŇ1pN͢$J2MqN+"j0Uls,&BB}异 V_ߌn% j-EqVe$s6JUch3Z=ՒzbRSF t~kx\QGzvb(NJLF[j|!]Y3M9s4t=E% ވPoU9 dean@~]0BF'PM-.O|@w3ClG#e~Y~ZgRu?ICa*t"w;_TI۟Y$ǔ2{-R&*"قVg2m y[[Qyu(<K [ćUg yFD@H@0A@p/2qסK.XxB[ %9ڰKv%etpP:uK +Mod\L;i4hHo&^2~$)drr[жJ3*džWN nc=;]p l33"ή҅`A@8 !D 8?u’ej5'IM[aogWT+mV,p1]'エϼr9 NkhE&Hieōͪ/DYis+x.Jb]QO|R昱9G@H_lP*C H%DBmW* V}z* {>JpUyUu!oF&P@ӄ$E95-MI4vhcՋ>y8'Rپ7x}{z8;'Y*^;Y&BB yYWj@NI..djZnDXF$ ':* N@͸uVN h/O ˛J+TCJbK>g]ϼDsE|UI@uxiSmG̈́.ݙ +:l_wۚG:zݕʁ@ $HTA8v( 赳7U[)?Xgz&"Nď , dFOFPԿ"i(v~H읱"ʇ\w?HOLjs7fNT;:N1hgp1yk-bӽ ~AA7(|ܒ|d Znu#5T*vbB:yg=EIg3guOCxgWv<Ӟ^PF碤/ B ~U0/0 v0 pv -0a>es7 $nQ   WoSx=b_dC]PǕe-?H^K[.$Z(?4;IwtGX(e٨U\5 S$جhso?1砦D]۷ӛ[2`b겤 [ ^@#޻ -ƖUkUl 1'=D9GS,iKiE^@-tDk;ڎƴbZF 0!D6)3Hr)_:KߏGyXQbmsHrAmW#!Pl.!O+l_휑Њ{y ةm9Mm$q4G8'e}S6QQMy(LKWCd.ͻ%J%,T)tK~Be$e2>uyj:5٥}p4bM}?AW(|ň\}kz!<^zm-zRs%m;^5UţFR4=",%{mXI@ǥ`Yfㅪ"ߛ,s]*"I~7K"%BBZdS Pf}8-u<'H8ͯJM/"RX! &/"\ߌڛF;] uL9!f`"i]9h}/Z UX{*6ZXhrAvW"gmH"-Z uf9f] gYBS[tOWuym(ix\V:6Xuބ:1p+ۂ&Nu=WT4Ii_R2  P{p)Ш=\n/F~-qhA' Ltcd3Rα]Kd} RtUsO ;{g/h``d1I#dw;0^U%cu Z6hS?w*'D@Hh5A2}8n#PNh8nȄ{஦JaJCsHptndtz.@*W+QZ]y3>.6mZ+>rG@H_@]vb#;CI^[Gb /Fdd=P瑟w@Jvgs[L$A|Jr%|>HU{JEݫ:6I7mǓnPِ 8@o*L m$$po.jڶg#}!R ޅsa[TX8٤ٴd T(녲0*#{@l,П9ݛt@@HG\lR*C`.]r߷7_N?vT9'qv2wLb1ykZm5b6k|@]!Qhzߝ>|Ɯ^;mM"x%BBZPo 0 B[PNhgeHVgȔ9{ZDK(sCDծ4GG*缤7ȏ;Is@"p=B"EO7KEL,jƅ^A-yQdG $#I6)!0`j{ U[`O:a+|&^3f`#h:A.W`S(r7 Zr{OAԇi;qt<7 /uH g$*p.%E11dPz k3'E.xBB=d P|4Mߝ5x>g lyt ;#*SEB,Jϡp}l$σXt[W(h(VҐVN}}]C &9/'u5Ct+;;z*yQbayy$Oyʄ[|ψD!Dثd?$5/҆c)' AB/l? H 7BB~dw p&}u=Ff#Hh$dx 5c3Hɔv0!k1ďig'W³S:ř$BwH-% D8yJţB CIDATl: 1AݝL%huWw -#9<ώ|d !5OE`ʷ@h*yBsفHqo`W/=LL&x症 }0$"[%Ǥ ӫ$&SnzS*>[&Vڿ@g{_1+QuEF OܻZʂ PC}'HhXkk8>))s|f#q\*7 T .VmY3n:5wܐ o{L[! $ԭC6# p'$5FDuuQ9BYy&gK> }SXeRǩэ1گ|rXA>Z˚ P+=KqϬ' "M7޳= w}:IҪdX!=wbS->1̙)f)Lj}hS?^[uެ^"BB xYVjK& R,iM"z&dEXw?sR*7#mWmg|1ýC ܮ;%iZ|4i:z8 P)~2# $X(x?]83yqlc*$\6;i3}h<-n~SjѾG2>"4ޖ/BB}吂#_h5!(qaHnSA1_Jkú-i,7QWD8i }//<K =[EKD2^s (A@1BBk L ?ݏm]mpSuZ|KlUQO0䴄"41*~0F5mp=L|!wrbAk)sF΀;c֖? ].AĦdtoA@V9ʁ5OZZ #|FRHSסso'ss ,'u[K>'+%FQxVZԂ p>z7L,ZUQb3dzA %0fQj!#pG˫+KMXJ&@ mdec+<0 ];XU7PO j=vA`x 6lm0yJmb##OkY2ՅMa F>r`'$P2L<IeVg!G7UeCsp1 hZBtt)BʯbzƥcOOvz@ CTg&q.٥  lJنuG6k3ȠpPe3"AkEJA 2TAӆmJwBxi%'5$zȧ>ʠ^Pqܚtd/! $T> lKۉՇ7b aGp@0IѱQ24aҝ_tا134)ӤB;ɫ]HhBڲ+gA@H|<A@c۰,ݿ nElf1H7錐P7.%;I*rM$ϧFij"~ C$RB$"I!WTL!nEAA@/Xiwr,WlH4#@z}vGb\tyTiAyHvK}m+-"v+)YId'uuq|"6aM!9"rAꋷ.gj!xJ,X~F.M"k ݨ?!!%=hI zTcӇdZEC-X]UL;mvèVCME@Hh+ "B$tXIeWC@QؾRBBu@zuhiLϞ b"F&H3fz)B'9%Vؘp I(Yn@~:TGcgW/D`{n!ݢ^nBوH*r2S>ijA:Z$mh I12BcaʢW62_י ߛrs䜙lڌ^N %6XVht௃J,1Z&On߼IBKL|!qrJA@8K)'[Rwbg^R5BXmVKBBX<DE|I(zVlK>r-y6+9{Itv)ݑilA5P{ Bbb`諝w:*'F%۴V?jYIERv?bl9{,#vflNz?>T^Xf7}uZ݀DG 4 cZ 9"I}  pTU_Q A)* +bņ QGQ!w 6Fb/c6l =@aw7l}&{uk'L@"Th@ (9A}hy4;W>&?'?Ϭ+ N "͚a͂f() "& GuQ fUQӥAS*ٴV^5ϊWh⢤b6VYUis*m…Af3k )m`Qڼ3ZZB@"4.OR~Ėvrf,oF Jh.}H^?}~b.tZR /[q0-٪42/EVH@"T_9r-(" " " " r۷6d֭[/^l+QL Y7;" "kUUUV\\lͽ^y{=w}w6t\o-71KrZĊDh%8qUVVvm˥j>:uԾ}{0aBb=zYg4iR^[hX _D_|~7n-Xy@ ^MYY=36j(fm"mv}ǭW^iEnfo|Iv7ԧD@ DhA| 6~5k&O?dGu!h8ZhaSLI[W_}e￿3&':#8]&"PFm~kF4l]tqou.jP6`_cǎF:vA#D za}qcx.@4\x;$ˉC|]z饉:Țz֮];cɚzɓ';ߊ?+~ܹs:)7]%"CK,n>B[&M㏷֮]mWvϷHSOڨQ#СxuԌJ+#}*%gyf~E@ꑀDh=խE@E'>:iӛlqv1k~:iӦ_|ᢩy&D^wih v;sLk֬Yb;wh۴i^1? c;DѺ:+65h^m"t=l K=駟Ɂ 9{g}5w}}#]$ ZtuoH%R~wۆnX/ mQmw6mY|H 49+J4-a~m%D@G@"4~T,ISZMF,wyV[`cǎMh":_~eר&Bi ħ˼ܚ7onkkZC$>ֲeKꪫlV[)Wo?SNqYߟDN͛{Z\{wY~Cz }PDa H6,."Nj]w?([%y.쒴s!אFZ#8žz)DH.Q_@{Ֆf;{NsbQصkW~5Zii=htw8P4Rq|8[ .p^sv;`(b-l.ω40~Z^D  ̞=ۉ?jESu]o߾F1z"4(Q 믶>nq OӕW^itg2"N: Q" D .L5< <خjbsӐ(/{ݟ#{L>53!iBlܸqҺ'|.B<]SآEp|nls"  L@"\O?4aD=܌N0t1,VZe7|D e P'"˙׫r7D#il&s<1$= Awg'~_uUk8qvǺF,:fp .K ql:t]Z e"  ] NGBX#.袴;$SɤY-|]'Ұq$ݍ! k1SOS_IDsi0Z}]TSیBcnEriB$M,U gN ڣG9r8}&_"2γNE@@DF1D31ãVk$Z!ڶm[[>sD8wF=E|衇ޛh' >a Ɉen@GYވK.$wDn}7{XnFQGfqls=͌u)Y(..> "?$BYh'" #PVVfC q{a7HQ_s5OD+̗^z)85/AkT}7wwygJsvj>V'ZqD3Q,vcr\M+ZNr rDGԓO>i{챇u]Fw}֭[ #MϹԖr1>21›rRD9ݯ(0a„H"P$B  E@_ki;%M#v3fpuLݧ~s=gDq-QO JzP H]NGC!Da8>?f̘DCSx>jPw3|p;ebcbO?byذa.N){FZY/[NBDU`}jUi˜CF}LڃD@Dh=mHD!k 1rv*G.bOmLD,i|E>~Asqq"м8ҥKa7m$§2&tieMZNCu/"=`^RRNcDdzD6 bMS_lphM;;])9%/#+b40&FFD3-IϋKRD"^}U(7R:uJ:ɿ("RO"NDH%Z G/?錈G42p?JGF#yݻ8ELbt#d_OhӧO߃:V`e" $ ]ˆ7#7$N({Ν]$2]G|:)١C7k424+C{3D!}-%"95;3'f, n}9Eij7Ҡ~dRE\`sih2hsӮE@"DapigNJF%t%Du/n$JzϚ#OQ { PO.]bF/(I2hsӮE@"F6H&*Imlp= ^4m X" @@"4 " "P+Q4֛KSV| H3'u/ZG+gxrQD@@3itg|"PH$B iW(Ǝkݻw'ڶ' @`H-:π@ HFi" "{mH'ύDDh<X9r8zh7J&"}@D@bMI(ƙ͛7rN Dh 🐘 --> --- ## Introduction This _Test Suite_ uses Ansible to deploy docker containers and typical architectures. It tends to support the 2 following use-cases. ### Use Case 1 - Directly-attached shared storage ![Shared storage](../docs/img/shared-storage.png) ### Use Case 2 - Dedicated repository host ![Repository host](../docs/img/with-repo-host.png) --- ## GitHub Actions [GitHub Actions](../.github/workflows/main.yml) are testing: * **Shared storage**: PG 16, Rocky Linux 9, using pgBackRest PGDG packages * **Repository host**: PG 16, Ubuntu 22.04, using pgBackRest PGDG packages --- ## Vagrant To be able to run the tests manually, first initialize the virtual machine with `make init`. * Deploy Use-Case 1 (shared-storage) and run the activity script: `make ACTIVITY=true uc1` * Deploy Use-Case 2 (with-repo-host) and run the activity script: `make ACTIVITY=true uc2` To build pgBackRest from sources, use `uc1_full` or `uc2_full` make targets. To install pgBackRest and **check_pgbackrest** using PGDG packages, without deploying Icinga2, use `uc1_light` or `uc2_light` make targets. ### Change the test profile Add `PROFILE=xxx` to the make command. Available profiles: `d11pg`, `u20pg`, `ro8pg`, `ro9pg`. ### Change the pgBackRest repository type Add `PGBR_REPO_TYPE=xxx` to the make command. Available types: `azure`, `s3`, `multi`, `posix`. When setting `multi` repository, both `s3` and `azure` will be used. When setting `posix` repository, the repository path will be automatically adjusted to `/shared/repo1` where */shared* is a shared volume between the docker containers. ### Icinga2 To interact with Icinga2, the easiest way is to use the API: ```bash # Login to the vagrant box $ vagrant ssh # Reschedule check_pgbackrest checks $ curl -k -s -u 'icinga2-director:anyPassWord' -H 'Accept: application/json' -X POST \ 'https://localhost:5665/v1/actions/reschedule-check' \ -d '{ "type": "Service", "filter": "match(pattern,service.name)", "filter_vars": { "pattern": "pgbackrest*" }, "pretty": true }' |jq # Get check_pgbackrest checks status $ curl -k -s -u 'icinga2-director:anyPassWord' -H 'Accept: application/json' -X GET \ 'https://localhost:5665/v1/objects/services' \ -d '{ "filter": "match(pattern,service.name)", "filter_vars": { "pattern": "pgbackrest*" } }' |jq ``` ### Cleaning Before changing the `PROFILE` to deploy a new architecture, remove the docker containers and cluster directory using `make PROFILE=xxx clean_ci`. To remove the vagrant virtual machine: `make clean_vm`. check_pgbackrest-REL2_4/tests/VALIDATION.md000066400000000000000000000026571464170754600204330ustar00rootroot00000000000000# Validation process First of all, initialize the virtual machine: ```bash time make init ``` ## PostgreSQL ```bash # Directly-attached shared storage - Rocky 8 - pgBackRest packages, multi-repositories time make ACTIVITY=true PROFILE=ro8pg PGBR_REPO_TYPE=multi uc1 make PROFILE=ro8pg clean_ci # Dedicated repository host - Rocky 8 - pgBackRest packages time make ACTIVITY=true PROFILE=ro8pg uc2 make PROFILE=ro8pg clean_ci # Directly-attached shared storage - Rocky 9 - pgBackRest packages time make ACTIVITY=true PROFILE=ro9pg uc1 make PROFILE=ro9pg clean_ci # Dedicated repository host - Rocky 9 - pgBackRest packages, multi-repositories time make ACTIVITY=true PROFILE=ro9pg PGBR_REPO_TYPE=multi uc2 make PROFILE=ro9pg clean_ci # Directly-attached shared storage - Debian 11 - pgBackRest packages, multi-repositories time make ACTIVITY=true PROFILE=d11pg PGBR_REPO_TYPE=multi uc1 make PROFILE=d11pg clean_ci # Dedicated repository host - Debian 11 - pgBackRest packages time make ACTIVITY=true PROFILE=d11pg uc2 make PROFILE=d11pg clean_ci # Directly-attached shared storage - Ubuntu 22.04 - pgBackRest packages time make ACTIVITY=true PROFILE=u22pg uc1 make PROFILE=u22pg clean_ci # Dedicated repository host - Ubuntu 22.04 - pgBackRest packages, multi-repositories time make ACTIVITY=true PROFILE=u22pg PGBR_REPO_TYPE=multi uc2 make PROFILE=u22pg clean_ci ``` * To build pgBackRest from sources, use `uc1_full` of `uc2_full` make target check_pgbackrest-REL2_4/tests/Vagrantfile000066400000000000000000000065141464170754600206400ustar00rootroot00000000000000require 'yaml' if File.file?('vagrant.yml') and ( custom = YAML.load_file('vagrant.yml') ) pgbackrest_git_url = custom['pgbackrest_git_url'] if custom.has_key?('pgbackrest_git_url') pgbackrest_git_branch = custom['pgbackrest_git_branch'] if custom.has_key?('pgbackrest_git_branch') end Vagrant.configure(2) do |config| config.vm.provider :virtualbox do |vb| vb.memory = 4096 vb.cpus = 4 vb.name = "check_pgbackrest-docker-host" end config.vm.box = "bento/ubuntu-22.04" config.ssh.insert_key = false # mount check_pgbackrest path for development testing config.vm.synced_folder "..", "/check_pgbackrest" config.vm.provision "shell", inline: <<-SHELL #----------------------------------------------------------------------------------------------------------------------- echo 'Extend disk space' && date lvextend -l +100%FREE /dev/ubuntu-vg/ubuntu-lv resize2fs /dev/mapper/ubuntu--vg-ubuntu--lv #----------------------------------------------------------------------------------------------------------------------- echo 'Use BE ubuntu mirrors' && date sed -E -i 's#http://[^\s]*archive\.ubuntu\.com/ubuntu#http://be.archive.ubuntu.com/ubuntu#g' /etc/apt/sources.list #----------------------------------------------------------------------------------------------------------------------- echo 'Install Docker' && date curl -fsSL https://get.docker.com | sh usermod -aG docker vagrant #----------------------------------------------------------------------------------------------------------------------- echo 'Install Perl modules' && date export DEBIAN_FRONTEND=noninteractive apt-get install -y libyaml-libyaml-perl jq SHELL config.vm.provision "shell", privileged: false, inline: <<-SHELL #----------------------------------------------------------------------------------------------------------------------- echo 'Install Ansible' && date export DEBIAN_FRONTEND=noninteractive sudo -E apt-get install -y python3-pip python3-venv python3 -m pip install --user pipx python3 -m pipx ensurepath python3 -m pipx install 'ansible-core<2.17' #ansible-core 2.16 is needed for RHEL8 compatibility SHELL # Execute CI script in Vagrant environment config.vm.provision "exec-ci", privileged: false, type: "shell", path: 'vagrant.sh', env: { "ACTIVITY" => ENV['ACTIVITY'], "ARCH" => ENV['ARCH'], "EXTRA" => ENV['EXTRA'], "PGBR_BUILD" => ENV['PGBR_BUILD'], "PGBR_REPO_TYPE" => ENV['PGBR_REPO_TYPE'], "PROFILE" => ENV['PROFILE'], "pgbackrest_git_url" => pgbackrest_git_url, "pgbackrest_git_branch" => pgbackrest_git_branch }, run: 'never' # Clean a specific cluster in Vagrant environment $clean_script = <<-SCRIPT cd /vagrant echo "PROFILE = '$PROFILE'" source profile.d/$PROFILE.profile source profile.d/vagrant.profile sh run.sh -C -c "$CLPATH/$CLNAME" SCRIPT config.vm.provision "clean-ci", privileged: false, type: 'shell', inline: $clean_script, env: { "PROFILE" => ENV['PROFILE'] }, run: 'never' end check_pgbackrest-REL2_4/tests/architectures/000077500000000000000000000000001464170754600213125ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/architectures/shared-storage/000077500000000000000000000000001464170754600242225ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/architectures/shared-storage/config.yml000066400000000000000000000006311464170754600262120ustar00rootroot00000000000000--- cluster_name: shared-storage platform: docker docker: image_name: rockylinux:9 exposed_ports: - '22' - '5432' cluster_vars: pg_version: 16 pgbackrest_repo_type: s3 instances: - name: node1 ansible_group: primary pgbackrest: true - name: node2 ansible_group: standby upstream: node1 pgbackrest: true - name: node3 ansible_group: standby upstream: node1 pgbackrest: true check_pgbackrest-REL2_4/tests/architectures/with-repo-host/000077500000000000000000000000001464170754600242035ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/architectures/with-repo-host/config.yml000066400000000000000000000010311464170754600261660ustar00rootroot00000000000000--- cluster_name: with-repo-host platform: docker docker: image_name: rockylinux:9 exposed_ports: - '22' - '5432' cluster_vars: pg_version: 16 instances: - name: bck-host ansible_group: pgbackrest_repo_host - name: node1 ansible_group: primary pgbackrest: true pgbackrest_repo_host: bck-host - name: node2 ansible_group: standby upstream: node1 pgbackrest: true pgbackrest_repo_host: bck-host - name: node3 ansible_group: standby upstream: node1 pgbackrest: true pgbackrest_repo_host: bck-host check_pgbackrest-REL2_4/tests/ci.sh000066400000000000000000000012541464170754600173760ustar00rootroot00000000000000#!/usr/bin/env bash set -o errexit set -o nounset cd "$(dirname "$0")" perl config.pl --force --architecture "$ARCH" \ --cluster-path "$CLPATH" --cluster-name "$CLNAME" \ --db-type "$DBTYPE" --db-version "$DBVERSION" \ --docker-image "$DOCKERI" --extra-vars "$EXTRA_VARS" sed -i "s/node1/$CLNAME-1/g" "$CLPATH/$CLNAME/config.yml" sed -i "s/node2/$CLNAME-2/g" "$CLPATH/$CLNAME/config.yml" sed -i "s/node3/$CLNAME-3/g" "$CLPATH/$CLNAME/config.yml" sed -i "s/bck-host/$CLNAME-bck/g" "$CLPATH/$CLNAME/config.yml" echo "ACTIVITY = '$ACTIVITY'" export ACTIVITY=$ACTIVITY echo "RUN_ARGS = '$RUN_ARGS'" sh run.sh -c "$CLPATH/$CLNAME" "$RUN_ARGS" check_pgbackrest-REL2_4/tests/config.pl000066400000000000000000000127621464170754600202570ustar00rootroot00000000000000#!/usr/bin/env perl #################################################################################################################################### # Perl includes #################################################################################################################################### use strict; use warnings; use File::Basename qw(dirname); use File::Path qw(make_path); use Getopt::Long qw(GetOptions); use List::Util qw(any); use Pod::Usage qw(pod2usage); use YAML::XS qw(LoadFile DumpFile); #################################################################################################################################### # Global vars #################################################################################################################################### my $dbTypes = { 'PG' => ['12', '13', '14', '15', '16', '17'] }; my @supportedDockerImages = ('debian:11', 'ubuntu:22.04', 'rockylinux:8', 'rockylinux:9'); #################################################################################################################################### # Usage #################################################################################################################################### =head1 NAME config.pl - generate configuration file =head1 SYNOPSIS config.pl [options] Cluster Options: --cluster-name cluster name (a directory named after this name will be created in cluster path) --cluster-path cluster path --extra-vars additional cluster variables ('key=value key2=value2' format) Test Options: --architecture target architecture --db-type database type ('PG') --db-version version of database Docker Options: --docker-image docker base image name ('debian:11', 'ubuntu:22.04', 'rockylinux:8', 'rockylinux:9') General Options: --help display usage and exit --force force configuration file update =cut #################################################################################################################################### # Command line parameters #################################################################################################################################### my $bHelp = 0; my $bForce = 0; my $strArchitecture; my $strClusterName; my $strClusterPath; my $strExtraVars; my $strDbType; my $strDbVersion; my $strDockerImage; GetOptions( 'architecture=s' => \$strArchitecture, 'cluster-name=s' => \$strClusterName, 'cluster-path=s' => \$strClusterPath, 'db-type=s' => \$strDbType, 'db-version=s' => \$strDbVersion, 'docker-image=s' => \$strDockerImage, 'extra-vars=s' => \$strExtraVars, 'force' => \$bForce, 'help' => \$bHelp, ) or pod2usage( -exitval => 127 ); pod2usage() if $bHelp; #################################################################################################################################### # Run in eval block to catch errors #################################################################################################################################### eval{ print("-------------------PROCESS START-------------------\n"); print("INFO: config begin\n"); die("cluster path must be provided") unless defined($strClusterPath); die("db type '$strDbType' not supported") if (defined($strDbType) and !defined($dbTypes->{$strDbType})); if(defined($strDbVersion)){ die("db type must be provided when db version is provided") unless defined($strDbType); die("db type '$strDbType', version '$strDbVersion' not supported") unless (any { $_ eq $strDbVersion } @{$dbTypes->{$strDbType}}); } die("docker image '$strDockerImage' not supported") unless !defined($strDockerImage) or (any { $_ eq $strDockerImage } @supportedDockerImages); # Validate architecture and load configuration file die("architecture must be provided") unless defined($strArchitecture); my $archConfFile = dirname($0)."/architectures/".$strArchitecture."/config.yml"; die("architecture '$strArchitecture' not found") unless (-f $archConfFile); print("INFO: load '$archConfFile'\n"); my $archConfig = LoadFile($archConfFile); # Modify cluster configuration $archConfig->{cluster_name} = $strClusterName if defined($strClusterName); $archConfig->{cluster_vars}->{pg_type} = $strDbType if defined($strDbType); $archConfig->{cluster_vars}->{pg_version} = $strDbVersion if defined($strDbVersion); $archConfig->{docker}->{image_name} = $strDockerImage if defined($strDockerImage); # Add extra cluster vars if(defined $strExtraVars and length $strExtraVars){ $strExtraVars =~ s/^\s+|\s+$//g; foreach(split(/\s+/, $strExtraVars)){ my ($key, $value) = split(/=/, $_); die("extra variables format must be 'key=value'") unless defined($key) and defined($value); $archConfig->{cluster_vars}->{$key} = $value; } } # Create cluster directory my $strClusterDir = $strClusterPath."/".$strClusterName; die("cluster directory already exists") if (-e $strClusterDir and !$bForce); if(! -e $strClusterDir){ print("INFO: create cluster directory '$strClusterDir'\n"); make_path($strClusterDir, { verbose => 1 }) or die("failed to create '$strClusterDir'"); } print("INFO: write cluster configuration file\n"); DumpFile($strClusterDir."/config.yml", $archConfig) or die("failed to write cluster configuration file"); # Exit with success exit 0; }; die("ERROR: test execution failed - $@\n") if $@; check_pgbackrest-REL2_4/tests/platforms/000077500000000000000000000000001464170754600204545ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/platforms/azure/000077500000000000000000000000001464170754600216025ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/platforms/azure/blob-create-container.py000077500000000000000000000020661464170754600263220ustar00rootroot00000000000000#!/usr/bin/python import argparse, os, urllib3 from azure.storage.blob import BlobServiceClient, __version__ try: print("Azure Blob Storage v" + __version__) # Parse arguments parser = argparse.ArgumentParser() parser.add_argument("--container_name", "-c", help="container name to create") args = parser.parse_args() # Get Connection String from environment connect_str = os.getenv('AZURE_STORAGE_CONNECTION_STRING') urllib3.disable_warnings() blob_service_client = BlobServiceClient.from_connection_string(connect_str, connection_verify=False) # Create the container if args.container_name: container_client = blob_service_client.get_container_client(args.container_name) if container_client.exists(): print("Container %s already exists..." % args.container_name) else: print("Container name to create: %s" % args.container_name) container_client = blob_service_client.create_container(args.container_name) except Exception as ex: print('Exception:') print(ex)check_pgbackrest-REL2_4/tests/platforms/common/000077500000000000000000000000001464170754600217445ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/platforms/common/inventory/000077500000000000000000000000001464170754600240015ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/platforms/common/inventory/docker.j2000066400000000000000000000002241464170754600255030ustar00rootroot00000000000000--- all: hosts: {% for h in instances %} {% if platform == 'docker' %} {{ h.name }}: ansible_connection: docker {% endif%} {% endfor %} check_pgbackrest-REL2_4/tests/platforms/common/inventory/inventory.j2000066400000000000000000000007501464170754600262750ustar00rootroot00000000000000--- all: children: {% for g in instances_groups %} {{ g }}: hosts: {% for h in instances %} {% if h.ansible_group == g %} {{ h.name }}: ansible_python_interpreter: /usr/bin/python3 {% endif%} {% endfor %} {% endfor %} {% if cluster_vars.deploy_icinga2 is defined and cluster_vars.deploy_icinga2 | bool %} icinga2: hosts: {{ cluster_name }}-icinga2: ansible_connection: docker ansible_python_interpreter: auto {% endif%} check_pgbackrest-REL2_4/tests/platforms/common/inventory/write.yml000066400000000000000000000046501464170754600256630ustar00rootroot00000000000000--- - name: Ensure that the cluster's inventory directories exist file: path: "{{ cluster_dir }}/{{ item }}" state: directory loop: - inventory - inventory/host_vars - inventory/group_vars - name: Create host_vars subdirectories file: path: "{{ cluster_dir }}/inventory/host_vars/{{ item.name }}" state: directory loop: "{{ instances | flatten(levels=1) }}" loop_control: label: >- {{ item.name }} - name: Get ansible groups set_fact: instances_groups: "{{ instances_groups | default([]) | union([ item.ansible_group ]) }}" loop: "{{ instances | flatten(levels=1) }}" loop_control: label: >- {{ item.ansible_group }} - name: Write docker static inventory file template: src: docker.j2 dest: "{{ cluster_dir }}/inventory.docker.yml" mode: 0644 when: platform == 'docker' - name: Write inventory file template: src: inventory.j2 dest: "{{ cluster_dir }}/inventory/inventory.yml" mode: 0644 - name: Write group_vars copy: content: | {{ group_vars|to_nice_yaml(indent=2) }} dest: "{{ group_dir }}/{{ file_name }}" mode: 0644 force: yes vars: ansible_ssh_private_key_file: "{{ cluster_dir }}/{{ ssh_key_file }}" file_name: "all.yml" group_dir: "{{ cluster_dir }}/inventory/group_vars" group_vars: > {{ cluster_vars|combine({ 'cluster_name': cluster_name, 'ansible_ssh_private_key_file': ansible_ssh_private_key_file, }) }} - name: Write instance variables for hosts copy: content: | {{ host_vars|to_nice_yaml(indent=2) }} dest: "{{ host_dir }}/{{ file_name }}" mode: 0644 force: yes vars: file_name: "instance_vars.yml" host_dir: "{{ cluster_dir }}/inventory/host_vars/{{ item.name }}" host_vars: "{{ item.vars }}" loop: "{{ instance_vars|flatten(levels=1) }}" loop_control: label: >- {{ item.name }} when: item.vars | length > 0 - name: Transform upstream property to upstream_node_private_ip ansible.builtin.lineinfile: path: "{{ host_dir }}/{{ file_name }}" regexp: '^upstream: ' line: "upstream_node_private_ip: {{ private_ip_list[item.vars.upstream] }}" vars: file_name: "instance_vars.yml" host_dir: "{{ cluster_dir }}/inventory/host_vars/{{ item.name }}" when: item.vars.upstream is defined loop: "{{ instance_vars|flatten(levels=1) }}" loop_control: label: >- {{ item.name }} check_pgbackrest-REL2_4/tests/platforms/common/provision.yml000066400000000000000000000017031464170754600245200ustar00rootroot00000000000000--- - name: Run ssh-keygen command: ssh-keygen -P "" -f "{{ ssh_key_file }}" args: chdir: "{{ cluster_dir }}" creates: "{{ ssh_key_file }}" - name: Ensure that the cluster's certs directory exist file: path: "{{ cluster_dir }}/certs" state: directory - name: Generate an OpenSSL private key - 2048 bits community.crypto.openssl_privatekey: path: "{{ cluster_dir }}/certs/{{ cluster_name }}.key" size: 2048 - name: Generate an OpenSSL Certificate Signing Request community.crypto.openssl_csr: path: "{{ cluster_dir }}/certs/{{ cluster_name }}.csr" privatekey_path: "{{ cluster_dir }}/certs/{{ cluster_name }}.key" - name: Generate a Self Signed OpenSSL certificate community.crypto.x509_certificate: provider: selfsigned path: "{{ cluster_dir }}/certs/{{ cluster_name }}.crt" privatekey_path: "{{ cluster_dir }}/certs/{{ cluster_name }}.key" csr_path: "{{ cluster_dir }}/certs/{{ cluster_name }}.csr"check_pgbackrest-REL2_4/tests/platforms/deprovision.yml000066400000000000000000000011561464170754600235430ustar00rootroot00000000000000--- - name: Deprovision cluster hosts: localhost tasks: - name: Require cluster directory to be specified assert: msg: "No cluster directory specified" that: - cluster_dir is defined and cluster_dir != '' - import_tasks: load-config.yml - assert: msg: "Unsupported platform: '{{ platform }}'" that: - platform is defined - platform in _available_platforms vars: _available_platforms: - 'docker' - include_tasks: "{{ platform }}/deprovision.yml" - include_tasks: "docker/deprovision-repository-types.yml"check_pgbackrest-REL2_4/tests/platforms/docker/000077500000000000000000000000001464170754600217235ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/platforms/docker/build_all_images.yml000066400000000000000000000010471464170754600257240ustar00rootroot00000000000000--- - name: Build all systemd images hosts: localhost tasks: - docker_image: name: "systemd/{{ item.base }}:{{ item.tag }}" state: present source: build build: path: systemd dockerfile: "{{ item.base }}.Dockerfile" pull: no args: BASE_IMAGE: "{{ item.base }}:{{ item.tag }}" loop: - { base: 'debian', tag: '11' } - { base: 'ubuntu', tag: '22.04' } - { base: 'rockylinux', tag: '8' } - { base: 'rockylinux', tag: '9' } check_pgbackrest-REL2_4/tests/platforms/docker/deprovision-repository-types.yml000066400000000000000000000017721464170754600303750ustar00rootroot00000000000000--- - name: Deprovision MinIO docker container docker_container: name: "{{ cluster_name }}-minio" state: absent container_default_behavior: compatibility when: > cluster_vars.pgbackrest_repo_type is defined and (cluster_vars.pgbackrest_repo_type == "s3" or cluster_vars.pgbackrest_repo_type == "multi") - name: Deprovision Azurite docker container docker_container: name: "{{ cluster_name }}-azurite" state: absent container_default_behavior: compatibility when: > cluster_vars.pgbackrest_repo_type is defined and (cluster_vars.pgbackrest_repo_type == "azure" or cluster_vars.pgbackrest_repo_type == "multi") - name: Deprovision Icinga2 docker container docker_container: name: "{{ cluster_name }}-icinga2" state: absent container_default_behavior: compatibility when: cluster_vars.deploy_icinga2 is defined and cluster_vars.deploy_icinga2 | bool - name: Delete docker network docker_network: name: "network_{{ cluster_name }}" state: absentcheck_pgbackrest-REL2_4/tests/platforms/docker/deprovision.yml000066400000000000000000000005451464170754600250130ustar00rootroot00000000000000--- - name: Deprovision docker containers docker_container: name: "{{ item.name }}" state: absent container_default_behavior: compatibility loop: "{{ instances | flatten(levels=1) }}" loop_control: label: >- {{ item.name }} - name: Delete docker network docker_network: name: "network_{{ cluster_name }}" state: absentcheck_pgbackrest-REL2_4/tests/platforms/docker/docker_container.yml000066400000000000000000000024351464170754600257630ustar00rootroot00000000000000--- - name: Provision docker container "{{ item.name }}" community.docker.docker_container: name: "{{ item.name }}" hostname: "{{ item.name }}" image: "systemd/{{ docker.image_name }}" state: started pull: false detach: true exposed_ports: "{{ _exposed_ports }}" published_ports: "{{ _exposed_ports }}" networks: - name: "network_{{ cluster_name }}" networks_cli_compatible: true volumes: - "/sys/fs/cgroup:/sys/fs/cgroup:rw" - "{{ cluster_dir }}/shared:/shared:z" cgroupns_mode: host privileged: true tmpfs: - "/tmp" - "/run" - "/run/lock" container_default_behavior: compatibility network_mode: default restart_policy: unless-stopped vars: _exposed_ports: "{{ docker.exposed_ports|default(['22','5432']) }}" register: docker_container_register - set_fact: docker_container_results: "{{ docker_container_results|default([])|union([ dc|combine({ 'item': item|combine({ 'private_ip': nw.Networks[nw_name].IPAddress, 'ansible_host': nw.Networks[nw_name].IPAddress, }) }) ]) }}" vars: dc: "{{ docker_container_register.container }}" nw: "{{ dc.NetworkSettings }}" nw_name: "network_{{ cluster_name }}" check_pgbackrest-REL2_4/tests/platforms/docker/provision-repository-types.yml000066400000000000000000000116431464170754600300620ustar00rootroot00000000000000--- - name: Create docker network docker_network: name: "network_{{ cluster_name }}" state: present - name: Ensure that the cluster's MinIO data directory exists with default bucket file: path: "{{ cluster_dir }}/{{ item }}" state: directory loop: - "minio" - "minio/data" when: > cluster_vars.pgbackrest_repo_type is defined and (cluster_vars.pgbackrest_repo_type == "s3" or cluster_vars.pgbackrest_repo_type == "multi") - name: Provision MinIO docker container community.docker.docker_container: image: minio/minio name: "{{ cluster_name }}-minio" state: started restart_policy: always command: server /data --address :443 exposed_ports: "443" published_ports: "443" networks: - name: "network_{{ cluster_name }}" volumes: - "{{ cluster_dir }}/minio/data:/data:z" - "{{ cluster_dir }}/certs/{{ cluster_name }}.crt:/root/.minio/certs/public.crt:z" - "{{ cluster_dir }}/certs/{{ cluster_name }}.key:/root/.minio/certs/private.key:z" networks_cli_compatible: yes network_mode: default container_default_behavior: compatibility env: MINIO_ROOT_USER: "accessKey" MINIO_ROOT_PASSWORD: "superSECRETkey" register: minio_docker_container_register when: > cluster_vars.pgbackrest_repo_type is defined and (cluster_vars.pgbackrest_repo_type == "s3" or cluster_vars.pgbackrest_repo_type == "multi") - name: Waits for MinIO wait_for: host: "{{nw.Networks[nw_name].IPAddress}}" port: 443 delay: 5 vars: dc: "{{ minio_docker_container_register.container }}" nw: "{{ dc.NetworkSettings }}" nw_name: "network_{{ cluster_name }}" when: > cluster_vars.pgbackrest_repo_type is defined and (cluster_vars.pgbackrest_repo_type == "s3" or cluster_vars.pgbackrest_repo_type == "multi") - name: MinIO Python Client API - create bucket command: "python3 {{ playbook_dir }}/minio/create-bucket.py -b bucket" vars: dc: "{{ minio_docker_container_register.container }}" nw: "{{ dc.NetworkSettings }}" nw_name: "network_{{ cluster_name }}" environment: MINIO_ENDPOINT: "{{nw.Networks[nw_name].IPAddress}}" MINIO_ROOT_USER: "accessKey" MINIO_ROOT_PASSWORD: "superSECRETkey" when: > cluster_vars.pgbackrest_repo_type is defined and (cluster_vars.pgbackrest_repo_type == "s3" or cluster_vars.pgbackrest_repo_type == "multi") - name: Ensure that the cluster's Azurite data directory exists file: path: "{{ cluster_dir }}/{{ item }}" state: directory loop: - "azurite" - "azurite/data" when: > cluster_vars.pgbackrest_repo_type is defined and (cluster_vars.pgbackrest_repo_type == "azure" or cluster_vars.pgbackrest_repo_type == "multi") - name: Provision Azurite docker container community.docker.docker_container: image: mcr.microsoft.com/azure-storage/azurite name: "{{ cluster_name }}-azurite" state: started restart_policy: always command: azurite-blob --blobPort 443 --blobHost 0.0.0.0 --cert=/root/public.crt --key=/root/private.key -l /workspace -d /workspace/debug.log exposed_ports: "443" published_ports: "443" networks: - name: "network_{{ cluster_name }}" volumes: - "{{ cluster_dir }}/azurite/data:/workspace:z" - "{{ cluster_dir }}/certs/{{ cluster_name }}.crt:/root/public.crt:ro" - "{{ cluster_dir }}/certs/{{ cluster_name }}.key:/root/private.key:ro" networks_cli_compatible: yes network_mode: default container_default_behavior: compatibility env: AZURITE_ACCOUNTS: "pgbackrest:aF49wnZP" register: azure_docker_container_register when: > cluster_vars.pgbackrest_repo_type is defined and (cluster_vars.pgbackrest_repo_type == "azure" or cluster_vars.pgbackrest_repo_type == "multi") - name: Azure Blob Storage - create container command: "python3 {{ playbook_dir }}/azure/blob-create-container.py -c container" vars: dc: "{{ azure_docker_container_register.container }}" nw: "{{ dc.NetworkSettings }}" nw_name: "network_{{ cluster_name }}" environment: AZURE_STORAGE_CONNECTION_STRING: "DefaultEndpointsProtocol=https;AccountName=pgbackrest;AccountKey=aF49wnZP;BlobEndpoint=https://{{nw.Networks[nw_name].IPAddress}}/pgbackrest;" when: > cluster_vars.pgbackrest_repo_type is defined and (cluster_vars.pgbackrest_repo_type == "azure" or cluster_vars.pgbackrest_repo_type == "multi") - name: Provision Icinga2 docker container community.docker.docker_container: image: jordan/icinga2 name: "{{ cluster_name }}-icinga2" state: started restart_policy: "no" published_ports: - "80:80" - "443:443" - "5665:5665" networks: - name: "network_{{ cluster_name }}" networks_cli_compatible: yes network_mode: default container_default_behavior: compatibility env: ICINGA2_FEATURE_DIRECTOR_PASS: "anyPassWord" when: cluster_vars.deploy_icinga2 is defined and cluster_vars.deploy_icinga2 | bool check_pgbackrest-REL2_4/tests/platforms/docker/provision.yml000066400000000000000000000034141464170754600245000ustar00rootroot00000000000000--- - assert: msg: "Unsupported docker image_name: '{{ docker.image_name }}'" that: docker.image_name in _available_images vars: _available_images: - 'debian:11' - 'ubuntu:22.04' - 'rockylinux:8' - 'rockylinux:9' - name: Build systemd image {{ docker.image_name }} docker_image: name: "systemd/{{ _image_base }}:{{ _image_tag }}" state: present source: build build: path: docker/systemd dockerfile: "{{ _image_base }}.Dockerfile" pull: no args: BASE_IMAGE: "{{ _image_base }}:{{ _image_tag }}" vars: _parts: "{{ docker.image_name.split(':') }}" _image_base: "{{ _parts[0] }}" _image_tag: "{{ _parts[1] }}" - name: Create docker network docker_network: name: "network_{{ cluster_name }}" state: present - name: Ensure that the cluster's default shared directory exist file: path: "{{ cluster_dir }}/shared" state: directory owner: root group: root mode: '1777' become: yes - name: Provision docker containers include_tasks: docker_container.yml loop: "{{ instances | flatten(levels=1) }}" loop_control: label: >- {{ item.name }} - name: Set instance variables set_fact: instance_vars: "{{ instance_vars | default([]) | union([ { 'name': item.item.name, 'vars': item.item } ]) }}" with_items: "{{ docker_container_results }}" loop_control: label: >- {{ item.item.name }} - name: Create private ip list set_fact: private_ip_list: "{{ private_ip_list | default({}) | combine({ item.name: item.vars.private_ip }) }}" when: item.vars.private_ip is defined loop: "{{ instance_vars|flatten(levels=1) }}" loop_control: label: >- {{ item.name }} check_pgbackrest-REL2_4/tests/platforms/docker/systemd/000077500000000000000000000000001464170754600234135ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/platforms/docker/systemd/debian.Dockerfile000066400000000000000000000012621464170754600266270ustar00rootroot00000000000000ARG BASE_IMAGE FROM ${BASE_IMAGE} ENV container docker ENV LC_ALL C ENV DEBIAN_FRONTEND noninteractive RUN apt-get update \ && apt-get install -y python3 systemd systemd-sysv \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* RUN rm -f /lib/systemd/system/multi-user.target.wants/* \ /etc/systemd/system/*.wants/* \ /lib/systemd/system/local-fs.target.wants/* \ /lib/systemd/system/sockets.target.wants/*udev* \ /lib/systemd/system/sockets.target.wants/*initctl* \ /lib/systemd/system/sysinit.target.wants/systemd-tmpfiles-setup* \ /lib/systemd/system/systemd-update-utmp* VOLUME [ "/sys/fs/cgroup" ] CMD ["/lib/systemd/systemd"] check_pgbackrest-REL2_4/tests/platforms/docker/systemd/rockylinux.Dockerfile000066400000000000000000000013601464170754600276130ustar00rootroot00000000000000ARG BASE_IMAGE FROM ${BASE_IMAGE} ENV container docker RUN dnf install -y systemd procps net-tools yum-utils python3.12 python3.12-setuptools glibc-langpack-en RUN dnf -qy module disable postgresql RUN dnf update -y RUN cd /lib/systemd/system/sysinit.target.wants/; \ for i in *; do [ $i = systemd-tmpfiles-setup.service ] || rm -f $i; done RUN rm -f /lib/systemd/system/multi-user.target.wants/* \ /etc/systemd/system/*.wants/* \ /lib/systemd/system/local-fs.target.wants/* \ /lib/systemd/system/sockets.target.wants/*udev* \ /lib/systemd/system/sockets.target.wants/*initctl* \ /lib/systemd/system/basic.target.wants/* \ /lib/systemd/system/anaconda.target.wants/* VOLUME [ "/sys/fs/cgroup" ] CMD ["/usr/sbin/init"] check_pgbackrest-REL2_4/tests/platforms/docker/systemd/ubuntu.Dockerfile000066400000000000000000000016111464170754600267250ustar00rootroot00000000000000ARG BASE_IMAGE FROM ${BASE_IMAGE} ENV container docker ENV LC_ALL C ENV DEBIAN_FRONTEND noninteractive RUN sed -i 's/# deb/deb/g' /etc/apt/sources.list RUN apt-get update \ && apt-get install -y systemd systemd-sysv \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* RUN cd /lib/systemd/system/sysinit.target.wants/ \ && ls | grep -v systemd-tmpfiles-setup | xargs rm -f $1 RUN rm -f /lib/systemd/system/multi-user.target.wants/* \ /etc/systemd/system/*.wants/* \ /lib/systemd/system/local-fs.target.wants/* \ /lib/systemd/system/sockets.target.wants/*udev* \ /lib/systemd/system/sockets.target.wants/*initctl* \ /lib/systemd/system/basic.target.wants/* \ /lib/systemd/system/anaconda.target.wants/* \ /lib/systemd/system/plymouth* \ /lib/systemd/system/systemd-update-utmp* VOLUME [ "/sys/fs/cgroup" ] CMD ["/lib/systemd/systemd"]check_pgbackrest-REL2_4/tests/platforms/load-config.yml000066400000000000000000000011151464170754600233570ustar00rootroot00000000000000--- - name: Set full path to cluster_dir and config_file set_fact: config_file: "{{ cluster_dir }}/{{ file }}" vars: file: >- {{ config|default('config.yml') }} - name: Load cluster configuration file include_vars: "{{ config_file }}" - name: Ensure cluster_name is specified assert: msg: "Please define cluster_name in {{ config_file }}" that: - cluster_name is defined - cluster_name != '' - name: Set ssh_key_file set_fact: ssh_key_file: >- {{ ssh_key_file|default(_default) }} vars: _default: "id_{{ cluster_name|lower }}"check_pgbackrest-REL2_4/tests/platforms/minio/000077500000000000000000000000001464170754600215675ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/platforms/minio/create-bucket.py000077500000000000000000000020201464170754600246540ustar00rootroot00000000000000#!/usr/bin/python import argparse, os, urllib3 from minio import Minio from minio.error import S3Error def main(): print("MinIO Python Client API") # Parse arguments parser = argparse.ArgumentParser() parser.add_argument("--bucket", "-b", help="bucket name to create") args = parser.parse_args() # Create HTTPS client connection without certificate verification urllib3.disable_warnings() client = Minio( os.getenv('MINIO_ENDPOINT'), os.getenv('MINIO_ROOT_USER'), os.getenv('MINIO_ROOT_PASSWORD'), secure=True, http_client=urllib3.PoolManager(cert_reqs='CERT_NONE') ) # Create the container if args.bucket: if client.bucket_exists(args.bucket): print("Bucket %s already exists..." % args.bucket) else: print("Bucket name to create: %s" % args.bucket) client.make_bucket(args.bucket) if __name__ == "__main__": try: main() except S3Error as exc: print("error occurred.", exc) check_pgbackrest-REL2_4/tests/platforms/provision.yml000066400000000000000000000013121464170754600232240ustar00rootroot00000000000000--- - name: Provision cluster hosts: localhost tasks: - name: Require cluster directory to be specified assert: msg: "No cluster directory specified" that: - cluster_dir is defined and cluster_dir != '' - import_tasks: load-config.yml - assert: msg: "Unsupported platform: '{{ platform }}'" that: - platform is defined - platform in _available_platforms vars: _available_platforms: - 'docker' - include_tasks: "common/provision.yml" - include_tasks: "{{ platform }}/provision.yml" - include_tasks: "docker/provision-repository-types.yml" - include_tasks: common/inventory/write.ymlcheck_pgbackrest-REL2_4/tests/platforms/system-config.yml000066400000000000000000000005441464170754600237710ustar00rootroot00000000000000--- - name: Apply default system configuration hosts: all tasks: - name: Require cluster directory to be specified assert: msg: "No cluster directory specified" that: - cluster_dir is defined and cluster_dir != '' run_once: true - import_tasks: load-config.yml - include_role: name: 'sys'check_pgbackrest-REL2_4/tests/playbooks/000077500000000000000000000000001464170754600204505ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/playbooks/activity.yml000066400000000000000000000063511464170754600230340ustar00rootroot00000000000000--- - name: Simulate activity hosts: all any_errors_fatal: true tasks: - name: Copy regression tests to remote host copy: src: regress/ dest: /tmp/regress/ directory_mode: yes mode: '0755' when: > 'primary' in group_names and (hostvars[inventory_hostname].pgbackrest is defined and hostvars[inventory_hostname].pgbackrest == true) - name: Run check_pgbackrest regression tests command: /usr/bin/env bash /tmp/regress/regression-tests.bash -P /usr/bin 2>&1 |tee /var/log/regression-tests.log environment: PGBIN: "{{ cluster_vars['pg_bin_path'] }}" PGDATABASE: "{{ cluster_vars['pg_database'] }}" PGUNIXSOCKET: "{{ cluster_vars['pg_unix_socket'] }}" PGUSER: "{{ cluster_vars['pg_owner'] }}" STANZA: "{{ cluster_vars['cluster_name'] }}" PGBR_HOST: "{{ cluster_vars['pgbackrest_repo_host'] | default(None) }}" PGBR_USER: "{{ cluster_vars['pgbackrest_user'] }}" PGBR_REPO_TYPE: "{{ cluster_vars['pgbackrest_repo_type'] }}" SCRIPT_PROFILE: "" vars: cluster_vars: "{{ ansible_local['profile']['global'] }}" register: regress_output when: > 'primary' in group_names and (hostvars[inventory_hostname].pgbackrest is defined and hostvars[inventory_hostname].pgbackrest == true) - name: Regression tests output debug: var=regress_output.stdout_lines when: regress_output.changed - name: Copy activity script to remote host copy: src: scripts/simulate-activity-basic.bash dest: /tmp/simulate-activity-basic.bash mode: '0755' when: > 'primary' in group_names and (hostvars[inventory_hostname].pgbackrest is defined and hostvars[inventory_hostname].pgbackrest == true) - name: Simulate basic activity command: /usr/bin/env bash /tmp/simulate-activity-basic.bash -s 10 -a 10 2>&1 |tee /var/log/simulate-activity-basic.log environment: PGBIN: "{{ cluster_vars['pg_bin_path'] }}" PGDATABASE: "{{ cluster_vars['pg_database'] }}" PGSVC: "{{ cluster_vars['pg_service'] }}" PGUNIXSOCKET: "{{ cluster_vars['pg_unix_socket'] }}" PGUSER: "{{ cluster_vars['pg_owner'] }}" STANZA: "{{ cluster_vars['cluster_name'] }}" PGBR_HOST: "{{ cluster_vars['pgbackrest_repo_host'] | default(None) }}" PGBR_STANDBIES: "{{ cluster_vars['pgbackrest_standbies'] | default(None) }}" PGBR_USER: "{{ cluster_vars['pgbackrest_user'] }}" PGBR_REPO_TYPE: "{{ cluster_vars['pgbackrest_repo_type'] }}" SCRIPT_PROFILE: "" vars: cluster_vars: "{{ ansible_local['profile']['global'] }}" register: basic_activity_output when: > 'primary' in group_names and (hostvars[inventory_hostname].pgbackrest is defined and hostvars[inventory_hostname].pgbackrest == true) - name: Basic activity output debug: var=basic_activity_output.stdout_lines when: basic_activity_output.changed - name: Check Icinga2 services include_role: name: setup_check_pgbackrest tasks_from: icinga2-check.yml vars: reschedule_check_icinga2: true when: deploy_icinga2 is defined and deploy_icinga2 | bool check_pgbackrest-REL2_4/tests/playbooks/deploy.yml000066400000000000000000000034211464170754600224670ustar00rootroot00000000000000--- - name: Deploy cluster hosts: all any_errors_fatal: true tasks: - name: Require cluster directory to be specified assert: msg: "No cluster directory specified" that: - cluster_dir is defined and cluster_dir != '' run_once: true # Set some global facts - set_fact: pg_instance_name: "main" # Install PGDG repositories - include_role: name: setup_repo when: "'icinga2' not in group_names" # Install db server and setup replication - include_role: name: install_dbserver when: "'primary' in group_names or 'standby' in group_names" - include_role: name: init_dbserver when: "'primary' in group_names" - include_role: name: setup_replication when: "'standby' in group_names" # Install pgBackRest and check_pgbackrest - include_role: name: setup_pgbackrest when: > 'pgbackrest_repo_host' in group_names or (hostvars[inventory_hostname].pgbackrest is defined and hostvars[inventory_hostname].pgbackrest == true) - include_role: name: setup_check_pgbackrest when: > 'pgbackrest_repo_host' in group_names or (hostvars[inventory_hostname].pgbackrest is defined and hostvars[inventory_hostname].pgbackrest == true) or 'icinga2' in group_names # Save facts locally for other playbooks - name: Ensure local facts directory exists file: state=directory path="/etc/ansible/facts.d" - name: Save local facts template: src: "profile.fact.j2" dest: "/etc/ansible/facts.d/profile.fact" when: > 'primary' in group_names and (hostvars[inventory_hostname].pgbackrest is defined and hostvars[inventory_hostname].pgbackrest == true) check_pgbackrest-REL2_4/tests/playbooks/regress/000077500000000000000000000000001464170754600221225ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/playbooks/regress/expected/000077500000000000000000000000001464170754600237235ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/playbooks/regress/expected/archives-age-alert-ko.out000066400000000000000000000000271464170754600305250ustar00rootroot00000000000000WAL_ARCHIVES CRITICAL check_pgbackrest-REL2_4/tests/playbooks/regress/expected/archives-age-alert-ok.out000066400000000000000000000000211464170754600305170ustar00rootroot00000000000000WAL_ARCHIVES OK check_pgbackrest-REL2_4/tests/playbooks/regress/expected/archives-ignore-after.out000066400000000000000000000000551464170754600306400ustar00rootroot00000000000000WAL_ARCHIVES UNKNOWN - no archived WAL found check_pgbackrest-REL2_4/tests/playbooks/regress/expected/archives-ignore-before.out000066400000000000000000000000551464170754600310010ustar00rootroot00000000000000WAL_ARCHIVES UNKNOWN - no archived WAL found check_pgbackrest-REL2_4/tests/playbooks/regress/expected/archives-max-archives-check-ko.out000066400000000000000000000000511464170754600323230ustar00rootroot00000000000000max-archives-check-number limit exceeded.check_pgbackrest-REL2_4/tests/playbooks/regress/expected/archives-ok-global.out000066400000000000000000000000211464170754600301160ustar00rootroot00000000000000WAL_ARCHIVES OK check_pgbackrest-REL2_4/tests/playbooks/regress/expected/archives-ok.out000066400000000000000000000000211464170754600266600ustar00rootroot00000000000000WAL_ARCHIVES OK check_pgbackrest-REL2_4/tests/playbooks/regress/expected/archives-repo2-ok.out000066400000000000000000000000211464170754600277050ustar00rootroot00000000000000WAL_ARCHIVES OK check_pgbackrest-REL2_4/tests/playbooks/regress/expected/list.out000066400000000000000000000003561464170754600254330ustar00rootroot00000000000000List of available services: archives Check WAL archives. check_pgb_version Check the version of this check_pgbackrest script. pgbackrest_version Check the version of pgBackRest. retention Check the retention policy. check_pgbackrest-REL2_4/tests/playbooks/regress/expected/retention-age-to-full.out000066400000000000000000000000611464170754600305720ustar00rootroot00000000000000BACKUPS_RETENTION OK - backups policy checks ok check_pgbackrest-REL2_4/tests/playbooks/regress/expected/retention-age-to-oldest-fail.out000066400000000000000000000000641464170754600320360ustar00rootroot00000000000000BACKUPS_RETENTION CRITICAL - backups are too young check_pgbackrest-REL2_4/tests/playbooks/regress/expected/retention-age.out000066400000000000000000000000611464170754600272120ustar00rootroot00000000000000BACKUPS_RETENTION OK - backups policy checks ok check_pgbackrest-REL2_4/tests/playbooks/regress/expected/retention-diff.out000066400000000000000000000000611464170754600273660ustar00rootroot00000000000000BACKUPS_RETENTION OK - backups policy checks ok check_pgbackrest-REL2_4/tests/playbooks/regress/expected/retention-fail.out000066400000000000000000000001601464170754600273710ustar00rootroot00000000000000BACKUPS_RETENTION CRITICAL - not enough full backups: 2 required, backups are too old, full backups are too old check_pgbackrest-REL2_4/tests/playbooks/regress/expected/retention-full-global.out000066400000000000000000000000611464170754600306560ustar00rootroot00000000000000BACKUPS_RETENTION OK - backups policy checks ok check_pgbackrest-REL2_4/tests/playbooks/regress/expected/retention-full-repo2-ko.out000066400000000000000000000000651464170754600310600ustar00rootroot00000000000000BACKUPS_RETENTION CRITICAL - repo2: no valid backups check_pgbackrest-REL2_4/tests/playbooks/regress/expected/retention-full.out000066400000000000000000000000611464170754600274200ustar00rootroot00000000000000BACKUPS_RETENTION OK - backups policy checks ok check_pgbackrest-REL2_4/tests/playbooks/regress/expected/retention-incr.out000066400000000000000000000000611464170754600274110ustar00rootroot00000000000000BACKUPS_RETENTION OK - backups policy checks ok check_pgbackrest-REL2_4/tests/playbooks/regress/regression-tests.bash000077500000000000000000000274431464170754600263160ustar00rootroot00000000000000#!/usr/bin/env bash set -o nounset cd "$(dirname "$0")" # vars PLUGIN_PATH=/usr/lib64/nagios/plugins RESULTS_DIR=/tmp/results SKIP_INIT=false SKIP_REPO2_CLEAR=false usage() { echo "Usage:" echo " -s Skip backups initialization step." echo " -S Skip repo2 clear step when multiple repositories are used." echo " -P Change check_pgbackrest plugin path." echo " -p Use local or remote profile." } while getopts "sSP:p:" o; do case "${o}" in s) SKIP_INIT=true ;; S) SKIP_REPO2_CLEAR=true ;; P) PLUGIN_PATH=${OPTARG} ;; p) SCRIPT_PROFILE=${OPTARG} ;; *) usage 1>&2 exit 1 ;; esac done shift $((OPTIND-1)) if [ -z "$SCRIPT_PROFILE" ]; then SCRIPT_PROFILE="local" if [ ! -z $PGBR_HOST ]; then SCRIPT_PROFILE="remote" fi fi if [ "$SCRIPT_PROFILE" != "local" ] && [ "$SCRIPT_PROFILE" != "remote" ]; then usage fi PYTHON="python3" command -v $PYTHON >/dev/null 2>&1 || { PYTHON="python"; } SSH_ARGS='-o ConnectTimeout=10 -o BatchMode=yes -o StrictHostKeyChecking=no' echo "SKIP_INIT = $SKIP_INIT" echo "PLUGIN_PATH = $PLUGIN_PATH" echo "SCRIPT_PROFILE = $SCRIPT_PROFILE" echo "PGBIN = $PGBIN" echo "PGDATABASE = $PGDATABASE" echo "PGUNIXSOCKET = $PGUNIXSOCKET" echo "PGUSER = $PGUSER" echo "STANZA = $STANZA" if [ ! -z "$PGBR_HOST" ]; then echo "PGBR_USER = $PGBR_USER" echo "PGBR_HOST = $PGBR_HOST" PGBR_HOST=(`$PYTHON -c "print(' '.join($PGBR_HOST))"`) fi echo "PGBR_REPO_TYPE = $PGBR_REPO_TYPE" REPO="" if [ "$PGBR_REPO_TYPE" = "multi" ]; then REPO="--repo=1" echo "...multi repo support, defaulting to repo1" if ! $SKIP_REPO2_CLEAR; then # Clear repo2 echo "...clear repo2" if [ "$SCRIPT_PROFILE" = "local" ]; then sudo -iu $PGUSER pgbackrest --stanza=$STANZA --repo=2 --recurse repo-rm archive/$STANZA sudo -iu $PGUSER pgbackrest --stanza=$STANZA --repo=2 --recurse repo-rm backup/$STANZA sudo -iu $PGUSER pgbackrest --stanza=$STANZA --log-level-console=warn stanza-create else sudo -iu $PGUSER ssh ${SSH_ARGS} ${PGBR_USER}@${PGBR_HOST} "pgbackrest --stanza=$STANZA --repo=2 --recurse repo-rm archive/$STANZA" sudo -iu $PGUSER ssh ${SSH_ARGS} ${PGBR_USER}@${PGBR_HOST} "pgbackrest --stanza=$STANZA --repo=2 --recurse repo-rm backup/$STANZA" sudo -iu $PGUSER ssh ${SSH_ARGS} ${PGBR_USER}@${PGBR_HOST} "pgbackrest --stanza=$STANZA --log-level-console=warn stanza-create" fi fi fi if [ ! -d $RESULTS_DIR ]; then mkdir $RESULTS_DIR else rmdir $RESULTS_DIR mkdir $RESULTS_DIR fi ## Tests # Initiate backups (full, diff, incr) if ! $SKIP_INIT; then echo "...Initiate backups (full, diff, incr)" if [ "$SCRIPT_PROFILE" = "local" ]; then sudo -iu $PGUSER pgbackrest --stanza=$STANZA $REPO backup --type=full --log-level-console=warn --repo1-retention-full=1 sudo -iu $PGUSER pgbackrest --stanza=$STANZA $REPO backup --type=diff --log-level-console=warn sudo -iu $PGUSER pgbackrest --stanza=$STANZA $REPO backup --type=incr --log-level-console=warn else sudo -iu $PGUSER ssh ${SSH_ARGS} ${PGBR_USER}@${PGBR_HOST} "pgbackrest --stanza=$STANZA $REPO backup --type=full --log-level-console=warn --repo1-retention-full=1" sudo -iu $PGUSER ssh ${SSH_ARGS} ${PGBR_USER}@${PGBR_HOST} "pgbackrest --stanza=$STANZA $REPO backup --type=diff --log-level-console=warn" sudo -iu $PGUSER ssh ${SSH_ARGS} ${PGBR_USER}@${PGBR_HOST} "pgbackrest --stanza=$STANZA $REPO backup --type=incr --log-level-console=warn" fi fi # --list echo "--list" $PLUGIN_PATH/check_pgbackrest --list | tee $RESULTS_DIR/list.out # --version echo "--version" $PLUGIN_PATH/check_pgbackrest --version # --service=retention --retention-full, --retention-diff, --retention-incr echo "--service=retention --retention-full, --retention-diff, --retention-incr" if [ "$PGBR_REPO_TYPE" = "multi" ] && ! $SKIP_REPO2_CLEAR; then # repo2 should be empty, the service should then fail $PLUGIN_PATH/check_pgbackrest --prefix="sudo -u $PGUSER" --stanza=$STANZA --service=retention --retention-full=1 > $RESULTS_DIR/retention-full-repo2-ko.out fi $PLUGIN_PATH/check_pgbackrest --prefix="sudo -u $PGUSER" --stanza=$STANZA $REPO --service=retention --retention-full=1 --retention-diff=1 --retention-incr=1 --output=nagios_strict echo $PLUGIN_PATH/check_pgbackrest --prefix="sudo -u $PGUSER" --stanza=$STANZA $REPO --service=retention --retention-full=1 --retention-diff=1 --retention-incr=1 --output=human $PLUGIN_PATH/check_pgbackrest --prefix="sudo -u $PGUSER" --stanza=$STANZA $REPO --service=retention --retention-full=1 --retention-diff=1 --retention-incr=1 --output=prtg echo $PLUGIN_PATH/check_pgbackrest --prefix="sudo -u $PGUSER" --stanza=$STANZA $REPO --service=retention --retention-full=1 | cut -f1 -d"|" > $RESULTS_DIR/retention-full.out $PLUGIN_PATH/check_pgbackrest --prefix="sudo -u $PGUSER" --stanza=$STANZA $REPO --service=retention --retention-diff=1 | cut -f1 -d"|" > $RESULTS_DIR/retention-diff.out $PLUGIN_PATH/check_pgbackrest --prefix="sudo -u $PGUSER" --stanza=$STANZA $REPO --service=retention --retention-incr=1 | cut -f1 -d"|" > $RESULTS_DIR/retention-incr.out if [ "$PGBR_REPO_TYPE" = "multi" ] && ! $SKIP_REPO2_CLEAR; then # Take an extra backup for repo2 and make sure the global check will see it if [ "$SCRIPT_PROFILE" = "local" ]; then sudo -iu $PGUSER pgbackrest --stanza=$STANZA --repo=2 backup --type=full --log-level-console=warn else sudo -iu $PGUSER ssh ${SSH_ARGS} ${PGBR_USER}@${PGBR_HOST} "pgbackrest --stanza=$STANZA --repo=2 backup --type=full --log-level-console=warn" fi $PLUGIN_PATH/check_pgbackrest --prefix="sudo -u $PGUSER" --stanza=$STANZA --service=retention --retention-full=2 | cut -f1 -d"|" > $RESULTS_DIR/retention-full-global.out fi # --service=retention --retention-age echo "--service=retention --retention-age" $PLUGIN_PATH/check_pgbackrest --prefix="sudo -u $PGUSER" --stanza=$STANZA $REPO --service=retention --retention-age=1h --output=nagios_strict echo $PLUGIN_PATH/check_pgbackrest --prefix="sudo -u $PGUSER" --stanza=$STANZA $REPO --service=retention --retention-age=1h --output=human $PLUGIN_PATH/check_pgbackrest --prefix="sudo -u $PGUSER" --stanza=$STANZA $REPO --service=retention --retention-age=1h | cut -f1 -d"|" > $RESULTS_DIR/retention-age.out # --service=retention --retention-age-to-full echo "--service=retention --retention-age-to-full" $PLUGIN_PATH/check_pgbackrest --prefix="sudo -u $PGUSER" --stanza=$STANZA $REPO --service=retention --retention-age-to-full=1h --output=nagios_strict echo $PLUGIN_PATH/check_pgbackrest --prefix="sudo -u $PGUSER" --stanza=$STANZA $REPO --service=retention --retention-age-to-full=1h --output=human $PLUGIN_PATH/check_pgbackrest --prefix="sudo -u $PGUSER" --stanza=$STANZA $REPO --service=retention --retention-age-to-full=1h | cut -f1 -d"|" > $RESULTS_DIR/retention-age-to-full.out # --service=retention --retention-age-to-oldest # check if the oldest backup is too young echo "--service=retention --retention-age-to-oldest fail" $PLUGIN_PATH/check_pgbackrest --prefix="sudo -u $PGUSER" --stanza=$STANZA $REPO --service=retention --retention-age-to-oldest=1h --output=nagios_strict echo $PLUGIN_PATH/check_pgbackrest --prefix="sudo -u $PGUSER" --stanza=$STANZA $REPO --service=retention --retention-age-to-oldest=1h --output=human $PLUGIN_PATH/check_pgbackrest --prefix="sudo -u $PGUSER" --stanza=$STANZA $REPO --service=retention --retention-age-to-oldest=1h | cut -f1 -d"|" > $RESULTS_DIR/retention-age-to-oldest-fail.out # --service=retention fail echo "--service=retention fail" sudo -iu $PGUSER $PGBIN/psql -h $PGUNIXSOCKET -d $PGDATABASE -c "SELECT pg_sleep(2);" > /dev/null 2>&1 $PLUGIN_PATH/check_pgbackrest --prefix="sudo -u $PGUSER" --stanza=$STANZA $REPO --service=retention --retention-full=2 --retention-age=1s --retention-age-to-full=1s --output=nagios_strict echo $PLUGIN_PATH/check_pgbackrest --prefix="sudo -u $PGUSER" --stanza=$STANZA $REPO --service=retention --retention-full=2 --retention-age=1s --retention-age-to-full=1s --output=human $PLUGIN_PATH/check_pgbackrest --prefix="sudo -u $PGUSER" --stanza=$STANZA $REPO --service=retention --retention-full=2 --retention-age=1s --retention-age-to-full=1s | cut -f1 -d"|" > $RESULTS_DIR/retention-fail.out # --service=archives echo "--service=archives" if [ "$PGBR_REPO_TYPE" = "multi" ] && ! $SKIP_REPO2_CLEAR; then $PLUGIN_PATH/check_pgbackrest --prefix="sudo -u $PGUSER" --stanza=$STANZA --repo=2 --service=archives | cut -f1 -d"-" > $RESULTS_DIR/archives-repo2-ok.out fi sudo -iu $PGUSER $PGBIN/psql -h $PGUNIXSOCKET -d $PGDATABASE -c "SELECT pg_create_restore_point('generate WAL');" > /dev/null 2>&1 sudo -iu $PGUSER $PGBIN/psql -h $PGUNIXSOCKET -d $PGDATABASE -c "SELECT pg_switch_xlog();" > /dev/null 2>&1 sudo -iu $PGUSER $PGBIN/psql -h $PGUNIXSOCKET -d $PGDATABASE -c "SELECT pg_switch_wal();" > /dev/null 2>&1 sudo -iu $PGUSER $PGBIN/psql -h $PGUNIXSOCKET -d $PGDATABASE -c "SELECT pg_sleep(1);" > /dev/null 2>&1 $PLUGIN_PATH/check_pgbackrest --prefix="sudo -u $PGUSER" --stanza=$STANZA $REPO --service=archives --output=nagios_strict echo $PLUGIN_PATH/check_pgbackrest --prefix="sudo -u $PGUSER" --stanza=$STANZA $REPO --service=archives --output=human $PLUGIN_PATH/check_pgbackrest --prefix="sudo -u $PGUSER" --stanza=$STANZA $REPO --service=archives --output=prtg echo $PLUGIN_PATH/check_pgbackrest --prefix="sudo -u $PGUSER" --stanza=$STANZA $REPO --service=archives | cut -f1 -d"-" > $RESULTS_DIR/archives-ok.out if [ "$PGBR_REPO_TYPE" = "multi" ] && ! $SKIP_REPO2_CLEAR; then $PLUGIN_PATH/check_pgbackrest --prefix="sudo -u $PGUSER" --stanza=$STANZA --service=archives | cut -f1 -d"-" > $RESULTS_DIR/archives-ok-global.out fi # --service=archives --ignore-archived-before echo "--service=archives --ignore-archived-before" sudo -iu $PGUSER $PGBIN/psql -h $PGUNIXSOCKET -d $PGDATABASE -c "SELECT pg_sleep(2);" > /dev/null 2>&1 $PLUGIN_PATH/check_pgbackrest --prefix="sudo -u $PGUSER" --stanza=$STANZA $REPO --service=archives --ignore-archived-before=1s > $RESULTS_DIR/archives-ignore-before.out # --service=archives --ignore-archived-after echo "--service=archives --ignore-archived-after" $PLUGIN_PATH/check_pgbackrest --prefix="sudo -u $PGUSER" --stanza=$STANZA $REPO --service=archives --ignore-archived-after=1h > $RESULTS_DIR/archives-ignore-after.out # --service=archives --latest-archive-age-alert echo "--service=archives --latest-archive-age-alert" sudo -iu $PGUSER $PGBIN/psql -h $PGUNIXSOCKET -d $PGDATABASE -c "SELECT pg_sleep(2);" > /dev/null 2>&1 $PLUGIN_PATH/check_pgbackrest --prefix="sudo -u $PGUSER" --stanza=$STANZA $REPO --service=archives --latest-archive-age-alert=1h | cut -f1 -d"-" > $RESULTS_DIR/archives-age-alert-ok.out $PLUGIN_PATH/check_pgbackrest --prefix="sudo -u $PGUSER" --stanza=$STANZA $REPO --service=archives --latest-archive-age-alert=1s --output=human $PLUGIN_PATH/check_pgbackrest --prefix="sudo -u $PGUSER" --stanza=$STANZA $REPO --service=archives --latest-archive-age-alert=1s | cut -f1 -d"-" > $RESULTS_DIR/archives-age-alert-ko.out # --service=archives --max-archives-check-number echo "--service=archives --max-archives-check-number" $PLUGIN_PATH/check_pgbackrest --prefix="sudo -u $PGUSER" --stanza=$STANZA $REPO --service=archives --max-archives-check-number=1 > $RESULTS_DIR/archives-max-archives-check-ko.out 2>&1 ## Results if [ "$PGBR_REPO_TYPE" = "multi" ] && ! $SKIP_REPO2_CLEAR; then diff -abB expected/ $RESULTS_DIR/ > /tmp/regression.diffs else diff -abB -x '*repo2*' -x '*-global.out' expected/ $RESULTS_DIR/ > /tmp/regression.diffs fi if [ $(wc -l < /tmp/regression.diffs) -gt 0 ]; then cat /tmp/regression.diffs exit 1 fi exit 0 check_pgbackrest-REL2_4/tests/playbooks/scripts/000077500000000000000000000000001464170754600221375ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/playbooks/scripts/simulate-activity-basic.bash000066400000000000000000000152201464170754600275320ustar00rootroot00000000000000#!/usr/bin/env bash set -o errexit set -o nounset cd "$(dirname "$0")" EXTENDED_ACTIVITY=false usage() { echo "Usage:" echo " -s " echo " -a " echo " -p " echo " -e (extended activity)" } while getopts "s:a:p:e" o; do case "${o}" in s) SCALE=${OPTARG} ;; a) ACTIVITY_TIME=${OPTARG} ;; p) SCRIPT_PROFILE=${OPTARG} ;; e) EXTENDED_ACTIVITY=true ;; *) usage 1>&2 exit 1 ;; esac done shift $((OPTIND-1)) if [ -z "$SCRIPT_PROFILE" ]; then SCRIPT_PROFILE="local" if [ ! -z $PGBR_HOST ]; then SCRIPT_PROFILE="remote" fi fi if [ "$SCRIPT_PROFILE" != "local" ] && [ "$SCRIPT_PROFILE" != "remote" ]; then usage fi PYTHON="python3" command -v $PYTHON >/dev/null 2>&1 || { PYTHON="python"; } SSH_ARGS='-o ConnectTimeout=10 -o BatchMode=yes -o StrictHostKeyChecking=no' echo "SCALE = $SCALE" echo "ACTIVITY_TIME = $ACTIVITY_TIME seconds" echo "SCRIPT_PROFILE = $SCRIPT_PROFILE" echo "PGBIN = $PGBIN" echo "PGDATABASE = $PGDATABASE" echo "PGSVC = $PGSVC" echo "PGUNIXSOCKET = $PGUNIXSOCKET" echo "PGUSER = $PGUSER" echo "STANZA = $STANZA" if [ ! -z "$PGBR_HOST" ]; then echo "PGBR_USER = $PGBR_USER" echo "PGBR_HOST = $PGBR_HOST" PGBR_HOST=(`$PYTHON -c "print(' '.join($PGBR_HOST))"`) fi if [ ! -z "$PGBR_STANDBIES" ]; then echo "PGBR_STANDBIES = $PGBR_STANDBIES" PGBR_STANDBIES=(`$PYTHON -c "print(' '.join($PGBR_STANDBIES))"`) fi echo "PGBR_REPO_TYPE = $PGBR_REPO_TYPE" REPO="" if [ "$PGBR_REPO_TYPE" = "multi" ]; then REPO="--repo=1" echo "...multi repo support, defaulting to repo1" fi # run echo "-------------------PROCESS START-------------------" echo "--Create pgbench setup" sudo -iu $PGUSER $PGBIN/dropdb -h $PGUNIXSOCKET --if-exists bench sudo -iu $PGUSER $PGBIN/createdb -h $PGUNIXSOCKET bench sudo -iu $PGUSER $PGBIN/pgbench -h $PGUNIXSOCKET -i -s $SCALE --quiet --foreign-keys bench echo "--Take a full backup" if [ "$SCRIPT_PROFILE" = "local" ]; then sudo -iu $PGUSER pgbackrest --stanza=$STANZA $REPO --type=full backup else sudo -iu $PGUSER ssh ${SSH_ARGS} ${PGBR_USER}@${PGBR_HOST} "pgbackrest --stanza=$STANZA $REPO --type=full backup" fi sudo -iu $PGUSER pgbackrest --stanza=$STANZA $REPO info echo "--Simulate $ACTIVITY_TIME sec activity" sudo -iu $PGUSER $PGBIN/pgbench -h $PGUNIXSOCKET -T $ACTIVITY_TIME bench echo "--Take an incremental backup" if [ "$SCRIPT_PROFILE" = "local" ]; then sudo -iu $PGUSER pgbackrest --stanza=$STANZA $REPO --type=incr backup else sudo -iu $PGUSER ssh ${SSH_ARGS} ${PGBR_USER}@${PGBR_HOST} "pgbackrest --stanza=$STANZA $REPO --type=incr backup" fi sudo -iu $PGUSER pgbackrest --stanza=$STANZA $REPO info echo "--Simulate $ACTIVITY_TIME sec activity" sudo -iu $PGUSER $PGBIN/pgbench -h $PGUNIXSOCKET -T $ACTIVITY_TIME bench echo "--Take a full backup to test the purge action" if [ "$SCRIPT_PROFILE" = "local" ]; then sudo -iu $PGUSER pgbackrest --stanza=$STANZA $REPO --type=full backup else sudo -iu $PGUSER ssh ${SSH_ARGS} ${PGBR_USER}@${PGBR_HOST} "pgbackrest --stanza=$STANZA $REPO --type=full backup" fi sudo -iu $PGUSER pgbackrest --stanza=$STANZA $REPO info echo "--Simulate $ACTIVITY_TIME sec activity" sudo -iu $PGUSER $PGBIN/pgbench -h $PGUNIXSOCKET -T $ACTIVITY_TIME bench echo "--Create restore point RP1 and get latest pgbench history time" sudo -iu $PGUSER $PGBIN/psql -h $PGUNIXSOCKET -d $PGDATABASE -c "select pg_create_restore_point('RP1');" sudo -iu $PGUSER $PGBIN/psql -h $PGUNIXSOCKET -d bench -c 'SELECT max(mtime) FROM pgbench_history;' echo "--Simulate $ACTIVITY_TIME sec activity and get latest pgbench history time" sudo -iu $PGUSER $PGBIN/pgbench -h $PGUNIXSOCKET -T $ACTIVITY_TIME bench sudo -iu $PGUSER $PGBIN/psql -h $PGUNIXSOCKET -d bench -c 'SELECT max(mtime) FROM pgbench_history;' echo "--Restore RP1 restore point and get latest pgbench history time" systemctl stop $PGSVC sudo -iu $PGUSER pgbackrest restore --stanza=$STANZA $REPO --delta --type=name --target=RP1 --target-action=promote systemctl start $PGSVC systemctl status $PGSVC echo "--Wait while pg_is_in_recovery" while [ `sudo -iu $PGUSER $PGBIN/psql -h $PGUNIXSOCKET -d $PGDATABASE -c 'SELECT pg_is_in_recovery();' -A -t` = "t" ] do echo "wait..." sleep 5 done sudo -iu $PGUSER $PGBIN/psql -h $PGUNIXSOCKET -d bench -c 'SELECT max(mtime) FROM pgbench_history;' echo "--Resync standby server(s)" echo "----Take incremental backup" if [ "$SCRIPT_PROFILE" = "local" ]; then sudo -iu $PGUSER pgbackrest --stanza=$STANZA $REPO --type=incr backup else sudo -iu $PGUSER ssh ${SSH_ARGS} ${PGBR_USER}@${PGBR_HOST} "pgbackrest --stanza=$STANZA $REPO --type=incr backup" fi for i in "${PGBR_STANDBIES[@]}"; do echo "----Restore on standby server - $i" ssh ${SSH_ARGS} "$i" "systemctl stop $PGSVC" ssh ${SSH_ARGS} "$i" "sudo -iu $PGUSER pgbackrest --stanza=$STANZA $REPO --reset-pg2-host --type=standby restore" ssh ${SSH_ARGS} "$i" "systemctl start $PGSVC" done echo "----Wait until at least 1 standby is replicated" while [ `sudo -iu $PGUSER $PGBIN/psql -h $PGUNIXSOCKET -d $PGDATABASE -At -c "SELECT count(*) FROM pg_stat_replication;"` -lt 1 ] do echo "wait..." sleep 5 done sudo -iu $PGUSER $PGBIN/psql -h $PGUNIXSOCKET -d $PGDATABASE -x -c "SELECT * FROM pg_stat_replication;" echo "--Simulate $ACTIVITY_TIME sec activity to get archives on different time-lines" sudo -iu $PGUSER $PGBIN/pgbench -h $PGUNIXSOCKET -T $ACTIVITY_TIME bench sudo -iu $PGUSER pgbackrest --stanza=$STANZA $REPO info if $EXTENDED_ACTIVITY; then echo "--Create test-checksums setup" sudo -iu $PGUSER $PGBIN/dropdb -h $PGUNIXSOCKET --if-exists test-checksums sudo -iu $PGUSER $PGBIN/createdb -h $PGUNIXSOCKET test-checksums sudo -iu $PGUSER $PGBIN/psql -h $PGUNIXSOCKET -d test-checksums -c "CREATE TABLE t1 (id int);INSERT INTO t1 VALUES (1);" sudo -iu $PGUSER $PGBIN/psql -h $PGUNIXSOCKET -d $PGDATABASE -c "CHECKPOINT;" FILE_TO_EDIT=`sudo -iu $PGUSER $PGBIN/psql -h $PGUNIXSOCKET -d test-checksums -A -t -c "SELECT current_setting('data_directory') || '/' || pg_relation_filepath('t1');"` echo "FILE_TO_EDIT=$FILE_TO_EDIT" echo "33" |xxd > $FILE_TO_EDIT echo "--Take an incremental backup" if [ "$SCRIPT_PROFILE" = "local" ]; then sudo -iu $PGUSER pgbackrest --stanza=$STANZA $REPO --type=incr backup else sudo -iu $PGUSER ssh ${SSH_ARGS} ${PGBR_USER}@${PGBR_HOST} "pgbackrest --stanza=$STANZA $REPO --type=incr backup" fi sudo -iu $PGUSER pgbackrest --stanza=$STANZA $REPO info fi echo "-------------------PROCESS END-------------------" check_pgbackrest-REL2_4/tests/playbooks/templates/000077500000000000000000000000001464170754600224465ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/playbooks/templates/profile.fact.j2000066400000000000000000000010561464170754600252610ustar00rootroot00000000000000[global] cluster_name={{ cluster_name }} pg_bin_path={{ pg_bin_path }} pg_data={{ pg_data }} pg_database={{ pg_database }} pg_owner={{ pg_owner }} pg_port={{ pg_port }} pg_service={{ pg_service }} pg_unix_socket={{ pg_unix_socket_directories[0] }} pg_version={{ pg_version }} pgbackrest_user={{ pgbackrest_user }} pgbackrest_repo_type={{ pgbackrest_repo_type }} {% if repository_server|length > 0 %} pgbackrest_repo_host={{ repository_server }} {% endif %} {% if pgbackrest_standbies|length > 0 %} pgbackrest_standbies={{ pgbackrest_standbies }} {% endif %} check_pgbackrest-REL2_4/tests/plugins/000077500000000000000000000000001464170754600201265ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/plugins/lookup/000077500000000000000000000000001464170754600214375ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/plugins/lookup/pg_sr_cluster_nodes.py000066400000000000000000000124421464170754600260570ustar00rootroot00000000000000from __future__ import (absolute_import, division, print_function) __metaclass__ = type DOCUMENTATION = """ name: pg_sr_cluster_nodes author: Julien Tachoires short_description: Lookup Postgres SR cluster nodes description: - "Retrieves the Postgres streaming replication nodes list, based on node's private IP" options: _terms: description: The private IP of one member of the SR cluster. required: False default: description: The private IP of the current node is used. """ EXAMPLES = """ - name: Show all members of the SR cluster that the current node is part of debug: msg="{{ lookup('pg_sr_cluster_nodes') }}" - name: Show all members of the SR cluster that the {{ primary_private_ip }} is part of debug: msg="{{ lookup('pg_sr_cluster_nodes', primary_private_ip) }}" """ RETURN = """ _value: description: - List of Postgres nodes type: list elements: dict """ from ansible.errors import AnsibleError from ansible.plugins.lookup import LookupBase class LookupModule(LookupBase): def run(self, terms, variables=None, **kwargs): pg_clusters = {} pg_standbys = {} pg_primary_map = {} myvars = getattr(self._templar, '_available_variables', {}) # If no terms, we'll used the current private IP if len(terms) == 0: node_private_ip = myvars['hostvars'][variables['inventory_hostname']]['private_ip'] else: node_private_ip = terms[0] # If no primary found in the inventory we return an empty list if 'primary' not in variables['groups']: return [] # Initiate pg_clusters and pg_primary_map for each primary node we have # in the inventory. for host in variables['groups']['primary']: hostvars = myvars['hostvars'][host] private_ip = hostvars['private_ip'] pg_clusters[private_ip] = [] pg_clusters[private_ip].append( dict( node_type='primary', ansible_host=hostvars['ansible_host'], hostname=hostvars.get('hostname', hostvars.get('ansible_hostname')), private_ip=hostvars['private_ip'], upstream_node_private_ip=None, replication_type=None, inventory_hostname=hostvars['inventory_hostname'] ) ) pg_primary_map[private_ip] = private_ip # Populate pg_standbys dict if we have standby nodes in the inventory if 'standby' in variables['groups']: for host in variables['groups']['standby']: hostvars = myvars['hostvars'][host] pg_standbys[host] = dict( node_type='standby', ansible_host=hostvars['ansible_host'], hostname=hostvars.get('hostname', hostvars.get('ansible_hostname')), private_ip=hostvars['private_ip'], upstream_node_private_ip=hostvars['upstream_node_private_ip'], replication_type=hostvars.get('replication_type', 'asynchronous'), inventory_hostname=hostvars['inventory_hostname'] ) pg_standbys_len = len(pg_standbys.keys()) # Append the standby nodes into the right pg_clusters item, based on # standby's upstream node. while pg_standbys_len != 0: for k in list(pg_standbys.keys()): sby = pg_standbys[k] if sby['upstream_node_private_ip'] in pg_primary_map: upstream_private_ip = sby['upstream_node_private_ip'] primary_private_ip = pg_primary_map[upstream_private_ip] pg_primary_map[sby['private_ip']] = primary_private_ip pg_clusters[primary_private_ip].append(sby) del(pg_standbys[k]) # Case when at least one host has not been handled in this loop # iteration. if pg_standbys_len == len(pg_standbys.keys()): raise AnsibleError( "Inventory error with the following standbys nodes %s. " "Upstream node is not configured or not found" % [s for s in pg_standbys.keys()] ) pg_standbys_len = len(pg_standbys.keys()) if node_private_ip in pg_primary_map: # Current node is part of one of the SR clusters found return pg_clusters[pg_primary_map[node_private_ip]] else: primary_private_ips = list(pg_clusters.keys()) # If the current node is not part of any SR cluster found, but, # only one SR cluster has been found, then we return this SR # cluster because there is no doubt. if len(primary_private_ips) == 1: return pg_clusters[primary_private_ips[0]] else: raise AnsibleError( "Unable to find the SR cluster topology because multiple " "SR clusters were found and this current node does not " "appear to be part of any of them" ) check_pgbackrest-REL2_4/tests/profile.d/000077500000000000000000000000001464170754600203275ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/profile.d/d11pg.profile000066400000000000000000000001321464170754600226210ustar00rootroot00000000000000export CLNAME="d11pg" export DBTYPE="PG" export DBVERSION="16" export DOCKERI="debian:11" check_pgbackrest-REL2_4/tests/profile.d/ro8pg.profile000066400000000000000000000001351464170754600227470ustar00rootroot00000000000000export CLNAME="ro8pg" export DBTYPE="PG" export DBVERSION="16" export DOCKERI="rockylinux:8" check_pgbackrest-REL2_4/tests/profile.d/ro9pg.profile000066400000000000000000000001351464170754600227500ustar00rootroot00000000000000export CLNAME="ro9pg" export DBTYPE="PG" export DBVERSION="16" export DOCKERI="rockylinux:9" check_pgbackrest-REL2_4/tests/profile.d/u22pg.profile000066400000000000000000000001351464170754600226470ustar00rootroot00000000000000export CLNAME="u22pg" export DBTYPE="PG" export DBVERSION="16" export DOCKERI="ubuntu:22.04" check_pgbackrest-REL2_4/tests/profile.d/vagrant.profile000066400000000000000000000003751464170754600233600ustar00rootroot00000000000000# Vagrant settings export CLPATH="/home/vagrant/clusters" # Ansible settings export ANSIBLE_ROLES_PATH=${ANSIBLE_ROLES_PATH:+$ANSIBLE_ROLES_PATH:}$(pwd)/roles export ANSIBLE_HOST_KEY_CHECKING=False export ANSIBLE_REMOTE_USER="root" export EXTRA_VARS="" check_pgbackrest-REL2_4/tests/roles/000077500000000000000000000000001464170754600175715ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/roles/init_dbserver/000077500000000000000000000000001464170754600224305ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/roles/init_dbserver/defaults/000077500000000000000000000000001464170754600242375ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/roles/init_dbserver/defaults/main.yml000066400000000000000000000002771464170754600257140ustar00rootroot00000000000000--- pg_instance_name: "main" pg_shared_libraries_list: - "$libdir/pg_stat_statements" pg_init_conf_params: [] # example: # pg_init_conf_params: # - name: "track_io_timing" # value: "on" check_pgbackrest-REL2_4/tests/roles/init_dbserver/tasks/000077500000000000000000000000001464170754600235555ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/roles/init_dbserver/tasks/create_directories.yml000066400000000000000000000020511464170754600301350ustar00rootroot00000000000000--- - name: Ensure postgres data directory exists ansible.builtin.file: path: "{{ pg_data }}" owner: "{{ pg_owner }}" group: "{{ pg_group }}" mode: "0700" state: directory become: true - name: Ensure postgres log directory exists ansible.builtin.file: path: "{{ pg_log }}" owner: "{{ pg_owner }}" group: "{{ pg_group }}" mode: "0700" state: directory become: true when: pg_log|length > 0 and pg_data not in pg_log - name: Ensure postgres wal directory exists ansible.builtin.file: path: "{{ pg_wal }}" owner: "{{ pg_owner }}" group: "{{ pg_group }}" mode: "0700" state: directory become: true when: pg_wal|length > 0 and pg_data not in pg_wal - name: Create unix socket domain directories ansible.builtin.file: path: "{{ line_item }}" owner: "{{ pg_owner }}" group: "{{ pg_group }}" mode: "02775" state: directory with_items: "{{ pg_unix_socket_directories }}" loop_control: loop_var: line_item become: true when: pg_unix_socket_directories|length > 0 check_pgbackrest-REL2_4/tests/roles/init_dbserver/tasks/main.yml000066400000000000000000000017311464170754600252260ustar00rootroot00000000000000--- - name: Reference PG variables ansible.builtin.include_vars: "PG_{{ ansible_os_family }}.yml" - name: Gather service facts ansible.builtin.service_facts: - name: Firewall open Postgres TCP port {{ pg_port }} ansible.posix.firewalld: port: "{{ pg_port }}/tcp" permanent: true state: enabled immediate: true when: - ansible_facts.services['firewalld.service'] is defined - ansible_facts.services['firewalld.service'].state == 'running' - ansible_facts.services['firewalld.service'].status == 'enabled' become: true - name: Check and configure the node as primary become: true block: - name: Create required directories ansible.builtin.import_tasks: create_directories.yml - name: Setup systemd for PG ansible.builtin.import_tasks: pg_setup_systemd.yml - name: Call initdb command ansible.builtin.import_tasks: pg_initdb.yml - name: Validate initdb ansible.builtin.import_tasks: validate_init_dbserver.yml check_pgbackrest-REL2_4/tests/roles/init_dbserver/tasks/pg_initdb.yml000066400000000000000000000026361464170754600262460ustar00rootroot00000000000000--- - name: Check if we have PG_VERSION in pg_data ansible.builtin.stat: path: "{{ pg_data }}/PG_VERSION" become: true register: pg_version_stat - name: Verify pg_wal and accordingly add in initdb ansible.builtin.set_fact: pg_initdb_options: "{{ pg_initdb_options + ' --waldir=' + pg_wal }}" when: - pg_wal|length > 0 - not pg_data in pg_wal - name: Initialize database postgres service ansible.builtin.shell: > {{ pg_initdb }} {{ pg_service }} args: executable: /bin/bash creates: "{{ pg_data }}/PG_VERSION" environment: PGSETUP_INITDB_OPTIONS: "{{ pg_initdb_options }}" when: ansible_os_family == 'RedHat' become: true - name: Initialize database postgres service ansible.builtin.shell: > {{ pg_initdb }} args: executable: /bin/bash creates: "{{ pg_data }}/PG_VERSION" environment: PGSETUP_INITDB_OPTIONS: "{{ pg_initdb_options }}" when: ansible_os_family == 'Debian' become: true - name: Copy the postgresql.conf.template to the server ansible.builtin.template: src: postgresql.conf.template dest: "{{ pg_data }}/postgresql.auto.conf" owner: "{{ pg_owner }}" group: "{{ pg_group }}" mode: "0600" when: - not pg_version_stat.stat.exists become: true - name: Enable and start systemd service ansible.builtin.systemd: name: "{{ pg_service }}" daemon_reload: true state: started enabled: true become: true check_pgbackrest-REL2_4/tests/roles/init_dbserver/tasks/pg_setup_systemd.yml000066400000000000000000000021121464170754600276720ustar00rootroot00000000000000--- - name: Copy global systemd unit file to /etc/systemd/system ansible.builtin.copy: remote_src: true src: "/usr/lib/systemd/system/postgresql-{{ pg_version }}.service" dest: "/etc/systemd/system/{{ pg_service }}.service" mode: "0600" owner: root group: root become: true when: - ansible_os_family == 'RedHat' - name: Update systemd unit file ansible.builtin.lineinfile: dest: "/etc/systemd/system/{{ pg_service }}.service" regexp: "{{ item.regexp }}" line: "{{ item.line }}" insertafter: "{{ item.insertafter | default(omit) }}" loop: - { regexp: "^Description=.*", line: "Description=Database server PG {{ pg_version }} - Instance: {{ pg_instance_name }}"} - { regexp: "^Environment=PGDATA.*", line: "Environment=PGDATA={{ pg_data }}" } - { regexp: "^PIDFile=.*", line: "PIDFile={{ pg_data }}/postmaster.pid", insertafter: "^\\[Service\\]$" } - { regexp: "^ExecStopPost=.*", line: "ExecStopPost=+/usr/bin/systemctl daemon-reload", insertafter: "^\\[Service\\]$" } become: true when: - ansible_os_family == 'RedHat' check_pgbackrest-REL2_4/tests/roles/init_dbserver/tasks/validate_init_dbserver.yml000066400000000000000000000052611464170754600310140ustar00rootroot00000000000000--- - name: Stat to pg_data ansible.builtin.stat: path: "{{ pg_data }}" register: data_check become: true - name: Stat to pg_data/pg_wal ansible.builtin.stat: path: "{{ pg_data }}/pg_wal" register: data_wal_check become: true - name: Stat to pg_wal ansible.builtin.stat: path: "{{ pg_wal }}" register: wal_check become: true when: pg_wal|length > 0 and pg_data not in pg_wal - name: Check that pg_data was configured correctly ansible.builtin.assert: that: - data_check.stat['gr_name'] == pg_group - data_check.stat['pw_name'] == pg_owner - data_check.stat['isdir']|bool fail_msg: "The directory {{ pg_data }} is not configured correctly." success_msg: "The directory {{ pg_data }} is configured correctly." - name: Check that pg_wal was configured correctly ansible.builtin.assert: that: - wal_check.stat['pw_name'] == pg_owner - wal_check.stat['isdir']|bool - data_wal_check.stat['islnk']|bool - data_wal_check.stat['lnk_source'] == pg_wal fail_msg: "The directory {{ pg_wal }} is not configured correctly." success_msg: "The directory {{ pg_wal }} is configured correctly." when: pg_wal|length > 0 and pg_data not in pg_wal # check if service pg_service is running - name: Gather service facts ansible.builtin.service_facts: - name: Check if service pg_service is running ansible.builtin.assert: that: - ansible_facts.services[pg_service + '.service']['state'] == 'running' fail_msg: "The service {{ pg_service }} is not running." success_msg: "The service {{ pg_service }} is running." - name: Check if service pg_service is enabled ansible.builtin.assert: that: - ansible_facts.services[pg_service + '.service']['status'] == 'enabled' fail_msg: "The service {{ pg_service }} is not enabled." success_msg: "The service {{ pg_service }} is enabled." when: ansible_os_family == 'RedHat' # check if sockets are listening - name: Get stat info for pg_unix_socket_directories ansible.builtin.stat: path: "{{ pg_unix_socket_directories[0] }}/.s.PGSQL.{{ pg_port }}" register: unix_stat - name: Check that port is listening ansible.builtin.wait_for: port: "{{ pg_port }}" state: started msg: "Port {{ pg_port }} is listening." become: true - name: Check pg_unix_socket_directories socket ansible.builtin.assert: that: - unix_stat.stat['issock']|bool fail_msg: "The socket {{ pg_unix_socket_directories[0] }} is not enabled." success_msg: "The socket {{ pg_unix_socket_directories[0] }} is enabled." - name: Reset variables ansible.builtin.set_fact: data_check: null data_wal_check: null wal_check: null unix_stat: null check_pgbackrest-REL2_4/tests/roles/init_dbserver/templates/000077500000000000000000000000001464170754600244265ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/roles/init_dbserver/templates/postgresql.conf.template000066400000000000000000000022331464170754600313120ustar00rootroot00000000000000# Do not edit this file manually! # It will be overwritten by the ALTER SYSTEM command. cluster_name = '{{ pg_instance_name }}' port = {{ pg_port }} unix_socket_directories = '{{ pg_unix_socket_directories | join(",") }}' listen_addresses = '*' wal_level = replica wal_compression = on wal_log_hints = on checkpoint_timeout = 15min checkpoint_completion_target = 0.9 archive_mode = on archive_command = '/bin/true' password_encryption = 'scram-sha-256' logging_collector = on log_checkpoints = on log_connections = on log_directory = '{{ pg_log }}' log_disconnections = on log_error_verbosity = 'default' log_filename = '{{ pg_log_filename }}' log_line_prefix = '%t [%p]: user=%u,db=%d,app=%a,client=%h ' log_lock_waits = on log_min_duration_statement = 1000 log_temp_files = 0 log_autovacuum_min_duration = 0 track_io_timing = on shared_preload_libraries = '{{ pg_shared_libraries_list | join(",") }}' pg_stat_statements.max = 10000 pg_stat_statements.track = top pg_stat_statements.track_utility = false pg_stat_statements.save = false {% if pg_init_conf_params|length > 0 %} {% for item in pg_init_conf_params %} {{ item.name }} = '{{ item.value }}' {% endfor %} {% endif %} check_pgbackrest-REL2_4/tests/roles/init_dbserver/vars/000077500000000000000000000000001464170754600234035ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/roles/init_dbserver/vars/PG_Debian.yml000066400000000000000000000012631464170754600257000ustar00rootroot00000000000000--- pg_wal: "" pg_data: "/var/lib/postgresql/{{ pg_version }}/{{ pg_instance_name }}" pg_log: "/var/log/postgresql" pg_log_filename: "{{ pg_instance_name }}-postgresql-%a.log" pg_service: "postgresql@{{ pg_version }}-{{ pg_instance_name }}" pg_bin_path: "/usr/lib/postgresql/{{ pg_version }}/bin" pg_initdb: "/usr/bin/pg_createcluster -s {{ pg_unix_socket_directories[0] }} {{ pg_version }} {{ pg_instance_name }} -- ${PGSETUP_INITDB_OPTIONS}" pg_initdb_options: "-k -A scram-sha-256 --encoding=UTF-8 --locale=C.UTF-8 --auth-local=peer" # PG configuration pg_port: 5432 pg_database: "postgres" pg_owner: "postgres" pg_group: "postgres" pg_unix_socket_directories: - "/var/run/postgresql" check_pgbackrest-REL2_4/tests/roles/init_dbserver/vars/PG_RedHat.yml000066400000000000000000000011351464170754600256630ustar00rootroot00000000000000--- pg_wal: "" pg_data: "/var/lib/pgsql/{{ pg_version }}/{{ pg_instance_name }}/data" pg_log: "/var/log/postgres" pg_log_filename: "{{ pg_instance_name }}-postgresql-%a.log" pg_service: "postgresql-{{ pg_version }}-{{ pg_instance_name }}" pg_bin_path: "/usr/pgsql-{{ pg_version }}/bin" pg_initdb: "{{ pg_bin_path }}/postgresql-{{ pg_version }}-setup initdb" pg_initdb_options: "-k -A scram-sha-256 --encoding=UTF-8 --locale=C.UTF-8 --auth-local=peer" # PG configuration pg_port: 5432 pg_database: "postgres" pg_owner: "postgres" pg_group: "postgres" pg_unix_socket_directories: - "/var/run/postgresql" check_pgbackrest-REL2_4/tests/roles/install_dbserver/000077500000000000000000000000001464170754600231335ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/roles/install_dbserver/defaults/000077500000000000000000000000001464170754600247425ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/roles/install_dbserver/defaults/main.yml000066400000000000000000000001531464170754600264100ustar00rootroot00000000000000--- # Debian specific variables pg_deb_cluster_name: "main" pg_deb_drop_cluster: "/usr/bin/pg_dropcluster" check_pgbackrest-REL2_4/tests/roles/install_dbserver/tasks/000077500000000000000000000000001464170754600242605ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/roles/install_dbserver/tasks/PG_Debian_install.yml000066400000000000000000000013371464170754600303050ustar00rootroot00000000000000--- - name: Install PostgreSQL ansible.builtin.package: name: - python3-psycopg2 - postgresql-{{ pg_version }} - postgresql-{{ pg_version }} - postgresql-server-dev-{{ pg_version }} state: present update_cache: true become: true register: installresult - name: Stop PG service ansible.builtin.systemd: name: "{{ pg_service }}" state: stopped vars: pg_service: "postgresql@{{ pg_version }}-{{ pg_deb_cluster_name }}" when: installresult.changed - name: Drop the default PG debian database ansible.builtin.shell: > {{ pg_deb_drop_cluster }} {{ pg_version }} {{ pg_deb_cluster_name }} args: executable: /bin/bash when: installresult.changed changed_when: true check_pgbackrest-REL2_4/tests/roles/install_dbserver/tasks/PG_RedHat_install.yml000066400000000000000000000014671464170754600302760ustar00rootroot00000000000000--- - name: Disable builtin postgresql module ansible.builtin.shell: > dnf -qy module disable postgresql args: executable: /bin/bash register: disable_builtin_postgres changed_when: disable_builtin_postgres.rc == 0 failed_when: disable_builtin_postgres.rc != 0 ignore_errors: true become: true when: ansible_distribution_major_version in ['8', '9'] - name: Install required python package on EL8 and EL9 ansible.builtin.package: name: - python3-devel - python3-psycopg2 state: present become: true when: ansible_distribution_major_version in ['8', '9'] - name: Install PostgreSQL ansible.builtin.package: name: - postgresql{{ pg_version }} - postgresql{{ pg_version }}-server - postgresql{{ pg_version }}-contrib state: present become: true check_pgbackrest-REL2_4/tests/roles/install_dbserver/tasks/main.yml000066400000000000000000000001671464170754600257330ustar00rootroot00000000000000--- - name: Install and Configure PostgreSQL ansible.builtin.include_tasks: "PG_{{ ansible_os_family }}_install.yml" check_pgbackrest-REL2_4/tests/roles/manage_dbserver/000077500000000000000000000000001464170754600227155ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/roles/manage_dbserver/defaults/000077500000000000000000000000001464170754600245245ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/roles/manage_dbserver/defaults/main.yml000066400000000000000000000006251464170754600261760ustar00rootroot00000000000000--- # manage_postgres_params pg_postgres_conf_params: [] params_restart_required: false # manage_privileges pg_grant_privileges: [] pg_grant_roles: [] # manage_users pg_users: [] # generate_password pass_dir: "~/.passdir" passfile: "" input_user: "" input_password: "" # PG configuration pg_port: 5432 pg_database: "postgres" pg_owner: "postgres" pg_unix_socket_directories: - "/var/run/postgresql" check_pgbackrest-REL2_4/tests/roles/manage_dbserver/tasks/000077500000000000000000000000001464170754600240425ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/roles/manage_dbserver/tasks/execute_sql_query.yml000066400000000000000000000013631464170754600303360ustar00rootroot00000000000000--- - name: Execute query community.postgresql.postgresql_query: query: "{{ line_item.query }}" db: "{{ line_item.db }}" port: "{{ line_item.port | default(pg_port) }}" login_host: "{{ line_item.host | default('') }}" login_user: "{{ line_item.login_user | default(pg_owner) }}" login_password: "{{ line_item.login_password | default('') }}" login_unix_socket: "{{ line_item.login_socket | default(pg_unix_socket_directories[0]) }}" autocommit: "{{ line_item.autocommit | default(false) }}" become: true become_user: "{{ line_item.become_user | default(pg_owner) }}" with_items: "{{ pg_query }}" loop_control: loop_var: line_item when: pg_query|length > 0 ignore_errors: true register: sql_query_output check_pgbackrest-REL2_4/tests/roles/manage_dbserver/tasks/generate_password.yml000066400000000000000000000015561464170754600303100ustar00rootroot00000000000000--- - name: Create hidden pass directory if not exists ansible.builtin.file: path: "{{ pass_dir }}" mode: "0700" state: directory become: false run_once: true delegate_to: localhost - name: Create passfile location ansible.builtin.set_fact: passfile: "{{ pass_dir }}/{{ input_user }}_pass" when: input_user|length > 0 become: false run_once: true delegate_to: localhost - name: Generate password or copy the password from pass file ansible.builtin.set_fact: input_password: "{{ lookup('password', passfile + ' chars=ascii_letters') }}" when: input_password|length < 1 become: false run_once: true delegate_to: localhost - name: Make sure file has permission for owner only ansible.builtin.file: path: "{{ passfile }}" mode: "0600" when: passfile|length > 0 run_once: true become: false delegate_to: localhost check_pgbackrest-REL2_4/tests/roles/manage_dbserver/tasks/manage_hba_conf.yml000066400000000000000000000022371464170754600276400ustar00rootroot00000000000000--- - name: Find the hba file location community.postgresql.postgresql_query: login_user: "{{ pg_owner }}" port: "{{ pg_port }}" login_unix_socket: "{{ pg_unix_socket_directories[0] }}" query: "show hba_file;" db: "{{ pg_database }}" when: pg_hba_ip_addresses|length > 0 become: true become_user: "{{ pg_owner }}" register: hba_info - name: Manage ip address entries into pg_hba file community.postgresql.postgresql_pg_hba: dest: "{{ hba_info.query_result[0].hba_file }}" contype: "{{ line_item.contype | default('hostssl') }}" users: "{{ line_item.users | default('all') }}" source: "{{ line_item.source }}" databases: "{{ line_item.databases | default('all') }}" method: "{{ line_item.method | default('scram-sha-256') }}" state: "{{ line_item.state | default('present') }}" create: true become: true become_user: "{{ pg_owner }}" when: pg_hba_ip_addresses|length > 0 with_items: "{{ pg_hba_ip_addresses }}" loop_control: loop_var: line_item throttle: 1 - name: Reload the pg service ansible.builtin.systemd: name: "{{ pg_service }}" daemon_reload: true state: reloaded become: true check_pgbackrest-REL2_4/tests/roles/manage_dbserver/tasks/manage_pgpass.yml000066400000000000000000000003141464170754600273700ustar00rootroot00000000000000--- - name: Manage entries in .pgpass ansible.builtin.include_tasks: pgpass_line.yml when: pg_pgpass_values | length > 0 with_items: "{{ pg_pgpass_values }}" loop_control: loop_var: line_item check_pgbackrest-REL2_4/tests/roles/manage_dbserver/tasks/manage_postgres_params.yml000066400000000000000000000030441464170754600313070ustar00rootroot00000000000000--- - name: Check user defined parameters and update community.postgresql.postgresql_set: login_unix_socket: "{{ pg_unix_socket_directories[0] }}" port: "{{ pg_port }}" db: "{{ line_item.database | default(pg_database) }}" login_user: "{{ pg_owner }}" name: "{{ line_item.name }}" value: "{{ line_item.value }}" when: pg_postgres_conf_params|length > 0 with_items: "{{ pg_postgres_conf_params }}" become: true become_user: "{{ pg_owner }}" loop_control: loop_var: line_item register: params - name: Register the restart requirements ansible.builtin.set_fact: params_restart_required: true when: - pg_postgres_conf_params|length > 0 - line_item.changed and line_item.restart_required with_items: "{{ params.results }}" loop_control: loop_var: line_item # Reload when no restart is required, otherwise restart will occur - name: Reload the pg service when restart not required ansible.builtin.systemd: name: "{{ pg_service }}" daemon_reload: true state: reloaded become: true when: - pg_postgres_conf_params|length > 0 - params.changed - not params_restart_required # Restart when restart required, otherwise only reload is done - name: Restart the pg service ansible.builtin.systemd: name: "{{ pg_service }}" daemon_reload: true state: restarted become: true when: - pg_postgres_conf_params|length > 0 - params.changed - params_restart_required - name: Reset the restart_required ansible.builtin.set_fact: params_restart_required: false check_pgbackrest-REL2_4/tests/roles/manage_dbserver/tasks/manage_privileges.yml000066400000000000000000000031771464170754600302560ustar00rootroot00000000000000--- - name: Check database is in read/write mode community.postgresql.postgresql_query: query: "SELECT pg_catalog.pg_is_in_recovery()" port: "{{ pg_port }}" db: "{{ pg_database }}" login_user: "{{ pg_owner }}" login_unix_socket: "{{ pg_unix_socket_directories[0] }}" become: true become_user: "{{ pg_owner }}" register: db_status - name: Grant object privilege to users community.postgresql.postgresql_privs: type: "{{ line_item.type }}" state: "{{ line_item.grant | default('present') }}" privs: "{{ line_item.privileges }}" schema: "{{ line_item.schema | default(omit) }}" objs: "{{ line_item.objects | default(omit) }}" roles: "{{ line_item.roles }}" db: "{{ line_item.database }}" login_user: "{{ pg_owner }}" login_unix_socket: "{{ pg_unix_socket_directories[0] }}" port: "{{ pg_port }}" become: true become_user: "{{ pg_owner }}" with_items: "{{ pg_grant_privileges }}" loop_control: loop_var: line_item when: - not db_status.query_result[0].pg_is_in_recovery - pg_grant_privileges|length > 0 - name: Grant role to users community.postgresql.postgresql_membership: group: "{{ line_item.role }}" target_role: "{{ line_item.user }}" state: "{{ line_item.grant | default('present') }}" db: "{{ pg_database }}" login_user: "{{ pg_owner }}" login_unix_socket: "{{ pg_unix_socket_directories[0] }}" port: "{{ pg_port }}" become: true become_user: "{{ pg_owner }}" with_items: "{{ pg_grant_roles }}" loop_control: loop_var: line_item when: - not db_status.query_result[0].pg_is_in_recovery - pg_grant_roles|length > 0 check_pgbackrest-REL2_4/tests/roles/manage_dbserver/tasks/manage_users.yml000066400000000000000000000023711464170754600272410ustar00rootroot00000000000000--- - name: Check database is in read/write mode community.postgresql.postgresql_query: query: "SELECT pg_catalog.pg_is_in_recovery()" port: "{{ pg_port }}" db: "{{ pg_database }}" login_user: "{{ pg_owner }}" login_unix_socket: "{{ pg_unix_socket_directories[0] }}" become: true become_user: "{{ pg_owner }}" register: db_status - name: Manage optional users community.postgresql.postgresql_user: name: "{{ line_item.name }}" password: "{{ line_item.pass | default(omit) }}" encrypted: "{{ line_item.encrypted | default(omit) }}" conn_limit: "{{ line_item.conn_limit | default(omit) }}" expires: "{{ line_item.expires | default(omit) }}" priv: "{{ line_item.priv | default(omit) }}" role_attr_flags: "{{ line_item.role_attr_flags | default(omit) }}" groups: "{{ line_item.groups | default(omit) }}" port: "{{ pg_port }}" db: "{{ pg_database }}" state: "{{ line_item.state | default('present') }}" login_user: "{{ pg_owner }}" login_unix_socket: "{{ pg_unix_socket_directories[0] }}" become: true become_user: "{{ pg_owner }}" with_items: "{{ pg_users }}" loop_control: loop_var: line_item when: - not db_status.query_result[0].pg_is_in_recovery - pg_users|length > 0 check_pgbackrest-REL2_4/tests/roles/manage_dbserver/tasks/pgpass_line.yml000066400000000000000000000031111464170754600270650ustar00rootroot00000000000000--- - name: Check if pgpass exists or not ansible.builtin.stat: path: "{{ pgpass_file | default('~/.pgpass') }}" become: true become_user: "{{ pg_owner }}" register: pgpass_file_exists - name: Prepare regex and entry line ansible.builtin.set_fact: search_line: "{{ line_item.host | default('*') }}:{{ pg_port }}:{{ line_item.database | default('*') }}:{{ line_item.user }}" pgpass_line: "{{ line_item.host | default('*') }}:{{ pg_port }}:{{ line_item.database | default('*') }}:{{ line_item.user }}:{{ line_item.password }}" when: - pg_pgpass_values|length > 0 - name: Delete entries in .pgpass ansible.builtin.lineinfile: path: "{{ pgpass_file | default('~/.pgpass') }}" regexp: "{{ search_line | regex_escape() }}" state: absent when: - pg_pgpass_values | length > 0 - pgpass_file_exists is defined - pgpass_file_exists.stat.exists become: true become_user: "{{ pg_owner }}" - name: Manage entries in .pgpass ansible.builtin.lineinfile: path: "{{ pgpass_file | default('~/.pgpass') }}" line: "{{ pgpass_line }}" state: "{{ line_item.state | default('present') }}" create: "{{ line_item.create | default(omit) }}" owner: "{{ pg_owner }}" group: "{{ pg_owner }}" mode: "0600" when: > pg_pgpass_values | length > 0 and (( pgpass_file_exists is defined and pgpass_file_exists.stat.exists ) or (line_item.create is defined and (line_item.create == 'yes' or line_item.create == 'true' or line_item.create == 'True' or line_item.create ))) become: true become_user: "{{ pg_owner }}" check_pgbackrest-REL2_4/tests/roles/setup_check_pgbackrest/000077500000000000000000000000001464170754600242735ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/roles/setup_check_pgbackrest/defaults/000077500000000000000000000000001464170754600261025ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/roles/setup_check_pgbackrest/defaults/main.yml000066400000000000000000000005771464170754600275620ustar00rootroot00000000000000--- check_pgbackrest_build: false deploy_icinga2: false skip_check_icinga2: false reschedule_check_icinga2: false build_packages: common: - git Debian: - libjson-perl - libdata-dump-perl RedHat: - nagios-plugins - perl-JSON - perl-Data-Dumper - perl-generators RedHat8: - perl-interpreter RedHat9: - perl-interpreter - perl-FindBin check_pgbackrest-REL2_4/tests/roles/setup_check_pgbackrest/tasks/000077500000000000000000000000001464170754600254205ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/roles/setup_check_pgbackrest/tasks/build.yml000066400000000000000000000026721464170754600272510ustar00rootroot00000000000000--- - name: Install build requirements package: name: "{{ item }}" state: latest with_items: - "{{ build_packages['common'] }}" - "{{ build_packages[ansible_os_family] }}" - name: Install ansible_distribution_major_version specific build requirements package: name: "{{ item }}" state: latest with_items: - "{{ build_packages[os_major_version] }}" vars: os_major_version: "{{ ansible_os_family }}{{ ansible_distribution_major_version }}" when: ansible_os_family == 'RedHat' - name: Check if development file exists on controller local_action: stat path="/check_pgbackrest/check_pgbackrest" register: dev_file - name: Ensure src directory exists file: state=directory path="{{ check_pgbackrest_src_dir }}" when: not dev_file.stat.exists | bool - name: Fetch check_pgbackrest from Github git: repo: "https://github.com/pgstef/check_pgbackrest" dest: "{{ check_pgbackrest_src_dir }}" version: "main" depth: 1 accept_hostkey: yes when: not dev_file.stat.exists | bool - name: Install check_pgbackrest copy: src: "{{ check_pgbackrest_src_dir }}/check_pgbackrest" remote_src: yes dest: /usr/bin/check_pgbackrest mode: '0755' when: not dev_file.stat.exists | bool - name: Install development check_pgbackrest copy: src: "/check_pgbackrest/check_pgbackrest" remote_src: no dest: /usr/bin/check_pgbackrest mode: '0755' when: dev_file.stat.exists | bool check_pgbackrest-REL2_4/tests/roles/setup_check_pgbackrest/tasks/icinga2-check.yml000066400000000000000000000033251464170754600305350ustar00rootroot00000000000000--- - set_fact: icinga_api_url: "https://localhost:5665/v1" icinga_api_user: "icinga2-director" icinga_api_pass: "anyPassWord" when: "'icinga2' in group_names" - name: Reschedule-check on all check_pgbackrest services uri: url: "{{ icinga_api_url }}/actions/reschedule-check" validate_certs: no user: "{{ icinga_api_user }}" password: "{{ icinga_api_pass }}" method: POST headers: Accept: "application/json" body_format: json body: '{ "type": "Service", "filter": "match(pattern,service.name)", "filter_vars": { "pattern": "pgbackrest*" } }' when: > 'icinga2' in group_names and reschedule_check_icinga2 - wait_for: timeout: 30 when: > 'icinga2' in group_names and reschedule_check_icinga2 - name: Get services status uri: url: "{{ icinga_api_url }}/objects/services" validate_certs: no user: "{{ icinga_api_user }}" password: "{{ icinga_api_pass }}" method: GET return_content: yes headers: Content-Type: "application/json" body_format: json body: '{ "filter": "match(pattern,service.name)", "filter_vars": { "pattern": "pgbackrest*" } }' register: icinga2_services_status when: "'icinga2' in group_names" - name: Verify services status debug: msg: - "Check {{ item.attrs.host_name }} - {{ item.attrs.name }}" - " State: {{ item.attrs.state }}" - " Last check time: {{ '%Y-%m-%d %H:%M:%S %Z' | strftime(item.attrs.last_check) }}" - " Output: {{ item.attrs.last_check_result.output }}" failed_when: "item.attrs.state != 0" loop: "{{ icinga2_services_status.json.results | sort(attribute='name') }}" loop_control: label: "{{ item.name }}" when: "'icinga2' in group_names" check_pgbackrest-REL2_4/tests/roles/setup_check_pgbackrest/tasks/icinga2-config.yml000066400000000000000000000304761464170754600307340ustar00rootroot00000000000000--- # -------------------- # SSH connection setup # -------------------- - name: Generate Icinga2 SSH keys community.crypto.openssh_keypair: path: "{{ cluster_dir }}/keys/id_icinga2" delegate_to: localhost when: "'icinga2' in group_names" - name: Ensure that .ssh exists on Icinga2 server file: path: "~nagios/.ssh" state: directory mode: '0700' become_user: "nagios" become: yes when: "'icinga2' in group_names" - name: Install keypair on Icinga2 server copy: src: "{{ item.src }}" dest: "{{ item.dest }}" mode: "{{ item.mode }}" with_items: - src: "{{ cluster_dir }}/keys/id_icinga2" dest: "~nagios/.ssh/id_rsa" mode: '0600' - src: "{{ cluster_dir }}/keys/id_icinga2.pub" dest: "~nagios/.ssh/id_rsa.pub" mode: '0640' become_user: "nagios" become: yes when: "'icinga2' in group_names" - name: Setup user accessed_by_ssh on db and repo hosts user: name: accessed_by_ssh groups: wheel append: yes when: > ( inventory_hostname in repository_server or inventory_hostname in pgbackrest_servers ) and ansible_os_family == 'RedHat' - name: Setup user accessed_by_ssh on db and repo hosts user: name: accessed_by_ssh groups: sudo append: yes when: > ( inventory_hostname in repository_server or inventory_hostname in pgbackrest_servers ) and ansible_os_family == 'Debian' - name: Add user to sudoers file on db and repo hosts lineinfile: path: /etc/sudoers regexp: '^accessed_by_ssh' line: 'accessed_by_ssh ALL=(ALL) NOPASSWD:ALL' validate: 'visudo -cf %s' when: inventory_hostname in repository_server or inventory_hostname in pgbackrest_servers - name: Authorise SSH connection on db and repo hosts authorized_key: user: accessed_by_ssh key: "{{ lookup('file', cluster_dir+'/keys/id_icinga2.pub') }}" when: inventory_hostname in repository_server or inventory_hostname in pgbackrest_servers - name: Test SSH connection from Icinga2 server to db hosts shell: "/usr/bin/ssh {{ssh_args}} {{ user }}@{{ host }} uname -a" vars: host: "{{ hostvars[item].private_ip }}" user: "accessed_by_ssh" ssh_args: "-o ConnectTimeout=10 -o BatchMode=yes -o StrictHostKeyChecking=no" loop: "{{ pgbackrest_servers }}" become_user: "nagios" become: yes when: "'icinga2' in group_names" - name: Test SSH connection from Icinga2 server to repo host shell: "/usr/bin/ssh {{ssh_args}} {{ user }}@{{ host }} uname -a" vars: host: "{{ hostvars[item].private_ip }}" user: "accessed_by_ssh" ssh_args: "-o ConnectTimeout=10 -o BatchMode=yes -o StrictHostKeyChecking=no" loop: "{{ repository_server }}" become_user: "nagios" become: yes when: "'icinga2' in group_names" # ------------------------------------ # Configure Icinga2 hosts and services # ------------------------------------ - set_fact: icinga_url: "http://127.0.0.1/icingaweb2" icinga_user: "icingaadmin" icinga_pass: "icinga" when: "'icinga2' in group_names" - name: Create Icinga2 host template telekom_mms.icinga_director.icinga_host_template: state: present url: "{{ icinga_url }}" url_username: "{{ icinga_user }}" url_password: "{{ icinga_pass }}" object_name: host-template check_command: hostalive when: "'icinga2' in group_names" - name: Create Icinga2 service template telekom_mms.icinga_director.icinga_service_template: state: present url: "{{ icinga_url }}" url_username: "{{ icinga_user }}" url_password: "{{ icinga_pass }}" object_name: service-template max_check_attempts: "5" check_interval: "1m" retry_interval: "30s" when: "'icinga2' in group_names" - name: Add db hosts to Icinga2 telekom_mms.icinga_director.icinga_host: state: present url: "{{ icinga_url }}" url_username: "{{ icinga_user }}" url_password: "{{ icinga_pass }}" object_name: "{{ item }}" address: "{{ hostvars[item].private_ip }}" imports: - "host-template" vars: os: "Linux" loop: "{{ pgbackrest_servers }}" when: "'icinga2' in group_names" - name: Add repo hosts to Icinga2 telekom_mms.icinga_director.icinga_host: state: present url: "{{ icinga_url }}" url_username: "{{ icinga_user }}" url_password: "{{ icinga_pass }}" object_name: "{{ item }}" address: "{{ hostvars[item].private_ip }}" imports: - "host-template" vars: os: "Linux" loop: "{{ repository_server }}" when: "'icinga2' in group_names" - name: Create Icinga2 check retention command telekom_mms.icinga_director.icinga_command: state: present url: "{{ icinga_url }}" url_username: "{{ icinga_user }}" url_password: "{{ icinga_pass }}" object_name: "by_ssh_pgbackrest_retention" imports: - "by_ssh" vars: by_ssh_command: "check_pgbackrest --stanza=$stanza$ --service=retention --retention-full=$retention_full$ --prefix=\"$prefix$\"" when: "'icinga2' in group_names" - name: Create Icinga2 check archives command telekom_mms.icinga_director.icinga_command: state: present url: "{{ icinga_url }}" url_username: "{{ icinga_user }}" url_password: "{{ icinga_pass }}" object_name: "by_ssh_pgbackrest_archives" imports: - "by_ssh" vars: by_ssh_command: "check_pgbackrest --stanza=$stanza$ --service=archives --prefix=\"$prefix$\"" when: "'icinga2' in group_names" - name: Create Icinga2 check retention services for db hosts telekom_mms.icinga_director.icinga_service: state: present url: "{{ icinga_url }}" url_username: "{{ icinga_user }}" url_password: "{{ icinga_pass }}" object_name: "pgbackrest_retention" imports: - "service-template" check_command: "by_ssh_pgbackrest_retention" host: "{{ item }}" vars: by_ssh_logname: "accessed_by_ssh" stanza: "{{ cluster_name }}" retention_full: "{{ hostvars[item].pgbackrest_repo_retention_full }}" prefix: "sudo -u {{ hostvars[item].pg_owner }}" loop: "{{ pgbackrest_servers }}" when: "'icinga2' in group_names" - name: Create Icinga2 check retention services for repo host telekom_mms.icinga_director.icinga_service: state: present url: "{{ icinga_url }}" url_username: "{{ icinga_user }}" url_password: "{{ icinga_pass }}" object_name: "pgbackrest_retention" imports: - "service-template" check_command: "by_ssh_pgbackrest_retention" host: "{{ item }}" vars: by_ssh_logname: "accessed_by_ssh" stanza: "{{ cluster_name }}" retention_full: "{{ hostvars[item].pgbackrest_repo_retention_full }}" prefix: "sudo -u {{ hostvars[item].pgbackrest_user }}" loop: "{{ repository_server }}" when: "'icinga2' in group_names" - name: Create Icinga2 check archives services for db hosts telekom_mms.icinga_director.icinga_service: state: present url: "{{ icinga_url }}" url_username: "{{ icinga_user }}" url_password: "{{ icinga_pass }}" object_name: "pgbackrest_archives" imports: - "service-template" check_command: "by_ssh_pgbackrest_archives" host: "{{ item }}" vars: by_ssh_logname: "accessed_by_ssh" stanza: "{{ cluster_name }}" prefix: "sudo -u {{ hostvars[item].pg_owner }}" loop: "{{ pgbackrest_servers }}" when: "'icinga2' in group_names" - name: Create Icinga2 check retention services for repo host telekom_mms.icinga_director.icinga_service: state: present url: "{{ icinga_url }}" url_username: "{{ icinga_user }}" url_password: "{{ icinga_pass }}" object_name: "pgbackrest_archives" imports: - "service-template" check_command: "by_ssh_pgbackrest_archives" host: "{{ item }}" vars: by_ssh_logname: "accessed_by_ssh" stanza: "{{ cluster_name }}" prefix: "sudo -u {{ hostvars[item].pgbackrest_user }}" loop: "{{ repository_server }}" when: "'icinga2' in group_names" # Multiple repositories support, add check commands using --repo=1 - name: Set default repo when multiple repositories are defined set_fact: default_repo_key: "1" when: "pgbackrest_repo_type is defined and pgbackrest_repo_type == 'multi'" - name: Create Icinga2 check retention command - multiple repositories telekom_mms.icinga_director.icinga_command: state: present url: "{{ icinga_url }}" url_username: "{{ icinga_user }}" url_password: "{{ icinga_pass }}" object_name: "by_ssh_pgbackrest_retention_with_repo" imports: - "by_ssh" vars: by_ssh_command: "check_pgbackrest --stanza=$stanza$ --service=retention --retention-full=$retention_full$ --prefix=\"$prefix$\" --repo={{ default_repo_key }}" when: "'icinga2' in group_names and default_repo_key is defined" - name: Create Icinga2 check archives command - multiple repositories telekom_mms.icinga_director.icinga_command: state: present url: "{{ icinga_url }}" url_username: "{{ icinga_user }}" url_password: "{{ icinga_pass }}" object_name: "by_ssh_pgbackrest_archives_with_repo" imports: - "by_ssh" vars: by_ssh_command: "check_pgbackrest --stanza=$stanza$ --service=archives --prefix=\"$prefix$\" --repo={{ default_repo_key }}" when: "'icinga2' in group_names and default_repo_key is defined" - name: Create Icinga2 check retention services for db hosts - multiple repositories telekom_mms.icinga_director.icinga_service: state: present url: "{{ icinga_url }}" url_username: "{{ icinga_user }}" url_password: "{{ icinga_pass }}" object_name: "pgbackrest_retention_repo{{ default_repo_key }}" imports: - "service-template" check_command: "by_ssh_pgbackrest_retention_with_repo" host: "{{ item }}" vars: by_ssh_logname: "accessed_by_ssh" stanza: "{{ cluster_name }}" retention_full: "{{ hostvars[item].pgbackrest_repo_retention_full }}" prefix: "sudo -u {{ hostvars[item].pg_owner }}" loop: "{{ pgbackrest_servers }}" when: "'icinga2' in group_names and default_repo_key is defined" - name: Create Icinga2 check retention services for repo host - multiple repositories telekom_mms.icinga_director.icinga_service: state: present url: "{{ icinga_url }}" url_username: "{{ icinga_user }}" url_password: "{{ icinga_pass }}" object_name: "pgbackrest_retention_repo{{ default_repo_key }}" imports: - "service-template" check_command: "by_ssh_pgbackrest_retention_with_repo" host: "{{ item }}" vars: by_ssh_logname: "accessed_by_ssh" stanza: "{{ cluster_name }}" retention_full: "{{ hostvars[item].pgbackrest_repo_retention_full }}" prefix: "sudo -u {{ hostvars[item].pgbackrest_user }}" loop: "{{ repository_server }}" when: "'icinga2' in group_names and default_repo_key is defined" - name: Create Icinga2 check archives services for db hosts - multiple repositories telekom_mms.icinga_director.icinga_service: state: present url: "{{ icinga_url }}" url_username: "{{ icinga_user }}" url_password: "{{ icinga_pass }}" object_name: "pgbackrest_archives_repo{{ default_repo_key }}" imports: - "service-template" check_command: "by_ssh_pgbackrest_archives_with_repo" host: "{{ item }}" vars: by_ssh_logname: "accessed_by_ssh" stanza: "{{ cluster_name }}" prefix: "sudo -u {{ hostvars[item].pg_owner }}" loop: "{{ pgbackrest_servers }}" when: "'icinga2' in group_names and default_repo_key is defined" - name: Create Icinga2 check retention services for repo host - multiple repositories telekom_mms.icinga_director.icinga_service: state: present url: "{{ icinga_url }}" url_username: "{{ icinga_user }}" url_password: "{{ icinga_pass }}" object_name: "pgbackrest_archives_repo{{ default_repo_key }}" imports: - "service-template" check_command: "by_ssh_pgbackrest_archives_with_repo" host: "{{ item }}" vars: by_ssh_logname: "accessed_by_ssh" stanza: "{{ cluster_name }}" prefix: "sudo -u {{ hostvars[item].pgbackrest_user }}" loop: "{{ repository_server }}" when: "'icinga2' in group_names and default_repo_key is defined" # Deploy - name: Deploy Icinga2 config uri: url: "{{ icinga_url }}/director/config/deploy" user: "{{ icinga_user }}" password: "{{ icinga_pass }}" method: POST headers: Accept: "application/json" when: "'icinga2' in group_names" - name: Check Icinga2 services include_tasks: icinga2-check.yml when: "'icinga2' in group_names and not skip_check_icinga2|bool" check_pgbackrest-REL2_4/tests/roles/setup_check_pgbackrest/tasks/main.yml000066400000000000000000000041051464170754600270670ustar00rootroot00000000000000--- - name: Create a list of primary and standby instances using pgbackrest set_fact: pgbackrest_servers: "{{ pgbackrest_servers | default([]) | union([ item ]) }}" when: "hostvars[item].pgbackrest == true" loop: "{{ groups['primary'] | list | union (groups['standby'] | default([]) | list) }}" loop_control: label: >- {{ item }} - name: Identify repository server set_fact: repository_server: "{{ groups['pgbackrest_repo_host']| default([]) | list }}" - name: Install check_pgbackrest package package: name: - check-pgbackrest state: latest when: > (inventory_hostname in repository_server or inventory_hostname in pgbackrest_servers) and not check_pgbackrest_build | bool and ansible_os_family == 'Debian' - name: Install check_pgbackrest package package: name: - nagios-plugins-pgbackrest state: latest when: > (inventory_hostname in repository_server or inventory_hostname in pgbackrest_servers) and not check_pgbackrest_build | bool and ansible_os_family == 'RedHat' - name: Create a symbolic link file: src: /usr/lib64/nagios/plugins/check_pgbackrest dest: /usr/bin/check_pgbackrest state: link when: > (inventory_hostname in repository_server or inventory_hostname in pgbackrest_servers) and not check_pgbackrest_build | bool and ansible_os_family == 'RedHat' - name: Build check_pgbackrest from sources include_tasks: build.yml vars: check_pgbackrest_src_dir: /opt/check_pgbackrest/src when: > (inventory_hostname in repository_server or inventory_hostname in pgbackrest_servers) and check_pgbackrest_build | bool - shell: check_pgbackrest --version | cut -f1 -d"," | awk '{print $3}' register: version when: inventory_hostname in repository_server or inventory_hostname in pgbackrest_servers - name: check_pgbackrest installed version debug: var=version.stdout when: inventory_hostname in repository_server or inventory_hostname in pgbackrest_servers - name: Deploy Icinga2 check services include_tasks: icinga2-config.yml when: deploy_icinga2 | bool check_pgbackrest-REL2_4/tests/roles/setup_pgbackrest/000077500000000000000000000000001464170754600231365ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/roles/setup_pgbackrest/defaults/000077500000000000000000000000001464170754600247455ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/roles/setup_pgbackrest/defaults/main.yml000066400000000000000000000021261464170754600264150ustar00rootroot00000000000000--- pgbackrest_build: false pgbackrest_configuration_file: "/etc/pgbackrest.conf" pgbackrest_excpected_release: "" pgbackrest_force_backup: false pgbackrest_git_url: "https://github.com/pgbackrest/pgbackrest.git" pgbackrest_git_branch: "main" pgbackrest_repo_path: "/var/lib/pgbackrest" pgbackrest_repo_retention_full: 1 pgbackrest_repo_type: "posix" pgbackrest_repo_s3_endpoint: "{{ cluster_name }}-minio" pgbackrest_repo_azure_host: "{{ cluster_name }}-azurite" pgbackrest_repo1_cipher_pass: "it3BF2WqbFCNbY4KkSbvUsRybHyJkvcmQYAOB46x3qXfrc0EKqGGClsh42Q1g91O" pgbackrest_repo2_cipher_pass: "TTSSHTZOY40hfvfBAZh8ytg3Qm06cJ2kIaeemrzZZSmDUvpBW0RQfH0Ut+utqjkN" pgbackrest_user: "pgbackrest" build_packages: common: - git - make - meson - gcc Debian: - libpq-dev - libssl-dev - libxml2-dev - pkg-config - liblz4-dev - libzstd-dev - libbz2-dev - libz-dev - libyaml-dev - libssh2-1-dev - python3-setuptools RedHat: - openssl-devel - libxml2-devel - lz4-devel - libzstd-devel - bzip2-devel - libyaml-devel - libssh2-devel check_pgbackrest-REL2_4/tests/roles/setup_pgbackrest/tasks/000077500000000000000000000000001464170754600242635ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/roles/setup_pgbackrest/tasks/backup.yml000066400000000000000000000050261464170754600262560ustar00rootroot00000000000000--- - name: Take full backup on repository server command: > pgbackrest backup --type=full --stanza="{{ cluster_name }}" become_user: "{{ pgbackrest_user }}" become: yes when: inventory_hostname in repository_server and pgbackrest_force_backup - name: Take diff backup on repository server command: > pgbackrest backup --type=diff --stanza="{{ cluster_name }}" become_user: "{{ pgbackrest_user }}" become: yes when: inventory_hostname in repository_server and pgbackrest_force_backup - name: Take incr backup on repository server command: > pgbackrest backup --type=incr --stanza="{{ cluster_name }}" become_user: "{{ pgbackrest_user }}" become: yes when: inventory_hostname in repository_server and pgbackrest_force_backup - name: Take incr backup from standby on repository server command: > pgbackrest backup --type=incr --backup-standby --stanza="{{ cluster_name }}" become_user: "{{ pgbackrest_user }}" become: yes when: inventory_hostname in repository_server and pgbackrest_force_backup and (pgbackrest_servers|length > 1) - name: Take full backup on primary server command: > pgbackrest backup --type=full --stanza="{{ cluster_name }}" become_user: "{{ postgres_user }}" become: yes when: pgbackrest_force_backup and (not repository_server|length > 0) and (inventory_hostname in pgbackrest_servers) and ('primary' in group_names) - name: Take diff backup on primary server command: > pgbackrest backup --type=diff --stanza="{{ cluster_name }}" become_user: "{{ postgres_user }}" become: yes when: pgbackrest_force_backup and (not repository_server|length > 0) and (inventory_hostname in pgbackrest_servers) and ('primary' in group_names) - name: Take incr backup on primary server command: > pgbackrest backup --type=incr --stanza="{{ cluster_name }}" become_user: "{{ postgres_user }}" become: yes when: pgbackrest_force_backup and (not repository_server|length > 0) and (inventory_hostname in pgbackrest_servers) and ('primary' in group_names) - name: Take backup on standby from primary server using ssh command: "/usr/bin/ssh {{ host }} pgbackrest backup --type=incr --backup-standby --stanza={{ cluster_name }}" vars: host: "{{ hostvars[item].private_ip }}" loop: "{{ pgbackrest_standbies }}" become_user: "{{ postgres_user }}" become: yes when: pgbackrest_force_backup and (not repository_server|length > 0) and (inventory_hostname in pgbackrest_servers) and ('primary' in group_names)check_pgbackrest-REL2_4/tests/roles/setup_pgbackrest/tasks/build.yml000066400000000000000000000043661464170754600261160ustar00rootroot00000000000000--- - name: Install build requirements package: name: "{{ item }}" state: latest with_items: - "{{ build_packages['common'] }}" - "{{ build_packages[ansible_os_family] }}" - name: Install PG devel package package: name: "postgresql{{ pg_version }}-devel" state: latest when: ansible_os_family == 'RedHat' - name: Ensure src directory exists file: state=directory path="{{ pgbackrest_src_dir }}" - name: Fetch pgbackrest from {{ pgbackrest_git_url }}, {{ pgbackrest_git_branch }} git: repo: "{{ pgbackrest_git_url }}" dest: "{{ pgbackrest_src_dir }}" version: "{{ pgbackrest_git_branch }}" depth: 1 accept_hostkey: yes - name: Fetch currently checked-out branch in {{ pgbackrest_src_dir }} shell: git branch | sed -n 's/^\* //p' args: chdir: "{{ pgbackrest_src_dir }}" register: git_branch - name: pgbackrest source branch to build debug: var=git_branch.stdout - name: Remove old build directory file: state=absent path="{{ pgbackrest_build_dir }}" force=yes - name: Ensure build directory exists file: state=directory path="{{ pgbackrest_build_dir }}" - set_fact: pkg_config_path: "PKG_CONFIG_PATH=/usr/pgsql-{{ pg_version }}/lib/pkgconfig" when: ansible_os_family == 'RedHat' - name: Configure pgbackrest shell: "{{ pkg_config_path | default('', true) }} meson setup {{ pgbackrest_build_dir }} {{ pgbackrest_src_dir }} --prefix={{ pgbackrest_build_prefix }} --bindir={{ pgbackrest_build_prefix }}/bin" - name: Build pgbackrest shell: "ninja -C {{ pgbackrest_build_dir }}" - name: Install pgbackrest shell: "ninja install" args: chdir: "{{ pgbackrest_build_dir }}" # Old make system has been deprecated # - set_fact: # cppflags: "CPPFLAGS='-I /usr/pgsql-{{ pg_version }}/include' LDFLAGS='-L/usr/pgsql-{{ pg_version }}/lib'" # - name: Configure pgbackrest # shell: "{{ pgbackrest_src_dir }}/src/configure {{ cppflags | default('', true) }} --prefix={{ pgbackrest_build_prefix }} --bindir={{ pgbackrest_build_prefix }}/bin" # args: # chdir: "{{ pgbackrest_build_dir }}" # - name: Build pgbackrest # shell: "make" # args: # chdir: "{{ pgbackrest_build_dir }}" # - name: Install pgbackrest # shell: "make install" # args: # chdir: "{{ pgbackrest_build_dir }}" check_pgbackrest-REL2_4/tests/roles/setup_pgbackrest/tasks/client.yml000066400000000000000000000013121464170754600262610ustar00rootroot00000000000000--- - name: Build configuration file {{ pgbackrest_configuration_file }} template: src: "pgbackrest-dbserver.conf.j2" dest: "{{ pgbackrest_configuration_file }}" owner: "{{ postgres_user }}" group: "{{ postgres_user }}" mode: 0640 become: yes register: pgbackrest_config - name: Record if a new backup is needed set_fact: pgbackrest_force_backup: true when: pgbackrest_config.changed - name: Ensure pgbackrest directories exist with the right ownership and permissions file: name: "{{ item }}" state: directory owner: "{{ postgres_user }}" group: "{{ postgres_user }}" mode: 0770 loop: - /var/log/pgbackrest - /var/spool/pgbackrest become: yes check_pgbackrest-REL2_4/tests/roles/setup_pgbackrest/tasks/create_user.yml000066400000000000000000000011321464170754600273040ustar00rootroot00000000000000--- - name: Ensure pgbackrest group exists group: name: "{{ pgbackrest_user }}" state: present become: yes - name: Ensure pgbackrest system user {{ pgbackrest_user }} exists user: name: "{{ pgbackrest_user }}" group: "{{ pgbackrest_user }}" state: present become: yes - name: Ensure pgbackrest directories exist with the right ownership and permissions file: name: "{{ item }}" state: directory owner: "{{ pgbackrest_user }}" group: "{{ pgbackrest_user }}" mode: 0770 loop: - /var/log/pgbackrest - "{{ pgbackrest_repo_path }}" become: yescheck_pgbackrest-REL2_4/tests/roles/setup_pgbackrest/tasks/info.yml000066400000000000000000000007371464170754600257500ustar00rootroot00000000000000--- - name: Info command on repository server command: > pgbackrest info --stanza="{{ cluster_name }}" become_user: "{{ pgbackrest_user }}" become: yes when: inventory_hostname in repository_server register: info - name: Info command on database server command: > pgbackrest info --stanza="{{ cluster_name }}" become_user: "{{ postgres_user }}" become: yes when: inventory_hostname in pgbackrest_servers register: info - debug: var=info.stdout_linescheck_pgbackrest-REL2_4/tests/roles/setup_pgbackrest/tasks/main.yml000066400000000000000000000051541464170754600257370ustar00rootroot00000000000000--- - name: Create a list of primary and standby instances using pgbackrest set_fact: pgbackrest_servers: "{{ pgbackrest_servers | default([]) | union([ item ]) }}" when: "hostvars[item].pgbackrest == true" loop: "{{ groups['primary'] | list | union (groups['standby'] | default([]) | list) }}" loop_control: label: >- {{ item }} - name: Create a list of standby instances using pgbackrest set_fact: pgbackrest_standbies: "{{ pgbackrest_standbies | default([]) | union([ item ]) }}" when: "hostvars[item].pgbackrest == true" loop: "{{ groups['standby'] | default([]) | list }}" loop_control: label: >- {{ item }} - name: Identify repository server set_fact: repository_server: "{{ groups['pgbackrest_repo_host']| default([]) | list }}" - set_fact: postgres_user: "{{ pg_owner }}" when: inventory_hostname in pgbackrest_servers - set_fact: pgbackrest_user: "{{ pgbackrest_user }}" pgbackrest_repo_retention_full: "{{ pgbackrest_repo_retention_full }}" pgbackrest_repo_type: "{{ pgbackrest_repo_type }}" - name: Ensure repository type is valid assert: msg: "Unsupported repository type: '{{ pgbackrest_repo_type }}'" that: - pgbackrest_repo_type in _available_repo_types vars: _available_repo_types: - 'azure' - 'multi' - 'posix' - 's3' - name: Build pgbackrest from sources include_tasks: build.yml vars: pgbackrest_src_dir: /opt/pgbackrest/src pgbackrest_build_dir: /opt/pgbackrest/build pgbackrest_build_prefix: /usr when: pgbackrest_build | bool - name: Install pgbackrest package package: name: - pgbackrest state: latest when: not pgbackrest_build | bool - shell: pgbackrest version | awk '{print $2}' register: version - name: pgbackrest installed version debug: var=version.stdout - name: Ensure the pgbackrest installed version match {{ pgbackrest_excpected_release }} assert: that: - pgbackrest_excpected_release in version.stdout - include_tasks: server.yml when: inventory_hostname in repository_server - include_tasks: ssh_setup.yml - include_tasks: client.yml when: inventory_hostname in pgbackrest_servers - name: Configure archive_command ansible.builtin.include_role: name: manage_dbserver tasks_from: manage_postgres_params vars: pg_postgres_conf_params: - name: archive_command value: "pgbackrest --stanza={{ cluster_name }} --log-level-console=debug archive-push %p" when: inventory_hostname in pgbackrest_servers - include_tasks: stanza-create.yml - include_tasks: backup.yml - include_tasks: info.yml check_pgbackrest-REL2_4/tests/roles/setup_pgbackrest/tasks/server.yml000066400000000000000000000007131464170754600263150ustar00rootroot00000000000000--- - include_tasks: create_user.yml - name: Build configuration file {{ pgbackrest_configuration_file }} template: src: "pgbackrest-repository.conf.j2" dest: "{{ pgbackrest_configuration_file }}" owner: "{{ pgbackrest_user }}" group: "{{ pgbackrest_user }}" mode: 0640 become: yes register: pgbackrest_config - name: Record if a new backup is needed set_fact: pgbackrest_force_backup: true when: pgbackrest_config.changed check_pgbackrest-REL2_4/tests/roles/setup_pgbackrest/tasks/ssh_setup.yml000066400000000000000000000075571464170754600270410ustar00rootroot00000000000000--- - name: Ensure the localhost keys directory exists file: state: directory path: "{{ cluster_dir }}/keys" delegate_to: localhost - name: Generate {{ pgbackrest_user }} SSH keys community.crypto.openssh_keypair: path: "{{ cluster_dir }}/keys/id_pgbackrest" delegate_to: localhost when: inventory_hostname in repository_server - name: Generate db hosts SSH keys community.crypto.openssh_keypair: path: "{{ cluster_dir }}/keys/id_postgres" delegate_to: localhost when: inventory_hostname in pgbackrest_servers - name: Authorise SSH connection from {{ pgbackrest_user }} authorized_key: user: "{{ postgres_user }}" key: "{{ lookup('file', cluster_dir+'/keys/id_pgbackrest.pub') }}" when: inventory_hostname in pgbackrest_servers and repository_server|length > 0 - name: Authorise SSH connection between db hosts authorized_key: user: "{{ postgres_user }}" key: "{{ lookup('file', cluster_dir+'/keys/id_postgres.pub') }}" when: inventory_hostname in pgbackrest_servers - name: Authorise SSH connection to {{ pgbackrest_user }} authorized_key: user: "{{ pgbackrest_user }}" key: "{{ lookup('file', cluster_dir+'/keys/id_postgres.pub') }}" when: inventory_hostname in repository_server - name: Ensure that .ssh exists on db hosts file: path: "~{{ postgres_user }}/.ssh" state: directory mode: '0700' become_user: "{{ postgres_user }}" become: yes when: inventory_hostname in pgbackrest_servers - name: Install keypair on db hosts copy: src: "{{ item.src }}" dest: "{{ item.dest }}" mode: "{{ item.mode }}" with_items: - src: "{{ cluster_dir }}/keys/id_postgres" dest: "~{{ postgres_user }}/.ssh/id_rsa" mode: '0600' - src: "{{ cluster_dir }}/keys/id_postgres.pub" dest: "~{{ postgres_user }}/.ssh/id_rsa.pub" mode: '0640' become_user: "{{ postgres_user }}" become: yes when: inventory_hostname in pgbackrest_servers - name: Ensure that .ssh exists on repo host file: path: "~{{ pgbackrest_user }}/.ssh" state: directory mode: '0700' become_user: "{{ pgbackrest_user }}" become: yes when: inventory_hostname in repository_server - name: Install keypair on repo host copy: src: "{{ item.src }}" dest: "{{ item.dest }}" mode: "{{ item.mode }}" with_items: - src: "{{ cluster_dir }}/keys/id_pgbackrest" dest: "~{{ pgbackrest_user }}/.ssh/id_rsa" mode: '0600' - src: "{{ cluster_dir }}/keys/id_pgbackrest.pub" dest: "~{{ pgbackrest_user }}/.ssh/id_rsa.pub" mode: '0640' become_user: "{{ pgbackrest_user }}" become: yes when: inventory_hostname in repository_server - name: Test SSH connection from repo host shell: "/usr/bin/ssh {{ssh_args}} {{ user }}@{{ host }} uname -a" vars: host: "{{ hostvars[item].private_ip }}" user: "{{ hostvars[item].pg_owner }}" ssh_args: "-o ConnectTimeout=10 -o BatchMode=yes -o StrictHostKeyChecking=no" loop: "{{ pgbackrest_servers }}" become_user: "{{ pgbackrest_user }}" become: yes when: inventory_hostname in repository_server - name: Test SSH connection to repo host shell: "/usr/bin/ssh {{ssh_args}} {{ user }}@{{ host }} uname -a" vars: host: "{{ hostvars[pgbackrest_repo_host].private_ip }}" user: "{{ pgbackrest_user }}" ssh_args: "-o ConnectTimeout=10 -o BatchMode=yes -o StrictHostKeyChecking=no" become_user: "{{ postgres_user }}" become: yes when: inventory_hostname in pgbackrest_servers and repository_server|length > 0 - name: Test SSH connection to db hosts shell: "/usr/bin/ssh {{ssh_args}} {{ user }}@{{ host }} uname -a" vars: host: "{{ hostvars[item].private_ip }}" user: "{{ hostvars[item].pg_owner }}" ssh_args: "-o ConnectTimeout=10 -o BatchMode=yes -o StrictHostKeyChecking=no" loop: "{{ pgbackrest_servers }}" become_user: "{{ postgres_user }}" become: yes when: inventory_hostname in pgbackrest_serverscheck_pgbackrest-REL2_4/tests/roles/setup_pgbackrest/tasks/stanza-create.yml000066400000000000000000000020131464170754600275430ustar00rootroot00000000000000--- - name: Initialise the stanza on repository server command: > pgbackrest stanza-create --stanza="{{ cluster_name }}" become_user: "{{ pgbackrest_user }}" become: yes when: inventory_hostname in repository_server - name: check cluster configuration on repository server command: > pgbackrest check --stanza="{{ cluster_name }}" become_user: "{{ pgbackrest_user }}" become: yes when: inventory_hostname in repository_server - name: Initialise the stanza on primary server command: > pgbackrest stanza-create --stanza="{{ cluster_name }}" become_user: "{{ postgres_user }}" become: yes when: (not repository_server|length > 0) and (inventory_hostname in pgbackrest_servers) and ('primary' in group_names) - name: check cluster configuration on primary server command: > pgbackrest check --stanza="{{ cluster_name }}" become_user: "{{ postgres_user }}" become: yes when: (not repository_server|length > 0) and (inventory_hostname in pgbackrest_servers) and ('primary' in group_names) check_pgbackrest-REL2_4/tests/roles/setup_pgbackrest/templates/000077500000000000000000000000001464170754600251345ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/roles/setup_pgbackrest/templates/pgbackrest-dbserver.conf.j2000066400000000000000000000055231464170754600322610ustar00rootroot00000000000000[global] {% if repository_server|length > 0 %} repo1-host={{ hostvars[pgbackrest_repo_host].private_ip }} repo1-host-user={{ pgbackrest_user }} repo1-cipher-type=aes-256-cbc repo1-cipher-pass={{ pgbackrest_repo1_cipher_pass }} {% if pgbackrest_repo_type == "multi" %} repo2-host={{ hostvars[pgbackrest_repo_host].private_ip }} repo2-host-user={{ pgbackrest_user }} repo2-cipher-type=aes-256-cbc repo2-cipher-pass={{ pgbackrest_repo2_cipher_pass }} repo1-path=/repo1 repo2-path=/repo2 {% endif %} {% elif pgbackrest_repo_type == "s3" %} repo1-type=s3 repo1-path=/repo1 repo1-s3-endpoint={{ pgbackrest_repo_s3_endpoint }} repo1-s3-region=eu-west-2 repo1-s3-bucket=bucket repo1-s3-key=accessKey repo1-s3-key-secret=superSECRETkey repo1-s3-uri-style=path repo1-storage-verify-tls=n repo1-retention-full={{ pgbackrest_repo_retention_full }} repo1-cipher-type=aes-256-cbc repo1-cipher-pass={{ pgbackrest_repo1_cipher_pass }} start-fast=y {% elif pgbackrest_repo_type == "azure" %} repo1-type=azure repo1-path=/repo1 repo1-storage-host={{ pgbackrest_repo_azure_host }} repo1-azure-account=pgbackrest repo1-azure-key=aF49wnZP repo1-azure-container=container repo1-storage-verify-tls=n repo1-retention-full={{ pgbackrest_repo_retention_full }} repo1-cipher-type=aes-256-cbc repo1-cipher-pass={{ pgbackrest_repo1_cipher_pass }} start-fast=y {% elif pgbackrest_repo_type == "multi" %} repo1-type=s3 repo1-path=/repo1 repo1-s3-endpoint={{ pgbackrest_repo_s3_endpoint }} repo1-s3-region=eu-west-2 repo1-s3-bucket=bucket repo1-s3-key=accessKey repo1-s3-key-secret=superSECRETkey repo1-s3-uri-style=path repo1-storage-verify-tls=n repo1-retention-full={{ pgbackrest_repo_retention_full }} repo1-cipher-type=aes-256-cbc repo1-cipher-pass={{ pgbackrest_repo1_cipher_pass }} repo2-type=azure repo2-path=/repo2 repo2-storage-host={{ pgbackrest_repo_azure_host }} repo2-azure-account=pgbackrest repo2-azure-key=aF49wnZP repo2-azure-container=container repo2-storage-verify-tls=n repo2-retention-full={{ pgbackrest_repo_retention_full }} repo2-cipher-type=aes-256-cbc repo2-cipher-pass={{ pgbackrest_repo2_cipher_pass }} start-fast=y {% else %} repo1-type=posix repo1-path={{ pgbackrest_repo_path }} repo1-retention-full={{ pgbackrest_repo_retention_full }} repo1-cipher-type=aes-256-cbc repo1-cipher-pass={{ pgbackrest_repo1_cipher_pass }} start-fast=y {% endif %} log-level-console=info log-level-file=debug delta=y process-max=2 compress-type=zst [{{ cluster_name }}] pg1-path={{ pg_data }} pg1-user={{ postgres_user }} pg1-port={{ pg_port }} pg1-socket-path={{ pg_unix_socket_directories[0] }} {% if 'standby' in group_names %} backup-standby=y pg2-host={{ upstream_node_private_ip }} pg2-host-user={{ postgres_user }} pg2-path={{ pg_data }} recovery-option=primary_conninfo=host={{ upstream_node_private_ip }} user={{ pg_replication_user }} port={{ pg_port }} application_name={{ inventory_hostname }} {% endif %} check_pgbackrest-REL2_4/tests/roles/setup_pgbackrest/templates/pgbackrest-repository.conf.j2000066400000000000000000000042341464170754600326620ustar00rootroot00000000000000[global] {% if pgbackrest_repo_type == "s3" %} repo1-type=s3 repo1-path=/repo1 repo1-s3-endpoint={{ pgbackrest_repo_s3_endpoint }} repo1-s3-region=eu-west-2 repo1-s3-bucket=bucket repo1-s3-key=accessKey repo1-s3-key-secret=superSECRETkey repo1-s3-uri-style=path repo1-storage-verify-tls=n repo1-retention-full={{ pgbackrest_repo_retention_full }} repo1-cipher-type=aes-256-cbc repo1-cipher-pass={{ pgbackrest_repo1_cipher_pass }} {% elif pgbackrest_repo_type == "azure" %} repo1-type=azure repo1-path=/repo1 repo1-storage-host={{ pgbackrest_repo_azure_host }} repo1-azure-account=pgbackrest repo1-azure-key=aF49wnZP repo1-azure-container=container repo1-storage-verify-tls=n repo1-retention-full={{ pgbackrest_repo_retention_full }} repo1-cipher-type=aes-256-cbc repo1-cipher-pass={{ pgbackrest_repo1_cipher_pass }} {% elif pgbackrest_repo_type == "multi" %} repo1-type=s3 repo1-path=/repo1 repo1-s3-endpoint={{ pgbackrest_repo_s3_endpoint }} repo1-s3-region=eu-west-2 repo1-s3-bucket=bucket repo1-s3-key=accessKey repo1-s3-key-secret=superSECRETkey repo1-s3-uri-style=path repo1-storage-verify-tls=n repo1-retention-full={{ pgbackrest_repo_retention_full }} repo1-cipher-type=aes-256-cbc repo1-cipher-pass={{ pgbackrest_repo1_cipher_pass }} repo2-type=azure repo2-path=/repo2 repo2-storage-host={{ pgbackrest_repo_azure_host }} repo2-azure-account=pgbackrest repo2-azure-key=aF49wnZP repo2-azure-container=container repo2-storage-verify-tls=n repo2-retention-full={{ pgbackrest_repo_retention_full }} repo2-cipher-type=aes-256-cbc repo2-cipher-pass={{ pgbackrest_repo2_cipher_pass }} {% else %} repo1-type=posix repo1-path={{ pgbackrest_repo_path }} repo1-retention-full={{ pgbackrest_repo_retention_full }} repo1-cipher-type=aes-256-cbc repo1-cipher-pass={{ pgbackrest_repo1_cipher_pass }} {% endif %} log-level-console=info log-level-file=debug start-fast=y delta=y process-max=2 compress-type=zst [{{ cluster_name }}] {% for server in pgbackrest_servers %} {% set v = hostvars[server] %} pg{{ loop.index }}-host={{ v.private_ip }} pg{{ loop.index }}-host-user={{ v.pg_owner }} pg{{ loop.index }}-path={{ v.pg_data }} pg{{ loop.index }}-socket-path={{ v.pg_unix_socket_directories[0] }} {% endfor %} check_pgbackrest-REL2_4/tests/roles/setup_replication/000077500000000000000000000000001464170754600233225ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/roles/setup_replication/defaults/000077500000000000000000000000001464170754600251315ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/roles/setup_replication/defaults/main.yml000066400000000000000000000002721464170754600266010ustar00rootroot00000000000000--- pg_instance_name: "main" pg_basebackup: "{{ pg_bin_path }}/pg_basebackup -Fp -R -l standby --checkpoint=fast --wal-method=stream" pg_basebackup_options: "" pg_allow_ip_addresses: [] check_pgbackrest-REL2_4/tests/roles/setup_replication/tasks/000077500000000000000000000000001464170754600244475ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/roles/setup_replication/tasks/configure_node.yml000066400000000000000000000015351464170754600301640ustar00rootroot00000000000000--- - name: Add password in pgpass ansible.builtin.include_role: name: manage_dbserver tasks_from: manage_pgpass vars: pg_pgpass_values: - user: "{{ pg_replication_user }}" password: "{{ pg_replication_user_password }}" create: true - name: Create standby ansible.builtin.shell: > PGAPPNAME={{ inventory_hostname }} {{ pg_basebackup }} args: creates: "{{ pg_data }}/PG_VERSION" become: true become_user: "{{ pg_owner }}" async: 180000 poll: 60 - name: Enable and start the service ansible.builtin.systemd: name: "{{ pg_service }}" daemon_reload: true state: started enabled: true become: true - name: Update standby hba config ansible.builtin.include_role: name: manage_dbserver tasks_from: manage_hba_conf vars: pg_hba_ip_addresses: "{{ pg_allow_ip_addresses }}" check_pgbackrest-REL2_4/tests/roles/setup_replication/tasks/create_directories.yml000066400000000000000000000032401464170754600310300ustar00rootroot00000000000000--- - name: Initialize postgres database for Debian ansible.builtin.shell: > {{ pg_initdb }} args: executable: /bin/bash creates: "/etc/postgresql/{{ pg_version }}/{{ pg_instance_name }}/postgresql.conf" environment: PGSETUP_INITDB_OPTIONS: "{{ pg_initdb_options }}" register: initdb_cmd when: - ansible_os_family == 'Debian' become: true - name: Remove the {{ pg_instance_name }} data directory ansible.builtin.file: path: "{{ pg_data }}" state: absent when: - ansible_os_family == 'Debian' - initdb_cmd is defined - initdb_cmd.rc == 0 - initdb_cmd.changed become: true - name: Ensure postgres data directory exists ansible.builtin.file: path: "{{ pg_data }}" owner: "{{ pg_owner }}" group: "{{ pg_group }}" mode: "0700" state: directory become: true - name: Ensure postgres log directory exists ansible.builtin.file: path: "{{ pg_log }}" owner: "{{ pg_owner }}" group: "{{ pg_group }}" mode: "0700" state: directory become: true when: pg_log|length > 0 and pg_data not in pg_log - name: Ensure postgres wal directory exists ansible.builtin.file: path: "{{ pg_wal }}" owner: "{{ pg_owner }}" group: "{{ pg_group }}" mode: "0700" state: directory become: true when: pg_wal|length > 0 and pg_data not in pg_wal - name: Create unix socket domain directories ansible.builtin.file: path: "{{ line_item }}" owner: "{{ pg_owner }}" group: "{{ pg_group }}" mode: "02775" state: directory with_items: "{{ pg_unix_socket_directories }}" loop_control: loop_var: line_item become: true when: pg_unix_socket_directories|length > 0 check_pgbackrest-REL2_4/tests/roles/setup_replication/tasks/main.yml000066400000000000000000000047651464170754600261320ustar00rootroot00000000000000--- - name: Reference PG variables ansible.builtin.include_vars: "PG_{{ ansible_os_family }}.yml" - name: Gather service facts ansible.builtin.service_facts: - name: Firewall open Postgres TCP port {{ pg_port }} ansible.posix.firewalld: port: "{{ pg_port }}/tcp" permanent: true state: enabled immediate: true when: - ansible_facts.services['firewalld.service'] is defined - ansible_facts.services['firewalld.service'].state == 'running' - ansible_facts.services['firewalld.service'].status == 'enabled' become: true - name: Gather the cluster_nodes information ansible.builtin.set_fact: pg_cluster_nodes: "{{ lookup('pg_sr_cluster_nodes', wantlist=True) }}" - name: Get the primary information ansible.builtin.set_fact: primary_inventory_hostname: "{{ node.inventory_hostname }}" loop: "{{ pg_cluster_nodes }}" loop_control: loop_var: node when: node.node_type == 'primary' run_once: true - name: Prepare hba list ansible.builtin.set_fact: pg_allow_ip_addresses: >- {{ pg_allow_ip_addresses | default([]) + [ { "contype": "host", "users": pg_replication_user, "source": node.private_ip + "/32", "databases": "replication" }, { "contype": "host", "users": pg_replication_user, "source": node.private_ip + "/32", "databases": "postgres" } ] }} loop: "{{ pg_cluster_nodes }}" loop_control: loop_var: node run_once: true - name: Update primary for replication ansible.builtin.import_tasks: primary_settings.yml run_once: true delegate_to: "{{ primary_inventory_hostname }}" - name: Call upstream update based on the upstream node ansible.builtin.import_tasks: upstream_node.yml when: - hostvars[inventory_hostname].upstream_node_private_ip is defined - name: Build standby service check become: true block: - name: Create directories if not exists ansible.builtin.import_tasks: create_directories.yml - name: Take PG base backup ansible.builtin.import_tasks: pg_basebackup.yml - name: Setup systemd file ansible.builtin.import_tasks: pg_setup_systemd.yml - name: Configure standby node ansible.builtin.import_tasks: configure_node.yml - name: Validate replication setup ansible.builtin.import_tasks: validate_setup_replication.yml - name: Reset variables ansible.builtin.set_fact: primary_inventory_hostname: "" pg_allow_ip_addresses: [] register: output check_pgbackrest-REL2_4/tests/roles/setup_replication/tasks/pg_basebackup.yml000066400000000000000000000017421464170754600277640ustar00rootroot00000000000000--- - name: Define pg_basebackup command. ansible.builtin.set_fact: pg_basebackup: "{{ pg_basebackup + ' -D ' + pg_data }}" - name: Verify pg_wal and accordingly add the in the pg_basebackup ansible.builtin.set_fact: pg_basebackup: "{{ pg_basebackup + ' --waldir=' + pg_wal }}" when: pg_wal|length > 0 and not pg_data in pg_wal - name: Set replication user information ansible.builtin.set_fact: pg_basebackup: "{{ pg_basebackup + ' --username=' + pg_replication_user }}" when: pg_replication_user|length > 0 - name: Set host and port ansible.builtin.set_fact: pg_basebackup: "{{ pg_basebackup + ' --host=' + hostvars[inventory_hostname].upstream_node_private_ip + ' --port=' + pg_port | string }}" when: - hostvars[inventory_hostname].upstream_node_private_ip is defined - name: Use other supplied options if given ansible.builtin.set_fact: pg_basebackup: "{{ pg_basebackup + ' ' + pg_basebackup_options }}" when: pg_basebackup_options|length > 0 check_pgbackrest-REL2_4/tests/roles/setup_replication/tasks/pg_setup_systemd.yml000066400000000000000000000021121464170754600305640ustar00rootroot00000000000000--- - name: Copy global systemd unit file to /etc/systemd/system ansible.builtin.copy: remote_src: true src: "/usr/lib/systemd/system/postgresql-{{ pg_version }}.service" dest: "/etc/systemd/system/{{ pg_service }}.service" mode: "0600" owner: root group: root become: true when: - ansible_os_family == 'RedHat' - name: Update systemd unit file ansible.builtin.lineinfile: dest: "/etc/systemd/system/{{ pg_service }}.service" regexp: "{{ item.regexp }}" line: "{{ item.line }}" insertafter: "{{ item.insertafter | default(omit) }}" loop: - { regexp: "^Description=.*", line: "Description=Database server PG {{ pg_version }} - Instance: {{ pg_instance_name }}"} - { regexp: "^Environment=PGDATA.*", line: "Environment=PGDATA={{ pg_data }}" } - { regexp: "^PIDFile=.*", line: "PIDFile={{ pg_data }}/postmaster.pid", insertafter: "^\\[Service\\]$" } - { regexp: "^ExecStopPost=.*", line: "ExecStopPost=+/usr/bin/systemctl daemon-reload", insertafter: "^\\[Service\\]$" } become: true when: - ansible_os_family == 'RedHat' check_pgbackrest-REL2_4/tests/roles/setup_replication/tasks/primary_settings.yml000066400000000000000000000026701464170754600306020ustar00rootroot00000000000000--- - name: Generate the pg_replication_user_password ansible.builtin.include_role: name: manage_dbserver tasks_from: generate_password vars: input_user: "{{ pg_replication_user }}" input_password: "{{ pg_replication_user_password }}" when: pg_replication_user_password|length < 1 - name: Set pg_replication_user_password ansible.builtin.set_fact: pg_replication_user_password: "{{ input_password }}" when: pg_replication_user_password|length < 1 - name: Set postgres replication users's database cluster password ansible.builtin.include_role: name: manage_dbserver tasks_from: manage_users vars: pg_users: - name: "{{ pg_replication_user }}" pass: "{{ pg_replication_user_password }}" role_attr_flags: replication - name: Grant minimum privileges to replication user for rewind ansible.builtin.include_role: name: manage_dbserver tasks_from: manage_privileges vars: pg_grant_privileges: - type: function privileges: execute schema: pg_catalog objects: "pg_ls_dir(text:boolean:boolean),pg_stat_file(text:boolean)" roles: "{{ pg_replication_user }}" database: "{{ pg_database }}" - type: function privileges: execute schema: pg_catalog objects: "pg_read_binary_file(text),pg_read_binary_file(text:bigint:bigint:boolean)" roles: "{{ pg_replication_user }}" database: "{{ pg_database }}" check_pgbackrest-REL2_4/tests/roles/setup_replication/tasks/upstream_node.yml000066400000000000000000000010171464170754600300360ustar00rootroot00000000000000--- - name: Get upstream node info ansible.builtin.set_fact: upstream_inventory_hostname: "{{ node.inventory_hostname }}" when: - hostvars[inventory_hostname].upstream_node_private_ip is defined - node.private_ip == hostvars[inventory_hostname].upstream_node_private_ip loop: "{{ lookup('pg_sr_cluster_nodes', wantlist=True) }}" loop_control: loop_var: node - name: Update upstream node config ansible.builtin.import_tasks: upstream_node_update.yml delegate_to: "{{ upstream_inventory_hostname }}" check_pgbackrest-REL2_4/tests/roles/setup_replication/tasks/upstream_node_update.yml000066400000000000000000000010001464170754600313700ustar00rootroot00000000000000--- - name: Update the pgpass with replication users password ansible.builtin.include_role: name: manage_dbserver tasks_from: manage_pgpass vars: pg_pgpass_values: - user: "{{ pg_replication_user }}" password: "{{ pg_replication_user_password }}" create: true run_once: true - name: Update hba config ansible.builtin.include_role: name: manage_dbserver tasks_from: manage_hba_conf vars: pg_hba_ip_addresses: "{{ pg_allow_ip_addresses }}" run_once: true check_pgbackrest-REL2_4/tests/roles/setup_replication/tasks/validate_setup_replication.yml000066400000000000000000000055631464170754600326050ustar00rootroot00000000000000--- - name: Run query to check if repuser was created correctly ansible.builtin.include_role: name: manage_dbserver tasks_from: execute_sql_query apply: delegate_to: "{{ primary_inventory_hostname }}" run_once: true vars: pg_query: - query: "select * from pg_user where usename = 'repuser' and userepl = 't'" db: "{{ pg_database }}" - name: Set repuser_query_result with sql_query_output ansible.builtin.set_fact: repuser_query_result: "{{ sql_query_output }}" become: true - name: Check if repuser was created correctly ansible.builtin.assert: that: - repuser_query_result.results[0].query_result[0]['usename'] == 'repuser' - repuser_query_result.results[0].query_result[0]['userepl']|bool fail_msg: "repuser was not succesfully created" success_msg: "repuser was succesfully created" run_once: true - name: Get the standby nodes information ansible.builtin.set_fact: standby_node_info: "{{ pg_cluster_nodes | selectattr('node_type', 'equalto', 'standby') | default([]) | list }}" - name: Run query to check if pg_stat_replication gives correct results ansible.builtin.include_role: name: manage_dbserver tasks_from: execute_sql_query apply: delegate_to: "{{ primary_inventory_hostname }}" run_once: true vars: pg_query: - query: "select application_name from pg_stat_replication" db: "{{ pg_database }}" - name: Set pg_stat_query_result with sql_query_output ansible.builtin.set_fact: pg_stat_query_result: "{{ sql_query_output }}" become: true - name: Check if pg_stat_replication gives correct results ansible.builtin.assert: that: - pg_stat_query_result.results[0].query_result|length == standby_node_info|length fail_msg: "Not enough replication connections established" success_msg: "Enough replication connections established" run_once: true - name: Run query to check replication status on standby nodes ansible.builtin.include_role: name: manage_dbserver tasks_from: execute_sql_query vars: pg_query: - query: "select status from pg_stat_wal_receiver" db: "{{ pg_database }}" when: - "'standby' in group_names" - name: Set pg_wal_reciever_query_result with sql_query_output ansible.builtin.set_fact: pg_wal_reciever_query_result: "{{ sql_query_output }}" become: true when: - "'standby' in group_names" - name: Check if replication was successful on standby nodes ansible.builtin.assert: that: - pg_wal_reciever_query_result.results[0].query_result[0]['status'] == 'streaming' fail_msg: "Replication was not successful on standby nodes" success_msg: "Replication was successful on standby nodes" - name: Reset variables ansible.builtin.set_fact: repuser_query_result: null pg_stat_query_result: null pg_wal_reciever_query_result: null standby_node_info: null check_pgbackrest-REL2_4/tests/roles/setup_replication/vars/000077500000000000000000000000001464170754600242755ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/roles/setup_replication/vars/PG_Debian.yml000066400000000000000000000013631464170754600265730ustar00rootroot00000000000000--- pg_wal: "" pg_data: "/var/lib/postgresql/{{ pg_version }}/{{ pg_instance_name }}" pg_log: "/var/log/postgresql" pg_log_filename: "{{ pg_instance_name }}-postgresql-%a.log" pg_service: "postgresql@{{ pg_version }}-{{ pg_instance_name }}" pg_bin_path: "/usr/lib/postgresql/{{ pg_version }}/bin" pg_initdb: "/usr/bin/pg_createcluster -s {{ pg_unix_socket_directories[0] }} {{ pg_version }} {{ pg_instance_name }} -- ${PGSETUP_INITDB_OPTIONS}" pg_initdb_options: "-k -A scram-sha-256 --encoding=UTF-8 --locale=C.UTF-8 --auth-local=peer" # PG configuration pg_port: 5432 pg_database: "postgres" pg_owner: "postgres" pg_group: "postgres" pg_unix_socket_directories: - "/var/run/postgresql" pg_replication_user: "repuser" pg_replication_user_password: "" check_pgbackrest-REL2_4/tests/roles/setup_replication/vars/PG_RedHat.yml000066400000000000000000000007701464170754600265610ustar00rootroot00000000000000--- pg_wal: "" pg_data: "/var/lib/pgsql/{{ pg_version }}/{{ pg_instance_name }}/data" pg_log: "/var/log/postgres" pg_log_filename: "{{ pg_instance_name }}-postgresql-%a.log" pg_service: "postgresql-{{ pg_version }}-{{ pg_instance_name }}" pg_bin_path: "/usr/pgsql-{{ pg_version }}/bin" # PG configuration pg_port: 5432 pg_database: "postgres" pg_owner: "postgres" pg_group: "postgres" pg_unix_socket_directories: - "/var/run/postgresql" pg_replication_user: "repuser" pg_replication_user_password: "" check_pgbackrest-REL2_4/tests/roles/setup_repo/000077500000000000000000000000001464170754600217565ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/roles/setup_repo/defaults/000077500000000000000000000000001464170754600235655ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/roles/setup_repo/defaults/main.yml000066400000000000000000000016351464170754600252410ustar00rootroot00000000000000--- # PGDG APT pg_apt_repo_url: "http://apt.postgresql.org/pub/repos/apt/" pg_apt_keys: "https://www.postgresql.org/media/keys/ACCC4CF8.asc" # PGDG RPM pg_rpm_repo_8_x86_64: "https://download.postgresql.org/pub/repos/yum/reporpms/EL-8-x86_64/pgdg-redhat-repo-latest.noarch.rpm" pg_rpm_repo_9_x86_64: "https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/pgdg-redhat-repo-latest.noarch.rpm" pg_gpg_key_x86_64: "https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-RHEL" # EPEL epel_repo_8: "https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm" epel_repo_9: "https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm " epel_gpg_key_8: "https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-8" epel_gpg_key_9: "https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-9" # Enable repositories for beta testing of the PG next major release pg_next_major_release: 17 check_pgbackrest-REL2_4/tests/roles/setup_repo/tasks/000077500000000000000000000000001464170754600231035ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/roles/setup_repo/tasks/PG_Debian_setuprepos.yml000066400000000000000000000016131464170754600276700ustar00rootroot00000000000000--- - name: Add PGDG keys ansible.builtin.apt_key: url: "{{ pg_apt_keys }}" state: present become: true - name: Add PG repo ansible.builtin.apt_repository: repo: "deb {{ pg_apt_repo_url }} {{ ansible_distribution_release }}-pgdg main" state: present filename: "pgdg" become: true - name: Add testing PG repo apt_repository: repo: "deb {{ pg_apt_repo_url }} {{ ansible_distribution_release }}-pgdg-testing main {{ pg_version }}" state: present filename: "pgdg" become: true - name: Add snapshot PG repo apt_repository: repo: "deb {{ pg_apt_repo_url }} {{ ansible_distribution_release }}-pgdg-snapshot main {{ pg_version }}" state: present filename: "pgdg" become: true when: - pg_version|int >= pg_next_major_release|int - name: Add apt pin preferences template: src: "pin-pgdg.pref.j2" dest: '/etc/apt/preferences.d/pgdg.pref' check_pgbackrest-REL2_4/tests/roles/setup_repo/tasks/PG_RedHat_setuprepos.yml000066400000000000000000000047531464170754600276650ustar00rootroot00000000000000--- - name: Update the ca-certificates package ansible.builtin.package: name: ca-certificates state: latest become: true - name: Download PGDG GPG key for EL8 and EL9 ansible.builtin.rpm_key: key: "{{ pg_gpg_key_x86_64 }}" state: present when: - ansible_distribution_major_version in ['8', '9'] become: true - name: Download EPEL GPG key for EL8 ansible.builtin.rpm_key: key: "{{ epel_gpg_key_8 }}" state: present when: - ansible_distribution_major_version == '8' become: true - name: Download EPEL GPG key for EL9 ansible.builtin.rpm_key: key: "{{ epel_gpg_key_9 }}" state: present when: - ansible_distribution_major_version == '9' become: true - name: Install dnf-plugins-core for EL8 and EL9 ansible.builtin.package: name: dnf-plugins-core state: present become: true when: - ansible_distribution_major_version in ['8', '9'] - name: Enable powertools for EL8 ansible.builtin.command: > dnf config-manager --set-enabled powertools become: true changed_when: true when: - ansible_distribution_major_version == '8' - name: Enable CodeReady Builder for EL9 ansible.builtin.command: > dnf config-manager --set-enabled crb become: true changed_when: true when: - ansible_distribution_major_version == '9' - name: Install EPEL repo for EL8 ansible.builtin.package: name: "{{ epel_repo_8 }}" state: present when: - ansible_distribution_major_version == '8' become: true - name: Install EPEL repo for EL9 ansible.builtin.package: name: "{{ epel_repo_9 }}" state: present when: - ansible_distribution_major_version == '9' become: true - name: Install PG repo for EL8 ansible.builtin.package: name: "{{ pg_rpm_repo_8_x86_64 }}" state: present become: true when: - ansible_distribution_major_version == '8' - name: Install PG repo for EL9 ansible.builtin.package: name: "{{ pg_rpm_repo_9_x86_64 }}" state: present become: true when: - ansible_distribution_major_version == '9' - name: Enable testing repository for PG beta releases ansible.builtin.command: > dnf config-manager --set-enabled pgdg{{ pg_version }}-updates-testing become: true changed_when: true when: - pg_version|int >= pg_next_major_release|int - name: Execute yum updateinfo ansible.builtin.shell: > set -o pipefail; yum updateinfo -y --refresh register: updateinfo_output become: true failed_when: false changed_when: updateinfo_output.rc != 0 check_pgbackrest-REL2_4/tests/roles/setup_repo/tasks/main.yml000066400000000000000000000002001464170754600245420ustar00rootroot00000000000000--- - name: Install PostgreSQL needed repositories ansible.builtin.include_tasks: "PG_{{ ansible_os_family }}_setuprepos.yml" check_pgbackrest-REL2_4/tests/roles/setup_repo/templates/000077500000000000000000000000001464170754600237545ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/roles/setup_repo/templates/pin-pgdg.pref.j2000066400000000000000000000004041464170754600266470ustar00rootroot00000000000000{% if pg_version|int >= pg_next_major_release|int %} Package: * {% else %} Package: pgbackrest {% endif %} Pin: release o=apt.postgresql.org,a={{ ansible_distribution_release }}-pgdg-testing,a={{ ansible_distribution_release }}-pgdg-snapshot Pin-Priority: 500 check_pgbackrest-REL2_4/tests/roles/sys/000077500000000000000000000000001464170754600204075ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/roles/sys/pkg/000077500000000000000000000000001464170754600211705ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/roles/sys/pkg/defaults/000077500000000000000000000000001464170754600227775ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/roles/sys/pkg/defaults/main.yml000066400000000000000000000003541464170754600244500ustar00rootroot00000000000000--- default_packages: common: - openssh-server - openssl - sudo - wget Debian: - openssh-client - iproute2 - gnupg - xxd RedHat: - openssh-clients - iproute - vim-common - yum-utils check_pgbackrest-REL2_4/tests/roles/sys/pkg/tasks/000077500000000000000000000000001464170754600223155ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/roles/sys/pkg/tasks/main.yml000066400000000000000000000003271464170754600237660ustar00rootroot00000000000000--- - name: Install required packages ansible.builtin.package: name: "{{ item }}" state: latest with_items: - "{{ default_packages['common'] }}" - "{{ default_packages[ansible_os_family] }}" check_pgbackrest-REL2_4/tests/roles/sys/tasks/000077500000000000000000000000001464170754600215345ustar00rootroot00000000000000check_pgbackrest-REL2_4/tests/roles/sys/tasks/main.yml000066400000000000000000000033441464170754600232070ustar00rootroot00000000000000--- - include_role: name: sys/pkg - name: Start ssh server service: name: "{{ ssh_service_name }}" state: started vars: ssh_service_name: "{{ (ansible_os_family == 'RedHat')|ternary('sshd', 'ssh') }}" - name: Ensure authorized_keys allows admin access authorized_key: user: root state: present key: "{{ lookup('file', ssh_key_file_pub) }}" vars: ssh_key_file_pub: "{{ cluster_dir }}/{{ ssh_key_file }}.pub" - name: Ensure that .ssh exists on all hosts file: path: "~/.ssh" state: directory mode: '0700' - name: Install keypair on all hosts copy: src: "{{ item.src }}" dest: "{{ item.dest }}" mode: "{{ item.mode }}" with_items: - src: "{{ cluster_dir }}/{{ ssh_key_file }}" dest: "~/.ssh/id_rsa" mode: '0600' - src: "{{ cluster_dir }}/{{ ssh_key_file }}.pub" dest: "~/.ssh/id_rsa.pub" mode: '0640' - name: Update network facts ansible.builtin.setup: gather_subset: - network - name: Set main /etc/hosts entry set_fact: my_hosts_lines: "{{ [main_hosts_line] }}" vars: main_hosts_line: >- {{ ansible_default_ipv4.address|default(ansible_all_ipv4_addresses[0]) }} {{ [inventory_hostname, inventory_hostname_short]|unique|join(' ') }} - name: Aggregate /etc/hosts lines across hosts set_fact: etc_hosts_lines: "{{ etc_hosts_lines|default([])|union(hostvars[item].my_hosts_lines) }}" with_items: "{{ groups['all'] }}" - name: Add entries to /etc/hosts lineinfile: path: /etc/hosts line: "{{ item }}" loop: "{{ etc_hosts_lines }}" when: platform != 'docker' - name: Ensure /run/nologin does not exist file: path: /run/nologin state: absent when: platform in ['docker'] check_pgbackrest-REL2_4/tests/run.sh000066400000000000000000000111761464170754600176130ustar00rootroot00000000000000#!/usr/bin/env bash set -o errexit set -o nounset cd "$(dirname "$0")" usage() { echo "Usage:" echo " -A Activity step only." echo " -c Cluster directory." echo " -C Cleaning step only." echo " -h Display this help message." echo " -i Initial step only." } INIT_ONLY=false CLEAN_ONLY=false PROVISION=true DEPLOY=true while getopts "Ac:Chi" o; do case "${o}" in A) INIT_ONLY=false CLEAN_ONLY=false PROVISION=false DEPLOY=false ACTIVITY=true ;; c) CLUSTER_DIR=${OPTARG} ;; C) CLEAN_ONLY=true ;; h ) usage exit 0 ;; i) INIT_ONLY=true ;; *) usage 1>&2 exit 1 ;; esac done shift $((OPTIND-1)) if $INIT_ONLY; then #------------------------------------------------------------------------------------------------------------------- echo '-------------------- Init --------------------' && date #------------------------------------------------------------------------------------------------------------------- # This section is intended to update the GitHub Action Runner (Ubuntu) echo 'Update apt' sudo apt-get update echo '--------------------' echo 'Whoami?' whoami echo '--------------------' echo 'Docker installed?' docker version echo '--------------------' echo 'Ansible installed?' ansible --version echo '--------------------' echo 'Install ansible dependencies' pipx inject ansible-core docker-py ansible-galaxy collection install ansible.posix ansible-galaxy collection install community.crypto ansible-galaxy collection install community.docker ansible-galaxy collection install community.general ansible-galaxy collection install community.postgresql ansible-galaxy collection install telekom_mms.icinga_director echo '--------------------' echo 'Install MinIO Python SDK' pip install minio echo '--------------------' echo 'Install Azure Storage Blobs client library for Python' pip install azure-storage-blob # Exit with success exit 0; fi if $CLEAN_ONLY; then #------------------------------------------------------------------------------------------------------------------- echo '-------------------- Clean --------------------' && date #------------------------------------------------------------------------------------------------------------------- if [ -e $CLUSTER_DIR ]; then ansible-playbook platforms/deprovision.yml -e cluster_dir=$CLUSTER_DIR sudo rm --force --preserve-root --recursive $CLUSTER_DIR fi # Exit with success exit 0; fi if $PROVISION; then #----------------------------------------------------------------------------------------------------------------------- echo '-------------------- Provision --------------------' && date #----------------------------------------------------------------------------------------------------------------------- export ANSIBLE_ROLES_PATH=${ANSIBLE_ROLES_PATH:+$ANSIBLE_ROLES_PATH:}$(pwd)/roles export ANSIBLE_LOOKUP_PLUGINS=${ANSIBLE_LOOKUP_PLUGINS:+$ANSIBLE_LOOKUP_PLUGINS:}$(pwd)/plugins/lookup : "${CLUSTER_DIR:?Variable not set or empty}" echo "CLUSTER_DIR=$CLUSTER_DIR" ansible-playbook platforms/provision.yml -e cluster_dir=$CLUSTER_DIR ansible-playbook platforms/system-config.yml -i "$CLUSTER_DIR/inventory.docker.yml" -e cluster_dir=$CLUSTER_DIR fi if $DEPLOY; then #----------------------------------------------------------------------------------------------------------------------- echo '-------------------- Deploy --------------------' && date #----------------------------------------------------------------------------------------------------------------------- export ANSIBLE_HOST_KEY_CHECKING=False export ANSIBLE_REMOTE_USER="root" ansible-playbook playbooks/deploy.yml -i "$CLUSTER_DIR/inventory" -e cluster_dir=$CLUSTER_DIR fi if $ACTIVITY; then #----------------------------------------------------------------------------------------------------------------------- echo '-------------------- Simulate basic activity --------------------' && date #----------------------------------------------------------------------------------------------------------------------- ansible-playbook playbooks/activity.yml -i "$CLUSTER_DIR/inventory" fi check_pgbackrest-REL2_4/tests/vagrant.sh000077500000000000000000000020021464170754600204400ustar00rootroot00000000000000#!/usr/bin/env bash set -o errexit set -o nounset cd /vagrant export RUN_ARGS="" if [ "$ACTIVITY" == "only" ]; then export ACTIVITY=true export RUN_ARGS="-A" fi echo "ARCH = '$ARCH'" echo "PGBR_BUILD = '$PGBR_BUILD'" echo "PGBR_REPO_TYPE = '$PGBR_REPO_TYPE'" echo "PROFILE = '$PROFILE'" source profile.d/vagrant.profile source profile.d/$PROFILE.profile if [ ! -z "$EXTRA" ]; then export EXTRA_VARS="$EXTRA_VARS $EXTRA" fi if $PGBR_BUILD; then export EXTRA_VARS="$EXTRA_VARS pgbackrest_build=true" fi if [ ! -z "$PGBR_REPO_TYPE" ]; then export EXTRA_VARS="$EXTRA_VARS pgbackrest_repo_type=$PGBR_REPO_TYPE" [ "$PGBR_REPO_TYPE" = "posix" ] && export EXTRA_VARS="$EXTRA_VARS pgbackrest_repo_path=/shared/repo1" fi [ ! -z "$pgbackrest_git_url" ] && export EXTRA_VARS="$EXTRA_VARS pgbackrest_git_url=$pgbackrest_git_url" [ ! -z "$pgbackrest_git_branch" ] && export EXTRA_VARS="$EXTRA_VARS pgbackrest_git_branch=$pgbackrest_git_branch" echo "EXTRA_VARS = '$EXTRA_VARS'" echo "CLNAME=$CLNAME" sh ci.sh check_pgbackrest-REL2_4/tests/vagrant.yml-dist000066400000000000000000000003001464170754600215640ustar00rootroot00000000000000# When building pgBackRest from sources, define which Github repository and branch to use # pgbackrest_git_url: "https://github.com/pgbackrest/pgbackrest.git" # pgbackrest_git_branch: "main"