https server
This commit is contained in:
parent
ccaef7827c
commit
92cdddec9c
@ -10,12 +10,13 @@ FROM gcr.io/distroless/base-debian12:latest
|
|||||||
|
|
||||||
ENV PORT 53
|
ENV PORT 53
|
||||||
|
|
||||||
WORKDIR /
|
WORKDIR /app
|
||||||
|
|
||||||
COPY --from=build /app/local-ip /
|
COPY --from=build /app/local-ip /app/local-ip
|
||||||
COPY ./.lego /.lego
|
COPY --from=build /app/http/static /app/http/static
|
||||||
|
COPY ./.lego /app/.lego
|
||||||
|
|
||||||
EXPOSE $PORT
|
EXPOSE $PORT
|
||||||
USER root
|
USER root
|
||||||
|
|
||||||
CMD ["/local-ip"]
|
CMD ["/app/local-ip"]
|
||||||
|
@ -39,7 +39,7 @@ as a proxy to access the files securely.
|
|||||||
|
|
||||||
## Self-hosting
|
## Self-hosting
|
||||||
|
|
||||||
I'm currently hosting [local-ip.sh](https://www.local-ip.sh) at [Fly.io](https://fly.io) but you can host the service yourself
|
I'm currently hosting [local-ip.sh](https://local-ip.sh) at [Fly.io](https://fly.io) but you can host the service yourself
|
||||||
if you're into that kind of thing. Note that you will need to edit your domain's glue records so make sure your registrar allows it.
|
if you're into that kind of thing. Note that you will need to edit your domain's glue records so make sure your registrar allows it.
|
||||||
|
|
||||||
You will essentially need to:
|
You will essentially need to:
|
||||||
|
@ -2,6 +2,7 @@ package certs
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
@ -16,13 +17,31 @@ import (
|
|||||||
|
|
||||||
type certsClient struct {
|
type certsClient struct {
|
||||||
legoClient *lego.Client
|
legoClient *lego.Client
|
||||||
lastCertificate *certificate.Resource
|
lastWildcardCertificate *certificate.Resource
|
||||||
|
lastRootCertificate *certificate.Resource
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *certsClient) RequestCertificate() {
|
func (c *certsClient) RequestCertificates() {
|
||||||
log.Println("requesting a certificate")
|
c.requestCertificate("wildcard")
|
||||||
if c.lastCertificate != nil {
|
c.requestCertificate("root")
|
||||||
certificates, err := certcrypto.ParsePEMBundle(c.lastCertificate.Certificate)
|
}
|
||||||
|
|
||||||
|
func (c *certsClient) requestCertificate(certType string) {
|
||||||
|
var lastCertificate *certificate.Resource
|
||||||
|
var domains []string
|
||||||
|
if certType == "wildcard" {
|
||||||
|
lastCertificate = c.lastWildcardCertificate
|
||||||
|
domains = []string{"*.local-ip.sh"}
|
||||||
|
} else if certType == "root" {
|
||||||
|
lastCertificate = c.lastRootCertificate
|
||||||
|
domains = []string{"local-ip.sh"}
|
||||||
|
} else {
|
||||||
|
log.Fatalf("Unexpected certType %s. Only \"wildcard\" and \"root\" are supported", certType)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("requesting %s certificate\n", certType)
|
||||||
|
if lastCertificate != nil {
|
||||||
|
certificates, err := certcrypto.ParsePEMBundle(c.lastWildcardCertificate.Certificate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -34,45 +53,55 @@ func (c *certsClient) RequestCertificate() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.renewCertificate()
|
c.renewCertificates()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
certificates, err := c.legoClient.Certificate.Obtain(certificate.ObtainRequest{
|
certificates, err := c.legoClient.Certificate.Obtain(certificate.ObtainRequest{
|
||||||
Domains: []string{"*.local-ip.sh"},
|
Domains: domains,
|
||||||
Bundle: true,
|
Bundle: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.lastCertificate = certificates
|
if certType == "wildcard" {
|
||||||
|
c.lastWildcardCertificate = certificates
|
||||||
|
} else if certType == "root" {
|
||||||
|
c.lastRootCertificate = certificates
|
||||||
|
}
|
||||||
|
|
||||||
|
persistFiles(certificates, certType)
|
||||||
|
|
||||||
persistFiles(certificates)
|
|
||||||
log.Printf("%#v\n", certificates)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *certsClient) renewCertificate() {
|
func (c *certsClient) renewCertificates() {
|
||||||
log.Println("renewing currently existing certificate")
|
log.Println("renewing currently existing certificate")
|
||||||
certificates, err := c.legoClient.Certificate.Renew(*c.lastCertificate, true, false, "")
|
wildcardCertificate, err := c.legoClient.Certificate.Renew(*c.lastWildcardCertificate, true, false, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
c.lastWildcardCertificate = wildcardCertificate
|
||||||
|
persistFiles(wildcardCertificate, "wildcard")
|
||||||
|
|
||||||
c.lastCertificate = certificates
|
rootCertificate, err := c.legoClient.Certificate.Renew(*c.lastRootCertificate, true, false, "")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
c.lastRootCertificate = rootCertificate
|
||||||
|
persistFiles(rootCertificate, "root")
|
||||||
|
|
||||||
persistFiles(certificates)
|
|
||||||
log.Printf("%#v\n", certificates)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func persistFiles(certificates *certificate.Resource) {
|
func persistFiles(certificates *certificate.Resource, certType string) {
|
||||||
os.WriteFile("/certs/server.pem", certificates.Certificate, 0o644)
|
os.MkdirAll(fmt.Sprintf("/certs/%s", certType), 0o755)
|
||||||
os.WriteFile("/certs/server.key", certificates.PrivateKey, 0o644)
|
os.WriteFile(fmt.Sprintf("/certs/%s/server.pem", certType), certificates.Certificate, 0o644)
|
||||||
|
os.WriteFile(fmt.Sprintf("/certs/%s/server.key", certType), certificates.PrivateKey, 0o644)
|
||||||
jsonBytes, err := json.MarshalIndent(certificates, "", "\t")
|
jsonBytes, err := json.MarshalIndent(certificates, "", "\t")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
os.WriteFile("/certs/output.json", jsonBytes, 0o644)
|
os.WriteFile(fmt.Sprintf("/certs/%s/output.json", certType), jsonBytes, 0o644)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCertsClient(xip *xip.Xip, user *Account) *certsClient {
|
func NewCertsClient(xip *xip.Xip, user *Account) *certsClient {
|
||||||
@ -86,16 +115,18 @@ func NewCertsClient(xip *xip.Xip, user *Account) *certsClient {
|
|||||||
provider := newProviderLocalIp(xip)
|
provider := newProviderLocalIp(xip)
|
||||||
legoClient.Challenge.SetDNS01Provider(provider, dns01.AddRecursiveNameservers([]string{"1.1.1.1:53", "8.8.8.8:53"}), dns01.DisableCompletePropagationRequirement())
|
legoClient.Challenge.SetDNS01Provider(provider, dns01.AddRecursiveNameservers([]string{"1.1.1.1:53", "8.8.8.8:53"}), dns01.DisableCompletePropagationRequirement())
|
||||||
|
|
||||||
lastCertificate := getLastCertificate(legoClient)
|
lastWildcardCertificate := getLastCertificate(legoClient, "wildcard")
|
||||||
|
lastRootCertificate := getLastCertificate(legoClient, "root")
|
||||||
|
|
||||||
return &certsClient{
|
return &certsClient{
|
||||||
legoClient,
|
legoClient,
|
||||||
lastCertificate,
|
lastWildcardCertificate,
|
||||||
|
lastRootCertificate,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLastCertificate(legoClient *lego.Client) *certificate.Resource {
|
func getLastCertificate(legoClient *lego.Client, certType string) *certificate.Resource {
|
||||||
jsonBytes, err := os.ReadFile("/certs/output.json")
|
jsonBytes, err := os.ReadFile(fmt.Sprintf("/certs/%s/output.json", certType))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "no such file or directory") {
|
if strings.Contains(err.Error(), "no such file or directory") {
|
||||||
return nil
|
return nil
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
const (
|
const (
|
||||||
email = "admin@local-ip.sh"
|
email = "admin@local-ip.sh"
|
||||||
caDirUrl = lego.LEDirectoryProduction
|
caDirUrl = lego.LEDirectoryProduction
|
||||||
|
// caDirUrl = lego.LEDirectoryStaging
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
34
fly.toml
34
fly.toml
@ -20,14 +20,6 @@ PORT = "53"
|
|||||||
source = "certs"
|
source = "certs"
|
||||||
destination = "/certs"
|
destination = "/certs"
|
||||||
|
|
||||||
[http_service]
|
|
||||||
internal_port = 53
|
|
||||||
force_https = true
|
|
||||||
auto_stop_machines = false
|
|
||||||
auto_start_machines = true
|
|
||||||
min_machines_running = 1
|
|
||||||
processes = ["app"]
|
|
||||||
|
|
||||||
[[services]]
|
[[services]]
|
||||||
protocol = "udp"
|
protocol = "udp"
|
||||||
internal_port = 53
|
internal_port = 53
|
||||||
@ -38,7 +30,33 @@ min_machines_running = 0
|
|||||||
[[services.ports]]
|
[[services.ports]]
|
||||||
port = 53
|
port = 53
|
||||||
|
|
||||||
|
[[services]]
|
||||||
|
protocol = "tcp"
|
||||||
|
internal_port = 80
|
||||||
|
|
||||||
|
[[services.ports]]
|
||||||
|
port = 80
|
||||||
|
|
||||||
|
[[services]]
|
||||||
|
protocol = "tcp"
|
||||||
|
internal_port = 443
|
||||||
|
|
||||||
|
[[services.ports]]
|
||||||
|
port = 443
|
||||||
|
|
||||||
|
# [[services.http_checks]]
|
||||||
|
# interval = 10000
|
||||||
|
# grace_period = "30s"
|
||||||
|
# method = "get"
|
||||||
|
# path = "/"
|
||||||
|
# protocol = "https"
|
||||||
|
# timeout = 15000
|
||||||
|
# tls_skip_verify = false
|
||||||
|
# tls_server_name = "local-ip.sh"
|
||||||
|
# [services.http_checks.headers]
|
||||||
|
|
||||||
[[vm]]
|
[[vm]]
|
||||||
|
size = "shared-cpu-1x"
|
||||||
cpu_kind = "shared"
|
cpu_kind = "shared"
|
||||||
cpus = 1
|
cpus = 1
|
||||||
memory_mb = 256
|
memory_mb = 256
|
||||||
|
@ -3,17 +3,61 @@ package http
|
|||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ServeCertificate() {
|
func ServeHttp() {
|
||||||
|
waitForCertificate()
|
||||||
|
|
||||||
|
go http.ListenAndServe(":80", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
url := r.URL
|
||||||
|
url.Host = r.Host
|
||||||
|
url.Scheme = "https"
|
||||||
|
http.Redirect(w, r, url.String(), http.StatusMovedPermanently)
|
||||||
|
}))
|
||||||
|
|
||||||
http.HandleFunc("/server.key", func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc("/server.key", func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/octet-stream")
|
w.Header().Set("Content-Type", "application/octet-stream")
|
||||||
http.ServeFile(w, r, "/certs/server.key")
|
http.ServeFile(w, r, "/certs/wildcard/server.key")
|
||||||
})
|
})
|
||||||
http.HandleFunc("/server.pem", func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc("/server.pem", func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/x-x509-ca-cert")
|
w.Header().Set("Content-Type", "application/x-x509-ca-cert")
|
||||||
http.ServeFile(w, r, "/certs/server.pem")
|
http.ServeFile(w, r, "/certs/wildcard/server.pem")
|
||||||
})
|
})
|
||||||
log.Printf("Serving cert files on :9229\n")
|
http.HandleFunc("/og.png", func(w http.ResponseWriter, r *http.Request) {
|
||||||
http.ListenAndServe(":9229", nil)
|
w.Header().Set("Content-Type", "image/png; charset=utf-8")
|
||||||
|
w.Header().Set("Cache-Control", "public, max-age=3600")
|
||||||
|
http.ServeFile(w, r, "./http/static/og.png")
|
||||||
|
})
|
||||||
|
http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "image/x-icon; charset=utf-8")
|
||||||
|
w.Header().Set("Cache-Control", "public, max-age=3600")
|
||||||
|
http.ServeFile(w, r, "./http/static/favicon.ico")
|
||||||
|
})
|
||||||
|
http.HandleFunc("/styles.css", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "text/css; charset=utf-8")
|
||||||
|
http.ServeFile(w, r, "./http/static/styles.css")
|
||||||
|
})
|
||||||
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
http.ServeFile(w, r, "./http/static/index.html")
|
||||||
|
})
|
||||||
|
log.Printf("Serving HTTPS server on :443\n")
|
||||||
|
http.ListenAndServeTLS(":443", "/certs/root/server.pem", "/certs/root/server.key", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitForCertificate() {
|
||||||
|
for {
|
||||||
|
_, err := os.Stat("/certs/root/output.json")
|
||||||
|
if err != nil {
|
||||||
|
if strings.Contains(err.Error(), "no such file or directory") {
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
BIN
http/static/favicon.ico
Normal file
BIN
http/static/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.2 KiB |
89
http/static/index.html
Normal file
89
http/static/index.html
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charSet="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1" />
|
||||||
|
<title>local-ip.sh</title>
|
||||||
|
<meta name="description" content="local-ip.sh is a magic domain name that provides wildcard DNS for any IP address." />
|
||||||
|
<meta name="author" content="Mokhtar Mial" />
|
||||||
|
<meta name="robots" content="index,follow" />
|
||||||
|
<meta name="googlebot" content="index,follow" />
|
||||||
|
<meta property="twitter:title" content="local-ip.sh" />
|
||||||
|
<meta property="twitter:description" content="local-ip.sh is a magic domain name that provides wildcard DNS for any IP address." />
|
||||||
|
<meta property="twitter:card" content="summary_large_image" />
|
||||||
|
<meta property="twitter:site" content="https://local-ip.sh/" />
|
||||||
|
<meta property="twitter:image" content="https://local-ip.sh/og.png" />
|
||||||
|
<meta property="twitter:image:alt" content="og image" />
|
||||||
|
<meta property="og:title" content="local-ip.sh" />
|
||||||
|
<meta property="og:description" content="local-ip.sh is a magic domain name that provides wildcard DNS for any IP address." />
|
||||||
|
<meta property="og:url" content="https://local-ip.sh/" />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:image" content="https://local-ip.sh/og.png" />
|
||||||
|
<meta property="og:image:alt" content="og image" />
|
||||||
|
<link rel="stylesheet" href="/styles.css" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<pre>
|
||||||
|
_ _ _ _
|
||||||
|
| | | | (_) | |
|
||||||
|
| | ___ ___ __ _| | _ _ __ ___| |__
|
||||||
|
| |/ _ \ / __/ _` | |_____| | '_ \ / __| '_ \
|
||||||
|
| | (_) | (_| (_| | |_____| | |_) |\__ \ | | |
|
||||||
|
|_|\___/ \___\__,_|_| |_| .__(_)___/_| |_|
|
||||||
|
| |
|
||||||
|
|_|
|
||||||
|
</pre>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<section>
|
||||||
|
<header><strong>What is local-ip.sh?</strong></header>
|
||||||
|
<main>
|
||||||
|
<article>local-ip.sh is a magic domain name that provides wildcard DNS for any IP address. It is heavily
|
||||||
|
inspired by <a href="http://local-ip.co">local-ip.co</a>, <a
|
||||||
|
href="https://sslip.io">sslip.io</a>, and <a href="https://xip.io">xip.io</a>.</article>
|
||||||
|
<article>Quick example, say your LAN IP address is <strong>192.168.1.10</strong>. Using
|
||||||
|
local-ip.sh,<br /><br />
|
||||||
|
<pre> <strong>192.168.1.10</strong>.local-ip.sh resolves to 192.168.1.10
|
||||||
|
dots.<strong>192.168.1.10</strong>.local-ip.sh resolves to 192.168.1.10
|
||||||
|
dashes.<strong>192-168-1-10</strong>.local-ip.sh resolves to 192.168.1.10</pre>
|
||||||
|
</article>
|
||||||
|
<article>...and so on. You can use these domains to access virtual hosts on your development web server
|
||||||
|
from devices on your local network. No configuration required!</article>
|
||||||
|
<article>The best part is, you can serve your content over HTTPS with our TLS certificate for
|
||||||
|
<code>*.local-ip.sh</code>:<ul>
|
||||||
|
<li><a href="/server.pem">server.pem</a></li>
|
||||||
|
<li><a href="/server.key">server.key</a></li>
|
||||||
|
</ul>Be aware that wildcard certificates are not recursive, meaning they don't match
|
||||||
|
"sub-subdomains". <br />In our case, this certificate will only match subdomains of
|
||||||
|
<code>local-ip.sh</code> such as <code>192-168-1-10.local-ip.sh</code> where dashes separate
|
||||||
|
the numbers that make up the IP address.</article>
|
||||||
|
</main>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<header><strong>How does it work?</strong></header>
|
||||||
|
<main>
|
||||||
|
<article>
|
||||||
|
local-ip.sh runs publicly a <a href="https://git.capsulecorp.dev/mokhtar/local-ip.sh">custom DNS server</a>.
|
||||||
|
When your computer looks up a local-ip.sh domain, the local-ip.sh DNS server resolves to the IP address it extracts from the domain.
|
||||||
|
</article>
|
||||||
|
<article>
|
||||||
|
The TLS certificate is obtained from Let's Encrypt and renewed up to a month before it expires.
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
<footer class="copyright">© 2024 <a href="https://www.mokhtar.dev">Mokhtar Mial</a></footer>
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
window.counterscale = {
|
||||||
|
q: [["set", "siteId", "local-ip"], ["trackPageview"]],
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
<script id="counterscale-script" src="https://counterscale.m5r.workers.dev/tracker.js" defer=""></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
BIN
http/static/og.png
Normal file
BIN
http/static/og.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
107
http/static/styles.css
Normal file
107
http/static/styles.css
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
html {
|
||||||
|
background: #111;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
color: #728ea7;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-inline: auto;
|
||||||
|
padding-left: 1.5em;
|
||||||
|
padding-right: 1.5em;
|
||||||
|
width: min(100%, 41.5rem);
|
||||||
|
margin-top: 50px;
|
||||||
|
margin-bottom: 50px;
|
||||||
|
|
||||||
|
font-family: ui-monospace, monospace;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
color: #7aa6da;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
header > pre {
|
||||||
|
margin: 1rem auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
main a {
|
||||||
|
color: #728ea7;
|
||||||
|
}
|
||||||
|
|
||||||
|
main a:hover {
|
||||||
|
background: #7aa6da;
|
||||||
|
color: #111;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
section:nth-child(n + 2) {
|
||||||
|
margin-top: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
section > main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
row-gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
padding: 0.2em 0.4em;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 85%;
|
||||||
|
background-color: rgba(27, 31, 35, 0.95);
|
||||||
|
border-radius: 3px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
header span.dim {
|
||||||
|
color: #728ea7;
|
||||||
|
}
|
||||||
|
|
||||||
|
section strong {
|
||||||
|
color: #7aa6da;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
margin: 5rem auto 0;
|
||||||
|
color: #556a7d;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a:hover {
|
||||||
|
color: #728ea7;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.cursor {
|
||||||
|
display: inline-block;
|
||||||
|
background: #111;
|
||||||
|
margin-left: 1px;
|
||||||
|
margin-right: -1px;
|
||||||
|
animation: blink 2s linear 0s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes blink {
|
||||||
|
0% {
|
||||||
|
background: #7aa6da;
|
||||||
|
}
|
||||||
|
47% {
|
||||||
|
background: #728ea7;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
background: #111;
|
||||||
|
}
|
||||||
|
97% {
|
||||||
|
background: #111;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background: #728ea7;
|
||||||
|
}
|
||||||
|
}
|
6
main.go
6
main.go
@ -20,16 +20,16 @@ func main() {
|
|||||||
certsClient := certs.NewCertsClient(n, account)
|
certsClient := certs.NewCertsClient(n, account)
|
||||||
|
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
certsClient.RequestCertificate()
|
certsClient.RequestCertificates()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
// try to renew certificate every day
|
// try to renew certificate every day
|
||||||
time.Sleep(24 * time.Hour)
|
time.Sleep(24 * time.Hour)
|
||||||
certsClient.RequestCertificate()
|
certsClient.RequestCertificates()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
go http.ServeCertificate()
|
go http.ServeHttp()
|
||||||
|
|
||||||
n.StartServer()
|
n.StartServer()
|
||||||
}
|
}
|
||||||
|
49
xip/xip.go
49
xip/xip.go
@ -23,6 +23,7 @@ type HardcodedRecord struct {
|
|||||||
TXT *dns.TXT
|
TXT *dns.TXT
|
||||||
MX []*dns.MX
|
MX []*dns.MX
|
||||||
CNAME []*dns.CNAME
|
CNAME []*dns.CNAME
|
||||||
|
SRV *dns.SRV
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -54,47 +55,45 @@ var (
|
|||||||
},
|
},
|
||||||
"local-ip.sh.": {
|
"local-ip.sh.": {
|
||||||
A: []*dns.A{
|
A: []*dns.A{
|
||||||
{A: net.IPv4(66, 241, 125, 48)},
|
// {A: net.IPv4(66, 241, 125, 48)},
|
||||||
},
|
{A: net.IPv4(137, 66, 40, 11)}, // fly.io edge-only ip address
|
||||||
AAAA: []*dns.AAAA{
|
|
||||||
{AAAA: net.IP{0x2a, 0x09, 0x82, 0x80, 0, 0x01, 0, 0, 0, 0, 0, 0, 0, 0x1C, 0xC1, 0xC1}},
|
|
||||||
},
|
},
|
||||||
TXT: &dns.TXT{
|
TXT: &dns.TXT{
|
||||||
Txt: []string{
|
Txt: []string{
|
||||||
"sl-verification=frudknyqpqlpgzbglkqnsmorfcvxrf",
|
"sl-verification=frudknyqpqlpgzbglkqnsmorfcvxrf",
|
||||||
"v=spf1 include:simplelogin.co ~all",
|
"v=spf1 include:capsulecorp.dev ~all",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
MX: []*dns.MX{
|
MX: []*dns.MX{
|
||||||
{Preference: 10, Mx: "mx1.simplelogin.co."},
|
{Preference: 10, Mx: "email.capsulecorp.dev."},
|
||||||
{Preference: 20, Mx: "mx2.simplelogin.co."},
|
},
|
||||||
|
},
|
||||||
|
"autodiscover.local-ip.sh.": {
|
||||||
|
CNAME: []*dns.CNAME{
|
||||||
|
{Target: "email.capsulecorp.dev"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"_autodiscover._tcp.local-ip.sh.": {
|
||||||
|
SRV: &dns.SRV{
|
||||||
|
Target: "email.capsulecorp.dev 443",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"autoconfig.local-ip.sh.": {
|
||||||
|
CNAME: []*dns.CNAME{
|
||||||
|
{Target: "email.capsulecorp.dev"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"_dmarc.local-ip.sh.": {
|
"_dmarc.local-ip.sh.": {
|
||||||
TXT: &dns.TXT{
|
TXT: &dns.TXT{
|
||||||
Txt: []string{"v=DMARC1; p=quarantine; pct=100; adkim=s; aspf=s"},
|
Txt: []string{"v=DMARC1; p=none; rua=mailto:postmaster@local-ip.sh; ruf=mailto:admin@local-ip.sh"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"dkim._domainkey.local-ip.sh.": {
|
"dkim._domainkey.local-ip.sh.": {
|
||||||
CNAME: []*dns.CNAME{
|
TXT: &dns.TXT{
|
||||||
{Target: "dkim._domainkey.simplelogin.co."},
|
Txt: []string{"v=DKIM1;k=rsa;t=s;s=email;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsMW6NFo34qzKRPbzK41GwbWncB8IDg1i2eA2VWznIVDmTzzsqILaBOGv2xokVpzZm0QRF9wSbeVUmvwEeQ7Z6wkfMjawenDEc3XxsNSvQUVBP6LU/xcm1zsR8wtD8r5J+Jm45pNFaateiM/kb/Eypp2ntdtd8CPsEgCEDpNb62LWdy0yzRdZ/M/fNn51UMN8hVFp4YfZngAt3bQwa6kPtgvTeqEbpNf5xanpDysNJt2S8zfqJMVGvnr8JaJiTv7ZlKMMp94aC5Ndcir1WbMyfmgSnGgemuCTVMWDGPJnXDi+8BQMH1b1hmTpWDiVdVlehyyWx5AfPrsWG9cEuDIfXwIDAQAB"},
|
||||||
},
|
|
||||||
},
|
|
||||||
"dkim02._domainkey.local-ip.sh.": {
|
|
||||||
CNAME: []*dns.CNAME{
|
|
||||||
{Target: "dkim02._domainkey.simplelogin.co."},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"dkim03._domainkey.local-ip.sh.": {
|
|
||||||
CNAME: []*dns.CNAME{
|
|
||||||
{Target: "dkim03._domainkey.simplelogin.co."},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"_acme-challenge.local-ip.sh.": {
|
"_acme-challenge.local-ip.sh.": {
|
||||||
// required for fly.io to obtain a certificate for the website
|
|
||||||
CNAME: []*dns.CNAME{
|
|
||||||
{Target: "local-ip.sh.zzkxm3.flydns.net."},
|
|
||||||
},
|
|
||||||
// will be filled in later when requesting the wildcard certificate
|
// will be filled in later when requesting the wildcard certificate
|
||||||
TXT: &dns.TXT{},
|
TXT: &dns.TXT{},
|
||||||
},
|
},
|
||||||
@ -365,7 +364,6 @@ func (xip *Xip) handleDnsRequest(response dns.ResponseWriter, request *dns.Msg)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (xip *Xip) StartServer() {
|
func (xip *Xip) StartServer() {
|
||||||
log.Printf("Listening on %s\n", xip.server.Addr)
|
|
||||||
err := xip.server.ListenAndServe()
|
err := xip.server.ListenAndServe()
|
||||||
defer xip.server.Shutdown()
|
defer xip.server.Shutdown()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -383,6 +381,7 @@ func (xip *Xip) StartServer() {
|
|||||||
|
|
||||||
log.Fatalf("Failed to start server: %s\n ", err.Error())
|
log.Fatalf("Failed to start server: %s\n ", err.Error())
|
||||||
}
|
}
|
||||||
|
log.Printf("Listening on %s\n", xip.server.Addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewXip(port int) (xip *Xip) {
|
func NewXip(port int) (xip *Xip) {
|
||||||
|
Loading…
Reference in New Issue
Block a user