Skip to content

Commit e7558f2

Browse files
authored
build changes (#2356)
* default build includes everything * allow compiling out PRECIS and Skeleton (still included by default) * consistently use `postgresql` in identifiers
1 parent 768c01c commit e7558f2

File tree

17 files changed

+522
-388
lines changed

17 files changed

+522
-388
lines changed

.github/workflows/build.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ jobs:
2222
go-version: "1.26"
2323
- name: "install python3-pytest"
2424
run: "sudo apt install -y python3-pytest python3-websockets"
25-
- name: "make build_full"
26-
run: "make build_full"
25+
- name: "make minimal"
26+
run: "make minimal"
27+
- name: "make build"
28+
run: "make build"
2729
- name: "make install"
2830
run: "make install"
2931
- name: "make test"

Makefile

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ GIT_TAG := $(shell git tag --points-at HEAD 2> /dev/null | head -n 1)
55
# this can be overridden by passing CGO_ENABLED=1 to make
66
export CGO_ENABLED ?= 0
77

8-
capdef_file = ./irc/caps/defs.go
8+
# build tags for the maximalist build with everything included
9+
full_tags = i18n mysql postgresql sqlite
910

10-
# default build tags; override by passing, e.g. ERGO_BUILD_TAGS="mysql postgres"
11-
ERGO_BUILD_TAGS ?= mysql
11+
# build everything by default; override by passing, e.g. ERGO_BUILD_TAGS="mysql postgresql"
12+
ERGO_BUILD_TAGS ?= $(full_tags)
1213

13-
# build tags for the maximalist build with everything included
14-
full_tags = "mysql postgres sqlite"
14+
capdef_file = ./irc/caps/defs.go
1515

1616
.PHONY: all
1717
all: build
@@ -20,31 +20,28 @@ all: build
2020
build:
2121
go build -v -tags "$(ERGO_BUILD_TAGS)" -ldflags "-X main.commit=$(GIT_COMMIT) -X main.version=$(GIT_TAG)"
2222

23-
.PHONY: build_full
24-
build_full:
25-
go build -v -tags $(full_tags) -ldflags "-X main.commit=$(GIT_COMMIT) -X main.version=$(GIT_TAG)"
26-
2723
.PHONY: install
2824
install:
29-
go install -v -tags $(ERGO_BUILD_TAGS) -ldflags "-X main.commit=$(GIT_COMMIT) -X main.version=$(GIT_TAG)"
30-
31-
.PHONY: install_full
32-
install_full:
33-
go install -v -tags $(full_tags) -ldflags "-X main.commit=$(GIT_COMMIT) -X main.version=$(GIT_TAG)"
25+
go install -v -tags "$(ERGO_BUILD_TAGS)" -ldflags "-X main.commit=$(GIT_COMMIT) -X main.version=$(GIT_TAG)"
3426

3527
.PHONY: release
3628
release:
3729
goreleaser --skip=publish --clean
3830

31+
.PHONY: minimal
32+
minimal:
33+
go build -v -tags "" -ldflags "-X main.commit=$(GIT_COMMIT) -X main.version=$(GIT_TAG)"
34+
3935
.PHONY: capdefs
4036
capdefs:
4137
python3 ./gencapdefs.py > ${capdef_file}
4238

4339
.PHONY: test
4440
test:
4541
python3 ./gencapdefs.py | diff - ${capdef_file}
46-
go test -tags $(full_tags) ./...
47-
go vet -tags $(full_tags) ./...
42+
go test -tags "$(full_tags)" ./...
43+
go vet -tags "$(full_tags)" ./...
44+
go vet -tags "" ./...
4845
./.check-gofmt.sh
4946

5047
.PHONY: smoke

README.md

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,18 +81,13 @@ You can also clone this repository and build from source. A quick start guide:
8181
1. Obtain an [up-to-date distribution of the Go language for your OS and architecture](https://golang.org/dl/). Check the output of `go version` to ensure it was installed correctly.
8282
1. Clone the repository.
8383
1. `git checkout stable`
84-
1. To build the default Ergo binary, `make`
85-
1. To instead build an Ergo binary that includes support for all datastores (including PostgreSQL and SQLite), `make build_full`
84+
1. `make`
8685
1. You should now have a binary named `ergo` in the working directory.
8786

88-
For more information, including on build customization, see [docs/BUILD.md](https://github.com/ergochat/ergo/blob/master/docs/BUILD.md).
87+
Ergo vendors all its dependencies, so you will not need to fetch any dependencies remotely. For more information, including on build customization, see [docs/BUILD.md](https://github.com/ergochat/ergo/blob/master/docs/BUILD.md).
8988

9089
For information on contributing to Ergo, see [DEVELOPING.md](https://github.com/ergochat/ergo/blob/master/DEVELOPING.md).
9190

92-
#### Building
93-
94-
You'll need an [up-to-date distribution of the Go language for your OS and architecture](https://golang.org/dl/). Once that's installed (check the output of `go version`), just check out your desired branch or tag and run `make`. This will produce an executable binary named `ergo` in the base directory of the project. (Ergo vendors all its dependencies, so you will not need to fetch any dependencies remotely.)
95-
9691
## Configuration
9792

9893
The default config file [`default.yaml`](default.yaml) helps walk you through what each option means and changes.

docs/BUILD.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,11 @@ The `master` branch is not recommended for production use since it may contain b
3131

3232
By default, Ergo is built with cgo disabled, producing a fully statically linked binary. You can disable this with `export CGO_ENABLED=1` before running `make`.
3333

34-
The default Ergo binary (built with `make` or `make build`) includes support for an in-memory history backend, plus a MySQL history backend. `make build_full` will additionally compile in support for PostgreSQL and SQLite history backends. You can also customize which backends are included, with, e.g. `export ERGO_BUILD_TAGS="mysql sqlite"`.
34+
The default Ergo binary (built with `make` or `make build`) includes support for all optional features. Each optional feature is controlled via a separate build tag; to override the build tags, pass the environment variable `ERGO_BUILD_TAGS` with a space-separated list of tags. (For example, for parity with v2.17.0 and earlier, you can run `ERGO_BUILD_TAGS="i18n mysql" make`. Passing the empty string disables all optional features.)
35+
36+
The supported build tags are:
37+
38+
* `i18n` enables support for non-ASCII casemappings (allowing Unicode in nicknames and channel names). (This was a default feature in Ergo v2.17.0 and earlier, but was not enabled by default at runtime. See the `server.casemapping` value of the config file.)
39+
* `mysql` enables support for MySQL as a persistent history backend. (This was a default feature in v2.17.0 and earlier.)
40+
* `postgresql` enables support for PostgreSQL as a persistent history backend.
41+
* `sqlite` enables support for SQLite as a persistent history backend.

irc/config.go

Lines changed: 20 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
"github.com/ergochat/ergo/irc/connection_limits"
3434
"github.com/ergochat/ergo/irc/custime"
3535
"github.com/ergochat/ergo/irc/email"
36+
"github.com/ergochat/ergo/irc/i18n"
3637
"github.com/ergochat/ergo/irc/isupport"
3738
"github.com/ergochat/ergo/irc/jwt"
3839
"github.com/ergochat/ergo/irc/languages"
@@ -41,7 +42,7 @@ import (
4142
"github.com/ergochat/ergo/irc/mysql"
4243
"github.com/ergochat/ergo/irc/oauth2"
4344
"github.com/ergochat/ergo/irc/passwd"
44-
"github.com/ergochat/ergo/irc/postgres"
45+
"github.com/ergochat/ergo/irc/postgresql"
4546
"github.com/ergochat/ergo/irc/sqlite"
4647
"github.com/ergochat/ergo/irc/utils"
4748
"github.com/ergochat/ergo/irc/webpush"
@@ -447,31 +448,6 @@ func (nr *NickEnforcementMethod) UnmarshalYAML(unmarshal func(interface{}) error
447448
return err
448449
}
449450

450-
func (cm *Casemapping) UnmarshalYAML(unmarshal func(interface{}) error) (err error) {
451-
var orig string
452-
if err = unmarshal(&orig); err != nil {
453-
return err
454-
}
455-
456-
var result Casemapping
457-
switch strings.ToLower(orig) {
458-
case "ascii":
459-
result = CasemappingASCII
460-
case "precis", "rfc7613", "rfc8265":
461-
result = CasemappingPRECIS
462-
case "permissive", "fun":
463-
result = CasemappingPermissive
464-
case "rfc1459":
465-
result = CasemappingRFC1459
466-
case "rfc1459-strict":
467-
result = CasemappingRFC1459Strict
468-
default:
469-
return fmt.Errorf("invalid casemapping value: %s", orig)
470-
}
471-
*cm = result
472-
return nil
473-
}
474-
475451
// OperClassConfig defines a specific operator class.
476452
type OperClassConfig struct {
477453
Title string
@@ -615,7 +591,7 @@ type Config struct {
615591
supportedCaps *caps.Set
616592
supportedCapsWithoutSTS *caps.Set
617593
capValues caps.Values
618-
Casemapping Casemapping
594+
Casemapping i18n.Casemapping
619595
EnforceUtf8 bool `yaml:"enforce-utf8"`
620596
OutputPath string `yaml:"output-path"`
621597
IPCheckScript IPCheckScriptConfig `yaml:"ip-check-script"`
@@ -664,7 +640,7 @@ type Config struct {
664640
Path string
665641
AutoUpgrade bool
666642
MySQL mysql.Config
667-
PostgreSQL postgres.Config
643+
PostgreSQL postgresql.Config
668644
SQLite sqlite.Config
669645
}
670646

@@ -1255,11 +1231,11 @@ func LoadConfig(filename string) (config *Config, err error) {
12551231
}
12561232
}
12571233
if config.Datastore.PostgreSQL.Enabled {
1258-
if !postgres.Enabled {
1234+
if !postgresql.Enabled {
12591235
return nil, fmt.Errorf("PostgreSQL is enabled in the config, but this binary was not built with PostgreSQL support. Rebuild with `make build_full` to enable")
12601236
}
1261-
if config.Limits.NickLen > postgres.MaxTargetLength || config.Limits.ChannelLen > postgres.MaxTargetLength {
1262-
return nil, fmt.Errorf("to use PostgreSQL, nick and channel length limits must be %d or lower", postgres.MaxTargetLength)
1237+
if config.Limits.NickLen > postgresql.MaxTargetLength || config.Limits.ChannelLen > postgresql.MaxTargetLength {
1238+
return nil, fmt.Errorf("to use PostgreSQL, nick and channel length limits must be %d or lower", postgresql.MaxTargetLength)
12631239
}
12641240
}
12651241
if config.Datastore.SQLite.Enabled {
@@ -1377,6 +1353,12 @@ func LoadConfig(filename string) (config *Config, err error) {
13771353
config.Server.capValues[caps.Multiline] = multilineCapValue
13781354
}
13791355

1356+
if !i18n.Enabled {
1357+
if config.Server.Casemapping != i18n.CasemappingASCII {
1358+
return nil, fmt.Errorf("i18n support was compiled out; set casemapping to 'ascii' or recompile")
1359+
}
1360+
}
1361+
13801362
// handle legacy name 'bouncer' for 'multiclient' section:
13811363
if config.Accounts.Bouncer != nil {
13821364
config.Accounts.Multiclient = *config.Accounts.Bouncer
@@ -1711,7 +1693,7 @@ func LoadConfig(filename string) (config *Config, err error) {
17111693
// same machine:
17121694
config.Datastore.MySQL.MaxConns = runtime.NumCPU()
17131695
}
1714-
// do the same for postgres
1696+
// do the same for postgresql
17151697
config.Datastore.PostgreSQL.ExpireTime = time.Duration(config.History.Restrictions.ExpireTime)
17161698
config.Datastore.PostgreSQL.TrackAccountMessages = config.History.Retention.EnableAccountIndexing
17171699
if config.Datastore.PostgreSQL.MaxConns == 0 {
@@ -1835,9 +1817,9 @@ func (config *Config) generateISupport() (err error) {
18351817
switch config.Server.Casemapping {
18361818
default:
18371819
casemappingToken = "ascii" // this is published for ascii, precis, or permissive
1838-
case CasemappingRFC1459:
1820+
case i18n.CasemappingRFC1459:
18391821
casemappingToken = "rfc1459"
1840-
case CasemappingRFC1459Strict:
1822+
case i18n.CasemappingRFC1459Strict:
18411823
casemappingToken = "rfc1459-strict"
18421824
}
18431825
isupport.Add("CASEMAPPING", casemappingToken)
@@ -1876,7 +1858,7 @@ func (config *Config) generateISupport() (err error) {
18761858
isupport.Add("STATUSMSG", "~&@%+")
18771859
isupport.Add("TARGMAX", fmt.Sprintf("NAMES:1,LIST:1,KICK:,WHOIS:1,USERHOST:10,PRIVMSG:%s,TAGMSG:%s,NOTICE:%s,MONITOR:%d", maxTargetsString, maxTargetsString, maxTargetsString, config.Limits.MonitorEntries))
18781860
isupport.Add("TOPICLEN", strconv.Itoa(config.Limits.TopicLen))
1879-
if config.Server.Casemapping == CasemappingPRECIS {
1861+
if config.Server.Casemapping == i18n.CasemappingPRECIS {
18801862
isupport.Add("UTF8MAPPING", precisUTF8MappingToken)
18811863
}
18821864
if config.Server.EnforceUtf8 {
@@ -1953,7 +1935,7 @@ func (config *Config) historyChangedFrom(oldConfig *Config) bool {
19531935
config.History.Persistent != oldConfig.History.Persistent
19541936
}
19551937

1956-
func compileGuestRegexp(guestFormat string, casemapping Casemapping) (standard, folded *regexp.Regexp, err error) {
1938+
func compileGuestRegexp(guestFormat string, casemapping i18n.Casemapping) (standard, folded *regexp.Regexp, err error) {
19571939
if strings.Count(guestFormat, "?") != 0 || strings.Count(guestFormat, "*") != 1 {
19581940
err = errors.New("guest format must contain 1 '*' and no '?'s")
19591941
return
@@ -1967,11 +1949,11 @@ func compileGuestRegexp(guestFormat string, casemapping Casemapping) (standard,
19671949
starIndex := strings.IndexByte(guestFormat, '*')
19681950
initial := guestFormat[:starIndex]
19691951
final := guestFormat[starIndex+1:]
1970-
initialFolded, err := casefoldWithSetting(initial, casemapping)
1952+
initialFolded, err := i18n.CasefoldWithSetting(initial, casemapping)
19711953
if err != nil {
19721954
return
19731955
}
1974-
finalFolded, err := casefoldWithSetting(final, casemapping)
1956+
finalFolded, err := i18n.CasefoldWithSetting(final, casemapping)
19751957
if err != nil {
19761958
return
19771959
}

irc/errors.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,8 @@ var (
8282

8383
// String Errors
8484
var (
85-
errCouldNotStabilize = errors.New("Could not stabilize string while casefolding")
86-
errStringIsEmpty = errors.New("String is empty")
87-
errInvalidCharacter = errors.New("Invalid character")
85+
errStringIsEmpty = errors.New("String is empty")
86+
errInvalidCharacter = errors.New("Invalid character")
8887
)
8988

9089
type CertKeyError struct {

irc/i18n/common.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package i18n
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"strings"
7+
)
8+
9+
// Casemapping represents a set of algorithm for case normalization
10+
// and confusables prevention for IRC identifiers (nicknames and channel names)
11+
type Casemapping uint
12+
13+
const (
14+
// "precis" is the default / zero value:
15+
// casefolding/validation: PRECIS + ircd restrictions (like no *)
16+
// confusables detection: standard skeleton algorithm
17+
CasemappingPRECIS Casemapping = iota
18+
// "ascii" is the traditional ircd behavior:
19+
// casefolding/validation: must be pure ASCII and follow ircd restrictions, ASCII lowercasing
20+
// confusables detection: none
21+
CasemappingASCII
22+
// "permissive" is an insecure mode:
23+
// casefolding/validation: arbitrary unicodes that follow ircd restrictions, unicode casefolding
24+
// confusables detection: standard skeleton algorithm (which may be ineffective
25+
// over the larger set of permitted identifiers)
26+
CasemappingPermissive
27+
// rfc1459 is a legacy mapping as defined here: https://modern.ircdocs.horse/#casemapping-parameter
28+
CasemappingRFC1459
29+
// rfc1459-strict is a legacy mapping as defined here: https://modern.ircdocs.horse/#casemapping-parameter
30+
CasemappingRFC1459Strict
31+
)
32+
33+
var (
34+
errInvalidCharacter = errors.New("Invalid character")
35+
)
36+
37+
func (cm *Casemapping) UnmarshalYAML(unmarshal func(interface{}) error) (err error) {
38+
var orig string
39+
if err = unmarshal(&orig); err != nil {
40+
return err
41+
}
42+
43+
var result Casemapping
44+
switch strings.ToLower(orig) {
45+
case "ascii":
46+
result = CasemappingASCII
47+
case "precis", "rfc7613", "rfc8265":
48+
result = CasemappingPRECIS
49+
case "permissive", "fun":
50+
result = CasemappingPermissive
51+
case "rfc1459":
52+
result = CasemappingRFC1459
53+
case "rfc1459-strict":
54+
result = CasemappingRFC1459Strict
55+
default:
56+
return fmt.Errorf("invalid casemapping value: %s", orig)
57+
}
58+
*cm = result
59+
return nil
60+
}
61+
62+
func isPrintableASCII(str string) bool {
63+
for i := 0; i < len(str); i++ {
64+
// allow space here because it's technically printable;
65+
// it will be disallowed later by CasefoldName/CasefoldChannel
66+
chr := str[i]
67+
if chr < ' ' || chr > '~' {
68+
return false
69+
}
70+
}
71+
return true
72+
}
73+
74+
func foldASCII(str string) (result string, err error) {
75+
if !isPrintableASCII(str) {
76+
return "", errInvalidCharacter
77+
}
78+
return strings.ToLower(str), nil
79+
}

0 commit comments

Comments
 (0)