├── .editorconfig
├── .eslintrc.yml
├── .github
├── FUNDING.yml
├── actions
│ └── cache-npm
│ │ └── action.yml
└── workflows
│ ├── build.yml
│ ├── reusable-verify.yml
│ ├── verify-on-container-alpine.yml
│ ├── verify-on-container-centos.yml
│ ├── verify-on-container-ubuntu.yml
│ ├── verify-on-macos.yml
│ ├── verify-on-ubuntu.yml
│ └── verify-on-windows.yml
├── .gitignore
├── .markdownlint.yml
├── .npmrc
├── CHANGELOG.md
├── LICENSE
├── README.md
├── action.yml
├── dist
├── main.js
├── main.js.map
├── post.js
└── post.js.map
├── package-lock.json
├── package.json
├── scripts
├── prepare-release.sh
├── rebuild.sh
└── update-dependencies.sh
├── src
├── common.ts
├── main.ts
└── post.ts
├── ssh-key-action.code-workspace
└── tsconfig.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org/
2 | # https://github.com/editorconfig/editorconfig/wiki/EditorConfig-Properties
3 | root = true
4 |
5 |
6 | # recommended settings; DO NOT CHANGE!
7 | [*]
8 | end_of_line = lf
9 | charset = utf-8
10 | trim_trailing_whitespace = true
11 | insert_final_newline = true
12 |
13 |
14 | # common settings
15 | [*]
16 | indent_style = tab
17 | indent_size = 4
18 | tab_width = 4
19 | max_line_length = off
20 |
21 | quote_type = double
22 | curly_bracket_next_line = true
23 | spaces_around_operators = true
24 | spaces_around_brackets = none
25 | indent_brace_style = allman
26 |
27 |
28 | # JavaScript/TypeScript
29 | [*.{js,ts}]
30 | indent_style = space
31 | curly_bracket_next_line = false
32 | indent_brace_style = K&R
33 |
34 |
35 | # JSON/YAML
36 | [*.{json,babelrc,code-workspace,yml,yaml}]
37 | indent_style = space
38 | indent_size = 2
39 | tab_width = 2
40 |
41 |
42 | # markdown
43 | [*.md]
44 | indent_style = space
45 |
--------------------------------------------------------------------------------
/.eslintrc.yml:
--------------------------------------------------------------------------------
1 | # https://eslint.org/
2 | env: # https://eslint.org/docs/user-guide/configuring#specifying-environments
3 | es6: true
4 | node: true
5 | extends:
6 | - eslint:recommended
7 | - plugin:@typescript-eslint/eslint-recommended
8 | - plugin:@typescript-eslint/recommended
9 | plugins:
10 | - "@typescript-eslint"
11 | parser: "@typescript-eslint/parser"
12 | parserOptions:
13 | sourceType: module
14 | project: ./tsconfig.json
15 | rules: # https://eslint.org/docs/rules/
16 | accessor-pairs: error
17 | array-bracket-newline:
18 | - error
19 | - consistent
20 | array-bracket-spacing:
21 | - error
22 | - never
23 | array-callback-return: error
24 | arrow-body-style: 'off'
25 | arrow-parens:
26 | - error
27 | - always
28 | arrow-spacing:
29 | - error
30 | - after: true
31 | before: true
32 | block-scoped-var: error
33 | block-spacing: error
34 | brace-style: 'off' # see "@typescript-eslint/brace-style"
35 | callback-return: error
36 | capitalized-comments: 'off'
37 | class-methods-use-this: error
38 | comma-dangle:
39 | - error
40 | - always-multiline
41 | comma-spacing:
42 | - error
43 | - after: true
44 | before: false
45 | comma-style:
46 | - error
47 | - last
48 | complexity:
49 | - error
50 | computed-property-spacing:
51 | - error
52 | - never
53 | consistent-return: 'off'
54 | consistent-this: error
55 | curly: error
56 | default-case: 'off'
57 | dot-location:
58 | - error
59 | - property
60 | dot-notation:
61 | - error
62 | - allowPattern: "^_"
63 | eol-last: error
64 | eqeqeq: error
65 | for-direction: error
66 | func-call-spacing: error
67 | func-name-matching: error
68 | func-names:
69 | - error
70 | - never
71 | func-style:
72 | - error
73 | - declaration
74 | function-paren-newline:
75 | - error
76 | - multiline-arguments
77 | generator-star-spacing: error
78 | getter-return: error
79 | global-require: error
80 | guard-for-in: error
81 | handle-callback-err: error
82 | id-blacklist: error
83 | id-length: error
84 | id-match: error
85 | implicit-arrow-linebreak:
86 | - error
87 | - below
88 | indent:
89 | - error
90 | - 4
91 | - SwitchCase: 1
92 | indent-legacy: 'off'
93 | init-declarations: error
94 | jsx-quotes: error
95 | key-spacing: error
96 | keyword-spacing:
97 | - error
98 | line-comment-position: 'off'
99 | linebreak-style:
100 | - error
101 | - unix
102 | lines-around-comment: 'off'
103 | lines-around-directive: error
104 | lines-between-class-members:
105 | - error
106 | - always
107 | - exceptAfterSingleLine: true
108 | max-depth: error
109 | max-len: 'off'
110 | max-lines: 'off'
111 | max-nested-callbacks: error
112 | max-params: 'off'
113 | max-statements: 'off'
114 | max-statements-per-line: error
115 | multiline-comment-style:
116 | - error
117 | - separate-lines
118 | multiline-ternary: error
119 | new-cap: error
120 | new-parens: error
121 | newline-after-var: 'off'
122 | newline-before-return: 'off'
123 | no-alert: error
124 | no-array-constructor: error
125 | no-await-in-loop: error
126 | no-bitwise: error
127 | no-buffer-constructor: error
128 | no-caller: error
129 | no-catch-shadow: error
130 | no-confusing-arrow: error
131 | no-continue: 'off'
132 | no-div-regex: error
133 | no-duplicate-imports: error
134 | no-else-return: 'off'
135 | no-empty-function: 'off'
136 | no-eq-null: error
137 | no-eval: error
138 | no-extend-native: error
139 | no-extra-bind: error
140 | no-extra-label: error
141 | no-extra-parens: error
142 | no-extra-semi: error
143 | no-floating-decimal: error
144 | no-implicit-coercion: error
145 | no-implicit-globals: error
146 | no-implied-eval: error
147 | no-inline-comments: 'off'
148 | no-invalid-this: error
149 | no-iterator: error
150 | no-label-var: error
151 | no-labels: error
152 | no-lonely-if: error
153 | no-loop-func: error
154 | no-magic-numbers: 'off'
155 | no-mixed-operators: error
156 | no-mixed-requires: error
157 | no-multi-assign: error
158 | no-multi-spaces: error
159 | no-multi-str: error
160 | no-multiple-empty-lines:
161 | - error
162 | - max: 1
163 | no-native-reassign: error
164 | no-negated-condition: 'off'
165 | no-negated-in-lhs: error
166 | no-nested-ternary: error
167 | no-new: error
168 | no-new-func: error
169 | no-new-object: error
170 | no-new-require: error
171 | no-new-wrappers: error
172 | no-octal-escape: error
173 | no-param-reassign: 'off'
174 | no-path-concat: error
175 | no-plusplus: 'off'
176 | no-process-env: 'off'
177 | no-process-exit: error
178 | no-proto: error
179 | no-prototype-builtins: 'off'
180 | no-restricted-globals: error
181 | no-restricted-imports: error
182 | no-restricted-modules: error
183 | no-restricted-properties: error
184 | no-restricted-syntax: error
185 | no-return-assign: error
186 | no-return-await: error
187 | no-script-url: error
188 | no-self-compare: error
189 | no-sequences: error
190 | no-shadow: error
191 | no-shadow-restricted-names: error
192 | no-spaced-func: error
193 | no-sync: 'off'
194 | no-tabs: 'off'
195 | no-template-curly-in-string: error
196 | no-ternary: error
197 | no-throw-literal: error
198 | no-trailing-spaces: error
199 | no-undef-init: error
200 | no-undefined: 'off'
201 | no-underscore-dangle: 'off'
202 | no-unmodified-loop-condition: error
203 | no-unneeded-ternary: error
204 | no-unused-expressions: error
205 | no-use-before-define: 'off'
206 | no-useless-call: error
207 | no-useless-computed-key: error
208 | no-useless-concat: error
209 | no-useless-constructor: error
210 | no-useless-rename: error
211 | no-useless-return: error
212 | no-var: error
213 | no-void: error
214 | no-warning-comments: error
215 | no-whitespace-before-property: error
216 | no-with: error
217 | nonblock-statement-body-position: error
218 | object-curly-newline: 'off'
219 | object-curly-spacing:
220 | - error
221 | - never
222 | object-property-newline: error
223 | object-shorthand: 'off'
224 | one-var: 'off'
225 | one-var-declaration-per-line: error
226 | operator-assignment: error
227 | operator-linebreak: error
228 | padded-blocks: 'off'
229 | padding-line-between-statements: error
230 | prefer-arrow-callback: error
231 | prefer-const: error
232 | prefer-destructuring: error
233 | prefer-numeric-literals: error
234 | prefer-promise-reject-errors: error
235 | prefer-reflect: error
236 | prefer-rest-params: error
237 | prefer-spread: error
238 | prefer-template: error
239 | quote-props: 'off'
240 | quotes: 'off'
241 | radix: error
242 | require-await: error
243 | require-jsdoc: error
244 | rest-spread-spacing:
245 | - error
246 | - never
247 | semi: 'off' # see "@typescript-eslint/semi"
248 | semi-spacing: error
249 | semi-style:
250 | - error
251 | - last
252 | sort-imports:
253 | - error
254 | - ignoreCase: false
255 | ignoreDeclarationSort: true
256 | sort-keys: 'off'
257 | sort-vars: error
258 | space-before-blocks: error
259 | space-before-function-paren: 'off'
260 | space-in-parens:
261 | - error
262 | - never
263 | space-infix-ops: error
264 | space-unary-ops: error
265 | spaced-comment:
266 | - error
267 | - always
268 | strict: error
269 | switch-colon-spacing: error
270 | symbol-description: error
271 | template-curly-spacing:
272 | - error
273 | - never
274 | template-tag-spacing: error
275 | unicode-bom:
276 | - error
277 | - never
278 | valid-jsdoc:
279 | - error
280 | - prefer:
281 | arg: param
282 | argument: param
283 | return: returns
284 | yield: yields
285 | exception: throws
286 | requireReturn: false
287 | requireParamType: false
288 | requireReturnType: false
289 | vars-on-top: error
290 | wrap-iife: error
291 | wrap-regex: error
292 | yield-star-spacing: error
293 | yoda:
294 | - error
295 | - never
296 |
297 | # @typescript-eslint plugin
298 | "@typescript-eslint/ban-ts-ignore": 'off'
299 | "@typescript-eslint/brace-style":
300 | - error
301 | - 1tbs
302 | "@typescript-eslint/member-delimiter-style":
303 | - error
304 | - multiline:
305 | delimiter: semi
306 | requireLast: true
307 | singleline:
308 | delimiter: semi
309 | requireLast: true
310 | "@typescript-eslint/no-empty-interface": 'off'
311 | "@typescript-eslint/no-floating-promises": error
312 | "@typescript-eslint/no-use-before-define":
313 | - error
314 | - functions: false
315 | "@typescript-eslint/semi": error
316 | "@typescript-eslint/strict-boolean-expressions":
317 | - error
318 | - allowString: false
319 | allowNumber: false
320 | allowNullableObject: false
321 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github:
2 | - shimataro
3 | custom:
4 | - "https://www.paypal.me/shimataro"
5 |
--------------------------------------------------------------------------------
/.github/actions/cache-npm/action.yml:
--------------------------------------------------------------------------------
1 | # https://docs.github.com/en/actions/creating-actions/creating-a-composite-action
2 | name: Cache NPM
3 | description: Composite action (cache NPM)
4 |
5 | runs:
6 | using: composite
7 | steps:
8 | # https://github.com/actions/cache/blob/master/examples.md#node---npm
9 | # https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-output-parameter
10 | - name: Get NPM cache directory
11 | run: echo "NPM_CACHE_DIRECTORY=$(npm config get cache)" >> ${{ github.env }} # use "github.env" instead of "github.output"; if use "github.output", "Warning: Input required and not supplied: path" is displayed on post-process
12 | shell: pwsh # use PowerShell; Bash doesn't work on Windows because the value of "github.env" is like "D:\a\_temp\_runner_file_commands\set_env_XXX".
13 |
14 | - name: Get Node.js version
15 | run: echo "NODEJS_VERSION=$(node -v)" >> ${{ github.env }}
16 | shell: pwsh
17 |
18 | - name: Cache NPM modules
19 | uses: actions/cache@v3
20 | with:
21 | path: ${{ env.NPM_CACHE_DIRECTORY }}
22 | key: npm-${{ runner.os }}-${{ runner.arch }}-${{ env.NODEJS_VERSION }}-${{ hashFiles('package-lock.json') }}
23 | restore-keys: |
24 | npm-${{ runner.os }}-${{ runner.arch }}-${{ env.NODEJS_VERSION }}-
25 | npm-${{ runner.os }}-${{ runner.arch }}
26 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | # https://help.github.com/en/articles/workflow-syntax-for-github-actions
2 |
3 | name: Build
4 |
5 | on:
6 | - push
7 |
8 | jobs:
9 | build:
10 | name: Build
11 | runs-on: ${{ matrix.os }}
12 | strategy:
13 | matrix:
14 | os:
15 | - windows-2019
16 | - windows-2022
17 | - macos-11
18 | - macos-12
19 | - ubuntu-20.04
20 | - ubuntu-22.04
21 | nodejs:
22 | - 20
23 | fail-fast: false
24 | steps:
25 | - name: Turn off auto-crlf
26 | run: git config --global core.autocrlf false
27 | - name: Checkout source codes
28 | uses: actions/checkout@v4
29 | - name: Install Node.js
30 | uses: actions/setup-node@v4
31 | with:
32 | node-version: ${{ matrix.nodejs }}
33 | - name: Cache NPM
34 | uses: ./.github/actions/cache-npm
35 | - name: Install dependencies
36 | run: npm ci
37 | - name: Build
38 | run: npm run build
39 | - name: Exit if differ (forgot to commit dist dir?)
40 | run: git diff --exit-code --quiet
41 | - name: Verify
42 | run: npm run verify
43 |
--------------------------------------------------------------------------------
/.github/workflows/reusable-verify.yml:
--------------------------------------------------------------------------------
1 | # https://docs.github.com/en/actions/using-workflows/reusing-workflows
2 | name: Reusable workflow (verify)
3 |
4 | on:
5 | workflow_call:
6 | inputs:
7 | os:
8 | required: true
9 | type: string
10 | description: host OS that CI 'runs-on'
11 | docker_image:
12 | required: false
13 | type: string
14 | default: ""
15 | description: Docker image name
16 | package_installation_command:
17 | required: false
18 | type: string
19 | default: ""
20 | description: package installation command
21 | secrets:
22 | SSH_KEY_PEM:
23 | required: true
24 | description: SSH private key (PEM format)
25 | SSH_KEY_PKCS8:
26 | required: true
27 | description: SSH private key (PKCS8 format)
28 | SSH_KEY_RFC4716:
29 | required: true
30 | description: SSH private key (RFC4716 format)
31 |
32 | jobs:
33 | ssh-pem:
34 | name: Connect to github.com (PEM format)
35 | runs-on: ${{ inputs.os }}
36 | container: ${{ inputs.docker_image }}
37 | steps:
38 | - name: Install packages
39 | run: ${{ inputs.package_installation_command }}
40 | if: ${{ inputs.package_installation_command != '' }}
41 | - name: Checkout source codes
42 | uses: actions/checkout@v3
43 | - name: Install SSH key
44 | uses: ./.
45 | with:
46 | key: ${{ secrets.SSH_KEY_PEM }}
47 | known_hosts: unnecessary
48 | - name: git clone through SSH
49 | run: git clone git@github.com:shimataro/ssh-key-action.git tmp
50 |
51 | ssh-pem-bitbucket:
52 | name: Connect to bitbucket.org (PEM format)
53 | runs-on: ${{ inputs.os }}
54 | container: ${{ inputs.docker_image }}
55 | steps:
56 | - name: Install packages
57 | run: ${{ inputs.package_installation_command }}
58 | if: ${{ inputs.package_installation_command != '' }}
59 | - name: Checkout source codes
60 | uses: actions/checkout@v3
61 | - name: Install SSH key
62 | uses: ./.
63 | with:
64 | key: ${{ secrets.SSH_KEY_PEM }}
65 | known_hosts: |
66 | bitbucket.org ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDQeJzhupRu0u0cdegZIa8e86EG2qOCsIsD1Xw0xSeiPDlCr7kq97NLmMbpKTX6Esc30NuoqEEHCuc7yWtwp8dI76EEEB1VqY9QJq6vk+aySyboD5QF61I/1WeTwu+deCbgKMGbUijeXhtfbxSxm6JwGrXrhBdofTsbKRUsrN1WoNgUa8uqN1Vx6WAJw1JHPhglEGGHea6QICwJOAr/6mrui/oB7pkaWKHj3z7d1IC4KWLtY47elvjbaTlkN04Kc/5LFEirorGYVbt15kAUlqGM65pk6ZBxtaO3+30LVlORZkxOh+LKL/BvbZ/iRNhItLqNyieoQj/uh/7Iv4uyH/cV/0b4WDSd3DptigWq84lJubb9t/DnZlrJazxyDCulTmKdOR7vs9gMTo+uoIrPSb8ScTtvw65+odKAlBj59dhnVp9zd7QUojOpXlL62Aw56U4oO+FALuevvMjiWeavKhJqlR7i5n9srYcrNV7ttmDw7kf/97P5zauIhxcjX+xHv4M=
67 | bitbucket.org ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPIQmuzMBuKdWeF4+a2sjSSpBK0iqitSQ+5BM9KhpexuGt20JpTVM7u5BDZngncgrqDMbWdxMWWOGtZ9UgbqgZE=
68 | bitbucket.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIazEu89wgQZ4bqs3d63QSMzYVa0MuJ2e2gKTKqu+UUO
69 | - name: git clone through SSH
70 | run: git clone git@bitbucket.org:shimataro999/ssh-test.git tmp
71 |
72 | ssh-pkcs8:
73 | name: Connect to github.com (PKCS8 format)
74 | runs-on: ${{ inputs.os }}
75 | container: ${{ inputs.docker_image }}
76 | steps:
77 | - name: Install packages
78 | run: ${{ inputs.package_installation_command }}
79 | if: ${{ inputs.package_installation_command != '' }}
80 | - name: Checkout source codes
81 | uses: actions/checkout@v3
82 | - name: Install SSH key
83 | uses: ./.
84 | with:
85 | key: ${{ secrets.SSH_KEY_PKCS8 }}
86 | known_hosts: unnecessary
87 | - name: git clone through SSH
88 | run: git clone git@github.com:shimataro/ssh-key-action.git tmp
89 |
90 | ssh-pkcs8-bitbucket:
91 | name: Connect to bitbucket.org (PKCS8 format)
92 | runs-on: ${{ inputs.os }}
93 | container: ${{ inputs.docker_image }}
94 | steps:
95 | - name: Install packages
96 | run: ${{ inputs.package_installation_command }}
97 | if: ${{ inputs.package_installation_command != '' }}
98 | - name: Checkout source codes
99 | uses: actions/checkout@v3
100 | - name: Install SSH key
101 | uses: ./.
102 | with:
103 | key: ${{ secrets.SSH_KEY_PKCS8 }}
104 | known_hosts: |
105 | bitbucket.org ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDQeJzhupRu0u0cdegZIa8e86EG2qOCsIsD1Xw0xSeiPDlCr7kq97NLmMbpKTX6Esc30NuoqEEHCuc7yWtwp8dI76EEEB1VqY9QJq6vk+aySyboD5QF61I/1WeTwu+deCbgKMGbUijeXhtfbxSxm6JwGrXrhBdofTsbKRUsrN1WoNgUa8uqN1Vx6WAJw1JHPhglEGGHea6QICwJOAr/6mrui/oB7pkaWKHj3z7d1IC4KWLtY47elvjbaTlkN04Kc/5LFEirorGYVbt15kAUlqGM65pk6ZBxtaO3+30LVlORZkxOh+LKL/BvbZ/iRNhItLqNyieoQj/uh/7Iv4uyH/cV/0b4WDSd3DptigWq84lJubb9t/DnZlrJazxyDCulTmKdOR7vs9gMTo+uoIrPSb8ScTtvw65+odKAlBj59dhnVp9zd7QUojOpXlL62Aw56U4oO+FALuevvMjiWeavKhJqlR7i5n9srYcrNV7ttmDw7kf/97P5zauIhxcjX+xHv4M=
106 | bitbucket.org ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPIQmuzMBuKdWeF4+a2sjSSpBK0iqitSQ+5BM9KhpexuGt20JpTVM7u5BDZngncgrqDMbWdxMWWOGtZ9UgbqgZE=
107 | bitbucket.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIazEu89wgQZ4bqs3d63QSMzYVa0MuJ2e2gKTKqu+UUO
108 | - name: git clone through SSH
109 | run: git clone git@bitbucket.org:shimataro999/ssh-test.git tmp
110 |
111 | ssh-rfc4716:
112 | name: Connect to github.com (RFC4716 format)
113 | runs-on: ${{ inputs.os }}
114 | container: ${{ inputs.docker_image }}
115 | steps:
116 | - name: Install packages
117 | run: ${{ inputs.package_installation_command }}
118 | if: ${{ inputs.package_installation_command != '' }}
119 | - name: Checkout source codes
120 | uses: actions/checkout@v3
121 | - name: Install SSH key
122 | uses: ./.
123 | with:
124 | key: ${{ secrets.SSH_KEY_RFC4716 }}
125 | known_hosts: unnecessary
126 | - name: git clone through SSH
127 | run: git clone git@github.com:shimataro/ssh-key-action.git tmp
128 |
129 | ssh-rfc4716-bitbucket:
130 | name: Connect to bitbucket.org (RFC4716 format)
131 | runs-on: ${{ inputs.os }}
132 | container: ${{ inputs.docker_image }}
133 | steps:
134 | - name: Install packages
135 | run: ${{ inputs.package_installation_command }}
136 | if: ${{ inputs.package_installation_command != '' }}
137 | - name: Checkout source codes
138 | uses: actions/checkout@v3
139 | - name: Install SSH key
140 | uses: ./.
141 | with:
142 | key: ${{ secrets.SSH_KEY_RFC4716 }}
143 | known_hosts: |
144 | bitbucket.org ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDQeJzhupRu0u0cdegZIa8e86EG2qOCsIsD1Xw0xSeiPDlCr7kq97NLmMbpKTX6Esc30NuoqEEHCuc7yWtwp8dI76EEEB1VqY9QJq6vk+aySyboD5QF61I/1WeTwu+deCbgKMGbUijeXhtfbxSxm6JwGrXrhBdofTsbKRUsrN1WoNgUa8uqN1Vx6WAJw1JHPhglEGGHea6QICwJOAr/6mrui/oB7pkaWKHj3z7d1IC4KWLtY47elvjbaTlkN04Kc/5LFEirorGYVbt15kAUlqGM65pk6ZBxtaO3+30LVlORZkxOh+LKL/BvbZ/iRNhItLqNyieoQj/uh/7Iv4uyH/cV/0b4WDSd3DptigWq84lJubb9t/DnZlrJazxyDCulTmKdOR7vs9gMTo+uoIrPSb8ScTtvw65+odKAlBj59dhnVp9zd7QUojOpXlL62Aw56U4oO+FALuevvMjiWeavKhJqlR7i5n9srYcrNV7ttmDw7kf/97P5zauIhxcjX+xHv4M=
145 | bitbucket.org ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPIQmuzMBuKdWeF4+a2sjSSpBK0iqitSQ+5BM9KhpexuGt20JpTVM7u5BDZngncgrqDMbWdxMWWOGtZ9UgbqgZE=
146 | bitbucket.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIazEu89wgQZ4bqs3d63QSMzYVa0MuJ2e2gKTKqu+UUO
147 | - name: git clone through SSH
148 | run: git clone git@bitbucket.org:shimataro999/ssh-test.git tmp
149 |
150 | key_if_exists_replace-key_exists:
151 | name: if_key_exists=replace / key exists
152 | runs-on: ${{ inputs.os }}
153 | container: ${{ inputs.docker_image }}
154 | steps:
155 | - name: Install packages
156 | run: ${{ inputs.package_installation_command }}
157 | if: ${{ inputs.package_installation_command != '' }}
158 | - name: Checkout source codes
159 | uses: actions/checkout@v3
160 | - name: Install SSH key (dummy)
161 | uses: ./.
162 | with:
163 | key: "dummy" # replaced
164 | known_hosts: unnecessary
165 | - name: Install SSH key (replaces existing key)
166 | uses: ./.
167 | with:
168 | key: ${{ secrets.SSH_KEY_PEM }}
169 | known_hosts: unnecessary
170 | if_key_exists: replace
171 | - name: git clone through SSH
172 | run: git clone git@github.com:shimataro/ssh-key-action.git tmp
173 |
174 | key_if_exists_replace-key_doesnt_exist:
175 | name: if_key_exists=replace / key doesn't exist
176 | runs-on: ${{ inputs.os }}
177 | container: ${{ inputs.docker_image }}
178 | steps:
179 | - name: Install packages
180 | run: ${{ inputs.package_installation_command }}
181 | if: ${{ inputs.package_installation_command != '' }}
182 | - name: Checkout source codes
183 | uses: actions/checkout@v3
184 | - name: Install SSH key
185 | uses: ./.
186 | with:
187 | key: ${{ secrets.SSH_KEY_PEM }}
188 | known_hosts: unnecessary
189 | if_key_exists: replace
190 | - name: git clone through SSH
191 | run: git clone git@github.com:shimataro/ssh-key-action.git tmp
192 |
193 | key_if_exists_ignore-key_exists:
194 | name: if_key_exists=ignore / key exists
195 | runs-on: ${{ inputs.os }}
196 | container: ${{ inputs.docker_image }}
197 | steps:
198 | - name: Install packages
199 | run: ${{ inputs.package_installation_command }}
200 | if: ${{ inputs.package_installation_command != '' }}
201 | - name: Checkout source codes
202 | uses: actions/checkout@v3
203 | - name: Install SSH key
204 | uses: ./.
205 | with:
206 | key: ${{ secrets.SSH_KEY_PEM }}
207 | known_hosts: unnecessary
208 | - name: Install SSH key (does nothing)
209 | uses: ./.
210 | with:
211 | key: "dummy" # ignored
212 | known_hosts: unnecessary
213 | if_key_exists: ignore
214 | - name: git clone through SSH
215 | run: git clone git@github.com:shimataro/ssh-key-action.git tmp
216 |
217 | key_if_exists_ignore-key_doesnt_exist:
218 | name: if_key_exists=ignore / key doesn't exist
219 | runs-on: ${{ inputs.os }}
220 | container: ${{ inputs.docker_image }}
221 | steps:
222 | - name: Install packages
223 | run: ${{ inputs.package_installation_command }}
224 | if: ${{ inputs.package_installation_command != '' }}
225 | - name: Checkout source codes
226 | uses: actions/checkout@v3
227 | - name: Install SSH key
228 | uses: ./.
229 | with:
230 | key: ${{ secrets.SSH_KEY_PEM }}
231 | known_hosts: unnecessary
232 | if_key_exists: ignore
233 | - name: git clone through SSH
234 | run: git clone git@github.com:shimataro/ssh-key-action.git tmp
235 |
236 | key_if_exists_fail-key_exists:
237 | name: if_key_exists=fail / key exists
238 | runs-on: ${{ inputs.os }}
239 | container: ${{ inputs.docker_image }}
240 | steps:
241 | - name: Install packages
242 | run: ${{ inputs.package_installation_command }}
243 | if: ${{ inputs.package_installation_command != '' }}
244 | - name: Checkout source codes
245 | uses: actions/checkout@v3
246 | - name: Install SSH key
247 | uses: ./.
248 | with:
249 | key: ${{ secrets.SSH_KEY_PEM }}
250 | known_hosts: unnecessary
251 | - name: Install SSH key (fails)
252 | uses: ./.
253 | with:
254 | key: "dummy" # fails
255 | known_hosts: unnecessary
256 | if_key_exists: fail
257 | continue-on-error: true
258 | - name: git clone through SSH
259 | run: git clone git@github.com:shimataro/ssh-key-action.git tmp
260 |
261 | key_if_exists_fail-key_doesnt_exist:
262 | name: if_key_exists=fail / key doesn't exist
263 | runs-on: ${{ inputs.os }}
264 | container: ${{ inputs.docker_image }}
265 | steps:
266 | - name: Install packages
267 | run: ${{ inputs.package_installation_command }}
268 | if: ${{ inputs.package_installation_command != '' }}
269 | - name: Checkout source codes
270 | uses: actions/checkout@v3
271 | - name: Install SSH key
272 | uses: ./.
273 | with:
274 | key: ${{ secrets.SSH_KEY_PEM }}
275 | known_hosts: unnecessary
276 | if_key_exists: fail
277 | - name: git clone through SSH
278 | run: git clone git@github.com:shimataro/ssh-key-action.git tmp
279 |
--------------------------------------------------------------------------------
/.github/workflows/verify-on-container-alpine.yml:
--------------------------------------------------------------------------------
1 | # https://help.github.com/en/articles/workflow-syntax-for-github-actions
2 | name: Docker container (Alpine Linux)
3 |
4 | on:
5 | - push
6 |
7 | jobs:
8 | verify:
9 | name: Verify
10 | strategy:
11 | fail-fast: false
12 | matrix:
13 | os:
14 | - ubuntu-20.04
15 | - ubuntu-22.04
16 | docker_image:
17 | - alpine:3.10
18 | - alpine:3.11
19 | - alpine:3.12
20 | - alpine:3.13
21 | uses: "./.github/workflows/reusable-verify.yml"
22 | with:
23 | os: ${{ matrix.os }}
24 | docker_image: ${{ matrix.docker_image }}
25 | package_installation_command: apk add openssh-client git
26 | secrets: inherit
27 |
--------------------------------------------------------------------------------
/.github/workflows/verify-on-container-centos.yml:
--------------------------------------------------------------------------------
1 | # https://help.github.com/en/articles/workflow-syntax-for-github-actions
2 | name: Docker container (CentOS)
3 |
4 | on:
5 | - push
6 |
7 | jobs:
8 | verify:
9 | name: Verify
10 | strategy:
11 | fail-fast: false
12 | matrix:
13 | os:
14 | - ubuntu-20.04
15 | - ubuntu-22.04
16 | docker_image:
17 | - quay.io/centos/centos:stream8
18 | uses: "./.github/workflows/reusable-verify.yml"
19 | with:
20 | os: ${{ matrix.os }}
21 | docker_image: ${{ matrix.docker_image }}
22 | package_installation_command: yum install -y git openssh-clients
23 | secrets: inherit
24 |
--------------------------------------------------------------------------------
/.github/workflows/verify-on-container-ubuntu.yml:
--------------------------------------------------------------------------------
1 | # https://help.github.com/en/articles/workflow-syntax-for-github-actions
2 | name: Docker container (Ubuntu)
3 |
4 | on:
5 | - push
6 |
7 | jobs:
8 | verify:
9 | name: Verify
10 | strategy:
11 | fail-fast: false
12 | matrix:
13 | os:
14 | - ubuntu-20.04
15 | - ubuntu-22.04
16 | docker_image:
17 | - ubuntu:20.04
18 | - ubuntu:22.04
19 | uses: "./.github/workflows/reusable-verify.yml"
20 | with:
21 | os: ${{ matrix.os }}
22 | docker_image: ${{ matrix.docker_image }}
23 | package_installation_command: |
24 | apt update
25 | apt install -y openssh-client git
26 | secrets: inherit
27 |
--------------------------------------------------------------------------------
/.github/workflows/verify-on-macos.yml:
--------------------------------------------------------------------------------
1 | # https://help.github.com/en/articles/workflow-syntax-for-github-actions
2 | name: macOS
3 |
4 | on:
5 | - push
6 |
7 | jobs:
8 | verify:
9 | name: Verify
10 | strategy:
11 | fail-fast: false
12 | matrix:
13 | os:
14 | - macos-11
15 | - macos-12
16 | uses: "./.github/workflows/reusable-verify.yml"
17 | with:
18 | os: ${{ matrix.os }}
19 | secrets: inherit
20 |
--------------------------------------------------------------------------------
/.github/workflows/verify-on-ubuntu.yml:
--------------------------------------------------------------------------------
1 | # https://help.github.com/en/articles/workflow-syntax-for-github-actions
2 | name: Ubuntu
3 |
4 | on:
5 | - push
6 |
7 | jobs:
8 | verify:
9 | name: Verify
10 | strategy:
11 | fail-fast: false
12 | matrix:
13 | os:
14 | - ubuntu-20.04
15 | - ubuntu-22.04
16 | uses: "./.github/workflows/reusable-verify.yml"
17 | with:
18 | os: ${{ matrix.os }}
19 | secrets: inherit
20 |
--------------------------------------------------------------------------------
/.github/workflows/verify-on-windows.yml:
--------------------------------------------------------------------------------
1 | # https://help.github.com/en/articles/workflow-syntax-for-github-actions
2 | name: Windows
3 |
4 | on:
5 | - push
6 |
7 | jobs:
8 | verify:
9 | name: Verify
10 | strategy:
11 | fail-fast: false
12 | matrix:
13 | os:
14 | - windows-2019
15 | - windows-2022
16 | uses: "./.github/workflows/reusable-verify.yml"
17 | with:
18 | os: ${{ matrix.os }}
19 | secrets: inherit
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # temporary files
2 | *~
3 | *.bak
4 | *.log
5 | *.swp
6 | *.tmp
7 |
8 | # image caches
9 | .DS_Store
10 | Thumbs.db
11 |
12 | # patch/merge
13 | *.orig
14 | *.rej
15 |
16 | # IDE
17 | /.idea/
18 |
19 | # Node.js modules
20 | /node_modules/
21 |
--------------------------------------------------------------------------------
/.markdownlint.yml:
--------------------------------------------------------------------------------
1 | MD007: # ul-indent
2 | indent: 4
3 | MD013: # line-length
4 | line_length: 1024
5 | MD024: # no-duplicate-heading
6 | siblings_only: true
7 | MD026: false # no-trailing-punctuation
8 | MD029: # ol-prefix
9 | style: one
10 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | #also=dev
2 | engine-strict=true
3 | heading="🍣"
4 | save-prefix=""
5 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
6 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
7 |
8 | ## [Unreleased]
9 |
10 | ## [2.7.0] - 2024-02-11
11 |
12 | ### Others
13 |
14 | * Update to Node.js v20 (thanks [@princemaple](https://github.com/princemaple))
15 | * drop old containers; Ubuntu 16.04, and CentOS 7
16 |
17 | ## [2.6.1] - 2023-10-13
18 |
19 | ### Fixed
20 |
21 | * JSON parse error on exit, if `if_key_exists`=`fail` and key exists
22 |
23 | ## [2.6.0] - 2023-10-11
24 |
25 | ### Others
26 |
27 | * back up and restore files when exist (thanks [@bambeusz](https://github.com/bambeusz))
28 | * remove `macos-10.15` and `ubuntu-18.04` virtual environment
29 |
30 | ## [2.5.1] - 2023-03-25
31 |
32 | ### Hotfix
33 |
34 | * update github.com key: (thanks [@phlax](https://github.com/phlax))
35 |
36 | ## [2.5.0] - 2022-12-24
37 |
38 | ### Added
39 |
40 | * remove SSH directory at the end of workflow
41 |
42 | ## [2.4.0] - 2022-11-03
43 |
44 | ### Added
45 |
46 | * always set server key of `github.com` to `known_hosts`
47 |
48 | ### Fixed
49 |
50 | * usage of `rsync` in README
51 |
52 | ### Others
53 |
54 | * add `windows-2022`, and `macos-11` (thanks [@ViacheslavKudinov](https://github.com/ViacheslavKudinov))
55 | * add `macos-12`, `ubuntu-22.04`, and `CentOS 8 Stream (Docker container)`
56 | * drop `ubuntu-16.04`, and `CentOS 8 (Docker container)`
57 | * [update Node.js version to 16](https://github.blog/changelog/2022-09-22-github-actions-all-actions-will-begin-running-on-node16-instead-of-node12/) (thanks [@duddu](https://github.com/duddu))
58 |
59 | ## [2.3.1] - 2021-08-01
60 |
61 | ### Security
62 |
63 | * Fix [CVE-2021-33502](https://github.com/advisories/GHSA-px4h-xg32-q955)
64 |
65 | ### Others
66 |
67 | * add `windows-2016` virtual environment
68 | * [remove `ubuntu-16.04` virtual environment](https://github.blog/changelog/2021-04-29-github-actions-ubuntu-16-04-lts-virtual-environment-will-be-removed-on-september-20-2021/)
69 |
70 | ## [2.3.0] - 2021-03-21
71 |
72 | ### Added
73 |
74 | * `if_key_exists` parameter
75 | * `known_hosts: unnecessary`
76 | * Support Alpine Linux Docker container
77 |
78 | ## [2.2.0] - 2021-02-27
79 |
80 | ### Added
81 |
82 | * Support Ubuntu/CentOS Docker container (thanks [@kujaomega](https://github.com/kujaomega))
83 | * Support PKCS8/RFC4716 formats (thanks [@tats-u](https://github.com/tats-u))
84 |
85 | ### Changed
86 |
87 | * Bundle dependencies (thanks [@tats-u](https://github.com/tats-u))
88 |
89 | ### Fixed
90 |
91 | * comments in README (thanks [@KimSoungRyoul](https://github.com/KimSoungRyoul))
92 |
93 | ## [2.1.0] - 2020-08-15
94 |
95 | ### Changed
96 |
97 | * Append LF to `known_hosts` / `config` (thanks [@jacktuck](https://github.com/jacktuck))
98 |
99 | ### Fixed
100 |
101 | * Typo (thanks [@psbss](https://github.com/psbss))
102 |
103 | ## [2.0.3] - 2020-06-06
104 |
105 | ### Added
106 |
107 | * Ubuntu 20.04
108 |
109 | ### Changed
110 |
111 | * Add short note on how to convert OPENSSH to PEM format by [@shadow1runner](https://github.com/shadow1runner)
112 |
113 | ## [2.0.2] - 2020-04-12
114 |
115 | ### Security
116 |
117 | * update [minimist](https://www.npmjs.com/package/minimist) to 1.2.5 ([CVE-2020-7598](https://github.com/advisories/GHSA-vh95-rmgr-6w4m))
118 |
119 | ## [2.0.1] - 2020-03-14
120 |
121 | ### Security
122 |
123 | * update [acorn](https://www.npmjs.com/package/acorn) to 7.1.1 ([GHSA-7fhm-mqm4-2wp7](https://github.com/advisories/GHSA-7fhm-mqm4-2wp7))
124 |
125 | ## [2.0.0] - 2020-02-08
126 |
127 | ### Changed
128 |
129 | * rename `private-key` to `key`
130 | * rename `known-hosts` to `known_hosts`
131 | * make `known_hosts` required
132 |
133 | ## [1.6.5] - 2020-02-08
134 |
135 | ### Others
136 |
137 | * update version of [Checkout](https://github.com/marketplace/actions/checkout) action
138 |
139 | ## [1.6.4] - 2020-01-27
140 |
141 | ### Fixed
142 |
143 | * `node_modules/.bin` error (thanks [@george3447](https://github.com/george3447))
144 |
145 | ## [1.6.3] - 2020-01-27
146 |
147 | ### Others
148 |
149 | * add Q&A
150 |
151 | ## [1.6.2] - 2020-01-25
152 |
153 | ### Others
154 |
155 | * some updates
156 |
157 | ## [1.6.1] - 2020-01-19
158 |
159 | ### Fixed
160 |
161 | * Some bugfixes
162 |
163 | ## [1.6.0] - 2020-01-18
164 |
165 | ### Changed
166 |
167 | * `public-key` is no longer necessarily
168 |
169 | ## [1.5.0] - 2019/12/30
170 |
171 | ### Changed
172 |
173 | * Append contents of `config` and `known_hosts` when called multiple times.
174 |
175 | ## [1.4.0] - 2019/12/22
176 |
177 | ### Added
178 |
179 | * `config` option
180 |
181 | ## [1.3.0] - 2019/09/29
182 |
183 | ### Added
184 |
185 | * `known-hosts` option
186 |
187 | ## [1.2.0] - 2019/09/22
188 |
189 | ### Fixed
190 |
191 | * CI trigger
192 | * example code in [README](README.md)
193 |
194 | ### Others
195 |
196 | * Install only `dependencies` packages.
197 |
198 | ## [1.1.0] - 2019/09/19
199 |
200 | ### Others
201 |
202 | * Support Visual Studio Code officially.
203 | * Use GitHub Actions for build test.
204 |
205 | ## [1.0.0] - 2019/09/18
206 |
207 | * First release.
208 |
209 | [Unreleased]: https://github.com/shimataro/ssh-key-action/compare/v2.7.0...HEAD
210 | [2.7.0]: https://github.com/shimataro/ssh-key-action/compare/v2.6.1...v2.7.0
211 | [2.6.1]: https://github.com/shimataro/ssh-key-action/compare/v2.6.0...v2.6.1
212 | [2.6.0]: https://github.com/shimataro/ssh-key-action/compare/v2.5.1...v2.6.0
213 | [2.5.1]: https://github.com/shimataro/ssh-key-action/compare/v2.5.0...v2.5.1
214 | [2.5.0]: https://github.com/shimataro/ssh-key-action/compare/v2.4.0...v2.5.0
215 | [2.4.0]: https://github.com/shimataro/ssh-key-action/compare/v2.3.1...v2.4.0
216 | [2.3.1]: https://github.com/shimataro/ssh-key-action/compare/v2.3.0...v2.3.1
217 | [2.3.0]: https://github.com/shimataro/ssh-key-action/compare/v2.2.0...v2.3.0
218 | [2.2.0]: https://github.com/shimataro/ssh-key-action/compare/v2.1.0...v2.2.0
219 | [2.1.0]: https://github.com/shimataro/ssh-key-action/compare/v2.0.3...v2.1.0
220 | [2.0.3]: https://github.com/shimataro/ssh-key-action/compare/v2.0.2...v2.0.3
221 | [2.0.2]: https://github.com/shimataro/ssh-key-action/compare/v2.0.1...v2.0.2
222 | [2.0.1]: https://github.com/shimataro/ssh-key-action/compare/v2.0.0...v2.0.1
223 | [2.0.0]: https://github.com/shimataro/ssh-key-action/compare/v1.6.5...v2.0.0
224 | [1.6.5]: https://github.com/shimataro/ssh-key-action/compare/v1.6.4...v1.6.5
225 | [1.6.4]: https://github.com/shimataro/ssh-key-action/compare/v1.6.3...v1.6.4
226 | [1.6.3]: https://github.com/shimataro/ssh-key-action/compare/v1.6.2...v1.6.3
227 | [1.6.2]: https://github.com/shimataro/ssh-key-action/compare/v1.6.1...v1.6.2
228 | [1.6.1]: https://github.com/shimataro/ssh-key-action/compare/v1.6.0...v1.6.1
229 | [1.6.0]: https://github.com/shimataro/ssh-key-action/compare/v1.5.0...v1.6.0
230 | [1.5.0]: https://github.com/shimataro/ssh-key-action/compare/v1.4.0...v1.5.0
231 | [1.4.0]: https://github.com/shimataro/ssh-key-action/compare/v1.3.0...v1.4.0
232 | [1.3.0]: https://github.com/shimataro/ssh-key-action/compare/v1.2.0...v1.3.0
233 | [1.2.0]: https://github.com/shimataro/ssh-key-action/compare/v1.1.0...v1.2.0
234 | [1.1.0]: https://github.com/shimataro/ssh-key-action/compare/v1.0.0...v1.1.0
235 | [1.0.0]: https://github.com/shimataro/ssh-key-action/compare/8deacc95b1ee5732107e56baa4c8aac4c386ef7e...v1.0.0
236 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 shimataro
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Install SSH Key
2 |
3 | [![Build][image-build]][link-build]
4 | [![Windows][image-verify-windows]][link-verify-windows]
5 | [![macOS][image-verify-macos]][link-verify-macos]
6 | [![Ubuntu][image-verify-ubuntu]][link-verify-ubuntu]
7 | [![Docker container (Ubuntu)][image-verify-docker-container-ubuntu]][link-verify-docker-container-ubuntu]
8 | [![Docker container (CentOS)][image-verify-docker-container-centos]][link-verify-docker-container-centos]
9 | [![Docker container (Alpine Linux)][image-verify-docker-container-alpine]][link-verify-docker-container-alpine]
10 | [![Release][image-release]][link-release]
11 | [![License][image-license]][link-license]
12 | [![Stars][image-stars]][link-stars]
13 |
14 | This action installs SSH key in `~/.ssh`.
15 |
16 | Useful for SCP, SFTP, and `rsync` over SSH in deployment script.
17 |
18 | tested on:
19 |
20 | * [all available virtual machines](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/virtual-environments-for-github-hosted-runners#supported-runners-and-hardware-resources) (Windows Server 2022/2019, macOS Monterey/Big Sur, and Ubuntu 22.04/20.04)
21 | * [Docker container (Ubuntu)](https://hub.docker.com/_/ubuntu) / requires `openssh-client` package; `apt install -y openssh-client`
22 | * [Docker container (CentOS)](https://quay.io/repository/centos/centos) / requires `openssh-clients` package; `yum install -y openssh-clients`
23 | * [Docker container (Alpine Linux)](https://hub.docker.com/_/alpine) / requires `openssh-client` package; `apk add openssh-client`
24 |
25 | ## Usage
26 |
27 | Add your SSH key to your product secrets by clicking `Settings` - `Secrets` - `Add a new secret` beforehand.
28 |
29 | PEM(RSA), PKCS8, and RFC4716(OpenSSH) formats are OK.
30 |
31 | ```yaml
32 | runs-on: ubuntu-latest
33 | steps:
34 | - name: Install SSH key
35 | uses: shimataro/ssh-key-action@v2
36 | with:
37 | key: ${{ secrets.SSH_KEY }}
38 | name: id_rsa # optional
39 | known_hosts: ${{ secrets.KNOWN_HOSTS }}
40 | config: ${{ secrets.CONFIG }} # ssh_config; optional
41 | if_key_exists: fail # replace / ignore / fail; optional (defaults to fail)
42 | - name: rsync over SSH
43 | run: rsync -r ./foo/ user@remote:bar/
44 | ```
45 |
46 | See [Workflow syntax for GitHub Actions](https://help.github.com/en/articles/workflow-syntax-for-github-actions) for details.
47 |
48 | **NOTE:**
49 |
50 | * Server key of `github.com` will be always set to `known_hosts`.
51 | * SSH keys will be removed at the end of workflow.
52 |
53 | ### Install multiple keys
54 |
55 | If you want to install multiple keys, call this action multiple times.
56 | It is useful for port forwarding.
57 |
58 | **NOTE:** When this action is called multiple times, **the contents of `known_hosts` and `config` will be appended**. `key` must be saved as different name, by using `name` option.
59 |
60 | ```yaml
61 | runs-on: ubuntu-latest
62 | steps:
63 | - name: Install SSH key of bastion
64 | uses: shimataro/ssh-key-action@v2
65 | with:
66 | key: ${{ secrets.SSH_KEY_OF_BASTION }}
67 | name: id_rsa-bastion
68 | known_hosts: ${{ secrets.KNOWN_HOSTS_OF_BASTION }}
69 | config: |
70 | Host bastion
71 | HostName xxx.xxx.xxx.xxx
72 | User user-of-bastion
73 | IdentityFile ~/.ssh/id_rsa-bastion
74 | - name: Install SSH key of target
75 | uses: shimataro/ssh-key-action@v2
76 | with:
77 | key: ${{ secrets.SSH_KEY_OF_TARGET }}
78 | name: id_rsa-target
79 | known_hosts: ${{ secrets.KNOWN_HOSTS_OF_TARGET }} # will be appended to existing .ssh/known_hosts
80 | config: | # will be appended to existing .ssh/config
81 | Host target
82 | HostName yyy.yyy.yyy.yyy
83 | User user-of-target
84 | IdentityFile ~/.ssh/id_rsa-target
85 | ProxyCommand ssh -W %h:%p bastion
86 | - name: SCP via port-forwarding
87 | run: scp ./foo/ target:bar/
88 | ```
89 |
90 | ## Q&A
91 |
92 | ### SSH failed even though key has been installed.
93 |
94 | Check below:
95 |
96 | * `Host key verification failed.`:
97 | * Set `known_hosts` parameter correctly (use `ssh-keyscan` command).
98 |
99 | ### I want to replace/ignore key if exists.
100 |
101 | Use `if_key_exists` parameter.
102 |
103 | * `replace`: replaces key
104 | * `ignore`: does nothing
105 | * `fail`: fails (default)
106 |
107 | ### How do I use encrypted SSH key?
108 |
109 | This action doesn't support encrypted key directly.
110 | Here are some solutions:
111 |
112 | * decrypting key beforehand: best bet, and works on any VM
113 | * `sshpass` command: next best bet, but not supported on Windows
114 | * `expect` command: be careful not to expose passphrase to console
115 | * `SSH_ASKPASS` environment variable: might be troublesome
116 |
117 | ### Which one is the best way for transferring files, "direct SCP/SFTP/rsync" or "SCP/SFTP/rsync via bastion"?
118 |
119 | I recommend **rsync via bastion**.
120 |
121 | ```bash
122 | rsync -r -e "ssh bastion ssh" ./foo/ target:bar/
123 | ```
124 |
125 | It has some advantages over other methods:
126 |
127 | * "Rsync via bastion" doesn't require to update workflow files and `secrets` even if it is necessary to transfer files to multiple servers.
128 | * Other methods require to update `known_hosts` if servers have changed.
129 | * Rsync:
130 | * is fastest of all.
131 | * does **NOT** break files even if disconnected during transferring.
132 | * can remove files that don't exist on server.
133 | * SCP is [deprecated by OpenSSH](https://www.openssh.com/txt/release-8.0) due to outdated and inflexible protocol.
134 | * Using bastion is more secure because:
135 | * it is not necessarily to expose SSH port on servers to public.
136 | * Address filtering is less effective.
137 | * Because Azure address range is [very wide](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/virtual-environments-for-github-hosted-runners#ip-addresses-of-github-hosted-runners).
138 | * And will be updated continuously.
139 | * if security incident ―e.g., private key leaked― occurs, it's OK just to remove `authorized_keys` on bastion.
140 |
141 | ### I want to omit `known_hosts`.
142 |
143 | First of all, you have to understand that it is NOT secure to SSH with no `known_hosts` and using `StrictHostKeyChecking=no` option.
144 |
145 | Why do you want to omit it?
146 | If the reason is **"I'm not understanding about the function of `known_hosts`"** or **"It's bother to fetch server key"**, you should not omit.
147 | If **"It is hard to prefetch server key because the server will be created dynamically"**, you can use bastion server.
148 |
149 | **"`known_hosts` is unnecessary because I'm using secure method for SSH, such as SSHFP and signed server key."** — OK, here is a special value to omit `known_hosts`.
150 | You should use it ONLY IF you are using secure methods...
151 | It is `known_hosts: unnecessary`.
152 |
153 | ## License
154 |
155 | The scripts and documentation in this project are released under the [MIT License](LICENSE)
156 |
157 | ## Changelog
158 |
159 | See [CHANGELOG.md](CHANGELOG.md).
160 |
161 | [image-build]: https://github.com/shimataro/ssh-key-action/workflows/Build/badge.svg?event=push&branch=v2
162 | [link-build]: https://github.com/shimataro/ssh-key-action/actions/workflows/build.yml
163 | [image-verify-windows]: https://github.com/shimataro/ssh-key-action/workflows/Windows/badge.svg?event=push&branch=v2
164 | [link-verify-windows]: https://github.com/shimataro/ssh-key-action/actions/workflows/verify-on-windows.yml
165 | [image-verify-macos]: https://github.com/shimataro/ssh-key-action/workflows/macOS/badge.svg?event=push&branch=v2
166 | [link-verify-macos]: https://github.com/shimataro/ssh-key-action/actions/workflows/verify-on-macos.yml
167 | [image-verify-ubuntu]: https://github.com/shimataro/ssh-key-action/workflows/Ubuntu/badge.svg?event=push&branch=v2
168 | [link-verify-ubuntu]: https://github.com/shimataro/ssh-key-action/actions/workflows/verify-on-ubuntu.yml
169 | [image-verify-docker-container-ubuntu]: https://github.com/shimataro/ssh-key-action/actions/workflows/verify-on-container-ubuntu.yml/badge.svg?event=push&branch=v2
170 | [link-verify-docker-container-ubuntu]: https://github.com/shimataro/ssh-key-action/actions/workflows/verify-on-container-ubuntu.yml
171 | [image-verify-docker-container-centos]: https://github.com/shimataro/ssh-key-action/actions/workflows/verify-on-container-centos.yml/badge.svg?event=push&branch=v2
172 | [link-verify-docker-container-centos]: https://github.com/shimataro/ssh-key-action/actions/workflows/verify-on-container-centos.yml
173 | [image-verify-docker-container-alpine]: https://github.com/shimataro/ssh-key-action/actions/workflows/verify-on-container-alpine.yml/badge.svg?event=push&branch=v2
174 | [link-verify-docker-container-alpine]: https://github.com/shimataro/ssh-key-action/actions/workflows/verify-on-container-alpine.yml
175 | [image-release]: https://img.shields.io/github/release/shimataro/ssh-key-action.svg
176 | [link-release]: https://github.com/shimataro/ssh-key-action/releases
177 | [image-license]: https://img.shields.io/github/license/shimataro/ssh-key-action.svg
178 | [link-license]: ./LICENSE
179 | [image-stars]: https://img.shields.io/github/stars/shimataro/ssh-key-action.svg
180 | [link-stars]: https://github.com/shimataro/ssh-key-action/stargazers
181 |
--------------------------------------------------------------------------------
/action.yml:
--------------------------------------------------------------------------------
1 | # https://help.github.com/en/articles/metadata-syntax-for-github-actions
2 | name: "Install SSH Key"
3 | description: "Install SSH key in ~/.ssh"
4 | author: "shimataro"
5 | branding:
6 | icon: "terminal"
7 | color: "gray-dark"
8 | inputs:
9 | key:
10 | description: "SSH private key"
11 | required: true
12 | name:
13 | description: "SSH key file name (default: id_rsa)"
14 | required: false
15 | default: "id_rsa"
16 | known_hosts:
17 | description: "public keys of SSH servers"
18 | required: true
19 | default: ""
20 | config:
21 | description: "SSH config"
22 | required: false
23 | default: ""
24 | if_key_exists:
25 | description: "replace / ignore / fail"
26 | required: false
27 | default: "fail"
28 | runs:
29 | using: "node20"
30 | main: "./dist/main.js"
31 | post: "./dist/post.js"
32 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "install-ssh-key",
3 | "version": "2.7.0",
4 | "private": true,
5 | "description": "Install SSH key in .ssh",
6 | "main": "./dist/main.js",
7 | "engines": {
8 | "node": ">=8.0.0",
9 | "npm": ">=5.7.0"
10 | },
11 | "scripts": {
12 | "build": "esbuild ./src/main.ts ./src/post.ts --bundle --platform=node --minify --sourcemap --outdir=./dist",
13 | "check-updates": "ncu",
14 | "lint": "run-p lint:*",
15 | "lint:es": "eslint ./src --ext .ts",
16 | "lint:ts": "tsc --noEmit ./src/main.ts ./src/post.ts",
17 | "lint:md": "markdownlint . --ignore node_modules --ignore examples",
18 | "lint:yaml": "yamllint **/{,.}*.{yml,yaml} --ignore=node_modules/**",
19 | "verify": "run-p lint"
20 | },
21 | "repository": {
22 | "type": "git",
23 | "url": "git+https://github.com/shimataro/ssh-key-action.git"
24 | },
25 | "keywords": [
26 | "actions",
27 | "github actions",
28 | "rsync",
29 | "ssh",
30 | "ssh key"
31 | ],
32 | "author": "shimataro",
33 | "license": "MIT",
34 | "devDependencies": {
35 | "@actions/core": "1.10.1",
36 | "@types/node": "20.11.17",
37 | "@typescript-eslint/eslint-plugin": "6.21.0",
38 | "@typescript-eslint/parser": "6.21.0",
39 | "esbuild": "0.20.0",
40 | "eslint": "8.56.0",
41 | "markdownlint-cli": "0.39.0",
42 | "npm-check-updates": "16.14.15",
43 | "npm-run-all": "4.1.5",
44 | "typescript": "5.3.3",
45 | "yaml-lint": "1.7.0"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/scripts/prepare-release.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # requires following programs:
3 | # - git; I believe you have already installed.
4 | # - sed; Both GNU sed and POSIX sed will work.
5 | set -eu
6 |
7 | GITHUB_BASE="https://github.com"
8 | GITHUB_USER="shimataro"
9 | GITHUB_REPO="ssh-key-action"
10 |
11 | UPSTREAM="origin"
12 |
13 | COLOR_ERROR="\033[1;41m"
14 | COLOR_SECTION="\033[1;34m"
15 | COLOR_COMMAND_NAME="\033[1;34m"
16 | COLOR_OPTION="\033[4;36m"
17 | COLOR_COMMAND="\033[4m"
18 | COLOR_FILE="\033[1;34m"
19 | COLOR_BRANCH="\033[1;31m"
20 | COLOR_INPUT="\033[1;31m"
21 | COLOR_SELECT="\033[1;32m"
22 | COLOR_RESET="\033[m"
23 |
24 | URL_PRODUCT="${GITHUB_BASE}/${GITHUB_USER}/${GITHUB_REPO}"
25 | URL_REPOSITORY="${URL_PRODUCT}.git"
26 | URL_COMPARE="${URL_PRODUCT}/compare"
27 | URL_RELEASE="${URL_PRODUCT}/releases/new"
28 |
29 | function main() {
30 | cd $(dirname ${0})/..
31 |
32 | if [[ $# -lt 1 ]]; then
33 | usage
34 | fi
35 |
36 | local NEW_VERSION=$1
37 | local CURRENT_VERSION=$(
38 | node -e 'console.log(JSON.parse(require("fs").readFileSync("package.json")).version)'
39 | )
40 | local TAG="v${NEW_VERSION}"
41 | local BRANCH="release/v${NEW_VERSION}"
42 | local BASE_BRANCH="${TAG%%.*}"
43 |
44 | check_version_format ${NEW_VERSION}
45 | check_current_branch ${BASE_BRANCH}
46 |
47 | create_branch ${BRANCH} ${BASE_BRANCH}
48 | update_package_version ${NEW_VERSION}
49 | update_changelog ${NEW_VERSION}
50 | verify_package
51 | commit_changes ${NEW_VERSION}
52 | finish ${NEW_VERSION} ${CURRENT_VERSION} ${BRANCH} ${BASE_BRANCH} ${TAG}
53 | }
54 |
55 | function usage() {
56 | local COMMAND=$(basename ${0})
57 |
58 | echo -e "${COLOR_SECTION}NAME${COLOR_RESET}
59 | ${COMMAND} - Prepare for new release
60 |
61 | ${COLOR_SECTION}SYNOPSIS${COLOR_RESET}
62 | ${COLOR_COMMAND_NAME}${COMMAND}${COLOR_RESET} <${COLOR_OPTION}new-version${COLOR_RESET}>
63 |
64 | ${COLOR_SECTION}DESCRIPTION${COLOR_RESET}
65 | This command will...
66 | - create a new branch for release
67 | - update ${COLOR_FILE}CHANGELOG.md${COLOR_RESET}
68 | - update package version in ${COLOR_FILE}package.json${COLOR_RESET}
69 | - update dependencies version in ${COLOR_FILE}package.json${COLOR_RESET}
70 | - verify
71 | - ...and commit!
72 |
73 | ${COLOR_OPTION}new-version${COLOR_RESET} must follow \"Semantic Versioning\" .
74 | "
75 | exit 1
76 | }
77 |
78 | function check_version_format() {
79 | if [[ ${1} =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*) ]]; then
80 | return
81 | fi
82 |
83 | echo -e "${COLOR_ERROR}ERROR:${COLOR_RESET} Follow \"Semantic Versioning\" for new version.
84 | " >&2
85 | exit 2
86 | }
87 |
88 | function check_current_branch() {
89 | local BASE_BRANCH=$1
90 | local CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
91 | if [ ${CURRENT_BRANCH} = ${BASE_BRANCH} ]; then
92 | return
93 | fi
94 |
95 | echo -e "${COLOR_ERROR}ERROR:${COLOR_RESET} Work on ${COLOR_BRANCH}${BASE_BRANCH}${COLOR_RESET} branch
96 | ${COLOR_COMMAND}git checkout ${BASE_BRANCH}${COLOR_RESET}
97 | " >&2
98 | exit 2
99 | }
100 |
101 | function create_branch() {
102 | local BRANCH=$1
103 | local BASE_BRANCH=$2
104 |
105 | git checkout -b ${BRANCH} ${BASE_BRANCH}
106 | }
107 |
108 | function update_package_version() {
109 | local VERSION=$1
110 |
111 | npm version --no-git-tag-version ${VERSION}
112 | }
113 |
114 | function update_changelog() {
115 | local VERSION=$1
116 | local DATE=`date "+%Y-%m-%d"`
117 | local KEYWORD="Unreleased"
118 |
119 | sed -i".bak" -r \
120 | -e "s/^((##[[:space:]]+)\[${KEYWORD}\])$/\1\n\n\2[${VERSION}] - ${DATE}/" \
121 | -e "s/^(\[${KEYWORD}\](.*))(v.*)\.\.\.HEAD$/\1v${VERSION}...HEAD\n[${VERSION}]\2\3...v${VERSION}/" \
122 | CHANGELOG.md
123 | }
124 |
125 | function verify_package() {
126 | npm run verify
127 | }
128 |
129 | function commit_changes() {
130 | local VERSION=$1
131 |
132 | git add CHANGELOG.md package.json package-lock.json
133 | git commit -m "version ${VERSION}"
134 | }
135 |
136 | function finish() {
137 | local NEW_VERSION="${1}"
138 | local CURRENT_VERSION="${2}"
139 | local BRANCH="${3}"
140 | local BASE_BRANCH="${4}"
141 | local TAG="${5}"
142 |
143 | local TITLE="${GITHUB_REPO} ${NEW_VERSION} released"
144 | local CHANGELOG=$(
145 | git diff v${CURRENT_VERSION} -- CHANGELOG.md |
146 | sed -r -e '/^[^+]/d' -e 's/^\+(.*)$/\1/' -e '/^## /d' -e '/^\+/d' -e '/^\[/d' |
147 | urlencode |
148 | replace_lf
149 | )
150 | local PRERELEASE=0
151 | if [[ ${NEW_VERSION} == "0."* ]]; then
152 | # < 1.0.0
153 | PRERELEASE=1
154 | fi
155 | if [[ ${NEW_VERSION} =~ -[0-9a-zA-Z] ]]; then
156 | # -alpha, -pre, -rc, etc...
157 | PRERELEASE=1
158 | fi
159 |
160 | echo -e "
161 | Branch ${COLOR_BRANCH}${BRANCH}${COLOR_RESET} has been created.
162 | Remaining processes are...
163 |
164 | 1. Make sure all changes are correct
165 | ${COLOR_COMMAND}git diff ${BASE_BRANCH} ${BRANCH}${COLOR_RESET}
166 | 2. Push to remote ${UPSTREAM}
167 | ${COLOR_COMMAND}git push --set-upstream ${UPSTREAM} ${BRANCH}${COLOR_RESET}
168 | 3. Create a pull-request: ${COLOR_BRANCH}${BRANCH}${COLOR_RESET} to ${COLOR_BRANCH}${BASE_BRANCH}${COLOR_RESET}
169 | ${URL_COMPARE}/${BASE_BRANCH}...${BRANCH}?expand=1
170 | select ${COLOR_SELECT}Squash and merge${COLOR_RESET}
171 | 4. Create a new release
172 | ${URL_RELEASE}?tag=${TAG}&target=${BASE_BRANCH}&title=$(urlencode <<<"${TITLE}")&body=${CHANGELOG}&prerelease=${PRERELEASE}
173 | Tag version: ${COLOR_INPUT}${TAG}${COLOR_RESET}
174 | Target: ${COLOR_INPUT}${BASE_BRANCH}${COLOR_RESET}
175 | Release title: ${COLOR_INPUT}${TITLE}${COLOR_RESET}
176 | Description this release: (copy and paste CHANGELOG.md)
177 | 5. Post processing
178 | ${COLOR_COMMAND}git checkout ${BASE_BRANCH}${COLOR_RESET}
179 | ${COLOR_COMMAND}git pull${COLOR_RESET}
180 | ${COLOR_COMMAND}git fetch -p${COLOR_RESET}
181 | ${COLOR_COMMAND}git branch -D ${BRANCH}${COLOR_RESET}
182 |
183 | That's all!
184 | "
185 | }
186 |
187 | function urlencode() {
188 | # https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding
189 | sed -r \
190 | -e 's/%/%25/g' \
191 | -e 's/\$/%24/g' -e 's/\(/%28/g' -e 's/\)/%29/g' -e 's/\*/%2A/g' -e 's/\+/%2B/g' -e 's/\//%2F/g' -e 's/\?/%3F/g' -e 's/\[/%5B/g' -e 's/\]/%5D/g' \
192 | -e 's/!/%21/g' -e 's/#/%23/g' -e 's/&/%26/g' -e "s/'/%27/g" -e 's/,/%2C/g' -e 's/:/%3A/g' -e 's/;/%3B/g' -e 's/=/%3D/g' -e 's/@/%40/g' \
193 | -e 's/ /+/g'
194 | }
195 |
196 | function replace_lf() {
197 | sed -r \
198 | -e ':a' -e 'N' -e '$!ba' \
199 | -e 's/^\n+//' -e 's/\n+$//' -e 's/\n/%0A/g'
200 | }
201 |
202 | main "$@"
203 |
--------------------------------------------------------------------------------
/scripts/rebuild.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -eu
3 |
4 | npm ci
5 | npm run build
6 | npm run verify
7 |
8 | rm -rf node_modules
9 | npm ci --only=production
10 | git add dist
11 |
--------------------------------------------------------------------------------
/scripts/update-dependencies.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # update dependencies
3 | set -eu
4 |
5 | DATE=$(date +"%Y%m%d")
6 | BASE_BRANCH=$(git rev-parse --abbrev-ref HEAD)
7 | TARGET_BRANCH=feature/update-dependencies-${DATE}
8 |
9 | COLOR_SUCCESS="\033[1;32m"
10 | COLOR_ERROR="\033[1;41m"
11 | COLOR_RESET="\033[m"
12 |
13 | cd $(dirname ${0})/..
14 |
15 | # create target branch
16 | if [[ ! ${BASE_BRANCH} =~ ^v[0-9]+$ ]]; then
17 | echo -e "${COLOR_ERROR}Error:${COLOR_RESET} Base branch must match 'v*'; got '${BASE_BRANCH}'."
18 | exit 1
19 | fi
20 | git checkout -b ${TARGET_BRANCH}
21 |
22 | # check updates
23 | npm ci
24 | npm run check-updates -- -u
25 |
26 | # re-install packages
27 | rm -rf package-lock.json node_modules
28 | npm i
29 | npm dedupe
30 |
31 | # test
32 | npm run build
33 | npm run verify
34 |
35 | # commit
36 | git add package.json package-lock.json dist
37 | git commit -m "update dependencies"
38 |
39 | # finished!
40 | echo -e "
41 | ${COLOR_SUCCESS}🎉All dependencies are updated successfully.🎉${COLOR_RESET}
42 |
43 | Push changes and merge into '${BASE_BRANCH}' branch.
44 |
45 | git push --set-upstream origin ${TARGET_BRANCH}
46 | "
47 |
--------------------------------------------------------------------------------
/src/common.ts:
--------------------------------------------------------------------------------
1 | import * as fs from "fs";
2 | import * as os from "os";
3 | import * as path from "path";
4 |
5 | import * as core from "@actions/core";
6 |
7 | /** state name of backup suffix */
8 | const STATE_BACKUP_SUFFIX = "backup-suffix";
9 | const STATE_CREATED_FILES = "created-files";
10 |
11 | /**
12 | * create backup suffix name
13 | * @param dirName directory to back up
14 | * @returns backup suffix; empty string if directory does not exist
15 | */
16 | export function createBackupSuffix(dirName: string): string {
17 | if (!fs.existsSync(dirName)) {
18 | return "";
19 | }
20 |
21 | const backupSuffix = `.bak-${Date.now()}`;
22 | core.saveState(STATE_BACKUP_SUFFIX, backupSuffix);
23 | return backupSuffix;
24 | }
25 |
26 | /**
27 | * get backup suffix name
28 | * @returns backup suffix (if not, empty string)
29 | */
30 | export function getBackupSuffix(): string {
31 | return core.getState(STATE_BACKUP_SUFFIX);
32 | }
33 |
34 | /**
35 | * save created file names
36 | * @param fileNames array of file names
37 | */
38 | export function saveCreatedFileNames(fileNames: string[]): void {
39 | const json = JSON.stringify(fileNames);
40 | core.saveState(STATE_CREATED_FILES, json);
41 | }
42 |
43 | /**
44 | * save created file names
45 | * @returns saved array of file names
46 | */
47 | export function loadCreatedFileNames(): string[] {
48 | const json = core.getState(STATE_CREATED_FILES);
49 | if (json === "") {
50 | return [];
51 | }
52 |
53 | return JSON.parse(json) as string[];
54 | }
55 |
56 | /**
57 | * get SSH directory
58 | * @returns SSH directory name
59 | */
60 | export function getSshDirectory(): string {
61 | return path.resolve(getHomeDirectory(), ".ssh");
62 | }
63 |
64 | /**
65 | * get home directory
66 | * @returns home directory name
67 | */
68 | function getHomeDirectory(): string {
69 | const homedir = os.homedir();
70 | if (homedir === "/github/home") {
71 | // Docker container
72 | return "/root";
73 | }
74 |
75 | return homedir;
76 | }
77 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import * as fs from "fs";
2 | import * as path from "path";
3 |
4 | import * as core from "@actions/core";
5 |
6 | import * as common from "./common";
7 |
8 | /** file creation info */
9 | interface FileInfo {
10 | /** file name */
11 | name: string;
12 | /** file contents */
13 | contents: string;
14 | /** creation options */
15 | options: fs.WriteFileOptions;
16 | /** file must not exist when creating */
17 | mustNotExist: boolean;
18 | }
19 |
20 | /** default known_hosts */
21 | const KNOWN_HOSTS = [
22 | "github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=",
23 | ];
24 |
25 | try {
26 | main();
27 | } catch (err) {
28 | if (err instanceof Error) {
29 | core.setFailed(err);
30 | }
31 | }
32 |
33 | /**
34 | * main function
35 | */
36 | export function main(): void {
37 | const sshDirName = common.getSshDirectory();
38 |
39 | // create ".ssh" directory
40 | const backupSuffix = common.createBackupSuffix(sshDirName);
41 | if (backupSuffix === "") {
42 | createDirectory(sshDirName);
43 | console.log(`✅SSH directory "${sshDirName}" has been created successfully.`);
44 | }
45 |
46 | // files to be created
47 | const files = buildFilesToCreate(sshDirName);
48 |
49 | // back up & create files
50 | const createdFileNames: string[] = [];
51 | const backedUpFileNames: string[] = [];
52 | for (const file of files) {
53 | const pathName = path.join(sshDirName, file.name);
54 | if (backup(pathName, backupSuffix, file.mustNotExist)) {
55 | backedUpFileNames.push(file.name);
56 | }
57 |
58 | fs.writeFileSync(pathName, file.contents, file.options);
59 | createdFileNames.push(file.name);
60 | }
61 | common.saveCreatedFileNames(createdFileNames);
62 |
63 | if (createdFileNames.length > 0) {
64 | console.log(`✅Following files have been created in "${sshDirName}" successfully; ${createdFileNames.join(", ")}`);
65 | }
66 | if (backedUpFileNames.length > 0) {
67 | console.log(`✅Following files have been backed up in suffix "${backupSuffix}" successfully; ${backedUpFileNames.join(", ")}`);
68 | }
69 | }
70 |
71 | /**
72 | * build files to create
73 | * @param dirName directory name in where files will be created
74 | * @returns files
75 | */
76 | function buildFilesToCreate(dirName: string): FileInfo[] {
77 | // parameters
78 | const key = core.getInput("key", {
79 | required: true,
80 | });
81 | const name = core.getInput("name");
82 | const knownHosts = core.getInput("known_hosts", {
83 | required: true,
84 | });
85 | const config = core.getInput("config");
86 | const ifKeyExists = core.getInput("if_key_exists");
87 |
88 | // files to be created
89 | const files: FileInfo[] = [
90 | {
91 | name: "known_hosts",
92 | contents: insertLf(buildKnownHostsArray(knownHosts).join("\n"), true, true),
93 | options: {
94 | mode: 0o644,
95 | flag: "a",
96 | },
97 | mustNotExist: false,
98 | },
99 | ];
100 | if (shouldCreateKeyFile(path.join(dirName, name), ifKeyExists)) {
101 | files.push({
102 | name: name,
103 | contents: insertLf(key, false, true),
104 | options: {
105 | mode: 0o400,
106 | flag: "wx",
107 | },
108 | mustNotExist: true,
109 | });
110 | }
111 | if (config !== "") {
112 | files.push({
113 | name: "config",
114 | contents: insertLf(config, true, true),
115 | options: {
116 | mode: 0o644,
117 | flag: "a",
118 | },
119 | mustNotExist: false,
120 | });
121 | }
122 |
123 | return files;
124 | }
125 |
126 | /**
127 | * create directory
128 | * @param dirName directory name to remove
129 | */
130 | function createDirectory(dirName: string): void {
131 | fs.mkdirSync(dirName, {
132 | recursive: true,
133 | mode: 0o700,
134 | });
135 | }
136 |
137 | /**
138 | * back up file
139 | * @param fileName file to back up
140 | * @param backupSuffix suffix
141 | * @param removeOrig remove original file
142 | * @returns is file backed up?
143 | */
144 | function backup(fileName: string, backupSuffix: string, removeOrig: boolean): boolean {
145 | if (backupSuffix === "") {
146 | return false;
147 | }
148 | if (!fs.existsSync(fileName)) {
149 | return false;
150 | }
151 |
152 | // move -> copy (in order to keep permissions when restore)
153 | const fileNameBak = `${fileName}${backupSuffix}`;
154 | fs.renameSync(fileName, fileNameBak);
155 | if (!removeOrig) {
156 | fs.copyFileSync(fileNameBak, fileName);
157 | }
158 |
159 | return true;
160 | }
161 |
162 | /**
163 | * prepend/append LF to value if not empty
164 | * @param value the value to insert LF
165 | * @param prepend true to prepend
166 | * @param append true to append
167 | * @returns new value
168 | */
169 | function insertLf(value: string, prepend: boolean, append: boolean): string {
170 | let affectedValue = value;
171 |
172 | if (value.length === 0) {
173 | // do nothing if empty
174 | return "";
175 | }
176 | if (prepend && !affectedValue.startsWith("\n")) {
177 | affectedValue = `\n${affectedValue}`;
178 | }
179 | if (append && !affectedValue.endsWith("\n")) {
180 | affectedValue = `${affectedValue}\n`;
181 | }
182 |
183 | return affectedValue;
184 | }
185 |
186 | /**
187 | * should create SSH key file?
188 | * @param keyFilePath path of key file
189 | * @param ifKeyExists action if SSH key exists
190 | * @returns Yes/No
191 | */
192 | function shouldCreateKeyFile(keyFilePath: string, ifKeyExists: string): boolean {
193 | if (!fs.existsSync(keyFilePath)) {
194 | // should create if file does not exist
195 | return true;
196 | }
197 |
198 | switch (ifKeyExists) {
199 | case "replace":
200 | // should create if replace (existing file will be backed up when creating)
201 | return true;
202 |
203 | case "ignore":
204 | // should NOT create if ignore
205 | return false;
206 |
207 | default:
208 | // error otherwise
209 | throw new Error(`SSH key is already installed. Set "if_key_exists" to "replace" or "ignore" in order to avoid this error.`);
210 | }
211 | }
212 |
213 | /**
214 | * build array of known_hosts
215 | * @param knownHosts known_hosts
216 | * @returns array of known_hosts
217 | */
218 | function buildKnownHostsArray(knownHosts: string): string[] {
219 | if (knownHosts === "unnecessary") {
220 | return KNOWN_HOSTS;
221 | }
222 | return KNOWN_HOSTS.concat(knownHosts);
223 | }
224 |
--------------------------------------------------------------------------------
/src/post.ts:
--------------------------------------------------------------------------------
1 | import * as fs from "fs";
2 | import * as path from "path";
3 |
4 | import * as core from "@actions/core";
5 |
6 | import * as common from "./common";
7 |
8 | try {
9 | post();
10 | } catch (err) {
11 | if (err instanceof Error) {
12 | core.setFailed(err);
13 | }
14 | }
15 |
16 | /**
17 | * cleanup function
18 | */
19 | export function post(): void {
20 | const sshDirName = common.getSshDirectory();
21 | const backupSuffix = common.getBackupSuffix();
22 | if (backupSuffix === "") {
23 | // remove ".ssh" directory if suffix is not set
24 | removeDirectory(sshDirName);
25 | console.log(`✅SSH directory "${sshDirName}" has been removed successfully.`);
26 | } else {
27 | // remove created files and restore from backup
28 | const removedFileNames = removeCreatedFiles(sshDirName);
29 | if (removedFileNames.length > 0) {
30 | console.log(`✅Following files have been removed successfully; ${removedFileNames.join(", ")}`);
31 | }
32 |
33 | const restoredFileNames = restoreFiles(sshDirName, backupSuffix);
34 | if (restoredFileNames.length > 0) {
35 | console.log(`✅Following files in suffix "${backupSuffix}" have been restored successfully; ${restoredFileNames.join(", ")}`);
36 | }
37 | }
38 | }
39 |
40 | /**
41 | * remove directory
42 | * @param dirName directory name to remove
43 | */
44 | function removeDirectory(dirName: string): void {
45 | fs.rmSync(dirName, {
46 | recursive: true,
47 | force: true,
48 | });
49 | }
50 |
51 | /**
52 | * remove created files in main phase
53 | * @param dirName directory name
54 | * @returns removed file names
55 | */
56 | function removeCreatedFiles(dirName: string): string[] {
57 | const createdFileNames = common.loadCreatedFileNames();
58 | for (const fileName of createdFileNames) {
59 | const pathName = path.join(dirName, fileName);
60 |
61 | fs.rmSync(pathName);
62 | }
63 | return createdFileNames;
64 | }
65 |
66 | /**
67 | * restore files from backups
68 | * @param dirName directory name
69 | * @param backupSuffix suffix of backup directory
70 | * @returns restored file names
71 | */
72 | function restoreFiles(dirName: string, backupSuffix: string): string[] {
73 | const restoredFileNames: string[] = [];
74 | const entries = fs.readdirSync(dirName)
75 | .filter((entry) => {
76 | // skip if not a backed-up file
77 | return entry.endsWith(backupSuffix);
78 | });
79 |
80 | for (const entry of entries) {
81 | const entryOrg = entry.substring(0, entry.length - backupSuffix.length);
82 | const pathNameOrg = path.join(dirName, entryOrg);
83 | const pathNameBak = path.join(dirName, entry);
84 |
85 | fs.renameSync(pathNameBak, pathNameOrg);
86 | restoredFileNames.push(entryOrg);
87 | }
88 | return restoredFileNames;
89 | }
90 |
--------------------------------------------------------------------------------
/ssh-key-action.code-workspace:
--------------------------------------------------------------------------------
1 | {
2 | "folders": [
3 | {
4 | "path": "."
5 | }
6 | ],
7 | "settings": {
8 | "eslint.enable": true,
9 | "json.format.enable": true,
10 | "typescript.format.enable": true,
11 | "typescript.format.insertSpaceAfterCommaDelimiter": true,
12 | "typescript.format.insertSpaceAfterConstructor": false,
13 | "typescript.format.insertSpaceAfterFunctionKeywordForAnonymousFunctions": false,
14 | "typescript.format.insertSpaceAfterKeywordsInControlFlowStatements": false,
15 | "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis": false,
16 | "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": false,
17 | "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets": false,
18 | "typescript.format.insertSpaceAfterSemicolonInForStatements": true,
19 | "typescript.format.insertSpaceBeforeAndAfterBinaryOperators": true,
20 | "typescript.format.insertSpaceBeforeFunctionParenthesis": false,
21 | "typescript.format.placeOpenBraceOnNewLineForFunctions": true,
22 | "typescript.format.placeOpenBraceOnNewLineForControlBlocks": true,
23 | "typescript.validate.enable": true
24 | },
25 | "extensions": {
26 | "recommendations": [
27 | "EditorConfig.EditorConfig",
28 | "eg2.vscode-npm-script"
29 | ]
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es6",
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "lib": [
7 | "es6"
8 | ],
9 | "sourceMap": true,
10 | "outDir": "./dist",
11 | "rootDir": "./src",
12 |
13 | "strict": true,
14 | "noImplicitAny": true,
15 | "strictNullChecks": true,
16 | "strictFunctionTypes": true,
17 | "strictBindCallApply": true,
18 | "strictPropertyInitialization": true,
19 | "noImplicitThis": true,
20 | "alwaysStrict": true,
21 |
22 | "noUnusedLocals": true,
23 | "noUnusedParameters": true,
24 | "noImplicitReturns": true,
25 | "noFallthroughCasesInSwitch": true,
26 |
27 | "esModuleInterop": true
28 | },
29 | "exclude": ["node_modules", "**/*.test.ts"]
30 | }
31 |
--------------------------------------------------------------------------------