Backups: backup to home server storage
Linting / YAML Lint (push) Successful in 41s
Linting / Ansible Lint (push) Failing after 1m4s

This commit is contained in:
2026-05-01 10:49:27 +03:00
parent 8efab2002f
commit df3a37e610
5 changed files with 286 additions and 184 deletions
+73 -23
View File
@@ -11,6 +11,7 @@ import sys
import subprocess import subprocess
import logging import logging
import pwd import pwd
import time
from abc import ABC from abc import ABC
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
@@ -53,7 +54,28 @@ class Application:
backup_targets: List[Path] backup_targets: List[Path]
@dataclass
class StorageRunResult:
name: str
success: bool
duration: float
def format_duration(seconds: float) -> str:
if seconds < 60:
return f"{seconds:.1f}s"
minutes = int(seconds // 60)
secs = int(seconds % 60)
if minutes < 60:
return f"{minutes}m{secs:02d}s"
hours = minutes // 60
minutes = minutes % 60
return f"{hours}h{minutes:02d}m{secs:02d}s"
class Storage(ABC): class Storage(ABC):
name: str
def backup(self, backup_dirs: List[str]) -> bool: def backup(self, backup_dirs: List[str]) -> bool:
"""Backup directories""" """Backup directories"""
raise NotImplementedError() raise NotImplementedError()
@@ -66,19 +88,15 @@ class ResticStorage(Storage):
self.name = name self.name = name
self.restic_repository = str(params.get("restic_repository", "")) self.restic_repository = str(params.get("restic_repository", ""))
self.restic_password = str(params.get("restic_password", "")) self.restic_password = str(params.get("restic_password", ""))
self.aws_access_key_id = str(params.get("aws_access_key_id", ""))
self.aws_secret_access_key = str(params.get("aws_secret_access_key", ""))
self.aws_default_region = str(params.get("aws_default_region", ""))
if not all( env_raw = params.get("env") or {}
[ if not isinstance(env_raw, dict):
self.restic_repository, raise ValueError(
self.restic_password, f"'env' must be a table for storage backend ResticStorage: '{self.name}'"
self.aws_access_key_id, )
self.aws_secret_access_key, self.env: Dict[str, str] = {str(k): str(v) for k, v in env_raw.items()}
self.aws_default_region,
] if not self.restic_repository or not self.restic_password:
):
raise ValueError( raise ValueError(
f"Missing storage configuration values for backend ResticStorage: '{self.name}'" f"Missing storage configuration values for backend ResticStorage: '{self.name}'"
) )
@@ -94,19 +112,13 @@ class ResticStorage(Storage):
return False return False
def __backup_internal(self, backup_dirs: List[str]) -> bool: def __backup_internal(self, backup_dirs: List[str]) -> bool:
logger.info("Starting restic backup") logger.info("Starting restic backup for storage '%s'", self.name)
logger.info("Destination: %s", self.restic_repository) logger.info("Destination: %s", self.restic_repository)
env = os.environ.copy() env = os.environ.copy()
env.update( env["RESTIC_REPOSITORY"] = self.restic_repository
{ env["RESTIC_PASSWORD"] = self.restic_password
"RESTIC_REPOSITORY": self.restic_repository, env.update(self.env)
"RESTIC_PASSWORD": self.restic_password,
"AWS_ACCESS_KEY_ID": self.aws_access_key_id,
"AWS_SECRET_ACCESS_KEY": self.aws_secret_access_key,
"AWS_DEFAULT_REGION": self.aws_default_region,
}
)
backup_cmd = ["restic", "backup", "--verbose"] + backup_dirs backup_cmd = ["restic", "backup", "--verbose"] + backup_dirs
result = subprocess.run(backup_cmd, env=env, capture_output=True, text=True) result = subprocess.run(backup_cmd, env=env, capture_output=True, text=True)
@@ -295,12 +307,15 @@ class BackupManager:
self.config = config self.config = config
self.storages = storages self.storages = storages
self.notifiers = notifiers self.notifiers = notifiers
self.archive_duration: float = 0.0
self.storage_results: List[StorageRunResult] = []
def run_backup_process(self, applications: List[Application]) -> bool: def run_backup_process(self, applications: List[Application]) -> bool:
"""Main backup process""" """Main backup process"""
logger.info("Starting backup process") logger.info("Starting backup process")
logger.info(f"Found {len(applications)} application directories") logger.info(f"Found {len(applications)} application directories")
archive_start = time.monotonic()
# Process each user's backup # Process each user's backup
for app in applications: for app in applications:
app_dir = str(app.path) app_dir = str(app.path)
@@ -316,6 +331,10 @@ class BackupManager:
continue continue
self._run_app_backup(str(app.backup_script), app_dir, username) self._run_app_backup(str(app.backup_script), app_dir, username)
self.archive_duration = time.monotonic() - archive_start
logger.info(
"Archive phase finished in %s", format_duration(self.archive_duration)
)
# Collect backup directories from applications # Collect backup directories from applications
backup_dirs: List[str] = [] backup_dirs: List[str] = []
@@ -328,10 +347,33 @@ class BackupManager:
overall_success = True overall_success = True
# Each storage is processed independently: a failure in one storage
# must not prevent the others from being attempted.
for storage in self.storages: for storage in self.storages:
storage_start = time.monotonic()
try:
backup_result = storage.backup(backup_dirs) backup_result = storage.backup(backup_dirs)
except Exception as exc: # noqa: BLE001
logger.error(
"Storage '%s' raised an unexpected error: %s", storage.name, exc
)
backup_result = False
storage_duration = time.monotonic() - storage_start
self.storage_results.append(
StorageRunResult(
name=storage.name,
success=backup_result,
duration=storage_duration,
)
)
logger.info(
"Storage '%s' finished in %s (success=%s)",
storage.name,
format_duration(storage_duration),
backup_result,
)
if not backup_result: if not backup_result:
self.errors.append("Restic backup failed") self.errors.append(f"Storage '{storage.name}' backup failed")
# Determine overall success # Determine overall success
overall_success = overall_success and backup_result overall_success = overall_success and backup_result
@@ -417,6 +459,14 @@ class BackupManager:
items = "".join(f"<li>{e}</li>" for e in self.errors) items = "".join(f"<li>{e}</li>" for e in self.errors)
message += f"<p>❌ Ошибки:</p><ul>{items}</ul>" message += f"<p>❌ Ошибки:</p><ul>{items}</ul>"
message += f"<p>⏱ Время архивации: {format_duration(self.archive_duration)}</p>"
if self.storage_results:
items = "".join(
f"<li>{'' if r.success else ''} {r.name}: {format_duration(r.duration)}</li>"
for r in self.storage_results
)
message += f"<p>⏱ Время записи в хранилища:</p><ul>{items}</ul>"
for notificator in self.notifiers: for notificator in self.notifiers:
try: try:
notificator.send(title, message) notificator.send(title, message)
+13 -3
View File
@@ -8,9 +8,19 @@ roots = [
type = "restic" type = "restic"
restic_repository = "{{ restic_repository }}" restic_repository = "{{ restic_repository }}"
restic_password = "{{ restic_password }}" restic_password = "{{ restic_password }}"
aws_access_key_id = "{{ restic_s3_access_key }}"
aws_secret_access_key = "{{ restic_s3_access_secret }}" [storage.yandex_cloud_s3.env]
aws_default_region = "{{ restic_s3_region }}" AWS_ACCESS_KEY_ID = "{{ restic_s3_access_key }}"
AWS_SECRET_ACCESS_KEY = "{{ restic_s3_access_secret }}"
AWS_DEFAULT_REGION = "{{ restic_s3_region }}"
[storage.pr86keedav]
type = "restic"
restic_repository = "{{ restic_pr86keedav_repository }}"
restic_password = "{{ restic_pr86keedav_password }}"
[storage.pr86keedav.env]
RCLONE_CONFIG = "{{ rclone_config_file }}"
[notifier.apprise] [notifier.apprise]
type = "apprise" type = "apprise"
+6
View File
@@ -0,0 +1,6 @@
[pr86keedav]
type = webdav
url = {{ rclone_pr86keedav_url }}
vendor = other
user = {{ rclone_pr86keedav_user }}
pass = {{ rclone_pr86keedav_password }}
+19
View File
@@ -10,6 +10,9 @@
backup_config_dir: "/etc/backup" backup_config_dir: "/etc/backup"
backup_config_file: "{{ (backup_config_dir, 'config.toml') | path_join }}" backup_config_file: "{{ (backup_config_dir, 'config.toml') | path_join }}"
rclone_config_dir: "/etc/rclone"
rclone_config_file: "{{ (rclone_config_dir, 'rclone.conf') | path_join }}"
restic_shell_script: "{{ (bin_prefix, 'restic-shell.sh') | path_join }}" restic_shell_script: "{{ (bin_prefix, 'restic-shell.sh') | path_join }}"
backup_all_script: "{{ (bin_prefix, 'backup-all.py') | path_join }}" backup_all_script: "{{ (bin_prefix, 'backup-all.py') | path_join }}"
@@ -22,6 +25,22 @@
group: root group: root
mode: "0755" mode: "0755"
- name: "Create rclone config directory"
ansible.builtin.file:
path: "{{ rclone_config_dir }}"
state: "directory"
owner: root
group: root
mode: "0755"
- name: "Create rclone config file"
ansible.builtin.template:
src: "files/backups/rclone.template.conf"
dest: "{{ rclone_config_file }}"
owner: root
group: root
mode: "0640"
- name: "Create backup config file" - name: "Create backup config file"
ansible.builtin.template: ansible.builtin.template:
src: "files/backups/config.template.toml" src: "files/backups/config.template.toml"
+174 -157
View File
@@ -1,158 +1,175 @@
$ANSIBLE_VAULT;1.1;AES256 $ANSIBLE_VAULT;1.1;AES256
39303531663233366261396235326638633861383835306130636239313035646134666236356333 30613937343031343632383733623435366535373231316163393436363636656462326262383565
3766646337396365636134653837373566333866333361360a323535626465383230366564336635 3032663665323131626263356531633934326639636231620a363635376263333438336331343366
63363031383731383238636330343337333563626462386461313066636130386565623061303733 36386337323165333861633062656433313062343764636138663533333639316336306230653732
6237316539363566640a613562613834383735313237363364613932323037633262636530336464 3331336137616263630a306135333566646434663231383138363966386661643836626561376338
30343764383036646138336364376566616231373864383538326666616365396432336239623161 33636362323937386664646630383062613535666431393634316337626564613733313861386238
65613831633838383135333163346531366337663966343764353335616236393134393635363062 39316263666662633066633836366236346431313531656339613566303962656165396662326563
61303166363136613734323937346461393133656162353231306532646535343233653135613961 65623036333932393739646162353836646562643866396263386232633933326538316637656365
31323033616666643062333337323736333730303939613761643661663036336464663161323663 38656562383861613030306635613236646235613436316635386531656666363738396461313263
31646663353865663032306332646364313230363765393666643661366465373734396262323264 30653934366537303133613962653137633131323431396266646339376339623034373963666438
35303137643435326130656264313164636461343163613230346362373035373538653432303232 66383464303431353962323032316533613138383831343036383230303931326433396333623935
64343935383531343434323135373766386362376262363165636265376634336132363061376132 37396432376637373135666236333332383262323931616432343665653836626265376632643765
33623762376337393937663738646238646365373766363839623239366261326465386463613139 33303835393863333334653664613337343063313362363136383234666335636565383237656639
35366166383965333639656333393034323930656132666535383661643630613266663063323437 34323839613765626231303230616661626530633530333165373535663139643339656438396237
64613865306465633833323462323366303164366563316236373531313637666533313136326132 36656134643636643733363336343739616532666130393863666665393138383261353730626565
62316364326430386336656564643434383934323037623436636633633834613236366364346133 38306133343463306462656534326431623238336562653433316233383861303032393437316336
38323034313735336434633632643965326231313961383465623737303932376366653264643364 32353338613639653735393239333235633565636563313933333763323339656237326162316465
61373032333463643262656364386138376532386537353730326131303238366634363262336632 66313034306263343462376632303539656533353265336366613338326439323732623438626162
65333436393739656532656263353037346166653661366661626235366132643633646238356462 39393937613836656236383030343436303632363330313734333665643365666138633034323462
37313230383031356535613766316136333239623239373932663536393964396638663561623365 66376333386237383666623434636662363338626538353933636632646236393630343739636666
62616561333734646335393664356638623362346230623931643865636165643736646562323231 37666531633839363365633863646530396432613166313035353638313463373338313139616133
37306665343135616362646237613933383862363664383666346532626332343065623432303434 62383234356665333132613664383931316238353863306538343831363233303862383737373939
33316631376430353566326434323164303834346663356265393066653033663536623062323839 37303430303766343366633536643139363366663734326162366434333165613033653666383337
39653130633463313465386432303234396562323334326631313564616633383866616564643537 62626538316463343466613065326666396266643661656164376336336532666134613663623163
63396262633336343066336335656136386661623762373262323062303761653139323537303465 64316335633839356231393130343938613334393737666663363662356466326235666561653239
39386436613432326539626536396631323766316566636135356132316465356635636262303237 39386635616165633063383032666366383861333038373636613663613461316433633562623664
31313864646563326166663163646563333133353162313339333431653233353861653730313836 65373536663230356632663133356639323838653431333836376330316162633261333934363335
66626566643266623739346536653537316263343865376436313364616335313135343432653537 34383937343063303835626435316534356239316230326566383036646237336238623036323161
65643733353934633030643236613134343336383033653465346461306235653631336637626437 62326264636130323965313866616631663039623431363139363462663435323866393437373566
34366666616330356133363337323734323733643034303236326438343766643331656331323961 37353463353731303434303435353061633531663464656336306439373238633038343237313133
30333034306461653134636565633938646535653037373733306336303037326261613238616339 34333463626261333038363438343034373335346332316430376436656331626664376664323037
39623961363233326533633236653437363766666263623933643036623465353934616262383065 32393631663434383265326231353035356333343739386132326435653438306136373237396539
61663566323766326663653731346537636262356364623338353562323832616661313634643565 33613462623562343966343933363037326234323836363636313938666534333337646139326533
37313537653535326233376137356663333266323063643361313734326662636565653330623965 61633666623936646366643336333339303633643230393465623031643963643635313264353236
63343961613034656461396639373266373666633732336231343836353234336563393531633735 65313631663430336262326463663938386630363464386230383766376363373235366438393635
38616239333930666634303362366364353338613437353331353030363064333237333161393433 65313232383334666263626662646264393565326164613364313138303638653333653963316561
32336131373935303033646161366138316338623334383434383131313236316539343937653961 34346464653637376433356335663930396432386238366132393562393162353235393438633533
62626333653531343238646430623635633534373333613837353964313336663831396439633133 62656533316431666463633530653832356263653030326366663932306662613465643638313633
31353162623938386366343738346433373032386434373165363131386663383432623532323363 62396562616463313066343832316238386234343537346436623039643132393562303130613331
62356630373566616436613136616238353163363634373530326262383164396433313564633737 38653261353132633036623138643338366534396237613333333765653436363032616235373035
38623432653337383961633365316366313664663132353766306636623035373830646566646432 35313966623531373636363638383862333935353931653861663966643531383335653739356565
65663038666232383566613534626466323765346464363461363335393032613363306464333738 31373937396234616135653765643131666530383030343064366531336135366265633232653433
35303535376335306266393666643063343831396533336562666661613934303062376237393637 65396566626232633831343734353432633462343336616135373861303836613463393736306133
30373563623935373733623864316539383830333663616534373230333132396232323266646538 64663531643630326432376235386433623365373163366663623632333531623863623663643434
66353539646530353832333065633266663664326566306262303966623662383635373362386535 36646134623665633531643732663137613862343666613139336231646564363266343935653263
39343333663138663438376338316536626662663930316532626232613231613063396539363364 66653366666635666535636637626134363633336233613732656166373063333237323465616434
31393966653966363831383261336336666662326630616364613563626434656334616539373262 39623238636235333866666536346430373735323530633133663937636366663530326465386161
61336533363532613335333365356634336334646266363762346362326533613734393136616639 39346466386133656633373438333133303566363233626238366133636333656462373065613863
65643139306464626662373565363033356439643933646138363537623563633966666130383732 65363439383163323332383931663833303234326132343462333835323664363461656566393065
64383337653361653731323965326566313132656563643264326130623334333237623632313137 38646261323336316239363465343238643132306235613031626438323838653066376561626661
65643234323464616537383637333766373161313862356364376466343231613039333539306561 34356530666665323230646436633935343861323638656638323163306236393865366630636236
30333032663761616237353962343266656330623765663039383864376236373838626361363961 62386161646131623738333664636361396239643666323837646332383538623734386531313664
32363430636337346134326337633166383236633937306435333838313636356437643361363063 65313632343365393130643137353735666565663030383231616231313237323866386336316361
33393337633939306365336130653761353461393531663734633831646261373832336532333136 66656165643261653464316639613635323531306362353164373531326461666437303434346233
65373666343636613737336466613933356130653965663036373136336231616263373966356666 31383864346233313633353065343236633636386138323761666662373564623234613965323131
34643838653532616134663530623963633563333531323939393264393839636263356230313966 36313861316563333262306434663265313237626631396561303236343330633738356666633663
66363666643537346130643930373730373062303836373632376533383433366432323633343863 61313663336237653361383963333764336137396666613634313036373564353564643334623363
37326162336262633635666131303039643561663939633430613936666431646633353762306234 62353531306532323664376363383938646536393339346666656339393230613362666337663861
35303130353138626462646431616161643930653165636531333861623066653265343239623936 37633633653463343430666634643863383438633933343839663865616136363538643061343437
35303238373562386463373266653631373266333961633430663931343262313234376266303931 34613037353835613866303230303162396531626663616164343263633261363335313936666339
32306362626563373133376161313633343438653438383433656533623861313365373831623463 63383533616530356262363838636466333038656339316364626263383731313464313734613630
34613362646432643631323232313236366666386334306237656535623465363134326566303535 62646266666136616632636161363631623362346230643134663664396565323932343462383661
39313836396231653131376538316263633463336263373839663464356531383266313439326262 38303663653262333236613833396237663834333139316666343065396137306562613265343863
38356638333863303066383330653135643130613933643236383164623935373138323662616134 65323065663862636230636664623132306231366462346432343030376236346465663831623537
63306562646366393865623462313633323136363461623461373136303665343839366233633238 63633231333165613731626137656539366131633364623661616136616434306563656139346137
63363833356131643364653338666461323365396634373030663939646163353532663365393834 63313032343161623235306230633361666163623061333738383135636664623438323238663631
63336230373134633136626137623939333566316238623337623033333939353835653463663033 37613964643931323432353431306564393639386437666539376238643065343738313265373661
61623937623335613236323233633661363565383232633239393137323533633938353537643733 61303764646463326632653335323432646436353765633862623838386337623464333839643833
31386530653837653763333237653763633962396237666662343534346661653730623664626638 39383961666234363638323735636231623962666461373435633631323530643237656464396465
64373963383065333138656231393466366333666631383334326164353638323032383430656165 66623431393461613634373237646636333965396435663563363161626666356638366462373261
30356534376261346233346630303863343739663035383637396337313638313436653138626261 37633238323135666136623663653665303832656437663536383236313334313461353032663933
38626233346639376534313563373563306266646336633861343466613761306438306532333138 63643164363664663939613635373362376162336262653332663936313737396130366330656532
64656365363634393733623965396237353164383031336634316634313334616338376165666233 31653463383132643262613839613962663836376463343661393736633633396164643264653431
65623835376431336162663839373264313534626564356561646635316537656637623430326635 30663732303236653165386537653432656266363239373030333630353661666636303730373937
62353661636534646130613134306563646233323131396363356663623865626164393865623931 65363237366333376133306437376534636133356238326461333762326563386265363636323831
30343534306361316536653633353162653965666437353361666662663732616433613838633733 39343665386262336265383865343563343832623766656534306661326462333561373835366631
61386564306266376361343335333132313235633535313164373734623934623930353137396331 39636361623831623533353962633363393531313530363833613962616331653565633733303964
61333562616139373437393638363865653961626330653738306564393939383262346630663162 32393433303938323566646264323761633035653231353761643261663839313665663434643834
39633961343132663464636637373335303834363663353266616332393335626535646335633138 65356432393431336235306437643861653437643362363839623634333835376636623664616139
31326630366437323931656566656462366433393235353735346538323933613639636539353063 66376562633232636431626436653161333137633466313433663433383230636337653535643430
63336436616333323238386537343362616634653266656536323235633366326561613365393230 61613032656135323765613837626266313632353661346636643866613138303930346563623738
39623034303239316663326466393032616561656534653533333565646661663936366231356361 35613831623565353432336338373465303437623234313736353661353430656661366365373230
33306130313630313333313137356639316430623636386464623566353338653763663532323630 33646134356661616164303865623464306339653439613365626261323237623135346537393535
61623165316132666238383935306362306230353764373639643064396163363434313830323539 62393465343134626333333462316331656134383362383031353863316632393061333933336362
65323036656461613137316264663032616562353235313466613436316233316462663062656234 36326662363833303436663166383365346433323866346462663261333330656666663162383564
61313163623937663734366231363938393932623531343533306532616235336262366534393962 35336438643064313833393638323864343237616163383033313966303262326135323335353931
64376436626466646631363262303938636538306431366335616363333738623235626263336264 66333938393264323533353231303935346661653835386262306133393065356535643835663665
61626135393336663730303362383039646161653461666439313237653262326538376466366233 38363930356530366135313734306464623739376438613430373634396339393864396264303135
34316430356335626334613736386337333634336366393031336433306537303538306663323665 61356333636236326566386264353930626564636438616265353939383733663837313233356363
61373462643263636164656339633432656337646533653732393262633662653330326437353836 63643835393437336366313030303864306536666638623430356263336234646462383666316431
38613964663261316161343166333734386134346137363437343739373132643962323563623736 36313464346266646438383762313138376338323537386635636561656662306533316362396162
35666135313165313564356162623430393934313834376538613635396465373936393063303038 38326165633532623933376165643861323735353831363264376162316561613038633961333337
31623633633065393766653830393735306438306563323139303835623537323032633438333935 30646461636332623466643033633764333330353832616365376633643263336131313733653139
30653363313963366661323865656531613836666533656537346330646531373864333638306138 39646239366261366465333962643565636430393464613866613038333636393362383636343534
66376534363734353664393163353933353861356433656233653862353666323039623166303065 61323830616234633364346131336630393965373730343464366166376232346464636263323639
35356539616432613435333831663834623162326336613663343833613961613361643639613239 63336464623733363139366665336131653163613833383261376138373032666663356637383832
38346230666362376364333664343438336361393430646437653161663235326533383962303833 32313130633363346435383638616236633761616166663339316437353938636636613530383836
63636135656265333230333863626566373536663439306134363165313332316562336362333666 64623661366130656439306266343435396334383564353466663339383862313733313931383463
35653361383666666136393431326438613233356261663639376634643537313435616433663165 64323237656361383262343735366562623965356636343963363966616333313333646233373464
64386662653733376337613339343163346664386465663835643534383132316562346639303030 33383939386262663730316333616663636161356463396362643237356532386162363131626461
33383736323639643334633733396535326131663230643364636436326333316335333333626634 37323965313063623463356133626531393339336535303562343530316663613639646531323136
62306137636563306132613239306236383030646163343133353538356537316436373266303032 30353732646237623264653963373863363965326338666264306562373932393333633639396131
39343735663634653963303161383434363632333433373335313438383734653036656434343165 34303764396330326165636264313532393961303038623031336631653831323337306261333630
34363233373238623166396634303038346663633066643466383831373664376139313161616137 37333964376533636132303335653935343932373330373632626235356437636165623436383036
65386263653766323330363736396334656230636332383166666663633538616165306262383630 38313565373561393834316532333930356135623439373161643063643738353031353565396330
35346434626432353536383136353761376139373865356663656333623636623166303863643463 37656162346433326638353439613666336534336562623633643230636134383931653538616665
34623830636164353362396532346538373139643730633065326238663938653536643437383333 32393432383265613237323138386361353934373965306462393666616532653563626232643035
35303336656264303733333832356138306464393934343738373932643935346130356532356266 61643732376434633537633663633130313437656166333239633533393334373163333566343430
30353736356430313965376537303033666665646339353235373030383763643231616332616162 38633165353637306237316436663235633162353132646562353638333038663636323465633632
38306333383562353830303435323362386435616439313561623032383464373431393033313035 34643037623634643534663366633133363030323966313065353333633636646636306565333238
61363832383030636635626463616430323063313566633966396130633935633935373135353134 39336662626138306464613461343762316533656433626165323764616535623539336439396663
39663038653338656161336138623662326536306264616265366435373861656230623938623361 63393365626235613063613934306132333162646237316364306637346136623061363236383765
64623064356264383462376636333665613739663138663961396462626137646561326162616563 37353138363337346530626563366136333635663863313038643537366237633362343136396664
33626239663230366664646631656636383931653938663964313235663264373337353531336664 61623237353433333238633163636565386134356565303763336238636366316330666339383365
66626464306161353731366563326132613063626432343839623734373364336363313864666465 30323235356633656362353738393234616435663333613364316539636430623262643162313337
30333966383733303036303536333234376233363962646430646630383739346638383464373633 36323466303832336530336566343731306362333862663537613339663562623739343636613162
61343731663865646537306362316261623736376161613963306664316462643334616138343435 36343563373665376565366266343461643562636630623166626165636337613931653338633862
38373035396230376534653438346432303361393330363131633537656635656563323730383931 65393138353661656265666335343263333063653430326532663839383433643966363639643636
63336438623035663834343237656131656664343636383137373063623565313731336661663065 61376365363538636235666235623638376334363265626136313536353637386564303936636263
66366663326233353936323361306361623064656637373835616431386137323762313766613134 32306239306339656238393864666135613663366332666135663461353366313833376430376263
34393332623733313239383634396333363030323065623138383035656563326138343063383265 62613163303964333735396338373737653837666435656130376435376434356462383264636561
33346433623639386361383866333736353934303464616334643837356432666138386634396632 34353563316132336663316166663832383939333634316562383634383838336531313731613666
37346335333734343034333664326536396335666334323633623437663339313931313132336663 62643231636266353935343539366465376139643834306261623738313432306133653461383738
63346563306430643632363266353965663338366136366666393235323863333762376630313630 39396332373364353833626661333634346131396337636235653431616336393666373231383030
37653961336366656638393233393362313961656437336233333734306364383632313432303434 32306466613136346265653038636537646330643337663863383562323638616661333037323232
63346138653130643432393062663330333161306664613933333838626163646230336463326162 35613138363330353533643064613366343339343032373737306364353135353334336666663732
35663334666534643132396639383932613764663362623634326331656262323763363061643239 36343963613636376561666266623537316432666161326331383761323437383738373762643937
66666134353135303734373238626433306164363338386237306465353566656235346664386433 30623737643239326261343939663065643265653363633661376265626637643336613635393335
65623833353931626634333631396237373366303163336562626337353939383463366461643534 65373565333936333431656331633039323135336236656337343532643939386338663239393065
66663432313134343436313362396566313462316662353336363733346339353965656463613863 65666536333732646235633762633032393463663334616165333834653938346230316236353839
39353635363436363861386464303565613663636365383732666261353334653965646262326231 34396362386265646261373561636230363962663433303535373035346334353932643365383763
63316661363231626164343463353661376561313766303531316134396362613337363836623532 32333239613961346466356562376663613062373162666264633636323833323263333765616563
32356235356463353564316334303864323637623137353130356139633831346430303262646539 39646530343962353362363634336336323463623137646531373362353832343335366461646535
66643733396432643532373162363735346563343133663064613565666234313230356139663062 38653735316536396438613866326438363036653833366636626130323437623366373833366165
34646661306461336339643239333536393861646130613633653366616434633032363566633465 30303636666263323062343931306435363961643838636163366433376436303231316338613034
66653332336464653238373165656163636538653966656230643732653665363437653665663162 37393631363632383461373566306365306631396335633432383939336332626237653462393136
65636533303038386537336632636339613030386466346633636338386433653434633435656639 34316636643464363634366535333463326533333564633163363062666463343731396231656234
62383765393966316539646661306666393762623836353730663263663331396337373837643138 39346333303465363037313063373366373439306333636465636366666437326362626264653033
34343166393061653036316336306564386530616339626465393332316237306534663965376631 64613062343538303931646630373565663530336133633032366331626536353237336235633636
37653563643931353436323264306236393561366465383730653135333161656634343565343965 63366639366439386530303966323563323862383865356630313636333333393464653762626634
32323839626562626335323431636135616230343833386130383138356663393735626165386231 65366231613661313233626239303035323666346236636362393036353839333636343434646266
63396461373231623534393665613339656165353665616232613831333561656530623036663339 65653039353966616361363335346565383863616161316134383365616636333732653233383261
38373564373966363366626439343539343236373665383863363861323539373237613463333835 65616664343830353861616666616237313532363334653430313437313535666436383338396363
39656530613562643666333933633236373666316135303339303438333064363433396631313635 33313436363061306431366332373936633034393733646137636338336431333033343532613531
32346461636366333737656637623161373763656164366136323136313739353937333764316333 32613839393232646565663931303530376432376337613762346230646366613935383234313666
62633565316335393530306630623935663865366133366331346665353666343434323862383933 61643339353933336434666466623133336637343534303737366162316561366632333335663233
61623139643163626139346566323339323363363330636336313865383431316238363738623237 34643036326630306632353438643666623939393033646238353261386231626634303266303530
37303333323661333734356261303736326365333163626434383162306266646136353537376131 64643436653234616332623835333165626135613465346162393335353133356233666536313632
31633636363335653037323733333036346637613536363739393836613937383062386334623736 65616135666533343839666132623639343565303436623162383738353633613864356535646365
34326463643739653163366136636266333662646630313830633134376231303162356537336364 32373337393936393830666365383462333437373539666633386361373135333163393334303235
65313836383135323063316435353038333833643365323364623235323931373166363532376538 33663631386566356366666132616265373533373561616564343538303432346562356234336663
37356131313630623438343364356430626232353839653937366138363865653762366134646438 32623866396434326264636539323132613239343938353739376539383139313833376563623434
39373263313961376334316334343336363237356232623364373439623033336635656361366438 62636334326234313230666662396561393130396137306437393334323561356435343866386636
37333435333464663039613363623938653465373864373339653662613333633165386364373839 32656337656439653830653365313031326562643437376538316561653963643232353434313538
63623033326637303666303833393034373033633032373837336463303166613365373330333239 64373638616133666463393462643465306565646136643862363162643638343565316139626539
61306535656334633235363865333432326531376638366332356163376637356635393065643463 64643939383936313035323936656438313039376635383733633032613165343130663930323166
30666438323766663564373633666565636338333930353965663365313062636466653232666632 37343261333332663863366533386335373962323163616564376434636361356438393035656533
6163 30383139323931306232353664636662313036643431663536353035356139643761613235663837
37613133363433356536316466343237613131386536356234343135323861396130663464323236
63636563633031396465663563366263373938373531336239323138653531386535643332653736
61396432356161643663623130656632633862333861656464613432623732656465376236313437
65386630633036636663303633636134343739366562643062343030383138653466326636366266
32633239343039633636313837643432333238366533393061646237626130303934356438633936
38366265656365333338363431643432633463313438633361333764653637623964363732303737
61343137653930353361653364656233343166633162313964306531383834356237343031396137
34366135623530366164646532643636346233353563333031343931643037613463613639356238
36306235336562333935643035313934366339623365616661616461653832336137336464393662
38363433646139646633353162616661323433636531393339643562373538616430363061366330
35333138613136323865346462653761666534343538313033663835653631363631623532663133
64383135326333626438363066633366316364643332623030653230353861633837646362626333
38363236616265313638626263316164323563616237653465353031353734333032323761393761
31333331643161396338653330653537353634306139656536363665643437633433666236356334
64386566623836306666653766626465646664303231613062663862613565393364303233333636
61633631643437373235636133333832646463366633353939383834373362633539333766303661
3132333365333061633665366432346636646564313437333061