From 2e5909e761f40e42684fbaa71f3b5bcb49500567 Mon Sep 17 00:00:00 2001 From: Andrew Cady Date: Sat, 23 Oct 2021 12:52:42 -0400 Subject: move all files to EndoForge --- EndoForge/Makefile | 40 +++++++++++ EndoForge/README.md | 102 ++++++++++++++++++++++++++++ EndoForge/src/AnonymousAccessCommand | 126 +++++++++++++++++++++++++++++++++++ EndoForge/src/AuthorizedKeysCommand | 15 +++++ EndoForge/src/anonymous-access.conf | 10 +++ EndoForge/test/Makefile | 38 +++++++++++ EndoForge/test/tests.sh | 66 ++++++++++++++++++ Makefile | 40 ----------- README.md | 101 ---------------------------- src/AnonymousAccessCommand | 126 ----------------------------------- src/AuthorizedKeysCommand | 15 ----- src/anonymous-access.conf | 10 --- test/Makefile | 38 ----------- test/tests.sh | 66 ------------------ 14 files changed, 397 insertions(+), 396 deletions(-) create mode 100644 EndoForge/Makefile create mode 100644 EndoForge/README.md create mode 100755 EndoForge/src/AnonymousAccessCommand create mode 100755 EndoForge/src/AuthorizedKeysCommand create mode 100644 EndoForge/src/anonymous-access.conf create mode 100644 EndoForge/test/Makefile create mode 100644 EndoForge/test/tests.sh delete mode 100644 Makefile delete mode 100644 README.md delete mode 100755 src/AnonymousAccessCommand delete mode 100755 src/AuthorizedKeysCommand delete mode 100644 src/anonymous-access.conf delete mode 100644 test/Makefile delete mode 100644 test/tests.sh diff --git a/EndoForge/Makefile b/EndoForge/Makefile new file mode 100644 index 0000000..2501da9 --- /dev/null +++ b/EndoForge/Makefile @@ -0,0 +1,40 @@ +ifeq ($(shell id -u),0) +SUDO = +else +SUDO = sudo +endif + +ROOT_INSTALL = $(SUDO) install + +USER != echo "$${SUDO_USER:-$$(id -un)}" + +SSH_CONFIG_DIR = /etc/ssh +SSHD_CONFIG_DIR = $(SSH_CONFIG_DIR)/sshd_config.d +SSH_LIB_DIR = /usr/lib/ssh +USER_SSH_CONFIG_DIR = ~$(USER)/.ssh + +BROWSER != 2>/dev/null which xdg-open || which w3m || which links || which elinks + +.PHONY: install shared doc test + +doc: README.html + $(BROWSER) $< + +shared: install + git config core.self-forge true + +SRC = src +SOURCES = $(addprefix $(SRC), AnonymousAccessCommand anonymous-access.conf AuthorizedKeysCommand) + +install: + install -t $(USER_SSH_CONFIG_DIR) $(SRC)/AnonymousAccessCommand + $(ROOT_INSTALL) -d "$(SSH_CONFIG_DIR)" "$(SSHD_CONFIG_DIR)" "$(SSH_LIB_DIR)" || true + $(ROOT_INSTALL) -m0644 -t "$(SSHD_CONFIG_DIR)" $(SRC)/anonymous-access.conf || true + $(ROOT_INSTALL) -t "$(SSH_LIB_DIR)" $(SRC)/AuthorizedKeysCommand || true + [ -e "$(SSH_LIB_DIR)"/AuthorizedKeysCommand ] || $(SUDO) ln -s -t /etc/ssh "$(SSH_LIB_DIR)"/AuthorizedKeysCommand + +README.html: README.md + pandoc -t html $< > $@ + +test: + make -C test diff --git a/EndoForge/README.md b/EndoForge/README.md new file mode 100644 index 0000000..9c22bf3 --- /dev/null +++ b/EndoForge/README.md @@ -0,0 +1,102 @@ +EndoForge +--------- +Convert a Git repository into a Forge by merging this repository. + + + + + + +WHAT IT DOES +------------ + +This repository contains the code to share itself (the repository) +through Git-over-SSH. + +It also contains the code to listen for changes sent to itself through +Git-over-SSH. + + + + + + +HOW IT WORKS +------------ + +Git contains a program `git-receive-pack` which implements a git +protocol server. The `git-receive-pack` expects to be launched as an SSH +server "ForcedCommand" in a configuration that protects the server from +untrustworthy users. + +Normally, the SSH server only permits users who have already uploaded +their public keys to the server. It assumes that access should be closed +to new users. + +Installing this code reverses that assumption, granting open access to +unrecognized users. This is made safe by limiting write access to a +GIT_NAMESPACE over which the user proves global mathematical ownership with +their SSH client key. This means that the user cannot overwrite anyone else's +data. + +The user's uploaded data is still saved and is ready to be be merged into the +main repository manually, or even automatically. + + + + + +HOW TO INSTALL +-------------- + +Run: +``` + make install +``` + +This installs the `AnonymousAccessCommand` in the current user's home +directory. + +Then, if sudo access is available, it enables anonymous access by +editing the system `OpenSSH` configuration. + + + + + + +NON-ROOT INSTALLATION +--------------------- + +If sudo access is not available, you can install to a different +location: + +``` + make SSH_CONFIG_DIR=.config/ssh \ + SSHD_CONFIG_DIR=.config/ssh/config.d \ + SSH_LIB_DIR=.local/lib/ssh \ + install +``` + +Then you will need to run `OpenSSH` on a non-default port (the default +port requires root access). + + + + + + +ALTERNATIVE LOCATION OF `AnonymousAccessCommand` +------------------------------------------------ + +It is also possible to choose the location of the +`AnonymousAccessCommand` itself: + +``` + make USER_SSH_CONFIG_DIR=$HOME/.config/ssh \ + install +``` + +First you would have to make the contents of the installed file +`AuthorizedKeysCommand` vary according to that `Makefile` paremeter, by +editing `Makefile`. diff --git a/EndoForge/src/AnonymousAccessCommand b/EndoForge/src/AnonymousAccessCommand new file mode 100755 index 0000000..443d25e --- /dev/null +++ b/EndoForge/src/AnonymousAccessCommand @@ -0,0 +1,126 @@ +#!/bin/sh +default_msg() +{ + sshfpline="$(get_sshfp_authline ${SSH_CLIENT%% *})" + cat <&2 + + You are: + + $authline + $sshfpline + +EOF +} + +get_sshfp_authline() +{ + ( + r=${1:-.} + key=$(mktemp) || exit + trap 'rm -rf "$key"' EXIT + echo "$authline" > "$key" + get_sshfp "$key" "$r" + ) +} + +get_sshfp() +{ + ( + key="$1" + r="${2:-.}" + dns=$(mktemp) || exit + trap 'rm -rf "$dns"' EXIT + + ssh-keygen -r "$r" -f "$key" > "$dns" + exec < "$dns" + while read line + do + set -- $line + if [ "$3 $5" = "SSHFP 2" ] + then + echo "$line" + break + fi + done + ) +} + +ssh_client_fingerprint_base16() +{ + set -- $(get_sshfp_authline) + [ "$6" ] + echo $6 +} + +check_if_self_forge() +{ + # TODO: don't use description, but something else. + local dir="$1" + [ -d "$dir" ] || exit + [ -r "$dir"/description ] || exit + read description < "$dir"/description + if [ "$description" != self-forge ] && [ "$(GIT_DIR=$dir git config core.self-forge)" != true ] + then + echo 'Error: access denied. The specified directory is not a self-forge.' >&2 + exit + fi +} + +read authtype authline < "$SSH_USER_AUTH" || exit +[ "$authtype" = publickey ] || exit + +cmd=${SSH_ORIGINAL_COMMAND%% *} + +case "$cmd" in + git-send-pack | git-upload-pack) + GIT_NAMESPACE= + ;; + git-receive-pack) + export GIT_NAMESPACE="$(ssh_client_fingerprint_base16)" + [ "$GIT_NAMESPACE" ] || exit + ;; + *) + default_msg + exit + ;; +esac + +arg=${SSH_ORIGINAL_COMMAND#* } +arg=${arg%\'} +arg=${arg#\'} +case "$arg" in + *\'*) exit ;; + *.git) ;; + *) arg=$arg/.git ;; +esac + +dir=$(readlink -e "$arg") || exit + +check_if_self_forge "$dir" + +with_allowCurrentBranch() +{ + local cmd="$1" dir="$2" + ( + set -eC + lockfile=$GIT_DIR/index.lock + echo $$ > "$lockfile" + trap 'rm -f "$lockfile"' EXIT + + # This doesn't seem very secure. Need to patch git probably. + for deny in CurrentBranch # DeleteCurrent + do git config receive.deny$deny false + done + "$@" + for deny in CurrentBranch # DeleteCurrent + do git config receive.deny$deny true + done + ) +} + +if [ "$GIT_NAMESPACE" ] +then + GIT_DIR=$dir with_allowCurrentBranch "$cmd" "$dir" +else + "$cmd" "$dir" +fi diff --git a/EndoForge/src/AuthorizedKeysCommand b/EndoForge/src/AuthorizedKeysCommand new file mode 100755 index 0000000..6e13063 --- /dev/null +++ b/EndoForge/src/AuthorizedKeysCommand @@ -0,0 +1,15 @@ +#!/bin/sh +username=$1 +userhome=$2 +fingerprint=$3 +authline="$4 $5" + +case "$userhome" in + *'"'*) exit ;; +esac + +usercommand=$userhome/.ssh/AnonymousAccessCommand + +[ -x "$usercommand" ] || exit + +printf 'command="%s",no-port-forwarding %s\n' "$usercommand $fingerprint" "$authline" diff --git a/EndoForge/src/anonymous-access.conf b/EndoForge/src/anonymous-access.conf new file mode 100644 index 0000000..5cd6b6a --- /dev/null +++ b/EndoForge/src/anonymous-access.conf @@ -0,0 +1,10 @@ +ExposeAuthInfo=yes +AuthorizedKeysCommandUser=root +AuthorizedKeysCommand=/etc/ssh/AuthorizedKeysCommand %u %h %f "%t %k" + +# %u The username. +# %h The home directory of the user. +# %f The fingerprint of the key or certificate. +# %t The key or certificate type. +# %k The base64-encoded key or certificate for authentication. + diff --git a/EndoForge/test/Makefile b/EndoForge/test/Makefile new file mode 100644 index 0000000..3bc1a66 --- /dev/null +++ b/EndoForge/test/Makefile @@ -0,0 +1,38 @@ +.DEFAULT_GOAL = test + +include ../Makefile + +testuser = testuser + +SU = $(SUDO) su + +.PHONY: test useradd cleanuser + +useradd: + $(SUDO) useradd $(testuser) --shell /bin/bash --create-home + $(SU) - $(testuser) -c 'ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N ""' + $(SU) - $(testuser) -c 'git config --global user.name $(testuser)' + $(SU) - $(testuser) -c 'git config --global user.email $(testuser)' + +test: $(shell getent passwd $(testuser) >/dev/null || echo useradd) + $(ROOT_INSTALL) -t ~$(testuser) tests.sh + $(SU) - $(testuser) -c ./tests.sh + +ifeq ($(testuser),) +$(error testuser not defined) +endif +cleanuser_command = $(SUDO) rm -I -r ~$(testuser) + +cleanuser: + : Preparing to run destructive command: + : + : + : $(cleanuser_command) + : + : + : Press ctrl-c to abort. + : + @for n in 5 4 3 2 1; do printf ' %d\r' "$$n"; sleep 1; done + $(cleanuser_command) || true + $(SUDO) userdel testuser || true + diff --git a/EndoForge/test/tests.sh b/EndoForge/test/tests.sh new file mode 100644 index 0000000..37f819f --- /dev/null +++ b/EndoForge/test/tests.sh @@ -0,0 +1,66 @@ +#!/bin/sh +set -ex +USER=u +DIR=src/anonymous-ssh +HOST=localhost +SSH_ID=~/.ssh/id_ed25519 + +get_sshfp() +{ + ( + key="$1" + r="${2:-.}" + dns=$(mktemp) || exit + trap 'rm -rf "$dns"' EXIT + + ssh-keygen -r "$r" -f "$key" > "$dns" + exec < "$dns" + while read line + do + set -- $line + if [ "$3 $5" = "SSHFP 2" ] + then + echo "$line" + break + fi + done + ) +} + +make_test_commit() +{ + newfile=newfile.$(date -Ins|tr -d :) + touch "$newfile" + git add "$newfile" + git commit -m "$newfile" +} + +[ -e "$SSH_ID" ] || ssh-keygen -t ed25519 -f "$SSH_ID" -P '' + +git_namespace=$(set -- $(get_sshfp "$SSH_ID") && echo $6) + +ssh -o NoHostAuthenticationForLocalhost=yes $USER@$HOST -- test || true +[ ! -e anonymous-ssh ] || rm -rf anonymous-ssh +export GIT_SSH_COMMAND="ssh -o NoHostAuthenticationForLocalhost=yes -i $SSH_ID" +git clone -v ${USER}@${HOST}:${DIR} +cd anonymous-ssh + +make install + +git pull --ff-only +make_test_commit +git push -f +make_test_commit +git push +git log -n4 +git pull --ff-only +git log -n4 +git push + +# branch=$(git branch -q --show-current) +# forkname=origin-myfork +# ns_branch=refs/namespaces/$git_namespace/refs/heads/$branch +# git remote add -m "$ns_branch" "$forkname" $(git remote get-url origin) +# git push "$forkname" +# git pull "$forkname" --ff-only "$branch" +exit diff --git a/Makefile b/Makefile deleted file mode 100644 index 2501da9..0000000 --- a/Makefile +++ /dev/null @@ -1,40 +0,0 @@ -ifeq ($(shell id -u),0) -SUDO = -else -SUDO = sudo -endif - -ROOT_INSTALL = $(SUDO) install - -USER != echo "$${SUDO_USER:-$$(id -un)}" - -SSH_CONFIG_DIR = /etc/ssh -SSHD_CONFIG_DIR = $(SSH_CONFIG_DIR)/sshd_config.d -SSH_LIB_DIR = /usr/lib/ssh -USER_SSH_CONFIG_DIR = ~$(USER)/.ssh - -BROWSER != 2>/dev/null which xdg-open || which w3m || which links || which elinks - -.PHONY: install shared doc test - -doc: README.html - $(BROWSER) $< - -shared: install - git config core.self-forge true - -SRC = src -SOURCES = $(addprefix $(SRC), AnonymousAccessCommand anonymous-access.conf AuthorizedKeysCommand) - -install: - install -t $(USER_SSH_CONFIG_DIR) $(SRC)/AnonymousAccessCommand - $(ROOT_INSTALL) -d "$(SSH_CONFIG_DIR)" "$(SSHD_CONFIG_DIR)" "$(SSH_LIB_DIR)" || true - $(ROOT_INSTALL) -m0644 -t "$(SSHD_CONFIG_DIR)" $(SRC)/anonymous-access.conf || true - $(ROOT_INSTALL) -t "$(SSH_LIB_DIR)" $(SRC)/AuthorizedKeysCommand || true - [ -e "$(SSH_LIB_DIR)"/AuthorizedKeysCommand ] || $(SUDO) ln -s -t /etc/ssh "$(SSH_LIB_DIR)"/AuthorizedKeysCommand - -README.html: README.md - pandoc -t html $< > $@ - -test: - make -C test diff --git a/README.md b/README.md deleted file mode 100644 index b2935b8..0000000 --- a/README.md +++ /dev/null @@ -1,101 +0,0 @@ -AnonymousSSH ------------- - - - - - - -WHAT IT DOES ------------- - -This repository contains the code to share itself (the repository) -through Git-over-SSH. - -It also contains the code to listen for changes sent to itself through -Git-over-SSH. - - - - - - -HOW IT WORKS ------------- - -Git contains a program `git-receive-pack` which implements a git -protocol server. The `git-receive-pack` expects to be launched as an SSH -server "ForcedCommand" in a configuration that protects the server from -untrustworthy users. - -Normally, the SSH server only permits users who have already uploaded -their public keys to the server. It assumes that access should be closed -to new users. - -Installing this code reverses that assumption, granting open access to -unrecognized users. This is made safe by limiting write access to a -GIT_NAMESPACE over which the user proves global mathematical ownership with -their SSH client key. This means that the user cannot overwrite anyone else's -data. - -The user's uploaded data is still saved and is ready to be be merged into the -main repository manually, or even automatically. - - - - - -HOW TO INSTALL --------------- - -Run: -``` - make install -``` - -This installs the `AnonymousAccessCommand` in the current user's home -directory. - -Then, if sudo access is available, it enables anonymous access by -editing the system `OpenSSH` configuration. - - - - - - -NON-ROOT INSTALLATION ---------------------- - -If sudo access is not available, you can install to a different -location: - -``` - make SSH_CONFIG_DIR=.config/ssh \ - SSHD_CONFIG_DIR=.config/ssh/config.d \ - SSH_LIB_DIR=.local/lib/ssh \ - install -``` - -Then you will need to run `OpenSSH` on a non-default port (the default -port requires root access). - - - - - - -ALTERNATIVE LOCATION OF `AnonymousAccessCommand` ------------------------------------------------- - -It is also possible to choose the location of the -`AnonymousAccessCommand` itself: - -``` - make USER_SSH_CONFIG_DIR=$HOME/.config/ssh \ - install -``` - -First you would have to make the contents of the installed file -`AuthorizedKeysCommand` vary according to that `Makefile` paremeter, by -editing `Makefile`. diff --git a/src/AnonymousAccessCommand b/src/AnonymousAccessCommand deleted file mode 100755 index 443d25e..0000000 --- a/src/AnonymousAccessCommand +++ /dev/null @@ -1,126 +0,0 @@ -#!/bin/sh -default_msg() -{ - sshfpline="$(get_sshfp_authline ${SSH_CLIENT%% *})" - cat <&2 - - You are: - - $authline - $sshfpline - -EOF -} - -get_sshfp_authline() -{ - ( - r=${1:-.} - key=$(mktemp) || exit - trap 'rm -rf "$key"' EXIT - echo "$authline" > "$key" - get_sshfp "$key" "$r" - ) -} - -get_sshfp() -{ - ( - key="$1" - r="${2:-.}" - dns=$(mktemp) || exit - trap 'rm -rf "$dns"' EXIT - - ssh-keygen -r "$r" -f "$key" > "$dns" - exec < "$dns" - while read line - do - set -- $line - if [ "$3 $5" = "SSHFP 2" ] - then - echo "$line" - break - fi - done - ) -} - -ssh_client_fingerprint_base16() -{ - set -- $(get_sshfp_authline) - [ "$6" ] - echo $6 -} - -check_if_self_forge() -{ - # TODO: don't use description, but something else. - local dir="$1" - [ -d "$dir" ] || exit - [ -r "$dir"/description ] || exit - read description < "$dir"/description - if [ "$description" != self-forge ] && [ "$(GIT_DIR=$dir git config core.self-forge)" != true ] - then - echo 'Error: access denied. The specified directory is not a self-forge.' >&2 - exit - fi -} - -read authtype authline < "$SSH_USER_AUTH" || exit -[ "$authtype" = publickey ] || exit - -cmd=${SSH_ORIGINAL_COMMAND%% *} - -case "$cmd" in - git-send-pack | git-upload-pack) - GIT_NAMESPACE= - ;; - git-receive-pack) - export GIT_NAMESPACE="$(ssh_client_fingerprint_base16)" - [ "$GIT_NAMESPACE" ] || exit - ;; - *) - default_msg - exit - ;; -esac - -arg=${SSH_ORIGINAL_COMMAND#* } -arg=${arg%\'} -arg=${arg#\'} -case "$arg" in - *\'*) exit ;; - *.git) ;; - *) arg=$arg/.git ;; -esac - -dir=$(readlink -e "$arg") || exit - -check_if_self_forge "$dir" - -with_allowCurrentBranch() -{ - local cmd="$1" dir="$2" - ( - set -eC - lockfile=$GIT_DIR/index.lock - echo $$ > "$lockfile" - trap 'rm -f "$lockfile"' EXIT - - # This doesn't seem very secure. Need to patch git probably. - for deny in CurrentBranch # DeleteCurrent - do git config receive.deny$deny false - done - "$@" - for deny in CurrentBranch # DeleteCurrent - do git config receive.deny$deny true - done - ) -} - -if [ "$GIT_NAMESPACE" ] -then - GIT_DIR=$dir with_allowCurrentBranch "$cmd" "$dir" -else - "$cmd" "$dir" -fi diff --git a/src/AuthorizedKeysCommand b/src/AuthorizedKeysCommand deleted file mode 100755 index 6e13063..0000000 --- a/src/AuthorizedKeysCommand +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh -username=$1 -userhome=$2 -fingerprint=$3 -authline="$4 $5" - -case "$userhome" in - *'"'*) exit ;; -esac - -usercommand=$userhome/.ssh/AnonymousAccessCommand - -[ -x "$usercommand" ] || exit - -printf 'command="%s",no-port-forwarding %s\n' "$usercommand $fingerprint" "$authline" diff --git a/src/anonymous-access.conf b/src/anonymous-access.conf deleted file mode 100644 index 5cd6b6a..0000000 --- a/src/anonymous-access.conf +++ /dev/null @@ -1,10 +0,0 @@ -ExposeAuthInfo=yes -AuthorizedKeysCommandUser=root -AuthorizedKeysCommand=/etc/ssh/AuthorizedKeysCommand %u %h %f "%t %k" - -# %u The username. -# %h The home directory of the user. -# %f The fingerprint of the key or certificate. -# %t The key or certificate type. -# %k The base64-encoded key or certificate for authentication. - diff --git a/test/Makefile b/test/Makefile deleted file mode 100644 index 3bc1a66..0000000 --- a/test/Makefile +++ /dev/null @@ -1,38 +0,0 @@ -.DEFAULT_GOAL = test - -include ../Makefile - -testuser = testuser - -SU = $(SUDO) su - -.PHONY: test useradd cleanuser - -useradd: - $(SUDO) useradd $(testuser) --shell /bin/bash --create-home - $(SU) - $(testuser) -c 'ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N ""' - $(SU) - $(testuser) -c 'git config --global user.name $(testuser)' - $(SU) - $(testuser) -c 'git config --global user.email $(testuser)' - -test: $(shell getent passwd $(testuser) >/dev/null || echo useradd) - $(ROOT_INSTALL) -t ~$(testuser) tests.sh - $(SU) - $(testuser) -c ./tests.sh - -ifeq ($(testuser),) -$(error testuser not defined) -endif -cleanuser_command = $(SUDO) rm -I -r ~$(testuser) - -cleanuser: - : Preparing to run destructive command: - : - : - : $(cleanuser_command) - : - : - : Press ctrl-c to abort. - : - @for n in 5 4 3 2 1; do printf ' %d\r' "$$n"; sleep 1; done - $(cleanuser_command) || true - $(SUDO) userdel testuser || true - diff --git a/test/tests.sh b/test/tests.sh deleted file mode 100644 index 37f819f..0000000 --- a/test/tests.sh +++ /dev/null @@ -1,66 +0,0 @@ -#!/bin/sh -set -ex -USER=u -DIR=src/anonymous-ssh -HOST=localhost -SSH_ID=~/.ssh/id_ed25519 - -get_sshfp() -{ - ( - key="$1" - r="${2:-.}" - dns=$(mktemp) || exit - trap 'rm -rf "$dns"' EXIT - - ssh-keygen -r "$r" -f "$key" > "$dns" - exec < "$dns" - while read line - do - set -- $line - if [ "$3 $5" = "SSHFP 2" ] - then - echo "$line" - break - fi - done - ) -} - -make_test_commit() -{ - newfile=newfile.$(date -Ins|tr -d :) - touch "$newfile" - git add "$newfile" - git commit -m "$newfile" -} - -[ -e "$SSH_ID" ] || ssh-keygen -t ed25519 -f "$SSH_ID" -P '' - -git_namespace=$(set -- $(get_sshfp "$SSH_ID") && echo $6) - -ssh -o NoHostAuthenticationForLocalhost=yes $USER@$HOST -- test || true -[ ! -e anonymous-ssh ] || rm -rf anonymous-ssh -export GIT_SSH_COMMAND="ssh -o NoHostAuthenticationForLocalhost=yes -i $SSH_ID" -git clone -v ${USER}@${HOST}:${DIR} -cd anonymous-ssh - -make install - -git pull --ff-only -make_test_commit -git push -f -make_test_commit -git push -git log -n4 -git pull --ff-only -git log -n4 -git push - -# branch=$(git branch -q --show-current) -# forkname=origin-myfork -# ns_branch=refs/namespaces/$git_namespace/refs/heads/$branch -# git remote add -m "$ns_branch" "$forkname" $(git remote get-url origin) -# git push "$forkname" -# git pull "$forkname" --ff-only "$branch" -exit -- cgit v1.2.3