Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,6 @@ jobs:
run: |
set -eu
sudo apt-get install -y --no-install-recommends bats >/dev/null
bats test/backup.bats
bats test/backup.bats test/clock.bats
bash test/test_release_script.sh
bash test/test_release_publish.sh
13 changes: 12 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,18 @@

Biçim [Keep a Changelog](https://keepachangelog.com/) esas alınmıştır.

## [Unreleased]
## [0.1.1] - 2026-11-06

### Eklendi
- **Saat sapması düzeltme.** chrony çalışırken saat 1 sn'den fazla sapmışsa `preview` plana `saat` ekler ve nedenini yazar; `apply` kaynaklar erişilebilir olunca (en çok 60 sn bekler) TEK SEFERLİK `chronyc makestep` yapar ve eşik altına inmesini doğrular. Kalıcı `makestep` ayarı YAZILMAZ. Ölçülemezse (erişilebilir kaynak yok) uyarır; ölçüldü ama düzelmediyse hata verir. Kapatmak için `utility_ntp_makestep=false` (varsayılan `true`). Uyarı metinleri adımlamanın etkisini (log/cron/auditd `time-change`, ssh/TOTP) söyler.
- `durum`: `sapma_ms` ve `secili_kaynak` alanları. `ntp_senkron=yes` artık chrony çalışırken seçili kaynak ve sapma <= 1 sn gerektirir (önceden yalnızca `timedatectl`'e bakılırdı; snapshot'tan geri açılan, 13 gün geride bir VM'de yanıltıcı `yes` görülmüştü).
- Birim testleri `test/clock.bats` (sahte `chronyc`), LAB testleri (sahte `chronyc` ile).

### Düzeltildi
- `apply` ve `geri_yukle` saat dilimi dosyalarını değiştirince `systemd-timedated` önbelleği eski değeri tutuyordu (`timedatectl show -p Timezone` yanlış; `date` ve `/etc/localtime` doğruydu). Artık `systemctl try-restart systemd-timedated` yapılır. Gerçek Ubuntu 26.04 VM'de ve LAB'da yeniden üretildi; düzeltme yalnız LAB'da sınandı.

### Denenmedi
- Saat adımlama gerçek VM'de DENENMEDİ: LAB konteynerinde chronyd ve saat adımlama yoktur, testler SAHTE `chronyc` ile yalnızca betik/playbook akışını sınar. Elle `chronyc makestep`'in kaynaklar Reach alınca gerçek VM'de işe yaradığı gözlendi, otomatik yol gözlenmedi.

## [0.1.0] - 2026-11-05

Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Düşük riskli, dar kapsamlı bir pakettir: ssh, ufw ve PAM'a dokunmaz, erişim

**Proxmox VE bilerek reddedilir:** Proxmox VE 9 Debian 13 tabanlıdır ve OS eşlemesinden geçerdi; bu yüzden ön kontrol `/usr/bin/pveversion` ya da `/etc/pve` varsa (`--check` dahil) "Proxmox VE v0.1 kapsam dışı" diye durur. Bu koruma sahte `pveversion` ile LAB'da test edildi, **gerçek Proxmox'ta denenmedi**.

Kapsam dışı (v0.1): Proxmox VE, Raspberry Pi, hostname, apt kaynakları, NTS, `makestep`, `LC_*`.
Kapsam dışı: Proxmox VE, Raspberry Pi, hostname, apt kaynakları, NTS, kalıcı `makestep` ayarı, `LC_*`. (Tek seferlik saat adımlama kapsamdadır, aşağıya bakın.)

## Eylemler
| Eylem | Etki | Sudo | Ne yapar |
Expand All @@ -29,6 +29,7 @@ Kapsam dışı (v0.1): Proxmox VE, Raspberry Pi, hostname, apt kaynakları, NTS,
| `utility_timezone` | string | boş | Örn. `Europe/Istanbul`. Boş = dokunma |
| `utility_locale` | string | boş | Yalnızca `LANG`, örn. `en_US.UTF-8`. Boş = dokunma. Gerekirse `locales` kurulur ve locale üretilir |
| `utility_ntp_servers` | list[string] | `[]` | En çok 8: `host`, `server host` veya `pool host` (`iburst` eklenir). Boş = dağıtım varsayılanı; dosya yazılmaz, chrony kurulmaz |
| `utility_ntp_makestep` | bool | `true` | Saat 1 sn'den fazla sapmışsa `apply` chrony çalışırken TEK SEFERLİK `chronyc makestep` yapar (kaynaklar erişilebilir olana kadar en çok 60 sn bekler). `false` = sapmaya dokunma. Kalıcı ayar yazılmaz |
| `utility_backup` | bool | `true` | `false` = yedek alınmaz (ve `geri_yukle` o değişikliği geri alamaz) |
| `utility_restore_from` | string | boş | `geri_yukle` için yedek adı (`20261105T101500Z`); boş = en son yedek |

Expand Down Expand Up @@ -71,7 +72,8 @@ LAB'ın sınırları: chrony.service konteynerde başlamaz (`ConditionVirtualiza
## Sınırlar ve v0.2 için bilinenler
- NTP kaynakları dağıtımın varsayılanlarının **yanına eklenir**; mevcut kaynakları (Ubuntu `ubuntu-ntp-pools.sources`, Debian `chrony.conf` pool satırları) silmez ve devre dışı bırakmaz. Biçim denetimi yalnızca `host`, `server host`, `pool host` kabul eder.
- v0.2 adayları (kod yok): (a) yerel NTP için dağıtım varsayılan kaynaklarını devre dışı bırakma seçeneği, (b) `prefer`, `minpoll` gibi kaynak seçenekleri.
- `durum`: chrony kurulu değilken `ntp_senkron=yes` görünebilir, çünkü `timedatectl` `systemd-timesyncd`'nin senkronunu da sayar (`ntp_servis=inactive` ile birlikte okuyun).
- `durum`: chrony kurulu değilken `ntp_senkron=yes` görünebilir, çünkü `timedatectl` `systemd-timesyncd`'nin senkronunu da sayar (`ntp_servis=inactive` ile birlikte okuyun). chrony çalışırken `yes` için seçili kaynak ve sapma <= 1 sn de gerekir; `sapma_ms` ve `secili_kaynak` alanları bunu gösterir.
- **Saat adımlama (sapmış saat).** Ölçüm: seçili kaynak varsa `chronyc tracking` farkı, yoksa erişilebilir (reach>0) kaynakların en küçük mutlak farkı. Etkisi bir zaman sıçramasıdır (log damgaları, cron, auditd `time-change`, ssh/TOTP/sertifika). chrony bu koşuda kurulacaksa `preview` sapmayı ölçemez; `apply` chrony başlayınca ölçer. Gerçek VM'de DENENMEDİ (yalnız sahte `chronyc` ile LAB ve birim testi); elle `chronyc makestep` gerçek VM'de işe yaramıştı.

## Bilinen sorunlar
- Ubuntu 26.04'te doğrudan `ansible-playbook --ask-become-pass` ile `Timeout (12s) waiting for privilege escalation prompt` alınır (sudo-rs): `-e ansible_become_exe=/usr/bin/sudo.ws` ekleyin. sshpocket menüsünün bunu kendi çözüp çözmediği doğrulanmadı. Ayrıntı: [docs/vm-turu.md](docs/vm-turu.md).
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.1.0
0.1.1
17 changes: 11 additions & 6 deletions component.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": "utility",
"version": "0.1.0",
"version": "0.1.1",
"sshpocket_api": 1,
"name": "Sunucu araçları: saat dilimi, locale, NTP",
"description": "Saat dilimini, sistem dilini (LANG) ve NTP kaynaklarını (chrony drop-in) önizleyerek, yedekleyerek ve onayla yönetir. Ubuntu 26.04 ve Debian 13; Proxmox VE kapsam dışı.",
Expand All @@ -17,8 +17,8 @@
"effects": [],
"requires_sudo": false,
"confirm": "none",
"warnings": ["Salt okunur: saat dilimi, LANG, chrony durumu ve ofsetini okur; hiçbir şeyi değiştirmez. chrony kurulu değilse ntp_servis=inactive/unknown, ofset unknown görünür."],
"outputs": { "format": "keyvalue-line", "prefix": "SSHPOCKET_RESULT utility.durum", "keys": ["timezone", "locale", "ntp_servis", "ntp_senkron", "offset_ms", "kaynak_sayisi"] }
"warnings": ["Salt okunur: saat dilimi, LANG, chrony durumu, ofseti ve gerçek sapmayı (seçili kaynak yoksa erişilebilir kaynakların farkı) okur; hiçbir şeyi değiştirmez. ntp_senkron=yes için chrony çalışırken seçili kaynak ve sapma <= 1 sn gerekir. chrony kurulu değilse ntp_servis=inactive/unknown, ofset ve sapma unknown görünür."],
"outputs": { "format": "keyvalue-line", "prefix": "SSHPOCKET_RESULT utility.durum", "keys": ["timezone", "locale", "ntp_servis", "ntp_senkron", "offset_ms", "kaynak_sayisi", "sapma_ms", "secili_kaynak"] }
},
"preview": {
"cmd": "ansible-playbook playbooks/preview.yml --check --diff",
Expand All @@ -27,7 +27,8 @@
"confirm": "none",
"warnings": [
"Önizleme yalnızca dosya farkını ve plan adlarını gösterir: timedatectl, locale-gen, paket kurulumu ve chrony yeniden başlatması --check'te çalışmaz, gösterilmez.",
"Boş bırakılan utility_timezone / utility_locale / utility_ntp_servers o alana hiç dokunulmayacağı anlamına gelir."
"Boş bırakılan utility_timezone / utility_locale / utility_ntp_servers o alana hiç dokunulmayacağı anlamına gelir.",
"chrony çalışıyorsa ve saat 1 sn'den fazla sapmışsa plana 'saat' girer: apply tek seferlik adımlar. chrony bu koşuda kurulacaksa sapma önizlemede ölçülemez."
],
"outputs": { "format": "keyvalue-line", "prefix": "SSHPOCKET_RESULT utility.preview", "keys": ["degisen", "plan"] }
},
Expand All @@ -42,7 +43,9 @@
"NTP kaynakları, dağıtımın varsayılan kaynaklarının YERİNE değil yanına eklenir (sources.d/utility.sources); chrony.conf'a dokunulmaz. Değişince chrony yeniden başlatılır.",
"Yanlış NTP kaynağının en kötü sonucu saat senkronunun kaybıdır (ssh sertifikaları, TOTP, apt 'Not live until' etkilenebilir); erişim kaybı değil.",
"auditd açıksa saat dilimi değişimi 'time-change' kaydı üretir (beklenen). ufw giden trafiği reddediyorsa (hardening_ufw_outgoing_deny) NTP için 123/udp çıkışı gerekir.",
"locale-gen ve chrony kurulumu gerekirse apt ile paket kurar (locales, chrony); geri_yukle paketleri kaldırmaz."
"locale-gen ve chrony kurulumu gerekirse apt ile paket kurar (locales, chrony); geri_yukle paketleri kaldırmaz.",
"Saat 1 sn'den fazla sapmışsa (chrony çalışıyorken ölçülür) apply kaynaklar erişilebilir olunca TEK SEFERLİK `chronyc makestep` yapar (en çok 60 sn bekler; kalıcı makestep ayarı yazılmaz). Adımlama zaman sıçramasıdır: log damgaları, cron işleri, auditd 'time-change' kaydı, ssh/TOTP/sertifika geçerliliği etkilenebilir. Kapatmak için utility_ntp_makestep=false.",
"Saat dilimi değişince systemd-timedated yeniden başlatılır (timedatectl önbelleği eski değeri tutmasın)."
],
"outputs": { "format": "keyvalue-line", "prefix": "SSHPOCKET_RESULT utility.apply", "keys": ["degisen", "plan", "yedek"] }
},
Expand All @@ -54,7 +57,8 @@
"warnings": [
"Yalnızca DOSYALARI geri yazar (/etc/localtime, /etc/timezone, /etc/default/locale, /etc/locale.gen, chrony conf.d ve sources.d); yedek yokken var olmayan dosyayı siler. utility_restore_from boşsa EN SON yedek kullanılır.",
"Kurulan paketleri (chrony, locales), üretilmiş locale'leri, servis durumunu ve geçmiş log zaman damgalarını geri ALMAZ. chrony drop-in'i değiştiyse chrony yeniden başlatılır.",
"Yedekten sonra elle yapılan değişikliklerin üzerine yazar."
"Yedekten sonra elle yapılan değişikliklerin üzerine yazar.",
"Saat dilimi dosyaları geri yazılınca systemd-timedated yeniden başlatılır. Adımlanmış saati geri ALMAZ."
],
"outputs": { "format": "keyvalue-line", "prefix": "SSHPOCKET_RESULT utility.geri_yukle", "keys": ["yedek", "geri_yuklenen", "degisen"] }
}
Expand All @@ -63,6 +67,7 @@
{ "name": "utility_timezone", "type": "string", "default": "", "group": "Zaman", "description": "Saat dilimi (örn. Europe/Istanbul). Boş = saat dilimine dokunma. /usr/share/zoneinfo altında var olmalı." },
{ "name": "utility_locale", "type": "string", "default": "", "group": "Dil", "description": "Sistem dili: yalnızca LANG (örn. en_US.UTF-8). Boş = dokunma. LC_* yönetilmez. Gerekirse locales kurulur ve locale üretilir." },
{ "name": "utility_ntp_servers", "type": "list[string]", "default": [], "group": "NTP", "description": "NTP kaynakları (en çok 8): 'host', 'server host' ya da 'pool host'; iburst otomatik eklenir. Boş = dağıtım varsayılanı (dosya yazılmaz, chrony kurulmaz). Dağıtımın varsayılan kaynakları kalır." },
{ "name": "utility_ntp_makestep", "type": "bool", "default": true, "group": "NTP", "description": "Saat 1 sn'den fazla sapmışsa apply chrony çalışırken TEK SEFERLİK 'chronyc makestep' yapar (kalıcı ayar yazılmaz). false = sapmaya dokunma." },
{ "name": "utility_backup", "type": "bool", "default": true, "group": "Yedek", "description": "Değiştirmeden önce /var/backups/utility/<tarih>/ altına yedek al. false = yedek alınmaz ve geri_yukle yeni değişikliği geri alamaz." },
{ "name": "utility_restore_from", "type": "string", "default": "", "group": "Yedek", "description": "geri_yukle için yedek adı (örn. 20261105T101500Z). Boş = en son yedek." }
]
Expand Down
2 changes: 2 additions & 0 deletions playbooks/apply.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
ansible.builtin.include_role: {name: locale, tasks_from: apply}
- name: Apply NTP
ansible.builtin.include_role: {name: ntp, tasks_from: apply}
- name: Step the clock (chrony)
ansible.builtin.include_role: {name: ntp, tasks_from: step}
- name: Show the plan
ansible.builtin.include_role: {name: common, tasks_from: result}
- name: Result line
Expand Down
16 changes: 14 additions & 2 deletions playbooks/durum.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Salt okunur durum: saat dilimi, LANG, NTP servisi/senkron/ofset/kaynak sayısı. Hiçbir şey değiştirmez, sudo gerektirmez.
# Sonuç satırı: SSHPOCKET_RESULT utility.durum timezone=… locale=… ntp_servis=… ntp_senkron=yes|no offset_ms=… kaynak_sayisi=…
# Sonuç satırı: SSHPOCKET_RESULT utility.durum timezone=… locale=… ntp_servis=… ntp_senkron=yes|no offset_ms=… kaynak_sayisi=… sapma_ms=… secili_kaynak=yes|no
- name: utility durum (salt okunur)
hosts: utility
gather_facts: false
Expand Down Expand Up @@ -52,9 +52,19 @@
failed_when: false
check_mode: false

# sapma: seçili kaynak varsa tracking, yoksa erişilebilir kaynakların en küçük farkı (utility_clock.py; ntp rolünün drift görevi)
- name: Read clock drift
ansible.builtin.include_role: {name: ntp, tasks_from: drift}

# chronyc -c tracking alanları: 1 ref-id, 2 ad, 3 katman, 4 ref zamanı, 5 sistem saati farkı (saniye, işaretli) ...
- name: Result line
vars:
utility_d_clock: "{{ utility_clock }}"
# chrony çalışıyorsa 'senkron' = timedatectl yes + seçili kaynak + sapma <= 1 sn; çalışmıyorsa (timesyncd) timedatectl'e bakılır
utility_d_synced: >-
{{ (utility_d_sync.stdout | default('') | trim == 'yes') and
(not (utility_d_clock.up | default(false)) or
(utility_d_clock.selected | default(false) and utility_d_clock.drift_s | default(none) is not none and utility_d_clock.drift_s | float <= 1.0)) }}
utility_d_offset: >-
{{ ((utility_d_tracking.stdout.split(',')[4] | float * 1000) | round(1)) if utility_d_tracking.rc == 0 and utility_d_tracking.stdout.split(',') | length > 4 else 'unknown' }}
ansible.builtin.debug:
Expand All @@ -63,6 +73,8 @@
timezone={{ (utility_d_tz.stdout | default('') | trim or 'unknown') | regex_replace('[^A-Za-z0-9_.:+-]', '_') | truncate(60, True, '') }}
locale={{ (utility_d_lang.stdout | default('') | trim or 'unset') | regex_replace('[^A-Za-z0-9_.:+-]', '_') | truncate(60, True, '') }}
ntp_servis={{ (utility_d_svc.stdout | default('') | trim or 'unknown') | regex_replace('[^A-Za-z0-9_.:+-]', '_') | truncate(60, True, '') }}
ntp_senkron={{ 'yes' if utility_d_sync.stdout | default('') | trim == 'yes' else 'no' }}
ntp_senkron={{ 'yes' if utility_d_synced | bool else 'no' }}
offset_ms={{ utility_d_offset | string | regex_replace('[^A-Za-z0-9_.:+-]', '_') | truncate(60, True, '') }}
kaynak_sayisi={{ (utility_d_sources.stdout_lines | default([]) | length) if utility_d_sources.rc == 0 else 0 }}
sapma_ms={{ ((utility_d_clock.drift_s | float * 1000) | round(1)) if utility_d_clock.drift_s | default(none) is not none else 'unknown' }}
secili_kaynak={{ 'yes' if utility_d_clock.selected | default(false) else 'no' }}
7 changes: 7 additions & 0 deletions playbooks/geri_yukle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@
- name: Restore from backup
ansible.builtin.include_role: {name: common, tasks_from: restore}

# systemd-timedated saat dilimini önbelleğe alır: geri yazılan /etc/localtime'ı görmesi için yeniden başlatılır
- name: Refresh systemd-timedated
ansible.builtin.command: systemctl try-restart systemd-timedated
changed_when: false
failed_when: false
when: not ansible_check_mode and ('/etc/localtime' in utility_restore.changed_paths or '/etc/timezone' in utility_restore.changed_paths)

- name: Result line
ansible.builtin.debug:
msg: >-
Expand Down
1 change: 1 addition & 0 deletions roles/common/tasks/preflight.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
- (utility_ntp_servers | default([])) is sequence and (utility_ntp_servers | default([])) is not string
- (utility_ntp_servers | default([])) | length <= 8
- utility_backup | bool in [true, false]
- utility_ntp_makestep | default(true) | bool in [true, false]
- (utility_timezone | default('')) == '' or (utility_timezone | default('')) is match('^[A-Za-z0-9_+-]+(/[A-Za-z0-9_+-]+){0,2}$')
- (utility_locale | default('')) == '' or (utility_locale | default('')) is match('^(C|POSIX|C\.UTF-8|[a-z]{2,3}_[A-Z]{2}\.(UTF-8|ISO-8859-[0-9]{1,2}))$')
- >-
Expand Down
15 changes: 15 additions & 0 deletions roles/common/tasks/warnings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,18 @@
UYARI (utility): ufw giden trafiği varsayılan olarak reddediyor (hardening_ufw_outgoing_deny). NTP kaynakları yalnızca 123/udp çıkışı
izinliyse çalışır; yoksa kaynaklara ulaşılamaz ve saat senkronu kaybolur. NTS bu sürümde yok.
when: (utility_ntp_servers | default([])) | length > 0 and 'deny (outgoing)' in utility_ufw.stdout | default('')

- name: Warn about the clock step
ansible.builtin.debug:
msg: >-
UYARI (utility): saat {{ utility_clock.drift_s }} sn sapmalı (ölçüm: {{ utility_clock.source }}); apply chrony ile TEK SEFERLİK adımlayacak (`chronyc makestep`).
Adımlama zaman sıçraması demektir: log zaman damgaları, cron işleri, auditd 'time-change' kaydı, ssh/TOTP/sertifika geçerliliği etkilenebilir.
Sapma kalıcı bir sorunsa (kaynak yok, snapshot'tan açılış) önce kaynakları kontrol edin. Kalıcı 'makestep' ayarı YAZILMAZ.
when: "'saat' in utility_plan and utility_clock is defined"

- name: Note chrony installation and clock drift
ansible.builtin.debug:
msg: >-
BİLGİ (utility): chrony bu koşuda kurulacak/değişecek; saat sapması önizlemede ölçülemez. apply, chrony başlayınca sapmayı ölçer
ve gerekirse (>{{ utility_ntp_step_threshold_s | default(1.0) }} sn) tek seferlik adımlar.
when: "'paket' in utility_plan and (utility_ntp_makestep | default(true) | bool)"
5 changes: 5 additions & 0 deletions roles/ntp/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
# boş = dağıtım varsayılanı; dosya yazılmaz, chrony kurulmaz
utility_ntp_servers: []
# saat sapması: eşikten büyükse apply TEK SEFERLİK `chronyc makestep` yapar (kalıcı makestep ayarı yazılmaz). false = sapmaya dokunma
utility_ntp_makestep: true
# iç sabitler (manifest değişkeni değil)
utility_ntp_step_threshold_s: 1.0
utility_ntp_step_timeout_s: 60
Loading