Compare commits
4
Commits
v1.0
...
0a97b78d63
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a97b78d63 | ||
|
|
05db8bfeb9 | ||
|
|
dccb98ce18 | ||
|
|
895858197e |
@@ -0,0 +1,72 @@
|
||||
name: Sync Gitea releases to GitHub
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch: {}
|
||||
|
||||
env:
|
||||
GITEA_URL: https://git.azuze.fr
|
||||
GITEA_OWNER: kawa
|
||||
GITEA_REPO: DockMV
|
||||
|
||||
jobs:
|
||||
sync-releases:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install jq
|
||||
run: sudo apt-get update && sudo apt-get install -y jq
|
||||
|
||||
- name: Fetch Gitea releases and sync to GitHub
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
AUTH_HEADER=()
|
||||
if [ -n "${GITEA_TOKEN:-}" ]; then
|
||||
AUTH_HEADER=(-H "Authorization: token ${GITEA_TOKEN}")
|
||||
fi
|
||||
# Récupère toutes les releases Gitea (pagination simple, 50 max, ajuster si besoin)
|
||||
releases=$(curl -sf "${AUTH_HEADER[@]}" \
|
||||
"${GITEA_URL}/api/v1/repos/${GITEA_OWNER}/${GITEA_REPO}/releases?limit=50")
|
||||
echo "$releases" | jq -c '.[]' | while read -r release; do
|
||||
tag=$(echo "$release" | jq -r '.tag_name')
|
||||
name=$(echo "$release" | jq -r '.name // .tag_name')
|
||||
body=$(echo "$release" | jq -r '.body // ""')
|
||||
prerelease=$(echo "$release" | jq -r '.prerelease')
|
||||
draft=$(echo "$release" | jq -r '.draft')
|
||||
# Skip si la release existe déjà sur GitHub
|
||||
if gh release view "$tag" >/dev/null 2>&1; then
|
||||
echo "Release $tag existe déjà sur GitHub, on passe."
|
||||
continue
|
||||
fi
|
||||
echo "Création de la release $tag sur GitHub..."
|
||||
flags=()
|
||||
[ "$prerelease" = "true" ] && flags+=(--prerelease)
|
||||
[ "$draft" = "true" ] && flags+=(--draft)
|
||||
# Le tag doit exister sur le repo GitHub ; s'il n'existe pas encore,
|
||||
# on utilise --notes-file avec target par défaut (branche par défaut)
|
||||
gh release create "$tag" \
|
||||
--title "$name" \
|
||||
--notes "$body" \
|
||||
"${flags[@]}"
|
||||
# Télécharge et attache les assets de la release Gitea
|
||||
asset_count=$(echo "$release" | jq '.assets | length')
|
||||
if [ "$asset_count" -gt 0 ]; then
|
||||
tmpdir=$(mktemp -d)
|
||||
echo "$release" | jq -c '.assets[]' | while read -r asset; do
|
||||
asset_name=$(echo "$asset" | jq -r '.name')
|
||||
asset_url=$(echo "$asset" | jq -r '.browser_download_url')
|
||||
echo "Téléchargement de $asset_name..."
|
||||
curl -sfL "${AUTH_HEADER[@]}" -o "${tmpdir}/${asset_name}" "$asset_url"
|
||||
gh release upload "$tag" "${tmpdir}/${asset_name}" --clobber
|
||||
done
|
||||
rm -rf "$tmpdir"
|
||||
fi
|
||||
done
|
||||
@@ -1,6 +1,6 @@
|
||||
<p align="center"><img src="assets/dockmv-logo-full.png" alt="DockMV" width="480"></p>
|
||||
|
||||
Move Docker containers — and their data — from one host to another, from a web UI, in a few clicks.
|
||||
Move Docker containers — **and their data** — from one host to another, from a web UI, in a few clicks.
|
||||
|
||||
It handles the whole container, not just the image: named volumes, anonymous volumes, bind mounts,
|
||||
user-defined networks, published ports, environment, capabilities, restart policy, healthchecks and
|
||||
@@ -10,148 +10,173 @@ Two ways to move things:
|
||||
|
||||
| Mode | What happens | When to use it |
|
||||
| --- | --- | --- |
|
||||
| **Host to host over SSH** | The source connects to the target over SSH and streams everything straight into the target's `docker cp` / `docker load`. Nothing touches disk in between. | The two hosts can reach each other. |
|
||||
| **Host to host over SSH** | The source streams everything straight into the target's `docker cp` / `docker load`. Nothing touches disk in between. | The two hosts can reach each other. |
|
||||
| **Migration package** | Builds a self-contained folder or `.tar` holding the data, the images, and a plain-bash `install.sh`. Carry it on a disk, run the script on the target. | Air-gapped targets, or when you want the move reviewed and replayed later. |
|
||||
|
||||
The target needs **nothing installed**: no agent, no Python, no Go. Just `sshd`, a working `docker`
|
||||
CLI, `bash` and `gzip`.
|
||||
The target needs **nothing installed**: no agent, no Python, no Go — just `sshd`, `docker`, `bash` and `gzip`.
|
||||
|
||||
---
|
||||
|
||||
## Quick start
|
||||
## Install
|
||||
|
||||
### Run it in a container (recommended)
|
||||
Run DockMV on the **source** host (the one holding the containers to move).
|
||||
|
||||
On the **source** host:
|
||||
### Docker Compose — recommended
|
||||
|
||||
```bash
|
||||
git clone https://git.azuze.fr/kawa/DockMV.git dockmv && cd dockmv
|
||||
docker compose up -d --build
|
||||
docker compose up -d
|
||||
docker compose logs dockmv # prints the URL, including the access token
|
||||
```
|
||||
|
||||
Then open the printed URL. It binds to `127.0.0.1` only; reach it from your laptop with a tunnel:
|
||||
Open the printed URL. It binds to `127.0.0.1` only; reach it from your laptop with a tunnel:
|
||||
|
||||
```bash
|
||||
ssh -L 8080:127.0.0.1:8080 you@source-host
|
||||
```
|
||||
|
||||
### Run it bare-metal
|
||||
Uses the published image `git.azuze.fr/kawa/dockmv:latest`. Pin a version with `VERSION=v1.2.0 docker compose up -d`,
|
||||
and set a fixed token with `DOCKMV_TOKEN` in the compose file to keep the same URL across restarts.
|
||||
|
||||
The binary is fully static and embeds the web UI, so there is nothing to install alongside it.
|
||||
### Prebuilt binary
|
||||
|
||||
Grab the archive for your platform from the [releases](https://git.azuze.fr/kawa/DockMV/releases)
|
||||
(`dockmv-<os>-<arch>.tar.gz`, `.zip` on Windows). The binary is static and embeds the web UI —
|
||||
nothing to install alongside it.
|
||||
|
||||
```bash
|
||||
make build # needs Go 1.25+ and Node 20+ ... or just `go build .` if you skip the UI rebuild
|
||||
tar xzf dockmv-linux-amd64.tar.gz
|
||||
./dockmv-linux-amd64 serve
|
||||
```
|
||||
|
||||
### From source
|
||||
|
||||
```bash
|
||||
git clone https://git.azuze.fr/kawa/DockMV.git dockmv && cd dockmv
|
||||
make build # rebuilds the UI, then the binary
|
||||
./dockmv serve
|
||||
```
|
||||
|
||||
Prebuilt for several platforms:
|
||||
`go build .` alone also works — the built UI is committed, so the Node toolchain is optional.
|
||||
|
||||
```bash
|
||||
make release # dist/dockmv-linux-amd64, -linux-arm64, -darwin-arm64, -windows-amd64
|
||||
```
|
||||
> DockMV needs access to the Docker socket on the source host: run it as a user in the `docker`
|
||||
> group, or as root. That is equivalent to root on that host, so keep the UI on loopback.
|
||||
|
||||
`dockmv` needs access to the Docker socket on the source host, so run it as a user in the
|
||||
`docker` group (or as root).
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
|
||||
### Source host — where DockMV runs
|
||||
|
||||
| Requirement | Notes |
|
||||
| --- | --- |
|
||||
| Docker daemon + access to `/var/run/docker.sock` | reads containers and streams their data |
|
||||
| Docker Compose | only for the compose install |
|
||||
|
||||
Nothing else. The binary is static: no libc, no runtime, no Python.
|
||||
|
||||
### Target host — where containers land
|
||||
|
||||
| Requirement | Why |
|
||||
| --- | --- |
|
||||
| `sshd`, reachable from the source | transport |
|
||||
| `docker` CLI + a working daemon | everything |
|
||||
| the login user can use docker | either in the `docker` group, or tick *run docker through `sudo -n`* |
|
||||
| `gzip` | compressed transfers; without it the tool falls back to uncompressed |
|
||||
| `bash` | only for the migration package installer |
|
||||
|
||||
The **connect** button runs a preflight and tells you which of these are missing, plus the target's
|
||||
free disk space and architecture.
|
||||
|
||||
### Building from source
|
||||
|
||||
| Tool | Version |
|
||||
| --- | --- |
|
||||
| Go | 1.25+ |
|
||||
| Node | 20+ (22 in CI) — only to rebuild the UI |
|
||||
| `make` | optional, wraps the two above |
|
||||
| PowerShell 7+ | only for `make publish` / `make release` |
|
||||
|
||||
Libraries: [`docker/docker`](https://github.com/docker/docker) v28.3.3 and `golang.org/x/crypto`
|
||||
on the Go side; React 19, Vite 7 and TypeScript 5.9 on the UI side. That is the whole list.
|
||||
|
||||
---
|
||||
|
||||
## Using it
|
||||
|
||||
1. **Containers tab** — everything on the source host, grouped by compose project.
|
||||
Tick the ones to move. Use *select all*, *select running*, or the compose-project checkbox for
|
||||
batch selection.
|
||||
2. **Expand a row** (`▸`) to choose per-container details: the name on the target, whether the image
|
||||
is pulled or transferred, whether networks and ports come along, and — per mount — whether to
|
||||
**copy the data**, **create it empty**, or **not mount it at all**. Bind mounts can be relocated to
|
||||
a different path on the target; named volumes can be renamed.
|
||||
3. **Apply to selected** in the toolbar does the same thing to every selected container at once
|
||||
(*copy all data*, *skip binds*, *image: pull on target*, …).
|
||||
4. **Right panel** — add the target host, hit *connect*, review the options, then either
|
||||
**migrate over SSH** or **build a package**.
|
||||
5. **Preview the commands** shows the exact `docker` invocations that will run on the target. Nothing
|
||||
is hidden.
|
||||
6. **Jobs tab** — live progress per container and per mount, with the full command log.
|
||||
1. **Containers tab** — everything on the source host, grouped by compose project. Tick what to move.
|
||||
2. **Expand a row** (`▸`) for per-container details: target name, image pulled or transferred,
|
||||
networks and ports, and — per mount — **copy the data**, **create it empty**, or **do not mount it**.
|
||||
Bind mounts can be relocated; named volumes renamed.
|
||||
3. **Apply to selected** does the same thing to every selected container at once.
|
||||
4. **Right panel** — add the target host, *connect*, then **migrate over SSH** or **build a package**.
|
||||
5. **Jobs tab** — live progress per container and per mount, with the full command log.
|
||||
|
||||
Start with **dry run** ticked. It performs every check and prints every command without changing
|
||||
anything on the target.
|
||||
Start with **dry run** ticked: it runs every check and prints every command without touching the target.
|
||||
**Preview the commands** shows the exact `docker` invocations that will run. Nothing is hidden.
|
||||
|
||||
---
|
||||
<details>
|
||||
<summary><b>How the data is actually moved</b></summary>
|
||||
|
||||
## How the data is actually moved
|
||||
|
||||
The interesting part is that there is exactly **one** mechanism for every kind of data location:
|
||||
One mechanism for every kind of data location:
|
||||
|
||||
```
|
||||
source daemon ──CopyFromContainer(/mount/path)──▶ tar stream ──gzip──▶ ssh ──▶ docker cp -a - ctr:/parent
|
||||
```
|
||||
|
||||
The source container's own mount path is read through the Docker archive API — the same thing
|
||||
`docker cp` uses. That means:
|
||||
The mount is read through the Docker archive API — the same thing `docker cp` uses. So:
|
||||
|
||||
- named volumes, anonymous volumes and bind mounts are all handled identically;
|
||||
- no helper image is pulled, and the container's image does not need `tar` inside it;
|
||||
- named volumes, anonymous volumes and bind mounts are handled identically;
|
||||
- no helper image is pulled, and the image does not need `tar` inside it;
|
||||
- it works whether the container is running or stopped;
|
||||
- file ownership, permissions, symlinks and hardlinks are preserved (`docker cp -a`).
|
||||
- ownership, permissions, symlinks and hardlinks are preserved (`docker cp -a`).
|
||||
|
||||
On the target the container is **created first, started last**. Creating it is what makes Docker
|
||||
materialise the named volumes and bind directories; the data is then copied into the stopped
|
||||
container, and only then is it started.
|
||||
On the target the container is **created first, started last** — creating it is what makes Docker
|
||||
materialise the volumes and bind directories; data is copied into the stopped container, then it
|
||||
starts. Mounts declared **read-only** get a throwaway container (never started) with the volume
|
||||
attached writable, and it is removed straight after.
|
||||
|
||||
A mount the container declares **read-only** cannot be written through the container itself. For
|
||||
those, a throwaway container is created (never started) with the same volume attached writable, the
|
||||
data is copied into it, and it is removed straight after.
|
||||
**Faithfully reproduced:** image (pull or layer transfer), command, entrypoint, environment, labels,
|
||||
working directory, user, hostname, published and exposed ports, all mount types, user-defined
|
||||
networks with subnets and aliases, DNS, extra hosts, capabilities, devices, sysctls, ulimits,
|
||||
security options, restart policy, stop signal and timeout, healthcheck, log driver, memory/CPU/pids
|
||||
limits, privileged, read-only rootfs, init, and the PID/IPC/UTS/userns modes.
|
||||
|
||||
### What is faithfully reproduced
|
||||
Settings that come from the **image** are deliberately not re-emitted, so the recreated container
|
||||
carries only genuine run-time overrides and keeps working when the image is updated.
|
||||
|
||||
Image (by pull or by layer transfer), command, entrypoint, environment, labels, working directory,
|
||||
user, hostname, published and exposed ports, all mount types, user-defined networks with their
|
||||
subnets and the container's aliases, DNS settings, extra hosts, capabilities, devices, sysctls,
|
||||
ulimits, security options, restart policy, stop signal and timeout, healthcheck, log driver and
|
||||
options, memory/CPU/pids limits, privileged, read-only rootfs, init, and the PID/IPC/UTS/userns
|
||||
modes.
|
||||
**What it will not do:**
|
||||
|
||||
Settings that come from the **image** are deliberately not re-emitted — the recreated container
|
||||
carries only genuine run-time overrides, so it stays readable and keeps working when the image is
|
||||
later updated.
|
||||
- `--rm` is never reapplied — a container that deletes itself cannot be inspected.
|
||||
- `--volumes-from` and `--network container:other` are not reproduced; you are warned.
|
||||
- Swarm services are out of scope. Plain containers only.
|
||||
- **Live databases**: copying a running database's files is crash-consistent at best. The default
|
||||
stops the source container while copying — leave it on, or migrate a dump instead.
|
||||
- **Cross-architecture**: an `amd64` image will not run on `arm64`. The preflight shows the target's arch.
|
||||
|
||||
### What it will not do for you
|
||||
</details>
|
||||
|
||||
- **`--rm` is never reapplied.** A migrated container that deletes itself cannot be inspected.
|
||||
- **`--volumes-from` is not reproduced.** You are warned; migrate the other container and mount
|
||||
explicitly.
|
||||
- **`--network container:other`** requires the other container to be migrated too. You are warned.
|
||||
- **Swarm services** are out of scope. This tool moves plain containers.
|
||||
- **Live databases**: copying a running database's files gives you a crash-consistent snapshot at
|
||||
best. The default is to stop the source container while copying — leave it on. For anything you
|
||||
really care about, take a dump instead and migrate that.
|
||||
- **Cross-architecture moves**: an `amd64` image will not run on an `arm64` target. The preflight
|
||||
shows the target's architecture; check it.
|
||||
|
||||
---
|
||||
|
||||
## Safety
|
||||
<details>
|
||||
<summary><b>Safety</b></summary>
|
||||
|
||||
The tool can stop containers and read every volume on the host, so it is treated as a privileged
|
||||
admin tool:
|
||||
|
||||
- It binds to **`127.0.0.1` by default**. Binding anywhere else automatically generates an access
|
||||
token and prints it.
|
||||
- **SSH host keys are verified** exactly like OpenSSH. An unknown key is refused until you approve
|
||||
the fingerprint in the UI; a *changed* key is refused outright until you explicitly replace it.
|
||||
Trusted keys go to `<data-dir>/known_hosts`.
|
||||
- **Credentials are not persisted unless you ask.** By default the password or key lives in memory
|
||||
for the session. Ticking *remember* writes it to `<data-dir>/connections.json`, mode `0600`.
|
||||
- **Nothing on the target is overwritten by default.** If a container name already exists the item
|
||||
fails; you choose *skip*, *rename* or *replace* explicitly. An existing **volume** is reused and
|
||||
merged into, never silently deleted, unless you pick *replace*.
|
||||
- Binds to **`127.0.0.1` by default**. Binding elsewhere auto-generates an access token and prints it.
|
||||
- **SSH host keys are verified** like OpenSSH. An unknown key is refused until you approve the
|
||||
fingerprint in the UI; a *changed* key is refused outright. Trusted keys go to `<data-dir>/known_hosts`.
|
||||
- **Credentials are not persisted unless you ask.** *Remember* writes them to
|
||||
`<data-dir>/connections.json`, mode `0600`.
|
||||
- **Nothing on the target is overwritten by default.** An existing container name fails the item;
|
||||
you pick *skip*, *rename* or *replace*. An existing volume is reused and merged into, never
|
||||
silently deleted, unless you pick *replace*.
|
||||
- Every command that runs on the target is echoed into the job log.
|
||||
- Bind mounts of `/var/run/docker.sock`, `/proc`, `/sys`, `/dev` and `/` are flagged, and a mount at
|
||||
`/` is refused outright.
|
||||
- Bind mounts of `/var/run/docker.sock`, `/proc`, `/sys`, `/dev` and `/` are flagged; a mount at `/` is refused.
|
||||
|
||||
---
|
||||
</details>
|
||||
|
||||
## The migration package
|
||||
<details>
|
||||
<summary><b>The migration package</b></summary>
|
||||
|
||||
`build package` produces:
|
||||
|
||||
@@ -171,8 +196,8 @@ On the target:
|
||||
./install.sh # restore
|
||||
```
|
||||
|
||||
The installer never parses the manifest — every command is written out literally, so it can be read
|
||||
and audited before running. It checksums each payload before feeding it to Docker, and supports:
|
||||
The installer never parses the manifest — every command is written out literally, so it can be
|
||||
audited before running. It checksums each payload, and supports:
|
||||
|
||||
```
|
||||
--dry-run print every command without changing anything
|
||||
@@ -186,24 +211,10 @@ and audited before running. It checksums each payload before feeding it to Docke
|
||||
--sudo prefix docker with sudo -n
|
||||
```
|
||||
|
||||
---
|
||||
</details>
|
||||
|
||||
## Target host requirements
|
||||
|
||||
| Requirement | Why |
|
||||
| --- | --- |
|
||||
| `sshd`, reachable from the source | transport |
|
||||
| `docker` CLI + a working daemon | everything |
|
||||
| the login user can use docker | either in the `docker` group, or tick *run docker through `sudo -n`* |
|
||||
| `gzip` | compressed transfers; without it the tool falls back to uncompressed |
|
||||
| `bash` | only for the migration package installer |
|
||||
|
||||
The **connect** button runs a preflight and tells you which of these are missing, plus the target's
|
||||
free disk space and architecture.
|
||||
|
||||
---
|
||||
|
||||
## Command line
|
||||
<details>
|
||||
<summary><b>Command line and HTTP API</b></summary>
|
||||
|
||||
```
|
||||
dockmv [serve] [flags] start the web interface (default)
|
||||
@@ -228,10 +239,6 @@ dockmv version
|
||||
dockmv inspect --sizes | jq '.containers[] | {name, image, mounts}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## HTTP API
|
||||
|
||||
Everything the UI does is available over HTTP. Pass the token as `X-Auth-Token` when one is set.
|
||||
|
||||
```
|
||||
@@ -254,9 +261,10 @@ POST /api/jobs/{id}/cancel
|
||||
GET /api/packages, /api/packages/{name}/download
|
||||
```
|
||||
|
||||
---
|
||||
</details>
|
||||
|
||||
## Development
|
||||
<details>
|
||||
<summary><b>Development</b></summary>
|
||||
|
||||
```
|
||||
web/ React + TypeScript UI (vite)
|
||||
@@ -276,30 +284,20 @@ make ui # rebuild the embedded UI
|
||||
cd web && npm run dev # UI dev server on :5173, proxying /api to :8080
|
||||
```
|
||||
|
||||
### Verifying it works
|
||||
`internal/webui/dist` is committed so that a plain `go build .` produces a working binary without a
|
||||
Node toolchain. Rerun `make ui` after changing anything under `web/`.
|
||||
|
||||
On a Linux host with Docker (a VM is fine):
|
||||
End-to-end tests, on a Linux host with Docker (a VM is fine):
|
||||
|
||||
```bash
|
||||
go test ./... # unit tests: command rendering, plan resolution,
|
||||
# and the generated installer, checked with bash
|
||||
|
||||
go test -tags e2e ./test/... -v # end to end, against the real daemon:
|
||||
# creates a container with a named volume, a
|
||||
# read-only bind mount and an anonymous volume,
|
||||
# writes files into all three, builds a package,
|
||||
# runs the generated install.sh, then reads the
|
||||
# files back out of the restored container
|
||||
go test -tags e2e ./test/... -v # end to end, against the real daemon
|
||||
```
|
||||
|
||||
The e2e tests restore onto the same daemon under a suffixed name and clean up after
|
||||
themselves, so a single machine is enough:
|
||||
|
||||
- `TestPackageRoundTrip` — builds a package and runs the generated `install.sh` for real.
|
||||
- `TestSSHMigration` — drives the host-to-host engine over a genuine SSH connection to
|
||||
`127.0.0.1`, so the whole transport (ssh, gzip streaming, the target's docker CLI, the
|
||||
staging container for read-only mounts, the verify step) is exercised. It needs
|
||||
`DM_SSH_HOST`, `DM_SSH_USER` and `DM_SSH_KEY`, and skips without them:
|
||||
- `TestSSHMigration` — drives the host-to-host engine over a genuine SSH connection to `127.0.0.1`,
|
||||
exercising the whole transport. Needs `DM_SSH_HOST`, `DM_SSH_USER` and `DM_SSH_KEY`, skips without them:
|
||||
|
||||
```bash
|
||||
ssh-keygen -t ed25519 -N '' -f ~/.ssh/dm_loop
|
||||
@@ -308,8 +306,7 @@ themselves, so a single machine is enough:
|
||||
go test -tags e2e ./test/... -run TestSSHMigration -v
|
||||
```
|
||||
|
||||
Both were run against Debian 13 with Docker 29.7.2, alongside a browser pass over the web
|
||||
UI covering the trust prompt, a batch migration and a package build.
|
||||
They restore onto the same daemon under a suffixed name and clean up, so one machine is enough.
|
||||
Both were run against Debian 13 with Docker 29.7.2, alongside a browser pass over the web UI.
|
||||
|
||||
`internal/webui/dist` is committed so that a plain `go build .` produces a working binary without a
|
||||
Node toolchain. Rerun `make ui` after changing anything under `web/`.
|
||||
</details>
|
||||
|
||||
+1
-6
@@ -9,11 +9,7 @@
|
||||
|
||||
services:
|
||||
dockmv:
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
VERSION: ${VERSION:-dev}
|
||||
image: dockmv:latest
|
||||
image: git.azuze.fr/kawa/dockmv:${VERSION:-latest}
|
||||
container_name: dockmv
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
@@ -31,7 +27,6 @@ services:
|
||||
command:
|
||||
- serve
|
||||
- --addr=0.0.0.0:8080
|
||||
- --token=auto
|
||||
|
||||
volumes:
|
||||
migrate-data:
|
||||
|
||||
@@ -81,7 +81,7 @@ Run "dockmv serve -h" for the server flags.
|
||||
func serve(args []string) error {
|
||||
fs := flag.NewFlagSet("serve", flag.ContinueOnError)
|
||||
addr := fs.String("addr", "127.0.0.1:8080", "address to listen on; use 0.0.0.0:8080 to expose it on the network")
|
||||
token := fs.String("token", "", "require this token on every request; \"auto\" generates one")
|
||||
token := fs.String("token", os.Getenv("DOCKMV_TOKEN"), "require this token on every request; \"auto\" generates one (default: $DOCKMV_TOKEN)")
|
||||
dataDir := fs.String("data-dir", defaultDataDir(), "directory for connections and trusted host keys")
|
||||
pkgDir := fs.String("package-dir", "", "directory for migration packages (default <data-dir>/packages)")
|
||||
dockerHost := fs.String("docker-host", "", "source docker daemon (default: the DOCKER_HOST environment)")
|
||||
|
||||
+22
-14
@@ -10,11 +10,13 @@
|
||||
|
||||
It also cross-compiles the release binaries (scripts/build-release.ps1) from
|
||||
the same commit, so both artifacts carry the same -Tag. Archives cannot live
|
||||
in a container registry, so -PublishRelease attaches them to the Gitea
|
||||
release for that tag instead (creating the release if it does not exist).
|
||||
in a container registry, so they're attached to the Gitea release for that
|
||||
tag instead (creating the release if it does not exist) — on by default,
|
||||
since a run that builds binaries and doesn't publish them is the unusual
|
||||
case. Pass -NoPublishRelease to build locally without uploading.
|
||||
|
||||
-BinariesOnly ships just the binaries: no docker build, no docker login, no
|
||||
image push, and the release upload is implied.
|
||||
image push.
|
||||
|
||||
Credentials are read, in order of precedence:
|
||||
1. -Username / -Password parameters
|
||||
@@ -27,11 +29,12 @@
|
||||
|
||||
.EXAMPLE
|
||||
./scripts/publish.ps1
|
||||
Build and push :latest plus the git-describe tag; build dist/ binaries locally.
|
||||
Full release: push the image (:latest plus the git-describe tag) and attach
|
||||
every dist/ archive to the matching Gitea release.
|
||||
|
||||
.EXAMPLE
|
||||
./scripts/publish.ps1 -Tag v1.2.0 -PublishRelease
|
||||
Full release: push the image and attach every dist/ archive to release v1.2.0.
|
||||
./scripts/publish.ps1 -Tag v1.2.0 -NoPublishRelease
|
||||
Push the image and build dist/ binaries locally, but don't upload them.
|
||||
|
||||
.EXAMPLE
|
||||
./scripts/publish.ps1 -BinariesOnly -Tag v1.2.0
|
||||
@@ -44,7 +47,7 @@
|
||||
|
||||
.EXAMPLE
|
||||
./scripts/publish.ps1 -NoBinaries
|
||||
Container only — no cross-compile.
|
||||
Container only — no cross-compile, nothing to publish as a release.
|
||||
|
||||
.EXAMPLE
|
||||
$env:GITEA_USER = "kawa"; $env:GITEA_TOKEN = "xxxx"; ./scripts/publish.ps1 -SkipLogin:$false
|
||||
@@ -96,13 +99,12 @@ param(
|
||||
# retrying a failed upload without paying for the build again.
|
||||
[switch]$NoBinaryBuild,
|
||||
|
||||
# Ship only the binaries: no docker build, login or push. Implies
|
||||
# -PublishRelease, since building alone is what build-release.ps1 already does.
|
||||
# Ship only the binaries: no docker build, login or push.
|
||||
[switch]$BinariesOnly,
|
||||
|
||||
# Attach the release archives to the Gitea release for $Tag, creating the
|
||||
# release if it is missing.
|
||||
[switch]$PublishRelease,
|
||||
# Skip attaching the release archives to the Gitea release for $Tag. The
|
||||
# upload happens by default whenever binaries are built.
|
||||
[switch]$NoPublishRelease,
|
||||
|
||||
# owner/repo holding the release. Defaults to $Owner/$Repo.
|
||||
[string]$ReleaseRepo,
|
||||
@@ -282,15 +284,21 @@ try {
|
||||
if ($NoBinaryBuild -and $NoBinaries) {
|
||||
throw "-NoBinaryBuild reuses the build that -NoBinaries skips entirely — pick one."
|
||||
}
|
||||
if ($BinariesOnly -and $NoPublishRelease) {
|
||||
throw "-BinariesOnly with -NoPublishRelease leaves nothing to do — pick one."
|
||||
}
|
||||
if ($BinariesOnly) {
|
||||
# Nothing to build, log into or push on the container side, and uploading
|
||||
# is the whole point (build-release.ps1 alone covers "just build them").
|
||||
$NoBuild = $true
|
||||
$SkipLogin = $true
|
||||
$PublishRelease = $true
|
||||
}
|
||||
$pushImage = -not $BinariesOnly
|
||||
|
||||
# On by default: a run that builds binaries and doesn't publish them is the
|
||||
# unusual case. -NoBinaries means there is nothing to publish either way.
|
||||
$PublishRelease = (-not $NoPublishRelease) -and (-not $NoBinaries)
|
||||
|
||||
if (-not $ReleaseRepo) { $ReleaseRepo = "$Owner/$Repo" }
|
||||
if (-not $ApiBase) { $ApiBase = "https://$Registry" }
|
||||
$apiRoot = "$($ApiBase.TrimEnd('/'))/api/v1"
|
||||
@@ -415,7 +423,7 @@ try {
|
||||
$releaseUrl = $null
|
||||
if ($PublishRelease) {
|
||||
if (-not $artifacts.Count) {
|
||||
throw "-PublishRelease has nothing to upload (was -NoBinaries set?)."
|
||||
throw "nothing to upload — no release archives were built (was -NoBinaries set?)."
|
||||
}
|
||||
Write-Host "Uploading binaries to release $Tag..." -ForegroundColor Cyan
|
||||
$Password = Resolve-Token -Provided $Password -Purpose "release upload to $ReleaseRepo"
|
||||
|
||||
Reference in New Issue
Block a user