aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/docker.yaml1
-rw-r--r--.golangci.yml1
-rw-r--r--README.md185
-rw-r--r--docker-compose-postgres.yaml102
-rw-r--r--go.mod4
-rw-r--r--go.sum10
-rw-r--r--main.go216
7 files changed, 498 insertions, 21 deletions
diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml
index 71b2988..33354ce 100644
--- a/.github/workflows/docker.yaml
+++ b/.github/workflows/docker.yaml
@@ -35,6 +35,7 @@ jobs:
context: .
file: ./Dockerfile
push: true
+ sbom: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
provenance: mode=max
diff --git a/.golangci.yml b/.golangci.yml
index 8a79a07..42f9a39 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -17,3 +17,4 @@ linters-settings:
- github.com/lrstanley/girc
- github.com/sashabaranov/go-openai
- github.com/BurntSushi/toml
+ - github.com/jackc/pgx/v5/pgxpool
diff --git a/README.md b/README.md
index ee79bdc..a0337f0 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# milla
-Milla is an IRC bot that sends things over to an LLM when you ask it questions and prints the answer with optional syntax-hilighting.<br/>
+Milla is an IRC bot that sends things over to an LLM when you ask it questions and prints the answer with optional syntax-highlighting.<br/>
Currently Supported:
- Ollama
@@ -39,7 +39,7 @@ The SASL username.
#### ircSaslPass
-The SASL password for SASL plain authentication.
+The SASL password for SASL plain authentication. Can also be passed as and environment variable.
#### ollamaEndpoint
@@ -77,7 +77,7 @@ Which LLM provider to use. The supported options are:
#### apikey
-The apikey to use for the LLM provider.
+The apikey to use for the LLM provider. Can also be passed as and environment variable.
#### ollamaSystem
@@ -89,7 +89,7 @@ The path to the client certificate to use for client cert authentication.
#### serverPass
-The password to use for the IRC server the bot is trying to connect to if the server has a password.
+The password to use for the IRC server the bot is trying to connect to if the server has a password. Can also be passed as and environment variable.
#### bind
@@ -169,6 +169,38 @@ List of channels for the bot to join when it connects to the server.
ircChannels = ["#channel1", "#channel2"]
```
+### databaseUser
+
+Name of the database user. Can also be passed an an environment variable.
+
+### databasePassword
+
+Password for the database user. Can also be passed an an environment variable.
+
+### databaseAddress
+
+Address of the database. Can also be passed as and environment variable.
+
+### databaseName
+
+Name of the database. Can also be passed as and environment variable.
+
+### ircProxy
+
+Determines which proxy to use to connect to the irc network:
+
+```
+ircProxy = "socks5://127.0.0.1:9050"
+```
+
+### llmProxy
+
+Determines which proxy to use to connect to the LLM endpoint:
+
+```
+llmProxy = "socks5://127.0.0.1:9050"
+```
+
## Commands
#### help
@@ -187,6 +219,20 @@ Get the value of all config options.
Set a config option on the fly. Use the same name as the config file but capitalized.
+#### memstats
+
+Returns memory stats for milla.
+
+## Environment Variables
+
+- MILLA_SASL_PASSWORD
+- MILLA_SERVER_PASSWORD
+- MILLA_APIKEY
+- MILLA_DB_USER
+- MILLA_DB_PASSWORD
+- MILLA_DB_ADDRESS
+- MILLA_DB_NAME
+
## Proxy Support
milla will read and use the `ALL_PROXY` environment variable.
@@ -200,6 +246,9 @@ ALL_PROXY=127.0.0.1:9050
## Deploy
+### Docker
+
+Images are automatically pushed to dockerhub. So you can get it from [there](https://hub.docker.com/r/terminaldweller/milla).
An example docker compose file is provided in the repo under `docker-compose.yaml`.
milla can be used with [gvisor](https://gvisor.dev/)'s docker runtime, `runsc`.
@@ -234,15 +283,141 @@ networks:
driver: bridge
```
+### Public Message Storage
+
+milla can be configured to store all incoming public messages for future use in a postgres database. An example docker compose file is provided under `docker-compose-postgres.yaml`.<br/>
+
+```yaml
+services:
+ terra:
+ image: milla_distroless_vendored
+ build:
+ context: .
+ dockerfile: ./Dockerfile_distroless_vendored
+ deploy:
+ resources:
+ limits:
+ memory: 128M
+ logging:
+ driver: "json-file"
+ options:
+ max-size: "100m"
+ networks:
+ - terranet
+ user: 1000:1000
+ restart: unless-stopped
+ entrypoint: ["/usr/bin/milla"]
+ command: ["--config", "/config.toml"]
+ volumes:
+ - ./config-gpt.toml:/config.toml
+ - /etc/localtime:/etc/localtime:ro
+ cap_drop:
+ - ALL
+ environment:
+ - HTTPS_PROXY=http://172.17.0.1:8120
+ - https_proxy=http://172.17.0.1:8120
+ - HTTP_PROXY=http://172.17.0.1:8120
+ - http_proxy=http://172.17.0.1:8120
+ postgres:
+ image: postgres:16-alpine3.19
+ deploy:
+ resources:
+ limits:
+ memory: 4096M
+ logging:
+ driver: "json-file"
+ options:
+ max-size: "200m"
+ restart: unless-stopped
+ ports:
+ - "127.0.0.1:5455:5432/tcp"
+ volumes:
+ - terra_postgres_vault:/var/lib/postgresql/data
+ - ./scripts/:/docker-entrypoint-initdb.d/:ro
+ environment:
+ - POSTGRES_PASSWORD_FILE=/run/secrets/pg_pass_secret
+ - POSTGRES_USER_FILE=/run/secrets/pg_user_secret
+ - POSTGRES_INITDB_ARGS_FILE=/run/secrets/pg_initdb_args_secret
+ - POSTGRES_DB_FILE=/run/secrets/pg_db_secret
+ networks:
+ - terranet
+ - dbnet
+ secrets:
+ - pg_pass_secret
+ - pg_user_secret
+ - pg_initdb_args_secret
+ - pg_db_secret
+ runtime: runsc
+ pgadmin:
+ image: dpage/pgadmin4:8.6
+ deploy:
+ resources:
+ limits:
+ memory: 1024M
+ logging:
+ driver: "json-file"
+ options:
+ max-size: "100m"
+ environment:
+ - PGADMIN_LISTEN_PORT=${PGADMIN_LISTEN_PORT:-5050}
+ - PGADMIN_DEFAULT_EMAIL=${PGADMIN_DEFAULT_EMAIL:-devi@terminaldweller.com}
+ - PGADMIN_DEFAULT_PASSWORD_FILE=/run/secrets/pgadmin_pass
+ - PGADMIN_DISABLE_POSTFIX=${PGADMIN_DISABLE_POSTFIX:-YES}
+ ports:
+ - "127.0.0.1:5050:5050/tcp"
+ restart: unless-stopped
+ volumes:
+ - terra_pgadmin_vault:/var/lib/pgadmin
+ networks:
+ - dbnet
+ secrets:
+ - pgadmin_pass
+networks:
+ terranet:
+ driver: bridge
+ dbnet:
+volumes:
+ terra_postgres_vault:
+ terra_pgadmin_vault:
+secrets:
+ pg_pass_secret:
+ file: ./pg/pg_pass_secret
+ pg_user_secret:
+ file: ./pg/pg_user_secret
+ pg_initdb_args_secret:
+ file: ./pg/pg_initdb_args_secret
+ pg_db_secret:
+ file: ./pg/pg_db_secret
+ pgadmin_pass:
+ file: ./pgadmin/pgadmin_pass
+```
+
The env vars `UID`and `GID`need to be defined or they can replaces by your host user's uid and gid.<br/>
-As a convinience, there is a a [distroless](https://github.com/GoogleContainerTools/distroless) dockerfile, `Dockerfile_distroless` also provided.<br/>
+As a convenience, there is a a [distroless](https://github.com/GoogleContainerTools/distroless) dockerfile, `Dockerfile_distroless` also provided.<br/>
A vendored build of milla is available by first running `go mod vendor` and then using the provided Dockerfile, `Dockerfile_distroless_vendored`.<br/>
+### Build
+
+For a regular build:
+
+```sh
+go mod download
+go build
+```
+
+For a vendored build:
+
+```sh
+go mod vendor
+go build
+```
+
## Thanks
- [girc](https://github.com/lrstanley/girc)
- [chroma](https://github.com/alecthomas/chroma)
+- [pgx](https://github.com/jackc/pgx)
- [ollama](https://github.com/ollama/ollama)
## Similar Projects
diff --git a/docker-compose-postgres.yaml b/docker-compose-postgres.yaml
new file mode 100644
index 0000000..a10e79f
--- /dev/null
+++ b/docker-compose-postgres.yaml
@@ -0,0 +1,102 @@
+services:
+ terra:
+ image: milla_distroless_vendored
+ build:
+ context: .
+ dockerfile: ./Dockerfile_distroless_vendored
+ deploy:
+ resources:
+ limits:
+ memory: 128M
+ logging:
+ driver: "json-file"
+ options:
+ max-size: "100m"
+ networks:
+ - terranet
+ user: 1000:1000
+ restart: unless-stopped
+ entrypoint: ["/usr/bin/milla"]
+ command: ["--config", "/config.toml"]
+ volumes:
+ - ./config-gpt.toml:/config.toml
+ - /etc/localtime:/etc/localtime:ro
+ cap_drop:
+ - ALL
+ environment:
+ - HTTPS_PROXY=http://172.17.0.1:8120
+ - https_proxy=http://172.17.0.1:8120
+ - HTTP_PROXY=http://172.17.0.1:8120
+ - http_proxy=http://172.17.0.1:8120
+ postgres:
+ image: postgres:16-alpine3.19
+ deploy:
+ resources:
+ limits:
+ memory: 4096M
+ logging:
+ driver: "json-file"
+ options:
+ max-size: "200m"
+ restart: unless-stopped
+ ports:
+ - "127.0.0.1:5455:5432/tcp"
+ volumes:
+ - terra_postgres_vault:/var/lib/postgresql/data
+ - ./scripts/:/docker-entrypoint-initdb.d/:ro
+ environment:
+ - POSTGRES_PASSWORD_FILE=/run/secrets/pg_pass_secret
+ - POSTGRES_USER_FILE=/run/secrets/pg_user_secret
+ - POSTGRES_INITDB_ARGS_FILE=/run/secrets/pg_initdb_args_secret
+ - POSTGRES_DB_FILE=/run/secrets/pg_db_secret
+ networks:
+ - terranet
+ - dbnet
+ secrets:
+ - pg_pass_secret
+ - pg_user_secret
+ - pg_initdb_args_secret
+ - pg_db_secret
+ runtime: runsc
+ pgadmin:
+ image: dpage/pgadmin4:8.6
+ deploy:
+ resources:
+ limits:
+ memory: 1024M
+ logging:
+ driver: "json-file"
+ options:
+ max-size: "100m"
+ environment:
+ - PGADMIN_LISTEN_PORT=${PGADMIN_LISTEN_PORT:-5050}
+ - PGADMIN_DEFAULT_EMAIL=${PGADMIN_DEFAULT_EMAIL:-devi@terminaldweller.com}
+ - PGADMIN_DEFAULT_PASSWORD_FILE=/run/secrets/pgadmin_pass
+ - PGADMIN_DISABLE_POSTFIX=${PGADMIN_DISABLE_POSTFIX:-YES}
+ ports:
+ - "127.0.0.1:5050:5050/tcp"
+ restart: unless-stopped
+ volumes:
+ - terra_pgadmin_vault:/var/lib/pgadmin
+ networks:
+ - dbnet
+ secrets:
+ - pgadmin_pass
+networks:
+ terranet:
+ driver: bridge
+ dbnet:
+volumes:
+ terra_postgres_vault:
+ terra_pgadmin_vault:
+secrets:
+ pg_pass_secret:
+ file: ./pg/pg_pass_secret
+ pg_user_secret:
+ file: ./pg/pg_user_secret
+ pg_initdb_args_secret:
+ file: ./pg/pg_initdb_args_secret
+ pg_db_secret:
+ file: ./pg/pg_db_secret
+ pgadmin_pass:
+ file: ./pgadmin/pgadmin_pass
diff --git a/go.mod b/go.mod
index f5a5b23..fea01cd 100644
--- a/go.mod
+++ b/go.mod
@@ -6,6 +6,7 @@ require (
github.com/BurntSushi/toml v0.3.1
github.com/alecthomas/chroma/v2 v2.12.0
github.com/google/generative-ai-go v0.11.2
+ github.com/jackc/pgx/v5 v5.5.5
github.com/lrstanley/girc v0.0.0-20240125042120-9add3166e52e
github.com/sashabaranov/go-openai v1.19.3
golang.org/x/net v0.24.0
@@ -29,6 +30,9 @@ require (
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.4 // indirect
+ github.com/jackc/pgpassfile v1.0.0 // indirect
+ github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
+ github.com/jackc/puddle/v2 v2.2.1 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
diff --git a/go.sum b/go.sum
index 403d1ed..1222b11 100644
--- a/go.sum
+++ b/go.sum
@@ -75,6 +75,14 @@ github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw
github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
+github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
+github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
+github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
+github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
+github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
+github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
+github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
+github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/lrstanley/girc v0.0.0-20240125042120-9add3166e52e h1:Y86mAFtJjS4P0atZ6QAKH88TV0ASQYJdIGWiOmJKoNY=
github.com/lrstanley/girc v0.0.0-20240125042120-9add3166e52e/go.mod h1:lgrnhcF8bg/Bd5HA5DOb4Z+uGqUqGnp4skr+J2GwVgI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -85,6 +93,8 @@ github.com/sashabaranov/go-openai v1.19.3/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adO
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
diff --git a/main.go b/main.go
index 68120e5..e2e5e93 100644
--- a/main.go
+++ b/main.go
@@ -9,10 +9,13 @@ import (
"flag"
"fmt"
"log"
+ "net"
"net/http"
+ "net/url"
"os"
"reflect"
"regexp"
+ "runtime"
"strconv"
"strings"
"time"
@@ -20,6 +23,7 @@ import (
"github.com/BurntSushi/toml"
"github.com/alecthomas/chroma/v2/quick"
"github.com/google/generative-ai-go/genai"
+ "github.com/jackc/pgx/v5/pgxpool"
"github.com/lrstanley/girc"
openai "github.com/sashabaranov/go-openai"
"golang.org/x/net/proxy"
@@ -33,6 +37,7 @@ var (
errCantSet = errors.New("can't set field")
errWrongDataForField = errors.New("wrong data type for field")
errUnsupportedType = errors.New("unsupported type")
+ dbConnection *pgxpool.Pool //nolint:gochecknoglobals
)
type TomlConfig struct {
@@ -50,6 +55,13 @@ type TomlConfig struct {
ClientCertPath string `toml:"clientCertPath"`
ServerPass string `toml:"serverPass"`
Bind string `toml:"bind"`
+ Name string `toml:"name"`
+ DatabaseAddress string `toml:"databaseAddress"`
+ DatabasePassword string `toml:"databasePassword"`
+ DatabaseUser string `toml:"databaseUser"`
+ DatabaseName string `toml:"databaseName"`
+ LLMProxy string `toml:"llmProxy"`
+ IRCProxy string `toml:"ircProxy"`
Temp float64 `toml:"temp"`
RequestTimeout int `toml:"requestTimeout"`
MillaReconnectDelay int `toml:"millaReconnectDelay"`
@@ -69,6 +81,7 @@ type TomlConfig struct {
Out bool `toml:"out"`
Admins []string `toml:"admins"`
IrcChannels []string `toml:"ircChannels"`
+ ScrapeChannels []string `toml:"scrapeChannels"`
}
func NewTomlConfig() *TomlConfig {
@@ -78,6 +91,9 @@ func NewTomlConfig() *TomlConfig {
ChromaStyle: "rose-pine-moon",
ChromaFormatter: "noop",
Provider: "ollama",
+ DatabaseAddress: "postgres",
+ DatabaseUser: "milla",
+ DatabaseName: "milladb",
Temp: 0.5, //nolint:gomnd
RequestTimeout: 10, //nolint:gomnd
MillaReconnectDelay: 30, //nolint:gomnd
@@ -206,6 +222,7 @@ func getHelpString() string {
helpString += "set - set a configuration value\n"
helpString += "get - get a configuration value\n"
helpString += "getall - returns all config options with their value\n"
+ helpString += "memstats - returns the memory status currently being used\n"
return helpString
}
@@ -251,6 +268,11 @@ func setFieldByName(v reflect.Value, field string, value string) error {
return nil
}
+func byteToMByte(bytes uint64,
+) uint64 {
+ return bytes / 1024 / 1024
+}
+
func runCommand(
client *girc.Client,
event girc.Event,
@@ -317,6 +339,13 @@ func runCommand(
fieldValue := v.Field(i).Interface()
client.Cmd.Reply(event, fmt.Sprintf("%s: %v", field.Name, fieldValue))
}
+ case "memstats":
+ var memStats runtime.MemStats
+ runtime.ReadMemStats(&memStats)
+
+ client.Cmd.Reply(event, fmt.Sprintf("Alloc: %d MiB", byteToMByte(memStats.Alloc)))
+ client.Cmd.Reply(event, fmt.Sprintf("TotalAlloc: %d MiB", byteToMByte(memStats.TotalAlloc)))
+ client.Cmd.Reply(event, fmt.Sprintf("Sys: %d MiB", byteToMByte(memStats.Sys)))
default:
client.Cmd.Reply(event, errUnknCmd.Error())
}
@@ -385,12 +414,28 @@ func ollamaHandler(
var httpClient http.Client
- dialer := proxy.FromEnvironment()
+ var dialer proxy.Dialer
- httpClient = http.Client{
- Transport: &http.Transport{
- Dial: dialer.Dial,
- },
+ if appConfig.LLMProxy != "" {
+ proxyURL, err := url.Parse(appConfig.IRCProxy)
+ if err != nil {
+ cancel()
+
+ log.Fatal(err.Error())
+ }
+
+ dialer, err = proxy.FromURL(proxyURL, &net.Dialer{Timeout: time.Duration(appConfig.RequestTimeout) * time.Second})
+ if err != nil {
+ cancel()
+
+ log.Fatal(err.Error())
+ }
+
+ httpClient = http.Client{
+ Transport: &http.Transport{
+ Dial: dialer.Dial,
+ },
+ }
}
response, err := httpClient.Do(request)
@@ -474,6 +519,10 @@ func geminiHandler(
// clientGemini, err := genai.NewClient(ctx, option.WithAPIKey(appConfig.Apikey), option.WithHTTPClient(&httpClient))
+ if appConfig.Apikey == "" {
+ appConfig.Apikey = os.Getenv("MILLA_APIKEY")
+ }
+
clientGemini, err := genai.NewClient(ctx, option.WithAPIKey(appConfig.Apikey))
if err != nil {
client.Cmd.ReplyTo(event, fmt.Sprintf("error: %s", err.Error()))
@@ -559,12 +608,30 @@ func chatGPTHandler(
var httpClient http.Client
- dialer := proxy.FromEnvironment()
+ if appConfig.LLMProxy != "" {
+ proxyURL, err := url.Parse(appConfig.IRCProxy)
+ if err != nil {
+ cancel()
- httpClient = http.Client{
- Transport: &http.Transport{
- Dial: dialer.Dial,
- },
+ log.Fatal(err.Error())
+ }
+
+ dialer, err := proxy.FromURL(proxyURL, &net.Dialer{Timeout: time.Duration(appConfig.RequestTimeout) * time.Second})
+ if err != nil {
+ cancel()
+
+ log.Fatal(err.Error())
+ }
+
+ httpClient = http.Client{
+ Transport: &http.Transport{
+ Dial: dialer.Dial,
+ },
+ }
+ }
+
+ if appConfig.Apikey == "" {
+ appConfig.Apikey = os.Getenv("MILLA_APIKEY")
}
config := openai.DefaultConfig(appConfig.Apikey)
@@ -613,7 +680,78 @@ func chatGPTHandler(
})
}
-func runIRC(appConfig TomlConfig, ircChan chan *girc.Client) {
+func connectToDB(appConfig TomlConfig, context *context.Context) {
+ for {
+ if appConfig.DatabaseUser == "" {
+ appConfig.DatabaseUser = os.Getenv("MILLA_DB_USER")
+ }
+
+ if appConfig.DatabasePassword == "" {
+ appConfig.DatabasePassword = os.Getenv("MILLA_DB_PASSWORD")
+ }
+
+ if appConfig.DatabaseAddress == "" {
+ appConfig.DatabaseAddress = os.Getenv("MILLA_DB_ADDRESS")
+ }
+
+ if appConfig.DatabaseName == "" {
+ appConfig.DatabaseName = os.Getenv("MILLA_DB_NAME")
+ }
+
+ dbURL := fmt.Sprintf(
+ "postgres://%s:%s@%s/%s",
+ appConfig.DatabaseUser,
+ appConfig.DatabasePassword,
+ appConfig.DatabaseAddress,
+ appConfig.DatabaseName)
+
+ conn, err := pgxpool.New(*context, dbURL)
+ if err != nil {
+ log.Println(err)
+ time.Sleep(time.Duration(appConfig.MillaReconnectDelay) * time.Second)
+ } else {
+ for _, channel := range appConfig.ScrapeChannels {
+ query := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s (id SERIAL PRIMARY KEY,channel TEXT NOT NULL,log TEXT NOT NULL,nick TEXT NOT NULL,dateadded TIMESTAMP DEFAULT CURRENT_TIMESTAMP)",
+ strings.ReplaceAll(channel, "#", ""))
+
+ log.Println(query)
+
+ _, err = conn.Query(*context, query)
+ if err != nil {
+ log.Println(err.Error())
+ time.Sleep(time.Duration(appConfig.MillaReconnectDelay) * time.Second)
+ }
+ }
+
+ dbConnection = conn
+ }
+ }
+}
+
+func scrapeChannel(irc *girc.Client) {
+ irc.Handlers.AddBg(girc.PRIVMSG, func(client *girc.Client, event girc.Event) {
+ if dbConnection == nil {
+ log.Println("missed logging message because currently not connected to db")
+
+ return
+ }
+ query := fmt.Sprintf("INSERT INTO %s (channel,log,nick) VALUES ('%s','%s','%s')",
+ strings.ReplaceAll(event.Params[0], "#", ""),
+ event.Params[0],
+ event.Last(),
+ event.Source.Name,
+ )
+ log.Println(query)
+
+ _, err := dbConnection.Query(
+ context.Background(), query)
+ if err != nil {
+ log.Println(err.Error())
+ }
+ })
+}
+
+func runIRC(appConfig TomlConfig, ircChan chan *girc.Client, dbChan chan *pgxpool.Pool) {
var OllamaMemory []MemoryElement
var GeminiMemory []*genai.Content
@@ -646,16 +784,29 @@ func runIRC(appConfig TomlConfig, ircChan chan *girc.Client) {
irc.Config.Out = os.Stdout
}
- if appConfig.ServerPass != "" {
- irc.Config.ServerPass = appConfig.ServerPass
+ if appConfig.ServerPass == "" {
+ appConfig.ServerPass = os.Getenv("MILLA_SERVER_PASSWORD")
}
+ irc.Config.ServerPass = appConfig.ServerPass
+
if appConfig.Bind != "" {
irc.Config.Bind = appConfig.Bind
}
+ if appConfig.Name != "" {
+ irc.Config.Name = appConfig.Name
+ }
+
saslUser := appConfig.IrcSaslUser
- saslPass := appConfig.IrcSaslPass
+
+ var saslPass string
+
+ if appConfig.IrcSaslPass == "" {
+ saslPass = os.Getenv("MILLA_SASL_PASSWORD")
+ } else {
+ saslPass = appConfig.IrcSaslPass
+ }
if appConfig.EnableSasl && saslUser != "" && saslPass != "" {
irc.Config.SASL = &girc.SASLPlain{
@@ -690,10 +841,42 @@ func runIRC(appConfig TomlConfig, ircChan chan *girc.Client) {
chatGPTHandler(irc, &appConfig, &GPTMemory)
}
+ context, cancel := context.WithTimeout(context.Background(), time.Duration(appConfig.RequestTimeout)*time.Second)
+ defer cancel()
+
+ go connectToDB(appConfig, &context)
+
+ if len(appConfig.ScrapeChannels) > 0 {
+ irc.Handlers.AddBg(girc.CONNECTED, func(c *girc.Client, e girc.Event) {
+ for _, channel := range appConfig.ScrapeChannels {
+ c.Cmd.Join(channel)
+ }
+ })
+
+ go scrapeChannel(irc)
+ }
ircChan <- irc
for {
- if err := irc.Connect(); err != nil {
+ var dialer proxy.Dialer
+
+ if appConfig.IRCProxy != "" {
+ proxyURL, err := url.Parse(appConfig.IRCProxy)
+ if err != nil {
+ cancel()
+
+ log.Fatal(err.Error())
+ }
+
+ dialer, err = proxy.FromURL(proxyURL, &net.Dialer{Timeout: time.Duration(appConfig.RequestTimeout) * time.Second})
+ if err != nil {
+ cancel()
+
+ log.Fatal(err.Error())
+ }
+ }
+
+ if err := irc.DialerConnect(dialer); err != nil {
log.Println(err)
log.Println("reconnecting in " + strconv.Itoa(appConfig.MillaReconnectDelay))
time.Sleep(time.Duration(appConfig.MillaReconnectDelay) * time.Second)
@@ -723,6 +906,7 @@ func main() {
log.Println(appConfig)
ircChan := make(chan *girc.Client, 1)
+ dbConn := make(chan *pgxpool.Pool, 1)
- runIRC(*appConfig, ircChan)
+ runIRC(*appConfig, ircChan, dbConn)
}