* feat: add Nix Flake release and update documentation for Nix integration - Added flake.nix with native library patching for Linux - Included SHA256 hashes for v0.10.0 DEB releases (x86_64 and arm64) - Updated README.md with instructions for nix run, NixOS, Home Manager, and devShells - Generated flake.lock for reproducibility * refactor: improve Nix Flake patterns for better consumer control - Extracted derivation logic into a reusable `mkPackage` function - Exposed `overlays.default` to allow consumers to use their own nixpkgs instance - Updated `pkgs.system` to `pkgs.stdenv.hostPlatform.system` to follow modern Nix patterns - Ensured default `packages` definition correctly handles unfree configuration for testing with `nix build` or `nix run` * docs: update README with advanced Nix integration patterns - Added Option 1 (Direct Integration) and Option 2 (Overlay) documentation - Explained how to use the overlay for full nixpkgs control - Fixed Markdown styling and lint issues in the Nix integration section * docs: simplify Nix documentation to favor Overlay integration - Removed "Option 1" (direct integration) as per user feedback - Promoted the Overlay method as the standard way to consume the package - Updated devShell examples to rely on the overlayed package set for consistency * fix(security): prevent master key overwriting on decryption failure and improve account loading resilience - Modify readSafeStorageKey and generatePrimaryMasterKey to stop if decryption fails for an existing file, preventing permanent data loss when keyring is temporarily locked. - Update CloudAccountRepo.getAccounts and getAccount to gracefully skip corrupted account records instead of throwing errors. - Added detailed logging for security failures to aid in diagnostics. * docs(readme): sync Nix/NixOS integration docs in README.zh-CN --------- Co-authored-by: Youssef <[email protected]>
This commit is contained in:
1 parent
61716bb512
commit
fce38ec6f0
6 files changed
+300
-23
No files matched your search
@@ -193,6 +193,53 @@ Download the latest release for your platform from the [Releases](https://github
|
|||||||
| Windows (x64/ARM64) | [.exe installer](https://github.com/Draculabo/AntigravityManager/releases/latest) |
|
| Windows (x64/ARM64) | [.exe installer](https://github.com/Draculabo/AntigravityManager/releases/latest) |
|
||||||
| macOS | [.dmg installer](https://github.com/Draculabo/AntigravityManager/releases/latest) |
|
| macOS | [.dmg installer](https://github.com/Draculabo/AntigravityManager/releases/latest) |
|
||||||
| Linux | [.deb / .rpm](https://github.com/Draculabo/AntigravityManager/releases/latest) |
|
| Linux | [.deb / .rpm](https://github.com/Draculabo/AntigravityManager/releases/latest) |
|
||||||
|
| NixOS / Nix | `nix run github:Draculabo/AntigravityManager` |
|
||||||
|
|
||||||
|
### ❄️ Nix Integration
|
||||||
|
|
||||||
|
You can integrate Antigravity Manager into your Nix configuration using the provided flake.
|
||||||
|
|
||||||
|
#### 🛠️ NixOS / Home Manager
|
||||||
|
|
||||||
|
Add the flake to your `flake.nix` inputs:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||||
|
antigravity-manager.url = "github:Draculabo/AntigravityManager";
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
To integrate with your system, add `antigravity-manager.overlays.default` to your `nixpkgs.overlays`. This ensures the package is correctly built against your own `nixpkgs` instance and respects your global configuration.
|
||||||
|
|
||||||
|
```nix
|
||||||
|
# Add to your NixOS/Home Manager configuration
|
||||||
|
{
|
||||||
|
nixpkgs.overlays = [ inputs.antigravity-manager.overlays.default ];
|
||||||
|
|
||||||
|
# Antigravity Manager has an unfree license (cc-by-nc-sa-40)
|
||||||
|
nixpkgs.config.allowUnfree = true;
|
||||||
|
|
||||||
|
environment.systemPackages = [
|
||||||
|
pkgs.antigravity-manager
|
||||||
|
];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 💻 Development Shell
|
||||||
|
|
||||||
|
If you're using the overlay above, you can simply add it to your `devShell`:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
devShells.default = pkgs.mkShell {
|
||||||
|
packages = [
|
||||||
|
pkgs.antigravity-manager
|
||||||
|
];
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Since the package is unfree, you **must** have `allowUnfree = true;` enabled in your `nixpkgs` configuration for it to evaluate correctly.
|
||||||
|
|
||||||
### Build from Source
|
### Build from Source
|
||||||
|
|
||||||
|
|||||||
@@ -192,6 +192,53 @@
|
|||||||
| Windows (x64/ARM64) | [.exe 安装包](https://github.com/Draculabo/AntigravityManager/releases/latest) |
|
| Windows (x64/ARM64) | [.exe 安装包](https://github.com/Draculabo/AntigravityManager/releases/latest) |
|
||||||
| macOS | [.dmg 安装包](https://github.com/Draculabo/AntigravityManager/releases/latest) |
|
| macOS | [.dmg 安装包](https://github.com/Draculabo/AntigravityManager/releases/latest) |
|
||||||
| Linux | [.deb / .rpm](https://github.com/Draculabo/AntigravityManager/releases/latest) |
|
| Linux | [.deb / .rpm](https://github.com/Draculabo/AntigravityManager/releases/latest) |
|
||||||
|
| NixOS / Nix | `nix run github:Draculabo/AntigravityManager` |
|
||||||
|
|
||||||
|
### ❄️ Nix 集成
|
||||||
|
|
||||||
|
你可以通过项目提供的 flake 将 Antigravity Manager 集成到你的 Nix 配置中。
|
||||||
|
|
||||||
|
#### 🛠️ NixOS / Home Manager
|
||||||
|
|
||||||
|
先在你的 `flake.nix` 的 `inputs` 中加入:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||||
|
antigravity-manager.url = "github:Draculabo/AntigravityManager";
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
然后把 `antigravity-manager.overlays.default` 添加到 `nixpkgs.overlays`。这样会基于你自己的 `nixpkgs` 实例构建,并继承你的全局配置。
|
||||||
|
|
||||||
|
```nix
|
||||||
|
# 添加到你的 NixOS/Home Manager 配置中
|
||||||
|
{
|
||||||
|
nixpkgs.overlays = [ inputs.antigravity-manager.overlays.default ];
|
||||||
|
|
||||||
|
# Antigravity Manager 使用非自由许可证 (cc-by-nc-sa-40)
|
||||||
|
nixpkgs.config.allowUnfree = true;
|
||||||
|
|
||||||
|
environment.systemPackages = [
|
||||||
|
pkgs.antigravity-manager
|
||||||
|
];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 💻 开发 Shell
|
||||||
|
|
||||||
|
如果你已经使用上面的 overlay,也可以直接在 `devShell` 里加入:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
devShells.default = pkgs.mkShell {
|
||||||
|
packages = [
|
||||||
|
pkgs.antigravity-manager
|
||||||
|
];
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> 由于该包是非自由软件,你必须在 `nixpkgs` 配置中开启 `allowUnfree = true;`,否则会评估失败。
|
||||||
|
|
||||||
### 从源码构建
|
### 从源码构建
|
||||||
|
|
||||||
|
|||||||
Generated
+27
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1774106199,
|
||||||
|
"narHash": "sha256-US5Tda2sKmjrg2lNHQL3jRQ6p96cgfWh3J1QBliQ8Ws=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "6c9a78c09ff4d6c21d0319114873508a6ec01655",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
{
|
||||||
|
description = "Antigravity Manager";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = {
|
||||||
|
self,
|
||||||
|
nixpkgs,
|
||||||
|
}: let
|
||||||
|
supportedSystems = ["x86_64-linux" "aarch64-linux"];
|
||||||
|
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
|
||||||
|
|
||||||
|
version = "0.10.0";
|
||||||
|
|
||||||
|
sources = {
|
||||||
|
"x86_64-linux" = {
|
||||||
|
url = "https://github.com/Draculabo/AntigravityManager/releases/download/v${version}/Antigravity.Manager_${version}_amd64.deb";
|
||||||
|
sha256 = "5fc08b4a9308c71fc00e44b95d4fb3178182aea8821708fa51fdf2bf5dbbb856";
|
||||||
|
};
|
||||||
|
"aarch64-linux" = {
|
||||||
|
url = "https://github.com/Draculabo/AntigravityManager/releases/download/v${version}/Antigravity.Manager_${version}_arm64.deb";
|
||||||
|
sha256 = "666c9659efa66bb6be6a8e7fb0c384ceb799a17245eab374bf9b3d7901c5c4cb";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
mkPackage = pkgs: let
|
||||||
|
system = pkgs.stdenv.hostPlatform.system;
|
||||||
|
source = sources.${system} or (throw "Unsupported system: ${system}");
|
||||||
|
in
|
||||||
|
pkgs.stdenv.mkDerivation {
|
||||||
|
pname = "antigravity-manager";
|
||||||
|
inherit version;
|
||||||
|
|
||||||
|
src = pkgs.fetchurl {
|
||||||
|
inherit (source) url sha256;
|
||||||
|
};
|
||||||
|
|
||||||
|
nativeBuildInputs = with pkgs; [
|
||||||
|
autoPatchelfHook
|
||||||
|
makeWrapper
|
||||||
|
zstd
|
||||||
|
];
|
||||||
|
|
||||||
|
buildInputs = with pkgs; [
|
||||||
|
alsa-lib
|
||||||
|
at-spi2-atk
|
||||||
|
atk
|
||||||
|
cups
|
||||||
|
dbus
|
||||||
|
expat
|
||||||
|
glib
|
||||||
|
gtk3
|
||||||
|
libsecret
|
||||||
|
libdrm
|
||||||
|
libxkbcommon
|
||||||
|
mesa
|
||||||
|
nspr
|
||||||
|
nss
|
||||||
|
pango
|
||||||
|
systemd
|
||||||
|
libx11
|
||||||
|
libxcomposite
|
||||||
|
libxdamage
|
||||||
|
libxext
|
||||||
|
libxfixes
|
||||||
|
libxrandr
|
||||||
|
libxcb
|
||||||
|
];
|
||||||
|
|
||||||
|
unpackPhase = ''
|
||||||
|
ar x $src
|
||||||
|
tar xf data.tar.* --no-same-permissions
|
||||||
|
'';
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
mkdir -p $out/bin $out/lib $out/share
|
||||||
|
cp -r usr/lib/antigravity-manager $out/lib/
|
||||||
|
cp -r usr/share/* $out/share/
|
||||||
|
|
||||||
|
ln -s $out/lib/antigravity-manager/antigravity-manager $out/bin/antigravity-manager
|
||||||
|
'';
|
||||||
|
|
||||||
|
postFixup = ''
|
||||||
|
# Wrap the executable so it finds Wayland/X11 and libsecret
|
||||||
|
wrapProgram $out/bin/antigravity-manager \
|
||||||
|
--add-flags "--enable-features=WaylandWindowDecorations" \
|
||||||
|
--add-flags "--ozone-platform-hint=auto" \
|
||||||
|
--prefix LD_LIBRARY_PATH : ${pkgs.lib.makeLibraryPath [pkgs.libsecret]}
|
||||||
|
|
||||||
|
if [ -e $out/share/applications/antigravity-manager.desktop ]; then
|
||||||
|
substituteInPlace $out/share/applications/antigravity-manager.desktop \
|
||||||
|
--replace "/usr/lib/antigravity-manager/antigravity-manager" "antigravity-manager"
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
|
||||||
|
meta = with pkgs.lib; {
|
||||||
|
description = "Professional multi-account manager for Google Gemini & Claude AI";
|
||||||
|
homepage = "https://github.com/Draculabo/AntigravityManager";
|
||||||
|
license = licenses.cc-by-nc-sa-40;
|
||||||
|
maintainers = [];
|
||||||
|
platforms = ["x86_64-linux" "aarch64-linux"];
|
||||||
|
mainProgram = "antigravity-manager";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
overlays.default = final: prev: {
|
||||||
|
antigravity-manager = mkPackage final;
|
||||||
|
};
|
||||||
|
|
||||||
|
packages = forAllSystems (system: let
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
config.allowUnfree = true;
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
default = mkPackage pkgs;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -638,21 +638,41 @@ export class CloudAccountRepo {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tokenResult = await decryptAndMigrateField(
|
let tokenResult: DecryptFieldResult;
|
||||||
orm,
|
try {
|
||||||
normalizedRow.id,
|
tokenResult = await decryptAndMigrateField(
|
||||||
'tokenJson',
|
orm,
|
||||||
normalizedRow.tokenJson,
|
normalizedRow.id,
|
||||||
);
|
'tokenJson',
|
||||||
const quotaResult = await decryptAndMigrateField(
|
normalizedRow.tokenJson,
|
||||||
orm,
|
);
|
||||||
normalizedRow.id,
|
} catch (error) {
|
||||||
'quotaJson',
|
logger.error(
|
||||||
normalizedRow.quotaJson,
|
`[CloudAccountRepo] getAccount ${id} failed - Decryption failed for token`,
|
||||||
);
|
error,
|
||||||
|
);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
if (!tokenResult.value) {
|
let quotaResult: DecryptFieldResult;
|
||||||
throw new Error(`Missing token data for account ${normalizedRow.id}`);
|
try {
|
||||||
|
quotaResult = await decryptAndMigrateField(
|
||||||
|
orm,
|
||||||
|
normalizedRow.id,
|
||||||
|
'quotaJson',
|
||||||
|
normalizedRow.quotaJson,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(
|
||||||
|
`[CloudAccountRepo] getAccount ${id} failed - Decryption failed for quota`,
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
quotaResult = { value: null, migrated: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokenValue = tokenResult.value;
|
||||||
|
if (!tokenValue) {
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
+24
-9
@@ -92,22 +92,29 @@ async function readSafeStorageKey(keyPath: string): Promise<Buffer | null> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let encryptedKey: Buffer;
|
||||||
|
try {
|
||||||
|
encryptedKey = await fs.readFile(keyPath);
|
||||||
|
} catch (error) {
|
||||||
|
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
logger.error('Security: Failed to read safeStorage key file', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const encryptedKey = await fs.readFile(keyPath);
|
|
||||||
const hexKey = safeStorage.decryptString(encryptedKey);
|
const hexKey = safeStorage.decryptString(encryptedKey);
|
||||||
|
|
||||||
if (!/^[a-f0-9]+$/i.test(hexKey) || hexKey.length !== 64) {
|
if (!/^[a-f0-9]+$/i.test(hexKey) || hexKey.length !== 64) {
|
||||||
|
logger.warn('Security: safeStorage key has invalid format');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Buffer.from(hexKey, 'hex');
|
return Buffer.from(hexKey, 'hex');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
logger.error('Security: Decryption of safeStorage key file failed. Keyring might be locked.', error);
|
||||||
return null;
|
throw error;
|
||||||
}
|
|
||||||
|
|
||||||
logger.warn('Security: Failed to read safeStorage key file', error);
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,14 +176,15 @@ async function readFileFallbackKey(keyPath: string): Promise<Buffer | null> {
|
|||||||
return Buffer.from(content, 'hex');
|
return Buffer.from(content, 'hex');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If it's not a valid hex key but file exists, it might be a safeStorage encrypted key
|
||||||
return null;
|
return null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.warn('Security: Error reading fallback key file', error);
|
logger.error('Security: Error reading fallback key file', error);
|
||||||
return null;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,6 +301,13 @@ async function generatePrimaryMasterKey(): Promise<MasterKeyState> {
|
|||||||
}
|
}
|
||||||
return { key: result.key, source: 'safeStorage' };
|
return { key: result.key, source: 'safeStorage' };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// If we failed to decrypt but the file exists, we should NOT proceed to other fallbacks
|
||||||
|
// as they might overwrite the existing file and cause permanent data loss.
|
||||||
|
const fileExists = await fs.access(keyPath).then(() => true).catch(() => false);
|
||||||
|
if (fileExists) {
|
||||||
|
logger.error('Security: safeStorage key file exists but decryption failed. Keyring might be locked. Stopping to prevent data loss.', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
logger.warn('Security: safeStorage failed, trying keytar', error);
|
logger.warn('Security: safeStorage failed, trying keytar', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user