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