Skip to content

Commit

Permalink
feat: Initial draft (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
ItsJustRuby authored Dec 29, 2024
1 parent 929b16e commit 80f2530
Show file tree
Hide file tree
Showing 36 changed files with 377 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[*]
end_of_line = lf
insert_final_newline = true
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
CARBS_BACKUP_TARGETS=/exampleInput
CARBS_CRONTAB=11 * * * *
CARBS_PASSWORD=example
CARBS_PATH=demo
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @ItsJustRuby
17 changes: 17 additions & 0 deletions .github/actionlint-matcher.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"problemMatcher": [
{
"owner": "actionlint",
"pattern": [
{
"regexp": "^(?:\\x1b\\[\\d+m)?(.+?)(?:\\x1b\\[\\d+m)*:(?:\\x1b\\[\\d+m)*(\\d+)(?:\\x1b\\[\\d+m)*:(?:\\x1b\\[\\d+m)*(\\d+)(?:\\x1b\\[\\d+m)*: (?:\\x1b\\[\\d+m)*(.+?)(?:\\x1b\\[\\d+m)* \\[(.+?)\\]$",
"file": 1,
"line": 2,
"column": 3,
"message": 4,
"code": 5
}
]
}
]
}
30 changes: 30 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
day: "sunday"
groups:
devenv:
patterns:
- "*"

- package-ecosystem: "docker"
directory: "/src"
schedule:
interval: "weekly"
groups:
docker:
patterns:
- "*"

- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "sunday"
groups:
actions:
patterns:
- "*"
4 changes: 4 additions & 0 deletions .github/workflows/.lintstagedrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default {
"**/*.yml": "actionlint",
};

50 changes: 50 additions & 0 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Continuous Deployment

on:
push:
branches: ["main"]
workflow_dispatch:

jobs:
build-and-push:
name: "Build & push Docker image"
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Create full Docker image name
run: echo "IMAGE_NAME=$(echo "ghcr.io/${{ github.repository_owner }}/carbs" | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_ENV"

- name: Log in to the Container registry
uses: docker/[email protected]
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Generate metadata
id: meta
uses: docker/[email protected]
with:
images: ${{ env.IMAGE_NAME }}
tags: |
type=sha
type=ref,event=tag
type=raw,value=latest,enable={{is_default_branch}}
- uses: docker/build-push-action@v6
name: "Build & push docker image"
with:
context: src
file: src/Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
Deploy:
runs-on: ubuntu-latest
needs: [build-and-push]
steps:
- name: Ping deployment to pull new images
run: |
curl -X POST -H 'Authorization: Bearer ${{ secrets.DEPLOYMENT_TOKEN }}' ${{ secrets.DEPLOYMENT_API_URL }}
39 changes: 39 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Lint

on:
- push
- workflow_dispatch

jobs:
Dockerfiles:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Run hadolint
uses: hadolint/[email protected]
with:
dockerfile: "src/Dockerfile"
github-action-workflows:
name: "GitHub Action workflows"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Download actionlint
id: get_actionlint
shell: bash
run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash)
- name: Run linter
shell: bash
run: |
echo "::add-matcher::.github/actionlint-matcher.json"
${{ steps.get_actionlint.outputs.executable }} -color
shellcheck:
name: "Shell scripts"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run ShellCheck
uses: ludeeus/[email protected]
with:
additional_files: "pre-commit"
12 changes: 12 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: Tests

on:
- push
- workflow_dispatch

jobs:
Tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: sh scripts/tests.sh
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules/
.env.dev
4 changes: 4 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh
set -e

lint-staged
3 changes: 3 additions & 0 deletions .lintstagedrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
"**/*.{md,js,json,yml}": "bun run prettier --write",
};
Empty file added .prettierrc
Empty file.
1 change: 1 addition & 0 deletions .shellcheckrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
external-sources=true
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
# carbs
# carbs

Carbs is a **c**ron **a**utomatic **r**egular **b**ackup **s**script.

It runs in a docker container and uses [restic](https://github.com/restic/restic) and [rclone](https://github.com/rclone/rclone) to back up local files to some remote location.
16 changes: 16 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# https://taskfile.dev

version: "3"

vars:
GREETING: Hello, World!

tasks:
default:
aliases: ["dev"]
cmds:
- sh ./scripts/dev.sh

tests:
cmds:
- sh ./scripts/tests.sh
Binary file added bun.lockb
Binary file not shown.
6 changes: 6 additions & 0 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
services:
main:
env_file: .env.dev
volumes:
- ./example/config:/config
- ./example/input:/exampleInput:ro
10 changes: 10 additions & 0 deletions docker-compose.tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
services:
main:
container_name: carbs-tests
env_file: .env.example
environment:
# Note: You never want this in production.
CARBS_EXIT_ON_SUCCESS: 1
volumes:
- ./example/config:/config
- ./example/input:/exampleInput:ro
6 changes: 6 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
services:
main:
build:
dockerfile: Dockerfile
context: src
container_name: carbs-main
1 change: 1 addition & 0 deletions example/config/exclude.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/input/skipMe.txt
2 changes: 2 additions & 0 deletions example/config/rclone.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[example-local]
type = local
1 change: 1 addition & 0 deletions example/input/backMeUp.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Magic!
1 change: 1 addition & 0 deletions example/input/skipMe.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
I'm not here!
13 changes: 13 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "carbs",
"type": "module",
"devDependencies": {
"husky": "^9.1.7",
"lint-staged": "^15.3.0",
"prettier": "^3.4.2",
"shellcheck": "^3.0.0"
},
"scripts": {
"prepare": "husky"
}
}
6 changes: 6 additions & 0 deletions scripts/dev.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/sh
set -ev

env COMPOSE_PATH_SEPARATOR=: \
COMPOSE_FILE=docker-compose.yml:docker-compose.dev.yml \
docker compose up -d --build
6 changes: 6 additions & 0 deletions scripts/tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/sh
set -ev

env COMPOSE_PATH_SEPARATOR=: \
COMPOSE_FILE=docker-compose.yml:docker-compose.tests.yml \
docker compose up -d --build
3 changes: 3 additions & 0 deletions src/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.lintstagedrc.js
Dockerfile
restore.sh
3 changes: 3 additions & 0 deletions src/.lintstagedrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
"*.sh": "npx shellcheck",
};
10 changes: 10 additions & 0 deletions src/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM rclone/rclone:1.68.2 AS rclone
FROM restic/restic:0.17.3
COPY --from=rclone /usr/local/bin/rclone /usr/local/bin/rclone

RUN mkdir /app
WORKDIR /app

COPY . .

ENTRYPOINT [ "/bin/sh", "/app/install.sh" ]
18 changes: 18 additions & 0 deletions src/backup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/sh
set -ev

echo "Starting backup..."

restic backup "$CARBS_BACKUP_TARGETS" --exclude-file=/config/exclude.txt --verbose
echo "Backup complete."

echo "Pruning old snapshots..."

# For the last n months which have one or more snapshots,
# keep only the most recent k for each month.
restic forget --prune --keep-last "${CARBS_KEEP_LAST:-1}" --keep-monthly "${CARBS_KEEP_MONTHLY:-3}"
echo "Pruning complete."

echo "Checking backup for consistency..."
restic check
echo "Check complete."
9 changes: 9 additions & 0 deletions src/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/sh
set -e

# shellcheck source-path=SCRIPTDIR
. "$(dirname "$0")/install_rclone.sh"
# shellcheck source-path=SCRIPTDIR
. "$(dirname "$0")/install_restic.sh"
# shellcheck source-path=SCRIPTDIR
. "$(dirname "$0")/install_cronjob.sh"
21 changes: 21 additions & 0 deletions src/install_cronjob.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/bin/sh

if [ -z "${CARBS_CRONTAB}" ]; then
echo "You must configure an env variable CARBS_CRONTAB."
exit 1
fi

echo "Installing backup job using crontab of '${CARBS_CRONTAB}'"
# Take crontab from ENV variable and install it as a cronjob.
# Note that you can't just overwrite the current crontab file in place - this won't trigger crond to reload it.
echo "${CARBS_CRONTAB} /carbs/backup.sh >> /carbs/cron.log 2>&1" > ./new.crontab
crontab ./new.crontab

if [ -n "${CARBS_EXIT_ON_SUCCESS}" ]; then
echo "Finished, exiting as configured (instead of starting cron daemon)."
exit 0
fi

# Run the cron daemon in the foreground, with log level 8, printing to stderr.
echo "Starting cron daemon"
/usr/sbin/crond -f -d 8
11 changes: 11 additions & 0 deletions src/install_rclone.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/sh
set -e

if [ ! -f "/config/rclone.conf" ]; then
echo "You must mount an rclone config file at /config/rclone.conf."
exit 2
fi

echo "Writing rclone config to file"
mkdir -p "$HOME/.config/rclone"
cp /config/rclone.conf "$HOME/.config/rclone/rclone.conf"
37 changes: 37 additions & 0 deletions src/install_restic.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/bin/sh

if [ -z "${CARBS_BACKUP_TARGETS}" ]; then
echo "You must configure CARBS_BACKUP_TARGETS."
exit 4
fi

if [ -z "${CARBS_PATH}" ]; then
echo "You must configure a CARBS_PATH for the remote."
exit 8
fi

if [ -z "${CARBS_PASSWORD}" ]; then
echo "You must configure a CARBS_PASSWORD for the remote."
exit 8
fi

CARBS_REMOTE=$(rclone config dump | jq -r 'to_entries[0]["key"]')

# Magic env vars for restic to access the remote
export RESTIC_REPOSITORY="rclone:$CARBS_REMOTE:$CARBS_PATH"
export RESTIC_PASSWORD="$CARBS_PASSWORD"

echo "Checking if repository exists..."
(restic cat config > /dev/null) && true
REPOSITORY_EXISTS=$?

if [ "$REPOSITORY_EXISTS" -ne 0 ]
then
echo "It does not, initializing..."
restic init
else
echo "It does, continuing."
fi

# Create ignorefile if it does not already exist
touch /config/exclude.txt
Loading

0 comments on commit 80f2530

Please sign in to comment.