Add README.md and final changes
This commit is contained in:
5 files changed
+227
-48
No files matched your search
@@ -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/
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in new issue
Block a user