Summary ¶
Obsidian を利用する環境を整えているが、同期の仕組みを構築し終わったのでバックアップも設定しておく。
いくつも方法はあるが、今回は vrtmrz/obsidian-livesync
の作者の vrtmrz/diffzip
を利用することにした。これにより起動時に別途用意した S3 へ差分バックアップを転送するようにしておくことで端末間同期のやり直しやいざという時の切り戻しができるようにしておく。
S3 バケットの準備 ¶
AWS を利用する MinIO などでセルフホストすることが可能だが、外部からのアクセス経路やデータの防護策として最適な方法を取ることが難しく NAS に格納して一緒にデータが吹き飛ぶなど事象が発生すると立ち直れないため今回は Amazon S3 を利用する。
数十 GB の容量なので月額も安く、基本はアップロードのみのため問題ないだろう。
Topic
当初は Cloudflare R2 を利用する予定だったが、 Bucket に絞った権限が作成出来なかったため見送った。
方法はいくつかあるが、私は rain を試してみたかったため AWS CloudFormation で用意した。
- スタック名にランダム文字列を付ける
- IAM Group を作る
- IAM user を作る
- S3 を作成
- バケットポリシーを作成して http は拒否
- ライフサイクルを設定する
- マルチパートアップロードの残骸は3日後に消す
- 最新以外のバージョンは45日後に削除
obsidian-diffzip.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
| AWSTemplateFormatVersion: '2010-09-09'
Description: 'DiffZip Backup Infrastructure for Obsidian Plugin - Fixed Version'
Parameters:
BucketBaseName:
Type: String
Description: 'Base name for the S3 bucket (6 random characters will be appended)'
Default: 'obsidian-diffzip'
AllowedPattern: '^[a-z0-9][a-z0-9-]*[a-z0-9]$'
ConstraintDescription: 'Bucket name must be lowercase, start and end with alphanumeric characters'
MinLength: 3
MaxLength: 57
Region:
Type: String
Description: 'AWS Region for deployment'
Default: 'ap-northeast-1'
AllowedValues:
- ap-northeast-1 # Tokyo
- us-east-1 # N. Virginia
- us-west-2 # Oregon
Resources:
# Generate random suffix for bucket name
RandomSuffixLambdaRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
RandomSuffixFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: !Sub '${AWS::StackName}-random-suffix-generator'
Runtime: python3.9
Handler: index.handler
Role: !GetAtt RandomSuffixLambdaRole.Arn
Code:
ZipFile: |
import json
import random
import string
import cfnresponse
def handler(event, context):
try:
if event['RequestType'] == 'Delete':
cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
return
# Generate 6 character random suffix
chars = string.ascii_lowercase + string.digits
suffix = ''.join(random.choice(chars) for _ in range(6))
response_data = {'RandomSuffix': suffix}
cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)
except Exception as e:
print(f"Error: {str(e)}")
cfnresponse.send(event, context, cfnresponse.FAILED, {})
RandomSuffix:
Type: AWS::CloudFormation::CustomResource
Properties:
ServiceToken: !GetAtt RandomSuffixFunction.Arn
# S3 Bucket for DiffZip backups
DiffZipBackupBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub '${BucketBaseName}-${RandomSuffix.RandomSuffix}'
CorsConfiguration:
CorsRules:
- AllowedHeaders:
- amz-sdk-invocation-id
- amz-sdk-request
- authorization
- content-type
- x-amz-content-sha256
- x-amz-date
- x-amz-user-agent
AllowedMethods:
- GET
- PUT
- HEAD
AllowedOrigins:
- app://obsidian.md
- capacitor://localhost
- http://localhost:*
MaxAge: 3600
# Block all public access
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
# Enable versioning
VersioningConfiguration:
Status: Enabled
# Lifecycle configuration
LifecycleConfiguration:
Rules:
# Clean up incomplete multipart uploads after 3 days
- Id: CleanupIncompleteMultipartUploads
Status: Enabled
AbortIncompleteMultipartUpload:
DaysAfterInitiation: 3
# Delete non-current versions after 45 days
- Id: DeleteNonCurrentVersions
Status: Enabled
NoncurrentVersionExpiration:
NoncurrentDays: 45
# Encryption configuration
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
Tags:
- Key: Purpose
Value: DiffZipBackup
- Key: Application
Value: Obsidian
# IAM Group for DiffZip backup operations
DiffZipBackupGroup:
Type: AWS::IAM::Group
Properties:
GroupName: !Sub '${AWS::StackName}-group-${RandomSuffix.RandomSuffix}'
Policies:
- PolicyName: DiffZipBackupPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
# Allow read/write operations on backup objects
- Sid: DiffZipBackupOperations
Effect: Allow
Action:
- s3:GetObject
- s3:PutObject
Resource:
- !Sub '${DiffZipBackupBucket.Arn}/*'
# Allow bucket-level operations
- Sid: DiffZipBackupBucketAccess
Effect: Allow
Action:
- s3:ListBucket
Resource:
- !Sub '${DiffZipBackupBucket.Arn}'
# IAM User for DiffZip backup access
DiffZipBackupUser:
Type: AWS::IAM::User
Properties:
UserName: !Sub '${AWS::StackName}-user-${RandomSuffix.RandomSuffix}'
Groups:
- !Ref DiffZipBackupGroup
Tags:
- Key: Purpose
Value: DiffZipBackup
# S3 Bucket Policy for additional security
DiffZipBackupBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref DiffZipBackupBucket
PolicyDocument:
Version: '2012-10-17'
Statement:
# Deny insecure connections
- Sid: DenyInsecureConnections
Effect: Deny
Principal: '*'
Action: 's3:*'
Resource:
- !Sub '${DiffZipBackupBucket.Arn}/*'
- !Sub '${DiffZipBackupBucket.Arn}'
Condition:
Bool:
'aws:SecureTransport': 'false'
Outputs:
BucketRegion:
Description: 'Region of the created S3 bucket'
Value: !Ref AWS::Region
Export:
Name: !Sub '${AWS::StackName}-BucketRegion'
S3Endpoint:
Description: 'S3 endpoint for the region'
Value: !Sub 'https://${DiffZipBackupBucket}.s3.${AWS::Region}.amazonaws.com'
Export:
Name: !Sub '${AWS::StackName}-S3Endpoint'
IAMUserName:
Description: 'Name of the created IAM user'
Value: !Ref DiffZipBackupUser
Export:
Name: !Sub '${AWS::StackName}-IAMUserName'
NextSteps:
Description: 'Next steps to complete the setup'
Value: !Sub |
Create access key for user ${DiffZipBackupUser} using: aws iam create-access-key --user-name ${DiffZipBackupUser}
|
1
2
3
4
5
| > export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
> export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
> export AWS_DEFAULT_REGION=ap-northeast-1
> ./rain deploy obsidian-diffzip.yml
|
構築後下記に情報が出るためこれを Obsidian のプラグイン画面に入力する
1
2
3
4
5
6
7
8
| Deploying template 'obsidian-diffzip.yml' as stack 'obsidian-diffzip' in ap-northeast-1.
Stack obsidian-diffzip: UPDATE_COMPLETE
Outputs:
NextSteps: Create access key for user obsidian-diffzip-user-b5ecge using: aws iam create-access-key --user-name obsidian-diffzip-user-b5ecge
BucketRegion: ap-northeast-1
S3Endpoint: https://obsidian-diffzip-b5ecge.s3.ap-northeast-1.amazonaws.com
IAMUserName: obsidian-diffzip-user-b5ecge
Successfully updated obsidian-diffzip
|
AccessKey, SecretKey は NextSteps
記載のコマンドで作成できる
Include hidden folders
はお好みで設定するBucket
は本来は S3 Bucket 名を設定するが今回は CORS 設定の都合で Endpoint にBucket名を含めているので変更しないTest
ボタンを押せば接続テストができる

これで Obsidian 起動時にバックアップが自動で作成され S3 に転送されるようになった。