Skip to content

Commit f548af2

Browse files
committed
Initial commit
0 parents  commit f548af2

File tree

6 files changed

+1053
-0
lines changed

6 files changed

+1053
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules
2+
.serverless
3+
serverless.yml

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# github-backup-aws
2+
3+
🛟 Easily backup all your Github repositories to AWS S3
4+
5+
Use AWS S3 and a scheduled Lambda.
6+
7+
## Usage
8+
9+
1. Copy `.serverless.example` and rename it `.serverless` and fill the config variables.
10+
11+
2. Install [Serverless Framework](https://github.com/serverless/serverless)
12+
```
13+
npm install -g serverless
14+
```
15+
16+
3Deploy to AWS
17+
```
18+
serverless deploy
19+
```
20+
21+
4. Enjoy 🥳

handler.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
'use strict';
2+
3+
import { Octokit } from 'octokit';
4+
import { simpleGit } from 'simple-git';
5+
import tar from 'tar'
6+
import fs from 'fs';
7+
import AWS from 'aws-sdk';
8+
import rmfr from 'rmfr';
9+
10+
export const backup = async (event, context, callback) => {
11+
const directory = `/tmp/${Date.now()}`;
12+
13+
try {
14+
const octokit = new Octokit({
15+
auth: process.env.GH_TOKEN
16+
});
17+
18+
const res = await octokit.request('GET /orgs/{org}/repos{?type,sort,direction,per_page,page}', {
19+
org: process.env.GH_ORGANIZATION,
20+
type: process.env.GH_VISIBILITY
21+
});
22+
23+
fs.mkdirSync(`/${directory}/repositories`, { recursive: true });
24+
25+
await Promise.all(res.data.map((item) => simpleGit().clone(
26+
`https://${process.env.GH_TOKEN}@github.com/${item.full_name}`,
27+
`/${directory}/repositories/${item.name}`,
28+
['--bare']
29+
)));
30+
31+
const filename = [
32+
(new Date()).toISOString().slice(0, 10),
33+
Date.now()
34+
].join('-') + '.tgz';
35+
36+
await tar.create(
37+
{gzip: true, file: `/${directory}/${filename}`},
38+
[`/${directory}/repositories`]
39+
);
40+
41+
const S3 = new AWS.S3({ region: process.env.REGION });
42+
43+
await S3.upload({
44+
Bucket: process.env.BUCKET,
45+
Body: fs.createReadStream(`/${directory}/${filename}`),
46+
Key: filename
47+
}).promise();
48+
49+
if (process.env.SUCCESS_PING_URL !== undefined && process.env.SUCCESS_PING_URL.trim().length > 0) {
50+
await fetch(process.env.SUCCESS_PING_URL);
51+
}
52+
} finally {
53+
await rmfr(directory);
54+
}
55+
};

package.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "github-backup-aws",
3+
"type": "module",
4+
"repository": "https://github.com/YieldStudio/github-backup-aws.git",
5+
"author": "James Hemery <[email protected]>",
6+
"license": "MIT",
7+
"dependencies": {
8+
"aws-sdk": "^2.1299.0",
9+
"octokit": "^2.0.13",
10+
"rmfr": "^2.0.0",
11+
"simple-git": "^3.16.0",
12+
"tar": "^6.1.13"
13+
}
14+
}

serverless.example

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
service: github-backup-aws
2+
3+
frameworkVersion: '3'
4+
5+
custom:
6+
bucket: "" # AWS Bucket name
7+
expirationInDays: 90 # Delay in days before delete old backups
8+
ghToken: "" # Github Personal Access Token (classic token, with repo and read:org scopes).
9+
ghOrganization: "" # Organization name
10+
ghVisibility: "all" # private, public or all
11+
region: eu-west-3 # AWS Region
12+
successPingUrl: "" # URL to ping after backup
13+
14+
provider:
15+
name: aws
16+
runtime: nodejs18.x
17+
region: ${self:custom.region}
18+
iam:
19+
role:
20+
statements:
21+
- Effect: Allow
22+
Action:
23+
- 's3:PutObject'
24+
Resource:
25+
- arn:aws:s3:::${self:custom.bucket}/*
26+
27+
functions:
28+
githubBackup:
29+
handler: handler.backup
30+
timeout: 300
31+
environment:
32+
REGION: ${self:custom.region}
33+
BUCKET: ${self:custom.bucket}
34+
GH_TOKEN: ${self:custom.ghToken}
35+
GH_ORGANIZATION: ${self:custom.ghOrganization}
36+
GH_VISIBILITY: ${self:custom.ghVisibility}
37+
SUCCESS_PING_URL: ${self:custom.successPingUrl}
38+
ephemeralStorageSize: 2048
39+
events:
40+
- schedule: cron(0 2 * * ? *)
41+
layers:
42+
- arn:aws:lambda:eu-west-3:553035198032:layer:git-lambda2:8
43+
44+
resources:
45+
Resources:
46+
Bucket:
47+
Type: AWS::S3::Bucket
48+
Properties:
49+
BucketName: ${self:custom.bucket}
50+
OwnershipControls:
51+
Rules:
52+
- ObjectOwnership: BucketOwnerEnforced
53+
PublicAccessBlockConfiguration:
54+
BlockPublicAcls: true
55+
BlockPublicPolicy: true
56+
IgnorePublicAcls: true
57+
RestrictPublicBuckets: true
58+
LifecycleConfiguration:
59+
Rules:
60+
- Id: RemoveOlderBackup
61+
Status: Enabled
62+
ExpirationInDays: ${self:custom.expirationInDays}

0 commit comments

Comments
 (0)