Releases: airvzxf/ftp-deployment-action
Release list
v2.2.0
Four PRs on top of v2.1.0. No breaking change.
Added
-
PR #52 (
feat(observability)) — script hardening + log
ergonomics:- Shebang is now
#!/bin/sh -euand the script enables
pipefailimmediately after. Catches silent typos in
variable names (set -u) and a failing command in a
pipeline (pipefail). ::add-mask::forINPUT_PASSWORD,INPUT_USERand
INPUT_SERVERso even future log lines that echo the
value will be redacted in the rendered log.::group::/::endgroup::around the four heavy
log phases (Inputs received, Resolved configuration,
Upload, final result) so the GitHub Actions UI can
collapse them.
- Shebang is now
-
PR #53 (
chore(devx)) — developer-experience polish:.github/dependabot.ymlwith weekly scans for both
dockerandgithub-actionsecosystems. lftp bumps are
restricted to patches; actions are grouped per major.- ASCII flow diagram of
init.shadded to the README. Makefilewithlint,test,build,run,release
andcleantargets (all no-op if the underlying tool
is not installed).
-
PR #59 (
feat:) — error classification and log
capture:- Permanent lftp errors (530 login, 550 permission, 550
no such file) are detected in the captured output and
abort the retry loop early, saving minutes of waiting
on a backoff that would never succeed. - Every lftp invocation's combined stdout+stderr is
written to a timestamped log file at
~/.lftp-logs/run-<UTC>.log. The path is exposed as
thelog_fileaction output (via$GITHUB_OUTPUT)
so a follow-up step can attach it as a workflow
artifact, and it is printed in the failure banner.
- Permanent lftp errors (530 login, 550 permission, 550
-
PR #60 (
feat:) —dry_runinput (defaultfalse).
When set totrue, the mirror command gets lftp's
--dry-runflag, the plan is reported without any
transfer or delete, and the final success banner switches
to FTP DRY RUN COMPLETED (no files transferred) so a
casual reader cannot mistake a dry run for a real upload.
Long-standing TODO in the README.
Changed
- Relicense to AGPL-3.0 (commit
f9bfc80). The
project moves from GPL-3.0 to the GNU Affero General
Public License v3.0 to add the network use clause
(section 13 of the AGPL), ensuring the source remains
available to users interacting with the software over a
network. No code change; onlyLICENSEis updated.
v2.1.0
Bundles three things: the release pipeline (already on main),
the digest pinning of the base image (also already on main
under [Unreleased]), and two follow-up PRs:
- PR #50 (
fix(b02,b18)) — quick wins + docs. - PR #51 (
feat:) — deprecation warning for EOL /@latest/
@master/ older v1.x refs.
Added
-
Deprecation warning (PR #51). At the very start of
init.sh,
the action inspects$GITHUB_ACTION_REFand emits a
::warning::,::notice::, or::error::workflow command:Ref Severity Reason @latestwarningmoving target, pin to @v2or@<sha>@master/@mainwarningdevelopment branch, use a tag v1.0-alpha.1, v1.0-alpha.2 warningEOL (see SECURITY.md: only v1.4+ supported) v1.1, v1.2.0 warningEOL v1.3.0 … v1.3.3 warningEOL v1.4 … v1.9 noticev2 is available (BREAKING: ssl_verify_certificatedefault flipped totrue)v2.x, anything not in the list (silent) current line The actual image version is baked into
/app/VERSIONat build
time (ARG VERSION=<tag>fromrelease.yml, defaultdevfor
local builds) and shown in the warning text.Strategy: hardcoded EOL list. No network call, no latency, no
rate limit. Updated on each major-line cut (next cut: v3, when
v2 becomes EOL). -
fail_on_deprecatedinput (PR #51, defaultfalse). When
trueand the ref is EOL, the warning is upgraded to
::error::and the action exits 1. Other warnings stay
advisory. Useful for orgs with a "no EOL versions" policy. -
Troubleshooting section in the README (PR #50). Five most
common lftp errors (530, 550, TLS verify failed, connection
refused, mirror access failed) with cause + fix in tabular form. -
Header badges in the README (PR #50): CI status, latest
release, license. -
.github/workflows/release.yml(already onmainunder
[Unreleased]): on every pushedv*.*.*tag (and via manual
workflow_dispatchwith a tag input), the workflow:- builds the Docker image with
docker/build-push-action@v6
using GitHub Actions cache (type=gha), - pushes the result to
ghcr.io/airvzxf/ftp-deployment-action
with tags<version>andv<version>plus OCI image labels
(source, license, version), - generates a CycloneDX SBOM with
anchore/sbom-action@v0and
attaches it to the image as an in-toto attestation via
actions/attest@v4, - signs the image with
cosign sign --yes(keyless, OIDC).
- builds the Docker image with
-
CI and release workflows use
actions/checkout@v5(was v4).
v5 targets Node 24, which silences the deprecation warning
every push produced since Node 20 was deprecated on the GH
Actions runners. No user-facing behaviour change.
Fixed
-
B-02 (PR #50):
max_retries=0now means "retry forever"
(the only exits are lftp success or the 5h global timeout). The
README andaction.ymldescription already implied this but
init.shtreated 0 as a single attempt. The loop guard now
skips the counter check whenINPUT_MAX_RETRIESis 0.
Non-breaking for every caller that uses the default
max_retries=10; only callers that explicitly passed
max_retries=0see the change. -
B-13 (already on
mainunder[Unreleased]): Dockerfile
now pins the base image by digest
(alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659,
the digest ofalpine:3.23.3at the time of v2.0.1). The
package versions are pinned too (lftp=4.9.2-r9,
ca-certificates=20260611-r0), which resolves hadolint
DL3018. Bumping the base image is now a controlled cadence
done via the release pipeline; the next bump will need a digest
refresh in the same commit that bumps the tag. -
B-18 (PR #50): README examples now use
@v2instead of
@latest, with a callout recommending major-version pins over
floating tags. The@latest/@mainantipattern is now
additionally enforced from inside the action via the
deprecation warning above.
v2.0.1
What's new
Adds the release pipeline that turns every pushed v*.*.* tag into
a signed image at ghcr.io/airvzxf/ftp-deployment-action with an
attached CycloneDX SBOM, automatically. The pipeline:
- builds the Docker image with
docker/build-push-action@v6, - pushes to
ghcr.io/airvzxf/ftp-deployment-action:v<version>and
:<version>, with OCI image labels, - generates a CycloneDX SBOM with
anchore/sbom-action@v0, - signs the image with
cosign sign --yes(keyless, OIDC), - attaches the SBOM as an in-toto attestation with
actions/attest@v4.
Also pins the Dockerfile to a known-good digest
(alpine@sha256:25109184...) and pins the apk package versions,
which resolves the hadolint DL3018 warning that was open since
the first PR.
Behaviour
v2.0.1 is behaviour-preserving for end users. The action's
runs.image stays on 'Dockerfile' in action.yml (which uses
the local Dockerfile in the repo); the published
ghcr.io/airvzxf/ftp-deployment-action:v2.0.1 is a side benefit
for anyone who wants to inspect the supply chain or pull the
image outside of the action context.
The action's input contract is unchanged from v2.0.0. The next
v2.x.y tag will go through the same pipeline automatically.
Verified
ghcr.io/airvzxf/ftp-deployment-action:v2.0.1is published.cosign verify --certificate-identity-regexp 'https://github.com/airvzxf/ftp-deployment-action' --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' ghcr.io/airvzxf/ftp-deployment-action:v2.0.1succeeds.- The SBOM is downloadable from the workflow run as
sbom.cyclonedx.json(CycloneDX 1.6, 289 components). - The build workflow run is
https://github.com/airvzxf/ftp-deployment-action/actions/runs/28730866556.
Assets
- Image:
ghcr.io/airvzxf/ftp-deployment-action:v2.0.1 - Source: https://github.com/airvzxf/ftp-deployment-action/tree/v2.0.1
- CHANGELOG: https://github.com/airvzxf/ftp-deployment-action/blob/v2.0.1/CHANGELOG.md
- SBOM: download from the workflow artifacts
v2.0.0
⚠️ Breaking change
The default of ssl_verify_certificate is flipped from "false" to
"true". The action now refuses to connect to a server with an
invalid, expired, or hostname-mismatched certificate. The previous
default made Man-in-the-Middle attacks trivial; the new default
makes certificate validation safe-by-default.
Migration
If you connect to a server with a self-signed certificate or a
direct IP address, you must opt back into the v1.x behaviour
explicitly:
- uses: airvzxf/ftp-deployment-action@v2
with:
server: ${{ secrets.FTP_SERVER }}
user: ${{ secrets.FTP_USERNAME }}
password: ${{ secrets.FTP_PASSWORD }}
ssl_verify_certificate: 'false' # opt back into v1.x behaviourIf you want to stay on the v1.x line forever and never see this
default flip, pin to the v1 major-version ref:
- uses: airvzxf/ftp-deployment-action@v1 # currently v1.5.0Note on direct-IP connections: lftp cannot validate a
hostname against an IP-address certificate, so
ssl_verify_certificate: truerequires both a valid certificate
and a hostname (not a bare IP) in theserverinput.
What ships in v2.0.0
Beyond the breaking default flip, v2.0.0 includes two prior
releases worth of backward-compatible work that landed on master
before this tag:
-
v1.4.0 — P0 quality fixes (PR #40):
mirror_verboseand
lftp_settingsare now real inputs, integer inputs are validated
with clear error messages, retry backoff is exponential with
jitter, a hard 5-hour timeout wraps thelftpcall, input values
are no longer echoed to the log by default, exit codes are
documented. CI workflow with shellcheck / actionlint / hadolint
/ contract test / smoke test on every push and PR. -
v1.5.0 — Security hardening (PR #42): the container now
runs as the unprivilegedlftpuser, the password is written
to~/.netrcwith mode0600and removed via anEXITtrap
(no longer inargv),local_dir/remote_dirare validated
against path-traversal and shell-metacharacter injection, and
lftp_settingsis lightly sanitised (rejects!, backtick,
dollar, control characters, more than three;-chained
directives).
Both v1.4.0 and v1.5.0 are behaviour-preserving — no input default
changes, no new required inputs, no new required configuration.
The only v2.0.0 behaviour change is the SSL default.
Assets
v1.3.3
Improve LFTP options. Before the SSL allow was false, it meant that the password of the FTP and files was unencrypted. It changed to true, but the SSL verification certification was turned to false to avoid the error and warnings during the connection and operations. Some notes were added to the README file for this security warning.
Full Changelog: v1.3.2...v1.3.3
v1.3.2
What's Changed
- [Snyk] Security upgrade alpine from 3.18.5 to 3.23.2 by @airvzxf in #37
- [Snyk] Security upgrade alpine from 3.23.2 to 3.23.3 by @airvzxf in #38
Full Changelog: v1.3.1...v1.3.2
v1.3.0
Focused on
- Security scans and fixes.
- Updated and fixed the documentation (README.md).
- Upgraded parameters:
- Some default values changed.
- Displayed logs when the mirror is executing.
- Added the
lftp_settingsto add any settings for thelftpcommand.
What's Changed
- [Snyk] Security upgrade alpine from latest to 3.15 by @snyk-bot in #4
- [Snyk] Security upgrade alpine from latest to 3.15.4 by @snyk-bot in #5
- [Snyk] Security upgrade alpine from 3.15.4 to 3.15.8 by @snyk-bot in #7
- [Snyk] Security upgrade alpine from 3.15.8 to 3.16.7 by @airvzxf in #9
Full Changelog: v1.2.0...v1.3.0
v1.2.0
When the connection returns an error, it will wait one minute before the next try.
Full Changelog: v1.1...v1.2.0
v1.1
This release version contain the follow improvements:
- Fixed the infinite loop if an error occurred, describing the solution in the next bullet.
- Added more
lftpsettings for retries and timeouts to customize your deployment. - If the
lftphas an error connection for any reason, it will try to execute a couple more times. - Added the
no-symlinksoption.
TODOs completed:
- Add the property/option for the upload the Symlinks.
Alpha 2
Added more information in the Read Me file also modified the title and description for this action.