Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
54 changes: 54 additions & 0 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: Publish to npm

on:
release:
types: [created]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '18.x'
registry-url: 'https://registry.npmjs.org'

- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 8
run_install: false

- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV

- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-

- name: Install dependencies
run: pnpm install

- name: Lint
run: pnpm run lint

- name: Build
run: pnpm run build

- name: Test
run: pnpm test -- --watchAll=false

- name: E2E Tests
run: pnpm run e2e-test

- name: Publish to npm
run: pnpm publish --no-git-checks
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
55 changes: 55 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: Run Tests

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x, 20.x]

steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}

- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 8
run_install: false

- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV

- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-

- name: Install dependencies
run: pnpm install

- name: Lint
run: pnpm run lint

- name: Build
run: pnpm run build

- name: Run tests
run: pnpm test -- --watchAll=false

- name: Run E2E tests
run: pnpm run e2e-test
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
node_modules
dist
dist
*.tgz
.npmrc
.npm
npm-debug.log*
91 changes: 81 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,66 @@ Because this helper is designed to allow sharing credentials on the same machine

## Installation and Usage

This helper is written in Typescript and compiles down to two Javascript scripts, one for the server and one for the client.
This helper is written in TypeScript and can be installed globally via npm or pnpm.

### Download
### Installation Options

Download the latest release from this repo. The release consists of a filed named `git-credential-forwarder.zip` which contains two Javascript scripts: `gcf-server.js` and `gcf-client.js`. These can be placed wherever you want, but these instructions assume they are placed in the home directories of the host and container.
#### Global Installation (Recommended)

Install the package globally using npm or pnpm:

```
npm install -g git-credential-forwarder
# or
pnpm add -g git-credential-forwarder
```

This will make the commands `gcf-server` and `gcf-client` available globally.

#### Manual Download

Alternatively, you can download the latest release from this repo. The release consists of a file named `git-credential-forwarder.zip` which contains two JavaScript scripts: `gcf-server.js` and `gcf-client.js`. These can be placed wherever you want.

After downloading, make the scripts executable:

```
chmod +x gcf-server.js gcf-client.js
```

### On the host

Run `node ~/gcf-server.js`. This will launch the server and it will listen for TCP connections on localhost at a random port which will be displayed in the console. You will need to keep this console/terminal open.
If installed globally:
```
gcf-server
```

If using manual download:
```
./gcf-server.js
```

This will launch the server and it will listen for TCP connections on localhost at a random port which will be displayed in the console. You will need to keep this console/terminal open.

Notes:

- You can tell it to use a specific port by setting the environmental variable `GIT_CREDENTIAL_FORWARDER_PORT`

### In the container

Run `export GIT_CREDENTIAL_FORWARDER_SERVER="host.Docker.internal:PORT` where PORT is replaced with the port displayed when you ran the server.
Run `export GIT_CREDENTIAL_FORWARDER_SERVER="host.docker.internal:PORT"` where PORT is replaced with the port displayed when you ran the server.

Edit your git configuration file to call the client as a git credential helper:

Edit your git configuration file to call the client you just complied as a git credential helper, as follows:
If installed globally:
```
[credential]
helper = "!f() { gcf-client $*; }; f"
```

If using manual download:
```
[credential]
helper = "!f() { node ~/gcf-client.js $*; }; f"
helper = "!f() { /path/to/gcf-client.js $*; }; f"
```

Run git normally and all requests for credentials should be passed through to the host which will handle appropriately on the host side.
Expand All @@ -46,14 +83,33 @@ Notes:

Here's a strategy to make this fairly easy to use with a Docker container built with a Dockerfile.

#### Option 1: Using npm or pnpm (Recommended)

On the host, set a specific port that you will listen on by configuring the env variable `GIT_CREDENTIAL_FORWARDER_PORT`.

Add these lines in the Dockerfile:

```
# Install Node.js and npm/pnpm first if needed
RUN npm install -g git-credential-forwarder
# or
RUN pnpm add -g git-credential-forwarder

RUN git config --global credential.helper '!f(){ gcf-client $*; }; f'
ENV GIT_CREDENTIAL_FORWARDER_SERVER host.docker.internal:[PORT]
```

#### Option 2: Using direct download

On the host, set a specific port that you will listen on by configuring the env variable `GIT_CREDENTIAL_FORWARDER_PORT`.

Add these lines in the Dockerfile
Add these lines in the Dockerfile:

```
RUN curl -LO https://github.com/sam-mfb/git-credential-forwarder/releases/download/v[VERSION]/git-credential-forwarder.zip
RUN unzip git-credential-forwarder.zip
RUN git config --global credential.helper '!f(){ node ~/gcf-client.js $*; }; f'
RUN unzip git-credential-forwarder.zip -d /usr/local/bin
RUN chmod +x /usr/local/bin/gcf-*.js
RUN git config --global credential.helper '!f(){ /usr/local/bin/gcf-client.js $*; }; f'
ENV GIT_CREDENTIAL_FORWARDER_SERVER host.docker.internal:[PORT]
```

Expand All @@ -77,6 +133,21 @@ Note that this will not work from a Mac OS host per [this Docker issue](https://

You can enable debugging on either the server or the client by setting the environmental variable `GIT_CREDENTIAL_FORWARDER_DEBUG` to `true`.

## Development

### Publishing to npm

This project uses GitHub Actions to automatically publish to npm when a new release is created. To set this up:

1. Generate an npm token with publish permissions
2. Add the token as a GitHub repository secret named `NPM_TOKEN`
3. Update the version in package.json
4. Commit the changes and push to GitHub
5. Create a new tag for the release: `git tag v1.x.x && git push --tags`
6. Create a new release on GitHub using the tag to trigger the publishing workflow

The GitHub Actions workflow will use pnpm to build, test, and publish the package to the npm registry.

## Security

Nothing is perfectly secure, but I have tried to think through the security implications of running a helper like this. Here are some thoughts and I would definitely welcome any others in the issues or discussions sections:
Expand Down
34 changes: 34 additions & 0 deletions add-shebang.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');

// Add shebang and make files executable for global npm installation
const shebang = '#!/usr/bin/env node\n';
const files = ['gcf-server.js', 'gcf-client.js'];
const distDir = path.join(__dirname, 'dist');

for (const file of files) {
const filePath = path.join(distDir, file);

if (fs.existsSync(filePath)) {
const content = fs.readFileSync(filePath, 'utf8');

// Only add shebang if it doesn't already exist
if (!content.startsWith('#!')) {
fs.writeFileSync(filePath, shebang + content);
console.log(`Added shebang to ${file}`);

// Make file executable
try {
fs.chmodSync(filePath, '755');
console.log(`Made ${file} executable`);
} catch (error) {
console.error(`Failed to make ${file} executable:`, error.message);
}
} else {
console.log(`Shebang already exists in ${file}`);
}
} else {
console.error(`File ${file} does not exist in dist directory`);
}
}
Loading
Loading