AdGuard Home as a system service
1. Reworked working with command-line arguments 2. Added service control actions: install/uninstall/start/stop/status 3. Added log settings to the configuration file 4. Updated the README file
This commit is contained in:
parent
b216475c20
commit
277415124e
|
@ -4,6 +4,7 @@
|
||||||
/AdGuardHome
|
/AdGuardHome
|
||||||
/AdGuardHome.exe
|
/AdGuardHome.exe
|
||||||
/AdGuardHome.yaml
|
/AdGuardHome.yaml
|
||||||
|
/AdGuardHome.log
|
||||||
/data/
|
/data/
|
||||||
/build/
|
/build/
|
||||||
/dist/
|
/dist/
|
||||||
|
|
40
README.md
40
README.md
|
@ -99,6 +99,35 @@ sudo ./AdGuardHome
|
||||||
|
|
||||||
Now open the browser and navigate to http://localhost:3000/ to control your AdGuard Home service.
|
Now open the browser and navigate to http://localhost:3000/ to control your AdGuard Home service.
|
||||||
|
|
||||||
|
### Command-line arguments
|
||||||
|
|
||||||
|
Here is a list of all available command-line arguments.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./AdGuardHome -h
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
./AdGuardHome [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-c, --config path to config file
|
||||||
|
-o, --host host address to bind HTTP server on
|
||||||
|
-p, --port port to serve HTTP pages on
|
||||||
|
-v, --verbose enable verbose output
|
||||||
|
-s, --service service control action: status, install, uninstall, start, stop, restart
|
||||||
|
-l, --logfile path to the log file. If empty, writes to stdout, if 'syslog' -- system log
|
||||||
|
-h, --help print this help
|
||||||
|
```
|
||||||
|
|
||||||
|
Please note, that you can register AdGuard Home as a system service on Windows, Linux/(systemd | Upstart | SysV), and OSX/Launchd.
|
||||||
|
|
||||||
|
* `AdGuardHome -s install` - install as a system service.
|
||||||
|
* `AdGuardHome -s uninstall` - uninstall's AdGuard Home service.
|
||||||
|
* `AdGuardHome -s start` - starts the service.
|
||||||
|
* `AdGuardHome -s stop` - stops the service.
|
||||||
|
* `AdGuardHome -s restart` - restarts the service.
|
||||||
|
* `AdGuardHome -s status` - shows the current service status.
|
||||||
|
|
||||||
### Running without superuser
|
### Running without superuser
|
||||||
|
|
||||||
You can run AdGuard Home without superuser privileges, but you need to either grant the binary a capability (on Linux) or instruct it to use a different port (all platforms).
|
You can run AdGuard Home without superuser privileges, but you need to either grant the binary a capability (on Linux) or instruct it to use a different port (all platforms).
|
||||||
|
@ -139,6 +168,7 @@ Settings are stored in [YAML format](https://en.wikipedia.org/wiki/YAML), possib
|
||||||
* `auth_name` — Web interface optional authorization username.
|
* `auth_name` — Web interface optional authorization username.
|
||||||
* `auth_pass` — Web interface optional authorization password.
|
* `auth_pass` — Web interface optional authorization password.
|
||||||
* `dns` — DNS configuration section.
|
* `dns` — DNS configuration section.
|
||||||
|
* `bind_host` - DNS interface IP address to listen on.
|
||||||
* `port` — DNS server port to listen on.
|
* `port` — DNS server port to listen on.
|
||||||
* `protection_enabled` — Whether any kind of filtering and protection should be done, when off it works as a plain dns forwarder.
|
* `protection_enabled` — Whether any kind of filtering and protection should be done, when off it works as a plain dns forwarder.
|
||||||
* `filtering_enabled` — Filtering of DNS requests based on filter lists.
|
* `filtering_enabled` — Filtering of DNS requests based on filter lists.
|
||||||
|
@ -159,7 +189,17 @@ Settings are stored in [YAML format](https://en.wikipedia.org/wiki/YAML), possib
|
||||||
* `name` — Name of the filter. If it's an adguard syntax filter it will get updated automatically, otherwise it stays unchanged.
|
* `name` — Name of the filter. If it's an adguard syntax filter it will get updated automatically, otherwise it stays unchanged.
|
||||||
* `last_updated` — Time when the filter was last updated from server.
|
* `last_updated` — Time when the filter was last updated from server.
|
||||||
* `ID` - filter ID (must be unique).
|
* `ID` - filter ID (must be unique).
|
||||||
|
* `dhcp` - Built-in DHCP server configuration.
|
||||||
|
* `enabled` - DHCP server status.
|
||||||
|
* `interface_name` - network interface name (eth0, en0 and so on).
|
||||||
|
* `gateway_ip` - gateway IP address.
|
||||||
|
* `subnet_mask` - subnet mask.
|
||||||
|
* `range_start` - start IP address of the controlled range.
|
||||||
|
* `range_end` - end IP address of the controlled range.
|
||||||
|
* `lease_duration` - lease duration in seconds. If 0, using default duration (2 hours).
|
||||||
* `user_rules` — User-specified filtering rules.
|
* `user_rules` — User-specified filtering rules.
|
||||||
|
* `log_file` — Path to the log file. If empty, writes to stdout, if 'syslog' -- system log.
|
||||||
|
* `verbose` — Enable our disables debug verbose output.
|
||||||
|
|
||||||
Removing an entry from settings file will reset it to the default value. Deleting the file will reset all settings to the default values.
|
Removing an entry from settings file will reset it to the default value. Deleting the file will reset all settings to the default values.
|
||||||
|
|
||||||
|
|
371
app.go
371
app.go
|
@ -3,6 +3,8 @@ package main
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
stdlog "log"
|
||||||
|
"log/syslog"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
@ -20,58 +22,19 @@ import (
|
||||||
// VersionString will be set through ldflags, contains current version
|
// VersionString will be set through ldflags, contains current version
|
||||||
var VersionString = "undefined"
|
var VersionString = "undefined"
|
||||||
|
|
||||||
|
// main is the entry point
|
||||||
func main() {
|
func main() {
|
||||||
log.Printf("AdGuard Home web interface backend, version %s\n", VersionString)
|
|
||||||
box := packr.NewBox("build/static")
|
|
||||||
{
|
|
||||||
executable, err := os.Executable()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
executableName := filepath.Base(executable)
|
|
||||||
if executableName == "AdGuardHome" {
|
|
||||||
// Binary build
|
|
||||||
config.ourBinaryDir = filepath.Dir(executable)
|
|
||||||
} else {
|
|
||||||
// Most likely we're debugging -- using current working directory in this case
|
|
||||||
workDir, _ := os.Getwd()
|
|
||||||
config.ourBinaryDir = workDir
|
|
||||||
}
|
|
||||||
log.Printf("Current working directory is %s", config.ourBinaryDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
// config can be specified, which reads options from there, but other command line flags have to override config values
|
// config can be specified, which reads options from there, but other command line flags have to override config values
|
||||||
// therefore, we must do it manually instead of using a lib
|
// therefore, we must do it manually instead of using a lib
|
||||||
loadOptions()
|
args := loadOptions()
|
||||||
|
|
||||||
// Load filters from the disk
|
if args.serviceControlAction != "" {
|
||||||
// And if any filter has zero ID, assign a new one
|
handleServiceControlAction(args.serviceControlAction)
|
||||||
for i := range config.Filters {
|
return
|
||||||
filter := &config.Filters[i] // otherwise we're operating on a copy
|
|
||||||
if filter.ID == 0 {
|
|
||||||
filter.ID = assignUniqueFilterID()
|
|
||||||
}
|
|
||||||
err := filter.load()
|
|
||||||
if err != nil {
|
|
||||||
// This is okay for the first start, the filter will be loaded later
|
|
||||||
log.Printf("Couldn't load filter %d contents due to %s", filter.ID, err)
|
|
||||||
// clear LastUpdated so it gets fetched right away
|
|
||||||
}
|
|
||||||
if len(filter.Rules) == 0 {
|
|
||||||
filter.LastUpdated = time.Time{}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update filters we've just loaded right away, don't wait for periodic update timer
|
// run the protection
|
||||||
go func() {
|
run(args)
|
||||||
refreshFiltersIfNecessary(false)
|
|
||||||
// Save the updated config
|
|
||||||
err := config.write()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
signalChannel := make(chan os.Signal)
|
signalChannel := make(chan os.Signal)
|
||||||
signal.Notify(signalChannel, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT)
|
signal.Notify(signalChannel, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT)
|
||||||
|
@ -81,110 +44,23 @@ func main() {
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Save the updated config
|
|
||||||
err := config.write()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
address := net.JoinHostPort(config.BindHost, strconv.Itoa(config.BindPort))
|
address := net.JoinHostPort(config.BindHost, strconv.Itoa(config.BindPort))
|
||||||
|
|
||||||
go periodicallyRefreshFilters()
|
|
||||||
|
|
||||||
http.Handle("/", optionalAuthHandler(http.FileServer(box)))
|
|
||||||
registerControlHandlers()
|
|
||||||
|
|
||||||
err = startDNSServer()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = startDHCPServer()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
URL := fmt.Sprintf("http://%s", address)
|
URL := fmt.Sprintf("http://%s", address)
|
||||||
log.Println("Go to " + URL)
|
log.Println("Go to " + URL)
|
||||||
log.Fatal(http.ListenAndServe(address, nil))
|
log.Fatal(http.ListenAndServe(address, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func cleanup() {
|
// run initializes configuration and runs the AdGuard Home
|
||||||
err := stopDNSServer()
|
func run(args options) {
|
||||||
if err != nil {
|
if args.configFilename != "" {
|
||||||
log.Printf("Couldn't stop DNS server: %s", err)
|
config.ourConfigFilename = args.configFilename
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getInput() (string, error) {
|
// configure log level and output
|
||||||
scanner := bufio.NewScanner(os.Stdin)
|
configureLogger(args)
|
||||||
scanner.Scan()
|
|
||||||
text := scanner.Text()
|
|
||||||
err := scanner.Err()
|
|
||||||
return text, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// loadOptions reads command line arguments and initializes configuration
|
// print the first message after logger is configured
|
||||||
func loadOptions() {
|
log.Printf("AdGuard Home, version %s\n", VersionString)
|
||||||
var printHelp func()
|
|
||||||
var configFilename *string
|
|
||||||
var bindHost *string
|
|
||||||
var bindPort *int
|
|
||||||
var opts = []struct {
|
|
||||||
longName string
|
|
||||||
shortName string
|
|
||||||
description string
|
|
||||||
callbackWithValue func(value string)
|
|
||||||
callbackNoValue func()
|
|
||||||
}{
|
|
||||||
{"config", "c", "path to config file", func(value string) { configFilename = &value }, nil},
|
|
||||||
{"host", "h", "host address to bind HTTP server on", func(value string) { bindHost = &value }, nil},
|
|
||||||
{"port", "p", "port to serve HTTP pages on", func(value string) {
|
|
||||||
v, err := strconv.Atoi(value)
|
|
||||||
if err != nil {
|
|
||||||
panic("Got port that is not a number")
|
|
||||||
}
|
|
||||||
bindPort = &v
|
|
||||||
}, nil},
|
|
||||||
{"verbose", "v", "enable verbose output", nil, func() { log.Verbose = true }},
|
|
||||||
{"help", "h", "print this help", nil, func() { printHelp(); os.Exit(64) }},
|
|
||||||
}
|
|
||||||
printHelp = func() {
|
|
||||||
fmt.Printf("Usage:\n\n")
|
|
||||||
fmt.Printf("%s [options]\n\n", os.Args[0])
|
|
||||||
fmt.Printf("Options:\n")
|
|
||||||
for _, opt := range opts {
|
|
||||||
fmt.Printf(" -%s, %-30s %s\n", opt.shortName, "--"+opt.longName, opt.description)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i := 1; i < len(os.Args); i++ {
|
|
||||||
v := os.Args[i]
|
|
||||||
knownParam := false
|
|
||||||
for _, opt := range opts {
|
|
||||||
if v == "--"+opt.longName || v == "-"+opt.shortName {
|
|
||||||
if opt.callbackWithValue != nil {
|
|
||||||
if i+1 > len(os.Args) {
|
|
||||||
log.Printf("ERROR: Got %s without argument\n", v)
|
|
||||||
os.Exit(64)
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
opt.callbackWithValue(os.Args[i])
|
|
||||||
} else if opt.callbackNoValue != nil {
|
|
||||||
opt.callbackNoValue()
|
|
||||||
}
|
|
||||||
knownParam = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !knownParam {
|
|
||||||
log.Printf("ERROR: unknown option %v\n", v)
|
|
||||||
printHelp()
|
|
||||||
os.Exit(64)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if configFilename != nil {
|
|
||||||
config.ourConfigFilename = *configFilename
|
|
||||||
}
|
|
||||||
|
|
||||||
err := askUsernamePasswordIfPossible()
|
err := askUsernamePasswordIfPossible()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -204,12 +80,217 @@ func loadOptions() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// override bind host/port from the console
|
// override bind host/port from the console
|
||||||
if bindHost != nil {
|
if args.bindHost != "" {
|
||||||
config.BindHost = *bindHost
|
config.BindHost = args.bindHost
|
||||||
}
|
}
|
||||||
if bindPort != nil {
|
if args.bindPort != 0 {
|
||||||
config.BindPort = *bindPort
|
config.BindPort = args.bindPort
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load filters from the disk
|
||||||
|
// And if any filter has zero ID, assign a new one
|
||||||
|
for i := range config.Filters {
|
||||||
|
filter := &config.Filters[i] // otherwise we're operating on a copy
|
||||||
|
if filter.ID == 0 {
|
||||||
|
filter.ID = assignUniqueFilterID()
|
||||||
|
}
|
||||||
|
err = filter.load()
|
||||||
|
if err != nil {
|
||||||
|
// This is okay for the first start, the filter will be loaded later
|
||||||
|
log.Printf("Couldn't load filter %d contents due to %s", filter.ID, err)
|
||||||
|
// clear LastUpdated so it gets fetched right away
|
||||||
|
}
|
||||||
|
if len(filter.Rules) == 0 {
|
||||||
|
filter.LastUpdated = time.Time{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the updated config
|
||||||
|
err = config.write()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
box := packr.NewBox("build/static")
|
||||||
|
{
|
||||||
|
executable, osErr := os.Executable()
|
||||||
|
if osErr != nil {
|
||||||
|
panic(osErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
executableName := filepath.Base(executable)
|
||||||
|
if executableName == "AdGuardHome" {
|
||||||
|
// Binary build
|
||||||
|
config.ourBinaryDir = filepath.Dir(executable)
|
||||||
|
} else {
|
||||||
|
// Most likely we're debugging -- using current working directory in this case
|
||||||
|
workDir, _ := os.Getwd()
|
||||||
|
config.ourBinaryDir = workDir
|
||||||
|
}
|
||||||
|
log.Printf("Current working directory is %s", config.ourBinaryDir)
|
||||||
|
}
|
||||||
|
http.Handle("/", optionalAuthHandler(http.FileServer(box)))
|
||||||
|
registerControlHandlers()
|
||||||
|
|
||||||
|
err = startDNSServer()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = startDHCPServer()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update filters we've just loaded right away, don't wait for periodic update timer
|
||||||
|
go func() {
|
||||||
|
refreshFiltersIfNecessary(false)
|
||||||
|
// Save the updated config
|
||||||
|
err := config.write()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
// Schedule automatic filters updates
|
||||||
|
go periodicallyRefreshFilters()
|
||||||
|
}
|
||||||
|
|
||||||
|
// configureLogger configures logger level and output
|
||||||
|
func configureLogger(args options) {
|
||||||
|
ls := getLogSettings()
|
||||||
|
|
||||||
|
// command-line arguments can override config settings
|
||||||
|
if args.verbose {
|
||||||
|
ls.Verbose = true
|
||||||
|
}
|
||||||
|
if args.logFile != "" {
|
||||||
|
ls.LogFile = args.logFile
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Verbose = ls.Verbose
|
||||||
|
|
||||||
|
if ls.LogFile == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: add windows eventlog support
|
||||||
|
if ls.LogFile == "syslog" {
|
||||||
|
w, err := syslog.New(syslog.LOG_INFO, "AdGuard Home")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("cannot initialize syslog: %s", err)
|
||||||
|
}
|
||||||
|
stdlog.SetOutput(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
logFilePath := filepath.Join(config.ourBinaryDir, ls.LogFile)
|
||||||
|
file, err := os.OpenFile(logFilePath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0755)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("cannot create a log file: %s", err)
|
||||||
|
}
|
||||||
|
stdlog.SetOutput(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanup() {
|
||||||
|
log.Printf("Stopping AdGuard Home")
|
||||||
|
|
||||||
|
err := stopDNSServer()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Couldn't stop DNS server: %s", err)
|
||||||
|
}
|
||||||
|
err = stopDHCPServer()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Couldn't stop DHCP server: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getInput() (string, error) {
|
||||||
|
scanner := bufio.NewScanner(os.Stdin)
|
||||||
|
scanner.Scan()
|
||||||
|
text := scanner.Text()
|
||||||
|
err := scanner.Err()
|
||||||
|
return text, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// command-line arguments
|
||||||
|
type options struct {
|
||||||
|
verbose bool // is verbose logging enabled
|
||||||
|
configFilename string // path to the config file
|
||||||
|
bindHost string // host address to bind HTTP server on
|
||||||
|
bindPort int // port to serve HTTP pages on
|
||||||
|
logFile string // Path to the log file. If empty, write to stdout. If "syslog", writes to syslog
|
||||||
|
|
||||||
|
// service control action (see service.ControlAction array + "status" command)
|
||||||
|
serviceControlAction string
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadOptions reads command line arguments and initializes configuration
|
||||||
|
func loadOptions() options {
|
||||||
|
o := options{}
|
||||||
|
|
||||||
|
var printHelp func()
|
||||||
|
var opts = []struct {
|
||||||
|
longName string
|
||||||
|
shortName string
|
||||||
|
description string
|
||||||
|
callbackWithValue func(value string)
|
||||||
|
callbackNoValue func()
|
||||||
|
}{
|
||||||
|
{"config", "c", "path to config file", func(value string) { o.configFilename = value }, nil},
|
||||||
|
{"host", "o", "host address to bind HTTP server on", func(value string) { o.bindHost = value }, nil},
|
||||||
|
{"port", "p", "port to serve HTTP pages on", func(value string) {
|
||||||
|
v, err := strconv.Atoi(value)
|
||||||
|
if err != nil {
|
||||||
|
panic("Got port that is not a number")
|
||||||
|
}
|
||||||
|
o.bindPort = v
|
||||||
|
}, nil},
|
||||||
|
{"service", "s", "service control action: status, install, uninstall, start, stop, restart", func(value string) {
|
||||||
|
o.serviceControlAction = value
|
||||||
|
}, nil},
|
||||||
|
{"logfile", "l", "path to the log file. If empty, writes to stdout, if 'syslog' -- system log", func(value string) {
|
||||||
|
o.logFile = value
|
||||||
|
}, nil},
|
||||||
|
{"verbose", "v", "enable verbose output", nil, func() { o.verbose = true }},
|
||||||
|
{"help", "h", "print this help", nil, func() {
|
||||||
|
printHelp()
|
||||||
|
os.Exit(64)
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
printHelp = func() {
|
||||||
|
fmt.Printf("Usage:\n\n")
|
||||||
|
fmt.Printf("%s [options]\n\n", os.Args[0])
|
||||||
|
fmt.Printf("Options:\n")
|
||||||
|
for _, opt := range opts {
|
||||||
|
fmt.Printf(" -%s, %-30s %s\n", opt.shortName, "--"+opt.longName, opt.description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := 1; i < len(os.Args); i++ {
|
||||||
|
v := os.Args[i]
|
||||||
|
knownParam := false
|
||||||
|
for _, opt := range opts {
|
||||||
|
if v == "--"+opt.longName || v == "-"+opt.shortName {
|
||||||
|
if opt.callbackWithValue != nil {
|
||||||
|
if i+1 >= len(os.Args) {
|
||||||
|
log.Printf("ERROR: Got %s without argument\n", v)
|
||||||
|
os.Exit(64)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
opt.callbackWithValue(os.Args[i])
|
||||||
|
} else if opt.callbackNoValue != nil {
|
||||||
|
opt.callbackNoValue()
|
||||||
|
}
|
||||||
|
knownParam = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !knownParam {
|
||||||
|
log.Printf("ERROR: unknown option %v\n", v)
|
||||||
|
printHelp()
|
||||||
|
os.Exit(64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return o
|
||||||
}
|
}
|
||||||
|
|
||||||
func promptAndGet(prompt string) (string, error) {
|
func promptAndGet(prompt string) (string, error) {
|
||||||
|
|
46
config.go
46
config.go
|
@ -18,6 +18,12 @@ const (
|
||||||
filterDir = "filters" // cache location for downloaded filters, it's under DataDir
|
filterDir = "filters" // cache location for downloaded filters, it's under DataDir
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// logSettings
|
||||||
|
type logSettings struct {
|
||||||
|
LogFile string `yaml:"log_file"` // Path to the log file. If empty, write to stdout. If "syslog", writes to syslog
|
||||||
|
Verbose bool `yaml:"verbose"` // If true, verbose logging is enabled
|
||||||
|
}
|
||||||
|
|
||||||
// configuration is loaded from YAML
|
// configuration is loaded from YAML
|
||||||
// field ordering is important -- yaml fields will mirror ordering from here
|
// field ordering is important -- yaml fields will mirror ordering from here
|
||||||
type configuration struct {
|
type configuration struct {
|
||||||
|
@ -34,6 +40,8 @@ type configuration struct {
|
||||||
UserRules []string `yaml:"user_rules"`
|
UserRules []string `yaml:"user_rules"`
|
||||||
DHCP dhcpd.ServerConfig `yaml:"dhcp"`
|
DHCP dhcpd.ServerConfig `yaml:"dhcp"`
|
||||||
|
|
||||||
|
logSettings `yaml:",inline"`
|
||||||
|
|
||||||
sync.RWMutex `yaml:"-"`
|
sync.RWMutex `yaml:"-"`
|
||||||
|
|
||||||
SchemaVersion int `yaml:"schema_version"` // keeping last so that users will be less tempted to change it -- used when upgrading between versions
|
SchemaVersion int `yaml:"schema_version"` // keeping last so that users will be less tempted to change it -- used when upgrading between versions
|
||||||
|
@ -79,20 +87,34 @@ var config = configuration{
|
||||||
SchemaVersion: currentSchemaVersion,
|
SchemaVersion: currentSchemaVersion,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loads configuration from the YAML file
|
// getLogSettings reads logging settings from the config file.
|
||||||
|
// we do it in a separate method in order to configure logger before the actual configuration is parsed and applied.
|
||||||
|
func getLogSettings() logSettings {
|
||||||
|
l := logSettings{}
|
||||||
|
yamlFile, err := readConfigFile()
|
||||||
|
if err != nil || yamlFile == nil {
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
err = yaml.Unmarshal(yamlFile, &l)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Couldn't get logging settings from the configuration: %s", err)
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseConfig loads configuration from the YAML file
|
||||||
func parseConfig() error {
|
func parseConfig() error {
|
||||||
configFile := filepath.Join(config.ourBinaryDir, config.ourConfigFilename)
|
configFile := filepath.Join(config.ourBinaryDir, config.ourConfigFilename)
|
||||||
log.Printf("Reading YAML file: %s", configFile)
|
log.Printf("Reading YAML file: %s", configFile)
|
||||||
if _, err := os.Stat(configFile); os.IsNotExist(err) {
|
yamlFile, err := readConfigFile()
|
||||||
// do nothing, file doesn't exist
|
|
||||||
log.Printf("YAML file doesn't exist, skipping: %s", configFile)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
yamlFile, err := ioutil.ReadFile(configFile)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Couldn't read config file: %s", err)
|
log.Printf("Couldn't read config file: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if yamlFile == nil {
|
||||||
|
log.Printf("YAML file doesn't exist, skipping it")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
err = yaml.Unmarshal(yamlFile, &config)
|
err = yaml.Unmarshal(yamlFile, &config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Couldn't parse config file: %s", err)
|
log.Printf("Couldn't parse config file: %s", err)
|
||||||
|
@ -107,6 +129,16 @@ func parseConfig() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readConfigFile reads config file contents if it exists
|
||||||
|
func readConfigFile() ([]byte, error) {
|
||||||
|
configFile := filepath.Join(config.ourBinaryDir, config.ourConfigFilename)
|
||||||
|
if _, err := os.Stat(configFile); os.IsNotExist(err) {
|
||||||
|
// do nothing, file doesn't exist
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return ioutil.ReadFile(configFile)
|
||||||
|
}
|
||||||
|
|
||||||
// Saves configuration to the YAML file and also saves the user filter contents to a file
|
// Saves configuration to the YAML file and also saves the user filter contents to a file
|
||||||
func (c *configuration) write() error {
|
func (c *configuration) write() error {
|
||||||
c.Lock()
|
c.Lock()
|
||||||
|
|
17
dhcp.go
17
dhcp.go
|
@ -165,3 +165,20 @@ func startDHCPServer() error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func stopDHCPServer() error {
|
||||||
|
if !config.DHCP.Enabled {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !dhcpServer.Enabled {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := dhcpServer.Stop()
|
||||||
|
if err != nil {
|
||||||
|
return errorx.Decorate(err, "Couldn't stop DHCP server")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -9,6 +9,7 @@ require (
|
||||||
github.com/gobuffalo/packr v1.19.0
|
github.com/gobuffalo/packr v1.19.0
|
||||||
github.com/hmage/golibs v0.0.0-20181229160906-c8491df0bfc4
|
github.com/hmage/golibs v0.0.0-20181229160906-c8491df0bfc4
|
||||||
github.com/joomcode/errorx v0.1.0
|
github.com/joomcode/errorx v0.1.0
|
||||||
|
github.com/kardianos/service v0.0.0-20181115005516-4c239ee84e7b
|
||||||
github.com/krolaw/dhcp4 v0.0.0-20180925202202-7cead472c414
|
github.com/krolaw/dhcp4 v0.0.0-20180925202202-7cead472c414
|
||||||
github.com/miekg/dns v1.1.1
|
github.com/miekg/dns v1.1.1
|
||||||
github.com/shirou/gopsutil v2.18.10+incompatible
|
github.com/shirou/gopsutil v2.18.10+incompatible
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -37,6 +37,8 @@ github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||||
github.com/joomcode/errorx v0.1.0 h1:QmJMiI1DE1UFje2aI1ZWO/VMT5a32qBoXUclGOt8vsc=
|
github.com/joomcode/errorx v0.1.0 h1:QmJMiI1DE1UFje2aI1ZWO/VMT5a32qBoXUclGOt8vsc=
|
||||||
github.com/joomcode/errorx v0.1.0/go.mod h1:kgco15ekB6cs+4Xjzo7SPeXzx38PbJzBwbnu9qfVNHQ=
|
github.com/joomcode/errorx v0.1.0/go.mod h1:kgco15ekB6cs+4Xjzo7SPeXzx38PbJzBwbnu9qfVNHQ=
|
||||||
|
github.com/kardianos/service v0.0.0-20181115005516-4c239ee84e7b h1:vfiqKno48aUndBMjTeWFpCExNnTf2Xnd6d228L4EfTQ=
|
||||||
|
github.com/kardianos/service v0.0.0-20181115005516-4c239ee84e7b/go.mod h1:10UU/bEkzh2iEN6aYzbevY7J6p03KO5siTxQWXMEerg=
|
||||||
github.com/krolaw/dhcp4 v0.0.0-20180925202202-7cead472c414 h1:6wnYc2S/lVM7BvR32BM74ph7bPgqMztWopMYKgVyEho=
|
github.com/krolaw/dhcp4 v0.0.0-20180925202202-7cead472c414 h1:6wnYc2S/lVM7BvR32BM74ph7bPgqMztWopMYKgVyEho=
|
||||||
github.com/krolaw/dhcp4 v0.0.0-20180925202202-7cead472c414/go.mod h1:0AqAH3ZogsCrvrtUpvc6EtVKbc3w6xwZhkvGLuqyi3o=
|
github.com/krolaw/dhcp4 v0.0.0-20180925202202-7cead472c414/go.mod h1:0AqAH3ZogsCrvrtUpvc6EtVKbc3w6xwZhkvGLuqyi3o=
|
||||||
github.com/markbates/oncer v0.0.0-20181014194634-05fccaae8fc4 h1:Mlji5gkcpzkqTROyE4ZxZ8hN7osunMb2RuGVrbvMvCc=
|
github.com/markbates/oncer v0.0.0-20181014194634-05fccaae8fc4 h1:Mlji5gkcpzkqTROyE4ZxZ8hN7osunMb2RuGVrbvMvCc=
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/hmage/golibs/log"
|
||||||
|
"github.com/kardianos/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Represents the program that will be launched by a service or daemon
|
||||||
|
type program struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start should quickly start the program
|
||||||
|
func (p *program) Start(s service.Service) error {
|
||||||
|
// Start should not block. Do the actual work async.
|
||||||
|
args := options{}
|
||||||
|
go run(args)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop stops the program
|
||||||
|
func (p *program) Stop(s service.Service) error {
|
||||||
|
// Stop should not block. Return with a few seconds.
|
||||||
|
cleanup()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleServiceControlAction one of the possible control actions:
|
||||||
|
// install -- installs a service/daemon
|
||||||
|
// uninstall -- uninstalls it
|
||||||
|
// status -- prints the service status
|
||||||
|
// start -- starts the previously installed service
|
||||||
|
// stop -- stops the previously installed service
|
||||||
|
// restart - restarts the previously installed service
|
||||||
|
func handleServiceControlAction(action string) {
|
||||||
|
log.Printf("Service control action: %s", action)
|
||||||
|
|
||||||
|
pwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Unable to find the path to the current directory")
|
||||||
|
}
|
||||||
|
svcConfig := &service.Config{
|
||||||
|
Name: "AdGuardHome",
|
||||||
|
DisplayName: "AdGuard Home service",
|
||||||
|
Description: "AdGuard Home: Network-level blocker",
|
||||||
|
WorkingDirectory: pwd,
|
||||||
|
}
|
||||||
|
prg := &program{}
|
||||||
|
s, err := service.New(prg, svcConfig)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if action == "status" {
|
||||||
|
status, errSt := s.Status()
|
||||||
|
if errSt != nil {
|
||||||
|
log.Fatalf("failed to get service status: %s", errSt)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch status {
|
||||||
|
case service.StatusUnknown:
|
||||||
|
log.Printf("Service status is unknown")
|
||||||
|
case service.StatusStopped:
|
||||||
|
log.Printf("Service is stopped")
|
||||||
|
case service.StatusRunning:
|
||||||
|
log.Printf("Service is running")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = service.Control(s, action)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
log.Printf("Action %s has been done successfully", action)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue