Skip to content

feat(nix): add flake support with automated vendorHash management #65

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions .github/workflows/update-vendor-hash.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
name: Update Vendor Hash

on:
push:
branches: [ main ]
paths:
- 'go.mod'
- 'go.sum'
workflow_dispatch:

jobs:
update-vendor-hash:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Install Nix
uses: cachix/install-nix-action@v24
with:
extra_nix_config: |
experimental-features = nix-command flakes

- name: Get current vendorHash from flake.nix
id: current-hash
run: |
current_hash=$(grep -o 'vendorHash = "sha256-[^"]*"' flake.nix | cut -d'"' -f2)
echo "current=$current_hash" >> $GITHUB_OUTPUT

- name: Calculate expected vendorHash
id: expected-hash
run: |
# Get the goModules derivation path
drv_path=$(nix eval --impure '.#packages.x86_64-linux.default.goModules.drvPath' 2>/dev/null | tr -d '"')

# Extract the hash from the derivation
hash=$(nix derivation show "$drv_path" | jq -r '.[] | .outputs.out.hash')

# Convert to SRI format
sri_hash=$(nix hash to-sri "sha256:$hash")

echo "expected=$sri_hash" >> $GITHUB_OUTPUT

- name: Check if update needed
id: check-update
run: |
if [ "${{ steps.current-hash.outputs.current }}" != "${{ steps.expected-hash.outputs.expected }}" ]; then
echo "update_needed=true" >> $GITHUB_OUTPUT
echo "Current hash: ${{ steps.current-hash.outputs.current }}"
echo "Expected hash: ${{ steps.expected-hash.outputs.expected }}"
else
echo "update_needed=false" >> $GITHUB_OUTPUT
echo "Vendor hash is up to date"
fi

- name: Update vendorHash
if: steps.check-update.outputs.update_needed == 'true'
run: |
sed -i 's/vendorHash = "sha256-[^"]*"/vendorHash = "${{ steps.expected-hash.outputs.expected }}"/' flake.nix

- name: Create Pull Request
if: steps.check-update.outputs.update_needed == 'true'
uses: peter-evans/create-pull-request@v5
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "build(nix): update vendorHash to ${{ steps.expected-hash.outputs.expected }}"
title: "Update Nix vendorHash"
body: |
## 🔄 Automated vendorHash Update

This PR updates the `vendorHash` in `flake.nix` to match the current Go module dependencies.

### Changes
- **Previous hash**: `${{ steps.current-hash.outputs.current }}`
- **New hash**: `${{ steps.expected-hash.outputs.expected }}`

### Trigger
This update was triggered by changes to Go module files (`go.mod` or `go.sum`).

### Testing
The hash was calculated from the actual Go module dependencies and verified to build correctly.

---
*This PR was created automatically by the `update-vendor-hash` workflow.*
branch: update-vendor-hash
branch-suffix: timestamp
delete-branch: true
labels: |
dependencies
automated
nix

- name: Build to verify
if: steps.check-update.outputs.update_needed == 'true'
run: |
echo "Building with updated hash to verify..."
nix build --impure
echo "✅ Build successful with updated vendorHash"
58 changes: 58 additions & 0 deletions .github/workflows/verify-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Verify Build

on:
pull_request:
paths:
- 'flake.nix'
- 'go.mod'
- 'go.sum'
- '**.go'
push:
branches: [ main ]

jobs:
verify-nix-build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Install Nix
uses: cachix/install-nix-action@v24
with:
extra_nix_config: |
experimental-features = nix-command flakes

- name: Verify flake
run: |
nix flake check --impure

- name: Build package
run: |
nix build --impure

- name: Test run
run: |
nix run --impure . -- --help

- name: Verify vendorHash is correct
run: |
# Get current hash from flake.nix
current_hash=$(grep -o 'vendorHash = "sha256-[^"]*"' flake.nix | cut -d'"' -f2)

# Calculate expected hash
drv_path=$(nix eval --impure '.#packages.x86_64-linux.default.goModules.drvPath' | tr -d '"')
expected_hash_raw=$(nix derivation show "$drv_path" | jq -r '.[] | .outputs.out.hash')
expected_hash=$(nix hash to-sri "sha256:$expected_hash_raw")

echo "Current vendorHash: $current_hash"
echo "Expected vendorHash: $expected_hash"

if [ "$current_hash" != "$expected_hash" ]; then
echo "❌ ERROR: vendorHash mismatch!"
echo "The vendorHash in flake.nix does not match the actual Go module dependencies."
echo "Please update the vendorHash or let the automation handle it."
exit 1
else
echo "✅ vendorHash is correct!"
fi
90 changes: 90 additions & 0 deletions NIX_AUTOMATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Nix Automation

This project includes automated workflows to keep the Nix `vendorHash` up to date.

## 🤖 Automated Workflows

### 1. Update Vendor Hash (`.github/workflows/update-vendor-hash.yml`)

**Triggers:**
- Push to `main` branch with changes to `go.mod` or `go.sum`
- Manual dispatch

**What it does:**
- Calculates the correct `vendorHash` from current Go dependencies
- Compares with the hash in `flake.nix`
- If different, creates a PR with the updated hash
- Verifies the build works with the new hash

### 2. Verify Build (`.github/workflows/verify-build.yml`)

**Triggers:**
- Pull requests affecting `flake.nix`, `go.mod`, `go.sum`, or Go files
- Push to `main` branch

**What it does:**
- Runs `nix flake check`
- Builds the package with `nix build`
- Tests the binary with `nix run`
- Verifies the `vendorHash` is correct

## 🛠️ Local Development

### Update vendorHash manually

```bash
./update-vendor-hash.sh
```

This script:
- ✅ Checks if update is needed
- 🔄 Updates `flake.nix` if hash differs
- 🔨 Verifies build works
- 📝 Shows current vs expected hash

### Quick commands

```bash
# Build the package
nix build --impure

# Run the binary
nix run --impure . -- --help

# Enter development shell
nix develop

# Check flake
nix flake check --impure
```

## 📋 How it works

1. **Hash Calculation**: The correct `vendorHash` is calculated from the Go module dependencies without building
2. **Smart Updates**: Only creates PRs when the hash actually needs updating
3. **Verification**: All changes are verified to build successfully
4. **Automation**: Runs automatically when Go dependencies change

## 🔧 Manual Hash Update

If you need to update the hash manually:

```bash
# Get the current hash
current_hash=$(grep -o 'vendorHash = "sha256-[^"]*"' flake.nix | cut -d'"' -f2)

# Calculate expected hash
drv_path=$(nix eval --impure '.#packages.x86_64-linux.default.goModules.drvPath' | tr -d '"')
expected_hash=$(nix derivation show "$drv_path" | jq -r '.[] | .outputs.out.hash' | xargs -I {} nix hash to-sri "sha256:{}")

# Update flake.nix
sed -i "s/vendorHash = \"sha256-[^\"]*\"/vendorHash = \"$expected_hash\"/" flake.nix
```

## 📈 Benefits

- **Automated**: No manual intervention needed for hash updates
- **Reliable**: Always uses the correct hash calculated from actual dependencies
- **Fast**: Doesn't require failed builds to get the hash
- **Verified**: All updates are tested before merging
- **Transparent**: Clear PR descriptions explain what changed
94 changes: 88 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,31 @@ This is an [MCP](https://modelcontextprotocol.io/introduction) server that runs

## Setup

### Option 1: Using Go

1. **Install Go**: Follow instructions at <https://golang.org/doc/install>
2. **Install or update this server**: `go install github.com/isaacphi/mcp-language-server@latest`
3. **Install a language server**: _follow one of the guides below_
4. **Configure your MCP client**: _follow one of the guides below_

### Option 2: Using Nix

1. **Run directly with Nix**: `nix run github:isaacphi/mcp-language-server -- --workspace /path/to/your/project --lsp gopls`
2. **Install a language server**: _follow one of the guides below_
3. **Configure your MCP client**: _follow one of the guides below_

<details>
<summary>Go (gopls)</summary>
<div>
<p><strong>Install gopls</strong>: <code>go install golang.org/x/tools/gopls@latest</code></p>
<p><strong>Configure your MCP client</strong>: This will be different but similar for each client. For Claude Desktop, add the following to <code>~/Library/Application\ Support/Claude/claude_desktop_config.json</code></p>

<p><strong>Install gopls</strong>:</p>
<ul>
<li><strong>Go</strong>: <code>go install golang.org/x/tools/gopls@latest</code></li>
<li><strong>Nix</strong>: <code>nix profile install nixpkgs#gopls</code> or use in shell</li>
</ul>

<p><strong>Configure your MCP client</strong>:</p>

<p><em>Using Go installation:</em></p>
<pre>
{
"mcpServers": {
Expand All @@ -41,6 +55,25 @@ This is an [MCP](https://modelcontextprotocol.io/introduction) server that runs
}
}
}
</pre>

<p><em>Using Nix:</em></p>
<pre>
{
"mcpServers": {
"language-server": {
"command": "nix",
"args": [
"run", "github:isaacphi/mcp-language-server", "--",
"--workspace", "/Users/you/dev/yourproject/",
"--lsp", "gopls"
],
"env": {
"PATH": "/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin"
}
}
}
}
</pre>

<p><strong>Note</strong>: Not all clients will need these environment variables. For Claude Desktop you will need to update the environment variables above based on your machine and username:</p>
Expand Down Expand Up @@ -77,9 +110,15 @@ This is an [MCP](https://modelcontextprotocol.io/introduction) server that runs
<details>
<summary>Python (pyright)</summary>
<div>
<p><strong>Install pyright</strong>: <code>npm install -g pyright</code></p>
<p><strong>Configure your MCP client</strong>: This will be different but similar for each client. For Claude Desktop, add the following to <code>~/Library/Application\ Support/Claude/claude_desktop_config.json</code></p>

<p><strong>Install pyright</strong>:</p>
<ul>
<li><strong>npm</strong>: <code>npm install -g pyright</code></li>
<li><strong>Nix</strong>: <code>nix profile install nixpkgs#pyright</code> or use in shell</li>
</ul>

<p><strong>Configure your MCP client</strong>:</p>

<p><em>Using npm installation:</em></p>
<pre>
{
"mcpServers": {
Expand All @@ -96,6 +135,23 @@ This is an [MCP](https://modelcontextprotocol.io/introduction) server that runs
}
}
}
</pre>

<p><em>Using Nix:</em></p>
<pre>
{
"mcpServers": {
"language-server": {
"command": "nix",
"args": [
"run", "github:isaacphi/mcp-language-server", "--",
"--workspace", "/Users/you/dev/yourproject/",
"--lsp", "pyright-langserver",
"--", "--stdio"
]
}
}
}
</pre>
</div>
</details>
Expand Down Expand Up @@ -235,6 +291,32 @@ Configure your Claude Desktop (or similar) to use the local binary:

Rebuild after making changes.

## Nix Usage

### Quick Start

```bash
# Run directly from GitHub
nix run github:isaacphi/mcp-language-server -- --workspace /path/to/project --lsp gopls

# Clone and run locally
git clone https://github.com/isaacphi/mcp-language-server.git
cd mcp-language-server
nix run . -- --workspace /path/to/project --lsp gopls

# Development shell with Go and tools
nix develop
```

### Nix Flake

This project includes a `flake.nix` with:
- **`packages.default`**: The mcp-language-server binary
- **`apps.default`**: Direct execution with `nix run`
- **`devShells.default`**: Development environment with Go, just, and gopls

The `vendorHash` is automatically maintained by GitHub Actions when Go dependencies change.

### Logging

Setting the `LOG_LEVEL` environment variable to DEBUG enables verbose logging to stderr for all components including messages to and from the language server and the language server's logs.
Expand Down
Loading