diff options
Diffstat (limited to 'modules/services/nixos/ganyupload')
| -rw-r--r-- | modules/services/nixos/ganyupload/README | 13 | ||||
| -rw-r--r-- | modules/services/nixos/ganyupload/default.nix | 47 | ||||
| -rw-r--r-- | modules/services/nixos/ganyupload/go.mod | 3 | ||||
| -rw-r--r-- | modules/services/nixos/ganyupload/main.go | 104 | ||||
| -rw-r--r-- | modules/services/nixos/ganyupload/pkg.nix | 6 |
5 files changed, 173 insertions, 0 deletions
diff --git a/modules/services/nixos/ganyupload/README b/modules/services/nixos/ganyupload/README new file mode 100644 index 0000000..ac9a50d --- /dev/null +++ b/modules/services/nixos/ganyupload/README @@ -0,0 +1,13 @@ +Ganyupload - File Upload Service + +Upload files using curl with a PUT request: + + curl -X PUT --data-binary @myfile.txt https://upld.williamsfam.us.com/myfile.txt + +The server will respond with "ok" on success. + +You can use any path you want: + + curl -X PUT --data-binary @document.pdf https://upld.williamsfam.us.com/docs/document.pdf + +Nested directories will be created automatically. diff --git a/modules/services/nixos/ganyupload/default.nix b/modules/services/nixos/ganyupload/default.nix new file mode 100644 index 0000000..0138100 --- /dev/null +++ b/modules/services/nixos/ganyupload/default.nix @@ -0,0 +1,47 @@ +{ + pkgs, + config, + lib, + ... +}: let + cfg = config.collinux.services.ganyupload; + + package = pkgs.callPackage ./pkg.nix {}; +in { + imports = [ + (import ../mkCaddyCfg.nix cfg) + ]; + + config = lib.mkIf cfg.enable { + users.groups."ganyupload" = {}; + users.users."ganyupload" = { + isSystemUser = true; + group = "ganyupload"; + home = "/var/lib/ganyupload"; + createHome = true; + homeMode = "755"; + }; + + systemd.services."ganyupload" = { + description = "Ganymede File Upload Service"; + restartIfChanged = true; + wants = ["network-online.target" "caddy.service"]; + after = ["network-online.target" "caddy.service"]; + + environment = { + PORT = toString cfg.port; + UPLOAD_DIR = "/media/ganyupload"; + }; + + serviceConfig = { + User = "ganyupload"; + Type = "simple"; + + WorkingDirectory = "/var/lib/ganyupload"; + ExecStart = "${package}/bin/ganyupload"; + }; + + wantedBy = ["multi-user.target"]; + }; + }; +} diff --git a/modules/services/nixos/ganyupload/go.mod b/modules/services/nixos/ganyupload/go.mod new file mode 100644 index 0000000..715e1b7 --- /dev/null +++ b/modules/services/nixos/ganyupload/go.mod @@ -0,0 +1,3 @@ +module git.ganymede/ganyupload + +go 1.25.5 diff --git a/modules/services/nixos/ganyupload/main.go b/modules/services/nixos/ganyupload/main.go new file mode 100644 index 0000000..6aa219e --- /dev/null +++ b/modules/services/nixos/ganyupload/main.go @@ -0,0 +1,104 @@ +package main + +import ( + _ "embed" + "io" + "log" + "net/http" + "os" + "path/filepath" + "strings" +) + +//go:embed README +var readme string + +var uploadDir = "." + +func handleRoot(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.Write([]byte(readme)) +} + +func handleUpload(w http.ResponseWriter, r *http.Request) { + // Show README on GET / + if r.Method == http.MethodGet && r.URL.Path == "/" { + handleRoot(w, r) + return + } + + if r.Method != http.MethodPut { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + + // Extract filename from URL path, preventing path traversal + filename := strings.TrimPrefix(r.URL.Path, "/") + if filename == "" { + http.Error(w, "filename required", http.StatusBadRequest) + return + } + + // Prevent path traversal + filename = filepath.Clean(filename) + if strings.HasPrefix(filename, "..") { + http.Error(w, "invalid filename", http.StatusBadRequest) + return + } + + fullPath := filepath.Join(uploadDir, filename) + + // Ensure the directory exists + dir := filepath.Dir(fullPath) + if err := os.MkdirAll(dir, 0755); err != nil { + log.Printf("failed to create directory: %v", err) + http.Error(w, "internal server error", http.StatusInternalServerError) + return + } + + // Create the file + f, err := os.Create(fullPath) + if err != nil { + log.Printf("failed to create file: %v", err) + http.Error(w, "internal server error", http.StatusInternalServerError) + return + } + defer f.Close() + + // Copy request body to file + written, err := io.Copy(f, r.Body) + if err != nil { + log.Printf("failed to write file: %v", err) + http.Error(w, "internal server error", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusCreated) + w.Write([]byte("ok\n")) + + log.Printf("uploaded %s (%d bytes)", filename, written) +} + +func main() { + if envUploadDir := os.Getenv("UPLOAD_DIR"); envUploadDir != "" { + uploadDir = envUploadDir + } + + port := os.Getenv("PORT") + if port == "" { + port = "8080" + } + + http.HandleFunc("/", handleUpload) + + log.Printf("starting ganyupload on port %s, upload directory: %s", port, uploadDir) + if err := http.ListenAndServe(":"+port, nil); err != nil { + log.Fatalf("server error: %v", err) + } +} diff --git a/modules/services/nixos/ganyupload/pkg.nix b/modules/services/nixos/ganyupload/pkg.nix new file mode 100644 index 0000000..18be8ed --- /dev/null +++ b/modules/services/nixos/ganyupload/pkg.nix @@ -0,0 +1,6 @@ +{pkgs ? import <nixpkgs> {system = "x86_64-linux";}, ...}: +pkgs.buildGoModule { + name = "ganyupload"; + src = ./.; + vendorHash = null; +} |
