Skip to content

Commit 8e7f6e9

Browse files
committed
Merge branch 'main' of github.com:devforth/adminforth
2 parents f283330 + 35ca466 commit 8e7f6e9

File tree

10 files changed

+383
-54
lines changed

10 files changed

+383
-54
lines changed

adminforth/documentation/blog/2024-10-31-compose-ec2-deployment/index.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,8 @@ resource "null_resource" "sync_files_and_run" {
220220
# -a would destroy cache
221221
"docker system prune -f",
222222
"cd /home/ubuntu/app/",
223-
"docker compose -f compose.yml up --build -d"
223+
# COMPOSE_FORCE_NO_TTY is needed to run docker compose in non-interactive mode and prevent stdout mess up
224+
"COMPOSE_FORCE_NO_TTY=1 docker compose -f compose.yml up --build -d"
224225
]
225226
226227
connection {

adminforth/documentation/blog/2024-11-14-compose-ec2-deployment-ci/index.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,8 @@ resource "null_resource" "sync_files_and_run" {
268268
"docker system prune -f",
269269
# "docker buildx prune -f --filter 'type!=exec.cachemount'",
270270
"cd /home/ubuntu/app/deploy",
271-
"docker compose -p app -f compose.yml up --build -d"
271+
# COMPOSE_FORCE_NO_TTY is needed to run docker compose in non-interactive mode and prevent stdout mess up
272+
"COMPOSE_FORCE_NO_TTY=1 docker compose -p app -f compose.yml up --build -d"
272273
]
273274
274275
connection {
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
---
2+
slug: backup-database-to-aws-glacier
3+
title: Backup database to AWS Glacier
4+
authors: ivanb
5+
tags: [aws, terraform]
6+
---
7+
8+
Every reliable system requires a backup strategy.
9+
10+
If you have no own backup infrastructure, here can suggest a small docker container that will help you to backup your database to AWS Glacier.
11+
12+
As a base guide we will use a previous blog post about [deploying adminforth infrastructure](/blog/compose-ec2-deployment-github-actions).
13+
14+
15+
First we need to allocate a new bucket in AWS S3 with modifying terraform configuration:
16+
17+
```hcl title="deploy/main.tf"
18+
resource "aws_s3_bucket" "backup_bucket" {
19+
bucket = "${local.app_name}-backups"
20+
}
21+
22+
resource "aws_s3_bucket_lifecycle_configuration" "backup_bucket" {
23+
bucket = aws_s3_bucket.backup_bucket.bucket
24+
25+
rule {
26+
id = "glacier-immediate"
27+
status = "Enabled"
28+
29+
transition {
30+
days = 0
31+
storage_class = "GLACIER"
32+
}
33+
}
34+
}
35+
36+
resource "aws_s3_bucket_server_side_encryption_configuration" "backup_bucket" {
37+
bucket = aws_s3_bucket.backup_bucket.bucket
38+
39+
rule {
40+
apply_server_side_encryption_by_default {
41+
sse_algorithm = "AES256"
42+
}
43+
}
44+
}
45+
46+
resource "aws_iam_user" "backup_user" {
47+
name = "${local.app_name}-backup-user"
48+
}
49+
50+
resource "aws_iam_access_key" "backup_user_key" {
51+
user = aws_iam_user.backup_user.name
52+
}
53+
54+
resource "aws_iam_user_policy" "backup_user_policy" {
55+
name = "${local.app_name}-backup-policy"
56+
user = aws_iam_user.backup_user.name
57+
58+
policy = jsonencode({
59+
Version = "2012-10-17"
60+
Statement = [
61+
{
62+
Effect = "Allow"
63+
Action = [
64+
"s3:PutObject",
65+
"s3:GetObject",
66+
"s3:DeleteObject",
67+
"s3:ListBucket"
68+
]
69+
Resource = [
70+
aws_s3_bucket.backup_bucket.arn,
71+
"${aws_s3_bucket.backup_bucket.arn}/*"
72+
]
73+
}
74+
]
75+
})
76+
}
77+
78+
```
79+
80+
Also add a section to main.tf to output the new bucket name and credentials:
81+
82+
```hcl title="deploy/main.tf"
83+
"docker system prune -f",
84+
# "docker buildx prune -f --filter 'type!=exec.cachemount'",
85+
"cd /home/ubuntu/app/deploy",
86+
//diff-add
87+
"echo 'AWS_BACKUP_ACCESS_KEY=${aws_iam_access_key.backup_user_key.id}' >> .env.live",
88+
//diff-add
89+
"echo 'AWS_BACKUP_SECRET_KEY=${aws_iam_access_key.backup_user_key.secret}' >> .env.live",
90+
//diff-add
91+
"echo 'AWS_BACKUP_BUCKET=${aws_s3_bucket.backup_bucket.id}' >> .env.live",
92+
//diff-add
93+
"echo 'AWS_BACKUP_REGION=${local.aws_region}' >> .env.live",
94+
95+
"docker compose -p app -f compose.yml --env-file ./.env.live up --build -d --quiet-pull"
96+
]
97+
```
98+
99+
100+
Add new service into compose file:
101+
102+
```yaml title="deploy/compose.yml"
103+
104+
database_glacierizer:
105+
image: devforth/docker-database-glacierizer:v1.7
106+
107+
environment:
108+
- PROJECT_NAME=MYAPP
109+
# do backup every day at 00:00
110+
- CRON=0 0 * * *
111+
112+
- DATABASE_TYPE=PostgreSQL
113+
- DATABASE_HOST=db
114+
- DATABASE_NAME=adminforth
115+
- DATABASE_USER=admin
116+
- DATABASE_PASSWORD=${VAULT_POSTGRES_PASSWORD}
117+
- GLACIER_EXPIRE_AFTER=90
118+
- GLACIER_STORAGE_CLASS=flexible
119+
- GLACIER_BUCKET_NAME=${AWS_BACKUP_BUCKET}
120+
121+
- AWS_DEFAULT_REGION=${AWS_BACKUP_REGION}
122+
- AWS_ACCESS_KEY_ID=${AWS_BACKUP_ACCESS_KEY}
123+
- AWS_SECRET_ACCESS_KEY=${AWS_BACKUP_SECRET_KEY}
124+
```
125+
126+
127+
128+
## Pricing
129+
130+
Just to give you row idea about pricing, here is a small calculation for case when you doing backup once per day (like in config)
131+
* Compressed database backup has size of 50 MB always and not growing. (Compression is already done by glacierizer)
132+
* Cost of glacier every month will be ~$0.80 after first 3 month, and will stay same every next month.
133+
* On first, second and third month cost will increase slowly from $0.00 to $0.80 per month.
134+

adminforth/documentation/docs/tutorial/05-Plugins/04-RichEditor.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ To get completion suggestions for the text in the editor, you can use the `compl
111111
//diff-add
112112
temperature: 0.7 //Model temperature, default 0.7
113113
//diff-add
114-
}
114+
}
115115
//diff-add
116116
}),
117117
//diff-add

adminforth/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,7 @@ class AdminForth implements IAdminForth {
296296

297297
// execute hook if needed
298298
for (const hook of listify(resource.hooks?.create?.beforeSave as BeforeSaveFunction[])) {
299+
console.log('🪲 Hook beforeSave', hook);
299300
const resp = await hook({
300301
recordId: undefined,
301302
resource,

adminforth/modules/configValidator.ts

Lines changed: 48 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ export default class ConfigValidator implements IConfigValidator {
297297
return [];
298298
}
299299
return this.inputConfig.resources.map((resInput: AdminForthResourceInput) => {
300-
const res: Partial<AdminForthResource> = { ...resInput, options: undefined };
300+
const res: Partial<AdminForthResource> = { ...resInput, options: undefined, hooks: undefined, };
301301
if (!res.table) {
302302
errors.push(`Resource in "${res.dataSource}" is missing table`);
303303
}
@@ -448,44 +448,62 @@ export default class ConfigValidator implements IConfigValidator {
448448
res.options = options as AdminForthResource['options'];
449449

450450
// transform all hooks Functions to array of functions
451-
if (!res.hooks) {
452-
res.hooks = {};
453-
}
451+
res.hooks = {};
454452
for (const hookName of ['show', 'list']) {
455-
if (!res.hooks[hookName]) {
456-
res.hooks[hookName] = {};
457-
}
458-
if (!res.hooks[hookName].beforeDatasourceRequest) {
459-
res.hooks[hookName].beforeDatasourceRequest = [];
453+
res.hooks[hookName] = {};
454+
res.hooks[hookName].beforeDatasourceRequest = [];
455+
456+
const bdr = resInput.hooks?.[hookName]?.beforeDatasourceRequest;
457+
if (!Array.isArray(bdr)) {
458+
if (bdr) {
459+
res.hooks[hookName].beforeDatasourceRequest = [bdr];
460+
} else {
461+
res.hooks[hookName].beforeDatasourceRequest = [];
462+
}
463+
} else {
464+
res.hooks[hookName].beforeDatasourceRequest = bdr;
460465
}
461466

462-
if (!Array.isArray(res.hooks[hookName].beforeDatasourceRequest)) {
463-
res.hooks[hookName].beforeDatasourceRequest = [res.hooks[hookName].beforeDatasourceRequest];
464-
}
467+
res.hooks[hookName].afterDatasourceResponse = [];
465468

466-
if (!res.hooks[hookName].afterDatasourceResponse) {
467-
res.hooks[hookName].afterDatasourceResponse = [];
468-
}
469+
const adr = resInput.hooks?.[hookName]?.afterDatasourceResponse;
469470

470-
if (!Array.isArray(res.hooks[hookName].afterDatasourceResponse)) {
471-
res.hooks[hookName].afterDatasourceResponse = [res.hooks[hookName].afterDatasourceResponse];
471+
if (!Array.isArray(adr)) {
472+
if (adr) {
473+
res.hooks[hookName].afterDatasourceResponse = [adr];
474+
} else {
475+
res.hooks[hookName].afterDatasourceResponse = [];
476+
}
477+
} else {
478+
res.hooks[hookName].afterDatasourceResponse = adr;
472479
}
473480
}
474481
for (const hookName of ['create', 'edit', 'delete']) {
475-
if (!res.hooks[hookName]) {
476-
res.hooks[hookName] = {};
477-
}
478-
if (!res.hooks[hookName].beforeSave) {
479-
res.hooks[hookName].beforeSave = [];
480-
}
481-
if (!Array.isArray(res.hooks[hookName].beforeSave)) {
482-
res.hooks[hookName].beforeSave = [res.hooks[hookName].beforeSave];
483-
}
484-
if (!res.hooks[hookName].afterSave) {
485-
res.hooks[hookName].afterSave = [];
482+
res.hooks[hookName] = {};
483+
res.hooks[hookName].beforeSave = [];
484+
485+
const bs = resInput.hooks?.[hookName]?.beforeSave;
486+
if (!Array.isArray(bs)) {
487+
if (bs) {
488+
res.hooks[hookName].beforeSave = [bs];
489+
} else {
490+
res.hooks[hookName].beforeSave = [];
491+
}
492+
} else {
493+
res.hooks[hookName].beforeSave = bs;
486494
}
487-
if (!Array.isArray(res.hooks[hookName].afterSave)) {
488-
res.hooks[hookName].afterSave = [res.hooks[hookName].afterSave];
495+
496+
res.hooks[hookName].afterSave = [];
497+
498+
const as = resInput.hooks?.[hookName]?.afterSave;
499+
if (!Array.isArray(as)) {
500+
if (as) {
501+
res.hooks[hookName].afterSave = [as];
502+
} else {
503+
res.hooks[hookName].afterSave = [];
504+
}
505+
} else {
506+
res.hooks[hookName].afterSave = as;
489507
}
490508
}
491509

0 commit comments

Comments
 (0)