Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add speedtest-tracker. #379

Merged
merged 7 commits into from
Jan 25, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,7 @@ Install these other services at your leisure/preference:
* [SFTP](sftp#readme) - a secure file server
* [Shaarli](shaarli#readme) - a bookmark manager
* [Smokeping](smokeping#readme) - a network latency measurement tool
* [Speedtest Tracker](speedtest-tracker#readme) - a network performance monitor
* [Step-CA](step-ca) - a secure, online, self-hosted Certificate Authority (CA)
* [Syncthing](syncthing#readme) - a multi-device file synchronization tool
* [Sysbox-Systemd](sysbox-systemd#readme) - a traditional service manager for Linux running in an unprivileged container via sysbox-runc
Expand Down
50 changes: 50 additions & 0 deletions speedtest-tracker/.env-dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# The docker image to use (linuxserver's registry only supports the "latest" tag
# (https://fleet.linuxserver.io/image?name=linuxserver/speedtest-tracker):
SPEEDTEST_TRACKER_IMAGE=lscr.io/linuxserver/speedtest-tracker:latest

# The domain name for the speedtest-tracker service:
SPEEDTEST_TRACKER_TRAEFIK_HOST=speedtest-tracker.example.com

# The name of this instance. If there is only one instance, use 'default'.
SPEEDTEST_TRACKER_INSTANCE=

# Filter access by IP address source range (CIDR):
##Disallow all access: 0.0.0.0/32
##Allow all access: 0.0.0.0/0
SPEEDTEST_TRACKER_IP_SOURCERANGE=0.0.0.0/0

# HTTP Basic Authentication:
# Use `make config` to fill this in properly, or set this to blank to disable.
SPEEDTEST_TRACKER_HTTP_AUTH=

# OAUTH2
# Set to `true` to use OpenID/OAuth2 authentication via the
# traefik-forward-auth service in d.rymcg.tech.
# Using OpenID/OAuth2 will require login to access your app,
# but it will not affect what a successfully logged-in person can do in your
# app. If your app has built-in authentication and can check the user
# header that traefik-forward-auth sends, then your app can limit what the
# logged-in person can do in the app. But if your app can't check the user
# header, or if your app doesn't have built-in authentication at all, then
# any person with an account on your Gitea server can log into your app and
# have full access.
SPEEDTEST_TRACKER_OAUTH2=
# In addition to Oauth2 authentication, you can configure basic authorization
# by entering which authorization group can log into your app. You create
# groups of email addresses in the `traefik` folder by running `make groups`.
SPEEDTEST_TRACKER_OAUTH2_AUTHORIZED_GROUP=

# Mutual TLS (mTLS):
# Set true or false. If true, all clients must present a certificate signed by Step-CA:
SPEEDTEST_TRACKER_MTLS_AUTH=false
# Enter a comma separated list of client domains allowed to connect via mTLS.
# Wildcards are allowed and encouraged on a per-app basis:
SPEEDTEST_TRACKER_MTLS_AUTHORIZED_CERTS=*.clients.speedtest-tracker.example.com

SPEEDTEST_TRACKER_APP_KEY=
SPEEDTEST_TRACKER_UID=1000
SPEEDTEST_TRACKER_GID=1000
SPEEDTEST_TRACKER_ADMIN_NAME=
SPEEDTEST_TRACKER_ADMIN_EMAIL=
SPEEDTEST_TRACKER_ADMIN_PASSWORD=
SPEEDTEST_TRACKER_DISPLAY_TIMEZONE=utc
40 changes: 40 additions & 0 deletions speedtest-tracker/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
ROOT_DIR = ..
include ${ROOT_DIR}/_scripts/Makefile.projects
include ${ROOT_DIR}/_scripts/Makefile.instance

.PHONY: config-hook
config-hook:
@${BIN}/reconfigure_ask ${ENV_FILE} SPEEDTEST_TRACKER_TRAEFIK_HOST "Enter the speedtest-tracker domain name" speedtest-tracker${INSTANCE_URL_SUFFIX}.${ROOT_DOMAIN}
@${BIN}/reconfigure ${ENV_FILE} SPEEDTEST_TRACKER_INSTANCE=$${instance:-default}
@${BIN}/reconfigure_auth ${ENV_FILE} SPEEDTEST_TRACKER
@echo
@${BIN}/reconfigure_ask ${ENV_FILE} SPEEDTEST_TRACKER_ADMIN_NAME "Enter the admin user's login name"
@${BIN}/reconfigure_ask ${ENV_FILE} SPEEDTEST_TRACKER_ADMIN_EMAIL "Enter the admin user's email"
@${BIN}/reconfigure_ask ${ENV_FILE} SPEEDTEST_TRACKER_ADMIN_PASSWORD "Enter the admin user's password"
@echo
EnigmaCurry marked this conversation as resolved.
Show resolved Hide resolved
@${BIN}/reconfigure_ask ${ENV_FILE} SPEEDTEST_TRACKER_DISPLAY_TIMEZONE "Enter the server's timezone"
@echo
@${BIN}/reconfigure_password ${ENV_FILE} SPEEDTEST_TRACKER_APP_KEY 32
@${BIN}/reconfigure ${ENV_FILE} SPEEDTEST_TRACKER_APP_KEY=$$(key=$$(${BIN}/dotenv -f ${ENV_FILE} get SPEEDTEST_TRACKER_APP_KEY); [[ "$${key}" == base64:* ]] && echo "$${key}" || echo "base64:$${key}")
@echo
@${BIN}/reconfigure_ask ${ENV_FILE} SPEEDTEST_TRACKER_UID "Enter the host UID to map to the container UID"
@${BIN}/reconfigure_ask ${ENV_FILE} SPEEDTEST_TRACKER_GID "Enter the host GID to map to the container GID"
mcmikemn marked this conversation as resolved.
Show resolved Hide resolved
@echo

.PHONY: override-hook
override-hook:
#### This sets the override template variables for docker-compose.instance.yaml:
#### The template dynamically renders to docker-compose.override_{DOCKER_CONTEXT}_{INSTANCE}.yaml
#### These settings are used to automatically generate the service container labels, and traefik config, inside the template.
#### The variable arguments have three forms: `=` `=:` `=@`
#### name=VARIABLE_NAME # sets the template 'name' field to the value of VARIABLE_NAME found in the .env file
#### # (this hardcodes the value into docker-compose.override.yaml)
#### name=:VARIABLE_NAME # sets the template 'name' field to the literal string 'VARIABLE_NAME'
#### # (this hardcodes the string into docker-compose.override.yaml)
#### name=@VARIABLE_NAME # sets the template 'name' field to the literal string '${VARIABLE_NAME}'
#### # (used for regular docker-compose expansion of env vars by name.)
@${BIN}/docker_compose_override ${ENV_FILE} project=:speedtest-tracker instance=@SPEEDTEST_TRACKER_INSTANCE traefik_host=@SPEEDTEST_TRACKER_TRAEFIK_HOST http_auth=SPEEDTEST_TRACKER_HTTP_AUTH http_auth_var=@SPEEDTEST_TRACKER_HTTP_AUTH ip_sourcerange=@SPEEDTEST_TRACKER_IP_SOURCERANGE oauth2=SPEEDTEST_TRACKER_OAUTH2 authorized_group=SPEEDTEST_TRACKER_OAUTH2_AUTHORIZED_GROUP enable_mtls_auth=SPEEDTEST_TRACKER_MTLS_AUTH mtls_authorized_certs=SPEEDTEST_TRACKER_MTLS_AUTHORIZED_CERTS

.PHONY: shell
shell:
@make --no-print-directory docker-compose-shell SERVICE=speedtest-tracker
43 changes: 43 additions & 0 deletions speedtest-tracker/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Speedtest Tracker

[Speedtest Tracker](https://github.com/alexjustesen/speedtest-tracker) is a
self-hosted application that monitors the performance and uptime of your
internet connection..

## Config

```
make config
```

This will ask you to enter the domain name to use. It automatically saves your
responses into the configuration file `.env_{INSTANCE}`.

### Authentication and Authorization

See [AUTH.md](../AUTH.md) for information on adding external authentication on
top of your app.

## Install

```
make install
```

## Open

```
make open
```

This will automatically open the page in your web browser, and will prefill the
HTTP Basic Authentication password if you enabled it (and chose to store it in
`passwords.json`).

## Destroy

```
make destroy
```

This completely removes the container and all its volumes.
66 changes: 66 additions & 0 deletions speedtest-tracker/docker-compose.instance.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#! This is a ytt template file for docker-compose.override.yaml
#! References:
#! https://carvel.dev/ytt
#! https://docs.docker.com/compose/extends/#adding-and-overriding-configuration
#! https://github.com/enigmacurry/d.rymcg.tech#overriding-docker-composeyaml-per-instance

#! ### Standard project vars:
#@ load("@ytt:data", "data")
#@ project = data.values.project
#@ instance = data.values.instance
#@ context = data.values.context
#@ traefik_host = data.values.traefik_host
#@ ip_sourcerange = data.values.ip_sourcerange
#@ enable_http_auth = len(data.values.http_auth.strip()) > 0
#@ http_auth = data.values.http_auth_var
#@ enable_oauth2 = data.values.oauth2 == "true"
#@ authorized_group = data.values.authorized_group
#@ enable_mtls_auth = data.values.enable_mtls_auth == "true"
#@ mtls_authorized_certs = data.values.mtls_authorized_certs
#@ enabled_middlewares = []

#@yaml/text-templated-strings
services:
speedtest-tracker:
#@ service = "speedtest-tracker"
labels:
- "backup-volume.stop-during-backup=true"
#! Services must opt-in to be proxied by Traefik:
- "traefik.enable=true"

#! 'router' is the fully qualified key in traefik for this router/service: project + instance + service
#@ router = "{}-{}-{}".format(project,instance,service)

#! The host matching router rule:
- "traefik.http.routers.(@= router @).rule=Host(`(@= traefik_host @)`)"
- "traefik.http.routers.(@= router @).entrypoints=websecure"

#@ enabled_middlewares.append("{}-ipallowlist".format(router))
- "traefik.http.middlewares.(@= router @)-ipallowlist.ipallowlist.sourcerange=(@= ip_sourcerange @)"
#@ if enable_http_auth:
#@ enabled_middlewares.append("{}-basicauth".format(router))
- "traefik.http.middlewares.(@= router @)-basicauth.basicauth.users=(@= http_auth @)"
- "traefik.http.middlewares.(@= router @)-basicauth.basicauth.headerField=X-Forwarded-User"
#@ end

#@ if enable_oauth2:
#@ enabled_middlewares.append("traefik-forward-auth@docker")
#@ enabled_middlewares.append("header-authorization-group-{}@file".format(authorized_group))
#@ end

#@ if enable_mtls_auth:
- "traefik.http.routers.(@= router @).tls.options=step_ca_mTLS@file"
#@ if len(mtls_authorized_certs):
- "traefik.http.middlewares.mtlsauth-(@= router @).plugin.certauthz.domains=(@= mtls_authorized_certs @)"
#@ enabled_middlewares.append("mtlsauth-{}".format(router))
#@ end
#@ enabled_middlewares.append("mtls-header@file")
#@ end

#! Override the default port that the app binds to:
#! You don't normally need to do this, as long as your image has
#! an EXPOSE directive in it, Traefik will autodetect it, but this is how you can override it:
#!- "traefik.http.services.(@= router @).loadbalancer.server.port=8080"

#! Apply all middlewares (do this at the end!)
- "traefik.http.routers.(@= router @).middlewares=(@= ','.join(enabled_middlewares) @)"
32 changes: 32 additions & 0 deletions speedtest-tracker/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
services:
speedtest-tracker:
image: ${SPEEDTEST_TRACKER_IMAGE}
restart: unless-stopped
#ports:
# - 8080:80
mcmikemn marked this conversation as resolved.
Show resolved Hide resolved
# - 8443:443
environment:
- PUID=${SPEEDTEST_TRACKER_UID}
- PGID=${SPEEDTEST_TRACKER_GID}
- APP_KEY=${SPEEDTEST_TRACKER_APP_KEY}
- DB_CONNECTION=sqlite
- ADMIN_NAME=${SPEEDTEST_TRACKER_ADMIN_NAME}
- ADMIN_EMAIL=${SPEEDTEST_TRACKER_ADMIN_EMAIL}
- ADMIN_PASSWORD=${SPEEDTEST_TRACKER_ADMIN_PASSWORD}
- DISPLAY_TIMEZONE=${SPEEDTEST_TRACKER_DISPLAY_TIMEZONE}
- APP_URL=${SPEEDTEST_TRACKER_TRAEFIK_HOST}
- ASSET_URL=${SPEEDTEST_TRACKER_TRAEFIK_HOST}
volumes:
- data:/config
- keys:/config/keys
mcmikemn marked this conversation as resolved.
Show resolved Hide resolved
#cap_drop:
# - ALL
#security_opt:
# - no-new-privileges:true
#sysctls:
# - net.ipv4.ip_unprivileged_port_start=1024
labels: []

volumes:
data:
keys:
1 change: 1 addition & 0 deletions speedtest-tracker/traefik-cert.pem
EnigmaCurry marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUdBekNDQk91Z0F3SUJBZ0lTQXpERjIzeVNmSU5uRno4UWF6S2dGVWNPTUEwR0NTcUdTSWIzRFFFQkN3VUEKTURNeEN6QUpCZ05WQkFZVEFsVlRNUll3RkFZRFZRUUtFdzFNWlhRbmN5QkZibU55ZVhCME1Rd3dDZ1lEVlFRRApFd05TTVRFd0hoY05NalF4TVRFd01UTTFPRE0wV2hjTk1qVXdNakE0TVRNMU9ETXpXakFhTVJnd0ZnWURWUVFECkV3OTBhR1YzYjI5emEyVjVjeTVqYjIwd2dnSWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUNEd0F3Z2dJS0FvSUMKQVFENlNEVHlnU040MlV0UFNJRG9wTVVzcjhCRlpESFp1bVRVVW03c21QUjc4d1lGa25RWi9mdkdRQmpwc3hEOQp4elhuaVZLL0VjVUpuWncyUytiQnMwZnFuUG9hRGg4VmdtQUFxY20wMU5icE5rOTAwUnYvM0dkRDJmNHVXRVB6CjVWY1FNdTBncGJIWHIvU2dwd2NXSmZya2RURitmWDBYeEd0MjlSazlSeTFUZEJ0UCtIYVFTQkZrVjZVL3EzelUKUXBLczl5MlROYytBRnBNMTFLK1JGV3RVUy9HdUFMdyt1c1ptakIrNVIxejdNOTlaeExmbFI4ekFoM2p4ZCtvdAp4MzJldUdhZUlybWUxV0hkRjVCWjdTSWxvZ2ZTcVN1SG9EWU1BVDEvbW9uUmE5OGtzcjhrNEI0UXZpNTAzSXBOCld1dllNcmhDaUlNRkdGVnBGQ1RvbjF0RUZpcTJqVSs1dTJuRHByUVBBYnloMG1nL3Mrd0xSQmxSeTNaWGdIM2cKcEtrUUM4ZVRpcDVTVkhmQ2pwRkZqeG9KbmVoaUhBWEVCcEVubmZ4NW1wK1dsalU4SVhVTDdHOW0zTGpSNklacAptTkZYczhGOHVEc2l5MzFSanlRQ2MyR1JoV2FPaFo2NWJ6ZWN5ZUdGdzcvaFVCeWZIYTJRdlNtU0draXBZSy9UCkdCOHhaYlpjWUlQK0xERXFoOVk3SVhVTXJmVmxGQWNDcGhFZHQ5eDdqRUJSZmJxN2gvaytsUFVMbms5amsveksKQ3Q3Q1FPcktaSkN0bVJYK2VFZXV0amY3TERCVmNhbmd1UkZvcWJpUzFGUU1pcjc5UlBKNkZjajN2S0R0SitiUgpxbWdCLzMxWnU1NVl1MWVENG1OZ3lSbE9oY2RhNlpDT2hJTDVGVFVha0tmbFlRSURBUUFCbzRJQ0tEQ0NBaVF3CkRnWURWUjBQQVFIL0JBUURBZ1dnTUIwR0ExVWRKUVFXTUJRR0NDc0dBUVVGQndNQkJnZ3JCZ0VGQlFjREFqQU0KQmdOVkhSTUJBZjhFQWpBQU1CMEdBMVVkRGdRV0JCU2Q4TWJqS1A1cG1ZRmdZWU5rOEVYOEQveUQ4ekFmQmdOVgpIU01FR0RBV2dCVEZ6MGFrNnZURHdIcHNsY1F0c0Y2U0x5Ymp1VEJYQmdnckJnRUZCUWNCQVFSTE1Fa3dJZ1lJCkt3WUJCUVVITUFHR0ZtaDBkSEE2THk5eU1URXVieTVzWlc1amNpNXZjbWN3SXdZSUt3WUJCUVVITUFLR0YyaDAKZEhBNkx5OXlNVEV1YVM1c1pXNWpjaTV2Y21jdk1DMEdBMVVkRVFRbU1DU0NFU291ZEdobGQyOXZjMnRsZVhNdQpZMjl0Z2c5MGFHVjNiMjl6YTJWNWN5NWpiMjB3RXdZRFZSMGdCQXd3Q2pBSUJnWm5nUXdCQWdFd2dnRUdCZ29yCkJnRUVBZFo1QWdRQ0JJSDNCSUgwQVBJQWR3Q2k0d3JrUmUrOXJadCtPTzFIWjNkVDE0SmJoSlRYSzE0YkxNUzUKVUtSSDV3QUFBWk1XbEJaOEFBQUVBd0JJTUVZQ0lRQzVNNFhFWnNmMUtnMy94Q3FvbWJBcHRMUUNEYUtXZGdDZApyY0hiUTVGU3h3SWhBTVJvTnNLTzdSeW9IVUp0TlZtblFXZ2hwWHJyYWNJRUh3cUs2cUh3aDdmQkFIY0FFMHJmCkdyV1lRZ2w0REcvdlRIcVJwQmEzSTBuT1dGZHEzNjdhcDhLcjRDSUFBQUdURnBRWFZ3QUFCQU1BU0RCR0FpRUEKcGdMbE14WGJzVThGdWdWdy9aK3dJRXNNcStRS1pveDZ5bkFqclNmVGNlUUNJUUN0Q2xkTmFNTnJjMWRVa1p2YgowbXNJMGxUNEJyMGd2UHlTMzhkMTAya2xlakFOQmdrcWhraUc5dzBCQVFzRkFBT0NBUUVBbnVITXM0NHlLRXZkCkJOa01ISDJIelM4OG0xYmVQRmNwa0l1U01NcmZ0bklFWlRpaUFydzNCYVpXSXJ5OTI2ZU8rZFhKMDlxaDV1NUgKT3hDTnNJU0VHK0RYSTVXVmc2SU1kM3BtQ1pFaDhmdUM0NEFJNXBBamlBeVM0NlJ5MnhEQkYzYUY2MCt2T3dFRQpZOVFpU0tXSktmYm00R2xmR0Nwbk43NTRuU2xsVFpTWlMyZ3NKbEFGRCs4ZWxUTkVGMnJMZXdkZGpLQ3VvbU8zCit1Y2VIQ2Zib2RrYnJmNGx5TDQvQlBaREpJRUQrRVdNMnBXN3phQ04wTDRQZThSVTZVbHRLbzViM0JVMXlUUUgKYTF4amFVSCtWUmNMWVhaTXlqUkdMYU9obmw3cVFoMzAxNlJ3UjV4RXBQTk9qVHBjSm9yVG96OWZrbXRHSEQ1bwp4UDRZVUZXRExBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQoKLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZCakNDQXU2Z0F3SUJBZ0lSQUlwOVBoUFdMekR2STRhOUtRZHJOUGd3RFFZSktvWklodmNOQVFFTEJRQXcKVHpFTE1Ba0dBMVVFQmhNQ1ZWTXhLVEFuQmdOVkJBb1RJRWx1ZEdWeWJtVjBJRk5sWTNWeWFYUjVJRkpsYzJWaApjbU5vSUVkeWIzVndNUlV3RXdZRFZRUURFd3hKVTFKSElGSnZiM1FnV0RFd0hoY05NalF3TXpFek1EQXdNREF3CldoY05NamN3TXpFeU1qTTFPVFU1V2pBek1Rc3dDUVlEVlFRR0V3SlZVekVXTUJRR0ExVUVDaE1OVEdWMEozTWcKUlc1amNubHdkREVNTUFvR0ExVUVBeE1EVWpFeE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQgpDZ0tDQVFFQXVvZThYQnNBT2N2S0NzM1VaeEQ1QVR5bFRxVmh5eWJLVXZzVkFiZTVLUFVvSHUwbnN5UVlPV2NKCkRBanM0RHF3TzNjT3ZmUGxPVlJCREU2dVFkYVpkTjVSMis5Ny8xaTlxTGNUOXQ0eDFmSnl5WEpxQzROMGxaeEcKQUdRVW1mT3gyU0xaemFpU3Fod21lai8rNzFnRmV3aVZnZHR4RDQ3NzR6RUp1d20rVUUxZmo1RjJQVnFkbm9QeQo2Y1JtcytFR1prTklHSUJsb0RjWW1wdUVNcGV4c3IzRStCVUFuU2VJKytKakY1WnNteWRuUzhUYktGNXB3bm53ClNWemdKRkRoeEx5aEJheDdRRzBBdE1KQlA2ZFl1Qy9GWEp1bHV3bWU4Zjdyc0lVNS9hZ0s3MFhFZU90bEtzTFAKWHp6ZTQxeE5HL2NMSnl1cUMwSjNVMDk1YWgySDJRSURBUUFCbzRINE1JSDFNQTRHQTFVZER3RUIvd1FFQXdJQgpoakFkQmdOVkhTVUVGakFVQmdnckJnRUZCUWNEQWdZSUt3WUJCUVVIQXdFd0VnWURWUjBUQVFIL0JBZ3dCZ0VCCi93SUJBREFkQmdOVkhRNEVGZ1FVeGM5R3BPcjB3OEI2YkpYRUxiQmVraThtNDdrd0h3WURWUjBqQkJnd0ZvQVUKZWJSWjVudTI1ZVFCYzRBSWlNZ2FXUGJwbTI0d01nWUlLd1lCQlFVSEFRRUVKakFrTUNJR0NDc0dBUVVGQnpBQwpoaFpvZEhSd09pOHZlREV1YVM1c1pXNWpjaTV2Y21jdk1CTUdBMVVkSUFRTU1Bb3dDQVlHWjRFTUFRSUJNQ2NHCkExVWRId1FnTUI0d0hLQWFvQmlHRm1oMGRIQTZMeTk0TVM1akxteGxibU55TG05eVp5OHdEUVlKS29aSWh2Y04KQVFFTEJRQURnZ0lCQUU3aWlWMEtBeHlRT05EMUgvbHhYUGpEajdJM2lIcHZzQ1VmN2I2MzJJWUdqdWtKaE0xeQp2NEh6L01yUFUwanR2ZlpwUXRTbEVUNDF5Qk95a2gwRlgrb3UxTmo0U2NPdDlabVduTzhtMk9HMEpBdElJRTM4CjAxUzBxY1loeU9FMkcvOTNaQ2tYdWZCTDcxM3F6WG5RdjVDL3ZpT3lrTnBLcVVneGRLbEVDK0hpOWkyRGNhUjEKZTlLVXdRVVpSaHk1ai9QRWRFZ2xLZzNsOWR0RDR0dVRtN2tadEI4djMyb09qekhUWXcrN0tkemRaaXcvc0J0bgpVZmhCUE9STnVheTRwSnhtWS9XcmhTTWR6Rk8ycTNHdTNNVUJjZG8yN2dvWUtqTDlDVEY4ai9aejU1eWN0VW9WCmFuZUNXcy9halVYK0h5cGtCVEErYzhMR0RMbldPMk5LcTBZRC9wbkFSa0FuWUdQZlVEb0hSOWdWU3AvcVJ4K1oKV2doaURMWnNNd2hOMXpqdFNDMHVCV2l1Z0YzdlROellJRUZmYVBHN1dzM2pEckFNTVllYlE5NUpRK0hJQkQvUgpQQnVIUlRCcHFLbHlEbmtTSERIWVBpTlgzYWRQb1BBY2dkRjNIMi9XMHJtb3N3TVdnVGxMbjFXdTBtcmtzNy9xCnBkV2ZTNlBKMWp0eTgwcjJWS3NNL0RqM1lJRGZialhLZGFGVTVDKzhiaGZKR3FVM3RhS2F1dXowd0hWR1QzZW8KNkZsV2tXWXRidDRwZ2RhbWx3VmVaRVcrTE03cVpFSkVzTU5QcmZDMDNBUEttWnNKZ3BXQ0RXT0tadmtaY3ZqVgp1WWtRNG9tWUNUWDVvaHkra25NamRPbWRIOWM3U3BxRVdCREM4NmZpTmV4K08wWE9NRVpTYThEQQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
Loading