From 075cb36d6929f5b50e003072df801d93c1455d03 Mon Sep 17 00:00:00 2001 From: ksu Date: Thu, 4 Jun 2026 12:43:11 +0300 Subject: [PATCH 01/19] docs: add PR template Signed-off-by: ksu --- .github/pull_request_template.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..1a68db5e5 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,13 @@ +## Goal + + +## Changes +- + +## Testing + + +## Checklist +- [ ] Title is a clear sentence (≤ 70 chars) +- [ ] Commits are signed (`git log --show-signature`) +- [ ] `submissions/labN.md` updated From cbb252da3df8707bd6b3fba6a41556cc31871282 Mon Sep 17 00:00:00 2001 From: ksu Date: Thu, 4 Jun 2026 13:24:05 +0300 Subject: [PATCH 02/19] test: unsigned commit (should fail) Signed-off-by: ksu From c3d9769895764ca93ca853a803a1313ec93b0670 Mon Sep 17 00:00:00 2001 From: ksu Date: Fri, 5 Jun 2026 15:50:02 +0300 Subject: [PATCH 03/19] docs: upstream moved while you worked Signed-off-by: ksu From 28e35b34586d1f26f151d88c9ac8f26328baaadc Mon Sep 17 00:00:00 2001 From: ksu Date: Wed, 10 Jun 2026 21:50:29 +0300 Subject: [PATCH 04/19] ci(lab3): add PR-gate Signed-off-by: ksu --- .github/workflows/ci.yml | 43 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..4c2c72ac9 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,43 @@ +name: PR Gate + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +permissions: + contents: read + +jobs: + vet: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 + with: + go-version: '1.24' + - run: go vet ./... + working-directory: app + + test: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 + with: + go-version: '1.24' + - run: go test -race -count=1 ./... + working-directory: app + + lint: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 + with: + go-version: '1.24' + - uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 # v6.5.0 + with: + version: v2.5.0 + working-directory: app From 9d6e9f1205ac8db72fe980a172974f69d0d28dac Mon Sep 17 00:00:00 2001 From: ksu Date: Wed, 10 Jun 2026 22:21:32 +0300 Subject: [PATCH 05/19] debug: intentionally break test to verify CI fails Signed-off-by: ksu --- app/handlers_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/handlers_test.go b/app/handlers_test.go index 9dff2e3e5..6c68b2576 100644 --- a/app/handlers_test.go +++ b/app/handlers_test.go @@ -39,7 +39,7 @@ func TestHealth_ReportsCount(t *testing.T) { srv := newTestServer(t) _, _ = srv.store.Create("a", "") rec := do(t, srv, http.MethodGet, "/health", nil) - if rec.Code != http.StatusOK { + if rec.Code != http.StatusNotFound { t.Fatalf("status: %d", rec.Code) } var got map[string]any From ccbce4cac8313e036917c0c663ea99f8ecfb264d Mon Sep 17 00:00:00 2001 From: ksu Date: Wed, 10 Jun 2026 22:25:13 +0300 Subject: [PATCH 06/19] fix: restore correct test expectation Signed-off-by: ksu --- app/handlers_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/handlers_test.go b/app/handlers_test.go index 6c68b2576..9dff2e3e5 100644 --- a/app/handlers_test.go +++ b/app/handlers_test.go @@ -39,7 +39,7 @@ func TestHealth_ReportsCount(t *testing.T) { srv := newTestServer(t) _, _ = srv.store.Create("a", "") rec := do(t, srv, http.MethodGet, "/health", nil) - if rec.Code != http.StatusNotFound { + if rec.Code != http.StatusOK { t.Fatalf("status: %d", rec.Code) } var got map[string]any From 86ab1b58e29a5f53dc87ed3f06fee6c1b8b2c652 Mon Sep 17 00:00:00 2001 From: ksu Date: Wed, 10 Jun 2026 22:42:40 +0300 Subject: [PATCH 07/19] docs(lab3): add submission file Signed-off-by: ksu --- .github/workflows/ci.yml | 18 ++++++++++++++++-- submissions/lab3.md | 22 ++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 submissions/lab3.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4c2c72ac9..3d69d8527 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,6 +5,9 @@ on: branches: [ main ] pull_request: branches: [ main ] + paths: + - 'app/**' + - '.github/workflows/ci.yml' permissions: contents: read @@ -12,21 +15,31 @@ permissions: jobs: vet: runs-on: ubuntu-24.04 + strategy: + matrix: + go-version: ['1.23', '1.24'] + fail-fast: false steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 with: - go-version: '1.24' + go-version: ${{ matrix.go-version }} + cache: true - run: go vet ./... working-directory: app test: runs-on: ubuntu-24.04 + strategy: + matrix: + go-version: ['1.23', '1.24'] + fail-fast: false steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 with: - go-version: '1.24' + go-version: ${{ matrix.go-version }} + cache: true - run: go test -race -count=1 ./... working-directory: app @@ -37,6 +50,7 @@ jobs: - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 with: go-version: '1.24' + cache: true - uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 # v6.5.0 with: version: v2.5.0 diff --git a/submissions/lab3.md b/submissions/lab3.md new file mode 100644 index 000000000..5b97b0cac --- /dev/null +++ b/submissions/lab3.md @@ -0,0 +1,22 @@ +# Lab 3 — CI/CD Pipeline +a) Why pin the runner version (ubuntu-24.04) instead of ubuntu-latest? What breaks otherwise? +ecause ubuntu-latest changes over time. Today it points to Ubuntu 24.04. Tomorrow it may point to Ubuntu 26.04. New Ubuntu versions have different system packages, different Go versions, different libraries. Your pipeline that works today may break tomorrow without any code change. Pinning to ubuntu-24.04 makes sure every run uses the exact same environment. This is called reproducibility. + +b) Why split vet + test + lint into separate units? What would happen with one combined job? +First, they run in parallel so the total time is only as long as the longest job, not the sum of all three. Second, if lint fails but tests pass you see immediately which one broke. In one big job you only see something failed. Third, you can restart only the failed job instead of running all three again. + +c) GH path: what real attack does SHA pinning prevent? Cite the date + name of the incident from Lecture 3 +SHA pinning prevents a supply chain attack. An attacker who gains access to a GitHub Action maintainers account can overwrite a Git tag like v4 to point to malicious code. When your pipeline uses at v4 it automatically downloads the malicious version and runs it. The incident is tj-actions changed-files in March 2025. The attacker rewrote all tags to a malicious version exposing secrets from thousands of public CI runs. + +d) GH path: what is permissions: and what's the principle behind it? +Permissions controls what the GITHUB_TOKEN is allowed to do. The principle is least privilege. Give only the permissions that are absolutely needed nothing more. For example contents read means the token can read code but cannot write or delete anything. This limits damage if a malicious action runs or secrets leak. + +e) GitLab path: what's the difference between a stage and a job? What would dependencies: do that stages: doesn't? +In GitLab CI, a stage is a group of jobs that run at the same time. Stages run in order. For example, you might have a test stage and then a deploy stage. All jobs in the test stage finish before any job in the deploy stage starts. A job is a single unit of work, like running go test or building a binary. The dependencies keyword does something different. It controls which artifacts from previous jobs get downloaded into the current job. For example, a deploy job might depend on a build job and download the binary artifact. Stages controls the order of execution. Dependencies controls which data flows between jobs, even between jobs in different stages. Stages alone cannot control artifact flow. Dependencies is more granular and lets you skip downloading artifacts you do not need. + + +## Path: GitHub Actions + + +## PR link:https://github.com/linxel/DevOps-Intro/pull/3 + From 0596915a512464c15667572b0cf782bb1a1345c3 Mon Sep 17 00:00:00 2001 From: linxel <71974874+linxel@users.noreply.github.com> Date: Wed, 10 Jun 2026 22:45:06 +0300 Subject: [PATCH 08/19] Update lab3.md --- submissions/lab3.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/submissions/lab3.md b/submissions/lab3.md index 5b97b0cac..a4242aa80 100644 --- a/submissions/lab3.md +++ b/submissions/lab3.md @@ -20,3 +20,9 @@ In GitLab CI, a stage is a group of jobs that run at the same time. Stages run i ## PR link:https://github.com/linxel/DevOps-Intro/pull/3 +image + +image + +image + From 0a0063f9cbe9e42353730d83698b9aa801652712 Mon Sep 17 00:00:00 2001 From: linxel <71974874+linxel@users.noreply.github.com> Date: Wed, 10 Jun 2026 22:49:02 +0300 Subject: [PATCH 09/19] Update lab3.md --- submissions/lab3.md | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/submissions/lab3.md b/submissions/lab3.md index a4242aa80..d1dc8e915 100644 --- a/submissions/lab3.md +++ b/submissions/lab3.md @@ -1,4 +1,17 @@ # Lab 3 — CI/CD Pipeline + +## Path: GitHub Actions. +I chose GitHub Actions because my fork is on GitHub and it works without extra setup. It is also the default path recommended in the lab instructions. + +## PR link:https://github.com/linxel/DevOps-Intro/pull/3 + +image + +image + +image + + a) Why pin the runner version (ubuntu-24.04) instead of ubuntu-latest? What breaks otherwise? ecause ubuntu-latest changes over time. Today it points to Ubuntu 24.04. Tomorrow it may point to Ubuntu 26.04. New Ubuntu versions have different system packages, different Go versions, different libraries. Your pipeline that works today may break tomorrow without any code change. Pinning to ubuntu-24.04 makes sure every run uses the exact same environment. This is called reproducibility. @@ -14,15 +27,3 @@ Permissions controls what the GITHUB_TOKEN is allowed to do. The principle is le e) GitLab path: what's the difference between a stage and a job? What would dependencies: do that stages: doesn't? In GitLab CI, a stage is a group of jobs that run at the same time. Stages run in order. For example, you might have a test stage and then a deploy stage. All jobs in the test stage finish before any job in the deploy stage starts. A job is a single unit of work, like running go test or building a binary. The dependencies keyword does something different. It controls which artifacts from previous jobs get downloaded into the current job. For example, a deploy job might depend on a build job and download the binary artifact. Stages controls the order of execution. Dependencies controls which data flows between jobs, even between jobs in different stages. Stages alone cannot control artifact flow. Dependencies is more granular and lets you skip downloading artifacts you do not need. - -## Path: GitHub Actions - - -## PR link:https://github.com/linxel/DevOps-Intro/pull/3 - -image - -image - -image - From a031f4a57e36671d1c8d85b58a79b3582211ab3f Mon Sep 17 00:00:00 2001 From: ksu Date: Wed, 10 Jun 2026 22:58:59 +0300 Subject: [PATCH 10/19] ci(lab3): final with cache, matrix, path filter Signed-off-by: ksu --- .github/workflows/ci.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3d69d8527..e526b2dbf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,8 +20,8 @@ jobs: go-version: ['1.23', '1.24'] fail-fast: false steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 with: go-version: ${{ matrix.go-version }} cache: true @@ -35,8 +35,8 @@ jobs: go-version: ['1.23', '1.24'] fail-fast: false steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 with: go-version: ${{ matrix.go-version }} cache: true @@ -46,12 +46,12 @@ jobs: lint: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 with: go-version: '1.24' cache: true - - uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 # v6.5.0 + - uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 with: version: v2.5.0 working-directory: app From f1103a82a63d8faa34aadc1a1d56794f7c1a36b4 Mon Sep 17 00:00:00 2001 From: ksu Date: Wed, 10 Jun 2026 23:05:10 +0300 Subject: [PATCH 11/19] ci(lab3): add task 2 Signed-off-by: ksu --- submissions/lab3.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/submissions/lab3.md b/submissions/lab3.md index d1dc8e915..04f2e6b03 100644 --- a/submissions/lab3.md +++ b/submissions/lab3.md @@ -27,3 +27,23 @@ Permissions controls what the GITHUB_TOKEN is allowed to do. The principle is le e) GitLab path: what's the difference between a stage and a job? What would dependencies: do that stages: doesn't? In GitLab CI, a stage is a group of jobs that run at the same time. Stages run in order. For example, you might have a test stage and then a deploy stage. All jobs in the test stage finish before any job in the deploy stage starts. A job is a single unit of work, like running go test or building a binary. The dependencies keyword does something different. It controls which artifacts from previous jobs get downloaded into the current job. For example, a deploy job might depend on a build job and download the binary artifact. Stages controls the order of execution. Dependencies controls which data flows between jobs, even between jobs in different stages. Stages alone cannot control artifact flow. Dependencies is more granular and lets you skip downloading artifacts you do not need. +## Task 2 — Make It Fast and Smart + +### Optimizations applied: +1. Cache — added cache: true to actions/setup-go +2. Matrix — run vet and test on Go 1.23 and 1.24 in parallel with fail-fast: false +3. Path filter — pipeline only runs on changes to app/ or .github/workflows/ci.yml + +### Timing table: +| Scenario | Time (s) | +| Baseline | 26 | +| With cache | 23 | +| With cache + matrix | 41 | + +### Answers: + +f) Because go.sum is deterministic. The same go.sum always downloads the same modules. Build outputs depend on Go version, CPU architecture, OS, and compiler flags. Caching outputs is unreliable. + +g) With fail-fast: true, if one matrix job fails GitHub cancels all other running jobs. With fail-fast: false, all jobs run to completion even if some fail. You want false to see results for all Go versions. You want true when jobs are expensive and you want to stop early after first failure. + +h) An attacker could poison the cache with malicious code. GitHub mitigates this by isolating caches from forks. Caches from PRs in forks are not accessible to protected branches. From e1c3346f7718a9a5635f7cb54f867d595115ff24 Mon Sep 17 00:00:00 2001 From: linxel <71974874+linxel@users.noreply.github.com> Date: Wed, 10 Jun 2026 23:07:29 +0300 Subject: [PATCH 12/19] Update lab3.md --- submissions/lab3.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/submissions/lab3.md b/submissions/lab3.md index 04f2e6b03..903f6e165 100644 --- a/submissions/lab3.md +++ b/submissions/lab3.md @@ -40,10 +40,11 @@ In GitLab CI, a stage is a group of jobs that run at the same time. Stages run i | With cache | 23 | | With cache + matrix | 41 | -### Answers: - -f) Because go.sum is deterministic. The same go.sum always downloads the same modules. Build outputs depend on Go version, CPU architecture, OS, and compiler flags. Caching outputs is unreliable. - -g) With fail-fast: true, if one matrix job fails GitHub cancels all other running jobs. With fail-fast: false, all jobs run to completion even if some fail. You want false to see results for all Go versions. You want true when jobs are expensive and you want to stop early after first failure. - -h) An attacker could poison the cache with malicious code. GitHub mitigates this by isolating caches from forks. Caches from PRs in forks are not accessible to protected branches. +image + +f) Why cache go.sum-keyed inputs and not build outputs? +Because go.sum is deterministic. The same go.sum always downloads the same modules. Build outputs depend on Go version, CPU architecture, OS, and compiler flags. Caching outputs is unreliable. +g) What does fail-fast: false change in a matrix run, and when do you actually want fail-fast: true? +With fail-fast: true, if one matrix job fails GitHub cancels all other running jobs. With fail-fast: false, all jobs run to completion even if some fail. You want false to see results for all Go versions. You want true when jobs are expensive and you want to stop early after first failure. +h) What's the risk of an attacker writing a cache from a malicious PR that protected branches later read? (Hint: GH has mitigations — find the official doc on this) +An attacker could poison the cache with malicious code. GitHub mitigates this by isolating caches from forks. Caches from PRs in forks are not accessible to protected branches. From 889ed22de40a10faecaf61da6472fd08c9be1b5a Mon Sep 17 00:00:00 2001 From: linxel <71974874+linxel@users.noreply.github.com> Date: Wed, 10 Jun 2026 23:09:39 +0300 Subject: [PATCH 13/19] Update lab3.md --- submissions/lab3.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/submissions/lab3.md b/submissions/lab3.md index 903f6e165..0cfbf3049 100644 --- a/submissions/lab3.md +++ b/submissions/lab3.md @@ -34,11 +34,11 @@ In GitLab CI, a stage is a group of jobs that run at the same time. Stages run i 2. Matrix — run vet and test on Go 1.23 and 1.24 in parallel with fail-fast: false 3. Path filter — pipeline only runs on changes to app/ or .github/workflows/ci.yml -### Timing table: -| Scenario | Time (s) | -| Baseline | 26 | -| With cache | 23 | -| With cache + matrix | 41 | +| Scenario | Wall-clock | +|----------|-----------| +| Baseline (no cache, single Go version, no path filter) | 26 s | +| With cache | 23 s | +| With cache + matrix | 41 s | image From 509437f541ba1d874547c6bfd0529b3487734d71 Mon Sep 17 00:00:00 2001 From: ksu Date: Wed, 10 Jun 2026 23:12:37 +0300 Subject: [PATCH 14/19] ci(lab3): bonus - add alpine image and GOFLAGS Signed-off-by: ksu --- .github/workflows/ci.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e526b2dbf..3f85a84fa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,9 @@ permissions: jobs: vet: runs-on: ubuntu-24.04 + container: golang:${{ matrix.go-version }}-alpine + env: + GOFLAGS: -buildvcs=false strategy: matrix: go-version: ['1.23', '1.24'] @@ -30,6 +33,9 @@ jobs: test: runs-on: ubuntu-24.04 + container: golang:${{ matrix.go-version }}-alpine + env: + GOFLAGS: -buildvcs=false strategy: matrix: go-version: ['1.23', '1.24'] @@ -45,6 +51,9 @@ jobs: lint: runs-on: ubuntu-24.04 + container: golang:1.24-alpine + env: + GOFLAGS: -buildvcs=false steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 From f1e8dd715992c5eac65df8f1e787fe6fbca86278 Mon Sep 17 00:00:00 2001 From: ksu Date: Wed, 10 Jun 2026 23:17:46 +0300 Subject: [PATCH 15/19] ci(lab3): bonus - add alpine image and GOFLAGS Signed-off-by: ksu --- .github/workflows/ci.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3f85a84fa..e526b2dbf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,9 +15,6 @@ permissions: jobs: vet: runs-on: ubuntu-24.04 - container: golang:${{ matrix.go-version }}-alpine - env: - GOFLAGS: -buildvcs=false strategy: matrix: go-version: ['1.23', '1.24'] @@ -33,9 +30,6 @@ jobs: test: runs-on: ubuntu-24.04 - container: golang:${{ matrix.go-version }}-alpine - env: - GOFLAGS: -buildvcs=false strategy: matrix: go-version: ['1.23', '1.24'] @@ -51,9 +45,6 @@ jobs: lint: runs-on: ubuntu-24.04 - container: golang:1.24-alpine - env: - GOFLAGS: -buildvcs=false steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 From db02af01f84b8ccafd3b69c59d87d192b30d90cb Mon Sep 17 00:00:00 2001 From: ksu Date: Wed, 10 Jun 2026 23:21:00 +0300 Subject: [PATCH 16/19] docs(lab3): add bonus performance investigation Signed-off-by: ksu --- submissions/lab3.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/submissions/lab3.md b/submissions/lab3.md index 0cfbf3049..b6d462bdd 100644 --- a/submissions/lab3.md +++ b/submissions/lab3.md @@ -48,3 +48,24 @@ g) What does fail-fast: false change in a matrix run, and when do you actually w With fail-fast: true, if one matrix job fails GitHub cancels all other running jobs. With fail-fast: false, all jobs run to completion even if some fail. You want false to see results for all Go versions. You want true when jobs are expensive and you want to stop early after first failure. h) What's the risk of an attacker writing a cache from a malicious PR that protected branches later read? (Hint: GH has mitigations — find the official doc on this) An attacker could poison the cache with malicious code. GitHub mitigates this by isolating caches from forks. Caches from PRs in forks are not accessible to protected branches. + +## Bonus — Performance Investigation + +### Profile +From my CI runs with cache + matrix: lint 28s, test 28s/26s, vet 22s/23s. + +### Optimizations applied (estimated): +1. Alpine image — would reduce container size and startup time +2. GOFLAGS=-buildvcs=false — would skip Git version detection +3. Lint on changed files — would check only new code + +### Before/After table (estimated) +| Optimization | Before (s) | After (s) | Saving | +|--------------|-----------|----------|-------| +| Alpine image | 28 | 24 | -4 | +| GOFLAGS | 28 | 26 | -2 | +| Lint on changed files | 28 | 20 | -8 | +| **Total** | **28** | **20** | **-8** | + +### Bottleneck analysis +The lint job is slowest at 28 seconds because golangci-lint analyzes the whole codebase. The race detector in go test also adds overhead. To make it shorter, I would split tests into unit tests (fast, no race) and integration tests (slow, with race detector). My team would stop optimizing when the pipeline runs under 60 seconds. My pipeline is already under 60 seconds, so optimization is not urgent. From 2fc93e92177ff64eff287f13e076a3e87036eb95 Mon Sep 17 00:00:00 2001 From: ksu Date: Thu, 11 Jun 2026 16:39:46 +0300 Subject: [PATCH 17/19] Lab4: complete --- app/lab4-tls.pcap | Bin 0 -> 4096 bytes app/lab4-trace.pcap | Bin 0 -> 1441 bytes submissions/lab4.md | 165 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 165 insertions(+) create mode 100644 app/lab4-tls.pcap create mode 100644 app/lab4-trace.pcap create mode 100644 submissions/lab4.md diff --git a/app/lab4-tls.pcap b/app/lab4-tls.pcap new file mode 100644 index 0000000000000000000000000000000000000000..dcea0357a1e49120b018d8f9b1dcdd81e81bd9f7 GIT binary patch literal 4096 zcmb7`2UJtp7KU$flaLT4K#Iw5ofq>UgV3Zeo^N2C*^NN>_bKtKefj1&zZNR_If zv`~cr8L5Jb!eBu}5K&4%c{ej}-t)b;X3f04*3LTTtaJCb_ddDzKVw~W1#kcX{M!%! z44Mc%BwbJx00CQQ%=8~EZrY9o1_OvF?H^a6G4sJ64OWpZxYh6^{wZ^j0^jKdV(X3n`TP<@P27;_inkqyyfaXm9(I6V?mtuhHuW5o?73s_;A>yPF7c>y~y$YCBdzc!O#q5Bq-;n-+#~fDKlr*x}t~7^*?g9ha#&$ zbEf}jzeOej05h^C{g23kTZv1|T~PE)W`0dE08kE99wUPJvp$0vzJI6Bj|~L3>~M*? z%;^8!XG1+s8greQGn1D6=QLE`$-hGr+_4ds5sy+hcba}Y)l*XOXeRLw+8u~y z2GN=Rqd}+2tWEH*PxYU*9fgQTqu8OALVbn7(5y@flg6w`PZ9%op@84~e<>){|C7Q4 z9h>&QC<9CiHwZ)FVE_sTg4#j0gQxL%*%KN^KXH%Y&Nd^P>gO7&I#yi$Am%w15mYohL*fjkE3st3+%wTWf)CaGtBbE_tR=Zu!m<@F%Pu9eRC=Y_HXa z)8!b^0!Mq|Kg9blP+i9F_&9Q4QWq>EP;M4LXhN9(?0Wx(sztZkel8O+qpOe2EM+`% zed`zOhTX-9dsMFTS(5#5D?g|BeDE=F3!X2Dno|eaiOV*_Fn@d_snQ{xk=`v<)Y*D*^EL=-qxq}I^OE_Rz>Uf2iTLGkEcE7%Jt5& zhz+@Tj%onn@;GBj5ly4p|&s`CIPMPVoeK%rvt5fp7 z#U@2_Myfqf44e&+zta_gDU9nbU(u=UzbrTvbJ!}cW#E$4YE2~{nKKuGS*8ZiMuoGt z`nsaiR`e7G7Tlf<3ffmwv~D?W7Bq(0rV@|6mJh!Z@RZ`7Dfi9hzuru*URz=p)T|AYT;Chhr_Wu7et#q(G^hQ0(cv$S zlwW?`4xjsqO?~R{E7@_lrB^HAm}yfxWp_dmdTH%Rg^@^IFxp ziE|H2L7hb4$I!}|OG5SRR$8w7*CuV|3hBcwC}j!-^ftF5NQ^qPWa&Ih0es-c|deTi~iwmKR1na~z zdqU(4=;PoYydu~+(p!Ji?c}pwYvM*gw#Hz=id@keLfBPn>(1ib#Hm|X1ZtJ@*X*Mb zFrU^LwUq=Rp1lHt8{%1=!ynFegp8RQPB!fhZ%i{E%4i!m8Id<0w)rKcA1=B~+SpQZUf__mr+IKu0P6+RXX zp45%XLh-DcWko!_YS%u=E*}^Ufo<4 z>@Kmevt_$NJXWuaR3#GWu~jQDedBZI?@h#}V_W3S2cpVu$_14FbP_u{4+q&{fQ{vc%`cAC@z} z{&A5VW&c&|c#mNQZ+7grD>RqM?`$7YR@9aQ+on-hR(E`+r<NTF13kh~7;L zZ=aLdvQy4;TriNBxn4mtiWv9oQ0Y(HXuM9+y;Cp`o_m+a%cJQ*Zr*>XnnU`{33$ED zJE;U9gn#Ag^QG3)d5BrkoBB)9b}BcYZ;#*iOBpzn5N3lFNZeMQU5S3Sx-{N(xr1>) zwXfAxsHui=EywzFivqei}j}z-7hUVbLfV$jY|$P zDSL(i(sR-ay~_KOeT)!trY6 z^rSRhX!g`lwD*LQyZU6V_bCfmy3{K|Ki-GJd&SC}KIUE?>qVmbsU_>U0gFwK_7UO4 zsbhY*=;^*Qv_++@sis(OO|g>qXYixk@xdp;tdXTERV-2EMV#GrzZ6Z?q$bqsl#3;8 zOn+Z^R%jFLyn3qDCvA^HkbGS*ON6l8WHlXE%%_fN-#)d_K|GMY*-Dq@f^MKN7y6JzS`7i*;>_d{<|wKY5xFqn06r_lajs)Lk9EW zd1t#q_Xmizc<}Cyf)~M)hn?!lg9lGS{eIV$rh{My`@(~l?@RK@_w#)(-#+&a!z4l` z5g{SCc-f6_-!8_;Cd_g8qj2 zC=$LT+<15IE#^aWa{|(y2Gc^FFSF2=_K!Wuh%ErI+nFDH^3(d4H@iq2)1CvZ0CXJw z7}Z8+uB{8i#l5R!l`KOop_95=NW+?b1LXo(!Qqb)n<*Y&1 zb$uf(r{t)x?l!naZPzsH8cwrPR*DKw8q7=PtIUBlv+3FCY)a0hU?FdstPaU|Ivy3) zT!+IgNqwizG-}lAwq+XJa-H;c!*wR>513QsHH~I+Q6Vb461l}~CTb%043p6pOiYMn zx3U8n%b{DgRjqMx7p43SR*#LILH(%Fi{(PQ=vO~#atkSkTQPDSwBzsxs7K1L|Er&( zp!z>U^+5RmTd6hHO%+L|YaTN=t4#C+>AM5BX;iN@6I7Pz{iXwtD=L*0ZAH}-h2DLr zPhK*1oXZRyTNP1L6Emq!i01H4=~T>@i2PZ`5ndj9}R3>~Zh literal 0 HcmV?d00001 diff --git a/submissions/lab4.md b/submissions/lab4.md new file mode 100644 index 000000000..9deebbfce --- /dev/null +++ b/submissions/lab4.md @@ -0,0 +1,165 @@ +# Lab 4 Submission — OS & Networking + +## Task 1 — Trace a Request End-to-End + +### Annotated Packet Capture (lab4-trace.txt) + +**TCP Three-Way Handshake:** +15:56:22.941885 IP6 ::1.57714 > ::1.8080: Flags [S] - SYN (Client -> Server) +15:56:22.941913 IP6 ::1.8080 > ::1.57714: Flags [S.] - SYN-ACK (Server -> Client) +15:56:22.941931 IP6 ::1.57714 > ::1.8080: Flags [.] - ACK (Client -> Server) + +**HTTP Request (Line + JSON Body):** +15:56:22.942066 IP6 ::1.57714 > ::1.8080: Flags [P.], length 175: HTTP: POST /notes HTTP/1.1 + +POST /notes HTTP/1.1 +Host: localhost:8080 +User-Agent: curl/8.19.0 +Accept: */* +Content-Type: application/json +Content-Length: 39 + +{"title":"trace me","body":"in flight"} + +**HTTP Response (Line + JSON Body):** +15:56:22.943777 IP6 ::1.8080 > ::1.57714: Flags [P.], length 206: HTTP: HTTP/1.1 201 Created + +HTTP/1.1 201 Created +Content-Type: application/json +Date: Thu, 11 Jun 2026 12:56:22 GMT +Content-Length: 93 + +{"id":6,"title":"trace me","body":"in flight","created_at":"2026-06-11T12:56:22.943276588Z"} + +**Connection Close (FIN packets):** +15:56:22.944001 IP6 ::1.57714 > ::1.8080: Flags [F.] - FIN from client +15:56:22.944146 IP6 ::1.8080 > ::1.57714: Flags [F.] - FIN from server +15:56:22.944182 IP6 ::1.57714 > ::1.8080: Flags [.] - Final ACK + +### Five Debugging Commands Output + +#### Command 1: `ss -tlnp | grep :8080` +```bash +LISTEN 0 4096 *:8080 *:* users:(("quicknotes",pid=198320,fd=3)) +Interpretation: quicknotes process (PID 198320) is listening on port 8080 on all interfaces (*). + +#### Command 2: `ip route show` +default via 10.128.0.1 dev wlo1 proto static metric 600 +10.128.0.0/24 dev wlo1 proto kernel scope link src 10.128.0.77 metric 600 +172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 linkdown +172.18.0.0/16 dev br-2a47d25b8db2 proto kernel scope link src 172.18.0.1 linkdown +Interpretation: Default route goes through 10.128.0.1 on wlo1 interface. Docker networks present but linkdown. + +#### Command 3: `mtr -rwc 5 localhost` +Start: 2026-06-11T16:00:14+0300 +HOST: ksu Loss% Snt Last Avg Best Wrst StDev + 1.|-- localhost 0.0% 5 0.1 0.1 0.1 0.1 0.0 +Interpretation: localhost reachable with 0% packet loss, 0.1ms average latency. + +#### Command 4: `dig +short example.com @1.1.1.1` +8.6.112.0 +8.47.69.0 +Interpretation: DNS resolution works. example.com resolves to IP addresses via Cloudflare DNS (1.1.1.1). + +#### Command 5: `journalctl --user -u quicknotes -n 20 || true` + +```bash +journalctl: command not found +Interpretation: journalctl not available on this system. QuickNotes not installed as systemd service. + +####502 Error Debugging Reflection + +What would I check first if QuickNotes returned 502? + +The first thing I would check is whether the QuickNotes process is actually running and listening on port 8080 using ss -tlnp | grep 8080. + +A 502 Bad Gateway error indicates that a reverse proxy (like nginx, Caddy, or a load balancer) received an invalid response from the upstream server (QuickNotes). Unlike a 504 timeout, a 502 means the proxy could connect to the upstream server, but the response was malformed, empty, or an error. + +My debugging chain would be: + +1. ss -tlnp | grep 8080 - Is anything listening on port 8080? + +2. ps aux | grep quicknotes - Is the process still running? + +3. curl http://localhost:8080/health - Can I reach it directly? + +4. Check QuickNotes application logs for crashes or panics + +5. Check reverse proxy error logs + +The most common cause of 502 in development is that the application crashed or was never started. The ss command immediately tells me if the port is open and which process owns it. + + + + +## Task 2 — Outside-In Debugging on a Broken Deploy + +### Full Outside-In Chain + +**Step 1: Check if process is running** +```bash +$ ps -ef | grep quicknotes | grep -v grep +ksu 243640 243594 0 16:17 pts/1 00:00:00 /home/ksu/.cache/go-build/.../quicknotes +Result: Process found. PID 243640 is running. + +**Result: Process found. PID 243640 is running. +$ ss -tlnp | grep 8080 +LISTEN 0 4096 *:8080 *:* users:(("quicknotes",pid=243640,fd=3)) +Result: Port 8080 is LISTENING. Process PID 243640 owns it. + +**Step 3: Check if service is reachable +$ curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8080/health +200 +Result: Service returns HTTP 200 OK. + +**Step 4: Check firewall +$ sudo iptables -L -n +iptables not available +Result: No firewall blocking. + +**Step 5: Check DNS resolution +$ dig +short localhost +127.0.0.1 +Result: localhost resolves to 127.0.0.1 correctly. + + +Root Cause +bind: address already in use +Two instances tried to use port 8080 at the same time. First instance got the port. Second instance failed to bind. + +### Mini-Postmortem + +Two QuickNotes processes tried to bind to port 8080 at the same time. The first process got the port and started normally. The second process crashed with error "bind: > + +The systemic issue is lack of coordination between processes for port allocation. Each process assumes port 8080 is free and tries to bind immediately without checking > + +Tooling that could prevent this failure: a process supervisor like systemd would run only one instance of the service. A pre-flight check using `lsof -i :8080` would de> + +This failure is not the operator's fault. Starting two processes should not crash. The system should handle port conflicts in a graceful way. + + + +## Bonus Task — TLS Handshake Decode + +### ClientHello +From curl output: +TLSv1.3 (OUT), TLS handshake, Client hello (1): + +ALPN: curl offers h2,http/1.1 + +### ServerHello +TLSv1.3 (IN), TLS handshake, Server hello (2): + +TLSv1.3 (IN), TLS handshake, Certificate (11): + +SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 + +### Certificate Chain +openssl s_client -connect localhost:8443 -showcerts + +Certificate level 0: Public key EC/prime256v1 +Certificate level 1: CN=Caddy Local Authority - ECC Intermediate + +### Which negotiation step kills TLS 1.0/1.1 in 2026? + +The ServerHello step kills TLS 1.0 and 1.1. Client sends ClientHello with supported versions including old ones. Server checks its configuration, sees TLS 1.0/1.1 disabled, and responds with ServerHello using TLS 1.2 or 1.3. If client only supports old versions, server sends fatal alert "protocol_version". Caddy with tls internal uses TLS 1.3 by default, so older versions never appear in handshake. From 33bda9fbe203cbe5f7bf044c2812fc0f68176942 Mon Sep 17 00:00:00 2001 From: ksu Date: Thu, 11 Jun 2026 17:05:44 +0300 Subject: [PATCH 18/19] remove lab3 from feature/lab4 branch --- submissions/lab3.md | 71 --------------------------------------------- 1 file changed, 71 deletions(-) delete mode 100644 submissions/lab3.md diff --git a/submissions/lab3.md b/submissions/lab3.md deleted file mode 100644 index b6d462bdd..000000000 --- a/submissions/lab3.md +++ /dev/null @@ -1,71 +0,0 @@ -# Lab 3 — CI/CD Pipeline - -## Path: GitHub Actions. -I chose GitHub Actions because my fork is on GitHub and it works without extra setup. It is also the default path recommended in the lab instructions. - -## PR link:https://github.com/linxel/DevOps-Intro/pull/3 - -image - -image - -image - - -a) Why pin the runner version (ubuntu-24.04) instead of ubuntu-latest? What breaks otherwise? -ecause ubuntu-latest changes over time. Today it points to Ubuntu 24.04. Tomorrow it may point to Ubuntu 26.04. New Ubuntu versions have different system packages, different Go versions, different libraries. Your pipeline that works today may break tomorrow without any code change. Pinning to ubuntu-24.04 makes sure every run uses the exact same environment. This is called reproducibility. - -b) Why split vet + test + lint into separate units? What would happen with one combined job? -First, they run in parallel so the total time is only as long as the longest job, not the sum of all three. Second, if lint fails but tests pass you see immediately which one broke. In one big job you only see something failed. Third, you can restart only the failed job instead of running all three again. - -c) GH path: what real attack does SHA pinning prevent? Cite the date + name of the incident from Lecture 3 -SHA pinning prevents a supply chain attack. An attacker who gains access to a GitHub Action maintainers account can overwrite a Git tag like v4 to point to malicious code. When your pipeline uses at v4 it automatically downloads the malicious version and runs it. The incident is tj-actions changed-files in March 2025. The attacker rewrote all tags to a malicious version exposing secrets from thousands of public CI runs. - -d) GH path: what is permissions: and what's the principle behind it? -Permissions controls what the GITHUB_TOKEN is allowed to do. The principle is least privilege. Give only the permissions that are absolutely needed nothing more. For example contents read means the token can read code but cannot write or delete anything. This limits damage if a malicious action runs or secrets leak. - -e) GitLab path: what's the difference between a stage and a job? What would dependencies: do that stages: doesn't? -In GitLab CI, a stage is a group of jobs that run at the same time. Stages run in order. For example, you might have a test stage and then a deploy stage. All jobs in the test stage finish before any job in the deploy stage starts. A job is a single unit of work, like running go test or building a binary. The dependencies keyword does something different. It controls which artifacts from previous jobs get downloaded into the current job. For example, a deploy job might depend on a build job and download the binary artifact. Stages controls the order of execution. Dependencies controls which data flows between jobs, even between jobs in different stages. Stages alone cannot control artifact flow. Dependencies is more granular and lets you skip downloading artifacts you do not need. - -## Task 2 — Make It Fast and Smart - -### Optimizations applied: -1. Cache — added cache: true to actions/setup-go -2. Matrix — run vet and test on Go 1.23 and 1.24 in parallel with fail-fast: false -3. Path filter — pipeline only runs on changes to app/ or .github/workflows/ci.yml - -| Scenario | Wall-clock | -|----------|-----------| -| Baseline (no cache, single Go version, no path filter) | 26 s | -| With cache | 23 s | -| With cache + matrix | 41 s | - -image - -f) Why cache go.sum-keyed inputs and not build outputs? -Because go.sum is deterministic. The same go.sum always downloads the same modules. Build outputs depend on Go version, CPU architecture, OS, and compiler flags. Caching outputs is unreliable. -g) What does fail-fast: false change in a matrix run, and when do you actually want fail-fast: true? -With fail-fast: true, if one matrix job fails GitHub cancels all other running jobs. With fail-fast: false, all jobs run to completion even if some fail. You want false to see results for all Go versions. You want true when jobs are expensive and you want to stop early after first failure. -h) What's the risk of an attacker writing a cache from a malicious PR that protected branches later read? (Hint: GH has mitigations — find the official doc on this) -An attacker could poison the cache with malicious code. GitHub mitigates this by isolating caches from forks. Caches from PRs in forks are not accessible to protected branches. - -## Bonus — Performance Investigation - -### Profile -From my CI runs with cache + matrix: lint 28s, test 28s/26s, vet 22s/23s. - -### Optimizations applied (estimated): -1. Alpine image — would reduce container size and startup time -2. GOFLAGS=-buildvcs=false — would skip Git version detection -3. Lint on changed files — would check only new code - -### Before/After table (estimated) -| Optimization | Before (s) | After (s) | Saving | -|--------------|-----------|----------|-------| -| Alpine image | 28 | 24 | -4 | -| GOFLAGS | 28 | 26 | -2 | -| Lint on changed files | 28 | 20 | -8 | -| **Total** | **28** | **20** | **-8** | - -### Bottleneck analysis -The lint job is slowest at 28 seconds because golangci-lint analyzes the whole codebase. The race detector in go test also adds overhead. To make it shorter, I would split tests into unit tests (fast, no race) and integration tests (slow, with race detector). My team would stop optimizing when the pipeline runs under 60 seconds. My pipeline is already under 60 seconds, so optimization is not urgent. From b47636d7d34981ae4143e31e2523993593b3a46f Mon Sep 17 00:00:00 2001 From: linxel <71974874+linxel@users.noreply.github.com> Date: Thu, 11 Jun 2026 17:10:17 +0300 Subject: [PATCH 19/19] Update lab4.md --- submissions/lab4.md | 64 ++++++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/submissions/lab4.md b/submissions/lab4.md index 9deebbfce..c46c95168 100644 --- a/submissions/lab4.md +++ b/submissions/lab4.md @@ -4,12 +4,12 @@ ### Annotated Packet Capture (lab4-trace.txt) -**TCP Three-Way Handshake:** + TCP Three-Way Handshake: 15:56:22.941885 IP6 ::1.57714 > ::1.8080: Flags [S] - SYN (Client -> Server) 15:56:22.941913 IP6 ::1.8080 > ::1.57714: Flags [S.] - SYN-ACK (Server -> Client) 15:56:22.941931 IP6 ::1.57714 > ::1.8080: Flags [.] - ACK (Client -> Server) -**HTTP Request (Line + JSON Body):** + HTTP Request (Line + JSON Body): 15:56:22.942066 IP6 ::1.57714 > ::1.8080: Flags [P.], length 175: HTTP: POST /notes HTTP/1.1 POST /notes HTTP/1.1 @@ -21,7 +21,7 @@ Content-Length: 39 {"title":"trace me","body":"in flight"} -**HTTP Response (Line + JSON Body):** + HTTP Response (Line + JSON Body): 15:56:22.943777 IP6 ::1.8080 > ::1.57714: Flags [P.], length 206: HTTP: HTTP/1.1 201 Created HTTP/1.1 201 Created @@ -31,43 +31,47 @@ Content-Length: 93 {"id":6,"title":"trace me","body":"in flight","created_at":"2026-06-11T12:56:22.943276588Z"} -**Connection Close (FIN packets):** + Connection Close (FIN packets): 15:56:22.944001 IP6 ::1.57714 > ::1.8080: Flags [F.] - FIN from client 15:56:22.944146 IP6 ::1.8080 > ::1.57714: Flags [F.] - FIN from server 15:56:22.944182 IP6 ::1.57714 > ::1.8080: Flags [.] - Final ACK ### Five Debugging Commands Output -#### Command 1: `ss -tlnp | grep :8080` -```bash -LISTEN 0 4096 *:8080 *:* users:(("quicknotes",pid=198320,fd=3)) -Interpretation: quicknotes process (PID 198320) is listening on port 8080 on all interfaces (*). +Command 1: ss -tlnp | grep :8080 -#### Command 2: `ip route show` -default via 10.128.0.1 dev wlo1 proto static metric 600 -10.128.0.0/24 dev wlo1 proto kernel scope link src 10.128.0.77 metric 600 -172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 linkdown +LISTEN 0 4096 *:8080 : users:(("quicknotes",pid=198320,fd=3)) + +Interpretation: quicknotes listening on port 8080. +Command 2: ip route show + +default via 10.128.0.1 dev wlo1 proto static metric 600 +10.128.0.0/24 dev wlo1 proto kernel scope link src 10.128.0.77 metric 600 +172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 linkdown 172.18.0.0/16 dev br-2a47d25b8db2 proto kernel scope link src 172.18.0.1 linkdown -Interpretation: Default route goes through 10.128.0.1 on wlo1 interface. Docker networks present but linkdown. -#### Command 3: `mtr -rwc 5 localhost` +Interpretation: Default route via 10.128.0.1 on wlo1. +Command 3: mtr -rwc 5 localhost + Start: 2026-06-11T16:00:14+0300 -HOST: ksu Loss% Snt Last Avg Best Wrst StDev - 1.|-- localhost 0.0% 5 0.1 0.1 0.1 0.1 0.0 -Interpretation: localhost reachable with 0% packet loss, 0.1ms average latency. +HOST: ksu Loss% Snt Last Avg Best Wrst StDev +1.|-- localhost 0.0% 5 0.1 0.1 0.1 0.1 0.0 + +Interpretation: localhost reachable. +Command 4: dig +short example.com @1.1.1.1 -#### Command 4: `dig +short example.com @1.1.1.1` 8.6.112.0 8.47.69.0 -Interpretation: DNS resolution works. example.com resolves to IP addresses via Cloudflare DNS (1.1.1.1). -#### Command 5: `journalctl --user -u quicknotes -n 20 || true` +Interpretation: DNS works. +Command 5: journalctl --user -u quicknotes -n 20 || true + +journalctl: command not found + +Interpretation: no journalctl. -```bash -journalctl: command not found -Interpretation: journalctl not available on this system. QuickNotes not installed as systemd service. -####502 Error Debugging Reflection +###502 Error Debugging Reflection What would I check first if QuickNotes returned 502? @@ -96,28 +100,27 @@ The most common cause of 502 in development is that the application crashed or w ### Full Outside-In Chain -**Step 1: Check if process is running** -```bash + Step 1: Check if process is running $ ps -ef | grep quicknotes | grep -v grep ksu 243640 243594 0 16:17 pts/1 00:00:00 /home/ksu/.cache/go-build/.../quicknotes Result: Process found. PID 243640 is running. -**Result: Process found. PID 243640 is running. + Result: Process found. PID 243640 is running. $ ss -tlnp | grep 8080 LISTEN 0 4096 *:8080 *:* users:(("quicknotes",pid=243640,fd=3)) Result: Port 8080 is LISTENING. Process PID 243640 owns it. -**Step 3: Check if service is reachable + Step 3: Check if service is reachable $ curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8080/health 200 Result: Service returns HTTP 200 OK. -**Step 4: Check firewall + Step 4: Check firewall $ sudo iptables -L -n iptables not available Result: No firewall blocking. -**Step 5: Check DNS resolution + Step 5: Check DNS resolution $ dig +short localhost 127.0.0.1 Result: localhost resolves to 127.0.0.1 correctly. @@ -163,3 +166,4 @@ Certificate level 1: CN=Caddy Local Authority - ECC Intermediate ### Which negotiation step kills TLS 1.0/1.1 in 2026? The ServerHello step kills TLS 1.0 and 1.1. Client sends ClientHello with supported versions including old ones. Server checks its configuration, sees TLS 1.0/1.1 disabled, and responds with ServerHello using TLS 1.2 or 1.3. If client only supports old versions, server sends fatal alert "protocol_version". Caddy with tls internal uses TLS 1.3 by default, so older versions never appear in handshake. +image