initial
This commit is contained in:
commit
7818103d5d
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
dist
|
||||
fbisender
|
17
Makefile
Normal file
17
Makefile
Normal file
@ -0,0 +1,17 @@
|
||||
build:
|
||||
go build -o dist/ .
|
||||
|
||||
run: build
|
||||
./dist/fbisender
|
||||
|
||||
tidy:
|
||||
go mod tidy
|
||||
|
||||
clean:
|
||||
rm -rf dist
|
||||
|
||||
uninstall:
|
||||
rm -f /usr/bin/fbisender
|
||||
|
||||
install:
|
||||
cp ./dist/fbisender /usr/bin
|
5
go.mod
Normal file
5
go.mod
Normal file
@ -0,0 +1,5 @@
|
||||
module git.asdf.cafe/abs3nt/fbisender
|
||||
|
||||
go 1.23.2
|
||||
|
||||
require gopkg.in/yaml.v2 v2.4.0
|
4
go.sum
Normal file
4
go.sum
Normal file
@ -0,0 +1,4 @@
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
283
main.go
Normal file
283
main.go
Normal file
@ -0,0 +1,283 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultHostPort = 8080
|
||||
defaultTargetPort = "5000"
|
||||
)
|
||||
|
||||
var acceptedExtensions = []string{".cia", ".tik", ".cetk", ".3dsx"}
|
||||
|
||||
type Config struct {
|
||||
TargetIP string `yaml:"target_ip"`
|
||||
HostIP string `yaml:"host_ip"`
|
||||
HostPort int `yaml:"host_port"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.SetFlags(0)
|
||||
|
||||
config, err := loadConfig()
|
||||
if err != nil {
|
||||
log.Fatalf("Error loading configuration: %v", err)
|
||||
}
|
||||
|
||||
targetPath, err := parseArguments()
|
||||
if err != nil {
|
||||
log.Fatalf("Error parsing arguments: %v", err)
|
||||
}
|
||||
|
||||
fileListPayload, directory, err := prepareFileListPayload(config, targetPath)
|
||||
if err != nil {
|
||||
log.Fatalf("Error preparing file list payload: %v", err)
|
||||
}
|
||||
|
||||
if err := changeDirectory(directory); err != nil {
|
||||
log.Fatalf("Error changing directory: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("\nURLs:")
|
||||
fmt.Println(fileListPayload + "\n")
|
||||
|
||||
server := startHTTPServer(config.HostPort)
|
||||
|
||||
conn, err := sendPayload(config.TargetIP, fileListPayload)
|
||||
if err != nil {
|
||||
log.Fatalf("Error sending payload: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := waitForInstallation(ctx, conn); err != nil {
|
||||
log.Printf("Installation process error: %v", err)
|
||||
}
|
||||
cancel()
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case sig := <-sigChan:
|
||||
fmt.Printf("\nReceived signal %v. Shutting down...\n", sig)
|
||||
cancel()
|
||||
}
|
||||
|
||||
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer shutdownCancel()
|
||||
if err := server.Shutdown(shutdownCtx); err != nil {
|
||||
log.Printf("HTTP server shutdown error: %v", err)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
fmt.Println("Server gracefully shut down.")
|
||||
}
|
||||
|
||||
func loadConfig() (*Config, error) {
|
||||
configDirectory, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting home directory: %w", err)
|
||||
}
|
||||
configDirectory = filepath.Join(homeDir, ".config")
|
||||
}
|
||||
data, err := os.ReadFile(filepath.Join(configDirectory, "fbisender", "config.yaml"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var config Config
|
||||
if err := yaml.Unmarshal(data, &config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if config.TargetIP == "" {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if config.HostIP == "" {
|
||||
log.Println("Detecting host IP...")
|
||||
hostIP, err := detectHostIP()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.HostIP = hostIP
|
||||
}
|
||||
|
||||
if config.HostPort == 0 {
|
||||
config.HostPort = defaultHostPort
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
func parseArguments() (string, error) {
|
||||
if len(os.Args) < 2 {
|
||||
return "", fmt.Errorf("usage: %s <file / directory>", os.Args[0])
|
||||
}
|
||||
|
||||
targetPath := strings.TrimSpace(os.Args[1])
|
||||
if _, err := os.Stat(targetPath); os.IsNotExist(err) {
|
||||
return "", fmt.Errorf("%s: no such file or directory", targetPath)
|
||||
}
|
||||
|
||||
return targetPath, nil
|
||||
}
|
||||
|
||||
func prepareFileListPayload(config *Config, targetPath string) (string, string, error) {
|
||||
fmt.Println("Preparing data...")
|
||||
|
||||
baseURL := config.HostIP + ":" + strconv.Itoa(config.HostPort) + "/"
|
||||
var fileListPayload string
|
||||
var directory string
|
||||
|
||||
fileInfo, err := os.Stat(targetPath)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if fileInfo.IsDir() {
|
||||
directory = targetPath
|
||||
files, err := os.ReadDir(directory)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("reading directory: %w", err)
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if !file.IsDir() && hasAcceptedExtension(file.Name()) {
|
||||
encodedFileName := url.PathEscape(file.Name())
|
||||
fileListPayload += baseURL + encodedFileName + "\n"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if hasAcceptedExtension(fileInfo.Name()) {
|
||||
encodedFileName := url.PathEscape(fileInfo.Name())
|
||||
fileListPayload = baseURL + encodedFileName
|
||||
directory = filepath.Dir(targetPath)
|
||||
} else {
|
||||
return "", "", fmt.Errorf("unsupported file extension. Supported extensions are: %v", acceptedExtensions)
|
||||
}
|
||||
}
|
||||
|
||||
if fileListPayload == "" {
|
||||
return "", "", fmt.Errorf("no files to serve")
|
||||
}
|
||||
|
||||
return fileListPayload, directory, nil
|
||||
}
|
||||
|
||||
func changeDirectory(directory string) error {
|
||||
if directory != "" && directory != "." {
|
||||
if err := os.Chdir(directory); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func startHTTPServer(port int) *http.Server {
|
||||
fmt.Println("Starting HTTP server on port", port)
|
||||
server := &http.Server{
|
||||
Addr: ":" + strconv.Itoa(port),
|
||||
Handler: http.FileServer(http.Dir(".")),
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
log.Fatalf("Error starting HTTP server: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
func sendPayload(targetIP, fileListPayload string) (net.Conn, error) {
|
||||
fmt.Printf("Sending URL(s) to %s on port %s...\n", targetIP, defaultTargetPort)
|
||||
|
||||
conn, err := net.Dial("tcp", net.JoinHostPort(targetIP, defaultTargetPort))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dialing target device: %w", err)
|
||||
}
|
||||
|
||||
payloadBytes := []byte(fileListPayload)
|
||||
length := uint32(len(payloadBytes))
|
||||
lengthBytes := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(lengthBytes, length)
|
||||
|
||||
if _, err := conn.Write(append(lengthBytes, payloadBytes...)); err != nil {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("writing to connection: %w", err)
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func waitForInstallation(ctx context.Context, conn net.Conn) error {
|
||||
fmt.Println("Waiting for the installation to complete...")
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
default:
|
||||
_, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
if err == io.EOF || strings.Contains(err.Error(), "connection reset by peer") {
|
||||
fmt.Println("Installation completed. Connection closed by target device.")
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("reading from connection: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func detectHostIP() (string, error) {
|
||||
conn, err := net.Dial("udp", "8.8.8.8:53")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("detecting host IP: %w", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
localAddr := conn.LocalAddr().(*net.UDPAddr)
|
||||
return localAddr.IP.String(), nil
|
||||
}
|
||||
|
||||
func hasAcceptedExtension(fileName string) bool {
|
||||
ext := strings.ToLower(filepath.Ext(fileName))
|
||||
for _, acceptedExt := range acceptedExtensions {
|
||||
if ext == acceptedExt {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
Loading…
Reference in New Issue
Block a user