Compare commits

...

5 commits

Author SHA1 Message Date
2576d53c35
feat: Only return exit code 1 for specific err
Only return with status code `1` if error type is `errProber` which one
of this following const:
errNoEndpoint, errNoTorSocks, errNoAPIKey, and errInvalidCredentials.
2024-06-19 18:46:12 +07:00
aa8ecc6b61
fix(lint): Deprecated linters.errcheck.ignore 2024-06-19 16:54:40 +07:00
0321006eb3
feat: Allow user to specify endpoint from CLI flag
`--no-tor` also added to `probe` CLI flags to force probing clearnet
nodes only.
2024-06-19 16:32:40 +07:00
c0885f5e5a
chore: default ACCEPT_TOR and APP_PREFORK to false
Also change allow-origin CORS from ditatompel.com to xmr.ditatompel.com
2024-06-19 16:25:53 +07:00
3f5c0c9472
refactor: Lowercase & upperase initialism acronyms 2024-06-19 16:24:18 +07:00
5 changed files with 80 additions and 34 deletions

View file

@ -6,18 +6,18 @@ LOG_LEVEL=INFO # can be DEBUG, INFO, WARN, ERROR
# #############
SERVER_ENDPOINT="http://127.0.0.1:18901"
API_KEY=
ACCEPT_TOR=true
ACCEPT_TOR=false
TOR_SOCKS="127.0.0.1:9050"
# Server Config
# #############
# Fiber Config
APP_PREFORK=true
APP_PREFORK=false
APP_HOST="127.0.0.1"
APP_PORT=18090
APP_PROXY_HEADER="X-Real-Ip" # CF-Connecting-IP
APP_ALLOW_ORIGIN="http://localhost:5173,http://127.0.0.1:5173,https://ditatompel.com"
APP_PROXY_HEADER="X-Real-Ip" # `CF-Connecting-IP` if using Cloudflare
APP_ALLOW_ORIGIN="http://localhost:5173,http://127.0.0.1:5173,https://xmr.ditatompel.com"
#DB settings:
DB_HOST=127.0.0.1

View file

@ -1,3 +1,7 @@
linters-settings:
errcheck:
ignore: ""
issues:
exclude-rules:
- path: frontend/embed.go

View file

@ -22,8 +22,10 @@ import (
const RPCUserAgent = "ditatombot/0.0.1 (Monero RPC Monitoring; https://github.com/ditatompel/xmr-remote-nodes)"
const (
errEnvNoEndpoint = errProber("please set SERVER_ENDPOINT in .env")
errEnvNoTorSocks = errProber("please set TOR_SOCKS in .env")
errNoEndpoint = errProber("no SERVER_ENDPOINT was provided")
errNoTorSocks = errProber("no TOR_SOCKS was provided")
errNoAPIKey = errProber("no API_KEY was provided")
errInvalidCredentials = errProber("invalid API_KEY credentials")
)
type errProber string
@ -33,38 +35,67 @@ func (err errProber) Error() string {
}
type proberClient struct {
config *config.App
message string // message to include when reporting back to server
endpoint string // server endpoint
apiKey string // prober api key
acceptTor bool // accept tor
torSOCKS string // IP:Port of tor socks
message string // message to include when reporting back to server
}
func newProber() *proberClient {
return &proberClient{config: config.AppCfg()}
cfg := config.AppCfg()
return &proberClient{
endpoint: cfg.ServerEndpoint,
apiKey: cfg.APIKey,
acceptTor: cfg.AcceptTor,
torSOCKS: cfg.TorSOCKS,
}
}
var ProbeCmd = &cobra.Command{
Use: "probe",
Short: "Probe remote nodes",
Run: func(cmd *cobra.Command, args []string) {
if err := RunProber(); err != nil {
slog.Error(fmt.Sprintf("[PROBE] %s", err.Error()))
os.Exit(1)
prober := newProber()
if e, _ := cmd.Flags().GetString("endpoint"); e != "" {
prober.SetEndpoint(e)
}
if t, _ := cmd.Flags().GetBool("no-tor"); t {
prober.SetAcceptTor(false)
}
if err := prober.Run(); err != nil {
switch err.(type) {
case errProber:
slog.Error(fmt.Sprintf("[PROBE] %s", err.Error()))
os.Exit(1)
default:
slog.Warn(fmt.Sprintf("[PROBE] %s", err.Error()))
}
}
},
}
func (p *proberClient) SetEndpoint(endpoint string) {
p.endpoint = endpoint
}
func (p *proberClient) SetAcceptTor(acceptTor bool) {
p.acceptTor = acceptTor
}
// Fetch a new job from the server, fetches node info, and sends it to the server
func RunProber() error {
if err := validateConfig(); err != nil {
func (p *proberClient) Run() error {
if err := p.validateConfig(); err != nil {
return err
}
prober := newProber()
node, err := prober.fetchJob()
node, err := p.fetchJob()
if err != nil {
return err
}
fetchNode, err := prober.fetchNode(node)
fetchNode, err := p.fetchNode(node)
if err != nil {
return err
}
@ -73,33 +104,37 @@ func RunProber() error {
}
// checks if all required environment variables are set
func validateConfig() error {
if config.AppCfg().ServerEndpoint == "" {
return errEnvNoEndpoint
func (p *proberClient) validateConfig() error {
if p.endpoint == "" {
return errNoEndpoint
}
if config.AppCfg().AcceptTor && config.AppCfg().TorSocks == "" {
return errEnvNoTorSocks
if p.apiKey == "" {
return errNoAPIKey
}
if p.acceptTor && p.torSOCKS == "" {
return errNoTorSocks
}
return nil
}
// Get monero node info to fetch from the server
func (p *proberClient) fetchJob() (monero.Node, error) {
queryParams := ""
if p.config.AcceptTor {
if p.acceptTor {
queryParams = "?accept_tor=1"
}
var node monero.Node
uri := fmt.Sprintf("%s/api/v1/job%s", p.config.ServerEndpoint, queryParams)
uri := fmt.Sprintf("%s/api/v1/job%s", p.endpoint, queryParams)
slog.Info(fmt.Sprintf("[PROBE] Getting node from %s", uri))
req, err := http.NewRequest(http.MethodGet, uri, nil)
if err != nil {
return node, err
}
req.Header.Add(monero.ProberAPIKey, p.config.ApiKey)
req.Header.Add(monero.ProberAPIKey, p.apiKey)
req.Header.Set("User-Agent", RPCUserAgent)
client := &http.Client{}
@ -109,7 +144,12 @@ func (p *proberClient) fetchJob() (monero.Node, error) {
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
switch resp.StatusCode {
case 200:
break
case 401:
return node, errInvalidCredentials
default:
return node, fmt.Errorf("status code: %d", resp.StatusCode)
}
@ -144,8 +184,8 @@ func (p *proberClient) fetchNode(node monero.Node) (monero.Node, error) {
req.Header.Set("Origin", "https://xmr.ditatompel.com")
var client http.Client
if p.config.AcceptTor && node.IsTor {
dialer, err := proxy.SOCKS5("tcp", p.config.TorSocks, nil, proxy.Direct)
if p.acceptTor && node.IsTor {
dialer, err := proxy.SOCKS5("tcp", p.torSOCKS, nil, proxy.Direct)
if err != nil {
return node, err
}
@ -290,12 +330,12 @@ func (p *proberClient) reportResult(node monero.Node, tookTime float64) error {
return err
}
endpoint := fmt.Sprintf("%s/api/v1/job", p.config.ServerEndpoint)
endpoint := fmt.Sprintf("%s/api/v1/job", p.endpoint)
req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(jsonData))
if err != nil {
return err
}
req.Header.Add(monero.ProberAPIKey, p.config.ApiKey)
req.Header.Add(monero.ProberAPIKey, p.apiKey)
req.Header.Set("Content-Type", "application/json; charset=UTF-8")
req.Header.Set("User-Agent", RPCUserAgent)

View file

@ -29,6 +29,8 @@ func init() {
cobra.OnInitialize(initConfig)
Root.PersistentFlags().StringVarP(&configFile, "config-file", "c", "", "Default to .env")
Root.AddCommand(client.ProbeCmd)
client.ProbeCmd.Flags().StringP("endpoint", "e", "", "Server endpoint")
client.ProbeCmd.Flags().Bool("no-tor", false, "Only probe clearnet nodes")
}
func initConfig() {

View file

@ -19,9 +19,9 @@ type App struct {
// configuration for prober (client)
ServerEndpoint string
ApiKey string
APIKey string
AcceptTor bool
TorSocks string
TorSOCKS string
}
var app = &App{}
@ -54,7 +54,7 @@ func LoadApp() {
// prober configuration
app.ServerEndpoint = os.Getenv("SERVER_ENDPOINT")
app.ApiKey = os.Getenv("API_KEY")
app.APIKey = os.Getenv("API_KEY")
app.AcceptTor, _ = strconv.ParseBool(os.Getenv("ACCEPT_TOR"))
app.TorSocks = os.Getenv("TOR_SOCKS")
app.TorSOCKS = os.Getenv("TOR_SOCKS")
}