package firewall import ( "context" "fmt" "os" "os/exec" "time" ) // applyTable writes ruleset to a temp file and loads it with `nft -f`, // after first deleting the given table (ignoring the error - the table may // not exist yet on first apply). Only ever touches that single table, // never any other nftables state. func applyTable(tableName, ruleset string) (string, error) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() // best-effort: drop the previous version of this table so reapplying // is idempotent. Error ignored - table may not exist yet. _ = exec.CommandContext(ctx, "nft", "delete", "table", "inet", tableName).Run() tmpFile, err := os.CreateTemp("", "wg-ui-multi-fw-*.nft") if err != nil { return "", fmt.Errorf("cannot create temp ruleset file: %w", err) } defer os.Remove(tmpFile.Name()) if _, err := tmpFile.WriteString(ruleset); err != nil { tmpFile.Close() return "", fmt.Errorf("cannot write temp ruleset file: %w", err) } if err := tmpFile.Close(); err != nil { return "", fmt.Errorf("cannot close temp ruleset file: %w", err) } cmd := exec.CommandContext(ctx, "nft", "-f", tmpFile.Name()) out, err := cmd.CombinedOutput() if err != nil { return string(out), fmt.Errorf("nft -f failed: %w", err) } return string(out), nil } // Apply loads a single server's ruleset live, scoped to TableName(serverID). func Apply(serverID, ruleset string) (string, error) { return applyTable(TableName(serverID), ruleset) } // ApplyGlobal loads the host-wide allow/block list ruleset live, scoped to // GlobalTableName. func ApplyGlobal(ruleset string) (string, error) { return applyTable(GlobalTableName, ruleset) }