1
0

Compare commits

...

46 Commits

Author SHA1 Message Date
1d5ce38922 Wakapi: upgrade to 2.15.0 2025-09-07 11:16:30 +03:00
0b9e66f067 Authelia: upgrade to 4.39.8 2025-09-05 09:29:39 +03:00
379a113b86 Dozzle: upgrade to 8.13.12 2025-09-05 09:29:22 +03:00
8538c00175 Outline: upgrade to 0.87.3 2025-09-05 09:29:05 +03:00
645276018b Authelia: upgrade to 4.39.7 2025-09-01 09:30:59 +03:00
ce5d682842 Dozzle: upgrade to 8.13.11 2025-09-01 09:30:35 +03:00
de5b0f66bd Gramps: upgrade to 25.8.0 2025-09-01 09:30:18 +03:00
64602b1db3 Outline: upgrade to 0.87.0 2025-09-01 09:29:55 +03:00
caecb9b57e Caddy: upgrade to 2.10.2 2025-08-29 08:31:52 +03:00
e8be04d5e1 Dozzle: upgrade to 8.13.10 2025-08-29 08:31:32 +03:00
a7f90da43f Netdata: upgrade to 2.6.3 2025-08-25 09:11:55 +03:00
0f80206c62 Gitea: upgrade to 1.24.5 2025-08-16 08:49:35 +03:00
1daff82cc5 Dozzle: upgrade to 8.13.9 2025-08-16 08:49:16 +03:00
9b4293c624 Add transcriber app 2025-08-14 15:13:29 +03:00
0d93e8094c Authelia: more strict policy 2025-08-13 19:21:14 +03:00
b92ab556e5 Dozzle: fix hostname 2025-08-13 19:21:00 +03:00
8086799c7b Dozzle: install version 8.13.8 2025-08-13 19:08:46 +03:00
6ec5df4b66 Netdata: upgrade to 2.6.2 2025-08-13 18:49:18 +03:00
fb91e45806 Outline: upgrade to 0.86.1 2025-08-11 08:15:06 +03:00
44f82434e7 Authelia: upgrade to 4.39.6 2025-08-11 08:14:37 +03:00
31ca27750e Docker: remove unnecessary call
Login to yandex registry only need in app playbooks
2025-08-07 15:46:39 +03:00
4be8d297ba Authelia: move secrets to separate file 2025-08-07 15:07:51 +03:00
bcd8e62691 Backups: rewrite backup script
To avoid specifying individual applications
2025-08-07 12:06:07 +03:00
160f4219c5 RSS-Bridge: upgrade to 2025-08-05 2025-08-07 09:57:05 +03:00
c518125bbd Gitea: upgrade to 1.24.4 2025-08-07 09:55:17 +03:00
e16e23d18c Outline: upgrade to 0.86.0 2025-08-07 09:52:44 +03:00
ede37e7fa3 Miniflux: add restart policy 2025-08-04 09:50:43 +03:00
b4cddb337a Miniflux: run postgres as app user 2025-08-04 09:15:37 +03:00
35f1abd718 Miniflux: change secret storage from env to files 2025-08-04 08:10:07 +03:00
21b52a1887 Secrets: add role for secret deploy 2025-08-04 08:06:58 +03:00
af39ca9de8 Security fixes: backups 2025-08-03 15:13:26 +03:00
e10b37a9f6 Secrets: remove unused variables 2025-08-03 15:03:33 +03:00
85627f8931 Authelia: protect secret files
Word "secrets" activate pre-commit hook
2025-08-03 11:11:50 +03:00
38e2294a65 Security fixes: telegram 2025-08-03 11:06:59 +03:00
dfcb781a90 Security fixes: S3 2025-08-03 11:04:33 +03:00
2c8bd2bb8d Security fixes: postbox 2025-08-03 10:42:35 +03:00
592c3a062b Postbox: refactor smtp tools 2025-08-01 14:04:56 +03:00
04d789f0a4 Secrets: remove unused 2025-08-01 13:43:24 +03:00
16719977c8 Add gitleaks and custom script to check secrets in commits
Additionally add lefthook to manage git hooks
2025-08-01 13:31:44 +03:00
6cd8d3b14b Netdata: add monitoring for postgresql databases 2025-08-01 10:58:07 +03:00
791caab704 Add tasks to clear docker objects 2025-07-20 11:51:10 +03:00
df60296f9d Gitea: upgrade to 1.24.3 2025-07-20 11:35:26 +03:00
232d06a123 Gramps: upgrade to 25.7.2 2025-07-20 11:32:28 +03:00
a3886c8675 Wakapi: upgrade to 2.14.1 2025-07-20 11:31:59 +03:00
db55fcd180 Authelia: upgrade to 4.39.5 2025-07-20 11:31:10 +03:00
53dd462cac Netdata: upgrade to 2.6.0 2025-07-20 11:30:05 +03:00
41 changed files with 2898 additions and 3973 deletions

2
.gitignore vendored
View File

@@ -7,5 +7,3 @@
/ansible-vault-password-file
/temp
*.retry
test_smtp.py

View File

@@ -46,8 +46,9 @@ tasks:
- >
ansible localhost
--module-name template
--args "src=files/authelia/configuration.yml.j2 dest={{.DEST_FILE}}"
--args "src=files/authelia/configuration.template.yml dest={{.DEST_FILE}}"
--extra-vars "@vars/secrets.yml"
--extra-vars "@files/authelia/secrets.yml"
- defer: rm -f {{.DEST_FILE}}
- >
{{.AUTHELIA_DOCKER}}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -2,14 +2,14 @@ services:
authelia_app:
container_name: 'authelia_app'
image: 'docker.io/authelia/authelia:4.39.4'
image: 'docker.io/authelia/authelia:4.39.8'
user: '{{ user_create_result.uid }}:{{ user_create_result.group }}'
restart: 'unless-stopped'
networks:
- "{{ web_proxy_network }}"
- "web_proxy_network"
volumes:
- "{{ config_dir }}:/config"
networks:
{{ web_proxy_network }}:
web_proxy_network:
external: true

136
files/authelia/secrets.yml Normal file
View File

@@ -0,0 +1,136 @@
$ANSIBLE_VAULT;1.1;AES256
37373465363866623436393966626530656465653837363463323664383666663164363233623738
3233383234343332623065386134643161346132653431350a303935373631656366633339663333
32353263346437626633346263323533313238613462613334353334643236343438306630333037
6435313930313262310a386662336637623461303636633337303531353261343861313966383764
32353439333364353434653164666434326232383562363063313433373137383138396266383134
36613538653531346232353236313262313138656234626638623034363436303337313961333536
66366666383363333439333439623931626662383764393463663733333034636633353538656137
62386263613533343963396166666532313862366433636536613266353064633932323765336362
34643634643962333563346633306665313765393663306364363362333536646635343832333634
30383361653063396333616433323235663338346439303465323135626639646166303164643339
31616534633034393339373934346531633433323433646436333863306566356462613531663136
61343561616434306163616130626338663737633866646537323263316636626137366361363963
31303361366365616335363230343239663038623830303232376236393639663232333764643064
34666439316430356664313531333363626562633636326463313765343263393636333465386339
33623037343134633535303863626564373630656463336330396336303462373735346331616663
63666161356565643539343431386231396162323030383836366161303634626266663934356362
61623833613734333661613338373663663230363331373236323166636534613962613763343663
31666534303965333466653335646263343764346465373461326166666266303138363933653566
37636530306632346636626336616536346236663664383864623863653835366133633635613861
63303634333962343039646564353534313063383434386462366333386331303433366665623734
61353039313762383664626330663230656237373061616132376564323763393632356665306633
34643865333165616664376162306634366532386437383461396163376366363832363834356164
64376637373135383539353636346461353761366561303530326363366238393932333039313264
34316539626365306461323336396631633532306637306331373863613531656565366236656338
30306237626561613561353265643137353965313033313939643161613163643566663632663964
61623134656238363134626530363933623930346532336366393163363562386265626233393139
63633333313531666335376538613765663933626533636137306564616333373766613665613332
65643331626361626136623432346233633364343963653932306632646436626433653337326665
39343033353030616630663865613630613032333831626538323461383264636633623439393765
65343866323237386336613764386439313830646239613135636161333138646664666339626137
36616433393339346139323333363030613731313236636464393864616135346234643664343533
31396661306664343031393865306533373762663962623730313261353231363661306134623934
64613931356164386431663536363361386566353361333861666365636564643536306638376238
37633865303063643962346664346366346362313463386432376637663934363165343537323532
63363431623663656163316662343435636165306134373839613731326139636337343862326338
61326433343631343065303735663434316330303139303834316137663330363762666664393062
30653236636538396234313735613365386635323062666236656164633136313362643834336339
32623834343334613839313138313462376237666238636663343333643533643537376261666433
64323933323134393461623034623563316135333566326135326434613237363830623063626535
66323533356366346130626530633337393263663664353430646330353339313534396434653137
36663737396261653162663337663338373433653233616363626130333833323533303363643730
66613135633761346433626164643130353963313762643361306537653639373934333565373439
31636133383866373032373562653933316163353936386339326266363233663633623437346665
66393630346434356563393039626537336537313930393437663562303031626338616266363361
65653033363539633364326531653563383634303830386362303665306438663035373831646562
30356564623733623939663332393463663730643533666134636361316263633166626566333831
66376461383139626230343136373437393464646331633139633435303236623132343035373037
39363131623330376262326235646633303232623139626239326361313236316665316464616265
35303166333561626130323864363430663332306338353731383139386131346132653632633132
37643865393462623831623435633838323664666264623232326561653866626437373864666232
64393466326162323236353539326364336238643031313434346566316434383733663663356334
62343337386532393236353432653239643735323531306337373739343839306264356666636635
35626665646634653766323939633434303238633564613962643364356631623539623032363039
31336535333763323236633531616661313834636231363362376661313931623131343364356364
35653539393265323636303930633639316139643631386632646139643266303531653865623664
31303930613561323330356337396138633033616265356137353336613638656161633063663964
33373965383532656634333863643131333461376135646635323035316230393439386130663036
37373331356364343433316435666130373031303038643063313131653835333365366138656238
38643437376234316332306434633039346564643863656461353364346335353839303734366565
32613364356532623231623632363637373664393764636262346264383134366439373238623032
62326163306532356262393565373937316530623963313266373736356632313831313465666663
61303962333836373832383236663532376130316465393039326366636133323233316134646430
63313437663662353962633561643535396332303533343962643038393165373239336431336664
65356663393565383263613530663762643731356463653538316439303863643363303261363838
33353739306337326665393164366232393665363465343537373866396136346164333663393738
31316335366238316537386236393461656266323566636364343139393665616138663432333564
37653837646666376530373530636164343633653162343131373034633432333138613138346339
32303332626338653561323835343266353633613434346465616162326162393733643837663230
62333630333464633362656661336139393639623863343036636534323637336561333734373262
36373365306531383830383361303566626239343062316166303636323539373966626336613638
31623863643632653036323834346362663834666431643637666137376139386666643834323465
63626264643337376663633335666631653637626364653866353131393336303937633430366430
38383066313831346461643862333838386566613661623130313038386137323331373434363033
34613537303134343532643430383532353934623066383530653435626566333239333162316435
30386361346336666665656336646633353663376337326131663435636533646162616332306530
30373263376437646639623039343234393537303931346461643966383732336366363331643135
65383462353034643464366334636136373035313437366639336338366133663765313735353366
62386239326134343761313464383239316465633932363862303536353365376338643863643834
32633233336262626336383061316137646431353766303930336562386136383530613538383837
66336235643437636138646663393565383466636232366133343232306563363635316561346565
36633637303163303963396132653731663134666238363939666663303033316564356364633162
31376233306138666131333634336639643163666562653934336162323964613863646564616361
33666264303163326664633839303562333664383130356134383836313635376239353137313363
66613132663137383737373530326131663861303935663635373464306334363962333566663261
64616361366137626163626139333630653331383763353632396130306231376662666363353962
66343064313932386631303663373432646135353438313632316634336235316139313237666362
31383638393362663038363765303634363366326265643332393165643635643339343137373930
37633031626365353033333938623466663963376366353561303166373164386132376365643630
32333134316464643564373537643734353534366563666435663663616331383039393862613838
63633962316533386337366263636265646334636235323430383832363964343939633264343338
64366539383831373636343330356537323662666533323935646634363466663239663362326531
66363863383762303539373636336330353834303239376330623964393439356130646166663332
65326162336366363466646230656362653531386162356235326235333866373966636434326537
38653139393563373337346636333337373039343439343139313366316264613763663664633037
32313237653239643635393363316465346561613331623033323137653865396239633639323534
61326262643365363737393031383461626530636266393836663937666135363662353665376362
63643039373931316439303731663762393237623065643236303737643966393836646335626132
36376665656662373437653933356330636638626162666564393636633630663562303839326662
35626261616534386361373539633636356136616137323737393466633364653730356138343638
36663537663361393566616365383161366236646630653737643765666638346531376136643163
37643530373330353238323431343761653633306464643835343333623837313135303031666535
30326538636432303363666131376334393361333232313834623230306630373834633265333237
34323731333835363863643031346166636464653731636636313161643265613861336638313338
32383438353763343933383537636464666466386131363566306562333136356538326239656232
33343631613134616265393232613063643561633335323665383133313536313364343066366665
63636439376436393162306638303062616435333039343566613961626434303766616535623364
32643866393430303137366264306262643365663034613965666332313430366630653736303537
39663832326132393066373166613161613130363033363633366563353461663435393565326362
37616462393933303937643664343663373234663066633834626164303866323835363333366266
36356133336165613032646436306162663534306239313330353935336332643637653534626233
38303965646361316434343131653461353234396163613736333235656639326231353734636266
61613566336437666265366637336363326266383666383165343661333766303830633633393664
34613061396564616337643032666561633038663062336233666263306132663139396565323035
33353438633338363263313630393239376162366461383265386633613939663461616233396334
35616433663862616530636362396333343464393339396538333861303763393066626439396361
32303732393062383662633937653531653933333463366638613035333832636235346233653866
35656664636636326163353439626538343463613465613634656530366566323165623162303565
31303139343138616132383731323061346431336133643735356532373838663761313139663361
33656365636261303532333131346633373732643232303139353431663132346532616334613034
30363137613133396335343162643936623330393834356365663932626262313366616534663033
37376132656233633361623733356334636266383361656437613331306636656333623139303661
38353639346266333833663533366661633136313262396465633738373438623262306637643336
34656136343139663461336264346666333537633065343766316630626566363761396537643334
30323766633664666639363965363138396334343365346333663035323839613030626533303830
31333734386565383831373939306265636432386332313531623638333663643162623339613366
34363935636266313736366639373833636230633661323935646331376336623937353039343561
39623865663462663431643738653663663733663765383663623437383163613232336332653531
64663133353934313436336633666435343162316135303663636130353936363936363032313263
65376436316237663434323736663263376164346139616465663737323963316361373438633339
31323261343635633338613636643232616537653331326331353161396331633461643861323466
64633033623537386263376263346666633939336133616234363964363339616331636464326163
63633862373030323132613439343431333938343864383637613435323732356234613965666364
37343765353735633737393664306533633262353562323565306537646534663833343430643662
39326134353335653938396532363136376332306162613836663464636233383436333735663731
313461396466396230323561646662653063

View File

@@ -1,37 +0,0 @@
#!/usr/bin/env bash
set -eu
set -o pipefail
echo "Backup: perform gitea backup"
su --login gitea --command '/home/gitea/backup.sh'
echo "Backup: perform outline backup"
su --login outline --command '/home/outline/backup.sh'
echo "Backup: perform gramps backup"
su --login gramps --command '/home/gramps/backup.sh'
echo "Backup: perform miniflux backup"
su --login miniflux --command '/home/miniflux/backup.sh'
echo "Backup: perform wakapi backup"
su --login wakapi --command '/home/wakapi/backup.sh'
echo "Backup: send backups to remote storage with retic"
restic-shell.sh backup --verbose /home/gitea/backups /home/outline/backups /home/gramps/backups /home/miniflux/backups /home/wakapi/backups \
&& restic-shell.sh check \
&& restic-shell.sh forget --compact --prune --keep-daily 90 --keep-monthly 36 \
&& restic-shell.sh check
echo "Backup: send notification"
curl -s -X POST 'https://api.telegram.org/bot{{ notifications_tg_bot_token }}/sendMessage' \
-d 'chat_id={{ notifications_tg_chat_id }}' \
-d 'parse_mode=HTML' \
-d 'text=<b>{{ notifications_name }}</b>: бекап успешно завершен!'
echo -e "\nBackup: done"

View File

@@ -0,0 +1,326 @@
#!/usr/bin/env python3
"""
Backup script for all applications
Automatically discovers and runs backup scripts for all users,
then creates restic backups and sends notifications.
"""
import os
import sys
import subprocess
import logging
import pwd
from pathlib import Path
from typing import List, Tuple, Optional
import requests
# Configure logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
handlers=[
logging.StreamHandler(sys.stdout),
logging.FileHandler("/var/log/backup-all.log"),
],
)
logger = logging.getLogger(__name__)
# Configuration from Ansible template variables
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 }}"
TELEGRAM_BOT_TOKEN = "{{ notifications_tg_bot_token }}"
TELEGRAM_CHAT_ID = "{{ notifications_tg_chat_id }}"
NOTIFICATIONS_NAME = "{{ notifications_name }}"
class BackupManager:
def __init__(self):
self.errors = []
self.warnings = []
self.successful_backups = []
def get_home_directories(self) -> List[Tuple[str, str]]:
"""Get all home directories and their owners"""
home_dirs = []
home_path = Path("/home")
if not home_path.exists():
logger.error("/home directory does not exist")
return home_dirs
for user_dir in home_path.iterdir():
if user_dir.is_dir():
try:
# Get the owner of the directory
stat_info = user_dir.stat()
owner = pwd.getpwuid(stat_info.st_uid).pw_name
home_dirs.append((str(user_dir), owner))
except (KeyError, OSError) as e:
logger.warning(f"Could not get owner for {user_dir}: {e}")
return home_dirs
def find_backup_script(self, home_dir: str) -> Optional[str]:
"""Find backup script in user's home directory"""
possible_scripts = [
os.path.join(home_dir, "backup.sh"),
os.path.join(home_dir, "backup"),
]
for script_path in possible_scripts:
if os.path.exists(script_path):
# Check if file is executable
if os.access(script_path, os.X_OK):
return script_path
else:
logger.warning(
f"Backup script {script_path} exists but is not executable"
)
return None
def run_user_backup(self, script_path: str, username: str) -> bool:
"""Run backup script as the specified user"""
try:
logger.info(f"Running backup script {script_path} as user {username}")
# Use su to run the script as the user
cmd = ["su", "--login", username, "--command", script_path]
result = subprocess.run(
cmd, capture_output=True, text=True, timeout=3600 # 1 hour timeout
)
if result.returncode == 0:
logger.info(f"Backup script for {username} completed successfully")
self.successful_backups.append(username)
return True
else:
error_msg = f"Backup script for {username} failed with return code {result.returncode}"
if result.stderr:
error_msg += f": {result.stderr}"
logger.error(error_msg)
self.errors.append(f"User {username}: {error_msg}")
return False
except subprocess.TimeoutExpired:
error_msg = f"Backup script for {username} timed out"
logger.error(error_msg)
self.errors.append(f"User {username}: {error_msg}")
return False
except Exception as e:
error_msg = f"Failed to run backup script for {username}: {str(e)}"
logger.error(error_msg)
self.errors.append(f"User {username}: {error_msg}")
return False
def get_backup_directories(self) -> List[str]:
"""Get all backup directories that exist"""
backup_dirs = []
home_dirs = self.get_home_directories()
for home_dir, _ in home_dirs:
backup_path = os.path.join(home_dir, "backups")
if os.path.exists(backup_path) and os.path.isdir(backup_path):
backup_dirs.append(backup_path)
return backup_dirs
def run_restic_backup(self, backup_dirs: List[str]) -> bool:
"""Run restic backup for all backup directories"""
if not backup_dirs:
logger.warning("No backup directories found")
return True
try:
logger.info("Starting restic backup")
# Set environment variables for restic
env = os.environ.copy()
env.update(
{
"RESTIC_REPOSITORY": RESTIC_REPOSITORY,
"RESTIC_PASSWORD": RESTIC_PASSWORD,
"AWS_ACCESS_KEY_ID": AWS_ACCESS_KEY_ID,
"AWS_SECRET_ACCESS_KEY": AWS_SECRET_ACCESS_KEY,
"AWS_DEFAULT_REGION": AWS_DEFAULT_REGION,
}
)
# Run backup
backup_cmd = ["restic", "backup", "--verbose"] + backup_dirs
result = subprocess.run(backup_cmd, env=env, capture_output=True, text=True)
if result.returncode != 0:
error_msg = f"Restic backup failed: {result.stderr}"
logger.error(error_msg)
self.errors.append(f"Restic backup: {error_msg}")
return False
logger.info("Restic backup completed successfully")
# Run check
check_cmd = ["restic", "check"]
result = subprocess.run(check_cmd, env=env, capture_output=True, text=True)
if result.returncode != 0:
error_msg = f"Restic check failed: {result.stderr}"
logger.error(error_msg)
self.errors.append(f"Restic check: {error_msg}")
return False
logger.info("Restic check completed successfully")
# Run forget and prune
forget_cmd = [
"restic",
"forget",
"--compact",
"--prune",
"--keep-daily",
"90",
"--keep-monthly",
"36",
]
result = subprocess.run(forget_cmd, env=env, capture_output=True, text=True)
if result.returncode != 0:
error_msg = f"Restic forget/prune failed: {result.stderr}"
logger.error(error_msg)
self.errors.append(f"Restic forget/prune: {error_msg}")
return False
logger.info("Restic forget/prune completed successfully")
# Final check
result = subprocess.run(check_cmd, env=env, capture_output=True, text=True)
if result.returncode != 0:
error_msg = f"Final restic check failed: {result.stderr}"
logger.error(error_msg)
self.errors.append(f"Final restic check: {error_msg}")
return False
logger.info("Final restic check completed successfully")
return True
except Exception as e:
error_msg = f"Restic backup process failed: {str(e)}"
logger.error(error_msg)
self.errors.append(f"Restic: {error_msg}")
return False
def send_telegram_notification(self, success: bool) -> None:
"""Send notification to Telegram"""
try:
if success and not self.errors:
message = f"<b>{NOTIFICATIONS_NAME}</b>: бекап успешно завершен!"
if self.successful_backups:
message += (
f"\n\nУспешные бекапы: {', '.join(self.successful_backups)}"
)
else:
message = f"<b>{NOTIFICATIONS_NAME}</b>: бекап завершен с ошибками!"
if self.successful_backups:
message += (
f"\n\n✅ Успешные бекапы: {', '.join(self.successful_backups)}"
)
if self.warnings:
message += f"\n\n⚠️ Предупреждения:\n" + "\n".join(self.warnings)
if self.errors:
message += f"\n\n❌ Ошибки:\n" + "\n".join(self.errors)
url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage"
data = {"chat_id": TELEGRAM_CHAT_ID, "parse_mode": "HTML", "text": message}
response = requests.post(url, data=data, timeout=30)
if response.status_code == 200:
logger.info("Telegram notification sent successfully")
else:
logger.error(
f"Failed to send Telegram notification: {response.status_code} - {response.text}"
)
except Exception as e:
logger.error(f"Failed to send Telegram notification: {str(e)}")
def run_backup_process(self) -> bool:
"""Main backup process"""
logger.info("Starting backup process")
# Get all home directories
home_dirs = self.get_home_directories()
logger.info(f"Found {len(home_dirs)} home directories")
# Process each user's backup
for home_dir, username in home_dirs:
logger.info(f"Processing backup for user: {username} ({home_dir})")
# Find backup script
backup_script = self.find_backup_script(home_dir)
if backup_script is None:
warning_msg = (
f"No backup script found for user {username} in {home_dir}"
)
logger.warning(warning_msg)
self.warnings.append(warning_msg)
continue
# Run backup script
self.run_user_backup(backup_script, username)
# Get backup directories
backup_dirs = self.get_backup_directories()
logger.info(f"Found backup directories: {backup_dirs}")
# Run restic backup
restic_success = self.run_restic_backup(backup_dirs)
# Determine overall success
overall_success = restic_success and len(self.errors) == 0
# Send notification
self.send_telegram_notification(overall_success)
logger.info("Backup process completed")
if self.errors:
logger.error(f"Backup completed with {len(self.errors)} errors")
return False
elif self.warnings:
logger.warning(f"Backup completed with {len(self.warnings)} warnings")
return True
else:
logger.info("Backup completed successfully")
return True
def main():
"""Main entry point"""
try:
backup_manager = BackupManager()
success = backup_manager.run_backup_process()
if success:
sys.exit(0)
else:
sys.exit(1)
except KeyboardInterrupt:
logger.info("Backup process interrupted by user")
sys.exit(130)
except Exception as e:
logger.error(f"Unexpected error in backup process: {str(e)}")
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -91,3 +91,14 @@ rssbridge.vakhrushev.me {
reverse_proxy rssbridge_app:80
}
dozzle.vakhrushev.me {
tls anwinged@ya.ru
forward_auth authelia_app:9091 {
uri /api/authz/forward-auth
copy_headers Remote-User Remote-Groups Remote-Email Remote-Name Remote-Filter
}
reverse_proxy dozzle_app:8080
}
}

View File

@@ -1,7 +1,7 @@
services:
{{ service_name }}:
image: caddy:2.10.0
image: caddy:2.10.2
restart: unless-stopped
container_name: {{ service_name }}
ports:
@@ -15,8 +15,8 @@ services:
- {{ data_dir }}:/data
- {{ config_dir }}:/config
networks:
- "{{ web_proxy_network }}"
- "web_proxy_network"
networks:
{{ web_proxy_network }}:
web_proxy_network:
external: true

View File

@@ -0,0 +1,23 @@
services:
dozzle_app:
image: amir20/dozzle:v8.13.12
container_name: dozzle_app
restart: unless-stopped
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
networks:
- "web_proxy_network"
environment:
DOZZLE_HOSTNAME: vakhrushev.me
DOZZLE_AUTH_PROVIDER: forward-proxy
healthcheck:
test: ["CMD", "/dozzle", "healthcheck"]
interval: 3s
timeout: 30s
retries: 5
start_period: 30s
networks:
web_proxy_network:
external: true

View File

@@ -1,7 +1,7 @@
services:
gitea_app:
image: gitea/gitea:1.24.2
image: gitea/gitea:1.24.5
restart: unless-stopped
container_name: gitea_app
ports:
@@ -13,10 +13,10 @@ services:
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
networks:
- "{{ web_proxy_network }}"
- "web_proxy_network"
environment:
- "USER_UID=${USER_UID}"
- "USER_GID=${USER_GID}"
- "USER_UID={{ user_create_result.uid }}"
- "USER_GID={{ user_create_result.group }}"
- "GITEA__server__SSH_PORT=2222"
# Mailer
@@ -29,5 +29,5 @@ services:
- "GITEA__mailer__FROM=gitea@vakhrushev.me"
networks:
{{ web_proxy_network }}:
web_proxy_network:
external: true

View File

@@ -3,14 +3,14 @@
services:
gramps_app: &gramps_app
image: ghcr.io/gramps-project/grampsweb:25.7.1
image: ghcr.io/gramps-project/grampsweb:25.8.0
container_name: gramps_app
depends_on:
- gramps_redis
restart: unless-stopped
networks:
- "gramps_network"
- "{{ web_proxy_network }}"
- "web_proxy_network"
volumes:
- "{{ (data_dir, 'gramps_db') | path_join }}:/root/.gramps/grampsdb" # persist Gramps database
- "{{ (data_dir, 'gramps_users') | path_join }}:/app/users" # persist user database
@@ -61,9 +61,12 @@ services:
restart: unless-stopped
networks:
- "gramps_network"
- "monitoring_network"
networks:
gramps_network:
driver: bridge
{{ web_proxy_network }}:
web_proxy_network:
external: true
monitoring_network:
external: true

View File

@@ -6,9 +6,9 @@ services:
ports:
- "127.0.0.1:{{ homepage_port }}:80"
networks:
- "{{ web_proxy_network }}"
- "web_proxy_network"
networks:
{{ web_proxy_network }}:
web_proxy_network:
external: true

View File

@@ -5,23 +5,27 @@ services:
miniflux_app:
image: miniflux/miniflux:2.2.10
container_name: miniflux_app
user: "{{ user_create_result.uid }}:{{ user_create_result.group }}"
depends_on:
miniflux_postgres:
condition: service_healthy
restart: 'unless-stopped'
networks:
- "miniflux_network"
- "{{ web_proxy_network }}"
- "web_proxy_network"
volumes:
- "{{ secrets_dir }}:/secrets:ro"
environment:
- DATABASE_URL=postgres://{{ miniflux_postgres_user }}:{{ miniflux_postgres_password }}@miniflux_postgres/{{ miniflux_postgres_database }}?sslmode=disable
- DATABASE_URL_FILE=/secrets/miniflux_database_url
- RUN_MIGRATIONS=1
- CREATE_ADMIN=1
- ADMIN_USERNAME={{ miniflux_admin_user }}
- ADMIN_PASSWORD={{ miniflux_admin_password }}
- ADMIN_USERNAME_FILE=/secrets/miniflux_admin_user
- ADMIN_PASSWORD_FILE=/secrets/miniflux_admin_password
- BASE_URL=https://miniflux.vakhrushev.me
- DISABLE_LOCAL_AUTH=1
- OAUTH2_OIDC_DISCOVERY_ENDPOINT=https://auth.vakhrushev.me
- OAUTH2_CLIENT_ID={{ miniflux_oidc_client_id }}
- OAUTH2_CLIENT_SECRET={{ miniflux_oidc_client_secret }}
- OAUTH2_CLIENT_ID_FILE=/secrets/miniflux_oidc_client_id
- OAUTH2_CLIENT_SECRET_FILE=/secrets/miniflux_oidc_client_secret
- OAUTH2_OIDC_PROVIDER_NAME=Authelia
- OAUTH2_PROVIDER=oidc
- OAUTH2_REDIRECT_URL=https://miniflux.vakhrushev.me/oauth2/oidc/callback
@@ -32,21 +36,28 @@ services:
miniflux_postgres:
image: postgres:16.3-bookworm
container_name: miniflux_postgres
user: "{{ user_create_result.uid }}:{{ user_create_result.group }}"
restart: 'unless-stopped'
environment:
- POSTGRES_USER={{ miniflux_postgres_user }}
- POSTGRES_PASSWORD={{ miniflux_postgres_password }}
- POSTGRES_PASSWORD_FILE=/secrets/miniflux_postgres_password
- POSTGRES_DB={{ miniflux_postgres_database }}
networks:
- "miniflux_network"
- "monitoring_network"
volumes:
- {{ postgres_data_dir }}:/var/lib/postgresql/data
- "/etc/passwd:/etc/passwd:ro"
- "{{ secrets_dir }}:/secrets:ro"
- "{{ postgres_data_dir }}:/var/lib/postgresql/data"
healthcheck:
test: ["CMD", "pg_isready", "-U", "miniflux"]
test: ["CMD", "pg_isready", "--username={{ miniflux_postgres_user }}", "--dbname={{ miniflux_postgres_database }}"]
interval: 10s
start_period: 30s
networks:
miniflux_network:
driver: bridge
{{ web_proxy_network }}:
web_proxy_network:
external: true
monitoring_network:
external: true

View File

@@ -2,7 +2,7 @@
services:
netdata:
image: netdata/netdata:v2.5.4
image: netdata/netdata:v2.6.3
container_name: netdata
restart: unless-stopped
cap_add:
@@ -11,7 +11,8 @@ services:
security_opt:
- apparmor:unconfined
networks:
- "{{ web_proxy_network }}"
- "web_proxy_network"
- "monitoring_network"
volumes:
- "{{ config_dir }}:/etc/netdata"
- "{{ (data_dir, 'lib') | path_join }}:/var/lib/netdata"
@@ -33,5 +34,7 @@ services:
NETDATA_EXTRA_DEB_PACKAGES: "fail2ban"
networks:
{{ web_proxy_network }}:
web_proxy_network:
external: true
monitoring_network:
external: true

View File

@@ -1,3 +1,3 @@
jobs:
- name: fail2ban
update_every: 5 # Collect Fail2Ban jails statistics every 5 seconds
update_every: 15 # Collect Fail2Ban jails statistics every 15 seconds

View File

@@ -0,0 +1,9 @@
update_every: 15
jobs:
- name: outline_db
dsn: 'postgresql://netdata:{{ netdata_postgres_password }}@outline_postgres:5432/outline'
- name: miniflux_db
dsn: 'postgresql://netdata:{{ netdata_postgres_password }}@miniflux_postgres:5432/miniflux'

View File

@@ -1,5 +1,4 @@
update_every: 5
autodetection_retry: 0
update_every: 15
jobs:
@@ -20,3 +19,6 @@ jobs:
selector:
allow:
- "miniflux_*"
- name: transcriber
url: http://transcriber_app:8080/metrics

View File

@@ -3,7 +3,7 @@ services:
# See sample https://github.com/outline/outline/blob/main/.env.sample
outline_app:
image: outlinewiki/outline:0.85.1
image: outlinewiki/outline:0.87.3
container_name: outline_app
restart: unless-stopped
depends_on:
@@ -13,7 +13,7 @@ services:
- "127.0.0.1:{{ outline_port }}:3000"
networks:
- "outline_network"
- "{{ web_proxy_network }}"
- "web_proxy_network"
environment:
NODE_ENV: 'production'
URL: 'https://outline.vakhrushev.me'
@@ -59,7 +59,7 @@ services:
restart: unless-stopped
networks:
- "outline_network"
- "monitoring_network"
outline_postgres:
image: postgres:16.3-bookworm
@@ -69,6 +69,7 @@ services:
- {{ postgres_data_dir }}:/var/lib/postgresql/data
networks:
- "outline_network"
- "monitoring_network"
environment:
POSTGRES_USER: '{{ outline_postgres_user }}'
POSTGRES_PASSWORD: '{{ outline_postgres_password }}'
@@ -77,5 +78,7 @@ services:
networks:
outline_network:
driver: bridge
{{ web_proxy_network }}:
web_proxy_network:
external: true
monitoring_network:
external: true

View File

@@ -1,12 +1,12 @@
services:
rssbridge_app:
image: rssbridge/rss-bridge:2025-06-03
image: rssbridge/rss-bridge:2025-08-05
container_name: rssbridge_app
restart: unless-stopped
networks:
- "{{ web_proxy_network }}"
- "web_proxy_network"
networks:
{{ web_proxy_network }}:
web_proxy_network:
external: true

View File

@@ -0,0 +1,44 @@
$ANSIBLE_VAULT;1.1;AES256
33396537353265633634336630353330653337623861373731613734663938633837613437366537
3439383366633266623463366530626662346338393165630a663539313066663061353635666366
61393437393131333166626165306563366661353338363138633239666566313330363331666537
3763356535396334380a386362383436363732353234333033613133383264643934306432313335
34646164323664636532663835306230386633316539373564383163346663376666633564326134
30666135626637343963383766383836653135633739636261353666303666633566346562643962
63376165636434343066306539653637343736323437653465656436323533636237643333326438
35626239323530643066363533323039393237333338316135313838643464306161646635313062
36386565626435373333393566393831366538363864313737306565343162316536353539333864
63376264643566613266373665666363366662643262616634333132386535383731396462633430
32343738343039616139343833366661303430383766376139636434616565356161396433643035
37363165383935373937346464343738643430333764336264373931616332393964346566636638
39303434343461326464623363323937396663376335316237373166306134636432376435663033
34346436623435626363636237373965633139343661623135633764303862353465306235666563
66653764666635636462636434663264646665383236343166643133613966366334653030653262
38326437313939616332636638323033346139343732653933356239306132613665376163646164
30316663643666633334653133613764396165646533636534613931663138666366316235396466
61313964396264626339306135376635633133366433303033633363396132303938363638346333
66326466326134313535393831343262363862663065323135643630316431336531373833316363
64376338653366353031333836643137333736363534363164306331313337353663653961623665
64626562366637336637353433303261303964633236356162363139396339396136393237643935
34316266326561663834353762343766363933313463313263393063343562613933393361653861
38363635323231666438366536626435373365323733663139666534636564623666356436346539
63326436386436356636633637373738343032353664323736653939346234643165313461643833
35666439613136396264313033336539313537613238393262306365656238396464373936616538
64316365616464386638313331653030346330393665353539393834346135643434363736323135
37663433326439356663633162616435313061353662373766633731636439636266666466613363
39343930386534376330663230623832643933336235636166626534366664366562356165373764
63343432323864366162376263656565646661633536666336643030363039616666343063386165
37343238303034313832393538313632396261316232376635633732656663396631323261363433
38373738363833323934353739643538376237316535623035383965613965636337646537326537
64663837643632666334393634323264613139353332306263613165383733386662366333316139
63373839346265366166333331353231663763306163323063613138323835313831303666306561
39316666343761303464333535336361333462623363633333383363303134336139356436666165
62616364373030613837353939363636653537373965613531636130383266643637333233316137
39353866366239643265366162663031346439663234363935353138323739393337313835313062
33373263326565383735366364316461323930336437623834356132346633636364313732383661
66346634613762613037386238656334616430633037343066623463313035646339313638653137
65643166316664626236633332326136303235623934306462643636373437373630346435633835
66346364393236393563623032306631396561623263653236393939313333373635303365316638
66373037333565323733656331636337336665363038353635383531386366633632363031623430
31356461663438653736316464363231303938653932613561633139316361633461626361383132
396436303634613135383839396566393135

View File

@@ -0,0 +1,23 @@
services:
transcriber_app:
image: "{{ registry_transcriber_image }}"
container_name: transcriber_app
user: '{{ user_create_result.uid }}:{{ user_create_result.group }}'
restart: unless-stopped
volumes:
- "{{ config_file }}:/config/config.toml:ro"
- "{{ data_dir }}:/data"
networks:
- "web_proxy_network"
- "monitoring_network"
environment:
- "USER_UID={{ user_create_result.uid }}"
- "USER_GID={{ user_create_result.group }}"
command: ./transcriber --config=/config/config.toml
networks:
web_proxy_network:
external: true
monitoring_network:
external: true

View File

@@ -3,12 +3,12 @@
services:
wakapi_app:
image: ghcr.io/muety/wakapi:2.14.0
image: ghcr.io/muety/wakapi:2.15.0
container_name: wakapi_app
restart: unless-stopped
user: '{{ user_create_result.uid }}:{{ user_create_result.group }}'
networks:
- "{{ web_proxy_network }}"
- "web_proxy_network"
volumes:
- "{{ data_dir }}:/data"
environment:
@@ -28,5 +28,5 @@ services:
networks:
{{ web_proxy_network }}:
web_proxy_network:
external: true

14
lefthook.yml Normal file
View File

@@ -0,0 +1,14 @@
# Refer for explanation to following link:
# https://lefthook.dev/configuration/
templates:
av-hooks-dir: "/home/av/projects/private/git-hooks"
pre-commit:
jobs:
- name: "gitleaks"
run: "gitleaks git --staged"
- name: "check secret files"
run: "python3 {av-hooks-dir}/pre-commit/check-secrets-encrypted-with-ansible-vault.py"

View File

@@ -5,6 +5,7 @@
vars_files:
- vars/ports.yml
- vars/secrets.yml
- files/authelia/secrets.yml
vars:
app_name: "authelia"
@@ -30,19 +31,17 @@
loop:
- "{{ config_dir }}"
- name: "Copy configuration files"
- name: "Copy users file"
ansible.builtin.copy:
src: "files/{{ app_name }}/{{ item }}"
dest: "{{ (config_dir, item) | path_join }}"
src: "files/{{ app_name }}/users.secrets.yml"
dest: "{{ (config_dir, 'users.yml') | path_join }}"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0600"
loop:
- "users.yml"
- name: "Copy configuration files (templates)"
ansible.builtin.template:
src: "files/{{ app_name }}/configuration.yml.j2"
src: "files/{{ app_name }}/configuration.template.yml"
dest: "{{ (config_dir, 'configuration.yml') | path_join }}"
owner: "{{ app_user }}"
group: "{{ app_user }}"

View File

@@ -8,7 +8,7 @@
vars:
restic_shell_script: "{{ (bin_prefix, 'restic-shell.sh') | path_join }}"
backup_all_script: "{{ (bin_prefix, 'backup-all.sh') | path_join }}"
backup_all_script: "{{ (bin_prefix, 'backup-all.py') | path_join }}"
tasks:
- name: "Copy restic shell script"
@@ -21,7 +21,7 @@
- name: "Copy backup all script"
ansible.builtin.template:
src: "files/backups/backup-all.sh.j2"
src: "files/backups/backup-all.template.py"
dest: "{{ backup_all_script }}"
owner: root
group: root

View File

@@ -21,13 +21,14 @@
- "docker-{{ docker_edition }}-cli"
- "docker-{{ docker_edition }}-rootless-extras"
docker_users:
- major
- name: "Login to yandex docker registry."
ansible.builtin.script:
cmd: "files/yandex-docker-registry-auth.sh"
- "{{ primary_user }}"
- name: Create a network for web proxy
community.docker.docker_network:
name: "{{ web_proxy_network }}"
name: "web_proxy_network"
driver: "bridge"
- name: Create a network for monitoring
community.docker.docker_network:
name: "monitoring_network"
driver: "bridge"

34
playbook-dozzle.yml Normal file
View File

@@ -0,0 +1,34 @@
---
- name: "Configure dozzle application"
hosts: all
vars_files:
- vars/ports.yml
- vars/secrets.yml
vars:
app_name: "dozzle"
app_user: "{{ app_name }}"
base_dir: "/home/{{ app_user }}"
tasks:
- name: "Create user and environment"
ansible.builtin.import_role:
name: owner
vars:
owner_name: "{{ app_user }}"
owner_extra_groups: ["docker"]
- name: "Copy docker compose file"
ansible.builtin.template:
src: "./files/{{ app_name }}/docker-compose.yml.j2"
dest: "{{ base_dir }}/docker-compose.yml"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0640"
- name: "Run application with docker compose"
community.docker.docker_compose_v2:
project_src: "{{ base_dir }}"
state: "present"
remove_orphans: true

View File

@@ -11,6 +11,7 @@
app_user: "{{ app_name }}"
base_dir: "/home/{{ app_user }}"
data_dir: "{{ (base_dir, 'data') | path_join }}"
secrets_dir: "{{ (base_dir, 'secrets') | path_join }}"
postgres_data_dir: "{{ (base_dir, 'data', 'postgres') | path_join }}"
postgres_backups_dir: "{{ (base_dir, 'backups', 'postgres') | path_join }}"
@@ -32,6 +33,21 @@
loop:
- "{{ postgres_backups_dir }}"
- name: "Copy secrets"
ansible.builtin.import_role:
name: secrets
vars:
secrets_dest: "{{ secrets_dir }}"
secrets_user: "{{ app_user }}"
secrets_group: "{{ app_user }}"
secrets_vars:
- "miniflux_database_url"
- "miniflux_admin_user"
- "miniflux_admin_password"
- "miniflux_oidc_client_id"
- "miniflux_oidc_client_secret"
- "miniflux_postgres_password"
- name: "Copy docker compose file"
ansible.builtin.template:
src: "./files/{{ app_name }}/docker-compose.yml.j2"
@@ -52,4 +68,5 @@
community.docker.docker_compose_v2:
project_src: "{{ base_dir }}"
state: "present"
recreate: "always"
remove_orphans: true

View File

@@ -42,21 +42,34 @@
group: "{{ app_user }}"
mode: "0640"
- name: "Copy prometheus plugin config file"
ansible.builtin.copy:
src: "files/{{ app_name }}/go.d/prometheus.conf"
dest: "{{ config_go_d_dir }}/prometheus.conf"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0640"
- name: "Find all go.d plugin config files"
ansible.builtin.find:
paths: "files/{{ app_name }}/go.d"
file_type: file
delegate_to: localhost
register: go_d_source_files
- name: "Copy fail2ban plugin config file"
ansible.builtin.copy:
src: "files/{{ app_name }}/go.d/fail2ban.conf"
dest: "{{ config_go_d_dir }}/fail2ban.conf"
- name: "Template all go.d plugin config files"
ansible.builtin.template:
src: "{{ item.path }}"
dest: "{{ config_go_d_dir }}/{{ item.path | basename }}"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0640"
loop: "{{ go_d_source_files.files }}"
- name: "Find existing go.d config files on server"
ansible.builtin.find:
paths: "{{ config_go_d_dir }}"
file_type: file
register: go_d_existing_files
- name: "Remove go.d config files that don't exist in source"
ansible.builtin.file:
path: "{{ item.path }}"
state: absent
loop: "{{ go_d_existing_files.files }}"
when: (item.path | basename) not in (go_d_source_files.files | map(attribute='path') | map('basename') | list)
- name: "Grab docker group id."
ansible.builtin.shell:

92
playbook-transcriber.yml Normal file
View File

@@ -0,0 +1,92 @@
---
- name: "Deploy transcriber application"
hosts: all
vars_files:
- vars/ports.yml
- vars/secrets.yml
vars:
app_name: "transcriber"
app_user: "{{ app_name }}"
base_dir: "/home/{{ app_user }}"
config_dir: "{{ (base_dir, 'config') | path_join }}"
config_file: "{{ (config_dir, 'config.toml') | path_join }}"
data_dir: "{{ (base_dir, 'data') | path_join }}"
backups_dir: "{{ (base_dir, 'backups') | path_join }}"
docker_registry_prefix: "cr.yandex/crplfk0168i4o8kd7ade"
# transcriber_image: "{{ transcriber_image | default(omit) }}"
tasks:
- name: "Create user and environment"
ansible.builtin.import_role:
name: owner
vars:
owner_name: "{{ app_user }}"
owner_extra_groups: ["docker"]
- name: "Create application internal directories"
ansible.builtin.file:
path: "{{ item }}"
state: "directory"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0750"
loop:
- "{{ config_dir }}"
- "{{ data_dir }}"
- "{{ backups_dir }}"
- name: "Copy configuration files (templates)"
ansible.builtin.copy:
src: "files/{{ app_name }}/config.secrets.toml"
dest: "{{ config_file }}"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0600"
- name: "Login to yandex docker registry."
ansible.builtin.script:
cmd: "files/yandex-docker-registry-auth.sh"
- name: "Deploy service"
when: transcriber_image is defined
block:
# - name: "Check is web service image passed"
# ansible.builtin.assert:
# that:
# - "transcriber_image is defined"
# fail_msg: 'You must pass variable "transcriber_image"'
- name: "Create full image name with container registry"
ansible.builtin.set_fact:
registry_transcriber_image: "{{ (docker_registry_prefix, transcriber_image) | path_join }}"
- name: "Push web service image to remote registry"
community.docker.docker_image:
state: present
source: local
name: "{{ transcriber_image }}"
repository: "{{ registry_transcriber_image }}"
push: true
delegate_to: 127.0.0.1
- name: "Copy docker compose file"
ansible.builtin.template:
src: "./files/{{ app_name }}/docker-compose.yml.j2"
dest: "{{ base_dir }}/docker-compose.yml"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0640"
- name: "Run application with docker compose"
community.docker.docker_compose_v2:
project_src: "{{ base_dir }}"
state: "present"
remove_orphans: true

View File

@@ -25,3 +25,18 @@
- name: Remove dependencies that are no longer required
ansible.builtin.apt:
autoremove: true
- name: Check if Docker is available
ansible.builtin.stat:
path: /usr/bin/docker
register: docker_exists
- name: Clean up unnecessary Docker data
ansible.builtin.command:
cmd: docker system prune --all --force
register: docker_prune_result
when: docker_exists.stat.exists
failed_when:
- docker_prune_result.rc is defined
- docker_prune_result.rc != 0
changed_when: "'Total reclaimed space' in docker_prune_result.stdout"

55
roles/secrets/README.md Normal file
View File

@@ -0,0 +1,55 @@
# Secrets Role
Ansible роль для сохранения секретов на удаленной машине.
## Описание
Роль позволяет безопасно сохранять секреты на удаленной машине в виде файлов с ограниченными правами доступа. Поддерживает сохранение переменных как файлов и копирование существующих файлов.
## Параметры
### Обязательные параметры
- `secrets_dest` - директория, куда будут сохранены секреты в виде файлов
- `secrets_user` - имя пользователя, который будет владеть директорией и файлами
- `secrets_group` - группа, которая будет владеть директорией и файлами (по умолчанию равна `secrets_user`)
### Опциональные параметры
- `secrets_vars` - список переменных, из которых будут извлечены секреты (по умолчанию: `[]`)
- `secrets_files` - список файлов, которые будут скопированы "как есть" (по умолчанию: `[]`)
- `secrets_dir_mode` - права доступа для директории (по умолчанию: `0750`)
- `secrets_file_mode` - права доступа для файлов с секретами (по умолчанию: `0400`)
## Функциональность
1. Создает директорию `secrets_dest` если она не существует
2. Сохраняет каждую переменную из `secrets_vars` как файл с таким же именем
3. Копирует файлы из `secrets_files` в директорию назначения
4. Устанавливает права доступа `0400` для всех файлов с секретами
5. Назначает указанного пользователя и группу владельцами директории и файлов
## Пример использования
```yaml
- name: Save application secrets
include_role:
name: secrets
vars:
secrets_dest: /opt/myapp/secrets
secrets_user: myapp
secrets_group: myapp
secrets_vars:
- database_password
- api_key
- jwt_secret
secrets_files:
- /path/to/ssl/certificate.pem
- /path/to/ssl/private.key
```
## Безопасность
- Все файлы с секретами создаются с правами `0400` (только чтение для владельца)
- Директория создается с правами `0750` (полный доступ для владельца, чтение и выполнение для группы)
- Используется `no_log: true` для предотвращения вывода секретов в логи Ansible

View File

@@ -0,0 +1,9 @@
---
# Default variables for secrets role
secrets_dest: ""
secrets_user: ""
secrets_group: "{{ secrets_user }}"
secrets_vars: []
secrets_files: []
secrets_dir_mode: "0750"
secrets_file_mode: "0400"

View File

@@ -0,0 +1,22 @@
---
galaxy_info:
author: 'Anton Vakhrushev'
description: Ansible role for saving secrets on remote machine
license: MIT
min_ansible_version: '2.9'
platforms:
- name: Ubuntu
versions:
- all
- name: Debian
versions:
- all
- name: EL
versions:
- all
galaxy_tags:
- secrets
- security
- files
dependencies: []

View File

@@ -0,0 +1,46 @@
---
# tasks file for secrets role
- name: "Validate secrets_dest parameter"
ansible.builtin.fail:
msg: "secrets_dest is required but not defined"
when: secrets_dest is not defined or secrets_dest == ""
- name: "Validate secrets_user parameter"
ansible.builtin.fail:
msg: "secrets_user is required but not defined"
when: secrets_user is not defined or secrets_user == ""
- name: "Validate secrets_group parameter"
ansible.builtin.fail:
msg: "secrets_group is required but not defined"
when: secrets_group is not defined or secrets_group == ""
- name: "Create secrets destination directory"
ansible.builtin.file:
path: "{{ secrets_dest }}"
state: directory
owner: "{{ secrets_user }}"
group: "{{ secrets_group }}"
mode: "{{ secrets_dir_mode }}"
- name: "Save variables as secret files"
ansible.builtin.copy:
content: "{{ lookup('vars', item) }}"
dest: "{{ secrets_dest }}/{{ item }}"
owner: "{{ secrets_user }}"
group: "{{ secrets_group }}"
mode: "{{ secrets_file_mode }}"
loop: "{{ secrets_vars }}"
when: secrets_vars | length > 0
no_log: true
- name: "Copy secret files"
ansible.builtin.copy:
src: "{{ item }}"
dest: "{{ secrets_dest }}/{{ item | basename }}"
owner: "{{ secrets_user }}"
group: "{{ secrets_group }}"
mode: "{{ secrets_file_mode }}"
loop: "{{ secrets_files }}"
when: secrets_files | length > 0

View File

@@ -1,11 +1,35 @@
#!/usr/bin/env python3
"""
SMTP Password Generator for Yandex Cloud Postbox
Этот скрипт конвертирует Secret Access Key в SMTP пароль для использования
с Yandex Cloud Postbox сервисом. Скрипт реализует алгоритм AWS4 подписи
для генерации корректного SMTP пароля.
Yandex Cloud Postbox использует AWS-совместимый API, и для SMTP аутентификации
требуется специальный пароль, который генерируется из Secret Access Key
с использованием определенного алгоритма подписи.
Примеры использования:
python3 smtp-convert-secret-key-to-password.py "YourSecretAccessKey123"
Требования:
- Python 3.x
- Стандартные библиотеки: hmac, hashlib, base64, argparse, sys
Автор: Yandex.Cloud Team
Ссылка: https://yandex.cloud/ru/docs/postbox/operations/send-email
"""
import hmac
import hashlib
import base64
import argparse
import sys
# These values are required to calculate the signature. Do not change them.
DATE = "20230926"
SERVICE = "postbox"

View File

@@ -0,0 +1,70 @@
#!/usr/bin/env python3
"""
SMTP Test Email Sender for Yandex Cloud Postbox
Скрипт для отправки тестового email через Yandex Cloud Postbox SMTP сервер.
Используется для проверки корректности настроек SMTP аутентификации.
Примеры использования:
python3 smtp-send-test-email.py --login "your-login" --password "smtp-password" --to "recipient@example.com"
"""
import smtplib
import argparse
import sys
from email.message import EmailMessage
def send_test_email(login, password, to_email):
"""Отправляет тестовое email через Yandex Cloud Postbox SMTP"""
# initialize connection to our email server
smtp = smtplib.SMTP("postbox.cloud.yandex.net", port="587")
smtp.set_debuglevel(2)
smtp.ehlo() # send the extended hello to our server
smtp.starttls() # tell server we want to communicate with TLS encryption
smtp.login(login, password) # login to our email server
message = EmailMessage()
message.set_content("Hello, world! This is a test email from Yandex Cloud Postbox.")
message.add_header("From", "service@vakhrushev.me")
message.add_header("To", to_email)
message.add_header("Subject", "Test Email from Yandex Cloud Postbox")
# send our email message
smtp.send_message(message)
smtp.quit() # finally, don't forget to close the connection
print(f"Test email successfully sent to {to_email}")
def main():
parser = argparse.ArgumentParser(
description="Send a test email via Yandex Cloud Postbox SMTP server."
)
parser.add_argument(
"--login", required=True, help="SMTP login (usually your email address)"
)
parser.add_argument(
"--password",
required=True,
help="SMTP password (generated using smtp-convert-secret-key-to-password.py)",
)
parser.add_argument("--to", required=True, help="Recipient email address")
args = parser.parse_args()
try:
send_test_email(args.login, args.password, args.to)
except Exception as e:
print(f"Error sending email: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -1,142 +1,142 @@
$ANSIBLE_VAULT;1.1;AES256
62653431636461623338643536653736633166303934626565363963373637396534303130373035
6565376162653735313737333439633862643366336264650a633265316463323062653032363861
32626536343138663837633334316537373662653262366163633334623764633938323363363962
6230333564643665320a613862653632363363616266336338346539323964383736366235306437
33306363353163383663643062656330313134353836666232616532316264303564336235356661
30653262363866653139646436333036393837383262643537313933613939326433313565393465
31373036353133663337613935343038616164316132303833363338623863633234656537653039
62626436346238636234393939366139363034306432326538656264343733356537393332633836
38636639626665666238656338363633383566616638353235383465623232646537616230626630
63303130316438353934656636393366306566346362356564393661643064323630636463383061
37636461386432323136393739633862313337333261306664323361393835323034643134383461
31313762616538336666656137373631336132383364646163633732323431613239333563653332
65616664333839363834333362626238633833666430653738613636333432333430333861356339
61323865663661383534343964346238383134613532616637346235616139383434623564333361
31636165653261363830623162623738333937316664633434346431626630393837366666643434
61643734653834326434353431393732376266626266313264376235323838313539306463653864
36393461366230643234376161623330326365616539323965633431633238386262373562383161
39323634633166643038356434616461613864303334393932663730303839373530643933323839
66353337326336656635636362356531613634623633303461336565363564393964663430393666
64326439346233346132653230343234653430653239636362616561636166343030303863373337
36363633646432613138313062346164663730313061363432396138323561366430316439343036
32353931393064666231323863656165363066313236613332356161363139616636333963386130
37363030383765613132353161613766633635363033656561343038633839313933646264383730
64336339646264383332373639326164373163383966626363653762643037353636376336626136
33346533303036326531316332306461646361376435316438376161663162336335353938366565
30633133653431393066393961313138383337313731653031323432633766356338316366373432
32373937663961623739633439636661336461346132376533373961666432353937373066643165
61663063363661633938373365393665356665636562646265313834373962336566393835633339
34396666396162613162326331313037303933366564623837386338363063636564656339336639
66346465366233663534373465313930323134313835316464363263383866313563396263616535
63383265623865636162346635613863356266336664343434393437656134353639353535383332
62623934643930313939646466663336633034343534396137333264623263663866663339663266
30343234356536663262616363376663646264353331646164376331376639363135373137396437
37363166386233356434656237373535326162303437346233623263663534383032363638376134
61653939306433393437656465343066613530396265396262373433383637656266303064623234
64333062353435373863636439663561393763333538303836303631666262326430623835656138
37653562353562373935333235316430613737653862303933333062643663333364333966643461
33323335346566363337643161303835356336306232653763346639323265373432376239363566
64373562653238333865326335613133636335373739396335633631313431363061616139303463
37333364393438666532396131343637373833353766396234383739306565646439366438653032
33656330343061636338643465653664326338663233316631303465666632653436633135643664
64616132366632666431653262393035393163343664303961396431666236303864303865343634
35616634613165373637653235323164323666343436646339646637646234306163333462393063
32346534636165656436353036316232303266616135303663343631303565623562616237306365
65303938646239393564333461343238636335336533633265383066653734613332656563666434
31316665613630336263613934316361383332363164323266373565323239343033666663396534
39323739313636616232663535386439363065333766623837336230303334656466656262613363
37386664336436376530373436353235616437333834646563353830626162336261333135383866
64383930316531373366646335306131633166353161336463376530353066356530393665393063
31613636386532623035373866373065633233633135343439616662616232366337313764646436
64626262643532613136373238316561616361393433323066326333663663353236393662396539
31653036303031303462643231333965653536666136313638613832393361666131363435633932
31663864326563663230626237643763333737613239373134626433636564386231383961316162
39383165336433626466393935383363396333636131643733663866356434366664613766396263
34313934626133653361633665323131613736306331373732323434323535346136393964356231
62346136356331393238346333393266613365633563626238353530333931613330663765393936
32333261353634646366323238353238643837633735636662356630373464343330626630656130
36356565356430643133386461313335343436316263303064366139316638663161356332386362
37376431393661386231313763303266313630323362363664336366633035353562303439373630
33343265633630343065363461363064653933303932613761303538393734373962613633386539
66636534333537313135356665633966326430373062346136326532666638303334653263646431
38393131653338316663313265653861663334326635353137623739396636333637343137636339
32303836373535326363396434326233623532633931653039643763326263616232333462616631
36666564623030396134346665386661386433366266363739626161653062323963313365353161
35643530343439326133613939353737653165326538666530366530323963363839373032326462
34666235376263616364656130633637346334353934396132353263313237316366303137386430
64653563333963313361303239666361336136356363306266633833366262326431616161613238
38653538613032386238623839663332613064333031303939363733396635373238666562386536
32316566666435376239386637396334643861643634316338613063656465373164646530363865
34373130636435326130633437303539646535336131393339613139383636333763336530636534
34636666666265373636326666333130623863316465663333653466353063313134386262333739
62626264393362353663303531313061643538663532333164336662343732373463623166396539
39396531376338616538633633343733343765306237656466666232623163303738643431633763
61656335616430653936303831393664653365363764333362373337323364323039363163353461
61336536316466396636306266353830316665343739613033346538333830306263386134613737
64316339613462346438656362346664303762643766373364343931626530626439336634666537
31633964386564663531343764326666666261643464353438353035333665363434646661646663
38636239373331623061343730376632393963303732393533396464633131633435373161303163
66383461343861326665623463636262336562633936623563373136613063356362383862663232
37333331373431393137363735613366656434323065346661366433663464666363343231393863
64633530316230653065356165366135396531663731323866376162306238343962376362633234
61626563306431623336623737353931316236623333623337383366613262346631646330313637
39366239396330303461303666396431663062626533336136643039353034633230353765353334
38613362653963336162326163356662356661386630353664333265373032316531656131376665
37376262363130336161613230333863653662623436666361396561613935323432663665643138
38616564636634613164313666393532396265396135326538336665373232316461326635306131
34343632636637653835653131613161316237346239363830386536363933643532333533373333
39643364306163666366376535653333323435383332633961343930633635383030356463333964
39626130666166313234386439383833616265316265363430343134633730336261383435356138
62373063346238613061363033343366623633373034346531303538396335653938646664303962
31336634623135616237323837623831306535316463613266326262663934303938373132343735
37656335333263326531646162393738653632376164323165393563656138613830633936396433
61353332343134636564333233393863643837353366386234376237623435663765343366363033
63326233383962633266303962613361643464613764303531333930363736323535386632393766
61353666303134663466333330383031333933666137346364656364313965656164303065303530
34616130653061613934393831373130333566363736626261316330303966656162326638333130
66373133613536623566303432356666346535636237616561323063643439616436393666376536
32613830343636393031333737376332396230313034393062663437613838363263333233613439
30623039336339373234326261306435366332656164613439376139346333616331326561383963
30643133376632656564616536323863373237623263366266396264633464373765316164346165
37636233633661643362636630356333333766613036663335613264333439323239633861363034
34663937376530653837653236303839336631313863363239626632646436653638366638366566
39306538353231623434373537313862386335393262633062313432646232623863383731313031
30656366363837366666393933346238363336363030373836386230343062363661306263633163
33626562623935643665626239386133636531393536336661613430343630333961303233343430
63656666346138643163393663316134666336323961626163376461663635633834333337393062
61656163613234633965356133666335343065626137633137333266613561633936386136643134
37383562663031393133326662623136386539633066323336306262346236613161613637626162
36636133666334333636653535623732343233396430653566393165353431303739656239373738
33323939633264303139323162613964306237376461383261646635343036313639626539373238
32336537373436373338386432646139303831383138326564333739353761616336346461356532
38303138656533386231303336336564656135346162376662663962663763353830663237323138
33373331656637363139626132393231313136303936633161636261643264313230356261366165
39666331306262643566663830626663656530303831343231323336306266363735393966613062
63353938386263376166316335656164633233633465303065663565373764343031663866653135
64663766386436653665356265333565323336636539656237303334383636353161643366656637
66356532373130323236313936623964663433333965326662333833316437326461326165376661
66396537653032346666363965313339323331303864616230646361386335663138613433326261
35613430363864336635343434333761656639633863323534653862383936653762646134356664
38326463326239636162333435656561343739366364313738663535636136323439373462643832
62633661663337343538393466613734633531666532353161616231323161646237653736346561
64323063656366373931396639393261643333393333626539663561636661393936316539633263
63343331313464623636353031343232613534663565303538333164306531303438616539386364
30376233333630336431336364663834633734636261353364343564333639623737363538313462
61616233663335303062336635376435643965373039336231346234363436356238356162613138
65326532663461616263626238346535623136633039613939353132313836373962646463333535
65313562346631633435616232366166373763346337303561326130333936346130363431383036
62356435616630396539303633343166646461393030336462366463636138316333633363643636
65376131333731356566333237363266656466376539326438313930376363386231616138336335
65333735653830373035656265336331346562353233663465343935383235303930633831613137
64303130666532303733633133386334613733383562613661643931636136386264396438316366
61653964643135646332343764666134336666336232376465353462356632346533633961636534
32643234396636303135663562656435376561336235303837643932366334616265383639343733
65633833653763643366646232343765306131313465326263623636386131376463356139623334
39343163366439643334646663393434353333316234623530393431643539346435616263303734
61633066653838363933646230623238653431393061646430383537343363643562653831336362
37626630633161653763386663373630306564663339393265663732623434643231326335376562
37663234643466366535326461396631633430613431346134316635653032663033623465346338
61353331393631343365663233376330333730366161353362626166646232313666336333386265
33373761313536326165343339346263316636363362393365663034353964373164643763383037
3666
39383739333834626566633766343639323331333132396231613638323463316361393939616532
3632323939633533313766616630333137393962623433620a343062373935633966643933326263
61316632373262323534636265356664636466643361656365303366616464373734653766373265
3531356432303735640a343965613833616631653836656561656262656263626363333436383165
39336332303365393562353036633033633064333961323735383239643533323038623739646134
34326465393261323539386438323737653938366333306633613066363738646639323139306337
62333966613033616535363830626661663066303838393366653834346136396339643136363238
38336166373238393364616463633865633431396335343562323731636639343965346365333966
30306335313337616161666535396236373465396239663966616532313731356566653733643239
66366361666134323136626662323932386664386165663236366436376562373431613736343663
35613738616364393138633762316265316236616136626131376332353133353666623330343763
38313731663739346636306435623831313835363866383138633130336638633834616564313232
61626239303162346138346138383131613135393035353435393331623135663734306530343434
35613463356139313034656335303637386666643435306534363637653931616631613932383763
34626331303232323964636631663537623638313538386336336666353262613062393365306161
33663865356533396334656361376464356534313537313739616661353665333532343032303634
61333334393432626366313836663762333965393133313163346338613662316462376438316564
36396632646538663262643135663364373331623730303965326237336132356434653331393932
38623561656263326265323337646430636363653966306234396330636661626566623936343361
34646332313130393439386534663536303932383936623938633835363236303863363032393331
39346335653836366634323736666234653239383730313964373265623036613666363136306433
64653265623731376239616634353262383033663134323861343566306336386263303063303162
65613631643666643134363738346235323632326639333465313239396163386438613136346435
64393636386637306165336235346531313333393631323239393165306535353964646637343464
63326530666365336633393736623863623061346436613362386262343039626438356538333432
63306136383236346166616339643530383331373233663536626535373532643464616235333935
39616562343063613437366238336366376636663137343830653330386635353838343338643735
62613236363337613365633334613732326130343764636663313432636665363936616162316663
62656331316233643437373762396265353537373937623036663661643036333331613363613764
34663963323630613337303061393266643565346464316136356132383864636463313035363565
64306562363338326430366561646132643539353636333563353664656637393664303534613262
33303561343035636136333766303637656232663530646630336236633165373163366533366166
37306165306437373931366530373161646165333336396132656264383638616164396164313362
39356230623737373135643131313264623230353366396266623538623535646566353665343163
65336530636661326234623934663861313131366635366431623266633130393061333466333637
36336330646566346530303961356230383430373438643336376439363036306638626633326266
30323932326537646363353533343865356135366636353037323463303738383739373736353431
34333337313065383561383730383461626238633561353231336639323131326239623566363734
66663836616536646162353836346266616366383664323365373730646537363938643834613239
62333135616264663435626535363534313138613731656466316366313032623433393336666138
32326163613232633433616232666635343964316466306266666665646261373637353464613564
36646162626131346537636531333339666339396666663934656334326432396231633331333465
30333163633464396130333061663865333165633531656662613666336438616639633566383538
63386263346161383430326662353433316664663764643438353434303666343938636237353637
32383335326632666232396332376531643438396332626334306333636662653263636262336362
39313130363235323334323237623631333031626266643137623132623163633039356132316266
38393136363235393833333962663834313138373239656366383265393238343062376531643734
62363833633261333634656364383635346431383564386639323033393265383261653665333238
62336465383265653538393332636336336464643732303234656438393838613537326234616335
62663336316231326639653961353736336331303831316338306439386461636563623363363564
30303937663064353738303762653636336134633262303939346535333531636530386561623738
31313262323663333262613832326538383430653836313438343864626336643538313839666331
66313137303331666434376662646266623963633533613939393565393834353139613032336633
34366333326137306432313838313463643832363462336164353838663231616634626133323962
39663864353166373236356334633039643531383631633431383532386530396161663638326266
36303462336531313964383563346462376164333263643664653639353539376561353037326364
65656431613035386461333731633165346134396436656638656635313761646161343061653134
39663339323566363636656434613735663235323164363638353565613231343464373439313535
61633736623232356166316237363435303036666461313138623632633962326265383262656466
64346637636135643837393637343465303761643063663730303366313937303833613435613662
64306662343464333435646261643239616636666134346161346365326361313966666134653366
34343362653837396264643033383562616235656362323836336362346162333631643437366432
61393731323561306263613764633061643131333564326666613833386561646131656233633135
39356232313565336437396637643432363466313930623661383430623236333364303835653532
65646632623362303437373230656330306531333963393232326337623865323262383938313739
65383364383135366464666536313365643563393633623261623764346238313339663638343735
37393331626230333761376133633335353035386438616566656334306235643436643030303737
32303764636663663662396534396438356639633537356434656335396462336231366530643934
30633831373731623065623631316164613166393134633738663432333936333832653462636565
61366164626666643366626432343335616166623437306262363264623334636539643163383736
34346636343432323534313631373666316666633638316530386638393166343166666236353433
34636331393433393939656265616161316535396536633330366533643135346238386237663337
35393333343338613435393266306665356432376564653439353563376363393561393035353234
37613535343339623334356165383532313331386430333330666132376563326532643339386432
34346633653362626239653133363765303965623233353630313936336333336634316464653565
33346539656538326565323966393566396636326362623839396330336364363366396132636462
32623138626337383533313739663066366562323237666666363836396639666430363232363461
35383963643836633432613036643064666337336166333130393730313661623035616636653166
39393936666436306234633863323562343337386336303337333235316438383430386562346437
30613039376633393065353031313832643339386461373433656136306339306537323434383332
30373935623537633432373637623536653838333239336634376265313437383632653030393761
39323033653632386264343335626330333331653635343061353633643864623062663763643236
32373564366165653538663263323139613533353566386162303336343662663430303030376234
62376532343134643931373436356331383431386435613839633137656566623766653131626132
61346538393335393461346538333866323033303431613965326532343761353133663930333835
63393537343035393339636663383961353838646234626162656563353466393335343764626166
34303731376165353737643131636334353562353735623764666130623263376363393330333733
37323539313263643838613863336464653536396662393730333833633030353834306439616661
61386165333964633765326163306437313161653730636261623463666530396634326461323232
35633635323461636361333065663466626139306464383966303435653731333732343431356463
65653534653762646666333934323564353332333135613066323262393162623463663530353265
61366164616261386430363337623464353933646332626439323435346230393563633330633361
33376632663963633237386136623333623762343233613161633832663536616564363536363363
31353035346236336131643538326663646237646539326563363161313864656332623035363663
33386336613662373266336537663038626666646337313532616464323737343661663338656235
65366465633932396665353965646234653431396630386535366263363263663837643465393564
61653638643662363036353033373963303131613536663837323934313131343438386163626461
61383065313637396361333738663130316137656237306237636637326432646437663232396632
66626664663231343135333964623234343037393366396465353865393532666130643437653334
33613135356637383239303931396638393163643633653662323333363066333735336466613834
63626136333865666130336361333465633361353032336430386263323461633634306461316136
62386263383936316534313436646266663731666265643235643538336564313632663466323037
32333262326539376139653166633362633765333830613134393032383161343236613639356161
34653336363739313661386331313030323331623162333837326237656137636138303439326537
61366266316339306237373732636564616135376361373964373962656365393532366133663333
64376565613563386134373035643266346664626537363932636532613631313861353433663537
35363764363361303564643330383063303637393265623138343961373261636131356231303630
33643338376562343738663136643533663530343339383834633864393234613739633730306164
65636535346562643939316339616537363238376266643331303564316235663866343539336638
64636564626665326635656132353531633864343964633936396465313032663262373139643735
39653232326130393461346331323336323163646661623333663830346535646164396364383463
35333563383536306635666232653438373961626563663332363238666433303033376365396337
30326639356265363630646134376335636136633536366539666430343834653031303139343164
31666130663363373537613964393338633131383266346131623662346134353033323135313632
37336630643534393663353535386134663666383762313230356537653834333463353661616134
37613637636364316263663464363530373463363661333037653465383139303736376166333430
32653533313262366165353163666161336564633435333765366536356662313661333866643962
66396538386237636631363633643634326665306539663835393465646532646535386332313761
30323232353535393936306162626665333161353836313031383533656632346163343636616235
62663233666361623563643638663730346165323065356335306335343565626161663164613637
37653430323937323535646236336464663832663334383361376634396236666662376661613132
65316638636330643131313962373766353236306136393335393435303234303863356136353666
39613266383465363766313230663038333135663632303033616462636235383561376166643433
38633934613436343034663432313236396333336237326536346434313662643830383734643435
62383764643964353831663839363032333961613535626136333834613661306635663262323835
30623232373037313666323534346330306265383561646666616432623930353432363936393564
30643435376531666234626439373561646561316265363466363632666338353536666361313936
62613630316630343537623437333832623563376463646662636562333336623539653031636565
64393535643562323362343534353563383030343439666539323038663837613065303762626366
37376433376438656531396364353532353530663333343332633437656465353864313366646231
62323033313834313264663662326363376136336237656332313230373537663836643333623262
63653063656639663963643737643037363231626361616530323763623739366164393863373630
31643230316162356431336435376138303631623065626261366330396363633265383839633931
39313538663237353730346462343438643234376165333634663232636464643561346139356539
63376466643833353032333230356439336234343866323639356533626465353538633363633361
38383339366665366339653632373162363838366536343935316366303464623065376632646334
63646539643832363865383534326662626132643232353735396237373731303865353538396432
36376434323163346536353563386662356266306231323831353361383161633239643736313135
37383362636339643737326562393433323633356137656563643838313432386337343266386230
63636538633935323130663338653566326334613735393932303939316134326636346663613666
3338