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" SERVER_ENDPOINT="http://127.0.0.1:18901"
API_KEY= API_KEY=
ACCEPT_TOR=true ACCEPT_TOR=false
TOR_SOCKS="127.0.0.1:9050" TOR_SOCKS="127.0.0.1:9050"
# Server Config # Server Config
# ############# # #############
# Fiber Config # Fiber Config
APP_PREFORK=true APP_PREFORK=false
APP_HOST="127.0.0.1" APP_HOST="127.0.0.1"
APP_PORT=18090 APP_PORT=18090
APP_PROXY_HEADER="X-Real-Ip" # CF-Connecting-IP 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://ditatompel.com" APP_ALLOW_ORIGIN="http://localhost:5173,http://127.0.0.1:5173,https://xmr.ditatompel.com"
#DB settings: #DB settings:
DB_HOST=127.0.0.1 DB_HOST=127.0.0.1

View file

@ -1,3 +1,7 @@
linters-settings:
errcheck:
ignore: ""
issues: issues:
exclude-rules: exclude-rules:
- path: frontend/embed.go - 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 RPCUserAgent = "ditatombot/0.0.1 (Monero RPC Monitoring; https://github.com/ditatompel/xmr-remote-nodes)"
const ( const (
errEnvNoEndpoint = errProber("please set SERVER_ENDPOINT in .env") errNoEndpoint = errProber("no SERVER_ENDPOINT was provided")
errEnvNoTorSocks = errProber("please set TOR_SOCKS in .env") errNoTorSocks = errProber("no TOR_SOCKS was provided")
errNoAPIKey = errProber("no API_KEY was provided")
errInvalidCredentials = errProber("invalid API_KEY credentials")
) )
type errProber string type errProber string
@ -33,38 +35,67 @@ func (err errProber) Error() string {
} }
type proberClient struct { type proberClient struct {
config *config.App endpoint string // server endpoint
message string // message to include when reporting back to server 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 { 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{ var ProbeCmd = &cobra.Command{
Use: "probe", Use: "probe",
Short: "Probe remote nodes", Short: "Probe remote nodes",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
if err := RunProber(); err != nil { prober := newProber()
slog.Error(fmt.Sprintf("[PROBE] %s", err.Error())) if e, _ := cmd.Flags().GetString("endpoint"); e != "" {
os.Exit(1) 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 // Fetch a new job from the server, fetches node info, and sends it to the server
func RunProber() error { func (p *proberClient) Run() error {
if err := validateConfig(); err != nil { if err := p.validateConfig(); err != nil {
return err return err
} }
prober := newProber()
node, err := prober.fetchJob() node, err := p.fetchJob()
if err != nil { if err != nil {
return err return err
} }
fetchNode, err := prober.fetchNode(node) fetchNode, err := p.fetchNode(node)
if err != nil { if err != nil {
return err return err
} }
@ -73,33 +104,37 @@ func RunProber() error {
} }
// checks if all required environment variables are set // checks if all required environment variables are set
func validateConfig() error { func (p *proberClient) validateConfig() error {
if config.AppCfg().ServerEndpoint == "" { if p.endpoint == "" {
return errEnvNoEndpoint return errNoEndpoint
} }
if config.AppCfg().AcceptTor && config.AppCfg().TorSocks == "" { if p.apiKey == "" {
return errEnvNoTorSocks return errNoAPIKey
} }
if p.acceptTor && p.torSOCKS == "" {
return errNoTorSocks
}
return nil return nil
} }
// Get monero node info to fetch from the server // Get monero node info to fetch from the server
func (p *proberClient) fetchJob() (monero.Node, error) { func (p *proberClient) fetchJob() (monero.Node, error) {
queryParams := "" queryParams := ""
if p.config.AcceptTor { if p.acceptTor {
queryParams = "?accept_tor=1" queryParams = "?accept_tor=1"
} }
var node monero.Node 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)) slog.Info(fmt.Sprintf("[PROBE] Getting node from %s", uri))
req, err := http.NewRequest(http.MethodGet, uri, nil) req, err := http.NewRequest(http.MethodGet, uri, nil)
if err != nil { if err != nil {
return node, err return node, err
} }
req.Header.Add(monero.ProberAPIKey, p.config.ApiKey) req.Header.Add(monero.ProberAPIKey, p.apiKey)
req.Header.Set("User-Agent", RPCUserAgent) req.Header.Set("User-Agent", RPCUserAgent)
client := &http.Client{} client := &http.Client{}
@ -109,7 +144,12 @@ func (p *proberClient) fetchJob() (monero.Node, error) {
} }
defer resp.Body.Close() 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) 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") req.Header.Set("Origin", "https://xmr.ditatompel.com")
var client http.Client var client http.Client
if p.config.AcceptTor && node.IsTor { if p.acceptTor && node.IsTor {
dialer, err := proxy.SOCKS5("tcp", p.config.TorSocks, nil, proxy.Direct) dialer, err := proxy.SOCKS5("tcp", p.torSOCKS, nil, proxy.Direct)
if err != nil { if err != nil {
return node, err return node, err
} }
@ -290,12 +330,12 @@ func (p *proberClient) reportResult(node monero.Node, tookTime float64) error {
return err 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)) req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(jsonData))
if err != nil { if err != nil {
return err 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("Content-Type", "application/json; charset=UTF-8")
req.Header.Set("User-Agent", RPCUserAgent) req.Header.Set("User-Agent", RPCUserAgent)

View file

@ -29,6 +29,8 @@ func init() {
cobra.OnInitialize(initConfig) cobra.OnInitialize(initConfig)
Root.PersistentFlags().StringVarP(&configFile, "config-file", "c", "", "Default to .env") Root.PersistentFlags().StringVarP(&configFile, "config-file", "c", "", "Default to .env")
Root.AddCommand(client.ProbeCmd) 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() { func initConfig() {

View file

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