Compare commits

1 Commits

Author SHA1 Message Date
Mikan
71ffcc89bf fix 2026-02-13 21:58:35 +03:00
10 changed files with 213 additions and 227 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
# Environment
.env
domains.txt
# Certificates and secrets
/nginx/certs/

175
README.md
View File

@@ -1,130 +1,117 @@
# Local DNS Services with Docker
# Локальная сеть с доменами `.mikan` и HTTPS
This project provides a local development infrastructure with automatic DNS resolution for custom domains (e.g., `.mikan` or `.local`) and valid HTTPS certificates.
Система для запуска сервисов в Docker с автоматическим HTTPS, локальными доменами (`service.mikan`, `service.local`) и возможностью расширения (Tailscale, публичные домены).
## Features
## Требования
- Automatic DNS resolution for `*.yourdomain` and `*.yourdomain.local`
- Valid HTTPS certificates for local domains using `mkcert`
- Easy service deployment with `VIRTUAL_HOST` environment variables
- Ready for future expansion: public domains, Let's Encrypt, Tailscale
- Single-command service creation
- Docker + Docker Compose
- `mkcert` — [инструкция по установке](https://github.com/FiloSottile/mkcert#installation)
- `bash`, `coreutils`
## Requirements
## Быстрый старт
- Docker and Docker Compose
- `mkcert` installed on the host system
## Quick Start
1. Clone this repository and enter the directory:
1. **Клонируй репозиторий**
```bash
git clone <repository-url> local-dns
cd local-dns
git clone ... && cd local-dns
```
2. Initialize the project:
2. **Настрой домен (опционально)**
```bash
./scripts/00-init.sh
echo "LOCAL_DOMAIN=mikan" > .env
```
This creates a `.env` file from `.env.example` and sets up the directory structure.
Замени `mikan` на желаемый суффикс (например, `home`).
3. Generate TLS certificates:
3. **Инициализируй CA и базовый сертификат**
```bash
./scripts/01-generate-cert.sh
./scripts/01-init-ca.sh
```
This creates trusted certificates for your local domain.
- Создаст доверенный корневой сертификат (`ca/rootCA.pem`)
- Сгенерирует `default.crt` для nginx-proxy
4. Start the core infrastructure:
4. **Установи корневой сертификат в систему**
Импортируй файл `ca/rootCA.pem` в хранилище **доверенных корневых центров сертификации**:
- **Linux**:
```bash
sudo cp ca/rootCA.pem /usr/local/share/ca-certificates/mkcert-local.crt
sudo update-ca-certificates
```
- **Windows/macOS**: двойной клик по файлу → «Доверять всегда» (macOS) или импорт в «Доверенные корневые центры» (Windows)
5. **Запусти инфраструктуру**
```bash
docker compose up -d
```
5. Add your first service (example: Gitea on port 3000):
6. **Добавь сервис**
```bash
./scripts/add-service.sh gitea 3000
./scripts/add-service.sh gitea
```
Then edit `services/gitea/docker-compose.yml` to set the correct image and volumes.
Сервис будет доступен по:
- `https://gitea.mikan`
- `https://gitea.local`
\+ может пригодиться:
6. Start the new service:
```bash
docker compose -f docker-compose.yml -f services/gitea/docker-compose.yml up -d
Add-Content -Path "C:\Windows\System32\drivers\etc\hosts" -Value "10.0.0.3 youtrack.mikan"
```
7. Visit `https://gitea.yourdomain` in your browser (replace `yourdomain` with your configured domain).
7. **Настрой сервис в Docker Compose**
Убедись, что в `docker-compose.yml` сервиса указано:
```yaml
services:
gitea:
environment:
- VIRTUAL_HOST=gitea.mikan,gitea.local
- VIRTUAL_PORT=3000 # порт внутри контейнера
networks:
- devnet
dns: 10.0.0.2 # обязательно для резолвинга доменов из контейнеров
```
## Configuration
## Важные замечания
Edit the `.env` file to customize your setup:
### DNS из контейнеров
Контейнеры **не могут** резолвить `*.mikan` без явного указания DNS:
```yaml
dns: 10.0.0.2 # IP dnsmasq
```
Добавляй эту строку во все сервисы, которым нужен доступ к другим по домену.
- `LOCAL_DOMAIN`: Your local domain suffix (default: `mikan`)
- `PUBLIC_DOMAIN`: Optional public domain for Let's Encrypt certificates
- `LETSENCRYPT_EMAIL`: Email for Let's Encrypt notifications
- Network settings (`NGINX_IP`, `DNSMASQ_IP`, etc.)
### Обновление сертификатов
При добавлении сервиса через `add-service.sh`:
- Автоматически обновляется `domains.txt`
- Генерируется новый `default.crt`
- Перезагружается `nginx-proxy`
After changing `.env`, re-run the initialization and certificate scripts.
### Пути
- Сертификаты: `nginx/certs/default.crt`
- Домены: `domains.txt`
- Корневой CA: `ca/rootCA.pem`
## DNS Setup
## Расширение
### Linux/macOS
Use the included `dnsmasq` service for automatic DNS resolution:
```bash
docker compose -f docker-compose.yml -f docker-compose.dnsmasq.yml up -d
### Tailscale
Добавь в `docker-compose.yml` сервис `tailscale` и объявляй дополнительные домены:
```yaml
environment:
- VIRTUAL_HOST=gitea.mikan,gitea.local,gitea.yourname.ts.net
```
Then configure your system to use `10.0.0.2` as the primary DNS server.
### Windows (WSL2)
Windows reserves port 53, so `dnsmasq` cannot run on the host. Instead:
1. Work inside WSL2
2. Manually add entries to the Windows hosts file:
```powershell
# Run PowerShell as Administrator
Add-Content -Path "C:\Windows\System32\drivers\etc\hosts" -Value "127.0.0.1 gitea.mikan"
Add-Content -Path "C:\Windows\System32\drivers\etc\hosts" -Value "127.0.0.1 gitea.mikan.local"
### Публичный домен
1. Купи домен (например, `example.com`)
2. В `.env` добавь:
```env
PUBLIC_DOMAIN=example.com
```
## Certificate Trust
After running `./scripts/01-generate-cert.sh`, install the root certificate on your devices:
- **Linux**: Depends on distribution (usually via `update-ca-certificates`)
- **macOS**:
```bash
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ca/rootCA.pem
3. В сервисах укажи:
```yaml
environment:
- VIRTUAL_HOST=gitea.mikan,gitea.local,gitea.example.com
- LETSENCRYPT_HOST=gitea.example.com
```
- **Windows**:
1. Copy `ca/rootCA.pem` to Windows
2. Open `certmgr.msc`
3. Import into "Trusted Root Certification Authorities"
Сертификат для `example.com` получит `acme-companion`.
## Adding Public Domain Support
---
To enable Let's Encrypt certificates for a public domain:
1. Set in `.env`:
```ini
PUBLIC_DOMAIN=yourdomain.com
LETSENCRYPT_EMAIL=you@yourdomain.com
```
2. Re-run initialization:
```bash
./scripts/00-init.sh
./scripts/01-generate-cert.sh
```
3. New services will automatically request Let's Encrypt certificates for the public domain.
## Service Management
- **Add a service**: `./scripts/add-service.sh <name> <port>`
- **Start all**: `docker compose up -d`
- **Start with dnsmasq**: `docker compose -f docker-compose.yml -f docker-compose.dnsmasq.yml up -d`
- **Stop all**: `docker compose down`
All service data is stored in the `services/` directory and persists between restarts.
Готово. Теперь все сервисы работают по HTTPS с валидными сертификатами.

View File

@@ -1,26 +0,0 @@
version: '3.8'
services:
dnsmasq:
image: jpillora/dnsmasq
container_name: dnsmasq
restart: unless-stopped
ports:
- "53:53/udp"
- "53:53/tcp"
volumes:
- ./dnsmasq/dnsmasq.conf:/etc/dnsmasq.conf:ro
cap_add:
- NET_ADMIN
networks:
devnet:
ipv4_address: ${DNSMASQ_IP}
healthcheck:
test: ["CMD", "nslookup", "localhost", "127.0.0.1"]
interval: 30s
timeout: 5s
retries: 3
networks:
devnet:
external: true

View File

@@ -56,13 +56,30 @@ services:
depends_on:
- nginx-proxy
dnsmasq:
image: jpillora/dnsmasq
container_name: dnsmasq
restart: unless-stopped
ports:
- "53:53/udp"
- "53:53/tcp"
volumes:
- ./dnsmasq/dnsmasq.conf:/etc/dnsmasq.conf:ro
cap_add:
- NET_ADMIN
networks:
devnet:
ipv4_address: ${DNSMASQ_IP}
healthcheck:
test: ["CMD", "nslookup", "localhost", "127.0.0.1"]
interval: 30s
timeout: 5s
retries: 3
networks:
devnet:
driver: bridge
ipam:
config:
- subnet: ${SUBNET}
gateway: ${GATEWAY}
external: true
name: devnet
volumes:
acme:

View File

@@ -24,7 +24,6 @@ echo "Проверка сети devnet..."
if ! docker network inspect devnet &>/dev/null; then
echo "Создаём сеть devnet..."
docker network create \
--driver=bridge \
--subnet="${SUBNET}" \
--gateway="${GATEWAY}" \
devnet

View File

@@ -1,49 +0,0 @@
#!/bin/bash
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$SCRIPT_DIR/.."
if [ ! -f "$PROJECT_ROOT/.env" ]; then
echo "Файл .env не найден. Запустите сначала ./scripts/00-init.sh"
exit 1
fi
export $(grep -v '^#' "$PROJECT_ROOT/.env" | xargs)
CERTS_DIR="$PROJECT_ROOT/nginx/certs"
CA_DIR="$PROJECT_ROOT/ca"
if ! command -v mkcert &> /dev/null; then
echo "mkcert не установлен."
echo "Linux: sudo apt install mkcert"
echo "macOS: brew install mkcert nss"
exit 1
fi
mkdir -p "$CERTS_DIR" "$CA_DIR"
DOMAINS=(
"*.$LOCAL_DOMAIN"
"*.$LOCAL_DOMAIN.local"
"$LOCAL_DOMAIN"
"localhost"
"127.0.0.1"
"::1"
)
if [ -n "$PUBLIC_DOMAIN" ]; then
DOMAINS+=("*.$PUBLIC_DOMAIN" "$PUBLIC_DOMAIN")
fi
mkcert -install
mkcert \
-cert-file "$CERTS_DIR/default.crt" \
-key-file "$CERTS_DIR/default.key" \
"${DOMAINS[@]}"
cp "$(mkcert -CAROOT)/rootCA.pem" "$CA_DIR/rootCA.pem"
echo "Сертификаты созданы в $CERTS_DIR"
echo "Установите корневой сертификат на устройствах:"
echo " $CA_DIR/rootCA.pem"

38
scripts/01-init-ca.sh Normal file
View File

@@ -0,0 +1,38 @@
#!/bin/bash
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$SCRIPT_DIR/.."
if [ -f "$ROOT_DIR/.env" ]; then
export $(grep -v '^#' "$ROOT_DIR/.env" | xargs)
fi
LOCAL_DOMAIN=${LOCAL_DOMAIN:-mikan}
CA_DIR="$ROOT_DIR/ca"
CERTS_DIR="$ROOT_DIR/nginx/certs"
DOMAINS_FILE="$ROOT_DIR/domains.txt"
mkdir -p "$CA_DIR" "$CERTS_DIR"
if [ ! -f "$DOMAINS_FILE" ]; then
touch "$DOMAINS_FILE"
fi
if [ ! -f "$CA_DIR/rootCA.pem" ]; then
mkcert -install
cp "$(mkcert -CAROOT)/rootCA.pem" "$CA_DIR/rootCA.pem"
cp "$(mkcert -CAROOT)/rootCA-key.pem" "$CA_DIR/rootCA-key.pem"
cat > "$DOMAINS_FILE" <<EOF
$LOCAL_DOMAIN
welcome.$LOCAL_DOMAIN
localhost
127.0.0.1
::1
*.services.$LOCAL_DOMAIN
*.services.local
EOF
fi
exec "$SCRIPT_DIR/regenerate-cert.sh"

View File

@@ -1,65 +1,41 @@
#!/bin/bash
set -e
if [ $# -ne 2 ]; then
echo "Использование: $0 <название> <порт>"
echo "Пример: $0 gitea 3000"
exit 1
fi
NAME=$1
PORT=$2
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$SCRIPT_DIR/.."
ROOT_DIR="$SCRIPT_DIR/.."
if [ ! -f "$PROJECT_ROOT/.env" ]; then
echo "Файл .env не найден. Запустите сначала ./scripts/00-init.sh"
if [ -f "$ROOT_DIR/.env" ]; then
export $(grep -v '^#' "$ROOT_DIR/.env" | xargs)
fi
LOCAL_DOMAIN=${LOCAL_DOMAIN:-mikan}
if [ $# -lt 1 ]; then
echo "Usage: $0 <service_name> [extra_domains...]"
exit 1
fi
export $(grep -v '^#' "$PROJECT_ROOT/.env" | xargs)
SERVICE_NAME=$1
shift
EXTRA_DOMAINS=("$@")
SERVICE_DIR="$PROJECT_ROOT/services/$NAME"
DOMAINS_FILE="$ROOT_DIR/domains.txt"
if [ -d "$SERVICE_DIR" ]; then
echo "Сервис '$NAME' уже существует!"
exit 1
if [ ! -f "$DOMAINS_FILE" ]; then
touch "$DOMAINS_FILE"
fi
mkdir -p "$SERVICE_DIR/data"
PRIMARY="${SERVICE_NAME}.${LOCAL_DOMAIN}"
ALIAS="${SERVICE_NAME}.local"
cat > "$SERVICE_DIR/docker-compose.yml" <<EOF
version: '3.8'
{
echo "$PRIMARY"
echo "$ALIAS"
for domain in "${EXTRA_DOMAINS[@]}"; do
echo "$domain"
done
} >> "$DOMAINS_FILE"
services:
$NAME:
image: nginx:alpine
container_name: $NAME
restart: unless-stopped
environment:
- VIRTUAL_HOST=$NAME.$LOCAL_DOMAIN,$NAME.$LOCAL_DOMAIN.local
$(if [ -n "$PUBLIC_DOMAIN" ]; then
echo " - LETSENCRYPT_HOST=$NAME.$PUBLIC_DOMAIN"
echo " - LETSENCRYPT_EMAIL=$LETSENCRYPT_EMAIL"
else
echo " # LETSENCRYPT_HOST="
echo " # LETSENCRYPT_EMAIL="
fi)
- VIRTUAL_PORT=$PORT
volumes:
- .//data
networks:
- devnet
depends_on:
- nginx-proxy
sort -u "$DOMAINS_FILE" | grep -v '^$' > "$DOMAINS_FILE.tmp" && mv "$DOMAINS_FILE.tmp" "$DOMAINS_FILE"
networks:
devnet:
external: true
EOF
echo "Сервис '$NAME' создан в $SERVICE_DIR"
echo "Запустите:"
echo " cd $PROJECT_ROOT"
echo " docker compose -f docker-compose.yml -f services/$NAME/docker-compose.yml up -d"
exec "$SCRIPT_DIR/regenerate-cert.sh"

View File

@@ -0,0 +1,32 @@
#!/bin/bash
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$SCRIPT_DIR/.."
if [ -f "$ROOT_DIR/.env" ]; then
export $(grep -v '^#' "$ROOT_DIR/.env" | xargs)
fi
DOMAINS_FILE="$ROOT_DIR/domains.txt"
CERTS_DIR="$ROOT_DIR/nginx/certs"
if [ ! -f "$DOMAINS_FILE" ]; then
echo "Error: $DOMAINS_FILE not found" >&2
exit 1
fi
mapfile -t DOMAINS < <(grep -v '^$' "$DOMAINS_FILE")
if [ ${#DOMAINS[@]} -eq 0 ]; then
echo "Error: no domains found in $DOMAINS_FILE" >&2
exit 1
fi
mkcert \
-cert-file "$CERTS_DIR/default.crt.new" \
-key-file "$CERTS_DIR/default.key.new" \
"${DOMAINS[@]}"
mv "$CERTS_DIR/default.crt.new" "$CERTS_DIR/default.crt"
mv "$CERTS_DIR/default.key.new" "$CERTS_DIR/default.key"

11
scripts/reload-nginx.sh Normal file
View File

@@ -0,0 +1,11 @@
#!/bin/bash
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$SCRIPT_DIR/.."
CERTS_DIR="$ROOT_DIR/nginx/certs"
cp "$CERTS_DIR/default.crt" "$CERTS_DIR/default.key" "$CERTS_DIR/"
docker compose -f "$ROOT_DIR/docker-compose.yml" exec nginx-proxy nginx -s reload 2>/dev/null || \
docker compose -f "$ROOT_DIR/docker-compose.yml" restart nginx-proxy