mirror of
https://github.com/AdguardTeam/AdGuardHome.git
synced 2024-12-15 03:02:07 +03:00
Added install methods to openapi.yaml
Print all net interfaces when bind_host is 0.0.0.0
This commit is contained in:
parent
e8898811fe
commit
4e1c1618cb
33
app.go
33
app.go
@ -208,8 +208,7 @@ func run(args options) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
URL := fmt.Sprintf("https://%s", address)
|
printHTTPAddresses("https")
|
||||||
log.Println("Go to " + URL)
|
|
||||||
err = httpsServer.server.ListenAndServeTLS("", "")
|
err = httpsServer.server.ListenAndServeTLS("", "")
|
||||||
if err != http.ErrServerClosed {
|
if err != http.ErrServerClosed {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -220,10 +219,10 @@ func run(args options) {
|
|||||||
|
|
||||||
// this loop is used as an ability to change listening host and/or port
|
// this loop is used as an ability to change listening host and/or port
|
||||||
for {
|
for {
|
||||||
address := net.JoinHostPort(config.BindHost, strconv.Itoa(config.BindPort))
|
printHTTPAddresses("http")
|
||||||
URL := fmt.Sprintf("http://%s", address)
|
|
||||||
log.Println("Go to " + URL)
|
|
||||||
// we need to have new instance, because after Shutdown() the Server is not usable
|
// we need to have new instance, because after Shutdown() the Server is not usable
|
||||||
|
address := net.JoinHostPort(config.BindHost, strconv.Itoa(config.BindPort))
|
||||||
httpServer = &http.Server{
|
httpServer = &http.Server{
|
||||||
Addr: address,
|
Addr: address,
|
||||||
}
|
}
|
||||||
@ -395,3 +394,27 @@ func loadOptions() options {
|
|||||||
|
|
||||||
return o
|
return o
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// prints IP addresses which user can use to open the admin interface
|
||||||
|
// proto is either "http" or "https"
|
||||||
|
func printHTTPAddresses(proto string) {
|
||||||
|
var address string
|
||||||
|
if config.BindHost == "0.0.0.0" {
|
||||||
|
log.Println("AdGuard Home is available on the following addresses:")
|
||||||
|
ifaces, err := getValidNetInterfacesForWeb()
|
||||||
|
if err != nil {
|
||||||
|
// That's weird, but we'll ignore it
|
||||||
|
address = net.JoinHostPort(config.BindHost, strconv.Itoa(config.BindPort))
|
||||||
|
log.Printf("Go to %s://%s", proto, address)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, iface := range ifaces {
|
||||||
|
address = net.JoinHostPort(iface.Addresses[0], strconv.Itoa(config.BindPort))
|
||||||
|
log.Printf("Go to %s://%s", proto, address)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
address = net.JoinHostPort(config.BindHost, strconv.Itoa(config.BindPort))
|
||||||
|
log.Printf("Go to %s://%s", proto, address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
10
config.go
10
config.go
@ -32,11 +32,11 @@ type configuration struct {
|
|||||||
ourWorkingDir string // Location of our directory, used to protect against CWD being somewhere else
|
ourWorkingDir string // Location of our directory, used to protect against CWD being somewhere else
|
||||||
firstRun bool // if set to true, don't run any services except HTTP web inteface, and serve only first-run html
|
firstRun bool // if set to true, don't run any services except HTTP web inteface, and serve only first-run html
|
||||||
|
|
||||||
BindHost string `yaml:"bind_host"`
|
BindHost string `yaml:"bind_host"` // BindHost is the IP address of the HTTP server to bind to
|
||||||
BindPort int `yaml:"bind_port"`
|
BindPort int `yaml:"bind_port"` // BindPort is the port the HTTP server
|
||||||
AuthName string `yaml:"auth_name"`
|
AuthName string `yaml:"auth_name"` // AuthName is the basic auth username
|
||||||
AuthPass string `yaml:"auth_pass"`
|
AuthPass string `yaml:"auth_pass"` // AuthPass is the basic auth password
|
||||||
Language string `yaml:"language"` // two-letter ISO 639-1 language code
|
Language string `yaml:"language"` // two-letter ISO 639-1 language code
|
||||||
DNS dnsConfig `yaml:"dns"`
|
DNS dnsConfig `yaml:"dns"`
|
||||||
TLS tlsConfig `yaml:"tls"`
|
TLS tlsConfig `yaml:"tls"`
|
||||||
Filters []filter `yaml:"filters"`
|
Filters []filter `yaml:"filters"`
|
||||||
|
53
control.go
53
control.go
@ -909,17 +909,6 @@ type firstRunData struct {
|
|||||||
|
|
||||||
func handleInstallGetAddresses(w http.ResponseWriter, r *http.Request) {
|
func handleInstallGetAddresses(w http.ResponseWriter, r *http.Request) {
|
||||||
data := firstRunData{}
|
data := firstRunData{}
|
||||||
ifaces, err := getValidNetInterfaces()
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, http.StatusInternalServerError, "Couldn't get interfaces: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(ifaces) == 0 {
|
|
||||||
httpError(w, http.StatusServiceUnavailable, "Couldn't find any legible interface, plase try again later")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// fill out the fields
|
|
||||||
|
|
||||||
// find out if port 80 is available -- if not, fall back to 3000
|
// find out if port 80 is available -- if not, fall back to 3000
|
||||||
if checkPortAvailable("", 80) == nil {
|
if checkPortAvailable("", 80) == nil {
|
||||||
@ -934,41 +923,15 @@ func handleInstallGetAddresses(w http.ResponseWriter, r *http.Request) {
|
|||||||
data.DNS.Warning = "Port 53 is not available for binding -- this will make DNS clients unable to contact AdGuard Home."
|
data.DNS.Warning = "Port 53 is not available for binding -- this will make DNS clients unable to contact AdGuard Home."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ifaces, err := getValidNetInterfacesForWeb()
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, http.StatusInternalServerError, "Couldn't get interfaces: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
data.Interfaces = make(map[string]interface{})
|
data.Interfaces = make(map[string]interface{})
|
||||||
for _, iface := range ifaces {
|
for _, iface := range ifaces {
|
||||||
addrs, e := iface.Addrs()
|
data.Interfaces[iface.Name] = iface
|
||||||
if e != nil {
|
|
||||||
httpError(w, http.StatusInternalServerError, "Failed to get addresses for interface %s: %s", iface.Name, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonIface := netInterface{
|
|
||||||
Name: iface.Name,
|
|
||||||
MTU: iface.MTU,
|
|
||||||
HardwareAddr: iface.HardwareAddr.String(),
|
|
||||||
}
|
|
||||||
|
|
||||||
if iface.Flags != 0 {
|
|
||||||
jsonIface.Flags = iface.Flags.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// we don't want link-local addresses in json, so skip them
|
|
||||||
for _, addr := range addrs {
|
|
||||||
ipnet, ok := addr.(*net.IPNet)
|
|
||||||
if !ok {
|
|
||||||
// not an IPNet, should not happen
|
|
||||||
httpError(w, http.StatusInternalServerError, "SHOULD NOT HAPPEN: got iface.Addrs() element %s that is not net.IPNet, it is %T", addr, addr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// ignore link-local
|
|
||||||
if ipnet.IP.IsLinkLocalUnicast() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
jsonIface.Addresses = append(jsonIface.Addresses, ipnet.IP.String())
|
|
||||||
}
|
|
||||||
if len(jsonIface.Addresses) != 0 {
|
|
||||||
data.Interfaces[iface.Name] = jsonIface
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
@ -983,7 +946,7 @@ func handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
|
|||||||
newSettings := firstRunData{}
|
newSettings := firstRunData{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&newSettings)
|
err := json.NewDecoder(r.Body).Decode(&newSettings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, http.StatusBadRequest, "Failed to parse new DHCP config json: %s", err)
|
httpError(w, http.StatusBadRequest, "Failed to parse new config json: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
52
helpers.go
52
helpers.go
@ -15,6 +15,8 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/joomcode/errorx"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
@ -237,6 +239,56 @@ func getValidNetInterfaces() ([]net.Interface, error) {
|
|||||||
return netIfaces, nil
|
return netIfaces, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getValidNetInterfacesMap returns interfaces that are eligible for DNS and WEB only
|
||||||
|
// we do not return link-local addresses here
|
||||||
|
func getValidNetInterfacesForWeb() ([]netInterface, error) {
|
||||||
|
ifaces, err := getValidNetInterfaces()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorx.Decorate(err, "Couldn't get interfaces")
|
||||||
|
}
|
||||||
|
if len(ifaces) == 0 {
|
||||||
|
return nil, errors.New("couldn't find any legible interface")
|
||||||
|
}
|
||||||
|
|
||||||
|
var netInterfaces []netInterface
|
||||||
|
|
||||||
|
for _, iface := range ifaces {
|
||||||
|
addrs, e := iface.Addrs()
|
||||||
|
if e != nil {
|
||||||
|
return nil, errorx.Decorate(e, "Failed to get addresses for interface %s", iface.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
netIface := netInterface{
|
||||||
|
Name: iface.Name,
|
||||||
|
MTU: iface.MTU,
|
||||||
|
HardwareAddr: iface.HardwareAddr.String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if iface.Flags != 0 {
|
||||||
|
netIface.Flags = iface.Flags.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// we don't want link-local addresses in json, so skip them
|
||||||
|
for _, addr := range addrs {
|
||||||
|
ipnet, ok := addr.(*net.IPNet)
|
||||||
|
if !ok {
|
||||||
|
// not an IPNet, should not happen
|
||||||
|
return nil, fmt.Errorf("SHOULD NOT HAPPEN: got iface.Addrs() element %s that is not net.IPNet, it is %T", addr, addr)
|
||||||
|
}
|
||||||
|
// ignore link-local
|
||||||
|
if ipnet.IP.IsLinkLocalUnicast() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
netIface.Addresses = append(netIface.Addresses, ipnet.IP.String())
|
||||||
|
}
|
||||||
|
if len(netIface.Addresses) != 0 {
|
||||||
|
netInterfaces = append(netInterfaces, netIface)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return netInterfaces, nil
|
||||||
|
}
|
||||||
|
|
||||||
// checkPortAvailable is not a cheap test to see if the port is bindable, because it's actually doing the bind momentarily
|
// checkPortAvailable is not a cheap test to see if the port is bindable, because it's actually doing the bind momentarily
|
||||||
func checkPortAvailable(host string, port int) error {
|
func checkPortAvailable(host string, port int) error {
|
||||||
ln, err := net.Listen("tcp", net.JoinHostPort(host, strconv.Itoa(port)))
|
ln, err := net.Listen("tcp", net.JoinHostPort(host, strconv.Itoa(port)))
|
||||||
|
25
helpers_test.go
Normal file
25
helpers_test.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hmage/golibs/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetValidNetInterfacesForWeb(t *testing.T) {
|
||||||
|
ifaces, err := getValidNetInterfacesForWeb()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Cannot get net interfaces: %s", err)
|
||||||
|
}
|
||||||
|
if len(ifaces) == 0 {
|
||||||
|
t.Fatalf("No net interfaces found")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, iface := range ifaces {
|
||||||
|
if len(iface.Addresses) == 0 {
|
||||||
|
t.Fatalf("No addresses found for %s", iface.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("%v", iface)
|
||||||
|
}
|
||||||
|
}
|
@ -39,6 +39,9 @@ tags:
|
|||||||
-
|
-
|
||||||
name: dhcp
|
name: dhcp
|
||||||
description: 'Built-in DHCP server controls'
|
description: 'Built-in DHCP server controls'
|
||||||
|
-
|
||||||
|
name: install
|
||||||
|
description: 'First-time install configuration handlers'
|
||||||
paths:
|
paths:
|
||||||
|
|
||||||
# API TO-DO LIST
|
# API TO-DO LIST
|
||||||
@ -713,6 +716,42 @@ paths:
|
|||||||
text/plain:
|
text/plain:
|
||||||
en
|
en
|
||||||
|
|
||||||
|
# --------------------------------------------------
|
||||||
|
# First-time install configuration methods
|
||||||
|
# --------------------------------------------------
|
||||||
|
|
||||||
|
/install/get_addresses:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- install
|
||||||
|
operationId: installGetAddresses
|
||||||
|
summary: "Gets the network interfaces information."
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/AddressesInfo"
|
||||||
|
/install/configure:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- install
|
||||||
|
operationId: installConfigure
|
||||||
|
summary: "Applies the initial configuration."
|
||||||
|
parameters:
|
||||||
|
- in: "body"
|
||||||
|
name: "body"
|
||||||
|
description: "Initial configuration JSON"
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/InitialConfiguration"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: OK
|
||||||
|
400:
|
||||||
|
description: "Failed to parse initial configuration or cannot listen to the specified addresses"
|
||||||
|
500:
|
||||||
|
description: "Cannot start the DNS server"
|
||||||
|
|
||||||
definitions:
|
definitions:
|
||||||
ServerStatus:
|
ServerStatus:
|
||||||
type: "object"
|
type: "object"
|
||||||
@ -1207,4 +1246,70 @@ definitions:
|
|||||||
warning_validation:
|
warning_validation:
|
||||||
type: "string"
|
type: "string"
|
||||||
example: "You have specified an empty certificate"
|
example: "You have specified an empty certificate"
|
||||||
description: "warning_validation is a validation warning message with the issue description"
|
description: "warning_validation is a validation warning message with the issue description"
|
||||||
|
NetInterface:
|
||||||
|
type: "object"
|
||||||
|
description: "Network interface info"
|
||||||
|
properties:
|
||||||
|
flags:
|
||||||
|
type: "string"
|
||||||
|
example: "up|broadcast|multicast"
|
||||||
|
hardware_address:
|
||||||
|
type: "string"
|
||||||
|
example: "52:54:00:11:09:ba"
|
||||||
|
mtu:
|
||||||
|
type: "integer"
|
||||||
|
format: "int32"
|
||||||
|
example: 1500
|
||||||
|
name:
|
||||||
|
type: "string"
|
||||||
|
example: "eth0"
|
||||||
|
ip_addresses:
|
||||||
|
type: "array"
|
||||||
|
items:
|
||||||
|
type: "string"
|
||||||
|
example:
|
||||||
|
- "127.0.0.1"
|
||||||
|
AddressInfo:
|
||||||
|
type: "object"
|
||||||
|
description: "Port information"
|
||||||
|
properties:
|
||||||
|
ip:
|
||||||
|
type: "string"
|
||||||
|
example: "127.0.01"
|
||||||
|
port:
|
||||||
|
type: "integer"
|
||||||
|
format: "int32"
|
||||||
|
example: 53
|
||||||
|
warning:
|
||||||
|
type: "string"
|
||||||
|
example: "Cannot bind to this port"
|
||||||
|
AddressesInfo:
|
||||||
|
type: "object"
|
||||||
|
description: "AdGuard Home addresses configuration"
|
||||||
|
properties:
|
||||||
|
dns:
|
||||||
|
$ref: "#/definitions/AddressInfo"
|
||||||
|
web:
|
||||||
|
$ref: "#/definitions/AddressInfo"
|
||||||
|
interfaces:
|
||||||
|
type: "object"
|
||||||
|
description: "Network interfaces dictionary (key is the interface name)"
|
||||||
|
additionalProperties:
|
||||||
|
$ref: "#/definitions/NetInterface"
|
||||||
|
InitialConfiguration:
|
||||||
|
type: "object"
|
||||||
|
description: "AdGuard Home initial configuration (for the first-install wizard)"
|
||||||
|
properties:
|
||||||
|
dns:
|
||||||
|
$ref: "#/definitions/AddressInfo"
|
||||||
|
web:
|
||||||
|
$ref: "#/definitions/AddressInfo"
|
||||||
|
username:
|
||||||
|
type: "string"
|
||||||
|
description: "Basic auth username"
|
||||||
|
example: "admin"
|
||||||
|
password:
|
||||||
|
type: "string"
|
||||||
|
description: "Basic auth password"
|
||||||
|
example: "password"
|
Loading…
Reference in New Issue
Block a user