Add README.md and final changes

This commit is contained in:
agosti_j committed 2026-08-13 09:40:15 +02:00
1 parent e54b792746
commit 93370602ee
5 files changed
+227 -48

No files matched your search

+21
View File
@@ -0,0 +1,21 @@
BINARY := findbaseport
.PHONY: all fmt vet lint build clean
all: fmt vet lint build
fmt:
go fmt ./...
vet:
go vet ./...
lint:
golangci-lint run
build:
mkdir -p bin
go build -o bin/$(BINARY)
clean:
rm -rf bin/
+163 -2
View File
@@ -1,3 +1,164 @@
# findcports
# findbaseport
`findbaseport` is a CLI tool that searches a specified port range and returns the starting port of the first contiguous block of available ports with the requested size.
## Motivation
This tool was created to simplify finding a suitable base port for a [CryoSPARC](https://cryosparc.com/) installation.
CryoSPARC requires a base port and expects a range of **10 contiguous ports** to be available starting from that port. Finding an appropriate base port manually can be inconvenient, particularly on systems where some ports are already in use.
`findbaseport` automates this process by searching a specified port range and returning the first port from which the requested number of contiguous ports are available.
For example, when looking for a base port with the default requirement of 10 contiguous ports, the tool searches for a sequence such as:
```text
8000 8001 8002 8003 8004 8005 8006 8007 8008 8009
```
If all 10 ports are available, `8000` can be used as the base port. If any of those ports are unavailable, the tool continues searching until it finds the next suitable contiguous block.
## Description
The tool searches an **inclusive** port range for available ports.
Given a range and a requested block size, `findbaseport` finds the first sequence of contiguous available ports that is large enough and returns the starting port of that block.
For example, if the requested block size is `3` in a range of `8000` to `8020`:
```bash
findbaseport --start 8000 --end 8020 --count 3
```
and ports `8000`, `8001`, and `8002` are available, the command returns:
```text
8000
```
If those ports are unavailable but `8010`, `8011`, and `8012` are available, it returns:
```text
8010
```
## Installation
### Build from source
Clone the repository and use the included Makefile:
```bash
git clone <repository-url>
cd <repository-directory>
make build
```
The resulting binary is placed in:
```text
bin/findbaseport
```
You can then run it with:
```bash
./bin/findbaseport --help
```
## Usage
```bash
./bin/findbaseport --help
```
The help output describes the available command-line options and arguments.
The command searches within the specified port range and returns the start port of the first contiguous block of available ports with the requested size.
## Development
The project provides a Makefile with commands for formatting, static analysis, linting, and building.
### Run everything
```bash
make
```
The default target runs:
1. `fmt`
2. `vet`
3. `lint`
4. `build`
### Format
Format all Go source files:
```bash
make fmt
```
### Vet
Run Go's static analysis:
```bash
make vet
```
### Lint
Run `golangci-lint`:
```bash
make lint
```
Make sure `golangci-lint` is installed and available in your `PATH`.
### Build
Build the CLI:
```bash
make build
```
The binary is written to:
```text
bin/findbaseport
```
### Clean
Remove build artifacts:
```bash
make clean
```
## Requirements
* Go
* `make`
* `golangci-lint` for linting
## Make Targets
| Target | Description |
| ------------ | ---------------------------------- |
| `make` | Format, vet, lint, and build |
| `make fmt` | Format Go source code |
| `make vet` | Run `go vet` |
| `make lint` | Run `golangci-lint` |
| `make build` | Build `findbaseport` into `bin/` |
| `make clean` | Remove the `bin/` directory |
## License
See the repository's `LICENSE` file for licensing information.
A CLI tool that searches within a specified port range and returns the start and end ports of the first contiguous block of available ports with the requested size.
+2 -2
View File
@@ -1,4 +1,4 @@
module findcports
module findbaseport
go 1.25.8
@@ -6,7 +6,6 @@ require (
charm.land/lipgloss/v2 v2.0.5
github.com/charmbracelet/fang v1.0.0
github.com/charmbracelet/x/exp/charmtone v0.0.0-20260727090823-41c9e6be3365
github.com/prometheus/common v0.70.1
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10
)
@@ -29,6 +28,7 @@ require (
github.com/muesli/mango-pflag v0.1.0 // indirect
github.com/muesli/roff v0.1.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/stretchr/testify v1.11.1 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/sync v0.22.0 // indirect
golang.org/x/sys v0.47.0 // indirect
-2
View File
@@ -45,8 +45,6 @@ github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8=
github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/common v0.70.1 h1:1HvjP4D5oL3t8RsPlwxA9onvvStjtIHYE5XuuwOi/PY=
github.com/prometheus/common v0.70.1/go.mod h1:VdFUQDMZK3VLkurFUVhia6uys/0suUp86TJz5qbJRhc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+41 -42
View File
@@ -3,14 +3,13 @@ package main
import (
"context"
"fmt"
"log"
"net"
"os"
"strconv"
"charm.land/lipgloss/v2"
"github.com/charmbracelet/fang"
"github.com/charmbracelet/x/exp/charmtone"
"github.com/prometheus/common/version"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@@ -21,7 +20,12 @@ func isPortFree(port uint) bool {
if err != nil {
return false
}
l.Close()
defer func() {
if err := l.Close(); err != nil {
log.Printf("failed to close Listener: %v", err)
}
}()
return true
}
@@ -33,10 +37,6 @@ func FindAvailablePorts(start, end, count uint) (uint, uint, error) {
err := fmt.Errorf("start port must not exceed end port")
return 0, 0, err
}
if start == 0 {
err := fmt.Errorf("start port must be greater than zero")
return 0, 0, err
}
if end == 0 {
err := fmt.Errorf("end port must be greater than zero")
return 0, 0, err
@@ -64,14 +64,15 @@ func FindAvailablePorts(start, end, count uint) (uint, uint, error) {
}
}
return 0, 0, fmt.Errorf("no contiguous range of %d ports found", count)
return 0, 0, fmt.Errorf(
"no contiguous range of %d ports found between %d and %d",
count, start, end)
}
// Color and column style variables
// Color style variables
var (
rangePortStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("6")).Bold(true)
returnPortStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("5")).Bold(true)
helpTitleStyle = lipgloss.NewStyle().Foreground(charmtone.Charple).Bold(true)
helpTitleStyle = lipgloss.NewStyle().Foreground(charmtone.Charple).
Bold(true)
)
// Flag variables
@@ -83,6 +84,9 @@ type config struct {
}
}
// Setting the port limit
var portLimit uint = 65535
var cfg config
func main() {
@@ -92,12 +96,12 @@ func main() {
// CLI Create a new cobra Command
cobraCmd := &cobra.Command{
Use: "findcports",
Use: "findbaseport",
Long: fmt.Sprintf(`%s
Searches within a specified port range and returns the
start and end ports of the first contiguous block of
available ports with the requested size.`,
Searches within a specified port range (inclusive) and returns the
start port of the first contiguous block of available
ports with the requested size.`,
helpTitleStyle.Render("DESCRIPTION")),
RunE: runApp,
}
@@ -107,15 +111,15 @@ available ports with the requested size.`,
// =============================================
// ports
portsFS.UintVarP(&cfg.ports.start, "start", "s", 20000,
portsFS.UintVarP(&cfg.ports.start, "start", "s", 0,
"First port in the search range",
)
portsFS.UintVarP(&cfg.ports.end, "end", "e", 20999,
portsFS.UintVarP(&cfg.ports.end, "end", "e", portLimit,
"Last port in the search range",
)
portsFS.UintVarP(&cfg.ports.count, "count", "c", 10,
portsFS.UintVarP(&cfg.ports.count, "count", "c", 0,
"Size of the contiguous port block to find",
)
@@ -128,47 +132,42 @@ available ports with the requested size.`,
// Set new flag display settings
cobraCmd.Flags().SetInterspersed(false)
cobraCmd.Flags().SortFlags = false
cobraCmd.Flags().PrintDefaults()
// Remove completion and help commands (--help is kept)
cobraCmd.CompletionOptions.DisableDefaultCmd = true
cobraCmd.SetHelpCommand(&cobra.Command{Hidden: true})
// CLI Version
cobraCmd.Version = version.Print("findcports")
// Execute cobra command
if err := fang.Execute(context.Background(), cobraCmd); err != nil {
os.Exit(1)
}
}
func runApp(cobraCmd *cobra.Command, args []string) error {
func runApp(cmd *cobra.Command, _ []string) error {
startPort, endPort, err := FindAvailablePorts(
// Check if --count was set with either --start or --end
if !cmd.Flags().Changed("count") ||
(!cmd.Flags().Changed("start") && !cmd.Flags().Changed("end")) {
return fmt.Errorf(
"flag --count with either --start or --end is required")
// Ports cannot exceed a limit
} else if cfg.ports.count > portLimit+1 ||
cfg.ports.start > portLimit ||
cfg.ports.end > portLimit {
return fmt.Errorf(
"the TCP port number cannot exceed %d", portLimit)
}
startPort, _, err := FindAvailablePorts(
cfg.ports.start,
cfg.ports.end,
cfg.ports.count)
if err != nil {
return err
} else {
fmt.Printf("%d\n", startPort)
}
if startPort == endPort {
fmt.Printf(
"Found the first available port between %s and %s:\n%s\n",
rangePortStyle.Render(strconv.FormatUint(uint64(cfg.ports.start), 10)),
rangePortStyle.Render(strconv.FormatUint(uint64(cfg.ports.end), 10)),
returnPortStyle.Render(strconv.FormatUint(uint64(startPort), 10)),
)
} else {
fmt.Printf(
"Found the first available continuous ports between %s and %s:\n%s-%s (%d ports)\n",
rangePortStyle.Render(strconv.FormatUint(uint64(cfg.ports.start), 10)),
rangePortStyle.Render(strconv.FormatUint(uint64(cfg.ports.end), 10)),
returnPortStyle.Render(strconv.FormatUint(uint64(startPort), 10)),
returnPortStyle.Render(strconv.FormatUint(uint64(endPort), 10)),
cfg.ports.count,
)
}
return nil
}