package firewall import ( "context" "errors" "fmt" ) // NewManager gets a firewall manager for this server, probing the higher-level // managers first: firewalld, then ufw/csf/apf, then plain iptables, and // nftables last, so a manager that is itself backed by iptables or nftables is // preferred over managing its tables behind its back. The context bounds the // detection probes (each shells out or opens a D-Bus/systemd connection). func NewManager(ctx context.Context, rulePrefix string) (Manager, error) { // A probe error usually means "not installed", but it can also be a // transient failure on a host whose firewall IS that manager (a D-Bus // hiccup with firewalld running); every probe's reason is carried in the // final error so a mis-detection is diagnosable. var errs []error probes := []struct { name string try func() (Manager, error) }{ {"firewalld", func() (Manager, error) { return NewFirewallD(ctx, rulePrefix) }}, {"ufw", func() (Manager, error) { return NewUFW(ctx, rulePrefix) }}, {"csf", func() (Manager, error) { return NewCSF(ctx, rulePrefix) }}, {"apf", func() (Manager, error) { return NewAPF(ctx, rulePrefix) }}, {"iptables", func() (Manager, error) { return NewIPTables(ctx, rulePrefix) }}, {"nftables", func() (Manager, error) { return NewNFT(ctx, rulePrefix) }}, } for _, p := range probes { mgr, err := p.try() if err == nil { return mgr, nil } errs = append(errs, fmt.Errorf("%s: %w", p.name, err)) } return nil, fmt.Errorf("no firewall manager found: %w", errors.Join(errs...)) }