This commit is contained in:
2026-05-08 14:06:08 +02:00
parent b314a683c9
commit b4811fba4a
6 changed files with 366 additions and 25 deletions

13
.dockerignore Normal file
View File

@@ -0,0 +1,13 @@
/cli
/server
/web
/gui
/env/
/build/
*.exe
/config.json
/.codex
/.vscode
# for docker-compose.yml
/data

7
.gitignore vendored
View File

@@ -5,4 +5,9 @@
/env/ /env/
/build/ /build/
*.exe *.exe
/config.json /config.json
/.codex
/.vscode
# for docker-compose.yml
/data

260
README.md
View File

@@ -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
View 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
View 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
View 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" ]