# Move your projects by hand

> Move one project from Nexus to Telesphorepo yourself, first its downloads, then its publishing.

Source: https://docs.telesphorepo.com/move-projects-by-hand/

After the [copy from Nexus](/move-from-nexus/), each project still names Nexus. You move one project in two stages:

1. **Downloads.** The build downloads from Telesphorepo. Publishing stays on Nexus.
2. **Publishing.** The project publishes to Telesphorepo, first to a test repository.

You can do the first stage while the copy syncs. To let a coding agent do it with you, see [Move your projects with an AI agent](/move-projects-with-an-agent/).

## Before you start

Write down the name of each repository in Nexus and in Telesphorepo. The **Repositories** page lists them. A lock after a name means the repository is private.

The examples use `https://repo.example.com` for Telesphorepo and `https://nexus.example.com` for Nexus.

## Find every Nexus address

Search every file of the project, hidden folders too, for two things:

- the Nexus host name
- `/repository/`, which every Nexus address holds

Look in the files below. Also look in CI files, Dockerfiles, compose files, scripts and READMEs.

### Gradle or Maven: where to look

- `pom.xml`, `.mvn/maven.config` and every `settings.xml`
- `build.gradle(.kts)`, `settings.gradle(.kts)`, `gradle.properties`, `gradle/*.gradle(.kts)`
- `buildSrc/`, `build-logic/` and init scripts
- `gradle/wrapper/gradle-wrapper.properties`
- On your computer: `~/.m2/settings.xml`, `~/.gradle/gradle.properties` and `~/.gradle/init.d/`

### npm, pnpm, Yarn or Bun: where to look

- `.npmrc`, in the project and in `~/.npmrc`
- `.yarnrc.yml`, `.yarnrc` and `bunfig.toml`
- `package.json` and the lockfiles
- `registry-url` of `actions/setup-node` in CI

### Python: where to look

- `pip.conf` or `pip.ini`, in the project, `~/.config/pip/` and `/etc/`
- `requirements*.txt` and constraints files
- `pyproject.toml`, `uv.toml`, `poetry.toml`, `setup.cfg`, `tox.ini`, `noxfile.py`
- `PIP_INDEX_URL`, `PIP_EXTRA_INDEX_URL`, `UV_INDEX_URL` and `UV_DEFAULT_INDEX` in Dockerfiles and CI
- `.pypirc`, for publishing

### Rust and Cargo: where to look

- `.cargo/config.toml`, in the project and in `~/.cargo/`
- `Cargo.toml` and `Cargo.lock`

### .NET and NuGet: where to look

- `nuget.config`, in the solution folder, its parents and your user folder
- `Directory.Build.props` and `.targets`
- `*.csproj` and `*.fsproj`
- `dotnet nuget add source`, `--source` and `source-url` of `actions/setup-dotnet` in CI

### Docker and Helm: where to look

Also search for each Nexus Docker host and port, such as `nexus.example.com:8082`.

- `Dockerfile*` and compose files
- Kubernetes manifests and Kustomize
- `Chart.yaml`, `Chart.lock` and `values*.yaml`
- helmfile, skaffold, Tiltfile and devcontainer files
- `docker login`, `docker pull`, `helm registry login` and `helm repo add` in CI

### Debian or raw files: where to look

- apt sources, and the Dockerfiles, Ansible, cloud-init files and scripts that write them
- keyrings
- `curl` and `wget` of Nexus files

## Change the download addresses

Change each download address to Telesphorepo. Leave the publish addresses and publish steps on Nexus for now.

The files are the same bytes as in Nexus. Keep every checksum and hash.

### Gradle or Maven: the addresses

`https://nexus.example.com/repository/<nexus name>/` becomes:

```text
https://repo.example.com/maven/<name>
```

- Only the prefix changes. The files keep their paths.
- Point a `<mirror>` with `<mirrorOf>*</mirrorOf>` at a Telesphorepo group.
- A `distributionUrl` on a Nexus raw repository becomes `https://repo.example.com/raw/<name>/<path>`. Keep `distributionSha256Sum`.
- Lockfiles and `gradle/verification-metadata.xml` hold no address. Keep them.

### npm, pnpm, Yarn or Bun: the addresses

`https://nexus.example.com/repository/<nexus name>/` becomes:

```text
https://repo.example.com/npm/<name>/
```

- Keep the slash at the end.
- Change scoped registries (`@acme:registry=`) the same way.
- Remove the Nexus `_auth`, `always-auth` and `email` lines.
- Yarn 2 and later: change `npmRegistryServer` and `npmScopes.<scope>.npmRegistryServer`.
- Bun reads `.npmrc`.

Then fix the lockfiles. In `package-lock.json`, `pnpm-lock.yaml`, `bun.lock` and a Yarn 1 `yarn.lock`, replace the Nexus prefix with the new one. Keep every `integrity`.

Run the frozen install of your tool:

```sh
npm ci                            # or:
pnpm install --frozen-lockfile
yarn install --immutable
bun install --frozen-lockfile
```

### Python: the addresses

`https://nexus.example.com/repository/<nexus name>/simple/` becomes:

```text
https://repo.example.com/pypi/<name>/simple/
```

Then renew the lockfiles with the lock command. Do not edit them by hand:

```sh
uv lock        # or:
poetry lock    # Poetry 1: poetry lock --no-update
```

Read `git diff`. The versions and hashes stay the same; only the addresses change.

In requirements files with `--hash`, change only the index lines.

### Rust and Cargo: the addresses

The index becomes:

```text
sparse+https://repo.example.com/cargo/<name>/index/
```

- Keep the registry names. Change only `index` in `[registries.<name>]`.

- For a proxy or a group of crates.io, also add this:

  ```toml title=".cargo/config.toml"
  [source.crates-io]
  replace-with = "<name>"
  ```

- In `Cargo.lock`, change each `source =` to the new index, exactly as in the config. Keep every `checksum`.

Then run `cargo fetch --locked`.

### .NET and NuGet: the addresses

`https://nexus.example.com/repository/<nexus name>/index.json` becomes:

```text
https://repo.example.com/nuget/<name>/index.json
```

- Keep the source keys, so `packageSourceMapping` and the credentials still match.
- `packages.lock.json` holds no address. Keep it.

### Docker and Helm: the addresses

The repository name is the first part of the image name:

```text
nexus.example.com:8082/team/app:1.0   →   repo.example.com/<name>/team/app:1.0
```

- Through a proxy of Docker Hub, `nginx:1.27` is `repo.example.com/<proxy name>/nginx:1.27`.
- Every `@sha256:` digest stays the same.

Helm charts live in Docker repositories:

- A chart dependency on Nexus becomes `oci://repo.example.com/<name>`.
- Remove its `helm repo add` line.
- Ask your admin which Docker repository holds the charts.
- Run `helm dependency update` to renew `Chart.lock`.

### Debian or raw files: the addresses

An apt source changes like this:

```text
deb https://nexus.example.com/repository/<nexus name>/ <dist> main
deb [signed-by=/etc/apt/keyrings/<name>.asc] https://repo.example.com/debian/<name> <dist> main
```

Every package is in the component `main`. Ask your admin which signing key the repository has:

- **The Nexus key:** the machines trust it already.
- **Another key:** install `https://repo.example.com/debian/<name>/key.asc` into `/etc/apt/keyrings/<name>.asc`.

A raw address `https://nexus.example.com/repository/<nexus name>/<path>` becomes `https://repo.example.com/raw/<name>/<path>`.

## Give the builds a read token

Skip this step when every repository is public.

1. Open **Settings › Tokens**, and click **New read token**.

2. Type a name, such as `my-app builds`, and click **Create token**.

3. Copy the token. It shows only once.

4. Put your username and the token in the variables below. Set them on your computer and as CI secrets.

When you choose the names, use `TELESPHOREPO_READ_USER` and `TELESPHOREPO_READ_TOKEN`. Never use `TELESPHOREPO_TOKEN`: the CLI uses that name when it publishes.

### Gradle or Maven: the read token

**Gradle:** in the `maven { }` block, set a name and the credentials:

```kotlin title="build.gradle.kts"
name = "myLibs"
credentials(PasswordCredentials::class)
```

Gradle then reads `ORG_GRADLE_PROJECT_myLibsUsername` and `ORG_GRADLE_PROJECT_myLibsPassword`. The name is the repository name as a Gradle identifier: `my-libs` is `myLibs`. In Groovy, write `credentials(PasswordCredentials)`.

**Maven:** add a `<server>` whose `<id>` is the `<id>` of the repository or mirror. Put it in `~/.m2/settings.xml`, and in a settings file that CI passes with `-s`:

```xml title="settings.xml"
<server>
  <id>maven-public</id>
  <username>${env.TELESPHOREPO_READ_USER}</username>
  <password>${env.TELESPHOREPO_READ_TOKEN}</password>
</server>
```

### npm, pnpm, Yarn or Bun: the read token

Set `NPM_TOKEN` to the read token, and add this line:

```ini title=".npmrc"
//repo.example.com/npm/<name>/:_authToken=${NPM_TOKEN}
```

Yarn 2 and later: set `npmAlwaysAuth: true` and `npmAuthToken: "${NPM_TOKEN}"` instead.

### Python: the read token

**pip** reads the credentials from the address. Put the whole address in the environment, and keep the plain address in the files:

```sh
export PIP_INDEX_URL="https://$TELESPHOREPO_READ_USER:$TELESPHOREPO_READ_TOKEN@repo.example.com/pypi/<name>/simple/"
```

**uv** reads `UV_INDEX_<NAME>_USERNAME` and `UV_INDEX_<NAME>_PASSWORD`. `<NAME>` is the index name in capitals, with `_` for every other character.

**Poetry** reads `POETRY_HTTP_BASIC_<SOURCE>_USERNAME` and `POETRY_HTTP_BASIC_<SOURCE>_PASSWORD`.

### Rust and Cargo: the read token

Add `credential-provider = "cargo:token"` to the registry. Cargo then reads the token from `CARGO_REGISTRIES_<NAME>_TOKEN`: the registry name in capitals, with `_` for `-`. This needs Cargo 1.74 or later.

### .NET and NuGet: the read token

Add credentials under the source key, here `dotnet`:

```xml title="nuget.config"
<packageSourceCredentials>
  <dotnet>
    <add key="Username" value="%NUGET_USERNAME%" />
    <add key="ClearTextPassword" value="%NUGET_TOKEN%" />
  </dotnet>
</packageSourceCredentials>
```

### Docker and Helm: the read token

**On your computer**, use the credential helper of the CLI instead of a token:

1. [Install the CLI](/install-cli/), and run `telesphorepo login https://repo.example.com`.

2. Add the helper to `~/.docker/config.json`, and keep the other keys:

   ```json title="~/.docker/config.json"
   {
     "credHelpers": {
       "repo.example.com": "telesphorepo"
     }
   }
   ```

Docker, Podman, Helm and ORAS then pull with no `docker login`.

**In CI and on servers**, log in with the read token:

```sh
printf '%s' "$TELESPHOREPO_READ_TOKEN" | docker login repo.example.com -u "$TELESPHOREPO_READ_USER" --password-stdin
```

**In Kubernetes**, make a pull secret with `kubectl create secret docker-registry`.

### Debian or raw files: the read token

**apt** reads credentials only from `/etc/apt/auth.conf.d/`. Run these lines. They ask for the token without showing it:

```sh
printf 'Read token: '; read -rs READ_TOKEN; echo
sudo install -m 600 /dev/null /etc/apt/auth.conf.d/<name>.conf
printf 'machine %s login <user> password %s\n' https://repo.example.com/debian/<name> "$READ_TOKEN" \
  | sudo tee -a /etc/apt/auth.conf.d/<name>.conf > /dev/null
```

**curl:** pass the credentials on stdin, never as an argument:

```sh
printf 'user = "%s:%s"\n' "$TELESPHOREPO_READ_USER" "$TELESPHOREPO_READ_TOKEN" | curl -fsSL -K - -o <file> <address>
```

## Check the build

1. Search again for the Nexus host and `/repository/`. Find out why each hit is still there.

2. Build with an empty cache, as below.

3. Check in the output that every download comes from `repo.example.com`.

4. Commit the changes.

### Gradle or Maven: the check

```sh
./gradlew build --refresh-dependencies
mvn -U -Dmaven.repo.local=/tmp/empty-m2 verify
```

### npm, pnpm, Yarn or Bun: the check

Remove `node_modules`, then run the frozen install, such as `npm ci`.

### Python: the check

In a new virtual environment:

```sh
pip install --no-cache-dir -r requirements.txt   # or: uv sync, poetry install
```

### Rust and Cargo: the check

```sh
cargo fetch --locked
cargo build
```

### .NET and NuGet: the check

```sh
dotnet nuget locals all --clear
dotnet restore
```

### Docker and Helm: the check

```sh
docker build --no-cache --pull .
```

Then pull each image of the compose files and manifests.

### Debian or raw files: the check

Run `sudo apt update`, then install a package. Download each raw file with `curl`, and compare its SHA-256.

If you move only the downloads, you are done.

## Move publishing

The copies refuse uploads until the switch-over. So the project first publishes to a test repository.

Ask your admin for two things:

- **A test repository**: a hosted repository of the same format, such as `maven-releases-rehearsal`, where your account has **Publish**.
- **A CI pipeline** for the project, with the test repository and the real one. See [Ask for a CI pipeline](/publish-from-ci/#ask-for-a-ci-pipeline).

Then change the project:

1. Remove the Nexus publishing from the project and its CI, as below. Keep the steps that build the artifacts.

2. Open the test repository, then the **Publish to this repository** tab.

3. Click **Set up a project**, and follow [Publish from a project](/publish-from-a-project/). The CLI writes `telesphorepo.toml` and, if you want, a CI file.

4. Delete the CI secrets that only Nexus used.

### Gradle or Maven: what to remove

- The Nexus `<distributionManagement>` entries and `<server>` credentials
- Nexus staging plugins, such as `nexus-staging-maven-plugin` and `io.github.gradle-nexus.publish-plugin`
- The Nexus repository in `publishing { repositories { } }`

Keep `maven-publish`, the publications, `group` and `version`. `telesphorepo publish` runs `mvn deploy` or `gradle publish` with the repository as the target.

### npm, pnpm, Yarn or Bun: what to remove

- `publishConfig.registry`
- The Nexus publish auth lines
- The `npm publish` steps of CI and their secrets

`telesphorepo publish` picks the tool by the lockfile. In a workspace, put a `telesphorepo.toml` in the folder of each package that publishes.

### Python: what to remove

- `.pypirc` and `twine upload`
- `uv publish` and `publish-url`
- `poetry publish -r` and the Poetry repository for Nexus

`telesphorepo publish` builds with Poetry, uv or `python -m build`, then uploads every wheel and sdist.

### Rust and Cargo: what to remove

- The `cargo publish --registry <nexus>` steps and the Nexus tokens

Where `Cargo.toml` names Nexus in `publish =`, set `publish = ["telesphorepo"]`. Keep the key: without it, a plain `cargo publish` goes to crates.io.

### .NET and NuGet: what to remove

- The `dotnet nuget push` or `nuget push` steps to Nexus
- `<apikeys>` and the API key secrets

`telesphorepo publish` runs `dotnet pack -c Release` and pushes each package.

### Docker and Helm: what to remove

- `docker login` to Nexus and the Nexus push credentials
- `helm push` and `curl` uploads to Nexus

Images push with `docker push repo.example.com/<repo>/<image>:<tag>` through the credential helper. They need no `telesphorepo.toml`. A chart folder gets one, and `telesphorepo publish` packages and pushes the chart.

### Debian or raw files: what to remove

- The `curl -u` uploads to Nexus and their passwords

`telesphorepo publish <file>` uploads the files.

## Rehearse publishing

1. In the project folder, run `telesphorepo status`. Check the command it names.

2. Publish to the test repository:

   ```sh
   telesphorepo publish
   ```

3. In a new empty folder outside the project, install what you published, as below. Then remove the folder.

4. To rehearse CI too, push to a branch that the CI pipeline lists. Check the run.

### Gradle or Maven: the install check

Write a small build that depends on the `group:artifact:version` you published, from `https://repo.example.com/maven/<test repository>`. Build it with an empty cache.

### npm, pnpm, Yarn or Bun: the install check

Run `npm init -y`, and write an `.npmrc` with the test registry. Then run `npm install <package>@<version>`.

### Python: the install check

In a new virtual environment:

```sh
pip install --index-url https://repo.example.com/pypi/<test repository>/simple/ \
  --extra-index-url https://pypi.org/simple/ <package>==<version>
```

### Rust and Cargo: the install check

Run `cargo new verify`, and write a `.cargo/config.toml` with the test registry. Then run `cargo add <crate>@<version> --registry <name>` and `cargo build`.

### .NET and NuGet: the install check

Run `dotnet new console`, and write a `nuget.config` with the test source. Then run `dotnet add package <id> --version <version>`.

### Docker and Helm: the install check

Remove the local image, then pull it:

```sh
docker pull repo.example.com/<test repository>/<image>:<tag>
helm pull oci://repo.example.com/<test repository>/<chart> --version <version>
```

### Debian or raw files: the install check

For raw files, download the file with `curl`, and compare its SHA-256. For Debian, add the test source and its key in a throwaway `debian` container. Then run `apt-get update` and `apt-get install <package>`.

## After the switch-over

When your admin has [switched over](/move-from-nexus/#switch-over), the copies take uploads.

1. Set `repo` in `telesphorepo.toml` to the real repository:

   ```toml title="telesphorepo.toml"
   server = "https://repo.example.com"
   repo = "maven-releases"
   ```

2. Check that the CI pipeline lists the real repository.

3. Publish once.

4. Delete the test repository.

## If something goes wrong

The publish answers 403

Your account lacks **Publish** on the repository. Ask your admin for it.

The Tokens page has no New read token

You sign in with single sign-on, and such an account has no read tokens. Ask your admin for an account with a password for the builds.

CI does not find telesphorepo.toml

Run `git check-ignore telesphorepo.toml`. If git ignores the file, CI never sees it. Change `.gitignore`, then commit the file.

The server runs on plain http\://

Each tool needs the host allowed:

- Gradle: `isAllowInsecureProtocol = true`. Maven refuses `http://` other than `localhost`.
- pip: `trusted-host = <host>`. uv: `allow-insecure-host`.
- Yarn: the host in `unsafeHttpWhitelist`.
- NuGet: `allowInsecureConnections="true"` on the source.
- Docker: the host in `insecure-registries` of `/etc/docker/daemon.json`.

### Gradle or Maven: problems

Public dependencies stop resolving

A hosted repository holds only its own packages. Keep `mavenCentral()`, or use a group with a proxy of Maven Central.

### npm, pnpm, Yarn or Bun: problems

Yarn 4 does not install a new version

On a hosted repository, add `npmMinimalAgeGate: 0`, or Yarn 4 waits a week. On a group or a proxy, list your own packages in `npmPreapprovedPackages` instead.

A group misses versions of a package

In an npm group, the first member that has a package name serves every version of it. Nexus merges them. If you need versions of one name from two members, ask your admin.

npm audit fails

Telesphorepo does not serve `npm audit`. Run it with `--registry=https://registry.npmjs.org/`.

### Python: problems

Dependencies stop resolving

A hosted repository holds only your own packages. Use a group with a proxy of PyPI, or PyPI itself. In a group, the first member that has a project name serves every file of it.

### Rust and Cargo: problems

A group misses versions of a crate

In a Cargo group, the first member that has a crate name serves every version of it.

### .NET and NuGet: problems

A group misses versions of a package

In a NuGet group, the first member that has a package id serves every version of it.

The source key starts with a digit

In `<packageSourceCredentials>`, write such a key as `_x003N_...`.

### Docker and Helm: problems

CI cannot pull a private base image

With its identity token, a CI job reads only what anyone can read. Log in with the read token before the build.

docker pull asks for a login on your computer

`telesphorepo login` does not add the credential helper. Run `telesphorepo status`: it shows whether `~/.docker/config.json` has the entry.
