Make the CLI configurable #1

Closed
mokhtar wants to merge 11 commits from proper-config into master
9 changed files with 159 additions and 190 deletions
Showing only changes of commit 3a65254902 - Show all commits

View File

@ -4,25 +4,20 @@ WORKDIR /app
COPY . .
RUN go mod download
RUN go build -o /app/local-ip
RUN go build
FROM gcr.io/distroless/base-debian12:latest
WORKDIR /local-ip
COPY --from=build /app/local-ip /local-ip/local-ip
COPY --from=build /app/local-ip.sh /local-ip/local-ip.sh
COPY --from=build /app/http/static /local-ip/http/static
VOLUME /local-ip/.lego
# DNS
EXPOSE 53/udp
# HTTP
EXPOSE 80/tcp
# HTTPS
EXPOSE 443/tcp
# DNS HTTP HTTPS
EXPOSE 53/udp 80/tcp 443/tcp
USER root
# TODO: make these configurable too
CMD ["/local-ip/local-ip", "--domain", "local-ip.sh", "--email", "admin@local-ip.sh", "--nameservers", "ns1.local-ip.sh.,ns2.local-ip.sh."]
CMD ["/local-ip/local-ip.sh"]

View File

@ -2,29 +2,48 @@ package cmd
import (
"fmt"
"net/mail"
"net/url"
"strings"
"time"
"github.com/asaskevich/govalidator"
"github.com/go-acme/lego/v4/lego"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"local-ip.sh/certs"
"local-ip.sh/http"
"local-ip.sh/utils"
"local-ip.sh/xip"
)
var command = &cobra.Command{
Use: "local-ip.sh",
PreRun: func(cmd *cobra.Command, args []string) {
nameservers, err := cmd.Flags().GetString("nameservers")
if err != nil {
utils.Logger.Fatal().Err(err).Msg("Unexpected error")
}
viper.Set("NameServers", strings.Split(nameservers, ","))
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
viper.SetEnvPrefix("XIP")
viper.AutomaticEnv()
staging, err := cmd.Flags().GetBool("staging")
email := viper.GetString("Email")
_, err := mail.ParseAddress(email)
if err != nil {
utils.Logger.Fatal().Err(err).Msg("Unexpected error")
utils.Logger.Fatal().Err(err).Msg("Invalid email address")
}
domain := viper.GetString("Domain")
if !govalidator.IsDNSName(domain) {
utils.Logger.Fatal().Err(err).Msg("Invalid domain")
}
nameservers := strings.Split(viper.GetString("nameservers"), ",")
for _, ns := range nameservers {
if !govalidator.IsIPv4(ns) {
utils.Logger.Fatal().Err(err).Str("ns", ns).Msg("Invalid name server")
}
}
viper.Set("NameServers", nameservers)
staging := viper.GetBool("staging")
var caDir string
if staging {
caDir = lego.LEDirectoryStaging
@ -35,36 +54,32 @@ var command = &cobra.Command{
parsedCaDirUrl, _ := url.Parse(caDir)
caDirHostname := parsedCaDirUrl.Hostname()
email := viper.GetString("Email")
viper.Set("AccountFilePath", fmt.Sprintf("./.lego/accounts/%s/%s/account.json", caDirHostname, email))
viper.Set("KeyFilePath", fmt.Sprintf("./.lego/accounts/%s/%s/keys/%s.key", caDirHostname, email, email))
utils.InitConfig()
},
Run: func(cmd *cobra.Command, args []string) {
c := utils.GetConfig()
fmt.Printf("%#v\n", c)
fmt.Println(c)
// n := xip.NewXip()
n := xip.NewXip()
// go func() {
// account := certs.LoadAccount()
// certsClient := certs.NewCertsClient(n, account)
go func() {
// try to obtain certificates once the DNS server is accepting requests
account := certs.LoadAccount()
certsClient := certs.NewCertsClient(n, account)
// time.Sleep(5 * time.Second)
// certsClient.RequestCertificates()
time.Sleep(5 * time.Second)
certsClient.RequestCertificates()
// for {
// // try to renew certificate every day
// time.Sleep(24 * time.Hour)
// certsClient.RequestCertificates()
// }
// }()
for {
// afterwards, try to renew certificates once a day
time.Sleep(24 * time.Hour)
certsClient.RequestCertificates()
}
}()
// go http.ServeHttp()
go http.ServeHttp()
// n.StartServer()
n.StartServer()
},
}
@ -82,15 +97,13 @@ func Execute() {
viper.BindPFlag("staging", command.Flags().Lookup("staging"))
command.Flags().String("domain", "", "Root domain (required)")
command.MarkFlagRequired("domain")
viper.BindPFlag("domain", command.Flags().Lookup("domain"))
command.Flags().String("email", "", "ACME account email address (required)")
command.MarkFlagRequired("email")
viper.BindPFlag("email", command.Flags().Lookup("email"))
command.Flags().String("nameservers", "", "List of nameservers separated by commas")
command.MarkFlagRequired("nameservers")
command.Flags().String("nameservers", "", "List of nameservers separated by commas (required)")
viper.BindPFlag("nameservers", command.Flags().Lookup("nameservers"))
if err := command.Execute(); err != nil {
utils.Logger.Fatal().Err(err).Msg("")

View File

@ -1,14 +1,19 @@
services:
local-ip.sh:
image: local-ip.sh
build: .
volumes:
- lego:/local-ip/.lego
restart: unless-stopped
ports:
- 53:53/udp
- 80:80/tcp
- 443:443/tcp
local-ip.sh:
image: local-ip.sh
build: .
volumes:
- lego:/local-ip/.lego
restart: unless-stopped
environment:
XIP_DOMAIN: "local-ip.sh"
XIP_EMAIL: "admin@local-ip.sh"
XIP_NAMESERVERS: "137.66.40.11,137.66.40.12"
# XIP_STAGING: true
ports:
- 53:53/udp
- 80:80/tcp
- 443:443/tcp
volumes:
lego:
lego:

View File

@ -1,8 +1,3 @@
# fly.toml app configuration file generated for local-ip-ancient-glade-4376 on 2023-11-29T11:43:10+01:00
#
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
#
app = "local-ip"
primary_region = "ams"
kill_signal = "SIGINT"
@ -14,7 +9,9 @@ auto_rollback = true
[build]
[env]
PORT = "53"
XIP_DOMAIN = "local-ip.sh"
XIP_EMAIL = "admin@local-ip.sh"
XIP_NAMESERVERS = "137.66.40.11,137.66.40.12" # fly.io edge-only ip addresses, see https://community.fly.io/t/custom-domains-certificate-is-stuck-on-awaiting-configuration/8329
[mounts]
source = "lego"

5
go.mod
View File

@ -3,9 +3,12 @@ module local-ip.sh
go 1.22
require (
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
github.com/go-acme/lego/v4 v4.10.1
github.com/miekg/dns v1.1.57
github.com/rs/zerolog v1.33.0
github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.19.0
golang.org/x/net v0.23.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
)
@ -26,9 +29,7 @@ require (
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/cobra v1.8.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.19.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect

31
go.sum
View File

@ -1,11 +1,15 @@
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4=
github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/go-acme/lego/v4 v4.10.1 h1:MiJvoBXNdmAwEK/SImyhwZ8ZL4IR0jtWDD1wST+N138=
@ -13,12 +17,17 @@ github.com/go-acme/lego/v4 v4.10.1/go.mod h1:EMbf0Jmqwv94nJ5WL9qWnSXIBZnvsS9gNyp
github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
@ -34,9 +43,11 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
@ -65,8 +76,6 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
@ -78,8 +87,6 @@ go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
@ -87,12 +94,10 @@ golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqR
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -101,14 +106,14 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=

View File

@ -2,8 +2,11 @@ package http
import (
"context"
"fmt"
"net"
"net/http"
"os"
"strconv"
"strings"
"time"
@ -45,8 +48,9 @@ func registerHandlers() {
}
func serveHttp() *http.Server {
utils.Logger.Info().Msg("Starting up HTTP server on :80")
httpServer := &http.Server{Addr: ":http"}
config := utils.GetConfig()
httpServer := &http.Server{Addr: fmt.Sprintf(":%d", config.HttpPort)}
utils.Logger.Info().Str("http_address", httpServer.Addr).Msg("Starting up HTTP server")
go func() {
err := httpServer.ListenAndServe()
if err != http.ErrServerClosed {
@ -84,22 +88,40 @@ func killServer(httpServer *http.Server) {
}
func redirectHttpToHttps() {
utils.Logger.Info().Msg("Redirecting HTTP traffic from :80 to HTTPS :443")
config := utils.GetConfig()
httpServer := &http.Server{
Addr: ":http",
Addr: fmt.Sprintf(":%d", config.HttpPort),
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
url := r.URL
host := r.Host
// Strip the port from the host if present
if strings.Contains(host, ":") {
hostWithoutPort, _, err := net.SplitHostPort(host)
if err != nil {
utils.Logger.Error().Err(err).Msg("Failed to split host and port")
} else {
host = hostWithoutPort
}
}
// Add the HTTPS port only if it's not 443
if config.HttpsPort != 443 {
host = net.JoinHostPort(host, strconv.FormatUint(uint64(config.HttpsPort), 10))
}
url.Host = r.Host
url.Scheme = "https"
http.Redirect(w, r, url.String(), http.StatusMovedPermanently)
}),
}
utils.Logger.Info().Str("http_address", httpServer.Addr).Msg("Redirecting HTTP traffic to HTTPS")
go httpServer.ListenAndServe()
}
func serveHttps() {
utils.Logger.Info().Msg("Starting up HTTPS server on :443")
httpsServer := &http.Server{Addr: ":https"}
config := utils.GetConfig()
httpsServer := &http.Server{Addr: fmt.Sprintf(":%d", config.HttpsPort)}
utils.Logger.Info().Str("https_address", httpsServer.Addr).Msg("Starting up HTTPS server")
go httpsServer.ListenAndServeTLS("./.lego/certs/root/server.pem", "./.lego/certs/root/server.key")
}

View File

@ -1,11 +1,9 @@
package xip
import (
"fmt"
"net"
"github.com/miekg/dns"
"local-ip.sh/utils"
)
type hardcodedRecord struct {
@ -17,41 +15,8 @@ type hardcodedRecord struct {
SRV *dns.SRV
}
var config = utils.GetConfig()
var hardcodedRecords = map[string]hardcodedRecord{
// TODO: maybe --nameservers ns1.local-ip.sh.=137.66.40.11,ns2.local-ip.sh.=137.66.40.12
fmt.Sprintf("ns.%s.", config.Domain): {
// record holding ip addresses of ns1 and ns2
A: []net.IP{
net.IPv4(137, 66, 40, 11),
net.IPv4(137, 66, 40, 12),
},
},
fmt.Sprintf("ns1.%s.", config.Domain): {
A: []net.IP{
net.IPv4(137, 66, 40, 11), // fly.io edge-only ip address, see https://community.fly.io/t/custom-domains-certificate-is-stuck-on-awaiting-configuration/8329
},
},
fmt.Sprintf("ns2.%s.", config.Domain): {
A: []net.IP{
net.IPv4(137, 66, 40, 12), // fly.io edge-only ip address #2
},
},
fmt.Sprintf("%s.", config.Domain): {
// same as ns.local-ip.sh, it's the same machine :)
A: []net.IP{
net.IPv4(137, 66, 40, 11),
net.IPv4(137, 66, 40, 12),
},
},
fmt.Sprintf("_acme-challenge.%s.", config.Domain): {
// will be filled in later when requesting the wildcard certificate
TXT: []string{},
},
}
// additional records I set up to host emails, feel free to change or remove them for your own needs
var extraRecords = map[string]hardcodedRecord{
var records = map[string]hardcodedRecord{
// additional records I set up to host emails, feel free to change or remove them for your own needs
"local-ip.sh.": {
TXT: []string{"v=spf1 include:capsulecorp.dev ~all"},
MX: []*dns.MX{
@ -85,69 +50,3 @@ var extraRecords = map[string]hardcodedRecord{
},
},
}
var records = mergeRecords(hardcodedRecords, extraRecords)
func mergeRecords(a, b map[string]hardcodedRecord) map[string]hardcodedRecord {
result := make(map[string]hardcodedRecord)
for k, v := range a {
result[k] = v
}
for k, v := range b {
if r, ok := result[k]; ok {
result[k] = hardcodedRecord{
A: uniqueIPs(append(r.A, v.A...)),
AAAA: uniqueIPs(append(r.AAAA, v.AAAA...)),
TXT: uniqueStrings(append(r.TXT, v.TXT...)),
MX: uniqueMX(append(r.MX, v.MX...)),
CNAME: uniqueStrings(append(r.CNAME, v.CNAME...)),
SRV: firstNonNil(r.SRV, v.SRV),
}
} else {
result[k] = v
}
}
return result
}
func uniqueIPs(ips []net.IP) []net.IP {
seen := make(map[string]bool)
result := []net.IP{}
for _, ip := range ips {
if !seen[ip.String()] {
seen[ip.String()] = true
result = append(result, ip)
}
}
return result
}
func uniqueStrings(strs []string) []string {
seen := make(map[string]bool)
result := []string{}
for _, str := range strs {
if !seen[str] {
seen[str] = true
result = append(result, str)
}
}
return result
}
func uniqueMX(mxs []*dns.MX) []*dns.MX {
seen := make(map[string]uint16)
result := []*dns.MX{}
for _, mx := range mxs {
if pref, exists := seen[mx.Mx]; !exists || pref > mx.Preference {
seen[mx.Mx] = mx.Preference
result = append(result, mx)
}
}
return result
}
func firstNonNil[T any](a, b *T) *T {
if a != nil {
return a
}
return b
}

View File

@ -14,7 +14,7 @@ import (
type Xip struct {
server dns.Server
nameServers []*dns.NS
nameServers []string
}
var (
@ -25,6 +25,7 @@ var (
func (xip *Xip) SetTXTRecord(fqdn string, value string) {
utils.Logger.Debug().Str("fqdn", fqdn).Str("value", value).Msg("Trying to set TXT record")
config := utils.GetConfig()
if fqdn != fmt.Sprintf("_acme-challenge.%s.", config.Domain) {
utils.Logger.Debug().Msg("Not allowed, abort")
return
@ -38,6 +39,7 @@ func (xip *Xip) SetTXTRecord(fqdn string, value string) {
func (xip *Xip) UnsetTXTRecord(fqdn string) {
utils.Logger.Debug().Str("fqdn", fqdn).Msg("Trying to set TXT record")
config := utils.GetConfig()
if fqdn != fmt.Sprintf("_acme-challenge.%s.", config.Domain) {
utils.Logger.Debug().Msg("Not allowed, abort")
return
@ -145,10 +147,10 @@ func (xip *Xip) handleNS(question dns.Question, message *dns.Msg) {
Rrtype: dns.TypeNS,
Class: dns.ClassINET,
},
Ns: ns.Ns,
Ns: ns,
})
additionals = append(additionals, xip.fqdnToA(ns.Ns)...)
additionals = append(additionals, xip.fqdnToA(ns)...)
}
for _, record := range nameServers {
@ -257,7 +259,15 @@ func (xip *Xip) handleSOA(question dns.Question, message *dns.Msg) {
message.Answer = append(message.Answer, xip.soaRecord(question))
}
func emailToRname(email string) string {
parts := strings.SplitN(email, "@", 2)
localPart := strings.ReplaceAll(parts[0], ".", "\\.")
domain := parts[1]
return localPart + "." + domain + "."
}
func (xip *Xip) soaRecord(question dns.Question) *dns.SOA {
config := utils.GetConfig()
soa := new(dns.SOA)
soa.Hdr = dns.RR_Header{
Name: question.Name,
@ -266,8 +276,8 @@ func (xip *Xip) soaRecord(question dns.Question) *dns.SOA {
Ttl: uint32((time.Minute * 5).Seconds()),
Rdlength: 0,
}
soa.Ns = xip.nameServers[0].Ns
soa.Mbox = config.Email
soa.Ns = xip.nameServers[0]
soa.Mbox = emailToRname(config.Email)
soa.Serial = 2024072200
soa.Refresh = uint32((time.Minute * 15).Seconds())
soa.Retry = uint32((time.Minute * 15).Seconds())
@ -321,6 +331,7 @@ func (xip *Xip) handleDnsRequest(response dns.ResponseWriter, request *dns.Msg)
error := response.WriteMsg(message)
if error != nil {
utils.Logger.Debug().Msg(message.String())
utils.Logger.Error().Err(error).Str("message", message.String()).Msg("Error responding to query")
}
}()
@ -335,6 +346,7 @@ func (xip *Xip) StartServer() {
err := xip.server.ListenAndServe()
defer xip.server.Shutdown()
if err != nil {
utils.Logger.Fatal().Err(err).Msg("Failed to start DNS server")
if strings.Contains(err.Error(), "fly-global-services: no such host") {
// we're not running on fly, bind to 0.0.0.0 instead
port := strings.Split(xip.server.Addr, ":")[1]
@ -349,16 +361,36 @@ func (xip *Xip) StartServer() {
utils.Logger.Fatal().Err(err).Msg("Failed to start DNS server")
}
utils.Logger.Info().Str("dns_address", xip.server.Addr).Msg("DNS server listening")
utils.Logger.Info().Str("dns_address", xip.server.Addr).Msg("Starting up DNS server")
}
func (xip *Xip) initHardcodedRecords() {
config := utils.GetConfig()
rootDomainARecords := []net.IP{}
for i, ns := range config.NameServers {
name := fmt.Sprintf("ns%d.%s.", i+1, config.Domain)
ip := net.ParseIP(ns)
rootDomainARecords = append(rootDomainARecords, ip)
entry := records[name]
entry.A = append(records[name].A, ip)
records[name] = entry
xip.nameServers = append(xip.nameServers, name)
}
records[fmt.Sprintf("%s.", config.Domain)] = hardcodedRecord{A: rootDomainARecords}
// will be filled in later when requesting the wildcard certificate
records[fmt.Sprintf("_acme-challenge.%s.", config.Domain)] = hardcodedRecord{TXT: []string{}}
}
func NewXip() (xip *Xip) {
config := utils.GetConfig()
xip = &Xip{}
for _, ns := range config.NameServers {
xip.nameServers = append(xip.nameServers, &dns.NS{Ns: ns})
}
xip.initHardcodedRecords()
xip.server = dns.Server{
Addr: fmt.Sprintf(":%d", config.DnsPort),