Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ jobs:
version: latest

- name: Run tests
run: make unit-test
run: make test
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.PHONY: unit-test
unit-test:
.PHONY: test
test:
go test -v -race ./...

.PHONY: lint
Expand Down
106 changes: 105 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,105 @@
package dns_manager
# DNS Manager

![Go Version](https://img.shields.io/badge/Go-1.26-00ADD8?style=flat&logo=go)
![Build Status](https://img.shields.io/badge/build-passing-success)
![Lint](https://img.shields.io/badge/lint-golangci--lint-blue)
![Architecture](https://img.shields.io/badge/architecture-hexagonal-orange)

**DNS Manager** - это легковесное клиент-серверное приложение на Go для безопасного управления DNS-серверами
операционной системы посредством редактирования конфигурации `/etc/resolv.conf`.

Проект реализован в виде **единого бинарного файла** (Unified Binary), который предоставляет CLI-интерфейс как для
запуска gRPC-сервера, так и для клиентского управления им.

## Ключевые особенности

* **Атомарная запись:** Обновление файла конфигурации происходит через временный файл и системный вызов `os.Rename`.
Система гарантированно не останется с поврежденным конфигом при сбоях питания или падении процесса.
* **Сохранность данных:** Парсер изменяет исключительно директивы `nameserver`, оставляя пользовательские комментарии и
системные настройки (например, `search`, `options`) в исходном виде.
* **Потокобезопасность:** Вся бизнес-логика защищена мьютексами (`sync.RWMutex`), что исключает состояние гонки при
конкурентных запросах к gRPC API.
* **Graceful Shutdown:** Сервер корректно обрабатывает системные сигналы (`SIGTERM`, `SIGINT`) и дожидается завершения
активных соединений перед остановкой.
* **Чистая Архитектура:** Разделение на порты и адаптеры (Hexagonal Architecture) позволяет легко тестировать
бизнес-логику изолированно от сети и файловой системы.

## Подготовка и сборка

Для работы с проектом потребуется **Go 1.26** и утилита **make**.

### Управление проектом через Makefile

В проекте настроен `Makefile` для автоматизации рутинных задач. Описание доступных команд:

* `make tools` - устанавливает все необходимые зависимости для разработки и кодогенерации (плагины `protoc`,
`protolint`, `grpcurl` и `golangci-lint`). *Убедитесь, что у вас установлен системный компилятор `protoc`*.
* `make protobuf` - компилирует файлы контрактов `proto/dns/v1/dns.proto` в исходный код Go (создает gRPC-клиенты и
стабы сервера).
* `make protolint` - проверяет стиль написания `.proto` файлов с помощью `protolint`.
* `make lint` - запускает строгую проверку исходного кода линтером `golangci-lint` в соответствии с конфигурацией
`.golangci.yaml`.
* `make test` - запускает все юнит-тесты и интеграционные E2E тесты с включенным детектором гонки данных (`-race`).

### Быстрый старт сборки

```bash
# 1. Установите инструменты и зависимости
make tools

# 2. Сгенерируйте gRPC код
make protobuf

# 3. Соберите бинарный файл
go build -o dns-manager ./cmd/dns-manager
```

## Использование

Приложение предоставляет единую точку входа со встроенной справочной системой (Cobra CLI). Для получения справки по
любой команде используйте флаг --help.

### Запуск сервера

По умолчанию сервер пытается изменять системный `/etc/resolv.conf`, для чего требуются права суперпользователя (`sudo`).
Для локального тестирования предусмотрен флаг `--config`.

```Bash
# Запуск в на порту 50051 (требует root)
sudo ./dns-manager server 50051

# Запуск с файлом конфига, куда будем писать
touch test_resolv.conf
./dns-manager server 50051 --config ./test_resolv.conf
```

### Команды клиента

Клиент позволяет управлять запущенным сервером по сети. По умолчанию клиент обращается к `localhost:50051`, но адрес
можно переопределить глобальным флагом `-s` или `--server`.

```Bash
# Добавить новый DNS-сервер
./dns-manager client add 8.8.8.8

# Удалить DNS-сервер
./dns-manager client remove 8.8.8.8

# Получить список текущих серверов
./dns-manager client list

# Пример управления удаленным сервером
./dns-manager client list --server 192.168.1.100:50051
```

## Тестирование

Проект покрыт изолированными юнит-тестами и полноценным интеграционным E2E-тестом.
Интеграционный тест разворачивает gRPC-сервер в памяти на динамическом порту, подключает к нему клиента и проводит
полный цикл проверки бизнес-логики над временным файлом.

Для запуска тестов используйте:

```Bash
make test
```
1 change: 0 additions & 1 deletion cmd/client/main.go

This file was deleted.

12 changes: 12 additions & 0 deletions cmd/dns-manager/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Package main is the entry point for the dns-manager unified binary.
// It delegates all execution logic to the CLI adapter, which handles
// command routing, flag parsing, and the application lifecycle.
package main

import "github.com/ilindan-dev/dns-manager/internal/adapters/cli"

func main() {
// Execute the root Cobra command. All setup, execution, and error handling
// (including os.Exit calls on fatal failures) are managed within the cli package.
cli.Execute()
}
1 change: 0 additions & 1 deletion cmd/server/main.go

This file was deleted.

3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ module github.com/ilindan-dev/dns-manager
go 1.26.2

require (
github.com/spf13/cobra v1.10.2
google.golang.org/grpc v1.81.0
google.golang.org/protobuf v1.36.11
)

require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/pflag v1.0.9 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.34.0 // indirect
Expand Down
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
Expand All @@ -10,6 +11,13 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
Expand All @@ -22,6 +30,7 @@ go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfC
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
Expand All @@ -36,3 +45,4 @@ google.golang.org/grpc v1.81.0 h1:W3G9N3KQf3BU+YuCtGKJk0CmxQNbAISICD/9AORxLIw=
google.golang.org/grpc v1.81.0/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
1 change: 0 additions & 1 deletion internal/adapters/cli/cobra.go

This file was deleted.

Loading
Loading