diff --git a/CHANGELOG.md b/CHANGELOG.md index 339849127f8..b534e63ff98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to ### Added +- Experimental OpenBSD support for AMD64 and 64-bit ARM CPUs ([#2439]). - Support for custom port in DNS-over-HTTPS profiles for Apple's devices ([#3172]). - `darwin/arm64` support ([#2443]). @@ -52,6 +53,7 @@ released by then. - Go 1.15 support. +[#2439]: https://github.com/AdguardTeam/AdGuardHome/issues/2439 [#2441]: https://github.com/AdguardTeam/AdGuardHome/issues/2441 [#2443]: https://github.com/AdguardTeam/AdGuardHome/issues/2443 [#3136]: https://github.com/AdguardTeam/AdGuardHome/issues/3136 diff --git a/HACKING.md b/HACKING.md index 1b19d217d49..3eae4ba74d9 100644 --- a/HACKING.md +++ b/HACKING.md @@ -349,6 +349,13 @@ on GitHub and most other Markdown renderers. --> * `snake_case`, not `camelCase` for variables. `kebab-case` for filenames. + * Start scripts with the following sections in the following order: + + 1. Shebang. + 1. Some initial documentation (optional). + 1. Verbosity level parsing (optional). + 1. `set` options. + * UPPERCASE names for external exported variables, lowercase for local, unexported ones. diff --git a/Makefile b/Makefile index 35716929edd..51269440c45 100644 --- a/Makefile +++ b/Makefile @@ -116,6 +116,7 @@ go-check: go-tools go-lint go-test go-os-check: env GOOS='darwin' "$(GO.MACRO)" vet ./internal/... env GOOS='freebsd' "$(GO.MACRO)" vet ./internal/... + env GOOS='openbsd' "$(GO.MACRO)" vet ./internal/... env GOOS='linux' "$(GO.MACRO)" vet ./internal/... env GOOS='windows' "$(GO.MACRO)" vet ./internal/... diff --git a/README.md b/README.md index e686325fca6..dcd798cd873 100644 --- a/README.md +++ b/README.md @@ -289,6 +289,8 @@ curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/s * macOS ARM: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_arm64.zip) * FreeBSD: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_amd64.tar.gz), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_386.tar.gz) * FreeBSD ARM: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_arm64.tar.gz), [32-bit ARMv5](https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv5.tar.gz), [32-bit ARMv6](https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv6.tar.gz), [32-bit ARMv7](https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv7.tar.gz) + * OpenBSD: (coming soon) + * OpenBSD ARM: (coming soon) * Edge channel builds * Linux: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_amd64.tar.gz), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_386.tar.gz) @@ -299,6 +301,8 @@ curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/s * macOS ARM: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_darwin_arm64.zip) * FreeBSD: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_freebsd_amd64.tar.gz), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_freebsd_386.tar.gz) * FreeBSD ARM: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_freebsd_arm64.tar.gz), [32-bit ARMv5](https://static.adguard.com/adguardhome/edge/AdGuardHome_freebsd_armv5.tar.gz), [32-bit ARMv6](https://static.adguard.com/adguardhome/edge/AdGuardHome_freebsd_armv6.tar.gz), [32-bit ARMv7](https://static.adguard.com/adguardhome/edge/AdGuardHome_freebsd_armv7.tar.gz) + * OpenBSD: [64-bit (experimental)](https://static.adguard.com/adguardhome/edge/AdGuardHome_openbsd_amd64.tar.gz) + * OpenBSD ARM: [64-bit (experimental)](https://static.adguard.com/adguardhome/edge/AdGuardHome_openbsd_arm64.tar.gz) diff --git a/internal/aghos/os.go b/internal/aghos/os.go index 827591fa6ea..0c3db64a138 100644 --- a/internal/aghos/os.go +++ b/internal/aghos/os.go @@ -5,18 +5,28 @@ import ( "fmt" "os/exec" "syscall" + + "github.com/AdguardTeam/golibs/errors" ) +// ErrUnsupported is returned when the functionality is unsupported on the +// current operating system. +// +// TODO(a.garipov): Make a structured error and use it everywhere instead of +// a bunch of fmt.Errorf and all that. +const ErrUnsupported errors.Error = "unsupported" + // CanBindPrivilegedPorts checks if current process can bind to privileged // ports. func CanBindPrivilegedPorts() (can bool, err error) { return canBindPrivilegedPorts() } -// SetRlimit sets user-specified limit of how many fd's we can use -// https://github.com/AdguardTeam/AdGuardHome/internal/issues/659. -func SetRlimit(val uint) { - setRlimit(val) +// SetRlimit sets user-specified limit of how many fd's we can use. +// +// See https://github.com/AdguardTeam/AdGuardHome/internal/issues/659. +func SetRlimit(val uint64) (err error) { + return setRlimit(val) } // HaveAdminRights checks if the current user has root (administrator) rights. diff --git a/internal/aghos/os_bsd.go b/internal/aghos/os_bsd.go index e2e4f21fb2d..07febc930ff 100644 --- a/internal/aghos/os_bsd.go +++ b/internal/aghos/os_bsd.go @@ -7,22 +7,18 @@ package aghos import ( "os" "syscall" - - "github.com/AdguardTeam/golibs/log" ) func canBindPrivilegedPorts() (can bool, err error) { return HaveAdminRights() } -func setRlimit(val uint) { +func setRlimit(val uint64) (err error) { var rlim syscall.Rlimit - rlim.Max = uint64(val) - rlim.Cur = uint64(val) - err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlim) - if err != nil { - log.Error("Setrlimit() failed: %v", err) - } + rlim.Max = val + rlim.Cur = val + + return syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlim) } func haveAdminRights() (bool, error) { diff --git a/internal/aghos/os_freebsd.go b/internal/aghos/os_freebsd.go index f5076d9a092..3c8b1e659e5 100644 --- a/internal/aghos/os_freebsd.go +++ b/internal/aghos/os_freebsd.go @@ -7,22 +7,18 @@ package aghos import ( "os" "syscall" - - "github.com/AdguardTeam/golibs/log" ) func canBindPrivilegedPorts() (can bool, err error) { return HaveAdminRights() } -func setRlimit(val uint) { +func setRlimit(val uint64) (err error) { var rlim syscall.Rlimit rlim.Max = int64(val) rlim.Cur = int64(val) - err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlim) - if err != nil { - log.Error("Setrlimit() failed: %v", err) - } + + return syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlim) } func haveAdminRights() (bool, error) { diff --git a/internal/aghos/os_linux.go b/internal/aghos/os_linux.go index a4ba15ab5f7..0850a5bf6c9 100644 --- a/internal/aghos/os_linux.go +++ b/internal/aghos/os_linux.go @@ -11,7 +11,6 @@ import ( "strings" "syscall" - "github.com/AdguardTeam/golibs/log" "golang.org/x/sys/unix" ) @@ -23,14 +22,12 @@ func canBindPrivilegedPorts() (can bool, err error) { return cnbs == 1 || adm, err } -func setRlimit(val uint) { +func setRlimit(val uint64) (err error) { var rlim syscall.Rlimit - rlim.Max = uint64(val) - rlim.Cur = uint64(val) - err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlim) - if err != nil { - log.Error("Setrlimit() failed: %v", err) - } + rlim.Max = val + rlim.Cur = val + + return syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlim) } func haveAdminRights() (bool, error) { diff --git a/internal/aghos/os_windows.go b/internal/aghos/os_windows.go index 8a39990d3aa..baf7d821c4d 100644 --- a/internal/aghos/os_windows.go +++ b/internal/aghos/os_windows.go @@ -15,7 +15,8 @@ func canBindPrivilegedPorts() (can bool, err error) { return HaveAdminRights() } -func setRlimit(val uint) { +func setRlimit(val uint64) (err error) { + return ErrUnsupported } func haveAdminRights() (bool, error) { diff --git a/internal/home/config.go b/internal/home/config.go index 53b0e303ffc..4a3d7705ebb 100644 --- a/internal/home/config.go +++ b/internal/home/config.go @@ -54,7 +54,7 @@ type configuration struct { AuthBlockMin uint `yaml:"block_auth_min"` ProxyURL string `yaml:"http_proxy"` // Proxy address for our HTTP client Language string `yaml:"language"` // two-letter ISO 639-1 language code - RlimitNoFile uint `yaml:"rlimit_nofile"` // Maximum number of opened fd's per process (0: default) + RlimitNoFile uint64 `yaml:"rlimit_nofile"` // Maximum number of opened fd's per process (0: default) DebugPProf bool `yaml:"debug_pprof"` // Enable pprof HTTP server on port 6060 // TTL for a web session (in hours) diff --git a/internal/home/home.go b/internal/home/home.go index 7b024440b46..82d449e5502 100644 --- a/internal/home/home.go +++ b/internal/home/home.go @@ -115,7 +115,15 @@ func Main(clientBuildFS fs.FS) { }() if args.serviceControlAction != "" { + // TODO(a.garipov): github.com/kardianos/service doesn't seem to + // support OpenBSD currently. Either patch it to do so or make + // our own implementation of the service.System interface. + if runtime.GOOS == "openbsd" { + log.Fatal("service actions are not supported on openbsd") + } + handleServiceControlAction(args, clientBuildFS) + return } @@ -175,7 +183,7 @@ func setupContext(args options) { Context.mux = http.NewServeMux() } -func setupConfig(args options) { +func setupConfig(args options) (err error) { config.DHCP.WorkDir = Context.workDir config.DHCP.HTTPRegister = httpRegister config.DHCP.ConfigModified = onConfigModified @@ -186,7 +194,7 @@ func setupConfig(args options) { // now which assume that the DHCP server can be nil despite this // condition. Inspect them and perhaps rewrite them to use // Enabled() instead. - log.Fatalf("can't initialize dhcp module") + return fmt.Errorf("initing dhcp: %w", err) } Context.updater = updater.NewUpdater(&updater.Config{ @@ -208,9 +216,11 @@ func setupConfig(args options) { Context.clients.Init(config.Clients, Context.dhcpServer, Context.etcHosts) config.Clients = nil - if (runtime.GOOS == "linux" || runtime.GOOS == "darwin") && - config.RlimitNoFile != 0 { - aghos.SetRlimit(config.RlimitNoFile) + if config.RlimitNoFile != 0 { + err = aghos.SetRlimit(config.RlimitNoFile) + if err != nil && !errors.Is(err, aghos.ErrUnsupported) { + return fmt.Errorf("setting rlimit: %w", err) + } } // override bind host/port from the console @@ -223,6 +233,8 @@ func setupConfig(args options) { if len(args.pidFile) != 0 && writePIDFile(args.pidFile) { Context.pidFileName = args.pidFile } + + return nil } func initWeb(args options, clientBuildFS fs.FS) (web *Web, err error) { @@ -266,8 +278,16 @@ func initWeb(args options, clientBuildFS fs.FS) (web *Web, err error) { return web, nil } +func fatalOnError(err error) { + if err != nil { + log.Fatal(err) + } +} + // run performs configurating and starts AdGuard Home. func run(args options, clientBuildFS fs.FS) { + var err error + // configure config filename initConfigFilename(args) @@ -294,14 +314,13 @@ func run(args options, clientBuildFS fs.FS) { // but also avoid relying on automatic Go init() function filtering.InitModule() - setupConfig(args) + err = setupConfig(args) + fatalOnError(err) if !Context.firstRun { // Save the updated config - err := config.write() - if err != nil { - log.Fatal(err) - } + err = config.write() + fatalOnError(err) if config.DebugPProf { mux := http.NewServeMux() @@ -318,7 +337,7 @@ func run(args options, clientBuildFS fs.FS) { } } - err := os.MkdirAll(Context.getDataDir(), 0o755) + err = os.MkdirAll(Context.getDataDir(), 0o755) if err != nil { log.Fatalf("Cannot create DNS data dir at %s: %s", Context.getDataDir(), err) } @@ -352,20 +371,14 @@ func run(args options, clientBuildFS fs.FS) { } Context.web, err = initWeb(args, clientBuildFS) - if err != nil { - log.Fatal(err) - } + fatalOnError(err) Context.subnetDetector, err = aghnet.NewSubnetDetector() - if err != nil { - log.Fatal(err) - } + fatalOnError(err) if !Context.firstRun { err = initDNSServer() - if err != nil { - log.Fatalf("%s", err) - } + fatalOnError(err) Context.tls.Start() Context.etcHosts.Start() @@ -374,7 +387,7 @@ func run(args options, clientBuildFS fs.FS) { serr := startDNSServer() if serr != nil { closeDNSServer() - log.Fatal(serr) + fatalOnError(serr) } }() diff --git a/scripts/install.sh b/scripts/install.sh index c5e4eaca1a7..e715404a0ac 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -2,6 +2,10 @@ # AdGuard Home Installation Script +# Exit the script if a pipeline fails (-e), prevent accidental filename +# expansion (-f), and consider undefined variables as errors (-u). +set -e -f -u + # Function log is an echo wrapper that writes to stderr if the caller # requested verbosity level greater than 0. Otherwise, it does nothing. log() { @@ -54,7 +58,7 @@ check_required() { required="curl" case "$os" in - ('freebsd'|'linux') + ('freebsd'|'linux'|'openbsd') required="$required $required_unix" ;; ('darwin') @@ -63,7 +67,7 @@ check_required() { (*) # Generally shouldn't happen, since the OS has already been # validated. - error_exit "unsupported operating system: $os" + error_exit "unsupported operating system: '$os'" ;; esac @@ -173,14 +177,17 @@ set_os() { os="$( uname -s )" case "$os" in - ('Linux') - os='linux' + ('Darwin') + os='darwin' ;; ('FreeBSD') os='freebsd' ;; - ('Darwin') - os='darwin' + ('Linux') + os='linux' + ;; + ('OpenBSD') + os='openbsd' ;; esac fi @@ -188,7 +195,7 @@ set_os() { # Validate. case "$os" in - ('darwin'|'freebsd'|'linux') + ('darwin'|'freebsd'|'linux'|'openbsd') # All right, go on. ;; (*) @@ -310,12 +317,30 @@ fix_freebsd() { fi } +# Function set_sudo_cmd sets the appropriate command to run a command under +# superuser privileges. +set_sudo_cmd() { + case "$os" + in + ('openbsd') + sudo_cmd='doas' + ;; + ('darwin'|'freebsd'|'linux') + # Go on and use the default, sudo. + ;; + (*) + error_exit "unsupported operating system: '$os'" + ;; + esac +} + # Function configure sets the script's configuration. configure() { set_channel set_os set_cpu fix_darwin + set_sudo_cmd check_out_dir pkg_name="AdGuardHome_${os}_${cpu}.${pkg_ext}" @@ -335,8 +360,7 @@ is_root() { return 0 fi - # TODO(a.garipov): On OpenBSD, use doas. - if is_command sudo + if is_command "$sudo_cmd" then log 'note that AdGuard Home requires root privileges to install using this script' @@ -358,24 +382,25 @@ rerun_with_root() { 'https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh' readonly script_url - flags='' + r='-R' if [ "$reinstall" -eq '1' ] then - flags="${flags} -r" + r='-r' fi + u='-U' if [ "$uninstall" -eq '1' ] then - flags="${flags} -u" + u='-u' fi + v='-V' if [ "$verbose" -eq '1' ] then - flags="${flags} -v" + v='-v' fi - opts="-c $channel -C $cpu -O $os -o $out_dir $flags" - readonly opts + readonly r u v log 'restarting with root privileges' @@ -384,7 +409,7 @@ rerun_with_root() { # following shell to execute to prevent it from getting an empty input # and exiting with a zero code in that case. { curl -L -S -s "$script_url" || echo 'exit 1'; }\ - | sudo sh -s -- $opts + | $sudo_cmd sh -s -- -c "$channel" -C "$cpu" -O "$os" -o "$out_dir" "$r" "$u" "$v" # Exit the script. Since if the code of the previous pipeline is # non-zero, the execution won't reach this point thanks to set -e, exit @@ -504,10 +529,6 @@ install_service() { # Entrypoint -# Exit the script if a pipeline fails (-e), prevent accidental filename -# expansion (-f), and consider undefined variables as errors (-u). -set -e -f -u - # Set default values of configuration variables. channel='release' reinstall='0' @@ -517,6 +538,8 @@ cpu='' os='' out_dir='/opt' pkg_ext='tar.gz' +sudo_cmd='sudo' + parse_opts "$@" echo 'starting AdGuard Home installation script' @@ -541,4 +564,4 @@ install_service echo "\ AdGuard Home is now installed and running you can control the service status with the following commands: -sudo ${agh_dir}/AdGuardHome -s start|stop|restart|status|install|uninstall" +$sudo_cmd ${agh_dir}/AdGuardHome -s start|stop|restart|status|install|uninstall" diff --git a/scripts/make/build-release.sh b/scripts/make/build-release.sh index 2ebde2d5ff5..a53d6f8c180 100644 --- a/scripts/make/build-release.sh +++ b/scripts/make/build-release.sh @@ -156,6 +156,8 @@ linux mips64 0 softfloat 0 linux mips64le 0 softfloat 0 linux mipsle 0 softfloat 0 linux ppc64le 0 0 0 +openbsd amd64 0 0 0 +openbsd arm64 0 0 0 windows 386 0 0 0 windows amd64 0 0 0" readonly platforms