mirror of
https://github.com/narrrl/proton-drive-linux.git
synced 2026-11-03 16:41:26 +01:00
fixed a migration bug where ProtonDrive becomse unwritable
This commit is contained in:
1 parent
77198ed261
commit
9bbd8eb982
4 files changed
+65
-9
No files matched your search
@@ -13,6 +13,7 @@ jobs:
|
||||
build-release:
|
||||
name: Build and Release
|
||||
runs-on: ubuntu-24.04
|
||||
environment: 'rpm signing'
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
@@ -30,7 +31,8 @@ jobs:
|
||||
libsecret-1-dev \
|
||||
libdbus-1-dev \
|
||||
desktop-file-utils \
|
||||
rpm
|
||||
rpm \
|
||||
rpm-sign
|
||||
|
||||
- name: Set up Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
@@ -154,6 +156,26 @@ jobs:
|
||||
# Copy built RPM to root directory
|
||||
cp rpmbuild/RPMS/x86_64/*.rpm .
|
||||
|
||||
- name: Sign Fedora package (.rpm)
|
||||
env:
|
||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
GPG_KEY_NAME: ${{ secrets.GPG_KEY_NAME }}
|
||||
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
||||
run: |
|
||||
if [ -n "$GPG_PRIVATE_KEY" ] && [ -n "$GPG_KEY_NAME" ]; then
|
||||
echo "$GPG_PRIVATE_KEY" | gpg --import
|
||||
|
||||
cat <<EOF > ~/.rpmmacros
|
||||
%_signature gpg
|
||||
%_gpg_name ${GPG_KEY_NAME}
|
||||
%__gpg_sign_cmd %{__gpg} gpg --batch --verbose --no-armor --pinentry-mode loopback --passphrase %{_gpg_passphrase} --no-secmem-warning -u "%{_gpg_name}" -sbo %{__signature_filename} --digest-algo sha256 %{__plaintext_filename}
|
||||
EOF
|
||||
|
||||
rpm --addsign --define "_gpg_passphrase $GPG_PASSPHRASE" *.rpm
|
||||
else
|
||||
echo "Skipping RPM signing: GPG_PRIVATE_KEY or GPG_KEY_NAME secret is not set."
|
||||
fi
|
||||
|
||||
|
||||
- name: Upload Workflow Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
|
||||
@@ -992,7 +992,15 @@ impl ProtonFs {
|
||||
warn!(uid = %root.uid, error = %e, "db upsert root failed");
|
||||
}
|
||||
st.by_uid.insert(root.uid.clone(), ROOT_INO);
|
||||
let access = st.access_for_node(ROOT_INO, &root);
|
||||
// The filesystem root is always owned — it must never be
|
||||
// classified as a shared root, even when the API returns
|
||||
// membership data (which happens when the owner shares the
|
||||
// folder with someone else). Clean up any stale share_access
|
||||
// row that a previous downgrade_all_share_access may have
|
||||
// written for this uid, so hydrate_access cannot pick it up.
|
||||
let access = Access::Owner;
|
||||
st.share_access.remove(&root.uid);
|
||||
let _ = st.db.delete_share_access(&root.uid);
|
||||
st.entries.insert(
|
||||
ROOT_INO,
|
||||
Entry {
|
||||
|
||||
@@ -448,8 +448,13 @@ impl State {
|
||||
let roots: Vec<u64> = self
|
||||
.entries
|
||||
.iter()
|
||||
.filter(|entry| {
|
||||
let (_, entry) = entry;
|
||||
.filter(|&(&ino, entry)| {
|
||||
// The filesystem root (parent == self) is always owned; never
|
||||
// downgrade it even if it carries membership from the owner
|
||||
// sharing this folder with someone else.
|
||||
if entry.parent == ino {
|
||||
return false;
|
||||
}
|
||||
entry.access != Access::Owner
|
||||
|| entry.node.membership.is_some()
|
||||
|| self.share_access.contains_key(&entry.uid)
|
||||
@@ -494,10 +499,12 @@ impl State {
|
||||
return Access::Unknown;
|
||||
}
|
||||
let stored = state.share_access.get(&entry.uid).copied();
|
||||
let access = if let Some(stored) = stored {
|
||||
stored
|
||||
} else if entry.parent == ino {
|
||||
// The root of this mount is always owned, regardless of any
|
||||
// stale share_access row that a global downgrade may have left.
|
||||
let access = if entry.parent == ino {
|
||||
Access::Owner
|
||||
} else if let Some(stored) = stored {
|
||||
stored
|
||||
} else if !state.entries.contains_key(&entry.parent) {
|
||||
entry
|
||||
.node
|
||||
@@ -538,8 +545,12 @@ impl State {
|
||||
}
|
||||
let discovered: Vec<(NodeUid, Access)> = self
|
||||
.entries
|
||||
.values()
|
||||
.filter_map(|entry| {
|
||||
.iter()
|
||||
.filter_map(|(&ino, entry)| {
|
||||
// The filesystem root is owned, not a discovered share root.
|
||||
if entry.parent == ino {
|
||||
return None;
|
||||
}
|
||||
let parent_access = self
|
||||
.entries
|
||||
.get(&entry.parent)
|
||||
|
||||
@@ -12,6 +12,21 @@ Conventions:
|
||||
|
||||
---
|
||||
|
||||
## B78 — Profile backup fails: Cannot create file at the root of a device
|
||||
|
||||
**Status:** Open
|
||||
**Found:** 2026-07-29, user reported `upload profile: proton api error NotEnoughPermissions (http 422): Cannot create file at the root of a device`
|
||||
|
||||
The background task that backs up the machine's profile (sync folder mappings, pins, cache budget) currently attempts to write `profile.json` directly to the device root node (`device.root_uid`) on the Drive backend. The Proton Drive API has started enforcing a restriction that files cannot be created directly at the root of a device; they must be placed inside a folder.
|
||||
|
||||
**Consequence:** The upload is rejected with HTTP 422. The daemon retries when changes happen but the profile is never successfully backed up. Local changes (pinning, adding a sync folder) still take effect locally, but they will not be restorable on a new machine.
|
||||
|
||||
**Planned Fix:** Match the original `RECOVERY.md` spec by putting it in a `.proton-drive-linux` folder within the device root.
|
||||
- `pdfs-fuse/src/profile.rs:save_profile`: Look up (or create) the `.proton-drive-linux` folder under the device root, and upload `profile.json` there instead.
|
||||
- `pdfs-fuse/src/profile.rs:load_profile`: Check for `PROFILE_FILE_NAME` inside `.proton-drive-linux` first, falling back to the device root to allow older, existing profiles to be restored until they are overwritten.
|
||||
|
||||
---
|
||||
|
||||
## B1 — FUSE rename loses the file (data loss)
|
||||
|
||||
**Status:** Fixed (verified 2026-07-20)
|
||||
|
||||
Reference in new issue
Block a user