Compare commits

...

10 Commits

Author SHA1 Message Date
eum3l f468921257
Merge 40a1fb707d into 1de95ed53e 2024-05-09 14:55:23 +08:00
Toby 1de95ed53e
Merge pull request #136 from apernet/wip-pcapgo
Add pcap back
2024-05-08 17:40:55 -07:00
Haruue 1934c065ec
feat(pcap): impl realtime wait() with time offset 2024-05-08 19:45:10 +08:00
Haruue 301f9af3d4
Revert "ci: install pcap for build 2"
This reverts commit 0daaa32fc6.
2024-05-08 19:26:19 +08:00
Haruue cb0427bfbb
Revert "ci: install pcap for build"
This reverts commit 5e15fd6dd9.
2024-05-08 19:26:15 +08:00
Haruue 7456e5907e
refactor(pcap): switch to pcapgo 2024-05-08 19:22:17 +08:00
Haruue 8cab86b924
Reapply "Merge pull request #132 from eddc005/feat-pcap"
This reverts commit 2ac8783eb6.
2024-05-08 19:13:49 +08:00
eum3l 40a1fb707d fix: allow unsupported systems; opengfw pname 2024-02-18 16:04:39 +01:00
eum3l 9edaba0f8f Provide for eachDefaultSystem (flake-utils); Add hydraJobs 2024-02-18 14:41:57 +01:00
eum3l aca830d189 feat: add Nix package and NixOS module 2024-02-17 20:34:01 +01:00
10 changed files with 656 additions and 10 deletions

View File

@ -43,6 +43,7 @@ var logger *zap.Logger
// Flags
var (
cfgFile string
pcapFile string
logLevel string
logFormat string
)
@ -118,6 +119,7 @@ func init() {
func initFlags() {
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file")
rootCmd.PersistentFlags().StringVarP(&pcapFile, "pcap", "p", "", "pcap file (optional)")
rootCmd.PersistentFlags().StringVarP(&logLevel, "log-level", "l", envOrDefaultString(appLogLevelEnv, "info"), "log level")
rootCmd.PersistentFlags().StringVarP(&logFormat, "log-format", "f", envOrDefaultString(appLogFormatEnv, "console"), "log format")
}
@ -167,6 +169,7 @@ type cliConfig struct {
IO cliConfigIO `mapstructure:"io"`
Workers cliConfigWorkers `mapstructure:"workers"`
Ruleset cliConfigRuleset `mapstructure:"ruleset"`
Replay cliConfigReplay `mapstructure:"replay"`
}
type cliConfigIO struct {
@ -177,6 +180,10 @@ type cliConfigIO struct {
RST bool `mapstructure:"rst"`
}
type cliConfigReplay struct {
Realtime bool `mapstructure:"realtime"`
}
type cliConfigWorkers struct {
Count int `mapstructure:"count"`
QueueSize int `mapstructure:"queueSize"`
@ -197,17 +204,30 @@ func (c *cliConfig) fillLogger(config *engine.Config) error {
}
func (c *cliConfig) fillIO(config *engine.Config) error {
nfio, err := io.NewNFQueuePacketIO(io.NFQueuePacketIOConfig{
QueueSize: c.IO.QueueSize,
ReadBuffer: c.IO.ReadBuffer,
WriteBuffer: c.IO.WriteBuffer,
Local: c.IO.Local,
RST: c.IO.RST,
})
var ioImpl io.PacketIO
var err error
if pcapFile != "" {
// Setup IO for pcap file replay
logger.Info("replaying from pcap file", zap.String("pcap file", pcapFile))
ioImpl, err = io.NewPcapPacketIO(io.PcapPacketIOConfig{
PcapFile: pcapFile,
Realtime: c.Replay.Realtime,
})
} else {
// Setup IO for nfqueue
ioImpl, err = io.NewNFQueuePacketIO(io.NFQueuePacketIOConfig{
QueueSize: c.IO.QueueSize,
ReadBuffer: c.IO.ReadBuffer,
WriteBuffer: c.IO.WriteBuffer,
Local: c.IO.Local,
RST: c.IO.RST,
})
}
if err != nil {
return configError{Field: "io", Err: err}
}
config.IO = nfio
config.IO = ioImpl
return nil
}

View File

@ -58,12 +58,17 @@ func (e *engine) UpdateRuleset(r ruleset.Ruleset) error {
}
func (e *engine) Run(ctx context.Context) error {
workerCtx, workerCancel := context.WithCancel(ctx)
defer workerCancel() // Stop workers
// Register IO shutdown
ioCtx, ioCancel := context.WithCancel(ctx)
defer ioCancel() // Stop workers & IO
e.io.SetCancelFunc(ioCancel)
defer ioCancel() // Stop IO
// Start workers
for _, w := range e.workers {
go w.Run(ioCtx)
go w.Run(workerCtx)
}
// Register IO callback
@ -85,6 +90,8 @@ func (e *engine) Run(ctx context.Context) error {
return err
case <-ctx.Done():
return nil
case <-ioCtx.Done():
return nil
}
}

130
flake.lock Normal file
View File

@ -0,0 +1,130 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1705309234,
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1694529238,
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"gomod2nix": {
"inputs": {
"flake-utils": "flake-utils_2",
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1705314449,
"narHash": "sha256-yfQQ67dLejP0FLK76LKHbkzcQqNIrux6MFe32MMFGNQ=",
"owner": "nix-community",
"repo": "gomod2nix",
"rev": "30e3c3a9ec4ac8453282ca7f67fca9e1da12c3e6",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "gomod2nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1658285632,
"narHash": "sha256-zRS5S/hoeDGUbO+L95wXG9vJNwsSYcl93XiD0HQBXLk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "5342fc6fb59d0595d26883c3cadff16ce58e44f3",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "master",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1701282334,
"narHash": "sha256-MxCVrXY6v4QmfTwIysjjaX0XUhqBbxTWWB4HXtDYsdk=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "057f9aecfb71c4437d2b27d3323df7f93c010b7e",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "23.11",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"gomod2nix": "gomod2nix",
"nixpkgs": "nixpkgs_2"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

49
flake.nix Normal file
View File

@ -0,0 +1,49 @@
{
description = "OpenGFW is a flexible, easy-to-use, open source implementation of GFW on Linux.";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=23.11";
gomod2nix.url = "github:nix-community/gomod2nix";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = {
self,
nixpkgs,
gomod2nix,
flake-utils,
}:
flake-utils.lib.eachDefaultSystem (
system: let
pkgs = import nixpkgs {
inherit system;
config.allowUnsupportedSystem = true;
overlays = [
gomod2nix.overlays.default
];
};
in {
packages = rec {
opengfw = pkgs.callPackage ./nix/package.nix {};
default = opengfw;
};
devShells.default = pkgs.mkShell {
OPENGFW_LOG_LEVEL = "debug";
buildInputs = let
goEnv = pkgs.mkGoEnv {pwd = ./.;};
in [
goEnv
pkgs.gomod2nix
];
};
}
)
// {
nixosModules.opengfw = import ./nix/module.nix self.packages;
hydraJobs = {
inherit (self) packages;
};
};
}

117
gomod2nix.toml Normal file
View File

@ -0,0 +1,117 @@
schema = 3
[mod]
[mod."github.com/bwmarrin/snowflake"]
version = "v0.3.0"
hash = "sha256-mOU/CgyC9W8XwuinYTPOH69wJY/f+nFVZjZ8lAr9fGM="
[mod."github.com/coreos/go-iptables"]
version = "v0.7.0"
hash = "sha256-6zG74a2qK8NnmKGL3Cv1IGDP/MSY/PYZpEcJT8bl7JU="
[mod."github.com/davecgh/go-spew"]
version = "v1.1.2-0.20180830191138-d8f796af33cc"
hash = "sha256-fV9oI51xjHdOmEx6+dlq7Ku2Ag+m/bmbzPo6A4Y74qc="
[mod."github.com/expr-lang/expr"]
version = "v1.15.7"
hash = "sha256-mQ8LOe+9hu72XTlQdymBrUsMBA7JB7S1G7Nob9hZNfo="
[mod."github.com/florianl/go-nfqueue"]
version = "v1.3.2-0.20231218173729-f2bdeb033acf"
hash = "sha256-KErxiIfDWHXrvJp0yweQ59M6lwmsY2McyKFaNVD6ifg="
[mod."github.com/fsnotify/fsnotify"]
version = "v1.7.0"
hash = "sha256-MdT2rQyQHspPJcx6n9ozkLbsktIOJutOqDuKpNAtoZY="
[mod."github.com/google/go-cmp"]
version = "v0.5.9"
hash = "sha256-lQc4O00R3QSMGs9LP8Sy7A9kj0cqV5rrUdpnGeipIyg="
[mod."github.com/google/gopacket"]
version = "v1.1.19"
hash = "sha256-EueA6b+c7SWJjII3HiFnMMKj3mIbAdzboJo6HBZnkkc="
[mod."github.com/hashicorp/golang-lru/v2"]
version = "v2.0.7"
hash = "sha256-t1bcXLgrQNOYUVyYEZ0knxcXpsTk4IuJZDjKvyJX75g="
[mod."github.com/hashicorp/hcl"]
version = "v1.0.0"
hash = "sha256-xsRCmYyBfglMxeWUvTZqkaRLSW+V2FvNodEDjTGg1WA="
[mod."github.com/inconshreveable/mousetrap"]
version = "v1.1.0"
hash = "sha256-XWlYH0c8IcxAwQTnIi6WYqq44nOKUylSWxWO/vi+8pE="
[mod."github.com/josharian/native"]
version = "v1.0.0"
hash = "sha256-ub4WrKNlMAvgn+OPuJYeJJYLyjqeTGDHA97RTzUC6+Y="
[mod."github.com/magiconair/properties"]
version = "v1.8.7"
hash = "sha256-XQ2bnc2s7/IH3WxEO4GishZurMyKwEclZy1DXg+2xXc="
[mod."github.com/mdlayher/netlink"]
version = "v1.6.0"
hash = "sha256-hFm39JacQH6ed6l/Xn10eP8pFVO0BK8a9phdgtj89f8="
[mod."github.com/mdlayher/socket"]
version = "v0.1.1"
hash = "sha256-g3GB7weGxxw8eJmD6VDY6os+zRJEZrmOLVscHGbDVXs="
[mod."github.com/mitchellh/mapstructure"]
version = "v1.5.0"
hash = "sha256-ztVhGQXs67MF8UadVvG72G3ly0ypQW0IRDdOOkjYwoE="
[mod."github.com/pelletier/go-toml/v2"]
version = "v2.1.0"
hash = "sha256-0u6oV8YMM26y2bw1oe3gLmEJc/whpNaFtEe4yOkN24c="
[mod."github.com/pmezard/go-difflib"]
version = "v1.0.1-0.20181226105442-5d4384ee4fb2"
hash = "sha256-XA4Oj1gdmdV/F/+8kMI+DBxKPthZ768hbKsO3d9Gx90="
[mod."github.com/sagikazarmark/locafero"]
version = "v0.4.0"
hash = "sha256-7I1Oatc7GAaHgAqBFO6Tv4IbzFiYeU9bJAfJhXuWaXk="
[mod."github.com/sagikazarmark/slog-shim"]
version = "v0.1.0"
hash = "sha256-F92BQXXmn3mCwu3mBaGh+joTRItQDSDhsjU6SofkYdA="
[mod."github.com/sourcegraph/conc"]
version = "v0.3.0"
hash = "sha256-mIdMs9MLAOBKf3/0quf1iI3v8uNWydy7ae5MFa+F2Ko="
[mod."github.com/spf13/afero"]
version = "v1.11.0"
hash = "sha256-+rV3cDZr13N8E0rJ7iHmwsKYKH+EhV+IXBut+JbBiIE="
[mod."github.com/spf13/cast"]
version = "v1.6.0"
hash = "sha256-hxioqRZfXE0AE5099wmn3YG0AZF8Wda2EB4c7zHF6zI="
[mod."github.com/spf13/cobra"]
version = "v1.8.0"
hash = "sha256-oAE+fEaRfZPE541IPWE0GMeBBYgH2DMhtZNxzp7DFlY="
[mod."github.com/spf13/pflag"]
version = "v1.0.5"
hash = "sha256-w9LLYzxxP74WHT4ouBspH/iQZXjuAh2WQCHsuvyEjAw="
[mod."github.com/spf13/viper"]
version = "v1.18.2"
hash = "sha256-MXYbK6w1LEaoZ2/L/STF3WU1tbK+7NwGVxUCLKPkwks="
[mod."github.com/stretchr/testify"]
version = "v1.8.4"
hash = "sha256-MoOmRzbz9QgiJ+OOBo5h5/LbilhJfRUryvzHJmXAWjo="
[mod."github.com/subosito/gotenv"]
version = "v1.6.0"
hash = "sha256-LspbjTniiq2xAICSXmgqP7carwlNaLqnCTQfw2pa80A="
[mod."go.uber.org/multierr"]
version = "v1.11.0"
hash = "sha256-Lb6rHHfR62Ozg2j2JZy3MKOMKdsfzd1IYTR57r3Mhp0="
[mod."go.uber.org/zap"]
version = "v1.26.0"
hash = "sha256-EUQnALSDtoJryWp01K/PMbRUvQYG1uDbqGnlJ/7thE4="
[mod."golang.org/x/exp"]
version = "v0.0.0-20230905200255-921286631fa9"
hash = "sha256-CyeVwjp12Wqh4ptqfi3KHCWPzOFhE8fSrP3sMjMXvec="
[mod."golang.org/x/net"]
version = "v0.19.0"
hash = "sha256-3M5rKEvJx4cO/q+06cGjR5sxF5JpnUWY0+fQttrWdT4="
[mod."golang.org/x/sync"]
version = "v0.5.0"
hash = "sha256-EAKeODSsct5HhXPmpWJfulKSCkuUu6kkDttnjyZMNcI="
[mod."golang.org/x/sys"]
version = "v0.15.0"
hash = "sha256-n7TlABF6179RzGq3gctPDKDPRtDfnwPdjNCMm8ps2KY="
[mod."golang.org/x/text"]
version = "v0.14.0"
hash = "sha256-yh3B0tom1RfzQBf1RNmfdNWF1PtiqxV41jW1GVS6JAg="
[mod."google.golang.org/protobuf"]
version = "v1.31.0"
hash = "sha256-UdIk+xRaMfdhVICvKRk1THe3R1VU+lWD8hqoW/y8jT0="
[mod."gopkg.in/ini.v1"]
version = "v1.67.0"
hash = "sha256-V10ahGNGT+NLRdKUyRg1dos5RxLBXBk1xutcnquc/+4="
[mod."gopkg.in/yaml.v3"]
version = "v3.0.1"
hash = "sha256-FqL9TKYJ0XkNwJFnq9j0VvJ5ZUU1RvH/52h/f5bkYAU="

View File

@ -48,6 +48,9 @@ type PacketIO interface {
ProtectedDialContext(ctx context.Context, network, address string) (net.Conn, error)
// Close closes the packet IO.
Close() error
// SetCancelFunc gives packet IO access to context cancel function, enabling it to
// trigger a shutdown
SetCancelFunc(cancelFunc context.CancelFunc) error
}
type ErrInvalidPacket struct {

View File

@ -281,6 +281,11 @@ func (n *nfqueuePacketIO) Close() error {
return n.n.Close()
}
// nfqueue IO does not issue shutdown
func (n *nfqueuePacketIO) SetCancelFunc(cancelFunc context.CancelFunc) error {
return nil
}
func (n *nfqueuePacketIO) setupNft(local, rst, remove bool) error {
rules, err := generateNftRules(local, rst)
if err != nil {

136
io/pcap.go Normal file
View File

@ -0,0 +1,136 @@
package io
import (
"context"
"hash/crc32"
"io"
"net"
"os"
"sort"
"strings"
"time"
"github.com/google/gopacket"
"github.com/google/gopacket/pcapgo"
)
var _ PacketIO = (*pcapPacketIO)(nil)
type pcapPacketIO struct {
pcapFile io.ReadCloser
pcap *pcapgo.Reader
timeOffset *time.Duration
ioCancel context.CancelFunc
config PcapPacketIOConfig
dialer *net.Dialer
}
type PcapPacketIOConfig struct {
PcapFile string
Realtime bool
}
func NewPcapPacketIO(config PcapPacketIOConfig) (PacketIO, error) {
pcapFile, err := os.Open(config.PcapFile)
if err != nil {
return nil, err
}
handle, err := pcapgo.NewReader(pcapFile)
if err != nil {
return nil, err
}
return &pcapPacketIO{
pcapFile: pcapFile,
pcap: handle,
timeOffset: nil,
ioCancel: nil,
config: config,
dialer: &net.Dialer{},
}, nil
}
func (p *pcapPacketIO) Register(ctx context.Context, cb PacketCallback) error {
go func() {
packetSource := gopacket.NewPacketSource(p.pcap, p.pcap.LinkType())
for packet := range packetSource.Packets() {
p.wait(packet)
networkLayer := packet.NetworkLayer()
if networkLayer != nil {
src, dst := networkLayer.NetworkFlow().Endpoints()
endpoints := []string{src.String(), dst.String()}
sort.Strings(endpoints)
id := crc32.Checksum([]byte(strings.Join(endpoints, ",")), crc32.IEEETable)
cb(&pcapPacket{
streamID: id,
timestamp: packet.Metadata().Timestamp,
data: packet.LinkLayer().LayerPayload(),
}, nil)
}
}
// Give the workers a chance to finish everything
time.Sleep(time.Second)
// Stop the engine when all packets are finished
p.ioCancel()
}()
return nil
}
// A normal dialer is sufficient as pcap IO does not mess up with the networking
func (p *pcapPacketIO) ProtectedDialContext(ctx context.Context, network, address string) (net.Conn, error) {
return p.dialer.DialContext(ctx, network, address)
}
func (p *pcapPacketIO) SetVerdict(pkt Packet, v Verdict, newPacket []byte) error {
return nil
}
func (p *pcapPacketIO) SetCancelFunc(cancelFunc context.CancelFunc) error {
p.ioCancel = cancelFunc
return nil
}
func (p *pcapPacketIO) Close() error {
return p.pcapFile.Close()
}
// Intentionally slow down the replay
// In realtime mode, this is to match the timestamps in the capture
func (p *pcapPacketIO) wait(packet gopacket.Packet) {
if !p.config.Realtime {
return
}
if p.timeOffset == nil {
offset := time.Since(packet.Metadata().Timestamp)
p.timeOffset = &offset
} else {
t := time.Until(packet.Metadata().Timestamp.Add(*p.timeOffset))
time.Sleep(t)
}
}
var _ Packet = (*pcapPacket)(nil)
type pcapPacket struct {
streamID uint32
timestamp time.Time
data []byte
}
func (p *pcapPacket) StreamID() uint32 {
return p.streamID
}
func (p *pcapPacket) Timestamp() time.Time {
return p.timestamp
}
func (p *pcapPacket) Data() []byte {
return p.data
}

147
nix/module.nix Normal file
View File

@ -0,0 +1,147 @@
packages: {
lib,
pkgs,
config,
...
}: let
inherit (lib) mkOption types mkIf;
cfg = config.services.opengfw;
settings =
if cfg.settings != {}
then (pkgs.formats.yaml {}).generate "OpenGFW-Config.yaml" cfg.settings
else cfg.settingsFile;
rules =
if cfg.rules != []
then (pkgs.formats.yaml {}).generate "OpenGFW-Rules.yaml" cfg.rules
else cfg.rulesFile;
in {
options.services.opengfw = {
enable = lib.mkEnableOption (lib.mdDoc "A flexible, easy-to-use, open source implementation of GFW on Linux.");
package = lib.mkPackageOption packages.${pkgs.system} "opengfw" {
default = "opengfw";
};
user = mkOption {
default = "opengfw";
type = types.singleLineStr;
};
dir = mkOption {
default = "/var/lib/opengfw";
type = types.singleLineStr;
};
rulesFile = mkOption {
default = null;
type = types.nullOr types.path;
};
settingsFile = mkOption {
default = null;
type = types.nullOr types.path;
};
settings = mkOption {
default = {};
type = types.attrs;
example = {
io = {
queueSize = 1024;
local = true;
};
workers = {
count = 4;
queueSize = 16;
tcpMaxBufferedPagesTotal = 4096;
tcpMaxBufferedPagesPerConn = 64;
udpMaxStreams = 4096;
};
};
};
rules = mkOption {
default = [];
type = types.listOf types.attrs;
description = ''
OpenGFW supports the actions 'allow', 'block', 'drop' and 'modify'
as listed on https://github.com/apernet/OpenGFW?tab=readme-ov-file#supported-actions.
It uses Expr Language (https://expr-lang.org/docs/language-definition).
Properties of the analyzers are documented here: https://github.com/apernet/OpenGFW/blob/master/docs/Analyzers.md.
'';
example = [
{
name = "block v2ex http";
action = "block";
expr = ''string(http?.req?.headers?.host) endsWith "v2ex.com"'';
}
{
name = "block google socks";
action = "block";
expr = ''string(socks?.req?.addr) endsWith "google.com" && socks?.req?.port == 80'';
}
{
name = "v2ex dns poisoning";
action = "modify";
modifier = {
name = "dns";
args = {
a = "0.0.0.0";
aaaa = "::";
};
};
expr = ''dns != nil && dns.qr && any(dns.questions, {.name endsWith "v2ex.com"})'';
}
];
};
};
config = mkIf cfg.enable {
security.wrappers.OpenGFW = {
owner = cfg.user;
group = cfg.user;
capabilities = "cap_net_admin+ep";
source = "${cfg.package}/bin/OpenGFW";
};
systemd = {
services.opengfw = let
cu = "${pkgs.coreutils}/bin";
in {
description = "OpenGFW";
wantedBy = ["multi-user.target"];
after = ["network.target"];
environment.PATH = lib.mkForce "${cu}:${pkgs.iptables}/bin";
preStart = mkIf ((cfg.rules != [] && cfg.settings != {}) || (cfg.rulesFile != null && cfg.settingsFile != null)) ''
${cu}/ln -sf ${settings} config.yaml
${cu}/ln -sf ${rules} rules.yaml
'';
serviceConfig = {
WorkingDirectory = cfg.dir;
ExecStart = "${config.security.wrapperDir}/OpenGFW -c config.yaml rules.yaml";
ExecReload = "${cu}/kill -HUP $MAINPID";
Restart = "always";
User = cfg.user;
};
};
tmpfiles.rules = [
"d '${cfg.dir}' 0660 ${cfg.user} ${cfg.user} - -"
];
};
users = {
users.${cfg.user} = {
description = "opengfw user";
isNormalUser = true;
group = cfg.user;
home = cfg.dir;
};
groups.${cfg.user} = {};
};
};
}

32
nix/package.nix Normal file
View File

@ -0,0 +1,32 @@
{
lib,
pkgs,
}:
pkgs.buildGoApplication rec {
pname = "opengfw";
version = "0.1.1";
pwd = ../.;
src = ../.;
buildPhase = ''
go build
'';
installPhase = ''
mkdir -p $out/bin
cp OpenGFW $out/bin
'';
meta = with lib; {
description = "A flexible, easy-to-use, open source implementation of GFW on Linux.";
longDescription = ''
OpenGFW is a flexible, easy-to-use, open source implementation of GFW on Linux
that's in many ways more powerful than the real thing.
It's cyber sovereignty you can have on a home router.
'';
homepage = "https://github.com/apernet/OpenGFW";
license = licenses.mpl20;
mainProgram = "OpenGFW";
platforms = [ "x86_64-linux" "aarch64-linux" ];
};
}