diff --git a/backend/cmd/tera-alarmd/main.go b/backend/cmd/tera-alarmd/main.go index 8905381..8f33823 100644 --- a/backend/cmd/tera-alarmd/main.go +++ b/backend/cmd/tera-alarmd/main.go @@ -17,10 +17,12 @@ import ( ) func main() { - secrets, err := secretstore.Open(getenv("TERA_ALARMD_SECRET_STORE", "/var/lib/tera-api/secrets.json")) + secretPath := getenv("TERA_ALARMD_SECRET_STORE", "/var/lib/tera-api/secrets.json") + _, err := secretstore.Open(secretPath) if err != nil { log.Fatalf("tera-alarmd secret store setup failed: %v", err) } + secrets := secretstore.FileReader{Path: secretPath} dispatcher, err := alarmdelivery.NewDispatcher(map[string]alarmdelivery.Transport{ alarmdelivery.ChannelWeb: alarmdelivery.WebTransport{}, alarmdelivery.ChannelSyslog: alarmdelivery.SyslogTransport{}, diff --git a/backend/internal/secretstore/reader.go b/backend/internal/secretstore/reader.go new file mode 100644 index 0000000..c7b2616 --- /dev/null +++ b/backend/internal/secretstore/reader.go @@ -0,0 +1,13 @@ +package secretstore + +// FileReader resolves the current atomic snapshot written by another process. +// It deliberately has no write methods and never falls back to stale secrets. +type FileReader struct{ Path string } + +func (r FileReader) Get(key string) ([]byte, bool) { + store, err := Open(r.Path) + if err != nil { + return nil, false + } + return store.Get(key) +} diff --git a/backend/internal/secretstore/reader_test.go b/backend/internal/secretstore/reader_test.go new file mode 100644 index 0000000..71bd7a0 --- /dev/null +++ b/backend/internal/secretstore/reader_test.go @@ -0,0 +1,44 @@ +package secretstore + +import ( + "os" + "path/filepath" + "testing" +) + +func TestFileReaderObservesOtherProcessUpdatesAndFailsClosed(t *testing.T) { + path := filepath.Join(t.TempDir(), "secrets.json") + reader := FileReader{Path: path} + writer, err := Open(path) + if err != nil { + t.Fatal(err) + } + key := "running/email/password" + if _, ok := reader.Get(key); ok { + t.Fatal("unexpected initial secret") + } + for _, value := range []string{"first-app-password", "rotated-app-password"} { + if err := writer.Set(key, []byte(value)); err != nil { + t.Fatal(err) + } + got, ok := reader.Get(key) + if !ok || string(got) != value { + t.Fatal("reader did not observe current snapshot") + } + } + if err := writer.Delete(key); err != nil { + t.Fatal(err) + } + if _, ok := reader.Get(key); ok { + t.Fatal("deleted secret remained available") + } + if err := writer.Set(key, []byte("temporary")); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(path, []byte("invalid JSON"), 0600); err != nil { + t.Fatal(err) + } + if _, ok := reader.Get(key); ok { + t.Fatal("invalid snapshot reused stale secret") + } +} diff --git a/demo-guide/README.md b/demo-guide/README.md index 72f7497..3121bf3 100644 --- a/demo-guide/README.md +++ b/demo-guide/README.md @@ -202,6 +202,8 @@ Remove-NetFirewallRule -DisplayName 'TERA Demo Syslog TCP' 여기서 ‘CH01’은 장비의 **1번 포트**라는 뜻입니다. PC의 COM 번호와 다른 이름입니다. +**COM4 PuTTY에 `root@iconsole-server`가 보이면 TERA 자체 관리 콘솔입니다.** 여기서 `DEMO_ALARM_0909`를 입력하면 셸이 명령으로 해석하고, `echo DEMO_ALARM_0909`는 관리 콘솔에만 글자를 출력합니다. 둘 다 CH01~CH48의 RX 입력이 아니므로 포트 키워드 이벤트가 발생하지 않습니다. 메일 자체의 동작은 먼저 메일 화면의 **테스트 발송**으로 확인합니다. + ### 케이블을 몰라도 지금 할 수 있는 설정 시험 1. **포트 설정**을 열고 1번 포트를 선택합니다. 적용 대상도 1번만 선택합니다. diff --git a/final-build/README.md b/final-build/README.md index fa6f80c..7f89dd5 100644 --- a/final-build/README.md +++ b/final-build/README.md @@ -2,7 +2,9 @@ 릴리스: **20260910-demo-log-controls-1** · Linux ARM64 · 시연 장비: http://192.168.0.223/ -2026-09-10 프론트엔드·API·로그 데몬을 장비에 배포했습니다. 세 파일의 설치 SHA256 일치와 서비스 준비 **12/12**를 확인했습니다. 나머지 다섯 파일은 이전 최종 빌드 세트에서 유지했습니다. +2026-09-10 프론트엔드·API·로그 데몬에 이어 메일 알림 데몬을 갱신했습니다. 설치 SHA256 일치와 서비스 준비 **12/12**를 확인했습니다. 나머지 네 파일은 이전 최종 빌드 세트에서 유지했습니다. + +메일 수정: 발송 시 현재 비밀번호 저장 파일을 읽어 UI에서 새로 저장한 SMTP/Trap 자격 증명을 반영합니다. 기존 테스트 메일 재시도는 **16:29:10 KST에 delivered**로 완료됐습니다. 이 상태는 SMTP 서버의 수락이며 수신함 도착 확인과는 구분합니다. 메일 데몬의 이전 실행 파일은 `/userdata/tera-backups/alarm-live-secrets-20260910`에 보관했습니다. 이 디렉터리는 실행 파일 모음입니다. boot/rootfs를 포함한 전체 펌웨어 `.pkg`는 아닙니다. 장비 설정, 사용자 계정, 인증서 개인키와 런타임 로그는 포함하지 않습니다. @@ -14,7 +16,7 @@ | backend/tera-clone-demo-api | /usr/bin/tera-api | S50tera-api | 갱신 | | backend/tera-logd | /usr/bin/tera-logd | S44tera-logd | 갱신 | | backend/tera-timed | /usr/bin/tera-timed | S45tera-timed | 유지 | -| backend/tera-clone-demo-alarmd | /usr/bin/tera-alarmd | S47tera-alarmd | 유지 | +| backend/tera-clone-demo-alarmd | /usr/bin/tera-alarmd | S47tera-alarmd | 비밀번호 갱신 반영 수정 | | backend/tera-seriald | /usr/bin/tera-seriald | S40tera-seriald | 유지 | | backend/tera-sessiond | /usr/bin/tera-sessiond | S55tera-sessiond | 유지 | | backend/tera-netd | /usr/bin/tera-netd | S43tera-netd | 유지 | diff --git a/final-build/SHA256SUMS b/final-build/SHA256SUMS index b227c87..0cb04e8 100644 --- a/final-build/SHA256SUMS +++ b/final-build/SHA256SUMS @@ -1,5 +1,5 @@ 7616cf19151d7ce0b8afc35cad7a5d66b1168bf856036c73fe92f59f8d2fddb0 frontend/tera-clone-demo-web -4f6c0e76b6fc158521adbf028aa57c0d3d0ac1ea3aea02ea62ed45bf065be859 backend/tera-clone-demo-alarmd +ac3149f54d93ce252527606bda35dc739f1f3ad40bfb77409f16b3514d437006 backend/tera-clone-demo-alarmd 0715323afbe9a472e7cb18e26b292e60f2c1a640dc084141921f8f27c4e49406 backend/tera-clone-demo-api 5b351238c8973f2c4222f6926397ab4544bf80b43ade438ab2dba8128aaecd4c backend/tera-logd 0df3697104ebdb8bfb1d8b0869a175789fedd8bda50f41b3395dcbd7d77dfcef backend/tera-netd diff --git a/final-build/backend/tera-clone-demo-alarmd b/final-build/backend/tera-clone-demo-alarmd index 3e2abcb..b5e069f 100644 Binary files a/final-build/backend/tera-clone-demo-alarmd and b/final-build/backend/tera-clone-demo-alarmd differ diff --git a/final-build/release-manifest.json b/final-build/release-manifest.json index 0903a46..18cb2ff 100644 --- a/final-build/release-manifest.json +++ b/final-build/release-manifest.json @@ -12,10 +12,10 @@ }, { "path": "backend/tera-clone-demo-alarmd", - "bytes": 5308578, - "sha256": "4f6c0e76b6fc158521adbf028aa57c0d3d0ac1ea3aea02ea62ed45bf065be859", + "bytes": 7711921, + "sha256": "ac3149f54d93ce252527606bda35dc739f1f3ad40bfb77409f16b3514d437006", "install_path": "/usr/bin/tera-alarmd", - "updated_in_this_release": false + "updated_in_this_release": true }, { "path": "backend/tera-clone-demo-api", @@ -79,5 +79,11 @@ "frontend": "go test ./internal/handlers ./internal/mock passed", "javascript": "11 clear-control tests, 7 log-view tests and 1 serial-keyword test passed", "limitations": "No existing device logs were deleted for verification. SNMP, certificate trust, full 48-port traffic and power-loss-on-device acceptance remain demonstration procedures, not claimed completed tests." + }, + "mail_fix": { + "date": "2026-09-10", + "description": "Reload current secret snapshot for email and SNMP Trap delivery", + "backup": "/userdata/tera-backups/alarm-live-secrets-20260910", + "verified_sha256": "ac3149f54d93ce252527606bda35dc739f1f3ad40bfb77409f16b3514d437006" } }