diff --git a/pkgs-set/pkgs/pds-upload.nix/default.nix b/pkgs-set/pkgs/pds-upload.nix/default.nix new file mode 100644 index 0000000..6e4447f --- /dev/null +++ b/pkgs-set/pkgs/pds-upload.nix/default.nix @@ -0,0 +1,49 @@ +{ lib +, stdenvNoCC +, makeWrapper +, nushell +, file +, secretsFile ? null +, ... +}: + +stdenvNoCC.mkDerivation { + pname = "pds-upload"; + version = "0.1.0"; + + src = ./pds-upload.nu; + + nativeBuildInputs = [ makeWrapper ]; + + buildInputs = [ nushell ]; + + dontUnpack = true; + dontBuild = true; + + installPhase = '' + runHook preInstall + + mkdir -p $out/bin $out/libexec + + install -Dm755 $src $out/libexec/pds-upload.nu + + makeWrapper ${nushell}/bin/nu $out/bin/pds-upload \ + --add-flags "$out/libexec/pds-upload.nu" \ + --prefix PATH : ${lib.makeBinPath [ file ]} \ + --run ${lib.escapeShellArg '' + # Optional: Set secrets file from argument override + ${lib.optionalString (secretsFile != null) '' + export ATPROTO_SECRETS_FILE="${secretsFile}" + ''} + + # Load secrets if the file exists (to populate ATPROTO_DID/PASSWORD) + if [ -n "$ATPROTO_SECRETS_FILE" ] && [ -f "$ATPROTO_SECRETS_FILE" ]; then + set -a + source "$ATPROTO_SECRETS_FILE" + set +a + fi + ''} + + runHook postInstall + ''; +} \ No newline at end of file diff --git a/pkgs-set/pkgs/pds-upload.nix/pds-upload.nu b/pkgs-set/pkgs/pds-upload.nix/pds-upload.nu new file mode 100644 index 0000000..a78aec7 --- /dev/null +++ b/pkgs-set/pkgs/pds-upload.nix/pds-upload.nu @@ -0,0 +1,96 @@ +#!/usr/bin/env nu + +# A script to upload a blob and create a record in the AT Protocol (Bluesky). +# Usage: nu pds-upload.nu + +def main [ + file_path: path # The path to the file you want to upload +] { + # --- 1. Setup & Configuration --- + let identifier = ($env.ATPROTO_DID? | default "") + let password = ($env.ATPROTO_PASSWORD? | default "") + + # Default to the main network if not specified in env + let pds_host = ($env.ATPROTO_PDS_URL? | default "https://bsky.social") + + # Validation + if ($identifier | is-empty) or ($password | is-empty) { + error make { + msg: "Missing Credentials", + label: { + text: "Please set ATPROTO_DID and ATPROTO_PASSWORD environment variables.", + span: (metadata $identifier).span + } + } + } + + if not ($file_path | path exists) { + error make { msg: $"File not found: ($file_path)" } + } + + # Detect mime-type + let mime_type = (try { + ^file --mime-type -b $file_path | str trim + } catch { + "application/octet-stream" + }) + + # --- 2. Create Session (Authentication) --- + let session_res = (http post + --content-type "application/json" + $"($pds_host)/xrpc/com.atproto.server.createSession" + { identifier: $identifier, password: $password } + ) + + let access_token = $session_res.accessJwt + let repo_did = $session_res.did + + # --- 3. Upload Blob --- + let file_data = (open $file_path) + + let blob_res = (http post + --content-type $mime_type + --headers { Authorization: $"Bearer ($access_token)" } + $"($pds_host)/xrpc/com.atproto.repo.uploadBlob" + $file_data + ) + + let blob_ref = $blob_res.blob + + # --- 4. Create Record --- + let file_name = ($file_path | path basename) + let now = (date now | format date "%Y-%m-%dT%H:%M:%SZ") + + let record_payload = { + repo: $repo_did + collection: "systems.gaze.file" + record: { + "$type": "systems.gaze.file" + createdAt: $now + file: $blob_ref + filename: $file_name + } + } + + let record_res = (http post + --content-type "application/json" + --headers { Authorization: $"Bearer ($access_token)" } + $"($pds_host)/xrpc/com.atproto.repo.createRecord" + $record_payload + ) + + # --- 5. Construct Return Value --- + # We construct the public URL for the blob + # The '$link' key requires special handling to extract + let cid = ($blob_ref.ref | get '$link') + let blob_url = $"($pds_host)/xrpc/com.atproto.sync.getBlob?did=($repo_did)&cid=($cid)" + + # Return a structured record with both the URI and the direct URL + let result = { + uri: $record_res.uri + cid: $record_res.cid + blob_url: $blob_url + } | to json + + return $result +} \ No newline at end of file diff --git a/secrets/atfileCfg.age b/secrets/atfileCfg.age new file mode 100644 index 0000000..e75ed1e --- /dev/null +++ b/secrets/atfileCfg.age @@ -0,0 +1,17 @@ +age-encryption.org/v1 +-> ssh-rsa Abmvag +cslEDLMgbmTeCxKXG4isYQdts8ET2tb4NSRgoHpk9g3jB3bEOlYdY7LZtZ8YLxJ0 +78YD6v81ssVQzCOoD0MQKveEkDIAie9JlYYDQS0pv/ACP+E1fqbIRo5pE7t9s26x +rBMZntIDx1vzGRM/+87Wz2NOPpNseP7P2CEIltMn3S5xbsl4N9WVx1mnH4NfsPCu +VRh3APe/Ee/Ph0/wBK2wQWRG97dd+p1bY7nzkRYE26DpODez1f9kTlTppIyivJDc +dIPMkykJOos/dwu6nR1XKS6l8f7Y+pQcYF24cqMe3PDQbhb19qwcpMiy0I11R2ik +/yb8pRziazd01gJsSKUWz/WVhKx7A4dj9TQloWO1FXoIB/mPaxZBghQfP55oV6b9 +MGIxRcn/fekbCYNSxVo9WFyBsfg4vNzUcBlUInANrxU2mUS5YFpY7jf2iHGeGfRU +ZQsgB/ftScvtoSdKJ3dW4fW/2MZ2qyzjcrvFvikL50fucXhhOGa4ToDy2e04TLpm +fXlKHBgS+xYIgnjw/6P54SwMbgC1fGUq7mC7TVKZiA/AyDyZMxQvNIfx6MKlffDM +ItU2mHjV2i23g3Nk9V6EmSxy/RCivVNZMWktLJrR+XYKGjhqjqObF83kE9vlzl1g +6hU80Ys0H8q0iCBnceLL1UlODByXczzRhiqrFetkmlc +--- goQ5BSnSK0NTZ13BPhqmduMllp9tF8n4mNHEVvm1dA8 +��?eO��X �=/@��c�fg�7  �ciG��ד`�,q(HT�rPs'Z��l����L�w@2"�?[�:��9��B�] �{+�����8%���%�w_vR���9�N +Q�G�0 �Z�l�C��u +�y���>J \ No newline at end of file diff --git a/secrets/secrets.nix b/secrets/secrets.nix index 00b5514..ff48878 100644 --- a/secrets/secrets.nix +++ b/secrets/secrets.nix @@ -2,7 +2,6 @@ let yusdacra = builtins.readFile ./yusdacra.key.pub; dzwonek = builtins.readFile ./dzwonek.key.pub; trimounts = builtins.readFile ./trimounts.key.pub; - develMobi = builtins.readFile ./develMobi.key.pub; in { "nixGithubAccessToken.age".publicKeys = [ yusdacra ]; @@ -26,10 +25,6 @@ in yusdacra dzwonek ]; - "develMobiTailscaleAuthKey.age".publicKeys = [ - yusdacra - develMobi - ]; "cloudflareDnsEdit.age".publicKeys = [ yusdacra dzwonek @@ -40,4 +35,5 @@ in dzwonek trimounts ]; + "atfileCfg.age".publicKeys = [yusdacra]; } diff --git a/users/mayer/default.nix b/users/mayer/default.nix index 8d77409..431ca45 100644 --- a/users/mayer/default.nix +++ b/users/mayer/default.nix @@ -2,7 +2,6 @@ pkgs, lib, tlib, - config, terra, ... }@globalAttrs: @@ -98,6 +97,7 @@ in home-manager.users.mayer = { + config, pkgs, inputs, ... @@ -135,6 +135,7 @@ in l.flatten [ (tlib.prefixStrings "${inputs.self}/users/modules/" modulesToEnable) ../modules/discord/service.nix + "${inputs.agenix}/modules/age-home.nix" ]; home = { @@ -158,9 +159,19 @@ in bs-manager cemu tor-browser - supersonic-wayland + # supersonic-wayland feishin - ]) ++ [terra.helium]; + ]) ++ [ + terra.helium + (terra.pds-upload.override { + secretsFile = config.age.secrets.atfileCfg.path; + }) + ]; + }; + + age.secrets.atfileCfg = { + file = ../../secrets/atfileCfg.age; + mode = "600"; }; fonts.fontconfig.enable = l.mkForce true; diff --git a/users/modules/clipman/default.nix b/users/modules/clipman/default.nix new file mode 100644 index 0000000..f7e58de --- /dev/null +++ b/users/modules/clipman/default.nix @@ -0,0 +1,6 @@ +{ + services.clipman = { + enable = true; + systemdTarget = "graphical-session.target"; + }; +} diff --git a/users/modules/discord/service.nix b/users/modules/discord/service.nix index f208d04..9be3e69 100644 --- a/users/modules/discord/service.nix +++ b/users/modules/discord/service.nix @@ -8,7 +8,7 @@ Service = { Type = "simple"; - ExecStart = "${pkgs.openssh}/bin/ssh -N -D 127.0.0.1:1338 root@dzwonek"; + ExecStart = "${pkgs.openssh}/bin/ssh -N -D 127.0.0.1:1338 root@trimounts"; Restart = "on-failure"; RestartSec = "3s"; }; diff --git a/users/modules/niri/clipboard.nu b/users/modules/niri/clipboard.nu new file mode 100755 index 0000000..609ad16 --- /dev/null +++ b/users/modules/niri/clipboard.nu @@ -0,0 +1,21 @@ +#!/usr/bin/env nu + +def main [] { + # Get clipboard history from clipman + let history = clipman show-history + + if ($history | is-empty) { + notify-send "Clipman" "No clipboard history" + exit 1 + } + + # Show in tofi and get selection + let selection = ($history | lines | tofi --prompt-text "select clipboard item: " | str trim) + + if ($selection | is-empty) { + exit 0 + } + + # Copy selection to clipboard + $selection | wl-copy +} diff --git a/users/modules/niri/config.kdl b/users/modules/niri/config.kdl index b81cb1f..d4f0ad7 100644 --- a/users/modules/niri/config.kdl +++ b/users/modules/niri/config.kdl @@ -179,6 +179,9 @@ binds { XF86MonBrightnessUp allow-when-locked=true { spawn "brightnessctl" "--class=backlight" "set" "+10%"; } XF86MonBrightnessDown allow-when-locked=true { spawn "brightnessctl" "--class=backlight" "set" "10%-"; } + Ctrl+Shift+U { spawn-sh "nu %%clipboard-upload%%"; } + Ctrl+Shift+S { spawn-sh "nu %%clipboard-select%%"; } + Mod+O repeat=false { toggle-overview; } Mod+Q cooldown-ms=50 { close-window; } diff --git a/users/modules/niri/default.nix b/users/modules/niri/default.nix index 33d1572..ebc4072 100644 --- a/users/modules/niri/default.nix +++ b/users/modules/niri/default.nix @@ -1,9 +1,6 @@ { - config, - nixosConfig, pkgs, lib, - tlib, ... }: let @@ -15,13 +12,16 @@ in ../wlsunset ../mako ../tofi + ../clipman ]; - home.packages = with pkgs; [niri xwayland-satellite brightnessctl swaybg]; + home.packages = with pkgs; [file libnotify clipman niri xwayland-satellite brightnessctl swaybg]; xdg.configFile."niri/config.kdl".text = let replace = { wallpaper = toString ../../mayer/wallpaper.png; + clipboard-upload = toString ./uploader.nu; + clipboard-select = toString ./clipboard.nu; }; in l.replaceStrings diff --git a/users/modules/niri/uploader.nu b/users/modules/niri/uploader.nu new file mode 100755 index 0000000..39127da --- /dev/null +++ b/users/modules/niri/uploader.nu @@ -0,0 +1,70 @@ +#!/usr/bin/env nu + +def main [] { + # --- 1. Get Clipboard Content --- + let timestamp = (date now | format date '%Y%m%d_%H%M%S') + let temp_file = $"/tmp/clip_($timestamp)" + + # Save clipboard to temp file + # We remove --no-newline to safely handle binary data if necessary, + # though wl-paste usually handles it. + try { + wl-paste | save -f $temp_file + } catch { + notify-send "Upload Failed" "Could not get content from clipboard" + exit 1 + } + + # Detect MIME type just for logging/notification + let mime_type = (try { + ^file --mime-type -b $temp_file | str trim + } catch { + "application/octet-stream" + }) + + print $"📤 Uploading ($mime_type)..." + notify-send "ATProto Upload" $"Uploading ($mime_type)..." -t 2000 + + try { + # --- 2. Call pds-upload --- + # pds-upload now returns: { uri, cid, blob_url } + let res = pds-upload $temp_file | from json + + let record_uri = $res.uri + let blob_url = $res.blob_url + + print $"🔗 Blob URL: ($blob_url)" + print "✂️ Shortening with is.gd..." + + # --- 3. Shorten URL --- + # is.gd expects the URL to be passed as a query parameter 'url' + # We must URL-encode the blob_url + let encoded_target = ($blob_url | url encode) + let shorten_api = $"https://is.gd/create.php?format=simple&url=($encoded_target)" + + # is.gd returns the short URL as the body text + let short_url = (http get $shorten_api) + + # --- 4. Success & Clipboard --- + # Check if is.gd returned a URL (starts with http) or an error + if ($short_url | str starts-with "http") { + $short_url | wl-copy + notify-send "ATProto Upload" $"Shortened: ($short_url)" + print $"✅ Shortened: ($short_url)" + } else { + # Fallback to the long blob URL if shortening failed + $blob_url | wl-copy + notify-send "ATProto Upload" $"Uploaded (Shorten failed)" + print $"⚠️ Shorten failed: ($short_url)" + } + + } catch {|e| + # Parse error for notification + let err_msg = ($e | to text) + notify-send "ATProto Upload" $"Upload failed: ($err_msg)" -u critical + print $"❌ Upload failed: ($err_msg)" + } + + # Cleanup + rm -f $temp_file +} \ No newline at end of file