diff --git a/.cleanup_test_env b/.cleanup_test_env deleted file mode 100644 index 9509c37..0000000 --- a/.cleanup_test_env +++ /dev/null @@ -1,4 +0,0 @@ -// Cleanup to avoid test pollution -std::env::remove_var("VAULT_ADDRESS"); -std::env::remove_var("VAULT_TOKEN"); -std::env::remove_var("VAULT_AGENT_PATH_PREFIX"); diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 04a0707..0000000 --- a/.dockerignore +++ /dev/null @@ -1,7 +0,0 @@ -venv -.env -.pylintrc -.travis.yml -.travis -tests.py -.ai \ No newline at end of file diff --git a/.env.example b/.env.example deleted file mode 100644 index ebcca56..0000000 --- a/.env.example +++ /dev/null @@ -1,35 +0,0 @@ -# Status Panel Agent - Example .env - -# Required for dashboard requests -AGENT_ID=your-agent-id -AGENT_TOKEN=replace-with-secret - -# Metrics webhook (optional). Agent pushes MetricsSnapshot JSON here. -METRICS_WEBHOOK=https://example.com/metrics - -# Heartbeat interval override (seconds) -METRICS_INTERVAL_SECS=15 - -# Login credentials for UI/API (default admin/admin if unset) -STATUS_PANEL_USERNAME=admin -STATUS_PANEL_PASSWORD=admin - -# Backup signer / verification -DEPLOYMENT_HASH=replace-with-secret -TRYDIRECT_IP=127.0.0.1 -BACKUP_PATH=/data/encrypted/backup.tar.gz.cpt - -# Docker integration -DOCKER_SOCK=unix:///var/run/docker.sock -NGINX_CONTAINER=nginx - -IP_HELP_LINK=https://try.direct/explains/what-is-dns-propagation - -# Self-update (beta) -# If set, agent checks for updates at UPDATE_SERVER_URL; alternatively set UPDATE_BINARY_URL directly. -UPDATE_SERVER_URL=https://releases.example.com -UPDATE_BINARY_URL= -# Optional SHA256 expected hash for downloaded binary -UPDATE_EXPECTED_SHA256= -# Where to store backups/manifest during deploy/rollback -UPDATE_STORAGE_PATH=/var/lib/status-panel \ No newline at end of file diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index e7f7b2c..0000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,3 +0,0 @@ -# These are supported funding model platforms - -github: [Magwaer, vsilent] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 93806f6..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,174 +0,0 @@ -name: Rust CI - -on: - push: - branches: - - master - - production - - testing - pull_request: - branches: - - master - -jobs: - build-and-test: - name: Build & Test (features=${{ matrix.features }}) - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - features: [default, minimal] - rust: [stable] - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Setup Rust toolchain (${{ matrix.rust }}) - uses: dtolnay/rust-toolchain@stable - - - name: Cache cargo build artifacts - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - - name: Show rustc and cargo versions - run: | - rustc -V - cargo -V - - - name: Build (default features) - if: matrix.features == 'default' - run: cargo build --release - - - name: Build (minimal features) - if: matrix.features == 'minimal' - run: cargo build --release --no-default-features --features minimal - - - name: Run tests (default features) - if: matrix.features == 'default' - env: - STATUS_PANEL_USERNAME: admin - STATUS_PANEL_PASSWORD: admin - AGENT_ID: ci-agent - AGENT_TOKEN: ci-token - NGINX_CONTAINER: nginx - METRICS_INTERVAL_SECS: 1 - run: | - cargo test --all --verbose - cargo test --test http_routes --verbose - - - name: Run tests (minimal features) - if: matrix.features == 'minimal' - env: - STATUS_PANEL_USERNAME: admin - STATUS_PANEL_PASSWORD: admin - AGENT_ID: ci-agent - AGENT_TOKEN: ci-token - NGINX_CONTAINER: nginx - METRICS_INTERVAL_SECS: 1 - run: | - cargo test --no-default-features --features minimal --all --verbose - cargo test --no-default-features --features minimal --test http_routes --verbose - - - name: Rustfmt check - run: cargo fmt --all -- --check - - - name: Clippy (default) - if: matrix.features == 'default' - run: cargo clippy -- -D warnings - - - name: Clippy (minimal) - if: matrix.features == 'minimal' - run: cargo clippy --no-default-features --features minimal -- -D warnings - - build-release-binaries: - name: Build Release Binaries (Linux x86_64) - runs-on: ubuntu-latest - needs: build-and-test - if: github.ref == 'refs/heads/production' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/') - strategy: - matrix: - target: [x86_64-unknown-linux-gnu, x86_64-unknown-linux-musl] - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Setup Rust toolchain - uses: dtolnay/rust-toolchain@stable - with: - targets: ${{ matrix.target }} - - - name: Install musl tools - if: matrix.target == 'x86_64-unknown-linux-musl' - run: sudo apt-get update && sudo apt-get install -y musl-tools - - - name: Cache cargo build artifacts - uses: Swatinem/rust-cache@v2 - with: - key: ${{ matrix.target }} - - - name: Build release binary - run: cargo build --release --target ${{ matrix.target }} - - - name: Strip binary - run: strip target/${{ matrix.target }}/release/status - - - name: Create artifact name - id: artifact - run: | - BRANCH=${GITHUB_REF#refs/*/} - if [[ "${{ matrix.target }}" == "x86_64-unknown-linux-musl" ]]; then - SIMPLE_NAME="status-linux-x86_64-musl" - else - SIMPLE_NAME="status-linux-x86_64" - fi - echo "name=${SIMPLE_NAME}-${BRANCH}-${GITHUB_SHA::7}" >> $GITHUB_OUTPUT - echo "binary=${SIMPLE_NAME}" >> $GITHUB_OUTPUT - - - name: Rename binary - run: | - cp target/${{ matrix.target }}/release/status ${{ steps.artifact.outputs.binary }} - - - name: Upload binary artifact - uses: actions/upload-artifact@v4 - with: - name: ${{ steps.artifact.outputs.name }} - path: ${{ steps.artifact.outputs.binary }} - retention-days: 30 - - docker-build: - name: Docker Build & Push (production/tags) - runs-on: ubuntu-latest - needs: build-and-test - if: github.ref == 'refs/heads/production' || startsWith(github.ref, 'refs/tags/') - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to DockerHub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_PASSWORD }} - - - name: Compute image tag - id: vars - run: | - REF_NAME="${GITHUB_REF#refs/*/}" - echo "ref_name=${REF_NAME}" >> $GITHUB_OUTPUT - echo "sha_short=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT - - - name: Build and push image - uses: docker/build-push-action@v5 - with: - file: Dockerfile.prod - push: true - tags: | - trydirect/status:${{ steps.vars.outputs.ref_name }} - trydirect/status:${{ steps.vars.outputs.sha_short }} diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 1f8702f..0000000 --- a/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -venv -.idea -__pycache__ -.DS_Store -.ai -target -.env \ No newline at end of file diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index 89a390b..0000000 --- a/.pylintrc +++ /dev/null @@ -1,425 +0,0 @@ -[MASTER] - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code -extension-pkg-whitelist= - -# Add files or directories to the blacklist. They should be base names, not -# paths. -ignore=CVS - -# Add files or directories matching the regex patterns to the blacklist. The -# regex matches against base names, not paths. -ignore-patterns= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Use multiple processes to speed up Pylint. -jobs=1 - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins= - -# Pickle collected data for later comparisons. -persistent=yes - -# Specify a configuration file. -#rcfile= - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED -confidence= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" -disable=blacklisted-name,invalid-name,missing-docstring,empty-docstring,unneeded-not,singleton-comparison,misplaced-comparison-constant,unidiomatic-typecheck,consider-using-enumerate,consider-iterating-dictionary,bad-classmethod-argument,bad-mcs-method-argument,bad-mcs-classmethod-argument,single-string-used-for-slots,line-too-long,too-many-lines,trailing-whitespace,missing-final-newline,trailing-newlines,multiple-statements,superfluous-parens,bad-whitespace,mixed-line-endings,unexpected-line-ending-format,bad-continuation,wrong-spelling-in-comment,wrong-spelling-in-docstring,invalid-characters-in-docstring,multiple-imports,wrong-import-order,ungrouped-imports,wrong-import-position,old-style-class,len-as-condition,fatal,astroid-error,parse-error,method-check-failed,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,literal-comparison,no-self-use,no-classmethod-decorator,no-staticmethod-decorator,cyclic-import,duplicate-code,too-many-ancestors,too-many-instance-attributes,too-few-public-methods,too-many-public-methods,too-many-return-statements,too-many-branches,too-many-arguments,too-many-locals,too-many-statements,too-many-boolean-expressions,consider-merging-isinstance,too-many-nested-blocks,simplifiable-if-statement,redefined-argument-from-local,no-else-return,consider-using-ternary,trailing-comma-tuple,unreachable,dangerous-default-value,pointless-statement,pointless-string-statement,expression-not-assigned,unnecessary-pass,unnecessary-lambda,duplicate-key,deprecated-lambda,assign-to-new-keyword,useless-else-on-loop,exec-used,eval-used,confusing-with-statement,using-constant-test,lost-exception,assert-on-tuple,attribute-defined-outside-init,bad-staticmethod-argument,protected-access,arguments-differ,signature-differs,abstract-method,super-init-not-called,no-init,non-parent-init-called,useless-super-delegation,unnecessary-semicolon,bad-indentation,mixed-indentation,lowercase-l-suffix,wildcard-import,deprecated-module,relative-import,reimported,import-self,misplaced-future,fixme,invalid-encoded-data,global-variable-undefined,global-variable-not-assigned,global-statement,global-at-module-level,unused-import,unused-variable,unused-argument,unused-wildcard-import,redefined-outer-name,redefined-builtin,redefine-in-handler,undefined-loop-variable,cell-var-from-loop,bare-except,broad-except,duplicate-except,nonstandard-exception,binary-op-exception,property-on-old-class,logging-not-lazy,logging-format-interpolation,bad-format-string-key,unused-format-string-key,bad-format-string,missing-format-argument-key,unused-format-string-argument,format-combined-specification,missing-format-attribute,invalid-format-index,anomalous-backslash-in-string,anomalous-unicode-escape-in-string,bad-open-mode,boolean-datetime,redundant-unittest-assert,deprecated-method,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call,useless-object-inheritance,comparison-with-callable - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. -enable=syntax-error,unrecognized-inline-option,bad-option-value,init-is-generator,return-in-init,function-redefined,not-in-loop,return-outside-function,yield-outside-function,return-arg-in-generator,nonexistent-operator,duplicate-argument-name,abstract-class-instantiated,bad-reversed-sequence,too-many-star-expressions,invalid-star-assignment-target,star-needs-assignment-target,nonlocal-and-global,continue-in-finally,nonlocal-without-binding,used-prior-global-declaration,method-hidden,access-member-before-definition,no-method-argument,no-self-argument,invalid-slots-object,assigning-non-slot,invalid-slots,inherit-non-class,inconsistent-mro,duplicate-bases,non-iterator-returned,unexpected-special-method-signature,invalid-length-returned,import-error,relative-beyond-top-level,used-before-assignment,undefined-variable,undefined-all-variable,invalid-all-object,no-name-in-module,unbalanced-tuple-unpacking,unpacking-non-sequence,bad-except-order,raising-bad-type,bad-exception-context,misplaced-bare-raise,raising-non-exception,notimplemented-raised,catching-non-exception,slots-on-old-class,super-on-old-class,bad-super-call,missing-super-argument,no-member,not-callable,assignment-from-no-return,no-value-for-parameter,too-many-function-args,unexpected-keyword-arg,redundant-keyword-arg,missing-kwoa,invalid-sequence-index,invalid-slice-index,assignment-from-none,not-context-manager,invalid-unary-operand-type,unsupported-binary-operation,repeated-keyword,not-an-iterable,not-a-mapping,unsupported-membership-test,unsubscriptable-object,unsupported-assignment-operation,unsupported-delete-operation,invalid-metaclass,logging-unsupported-format,logging-format-truncated,logging-too-many-args,logging-too-few-args,bad-format-character,truncated-format-string,mixed-format-string,format-needs-mapping,missing-format-string-key,too-many-format-args,too-few-format-args,bad-str-strip-call,print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,yield-inside-async-function,not-async-context-manager,unused-variable,attribute-defined-outside-init,bad-indentation - - -[REPORTS] - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details -#msg-template= - -# Set the output format. Available formats are text, parseable, colorized, json -# and msvs (visual studio).You can also give a reporter class, eg -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Tells whether to display a full report or only the messages -reports=no - -# Activate the evaluation score. -score=yes - - -[REFACTORING] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 - - -[LOGGING] - -# Logging modules to check that the string format arguments are in logging -# function parameter format -logging-modules=logging - - -[SPELLING] - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=FIXME,XXX,TODO - - -[TYPECHECK] - -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# This flag controls whether pylint should warn about no-member and similar -# checks whenever an opaque object is returned when inferring. The inference -# can return multiple potential results while evaluating a Python object, but -# some branches might not be evaluated, which results in partial inference. In -# that case, it might be useful to still emit no-member and other checks for -# the rest of the inferred objects. -ignore-on-opaque-inference=yes - -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules= - -# Show a hint with possible names when a member name was not found. The aspect -# of finding the hint is based on edit distance. -missing-member-hint=yes - -# The minimum edit distance a name should have in order to be considered a -# similar match for a missing member name. -missing-member-hint-distance=1 - -# The total number of similar names that should be taken in consideration when -# showing a hint for a missing member. -missing-member-max-choices=1 - - -[VARIABLES] - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -additional-builtins= - -# Tells whether unused global variables should be treated as a violation. -allow-global-unused-variables=yes - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_,_cb - -# A regular expression matching the name of dummy variables (i.e. expectedly -# not used). -dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore -ignored-argument-names=_.*|^ignored_|^unused_ - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six.moves,future.builtins - - -[FORMAT] - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )??$ - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - -# Maximum number of characters on a single line. -max-line-length=100 - -# Maximum number of lines in a module -max-module-lines=1000 - -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check=trailing-comma,dict-separator - -# Allow the body of a class to be on the same line as the declaration if body -# contains single statement. -single-line-class-stmt=no - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - - -[SIMILARITIES] - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=no - -# Minimum lines number of a similarity. -min-similarity-lines=4 - - -[BASIC] - -# Naming hint for argument names -argument-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct argument names -argument-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Naming hint for attribute names -attr-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct attribute names -attr-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Bad variable names which should always be refused, separated by a comma -bad-names=foo,bar,baz,toto,tutu,tata - -# Naming hint for class attribute names -class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Regular expression matching correct class attribute names -class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Naming hint for class names -class-name-hint=[A-Z_][a-zA-Z0-9]+$ - -# Regular expression matching correct class names -class-rgx=[A-Z_][a-zA-Z0-9]+$ - -# Naming hint for constant names -const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Regular expression matching correct constant names -const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - -# Naming hint for function names -function-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct function names -function-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Good variable names which should always be accepted, separated by a comma -good-names=i,j,k,ex,Run,_ - -# Include a hint for the correct naming format with invalid-name -include-naming-hint=no - -# Naming hint for inline iteration names -inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ - -# Regular expression matching correct inline iteration names -inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ - -# Naming hint for method names -method-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct method names -method-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Naming hint for module names -module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Regular expression matching correct module names -module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=^_ - -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. -property-classes=abc.abstractproperty - -# Naming hint for variable names -variable-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct variable names -variable-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - - -[IMPORTS] - -# Allow wildcard imports from modules that define __all__. -allow-wildcard-with-all=no - -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=optparse,tkinter.tix - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled) -int-import-graph= - -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= - -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant - - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__,__new__,setUp - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict,_fields,_replace,_source,_make - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs - - -[DESIGN] - -# Maximum number of arguments for function / method -max-args=5 - -# Maximum number of attributes for a class (see R0902). -max-attributes=7 - -# Maximum number of boolean expressions in a if statement -max-bool-expr=5 - -# Maximum number of branch for function / method body -max-branches=12 - -# Maximum number of locals for function / method body -max-locals=15 - -# Maximum number of parents for a class (see R0901). -max-parents=7 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - -# Maximum number of return / yield for function / method body -max-returns=6 - -# Maximum number of statements in function / method body -max-statements=50 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=2 - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "Exception" -overgeneral-exceptions=Exception \ No newline at end of file diff --git a/BUGS.md b/BUGS.md deleted file mode 100644 index 27558c8..0000000 --- a/BUGS.md +++ /dev/null @@ -1,16 +0,0 @@ -# Known Bugs - -## GLIBC version mismatch inside container -- **Summary**: The `status` binary inside the container requires `GLIBC_2.39`, but the deployed base image ships an older glibc, causing startup failure. -- **Error**: `/usr/local/bin/status: /lib/x86_64-linux-gnu/libc.so.6: version 'GLIBC_2.39' not found (required by /usr/local/bin/status)` -- **Impact**: Status agent fails to start in the deployed container; health endpoints and status panel remain unavailable. -- **Environment**: Remote server container image (likely Debian/Ubuntu with glibc < 2.39). Local build environment used a newer glibc when compiling. -- **Repro Steps**: - 1) Build the image with the current toolchain. - 2) Run the container on a host/base image with glibc < 2.39. - 3) Execute `/usr/local/bin/status` → startup fails with the glibc error above. -- **Suspected Cause**: Binary compiled against glibc 2.39 on host/build image; deployed runtime provides an older glibc. No compatibility shim present. -- **Suggested Fixes/Workarounds**: - - Rebuild `status` using the same base image as runtime (e.g., align Dockerfile build stage to target glibc version) so it links against the older glibc available in container. - - Alternatively, build a statically linked binary using musl (`musl-gcc`/`cargo build --target x86_64-unknown-linux-musl`) to remove glibc dependency. - - Ensure CI uses the production base image for builds, or pin toolchain to match deployed distro glibc. diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 278dfe5..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,8 +0,0 @@ -# Changelog - -## Unreleased - 2026-01-05 -- Known issue: containerized `status` binary fails to start on hosts with glibc versions older than 2.39; rebuild against the production base image or ship a musl-linked binary to restore compatibility. -- Changed: Docker builds now produce a statically linked musl binary (Dockerfile, Dockerfile.prod) to avoid glibc drift at runtime. -- Planned: align build and runtime images to avoid glibc drift; keep the musl-based build variant as the default container target. -- Planned: update CI to build and test using the production base image so linker/runtime errors are caught early. -- Planned: add a container startup smoke check to surface missing runtime dependencies before release. diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index c2b2113..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,3066 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anstream" -version = "0.6.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" - -[[package]] -name = "anstyle-parse" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys 0.61.2", -] - -[[package]] -name = "anyhow" -version = "1.0.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" - -[[package]] -name = "assert-json-diff" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" -dependencies = [ - "serde", - "serde_json", -] - -[[package]] -name = "assert_cmd" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcbb6924530aa9e0432442af08bbcafdad182db80d2e560da42a6d442535bf85" -dependencies = [ - "anstyle", - "bstr", - "libc", - "predicates", - "predicates-core", - "predicates-tree", - "wait-timeout", -] - -[[package]] -name = "async-stream" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "axum" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425" -dependencies = [ - "axum-core", - "base64", - "bytes", - "form_urlencoded", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "serde_core", - "serde_json", - "serde_path_to_error", - "serde_urlencoded", - "sha1", - "sync_wrapper", - "tokio", - "tokio-tungstenite", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum-core" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "http-body-util", - "mime", - "pin-project-lite", - "sync_wrapper", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "bitflags" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "bollard" -version = "0.19.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87a52479c9237eb04047ddb94788c41ca0d26eaff8b697ecfbb4c32f7fdc3b1b" -dependencies = [ - "base64", - "bollard-stubs", - "bytes", - "chrono", - "futures-core", - "futures-util", - "hex", - "http", - "http-body-util", - "hyper", - "log", - "pin-project-lite", - "serde", - "serde_derive", - "serde_json", - "serde_repr", - "serde_urlencoded", - "thiserror", - "tokio", - "tokio-util", - "tower-service", - "url", - "winapi", -] - -[[package]] -name = "bollard-stubs" -version = "1.49.1-rc.28.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5731fe885755e92beff1950774068e0cae67ea6ec7587381536fca84f1779623" -dependencies = [ - "chrono", - "serde", - "serde_json", - "serde_repr", - "serde_with", -] - -[[package]] -name = "bstr" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" -dependencies = [ - "memchr", - "regex-automata", - "serde", -] - -[[package]] -name = "bumpalo" -version = "3.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" - -[[package]] -name = "bytes" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" - -[[package]] -name = "cc" -version = "1.2.49" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" -dependencies = [ - "find-msvc-tools", - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "chrono" -version = "0.4.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" -dependencies = [ - "iana-time-zone", - "js-sys", - "num-traits", - "serde", - "wasm-bindgen", - "windows-link", -] - -[[package]] -name = "chrono-tz" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb" -dependencies = [ - "chrono", - "chrono-tz-build", - "phf", -] - -[[package]] -name = "chrono-tz-build" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1" -dependencies = [ - "parse-zoneinfo", - "phf", - "phf_codegen", -] - -[[package]] -name = "clap" -version = "4.5.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.5.49" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" - -[[package]] -name = "colorchoice" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" - -[[package]] -name = "colored" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "crypto-common" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "daemonize" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab8bfdaacb3c887a54d41bdf48d3af8873b3f5566469f8ba21b92057509f116e" -dependencies = [ - "libc", -] - -[[package]] -name = "data-encoding" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" - -[[package]] -name = "deranged" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" -dependencies = [ - "powerfmt", - "serde_core", -] - -[[package]] -name = "deunicode" -version = "1.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" - -[[package]] -name = "difflib" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", - "subtle", -] - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "dotenvy" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" - -[[package]] -name = "dyn-clone" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" - -[[package]] -name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "find-msvc-tools" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-core", - "futures-macro", - "futures-sink", - "futures-task", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi", - "wasip2", - "wasm-bindgen", -] - -[[package]] -name = "globset" -version = "0.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" -dependencies = [ - "aho-corasick", - "bstr", - "log", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "globwalk" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" -dependencies = [ - "bitflags", - "ignore", - "walkdir", -] - -[[package]] -name = "h2" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap 2.12.1", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - -[[package]] -name = "http" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" -dependencies = [ - "bytes", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "http-range-header" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "humansize" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" -dependencies = [ - "libm", -] - -[[package]] -name = "hyper" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "pin-utils", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" -dependencies = [ - "http", - "hyper", - "hyper-util", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", - "webpki-roots", -] - -[[package]] -name = "hyper-util" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" -dependencies = [ - "base64", - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core 0.62.2", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "icu_collections" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" -dependencies = [ - "displaydoc", - "potential_utf", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" - -[[package]] -name = "icu_properties" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" -dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" - -[[package]] -name = "icu_provider" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "ignore" -version = "0.4.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" -dependencies = [ - "crossbeam-deque", - "globset", - "log", - "memchr", - "regex-automata", - "same-file", - "walkdir", - "winapi-util", -] - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - -[[package]] -name = "indexmap" -version = "2.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" -dependencies = [ - "equivalent", - "hashbrown 0.16.1", - "serde", - "serde_core", -] - -[[package]] -name = "ipnet" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" - -[[package]] -name = "iri-string" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - -[[package]] -name = "itoa" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" - -[[package]] -name = "js-sys" -version = "0.3.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.178" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" - -[[package]] -name = "libm" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" - -[[package]] -name = "linux-raw-sys" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" - -[[package]] -name = "litemap" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" - -[[package]] -name = "lru-slab" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" - -[[package]] -name = "matchers" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" -dependencies = [ - "regex-automata", -] - -[[package]] -name = "matchit" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" - -[[package]] -name = "memchr" -version = "2.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "mime_guess" -version = "2.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" -dependencies = [ - "mime", - "unicase", -] - -[[package]] -name = "mio" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.61.2", -] - -[[package]] -name = "mockito" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e0603425789b4a70fcc4ac4f5a46a566c116ee3e2a6b768dc623f7719c611de" -dependencies = [ - "assert-json-diff", - "bytes", - "colored", - "futures-core", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "log", - "pin-project-lite", - "rand 0.9.2", - "regex", - "serde_json", - "serde_urlencoded", - "similar", - "tokio", -] - -[[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags", - "cfg-if", - "cfg_aliases", - "libc", -] - -[[package]] -name = "ntapi" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" -dependencies = [ - "winapi", -] - -[[package]] -name = "nu-ansi-term" -version = "0.50.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link", -] - -[[package]] -name = "parse-zoneinfo" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" -dependencies = [ - "regex", -] - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "pest" -version = "2.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbcfd20a6d4eeba40179f05735784ad32bdaef05ce8e8af05f180d45bb3e7e22" -dependencies = [ - "memchr", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51f72981ade67b1ca6adc26ec221be9f463f2b5839c7508998daa17c23d94d7f" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee9efd8cdb50d719a80088b76f81aec7c41ed6d522ee750178f83883d271625" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pest_meta" -version = "2.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf1d70880e76bdc13ba52eafa6239ce793d85c8e43896507e43dd8984ff05b82" -dependencies = [ - "pest", - "sha2", -] - -[[package]] -name = "phf" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -dependencies = [ - "phf_shared", -] - -[[package]] -name = "phf_codegen" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" -dependencies = [ - "phf_generator", - "phf_shared", -] - -[[package]] -name = "phf_generator" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" -dependencies = [ - "phf_shared", - "rand 0.8.5", -] - -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "potential_utf" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" -dependencies = [ - "zerovec", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "predicates" -version = "3.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" -dependencies = [ - "anstyle", - "difflib", - "predicates-core", -] - -[[package]] -name = "predicates-core" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" - -[[package]] -name = "predicates-tree" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" -dependencies = [ - "predicates-core", - "termtree", -] - -[[package]] -name = "proc-macro2" -version = "1.0.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quinn" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" -dependencies = [ - "bytes", - "cfg_aliases", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "socket2", - "thiserror", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-proto" -version = "0.11.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" -dependencies = [ - "bytes", - "getrandom 0.3.4", - "lru-slab", - "rand 0.9.2", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "slab", - "thiserror", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2", - "tracing", - "windows-sys 0.60.2", -] - -[[package]] -name = "quote" -version = "1.0.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.3", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.3", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.16", -] - -[[package]] -name = "rand_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" -dependencies = [ - "getrandom 0.3.4", -] - -[[package]] -name = "rayon" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags", -] - -[[package]] -name = "ref-cast" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "regex" -version = "1.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" - -[[package]] -name = "reqwest" -version = "0.12.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b4c14b2d9afca6a60277086b0cc6a6ae0b568f6f7916c943a8cdc79f8be240f" -dependencies = [ - "base64", - "bytes", - "futures-core", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "js-sys", - "log", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots", -] - -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.16", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" - -[[package]] -name = "rustix" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls" -version = "0.23.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" -dependencies = [ - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pki-types" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c" -dependencies = [ - "web-time", - "zeroize", -] - -[[package]] -name = "rustls-webpki" -version = "0.103.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "schemars" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "schemars" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.145" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", - "serde_core", -] - -[[package]] -name = "serde_path_to_error" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" -dependencies = [ - "itoa", - "serde", - "serde_core", -] - -[[package]] -name = "serde_repr" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_with" -version = "3.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" -dependencies = [ - "base64", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.12.1", - "schemars 0.9.0", - "schemars 1.1.0", - "serde_core", - "serde_json", - "time", -] - -[[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" -dependencies = [ - "indexmap 2.12.1", - "itoa", - "ryu", - "serde", - "unsafe-libyaml", -] - -[[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook-registry" -version = "1.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" -dependencies = [ - "libc", -] - -[[package]] -name = "similar" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" - -[[package]] -name = "siphasher" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" - -[[package]] -name = "slab" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" - -[[package]] -name = "slug" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724" -dependencies = [ - "deunicode", - "wasm-bindgen", -] - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "socket2" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" -dependencies = [ - "libc", - "windows-sys 0.60.2", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "status-panel" -version = "0.1.2" -dependencies = [ - "anyhow", - "assert_cmd", - "axum", - "base64", - "bollard", - "bytes", - "chrono", - "clap", - "daemonize", - "dotenvy", - "futures-util", - "hmac", - "http-body-util", - "hyper", - "mockito", - "nix", - "rand 0.8.5", - "reqwest", - "ring", - "serde", - "serde_json", - "serde_yaml", - "sha2", - "subtle", - "sysinfo", - "tempfile", - "tera", - "thiserror", - "tokio", - "tokio-test", - "tower", - "tower-http", - "tracing", - "tracing-subscriber", - "uuid", -] - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "syn" -version = "2.0.111" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "sysinfo" -version = "0.30.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a5b4ddaee55fb2bea2bf0e5000747e5f5c0de765e5a5ff87f4cd106439f4bb3" -dependencies = [ - "cfg-if", - "core-foundation-sys", - "libc", - "ntapi", - "once_cell", - "rayon", - "windows", -] - -[[package]] -name = "tempfile" -version = "3.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" -dependencies = [ - "fastrand", - "getrandom 0.3.4", - "once_cell", - "rustix", - "windows-sys 0.61.2", -] - -[[package]] -name = "tera" -version = "1.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8004bca281f2d32df3bacd59bc67b312cb4c70cea46cbd79dbe8ac5ed206722" -dependencies = [ - "chrono", - "chrono-tz", - "globwalk", - "humansize", - "lazy_static", - "percent-encoding", - "pest", - "pest_derive", - "rand 0.8.5", - "regex", - "serde", - "serde_json", - "slug", - "unicode-segmentation", -] - -[[package]] -name = "termtree" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" - -[[package]] -name = "thiserror" -version = "2.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "thread_local" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "time" -version = "0.3.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" - -[[package]] -name = "time-macros" -version = "0.2.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tinystr" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tinyvec" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tokio" -version = "1.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" -dependencies = [ - "bytes", - "libc", - "mio", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.61.2", -] - -[[package]] -name = "tokio-macros" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-stream" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-test" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2468baabc3311435b55dd935f702f42cd1b8abb7e754fb7dfb16bd36aa88f9f7" -dependencies = [ - "async-stream", - "bytes", - "futures-core", - "tokio", - "tokio-stream", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" -dependencies = [ - "futures-util", - "log", - "tokio", - "tungstenite", -] - -[[package]] -name = "tokio-util" -version = "0.7.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tower" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-http" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" -dependencies = [ - "bitflags", - "bytes", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "http-range-header", - "httpdate", - "iri-string", - "mime", - "mime_guess", - "percent-encoding", - "pin-project-lite", - "tokio", - "tokio-util", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" -dependencies = [ - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-serde" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" -dependencies = [ - "serde", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex-automata", - "serde", - "serde_json", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", - "tracing-serde", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "tungstenite" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" -dependencies = [ - "bytes", - "data-encoding", - "http", - "httparse", - "log", - "rand 0.9.2", - "sha1", - "thiserror", - "utf-8", -] - -[[package]] -name = "typenum" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" - -[[package]] -name = "ucd-trie" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" - -[[package]] -name = "unicase" -version = "2.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" - -[[package]] -name = "unicode-ident" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" - -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - -[[package]] -name = "unsafe-libyaml" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "uuid" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" -dependencies = [ - "getrandom 0.3.4", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "wait-timeout" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" -dependencies = [ - "libc", -] - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasip2" -version = "1.0.1+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" -dependencies = [ - "cfg-if", - "js-sys", - "once_cell", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "web-sys" -version = "0.3.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki-roots" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" -dependencies = [ - "windows-core 0.52.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-core" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - -[[package]] -name = "wit-bindgen" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" - -[[package]] -name = "writeable" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" - -[[package]] -name = "yoke" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zerocopy" -version = "0.8.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zerofrom" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" - -[[package]] -name = "zerotrie" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index 1dde0e9..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,58 +0,0 @@ -[package] -name = "status-panel" -version = "0.1.2" -edition = "2021" - -[features] -default = ["docker"] -docker = ["bollard"] -minimal = [] - -[dependencies] -anyhow = "1" -thiserror = "2" -serde = { version = "1", features = ["derive"] } -serde_json = "1" -tracing = "0.1" -tracing-subscriber = { version = "0.3", features = ["fmt", "json", "env-filter"] } -clap = { version = "4", features = ["derive"] } -tokio = { version = "1", features = ["full"] } -axum = { version = "0.8", features = ["ws"] } -reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } -ring = "0.17" -bytes = "1" -uuid = { version = "1", features = ["v4"] } -chrono = { version = "0.4", features = ["serde"] } -serde_yaml = "0.9" -futures-util = "0.3" -tera = "1" -tower-http = { version = "0.6", features = ["fs"] } -base64 = "0.22" -hmac = "0.12" -sha2 = "0.10" -rand = "0.8" -subtle = "2" -# System metrics -sysinfo = "0.30" -# Docker client for Rust (SSL disabled to avoid OpenSSL dependency on musl) -bollard = { version = "0.19", optional = true, default-features = false, features = ["chrono"] } -# Daemonization -daemonize = "0.5" -# Load environment variables from .env -dotenvy = "0.15" - -[target.'cfg(unix)'.dependencies] -nix = { version = "0.29", features = ["signal"] } - -[[bin]] -name = "status" -path = "src/main.rs" - -[dev-dependencies] -assert_cmd = "2.0" -tokio-test = "0.4" -tempfile = "3" -mockito = "1" -tower = "0.5" -http-body-util = "0.1" -hyper = "1" diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index ff118ea..0000000 --- a/Dockerfile +++ /dev/null @@ -1,23 +0,0 @@ -FROM clux/muslrust:1.83.0 AS builder - -WORKDIR /app -COPY Cargo.toml Cargo.lock* ./ -COPY src src -COPY templates templates -COPY static static -COPY config.json config.json - -# Build a statically linked binary to avoid runtime glibc mismatches. -RUN rustup target add x86_64-unknown-linux-musl && \ - cargo build --release --target x86_64-unknown-linux-musl - -FROM gcr.io/distroless/cc -WORKDIR /app -COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/status /usr/local/bin/status -COPY templates templates -COPY static static -COPY config.json config.json -ENV RUST_LOG=info -# Expose API/UI port -EXPOSE 5000 -CMD ["/usr/local/bin/status", "serve", "--port", "5000", "--with-ui"] diff --git a/Dockerfile.prod b/Dockerfile.prod deleted file mode 100644 index 5f493f6..0000000 --- a/Dockerfile.prod +++ /dev/null @@ -1,23 +0,0 @@ -FROM clux/muslrust:1.83.0 AS builder - -WORKDIR /app -COPY Cargo.toml Cargo.lock* ./ -COPY src src -COPY templates templates -COPY static static -COPY config.json config.json - -# Build a statically linked binary to remove glibc runtime dependency. -RUN rustup target add x86_64-unknown-linux-musl && \ - cargo build --release --target x86_64-unknown-linux-musl - -FROM gcr.io/distroless/cc -WORKDIR /app -COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/status /status -COPY templates templates -COPY static static -COPY config.json config.json -ENV RUST_LOG=info -EXPOSE 5000 -USER 0 -ENTRYPOINT ["/status", "serve", "--port", "5000", "--with-ui"] \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index fb57037..0000000 --- a/README.md +++ /dev/null @@ -1,196 +0,0 @@ -# Status Panel (Beacon) - -Server stack health application with UI. - - -## Build - -```bash -cargo build --release -``` - -## Run - -Foreground daemon (default without subcommands): - -```bash -./target/release/status --config config.json -``` - -Daemon mode (background): - -```bash -./target/release/status --daemon --config config.json -``` - -Local API server (API-only mode): - -```bash -./target/release/status serve --port 5000 -``` - -Local API server with UI (serves HTML templates): - -```bash -./target/release/status serve --port 5000 --with-ui -``` - -Then open your browser to `http://localhost:5000/login` to access the web interface. - -Docker operations (requires `--features docker`): - -```bash -cargo run --features docker --bin status -- containers -cargo run --features docker --bin status -- restart status -``` - -## Features - -- **API-only mode**: Returns JSON responses for programmatic access -- **UI mode** (`--with-ui`): Serves HTML templates from `templates/` directory with static files from `static/` -- Docker container management (list, restart, stop, pause) -- Session-based authentication -- Health check endpoint -- Self-update (beta): remote version check, binary download + SHA256 verify, deploy with backup/rollback - -## Command Execution (API) - -Execute validated shell commands via the local API. The endpoint accepts a `transport::Command` payload and returns a `transport::CommandResult`. - -- Endpoint: `POST /api/v1/commands/execute` -- Required fields: `id` (string), `name` (full command line) -- Optional: `params.timeout_secs` (number) to override the default 60s timeout - -Example: run a simple echo - -```bash -curl -s \ - -H 'Content-Type: application/json' \ - -X POST http://localhost:5000/api/v1/commands/execute \ - -d '{ - "id": "cmd-001", - "name": "echo hello from agent", - "params": { "timeout_secs": 10 } - }' | jq . -``` - -Example: run a short sleep - -```bash -curl -s \ - -H 'Content-Type: application/json' \ - -X POST http://localhost:5000/api/v1/commands/execute \ - -d '{ - "id": "cmd-002", - "name": "sleep 2", - "params": { "timeout_secs": 5 } - }' | jq . -``` - -Notes: -- Commands are validated by a conservative allowlist and safety checks; see `src/commands/validator.rs`. -- Disallowed by default: shells (`sh`, `bash`, `zsh`) and metacharacters like `; | & > <`. -- Absolute paths must match allowed prefixes (defaults: `/tmp`, `/var/tmp`). -- Output (`stdout`/`stderr`) and `exit_code` are included when available, along with a `status` of `success`, `failed`, `timeout`, or `killed`. - -## Long-Poll Command Queue - -The agent supports an in-memory command queue for dashboard-driven execution via long-polling. Commands are queued and agents poll for them with configurable timeouts. - -### Endpoints - -- `GET /api/v1/commands/wait/{hash}?timeout=N` - Long-poll for next queued command (default 30s timeout) -- `POST /api/v1/commands/report` - Report command execution result -- `POST /api/v1/commands/enqueue` - Enqueue a command (for testing/local use) - -All endpoints require `X-Agent-Id` header matching the `AGENT_ID` environment variable. - -### Manual Testing - -Start the server with agent ID: - -```bash -export AGENT_ID=test-agent -cargo r -- serve --port 5000 -``` - -**Terminal 1: Long-poll for commands** - -```bash -curl -H 'X-Agent-Id: test-agent' \ - 'http://localhost:5000/api/v1/commands/wait/demo?timeout=10' -``` - -**Terminal 2: Enqueue a command** - -```bash -curl -s \ - -H 'Content-Type: application/json' \ - -X POST http://localhost:5000/api/v1/commands/enqueue \ - -d '{ - "id": "cmd-001", - "name": "echo hello from queue", - "params": {} - }' | jq . -``` - -The long-poll in Terminal 1 will immediately return the queued command. - -**Report command result** - -```bash -curl -s \ - -H 'Content-Type: application/json' \ - -H 'X-Agent-Id: test-agent' \ - -X POST http://localhost:5000/api/v1/commands/report \ - -d '{ - "command_id": "cmd-001", - "status": "success", - "result": {"exit_code": 0, "stdout": "hello from queue\n"}, - "error": null - }' | jq . -``` - -### Demo Script - -Run the automated demo: - -```bash -export AGENT_ID=test-agent -./examples/long_poll_demo.sh -``` - -This script starts a background poller, enqueues a command, and demonstrates the long-poll notification mechanism. - -## Templates - -The UI uses Tera templating engine (similar to Jinja2). Templates are located in: -- `templates/` - HTML templates (login.html, index.html, error.html) -- `static/` - CSS, JavaScript, and other static assets - -## Notes - -- Reads `config.json` and normalizes `apps_info` to structured items. -- Subsystems marked with `@todo` will be implemented per `.ai/GOAL.md`. - -## Self-update (beta) - -- Env vars: `UPDATE_SERVER_URL` or `UPDATE_BINARY_URL`, optional `UPDATE_EXPECTED_SHA256`, `AGENT_ID`, `UPDATE_STORAGE_PATH` -- Endpoints: - - `GET /api/self/version` → current + available (when `UPDATE_SERVER_URL` is set) - - `POST /api/self/update/start` → returns `job_id` (requires `X-Agent-Id`) - - `GET /api/self/update/status/{id}` → phase: pending|downloading|verifying|completed|failed - - `POST /api/self/update/deploy` → body: `{ "job_id", "install_path?", "service_name?" }`; backs up current binary, deploys prepared one - - `POST /api/self/update/rollback` → restore latest backup - -Example (start + deploy): - -```bash -curl -X POST http://localhost:5000/api/self/update/start \ - -H "X-Agent-Id: $AGENT_ID" \ - -d '{"version":"1.2.3"}' - -curl -X POST http://localhost:5000/api/self/update/deploy \ - -H "X-Agent-Id: $AGENT_ID" \ - -d '{"job_id":"","service_name":"status-panel"}' -``` diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index 78c72e4..0000000 --- a/SECURITY.md +++ /dev/null @@ -1,54 +0,0 @@ -# Security Policy -## Reporting a Vulnerability -If you believe you've found something in "Status" which has security implications, -please do not raise the issue in Github issue tracker or other public forums. - -Send a description of the issue via email to *security@try.direct*. -The project maintainers will then work with you to resolve any issues where required, prior to any public disclosure. - ---- - -## Vault Integration Security - -### Token Rotation Best Practices - -The agent supports automatic token rotation via Vault KV store. When enabled: - -1. **Service Token Security** - - Vault service token (`VAULT_TOKEN`) should have minimal required permissions - - Restrict to specific KV path: `status_panel/deployment-*/token` - - Rotate service token independently from agent token - - Never commit `VAULT_TOKEN` to version control - -2. **Network Security** - - Always use HTTPS to Vault with certificate pinning in production - - Restrict Vault network access to authorized agent IPs - - Use Vault VPC peering or private networks when available - -3. **Token Storage** - - Store agent tokens encrypted at rest in Vault - - Use KV v2 for versioning capability - - Audit all token access via Vault audit logs - - Rotate agent tokens regularly (e.g., monthly) - -4. **Monitoring** - - Alert if agent token refresh fails for > 10 minutes - - Monitor `/health` endpoint for `token_age_seconds` > 600 - - Log all token rotation events - - Track Vault fetch errors in central logging - -### Threat Model - -**Threat:** Compromise of static token -**Mitigation:** Vault-based rotation enables frequent token changes without restart -**Residual Risk:** Compromised token valid for up to 60s before refresh - -**Threat:** Vault unavailability -**Mitigation:** Agent continues with current token; automatically retries fetch -**Residual Risk:** Token staleness increases if Vault unreachable > 10 minutes - -**Threat:** Network eavesdropping of Vault connection -**Mitigation:** Enforce TLS with certificate pinning -**Residual Risk:** Requires valid client cert for fetch requests - -See [VAULT_INTEGRATION.md](VAULT_INTEGRATION.md) for complete setup and monitoring guidance. diff --git a/_config.yml b/_config.yml new file mode 100644 index 0000000..2f7efbe --- /dev/null +++ b/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-minimal \ No newline at end of file diff --git a/assets/logo/status.ai b/assets/logo/status.ai deleted file mode 100644 index c6e8e28..0000000 --- a/assets/logo/status.ai +++ /dev/null @@ -1,3134 +0,0 @@ -%PDF-1.5 % -1 0 obj <>/OCGs[5 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <>stream - - - - - application/pdf - - - vector ai file - - - 2022-02-03T15:16:10+05:00 - 2022-02-03T15:16:10+05:00 - 2022-02-03T15:16:10+06:00 - Adobe Illustrator CC 2015 (Windows) - - - - 256 - 56 - JPEG - /9j/4AAQSkZJRgABAgEBLAEsAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABABLAAAAAEA AQEsAAAAAQAB/+IMWElDQ19QUk9GSUxFAAEBAAAMSExpbm8CEAAAbW50clJHQiBYWVogB84AAgAJ AAYAMQAAYWNzcE1TRlQAAAAASUVDIHNSR0IAAAAAAAAAAAAAAAAAAPbWAAEAAAAA0y1IUCAgAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARY3BydAAAAVAAAAAz ZGVzYwAAAYQAAABsd3RwdAAAAfAAAAAUYmtwdAAAAgQAAAAUclhZWgAAAhgAAAAUZ1hZWgAAAiwA AAAUYlhZWgAAAkAAAAAUZG1uZAAAAlQAAABwZG1kZAAAAsQAAACIdnVlZAAAA0wAAACGdmlldwAA A9QAAAAkbHVtaQAAA/gAAAAUbWVhcwAABAwAAAAkdGVjaAAABDAAAAAMclRSQwAABDwAAAgMZ1RS QwAABDwAAAgMYlRSQwAABDwAAAgMdGV4dAAAAABDb3B5cmlnaHQgKGMpIDE5OTggSGV3bGV0dC1Q YWNrYXJkIENvbXBhbnkAAGRlc2MAAAAAAAAAEnNSR0IgSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAS c1JHQiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAFhZWiAAAAAAAADzUQABAAAAARbMWFlaIAAAAAAAAAAAAAAAAAAAAABYWVogAAAA AAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9kZXNj AAAAAAAAABZJRUMgaHR0cDovL3d3dy5pZWMuY2gAAAAAAAAAAAAAABZJRUMgaHR0cDovL3d3dy5p ZWMuY2gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGVzYwAA AAAAAAAuSUVDIDYxOTY2LTIuMSBEZWZhdWx0IFJHQiBjb2xvdXIgc3BhY2UgLSBzUkdCAAAAAAAA AAAAAAAuSUVDIDYxOTY2LTIuMSBEZWZhdWx0IFJHQiBjb2xvdXIgc3BhY2UgLSBzUkdCAAAAAAAA AAAAAAAAAAAAAAAAAAAAAGRlc2MAAAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlvbiBp biBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAACxSZWZlcmVuY2UgVmlld2luZyBDb25kaXRpb24gaW4g SUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB2aWV3AAAAAAATpP4AFF8uABDP FAAD7cwABBMLAANcngAAAAFYWVogAAAAAABMCVYAUAAAAFcf521lYXMAAAAAAAAAAQAAAAAAAAAA AAAAAAAAAAAAAAKPAAAAAnNpZyAAAAAAQ1JUIGN1cnYAAAAAAAAEAAAAAAUACgAPABQAGQAeACMA KAAtADIANwA7AEAARQBKAE8AVABZAF4AYwBoAG0AcgB3AHwAgQCGAIsAkACVAJoAnwCkAKkArgCy ALcAvADBAMYAywDQANUA2wDgAOUA6wDwAPYA+wEBAQcBDQETARkBHwElASsBMgE4AT4BRQFMAVIB WQFgAWcBbgF1AXwBgwGLAZIBmgGhAakBsQG5AcEByQHRAdkB4QHpAfIB+gIDAgwCFAIdAiYCLwI4 AkECSwJUAl0CZwJxAnoChAKOApgCogKsArYCwQLLAtUC4ALrAvUDAAMLAxYDIQMtAzgDQwNPA1oD ZgNyA34DigOWA6IDrgO6A8cD0wPgA+wD+QQGBBMEIAQtBDsESARVBGMEcQR+BIwEmgSoBLYExATT BOEE8AT+BQ0FHAUrBToFSQVYBWcFdwWGBZYFpgW1BcUF1QXlBfYGBgYWBicGNwZIBlkGagZ7BowG nQavBsAG0QbjBvUHBwcZBysHPQdPB2EHdAeGB5kHrAe/B9IH5Qf4CAsIHwgyCEYIWghuCIIIlgiq CL4I0gjnCPsJEAklCToJTwlkCXkJjwmkCboJzwnlCfsKEQonCj0KVApqCoEKmAquCsUK3ArzCwsL Igs5C1ELaQuAC5gLsAvIC+EL+QwSDCoMQwxcDHUMjgynDMAM2QzzDQ0NJg1ADVoNdA2ODakNww3e DfgOEw4uDkkOZA5/DpsOtg7SDu4PCQ8lD0EPXg96D5YPsw/PD+wQCRAmEEMQYRB+EJsQuRDXEPUR ExExEU8RbRGMEaoRyRHoEgcSJhJFEmQShBKjEsMS4xMDEyMTQxNjE4MTpBPFE+UUBhQnFEkUahSL FK0UzhTwFRIVNBVWFXgVmxW9FeAWAxYmFkkWbBaPFrIW1hb6Fx0XQRdlF4kXrhfSF/cYGxhAGGUY ihivGNUY+hkgGUUZaxmRGbcZ3RoEGioaURp3Gp4axRrsGxQbOxtjG4obshvaHAIcKhxSHHscoxzM HPUdHh1HHXAdmR3DHeweFh5AHmoelB6+HukfEx8+H2kflB+/H+ogFSBBIGwgmCDEIPAhHCFIIXUh oSHOIfsiJyJVIoIiryLdIwojOCNmI5QjwiPwJB8kTSR8JKsk2iUJJTglaCWXJccl9yYnJlcmhya3 JugnGCdJJ3onqyfcKA0oPyhxKKIo1CkGKTgpaymdKdAqAio1KmgqmyrPKwIrNitpK50r0SwFLDks biyiLNctDC1BLXYtqy3hLhYuTC6CLrcu7i8kL1ovkS/HL/4wNTBsMKQw2zESMUoxgjG6MfIyKjJj Mpsy1DMNM0YzfzO4M/E0KzRlNJ402DUTNU01hzXCNf02NzZyNq426TckN2A3nDfXOBQ4UDiMOMg5 BTlCOX85vDn5OjY6dDqyOu87LTtrO6o76DwnPGU8pDzjPSI9YT2hPeA+ID5gPqA+4D8hP2E/oj/i QCNAZECmQOdBKUFqQaxB7kIwQnJCtUL3QzpDfUPARANER0SKRM5FEkVVRZpF3kYiRmdGq0bwRzVH e0fASAVIS0iRSNdJHUljSalJ8Eo3Sn1KxEsMS1NLmkviTCpMcky6TQJNSk2TTdxOJU5uTrdPAE9J T5NP3VAnUHFQu1EGUVBRm1HmUjFSfFLHUxNTX1OqU/ZUQlSPVNtVKFV1VcJWD1ZcVqlW91dEV5JX 4FgvWH1Yy1kaWWlZuFoHWlZaplr1W0VblVvlXDVchlzWXSddeF3JXhpebF69Xw9fYV+zYAVgV2Cq YPxhT2GiYfViSWKcYvBjQ2OXY+tkQGSUZOllPWWSZedmPWaSZuhnPWeTZ+loP2iWaOxpQ2maafFq SGqfavdrT2una/9sV2yvbQhtYG25bhJua27Ebx5veG/RcCtwhnDgcTpxlXHwcktypnMBc11zuHQU dHB0zHUodYV14XY+dpt2+HdWd7N4EXhueMx5KnmJeed6RnqlewR7Y3vCfCF8gXzhfUF9oX4BfmJ+ wn8jf4R/5YBHgKiBCoFrgc2CMIKSgvSDV4O6hB2EgITjhUeFq4YOhnKG14c7h5+IBIhpiM6JM4mZ if6KZIrKizCLlov8jGOMyo0xjZiN/45mjs6PNo+ekAaQbpDWkT+RqJIRknqS45NNk7aUIJSKlPSV X5XJljSWn5cKl3WX4JhMmLiZJJmQmfyaaJrVm0Kbr5wcnImc951kndKeQJ6unx2fi5/6oGmg2KFH obaiJqKWowajdqPmpFakx6U4pammGqaLpv2nbqfgqFKoxKk3qamqHKqPqwKrdavprFys0K1Erbiu La6hrxavi7AAsHWw6rFgsdayS7LCszizrrQltJy1E7WKtgG2ebbwt2i34LhZuNG5SrnCuju6tbsu u6e8IbybvRW9j74KvoS+/796v/XAcMDswWfB48JfwtvDWMPUxFHEzsVLxcjGRsbDx0HHv8g9yLzJ Osm5yjjKt8s2y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc 1+DYZNjo2WzZ8dp22vvbgNwF3IrdEN2W3hzeot8p36/gNuC94UThzOJT4tvjY+Pr5HPk/OWE5g3m lucf56noMui86Ubp0Opb6uXrcOv77IbtEe2c7ijutO9A78zwWPDl8XLx//KM8xnzp/Q09ML1UPXe 9m32+/eK+Bn4qPk4+cf6V/rn+3f8B/yY/Sn9uv5L/tz/bf///+4ADkFkb2JlAGTAAAAAAf/bAIQA BgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoKDBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8f Hx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f Hx8fHx8fHx8fHx8fHx8f/8AAEQgAOAEAAwERAAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQF AwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAAAQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMB AgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPBUtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdU ZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eX p7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZqbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUE BQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEyobHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PS NeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG 1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/a AAwDAQACEQMRAD8A4DrGs6prWpT6nqt1JeX1yxea4lYsxJ+fQDsBsMklB4q7FXYq7FXYq7FXYq+l P+cOtc1aS+17RpLqSTTIYIriC1diyRymQqzID9nkD8VOuAqX0/gQ8C/PP/nI258rapL5Y8qJFLq0 AH1/UZRzSB2FfSjQ/C0gBBYmqjpQmtDSXhf/ACtX879Zd7qDW9Wn3o5sw6Rg9acYFVB92KozRv8A nID84vLl8FudVmuxGf3tjqcYk5CvRiwWZfoYYq+sPyn/ADQ0r8w/Lf6TtU+rX1uwh1KwLcjDKRUE Gg5I43VvmOoOBDKda1a00fRr/V7zl9U063lu7ngOTenAhkfiNqniu2KvNPyo/wCcgtH/ADC1650S LS59NuooDcwM8iyrJGjBXqVVODDmtOvffxVer4q7FXYq7FXYq7FXjvkT/nJbyz5u86p5Zg024tFu mkXTr6RlYSmMFxzjArHyRSep8MNK9iwK7FXYq7FXYq7FXYq7FXYq/MzJJZJ+XXkm786+cNP8uW0y 2zXjMZblhyEcUSGSRuNRyPFdh3Phir3TVP8AnHP8mtIuzY6p5wvLa9RVMkTGAEchUGnpNSvXrlMs 8YmiXOwdmajNHjhAyj3oT/lRf5C/9Tvdf8FD/wBUcj+Zh3t38iaz/U5O/wCVF/kL/wBTvdf8FD/1 Rx/Mw71/kTWf6nJ3/Ki/yF/6ne6/4KH/AKo4/mYd6/yJrP8AU5O/5UX+Qv8A1O91/wAFD/1Rx/Mw 71/kTWf6nJEaf/zj3+SWo3sVlZecrye6nPGGFWgqxpWgrFko54E0C15eytVjiZShIRD2v8t/yl8o /l9aXEWhpNJcXhX61fXTiSdwleKVVURVWp2VR71yx1zMndURnc0VQSx8ANzir4H/AC20uPzz+bum W+sj1o9WvpbvUFJ/vAoe5kUnrR+BB74UvvS1tLW0to7W0hS3toVCQwRKERFHRVVQAB8sCGFfnP5E sfN3kLVrY2K3WrW1tJcaS6oDOLiJeaJG3X94V4Ed64q8G/5xIi16x8/6taTWk8NnLp7i79SN0VJo po/T5ch9r4nAHz8MJSXrP5tfnJ+Wtt5c81eVZdbQ67Jp15ZraRw3Eo+sS27okZljjaINyYA1f4T1 pih86/8AOO/nPy15Q8/Sar5ivPqNg1jNAJvTll/eO8ZVeMKSNuFPbEpfU+gfnt+VGv6tbaRpevpN qF23p20MkF1AHc9FDzRRpyPQCu52G+BDPHdERndgqKCzMxoABuSScVea65/zkb+UWkXDW760L2ZD RhZRSTp9Eqr6TfQxxpVDSv8AnJf8oNQnWA6u9k7mim6t5Y0+lwrIv+yIxpXptpd2l5bRXVpNHc2s yh4Z4mDxup6MrKSCD4jFWI+a/wA5Py18p6odK17W0tdQCLI9ukNxOyq32efoRyhSRvRt6b4q+Mfy d8waR5d/MrQ9Z1i4+q6bZyyNcz8Hk4hoXQfDGrufiYdBhS+vdO/5yE/J3UL6CxtfMSfWLlxFCJbe 7hQsxoA0ksKRr82YYKQy3zR5t8u+VdJfVvMF8lhYIyp6rhmJduioiBndu9FB236Yqv8ALXmjQPM+ kRavoN6l9p8xKpMgZaMpoVZHCujDwYA4qmmKsU1r81fy90TzHB5c1XWobXWbjgEtmEhCmT7AklVT FET/AJbDbfvirK8VYv5x/M3yL5OCjzDq8VnO6847UBpZ2HQERRB3oT3Ip74qwyD/AJyl/KCSYRvf 3UKE0Mz2spQe9EDt/wALhpXo/lzzT5d8y6eNQ0HUIdRsyeJkgYNxbrxdftI3swBwK/ODJJZf+U3n e38k+fdN8w3MDXNpbmSO5ijp6npzRtGzJUgFl5VAPXpt1wK9/wDMXnr/AJxs8z6o+s6n5hvILydU WSJLe7UDgoUbC2kFaDs2Y2TSxmbLu9F2/qNNjGOHDwjvH7Us/SX/ADiz/wBTPff8iLz/ALJMh+Rh 5uV/or1fdD5ftd+kv+cWf+pnvv8AkRef9kmP5GHmv+ivV90Pl+136S/5xZ/6me+/5EXn/ZJj+Rh5 r/or1fdD5ftd+kv+cWf+pnvv+RF5/wBkmP5GHmv+ivV90Pl+1HaH5q/5xk0XV7XVbPzNdm5tH9SI SW94ycqU3AtR4+OShpIxNi2jVe0epzYzjlw8MvL9r23yj568pecLKS98t6lHqEELBJuAdHRjuOcc ipItabVXfMl0Ce4q+DfO/lnzN+U35kiWz5QfVbg3ehXxXkksHL4euzEKeEi/wIwpfRn5af8AOTnk /wAz+lYa9x0DWWooMrf6HKx2/dzH7BP8slPAMcaQ9mBBAINQdwRgV2Kvl38zf+cX54f8U+cINfQ2 0a3uriye2POiq9wYvUElP8nlx96YbS8f/Kr8uZvzB8zvoMN8unuttJdeu8ZlFI2RePEMnXn44q92 8of84mXmh+atI1ufzJHPHpd5BemBLVlZzbyCVU5GU05FaE0xtbV/+cvPOl/p+i6V5XspTFHqxln1 EqaFoYCojj/1WdiT/qjEKHnn5U/840aj528uQeYr/V10qwumcWkKQGeWRY2MZdqvEqDmpp1qMbVM vzB/5xSvvLnlq+13SdcGpLp0LXNzZy2/oOYYgWkZHEkgJVRXiQOnXG1tMP8AnEHzrqI1jUvJ9xM0 mnyW7X1jGxqIpY3VZFTwEgk5HtVfc4lSyr8yv+cX7nzf501HzHbeYEs01Eo7W0lsZCjJGsZowkSo PCvTG1fM/kLylJ5v83ad5cjuRZvqLsi3LIZAnCNpK8QVr9inXCr3SH/nDK99aP1vNMZh5D1Qlowb jX4uNZaVp0wWtsj/AOcxf+UG0X/tpj/qHlxChFf84ff+S01P/ttT/wDULa4lS9zwIfB//OQf/k4/ Mv8Axmi/6h4sKX255r1xNA8satrbqHGmWk916Z/aMMZcL/siKYEPhfyd5V80fmz5+ltmvAdQvPUv dS1CerLHEpAZ+K/5TqiKKDcDYYUvY73/AJwypZsbLzVzvVWqLNZ8InanQlZnZAT3o1PA42tvMfyc 8x635C/Ny006dmiWa+/Q2tWgNUPKX0CSB1MUnxAjw9zirzaeCaCaSCdGimiYpLE4KsrKaMrA7gg4 VWYq7FXYq7FXYq7FXYq+h/8AnDYn/EvmIV2NnCSPlKcBUvqzAhKPM/lLy55p0xtM1+wiv7Ntwkg+ JGpTlG4oyN7qQcVfMv5lf84oa1pvq6j5KmbVbIVZtLmKrdxjr+7f4UlA8Nm9mw2m2FeQPzu/ML8u rv8ARc/qXemWz+ncaHqAdWi4n4liLD1IGHh9nxU4q+vvy8/MXy7580FNW0aUgqQl5ZyU9a3lp9iQ Dx/ZYbEfTgQu/ND/AMlp5t/7Yuof9QsmKvlz/nEn/wAmnL/2zLj/AJORYSkvsnAhjPnP8tPI/nQ2 x8y6Wt+1nyFs/qTQuoenIcoXjYg06E4qlWrfmj+Uvkiyj0yXWLK0jsVEMWmWf7+SMJsE9KAOU/2V MVePfmT/AM5X6TqWg6jovlfS5y1/C9q2oXvBFRJV4OyQoXLEqSF5MKdaHphpNMI/5xV/8m3b/wDM Fdf8RGJUvtPAh8H/APOPn/k4/LX/ABml/wCoeXCl94YEPAv+cxf+UG0X/tpj/qHlwhIRX/OH3/kt NT/7bU//AFC2uJUvc8CHwf8A85B/+Tj8y/8AGaL/AKh4sKX2p560KfX/ACZrmiW7BLjUbGe3gY7A SSRkJX25UrgQ+Ify28+az+Vvnae/k04TXEccun6lp05MThTIrOoajcHV4l3KnCl9P+UP+cnfyy1/ 04b65k0G9fYxX4pDX2uE5Rge78caQyhvyv8Ayv1fzDB5yXSbW71UutxDqMMjmJ5ENVlKRv6Dty35 FSa4FQ/5jeW/y+tNJvfM+seV7DVbqD0/VZ4IhLIZJEiHKQqSac+9cu0+LxJiN1bj6vUeDjM6uv10 8o/xX+T/AP5biw+6H/qlmy/kk/zvsdL/AKIR/M+39jv8V/k//wCW4sPuh/6pY/ySf532L/ohH8z7 f2O/xX+T/wD5biw+6H/qlj/JJ/nfYv8AohH8z7f2O/xX+T//AJbiw+6H/qlj/JJ/nfYv+iEfzPt/ Y7/Ff5P/APluLD7of+qWP8kn+d9i/wCiEfzPt/Yi/wAwvy+/LfXfyXvPOGk+X4NDv7aF7i2NqAhB hnMTJJwCq6uFPUbZrc+Lw5mN3TutJqPGxidVf66fKeVuS+hv+cNv+Um8xf8AMFF/ydwFS+qrieG3 gkuJ3WKCFWklkc0VUUVZiT0AAwIY55V/MvyL5rup7Xy/rEF/c26h5YU5o/EmnJVkVCw8StaYqybF Xn35vflP5Z87+X7uS6t0h1y2gd7DVEAWVWRSypIw+3GTsVbp2ocVfNv/ADixr17p/wCa9ppsTn6r rMFxBcx1+E+jC9wjEeKmKgPucJS+rfzQ/wDJaebf+2LqH/ULJgQ+XP8AnEn/AMmnL/2zLj/k5FhK S+ycCHzN/wA5VfmrrNlqMXkjRrlrWBoFn1iaIlZJPVrwg5DcLwHJv5qgdK1ISGB+Qv8AnGXz55q0 +11a5lt9G0q7USwSXJZ53iYVWRIUHRhuObLXr0xtXp13/wA4t+Q/LflDWtV1O9utWvrHT7q4R3YW 9urwwO6uI46vsRWhkIxtXmf/ADir/wCTbt/+YK6/4iMSpfaeBD4P/wCcfP8Aycflr/jNL/1Dy4Uv vDAh4F/zmL/yg2i/9tMf9Q8uEJCK/wCcPv8AyWmp/wDban/6hbXEqXueBD4P/wCcg/8AycfmX/jN F/1DxYUvvDAhh3nj8ovIHnUmbXNMR77iFXUYCYbkAbCrp9ug6BwwGKvCvOH/ADh7qkHqXHlLV0vI xutjqA9KWngJox6bn5ogw2m3nn5bef8AzX+VnnkafqDS29hHci317SJDyQLXi8iqCV9RB8SsvXx4 nFXrXmD/AJzB0Nb65srDyu2q6YjFUuLi6WD1QrbN6PoT0U0qKtX2GEWOTExBFFJ/+httF/8ALf23 /SZH/wBkeS8SXeWHgQ/mj5O/6G20X/y39t/0mR/9kePiS7yvgQ/mj5O/6G20X/y39t/0mR/9kePi S7yvgQ/mj5O/6G20X/y39t/0mR/9kePiS7yvgQ/mj5O/6G20X/y39t/0mR/9kePiS7yvgQ/mj5Mc /MX/AJyY1XzV5Xn8t6ZocGh6fdgJdFZfrDmPlyKJSOFUDHr8JyJ35tkYgCg8WxS+hv8AnDb/AJSb zF/zBRf8ncBUvqLWNOi1PSb3TZTSK+t5baQjssqFD+DYEPhDyV5g1T8q/wAzkutQtGefSZpbTUrO vFnjcGN+JO3Qh07HbthS+uNK/wCcgPyj1K2SdPMMFszLVobtXgkQgVKkOoFf9UkeGBDEvzS/5yU8 i2Plu+sfLN9+ltbvIXgt2gVxDAZFK+q8rqqnjWoVa79adcNK8p/5xO8q3eo/mI2v+mRZaHbyEzU+ Ez3KGFEr48GdvoxKS+tfMWkR6z5f1PR5G4x6laT2jt4CeNoyf+GwIfDP5bea738sPzMS71S2cfUZ JrDWLRaep6ZPCTjWgJRlDDxphS+zPLn5qfl55jnt7bR9etLm7ugTBZ8+E7UHIj0n4vUAdKYEPln/ AJyr0q7tPzWmvZUIt9StLeW2kpsRGnouK+IaPf5jCEvePIH5+flnd+TtOa/1mDS761toYbyyuSyu skaBG4bfvFJFQV7daYEPOfz0/wCcj9C1fy/deV/Jzvcpfr6WoaoyNEghr8UcKuFdi/RmIA49K1qD SWFf84q/+Tbt/wDmCuv+IjEqX2ngQ+BWGqflT+bfJ4fUm0C+LJG23rWzVoQe3qwPse1cKX2H5a/O r8svMMVobLXraK6vCiR2Fy4guBK+wiKPSrctvhqD2JwIYx/zlJ5Yu9b/ACukubSMyTaLdR38iKCW MKq8UtAOyiXmfZcIV49/zjb+c+g+S0v9A8xyNbaXfTC6tr5VaRYp+IjcSKgZuLqi0YDam+24Sl7l 5l/5yM/KrRdOe5g1dNWueNYLKxBkd2pUAtQIg8Sx+g9MFIfGfnPzReeavNOpeYbxFin1GYymJN1R aBUQHvxRQK98kl92/mvaa1d/lx5hg0SWWLVDZu9u0BZZT6dHZEK/FydFKinjkUPlz8gvzpHlLzJc xeab+6m0TUogjTO0lwLeZGqknCrNxILK3EV6eGFL6Ruvz6/KK3tGuW8y20iKCRHEJJJDTsI1UtX6 MCHyB5t1Kf8AMn817q50qBkbX76OCxjYfEE+GCNnAJoeChn3233wpQ2vflP+YuianPp915fvpHhY qs9vbyzQyAHZ45EVlZT/ALe+Kpf/AID88/8AUu6n/wBIdx/zRhV3+A/PP/Uu6n/0h3H/ADRirv8A Afnn/qXdT/6Q7j/mjFXf4D88/wDUu6n/ANIdx/zRirv8B+ef+pd1P/pDuP8AmjFXf4D88/8AUu6n /wBIdx/zRirv8B+ef+pd1P8A6Q7j/mjFX0X/AM4ofl/5s0K51rWta06bTba8hit7SO6RoppCHLsw jYBgoFNyN67YCpfReBDzn80fyL8ofmAwvLrnp2togRNTtwCzKPsrNGfhkA7dG7VpirxW7/5w382r MRaa/YTQfsvKk0Tn5oqyj/hsNptMfL//ADhvd/WUfzF5gjFspq8GnxMXcV6CWXiE/wCAONrb6G8p eUPL/lLRYdG0K0W1soviIG7yOftSSOd2dqdT8umBCc4q8p/Nz/nH3y95+nOq2850nzCFCNeKnqRT KgoomjqpJA2Dg1p1rQYq848m/wDOKXm3RPOGi6zc6zYPa6XfW95IkQmMjrbyrJxAZFFW4067YbS9 w/Mn8svLn5gaKum6wrRywMZLG+ioJYJGFCVrsVag5Kdj8wDgQ+e7v/nDjzctw62evWEtsD+7kmSa JyP8pFWUD/gjhtNsn8q/84eaNbky+aNZlvmKkLa2SegisRQEyOXZ6dfsrja2yL8o/wDnHNPIXmuX zBPrR1FlhkgtIEh9EASmjNIS8lfhGwFMbQ9owK88/Nn8lfLf5iWscly50/WrZSlrqkSh24Vr6cqE r6iVNQKgg9DuaqvEB/zhz5yFwB+ndO9DkKyAT8+NevHhStO3L6cNpt9YyRxyRtHIoeNwVdGFQQdi CD2wIfP3n3/nEjRNTvJb/wAp6h+h3lYu+nToZLYMT/uplIeNf8mje1BthtNsTsP+cN/ND3KjUPMF jBbftvbxzTSfQjiEf8Nja2yG9/5w30htVtnsvME8WlKIxdwTQq87Ff70xyKURefaqHj742tvo3Ah 4V+Y/wDzit5e8xajNq3l29/Qd5cMXntDH6lozncsiqUaKp3NKjwAw2lgEX/OHPnQyKJdc01IyfjZ ROzAeylFr9+Nrb2P8qP+cf8Ayv5AuBqZmfVtf4FBfyqI0iDCjCCIFuPIbFmZj4UBOBD/AP/Z - - - - uuid:0dd42048-77f8-4e9e-8c41-a906388d66f6 - xmp.did:6a095f1e-0eb1-2e45-ab17-a343816fd46e - uuid:5D20892493BFDB11914A8590D31508C8 - proof:pdf - - xmp.iid:70cae1ca-1103-a949-b577-7861e1e665ff - xmp.did:70cae1ca-1103-a949-b577-7861e1e665ff - uuid:5D20892493BFDB11914A8590D31508C8 - proof:pdf - - - - - saved - xmp.iid:c85cb486-a8f9-fd44-9b74-5dfac7c2983a - 2022-02-03T15:12:11+05:00 - Adobe Illustrator CC 2015 (Windows) - / - - - saved - xmp.iid:6a095f1e-0eb1-2e45-ab17-a343816fd46e - 2022-02-03T15:16:08+05:00 - Adobe Illustrator CC 2015 (Windows) - / - - - - Print - Document - False - False - 1 - - 2000.000000 - 600.000000 - Pixels - - - - Cyan - Magenta - Yellow - Black - - - - - - Default Swatch Group - 0 - - - - White - RGB - PROCESS - 255 - 255 - 255 - - - Black - RGB - PROCESS - 35 - 31 - 32 - - - CMYK Red - RGB - PROCESS - 237 - 28 - 36 - - - CMYK Yellow - RGB - PROCESS - 255 - 242 - 0 - - - CMYK Green - RGB - PROCESS - 0 - 166 - 81 - - - CMYK Cyan - RGB - PROCESS - 0 - 174 - 239 - - - CMYK Blue - RGB - PROCESS - 46 - 49 - 146 - - - CMYK Magenta - RGB - PROCESS - 236 - 0 - 140 - - - C=15 M=100 Y=90 K=10 - RGB - PROCESS - 190 - 30 - 45 - - - C=0 M=90 Y=85 K=0 - RGB - PROCESS - 239 - 65 - 54 - - - C=0 M=80 Y=95 K=0 - RGB - PROCESS - 241 - 90 - 41 - - - C=0 M=50 Y=100 K=0 - RGB - PROCESS - 247 - 148 - 30 - - - C=0 M=35 Y=85 K=0 - RGB - PROCESS - 251 - 176 - 64 - - - C=5 M=0 Y=90 K=0 - RGB - PROCESS - 249 - 237 - 50 - - - C=20 M=0 Y=100 K=0 - RGB - PROCESS - 215 - 223 - 35 - - - C=50 M=0 Y=100 K=0 - RGB - PROCESS - 141 - 198 - 63 - - - C=75 M=0 Y=100 K=0 - RGB - PROCESS - 57 - 181 - 74 - - - C=85 M=10 Y=100 K=10 - RGB - PROCESS - 0 - 148 - 68 - - - C=90 M=30 Y=95 K=30 - RGB - PROCESS - 0 - 104 - 56 - - - C=75 M=0 Y=75 K=0 - RGB - PROCESS - 43 - 182 - 115 - - - C=80 M=10 Y=45 K=0 - RGB - PROCESS - 0 - 167 - 157 - - - C=70 M=15 Y=0 K=0 - RGB - PROCESS - 39 - 170 - 225 - - - C=85 M=50 Y=0 K=0 - RGB - PROCESS - 28 - 117 - 188 - - - C=100 M=95 Y=5 K=0 - RGB - PROCESS - 43 - 57 - 144 - - - C=100 M=100 Y=25 K=25 - RGB - PROCESS - 38 - 34 - 98 - - - C=75 M=100 Y=0 K=0 - RGB - PROCESS - 102 - 45 - 145 - - - C=50 M=100 Y=0 K=0 - RGB - PROCESS - 146 - 39 - 143 - - - C=35 M=100 Y=35 K=10 - RGB - PROCESS - 158 - 31 - 99 - - - C=10 M=100 Y=50 K=0 - RGB - PROCESS - 218 - 28 - 92 - - - C=0 M=95 Y=20 K=0 - RGB - PROCESS - 238 - 42 - 123 - - - C=25 M=25 Y=40 K=0 - RGB - PROCESS - 194 - 181 - 155 - - - C=40 M=45 Y=50 K=5 - RGB - PROCESS - 155 - 133 - 121 - - - C=50 M=50 Y=60 K=25 - RGB - PROCESS - 114 - 102 - 88 - - - C=55 M=60 Y=65 K=40 - RGB - PROCESS - 89 - 74 - 66 - - - C=25 M=40 Y=65 K=0 - RGB - PROCESS - 196 - 154 - 108 - - - C=30 M=50 Y=75 K=10 - RGB - PROCESS - 169 - 124 - 80 - - - C=35 M=60 Y=80 K=25 - RGB - PROCESS - 139 - 94 - 60 - - - C=40 M=65 Y=90 K=35 - RGB - PROCESS - 117 - 76 - 41 - - - C=40 M=70 Y=100 K=50 - RGB - PROCESS - 96 - 57 - 19 - - - C=50 M=70 Y=80 K=70 - RGB - PROCESS - 60 - 36 - 21 - - - - - - Grays - 1 - - - - C=0 M=0 Y=0 K=100 - RGB - PROCESS - 35 - 31 - 32 - - - C=0 M=0 Y=0 K=90 - RGB - PROCESS - 65 - 64 - 66 - - - C=0 M=0 Y=0 K=80 - RGB - PROCESS - 88 - 89 - 91 - - - C=0 M=0 Y=0 K=70 - RGB - PROCESS - 109 - 110 - 113 - - - C=0 M=0 Y=0 K=60 - RGB - PROCESS - 128 - 130 - 133 - - - C=0 M=0 Y=0 K=50 - RGB - PROCESS - 147 - 149 - 152 - - - C=0 M=0 Y=0 K=40 - RGB - PROCESS - 167 - 169 - 172 - - - C=0 M=0 Y=0 K=30 - RGB - PROCESS - 188 - 190 - 192 - - - C=0 M=0 Y=0 K=20 - RGB - PROCESS - 209 - 211 - 212 - - - C=0 M=0 Y=0 K=10 - RGB - PROCESS - 230 - 231 - 232 - - - C=0 M=0 Y=0 K=5 - RGB - PROCESS - 241 - 242 - 242 - - - - - - Brights - 1 - - - - C=0 M=100 Y=100 K=0 - RGB - PROCESS - 237 - 28 - 36 - - - C=0 M=75 Y=100 K=0 - RGB - PROCESS - 242 - 101 - 34 - - - C=0 M=10 Y=95 K=0 - RGB - PROCESS - 255 - 222 - 23 - - - C=85 M=10 Y=100 K=0 - RGB - PROCESS - 0 - 161 - 75 - - - C=100 M=90 Y=0 K=0 - RGB - PROCESS - 33 - 64 - 154 - - - C=60 M=90 Y=0 K=0 - RGB - PROCESS - 127 - 63 - 152 - - - - - - - Adobe PDF library 15.00 - - - - - - - - - - - - - - - - - - - - - - - - - -endstream endobj 3 0 obj <> endobj 7 0 obj <>/Resources<>/ExtGState<>/Properties<>>>/Thumb 12 0 R/TrimBox[0.0 0.0 2000.0 600.0]/Type/Page>> endobj 8 0 obj <>stream -HI%EoEˀGax||k )A; ʈ&_o_!?ח -Ƿ#1z>woxţ~&G -9㻗~lyi%);ֳt#k?^&Z?sגϞ3GvK?cR?T'pfscٚ8+nqgyKm˟:.97…*/:^~ -̱0~mgv𛎕E|0qUY{>bmhqs;T%Q+l>"fq;rLº"k-=[ҤQ(Ӱ9u͙=o $͎R!DK!D$-W*UV]Fٽ!3sXK!b9klEʙܧ6^7J΂6ܯAy شqF:`>tVIk zz.8wΒ}*[rG[)2%Z --D%V˕ޑlEJ9TsGt)fR2{Li& _MX,mJ^1Rx -%'Q~\UͲ9 G@__|Y ,huFSPVէ|~;XHgYb^~m{kC gۢߡLjY]ގM=dnJKM~WpbUzۊۜ5QGm>TFE8@]hzR#ŭd;Za] ǝV|K|Muѷw\T%jʈw(BR3K@F!1QQSŝ( dnd\m{5w0?vʥLOW%LwgLgOw;Ƨ5 vc(* a^G -Iuib@3&2&I3/zkxi,YIvKu_uNՔ -g!VT&-emVeAKhL `VЙ\(I02gY:QXϴR#ż11tUȱw5hfG-s!&ĵB&6uYm~EXHArOו9`G=d0Ǧ4V3 ÅiyMJ>j>nk^rg -`3L!35u;ci '$ܷClfJD13Z(EvZ>oEA Zȡ_<4P98,LD3OxHS}{NEZ* ذ9x%CȧF_SjJ]*R9,z(-bZ{aB౺j>]U7kmB -~&$%6I rJ֘}ቿ -:DH_ Uق'xhޔck>_fqK .y-ܡΒN^JuG?F@8*5NܠnAn`On0~}n`7(?l[sypoBBrfӅ-^a)nڰ7u %CWf/fk;4LuB b^tL@X"NVČC=i64IL)ny졑I סּko_$9LPČi1ai6uLYW40QWbI݇q'P9oLgBTjFHSڨ"]${ыާ~@j<.IEOo̝r"^l̄x؋ @h3#ȚX<^Ixq{}JoO~^?J5&*])c/QoHܠ)o1)=.H -X-߲1>stream -8;Z\4_$u#N$tKHHm0:5UTMs8T__?nu.1o.!5%7235#k/g"UjA^q.rIeoa>R%gA>=r -#6g2A_Y%nhCpgV"NY4,deo.*Hn$e*?1J3T?1!3aYm4oSI7WIOiT-*ScU\?Msp1PKc -$Zjr`V/W'\o89/Oh^H+X?Zr8As%iA82^TA<\;*%Q+k< -k6.R>72eUR(o`#W:HGG3+sjiL=&d[6U9s>LVeNj@h:>g?F8!Zpk -D-W&=^:m(8!ns4=PurG2<_gsQfgS'n;^FJUmtH.`3T=r&Dn%`aL[jL5#j033dY^RQ -jXb6g&*I:!Z9A?jB'e7`~> -endstream endobj 13 0 obj [/Indexed/DeviceRGB 255 14 0 R] endobj 14 0 obj <>stream -8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 -b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` -E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn -6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( -l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~> -endstream endobj 5 0 obj <> endobj 15 0 obj [/View/Design] endobj 16 0 obj <>>> endobj 11 0 obj <> endobj 10 0 obj [/ICCBased 17 0 R] endobj 17 0 obj <>stream -HyTSwoɞc [5laQIBHADED2mtFOE.c}08׎8GNg9w߽'0 ֠Jb  - 2y.-;!KZ ^i"L0- @8(r;q7Ly&Qq4j|9 -V)gB0iW8#8wթ8_٥ʨQQj@&A)/g>'Kt;\ ӥ$պFZUn(4T%)뫔0C&Zi8bxEB;Pӓ̹A om?W= -x-[0}y)7ta>jT7@tܛ`q2ʀ&6ZLĄ?_yxg)˔zçLU*uSkSeO4?׸c. R ߁-25 S>ӣVd`rn~Y&+`;A4 A9=-tl`;~p Gp| [`L`< "A YA+Cb(R,*T2B- -ꇆnQt}MA0alSx k&^>0|>_',G!"F$H:R!zFQd?r 9\A&G rQ hE]a4zBgE#H *B=0HIpp0MxJ$D1D, VĭKĻYdE"EI2EBGt4MzNr!YK ?%_&#(0J:EAiQ(()ӔWT6U@P+!~mD eԴ!hӦh/']B/ҏӿ?a0nhF!X8܌kc&5S6lIa2cKMA!E#ƒdV(kel }}Cq9 -N')].uJr - wG xR^[oƜchg`>b$*~ :Eb~,m,-ݖ,Y¬*6X[ݱF=3뭷Y~dó ti zf6~`{v.Ng#{}}jc1X6fm;'_9 r:8q:˜O:ϸ8uJqnv=MmR 4 -n3ܣkGݯz=[==<=GTB(/S,]6*-W:#7*e^YDY}UjAyT`#D="b{ų+ʯ:!kJ4Gmt}uC%K7YVfFY .=b?SƕƩȺy چ k5%4m7lqlioZlG+Zz͹mzy]?uuw|"űNwW&e֥ﺱ*|j5kyݭǯg^ykEklD_p߶7Dmo꿻1ml{Mś nLl<9O[$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! -zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Km -endstream endobj 9 0 obj <> endobj 18 0 obj <> endobj 19 0 obj <>stream -%!PS-Adobe-3.0 -%%Creator: Adobe Illustrator(R) 17.0 -%%AI8_CreatorVersion: 19.0.0 -%%For: (H TECH) () -%%Title: (svg file.svg) -%%CreationDate: 2/3/2022 3:16 PM -%%Canvassize: 16383 -%%BoundingBox: 43 -534 1957 -121 -%%HiResBoundingBox: 43.468601053878 -533.128335741215 1956.53139894612 -121.656243053342 -%%DocumentProcessColors: Cyan Magenta Yellow Black -%AI5_FileFormat 13.0 -%AI12_BuildNumber: 44 -%AI3_ColorUsage: Color -%AI7_ImageSettings: 0 -%%RGBProcessColor: 0 0 0 ([Registration]) -%AI3_Cropmarks: 0 -627.392289228923 2000 -27.3922892289229 -%AI3_TemplateBox: 1000.5 -1000.5 1000.5 -1000.5 -%AI3_TileBox: 616 -621.392289228923 1384 -33.3922892289229 -%AI3_DocumentPreview: None -%AI5_ArtSize: 14400 14400 -%AI5_RulerUnits: 6 -%AI9_ColorModel: 1 -%AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 -%AI5_TargetResolution: 800 -%AI5_NumLayers: 1 -%AI17_Begin_Content_if_version_gt:17 1 -%AI9_OpenToView: -566.337533753377 419.322832283231 0.3333 1243 611 18 0 0 78 112 0 0 0 1 1 0 1 1 0 1 -%AI17_Alternate_Content -%AI9_OpenToView: -566.337533753377 419.322832283231 0.3333 1243 611 18 0 0 78 112 0 0 0 1 1 0 1 1 0 1 -%AI17_End_Versioned_Content -%AI5_OpenViewLayers: 7 -%%PageOrigin:694 -1396 -%AI7_GridSettings: 72 8 72 8 1 0 0.800000011920929 0.800000011920929 0.800000011920929 0.899999976158142 0.899999976158142 0.899999976158142 -%AI9_Flatten: 1 -%AI12_CMSettings: 00.MS -%%EndComments - -endstream endobj 20 0 obj <>stream -%%BoundingBox: 43 -534 1957 -121 -%%HiResBoundingBox: 43.468601053878 -533.128335741215 1956.53139894612 -121.656243053342 -%AI7_Thumbnail: 128 28 8 -%%BeginData: 6693 Hex Bytes -%0000330000660000990000CC0033000033330033660033990033CC0033FF -%0066000066330066660066990066CC0066FF009900009933009966009999 -%0099CC0099FF00CC0000CC3300CC6600CC9900CCCC00CCFF00FF3300FF66 -%00FF9900FFCC3300003300333300663300993300CC3300FF333300333333 -%3333663333993333CC3333FF3366003366333366663366993366CC3366FF -%3399003399333399663399993399CC3399FF33CC0033CC3333CC6633CC99 -%33CCCC33CCFF33FF0033FF3333FF6633FF9933FFCC33FFFF660000660033 -%6600666600996600CC6600FF6633006633336633666633996633CC6633FF -%6666006666336666666666996666CC6666FF669900669933669966669999 -%6699CC6699FF66CC0066CC3366CC6666CC9966CCCC66CCFF66FF0066FF33 -%66FF6666FF9966FFCC66FFFF9900009900339900669900999900CC9900FF -%9933009933339933669933999933CC9933FF996600996633996666996699 -%9966CC9966FF9999009999339999669999999999CC9999FF99CC0099CC33 -%99CC6699CC9999CCCC99CCFF99FF0099FF3399FF6699FF9999FFCC99FFFF -%CC0000CC0033CC0066CC0099CC00CCCC00FFCC3300CC3333CC3366CC3399 -%CC33CCCC33FFCC6600CC6633CC6666CC6699CC66CCCC66FFCC9900CC9933 -%CC9966CC9999CC99CCCC99FFCCCC00CCCC33CCCC66CCCC99CCCCCCCCCCFF -%CCFF00CCFF33CCFF66CCFF99CCFFCCCCFFFFFF0033FF0066FF0099FF00CC -%FF3300FF3333FF3366FF3399FF33CCFF33FFFF6600FF6633FF6666FF6699 -%FF66CCFF66FFFF9900FF9933FF9966FF9999FF99CCFF99FFFFCC00FFCC33 -%FFCC66FFCC99FFCCCCFFCCFFFFFF33FFFF66FFFF99FFFFCC110000001100 -%000011111111220000002200000022222222440000004400000044444444 -%550000005500000055555555770000007700000077777777880000008800 -%000088888888AA000000AA000000AAAAAAAABB000000BB000000BBBBBBBB -%DD000000DD000000DDDDDDDDEE000000EE000000EEEEEEEE0000000000FF -%00FF0000FFFFFF0000FF00FFFFFF00FFFFFF -%524C45A82127F827F827F827F827F827F827F827F827F827F827F827F827 -%F827F827F82752FD0BFF7DA87DA8A8FD4EFFF827F827F827F827F827F827 -%F827F827F827F827F827F827F827F827F827F827F8F8A8FD07FFA852F821 -%F8F8F8272152A8FD4BFF27F827F827F827F827F827F827F827F827F827F8 -%27F827F827F827F827F827F827F8A8FD06FF7D27F827F827F800F827F827 -%A8FD05FFA85227A8FD19FF7D277DFD25FFF821F827F821F827F821F827F8 -%21F827F821F827F821F827F821F827F821F827F8F87DFD05FFA800F821F8 -%2727522727F827F852FD05FF7DF8F852FD19FFF8F8F8A8FD24FF27F827F8 -%27F827F821F827F800F827F800F827F800F827F800F827F800F827F8F827 -%FD06FF7DF82727A8FD05FF7DF827F8A8FD04FF7DF8F87DFD19FF27F827A8 -%FD24FFF827F827F827F82752525259525252595252525952525259525252 -%59FD0552A8FD06FF27F8F87DFD07FF52F8F852FD04FF7DF8F852FD19FFF8 -%F8F8FD25FF27F827F827F8277DFD1FFFCB27F827A8FD07FF52F8F852FD04 -%FF52F8F852FD0BFFA87EA8A8FD09FFA827F827A8FD1BFFFD05A8FD04FFF8 -%27F821F8F827FFFFC393BC93BC94BC93BC94BC93BC94BC93BC94BC93BC94 -%CAFD08FFF8F8F8FD08FFA827277DFF7D27F827F827F827F827F8A8FD04FF -%5227F8F8F827F8527DFD04FF52F827F827F827F827F87DFFFF7D2721A8FD -%07FF52F87DFD05FFA82727F821F82727A8FFFF27F827F827F852FFFF93FC -%8CB58CB08CB58CB08CB58CB08CB58CB08CB58CFCC3FD08FF27F827A8FD0C -%FF52F827F8270027F827F8F852FFFFFF27F8F827F827F827F8277DFFFFFF -%F827F827F827F827F821F8FFFF52F8F87DFD07FFF821F8FD04FFA8F821F8 -%27F827F827F8A8FFF827F827F82727FFFFCAC3C4C3CAC3C4C3CAC3C4C3CA -%C3C4C3CAC3C4C3CAC3FD09FF27F8F827A8FD0BFF52F8F827F827F827F827 -%F87DFFFF52F8F821F8272727F8F8F827A8FFA827F827F827F827F827F827 -%FFFF52F8F87DFD06FFA827F827A8FFFFFFF821F8F8275227F8F821F8FF27 -%F827F827F82752FD20FFA8F827F82752FD0BFFA8FF52F8F852A8FFA8FFFF -%FFA827F82752FD04FFA82727F87DFFFFA8FFA827F827A8FFA8FD04FF7DF8 -%F877FD07FFF82727FFFFFF7DF8F827A8FFFFFFA827F8F87DF821F827F821 -%F8F8F827F827F827F827F827F827F827F827F827F827F827F827F87DFD07 -%FF7DFD05F87DFD0BFF7DF8F852FD06FFA8F8F8F8FD06FF7DF8F852FD05FF -%F8F8F8FD07FF52F8F87DFD06FFA827F827A8FFFF52F8F852FD05FF52F8F8 -%5227F827F827F827F827F827F827F827F827F827F827F827F827F827F827 -%F827F827F8A8FD07FFA827F8F827F8277DFD09FF7DF8F87DFD06FFA852F8 -%7DFD06FF7D21F852FD05FF27F827A8FD06FF7DF8F87DFD07FFF82727FFFF -%FF5200F87DFD06FF27277DF827F827F827F827F827F827F827F827F827F8 -%27F827F827F827F827F827F827F8F87DFD09FF7D27F827F8F827A8FD07FF -%7DF8F852FD0FFFA852F8F827FD05FFF8F8F8CBFD06FF52F8F87DFD06FFA8 -%27F827A8FFFF7DF8F827FD07FFA8FF27F827F827F827F827F827F827F827 -%F827F827F827F827F827F827F827F827F827F8A8FD0BFF7DF821F8F8F87D -%FD06FF77F8F877FD0CFFA8A85227F827F852FD05FF27F827A8FD06FF77F8 -%F87DFD07FF002721FFFFFF7D27F827F8527DFD06FF27FD1AF827F827F821 -%F8217DFD0CFFA827FD04F87DFD05FF7DF8F852FD09FF7D7D2727F8F8F800 -%F8F827FD05FFF8F8F8A8FD06FF52F8F87DFD06FFA827F827A8FFFFFF52FD -%06F8527DFD04FF7D7D527D527D527D527D527D527D527D527D527D527D52 -%7D5227F827F827F827F8A8FD0EFFA827F827F8A8FD04FF7D21F87DFD08FF -%5227F827F827F827F827F852FD05FF27F827A8FD06FF7DF8217DFD07FF21 -%2727FD05FF7D27F827F827F82727FD1CFFA827F827F827F8277DFD10FF27 -%F8F827FD04FF7DF8F852FD07FF27F8F800F821277DA87DF8F827FD05FFF8 -%21F8A8FD06FF52F8F87DFD06FFA827F827A8FD06FFA85227FD05F8A8FFFF -%FF36363536363635363636353636363536363635363636FFFF5200F827F8 -%27F8A8FD10FFA827F827FD04FF7DF8F87DFD06FF7DF8F821277DA8FFFFFF -%7D00F852FD05FF27F827A8FD06FF7DF8F877FD07FFF82727FD0AFFA85227 -%F80052FFFFA8140D0D0D130D0D0D130D0D0D130D0D0D13FD050DFFFF52F8 -%21F827F8F87DFD04FFA827277DFD09FFF8F8F8FD04FF7DF8F852FD06FF52 -%F8F852FD06FF7DF8F827FD05FFF8F8F8A8FD06FF52F8F87DFD06FFA827F8 -%27A8FD0BFF7DF8F827FFFFFFA9AFA9AFA9AFA9AFA9AFA9AFA9AFA9AFA9AF -%A9AFA9AFFFFF4C27F827F827F8A8FD04FF7DF8F852FD08FFA827F827FD04 -%FF7DF8F87DFD06FF2727F8A8FD06FF5221F852FD05FF27F827A8FD06FF7D -%F8F852FD07FFF82727FFFFFF7D5252FD07FF2127F8FFFFA8FFFFFFCFFFFF -%FFCFFFFFFFCFFFFFFFCFFFFFFFCFFFFFFF5227F827F827F8F87DFD04FFA8 -%F8F8F8FD08FF7DF8F827FD04FFA8F8F8F8FD06FF27F8217DFD05FF7D27F8 -%F827FD05FF27F8F87DFD06FF52F8F827FD06FF52F8F827A8FFFF27F8F87D -%FD05FFA821F827A827272127272721272727212727272127272721272727 -%21272121F827F827F827F8A8FD05FF27F82727A8FD05FF7DF827F87DFD04 -%FFA827F80052FD05FF52F8F852FD04FF7D27F827F852FD05FF7DF827F8A8 -%FD05FFA8F827F87DFD04FF5227F82721FFFFFF5221F827A8FD04FF52F821 -%27FD05F821F8F8F821F8F8F821F8F8F821F8F8F821F8F8F827F821F827F8 -%21F8217DFD05FF7DFD04F82727522727F8F8F827A8FD05FF52F8F8F82752 -%52FFFF7DF821F8275252F8F8F821F8F827FD05FFA827F8F8F85252A8FFFF -%FF52F827F8274C52F8F8F827F827A8FFFFA8F827F82727522727F827F87D -%270027F827F827F827F827F827F827F827F827F827F827F827F827F827F8 -%27F827F8A8FD06FF7DF8F827F821F827F827F827A8FD07FF52F8F827F8F8 -%7DFFFF52F827F8F8F827F821F827F852FD06FFA827F827F8F8F8FD04FF52 -%F827F827F827F827F827F8FD04FF7DF827F827F821F827F852FFF827F827 -%F827F827F827F827F827F827F827F827F827F827F827F827F827F827F821 -%7DFD07FF7D4CF821F8F8F827F852A8FD09FF5227F8F8F87DFFFFFF52FD05 -%F82752A800F852FD07FF7D27F8F8F827A8FFFFFFA87DFD05F8275252F852 -%FD05FF7D27FD06F87DFFFF52F821F8F8F821F8F8F821F8F8F821F8F8F821 -%F8F8F821F8F8F821F8F8F821F8F821FD0BFF7DA87D7D7DFD0DFFA8A87DA8 -%A8FD05FF7DA87DA8A8FFFFFFA8FD0AFFA87DA2A8FD07FF7D7D7DA8FD0CFF -%A87D7DA8A8FD04FFA87DFD1F52A8FD5DFFFF -%%EndData - -endstream endobj 21 0 obj <>stream -S+}"p(J\0v\z]L*|W hWB)9Dq#ݓ5 5,e)(XRtdf;XuAfyaMrubn_WUUꮊmPBl^ӑ"72IEWX@iQC929R׌ H?vwn-#@/? >1'*tO|ijjZaEUp+wT3GהB!ֵHk\Fhd +_0M4*]k]@]kDbҸ*D.l=U,E+VUe&R8+`RX>#ͬj&/pv4 ke)0z${i?*Oѝ ^\Jt/a2 -f+gd&M`G@IEIIQj)U-6О.Z;) ]ռα]*B^%l'z _dDˑ0P(d,.:"/;!u8w-@6EK,gAZF/k0t9V N1&^'c!M<MkS5gS4m2)kJw=2DUrU]AmC@H uS/̀1Q1 4wZW[W\(dJSE!R;/|< ޡ/UEU麮K:^©[!.aX-SymL3p@S~eS F8J 3Ayo -틈#n}\ ;ݤTB{ڲ%gLeu7M]Tӻ6WsWYtWtjkyP_qnMdp@ijL鮮BԮRUWugJ:K@D-]ɠ2#bɢ]tSgqcW5וRv[ܚkEu.1Dg]H]lg*]$ɟ\N5: wtt, -a'0Yy]]jW€%te2wvE8 Lʂ1G(;=ݙk]iˮfg -K<[azMJ{Q+S#qsΜ 1B."WJu3|\km~W40:FȮ+lZ&fS{)}uzw܇ -G w3wnw5VG戆4XNWćU*  t(p-\>;~,YNW -0T˓s M9 sT"jMNw8tx7Htltov{{f6NC&ӵ 5 3=]]5m7;T _a[Y&ᯄ/%pG%`y򞘩6`߉ -3ht93x -fG>0+G,BnnH_ABHv(TLGmRӴDT -ã:n直06(%F=gHRsCBk],InH+pu3j+ijX5ˢ/ *nSDKf ڪ3(PLLn97 (;P&Y)| &$؜|8r5{I|>1WӍjNh#`^@`UbY.++41{&vy&_ClP6i=תd2gL'KO)\13Dj`@_kD60)0Uj `NW z`jCVhjfqBKl\/'QƖbkNI%Z[1QU)>#Rprmr"V8_Sdb+}:C;]9/}M.NLP04/^]v+d5PӐh`BՂ!gۘ!`x]>qݓv~Dh5ո -V㍰5jmhѹJ5~,b#F_ǤdPʍPR2[88ݸy5wg|U$نALs-F-f8[2y@c\qՕj\G^7QU%1WbTl:Gc;0^hH)_As,v6wznw.|N㺇7D.tRRV5B6R8c'bntR{(8 - 8ffbnBElO0E49ixyui\Kj;~NkgG -%0 Nv\wDy@>?yi-.uNpab 4˲s3v5ѝeQH#P}o :_IG),eHu}õZz3yx'Ho['璦dil+>[οp1N H:lRU,X7@rx`Aˊ0LjWȯ!M ܤ0%F̠t?--5۟b6A/DRa4x+Yf_V^վq=dLsd4ױ/b*_ KL6YLҊ۰ -)=!#To4xSU: ײ1SOc#M6U5sI16ʫŵس-,jppY+]\`|쭨2&"->`eV-Lh Θ.嗢-J:C`V -3>Yk uEzKƵerj*:x"]Ao]k>LQ?aðG1?@[Ѷ f]\`?*w!xނ04 w0c휏_:chByO+E߮/zV= (}uQ__cnﺄ^Xw}I _{{~.fvQo{vR|ͬ|o1 OHBD*5l ,ĤX;W::}LdaG0ܠ-5_WV/3:!.=uKӗ 3m7f_Ga~{`mLykئ=橴ҜhۏA;/n߰G5c%Q0US7pѸΏkV1.{^20â>2m2\oЋ]3# &]-&R5U|+)s"NY/1F|PqKq 1_d2\n\D C|Zxj]Yuf Mʑ㚬h=nTm'?6. *Ů ~ٍԀ*ܦu<Gўh:hS綷Zk'ow?ֻVnؠDS0on13,ZX<WK4;؃qQ nUI2X -oLJ -uC*m_C6ۣI`ԙ2%=[()>Pa)MYd2 ="͝]|[Ŵd:)?o9DKT4@ -ej񬙗 ߼%:`ujS6qb -L 3e&6[&&8yQUw3g^+d>vw5w?zMk?o}pz}zR5RM |" c ;QV5D -~/& hf%Ø $ʸ}QeS%b&"T"ͨAqw+I>*?n+Ve2 ҃mtƯ j0l,̩$8gpB 7s;&eƏa'YΦh<а" }~[SK&w4h@b(B_1!xVfB)PRYt`9kb1U xCL(:2<ۼ76og^(t^&m܍Jj_9k*L4K,t"m۔ym;^޳s&ژ]'nraZ"v؎!ZMO1i)uWxw0dK~5Cap,+XFJ,AtS@EǮ*'*b!t@5W g[MbMFFo-"P1;$pq"*fu~ `(:j0@}SLx&VޱOIQ 7E83&5Fe@? ֢0lcM촮.knt~ -"XTNȔE"* HX%AqAh3ڑP/a -( `FOCAMPī1 ,6g$GGS/"w`&:(QDA-_qӾ}iɘ A -53}-ľ6gNjym( Tt:K:!#ѩ_Q7v ,U-y?PKH1[Fo:DQ?#>{st@2 -@g㕴5+Z9ܫ[= -pqIՕJ  Qs;$uk}W HFR9kV²Zʦ(1SCW '&<4W`D0[mځ?ԞYӢtNH~#ÞPXAι"@֧RqJ,jUr#LH,_A&<,xr6Z†t6])fY0\LV%)ldI -!5a -\smZ%:RL̘pd\%)ID3CMC[ -HA'}n.Zƫӂ,aR" -%cz(H Q^W7&Hgn8R4=j8QθP3Z&`"ˊś`$<ga y9E\4FJEᔌR -d,Gd0k S IHh,Z]K|(*R_sCH 58Hђ_H;A/w0B}.X~N@Bs_U\j@-%3&WϕOlB̶Wkc技y k05|EBd`RZėf[QFȔO탟yC?JoVlRUb"i}j*su8Xp4b VnbWqm #vnF`UԾ1{ p+vȲ2 ٹ -(<5wAF=v\TqcQ4͟]uVo7RKe$w܂o[j*)EhjLRkC9ix \ukO8q{fv S<\8= DO.a㒃Ųu+ux(rGK = W\0tK cIJDz%-w k -TTPb=gWYlgy :hUx=t6<^ xr,mM;o..vspORkJw69iw1VI'UC-B[Ā,NHFvg^.0M*< ?-0Y2r%itȵJ%l~J~_q8l$?+M0J.D, -ӔB20ԸMs2MV#'`E_+A$त"i"zh I'Es{>~&z< mҟsOz,Ȅ_VE?ֻwwu Z0w0uYNΐ秧S]j?ԾHrZvo9mku V_B6T2=) 6fLL8Zjc\r"R)_8FF҃Dn*ŀNA5ېǣwl& } ӃUS*ڥN7aTH iRR&rq|"R=Xڝj.s.`w84=Pt9 B{\jQ=R1R ? .&u¥EJF+Tr P&yߛ(i\eX.s6 r" -O!lMnY;2l :ޅ95 7dT D/c;mg>f[C~b{IeDʗphV=c.pHҟ۪I}~Ҕ̴rTD&ᨀFij @I/S >1$&E*4-4Ʋ0\Exɴ0mMu^ud(j:״t5u`\A'H. -WC 72g(P$Ȝj/@0ˠcy `2kǦ}jrj< }1(0ykTCcF3-?V8|g,gZPĴEe~q q&e*NĺJ VW$m@ -]j>G -x/=DL8 Oe$Da8yP*DrrT -@RCUȚ\UXMʠWe咒yC:iYn="{6ٱW]P5c2/6 -l_UoVs^` וoE*µm{ @:4`!+ W1R]NVb+b%s:7̶h~/N{׭G4!jK Ld[wy[+B-{Tʃ~Q.;FݲeV *Sh4U)G82]6BPlM'wPЛg^b&TEZy__D¾]طXs1,$2T -aQaEOEE/{;R.Ȳ*^jHrڲ@{Z$kYr`8ܵ_sYfMu Q<]EUE)rE1@[\r@e - *??.l. HAZA67KmwN wޝ]ZɬN% RZOCoaG1jo筏OÐ q+ӂ2DHY,*Mx{Pm*`c @U&4j@Krm'T H6 y/ - KVBļ-04lAQ8R΀~ hk _#JV8R J=|=G=9媬r(-G:G4=hl`sD9v+Ohg"D3)(c"4)y'lA 3Ғ̥Lq 9"VZ> YR?>YƮDƒBYRP3-4na8jv qzlӡF.tp+ـ 9.XzЄu8`*4 qj?k7DMτ^dQ6y): *+{,VН iphDo3ךhԵdĵ&;yDQ$-?~㛀[r>3xX[%)V\89? ) %@oD_U*YGdS`;"o`^h?߅h y/P xP9({8"< j56BNM;@o S*?gPkp y:Zu ܹVDx8sA5@M6<2D*45yK k-4D6фj֧)nkAM{5(dYPڰ[eeZ)"񯡘u&(A!8D~/چxl_w˸7>$WQUH& Z5PJBK</֩Y70W'Ŕ]--" -е,B2\P"^k"7%^oW6<;:7zq?Fk .\lB Kh.d9Gz,amEV=4D.-ˠ.pU&eXǖMbkA{)BAS.ֺ!3-Ix75&|xLM!}J\V#]bA8>m.T:hiL.d>EDhy}B2)J i+AՒ(s$U\oi" 0,S*Wi2eMk IBwҟy͖7m -yLJA6U*#nhW~ފS)katW?'VtyUa7<$ccîrv]JB JG F" -Ԉ#L-#o3UB%#Z=\vu]@P<H$FydȹD#_&܄E!g - [IHFԖ2݌JR@#$VFj*2gsmDMN?9-Dx6ۏ 'ƨIk =GDhp֬H;x]k6eG`z#)9d Ċd=?$#HA -NA:fhE'qMEmLPM岛LZu/G.~a".'HT+cz5B"('lZh7_\ 颙<'q&h4gvE##$ɵ|}([2@cf7(nY*'n~˝RPRwF$\7U`P.|;3x2fss$fD%2^lnB.<8LtrJ(JM ILV&PhUj!U)Bk)(ea98 -UxT0VۨR5< ze)mi,.acom|_Y< L(ONJwwY5ΌQ Z]([dAJyd6#|-Z3 *Q\Q8kA. juF&FޕWNmfx槜[%J4\o& -*IRe^qiA֤Ј^,60?㭾Wr¦ޱ.z ElyєLk \'r~6"9qwo1vѺTUgkZP-4^e/vHН <¤gYdWѢA/q䘤-N)O OhpU*XJAΤHCoaQHAtɐ+ق ; @BA3JfT#MFBjEt _"[ b$;'/LF@C_9FdTjMD&I`*Il^w67m" -v /i ^Y2]7xI^vE^ݢ%zK[]7xIn&%&v1I[]/xIn+^Ү]/xIEKYoq7xf\7xI^v%/i ^Үݢ%zK[5%zK- -^Ү]/xIEKM/i(xI^𒶽ʂv!xI^2;%m{K]}Re%m{K(^JR4ƶVTm/i ^Ҷ%m{Kt'с/i ^]ŧ%m{K]m/xI^6ֶ7/i ^Ҷ%K[]/xI^v/i ^nQn3;%zKaUBEV]j~U" m*bم:zZ%]dc`$ͭ[%4JdU"Dو!v06$K@ n%uDrF*X%(Jd\ʬ[%z*َUBU"rX\*UЎU/kno`/J˭ٚVTؔY%x*SY%x-UW[%ID*IJ$g DV=Uo63a\*] [%E!ۚԷJfV >**r\E*E/JT.s*<ŮU"rؐ`W -[%-l{Z%H׵J$ǫ[v*?ٸ[%0JTzHFq`JkKI/ ON"yHUO }PZ7 ͂TbHukPI!dJ e5RnZ(( -bQeQL4Q@ƪyL0Q y:F^, H?xͬtpo]&HKzjt4݂ZE.~*̀$&dr.UJ@35>Po"X=4D|r(eRE)MRW@o LN;3L4Q8Lu& -@`$-N -1P8N KWh PSJwWUpl}20"z҆U -KLZE} `"tjH@Q׷BQ+\ h %@F5DC_@(J!d4r{#p Z|r Ӄ,7P`L?LaH<75au qYyaQ]biF^wJv %*MZ(iU'iO&-MqЂҐI2gjbwią!&xr%@_SU5fMhXGB^kBi!"mBϥeQDą -w^RLSZZ2iؾd -USR6* -O*M'ϰT6.BKE!U: -J硥*6h`4pHҹ(AgY*9T'{a`;IBxTpR,{zriAҤhPk[4I__EvTiv'0d R$uN/F%ntFn,k"XL #v܀2"v'bίYnq3mJJY=yF7Idi ʂ*#4F*W|щpaNk14(Ξ2O$8![늶B('"5=?'Vn]8{ >WGҤ..b&$zGѮBRN.[‰&Nd~}yr[O/{pdYS Gd.Lplq (+Zh?bo -3'J= 3H9qJCj1UspFXٽRv32QHYSfZȒ0-,о|!Of"/ܑd&Xrf.V Q:9em^r0Bㅬ[K{zjh 20z%NA9MEg-"tf`L JFR%D]T [/<=㸌$\^kI+uQ}-T0 >*BDRmV -j% l{kw9G@xjVU 5ԙxphF2.zpOH0fӺD8,X'-!"fxSUT`Ȋɑ:/2IшAGHם2u{sT< 0)%[md -VHV;lE)NSHpӁ&B ࢉQEc6Ő]@;XI 8uSF:ԍnjrEᔭuܾrUCm\w yZLQLRQW2&FzHg34ٶ]hw -Q{^U#{-05."'DIC^/2V"JIu%ђ"8pzR웤0@Ue6<>* -jAɧF l(ly=(j,f/#dee^ԲG˅a"(ۃmÅD*?`F.Ds4?ˏ D"{v УakE2}3kl#Ƙ2lkewX&thA lU։jKAFIwU7MC5AnOuG|âPa >EcC*?"K$;[=FtT ӬS^Cz@]rz!G/wK^f6Ixpិg-9jä|b`@Ύ Y/ҡ:HM c۔ SJ>J='N41G*p:"(rz2+o`5opCSkf4e &;n5N9@¶'NBymmݍ&_y^NwlDjTʌ\)R Ʉ^MKMzPH~$K]{R V*9BTQ|W.~ݾΖ_ê})⟂=z#Um7N2?^? D -Cw1?"9jቢt0, t^U!)fԂxH1;"Oql4~ f FnXAtgh -}EA8)mp|M(`h`ko15>ʗ[vNE{N]Ѭ"a(YQ#dAIpf#^3|`~B Mcnxqg6޽p+wYtz8 -IʄwGGmn9IW\8so6>_ЅgMـ?b3v Wv~`T|m17aֺ yV0Χ٪Q?``xl33K#ŨҁXZYұ+8H -a\5˵rDB y*P)6,%;e)hZWLx\.#WM!ylhƏ2"R2sѺPGU<9Toږ(#No5*0jШRZk 1I>'qXDU"V=@ W(8W|f(%rIKnJ.+'s?O{ȃ6&n%c~4ҙb5C\ Aȱ5!u(8΋Vs6*D J$!Qr^ %%$)Y+0 J -Y>H>j)jV"!1q `5ttFRȚd匼:(t!Ԩ7 bB<%A T@*P{rJGUZJ1ySCM +TmD<7؝@$ܐK -}Zx&=I2k上D,_[[/g\axNr -x%J(&!GoPcU#a^\G8V(3>$66q>1u'!t=T.jՎӃ0Ѩ\$NeF υ% b$f!+D'\?b}%D+{ - 9 Y+i$pLXB8Tb> :V+6ebQ -i8GpRBcHPyC3GBCwùZȬE #wuP$ߡ,MG] Vzpj:-%7 \!-䊏N- x YB^WiYcף:x#E@..[d+n#zD`,n)RK_s ͠=RGW^EYqZ=O@țZ0A2T_GhUC9zXJ~K~Z|/a pozH p낓^,15\۴JwY0eʂ'Ʈ9 u%Yh(]R]]dł/K5mhud?R7HHCr=D'$W2t`H킎QD+Smjij u44+ -V+:gR JIb x6 ?\L\m|%gHI qTd,;NӖ~E!0xe+T}`L1\}ME1o*`V8<9R_ވ+p F8$lZS+x'ɖ1|CXn|p׼P2V6ȳNEϖJ"Cz_iZ)y}9iyHn:9L!OQV98ΩYBlNAE6A A ->wΒ DP>0# 6n7W+rs.YbX&%4ٚk^7PPRo(ƃUYCU) -"VUAu[RuO@Y!RHF -QIJ0D/ -m'|${g)@69ɀ.uƼpx[%-p's+3z p/u!U̽' D+#Bl#%ІS)ͫH|8AkV"~d,>|eiZTR# +a|ӊu]/HkX^ְ]dF^6/ps~{ -QبirF^aˡsJ찾vJEFԓ^܁%lP -ʥ0Rs&B|G C ܈*ޑ=e*Ö6R2#RD B0r9Cl| GٲV{Kh7tt5/S~~A`*T%ɤ4.BVsNh2_;+PhX4ۄb)E31R}HMaPј -Vy,ҨDL JS{SF -OçN:O]MY%ĵ$dYh%I^ހ?KFL_cR PxeDzF YXI|\/JV` 8iU265(X7F75'1 {vJSpv,+ sBh Dn]ҍb!)"F ^ -(~:޽ÉMYFIAt^0'سONV|q$N=G'tXY6Zܝ:8*q@w5SFRcYhm2|TlRflY,.i{{͂f1twZ4\HLuk$.;.ēBQڈAK)b$/ 2Rw|Y`$JIn`"sYglޟ<>#aq4Tgi#DTB[RjyBD٧b7%, -UQ_DJrn[z[βb@r k *^xVLSH_&II5[i䊅< -ɔ9[Y 잱P>x8A:Ty+ 2xph3Y/˓x;j]90%sU}<1T!G 3ګ՜UNٮZCD̓սlmf 2p! 2bᝄ25@pEc x22³ /4ЊSIĺXGV@KF݅ -v;8} ]e nFL)cJd>g''L oqSp+K#vH$w1XHWRxhI#w\"pޞEB^728S`C֗(.t:b~!q!ՃMR&afiId~BF0/mdQY#i,2 - to<}pRס# } 3$"ǢxX'hᩑkD"b_MDUq@Cu6^PP -++qA8fgfſtAAR蕊b"縸qJ^*0( #8x]R>(7$H0 30W 58ذ d୥##5 Xԏ\Ό\9'A HZdq37l!FAW\ܕ}YJ.8r"xBi(' a 3FȥbW=xRI`5R!Gqqq&il0p<a 942),.| -6TQK$!|ZJ5X9,IG9++'M4DU -P(q6X( kʹJ -$+ֈfŶ.b]V>Uv/kbL,-Z`+ !PViT0#J"l ♜^s#R SڀKZ,!d7i`t%G1 - Klubڊ)σ7Mhfog|JjHa!P$01H {1-X cCCGu,URR࢈H9}ş΀ЄAw$wUAb⼥*o (f_ kZYBj Xk,/+ ,}EM8ITPaU 8ʇK@*{;Z̔tb|/;p4؟vXz|T [X퉪~BU򎆸*l<zwӃ`N^ZpO}zoch +`uv|rpx0|1 vGgq ]N&ov ˃'+4 Ofp(}~N@:6>N`c2ѷq~>yQlTNK5Nj}UlIy#ܢFh`c aX+,?єŀi"3eDϠ}:8DPmQK${|cx.iqa9rW G h~zveOԠ}O,rN_GdqDυm8 3"D^J`)y3gIׁ/;Z v98J?d^+wON"p %;5A>"̜M$2: vxzx~[N c;;eFnWTp/<9q=r'1S{s=ܚ:_WL> ^X9)]ypWVHi~e  Q` M|QU2t+Gc!l͇;~S'ãN:x~I0\ ښϦFe64y4T;Οv$q_؁OGU 8sjmQ׍kbeTv}B7W_U#!i|ZKTb h.$ `iڀW<ȷOȅ[hoh4K9 ?[i<}En}1 -~!r0p{ -5:γph %Nq@"dzD|H:r,Nw'L#dDo9on! ϥ57[rԶW7`*ۚv4 iwio{Y!OVGjw<")/54ʯWWsySPԯ[PxÿdM{c~}on29dbQt*N}+3QOhBRvs-á8o# ҶR1 J g)Y,)͇, RXjoe…˷B[:?q \Lt'J)lqURy[WG?-{Mu>N+R^N_hP -y*/+tSAp/l7_GRz'k!> Ov#ОVOī𭟵'-Oޒn ?_t~82 -M{G[YY1 ?ϼg½@GvB3L?䣩<缜n=>mn=X. wf;bQDpa`Ý/h6;'lW/D_JEI 1.E!@U!KR Do vmhʚ>O qb$ Ld.J+١293V3Hho: fGcͼ7SC~|",v}ղn$ocVw{:Q?gņ'2ބ5Og7G5"$i|3>y1DSQdF|t>Cyt"=Wvz~8>؜`v; {0>ŁyCUgXFUSTxOn[>-J_'f嶿۪(ߠP ۷tѷxO'n_ixk{VUZ!I::XDUb7[Fi[hBP|{Y5yKҡ6zW!C%A /sH[A?]1/Flu#ov1^"k6n.i]q:QCҕjlWC'Z~Y4tJSrvF)m &ʛ&%OD|񖚾(%[vʷ|Hpwz<{+=SE_PO$I vG?킫(WUI:0MU"!PE(y-ٮ=yN: ( ӿU1ITkhv4Ѓÿ)b/Y" 7>Y#&fgELc8'Xz~NNĥ4ue˯ ><̿Mixpy $>vSO>MOzERdk6}[\k5eՔvNJ۲*V jBk*md=N|{?_;/Qk~IK$N(uDh. 3DU)\^ѬlYƸpM*ՂЗFaW!+\1zBlڇ -5Ū{s@8<Sؗ7bp~l*"( >VUYX#Ft &W t4P(iˉNnD1Q$K$̤]f1XcPQ1Nt"R-5ѓM)3 8Gͅ0K+%Bj:>Q#Q!H}O('D޺‚~cf AkM(Ztl!um0ba;j#~CʴW` D*.jjkd[Q7%>g ?WE 6(:ע@"I$kҕDN,a\0tO³.˘-L5rHcH Qsc/$iDBۈcPT7t*!=_ZQ+_HOj5)5@R3CѢ/ĺXJ$2pll$4F.U-X_5oe@Y*uھ٬ Ɯ OM Ų] `/J/L'?'G& ]TfDh[iY*-LsRhFH[Wԝc&Э]jFAiLvpbp5)|:!;j99TyZ. 9!b(kˎ~%,vɺp>uډ-`.1]Dp.EW!! Sz-đwD"#⧗rYԇEכfSy>Fk&ݮh@e0P6@"%H#5DU"{wcWR\$LXplQ3 lQLW.H6E^T=Qf#{(f+`S MA Tq5 V 1b2L݀0WOfƷvZ0Ed6ҧxU<Ż"m\~iL~";*]40 8S"xâ,EX-6k -U%F,AjP3ɤ~!YOvnJ$d"Xx/u`Db& !_z9`k -ӓSS(G.΄t/JA,MԈ̡:1A9H"+Y߄F8;>*L'd"itgYGfÃ'o;Yg]z9:Ju|8qkloeS#V$̓0|l+R7_Fm60H5҄3=z5Dv?"$ac%kd q=S +ŽdS!}ұL -"Ֆ!| - FxB ty"&kr~1+>K~).\3=sLISE\11-Y̌Xi[5̚$Pj ɐ0O Qar5XmCsvN:uG[02jQItXV[B̔K :eI K!gzvHN̥"6@4DH}MBP)6!uN`$r1gJgJilʼntf9P7P e}|OׄR_u3ILL$҅W՚b`ALZlmuOE{^w0|LtքaYaq -QaC4.4(9&ROQv$nj0a"o9*CNOw=\][:x9OwPhuI lh -VZwL"LhnU#FiD-+a]3J]#R2-hGk-!j`ʐ&$PdHvu0ԑh{j`# v$:%R z#2NjApe]䧘բ#jHiu v7ؕ^CaAqJE6!*?}s+-&"lH!ADjN©lq1ⷕ0>ga,γY/?DwV>Cpޏ窐?n=gӝMugN*g#5^wv'y9FU(=?_Oѵd!b|c脆Ho`t]Lw^vϵM7byI85ʫ9B&RMQob<ܞu3sү~;QھuͧoKQqtϏǽB97k>|?U US˨ǻ\-*N:청45̤g.s34}#!6:ҕ\ZoY - -U}~9zCVߍ$٘s-#t05ݚvFR17bpı?a2̻ަr.KZ3(XFgI!-lz2 "{'+O酯v'ؿ.n4wx$n6&pr0Azmyh>Ւ'>yt|vwh`@2XLS=/6JN}>Zm{8mW#siNv(`x>{66] -`0N֯;t"/fӸIfp8DxxG 89ίxxå-=+3F5xoxxg6nI#'Ig6M{ftuLɓ( m:=O +jpF0hs} T3N6kto<9۬ $L\5:>8Jkp6Nhz;1GC)]'pbt:0_]m7Wit$"WJtۭ&5͉A˄oE,wڣ7x5xD$]0ٟ.l.r|ĽÝ -j}赮Oʳg$̇;"{bQ0queOhi΍'*5|2A݌f$'ImL -eOaБʜ)?Y۔(F9I I|< IdD .E)8L_E8$(I/bC0xCǭPm"Ӄ\ dw{&Z%^pt93{ToX}Iw?T%Ai"|O`k3'=uq1o5ƻ_6Ƴhgz(\7۸]xv.Tr•v[NB%.i㎠4b,Z'р`~!@Vj>Őߓ9 z4fu% >-Sy@zTԵ*ф6 5JHJ7(էR *K nonsv X\ilE#1efYjVQN1{\yL}aj#ٶ2ViˆS6_Q`]l>"ٛ%zB볶K<~I7,owŽIsnﱻggWZ;tw -_Zކ:[APNl^{`:yG#,"xӎvLjr[loģdyW;Ab>Gu{%dѡ|ӎ VzSԋ[5'6˜'6'BCNg5ڙMjsHx|7=,3y0mgGi.$ljsHOL:g7Ngf̗w'gPDit8ۛJx;R贎hlq1h`b j9!3zKNãltnhͯݏuU/˹遧٬>Eâe&Eg6M'Ө -x}'Fqi-vOőw `ytu$3۝pポLy;"̆?(V5;S)OŲ -o->mίʾo.~g$8sF2SFDS8i9zJh?H,5-'?<;]ݷw[͈ώF쌦GƇ$&:eXܐDt%ߛn%9#h91_NdbP\'Uùr 3x$JTHZfz3:zϕqwk>!_Ǐ>o|xm{GKfw_^}qνw._pm<\>zWo}vC|xڸ+k-_\.޻un]|xt达BܺYhuon];`KZxj՝߮گ.Oݚt|xcݾ?a{ {{jgfGM]W޺zi0_?t䋫{/k/T#x~.Ѥ־{ݔ~wY]xoekc¥;񅵗+.~zK>lcmtg}ymgg -?/{RVŕa5wvW'7+> xڽ)=·nW;+Ow[-?{|7Wܙlp6@/ߥv]ͰWmodyI8/{[wyKK7߽zan?W/mܚ^~f>c>ڍg%G/Nﺻ/=Ur芆vZ-;X:YnOo]{:{nvN3p_)hڏV?iF>8|Gr1ڼq-Dv$nǃ;+)ƹKo_^C5T@NqʣVnݫ u{wcmwK ꤼyy9pO{3 1z~#uS]|ڟoty~?Ms'6>L:߽Ikӝ~rHҍ{_ h;+[[2qaC:r[ϞnܮA\T&O/;~ e22> ZyÕ'Ïv)VO^~r-5'=4x~i?>U X“y !|0; (2Γ|;ۗܠ|9]/޻tFi+[oǏww,m\._y5/>:OzurXy}^ԪxlJ'jVw[_|5ug-t0](FW?V/}a7i#i0 - -_[V/mTni~m4 -DZVӶ#}܊ӿM0MOVd2X=}tw ^4*6 ]n]_y!)1;ȃM-\OWӞS܇9 s[ ^W+wN";XdS\1m!z7A\W/]9qnZѮ-Εa64nռ鬵_;i/`7lc3V\kql^rd" :P{;mg7zgGA\kU>[#rY9Xy> ;czEyswL$_x>ѭk;n?ۖKdDRQi; 4j+_J7ՐBsd‹_nU׾zb^w|IX}a齢tyϮG|`볕ۗ\uqy0O.Ꙍp?{R}sG/`}ݕ? =ͼL\%VrT-OW-N+XzϾ_{|ue5Է\a|}tDäRX(_>M1tH, ݐxztھuv\;ag/ib[R]SiIxbӧyM.tmyBo\ܜ\Xw_K7~$>H/yKq67{Ovn]}W[>JR*Hp]kB ! A8{df2s_ȬgD@w' "t<]>s:cJ~6}C\)wg~U#–؇%r"zqZ%w9mW*VM|~h'X k)s~ {x JD%މy±_ lif^tgiRH$/Ny3ߎ]4\֓RQ Oěi|iF?Z+F4xSX˵#Q7?/pI;| 3zp 7-e W@^5CY_ܬh*i+$]orʽ'd5aR_SG"zuE6MlpD7Nx2ɛjwA8uĸxO_(t)>ߔS-;YaB_gRxMf -l#Àn+ys6y): AI/RA7@{8ڈ'n~snW%v+c9y&nu'na!YfNnTW4deyvІvUm3y*:RFf/0M/`?4y4V0mȂBHf9a/OS` -A1Io 4J}~$s0ZT#Oɯ7јw}Ud4ܑ/Af>3Z|6Ň,lu-bZ:4)^YqͼNqr>9͵‘a>Z7ؑ_({~8PNx -s{I@+vcߝ#s~NZ妩bu1"FWn?*`} -%/Wj6&?tlk!U5ۖ,~Pf7,'ыtM_F<;#,p^]4I -~%CazM򄃩@+KէB$&IxO(I^DRLmu̮2,AT8ڑ|9̳ɾD3o9kPu =_-y3(=___kX#SR6LQ[{%i@2ݻ ]oOa. yOrNqt]B}"sZTkD_"@`u}}̜Qv j!#_DBJRT1bZ}y4dq^j~>yR>O-U.xvkQjU(_)` ]^O~L4?ΕD:%VkЍ/rQ𽰛u3gk>w/$-ᖍ;ܸmTNV|M)q -Mմ H${^8o8qܬb4ߚ,*_ȼ7` ďX󊖚͹2)L(}PRTRnG0bԃ&Ю3`2|;un=b[ eA{2x)Mw[4r&^g,I 3Jw[}|[l-wzŕJ ӭZWIMikfq{NԦȲce7 o7vF{pV&y8M҉{?js;c|f>Iw/ojwo,9 <?Nsԧw:œ:dzzx+dHU߲uf7x]1> [# pCxrE6V%y[i\V V*Lɬ:.2nHʛmLv~`f*";CďE!qIǼ}4e`׏^Xz"-3<~FSy̒q11F6ީKtO"BƧ ż%F&6s}VjheBu P(=꼉CRɱuXmTI mTM/Q}]ӻ`$"PٿƓy^*F©g“t5VA8ѿ<MϲЎT)T#2rNS g;-MM"z(x7_=e(qṔyw Cu~&|8?9J@Hԛ>٧~=&_S|ǯWjs>I;?IW;ů`{ |c+M%<W#LUȄoBcb\7ů5>~!žwG䫺& -k_^gUڑ;LasHrٔx{ٮ+@)"P#䈏v tRl]C~37F5_w,09l4nauP58@<ğ3XƎ(nUoҤ+q{ 3T&yw'Of X4G)Տ^!a6͝Wxŕh {Qb0D -dKKsM7?;H{΢Rū:MTsԦ.UvE꺛* w5>tPw`obzITz -A[P/wC+Q΄mRp}&Aࠊ9L]8)}98E10C9Kqyt<Gbuo*\w/Gpz7_?ڤ^u4h1p+T_|;Mǰ,c1D2r9|:Cd\)wΓWgޙ N3r*Oqܤߝcw帊<+ʿO@վ]c;Rt -\2 -u]zՉ:\xN#x(GQ`y?ģSh^:vC7/92N|$1]wo= !߭= yBȝ7ÞGO|{WO1~x*6Rq䆞) *w^QOy#՟y Kޯ-z'sϓ="@'J1_5򿙱ҷuUq,2̯?^݃1ݯΣ|O퇀 8.8 fw@1> -|^q#0Aoۙ -e$4xfǹl4\ېb %C TEyMp pݯp}Äpt -wkWqƝDB77݈"iN}t"Vz'4Wsgen5'?00sTba.}{g $2S(٣ߏ$˞zllbӭ{Y0$pLyj\YJqMŕ>Ko2︽\g>2I͟5k̏9XAB>>j+zߠ_T\KE*b_/Wi%#/J[wR=/_P\\'P>Ox>v|)'>UUyYIC:~<\@I0h\G9+6zG4ο|:;dvD*I$%ΓxwZ~<|$/kyOyN)z:ңT+w4×~.dKte29Kvn.Ǚp8Ӽ}OfOlȦd/[{w##JׯWqNBHn勾K&_>Rj탽LS2)f.oc*|;ʴh1 ^f2qi=iMZ W~ oLĹO.G7ߗJUnuV)c*W'r{U}vk纳z8+ڨzܯ S~Tano{q;zeB*={_<7O.iֺ+S{)~_ FY_yιK{>S飏0֤aIk_FuT+!kpm~{<)Z玫}}\LMܭݑ{,_N2f΋||~G/_?^>J߼r׫ۻk5q,W|ozˑW4}׵e O\<?oӾz3I?vNWcw߱I~<^0?t“Ywc'Cr˕p`_)&;Ņ{DYR~;zUVk&=w(W -BX]vpܐzK_G q~|ShԼj:8۫ӻD0xO;g5k}8.[c?1 -W_rưctk%U7f-<6ßdQig^یK2L;{M<8$n.BO,r/ȋc.^2J_pޜv2t#xNowNPăɍ`3OpW{zfP /X:r*f9Y9*u.{{< Mgse 'gGt>רNό?)Б^ KoX| OB} -WL^΄1-ӏ)קn -cɰ#k<wZ5Vw#3ii</hyrK\{K's'\IbyʽT~Fm!КrQO_6%ޗF-84'YoG>Ģ3Qɨ~W0^.߸spTGEtI7KQ\6F;<*WIQf;Qqo?;lǣZxBFfW1jg8o}_E:X¹Pוcb6jgrՍFaǹg#rd`d3QOޱGrȨVM#745pr|cVKFf7IȎܷr_fFs3QW<'rF_;>GUF{9!rTq`_Q}VFѥFҥΕɨE5E_}]UG=ܟÃnT \$QKy7ŊdR?d:=0ڻZ-OL7*\R.n`X'/ѮJ2'~y4悶{KJ-\ -߮@tes`[:{0R0oP_jӳ mF xWմiRWuѕl՛$Υ\aġ5wQOqt-=5{*,ފ^ 3of{|k3y3} >3z;C󟟆_koS -ϛq+1WcӷQp6}Kia=Z庈 zFHt{Pg!ǹYN.~fs}z:/i5gҩm;dJUQ)|ʗ'{̽,;ш5|v},0 -#9Xi9dhisrY+zZB|7EHzb4*0(c:*LFMrQ;GE;hWFSm>jK; ӎW\ZV3\Qw G}MQNxUO[&ɨ騸G%.&Flԛ3upȗuQQXE( .>fZ4&M4mKMZN-YxG.҅SwiN? IYAk|v* Ku),Zǣ`ZT`꟏*ȳ43*&S >bCKuM^V͞[~sY޺Err+?0C+ۈzy VzV{8wm#շ9Fjzl6y#6(Hz-V퇂tx ecU.]rX`iG+0\xa/FwQstS|<[Y9MqCTx 9N^_X,N%r&:f{8}̄tKZns37ү=ltY -cUv39Wz{K+g&j+Ƽ7v*j! -l .;&ȇ3$fVVpc0նc!Hn.4HнI-6.3ێ5]f}8]fYlClK~v…f:!#/yƟ+ -T\k}yb-ll2i oMcWY*Uy=^_+5if39g.=n`0!{hm4PRU #z9+И+и63t [oZ-*BoҜ݋!1uMy$N־jDkKsDv1-u79@8n:&z5^]iHܘmd$*dfbudtZf!ypqL2@|6h30ͺ{3# f.F4ChjsBW}#|N` -@ XfӜxY!W/f{yM4" q+Vdn χ$0Yz""cPjl-Μuιt:SVNL@8;rk\CA}mqwU4D/-!О9Z[cĉm8f(`רކڷC4{Lj)$VȺ=<'@jG0Qx-D7Qkxn(WdžHK^_\tt3<'R.icg]V~KiIzh -T[Z`D?65gN;j.ƻVBgԧQ7 BޚUtlnlxinHh 1hڑ>޸ڋj{&)R;9AzYˊ-dᬷtXUu :F:M)H"1ao~ϞhG -MY1huSKVs7xfݖz4Zs7wz-Q *MIRbP[]4\6o {YnRu;w^mԆn c`uӬl5.C-<]F(nFW1^%n`v#ؕ>j/bo]H5"㣹edscz{1mUa!ٲ7N8I,m -w]>³moyZdW0 n(/7'hh&qtJFyswv@8AaGVڿmk{ Gl;jf4L[Z8"O|d*5 -T{d!Y^6;VڹiL3FFK"al9n2͉|0;끶Rip.*${?iYhPubJ;ޏNC\emܖmNd3)4 -c88s^ړ3;凞&$74b+ӗfw`m]A `nNhl3g䁛Q =U&JQ봽K9u-NϺo,޽Ϻ{Ϻ`t|ֵ|0~kJXg]ˇs/|ֵ|륋+糮[MKءϺO}C9|uȼ'Ocg^us҉Ӛ)m]\a,-!Vvxk *8VoᶳuTfv,q[YK0{]pڔ奮FŊUdB3l[԰*:Ɣv6f2D,V@H~AH^k ӼhA %P -VКfެNOkNTӶ oLA5 xvղm(sQ 2?.Nc{~z8C"~=_7"Y{C -W7Fn;[$3j剾46^;ڐcGϹ -sڌ.3fxgK!^g:!m"j +ʸsJtU%~&Q[,;:=+5Vl;h/f2`ù{̛Ey20k[S2ʼm>*Q*u<4mʼfNoyf{307 2`s῭Pz4?׭z: -n_*1tr#:exusG;o#竒:b<}ucNl 6Y -M"F[?+:JO&6ufbt>{ڛY c4+mPt'*>Hql8~,Л;2ޮz`U$qlTX*WdoR${ktе(QIiWR KIthp5Vլ%VS0k*;i(;Xi۾T_f+Ǣ #35( --tIۤ.3&̮FfoTM,Jr-*td4~jP*IPܤ9[x=r༾hR%;بb\wp[]2W -'۵ɨY~=[Obˊnl6j(,Uь+sR_u}/ֺVO͋B_SxͶ -,ƼS}9==nvOajԒtTq-F.J4CՕ)ɧf/ߦT.0oښ~lRtwW 3[AOI:yOgMo2=X; nU52=d6s.µ>B'@#*6]E挮}\gkQ{^/fpޟvl8Qf;.ݬgTm/oOf}x -Ǥ xx-2ԋ\ca~gVgd%, Vi)3rU/`WgU {aQU -짰o w.}]1}UmȄKaQggau(kS UVoi{>UgTշعϨOe6{-3CnZطISaQW?uum V@[a\gTw`vayYgTէ{(쳨XgaQ SaߦtTgKKvo}vm {,3"wTطKVСW37^[9enFA?Ǵ*/ݤr?#5R-~f*[c:'m46kxxxnۣaN D-suNvZ[c5MZ9- L6aoSlsd:M&,69ЬF_7O4۞-/3rc 撖wȇ?|;Ju?[vom7;gs6m[k\:n[;78fq>-jVewvo-RkTl(;7Mn*IMq64u9}^=ɇ&Է]J|{V۾RvSkL:m;7gn´Z3I$>产MfG?NG~HllSio8]NվuAxWЛjlTm260[޼Y[WiMQ+uО0_wV1g7[wڼq7O}yXbncX'&і%)##PBO|b=j`Q`zɾI;W*<[Vշ\U]+UhSIӁ w+i:X^ hNgllm?]:jߎ!y}oJ7ގZs C{^_ftm\^ Xm]="hԞ`lRG6f~MT;RZkxTv7@kFy]#՟'`vnFEcۺ]:AWyݭ>OjͼĆfCiE] h2L#&𞾽\(GR\83eab??fH V`JI?gum :[XxBy/ēZ zN C[-h;mnwz"Q{3MJZ*$n=5yn,I[$qK?_KnG\$n="PhJT.I[fIoWhEKgY_ -o.+!?_Vjx.+4v%O^VHLV׽_Vhe+{Py?@wմ,ju:OCw϶]xh]q`Tnq..SL/C3Wo;\M;=֭c<n )2qм$Fbޅ2Vm\x!@^2/<\v}FZ+?j^.M -n݋$!-K~Jm+ MsBfc{8nـ] ULxȌ/ "~*rdA}M5e$p:\>L?:xo.^}yuPt%j5KŋEK-^,lyKbQ/:?/34stNѷO-w?bbwZ/մ Ev,zb̼? #_+r>ˬuQ.w}M:bf6{B|E|rG3RUG-6YدnzIߐ.CD1woSѢ@TRl6 yѢJOGE"yǽ5.R_S0$˃Yy?1(Б]x1 _&-3 /|0O/X䓟 zK"tq͹c>د8#;کQ&Vf9t۱66n.5=DQrB -U&nv ēڛp֤󋩢 0<ˋl=#(N=}*.W/VOc='A5# TbnVh i30s - #cIŮ6g; huE4Am/ԗ'djAS?eOW{^ɿ˰JD2g'zg<ͧeK6~6+UrE_xOP˕m L&#&Pca!z~5(yFMEha_~2WZ'!@tR֫4ʡpre:kSNձPYir1:DLLy_+ڿS3%)@O#W=Ll#[DT぀jq # W-xZ8pCukO Ni>9EeuMpG?X:ˆ׌\6@n T0@ d M| 8agmpPceI4>k}#QO&1iylD5#Z4 Ͽj~}@Ҟ>4#}0&4gg`yB:'>/F1L'& GUdHcG -,N?_M|7kd̓ހ]1M.J|x7to%1XA)\vŴU`hX[HK|;#Lh+ l?[N;gH0Q+?]Di!P6jƅ$pB GOµ$ "AVMbH*wڑgڑu2ϡ&R.=饐#?NjqUB\EFQ*FO_SB.z] 뻿Ih<%_Z)b%xcZL<оL;Ry8g!l~1y*`osDf[ѓzq!]x zmC3W5x͡z~2A:#_a ̼+U6=Y,2o07]F7:Ff zbz`2&Do l@A__/ŋ01 -[ҳr>$Kf=kmP23*x1P( i:B3轹f]HnB{N)Һ#c昋W'7?Y~~-e΋Tey 6,3%i`B8ۯ{joQ.Q{zI}OӾ]s0_ͽ>L_lPtкj⁺Yt|P娜wpm{*ͼ휙^W%/8oN;W;=/:QFo@\AP=C cڦԑ'gfE0qS``:18 -~'1<'Shr N}wqLfNL8Y"2~9)~9{ZX~,T7U~e}@9^,K 4E*8竄D15' a%oBdAzJ©NT$_" QX{{A)>y@w;KLWb~"W1vP1*b,"F^%-Ku -m -Z^ V^@.b1*d'6dV2W%O=`mӟ (2g88|/N9uu}̼Z䰜}X%XVy/w,Xf -NeßӡuVx9:g&9]꣹"Z0 Gwo#6`bC*w%3ucŊsMϦ{C9,_rL!I"T{fi>RsbT4_B%=b]c^(K \uk 2U"= 2YM݋|Q="+Ty8uX+[Qb? BNm=ӅU(&>+}܅eO`~ -/E`oVuQ(0`+Hؙmڸp".LN68&ڌ  <Qf%K߿p\|[A5!X38݂)).CRnE}I9[g;O {;q!S]~4;R~ԧ<@탕b y؅f767@SKG<;Xc@m!41.\4c5hm-zΘVzݒ{_V]1tuYƽB -M HmNDGwI>О!@Hݐx5\HGnL vZHO#F2f:vĽiCHe^ K!G &+ n3lNK_=VٝRگNWs]&ʂِz<:mD/T~qRY$ ^f)ȵ{Y,VR6άZt2ڋ8 5;Ci(F6Ii - ǂj5d_+*aU]bT`ŴZcn+X1V!fo+*,Vςjv -ڴ+ό%eˢ:˃ *; Ƿ*p 5zާ:L \z"utHi/Ɑaru)zwkRH~2}x|ZX3aM%E<5zYFFW[q/>c3/c '过QT+foC/̌nCsx#16ηM?w!{LW>dtXhITdES 7$^a%e|'/J DdF9Vbx^d9xЌ=4×xysLa^0ıbeNaw}IZyCe^Xh}XR[q r-HīGHV$FV$vayf/)qQ˜"CSNYo(RXNiHana^\"CIx @Et`naFx^5h b‚(3<#"47h˂ α#@VdX@ ]<<^aQU?LXf9 sa^b8_,9K2`Md3+lHh 7xg`FXV26,)"+F \6D -,HaF`9EQ.Uax&aM=a*9C4c`iNf.#eZ1+)2`ZI !""!+W(Y9ċva>@ipvKĹ -'=a(IC0U; ,t%k h}¬$:0cI@9ZppayP%, %zND&!b(@3` K! )!ED2!MP;ShZB,#P ځ@ V:F -.0,OUaD "PA] :D 1Mm8n(Xи]q *)Aڟh -Uu< m[п"Z05ص:4BT2Tؕ<`݀ 5DTÁg`r`bfUhw -+kg"-0I`0~YF%-P]TOi{ -P sGA]kdXXMh40PAML  - Qt 􉈨@$`ꀼ,YVwH1 xbS!O(#I2` 2ip{VX Qb ]X2b,m4 k  ڞMpOMg` dY0j _*[`2t=$SF$bV>"h†ZD#9̀*͖ GE`6A$P Mf`j i2`ڂxATX j@'V"HQ Sd "\Z‰0z@E`.]ᾠ0Z*h8#JD.4Af%&hy@3fi(p̀.jEŠ(sH`^J p-#U4k-TA S"6.¯pPA?v`,.6at_YUTxBG4'F&β -` 8~sl J!1& ^#oA3& F/ K<@ EA<h q#1 0DX/oԄLFPwQj[kE& l,"*h hN-}4Ɍ,dԈ|u;,]4X4֡O` 7QA BF#oTz2QABć -`;}7L -H ctQiPU~s@8߀Tw% - -"`a"r$"DUm]*itd6DFW'v4%A^14U#Cߨ:Oť CdDEɌ[8rɰqơa:#̖ApeWl*F 0X($"QDc\]Ӱ*' - A0$(nl 1coH&bЇ%6Pv+@@'XnˏGi#dETm#@%4R}P뭌\~35TU@]D>ш:4HHa]]~f&1Q+Ed4O~J&>VZaNrZɬ$<*chWDy2hzwźϠϠ՚USF''@A hXke3h1H(hc0F ChRYϏ0 =D鬀2Cus M -`vdBPҁ1 PW$8-$Jz ʨv*2 RjsF6v: 8#i Bp,$L1TةZ"JI3>aJK[`0&: @UE= [Ţu V& o#E<:L9d -֛0$H0i nj!7c,&2F y Wk#3`/Ph#!Œ ,0as/kgbGn} n "IUJLVdH&8cI~MK -톘0}ܪ#|hrYnD[=&CB 5 f[XW\({HENڛXz,0;@mkh$̨eU,Tϸ۸+I(U͖a[|ǔ׉DZU1VUX!la^b[Fo=,#g}Dป{ ޏ.N+G5k*]-T59e%|ۄ5 -Z1V+*UM$RDC˳"_Kܳ-|X 靈ٚhp#^Td=; geǣbKK#At F\Dj"waWg7Q\̘6T,~a^d+w3o;FTҡʮóòIAaP~";RmIAςb2X,dLFȤqOVҒt80,EgF-CS7 B!$x"ZiVphѱr>X?A(^w+믱+Yn$#:rTNZ uҺh8 F3PW/HbYMd'.).NWEXz828C!6V9wY,Ab[~ qXD1Uo?,͔&"l8P.}Fa/ ͯ- 6l u6Q@gFy f[ 2j2XU:;VƯY M$س0 l<{l!|gR|nsث.,xWT1٬pBĩhݢm}J"6 *,9:菼z]U~lXF -h)WvM4(G އ],B~KR{ Xc,-2IWs ἓ&g:h,)͢d<xF 7ڂ'&1GևŽR" LB} &LPU{(ޔ9vp,԰NnQ9=gUo Gv:~\3-=]D{8>G 0G9t~LR̺zD:>0v52Hs:t*yDlEqXB=&D}AGNM{8Da07Usa3mgZ<7ی惲ieU8ij!N&Kmiu ]Qyh x8 H2 -v"D.U $pt{.حp9,{lNW$KuڄjApDc?QۀT{UBfr~~SzG?8ĺCn[X1QŐzS&*OcԳFI!1m -c61{l3U -9 -oT=Jf尗VfELDDDv_IxgJGuT}AUk,c) b*E:LNRĘO&5F f*F(߬>stream -%AI12_CompressedDataxkǕ%]ùWf`n,wAKec(_xHwUȈ;c^O'?>w/_~7'N-W~>W^~)|{]NO~~[|_=@tp~럙IwJx/ϿlVՋW__=ubn=UZ}v^IMά{^XwFz;+-g7kkp=_7~}wz~;ݯo˗;|q ^S;edP/f|lIzC|<7۷oRxoXJz~͟y鬗Zi{wͷ/P2 -W(5݀wg,')Sa4_Wq.o~ZW_oՋxY>a6ۗcsDij;^o} *zBk~ -vu -z~Ƴ\-:7޾Wzyoeg &"17* fQ**hN9|{V+ G~W"w1pi ߼yIA؆s$y⫍[_\{, z?/?ˬܪ~'qA7o2yӯ=9pz I;aY$lԗnwoZ+v_g_˛W|䳟f_!c[|7~~ݛ_?OvŽkC?n |zyۗ-:=_>N_s%/} -"5Mw\P|Gū }\no'C:ϷoSm}:?}wy.¯5*OxA@OiѓMOIsXԕx5_9h/ bo}{/?;ŗ/owooěR7k=j=i{睛(N$<~C_~ x5͛篾̑OХKt_1qhsl'nz~<4ɜɝrZOt:Oy:>۳;弞x>2] ,5ܓflL+gߡSsNvàУzA. -5.W)|kDͺMMUmjOԯ֛8a=nVuZn ^Co\2L||wjl*zMK3E(@S}zG*9{=y7أ8R[g[|N;}L0gg˄y4QDrl߬ެݜoNM;Vts9fo4+tL\yV8B#mpiĖ^p+ =nȽnz "&Um~:LG8\f\Ɍ[*}ȌOe f=*3b24FŦcEq)2nXl D>b#ͲpF?Fpd*nR:)>쁰'B[Oh>֡hǦ{=Eʍ~Czl?2̌.<%xER#/s? ErfrXCqc -yv9auy+~bE|]eC̃,`&khCG@U $X9I,(?Hą @&ZG0-l[1--LmGn\ӸUlOIGoѽqƁ)5h/7>,XDg -ˆ~RLe'.&t fܲlцb.zYAm'AwMWm4g՜|24'DG'uKKYYa"jRe6yޯr^>0L@JT=:X#8?᠄dmgyR H\HC!u PF I{!|BK|L.wG2 /v|<۷ooi;%g -s&(5l 8b|dZ3EM8-~'<Rji僮I _ʼneo>KKkIgŇ}4-Іgs2uj(Ft%I!@N:@hB&qˊÉiBv{ TK4U/QyU/;9G?$V\/FA #F>(DI/7j=VtG [/]FDĢae 8W1qyoȩй(K4wjRzd -ވ_WC4_@ 0GG9Tt#ć8%^v{eZ4{p^ =5\b {TfT[pIk`$F_.;)VqH&'&IpZlt¹2n1.aۭvd뾈+,zJh9d>b M~ye[%Hg!@oqp&+sf Te@Eܛ^O{BwY7=ů{gsqOJ5:erl1;3cTT*Gu+Y(Z}-f}ݢKŒLi*<h+QZ󃭔Ou爔@ -*30OxUF8=[o KOr -rӼ$M8-D>&aɨzC'1#9xV1l^]_Ë?)j̰gjh-4:+cnim̻Ļ2!7F@t,2˶Vm)Z?%@l%KJ]EA劣]c -78j[܋[rpkY4/L8A-#FqD*JKaFzwmB? c $uF$.%%AN"=R' -2UJXŭ -سKA -ؒ[mϛa>8Tt,3JY ɵdBZÂNQ>dJYw3y1*%@䦛'8y{;vS'8tqqL(4%uo7'os55 -\4g?9uP ǞĂS,Nؠ"dN&xPF"8 4H֟Њ\D[gB:y4EQpyYMWSd q QeozP{1}=z)|Dݚ.ZS!'hk|j]wZS˃oX˨[kU˘XzkOoW|"^%d@3E=*AN;*gsSzML o|1D&~aO"hȃNĴG(,(hAKuMXi7Yzz,sѲE=\ѳ=g=;hRmeRx, HQz6GL5U+*mUhK$4Aih>M8 gښ!jgl ͒m9Krݖ֜rc`̦2(s2Ws\ͰV$9IVCumRR>n"F9}YlBp+Ȇ}+H2S Atg@\|8kز{b~/,`< KǠ+S]T|5256_CюU;-J-5D[ 5φ| "rqpm'6vUoxyn-e;N5l4sx[v\ƒqsCT4Dh&JJ&`*\<}@|ԙ$?͗,.%,'kʿB\sW -.6\%B"z,1U$wue pG!V\\![P,- n%pֽEXt LR>Z%}3[)hO@&Lti aedpOx#,$h}4^Y";Dύl4Piboy84_b;ws\\Ҧma9h3K81@W9\umzPքbRdڢrhKRhi+X K:9uѕɄ{vHxw>CR+l.$fJ4t7ͤ##ۈd bXEA] N>=Rњii+ZԣǍ6K~ЊFiЎW!5ၣoծd;؊V)Z Lړφvڒl47N-sf!Y4 )fKGSڰ#NlKM8EYIpD{'"&g>IGK; Y*}`E?8!RB|WIv ZDszRdgxp| -,rfj'Y s(k\dnxƇ?O.+!cBBi181?1rPGSE08yHsbK>dZ ULa't8;1|4 \6\njQ1s\e3`IcO9 FV̐^wxyqd3wf -$@lHSD"V56ҲW0 S" -$,YNE\"Ď%XTn砪L:^=XvmS:>X`U̼ra$Iõt n 8qhM3 ~UǗ~Zzo< 0ONU>r1,ݳGrX3}@&ǕS1X8Ey}ls >V>6tGĨU@zDZS:(=K *!DmAt[]VՁX3haQpZ#t8= `V YaE1{C?xxInbJ /"~;/]gxF = =s!ڻ"a𗓗(1>Kjk)=I㣨{ 4N0cFuVDe `PNjzݠ@R;!9.PI)92o!|۴ `x(|֢ՠ958~em֊XIJԽmcnz,17B.]ާI1{&5% UdLx -&h$H'MA8VU@w%>4ٔgoEW1άGsf !OBd~W(HЛ7F0{#1Nb(]D>2W1)Oe@ː.p$ݝBw'5F] h (Bwx@w@SdN.2 tʦTH (c)Sr-0%;ֶbȎS_ {c (>vhXrI9A^*2jE**-F\PhjSU臟UfһC ?qW؏i7pXYL]Rl0X'VtE jfA=ϡkr5s@twl'oؼJm:JoV(C/"M9"DViՂJ-bgleVF|>T3EŲg(9GeZEzR -Ew1 Pc2YRKO+] -uex+9x(ЌT4d_BO9Oj T9bF - SxѢ}]9LH8 U  83Hec"ծ `4^h^Ib%hn`ѹ(Ml[3M+۪hu9S.%SkL}i|.o4ȡ*;reGHYtA,7"XVjd<6lĨpDU:BVd+V$ɜѸHع9"DfKlZGslZSI} -2cHМ^LcQ 9gfBg^r4:BŮN{jӔ*})WGk8l֖JhHseq'9xMC:5:7&H)fUUYzxARqvR7s2MV8֭^uצ~΋]q&M )ż{Ϩj}77*+=}5 56,O]eRէSVy+𮣟Tm0W%]oĀeh'A@m*x=4՘μ宊wMzxA"L1AIУxbz>Eo'h_RO[ -r3ƐD<֮'e!k".-uM%Pvs/~G.+ x5ܥ bzCc k5bW Ń<7^p:+gsYlz7bu]{W*ܖ,A`-Զ-F}oo-) }2J^:auJN&UbSMh?!Y)(Ģ! 5]s ?wD?]XtYJ^jHS*χ{sUzrVc2sܹasl֊H!ڰD/h"!;kP*P ~Ka[dIᑪG)$Rt7fr s'}uP-0ցU!5'Aea%.C EeY5_잎>d]zp]azMgP*< Ϋꀼhw6axu]pWAugqU]"|¯]RŋGrk$5*GR\J]qH.a Tu|`$=vK2ؖ.BprէP+y6U'n'S߫}I8j (@ -?r] |޷HF(rtmUo8-g1s٘B$4$ĖQ@\ʏ5цMM$*"vBWQn¿D ĭPRn|H:"RDiL#ZokE輸6=Vʓ -yx$Y2"81Ǐ|' t1Rjsŗ2iHޤsC*k #wȅ0ȭI):| -%bw$5S[yJ͢90)*)}MDe[Èֶ~nU3Y+ܯ&`wIe&sMm:]Zռ`{|`m,ZM3c)Q"p;g8 n@~$|q O#kV{장29~!=-8.۬qO~&0V؟PBǍ~Wn*UTS %Oe@+W]-1c   >&@|L1c  >&@  >&@|L1c  ~ ݼ_?62 1c -ďэѴNx[B{Z+Eҫ}kgmskjɣ&2(Sb9EO9[@e 9FL5ʶFЧ*u4V11o*ء-z?e(0EUaԶ:)YH#%djBWVB|&TGAHTQ҅CEA}H4QЄT k o'-GtR+\aET Pʹ)Kqe[TYR-N{!~u35[vO9kN ~?Tk_ok=yݝ(|azD59dV&K^cxsaYx˰csVQ'7uֈ(/A&ir 1D }A u%lC@,95j{gV9:#o -Ǘ"{kKkTFdt ]/b-R״%˧T*?T'u2Fe5n /,IKfXaT򺶎A+CZgxq3N?|OyE@$JV~Bw&F͡p) -;!;+!F!5DF:RֻNZ9nj+cx|DWAźZ+-umV*kv^\WF˜qag?nX)Sh?zwKUN=)뵺9.Y7JJ|UXՇw*vT_W_nS -Mds)P jN{|TF n!nu'*BǤƽB߮2Pu)_S7|KRޑWcO|4ܫ~E?=}ܡ7e`y'5վ'ԕuªc>7K(pX*7 ͘n;{_ OVeO'he:FVmw>!},sڏmȃx_: -4 ->R5H(1J.cU_,c}C޻x={| -eP™ڔoU. r&Vo jL6թiR6PjfRւlsM-mRE.l_BݗY !AY, -b+Z-BeY%ley.ڥl)IBHI \i.50m~p~yHa*M|=V;8dJϽµZ4p~4o6!{Eڌ'1:p`pbvD!$u.RzV&V A(OjvrXJYeG:98EyrjN+㔵pB0SP7ЧTp+)xؤsV(+u^8lZ M= ҴpNzjˠ]tضzE5`0>1\.2nn׊%ZlP-l˶JʷuecWmPMz늼aq;jc=c\3nuة -,nC[#؅܁Z2_d1"z=f>ThC,ʣ+&VlGΖ-_@c4_W_u<`l*fdј(2#4Sfh16GnZPv~CNܺJULTz'_:&[0wmJAۧci*S2f;1NWjvy(1"F+isj0tj[!w0 -pc|jO_c1io_F=l=,K['f91mi|A*e9}({[==oS3b69ѱ!&)L0{=0{xNfTȋv(N䔌M`0z`}xʤדQJQZܪZd YJlU9wY硼bx?aĨ"| -֐?#WX+x_?G};2ooyw|?U*soqnAiT-1t)#XHܲn߰rkMnAegE\s[_lp ׫1ب|_uY1? -ǨAԢ/wв>T[ͽޯpU%@]ĜEMLR=Ht"fsI 0}z긭A= A= 妣ʿӚiZѺYZӰe -nI*FL:c݉B[,ң7 OG}\*wV@V9xJ]ֹ]iz]5V}[Kv%?x{1en -#>R^I8'ΣxD15tAL &N#0q`j(LɒV⹴F>|#y<)E74 J**5NHՃ`T%>@:*IF%Ĩte2o=--Ej12%Oժ -烮*٣yUCêEA,pzZEtZ^P)=?3Rx<>&ɠ nEƒ|P\`UH1 Q߫j(>yׯ>~TOo|o;۷o^AsyoLggIvԺ7{!@ǶwfY${__o/*~Ƨl~5z"wV P'ele~9|ߎŗo_~v?ҟ_~q]zg_kw/^>og&좭~\l 4)j%]n2䪊 w%iSLf~?{3yݩu?0 t\;0<\ܸDnSŕzVSz%D3)tq -&(BʅGh&{&#ƻ :Xũu%~?-v[sF˸-+ܾx|0yQC?FrI $ N -}d֝4_U$+Mbf^7O7vԲ_@X.Qb,vo c, - %'G<̟W̝G;cqy1x9 b1Pn՝)O8!HRظJz L \_ЧjmG%_!O&r *=,eCCr{ -nG%(pѫ%S -%sgA# -] tw/.?M,\ 1Bd+Fk\ScasNŠ%(|S$|_/ |;?4첢.||!ywoDnyќ8Z %b15ag[ݯ| qH~/W~r:"f7߄}v|o>6z߁̻tQ\r6d -0&ΆbD? &N0pE9dBXvj D#+Iفf̃ Dix^ 1ncm` e#!MMܮDӀ \j,p>MQnwq 8 W"B4cQӈ=fQ,eqP&k_4(ӺW-Dq8ys -a.;0=YLUX57} L<<e5A|Gc!SjȖ#̵-S#"$n,M8Lc!Z- -.h#,شPx~\,8j ڱ;%CZ'8'N^ -Ҷm>X"knB6iAYO"w#qX)Κ7T =<ws-$/ЌcGB*5%#[Rq؂!Zʍ`gǣ}a! ˒ 3)ôWL2Ŝ0sg0HZKcoȗ+?wBЩ(7aRf 0ȵ_,_mP!``p!5Bt=[E+nS#)/rl/f -8€oAb-J:AJQ)ZNiRS<@f{M L3+΃v)C4'i5 !(^ n@Jmse\ "jkд lv蛜vmET-h.XUxw;s0eY7lC:CXp`O1)Zs5<4CJ)' Tf!a`ό`tr#|[(7" N~TƘ@49Tٽ'O:.ab 4yYZeZsO4%)u E46!X|r),wP4F`gO dO> -Ib#MQFSP&X]~[O),$;q҅ks#=s ZI>%J -:)y>= O8|_6 ;FOT%!W`JRAq0|=Y\O3 jPݷ] 3C)Äw2 &,1qi,HŊж,AP?&[".vF@m+"2]"ؐKMCt^S\&lgDM$|fO]2 ,&liMN`pYk8v-("kcd'/a\'%+Y!cࣤƽqcr re(Ǝ^A߸24cJ4K@EG݃'eE%v&a^1E8WɆDS\U 8gmȞ>L>,BTΝ 8$Aj- aRU'r|8ݘ} >'h /wlhcԉyiW*d_M^e9fȇx'"#v6?0+;5خ?bBoBm -]t m+ @scIVL>tA;R28n  . 'B:IX3On,~F;ޫxؙ$G5tC'd[l*OBsuG==Tt3T`\6M:0,'l82-Gr04Ǫ(GUw"+2V;l?A4{HNyzV"eܿdb#Ԣ`BDT+&@w`7iN`\Co6NK -!Uz<椪" m0Mp+ @(yt"~|k4cZ)R84Ne!bE h_ )X|I2Ӧ7^&ZڭD#z2cl5MR:yU0K}u(6_9"wZKwŗI+'icmAxRW#f9+Z'|cHCG& QE2 w24T`c53 аv( ;Tmm*@Y0mudPБfI] -ͽ|< -|2=07J'nT mmA 7]<)](H - YQ`wIYps/ٗ"tZ'$ck1%Js< A1ecUB  mmŚ\+C%XNNe14#xiL٨EPt6n c&5k=:: cQ^$L-h8М֨OhT.2 QB BkMN(<`gLU_?cx;1ؑx:Q1Jhhx[B ާup بe tnmLfÇ+L5p0qjjx]jjpD$,osԽs-Q\o -ȴwZZOkG_1|nnZob?7B3-o(#9Hǔ YFkKΝv):7oZq=;}^cV;׹;Cy;OОH=!e=BbxPyA;G@&d2N:0K'$K:X6thׁ{ƒaD'JBi>  k St@V`a -B)+ltN}l -_10:m퍵P!We~a;վAH-礪fv9{`h2tOY~:kg=m;U^wl70CwF<v0֨ᣓU*;@;wgأQ#C7LA2Up5&yo|o#qYQڒefTm5}<p=0: )1)"ّɻ+=g xI>KFmwE\%YryzuZ'e\(!a{` pv2l njT3 _DL+b8띯79XmqB5Uok[s -L>~J[gzk\풶2PZHAG yb6I>M%#AuU`hw֩JKHZZ6'7\KaH)480yi?1AEx{ox$4A=1:8ܙ:KݮРR% ݃ ZqWDrm_먛>I)Bg4Ε/`}V(<Ays2+uVR -t,2v.ZNLZr΂[Nhls.IA]EkvPifz4$"YA0|כ !LB0q>c D`aVx9'8Pfa li v_J^X :TʬcY:S 5 isP:P^8xÕeN<L:Ѭ,v>icXpIB]|^ LfKnY6=5-#c xa :7V"`5[vK#ͥ`}w`i^T0=Rx8A솑kuOT;JZ/GLYi{c -$jA1F%>-"P223!گ&xbh& p+C݃ЀqñzZOM1.y5GdMdXڒ ;q`ɉn$U[d lvW,s<43W؟:'N5 l?G[ߝF鄃v=ܴn5HL.rFX}gOI <(Og!IeX k fW3g}㧟{8܎v"J=+p# X%jڄ`-#Sim+.:pl8ǔuKamP+U xA`g |ofID̸2+B0,kBJj2,ra2_ۃ_Y12`EaAfP3I2 -w}3$ Ƶ*`/3;2Y'-> qTF-1/%zdaE-3ycPILi%3X΢EbKEeDs\'{DQQgSjt-Xq2J'o&{$AZ=TgsQ"$B[RgSR.y AFMI9'*<w.9mls;ѴR< D8Eq;7H0'Ba)y{9`(i j=JIN̓rj)Y/ODSXo$ě3'ڧ$ m&rr%9ԑiiRkP< SbPSW2kB.g %1x(a\/t_/uqrE|2Ď)Byš -Oy3H13Q#"Ux\3L3N0vb>3S {)Lw`-b&rM'm9%yh{E7&LJO24܄-9CfX4`x y*k&\=P=ZAm!jy.3 Ykz M44]qyAJ=oӻ|D'*ٸ|([ ZYi]>™EWHZkC(À % Uᣒmcl v6FU>`-Xp8|$Ei>T6rнÇO%I8|B 8\ɻÇ#ӄPddvp}ʰZ}jX2hÇHtY[sy]qwL@\&RV - coOS1au^܉ :g!=;NN]2gDd%!!w9V2< ߎc󟰠xy(!.h@F>WDʁ(bt1V'%%̝2Rc(H}:\<)yM&Jݖ2R ,UJ68wu -WHLʓRt3ʹUqHFU fyۢbg+H׊8H{ +ˬ" -δHιr!.PT亴a?Ѷ:%7@K6\2B ;+[ !R]ʔBVCcEZL3$U%*[ !f%f3qX$692R "n] -Bp EeJR=*\IfR*fpR RYNޔ?jPpI2N+MZiujj)5ō 0Q68Rʈ-"Q$06͒ ̘([1Rs(LAyJ27OBê#2$1xE6襌g"Zp-mY^DL\rԩeUcFi2eNʨhknz=^%jOlOaӲDd+c񜿰,HAuE:=Kȶk;Qf Zt1)>'I8IIƫFhI .$|I{aYmrN9)mC7僸NTqL,8Y, 'gtq*))#s!è 0p+*8ŗ.݅iᮊR[IC12AOvE*NܦU)E19PUƁg+6[=! -42%$p%EgT0$"&po,a)ckLI\o1=aZ&#ܢ5$a·A|D3::֙M.:H8s[Q>ĪL׭| tr؛UȖZ5]~ UK&|jI#H*0?T;1P5IYѓHӃK0߈Tpԕ_dË -vX/F&B}r]:+҈Z>1Kt4ˉul:e;UQ'$WK]0G~ -_R,! H*龨t߆iY6P3^FwSfTc`[`F : ع>3ro@5*O̞}DS6sw d1[!"LV%gVdH"{}AKj= -I:cZ #_AGa=LS&5LTFLC)ɛ3kW9~aoza(*ʀծd ;Nscd*ưΉRLnT`FrAKX~Łq%n[ )VҪwnlexZu7bH0~$PSt.T.QG_{=Բ)ܞPӱHN*?a+*ItI*t3"mKzR=TQR=!=օzZZ;-!m9RghXlff jQ7ke>\k2M5 -/_ -Ƥ:1i0yMb0q~0iB Oy_e^*]"M[EbekSj;͕)F,n+iҒe4y-%g^|d-2.[bN]θDs"S"WSlKq_;%r+%S"C!L'0`:eR*t - Cl1}]xDvy:ޘN.cw^w$@ ]Vr_v8f't8;)%̢0g'uC;a@ T09訑C+FY5y^<ljf;qKXIma6nRMF3iD]"NG l8ik]&u]p\פS$S -N5הB:]3U':u;*WjG}T.aw\l} d[2GCD<*ɦfDaFl/[Jʌ3wV.|uh>Z X$,f)g_Hf@gk+faΆؖS譏Mr9>4L^ћ -Re;wטA:xOgl]1~nrZG@?#J5ҍ碧q:IK]3<ۂŭ -hTn4^+uu|u, pwIXr@^9:Zew%Gg2>QPYc%kץ׵"pN W,Z2A-J {Gi R l`bn.cWݛ޽$2BA,_Θ3E* [YW&qE5$Hb -[9)2Miʄ[`}: -_uV3,s :T:d;i%pG[wK댾?RC>B% 8"'<֘ 0>wxR]JjQ /m*) yFjmu 9ߵA?PB*Ê tuږ.B(Xp\d[BUqQ{@Q@ -H[Z EA(/vWk>(b?+eL}9ߔaˮ%Rj}huP+z=P>n yJ=iXW*ķi߲vTߒGY6("U辨 -uq[XHeًa,1PV{e5ڹ2Iu1(@OQ|i 4(1+i+j6.MƟzPq! -T1YC^*l) hKH&*;q۪A%A%4;u(IFȁTDlK4!&ڷZt֊vԛ`)CWbe,k詌3RWBq:}͌7x ՘'A썳X v4a!2aBã&.C&jL вz҆I92 )4N%ZI H֘^$̡#{Mٛg ỳ,Je0p[͌^n_u "/ x pn-SCښٛTok=߿[,ö@C_նBvBKL :nAt&mT{ۖmՊ~{_t|"XL*ѱLG]-sx^82w$ T)㚙~S7d2,Hl0tM>&{o\{Ō*-u.X;W^#u174J07ozoZa^{ȃ%D aDž]o2&{UU{H*/Onwh|0=VL#go4%p1iqڙeq@M+$MOP Iy`"Lr`?jطedP i\ПI BRW\HU03>)ɏj& =a{s21e}(T ()I =^ -j[\7eKީp)?zMJDpU&z+tЬj.b=OhIl-XՏl|K!\ PJVBKh@2%du_<+ZC=XCl/µ5$@~H:5أئRV?CRVMזb?PܭY -'mY/~G_}>w?aobn飪{ٴЦP85éZ:*(N1KT0'`T%"P:`=uU*3B <٪a,3ƥK3Pha9O1m$؆7;Mr$}o2ȥZ`V,RNc۶0,gӼ%!FnfHgTsRݴKQ>:boCiǒ!2I~dgGhAM] 1z,)Hq/Xkmir˸t;7d(;k|M2$y,.F c߃ r'B|"LCD'gM{tY|<| -aHcWy@4a搤i-1drk"n] -A.[Lt'0=3JtݙEɼ[ZvV$-ģkG$'({{g;I\U 0Sbyf\`cʕ%p=%! 6قQ۱Z&MnkiK}k5lGir7:-ocԥ 2tBLͫh x`{tY6lve@({_و|v -iQP(z p#WäUM[pQ2cKCMxq ?.f5N]=3[HQO(ʴ){^R\R -W0?f?sۇ`0٢Ώ sbwO{ˆef-:<.̾2N(SI{X]HqJCň}!Pg ̮҅ -ɂv#~ss$<1RaklCV<<drcW6 uٷ5v@bN [RDL~朮l^-fa8e$pf>>:p-׼hlڸU*T'vn,G0VG>4LővvMإPİ0GE̦lt3  ~=ړ,JgC?m9d­̘ev;N [3n T7Y`FW>]{@{2C`1&4_gÌ`7<,H*ﻄ+77`]EqM<y椳xtJxv>1"2Hrpp@f\;bKMV9D:sٻ6? mj]-JSX)U-| iXCe:$e(͹whEZҺ8Bxpڑ0۸ruhZp;Wq2Hڶmh0GW4 $f:Rv/LaplwN}t2Uzmlǁ((ښl>QI#6 ~ksۢՏE=|)a"'4vX,dFZcx&ڦ=uT`d^J?gۦv?V^ -4 SsM֫f3u><mb:Zڐ۹J"::pYa F8! [A(K߉JR¬Avnƈ 2mꢒO: ?rV5nQTt D1WQC[$%Q>GXkz%6?)zf pؼŇ2+ b$NhXvh½#t^ȧK|H(|Ts[&|s'L )xHl$Ѫ\.V%-hCVG/7w(AT{CWښdyu_mjx)t3j(?ZDiayLfaH^Vᰄ V*3kheIzV/ 3$EYoL  {=I=r[{=2+GkÌhpoFlbw'.\ao -{oPdg3K]3Kl 7 ͱE!nݏX2 fJ1"jʂ#@=UQyrvYeiun”5Kk BYF uI߮%O+J)hmWRooRC X-8u{)E݃7RC_=WP'9ەjT1-Xe%2T =Xͩ:B V^ -S\B]'Z45 )u -"NM<#b4=7;\@($a/ -mWV][c*}66kyQCm u41b)A!V({XU+c`0UF7L7sQ7sfh9jytF5PM#V.=RڻGJG{QhlEIO[[z|MMFI_;MnDpVg- }=eҖxͻ~;0d8>sHML!=/zHXТ}N NxnF% e( ϧ:DZ?"Vw6H9r # LJp):Qq;h| } PS|.9PhW\ݝ (v-ኔIEKi7{ -&p DT^[c)*9S::o<鴘Խwx[z5~&v1}Nb~$d5ónҵdGQ`dCoݒOt.˼&X ~n~Uj\ 0!~DvN<3wsŎZ>cG?ZPCoM޸DPMQeўf>l*[lm)W}aB+*"_JݍHGfm _͍TP4wr7 -~BU8P7K_dKi:xrz Q -jpӳ_'dRA;$Xk -1F˿tBp)ND,T.^.hX7:jypƒ1 ͮbl -sqvsZ؝Ig2$&9r X++=9 X#`Wjg"fdPqmqYRydSE aIɫ -FĿBOH&TXPXx́TeN !BVEu #>]SO4N/fP.:|ReJV+e 9rBnsl" H韒u, l !ᯢ'3-8W`3/*[ٷ6f^TGc\nY=`!fo"fЃډbaBOx0B.5W*pMxsqZMxȹX@C}rWE%A b݌KɡڥeJ -<"YՏՁj`1Л\<9L `bP%!zhbv -^DZW!1[eVpLL^\Roù[ :94jǕ!"[a$m)h-˵l[X0ev+@5ُ41)|d"|eLCʫoFc9!TBw@zYsO2ArFsGf&ڤ -}{u9*DsE,K5jt/ )Tqࢠ m -"V -{SLWȧ6N:;[aAcc zbO6Qb1+ߵ9;`0o{B{zRhZeZۄSBF}!-/̍J wN}RBN+MDs$BChr.ؓܔ0RKlArE[5͍hr>mDL]5uÉBWC_p2_6rJr}]IxD*8?V @τau'L%QlN`IJj+W0䱮iP 'GWU0a|fk1u}4Bk*Ѳ;54ÔӺBQ{ -u0. PcW?̞av-[W1u#h4N_蹄Iu&LOJFqq~ EE鍷%iv -#.Džh{-ZjnEskⲏo'7Z͡b7b0Kh[ ˦kTL'_l!@cTǷ,z>Mwi%riz1+ur cO4x3 7ŶcS7ۼdf6ˎM#S\o֯lɭ)3Kzt#wl}+&0z|&"gMFi{lZ yS,N [ oDmQl*P47PIuic*|ZN/ų4|J5@ {JgJFM/5a2u3~68{!mNEԤ./,v Nf;y=-jnWZm h f ޡ{Wi m=s;i~p{4 p9j OY=5DPTr|=]Z\¤x](~aBTiY̆·.&[\ t{(No' J褵TB$sҘbF ‚ՍH|JDSO4n+}>;9P)b yַ`A -N@B!vѾ}hMV1l]׏xѮ/ kAtqu} ttBApcgxaޓa;0q є,տ8D5-ش d?!~;qki\Wr6g$md"sjnU2?< VbSݣ;}J{,}?W"V-G@ޫ|x|ې!&r \Rt'vS&Q误 3QΥ[{F6/ĪSe[ -E30bXmMD5!KBC -CMcv s:zbϫP&FS(K)\<^w͏n-9 -6c&@o{j ,;b%E 4f8fGuۼБ&:f0FĠP`MGջE -oz|peeCo&w,ehe$'ZfU_ܨQ>H1 ``fBbu>#X% ֪ر5nY&NȚio%(_He>Ղáow5'c* ^vX4f0`jI9͈'!0'TYk&F{j[P5dnDZƳt"fpT"~JlU LDcOG=щ&IˣFCaT~"9cm/x~OΨ-m&3\?/\ :GQQ𢡊G| _#@DZ&I*ѤzZMUDÄ_Dho/P}7!=(%FS9 -W%EBThfUޫ&b@V?nRޘT3`^WW -v:QXKa hz䞖l1j}#kp ?f_lt 3paDSXlѤϋvҭU*w}X !%14fChYC+vN L/*Rb)ú+RU5*$oC$F!L_4b> -~)tv8] 94}H[΂qimz&-do.a,MNDsH9u85ra(XG'&i?W*=G cP%;(|+(L@d_v[<;1$+vhe@T68^hI#v. .ouP@~g2-ƒujR?E$,yX^84z9ây l, fAO*7X3JmlJڎ?ۋq2Rjѱ yc("s/@Fiک(ñpl2GvB)_S,Ac;M j fw[PG8іY}_y6^szf^#x6ʺ ۠+vNyӏg; ~f& -³5ކּ)6 G@ ==ZE(Dr9o 5a[=5)[wMjn_ܞ,ya7:ru}%zz !HY6CˋT* vI}gڱ鿹[K߇W'snpE(>3P<\D8_5L ȅKLj¨]_eDu)zjfY6zNr¾ &ͭV%f,|69\bC~{ؚ웽4J_t9,h=*Zo̹>C<& -eR oTqM_tyWGuaB{3^ܰSGz: ʚ==.1e?V+sKͼ}[%pFg.t~vnϞ³qe労x2D]-M -en@?)Bu$,ۺa KۉحC.frJ,'_Cm"ڃtg?L*G8/1uˉ"32+ɖ@mgKTzj't`W].\A}C a/Hb~x7/ 4" d+:OuBGD78 ^Ͻ;z( ;J2m=At% -&X <)&/UF 2*d-3>8!j/"}< ;xkJ4%IC zIu 8+U1٦mFduO6Xi0^nLi']#~B@t #aD0XjA=V|"Hπ@w1mjg7O "/R?szϙb -H |قܜBhGE%%/RAYry)-?e[ !11lNL], RўlXDM0ta@af-_ <&;#t{ޡ, )oʅEtHjT Hcڐ(ڗg0 4PK ] $z43wEbREKHiRv3VĴ!%g&,6dzIk.ӆ!BP*ܯ!30q -gO'oYN\OB_n %^uCNgi[!eMpEhe/^`.Io! @1c迤ѯܡKme/z: T@ܭ|;趝1 -]O+ -US$Qɾs\Í:|-}.?fn-S9et=ϹӆF w~>@mÏRcs>)4{ԍ3w}`liƏG'ۮ#O|}tbqocE2WaF`v'ǻ`R4Ka?NABrT,͋Vb+~,f4JZRDxrhD'$r(? -t3 tգ^Ab֣rz)H0GC0{l#u !mJC_ xg;М8(tq/75̲`T$#s pv:c_@I=#ǝ)M:$WҝlLAz(X|q_AڛM|^s`  ^x728[N=v?v7rK, Yn/ZbMHf!}kR3 5Y:B((cDBh:ys 7wQhDʚRL%6 -@M; k焄Hl%<<WmF7K= aw5(fo>Kw_k'`3&2*,*[g{ࢂ~3oa- jH Q51a v/D,;#m>JL~^A  䖋U~S@81Z틴+Gm~#0%]Iy5+qy&N,KIҡ1+%!?#dCCGl\;JZ{$!mh(RIzF \0K~;`Ͷٞc!>B6!FEp&S*ch bf6ܓd<.}a>ZuH4 83K?p^ H][UNCXThCG >B % Vb -?qWP3wQ\ʜuH2iP)OJ&+γۗH yYw՟G*wH.L#B<~ELLLr0 X< -$Q =ҡk}MrǥWu/.'yR?u\b2|o=ߜ\Yԅ#;ZOQ.) QT%8--W(X-:ٴ0>Epl[9 |F1~g,<@KGdMhv]!6ʥ_Rpo n<UȘ"RȺ'`,Sn2tڻ6J]FMxN;˜׆dR6o(nG(Kv-uŽ7[ZglKE^q8RC|]HOnAdД^TMz-ynJ'T`6AI&\Cc|\d)6.K%. a`Jeqq!]FK组`h(LAFd,,#΂W_*VfI(hnMvH!Ɩm DW!`4Kf[>ltFyՅ̙waѳZ'[?r 63^+qf\aלւ{PF\ͨn+xn\ɩ V[J[J6>ڔ RW^ -BԆhA^É0lja U03cزjwhҸ);Yvû2 &&C](O{՝.WBx)] *;Rq ,KLYɝ i-g]}xbuǧϛ3@ )ݣr0V1r HdV#6^}ƪrT$7&6'/2hL8q`Jr卼Ry;55\[ݰ%f-`nRij NnnQD '݌+!l"jm`r^ -+l,|̥tU@&pjx6K—,h{+J7M[?^ZV ;H=iEYXu"tZ -U*<8wl?@VH[Th|PđuxGv%`"-,bc4.gxTfJkqpaA7AE l!*laKXv@)@j <7pb-QM03\Xr kѲEa[Q0nRTŮxG QٔKASe62 )H%;%?+[N%r`D87ykVvgM 'ZP|1~@;/ıVN1T7Y<nVu{r9n5m6 &5glRgvk6=E ]&lFVԁX.@dsa(i*~K( [u4ƉNM8sX'f{؂3FThqVœ%1/(chu9 -4e ĺ7..,V`sU=_h5٭d4\+ն7$܇IXۋaiYHUc>\nS,b4*'$OEP47^ETp`k* U^sVS `q472NI(1/8%[^.J0%6"w5=V=VUNttMPj˾@aI6M@-@^G<4%zHƱ-gݿ-,h n㢙$.|نuS]m2fno|U=67HTh M2S*Idؾhj$|2ώq7T5PQ+gV{-_E]̙k4nbHPlZ{r06vEXscnjPk̷yħy+HOWA~ -c86B~w VRL |l9J-#"ېoYpF[#vƾh+qUZo!y/Eжq̒4,ЌS±^&ez9iΉBce,V$;?t {/X7eoj!Z[pfI,&>E"Y%2+M&x*S3f)X* K"ET/8pƕJGK:Sy7"Ao!\lZl&?>s ";z:X uIdR)T;R-Tf\+dL=UK1" !TFHk+%m{5UnAO\a\;r;McfwQr6Tgc@| %e~w-yĠYzWm)A4xM15TXd22ê!1Ǡ\M]R?Z@)$*O#]Ar)=:OĚLRF:ܸ"4z^"h m$ -Bϧ}s7嫆xWNfMY0bGњ'-3[\yn)ع=91.>'3Xi{ѽҲtGtqjY[sa=L|%[LZhı㖝xgI[_ȉs3(2.Ju S6;nťL߻&l)ca8R6"3@/b׮%.~J,r-vyt%=$؁$j'֝w}d ^9Dt7ui%6fjnpv$ݖQ[i^s_in|Z~۹9̝}g0>`fTB\3!%B"nj7ތ0j 5?1+v'6.Ot⠊8N@U k=Nм㟏[~'/x/T-DvAdn4zIUNszඈ/1THDxY t@& 7y1u. -xYi~~ 9AZUx=UYhV-(,@]xLOvGRI}yuWnsSh+ͬ" jm7t+O9_R_~Ѱ`I4"$2p"4+DT{Yw#L;9l #-fi⻝gQ܊beBŸ18aH0;xʞ,k?0_@>[f\lZ+G̷iiFhbH8Y~JԙIz3 qH*2R{-ؐ$5PhY@swdYDW -%`E*FHx(->1JFkAm_xn -,En(]}*Z0ێHWT6R1dmw9h,_UGB9b46Eߋ~L:G5%$~.ћF .Y> 9y˂;_܎Pe9LyZqbכ#@KɬPlx2x9'yMMMq=<0yx+Ut v -ED_8J/4T;h;CJϬo_Gtg8Sɭ¦CuN,\6X6ᰦ[vRܗF|Hus wlNma&$VUu4foo>MZJL~6η,Q.^Eug.S -wvkʘzaVhp"~YBv]6ΝumB3φOdҜ0s{VGqLRJX4n-^l(MGo7*r$ZS>Qb\jVF"vZed3eR3 ɥdE.H$0Z0eX2`ke/Ԓ#QԒpA7f d;I+Yg}㡖C 3r% j7H]r 7rT/^]Kޘz}<ꬕ<-,#|Z,j}(hrgQpp/m/m q ➳= k=32uϥ/Ꭓ/ꞓ/K_=g_=gOU>0}ξ0}ξ0~.}a}aTw/lGoϊ* ]AzΒ^[ EW{o45T@o vgꟼ-=%xZQ?{(?ϫ۳TBeB1c -ufŝl@k-QW-`7\`Dta,IL85-6/x,]ZAN0\۞Wh)M09GPEAGд,Y ^g80ZCx&e΅0]ES4i\at(,?X,ni(ti V.]Gp۵$4spĽ9sT@N.As(P,_Vz]"{G%t.5m 8:j*b,uŬl'f:qO巾b7giGWQ R/%t3J+I\_$0S9?rr8KnSB̿4kj %uXsD(ty1 4|FcOdg!drv曥@=cZ9kf`:9p2Km͑GC<>UV53A$;qZSdV87BfW3.U JؒӁ'&lC \~ww)Rv cJefv{G-C0DBqthyyyCEw<}|7G遼x9AZvz)i*+-Q~ H#Aom-IpOz{صb6Wʢ[:JꋵO4(nְ.{AYS4 1!eNx&CWʀJ[|T@ԼX&T(S'.K,Ӹai~قwkms771qN\S{TZ75W`a}j7 Nxr?K03Heʌ`^ݟ$f;77% *y} D(1wx77۝g],NgeɥT'j#hS5NUbÞ(](NĴn^i7bTQTЄR8)[RW]t 9愦n) ?01._&R,uH+lz<;,.+85!܋cOhv3IcYUN,)C?1<.4 r`JRo,k}w> ܽZ}4ZԊ>wtP18 -ɤ -"f5!(xR@v@gRGibܵ {\*Ui>? -} :bTU܌TGsEC@q;":&3Q:QPĂ>~LQFPG}-/ Pz6 ktt"}(ZN- Fszm0p[^rSJfN.j]ScJ`ٿd$_ч>-諯ӧ'qc+<\Ƃ*>d{;;aP m R4`${jC*%".c]PhPm3%,vZC~pv gʤR²2H1R -v9H?*Mr5]~eHN-AFׂ*8klDSI KޭPd"|K˶4 b*;oE\piŚρmZ-[0,CQl=vqPh8x  Ch>vD*ثvA%AĹvNv"ˢ"e+_~R2Ka)CR9Э!Pi(n;J ˓rKhsH^чb|xo ҙ[J4RaJT^Sz5IGѯGQ6v1;Ƹ k~û zӝTuԪ"Zf[Cc<9:b/m/ 3. 5-FE;+9=0._fTѿV-_bC.$@! -):]G7X?U8,2;_q1¿[ ՙ aۀZSn_xغmcP֫ b])/%]~eK<} iz 5iwtetXCbMl* -痶%.ѶNU F =,hPpJ. ݣAOt{0߾ H=•#/qX[<*ًԒr ~70nL;!'˭Rր߈ʵv]G5n7k-(hhI@AKlA_i;v$wf?JWT3Rldc8dZ4nNӇ"q+l'}NgYF˧v ?{`M ס5&ڪ[hRf,<*o%Ƅ ̀X2ە%lɽǘ *h; 5K5r' x|nxicD,怹gD -`؝khbkLRcԽ(lhf؄wyoq7n3I{DM$ȲmsoI^{x{- ; 6P,6>b)+-^vIMD$*v3h}6I+hvEÎ.Ow;)D 2)ΰEf 7d[%`$䃗1!}ts-t[3%E £RuEMQ=VC5#(2*=pBge ?LME3c/:'at Bn9dU!іk_.u8_$mbA&Z W ""l `!+i+֑Eo!sm#6Qz@YĚl[d>ƮN0:sRvg-`]R~|ܧ4C(@3Q],2Hh$N\_Hua@UAA:v -{`Ⱟ"T!h -U;I]gUr&Ft֮f{ FJޮ%xʄ](FgkEV4*d!!JjMN(1J+`4h`~eyqwdQ*J/Q %DLυ[*U]~`Nc93A#? ٦x* VT~:Wgsz!5Bi*'# -dI'$k*Dpۛk֛ؐ -?%Rao;?#L5ut|a-e>v0->1'XxeNH&p,֒*`PuQ5uj^ -1U$pr.W҄~ad8e -Ji!Up425Vpam~څN}{am kw:L"/vōnǝ;7_1-ێgꑯmjMa#ke/“q DMbm|c{p#k|N39 Зu04! 8@'B0E!ʚI -`BpR!: f~DnxuZ KvEͶ,.Zs"!~!y\XgK_C5c6!_~3230 X9!1A/O4#hc @q 3,8=f| Teu3 \P -`X`u)+C)lƛZĭ/kpy;?]E `P sWr#X;aQ S sA5 Aa"s9Ps -r 0Jr(ʺFYB<Ä{YFe -0n!qЂ#IS"6-{93]$!YsE>)\U&O$Ysz!X]#YaPg{NcxhĖRl"nHy!is SzޏS\&˪J=l޿;s㹷Ah|OsK؝ݼ|]rߒ- +Pti[،PCX jŽ o/ȋX9z^]/_п?2a-J)3,s# -o0w&IDD.F3<23Hka(&UO{2EhGLG 3F,m(cpŽ:* %Zp' ms/v'+|l$3ض^VEeLnۄ%vN鞇!a ؐD&_*m!IjjY[!!u˔(\{}pX/mh{UF{鉲3'[굴2Ax4&ya[l( Os#'M9+An}ëVe!HÛT˫ -@kP:/ -h␖/m0 n}^ܙ033P˫MR\ƹpBHjڔ6tBul%fiEvF)"Z(#n8gN4 SlD8#>f}xeQ0h'Jp_}eb?ȘT;Fw{ +J:F - )i0CŃWX=!1a#$yNS3lR(@mE;|2`5V6fނ"Y sfd R2ݢW3Kg -fz'/qv/F@W N, +xհLg# -Dؙ![<6T|><{p=PH G!::`sFv3Q- 5=c)NAb*$ :Sp(y|\`̀GQr ![qz5 -Ѷ@Aѥwҝ\"Rv1 hO6T!LE(E -pD?dmҺy%-cG!]A(e8SIᨤ)yE#v"tn3,Re" 7fi[IגK@i)lBB+mB~)Z \>XM\]+So~?8) -}fNIBO<MhҞĝaZІDOKdHS \ZML$i2}<-AH[ݣjwH?nyq`" 3a3 '`W![7Bx0$ąpyڕqə} !+ْ@Zvg$V25ȶb>R7׏0_Ul%x[Vv H\OB P -zJt-6[+$aj!2vt1<}#pgS+gL*H֬{<rK*Sˀ; GR= ]v.<،HH΢"'b-M^h$ၬ^@\ʀ&ml;?:\ІtSUQĻ}r UGQt؉ B5THvjIT;։ m4V"Ti0W źƄ29#Y;~ueӊTK]ᕡ_^V@x4~P߰nc5@Mn -˯Pm`(a n -peZ;hvx3{u*"gK=X uk>t@9hSlZA5U'j'bu4 Xtϋ_f&z} M)%^ǭy0oW7Gv"`bk j=.[gaID3ZZ\%M?;*_z7?|_|_}o÷?3Ϟ~˳Áe.o_}2۟yx8'_/%?9w/`u_?|_|_\^/?}W?c>ˇۓo7ŋ_7~#;4"Юi7???]ˢ)^?_웿_^?^_t~W߾_~/WwEX9շDKzW-Flm~D"?+??_]|O/_, Oˋ~a0I{OZkВ`^a{=x`;RHJl9P!vP?fMc9P H%T(쿁~Eҹ19~oQNW|$תbkaBTn ryoV?HJvB안8|'u'Дec0J| NH!2Fw^] Sr hRDi:kB4#aEEAr&Fwe8ȏ#OnajbL*QĘ%yt C!Ŧ-e^ TL`2h96@Oq}>/[(6`<fzNd0HkPGЏ%3=-A*:*vuI-T+ԧzmR V纻MQ< -kstTt+Tnlo0[aƄhM=};W:sjEa' ~Q*^]jk&`2+cUg_Q;D~hW1DYR[Q}Fan;x(^VXҲd/6a<2hg`ld4ƘK{")0ߑa/fXD-;W%UO7}uXB3qNz7̲cۏIIQd5SHGl(폙&b"vȒ|6D0Ǧ4E~!DG'\%T36w#uqd6(aۓd7Or|^voMmTozktD.*&N-)^cwOٙaiXu8Hya?2Җ -nw0D -ū݅1#Ma`lOrd8vC|TT*v04wsa҆?c.eSS4=.IX];azN0Z0:L+׉Kݠ.!QaD@!ѕSGp}4z̧ H1C5$AET(G>~XI1A|t 18f\ol[f3M0"ށ}Rjc^nM3n{cs o$@b*q̭Ro$_*i0b{C"sϝ҄*L1r4 ;݂5تr]kq؆+ qa ljڭM@PWaIR WMQ7,djxޓXP>P(QGWu/rž| Հl -+ -YDG =Tc}-jA?Ċ:oj6\ "d,]f:Ľqߺ; y0,՗ҷȼb9Nԁ͝_+|pgy9$Ĥ'gG^~8.(Ԉ9џeim4b˩b|CF}A£n~<οv~{4ۀ/DU\xDnȚ6w`:RՎTƠv;*M0MЎ-m":_8(LPK˖Sq@hfK'T+XhTR:@8QryR_(LppUJJkf= -~.[@0;A;俟?0;6IU ⎯$h9,Q|`qLQ(B -quR>gVKU'5j՞̏{+PK;Xm);(B5IU# nk4H xc;Z/ dM_P,ue>3p\Gt7^TgL -FyM?>nx\wȆfT -;DRƍu|8z<#jںL & C'Tw'9#2ԥ&u88<dF^1PDe4nE\3@-)  Gp&O5ܟB~I'=A>{$A` :\չceOOc~ =t΄<ybER|6db>WBb`v^I| -y0(~L6hGaRιWLŊ!t$W#grm axU,k60!b7oQiy(Q?ڐH! q}QE|TkPwF,>UM3* -0ێ窽.jlӑ"OgN\=t\sYhiGfΟ\ V<<vHeixW\P -[ ZUȆ4g`V`ӌ1XZ[Tb=8DH;D{u"/9:H8%}Iەa u8'9^.v8nZt^7or4"F?fޒ]jB :J.Y2 ̓=b+B+NNҍ=6zQ2|]@{ʮ 9RlOlI>EeD;l>mƑ4  -O>#$` -5R2v݉AAUͲŎ -`]6cSʤx{R,Ƕ/$}Mmz}ᖞj VM=vhRD]?g웑^go,[7nXijrAT}Aމt^ӶHp3Bi ZW<`w6DmMeZ\AfeVEn=S% 0i}ƲP{K 0 GX~ vms郆췏`{IrE.ϧwuV ibu1{qH!.~~ JN˥5VXŲÌ?kҋ?k"5KkˍfrBck|AjP־2־6{% *͓K~N,Yu\xox{0&x,gHP[բ Շŷ܎9+sftLa;PB!9[sANâi]f og07[i*+(;WN1W -!0G8Tzr#LZ#W[i>#IO %a; i!%]ZO?fq`~JRm}~@dԟfpsp, Ex^E-.g3 u̎IQ}kE(B&ocND*x+^V4r?fW4 a0}xc|9H,U*6AIu\ GWba+t`*AN /DT̕c! .h0Tgޝ2C9 12 V~Ω "obQv8U[^%Ug*# 0.=*K>H"x򑈗oty"r$Gb$[B^]1uOH删CL޵ݽ҇(S(n6k0#"6f,:|(bEXmug83/Ï3սB6Q/M9w#< e`RԿؗg4xXʏ47".+DU\gp@{;N ӊ3v2U 3" US1O41*[HC3H9.=Og-SYJ luxA˗dOaQ.zwzZEnd] XѲmrk -M-G Q?=f2(bˈv5 H DWl{ N:NA~O -endstream endobj 23 0 obj <>stream -rC@zO5sP'OY0 L/W6 4@M -{LrTxr57&V*D\<Ĵ, b-),"pWIϒg-G(&Y89 !TA|4yɊ% D I -Ʒ? -$ ìG2hΒb:zV[ݚOcm! C?1*< IJʉ_"_dT]+ilr*n¢Kԃnu;[GFhH$<;wEˊ75݂uLQjρkQ@<q,14'B)1pڿLIMn3#d\,xz 0ShR~\U9"t= 9A8AX9diPi"3v7G6z#/J,'W.RKҎS= LQ-WX40# }z˛Ej :pǵdx5 8" 0:N)R߱RJ4᯲c)Z1a[L^Oj#2[sW_Oo_?-e(60q~X&Bm qB! >7ǻ-':z", k$A,K4_Ivo~m5zA4 Hҍ5!"\{\7Ul2B |/vlK EQhOrxCkʮ}aojI9'x -mf4 ,&mhHH䳊&>Q+E9ʒ 癝^4 Xފ@d̨ߤ6M仰0`Z@E QscHT[3)u[N+#X4b?t'X R%D5dB\E6B)Y)\7= 2XêS2Kn!T[z[.>K* 꺆=yؓm='2f(31Ɏֶ|ӻz{\{n`i_տ7lX׵Vo1 ~RD9ތYR%r(X}}C̥Tb:CX8@̬qG0V YVs mkʛ|Qes~*HN@M)hv2= -R@X`y"WCMj -Cg&1 -ؤC<Gj[wgFNKNs7S W?AUk/OsK_Y3&h-=L@U%7sI/j,>|VstbO !S:!.ReXf,rdOz`Km9>λ/8(a\Op @o*^b8Y/:fP&"7ZRK0 &< `Zeު)#H[ݙuHY$1#N}CZ& -iϪv5? -46C=%m`"njow. E[ri -.xeY"ƀ|f IP[5q[GI+pfMnѫG[" }XVzHFȢTݵn{%S+3u ȨSu; /?7PcAHqs6-[e j =Fj*uIx 0dCOF?_]61P$6~ ?x4ioL,1 v۾]gUN:mĄH =^#0<6,yঝrwPGL#蔲)WhrdO BhY!@Q~eͨg-n9Y=js"dAx8b[~|I/vI{eW.5}RޒrK8!%nYl9Q*R@?dQi=lcz/|TSP=&+ ~c Ee|GR-%][?6BN~ʄM0xXcQhDzRB|() m*)!QQ(CPu [(nHִ b@s<T7wDZM׃M!" -xd%b rER1OJEye - j*YkI9"MX=Ͻ=f3CB%ɪe|=5cJH*͵*Z?pG5wUDΣ@j(&̟O=ǣOhĞѢ@wI5P"jw[zx<֠AOʺùE* zb!cGe|{ ))L߫'g}P=Qk,h|7,C4Xk] TCBÂRu~c]vS{wHLn";Oi@vt#=5I_Y<bt3,;w&H>GA\!eg.p%k+<1by|O w0evCv &AFV!N)¨%8ʕMnOa崶3sB? ]c9n23( 2iE34J4L7c t^$1#T9aS;b\Al$VLK gdƌ03lj=ה+fD|j*YOOk -rU1mn+SSӕ",hUD,NK6#vYTl%Qmh¶IN?u`c-Yo;6w~?aT3?NBS6y _Nh7+sZ0Ҋ44+HPқӼTaA8 ;%qf4T2XQ37)?(TTFW`#M !%I Q"5 K{5[m; r#ԇ^vL:y' u h1g y. I{zl[d;ZdZ._ש3 +>PEg[Ξ.=s G)1S8"9?Krdf w?⤥Z U,'\²a0MlYMAv&Y{vJRrf9-Bv P4gd ]zG0AČ8Ĵ#uPKj 0vxg<>?~Rr4&\t'8'V'\b[z=ݍ0“Pv{J-7|E#J+q,Pξ{TBLFMfG)uOҸνpNٞpl[OݠUI@y䔵P{|ǔf G)W("y[bdkQ4nIH2I"Ov4ɂO^>uG"rj^He3Uq$|<>X\c4><`ei7סmSTp]d[Y0]GR7}8+}MO_ FрP8$ѹI%2PkPT٣"oMyb|K,_Sgi^"=-tbZ,;JbIgϼ.4ATiU.5"l 9"(o|)* L" -1> dW2fXR) `<9-]:9q}Hd -IS}k'F(\PE!$h+]XޥpXZU9h 3# -\a7=" fY4X(-.XN4S )JP>R;!>آz~"O(Xfh+Ca:M[nם܎(RW~Ah궋E񇮗qX</Om9zr85>YR\X\K v5bz[w@- ;= eDg5Gtm]oڞdAԃ!m֬T %& h'DVg/~dk~mxu繡`6|U>;s]rq*r8MUcY AR~ ||?SjWf=J4:E6VuEŁfEDĤ\'.E^zϋ2E+(22*p:< Ў%GPN̞R=l_@4Of -b0`-Tc&[6WS! 1U2H3+5W|f["TN 7JKr1rwv|{Պb\%ksD@2mLj:*k]vY${d~>HG/F -7-`k1#I￙Cn!9x}&4paMm+F[_[s[4)>F=[yqN1RAҚv I3zD!˲e.io5CFQ#ʛB&@OubS*@3}{@c}՜Oe?܅[^yWʶ{x `*jP輫+J2mCZk ֍` ߐE>gb&mh]1v VGsjl3rtO 6`RYma=A؇KYf L -bL1cǾW[fx>vĩRs$;-ӷ$}x;YPHǝu˯[Y53R3HbQ8ȃzHgSBAY%My< `X:y 3`FS\F(Wy[[ *  ˙@-J2,LNQ.y0Ps|ۯ*ɖ?2NЄafH3DZ"zs>ݕ؇.oATKܛ[c#6w7) |8ƀM_@ DZIK,ucv<"zכEQ1PuoI 3`9UO1&˪.a% a fZw tB^X;}%AͲ0@Z.7r'I5zlz =nM6ӎM-L}MXk覨ag{W7.Q'cFKSa;*S -~/~_ae01تfLyqOӟ |t:ݍx3_jD]VDs_Y{Ӑmc9:n$4#9w%/BhlڢO㬂ɒcB?ck Ъ-|ڧq -T䃃ɂN\w ñ$CWzBr8%NUGZ`XnCZI'5БWDi7&PBG#s qauqw'(jW]ř+ϵ!~ -}.͕ -dz6HE{ &zj@I=Pu>P"P EHWgeDExσh8ϛ~`Ffyh\&p4fyI$iLa4DOu>qX[$~_" Ja?m щc[-Žj~c,ˌ ?g~WQxG 2LoT`b9̂phi(3'@^wgt[J 4mEɔ y ܤ,-98,J<iCD3XF4 S=ˡd*%c<~.Ps?1c_P !>t wՏ.V` 4ke4/H(@EG߳3ҩ0ҲWO/ȏoQ"@I3:*ڋ*IGXi4&J8"8q5j>)wƤU(F0̍?*bC 4z*. n>ݘ\{Z Gעv`i>fq0~yl~ʤGG|n'j8 Uk 9%fT%Fooe,l==zWc!>U욾q ddAtjlV|΀'+uꅖ][hi]O\}HxRʊ󮸶\%TdcpQ&ڔ@A8jou:5摠\'ý7!L{8y耉Z +R|?FŖvT-git1d ^R؏{xyml/B_33#x/6@*c*J zgFkL${3*G*C|_JH9P""_`Dx:=*vhKEyuI+?]U;_ۯ3G׃OWLLaC$vsx/4 J p;` -DvhSL)bHJl9Sc1z2񾣃V6Ҟ}D=?vln̈=W$IҔyznyc6F3J}]( HL9irƲ - -@1|- %gM8"~ Ģ ,J6cN=k(%'Ȟ3%%1/ijI@ :UUÁ:VJ>p}Kw`.PRUd@٬nH3%a%*!ƫލJND.17Lo25z=$1Yʴ -0~ - -F-r%E i ӮOcScЖ3@k,(3g1Pg=ulFuoAgq)EQq,@22~|+uU L׭lqT"&-s#%3= $2%d+Ăp;+5j>ڍ_8vB;DCv"GYxS2nz~7dT>W\ A^9KR((5ms!Fít N GJVCOh‘MWD'b}qH8R9ҳl!Pf(p*{bqSDz>tn!))IqW;,0>Ǭr:0vTǧc0bCM 4>EEut 0ա=ߖ^L4Q폇*KZy!6^^VU^W}E'+p` Mn}G&PŨVL~c -zOrfd/?g:JfFWlWrstO "^:\s׋wJ?+l>gZ 9:PU rF{ȸxAaa{&9Ȫ }"1S @=3Vb!+dvw76Sʬ%7K<<¥G۟B -^ |dD`e-਼K2.h-?Ʀ¹8wθbdFG@  PFEZq13qƌ. 0V3:QbU_i"Aw*%%ZRbЋX|kinƫa4j!]@mR/H -D%El@,4!ߘT -M=V7)m~;n[L T\K3(1#!y٣~w(Il;*:Q oJ"GAdВVh)$Z]Boj7>3~5T؋jeV)>у$G^V~S]de`~"1o Qղ@=l?~TOdjW86 >Ad5vq>54P}q<ͦcSlѩID $ȐN1GzQu.KëCs{2rIvzqpbDMx~Cl`U >p@!ᵛ݌VZK6X+APRo!ad,%Ǒ\߻4}Q/ψgm?e@7V_!tƞ9Y1X XuN=>2_{ D^)pޠVe#2;̏+„ ^[ol>Ǹݸ6)$'L "v% A1, !Ka5A&cS{4:5 DenX;Q*Q,DžV6q昍hg>^Jz!IkS|Xo*cPg ) -GxsMK z9LUQ˽OXUC/PnrY^:S0SåvL:%Ѭ:8`2%!NŒ}ΩR J1pi/^ P5N{V!Z`Wv?Fc>iĆwf i ~8ň5.ŊИ䳵đ].1a0ӧ3#eDΘ*SI/o`b_~K"\zq 8A@s +>nxL2nfX:7߰8o5 (;OؒHh(vA$\)Џq>F%JGŽJ-XoLs=RY^)]A|v${3#cז<c lӟolwY^āC,L^jzfev)bN&[Ox#򫀛bR 7OܠWVp[&;2|˗0Ю~@Qif9WCL1g5=]rY{[z9QPUf+ǯ1uFT#G.#8"xLɋGAv*l#bW+Af}%GU9L"{k@\/*g2$Ӽ9uᢹs;nllUUfNՆ3 8(/lh/<.h,{]HY@~8&>`F!L{9 ʾzjD 9Pr<˭{ U{`ZE zN+D .e}tq;\<QVW΍&q.d`"BN-Ib0LW6qe9A돣CI= -#z^+V`U$?9PҗwHh&؁ %k^`iDkt́6?vԈ eOFN!/ODךj\a&­@.|urQ(/{Y(tf2rw@mJHwm>@ ޽ I_ Ss'& q4EX/O hwe,W<^y*#Y6&/& 8hֶBű%R<  AdK|QHVUv6ɫffQr^'*ȴC??oiF[>E| 4׹wLkMˌ XrG/LnlKRW6mfuT9bRP}&5hMT X@W\H<@rgˮ$% |j=Nk^4I||ΩPqO1 Hkng99n:*Ք]6B'pҤ^9Ӭ=*>jGWAz(QA*xqw,?x*w BxB sb9r(dG(l4iyCTY(*k}TLJ:4 7oZ zq>fakcVaZIxt]b0zu-kuJۤ(~WReQQ U"` Yĵ0chE3Cɴ-NLnU;_;Ag&ߧ f s9'P)}E^\ՇUNJi*LA׳R逸3o[|-V}Պ-Am{۾{Uv c3w[P+էȬrhm:~Y`Tf`PI[6f._m"wTǗ]_j|׆4K^6̩,9Jcm+=4Z4%w;e{j -UbTDH~3rǖT,<8 5}mɂ}g2C$3R u۩0Sܢ{UH]!W;,J\ T3L!tŌm$z%&iH,xPa0l B)4&sJ \>>Skov!>>wdzc3?hZ|X"u!$ 9x`խDE% e+V7L.;p043^kx_QM]AvXYF -EF -LMC#Mo}{^ni" -OQ٩o{v,3 ϩBa0;4hF*=.-EHQxbН.xTgfڸVFB4 &3 > "ZOOj M#_A_!h0斎 -ߒeo@e!EtsFf``-AU_gRU6ڈSڮ|8JŌTo>Բ;;t<sdtMĐJ+}sԕ}@ziL hUcQs}]Eб,bE-h{FI}!`7~: -d7T{?VgY9oʸ NXqn q}NWCgw -C`4B߀r34]4eXuV^}9μ+OY0rQ̟HS#B?t&TKj:zt(R.OôQ%bU3r嶥F]  tHUSФ#BcY!ɏZbEO j9.ʱ<5&zzƗ8**Bi1<a\CrM?)$cSԍ+ x_Hڛ@%4 ]ՈTY lզzEz ցt 1ݹѕ&.jC [{ktaӇ$"v~ J4R˴מ, *uEa} :E\~M9eEWȥLyfe;MM/k,a6w(* :&ꉯ'ǡNKt(J:-9q{!y&<;\Js?F25;4hXL -ʖ.I6]=}JRCFH{G"2U h0 3'lƁ'\sڱGR[4:>&䌥l'+ -VmoZE(KÈ-|$*9r~S_2iJEfvƹ8C>O|M,*>$lc#VE.#r7:uhN=N\}f`y>=^ƣ'2+9:u]PjuD<'aa9$Gt(CagN[Gn aBZɢ+)ɌL jJ6?-ON+Wk:Zh` %OE]1Jpk k<@rR霯#{ {,'ʙj!pȀn,Xg3BP#% 1FK$xUlf5h2KVh[L*}[9SQ 78p`eDF20Xޏ#ƺz0]X"8cVli |0QiC6ץ^i/ϣ$ >ϒN=Pe\-h$PP@?(ށ$৮D>Po-CKX]y3z"#/<6g~laA1w ЁĠՓDmްA}!)3bCάn`nJ̈́q0 }BZEJԚAqX}<ْlV<`k3;pTS!&V*1:b`%R4mD˓ 8ũ.~!FPD_;5!Rn`,3/HS!| $ʇ^0j=4XP\"Á5vrzE,vh>(w,UXPe`ZUpx7H}RHCBg㙬&H" ׫tm4M%-!9Md)@^8 -Dc4YP{n5plаhJ(׳$Zaʜ`.Rশ?\ c{8i4bAK3?HCM,g2Cwώ@יȇEδf#K@Kf((i mb # a] al( -hZ?WWa@,Mkm[mxeÐE*p7*;`@rvTZb"zh!G1*N@T=ӥ.P~6 Ӻf=4ƢDM{EjWE p +ˋIaQ ^Χr eCf ZEŦA3z hC9WeiC߅d?/"ѣ앫FC6D! @~TJK暪SX Q-DkA@.%#~*wNZ0V(f~_~ݤtD"mΫ_ 9^3>)=A(_)Bh-ܨr,7'~szjdQ UjzyFbS|f=ހ`ךa?|G,[%4CS-RS|,5Cݢ0E"@{Ź UUv6R5R.p?JpUj@A?|_'fu\|+ W Wo"qSyJUd>_g|@lWz(rgL0^NQP@cr/"a/08 T:ob#ei. -nrY{+20 i&w(vEkct$'j IwU^VAN8;oŰ|C#$dݎõ܍߇ï)N$ İ TO'@ƘF ;Kv:@__240vS##Ȓ(EltOTW⺐J9:yI}#V I '@>:[hMu&C^[H5lH׷2xBsՐ%m#A< 0ٿe,ONO%)5i7g Buh I6?LK](2^SYs=be<\u9| rpN'R%wyfyQ.:,rZ@;U#ۻo׋2ni\+))Y%i Q11aOj, 4ǀ] Brik\|-ԩBKKZ/: 17_{|B[L:"natnkRb -*EVP3N^:oĢp%|'+_w~>J:w_wYS1rVDRIM(ȫk~ H5j".Ը"#)IJh: CGu@vsﴠhwTb -mH'!+SC/wL?.^G6 -Ҕ i#سG{oԟȇEd@ uɾT9^؍1^6ZM/j5R| +6H;PYyp$¨P^S-H(<(oh,$-9EEEMKֵR[ŢĊ + -,Z v -E]&ẍ{4=uq枰 ~IgF0q$ϺMogb2ifuT]L^ d/_gD:h8)rg3ҝ MabRǚB!qR>HԦn驉t"q] iH8SaWBغM % qĹcqZ'jI)#]$b@_i0zl>Eg37ڷ-?[Ƀ>ve̯-^pBjb(˶DxԜf;M|SF{eol\de_woJKw5ֳ  ('i}}<5|nKsn`8Djbe^֖.GƜGJ$<[9Qc9J ٙDtvBHNrI)~}I"ꢪuJ0c۫/'g[]έKOR<p^Fx1m03?kƱ5<5㥻u@s|ZF-% 5:qRπ{g>ϫmk'[^$1ЯL , Ǒs!8Q<ԙ2-j-@,"R-F -+(ؑS4<7%CHt6mN));Ff)=;Ibس3)Y]8KM# 6돚7PVq'H&h8}45-D+\%m$)K`3N5WJU60 8Z j馛k"Y|Sh8zk-;'$Dbp.rFɷKV4;T( -#/Rey-Qǫ{I{W>=#+{IS=59f*tS\Uo)XsT_® y|i8MQͥrʡAkG+}||N!9nu:>M b3 ΥXEHM&H127T.%yvABŧx{y),bh,vII)RS)åBA|vO $蕍@6?t4$(k# qY*aTsFeʁ<):g7j%?[0@oi}gEpf`S>W:;Ga]'L0k<%?ߛ;F)Θ>_4Ƈ *2SM܂i52 m92wyVIQT"Cl{4z)!D*U.XэZâ5 M(I =[b+e_HKb>)*po$I5 ZR,-Ɗ[ Ix>kɑ{W#0/j՗D1}hj{6*7X1C–ljUL ߪTdokh^kܚt<>#%PqOa.` |IEJ~ iIpdP.dɀD=C n8 ~0>uJr3-3R$,gQIE)N76Ȣ!Xuxy{"X-횄$_q .)Fb6~!)"G\k8)RŽ'$9HIwCA -sc6g%s/|s{G2 :0T -g3Nm -}n50uSU|5/ p7+wk=A[{sbh2?6~u&F(+/eA=߃Gʡvk`]{ٲ^eC= t@) -4>:_!(/&[ -4D"Ek7R_SWucg:1#f#urʪDGU%+Jxc)Gg.8r)9 !8TiH:7OMn%8gX)"9?D1@OY1Mz-dڻu"k_n f!QWF5#f[;"qdR QBywNAos:uތk!9$s|s kL2/;GJ?D4H|߈@P e|c! - ʡ" ,mï"49zECM6ІxU<*RC*n m7AiG"~ؘS)j]==ǘ_ldgQ/^l^7FWUmh.R$lV hk!8ǡ*,xGd>D 3>M{.2Cޑ<F dX@ Kf@E*|A6)>\N}Q2E'o,)G86FTV9! ;m,iD,IUiZ%qS߹ ^纐=v7"K:I6Gr*@`f`:[E -u) -(jw[뇐kuD+$SiG(|L[m:O1(_P||r͂A4َ@葚*A0u^A 6Ӭģ=E[=*31(qz=PV-E .[X@8~5ppz<|f]Ipvk+)„9y(,YC&`QϸN(GS2RڄSL 8nEWBC]yآ W(A ץ UH˼s2S:!٢Wl(B*"T (ud%4 -߃!D.=T*$4"iv s#*,lCȵ]_7O|W'hT!8nlrd -"œv%$ayIEhj\ۈG,[0erqL\:n%X~<G\j6U110 -uc*llKF2.| PWhp"D[QpRPTzaiP{эNa{R8y`2Ra&o @HH%d m/,G=,pQXECog)MVBSDaO>{wP5@fZ(+ -=OkMZxgV] ,5UViz -Xaf؏ ~$ӗ o秀J?l9=hT -Ks6LaM6'jDAׇWב QY1G<{mB4\TYG9=xE&Vyv&9"JnPTwzuO K-J#(ੀ_&Z0ځ]6St/ű< mHI|vS,S ?sf֔ =_2?Ev)Whk._!i {(5lSb5\mavωe&h(%UaB*f,FRĕ)dٱJ\@TNfoWMfx 5|;L}}p*9(v̌ءLf0oTj.G3Ee2O/9<1uz豗cAGInl\$rO"K<6!'-CSB= im9H! 5O]"I8XR<̘0T~ o-SI! "WTuQzi[l6{.iϲR+:R[6C@?s _sJJ5$tBfj1n롂ɠ([ߡX % ;.avl]h):jLYjg?2~b˫^<84x#Z&ꅏM!m^楃VZUv3VfÉh'>ϥ8Q^0-&CV֪]dxp{}숀f&u)x{~':[+P޹!*,[a@zv쇄n ʻU@|} MT; WHFhESF;yviH+To2=.N=@t#uIֵ[G#% :]3¶מUr%hfpݗjlUCvFކJ|vXa )Y`zi@[$WÇ:Hhр8ڣYݟCkZb&8~FoOÙ7Gso__#eJ Y l;ϯX/Zo˪:k5,x̋* Ż~^ \ lvdvw -"#1Ym0)R7p9kc=W=-Sx$4EC -tJ@\$e|>לcylDǩa~ 0 -de= ATޛ\h 2SXWU;ҙOÛUGjGȽ9P~v(}0ּc~t m=/R +b C4ZàyT+p[#{9[c EsA -ܗ8O˶/]! UiIN͉]a X{{|3X6˾/=)fBhIW5->n}| -qlSoWJ : +Oo(sTldrqTpֻEH3(jyAE_`jð!VnvhD߾.ᘵ('"Wf4(REIHxH(2D}Z?JoETG(0ID[(&/N!Q_'*x!E5Y`F#6BHdCQdŖp~ lE2Cl`@ _~ȄAf-5~Ϳ?- P -E玆cEe?+vp -"VoqX ph!6ֹ}}M%2{k@;l-?]Ѡ-!Rt~Tr ">Qѹ TW q#ּP '.A1s {!^0FhP꼁?thhGV)cV@6+K2Sh]a)&@r>҃Khl7)!t>P Pʜq{X]eРOJ\۽@;}ѧ#8#e3v/癰cPWT /Amh;DA^3|˜F?^)s%{l8PN4_cka~m $^}bܗ#) <LΈW,#c6#%~4%aI pT$#s1 &_y-`77clr22\Wڳ;"het*T;yC$ԑ[1\%J}gkFK5}76R211ܡq"{(0Qo/mJ/\O,+8Bs֔= ]G@w$L0Br+ 8\jVP8HBQ$iȾwe],5_7K,BXYl+GAi@|Z^jHyuK'B.`6GMF+4z9)sƒ2*;pߙsr%dgE%HҮ0Av>9n$S$p¥#[ڨJf8]P\{ k#”SH؏p/AarquUCηv;tjwb\D7D<أJvXu|9z$م@[ ETzmt=#oܵZpG7kNzłA-1+w72;^_o[m/H' 5n8C 5x0ji^h%} Ǻ KhNwQ),2ba B:a]B!jjdŇ`+ FOlוm&" KU=ĸ?*0}l΄ v*Ը+sxpڥKm]'ȏhs[jU7S{g>#Mc-Sr=VՍee@DoVbz/̐#w+ǤNi?Ǡ*k1t) ˢEH8iB1GH} aYaQC#}~(0nPN%9.1mo|5M!\ -"=^Hpt@`׊c?ϲUe26ǯ= -yd\1h o"{2ўl '8>8XE,ј,\DCǂa~4l2E{I>%pI1?*y)n!#Bύ~?<7sDlR58 0=fp>;)đBĶw8]S5L u6W:unN9aL86VLIմ_NPreET[Z߰)yE<uF"L^/: kBjd=*enL enE[}p#89@*M!ۈ^TLlltk;$}oEb13а{TF_cnkU|9%42G4] ʢʇ&Х@cn|2In4GvНPb t|L]A[+`dV/%}u[6]ok; 7Fz0LW83:￯lA&E&Nb{ZԨqa+"rjKQ;4D0<0S];K},J( -{ rA*YƸNz. T֯3e)|8Bex`HjqJЭ̢v`q#{n~O+6]$]+5OrIW}( -yFe -yV -_oGEPBϺ *(˻{oH8ό<|ȇ;8 N?`rU [WCzXgk#U>PGIy}xx@[b΢ yuna[/ѫnJOp)SZ{}-h! 9G:c$p(>UjXsKl*gF.D)OHSj8Ԟ.x'q& dn*03A6J>A]8Njg1xGh/zEaB[B: -Uv51P]q`Y7AЍ{ׇr~ܗFsU tQՑB= YǮ7#),Fc}6tlTyWX@%X}6.,EUc !-@y5\yvOO+B1M|} -1]^32XY_0a(._S\HwV Rlʚq"w*!CK]Vvf*t -t?%F>B*]370| -QEro> 1YSrχ4~eg+Gm3ՔgD^EKK WG_cr#"UCA c$CE `8 +0={oVԂ>'7 ɚwCAIL3bG/F^,#^Z#ZsҮ t<|j(323zYcw!d_5E%CĠˤδ ^0zϻhEh{QqWk0"p*!e᳏'3bKl0Z}4gXG@g}1ܱBH//'W ]R I@[sܹZ G_>6ݛvÓy@ vhlbteT׎TsGa !nUwg&q2ᤰ_\| Q5_$KBW!+41# <މDxߨL5؄H#쁰/rmxWܽ3V2(#t^DL-J6çaZDDCpM^a+A10`a{Hl BC⽘dZbequ`ѮlȎ"ʥU @('+944q =jѰ{ݕq/,LIJdv(F)V{ٺ@R_:o3=HУlLV19gPLOa7YSGQE`,c1͚#=e7>Y'3͈ }d -hhp]ǔ06V5Dz= -<.Tȅ{ɤPē{cu\:0fE'XK?9 -q_9Ӭa$EV[$63T,q|65i-q8֐OD]u B<#* R/R< -Ci|.꫔ɯ#J=S%+OZo=B*6={KPGL^^Uª)v9GaEE r$gBuj݁1vP('B1z 0+'8 C'}ia˺Q} wԳ["߇ٲ Afj yX ޮH4?jڂ=E^"3iISȝE`g'rhS{;[{Wָ~E.&$[ ;s.`aY3ꟍy{Wb1TUZ$9" j"j2i-2 D`Po*zVھ}fn`Gn(HQ=TҦH(n.u[LKjb{P6hŐ*޽]Dhz)Af"'6Q5RK{~l-+%[@g,|II<5!gEn@0{)<ت3g'.5m+W"-X Щ} tϚUX3ZȠx*b/vL"1MT]~1k-[MV`L2B x&3x'`6#K}|2?n( D7+/f .{?2oP#bTZ->^IaeKXb"TywވЬ5|_ǻ~F! CnrHTXRƪB4nYl -$Nna ֲ .rp?Ce|` ͯ'|G,!)Lth. hv?n2L 1z t/g ;3*Əݛ(noܟoXVbo% -|.@do.u0rQ Gn@4)esK =/M b7-Csa YdرrS[a>Hָ=L*َFxܧb]ǔB؈-g&N~# LdOKLYnYLoy6zCn̮Llaf=r{Ӿ,~&"N+LO:M>Y'ǭ W$@ЁTW:8ng崟tF8YG*][aOJ't(.`6E;f~+#.;#{(LQ#F  -h(v%ya!+8Q /?_2DdR* -F]%{PRE"[kP ]@o<' *Jź2~@<&SztOqb,q}uׇp6[!݉$7tz;~{ž)IQA2 -uMJƩ^=f+t^[9p:!HF@-!{4)@TVQwD=F՘4<̬U)&Bˊ=2)#_peq%8>,:ngc\UTM#j.pIcytBe:o3C= -XĭV٫7L?2$:lBaZj3<<\$|ңGAP1ȁѥ-s{!=1H!E쵂8? f14LKlFC!+:f!}^-q1 S7KOIcb -ܚkc)3U1f -J.$h ?+ f$# -8D%۞Pdۜ!ԾVH2HɔjHL=CHۉ/$V#4uMQ#~è0!#INL(b)M ԷZdS!IYFMn}iәϨNyˆlj1]tU9Su AUTvG^m_S+*2ΩV!PESH(Cm.p{.=E`9:{/XQC$[誤0j#He]0hO, ϙRȖ%eMẀc2  ^EV A>$@қ.^<$DJݣp#dp8׆ zGU6`Bd$~uW؏ vk$Xs ݣ&28Q{:'!UY َghC_uZȅvfh ">WGLM|fj;u-^JgG_ 4 -o8pO7{btށ|4yi;įET~8.`P4sۑ&P`H5nFf6ђR:"ҍJP${zv);{a {yA%o1 'n`FXP!4Hi1Ë6*gjz)fN{I0K*9 |;~%γ{tښ=R1?K`5̦ m?L>0%?P󑈡oUC`ZMsP:(vE-V!IAFFqjIe -E[!^P!!RԾ(n"o9rOg֨5KU<>0n.&Rݢ|^pˏ1|# b$|npm}&[gRxJ3sj{(j6'4yF=-Dg6|}%Ec"Kwe b0OЃBr@^=4OfԎhpBLk:q4tq/zR+D"Ӱ?X4&ݷ#0YoֿL%7 G9%3no7(eRD~:{8jxf[Unօã% 6<32D9F -wdZҏ* =: bFtXTT֙dC?tR+P &EQȒNx@p/@Wi(̎Aqx]pFƽ M-Gk@ޛ=CIQdǂ3sqTwO`leR-60'l&] a'AQ3~n$r27Fi|l Fa~ -ſ`F٭WV %s"64fV̩qGEhh9<܏aRWGk=ELVf >0 ծFãj z>+@y1YIi*G/{kCT ^Ekž5`|ZߘwF1^Ŋ4T -+ %6 ~6 d|kc#DłGs0EoFoh zej+PD1`Y!f ţE=4=a4o|-G~>y>JKBW(i2L6ݺd!Dfbw*:^IKd( -HJ:n֟\\h[l|5 AIʅ`V.S.]ԍr ?#~Ӣ~_Aݎ`60k[r`vO88+/ܑ"א6 Lc ,!TZ1C-Aj&4$PW5UInT|ׄ=N,One٭%TWIJ2)+wױT 鋪6B&(p|zζ[r ݣqGPi5(*@U^~RdHuhʤڈ2bТ=Hyr/U˷`Ŭj -@8`bls%Ó ZgnPQL$i CCB_'t,NM";Uf{@ϣSEߋq7;Tט׸577wnV("cwmŠȿ$h V t%m)1V@SKPF -P6`J9C -y=*@||@ҝqTP+p>RtK/j4^gBnCݠz a Xtq8l^^& tϐ^N 6Axe:bR~XvmoG =j\zn@bp0mUzJPg δu~;-qʩf:aBupxG"꣛P$%FA,U;nTs_ApR j(f"Hk~R'aQn*ҞCq*3 B 5;/B#mxF 8 p0 UJ |t!<7Dq'9\)Bxu2MCQjEqO<0 n`6pSΐjx= Li)+7IP'}$jR#|{萆[q*#YNj]FzB7TVӰo c'6ՐCw [o[.lk#|*eRm?Dvsf^VQ.*% (!PbS>(pK"4Nʫٍ,K݆b(Hkh@7cx|Z?"89ݓ79Y9{4&lfV]\q+, -m*4cZ#H݉*_BLLNJK,*j+}p;Oa(x[*d&ӫܡxn `uY](W$cϻ, #Z{]F[r4mЎ|h$hC$^1ʁa5?"܄Y*դ7a~>#Rl`4D̓1wCic-1̱OtnMvMFe>1JG2YWS:3w q{*꯲? Ƿg7pAY4,G7uO@4/zuWv8mV=&[wKP ~hͪ@)V)mM h:&^H Vu?/<_|(Bp_ -Dx~-# FO0xVzEw/GPC{ÜG )#{THpwSWWEt`r֎v,|[ -P(v&sL79PfV 6z _'dP.yƄ`SH^OOXNxXI9HGObY`TeT@O;%{ {JV -bdg}9bënaR|]E˴ӛ^V8-,|X8[B <,8e.K:77f޷Aˎ*.p>eU4nљJpo2po15wJ :nR̠C`mFH ?8w+8@aJt3v[bFGܦ!h/0JC^9Izб=dQys+d> Www{7DCpyzc#Pޞ1C_v" -d`4HvkX4q_kR+z+,#PsD]g)EH݇ف퉵9 `maw3cjf2cm6IjͿ1>&\83,u!ɜyVJ?>?z8{z)s~`1gNs}o -PKS0#7(`zh%f{1P#?@}(|-L'|*R<o#ף8C"/7-MD6Žir5mj _]XbKZV@zw߿3DZ`ttSM#Z2GE}0͈yz -ggVX=WT$E'5|?dS-PVѠ*IR#dȹB}w3D'ʌ˥KB9ש̙X3Q'5+YqJChQ:F4vmeem'cn jU>?b{*nh̨m%J{!R/!_:_/w h{} -$FPLL^$ry)[Sk=aja\Oz#ie{@#ΘZٺg3#nvڤܣFi8ܽW IDMquj)! %f =aܭ7ܗ@\Trvݻ\|n%j!$r&ތqL t8|>Eup S[49gC;$i|o&G{6{,@FL)vʱFם%0M_PpZ.6ÁH'O8@qp8 T1jS f~R>?;t>([Wr b(VZisK1u<~/\O/F6v ((wٮ01IJ&Jdx,Ɂ8sz*l׿4"`h+( -Jkh=[ 0s]L?("!/V83o)eadžQP -;w GJz6V2_+4;DB&MEO+S5rЁךys:6%g%H͕cHoVb'{$=0ߗC R_~Ȅ3Uo~'O??W?߯~~OO~?'yu!~-Ì=/gJLxhPp D}R:q -qJHSD}Arorgy'i? -!)H 9?,U"FrWyv+Z GWUK -z aRd;W; ~G ݬ-/\MzۀY\[- } -paPi cfKJI`!У\MgM} l {86=iV7Z 4:8 LR@E p-&+{!SS3E}(rkIY-Mg=guJs_3B-I|t֍2@ܡڼp+LhSbeTP,4@/38'{WK:3'jst b;a!QA~+zOKs0tSn"a؇}NtT>L{{(!XW$d^M -qB}<6<(3J*-/ڛޤ6VKd(!RH%$ =>{P_o0%JMT$1^={p!F囯Ġrun E=IT`HCKM"t8pzf֭ˋI6H!ݣ|D7yVBM&Lܨi};f^1g|6ϭQP'I4L5N Uy 7ӣ呁Fư -{@ʅ\UZ1ĐypFZ &10c iv߭ 6%XPS "ˋ|C[NXDX(ƻcU3 -IA$:c'3{E -4[Rk,3 L[Fn+3C!9DFBO1V͸ 3]6M b]C(1%>(cǎ\ qi֪{8&7jn8=K^R/yi1i r+vEΤDmo"Q - h$A$0ӟ^q!5*җ2Dq[LWZ֡{a^T F ڞHCYi(H(hܫX#\XfqD  L1QVDhn_N`TA=~/Qэy_LzT$L߯RqqASY)NbaaR8r'0½Bj)}.ߴw7C'!QŠh6E|\uq.I_+tbI\{^Z(ԗ='hqu4O&V),9@1]zz_IʵX^hDOA كqGكgQD@ Iط3G犸)l Y*GlKWjUP \\SNP)[K:Ypcρ*]G##rK˞'H :RV{W2N]6q>V{V=t`3,홁 -wK,x.H⑓cˡؑIq3o$é$9̐$\xN$q@%)YyKlqHӍ |/45HIyS/?8}iU3T8ړtd1Wf{-᤮`A/cH˶3sd4w;+kH1tqjr`<=y a*@WjfvV;25:sMdBU"*%%8YMCx2!)E>2\`aCbJ _,W]J_V(E^ -yzX+/ذa`D)V(QPa?'5$Яk'Cuz ,XƑE\< sGB/"M/i*:d0Sk -1GǢWdGM{ĉB!궯<灒 &rvM~@z!Ůx،\UG -(]I5Nr%\+ps?e۲*jGࡹ9+Ure~xQ_/dļz&Q6@Yju+7`=΃@#{OBޏBj  h5 ~>jݨUL_DkLs(v"QQ0-34m*x?)HyG -6{-ESwH1D':׵uc=tLG ,Dg3}nH:Y'ka!Tl"G 4 ,`דceV5pmU;l5zI6=Z0ak[TcV^e-ftM7vY=س=4iXd'-m"]ԅ0G -u - zyм8>끬V8oA$Wz؎rx\{OJ,r)(|۔Q*"JkU[?>'R&@t[Oe{8aP50Yq@2m+M8΁pVٍAb{;'9qr]CVWDDPxDp0g%`k(N_bV YA -V;uTۘnFTo"w/Z*/1{ՔEA{.#G/{$1Q<;uf.g!bu1j;5DW@R< -M;Z"p"YHmU,Y71:Eɵ{Y} E)YQ!q :u067 8W^v3犲w aϾ@mp0>kApW/6Oa[u~'M1Y++!x%iK U"dF+rRמ) v={h=fԺ#j~kGb@+3a̙ Fl7ts4dQ}!oof*Hli544<{nVwjNŁ8Q1Qyo8 $dk -6q9tb ?o՜qH& -$ZdP&P3i\ÓSɑk'灒Xs -!\2/c -Ǥ*egDy5꽥sKHcB;X5emZ hwȈ&N; -kN JkEM-v/=z(z-=Xwpx#[_2x':DSF17kb3Z~v83{@qb_Z(\hjQqbM"5GyxG%D*S\J vn&T? -D5w[fIne']F桽0A'n Q-Cjj 0F!)Zדּ]c*B1 ʓb7\aߑl-U Ҧ `T@/k 2k@1,20R`y*o UWa]@>V$v˞iJh4 RVeQaD+ 6tCHWs?"dS[+UQl%Vwh :X9/>JzGƙ^F -3\Gs aԁhֺK*mĢkys;7uDMx-[@@zM#|G.C3T[%H)IJCUA@# yL1w`|vV:b+fLFxxZQmpׅ#V_ K[J̌H]W6),E|uWb"]~8 fB&X@^3.$ˉW&ST3E.tСф}f.6>ڽ@%]YdSπ3p󺒬WC3J`run},>f b~B#uQ{tˠ|\SI<"k.I7CK@À4xFL!QU]_g>M{s >CFu֗HH@E-1ljP`Zî:yp7X +`"h>OKCXsX1*6jucg;Lĥgľ\„vtn&e le߫M<BA}zo/hSL AYafrAirVk# à^]~hA8M/-?[y gpւK^|σI DH6uWm+ћB @-{x"Rf gJ-Hd,Z$d`V&S>]|_+ѐDњ*# B!Pg{ǟoW:m}-#،u\>tzX@]h ۅX#%6d( ]p@j}]}tqN,uVJW(/bd ~+KْE`Mh< ҥ_NnChi!@ |?`0qwjDBfMS>/zQv'imQ[آlͣ?fnV"xc`6 6 zhߣǸ?R@ -{^e L:t7%"'-l3Q6icU1 *Ӥ4ޗ8 -dU~HfAq,]\>Xl/i|ʜ_E"lU{#Dױ#3s7:nk}/OYs"VD54U Pp׫5 -1< ]ym#} w -:uq0~(cf%L7g6x٩ă.rE:qYZOx =O|U$/f^m|g_ UkGŕ~B/.aF62ß  g+AV1rG0̠blZo˙70Lv01AdE\_g}Kszϥ2 nIu&;D!tO&|/`\um ٢@aT/}o{X PNT֞iYEcpWvQUOōT즿ߡ,M`L;Ќñ/(xۼӶ8BstŒҮi$7DPK-*4K,N1#hs _&[(Dq5ŝ>~SDTvz[Ur&&>%2=_A@Aig`࠭'{CA֔ RO'ubh{Ҏ/>|jxY6呤Fr\\OϟQ`aq #;ӯzM_Ϙ̃[,DU23%bޙ[-M3+Ϳ}?k[볶Go`Tv /Bz80khwTU؄(D7G,e$DҌh|J{ 9Z0OK u=SVA 57bsv.kT%םnb#0<Є4B6oSR]ǥA>"¦`ɞnPw FZ:StցE[d9c2xI -ak-ɷŬ!u]r4s\`ʨPIO؆n@9̏LA0B~6/2եpW`O#"\:a+1 |tĞy-_ĜM:G}fyGDz\FwjHY'][\TA~˯7PZ[nB7-e| 0j Lze -mz p+9# -߻%E"re><0Z9@D$ktE{ 2(]֍]kn- XM w}+ZC?*%: a@ʹWnY<"՝޵ u2 ' aRhR۵rMvO2h* DN ⑈Af?`$BK"|w%:* cA媏t9ʷ흒߲ :D M~_x͍ȹqҚN"t<#S+Ƹ?;>=@߱+ - Q@n+ǹigO;OV_K}#|fTG*bP`K20lBPZMWzyZH+1sļ,jtн;舲"C-\w[TSB. 5 AX@zVe`I{G@> U2 ~\rzDHrtO1w -r*I*C `mƙbEbBjJ 7ӊ+zo12J,{u8r`sǙB&Ħf"xYdp#2; A q\ 51e=9S%n@Ne"U}U" PIi77X$"qI:Nҿ5:J4ɜ{K9^0G5#rLunK_'Y; t r뿟MYrس]ռ\P2~1Uf3,[\6۬xE ©5ؙ`e\w ~KM*MҜ-~_Y7e JT!ekιK7ÄɣvZ|+ -4u' -1XW+?K"% M,~XAP7MhUơ[䧛_LuM`HW߻͏TҹWx zD;6ܩyў& bX%~GPH) N&,U݉m2 -\ac`aL`HxgG -a\qzz,S("ɭ*9t(DX-փxTDTb|D5k4K)~3FIoɴmNfeW>lyR貫~DNLdR&5/:N -0+FԩsFJ)ֱ#+a'b b\ѿ$P= ݜ0^nq|raPCDT*"j/ODI Txk! ;^4@!3 ( $/?@jGc:<>N -g", -R$ }A -A߽i2s&?4hBjı6) cz bdX8 -[y,J/ -]к RmWJ4ê%H*E5{\M 5o[ t**7%r t|/er,8Am5UkC9r!wtk_6wߦY%8W9dV~_xV!GgXӁhNh.h4` \Mu>Z;(\"q_`yڐ4W0D(u !F~CZ3x#LR[,E(^,p?P L_sur#z+}El!sC?IJ;3U -W;EqGMMJ[ֶo$QzuRh*ï~ -NF%h? }m2;1%kXK -ttME$AS); qY35nI}ޣE -(#!@RB+~`/ݾYw@Әי?PgTh}{' BW0Q>j؅p(">& .{?:LB`G&0:-)0F?Э%:~p s$,ݗ4\S~0lh麵|E֧˾{PXZ=$㇕D8~h`rޣ N=Ubx,niN+&Da/ֆZ\G{ȫuAC !ZU`Iػ*MplvGQ6IY%baM\WM*VmIoJ:M&9ݡ|5~ ɜ2w^CHSiAM5Ȇ$QGvS#TY%iTu;h®-kb)!f`j]X_|Cƚ&xkE$tA0D@S ،'m'MÈb8x)im{wj?`Va;m xS7^-6RȔ}F8lo-qUL+zh\cw{Vp{3:2ZA,zH}G -CT?@񂄰 [ǫA5&fUᚹZ~׆ѫ5BYS_bW;Fd0e>!K"DzkI=}>xq& x9,(f/ͣ#A.Vāzs 47t|uLx23٧(,N"u$+cߡH3c*i`Mzf5b`8{;YJl8q- -xϤhĩBom9Mv5 O{|:9;> [oFs7DZPNWc poV{ `Hus WH,) Wp\<ીobd-+=gZf+qe -*9 -@ME[TJbg=xα> {칹k'1#R9?^Etre37~\~n>;YdFhkeDҳ(I;X맀Qm;rH&aL0美xI]s\dF>g*xXJX<;$ 0}quq+è {tl3gLlL5``EӋmQ=bKSe0ax -`n.fFҞ#;%Kgu7gʲ/yG:ay+}i6eo- 澊 6釢~('"*XE8 a6B=d}\0bE':,| bi/ |hkwu|/-5|T.ƂSF5FN&ͥ9&t8U]8ީ8M5fUڮ-IwY+`k=)>WJх^]lDB-+-vg%qrZF kಊ<0ХFPn nļ'".OfbIbv\$BR(PTVHK;E<,{Jwfsu*:`+մvIsa6' "N{fpJDoUUnׄuD֢&h3@Bf-a +$2%rnREO ^EcZQ{|ݘIǓgw0Й;Ύ% -p5=ien&:+"z+>%}gϓulLз$N _z55@$Y5R;"|$֚tYK_0Kz"Vd&h@O 9ցʡlĕ?z4_k'e;]}YS~XZD#NЊ_j!T+{RG"YgL6a -976ocJJm҃tp!8eP {+PO[['y )5,ƌpʆ3]|z0;Vr7p86mg2qw'$7ԨSMLjr2S2UχaloPمK'/@H!*\H(4 etBW;8`Hۧ1h-ܛ1a&,yr3$)S-fC0_Oᡇی`f]R_/%\LJvܐn WDa6I$'rKb߱84tEu^x,dM:CnmӬ0+8A+5RdQb_a_-#PϿ[dzh=uO cݨ(bR2׉gTCWS(#hX b{P~o9V,JfmӻUCg0j]y,ٿ`6(YOM$hRLmƄ爇h9Ff҅% -+Uu ~x?{Ci -P}n@`קw.:FS藯ZHȨԄTȤ-,ȧg;dG[HeqRBs_b(gl2/Tch\VC\t> :5Y;z*VJpoH1SA_L0r׾lر~̪A0+Lf_g3gȏA`s"rBΤeqέCwOYab %S؟[nbӰ6w(KAwȩ -(DZJ^  π$,'d<ߢ誐t;!>T;ԌU|N0kY?C^e$[ -NTخǚpX|> {ꑁ:ΙBooc_d˯կ0ꉨ #l  9 N -}bzO5 ꆭ8nk\w#x]>i}.au 3A,4Mm <+vAW\qvC:fu튌"~|h?*Eo\k[ (U Zv\ޫi N+w +H,kps]Ugl]g0lv7\,q6X"P "b)2CC;MSD{1 )Ͽgpq ckIef\A3Xp x#Hrt@6\:#b%́Ͽ 0OVGK֤7\7U!Lf0g˜dF 3vK[o9UK)ks[gIxn~%uKZ9Is2*Ld &SQ[Nx7e&;,eLjΩ 6wgnC45zuC˖&9';F 4~sg{uf-#hImy)i`rjّ uV<\6@xcRg8Y#IͿ-%5оõy{Qu\1 -J 1%O^¡B[ -"__&<}#Fd\fEn9.v"Ti`, IdAa`S7&qV * dNj/e1ML%"P#Uҟ[^)/K/th;5j\*"{¾/VrU?qA) jTɆ6Mէ#ՂR+=s Ik`Er^:ON{ddE?0tS<)sܯ:7`2;CKQi1q:Z,$#b]wƳgaTN]n Z_g4!- \mlɋ FHXZΥDT-B)iFIIX"HƳ -gwZ[ -I#\":>L >e,( `biٳ } NXL -ٍ! ׮Kwm"#xLH:W5tEFbGf#X,#G -t*>co:^:??# \Oڰѓb˘qX>8T_3|a4c-~;"`/5;Mԡ?׆mZdxO#tvIE/d`u(%լJ#\(+X#2(@yb&Qff@1I= N^47i;x}ڮBT ]fm{r`]SW鏈M]9I= $[{WR)ʘi{{ˑ,,b]:8"k_Ba3$>9.4l9jD}e|W6uE [էEZ!,?=Jm/ICrl@I)BOO+ߡ xP+?, X -0 -p;bX|#X8_݁3|*?&UQ:Zk88DD8:Rl֕Gf||G1Ԯjt)͹LI,%ᯠaGAA =6vκ &")VVZVU[`9,<8fɣa@~kpL(p= K)U0(bpJ^૵2s[Z(6?L!)kH-0#|3){[ӿ%jq޾DgQq䉼H^| |I4?J̓sE{*Q3jvY**u:ՅV]y %`XٮELA#Є.bD:4 8v &A -C&a@dj8d{x{'lP!T^7b#{Ƈ:T#[<ݽ }n:G 3 gpX 4e=zKaCIFl ~|K*C1s -FYPKm̶mD(Sr\ώ5@b1w@W]BÔ0_UGC*,;;+bMeH ;0)n]](^_5K>stream -*twܛکGVds 7oQ.M{0.D!1OyHG?cn6?3?gz+냞De+; 8Nkkr~B^٤^7IP d1#_ OޡW,`[ -d, -x)Ce3@!T3IasmeM?^PĞ']׃Ugaa{nk". -W"APm -#B]ǫF/÷_օj1VTxp~PٍD0ӷWZS%_)T4y_A ?uГ\US/b(:BLmSx`1wc0o{>Pύ\8N0/"Z˟^X: ^V`2~QBv="5g@&!TMZZƼ}zyi$~f4!nvzWB%KBd~ە -3+JGωTkCM3JEbS="1t zݡch]cY*gxٽ'tHcB7ݝHr{yd'uK_lFzU853B <ر1U^zOD ݄M)7dݞҜTU(W6rW;R{%5c)T$FB}ELDyFl5(t @0um-2['(Cr,m4`Fx71AX:M=y`й#B4ص-;h(}6_ :Pr A%N}>8A}SV.xy"\VW:WkE4h*d>*E=z:{>wz:s>*T !hS|$@FgPMOU!6Z@1VDHGѨH__-2]ѿL,jmHp.A][rN`{l1GB}FG7sfߥ#D_ndHEM} /gEaby -M 俨Wn&ZdxrT'NqcC#b\qqYl6P&M#!A)=uu8sՐKGSnzmѕ'_$ͨWfG09|$?trl Gh{=T$0b>sDftp Aj ;ԜOGA #Q_EJR{[)҉rP3&:3-0ػJGaZo+Rc:wVR>d 1Ns4:6#)PɁKt4E!#|S%IQ:r(L9S؇ř׋Uoq\2~l>d)rGL``x5iG֋v OѸ鳯y=d]9$ϑĢwm 3b|ECbASW9 v1Fq|(xք&3_0X֧1*4S3d7h\zPWœ NgG -7LX6%OBWos/ƞ`DZ DKX/ "CEn3aGD1k^OmY&3=xݪe9[~ 2L8AVZPGw쟾`E -Ҿ+G)I>C҄d'! Щ]puZ9 -#3hٞS["=U>|qƶX`GH)vu?' g>U0 -P<tl$Zu[`X -žK)iUV݉fDQI\SCI`ZJ(+JSZbPʣ[8~}PaTi!hpA'?woWOZ~w?O_?w?/'OIB?:& * fTӿ{*70J72DxU7$47Db#*T݃˞]0T(WPd#fNO#$P{(eea_U^0 GRۀM f03 -FQB W$jmlcĴAׂuky"m"uW"?UxSBzđhX5 PRGAS\zxS(4a_$GF9g> 9;! |hf& -1 Apqq]CXv ;g6 |(VZQT4d;{o_?)8C ԕ4L=8D8(͕[|4Ce(8wR~Hq{P9GXOÞzFM_GHVZAL\rlv'X?|fƻ\ UcZ厵"ҁ;>z¢H~^| zW]vW;pgD\u6ѣ3?!\VL tpE]S}gCcG -ԣ`81kM&aǃ|#=(і'T!zڛEl/&C- '#ڎ|?Zvl%L -RIG,ь+b Tx?H`Qt'+e{^kT1YwFFkѻ%W">e : HT.2j}ÚIЯT%>NVW{0$~1}IkHhEzNE0K68lpl>6 ynYQ`+yψ,%ȜO]/Nk1Tw_.ibEb3!`$LdJ:4NðR~ǁ - -)ӿ}%[+pB23t0:0 X$qZ۷1cz cۙ/p˞y-b2UrE H\Cx#NgѶ#'@!ӒTwP^!+Q^ه`@DB*AvOAL(ǖӆ( Вt GE˝t_/7({r<܃zN2h ںZ%u%b2|UBפ8HJWԛ;䀍࠽۠^Rz]_O 7F URG,3^F z$GXRϩ>wuO=uc݅G"Y*Uqٹ+zwj=/-Sv>޽ `wu`ڬjZ FZ:=cNܳF0$4z0Ppmiv%jƮR؈֝*Ey*y恠!NS5 Qkױ'ju?`NL4M'=uEWZЌ)8@zϰ; buUrx` Xm~ˏqpPQ+6j!+)0L鹰-[ޟ5gyl 8T <ÎӈMM17'#LDžtRAW[("eTQm:R< Ʊ*y\U!ֳ2~@k(We`j~"\*9ÈeCYB`+ܿw\ǷGkEhҌЕ$ -CAևFAv I*]MK1+U:C=0Sj6@fݽXg"\~[Q]=Pϒ=ݑq -.>ߣ!m$;cP'C;Y?c%W\CS>rgrx]zK1bf*IQ0t>}on+Fw)uY$hψMS5:~8:2LUeaPG -9bϯp#Zi#$ /7#W%\-vfl}iciX]mo7ߨ2(OamH kca!fu*6,ILebc}}+ 3&vtЂ}'KJ!q_VUOnЛO1@JZG\S"$~4Iiv!ZhD{U"]k_́4> 0A~ x~^ێl[s@pvMXC+PXopm:F.u:`3OL'ihԵo?ہ8=tfVėٻ}ٮy|+ ݩ<@d,n'B<'=D=&ߧ +xY)ј-HM -!4<0x>zzρ_!*$h ,LǢNZ5] ~~kaC(9yqa*iŒmT YYv~cޜ,T+mm ;qBe3Yմn"tʎES?QP1(s[v1 XZ$x#0( ؄FF||fEX% ET8Nb]AKأ}괢SRPէsZX2,<^rxtKZN-K9 \emZg%PĹ ^\WON*jz==o[ )ZYً\wĽ AUJ-X4nڝy;WDhs۴%HD -2Vܧ 5ӰZ}- -q`ÿ7n3Xrţxl|R)F.GU,9iNճ}LD^xd6I]WP2`Lk?4+=d$?xz1XTOEVFb@'tL%^aZ|"`#aˮs3X|A'E> <1X2NZ%E3Ѓ-6%V'\\Ջ8VZ 2tq-'}[ӢC*ĊT3]I.8?g - -(a~af-Z`x T̘A~$/bZ9<3DpLDZˀֺA'ԍ$Wsy<@;|L08&[wa~7zd**b$wrpApVT~]՞OcMņCSGH(?S6\,MYoa݋7_ #]7. #p9鲯Mh";I< -#F"|qMǺKcrb븧L؛0YQrrj/n$DJ=>YwԠh1f-қ}OV=bߞ ǁ~Fr뻠3?|z"4]E}fسGeQqLҺgğ]ܝ!e%J(8d;c¼!= ߚMf5E1z -"n[<1 4ilXd%"0 -,v*2y@H9{GS+F0P39">̄V:k@Ua028"O)v=gfESܯ;aЮ S0Ĕ 4u i#=2չ^Fjm?ގ{0GwG*{ k?:l=s<9æǥ+dL{'ٸXaV+M/v6k_HB@GĀD?6['Ns #z>#v-OY'.Uh*ȕ1"Ì#VZo93пUwo9`?̔If~ҤN3^5ܜ K182W\Vk)l&xGS͍Y?9^9^)913Usa5͉֔Nx֤,,NM#d@ʼD/^S||3ETk.-.Ռ) >)L3ʩDZ:5=9`J.:) $cܝ63 -Ov}3!E=Ag].*8|!XiǛ}|S68-ւd^`\ѓ#201; $rQb$FK;M8h@( : e,Lgk1/>Hg~?Ep%G,ɉخZ)& -zDBd:6-}iv᧓t{7}=aiJ:E8C][Wi2r?h !P rdBnJ 8cֆSG,\{'故*:8/z3CsS1b&?oxloȠr7! &H3{38(1 -C1^s(?DaS5]NA"Wx"{5no_k$4YUQMa -u<>*Dzsr{_H/pob6N8(&Έ=&=Y(EOgm!Qi!ja;a1qe@bdf`GɰpDOS!x5rgq<(MötKʘZI~J-ViAC? ֒ h[^,z;/ ]θYQO&aEƭ$H"Z yI:z{ME8`;FLA"VKWc-qլ.1?gE0m,^~kHaڞJydc+&k )w忋KSw €Z\މ׸Xn2UsxW<2~ \̡=NTJ;v o%ČJm &x*LH_6c< ׎jz[3rֽrv;:mp!HJ2> T!!N~1$1ylJ4G>Z,/ -TϭYRQ3;2?J=H53_(i]$+"+}*(:"SߊSID0)+4_}SCW">_^GCq1([u6(M>GEֲWE->T:Rᴞ ;ΥHE+)0򊘪|W~i䟟S[L #wȁ-R b[ru9/#JC|Qv,qkU'V3fI$s -0JVx5ƈ {<D?mOd뉼7y[C1=^ǫۉ -;.\Xxb́ =aQ1n&wLx]hJ3*%d#Ha.nȺW v~h9F1)Zf`"K 1F[5Wi_ V@;A?QgrVas{EO'6;7@}u4d"}!!zt($} -=_pJ\FZv&r"S@ ߻eaCvog:ᛤ$CVb `Wi;#pn$t ޷R S̤4^ }˾q;!+V@m8_ @i_4tftqB<jAs#19m?쀆i{׫JeER:J8ECNnlnA|%'f;Cm8C*) {FuQ3?Yש{26FO)~rL1,<`cMGo2])`z{m×?BR4#5%Ylao03i( `(bR#ҶXsoG勒:_hr_^sAtAB?uI7o# Tb{y F6})MA7Ld -y qSvT1;{JTWPKaxTԣA1u{e#"%vuƣ+@#=xw2ǖ*-[/n-K&.?M݂ң`ekGzCJ@h$ zׯZ0~J.IOם\!"1P:<X=x]#sȟ'm  #N0 !<("9z(eSᝑ?'@ewEPa Ň`Q!̯Xh2=б5O[f! vnAK~Li쮹^PFV!Q|֧WiW?EǶp!}l@vaEʼ_ѷ }x T&N TPDϠ;",8Tvrdu"ZùÆǵ#5ul" _ؙ*=Jz8יeD4^۪Cm7Z_^ FxAT)M2#}0VZM J~#@H!:Oň\|)7*j="q+Zb*,{^UPAŎH|_}}Oy᱐d&lh8Y[o,]q>Do^` )[QKx x] 0$M$[6WxGbگ2Ѫdmrx9$ћ-I9IaJncrGeX /+u졌Eh[v}G vCҟPTiC_3iSՏ(.m2'ՉTPq -a64iI;kSL|uör[#=>S>+PB (=WE1puE풇λ 5]XfUW{myֽ9U`Ag)>/v<0蹩- -]a'ĬI8w̼&;d@4EكN; -dւ+7\\͸70O=:ylfqXr*Yhz{+ZJ^m܂bF#DHWobKJv:q0*]x*7.k@ʊøT!t=+٥`5*#RƞpR"a5yrhI";ͻC{eb>tܫvޟ|뭈8ѡ9*y^_grM[gF֝Ej(/#HYWA#P! -뿜I k3\CuXǛ~<#8=2 uyBSؤG|UYQ3),F;% - bI -$5.xNsy;Hַ?Z:wxm0u*1b D!k Go@Z%aIi^)'g/T0jKHSQ&^,õy@("/c9eI趿6Ӕ - Cu]/v ,۶ D}Acɯu[Ž-_065[`ۈDЫF\;Bz kG"9IFP.v>y8[Bᥬ%ƴ:m٤Df^ʁD#hqu+f\zR}#be; >>Թ]g:EɽqAmI[ b) } -7%"L`z1,tHʿ{kP}vd鎰ɱ/6v _jY@c*U -[:+&Ҙ9cU27 #kQzP -z}~v~Ω/urc~v䶰i{F>>6"%\ެMS]-RRjL25N6f؄DOrdD~]b_sQ$3!i9 -B~(0:dq{;*}',*bw<Bgtx]? Y!2$oJ-a (tu(%X_[i)r]Օb1f?$t E2|U.dw-^pM-]Ґhá]ЪA+%L:q -um~P/wΞV+Dh5EHdہfu#+UB9"xU2(gz7mGP03Lm-k,I - ^ !<2R%3eMc.xĞ\Tk64Y1R| X;ZfQE~[u=-wP@OOB-!ֵNQ/ZIiO?m⬗xxX}KMKVgυg(("?2i/h/UnI+ -AP߅^E2 doAvFPm gF ݏ}+ei0cΎ ul*&FHQF)<ަ~[$.hfd5?^0*yP\mSMMoA ݵn $fR!@T42&>DP?g <3M8=v]5}rIEssW!{l]Bv|δkLJ#y3M!@4MtC{3ʄ[!U t?T=I6*:h29]|Pi\ -RvrA-ͤ1;s*}Q@eo DN7;Vze/ .i=ĭaK4h釪,!]@_d`V ˆr -vf;[/o/lN19 %>h4tAH@EZrk\Q˙ --%+pPb|NIB8, -= ơu.dUY};qÒ0ZteUǯLB/dzt*`'gă8G94$-۪vׂAynzrHnJj(t:/O]__c 6GMWFMB;-|ęYo28 q/' P99v`;g "7*-ej Q |q+Xj0Cz -[ hQmy4=JlWڂϴdꦶa[@IT0\R5Oݿc'UoUH!?/-ɻD+̢B޳ mGgg:ko&T@G^)ubR֭X4^zRpc8gH42Y`_ c8QbR>Hz:UDs-rO+ '6N0! uO{jcviz:ݙUAE-4K~z\Y-Nů[c}*a3;Ǐi\uk}vĚ.lwA&Zm/kU10SUrE". yuG_pl55q9Hu`1$>I]ŽϜGCd$, }m>0^~ -Ɉ}Lr]0ګg{(\ET#~Ǫ)5LI@<ż z>(MM 2=.@&aS-Ughρs`]4"ycJ?M86tS';]O,zEh0`~GvGD :4ik"!5|TyŃ\3}O[Ȉ(`d#_nJ0- lS8(o''%ż[ۇ VJ>4!0:`I8L$eRNQOMiE ^}r [u($bG|oa~A{VͼP5rBiϼ/ ߚ<*\J1>U -`lk`P2T:rt5܇ eI+WTģP3S,{bXboM -Y Q)GS?ο/2>͊j peqM!l@G^Bc-a$3=I83OpP߁|݂@v27e+َ2^AqII`jW 8rnX+_zqkˠ6z &#мK- =뮏v Dr uSB7[SZO [oZ&02ݴ&gP&?>S6(Qy^R5{ܥrnI/(%awbK>4ܖpK]tR} 2x~9_պ'l qʋ(s:HJD+zɳ̏1edd Vz4co5vZ, څן~q~_Ŵ&(2JPj%9uj=%RgZH{S?[h`S  ˳*U_آ Z¨g}T9@_&ZHԋp9MBIzzVPM_t~/t,hQ)d XiAݳ̺S*س󠖥=Q*%ԸCM)(H$Z%ºlQ 7t0HHR!>@^?"P\Nz+)S}/l۹b̞3D?8{]H#ݓ汬"`}k[aRȆ7y":&LM2=o:{mPA7CN(R9Wx nc%}i3UqiΤ:]ca\i#qEUMc4"v]VAi>#N <##YoP[p{t.u!!*}(zA y|`3+EAfiRwQ緈C-.Nϼ3 ŸJ!{8JYx>G4ʇ%wٛv0j0/0vUZR>CVQ-8G- !h?HGX߆2Qoc9 -ov92уExWDG/=(KD^,,MgRny4hUz Mh.Gsm%sct! -Aq7,´kG,/?M -njzwލ}L 7ʕ!քV"p;![ek^F!"Go )Ub;aU4,3t ޵4^c\(yvf++j*L(p3Lzex0/]vѡZ]g;b͊K$*݇nj$zBzhPy zɍ! -SbKhP8 cɱkG ʵ!!#oP p2Cb:&Lȁl $P~D{8-:v7*O#fF[7"DLuH -p@@4cSAjѫG@_(2-Dfs~n6CB\GXȗo*5ԫzgq RBx&{?"W5r'WE}pg0{w'Q{ -|?] -}h\ajCy|-Pxkv["pidbZv쵬&I`uፒ:߼pZcIJFPp@?7]VJ:8aJÔZxR5G0'eDPi+{$ - L}^ߣ| ])bOxȱ k).{-fF+}.B -["x*=)󏷈Yd~{Xy)Qhi fԕE^:rEd(?hiZZ?p C-( Xϼ ;&eO=fK(;YO! d8&Nr \ -9:6!Ucٚh^"z<#a)C5b~ ~|5*MG3#G1ϒDY!h)8V,{l3U"! ^R~'e!` -0)w0=i \C<.Ŧ)=3&f:`p8ILA 5߉e{nDwG e[4[ߗN]a)c-z)h4xpNwzwuD!͊+n7Ɓ 8+}wvK[dǤʰz7 2:GֆUu"d%NDDU6Oю|2Y=B94i)F>~:ze)w(IߥhEIf%4#sK2,]ej_~UU\s[mJ|$?* 0=m\,Ƭ 3Z£iG:' -v0硛 ؉:!6OI傶,"Ԯ"[E2̀I8G ;Դڨ=l7#H^@pj{/79HJ= *b+J%2HZ`t}wBKp0LSF76^Wz'!EKfOa_Q8mËwɡN~&w4x.*zK]'paXH(H}qӱQ{Fя522ݝzu W?Dpr18Zty& {إ-bx> ֡BNd w<ɇC3_Mr>9 -}+Fv^Vw!1lL>PGwY8 =S*G(bU@QMm֮4@$!Y\SA0#E-4}7`5n[kMbN+E苀d tJ>DA7=4JE?E,0a a>9THk`Xΐ:y?oǹ~b]skpLYh>Dc1{~o~g򘘼^VB΍P == 9DYr'"ծCEq~_{q5㦦X2[D_DOGSfHuH.;&K $fBؾ])X%V!Ez~xìi;{TNcBp~zKj+ڬq{*#^ǁU|5ֹ4K8~}LG98^/J$lW^Sfj=ыFqT微<KȖ/>v$% /jv(0f8?0DGE;PD"(a4꾣+VFpE삥MGk=):;'3 b's хg mdzH2J;4Z^]jS̝('B.}=Yϰ=oG؃(ÐV -$HMba9N_C/.E%qx^j\ -GI_V -"a_:̱(+1-fA/j !TT@4wnD'tLBĕ!|pʽlQ.Xu_ZWB>G<t*É&[V2]}@nў%(>=D⹂.=T`rb*BcSIu'cV}dbNlۋm*}~Da)u(5|G/_/???~????:~ UpVDJvMwpDL ^ -'" o'ۡ.Ѳc{=g?-"@0AsQ;}7P+^m~!k8:5!6:ܢxөTiQaP%(%('ƜKXgST1 \[E-X|b3<\Ec)BDǘZ-@iђSXwE.d7 c,$|1m>y\v旿>,SK(9I<;{pQdž8:dC ez hz63so@ DiaNbskݟP3Y7K^nC"vygzKS`@aSzݯ-/9/"npQ:_>'8(ުD2cZa5goϷPˡQ-Ӻ :jIoBX/AE1Q5;Itp&} 8tA̲ -^ܗ{2S: fi焐o8<+ZuзxQv0 =@'혿rg[_4_l45 }u()kcE|>.vN7cPLDLr]ǀ+: +P}e9 F?8o+)PoI!W Il -/xbGD5N^pth[ y{eJ)|#!`%Bq4i,hc:KฎdurH/aSQaV5L`Xr>Z4* %64ԝ1+} @Rv~sKw07u!=,½~4T V =(9ƽ -aגBcb휲];$8vȅTjP+c[*a&\7_vv~Uʗa:5O={?"^c=GPmR}H VPьHM Z_y;Qe$ƃ_GSka7u ^@PϤ6 ^/EC ܶ -(_c\@?9W g9|ն$[*I2+x~([4EQ_6q40cZ̽). lFwwR-ElBCui'*ظaEXA06 -V瞿Ab{(̬2B*^+F5 jh(j:#"\41ݙCYyU-z& ]"/eΔlҺSaNB6D0AFDG1}0`A`}j:FbfNx+XC\HUf~׉V5PT jQwX 9}}by?gӴuҿ>#EHzb=$RCJK@p*I:uw/ih{_Njfttʼnt YcC!²|=MJPm`!!"ʰCDLBce'X "sSEri3[STLavwx2JNȞ0mqa!:[d3ertQ(=*9߁90~?<56dX2?DD>ca -Ȅ'T eZ"fS.²MlwB@ S@juJɍy^ǽx⋅WFd!J Nl E)dsxܠ*Y,&.br+,Y;7=p*7$ cB",B 3m#*{Fh$ͥ # J{ co~ qvSe`{DvQJe{/U sœ0d-ax01Ȏx[ v`1Fl/WL̑*S\: {_(I_sր]`lӰ|!%~#,"Hk;0pQl7X)ƻ[mkZ族v~K)hxf_@K+teoe Lnr{l7Oc^-p;.o'%tBȍICQ}"EK8 3Yy\K1brnzȕ}S4xȩ*G={rR/@,Z<4T |5佴aӰ]`>V6´GouWt!>FN.ۘġ1+bW/AH J;$u Cpk]W⏾QI XhkeƳx8Ts"h3fAuy)CTҋ5>BnB";bHQyN+&"푊FQ&[]vAvFV{2>D–z/iƁ$s<\p=+5/!K~ t=\5C_dQBolTf6i:uD´'٪+>˃dxkUtzt4V9!pz:-fj=G'*<,:_r(cE&SoI*gQW;W|3"Gp(1"9_kO1V꾐='c#x1$",ԗ0@vP~2!fy+dЍg(.%(!p~ -;cw"Ɯ! `:P~Nkt(vLqv` ibaą HQ⾔}XMy@*O%d$-NB ]4z׽˶:Qk $WJVqBx飬l*̭޺(\4S`xjloޥhޣSgE 5t$LQGWW;wL*X`b{;n8/12o{f{xNթejetf5(@#Vq$Ap" -/l Gxq,$kf?0yګ}%gExQFrG:fjha`c(_IhBGP<]C0*!8% !̼`e)~\@!'X|0J;.XʈqhKgh_YGQRtcD;_E2(*֕̅NF'[hQ>-¡ЎwZXїz?<㖯#!dtc3on},eY -,H&Jl%ZX%щKr(ެ F3(Yi֠U aYELb=r$o -tðg9*~/`t/EI S?Ck -e(Oz]7Ȉ)~T,4HYЗ'꠺Pj uw;/¯~"ěF~mX.P 5jb"" -ԯ|0Pʑ-'b),+BnqvsVWf[؅y:iʱ3sr`*8J5,~L~Ɏɏ`gAa% B)Yx~1QTF1"nV -3Kb5S@RowbD[vB:9 -4%|PH!@s?FXG(ɞQ 6ܿgtP9G)_'@a`O=ǘ) 53~TdE|LIB~a[@ǥ>#pp -^ϱSG[IƏ8ΎfM2OvbdIspj0So W)~`~=tk)0]ik+:`_{NVUh$AA{.aD-\lLTQm)I)nCkhPh\Qk'x!B; -3jJ`<5l -"IA`ɁCM9{+{dT ]XþJ! -)`*@4Tb7{ĩضv"=Apwgُ_~vK}PߢZ!R) lnZaZH.^i:Pz^2Ic(W5z]?{!ʂtaH C1.]ٮ}wБ+ϔqApx7ʑaw1Zk$^ɀF欃ãxwDd,=5x gw)|$,mkGr,[.~\Fe1*x)K.uڄ c -f e4Ps q. -A%KLM;{nllj+݆vJZΰ$؜uF#r\4KmFƍkB\)`*luV$_/(Jr e)0G@š1DQ3Xim@Ǿ!S1K¬a(/,^|b+@DɆ ڀd~8 t;`Sʈ5TUšN*zrw'+h?5÷2Ф5ɚi}]jqhůlچa7Bdh-% -,bӿD)za5zAibJ9Z&QI "P gm__g \<&rkiw])~o_g{hcW>K2~B<njs{=?Pv;V}]A[R -2zq)|" 2+ԓlyC4!(30]Au+F[飈ٜL`aoXRƗ1Ѽ"|D Š=zD؄ 9nBV%(=^vPYjC63%\҂Q0,G^]5L'6,1!޲WmͲ FvXDOy| M~d&kXޗO릛ɤ<P'(^zg2ޠ5M>_T#S~[iV#%ӔDCLbcdzWY`$IݓP:`8[dqХ~Å{ u*dW -Gct>rFqT 2=Jt 0W&8DhsD(K N.u#þG=u~&.N(R=2x!,0n /'9Q-*^kׇ5BS*(Oؖ{iCތ,nD@64&!oʏg?Fd&rxN5ʗ=k 98CUe\s*TT &fwN&E53]ͪ oeۖ /IRs%zd٩VR{wCd:e&Jٟl{7vFl=Ĝ̦cLϯ -k@;%ZXb:[7Qj2ɓX>YfI'bRd ƿ_nHȱdZh?_ -. -wu$d"<*b"mOG{i']F|itqFX~D"Zy~)!q8rDo+i<[xsd`) -Dh{){2eYn3֚X֣/UCо9 ۘ)/W]Qz`hyFQ*IhGhVe5v}aCYZnpe6g~+%v -]aG42#Ś96bg ֛^iHY/4`ɻe;AﰓV#LB#7q}@V`:9Mgʁ"w`[vt:naa;u k?L ̅rE{FFQH:f%[j7C Z,*<(3Dbdgҭo(Yeؓ>I߻l(VXV1K F[ /QUkf}x -[ЧQ0yp#]g@N.%dyYONtw6;TXDHub{e5rڟgD~$;mbgo_jbWڇ:ܥVee/th3Di=.H"0&| -4!m 7PʗBK@K+zgTp@w( -VvOu}~^h P C=X t\$5?Tّ<{c难uoڑLK+%+~ܦfE Q( vg"u|,BrZQ/_Ɛ_Vp#q[*5턝-v!9J2RX|oC BYnM0%N̖4*?bÂRp|ο*9+X?Ҍ -G@ ^e f{M|@o׳%&o3m7P#O!L=%ʲo44ʷ팔Ych5ru mO @d۳U7MtG9 rV ؘv'dVmN9/Nj!ڼWm$Z茶ݕ<_& -jvWt!lݫv0дM.XGv&^6o v9tӥlBje/dfXGVG,NEU,kL5t -Հ.֥Wq ]0~"V+8%H+nobՅ[<cQ@; wp(x%AT_Y:WQ|dt,c wʅQn\xŬgZ9wH͕ Ja#2rSw Z-ACTUBL&Á"w Ls^BnĿԠ5+(1\v'UGc ǨݨF+)TN򑹽3H ##@BQmiݬJ1J6ػ՚054Dg-0q]jeHTrѠOfpΘ**l3pqA]Wh/ӑ~,c)]Q`Ԩ}f.PhJ?qe_nWB냄DP{rUN4hq9at8ų,gQߢ8#Ik#(bО225r<įH6x2atu/l$ -u -@ߠ KMLUf 49/߼(]K3)7{4hmPDltZPި DmV6;𴁲ww]u{@Tj>`6 ;xmQ_dAbĕ\i٠=ZkO -n|},Nt8d~L17USS$+*Xm鐶Sбj~DB~bY>}a~:<}{oK < qYXFa$,8D7D"P̑Ȥ+8~OjK -;#ђg*D}x;kJ#¸`&t|g:Ya' F<],/'{]EuI/S^+O:Ar#fD68$Dc8܇G( nز>k|iH>0oIiC7)A+OM*Kb1ӔOn]p_{*y ڥ0wĽe6C_uVZ>5Vdk@fU4hSx71sU.u ؾU!4XS8HAu\yplw[I@K9 ΔPQX8Wq4!#5^D 8Rem ˣ Ry!!z[pċϕ; -$VuQ -3NDjOOW- 4~ ɤJȑNlF -Sh ⲗB\nG@b;<5 **t.{Lkt0AeZפjh!EU;F4y+Gdq/m6 -mPdFRai*|QC a*gf"JR,[-?],E_`W]`/J#K p1{!BE6L4b?K6/%ð 9b W$:`vl_T[,Ag]6#5HTpgF9]Rq_b0^z;Yo2s*ra4fM /;7 iQ-IJ2DŽO 6^>~g:x]AaɵJ\F jD͐."Bw&wJuEW:[mI" Ʊ装-ADf;<<}g+1 (&H.EF}@j Ke$uAj/V:68hfFË3M|7ʝV2LΔ0TZA5^D*J ihD-R%]r^|1<2 -{|썖а޿"8Pn0% &ptek5H;[([ mz]Gٚ\`O%K6l0RS)&SWW'uMNPjLWJVB -$@m $71w -DL;je]!"pm &Ijܼ A !)h"Q53!|5QhSn^huu=Vw.dwiTڊJ.~"m 7ȲFU ,: v#ZFPkЏO(d|xeӞRX>*_#!72Q] vO{wqAL?@x1͵B$[xYQ RK--I Bl7.} Kq䁋Gv׽BhEӟK_oP?|6&%CqO]LϪO0}vآ(OI} I6*Bom\L)ҋUX.*D~0YD 0˄Ж=A;d`IQy=ǂ%lP*^G#bw!"@o/:NV^Gq*Iw/>BhMgGZl%}/N `x W),n"͢z [st_$F&$kDZB)QvV"-<ǜXzwY=ZbC#8{@3澡*3 {$:gB+>Sя+w/\4.L - -%bNY:ތE*oP3CYK8D;j;Š;춁c!LjX3ҊM(!'2 aC93MW1wMs|T}냸ɖs#=_@Źه2pH}1WxZL!_,RjfYB@PRPn8ׇrbCy /7:*hpD -QOč/C鷃.$۟C:+|tF|oapwf -=1=6*W xwUo}ll/ B`2i#*a{[$# !|ؙ$95y4I±YbJr ?N"-4w QԈ$AuȒU|cy Lࡓ0]K(곊oӪHPqmLD ]Y -;%]!;Mqubڔ/\P6+p}pSM%GFkS#t]9Q/drk-]a/M~?n(@3[ -cCJlݍw0J"l9\r(ڑm&?JqXh\DWQ"5oϨhEE#(>iWuOPƍK\ -3?u֢$4[r4}L*~7䗋b: d[lDj\ -?gJ"8=vN~OP͆gQT>͆e!"!(qQ5dA׸;3_#o~a7,B:ƒ?=G3#lT=&7Z4E.d5ZYS;Ʊ8vkt8)T A7f߬"\p><{خ`"ǁ\ le~ﺤb$G`GU*n.@\,hWم}@dve]]x3IGcq[cZ4ς]6,k:Ы3Ll<,D]8~*1>WJJ6 ; -*ڄ T"5+, \1HPz -۱aDˊ)q|(N0!A1yl|6㠤;0nayulIӟՎHXz!K_`*C 1nNsf65\O˹Q9h3 -|HfufNĪw^hP@4(.rawƾ8]jY[L(_0*`"iF\ϷG Q(B -'x)c-ݲ `k;T+hUs"]cKYϣJ;XWLkW s@@U{i!9pOV|Ɂ~xdt,{ ݩ9Ir1 06LlpŚ @eE+ -P/G-/רBlZOz -Y?|B-= |ܷz g9 |a/r!~5٧hURaoqytP6盂3۳&csޘ7*mB!`fe !swwDT\{_߆)?C ~*9!^QaqiݿOG̨ovi̔C$_TuBYW13U1κ~ ų`вxWk_C'I0s -2 -Pݣ-|*Nex'X#X!(6Uv['l`shASƃv\g@ v:J TdcՌ~a;c:Ɛ׻fWCq6>c)=qn]gq 9? -*xqAWo">|!qgf9w@_+;̝dZ*X֥*p00TڟⱰt4$߮ NsG 04 >2GP?Cƿ1:J3B$Hs_kq[$Ћ/oK;d0_Zyd_ed ~V{+:XYtNz;JL+㽁Y;^r 3Fm!=!bv(-ypxÆlU㞝8)]9i458G%Edo~j0@8Y }j~qrk_$Lj=RU0x.YF9ļ2p=dਯڄabuK`o⨊ l'p`6B`#Zg~=sB35= akN@Y;yoFлQ{DHX+ک;q. +:xَo$9~K$frDCđlwT]klsv9*:BڑاX:\$,LcN$@j=k:N p`S%B7AwQX|}*\:!*,"/{v:(Lc\aX=a`xI#blae$r!x)KmtL䔦x7\whz;03Nِ$4nD7gY߻/*&(Q"懀f'͠!A}URy,7iuPpBWteg<>Q+z %*+,2vƧvl*U7"T b UXm U)waX8E{;|+!]Q!Y諞a^T6hIY-4W-$,@=b?TZfhPͧjm)@@&_C#bPY@oQS,qke;0 .(x9shǞqB8As}O:2 (x^DLy[Ebzbwz,5WzA"I9(3XSk t@ ĝ$Xor[MNG4W%9;]?~*_? + -Lz{ml̥E az;6brAXY -eI;A+6TioVJIһ}oLl|ATyg?YH;,|5 205T%0$jjOV *}҇jEMT#FDfwjED -rڥ}OߣP!̪F!koɅ\F '~M+ȆQ5dIZ5lﴸFPuU)8Jxo&E -o!"%+}Rj,@NQ-:Gw06@G|Y{fO JQ|"y{Q} mﬢbunŭ +6i3i ѝA@\:oi£ -[؛;QšgA~QTS=l7 5XuG>0_(q(o&TT(Ʉe9SqdGfFNF85Gjǰ_1CPBd!e"ؽ+K -HY]vYZwE bZ q-3z->\3q@^r+TǠE_ -TNd@j!95(+yW>|JS|ϔ m4?#+pOvJp"SYlm!SˇVi>ao4E(eo62*:gk:!O>gdzl_[Gn~>z -a؁)`zvpHWÑ1DTD;D]f0άv8Q0OV)+mU׹ #kPΥb{=V RKLlq7~y$+jhS a+<>8o'Q.iGuD>Z(w=xkΫAROF{+Cx0b<$`S-j(-tz0,jo 1yb#v$j -˔9# -]-=9Ng -<G,s` Į[DBdo&St.P#cdbUxZE{QMh $ ۓP&^=>xӀ4żִt#7˚ً~?Ѭ{\T~`Ebc#E[YPWkOt žj#1<-N -eJ"0֊ pTD͸gCKz 'Cs9QaeE.umĢ0H4hsXY^[B )e21a@!jTX0DL*DzjAM8xRI 5iCsUq Jw:\:)v##C{z [l︴1DEh_Vzea#*iգ.SEN1):( 1Gh A -=u@Uk^tPu)\.mϜ@Da鞣8џYq F@ -:T-ПW0z=j Dez6zbyEПll?B)ן27_ы/_O_OwO??_w"?z ?_TL [BCI<6JۄOj`OYxO zX}Wc_@.\uFp":e[Έi#innU*i.8*Ԅq9 Z΃RXJXPZہ֦\ 6l:K2P~PRTw6gY{DF-mhAHH.Dj+}-E3GNPvJPT]#o|`V˯Tbg yq `?VEdR<8{?Ah2a` kXqeWNT@i<\̤D\3@W3 BckѡK8Lh*KBzl㴽Fz`y>n@T(M: ׳U1!JIX?ُ줥:׸|b.X#g;vу3jRt _] #7bMg⢎LX^]I+b 4Zځy *A<.@~+VҘP3umO7J;s -r2`fS VdXEĥsJ>wpw߻0}I䕘aM_)Pyz c^I5Bt"ǟ҈#fEq%셹Ҹ,V1HãP{UmDbqu)! -rNϲ#|To“+=5;wRսgjNT\UC.te3."{P*VV9kuVQ~c L.box0{gÏ\bHvU#MFԟf8wW0#уǝҔq 9DD*Пs$V`PZ+ Q'x"! ޔ_wkgLM& - uki}f0pDD[i9AoK0-YEiʐ(IJ/bDq gȰE'NfbQ~Li B{Q/߳ A#ȱP-*68={D\^/Mf`v"%iEEx*d ˊ`x KRZX#b+&PENpN~? -zSx2F>-QUn v5Bq$ZP'Yjz2;}\{Ew3,\ۓ6<2$kO)нΪ\XݐmAQ& pK-#&L@>5Qd?=ɰH\w6jzIrpjڋ$sCU=TEa93/;I"hRY6>'Ttx=F *u!EaM,˹<izQӝ -`e5L4eBXTl' fAQ-a,4V7XUH;i?„=_,-2Mp z򸷰k(,apdx9I Q,A<B_ É@Y*SLN4{:2G -{n ]}"Rqa^)?6(Ŋ@)!V]DF% RC@:k s߆K1^jz~E0=uztX OmG7,RMҦ.dlSl4a8PbLeq)v@}SwOx*p P}K'gLK؆Fknb\:_edPT;ҲS{#XH`1B?T[`'91V2jg>*0eTMT$-o*:κHLfhLYO^FiuCHuv'6^sN, -ʄ* CAlo툀"*lI1¯㞅 &xwJXf:qM" YeQgbK8ݨekԜN'7)HtI /S6!DT8fd`|"iT z'ⱉ@Ojh(ݢ@O;1CA\Y]h˧vQYcnl -,.n +7"n@hqLq^Xr]F~$X=A^!cÔw+i[JEZAٞ5!hZ}DMrD^t7kbP4@d!ڜW0Tf}Me.&m^ԁ]G4Bӡ¬MۓQ*ׁ6t~H#%:4-Nm]WWgrjU$qP zT8zqԮdj(kj,yޮj Thld&'d^eaۀߜIx AÁiv}4S8ϜQk`I'@)"p - #md@w麬Mޓ|fٓ)P.xx <0bƜ&_g. ?9&svu_Eћg½U,.J~De~sӡ2V!Y.4E !8EHOj`=8 〵elqMJzamSKC߿_MWV'ȘNds&fN+4vEQ9}ĐqIȊ6I/3=;ӍzvP03W4Y)jnAY1;F88ǥ(MB[;^_Ta|`wHDvs"A=pS2s\)jÛ2O~φPKg<C{WfP\431|э`zyL}W  ="i>=pΧ26F^0:$ G70F^WthU6ٹYODxȦΩNn+Hڭ}*ٽe4s7+oaJi]`ёO,X >N7١vJK![r1wL|@H~~y\/v{>ĕJnw9 -`R_d K>^P,^ aJɩMS^H1]%/;{:WŊB}Zo9\a9-%LE@̓(G0g+ zиN՝@эczy}$'~ <d#ߎ5cx'ㇺ=>p"fNǩcӼ'%yi饱si=ݐ"7'ቸ*#Л䀗.a߮Vٞ Txs>e`yq*-(W0°Qe -\iK6b~UO1W[ 52qK$~}`&Pzg+da\ksÕw -ZEC,pyi 4cݛO~~F_ePeh?(zx 9fs}"aUÕdGrK߯\U_s۝_w0Dbβqi K Oy!a;"JxaW™]^r_pW;/$+}}ɞ*l>f]J]fzÁHofE]? --<w x(>gOBO‹"4}s_F"b0Z'WbI~CğȝCǁ@_G}}xe=.R3?=(a\:SuN@ꧫ> Mٔ[BMsuRd[Du+ E~ZlwY  +C!gJi;CnN4$cA&CER;Ut/a7yez K`G#B$ڛq"Bt8d%B?n}|Gݜ\$vN'#85.Csv(̒$]EV١>\0LŪ -}yM7.ޥ vHk@[?(qWajJѣ79BPsFXyabObψ @P/@HXHRâJe]+c8tYI])׾-ws[]BB{P -Pv>G3 yH cLY,0mGŽ/l J |ө;@"/(W!МX;]|=aW1/Bo-yhVS,f`B?–EԵh9dhײެS~X7 #%,`['4/l^.m L@,pMv>)~=T2TPvĥgľ]-9=s8،D<+u!!-MپČ0A鲙3Y!:IZ;=3[+1&båwV->#A %'E.%ءOo5M*tmdmgǎaH -ɲE&}lřMbX,i8*W߻ >XQ^F0a f*j=%0t/nII 5N*" F03ЄFUFP3,hfEb$-@I 0h#'1-}ixA%2C5ThN>H.BZr]J60 ns_)fȩ?\ºl>zCr <ΈvlfI~EW -`- '󮎷CR<9p87-4RvAP RdIFӛw=JOid+utr[N ;m,&)=`=Ͼ1ȦWkI'eCht X2 C#L`WՅv\ktD/I<0çt'Rr+vj}yF:[xɇ -3ɗ(qa1t<ҩZKZAu%e E>sxICӡ蚤kp.fkCH gL:,Q1C}xF Vcvo*! .dD.Y pҏalމB8Ahpd2ղDH5+f0Lo'8/ʎI5B0?#*XGtfFۨ - 6WݤgXS/H b<"0U*qms}3<,H_mWN!Z ե~KӅi8V=4Gb=mDϡ,\󾞾W CE!%2}^-nb:t!= -w!ԬpUy'ثiى +ϬGzM?-ms; j'Yj;Y2(ͭ&8-XGzf2,r<b6"isb>A8cwFfhwFcͣ6\qKdԊՙOi(:"%p"v^m$b< N0w -ȠM&ϰ69USH׮׆gϖJ7i$S蛡:kq~ tN[?e -e $-l)wBv$jWٶv;K&i X{ݟ m[Mתڼǒ dǑٓ3hty8RQQT\Z3<mC\s[os _t('ZpA_ͼU;!BBOD)cx=tV%%_YWlE -l*V@Db@9;@_8ؗ"\JT@!c)EXV.` G~茞D`lEW xia@,45OHn(Qə^m- yxҤ|z?MKJ*8``kŜ:N9T /Cxi΄(ae3Z&p"ߑ~O^ivƫR! fĝMenמ4)BH:+&D](auUD-it@4䖕g;,z)0AEge}F%xK=5ds㟂ۓv+ p~U%9}3+uO{'pP{8tV k'㑴.ziyi'Es5r ;T*"ac}~,L4b:q)n f^9ۦ9@eڤŚ6+`|ٿnJiT;̠ፁ |W< -B doN*0`RqIg@R'wJ! -ݏ6S|$wni1b-<{!G-ډԷ-SMย\3˂B*_8n -Iryvj#X0*t埫^,M*laR_ZaīxGXݘ1H&Lʋ*.x6M_AbSh:v,i^y } 4knTDWk;8HW ROS<uNLV.I#mtVDE O/>LPRoRs~M|C|kP-n}Oڱyn+l3a-:.PnZ2O&uG%:nGUC"z,c,jaf݌ݺhq1z! -wAH]%pc@Cx 6fkA3͗ }~/Ke$D^@)ø'~nyH4--s7zc~PW7",5 6%*+RNaX ،NLj{0= ?Lԟ#,>$mYM" F‘h?BP f,jܖEycCZEt-^./!!jd{n@"Tu(#ZLulINK͉PA\ -16G *R ߠCw8e<$Z3KR]QW$J'H_!Y#;x)Hb-AWy$8fk倢EsA0{ -EPknMjЍ10^98Vė2r{nS#{KBuQ#TB.b^g#w_]m>*\G$⊫ 韓Tb[᩿Yxy.JVZ#wmx:;%&S"3 Fn CTDo3..pwңr+;a͆0pܒ3-z{)n'tg#9"6e=EQj|ˋ]kx?-ɪ6B Yʠ6Z+@ٝɦ\j:+)Vde(5ZCoЫhɕOdF?34tT&H+%V¥%FL{oX̝;A4W&vʫtܹG,G;(w"}hw  -(ܿfh[Tf$hT-$q'|1^4G Y -m7E1@t*zj_EJ.NQ@CX ،ljqC3tuEV <_,&fM(yDڵh)Hx6”wK;dk# +#|!d~Op7ّUX+i^GߓS M?c:JS]beҕhne'ϹȰ<FfDž{#[$. 4HLy*|U"ݿK] -Aim=Th4w멷8MY͑^qE =:rI9zP9ZׯEײ'@mp*4n3;3,K(xmp`s跐[Oמ>wב: q8>; -=Po$3efN- ]c(cUcCYJh[Eɇ”jDՄfԜM)ȿE'/ 5*H+։z=;˩Xmk(ߘ *JӨ%h@_:P ;bH?gFx(aRz c8#vێI:7a*nZHP$]ըҭ %?R O4|J|/ňtРW?s;0:@$& dLxWp -!+ -D҆Ҕ(`٩UeÐ]| ֙͝^zjfʹ]Nke( |g,?؄jݥ=lPgczFד]b C/L!HX~``cK5V,w˙|O @fsC2=EZ󊹦Kϊ]\J|ێb>S CPa[V4@* Z^p%_JjIg\R5V>shCab9w/_Sɔrg%(ߘԀ>ٷ^H~۾+?HۨjOt yl38{\ ()&BwZ#:]X>af򀭇oq/T.ȍ\\%+(}3dhEOF{<@hUTbjBk%%OYWO7TUȨ -ɦ]͵ e -*g.6_-yz8E{˖b}]C*#eLiN %J[Jd%:I@ 0>I磌߮9'iq<G|̉"(ObV.HDXǚ">]"rd=0n>/` dθ1yNe$æтJ;ͨ 3s+bxeoa\7ݷZO+P@:Vԯ=m[k-/ V(߿[΀D›` G܇6emS$xb[CﱝHxTcG4& !T$mi=з% 3#L8et"$ULwaXao_Q&qY40EuYaʊ9C:{"D*rBNjňl{Ye5U *qYv*72z_6BuJHQ?#r-r!o]cOK-Hg=`.0|=0rF2c_ a*C;%  3f}xLS"4bEٛ>%ڷ XSt"wӕrBH1"s9Suո3zM2cN}!^P>vd.CD9; ;O*給Sd˽k0=*/%ĺ؝h|=X"4iY[XA W+Ez^TotlT 'JV@AgD/UJ߮ ?ˇMm#֝#ui,)yue0WzIJI/Z6n+9"(J 1k>!nfXn!5ա(pq@np#4A!:^ n/N,/@GLVlչWZ\Kuƫ^C) }FZЭlSi*YC|M"C)P_'mK -lK<6Šis˯Οol%O-E|DBuӢ8 0:NߡֹqR58h᩺@n!PI% HgK7slYL&Dzk[6JwͿm5lC).tsU)MwIם!3L-JgZ2eȈTzOh=Uv\`̹t@Ypz"&eah𣳾 D#DZ>C,0"Es\z%Z,UL:&ǖRYxPUvTu#aۮkw" Y;TkQg0L:y?"@7iS>a3DAi>Ң&4kCe4?#dγꢀ9>Zr1QtL3:Zo# C V=% L糬4uR PX|  /F@i2[mj]5qgTB:Uh|Mܧ&]Y|W -7 |Aݝr}S߸*rE4S̕[Cd{٤A;6HQ~nO~eڐ1@bToW|>40|X'D@ c[bK23r<+ f9ȝ@ k<v\XEo +r.Iҿ?Ӥ wP"0~{/9Pk9`ݒۊ{V_4ixodfde4W)YsY@Ќ&N<`_>, XHhgbQ"CF󉜠Ī!Rt֕GӋ`V\wFBÓ:6 - TAVVS Faѽ9 -V)STEwW p>*c>GFv'q'ns8 ?];-"*k];Yϗ)o;v㹺E;Z%Qɚ5ǽA%eD[{1:Ւ,zIki:Z^<|M"Ԋ~04Uu5Qq-) wWgp, -]!U5y*f_>~oGT܆&d( ۙ68v3cRĢt {T"##2 ^lS񧆬Y-[/?4P-c " O4.vץ犲<¶~Pl0|$*O~hAaܶ;3l 쓋 -5.oMnd1vbr6wP,4a;Y6,(`>j/oȐ MOSRKoݔwς]_ZRT4i?qH)ls2[G |B~ӂJ pޯsݫZSagp7P~nak;#(t>{fiD7"6>yFe4(Z@|D\g]/fۚ98 1l:YNV.ތqW?jkQѥ4)gM w[=α_ww}nw1%GCh,i93_Pn6ryAbEm+cᛂW3'rh  Ϛ#="(Z֙N=n~VpӃ Lс$VVxǽ9In@*Iڑ4llbVrt ->\׻-3e+Q{Ρ.F Z񏌪&$vƮ}b؄+¦hVóD))v@-qos*hHSJ(xť_@q&SìkI6D'2N hO(}b0Xes5)YayYT?WmՍ75g7#Os0r. dO"IVkBF!2T=Jzn!κt>@X/((*.Œd C{;Х: ip#БͯrW[Z1fHQ49|క[) Gg< kэׁý2ԫQX4xflpϾHr Qùd0z?G /gLi>µx7|JPӳ [7~.?T7̑#. NY\\|F iʙ?h^’Rzy 791|Z!o3~^ILsg>>gG4W1E$sOK+ʔK{"vr /Aб~1W#B}Dsr)d\/1F(ZujiR*I4(^o$jf|l+B^9Lx]#Ȳ(֐S$T+#w*vE& U w!Ժ[_AVSP(75%?8]9LA; PGs)3{C^L ӽ z&uO -nm:! ӭI†;Pl OkȈ?&V%4 _#BDd伍ϓ"ï?^#_ --Y;?%9Guɓ~F蠍jm: _n7LuLI!RԩoI|$5Hm bo4Љ?A3Qqg{o:qd}-cnjb+=v9 -yM#C)4{|j?dd$!;(BpQ颱żQ\lօe٤T\,,`XЁ f=]{}ATX!JM(}FS/#J?kF>W|ed(~p\5{`;Z\@D ;XɖA!SM>*`u%C *\e7X :z=q88݈wDgTW]M{WA);3}{؈Y+۵Tm~R=Qv_=q繐dHǗq.h1?= ZĠw --e;{Rn(?/~#ͮе437·orm!&l;ϲMgT -´a~F|%Ґh8t\@!_Wcx(1)X3(em`KUH(p`>uO& -/1Ngӣ>ꉪ 1'm|6[US@p aJzS$n[85 V@/KTbY[}{>2fܔbOS -p3VA~Fb"Җ@p[o5:N -a:GO1TcJQw# wb{\ͼ6O%z 2.tX{,?=1?UꆑfJlg/̔ѶB!?sLÁVZe_Q_@ed2X!V\-_# P?i[` "3~]|) ԯ,SC@8\PROV`FN)ByCq|H[>~=a60$!?$)ܑvπVĢFʺf43HM\jGfz0F}8s,q0oѷ#MTbaɾr3qW_jljlGxg#7|p_-<aUWv^;lW\.ìiΠ^b[傅n/1Q8&tZ8E%˖z, -0žn-QBEd't}Rvw)2|Wߚ`@ύwE,k+Sگ^1vuPD_9x;:CnѸl[)aw Ys쑜Q B=!ZFL۠ˁm ,b~.*wln?S _3E-!C͍uGȑoP d!wl8> LTQRz񢤂 &H= ڊF'y3Z5H0ߵ^~J~0ԹMw5,eg e%x+z|7)< H6.ew9zq?m}m-n-:﯏ "%~?=k7ݯ'-o??w??O?:.Fs(p`3IX+3])1g%/gw?Z/0_uDc=@?K.:n4N]z'L%avb&P}#(Z=b'U8=!-JQuH((&VSkER.lk;D1Q2w)7NMM2%;pڈvΧgGmpI' 7ʯUebsዏڕ -+$BPH 9;g`ְ~͕PR,7́A"j) E-ͯ -P1Ia (Qs+XieW4_Xwm)"< D{kn[~ewTNV@cuJ{`\Aߋ*F:\"agTx!"$X-2(>@nePG:#ڈ)H@+P:=WPlPLr l+Pˊf<ǿb:҂kgk`6߶\c*جbqJQF}'0DO%?ybU]TePs~M1{%aE:2 y^Iqrsv'X{%"5@AY U %Akdި@Oq@ab;;Sa=ӮN?yˈx #C{tݾ63qHfX֪RݓY~Eߧp)nx U3AO.Ja@C+]#dӹ=9*PP+m0Piv#+="BKp C(]足j<'Sψ(X@@#s޳ö'%BB014_ r4ͅ/^GN4)&W8xk0{CsZia!b*@SDm[T>'@C:ρYg=_@ErSSں8uovDς>bI XW-U}aw5)U)6\*,_n@(͈Oe]4<-$`._ª)>%ǤJ8ǝKjUf(gQIb %csqyF@UrUT٭ ^hwŸ54 /'`ov|U  | !ET26L>{-*ZEAQODSn}kEtv@/OOԠwU"\ߕ7שTN,[zNhl3 'h-b1EzE;TQW::G q*ՔM~lJ ax')j,f r&;+'Jbe~||hѭT(1>. "EZ o ' (p&8}"TMQڡNS[u}BހJrȥ{6+锇-~Zڽ 8}{SNVʾҲjL x[{xsӶ鐊^~M!ӎ&JZlcq%n o/\vS-St.s1.) HqSmIoxEnvwVӤ;8WNV4&4'_(`i@A1n?p &-ÊӹUu[p mX' AcQe>t.x]\Sܗ99H*Mf&ѵY+6e z󸏰{DVтk64,DaDε4#Ub+q -qԛXD}cɏd! &!#˯ɄkmHmCzZ9ߤ- \&t( 6CvtqH1 Tk|'Վe@8h_Ki=S:.c<gψCUAL%[*C\C=hƙVU2; 6:W擌E!RmV~HR4V-^!u<~(䏴,44.qQ$DɦG OK|Nu[h1: (S.#Nttuav2w7ܧm=S-˵ -WIvp OEwmSPKe?[z8D@]HJwT#Ƚ -endstream endobj 25 0 obj <>stream -1zxPns_mo3 :a0l -2 cS/mI݊I"I81_-8bZwtIJ`1^SɿFMذHU ɜ[Atr%" @:h[ChRg`*b/ޱ y43gS[gs~Cv'~]25= ۷4o TY!"rvI\IÁZQgpCMgRcpd@TZr+BgeI-Ts׏'bqEKS|}?DY."Vu¿-fPj}#}qlbt+8\GTk(} -Hmzº'Z -X$lѫ(?E0.C6|N=L+#Ou83&JXRI/Ng .g6X -"7⌄mRb/h -̲D%ra=6ty j΂0X5YX)NLg+{grZXkgBqq!8z묝h@(e6lµDjH/òjhKΏ(xVz% @M{ - oW=Ru|=7{dL Lc(*pkX3Qo/JHdN*ț6ەvz܆*@G5=ݠE?EWd`9(Gϰp*4 i X@|DU~V˥*:%{qECҶ1-ޙɕ21Uլ &+gZugQkVoydi@;as^ZhW'G/u̿SN`z]dVN'#<%ͧխ~}xB g ]S;|:ҥ@~>U愍Ԕ,ODU\"b3€]18 zj4|O mϡ#HܱƼ>x}QPQD/k@EW W \e;XJ3_~uAa:1)=k*Hx=:UUo:zeدQN#iDhq,+W d{bRdrn9")?`~R _JsΘ+cDtkt4'E^›/TU[%o]ũg 1))+wCnkeqwHIUۆg,kBQD7})"{SaC|3GdjX+%24c=ɶ޷ܺ!qA7dAW9B#s:#]UZ9QjE`ԕ ~=yO$ZN^OGn emni/; ,GoĆ|]lmN0]נnNHLΑgjDtNT3~oTM݈PH̫tsl7.a܀F[S0SOSuhG3<]CQ.;āhI~y>=FtN)rK( _:'v~nooʚ#?/3Q p H)ak5#uX[dBX=oGhr -W_8+@@_6 cvG,ʋq2+XJ%n_wZ1';93=P BؑsG:zˀH΍(6-]u,uqٲvkP2bPW0~q^뽗>^~+ahhɇRzֿ{אSkq?,5C LT?з@ -U$HעN|~G$ދMQ<C%B\'T|lF[]UqV7* -@ e8v~`P[jHmsS@ZMdL!KWCzUy?0&K,xLsRW_QnG9ޟ4DF5|{% "P -W  ܩ@0T%sUާ$џy eiTnr C`΁tQȊ=ka<*n@\g=^'k;k?]o㯧;a )wyP#.=U\rE3XO#]lS(CM?\1DhL?b Ɠ _|Gk%,`g*_1Z5<${1UT/Kj1Na,:6jC-M݊MN0p=UsN?V7X\&{&w#-ZG2T.H%zw'>* s3yoU -T36{rx)[]ܷH )=BLF|V!w0BN7Zg!!5F@`EnŘQR{H7RUd01||9řC%uhXێ8ʵψ}ıB?h%@P -Am1F]Ӹ3]vӵ[a[{>W䌲,(*Q@NjtAH;o"XN5^bGX$-Eg=֛kʷ24|O/E\Y,?Hfeϻ=cakQ b-΅QK6?t0c2d7LwB3NP/p#@ˀ#D x&f`MZu|E!=B(&(/dxx@axin K`u>`<ݍzDq ;}Op:x2/ XH˯P@{3mkNiYE"*k{ϰ#z, 4Ȍ(1EF?EםU=xbVr!1nvj/B PQ-_`!_ Y#(Tn jcT60>>LU~Mx6ʌq"q6QWs>3̨kokVv̤hN[@A i _V[+=y -Dm^7 ^}hBIˀQV%@) /.)z?M[šk¤6KC^_S"c|U =';8| n;xzT=$D2̃MW0l-+ᑉ1r,H]y? ^?(7eh -H)'- }i>hit~j81B*&{"ɯ1_rsKNXaϺya;}[XWօ((Ş&,4da!^!cbBqb!Bz@)ݴ*=`Yz|&\QVE]##ҡ:.MZP;(QFWO{Ѹ0v*ӗ' z\v2G}CZU^4 K~ TzF]6-'Uޚaxd)y-ʈB$!+b %Gկ1/3KZq[6Zb LGNF9OO.ܞ.+z\.(}ڜ6 utIa^  Ѩ4b@/+DXA:W\a`v̓Կzl*FFDyDў ̙9BL!*j5W$Kى`%❂ ƿ v)@L:Lzj:߻Aص3?E"hZT>K3fvL~§ ߣj,x<ѽp>tU?(Aѷ6ݵ^SZwtp̡HduTsnSۄKrܻҕ|L~مEF7IQPwǦjՓ}Gh"?u0DVz:_Ah$箭 ݑ{*7|:G0WV{~sqڶ [ ەAX ,e@SB=\<8䒘W|Ce,+s[{1h(d+XI-Γ)(@Tl*Ƒ]gĄl9;`w j &]z+ܯO3$tD;:b@aĢN -N<΢-؜#ڗ/7b+=4#+2v*&xu6wN- U -}YPZ V(ʱ E I3yw+1hDFk f߆;9zp L(|k+j)fO6޴/RR8W}[ޟWM w{x|KƩveq#*nH2z$WPy7 Hy.;Jf]cogH -K - -c;7 -Sk˘,l|!{ڐ - ˽+~:4T雌+󜪑;qWnV %aJLiI[ԍxlݰ'TX\Z8"l۹Y<.OQ܆tcӈ4ۮ -._ JpLW@ۄ9T^V ~.F+@ƺRi ޅdۙs0%'7^PVoԝƈ$SK۵Z,2jU7n(VbxVf5ԧpN_n.$V'#: EmT@ ՠԯ^m۹Q#/-FVq= -Ll:D.&scx`gny@ft!Y@l"oПh -78*poc0Ai-m|"&= ׎Мv*1VJ/ rYw)gs-ҺD\-SoFWNr"!A*&~8lt Po ,kEMO3]C(Xh aVaGqx"7K;6F4ix1JbOu Q|kZ-]pub -Qި~;>5;1)<-USkJODI"=uuˢ8":ru+m|)/ u";gyFB|DXяM>ۃQZkik1?нCHҟ侂0%"Yj'I辶 '>MO+isq_߭t|Q;p]ܡ["tJ}¦a*QB|girkQ4ъ4Gduo綝Bs|ZAgVc!ƐFa|'U`{[M1ZxU4T]}c,98жh5~Q”̡79]X)12Z9"_&0祈xT F17CG0\xt @h՗O+3 F70b'-t0ʄ:ãI.OeeN*cһE{rc{*,*2 V;г((n F$#Æ -co7tMDT 2)İ+T Ek+qiؗ3Αמ݂ D50IAmO<ϊ߮h ("w̬V"`&-#UmQt6dq\w}c@iqRѺѻ}[tgT3X[Y9n5C{) - {G^ -֓Gw>R0MSASW󠿗i@g!Bh }jl"G SNCQQlF^~gm>Op 9Ĺ^ Jesي)GҊI]lN4(|Eq6](X*1*ח $ddkTT< >15Sl =jy̌5[T*!@\PD5PPNE<2-q:ޡ_pG@~ݮz.fj2VaDa7UI\CtZ\BLuŞ0ݬO>l{La^;YtdOXuqL`hxmGDA;L)/h[:DߔQf -Q2@o&t]&F*[(dL-?ֈ%uV{Sjk03;~OV,?+fg4^ŀUC5 -FĭDraE71J-2!ۣ wY"aVzP2GġV뱳Se,"ȸF 8mYxZs@FI x4zU`)UP,M:7hg*"TMv7%j"r:ò С$ZJ>kȬuǶ0zPu1ЉPςw ]J+z'Ft2<#0t2"F [ |Zҍـa݂Sت,SS_7ᛄQR&^ɞF&A@E- Gk*e. CcnzD]9 !q{VGD:Oa bG~"&R)Zm -HCRYB`B! o<V+\>Wt2p/N`;YJI5֯q5*=2l# Bq?k++2c -|< 0&@@؍膼R*b [bwc1*_a54׎jV -}F΄OILv-XlHΣi1<Z4*R[ש  J":HGelJMo9Z1OG~?=0x?.=}gHr45mDve7WT إ(yW!{]|2Ǡl ?, -)|C|(hwC}Ϝ<tn:`, BX֣eb - uRf}G!"rS3OCn0m$w$ \B8I-)gJrt5!̝㞛붿7:ie 915(R];‚. -BjɔٵKWԶS %@HlTml SP9C#װmB zcncMs!* wOMc0-F -GDb"/Ji?VN_9Ϭ??7yxj KS$UvYǞM}ذA4khɃ_cWۼꖦ#3 e_o铮M1Sh(u3Da E;d1t!bH+8 - R %Vua1#pF6>d@E5ۻb& ;ҥ@-)f6 qLu?YGi &:p.QJR#(;N7ڌ8K\ бc]|^@H5ELe5xSS[W4zUa{cJ7~%< ƶI@0 !r\a|[zZ`C҅[8cMfX%(#z+𮹨%[w_ Ujj%l(J6A:)arZ1ӟor*P#v5+5z5kKsXPl?uO^w7xOGaeEx H "͢BՈF -5+LE1v8?&̚`y+¨="k%pz TYa;Ř}(4+b~jᶖ!~_E!]-]kɗPBQR 8Jttd"Z|LTrO)4sVvW9ΝSvE;h$=U!ghS1lP1#E9J!(*h%aI,x/WWL~sQh@]m`( Wљso E524pݻ֞x5bXM0nV+e5CR[t/o՝GDAjCaV"m$'wVqjD\؁]%IR8S^oͰ&8Q &"G4f#tvKZ:yYMFsZ6jV].zrpO>Fl}>N@*NV ||?zc!F`]0 C29kB0Ξ> F -O {φ<+yE;96JBG"X$?6 bΑP$J)h+bjFDHZ;b]HD<|3v6WW +8Yni &e>G#wCVD1#, f$} 2#΀ - ӹ=4*- Z2ER wy*r*w9#P{ӬՎ8H7Zĸ+Ơ*"05ܲ+OAԳi8g4څ5C&`ɌaqًynF2K1(҆kZ+ SafbRj-$T,vQ]EdB*tl# h:|ô,TA32gpEW-Lh>TFV,\v -Ҍn@AKE7<J`$7Zp ز[G"(7&3wFkrre.P7N.)`ۄ@cpMfs;r 9lȔiDCYMF#pSEێRyw}ʢ[ Uds;72ć@{Bt Yۊb al[;)娤GvΚN 9w_%)q?A/v%ЍJs))*Zhvc!L!In>h4$m&E~q]1#ܴ PNL`2"ء=L'vth4hauHwzP5YPY e/4[p͓N}wD$GW?\YQy(?DUg#"D_1vD" "*ZC5+B֞:Žǡ@>fTgD=$=֔h ⬐\J z%NZ2Тv~.2ۮx#VaY,ʒ} :F[ -҅g|[XkwtI}m9Q@8R oh2B07s`' ]9\QjW0FbCQ .=Jω/_v睢Wo þ=ծlIX$[4 -ȡrH͞#/ #ͻt ѹl^hӋ_(\I`#bX&.¬o 3G - TTEp0&drR p;psD Mѕ78v ;G৒(7] P:1f {N]灌P_֜BqPQ+ƋWJ]ňGyܞ9hQ"+b0LK7b+ t&>@ "xn"-c,!fݩ .tRW=Uw2d;])$_q( -;v aIB(k3 Dt3rU4B tsl(=krչG'i7pq}ulJXw>T%P\QB69d l=EJ6 7) hd-p@!tS՞.#zl =< #DBV#ϽE -!LO舱9,y*dV.e -BganeW5]K3V2HV> PE~MmsPQAYKڅ o͒'섓(LL~TX2j. ٞuk((Łoe[yAj8jTL\Ĺ膑 ;G W`ufHK> p Y vR@f[t3&(InZ$5X6S0-&x|Q&-KƒTX`SPF8S( P[g|UZli|BC5 tWhnt )~@joƆȎOrfĬ مAys}= --N (2BvB1>7VI|Bۙ\<:=olCHo(1b$=q++J|?›&VL$KuPС@Gk+XBLQKa'C'*Uc)@ﰇaq0frs|:B6k|r\#]ߠ+U4CW: -])TL$[ _:W׆8 {BPH۫sUʋdlP;*")o,ׇ(8A]8l16㡄t_tL?̞cɷV|:z_Za:@'=I H}j-nɘ -zXo6\}Rf\ L<;5kK -ϧq3A`f ?3KZ+Z ThϢxZkc+ Q?VKC221=Gk"DRB2CR3Y,YfTd[8y\Q06BNsΘ Hp  @MqMJP)4 wu*bLx2'\ZBAQ'_Լ+1\硼,,Ɇ'$5l#g$a 0bL>TZ* o;9+y7mߐ.ʱe/R/j[J*KEx] -+IEQGߩ(JȔY?/>5=׋=t#Pj)e?pZ)Ύ5 tEeP.sqp Rv֍] S(Ffv B]{e '/ ^lՌճ&L AV[sSWyG OSlB] -t) vuTIHA?"`_ -D3OGYr>p ]0i,1Gzn!G5^-xyd9׵q·-1M5U +ybqC,rmaؿ8 ұǦqn 1`9sIn$I2)@5KsvSX,9b$*[d###F.][lt9alpH[QMjg'3L kJa.N8Y3,fW6Osw'/)W@ -cn:V4͙PٓBLIȳ6_=nY*byh6hob `Z(ITTNecɲ/ZkGJf] -X*dF X+dS14.q -,L$m Hv#PkH -"يX -Vg H_xTtL|J`7Ohr{nJFǠ)V߂֮cy -XBbM$ZϳeOP fP&6.B?.e߈˴K Iu -ذdHV + 9U|һ!*cn#"X"pDd?k4g_`iƹnsfsIX5&Հ0kuN3:b9G^OW<Jڈi-\eYcg^V{-O{W YsP (@5 Ys|*uھ"܆H++ȥIWEʺsBk{CPTC,&U*iu _lY8<hj -zlsi_VG$hoEy{W$?M*~ޟn]o' 6d:m5IdL.G``^v;uy!C{Z$0x8FYbRxqfز vV2LIEj_ztz׈a50M8|4L"FNimy_\7;t;6):\Zj >5l.9 -(EamE)-DiXoǔgG kk`HxSF[4nPQDi@o }螆8ẳrgmVٵ!ŀzhZ3ĸ(ڂaZyNdKlġ9Rg"lILlu&a4` U.P='3}vU`kV6ٛ i'x ԦM- }!{ci96Cc^лcʀ*<Du Sb{mL0<=JO͇Rӧl[y˄b "rG)1TQAcCgQJϘf<~NBb`n!E"(A/Z}-&g.~ʁ :G_*Wz - {L4ő$#B@{<[ᶣJR~Cm`,Gߔh4u^DĘ#u]?#L {Vdpʲ|*Fr*x4tX FdDӵMŅ8<]awR` i9bW(p>Wć0n%nW% `'3YPQPj"GXۮ5Z|-0Y_ih|BѹacuWFLϫS:x ? az`|P7mc8HrV6 H4jHε)˩Z`ƪўs܁W V%0 mbT"pv>6!;e_ۍC5hIu";u1%MQGATlޱA(֌CZ ^SW)l@:03V2 *)n@_M!xxOOM_ -ㆣE ^g墼 -Z`h&0mͳ {D`HDv!z葳>596h[X3/9I?0wsY;HkRp\?-gC{YrJ8=Oݡ|v!(OoTdр7D)g^&~xVt`δJл1HxX &T1Fʽ$8v׈3! pQ]#hD36qN6d4{M7  -kn Av.c^B~Cf"1LUmZ7-AHmѣn[S@fkKU9(6zG_h]Q&_4 mD3i` ntkhdKCY6@/7N`$'撍IPIɡ{CG:v<rkXU@jcXîEd-1dž7kT5|;p ͖J;>$X ,9WDf,ŘǀG.XN&=j@+r|Sd%g)A4> Sĝ Jb} )V)k 7h >v6Z7LJ(]ڐ9}:20S{-_4ds"#i<ƣ5=#+荕FV 0m‹ԎUzQO3^a+Ч5L9s.Z(aZ][a'Xƽ @ӢkHX!֮âВάFQPn.T2XkdR[׺Pnq(eq(E¬%5( 1fhB Z.%tHDԇ*"U.+! ΏHVOD,\CWad͎hf aQ:ݑeEt5 E#b.ף![kul\)lw1 -=nmxp+׆Q=fYYqm ]0+k yFau A@_e{$J &kL&B 0GIGaⲒ8_P:P(}P>3*\X%@ĄZ !xr"QDR;>(͈}7j+#i Ŏ|JyᨍP^D@P.a}pu9^; -٠ ~^ʻOʢw -ˤ"u`uGApNߍ<"}k]}0 31h8#U*&G"Bě$UDFK8k m>d^o,]ɼ5Vvt‡ ҹ<0YTȰr({H߄ Q%Ém1R*oOtϐ-!%XIKD$23"0s^x;=*[%s3QwmJbuy<KW*1cC2՝j"fvy~5-~/"H;PJjzare wh,EWd$QP cTyï+m䧰S|_ ]9Ef6,\^-<2 !" ֵ:#e ؟##HE{"faC%^3%q oc}4 -a`PGThENL'b""Z<)zzc%F>6fWC9<A~8cڧ )<>cx$u e$bFiBpMHh3‹}}Qh=>E228lWuNqc:`02fx/apڕ\)\:ta&9* -IKtv粒ZTAAՍC$LjMn(L) 0-Yzy(ad~D $TFvM -\0Y3B&9Lc,;bC}ѫ9Pw#oc'ZNb5.>OбP&tVM!pCz@֛ -,1XM*8\_Dmw&a_eUW(G*Z=AzjH hV#5YpIQ}\رVsmIS[hPl@!P -VKg2'Q:j?, 2¤+wrP,N# -t"9%!!Mh q34%(NcNz9\cGc2AC -{ -retoy| 63iߚuMT՚ ,Vf@ k17% 2UtuJa1@@~ơnɠ6^c.n-XUioYe,z <@`z>yPD9V8}1VȨQA0}SlZ]խGMaR0@3gDe̮ +G]?/8!G -VvKL1^$PjyWt9e! qIN48m&SO6hDyDŽ M<&h+CY(gYS)3#*" 8ɍۈrYcPiVȚfT:~R/1$Vч@Hb錁lx6  **_e[= ^lz U^2W /v Z[vS^gjn]{P>o\Bд?"ژR~9EZn57ښ+_1E]X D(H![+%k,ؠ[D׮y^lm9i1ң>ܠ#G!zGGrd֓2pzڭ€;\1 ;i = -cJ`]H -2JzGy5_tN h!r2օ j(@"ht17C3iCs4[?%T -&<̓uھI~*! I]M\}&OO@Z̈iC ix:HHIii}?@$`ǂ*vE[QCZ/-L։TSThU@RM= ]C( +_ulgh|*8IN?y]ఢga*#t<$?83e[,ש>Sg/ojCwu3<ʫrDz>TJ25Xz%C~{9ϭ^5e.BJeRpT^~rdz%l<}2Fh< ԣ7x"Rz\8SX>,Nwx*4k< 9)]D]K4[mc!Gf{Tg-gh{e)5w +EeA&->8L$CAt"܍DxN'!rtDP -rs ܹxuL?U/{JR%)K=B*T~!IhP$]}`u䉄ւKt4-T;VM;y~ -#8-/浻#NWI5V/ 3AXw(j( . D>~VfB19Cu+"Bkgb'9wIJ -v]zO`o|`kPlt@qS`C@䐑78?prI_ <xAD葀<%$?0h8Cz $΀"ogEn)Pĺmt$܉0\f}aǀeͭd8nu,!YBSٚ g6pt /i#f0ز (Qp Ty@( -ֲ+C1t%uѩZ$LPVCSOz!;XJ4pIs ;r@dε9FY "TSxO: --(ht׷Jqdb;PZCj7X߮RhM]Ҕ;O m`b-azk>p'k:4ѵG6Gp d~}wZ+o$iQ 1mNS&Pxt"}ݞЩDˈH9-+ fÈ!_AD5PTvSKKJ_"Jz+5 UfrT-eRL40t6<$W/Z{>{7a!$Q8&R B",3t"Ljq&T;"5ѱE'J O=Qu6MI]C3Q`J[Wcg&2UG{{,:!]jٌuۺ,I;B]v@EID~`DUAgBCI+Fq[3zZϘ -A4IvdL1+ d1zz|MQKxs!v[GI-wpIhD늀h$A*.AU^x~D#N]#{(-T#M -nL/ArS%%!v!fp12pHFHytW-fdlh+&ύ,1({pJ= -m$*ǒC否F* #(*"!uٴHGJcRm'X¢̯j։:E8J"]ˈ&_$tPG`-[FJaWJyOQwpS0t/# - KG t!;m3Τ\V]~sdPF4]1}׊Ad _ko5JӺ6>"21լ@bz|~z9161r`!h@Av)J '{bE&ˌ| Q'cJ[{+l'hѼږ >{foN?In#Fb7. 8|hI 8cVGv nax7R+# -YJ V3ZX嫮O-CխsC&K;_fy:,P>&vsp]kI,Be(p %Mâ:2|TMe0$0xp ȠӶ&IRS-_Euy9)\5B+P%#uky%[d@Zc4MQpa|Fn.N8 !>DkP"(関~É3cLSAc+iºbDIP?/ Bp2 aQc)XE\s;W|GJ8Ԋ?}]~!0Rͯ܂kzkuhج+ro~Q|H$<ZSC] :/~E',Y8gE5"<~>WzA )-KB.9| \8G1p$v^z(Cj-i˒*0H%>| Ub .ML 9p:ʨcұWs .r<۲^gf{?ĸ` W=hw3Z^X~%e[,1cGTltƃ/cL;BfJʊhO!16`'i#>!=9Q8y4X=VBxr-g>XDбAYv׼оdXqiW}CF("+,ṷ7a -hI;iM[]@:Cm>dk/E3&؊&ըzbV@ԃ F w: W +J}|-).oph,Kt=S \\XH wX)aa:;L7aNhyƤJԋ^v,iE_D%ŀ%;aF1n ysJUd!IY񚦈_CѪZ$TD- -N(et -i7 -ph.=MX2ZჅ&@:ە9RD oG&ܭ+(PG?}o'kR?P?  X(<*ӭM`mQe,{駱!S24ɕ !kw$@V\c9sjs9Ą - 9adJƳ.#uBj0dґePzH>nВ~vץ[SIǏU]#ffjQB*5zY2څ!6v;q&d걬CFzDƔA]O`+QF4Yk}7Sc^=4) XJ}>ԒQch]zء"L 3pg!4UnPZ0Ab -L-{P_AO[փL3z):94q.10{` ƃk7DĀlm2{"3 d쬅@Gq8lvꚖƾqa SQ |Fe -֜'ہT_Z~jĬzO6r3}-6+hT֣̎WÑP;f+j [DR,V#Xvh(A~.nL ވPme}pyHKKxSv+/e%*xm(wo03-a(|e6US_l)1 {M`2>##r\5 "b|ޘ1)=i!']`4Ҧ#g_>Oa'1E`cyGw/:\+|Or"3D,L܄ҕ(y9/\akF˲Yxy)Ks&xLi{?>&VpPpy%K+Ww2.LU FXӻ񢰨A('M%MnURu3Frx%J7:jyDA0 -ef8e̡B٥QXx}P¡Gwv̚o;e*m߷ -$n+Q C |LjjZ{Oa$u&G}H'-M_c$NCvQ[0%ڪgF@Q3N+/J#BM%JQ?9gR|VD -ǏQ}aZwG$VĠlJ,t(<]w,YmBc-YN;KȃzFPu1諩")8hX:L] C `˫B®#YIA\RtT+=A:cGkfK>D(aM/”r;zFz d/t_IluXT(Yt5Jga= 4A6ݪx#2`U`Ɉh{cP8icd[XOq[]5 "[En"z<8B*"c 뢆d'?u0:[Q1{QqS$|ti 6kz;6j!s߀FPnPav}cܟ{ޫm&Ooċ [E,z2#<ԭ_R֨f6Ր,k/:B+31 C)r fECc^v;CBgR|]ߦ> + J8. mhe$(Gyp=:U94iZw>,/z :CD2))UNM]d x\GcKhK!z Bm໰F,"!̰[둒aq%F!* CJ=e3|_߀R.Kn\'"ZŎCR@%݄otjѝB,$ۏN?R^#k/ *HӈMg-Z!Ew}SJq '"rĻ& 1RygC4Oi"yBXKeK6Ws{D6\+S#i ݊v!;Á J) _r}7(V(PJhU[,j06xNǭ2y%٠u.(S&iPH7ȼ 4C\^ 6!1-ᰕ6[ic%DH/a8k3Gf,>R+~lx$)ώ3SI[q?|~1?ߨMv @TV,h&7 &jSs'XvXL5:Ր/.s!竭x{Aq8Y6lCku%`'zB6pT|soT0hrF mf֛DB" 3q(/^RCe-@ף_kS)Ϡn-yMbj83,(>>1_/xi;9w Y}W];*jtZOq@;Ce4B5[PS#ӆ,ɋv1<`@iį =[Ni0FGM!B*^9Hbg5ڈ!=d~Zqgs)|'۠VB5٫2(!:e?VH҂LB@hCjq!ZV =MP"3+Ъ07IAޟ2lPUv?3̬w'wUU:$ p1͑U0LvInސY6[gXPb dШ /,Lӭ+ ^MisV0̶%ҼBÇ=(=WDp aN$_ߝ JwN}?R B$t -ɏ a#I~x9?upY}o./V8~|'c"\tfR͈¯_1]Yve=%!E y(YGuFn),Whp\`$h#,:gfM~XiI)d',7߮%u@wc24 Eӕ\Z߫$'QR%JC(ֳ^ޏ #cGh:xF&"]um1=C%ʳ G@[r^&T2,Du >!-[ j^LZ$R,M(3I`K>E]&^LD-Tdwy7$ WwzTB$'5F}M3Ի$&YG=ٖ/g -"̎׌Vk"BTy4A;Ď_{ AeKhG*åLXkzSOGw{]"Rzzk.Z}w~ß,Wt hon{§7VLkirpzb%}fAj[t4簀见n<¶&H,تp[.P)JSs[`KsOn-,sy9"pFI0diYUdBR3'7*U XI: 1ܪTk/#)Dw#ŎrKAJ `׆P=\\}w.Y6yu~8飣XVB2 W"M ("]=.syn㈌Eʎ:a$?DpFg]m{Tp+#F(h|د A0A2iG뙀`3_lg2s~גAO6)KZDvuMhL" -xB R~PQo#zLʠ@ x[ɼZ P*o(8*tf0G -D<$aq( -2#4YGlXQ>2BLj,\(CCA_l:08P@ po@~D $ઈ+UcMo+qhwOb,gM'E!]]-BknsPV[ -{. @W]Y @'*9aԂ+ XѼ mّiS5*mZ>q$H>u0"`) 4C}I[2@>Nˋq?V -U-(\fjkE,)ԓG -/ }[P -`]%p?KR]:홷. CD᭗Y(t97"B -ǝ_E ϫpz}a c%* -zj_ObkE`}+)v1`~EtR9Y\&$] T=f&<md3"ٞ&S<:TaL5iv\P5g8GMY^1=UTV@+q5X1M.4'9N3 3! vJ4$.ܪRBRZϴ*]}p;awYbXG7Zumߖ{ks&/~S(hJ:., )@{$bRQ<-oWMMW\ Ȣ` -=?=hzy_W?W~է~_}O_o~w}_׿WA>9~>-G_~/cセϿE8/__ҋs~מj~:GσN_?ugyw֗o%O믿yo4>}/z/\n˟flo7_şo!g_4_oY{_%d+Op~M.u/?~(/?_(yoA^啣Dj>[Zv[Ǘ[f{m>ZCrxTTdmtP4gxQ!ŇR/և)S_ ΡWpc{=}b(n)ujto[on -ϗ!hnsC++KU=wˠy1-:qfCfϸO>5q򿼎xܟS*G eַ|_1ǹo3[^C"?\땶pjg[>+j臹]zi;uX]kuWPoӞߛp|N?~Y ʵ KGZPKo}D`Ə;l^ǿz;t#ّuzmV>GzA^Ǚ>(c=@.1Ȩ{>[=wb`{{yǹwon*\t}[|Sm.i/r]IǠRۧa%Է&s{zv;|w<3?NxuЋ~yq>Qn1</H<\G12Mv8ZW螮#/f4dC}M{ˉ>]{;58¾#>6Oz5]c'F]"De-?%AԶqiFPH m/#P$pf"XH3d{gD N3E DTةdPud$ -ǹR A*9=3bևQ/amIؐgyJ^2^X)js8d+ c}{f{#Hv_zj [x)4)c!3\ILBh0&<"ݟ*iyƒ2xaƅP(84 -l/>y8N5=AєAi516~g^ܒ,D^PqbWͨI[)8*)V4 -"F*t9P~T6 -~uhnZ$ nS~'Sɲ-2Jb+y@ћPiZ}p -[kOd̬+ۗCI21Z:JAQ#!y{-k,i'%gTZqc;W(bE?> -.J:!aqZ3eЊ%:Xi\H(!ϲ2$e4JWZ+ tܵl<{PMdN 2lCmiN2V(8RNI(᪇ʉ%qT8*Z>v|5B@4# M$ے[FTAB"ҝ~q(=gNcKO]y*iw&ͷvU=@?]Ya)flˏ Bf1$MBWs|4^5$-!{L Eҩ%P2VIs| 4\`m!K'gP'K -FZS-Gbm_z^,r$:&xWM=/уh?D⭑@ I=$;rm4y5̾r۶`&skqx\#CI|o\j@oA¡Fh)[qys^2F0[#B(s*2'XGsv-aZ(E$&-_i:i-cM`{rZ҈brBgg@TDKbTXhSV -[F|Ҩg)7GX_*ZP,i{ -Q^*lYApRj1rP$@2 -4b|gn gkFJsfPEɔ/y+ -:#VQ3BpڲD&򖱔AI]K PW/@G tItw54%JqS8)=<,:@)U:?)!#t{dQJ+7'u~iG"= 9`19[)^ꯢî5ǒ 1jg"}dYUhK%jy8FNMϚTE+TЮfMtΈWfF 1eiǁ0hF -Q^Q+b8+VH?.uϣ ۟^P/u$$ZSQaJOr$dcAdi3eΉ`.5`*ahL\@KZy~g(QV*LjdI"קthcywiN;W?`;Dt)O-B!.zº;#koC -?bh|t=yH0"RP4C "ͿBlbUSHWteP+Y0?kzii+qz"fk) -SD㤂Y -@~` -zvt"v!;L9^I9D+"z–M#hWx"v@tu-GDU=(tЯO?4$- ('#2%\tʆ`Lgs7vMl#Nwv]8%` ϞT \C` =nUiH/p=9R1&&\k \20DG#=P@>^Zt=D{XC+Nx3hHWM2YHUq*ϭ'!vZ,5Hq=kvd$ -xQ D]e -T"Z2H{/ N"H?ڳ鄉l.rِBHI(r~]F EC" r!TiLd%"B)$5yp 7L8EF)Vc1`i"RZu䁩~[bw&<4b aa}Qj\GuB[JbثPBQV́\\.aDC9NJ7AP6[{Q-E*݂'pVS$H#xJRYLR lsqg6I7{%7 .OaB }ӌ>߱> uWqZE>,/Վ)9Jzy1JY*z*,8[!2ꘓÉJr&Eb\"zpgQ,y$7;8҂N>tQ"c(ISiZ11/ L8*tߑڌHDŬ -~d4"5ESc"HaIDQ825'e<5r 18:3? -H1O"ҡASvv{صWϲpT.X$M"\ gUԄ^_fG&j9/Ц!5! =5l2p#mJ ȾslZZEOvJ۩( \)Hb%}[3б!e%Ho$F /Nk@Sx`hzTwzQ)BZi zf&]qB38i؀CYʣߕbD:Q"ޗ0rT$eR篔wS>"˄8;AhX aC!)zkUm2Idtk*{SX30i">aDWY9Ddd)EDML$"=Ьi H2)p F9.r91&:"V*`,蔼RL"%9 Y4|VĢ&ԍȺ\5Q7:PU|D#D52.*Y a)"Nm QRُx:EK">Ô nr_MsH#pP7qޓh~e\@Ob t -P6[ }Y#QbqFr$CNrH%U (!#xu.VB{2m4=wVV[RQ޽q ~s]0Bhbȥ5| 7L/#7($'7 -uG~XakO'ϐ!/Fk$ENE -.aڧvETQGQ($ۄ#W6#3j@>áU&Q̌s_ _zO1+(y_ПY=kH1F!bșxkr[Yf--TtDԅ&ϓO6%žDرHT#_bqnNQw ̨G5cIzBw uYȯyDY`эhE, -ze -lR,\'E0P#(>" SVa:+KI:G؎;Bn: -oZ}N 08뜎SJ+u 6` ls+Nκ={w˩I7JiN֍}:2K.'9`DVNamʨ j'}\P`*F;K,Q"+(I=Jg.Z3(iag!/ ewfR]⢛G1@JF40= -(=tG}al}@ tn` ѥ+xhVVE({iz7*Sr7P yKXnSR{9#aM':_0hV˸2^bFd]jW.*!t&6A1' -e$!p Pt6Y5ӵ9[ܪԲJ;9-AiiJ:yrbX!'dtNi eѩ~L+v~cpHxKkP0W$O愈YiȆ'<-Bkhʔ"T`[qѵ; -H&4 GKP+P 1XEEC#Ѷ/NKqTLlEaùu{)bi&Uuʃ lrNrdN5 OvRF"{yx2M[2Qi,/d+ȩ>bQH&^.e)y[N-B&QēO3GekDVeu -Q)Cw{ٳ!ŞfX/LK4U%.>T o -Gl^AeN[?RlESPaj(̰4ls',V@)phT`ATۉ2U'̕ʔSNѮKd9nVswD%'(2竚;ɬLDjHj56\QK=x"<β9eaT%J0=uV}o"ds𧝉 p.ETgy&-,N#p٩W'T]1D6/Nm% -{4HT3dk@d^0uckIBɈ]ݣP>.P0RIS>ҿVOB0Vt2>3.Z)6,YBT ,{jzAG~a%#jUĬHOQ6Rꔖҝ,(-`AWdc@"6g -#(jF]si0ut% sMo (t@ *랈\0p*uD -ް)lF""QB@Wgǹ}N,8rNu-u%]Es-LEyLW<*@LEhZ:- \_ UA rBi9R g$r+D{oB+rY*GИ?h9䂗mE9rU"Cz]]+Q8yL'sgҽDQ$ueQFJkYd~e22$h71]&A曯kDW%)".8O aZ"3JXz}N\j |Dx)#r-I22*hWl.9[;䑮L8+* 7LI]#-LM0G}x9í-&o8f:'_t\tL$HS=zTxA)S)u׊DHwHIs"}U‚b̦_QDs,H(eD4O -WJ}˷*/WY -ON5ʵ@O9is*tLH@=iD{hf>I -u(zcEwZ$-}ޟ©nܱXLT]% 4ҁeUN#UNiD Nxg -zv"ן_"PSU+l 3,duk)Bs7ޤ#nSN:.f:XCeNQ=sBrqtps_ztwD\.Fhcn|/Rz ,a6ۍ:R?.ڜr~ _+4%&J|]*,EGS>EAĀ9Y\L @W=og- -@a:AqKlM]`NQ#>> }^!7B"]t:(ְqmu@u9LgCHSōfE61k"i.JtSD`>8be2m*V ?3=^H"+byن$rwx#`{+/^B~W ڏaq=VEZRg8/,zأ/y/nHζw4̝[|5/4#|S) -^F1J $ ?TZ@/AXLB ;I34b} -R$#>I*kt<&[Z -am"G2] -1IS}jEt|̓V"3[$0=hTM#}k<@׏ވB L1yEcٸKuY)8|I0 D#udyhTt)3^%oB!8ioRI3KŅUŷ7)TR͇%TΖK.ҙj?3!)Eli)$ծb"Ĉk8bLGNhuQ@T"Xerbr}h@2 #/wBcI| jOB -Җ_H Mo}cFEA7?~y~{!hkre 3{{l/TbSGHo{,!⇟W ͟X}G8/?|像f&&Dg}/_8 ?l Ot#Ks 79?YݼixF'qVl+9.޺ɚ۟ͅήYxdXI+N:?&'^x3nKM5Z㮓Ӊwx0tG'F7 b<;6l4/EoxnvV\tqOѝJQӈn2zcfxrFgt4|ucuIOf'jPǏ/W~<^~1?ݣk"DƴZcN? Xz+hæ[Aopb;{dV}sJ}ZmZ?_&֬ќܓ6~6m=Ndv~ZV+l'z;zGڴXhifN;l#^5ZA6nqH]t<ڶI>-9S U|HеǦ˽ݽN֛f;h4ÎucZ3_t-#,Wz,v;Gkq~]y9FV3{PkqlzinuBIËZ}?`n0}p%ݬјtdm'p4c~O?3l|n;-34^h۱̴Z+#L{vzlytVg1^z٫gU=Îx/iZs>?\|u/m90c ^Ozk}lD-,;-s߰:ga51ٚvhjIh5=04E?-^Z=5uiZm7ڴ 32 p>[<4xd5*hxBU'Wmn[t4 #TZ}^j4Re^o͖߶5 _Q"x |/4yo_hzsx}v3:5t1ژTjPZFU#;i9&sܱϦNUXAᕫ㝽Lۈ-H0bզ6jeX.+el&U*˥nجܫ7;[gp=>45ȇ-81A|j9R\q 3¡+L! -0Fk5UF۷#7Q{:tO{,q*rՇ6m!;+$̴? w4_<\~7n?Ww'Ax4nAYF' -Ec0m{h{;Pѕac%' M&3mё?G&εs40|>{3ov\w#wz96ve=x[ #sז} [_8j}^o&=Kw/l+eYGIpqV~ԋgSҙ'??t|WvB&_}O7Բ!v Z! xN:T Zk:oK d8_Jq2 zm38ҩ^X߆^ky<,W& poS֚~,ju`6XZ3e}LVmc -n BHԨIXf\t oF#uv1+0Jt_>mw[ v̽=۶Vs1?  othc+Nxt}LP -?888\m?٫z=]܌Wim! ٭>K9c - -a݌dzg3;YN7Ve]oFXv87Q}Yoj!v88XvhcpR-o:5adCv|쒴ZGӎeM{^bѺc`0 e&~kn-uUjp)0d{ ?/y ʞvG`0f?^z5"7Mo 86,ys%t0/.'??>7&`t1^<'`r:{:8r2{Zq[ݵ;</[w|OևMWm+K~2;x3ѣ_~_s0|@ W@쐨ÐvCeƔ HXVVhSBCV@l٧TOF4 E{'=9h6ݒU:ri#vV ՂVmZVcX2v֘AuM\Bc6,@Äl/k6{C`f 11@Wxr2$^N)?z6j1 5;I:fÀU"X[9aX!ְrڋm,GϿtqFs? g {3;}ﭖW h.9/w?Et|r/yV7VoEϢx w|) gNhM D'?z{ik6}DdOoȢXߴs?zv~"~: WV wSTv2K`G;֬t Sa #$ Oc:j sÿ6fy2-Uefk{ :r/`pQk lUaLy?7`F Bxi -Hm<8$|0/,Jc >Qoqxk t'P^ .{`;AjA.WZ`c߿1ܳ10Oò?-{=?az[B;ͬvkL3+$CYV>qN-3:bgn_Y=AA[ucroQ.Ð:tKfz4@37s:-Tax}@6Q01l~R\v!p"vWWOy^ Cul<=4M>+>. fsFab;j!,}b2Mα0+k6t8:MLZVZO`0?|Ojs -sɰdž`||^붝x#m{oVg?G>|etZ{_Mcx !Lh%Liru9w}Xvp~[k>(Nkf إ-hbZͅe8qmHzs%` G2xCjа{[/*#H6hUh5f IdLQќT=Thj|dwoP-vvjwӾ(A82pmρlCضS`{; ƃ1چ8G0H!Htek)ֽ6$c0P~^JFiըOz:0y>O0!rnVaiq`\F|wu9N,'5MO6?BډY|YΡ3speCVz[VԨVGMV/ں5Hqduh- ' P} .'~BU* T+\@,{gǀ6t6Wr+`[ -o/׏Tjg{VSp$G5 -M'µ2Vff6Q?$S7?u˰= {trP;2`G5Xk<8` h>\xOmy]Gqӊmx~/{{Fss s4fN70!rjCo!+T{xl|vZ{5,;XCaX0};j'rۓ=zpW.0X@(LϺގӆ="N:pola՞'X[8[Iܔp HIǍڴkeE,dfim#<<|m2.zv8Z]J~ѳ?TJɶ> Ȯ]|铹wY.1ًv씱!0eZJ_&mlD7sktZunC&qcxwGozA?p_E_K=u%7Ĵ7?,qvl =ք6w&O'fwن!l=Kg:i9Xۻ̟vxI NG8v+pZ5۳98y^?[nczApv&j &daz0C(7?,X=OH j7Vb\^Q\} -il\.{{=_̯wU 1!'wlh(Ao9ÓġhN!vvvZu|84J9\j/ىP߆?#"s` hKǏ?bpk[X 0!K%NPWV% ;ٳ ;Ą%nİVJe\fp~]mRDlbXG a =Kgph^=T:O䇄BXg߶3YAk<-/HLǦsPk!xGmc0C[k+$XT`swv0I?M|3VVRoE{3Tn^>=2\R mv>'ލ㞐5UJc!@~{6^L:c`_?`Mnc=rݘ4jE}0CA&mQ>=RoR\uO`^m6gЏ{{dBNRԦ 3?=7f}&2ڰF8zJ}2Q4E_-]룏'NP6Prh [Rb“e S?CՒ\ - 3^hN4uD?-F!FPVl'xiLjQ= /{vo0Iv]C?z5WĒe@9Ɩr8 ֶKƝ޶Yl2 -^tgV7TMPa#>E<}\@´Kvi$X>?l42,݉{~ou;es:G ~5r3X;"FBZ>ⴃ^OQ6> _wjZǙ5ZA4QYnU`Fyrf*Z -T-vͶvӉz&Vjyl& &gw̓Dsj޽=blTJ#QMXV\ATks1XafNEb=އڂ/Á1i&pv?+Zf@Ut*a?v{l0u#(b _ -۬TC5pR5Lr;̀jzSr2 LJ^ }Y4s,X{},f#p[FmUj̚`hjtVkA" `ݜ cy[ FwV.q10am+4X@~߁*&Ξ-G-r?`~6ZÖ(Q}j?&sg@ c(0̃I8@me?wdZ.)>7 - $ODBcAsav80Z,x0ߘÞs8U=&$V -H}lP`pHQlsvUu#b?@̳GgW_݃d"W=U*Dtæp{'Ev;;v>@A8uk.2TA ``-!@Ncwك-_/]ыn/ҽ+ -&YD`'' [LJ8jdmV՘܃ %.jN΀P@t"RLd W2EYB%6U_!M9.Pӓl8t׽Q-rvu RX OaA4[PjY#PgCl{^*eWd{um|NUd-#^PCT4+Raٷ({-_z`Nܣ3ju>f\XgpzOur=jSb @\8+ӂ48Sp;{Ntv>;y=R|=\]\pr2!ĘBɰf{ 1`|1ݺk@";Vl-gd풗&^esr8Nawi ff[&寺shUX.Vf=cia5M0 Jj p3jaI mX ռ;hr ZcODX 4^WObw( 1|HEۂx~fjl68J56ghvzGMJHy(Bb!( zƏݰ bW -Ŷ"c:8nsȇ.yV؝?>^`< 'ɕ>]}Gw]*l=gp*9/N>wg-+8n=L7s0^FO!e34Kgx!D\2mu"g4;"|2]:Ή]Cu6eq`Rlicg." -K ?Pn|Ӏ``Jsءaz3*?|\2B[;te0"5b `['Ta=f09`rdؑ;='4x_,_x]wtLÓzӋ y|ɧ`^&cJ~i|rǫ/ٗz6Іc(>Qp =`omz:֋ }g/gӉhx|_ɣjƥOxq؀p|: ;l'^/ -쐾%9xuSi!cZi*bfO߬ο\|?X.|&N=8OOgуYϰYޚ[v2@ʏ}zo[,ozI=߾?^}%^'I87^GӣW`w?Nn'7V'"|<5kzѶ0}hjQ'uu7?zlfE܋}y3jf3"?z._dncz|{mcǛ0du?,Gsܣ  ;~Sv3> -Mz:Z}36Ou~Otwן?~/\}.>~8e֏.n09Nrw?.'t_+ LdjyWwX| I ]fUz:"H'hӛN?;8 -+|SF~n0QԼrh&_/wNs|02I0Xf\ "I5 %*Ԁ('壩|m9zRMO2b1T8pZIX1*Z<ӯwR--4n<~8'S''W/&ՕHjj h Սc&sSD|8:wrl5}zb1˛8ȃ@l3{9q6_֣ӥڬ#] -(m.Ж rSJJc9{.zzfQO'J%X,/ -rIGtu:mкd_Da&b~w~Zq#ig.gf.-]e*Յ}c,ڕ̹RootBh{~Zi2XӢjw`Jo{|b"X(73%(ͫ.>upbq>5EEݷyѓ+fRBZ hf,3x P -`pV*t)$( ٨`AZHHfyz0&Y;[hJ+qJ(PNMߺ5Oz>h= \[,3΁( Fh< 7IO.~+˧Wpb&Zo?^=ID>"frp2z:z:WVNQh&t$5?rCOz] OD?0~5dn=OBt(z%P߿  qy(ryWJ0-ӔmE wGZ1͔Z0:|y=WJ旁DZퟹw%gKZ$7W?Ho<-"P >vmzg_z{(щW_*ȍqP-\e-UXZS1!P;yu~y䒩|'bck= )K0|A[W2x NnT\EɐNj8>UMMO:Ɂ|aء:-bez2Z՝ًfqxPXrYY+ -c g SУ}/JF*t ɝ]P>Ag3ۛ>,kZGʷ}G8lx|'bWJw;:fDHr\f2Jn ܻnB|H9 (d4kk @ƵBolw\kOSb;;<`b8zG(9gq8C ̹ +sxv^2wVnG"FrSjt -!Sldk\6;O+L0: Ld*p{bf?Y%,js>4a(oD'GX$bdklz+$ƉɅ;d@=Qv6zyΌ>EXn@S'=|6 B_̶0@h="x+Q|)-d@;_7ari=T[0S UAT@T-6A%Ukz0֊f<3 w$9S.Nͭ&h1G y^]):ɧ3RkZ/viCnZDEa4tƮ<Jsp" -\1"CX*`_՚ӣ+Kw3 LJLݻte1~?s1 B%9rZ2t~vbL3K$F{ -; gñ)ZW;M91 3EPch~1U'@0i^*R%mLΟŞNu&0R=^łnwT}03mΟԒpzvNNn̡Nڲ;45ֶP\WGcNTCUˁ6 2dp,׻|NBLP)Dœ]% -9UjoiV48?Gr!؉ѩSinx@ |1t3s5NN+Ѝxuh~JD#c,xT7L"p fj 4ūTq+W=R VL$e8/RIt&Sdm3{L1q%PbI 4dsrD8L|Lr}7 @Fj(ҿp:7kAi. 7u>%HP dn#pRnO1=ޟXIɽX`o - s\bx8nbB % p4%񰢵h.DzyXl=n54ͥv偞aKX>ZmX=-mɱ-'hzb;pR2hU -fe=,_~pfc?YBDқӋ=?Ŋ (!/Ir4Y/RJMI1H$&0F731۝4v,=YpJ'xV"kWyFIJorajm;*{IUP>ap94r:dz"F>$Ixpp̫1º0*N&-zxs dATK9yJI16 āNb -FF1264- Xb5lvRۑT0ōtegCf -AW։3,4p4FfyUkXl8‰pr^R|c.ߚbXOOr D|䈓ZSGCnUbStvd4 #g!kb2dn$J\1w{=O[oo۟\:\ȭ5`΀4~=]^>u{ݏ<~Oy~=>W?yb}T2JŃr4rgrzayuS~/\z^ʗ}kz X<)8]4!W%E%مSKͱ՝3O?_7~7:O4NiNGx[*n<le>stream -~#O}w?Ѓ~}\灇|=?x5VL\PpdTD'bzcv鉩ɓng???oPFIhPh~bcս@\ORmgoջz_7>W#__կ_y7oCt?ޏ_zo/|Op%~S϶VHňZ+plm\~}xW>§>?ҫWÿwG.\;毾o/o7?}?߿}7K/ޕL7i6j^ȳRabble{}'_/~~o??~_<ryĄdg!X.O&tz]~O/~__?gw߯iu-1ߜX[ظ}wڝg_̿oz??ُuׅtG[{Mxʯjxlsޛ>Ï=G^x?~o~_}?yF{AP%W3D83zt}+_Ʒۿ|x_ʃ7D9-HqY⩃'N;zoo}o_ۿy㷿fg_}굛4i8J4>NQjn-=};zs//㍷~?goӟK^9`%zT33'w?O|s/?_?׿/OygE/fv\R.Gv~SG?Og??ћo?w?}Ͼ+w]Kqp(-Ղh4C|_?οw~_?Og}^0rH.z9%Q۞هy}3l~?~#h&/}ҥ;VvPXZ F]- -xwainj6uƩrwD/PRYps0'DX1Vl[L̩?#wuk7p?9# n'u18‘(䱳G3D3<:}=po~+9͛?{?ϝclj<@D9}虳gcR}^?o~ͷx_w>>??CNlm4Pdq<#5J(XwO_~~_~OڅczP`kA8HBa>׋?ڋoo?'_^WQA|~۬ i%#>+G\LMM/y܍+|_K/c|s/}w$$wyPf©T7q+}ϋ/|ϼkw=̓7}"OhK -e>D t$\ȦGN-,/b1G 5[0 jmvJ`at 8F !a5ExPOA= -wkj&d%̀kFMGHd@Ħ41Zd2B-£ZQZ>(rhcb9p|R0v54"YӴ>7tn(@S \V_]06'E9U>01"A*;{@l&oAbh`IDaq9SD\91'T)];)n -rmV(a͊{],AD9.]ȍ]sz%ea_a[@Ƴ+Cx$ 8:NR#HFXl x!sLpN7G$)bFe9v7A"$ -Rʋ*1< -tQ4*IivmQn`a8G,E6 ~mp248etVq"e2a=nnr9ۈ6 -p8$pf ϭ6+")XLة[l9$8r~*l<& D'4n0jaai{!7VhyGaCmWWǨVu)'qPcUT2W$a[rAG -M>Ϭ ˍ54j6T2cqHq sQ pll R9Fjxy4yt;%;Qpsj;8vJAB,ϧ!h0H>lZ,d4_!;[ y0$mAizmm6{=) DDcx9g]n -p{üXO}0& FHF#D0bcQ5P#r)\WUL:Nv4ClacbM C>N֝N#s fTPހg"MJ bUNG=$A {10n?{\Ʉa6cN6`р.CV8Զm2ַ{1o_biM!-8\CiQi%8*nOÂ`Rc,%0"iK^EH'oa;FDI23iU.PGYlfGL#$Nļh'SNfyb6U5\O1VB]V,W0"( Fh1:4ru6cq( rP$D|X#UlbFH.+kHr* bF0GUyqAjch:)9ŀSMt*X -f(TqXS`x Z~Cޑ "My)`dlDcq -8mrJR{X [ȸ{ &NK#C! Z$: #C^Q)&~dsU9>-y[v1<?]n/91^-V׼#%xshЅXr&a1c*lm=y E+ØSLnj&omS߇@YHcC@|A[q@#f ,,W̡CaPT8Є tX8ՂVpUVDI$PȧƦ RȣTLQ+kw !.5=4Ӓ!h"2;&J#GFHd Թbm=_<ɧTy9S]7rBQxԓxDG4-3Xi*Ixڇ 2qab2vg} yAZ9j{ᔪL8<凫tkfsZK j0EdW&ԕz}NDA>D`R -.XP"4k¤FJQAA9\20jFv z<\N <_}‹N(L!q2B 啲tD)1+Ж-2Rc.4Kz7lvccYM$==%HDՇ*CfY𒑓Icϗ‰Lf콴eLV럨גˑLZx_=y=N"*K,f WVѤHXSVsqD`EtCüTF2)v'xPzd-lVnqp#:(栶fêDsYNHF*,0T,ft/G)Y"moV+n+CщX| -JesX`Ur,Wd6PMF@#[nxHt:623THB Ae86h1#4ǓpK5ԣ"d&E'$+#57nV#D.&Trx풼^ x|!$'I: tf4v@\%9.'Ew p |pbS0)v|4^INۻpWO@M[rNO~e{} &?TԦF9mEd"\nz(OY5S K郭ݛ |0X7rS\i^$;mw&,nL3$`1(ϊj`SbUE#H],F2} S͕ŋ>#9!kw|O:?x|!?M*K+ -Ș6JOΞ=8xɩT -bGrBIJFG@obTj_Vml7n7P2p$oFKԢH0UQKT4/e.-&hf 4V8M#X}NDX>$W$蕚'R5M(1" EĐQR)db6f=n0w)n6ai P΂ah&GQ8Zx#'Dn~|lͧՍH -&O˫Vd2$'7] $2iJ:Oad~‚UH0Dbh|}=W[%&*I@IM=(hrnnmV -'Njs&  +dacnZ(#datAp-iM~g\y3}hy%%TPgYNi%s M%s8"(y(@|W$V@(c H3ubltc|ڈY90}Gġ)㒿k0G֕Cmg϶.rKN4{3G$`_m?=m 6BQ!:bbiIMpg`v$u{U|s6~0}[6w䈷 OM&RsN#Cʱ}pǬN 5RAF1 <2 Wzܠ8Aq%JX1_h+|&8\ !(=>ynxf3r`9<PŋJnxkX*WY_2NgX_Hz*rZcRX.HτX?1RՃDX:76092u{dz&Y$NF2YA-} *\|hvvJH6n>qd*9o N ţ3Dn 92!zl"UZzkUSa&l -2U揝y 3 ZhT6vj^%mw$D9ZR|Aǔh8ӛ9y{:SÄ}Imt -<"yAH:l:x)'V`_ MRT+nP?dC_HM/j/Jѹdv-e}Om<D|5YfWe MyAE<v+/ n7vukfڌEPȤ DKRʀc% T$t(! IDIY-擹7JYfsp"2SkW'/gGIZd&X]O!ũ(w80 l|&$FQ2H|V wi6!Z{9~>~,W]:36sR Tpޮu6[[jlԉDgZ14|QFe zNHQA\>L)OS9|xA5)ַSPvIў˫>be}Hק;{|S7=A/2[Ss4ux3'ШD-#mbNGh J`xT3t,NVo2sg1ow5cݡ!X4yd -RC wY4#xv3q|.1r9l:}|ӭXr*OYa|JRd@2X$TZvk3PZ{.W 6wO'V3h<9A>&54ɉuE$V E ~6rN#&^c5P/RƱp>7C`ep -b.`:8psVdrq4!ETGZ~QIZ'dj=sup4#hf@L ?Gc 8kD. 5bfYHMU?lЫOl \y#h/S^f89*)ڤzÒT4+xC sK<(" (RP넣3u`~%yQ}ksECn24B )*Zlc\@ PZ$#BS*L}lrv< fmNLo{[-$Ev -Ń.O2PO[XdR Y5+ Bǽb TztF ij ҰXP{V ݪONl1q&sJ5^\.ɁO T2i} A(4B\*e%J&YXUm7"r#U;`ZHL#?4<M  E8adcwTk#xPI/LoΞ-؝xۨH UPDq84{| 1ΎlDEB*#<ƙWw.m>1:U]EH8~c.K9IiPLERw`=.$x.Xn STوd|#8Ĩ$UZ-쟾G1 ΂f3N-΀j¬vYۣ b ,ñP0Xԅ -: -,y<=uN܀o3\R&!BybPxn/ԧ.oV"=E*x\i'@kx>m5ld V*+݀ޗ#prYY&ClRD[ٌg,b,ޏTWpeJX4;klAkBnV7kum0TE#nXH&7>FT!-õJ}~v|:GrsDɄ(/YXDKAD!͎xЃ.3l[,XHq[o'Hq^Vk/}s'dT[lNWՌ+$YE0>4<Ҳ{p1bB,#h@ݞ]çX. 21ッ.ӰDI*a7rr#P?)ltNٟ?r.u[R$I`dSL*V Vep(0 ^"Ȭ0]?CЪrsz>rzeB>?|rPbz gc(D?NwovƳDo^Lþ#({{1@#xr" brmbP!.P)vvg5ؘ]8].LD6jxs6 ^Èz6ëz<>$\q10^x/ A7cHGw݊f# lH Gw^{rUۏaq/r `qDdg ǣP1yM EP8<́1_Bw BP˭Pۊadzw/,^.w;+fbK3}%2G0Vሑ `[2ںM&SL"UU)ec/5T.iٌ3fv^4d>|t6+T5p̧Bc`"]3BEAi/4\_eMp>N)Gd>Tvj*A -bS(f| ڮ;[䵻#ZNrxz)4-lޜ;?:HKb DZHxJQ;ryV; ʈM1>Eh 0kv.vG$5źj7pig7_mך'o'&Vi&T1"7]b5:$`}bd2tlzF~oⳁP I.(U FŸ8I΀&*FN{cC$A xKXnOB(Kj,J9*#ạ̊ՊY(T@#hnh1i!q8*[ٿs^-1C0/[Z:/|=ꁀZ(J|EXs$Nur c+sGVIY:=b2RvMGuZ;oAdb&Ņt~b~zg>Z[@$-y`jG^CCAܑfH:qp5E+y@9Vf"os$FuQi3B -b)w˵)ejZsT ׬ /EeD%#6ȠD )6ey.z[UprLZXs̽c̱^?uPKPjcԕdPmlRxJcW -Ah2^gC1Kj,=W0/f8Ovomtb1"HrV jNr΀\ ZXLKV{L^,kP!L"ܬe;xѦ{܊X!SvWJbN-\]۹kčk,M- PdX2ل cѵf.?v eܧ9,>e&"3Ф( -:uI "$ƈ /f+7VΡc3xCiHPLekISu6Ry-]H $y.HI=994=9 f0f3@ H0Q(QpmSu-i-K^ޭںwkpԤsy;缯:oRAnm] -odmwrr76KZ/~T|LٶӭwT -@hC@A5&z3aTU0MݮZX[;xܭ7($Le gDh0budf. EF37!"|A ٥lu+W9%xj@<@@d r|q=!r4q{w'OwD'}\ +͝;^`Xc/=/<<"Jdby:0(rVkgP&P(F'( C{fUG4Z?Vq*0 @ Yq\Oe@V"rյFws@sJbF{3t(˖`q{ =+F(K;OgskK{|Ճ+gi_n̜_ؾ~|uX~kyIUjG!4p<9~47PLڂ\dg%@YQopjM4ڪ3 -Id'-I/f+csgnH׷es{>gh' 6P!zAPCp|T,7^l ruZ*# -e!HPXcRۙ8f)Ht.ND -#Rk\,$(%0^?x}$DeL˳jT(WZ{=JyE/GWaTs*fʾBo y<|(F"Tz{(I'T,ܞ:rg^xnJ38!0-M]Z|X?۝0$C҅j 4Dz *s1b˙rk'dzZwEFF(HjK7:2:JJP Lr@ZL]FO6jFGr%Y5:'[ e=ܫ{fsp?8ʭ}kn7陝SOۉ֨ T8ƍ9_*MK,h\HWVKSY8OVS5#1kUJ f-]^60n`lwgt--BBYfp&ŪEFLJ$#龽 zecuwangB*.w[vLΝx*]?!;c^uydNfUsaҚ^ H6 DOJ4(c[uEA\_y[w-_L'5 -1(,\ -~/`b0u^رp=ԯa&%q"qTFjh(ڥ̓ gcYʅ˸/ܮ7 P2'0@a^*ϪZ'xBRVUݪ<է~q {'fֻgӅ@ۅa?6 "(lRfCѐKt[K'#Cq: 6w:YR\Y$SBӚة3QԖWvo,mkM's3n0Ix^ˬvz'd¦:A&V2@X_L}^(Ci,_>{ݧ:ӻs[?<6d-[xmnJqwL;5ShtΝ8xW&W5ӟQ{/?'DB2Bx3{ך3Z3ҹ[\zϼc3Nr W?a?JBY34ar dp|%& A, ғd'fN)F1UkSfOR\$$?HHD" -~084C12@'I\J^j@ {`À}y%%e%UAg:BU&M'5FFY>/AZ6r5kǧ0*s&Zf(vOS | -G#ǎoQXő -A8GgG?^}qBKOdzM -@PHG͝L_v@ԍuE1 +ѕRmtN5*+٫V| F2잤7wS$ȭI5CѩByIn$*9 Ƅ19aHJg8'fy9%VH 'VN\.gQATA(G²ǃ -A2%TJfR<پTԒ' Ռ*X;$G=$>g34BC@"EuN>jqb܎6 K 7%R,AY4bSi3Ջź"gH &aBk -|,dLqbRkbv2ttrN"=IBE)jŲ R}IRsEp lEXf!w~83ͱ[{<^ԍΥ;/~g}+w斶򃩙:|}zC`Z ʍ^pfp`4z?`^~(F-'?\ ó_}fo "&Νz`͛۫>'AHs6!T2ۚ_ZkV1Y-) h. )6!f})Y.XH/ϻe8>#xrL3;I:- b@@!6c/bf6~; ?5!C[VbRvT!iu@դ8I[^?+bTa-$JxZP7V(7O>.Jٯ7:JA@b 8H L8m bHnnSU/:@#d~]5;b[u ypĠ&'Z`iUwVe AJVXMm*z0YnN-* >^FRUs4i!S<}9_eIJ @A9͖lr֙{sWĸĹg$Osn>;e{;oB0w5*hM;v*7w?ϼ#'7o_|us|\_PDRyޛ Օ/ -sy"QLvnvȧ_w2$WFl~_,5Խ!(v'(8U W<{aVkR4pWb?{P@bt*7GPD=[tITv]YD4pXH(`4(f3}rӮ ;pc]YN12k^B [d<*0"1Xr, qhڕLܩs<g˭X))IQtuյTW"`:Z$G HOnlW>M$r >5q\qܽNONF]+NV =nNiYY2 -vV +JwP'sNrc̭쯝$ 5@}zfʹ+o/:yC6DfPR/ǎǗӥMxIXP HI`k"Ry&"a XI´AADdH( Pۚ3!icc(cs5JZI<`0UA?3bRIl_.>+sFyvv k  -J>@^L~ a40& qkQrkj49caY_,f~e0 -$ҥGo - -aeU4eHx )E`#(#GUZV!yxAwRAMX>@Ne{L'Ր d1eiis>B`dE@X*x2Xw!51(!^bM4Rk]RK$%@/qL\S@&Ǐ}BcfqzxxhD8È$)Z?~.B"K,JR&ɹT0MNZўn!2ʖU fv P&=CyS22A` KDAA0l -A0/2)xa_aCP@q 1D 9! -D8Pj8ds'ZzQy>JSK*AA:(Ka!R*֬m  Lb ?g8qCRKK<'PHxljshb; C./^Ň lET%2 ->47wt z ϊ n -1<DaFXfi'P~ [dxz0GGdwh`tB"2%]"$G!:gc!9" G y;;2Bb -Ǹ~/C62"OFqH pD<0 -9/7p?ptT RaHi -f8"=Hȓy d893Z*r0HzNfH8E"60F4^>v74 iwIZ:8& E!CSNؙ6׹v15< z9[r'Wvw<ygc*k$.Ba>>?p~?R(q" -I Q$ d)ې3 =x{cG< 99H=>r$pX;G!('4xyOPӮo>~ ah尀#A$3UղMIyR}nnk(UP;~dhtȇ fW0_Jj44ZW9E\%+-nν˳O__*C QYN[: -'&/Zy_<_ů>MIQ*qWbҶ)|& N4+TmF>\1W4H24w IèfҹFH Ńv4ՙT*D( 0Pn |MȀD5lfn&FBş^=ze!!/NºȥL``LВtJ'63[hk/۟ͧN -XD(@Ѥ&ΩB= f/gɭƭ k]Y|]{](K)$+Z3çƕg7v[w/ۿ;ͻX8~?fBi.2'BܬzD`=u[wʍkw:T[N/;_|w.lQhelA˘b=.֔q}: v_l+wc_*p~͏>hȇBap@3$ʐˌfBKߝ߼ýno|s?;>7g';3K-&8i3.¹/?|KO}~w=o{??깿3E`_&! D!_f;}i?/7_+ˇ?<]~n^zg: reP"Hہekϟ?<Ǐ>/n߾_77çy2$,,!#R~ȣ>bR~·/m^NfK(^P8U!n-߬OׯOeo˝p` ut!gvo^{ꏾ?z?훿}o+WJv?  -jQ _~w?}voõ_}{|ifr>T[˯xuQΏ|;|¯ԟ~q營__=|ouÃCAݤ46-ubi>ix.é|iۯ[??~ ^'m!2%vV%?]7~ɥウ[?ۿYoy \OqUD ?9xFc2:b.޽;z?~v?~?kW3(~9QFqճrmLQ |ҝG_|z?}o߽ˏ6WOrո -iM3rr=Wm|EO;sS`4n?O_O>yc/yxcE)4D1).ܶ,v_8++?O>8ݏ_81+8kR:, w0ydo߻>?o~tl $16%XOu39 W*OK?yc,=:|exk5ڒkIp茭EϛJV! 26}륹>_3<蚯_~s?K[yFj X 0a "TU{E>^?z/>~xQ8%QT_QI'z0zz疾7oO*N5B>w$ -rqU)-Lk*N]|t/+ϟ 7O]ZoNZI 084nFL#t­8%ˆI9M|btJbaw"5N&` -(c)K THg |ۖڒ1ZR󙭶yf:u8_[&Ɠj'nLG gۇGcod:)sDR6 :'Y:LpiśέN- Ip qa؄`U]i헫{D S0E%GJdB)QN)QH`KBa>,G!,r=d8!,q xH pe+V:Ga@@pH1PXCF" 4 &gaDJ2I<>:B> ybMLѲ( QDSY\4bAQ6Yl? 4sXsŠ'HK*r<3KR -=>x`02XqZ Kuz'D ?`HE!BU(2pep"IME #Ry6 dɨ[j%ӪJ.VBW,_ĢuRƢPXG0A@@@`LYN B\ t5G!O X>>2 P$iƗ29%ǎ0,-Q!PԦq SXS5AbV H5Q̧s _ `P@tظg ڂZv%yq "-kfG0a$ڕ(h4ӱBߑvqq$zsW/ %3+Nrqħď6:B= ,ʍX~=_@񣡐_z' "P_ь^ dCd#CQCtUKl3{ <"ఌ#K)8F ΦX>rB(%d1*9I-f9`Dؤmt24&jckéL8,Px2ٓ%P EbLN1Nb\T+(v YE-S\ךh~Njv2sT Cf).XmF*#T3it6dtCZޒWiyR=^?A>.$>> (D~)YX#*U"h44%d$rl:|>%bG܈_$ ؑ~!YqXL19ͣx,_TWGlMbdB$G64)J峾ùeKm0j!DW|a"VjY&Ă?>@04\(C0h0rFx(82iu#=P)I-M-CaȑF@ -DC(53ukL%+]?bJxork9j ׌vdbVgmXF/S\:[]v>UV,+֤bOcL "R`uMΨ@HՖ2K$xF(,b"_ɕ7ӅU71OL2)3s[wVodەӲ\M!Z3(lV:Da!\FȊj8T'&w Jy` @\N'$#@x^81]~_tv gǧPʂp L"-DaYNXgG7[~G D-\MzP`55 @ @[ƜB,O-zEAJག3FرYc-kZ(C -bua5*(գ3P Zn!G`)hDwd+H#T(J-^jf 4;9r{t@1QiiVK^>5ynuNͧ!⭉*k%WFOܥhӚ=$eWEfJ;%YX $FL^.ZKsz //?fZC`-KLp8Δ}-_XuވO`A.8 A BDrb`5qBK^b  #7Z<}~|%͝dngTa"FV3Vl;* -/ALxP]#'0BLmɣ!` ,IVit,I %RnzpXLdͳ+7i)cRBmڈggS.~\lFiÝ 0 - -"k`k5?[란{{6-T%k\OIzevi!)m=1gZ,Jg˹*J#^`R8 AzgbsS uVN3RR1^l |e'&G pX0D♅76N!~媭Υg L޳KXqz߿ʇ dgUkdž36"HRfSk+ϕLd#>*t;kSӇBP^6SZOblOps $ -2hQzͶuY ;6gǁQB@0|2$X_ӜMW mK'@  kw$e,ht熇Pԕ櫛>|#; Pb;bŗcw\}j[5Tf&JZd}TK|i91EX9G-tiܽҘJɵ[kOE;,Ϯ\;{^:XgVF2T8,njs+W$ -f{}zS|s(JkFtV -z%:3hsz1yaf.NٍKɶdV=XClHTq=_6'V78}Zhjݬ9ŬbNSda1SZO`h'厕BZq4{*W=aD{Xn7'Tgp'x*  %ҹ+OuRt&~ :ac6q:qD+/ΞFӓ+{׫ ی Csz|@$ -k XzbK60DBp30@B.G_PTL<9* 9diLbRS8Zݸ}3AΎSg}@l52l}7]ي$-.fCw{V.9]J(fkӫw8B:ta;wU;1.řS=N̥s?=|qݙIUOi$ $-DaKuHJM^~ꑛ-93R=y[ntz{EF-:Eٝ[Lvc/NqDFRl6WXԷ6&\ Qi/^&$әZ{}!(Mzݳ󛏖;ve\;/:_|?FՋ.zn_V2ׯ2t͉e9.6'5UYNg=d(k_N_Kr1>ݲ1 mpM>H!_`NJލ 粜 3R.4n<7V3֜qXT7zsMoug 2Vobx1 ,jJqYSD ̩b p_Fiz9 -gx KȌM_mG2>Ӵ $ft{vO;NpXަ9r}ՙpi`WO/>}bznUn4ּb4iNs{,W_W+V50!/2H_|k d1gˬ>t0\݂&Ս^ϋd8yd1]~_J?zZva'/-60qN8>4{W7WZȴgu~'|8\~v(4Ma ܽڮIӑo΅,p4{Yv.wkJAv qٽ[Й|Q?f~4)R2ȏ !x4{6{;*HP{pW6m}OeiV ᛣo4Z]-~sٟ% ֈSI:/>ۯ;lo!m t=7'i:wxA^^եM퀬.:˳RsPI!5y01=cZm;g>Q~u`zW9`uxj<˕a/z#{L+Eu7kUJR AL;ח^nj/`m'W[@DX{QZ^mk ;ŏSNq.` L?9n-ƫHѮ[/+Hi(^[LDpdg;QɲlEo<+oC1Q6[f5 g?oqФm1"w\P2ƀ!oV7h6Ŀ F g >'{zOp &%>٦=B0#q -8h5:vV]o5]z?Nϫ />(W^6ݙYQz:/?dKN K+݄_X>ɧ7C틂)adq'h5oVVdC`/y[C<&JV(LJiRg~r;xI `u H1CSrU[Ny /m͖>_]}h-˜P"|wWXJ5i -5ݵl ѭ`}Ѻۿ=:S4B殖_8c3aimi3yq+JL#v~f4{ØuR]Չ3 7z(ulkSE+gy&-oQv)qz^4JԵY{x=`\ƭ@/0-qqg3T|[-*t5D>-ezN/z۝Οeչn CKYhޏnYQ! qQ$|N\O1<67[>ͤUR:|iCI&<j7+NeCœBQgwŻo˿-,$dWAvWqnOސ2ktV|ڒM:pT2<)2ӜLJrR!EBwC7TQ_bY .D)Yw7y$W3hVe5#%:SLTe1r̎/?}3_[5BO3/kXP6 ~;#;}wxv< -ngYhR M3f@f.g= <77?_}O@9޺ݻw)YҴ[T'SZ6'5k -0Mr*OϾ;T՚nEu(EM{q]^eoSY462_MWf_0'6P<`vXplz}Dbɷ5EjSI0}J%-m`L&{/N}8;$to_a;(G8T*gyj+^ -0yĚTG>wkp -Mj5~86UレTw볟V}yjmxsY-6[ -J@?p&RA/^IV%CA? iFrn}ß-Ay4]"c#h_%ul(ɽՋsHiֹNCm h~J(ٴ$t8hڒ( 'P,z־A -;UʇV;E~!B~oxs OAڗP8$H#G 9֬yn3ejet Z8NBǝ&36&8Nr[vy]^ 3[S(./xOJ]'|ΪJY%:/@xUUu1>OC$Agx#$pa):7Q~PPdY\T jpĤXmrgGQlhêw%~qes@tgJ (*Ѝ) R -`GTrQneɱn\fYS\;K}R!HJCkC;@ -9ˆ8[0 lfϴЍUcjd4M*ۤsӟ&|Df\B²A{@]^ d; .XZԙsd]ϻkM/t9Cy=#c |Rw?*UmD6eW/{ (#JTz>qs$٦]ug.M{:<gA M}H.ABnk9}ER ΰЌ1RTGxR0v:;UrfT\d/=U ͇i ӷዛ?G{7BHU^.Lj9p|ޖ7Y6Dž>EփՅw+Zo=5}d$YHIȷ&cu0ݛYjTzud#,R_2cM'IIwxl6 ``Mk,ZM,r/ s\مF'߆0.+f I -e R,<ڪ^r,E%0{i~<= }8!fvزge"YES޷+1!HtՋ0>^F]$!89ˋ{=MV rF)Yl3^mپzy} ?I:Ya7 -~`<|^Ӎ\ì@vLÍV@щn̊yŲ 0,5Su\Ф^ goç懿{/ g]Jo#^ "Tٕk5sE!r9/";Ig0^hHTQ +/j5s)x6N.qOHOvIj[M^3[x$>kEPSPeк\a荢tOѓ;Xje70\˪w&A2cmpf: ۙ֘fݺZoZP6h)Y[°ao?g Ԯ,#]Mo;"ݽ$i1NݿKHdx?κ{ÝH쒤uJ#N4\qIJ< GT^ww ^!jXs]g XbRF?5QLgYp[t`Z%n2.$Y E:_ߧ.cA`!ngp٢"w TseUa E.H 1Bv|c5PJU.vhoݺ#ʣ}l4lIwU it=rUvoF7L"aO̢P@nQLOHQt`83-tf+Q;`Ăb̡}i<iMڽg:>$yԢc 'd kKrö5 F3|E[ MKm7|]N.U@H'׫7K՚(޷>Di,:%)bٗ匦0{4I5kq8ϴ<\t%fI~$i %4po!ZLg- @a"^x i>SXoE ,ɷZQκ[!аCTtW?Ken*Q۬!NnkYôX3[_;WRfӔƠР8묺25a+\8nx>A0 ,:5CJe5z_;;: F݂04l2{=[u qr$_dC/7B$ IB!x{)ݣBVA|W4.Ls0>+lOT*A& h3/1A 0/%Q IǬ@-`(} 2r?~\Kr[ߛΐf:2(f/ y΃^U×Y@&%ȩNcDtN 9O}&/ I)nq`0yky{=r&<ٕD>˺4eP-uٹږc.k?R-l>C0\>?u;(?Y' {R #P5$Pf vZMDSF_g߾͗Ж4>yZ$_ yy1q@]\X4roo͟ɿ꛿fLV:s5APmnp_wI)R<;9þ)F)o9B}ĤiM 9W.8 %6>7+5b@\>r_5vȦY{;d_T %Q.x5?=Z-4*xgW1AuU#ّ\7?>q}^Jh"uӽ͗[,tHnu5; 1v \Ujr>92Lr+R-'_Xl otY.epI&ee, ewp (iy(h,Fp .9ܼ۠f˂ܭk5UKdՁ,5 -؉u}lrsd(:x:LL46Z#ՙϋ|ja۞7S.(5kF+/d-YqJ+vysÜS$| }`cP"F-ȭr ,o!RJߙ6WVr^$%ID/frPO _!9tFbKTk e 3Vթ\}־WtbZRWpM`Kdz;YB)=Y@_h}zWD+NX)"pz dkЌ A0Y g4[7g׆{(9d[8; 7WÏג6@0LE8Vq-eApuluMhX-*h񚕊zlR@[<,[6Wlw#$QIٽCOku7xHً)RΎIJioxwbNj VIvJ*/>_,*0/3X(;5)Io4z9/Gˏ֛.91kZO1^toWF" Z3Rm>Q-ݖIxp֪9U I6Ueo2aHE9rq=/FUEkqVatdq):{7az>Qqn`8?N_ 8da Rm^VK/=.}!15hVע2=A/zBH2fp߆k'rBQlodwAFVHf >-e)$,mkz`H+S./\[2ֵF:! џ<-D]';_V;K*,,6{`Z :s|z41\6kVSJ@ [4^*B}rQCO.˼:ROCs"AUht36f `2!Q\v/+v2Zn7'HE,wwӢwx MA'270>]oj}NǓ۟~A~7L,ϓu \׀0:j6a Xr^H4m{A.iYGI\r|4}]E鱬 ,wGW7/x/KQB5 +Q{^třby[$=( 3ޓ,z(ipIk'X\ΰ9冽9%@-ͦ*+i=-Qr ].I89NNf[o# -gMy.&~z7ˬxf I78B4Ώ0ǥ*̈́yqzKAHywT˕ - `SIGzN$TBw0G~|etB%vd}@TAM:dŒۖ9L20eo4@G צAY1Pwj9?_B @=,} -hoK/ -GY3lH!R&U|rdN4{@FVZV|u})lt.5˯6 WG5TpIXTJK"DE^,&~8RH-uJ Ju/>m^:vj$ד;& lߗ]@B`twp(%˴Y5P 6K `U(,v+J=\9.ٕ(ڵDQ>,>f/,g[ww5<̗aO9# *AcZCX0_ɅdQρ4o - agu BZkinJF.h.X72ڈkB1Y4(ggYZ&_'{o_^@oX̚V _xѭ6F] 0D5dX O%GPǸ<m`(1ԝ)/-”DuhÍ8!wwuImz(89)70ȼ\Ҡu*SpӿŇEytQX44'd=I*l{{cwf۲w%qʟVH덺!Uª5)ȧ$/!uv6?$խ$$;b,轝~tã*E~xw +}4ЏN0U)^y4i((Z͗O('QxݝChѐo]BOؑ;cGZ{Ckyh#=v䡵ǎ<ؑ;cGZ{Ckyh#=v䡵ǎ<ؑ;cGZ{Ckyh#=v䡵ǎ<ؑ;cGZ{Ckyh#=v䡵ǎ<ؑ;cGZ{Ckyh#=v䡵ǎ<ؑ;cGZ{Ckyh#=v䡵ǎ<ؑ;cGZ{ҎB?0_ܬ>1/^WO&0||/r<i.Ggo^}%ۿXƫ?O~Gk{'"~oߟoˏ_}sy4-ObyobgsH~Ix/5~,'|(?}˟7g&h'DzW'6x׷|N:d{Fہ]|M?f!'7O^9[NS9gɵ;an'LN,1~JpI{{깼=؞,\f4EFVI]AJ5nb_熳P$37 ]RJjZ:4^Lxx9 +-JoTuI)oŇNtdzkՙ -j(YD+[IZ\k(ug%kC^(6ԾjNuk'ޯVE 1X"+.h;VHD-nwhWn\g[/=Y)g4Ϊ˴{ 'K{42|ƊLMoXcEʠݾO+Q|ڤpBp5ǒG֤ӹ\$u-ʫu+eGsړbP'J7&6\V MłJKY>,HUln]ǣѴ[-t$U`#vE!n4ݚԤ, Y>eL(gƐFSc'hX<_(XG63EA,gfGGM9<〪9Ŧ06<tkIBBJ^RӪ0POLw%V+$MdBlNY?gD|3N,YRѱ ;uɮc,h-aȦ;B|cMRVp7[E;%͐fR#4g  B2N6I s./~;'I! ݙƑD<)b<}6-h{ -"\霟+ 딥("m+jGVR%V@^RW1~xmwO#ߟ7~VjM0򦻌ғït#2L΃R8/xnA#>BJ -rS EYE1yAD|╛@v<=],߶W@`Q,h/@&.Ex!eV[6E-c-G~-6lr i&2k7i G7ZNBr@0'Dݨ2`sLs׍FVzl$*}=s=bXDŀ#*^LEexGyy/hHj)j_ۀl#"(Dǂ-X6Wՙm4c=$B5Z PZ$Eʻ2R -:m@V@y0:Oh˟%CJK uZ["@Zadlsd۾3~ЭǒR餴JP0D+[ @1"ZoYv4 -8}[ҍ7CIUՕ/դ]A,{_VP`)~i= a2H[Ezϴǖ7m~Tx鉤"ΏMKݟ燧>`#ߝQk !zN f7=.`φFA | dTc1Hgq/26tMlx1!Љ|
p )FbwWk5fّYm x+&aDpMp se~xpۭͦ ڭU{k{:l1!2m1eK5Āi9D(TE)9yk& < \@Z@`kZ{m?Z]O_L!첬g=!Lq+7Lڦ5 k1w`{]ϓK'3;~'492mǑm#,!o*"m82 !蟮 wvB8F+;nlǰO Ntkq[-Tj`mc"V'Z`@VP`ͦKQ˦ -LbXk Ak@R 0hYFLpp (^uPE^Wb;*h tCq#_R0,Qjh0vx#]evnFfMJ.f RJ{TT/ XZFiՅj`͊E _l8cA.ա5sx'Yۆ, EA|\to7߬?Zi3;zf1E5C78ͅ^px+hՑewZ*Gg^eRSYGЁIqS ^9A~ۣ+<ç:]AQ,_wPȱ.*:h"mjρSf"5Mq +tHhSMJҀeKn]nthQ K57,s -`VziQ^"ƚM3+ nIJI1-!g!#ڒ4E%fdj䮦 qL7ЂjB6QMV3NHQ> 6j1*J%rp@RQ[Hj#k}Df͖A$ ̾#]9fwz={)fuk{IvܹѪ\on3h,(Eɥb bCF4@+7|ryqq, g2]KZho2܂$ip"@[xԌ)&~+@Ÿ }T㸼pj58FD]r,=CP;.v6b NJ˘6(Dh .?([z]S - -VfM~H{ I2 ךuhoIokVGJZA~ ۮ>1'vLƋ]8>vEue 9tL!#Abm\sk.ۃ pS;D$Bj~|ez'ᕴ v[ϵb fz~pNJdBq@GAxTo!e#E!)f4&Ir! k -75)Ĥ'pv힙!,$IWn MPdYR~ -DB`l|"J@;!Ht} -e.x>+8<%i2V-m'&0n L -iV]рK I5\U - HIYk[Ȇ;E]wmA#4%sBK 7]䍠u8Zݬ+2vwɷ(KUx٨ iJijp)vwp[3Ȥ&ȴ/P+AT+!JNDc'W d lsɑҽxOQږ5QpLV\r6읿7Av"r@"ڃ3l{FE͚.%>WS0 K?J;xm;Ph@Z;IaXS!a>{%#YsRKb -VGxjQĘA@ -82:='íkkR ~ -B ԤW1_}NMr5L`/H3:)ڸQWj5εp_uc'wj -lj=S4eȒ5FljN.yy_a2$>-$\7$"! nM%O]fpkH)y.vm/"8d`QJN\(Vju \(Y]^"c\9GP=v&չüts(kɾf 4'Ib0m sqyR>AU/+/;ņ^biA;>ձn( b]֢2" `3=/:WE{C@ؘʦPCP Rml,L^6%' Ucrдc:/^֔>q>қMɊYb2|U%0=E;&So g<$!CMd?: ,.fY3f2sYO $WmT m!  de d <ܨԃG bQr21ѷkLwvO>"8idAs2l<ɢG,)V9XvDzf*V $͟uG= @0 $Hs9˗`m|$50{52EZޅ酨ua8h-ȥ?M:oJ[ AKd88F7<eHmI[C-TR " D쁨^2Yj$ 33cX\U kHMhkG,0S0 ؓINd@ -&ѨkYp *YR/u$|: SW. &%My@m"` Kĭ(CO9 @C&' <.,a75'2PMv& DĥY4 -pLwMTW$0._ٍ 1Y㳨5-(ekNIaNgX3A@{6SPlb F:qR37:t-nC Zޞhhw :crzCZ Bl"2qvk<50tL" -vHo:Y%[?8#+ETZC&.v2T}['pv5qޭfoc܇e" ϠM,b?EDحQ'W'BzrO!:T 5p0V@hsH rrفC$oG0 <`a :AVN~u f{)om߄cX- }BڄˇThD1}{^ f, ݄Qcy؀ 1OIn~pH#ssB~?4HmăԴ!,ĉӭ@:ܪj,0Ԁ; AYKJVDA90Σ4  )Q?< -`<5`WutaAo/CD}W4hM`@v%YjN;62YJA `zoy_R)CHP`0={!]:̋v{dA8ƻ(q@8нWr;Y@m @9,LS##}8!J/NI2'Xtvo 5Iq`C*м^\^c ^ހ4/T;"HkƁjnFF k` a{az%= jQ~Akf' 06FCC!P2< |& ~RF/(l^?#O8b5׌f5n4 `s|o? %`R `|ٶ?{Z[@ -_oz}`Q["BN8iWD?hNyGYVbyܶ YU[I1xir@*~m$O|9]BOIbfA4i^UV+ a\ܘ}^$8tPu2 NP ~֮SAlC?J}[)70iML!YGIL !.p0xgECıKP^'Wk`Af9m<I{Àmب] ]"#aV2Eۧ`n0 .5qګ(XlOChP>^{ 40(g\E OjSP*JQ$Wl4kL,Ѵ.,F8k h9ik_ªT -AQ{b{30TT̮rVJe^A rfJ!W5hHK 5ؒA\fbjSEq#,ٖ!Zjř ŗY-GUkڈ͊za՚ruii͂ZRA,ڵ*-IctqJcQiŕp*+ _> -cx 1`*eY-K exxM͙I9FIYFj~;U[nn[YF)31m%hvֈu (63#bFB Y Nj؄bv(%S x LJ3_^V P)p_BЈVtuߝݼ,lA e] $STV.Bi[uJ: -ޜef؄;DB#~D--m-c$}$cTSfť\}Uԋ7-Uc]35)pr Z*QB`R -|*Z=Lf+0^Wf,h$Wգ0&ȸՀ)ϯzKPʴW"S>)VjEc)82L\86FXJur>WjI}1CP(╚'mȵ)b PdrDLb Ϧb,; t2!hz&Kj.BL0(l3vcNΨL}mXmx,Y^f8Lmwg*R2|b4>NB!x#TRnVuW/oP 1Ikҳj -/B-BH"0-fԞ93uc.m^/.h1~"X8i VT ahXVz&#Zxwke-@c'iY3fqTһ<$:J0]/~-n3=ɨʋʲJggn2SS8&HܮtFS;7q1iլTeEO-Fb/;J>bSVr>ZuG|wx,f)עSTzl..ჂR0JM1Fl2S*6\ӊ^*EEf;1< Wa,=WG5@Z r8>/,hѾdzrbeZ(/WT'+# dO+;N}Tv:`ON[K7qzmZ%]\$<- $jpޟ= jcfgdkDsԭCJ1>2 te#M "E Nȸçf6/|A d}pC%JfkAlIdpe'M#>DţIO;ܬnNHz wc˺*1]l[Z3㷞}VOd g.No 7./29}'7~Sdخ6&No\X9y \gN/48[D+K(iqJCME BS2Z[zGKy#֍$Ġ?w9Sc)NeUqa%dnHTBI`I5M砵RM u+;CU=ޯ6yA+F. #ڪT:axVnl͞779^nԶ;[GpY3˵[WgDYpQ ©ҍًvEdPk-Ai &a<3oZht6rx2ZX3 Z,; ꋤ#V3SrU/v [Xvh[\/Zp̧RmpJ7L.^^+7`q~s炨~RlnO.;yݽ=A<0: frr1u]s|8:y칛:mH.$r+sG3K5c=SHoF2 j--|;ǯb.Z3m-:IEIn9s=?tx nd&B5*DuUUI@m4'5L'V%qʔk`[;,hURSjfbլ4\{؍-6Di5s610~u@Aжà-,Kߙ9hG}hեCWwfu=s7=B=҃/-5sr{7>yLNqJdz ѻz#مsuI u6+/eszs`b -zr -8Qԛ\gB5sNTD*~rr+#Dj`Xpwx*QZ@LRJeʝOiV%W^М<NJzU6j#"")j5щp//p;|O=|p?rrhaY|AӬPTvN]ޏRy -$\mFmpfbի i׹B}m)VTlʔW{'xSkGn&r SBQ4;P)6e}$\m2az86U p3tHL'XhEy4'Ɠɉ#G[/eq$iN}rwf֩;#@Xb\Qe,mo~R{X_uwO8zM&6I2xn =<_8ѝUc 6D3]-7Krd@DO Z=,l {é~w(W_gl";eA z`㾐ӧnOtuJQiISjk(T[Y:xkkHrurbmTo}U -sr܎d֫SDznLbu ij}]c F*mXl2Df&N{ j#<IQX>v,E:}flC8)3Q[)ZCJH9('b$]G15^:rS6D4M մVIҕeEJ.,D}U+ԫ;%JR䲤7@ OZl}@DžRw۩sFc{:Z&=rރg)f%`4S?~m|y95GR}=ѳCgOlb>kNT,7WH⾠rs>t.Z#ΑԅH~pTVmcSH95C萤a*4 -;s7 bwr7Փ'oI-YZLZʧOޚPʗf/4{Jdrr0u'bxu綉ًԼntp+\yAT0S4dw%^z=dSp3i>/5Im$2Ks+@s Z - q6V.K~<$ FY!j]߷?^[3c]AyZڸ(O-CTkfvi&r0)܋g!q024 Y@uH&vx/:xin#H&OOL͟vcJ2Zy 3GX߽UrđWyuh/Œs,x/`\ t=~7}.ӓNL2#`@=!ҵ>}pqլ6R(S "vH^)drda^Uˣ)e(v7ĩx}pb4$&@[}} - 0XfW4%+9[m b9"jE;ːTfو94:H[ [. - `:ed㠩%ì: %T(:C3Y+ ezB<uIVE#n+ 凋 N CU+oX&lqݾ,IbN6B N̈Z W73Bh8MрBh 0D(Ť%ۃ8{\cMF)|d,( StPmk ZKb!db63 T -y=ZCHexͲu4WT/dd͖E=Њv7'N.n^7x߉ۉDܹa :#mYF_B3HFHʂY^I W-5ltH9 ]ysL αׅi="2$^Li6]]d) A ì;`.YɛV[p MR|i$81w)ܡ?" FY!<&óݩ*͕! F!DbT4=ݼ3<MqR^aVrrF Ie2E̸C - 4yͪrɛc en8$eId␐γlD gєm;st=..:qpZ60f4:) !"M GVFZL\Ńg-'X54d hGΗW%d- tLWY=b FR?G*1|}ϵGD=IђDD,L2gp2nu±ɽs -h`l4l2"ǀDNWV\g*JpVĂOG9&f ({^3SCRqTxĥsLǽYHbdRcc8r&Șc߄):GbZ:|S%\~oF1ˬyJEih)' /V 8! d jeYzETʝroJ5R~:+Ɠ.ׁM[vǎ|YzrEq8ajXpd<8X-LW8ʺYStYY=sԅW{>'췾7xoϷݗy3msՀdZmֆs^#7w雮\~/| _;U3*+U礄adj|}O~7['>Oͯ?귿virŬZ*Th*[^Z>>~;=7udԅ'/^~֋֍35t)UB"ԠV[Hb?hO3ʠؚ9t|}?_o{w>{yRE՚Rh+g -J۞XXX9zG?O=ׯo-w.,D9B`HYx'D,6zb?]잹~#w~eو'Tr*iw=?;?wܵpxK_oo>'*qb|f -u[r(4$RasAQac:G|3~C?]ZSx|K/~~o~??WO;ǮvCh t& qk-?W} s/|ӟs/W~O~c|Ҕ"9*N+nbY#~ǝ?O}'z/_٫nE6'M xYX]=x{cO}~^髿x֛7dO~/|ʬ'׮i&babzGkz~{[_7[|?߿g d]Mx*Ufg7Opz﷾CDo~KR;x X"bءwyGǟO+_嗿__ _◿xӭwlLjZ>k*|Tgv6sҿ|Ƌ7x_oo=+関e5z8vӧN:vۯ^{ʷ_o߂dyK/p;( -dzk\<WܗW~_?xx?'{3^25CVQ&Dc80x>ا??/~_߼~w?׾Ovzk(u"$Ji'>>~w;O_oλo+~/_S|-fsmINaH)/o?_~Ͽow|/[?˿|O=KlAdЯ~`b _?gv)Lgč7^y$Tk[%թM4J0=3X]_<=~j%6dPQ림n%iLNhj:QMM=8pn{/{ݙH x蠟yJ]_~䎛/} //~/?zɃKvRi,'txZi)Iݭ?ؽأҳ[۟^?CWn>j 1^HD9Q0K)3775xɛn7w/|~/=Ч?藟rn![gX#"\n6&Cwq׳ȇ|ݷ̓/ݗ累=Wmԫ \ UQ$Gi2N;Ɋ -!>0TnjpyR)BkH<$+B0Y4QP!>)?96'1 r*: 膬3.3l>s3Sc'NnR)Gpyp0 ~>VӑH$7c`Gf8JdSr>YFX|j5 !\3A9.i%&>ԎVT!g)vZMea3mwVV'[Z.>meb vb.G9p#sǦ5X+ sJ5UMgjdfU,/Lt{Rh$FxGG.'؅4J>6+Ϥ``ALUR:ՙ袔#resRcNie scXZsSiS -& Q$v\58Qx.IGd 什Q)RTYZm2EƻVBe|bSRPB! 0PBORIH h]1"BR&Z>?v~?QaGV"˦x!ㆡ&%s2Iu+9$h`$$:s,w!^g `A6$>B2M~ܷrG"h( >$rx*pM[b\qx%RD[ t0jH,G]3>>")Dth0Ze\a4Q`:?;=3 - P祆 *Ao5kDӅ|e3u֌M\qR9u>ӾB5pLw#αP);E#AMѰֳdbfG$)yL("0N$5י(:6w΀D)^T:ZhF0 ']nx,FFnKQ -B¯R\5GǐL:gUcBZZ$.n-rbv|q8B. j*NL+FRt=-C| иL HwU/ru,\h4@eL'`P״n4i&D[TzہMPD C IOٷwG B@s3jy os0cez$FS8D~}V - ZE\nF.0 ak:LIqimQ 7<wX: -0>Rn dLJ{,]" Ig>u|Fta7 -Acp8NDbr>-lHR`NCuH :%MAnk@+&+ NI;QoJML3YVH JzE4_ -1VH@pF HXDJC0@Ih%=NVOIzcRqB=_9 Qi-2LAڅhD$\ 4f45CHf3a })8 xEːbD^Yd ̠cw:`(Zi'AtC=ǠI 3L@Vp, ˣdֈ9^*x}}Y - 61ҙ׸F0u} P0b߽E`.aTncyj?d R' P$e2pMTȸkנcDd5 B -b #G$"k3 1$,SwJcav`s17PsfNM riqw_ - b۰~0 {Bc,fI$I@1Y)A YMrY6k$bĢfM&k4J4:z#aRQ#ѠCq\VJ lKDCa\CRSt3k˅ëCH*M3Y+8s fQ(M9Ё=uLoG2h*QB*(&6:w`n)$mpBvai;߷)8#ɤa)Ov4n| H4؄(eNA2ՕK1t'm8]8rP`̱ E'RDv#S!bYYXlƜs>w~f&~"hH{A0ND«kto{|.EH"]">/9I*)hM(0cRgL)llo]éjxl&^P1E80@$D:篫v3W9Hda6W(4ʝR d:z CJ"B48O.JJgoh 9Ycdffx`F<> tF2dR,r!j# DY}QobLƏմwpL)C ,N|"x YvӠ]Ne5ns,]-n65`&ME!<˒}") Q1|Xq/bQ~(q+7?9q,ҁY&EQQZ> YOt dkrrH bc!!$В^ `9]G>(ЯQdtQT1bاD4 bIj=Ua%>@7^|!Wˤ HzFTw|$Lf2 d:w6jߓxfP>{nN+zn OgN&#>9[}K:}]D&pSʲLe -CXx>LGSfhf1~#&G=~ Kj4h^_z#+}VzLΉf߈FKL! -{D3m_ -jԤ ].QĢ6pU02*n N5R}QP oeZ`,9JC׹c-X Vy -6\.6 {Tz՜" J@*d!hP otpxvH36IU/#]7YٷQ,w/KiJ-^U5c8CgI*py}4@PǨ}( -}ǺGGn70'ɹp| pKx?W=Pw=5|9L -bj͈tu/K _q8`HKti梺Yh bch5HL+3 gwOI"}o\V6TJL& 6 cxa -EByI1cŜ |m6h'Z0$IݜY>yWۢցW''of@D *j|4YU 1N^J-]<{jggO@RXT'Wn\;`qT(FeDr Pq$[_(/gwP$pbtV0N$94u&1VKjYڪ낆lkvsC-'v;=.,GXf4_“\iGG OrBs\md8 !$.{7Ӿ'.{5.c(@8 ^'M%}hc92sR͖3铷hIb`" N -OHu8M3xT* !ϗI&ZajƧt:=u * QV)vb97U(gH*./;w!^K1j}MHz'[9uk,9&FnHu{piA+ݣ7řT8)/z/[Z=KZy-*j[ҚD­Vmhp-A-Js - ZNnw0_2'>/|/N beLs\5.s^ٷLkX6֡Xi=@Ñ×.\m$>Oy;u޾8n' M9ĆvMX0l2٥w7sJHﱯZ%+`&zS} zU,@yZo>g 1 E J8p%=( R & -' 6&(JHq([ NFSsunuE#/T\.1`92,X%앁Jɥk>oWcJ#iN[hj -T 3Xr*FRD @FƐch1ta5Uڈ(& `U-f5Vjt֎ ,m]n{;웽wCٹKLcr۾{-_@%o0,řDSښm{\5"-cF 9R,ZX9vli^#ɳ@Zz>35Z+8J=:'2ӗ@8p|=ˁcR4,}F̦)CֆJlrnerJԏ9pWH RG$˕eGX,) qx@HXa+YJ6H""ko|ܻ/ O۠fmvg0ĆS,0BKZ]("&qBx`x#^PdKCV='^XTdl˗X\;ĵTa~i͵ũTpFVTR(ZA*Ͼ# 6ÊeI}: -TNR5ٝ9ޞ9Vj-/_^0A4 3,FYYnR h)gU0A0w@v*05{?I秣yNbj@%sq:flZ1 -n!4Gq3KQ#`JG8_WW|~ <kxʒ|n.]H7LU6!m{Mqh~JWy_ɠK!%#Ͼ* -N*)̨Vw+i BvzoB`bnB)" Uc:W:Z^/86vAys,7IP\ ۝ Z,ϭN^}irsw$Jq`k7Dn2 -Beؔ' N6x3-5͐TR -p"#Rnw -sKg6.Jf1⃱1&4$WA>Ї8Q*'2R[۽HB饲J&&Hay^ΖG2Ph8a&S=_x-HS onNO_ #e XMT[׸~M31tAp\Ō͉J"0Lq(9𳭅^y4=3(FQzMzl2Yrk$x `5p^,JU -d3: 8+ߠ>VZ$n$9^8X: o2%I9Q.f;Or]?"sst03A A E1D(Y%[Az~:m[BU] {~?s_w6*f8s9K1ଁ'f^dr q"t)n1YNN/_Jk<&dP /yN#(UAq0=&Ce`29@]F !HI/Tﯜ|h0]+{N|Ep]ogF)T QK3PEz4FE b@ BϰxLޡ 0^$M)Xn_t9ʫ 9>m5vO0&C8jIǽ8%[Jd BA^č1HfYV0jB76 -: 71)% %E^.1\lu;] .g+ۆ׃00j4Q܋DM`Z#~<: ljq}~_ 3R0kKgI.IoNwO./_Rz "1W3]cZ4Jj^DF3$fGlw#J-Ahc:/չ彵[gl?\sg5^LaQTJaMaX:s 51,!^H,H̳9Q|B -(G2Y -ZݰڅK/zUY|Ak䜮wİj_; lKr+P" ײuo<_\8~sPD8 $0R_<1x $,n|>omMASj}'esFS@Z5PP)ZU >B>FC3 Q) 0ѡ9'I jՁ*VLX9%<=n~ n q<u@%~s4ƿEU1\[^^]޻^h. -Mt1k;6bOhDcPW3וf$P\=3\YvXn (YX5.f "ړ2A UQKD{uu )'5e˽iTƅ|~ -p?KD,E +c_(/q| KٹTf ;tz{ -f:yؙ.VgyR` - H2=LJbc(̌O?8 1yh84: :K!Dd0>Pz&Zk CNQ,׸h$DЄ`G 'd!-,9.%{}{qH'(Jk@1֓I&ǡ\Eʶtx:d1ZUpz:}gT4.Ar!VXWovizssrpu|*.qB._hf `lKApgl "erz;}pΫ3KWAƥ?ޏJ> 2Yl(bq M-M^;"A8GhA!UCѴgc~ݡ!&x 6b! L@D#Q]VVekɷ`}{b";By {Flן~zdx(02wlb9wtYͮ@%^-8LiЙٮLB*.vde};1ढn6%:S֚bfRlqFڡ/d .Nea.s5 -EQl_okVֺ vnhf(YX::P&*577YRIUJJJ~_(*19A* Ai+ %;+WWY>럘;)HJ xx܂b&! j`bkp{l&N$qӹҲ d "*fډF؈C̓k+M6m;qpged蔿3l"Ɍ[L8DXH Dh!Dx a ]pyPc #}U* xer!YYwxw5!wfh4H)l2 W,9{z< bQZw㘙(,x\gStto{PVp%.]I$֮M3uO=T($cIPKL:3 -'=>i ׬F2;V緶- ]2v-)Z{ʀm/!S8`];W\Η!DA&ؾgĐd,fc4+cH zWn\xGc4$m5 HVZw,Whylq,e2PՑLdW7ťSw}sbj/푰=6ґ_"{d -ELwISwx썗.>stream -b=-'ršv1QaJ Pqh8"  UuRpSٍkvўگw*XJpT|wbl3t?v(duMu7V/FNTۅadFap Š;iYx$D~6U쟹}ג~ܝ}2Wj)ֶCXU3$$u7bR_.FRl.6HVOOwg.̮^)vX"+ə\y@n}3o]x1]Y6&୬iV A0bهM>Ð^4ymc~U|9 LA 5ըT۫KWs5tϾڧ]BQNB~İ3$xn85T5k2[hU'96yKnHkkϾw&$a|ᣯ]ǔw1Df*+ɝrsk<@Cb~Qyy;9>gZwS3ɛ7|? Wvo`Jѥh:~,@r.3(<&-F:䁮Zg5y^ʸpt N8эO{ιWV6זDxpy/ Ed&I#~7"-6S7%Zhn[g/ũB{+ڶ b ;;)(;aB=8SV"HyFk\g26AѬGսV/]iLk`ꓩΉû^a*-|9%TNj(+LN4*2g;m^(;Aʖ[=Af${n8yX=q5_3:Jb10ǰjcZ]N034E&bb\2Ձe =Y..QlmO~O.-CO`# X2OcB1{|>(.L_(TWi K#Dw8%yP>~q` Q䀢Ќn"JL -53yY >E*jg7{О:)Κv<}{}RoD$uZIͨ[YeYWea*2tōn}'_xHv\{:S(v]ɝ'1ީfɣ7O^~mnnw]BQI< 3ί6[kwp3ÙKų ^'֭O_~4x1 -/d6 a$7L E^i [(8拺jMJ 44xVk\՞/TLo Sb,p^" BAtLF(o_W[GbhcNb~0w$Q cqd<ɅDv5E)ք6ޚ9{㢚ݒh ZcbM7E.@Zx GTM!ȳ\@̯̜ҽL^Fq`gXK3Rnm'ڕF$J6ӆݷݩ\~!S$GU])Ѭk:];=]@CWLtz:QGuD0-Ar9yޚTsq([엫lar uaMכ)\;QfN$fuL"3xY0t;MVGw-O/=x]ƭͻ{V/Jɔz. 93GU'^϶@ :7Hr[AwGr9ٜ#f/Vkp;/u&BoQQ[ncAFl˂lJdV 7Ae\ -"Q' Mr17!)K϶[sSk|ipkV5KٝϽpI(sc -DJkg̤ۚ]oJ eIpK?X޹ΨbŇ/Wwx*rΣ;/}a~亅w>swc -̝'{n//e_ԭ F$`O){W}vkوQ=G]Ս[(W$i&.L} >xoAzt*=ZO]# ǎ=|ccǐ8e WLNT5p2p],4m -fS7'q^z<4AɸA!.xFEH思Agbh ڳgv/<\ݿ[*mHQCP/@1U 2+AFSS D-PKJ?/-%Nq >lHcT Nzfi:̄ꑔ# -IMl42Y 76No19$cJi!m`A |fY>qIU+f^g^.~zBuU=(IHFRS}.K=%S1jbj9ݙ+;Wk-׊YHfrs܌uhXeʁI%ƞ4Zqg%Mo\ҐFlfsp ,˥iL<>yMV@ -ETr8H_z_{xj@ C%Tslq|ǯ2CǡpHF~߼J0#r`[$ah`!X]>Uo,!M bY>h ChWx`"x!U79Ŋe秷nӪYKdY^B<.x<;4؜/C$CuVp†j~XXQo;`*/Iyh天YRf$ #$HT!DUPh9eDͫQT飋w T:;ߞ8LfVH`P%QX`&mJK`8Jޥ?Fq@p! q<e!F'EDR.z-u(R+WեTnM ,gJp`lnZZE`b_F&$s ]Ɖ(j'}^bq1F GY xq̯0F,[$a[+RS8j`M%`_*F8L%I)u֣ SPi#14P8a!ft@0 baix3yrzGWpc{$aGNc_D-.;&XǏG Z1X2"eі$`npV2Z8DcOp'p0AYt.52kP -X<$h`b4ƉQ ncTQ1jV0FXd2մ$@AM/r̨pB1MzAOJ+D"tyn̙[4ixyJRc -`Oltx$ -\v#T 6hB䍢*/,|dxֳ"Fǟ:qQ1ر@C(òrXRODB⒑6< 0h"Kh+wJdݹټwiyokbjw^MKX'$J'#y ݘLjmr!:l%ugr+0 C,!OMWK+w7_\/>X)f=VxIbh [f;A3TȜ@H4HR[KZsbh02 qVPκ(Ce^NMZ38FC% O\BpUplaT;k' O<Ͽ'kSF (L1yyY+zb,M7൓/_zۃFd8 XDڡ(0eCլDnoin™O~܏x/x/vk2L#F|tHI}g~݇ ~O}?p_~}瞫>wp/zz) 2m932@7*ܭ[̼|B?_|;7տϿ{Nm5@+QIElr|H_]M_Yϟ1/.;/]߸֋ ^y*AUuv.C5黫w:~̷=/?/~O7.灃deHe3h"V>}kI{tꛯs_{k_8_}osW|u'A BbaI٨JyeWr7z}_?ͿӿɃ~3ݛv>G0,ΰ01U%65K?©o7V~W~g3Oo}۷4X<lw8~ N:~~vok?w?| ۇՅAXᘖw Wqe߼ɭͻ?W߾rkꥯ?~VpJ0Bp4: \ -:oُ/n'tǏ/+G??9+T(_N79JZ~1˳KYΟ ?՟w߻/x߻OL~rJ+cqN -<[-J-}y0gw~?_폿>}-` OJF".t&T84`^)${8,ٶ,{gOߺZ~g/>Wxŵ|~Ϝ?w7կ&4Ā!꥽ȩzizf>߾vjXS38" -W1+TTiyg3ܥ sIrVR Z`P.cP.D!HAW^#lyNQr"SR#%h ]+G|GV<6iQrgжZi._Y,d+ꉪRЗVC x!Ɓ DȨ?El;rYFg3FY:}cSW'"G'u9S*GMI8ْvXKYeW*bRemE0ceUDyCĺr@PJ`[If0HN -q PJV KNRi|wy6HΊp| C S,ߐ庡8 GכIOY+Jqo9legrF?"ÁX?>oL!]WʙʰH4l%=;rmw^ښ_mT,S$Qabv8*dwh)fTZYv`BMRZ6v^54Dc15l -hԌF4 ue!P2 c(EY(BX081AW,JaXL7f)H6j ('DΩ:,* J1 u* -0O/cǞ~BUGq=-Š, :-%$?K 6, +dD/-eOu'T'$q YldK klȉB0ƃa^PZ7cӂ_Y++b51 CCZ,aDCp$)E'zc$@P;[-*0YKT^8Fq %S <(IԞx\XNo H#esU3`H2nM(jN%z& -+QtgűD(nMx%+'dkR2܈B:lݝkКWR6{֍ ŜJV-HJt!hA=ݞD "p>BдLwKpF$I 1DJ3z-vKM\W*vzIA)KzSFoWw;X12C EJ?XT)+_\=x2գ8a7' ٳ~}mZaSk5e*&rsS_Z?{98ֶIp#3`94*Zk=Ck 2!Rʬ.=]{w\h $9z?.?S褌$WUe}W_|ag "ұn{(pKoTv];njU=_y6}^Ԉn;EðWu&˗Kh\ \֯h׭KLP&jϣhǥRQVq>gWo֓כo[KW5E}jQxx}Pkٛ~:ng?ۏ_184ꇝɓ?߾wO5vvo j}gl/}{\=996D%Yqp~8=}cGsZEC z`F[G?9}+ɸ~T><{ VOٔbA9zL{fxIkЭ bYn؈$hO۰ʻI_E)gdy!C+Xbj(Z~=ŵ]]o~O8sR:\}ui~ggx>Qtُ,UlUly:1M?y3lٻPib\@l㸽N|5왒eP=}V*'!%4 +igMeo|̇O|WO]?_@ɉl/l͟7?L.VT̞7gOڞjO'~QJm7;_=>mxɧ3 }7__>qzD/aEaoCσbLݣo>';y^>ߜ=yq_^ҫ?ݨy|-}L Z1-Tc0?o>esaxKdr#.`S'ǭd޿d/wz'%ͧ7iM 6>:pmo,Jֆ`e -V3:__mZ`8=7|~O?=y:43_48x|6\<tRq)WW'}WG/~|xޙ=3Rw0G =&9M@ѡe/E '4v/! Mp 6'Qy9˪͔eumwVkB1:iOZk7 -1|vz&;E)л[ۯ`mÇ?_<Ճg<ʊ78)>L@ҤY{s/O{ KrFk>5Qc_uø$UFt39uwX!HzރK =xWG[_4G/CdТn>'(=e}ll.ſǙ?>ɿ[]h:ׯ:_GӼ} }ۿUc8[ؓuȷvEk :=y]wx|Y55x oT@t+?㭠ڣۣ_75N4Z{ -Z u>xhKXb (wnL `TO׍rczt++ %: -E?:GЪT|QNDЀsY FT _B4ۮxD[V0P>HtY)&(ug=Oӭ/TN~w6q&4It{tt9<]=qZS{8t۟;ޢ=ϒJ:Hۂf a;<g$:7pڴ^cx:yuxkVku67gn5jŊ UP\=SmN(|5+ Ֆbq7ŦA8Jσle|ߝ_k4L:_Շςt/IFodz`).iŃ'9|y2?vb6xxՙ?._bhAýzpM{t;AxO?A16Н090mQpBX흼+1y5..D{;K_{/UIaX]|/MVw۟ p-*f~ۺڄ - E~C[dv޾u`AyV{V!LP{SY]P)! -k=(mym6yh#~]oWg?<}gO9a8,pa,}}' |FBaBp:kv'C .ry_9źd(.!4N X7S~jO{ӇM{vYS$2BUPAN8@q2 -͵sŭSEmRt><~M!bEch8S1[otd#mlz<EX::Ƌ\qk^{[7>Y/h̅娠4'z ,*Zʉ /w+m]4wCXM+V -\Ӳ4MLy1yK]B[G ]Km[ooA!/VׄrT1`t*)*(47:gAZ[ l@8a_Ճmz5f}lzJR' SJVs][W oi3 8VQJ.vo C݌j{32dv5~J #V)ͯ~;N>P4Fl C2 8=xO譤q*Y ~q wu1TMIY vh%BI ./Z#`x Tϓ<^AU04b-6aY%ZO7*m}5!ffwE1&FEO `Y˨YquSQ;>+u{2 >6_Gf8d jC;ؕ1L[m?)&E)@b IWlF$٤e~̓z󐤂wO?!L KK%I8B'=L5̾GV?8~noJd8=,mLl9,Q*\NFv%4aj]"J'jLuI*$6TzrLJƆ 8M[>``.y.s"xiuqbyfp^(afl8t닧65,?D"J޺ ?޵폓t{k-Vӽ`V9cUOSe-w+m/߀5.U4ERtb+''X1\H 'MVWgk\z" Zs'عi[۟}ڂ21q^k?%ozJ3f9xBgMFkP,t̲iEC@MG/:vp3~4O\GViyVbѣ?  4f81m6ёkmȓ7z/#UjِE۲jClO{JYNh* R |pbAŪ,ŕ2Wqł"U9.eɦXP:dfuV""HSU evk-/0YC䘝$۰fgX1~(LFŢCRc,_G\h\o'^2i$iP7@8xh-Q\GM8癛M _AK$O`|_75s\Mo6G(1O8m*p{_ޢ"kH/+M'\C -̸Y$xKVS˛X޴95d׎V^!ʀ>z꛳? KbM4֎p ε -9Y?rhG3&$YNX?lgMo&/ Uc:v7~3rp&753=P37WWԺg4Ւ嗽)A9Y[:fohJO"|B&p*ڄ)zxnKwԩd(tUgל3ه|3N΀D~[~QPDWEn`<EGeO.5ͩ ]wʰr@T o**-EkvS=80V88]%p }0JaJ`I"嬝SuM˄o.DHEz_᫿91H̒Ԗ XAkz&7D^0pIy$ZE2-[NǏCuE9/egQx@g- M;_?3mNLa^b &Q5`΁Q VM pDOtHwDY*s9b<ݜWl[YgJELyF$9ug3m_u/Lo?\?Lę:%耼od;m]7*h0v\o\*K @|G sB(t4y@RNtk<.IG -rOVar -U\Vh9zQB{uc&p"D?~K *'u Nq~^f{cm8IM1q AeEN{3z^hAJA}Eyqmxm>"«uH$M7뻥}l} 4H0}J nn UN6M϶W[2M|?=b5( نF" -k 쏽z8txVw0Ҁ'ǔˬYǟ?WRz5Z7=A{|N'`էӴ$Key8M߈Iٚ><t>9bjC~g l㟉$_Rˈ&)0ڄcI{ׇ?U:(7<[QԦ `>LK%PI'*1"T,j`nb>/+rg8 B13(&J}X|^iԷ~3kk'(p-ۅ˾ sUj B UUoP7T ɲK.!Eeɲ/fd -;֝/Pܪ :aQlsBO@N/ =8,#˨(=tsIY6:8>Sڂxy9myl[I ͋ڀbAOCufI\hèGg!>^A^8iCzls|v@e!Ű5N0^(ԯwCAL_Oxyp-^ctdޖH{&TNA ЄW*רuAUzEB-B7p!bS_:&lЬ,$%]Y" 2UVX.ðHM80=pmHȋ#^&\l$6Rx0>$m\V!@\ DJr[A!ɭ׻İpŋ'CijK3h. #/>(0˕x: ,R-|AD1n -rc=ϕ+fm}?>;VٞcxBEIsv0gC@B9˟a%>Kʀ&p(VڪXҊ%j#-(HyA'4C"U0n3 *ri j-'T y$bfBR* -{U!4 L>ȭREO:=T)#`7YYނ4deHf4++_Ar]Ds5Y96 ּ}&$ĈjVuANP 'Ħuv*c՜%]*<rbvh;zpهk "ך:c޴F:Q+ Q9Zư>&!(\}ok]AH-m bN#lYiy:&EY@j$ ! .E7- Fj0Uc5Ý1O ܑb@/^L6^A[M  yVh *tg|[0]T"NB (\7%iPwZ P|gAݡdH 9n[&|@U<pY.: "zk -% ͳq~7<9[ b\tJvC6_<9Ŋ]By_v8SEBB@"1\ -Q( --)zOکQkX;en\@0BzTv-m4y0bp1_}FP}Ys\,Te,&m.IakjG -(S 7aQ(nCIٗqİQ̭8R Ai|#97 (i0H)U_wq Y7&ֵ8Xb[# Ҵ(_*r6ht.xsPoj(U_\ V'6 [ۯv MoSTP;xaUT 8 PAc<Q(iAAV>dF JX6nۼЧu+u} ]_'0f/+6ՏSЖsb~8(j k08@m~rƨD _~zp):9FF2T7E1$;PJY037O9hF) @G3U,?kq)xQ lPFP &)HT>Xm م*+dw^Ql3° r l@B6xV][#JE˾ f_dF:BY΀b}Eo+/:vnBPijmM`GwzZ!* µ{(de| Z(r:CPǧj"Ko옹[)kVlC0G<1%j g.@CZ^\Q/OEPU;4[J5V+P5 Z ]3ۗ*eRBstc~QEe4/De۲11,b!!q2(#<)㗢= I$o(PT?Vm{ !s^[D1S~:sFlV@fT  > Vae+Ђh.s*5ᖪ@-mvj݇FdIv' ௺9\}vxlQEHK5N :NhӨ~P<]HdF`&%mΊ}{a٪.[ ^TXJqiMgً9 *P TQ4e mVl>2%SƍBP( ^Ig8؁^yMR7~/UliT 1Vf}/rOGIq!-vq*'"̺ұj-G }kf^V %8n~mc8klB mv}7ڕ!`c& jX7H3X,+7-ul@H`z$H\R?1z+/ f@MxCNTmri (:$c^F%3Աniւ:8^\^&pWje~%{Ιlx`L۝3}q̓ډJE5\1<w0%?>+@u1<[$0Ow5#/QI%Y`Fp:A e!EE.ͺԢ4e A4*hXF|Iss܍% u z*]o{7DfP87--!3V؊(AuFWёX@F bRA2hZ ^ D_.,l*+UU6[=B0\17o -jDTA郀Weҋ!jXnK34O6?3XVGb YQNnu:|d)5!+e`ŘuF*&"HNIsʰÄRzS?2TT{0i*)]%ȄƼ8RYNbV !U ws,GI02+_}Bs0兺 Jw=@n]Fl}}@z_S},d m澬/x ws { AACY}aDvJ9Q~,g8[>b:qTz5`A[Urť +vzޖްܩnvH1V,8XjFQp1[>;{sV( }TMI:G :ـw P*RUX{~s+^1(f’q -rsfLU} |R; -e!keo){`1*@ZJ. 띇q w4NB1kfJ ~:|N~Z;'M{$4'?n|i38O`QiBb*HFWϞ78q[0 -!'M:lw&kuaKe뚺=Q 3`:@5FhR jl:f -@6:Ͻ.H 5p z r0\ >M0i]{3Jh1xsOH66H2pè~g^F<m -TQOkv"'slX4׳OO`J :GM_#v\? -3\=,TB5bv>dWVX= Xfvp4gb4[ZfJ*FGqTVIҗ᮪ 9eA1, - z>ǓTd` -%O/@r$O_ѾysVs$k8e}Oc^kkO -X 5:_24s2`tUNnM -e[8^l~MP{dWGpJd,XH^VyߺY?Ã|Oqmwܶqmwܶqmwܶqmwܶqmwܶqmwܶqmwܶqmwܶqmwܶqmwܶqmwܶqmwܶqmwܶqmwܶqmwܶqmwܶqmwܶqmwܶqmwܶqmwܶqmwܶqmwܶqmwܶqmwܶqmwܶqmg習1?ދ?~O^9ly}|o>Pwxuud -E-<~ryqJI,ʦ}#==װ} lˊb -m3+ -#7<+ 7 ѿ|ֳg1,nhGgw{ֿ|GG/>hbJ,#o"gW?0&dGf㋫Aڃi?̎MPOx1X6e[AvG/_Q.ޜGe\͒Jn8#(&kg'P~- F4>6H2۵f>./5q*6ʄ-UYm[X]Vb+QSjGf91#q&,iEP swYG)')~1I&e6'Mo\QFp`b EJ6ǜBp`Bq9).#V`aZur*c[Q̩᎟Ɯf$xV,I8a1+$ŲERIu*>5{k\ʋMM GC02Z#YUPhؖ~`a#8HM5%u=_QE1x,eŶ)K"Q̣贂:^Մ:fbGY3,(|A*!C1e|][' %Tj$Yp&QJŀE iIR R_;TH;^%C=8 #t VBRฺiNum@eM8.NF*%x@rjƆLikəfJE%@~zYcNJh#O1A8+ gb{(iJczCFŠ|1Zبht_KFb# -b4℟5h,yXYsnau(HxNS1FVQ5KL2/$j(dWΚvK%)(bAA"xX v;rl(: tˆ(L$i\,iY ",F>tsSuo PQ, XJ%0LZX8  -ET!*+劃Y*VeR!+8u#))IC@"d滚]Iᘩi01#؂lLfLgJP>a/uwň=HY<!"̄$ifڀl4$Y7 -93lwW81BU= -,}%uS;\I`[!4 2xɺ%BQ# T(m_Dj#dKi,WdЬR ' @Y P 0D%5;=ZH:Ⲏ&CT@(*Rվn1&hI@]s͛sÝDuFbM'I:bx$J#ÂdP/(X6Hr=j^̺Lp 5EIf:$-YNklCjDSyRHQqzhSQPt`'۔1Er XYAPX JxY -J[A0{l:K<U!$ jC&[#nYN %8T 8 `D-J~Ʋ)k?$Fqm-BAB$-UT<;4yNPʸ\ܮ4cබ -`:NTV%u3bŀ@qzH8]Ȭ4AHQT&bk4ԌMӆ >ˆX#k!d8P\6mP;E@V{ۚ9Va/=>Flb :֎9EGF]T1v}%4 .Qq ˈ3)Iwv?ouXֿpqAy7/5{/L5`@͍Jр\lpdxT2"Sũx-b.m/劏b %lQHKT Nܴ{K|**6`q,(rȅ/E LX(ꀓZ YyPPJ%A -X/(BJʔ(j0Pӂrb.'6%ᘛb-x>9kZ -=H#i^1l 4vQBWVDi]JR Y*©Ot} ~νh` ii8ڋeĖ 5@.f@V7ڥ AքjLJ5Zսgf|Lt$c*S lcFmHr: ^LЗ1p&gQbԕVTlԏUcQY+ekv.|ac9u2 -z|7P9YocQ UfQ,V6(kE+'!x5!m\$k_rTȁEpI<؆(BUQ'^@ -2,Iť춾N"+kk"A͂H68- -FӵQ)Y4Te렙ᘠpZ - -R :q^#RAt -Sj !ర -h68`HabSNk+ IZH(ujƜYDĦlws/ڕh<[iCj!,M~AL=]vOgV -Ve<ҰSQ)IJ7k;`RܥvϜx%h-JgUchQ1L Atl{ok"ntXj -*0/Hzq"$ )+N68oÊYߣ&+'c<\AIz/3_xH*WSXrι:VwK,"h3`{0q9  㙹oZzI-թs~a}jù_AfIm241j NUj35r2”(@dd;DnmK &5p[zd A >HV  ⠀~;46F!S ! bE1bIAX$XSG`<~@hhSRqql>?8X -,wd2jF0 ,󅕨݅j7!Fego0Vddde^\cFl9aa Br۶fM@P. !YT^  Y)5м M -C - 5\/8. ؼA -Fta/l T犦 CQ{BMHv@ <.DoxQ^G  g!NgH1/D0;HXΜF"hD0T: gIjJr  --7 6|eCA'vopb`AtzۨOILO]=aá,[Vskc} -N0IL$!)U(+gQi|qdظ(倯LwFWa2A?`=mŦx 0} bF!r+ҝ9AmF(װ35CZ9LeW26\at»TkA6D@b%vVrqT N -nqtɋ+C -vd')51GQ/atveF{ۓx>b EG$~Ck3B* )aВ.`ftAX+Ž!" 䕀@UA 2DFʋA!aQyO1>סFF8fT2A@(,SR d~C#6d[)8K m"R/6tBLf?hmоqIʱyUx4j&Pn0!G'|<ʰ (;~ MIUZCH6A5`PC:Ժn?i0eZ)t&fOV+Nz $c*d L$kF"gu#>)M]sgފy =iG 'jhZC`ZI) a#!-7y.-T.Nr #bip#<8^(%V̚ʸx t(7xJBH*MHQ=P @P#p8:Ha U(2<~?GN0 Ӥs! RGkP -9qXX -0EW]>~m@>yp\8ݳX< TMm?x <% l.?P|CF҆$\aQ^=pTU{S+ϫCVmcz+eGhdOd /kXQ0qJB%ljjfzMQ9ݘj.K[8C\ x0s1{p(]-A?Kڂ6z'!F+trn}$5ҝG(FR 'ӣ],ha'OQ -ŀvr%jo 7##يZld$9tdOtТC1{|@N)%my޴ 0x)4ДZ D8.f Bٹ$#X$k:0(e 0Т0] ('}!;Np(:y%OFg#1HS\c2$/ODtB@ ~_PjLƄeL 4g0/ή^G Bc$I'\zr͚!;EV*ԜULs)71a2zm-OT[k !BQ(4贤r)BEph洞l1jvFX@)ha< -KI30a9`Z8VM7ys0܈D4 Apfu]a:wOʄXv{uJc4et4R1 5b:S`ƞqjlG' U=d̸_@A@y&3Y,z$b -Tp.dq,; |^}3b1#%ͮXiϑL҆dxLE;A"h1`*A Cn{Y( -ċx@$B%+#d^7fts^p0n>?-Y `ѱ0$gݝܿ?rUq:: pK?Bx:KaX0<դz$l:< X|tI}}Qڛf^ttY29088JU0c~{\U{hI@D "ɨ>1\-,qh(tA&%.*AY)O V^m j;&(pXACK 5^y?yW aZ zjzЇn8Y:n(\kV.@F[5$QDR1Kvr4[.,K|zC po`.zM'b1 +򇢐qL2~ =-h joUBt9QH|/'{ 0^Z<8 `"g8hxje Y@;.A(8cv8GhЫz f{7 ق W/jGg߂GhwF#̀98,e1W,ݴq.6h0w@n$P*e9ap |ލx= ' ZAD :' 0АN:'t~@;Sp8'ϯcx*P -Y8Z6 UH8}@QXJ.K&D!d%"K%Nl7AJ`jj  f.Mchu FBq޿/_3Ά rփ~4 ~.s'CYd]JAFaq/Y2c`A!B88A\~88Cp,NpQ7,lAZ E 4r"xT n|tZC ؛)4Iъ@'TP_GpH4а_:ہB#@hfh 'r [ЙpxqPHL 'ςk!k dbPilfﭕ@=Ke| ڟ(=2ByAQ hC_/  `-!.Hpt@GWǝY߿",-bz'whI`ՀQ vp0zUH ՑidEAˣU(4.Aw\mxQj!'Iܚ 1=ёǑ,˩jIVDFy+0h&LPa5M{2AġL ϛָ_A-%hن92.9%J{t ;{4]uU`U!tVHb8"$Hr wh#IQiZ̦`H^ijӈM7хZXp6":OQ^ZC}E YUF.+T9ByYijr5ECX1 I4ryz)^Z[P[UVI&L5s覗(rtxo+AծLŽIK`6SZ 9_**K`r@p-ҁkIQ3l sZHy!@) CՈ p1bp!'Z>3I9mKJ\?`9ƀ'':OCGn-P$q/@#`W@ҙ H@/ M-/gLg`E!ⲜGk*He)b<h(rvl -"[aI0leL  ^m0 ,/M^ܴN?hf Q'vᲲV JÌO +lԙ2HnMUB{*GkҊV¤ J-QvԺ"svjf@@{%a!$$]ӝI^l A4e(0xK -* AQ4^oAkCY0F9 -9勦ݏB+ gRJzATZ&C0iT) -+vK4NHZM5:zp$yBQ,8(cW`v ,\54d[zbT hM"EjN4bC'g -+4((HEj8v!" e`2.~I -GԨYI\ 99F/# *PLzSwNjZ6ܕzklV05*ò$*5hy#6I:zBs n'W쬛]6'u@ji6F2Per' Z@KIzKTԆlL+K/EIUǭ\sl'$~&j!O0 -[I -dn>_}`@:+h4Bf{毂 ĪhtR(GPpR> T*g- ./$Z]Zaw8YZN,3fbh w/UZ.Adj,=wgעu5A(*z ;D(| oF^Ujnqr -{LDje뫅f{ +WyP#T⥚iLi .PT pX!E+xnj*]\tء -+ahZAw&Ԥ4#9lrՒ͞ft^lFfWbZ顛:܌tE.h`N jI1L~ujtqp'hja8Kk܁R$ @1|F VjڜTN_39͙ŔSɅ; (\ p t@Y\u}b⡛3u3E2=ft֞:,V=>mkF-Yӳr{t} ^F0fsܱN{Rw -4:V(Xt`s]'ŵkF,;M-]X:tc<#9g'&Dnnc6Zfvayɯ%*t4>7HO'˃pT8iteNS9Bm=sި~0)>%@ͶgCq^)jt$k J*jӥrwiM͟ tamL̔ӂVtB6♹LuNMLU(eiV6`'UI%IX|?9[v\#Jg~6߼zM YrER5ha2wgW'wcb|ZBO'Kv*Ԟ:IiAAR [w.4՛VrKwMeK0U7@ -~0?2PW_PsgcIDawMR -u Uv=ϟo v̍ڝlqefŃ6ϟ߾zمs?\O&íɓݙSSvnY=pf #+/{G⹵g.8n:uO*fNϥjmb1y  u3wR^L";3|abU9gD<3͉L7NZ՛:{KQ/ϬNt{07wvaZqE5hOT͹K+7^ 2>UXkngޢ$/D.:luN!郊ȕBmՊ5E9j؟=cŇ2jՕjo+YZԖnO+Y)wRBu~bD4[w=Op՜Y9|Jo'[^V>fБS+kn=qpnօkfĂm؃x~}zҥ|pCbZmqʌ8h|F>) vv܈Ϧj}0]ZF(ńa\QjN OA'gҥd_,kR2?9?sO|5Cn2;9vmk.+镳ˇoZ={vncK|"YBfJSwvg/.3nAͫ{ 1brtw5 o:Xn5[>sWN)7= z --`@{꫷7L L'Va;5ˉ5?}>O TʵK7Zc&h-&bLT NƒMQT3_3{s5VֻJmP13kv7_{S73B۪g [*_pMw=tAp$vb4 _x[S'Muu/~_ܹb؃y-rIL‡ֻͭɕ SFܕh>P즊+jkpL]NdV/ʸ7u=A@ֈSz~cqbAbX c(+xޘ מZ=s~j햹+`Ejg3)h?ZllN.O{N4y) -ܻVyY;~}AN4Z" .UG2@(WR@nu6zÃrpiNcɩxnp'bLjZijD}pLMkvooZ)E9y8g̞͜͞~x rZh^޾T(. -xzU+P&d+jzbTq6q-dSZJwa' <3A*Q4`Xa9M6D~܀Xd&j"3mNkecl}I1Нn,1cgRgW+ z{Hkr5}\u)19PsNF -9z^6VҜO;di3*K砚r+/f+6zN%iaJ3A6DwDe 0V]o8]ˬ{3GpցHy1p l f8Wĺ)]޺Pݬ5׆e(\":s\ĵTy=6X4vS<$F۬Tƹ3wJ344֗6M+T[fgN\:ܽsjR&Ćm•Ql0gO-A[3xGPqQV[…7cU&Amp|$82tek LmL"%kݨ=#8dm;6hma%Z3ʡh1@ݡ(~^ೱԌ̔q (fFQVҌAҞ:LãGnX<+EӼ*T[XAqcN1 Y:ő[>qֱ;vY:qJ{CZ:/Af/G𘬔)6j^ܺ I`;Δ[S\ aV(/UԊTL1 he' ɘnpa(K+\QPD$(T*"`y:XL6^}1;TR*7R)I#IEE5Rv|3#Y*F53:Zu&C1*naU+gB-d'OJ1LKzez7ӼBj#u7vܟ9urkf*mIzp N:T;rxܡ%Iٽq3NXTY@Ġ <$N-RBSrGwX A;N0)EʲF)5 -'ď]}汋bǏaJ/=Aa7!#NeK+aՏ|JwNL`gjMU:kY@4jSWnp`Xh)Z>S1bը04D1hLHtZofr _`tB21Gѐg h[Y˲\L*bKx|J#EF†MPl=rWx=ǟ77x/oۻ/gw`nͿL3Ua=?Z_X>|u|wћx-p=w/}K;G=z]' ;ތ%;Ŋ#箽x{z>ztɗկ}_w>O?앟oo|}s -x$qGRL}@SGsjbs_?p}z~gs.ߡqLv39lx#L ֏fie뮻[/|ӿ?o3k6<^땠ju+ԃ&uC/Kr4HrZQĉo Wz'>n]9۟>jU*<*'5WnxK/~~~kOvO]&M3ʭB1]q¥nO<ė|۟ <}[7/~o/~g]GO}7x/~wOg'=¡Tv) -f6Y'wu+w? g?>܋/׿ÿ۷_Z}PIfk-MtJ@'{yGxů~G?'o߽?us63L8XUK -a0Z!Rމ5kթ#x}s7t{2T\8,дkvsiiaksC<O_η~?ͷ|Gݮ7gפfV dzfًßy3˷~~xݿ3/|' MkBl~txugquĠ s9DGl0Z=pO=~'y^ѿ?{n=68Ve(d!4huyǪ$J(\E͔3q"0Z>Y]?=>?>яw݃۝VWqŠHnXLWVvI3-UTq\ahS⑈46.Ɛ2zlfSdȐM2 ū(vԮXDs -n=^H,4pn9RR GbXC+LV^9u|>u @VPX Gd($ UMU݌8q|/4T"ULgf^,T!5s\\Qr씤GFvbVI37QmR-Z :"C3>/oR䂭rr˅G|ӯ5Vkz;hx| 9.e>嘕4ĬlZ!Su -F׋pщT!ad$ֈe_EpLi EIєƐйL MmD[fʼniSW &(2X|:?^0<1MbM3N.RJ]cbzѵjIJN`t 3/PzpH MshY_8q0eQjQR2i4+ %>,Y̺aԣJA苤b0Ql0T䘸?@c:HBDdA+fՌ5lJ"E`r.Iai΁ 9hp^bWL e4AHD82id9jeŬn$=AN,@%)2~qvUW"g\,H߁G!cS$n}\=N%˜'zmı|# mi]LJp9QcLz=$&B!^bn\]s^*֐U -RŊUAzײ;H[;6 -C` @Fy?F u&OL(&Ff8tܶ:j. 1@4PP"q", &fi0 -rW( YB^Q -$Y.#e1MTld$06 dMirI*fm;<<.%+6e}8,Mٺ)ђK|d @S/mij f(BHRB@qD~B =CR (& Co@e,C2LF Ϲ> ۞pb=Q*tJ롈 pXAO.>*ANn D{_6Ip}(): -UpI:I'Qdz2_e8$6  gy$K)" -1<`>O'2+8(JM%ZNtRCbZ֚cN}[TI̙!Iy>!3<-s|KҎQ#^IRIW+d8!,ȯ-/ b2TRu[׻ @kx=E蒘׌fLmm*f맃!} 2Tv?}X[f$vbZT8= -͎{1,$ nz69)k'h-BuVw>Cb9z24Ǔ.8Тք,$;5kJ6o6>C-|+;sRq! ƣ,bFЉ\\"Te> Sp75:ABAC"hCWD% $pVyd㣃aKtp@oF&q2^^ Y, pg&D}W4y8‘hdo! -AW02饕3O+rU -P"A.PUF"@h16C&N` eC_FSyKۖ2g"a5>3>um3l -E+Aa=z68M]>62ƌs`#^s'("Q)ڵRNs8_Ќp9ĵ tZt"\ bpE8{–"x$9`^re=+h%VFpۼ5;9-ԭ.P-]+;Nr!^Pu˹c)>X Vy -nANIFWg * wݜ! J@*d!&&񱱰$D!Kg #LgIU/F.eJVn49Ϫ>\JSjVQ %,ӀVBa&J; XR*;n/z -LJ0HW%L,3-5x~I@] 1s0/(!5fgC~$29I f*tИu7EAbltO?d,MkpU( ؉A@II,guNUF(GT,*ʫ٠+kSǍD;n7!I^mZ^:}|i8e@D *F|>YuUף 1S D_\qrjNϜ9Y\9.JBc?\[yfq*ac^"8M-Ϲ' Ba'FgYMa5QB^R˚6L ˅\$!crz>OM;VbdJe-:u/J><6$'TI:'UܕIP2LqW3݊=ϗ,&<69p9/T2}. >'*ly-SZ;~c'ͳL\7sqfx9_!$ixRrn8MAH%IJRވu0]0㳺^G -C)m_/J='⦪%u> I% ٵ+"7 NziId+gv^r3D?[Y&ؤ)Jw_,h\~iOxYIK¾-CFzN,lQQے Et Jm݅]5Z%eoH G JPDtq̵jl",heO|ߺN JeJs3sn흻4֨l Ni+DģVK_)֊K箼ٝ BeĔĆvMX6&dq{ؚ?Y=xuU^(C+V Tn-VAx1_AnnP&шAS) -O2`*J018|E+|c?[Rn9\/K⺢#p x*;XN ) yi`ʳm`Rc{pOؠ%+5R}5sK͂Jr&$g㩡j3'275:Gͯ ҙXzbVڪlV̔:PytXyK?)ko=7^Ub8\u뼑['TJ[-` <3R ڦk{kGWVbQB V*5/\-͹Gp58լ/"t";T"+U؜TW+_xAxlD4>TC)R~_PRarY6x9}ϙt>+& R=ӠD  -*4%(IN,3K鋩׀7a qV~}pk7_|B7hrFg͆)B P.1 N]y;߸ʗ-O@" ELDV3:4Y%ƭ @S AKҥ*+ՀJ_'Μb6 -^!6c8:QGNM~n2#JGǹ@W59>@@ 5P8wh\m%|nb,nU*ېGMq;K -=AB?+K(ZV'B`"<~J2OZݝ֍Y#d8FCa<(C՘˕;'s03DZh G2}bof9ٻnq|郧7\{@ -(aTː͞,ӣ1*c::$2N ܞGvOufH*)Kbey}ʡQWrՓ37xJQ/q&؂ |`Xa|NʉR_8r;VkݰzRo -RcX&B xmls4[l 3uPh8a&S@Px-HKO[sWhtz#d,0|q4ʹbp}hϚap\tE Myy͓ FQݽ]X5;IyK+[ŢT% *GG}ccФ[~VL Hx4`%Yd~>Y\YY䌝S&ْagY9:Q*9qKE"6Y'd#*'֔jJ&dXM0h҈JSLVsW*Rie{fHs '9($.窻Q4UQʃ7C/XL1Pcӣ^Q-Ok^ f+g;X6g)vr*Ϗ#EA*Ec}B="A1L"@ȅ0qwW璕B{rpgŽ}^eKPV8e؂ XWVD vs,g!,F EkĒืBC`N,bW Ex}Ğ g2qD1D.h1̎6 - d+Z a^T˧e;vgx=[1zwMQ0YPSS۾ G<ИVV/R| /{z7ç-=|nuCI#c7\iCkJ A#X70wqFP܍$D>"nN/o}to֯;UT8"IjI;,POp1HXG#6p40 -'$4I)6KYQvڸrUŨJ'TH.F - NVYu^DlYiREKXVPtXt~;_{mq9 5w+" -'Iܨԗ$% &dy|̹W֥?_9ǫ|j­1|DcCM S0 / xWtyՕԂ?Ȃh{؂3*ㅹdqُVd -;an n " D`I2u=d"ADMǮVh*4W -Di׻Ek{golǟ5NRsR"9k#\ W=+΂Zy.#Ғ^m?ßϯo6ڞ.f4ʺQWnv)cnqB+5,dl485{HN`u7A|lֳ|e[Iw[_$IPBnP)OE&qPsj  tZ6x(Z%v[˂TOo^.eETaHvxafwLBF!ۼqeG^lˇeMg2Djb UP.CZ,Hƫ+ۏ=1AefS&?`QT Q o*>⦓YeR&`:Vuz7wiw>⣏.?奜 fAf(蔇 ON *ǰ['* E0Rlu~r,nTGو{ UϐL<,_Fu= V0 ^b(uO@O!)R5!_ˆK׷m{^_?s]éb*1b٭+ 8p8>KDE#V'')P[qjIO مTf(+ݽ;8Fܙ.mVguZ0L'TrىȩA.&&&bz箘H4^5OJh]anci8(Eg - H~vbb9iYp9xrK=wA1H2-I5\i,+{ 0Y d$(z`\`bY9$S"xC%AHH+Uv,m$ Y@*A̭ɲ@@q{yW1Ik\rjr"Hq +4p?$|8 -2XOyN<8ZJWK9ڶ7:ÓfpOWgTFÍyZȅ#&67Ljj2~HԬd$%GqA*G3sK?uћv͈t~+Yot.l?^X5x%N'QA,neX ʔ_r`gS+vbڅgfwUlffƝKxQcpn^~Xfr:bPŀ GT͸jʴi΂0uk2 \DѴ[?3bCR1y% -IPyZccFНD-j8`2DY]O} 58T! \E ʚiqI=VFx4^E6R`cW3i<:+>3+4_Aֵ?OKH،"5+)PXkc'('Ts c \~jҭ;: -nB#f'cH?~䳁 G1'-dFDZLd!8 $7~w~بg|,4wYD.1\8y%ݮE@(%A+RLiؙ۩t1͖[{޲rш6ef 5IZ -M }A/ 3S~yd хnMsbQ{;7ݪųaKןm$sK+?5Ӡ$=3I[+YYOio+‹PtX(ej3_k\Ԣ0/~ 9 \RD#8 E."AxpBBQ/\LSXébktQ2VgI6 ʡ͕Vc"? ~ebpbD!45JU)›;37>=|xpK*G-Lv{6QTݭA%bvT^Y젗%QpL,SpE( FА_@dAr%oɰq$,ϭ]?hn&;rU2d -' ! |8 ̠njśDapέ Y^*JɃppO.,,]Yx>^Z6/~3{|/$lshf'ꍌ#S^!ݚuHfҐMҞo*Rqtʵ bsJ(#T"v/"26l&fG5>ML2HD B,AV|,mZ>Bfλ_|i0f"?3Cn zPdAat4Bhtznvwx JR:ىYЙ+VrKvg!qDHXd)t }QgK{ùg߻_x}KVB AU[yݬۥ4A@rSܢO?Cb]/B!P*vCb=|>C"3|BkK{w%k EBGIrr;=M܂AeT C$`V0âfUDTin~fSnng0S%Ϙ{$4Ō{ݵ[_ёE9Ֆ*;Ϫ%I&qr (zD`ۻ؟;zg/Vd-wU+̬^]9{geB@ᵓO\|T-5wsm%$ʳx< (| Y2ty^޻x۟Z?{A'AR Ì`C"QQ/ 毦+]BwP$# httnS10@@PXNHV+[\4Vi79Ω47:v7[W 5jnd4/6֒Y#F AjznD,eÑX0SS$b_Ͽ7W>"|r侤d -孙ۋ/:׬ |qa1" =]Z: 4Ԫ7kTfY,u6v%<抚9mكp8ըU;g%j24ڲf/z98Ue WȱɘӾtw𭵭`Kᐊ8$>9lg&PeY -׏VhL>swҝͮQYe<]_㤊sTqyyٿteՌ7)Ɖn -+]2,6Ɛ[mDdzX {*@7Uj{}eFS7Th\ T껱Ģ}>Cݧ ɬ[CUVkp=ݼ -3wo'KgԦH_uHmӞOd6921uWnnOy10o ixzNe9wO>DbŢVwRs۷}~x,Wvm>u4S -AQr9S`K-AvkX1PƕQgN^̃_ۻڙo՝{խ/Nb1AIBHv Dzܮ om^&{WgV |k.XزmSb>.Cl%˴͚`i&*uV(koCaRgA~`F0_ۄO:azB眞Ѭ8֭3'|af %۱ Oks(gm"H4N opkt6=zVAd)nk̳\بVWώ`[ÄfbYвh坛gOϭ_IWhآ̻_YiS-,㶇pHX1S2tV3ZUӷ_zI/^)T ,';`A Ql\j A?3;'$ <( -%KD_dUgBShJ;Lq>j73n\͕9@.x^' f">11-2@DE!?0^޺ugw.4XӿW6Xvڻ)o[g\ 7?2痫盃kN=w{?Fȸ/?둧LVͦƽ[>ۺ6|1[_]=^޼vk^|nyo}'3WARM{A4`҈^, -j#ؒ\p$pWԵ5uL=Fi~a2vXD `dy4$  Cqg$%9>#U6ˆD}'դJg:wcUsQgPP -D@ڀ_>,Z>=!Y? Z9aF){Qű0')HHAGOFG#4ᮠ1Lb?m8y|$ehƂLI2 Y jk;ytieo>|Os|ǟz]ug3֯ied*zOO/Ë95s'Ugж ">wJ;oqoO9%ڥ+g]^kxLP{u۾Dhbr:m ׶of;rSIQVmzWK)I3| gE]ډ=$¹KAyXfgGfh9s9\`xNuEmBäd/Zn)@L4I%cDfIw(jcv Vj`ƺZ Q-?ȒLTTrZ6mNȱtw:Vҭ1r0xa -lݦb3frZ2JQS*&B!w#/rŭ\e׌ 1r:\,dS"3P*A:R*ݝ$ e,#n-ogԬL'[]hV.{^~gi&iy!ppDJkm3E'>mƧ 8SyPWDEyC wn?YM.ç鵃>b{K{Ϧ)/31EpX#(^[w_|r)s;} # ;cQvl+T#30]LMEma* -s)3U6^egI(A7z=ut%!|ȏ  -C0)ˁdBdpWܟ^*m<ʦYC13/"!M -*'!#ҩ$U`=~ ܢ[tp2]])L*hf9p`WNbN&Ӎ):&I] \ur ?9EEQ4"a)n r-]\ۻQmӴ]ͷ_Y,d2[HxG5UWNVQK|֜b̙KWt%IS7jp=X=*iO\y)4KgwT"HեP_IM)CkO>/WH~$N\e;[\_o~eGGOF~88s/C~Va[J7=!艉rHD,'`p) HCHALUļᤲًSY-ZKd,t!9TT8!A"b \,6 Ó,6.廍9X m_jkFQҺwUJ"&sBZCx:\pn (I0J8"kbLdc`(ЈV:˦X.M6%IȘz!G 7i OE^#ӱZy:6Q4EeIde5LK}:TP\X|nzk`1('"pep KVsDI! %hRO& AIʱf"NYjօ(T:'3k#> - $n`( elFQ["'"._{i#A(j2Ma~pCj5yDo\ruX]IЊ͈ iYHf3±b(z'#1EJedc2A$av2=< $  H;OnUI 4aQꝊz9!0GT+xՅ; eD`AQpSxD0+h,,.̐ -A#d$>Sh$$'I08 ( Q:-T&L _Td4$qv;NG$~9w -EO=a)CA?`<*lƢ 0 UnOGFOO-Ӥn tZ)L8$d2XߋYsr_AUQ?T!n꩸W"45y(3) -&GS(Y,E=1"#rG ͒Z©"0ĤDPFФtL41`*Iici51̢pCS,k|u#e$|,x4T8*!GI沱*!?8Rm"*dv@8ar0p V!F@ 0r$DBމiH0c2l!X R@;#H\kx4ZVg -}>ˢr10t!]/-.)'qTA)Raju6-FAR?y5Y@%lω2ń7(۪*QAbvz}' X" -@d!,(LDXЏ$wsz`…ލٷ^u,bO ,S316 -)3%T=hQI%!y&HDz Pm cS@:'ӑӧCc0,gk <Ȅ'>|l hJhX+',vJ/~=vLnjw05$ӓ,^LA:Oj1;[-U21Y67[+٬W2S00E9&1qOHذbn RU`arn>3o<O?WqNMX*&^՚句LR -1YTA}{s卷jvN8\(]͹N8VP:$&AV -7Ng1;gAZQ.< -D-[",ŤI.ݬ?̼z{3/ۗya=4aA,nR^ 'ي1+]Au~{o|;~*Ba q<TM2P6g2J;7ޫGytƅj-pL#aP_HNNI -u3D8,]~-$d=DY+K 2оv,rqxqBO=G7晏w7ٷ^b"! ")$t>/dUڊy\d7u'֣+}8ʓ7o/]|p)[D{#< Dp m]^}/ >wޝ/n?_pF)QV!D17ќTWT?KobgɝA~{7>~郝 L>$@b21==z=I1ђ%_R -J^.[{Go/]~/y'o^՗ -Kb(%+ӶSY!#Ul%{q˕W -/_~sgW>|+2: -@7JljjXO?0{z'o|}V!" FU[ȐM?~{wO'o[ArJL*4mX"׋ؕsgEBoo[w~K~o/z_S?}oo腷^X]G0T̚e7_O}v{o/w._??|??O>';џ|v> x"Leg:TooDd𓯞_?>?~' plbAiwc}|6ا>y/{k??{ǯwWbi|,pAٯϲ_ywo~Ý~~g_}|y~ky\ʝw"8vj|p!\noO?zyxǷ=?ok/.>ͤJD(O[I4h<ů/=g?/w?Ư?z_~|kǕV¼,.nJz'oL4nXOz߻o~v{_HrdDH)jFR'6JYu_8W.o>_'?Ǐ-3'ݛ6I^}twU}_*3++kߺ}}  Z(HA”ôl -VЯlkp{o{KFGUYiGy}^w?߸^?ŋ᳓VBNMvI hn}?:Ǔœo?z/~~Oޝ欩x!&vBgsj߷?0ӞŃ޿}~oo?~o_|iD)oG G}Oon}g/՟|r{9؉}ӑxNgrequLЧ[n~{/߸g˿ӏW鏯~_w$DEBЈJĕwΖthyן ozO^, ;20.ǪXVG3t9 -_5_?{'?>o|G/?֭3 'cHC6}k X_v_,|_Տw:V-p%}]LǒP// nO_?<:@b1RZ0Ef}qѺٳ~_{gwm5l IBg:ypЍhR5|9FSǁ|s>~|5{bdu؋1lcRd&0RJ%Y`ibE(Eyaո>~_>ox?g?<՗Gc'R00h%*G0D(? E-lU?[{/>|t>zy.xeCtQK67 Oekq]|W/,6'X.J -%VHVBQ 5qMCcMW<,XYg! -HZKH `A{{yyE{o~nwY:icKF 2(#q8|Ĕmz_/{?{jE=5oD." b9eNg@Jd.;) -h`ճYMMobX(Bj~<Ʋ0a 438Ed0x'VBXkͳF,hI#E,ew$ow|t8?]ױX(U?0f*_,8 XF1Y'*EF^dzgMl+z}ֹ_ޜͮ##fRNm樍2Mo7ǣY>7x!T't|x_{ah˰$+J!I˪/'vgهo* -LUѭFqkI8bn`T(NdDu@0L>K&<(n L !K(/(ʇՁ kێ?X7$#.QTXhi omM w̵{ƪ\hp c/)Y(@`PL۰.h:* ؒk.9iL GIs,d -zݻa ;^>WJwMgr0WUF2(\VnwpWE2l"G2TchG Hvڸst/w<Oj"W6fFͤIܮ4 ѻyEZ1\VI)kXnTru -%3I9\%˥7rg(3h9Oƒ&]ђG*FU{R!usZlbT5^LT/cajYv"ӌh7 -IY;NxhcU =xgjcO'4;j] JWu;l8R:˃wUcnwcKSB;n^=yS3yKn<;nT4fgʼnjN{eu]iCohЬY9+S^tt9")L jzJ!%d *cAIrfؕա- E Cn2|ԧF:dҌed[HT Xoz Hn$ZUn=0isqcr)>вǍh1HҺ=1+v >wZw|[ƩS!j7{?mjd`g)f|##/\CZ6M'b A#_H"'ye9kQc,wAC٩6kykƖn}r2JT 'vLUԆT˨m$Tڄn|,V/sN<˗ ,M- vp -endstream endobj 28 0 obj <>stream -@!tkVk_4ْʈʶq=zF5Z8ZRGżT.ڗ ~tl Y+'\0b,}&H{AZy0^ܳfΔ[$SNnܘ`{wwq:ҽ7>ޜ?cdЎV:,bAhn`z͒jL1NJ9y96xŋow'Gmz1 '5ub9MgO`;a}rʼnww[OmG b?16x<7'O}iRlU{;MbI#'<6/Kugu.9d-'29F9МS]C]<Ó7P/#ˁưJRnUutvj DHOU.$tx?׽y:Wu~svQ;T%FN;AX0QR>T.OvrJbIBEg ,! ApAI%kNֽ](H'5kE[`aI][&nwc˟bjxz~|ڟo#8"f|2W u3;؟z> {vݥM`ϭݧp **>_mOM׷&z[4"'XEp[~{8P:;/$cB -._sY^t(c\9uWnĘ&-Lg]~_ObYb:OK@.?/>Ђl;SM+8i->z 5W:\I@H_Q.=z%d1Owf;?;yb@eh]ivrS+fu㏾3Pl#u0kArbhDR'Y*#ʎSݭO[CХb\kTZD25+i~x'?p;_f34b~~?W*~0ޭ`N^_Nd /;=zWOk;?^ⲽYXmߙo=.T%[Cř0 -b/;+5jw?>z.[(\~͸aFᝓO_>M5Y_>f`v꓇o;G9=Ajj@b:S٘ZhuDs']uPtOqWĤD|%2o[eB2esީ  gq,0-Yؼ['$LN-"Ͱh.NdBǰq6`WA@ڊΝ7}2^}/Wmr8./Nn߻d|UWTe#ٸZߟ>x31 ?89yAQ/'{/>_}usIg u1 AԭOcpѻJK8_{`Fǵ΅rwk- ګɋ/|~rǯO}=^(ގ|En{魨&Y;E4u$nOmX^*ŗ &g+#aV5exd#In_} -~[guH6\?D@0Iysb:[q p'A{k 0l:^uέq3e89vڱ~|y>Kg?;{yr=PZZ~g4x7z4Ima:Ԯ7^Q*Wػ\'ɟ&7Z0ϟv 'lQIo Zh1|K&ts?\FEԮ'#ݲmrqIY\aע~Խ9l1؄zoMbGw{ ON:41ye?_=Mvy@|^Ke$MэVpRէi{Jt4\T7)۪= O5w;(UH3T~cpNxjF&ªںی&ݱx Hm-zV1)h]뜵@kD7Agpw.JqX[Ђ(L`՝<g]u{X2hv;Ov_|lc_P ?3YHB NWky3^IΝqYmy]5i?6ŷm\\?;#^gpPm)6JZ d2E{q?Z@ ép)=Qi޺ny y -"\Zϰd^]7'xu#Gʊuh{k~ 2Y1[nbO{'/VcRLg;y:|ฌFٜZ=t D˗*.ȭB3} H$Wc A;I#U[]o*8Hol|hΓy35`En']ݙcӏ;eZ(h=}5ֶ ʴZ;.DO-AUZ1ͶP+W,3TXQ)5+k,b.`]BA a}t=%d3y v -v˨(5aG`)[3;[Pi6KJjW%4R:jorSày:B!RRXT=yRcB0 &SX1nQéqrpy!`V,s+ - u\^//ckFH7$eP*щN0 1](+{[nS(iQTT*ۼЇ墒(6[>B+T4mK|{)2-ElV$(nN%-kj{3~ǭ J,Q RRR2' ѩu8_XRQa}o@ĘX6I`xƖ[ݕ㛓C p'zv>`P4 %c./ܻk}/Z.B:0!:8쇳h%`x[Q дWF:rP.ϝC/ :AT -IƘl+KQ07~()Y8]-5Z -E b\Qf:#t$mN77|tu4txE/#6#4{CFJrq?Eq F, \j2p\ !\)>ڋw_F}x ]leiFpmY -kDzKȢ>l4ޕ6BTe֎deЍL?Dh4KPbQfM$bz{斊 H/eHm - PY>d݄HYyF}6HIW.HC{Viɀg+j+O!#0W6)V4Ii&N>8Q=p~wymf"wj,ob{F섻f/yeL0MdNS tTc;.LO@jg"/|rwx8]I n8K:Fn+W=.ǫWq>v@-5Hlu;og@{oE滜4ħm՜Tʹ< 2yac#] -J d<u -IQ0c}ݫ`l5wڑap(W66tVv*E\5[_s K/4CPK4IٜI -2+VU%MUڄW,%=_\VDNa+blX*ësfsdf̤yNiDQ];O`L&Q,Pr`ٽ??)n[ɣ?~4 -Ctmk@-ڃ~HE2MvLN)VLɜg@T;ePC놳jT[7a uz RhpHvW6iwxcߟ*tk+wz\d_#n;63Ye6,xk8"p}^ƶe;UmT((0B Ukpp`C3|˗\8 -bRT(Q&MJm &)4ms0 6Hy`< ccAg2"$+$s**\uAT[%R OD*N"-/3d$X4 -RQmS)צ:_Fߗ/+2ddU;(bNdl$]. o|9(YxɚLx_2n r]hJP59G`xO V4ÁfZ`O> -LUaCuؼTez2+'QL! t='Lz>R;ÃBIeȤ$c[vTg{ЄP]Ӎa/p"x:* -NeT/9OۏM{R.r?N-~$E`uǥ ktZYLPŚ90>8PYd4F.ƑfC/sRV t!G6]Q5&djHв[]Ij26$[6#7X( t&O8jq|إ65(* ,.@)mX|`futc))[6%vh0l%lQ'.z1LBP؈':UW?Εd #ǒ6P^ot+j(ƈ"]c -e; Q5vT>Rq8q{ټ^QZ!T""DQ!I*8$e/qwqQ[^** noY%oJ-_XqEB(Ф EXDZ G)Q, .Yx41LqɰI<+/ԯ9֚6NUkj\À>BR(H?7n.& %K,- $r~uj#Yo`O ,LǨ=VqɈ޾bq"N(#;Xos r6# - d]9\%! -R,\f|{ Xjɭ_ $!E Yc>F4y8Bj<_mVVi2_yc&E0]A˜z$TP Mg6.x0>S/ -Ņy+^&ÕZfޖK#FήU,$t+* 78+IzdL5MF*"SUw8ƃ.慤{sAflFdKm4FX| * ˕N:Dt ,*gcqP./J;WE1fC(R5{ bInD2l"h_N._oIbV,&]hi! E.M RM3zaJV%\NT-W)&4j6QP _-|A;0 n"ƒd̲JrƷᝲ"bDFP~xfDz ?-IeM5U6aQ_b)P"fD&vmwm;eF^RL7ݩt \#+=Œ B |uBhI4 S)Hi+;BAʼn*, 4q -Jۉ7hf‡Ͼqx>uqNg -%鸐 -# bf3-m"ues˘1L=t=)B2)T-+ r8ܨP63,;7ՍMƺ2JHŃU)/um7nfB^ uA0WV6/vJ(KR5Ig߫`$ۃ+V&pFV9`/z VRq& -8ܰTPЋep[OCS%etx[UX?#/\PH_?:gTcvxNF@ierP-s"-yu -L Yh_-ENBpBS3'VBs# AF@A UJ;@mg`39Pdg0}0=A )͋^3?X}fήѾXFBـ8qKytk+f_tF!j:E9C ]@ahyBmpK.:eLh@bׯ/_|{Qu ~R [.0֬7ϛK>ZLڤ\v2y-Srj[{G_&vlG,H8Y<6B6S-IJVwr3dTPHV9خ`nM.CA bjS Aq3,.T:$鱔->C!K kBPRDsQ̧ERYMio<I6q_8 -l4ViQq96\f ԏ`*$ MPh8IV뗌&٘WvR),\fH\Asy;َ+QRX%ay?l/C@+# IvT bKV@q'@qdbK #yI 67< p]5yaFqcR*hL-l^o~>Xl[rkPGK:/L{h]lzn\^+V/~~Z(΁> #^,;-S0$37 -A9ѷI) !&ihTFM fYɈ k&\(CStf8@PMv@]ު ߞWF4Й -j* PMQ|\&CД'oa%,0jޣ8}'顸IUJhPTg/!Z.$snmpz(%:d|Nj; >?B`3g?SzE( -Bm#MI[@n\d t$:CLG۷9ŏ+hI`8vpTH:j u:Q0%P]'lF*mQ颸I$- K!*8;L,]A+ 6!١Uo#V6FmtcT\I^ MtŜx:R(Aw2cLUL~{#j<PD2Va޽A4fZ;uN}unsqdpm*X2AaegU(%8mVY1/*0iɕ΀K$-bLdm ,fU((HMZ <`nh:]VL - ֏ h];vŵ?{`QkɥLKRT}pP\)}oztP3 H3X -D?ݸqk깂ڲebZ{]PBxo1zk1Aadl2D2}+8A 4^0Bf"B>/|!sK "Z$M%/5rX^F-4% -&[, 4gOQ2[<p;'ﻵ -^/`WYm/1Ԟfl`岿7S\lLTTq?ݸyb |IQy*|% z${ )7\^6%/teT$\(AE]4(FAGHH:R񊅁wcQxZiI&q`|Q.`FX@|IB.VTjRE'WSYۻJMp-v1^t@\A遬4̝Fd~ₚA‪1)l7\d/ȹ aI2u7L.Mvf u@ N bb~ -}6%WQ Ru:`<%Og|Q!#X%&UEuQ1*?zm'oGI#j 5˘mSsl4"Ƀz`*\9T4!^ L0G`~(ۣW0ʪq\n$ŌDiZ,o ϰl^jmު60\y+bEԇwR -US"Q -UŒ%j ^BL\V|KLFJ|x&@RY$?lW{({;(`^QʣVlP[* L=76RR.%5Pm_LT!_oߚ1Fr b'9`ZeŅ9iJkivA2xepc*\ZT{VIn*S5J1Ŋ)ۜ'qKfAŇPSC]Hp^|k#!hyG0-;8A5sp7Tc$)Men亄􍴘ɫ 54(TLA0x~gt@TGsݳ=YA\+$kQWNvGV%mc/22 uXz e>ɦszH 5@6SSv PTSyk3kd/j^yJE:zq| >/b7R|@]{'Jo&rzkMkӣg{** yP'~?7CV8UC3ƜJx6g Hf]VgKeO+Dka bkTh[ "֜:BeywDeJ|*pAOP:Δ{BN)Alws8\QHQ.8pv^"ɑDɷ',TG 8Lb:/3lKK1`aTv\A %2QIU+JSbـfsX,vr1ߔ_%qwȿTJZ!6)ue_: |sah9~BQmX]k"^Qƨ'Xe'pf++!sKeeQZX'U%ueN1V -_{': -^[%ׯ` l(Vo Z~ڝ<՜Te=d;ě]dVz_Ui*{{ow_~̀FxZCpb V@+!>xӳ {/b?SQ]29y9jqGqܑvw9jqGqܑvw9jqGqܑvw9jqGqܑvw9jqGqܑvw9jqGqܑvw9jqGqܑvw9jqGqܑvw9jqGqܑvw9jqGqܑvw9jqGqܑvw9jqGqܑvw9jqGqܑv&ǘ?Dui{=Ys1^N֮<{꾳/^kfN1ɹ vKxq}1{ טŘܹ5%LNۖ妲Lʙ㺙,s]n*aL&d4s]3ͬ6mOMYwOx]xי{^|k.{^cYgeeڍgfv]1Jueҥ݋{ ^gl;c252t+smGd^X&3b7Mf'v'g1OgNdү0/8«\ !?\-[tEsNaж=I6o=?ש3+Hu?̫Wa,\PY&c&8vi?2Zjebȉ&Z*6L1Iy=,9 5&hE|~9TKpKCv8B -njKPE/HQiZUZF&p[V/{"}Ro-MG1?z-f7Ix2ҹm;r9RTeXFb:YVMF3S\1>9NҞ.$!͸H˄5w9fc\)#v?zMbX̝Ձl,[t0/R9Oqܨ)!IyjV_2j\dK@`v,*]ұT+ߠacU:F _ -#ZmrL,2Y!&$LJ#@00p()^"pԵ$ՐQHx`8IEE @z1iPYQN(iTz'P4)rE -Ê -d*j1LBp8q En|N -S>뷀4Ş(@;e<,::#5aAyDr8FNr RP4b9!`ZaDH3 -r)5clV \ vGiWTXb*y-Tk34wrlt4sgV(B6oP5Ja(0T0 0 yeR crN(HJU֛B $+=`%i[;Y?К@)ahPV "WhcS.X/2iL`Dm+/4O["(m -&N11ÚIקqd‰K -d6a@K 4-Hu6C0FL)pbq|1J$v(8r1(NN~L8v x˔CϕmkL<  eQI:Fbp|> 59EOM,-WGqe(2UH1[HmI*F"*M[ _MZe9 jL ` RD"pL.r P;S~rHD7?WNޡx7:I60tv-_qXYZbI/* F2P@$SIU(PZs'%||2_U kh[, !=B -U0Vځ9T)ȅ;E+"Dܤ:jNLۢH4i]EMz-Z0->gsX ~򉁐΀Y -"HE \D_S<N" ,/˝C|Z mB-NoPVцIQ°cF3 2g@y(EGsoPQabh FIRb/>2b,%GQ`8 FE] N qV'}WA_@.f %a,ŔdmfV<ޑE7 TEA6b0ڹJ [Z -"QB:>+9Y dIB#Tn~ٿvM4*!,ëeX$a^rCOXڊWѠw3pYiZZq+M=э2t4'T-w%[U+5{2%$|(j$ D.~ePQrV%B%L᪁ :`3%=> ya*G -,_z,V -I ТpMQ -|EZ U#ǫ߯!|>1@(vÖ׫MN1Hd9 qРXƲ%⿘"Ҍ|ׄ~5~DzSzISS"҉бWz?,[=!=r E@X:נpAR80LዂmT6LL-r[n~[h5IՈ&%әOe vn5-Zl%P3QrXIClJ-aO*9G qxlg)@iƌ 6C qyR 8LzDOmm56y}LJHRUv:,I@>:~ȊHf u #;Q48'PZNNQpDrBFBa"Q*m]z a}V.#!f-^(jxeD?MJz7dU f9 ؀]41D~tLF/ʦMgiXr;-6ϫlL$tW D8˰4sMA\2Bž >.cbxHa.%igtk%I5>8;Uo+A jMD"U>GPl GLD(H8.#" d )q(L4rfr'IanQ/D5T X c!¼. VBKB^º"Q㐩G}rJ -)D,ͅHJ@F&(!&R-` -rb"8塑x01Ԧ`p82S8\`NZc30JN$eb.t-J<]_6s4́wF$(^ `PŜ)O 3F -17LR(mE65=^ī i b^HNN x(!8"<IdT(qLM:J8d@Na86KE.pV8DHA4IVt1>i|/2)BCd0ׇ^hPbpuѽ|86x<pU*-+sH?3 q`?% bbru^ ny{xCNzpiE⑰%]A,'.$Z#*MC> -dΰlN+H-h KwD{h-'ͅN~@sEQ&sa&Z`.2>9+r+=?TIdT+a˵]gGfW&\wx2A` -*|gdtZs4s&&&CfIQL@X& RuHOl@ -D 6_]螜׃-`1!G7P81AMLD#>KM? L܊u'e1 MYV4aUrHD9H0B< Gc&qq -aDJh]7( kLL1(0c,)neCKKc6M;TͤD"TarGLJ*цdγ{A łix ") -*bQ*cq(e"ųPNWwugJpKxPUV(2\UPQ4U !^h!rT%!$ =9s ܒ -9P8 -B飦5% Bq!*} H8d6&SG;y -!=cT*e LG5L侘,7 )*SD:']ȣHAȃ#3lIȢpR:{Q^yIL4'7"^C>1E}>C5@ձ Ctg&a6''!DX{OD`,ЁmaCҼ(6)~WÉ,[ OpZ<>PLgf)! tNqs5Sc17#܂PTՖ x --e܅@- s:og֐?'gKc(a gV-\]d(o2\Q5:E%SpQnP4Jރ @8 -'? &ͧy ʪ^i2AԤ75dxr.DS)H {qd3(NNEH!y Y1 -'(LoYYO&WY)1&5e j@u!"LѓnSq -}Pb#uMP x#9L{ݼrwĉ==u:LMOL4]F1zy*HiqدJGWFQ%Fe&'Y?q+y򅂉4 eO”DT)4#e܌ @-=9&O˗Y$^xcfAsA$HN0YQJGѡ(GVoec!E6%"-'cb3ɍ/A| -S^ %=r9?tr#r0onܿu j 'gc.䈉9b /=9ONf1'H)/7A( EpJG"[q6_Er?LJM s~kSV - [S>T]QLhjf2 -OjyXi ` Y]B~DIl|"* ;1D Zx}I/lqx7F-M`5A@¡=H~SC`H{Pрx>Y0{Dz[Avaʙ$[5h$eԀzB@nwx#qfE2eڔ:$0!;Dqs`( f dn #m(a:aآ?|x_ mٛkNW[p4E}@(*@rd%YHIlUkjj]+C.類 H) HM7S0C)RWqIBCP0'7 @d(I" z `t(_CXi]N@P RIJ2M `gx[S|irG8lv"7#cg+N._Fp S̽@P<ԧ,!Ɓ:\P17IH CdX.GGQ!y#d-&+`d|^#Q)ڍ2/d`o8PIsfl,?!`?C%B -4_q, Y"X.0Bf:h+{>WSLnIZ]3J#V&J+AGZ"F,&Α( n$֎q^d%cmY r,AAԠ{ZCb'NP ~*1mÚ(%JD74ƕ$sS'3A WZ1U$s !c\}2 -j G`bz|"d 4FPdO5PʲYP3~,["b*+6>8Ȣˇ7xh01n Ԁ?Y'x0i0{-xDFZN飿ZhN{vf!aV2\|S{3y'.mS\5J6\rƬ/ ,A ?>X#rɈH[Mwʎ@&D2ܐ V`QsC1yIk+la֠;]k _ -B!35.l2YuqoʧN*HgdfbZF,UYkU..plQ&dQ -/bLI $p,VL ]/Qhp8@l`Ό)0Us+ރj -PM04幒WhOC'Cn-<^p.`rE,"^ٸD( &iK501GM8B#Wd;ECn;PP"a٩9ǝE!Vl$8*F[jnXY!ӝ.0Z{6UܐK[NfVT[ =ZbIwܼly޵2 VHpr4-*.HDTߒfI#W39+b'T/̊#Qd&K1B5ފ[#+t%#u:CdɢD %Ȃ.R6 -[dBƇ)!J%$0dD*H5˙NkM^J=|5d [39IryA3+uNoS`>T4aLQbMĠvp =BH&NdbC&`p+\3~8geT539'ᥦ wY&M9CV,c.""4\!FC/4cRVjq01G6i<4 ey^${\*]vN*wO90sZ@mLyӝ;#%V*kᾨ֢ cgWr.Qj9exD9 |3fzڰȖɁ&]͜1kvn-l$RFݟlVTNv3˕Epdo bVm̕W 5 T FYr-Bv0:[#oۇ7u& %Yi9Kz]KW)6%-tÞ@+²; I=t+ҚKp 0쒵bXl;n/J%0P~#'G`B(9RLXJXf;6+p=dl:`ƙGAkIPKj۲[b}TNpA K rH ;xBV|aE&yN-jdjϫIK|%G(rY5;Nnm viY0gҕ;̔HIvxH"٨fg+ 6njkfzFN 35B}-bLXR1 @v~EDWlUH즕]tkVKN k7YD2 ;%GZr$ǛEpf( EudAL.B1rrŰaډ% @$&߈ɳ|'[ܨB} JehSxjأϼsfJL,!ӥM}v>$Jk=U/6ӥBm;}6+ [nuOW۶S2pz+77ZWZ[A.Tqq0ۊ[𔛟VgV4{Rou/+\*anm}bzvs|4ikowXt[kw8wkZʐsU*e"*;.ZX -J;Ypk[]ht{nftu6/13C}IJtJu9>ۛޞߗ-꫹tsY[g!+ͯ<ΣkZ[_C,n<Ã+{?Vzo}ossמ-_^߸Ng4r`8w5:@]|t{[I#Vٍ{twWn>sUlʪusn^xQ1^Y.*5^NbH ; :73>yM2kn6gV.ݱ'5 - QrbPZv9@jjrwߛ^z[DRY_nVܟ/9s'K塈@[.5Nmy] -ta|)``qmgf.D4[ӗksN~YTIst2Y#EFË'Sr!ƥXζSl SԞoKRuq᪛ѼnXZgg0{~fx~oerivѥz4Xʍ{x'x94ϕ:'ԧϧk;{uosC_0S@TaI;vs W-{࡫q݋O_3Ϙ샷{ɷKZ-LJvF̹;kffgN&|mX^ڿGJK|3[7Cx,zyHٵOߵ3j*b,'{>*d9]iXzTYrsӳvn:2Ĥ~}Nm=0^n$S}zCы|yT+Ic2fwikP>s+',nYܺ}uvt.7//?R]MwØJ噧Nwnp`Γ+{@*p/DSlN{b7vo+OsIIv"=;̅=8h gN5[r5->VsiN2BiQP:DY"\~0bvp<%-TvTi/=tYV2ds [g̜os'1ޱs Vb4Y}%07WW;3fvITʥl/`:l~!ny>NGK2@KK$aw0Z18,u9 EL7s (/WE\@ N<--?4Xngデ}f^f 7 s&[;[6v.G!-f -z)(k[Y(@f;Yv31aFM!O4Q(ЌIOm$8N纹L2_흄-svh;aK f7%;ևdt//U -JN6w/~KoDFޜ|r|>!ujVS\}׊@ō[h@x/b4fCz2!!V++xaFplwDѮ&Z͝/]˯JJ՗10W#'=M^3|PDnƍז7K<)n(Vm{Ƴݍ/U[5-V bP\;CVʝs3Z6 H)jP]͖疯m?b$ZLfc3Lڃ=*vlzXti88vM/bVOVjZK[+{?+:SShXL -2)7qPPKkfgb + -;nu> ,"  - Qe 0Ngo7gi1!L8NHdcvρ%1YGZM} Z¥G;TaZ-(Ijv[Mؽt答OuNϊn ~a*Be3Js+;!TZT~JΕ;OnpSe`|L9H4Xv?wfaΙwg3ݧ}DgLg._ZM3Ndf g>^j/uDTU.* ]M]oj/ rhF5S7i R935ЉZg?L˹nz09{-Lrm7<[(~Fbz-'3ѠbVJy9Tayfs+;{ޥFha^WW7<>z'_ԓm\2YZlJmV1b"Q2KFd3aHOՋ#Sb*7':xWUT uI7[pT|X̓ZsfezuN߸|pɩt//8_PJ(2lBQҖ]䇹ζ|[xw.ܮٵBm4Z>/|oa(,lvO^2r}վ0ZL9\ܔdQ:ܜ^He%n&sf*vT-_-omi 95f -ˇS0=T( r0}\Lj[ZK+E=Wl -%cYYBcICjIťIVH[A*ś Q\Kwa"lgK+ZzY ^ȈR!& !IPB-,vG/lVx!H)PNqdmZ0D91l#ђڞYH:7H-=^)֖LpӷS='ΞΟUFbkAr$j\vjSaM)Mo'ؔ/Z_o_TOj+1SeW;S{_ϼw~_/מ{;gWΣm=`TlAƃمյ.}۟x[O>ȳ?wo'>O_C rPO\6.nn>{wzw7~gKOg??_,.H):ˢS *ձWݽs׷.έm:}ʥ}k_/O<,M')YW+K)Eɇj2Q^\==|=Zo/>O^ 7fa1FjFX(WWGgj{OCz׽y oy/}揾+[ox}6n5c$MOGҊmL[^z死>O=''?wOx}ϧb -A8 eIɆ5X.q)U+FјrO#ϼc[7/VeNhy3O[?~}_[o:g%Aocc̍oyۻ?xƗO>/'_~o};Ow{,EW IXMhť7/G~o}ן/|O?O|~ss.7 ]8w}7͟!2{7>}ٙɗ!2dsvo|[>???g?/}_?~7:}svaq¨[jkg?xӏOs{};?wʷѿ_|NXN[NӴvwy[7]}y~[_O_y_o~k|?dg}~4-K9Yeңru]Uq-WL7o??G>ѯ?[_W?}c~t~9r~4S,VWv7vnݸ?'?տOo'|?7Iz'I:xܙ.YYY{_]]6Yk`  I(O(Z_PDz J" "cbg*ws{/?ɏ;np hXи,͵zut_w[g?_?׿?O//k?'Ϟ=YIGc4Vܬ؉jin~G'?Gp?W}g{ސC"Z," ,͏V_{g?~7~G0?ßAş@}yo4juz429>\}?~o<֨筸1zrq0XJ8hT2dbz6}iw׿O]vqcm!cp#^cpΕ}gG?՟w'w5<ЩP(DiLOլY\YX\uڣ7xo=_֯/go޳;g'B1T@Q"M^HYq1kO}}[ʧ>ͯ|~]?ߞKprj'^/$ 19/gg{>{[׾Ͼ{?Go^9$hL pCc, )f9kb8pK!(c*M[0;sdGd9NM'˶CpNy]KQH'} -f0By@`1dE#,N(,WJ׷v,c0Dʑ 0(D&I$LєiB6WKkD2N5@5;=b !`;qE[ʍFP̜immOf1025Q0Df[Q8Uٵ !Ʌd.XB 9=c"1CU^$8뤜6$"Íjƒ^LL٢pL -H5W/Qt% SFst)S.Lq9oj۬'^$%SEo% DHR^]<D H i4A#J㲂X$x\Ȋ=;Qc3PYb&hRQDy1\,GCXce! -CZ~<I"D ;@HPD0v2>;YւސL -ȌFD;3PD"႔GwF8Vfnd8:XnE0eZl r[[$ Z 2ēA_e: ~?8Vv:Zhћ. LaB7T $0 tU(eҋ癉P(M9`"Af@5יYlx#^_獡[ྸ^dl. \hIKPq S=N? M$E %ze9C0P#Em1TdKzCQ-[شҋj\HVb 0-KjT=a 8!݇[C?@Bԡ%5En]c4 L@H"2eNjbxa1,Is`XhP9\ߋ$M& p, ^}D:$Qsa(f@L\'=/ HW3'8i“<(K3C3^u -;;^/ODz]`@e,{HFc;ݡvι1 -ڵ]Kyq8YvݢJFbAԢrARy)šǃJi2·)&òY(z,`#j}2M>f٢n -=Ynlᙋ1en΋RݵtEMjAuM|eZŊ1#Tz={4I1*$ijѤYgփxx Dbs/vhι s‘JEef7Y&'.̠// .M $(y|T0,Db*E띋K;x bLC KC}SY:sn6F3la=]Xǩ*N{6Z +AsB1!g_n3 B 1"!Pk{灡ϝ x<$ hHh%pÙ `IELQEN 7q6K%+*5fO>YM:K)g)[Y3eUk\0B$SdJݣLsS^r)S^.ʝZ:X#R/}[+("6/SuY]7x@us -$]©LkL#1 ww$ˊZԶ[ g4t{,GP3Ck5܄Ūlbc:8W6d>LR Y0>csPۃ-B(zK$N "1*l&ϝb05Y?ն4YD0N*S Ud K2zCU+hx<8P96KIZ( -A1 -m%9HF1i+#I3G}<UĮ"Q)"J皛EggPhMW$_\&0r7jZNӪr>nfA4Ba5݉W([aD%cd"[Ғ``N!|~3,d{vXu{/Zk7_(o{+QӫaJUVL?߽;QT! SkwʵcAjFMRq7EQl3~5_G@oF"d0@)Ykgcr<9n6>dypO-Ǔ0(bN:DU~?)3䥊5;8D^[Fwƪ-Qok[0Hsv>dUoATE!HuNt mx)ֆD4'<o&IV T1Y*Kb1'x6+SyV.΄P/NVknÝnf5jXX4 ,S˗ӠDc+xg] WIz -n. P)T0`>̬F׬|2?qZC`5 ^0)h\њ0 qGT!2u'7-Զs Of=.6*^TNn\{[P2(!'=7UiB0ڱӓte-/z w˷R VT6ʵ l3bc${P^4w)EMAIz`DRu ? A\n\o,N*VhhK+n=?!ޭի\(ŭ;UW\R1u*AB xw{D(h‰31>4)iMIqbIj+5MgY G<@ԱQԌU>:5Vj0BM&b0RfgQbb \ay& !f~w6uA@ `_y>@Y"("/T:BV.@X(Bm3_ZZ_Of\t.0pR h^/r9&T:pAHJUMr)im poDQ (_ݩUUGN~#LM4Aڳ67 Vnd2h6@X7;dz\\ Nk>y+w|2^!bur<`j!Bu{|lXH4^jUZO;QԀ[onu*@5[]Qj 5W.[1RŽ;b&5AH NG7++Z|aƃݣ'΃hs\6 7_sQ2e%?8>}jik|G;qlXKmI: s ` ŵYw;;W>7\- Y"9V$|lq3(W%X0jHׅ I6: 4RP+Q|0!ND#$hT~%9GTkY}*n%+nCJ(=]Ob1'S`*bv!rXޟl:}Mra6ti5]VuН.KZdRi";JE27;3^,slu/[ U 7߹r7>8xS -󷿘/ޕѕ;_x/&6CóRe+QhIgpEw"Q%dv܉'敛O.ͥpJ"%EZmo]A!-(zb kaF)0@Y$R云\4 dkouzmTJM5sEPss@t|(4S#t6˹P+Ol; -P??y[&PMO3_-T6AB!1Rrd9Ѐ^;N S9ȕ(" ={!왣^2%,}X҂؈WT] ]]VTcQkҤ#r)IBQDepD ' تoSUGvwܝ^+w6mڃwn=[^8|92]y_^? uq(]JsuUC+H.I5-ʺ;˼\SW\w?X[\nn]\aX;5N҉]9a1sJ#K@FTɠ%KT~uv5-FЇiqwzp6n8& #+3aÆh_P  'QY˘KLGb͓/L}V],)bYP$w@"M*:k `X@i`at<=ʕU^0RPJF+L!TLgQ5;B@L~pZP9Wwu1253LJc6*} -*蠡"hSbsRq 웙 -1!ڪ{͗TCR8$"hX^ -ҖPJ$a׮ -Dh /WUȌV*DW 뷖w/SNL6 EHXfKōǸk\J""s$C|l`R{,pR[ٺj^Fgy ­7x~ `@"i Z,y nu`w\3\צXfqkm2k~xj~n!'B=3CScPp0/ɵt~5W/_;'H ny/0[X-QaYMт6;*vړ|m A0aB ǝśX=ԍW?bT XN/'٬+ 3 ^@uY|cO290"gko=, -O$10wa goJ(TV]E޺8/U!ˀTXT?3D9ҲLE4+d'R$2 Sy;ݢ8+,orQRq8ڈ3IBa(ֵCQ[ʠAA  -/@izOtH2Pq+Y;L}[>YظYomWO;Iu$Ta84aXJ\rm^c -/8FI`׳h {ʬZ'o7+;890T 9PD媕 DzHD" ]Z.lͥ㋱qp#D Xp&*4&9Ԇ7SIb?_\)[1,dk4'3vj=WV~  aokek%H\2:^sxYF3m׃\84bǪ^DX̄K+ZB WOfjiz3d*@X( ?uã<: UZH J|k^=# -Q^;ظ(26&Ir6IKZBR9CL9;fnu#+]Ij@(:w<>8{xz;ɥGE9CeYjFښ1OX,530&,b@ :Y䋲VŴnq2N1)+P\A2ZtoyG7ʂZ,C/N9f}#`A AmEmr]4$-@]WQ sx ;pwr8'ݥ8x*%1{D":]NvnqY/wMl>i5n>!^ &'UΰYU)U*4z-A|% 9el NFƷƷ~?ktqe8/O7֮fٌ׌xK[9J0θG,y0pHsXL%>"lO7vn<=VVSVڌ.E#u@zu3zL1ca)C -d}=U.L ')5QntG@@fpl*i4,Iәy.F`yZ )2^.t{Q:YqHi:;Os% T ff/,;/T "¹!R(2.Ka.~ -nTV7@Tv'Ve;GHuo}7^|\ il@Ta'GRH ')C1 {oO.##vwP0P1:yMX jnuOz#C@"MDc?dTv U#%HtokkލNCI:j<^ݽY*ç@Ab.\ChLzpk=38PTz+,gSE-#YZnm&wZm)Ln)YR`xs"A\L z<1,|)Θ ZEq ƎeWN6"2E$hҝpb);`'=> -*Q Rѣ_<4)*'M60XP.:TKBN*D+1-s|IX?Db -P@T@sVb3\ԫvMzzޫxzdV.HRyjX+s wM` adŐ bcƋ?.=~wM(6p%-Zɮ(廽ξjft Pg"/Df4#Ec/mT/,>/_~֦J}7D g<^jl[*U5 ʗ -g!Q7 3\ԊxM֘]p\aS,?8hvc|]M"Q_H. Qwq2|@/KեӃ.\\8CRSv5k{@ cx!3pӲ3"TR8ąBoAb*h0ɢ5V/n\ݾ)oDwP@Bw9>9hLe$MKs`QȜ??{̅ D5h]. neA8iC+P]ˊճ3[O^ QT~ L.1b1"~'s!7 HجeHR)QYQg7^:i4W?{ыl*n6_>~{yM+9ʥGdlPFv&vz*(QԦou5+-wUPF4PWV(\BʅY HB8FbKY*OD3UXjƫ]έtiqu;@5ĴUm^ ֍zQ[SGP$ ;yPhLfHZ'[_-thr|"JBuIRj0 b $j!!w|y"3bqQ.6:%ìڥI@Siο̡uh&Ӈ3?xɚ1L8SHT VHkv1zprWNLd.-Gq.ۆ3$ؤ/FB2Q~Pqf6&F&"Iza[dKkZg7ꃘ ,U 7a، 4l>x;՟iLK׾ ŦE^)@ 09рX pҨv6o.m^^I&t?bd3<{Vo^7/[I 5|~")jŒkkC\a'<{38U7ޠ -D]XMi+RL{ CA9_8b(+$JU[`;9u-ѻ3o E%EipD2ä|cDYUo\_[_ۿp`K/?wwѧ@V\zL`sDh̺p -Dhn_ ^@\!tkJu]7%챙^TŨKɱhV{/>\?~?5R\fz{WJEaZ ς - _xelŗj={[& Ix<ԒVotyt;WZe2u3@XA22jJn;j# -GPŬ*:iw7>WJrs3hgݫ_:FPV&TiofJ PTAbkU_  +`x!&K奇?7]QP+xkϾ߸bK[Od51VίٿHMP1o:f?j'3sխ -Ha{ias֐vy=:'0^^= śEYļp4F兵|ԙkQ\&}ӟݻ Lk"XTC|Kx\C{o; 0⛛>6`sbڏa +ކrLѭ?O>[Y[+ ۷1eNJo|u[5;vz;(7v g ?=man)Qw%-O1)Qz=]ZʖW=xLuRm W OtJ,2ɕ=KZbʬK ;&0\+Q!kE+&7ˍ śK[VG\WxX2~Oooܾ͛e$$kJFk.lyNիTxcuwgDP;6ZJi`lfZ?Wlnss/On[J}?C{{=dP5Bu; -|ٓ,!̓Gƣd{fӔPT #B!0Tj}E8N dʭؙ=ݟz:=pK[Nv?y_Ǖ4q; @WF" # '}",#q+3^_ &Fנdܼџ& [}9ޓ?ՍĠ^^;V0 -F(b[ a4"#t&eJ8i)FoN,ܶҝʽR.*VjjWnڅ)شTIuLH0\Y2#$)=FlJjkhLc' ෎:}sj{|VjgkORiq?1z uwIPnyQ,['#Rv'Jcsܜ&)Zkߴbb$|vORyJD]KYgFc#BLT& "9cӄ]zk/z{qf5%1Oq9}~5qpDnZs@LAw5sY#wVDz^/,7 Adi08|7>zoӓ(LKɝ[ѯ|?FT0b<# ؂\ȕ:O9pBk}aΝK`ዯ_|ⲨEC8裂-/HQkG[V8"$ᒺZ2Pxuijn拽no\_R˼*@yi St&Q="܆rqBVVX/@xx)L7Kb;qV%h9#9i!9Q}-]NWh -K^K@QTevy.d3?ݥF ̅H&C3h,p. ?8AV LɕWmEFwk0=U@9˲Jk]IiD@b`<$81i1g!w~>IrsvWnTJ#Y˧;`ۙ/V%IIc&,Xɡ[!!Ai. DqN"y搗B%@dBRZ鏎N.?rYk-TsPBy^Tx4;Nj)\;Y*84['٨kقx[,X^1?(dw?Xݹk˫g}s_|Okmx]YÏw=;~uD+g9/TԬ~u:X%*M}v J9)Ko_cE-ܾϾ5BQҝ>N~0,Gu̲Bm0rǴ:Å+сb5 :#iu#mEC%x.Aj @X!o[w&W a1wNF] :YhZ!_׬AgYfN-U@9F,s2\i NPhNƺb6= F{m35r -ˉLt'(֒RVj [^zBP%kݓKSJ/角z -:<{<1\ @ 0@.\7gɃ=33U鷀Jh]$beX?4tSamt}ڳvq_7Ϯ?":Tjz+z> -ȓfjk{Ͽvo_v7LVgxͥ ^{Zcc= DBTT;4Q -'w|b#(eS+[\y{ealiAM)b -OEx$"d_L&Gp"ѹR5Ҡy !,(+$e#1= bޮTBA!#},['4F:SANgVr~0-9oZkL 3H ҁ&EBxBV3)tAN| -$3f(mg${FAZ14e@x4 -0iE6($ - [Clq\s$a_żiX2 9r31,<%*K6Ya0,5QTtDKkr곳/,8ZpqH ‰NB[KъtI j2r2N1T&IL蠏 P|i #& 9'$CJ-.;i"9/$c F!](hKhD xX0*/621`.rlbḝMDD'RP_) Fxp]4I2AZ$$u.<3WgVf 3 ,$ރF"%EV$WeVOZj#6(b{*+e<5[Ҡ,IPR:R,9I"kU\*g1\.fgD;u2#Ȧ ֋0L/5*x.H\X,ɤ$KBmz6Dwœ'3 -"-1X:UU$D(Uy , J2,Sld ͐ $Km/W*CnRdͦyҤKz8/W lb.j]*5Y/d# 9 q\N2A9hN !pir8%Zyo/]shT xN=y2墡Li|QjmK`E3n.Z^>rI/2$Zb0αyx"8+ L2;3& dsd]"5!ΡS'8rQ|<2;4aJ LZm$|dZrz2EdM#QSIRX){ʩJyDe3"Q~?,г( Ge:ʀ2@*Zq@ -oʒO*h̦8[RMgL6Eey`G,Ci41ͤ4FjGݑx\8hHZʜ|4vX&+bMJXF+tEnts2AQ@,'3'SY$e.05C8 _`<ٔ<39rv:83x&4!ع\"F O)%N9Ɛv$`b9 jil-JLFqvXerT"IPS͂~ޙsX.6M<2uKVHJ-t52)*ڭ7o^zjhTyh)8S1=[ ճ}uM3y=9Tbfݛ_2B2/EH4.#vv:wD2EGb`I|gi:S " P~!˹mL&˅tr`]4%!1R$T$> M[Wdd>/mX% 42 DD43),vdemw k..nc4 {]'p1Ø+S|۲FAfp]e9Q]GgN$guβ'O'NdT8%a% Y -!L'D*NaJt6AEXm=Q]a;;Gmw)R>1KY@bL:]5qa\[p,sMKo#gμoԩnKfs$ -5$œY_"_iYr[9_+}S|Ϝ٭}^WD < POz Ba_ -u{yЮN&ݣSofqd&M L_ q܉) *;ך<eqU[{-GmU%`Q@Pn<D raÓya~X֎WJTB]v_ rN X$E2\I{Uk/^=zC׾?Ͻ<_O'w޸`2 H&ckEbocwTo>yy7^ϿW.KR T`tQYЉŮ kW+VwO>__w|ߺ??o<; ZE_랶P+fn2?'_}{ϝ|fTt")Sgg3S'fD<Њ5 <큻7r.9yG^sgcw~O|-o{}}ԹLEeRebQWWo_ܼqvoڿݗ/_oܽyHG5 9\e1JE1]?[g/gn~?/]_w.ӯ7?/\]ܬ}B,fEĒ%>r?xz&C+2ͤ]:Ug;Uj^;?x_x~ SLKq}v3 ؕ|Oo}G_W׫Up[0./8^9a6O}׿џ}{/?/ȯow]?J B)ʑju,}Ľqg烛۟Op߾?}ȍ>w'7Ϝ{\uiq'mKQQ9Q't^3/' ~+~ų/ _,<'(RM}K6gBw0Ox#)T,ZIf2:}{Y{jo~W~}^;U/D1 z˾|kܹ{.~G{O}>/g^޹\)Rx11kn*0Wk?_}?| z+gJG#c"*xUvOcg6Lm;Mo>w/s7G~?+7~~ڕvKl.Mxflxza&gܐ>pW^}~~_'|o|g"pzGJ*{z_)E[^>xLO?zK/~ݍNr*22Gdbӈ* R>]Y]\*=Wۏ/}ͷܼ>okcw/Ȫ -#פ"-j+AXjzʭe|s/yڟW^]ʋ{/U -+e6AY ,2t}Ľty_^{/~WW)(fHes|VAϺU_}b^;]sw;H:琜\ -G?2b؅й;qsL^5{c|KKj<}uaş̲%/*Wy [##DZM[oJ//om6?߾Vh+XƲ)K L6˧ϔX@Sqr|fܸ<{?u;w~[w޷՗6?}poeL*Qc:`)/ :¨W$]w֜'wu/7'n`olhe[X\tVUMhY /tokUohn;玗nǞ5ղYB&%縙)P)dDÉiC߫BE@ %u>T\ JWC94m̱ʼ7'c[+-LحUgJȂ9G o8LD[yOmZI8h6?X[^]˂km M(2@b(|)8ͼˡλdk$U 3T ~2͡yGAYjsRAui+8)$+$L<dX-w-dzUl5d0HӜ-˽͍θR0&/rEIFx]KI&fSm롯uך8 ػY;8(VIz.Ll{,NNMgo\R6wL\Q ??,zhc. ONbyZopVXygouO %QzA[Uݪx%zl -B|&cgU&q: NĈWCs2<l"Gf94-YIX9H!lMk՚\Np&aq5i^_=A&k]YnSJ<&l>c{X b,u!4x4An˛^;H۸Ʊ9)^nޤPک~ 5}4;;Kltҡd76fR"gdi$hұ.sH&3 ˝UG:J9,nqR<}x{zL^m|Y1uprrK0 -JUT`'>sxb˯UJj>AUsׁu(LD0#]Nl8(jVb -yhRԆixJ|uگlXV;GqS\7cQ|?Y\9uI+%LNf+RgwGcYНϓ\94piZS5&,ʦ\Fֵ՜/NUڧ9y}* + `%KjyExƚX.!R<@RzŔ9!)'S*8U&N8E9zn6yIibXmFlHѣm'j"cQgkꁞ_{nawzEAl(Z/șKbW=/cx>=x/#U 2F:sK{N~WuoYui1=E ڂv k7^&"^a+G1lꗖJi~otX}"Q’_7MZlO --_z"f[8%Cd' 8"!i@Eu읫VqⳲN};o=X_nJqiAtH2r\r;Ynl( ͚#3Rڵ3_VLnPk_q"J"ýpi D+~i8EE:`,3&o㍧DDVpjj',Qlk:@(RP+N{ɔ 9!|W9g&.!xYշAX' &/5Jh}Jh8-X45@n" 4Z R_ax.A"<9_ 29mDĉ&5sdWDJ(-BϤ -5as=-DW>b.sPYΙ4+]bz*clg?9jSOn}cJ[9O,Rlp* ;5V zGse'}{AeSnatk>_X6M$ da0ci.k.keçә](myjuh嗭pH #]AKgv_y: -TL\L5.޴rPiݰ~˷wTGBKw()F[GvyAuholR&jf`MvVڭQFUrh@25' gUg=˕+ןE%vΞ~I}/}l\N6ﮞ}vAJsB#ff-޽/31OP]3q95EPa>ѓvrH0L.E;,6!A'J.'E#.7W_\vOOFgK-ӛT^orV-jNkzkn0SAp!dXNnD:+[Dx'3ԁ_uJ޽qv0"UPƒk+s;pyC絅UFlQj9:ݳ/wWdkR`麨I|eO'W~ym6gP6 QB]^)Ngg\*J`4y|+^< b|=ȡ./s/` Ԃe/᯺\BȊ߮t6_w(.]z깗?A])kk@usBz}ݧˏa,b|i#_qJ>]n̊k{ԝsqzfÉ23e2ɩ,WIqꚬN_)U6@tHpuHӠz4IJ,Ww7.ګ$V { / &WD5K+Íiul/ֻW7.n=[t•J;˼iNo=UVI..Wza~y0:[WW6Zܾ~˷߷uƗ,zn@@_]c+n~o +U7/g+/N7]8rbob-jzNy)1RU1F}p|쭵NHR)6QҗNsXXTs᪕__b@\B BwxX%PTڻspX]=(6;tx ukܳ~c_vdg [/6(iF3<U7\J!~,iz'sw?~qwN2Vb tUInaw7x hsI(Pj+HifNd=IJ >dF""^Es- wV#(UU5F:N.=&+zua[TXfE[4W~!;T[ؼI@B -7~gptLw -&/_]߽W1|8-9Xp ڵ6UkK 6TߡCӧjf}F-Jb}=_^A aa.Ny, ?\\r0SYN~B.٦өvVOvnZ[nJ1<Hݝ/k J,aD˧͵-)@`=\07k9,DbP𪂍6_~~q,]+[]zlqxh`}s:AdR{4Wdb.*/`t9,uu<<9Au `2ٹbf&ChJib2JE  YǥqqR !>GYMd}^rRdk@~iٰ99jd_v/gA AXd,J4ڝ5%8Qnlg3OY^nz9BWJ %]p1!H]ZF x)0(2R 5JM8Ru0:_a:By=. MD LX(7?ʰ4OsJs-/6>X۸6i)i]Xv:hzPY~aC%f 'L [0]dxQO?=g2:F8Y7^SB"r$R4AEjaiK^L8'h „d,ovj}=T/$26Neo,KKH -Zb*`rmohqO|Uq;ڸ - --6ffDR噚զNRmĊ mBgg -p¦9z[:wy y !)}Vg4S -b ,y ,f|!/[5E_peQkIj;]p2hC*d) 8JpEXVP7Z( -f!_ RZN:F0fqH/[eXw񩌉uQUg:K)HtN~!295a-ia<:l }@(_ ʧcr 9 Lw0০]/WQq_V4WHR -++,B}&_%6;GRmfg0l &o>gl xI냘S%C8[<5MƓaE.(T QM'>JaeB2PLJٌfD9s,K{,&417˱Y6UʒWlTΘ!(%Z*Kѣ9?ri$uEklEh\Xțz4A5tWzv3 4+U 7Yt[o|h *wA) ؓEA` '\V4 yQ # sq95Dc5 KfénO*ALWw_NeVJ΄U_\bͷpesm՛;å ld_P):T@ 8.j'aT8鋢Me,?\Sn2)XF~=Vg \ -'6_@yF# y. `~ F+1Y@ݒ:p!Ѻܜ᣾c8&l<8N;,2j40@ZB#եːO$ hBa&%O̦SJ6cLaf{xTKqmFDŮbM%2 =?y~Y\Ov|*es.CPFq 4lfYg𿊡E A5ArTpςXlt^F曃KE^uWJٽ4+xrci𲨴a9[݋;OAuE ?ٿSkle-jKVqWc|2Anx^P:4tfsB0 -XS-A{pCw$SID(D{ 3YFD "??Gl^畫3$p /@ԧgLBP'Ie&w1<'p( PA\R+qBL$Ohb`lgj6mx:oŔ̝e /JQgtotV StI'r}z`rֿtBTC -1QE;aa-sA_^&( -b +hY#: z/ Wj'.OBg&Ǔxc:zqm).À5jM^^&6 `gfɨ֍Y:zU쵰rq:%4pA |[Nc.{_Db?.JĕW`X݇b>HP䒪p\" PZ-VwEuթi&)ܠI*jn0܇x"zcaY -r\"m(߸XdXL ^gpn]fXd;V: /*IGӳ<6-AmbtimlZc88(,offK0vIY:$JjtecI6W@mr<@)lZvDjҪc=H*t#YmzU ;P#r, I/K -  ` "`ElML4SBp?^Ƣ.31 E ^ )xZz J`jP~ATuQn6Gay[ֻ$,fb&k=*Y2g)1YY(b2Ag3"PC(p`@tU5Ex ˣrqZ:%=E<|%bݵ)E.&R"BPRiELJp83DDM ,E81Q# !Ǘ=鮨_QlL}Q4EWHI5Mg"İ(KKA4rۊ6Lm$.ie0QDDٍe+3%kfQpp=i gꉬH)h -%ki2 pJT=9t>J"@@NE^lD:ڀJn]Yq5JUY.\s!96(&)%YiєDhX+j JM zڗ -E@-wBF0 -8FGT`)2x҈6],b.pp#ԲA h~iW@L-xT 9?{#Y懽]ao\7w齩,oM3;\rwi\ZJHA E @߉J |QYqossoķ{UR@M-o SvӲAdM8G3."ur2^0\r3ɇq,@jˏvۤyphFjqbISE>^@Mu BAJ9ǁh,_u g=;\޶hڦZ}w=6N7혫Cf&pI!N|{nzx$ÙUu;r>` <ĺ -*Xv|*@}(m(:DT*NnxU~{o'_ׯ-)*wH>DZp Uv|UV=*+/~|/i>|.2HMwѵ/T|LǵaQ\8g7Ik9ͺ=Cԍ'ge\ -rs\em(HIh׀l1^ Qq%Pqgx1cy]o +]kʤ-frd4wGo4GBx!ʩjv4gd+7ܷ+DT }uRw% BME'wNG J+_e[VZь ]GB4QNrIc A602Ž7 -♨d7hIċp@T9ezk%PTiѵ\=z3˞1|Prs% -p;ld^Z[7 V5S++`' -lRɗi('Xn킑+t]f+uRTI Y)}Fqxmg\T4Wdv{?JV~,NzV0gĄB'-]0:Ud͛zZP_3i1!pFQYmo};};-Qle6=p=^?ҠRH'Q1L f\@py|Ҕ Q*MB/Ųx%'V*)|;%oHVD אAy:lcs>HۋPRˤOGܭ-V׽ٕd9ףtNכX~K0/#YJj;˘Vw>jze:mrb\)-I:q\Hg;6aPr* @=ɮ@4&岟/h4|3&9uJhOhZxVߩՏm QnP\*Ȱ}[|10>XEQeW`r^BgŢXf\P;f?@ 4!{H(Z' l6zs[,-^BQE֏5g`|c#㳍h:y-Nhʤ@ɰi,i^ek˙*j :Ilxpn!/_!{"+x=hdDvEm "E΀3`Z@Qag 6u{a ܖ8 - {i7{x~[^\ W6av(}x,WZP2'׭YMw.?ylyKI+tJ1faN Pi(R\od h.FE~Fb8/sc Y&$~ZmR=x^0X|!?4)2Uvfl%r%[PE  *s U$x熻 -endstream endobj 29 0 obj <>stream -EZ7JVk/wplDᤖj*|H4_eD)LmkE]|(&. IțXXFů[א:+P2IO^rRp3C2 -STAM>|p){x:\\@1vd$+UhUm`_},,Gd5[7't'a !k}IWa"/ZTJ -ŵs"xEQ(LߓHumּE7<۟+FSN&ͣNH;ق`\1IAl6wF&p!:vU{^0PJntt cKc`YnPY}˚I2vZԌ1L-)0GJ%Q}3zeX휺`a4sȈMe&B -(w>d̍ l -̤FJ%X+4 !hMsy4g;'?$ mlyW3X*VE\]` ʕ$W( - -äy&]^8w EI5 J\XޞfB`5^Z88&@TC 8pXrX%_k=߆}B,k]bV OI#PX MbsEK[;HrjV=_QzԮ7d#A}wPu o^\-UB@n i/NR9zu~mX|ol_R&Nˬqȟ{(t6eĺ://lM[Q./3lDyh -u.·:wyH4.: msdVBPDVFc=\Еoس_;Yn!oִݹ;A{$v yq0Gb+16o^I-+Nla%X讹jPQc]2\u'f(~V=W:ޗ. # P']*4mon:m\ABBæ~Tse(_4[P 0Yڼ^ K{Bl|AjRw)4bTwޣ_vc -oTW*z)vݼ{;.6׵֭+n8" jͫZ ujtEIgSTUBe᧦ e4G;!7#?9iU[` mr׶kۇܵ@a wm0}]> mr׶kۇܵ@a wm0}]> mr׶kۇܵ@a wm0}]> mr׶kۇܵ@a wm0}]> mr׶kۇܵ@a wm0}]> mr׶kۇܵ@a wm0}]> mr׶ߓmWd;6ydˇ|O"3dOϿ|O>[I 8ç߈_,U/|'?|]OB1~ej\OӰ^kQ#aֈZ?,L$8l^ELzV5KXo zO>ۏ~rOigd5h3͜N_y?~> c_L죟_>~z8+q307X#?VObFbV^A{b3jNp4Ij4j҈cd'LpBh-U﨤[Kے:֬|g4]oaXcsbE؋ ݕHכ3 ]*GTVK%mr9R\?7ź,`?s m՚J -r[և1 }^mKͥ`x<_iXR4rlP+Ͱz7zϜBZ(9oET;Z7+ -)zOR1 i&ZC[%{ bb -|&VoCImkF_Rڼԩծ䔗{)T.rL*W]֔ףI#MchBʓfSRUa#SbLDe(*iI|]eT&&Tg,Wf#֔aEΫxfIJTSt@Q6υe}+t"Ahhe -gZBQT|d`l&}A)H75d8nX3?5 E{Tb1/wt\|UsgLR /I1|& C Hz&xIO`mu(PpxYwX&(m[Wӽ-etga5~7\6ƈwR/Ӿok'1)++ig\A萴榃|.ZWĄ Vh2|:"=a -"Q%t^%}5 -M "HsJd vNB1X^)ڌ,5i.Ke૒A,|x'f[y62(rSo: -ygcY+@jิX2ʔ\iSfH)Z`,I(hW 83MժG||lK}Zc("(ULos۝Hpqtˌ/Ԫ6nڵ$7D).Mߤ9Д掠3ӞplZMٖf^gAyk^̐~t%'1aغ wX{F(@AB$yd>/V)!P&rA|<^5X>T֛N8$sfzj:{Vky>suT;1 D CBBzL@Ab{E%ȗhD4* iFg.Rv˕Pr:pR)%?JP*V, ] {dT> -E6U_ul{B#+: ipTg9`KMע Q$y(io[['B3Neyd#Eiz +:RIg&+XⰹDi/%P^zg^eøJ0Rh ҆PVQk q͵띗ѩ UdII"uǙz3ZGr^|Uv 9VXt2 E\ ҫTƙe -ZU!fH[6Cif9Į0)Fl{[)biSx+v+Tq -ie}Tլ 9qm;':k;r$ YprTQ gn#.+f#QBr_| kF:i㠸w6vr%Ս r ?eG_?S2VJl&'es_5d82BW3ֶj{'BMW[,3)ȚS̤Lg4=>=z0Xҽ܏GFh/oRTij2FQr96%ֶ Y6&h{ $LKZ9äc]"viU-]01YBB3)b;oϛZ֨cY# %FѽȲك\$C+r'I:Y+I:^5dzgˣRMo &f6DP1rUi/Z|Zf2Dgc˞a_Þ'Q{H)j:0띇҃;EWXs(v *0f3ؘt6%B%bGe-pxJs`YFV$:2T;,lm3F.r`fB$'d$&QpՖ9uLVgәzTy)RԚiuWQΜk8;֭Qva8 -~p!'t{Y< s]R5Lf5 mLi-z_᥉Q{&wG9(LNDP GKK':Huқr*vQ3Gadғt:bF: bJ-;ǡ$șB7P 9[L QME V -bB6q(S.I./n$w*d4JY>15تPpī FU2ye2Q/$s*^iS8(E)AO(ʖ4K4k6֣+1Ur(Ku \,N޶JzW]O^(iŲAZqb -P(47KGp=wAǶbQÓ[PaN;DRQ%A}EL`?7D_"o7Is`uFT:fILպdInT D>hvn5s:?-P*YkЍRvͷ?)l-I <^~>嶦!-`}5γxg3h;:B?4:6yKŮmmS -X^Fw~=3(ve{:i©t2 =Ie)#)eXǏszpmLJw,{[ ِNM7 ͇DCny0. Yр IJٕźLYiZ@JzY@\^JJ؅@*rl92JB!drEvQ+f1/l&'_*\Yg<'r", gbKrӲ';A&A%+qXBf -.|"0I6p?ym51Q;Z-`y9 HG#1"ܺ&'UA8Y5Rj5H$9k n 䒓1ɨnP(ҭ.UcHV\RE@Yb(W|Y%/% ¾L;Y$p7sVJ4w 9hU(~u15[9KӤgrHG]$j#yLG<y(xPOH@(=x\^#BN RֶG]6'%PD ,hc#M'Y( RؙF"Z5\  v_Tj6f:͵8qqEVZQr f`meѧXO[i\c^iCKpRvx59)f:C̘4ġkv't(&Ai R؂ǸX*n*Y 2/[@ZJ&u^#k]Mb -,Xy&WR#Xjx- --|J1vI1$E<@ - -(BC4e^b4!z$=6+4;r59& :U% YY`"oy~kǚ"*eT$?U GF1)Fx ޶-b -BV LB*^6f6k=NQYlIJJ2X  *W$!SfB#_o\Qվم@eZC[E( %UɃȭ8l(F><>$x].GYKE g+ni#;7z JNL/Ax!z*A!EZhh`8r2H; -UI|*;UV ` - hy!jR*Y l&F`œ3 c^+(+}laa1vD,fȎ&摱 }d&PUźi0!$ V&mcjF9fy?E 0EH 4N04C鲎! |ct!ҕġRu 1]3U w2OVʞtd 0x W  }KppEbO&0 gm ]ϐw.h\ 9㏋Zє!14XBYqB0D & rG|r !k1Y# -Gr|ƊYboPA'A|G ˜ "MhSY:Y"BU`,%tV}>&Y[; -.REK81?t“Nf1T˩lg[a@ oADTܭ{2ȢJ.Y@pBU+npi"1Qr 61bDDu]wcӞ -rO -Y RƋ(هATeC/V|"CP ɩrF7&K-7T;5Vw.u`&hZ{P˂G2)8 .p0גL!p-(erds׮ m%v@*L#,֑!Ȳf-nO q?G6+>9cXQ8rI%6ze|*vQ,)f T/?B -9 !19V b9R:8(ațS - -MLhp5E )u~{ek %i#>a:vǻ^j?֝.(i\V;7gɍ%00\{Qm^t5k: ft\o;1ьL.lg>no#AZBpCփm/&jÉMݞp0kPkNׁt((dEֵdUnPgy{;i;&@f;J0b)˓W {V# dbc=EbI GHD &Bqt80VL3Y8 -JQ$ҳTk#l=P kaNx\Z&x^p.`rE(({sGvHKE@lGm<ԑirm#!A(F@LJqzd74]!J.2)Yyg|5l_w -R"\ڗ˴}3rځfM2Yh#:N0쁟c{qd<ÙGVAs1^Ye U}y3bQNX,my|Z3NnTAdRQmay/ډAr[3UJrY!KܸCW3Ж" ^He؅OsW;H`Ni>TQÌcE搕E2PjyEbDܔpX=z)iu^[ (=vFW$0M -9+;XЇ%f^cS+ ^ӻjրٳvuΪ+YiT+ UC\{nFT -V=Tp?_ƍˬuG7]}7).UQRLxO'(uLn.GEwG{@I gln` 7Ü$٩,ugh;}NJ w!{f~uey竺9';Wys[wu2v{p2d͕lQ̞Dlh68؄Jdޓt9&;vhw<6\<)Fzp=HrݏMdutT{B"!4ԭiMf[ tg͂ х:Rj5ZA4%ؑ KEr~'#+,GrԶbtxiG^r4NӇ?z8k$øuXٺ=u3HY=:{{7_o ./0]G6>.aN$)z;HVy{+]Otϋ榇Gs/=v;kN.Ճ"pu\?;X#b>'%A<]2jQ6znD K(jֈ&iJJLRo_Oړ>. $]y鮗uǏ(L=cTh=Yz)ֽmXݭ5w_^}6T: x(۱QEj -#T23\>KGZQmnݝ?!)+֓ڞ7J荴N_}sWዟEͶ,Nӿ]#W %EyѿHZgKı>\< AAcwM{4ړwAz=@ЩF?n\O_<|Ͼ'}w⻬sdxuNzo_;Wb PW7:}#u}[\~G 7 D9ZE~:,^Nw?՝t.4x>__| Fk'bEz=>҂+Z=I'Au?j5WnVz^8aW'p!ɩ5DF TqV;߻bqzz}~013`O'\^\<_>W7_UaOou{lzs͙գGVNEBb#h:^\}wITۑE>rO>/7{׫wqU1,?^=z?$0=?eoB5g 'mK1CPau&YB6Ϳ/zTx?x )&:ӝ.?M֝ӛﯞpwyGg__]o=Z99}szxGo?ic39 j3Yo8N:oN57OÝè6`z n958w"(iֺh/->w ]U;vl/~sΆ'|slM2H_>TMX~6yٛ?9Ã'|3A 竝Hf&i'R54aor:Ikp1{{`j܏|yzg ξk}-{g.pƣ㬹ן\_|6Z=iN~SʏW{ߌw_tFpvz?=0WO}}ۣ_&eK۟({xW?z]"*f˛C^X!k !Axs;=kgjfz^BHԗndUmw'ƻ/G_L?5N[ËFo?Xܶ'׍7腨V?폗~lM>OoRN7Ƿ?_?_d~ѿD^<}dӫտfϑדgH'_u{ =d6TӃ|gq}T]D~؛O Bu3+OQ 3xf,6bH zɟſ>.X^\hwϿ5_xwt] Y3z|ڬ((7q`8ÿׯd]9}:XJg9 ou~sdTw|/Lo"șf98Nw+izUsT'ƂW7 fvK&kl~=y;?EΣo~~+hNw^ufv//OXqt_`nnt|.J揞gy?ͧՋ߆Ͼ_t{֏𦳝Wų߮>k{Xpz<=xTĮ,\N? z;{oOowf IcX<͟Ic1Z\_>u.l9#ݛbWt6q}0B8=|v^?ۓ_ݼ;8yh?>ifHm92oogsH(7Ƿ>[@ӆn~R!)-6d0rt_5Gԯ t3]x1gǣ(ֵhjx.Fm9Q6.wǶ;m4%59g>x9E(VͪnNohz>{{䇗GQmTg//==]?[*L}75[iqkG3Fg?X}1;4jf=hNd]o{PQ7 ;s$Ͻdknb"ɵ tOAU6ʍJc}{˽'7wz/rv4';Tc/ZG.4tj|rp^ɓ8yӟ>h+FW͞{Tu~JOvz>ֹio.n\$d\?=|jEkiF5kz4= zNgD/,+5%G"ir N=|A7\}a-/AQO|߽!,1Z ƪ`*Jv`EӠvܟ?%7jM7[;Ő|: 4Qژ7Q('7IL^3v΃t'ƵFp -ƠS'ۑ&]/%= ?|GG}Yl4ɧ~XTYq;I;Gb*'_#?*wY!*N{xKR{wsNTU]9WuuuW9}9sf 0! Y1 (^# ^ULW}~zԸV/Lw U{#Ʉ#n[;͂Fk8A3RgJctӋkpFO ?f -4z@ozQTߘZ/ʳv4=VH5^!շgbXř(?z-F4791/ˌPJ$zcq"u7n<KNkN04̊eliFT20S4x;4t*.'TPCC4Ԫ񽩅ssi^-0d #HX 6N?<~3gR#GĈY7@c np1U/&'1&O0R0+dkDa^2Uc~]Kv+641Q": tbqh VY3>O0D4go47FxC1"mew^*V9"!MJISR؝'x|h.{,C@-BJ46{J:E(tgw.정j$k,jVt:2utvlovZxKOfuՖD_DQI$$e,@m6le8Di|<5*)_ХdQ_6|J "AIjU]^1]LUqFJiB`PZ`mvnY>͛HOkkxbFpb*T:RMYOq{DA?e xXZS#"͕<#*1\2OEST/+g89-y-NҙYŴņ[PBRyh$Vs8q#J zj -r ꩺZa er\wT$1I" Yn/M5GwE-AђD -#b%^KGRN([]3ڡh>yp;4zLn2FN>$OTX&I.HS3d2$pLuOvy%' s'̓P)` c$B-x r+:L=Y-=y,WCB|eVjRd4p4Z#'tpQPZG`^ @EʁpԞ/ /z2MfXbnd?p˰)#فΡAR. ,Wlj ,!*f21yK#~zηz띿??ßv 6ۍ5`4˃w~{d?>s=<|_|;zK/~ udcd>JP9h8) '/y}=򁧟pgsǃO|ⳟy_wo~CO}viqdjd[\?<XXY>v'?~߾7qᶻ&R+y] -("JV 4q:ILt+B>.[Got}/=?~zR -%>+Ke5А"ED-okc<>~[__cfP6%9} -IPnDD_^MBًԧo=w?p{_w?3ws%I^ϲMcRIP)lsOMNO?yͷ^tK??O~rylBX, 4]4:/;Na$KY͢X4r||:?;s˹г7ͷ}˯O W'?Z$)%ndᕕUl^Ͻ>>^zǯWw߾O~ 1E(qT.~Q2ܻ;~C=Г~Oo??K_Ρ ´(C #Q#g7ݻ}>?̳Ͽ_/kow__@g _˗'n8] -IfZ\ȏll}>Oo[O ?mc-ATߙ\_ܼksg}K?{}z/}'@{x -ahPL8rp{>_|'콿齿ǯͷxOko~_zklK^ FV/+?y{oί~_g??O>箮U -,Q}D`utdg_ / {  sO_pn$ڮTJ]iTl~|btqy~z>}=tm'o=wRN2KsrP.q_de6<620;z}~WrS#ь#qec>EA?tfs`>2f.;rxoyg>ѳI=T`W1r3g4jk;17q3xw^}ͷx^~ #>v#ۛD8Zz -#X_C8hVN.?|W|?:9m:I -n2axZSJgmfʩO>vϧ?迾o_է?Kǚ?HS1$DfSX zvjlbi~vo[^r뽷_zs^~~鹧|ưAʣD:DzѐBHnu<|]_}/?rϹ?xcs˙c۫h.721"">$q'ʻ+kw]}/|#>}=~yꩻ\XZUxSNPMOÝUVT0?hKއ^/ -"~Dbk~$U⑼JC~Yb 49ܧ;=\-Nej`R"e2r!;16zȱŕd2 tv'#9]r*.|$UczhLL2U*3p$6|.,ƒk&%çp eUJTLHO額^P:T$ cN9l~YV $+MB5ʕd+n3~,fFڳõbGe\KF4%ϥY}~RF)&X{r3R& -1.vF hOSr P -~:7֡e1l-<PQHJB*r{,M(ID5 -ĴX6NED$RJi]-z9b<5ȁP* rRO(c׼LBAJ+)*C08f>+pgȂج,׃uY.\afvXƜg҄?dc/E$6">f)6)y%X -BP -K j#4$VonE AIJgX\.Adxh<"X6 YՄ$C/ Lh -zdV+ ED >T#~*WY]oD%Ư^#kW KRpCN'fbw0V?#݆rC!I)Rne8B!1GȄj|bE|>-hiͤ}XbNjQP~.ܹrXQu9C ۭ$4F1#XLҳ2VceDٞNm s: >3V>v;98;aspl8. KAml̐#P̳1QV({|!GsiQ9-<;"0ZP<3Giθܪłir7zI:0遲 z?q(*n1ĈdH&"Iuqh?AkX|Ob 7GlFPbYsJbNL*J5;$+$ - 4_bQ׍^ߍ=~DeCYB01BWMū$j1DTR'xǃ{ ;PZbE&vC0~FIQmDӚ'D&=2p/&{Eb :Euʳ Zp!xu#u`w7 &݌ 1L@ㅫJ Y1L0x)rfbx{MBwXX:&~b e nBp=OuE(>$hF02΂ ÕslR ϧVE} i.Fdve~Jk׃0 30 C+UU"K]TYخA:0t#PspHֳ_(Mb 06bAdCFlm=Xg{lɇDZ[AcPvPX6ñ  &Y$UM2b!`%Itb -#͍`:Z(RQACq\F lKD{Jdh.uY*:f4Z !dP}k ,͆M8eIЁ=vR:5K׆^4(U }^@UѪ ᤽B2@aN,RÍ}<=n'$xbmC,:0`AјwbQJɋtBRircsb'B)Jc6;kPv -c (:3~2inQhLɁ -͖`Y>vs^376C#0hf|hhq霦sX,v("KiuMEt!N5+#g &DIMrC~&SUoFD*56㓽CZ9p`J&T(O47W8HD~2[[7Ja94BTcDQAr,1+)MhfxNr~2G3%=4GlpsE;e\(-r>b 7D03i{|AZPM769hDG9& BQ1< r9oPtAi>`bn,L4&p>k)~\mqXP86ow`yu.IRE9Nbĭ bAj(q˗W,G ev MGbىLuQt ^.t<nD2m$, {.x$- 54pyUEz,X ;s7!!sSC֢>hZEJÉ:\-2~+ЖQxD -i&h,5=?, B:ER4 p{Nw6꫒j0c^BQJr,=Ϗ x-_^o&Z*#>^|8~˝^T&p]JH-^iZlR%#5'DLFsLH7Rt"o0_ )Jjn*XպjW򒟌xNSDa.e72md25 2`ƹWN*EAb@NJ֡7 *wV#Fc|l PVR!f⃃>I̋B~a9&tcQF'U8 -wX.c&Y);d@f3 OŰ x|g Ie.)7d4v@\eD*p@h|P`R܀DR$1U9F})T#ÁX7VBà.|rl(s0/4(`X=J z4ϔS9hz>n.6V ^x/W^lLm(.-37UUaP6TԈ7Xh|5v=1B %^ /)zd -[HIRkuV'nNjK w|0W OD9Z̗&ssZ2Bɉ3{q*75}z\2b[=2paib0PZ7I4/3[ {}N?Du#]Y?kzUTˀcRh媋zy4hci{SQ`pM0bKP9-;] P!$WpS*'J -H0 |i& `<_ ``ob6:pY/T& >+*Li>]\?z$9)WhlƳ0q#ښQVaS &2erAIriJR.~42>u垏xQ(]\*Pjm -q83$Kd緮vNDd)&t[:hIkgʛkעH)lqLJgn|N_~;s\ Zeq8Lӥ9Q{[Zsm#>MEmIՀpՅFg - 8HtUoBMA-9Fk>B5Sf<7#J<^:wAȉB;ngyc`ZC4ZsgG^" -W? [;lv\m8֯umx3v tBeSCV`b9.pg`n @0mNܹtቓP_4S.`BΈ1BEʍe`b}7wsJkprkZsp$9*EKl41K6P<.йɁ!z&7/&+$9Y ~]Ԁ{NzͬX:o&{ONlܩD&p&S1\aa漑~3'p{UJ葞`&r ~Q<3i5܎$'ե`?|J0ܤؘ PBBX/:}o8n>h qU[0-n-x\`clo;>؝>:hd<0eC )$˹}]Nj c k3 ~"~돮_8gꇬ}˷<_u,WB!>P+Rl>VZʯDEšeJQv;uPpm!`|J1̅PZѤ%jY9cU -l#PB$@(a -|xOdf].V^f /,*rk?ozg|x [2t9ܟ936V5:V; p* )Ŗ@A ͳo!9Mb`RJyN*QNHR9ڙ8ܚ8Tl-=Fl1n Ol1/0c=r-6zB -RE3%Qn {= -M'P3R,=ݝ9]9MkZqg?4&!NJ[isGe[  LDF304ӌ hc;F*7IMsJ+T*:S֣0ק8׋ U07AOj#bCgZ'`.vܞx(1d,[]dh.;FR[HVĜ%˫GEqy;nvI> -AB߈%یB`".ydF-o6;kpS2{g꣛XEqAV̓2TZ{o| 86t@<s,C=P\]oW:LBija3=s{?^r֓ws@#2䖆oʼnt0J1 Cw;l,mb^#%;6MR Y33Tzzs"ۛY9+%G -饻X06:4CAi >X͉R)N溓Or>P ;'O]E`x8ҹ%Aja9^ʔtiN#8<4ڱ{˗'hǎ#e=HRjp,_.Tvg\.vp7n+)Qi/:߯Mv:x ٽˏz40\- (2V*rX8/d"ITW?0 xf9}#Ly'JR!n&rtex;/K^g8ChtY9:!*Y1#P<8kD>D²yfA,p 4 Eh!Q&NkbmD/_ېN,3k0,Qʁr7t7C3/XDP׃aK̖j;۷ j[T;X6VZC6jEpC|\QHRG%h={<9_CUȡd%aϫ8Flf *CC G{ -\LdW*Br&  *z${ #Y#6*,*Z B9pkajrӌ^$(jTe:|(z>?b  @D]%PFp98O()eZΔz ?s{B`ZayNlF\QDG?UgP|3CG?rsg8çZnqf{nռJ1BࡨQ'p㡃\@Ɂa!O;]Lwv`#( -FXAAL5F&7O{am 5W+Sg)C%I-Z6)/\ ijxb>X -,pYI-sB{7ƹ a.a1D˵iIAAFؚ9}{"[]>o(*7S+.?vJh]#I%h+19z@ ENNd5pGR. &vn=ps9~bΨ+_]b{+aPȝۃFaFp'<#8 !Khy^HLR8֬B8kIGBՍӋ˗hxMDYE)LRs8Ek#\ Wp6g@x.#hRVm4>/HAd=.?sG -XXjz=ۧ|db(Hg$kXaC$Y8@*ݥڍBz<<# -/&#Q=^\;=?DP1̯(-p>_|%86!AԧZ-~g==ڬ~x/LY߸=Q1Tt'Nz=xXI& Y*9:]łq8fbbQ$aAKND UQ|P,B+%3m8y$Sn$C.;a '1% QEN|*xCW|H$STNAR³$E%ǡ|YKFtxi) W@ܴCMc5O"㓁w,CAPGC4t[IIpR ZP[֍ +7+ͽda|_ n`Gn&:ؖ|C|˧׏_xhxlҥd$#(&#hr6|;wHO -b,4VCaUov8|`cgϠlFLslqb'G/<͇}A.n]^ܼU T0v+hr]EIte vbzq/71;renb;xCDh\zjA nQB -CZ0ؿϾuMցNj耔i^IqpR^ˡ57_v]Ϩt,=7G&h!C/-'"C.v.'HԬ%%K1A*#v:u?`$[XH喓zƦN"#PʋbxxJӻr`؇n L8K`gŒF|ڡ[GUd$GsյFczoY:~md8g*<:fǡ*#>:f&vD&L4h -hW2B񸪷'BmT4{I x!Ku7* ݑ́p<zR -^olkbrWDY5E-r\B H4aNa'(@JEJ:ehw|}ݓwY/cxryԗb`96HuEHJSAfhhrRPN q~D!Ѐf& .p9L7!u~$ԟ\1('p+TsɅG?n9 LAiq2H!W3Q۷ohp>49y`6&nUTRH.k<#&ٱRsCњFg\RFFkE5N曊t 2/6|`хQ-<‰Imִp5vO.n\7k,F<6d udI,&A #鰤[G/o>z`M=)u -:ˑt}ޟXmgPqm^!B:r |x( F;C&(JޕppKLNNXX9VuC~=v𦛜v$bf{W!u!>0 ,CuJ+cӋQ2(0XC {kPA'}Xċﵳ\i!e8zCbeTz^ p(.[|#O?{ǫF<7}=:(:= e1Wb nz%33sKny<D}Od)cFdYh[EPtRH+/9mNtXPr -@v):E&P;)T);k7-C+zł P7/Xhɪ6aG<^5?1|\5 R'jdA &sL*Eq&bsxdTA%q#4pE(1,O,-JN fon`)$x1-[4bnᖬV0I1VR-wvo/֖ⓟ]\ݛ F5L=vb< p H.?q}MdJS'_}·ۼN2NceIICKˣSL-^Oc=ԯ1wf; nO$ a*ʼn-sI jEn2,^v;Gs>6z>_`ޔ2qVHJO^)WgT-?6slyFJpD[Nȇ>7||U/!321n bvve0X׏aPtL2޽.<>pGsGfrSIPLj4sO.T/%-:S04Rd$^s%-G1A)Jr<7w_6<~bbbsʊoeK}& 33kwszAq(p`tQ+RɄM!Rrq<kn,j$RJm^] Հ^fd8Z:?i:=(#)|wݝUyȬrwIAkS+^{$+fPӟ:߃!J6=vf `8Т:=hUaViRwpd(PƉ+Wo/^^X?O5H< K9p[V,`咕b2J\.v` 73Lh&"JYtV重ՊY;w‰TaJ(jBG;lv 10p>nreL T1ߪGwе$`-a41 @ 0tb!H<.u1T̏ 36O~n%<( -M'Ȇ'@8RGSjIz/Zx:Un999эn@d  fQT EE*Vxez,aq5Zޛ5)x^X誮{oNn3U77Zۻܝ `6NzuqllaeX\2"! -(C._xgK?>k߱ӳA^l쮞{7o4{;^f3GoNg[~0#y*J{*]7V<¹݅ҹpOfK<<\ef9 ID s(-J5j|kQ2*E]S1ے\q'U+,լJ*l4rEÝeMh* v5#itXi"(btO> f4*1H }Cy-5ݬdžg:#*e3EP+A0m8$ |bT[W3nj`20+E` ` ')-8<?q,zx: QwP$&a{#6!ER&dn>UXy+W.7VڃRk1[5Kj!]Xv׋ 7R3 `L$ /`Fa`I&)GҊ~nڞ[:qpsK뇭K/=wګT+Sqfp{V+뻷' -7Sr>{7} !۟U~"r+.TOm}{z+v[tWo?^__s fb._YX{ҭO=bŊ$d h^zɩUY.fIONIIkg,(i)^WkS7 *o囹[cQ#Vf˵MA*@q 聹՛3ۑIԊ&R*h䥔 DYU^Ecd7N_/*A!]jqX H@Z2BZY.+r ZKMF0 jb' t -U0 h;"Mzbgl@@ո5KU^) -rM-oJ3Χr3h+q c%=gLNT,c@#B703ouwN<}xL*(I7Q_X;Jr? tm¹\EF,`C0f/N{dmDV/Zͯ2g^Ͽ~˯t'o6~{~ݻ+i?6^x탅K#cDbl2ս'e1޵*H08v7<8s;tkolF2ڤPWl au f0, [KPl:nԧ˛;ShTPj7*mN)`XMnkzʽ׿ڟ?e˛0G.`-Ar:v}R,N~\,(*dv *em#`q;<@4666F8pquFbnIϚnꆒY'ql2-oKVe%J7u -vbtsqQank%Lx/L A9:lo+ ]A+N -$+EÍ@P3mc"X1?ŲiM^/e w"Q>  $][gڒ0t1;9ڜzS1StyY[?(Wffv{}~I]/23YrzzeR9ǝT&JOh 9jko̽ vnziE?şv=ELu=? _;L8?̝dk3n-.֋/~"'!HSw>rKk~Zd(.z9ջzsdpSkݝdhYMwt_g4ϦeplǍ8,cHO~rRሚ,+ݙ?s$Im9/̐&2Ӧےn+NV =nNLEUkpKn6F}pT &)T8u$46I+R1B_x˯̰\/gJeWv(@8 rƝHDh pHI`vҙjm!)q"2`r80@8'J=ÛQIF(r573Rfy#s,$bpfS3_>M$5 -vmqqB V0܂bJ~+ -Ql؂yB %YQ\rf,dw'QTVՅXtX$#.$.)BL%a MQ?B9<ISn4*0>0Ő$b -D dx:; GZ,N&Q6[$xXTKTcd)3aE -୪`8 $Y`PHTxm`(Rn+z4P %Kj´H@lyDl$` Ή+ӓ'Q2Ol8nӴRN\;ݝ0y{z2abH0; -(g\xM|M 5PX4N DVxZMNɡ )'O]@%fZ&9(LFqTC`%h:% bjə ?$1X0=+SR!"8ŀ 7$qqdM:nflP|y1uӞ%"c^ VOL3@|4"XUE[221`Td 'BA]LJ@b<"(.Mb&ɁqR$($N@ 0}/Q p2E12ϸ 1 -cgX(D$rCP8''PV!6 U`37)Y82c.[ ":NB#FB!U ,9 ԵK+m0C,/s> -05>QcH0yYBp @Q#Dplj:HBQ'&Bcxh08; $s)[¹k@dd qU - fjz$ -$Qab=``>ı ΏHL(LH'HĀ#GyX?8vl U][u5˶S XTpfR,_$d(st;_^4.FNţ̹p]ܽ#s7?z󟿳O"r4¶Dy͙VbPO -smca(٢*gKZoQp#'_֦ܱ,f,a1$E$NnrWMeon.d) $<Kq@,^%` E[_<5m$iywz'z ?wҠq,BH5e!W}nKbۺ/WO/}vn zKL1 -2, -M`Jekvfw[Wݿ󇟸^]dX /(0h$BPLɼ*_Lgj62&PbKclisj/u?x7>{W6_'_Vzy!@q -LMGVˋ쥭쭃WLgO__;;{w/U4X0 -,pDF{V\|ҧtf~ͧOqgܭ.ʹV_mj|],4ڱ7V߽[죝ۿ׿nlOi"0ĥȉc#1^P |J<1~×|?xQg {kjF lI -hN/M/]{> ~Ͽ_:O|Ͽq²0$"NrZJdIXRh) -d"ՕkكiҒ•}^'!0Xɲ̦:w9蝃|o}G_:WgKgY }$-iJاn/$O~ƛsg_owxsUw`<^ѕ)+oy Υ~S7OeYhf`b}U?™oח~g3??~{7)X,nYn.'XW^=e|7\7}O[)BCլv+S6|w'7w~{w}xիo>(vܤXEhcX d# Bj=Xٟ~?kOهG?|r37O) 2%roz/5egZ~ܟ|a]O>ŷ;'׾ާ/+5R:cE>1.pI??PT_3ߺ|]׏/>GOm8\BSKSKgK|u>/ۯ??zVN/S$+dd)/ͦL}?zK/}A{o-󕳋Vd3n/ƑZJW7/Syvʾ0c__?}~3U^yfj63%1pؼkdJ,ZA*T.~㕥?{_|t7߿??~ϾO?}_үGS%E0jk5xfIv5(W-߾>?~~͛_}/N&lh k<;HIEJdg=շnÛzuKMd\br**i@ɌXOE&be'SV?[y۷߼5g\jzRIJq"/ MRj2UƭW ә)'|u/yŸgzʮ`Y{,K6e"-K.$Lp̣o]-~g??|T.xq sx4 uyktZ\G 髫_yakoyl -B1(&nt*k$4qiZ{B˯=y䵵S;ii%a¸MR)`䁃NuOudih(%K9Z9/h>Z>Xƴ&$d4ʄ{iHOg~ʮ<9 ϶O`'NFߤ\&P-.Sf\YlQ r~w^ޜ[ӊ$,碨V("v=Wk -cL/_tA^22 LF2C"KX^Q:ΩUP̗]iL +GQĦTp;17IDeU4P0Q'óxܢ1a\pTd t}ן2NbHюnT~f,tɛh,xaӺ5+k%!Z bPN&c#& 6r2 r$BsAg, `x'|ݨNC׊ ! | a ~ŋeI(Ztچ]ĄP"qĄ`#| sQ C5U5'I)U-'4" "#`aD,QPZL0D'N00" P8G : UpCasq)!˥\q+ј)qDw0^W ь^$) -UA(C8KQBy۰B@*&GcOVÎ34:aròR&eV'ډQ( 扏N3M<A)x"::TZ3Ԇa  cR0 M"AL].)|"0EHT%rGᑥc^,<Vr䳼ڸԙŘ#&-EEZv3}EX\fl8n}ptBcmd'̎H&p [oDZ& w0D1iP܊c5祧e kT#DN̢)ߛ,*udyqJ c7~vN'T$0|qNSmT]l)fU]"ٺ "1@;^  Ex\墤) IkKk-4hbBb2.l*N%3ʼn>2JSa!YxpE/df Xѭ^0$q3]D͕p*=:FB8E!P&juEe-"Z*dgNX#rQ.BqAA'b'Rd$݁Lg{43b]P^C`ر 2b~txׄ (;8Zt&uwZ$k` -'Obj"58wA$QʷSvrvX1hq6nj2*Q> Pu/OQk3WE,'2 ; % -VbW:A0\܂`+Fv*W ӂ^J}03sOZ{$'Wwo0Z.FT&'H6)]Qk̜>x i "X^f<)6!Cp2LJ]a_#;pSs8 U=l&fb1mo]0މ(+Д+T!ЫT 1WTv%%%k}Aϊ^0fLE+U$'}VE [sP58[0 VO6&r `hTI;3FG8j$H kLV12A,l' !HRֺ3j(E¢(8¨ 3wyS*T7 K N.Y\5Z°" ΢DQ<jY=Ѹ}ٚ4ӏ 7d -E_Y9nvSe՚2}jfY"MA j]2;Ŭ螾٧teK&@Z͵[`DRl;G'KٙB輛Yظ]mEF"}{zJwݟ$#(UV!E0էgn%J8HO֋BapfYϔQR_+SGS72MÝBK1;0fjY27yA2g/L*df`F0I&r`#sw$s6$әĆZY; -/֯;5rKf_$_8Jn|4k1Rvb3wckƩpDvu71(Pr/cqRGoo<ԍڙkP\t3v5}6U]bRL*Q' -H5s.-ZVzX^Q㵪vMf L&S^ܸuʋo_%&ݩ=jgJkʩrͽKA=tBEI%ƊM++ ͨ$̀ ӂo>s`g͵zO .غ*":UZj/[N6F16AtZ1ZQͶ`e7.İUu^θAc|)۹Jw @Hk ɵvgHf,FզR2=P͉.]ܻMoܬ[^RgܾldKtf/g/5g7fH~l>}~v`vc]N$+̒?V;2?[a8:6A w#(T+Rl93?>@H$i(.'mX^<׋rwnkT_R 0u:|t{p:wE*+6Am+#U -g4F{\y}jѽnzZU+ sg\_ytpvVzgt3{.>}U 0.WϾ(gfrnT.~b~UNxU_f\MkGޖ>8!:("*%9by9Y@ ^Ba>B% 7z}H.[JW"Q\};Y\1W+fuоО]l՝ xtpՀ"0Ιd~'e^rX]:8M4=X8Z<[XIbqdY-W:M?5 &C;e`vo+=}?5w3S:@NMR<̻lhm0쎌lRڼ᥮孺U˂'k`ܰ|L3@9at(C)YIyX*ºG@-zlwQq& .T˶^4 [`WN[>|kh5QΜX iaȫuhe ҊIqF"U/Vg931 SAvT礗!,xI74t;9?<<4m7|aq -`ZKKns phTN'z)֗O8met[g#q4߰wwdo Ϝ\_U$tcٗQ(1EqoitV촽#?g}^cGߔƾf4{}F*._MdWHf8PZNhjbh_ؐXb`R%<6} CٞO$4_$DsE41*xK"5a}0ROef1<H#cȉl~R ʍBi)WXCv4?$0<#mQ!@y 9!(0}I-EaUPJ]r %FGA$L4nNqRjk;,.ink(Ĺ\2lr]HggE'5,p]1W u`-3e'h,Hz.Q\OϬ-\ C4܊_Μ!jj}~2O$y^l4'/fۂA^{óO8_ Lˆ$>-APS4WƖNYsk8#R ݛzxNTid[*R4eӉzsxk CJr~Y\@v"5T(l09ɜ"+d6O] -e@Sr|quq)xb/%u+:Z ;WGN*~Y8;2΅:FgMOCYVRсJQ sWvB'DϛtS1^>5 fj DM1:SgDDwq /WX܇Ѥ]]F (6{L{GҮf 䁛YIkwv ť8jFL.HZ*mG`/ݙ:MI.Z[%?5a^g]VP3~ Bj$"d($C ;^f-lSKvc -c9e*W0*(MMF'[>% L'ӜBr"Ֆb*@3\n zc<@sB&Ƥc rdv>Wۢ='3o& Q @KPń*WE'5Z_&|/AHBYFFEpK1l{:_2C3{B dA=Qk6`lq)Ƕ[ E`1c4R 4Hs>3w zT&"/We pD|"3e8M7Uaby'A5 fJ`aEeۧo{$(qSvjob͟ea,G ?.A#ñYMmhR>Ӭ:ZMvI8esXM2J+14) 2q.ט<]};ص^o,#t+t\^s:Ne96Qndx}QLzt@jpyfd`LF0P\dd$62 `a,9He%qw:ZqVi`R! @V[Jf{P\I3 w@͂e Ʋ[4cB !+3H8UVʑNEI|`0*  0"ذPC1ęi?9S 0QS('Iܰ*|4TZnr3qQe鹽t&/b579 5O 0L8Ǚ' IެLðq 6`-ifLI: ?qGndsHIdAn+V?1;'7C 6.2R=8! X\ܸs=QXb[:2sc!~j]T۞: #v0Trؗ-"65ӔAE-Qiɥ̥zXfK||k $@nq4 -0bZ$*[lw;S_LvdX+#e}qOV:=wt|?p@qeĒ+(TXYcĉaӮRu0BWDV3((F+Sv6I1pD|t BRk_|;; xƺ1װ] Xd0o|'' ~K($(BE3 ÚǎEx"`I|+e!'PXFl`FD8Gol":v)yUuEc: ،!n2ue rO$ [ ԓ#(r`|ԬڍFX0E Whnf/DMP4%iɁ6+=uRu,5xժ eƊJhL -ɦՒ6NxJ\y̋E9Ud͊t͠ -F(R,#3a -a:J $J a1qhNg 2rr{6-O{yĚw o pUI.UQI|E"UU=13c3#b?>ܛ$JZ*Tի"/3y=,Pؐgb\ndr FX|7 _e$r'չ0+0SߌV1VQ2Gۇyd -*~_l_Xa,:=&k+ߜOoG/Ř}ʙ|d׋NA$jz71z&?GnX|F΋{򟬅=X|(ŭpIԲ U.K s`dr"a d@&"1,Z" gnDK3z< --)nqk"y{21(rKk ej?+/X -F8@ -/``|<:?.W/SVd?< -l(AyʒsF:OTLL$R"Uf}r$^WLY}`U_+:>=㉅Tfi,?#M˅tmqQ6mrfؽ_LO./w?a#,<_#9.3L$h@$*/`5''u+vv!IQ=*`H&*V*UM*"S׋tf^+$Ð v0c +,ɡp97i%GdhʏEe# tz)ZNo{$sxfYSk9F3q>Sy0Cef|@MǥKv4E& MƳT)@XU/ lDfͰpcxϹbG*](FKu$3,%%?K,0v<<1UDAer,>ӒHc 0pb2:_oU柧ҳcٵX̭)?Ԃ^$.oX*[],%eC{tm -;%gĽDdj"Z%׫nA}l,q)Kp&&ANؚ"?܊F"&hũXj -.OOgM*V-Vgmmd9[dF8VKtff#P֬C ςɦR&[kU*.'Ӌ>xl|2 0ѸRj2r쏨cIfO^7+x"9L1@fT^$s)<NvI J$8>+Ụ&@`8Lv)]h2G5͇tS)07Y}Y@E0O'&ˉ< >yb2+SمoF.;n?wl3F6581mf"Tʓt?h0s8ܚ)O8E)Z)7e-R6Ȭ?LX ..&油 Ey x,oՖߘ#`xF)<PNH'ҋl]cjr֞1_n Sf8C0seźf*5 4d.;X{~Uk0^7ҩY@GJte <2&(dSjVTt,m ˩lK"d0̲ExOŸp1_Lۊe0WkM}pK*{52$Tbt-+ ӟ Q6AΝ퓻ӆ%24a237=SPa>=yYVJ I?r̥~gJUP(dJOMN"f4 j=JҙVa@[ -lNSP@=&@$ kŗnJ6&@|z:ՅcmB -Ĝfӯ??0:OB)*r9; 0LJTVDz(?.ɏ|~ .jXm9+2p -I@a$ b15WZb -&v&irl nf4 Y!_e烡di0TEY8U1 nƙFjmY/SP>X+$)\-\@ce=/jmϨ>BP*_-C-^+BN;g?܎rPsT/-ԛc6JT z \/ ;e~:HJ ˥@q'"K,Ɠ n -NL$pLNgJᨁ1f_{LFqqՙX8gxzq~/wϧE_(˚`i58`TnfXPǿoGR8~MJNmJ ;6铧YpA}&gAsJ)LkBUfxA*[dza&XP֒Tfd?\H`htK03aIAA {bqSSToa& *?׏X-ŴDf.gJw la#a4_ix0_(t%ª6p'XxWx'ū\,^b?PgK zvxz* iȖ5RzYYzX]li֏V*Kܣ@4@CTab2S*x/ײ@21 -?9x{<~{,Dr6Xu7]'fTVP; 3~lrQ2Q/wau;%e+3ޞHMX=WX w`|WO\y=_j?˩SU}֌/fdjyYJƢ"0R~b -ͳV:"`O!Cw Z0TD"/V\9@˫~?=+`XKd~^يA̘)0-Kj&POFS酅Aen?_T"x[X9FJ,CF$V]w_;^; 2eGDͨ<JS6V*2{JL9_֭߯T=k<^I`X-S}v^^Xy{gpz,M,}'aN {ɴ_ዅx5Zʗ(Sz-nD`0 8_*?F=Nrtu,K1ְ*x=ZYyXY7! Kl~Q& -š>d-41+7õr1Oȕ\N|7*aqLaW lVMJעbnB bsImppڲam.+󫭍l\d[ ԰4vDN8ZKgWY> V~I_JcD4x`%_f꽂 qZY*2ٵLzez4>ˑtLPҝtfPA;K/$  4fk?W[fP`Z[PRH \E NPr|_Vv,Xϧ -eπk6+aB03~}r2?6lfL _YvO*T{%(_ U*݅' K/an'_e|*5䂬?_փd~-m:D -r#X Ex(tnI.)wujN4I,k݌KZ\lfwmƯZ2)*69U*:-eؽtmx -U' -pHͧdR5x X *k[O%Y:(o|3%M˯BJ0̃X_(nIc4_$@ :1-mrC͜|e"]fqu tl6^Z<3J[5ctu&PsB C?elɲL%_|x,pٱ"4.XM>϶ B0 -h -FKXt:ꨘ+sAI E /ۏkf6S4R,ݩgPA ܜ\i֨(`-`Lq+[ RJs'Z1_ibSӀٵh J8RM$ӍBq;PoݎLrL}z nͭoNgo%nߎ̃, -|>*H.S06bs`U@P/9Jfh8Mצ?'+261Y1M*# `^8#龐* ʼnԆ= dj>MϨcELjUVwXz>7q@~v_=X1pբf`4)<ȗf b  {M xL$oVOd^K'KAIaddX+|xX|0 A M@ea<<6HU;ߌ͂.^6{એO@ 3mG5ސF\DRf(OԌ &]KSjno_~09 @٧3~VR/2j͹\y eX yxl!4|uw3>ܞٽ2V<9(Č>>7#/aku'&2cpt)[{xd~c|01dWg^wԟ~Dc ?˽gWf`q`ҹE`B,UY<?fwf"2`\]]nf `V|ڧFB3t[R̼0 4YS%0B/hs^V*)%4 RiB$2X*/TPD"1T5߄7JcItC@Pkγ^KA}Gg񅩩fO`JYY{\i^RnɞaV_N&gJ2ÃUp0fVd0Dz^Jء(Z|"9t>j@~ ¯Ӿ|$g2|a sR{mT_ǐ82 `[!\OF$TW} iOLӷ3F1i6L(N$Ru;Mf\ikj&vT6ʁ+tPht8W[Nr h-^_^;R?at|Dn`ɭ[qpmY'ך0k[lbZn_K}!GȰ}!GȰ}!GȰ}!GȰ}!GȰ}!GȰ}Z[=<)_KR~:=hϝ;J_NExWlgkoZ't"7Nί:_ dV*?<9l.|ɛk?]9<8WuQ misqj/^{.3qӠhc9띴FOo;A^=3apH‹ -)WGf)7ܗyJT\o7?P; B\F=zWvGF/@U;}:??=} rx }a4A{zql{{샾~Oߝ?V?HAQ˸lk,}sA`4}Өׯ߷?->*QVGh@yztzV'KQTqزg-Qa3ywy ]} A`q.YI4v90j]IsȽsD&p)~Q#X νÓF_{]! ^F,|F%/.Pof6;vyRջ wPFOĸ:(FcY"up=nUEܿcUF (Pw~oSg?gEͤF} -|6[goZ8oa r0敃F[ܬTgAG%n;E#SW?==Z:k{:R-'/MA~9)zkuZ >={MFn}(O'NyHc7 ܸz~_v=5r}JFJ7FDU^n>;^GL~S;z_/ֽcu,hHBvisg΀w#Sms-pY}L,`Ihuj`sP!ljY[>|h:noev_{~~|7Yr'u:~| \? -uTaȑ3}vQ?7{P5í,Y>vxr{ރ^#4z'+)aUw><:hqTՃw>?|_N9g)~à<:^_a+zUZ hxjQLxjSZjq F< =)ޔo>T=ؓRlϰY|puc3r⣘e$%$MLxIڧI%jN>;t2(- ᯭ{G#ϳohÓÓOQO[Eos6:Qlsh 9y}Phr4=#"KL -?483Z; Zg`j?鹏F +[QMџo;}'ݗQ#uQV"~C~\wPFOd+;(N' -:(RGyjtrn˨ϲ^h76bB|*ü^6cSeG,gS/e?DlwhͿ>ez|/? |WjPR(l4bE^4Ni`w|Vܚ>DA[ >:|.(6#rfJ\X'v5tD k9݋*zQE/8QE,t/EZzG7&Ƚ꛳V -hU[yu^=kTOO>Uߌ­#\-3VvȣsÀWˇh2j!xrB9.f/̋lVMf@󧘎cw8^!N/EH"Q^$ʋDy)H;HKQ@ʋDET$jpgo%+`&u"j7~s753}45 ?E^a2}tQpq~oS)&My6yγd?ϝܷ55&bE57Ml6G}pw7ww^ߍ7EE~67];(냢O~yUx9Y?m?*c(}Ò N˰-s/&'{f_)ʔ2޶*~`8 "\>@X~  Oo"/xo(,7^o t~|h^Wa!UI6~3U=3j-NNwϛwD>G,bM"U.Y/fWmG0׬lؚ#NX@zpt~XiHtp4y: r7~M_}k }F뱯{pq`^CkbdzWgBߟы8 b>R+4.PFG8ޫF5 e?nBC@cN=ti_O=O=O=+#ޛxCW=uh4z~~s~HwPƹ}42NӃ7g}ez^˕!uQ1Zޔ>2^) MSQӃ=4]ME &ز<=:=:o\o~;# .k7VnnYtLJM]F%LܧydT{jc[Ɋ;snsqӠxwőӓ:DZÍs8(ZyO_#L m;EmeE/^7[}o]tQ3}>mE2zo: 20czx|OAQG;[>$+^C:!(Lxd$pP?jؤwș=;Fmy_ߝ>ܫ;=41lWMh7&FgFYMz# #*v7l#ullhB@öXQ -*R1G}Fn;n2Z8i|X2%Y8qġDĥa(" ?8?yrP:nxsьJ (}Ä,O֎em~> |ީzVN.Oף1NOPײ_j9͊nS(4\ -V^&FDR`< H u JAk(|: AQLUӈPfk8]0̐I n^(BwàfXdp` $Xd2Q~qĶErۉd=ad G#`PUY7]?wIM h ^lC&hp" `s~ur:``«6N -p7*Tq!p.~ ӀUY`J*r6iI{\k7x5f6d#(| 4e ´D)䆏ηp$ h-0qo CâFDaA@?2.|>PۊAthN9 F -PKjUM)S -;WX(2 c:MPxix ."#o@e+ŹFA FSjapr&ݤ_\(hpiY_/0r Wa, -DcD'}9 z)\5}sAqȔk)!6˅%,.*?p#2 4PdxM3>B,neRT$+Z](x!&ϙ5wnpRlj&m Dv|;tUG͖tBEM1bkKiS⚘v@KB7J(#U7ɕ5} -(N{f8|EFo]cg ƸXƅghl}@5mrq„m uIM2&:L4$ZXm_ | -_ڧ8*#-KZ4bd^L룢NNQP M@V'@& XBNLʱzU9>cn0C˄y@ns3 "D06u_7G< -> h_hSdTK&*Dhhw)6j( rWe[oXU-{pZQ`~+A8IϡIbgLH":B,pt:i\Q(Cc`McS'7.R+;N2jBgcz/a@(D gZ`dSDP Y!ƺ%'Vi;lon񀍛)c)PAGz•ctg!R\*vc HCZVm@f -ܤpfN0zͮ,hTig>a%*<d/p8YzL)EH2kG!lMM18/蚙`UswևO1qLAxC4N题kf{)Qd[1-(Hrw̦_["BB -FmSܣJ5z z68UD~(w@wAk v ;{0w%<%t0g<-Uh" -h8y)T .Lus@[.~it3vzwʏw5Vltu| ,c)~ ܷj'ţC ]h. 3\M伮b헩{kJbJ\  Jy G4JOJP QUޮz ^..p?RY"2EB wBbc_f81Sp!Bf M*o oLw-ȸ5tҊsyq- *(S8bΓ6,~ qkE(D%E/M8bkEѠ3^7xr^➠I&-@]Na Ab(A0%%O`a e=xrD^ʾ,aj6*!5;6ku!|*ε*b0'u*m -"㸨ua4@3~#`Ryi h4ڠĹzM,2/U{8I2 ym9^"B6yYS+cTO=ncu9Z^W(LF=$TΕBI<8њ\)]U%읫U&&%Lls~zm׭ -.3c)9gXDHKU>U& (_DABS`MʅXMJIAN`]%vXoc[&&>UʕSLßUgLBl-sPM7XaL:B0;Z j2`&5ȟwQp3boR-UTڄy{a>Yb׺ %ٹ]fQ~+j9iXUȌFW qG]2(`gYaWݡ8QdunT\KwqnRy:];7(L3tbBʓ[]!8 6q-\wjъ0mYwPC]Ւ>\]&/ Eх(ӲKF/uT V(^ ,/*XuV 4$S -D-ڭpKΫY)L|X7 gU(#ZZ;wwSuh h!xZ̥LB3"Sj:e&z6SC_Cl% -T -7J,k$shh7/QAÌh1bT/* m^D*_h1a&R{mR, J4[uVX5q -endstream endobj 6 0 obj [5 0 R] endobj 30 0 obj <> endobj xref -0 31 -0000000000 65535 f -0000000016 00000 n -0000000144 00000 n -0000046539 00000 n -0000000000 00000 f -0000051124 00000 n -0000648115 00000 n -0000046590 00000 n -0000046953 00000 n -0000054107 00000 n -0000051423 00000 n -0000051310 00000 n -0000050001 00000 n -0000050562 00000 n -0000050610 00000 n -0000051194 00000 n -0000051225 00000 n -0000051458 00000 n -0000054180 00000 n -0000054532 00000 n -0000055972 00000 n -0000062907 00000 n -0000123403 00000 n -0000188992 00000 n -0000254581 00000 n -0000320170 00000 n -0000385759 00000 n -0000451348 00000 n -0000516937 00000 n -0000582526 00000 n -0000648138 00000 n -trailer -<<0FA88353907EA141A300762457B17864>]>> -startxref -648333 -%%EOF diff --git a/assets/logo/status.jpg b/assets/logo/status.jpg deleted file mode 100644 index 94548c1..0000000 Binary files a/assets/logo/status.jpg and /dev/null differ diff --git a/assets/logo/status.png b/assets/logo/status.png deleted file mode 100644 index 6bd138d..0000000 Binary files a/assets/logo/status.png and /dev/null differ diff --git a/assets/logo/status.svg b/assets/logo/status.svg deleted file mode 100644 index 3c2dcd1..0000000 --- a/assets/logo/status.svg +++ /dev/null @@ -1,87 +0,0 @@ - -image/svg+xml diff --git a/assets/logo/status_90x15.png.png b/assets/logo/status_90x15.png.png deleted file mode 100644 index 8b25535..0000000 Binary files a/assets/logo/status_90x15.png.png and /dev/null differ diff --git a/assets/screenshot.png b/assets/screenshot.png deleted file mode 100644 index 7998086..0000000 Binary files a/assets/screenshot.png and /dev/null differ diff --git a/config.json b/config.json deleted file mode 100644 index baf21d7..0000000 --- a/config.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "ssl":"letsencrypt", - "domain":"example.com", - "reqdata" : { - "country": "US", - "state": "Texas", - "locality": "Texas", - "organization":"Example", - "organizationalunit": "IT", - "email": "info@example.com", - "commonname": "example.com" - }, - "project":"status", - "container":"nginx", - "apps_info": "phpMyAdmin-5,MySQL-5,PHP-7,Apache-2,Redis-5", - "subdomains": {"dev": "dev.example.com", "prod": "example.com"} -} diff --git a/docker-compose-build.yml b/docker-compose-build.yml deleted file mode 100644 index 44b05e0..0000000 --- a/docker-compose-build.yml +++ /dev/null @@ -1,17 +0,0 @@ -version: '2.2' - -services: - statuspanel: - image: trydirect/status - build: - context: . - container_name: status - ports: - - "5000:5000" - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - /data/encrypted:/data/encrypted - env_file: - - .env - environment: - - NGINX_CONTAINER=nginx diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml deleted file mode 100644 index df47a8d..0000000 --- a/docker-compose-dev.yml +++ /dev/null @@ -1,16 +0,0 @@ -version: '2.2' - -services: - statuspanel: - image: status - container_name: statuspanel - ports: - - "5000:5000" - volumes: - - .:/app - - /var/run/docker.sock:/var/run/docker.sock - - /data/encrypted:/data/encrypted - env_file: - - .env - environment: - - NGINX_CONTAINER=nginx diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index a31d569..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,18 +0,0 @@ -version: '2.2' - -services: - statuspanel: - image: trydirect/status:latest - container_name: statuspanel - ports: - - "5001:5000" - volumes: - - .:/app - - /var/run/docker.sock:/var/run/docker.sock - - /data/encrypted:/data/encrypted -# entrypoint: [""] -# command: ["bash", "-c", "sleep infinity"] - env_file: - - .env - environment: - - NGINX_CONTAINER=nginx diff --git a/examples/command_execution.rs b/examples/command_execution.rs deleted file mode 100644 index bc1af18..0000000 --- a/examples/command_execution.rs +++ /dev/null @@ -1,59 +0,0 @@ -/// Example: Command execution with timeout monitoring -/// -/// This demonstrates how to use CommandExecutor with TimeoutStrategy -/// to execute commands with multi-phase timeout handling. -/// -/// Run with: cargo run --example command_execution -use status_panel::commands::executor::CommandExecutor; -use status_panel::commands::timeout::TimeoutStrategy; -use status_panel::transport::Command; - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - // Initialize logging - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - .init(); - - // Create a command to execute - let command = Command { - id: "example-1".to_string(), - name: "echo Hello from CommandExecutor!".to_string(), - params: serde_json::json!({}), - }; - - // Create executor with progress callback - let executor = CommandExecutor::new().with_progress_callback(|phase, elapsed| { - tracing::info!("⏱️ Command in {:?} phase after {}s", phase, elapsed); - }); - - // Use quick strategy for demonstration (10 second timeout) - let strategy = TimeoutStrategy::quick_strategy(10); - - tracing::info!("🚀 Starting command execution: {}", command.name); - - // Execute the command - let result = executor.execute(&command, strategy).await?; - - // Display results - tracing::info!("✅ Command completed with status: {:?}", result.status); - tracing::info!("📊 Exit code: {:?}", result.exit_code); - tracing::info!("⏲️ Duration: {}s", result.duration_secs); - - if !result.stdout.is_empty() { - tracing::info!("📤 stdout:\n{}", result.stdout); - } - - if !result.stderr.is_empty() { - tracing::info!("📤 stderr:\n{}", result.stderr); - } - - // Convert to CommandResult for transport - let command_result = result.to_command_result(); - tracing::info!( - "📦 Transport payload: {}", - serde_json::to_string_pretty(&command_result)? - ); - - Ok(()) -} diff --git a/examples/long_poll_demo.sh b/examples/long_poll_demo.sh deleted file mode 100755 index faaa676..0000000 --- a/examples/long_poll_demo.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env bash -# Demo script: Enqueue a command and long-poll for it with another process -# Usage: ./examples/long_poll_demo.sh - -set -e - -BASE_URL="http://localhost:5000" -AGENT_ID="${AGENT_ID:-test-agent}" - -echo "=== Long-poll command queue demo ===" -echo "Ensure server is running: cargo r -- serve --port 5000" -echo "" - -# Start long-poll in background -echo "[1] Starting long-poll wait in background..." -( - echo " Waiting for command (timeout=10s)..." - RESPONSE=$(curl -s \ - -H "X-Agent-Id: $AGENT_ID" \ - "$BASE_URL/api/v1/commands/wait/demo-hash?timeout=10") - - if [ -n "$RESPONSE" ]; then - echo " Received command:" - echo "$RESPONSE" | jq . - else - echo " No commands (timeout)" - fi -) & - -POLLER_PID=$! -sleep 1 - -# Enqueue a command -echo "[2] Enqueuing a command..." -curl -s \ - -H 'Content-Type: application/json' \ - -X POST "$BASE_URL/api/v1/commands/enqueue" \ - -d '{ - "id": "cmd-demo-001", - "name": "echo Hello from long-poll demo", - "params": {} - }' | jq . - -echo "" -echo "[3] Waiting for poller to complete..." -wait $POLLER_PID - -echo "" -echo "=== Demo complete ===" -echo "Next: execute the command via /api/v1/commands/execute and report result via /api/v1/commands/report" diff --git a/index.md b/index.md new file mode 100644 index 0000000..4d2440d --- /dev/null +++ b/index.md @@ -0,0 +1,37 @@ +## Welcome to GitHub Pages + +You can use the [editor on GitHub](https://github.com/trydirect/status/edit/gh-pages/index.md) to maintain and preview the content for your website in Markdown files. + +Whenever you commit to this repository, GitHub Pages will run [Jekyll](https://jekyllrb.com/) to rebuild the pages in your site, from the content in your Markdown files. + +### Markdown + +Markdown is a lightweight and easy-to-use syntax for styling your writing. It includes conventions for + +```markdown +Syntax highlighted code block + +# Header 1 +## Header 2 +### Header 3 + +- Bulleted +- List + +1. Numbered +2. List + +**Bold** and _Italic_ and `Code` text + +[Link](url) and ![Image](src) +``` + +For more details see [Basic writing and formatting syntax](https://docs.github.com/en/github/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax). + +### Jekyll Themes + +Your Pages site will use the layout and styles from the Jekyll theme you have selected in your [repository settings](https://github.com/trydirect/status/settings/pages). The name of this theme is saved in the Jekyll `_config.yml` configuration file. + +### Support or Contact + +Having trouble with Pages? Check out our [documentation](https://docs.github.com/categories/github-pages-basics/) or [contact support](https://support.github.com/contact) and we’ll help you sort it out. diff --git a/src/agent/backup.rs b/src/agent/backup.rs deleted file mode 100644 index 2b58065..0000000 --- a/src/agent/backup.rs +++ /dev/null @@ -1,123 +0,0 @@ -use anyhow::Result; -use base64::{engine::general_purpose, Engine as _}; -use ring::hmac; -use serde::{Deserialize, Serialize}; -use std::time::{SystemTime, UNIX_EPOCH}; - -/// Time-based signed hash for backup verification -/// Similar to Flask's URLSafeTimedSerializer -#[derive(Debug)] -pub struct BackupSigner { - secret: Vec, -} - -#[derive(Debug, Serialize, Deserialize)] -struct SignedData { - value: String, - timestamp: u64, -} - -impl BackupSigner { - /// Create a new BackupSigner with a secret key - pub fn new(secret: impl Into>) -> Self { - Self { - secret: secret.into(), - } - } - - /// Sign a value with timestamp - pub fn sign(&self, value: &str) -> Result { - let timestamp = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(); - - let data = SignedData { - value: value.to_string(), - timestamp, - }; - - let json_str = serde_json::to_string(&data)?; - let json_bytes = json_str.as_bytes(); - - // Sign using HMAC-SHA256 - let key = hmac::Key::new(hmac::HMAC_SHA256, &self.secret); - let signature = hmac::sign(&key, json_bytes); - - let mut signed = json_bytes.to_vec(); - signed.extend_from_slice(signature.as_ref()); - - // Base64 encode the combined data - Ok(general_purpose::URL_SAFE_NO_PAD.encode(signed)) - } - - /// Verify a signed hash within max_age seconds - pub fn verify(&self, signed_hash: &str, max_age_secs: u64) -> Result { - // Base64 decode - let decoded = general_purpose::URL_SAFE_NO_PAD.decode(signed_hash)?; - - // HMAC-SHA256 produces 32-byte signature - let signature_len = 32; - if decoded.len() < signature_len { - anyhow::bail!("Invalid signed hash: too short"); - } - - let (data, signature_bytes) = decoded.split_at(decoded.len() - signature_len); - - // Verify signature - let key = hmac::Key::new(hmac::HMAC_SHA256, &self.secret); - ring::hmac::verify(&key, data, signature_bytes) - .map_err(|_| anyhow::anyhow!("Invalid signature"))?; - - // Parse JSON data - let signed_data: SignedData = serde_json::from_slice(data)?; - - // Check timestamp - let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(); - - if now - signed_data.timestamp > max_age_secs { - anyhow::bail!("Hash expired"); - } - - Ok(signed_data.value) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_sign_and_verify() { - let signer = BackupSigner::new("test_secret"); - let hash = signer.sign("deployment_data").unwrap(); - - // Should verify successfully - let result = signer.verify(&hash, 3600).unwrap(); - assert_eq!(result, "deployment_data"); - } - - #[test] - fn test_verify_fails_with_wrong_secret() { - let signer = BackupSigner::new("test_secret"); - let hash = signer.sign("deployment_data").unwrap(); - - let wrong_signer = BackupSigner::new("wrong_secret"); - assert!(wrong_signer.verify(&hash, 3600).is_err()); - } - - #[test] - fn test_verify_fails_with_expired_hash() { - let signer = BackupSigner::new("test_secret"); - let hash = signer.sign("deployment_data").unwrap(); - - // Sleep to ensure timestamp difference - std::thread::sleep(std::time::Duration::from_millis(10)); - - // Should fail with max_age of 0 (assuming > 10ms passed) - // In reality, this might still pass if execution is too fast, - // so we test that the verification logic works by checking - // that a very old timestamp would fail - let result = signer.verify(&hash, 0); - // Note: Due to timing precision, this might occasionally pass. - // The important test is the crypto verification above. - let _ = result; // Allow either result - } -} diff --git a/src/agent/config.rs b/src/agent/config.rs deleted file mode 100644 index 0dbde82..0000000 --- a/src/agent/config.rs +++ /dev/null @@ -1,120 +0,0 @@ -use anyhow::{Context, Result}; -use serde::{Deserialize, Serialize}; -use std::fs; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ReqData { - pub email: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct AppInfo { - pub name: String, - pub version: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Config { - pub domain: Option, - pub subdomains: Option, - pub apps_info: Option>, // normalized - pub reqdata: ReqData, - pub ssl: Option, -} - -impl Config { - pub fn from_file(path: &str) -> Result { - let raw = fs::read_to_string(path).context("reading config file")?; - let mut cfg: serde_json::Value = serde_json::from_str(&raw).context("parsing JSON")?; - let apps_info = cfg.get("apps_info").and_then(|v| v.as_str()).map(|s| { - s.split(',') - .filter_map(|item| { - let mut parts = item.split('-'); - let name = parts.next()?; - let version = parts.next().unwrap_or(""); - Some(AppInfo { - name: name.to_string(), - version: version.to_string(), - }) - }) - .collect::>() - }); - if let Some(v) = apps_info.clone() { - cfg["apps_info"] = serde_json::to_value(v).unwrap_or(serde_json::Value::Null); - } - let mut typed: Config = serde_json::from_value(cfg).context("mapping to Config")?; - typed.apps_info = apps_info; - Ok(typed) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::io::Write; - use tempfile::NamedTempFile; - - #[test] - fn test_config_parsing() { - let mut file = NamedTempFile::new().unwrap(); - writeln!( - file, - r#"{{ - "domain": "example.com", - "apps_info": "app1-1.0,app2-2.0", - "reqdata": {{"email": "test@example.com"}}, - "ssl": "letsencrypt" - }}"# - ) - .unwrap(); - - let config = Config::from_file(file.path().to_str().unwrap()).unwrap(); - assert_eq!(config.domain, Some("example.com".to_string())); - assert_eq!(config.reqdata.email, "test@example.com"); - assert_eq!(config.ssl, Some("letsencrypt".to_string())); - - let apps = config.apps_info.unwrap(); - assert_eq!(apps.len(), 2); - assert_eq!(apps[0].name, "app1"); - assert_eq!(apps[0].version, "1.0"); - assert_eq!(apps[1].name, "app2"); - assert_eq!(apps[1].version, "2.0"); - } - - #[test] - fn test_config_missing_file() { - let result = Config::from_file("/nonexistent/path/config.json"); - assert!(result.is_err()); - } - - #[test] - fn test_config_invalid_json() { - let mut file = NamedTempFile::new().unwrap(); - writeln!(file, "{{invalid json").unwrap(); - - let result = Config::from_file(file.path().to_str().unwrap()); - assert!(result.is_err()); - } - - #[test] - fn test_apps_info_parsing() { - let mut file = NamedTempFile::new().unwrap(); - writeln!( - file, - r#"{{ - "apps_info": "nginx-latest,postgres-14.5,redis-7.0", - "reqdata": {{"email": "test@test.com"}} - }}"# - ) - .unwrap(); - - let config = Config::from_file(file.path().to_str().unwrap()).unwrap(); - let apps = config.apps_info.unwrap(); - - assert_eq!(apps.len(), 3); - assert_eq!(apps[0].name, "nginx"); - assert_eq!(apps[0].version, "latest"); - assert_eq!(apps[2].name, "redis"); - assert_eq!(apps[2].version, "7.0"); - } -} diff --git a/src/agent/daemon.rs b/src/agent/daemon.rs deleted file mode 100644 index e75283d..0000000 --- a/src/agent/daemon.rs +++ /dev/null @@ -1,41 +0,0 @@ -use std::sync::Arc; - -use anyhow::Result; -use tokio::signal; -use tokio::sync::{broadcast, RwLock}; -use tokio::time::Duration; -use tracing::info; - -use crate::agent::config::Config; -use crate::monitoring::{spawn_heartbeat, MetricsCollector, MetricsSnapshot, MetricsStore}; - -pub async fn run(config_path: String) -> Result<()> { - let cfg = Config::from_file(&config_path)?; - info!(domain=?cfg.domain, "Agent daemon starting"); - - let collector = Arc::new(MetricsCollector::new()); - let store: MetricsStore = Arc::new(RwLock::new(MetricsSnapshot::default())); - let (tx, _) = broadcast::channel(32); - let webhook = std::env::var("METRICS_WEBHOOK").ok(); - let interval = std::env::var("METRICS_INTERVAL_SECS") - .ok() - .and_then(|s| s.parse::().ok()) - .map(Duration::from_secs) - .unwrap_or(Duration::from_secs(10)); - - let heartbeat_handle = spawn_heartbeat(collector, store, interval, tx, webhook.clone()); - info!( - interval_secs = interval.as_secs(), - webhook = webhook.as_deref().unwrap_or("none"), - "metrics heartbeat started" - ); - - // Wait for shutdown signal (Ctrl+C) then stop the heartbeat loop - signal::ctrl_c().await?; - info!("shutdown signal received, stopping daemon"); - - heartbeat_handle.abort(); - let _ = heartbeat_handle.await; // Ignore cancellation errors - - Ok(()) -} diff --git a/src/agent/docker.rs b/src/agent/docker.rs deleted file mode 100644 index bb51e4e..0000000 --- a/src/agent/docker.rs +++ /dev/null @@ -1,419 +0,0 @@ -#![cfg(feature = "docker")] -use anyhow::{Context, Result}; -use bollard::exec::CreateExecOptions; -use bollard::models::{ContainerStatsResponse, ContainerSummaryStateEnum}; -use bollard::query_parameters::{ - ListContainersOptions, ListContainersOptionsBuilder, RestartContainerOptions, - StopContainerOptions, -}; -use bollard::Docker; -use serde::Serialize; -use tracing::{debug, error}; - -#[derive(Serialize, Clone, Debug)] -pub struct ContainerInfo { - pub name: String, - pub status: String, - pub logs: String, - pub ports: Vec, -} - -#[derive(Serialize, Clone, Debug, Default)] -pub struct ContainerHealth { - pub name: String, - pub status: String, - pub cpu_pct: f32, - pub mem_usage_bytes: u64, - pub mem_limit_bytes: u64, - pub mem_pct: f32, - pub rx_bytes: u64, - pub tx_bytes: u64, - pub restart_count: Option, -} - -#[derive(Serialize, Clone, Debug)] -pub struct PortInfo { - pub port: String, - pub title: Option, -} - -fn docker_client() -> Result { - Docker::connect_with_defaults().context("docker client connect") -} - -pub async fn list_containers() -> Result> { - let docker = docker_client()?; - let opts: Option = - Some(ListContainersOptionsBuilder::default().all(true).build()); - let list = docker - .list_containers(opts) - .await - .context("list containers")?; - Ok(list - .into_iter() - .map(|c| { - let name = c - .names - .unwrap_or_default() - .first() - .cloned() - .unwrap_or_default() - .trim_start_matches('/') - .to_string(); - let status = c - .state - .as_ref() - .map(|s| format!("{:?}", s)) - .unwrap_or_else(|| "unknown".to_string()); - ContainerInfo { - name, - status, - logs: String::new(), - ports: vec![], - } - }) - .collect()) -} - -pub async fn list_containers_with_logs(tail: &str) -> Result> { - let docker = docker_client()?; - let opts: Option = - Some(ListContainersOptionsBuilder::default().all(true).build()); - let list = docker - .list_containers(opts) - .await - .context("list containers")?; - - let mut result = Vec::with_capacity(list.len()); - - for c in list.into_iter() { - let name = c - .names - .as_ref() - .and_then(|v| v.first().cloned()) - .unwrap_or_default() - .trim_start_matches('/') - .to_string(); - - let status = c - .state - .as_ref() - .map(|s| s.to_string()) - .unwrap_or_else(|| "unknown".to_string()); - - let logs = get_container_logs(&name, tail).await.unwrap_or_default(); - - result.push(ContainerInfo { - name, - status, - logs, - ports: vec![], - }); - } - - Ok(result) -} - -fn calc_cpu_percent(stats: &ContainerStatsResponse) -> f32 { - let (cpu_stats, precpu_stats) = match (&stats.cpu_stats, &stats.precpu_stats) { - (Some(cpu), Some(precpu)) => (cpu, precpu), - _ => return 0.0, - }; - - let total_delta = cpu_stats - .cpu_usage - .as_ref() - .and_then(|c| c.total_usage) - .unwrap_or(0) - .saturating_sub( - precpu_stats - .cpu_usage - .as_ref() - .and_then(|c| c.total_usage) - .unwrap_or(0), - ); - - let system_delta = cpu_stats - .system_cpu_usage - .unwrap_or(0) - .saturating_sub(precpu_stats.system_cpu_usage.unwrap_or(0)); - - if system_delta == 0 || total_delta == 0 { - return 0.0; - } - - let online_cpus = cpu_stats.online_cpus.map(|v| v as f64).unwrap_or_else(|| { - cpu_stats - .cpu_usage - .as_ref() - .and_then(|c| c.percpu_usage.as_ref()) - .map(|v: &Vec| v.len() as f64) - .unwrap_or(1.0) - }); - - ((total_delta as f64 / system_delta as f64) * online_cpus * 100.0) as f32 -} - -fn calc_memory(stats: &ContainerStatsResponse) -> (u64, u64, f32) { - let usage = stats - .memory_stats - .as_ref() - .and_then(|m| m.usage) - .unwrap_or(0); - let limit = stats - .memory_stats - .as_ref() - .and_then(|m| m.limit) - .unwrap_or(0); - - let pct = if limit > 0 { - (usage as f64 / limit as f64 * 100.0) as f32 - } else { - 0.0 - }; - - (usage, limit, pct) -} - -fn calc_network(stats: &ContainerStatsResponse) -> (u64, u64) { - if let Some(networks) = &stats.networks { - let mut rx = 0u64; - let mut tx = 0u64; - for (_iface, data) in networks.iter() { - rx = rx.saturating_add(data.rx_bytes.unwrap_or(0)); - tx = tx.saturating_add(data.tx_bytes.unwrap_or(0)); - } - (rx, tx) - } else { - (0, 0) - } -} - -async fn fetch_stats_for(docker: &Docker, name: &str) -> Result { - use futures_util::StreamExt; - - let mut stream = docker.stats( - name, - Some(bollard::query_parameters::StatsOptions { - stream: false, - one_shot: true, - }), - ); - let mut health = ContainerHealth { - name: name.to_string(), - status: "unknown".to_string(), - ..Default::default() - }; - - if let Some(next) = stream.next().await { - match next { - Ok(stats) => { - health.cpu_pct = calc_cpu_percent(&stats); - let (usage, limit, pct) = calc_memory(&stats); - health.mem_usage_bytes = usage; - health.mem_limit_bytes = limit; - health.mem_pct = pct; - let (rx, tx) = calc_network(&stats); - health.rx_bytes = rx; - health.tx_bytes = tx; - - if let Some(cont) = stats.name.clone() { - health.name = cont.trim_start_matches('/').to_string(); - } - } - Err(e) => { - error!("failed to read stats for {}: {}", name, e); - } - } - } - - Ok(health) -} - -pub async fn list_container_health() -> Result> { - let docker = docker_client()?; - let opts: Option = - Some(ListContainersOptionsBuilder::default().all(true).build()); - let list = docker - .list_containers(opts) - .await - .context("list containers")?; - - let mut health = Vec::with_capacity(list.len()); - - for c in list.into_iter() { - let name = c - .names - .as_ref() - .and_then(|v| v.first().cloned()) - .unwrap_or_default() - .trim_start_matches('/') - .to_string(); - - let status = c - .state - .as_ref() - .map(|s| s.to_string()) - .unwrap_or_else(|| "unknown".to_string()); - - let mut item = ContainerHealth { - name: name.clone(), - status, - ..Default::default() - }; - - // Only attempt stats if container is running or paused - if matches!( - c.state, - Some( - ContainerSummaryStateEnum::RUNNING - | ContainerSummaryStateEnum::RESTARTING - | ContainerSummaryStateEnum::PAUSED - ) - ) { - match fetch_stats_for(&docker, &name).await { - Ok(stats) => { - item.cpu_pct = stats.cpu_pct; - item.mem_usage_bytes = stats.mem_usage_bytes; - item.mem_limit_bytes = stats.mem_limit_bytes; - item.mem_pct = stats.mem_pct; - item.rx_bytes = stats.rx_bytes; - item.tx_bytes = stats.tx_bytes; - } - Err(e) => { - error!("failed to fetch stats for {}: {}", name, e); - } - } - } - - health.push(item); - } - - Ok(health) -} - -pub async fn get_container_logs(name: &str, tail: &str) -> Result { - let docker = docker_client()?; - use bollard::query_parameters::LogsOptionsBuilder; - use futures_util::StreamExt; - let opts = LogsOptionsBuilder::default() - .stdout(true) - .stderr(true) - .follow(false) - .tail(tail) - .build(); - let mut logs = docker.logs(name, Some(opts)); - let mut log_text = String::new(); - while let Some(log_line) = logs.next().await { - match log_line { - Ok(output) => log_text.push_str(&output.to_string()), - Err(e) => error!("error reading log: {}", e), - } - } - Ok(log_text) -} - -pub async fn restart(name: &str) -> Result<()> { - let docker = docker_client()?; - docker - .restart_container(name, None::) - .await - .context("restart container")?; - debug!("restarted container: {}", name); - Ok(()) -} - -pub async fn stop(name: &str) -> Result<()> { - let docker = docker_client()?; - docker - .stop_container(name, None::) - .await - .context("stop container")?; - debug!("stopped container: {}", name); - Ok(()) -} - -pub async fn pause(name: &str) -> Result<()> { - let docker = docker_client()?; - docker - .pause_container(name) - .await - .context("pause container")?; - debug!("paused container: {}", name); - Ok(()) -} - -/// Execute a shell command inside a running container. -/// Returns Ok(()) on success (exit code 0), Err otherwise. -pub async fn exec_in_container(name: &str, cmd: &str) -> Result<()> { - use bollard::exec::StartExecResults; - use futures_util::StreamExt; - - let docker = docker_client()?; - // Create exec instance - let exec = docker - .create_exec( - name, - CreateExecOptions { - attach_stdout: Some(true), - attach_stderr: Some(true), - tty: Some(false), - cmd: Some(vec![ - "/bin/sh".to_string(), - "-c".to_string(), - cmd.to_string(), - ]), - ..Default::default() - }, - ) - .await - .context("create exec")?; - - // Start exec and capture output - let start = docker - .start_exec(&exec.id, None) - .await - .context("start exec")?; - - let mut combined = String::new(); - match start { - StartExecResults::Detached => { - debug!(container = name, command = cmd, "exec detached"); - } - StartExecResults::Attached { mut output, .. } => { - while let Some(item) = output.next().await { - match item { - Ok(log) => { - let s = format!("{}", log); - combined.push_str(&s); - } - Err(e) => error!("exec output stream error: {}", e), - } - } - } - } - - // Inspect exec to get exit code - let info = docker - .inspect_exec(&exec.id) - .await - .context("inspect exec")?; - let exit_code = info.exit_code.unwrap_or_default(); - if exit_code == 0 { - debug!( - container = name, - command = cmd, - "exec completed successfully" - ); - Ok(()) - } else { - error!( - container = name, - command = cmd, - exit_code, - output = combined, - "exec failed" - ); - Err(anyhow::anyhow!("exec failed with code {}", exit_code)) - } -} diff --git a/src/agent/mod.rs b/src/agent/mod.rs deleted file mode 100644 index 6b085c1..0000000 --- a/src/agent/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod backup; -pub mod config; -pub mod daemon; -pub mod docker; diff --git a/src/commands/deploy.rs b/src/commands/deploy.rs deleted file mode 100644 index a31d004..0000000 --- a/src/commands/deploy.rs +++ /dev/null @@ -1,115 +0,0 @@ -use anyhow::{Context, Result}; -use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; -use std::path::Path; -use tokio::process::Command; - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct RollbackEntry { - pub job_id: String, - pub backup_path: String, - pub install_path: String, - pub timestamp: DateTime, -} - -#[derive(Debug, Serialize, Deserialize, Default)] -pub struct RollbackManifest { - pub entries: Vec, -} - -fn storage_path() -> String { - std::env::var("UPDATE_STORAGE_PATH").unwrap_or_else(|_| "/var/lib/status-panel".to_string()) -} - -fn manifest_path() -> String { - format!("{}/rollback.manifest", storage_path()) -} - -pub async fn load_manifest() -> Result { - let p = manifest_path(); - if !Path::new(&p).exists() { - return Ok(RollbackManifest::default()); - } - let data = tokio::fs::read(&p) - .await - .context("reading rollback manifest")?; - serde_json::from_slice(&data).context("parsing rollback manifest") -} - -pub async fn save_manifest(m: &RollbackManifest) -> Result<()> { - let p = manifest_path(); - if let Some(dir) = Path::new(&p).parent() { - tokio::fs::create_dir_all(dir).await.ok(); - } - let data = serde_json::to_vec_pretty(m).context("serializing rollback manifest")?; - tokio::fs::write(&p, data) - .await - .context("writing rollback manifest") -} - -pub async fn backup_current_binary(install_path: &str, job_id: &str) -> Result { - let ts = Utc::now().format("%Y%m%d%H%M%S"); - let backup_dir = format!("{}/backups", storage_path()); - tokio::fs::create_dir_all(&backup_dir).await.ok(); - let backup_path = format!("{}/status.{}.{}.bak", backup_dir, ts, job_id); - tokio::fs::copy(install_path, &backup_path) - .await - .context("copying current binary to backup")?; - Ok(backup_path) -} - -pub async fn deploy_temp_binary(job_id: &str, install_path: &str) -> Result { - let tmp_path = format!("/tmp/status-panel.{}.bin", job_id); - // move temp to install path and chmod +x - tokio::fs::copy(&tmp_path, install_path) - .await - .context("installing new binary")?; - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - let meta = tokio::fs::metadata(install_path).await?; - let mut perms = meta.permissions(); - perms.set_mode(0o755); - tokio::fs::set_permissions(install_path, perms).await?; - } - Ok(tmp_path) -} - -pub async fn restart_service(service_name: &str) -> Result<()> { - // Best-effort systemd restart; if not present, return error. - let status = Command::new("systemctl") - .arg("restart") - .arg(service_name) - .status() - .await - .context("running systemctl restart")?; - if !status.success() { - anyhow::bail!("systemctl restart failed with status {:?}", status.code()); - } - Ok(()) -} - -pub async fn record_rollback(job_id: &str, backup_path: &str, install_path: &str) -> Result<()> { - let mut m = load_manifest().await?; - m.entries.push(RollbackEntry { - job_id: job_id.to_string(), - backup_path: backup_path.to_string(), - install_path: install_path.to_string(), - timestamp: Utc::now(), - }); - save_manifest(&m).await -} - -pub async fn rollback_latest() -> Result> { - let mut m = load_manifest().await?; - let entry = match m.entries.pop() { - Some(e) => e, - None => return Ok(None), - }; - // restore backup to install path - tokio::fs::copy(&entry.backup_path, &entry.install_path) - .await - .context("restoring backup binary")?; - save_manifest(&m).await?; - Ok(Some(entry)) -} diff --git a/src/commands/docker_executor.rs b/src/commands/docker_executor.rs deleted file mode 100644 index 6ef2919..0000000 --- a/src/commands/docker_executor.rs +++ /dev/null @@ -1,146 +0,0 @@ -use crate::commands::DockerOperation; -use crate::transport::CommandResult; -use anyhow::Result; - -#[cfg(feature = "docker")] -use std::time::Instant; -#[cfg(feature = "docker")] -use tracing::{error, info}; - -#[cfg(feature = "docker")] -use crate::agent::docker; - -/// Execute Docker operations via command API -#[cfg(feature = "docker")] -pub async fn execute_docker_operation( - command_id: &str, - operation: DockerOperation, -) -> Result { - let start = Instant::now(); - let container_name = operation.container_name().to_string(); - let op_type = operation.operation_type().to_string(); - - info!( - "Executing Docker operation: {} on container: {}", - op_type, container_name - ); - - let (exit_code, stdout, stderr) = match operation { - DockerOperation::Restart(ref name) => match docker::restart(name).await { - Ok(_) => { - let msg = format!("Container '{}' restarted successfully", name); - info!("{}", msg); - (0, msg, String::new()) - } - Err(e) => { - let err_msg = e.to_string(); - error!("Failed to restart container '{}': {}", name, err_msg); - (1, String::new(), err_msg) - } - }, - - DockerOperation::Stop(ref name) => match docker::stop(name).await { - Ok(_) => { - let msg = format!("Container '{}' stopped successfully", name); - info!("{}", msg); - (0, msg, String::new()) - } - Err(e) => { - let err_msg = e.to_string(); - error!("Failed to stop container '{}': {}", name, err_msg); - (1, String::new(), err_msg) - } - }, - - DockerOperation::Logs(ref name, tail) => { - match docker::list_containers_with_logs( - tail.map(|t| t.to_string()).as_deref().unwrap_or("100"), - ) - .await - { - Ok(containers) => { - if let Some(container) = containers.iter().find(|c| c.name == *name) { - let logs = container.logs.clone(); - let msg = format!( - "Retrieved {} bytes of logs from container '{}'", - logs.len(), - name - ); - info!("{}", msg); - (0, logs, String::new()) - } else { - let err_msg = format!("Container '{}' not found", name); - error!("{}", err_msg); - (1, String::new(), err_msg) - } - } - Err(e) => { - let err_msg = e.to_string(); - error!("Failed to get logs for container '{}': {}", name, err_msg); - (1, String::new(), err_msg) - } - } - } - - DockerOperation::Inspect(ref name) => match docker::list_containers().await { - Ok(containers) => { - if let Some(container) = containers.iter().find(|c| c.name == *name) { - let inspect_json = serde_json::to_string_pretty(container) - .unwrap_or_else(|_| format!("Container: {}", container.name)); - info!("Inspected container '{}'", name); - (0, inspect_json, String::new()) - } else { - let err_msg = format!("Container '{}' not found", name); - error!("{}", err_msg); - (1, String::new(), err_msg) - } - } - Err(e) => { - let err_msg = e.to_string(); - error!("Failed to inspect container '{}': {}", name, err_msg); - (1, String::new(), err_msg) - } - }, - - DockerOperation::Pause(ref name) => match docker::pause(name).await { - Ok(_) => { - let msg = format!("Container '{}' paused successfully", name); - info!("{}", msg); - (0, msg, String::new()) - } - Err(e) => { - let err_msg = e.to_string(); - error!("Failed to pause container '{}': {}", name, err_msg); - (1, String::new(), err_msg) - } - }, - }; - - let duration_secs = start.elapsed().as_secs(); - let status = if exit_code == 0 { "success" } else { "failed" }; - - Ok(CommandResult { - command_id: command_id.to_string(), - status: status.to_string(), - result: Some(serde_json::json!({ - "exit_code": exit_code, - "duration_secs": duration_secs, - "operation": op_type, - "container": container_name, - "stdout": stdout, - })), - error: if exit_code != 0 { Some(stderr) } else { None }, - }) -} - -/// Fallback for non-Docker builds -#[cfg(not(feature = "docker"))] -pub async fn execute_docker_operation( - _command_id: &str, - _operation: DockerOperation, -) -> Result { - use anyhow::anyhow; - Err(anyhow!( - "Docker operations not available: build without docker feature" - )) -} diff --git a/src/commands/docker_ops.rs b/src/commands/docker_ops.rs deleted file mode 100644 index 90d5484..0000000 --- a/src/commands/docker_ops.rs +++ /dev/null @@ -1,163 +0,0 @@ -use anyhow::{bail, Result}; -use serde::{Deserialize, Serialize}; - -/// Allowed Docker operations that can be executed via command API -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum DockerOperation { - /// Restart a container: docker:restart:nginx - Restart(String), - /// Stop a container: docker:stop:redis - Stop(String), - /// View container logs: docker:logs:nginx:50 (tail 50 lines, default 100) - Logs(String, Option), - /// Inspect container: docker:inspect:nginx - Inspect(String), - /// Pause a container: docker:pause:nginx - Pause(String), -} - -impl DockerOperation { - /// Parse command string in format "docker:operation:args" - /// Examples: - /// - "docker:restart:nginx" - /// - "docker:stop:redis" - /// - "docker:logs:nginx:50" - /// - "docker:inspect:nginx" - pub fn parse(cmd: &str) -> Result { - let parts: Vec<&str> = cmd.split(':').collect(); - - match (parts.first(), parts.get(1), parts.get(2)) { - (Some(&"docker"), Some(&"restart"), Some(&name)) => { - validate_container_name(name)?; - Ok(DockerOperation::Restart(name.to_string())) - } - (Some(&"docker"), Some(&"stop"), Some(&name)) => { - validate_container_name(name)?; - Ok(DockerOperation::Stop(name.to_string())) - } - (Some(&"docker"), Some(&"logs"), Some(&name)) => { - validate_container_name(name)?; - let tail = parts.get(3).and_then(|s| s.parse::().ok()); - Ok(DockerOperation::Logs(name.to_string(), tail)) - } - (Some(&"docker"), Some(&"inspect"), Some(&name)) => { - validate_container_name(name)?; - Ok(DockerOperation::Inspect(name.to_string())) - } - (Some(&"docker"), Some(&"pause"), Some(&name)) => { - validate_container_name(name)?; - Ok(DockerOperation::Pause(name.to_string())) - } - _ => bail!("Invalid docker operation. Use format: docker:operation:container_name"), - } - } - - /// Get container name for this operation - pub fn container_name(&self) -> &str { - match self { - DockerOperation::Restart(name) => name, - DockerOperation::Stop(name) => name, - DockerOperation::Logs(name, _) => name, - DockerOperation::Inspect(name) => name, - DockerOperation::Pause(name) => name, - } - } - - /// Get operation type as string - pub fn operation_type(&self) -> &str { - match self { - DockerOperation::Restart(_) => "restart", - DockerOperation::Stop(_) => "stop", - DockerOperation::Logs(_, _) => "logs", - DockerOperation::Inspect(_) => "inspect", - DockerOperation::Pause(_) => "pause", - } - } -} - -/// Validate container name: alphanumeric, dash, underscore, max 63 chars -fn validate_container_name(name: &str) -> Result<()> { - if name.is_empty() { - bail!("Container name cannot be empty"); - } - - if name.len() > 63 { - bail!("Container name too long (max 63 chars)"); - } - - // Docker allows alphanumeric, dash, underscore - if !name - .chars() - .all(|c| c.is_alphanumeric() || c == '-' || c == '_' || c == '.') - { - bail!("Container name contains invalid characters (only alphanumeric, dash, underscore allowed)"); - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_parse_restart() { - let op = DockerOperation::parse("docker:restart:nginx").unwrap(); - match op { - DockerOperation::Restart(name) => assert_eq!(name, "nginx"), - _ => panic!("Expected Restart"), - } - } - - #[test] - fn test_parse_stop() { - let op = DockerOperation::parse("docker:stop:redis").unwrap(); - match op { - DockerOperation::Stop(name) => assert_eq!(name, "redis"), - _ => panic!("Expected Stop"), - } - } - - #[test] - fn test_parse_logs_with_tail() { - let op = DockerOperation::parse("docker:logs:nginx:50").unwrap(); - match op { - DockerOperation::Logs(name, tail) => { - assert_eq!(name, "nginx"); - assert_eq!(tail, Some(50)); - } - _ => panic!("Expected Logs"), - } - } - - #[test] - fn test_parse_logs_without_tail() { - let op = DockerOperation::parse("docker:logs:nginx").unwrap(); - match op { - DockerOperation::Logs(name, tail) => { - assert_eq!(name, "nginx"); - assert_eq!(tail, None); - } - _ => panic!("Expected Logs"), - } - } - - #[test] - fn test_parse_invalid_format() { - let result = DockerOperation::parse("docker:restart"); - assert!(result.is_err()); - } - - #[test] - fn test_parse_invalid_characters() { - let result = DockerOperation::parse("docker:restart:nginx; rm -rf /"); - assert!(result.is_err()); - } - - #[test] - fn test_container_name_too_long() { - let long_name = "a".repeat(64); - let result = DockerOperation::parse(&format!("docker:restart:{}", long_name)); - assert!(result.is_err()); - } -} diff --git a/src/commands/executor.rs b/src/commands/executor.rs deleted file mode 100644 index 06963d9..0000000 --- a/src/commands/executor.rs +++ /dev/null @@ -1,394 +0,0 @@ -use anyhow::{Context, Result}; -use std::process::Stdio; -use tokio::io::{AsyncBufReadExt, BufReader}; -use tokio::process::{Child, Command}; -use tokio::time::{sleep, timeout as tokio_timeout, Duration}; -use tracing::{debug, error, info, warn}; - -use crate::commands::timeout::{TimeoutPhase, TimeoutStrategy, TimeoutTracker}; -use crate::transport::{Command as AgentCommand, CommandResult}; - -/// Result of command execution -#[derive(Debug, Clone)] -pub struct ExecutionResult { - pub command_id: String, - pub status: ExecutionStatus, - pub exit_code: Option, - pub stdout: String, - pub stderr: String, - pub duration_secs: u64, - pub timeout_phase_reached: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum ExecutionStatus { - Success, - Failed, - Timeout, - Killed, -} - -impl ExecutionResult { - pub fn to_command_result(&self) -> CommandResult { - let status = match self.status { - ExecutionStatus::Success => "success", - ExecutionStatus::Failed => "failed", - ExecutionStatus::Timeout => "timeout", - ExecutionStatus::Killed => "killed", - } - .to_string(); - - let mut result_data = serde_json::json!({ - "exit_code": self.exit_code, - "duration_secs": self.duration_secs, - }); - - if !self.stdout.is_empty() { - result_data["stdout"] = serde_json::json!(self.stdout); - } - if !self.stderr.is_empty() { - result_data["stderr"] = serde_json::json!(self.stderr); - } - - CommandResult { - command_id: self.command_id.clone(), - status, - result: Some(result_data), - error: if self.status == ExecutionStatus::Success { - None - } else { - Some(self.stderr.clone()) - }, - } - } -} - -/// Progress callback for command execution -pub type ProgressCallback = Box; - -/// Executes commands with timeout management and signal handling -pub struct CommandExecutor { - /// Optional callback for progress updates - progress_callback: Option, -} - -impl CommandExecutor { - pub fn new() -> Self { - Self { - progress_callback: None, - } - } - - /// Set progress callback for dashboard updates - pub fn with_progress_callback(mut self, callback: F) -> Self - where - F: Fn(TimeoutPhase, u64) + Send + Sync + 'static, - { - self.progress_callback = Some(Box::new(callback)); - self - } - - /// Execute a command with timeout monitoring - pub async fn execute( - &self, - command: &AgentCommand, - strategy: TimeoutStrategy, - ) -> Result { - info!("Executing command: {} (id: {})", command.name, command.id); - - let mut tracker = TimeoutTracker::new(strategy.clone()); - let start = std::time::Instant::now(); - - // Parse command and arguments - let (cmd_name, args) = self.parse_command(&command.name)?; - - // Spawn the process - let mut child = Command::new(&cmd_name) - .args(&args) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .kill_on_drop(true) - .spawn() - .context("failed to spawn command")?; - - let child_id = child.id(); - debug!("Spawned process with PID: {:?}", child_id); - - // Capture output streams - let stdout = child.stdout.take().context("failed to capture stdout")?; - let stderr = child.stderr.take().context("failed to capture stderr")?; - - let stdout_reader = BufReader::new(stdout); - let stderr_reader = BufReader::new(stderr); - - let mut stdout_lines = stdout_reader.lines(); - let mut stderr_lines = stderr_reader.lines(); - - let mut stdout_output = String::new(); - let mut stderr_output = String::new(); - let mut last_phase = TimeoutPhase::Normal; - - // Monitor execution with timeout phases - let execution_result = loop { - let current_phase = tracker.current_phase(); - - // Report phase transitions - if current_phase != last_phase { - let elapsed = tracker.elapsed().as_secs(); - info!( - "Command {} entered phase {:?} after {}s", - command.id, current_phase, elapsed - ); - - if let Some(ref callback) = self.progress_callback { - callback(current_phase, elapsed); - } - - last_phase = current_phase; - } - - match current_phase { - TimeoutPhase::Normal | TimeoutPhase::Warning => { - // Continue monitoring - tokio::select! { - result = child.wait() => { - // Process completed - let status = result.context("failed to wait for child")?; - - // Drain remaining output - while let Ok(Some(line)) = stdout_lines.next_line().await { - stdout_output.push_str(&line); - stdout_output.push('\n'); - } - while let Ok(Some(line)) = stderr_lines.next_line().await { - stderr_output.push_str(&line); - stderr_output.push('\n'); - } - - let exec_status = if status.success() { - ExecutionStatus::Success - } else { - ExecutionStatus::Failed - }; - - break ExecutionResult { - command_id: command.id.clone(), - status: exec_status, - exit_code: status.code(), - stdout: stdout_output, - stderr: stderr_output, - duration_secs: start.elapsed().as_secs(), - timeout_phase_reached: Some(current_phase), - }; - } - - Ok(Some(line)) = stdout_lines.next_line() => { - stdout_output.push_str(&line); - stdout_output.push('\n'); - tracker.report_progress(); - } - - Ok(Some(line)) = stderr_lines.next_line() => { - stderr_output.push_str(&line); - stderr_output.push('\n'); - tracker.report_progress(); - } - - _ = sleep(strategy.progress_interval()) => { - // Check for stalls - if tracker.is_stalled() { - warn!("Command {} has stalled (no output for {}s)", - command.id, strategy.stall_threshold_secs); - } - } - } - } - - TimeoutPhase::HardTermination => { - warn!( - "Command {} reached hard timeout, attempting graceful termination", - command.id - ); - - if strategy.allow_graceful_termination { - // Send SIGTERM and wait 30 seconds - self.send_sigterm(&mut child, child_id)?; - - match tokio_timeout(Duration::from_secs(30), child.wait()).await { - Ok(Ok(status)) => { - info!("Command {} terminated gracefully", command.id); - break ExecutionResult { - command_id: command.id.clone(), - status: ExecutionStatus::Timeout, - exit_code: status.code(), - stdout: stdout_output, - stderr: stderr_output, - duration_secs: start.elapsed().as_secs(), - timeout_phase_reached: Some(TimeoutPhase::HardTermination), - }; - } - _ => { - // Fall through to force kill - continue; - } - } - } else { - // Skip to force kill - continue; - } - } - - TimeoutPhase::ForceKill => { - error!( - "Command {} reached kill timeout, force terminating", - command.id - ); - self.send_sigkill(&mut child, child_id).await?; - - // Wait a brief moment for kill to take effect - let _ = tokio_timeout(Duration::from_secs(2), child.wait()).await; - - break ExecutionResult { - command_id: command.id.clone(), - status: ExecutionStatus::Killed, - exit_code: None, - stdout: stdout_output, - stderr: stderr_output, - duration_secs: start.elapsed().as_secs(), - timeout_phase_reached: Some(TimeoutPhase::ForceKill), - }; - } - } - }; - - info!( - "Command {} completed with status: {:?}", - command.id, execution_result.status - ); - Ok(execution_result) - } - - /// Parse command string into program and arguments - fn parse_command(&self, cmd: &str) -> Result<(String, Vec)> { - let parts: Vec<&str> = cmd.split_whitespace().collect(); - if parts.is_empty() { - anyhow::bail!("empty command"); - } - - let program = parts[0].to_string(); - let args = parts[1..].iter().map(|s| s.to_string()).collect(); - - Ok((program, args)) - } - - /// Send SIGTERM to process - #[cfg(unix)] - fn send_sigterm(&self, child: &mut Child, pid: Option) -> Result<()> { - if let Some(pid) = pid { - use nix::sys::signal::{kill, Signal}; - use nix::unistd::Pid; - - debug!("Sending SIGTERM to PID {}", pid); - kill(Pid::from_raw(pid as i32), Signal::SIGTERM).context("failed to send SIGTERM")?; - } else { - child.start_kill().context("failed to send SIGTERM")?; - } - Ok(()) - } - - #[cfg(not(unix))] - fn send_sigterm(&self, child: &mut Child, _pid: Option) -> Result<()> { - child.start_kill().context("failed to terminate process")?; - Ok(()) - } - - /// Send SIGKILL to process - #[cfg(unix)] - async fn send_sigkill(&self, child: &mut Child, pid: Option) -> Result<()> { - if let Some(pid) = pid { - use nix::sys::signal::{kill, Signal}; - use nix::unistd::Pid; - - debug!("Sending SIGKILL to PID {}", pid); - kill(Pid::from_raw(pid as i32), Signal::SIGKILL).context("failed to send SIGKILL")?; - } else { - child.kill().await.context("failed to kill process")?; - } - Ok(()) - } - - #[cfg(not(unix))] - async fn send_sigkill(&self, child: &mut Child, _pid: Option) -> Result<()> { - child.kill().await.context("failed to kill process")?; - Ok(()) - } -} - -impl Default for CommandExecutor { - fn default() -> Self { - Self::new() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[tokio::test] - async fn test_execute_simple_command() { - let executor = CommandExecutor::new(); - let command = AgentCommand { - id: "test-1".to_string(), - name: "echo hello".to_string(), - params: serde_json::json!({}), - }; - - let strategy = TimeoutStrategy::quick_strategy(10); - let result = executor.execute(&command, strategy).await.unwrap(); - - assert_eq!(result.status, ExecutionStatus::Success); - assert!(result.stdout.contains("hello")); - } - - #[tokio::test] - async fn test_command_timeout() { - let executor = CommandExecutor::new(); - let command = AgentCommand { - id: "test-2".to_string(), - name: "sleep 100".to_string(), - params: serde_json::json!({}), - }; - - let strategy = TimeoutStrategy { - base_timeout_secs: 2, - soft_multiplier: 0.5, - hard_multiplier: 0.8, - kill_multiplier: 1.0, - allow_graceful_termination: false, - ..Default::default() - }; - - let result = executor.execute(&command, strategy).await.unwrap(); - - assert!(matches!( - result.status, - ExecutionStatus::Timeout | ExecutionStatus::Killed - )); - } - - #[tokio::test] - async fn test_failed_command() { - let executor = CommandExecutor::new(); - let command = AgentCommand { - id: "test-3".to_string(), - name: "false".to_string(), - params: serde_json::json!({}), - }; - - let strategy = TimeoutStrategy::quick_strategy(10); - let result = executor.execute(&command, strategy).await.unwrap(); - - assert_eq!(result.status, ExecutionStatus::Failed); - assert_eq!(result.exit_code, Some(1)); - } -} diff --git a/src/commands/mod.rs b/src/commands/mod.rs deleted file mode 100644 index 79387e5..0000000 --- a/src/commands/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -pub mod deploy; -pub mod docker_executor; -pub mod docker_ops; -pub mod executor; -pub mod self_update; -pub mod timeout; -pub mod validator; -pub mod version_check; - -pub use deploy::{ - backup_current_binary, deploy_temp_binary, record_rollback, restart_service, rollback_latest, - RollbackEntry, RollbackManifest, -}; -pub use docker_executor::execute_docker_operation; -pub use docker_ops::DockerOperation; -pub use self_update::{get_update_status, start_update_job, UpdateJobs, UpdatePhase, UpdateStatus}; -pub use timeout::{TimeoutPhase, TimeoutStrategy, TimeoutTracker}; -pub use validator::{CommandValidator, ValidatorConfig}; -pub use version_check::check_remote_version; diff --git a/src/commands/self_update.rs b/src/commands/self_update.rs deleted file mode 100644 index 0c44069..0000000 --- a/src/commands/self_update.rs +++ /dev/null @@ -1,176 +0,0 @@ -use anyhow::{Context, Result}; -use sha2::{Digest, Sha256}; -use std::collections::HashMap; -use std::sync::Arc; -use tokio::sync::RwLock; -use uuid::Uuid; - -#[derive(Debug, Clone)] -pub enum UpdatePhase { - Pending, - Downloading, - Verifying, - Completed, - Failed(String), -} - -#[derive(Debug, Clone)] -pub struct UpdateStatus { - pub phase: UpdatePhase, -} - -impl UpdateStatus { - pub fn new() -> Self { - Self { - phase: UpdatePhase::Pending, - } - } -} - -impl Default for UpdateStatus { - fn default() -> Self { - Self::new() - } -} - -pub type UpdateJobs = Arc>>; - -/// Start a background update job that downloads a binary to a temp path -/// and verifies sha256 if `UPDATE_EXPECTED_SHA256` is provided. -/// This initial version does NOT deploy the binary; it prepares it. -pub async fn start_update_job(jobs: UpdateJobs, target_version: Option) -> Result { - let id = Uuid::new_v4().to_string(); - { - let mut m = jobs.write().await; - m.insert(id.clone(), UpdateStatus::new()); - } - - let binary_url = std::env::var("UPDATE_BINARY_URL").ok(); - let server_url = std::env::var("UPDATE_SERVER_URL").ok(); - - let expected_sha = std::env::var("UPDATE_EXPECTED_SHA256").ok(); - - let id_clone = id.clone(); - let jobs_clone = jobs.clone(); - - tokio::spawn(async move { - // Resolve URL - let url = if let Some(u) = binary_url { - u - } else if let (Some(srv), Some(ver)) = (server_url, target_version.clone()) { - // Detect platform and construct binary name - let binary_name = detect_binary_name(); - format!( - "{}/releases/{}/{}", - srv.trim_end_matches('/'), - ver, - binary_name - ) - } else { - let mut w = jobs_clone.write().await; - if let Some(st) = w.get_mut(&id_clone) { - st.phase = UpdatePhase::Failed("No update URL resolved".to_string()); - } - return; - }; - - { - let mut w = jobs_clone.write().await; - if let Some(st) = w.get_mut(&id_clone) { - st.phase = UpdatePhase::Downloading; - } - } - - let tmp_path = format!("/tmp/status-panel.{}.bin", id_clone); - let dl = async { - let resp = reqwest::Client::new() - .get(&url) - .timeout(std::time::Duration::from_secs(300)) - .send() - .await - .context("download request failed")?; - if !resp.status().is_success() { - anyhow::bail!("download returned status {}", resp.status()); - } - let bytes = resp.bytes().await.context("reading download bytes")?; - tokio::fs::write(&tmp_path, &bytes) - .await - .context("writing temp binary")?; - Result::<()>::Ok(()) - }; - - if let Err(e) = dl.await { - let mut w = jobs_clone.write().await; - if let Some(st) = w.get_mut(&id_clone) { - st.phase = UpdatePhase::Failed(e.to_string()); - } - return; - } - - { - let mut w = jobs_clone.write().await; - if let Some(st) = w.get_mut(&id_clone) { - st.phase = UpdatePhase::Verifying; - } - } - - // Optional SHA256 verification - if let Some(expected) = expected_sha { - let verify_res = async { - let data = tokio::fs::read(&tmp_path) - .await - .context("reading temp binary for sha256")?; - let mut hasher = Sha256::new(); - hasher.update(&data); - let got = format!("{:x}", hasher.finalize()); - if got != expected.to_lowercase() { - anyhow::bail!("sha256 mismatch: got {} expected {}", got, expected); - } - Result::<()>::Ok(()) - } - .await; - - if let Err(e) = verify_res { - let mut w = jobs_clone.write().await; - if let Some(st) = w.get_mut(&id_clone) { - st.phase = UpdatePhase::Failed(e.to_string()); - } - return; - } - } - - // Completed preparation (download + verify). Deployment handled in a later phase. - let mut w = jobs_clone.write().await; - if let Some(st) = w.get_mut(&id_clone) { - st.phase = UpdatePhase::Completed; - } - }); - - Ok(id) -} - -pub async fn get_update_status(jobs: UpdateJobs, id: &str) -> Option { - let m = jobs.read().await; - m.get(id).cloned() -} - -fn detect_binary_name() -> String { - // Detect if we're running on musl by checking for /etc/alpine-release or ldd output - #[cfg(target_os = "linux")] - { - // Check if musl by trying to detect Alpine or running ldd on ourselves - if std::path::Path::new("/etc/alpine-release").exists() { - return "status-linux-x86_64-musl".to_string(); - } - // Default to glibc version for Linux - "status-linux-x86_64".to_string() - } - #[cfg(target_os = "macos")] - { - "status-darwin-x86_64".to_string() - } - #[cfg(not(any(target_os = "linux", target_os = "macos")))] - { - "status-linux-x86_64".to_string() - } -} diff --git a/src/commands/timeout.rs b/src/commands/timeout.rs deleted file mode 100644 index fc7dbe1..0000000 --- a/src/commands/timeout.rs +++ /dev/null @@ -1,315 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::time::{Duration, Instant}; - -/// Multi-phase timeout strategy for command execution -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct TimeoutStrategy { - /// Base timeout duration in seconds - pub base_timeout_secs: u64, - - /// Soft timeout multiplier (default 0.8) - warning phase - #[serde(default = "default_soft_multiplier")] - pub soft_multiplier: f64, - - /// Hard timeout multiplier (default 0.9) - SIGTERM phase - #[serde(default = "default_hard_multiplier")] - pub hard_multiplier: f64, - - /// Kill timeout multiplier (default 1.0) - SIGKILL phase - #[serde(default = "default_kill_multiplier")] - pub kill_multiplier: f64, - - /// Interval for progress reports in seconds - #[serde(default = "default_progress_interval")] - pub progress_interval_secs: u64, - - /// Time without progress before considering command stalled (seconds) - #[serde(default = "default_stall_threshold")] - pub stall_threshold_secs: u64, - - /// Allow graceful termination with SIGTERM before SIGKILL - #[serde(default = "default_true")] - pub allow_graceful_termination: bool, - - /// Enable checkpoint support for resumable operations - #[serde(default)] - pub enable_checkpoints: bool, -} - -fn default_soft_multiplier() -> f64 { - 0.8 -} -fn default_hard_multiplier() -> f64 { - 0.9 -} -fn default_kill_multiplier() -> f64 { - 1.0 -} -fn default_progress_interval() -> u64 { - 30 -} -fn default_stall_threshold() -> u64 { - 300 -} -fn default_true() -> bool { - true -} - -impl Default for TimeoutStrategy { - fn default() -> Self { - Self { - base_timeout_secs: 300, - soft_multiplier: 0.8, - hard_multiplier: 0.9, - kill_multiplier: 1.0, - progress_interval_secs: 30, - stall_threshold_secs: 300, - allow_graceful_termination: true, - enable_checkpoints: false, - } - } -} - -impl TimeoutStrategy { - /// Create strategy for backup operations (longer soft phase) - pub fn backup_strategy(base_timeout_secs: u64) -> Self { - Self { - base_timeout_secs, - soft_multiplier: 0.7, - hard_multiplier: 0.85, - kill_multiplier: 1.0, - progress_interval_secs: 60, - stall_threshold_secs: 600, - allow_graceful_termination: true, - enable_checkpoints: true, - } - } - - /// Create strategy for quick operations - pub fn quick_strategy(base_timeout_secs: u64) -> Self { - Self { - base_timeout_secs, - soft_multiplier: 0.8, - hard_multiplier: 0.95, - kill_multiplier: 1.0, - progress_interval_secs: 5, - stall_threshold_secs: 60, - allow_graceful_termination: false, - enable_checkpoints: false, - } - } - - /// Get soft timeout duration - pub fn soft_timeout(&self) -> Duration { - Duration::from_secs((self.base_timeout_secs as f64 * self.soft_multiplier) as u64) - } - - /// Get hard timeout duration - pub fn hard_timeout(&self) -> Duration { - Duration::from_secs((self.base_timeout_secs as f64 * self.hard_multiplier) as u64) - } - - /// Get kill timeout duration - pub fn kill_timeout(&self) -> Duration { - Duration::from_secs((self.base_timeout_secs as f64 * self.kill_multiplier) as u64) - } - - /// Get progress interval - pub fn progress_interval(&self) -> Duration { - Duration::from_secs(self.progress_interval_secs) - } - - /// Get stall threshold - pub fn stall_threshold(&self) -> Duration { - Duration::from_secs(self.stall_threshold_secs) - } -} - -/// Current phase of command execution timeout -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub enum TimeoutPhase { - /// Normal execution (0-80% of timeout) - Normal, - /// Warning phase - command taking longer than expected (80-90%) - Warning, - /// Hard termination phase - attempting graceful shutdown (90-100%) - HardTermination, - /// Force kill phase - command must be terminated immediately (>100%) - ForceKill, -} - -/// Tracks timeout state for a running command -#[derive(Debug)] -pub struct TimeoutTracker { - strategy: TimeoutStrategy, - start_time: Instant, - last_progress: Instant, - current_phase: TimeoutPhase, -} - -impl TimeoutTracker { - /// Create a new timeout tracker - pub fn new(strategy: TimeoutStrategy) -> Self { - let now = Instant::now(); - Self { - strategy, - start_time: now, - last_progress: now, - current_phase: TimeoutPhase::Normal, - } - } - - /// Report progress (resets stall detection) - pub fn report_progress(&mut self) { - self.last_progress = Instant::now(); - } - - /// Get current phase based on elapsed time - pub fn current_phase(&mut self) -> TimeoutPhase { - let elapsed = self.start_time.elapsed(); - - let phase = if elapsed >= self.strategy.kill_timeout() { - TimeoutPhase::ForceKill - } else if elapsed >= self.strategy.hard_timeout() { - TimeoutPhase::HardTermination - } else if elapsed >= self.strategy.soft_timeout() { - TimeoutPhase::Warning - } else { - TimeoutPhase::Normal - }; - - // Update internal state if phase changed - if phase != self.current_phase { - self.current_phase = phase; - } - - phase - } - - /// Check if command has stalled (no progress within threshold) - pub fn is_stalled(&self) -> bool { - self.last_progress.elapsed() >= self.strategy.stall_threshold() - } - - /// Get elapsed time - pub fn elapsed(&self) -> Duration { - self.start_time.elapsed() - } - - /// Get time remaining until next phase - pub fn time_to_next_phase(&self) -> Option { - let elapsed = self.start_time.elapsed(); - - match self.current_phase { - TimeoutPhase::Normal => { - let soft = self.strategy.soft_timeout(); - if elapsed < soft { - Some(soft - elapsed) - } else { - None - } - } - TimeoutPhase::Warning => { - let hard = self.strategy.hard_timeout(); - if elapsed < hard { - Some(hard - elapsed) - } else { - None - } - } - TimeoutPhase::HardTermination => { - let kill = self.strategy.kill_timeout(); - if elapsed < kill { - Some(kill - elapsed) - } else { - None - } - } - TimeoutPhase::ForceKill => None, - } - } - - /// Get the timeout strategy - pub fn strategy(&self) -> &TimeoutStrategy { - &self.strategy - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_default_strategy() { - let strategy = TimeoutStrategy::default(); - assert_eq!(strategy.base_timeout_secs, 300); - assert_eq!(strategy.soft_multiplier, 0.8); - assert_eq!(strategy.soft_timeout(), Duration::from_secs(240)); - assert_eq!(strategy.hard_timeout(), Duration::from_secs(270)); - assert_eq!(strategy.kill_timeout(), Duration::from_secs(300)); - } - - #[test] - fn test_backup_strategy() { - let strategy = TimeoutStrategy::backup_strategy(3600); - assert_eq!(strategy.base_timeout_secs, 3600); - assert_eq!(strategy.soft_multiplier, 0.7); - assert!(strategy.enable_checkpoints); - assert_eq!(strategy.soft_timeout(), Duration::from_secs(2520)); // 70% of 3600 - } - - #[test] - fn test_quick_strategy() { - let strategy = TimeoutStrategy::quick_strategy(60); - assert_eq!(strategy.base_timeout_secs, 60); - assert!(!strategy.allow_graceful_termination); - assert!(!strategy.enable_checkpoints); - } - - #[test] - fn test_timeout_tracker_phases() { - let strategy = TimeoutStrategy { - base_timeout_secs: 10, - soft_multiplier: 0.5, - hard_multiplier: 0.8, - kill_multiplier: 1.0, - ..Default::default() - }; - - let mut tracker = TimeoutTracker::new(strategy); - assert_eq!(tracker.current_phase(), TimeoutPhase::Normal); - - // Note: In real tests, we'd need to mock time or use sleeps - // This just tests the logic structure - } - - #[test] - fn test_progress_reporting() { - let strategy = TimeoutStrategy::default(); - let mut tracker = TimeoutTracker::new(strategy); - - std::thread::sleep(Duration::from_millis(10)); - tracker.report_progress(); - - // Progress should be recent - assert!(!tracker.is_stalled()); - } - - #[test] - fn test_time_to_next_phase() { - let strategy = TimeoutStrategy { - base_timeout_secs: 100, - soft_multiplier: 0.8, - hard_multiplier: 0.9, - kill_multiplier: 1.0, - ..Default::default() - }; - - let tracker = TimeoutTracker::new(strategy); - let time_to_warning = tracker.time_to_next_phase(); - assert!(time_to_warning.is_some()); - // Should be approximately 80 seconds (soft timeout) - let secs = time_to_warning.unwrap().as_secs(); - assert!(secs >= 79 && secs <= 80); - } -} diff --git a/src/commands/validator.rs b/src/commands/validator.rs deleted file mode 100644 index 3ffdef9..0000000 --- a/src/commands/validator.rs +++ /dev/null @@ -1,212 +0,0 @@ -use anyhow::{bail, Result}; -use std::collections::HashSet; - -use crate::transport::Command as AgentCommand; - -/// Configuration for command validation rules -#[derive(Debug, Clone)] -pub struct ValidatorConfig { - pub allowed_programs: HashSet, - pub allow_shell: bool, - pub max_args: usize, - pub max_arg_len: usize, - pub allowed_path_prefixes: Vec, -} - -impl Default for ValidatorConfig { - fn default() -> Self { - let mut allowed_programs = HashSet::new(); - // Minimal safe defaults; expand as needed - for p in [ - "echo", "sleep", "ls", "tar", "gzip", "uname", "date", "df", "du", - ] - .iter() - { - allowed_programs.insert(p.to_string()); - } - - Self { - allowed_programs, - allow_shell: false, - max_args: 16, - max_arg_len: 4096, - allowed_path_prefixes: vec!["/tmp".to_string(), "/var/tmp".to_string()], - } - } -} - -/// Validates commands for safety prior to execution -#[derive(Debug, Clone)] -pub struct CommandValidator { - config: ValidatorConfig, -} - -impl CommandValidator { - pub fn new(config: ValidatorConfig) -> Self { - Self { config } - } - - pub fn default_secure() -> Self { - Self { - config: ValidatorConfig::default(), - } - } - - /// Validate a command; returns Ok if safe else Err explaining the issue - pub fn validate(&self, command: &AgentCommand) -> Result<()> { - // Check for Docker operation first (special case: docker:operation:name) - if command.name.starts_with("docker:") { - return self.validate_docker_command(&command.name); - } - - let (program, args) = self.parse_command(&command.name)?; - - // Basic program checks - if program.is_empty() { - bail!("empty command"); - } - - // Disallow environment assignment hijacks like FOO=bar cmd - if program.contains('=') { - bail!("environment assignment in program not allowed"); - } - - // Shell usage restricted unless explicitly allowed - if ["sh", "bash", "zsh"].contains(&program.as_str()) && !self.config.allow_shell { - bail!("shell execution is disabled by policy"); - } - - // Enforce whitelist for non-shell programs - if !["sh", "bash", "zsh"].contains(&program.as_str()) - && !self.config.allowed_programs.contains(&program) - { - bail!(format!("program '{}' is not allowed", program)); - } - - // Argument constraints - if args.len() > self.config.max_args { - bail!(format!( - "too many arguments: {} > {}", - args.len(), - self.config.max_args - )); - } - - // Disallowed metacharacters commonly used for command injection - const DISALLOWED_CHARS: &[char] = &[';', '|', '&', '`', '$', '>', '<']; - - for arg in &args { - if arg.len() > self.config.max_arg_len { - bail!("argument too long"); - } - - if arg.chars().any(|c| DISALLOWED_CHARS.contains(&c)) { - bail!(format!("unsafe characters in argument: {}", arg)); - } - - // Simple path validation - if arg.contains('/') { - // Prevent traversal - if arg.contains("../") || arg.starts_with("../") || arg.contains("/..") { - bail!("path traversal detected in argument"); - } - - // Disallow absolute paths outside allowed prefixes - if arg.starts_with('/') { - let allowed = self - .config - .allowed_path_prefixes - .iter() - .any(|prefix| arg.starts_with(prefix)); - if !allowed { - bail!(format!("absolute path not permitted: {}", arg)); - } - } - } - - // Conservative character policy: allow common filename chars - if !self.is_safe_string(arg) { - bail!(format!("argument contains unsafe characters: {}", arg)); - } - } - - Ok(()) - } - - fn parse_command(&self, cmd: &str) -> Result<(String, Vec)> { - let parts: Vec<&str> = cmd.split_whitespace().collect(); - if parts.is_empty() { - bail!("empty command"); - } - let program = parts[0].to_string(); - let args = parts[1..].iter().map(|s| s.to_string()).collect(); - Ok((program, args)) - } - - fn is_safe_string(&self, s: &str) -> bool { - // Allow letters, numbers, space, underscore, dash, dot, slash, colon, equals - s.chars() - .all(|c| c.is_alphanumeric() || matches!(c, ' ' | '_' | '-' | '.' | '/' | ':' | '=')) - } - - /// Validate Docker command in format: docker:operation:container_name - fn validate_docker_command(&self, cmd: &str) -> Result<()> { - use crate::commands::DockerOperation; - - // Parse and validate the Docker operation - let _op = DockerOperation::parse(cmd)?; - - // If parsing succeeds, the command is valid - Ok(()) - } -} - -impl Default for CommandValidator { - fn default() -> Self { - Self::default_secure() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn cmd(id: &str, name: &str) -> AgentCommand { - AgentCommand { - id: id.to_string(), - name: name.to_string(), - params: serde_json::json!({}), - } - } - - #[test] - fn allows_simple_echo() { - let v = CommandValidator::default_secure(); - assert!(v.validate(&cmd("1", "echo hello")).is_ok()); - } - - #[test] - fn blocks_shell_when_disabled() { - let v = CommandValidator::default_secure(); - assert!(v.validate(&cmd("2", "bash -c echo hi")).is_err()); - } - - #[test] - fn blocks_metachars() { - let v = CommandValidator::default_secure(); - assert!(v.validate(&cmd("3", "echo hello && ls")).is_err()); - assert!(v.validate(&cmd("4", "echo `whoami`")).is_err()); - } - - #[test] - fn blocks_absolute_path_outside_whitelist() { - let v = CommandValidator::default_secure(); - assert!(v.validate(&cmd("5", "ls /etc")).is_err()); - } - - #[test] - fn allows_sleep_numeric() { - let v = CommandValidator::default_secure(); - assert!(v.validate(&cmd("6", "sleep 1")).is_ok()); - } -} diff --git a/src/commands/version_check.rs b/src/commands/version_check.rs deleted file mode 100644 index 13345ca..0000000 --- a/src/commands/version_check.rs +++ /dev/null @@ -1,36 +0,0 @@ -use anyhow::{Context, Result}; -use serde::Deserialize; - -#[derive(Debug, Deserialize)] -pub struct RemoteVersion { - pub version: String, - #[serde(default)] - pub checksum: Option, -} - -/// Checks a remote update server for the latest version. -/// Falls back gracefully if `UPDATE_SERVER_URL` is not provided or unreachable. -pub async fn check_remote_version() -> Result> { - let base = match std::env::var("UPDATE_SERVER_URL") { - Ok(v) if !v.is_empty() => v, - _ => return Ok(None), - }; - // Conventional endpoint: `${UPDATE_SERVER_URL}/api/version` - let url = format!("{}/api/version", base.trim_end_matches('/')); - let resp = reqwest::Client::new() - .get(&url) - .timeout(std::time::Duration::from_secs(10)) - .send() - .await - .context("requesting remote version")?; - - if !resp.status().is_success() { - return Ok(None); - } - - let rv: RemoteVersion = resp - .json() - .await - .context("parsing remote version response")?; - Ok(Some(rv)) -} diff --git a/src/comms/local_api.rs b/src/comms/local_api.rs deleted file mode 100644 index 9707373..0000000 --- a/src/comms/local_api.rs +++ /dev/null @@ -1,1442 +0,0 @@ -use anyhow::Result; -use axum::extract::ws::{Message, WebSocket}; -use axum::extract::FromRequestParts; -use axum::http::request::Parts; -use axum::{ - extract::Form, - extract::Path, - extract::Query, - extract::State, - extract::WebSocketUpgrade, - http::{HeaderMap, StatusCode}, - response::Html, - response::IntoResponse, - response::Redirect, - routing::{get, post}, - Json, Router, -}; -use bytes::Bytes; -use serde::{Deserialize, Serialize}; -use serde_json::json; -use std::collections::VecDeque; -use std::future::IntoFuture; -use std::net::SocketAddr; -use std::sync::Arc; -use std::time::Duration; -use tera::Tera; -use tokio::sync::{broadcast, Mutex, Notify}; -use tracing::{debug, error, info}; - -use crate::agent::backup::BackupSigner; -use crate::agent::config::Config; -#[cfg(feature = "docker")] -use crate::agent::docker; -#[cfg(feature = "docker")] -use crate::commands::execute_docker_operation; -use crate::commands::executor::CommandExecutor; -use crate::commands::{ - backup_current_binary, deploy_temp_binary, record_rollback, restart_service, rollback_latest, -}; -use crate::commands::{ - check_remote_version, get_update_status, start_update_job, UpdateJobs, UpdatePhase, -}; -use crate::commands::{CommandValidator, DockerOperation, TimeoutStrategy}; -use crate::monitoring::{ - spawn_heartbeat, MetricsCollector, MetricsSnapshot, MetricsStore, MetricsTx, -}; -use crate::security::audit_log::AuditLogger; -use crate::security::auth::{Credentials, SessionStore, SessionUser}; -use crate::security::rate_limit::RateLimiter; -use crate::security::replay::ReplayProtection; -use crate::security::request_signer::verify_signature; -use crate::security::scopes::Scopes; -use crate::security::token_cache::TokenCache; -use crate::security::token_refresh::spawn_token_refresh; -use crate::security::vault_client::VaultClient; -use crate::transport::{Command as AgentCommand, CommandResult}; -use crate::VERSION; - -type SharedState = Arc; - -// Extract client IP from ConnectInfo, headers, or fallback to 127.0.0.1 -#[derive(Debug, Clone)] -struct ClientIp(pub String); - -impl FromRequestParts for ClientIp -where - S: Send + Sync, -{ - type Rejection = std::convert::Infallible; - - async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { - // Prefer SocketAddr inserted by Axum's connect info middleware - if let Some(addr) = parts.extensions.get::() { - return Ok(ClientIp(addr.ip().to_string())); - } - - // Check common proxy headers - if let Some(forwarded) = parts.headers.get("x-forwarded-for") { - if let Ok(s) = forwarded.to_str() { - // Take the first IP if multiple - let ip = s.split(',').next().unwrap_or(s).trim().to_string(); - if !ip.is_empty() { - return Ok(ClientIp(ip)); - } - } - } - if let Some(real_ip) = parts.headers.get("x-real-ip") { - if let Ok(s) = real_ip.to_str() { - let ip = s.trim().to_string(); - if !ip.is_empty() { - return Ok(ClientIp(ip)); - } - } - } - - // Fallback for tests or when info is unavailable - Ok(ClientIp("127.0.0.1".to_string())) - } -} -#[derive(Debug, Clone)] -pub struct AppState { - pub session_store: SessionStore, - pub config: Arc, - pub templates: Option>, - pub with_ui: bool, - pub metrics_collector: Arc, - pub metrics_store: MetricsStore, - pub metrics_tx: MetricsTx, - pub metrics_webhook: Option, - pub backup_path: Option, - pub commands_queue: Arc>>, - pub commands_notify: Arc, - pub audit: AuditLogger, - pub rate_limiter: RateLimiter, - pub replay: ReplayProtection, - pub scopes: Scopes, - pub agent_token: Arc>, - pub vault_client: Option, - pub token_cache: Option, - pub update_jobs: UpdateJobs, -} - -impl AppState { - pub fn new(config: Arc, with_ui: bool) -> Self { - let templates = if with_ui { - match Tera::new("templates/**/*.html") { - Ok(t) => { - debug!("Loaded {} templates", t.get_template_names().count()); - Some(Arc::new(t)) - } - Err(e) => { - error!("Template parsing error: {}", e); - None - } - } - } else { - None - }; - - let vault_client = VaultClient::from_env() - .ok() - .flatten() - .inspect(|_| debug!("Vault client initialized for token rotation")); - - let token_cache = vault_client - .is_some() - .then(|| TokenCache::new(std::env::var("AGENT_TOKEN").unwrap_or_default())); - - Self { - session_store: SessionStore::new(), - config, - templates, - with_ui, - metrics_collector: Arc::new(MetricsCollector::new()), - metrics_store: Arc::new(tokio::sync::RwLock::new(MetricsSnapshot::default())), - metrics_tx: broadcast::channel(32).0, - metrics_webhook: std::env::var("METRICS_WEBHOOK").ok(), - backup_path: std::env::var("BACKUP_PATH").ok(), - commands_queue: Arc::new(Mutex::new(VecDeque::new())), - commands_notify: Arc::new(Notify::new()), - audit: AuditLogger::new(), - rate_limiter: RateLimiter::new_per_minute( - std::env::var("RATE_LIMIT_PER_MIN") - .ok() - .and_then(|v| v.parse::().ok()) - .unwrap_or(120), - ), - replay: ReplayProtection::new_ttl( - std::env::var("REPLAY_TTL_SECS") - .ok() - .and_then(|v| v.parse::().ok()) - .unwrap_or(600), - ), - scopes: Scopes::from_env(), - agent_token: Arc::new(tokio::sync::RwLock::new( - std::env::var("AGENT_TOKEN").unwrap_or_default(), - )), - vault_client, - token_cache, - update_jobs: Arc::new(tokio::sync::RwLock::new(std::collections::HashMap::new())), - } - } -} - -#[derive(Deserialize)] -pub struct LoginRequest { - pub username: String, - pub password: String, -} - -#[derive(Serialize)] -pub struct LoginResponse { - pub session_id: String, -} - -#[derive(Serialize)] -pub struct ErrorResponse { - pub error: String, -} - -#[derive(Deserialize)] -pub struct BackupPingRequest { - pub hash: String, -} - -#[derive(Serialize)] -pub struct BackupPingResponse { - pub status: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub hash: Option, -} - -// Health check with token rotation metrics -#[derive(Serialize)] -pub struct HealthResponse { - pub status: String, - pub token_age_seconds: u64, - pub last_refresh_ok: Option, -} - -async fn health(State(state): State) -> impl IntoResponse { - let token_age_seconds = if let Some(cache) = &state.token_cache { - cache.age_seconds().await - } else { - 0 - }; - - let last_refresh_ok = if state.vault_client.is_some() { - // If Vault is configured, we track refresh success via audit logs - Some(true) - } else { - None - }; - - Json(HealthResponse { - status: "ok".to_string(), - token_age_seconds, - last_refresh_ok, - }) -} - -// Login form (GET) -async fn login_page(State(state): State) -> impl IntoResponse { - if state.with_ui { - if let Some(templates) = &state.templates { - let mut context = tera::Context::new(); - context.insert("error", &false); - - match templates.render("login.html", &context) { - Ok(html) => Html(html).into_response(), - Err(e) => { - error!("Template render error: {}", e); - Html("Error rendering template".to_string()) - .into_response() - } - } - } else { - Html("Templates not loaded".to_string()).into_response() - } - } else { - Html("
".to_string()).into_response() - } -} - -// Login handler (POST) -async fn login_handler( - State(state): State, - Form(req): Form, -) -> Result { - let creds = Credentials::from_env(); - if req.username == creds.username && req.password == creds.password { - let user = SessionUser::new(req.username.clone()); - let _session_id = state.session_store.create_session(user).await; - debug!("user logged in: {}", req.username); - // Redirect to home page on successful login - Ok(Redirect::to("/").into_response()) - } else { - error!("login failed for user: {}", req.username); - // Re-render login page with error if UI is enabled, otherwise return error JSON - if state.with_ui { - if let Some(templates) = &state.templates { - let mut context = tera::Context::new(); - context.insert("error", &true); - match templates.render("login.html", &context) { - Ok(html) => Err((StatusCode::UNAUTHORIZED, Html(html).into_response())), - Err(e) => { - error!("Template render error: {}", e); - Err(( - StatusCode::INTERNAL_SERVER_ERROR, - Html("Login failed".to_string()) - .into_response(), - )) - } - } - } else { - Err(( - StatusCode::UNAUTHORIZED, - Html("Login failed".to_string()).into_response(), - )) - } - } else { - Err(( - StatusCode::UNAUTHORIZED, - Json(ErrorResponse { - error: "Invalid credentials".to_string(), - }) - .into_response(), - )) - } - } -} - -// Logout handler -async fn logout_handler(State(state): State) -> impl IntoResponse { - // @todo Extract session ID from cookies and delete - debug!("user logged out"); - if state.with_ui { - Redirect::to("/login").into_response() - } else { - Json(json!({"status": "logged out"})).into_response() - } -} - -// Get home (list containers, config) -#[cfg(feature = "docker")] -async fn home(State(state): State) -> impl IntoResponse { - use crate::agent::docker; - let list_result = if state.with_ui { - docker::list_containers_with_logs("200").await - } else { - docker::list_containers().await - }; - - match list_result { - Ok(containers) => { - if state.with_ui { - if let Some(templates) = &state.templates { - let mut context = tera::Context::new(); - // Match template expectations - context.insert("container_list", &containers); - context.insert( - "apps_info", - &state.config.apps_info.clone().unwrap_or_default(), - ); - context.insert("errors", &Option::::None); - context.insert("ip", &Option::::None); - context.insert("domainIp", &Option::::None); - context.insert("panel_version", &env!("CARGO_PKG_VERSION")); - context.insert("domain", &state.config.domain); - context.insert("ssl_enabled", &state.config.ssl.is_some()); - context.insert("can_enable", &false); // TODO: implement DNS check - context.insert("ip_help_link", "https://www.whatismyip.com/"); - - match templates.render("index.html", &context) { - Ok(html) => Html(html).into_response(), - Err(e) => { - error!("Template render error: {}", e); - Json(json!({"error": format!("Template error: {}", e)})).into_response() - } - } - } else { - Json(json!({"error": "Templates not loaded"})).into_response() - } - } else { - Json(json!({ - "containers": containers, - "config": { - "domain": state.config.domain, - "apps_info": state.config.apps_info, - } - })) - .into_response() - } - } - Err(e) => { - error!("failed to fetch containers: {}", e); - Json(json!({"error": e.to_string()})).into_response() - } - } -} - -// ---- SSL enable/disable (Let’s Encrypt or self-signed) ---- -#[cfg(feature = "docker")] -fn build_certbot_cmds(config: &Config) -> (String, String) { - // Domains from subdomains can be object, array, or comma-separated string - let mut domains: Vec = Vec::new(); - if let Some(ref sd) = config.subdomains { - match sd { - serde_json::Value::Object(map) => { - for v in map.values() { - if let Some(s) = v.as_str() { - domains.push(s.to_string()); - } - } - } - serde_json::Value::Array(arr) => { - for v in arr { - if let Some(s) = v.as_str() { - domains.push(s.to_string()); - } - } - } - serde_json::Value::String(s) => { - for part in s.split(',') { - let p = part.trim(); - if !p.is_empty() { - domains.push(p.to_string()); - } - } - } - _ => {} - } - } - - let domains_flags = domains - .into_iter() - .map(|d| format!("-d {}", d)) - .collect::>() - .join(" "); - - let email = config.reqdata.email.clone(); - let reg_cmd = format!("certbot register --email {} --agree-tos -n", email); - let crt_cmd = if domains_flags.is_empty() { - "certbot --nginx --redirect".to_string() - } else { - format!("certbot --nginx --redirect {}", domains_flags) - }; - - (reg_cmd, crt_cmd) -} - -#[cfg(feature = "docker")] -async fn enable_ssl_handler(State(state): State) -> impl IntoResponse { - let nginx = std::env::var("NGINX_CONTAINER").unwrap_or_else(|_| "nginx".to_string()); - // Prepare challenge directory - if let Err(e) = docker::exec_in_container( - &nginx, - "mkdir -p /tmp/letsencrypt/.well-known/acme-challenge", - ) - .await - { - error!("failed to prepare acme-challenge dir: {}", e); - return Redirect::to("/").into_response(); - } - - if state.config.ssl.as_deref() == Some("letsencrypt") { - let (reg_cmd, crt_cmd) = build_certbot_cmds(&state.config); - info!("starting certbot registration and certificate issue"); - if let Err(e) = docker::exec_in_container(&nginx, ®_cmd).await { - error!("certbot register failed: {}", e); - return Redirect::to("/").into_response(); - } - if let Err(e) = docker::exec_in_container(&nginx, &crt_cmd).await { - error!("certbot issue failed: {}", e); - return Redirect::to("/").into_response(); - } - let _ = docker::restart(&nginx).await; - } else { - // Self-signed path: replace conf files - let mut names: Vec = Vec::new(); - if let Some(ref sd) = state.config.subdomains { - match sd { - serde_json::Value::Object(map) => { - for k in map.keys() { - names.push(k.clone()); - } - } - serde_json::Value::Array(arr) => { - for v in arr { - if let Some(s) = v.as_str() { - names.push(s.to_string()); - } - } - } - serde_json::Value::String(s) => { - for part in s.split(',') { - let p = part.trim(); - if !p.is_empty() { - names.push(p.to_string()); - } - } - } - _ => {} - } - } - for fname in names { - let src = format!("./origin_conf/ssl-conf.d/{}.conf", fname); - let dst = format!("./destination_conf/conf.d/{}.conf", fname); - if let Err(e) = std::fs::copy(&src, &dst) { - error!("failed to copy {} -> {}: {}", src, dst, e); - return Redirect::to("/").into_response(); - } - } - let _ = docker::restart(&nginx).await; - debug!("self-signed SSL conf files replaced"); - } - - Redirect::to("/").into_response() -} - -#[cfg(feature = "docker")] -async fn disable_ssl_handler(State(state): State) -> impl IntoResponse { - let nginx = std::env::var("NGINX_CONTAINER").unwrap_or_else(|_| "nginx".to_string()); - let mut names: Vec = Vec::new(); - if let Some(ref sd) = state.config.subdomains { - match sd { - serde_json::Value::Object(map) => { - for k in map.keys() { - names.push(k.clone()); - } - } - serde_json::Value::Array(arr) => { - for v in arr { - if let Some(s) = v.as_str() { - names.push(s.to_string()); - } - } - } - serde_json::Value::String(s) => { - for part in s.split(',') { - let p = part.trim(); - if !p.is_empty() { - names.push(p.to_string()); - } - } - } - _ => {} - } - } - for fname in names { - let src = format!("./origin_conf/conf.d/{}.conf", fname); - let dst = format!("./destination_conf/conf.d/{}.conf", fname); - if let Err(e) = std::fs::copy(&src, &dst) { - error!("failed to copy {} -> {}: {}", src, dst, e); - return Redirect::to("/").into_response(); - } - } - let _ = docker::restart(&nginx).await; - Redirect::to("/").into_response() -} - -// Restart container -#[cfg(feature = "docker")] -async fn restart_container( - State(state): State, - Path(name): Path, -) -> impl IntoResponse { - use crate::agent::docker; - match docker::restart(&name).await { - Ok(_) => { - info!("restarted container: {}", name); - if state.with_ui { - Redirect::to("/").into_response() - } else { - Json(json!({"action": "restart", "container": name, "status": "ok"})) - .into_response() - } - } - Err(e) => { - error!("failed to restart container: {}", e); - Json(json!({"error": e.to_string()})).into_response() - } - } -} - -// Stop container -#[cfg(feature = "docker")] -async fn stop_container( - State(state): State, - Path(name): Path, -) -> impl IntoResponse { - use crate::agent::docker; - match docker::stop(&name).await { - Ok(_) => { - info!("stopped container: {}", name); - if state.with_ui { - Redirect::to("/").into_response() - } else { - Json(json!({"action": "stop", "container": name, "status": "ok"})).into_response() - } - } - Err(e) => { - error!("failed to stop container: {}", e); - Json(json!({"error": e.to_string()})).into_response() - } - } -} - -// Pause container -#[cfg(feature = "docker")] -async fn pause_container( - State(state): State, - Path(name): Path, -) -> impl IntoResponse { - use crate::agent::docker; - match docker::pause(&name).await { - Ok(_) => { - info!("paused container: {}", name); - if state.with_ui { - Redirect::to("/").into_response() - } else { - Json(json!({"action": "pause", "container": name, "status": "ok"})).into_response() - } - } - Err(e) => { - error!("failed to pause container: {}", e); - Json(json!({"error": e.to_string()})).into_response() - } - } -} - -// Backup ping endpoint - verify hash and generate new one -async fn backup_ping( - ClientIp(request_ip): ClientIp, - Json(req): Json, -) -> Result { - let allowed_ip = std::env::var("TRYDIRECT_IP").ok(); - - // Check if request is from allowed IP - if let Some(allowed) = allowed_ip { - if request_ip != allowed { - error!("Backup ping from unauthorized IP: {}", request_ip); - return Err(( - StatusCode::FORBIDDEN, - Json(ErrorResponse { - error: "Invalid IP".to_string(), - }), - )); - } - } - - // Get deployment hash from environment - let deployment_hash = - std::env::var("DEPLOYMENT_HASH").unwrap_or_else(|_| "default_deployment_hash".to_string()); - - let signer = BackupSigner::new(deployment_hash.as_bytes()); - - // Check if hash matches deployment_hash or verify it's a valid signed hash - let is_valid = if req.hash == deployment_hash { - true - } else { - // Try to verify as a signed hash (for backward compatibility) - signer.verify(&req.hash, 1800).is_ok() - }; - - if is_valid { - // Generate new signed hash - let new_hash = signer - .sign(&deployment_hash) - .unwrap_or_else(|_| deployment_hash.clone()); - - debug!("Backup ping verified from {}", request_ip); - Ok(Json(BackupPingResponse { - status: "OK".to_string(), - hash: Some(new_hash), - })) - } else { - error!("Invalid backup ping hash from {}", request_ip); - Err(( - StatusCode::UNAUTHORIZED, - Json(ErrorResponse { - error: "ERROR".to_string(), - }), - )) - } -} - -// Backup download endpoint - send backup file with hash/IP verification -async fn backup_download( - State(state): State, - ClientIp(request_ip): ClientIp, - Path((hash, target_ip)): Path<(String, String)>, -) -> Result { - // Check if request is from target IP - if request_ip != target_ip { - error!( - "Backup download from wrong IP. Expected: {}, Got: {}", - target_ip, request_ip - ); - return Err(( - StatusCode::FORBIDDEN, - Json(ErrorResponse { - error: "Invalid IP".to_string(), - }), - )); - } - - // Get deployment hash and verify - let deployment_hash = - std::env::var("DEPLOYMENT_HASH").unwrap_or_else(|_| "default_deployment_hash".to_string()); - - let signer = BackupSigner::new(deployment_hash.as_bytes()); - - // Verify hash (30 minute window) - match signer.verify(&hash, 1800) { - Ok(_) => { - // Resolve backup path from state (set at startup) to avoid env races in tests - let backup_path = state - .backup_path - .clone() - .unwrap_or_else(|| "/data/encrypted/backup.tar.gz.cpt".to_string()); - - if !std::path::Path::new(&backup_path).is_file() { - error!("Backup file not found: {}", backup_path); - return Err(( - StatusCode::NOT_FOUND, - Json(ErrorResponse { - error: "Backup not found".to_string(), - }), - )); - } - - // Read and send backup file - match tokio::fs::read(&backup_path).await { - Ok(content) => { - // Extract filename for logging and headers - let filename = std::path::Path::new(&backup_path) - .file_name() - .and_then(|n| n.to_str()) - .unwrap_or("backup.tar.gz.cpt"); - - debug!("Backup downloaded by {}: {}", request_ip, filename); - - // Use HeaderMap to avoid lifetime issues - use axum::http::HeaderMap; - let mut headers = HeaderMap::new(); - headers.insert( - axum::http::header::CONTENT_TYPE, - "application/octet-stream".parse().unwrap(), - ); - headers.insert( - axum::http::header::CONTENT_DISPOSITION, - format!("attachment; filename=\"{}\"", filename) - .parse() - .unwrap(), - ); - - Ok((StatusCode::OK, headers, content)) - } - Err(e) => { - error!("Failed to read backup file: {}", e); - Err(( - StatusCode::INTERNAL_SERVER_ERROR, - Json(ErrorResponse { - error: "Failed to read backup".to_string(), - }), - )) - } - } - } - Err(_) => { - error!("Invalid backup download hash from {}", request_ip); - Err(( - StatusCode::UNAUTHORIZED, - Json(ErrorResponse { - error: "Invalid or expired hash".to_string(), - }), - )) - } - } -} - -#[cfg(feature = "docker")] -async fn stack_health() -> impl IntoResponse { - match docker::list_container_health().await { - Ok(health) => Json(health).into_response(), - Err(e) => { - error!("stack health error: {}", e); - ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(json!({"error": e.to_string()})), - ) - .into_response() - } - } -} - -// Return the latest metrics snapshot (refreshing before responding) -async fn metrics_handler(State(state): State) -> impl IntoResponse { - let snapshot = state.metrics_collector.snapshot().await; - { - let mut guard = state.metrics_store.write().await; - *guard = snapshot.clone(); - } - - Json(snapshot) -} - -async fn metrics_ws_handler( - State(state): State, - ws: WebSocketUpgrade, -) -> impl IntoResponse { - ws.on_upgrade(move |socket| metrics_ws_stream(state, socket)) -} - -async fn metrics_ws_stream(state: SharedState, mut socket: WebSocket) { - let mut rx = state.metrics_tx.subscribe(); - - // Send latest snapshot immediately - let current = state.metrics_store.read().await.clone(); - if let Ok(text) = serde_json::to_string(¤t) { - let _ = socket.send(Message::Text(text.into())).await; - } - - while let Ok(snapshot) = rx.recv().await { - if let Ok(text) = serde_json::to_string(&snapshot) { - if socket.send(Message::Text(text.into())).await.is_err() { - break; - } - } - } -} - -pub fn create_router(state: SharedState) -> Router { - let mut router = Router::new() - .route("/health", get(health)) - .route("/metrics", get(metrics_handler)) - .route("/metrics/stream", get(metrics_ws_handler)) - // Self-update endpoints - .route("/api/self/version", get(self_version)) - .route("/api/self/update/start", post(self_update_start)) - .route("/api/self/update/status/{id}", get(self_update_status)) - .route("/api/self/update/deploy", post(self_update_deploy)) - .route("/api/self/update/rollback", post(self_update_rollback)) - .route("/login", get(login_page).post(login_handler)) - .route("/logout", get(logout_handler)) - .route("/backup/ping", post(backup_ping)) - .route("/backup/{hash}/{target_ip}", get(backup_download)); - // v2.0 endpoints: long-poll commands wait/report and execute - router = router - .route("/api/v1/commands/wait/{hash}", get(commands_wait)) - .route("/api/v1/commands/report", post(commands_report)) - .route("/api/v1/commands/execute", post(commands_execute)) - .route("/api/v1/commands/enqueue", post(commands_enqueue)) - .route("/api/v1/auth/rotate-token", post(rotate_token)); - - #[cfg(feature = "docker")] - { - router = router - .route("/", get(home)) - .route("/restart/{name}", get(restart_container)) - .route("/stop/{name}", get(stop_container)) - .route("/pause/{name}", get(pause_container)) - .route("/stack/health", get(stack_health)); - // SSL management routes - router = router - .route("/enable_ssl", get(enable_ssl_handler)) - .route("/disable_ssl", get(disable_ssl_handler)); - } - - // Add static file serving when UI is enabled - if state.with_ui { - use tower_http::services::ServeDir; - router = router.nest_service("/static", ServeDir::new("static")); - } - - router.with_state(state) -} - -#[derive(Serialize)] -struct SelfVersionResponse { - current: String, - available: Option, - has_update: bool, -} - -async fn self_version(State(_state): State) -> impl IntoResponse { - let current = VERSION.to_string(); - let mut available: Option = None; - if let Ok(Some(rv)) = check_remote_version().await { - available = Some(rv.version); - } - let has_update = available.as_ref().map(|a| a != ¤t).unwrap_or(false); - Json(SelfVersionResponse { - current, - available, - has_update, - }) -} - -#[derive(Deserialize)] -struct StartUpdateRequest { - version: Option, -} - -async fn self_update_start( - State(state): State, - headers: HeaderMap, - body: Bytes, -) -> impl IntoResponse { - // Require agent id header as with v2.0 endpoints - if let Err(resp) = validate_agent_id(&headers) { - return resp.into_response(); - } - let req: StartUpdateRequest = match serde_json::from_slice(&body) { - Ok(v) => v, - Err(e) => { - return ( - StatusCode::BAD_REQUEST, - Json(json!({"error": e.to_string()})), - ) - .into_response() - } - }; - match start_update_job(state.update_jobs.clone(), req.version).await { - Ok(id) => (StatusCode::ACCEPTED, Json(json!({"job_id": id}))).into_response(), - Err(e) => ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(json!({"error": e.to_string()})), - ) - .into_response(), - } -} - -async fn self_update_status( - State(state): State, - Path(id): Path, -) -> impl IntoResponse { - match get_update_status(state.update_jobs.clone(), &id).await { - Some(st) => { - let phase = match st.phase { - UpdatePhase::Pending => "pending", - UpdatePhase::Downloading => "downloading", - UpdatePhase::Verifying => "verifying", - UpdatePhase::Completed => "completed", - UpdatePhase::Failed(_) => "failed", - }; - Json(json!({"job_id": id, "phase": phase})).into_response() - } - None => ( - StatusCode::NOT_FOUND, - Json(json!({"error": "job not found"})), - ) - .into_response(), - } -} - -#[derive(Deserialize)] -struct DeployRequest { - job_id: String, - install_path: Option, - service_name: Option, -} - -async fn self_update_deploy( - State(_state): State, - headers: HeaderMap, - body: Bytes, -) -> impl IntoResponse { - if let Err(resp) = validate_agent_id(&headers) { - return resp.into_response(); - } - let req: DeployRequest = match serde_json::from_slice(&body) { - Ok(v) => v, - Err(e) => { - return ( - StatusCode::BAD_REQUEST, - Json(json!({"error": e.to_string()})), - ) - .into_response() - } - }; - let install_path = req - .install_path - .unwrap_or_else(|| "/usr/local/bin/status".to_string()); - // Backup current - match backup_current_binary(&install_path, &req.job_id).await { - Ok(backup_path) => { - if let Err(e) = record_rollback(&req.job_id, &backup_path, &install_path).await { - return ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(json!({"error": e.to_string()})), - ) - .into_response(); - } - } - Err(e) => { - return ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(json!({"error": format!("backup failed: {}", e)})), - ) - .into_response() - } - } - - // Deploy temp binary - if let Err(e) = deploy_temp_binary(&req.job_id, &install_path).await { - return ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(json!({"error": format!("deploy failed: {}", e)})), - ) - .into_response(); - } - - // Try to restart service if provided - if let Some(svc) = req.service_name { - if let Err(e) = restart_service(&svc).await { - // Best-effort: return 202 with warning so external orchestrator can proceed - return ( - StatusCode::ACCEPTED, - Json(json!({"deployed": true, "restart_error": e.to_string()})), - ) - .into_response(); - } - } - - (StatusCode::ACCEPTED, Json(json!({"deployed": true}))).into_response() -} - -async fn self_update_rollback( - State(_state): State, - headers: HeaderMap, -) -> impl IntoResponse { - if let Err(resp) = validate_agent_id(&headers) { - return resp.into_response(); - } - match rollback_latest().await { - Ok(Some(entry)) => ( - StatusCode::ACCEPTED, - Json(json!({"rolled_back": true, "install_path": entry.install_path})), - ) - .into_response(), - Ok(None) => ( - StatusCode::NOT_FOUND, - Json(json!({"error": "no backups available"})), - ) - .into_response(), - Err(e) => ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(json!({"error": e.to_string()})), - ) - .into_response(), - } -} - -// ------- v2.0 long-poll and execute endpoints -------- - -#[derive(Deserialize)] -#[allow(dead_code)] -struct WaitParams { - #[serde(default = "default_wait_timeout")] - timeout: u64, - #[serde(default)] - priority: Option, -} - -fn default_wait_timeout() -> u64 { - 30 -} - -fn validate_agent_id(headers: &HeaderMap) -> Result<(), (StatusCode, Json)> { - let expected = std::env::var("AGENT_ID").unwrap_or_default(); - if expected.is_empty() { - return Ok(()); - } - match headers.get("X-Agent-Id").and_then(|v| v.to_str().ok()) { - Some(got) if got == expected => Ok(()), - _ => Err(( - StatusCode::UNAUTHORIZED, - Json(ErrorResponse { - error: "Invalid or missing X-Agent-Id".to_string(), - }), - )), - } -} - -fn header_str<'a>(headers: &'a HeaderMap, name: &str) -> Option<&'a str> { - headers.get(name).and_then(|v| v.to_str().ok()) -} - -async fn verify_stacker_post( - state: &SharedState, - headers: &HeaderMap, - body: &[u8], - required_scope: &str, -) -> Result<(), (StatusCode, Json)> { - validate_agent_id(headers)?; - - // Rate limiting per agent - let agent_id = header_str(headers, "X-Agent-Id").unwrap_or(""); - if !state.rate_limiter.allow(agent_id).await { - state - .audit - .rate_limited(agent_id, header_str(headers, "X-Request-Id")); - return Err(( - StatusCode::TOO_MANY_REQUESTS, - Json(ErrorResponse { - error: "rate limited".into(), - }), - )); - } - - // HMAC signature verify - let token = { state.agent_token.read().await.clone() }; - let skew = std::env::var("SIGNATURE_MAX_SKEW_SECS") - .ok() - .and_then(|v| v.parse::().ok()) - .unwrap_or(300); - if let Err(e) = verify_signature(headers, body, &token, skew) { - state - .audit - .signature_invalid(Some(agent_id), header_str(headers, "X-Request-Id")); - return Err(( - StatusCode::UNAUTHORIZED, - Json(ErrorResponse { - error: format!("invalid signature: {}", e), - }), - )); - } - - // Replay prevention - if let Some(req_id) = header_str(headers, "X-Request-Id") { - if state.replay.check_and_store(req_id).await.is_err() { - state.audit.replay_detected(Some(agent_id), Some(req_id)); - return Err(( - StatusCode::CONFLICT, - Json(ErrorResponse { - error: "replay detected".into(), - }), - )); - } - } else { - return Err(( - StatusCode::BAD_REQUEST, - Json(ErrorResponse { - error: "missing X-Request-Id".into(), - }), - )); - } - - // Scope authorization - if !state.scopes.is_allowed(required_scope) { - state.audit.scope_denied( - agent_id, - header_str(headers, "X-Request-Id"), - required_scope, - ); - return Err(( - StatusCode::FORBIDDEN, - Json(ErrorResponse { - error: "insufficient scope".into(), - }), - )); - } - - state.audit.auth_success( - agent_id, - header_str(headers, "X-Request-Id"), - required_scope, - ); - Ok(()) -} - -async fn commands_wait( - State(state): State, - Path(_hash): Path, - Query(params): Query, - headers: HeaderMap, -) -> impl IntoResponse { - if let Err(resp) = validate_agent_id(&headers) { - return resp.into_response(); - } - // Optional signing for GET /wait (empty body) controlled by env flag - let require_sig = std::env::var("WAIT_REQUIRE_SIGNATURE") - .map(|v| v == "true") - .unwrap_or(false); - if require_sig { - if let Err(resp) = verify_stacker_post(&state, &headers, &[], "commands:wait").await { - return resp.into_response(); - } - } else { - // Lightweight rate limiting without signature - if !state - .rate_limiter - .allow( - headers - .get("X-Agent-Id") - .and_then(|v| v.to_str().ok()) - .unwrap_or(""), - ) - .await - { - state.audit.rate_limited( - headers - .get("X-Agent-Id") - .and_then(|v| v.to_str().ok()) - .unwrap_or(""), - None, - ); - return ( - StatusCode::TOO_MANY_REQUESTS, - Json(json!({"error": "rate limited"})), - ) - .into_response(); - } - } - let deadline = tokio::time::Instant::now() + Duration::from_secs(params.timeout); - loop { - if let Some(cmd) = { - let mut q = state.commands_queue.lock().await; - q.pop_front() - } { - return Json(cmd).into_response(); - } - let now = tokio::time::Instant::now(); - if now >= deadline { - return (StatusCode::NO_CONTENT, "").into_response(); - } - let wait = deadline - now; - tokio::select! { - _ = state.commands_notify.notified() => {}, - _ = tokio::time::sleep(wait) => { return (StatusCode::NO_CONTENT, "").into_response(); } - } - } -} - -async fn commands_report( - State(state): State, - headers: HeaderMap, - body: Bytes, -) -> impl IntoResponse { - if let Err(resp) = verify_stacker_post(&state, &headers, &body, "commands:report").await { - return resp.into_response(); - } - let res: CommandResult = match serde_json::from_slice(&body) { - Ok(v) => v, - Err(e) => { - return ( - StatusCode::BAD_REQUEST, - Json(json!({"error": e.to_string()})), - ) - .into_response() - } - }; - info!(command_id = %res.command_id, status = %res.status, "command result reported"); - (StatusCode::OK, Json(json!({"accepted": true}))).into_response() -} - -// Execute a validated command with a simple timeout strategy -async fn commands_execute( - State(state): State, - headers: HeaderMap, - body: Bytes, -) -> impl IntoResponse { - if let Err(resp) = verify_stacker_post(&state, &headers, &body, "commands:execute").await { - return resp.into_response(); - } - let cmd: AgentCommand = match serde_json::from_slice(&body) { - Ok(v) => v, - Err(e) => { - return ( - StatusCode::BAD_REQUEST, - Json(json!({"error": e.to_string()})), - ) - .into_response() - } - }; - // Check if this is a Docker operation - if cmd.name.starts_with("docker:") { - match DockerOperation::parse(&cmd.name) { - Ok(op) => { - // Extra scope check for specific Docker operation - let scope = match &op { - DockerOperation::Restart(_) => "docker:restart", - DockerOperation::Stop(_) => "docker:stop", - DockerOperation::Logs(_, _) => "docker:logs", - DockerOperation::Inspect(_) => "docker:inspect", - DockerOperation::Pause(_) => "docker:pause", - }; - if !state.scopes.is_allowed(scope) { - return ( - StatusCode::FORBIDDEN, - Json(json!({"error": "insufficient scope for docker operation"})), - ) - .into_response(); - } - #[cfg(feature = "docker")] - match execute_docker_operation(&cmd.id, op).await { - Ok(result) => return Json(result).into_response(), - Err(e) => { - return ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(json!({"error": e.to_string()})), - ) - .into_response(); - } - } - #[cfg(not(feature = "docker"))] - return ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(json!({"error": "Docker operations not available"})), - ) - .into_response(); - } - Err(e) => { - return ( - StatusCode::BAD_REQUEST, - Json(json!({"error": format!("invalid docker operation: {}", e)})), - ) - .into_response(); - } - } - } - - // Regular command validation - let validator = CommandValidator::default_secure(); - if let Err(e) = validator.validate(&cmd) { - return ( - StatusCode::BAD_REQUEST, - Json(json!({"error": format!("invalid command: {}", e)})), - ) - .into_response(); - } - - // Optional timeout override in params.timeout_secs - let timeout_secs = cmd - .params - .get("timeout_secs") - .and_then(|v| v.as_u64()) - .unwrap_or(60); - - let strategy = TimeoutStrategy::quick_strategy(timeout_secs); - let executor = CommandExecutor::new(); - - match executor.execute(&cmd, strategy).await { - Ok(exec) => Json(exec.to_command_result()).into_response(), - Err(e) => ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(json!({"error": e.to_string()})), - ) - .into_response(), - } -} - -async fn commands_enqueue( - State(state): State, - headers: HeaderMap, - body: Bytes, -) -> impl IntoResponse { - if let Err(resp) = verify_stacker_post(&state, &headers, &body, "commands:enqueue").await { - return resp.into_response(); - } - let cmd: AgentCommand = match serde_json::from_slice(&body) { - Ok(v) => v, - Err(e) => { - return ( - StatusCode::BAD_REQUEST, - Json(json!({"error": e.to_string()})), - ) - .into_response() - } - }; - { - let mut q = state.commands_queue.lock().await; - q.push_back(cmd); - } - state.commands_notify.notify_waiters(); - (StatusCode::ACCEPTED, Json(json!({"queued": true}))).into_response() -} - -#[derive(Deserialize)] -struct RotateTokenRequest { - new_token: String, -} - -async fn rotate_token( - State(state): State, - headers: HeaderMap, - body: Bytes, -) -> impl IntoResponse { - if let Err(resp) = verify_stacker_post(&state, &headers, &body, "auth:rotate").await { - return resp.into_response(); - } - let req: RotateTokenRequest = match serde_json::from_slice(&body) { - Ok(v) => v, - Err(e) => { - return ( - StatusCode::BAD_REQUEST, - Json(json!({"error": e.to_string()})), - ) - .into_response() - } - }; - { - let mut token = state.agent_token.write().await; - *token = req.new_token.clone(); - } - let agent_id = headers - .get("X-Agent-Id") - .and_then(|v| v.to_str().ok()) - .unwrap_or(""); - state.audit.token_rotated( - agent_id, - headers.get("X-Request-Id").and_then(|v| v.to_str().ok()), - ); - (StatusCode::OK, Json(json!({"rotated": true}))).into_response() -} - -pub async fn serve(config: Config, port: u16, with_ui: bool) -> Result<()> { - let cfg = Arc::new(config); - let state = Arc::new(AppState::new(cfg, with_ui)); - - // Spawn token refresh task if Vault is configured - if let (Some(vault_client), Some(token_cache)) = (&state.vault_client, &state.token_cache) { - let deployment_hash = - std::env::var("DEPLOYMENT_HASH").unwrap_or_else(|_| "default".to_string()); - - let vault_client_clone = vault_client.clone(); - let token_cache_clone = token_cache.clone(); - - let _refresh_task = - spawn_token_refresh(vault_client_clone, deployment_hash, token_cache_clone); - info!("Token refresh background task spawned"); - } - - let heartbeat_interval = std::env::var("METRICS_INTERVAL_SECS") - .ok() - .and_then(|s| s.parse::().ok()) - .map(Duration::from_secs) - .unwrap_or(Duration::from_secs(30)); - spawn_heartbeat( - state.metrics_collector.clone(), - state.metrics_store.clone(), - heartbeat_interval, - state.metrics_tx.clone(), - state.metrics_webhook.clone(), - ); - - let app = create_router(state.clone()).into_make_service_with_connect_info::(); - - if with_ui { - info!("HTTP server with UI starting on port {}", port); - } else { - info!("HTTP server in API-only mode starting on port {}", port); - } - - let addr = SocketAddr::from(([0, 0, 0, 0], port)); - let listener = tokio::net::TcpListener::bind(addr).await?; - info!("HTTP server listening on {}", addr); - axum::serve(listener, app).into_future().await?; - Ok(()) -} diff --git a/src/comms/mod.rs b/src/comms/mod.rs deleted file mode 100644 index f2536a5..0000000 --- a/src/comms/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod local_api; diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 6c47f19..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,10 +0,0 @@ -pub mod agent; -pub mod commands; -pub mod comms; -pub mod monitoring; -pub mod security; -pub mod transport; -pub mod utils; - -// Crate version exposed for runtime queries -pub const VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 927c85d..0000000 --- a/src/main.rs +++ /dev/null @@ -1,101 +0,0 @@ -use dotenvy::dotenv; -use status_panel::{agent, comms, utils}; - -use anyhow::Result; -use clap::{Parser, Subcommand}; -use tracing::info; - -#[derive(Parser)] -#[command(name = "status", version, about = "Status Panel (TryDirect Agent)")] -struct AppCli { - /// Run in daemon mode (background) - #[arg(long)] - daemon: bool, - - /// Config file path - #[arg(short, long, default_value = "config.json", global = true)] - config: String, - - /// Subcommands - #[command(subcommand)] - command: Option, -} - -#[derive(Subcommand)] -enum Commands { - /// Start HTTP server (local API) - Serve { - #[arg(long, default_value_t = 5000)] - port: u16, - /// Enable UI with HTML templates - #[arg(long, default_value_t = false)] - with_ui: bool, - }, - /// Show Docker containers - #[cfg(feature = "docker")] - Containers, - /// Restart container - #[cfg(feature = "docker")] - Restart { name: String }, - /// Stop container - #[cfg(feature = "docker")] - Stop { name: String }, - /// Pause container - #[cfg(feature = "docker")] - Pause { name: String }, -} - -fn run_daemon() -> Result<()> { - use daemonize::Daemonize; - let daemonize = Daemonize::new() - .pid_file("status.pid") - .working_directory(".") - .umask(0o027) - .privileged_action(|| { - info!("daemon started"); - }); - - daemonize.start().map_err(|e| anyhow::anyhow!(e))?; - Ok(()) -} - -#[tokio::main] -async fn main() -> Result<()> { - // Load environment variables from .env if present - let _ = dotenv(); - utils::logging::init(); - - let args = AppCli::parse(); - if args.daemon { - run_daemon()?; - } - - match args.command { - Some(Commands::Serve { port, with_ui }) => { - if with_ui { - info!("Starting local API server with UI on port {port}"); - } else { - info!("Starting local API server on port {port}"); - } - let config = agent::config::Config::from_file(&args.config)?; - comms::local_api::serve(config, port, with_ui).await?; - } - #[cfg(feature = "docker")] - Some(Commands::Containers) => { - let list = agent::docker::list_containers().await?; - println!("{}", serde_json::to_string_pretty(&list)?); - } - #[cfg(feature = "docker")] - Some(Commands::Restart { name }) => agent::docker::restart(&name).await?, - #[cfg(feature = "docker")] - Some(Commands::Stop { name }) => agent::docker::stop(&name).await?, - #[cfg(feature = "docker")] - Some(Commands::Pause { name }) => agent::docker::pause(&name).await?, - None => { - // Default: run the agent daemon - agent::daemon::run(args.config).await?; - } - } - - Ok(()) -} diff --git a/src/monitoring/mod.rs b/src/monitoring/mod.rs deleted file mode 100644 index a0ba139..0000000 --- a/src/monitoring/mod.rs +++ /dev/null @@ -1,178 +0,0 @@ -use reqwest::Client; -use serde::Serialize; -use std::sync::Arc; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; -use sysinfo::{Disks, System}; -use tokio::sync::broadcast; -use tokio::sync::{Mutex, RwLock}; -use tokio::task::JoinHandle; -use tracing::info; - -#[derive(Debug, Clone, Serialize, Default)] -pub struct MetricsSnapshot { - pub timestamp_ms: u128, - pub cpu_usage_pct: f32, - pub memory_total_bytes: u64, - pub memory_used_bytes: u64, - pub memory_used_pct: f32, - pub disk_total_bytes: u64, - pub disk_used_bytes: u64, - pub disk_used_pct: f32, -} - -pub type MetricsStore = Arc>; -pub type MetricsTx = broadcast::Sender; - -/// Collects host metrics using sysinfo. -#[derive(Debug)] -pub struct MetricsCollector { - system: Mutex, -} - -impl Default for MetricsCollector { - fn default() -> Self { - Self::new() - } -} - -impl MetricsCollector { - pub fn new() -> Self { - let mut system = System::new_all(); - system.refresh_all(); - Self { - system: Mutex::new(system), - } - } - - /// Capture a fresh snapshot of system metrics. - pub async fn snapshot(&self) -> MetricsSnapshot { - let mut system = self.system.lock().await; - system.refresh_all(); - - let cpu_usage_pct = system.global_cpu_info().cpu_usage(); - - // sysinfo reports memory in KiB; convert to bytes for clarity. - let memory_total_bytes = system.total_memory() * 1024; - let memory_used_bytes = system.used_memory() * 1024; - let memory_used_pct = if memory_total_bytes > 0 { - (memory_used_bytes as f64 / memory_total_bytes as f64 * 100.0) as f32 - } else { - 0.0 - }; - - let mut disk_total_bytes = 0u64; - let mut disk_used_bytes = 0u64; - - let mut disks = Disks::new_with_refreshed_list(); - disks.refresh(); - for disk in disks.list() { - let total = disk.total_space(); - let available = disk.available_space(); - disk_total_bytes = disk_total_bytes.saturating_add(total); - disk_used_bytes = disk_used_bytes.saturating_add(total.saturating_sub(available)); - } - let disk_used_pct = if disk_total_bytes > 0 { - (disk_used_bytes as f64 / disk_total_bytes as f64 * 100.0) as f32 - } else { - 0.0 - }; - - MetricsSnapshot { - timestamp_ms: SystemTime::now() - .duration_since(UNIX_EPOCH) - .map(|d| d.as_millis()) - .unwrap_or_default(), - cpu_usage_pct, - memory_total_bytes, - memory_used_bytes, - memory_used_pct, - disk_total_bytes, - disk_used_bytes, - disk_used_pct, - } - } -} - -/// Periodically refresh metrics and log a lightweight heartbeat. -pub fn spawn_heartbeat( - collector: Arc, - store: MetricsStore, - interval: Duration, - tx: MetricsTx, - webhook: Option, -) -> JoinHandle<()> { - let client = webhook.as_ref().map(|_| Client::new()); - let agent_id = std::env::var("AGENT_ID").ok(); - tokio::spawn(async move { - loop { - let snapshot = collector.snapshot().await; - - { - let mut guard = store.write().await; - *guard = snapshot.clone(); - } - - // Broadcast to websocket subscribers; ignore if no receivers. - let _ = tx.send(snapshot.clone()); - - // Optional remote push - if let (Some(url), Some(http)) = (webhook.as_ref(), client.as_ref()) { - let http = http.clone(); - let url = url.clone(); - let payload = snapshot.clone(); - let agent = agent_id.clone(); - tokio::spawn(async move { - // Exponential backoff with jitter; stop on success or client 4xx - let max_retries: u8 = 5; - let mut delay = Duration::from_millis(500); - for attempt in 1..=max_retries { - let mut req = http.post(url.clone()).json(&payload); - if let Some(aid) = agent.as_ref() { - req = req.header("X-Agent-Id", aid); - } - - match req.send().await { - Ok(resp) => { - let status = resp.status(); - if status.is_success() { - tracing::debug!(attempt, status = %status, "metrics webhook push succeeded"); - break; - } else if status.is_client_error() { - // Do not retry on client-side errors (e.g., 401/403/404) - tracing::warn!(attempt, status = %status, "metrics webhook push client error; not retrying"); - break; - } else { - tracing::warn!(attempt, status = %status, "metrics webhook push server error; will retry"); - } - } - Err(e) => { - tracing::warn!(attempt, error = %e, "metrics webhook push failed; will retry"); - } - } - - // Jitter derived from current time to avoid herd effects - let nanos = SystemTime::now() - .duration_since(UNIX_EPOCH) - .map(|d| d.subsec_nanos()) - .unwrap_or(0); - let jitter = Duration::from_millis(50 + (nanos % 200) as u64); - tokio::time::sleep(delay + jitter).await; - // Exponential backoff capped at ~8s - delay = delay.saturating_mul(2).min(Duration::from_secs(8)); - } - }); - } - - info!( - cpu = snapshot.cpu_usage_pct, - mem_used_bytes = snapshot.memory_used_bytes, - mem_total_bytes = snapshot.memory_total_bytes, - disk_used_bytes = snapshot.disk_used_bytes, - disk_total_bytes = snapshot.disk_total_bytes, - "heartbeat metrics refreshed" - ); - - tokio::time::sleep(interval).await; - } - }) -} diff --git a/src/security/audit_log.rs b/src/security/audit_log.rs deleted file mode 100644 index 7f163fa..0000000 --- a/src/security/audit_log.rs +++ /dev/null @@ -1,57 +0,0 @@ -use tracing::{error, info, warn}; - -#[derive(Debug, Clone, Default)] -pub struct AuditLogger; - -impl AuditLogger { - pub fn new() -> Self { - Self - } - - pub fn auth_success(&self, agent_id: &str, request_id: Option<&str>, action: &str) { - info!(target: "audit", event = "auth_success", agent_id, request_id = request_id.unwrap_or(""), action); - } - - pub fn auth_failure(&self, agent_id: Option<&str>, request_id: Option<&str>, reason: &str) { - warn!(target: "audit", event = "auth_failure", agent_id = agent_id.unwrap_or("") , request_id = request_id.unwrap_or(""), reason); - } - - pub fn signature_invalid(&self, agent_id: Option<&str>, request_id: Option<&str>) { - warn!(target: "audit", event = "signature_invalid", agent_id = agent_id.unwrap_or("") , request_id = request_id.unwrap_or("")); - } - - pub fn rate_limited(&self, agent_id: &str, request_id: Option<&str>) { - warn!(target: "audit", event = "rate_limited", agent_id, request_id = request_id.unwrap_or("")); - } - - pub fn replay_detected(&self, agent_id: Option<&str>, request_id: Option<&str>) { - warn!(target: "audit", event = "replay_detected", agent_id = agent_id.unwrap_or("") , request_id = request_id.unwrap_or("")); - } - - pub fn scope_denied(&self, agent_id: &str, request_id: Option<&str>, scope: &str) { - warn!(target: "audit", event = "scope_denied", agent_id, request_id = request_id.unwrap_or(""), scope); - } - - pub fn command_executed( - &self, - agent_id: &str, - request_id: Option<&str>, - command_id: &str, - name: &str, - ) { - info!(target: "audit", event = "command_executed", agent_id, request_id = request_id.unwrap_or(""), command_id, name); - } - - pub fn token_rotated(&self, agent_id: &str, request_id: Option<&str>) { - info!(target: "audit", event = "token_rotated", agent_id, request_id = request_id.unwrap_or("")); - } - - pub fn internal_error( - &self, - agent_id: Option<&str>, - request_id: Option<&str>, - error_msg: &str, - ) { - error!(target: "audit", event = "internal_error", agent_id = agent_id.unwrap_or("") , request_id = request_id.unwrap_or(""), error = error_msg); - } -} diff --git a/src/security/auth.rs b/src/security/auth.rs deleted file mode 100644 index 0edd50e..0000000 --- a/src/security/auth.rs +++ /dev/null @@ -1,159 +0,0 @@ -use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; -use std::sync::Arc; -use uuid::Uuid; - -/// Session-based user info. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SessionUser { - pub id: u64, - pub username: String, - pub created_at: DateTime, -} - -impl SessionUser { - pub fn new(username: String) -> Self { - Self { - id: 1, - username, - created_at: Utc::now(), - } - } -} - -/// In-memory session store (replace with persistent store in production). -#[derive(Debug, Clone)] -pub struct SessionStore { - sessions: Arc>>, -} - -impl SessionStore { - pub fn new() -> Self { - Self { - sessions: Arc::new(tokio::sync::RwLock::new(std::collections::HashMap::new())), - } - } - - pub async fn create_session(&self, user: SessionUser) -> String { - let session_id = Uuid::new_v4().to_string(); - let mut sessions = self.sessions.write().await; - sessions.insert(session_id.clone(), user); - session_id - } - - pub async fn get_session(&self, session_id: &str) -> Option { - let sessions = self.sessions.read().await; - sessions.get(session_id).cloned() - } - - pub async fn delete_session(&self, session_id: &str) { - let mut sessions = self.sessions.write().await; - sessions.remove(session_id); - } -} - -impl Default for SessionStore { - fn default() -> Self { - Self::new() - } -} - -/// Credentials from environment. -pub struct Credentials { - pub username: String, - pub password: String, -} - -impl Credentials { - pub fn from_env() -> Self { - let username = - std::env::var("STATUS_PANEL_USERNAME").unwrap_or_else(|_| "admin".to_string()); - let password = - std::env::var("STATUS_PANEL_PASSWORD").unwrap_or_else(|_| "admin".to_string()); - Self { username, password } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[tokio::test] - async fn test_session_store_create_and_get() { - let store = SessionStore::new(); - let user = SessionUser::new("testuser".to_string()); - - let session_id = store.create_session(user.clone()).await; - assert!(!session_id.is_empty()); - - let retrieved = store.get_session(&session_id).await; - assert!(retrieved.is_some()); - assert_eq!(retrieved.unwrap().username, "testuser"); - } - - #[tokio::test] - async fn test_session_store_delete() { - let store = SessionStore::new(); - let user = SessionUser::new("testuser".to_string()); - - let session_id = store.create_session(user).await; - let retrieved = store.get_session(&session_id).await; - assert!(retrieved.is_some()); - - store.delete_session(&session_id).await; - let after_delete = store.get_session(&session_id).await; - assert!(after_delete.is_none()); - } - - #[tokio::test] - async fn test_session_store_multiple_sessions() { - let store = SessionStore::new(); - let user1 = SessionUser::new("user1".to_string()); - let user2 = SessionUser::new("user2".to_string()); - - let session1 = store.create_session(user1).await; - let session2 = store.create_session(user2).await; - - assert_ne!(session1, session2); - - let retrieved1 = store.get_session(&session1).await.unwrap(); - let retrieved2 = store.get_session(&session2).await.unwrap(); - - assert_eq!(retrieved1.username, "user1"); - assert_eq!(retrieved2.username, "user2"); - } - - #[test] - fn test_session_user_creation() { - let user = SessionUser::new("testuser".to_string()); - assert_eq!(user.id, 1); - assert_eq!(user.username, "testuser"); - } - - #[test] - fn test_credentials_from_env() { - std::env::set_var("STATUS_PANEL_USERNAME", "envuser"); - std::env::set_var("STATUS_PANEL_PASSWORD", "envpass"); - - let creds = Credentials::from_env(); - assert_eq!(creds.username, "envuser"); - assert_eq!(creds.password, "envpass"); - - std::env::remove_var("STATUS_PANEL_USERNAME"); - std::env::remove_var("STATUS_PANEL_PASSWORD"); - } - - #[test] - fn test_credentials_defaults() { - // Clear any environment variables first - std::env::remove_var("STATUS_PANEL_USERNAME"); - std::env::remove_var("STATUS_PANEL_PASSWORD"); - - // Small delay to avoid race with other tests - std::thread::sleep(std::time::Duration::from_millis(10)); - - let creds = Credentials::from_env(); - assert_eq!(creds.username, "admin"); - assert_eq!(creds.password, "admin"); - } -} diff --git a/src/security/mod.rs b/src/security/mod.rs deleted file mode 100644 index de7da14..0000000 --- a/src/security/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -pub mod auth; - -// @todo crypto operations, keys, validation per GOAL.md -pub mod audit_log; -pub mod rate_limit; -pub mod replay; -pub mod request_signer; -pub mod scopes; - -// Vault integration for token rotation -pub mod token_cache; -pub mod token_refresh; -pub mod vault_client; diff --git a/src/security/rate_limit.rs b/src/security/rate_limit.rs deleted file mode 100644 index 245e835..0000000 --- a/src/security/rate_limit.rs +++ /dev/null @@ -1,43 +0,0 @@ -use std::sync::Arc; -use std::{ - collections::{HashMap, VecDeque}, - time::{Duration, Instant}, -}; -use tokio::sync::Mutex; - -#[derive(Debug, Clone)] -pub struct RateLimiter { - window: Duration, - limit: usize, - inner: Arc>>>, -} - -impl RateLimiter { - pub fn new_per_minute(limit: usize) -> Self { - Self { - window: Duration::from_secs(60), - limit, - inner: Arc::new(Mutex::new(HashMap::new())), - } - } - - pub async fn allow(&self, key: &str) -> bool { - let now = Instant::now(); - let mut map = self.inner.lock().await; - let deque = map.entry(key.to_string()).or_insert_with(VecDeque::new); - // purge old - while let Some(&front) = deque.front() { - if now.duration_since(front) > self.window { - deque.pop_front(); - } else { - break; - } - } - if deque.len() < self.limit { - deque.push_back(now); - true - } else { - false - } - } -} diff --git a/src/security/replay.rs b/src/security/replay.rs deleted file mode 100644 index 8f9fc4e..0000000 --- a/src/security/replay.rs +++ /dev/null @@ -1,35 +0,0 @@ -use std::sync::Arc; -use std::{ - collections::HashMap, - time::{Duration, Instant}, -}; -use tokio::sync::Mutex; - -#[derive(Debug, Clone)] -pub struct ReplayProtection { - ttl: Duration, - inner: Arc>>, -} - -impl ReplayProtection { - pub fn new_ttl(ttl_secs: u64) -> Self { - Self { - ttl: Duration::from_secs(ttl_secs), - inner: Arc::new(Mutex::new(HashMap::new())), - } - } - - // Returns Ok(()) if id is fresh and stored; Err(()) if replay detected - pub async fn check_and_store(&self, id: &str) -> Result<(), ()> { - let now = Instant::now(); - let mut map = self.inner.lock().await; - // purge expired - let ttl = self.ttl; - map.retain(|_, &mut t| now.duration_since(t) < ttl); - if map.contains_key(id) { - return Err(()); - } - map.insert(id.to_string(), now); - Ok(()) - } -} diff --git a/src/security/request_signer.rs b/src/security/request_signer.rs deleted file mode 100644 index 026daab..0000000 --- a/src/security/request_signer.rs +++ /dev/null @@ -1,79 +0,0 @@ -use anyhow::{anyhow, Result}; -use axum::http::HeaderMap; -use base64::{engine::general_purpose, Engine}; -use chrono::Utc; -use hmac::{Hmac, Mac}; -use sha2::Sha256; -use subtle::ConstantTimeEq; - -// HMAC-SHA256(request_body, AGENT_TOKEN) → X-Agent-Signature (base64) - -type HmacSha256 = Hmac; - -pub fn compute_signature_base64(key: &str, body: &[u8]) -> String { - let mut mac = - HmacSha256::new_from_slice(key.as_bytes()).expect("HMAC can take key of any size"); - mac.update(body); - let sig = mac.finalize().into_bytes(); - general_purpose::STANDARD.encode(sig) -} - -fn decode_signature(sig: &str) -> Result> { - // Prefer base64; if it fails, try hex as a fallback - if let Ok(bytes) = general_purpose::STANDARD.decode(sig) { - return Ok(bytes); - } - // hex fallback - fn from_hex(s: &str) -> Option> { - if !s.len().is_multiple_of(2) { - return None; - } - let mut out = Vec::with_capacity(s.len() / 2); - let bytes = s.as_bytes(); - for i in (0..s.len()).step_by(2) { - let hi = (bytes[i] as char).to_digit(16)? as u8; - let lo = (bytes[i + 1] as char).to_digit(16)? as u8; - out.push((hi << 4) | lo); - } - Some(out) - } - from_hex(sig).ok_or_else(|| anyhow!("invalid signature encoding")) -} - -pub fn verify_signature( - headers: &HeaderMap, - body: &[u8], - key: &str, - max_skew_secs: i64, -) -> Result<()> { - // Require timestamp freshness - let ts = headers - .get("X-Timestamp") - .and_then(|v| v.to_str().ok()) - .ok_or_else(|| anyhow!("missing X-Timestamp"))?; - let ts_val: i64 = ts.parse().map_err(|_| anyhow!("invalid X-Timestamp"))?; - let now = Utc::now().timestamp(); - let skew = (now - ts_val).abs(); - if skew > max_skew_secs { - return Err(anyhow!("stale request (timestamp skew)")); - } - - // Require signature header - let sig_hdr = headers - .get("X-Agent-Signature") - .and_then(|v| v.to_str().ok()) - .ok_or_else(|| anyhow!("missing X-Agent-Signature"))?; - let provided = decode_signature(sig_hdr)?; - - // Compute expected - let mut mac = - HmacSha256::new_from_slice(key.as_bytes()).map_err(|_| anyhow!("invalid hmac key"))?; - mac.update(body); - let expected = mac.finalize().into_bytes(); - - if provided.ct_eq(expected.as_slice()).unwrap_u8() == 1 { - Ok(()) - } else { - Err(anyhow!("signature mismatch")) - } -} diff --git a/src/security/scopes.rs b/src/security/scopes.rs deleted file mode 100644 index 68247c2..0000000 --- a/src/security/scopes.rs +++ /dev/null @@ -1,30 +0,0 @@ -use std::collections::HashSet; - -#[derive(Debug, Clone, Default)] -pub struct Scopes { - allowed: HashSet, -} - -impl Scopes { - pub fn from_env() -> Self { - let mut s = Self { - allowed: HashSet::new(), - }; - if let Ok(val) = std::env::var("AGENT_SCOPES") { - for item in val.split(',') { - let scope = item.trim(); - if !scope.is_empty() { - s.allowed.insert(scope.to_string()); - } - } - } - s - } - - pub fn is_allowed(&self, scope: &str) -> bool { - if self.allowed.is_empty() { - return true; - } - self.allowed.contains(scope) - } -} diff --git a/src/security/token_cache.rs b/src/security/token_cache.rs deleted file mode 100644 index c27c7c4..0000000 --- a/src/security/token_cache.rs +++ /dev/null @@ -1,104 +0,0 @@ -use chrono::{DateTime, Utc}; -use std::sync::Arc; -use tokio::sync::RwLock; -use tracing::debug; - -/// Token cache with atomic swap capability and rotation tracking. -#[derive(Debug, Clone)] -pub struct TokenCache { - token: Arc>, - last_rotated: Arc>>>, -} - -impl TokenCache { - /// Create a new token cache with initial token. - pub fn new(initial_token: String) -> Self { - Self { - token: Arc::new(RwLock::new(initial_token)), - last_rotated: Arc::new(RwLock::new(Some(Utc::now()))), - } - } - - /// Get the current token (read-only). - pub async fn get(&self) -> String { - self.token.read().await.clone() - } - - /// Atomically swap the token and record rotation time. - pub async fn swap(&self, new_token: String) { - let mut token = self.token.write().await; - if *token != new_token { - *token = new_token; - drop(token); - - let mut last_rotated = self.last_rotated.write().await; - *last_rotated = Some(Utc::now()); - debug!("Token rotated at {:?}", last_rotated); - } - } - - /// Get the time of last rotation. - pub async fn last_rotated(&self) -> Option> { - *self.last_rotated.read().await - } - - /// Get token age in seconds since last rotation. - pub async fn age_seconds(&self) -> u64 { - if let Some(rotated) = self.last_rotated().await { - let age = Utc::now() - rotated; - age.num_seconds().max(0) as u64 - } else { - 0 - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[tokio::test] - async fn test_token_cache_get_set() { - let cache = TokenCache::new("initial_token".to_string()); - assert_eq!(cache.get().await, "initial_token"); - - cache.swap("new_token".to_string()).await; - assert_eq!(cache.get().await, "new_token"); - } - - #[tokio::test] - async fn test_token_cache_no_rotation_on_same_token() { - let cache = TokenCache::new("token".to_string()); - let first_rotated = cache.last_rotated().await; - - // Try to swap with the same token - cache.swap("token".to_string()).await; - let second_rotated = cache.last_rotated().await; - - assert_eq!(first_rotated, second_rotated); - } - - #[tokio::test] - async fn test_token_cache_age_seconds() { - let cache = TokenCache::new("token".to_string()); - let age = cache.age_seconds().await; - - // Should be 0 or very small - assert!(age <= 1); - - tokio::time::sleep(std::time::Duration::from_millis(500)).await; - let age = cache.age_seconds().await; - - // May be 0 or 1 depending on timing - assert!(age <= 2); - } - - #[tokio::test] - async fn test_token_cache_clone() { - let cache = TokenCache::new("token".to_string()); - let cloned = cache.clone(); - - cloned.swap("new_token".to_string()).await; - assert_eq!(cache.get().await, "new_token"); - } -} diff --git a/src/security/token_refresh.rs b/src/security/token_refresh.rs deleted file mode 100644 index 24b8ec7..0000000 --- a/src/security/token_refresh.rs +++ /dev/null @@ -1,79 +0,0 @@ -use tokio::time::{sleep, Duration}; -use tracing::{debug, info, warn}; - -use crate::security::token_cache::TokenCache; -use crate::security::vault_client::VaultClient; - -/// Background task that refreshes the agent token from Vault. -/// -/// Runs every 60 seconds (+ 5-10s jitter) and: -/// 1. Fetches the current token from Vault -/// 2. If changed, atomically swaps it in the cache -/// 3. Handles Vault errors gracefully with warnings -pub async fn spawn_token_refresh( - vault_client: VaultClient, - deployment_hash: String, - token_cache: TokenCache, -) -> tokio::task::JoinHandle<()> { - tokio::spawn(async move { - loop { - // Generate jitter (5-10s) outside the loop context to satisfy Send - let jitter = { - use rand::Rng; - rand::thread_rng().gen_range(5..10) - }; - let interval = Duration::from_secs(60 + jitter as u64); - - sleep(interval).await; - - match vault_client.fetch_agent_token(&deployment_hash).await { - Ok(new_token) => { - let current = token_cache.get().await; - if current != new_token { - token_cache.swap(new_token).await; - info!( - deployment_hash = %deployment_hash, - "Agent token rotated from Vault" - ); - } else { - debug!( - deployment_hash = %deployment_hash, - "Token unchanged from Vault" - ); - } - } - Err(err) => { - warn!( - deployment_hash = %deployment_hash, - error = %err, - "Failed to fetch token from Vault (will retry)" - ); - } - } - } - }) -} - -/// Graceful token rotation handler for in-flight requests. -/// -/// This helper can be called when a token rotation is detected -/// to ensure in-flight requests are not prematurely terminated. -pub fn allow_graceful_termination(_token_cache: &TokenCache) { - // In-flight requests with the old token will complete successfully - // because new requests will pick up the swapped token from the cache. - // This is handled implicitly via Arc-based sharing of TokenCache. - debug!("Token rotation allowed to proceed; in-flight requests will complete"); -} - -#[cfg(test)] -mod tests { - use super::*; - - #[tokio::test] - async fn test_allow_graceful_termination() { - let cache = TokenCache::new("token".to_string()); - allow_graceful_termination(&cache); - // Just ensure it doesn't panic - assert_eq!(cache.get().await, "token"); - } -} diff --git a/src/security/vault_client.rs b/src/security/vault_client.rs deleted file mode 100644 index dbeb9ca..0000000 --- a/src/security/vault_client.rs +++ /dev/null @@ -1,194 +0,0 @@ -use anyhow::{Context, Result}; -use reqwest::Client; -use serde::Deserialize; -use tracing::{debug, info, warn}; - -/// Vault KV response envelope for token fetch. -#[derive(Debug, Deserialize)] -struct VaultKvResponse { - #[serde(default)] - data: VaultKvData, -} - -#[derive(Debug, Deserialize, Default)] -struct VaultKvData { - #[serde(default)] - data: VaultTokenData, -} - -#[derive(Debug, Deserialize, Default)] -struct VaultTokenData { - token: Option, -} - -/// Vault client for fetching and managing agent tokens. -#[derive(Debug, Clone)] -pub struct VaultClient { - base_url: String, - token: String, - prefix: String, - http_client: reqwest::Client, -} - -impl VaultClient { - /// Create a new Vault client from environment variables. - /// - /// Environment variables: - /// - `VAULT_ADDRESS`: Base URL (e.g., http://127.0.0.1:8200) - /// - `VAULT_TOKEN`: Authentication token - /// - `VAULT_AGENT_PATH_PREFIX`: KV mount/prefix (e.g., status_panel or kv/status_panel) - pub fn from_env() -> Result> { - let base_url = std::env::var("VAULT_ADDRESS").ok(); - let token = std::env::var("VAULT_TOKEN").ok(); - let prefix = std::env::var("VAULT_AGENT_PATH_PREFIX").ok(); - - match (base_url, token, prefix) { - (Some(base), Some(tok), Some(pref)) => { - let http_client = Client::builder() - .timeout(std::time::Duration::from_secs(10)) - .build() - .context("creating HTTP client")?; - - debug!("Vault client initialized with base_url={}", base); - - Ok(Some(VaultClient { - base_url: base, - token: tok, - prefix: pref, - http_client, - })) - } - _ => { - debug!("Vault not configured (missing VAULT_ADDRESS, VAULT_TOKEN, or VAULT_AGENT_PATH_PREFIX)"); - Ok(None) - } - } - } - - /// Fetch agent token from Vault KV store. - /// - /// Constructs path: GET {base_url}/v1/{prefix}/{deployment_hash}/token - /// Expects response: {"data":{"data":{"token":"..."}}} - pub async fn fetch_agent_token(&self, deployment_hash: &str) -> Result { - let url = format!( - "{}/v1/{}/{}/token", - self.base_url, self.prefix, deployment_hash - ); - - debug!("Fetching token from Vault: {}", url); - - let response = self - .http_client - .get(&url) - .header("X-Vault-Token", &self.token) - .send() - .await - .context("sending Vault request")?; - - if !response.status().is_success() { - let status = response.status(); - let body = response.text().await.unwrap_or_default(); - return Err(anyhow::anyhow!( - "Vault fetch failed with status {}: {}", - status, - body - )); - } - - let vault_resp: VaultKvResponse = - response.json().await.context("parsing Vault response")?; - - vault_resp - .data - .data - .token - .context("token not found in Vault response") - } - - /// Store agent token in Vault KV store (for registration or update). - /// - /// Constructs path: POST {base_url}/v1/{prefix}/{deployment_hash}/token - pub async fn store_agent_token(&self, deployment_hash: &str, token: &str) -> Result<()> { - let url = format!( - "{}/v1/{}/{}/token", - self.base_url, self.prefix, deployment_hash - ); - - debug!("Storing token in Vault: {}", url); - - let payload = serde_json::json!({ - "data": { - "token": token - } - }); - - let response = self - .http_client - .post(&url) - .header("X-Vault-Token", &self.token) - .json(&payload) - .send() - .await - .context("sending Vault store request")?; - - if !response.status().is_success() { - let status = response.status(); - let body = response.text().await.unwrap_or_default(); - return Err(anyhow::anyhow!( - "Vault store failed with status {}: {}", - status, - body - )); - } - - info!("Token successfully stored in Vault for {}", deployment_hash); - Ok(()) - } - - /// Delete agent token from Vault KV store (for revocation). - pub async fn delete_agent_token(&self, deployment_hash: &str) -> Result<()> { - let url = format!( - "{}/v1/{}/{}/token", - self.base_url, self.prefix, deployment_hash - ); - - debug!("Deleting token from Vault: {}", url); - - let response = self - .http_client - .delete(&url) - .header("X-Vault-Token", &self.token) - .send() - .await - .context("sending Vault delete request")?; - - if !response.status().is_success() && response.status() != 204 { - let status = response.status(); - let body = response.text().await.unwrap_or_default(); - warn!( - "Vault delete returned status {}: {} (may still be deleted)", - status, body - ); - } - - info!("Token deleted from Vault for {}", deployment_hash); - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_vault_client_from_env_missing() { - // Clear env vars if set - std::env::remove_var("VAULT_ADDRESS"); - std::env::remove_var("VAULT_TOKEN"); - std::env::remove_var("VAULT_AGENT_PATH_PREFIX"); - - let result = VaultClient::from_env(); - assert!(result.is_ok()); - assert!(result.unwrap().is_none()); - } -} diff --git a/src/transport/http_polling.rs b/src/transport/http_polling.rs deleted file mode 100644 index b0bea25..0000000 --- a/src/transport/http_polling.rs +++ /dev/null @@ -1,64 +0,0 @@ -use anyhow::{Context, Result}; -use serde_json::Value; -use std::time::Duration; - -use crate::transport::Command; - -/// Long-poll the dashboard for a command. -/// Returns Some(Command) if available within timeout, else None. -pub async fn wait_for_command( - base_url: &str, - deployment_hash: &str, - agent_id: &str, - timeout_secs: u64, - priority: Option<&str>, -) -> Result> { - let url = format!( - "{}/api/v1/commands/wait/{}?timeout={}&priority={}", - base_url, - deployment_hash, - timeout_secs, - priority.unwrap_or("normal") - ); - - let client = reqwest::Client::builder() - .timeout(Duration::from_secs(timeout_secs + 5)) - .build() - .context("building http client")?; - - let resp = client - .get(&url) - .header("X-Agent-Id", agent_id) - .send() - .await - .context("long poll send")?; - - match resp.status().as_u16() { - 200 => { - let val: Value = resp.json().await.context("parse command json")?; - let cmd: Command = serde_json::from_value(val).context("map to Command")?; - Ok(Some(cmd)) - } - 204 => Ok(None), - code => Err(anyhow::anyhow!("unexpected status: {}", code)), - } -} - -/// Report command result back to dashboard. -pub async fn report_result(base_url: &str, agent_id: &str, payload: &Value) -> Result<()> { - let url = format!("{}/api/v1/commands/report", base_url); - let client = reqwest::Client::new(); - let resp = client - .post(&url) - .header("X-Agent-Id", agent_id) - .json(payload) - .send() - .await - .context("report send")?; - - if resp.status().is_success() { - Ok(()) - } else { - Err(anyhow::anyhow!("report failed: {}", resp.status())) - } -} diff --git a/src/transport/mod.rs b/src/transport/mod.rs deleted file mode 100644 index 978f2ac..0000000 --- a/src/transport/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -pub mod http_polling; -pub mod websocket; - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Command { - pub id: String, - pub name: String, - pub params: serde_json::Value, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct CommandResult { - pub command_id: String, - pub status: String, // "success" | "failed" | "timeout" - pub result: Option, - pub error: Option, -} diff --git a/src/transport/websocket.rs b/src/transport/websocket.rs deleted file mode 100644 index 2810ba3..0000000 --- a/src/transport/websocket.rs +++ /dev/null @@ -1,11 +0,0 @@ -use anyhow::Result; -use tracing::{debug, info}; - -/// Placeholder for WebSocket streaming (logs/metrics/status). -/// This stub will be replaced with a `tokio_tungstenite` client. -pub async fn connect_and_stream(_ws_url: &str) -> Result<()> { - info!("WebSocket stub: connect_and_stream called"); - // TODO: implement ping/pong heartbeat and reconnection - debug!("Streaming stub active"); - Ok(()) -} diff --git a/src/utils/logging.rs b/src/utils/logging.rs deleted file mode 100644 index 9ebbe9f..0000000 --- a/src/utils/logging.rs +++ /dev/null @@ -1,12 +0,0 @@ -use tracing_subscriber::prelude::*; -use tracing_subscriber::{fmt, EnvFilter}; - -pub fn init() { - let fmt_layer = fmt::layer().with_target(false); - let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); - - tracing_subscriber::registry() - .with(filter) - .with(fmt_layer) - .init(); -} diff --git a/src/utils/mod.rs b/src/utils/mod.rs deleted file mode 100644 index 31348d2..0000000 --- a/src/utils/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod logging; diff --git a/static/css/controls.css b/static/css/controls.css deleted file mode 100644 index c55320b..0000000 --- a/static/css/controls.css +++ /dev/null @@ -1,28 +0,0 @@ -.container-status{ - display:inline-block; - padding:6px 12px; - border-radius:3px; -} - -.active{ - color: #4caf50; -} -.paused{ - color: red; -} -.btn_icon{ - vertical-align:middle; - font-size:16px; -} -.tab-content{ /* Tabs */ - display: none; -} -.active-tab{ - display: block; -} -.vanish-tab{ - display: none; -} -.tab-title{ - margin-top:30px; -} \ No newline at end of file diff --git a/static/css/style.css b/static/css/style.css deleted file mode 100644 index e6b3aa8..0000000 --- a/static/css/style.css +++ /dev/null @@ -1,29 +0,0 @@ -.footer-basic { - position: flexible; - left: 0; - bottom: 0; - width: 100%; - padding:40px 0; - background-color:#ffffff; - color:#4b4c4d; -} -.footer-basic .copyright { - margin-top:15px; - text-align:center; - font-size:20px; - color:#aaa; - margin-bottom:0; -} -.nav-buttons{ - float:right; - display:inline-block; - margin:15px 0 0 -} -a.nav-title{ - color:rgba(0,0,0,0.7); - text-decoration:none; -} -div.nav-title{ - padding:10px 0; - font-size:18px; -} diff --git a/static/js/tab-controller.js b/static/js/tab-controller.js deleted file mode 100644 index f0a8dfb..0000000 --- a/static/js/tab-controller.js +++ /dev/null @@ -1,15 +0,0 @@ -function displayTab(tabName) { - let i, content, buttons; - - content = document.getElementsByClassName("tab-content"); - for (i = 0; i < content.length; i++) { - content[i].style.display = "none"; - } - - buttons = document.getElementsByClassName("tab-button"); - for (i = 0; i < buttons.length; i++) { - buttons[i].className = buttons[i].className.replace(" active", ""); - } - - document.getElementById(tabName).style.display = "block"; -} \ No newline at end of file diff --git a/templates/error.html b/templates/error.html deleted file mode 100644 index b81b173..0000000 --- a/templates/error.html +++ /dev/null @@ -1,40 +0,0 @@ - - - {{code}} - {{name}} - - - - -
-

{{ code }}

-

{{name}}

-

{{ message }}
Home Page

-
- - diff --git a/templates/index.html b/templates/index.html deleted file mode 100644 index 224fa4a..0000000 --- a/templates/index.html +++ /dev/null @@ -1,216 +0,0 @@ - - - Status Panel - - - - - - - - - -
- - - logout Logout - - - - - {% if ssl_enabled %} -

SSL is already enabled

- https://{{domain}}
- - lock DISABLE SSL - - {% else %} -


- - {% if can_enable %} - - lock Enable SSL - - {% else %} -
-
-
- You can't enable SSL because the DNS are not propagated yet to this IP - - help - -
-
-
-
- {% if ip %} -

Server IP: {{ip}}

- {% endif %} - {% if domainIp %} -

Your domain "A" record is set to: {{domainIp}} - - help - -

- {% endif %} -
-
- {% endif %} - {% endif %} - {% if errors %} -
-
-

{{errors}}

-
-
- {% endif %} - - - - - - - - - {% if container_list %} - -
-

Running containers

- - - - - - - - - - {% for container in container_list %} - - - - - - {% endfor %} - -
NameStatusLogs
- {{container['name']}} - - {% if container['status'] == 'running' %} - - fiber_manual_record {{container['status']}} - - {% else %} - - fiber_manual_record {{container['status']}} - - {% endif %} - Actionarrow_drop_down - - - - - - history - View logs - -
-
- - - -
-

Available web pages

- - - - - - - - - {% for container in container_list %} - {% if container.ports %} - {% for port in container.ports %} - - - - - {% endfor %} - {% endif %} - {% endfor %} - -
Web pageContainer name
- - {% if port.title %} - {{ port.title }} - {% else %} - {{ ip }}:{{port.port}} - {% endif %} -
-
{{container['name']}}
-
- - - {% for container in container_list %} - - {% endfor %} - - {% endif %} -
-

Installed Applications

- - - - - - - - - {% for app_info in apps_info %} - - - - - {% endfor %} - -
ApplicationVersion
{{ app_info['name'] }}
{{ app_info['version'] }}
-
- -
- - - - \ No newline at end of file diff --git a/templates/login.html b/templates/login.html deleted file mode 100644 index 3aeeecb..0000000 --- a/templates/login.html +++ /dev/null @@ -1,36 +0,0 @@ - - - Status Panel - - - - -
-
-
- -
- -
-
-
-
- - -
-
-
-
- - -
-
- {% if error %} -
Login failed
- {% endif %} - -
-
- - - diff --git a/tests/http_routes.rs b/tests/http_routes.rs deleted file mode 100644 index 0a798b7..0000000 --- a/tests/http_routes.rs +++ /dev/null @@ -1,425 +0,0 @@ -use axum::body::Body; -use axum::http::{Request, StatusCode}; -use axum::Router; -use http_body_util::BodyExt; -use serde_json::Value; -use status_panel::agent::config::{Config, ReqData}; -use status_panel::comms::local_api::{create_router, AppState}; -use std::sync::Arc; -use tower::ServiceExt; - -// Helper to create test config -fn test_config() -> Arc { - Arc::new(Config { - domain: Some("test.example.com".to_string()), - subdomains: None, - apps_info: None, - reqdata: ReqData { - email: "test@example.com".to_string(), - }, - ssl: Some("letsencrypt".to_string()), - }) -} - -// Helper to create router without UI -fn test_router() -> Router { - let state = Arc::new(AppState::new(test_config(), false)); - create_router(state) -} - -#[tokio::test] -async fn test_health_endpoint() { - let app = test_router(); - - let response = app - .oneshot( - Request::builder() - .uri("/health") - .body(Body::empty()) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!(response.status(), StatusCode::OK); -} - -#[tokio::test] -async fn test_login_page_get() { - let app = test_router(); - - let response = app - .oneshot( - Request::builder() - .uri("/login") - .body(Body::empty()) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!(response.status(), StatusCode::OK); - - let body_bytes = response.into_body().collect().await.unwrap().to_bytes(); - let body = String::from_utf8(body_bytes.to_vec()).unwrap(); - assert!(body.contains("username")); -} - -#[tokio::test] -async fn test_login_post_success() { - // Ensure no environment variables interfere - std::env::remove_var("STATUS_PANEL_USERNAME"); - std::env::remove_var("STATUS_PANEL_PASSWORD"); - - let app = test_router(); - - let body = "username=admin&password=admin"; - let response = app - .oneshot( - Request::builder() - .method("POST") - .uri("/login") - .header("content-type", "application/x-www-form-urlencoded") - .body(Body::from(body)) - .unwrap(), - ) - .await - .unwrap(); - - // Should redirect to home on successful login - assert_eq!(response.status(), StatusCode::SEE_OTHER); -} - -#[tokio::test] -async fn test_login_post_failure() { - let app = test_router(); - - let body = "username=wrong&password=wrong"; - let response = app - .oneshot( - Request::builder() - .method("POST") - .uri("/login") - .header("content-type", "application/x-www-form-urlencoded") - .body(Body::from(body)) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!(response.status(), StatusCode::UNAUTHORIZED); -} - -#[tokio::test] -async fn test_logout_endpoint() { - let app = test_router(); - - let response = app - .oneshot( - Request::builder() - .method("GET") - .uri("/logout") - .body(Body::empty()) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!(response.status(), StatusCode::OK); -} - -#[tokio::test] -async fn test_metrics_endpoint() { - let app = test_router(); - - let response = app - .oneshot( - Request::builder() - .uri("/metrics") - .body(Body::empty()) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!(response.status(), StatusCode::OK); - - let body_bytes = response.into_body().collect().await.unwrap().to_bytes(); - let json: serde_json::Value = serde_json::from_slice(&body_bytes).unwrap(); - - assert!(json.get("timestamp_ms").is_some()); - assert!(json.get("cpu_usage_pct").is_some()); -} - -#[tokio::test] -#[cfg(feature = "docker")] -async fn test_home_endpoint() { - let app = test_router(); - - let response = app - .oneshot(Request::builder().uri("/").body(Body::empty()).unwrap()) - .await - .unwrap(); - - // Should return 200 with container list (or error if Docker not available) - assert!( - response.status() == StatusCode::OK - || response.status() == StatusCode::INTERNAL_SERVER_ERROR - ); -} - -#[cfg(feature = "docker")] -#[tokio::test] -async fn test_restart_endpoint() { - let app = test_router(); - - let response = app - .oneshot( - Request::builder() - .uri("/restart/test-container") - .body(Body::empty()) - .unwrap(), - ) - .await - .unwrap(); - - // Will fail if container doesn't exist, but route should be valid - assert!( - response.status() == StatusCode::OK - || response.status() == StatusCode::INTERNAL_SERVER_ERROR - ); -} - -#[cfg(feature = "docker")] -#[tokio::test] -async fn test_stack_health_endpoint() { - let app = test_router(); - - let response = app - .oneshot( - Request::builder() - .uri("/stack/health") - .body(Body::empty()) - .unwrap(), - ) - .await - .unwrap(); - - assert!( - response.status() == StatusCode::OK - || response.status() == StatusCode::INTERNAL_SERVER_ERROR - ); -} - -#[cfg(feature = "docker")] -#[tokio::test] -async fn test_index_template_renders() { - use status_panel::agent::docker::{ContainerInfo, PortInfo}; - let mut tera = tera::Tera::new("templates/**/*.html").unwrap(); - - let containers = vec![ContainerInfo { - name: "demo".to_string(), - status: "running".to_string(), - logs: String::new(), - ports: vec![PortInfo { - port: "8081".to_string(), - title: Some("demo".to_string()), - }], - }]; - - let apps_info = vec![status_panel::agent::config::AppInfo { - name: "app".into(), - version: "1.0".into(), - }]; - - let mut context = tera::Context::new(); - context.insert("container_list", &containers); - context.insert("apps_info", &apps_info); - context.insert("errors", &Option::::None); - context.insert("ip", &Option::::None); - context.insert("domainIp", &Option::::None); - context.insert("panel_version", &"test".to_string()); - context.insert("domain", &Some("example.com".to_string())); - context.insert("ssl_enabled", &false); - context.insert("can_enable", &false); - context.insert("ip_help_link", &"https://www.whatismyip.com/"); - - let html = tera.render("index.html", &context); - assert!(html.is_ok(), "template error: {:?}", html.err()); -} - -#[tokio::test] -async fn test_backup_ping_success() { - use serde_json::json; - use status_panel::agent::backup::BackupSigner; - - // Set required environment variables - std::env::set_var("DEPLOYMENT_HASH", "test_deployment_hash"); - std::env::set_var("TRYDIRECT_IP", "127.0.0.1"); - - let app = test_router(); - - // Create a valid hash - let signer = BackupSigner::new(b"test_deployment_hash"); - let valid_hash = signer.sign("test_deployment_hash").unwrap(); - - let payload = json!({"hash": valid_hash}); - - let response = app - .oneshot( - Request::builder() - .method("POST") - .uri("/backup/ping") - .header("content-type", "application/json") - .body(Body::from(payload.to_string())) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!(response.status(), StatusCode::OK); - - let body = response.into_body().collect().await.unwrap().to_bytes(); - let json: Value = serde_json::from_slice(&body).unwrap(); - - assert_eq!(json["status"], "OK"); - assert!(json["hash"].is_string()); -} - -#[tokio::test] -async fn test_backup_ping_with_deployment_hash() { - use serde_json::json; - - // Set required environment variables - std::env::set_var("DEPLOYMENT_HASH", "test_deployment_hash"); - std::env::set_var("TRYDIRECT_IP", "127.0.0.1"); - - let app = test_router(); - - // Test with plain deployment hash (Flask compatibility) - let payload = json!({"hash": "test_deployment_hash"}); - - let response = app - .oneshot( - Request::builder() - .method("POST") - .uri("/backup/ping") - .header("content-type", "application/json") - .body(Body::from(payload.to_string())) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!(response.status(), StatusCode::OK); - - let body = response.into_body().collect().await.unwrap().to_bytes(); - let json: Value = serde_json::from_slice(&body).unwrap(); - - assert_eq!(json["status"], "OK"); - assert!(json["hash"].is_string()); -} - -#[tokio::test] -async fn test_backup_ping_invalid_hash() { - use serde_json::json; - - std::env::set_var("DEPLOYMENT_HASH", "test_deployment_hash"); - std::env::set_var("TRYDIRECT_IP", "127.0.0.1"); - - let app = test_router(); - - let payload = json!({"hash": "invalid_hash_value"}); - - let response = app - .oneshot( - Request::builder() - .method("POST") - .uri("/backup/ping") - .header("content-type", "application/json") - .body(Body::from(payload.to_string())) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!(response.status(), StatusCode::UNAUTHORIZED); -} - -#[tokio::test] -#[ignore] -async fn test_backup_download_file_not_found() { - use status_panel::agent::backup::BackupSigner; - - std::env::set_var("DEPLOYMENT_HASH", "test_deployment_hash"); - let unique = format!( - "/tmp/nonexistent_backup_{}.tar.gz.cpt", - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_millis() - ); - std::env::set_var("BACKUP_PATH", unique); - - let app = test_router(); - - // Create valid hash - let signer = BackupSigner::new(b"test_deployment_hash"); - let valid_hash = signer.sign("test_deployment_hash").unwrap(); - - let response = app - .oneshot( - Request::builder() - .uri(format!("/backup/{}/127.0.0.1", valid_hash)) - .body(Body::empty()) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!(response.status(), StatusCode::NOT_FOUND); -} - -#[tokio::test] -async fn test_backup_download_success() { - use status_panel::agent::backup::BackupSigner; - use std::io::Write; - use tempfile::NamedTempFile; - - std::env::set_var("DEPLOYMENT_HASH", "test_deployment_hash"); - - // Create a temporary backup file - let mut temp_file = NamedTempFile::new().unwrap(); - write!(temp_file, "test backup content").unwrap(); - let temp_path = temp_file.path().to_str().unwrap().to_string(); - std::env::set_var("BACKUP_PATH", &temp_path); - - let app = test_router(); - - // Create valid hash - let signer = BackupSigner::new(b"test_deployment_hash"); - let valid_hash = signer.sign("test_deployment_hash").unwrap(); - - let response = app - .oneshot( - Request::builder() - .uri(format!("/backup/{}/127.0.0.1", valid_hash)) - .body(Body::empty()) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!(response.status(), StatusCode::OK); - - // Check headers - assert_eq!( - response.headers().get("content-type").unwrap(), - "application/octet-stream" - ); - assert!(response.headers().get("content-disposition").is_some()); - - // Check body content - let body = response.into_body().collect().await.unwrap().to_bytes(); - assert_eq!(body.as_ref(), b"test backup content"); -} diff --git a/tests/security_integration.rs b/tests/security_integration.rs deleted file mode 100644 index ecef619..0000000 --- a/tests/security_integration.rs +++ /dev/null @@ -1,249 +0,0 @@ -use axum::http::{Request, StatusCode}; -use axum::{body::Body, Router}; -use base64::{engine::general_purpose, Engine}; -use hmac::{Hmac, Mac}; -use http_body_util::BodyExt; -use serde_json::json; -use sha2::Sha256; -use status_panel::agent::config::{Config, ReqData}; -use status_panel::comms::local_api::{create_router, AppState}; -use std::sync::Arc; -use std::sync::{Mutex, OnceLock}; -use tower::ServiceExt; // for Router::oneshot -use uuid::Uuid; - -static TEST_LOCK: OnceLock> = OnceLock::new(); -fn lock_tests() -> std::sync::MutexGuard<'static, ()> { - match TEST_LOCK.get_or_init(|| Mutex::new(())).lock() { - Ok(g) => g, - Err(e) => e.into_inner(), - } -} - -fn test_config() -> Arc { - Arc::new(Config { - domain: Some("test.example.com".to_string()), - subdomains: None, - apps_info: None, - reqdata: ReqData { - email: "test@example.com".to_string(), - }, - ssl: Some("letsencrypt".to_string()), - }) -} - -fn router_with_env(agent_id: &str, token: &str, scopes: &str) -> Router { - std::env::set_var("AGENT_ID", agent_id); - std::env::set_var("AGENT_TOKEN", token); - std::env::set_var("AGENT_SCOPES", scopes); - std::env::set_var("RATE_LIMIT_PER_MIN", "1000"); - let state = Arc::new(AppState::new(test_config(), false)); - create_router(state) -} - -type HmacSha256 = Hmac; - -fn sign_b64(token: &str, body: &[u8]) -> String { - let mut mac = HmacSha256::new_from_slice(token.as_bytes()).unwrap(); - mac.update(body); - let sig = mac.finalize().into_bytes(); - general_purpose::STANDARD.encode(sig) -} - -async fn post_with_sig( - app: &Router, - path: &str, - agent_id: &str, - token: &str, - body_json: serde_json::Value, - request_id: Option, -) -> (StatusCode, bytes::Bytes) { - let body_str = body_json.to_string(); - let ts = format!("{}", chrono::Utc::now().timestamp()); - let rid = request_id.unwrap_or_else(|| Uuid::new_v4().to_string()); - let sig = sign_b64(token, body_str.as_bytes()); - let response = app - .clone() - .oneshot( - Request::builder() - .method("POST") - .uri(path) - .header("content-type", "application/json") - .header("X-Agent-Id", agent_id) - .header("X-Timestamp", ts) - .header("X-Request-Id", rid) - .header("X-Agent-Signature", sig) - .body(Body::from(body_str)) - .unwrap(), - ) - .await - .unwrap(); - let status = response.status(); - let body = response.into_body().collect().await.unwrap().to_bytes(); - (status, body) -} - -#[tokio::test] -async fn execute_requires_signature_and_scope() { - let _g = lock_tests(); - let app = router_with_env("agent-1", "secret-token", "commands:execute"); - - // Missing signature - let response = app - .clone() - .oneshot( - Request::builder() - .method("POST") - .uri("/api/v1/commands/execute") - .header("content-type", "application/json") - .header("X-Agent-Id", "agent-1") - .body(Body::from( - json!({ - "id": "cmd-1", - "name": "echo hello", - "params": {"timeout_secs": 2} - }) - .to_string(), - )) - .unwrap(), - ) - .await - .unwrap(); - assert_eq!(response.status(), StatusCode::UNAUTHORIZED); - - // With signature & scope - let (status, _) = post_with_sig( - &app, - "/api/v1/commands/execute", - "agent-1", - "secret-token", - json!({"id": "cmd-2", "name": "echo hi", "params": {"timeout_secs": 2}}), - None, - ) - .await; - assert_eq!(status, StatusCode::OK); -} - -#[tokio::test] -async fn replay_detection_returns_409() { - let _g = lock_tests(); - let app = router_with_env("agent-1", "secret-token", "commands:execute"); - let rid = Uuid::new_v4().to_string(); - let path = "/api/v1/commands/execute"; - let body = json!({"id": "cmd-3", "name": "echo hi", "params": {}}); - - let (s1, _) = post_with_sig( - &app, - path, - "agent-1", - "secret-token", - body.clone(), - Some(rid.clone()), - ) - .await; - assert_eq!(s1, StatusCode::OK); - - let (s2, b2) = post_with_sig(&app, path, "agent-1", "secret-token", body, Some(rid)).await; - assert_eq!(s2, StatusCode::CONFLICT); - let msg: serde_json::Value = serde_json::from_slice(&b2).unwrap(); - assert_eq!(msg["error"], "replay detected"); -} - -#[tokio::test] -async fn rate_limit_returns_429() { - let _g = lock_tests(); - // Set very low rate limit BEFORE creating router - std::env::set_var("RATE_LIMIT_PER_MIN", "1"); - std::env::set_var("AGENT_ID", "agent-1"); - std::env::set_var("AGENT_TOKEN", "secret-token"); - std::env::set_var("AGENT_SCOPES", "commands:execute"); - let state = Arc::new(AppState::new(test_config(), false)); - let app = create_router(state); - let path = "/api/v1/commands/execute"; - - let (s1, _) = post_with_sig( - &app, - path, - "agent-1", - "secret-token", - json!({"id":"r1","name":"echo a","params":{}}), - None, - ) - .await; - assert_eq!(s1, StatusCode::OK); - - let (s2, _) = post_with_sig( - &app, - path, - "agent-1", - "secret-token", - json!({"id":"r2","name":"echo b","params":{}}), - None, - ) - .await; - assert_eq!(s2, StatusCode::TOO_MANY_REQUESTS); -} - -#[tokio::test] -async fn scope_denied_returns_403() { - let _g = lock_tests(); - // Do not include commands:execute - let app = router_with_env("agent-1", "secret-token", "commands:report"); - let (status, body) = post_with_sig( - &app, - "/api/v1/commands/execute", - "agent-1", - "secret-token", - json!({"id": "cmd-4", "name": "echo hi", "params": {}}), - None, - ) - .await; - assert_eq!(status, StatusCode::FORBIDDEN); - let msg: serde_json::Value = serde_json::from_slice(&body).unwrap(); - assert_eq!(msg["error"], "insufficient scope"); -} - -#[tokio::test] -async fn wait_can_require_signature() { - let _g = lock_tests(); - // Enable signing for GET /wait - std::env::set_var("WAIT_REQUIRE_SIGNATURE", "true"); - let app = router_with_env("agent-1", "secret-token", "commands:wait"); - - // Missing signature should fail - let response = app - .clone() - .oneshot( - Request::builder() - .method("GET") - .uri("/api/v1/commands/wait/session?timeout=1") - .header("X-Agent-Id", "agent-1") - .body(Body::empty()) - .unwrap(), - ) - .await - .unwrap(); - assert_eq!(response.status(), StatusCode::UNAUTHORIZED); - - // Provide signature over empty body - let ts = format!("{}", chrono::Utc::now().timestamp()); - let rid = Uuid::new_v4().to_string(); - let sig = sign_b64("secret-token", b""); - let response = app - .clone() - .oneshot( - Request::builder() - .method("GET") - .uri("/api/v1/commands/wait/session?timeout=1") - .header("X-Agent-Id", "agent-1") - .header("X-Timestamp", ts) - .header("X-Request-Id", rid) - .header("X-Agent-Signature", sig) - .body(Body::empty()) - .unwrap(), - ) - .await - .unwrap(); - // No commands queued -> 204 No Content - assert_eq!(response.status(), StatusCode::NO_CONTENT); -} diff --git a/tests/self_update_integration.rs b/tests/self_update_integration.rs deleted file mode 100644 index d5fa66e..0000000 --- a/tests/self_update_integration.rs +++ /dev/null @@ -1,63 +0,0 @@ -use sha2::{Digest, Sha256}; -use status_panel::commands::{get_update_status, start_update_job, UpdatePhase}; -use std::collections::HashMap; -use std::sync::Arc; -use tokio::sync::RwLock; -use tokio::time::{sleep, Duration}; - -// Integration test covering download + optional sha256 verification. -#[tokio::test] -async fn start_update_job_downloads_and_verifies() { - let binary_bytes = b"hello-update"; - // Compute sha256 for verification - let mut hasher = Sha256::new(); - hasher.update(binary_bytes); - let expected = format!("{:x}", hasher.finalize()); - - // Mock server hosting the binary - let mut server = mockito::Server::new_async().await; - let mock = server - .mock("GET", "/releases/1.2.3/status-linux-x86_64") - .with_status(200) - .with_body(binary_bytes.as_slice()) - .create_async() - .await; - - // Point updater to the mock server - std::env::set_var("UPDATE_SERVER_URL", server.url()); - std::env::set_var("UPDATE_EXPECTED_SHA256", expected); - - let jobs = Arc::new(RwLock::new(HashMap::new())); - let job_id = start_update_job(jobs.clone(), Some("1.2.3".to_string())) - .await - .expect("job should start"); - - // Wait for completion - let mut phase = UpdatePhase::Pending; - for _ in 0..30 { - if let Some(st) = get_update_status(jobs.clone(), &job_id).await { - phase = st.phase; - if matches!(phase, UpdatePhase::Completed | UpdatePhase::Failed(_)) { - break; - } - } - sleep(Duration::from_millis(100)).await; - } - - mock.assert_async().await; - match phase { - UpdatePhase::Completed => {} - UpdatePhase::Failed(msg) => panic!("update failed: {}", msg), - other => panic!("unexpected phase: {:?}", other), - } - - // Temp file should exist - let tmp_path = format!("/tmp/status-panel.{}.bin", job_id); - let data = tokio::fs::read(&tmp_path) - .await - .expect("temp binary exists"); - assert_eq!(data, binary_bytes); - - // Cleanup temp file - let _ = tokio::fs::remove_file(&tmp_path).await; -}