diff --git a/src/lib/generateInstallScript.ts b/src/lib/generateInstallScript.ts index e951731..316b13a 100644 --- a/src/lib/generateInstallScript.ts +++ b/src/lib/generateInstallScript.ts @@ -9,7 +9,7 @@ import { generateArchScript, generateFedoraScript, generateOpenSUSEScript, - generateNixScript, + generateNixConfig, generateFlatpakScript, generateSnapScript, generateHomebrewScript, @@ -21,10 +21,7 @@ interface ScriptOptions { helper?: 'yay' | 'paru'; } -/** - * Generate a full install script with progress bars, error handling, - * and all the fancy stuff. This is what gets downloaded as .sh file. - */ +// Full install script for download. Nix gets a config file, others get shell scripts. export function generateInstallScript(options: ScriptOptions): string { const { distroId, selectedAppIds, helper = 'yay' } = options; const distro = distros.find(d => d.id === distroId); @@ -40,7 +37,7 @@ export function generateInstallScript(options: ScriptOptions): string { case 'arch': return generateArchScript(packages, helper); case 'fedora': return generateFedoraScript(packages); case 'opensuse': return generateOpenSUSEScript(packages); - case 'nix': return generateNixScript(packages); + case 'nix': return generateNixConfig(packages); case 'flatpak': return generateFlatpakScript(packages); case 'snap': return generateSnapScript(packages); case 'homebrew': return generateHomebrewScript(packages); @@ -61,7 +58,7 @@ export function generateSimpleCommand(selectedAppIds: Set, distroId: Dis case 'arch': return `yay -S --needed --noconfirm ${pkgList}`; case 'fedora': return `sudo dnf install -y ${pkgList}`; case 'opensuse': return `sudo zypper install -y ${pkgList}`; - case 'nix': return `nix-env -iA ${packages.filter(p => p.pkg.trim()).map(p => `nixpkgs.${p.pkg.trim()}`).join(' ')}`; + case 'nix': return generateNixConfig(packages); case 'flatpak': return `flatpak install flathub -y ${pkgList}`; case 'snap': if (packages.length === 1) return `sudo snap install ${pkgList}`; diff --git a/src/lib/nixUnfree.ts b/src/lib/nixUnfree.ts new file mode 100644 index 0000000..b845e7b --- /dev/null +++ b/src/lib/nixUnfree.ts @@ -0,0 +1,35 @@ +// Nix unfree package detection - these require allowUnfree = true + +export const KNOWN_UNFREE_PACKAGES = new Set([ + 'discord', + 'slack', + 'zoom-us', + 'teams', + 'skypeforlinux', + 'google-chrome', + 'vivaldi', + 'opera', + 'spotify', + 'steam', + 'heroic', + 'vscode', + 'sublime4', + 'jetbrains.idea-ultimate', + 'jetbrains.webstorm', + 'jetbrains.pycharm-professional', + 'nvidia-x11', + 'dropbox', + '1password', + 'masterpdfeditor', +]); + +export function isUnfreePackage(pkg: string): boolean { + const cleanPkg = pkg.trim().toLowerCase(); + if (KNOWN_UNFREE_PACKAGES.has(cleanPkg)) return true; + + // Nested packages like jetbrains.idea-ultimate + for (const unfree of KNOWN_UNFREE_PACKAGES) { + if (cleanPkg.includes(unfree)) return true; + } + return false; +} diff --git a/src/lib/scripts/index.ts b/src/lib/scripts/index.ts index fa2d357..7c451be 100644 --- a/src/lib/scripts/index.ts +++ b/src/lib/scripts/index.ts @@ -6,7 +6,7 @@ export { generateDebianScript } from './debian'; export { generateArchScript } from './arch'; export { generateFedoraScript } from './fedora'; export { generateOpenSUSEScript } from './opensuse'; -export { generateNixScript } from './nix'; +export { generateNixConfig } from './nix'; export { generateFlatpakScript } from './flatpak'; export { generateSnapScript } from './snap'; export { generateHomebrewScript } from './homebrew'; diff --git a/src/lib/scripts/nix.ts b/src/lib/scripts/nix.ts index 4addaf2..2c39286 100644 --- a/src/lib/scripts/nix.ts +++ b/src/lib/scripts/nix.ts @@ -1,55 +1,22 @@ -// Nix script - nix-env +// Nix declarative config generator -import { generateAsciiHeader, generateSharedUtils, escapeShellString, type PackageInfo } from './shared'; +import { type PackageInfo } from './shared'; +import { isUnfreePackage } from '../nixUnfree'; -export function generateNixScript(packages: PackageInfo[]): string { - return generateAsciiHeader('Nix', packages.length) + generateSharedUtils(packages.length) + ` -is_installed() { nix-env -q 2>/dev/null | grep -q "$1"; } +export function generateNixConfig(packages: PackageInfo[]): string { + const validPackages = packages.filter(p => p.pkg.trim()); + if (validPackages.length === 0) return '# No packages selected'; -install_pkg() { - local name=$1 attr=$2 - CURRENT=$((CURRENT + 1)) - - if is_installed "$attr"; then - skip "$name" - SKIPPED+=("$name") - return 0 - fi - - show_progress $CURRENT $TOTAL "$name" - local start=$(date +%s) - - local output - if output=$(with_retry nix-env -iA "nixpkgs.$attr"); then - local elapsed=$(($(date +%s) - start)) - update_avg_time $elapsed - printf "\\r\\033[K" - timing "$name" "$elapsed" - SUCCEEDED+=("$name") - else - printf "\\r\\033[K\${RED}✗\${NC} %s\\n" "$name" - if echo "$output" | grep -q "attribute.*not found"; then - echo -e " \${DIM}Attribute not found\${NC}" - fi - FAILED+=("$name") - fi -} - -# ───────────────────────────────────────────────────────────────────────────── - -command -v nix-env &>/dev/null || { error "nix-env not found"; exit 1; } - -info "Updating channels..." -with_retry nix-channel --update >/dev/null && success "Updated" || warn "Update failed" - -echo -info "Installing $TOTAL packages" -echo - -${packages.filter(p => p.pkg.trim()).map(({ app, pkg }) => `install_pkg "${escapeShellString(app.name)}" "${pkg.trim()}"`).join('\n')} - -print_summary -echo -info "Restart your shell for new commands." -`; + const sortedPkgs = validPackages.map(p => p.pkg.trim()).sort(); + const packageList = sortedPkgs.map(pkg => ` ${pkg}`).join('\n'); + + // Add unfree warning if needed + const unfreePkgs = sortedPkgs.filter(pkg => isUnfreePackage(pkg)); + const unfreeComment = unfreePkgs.length > 0 + ? `# Unfree: ${unfreePkgs.join(', ')}\n# Requires: nixpkgs.config.allowUnfree = true;\n\n` + : ''; + + return `${unfreeComment}environment.systemPackages = with pkgs; [ +${packageList} +];`; }