diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..b893687 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,99 @@ +# flyctl launch added from .gitignore +# Created by https://www.toptal.com/developers/gitignore/api/vim,linux,macos,go +# Edit at https://www.toptal.com/developers/gitignore?templates=vim,linux,macos,go + +### Go ### +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +**/*.exe +**/*.exe~ +**/*.dll +**/*.so +**/*.dylib + +# Test binary, built with `go test -c` +**/*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +**/*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +**/go.work + +### Linux ### +**/*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +**/.fuse_hidden* + +# KDE directory preferences +**/.directory + +# Linux trash folder which might appear on any partition or disk +**/.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +**/.nfs* + +### macOS ### +# General +**/.DS_Store +**/.AppleDouble +**/.LSOverride + +# Icon must end with two \r +**/Icon + + +# Thumbnails +**/._* + +# Files that might appear in the root of a volume +**/.DocumentRevisions-V100 +**/.fseventsd +**/.Spotlight-V100 +**/.TemporaryItems +**/.Trashes +**/.VolumeIcon.icns +**/.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +**/.AppleDB +**/.AppleDesktop +**/Network Trash Folder +**/Temporary Items +**/.apdisk + +### macOS Patch ### +# iCloud generated files +**/*.icloud + +### Vim ### +# Swap +**/[._]*.s[a-v][a-z] +!**/*.svg # comment out if you don't need vector files +**/[._]*.sw[a-p] +**/[._]s[a-rt-v][a-z] +**/[._]ss[a-gi-z] +**/[._]sw[a-p] + +# Session +**/Session.vim +**/Sessionx.vim + +# Temporary +**/.netrwhist +# Auto-generated tag files +**/tags +# Persistent undo +**/[._]*.un~ + +# End of https://www.toptal.com/developers/gitignore/api/vim,linux,macos,go + +**/.direnv +fly.toml diff --git a/cmd/admin/main.go b/cmd/admin/main.go new file mode 100644 index 0000000..8de1cff --- /dev/null +++ b/cmd/admin/main.go @@ -0,0 +1,16 @@ +package main + +import ( + "encoding/base64" + "fmt" + + "github.com/AYM1607/ccclip/pkg/crypto" +) + +func main() { + privK := crypto.NewPrivateKey() + privStr := base64.StdEncoding.EncodeToString(privK.Bytes()) + pubStr := base64.StdEncoding.EncodeToString(privK.PublicKey().Bytes()) + + fmt.Printf("Priv: %s\nPub: %s\n", privStr, pubStr) +} diff --git a/cmd/cli/main.go b/cmd/cli/main.go index f6facc6..f2e306e 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -7,7 +7,7 @@ import ( var apiclient *client.Client func init() { - apiclient = client.New("https://api.ccclip.io") + apiclient = client.New("https://clipboard.jmug.me") } func main() { diff --git a/cmd/server/Dockerfile b/cmd/server/Dockerfile index 41104b2..1825952 100644 --- a/cmd/server/Dockerfile +++ b/cmd/server/Dockerfile @@ -1,12 +1,7 @@ -FROM golang:1.21-alpine AS builder +FROM golang:1.24-alpine AS builder # Ensure we have a c compiler. RUN apk add --no-cache build-base ca-certificates fuse3 sqlite -# Install LiteFS for distribute SQLite -COPY --from=flyio/litefs:0.5 /usr/local/bin/litefs /usr/local/bin/litefs - -COPY cmd/server/litefs.yml /etc/litefs.yml - WORKDIR /src COPY go.mod . COPY go.sum . @@ -14,6 +9,6 @@ RUN go mod download COPY . . RUN go build -ldflags='-s -w' -tags 'linux' -trimpath -o /dist/app ./cmd/server -EXPOSE 3000 +EXPOSE 8080 -ENTRYPOINT ["litefs", "mount"] +ENTRYPOINT ["/dist/app"] diff --git a/flake.lock b/flake.lock index 3a487c5..b045e51 100644 --- a/flake.lock +++ b/flake.lock @@ -20,6 +20,22 @@ "type": "github" } }, + "gonixpkgs": { + "locked": { + "lastModified": 1746518791, + "narHash": "sha256-MiJ11L7w18S2G5ftcoYtcrrS0JFqBaj9d5rwJFpC5Wk=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "1cb1c02a6b1b7cf67e3d7731cbbf327a53da9679", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "1cb1c02a6b1b7cf67e3d7731cbbf327a53da9679", + "type": "github" + } + }, "nixpkgs": { "locked": { "lastModified": 1734629298, @@ -39,6 +55,7 @@ "root": { "inputs": { "flake-utils": "flake-utils", + "gonixpkgs": "gonixpkgs", "nixpkgs": "nixpkgs", "systems": "systems" } diff --git a/flake.nix b/flake.nix index 03ff0d3..54a4ddf 100644 --- a/flake.nix +++ b/flake.nix @@ -2,6 +2,7 @@ description = "A basic flake with a shell"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/release-24.11"; + gonixpkgs.url = "github:NixOS/nixpkgs/1cb1c02a6b1b7cf67e3d7731cbbf327a53da9679"; systems.url = "github:nix-systems/default"; flake-utils = { url = "github:numtide/flake-utils"; @@ -10,15 +11,17 @@ }; outputs = - { nixpkgs, flake-utils, ... }: + { nixpkgs, gonixpkgs, flake-utils, ... }: flake-utils.lib.eachDefaultSystem ( system: let pkgs = nixpkgs.legacyPackages.${system}; + gopkgs = gonixpkgs.legacyPackages.${system}; in { devShells.default = pkgs.mkShell { - packages = with pkgs; [ + packages = with gopkgs; [ + pkgs.flyctl go gotools gopls diff --git a/fly.toml b/fly.toml index 18ca10b..700ab2b 100644 --- a/fly.toml +++ b/fly.toml @@ -1,30 +1,30 @@ -# fly.toml app configuration file generated for dark-paper-8180 on 2023-11-10T08:23:22Z +# fly.toml app configuration file generated for ccclip on 2025-05-10T00:45:48-07:00 # # See https://fly.io/docs/reference/configuration/ for information about how to use this file. # -app = "dark-paper-8180" -primary_region = "sea" +app = 'ccclip' +primary_region = 'sea' [build] - dockerfile = "./cmd/server/Dockerfile" + dockerfile = './cmd/server/Dockerfile' [env] - CCCLIP_PORT = "3000" - CCCLIP_DATABASE_LOCATION = "/litefs/ccclip.db" + CCCLIP_DATABASE_LOCATION = '/database/ccclip.db' -[mounts] - source = "litefs" - destination = "/var/lib/litefs" +[[mounts]] + source = 'database' + destination = '/database' [http_service] internal_port = 8080 force_https = true - auto_stop_machines = true + auto_stop_machines = 'stop' auto_start_machines = true - min_machines_running = 1 - processes = ["app"] + processes = ['app'] -[vm] - size = "shared-cpu-1x" - memory = "1gb" +[[vm]] + size = 'shared-cpu-1x' + memory = '512mb' + cpu_kind = 'shared' + cpus = 1 diff --git a/go.mod b/go.mod index 3e2d6a8..4e167a3 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/AYM1607/ccclip -go 1.21 +go 1.24 + +toolchain go1.24.3 require ( github.com/gorilla/mux v1.8.0 diff --git a/internal/configfile/config.go b/internal/configfile/config.go index 6069612..4e927c5 100644 --- a/internal/configfile/config.go +++ b/internal/configfile/config.go @@ -55,12 +55,12 @@ func Write(c ConfigFile) error { func LoadPrivateKey() (*ecdh.PrivateKey, error) { fp := path.Join(Path, PrivateKeyFileName) - return crypto.LoadPrivateKeyFromFile(fp) + return crypto.LoadPrivateKeyFromFile(fp), nil } func LoadPublicKey() (*ecdh.PublicKey, error) { fp := path.Join(Path, PublicKeyFileName) - return crypto.LoadPublicKeyFromFile(fp) + return crypto.LoadPublicKeyFromFile(fp), nil } func SavePrivateKey(k *ecdh.PrivateKey) error { diff --git a/internal/server/client/client.go b/internal/server/client/client.go index a53761e..f558d52 100644 --- a/internal/server/client/client.go +++ b/internal/server/client/client.go @@ -5,6 +5,7 @@ import ( "crypto/ecdh" "encoding/json" "errors" + "fmt" "io" "net/http" "time" @@ -110,6 +111,8 @@ func (c *Client) SetClipboard(plaintext []byte, deviceId string, pvk *ecdh.Priva } hres, err := http.Post(c.url+"/setClipboard", "application/json", bytes.NewReader(authReqJson)) + hresBytes, _ := io.ReadAll(hres.Body) + fmt.Println(string(hresBytes)) if err != nil { return err } @@ -194,6 +197,8 @@ func (c *Client) getDevices(deviceId string, pvk *ecdh.PrivateKey) ([]*api.Devic hresBody, err := io.ReadAll(hres.Body) defer hres.Body.Close() + hresBytes, _ := io.ReadAll(hres.Body) + fmt.Println(string(hresBytes)) if err != nil { return nil, err } diff --git a/internal/server/client/key.go b/internal/server/client/key.go index db8ceda..da7b983 100644 --- a/internal/server/client/key.go +++ b/internal/server/client/key.go @@ -8,7 +8,7 @@ import ( "github.com/AYM1607/ccclip/pkg/crypto" ) -const serverPublicKeyB64 = "JTyaIVDHe1Nwqmd4NFlkvqj+MZOVp5s3JZP+T3QuoT8=" +const serverPublicKeyB64 = "Dg6HYJ8aoQOOzGqOCw4J7tnT+QHkokjfdeWM8ktwnks=" var serverPublicKey *ecdh.PublicKey diff --git a/internal/server/logging.go b/internal/server/logging.go new file mode 100644 index 0000000..ccb8697 --- /dev/null +++ b/internal/server/logging.go @@ -0,0 +1,9 @@ +package server + +import "log" + +var logger *log.Logger + +func init() { + logger = log.Default() +} diff --git a/internal/server/server.go b/internal/server/server.go index aaa74d9..e9274e7 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -166,9 +166,11 @@ type GetUserDevicesResponse struct { } func (c *controller) handleGetUserDevices(w http.ResponseWriter, r *http.Request) { + logger.Println("handling get user devices") var authReq AuthenticatedPayload err := json.NewDecoder(r.Body).Decode(&authReq) if err != nil { + logger.Printf("decoding request: %s\n", err) http.Error(w, err.Error(), http.StatusBadRequest) return } @@ -177,18 +179,21 @@ func (c *controller) handleGetUserDevices(w http.ResponseWriter, r *http.Request // TODO: verify the request fingerprint. Right now we're just trusting that // if it decrypts successfully then we can trust it. if err != nil { + logger.Printf("decrypting payload: %s\n", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } user, err := c.store.GetDeviceUser(authReq.DeviceID) if err != nil { + logger.Printf("getting user for device: %s\n", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } devices, err := c.store.GetUserDevices(user.ID) if err != nil { + logger.Printf("getting devices for user: %s\n", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -197,6 +202,7 @@ func (c *controller) handleGetUserDevices(w http.ResponseWriter, r *http.Request w.WriteHeader(http.StatusOK) err = json.NewEncoder(w).Encode(res) if err != nil { + logger.Printf("encoding response: %s\n", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -208,9 +214,11 @@ type SetClipboardRequest struct { } func (c *controller) handleSetClipboard(w http.ResponseWriter, r *http.Request) { + log.Default().Println("handling set clipboard") var authReq AuthenticatedPayload err := json.NewDecoder(r.Body).Decode(&authReq) if err != nil { + log.Default().Printf("decoding request: %s\n", err) http.Error(w, err.Error(), http.StatusBadRequest) return } @@ -219,18 +227,21 @@ func (c *controller) handleSetClipboard(w http.ResponseWriter, r *http.Request) // TODO: verify the request fingerprint. Right now we're just trusting that // if it decrypts successfully then we can trust it. if err != nil { + log.Default().Printf("decrypting authenticated payload: %s\n", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } user, err := c.store.GetDeviceUser(authReq.DeviceID) if err != nil { + log.Default().Printf("getting user device: %s\n", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } err = c.store.PutClipboard(user.ID, req.Clipboard) if err != nil { + log.Default().Printf("putting keyboard: %s\n", err) http.Error(w, err.Error(), http.StatusInternalServerError) return }