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
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -6,3 +6,8 @@
|
||||
/build/
|
||||
*.exe
|
||||
/config.json
|
||||
/.codex
|
||||
/.vscode
|
||||
|
||||
# for docker-compose.yml
|
||||
/data
|
||||
260
README.md
260
README.md
@@ -1,58 +1,270 @@
|
||||
# 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
|
||||
|
||||
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
|
||||
|
||||
### 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.:
|
||||
```bash
|
||||
./build.sh
|
||||
```
|
||||
|
||||
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.
|
||||
```
|
||||
|
||||
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
|
||||
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
|
||||
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.
|
||||
Run this command to start the scan, if needed, the tool will create a new archive
|
||||
### 2. List registered games
|
||||
|
||||
```bash
|
||||
cloudsave list
|
||||
```
|
||||
|
||||
To include local backup IDs:
|
||||
|
||||
```bash
|
||||
cloudsave list -include-backup
|
||||
```
|
||||
|
||||
### 3. Create or refresh the local archive
|
||||
|
||||
```bash
|
||||
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
|
||||
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