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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,5 @@ tests/verdict/
tests/reports/
tests/logs/
coverage-reports/
coverage
coverage
API_DOCUMENTATION.md
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

<div align="center">

![Logo](./assets/dyno.png)
![Version](https://img.shields.io/badge/version-0.0.1-blue.svg)
![License](https://img.shields.io/badge/license-MIT-green.svg)
![Go Version](https://img.shields.io/badge/go-1.24-00ADD8.svg)
Expand All @@ -16,7 +17,7 @@ Lightweight dynamic DNS service that lets you manage your DNS records without ma
</div>

---

## 📖 Overview

Dyno is a self-hosted dynamic DNS service that eliminates the manual overhead of managing DNS records. Bring your own domain and Cloudflare tokens, and let Dyno handle the rest. Perfect for home labs, development environments, and self-hosted infrastructure.
Expand Down Expand Up @@ -63,7 +64,7 @@ Dyno is a self-hosted dynamic DNS service that eliminates the manual overhead of

1. **Pull the Docker image**:
```bash
docker pull ghcr.io/rndmcodeguy20/dyno:latest
docker pull ghcr.io/rndmcodeguy20/dyno:production
```

2. **Create a `.env` file**:
Expand Down
Binary file added assets/dyno.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func main() {
}
}(baseLogger)

baseLogger.Sugar().Infof("Starting %s srv on https://%s:%d in %s mode", "Dyno", cfg.Server.Host, cfg.Server.Port, cfg.Environment)
baseLogger.Sugar().Infof("Starting %s server on https://%s:%d in %s mode", "Dyno", cfg.Server.Host, cfg.Server.Port, cfg.Environment)

db, err := database.NewPostgresDB(cfg.DB)
if err != nil {
Expand Down
5 changes: 2 additions & 3 deletions db/migrations/001_seed.sql
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,15 @@ CREATE TABLE IF NOT EXISTS users (

CREATE TABLE IF NOT EXISTS domain_records (
id SERIAL PRIMARY KEY,
domain_name VARCHAR(255) NOT NULL,
domain_name VARCHAR(255) NOT NULL UNIQUE,
current_ip_v4 INET NOT NULL,
current_ip_v6 INET,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
dns_record_id_v4 VARCHAR(32) UNIQUE NULL,
dns_record_id_v6 VARCHAR(32) UNIQUE NULL,
provider provider NOT NULL DEFAULT 'cloudflare',
user_id INT REFERENCES users(id) ON DELETE CASCADE,
UNIQUE (domain_name, user_id)
user_id INT REFERENCES users(id) ON DELETE CASCADE
);

-- Add trigger to auto-update updated_at timestamp
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
--+ migrate up
-- ALTER TABLE domain_records
-- DROP CONSTRAINT IF EXISTS domain_records_domain_name_user_id_key;
-- ALTER TABLE domain_records
-- ADD CONSTRAINT unique_domains UNIQUE (domain_name);
1 change: 1 addition & 0 deletions dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ ARG VERSION
ARG COMMIT_HASH
ARG ENV
ARG BUILD_TIME
ARG HOST

# Build static binary
RUN CGO_ENABLED=0 \
Expand Down
15 changes: 7 additions & 8 deletions internal/models/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,17 @@ package models

// CreateDomainRequest represents the payload to create a new domain
type CreateDomainRequest struct {
UserID string `json:"user_id" binding:"required"`
DomainName string `json:"domain_name" binding:"required"`
IPV4 string `json:"ip_v4,omitempty" binding:"required"`
IPV6 string `json:"ip_v6,omitempty"`
UserID string `json:"userID" binding:"required"`
DomainName string `json:"domainName" binding:"required"`
IPV4 string `json:"IPV4,omitempty" binding:"required"`
IPV6 string `json:"IPV6,omitempty"`
}

// UpdateDomainRequest represents the payload to update an existing domain
type UpdateDomainRequest struct {
UserID string `json:"user_id" binding:"required"`
DomainName string `json:"domain_name"`
NewIPV4 string `json:"new_ip_v4,omitempty" binding:"required"`
NewIPV6 string `json:"new_ip_v6,omitempty"`
UserID string `json:"userID" binding:"required"`
NewIPV4 string `json:"newIPV4,omitempty" binding:"required"`
NewIPV6 string `json:"newIPV6,omitempty"`
}

// DeleteDomainRequest represents the payload to delete a domain
Expand Down
7 changes: 4 additions & 3 deletions internal/service/domain.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,9 @@ func (s *domainService) CreateDomainRecord(ctx context.Context, body models.Crea
},
})
if err != nil {

s.logger.Error("Failed to create A record in Cloudflare", zap.Error(err))
return "", err
return "", errors.NewBadRequestError("Failed to create A record in Cloudflare", err)
}
dnsRecordIds = append(dnsRecordIds, response.ID)
}
Expand Down Expand Up @@ -285,7 +286,7 @@ func (s *domainService) DeleteDomainRecord(ctx context.Context, domainId string,
zap.String("dns_record_id_v6", currentDomainRecord.DNSRecordIdV6.String),
)

if currentDomainRecord.CurrentIPV4.Valid {
if currentDomainRecord.DNSRecordIdV4.Valid {
_, err := s.client.DNS.Records.Delete(
ctx,
currentDomainRecord.DNSRecordIdV4.String,
Expand All @@ -299,7 +300,7 @@ func (s *domainService) DeleteDomainRecord(ctx context.Context, domainId string,
}
}

if currentDomainRecord.CurrentIPV6.Valid {
if currentDomainRecord.DNSRecordIdV6.Valid {
_, err := s.client.DNS.Records.Delete(
ctx,
currentDomainRecord.DNSRecordIdV6.String,
Expand Down
Loading