wip
This commit is contained in:
13
.dockerignore
Normal file
13
.dockerignore
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/cli
|
||||||
|
/server
|
||||||
|
/web
|
||||||
|
/gui
|
||||||
|
/env/
|
||||||
|
/build/
|
||||||
|
*.exe
|
||||||
|
/config.json
|
||||||
|
/.codex
|
||||||
|
/.vscode
|
||||||
|
|
||||||
|
# for docker-compose.yml
|
||||||
|
/data
|
||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -5,4 +5,9 @@
|
|||||||
/env/
|
/env/
|
||||||
/build/
|
/build/
|
||||||
*.exe
|
*.exe
|
||||||
/config.json
|
/config.json
|
||||||
|
/.codex
|
||||||
|
/.vscode
|
||||||
|
|
||||||
|
# for docker-compose.yml
|
||||||
|
/data
|
||||||
260
README.md
260
README.md
@@ -1,58 +1,270 @@
|
|||||||
# CloudSave
|
# CloudSave
|
||||||
|
|
||||||
The software is still in alpha.
|
CloudSave is a small client/server tool to keep save folders in sync across multiple computers.
|
||||||
|
It is aimed at games that do not provide their own cloud sync, such as emulators, old games, or any title that stores progress in a local directory.
|
||||||
|
|
||||||
A client/server that allows unsynchronized games (such as emulators, old games, etc.) to be kept up to date on multiple computers.
|
The project is still in alpha.
|
||||||
|
|
||||||
|
## What Is In The Repository
|
||||||
|
|
||||||
|
This repository currently contains three Go binaries:
|
||||||
|
|
||||||
|
- `cmd/cli`: the end-user CLI (`cloudsave`)
|
||||||
|
- `cmd/server`: the HTTP API server
|
||||||
|
- `cmd/web`: a small read-only web UI that talks to the API server
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
You need go1.24
|
The module targets Go `1.24` in [go.mod](/home/aurelie/src/cloudsave/go.mod), while the container image builds with Go `1.26.3` from [dockerfile](/home/aurelie/src/cloudsave/dockerfile). In practice, using a recent Go toolchain is recommended.
|
||||||
|
|
||||||
After downloading the go toolchain, just run the script `./build.sh`
|
To build all binaries for the platforms configured in the project:
|
||||||
|
|
||||||
## Usage
|
```bash
|
||||||
|
./build.sh
|
||||||
### Server
|
|
||||||
|
|
||||||
The server needs an empty directory. After creating this directory, you need to make a file that contains your credential. The format is "username:password". The server only understand bcrypt password hash for now.
|
|
||||||
|
|
||||||
e.g.:
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Artifacts are written to `./build`.
|
||||||
|
|
||||||
|
If you only want one binary, you can also build it directly:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go build -o cloudsave ./cmd/cli
|
||||||
|
go build -o cloudsave_server ./cmd/server
|
||||||
|
go build -o cloudsave_web ./cmd/web
|
||||||
|
```
|
||||||
|
|
||||||
|
## Server
|
||||||
|
|
||||||
|
The server exposes an authenticated HTTP API on port `8080` by default.
|
||||||
|
|
||||||
|
### Data Directory
|
||||||
|
|
||||||
|
By default, the server uses:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/var/lib/cloudsave
|
||||||
|
```
|
||||||
|
|
||||||
|
You can override it with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cloudsave_server -document-root /path/to/cloudsave-data
|
||||||
|
```
|
||||||
|
|
||||||
|
Inside this directory, the server expects:
|
||||||
|
|
||||||
|
- `.htpasswd`: credentials file
|
||||||
|
- `data/`: stored save archives and metadata
|
||||||
|
|
||||||
|
### Authentication
|
||||||
|
|
||||||
|
The API uses HTTP Basic Auth. Credentials are read from `.htpasswd`.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```text
|
||||||
test:$2y$10$uULsuyROe3LVdTzFoBH7HO0zhvyKp6CX2FDNl7quXMFYqzitU0kc.
|
test:$2y$10$uULsuyROe3LVdTzFoBH7HO0zhvyKp6CX2FDNl7quXMFYqzitU0kc.
|
||||||
```
|
```
|
||||||
|
|
||||||
To generate bcrypt password, I recommand [hash_utils](https://git.thelilfrog.com/thelilfrog/hash_utils), which is offline and secure
|
The code currently expects bcrypt hashes when validating passwords.
|
||||||
|
|
||||||
The default path to this directory is `/var/lib/cloudsave`, this can be changed with the `-document-root` argument
|
### Start The Server
|
||||||
|
|
||||||
### Client
|
```bash
|
||||||
|
cloudsave_server
|
||||||
|
```
|
||||||
|
|
||||||
#### Register a game
|
Useful flags from [cmd/server/runner.go](/home/aurelie/src/cloudsave/cmd/server/runner.go):
|
||||||
|
|
||||||
|
- `-document-root`: change the storage directory
|
||||||
|
- `-port`: change the listening port
|
||||||
|
- `-no-cache`: use the lazy repository instead of the eager cache
|
||||||
|
- `-verbose`: enable more logs
|
||||||
|
|
||||||
|
On non-Windows systems, sending `SIGHUP` reloads the eager cache and the `.htpasswd` file.
|
||||||
|
|
||||||
|
## Docker
|
||||||
|
|
||||||
|
The repository contains a server-only container setup:
|
||||||
|
|
||||||
|
- [dockerfile](/home/aurelie/src/cloudsave/dockerfile)
|
||||||
|
- [docker-compose.yml](/home/aurelie/src/cloudsave/docker-compose.yml)
|
||||||
|
|
||||||
|
Run it with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up --build
|
||||||
|
```
|
||||||
|
|
||||||
|
This maps:
|
||||||
|
|
||||||
|
- port `8080`
|
||||||
|
- local `./data` to `/var/lib/cloudsave`
|
||||||
|
|
||||||
|
Before starting the container, you still need to create `./data/.htpasswd`.
|
||||||
|
|
||||||
|
## Client
|
||||||
|
|
||||||
|
The CLI stores its local database in the user config directory under `cloudsave/data`.
|
||||||
|
|
||||||
|
It keeps:
|
||||||
|
|
||||||
|
- per-game metadata
|
||||||
|
- current local archive
|
||||||
|
- backup archives
|
||||||
|
- `remote.json` per game when a remote is configured
|
||||||
|
|
||||||
|
Saved credentials are stored separately in `credential.json`.
|
||||||
|
|
||||||
|
Important: the `login` command stores credentials in plain text. This is also stated in the code.
|
||||||
|
|
||||||
|
## Typical Workflow
|
||||||
|
|
||||||
|
### 1. Register a game
|
||||||
|
|
||||||
You can register a game with the verb `add`
|
|
||||||
```bash
|
```bash
|
||||||
cloudsave add /home/user/gamedata
|
cloudsave add /home/user/gamedata
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also change the name of the registration and add a remote
|
You can override the displayed name:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cloudsave add -name "My Game" -remote "http://localhost:8080" /home/user/gamedata
|
cloudsave add -name "My Game" /home/user/gamedata
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Make an archive of the current state
|
Note: the `-remote` flag exists on `add`, but the current implementation does not persist it. Use `cloudsave remote -set` after `add`.
|
||||||
|
|
||||||
This is a command line tool, it cannot auto detect changes.
|
### 2. List registered games
|
||||||
Run this command to start the scan, if needed, the tool will create a new archive
|
|
||||||
|
```bash
|
||||||
|
cloudsave list
|
||||||
|
```
|
||||||
|
|
||||||
|
To include local backup IDs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cloudsave list -include-backup
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Create or refresh the local archive
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cloudsave scan
|
cloudsave scan
|
||||||
```
|
```
|
||||||
#### Send everything on the server
|
|
||||||
|
|
||||||
This will pull and push data to the server.
|
This scans all registered folders. If a folder changed since the last scan, the current archive is moved to the backup history and a new `data.tar.gz` archive is created.
|
||||||
|
|
||||||
Note: If multiple computers are pushing to this server, a conflict may be generated. If so, the tool will ask for the version to keep
|
### 4. Configure the remote server for a game
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cloudsave remote -set GAME_ID http://localhost:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
To list configured remotes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cloudsave remote -list
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Save credentials locally
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cloudsave login http://localhost:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
This verifies the credentials against the server and then stores them locally in plain text.
|
||||||
|
|
||||||
|
### 6. Synchronize with the server
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cloudsave sync
|
cloudsave sync
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The sync command:
|
||||||
|
|
||||||
|
- groups games by remote URL
|
||||||
|
- authenticates once per remote
|
||||||
|
- compares local and remote metadata
|
||||||
|
- pushes or pulls as needed
|
||||||
|
- asks for a resolution if versions conflict
|
||||||
|
|
||||||
|
### 7. Restore a save locally
|
||||||
|
|
||||||
|
Apply the latest local archive for a game:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cloudsave apply GAME_ID
|
||||||
|
```
|
||||||
|
|
||||||
|
Apply a specific backup:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cloudsave apply GAME_ID BACKUP_ID
|
||||||
|
```
|
||||||
|
|
||||||
|
## Other CLI Commands
|
||||||
|
|
||||||
|
Show metadata for one game:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cloudsave show GAME_ID
|
||||||
|
```
|
||||||
|
|
||||||
|
Pull one game and its backups from a remote into a local path:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cloudsave pull http://localhost:8080 GAME_ID /path/to/restore
|
||||||
|
```
|
||||||
|
|
||||||
|
Show local version information:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cloudsave version
|
||||||
|
```
|
||||||
|
|
||||||
|
Show remote version information:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cloudsave version -a http://localhost:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
Remove a registered game and its local backups:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cloudsave remove GAME_ID
|
||||||
|
```
|
||||||
|
|
||||||
|
## Web UI
|
||||||
|
|
||||||
|
The repository also contains a small web frontend in `cmd/web`.
|
||||||
|
|
||||||
|
It uses a JSON config file, for example:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"server": {
|
||||||
|
"port": 8081
|
||||||
|
},
|
||||||
|
"remote": {
|
||||||
|
"url": "http://localhost:8080"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then start it with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cloudsave_web -config /path/to/config.json
|
||||||
|
```
|
||||||
|
|
||||||
|
The web UI itself does not manage users. It forwards HTTP Basic Auth credentials to the configured API server.
|
||||||
|
|
||||||
|
## Current Caveats
|
||||||
|
|
||||||
|
These points are worth knowing in the current state of the project:
|
||||||
|
|
||||||
|
- the software is still alpha
|
||||||
|
- credentials saved by `cloudsave login` are stored in plain text
|
||||||
|
- the Docker setup only runs the API server, not the web UI
|
||||||
|
- `add -remote` is exposed by the CLI but is not currently persisted by the service layer
|
||||||
|
|
||||||
|
|||||||
24
api.Dockerfile
Normal file
24
api.Dockerfile
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
FROM golang:1.26.3-trixie AS build
|
||||||
|
|
||||||
|
ENV GOOS=linux
|
||||||
|
ENV CGO_ENABLED=0
|
||||||
|
|
||||||
|
ENV GOAMD64=v3
|
||||||
|
ENV GORISCV64=rva22u64
|
||||||
|
ENV GOARM64=v8.2
|
||||||
|
|
||||||
|
COPY . /src
|
||||||
|
|
||||||
|
RUN cd /src \
|
||||||
|
&& go build -ldflags="-s -w" -o server ./cmd/server \
|
||||||
|
&& chown 0:0 server \
|
||||||
|
&& chmod ugo+x server
|
||||||
|
|
||||||
|
FROM busybox:1.37.0 AS prod
|
||||||
|
|
||||||
|
COPY --from=build /etc/passwd /etc/passwd
|
||||||
|
COPY --from=build /etc/shadow /etc/shadow
|
||||||
|
COPY --from=build /src/server /server
|
||||||
|
|
||||||
|
VOLUME [ "/var/lib/cloudsave" ]
|
||||||
|
ENTRYPOINT [ "/server" ]
|
||||||
63
docker-compose.yml
Normal file
63
docker-compose.yml
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
services:
|
||||||
|
api:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: api.Dockerfile
|
||||||
|
volumes:
|
||||||
|
- "./data:/var/lib/cloudsave"
|
||||||
|
networks:
|
||||||
|
- cloudsave_net
|
||||||
|
healthcheck:
|
||||||
|
test: wget --no-verbose --tries=1 --spider http://localhost:8080/heartbeat || exit 1
|
||||||
|
interval: 3s
|
||||||
|
timeout: 2s
|
||||||
|
retries: 3
|
||||||
|
start_period: 10s
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.api-router.rule=Host(`${DOMAIN:-localhost}`) && PathPrefix(`/api`)"
|
||||||
|
- "traefik.http.routers.api-router.entrypoints=web"
|
||||||
|
- "traefik.http.services.cloudsave-api.loadbalancer.server.port=8080"
|
||||||
|
|
||||||
|
web:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: web.Dockerfile
|
||||||
|
volumes:
|
||||||
|
- "./config.json:/var/lib/cloudsave/config.json"
|
||||||
|
networks:
|
||||||
|
- cloudsave_net
|
||||||
|
depends_on:
|
||||||
|
api:
|
||||||
|
condition: service_healthy
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.web-router.rule=Host(`${DOMAIN:-localhost}`) && PathPrefix(`/web`)"
|
||||||
|
- "traefik.http.routers.web-router.entrypoints=web"
|
||||||
|
- "traefik.http.services.cloudsave-web.loadbalancer.server.port=8080"
|
||||||
|
|
||||||
|
proxy:
|
||||||
|
image: traefik:3.7.0
|
||||||
|
ports:
|
||||||
|
- 127.0.0.1:80:80
|
||||||
|
- 127.0.0.1:8080:8080
|
||||||
|
networks:
|
||||||
|
- cloudsave_net
|
||||||
|
depends_on:
|
||||||
|
api:
|
||||||
|
condition: service_healthy
|
||||||
|
web:
|
||||||
|
condition: service_started
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
command:
|
||||||
|
- --api.dashboard=true
|
||||||
|
- --api.insecure=true
|
||||||
|
- --providers.docker=true
|
||||||
|
- --providers.docker.exposedbydefault=false
|
||||||
|
- --entrypoints.web.address=:80
|
||||||
|
- --log.level=DEBUG
|
||||||
|
- --accesslog=true
|
||||||
|
|
||||||
|
networks:
|
||||||
|
cloudsave_net:
|
||||||
24
web.Dockerfile
Normal file
24
web.Dockerfile
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
FROM golang:1.26.3-trixie AS build
|
||||||
|
|
||||||
|
ENV GOOS=linux
|
||||||
|
ENV CGO_ENABLED=0
|
||||||
|
|
||||||
|
ENV GOAMD64=v3
|
||||||
|
ENV GORISCV64=rva22u64
|
||||||
|
ENV GOARM64=v8.2
|
||||||
|
|
||||||
|
COPY . /src
|
||||||
|
|
||||||
|
RUN cd /src \
|
||||||
|
&& go build -ldflags="-s -w" -o web ./cmd/web \
|
||||||
|
&& chown 0:0 web \
|
||||||
|
&& chmod ugo+x web
|
||||||
|
|
||||||
|
FROM scratch AS prod
|
||||||
|
|
||||||
|
COPY --from=build /etc/passwd /etc/passwd
|
||||||
|
COPY --from=build /etc/shadow /etc/shadow
|
||||||
|
COPY --from=build /src/web /web
|
||||||
|
|
||||||
|
VOLUME [ "/var/lib/cloudsave" ]
|
||||||
|
ENTRYPOINT [ "/web" ]
|
||||||
Reference in New Issue
Block a user