phase 0: project bootstrap
P0-01 Go module + cmd/server + cmd/agent skeletons + internal/ tree
P0-02 LICENSE (PolyForm NC 1.0.0), README, CONTRIBUTING
P0-03 golangci-lint, pre-commit, .editorconfig, .gitignore
P0-04 Gitea Actions CI: test (race+coverage), lint, cross-platform build matrix
P0-05 Dockerfile.server (multi-stage, distroless/static), docker-compose.yml
P0-06 Makefile with build/test/lint/fmt/run/release targets
build, vet, test, and cross-compile to linux/{amd64,arm64} + windows/amd64
all verified locally.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,18 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.go]
|
||||||
|
indent_style = tab
|
||||||
|
|
||||||
|
[Makefile]
|
||||||
|
indent_style = tab
|
||||||
|
|
||||||
|
[*.{md,yml,yaml,json}]
|
||||||
|
indent_size = 2
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
env:
|
||||||
|
GO_VERSION: "1.23"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
name: Test (linux/amd64)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
cache: true
|
||||||
|
- name: go vet
|
||||||
|
run: go vet ./...
|
||||||
|
- name: go test
|
||||||
|
run: go test -race -coverprofile=coverage.out ./...
|
||||||
|
- name: coverage summary
|
||||||
|
run: go tool cover -func=coverage.out | tail -1
|
||||||
|
|
||||||
|
lint:
|
||||||
|
name: Lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
cache: true
|
||||||
|
- uses: golangci/golangci-lint-action@v6
|
||||||
|
with:
|
||||||
|
version: v1.61.0
|
||||||
|
args: --timeout=5m
|
||||||
|
|
||||||
|
build:
|
||||||
|
name: Build (${{ matrix.goos }}/${{ matrix.goarch }})
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- goos: linux
|
||||||
|
goarch: amd64
|
||||||
|
- goos: linux
|
||||||
|
goarch: arm64
|
||||||
|
- goos: windows
|
||||||
|
goarch: amd64
|
||||||
|
ext: ".exe"
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
cache: true
|
||||||
|
- name: build server + agent
|
||||||
|
env:
|
||||||
|
GOOS: ${{ matrix.goos }}
|
||||||
|
GOARCH: ${{ matrix.goarch }}
|
||||||
|
CGO_ENABLED: "0"
|
||||||
|
run: |
|
||||||
|
mkdir -p bin
|
||||||
|
go build -trimpath -ldflags="-s -w" \
|
||||||
|
-o bin/restic-manager-server-${{ matrix.goos }}-${{ matrix.goarch }}${{ matrix.ext }} \
|
||||||
|
./cmd/server
|
||||||
|
go build -trimpath -ldflags="-s -w" \
|
||||||
|
-o bin/restic-manager-agent-${{ matrix.goos }}-${{ matrix.goarch }}${{ matrix.ext }} \
|
||||||
|
./cmd/agent
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: binaries-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||||
|
path: bin/*
|
||||||
|
retention-days: 7
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
# Build output
|
||||||
|
/bin/
|
||||||
|
/dist/
|
||||||
|
|
||||||
|
# Local data / runtime state
|
||||||
|
/data/
|
||||||
|
/certs/
|
||||||
|
*.db
|
||||||
|
*.db-journal
|
||||||
|
*.db-wal
|
||||||
|
*.db-shm
|
||||||
|
|
||||||
|
# Editor / OS
|
||||||
|
.DS_Store
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# Coverage
|
||||||
|
coverage.out
|
||||||
|
coverage.html
|
||||||
|
|
||||||
|
# Local environment overrides
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
*.local
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
run:
|
||||||
|
timeout: 5m
|
||||||
|
tests: true
|
||||||
|
|
||||||
|
linters:
|
||||||
|
disable-all: true
|
||||||
|
enable:
|
||||||
|
- errcheck
|
||||||
|
- gosimple
|
||||||
|
- govet
|
||||||
|
- ineffassign
|
||||||
|
- staticcheck
|
||||||
|
- unused
|
||||||
|
- gofumpt
|
||||||
|
- goimports
|
||||||
|
- misspell
|
||||||
|
- revive
|
||||||
|
- bodyclose
|
||||||
|
- errorlint
|
||||||
|
- nilerr
|
||||||
|
- prealloc
|
||||||
|
- unconvert
|
||||||
|
- unparam
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
goimports:
|
||||||
|
local-prefixes: gitea.dcglab.co.uk/steve/restic-manager
|
||||||
|
revive:
|
||||||
|
rules:
|
||||||
|
- name: exported
|
||||||
|
arguments: ["disableStutteringCheck"]
|
||||||
|
misspell:
|
||||||
|
locale: US
|
||||||
|
|
||||||
|
issues:
|
||||||
|
exclude-rules:
|
||||||
|
- path: _test\.go
|
||||||
|
linters:
|
||||||
|
- errcheck
|
||||||
|
- unparam
|
||||||
|
max-issues-per-linter: 0
|
||||||
|
max-same-issues: 0
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
repos:
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v4.6.0
|
||||||
|
hooks:
|
||||||
|
- id: trailing-whitespace
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
- id: check-yaml
|
||||||
|
- id: check-added-large-files
|
||||||
|
args: ["--maxkb=512"]
|
||||||
|
- id: check-merge-conflict
|
||||||
|
- id: mixed-line-ending
|
||||||
|
args: ["--fix=lf"]
|
||||||
|
|
||||||
|
- repo: https://github.com/dnephin/pre-commit-golang
|
||||||
|
rev: v0.5.1
|
||||||
|
hooks:
|
||||||
|
- id: go-fmt
|
||||||
|
- id: go-imports
|
||||||
|
- id: go-vet-mod
|
||||||
|
- id: go-mod-tidy
|
||||||
|
|
||||||
|
- repo: https://github.com/golangci/golangci-lint
|
||||||
|
rev: v1.61.0
|
||||||
|
hooks:
|
||||||
|
- id: golangci-lint
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Contributing
|
||||||
|
|
||||||
|
Thanks for your interest in contributing to restic-manager.
|
||||||
|
|
||||||
|
> This is a placeholder. The project is in pre-alpha (Phase 1 / MVP). A
|
||||||
|
> full contributor guide will land alongside the Phase 5 OSS-readiness
|
||||||
|
> work — see [`tasks.md`](./tasks.md) P5-02. Until then the notes below
|
||||||
|
> apply.
|
||||||
|
|
||||||
|
## Before opening a PR
|
||||||
|
|
||||||
|
1. Open an issue first for non-trivial changes — the design is still
|
||||||
|
moving (see [`spec.md`](./spec.md)) and unsolicited large PRs may
|
||||||
|
conflict with in-flight work.
|
||||||
|
2. `make lint test` should pass.
|
||||||
|
3. Match the existing code style — `gofumpt`, `goimports`, no comments
|
||||||
|
that just restate what the code does.
|
||||||
|
4. Keep commits focused; one logical change per commit.
|
||||||
|
|
||||||
|
## Reporting security issues
|
||||||
|
|
||||||
|
Please do **not** open a public issue for security problems. A
|
||||||
|
`SECURITY.md` with a private disclosure path will be added in Phase 5
|
||||||
|
(P5-05). Until then, contact the repository owner directly via the
|
||||||
|
contact details on their gitea profile.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
By contributing you agree that your contributions are licensed under
|
||||||
|
the [PolyForm Noncommercial 1.0.0](./LICENSE) license.
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
PolyForm Noncommercial License 1.0.0
|
||||||
|
|
||||||
|
<https://polyformproject.org/licenses/noncommercial/1.0.0>
|
||||||
|
|
||||||
|
## Acceptance
|
||||||
|
|
||||||
|
In order to get any license under these terms, you must agree to them
|
||||||
|
as both strict obligations and conditions to all your licenses.
|
||||||
|
|
||||||
|
## Copyright License
|
||||||
|
|
||||||
|
The licensor grants you a copyright license for the software to do
|
||||||
|
everything you might do with the software that would otherwise infringe
|
||||||
|
the licensor's copyright in it for any permitted purpose. However, you
|
||||||
|
may only distribute the software according to Distribution License and
|
||||||
|
make changes or new works based on the software according to Changes
|
||||||
|
and New Works License.
|
||||||
|
|
||||||
|
## Distribution License
|
||||||
|
|
||||||
|
The licensor grants you an additional copyright license to distribute
|
||||||
|
copies of the software. Your license to distribute covers distributing
|
||||||
|
the software with changes and new works permitted by Changes and New
|
||||||
|
Works License.
|
||||||
|
|
||||||
|
## Notices
|
||||||
|
|
||||||
|
You must ensure that anyone who gets a copy of any part of the software
|
||||||
|
from you also gets a copy of these terms or the URL for them above, as
|
||||||
|
well as copies of any plain-text lines beginning with "Required Notice:"
|
||||||
|
that the licensor provided with the software. For example:
|
||||||
|
|
||||||
|
> Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
|
||||||
|
|
||||||
|
## Changes and New Works License
|
||||||
|
|
||||||
|
The licensor grants you an additional copyright license to make changes
|
||||||
|
and new works based on the software for any permitted purpose.
|
||||||
|
|
||||||
|
## Patent License
|
||||||
|
|
||||||
|
The licensor grants you a patent license for the software that covers
|
||||||
|
patent claims the licensor can license, or becomes able to license,
|
||||||
|
that you would infringe by using the software.
|
||||||
|
|
||||||
|
## Noncommercial Purposes
|
||||||
|
|
||||||
|
Any noncommercial purpose is a permitted purpose.
|
||||||
|
|
||||||
|
## Personal Uses
|
||||||
|
|
||||||
|
Personal use for research, experiment, and testing for the benefit of
|
||||||
|
public knowledge, personal study, private entertainment, hobby projects,
|
||||||
|
amateur pursuits, or religious observance, without any anticipated
|
||||||
|
commercial application, is use for a permitted purpose.
|
||||||
|
|
||||||
|
## Noncommercial Organizations
|
||||||
|
|
||||||
|
Use by any charitable organization, educational institution, public
|
||||||
|
research organization, public safety or health organization,
|
||||||
|
environmental protection organization, or government institution is use
|
||||||
|
for a permitted purpose regardless of the source of funding or
|
||||||
|
obligations resulting from the funding.
|
||||||
|
|
||||||
|
## Fair Use
|
||||||
|
|
||||||
|
You may have "fair use" rights for the software under the law. These
|
||||||
|
terms do not limit them.
|
||||||
|
|
||||||
|
## No Other Rights
|
||||||
|
|
||||||
|
These terms do not allow you to sublicense or transfer any of your
|
||||||
|
licenses to anyone else, or prevent the licensor from granting licenses
|
||||||
|
to anyone else. These terms do not imply any other licenses.
|
||||||
|
|
||||||
|
## Patent Defense
|
||||||
|
|
||||||
|
If you make any written claim that the software infringes or contributes
|
||||||
|
to infringement of any patent, your patent license for the software
|
||||||
|
granted under these terms ends immediately. If your company makes such
|
||||||
|
a claim, your patent license ends immediately for work on behalf of
|
||||||
|
your company.
|
||||||
|
|
||||||
|
## Violations
|
||||||
|
|
||||||
|
The first time you are notified in writing that you have violated any
|
||||||
|
of these terms, or done anything with the software not covered by your
|
||||||
|
licenses, your licenses can nonetheless continue if you come into full
|
||||||
|
compliance with these terms, and take practical steps to correct past
|
||||||
|
violations, within 32 days of receiving notice. Otherwise, all your
|
||||||
|
licenses end immediately.
|
||||||
|
|
||||||
|
## No Liability
|
||||||
|
|
||||||
|
***As far as the law allows, the software comes as is, without any
|
||||||
|
warranty or condition, and the licensor will not be liable to you for
|
||||||
|
any damages arising out of these terms or the use or nature of the
|
||||||
|
software, under any kind of legal claim.***
|
||||||
|
|
||||||
|
## Definitions
|
||||||
|
|
||||||
|
The **licensor** is the individual or entity offering these terms, and
|
||||||
|
the **software** is the software the licensor makes available under
|
||||||
|
these terms.
|
||||||
|
|
||||||
|
**You** refers to the individual or entity agreeing to these terms.
|
||||||
|
|
||||||
|
**Your company** is any legal entity, sole proprietorship, or other
|
||||||
|
kind of organization that you work for, plus all organizations that
|
||||||
|
have control over, are under the control of, or are under common
|
||||||
|
control with that organization. *Control* means ownership of
|
||||||
|
substantially all the assets of an entity, or the power to direct its
|
||||||
|
management and policies by vote, contract, or otherwise. Control can
|
||||||
|
be direct or indirect.
|
||||||
|
|
||||||
|
**Your licenses** are all the licenses granted to you for the software
|
||||||
|
under these terms.
|
||||||
|
|
||||||
|
**Use** means anything you do with the software requiring one of your
|
||||||
|
licenses.
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# restic-manager — common dev targets
|
||||||
|
|
||||||
|
SHELL := /bin/bash
|
||||||
|
BIN_DIR := bin
|
||||||
|
SERVER_BIN := $(BIN_DIR)/restic-manager-server
|
||||||
|
AGENT_BIN := $(BIN_DIR)/restic-manager-agent
|
||||||
|
VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo dev)
|
||||||
|
LDFLAGS := -s -w -X main.version=$(VERSION)
|
||||||
|
GOFLAGS := -trimpath
|
||||||
|
DOCKER_IMAGE ?= ghcr.io/dcglab/restic-manager
|
||||||
|
DOCKER_TAG ?= dev
|
||||||
|
|
||||||
|
.PHONY: help build server agent test test-race lint fmt tidy clean run-server run-agent docker release
|
||||||
|
|
||||||
|
help:
|
||||||
|
@grep -E '^[a-zA-Z_-]+:.*?## ' $(MAKEFILE_LIST) | awk 'BEGIN{FS=":.*?## "};{printf " \033[36m%-14s\033[0m %s\n",$$1,$$2}'
|
||||||
|
|
||||||
|
build: server agent ## Build server + agent into ./bin
|
||||||
|
|
||||||
|
server: ## Build the server binary
|
||||||
|
@mkdir -p $(BIN_DIR)
|
||||||
|
CGO_ENABLED=0 go build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(SERVER_BIN) ./cmd/server
|
||||||
|
|
||||||
|
agent: ## Build the agent binary
|
||||||
|
@mkdir -p $(BIN_DIR)
|
||||||
|
CGO_ENABLED=0 go build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(AGENT_BIN) ./cmd/agent
|
||||||
|
|
||||||
|
test: ## Run tests
|
||||||
|
go test ./...
|
||||||
|
|
||||||
|
test-race: ## Run tests with the race detector
|
||||||
|
go test -race -coverprofile=coverage.out ./...
|
||||||
|
|
||||||
|
lint: ## Run golangci-lint
|
||||||
|
golangci-lint run ./...
|
||||||
|
|
||||||
|
fmt: ## Format with gofumpt + goimports
|
||||||
|
gofumpt -w .
|
||||||
|
goimports -local gitea.dcglab.co.uk/steve/restic-manager -w .
|
||||||
|
|
||||||
|
tidy: ## go mod tidy
|
||||||
|
go mod tidy
|
||||||
|
|
||||||
|
clean: ## Remove build artifacts
|
||||||
|
rm -rf $(BIN_DIR) coverage.out coverage.html
|
||||||
|
|
||||||
|
run-server: server ## Build and run the server
|
||||||
|
$(SERVER_BIN)
|
||||||
|
|
||||||
|
run-agent: agent ## Build and run the agent
|
||||||
|
$(AGENT_BIN)
|
||||||
|
|
||||||
|
docker: ## Build the server Docker image
|
||||||
|
docker build -f deploy/Dockerfile.server --build-arg VERSION=$(VERSION) -t $(DOCKER_IMAGE):$(DOCKER_TAG) .
|
||||||
|
|
||||||
|
release: ## Cross-compile for all supported platforms
|
||||||
|
@mkdir -p $(BIN_DIR)
|
||||||
|
@for target in linux/amd64 linux/arm64 windows/amd64; do \
|
||||||
|
goos=$${target%/*}; goarch=$${target#*/}; \
|
||||||
|
ext=""; if [ "$$goos" = "windows" ]; then ext=".exe"; fi; \
|
||||||
|
echo "==> $$goos/$$goarch"; \
|
||||||
|
GOOS=$$goos GOARCH=$$goarch CGO_ENABLED=0 \
|
||||||
|
go build $(GOFLAGS) -ldflags "$(LDFLAGS)" \
|
||||||
|
-o $(BIN_DIR)/restic-manager-server-$$goos-$$goarch$$ext ./cmd/server; \
|
||||||
|
GOOS=$$goos GOARCH=$$goarch CGO_ENABLED=0 \
|
||||||
|
go build $(GOFLAGS) -ldflags "$(LDFLAGS)" \
|
||||||
|
-o $(BIN_DIR)/restic-manager-agent-$$goos-$$goarch$$ext ./cmd/agent; \
|
||||||
|
done
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
# restic-manager
|
||||||
|
|
||||||
|
Self-hosted, browser-based, single-pane-of-glass for managing
|
||||||
|
[restic](https://restic.net) backups across a fleet of Linux and Windows
|
||||||
|
endpoints.
|
||||||
|
|
||||||
|
> Status: pre-alpha. Phase 0 (project bootstrap) complete; Phase 1 (MVP) in
|
||||||
|
> progress. See [`spec.md`](./spec.md) for the design and
|
||||||
|
> [`tasks.md`](./tasks.md) for the roadmap.
|
||||||
|
|
||||||
|
## What it does (target)
|
||||||
|
|
||||||
|
- Central visibility into backup state for every endpoint
|
||||||
|
- Trigger any restic operation remotely (`backup`, `forget`, `prune`,
|
||||||
|
`check`, `unlock`, `snapshots`, `stats`, `diff`, `restore`)
|
||||||
|
- Manage per-host backup schedules from the UI
|
||||||
|
- Live job progress streamed back to the UI
|
||||||
|
- Restore wizard (browse snapshots, pick paths, restore to original or
|
||||||
|
alternate host)
|
||||||
|
- Repo health surfacing (size, dedup ratio, last check, lock state)
|
||||||
|
- Alerting on failure or staleness
|
||||||
|
- Cross-platform agent (Linux + Windows)
|
||||||
|
- Ransomware-resistant repo access via append-only credentials
|
||||||
|
|
||||||
|
## Architecture (one-line summary)
|
||||||
|
|
||||||
|
A small Go control-plane on the Proxmox host, lightweight Go agents on each
|
||||||
|
endpoint that hold an outbound WebSocket to the control-plane, and a
|
||||||
|
`restic/rest-server` on Unraid that holds the actual backup data. The
|
||||||
|
control-plane never touches backup bytes.
|
||||||
|
|
||||||
|
Full architecture diagram and component breakdown:
|
||||||
|
[`spec.md` §3](./spec.md).
|
||||||
|
|
||||||
|
## Repository layout
|
||||||
|
|
||||||
|
```
|
||||||
|
cmd/server/ control-plane binary
|
||||||
|
cmd/agent/ endpoint agent binary
|
||||||
|
internal/api shared API types (REST + WS envelopes)
|
||||||
|
internal/server/ HTTP, WS, UI handlers
|
||||||
|
internal/agent/ service integration, restic runner, local scheduler
|
||||||
|
internal/restic restic CLI wrapper
|
||||||
|
internal/store SQLite persistence
|
||||||
|
internal/crypto secret encryption
|
||||||
|
internal/auth passwords, sessions, agent tokens
|
||||||
|
web/ server-rendered templates + static assets
|
||||||
|
deploy/ Dockerfile, docker-compose.yml, install scripts
|
||||||
|
design/ UI wireframes (Phase 0 design pass)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Local development
|
||||||
|
|
||||||
|
Requires Go 1.23+ (built and tested on 1.26).
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make build # builds cmd/server and cmd/agent into ./bin
|
||||||
|
make test # runs go test ./...
|
||||||
|
make lint # runs golangci-lint
|
||||||
|
make run-server # runs the server (dev defaults)
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
PolyForm Noncommercial 1.0.0 — see [`LICENSE`](./LICENSE). Free for personal,
|
||||||
|
hobby, research, educational, governmental, and other noncommercial use.
|
||||||
|
Commercial use requires a separate license.
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
var version = "dev"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
showVersion := flag.Bool("version", false, "print version and exit")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *showVersion {
|
||||||
|
fmt.Println("restic-manager-agent", version)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))
|
||||||
|
slog.SetDefault(logger)
|
||||||
|
|
||||||
|
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
defer stop()
|
||||||
|
|
||||||
|
slog.Info("restic-manager agent starting", "version", version)
|
||||||
|
<-ctx.Done()
|
||||||
|
slog.Info("shutting down")
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
var version = "dev"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
showVersion := flag.Bool("version", false, "print version and exit")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *showVersion {
|
||||||
|
fmt.Println("restic-manager-server", version)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))
|
||||||
|
slog.SetDefault(logger)
|
||||||
|
|
||||||
|
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
defer stop()
|
||||||
|
|
||||||
|
slog.Info("restic-manager server starting", "version", version)
|
||||||
|
<-ctx.Done()
|
||||||
|
slog.Info("shutting down")
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# syntax=docker/dockerfile:1.7
|
||||||
|
|
||||||
|
# ---- Build stage --------------------------------------------------------
|
||||||
|
FROM golang:1.23-alpine AS build
|
||||||
|
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
|
# Pure-Go SQLite (modernc.org/sqlite) means we can keep CGO off and build a
|
||||||
|
# fully static binary that runs on distroless/static.
|
||||||
|
ENV CGO_ENABLED=0 \
|
||||||
|
GOOS=linux \
|
||||||
|
GOFLAGS="-trimpath"
|
||||||
|
|
||||||
|
# Cache module downloads in a separate layer.
|
||||||
|
COPY go.mod go.sum* ./
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
ARG VERSION=dev
|
||||||
|
RUN go build -ldflags="-s -w -X main.version=${VERSION}" \
|
||||||
|
-o /out/restic-manager-server \
|
||||||
|
./cmd/server
|
||||||
|
|
||||||
|
# ---- Runtime stage ------------------------------------------------------
|
||||||
|
FROM gcr.io/distroless/static-debian12:nonroot
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.source="https://gitea.dcglab.co.uk/steve/restic-manager"
|
||||||
|
LABEL org.opencontainers.image.licenses="PolyForm-Noncommercial-1.0.0"
|
||||||
|
|
||||||
|
USER nonroot:nonroot
|
||||||
|
WORKDIR /
|
||||||
|
|
||||||
|
COPY --from=build /out/restic-manager-server /usr/local/bin/restic-manager-server
|
||||||
|
|
||||||
|
EXPOSE 8443
|
||||||
|
ENTRYPOINT ["/usr/local/bin/restic-manager-server"]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Reference deployment for the restic-manager control plane.
|
||||||
|
# Mirrors spec.md §10.1. Adjust image tag and RM_BASE_URL for your env.
|
||||||
|
services:
|
||||||
|
restic-manager:
|
||||||
|
image: ghcr.io/dcglab/restic-manager:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "8443:8443"
|
||||||
|
volumes:
|
||||||
|
- ./data:/data
|
||||||
|
- ./certs:/certs:ro
|
||||||
|
environment:
|
||||||
|
- RM_DATA_DIR=/data
|
||||||
|
- RM_LISTEN=:8443
|
||||||
|
- RM_BASE_URL=https://restic.lab.example
|
||||||
|
- RM_TLS_CERT=/certs/fullchain.pem
|
||||||
|
- RM_TLS_KEY=/certs/privkey.pem
|
||||||
|
- RM_SECRET_KEY_FILE=/data/secret.key
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
// Package runner spawns restic processes, parses their --json output
|
||||||
|
// stream, and forwards events to the server over WebSocket.
|
||||||
|
package runner
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// Package scheduler runs the agent's local cron loop. The server is the
|
||||||
|
// source of truth for schedules; this package reconciles to whatever
|
||||||
|
// the server most recently pushed.
|
||||||
|
package scheduler
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
// Package service wires the agent into the host's service manager
|
||||||
|
// (systemd on Linux, the Service Control Manager on Windows).
|
||||||
|
package service
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// Package api defines shared types for the control-plane API:
|
||||||
|
// REST request/response shapes (server ↔ browser) and WebSocket
|
||||||
|
// message envelopes (server ↔ agent). See spec.md §6.
|
||||||
|
package api
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
// Package auth handles password hashing (argon2id), session cookies,
|
||||||
|
// CSRF tokens, and bearer-token verification for agents.
|
||||||
|
package auth
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
// Package crypto wraps AEAD encryption used to protect repo passwords,
|
||||||
|
// REST-server credentials, and pre/post hook bodies at rest.
|
||||||
|
package crypto
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
// Package restic wraps the restic CLI: locating the binary, invoking
|
||||||
|
// it with --json, and parsing the streamed event payloads.
|
||||||
|
package restic
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
// Package http hosts the chi-based REST handlers for the control plane.
|
||||||
|
package http
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
// Package ui renders the HTMX/Tailwind frontend from server-side
|
||||||
|
// html/templates.
|
||||||
|
package ui
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
// Package ws hosts the WebSocket transport for agent ↔ server and the
|
||||||
|
// browser-facing live job log stream.
|
||||||
|
package ws
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
// Package store is the SQLite persistence layer
|
||||||
|
// (modernc.org/sqlite, no CGo).
|
||||||
|
package store
|
||||||
@@ -8,12 +8,12 @@ Sizes: **S** = under a day, **M** = 1–3 days, **L** = 3–7 days.
|
|||||||
|
|
||||||
## Phase 0 — Project bootstrap
|
## Phase 0 — Project bootstrap
|
||||||
|
|
||||||
- [ ] **P0-01** (S) Initialize Go module, `cmd/server`, `cmd/agent`, baseline `internal/` packages
|
- [x] **P0-01** (S) Initialize Go module, `cmd/server`, `cmd/agent`, baseline `internal/` packages
|
||||||
- [ ] **P0-02** (S) Add LICENSE (PolyForm Noncommercial 1.0.0), README stub, CONTRIBUTING placeholder
|
- [x] **P0-02** (S) Add LICENSE (PolyForm Noncommercial 1.0.0), README stub, CONTRIBUTING placeholder
|
||||||
- [ ] **P0-03** (S) Set up `golangci-lint`, `gofumpt`, `goimports`; pre-commit config
|
- [x] **P0-03** (S) Set up `golangci-lint`, `gofumpt`, `goimports`; pre-commit config
|
||||||
- [ ] **P0-04** (S) GitHub Actions: build matrix (linux amd64/arm64, windows amd64), unit tests, lint
|
- [x] **P0-04** (S) ~~GitHub Actions~~ Gitea Actions: build matrix (linux amd64/arm64, windows amd64), unit tests, lint
|
||||||
- [ ] **P0-05** (S) `Dockerfile.server` (multi-stage, distroless), `deploy/docker-compose.yml`
|
- [x] **P0-05** (S) `Dockerfile.server` (multi-stage, distroless), `deploy/docker-compose.yml`
|
||||||
- [ ] **P0-06** (S) Makefile / `taskfile.yml` with common targets (`build`, `test`, `run`, `release`)
|
- [x] **P0-06** (S) Makefile / ~~`taskfile.yml`~~ with common targets (`build`, `test`, `run`, `release`)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user