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
80 changes: 28 additions & 52 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,60 +2,36 @@

## Unreleased

- Archive home directories of deleted users in the data directory
([#9](https://github.com/jonasehrlich/octosync/pull/9))
- Only delete users whose GitHub ID is missing from the fetched org member list. Users whose
processing failed stay in the store unchanged.
- Departed members are now locked out instead of immediately deleted: octosync expires their
account, ends their sessions, removes their crontab and queued `at` jobs, and strips supplementary
groups while keeping the account and home directory intact. Rejoining reverses the expiry and
restores access to the same files and IDs. The `delete` command now follows this reversible
departure flow as well. ([#25](https://github.com/jonasehrlich/octosync/pull/25))
- Expired accounts are permanently purged after a configurable retention period (180 days by
default), either during sync or with the new `purge` command. Purging removes the home directory
without an archive, but reserves the departed member's UID and GID for a later rejoin.
([#25](https://github.com/jonasehrlich/octosync/pull/25))
- Sync no longer offboards members because their processing or key fetch failed, and refuses a sync
that would expire every stored user. Dry runs no longer write preview state to `users.json`.
([#15](https://github.com/jonasehrlich/octosync/pull/15),
[#17](https://github.com/jonasehrlich/octosync/pull/17),
[#25](https://github.com/jonasehrlich/octosync/pull/25))
- Account identity is preserved across departures, manual account removal and rejoins. octosync
restores stored UIDs and GIDs, prevents their reuse by other members, and refuses unsafe login or
ID collisions rather than modifying the wrong account.
([#19](https://github.com/jonasehrlich/octosync/pull/19),
[#22](https://github.com/jonasehrlich/octosync/pull/22),
[#25](https://github.com/jonasehrlich/octosync/pull/25))
- GitHub SSH keys are now fetched through the authenticated client and updated inside an
octosync-managed block, preserving manually managed keys. Existing keys survive fetch failures,
and updates are atomic and protected against unsafe permissions and symlinks.
([#15](https://github.com/jonasehrlich/octosync/pull/15))
- A failed authorized keys fetch no longer fails user processing which could lead to the deletion of
users whose keys could not be fetched. ([#15](https://github.com/jonasehrlich/octosync/pull/15))
- Fetch public keys through the authenticated GitHub client instead of the anonymous, rate-limited
endpoint ([#15](https://github.com/jonasehrlich/octosync/pull/15))
- Manage SSH fetched keys in a marked, octosync-managed block inside `authorized_keys`
([#15](https://github.com/jonasehrlich/octosync/pull/15))
- Write `authorized_keys` atomically, never follow symlinks and enforce exact permissions on `.ssh`
and `authorized_keys` ([#15](https://github.com/jonasehrlich/octosync/pull/15))
- Refuse a sync that would delete every stored user
([#17](https://github.com/jonasehrlich/octosync/pull/17))
- Implement mapping GitHub teams to Linux groups with `--group <gh-team-slug>:<linux-group>`
- GitHub teams can now be mapped to Linux groups with `--group <gh-team-slug>:<linux-group>`.
([#20](https://github.com/jonasehrlich/octosync/pull/20))
- Re-create stored users whose account disappeared from the system, passing the stored name and UID
to `useradd` so file ownership survives a delete and re-create cycle
([#19](https://github.com/jonasehrlich/octosync/pull/19))
- Refuse to update a user whose stored UID or name belongs to a different account, instead of
renaming that account and handing it the GitHub user's SSH keys
([#19](https://github.com/jonasehrlich/octosync/pull/19))
- Kill a user's processes a second time right before `userdel`, so a process spawned while the home
directory was archived cannot fail the deletion
([#19](https://github.com/jonasehrlich/octosync/pull/19))
- Stop user processes gracefully: send SIGTERM first and SIGKILL only the processes still running
after a grace period, then wait for the killed processes to leave the process table
([#19](https://github.com/jonasehrlich/octosync/pull/19))
- Expire an account at the day before its deletion starts, so no new SSH session can begin while the
home directory is archived. Syncing a user whose account survived a failed deletion lifts an
expiry that is already in effect. A future expiry date set by an operator stays.
([#19](https://github.com/jonasehrlich/octosync/pull/19))
- Store `users.json` in schema v2: users deleted by octosync are kept as tombstones under an
`archived` key, so a member who leaves and rejoins gets their old UID back. The v1 file is backed
up to `users-v1.json` once before the first v2 save.
([#22](https://github.com/jonasehrlich/octosync/pull/22))
- Save the tombstone before `userdel` runs and re-enqueue archived users whose account still exists
on every sync, so a deletion interrupted at any point is finished by a later sync instead of
orphaning a live account ([#22](https://github.com/jonasehrlich/octosync/pull/22))
- Record the home directory archive path on the tombstone, so a rejoin can later restore it
([#22](https://github.com/jonasehrlich/octosync/pull/22))
- The `delete` command keeps the store file and writes tombstones instead of removing `users.json`,
preserving the UID memory of a full wipe ([#22](https://github.com/jonasehrlich/octosync/pull/22))
- Track the primary group GID in the store and re-create a rejoining member's private group with
`groupadd --gid <stored>` before `useradd --gid <stored>`, so group ownership of files outside the
archived home directory also survives the delete and re-create cycle. A GID that meanwhile belongs
to another group fails the re-creation loudly instead of falling back to a fresh one. Entries
migrated from a v1 store carry no GID and are backfilled on their next update.
([#22](https://github.com/jonasehrlich/octosync/pull/22))
- Never allocate a departed user's UID or GID to a brand-new member: `useradd` hands out the highest
ID in range plus one, which is exactly what deleting the highest-UID user frees. A new account
whose auto-allocated IDs collide with a tombstone is removed while still empty and re-created with
explicitly chosen free IDs. ([#22](https://github.com/jonasehrlich/octosync/pull/22))
- `users.json` is automatically migrated to a versioned v2 format that records departed and purged
members. The original v1 file is backed up once as `users-v1.json`.
([#22](https://github.com/jonasehrlich/octosync/pull/22),
[#25](https://github.com/jonasehrlich/octosync/pull/25))

## v0.3.0

Expand Down
80 changes: 0 additions & 80 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ repository = "https://github.com/jonasehrlich/octosync"

[dependencies]
anyhow = "1"
astral-tokio-tar = "0.6.4"
async-compression = { version = "0.4", features = ["gzip", "tokio"] }
atomic-write-file = "0.3.1"
bon = { version = "3.9" }
chrono = { version = "0.4.44", features = ["serde"] }
Expand Down
30 changes: 22 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,30 @@ octosync fully manages the supplementary groups of synced users. On every sync t
with the groups derived from the `--group` arguments, so memberships added through other channels
are removed.

### Home directory archives
### Departures: expiry instead of deletion

Before a user is deleted, their home directory is archived to
`<data-dir>/home-archive/<user>-<uid>-<timestamp>.tar.gz`. The archive directory and the archives
are owned by root with permissions `700` and `600` respectively, so only root can access them. If
archiving fails, the user is not deleted and the deletion is retried on the next sync.
When a member leaves the org, their account is not deleted. It is expired instead, which blocks both
password and pubkey SSH logins, running sessions are ended, their crontab and queued `at` jobs are
removed and the supplementary groups are stripped. The account and home directory stay on the
machine as the durable departure record, so a wrong departure decision is a reversible lockout: a
member who rejoins is synced back into their account with their files, UID and GID intact.

Sockets and other special files cannot be stored in a tar archive and are skipped with a warning.
Only the three most recent archives are kept per user, older ones are pruned after a successful
archive. In dry-run mode, archiving is neither performed nor validated.
Sessions are ended through logind, which logs the user out cleanly and tears down their session
scopes. On a machine without logind, such as a container, the SIGTERM/SIGKILL process sweep is the
whole mechanism and the missing system bus is not reported as an error.

Accounts that have been expired for longer than the retention period (180 days by default,
configurable with `--purge-after-days`) are purged at the end of each sync: the account and its home
directory are removed permanently and without an archive. A purge only happens when the tombstone in
the users database and the account's own shadow expiry agree on the age of the departure and the
member is absent from the fetched member list. The purge can also be run explicitly:

```sh
octosync purge --org <org-name> --app-id <app-id> --private-key <private-key.pem>
```

Even after a purge the users database remembers the member's UID and GID, so a member rejoining
later still gets their old IDs back, with an empty home directory.

## Development

Expand Down
Loading